| @@ -1,20 +1,21 @@ | |||
| # data file for the Fltk User Interface Designer (fluid) | |||
| version 1.0108 | |||
| version 1.0300 | |||
| header_name {.H} | |||
| code_name {.C} | |||
| decl {\#include <string.h>} {} | |||
| decl {\#include <string.h>} {private local | |||
| } | |||
| widget_class Fl_Text_Edit_Window {open selected | |||
| xywh {375 272 355 410} type Double resizable | |||
| widget_class Fl_Text_Edit_Window {open | |||
| xywh {377 295 355 410} type Double resizable | |||
| code0 {this->size_range( 0, 0, 400, 400 );} | |||
| class Fl_Window modal visible | |||
| class Fl_Double_Window modal visible | |||
| } { | |||
| Fl_Box title_box { | |||
| label {<title>} | |||
| xywh {5 7 345 45} | |||
| xywh {5 7 345 28} | |||
| } | |||
| Fl_Text_Editor text_editor { | |||
| xywh {5 58 345 320} resizable | |||
| Fl_Text_Editor text_editor {selected | |||
| xywh {5 37 345 341} resizable | |||
| code0 {o->buffer( new Fl_Text_Buffer );} | |||
| } | |||
| Fl_Group {} {open | |||
| @@ -33,10 +34,11 @@ widget_class Fl_Text_Edit_Window {open selected | |||
| } | |||
| } | |||
| Function {fl_text_edit( const char *title, const char *button_text, const char *initial_text )} {open C return_type {char *} | |||
| Function {fl_text_edit( const char *title, const char *button_text, const char *initial_text, int W = 355, int H = 410 )} {open C return_type {char *} | |||
| } { | |||
| code {Fl_Text_Edit_Window tew( 355, 410, title ); | |||
| tew.size( W, H ); | |||
| tew.return_button->label( button_text ); | |||
| tew.title_box->label( title ); | |||
| if ( initial_text ) | |||
| @@ -313,6 +313,12 @@ Chain::remove ( Controller_Module *m ) | |||
| redraw(); | |||
| } | |||
| void | |||
| Chain::send_feedback ( void ) | |||
| { | |||
| for ( int i = 0; i < modules(); i++ ) | |||
| module(i)->send_feedback(); | |||
| } | |||
| /* remove a module from the chain. this isn't guaranteed to succeed, | |||
| * because removing the module might result in an invalid routing */ | |||
| @@ -104,6 +104,8 @@ public: | |||
| const char *name ( void ) const { return _name; } | |||
| void name ( const char *name ); | |||
| void send_feedback ( void ); | |||
| int get_module_instance_number ( Module *m ); | |||
| void configure_ports ( void ); | |||
| @@ -45,8 +45,10 @@ | |||
| // needed for mixer->endpoint | |||
| #include "Mixer.H" | |||
| bool Controller_Module::_learn_mode = false; | |||
| bool Controller_Module::learn_by_number = false; | |||
| bool Controller_Module::_learn_mode = false; | |||
| Controller_Module::Controller_Module ( bool is_default ) : Module( is_default, 50, 100, name() ) | |||
| @@ -186,7 +188,7 @@ Controller_Module::mode ( Mode m ) | |||
| Port *p = control_output[0].connected_port(); | |||
| JACK::Port po( chain()->engine(), JACK::Port::Input, p->name(), 0, "CV" ); | |||
| JACK::Port po( chain()->engine(), JACK::Port::Input, JACK::Port::Audio, p->name(), 0, "CV" ); | |||
| if ( ! po.activate() ) | |||
| { | |||
| @@ -549,9 +551,24 @@ Controller_Module::handle ( int m ) | |||
| if ( p ) | |||
| { | |||
| DMESSAGE( "Will learn %s", p->osc_path() ); | |||
| if ( learn_by_number ) | |||
| { | |||
| char *path = p->osc_number_path(); | |||
| DMESSAGE( "Will learn %s", path ); | |||
| mixer->osc_endpoint->learn( path ); | |||
| free(path); | |||
| } | |||
| else | |||
| { | |||
| const char *path = p->osc_path(); | |||
| DMESSAGE( "Will learn %s", path ); | |||
| mixer->osc_endpoint->learn( p->osc_path() ); | |||
| mixer->osc_endpoint->learn( path ); | |||
| } | |||
| } | |||
| return 1; | |||
| @@ -44,6 +44,7 @@ public: | |||
| static bool _learn_mode; | |||
| static bool learn_by_number; | |||
| static bool learn_mode ( void ) { return _learn_mode; } | |||
| static void learn_mode ( bool b ) { _learn_mode = b; } | |||
| @@ -196,7 +196,7 @@ get_connections_for_ports ( std::vector<JACK::Port> ports ) | |||
| if ( ! connections ) | |||
| return names; | |||
| bool is_output = ports[i].type() == JACK::Port::Output; | |||
| bool is_output = ports[i].direction() == JACK::Port::Output; | |||
| for ( const char **c = connections; *c; c++ ) | |||
| { | |||
| @@ -356,9 +356,9 @@ JACK_Module::configure_inputs ( int n ) | |||
| JACK::Port *po = NULL; | |||
| if ( !_prefix ) | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Output, i ); | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Output, JACK::Port::Audio, i ); | |||
| else | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Output, _prefix, i ); | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Output, JACK::Port::Audio, _prefix, i ); | |||
| if ( ! po->activate() ) | |||
| { | |||
| @@ -415,9 +415,9 @@ JACK_Module::configure_outputs ( int n ) | |||
| JACK::Port *po = NULL; | |||
| if ( !_prefix ) | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Input, i ); | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Input, JACK::Port::Audio, i ); | |||
| else | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Input, _prefix, i ); | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Input, JACK::Port::Audio, _prefix, i ); | |||
| if ( ! po->activate() ) | |||
| { | |||
| @@ -37,7 +37,7 @@ | |||
| #include <FL/Fl_File_Chooser.H> | |||
| #include <FL/Fl_Theme_Chooser.H> | |||
| #include <FL/Fl_Value_SliderX.H> | |||
| #include "FL/Fl_Text_Edit_Window.H" | |||
| #include "file.h" | |||
| #include <string.h> | |||
| @@ -50,6 +50,8 @@ | |||
| #include "Controller_Module.H" | |||
| const double FEEDBACK_UPDATE_FREQ = 1.0f; | |||
| extern char *user_config_dir; | |||
| extern char *instance_name; | |||
| @@ -63,16 +65,16 @@ extern NSM_Client *nsm; | |||
| static void | |||
| mixer_show_tooltip ( const char *s ) | |||
| void | |||
| Mixer::show_tooltip ( const char *s ) | |||
| { | |||
| mixer->status( s ); | |||
| mixer->_status->label( s ); | |||
| } | |||
| static void | |||
| mixer_hide_tooltip ( void ) | |||
| void | |||
| Mixer::hide_tooltip ( void ) | |||
| { | |||
| mixer->status( 0 ); | |||
| mixer->_status->label( 0 ); | |||
| } | |||
| @@ -94,6 +96,7 @@ static int osc_add_strip ( const char *path, const char *, lo_arg **, int , lo_m | |||
| OSC_DMSG(); | |||
| Fl::lock(); | |||
| ((Mixer*)(OSC_ENDPOINT())->owner)->command_add_strip(); | |||
| Fl::unlock(); | |||
| @@ -258,6 +261,10 @@ void Mixer::cb_menu(Fl_Widget* o) { | |||
| { | |||
| command_add_strip(); | |||
| } | |||
| else if ( !strcmp( picked, "&Mixer/Send Feedback" ) ) | |||
| { | |||
| send_feedback(); | |||
| } | |||
| else if ( !strcmp( picked, "&Mixer/Add &N Strips" ) ) | |||
| { | |||
| const char *s = fl_input( "Enter number of strips to add" ); | |||
| @@ -278,18 +285,37 @@ void Mixer::cb_menu(Fl_Widget* o) { | |||
| fl_alert( "%s", "Failed to import strip!" ); | |||
| } | |||
| } | |||
| else if ( ! strcmp( picked, "&Mixer/Start Learning" ) ) | |||
| else if ( ! strcmp( picked, "&Project/Se&ttings/Learn/By Strip Name" ) ) | |||
| { | |||
| Controller_Module::learn_by_number = false; | |||
| } | |||
| else if ( ! strcmp( picked, "&Project/Se&ttings/Learn/By Strip Number" ) ) | |||
| { | |||
| Controller_Module::learn_by_number = true; | |||
| } | |||
| else if ( ! strcmp( picked, "&Mixer/Remote Control/Start Learning" ) ) | |||
| { | |||
| Controller_Module::learn_mode( true ); | |||
| status( "Now in learn mode. Click on a highlighted control to teach it something." ); | |||
| tooltip( "Now in learn mode. Click on a highlighted control to teach it something." ); | |||
| redraw(); | |||
| } | |||
| else if ( ! strcmp( picked, "&Mixer/Stop Learning" ) ) | |||
| else if ( ! strcmp( picked, "&Mixer/Remote Control/Stop Learning" ) ) | |||
| { | |||
| Controller_Module::learn_mode( false ); | |||
| status( "Learning complete" ); | |||
| tooltip( "Learning complete" ); | |||
| redraw(); | |||
| } | |||
| else if ( ! strcmp( picked, "&Mixer/Remote Control/Clear Mappings" ) ) | |||
| { | |||
| if ( 1 == fl_ask( "This will remove all mappings, are you sure?") ) | |||
| { | |||
| command_clear_mappings(); | |||
| } | |||
| } | |||
| else if ( ! strcmp( picked, "&Mixer/Remote Control/Edit Mappings" ) ) | |||
| { | |||
| edit_translations(); | |||
| } | |||
| else if ( !strcmp( picked, "&Mixer/Paste" ) ) | |||
| { | |||
| Fl::paste(*this); | |||
| @@ -452,8 +478,8 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : | |||
| Fl_Tooltip::hoverdelay( 0 ); | |||
| Fl_Tooltip::delay( 0 ); | |||
| fl_show_tooltip = mixer_show_tooltip; | |||
| fl_hide_tooltip = mixer_hide_tooltip; | |||
| 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 ) ); */ | |||
| @@ -471,18 +497,20 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : | |||
| o->add( "&Project/Se&ttings/&Rows/Two", '2', 0, 0, FL_MENU_RADIO ); | |||
| o->add( "&Project/Se&ttings/&Rows/Three", '3', 0, 0, FL_MENU_RADIO ); | |||
| o->add( "&Project/Se&ttings/Make Default", 0,0,0); | |||
| o->add( "&Project/Se&ttings/Learn/By Strip Number", 0, 0, 0, FL_MENU_RADIO ); | |||
| o->add( "&Project/Se&ttings/Learn/By Strip Name", 0, 0, 0, FL_MENU_RADIO | FL_MENU_VALUE ); | |||
| o->add( "&Project/&Save", FL_CTRL + 's', 0, 0 ); | |||
| o->add( "&Project/&Quit", FL_CTRL + 'q', 0, 0 ); | |||
| o->add( "&Mixer/&Add Strip", 'a', 0, 0 ); | |||
| o->add( "&Mixer/Add &N Strips" ); | |||
| o->add( "&Mixer/Send Feedback" ); | |||
| 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( "&Mixer/Remote Control/Start Learning", FL_F + 9, 0, 0 ); | |||
| o->add( "&Mixer/Remote Control/Stop Learning", FL_F + 10, 0, 0 ); | |||
| o->add( "&Mixer/Remote Control/Clear Mappings", 0, 0, 0 ); | |||
| o->add( "&Mixer/Remote Control/Edit Mappings", 0, 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); */ | |||
| /* o->add( "&Options/&Display/Update Frequency/15 Hz", 0, 0, 0, FL_MENU_RADIO | FL_MENU_VALUE ); */ | |||
| o->add( "&Help/&Manual" ); | |||
| o->add( "&Help/&About" ); | |||
| o->callback( cb_menu, this ); | |||
| @@ -510,7 +538,7 @@ 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 + 18 ) ); | |||
| { Fl_Scroll *o = scroll = new Fl_Scroll( X, Y + 24, W, H - ( 100 ) ); | |||
| o->box( FL_FLAT_BOX ); | |||
| // o->type( Fl_Scroll::HORIZONTAL_ALWAYS ); | |||
| // o->box( Fl_Scroll::BOTH ); | |||
| @@ -538,6 +566,8 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : | |||
| update_frequency( 15 ); | |||
| Fl::add_timeout( FEEDBACK_UPDATE_FREQ, send_feedback_cb, this ); | |||
| update_menu(); | |||
| load_options(); | |||
| @@ -549,16 +579,13 @@ Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, | |||
| { | |||
| int n; | |||
| char *rem; | |||
| char *client_name; | |||
| OSC::Endpoint *ep = (OSC::Endpoint*)user_data; | |||
| DMESSAGE( "%s", path ); | |||
| if ( 2 != sscanf( path, "/strip#/%d/%a[^\n]", &n, &rem ) ) | |||
| if ( 3 != sscanf( path, "%a[^/]/strip#/%d/%a[^\n]", &client_name, &n, &rem ) ) | |||
| return -1; | |||
| DMESSAGE( "%s", rem ); | |||
| Mixer_Strip *o = mixer->track_by_number( n ); | |||
| if ( ! o ) | |||
| @@ -569,12 +596,10 @@ Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, | |||
| char *new_path; | |||
| asprintf( &new_path, "/strip/%s/%s", o->name(), rem ); | |||
| asprintf( &new_path, "%s/strip/%s/%s", client_name, o->name(), rem ); | |||
| free( rem ); | |||
| DMESSAGE( "Sending %s", new_path ); | |||
| lo_send_message( ep->address(), new_path, msg ); | |||
| free( new_path ); | |||
| @@ -582,6 +607,79 @@ Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, | |||
| return 0; | |||
| } | |||
| void | |||
| Mixer::load_translations ( void ) | |||
| { | |||
| FILE *fp = fopen( "mappings", "r" ); | |||
| if ( ! fp ) | |||
| { | |||
| WARNING( "Error opening mappings file for reading" ); | |||
| return; | |||
| } | |||
| char *to; | |||
| char *from; | |||
| while ( 2 == fscanf( fp, "%a[^|> ] |> %a[^ \n]\n", &from, &to ) ) | |||
| { | |||
| osc_endpoint->add_translation( from, to ); | |||
| free(from); | |||
| free(to); | |||
| } | |||
| fclose( fp ); | |||
| } | |||
| void | |||
| Mixer::save_translations ( void ) | |||
| { | |||
| FILE *fp = fopen( "mappings", "w" ); | |||
| if ( ! fp ) | |||
| { | |||
| WARNING( "Error opening mappings file for writing" ); | |||
| return; | |||
| } | |||
| for ( int i = 0; i < osc_endpoint->ntranslations(); i++ ) | |||
| { | |||
| const char *to; | |||
| const char *from; | |||
| if ( osc_endpoint->get_translation( i, &to, &from ) ) | |||
| { | |||
| fprintf( fp, "%s |> %s\n", to, from ); | |||
| } | |||
| } | |||
| fclose( fp ); | |||
| } | |||
| void | |||
| Mixer::edit_translations ( void ) | |||
| { | |||
| char *file_contents = NULL; | |||
| if ( exists( "mappings" ) ) | |||
| { | |||
| size_t l = ::size( "mappings" ); | |||
| file_contents = (char*)malloc( l ); | |||
| FILE *fp = fopen( "mappings", "r" ); | |||
| fread( file_contents, l, 1, fp ); | |||
| fclose( fp ); | |||
| } | |||
| char *s = fl_text_edit( "Mappings", "&Save", file_contents, 800, 600 ); | |||
| if ( file_contents ) | |||
| free(file_contents); | |||
| } | |||
| int | |||
| Mixer::init_osc ( const char *osc_port ) | |||
| { | |||
| @@ -601,6 +699,8 @@ Mixer::init_osc ( const char *osc_port ) | |||
| osc_endpoint->start(); | |||
| osc_endpoint->add_method( NULL, NULL, osc_strip_by_number, osc_endpoint, ""); | |||
| return 0; | |||
| } | |||
| @@ -613,7 +713,9 @@ Mixer::~Mixer ( ) | |||
| Fl::remove_timeout( &Mixer::update_cb, this ); | |||
| /* FIXME: teardown */ | |||
| Fl::remove_timeout( &Mixer::send_feedback_cb, this ); | |||
| /* FIXME: teardown */ | |||
| mixer_strips->clear(); | |||
| } | |||
| @@ -621,9 +723,9 @@ void Mixer::resize ( int X, int Y, int W, int H ) | |||
| { | |||
| Fl_Group::resize( X, Y, W, H ); | |||
| mixer_strips->resize( X, Y + 24, W, H - 18 - 24 ); | |||
| mixer_strips->resize( X, Y + 24, W, H - (18*2) - 24 ); | |||
| scroll->resize( X, Y + 24, W, H - 24 ); | |||
| scroll->resize( X, Y + 24, W, H - 24 - 18 ); | |||
| rows( _rows ); | |||
| } | |||
| @@ -841,6 +943,8 @@ Mixer::save ( void ) | |||
| MESSAGE( "Saving state" ); | |||
| Loggable::snapshot_callback( &Mixer::snapshot, this ); | |||
| Loggable::snapshot( "snapshot" ); | |||
| save_translations(); | |||
| return true; | |||
| } | |||
| @@ -872,6 +976,27 @@ Mixer::update_menu ( void ) | |||
| project_name->label( Project::name() ); | |||
| } | |||
| void | |||
| Mixer::send_feedback_cb ( void *v ) | |||
| { | |||
| Mixer *m = (Mixer*)v; | |||
| m->send_feedback(); | |||
| Fl::repeat_timeout( FEEDBACK_UPDATE_FREQ, send_feedback_cb, v ); | |||
| } | |||
| /** unconditionally send feedback to all mapped controls. This is | |||
| * useful for updating the state of an external controller. */ | |||
| void | |||
| Mixer::send_feedback ( void ) | |||
| { | |||
| for ( int i = 0; i < mixer_strips->children(); i++ ) | |||
| { | |||
| ((Mixer_Strip*)mixer_strips->child(i))->send_feedback(); | |||
| } | |||
| } | |||
| int | |||
| @@ -916,6 +1041,12 @@ Mixer::handle ( int m ) | |||
| /* Commands */ | |||
| /************/ | |||
| void | |||
| Mixer::command_clear_mappings ( void ) | |||
| { | |||
| osc_endpoint->clear_translations(); | |||
| } | |||
| bool | |||
| Mixer::command_save ( void ) | |||
| { | |||
| @@ -950,6 +1081,8 @@ Mixer::command_load ( const char *path, const char *display_name ) | |||
| load_project_settings(); | |||
| load_translations(); | |||
| update_menu(); | |||
| mixer->activate(); | |||
| @@ -46,6 +46,9 @@ private: | |||
| float _update_interval; | |||
| static void show_tooltip ( const char *s ); | |||
| static void hide_tooltip ( void ); | |||
| int _rows; | |||
| int _strip_height; | |||
| @@ -71,7 +74,11 @@ private: | |||
| void load_options ( void ); | |||
| void save_options ( void ); | |||
| void update_menu ( void ); | |||
| void save_translations ( void ); | |||
| void load_translations ( void ); | |||
| static void send_feedback_cb ( void *v ); | |||
| void send_feedback ( void ); | |||
| void redraw_windows ( void ); | |||
| static void handle_dirty ( int, void *v ); | |||
| @@ -135,7 +142,9 @@ public: | |||
| void load_project_settings ( void ); | |||
| public: | |||
| void edit_translations ( void ); | |||
| void command_clear_mappings ( void ); | |||
| void command_new ( void ); | |||
| bool command_save ( void ); | |||
| bool command_load ( const char *path, const char *display_name = 0 ); | |||
| @@ -770,6 +770,13 @@ Mixer_Strip::handle ( int m ) | |||
| return 0; | |||
| } | |||
| void | |||
| Mixer_Strip::send_feedback ( void ) | |||
| { | |||
| if ( _chain ) | |||
| _chain->send_feedback(); | |||
| } | |||
| int | |||
| Mixer_Strip::number ( void ) const | |||
| { | |||
| @@ -141,6 +141,7 @@ protected: | |||
| public: | |||
| void send_feedback ( void ); | |||
| int number ( void ) const; | |||
| static bool import_strip ( const char *filename ); | |||
| @@ -226,33 +226,70 @@ Module::paste_before ( void ) | |||
| char * | |||
| Module::Port::osc_number_path ( void ) | |||
| { | |||
| int n = _module->chain()->strip()->number(); | |||
| char *rem; | |||
| char *client_name; | |||
| char *strip_name; | |||
| if ( 3 != sscanf( _scaled_signal->path(), "%a[^/]/strip/%a[^/]/%a[^\n]", &client_name, &strip_name, &rem ) ) | |||
| return NULL; | |||
| free( strip_name ); | |||
| char *path; | |||
| asprintf( &path, "%s/strip#/%i/%s", client_name, n, rem ); | |||
| free( client_name ); | |||
| free( rem ); | |||
| return path; | |||
| } | |||
| void | |||
| Module::Port::send_feedback ( void ) | |||
| { | |||
| float f = control_value(); | |||
| if ( hints.ranged ) | |||
| { | |||
| // scale value to range. | |||
| float scale = hints.maximum - hints.minimum; | |||
| float offset = hints.minimum; | |||
| f = ( f - offset ) / scale; | |||
| } | |||
| if ( f > 1.0 ) | |||
| f = 1.0; | |||
| else if ( f < 0.0 ) | |||
| f = 0.0; | |||
| if ( _scaled_signal ) | |||
| { | |||
| /* send feedback for by_name signal */ | |||
| mixer->osc_endpoint->send_feedback( _scaled_signal->path(), control_value() ); | |||
| mixer->osc_endpoint->send_feedback( _scaled_signal->path(), f ); | |||
| /* 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() ); | |||
| char *path = osc_number_path(); | |||
| mixer->osc_endpoint->send_feedback( path, f ); | |||
| free(path); | |||
| } | |||
| } | |||
| } | |||
| void | |||
| Module::send_feedback ( void ) | |||
| { | |||
| for ( int i = 0; i < ncontrol_inputs(); i++ ) | |||
| control_input[i].send_feedback(); | |||
| } | |||
| void | |||
| @@ -273,6 +310,12 @@ Module::Port::connected_osc ( void ) const | |||
| return false; | |||
| } | |||
| void | |||
| Module::Port::learn_osc ( void ) | |||
| { | |||
| _scaled_signal->learn_connection(); | |||
| } | |||
| char * | |||
| Module::Port::generate_osc_path () | |||
| { | |||
| @@ -123,6 +123,8 @@ public: | |||
| static int osc_control_change_exact ( float v, void *user_data ); | |||
| static int osc_control_change_cv ( float v, void *user_data ); | |||
| void learn_osc ( void ); | |||
| Hints hints; | |||
| Port ( Module *module, Direction direction, Type type, const char *name = 0 ) | |||
| @@ -176,6 +178,8 @@ public: | |||
| return NULL; | |||
| } | |||
| char *osc_number_path ( void ); | |||
| void update_osc_port ( ) | |||
| { | |||
| // if ( INPUT == _direction ) | |||
| @@ -200,6 +204,27 @@ public: | |||
| { | |||
| *((float*)buffer()) = f; | |||
| } | |||
| if ( _scaled_signal ) | |||
| { | |||
| if ( hints.ranged ) | |||
| { | |||
| // scale value to range. | |||
| float scale = hints.maximum - hints.minimum; | |||
| float offset = hints.minimum; | |||
| f = ( f - offset ) / scale; | |||
| } | |||
| if ( f > 1.0 ) | |||
| f = 1.0; | |||
| else if ( f < 0.0 ) | |||
| f = 0.0; | |||
| // _scaled_signal->value( f ); | |||
| } | |||
| } | |||
| void control_value ( float f ) | |||
| @@ -405,7 +430,8 @@ public: | |||
| char *get_parameters ( void ) const; | |||
| void set_parameters ( const char * ); | |||
| void send_feedback ( void ); | |||
| virtual bool initialize ( void ) { return true; } | |||
| /* for the given number of inputs, return how many outputs this | |||
| @@ -382,9 +382,9 @@ Module_Parameter_Editor::cb_bound_handle ( Fl_Widget *w, void *v ) | |||
| Fl_Button *fv = (Fl_Button*)w; | |||
| fv->value( 1 ); | |||
| fv->value( 1 ); | |||
| cd->base_widget->bind_control( cd->port_number[0] ); | |||
| cd->base_widget->bind_control( cd->port_number[0] ); | |||
| } | |||
| void | |||
| @@ -392,6 +392,8 @@ Module_Parameter_Editor::bind_control ( int i ) | |||
| { | |||
| Module::Port *p = &_module->control_input[i]; | |||
| /* p->learn_osc(); */ | |||
| if ( p->connected() ) | |||
| /* can only bind once */ | |||
| return; | |||
| @@ -0,0 +1,768 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2013 Jonathan Moore Liles */ | |||
| /* */ | |||
| /* This program is free software; you can redistribute it and/or modify it */ | |||
| /* under the terms of the GNU General Public License as published by the */ | |||
| /* Free Software Foundation; either version 2 of the License, or (at your */ | |||
| /* option) any later version. */ | |||
| /* */ | |||
| /* This program is distributed in the hope that it will be useful, but WITHOUT */ | |||
| /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ | |||
| /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ | |||
| /* more details. */ | |||
| /* */ | |||
| /* You should have received a copy of the GNU General Public License along */ | |||
| /* with This program; see the file COPYING. If not,write to the Free Software */ | |||
| /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||
| /*******************************************************************************/ | |||
| #include <JACK/Client.H> | |||
| #include <JACK/Port.H> | |||
| #include <OSC/Endpoint.H> | |||
| #include <MIDI/midievent.H> | |||
| #include "debug.h" | |||
| #include <sys/stat.h> | |||
| #include <sys/types.h> | |||
| using namespace MIDI; | |||
| #include <jack/ringbuffer.h> | |||
| #include <jack/thread.h> | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| #include <map> | |||
| #include <string> | |||
| #include <unistd.h> /* usleep */ | |||
| /* simple program to translate from MIDI<->OSC Signals using a fixed mapping */ | |||
| #include <nsm.h> | |||
| #undef APP_NAME | |||
| const char *APP_NAME = "non-midi-mapper"; | |||
| #undef VERSION | |||
| const char *VERSION = "1.0"; | |||
| nsm_client_t *nsm; | |||
| char *instance_name; | |||
| OSC::Endpoint *osc = 0; | |||
| /* const double NSM_CHECK_INTERVAL = 0.25f; */ | |||
| void | |||
| handle_hello ( lo_message msg ) | |||
| { | |||
| int argc = lo_message_get_argc( msg ); | |||
| lo_arg **argv = lo_message_get_argv( msg ); | |||
| if ( argc >= 4 ) | |||
| { | |||
| const char *url = &argv[0]->s; | |||
| const char *name = &argv[1]->s; | |||
| const char *version = &argv[2]->s; | |||
| const char *id = &argv[3]->s; | |||
| MESSAGE( "Discovered NON peer %s (%s) @ %s with ID \"%s\"", name, version, url, id ); | |||
| /* register peer */ | |||
| osc->handle_hello( id, url ); | |||
| } | |||
| } | |||
| void | |||
| check_nsm ( void ) | |||
| { | |||
| nsm_check_nowait( nsm ); | |||
| // Fl::repeat_timeout( NSM_CHECK_INTERVAL, &check_nsm, v ); | |||
| } | |||
| static int | |||
| osc_non_hello ( const char *, const char *, lo_arg **, int , lo_message msg, void * ) | |||
| { | |||
| handle_hello( msg ); | |||
| return 0; | |||
| } | |||
| static void | |||
| say_hello ( void ) | |||
| { | |||
| if ( nsm_is_active( nsm ) ) | |||
| { | |||
| lo_message m = lo_message_new(); | |||
| lo_message_add( m, "sssss", | |||
| "/non/hello", | |||
| osc->url(), | |||
| APP_NAME, | |||
| VERSION, | |||
| instance_name ); | |||
| nsm_send_broadcast( nsm, m ); | |||
| } | |||
| } | |||
| class Engine : public JACK::Client | |||
| { | |||
| public: | |||
| jack_ringbuffer_t *input_ring_buf; | |||
| jack_ringbuffer_t *output_ring_buf; | |||
| JACK::Port *midi_input_port; | |||
| JACK::Port *midi_output_port; | |||
| Engine ( ) | |||
| { | |||
| input_ring_buf = jack_ringbuffer_create( 16 * 16 * sizeof( jack_midi_event_t )); | |||
| jack_ringbuffer_reset( input_ring_buf ); | |||
| output_ring_buf = jack_ringbuffer_create( 16 * 16 * sizeof( jack_midi_event_t )); | |||
| jack_ringbuffer_reset( output_ring_buf ); | |||
| midi_input_port = 0; | |||
| midi_output_port = 0; | |||
| } | |||
| int process ( nframes_t nframes ) | |||
| { | |||
| /* process input */ | |||
| { | |||
| if ( !midi_input_port ) | |||
| return 0; | |||
| void *buf = midi_input_port->buffer( nframes ); | |||
| jack_midi_event_t ev; | |||
| jack_nframes_t count = jack_midi_get_event_count( buf ); | |||
| /* place MIDI events into ringbuffer for non-RT thread */ | |||
| for ( uint i = 0; i < count; ++i ) | |||
| { | |||
| // MESSAGE( "Got midi input!" ); | |||
| jack_midi_event_get( &ev, buf, i ); | |||
| /* /\* time is frame within cycle, convert to absolute tick *\/ */ | |||
| /* e.timestamp( ph + (ev.time / transport.frames_per_tick) ); */ | |||
| /* e.status( ev.buffer[0] ); */ | |||
| /* e.lsb( ev.buffer[1] ); */ | |||
| /* if ( ev.size == 3 ) */ | |||
| /* e.msb( ev.buffer[2] ); */ | |||
| if ( jack_ringbuffer_write( input_ring_buf, (char*)&ev, sizeof( jack_midi_event_t ) ) != sizeof( jack_midi_event_t ) ) | |||
| WARNING( "input buffer overrun" ); | |||
| } | |||
| } | |||
| /* process output */ | |||
| { | |||
| void *buf = midi_output_port->buffer(nframes); | |||
| jack_midi_clear_buffer( buf ); | |||
| jack_midi_event_t ev; | |||
| nframes_t frame = 0; | |||
| while ( true ) | |||
| { | |||
| /* jack_ringbuffer_data_t vec[2]; */ | |||
| /* jack_ringbuffer_get_read_vector( output_ring_buf, vec ); */ | |||
| if ( jack_ringbuffer_peek( output_ring_buf, (char*)&ev, sizeof( jack_midi_event_t )) <= 0 ) | |||
| break; | |||
| unsigned char *buffer = jack_midi_event_reserve( buf, frame, ev.size ); | |||
| if ( !buffer ) | |||
| { | |||
| WARNING("Output buffer overrun, will send later" ); | |||
| break; | |||
| } | |||
| memcpy( buffer, &ev, ev.size ); | |||
| jack_ringbuffer_read_advance( output_ring_buf, sizeof( jack_midi_event_t ) ); | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| void freewheel ( bool starting ) | |||
| { | |||
| } | |||
| int xrun ( void ) | |||
| { | |||
| return 0; | |||
| } | |||
| int buffer_size ( nframes_t nframes ) | |||
| { | |||
| return 0; | |||
| } | |||
| void shutdown ( void ) | |||
| { | |||
| } | |||
| void thread_init ( void ) | |||
| { | |||
| } | |||
| }; | |||
| Engine *engine; | |||
| const float MAX_NRPN = 16383.0f; | |||
| static char | |||
| get_lsb( int i ) | |||
| { | |||
| return i & 0x7F; | |||
| } | |||
| static char | |||
| get_msb( int i ) | |||
| { | |||
| return ( i >> 7 ) & 0x7F; | |||
| } | |||
| static int | |||
| get_14bit ( char msb, char lsb ) | |||
| { | |||
| return msb * 128 + lsb; | |||
| } | |||
| class signal_mapping | |||
| { | |||
| public: | |||
| bool is_nrpn; | |||
| // int nrpn; | |||
| midievent event; | |||
| std::string signal_name; | |||
| OSC::Signal *signal; | |||
| signal_mapping ( ) | |||
| { | |||
| is_nrpn = false; | |||
| signal = NULL; | |||
| } | |||
| ~signal_mapping ( ) | |||
| { | |||
| if ( signal ) | |||
| delete signal; | |||
| signal = NULL; | |||
| } | |||
| char *serialize ( void ) const | |||
| { | |||
| char *s; | |||
| const char *opcode = 0; | |||
| int v1 = 0; | |||
| if ( is_nrpn ) | |||
| { | |||
| opcode = "NRPN"; | |||
| v1 = get_14bit( event.msb(), event.lsb() ); | |||
| } | |||
| else | |||
| switch ( event.opcode() ) | |||
| { | |||
| case MIDI::midievent::CONTROL_CHANGE: | |||
| opcode = "CC"; | |||
| v1 = event.lsb(); | |||
| break; | |||
| case MIDI::midievent::NOTE_ON: | |||
| opcode = "NOTE_ON"; | |||
| v1 = event.note(); | |||
| break; | |||
| default: | |||
| // unsupported | |||
| break; | |||
| } | |||
| asprintf( &s, "%s %d %d", opcode, event.channel(), v1 ); | |||
| return s; | |||
| } | |||
| void deserialize ( const char *s ) | |||
| { | |||
| int channel; | |||
| char *opcode; | |||
| int control; | |||
| if ( 3 == sscanf( s, "%as %d %d", &opcode, &channel, &control ) ) | |||
| { | |||
| event.channel( channel ); | |||
| event.opcode( MIDI::midievent::CONTROL_CHANGE ); | |||
| is_nrpn = 0; | |||
| if ( !strcmp( opcode, "NRPN" ) ) | |||
| { | |||
| is_nrpn = 1; | |||
| event.lsb( get_lsb( control )); | |||
| event.msb( get_msb( control )); | |||
| } | |||
| else if ( !strcmp( opcode, "CC" ) ) | |||
| { | |||
| event.lsb( control ); | |||
| } | |||
| free(opcode); | |||
| } | |||
| } | |||
| }; | |||
| int signal_handler ( float value, void *user_data ) | |||
| { | |||
| signal_mapping *m = (signal_mapping*)user_data; | |||
| if ( m->is_nrpn ) | |||
| { | |||
| jack_midi_event_t jev[4]; | |||
| { | |||
| midievent e; | |||
| e.opcode( MIDI::midievent::CONTROL_CHANGE ); | |||
| e.channel( m->event.channel() ); | |||
| e.lsb( 99 ); | |||
| e.msb( m->event.msb() ); | |||
| jev[0].size = e.size(); | |||
| e.raw( (byte_t*)&jev[0], e.size() ); | |||
| // e.pretty_print(); | |||
| } | |||
| { | |||
| midievent e; | |||
| e.opcode( MIDI::midievent::CONTROL_CHANGE ); | |||
| e.channel( m->event.channel() ); | |||
| e.lsb( 98 ); | |||
| e.msb( m->event.lsb() ); | |||
| jev[1].size = e.size(); | |||
| e.raw( (byte_t*)&jev[1], e.size() ); | |||
| // e.pretty_print(); | |||
| } | |||
| { | |||
| midievent e; | |||
| e.opcode( MIDI::midievent::CONTROL_CHANGE ); | |||
| e.channel( m->event.channel() ); | |||
| e.lsb( 6 ); | |||
| e.msb( int(value * MAX_NRPN ) >> 7 ); | |||
| jev[2].size = e.size(); | |||
| e.raw( (byte_t*)&jev[2], e.size() ); | |||
| // e.pretty_print(); | |||
| } | |||
| { | |||
| midievent e; | |||
| e.opcode( MIDI::midievent::CONTROL_CHANGE ); | |||
| e.channel( m->event.channel() ); | |||
| e.lsb( 38 ); | |||
| e.msb( int( value * MAX_NRPN ) & 0x7F ); | |||
| jev[3].size = e.size(); | |||
| e.raw( (byte_t*)&jev[3], e.size() ); | |||
| // e.pretty_print(); | |||
| } | |||
| for ( int i = 0; i < 4; i++ ) | |||
| { | |||
| if ( jack_ringbuffer_write( engine->output_ring_buf, (char*)&jev[i], | |||
| sizeof( jack_midi_event_t ) ) != sizeof( jack_midi_event_t ) ) | |||
| WARNING( "output buffer overrun" ); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| jack_midi_event_t ev; | |||
| m->event.msb( value * 128.0f ); | |||
| ev.size = m->event.size(); | |||
| m->event.raw( (byte_t*)&ev, m->event.size() ); | |||
| // m->event.pretty_print(); | |||
| if ( jack_ringbuffer_write( engine->output_ring_buf, (char*)&ev, sizeof( jack_midi_event_t ) ) != sizeof( jack_midi_event_t ) ) | |||
| WARNING( "output buffer overrun" ); | |||
| } | |||
| return 0; | |||
| } | |||
| std::map<std::string,signal_mapping> sig_map; | |||
| bool | |||
| save_settings ( void ) | |||
| { | |||
| FILE *fp = fopen( "signals", "w" ); | |||
| if ( !fp ) | |||
| return false; | |||
| for ( std::map<std::string,signal_mapping>::const_iterator i = sig_map.begin(); | |||
| i != sig_map.end(); | |||
| i++ ) | |||
| { | |||
| fprintf( fp, "[%s] %s\n", i->first.c_str(), i->second.signal_name.c_str() ); | |||
| } | |||
| fclose(fp); | |||
| return true; | |||
| } | |||
| bool | |||
| load_settings ( void ) | |||
| { | |||
| FILE *fp = fopen( "signals", "r" ); | |||
| if ( !fp ) | |||
| return false; | |||
| sig_map.clear(); | |||
| char *signal_name; | |||
| char *midi_event; | |||
| while ( 2 == fscanf( fp, "[%a[^]]] %a[^\n]\n", &midi_event, &signal_name ) ) | |||
| { | |||
| DMESSAGE( "%s, %s", midi_event, signal_name ); | |||
| if ( sig_map.find( midi_event ) == sig_map.end() ) | |||
| { | |||
| signal_mapping m; | |||
| m.deserialize( midi_event ); | |||
| sig_map[midi_event] = m; | |||
| sig_map[midi_event].signal_name = signal_name; | |||
| sig_map[midi_event].signal = osc->add_signal( signal_name, OSC::Signal::Output, 0, 1, 0, signal_handler, &sig_map[midi_event] ); | |||
| } | |||
| free(signal_name); | |||
| free(midi_event); | |||
| /* if ( sig_map.find( s ) == sig_map.end() ) */ | |||
| /* { */ | |||
| /* int channel, control; */ | |||
| /* if ( 2 == sscanf( s, "/midi/%d/CC/%d", &channel, &control ) ) */ | |||
| /* { */ | |||
| /* signal_mapping m; */ | |||
| /* m.event.channel( channel ); */ | |||
| /* m.event.opcode( MIDI::midievent::CONTROL_CHANGE ); */ | |||
| /* m.event.lsb( control ); */ | |||
| /* MESSAGE( "creating signal %s", s ); */ | |||
| /* sig_map[s] = m; */ | |||
| /* sig_map[s].signal = osc->add_signal( s, OSC::Signal::Output, 0, 1, 0, signal_handler, &sig_map[s] ); */ | |||
| /* } */ | |||
| /* if ( 2 == sscanf( s, "/midi/%d/NRPN/%d", &channel, &control ) ) */ | |||
| /* { */ | |||
| /* signal_mapping m; */ | |||
| /* m.event.channel( channel ); */ | |||
| /* m.event.opcode( MIDI::midievent::CONTROL_CHANGE ); */ | |||
| /* m.event.lsb( get_lsb( control ) ); */ | |||
| /* m.event.msb( get_msb( control ) ); */ | |||
| /* m.is_nrpn = true; */ | |||
| /* MESSAGE( "creating signal %s", s ); */ | |||
| /* sig_map[s] = m; */ | |||
| /* sig_map[s].signal = osc->add_signal( s, OSC::Signal::Output, 0, 1, 0, signal_handler, &sig_map[s] ); */ | |||
| /* } */ | |||
| /* else */ | |||
| /* WARNING( "Could not decode signal spec \"%s\"", s ); */ | |||
| /* } */ | |||
| /* free(s); */ | |||
| } | |||
| return true; | |||
| } | |||
| static int | |||
| command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg, void *userdata ) | |||
| { | |||
| if ( instance_name ) | |||
| free( instance_name ); | |||
| instance_name = strdup( client_id ); | |||
| osc->name( client_id ); | |||
| mkdir( name, 0777 ); | |||
| chdir( name ); | |||
| load_settings(); | |||
| say_hello(); | |||
| return ERR_OK; | |||
| } | |||
| static int | |||
| command_save ( char **out_msg, void *userdata ) | |||
| { | |||
| if ( save_settings() ) | |||
| { | |||
| nsm_send_is_clean(nsm); | |||
| return ERR_OK; | |||
| } | |||
| else | |||
| return ERR_GENERAL; | |||
| } | |||
| static int | |||
| command_broadcast ( const char *path, lo_message msg, void *userdata ) | |||
| { | |||
| lo_message_get_argc( msg ); | |||
| // lo_arg **argv = lo_message_get_argv( msg ); | |||
| if ( !strcmp( path, "/non/hello" ) ) | |||
| { | |||
| handle_hello( msg ); | |||
| return 0; | |||
| } | |||
| else | |||
| return -1; | |||
| } | |||
| struct nrpn_state | |||
| { | |||
| char control_msb; | |||
| char control_lsb; | |||
| char value_msb; | |||
| char value_lsb; | |||
| bool decending; | |||
| }; | |||
| static | |||
| struct nrpn_state * | |||
| decode_nrpn ( nrpn_state *state, midievent e, int *take_action ) | |||
| { | |||
| nrpn_state *n = &state[e.channel()]; | |||
| *take_action = 0; | |||
| switch ( e.lsb() ) | |||
| { | |||
| case 6: | |||
| if ( e.msb() < n->value_msb ) | |||
| n->value_lsb = 127; | |||
| else if ( e.msb() > n->value_msb ) | |||
| n->value_lsb = 0; | |||
| n->value_msb = e.msb(); | |||
| *take_action = 1; | |||
| return n; | |||
| case 38: | |||
| n->value_lsb = e.msb(); | |||
| *take_action = 1; | |||
| return n; | |||
| case 99: | |||
| n->control_msb = e.msb(); | |||
| n->control_lsb = 0; | |||
| return n; | |||
| case 98: | |||
| n->control_lsb = e.msb(); | |||
| return n; | |||
| } | |||
| return NULL; | |||
| } | |||
| int | |||
| main ( int argc, char **argv ) | |||
| { | |||
| nrpn_state nrpn_state[16]; | |||
| nsm = nsm_new(); | |||
| // set_nsm_callbacks( nsm ); | |||
| nsm_set_open_callback( nsm, command_open, 0 ); | |||
| nsm_set_broadcast_callback( nsm, command_broadcast, 0 ); | |||
| nsm_set_save_callback( nsm, command_save, 0 ); | |||
| char *nsm_url = getenv( "NSM_URL" ); | |||
| if ( nsm_url ) | |||
| { | |||
| if ( ! nsm_init( nsm, nsm_url ) ) | |||
| { | |||
| nsm_send_announce( nsm, APP_NAME, ":dirty:", argv[0] ); | |||
| /* poll so we can keep OSC handlers running in the GUI thread and avoid extra sync */ | |||
| // Fl::add_timeout( NSM_CHECK_INTERVAL, check_nsm, NULL ); | |||
| } | |||
| } | |||
| engine = new Engine(); | |||
| DMESSAGE( "Creating JACK engine" ); | |||
| if ( ! engine->init("midi-to-osc" ) ) | |||
| { | |||
| WARNING( "Failed to create JACK client" ); | |||
| } | |||
| engine->midi_input_port = new JACK::Port( engine, "midi-in", JACK::Port::Input, JACK::Port::MIDI ); | |||
| engine->midi_output_port = new JACK::Port( engine, "midi-out", JACK::Port::Output, JACK::Port::MIDI ); | |||
| if ( !engine->midi_input_port->activate() ) | |||
| { | |||
| WARNING( "Failed to activate JACK port" ); | |||
| } | |||
| if ( !engine->midi_output_port->activate() ) | |||
| { | |||
| WARNING( "Failed to activate JACK port" ); | |||
| } | |||
| WARNING( "Can fit %i events in a period", ( engine->nframes() * 4 ) / 3 ); | |||
| osc = new OSC::Endpoint(); | |||
| osc->init( LO_UDP, NULL ); | |||
| osc->add_method( "/non/hello", "ssss", osc_non_hello, osc, "" ); | |||
| MESSAGE( "OSC URL = %s", osc->url() ); | |||
| /* now we just read from the MIDI ringbuffer and output OSC */ | |||
| DMESSAGE( "waiting for events" ); | |||
| static int max_signal = 1; | |||
| jack_midi_event_t ev; | |||
| midievent e; | |||
| while ( true ) | |||
| { | |||
| while ( jack_ringbuffer_read( engine->input_ring_buf, (char *)&ev, sizeof( jack_midi_event_t ) ) ) | |||
| { | |||
| e.timestamp( ev.time ); | |||
| e.status( ev.buffer[0] ); | |||
| e.lsb( ev.buffer[1] ); | |||
| if ( ev.size == 3 ) | |||
| e.msb( ev.buffer[2] ); | |||
| switch ( e.opcode() ) | |||
| { | |||
| case MIDI::midievent::CONTROL_CHANGE: | |||
| case MIDI::midievent::PITCH_WHEEL: | |||
| { | |||
| int is_nrpn = 0; | |||
| struct nrpn_state *st = decode_nrpn( nrpn_state, e, &is_nrpn ); | |||
| if ( st != NULL && !is_nrpn ) | |||
| continue; | |||
| char *midi_event; | |||
| if ( is_nrpn ) | |||
| { | |||
| asprintf( &midi_event, "NRPN %d %d", e.channel(), st->control_msb * 128 + st->control_lsb ); | |||
| } | |||
| else if ( e.opcode() == MIDI::midievent::CONTROL_CHANGE ) | |||
| asprintf( &midi_event, "CC %d %d", e.channel(), e.lsb() ); | |||
| /* else if ( e.opcode() == MIDI::midievent::PITCH_WHEEL ) */ | |||
| /* asprintf( &s, "/midi/%i/PB", e.channel() ); */ | |||
| else | |||
| break; | |||
| if ( sig_map.find( midi_event ) == sig_map.end() ) | |||
| { | |||
| char *s; | |||
| asprintf( &s, "/control/%i", max_signal++ ); | |||
| signal_mapping m; | |||
| m.event.opcode( e.opcode() ); | |||
| m.event.channel( e.channel() ); | |||
| m.event.lsb( e.lsb() ); | |||
| m.event.msb( e.msb() ); | |||
| m.is_nrpn = is_nrpn; | |||
| if ( is_nrpn ) | |||
| { | |||
| m.event.lsb( st->control_lsb ); | |||
| m.event.msb( st->control_msb ); | |||
| } | |||
| /* if ( is_nrpn ) */ | |||
| /* m.nrpn = nrpnc_msb * 127 + nrpnc_lsb; */ | |||
| MESSAGE( "creating signal %s", s ); | |||
| sig_map[midi_event] = m; | |||
| sig_map[midi_event].signal_name = s; | |||
| sig_map[midi_event].signal = osc->add_signal( s, OSC::Signal::Output, 0, 1, 0, signal_handler, &sig_map[midi_event] ); | |||
| nsm_send_is_dirty( nsm ); | |||
| free(s); | |||
| } | |||
| float val = 0; | |||
| if ( is_nrpn ) | |||
| { | |||
| val = ( st->value_msb * 128 + st->value_lsb ) / ( MAX_NRPN ); | |||
| } | |||
| else if ( e.opcode() == MIDI::midievent::CONTROL_CHANGE ) | |||
| val = e.msb() / 128.0f; | |||
| else if ( e.opcode() == MIDI::midievent::PITCH_WHEEL ) | |||
| val = e.pitch() / ( MAX_NRPN ); | |||
| // MESSAGE( "sending signal for %s = %f", s, val ); | |||
| sig_map[midi_event].signal->value( val ); | |||
| free( midi_event ); | |||
| break; | |||
| } | |||
| default: | |||
| break; | |||
| } | |||
| // e.pretty_print(); | |||
| } | |||
| osc->wait(20); | |||
| check_nsm(); | |||
| // usleep( 500 ); | |||
| } | |||
| } | |||
| @@ -72,6 +72,13 @@ src/main.C | |||
| uselib = [ 'JACK', 'LIBLO', 'LRDF', 'NTK', 'NTK_IMAGES', 'PTHREAD', 'DL', 'M' ], | |||
| install_path = '${BINDIR}') | |||
| bld.program( source = 'src/midi-to-osc.C', | |||
| target = 'midi-to-osc', | |||
| includes = ['.', 'src', '..', '../nonlib'], | |||
| use = ['nonlib', 'fl_widgets'], | |||
| uselib = [ 'JACK', 'LIBLO', 'LRDF', 'NTK', 'NTK_IMAGES', 'PTHREAD', 'DL', 'M' ], | |||
| install_path = '${BINDIR}') | |||
| bld( features = 'subst', | |||
| source = 'non-mixer.desktop.in', | |||
| target = 'non-mixer.desktop', | |||
| @@ -20,9 +20,10 @@ | |||
| #pragma once | |||
| #include <jack/jack.h> | |||
| #include <jack/midiport.h> | |||
| typedef jack_nframes_t nframes_t; | |||
| typedef float sample_t; | |||
| typedef jack_default_audio_sample_t sample_t; | |||
| #include <list> | |||
| @@ -28,7 +28,7 @@ | |||
| namespace JACK | |||
| { | |||
| static const char *name_for_port ( Port::type_e dir, const char *base, int n, const char *type ); | |||
| static const char *name_for_port ( Port::direction_e dir, const char *base, int n, const char *type ); | |||
| int | |||
| Port::max_name ( void ) | |||
| @@ -43,6 +43,7 @@ namespace JACK | |||
| _client = rhs._client; | |||
| _port = rhs._port; | |||
| _direction = rhs._direction; | |||
| _type = rhs._type; | |||
| _name = strdup( rhs._name ); | |||
| _client->port_added( this ); | |||
| @@ -56,37 +57,44 @@ namespace JACK | |||
| _port = port; | |||
| _name = strdup( jack_port_name( port ) ); | |||
| _direction = jack_port_flags( _port ) == JackPortIsOutput ? Output : Input; | |||
| const char *type = jack_port_type( _port ); | |||
| _type = Audio; | |||
| if ( strstr( type, "MIDI") ) | |||
| _type = MIDI; | |||
| } | |||
| Port::Port ( JACK::Client *client, const char *name, type_e dir ) | |||
| Port::Port ( JACK::Client *client, const char *name, direction_e dir, type_e type ) | |||
| { | |||
| _name = NULL; | |||
| _freezer = NULL; | |||
| _client = client; | |||
| _direction = dir; | |||
| _type = type; | |||
| _name = strdup( name ); | |||
| } | |||
| Port::Port ( JACK::Client *client, type_e dir, const char *base, int n, const char *type ) | |||
| Port::Port ( JACK::Client *client, direction_e dir, type_e type, const char *base, int n, const char *subtype ) | |||
| { | |||
| _name = NULL; | |||
| _freezer = NULL; | |||
| _client = client; | |||
| _name = strdup( name_for_port( dir, base, n, type ) ); | |||
| _name = strdup( name_for_port( dir, base, n, subtype ) ); | |||
| _direction = dir; | |||
| _type = type; | |||
| } | |||
| Port::Port ( JACK::Client *client, type_e dir, int n, const char *type ) | |||
| Port::Port ( JACK::Client *client, direction_e dir, type_e type, int n, const char *subtype ) | |||
| { | |||
| _name = NULL; | |||
| _freezer = NULL; | |||
| _client = client; | |||
| _name = strdup( name_for_port( dir, NULL, n, type ) ); | |||
| _name = strdup( name_for_port( dir, NULL, n, subtype ) ); | |||
| _direction = dir; | |||
| _type = type; | |||
| } | |||
| Port::~Port ( ) | |||
| @@ -118,7 +126,7 @@ namespace JACK | |||
| static const char * | |||
| name_for_port ( Port::type_e dir, const char *base, int n, const char *type ) | |||
| name_for_port ( Port::direction_e dir, const char *base, int n, const char *type ) | |||
| { | |||
| static char pname[ 512 ]; | |||
| @@ -145,7 +153,7 @@ namespace JACK | |||
| } | |||
| bool | |||
| Port::activate ( const char *name, type_e dir ) | |||
| Port::activate ( const char *name, direction_e dir ) | |||
| { | |||
| _name = strdup( name ); | |||
| _direction = dir; | |||
| @@ -157,7 +165,7 @@ namespace JACK | |||
| Port::activate ( void ) | |||
| { | |||
| _port = jack_port_register( _client->jack_client(), _name, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| _type == Audio ? JACK_DEFAULT_AUDIO_TYPE : JACK_DEFAULT_MIDI_TYPE, | |||
| _direction == Output ? JackPortIsOutput : JackPortIsInput, | |||
| 0 ); | |||
| @@ -217,7 +225,7 @@ namespace JACK | |||
| bool | |||
| Port::name ( const char *base, int n, const char *type ) | |||
| { | |||
| return name( name_for_port( this->type(), base, n, type ) ); | |||
| return name( name_for_port( this->direction(), base, n, type ) ); | |||
| } | |||
| void | |||
| @@ -252,12 +260,6 @@ namespace JACK | |||
| return jack_port_get_connections( _port ); | |||
| } | |||
| Port::type_e | |||
| Port::type ( void ) const | |||
| { | |||
| return _direction; | |||
| } | |||
| /** Restore the connections returned by connections() */ | |||
| bool | |||
| Port::connections ( const char **port_names ) | |||
| @@ -41,14 +41,15 @@ namespace JACK | |||
| bool operator < ( const Port & rhs ) const; | |||
| enum type_e { Output, Input }; | |||
| enum direction_e { Output, Input }; | |||
| enum type_e { Audio, MIDI }; | |||
| static int max_name ( void ); | |||
| Port ( JACK::Client *client, jack_port_t *port ); | |||
| Port ( JACK::Client *client, const char *name, type_e dir ); | |||
| Port ( JACK::Client *client, type_e dir, const char *base, int n, const char *type=0 ); | |||
| Port ( JACK::Client *client, type_e dir, int n, const char *type=0 ); | |||
| Port ( JACK::Client *client, const char *name, direction_e dir, type_e type ); | |||
| Port ( JACK::Client *client, direction_e dir, type_e type, const char *base, int n, const char *subtype=0 ); | |||
| Port ( JACK::Client *client, direction_e dir, type_e type, int n, const char *subtype=0 ); | |||
| // Port ( ); | |||
| ~Port ( ); | |||
| @@ -58,7 +59,8 @@ namespace JACK | |||
| bool valid ( void ) const { return _port; } | |||
| bool connected ( void ) const { return jack_port_connected( _port ); } | |||
| type_e type ( void ) const; | |||
| direction_e direction ( void ) const { return _direction; } | |||
| type_e type ( void ) const { return _type; } | |||
| const char * name ( void ) const { return _name; } | |||
| bool name ( const char *name ); | |||
| bool name ( const char *base, int n, const char *type=0 ); | |||
| @@ -85,9 +87,10 @@ namespace JACK | |||
| private: | |||
| type_e _direction; | |||
| direction_e _direction; | |||
| type_e _type; | |||
| bool activate ( const char *name, type_e dir ); | |||
| bool activate ( const char *name, direction_e dir ); | |||
| /* holds all we need to know about a jack port to recreate it on a | |||
| new client */ | |||
| @@ -0,0 +1,215 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2008 Jonathan Moore Liles */ | |||
| /* */ | |||
| /* This program is free software; you can redistribute it and/or modify it */ | |||
| /* under the terms of the GNU General Public License as published by the */ | |||
| /* Free Software Foundation; either version 2 of the License, or (at your */ | |||
| /* option) any later version. */ | |||
| /* */ | |||
| /* This program is distributed in the hope that it will be useful, but WITHOUT */ | |||
| /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ | |||
| /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ | |||
| /* more details. */ | |||
| /* */ | |||
| /* You should have received a copy of the GNU General Public License along */ | |||
| /* with This program; see the file COPYING. If not,write to the Free Software */ | |||
| /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||
| /*******************************************************************************/ | |||
| #include "midievent.H" | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <stdio.h> | |||
| #include "debug.h" | |||
| namespace MIDI | |||
| { | |||
| static const char *opcode_names[] = | |||
| { | |||
| "Note Off", | |||
| "Note On", | |||
| "Aftertouch", | |||
| "Control Change", | |||
| "Program Change", | |||
| "Channel Pressure", | |||
| "Pitch Wheel" | |||
| }; | |||
| midievent::midievent ( void ) | |||
| { | |||
| _sysex = NULL; | |||
| _timestamp = 0; | |||
| _data.status = NOTE_OFF; | |||
| _data.msb = _data.lsb = 0; | |||
| } | |||
| midievent::~midievent ( void ) | |||
| { | |||
| if ( _sysex ) | |||
| delete _sysex; | |||
| _sysex = NULL; | |||
| } | |||
| int | |||
| midievent::pitch ( void ) const | |||
| { | |||
| return ((_data.msb << 7) | _data.lsb) - 0x2000; | |||
| } | |||
| void | |||
| midievent::pitch ( int n ) | |||
| { | |||
| n += 0x2000; | |||
| _data.lsb = n & 0x7F; | |||
| _data.msb = (n >> 7) & 0x7F; | |||
| } | |||
| void | |||
| midievent::data ( byte_t D1, byte_t D2 ) | |||
| { | |||
| _data.lsb = D1 & 0x7F; | |||
| _data.msb = D2 & 0x7F; | |||
| } | |||
| void | |||
| midievent::data ( byte_t *D1, byte_t *D2 ) const | |||
| { | |||
| *D1 = _data.lsb; | |||
| *D2 = _data.msb; | |||
| } | |||
| void | |||
| midievent::raw ( byte_t *p, int l) const | |||
| { | |||
| memcpy( p, &_data, l ); | |||
| } | |||
| int | |||
| midievent::size ( void ) const | |||
| { | |||
| return midievent::event_size( opcode() ); | |||
| } | |||
| void | |||
| midievent::note_velocity ( int vel ) | |||
| { | |||
| _data.msb = vel & 0x7F; | |||
| } | |||
| void | |||
| midievent::note ( char note ) | |||
| { | |||
| _data.lsb = note & 0x7F; | |||
| } | |||
| unsigned char | |||
| midievent::note_velocity ( void ) const | |||
| { | |||
| return _data.msb; | |||
| } | |||
| bool | |||
| midievent::is_same_note ( midievent * e ) const | |||
| { | |||
| return channel() == e->channel() && note() == e->note(); | |||
| } | |||
| /** get name from opcode */ | |||
| const char * | |||
| midievent::name ( void ) const | |||
| { | |||
| return opcode_names[ (opcode() >> 4) - 8 ]; | |||
| } | |||
| /** get opcode from name */ | |||
| int | |||
| midievent::name ( const char *name ) const | |||
| { | |||
| for ( unsigned int i = elementsof( opcode_names ); i--; ) | |||
| if ( ! strcmp( name, opcode_names[ i ] ) ) | |||
| return (i + 8) << 4; | |||
| return -1; | |||
| } | |||
| /** print event in hexadecimal */ | |||
| void | |||
| midievent::print ( void ) const | |||
| { | |||
| printf( "[%06f] %02X %02X %02X\n", | |||
| _timestamp, | |||
| _data.status, | |||
| _data.lsb, | |||
| _data.msb ); | |||
| } | |||
| /** print event in english/decimal */ | |||
| void | |||
| midievent::pretty_print ( void ) const | |||
| { | |||
| printf( | |||
| "[%06f] %-15s c: %2d d1: %3d d2: %3d\n", | |||
| _timestamp, | |||
| name(), | |||
| channel(), | |||
| _data.lsb, | |||
| _data.msb ); | |||
| } | |||
| /*********/ | |||
| /* Sysex */ | |||
| /*********/ | |||
| midievent::sysex::sysex ( void ) | |||
| { | |||
| _data = NULL; | |||
| _size = 0; | |||
| _alloc = 0; | |||
| } | |||
| midievent::sysex::~sysex ( void ) | |||
| { | |||
| if ( _data ) | |||
| free( _data ); | |||
| _data = NULL; | |||
| } | |||
| /** add bytes to sysex message */ | |||
| void | |||
| midievent::sysex::append ( byte_t *data, size_t size ) | |||
| { | |||
| if ( _size + size > _alloc ) | |||
| _data = (byte_t *)realloc( _data, _alloc += 256 ); | |||
| memcpy( data + _size, data, size ); | |||
| _size += size; | |||
| } | |||
| /** return SysEx data */ | |||
| const byte_t * | |||
| midievent::sysex::data ( void ) const | |||
| { | |||
| return _data; | |||
| } | |||
| long | |||
| midievent::sysex::size ( void ) const | |||
| { | |||
| return _size; | |||
| } | |||
| bool | |||
| midievent::operator== ( const midievent &rhs ) const | |||
| { | |||
| return _timestamp == rhs._timestamp && | |||
| ! bcmp( (void*)&_data, (void*)&rhs._data, size() ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,243 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2008 Jonathan Moore Liles */ | |||
| /* */ | |||
| /* This program is free software; you can redistribute it and/or modify it */ | |||
| /* under the terms of the GNU General Public License as published by the */ | |||
| /* Free Software Foundation; either version 2 of the License, or (at your */ | |||
| /* option) any later version. */ | |||
| /* */ | |||
| /* This program is distributed in the hope that it will be useful, but WITHOUT */ | |||
| /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ | |||
| /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ | |||
| /* more details. */ | |||
| /* */ | |||
| /* You should have received a copy of the GNU General Public License along */ | |||
| /* with This program; see the file COPYING. If not,write to the Free Software */ | |||
| /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||
| /*******************************************************************************/ | |||
| /* this class represents a single raw MIDI event plus a timestamp */ | |||
| #pragma once | |||
| #include "types.h" | |||
| #include <stdlib.h> | |||
| namespace MIDI | |||
| { | |||
| class midievent | |||
| { | |||
| public: | |||
| class sysex { | |||
| size_t _size, _alloc; | |||
| byte_t *_data; | |||
| public: | |||
| sysex ( void ); | |||
| ~sysex ( void ); | |||
| void append ( byte_t *data, size_t size ); | |||
| const byte_t * data ( void ) const; | |||
| long size ( void ) const; | |||
| }; | |||
| private: | |||
| sysex *_sysex; | |||
| tick_t _timestamp; /* in ticks */ | |||
| struct { | |||
| byte_t status, /* full status byte */ | |||
| lsb, /* data 1 */ | |||
| msb; /* data 2 */ | |||
| } _data; | |||
| public: | |||
| static inline int | |||
| event_size ( byte_t op ) | |||
| { | |||
| switch ( op ) | |||
| { | |||
| case NOTE_ON: case NOTE_OFF: case AFTERTOUCH: | |||
| case CONTROL_CHANGE: case PITCH_WHEEL: | |||
| return 3; | |||
| case PROGRAM_CHANGE: case CHANNEL_PRESSURE: | |||
| return 2; | |||
| default: | |||
| return 1; | |||
| } | |||
| }; | |||
| /* define MIDI status bytes */ | |||
| enum { | |||
| STATUS_BIT = 0x80, | |||
| NOTE_OFF = 0x80, | |||
| NOTE_ON = 0x90, | |||
| AFTERTOUCH = 0xA0, | |||
| CONTROL_CHANGE = 0xB0, | |||
| PROGRAM_CHANGE = 0xC0, | |||
| CHANNEL_PRESSURE = 0xD0, | |||
| PITCH_WHEEL = 0xE0, | |||
| CLEAR_CHAN_MASK = 0xF0, | |||
| MIDI_CLOCK = 0xF8, | |||
| SYSEX = 0xF0, | |||
| SYSEX_END = 0xF7, | |||
| META = 0xFF | |||
| }; | |||
| midievent ( void ); | |||
| virtual ~midievent ( void ); | |||
| tick_t timestamp ( void ) const; | |||
| void timestamp ( tick_t time ); | |||
| void status ( byte_t status ); | |||
| byte_t status ( void ) const; | |||
| void channel ( byte_t channel ); | |||
| byte_t channel ( void ) const; | |||
| byte_t opcode ( void ) const; | |||
| void opcode ( byte_t o ); | |||
| void lsb ( byte_t n ); | |||
| void msb ( byte_t n ); | |||
| int lsb ( void ) const; | |||
| int msb ( void ) const; | |||
| int pitch ( void ) const; | |||
| void pitch ( int n ); | |||
| void data ( byte_t D1, byte_t D2 ); | |||
| void data ( byte_t *D1, byte_t *D2 ) const; | |||
| void raw ( byte_t *p, int l) const; | |||
| int size ( void ) const; | |||
| void note_velocity ( int vel ); | |||
| bool is_note_on ( void ) const; | |||
| bool is_note_off ( void ) const; | |||
| virtual unsigned char note ( void ) const; | |||
| virtual void note ( char note ); | |||
| unsigned char note_velocity ( void ) const; | |||
| bool is_same_note ( midievent * e ) const; | |||
| const char * name ( void ) const; | |||
| int name ( const char *name ) const; | |||
| void print ( void ) const; | |||
| void pretty_print ( void ) const; | |||
| bool operator< ( const midievent &rhs ) const; | |||
| bool operator>= ( const midievent &rhs ) const; | |||
| bool operator== ( const midievent &rhs ) const; | |||
| }; | |||
| /**********************/ | |||
| /* Inlined accessors */ | |||
| /**********************/ | |||
| inline tick_t | |||
| midievent::timestamp ( void ) const | |||
| { | |||
| return _timestamp; | |||
| } | |||
| inline void | |||
| midievent::timestamp ( tick_t time ) | |||
| { | |||
| _timestamp = time; | |||
| } | |||
| inline void | |||
| midievent::status ( byte_t status ) | |||
| { | |||
| _data.status = status; | |||
| } | |||
| inline byte_t | |||
| midievent::status ( void ) const | |||
| { | |||
| return _data.status; | |||
| } | |||
| inline void | |||
| midievent::channel ( byte_t channel ) | |||
| { | |||
| _data.status = (_data.status & 0xF0) | (channel & 0x0F); | |||
| } | |||
| inline byte_t | |||
| midievent::channel ( void ) const | |||
| { | |||
| return _data.status & 0x0F; | |||
| } | |||
| inline byte_t | |||
| midievent::opcode ( void ) const | |||
| { | |||
| return _data.status & 0xF0; | |||
| } | |||
| inline void | |||
| midievent::opcode ( byte_t opcode ) | |||
| { | |||
| _data.status = (_data.status & 0x0F) | (opcode & 0xF0); | |||
| } | |||
| inline void | |||
| midievent::lsb ( byte_t n ) | |||
| { | |||
| _data.lsb = n & 0x7F; | |||
| } | |||
| inline void | |||
| midievent::msb ( byte_t n ) | |||
| { | |||
| _data.msb = n & 0x7F; | |||
| } | |||
| inline int | |||
| midievent::lsb ( void ) const | |||
| { | |||
| return _data.lsb; | |||
| } | |||
| inline int | |||
| midievent::msb ( void ) const | |||
| { | |||
| return _data.msb; | |||
| } | |||
| inline bool | |||
| midievent::is_note_on ( void ) const | |||
| { | |||
| return (opcode() == NOTE_ON); | |||
| } | |||
| inline bool | |||
| midievent::is_note_off ( void ) const | |||
| { | |||
| return (opcode() == NOTE_OFF); | |||
| } | |||
| inline unsigned char | |||
| midievent::note ( void ) const | |||
| { | |||
| return _data.lsb; | |||
| } | |||
| inline bool | |||
| midievent::operator< ( const midievent &rhs ) const | |||
| { | |||
| return _timestamp < rhs._timestamp; | |||
| } | |||
| inline bool | |||
| midievent::operator>= ( const midievent &rhs ) const | |||
| { | |||
| return _timestamp >= rhs._timestamp; | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2007,2008 Jonathan Moore Liles */ | |||
| /* */ | |||
| /* This program is free software; you can redistribute it and/or modify it */ | |||
| /* under the terms of the GNU General Public License as published by the */ | |||
| /* Free Software Foundation; either version 2 of the License, or (at your */ | |||
| /* option) any later version. */ | |||
| /* */ | |||
| /* This program is distributed in the hope that it will be useful, but WITHOUT */ | |||
| /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ | |||
| /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ | |||
| /* more details. */ | |||
| /* */ | |||
| /* You should have received a copy of the GNU General Public License along */ | |||
| /* with This program; see the file COPYING. If not,write to the Free Software */ | |||
| /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||
| /*******************************************************************************/ | |||
| #pragma once | |||
| typedef unsigned char byte_t; | |||
| typedef double tick_t; | |||
| typedef unsigned int uint; | |||
| #define elementsof(x) (sizeof((x)) / sizeof((x)[0])) | |||
| @@ -131,7 +131,7 @@ namespace OSC | |||
| class Signal | |||
| { | |||
| static int next_id; | |||
| // static int next_id; | |||
| public: | |||
| @@ -152,8 +152,6 @@ namespace OSC | |||
| Peer *_peer; | |||
| int _id; | |||
| char *_path; | |||
| char *_documentation; | |||
| @@ -180,15 +178,12 @@ namespace OSC | |||
| Signal ( const char *path, Direction dir ); | |||
| ~Signal ( ); | |||
| static Signal *get_peer_signal_by_id ( Peer *p, int signal_id ); | |||
| int noutput_connections() { return _outgoing.size(); } | |||
| bool connected ( void ) const { return _outgoing.size() + _incoming.size(); } | |||
| char * get_output_connection_peer_name_and_path ( int n ); | |||
| int id ( void ) const { return _id; } | |||
| Direction direction ( void ) const { return _direction; } | |||
| void parameter_limits ( float min, float max, float default_value ) | |||
| @@ -218,6 +213,8 @@ namespace OSC | |||
| float value ( void ) const { return _value; } | |||
| bool is_connected_to ( const Signal *s ) const; | |||
| void learn_connection ( void ); | |||
| friend class Endpoint; | |||
| }; | |||
| @@ -243,6 +240,9 @@ namespace OSC | |||
| class Endpoint | |||
| { | |||
| Thread _thread; | |||
| friend class Signal; | |||
| Signal *_learn_signal; | |||
| // lo_server_thread _st; | |||
| lo_server _server; | |||
| @@ -259,6 +259,13 @@ namespace OSC | |||
| public: | |||
| std::string path; | |||
| float current_value; | |||
| bool suppress_feedback; | |||
| TranslationDestination ( ) | |||
| { | |||
| suppress_feedback = false; | |||
| current_value = -1.0f; | |||
| } | |||
| }; | |||
| std::map<std::string,TranslationDestination> _translations; | |||
| @@ -291,9 +298,8 @@ namespace OSC | |||
| static void *osc_thread ( void *arg ); | |||
| void osc_thread ( void ); | |||
| OSC::Signal *find_signal_by_id ( int id ); | |||
| OSC::Signal *find_peer_signal_by_path ( Peer *p, const char *path ); | |||
| OSC::Signal *find_peer_signal_by_id ( Peer *p, int id ); | |||
| OSC::Signal *find_signal_by_path ( const char *path ); | |||
| Peer *find_peer_by_name ( const char *name ); | |||
| Peer *find_peer_by_address ( lo_address addr ); | |||
| @@ -309,7 +315,7 @@ namespace OSC | |||
| void *_peer_signal_notification_userdata; | |||
| public: | |||
| void send_feedback ( const char *path, float v ); | |||
| void learn ( const char *path ); | |||
| @@ -318,8 +324,14 @@ namespace OSC | |||
| return _addr; | |||
| } | |||
| void clear_translations ( void ); | |||
| void del_translation ( const char *a ); | |||
| void add_translation ( const char *a, const char *b ); | |||
| void rename_translation_destination ( const char *a, const char *b ); | |||
| void rename_translation_source ( const char *a, const char *b ); | |||
| int ntranslations ( void ); | |||
| bool get_translation ( int n, const char **from, const char **to ); | |||
| void peer_signal_notification_callback ( void (*cb)(OSC::Signal *, OSC::Signal::State, void*), void *userdata ) | |||
| { | |||
| _peer_signal_notification_callback = cb; | |||
| @@ -338,10 +350,10 @@ namespace OSC | |||
| ~Endpoint ( ); | |||
| bool disconnect_signal ( OSC::Signal *s, OSC::Signal *d ); | |||
| bool disconnect_signal ( OSC::Signal *s, const char *peer_name, const char *signal_path ); | |||
| bool disconnect_signal ( OSC::Signal *s, const char *signal_path ); | |||
| bool connect_signal ( OSC::Signal *s, OSC::Signal *d ); | |||
| bool connect_signal ( OSC::Signal *s, const char *peer_name, const char *signal_path ); | |||
| bool connect_signal ( OSC::Signal *s, const char *peer_name, int signal_id ); | |||
| // bool connect_signal ( OSC::Signal *s, const char *peer_name, int signal_id ); | |||
| bool connect_signal ( OSC::Signal *s, const char *peer_and_path ); | |||
| Signal * add_signal ( const char *path, Signal::Direction dir, float min, float max, float default_value, signal_handler handler, void *user_data ); | |||
| @@ -395,6 +407,8 @@ namespace OSC | |||
| int send ( lo_address to, const char *path, lo_message msg ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, float v4, float v5, float v6 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, float v4, float v5, float v6 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, float v5, float v6, float v7 ); | |||
| @@ -19,6 +19,7 @@ Thread.C | |||
| debug.C | |||
| dsp.C | |||
| file.C | |||
| MIDI/midievent.C | |||
| string_util.C | |||
| ''', | |||
| includes = '.', | |||
| @@ -133,17 +133,45 @@ Control_Sequence::name ( const char *s ) | |||
| if ( mode() == CV ) | |||
| update_port_name(); | |||
| else | |||
| update_osc_path(); | |||
| redraw(); | |||
| } | |||
| void | |||
| Control_Sequence::update_osc_path ( void ) | |||
| { | |||
| char *path; | |||
| asprintf( &path, "/track/%s/%s", track()->name(), name() ); | |||
| char *s = escape_url( path ); | |||
| free( path ); | |||
| path = s; | |||
| if ( !_osc_output() ) | |||
| { | |||
| OSC::Signal *t = timeline->osc->add_signal( path, OSC::Signal::Output, 0, 1, 0, NULL, NULL ); | |||
| _osc_output( t ); | |||
| } | |||
| else | |||
| { | |||
| _osc_output()->rename( path ); | |||
| } | |||
| free(path); | |||
| } | |||
| void | |||
| Control_Sequence::update_port_name ( void ) | |||
| { | |||
| bool needs_activation = false; | |||
| if ( ! _output ) | |||
| { | |||
| _output = new JACK::Port( engine, JACK::Port::Output, track()->name(), track()->ncontrols(), "cv" ); | |||
| _output = new JACK::Port( engine, JACK::Port::Output, JACK::Port::Audio, track()->name(), track()->ncontrols(), "cv" ); | |||
| needs_activation = true; | |||
| } | |||
| @@ -332,23 +360,7 @@ Control_Sequence::mode ( Mode m ) | |||
| } | |||
| else if ( OSC == m && mode() != OSC ) | |||
| { | |||
| /* FIXME: use name here... */ | |||
| char *path; | |||
| asprintf( &path, "/track/%s/%s", track()->name(), name() ); | |||
| char *s = escape_url( path ); | |||
| free( path ); | |||
| path = s; | |||
| OSC::Signal *t = timeline->osc->add_signal( path, OSC::Signal::Output, 0, 1, 0, NULL, NULL ); | |||
| free( path ); | |||
| _osc_output( t ); | |||
| DMESSAGE( "osc_output: %p", _osc_output() ); | |||
| update_osc_path(); | |||
| header()->outputs_indicator->label( "osc" ); | |||
| } | |||
| @@ -524,24 +536,21 @@ Control_Sequence::menu_cb ( const Fl_Menu_ *m ) | |||
| const char *path = ((OSC::Signal*)m->mvalue()->user_data())->path(); | |||
| char *peer_and_path; | |||
| asprintf( &peer_and_path, "%s:%s", peer_name, path ); | |||
| if ( ! _osc_output()->is_connected_to( ((OSC::Signal*)m->mvalue()->user_data()) ) ) | |||
| { | |||
| _persistent_osc_connections.push_back( peer_and_path ); | |||
| _persistent_osc_connections.push_back( strdup(path) ); | |||
| connect_osc(); | |||
| } | |||
| else | |||
| { | |||
| timeline->osc->disconnect_signal( _osc_output(), peer_name, path ); | |||
| timeline->osc->disconnect_signal( _osc_output(), path ); | |||
| for ( std::list<char*>::iterator i = _persistent_osc_connections.begin(); | |||
| i != _persistent_osc_connections.end(); | |||
| ++i ) | |||
| { | |||
| if ( !strcmp( *i, peer_and_path ) ) | |||
| if ( !strcmp( *i, path ) ) | |||
| { | |||
| free( *i ); | |||
| i = _persistent_osc_connections.erase( i ); | |||
| @@ -549,7 +558,7 @@ Control_Sequence::menu_cb ( const Fl_Menu_ *m ) | |||
| } | |||
| } | |||
| free( peer_and_path ); | |||
| //free( path ); | |||
| } | |||
| } | |||
| @@ -619,7 +628,7 @@ Control_Sequence::process_osc ( void ) | |||
| if ( mode() != OSC ) | |||
| return; | |||
| if ( _osc_output() && _osc_output()->connected() ) | |||
| if ( _osc_output() ) | |||
| { | |||
| sample_t buf[1]; | |||
| @@ -646,10 +655,12 @@ Control_Sequence::peer_callback( OSC::Signal *sig, OSC::Signal::State state, vo | |||
| if ( sig->direction() != OSC::Signal::Input ) | |||
| return; | |||
| // DMESSAGE( "Paramter limits: %f %f", sig->parameter_limits().min, sig->parameter_limits().max ); | |||
| /* only list CV signals for now */ | |||
| if ( ! ( sig->parameter_limits().min == 0.0 && | |||
| sig->parameter_limits().max == 1.0 ) ) | |||
| return; | |||
| return; | |||
| if ( ! v ) | |||
| { | |||
| @@ -669,11 +680,12 @@ Control_Sequence::peer_callback( OSC::Signal *sig, OSC::Signal::State state, vo | |||
| unescape_url( path ); | |||
| asprintf( &s, "%s/%s%s", peer_prefix, name, path ); | |||
| asprintf( &s, "%s/%s", peer_prefix, path ); | |||
| peer_menu->add( s, 0, NULL, (void*)( sig ), 0 ); | |||
| peer_menu->add( s, 0, NULL, (void*)( sig ), | |||
| FL_MENU_TOGGLE | | |||
| ( ((Control_Sequence*)v)->_osc_output()->is_connected_to( sig ) ? FL_MENU_VALUE : 0 ) ); | |||
| /* FL_MENU_TOGGLE | */ | |||
| /* ( ((Control_Sequence*)v)->_osc_output()->is_connected_to( sig ) ? FL_MENU_VALUE : 0 ) ); */ | |||
| free( path ); | |||
| @@ -102,6 +102,7 @@ protected: | |||
| void draw ( void ); | |||
| int handle ( int m ); | |||
| void update_osc_path ( void ); | |||
| void update_port_name ( void ); | |||
| @@ -86,7 +86,7 @@ Track::configure_outputs ( int n ) | |||
| { | |||
| for ( int i = on; i < n; ++i ) | |||
| { | |||
| JACK::Port p( engine, JACK::Port::Output, name(), i ); | |||
| JACK::Port p( engine, JACK::Port::Output, JACK::Port::Audio, name(), i ); | |||
| if ( !p.activate() ) | |||
| { | |||
| @@ -139,7 +139,7 @@ Track::configure_inputs ( int n ) | |||
| { | |||
| for ( int i = on; i < n; ++i ) | |||
| { | |||
| JACK::Port p( engine, JACK::Port::Input, name(), i ); | |||
| JACK::Port p( engine, JACK::Port::Input, JACK::Port::Audio, name(), i ); | |||
| if ( !p.activate() ) | |||
| { | |||
| @@ -73,7 +73,7 @@ OSC_Thread::process ( void ) | |||
| unlock(); | |||
| } | |||
| usleep( 100 * 1000 ); | |||
| usleep( 50 * 1000 ); | |||
| } | |||
| DMESSAGE( "OSC Thread stopping." ); | |||
| @@ -74,7 +74,7 @@ bool Timeline::snap_magnetic = true; | |||
| bool Timeline::follow_playhead = true; | |||
| bool Timeline::center_playhead = true; | |||
| const float UPDATE_FREQ = 1.0 / 18.0f; | |||
| const float UPDATE_FREQ = 1.0f / 18.0f; | |||
| extern const char *instance_name; | |||
| extern TLE *tle; | |||