From 37d5dd87b9ecf62688b12494c5fd2f84ee82bb65 Mon Sep 17 00:00:00 2001 From: Jonathan Moore Liles Date: Wed, 5 Jun 2013 15:58:52 -0700 Subject: [PATCH] Mixer: Implement two way mapping for OSC learning. --- lib/ntk | 2 +- mixer/src/Controller_Module.C | 34 +++++++- mixer/src/Controller_Module.H | 14 ++-- mixer/src/Mixer.C | 151 ++++++++++++++++++++++++++-------- mixer/src/Mixer.H | 9 ++ mixer/src/Mixer_Strip.C | 20 +++-- mixer/src/Mixer_Strip.H | 1 + mixer/src/Module.C | 30 +++++++ mixer/src/Module.H | 2 + nonlib/OSC/Endpoint.C | 86 ++++++++++++++++++- nonlib/OSC/Endpoint.H | 26 +++++- 11 files changed, 321 insertions(+), 54 deletions(-) diff --git a/lib/ntk b/lib/ntk index 02768c4..8ee564b 160000 --- a/lib/ntk +++ b/lib/ntk @@ -1 +1 @@ -Subproject commit 02768c459a81592abaa50c6095b3a60b1049b4fc +Subproject commit 8ee564bef320b3561e204d17c1aa054311b2ce47 diff --git a/mixer/src/Controller_Module.C b/mixer/src/Controller_Module.C index f3b79a4..a6ce1b8 100644 --- a/mixer/src/Controller_Module.C +++ b/mixer/src/Controller_Module.C @@ -45,6 +45,8 @@ // needed for mixer->endpoint #include "Mixer.H" +bool Controller_Module::_learn_mode = false; + Controller_Module::Controller_Module ( bool is_default ) : Module( is_default, 50, 100, name() ) @@ -517,6 +519,19 @@ Controller_Module::menu ( void ) return m; } +void +Controller_Module::draw ( void ) +{ + draw_box(x(),y(),w(),h()); + Fl_Group::draw(); + + + if ( learn_mode() ) + { + fl_rectf( x(),y(),w(),h(), fl_color_add_alpha( FL_MAGENTA, 50 ) ); + } +} + int Controller_Module::handle ( int m ) { @@ -525,7 +540,24 @@ Controller_Module::handle ( int m ) { case FL_PUSH: { - if ( test_press( FL_BUTTON3 ) ) + if ( learn_mode() ) + { + tooltip( "Now learning control. Move the desired control on your controller" ); + + //connect_to( &module->control_input[port] ); + Port *p = control_output[0].connected_port(); + + if ( p ) + { + DMESSAGE( "Will learn %s", p->osc_path() ); + + mixer->osc_endpoint->learn( p->osc_path() ); + } + + return 1; + } + + if ( Fl::event_button3() ) { /* context menu */ if ( type() != SPATIALIZATION ) diff --git a/mixer/src/Controller_Module.H b/mixer/src/Controller_Module.H index 9196de3..ee2efb0 100644 --- a/mixer/src/Controller_Module.H +++ b/mixer/src/Controller_Module.H @@ -40,11 +40,13 @@ class Controller_Module : public Module static void menu_cb ( Fl_Widget *w, void *v ); void menu_cb ( const Fl_Menu_ *m ); - char *_osc_path; - char *_osc_path_cv; - public: + static bool _learn_mode; + + static bool learn_mode ( void ) { return _learn_mode; } + static void learn_mode ( bool b ) { _learn_mode = b; } + enum Mode { GUI, CV, OSC, MIDI }; enum Type { KNOB, @@ -88,11 +90,7 @@ public: void process ( nframes_t nframes ); - void draw ( void ) - { - draw_box(x(),y(),w(),h()); - Fl_Group::draw(); - } + void draw ( void ); int handle ( int m ); diff --git a/mixer/src/Mixer.C b/mixer/src/Mixer.C index bb57f0b..74924d2 100644 --- a/mixer/src/Mixer.C +++ b/mixer/src/Mixer.C @@ -48,6 +48,8 @@ #include "OSC/Endpoint.H" #include +#include "Controller_Module.H" + extern char *user_config_dir; extern char *instance_name; @@ -55,11 +57,25 @@ extern char *instance_name; #include "string_util.h" #include "NSM.H" +#include extern NSM_Client *nsm; +static void +mixer_show_tooltip ( const char *s ) +{ + mixer->status( s ); +} + +static void +mixer_hide_tooltip ( void ) +{ + mixer->status( 0 ); +} + + /************************/ /* OSC Message Handlers */ /************************/ @@ -262,6 +278,18 @@ void Mixer::cb_menu(Fl_Widget* o) { fl_alert( "%s", "Failed to import strip!" ); } } + else if ( ! strcmp( picked, "&Mixer/Start Learning" ) ) + { + Controller_Module::learn_mode( true ); + status( "Now in learn mode. Click on a highlighted control to teach it something." ); + redraw(); + } + else if ( ! strcmp( picked, "&Mixer/Stop Learning" ) ) + { + Controller_Module::learn_mode( false ); + status( "Learning complete" ); + redraw(); + } else if ( !strcmp( picked, "&Mixer/Paste" ) ) { Fl::paste(*this); @@ -286,18 +314,6 @@ void Mixer::cb_menu(Fl_Widget* o) { { fl_theme_chooser(); } - else if (! strcmp( picked, "&Options/&Display/Update Frequency/15 Hz" ) ) - { - update_frequency( 15.0f ); - } - else if (! strcmp( picked, "&Options/&Display/Update Frequency/30 Hz" ) ) - { - update_frequency( 30.0f ); - } - else if (! strcmp( picked, "&Options/&Display/Update Frequency/60 Hz" ) ) - { - update_frequency( 60.0f ); - } else if ( ! strcmp( picked, "&Help/&About" ) ) { About_Dialog ab( PIXMAP_PATH "/non-mixer/icon-256x256.png" ); @@ -422,7 +438,7 @@ Mixer::load_project_settings ( void ) { reset_project_settings(); -// if ( Project::open() ) + if ( Project::open() ) ((Fl_Menu_Settings*)menubar)->load( menubar->find_item( "&Project/Se&ttings" ), "options" ); update_menu(); @@ -434,6 +450,15 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : Loggable::dirty_callback( &Mixer::handle_dirty, this ); Loggable::progress_callback( progress_cb, NULL ); + Fl_Tooltip::hoverdelay( 0 ); + Fl_Tooltip::delay( 0 ); + fl_show_tooltip = mixer_show_tooltip; + fl_hide_tooltip = mixer_hide_tooltip; + /* Fl_Tooltip::size( 11 ); */ + /* Fl_Tooltip::textcolor( FL_FOREGROUND_COLOR ); */ + /* Fl_Tooltip::color( fl_color_add_alpha( FL_DARK1, 0 ) ); */ +// fl_tooltip_docked = 1; + _rows = 1; box( FL_FLAT_BOX ); labelsize( 96 ); @@ -452,6 +477,8 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : o->add( "&Mixer/Add &N Strips" ); o->add( "&Mixer/&Import Strip" ); o->add( "&Mixer/Paste", FL_CTRL + 'v', 0, 0 ); + o->add( "&Mixer/Start Learning", FL_F + 9, 0, 0 ); + o->add( "&Mixer/Stop Learning", FL_F + 10, 0, 0 ); o->add( "&View/&Theme", 0, 0, 0 ); /* o->add( "&Options/&Display/Update Frequency/60 Hz", 0, 0, 0, FL_MENU_RADIO ); */ /* o->add( "&Options/&Display/Update Frequency/30 Hz", 0, 0, 0, FL_MENU_RADIO); */ @@ -483,18 +510,17 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : } // Fl_Blink_Button* sm_blinker o->end(); } - { Fl_Scroll *o = scroll = new Fl_Scroll( X, Y + 24, W, H - 24 ); + { Fl_Scroll *o = scroll = new Fl_Scroll( X, Y + 24, W, H - ( 24 + 18 ) ); o->box( FL_FLAT_BOX ); // o->type( Fl_Scroll::HORIZONTAL_ALWAYS ); // o->box( Fl_Scroll::BOTH ); { - Fl_Flowpack *o = mixer_strips = new Fl_Flowpack( X, Y + 24, W, H - 18 - 24 ); + Fl_Flowpack *o = mixer_strips = new Fl_Flowpack( X, Y + 24, W, H - ( 18*2 + 24 )); // label( "Non-Mixer" ); align( (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE) ); - o->flow( false ); o->box( FL_FLAT_BOX ); o->type( Fl_Pack::HORIZONTAL ); - o->hspacing( 2 ); + o->hspacing( 2 ); o->vspacing( 2 ); o->end(); Fl_Group::current()->resizable( o ); @@ -502,7 +528,12 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : o->end(); Fl_Group::current()->resizable( o ); } - + { Fl_Box *o = _status = new Fl_Box( X, Y + H - 18, W, 18 ); + o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); + o->labelsize( 10 ); + o->box( FL_FLAT_BOX ); + o->color( FL_DARK1 ); + } end(); update_frequency( 15 ); @@ -512,6 +543,45 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : load_options(); } +/* translate message addressed to strip number to appropriate strip */ +int +Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + int n; + char *rem; + + OSC::Endpoint *ep = (OSC::Endpoint*)user_data; + + DMESSAGE( "%s", path ); + + if ( 2 != sscanf( path, "/strip#/%d/%a[^\n]", &n, &rem ) ) + return -1; + + DMESSAGE( "%s", rem ); + + Mixer_Strip *o = mixer->track_by_number( n ); + + if ( ! o ) + { + DMESSAGE( "No strip by number %i", n ); + return 0; + } + + char *new_path; + + asprintf( &new_path, "/strip/%s/%s", o->name(), rem ); + + free( rem ); + + DMESSAGE( "Sending %s", new_path ); + + lo_send_message( ep->address(), new_path, msg ); + + free( new_path ); + + return 0; +} + int Mixer::init_osc ( const char *osc_port ) { @@ -569,6 +639,11 @@ void Mixer::add ( Mixer_Strip *ms ) ms->take_focus(); } +int +Mixer::find_strip ( const Mixer_Strip *m ) const +{ + return mixer_strips->find( m ); +} void Mixer::quit ( void ) @@ -584,8 +659,6 @@ Mixer::insert ( Mixer_Strip *ms, Mixer_Strip *before ) { // mixer_strips->remove( ms ); mixer_strips->insert( *ms, before ); - -// scroll->redraw(); } void Mixer::insert ( Mixer_Strip *ms, int i ) @@ -646,23 +719,26 @@ Mixer::rows ( int ideal_rows ) { int sh; - int actual_rows; + int actual_rows = 1; - /* calculate how many rows will actually fit */ - int can_fit = scroll->h() / ( Mixer_Strip::min_h() ); - - actual_rows = can_fit > 0 ? can_fit : 1; - - if ( actual_rows > ideal_rows ) - actual_rows = ideal_rows; - - /* calculate strip height */ - if ( actual_rows > 1 ) + if ( ideal_rows > 1 ) { - sh = ( scroll->h() / (float)actual_rows ) - ( mixer_strips->vspacing() * ( actual_rows - 2 )); - mixer_strips->flow(true); + sh = (scroll->h() / ideal_rows ) - (mixer_strips->vspacing() * (ideal_rows - 1)); + mixer_strips->flow( true ); + + if ( sh < Mixer_Strip::min_h() ) + { + int can_fit = ( scroll->h() - 18 ) / Mixer_Strip::min_h(); + + actual_rows = can_fit > 0 ? can_fit : 1; + } + else + actual_rows = ideal_rows; } else + actual_rows = 1; + + if ( 1 == actual_rows ) { sh = (scroll->h() - 18); mixer_strips->flow(false); @@ -707,6 +783,15 @@ Mixer::track_by_name ( const char *name ) return NULL; } +/** retrun a pointer to the track named /name/, or NULL if no track is named /name/ */ +Mixer_Strip * +Mixer::track_by_number ( int n ) +{ + if ( n < 0 || n >= mixer_strips->children() ) + return NULL; + + return (Mixer_Strip*)mixer_strips->child(n); +} /** return a malloc'd string representing a unique name for a new track */ char * diff --git a/mixer/src/Mixer.H b/mixer/src/Mixer.H index e111fbb..7e20e0c 100644 --- a/mixer/src/Mixer.H +++ b/mixer/src/Mixer.H @@ -52,6 +52,7 @@ private: Fl_Color system_colors[3]; Mixer_Strip* track_by_name ( const char *name ); + Mixer_Strip* track_by_number ( int n ); void snapshot ( void ); static void snapshot ( void *v ) { ((Mixer*)v)->snapshot(); } @@ -63,6 +64,7 @@ private: Fl_Scroll *scroll; Fl_Pack *pack; Fl_Box *project_name; + Fl_Box *_status; Fl_Flowpack *mixer_strips; @@ -75,6 +77,7 @@ private: static void handle_dirty ( int, void *v ); static int osc_non_hello ( const char *, const char *, lo_arg **, int , lo_message msg, void * ); + static int osc_strip_by_number ( const char *, const char *, lo_arg **, int , lo_message msg, void * ); static void update_cb ( void * ); void update_cb ( void ); @@ -83,6 +86,11 @@ public: void update_frequency ( float f ); + void status ( const char *s ) { + if ( s ) _status->copy_label( s ); + else _status->label(0); + _status->redraw(); } + virtual int handle ( int m ); char * get_unique_track_name ( const char *name ); @@ -102,6 +110,7 @@ public: void insert ( Mixer_Strip *ms, int i ); bool contains ( Mixer_Strip *ms ); Mixer_Strip * event_inside ( void ); + int find_strip ( const Mixer_Strip *m ) const; bool save ( void ); void quit ( void ); diff --git a/mixer/src/Mixer_Strip.C b/mixer/src/Mixer_Strip.C index 1128a6c..cf1c6c2 100644 --- a/mixer/src/Mixer_Strip.C +++ b/mixer/src/Mixer_Strip.C @@ -389,6 +389,7 @@ Mixer_Strip::init ( ) { Fl_Box *o = color_box = new Fl_Box( 0,0, 25, 10 ); o->box(FL_FLAT_BOX); + o->tooltip( "Drag and drop to move strip" ); } { Fl_Pack *o = new Fl_Pack( 2, 2, 114, 100 ); @@ -727,8 +728,9 @@ Mixer_Strip::handle ( int m ) } case FL_PUSH: if ( Fl::event_button1() && Fl::event_inside( color_box ) ) - { - } + dragging = this; + else + dragging = NULL; _button = Fl::event_button(); @@ -737,18 +739,17 @@ Mixer_Strip::handle ( int m ) break; case FL_DRAG: - if ( Fl::event_is_click() ) - return 1; - - dragging = this; + return 1; break; case FL_RELEASE: - if ( dragging == this ) + if ( dragging == this && ! Fl::event_is_click() ) { mixer->insert( this, mixer->event_inside() ); dragging = NULL; return 1; } + + dragging = NULL; int b = _button; _button = 0; @@ -769,6 +770,11 @@ Mixer_Strip::handle ( int m ) return 0; } +int +Mixer_Strip::number ( void ) const +{ + return mixer->find_strip( this ); +} /************/ /* Commands */ diff --git a/mixer/src/Mixer_Strip.H b/mixer/src/Mixer_Strip.H index 704cb20..03ece52 100644 --- a/mixer/src/Mixer_Strip.H +++ b/mixer/src/Mixer_Strip.H @@ -141,6 +141,7 @@ protected: public: + int number ( void ) const; static bool import_strip ( const char *filename ); void command_move_left ( void ); diff --git a/mixer/src/Module.C b/mixer/src/Module.C index 02545e7..e38f2f5 100644 --- a/mixer/src/Module.C +++ b/mixer/src/Module.C @@ -226,12 +226,42 @@ Module::paste_before ( void ) +void +Module::Port::send_feedback ( void ) +{ + + if ( _scaled_signal ) + { + /* send feedback for by_name signal */ + mixer->osc_endpoint->send_feedback( _scaled_signal->path(), control_value() ); + +/* send feedback for by number signal */ + { + int n = _module->chain()->strip()->number(); + + char *s = strdup( _scaled_signal->path() ); + + char *suffix = index( s, '/' ); + suffix = index( suffix, '/' ); + suffix = index( suffix, '/' ); + + char *path; + asprintf( &path, "/strip#/%i%s", suffix ); + + mixer->osc_endpoint->send_feedback( path, control_value() ); + + } + } + +} void Module::handle_control_changed ( Port *p ) { if ( _editor ) _editor->handle_control_changed ( p ); + + p->send_feedback(); } bool diff --git a/mixer/src/Module.H b/mixer/src/Module.H index 8a04959..0f9248e 100644 --- a/mixer/src/Module.H +++ b/mixer/src/Module.H @@ -239,6 +239,8 @@ public: update_connected_port_buffer(); } + void send_feedback ( void ); + void disconnect ( void ) { if ( _connected && _connected != (void*)0x01 ) diff --git a/nonlib/OSC/Endpoint.C b/nonlib/OSC/Endpoint.C index 2bf499e..a3817c2 100644 --- a/nonlib/OSC/Endpoint.C +++ b/nonlib/OSC/Endpoint.C @@ -180,6 +180,7 @@ namespace OSC Endpoint::Endpoint ( ) { + _learning_path = NULL; _peer_signal_notification_callback = 0; _peer_signal_notification_userdata = 0; _peer_scan_complete_callback = 0; @@ -196,6 +197,10 @@ namespace OSC _server = lo_server_new_with_proto( port, proto, error_handler ); + char *url = lo_server_get_url( _server ); + _addr = lo_address_new_from_url( url ); + free( url ); + if ( ! _server ) { WARNING( "Error creating OSC server" ); @@ -210,8 +215,8 @@ namespace OSC add_method( "/signal/created", "ssifff", &Endpoint::osc_sig_created, this, "" ); add_method( "/signal/change", "iif", &Endpoint::osc_sig_handler, this, "" ); add_method( "/signal/list", NULL, &Endpoint::osc_signal_lister, this, "" ); - add_method( NULL, "", &Endpoint::osc_generic, this, "" ); add_method( "/reply", NULL, &Endpoint::osc_reply, this, "" ); + add_method( NULL, NULL, &Endpoint::osc_generic, this, "" ); return 0; } @@ -694,16 +699,46 @@ namespace OSC return 0; } + void + Endpoint::add_translation ( const char *a, const char *b ) + { + _translations[a].path = b; + } + int Endpoint::osc_generic ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { // OSC_DMSG(); + Endpoint *ep = (Endpoint*)user_data; + + if ( ep->_learning_path ) + { + ep->add_translation( path, ep->_learning_path ); + + DMESSAGE( "Learned translation \"%s\" -> \"%s\"", path, ep->_learning_path ); + + free(ep->_learning_path); + ep->_learning_path = NULL; + + return 0; + } + + { + std::map::const_iterator i = ep->_translations.find( path ); + + if ( i != ep->_translations.end() ) + { + const char *dpath = i->second.path.c_str(); + + DMESSAGE( "Translating message \"%s\" to \"%s\"", path, dpath ); + lo_send_message(ep->_addr, dpath, msg ); + return 0; + } + } if ( argc || path[ strlen(path) - 1 ] != '/' ) return -1; - Endpoint *ep = (Endpoint*)user_data; - for ( std::list::const_iterator i = ep->_methods.begin(); i != ep->_methods.end(); ++i ) { if ( ! (*i)->path() ) @@ -1212,6 +1247,51 @@ namespace OSC _signals.remove( o ); } + /* prepare to learn a translation for /path/. The next unhandled message to come through will be mapped to /path/ */ + void + Endpoint::learn ( const char *path ) + { + if ( _learning_path ) + free( _learning_path ); + + _learning_path = NULL; + + if ( path ) + _learning_path = strdup( path ); + } + + /** if there's a translation with a destination of 'path', then send feedback for it */ + void + Endpoint::send_feedback ( const char *path, float v ) + { + for ( std::map::iterator i = _translations.begin(); + i != _translations.end(); + i++ ) + { + if ( ! strcmp( i->second.path.c_str(), path ) ) + { + /* found it */ + if ( i->second.current_value != v ) + { + const char *spath = i->first.c_str(); + + DMESSAGE( "Sending feedback to \"%s\": %f", spath, v ); + + /* send to all peers */ + for ( std::list::iterator p = _peers.begin(); + p != _peers.end(); + ++p ) + { + send( (*p)->addr, spath, v ); + } + + i->second.current_value = v; + } + + } + } + } + Peer * Endpoint::add_peer ( const char *name, const char *url ) { diff --git a/nonlib/OSC/Endpoint.H b/nonlib/OSC/Endpoint.H index ce5b082..d0e5d18 100644 --- a/nonlib/OSC/Endpoint.H +++ b/nonlib/OSC/Endpoint.H @@ -22,8 +22,10 @@ #include #include "Thread.H" #include +#include #include #include +#include namespace OSC { @@ -244,11 +246,23 @@ namespace OSC // lo_server_thread _st; lo_server _server; - + lo_address _addr; + std::list _peers; std::list _signals; std::list _methods; + char *_learning_path; + + class TranslationDestination { + + public: + std::string path; + float current_value; + }; + + std::map _translations; + void (*_peer_scan_complete_callback)(void*); void *_peer_scan_complete_userdata; @@ -296,6 +310,16 @@ namespace OSC public: + void send_feedback ( const char *path, float v ); + void learn ( const char *path ); + + lo_address address ( void ) + { + return _addr; + } + + void add_translation ( const char *a, const char *b ); + void peer_signal_notification_callback ( void (*cb)(OSC::Signal *, OSC::Signal::State, void*), void *userdata ) { _peer_signal_notification_callback = cb;