| @@ -1 +1 @@ | |||
| Subproject commit 02768c459a81592abaa50c6095b3a60b1049b4fc | |||
| Subproject commit 8ee564bef320b3561e204d17c1aa054311b2ce47 | |||
| @@ -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 ) | |||
| @@ -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 ); | |||
| @@ -48,6 +48,8 @@ | |||
| #include "OSC/Endpoint.H" | |||
| #include <lo/lo.h> | |||
| #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 <FL/Fl_Tooltip.H> | |||
| 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 * | |||
| @@ -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 ); | |||
| @@ -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 */ | |||
| @@ -141,6 +141,7 @@ protected: | |||
| public: | |||
| int number ( void ) const; | |||
| static bool import_strip ( const char *filename ); | |||
| void command_move_left ( void ); | |||
| @@ -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 | |||
| @@ -239,6 +239,8 @@ public: | |||
| update_connected_port_buffer(); | |||
| } | |||
| void send_feedback ( void ); | |||
| void disconnect ( void ) | |||
| { | |||
| if ( _connected && _connected != (void*)0x01 ) | |||
| @@ -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<std::string,TranslationDestination>::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<Method*>::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<std::string,TranslationDestination>::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<Peer*>::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 ) | |||
| { | |||
| @@ -22,8 +22,10 @@ | |||
| #include <lo/lo.h> | |||
| #include "Thread.H" | |||
| #include <list> | |||
| #include <string> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <map> | |||
| namespace OSC | |||
| { | |||
| @@ -244,11 +246,23 @@ namespace OSC | |||
| // lo_server_thread _st; | |||
| lo_server _server; | |||
| lo_address _addr; | |||
| std::list<Peer*> _peers; | |||
| std::list<Signal*> _signals; | |||
| std::list<Method*> _methods; | |||
| char *_learning_path; | |||
| class TranslationDestination { | |||
| public: | |||
| std::string path; | |||
| float current_value; | |||
| }; | |||
| std::map<std::string,TranslationDestination> _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; | |||