OSC: Simplify OSC signal protocol. Add midi<->osc gateway program.

pull/116/head
Jonathan Moore Liles 2013-06-05 16:26:36 -07:00
parent 37d5dd87b9
commit 6adf6a9389
29 changed files with 2004 additions and 521 deletions

View File

@ -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 )

View File

@ -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 */

View File

@ -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 );

View File

@ -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;

View File

@ -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; }

View File

@ -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() )
{

View File

@ -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();

View File

@ -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 );

View File

@ -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
{

View File

@ -141,6 +141,7 @@ protected:
public:
void send_feedback ( void );
int number ( void ) const;
static bool import_strip ( const char *filename );

View File

@ -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 ()
{

View File

@ -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

View File

@ -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;

768
mixer/src/midi-to-osc.C Normal file
View File

@ -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 );
}
}

View File

@ -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',

View File

@ -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>

View File

@ -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 )

View File

@ -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 */

215
nonlib/MIDI/midievent.C Normal file
View File

@ -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() );
}
}

243
nonlib/MIDI/midievent.H Normal file
View File

@ -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;
}
}

28
nonlib/MIDI/types.h Normal file
View File

@ -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

View File

@ -131,7 +131,7 @@ namespace OSC
class Signal
{
static int next_id;
// static int next_id;
public:
@ -152,8 +152,6 @@ namespace OSC
Peer *_peer;
int _id;
char *_path;
char *_documentation;
@ -180,15 +178,12 @@ namespace OSC
Signal ( const char *path, Direction dir );
~Signal ( );
static Signal *get_peer_signal_by_id ( Peer *p, int signal_id );
int noutput_connections() { return _outgoing.size(); }
bool connected ( void ) const { return _outgoing.size() + _incoming.size(); }
char * get_output_connection_peer_name_and_path ( int n );
int id ( void ) const { return _id; }
Direction direction ( void ) const { return _direction; }
void parameter_limits ( float min, float max, float default_value )
@ -218,6 +213,8 @@ namespace OSC
float value ( void ) const { return _value; }
bool is_connected_to ( const Signal *s ) const;
void learn_connection ( void );
friend class Endpoint;
};
@ -243,6 +240,9 @@ namespace OSC
class Endpoint
{
Thread _thread;
friend class Signal;
Signal *_learn_signal;
// lo_server_thread _st;
lo_server _server;
@ -259,6 +259,13 @@ namespace OSC
public:
std::string path;
float current_value;
bool suppress_feedback;
TranslationDestination ( )
{
suppress_feedback = false;
current_value = -1.0f;
}
};
std::map<std::string,TranslationDestination> _translations;
@ -291,9 +298,8 @@ namespace OSC
static void *osc_thread ( void *arg );
void osc_thread ( void );
OSC::Signal *find_signal_by_id ( int id );
OSC::Signal *find_peer_signal_by_path ( Peer *p, const char *path );
OSC::Signal *find_peer_signal_by_id ( Peer *p, int id );
OSC::Signal *find_signal_by_path ( const char *path );
Peer *find_peer_by_name ( const char *name );
Peer *find_peer_by_address ( lo_address addr );
@ -309,7 +315,7 @@ namespace OSC
void *_peer_signal_notification_userdata;
public:
void send_feedback ( const char *path, float v );
void learn ( const char *path );
@ -318,8 +324,14 @@ namespace OSC
return _addr;
}
void clear_translations ( void );
void del_translation ( const char *a );
void add_translation ( const char *a, const char *b );
void rename_translation_destination ( const char *a, const char *b );
void rename_translation_source ( const char *a, const char *b );
int ntranslations ( void );
bool get_translation ( int n, const char **from, const char **to );
void peer_signal_notification_callback ( void (*cb)(OSC::Signal *, OSC::Signal::State, void*), void *userdata )
{
_peer_signal_notification_callback = cb;
@ -338,10 +350,10 @@ namespace OSC
~Endpoint ( );
bool disconnect_signal ( OSC::Signal *s, OSC::Signal *d );
bool disconnect_signal ( OSC::Signal *s, const char *peer_name, const char *signal_path );
bool disconnect_signal ( OSC::Signal *s, const char *signal_path );
bool connect_signal ( OSC::Signal *s, OSC::Signal *d );
bool connect_signal ( OSC::Signal *s, const char *peer_name, const char *signal_path );
bool connect_signal ( OSC::Signal *s, const char *peer_name, int signal_id );
// bool connect_signal ( OSC::Signal *s, const char *peer_name, int signal_id );
bool connect_signal ( OSC::Signal *s, const char *peer_and_path );
Signal * add_signal ( const char *path, Signal::Direction dir, float min, float max, float default_value, signal_handler handler, void *user_data );
@ -395,6 +407,8 @@ namespace OSC
int send ( lo_address to, const char *path, lo_message msg );
int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, float v4, float v5, float v6 );
int send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, float v4, float v5, float v6 );
int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, float v5, float v6, float v7 );

View File

@ -19,6 +19,7 @@ Thread.C
debug.C
dsp.C
file.C
MIDI/midievent.C
string_util.C
''',
includes = '.',

View File

@ -133,17 +133,45 @@ Control_Sequence::name ( const char *s )
if ( mode() == CV )
update_port_name();
else
update_osc_path();
redraw();
}
void
Control_Sequence::update_osc_path ( void )
{
char *path;
asprintf( &path, "/track/%s/%s", track()->name(), name() );
char *s = escape_url( path );
free( path );
path = s;
if ( !_osc_output() )
{
OSC::Signal *t = timeline->osc->add_signal( path, OSC::Signal::Output, 0, 1, 0, NULL, NULL );
_osc_output( t );
}
else
{
_osc_output()->rename( path );
}
free(path);
}
void
Control_Sequence::update_port_name ( void )
{
bool needs_activation = false;
if ( ! _output )
{
_output = new JACK::Port( engine, JACK::Port::Output, track()->name(), track()->ncontrols(), "cv" );
_output = new JACK::Port( engine, JACK::Port::Output, JACK::Port::Audio, track()->name(), track()->ncontrols(), "cv" );
needs_activation = true;
}
@ -332,23 +360,7 @@ Control_Sequence::mode ( Mode m )
}
else if ( OSC == m && mode() != OSC )
{
/* FIXME: use name here... */
char *path;
asprintf( &path, "/track/%s/%s", track()->name(), name() );
char *s = escape_url( path );
free( path );
path = s;
OSC::Signal *t = timeline->osc->add_signal( path, OSC::Signal::Output, 0, 1, 0, NULL, NULL );
free( path );
_osc_output( t );
DMESSAGE( "osc_output: %p", _osc_output() );
update_osc_path();
header()->outputs_indicator->label( "osc" );
}
@ -524,24 +536,21 @@ Control_Sequence::menu_cb ( const Fl_Menu_ *m )
const char *path = ((OSC::Signal*)m->mvalue()->user_data())->path();
char *peer_and_path;
asprintf( &peer_and_path, "%s:%s", peer_name, path );
if ( ! _osc_output()->is_connected_to( ((OSC::Signal*)m->mvalue()->user_data()) ) )
{
_persistent_osc_connections.push_back( peer_and_path );
_persistent_osc_connections.push_back( strdup(path) );
connect_osc();
}
else
{
timeline->osc->disconnect_signal( _osc_output(), peer_name, path );
timeline->osc->disconnect_signal( _osc_output(), path );
for ( std::list<char*>::iterator i = _persistent_osc_connections.begin();
i != _persistent_osc_connections.end();
++i )
{
if ( !strcmp( *i, peer_and_path ) )
if ( !strcmp( *i, path ) )
{
free( *i );
i = _persistent_osc_connections.erase( i );
@ -549,7 +558,7 @@ Control_Sequence::menu_cb ( const Fl_Menu_ *m )
}
}
free( peer_and_path );
//free( path );
}
}
@ -619,7 +628,7 @@ Control_Sequence::process_osc ( void )
if ( mode() != OSC )
return;
if ( _osc_output() && _osc_output()->connected() )
if ( _osc_output() )
{
sample_t buf[1];
@ -646,10 +655,12 @@ Control_Sequence::peer_callback( OSC::Signal *sig, OSC::Signal::State state, vo
if ( sig->direction() != OSC::Signal::Input )
return;
// DMESSAGE( "Paramter limits: %f %f", sig->parameter_limits().min, sig->parameter_limits().max );
/* only list CV signals for now */
if ( ! ( sig->parameter_limits().min == 0.0 &&
sig->parameter_limits().max == 1.0 ) )
return;
return;
if ( ! v )
{
@ -669,11 +680,12 @@ Control_Sequence::peer_callback( OSC::Signal *sig, OSC::Signal::State state, vo
unescape_url( path );
asprintf( &s, "%s/%s%s", peer_prefix, name, path );
asprintf( &s, "%s/%s", peer_prefix, path );
peer_menu->add( s, 0, NULL, (void*)( sig ),
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 );

View File

@ -102,6 +102,7 @@ protected:
void draw ( void );
int handle ( int m );
void update_osc_path ( void );
void update_port_name ( void );

View File

@ -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() )
{

View File

@ -73,7 +73,7 @@ OSC_Thread::process ( void )
unlock();
}
usleep( 100 * 1000 );
usleep( 50 * 1000 );
}
DMESSAGE( "OSC Thread stopping." );

View File

@ -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;