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