OSC: Simplify OSC signal protocol. Add midi<->osc gateway program.
This commit is contained in:
parent
37d5dd87b9
commit
6adf6a9389
|
@ -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
|
widget_class Fl_Text_Edit_Window {open
|
||||||
xywh {375 272 355 410} type Double resizable
|
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 {
|
Fl_Text_Editor text_editor {selected
|
||||||
xywh {5 58 345 320} resizable
|
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();
|
||||||
|
|
||||||
mixer->osc_endpoint->learn( p->osc_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( 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
|
void
|
||||||
mixer_show_tooltip ( const char *s )
|
Mixer::show_tooltip ( const char *s )
|
||||||
{
|
{
|
||||||
mixer->status( s );
|
mixer->_status->label( s );
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
mixer_hide_tooltip ( 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_show_tooltip = &Mixer::show_tooltip;
|
||||||
fl_hide_tooltip = mixer_hide_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/Remote Control/Start Learning", FL_F + 9, 0, 0 );
|
||||||
o->add( "&Mixer/Stop Learning", FL_F + 10, 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 ( 3 != sscanf( path, "%a[^/]/strip#/%d/%a[^\n]", &client_name, &n, &rem ) )
|
||||||
|
|
||||||
if ( 2 != sscanf( path, "/strip#/%d/%a[^\n]", &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 *path = osc_number_path();
|
||||||
|
|
||||||
char *s = strdup( _scaled_signal->path() );
|
mixer->osc_endpoint->send_feedback( path, f );
|
||||||
|
|
||||||
char *suffix = index( s, '/' );
|
free(path);
|
||||||
suffix = index( suffix, '/' );
|
|
||||||
suffix = index( suffix, '/' );
|
|
||||||
|
|
||||||
char *path;
|
|
||||||
asprintf( &path, "/strip#/%i%s", suffix );
|
|
||||||
|
|
||||||
mixer->osc_endpoint->send_feedback( path, control_value() );
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, const char *name, direction_e dir, type_e type );
|
||||||
Port ( JACK::Client *client, type_e dir, const char *base, int n, const char *type=0 );
|
Port ( JACK::Client *client, direction_e dir, type_e type, const char *base, int n, const char *subtype=0 );
|
||||||
Port ( JACK::Client *client, type_e dir, int n, const char *type=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]))
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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... */
|
update_osc_path();
|
||||||
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() );
|
|
||||||
|
|
||||||
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 ),
|
peer_menu->add( s, 0, NULL, (void*)( sig ), 0 );
|
||||||
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;
|
||||||
|
|
Loading…
Reference in New Issue