@@ -1,20 +1,21 @@ | |||||
# data file for the Fltk User Interface Designer (fluid) | # data file for the Fltk User Interface Designer (fluid) | ||||
version 1.0108 | |||||
version 1.0300 | |||||
header_name {.H} | header_name {.H} | ||||
code_name {.C} | 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 );} | code0 {this->size_range( 0, 0, 400, 400 );} | ||||
class Fl_Window modal visible | |||||
class Fl_Double_Window modal visible | |||||
} { | } { | ||||
Fl_Box title_box { | Fl_Box title_box { | ||||
label {<title>} | 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 );} | code0 {o->buffer( new Fl_Text_Buffer );} | ||||
} | } | ||||
Fl_Group {} {open | 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 ); | code {Fl_Text_Edit_Window tew( 355, 410, title ); | ||||
tew.size( W, H ); | |||||
tew.return_button->label( button_text ); | tew.return_button->label( button_text ); | ||||
tew.title_box->label( title ); | tew.title_box->label( title ); | ||||
if ( initial_text ) | if ( initial_text ) | ||||
@@ -313,6 +313,12 @@ Chain::remove ( Controller_Module *m ) | |||||
redraw(); | 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, | /* remove a module from the chain. this isn't guaranteed to succeed, | ||||
* because removing the module might result in an invalid routing */ | * because removing the module might result in an invalid routing */ | ||||
@@ -104,6 +104,8 @@ public: | |||||
const char *name ( void ) const { return _name; } | const char *name ( void ) const { return _name; } | ||||
void name ( const char *name ); | void name ( const char *name ); | ||||
void send_feedback ( void ); | |||||
int get_module_instance_number ( Module *m ); | int get_module_instance_number ( Module *m ); | ||||
void configure_ports ( void ); | void configure_ports ( void ); | ||||
@@ -45,8 +45,10 @@ | |||||
// needed for mixer->endpoint | // needed for mixer->endpoint | ||||
#include "Mixer.H" | #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() ) | 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(); | 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() ) | if ( ! po.activate() ) | ||||
{ | { | ||||
@@ -549,9 +551,24 @@ Controller_Module::handle ( int m ) | |||||
if ( p ) | 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; | return 1; | ||||
@@ -44,6 +44,7 @@ public: | |||||
static bool _learn_mode; | static bool _learn_mode; | ||||
static bool learn_by_number; | |||||
static bool learn_mode ( void ) { return _learn_mode; } | static bool learn_mode ( void ) { return _learn_mode; } | ||||
static void learn_mode ( bool b ) { _learn_mode = b; } | static void learn_mode ( bool b ) { _learn_mode = b; } | ||||
@@ -196,7 +196,7 @@ get_connections_for_ports ( std::vector<JACK::Port> ports ) | |||||
if ( ! connections ) | if ( ! connections ) | ||||
return names; | 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++ ) | for ( const char **c = connections; *c; c++ ) | ||||
{ | { | ||||
@@ -356,9 +356,9 @@ JACK_Module::configure_inputs ( int n ) | |||||
JACK::Port *po = NULL; | JACK::Port *po = NULL; | ||||
if ( !_prefix ) | 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 | 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() ) | if ( ! po->activate() ) | ||||
{ | { | ||||
@@ -415,9 +415,9 @@ JACK_Module::configure_outputs ( int n ) | |||||
JACK::Port *po = NULL; | JACK::Port *po = NULL; | ||||
if ( !_prefix ) | 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 | 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() ) | if ( ! po->activate() ) | ||||
{ | { | ||||
@@ -37,7 +37,7 @@ | |||||
#include <FL/Fl_File_Chooser.H> | #include <FL/Fl_File_Chooser.H> | ||||
#include <FL/Fl_Theme_Chooser.H> | #include <FL/Fl_Theme_Chooser.H> | ||||
#include <FL/Fl_Value_SliderX.H> | #include <FL/Fl_Value_SliderX.H> | ||||
#include "FL/Fl_Text_Edit_Window.H" | |||||
#include "file.h" | #include "file.h" | ||||
#include <string.h> | #include <string.h> | ||||
@@ -50,6 +50,8 @@ | |||||
#include "Controller_Module.H" | #include "Controller_Module.H" | ||||
const double FEEDBACK_UPDATE_FREQ = 1.0f; | |||||
extern char *user_config_dir; | extern char *user_config_dir; | ||||
extern char *instance_name; | 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(); | OSC_DMSG(); | ||||
Fl::lock(); | Fl::lock(); | ||||
((Mixer*)(OSC_ENDPOINT())->owner)->command_add_strip(); | ((Mixer*)(OSC_ENDPOINT())->owner)->command_add_strip(); | ||||
Fl::unlock(); | Fl::unlock(); | ||||
@@ -258,6 +261,10 @@ void Mixer::cb_menu(Fl_Widget* o) { | |||||
{ | { | ||||
command_add_strip(); | command_add_strip(); | ||||
} | } | ||||
else if ( !strcmp( picked, "&Mixer/Send Feedback" ) ) | |||||
{ | |||||
send_feedback(); | |||||
} | |||||
else if ( !strcmp( picked, "&Mixer/Add &N Strips" ) ) | else if ( !strcmp( picked, "&Mixer/Add &N Strips" ) ) | ||||
{ | { | ||||
const char *s = fl_input( "Enter number of strips to add" ); | 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!" ); | 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 ); | 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(); | redraw(); | ||||
} | } | ||||
else if ( ! strcmp( picked, "&Mixer/Stop Learning" ) ) | |||||
else if ( ! strcmp( picked, "&Mixer/Remote Control/Stop Learning" ) ) | |||||
{ | { | ||||
Controller_Module::learn_mode( false ); | Controller_Module::learn_mode( false ); | ||||
status( "Learning complete" ); | |||||
tooltip( "Learning complete" ); | |||||
redraw(); | 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" ) ) | else if ( !strcmp( picked, "&Mixer/Paste" ) ) | ||||
{ | { | ||||
Fl::paste(*this); | 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::hoverdelay( 0 ); | ||||
Fl_Tooltip::delay( 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::size( 11 ); */ | ||||
/* Fl_Tooltip::textcolor( FL_FOREGROUND_COLOR ); */ | /* Fl_Tooltip::textcolor( FL_FOREGROUND_COLOR ); */ | ||||
/* Fl_Tooltip::color( fl_color_add_alpha( FL_DARK1, 0 ) ); */ | /* 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/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/&Rows/Three", '3', 0, 0, FL_MENU_RADIO ); | ||||
o->add( "&Project/Se&ttings/Make Default", 0,0,0); | 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/&Save", FL_CTRL + 's', 0, 0 ); | ||||
o->add( "&Project/&Quit", FL_CTRL + 'q', 0, 0 ); | o->add( "&Project/&Quit", FL_CTRL + 'q', 0, 0 ); | ||||
o->add( "&Mixer/&Add Strip", 'a', 0, 0 ); | o->add( "&Mixer/&Add Strip", 'a', 0, 0 ); | ||||
o->add( "&Mixer/Add &N Strips" ); | o->add( "&Mixer/Add &N Strips" ); | ||||
o->add( "&Mixer/Send Feedback" ); | |||||
o->add( "&Mixer/&Import Strip" ); | o->add( "&Mixer/&Import Strip" ); | ||||
o->add( "&Mixer/Paste", FL_CTRL + 'v', 0, 0 ); | 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( "&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/&Manual" ); | ||||
o->add( "&Help/&About" ); | o->add( "&Help/&About" ); | ||||
o->callback( cb_menu, this ); | 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 | } // Fl_Blink_Button* sm_blinker | ||||
o->end(); | 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->box( FL_FLAT_BOX ); | ||||
// o->type( Fl_Scroll::HORIZONTAL_ALWAYS ); | // o->type( Fl_Scroll::HORIZONTAL_ALWAYS ); | ||||
// o->box( Fl_Scroll::BOTH ); | // 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 ); | update_frequency( 15 ); | ||||
Fl::add_timeout( FEEDBACK_UPDATE_FREQ, send_feedback_cb, this ); | |||||
update_menu(); | update_menu(); | ||||
load_options(); | load_options(); | ||||
@@ -549,16 +579,13 @@ Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, | |||||
{ | { | ||||
int n; | int n; | ||||
char *rem; | char *rem; | ||||
char *client_name; | |||||
OSC::Endpoint *ep = (OSC::Endpoint*)user_data; | 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; | return -1; | ||||
DMESSAGE( "%s", rem ); | |||||
Mixer_Strip *o = mixer->track_by_number( n ); | Mixer_Strip *o = mixer->track_by_number( n ); | ||||
if ( ! o ) | if ( ! o ) | ||||
@@ -569,12 +596,10 @@ Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, | |||||
char *new_path; | 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 ); | free( rem ); | ||||
DMESSAGE( "Sending %s", new_path ); | |||||
lo_send_message( ep->address(), new_path, msg ); | lo_send_message( ep->address(), new_path, msg ); | ||||
free( new_path ); | free( new_path ); | ||||
@@ -582,6 +607,79 @@ Mixer::osc_strip_by_number ( const char *path, const char *types, lo_arg **argv, | |||||
return 0; | 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 | int | ||||
Mixer::init_osc ( const char *osc_port ) | Mixer::init_osc ( const char *osc_port ) | ||||
{ | { | ||||
@@ -601,6 +699,8 @@ Mixer::init_osc ( const char *osc_port ) | |||||
osc_endpoint->start(); | osc_endpoint->start(); | ||||
osc_endpoint->add_method( NULL, NULL, osc_strip_by_number, osc_endpoint, ""); | |||||
return 0; | return 0; | ||||
} | } | ||||
@@ -613,7 +713,9 @@ Mixer::~Mixer ( ) | |||||
Fl::remove_timeout( &Mixer::update_cb, this ); | Fl::remove_timeout( &Mixer::update_cb, this ); | ||||
/* FIXME: teardown */ | |||||
Fl::remove_timeout( &Mixer::send_feedback_cb, this ); | |||||
/* FIXME: teardown */ | |||||
mixer_strips->clear(); | 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 ); | 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 ); | rows( _rows ); | ||||
} | } | ||||
@@ -841,6 +943,8 @@ Mixer::save ( void ) | |||||
MESSAGE( "Saving state" ); | MESSAGE( "Saving state" ); | ||||
Loggable::snapshot_callback( &Mixer::snapshot, this ); | Loggable::snapshot_callback( &Mixer::snapshot, this ); | ||||
Loggable::snapshot( "snapshot" ); | Loggable::snapshot( "snapshot" ); | ||||
save_translations(); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -872,6 +976,27 @@ Mixer::update_menu ( void ) | |||||
project_name->label( Project::name() ); | 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 | int | ||||
@@ -916,6 +1041,12 @@ Mixer::handle ( int m ) | |||||
/* Commands */ | /* Commands */ | ||||
/************/ | /************/ | ||||
void | |||||
Mixer::command_clear_mappings ( void ) | |||||
{ | |||||
osc_endpoint->clear_translations(); | |||||
} | |||||
bool | bool | ||||
Mixer::command_save ( void ) | Mixer::command_save ( void ) | ||||
{ | { | ||||
@@ -950,6 +1081,8 @@ Mixer::command_load ( const char *path, const char *display_name ) | |||||
load_project_settings(); | load_project_settings(); | ||||
load_translations(); | |||||
update_menu(); | update_menu(); | ||||
mixer->activate(); | mixer->activate(); | ||||
@@ -46,6 +46,9 @@ private: | |||||
float _update_interval; | float _update_interval; | ||||
static void show_tooltip ( const char *s ); | |||||
static void hide_tooltip ( void ); | |||||
int _rows; | int _rows; | ||||
int _strip_height; | int _strip_height; | ||||
@@ -71,7 +74,11 @@ private: | |||||
void load_options ( void ); | void load_options ( void ); | ||||
void save_options ( void ); | void save_options ( void ); | ||||
void update_menu ( 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 ); | void redraw_windows ( void ); | ||||
static void handle_dirty ( int, void *v ); | static void handle_dirty ( int, void *v ); | ||||
@@ -135,7 +142,9 @@ public: | |||||
void load_project_settings ( void ); | void load_project_settings ( void ); | ||||
public: | public: | ||||
void edit_translations ( void ); | |||||
void command_clear_mappings ( void ); | |||||
void command_new ( void ); | void command_new ( void ); | ||||
bool command_save ( void ); | bool command_save ( void ); | ||||
bool command_load ( const char *path, const char *display_name = 0 ); | bool command_load ( const char *path, const char *display_name = 0 ); | ||||
@@ -770,6 +770,13 @@ Mixer_Strip::handle ( int m ) | |||||
return 0; | return 0; | ||||
} | } | ||||
void | |||||
Mixer_Strip::send_feedback ( void ) | |||||
{ | |||||
if ( _chain ) | |||||
_chain->send_feedback(); | |||||
} | |||||
int | int | ||||
Mixer_Strip::number ( void ) const | Mixer_Strip::number ( void ) const | ||||
{ | { | ||||
@@ -141,6 +141,7 @@ protected: | |||||
public: | public: | ||||
void send_feedback ( void ); | |||||
int number ( void ) const; | int number ( void ) const; | ||||
static bool import_strip ( const char *filename ); | 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 | void | ||||
Module::Port::send_feedback ( 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 ) | if ( _scaled_signal ) | ||||
{ | { | ||||
/* send feedback for by_name 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 */ | /* 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 | void | ||||
@@ -273,6 +310,12 @@ Module::Port::connected_osc ( void ) const | |||||
return false; | return false; | ||||
} | } | ||||
void | |||||
Module::Port::learn_osc ( void ) | |||||
{ | |||||
_scaled_signal->learn_connection(); | |||||
} | |||||
char * | char * | ||||
Module::Port::generate_osc_path () | 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_exact ( float v, void *user_data ); | ||||
static int osc_control_change_cv ( float v, void *user_data ); | static int osc_control_change_cv ( float v, void *user_data ); | ||||
void learn_osc ( void ); | |||||
Hints hints; | Hints hints; | ||||
Port ( Module *module, Direction direction, Type type, const char *name = 0 ) | Port ( Module *module, Direction direction, Type type, const char *name = 0 ) | ||||
@@ -176,6 +178,8 @@ public: | |||||
return NULL; | return NULL; | ||||
} | } | ||||
char *osc_number_path ( void ); | |||||
void update_osc_port ( ) | void update_osc_port ( ) | ||||
{ | { | ||||
// if ( INPUT == _direction ) | // if ( INPUT == _direction ) | ||||
@@ -200,6 +204,27 @@ public: | |||||
{ | { | ||||
*((float*)buffer()) = f; | *((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 ) | void control_value ( float f ) | ||||
@@ -405,7 +430,8 @@ public: | |||||
char *get_parameters ( void ) const; | char *get_parameters ( void ) const; | ||||
void set_parameters ( const char * ); | void set_parameters ( const char * ); | ||||
void send_feedback ( void ); | |||||
virtual bool initialize ( void ) { return true; } | virtual bool initialize ( void ) { return true; } | ||||
/* for the given number of inputs, return how many outputs this | /* 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; | 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 | void | ||||
@@ -392,6 +392,8 @@ Module_Parameter_Editor::bind_control ( int i ) | |||||
{ | { | ||||
Module::Port *p = &_module->control_input[i]; | Module::Port *p = &_module->control_input[i]; | ||||
/* p->learn_osc(); */ | |||||
if ( p->connected() ) | if ( p->connected() ) | ||||
/* can only bind once */ | /* can only bind once */ | ||||
return; | 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' ], | uselib = [ 'JACK', 'LIBLO', 'LRDF', 'NTK', 'NTK_IMAGES', 'PTHREAD', 'DL', 'M' ], | ||||
install_path = '${BINDIR}') | 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', | bld( features = 'subst', | ||||
source = 'non-mixer.desktop.in', | source = 'non-mixer.desktop.in', | ||||
target = 'non-mixer.desktop', | target = 'non-mixer.desktop', | ||||
@@ -20,9 +20,10 @@ | |||||
#pragma once | #pragma once | ||||
#include <jack/jack.h> | #include <jack/jack.h> | ||||
#include <jack/midiport.h> | |||||
typedef jack_nframes_t nframes_t; | typedef jack_nframes_t nframes_t; | ||||
typedef float sample_t; | |||||
typedef jack_default_audio_sample_t sample_t; | |||||
#include <list> | #include <list> | ||||
@@ -28,7 +28,7 @@ | |||||
namespace JACK | 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 | int | ||||
Port::max_name ( void ) | Port::max_name ( void ) | ||||
@@ -43,6 +43,7 @@ namespace JACK | |||||
_client = rhs._client; | _client = rhs._client; | ||||
_port = rhs._port; | _port = rhs._port; | ||||
_direction = rhs._direction; | _direction = rhs._direction; | ||||
_type = rhs._type; | |||||
_name = strdup( rhs._name ); | _name = strdup( rhs._name ); | ||||
_client->port_added( this ); | _client->port_added( this ); | ||||
@@ -56,37 +57,44 @@ namespace JACK | |||||
_port = port; | _port = port; | ||||
_name = strdup( jack_port_name( port ) ); | _name = strdup( jack_port_name( port ) ); | ||||
_direction = jack_port_flags( _port ) == JackPortIsOutput ? Output : Input; | _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; | _name = NULL; | ||||
_freezer = NULL; | _freezer = NULL; | ||||
_client = client; | _client = client; | ||||
_direction = dir; | _direction = dir; | ||||
_type = type; | |||||
_name = strdup( name ); | _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; | _name = NULL; | ||||
_freezer = NULL; | _freezer = NULL; | ||||
_client = client; | _client = client; | ||||
_name = strdup( name_for_port( dir, base, n, type ) ); | |||||
_name = strdup( name_for_port( dir, base, n, subtype ) ); | |||||
_direction = dir; | _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; | _name = NULL; | ||||
_freezer = NULL; | _freezer = NULL; | ||||
_client = client; | _client = client; | ||||
_name = strdup( name_for_port( dir, NULL, n, type ) ); | |||||
_name = strdup( name_for_port( dir, NULL, n, subtype ) ); | |||||
_direction = dir; | _direction = dir; | ||||
_type = type; | |||||
} | } | ||||
Port::~Port ( ) | Port::~Port ( ) | ||||
@@ -118,7 +126,7 @@ namespace JACK | |||||
static const char * | 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 ]; | static char pname[ 512 ]; | ||||
@@ -145,7 +153,7 @@ namespace JACK | |||||
} | } | ||||
bool | bool | ||||
Port::activate ( const char *name, type_e dir ) | |||||
Port::activate ( const char *name, direction_e dir ) | |||||
{ | { | ||||
_name = strdup( name ); | _name = strdup( name ); | ||||
_direction = dir; | _direction = dir; | ||||
@@ -157,7 +165,7 @@ namespace JACK | |||||
Port::activate ( void ) | Port::activate ( void ) | ||||
{ | { | ||||
_port = jack_port_register( _client->jack_client(), _name, | _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, | _direction == Output ? JackPortIsOutput : JackPortIsInput, | ||||
0 ); | 0 ); | ||||
@@ -217,7 +225,7 @@ namespace JACK | |||||
bool | bool | ||||
Port::name ( const char *base, int n, const char *type ) | 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 | void | ||||
@@ -252,12 +260,6 @@ namespace JACK | |||||
return jack_port_get_connections( _port ); | return jack_port_get_connections( _port ); | ||||
} | } | ||||
Port::type_e | |||||
Port::type ( void ) const | |||||
{ | |||||
return _direction; | |||||
} | |||||
/** Restore the connections returned by connections() */ | /** Restore the connections returned by connections() */ | ||||
bool | bool | ||||
Port::connections ( const char **port_names ) | Port::connections ( const char **port_names ) | ||||
@@ -41,14 +41,15 @@ namespace JACK | |||||
bool operator < ( const Port & rhs ) const; | 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 ); | static int max_name ( void ); | ||||
Port ( JACK::Client *client, jack_port_t *port ); | 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 ( ); | ||||
~Port ( ); | ~Port ( ); | ||||
@@ -58,7 +59,8 @@ namespace JACK | |||||
bool valid ( void ) const { return _port; } | bool valid ( void ) const { return _port; } | ||||
bool connected ( void ) const { return jack_port_connected( _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; } | const char * name ( void ) const { return _name; } | ||||
bool name ( const char *name ); | bool name ( const char *name ); | ||||
bool name ( const char *base, int n, const char *type=0 ); | bool name ( const char *base, int n, const char *type=0 ); | ||||
@@ -85,9 +87,10 @@ namespace JACK | |||||
private: | 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 | /* holds all we need to know about a jack port to recreate it on a | ||||
new client */ | 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 | class Signal | ||||
{ | { | ||||
static int next_id; | |||||
// static int next_id; | |||||
public: | public: | ||||
@@ -152,8 +152,6 @@ namespace OSC | |||||
Peer *_peer; | Peer *_peer; | ||||
int _id; | |||||
char *_path; | char *_path; | ||||
char *_documentation; | char *_documentation; | ||||
@@ -180,15 +178,12 @@ namespace OSC | |||||
Signal ( const char *path, Direction dir ); | Signal ( const char *path, Direction dir ); | ||||
~Signal ( ); | ~Signal ( ); | ||||
static Signal *get_peer_signal_by_id ( Peer *p, int signal_id ); | |||||
int noutput_connections() { return _outgoing.size(); } | int noutput_connections() { return _outgoing.size(); } | ||||
bool connected ( void ) const { return _outgoing.size() + _incoming.size(); } | bool connected ( void ) const { return _outgoing.size() + _incoming.size(); } | ||||
char * get_output_connection_peer_name_and_path ( int n ); | char * get_output_connection_peer_name_and_path ( int n ); | ||||
int id ( void ) const { return _id; } | |||||
Direction direction ( void ) const { return _direction; } | Direction direction ( void ) const { return _direction; } | ||||
void parameter_limits ( float min, float max, float default_value ) | void parameter_limits ( float min, float max, float default_value ) | ||||
@@ -218,6 +213,8 @@ namespace OSC | |||||
float value ( void ) const { return _value; } | float value ( void ) const { return _value; } | ||||
bool is_connected_to ( const Signal *s ) const; | bool is_connected_to ( const Signal *s ) const; | ||||
void learn_connection ( void ); | |||||
friend class Endpoint; | friend class Endpoint; | ||||
}; | }; | ||||
@@ -243,6 +240,9 @@ namespace OSC | |||||
class Endpoint | class Endpoint | ||||
{ | { | ||||
Thread _thread; | Thread _thread; | ||||
friend class Signal; | |||||
Signal *_learn_signal; | |||||
// lo_server_thread _st; | // lo_server_thread _st; | ||||
lo_server _server; | lo_server _server; | ||||
@@ -259,6 +259,13 @@ namespace OSC | |||||
public: | public: | ||||
std::string path; | std::string path; | ||||
float current_value; | float current_value; | ||||
bool suppress_feedback; | |||||
TranslationDestination ( ) | |||||
{ | |||||
suppress_feedback = false; | |||||
current_value = -1.0f; | |||||
} | |||||
}; | }; | ||||
std::map<std::string,TranslationDestination> _translations; | std::map<std::string,TranslationDestination> _translations; | ||||
@@ -291,9 +298,8 @@ namespace OSC | |||||
static void *osc_thread ( void *arg ); | static void *osc_thread ( void *arg ); | ||||
void osc_thread ( void ); | 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_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_name ( const char *name ); | ||||
Peer *find_peer_by_address ( lo_address addr ); | Peer *find_peer_by_address ( lo_address addr ); | ||||
@@ -309,7 +315,7 @@ namespace OSC | |||||
void *_peer_signal_notification_userdata; | void *_peer_signal_notification_userdata; | ||||
public: | public: | ||||
void send_feedback ( const char *path, float v ); | void send_feedback ( const char *path, float v ); | ||||
void learn ( const char *path ); | void learn ( const char *path ); | ||||
@@ -318,8 +324,14 @@ namespace OSC | |||||
return _addr; | return _addr; | ||||
} | } | ||||
void clear_translations ( void ); | |||||
void del_translation ( const char *a ); | |||||
void add_translation ( const char *a, const char *b ); | 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 ) | void peer_signal_notification_callback ( void (*cb)(OSC::Signal *, OSC::Signal::State, void*), void *userdata ) | ||||
{ | { | ||||
_peer_signal_notification_callback = cb; | _peer_signal_notification_callback = cb; | ||||
@@ -338,10 +350,10 @@ namespace OSC | |||||
~Endpoint ( ); | ~Endpoint ( ); | ||||
bool disconnect_signal ( OSC::Signal *s, OSC::Signal *d ); | 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, 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, 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 ); | 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 ); | 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, 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, 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 ); | 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 | debug.C | ||||
dsp.C | dsp.C | ||||
file.C | file.C | ||||
MIDI/midievent.C | |||||
string_util.C | string_util.C | ||||
''', | ''', | ||||
includes = '.', | includes = '.', | ||||
@@ -133,17 +133,45 @@ Control_Sequence::name ( const char *s ) | |||||
if ( mode() == CV ) | if ( mode() == CV ) | ||||
update_port_name(); | update_port_name(); | ||||
else | |||||
update_osc_path(); | |||||
redraw(); | 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 | void | ||||
Control_Sequence::update_port_name ( void ) | Control_Sequence::update_port_name ( void ) | ||||
{ | { | ||||
bool needs_activation = false; | bool needs_activation = false; | ||||
if ( ! _output ) | 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; | needs_activation = true; | ||||
} | } | ||||
@@ -332,23 +360,7 @@ Control_Sequence::mode ( Mode m ) | |||||
} | } | ||||
else if ( OSC == m && mode() != OSC ) | 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" ); | 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(); | 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()) ) ) | 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(); | connect_osc(); | ||||
} | } | ||||
else | 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(); | for ( std::list<char*>::iterator i = _persistent_osc_connections.begin(); | ||||
i != _persistent_osc_connections.end(); | i != _persistent_osc_connections.end(); | ||||
++i ) | ++i ) | ||||
{ | { | ||||
if ( !strcmp( *i, peer_and_path ) ) | |||||
if ( !strcmp( *i, path ) ) | |||||
{ | { | ||||
free( *i ); | free( *i ); | ||||
i = _persistent_osc_connections.erase( 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 ) | if ( mode() != OSC ) | ||||
return; | return; | ||||
if ( _osc_output() && _osc_output()->connected() ) | |||||
if ( _osc_output() ) | |||||
{ | { | ||||
sample_t buf[1]; | 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 ) | if ( sig->direction() != OSC::Signal::Input ) | ||||
return; | return; | ||||
// DMESSAGE( "Paramter limits: %f %f", sig->parameter_limits().min, sig->parameter_limits().max ); | |||||
/* only list CV signals for now */ | /* only list CV signals for now */ | ||||
if ( ! ( sig->parameter_limits().min == 0.0 && | if ( ! ( sig->parameter_limits().min == 0.0 && | ||||
sig->parameter_limits().max == 1.0 ) ) | sig->parameter_limits().max == 1.0 ) ) | ||||
return; | |||||
return; | |||||
if ( ! v ) | if ( ! v ) | ||||
{ | { | ||||
@@ -669,11 +680,12 @@ Control_Sequence::peer_callback( OSC::Signal *sig, OSC::Signal::State state, vo | |||||
unescape_url( path ); | 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 ); | free( path ); | ||||
@@ -102,6 +102,7 @@ protected: | |||||
void draw ( void ); | void draw ( void ); | ||||
int handle ( int m ); | int handle ( int m ); | ||||
void update_osc_path ( void ); | |||||
void update_port_name ( void ); | void update_port_name ( void ); | ||||
@@ -86,7 +86,7 @@ Track::configure_outputs ( int n ) | |||||
{ | { | ||||
for ( int i = on; i < n; ++i ) | 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() ) | if ( !p.activate() ) | ||||
{ | { | ||||
@@ -139,7 +139,7 @@ Track::configure_inputs ( int n ) | |||||
{ | { | ||||
for ( int i = on; i < n; ++i ) | 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() ) | if ( !p.activate() ) | ||||
{ | { | ||||
@@ -73,7 +73,7 @@ OSC_Thread::process ( void ) | |||||
unlock(); | unlock(); | ||||
} | } | ||||
usleep( 100 * 1000 ); | |||||
usleep( 50 * 1000 ); | |||||
} | } | ||||
DMESSAGE( "OSC Thread stopping." ); | DMESSAGE( "OSC Thread stopping." ); | ||||
@@ -74,7 +74,7 @@ bool Timeline::snap_magnetic = true; | |||||
bool Timeline::follow_playhead = true; | bool Timeline::follow_playhead = true; | ||||
bool Timeline::center_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 const char *instance_name; | ||||
extern TLE *tle; | extern TLE *tle; | ||||