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