| @@ -17,7 +17,7 @@ | |||
| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # | |||
| ############################################################################### | |||
| SUBDIRS=nonlib FL timeline mixer | |||
| SUBDIRS=nonlib FL timeline mixer session | |||
| all: | |||
| @ for dir in $(SUBDIRS); do $(MAKE) -s -C $$dir; done | |||
| @@ -422,7 +422,7 @@ Chain::can_configure_outputs ( Module *m, int n ) const | |||
| unsigned int | |||
| Chain::maximum_name_length ( void ) | |||
| { | |||
| return JACK::Client::maximum_name_length() - ( strlen( APP_NAME ) + 1 + ( instance_name ? strlen( instance_name ) + 1 : 0 ) ); | |||
| return JACK::Client::maximum_name_length() - ( strlen( instance_name ) + 1 ); | |||
| } | |||
| /* rename chain... we have to let our modules know our name has | |||
| @@ -432,7 +432,7 @@ void | |||
| Chain::name ( const char *name ) | |||
| { | |||
| char ename[512]; | |||
| snprintf( ename, sizeof(ename), "%s%s%s/%s", APP_NAME, instance_name ? "." : "", instance_name ? instance_name : "", name ); | |||
| snprintf( ename, sizeof(ename), "%s/%s", instance_name, name ); | |||
| if ( ! _engine ) | |||
| { | |||
| @@ -71,14 +71,14 @@ Controller_Module::Controller_Module ( bool is_default ) : Module( is_default, 5 | |||
| end(); | |||
| Fl::add_timeout( CONTROL_UPDATE_FREQ, update_cb, this ); | |||
| // Fl::add_timeout( CONTROL_UPDATE_FREQ, update_cb, this ); | |||
| log_create(); | |||
| } | |||
| Controller_Module::~Controller_Module ( ) | |||
| { | |||
| Fl::remove_timeout( update_cb, this ); | |||
| // Fl::remove_timeout( update_cb, this ); | |||
| log_destroy(); | |||
| @@ -32,7 +32,7 @@ | |||
| const float METER_UPDATE_FREQ = 0.1f; | |||
| const float METER_UPDATE_FREQ = 0.2f; | |||
| @@ -28,31 +28,39 @@ | |||
| #include <FL/Fl_Menu_Bar.H> | |||
| #include <FL/fl_ask.H> | |||
| #include <FL/Fl.H> | |||
| #include <FL/Fl_File_Chooser.H> | |||
| #include "New_Project_Dialog.H" | |||
| #include "Engine/Engine.H" | |||
| #include "FL/Fl_Flowpack.H" | |||
| #include "Project.H" | |||
| #include "FL/Fl_Menu_Settings.H" | |||
| #include "About_Dialog.H" | |||
| #include <FL/Fl_File_Chooser.H> | |||
| #include "file.h" | |||
| #include <string.h> | |||
| #include "debug.h" | |||
| #include <unistd.h> | |||
| #include <sys/types.h> | |||
| #include "FL/color_scheme.H" | |||
| #include "OSC/Endpoint.H" | |||
| #include <lo/lo.h> | |||
| #include "FL/Fl_Blinker.H" | |||
| const double STATUS_UPDATE_FREQ = 0.2f; | |||
| const double OSC_INTERVAL = 0.1f; | |||
| const double OSC_INTERVAL = 0.2f; | |||
| extern char *user_config_dir; | |||
| extern char *instance_name; | |||
| #include "debug.h" | |||
| #include "NSM.H" | |||
| extern NSM_Client *nsm; | |||
| /* static void update_cb( void *v ) { */ | |||
| /* Fl::repeat_timeout( STATUS_UPDATE_FREQ, update_cb, v ); */ | |||
| @@ -65,69 +73,19 @@ extern char *user_config_dir; | |||
| /* OSC Message Handlers */ | |||
| /************************/ | |||
| #undef OSC_REPLY_OK | |||
| #undef OSC_REPLY_ERR | |||
| #undef OSC_REPLY | |||
| OSC_HANDLER( quit ) | |||
| { | |||
| OSC_DMSG(); | |||
| ((Mixer*)user_data)->command_quit(); | |||
| OSC_REPLY_OK(); | |||
| return 0; | |||
| } | |||
| OSC_HANDLER( save ) | |||
| { | |||
| OSC_DMSG(); | |||
| if ( ((Mixer*)user_data)->command_save() ) | |||
| OSC_REPLY_OK(); | |||
| else | |||
| OSC_REPLY_ERR(); | |||
| return 0; | |||
| } | |||
| OSC_HANDLER( load ) | |||
| { | |||
| OSC_DMSG(); | |||
| const char *project_path = &argv[0]->s; | |||
| const char *project_display_name = &argv[1]->s; | |||
| if ( ((Mixer*)user_data)->command_load( project_path, project_display_name ) ) | |||
| OSC_REPLY_OK(); | |||
| else | |||
| OSC_REPLY_ERR(); | |||
| return 0; | |||
| } | |||
| OSC_HANDLER( new ) | |||
| { | |||
| OSC_DMSG(); | |||
| const char *project_path = &argv[0]->s; | |||
| const char *project_display_name = &argv[1]->s; | |||
| if ( ((Mixer*)user_data)->command_new( project_path, project_display_name ) ) | |||
| OSC_REPLY_OK(); | |||
| else | |||
| OSC_REPLY_ERR(); | |||
| return 0; | |||
| } | |||
| // OSC_HANDLER( root ) | |||
| // { | |||
| // } | |||
| #define OSC_REPLY_OK() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, 0, "OK" ) | |||
| #define OSC_REPLY( value ) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, value ) | |||
| #define OSC_REPLY_ERR(errcode, value) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path,errcode, value ) | |||
| OSC_HANDLER( add_strip ) | |||
| { | |||
| OSC_DMSG(); | |||
| ((Mixer*)user_data)->command_add_strip(); | |||
| ((Mixer*)(OSC_ENDPOINT())->owner)->command_add_strip(); | |||
| OSC_REPLY_OK(); | |||
| @@ -138,20 +96,45 @@ OSC_HANDLER( finger ) | |||
| { | |||
| OSC_DMSG(); | |||
| lo_address src = lo_message_get_source( msg ); | |||
| const char *s = "APP_TITLE\n"; | |||
| /* if ( 1 >= lo_send_from( src, ((Mixer*)user_data)->osc_endpoint, LO_TT_IMMEDIATE, "/finger-reply", "s", s ) ) */ | |||
| /* { */ | |||
| /* DMESSAGE( "Failed to send reply" ); */ | |||
| /* } */ | |||
| OSC::Endpoint *ep = ((OSC::Endpoint*)user_data); | |||
| lo_address reply = lo_address_new_from_url( &argv[0]->s ); | |||
| ep->send( reply, | |||
| "/reply", | |||
| path, | |||
| ep->url(), | |||
| APP_NAME, | |||
| VERSION, | |||
| instance_name ); | |||
| lo_address_free( reply ); | |||
| return 0; | |||
| } | |||
| static | |||
| Fl_Menu_Item * | |||
| find_item( Fl_Menu_ *menu, const char *path ) | |||
| { | |||
| return const_cast<Fl_Menu_Item*>(menu->find_item( path )); | |||
| } | |||
| void | |||
| Mixer::sm_active ( bool b ) | |||
| { | |||
| sm_blinker->value( b ); | |||
| sm_blinker->tooltip( nsm->session_manager_name() ); | |||
| if ( b ) | |||
| { | |||
| find_item( menubar, "&Project/&Open" )->deactivate(); | |||
| find_item( menubar, "&Project/&New" )->deactivate(); | |||
| } | |||
| } | |||
| void Mixer::cb_menu(Fl_Widget* o) { | |||
| Fl_Menu_Bar *menu = (Fl_Menu_Bar*)o; | |||
| @@ -347,6 +330,19 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : | |||
| o->align( FL_ALIGN_INSIDE | FL_ALIGN_CENTER ); | |||
| o->labeltype( FL_SHADOW_LABEL ); | |||
| } | |||
| { sm_blinker = new Fl_Blinker( ( X + W) - 52, Y + 4, 50, 15, "SM"); | |||
| sm_blinker->box(FL_ROUNDED_BOX); | |||
| sm_blinker->down_box(FL_ROUNDED_BOX); | |||
| sm_blinker->color((Fl_Color)75); | |||
| sm_blinker->selection_color((Fl_Color)86); | |||
| sm_blinker->labeltype(FL_NORMAL_LABEL); | |||
| sm_blinker->labelfont(2); | |||
| sm_blinker->labelsize(14); | |||
| sm_blinker->labelcolor(FL_DARK3); | |||
| sm_blinker->align(Fl_Align(FL_ALIGN_CENTER)); | |||
| sm_blinker->when(FL_WHEN_RELEASE); | |||
| sm_blinker->deactivate(); | |||
| } // Fl_Blinker* sm_blinker | |||
| { Fl_Scroll *o = scroll = new Fl_Scroll( X, Y + 24, W, H - 24 ); | |||
| o->box( FL_NO_BOX ); | |||
| // o->type( Fl_Scroll::HORIZONTAL_ALWAYS ); | |||
| @@ -375,22 +371,20 @@ Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : | |||
| load_options(); | |||
| } | |||
| void | |||
| int | |||
| Mixer::init_osc ( const char *osc_port ) | |||
| { | |||
| osc_endpoint = new OSC::Endpoint(osc_port); | |||
| osc_endpoint = new OSC::Endpoint(); | |||
| osc_endpoint->url(); | |||
| if ( int r = osc_endpoint->init( osc_port ) ) | |||
| return r; | |||
| // if ( 1 >= lo_send_from( src, ((Mixer*)user_data)->osc_endpoint, LO_TT_IMMEDIATE, "/finger-reply", "s", s ) ) | |||
| osc_endpoint->owner = this; | |||
| printf( "OSC=%s\n", osc_endpoint->url() ); | |||
| osc_endpoint->add_method( "/nsm/quit", "", OSC_NAME( quit ), this, "" ); | |||
| osc_endpoint->add_method( "/nsm/load", "ss", OSC_NAME( load ), this, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/save", "", OSC_NAME( save ), this, "" ); | |||
| osc_endpoint->add_method( "/nsm/new", "ss", OSC_NAME( new ), this, "path,display_name" ); | |||
| // osc_endpoint->add_method( "/nsm/", "", OSC_NAME( root ), this ); | |||
| osc_endpoint->add_method( "/finger", "", OSC_NAME( finger ), this, "" ); | |||
| osc_endpoint->add_method( "/mixer/add_strip", "", OSC_NAME( add_strip ), this, "" ); | |||
| osc_endpoint->add_method( "/non/finger", "s", OSC_NAME( finger ), osc_endpoint, "" ); | |||
| osc_endpoint->add_method( "/non/mixer/add_strip", "", OSC_NAME( add_strip ), osc_endpoint, "" ); | |||
| // osc_endpoint->start(); | |||
| @@ -680,6 +674,8 @@ Mixer::command_save ( void ) | |||
| bool | |||
| Mixer::command_load ( const char *path, const char *display_name ) | |||
| { | |||
| mixer->hide(); | |||
| if ( int err = Project::open( path ) ) | |||
| { | |||
| // fl_alert( "Error opening project specified on commandline: %s", Project::errstr( err ) ); | |||
| @@ -691,6 +687,8 @@ Mixer::command_load ( const char *path, const char *display_name ) | |||
| update_menu(); | |||
| mixer->show(); | |||
| return true; | |||
| } | |||
| @@ -26,6 +26,7 @@ | |||
| #include <FL/Fl_Pack.H> | |||
| #include "Mixer_Strip.H" | |||
| class Fl_Blinker; | |||
| class Fl_Flowpack; | |||
| class Fl_Menu_Bar; | |||
| @@ -37,9 +38,10 @@ class Mixer : public Fl_Group | |||
| public: | |||
| OSC::Endpoint *osc_endpoint; | |||
| Fl_Blinker *sm_blinker; | |||
| private: | |||
| int _rows; | |||
| Fl_Color system_colors[3]; | |||
| @@ -93,9 +95,11 @@ public: | |||
| static void check_osc ( void * v ); | |||
| void announce ( const char *nash_url ); | |||
| void announce ( const char *nash_url, const char *process_name ); | |||
| int init_osc ( const char* osc_port ); | |||
| void init_osc ( const char* osc_port ); | |||
| void sm_active ( bool b ); | |||
| public: | |||
| @@ -213,11 +213,9 @@ Module::Port::generate_osc_path () | |||
| int n = module()->chain()->get_module_instance_number( module() ); | |||
| if ( n > 0 ) | |||
| asprintf( &path, "/mixer/strip/%s/control/%s.%i/%s", module()->chain()->name(), p->module()->label(), n, p->name() ); | |||
| asprintf( &path, "/non/mixer/strip/%s/control/%s.%i/%s", module()->chain()->name(), p->module()->label(), n, p->name() ); | |||
| else | |||
| asprintf( &path, "/mixer/strip/%s/control/%s/%s", module()->chain()->name(), p->module()->label(), p->name() ); | |||
| // asprintf( &path, "/mixer/strip/control/%s/%s", p->module()->label(), p->name() ); | |||
| asprintf( &path, "/non/mixer/strip/%s/control/%s/%s", module()->chain()->name(), p->module()->label(), p->name() ); | |||
| // Hack to keep spaces out of OSC URL... Probably need to handle other special characters similarly. | |||
| for ( int i = strlen( path ); i--; ) | |||
| @@ -254,6 +252,30 @@ Module::Port::change_osc_path ( char *path ) | |||
| mixer->osc_endpoint->add_method( _osc_path, "f", &Module::Port::osc_control_change_cv, this, "value" ); | |||
| mixer->osc_endpoint->add_method( _osc_path_unscaled, "f", &Module::Port::osc_control_change_exact, this, "value" ); | |||
| if ( hints.ranged ) | |||
| { | |||
| mixer->osc_endpoint->set_parameter_limits( _osc_path_unscaled, "f", 0, | |||
| hints.minimum, | |||
| hints.maximum, | |||
| hints.default_value ); | |||
| } | |||
| float scaled_default = 0.5f; | |||
| if ( hints.ranged ) | |||
| { | |||
| float scale = hints.maximum - hints.minimum; | |||
| // float offset = hints.minimum; | |||
| scaled_default = ( hints.default_value / scale ); | |||
| } | |||
| mixer->osc_endpoint->set_parameter_limits( _osc_path, "f", 0, | |||
| 0.0f, | |||
| 1.0f, | |||
| scaled_default ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,99 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 "const.h" | |||
| #include "debug.h" | |||
| #include "Mixer.H" | |||
| #include "NSM.H" | |||
| #include "Project.H" | |||
| extern char *instance_name; | |||
| extern Mixer *mixer; | |||
| extern NSM_Client *nsm; | |||
| NSM_Client::NSM_Client ( ) | |||
| { | |||
| } | |||
| int command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ); | |||
| int command_save ( char **out_msg ); | |||
| int | |||
| NSM_Client::command_save ( char **out_msg ) | |||
| { | |||
| Fl::lock(); | |||
| int r = ERR_OK; | |||
| if ( ! mixer->command_save() ) | |||
| { | |||
| *out_msg = strdup( "Failed to save for unknown reason"); | |||
| return r = ERR_GENERAL; | |||
| } | |||
| Fl::unlock(); | |||
| return r; | |||
| } | |||
| int | |||
| NSM_Client::command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ) | |||
| { | |||
| Fl::lock(); | |||
| if ( instance_name ) | |||
| free( instance_name ); | |||
| instance_name = strdup( client_id ); | |||
| int r = ERR_OK; | |||
| if ( Project::validate( name ) ) | |||
| { | |||
| if ( ! mixer->command_load( name, display_name ) ) | |||
| { | |||
| *out_msg = strdup( "Failed to load for unknown reason" ); | |||
| r = ERR_GENERAL; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if ( ! mixer->command_new( name, display_name ) ) | |||
| { | |||
| *out_msg = strdup( "Failed to load for unknown reason" ); | |||
| r = ERR_GENERAL; | |||
| } | |||
| } | |||
| Fl::unlock(); | |||
| return r; | |||
| } | |||
| void | |||
| NSM_Client::command_active ( bool active ) | |||
| { | |||
| Fl::lock(); | |||
| mixer->sm_active( active ); | |||
| Fl::unlock(); | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 | |||
| #include "NSM/Client.H" | |||
| class NSM_Client : public NSM::Client | |||
| { | |||
| public: | |||
| NSM_Client ( ); | |||
| ~NSM_Client ( ) { }; | |||
| protected: | |||
| int command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ); | |||
| int command_save ( char **out_msg ); | |||
| void command_active ( bool active ); | |||
| }; | |||
| @@ -304,7 +304,7 @@ Project::create ( const char *name, const char *template_name ) | |||
| if ( mkdir( name, 0777 ) ) | |||
| { | |||
| WARNING( "Cannot create project directory" ); | |||
| WARNING( "Cannot create project directory: %s", name ); | |||
| return false; | |||
| } | |||
| @@ -52,14 +52,21 @@ | |||
| #include "Chain.H" | |||
| #include "Mixer_Strip.H" | |||
| #include "NSM.H" | |||
| #include <signal.h> | |||
| /* TODO: put these in a header */ | |||
| #define USER_CONFIG_DIR ".non-mixer/" | |||
| const double OSC_INTERVAL = 0.1f; | |||
| char *user_config_dir; | |||
| Mixer *mixer; | |||
| NSM_Client *nsm; | |||
| const char *instance_name; | |||
| char *instance_name; | |||
| #include <errno.h> | |||
| @@ -83,6 +90,32 @@ static void cb_main ( Fl_Double_Window *o, void *) | |||
| mixer->command_quit(); | |||
| } | |||
| void | |||
| check_osc ( void * v ) | |||
| { | |||
| nsm->check(); | |||
| Fl::repeat_timeout( OSC_INTERVAL, check_osc, v ); | |||
| } | |||
| static int got_sigterm = 0; | |||
| void | |||
| sigterm_handler ( int ) | |||
| { | |||
| got_sigterm = 1; | |||
| Fl::awake(); | |||
| } | |||
| void | |||
| check_sigterm ( void * ) | |||
| { | |||
| if ( got_sigterm ) | |||
| { | |||
| MESSAGE( "Got SIGTERM, quitting..." ); | |||
| mixer->command_quit(); | |||
| } | |||
| } | |||
| int | |||
| main ( int argc, char **argv ) | |||
| { | |||
| @@ -93,6 +126,8 @@ main ( int argc, char **argv ) | |||
| ensure_dirs(); | |||
| signal( SIGTERM, sigterm_handler ); | |||
| Fl_Tooltip::color( FL_BLACK ); | |||
| Fl_Tooltip::textcolor( FL_YELLOW ); | |||
| Fl_Tooltip::size( 14 ); | |||
| @@ -119,6 +154,8 @@ main ( int argc, char **argv ) | |||
| Fl::get_system_colors(); | |||
| Fl::scheme( "plastic" ); | |||
| Fl::lock(); | |||
| Plugin_Module::spawn_discover_thread(); | |||
| Fl_Double_Window *main_window; | |||
| @@ -142,6 +179,10 @@ main ( int argc, char **argv ) | |||
| const char *osc_port = NULL; | |||
| nsm = new NSM_Client; | |||
| instance_name = strdup( APP_NAME ); | |||
| { | |||
| int r = argc - 1; | |||
| int i = 1; | |||
| @@ -152,7 +193,7 @@ main ( int argc, char **argv ) | |||
| if ( r > 1 ) | |||
| { | |||
| MESSAGE( "Using instance name \"%s\"", argv[i+1] ); | |||
| instance_name = argv[i+1]; | |||
| instance_name = strdup( argv[i+1] ); | |||
| --r; | |||
| ++i; | |||
| } | |||
| @@ -185,17 +226,33 @@ main ( int argc, char **argv ) | |||
| mixer->init_osc( osc_port ); | |||
| if ( r >= 1 ) | |||
| char *nsm_url = getenv( "NSM_URL" ); | |||
| if ( nsm_url ) | |||
| { | |||
| MESSAGE( "Loading \"%s\"", argv[i] ); | |||
| if ( ! mixer->command_load( argv[i] ) ) | |||
| if ( ! nsm->init() ) | |||
| { | |||
| fl_alert( "Error opening project specified on commandline" ); | |||
| nsm->announce( nsm_url, APP_NAME, ":switch:dirty:", argv[0] ); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if ( r >= 1 ) | |||
| { | |||
| MESSAGE( "Loading \"%s\"", argv[i] ); | |||
| if ( ! mixer->command_load( argv[i] ) ) | |||
| { | |||
| fl_alert( "Error opening project specified on commandline" ); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // poll so we can keep OSC handlers running in the GUI thread and avoid extra sync | |||
| Fl::add_timeout( OSC_INTERVAL, check_osc, NULL ); | |||
| Fl::add_check( check_sigterm ); | |||
| Fl::run(); | |||
| @@ -44,7 +44,7 @@ namespace JACK | |||
| jack_client_close( _client ); | |||
| } | |||
| /** Tell JACK to calling process callback. This MUST be called in | |||
| /** Tell JACK to stop calling process callback. This MUST be called in | |||
| * an inheriting class' destructor */ | |||
| void | |||
| Client::deactivate ( ) | |||
| @@ -201,6 +201,15 @@ namespace JACK | |||
| } | |||
| } | |||
| void | |||
| Client::close ( void ) | |||
| { | |||
| jack_deactivate( _client ); | |||
| jack_client_close( _client ); | |||
| _client = NULL; | |||
| } | |||
| const char * | |||
| Client::name ( const char *s ) | |||
| { | |||
| @@ -222,4 +231,28 @@ namespace JACK | |||
| return s; | |||
| } | |||
| void | |||
| Client::transport_stop ( ) | |||
| { | |||
| jack_transport_stop( _client ); | |||
| } | |||
| void | |||
| Client::transport_start ( ) | |||
| { | |||
| jack_transport_start( _client ); | |||
| } | |||
| void | |||
| Client::transport_locate ( nframes_t frame ) | |||
| { | |||
| jack_transport_locate( _client, frame ); | |||
| } | |||
| jack_transport_state_t | |||
| Client::transport_query ( jack_position_t *pos ) | |||
| { | |||
| return jack_transport_query( _client, pos ); | |||
| } | |||
| } | |||
| @@ -89,6 +89,7 @@ namespace JACK | |||
| const char * init ( const char *client_name, unsigned int opts = 0 ); | |||
| const char * name ( const char * ); | |||
| void close ( void ); | |||
| nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); } | |||
| float frame_rate ( void ) const { return jack_get_sample_rate( _client ); } | |||
| static nframes_t sample_rate ( void ) { return _sample_rate; } | |||
| @@ -98,6 +99,13 @@ namespace JACK | |||
| bool zombified ( void ) const { return _zombified; } | |||
| float cpu_load ( void ) const { return jack_cpu_load( _client ); } | |||
| void transport_stop ( void ); | |||
| void transport_start ( void ); | |||
| void transport_locate ( nframes_t frame ); | |||
| jack_transport_state_t transport_query ( jack_position_t *pos ); | |||
| static int maximum_name_length ( void ) { return jack_client_name_size(); } | |||
| }; | |||
| } | |||
| @@ -0,0 +1,264 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 "../debug.h" | |||
| #include "Client.H" | |||
| #include <string.h> | |||
| #include <sys/types.h> | |||
| #include <unistd.h> | |||
| #include <stdlib.h> | |||
| namespace NSM | |||
| { | |||
| /************************/ | |||
| /* OSC Message Handlers */ | |||
| /************************/ | |||
| #undef OSC_REPLY | |||
| #undef OSC_REPLY_ERR | |||
| #define OSC_REPLY( value ) lo_send_from( ((NSM::Client*)user_data)->nsm_addr, ((NSM::Client*)user_data)->_server, LO_TT_IMMEDIATE, "/reply", "ss", path, value ) | |||
| #define OSC_REPLY_ERR( errcode, value ) lo_send_from( ((NSM::Client*)user_data)->nsm_addr, ((NSM::Client*)user_data)->_server, LO_TT_IMMEDIATE, "/error", "sis", path, errcode, value ) | |||
| Client::Client ( ) | |||
| { | |||
| nsm_addr = 0; | |||
| nsm_client_id = 0; | |||
| _session_manager_name = 0; | |||
| nsm_is_active = false; | |||
| _server = 0; | |||
| _st = 0; | |||
| } | |||
| Client::~Client ( ) | |||
| { | |||
| if ( _st ) | |||
| stop(); | |||
| if ( _st ) | |||
| lo_server_thread_free( _st ); | |||
| else | |||
| lo_server_free ( _server ); | |||
| } | |||
| void | |||
| Client::announce ( const char *nash_url, const char *application_name, const char *capabilities, const char *process_name ) | |||
| { | |||
| MESSAGE( "Announcing to NSM" ); | |||
| lo_address to = lo_address_new_from_url( nash_url ); | |||
| if ( ! to ) | |||
| { | |||
| MESSAGE( "Bad address" ); | |||
| return; | |||
| } | |||
| int pid = (int)getpid(); | |||
| lo_send_from( to, _server, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii", | |||
| application_name, | |||
| capabilities, | |||
| process_name, | |||
| 0, /* api_major_version */ | |||
| 5, /* api_minor_version */ | |||
| pid ); | |||
| lo_address_free( to ); | |||
| } | |||
| void | |||
| Client::progress ( float p ) | |||
| { | |||
| if ( nsm_is_active ) | |||
| { | |||
| lo_send_from( nsm_addr, _server, LO_TT_IMMEDIATE, "/nsm/client/progress", "f", p ); | |||
| } | |||
| } | |||
| void | |||
| Client::is_dirty ( void ) | |||
| { | |||
| if ( nsm_is_active ) | |||
| { | |||
| lo_send_from( nsm_addr, _server, LO_TT_IMMEDIATE, "/nsm/client/is_dirty", "" ); | |||
| } | |||
| } | |||
| void | |||
| Client::is_clean ( void ) | |||
| { | |||
| if ( nsm_is_active ) | |||
| { | |||
| lo_send_from( nsm_addr, _server, LO_TT_IMMEDIATE, "/nsm/client/is_clean", "" ); | |||
| } | |||
| } | |||
| void | |||
| Client::message ( int priority, const char *msg ) | |||
| { | |||
| if ( nsm_is_active ) | |||
| { | |||
| lo_send_from( nsm_addr, _server, LO_TT_IMMEDIATE, "/nsm/client/message", "is", priority, msg ); | |||
| } | |||
| } | |||
| void | |||
| Client::check ( ) | |||
| { | |||
| lo_server_recv_noblock( _server, 0 ); | |||
| } | |||
| void | |||
| Client::start ( ) | |||
| { | |||
| lo_server_thread_start( _st ); | |||
| } | |||
| void | |||
| Client::stop ( ) | |||
| { | |||
| lo_server_thread_stop( _st ); | |||
| } | |||
| int | |||
| Client::init ( ) | |||
| { | |||
| _server = lo_server_new( NULL, NULL ); | |||
| if ( ! _server ) | |||
| return -1; | |||
| lo_server_add_method( _server, "/error", "sis", &Client::osc_error, this ); | |||
| lo_server_add_method( _server, "/reply", "ssss", &Client::osc_announce_reply, this ); | |||
| lo_server_add_method( _server, "/nsm/client/open", "sss", &Client::osc_open, this ); | |||
| lo_server_add_method( _server, "/nsm/client/save", "", &Client::osc_save, this ); | |||
| return 0; | |||
| } | |||
| int | |||
| Client::init_thread ( ) | |||
| { | |||
| _st = lo_server_thread_new( NULL, NULL ); | |||
| _server = lo_server_thread_get_server( _st ); | |||
| if ( ! _server || ! _st ) | |||
| return -1; | |||
| lo_server_thread_add_method( _st, "/error", "sis", &Client::osc_error, this ); | |||
| lo_server_thread_add_method( _st, "/reply", "ssss", &Client::osc_announce_reply, this ); | |||
| lo_server_thread_add_method( _st, "/nsm/client/open", "sss", &Client::osc_open, this ); | |||
| lo_server_thread_add_method( _st, "/nsm/client/save", "", &Client::osc_save, this ); | |||
| return 0; | |||
| } | |||
| /************************/ | |||
| /* OSC Message Handlers */ | |||
| /************************/ | |||
| int | |||
| Client::osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| char *out_msg = NULL; | |||
| int r = ((NSM::Client*)user_data)->command_save(&out_msg); | |||
| if ( r ) | |||
| OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") ); | |||
| else | |||
| OSC_REPLY( "OK" ); | |||
| if ( out_msg ) | |||
| free( out_msg ); | |||
| return 0; | |||
| } | |||
| int | |||
| Client::osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| char *out_msg = NULL; | |||
| NSM::Client *nsm = (NSM::Client*)user_data; | |||
| nsm->nsm_client_id = strdup( &argv[2]->s ); | |||
| int r = ((NSM::Client*)user_data)->command_open( &argv[0]->s, &argv[1]->s, &argv[2]->s, &out_msg); | |||
| if ( r ) | |||
| OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") ); | |||
| else | |||
| OSC_REPLY( "OK" ); | |||
| if ( out_msg ) | |||
| free( out_msg ); | |||
| return 0; | |||
| } | |||
| int | |||
| Client::osc_session_is_loaded ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| NSM::Client *nsm = (NSM::Client*)user_data; | |||
| nsm->command_session_is_loaded(); | |||
| return 0; | |||
| } | |||
| int | |||
| Client::osc_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) ) | |||
| return -1; | |||
| NSM::Client *nsm = (NSM::Client*)user_data; | |||
| WARNING( "Failed to register with NSM: %s", &argv[2]->s ); | |||
| nsm->nsm_is_active = false; | |||
| nsm->command_active( nsm->nsm_is_active ); | |||
| return 0; | |||
| } | |||
| int | |||
| Client::osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) ) | |||
| return -1; | |||
| NSM::Client *nsm = (NSM::Client*)user_data; | |||
| MESSAGE( "Successfully registered. NSM says: %s", &argv[1]->s ); | |||
| nsm->nsm_is_active = true; | |||
| nsm->_session_manager_name = strdup( &argv[2]->s ); | |||
| nsm->nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) )); | |||
| nsm->command_active( nsm->nsm_is_active ); | |||
| return 0; | |||
| } | |||
| }; | |||
| @@ -0,0 +1,101 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 | |||
| #include <lo/lo.h> | |||
| namespace NSM | |||
| { | |||
| class Client | |||
| { | |||
| private: | |||
| lo_server _server; | |||
| lo_server_thread _st; | |||
| lo_address nsm_addr; | |||
| bool nsm_is_active; | |||
| char *nsm_client_id; | |||
| char *_session_manager_name; | |||
| public: | |||
| enum | |||
| { | |||
| ERR_OK = 0, | |||
| ERR_GENERAL = -1, | |||
| ERR_INCOMPATIBLE_API = -2, | |||
| ERR_BLACKLISTED = -3, | |||
| ERR_LAUNCH_FAILED = -4, | |||
| ERR_NO_SUCH_FILE = -5, | |||
| ERR_NO_SESSION_OPEN = -6, | |||
| ERR_UNSAVED_CHANGES = -7, | |||
| ERR_NOT_NOW = -8 | |||
| }; | |||
| Client ( ); | |||
| virtual ~Client ( ); | |||
| bool is_active ( void ) { return nsm_is_active; } | |||
| const char *session_manager_name ( void ) { return _session_manager_name; } | |||
| /* Client->Server methods */ | |||
| void is_dirty ( void ); | |||
| void is_clean ( void ); | |||
| void progress ( float f ); | |||
| void message( int priority, const char *msg ); | |||
| void announce ( const char *nsm_url, const char *appliction_name, const char *capabilities, const char *process_name ); | |||
| /* init without threading */ | |||
| int init ( void ); | |||
| /* init with threading */ | |||
| int init_thread ( void ); | |||
| /* call this periodically to check for new messages */ | |||
| void check ( void ); | |||
| /* or call these to start and stop a thread (must do your own locking in handler!) */ | |||
| void start ( void ); | |||
| void stop ( void ); | |||
| protected: | |||
| /* Server->Client methods */ | |||
| virtual int command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ) = 0; | |||
| virtual int command_save ( char **out_msg ) = 0; | |||
| virtual void command_active ( bool active ) { } | |||
| virtual void command_session_is_loaded ( void ) { } | |||
| private: | |||
| /* osc handlers */ | |||
| static int osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| static int osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| static int osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| static int osc_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| static int osc_session_is_loaded ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| }; | |||
| }; | |||
| @@ -22,51 +22,120 @@ | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| #include <string.h> | |||
| #include <assert.h> | |||
| #include "Endpoint.H" | |||
| #include "Thread.H" | |||
| namespace OSC | |||
| { | |||
| void | |||
| Endpoint::error_handler(int num, const char *msg, const char *path) | |||
| { | |||
| WARNING( "LibLO server error %d in path %s: %s\n", num, path, msg); | |||
| } | |||
| Endpoint::Endpoint ( const char *port ) | |||
| Endpoint::Endpoint ( ) | |||
| { | |||
| DMESSAGE( "Creating OSC server" ); | |||
| } | |||
| // _st = lo_server_thread_new( s, error_handler ); | |||
| // _server = lo_server_thread_get_server( _st ); | |||
| int | |||
| Endpoint::init ( const char *port ) | |||
| { | |||
| DMESSAGE( "Creating OSC server" ); | |||
| _server = lo_server_new( port, error_handler ); | |||
| if ( ! _server ) | |||
| FATAL( "Error creating OSC server" ); | |||
| char *url = lo_server_get_url(_server); | |||
| printf("OSC: %s\n",url); | |||
| free(url); | |||
| { | |||
| WARNING( "Error creating OSC server" ); | |||
| return -1; | |||
| } | |||
| // char *url = lo_server_get_url(_server); | |||
| // printf("OSC: %s\n",url); | |||
| // free(url); | |||
| // add generic handler for path reporting. | |||
| add_method( "/osc/query/parameters", "s", osc_query_parameters, this, "" ); | |||
| add_method( NULL, "", &Endpoint::osc_generic, this, "" ); | |||
| // _path_names = new std::list<const char*>(); | |||
| return 0; | |||
| } | |||
| Endpoint::~Endpoint ( ) | |||
| { | |||
| // lo_server_thread_free( _st ); | |||
| lo_server_free( _server ); | |||
| } | |||
| int | |||
| Endpoint::osc_query_parameters ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| OSC_DMSG(); | |||
| Endpoint *ep = (Endpoint*)user_data; | |||
| const char *qpath = &argv[0]->s; | |||
| Method_Data *md = NULL; | |||
| for ( std::list<Method_Data *>::iterator i = ep->_methods.begin(); i != ep->_methods.end(); ++i ) | |||
| { | |||
| if ( ! (*i)->path ) | |||
| continue; | |||
| if ( ! strcmp( qpath, (*i)->path ) && (*i)->typespec ) | |||
| { | |||
| md = *i; | |||
| /* FIXME: what about the fact that there could be multiple messages with the same path but | |||
| different typespecs ? */ | |||
| break; | |||
| } | |||
| } | |||
| if ( ! md ) | |||
| { | |||
| ep->send( lo_message_get_source( msg ), "/error", path, | |||
| "Could not find specified path" ); | |||
| return 0; | |||
| } | |||
| char *r = (char*) malloc( 256 ); | |||
| r[0] = 0; | |||
| for ( int i = 0; i < strlen( md->typespec ); ++i ) | |||
| { | |||
| char desc[50]; | |||
| snprintf( desc, sizeof(desc), "f:%f:%f:%f\n", | |||
| md->parameter_limits[i].min, | |||
| md->parameter_limits[i].max, | |||
| md->parameter_limits[i].default_value ); | |||
| r = (char*)realloc( r, strlen( r ) + strlen( desc ) + 2 ); | |||
| strcat( r, desc ); | |||
| strcat( r, "\n" ); | |||
| } | |||
| ep->send( lo_message_get_source( msg ), "/reply", path, | |||
| qpath, | |||
| r ); | |||
| return 0; | |||
| } | |||
| int | |||
| Endpoint::osc_generic ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| OSC_DMSG(); | |||
| // OSC_DMSG(); | |||
| if ( path[ strlen(path) - 1 ] != '/' ) | |||
| return -1; | |||
| @@ -87,16 +156,18 @@ namespace OSC | |||
| char *r = (char*)malloc( 1024 ); | |||
| r[0] = 0; | |||
| for ( std::list<char*>::iterator i = _path_names.begin(); i != _path_names.end(); ++i ) | |||
| for ( std::list<Method_Data*>::const_iterator i = _methods.begin(); i != _methods.end(); ++i ) | |||
| { | |||
| if ( ! *i ) | |||
| if ( ! (*i)->path ) | |||
| continue; | |||
| if (! strncmp( *i, prefix, strlen(prefix) ) ) | |||
| if (! strncmp( (*i)->path, prefix, strlen(prefix) ) ) | |||
| { | |||
| r = (char*)realloc( r, strlen( r ) + strlen( *i ) + 2 ); | |||
| r = (char*)realloc( r, strlen( r ) + strlen( (*i)->path ) + 2 ); | |||
| strcat( r, *i ); | |||
| /* asprintf( &stored_path, "%s (%s); %s", path, typespec, argument_description ); */ | |||
| strcat( r, (*i)->path ); | |||
| strcat( r, "\n" ); | |||
| } | |||
| } | |||
| @@ -104,67 +175,139 @@ namespace OSC | |||
| return r; | |||
| } | |||
| void | |||
| void | |||
| Endpoint::set_parameter_limits ( const char *path, const char *typespec, | |||
| int index, | |||
| float min, float max, float default_value ) | |||
| { | |||
| assert( typespec ); | |||
| assert( index < strlen( typespec ) ); | |||
| for ( std::list<Method_Data *>::iterator i = _methods.begin(); i != _methods.end(); ++i ) | |||
| { | |||
| if ( ! (*i)->path ) | |||
| continue; | |||
| if ( ! strcmp( path, (*i)->path ) && | |||
| ! strcmp( typespec, (*i)->typespec ) ) | |||
| { | |||
| (*i)->parameter_limits[index].min = min; | |||
| (*i)->parameter_limits[index].max = max; | |||
| (*i)->parameter_limits[index].default_value = default_value; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| method_handle | |||
| Endpoint::add_method ( const char *path, const char *typespec, lo_method_handler handler, void *user_data, const char *argument_description ) | |||
| { | |||
| DMESSAGE( "Added OSC method %s (%s)", path, typespec ); | |||
| // DMESSAGE( "Added OSC method %s (%s)", path, typespec ); | |||
| lo_server_add_method( _server, path, typespec, handler, user_data ); | |||
| char *stored_path = NULL; | |||
| Method_Data *md = new Method_Data; | |||
| asprintf( &stored_path, "%s (%s); %s", path, typespec, argument_description ); | |||
| if ( path ) | |||
| md->path = strdup( path ); | |||
| if ( typespec ) | |||
| md->typespec = strdup( typespec ); | |||
| if ( argument_description ) | |||
| md->documentation = strdup( argument_description ); | |||
| _path_names.push_back( stored_path ); | |||
| if ( typespec ) | |||
| md->parameter_limits = new Parameter_Limits[strlen(typespec)]; | |||
| _methods.push_back( md ); | |||
| return md; | |||
| /* asprintf( &stored_path, "%s (%s); %s", path, typespec, argument_description ); */ | |||
| /* _path_names.push_back( stored_path ); */ | |||
| } | |||
| void | |||
| Endpoint::del_method ( const char *path, const char *typespec ) | |||
| { | |||
| DMESSAGE( "Deleted OSC method %s (%s)", path, typespec ); | |||
| // DMESSAGE( "Deleted OSC method %s (%s)", path, typespec ); | |||
| lo_server_del_method( _server, path, typespec ); | |||
| for ( std::list<char *>::iterator i = _path_names.begin(); i != _path_names.end(); ++i ) | |||
| for ( std::list<Method_Data *>::iterator i = _methods.begin(); i != _methods.end(); ++i ) | |||
| { | |||
| if ( ! *i ) | |||
| if ( ! (*i)->path ) | |||
| continue; | |||
| if ( ! strncmp( path, *i, index( *i, ' ' ) - *i ) ) | |||
| if ( ! strcmp( path, (*i)->path ) && | |||
| ! strcmp( typespec, (*i)->typespec ) ) | |||
| { | |||
| free( *i ); | |||
| i = _path_names.erase( i ); | |||
| delete *i; | |||
| i = _methods.erase( i ); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| /* void * */ | |||
| /* Endpoint::osc_thread ( void * arg ) */ | |||
| /* { */ | |||
| /* ((Endpoint*)arg)->osc_thread(); */ | |||
| void | |||
| Endpoint::del_method ( const method_handle mh ) | |||
| { | |||
| // DMESSAGE( "Deleted OSC method %s (%s)", path, typespec ); | |||
| Method_Data *meth = const_cast<Method_Data*>( (const Method_Data*)mh ); | |||
| lo_server_del_method( _server, meth->path, meth->typespec ); | |||
| delete meth; | |||
| /* return NULL; */ | |||
| /* } */ | |||
| _methods.remove( meth ); | |||
| /* void */ | |||
| /* Endpoint::osc_thread ( void ) */ | |||
| /* { */ | |||
| /* _thread.name( "OSC" ); */ | |||
| /* DMESSAGE( "OSC Thread running" ); */ | |||
| /* for ( std::list<Method_Data *>::iterator i = _methods.begin(); i != _methods.end(); ++i ) */ | |||
| /* { */ | |||
| /* if ( ! (*i)->path ) */ | |||
| /* continue; */ | |||
| /* for ( ;; ) */ | |||
| /* { */ | |||
| /* lo_server_recv( _sever ); */ | |||
| /* } */ | |||
| /* } */ | |||
| /* if ( ! strcmp( path, (*i)->path ) && */ | |||
| /* ! strcmp( typespec, (*i)->typespec ) ) */ | |||
| /* { */ | |||
| /* delete *i; */ | |||
| /* i = _methods.erase( i ); */ | |||
| /* break; */ | |||
| /* } */ | |||
| /* } */ | |||
| } | |||
| void * | |||
| Endpoint::osc_thread ( void * arg ) | |||
| { | |||
| ((Endpoint*)arg)->osc_thread(); | |||
| return NULL; | |||
| } | |||
| void | |||
| Endpoint::osc_thread ( void ) | |||
| { | |||
| _thread.name( "OSC" ); | |||
| DMESSAGE( "OSC Thread running" ); | |||
| run(); | |||
| } | |||
| void | |||
| Endpoint::start ( void ) | |||
| { | |||
| /* if ( !_thread.clone( &Endpoint::osc_thread, this ) ) */ | |||
| /* FATAL( "Could not create OSC thread" ); */ | |||
| if ( !_thread.clone( &Endpoint::osc_thread, this ) ) | |||
| FATAL( "Could not create OSC thread" ); | |||
| /* lo_server_thread_start( _st ); */ | |||
| @@ -173,6 +316,7 @@ namespace OSC | |||
| void | |||
| Endpoint::stop ( void ) | |||
| { | |||
| _thread.join(); | |||
| // lo_server_thread_stop( _st ); | |||
| } | |||
| @@ -227,9 +371,11 @@ namespace OSC | |||
| switch ( ov->type() ) | |||
| { | |||
| case 'f': | |||
| DMESSAGE( "Adding float %f", ((OSC_Float*)ov)->value() ); | |||
| lo_message_add_float( m, ((OSC_Float*)ov)->value() ); | |||
| break; | |||
| case 'i': | |||
| DMESSAGE( "Adding int %i", ((OSC_Int*)ov)->value() ); | |||
| lo_message_add_int32( m, ((OSC_Int*)ov)->value() ); | |||
| break; | |||
| case 's': | |||
| @@ -287,10 +433,83 @@ namespace OSC | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "s", v ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char * v1, float v2 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sf", v1, v2 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char * v1, const char *v2 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ss", v1, v2 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char * v1, const char *v2, const char *v3 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sss", v1, v2, v3 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, int v3, int v4 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "siii", v1, v2, v3, v4 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, int v4, int v5 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ssiii", v1, v2, v3, v4, v5 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, int v5, int v6 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sssiii", v1, v2, v3, v4, v5, v6 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, int v2 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "si", v1, v2 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, int v1, const char *v2 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "is", v1, v2 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, const char *v3 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sis", v1, v2, v3 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, int v1, const char *v2, const char *v3, const char *v4 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "isss", v1, v2, v3, v4 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, const char *v3, const char *v4, const char *v5 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sisss", v1, v2, v3, v4, v5 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4, const char *v5 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sssss", v1, v2, v3, v4, v5 ); | |||
| } | |||
| int | |||
| Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4 ) | |||
| { | |||
| return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ssss", v1, v2, v3, v4 ); | |||
| } | |||
| } | |||
| @@ -20,11 +20,13 @@ | |||
| #pragma once | |||
| #include <lo/lo.h> | |||
| //#include "util/Thread.H" | |||
| #include "Thread.H" | |||
| #include <list> | |||
| #include <stdlib.h> | |||
| namespace OSC | |||
| { | |||
| typedef void * method_handle; | |||
| class OSC_Value | |||
| { | |||
| @@ -111,27 +113,77 @@ namespace OSC | |||
| static int osc_generic ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| static int osc_query_parameters ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); | |||
| // Thread _thread; | |||
| Thread _thread; | |||
| // lo_server_thread _st; | |||
| // lo_server_thread _st; | |||
| lo_server _server; | |||
| std::list<char*> _path_names; | |||
| struct Parameter_Limits | |||
| { | |||
| float min; | |||
| float max; | |||
| float default_value; | |||
| }; | |||
| struct Method_Data | |||
| { | |||
| char *path; | |||
| char *typespec; | |||
| char *documentation; | |||
| struct Parameter_Limits *parameter_limits; | |||
| std::list<lo_address> subscribers; | |||
| Method_Data ( ) | |||
| { | |||
| path = typespec = documentation = 0; | |||
| parameter_limits = 0; | |||
| } | |||
| ~Method_Data ( ) | |||
| { | |||
| if ( path ) | |||
| free( path ); | |||
| if ( typespec ) | |||
| free( typespec ); | |||
| if ( parameter_limits ) | |||
| free( parameter_limits ); | |||
| for ( std::list<lo_address>::iterator i = subscribers.begin(); | |||
| i != subscribers.end(); | |||
| ++i ) | |||
| { | |||
| lo_address_free( *i ); | |||
| } | |||
| subscribers.clear(); | |||
| } | |||
| }; | |||
| std::list<Method_Data*> _methods; | |||
| static void *osc_thread ( void *arg ); | |||
| void osc_thread ( void ); | |||
| public: | |||
| Endpoint ( const char *port = 0 ); | |||
| int init ( const char *port = 0 ); | |||
| Endpoint ( ); | |||
| ~Endpoint ( ); | |||
| char *get_paths ( const char *prefix ); | |||
| void add_method ( const char *path, const char *typespec, lo_method_handler handler, void *user_data, const char *argument_description ); | |||
| method_handle add_method ( const char *path, const char *typespec, lo_method_handler handler, void *user_data, const char *argument_description ); | |||
| void del_method ( const char *path, const char *typespec ); | |||
| void del_method ( const method_handle ); | |||
| void start ( void ); | |||
| void stop ( void ); | |||
| int port ( void ) const; | |||
| char * url ( void ) const; | |||
| void set_parameter_limits ( const char *path, const char *typespec, int index, float min, float max, float default_value ); | |||
| void check ( void ) const; | |||
| void wait ( int timeout ) const; | |||
| @@ -145,20 +197,36 @@ namespace OSC | |||
| int send ( lo_address to, const char *path, double v ); | |||
| int send ( lo_address to, const char *path, int v ); | |||
| int send ( lo_address to, const char *path, long v ); | |||
| int send ( lo_address to, const char *path, const char * v1, float v2 ); | |||
| int send ( lo_address to, const char *path, const char *v ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3 ); | |||
| int send ( lo_address to, const char *path, const char *v1, int v2, int v3, int v4 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, int v4, int v5 ); | |||
| int send ( lo_address to, const char *path, const char *v1, int v2 ); | |||
| int send ( lo_address to, const char *path, int v1, const char *v2 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, int v5, int v6 ); | |||
| int send ( lo_address to, const char *path, const char *v1, int v2, const char *v3 ); | |||
| int send ( lo_address to, const char *path, int v1, const char *v2, const char *v3, const char *v4 ); | |||
| int send ( lo_address to, const char *path, const char *v1, int v2, const char *v3, const char *v4, const char *v5 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4, const char *v5 ); | |||
| int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4 ); | |||
| // can be used to point back to owning object. | |||
| void *owner; | |||
| }; | |||
| }; | |||
| /* helper macros for defining OSC handlers */ | |||
| #define OSC_NAME( name ) osc_ ## name | |||
| #define OSC_DMSG() DMESSAGE( "Got OSC message: %s", path ); | |||
| #define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| #define OSC_REPLY_OK() ((Mixer*)user_data)->osc_endpoint->send( lo_message_get_source( msg ), "/reply", path, "ok" ) | |||
| #define OSC_REPLY( msg_str ) ((Mixer*)user_data)->osc_endpoint->send( lo_message_get_source( msg ), "/reply", path, msg_str ) | |||
| #define OSC_REPLY_ERR() ((Mixer*)user_data)->osc_endpoint->send( lo_message_get_source( msg ), "/reply", path, "err" ) | |||
| #define OSC_REPLY_OK() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, "ok" ) | |||
| #define OSC_REPLY( value ) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, value ) | |||
| #define OSC_REPLY_ERR() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, "err" ) | |||
| #define OSC_ENDPOINT() ((OSC::Endpoint*)user_data) | |||
| @@ -1,6 +1,6 @@ | |||
| # -*- mode: makefile; -*- | |||
| nonlib_SRCS := $(wildcard nonlib/*.C nonlib/JACK/*.C nonlib/LASH/*.C nonlib/OSC/*.C) | |||
| nonlib_SRCS := $(wildcard nonlib/*.C nonlib/JACK/*.C nonlib/LASH/*.C nonlib/OSC/*.C nonlib/NSM/*.C) | |||
| nonlib_SRCS:=$(sort $(nonlib_SRCS)) | |||
| nonlib_OBJS:=$(nonlib_SRCS:.C=.o) | |||
| @@ -282,18 +282,18 @@ require_package () | |||
| _test_version () | |||
| { | |||
| if [ $# == 6 ] | |||
| if [ $# = 6 ] | |||
| then | |||
| [ $1 -gt $4 ] && return 0 | |||
| [ $1 -eq $4 ] && [ $2 -gt $5 ] && return 0 | |||
| [ $1 -eq $4 ] && [ $2 -eq $5 ] && [ $3 -gt $6 ] && return 0 | |||
| [ $1 -eq $4 ] && [ $2 -eq $5 ] && [ $3 -eq $6 ] && return 0 | |||
| return 1 | |||
| elif [ $# == 4 ] | |||
| elif [ $# = 4 ] | |||
| then | |||
| [ $1 -gt $3 ] && return 0 | |||
| [ $1 -eq $3 ] && [ $2 -eq $4 ] && return 0 | |||
| return 1; | |||
| return 1 | |||
| fi | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| ../FL | |||
| @@ -0,0 +1,137 @@ | |||
| ############################################################################### | |||
| # 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. # | |||
| ############################################################################### | |||
| ## Makefile for the Non-DAW. | |||
| ## | |||
| ## Do not edit this file; run `make config` instead. | |||
| ## | |||
| VERSION := 1.0.0 | |||
| PACKAGE := SESSION | |||
| all: .config | |||
| .config: configure | |||
| @ echo '<<< Configuring '$(PACKAGE) | |||
| @ ./configure | |||
| config: | |||
| @ echo '<<< Configuring '$(PACKAGE) | |||
| @ ./configure | |||
| -include .config | |||
| export SYSTEM_PATH:=$(prefix)/share/ | |||
| export DOCUMENT_PATH:=$(prefix)/share/doc/ | |||
| export PIXMAP_PATH:=$(prefix)/share/pixmaps/ | |||
| # a bit of a hack to make sure this runs before any rules | |||
| ifneq ($(CALCULATING),yes) | |||
| TOTAL := $(shell $(MAKE) CALCULATING=yes -n 2>/dev/null | sed -n 's/^.*Compiling: \([^"]\+\)"/\1/p' > .files ) | |||
| endif | |||
| ifeq ($(USE_DEBUG),yes) | |||
| CFLAGS := -pipe -ggdb -fno-inline -Wall -Wextra -O0 | |||
| CXXFLAGS := -Wnon-virtual-dtor -Wno-missing-field-initializers -fno-rtti -fno-exceptions | |||
| else | |||
| CFLAGS := -pipe -O2 -DNDEBUG | |||
| CXXFLAGS := -fno-rtti -fno-exceptions | |||
| endif | |||
| CFLAGS+=-DVERSION=\"$(VERSION)\" \ | |||
| -DINSTALL_PREFIX=\"$(prefix)\" \ | |||
| -DSYSTEM_PATH=\"$(SYSTEM_PATH)\" \ | |||
| -DDOCUMENT_PATH=\"$(DOCUMENT_PATH)\" \ | |||
| -DPIXMAP_PATH=\"$(PIXMAP_PATH)\" | |||
| CXXFLAGS += $(SNDFILE_CFLAGS) $(FLTK_CFLAGS) $(JACK_CFLAGS) | |||
| CXXFLAGS := $(CFLAGS) $(CXXFLAGS) | |||
| INCLUDES := -I. -Iutil -IFL -Inonlib | |||
| include scripts/colors | |||
| ifneq ($(CALCULATING),yes) | |||
| COMPILING="$(BOLD)$(BLACK)${PACKAGE} [$(SGR0)$(CYAN)`scripts/percent-complete .files "$<"`$(SGR0)$(BOLD)$(BLACK)]$(SGR0) $(BOLD)$(YELLOW)$<$(SGR0)" | |||
| else | |||
| COMPILING="Compiling: $<" | |||
| endif | |||
| .C.o: | |||
| @ echo $(COMPILING) | |||
| @ $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ | |||
| %.C : %.fl | |||
| @ cd `dirname $<` && fluid -c ../$< | |||
| DONE := $(BOLD)$(GREEN)done$(SGR0) | |||
| include FL/makefile.inc | |||
| #include nonlib/makefile.inc | |||
| include makefile.inc | |||
| SRCS:=$(Session_SRCS) | |||
| OBJS:=$(Session_OBJS) | |||
| # FIXME: isn't there a better way? | |||
| $(OBJS): .config Makefile | |||
| TAGS: $(SRCS) | |||
| etags $(SRCS) | |||
| .deps: .config $(SRCS) | |||
| ifneq ($(CALCULATING),yes) | |||
| @ echo -n Calculating dependencies... | |||
| @ makedepend -f- -- $(CXXFLAGS) $(INCLUDES) -- $(SRCS) 2>/dev/null > .deps && echo $(DONE) | |||
| @ # gcc -M $(CXXFLAGS) $(INCLUDES) $(SRCS) > .deps && echo $(DONE) | |||
| endif | |||
| install: all | |||
| @ echo -n "Installing..." | |||
| @ install src/nsmd $(prefix)/bin/nsmd | |||
| @ install src/session-manager $(prefix)/bin/non-session-manager | |||
| @ # mkdir -p $(SYSTEM_PATH)/non-mixer | |||
| @ # mkdir -p $(PIXMAP_PATH)/non-mixer | |||
| @ # cp pixmaps/*.png $(PIXMAP_PATH)/non-mixer | |||
| @ # $(MAKE) -s -C doc install | |||
| @ echo "$(DONE)" | |||
| ifneq ($(USE_DEBUG),yes) | |||
| @ echo -n "Stripping..." | |||
| @ strip $(prefix)/bin/nsmd | |||
| @ strip $(prefix)/bin/non-session-manager | |||
| @ echo "$(DONE)" | |||
| endif | |||
| clean_deps: | |||
| @ rm -f .deps | |||
| .PHONEY: clean config depend clean_deps | |||
| clean: FL_clean Session_clean | |||
| dist: | |||
| git archive --prefix=non-session-$(VERSION)/ v$(VERSION) | bzip2 > non-session-$(VERSION).tar.bz2 | |||
| scan-gpl: | |||
| @ scripts/scan-gpl $(SRCS) || echo $(BOLD)$(RED)Some source files do not contain proper license information! | |||
| -include .deps | |||
| @@ -0,0 +1,27 @@ | |||
| #!/bin/sh | |||
| # | |||
| # Copyright (C) 2008 Jonathan Moore Liles | |||
| # This file is licensed under version 2 of the GPL. | |||
| . scripts/config-funcs | |||
| begin | |||
| begin_options | |||
| ask "Installation prefix" prefix /usr/local | |||
| ask "Build for debugging" USE_DEBUG no | |||
| begin_tests | |||
| require_FLTK 1.1.7 images | |||
| require_command FLUID fluid | |||
| require_command ar ar | |||
| require_command makedepend makedepend | |||
| # require_package JACK 0.103.0 jack | |||
| require_package lrdf 0.4.0 lrdf | |||
| require_package liblo 0.23 liblo | |||
| test_version `version_of liblo` 0.26 || warn "Version $(version_of liblo) of liblo is slow to create servers. Consider upgrading to 0.26 or later" | |||
| end | |||
| @@ -0,0 +1,492 @@ | |||
| ! title Non Session Management API | |||
| ! author Jonathan Moore Liles #(email,male@tuxfamily.org) | |||
| ! date August 1, 2010 | |||
| -- Table Of Contents | |||
| : Non Session Management API version 0.7 | |||
| The Non Session Management API is an API for session management used | |||
| by the various parts of the Non music production suite. It comprises | |||
| a simple OSC based protocol which can easily be implemented by other | |||
| applications. NSM provides robust session management, including | |||
| interactive features. | |||
| The Non project contains an implementation of the NSM server API | |||
| called `nsmd` which can be controlled by the `non-session-manager` | |||
| GUI, but the same server API can easily be implemented by other | |||
| session managers (such as LADISH). | |||
| The only dependency for clients `liblo` (the OSC library), which | |||
| several Linux audio applications already link to or plan to link to | |||
| in the future. | |||
| The aim of this project is to thoroughly define the behavior | |||
| required of clients. This is an area where other attempts at session | |||
| management (LASH and JACK-Session) have failed. Often the difficulty | |||
| with these previous system been, not in implementing support for | |||
| them, but in attempting to interpret the confusing and ambiguous API | |||
| documentation. For this reason, all LASH support has been removed | |||
| from Non. | |||
| You *WILL* see a lot of unambiguous language in this document. These | |||
| rules are meant to be followed and are non-negotiable. If an | |||
| application does not conform to this specification it should be | |||
| considered broken. Consistency across applications under session | |||
| management is very important for a good user experience. | |||
| :: Client Behavior Under Session Management | |||
| Most graphical applications make available to the user a common set | |||
| of file operations, typically presented under a File or Project | |||
| menu. | |||
| These are: New, Open, Save, Save As, Close and Quit or Exit. | |||
| The following sub-sections describe how these options should behave when | |||
| the application is part of an NSM session. These rules only apply | |||
| when session management is active (that is, after the `announce` | |||
| handshake described in the #(ref,NSM OSC Protocol) section). | |||
| In order to provide a consistent and predictable user experience, it | |||
| is important for applications to adhere to these guidelines. | |||
| ::: New | |||
| This option may empty\/reset the current file or project (possibly | |||
| after user confirmation). *UNDER NO CIRCUMSTANCES* should it allow | |||
| the user to create a new project\/file in another location. | |||
| ::: Open | |||
| This option should be disabled. | |||
| The application may, however, elect to implement an option called | |||
| 'Import into Session', creates a copy of a file\/project which is | |||
| then saved in the session path provided by NSM. | |||
| ::: Save | |||
| This option should behave as normal, saving the current | |||
| file\/project as established by the NSM `open` message. | |||
| *UNDER NO CIRCUMSTANCES* should this option present the user with a | |||
| choice of where to save the file! | |||
| ::: Save As | |||
| This option should be disabled. | |||
| The application may, however, elect to implement an option called | |||
| 'Export from Session', which creates a copy of the current | |||
| file\/project which is then saved in a user-specified location | |||
| outside of the session path provided by NSM. | |||
| ::: Close (as distinguished from Quit or Exit) | |||
| This option should be disabled, unless its meaning is to disconnect | |||
| the application from session management. | |||
| ::: Quit or Exit | |||
| This option may behave as normal (even possibly asking the user to | |||
| confirm exiting). | |||
| :: NSM OSC Protocol | |||
| All message parameters are *REQUIRED*. All messages *MUST* be sent | |||
| from the same socket as the `announce` message, using the | |||
| `lo\_send\_from` method of liblo (the server uses the return | |||
| addresses to distinguish between clients). | |||
| ::: Establishing a Connection | |||
| :::: Announce | |||
| When started clients *MUST* check the environment for the value of | |||
| `NSM\_URL`. If present, the client *MUST* send the following message | |||
| to the provided address as soon as it is ready to respond to the | |||
| `\/nsm\/client\/open` event: | |||
| > /nsm/server/announce s:application_name s:capabilities i:api_version_major i:api_version_minor i:pid | |||
| If `NSM\_URL` is undefined, invalid, or unreachable, then the client | |||
| should proceed assuming that session management is unavailable. | |||
| `api\_version\_major` and `api\_version\_minor` must be the two parts of | |||
| the version number of the NSM API as defined by this document. | |||
| Note that if the application intends to register JACK clients, | |||
| `application\_name` *MUST* be the same as the name that would | |||
| normally by passed to `jack\_client\_open`. For example, Non-Mixer | |||
| sends "Non-Mixer" as its `application\_name`. Applications *MUST | |||
| NOT* register their JACK clients until receiving an `open` message; | |||
| the `open` message will provide a unique client name prefix suitable | |||
| for passing to JACK. This is probably the most complex requirement | |||
| of the NSM API, but it isn't difficult to implement. | |||
| `capabilities` *MUST* be a string containing a colon separated list | |||
| of the special capabilities the client | |||
| possesses. e.g. ":dirty:switch:progress:" | |||
| // Available Client Capabilities | |||
| [[ Name, Description | |||
| [[ switch, client is capable of responding to multiple `open` messages without restarting | |||
| [[ dirty, client knows when it has unsaved changes | |||
| [[ progress, client can send progress updates during time-consuming operations | |||
| [[ status, client can send textual status updates | |||
| :::: Response | |||
| The server will respond to the client's `announce` with the following message: | |||
| > /reply "/nsm/server/announce" s:message s:name_of_session_manager s:capabilities | |||
| `message` is a welcome message. | |||
| The value of `name\_of\_session\_manager` will depend on the | |||
| implementation of the NSM server. It might say "Non Session Manager", | |||
| or it might say "LADISH". | |||
| `capabilities` will be a string containing a colon separated list of | |||
| special server capabilities. | |||
| Presently, the server `capabilities` are: | |||
| // Available Server Capabilities | |||
| [[ Name, Description | |||
| [[ server_control, client-to-server control | |||
| A client should not consider itself to be under session management | |||
| until it receives this response (the Non programs activate their | |||
| "SM" blinkers at this time.) | |||
| If there is an error, a reply of the following form will be sent to | |||
| the client: | |||
| > /error "/nsm/server/announce" i:error_code s:error_message | |||
| The following table defines possible values of `error\_code`: | |||
| // Response codes | |||
| [[ Code, Meaning | |||
| [[ ERR_GENERAL, General Error | |||
| [[ ERR_INCOMPATIBLE_API, Incompatible API version | |||
| [[ ERR_BLACKLISTED, Client has been blacklisted. | |||
| ::: Server to Client Control Messages | |||
| Compliant clients *MUST* accept the client control messages | |||
| described in this section. All client control messages *REQUIRE* a | |||
| response. Responses *MUST* be delivered back to the sender (NSM) | |||
| from the same socket used by the client in its `announce` message | |||
| (by using `lo\_send\_from`) *AFTER* the action has been completed or | |||
| if an error is encountered. The required response is described in | |||
| the subsection for each message. | |||
| If there is an error and the action cannot be completed, then | |||
| `error\_code` *MUST* be set to a valid error code (see #(fig,Error Code Definitions)) | |||
| and `message` to a string describing the problem (suitable | |||
| for display to the user). | |||
| The reply can take one of the following two forms, where `path` *MUST* be | |||
| the path of the message being replied to (e.g. "/nsm\/client\/save"): | |||
| > /reply s:path s:message | |||
| > /error s:path i:error_code s:message | |||
| :::: Quit | |||
| There is no message for this. Clients will receive the Unix SIGTERM | |||
| signal and *MUST* close cleanly *IMMEDIATELY*, without displaying | |||
| any kind of dialog to the user and regardless of whether or not | |||
| unsaved changes would be lost (when a session is closed the | |||
| application will receive this signal soon after having responded to | |||
| a `save` message). | |||
| :::: Open | |||
| > /nsm/client/open s:path_to_instance_specific_project s:client_id | |||
| The client *MUST* open an existing project, or create new one if one | |||
| doesn't already exist, at `path\_to\_instance_specific\_project` | |||
| If the path provided doesn't exist, then the client *MUST* | |||
| immediately create and open a new file\/project at the specified | |||
| path (whether that means creating a single file or a project | |||
| directory). | |||
| No file or directory will be created at the specified path by the | |||
| server. It is up to the client to create what it needs. | |||
| The client may append to the path, creating a subdirectory, | |||
| e.g. '/song.foo' or simply append the client's native file extension | |||
| (e.g. '.non' or '.XML'). The same transformation *MUST* be applied | |||
| to the name when opening an existing project, as NSM will only | |||
| provide the instance specific part of the path. | |||
| For clients which *HAVE NOT* specified the 'switch' capability, the | |||
| `open` message will only be delivered once, immediately after the | |||
| 'announce' response. | |||
| For client which *HAVE* specified the `:switch:` capability, the | |||
| client *MUST* immediately switch to the specified project or create | |||
| a new one if it doesn't exist. | |||
| Clients which are incapable of switching projects or are prone to | |||
| crashing upon switching *MUST NOT* include `:switch:` in their | |||
| capability string. | |||
| If the user the is allowed to run two or more instances of the | |||
| application simultaneously (that is to say, there is no technical | |||
| limitation preventing them from doing so, even if it doesn't make | |||
| sense to the author), then such an application *MUST* prepend the | |||
| provided `client\_id` string to any names it registers with common | |||
| subsystems (e.g. JACK client names). This ensures that the multiple | |||
| instances of the same application can be restored in any order | |||
| without scrambling the JACK connections or causing other | |||
| conflicts. The provided `client\_id` will be a concatenation of the | |||
| value of `application\_name` sent by the client in its `announce` | |||
| message and a unique identifier. Therefore, applications which | |||
| create single JACK clients can use the value of `client\_id` directly | |||
| as their JACK client name. Applications which register multiple JACK | |||
| clients (e.g. Non-Mixer) *MUST* prepend `client_id` value to the | |||
| client names they register with JACK and the application determined | |||
| part *MUST* be unique for that (JACK) client. | |||
| For example, a suitable JACK client name would be: | |||
| > $CLIENT_ID/track-1 | |||
| A response is *REQUIRED* *AFTER* the load\/new operation has been | |||
| completed. Ongoing progress may be indicated by sending messages to | |||
| `\/nsm\/client\/progress`. | |||
| ::::: Response | |||
| The client *MUST* respond to the 'open' message with: | |||
| > /reply "/nsm/client/open" s:message | |||
| Or | |||
| > /error "/nsm/client/open" i:error_code s:message | |||
| // Response Codes | |||
| [[ Code, Meaning | |||
| [[ ERR, General Error | |||
| [[ ERR_BAD_PROJECT, An existing project file was found to be corrupt | |||
| [[ ERR_CREATE_FAILED, A new project could not be created | |||
| [[ ERR_UNSAVED_CHANGES, Unsaved changes would be lost | |||
| [[ ERR_NOT_NOW, Operation cannot be completed at this time | |||
| :::: Save | |||
| > /nsm/client/save | |||
| The client *MUST* immediately save the current application specific | |||
| project data to the project path previously established in the | |||
| 'open' message. *UNDER NO CIRCUMSTANCES* should a dialog be | |||
| displayed to the user (giving a choice of where to save, etc.) | |||
| ::::: Response | |||
| The client *MUST* respond to the 'save' message with: | |||
| > /reply "/nsm/client/save" s:message | |||
| Or | |||
| > /error "/nsm/client/save" i:error_code s:message | |||
| // Response Codes | |||
| [[ Code, Meaning | |||
| [[ ERR, General Error | |||
| [[ ERR_SAVE_FAILED, Project could not be saved | |||
| [[ ERR_NOT_NOW, Operation cannot be completed at this time | |||
| ::: Server to Client Informational Messages | |||
| :::: Session is Loaded | |||
| Accepting this message is optional. The intent is to signal to | |||
| clients which may have some interdependency (say, peer to peer OSC | |||
| connections) that the session is fully loaded and all their peers | |||
| are available. | |||
| > /nsm/client/session_is_loaded | |||
| This message does not require a response. | |||
| ::: Client to Server Informational Messages | |||
| These are optional messages which a client can send to the NSM | |||
| server to inform it about the client's status. The client should not | |||
| expect any reply to these messages. If a client intends to send a | |||
| message described in this section, then it *MUST* add the | |||
| appropriate value to its `capabilities` string when composing the | |||
| `announce` message. | |||
| :::: Progress | |||
| > /nsm/client/progress f:progress | |||
| For potentially time-consuming operations, such as `save` and | |||
| `open`, progress updates may be indicated throughout the duration by | |||
| sending a floating point value between 0.0 and 1.0, 1.0 indicating | |||
| completion, to the NSM server. | |||
| The server will not send a response to these messages, but will | |||
| relay the information to the user. | |||
| Note that, even when using the `progress` feature, the final | |||
| response to the `save` or `open` message is still *REQUIRED*. | |||
| Clients which intend to send `progress` messages should include | |||
| `:progress:` in their `announce` capability string. | |||
| :::: Dirtiness | |||
| > /nsm/client/is_dirty | |||
| > /nsm/client/is_clean | |||
| Some clients may be able to inform the server when they have unsaved | |||
| changes pending. Such clients may optionally send `is\_dirty` and `is\_clean` | |||
| messages. | |||
| Clients which have this capability should include `:dirty:` in their | |||
| `announce` capability string. | |||
| :::: Status Messages | |||
| > /nsm/client/message i:priority s:message | |||
| Clients may send miscellaneous status updates to the server for | |||
| possible display to the user. This may simply be chatter that is normally | |||
| written to the console. `priority` should be a number from 0 to 3, 3 | |||
| being the most important. | |||
| Clients which have this capability should include `:message:` in their | |||
| `announce` capability string. | |||
| ::: Error Code Definitions | |||
| // Error Code Definitions | |||
| [[ Symbolic Name, Integer Value | |||
| [[ ERR_GENERAL, -1 | |||
| [[ ERR_INCOMPATIBLE_API, -2 | |||
| [[ ERR_BLACKLISTED, -3 | |||
| [[ ERR_LAUNCH_FAILED, -4 | |||
| [[ ERR_NO_SUCH_FILE, -5 | |||
| [[ ERR_NO_SESSION_OPEN, -6 | |||
| [[ ERR_UNSAVED_CHANGES, -7 | |||
| [[ ERR_NOT_NOW, -8 | |||
| [[ ERR_BAD_PROJECT, -9 | |||
| [[ ERR_CREATE_FAILED, -10 | |||
| ::: Client to Server Control | |||
| If the server publishes the `server\_control` capability, then | |||
| clients can also initiate action by the server. For example, a | |||
| client might implement a 'Save All' option which sends a | |||
| `\/nsm\/server\/save` message to the server, rather than requiring | |||
| the user to switch to the session management interface to effect the | |||
| save. | |||
| ::: Server Control API | |||
| The session manager not only manages clients via OSC, but it is itself | |||
| controlled via OSC messages. The server responds to the following | |||
| messages. | |||
| All of the following messages will be responded to back to the sender's address | |||
| with one of the two following messages: | |||
| > /reply s:path s:message | |||
| > /error s:path i:error_code s:message | |||
| The first parameter of the reply is the path to the message being | |||
| replied to. The `\/error` reply includes an integer error code | |||
| (non-zero indicates error). `message` will be a description of the | |||
| error. | |||
| The possible errors are: | |||
| // Responses | |||
| [[ Code, Meaning | |||
| [[ ERR_GENERAL, General Error | |||
| [[ ERR_LAUNCH_FAILED, Launch failed | |||
| [[ ERR_NO_SUCH_FILE, No such file | |||
| [[ ERR_NO_SESSION, No session is open | |||
| [[ ERR_UNSAVED_CHANGES, Unsaved changes would be lost | |||
| = /nsm/server/add s:path_to_executable | |||
| Adds a client to the current session. | |||
| = /nsm/server/save | |||
| Saves the current session. | |||
| = /nsm/server/load s:project_name | |||
| Saves the current session and loads a new session. | |||
| = /nsm/server/new s:project_name | |||
| Saves the current session and creates a new session. | |||
| = /nsm/server/close | |||
| Saves and closes the current session. | |||
| = /nsm/server/abort | |||
| Closes the current session *WITHOUT SAVING* | |||
| = /nsm/server/quit | |||
| Saves and closes the current session and terminates the server. | |||
| = /nsm/server/duplicate s:new_project | |||
| Saves and closes the current session, creates a complete copy of | |||
| it as `new_project` and opens it. The existing project should ideally be | |||
| a lightweight template, as copying any audio data could be very time | |||
| consuming. | |||
| = /nsm/server/list | |||
| Lists available projects. One `\/reply` message will be sent for each existing project. | |||
| # = /nsm/server/client/list | |||
| # Lists clients in the current session, their client IDs and statuses | |||
| # = /nsm/server/ve | |||
| :::: Client to Client Communication | |||
| If the server includes `:broadcast:` in its capability string, then | |||
| clients may send broadcast messages to each other through the NSM | |||
| server. | |||
| Clients may send messages to the server at the path | |||
| `\/broadcast`. | |||
| The format of this message is as follows: | |||
| > /nsm/server/broadcast s:path [other parameters...] | |||
| The message will then be relayed to all clients in the session at | |||
| the path given in the `path` parameter and with the other parameters | |||
| shifted forward by one. | |||
| For example the message: | |||
| > /nsm/server/broadcast /tempomap/update "0,120,4/4:12351234,240,4/4" | |||
| Would broadcast the following message to all clients in the session | |||
| (except for the sender), some of which might respond to the message | |||
| by updating their own tempo maps. | |||
| > /tempomap/update "0,120,4/4:12351234,240,4/4" | |||
| Clients may use this feature to establish peer to peer OSC | |||
| communication with symbolic names without having to remember the OSC | |||
| URLs of peers. | |||
| @@ -0,0 +1,449 @@ | |||
| /* Example CSS Style for MUP */ | |||
| a:link { | |||
| color: yellow; | |||
| } | |||
| a:visited { | |||
| color: olive; | |||
| } | |||
| a:active { | |||
| color: white; | |||
| } | |||
| a:link:hover { | |||
| text-decoration: underline; | |||
| } | |||
| /* #(url) */ | |||
| a.ext:link { | |||
| color: red; | |||
| text-decoration: none; | |||
| border-bottom: dashed silver 1; | |||
| } | |||
| a.ext:visited { | |||
| color: darkred; | |||
| border-bottom: dashed silver 1; | |||
| text-decoration: none; | |||
| } | |||
| /* #(ref) */ | |||
| a.int:link { | |||
| border-bottom: dashed silver 0.15em; | |||
| } | |||
| a.int:link:hover { | |||
| text-decoration: none; | |||
| color: white; | |||
| } | |||
| /* | |||
| a[href^="#"]:link { | |||
| border-bottom: dashed silver 0.15em; | |||
| } | |||
| a[href^="#"]:link:hover { | |||
| text-decoration: none; | |||
| color: white; | |||
| } | |||
| */ | |||
| p:contains("Warning:") { | |||
| background: #d00; | |||
| color: white; | |||
| border: dotted gray 0.5em; | |||
| display: block; | |||
| } | |||
| /* First letter of first paragraph of every chapter */ | |||
| /* | |||
| h1 + p:first-letter { | |||
| text-transform: uppercase; | |||
| float: left; | |||
| line-height: 0.8em; | |||
| font-size: 350%; | |||
| font-family: Serif; | |||
| letter-spacing: 0; | |||
| margin-right: 0.1em; | |||
| margin-top: 0.1em; | |||
| border: solid gray 1px; | |||
| padding: 1px; | |||
| color: #d00; | |||
| text-shadow: #666 3px 3px 3px; | |||
| } | |||
| */ | |||
| /* First paragraph of every chapter */ | |||
| /* | |||
| h1 + p { | |||
| text-indent: 0; | |||
| } | |||
| */ | |||
| /* cover */ | |||
| #cover * { | |||
| background: transparent; | |||
| } | |||
| #cover { | |||
| position: relative; | |||
| background: #da0; | |||
| color: black; | |||
| text-align: center; | |||
| margin: 0; | |||
| padding: 0.5em; | |||
| } | |||
| #cover h1, #cover h3 { | |||
| text-shadow: #444 0.2em 0.2em 0.2em; | |||
| color: white; | |||
| border: none; | |||
| letter-spacing: 0.2em; | |||
| line-height: 0.8em; | |||
| margin-left: 2em; | |||
| margin-right: 2em; | |||
| } | |||
| #cover h1:before, #cover h1:after { | |||
| content: "::"; | |||
| font-size: 300%; | |||
| color: black; | |||
| } | |||
| #cover h1:before { | |||
| position: absolute; | |||
| top: 0.2em; | |||
| left: 0.1em; | |||
| } | |||
| #cover h1:after { | |||
| position: absolute; | |||
| top: 0.2em; | |||
| right: 0.1em; | |||
| } | |||
| #cover hr { | |||
| display: none; | |||
| } | |||
| hr:first-child { | |||
| display: none; | |||
| } | |||
| hr { | |||
| height: 0.2em; | |||
| background: #555; | |||
| color: #555; | |||
| margin-left: 0.5em; | |||
| } | |||
| #cover a:visited { | |||
| color: black; | |||
| } | |||
| /* endnote */ | |||
| #endnote { | |||
| color: black; | |||
| } | |||
| /* TOC */ | |||
| #toc { | |||
| position: relative; | |||
| } | |||
| #toc hr { | |||
| } | |||
| #toc h1 { | |||
| } | |||
| #toc ul { | |||
| font-size: 125%; | |||
| font-weight: bold; | |||
| margin-bottom: 1em; | |||
| } | |||
| #toc ul ul { | |||
| font-size: 90%; | |||
| font-weight: normal; | |||
| margin-bottom: 0; | |||
| } | |||
| #toc li { | |||
| list-style: none; | |||
| } | |||
| #toc a:link { | |||
| border-bottom: 0; | |||
| } | |||
| body { | |||
| margin: 0; | |||
| background: #222; | |||
| color: white; | |||
| font-family: Arial, sans-serif; | |||
| } | |||
| /* */ | |||
| #body { | |||
| position: relative; | |||
| margin: 0.5em; | |||
| padding: 0.5em; | |||
| } | |||
| /* ;, : */ | |||
| h1 { | |||
| color: #ff0; | |||
| border-bottom: solid #444 0.1em; | |||
| } | |||
| /* ::, :::, ::::, :::::, :::::: */ | |||
| h2, h3, h4, h5, h6 { | |||
| color: #dd0; | |||
| } | |||
| /* tables, figures */ | |||
| .fig caption { | |||
| color: gray; | |||
| text-align: center; | |||
| /* Required for Mozilla */ | |||
| margin: auto; | |||
| } | |||
| .fig table { | |||
| border: none; | |||
| margin: auto; | |||
| /* border-collapse: collapse; */ | |||
| } | |||
| /* / */ | |||
| .fig.table th { | |||
| border: none; | |||
| background: gray; | |||
| color: black; | |||
| } | |||
| /* [ */ | |||
| .fig.table td { | |||
| border: none; | |||
| background: silver; | |||
| color: black; | |||
| padding-left: 1em; | |||
| padding-right: 1em; | |||
| padding-top: 0.2em; | |||
| padding-bottom: 0.2em; | |||
| } | |||
| /* < */ | |||
| .fig.image table { | |||
| border: dashed silver 0.2em; | |||
| background: transparent; | |||
| /* Every browser should support border radii */ | |||
| -moz-border-radius: 0.5em; | |||
| border-radius: 0.5em; | |||
| } | |||
| .fig.image tr, .fig.image td { | |||
| border: none; | |||
| background: transparent; | |||
| padding: 0; | |||
| } | |||
| /* */ | |||
| p { | |||
| margin-right: 2%; | |||
| text-align: justify; | |||
| text-indent: 1em; | |||
| } | |||
| /* > */ | |||
| .example * | |||
| { | |||
| background: transparent; | |||
| } | |||
| .example table | |||
| { | |||
| margin: 0; | |||
| padding: 0; | |||
| table-layout: fixed; | |||
| width: 100%; | |||
| caption-side: top; | |||
| overflow: auto; | |||
| } | |||
| .example caption | |||
| { | |||
| caption-side: top; | |||
| } | |||
| .example { | |||
| } | |||
| .example p { | |||
| display: inline; | |||
| margin: 0; | |||
| padding: 0; | |||
| text-align: center; | |||
| } | |||
| .example pre { | |||
| margin-top: 0; | |||
| font-family: Monospace; | |||
| padding: 1em; | |||
| border: dashed 0.3em gray; | |||
| background: #111; | |||
| color: white; | |||
| display: block; | |||
| overflow: auto; | |||
| /* Every browser should support border radii */ | |||
| -moz-border-radius: 0.5em; | |||
| border-radius: 0.5em; | |||
| } | |||
| /* " */ | |||
| /* | |||
| .quote:before { | |||
| float: left; | |||
| font-size: 500%; | |||
| content: "\201C"; | |||
| } | |||
| */ | |||
| /* | |||
| blockquote:after { | |||
| content: "\201D"; | |||
| }*/ | |||
| .quote blockquote { | |||
| padding: 0.5em; | |||
| margin-left: 0.5em; | |||
| font-family: Serif; | |||
| border-left: solid 0.4em gray; | |||
| /* background: #333; */ | |||
| color: white; | |||
| } | |||
| /* ^ */ | |||
| small { | |||
| /* | |||
| color: silver; | |||
| font-size: 50%; | |||
| */ | |||
| } | |||
| .footnote p { | |||
| color: silver; | |||
| margin: 0; | |||
| } | |||
| /* Popup footnotes */ | |||
| .footnote p { | |||
| display: none; | |||
| } | |||
| .footnote p:target { | |||
| display: block; | |||
| overflow: auto; | |||
| position: fixed; | |||
| left: auto; | |||
| bottom: 0; | |||
| right: 0; | |||
| max-width: 50%; | |||
| border: solid 0.3em white; | |||
| -moz-border-radius: 0.5em; | |||
| background: black; | |||
| padding: 0.2em; | |||
| } | |||
| /* { */ | |||
| .admonition * { | |||
| background: transparent; | |||
| color: white; | |||
| } | |||
| .admonition dl | |||
| { | |||
| display: table; | |||
| margin: 0; | |||
| padding: 0; | |||
| background: #333; | |||
| border: dotted black 0.3em; | |||
| width: 90% | |||
| margin-top: 0.5em; | |||
| margin-bottom: 0.5em; | |||
| } | |||
| .admonition dt | |||
| { | |||
| display: table-cell; | |||
| vertical-align: center; | |||
| border-right: solid silver 0.4em; | |||
| font-weight: bold; | |||
| font-size: 115%; | |||
| font-family: Serif; | |||
| background: gray; | |||
| width: 0; | |||
| text-shadow: black 0.15em 0.15em 0.15em; | |||
| } | |||
| .admonition dd | |||
| { | |||
| padding-left: 0.4em; | |||
| display: table-cell; | |||
| width: 100%; | |||
| text-align: justify; | |||
| } | |||
| .admonition table | |||
| { | |||
| margin: 0; | |||
| padding: 0; | |||
| background: #333; | |||
| border: dotted black 0.3em; | |||
| width: 90% | |||
| margin-top: 0.5em; | |||
| margin-bottom: 0.5em; | |||
| } | |||
| .admonition td { | |||
| width: 100%; | |||
| text-align: justify; | |||
| } | |||
| .admonition td:first-child:contains("Warning:") { | |||
| background: #900; | |||
| } | |||
| .admonition td:first-child:contains("Caution:") { | |||
| background: #960; | |||
| } | |||
| .admonition td:first-child:contains("Note:") { | |||
| background: #690; | |||
| } | |||
| .admonition td:first-child { | |||
| border-right: solid silver 0.4em; | |||
| font-weight: bold; | |||
| font-size: 115%; | |||
| font-family: Serif; | |||
| background: gray; | |||
| width: 0; | |||
| text-shadow: black 0.15em 0.15em 0.15em; | |||
| } | |||
| /* #(b) */ | |||
| /* b { color: olive; } */ | |||
| /* #(c) */ | |||
| tt { | |||
| color: #7f0; | |||
| } | |||
| /* ! keywords ... */ | |||
| p em { | |||
| color: gray; | |||
| font-style: normal; | |||
| font-weight: bold; | |||
| } | |||
| /* *, + */ | |||
| /* Bullet, numbe */ | |||
| li { | |||
| color: #f0f; | |||
| } | |||
| /* Text */ | |||
| li span, li p { | |||
| color: white; | |||
| } | |||
| li p { | |||
| color: red; | |||
| display: block; | |||
| } | |||
| ul { | |||
| list-style-type: square; | |||
| } | |||
| dl { | |||
| margin-left: 2%; | |||
| margin-top: 1em; | |||
| } | |||
| /* = */ | |||
| dt { | |||
| background: #181818; | |||
| padding: 0.2em; | |||
| /* font-variant: small-caps; */ | |||
| font-weight: bold; | |||
| color: #f0f; | |||
| } | |||
| dd { | |||
| color: white; | |||
| text-align: justify; | |||
| margin-right: 5%; | |||
| } | |||
| dt a:link, dt a:visited { | |||
| color: #f0f; | |||
| } | |||
| dt a:link:hover { | |||
| color: silver; | |||
| text-decoration: underline; | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| # -*- mode: makefile; -*- | |||
| all: Session | |||
| Session_SRCS := $(wildcard src/*.C src/*.fl) | |||
| # Session_SRCS += util/debug.C util/Thread.C util/file.C | |||
| Session_SRCS:=$(Session_SRCS:.fl=.C) | |||
| Session_SRCS:=$(sort $(Session_SRCS)) | |||
| Session_OBJS:=$(Session_SRCS:.C=.o) | |||
| Session_LIBS := $(LIBLO_LIBS) | |||
| src/nsmd: src/nsmd.o nonlib/libnonlib.a | |||
| @ echo -n Linking session handler. | |||
| @ $(CXX) $(CXXFLAGS) $(Session_LIBS) src/nsmd.o -o $@ -Lnonlib -lnonlib -ldl && echo $(DONE) | |||
| src/session-manager: src/session-manager.o nonlib/libnonlib.a | |||
| @ echo -n Linking session handler. | |||
| @ $(CXX) $(CXXFLAGS) $(FLTK_LIBS) $(Session_LIBS) src/session-manager.o -o $@ -Lnonlib -lnonlib -ldl && echo $(DONE) | |||
| src/send_osc: src/send_osc.o nonlib/libnonlib.a | |||
| @ $(CXX) $(CXXFLAGS) $(Session_LIBS) src/send_osc.o -o $@ -Lnonlib -lnonlib -ddl && echo $(DONE) | |||
| Session: src/send_osc src/nsmd src/session-manager | |||
| Session_clean: | |||
| rm -f $(Session_OBJS) src/nsmd | |||
| @@ -0,0 +1 @@ | |||
| ../nonlib/ | |||
| @@ -0,0 +1 @@ | |||
| ../scripts | |||
| @@ -0,0 +1 @@ | |||
| ../../FL | |||
| @@ -0,0 +1,112 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2010 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 <lo/lo.h> | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include <OSC/Endpoint.H> | |||
| #include <ctype.h> | |||
| #include <string.h> | |||
| #include <time.h> | |||
| #include <unistd.h> | |||
| /* helper macros for defining OSC handlers */ | |||
| #define OSC_NAME( name ) osc_ ## name | |||
| // #define OSCDMSG() DMESSAGE( "Got OSC message: %s", path ); | |||
| #define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| static bool got_response = false; | |||
| /************************/ | |||
| /* OSC Message Handlers */ | |||
| /************************/ | |||
| OSC_HANDLER( reply ) | |||
| { | |||
| // OSCDMSG(); | |||
| printf( "Reply: " ); | |||
| for ( int i = 0; i < argc; ++i ) | |||
| { | |||
| switch ( types[i] ) | |||
| { | |||
| case 's': | |||
| printf( "\"%s\" ", &argv[i]->s ); | |||
| break; | |||
| case 'f': | |||
| printf( "%f ", argv[i]->f ); | |||
| break; | |||
| case 'i': | |||
| printf( "%i ", argv[i]->i ); | |||
| break; | |||
| } | |||
| } | |||
| printf( "\n" ); | |||
| got_response = true; | |||
| return 0; | |||
| } | |||
| int main(int argc, char *argv[]) | |||
| { | |||
| OSC::Endpoint s; | |||
| s.init( NULL ); | |||
| s.add_method( NULL, NULL, OSC_NAME( reply ), 0, ""); | |||
| int r; | |||
| std::list<OSC::OSC_Value> args; | |||
| for ( int i = 3; i < argc; ++i ) | |||
| { | |||
| const char *s = argv[i]; | |||
| if ( strspn( s, "+-0123456789" ) == strlen( s ) ) | |||
| { | |||
| args.push_back( OSC::OSC_Int( atol( s ) ) ); | |||
| } | |||
| else if ( strspn( s, ".+-0123456789" ) == strlen( s ) ) | |||
| args.push_back( OSC::OSC_Float( atof( s ) ) ); | |||
| else | |||
| { | |||
| args.push_back( OSC::OSC_String( s ) ); | |||
| } | |||
| } | |||
| lo_address t = lo_address_new_from_url( argv[1] ); | |||
| fprintf( stderr, "Sending to %s\n", argv[1] ); | |||
| s.send( t, argv[2], args ); | |||
| printf( "Waiting for reply...\n" ); | |||
| while ( ! got_response ) | |||
| s.wait( 1000 * 30 ); | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,763 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 "OSC/Endpoint.H" | |||
| #include <FL/Fl_Window.H> | |||
| #include <FL/Fl_Double_Window.H> | |||
| #include <FL/Fl_Widget.H> | |||
| #include <FL/Fl.H> | |||
| #include <FL/Fl_File_Chooser.H> | |||
| #include <FL/Fl_Box.H> | |||
| #include <FL/Fl_Pack.H> | |||
| #include <FL/Fl_File_Chooser.H> | |||
| #include <FL/Fl_Progress.H> | |||
| #include "debug.h" | |||
| #include <FL/Fl_Browser.H> | |||
| #include <FL/Fl_Select_Browser.H> | |||
| #include <FL/Fl_Tile.H> | |||
| #include "FL/Fl_Packscroller.H" | |||
| #include <unistd.h> | |||
| #include <errno.h> | |||
| #include <time.h> | |||
| #define APP_NAME "Non Session Manager" | |||
| static lo_address nsm_addr = NULL; | |||
| static time_t last_ping_response; | |||
| static OSC::Endpoint *osc_endpoint; | |||
| class NSM_Client : public Fl_Group | |||
| { | |||
| char *_client_id; | |||
| // Fl_Box *client_name; | |||
| Fl_Progress *_progress; | |||
| Fl_Light_Button *_dirty; | |||
| Fl_Button *_remove_button; | |||
| Fl_Button *_restart_button; | |||
| public: | |||
| void | |||
| name ( const char *v ) | |||
| { | |||
| label( strdup( v ) ); | |||
| } | |||
| void | |||
| client_id ( const char *v ) | |||
| { | |||
| if ( _client_id ) | |||
| free( _client_id ); | |||
| _client_id = strdup( v ); | |||
| } | |||
| void | |||
| progress ( float f ) | |||
| { | |||
| _progress->value( f ); | |||
| _progress->redraw(); | |||
| } | |||
| void | |||
| dirty ( bool b ) | |||
| { | |||
| _dirty->value( b ); | |||
| _dirty->redraw(); | |||
| } | |||
| void | |||
| stopped ( bool b ) | |||
| { | |||
| if ( b ) | |||
| { | |||
| _remove_button->show(); | |||
| _restart_button->show(); | |||
| color( FL_RED ); | |||
| redraw(); | |||
| } | |||
| else | |||
| { | |||
| _restart_button->hide(); | |||
| _remove_button->hide(); | |||
| } | |||
| /* _restart_button->redraw(); */ | |||
| /* _remove_button->redraw(); */ | |||
| } | |||
| void | |||
| pending_command ( const char *command ) | |||
| { | |||
| char *cmd = strdup( command ); | |||
| free( (void*)_progress->label() ); | |||
| _progress->label( cmd ); | |||
| stopped( 0 ); | |||
| if ( ! strcmp( command, "ready" ) ) | |||
| { | |||
| color( FL_GREEN ); | |||
| // _progress->value( 0.0f ); | |||
| } | |||
| else if ( ! strcmp( command, "quit" ) || | |||
| ! strcmp( command, "kill" ) || | |||
| ! strcmp( command, "error" ) ) | |||
| { | |||
| color( FL_RED ); | |||
| } | |||
| else if ( ! strcmp( command, "stopped" ) ) | |||
| { | |||
| stopped( 1 ); | |||
| } | |||
| else | |||
| { | |||
| color( FL_YELLOW ); | |||
| } | |||
| redraw(); | |||
| } | |||
| static void | |||
| cb_button ( Fl_Widget *o, void * v ) | |||
| { | |||
| ((NSM_Client*)v)->cb_button( o ); | |||
| } | |||
| void | |||
| cb_button ( Fl_Widget *o ) | |||
| { | |||
| if ( o == _dirty ) | |||
| { | |||
| MESSAGE( "Sending save."); | |||
| osc_endpoint->send( nsm_addr, "/nsm/gui/client/save", _client_id ); | |||
| } | |||
| if ( o == _remove_button ) | |||
| { | |||
| MESSAGE( "Sending remove."); | |||
| osc_endpoint->send( nsm_addr, "/nsm/gui/client/remove", _client_id ); | |||
| } | |||
| else if ( o == _restart_button ) | |||
| { | |||
| MESSAGE( "Sending resume" ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/gui/client/resume", _client_id ); | |||
| } | |||
| } | |||
| const char * | |||
| client_id ( void ) | |||
| { return _client_id; } | |||
| NSM_Client ( int X, int Y, int W, int H, const char *L ) : | |||
| Fl_Group( X, Y, W, H, L ) | |||
| { | |||
| _client_id = NULL; | |||
| align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); | |||
| color( FL_RED ); | |||
| box( FL_UP_BOX ); | |||
| { Fl_Progress *o = _progress = new Fl_Progress( ( X + W ) - ( W / 4) - 20, Y + 5, ( W / 4 ), H - 10, NULL ); | |||
| o->label( strdup( "launch" ) ); | |||
| o->minimum( 0.0f ); | |||
| o->maximum( 1.0f ); | |||
| } | |||
| { Fl_Light_Button *o = _dirty = new Fl_Light_Button( _progress->x() - 30, Y + 7, 25, 25 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color(); | |||
| o->selection_color( FL_YELLOW ); | |||
| o->value( 0 ); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| { Fl_Button *o = _remove_button = new Fl_Button( _progress->x() - 60, Y + 7, 25, 25 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color( FL_RED ); | |||
| o->value( 0 ); | |||
| o->label( "X" ); | |||
| o->tooltip( "Remove" ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| { Fl_Button *o = _restart_button = new Fl_Button( _progress->x() - 90, Y + 7, 25, 25 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color( FL_GREEN ); | |||
| o->value( 0 ); | |||
| o->label( "@>" ); | |||
| o->tooltip( "Resume" ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| end(); | |||
| } | |||
| }; | |||
| class NSM_Controller : public Fl_Group | |||
| { | |||
| public: | |||
| Fl_Pack *clients_pack; | |||
| Fl_Pack *buttons_pack; | |||
| Fl_Button *close_button; | |||
| Fl_Button *abort_button; | |||
| Fl_Button *save_button; | |||
| Fl_Button *open_button; | |||
| Fl_Button *new_button; | |||
| Fl_Button *add_button; | |||
| Fl_Button *duplicate_button; | |||
| Fl_Select_Browser *session_browser; | |||
| static void cb_handle ( Fl_Widget *w, void *v ) | |||
| { | |||
| ((NSM_Controller*)v)->cb_handle( w ); | |||
| } | |||
| void | |||
| cb_handle ( Fl_Widget *w ) | |||
| { | |||
| if ( w == abort_button ) | |||
| { | |||
| if ( 0 == fl_choice( "Are you sure you want to abort this session? Unsaved changes will be lost.", "Abort", "Cancel", NULL ) ) | |||
| { | |||
| MESSAGE( "Sending abort." ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/abort" ); | |||
| } | |||
| } | |||
| if ( w == close_button ) | |||
| { | |||
| MESSAGE( "Sending close." ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/close" ); | |||
| } | |||
| else if ( w == save_button ) | |||
| { | |||
| MESSAGE( "Sending save." ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/save" ); | |||
| } | |||
| else if ( w == open_button ) | |||
| { | |||
| const char *name = fl_input( "Open Session", NULL ); | |||
| if ( ! name ) | |||
| return; | |||
| MESSAGE( "Sending open for: %s", name ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/open", name ); | |||
| } | |||
| else if ( w == duplicate_button ) | |||
| { | |||
| const char *name = fl_input( "New Session", NULL ); | |||
| if ( ! name ) | |||
| return; | |||
| MESSAGE( "Sending duplicate for: %s", name ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/duplicate", name ); | |||
| } | |||
| else if ( w == session_browser ) | |||
| { | |||
| const char *name = session_browser->text( session_browser->value()); | |||
| /* strip out formatting codes */ | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/open", index( name, ' ' ) + 1 ); | |||
| } | |||
| else if ( w == new_button ) | |||
| { | |||
| const char *name = fl_input( "New Session", NULL ); | |||
| if ( !name ) | |||
| return; | |||
| MESSAGE( "Sending new for: %s", name ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/new", name ); | |||
| } | |||
| else if ( w == add_button ) | |||
| { | |||
| const char *name = fl_input( "Add Client" ); | |||
| if ( !name ) | |||
| return; | |||
| MESSAGE( "Sending add for: %s", name ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/add", name ); | |||
| } | |||
| } | |||
| void | |||
| ForwardSort( Fl_Browser *b ) { | |||
| for ( int t=1; t<=b->size(); t++ ) { | |||
| for ( int r=t+1; r<=b->size(); r++ ) { | |||
| if ( strcmp(b->text(t), b->text(r)) > 0 ) { | |||
| b->swap(t,r); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void | |||
| sort_sessions ( void ) | |||
| { | |||
| ForwardSort( session_browser ); | |||
| } | |||
| NSM_Client * | |||
| client_by_id ( const char *id ) | |||
| { | |||
| for ( int i = clients_pack->children(); i--; ) | |||
| { | |||
| NSM_Client *c = (NSM_Client*)clients_pack->child( i ); | |||
| if ( ! strcmp( c->client_id(), id ) ) | |||
| { | |||
| return c; | |||
| } | |||
| } | |||
| return NULL; | |||
| } | |||
| void | |||
| session_name ( const char *name ) | |||
| { | |||
| if ( clients_pack->label() ) | |||
| free( (char*)clients_pack->label() ); | |||
| clients_pack->parent()->label( strdup( name ) ); | |||
| redraw(); | |||
| } | |||
| void | |||
| client_stopped ( const char *client_id ) | |||
| { | |||
| NSM_Client *c = client_by_id( client_id ); | |||
| if ( c ) | |||
| { | |||
| c->stopped( 1 ); | |||
| } | |||
| } | |||
| void | |||
| client_quit ( const char *client_id ) | |||
| { | |||
| NSM_Client *c = client_by_id( client_id ); | |||
| if ( c ) | |||
| { | |||
| clients_pack->remove( c ); | |||
| delete c; | |||
| } | |||
| if ( clients_pack->children() == 0 ) | |||
| { | |||
| ((Fl_Packscroller*)clients_pack->parent())->yposition( 0 ); | |||
| } | |||
| parent()->redraw(); | |||
| } | |||
| void | |||
| client_new ( const char *client_id, const char *client_name ) | |||
| { | |||
| NSM_Client *c; | |||
| c = client_by_id( client_id ); | |||
| if ( c ) | |||
| { | |||
| c->name( client_name ); | |||
| return; | |||
| } | |||
| c = new NSM_Client( 0, 0, w(), 40, NULL ); | |||
| c->name( client_name ); | |||
| c->client_id( client_id ); | |||
| c->stopped( 0 ); | |||
| clients_pack->add( c ); | |||
| redraw(); | |||
| } | |||
| void client_pending_command ( NSM_Client *c, const char *command ) | |||
| { | |||
| if ( c ) | |||
| { | |||
| if ( ! strcmp( command, "removed" ) ) | |||
| { | |||
| clients_pack->remove( c ); | |||
| delete c; | |||
| parent()->redraw(); | |||
| } | |||
| else | |||
| c->pending_command( command ); | |||
| } | |||
| } | |||
| void add_session_to_list ( const char *name ) | |||
| { | |||
| char *s; | |||
| asprintf( &s, "@S18@C3 %s", name ); | |||
| session_browser->add( s ); | |||
| free(s); | |||
| } | |||
| NSM_Controller ( int X, int Y, int W, int H, const char *L ) : | |||
| Fl_Group( X, Y, W, H, L ) | |||
| { | |||
| align( FL_ALIGN_RIGHT | FL_ALIGN_CENTER | FL_ALIGN_INSIDE ); | |||
| { Fl_Pack *o = buttons_pack = new Fl_Pack( X, Y, W, 30 ); | |||
| o->type( Fl_Pack::HORIZONTAL ); | |||
| o->box( FL_NO_BOX ); | |||
| { Fl_Button *o = open_button = new Fl_Button( 0, 0, 80, 50, "Open" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| { Fl_Button *o = close_button = new Fl_Button( 0, 0, 80, 50, "Close" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| { Fl_Button *o = abort_button = new Fl_Button( 0, 0, 80, 50, "Abort" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->color( FL_RED ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| { Fl_Button *o = save_button = new Fl_Button( 0, 0, 80, 50, "Save" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| { Fl_Button *o = new_button = new Fl_Button( 0, 0, 80, 50, "New" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| { Fl_Button *o = duplicate_button = new Fl_Button( 0, 0, 100, 50, "Duplicate" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| { Fl_Button *o = add_button = new Fl_Button( 0, 0, 100, 100, "Add Client" ); | |||
| o->box( FL_UP_BOX ); | |||
| o->callback( cb_handle, (void*)this ); | |||
| } | |||
| o->end(); | |||
| add(o); | |||
| } | |||
| { Fl_Tile *o = new Fl_Tile( X, Y + 50, W, H - 50 ); | |||
| { | |||
| Fl_Select_Browser *o = session_browser = new Fl_Select_Browser( X, Y + 50, W / 3, H - 50 ); | |||
| o->callback( cb_handle, (void *)this ); | |||
| o->color( fl_darker( FL_GRAY ) ); | |||
| o->box( FL_ROUNDED_BOX ); | |||
| o->label( "Sessions" ); | |||
| } | |||
| { | |||
| Fl_Packscroller *o = new Fl_Packscroller( X + ( W / 3 ), Y + 50, ( W / 3 ) * 2, H - 50 ); | |||
| o->align( FL_ALIGN_TOP ); | |||
| o->labeltype( FL_SHADOW_LABEL ); | |||
| { | |||
| Fl_Pack *o = clients_pack = new Fl_Pack( X + ( W / 3 ), Y + 50, ( W / 3 ) * 2, H - 50 ); | |||
| o->align( FL_ALIGN_TOP ); | |||
| o->type( Fl_Pack::VERTICAL ); | |||
| o->end(); | |||
| Fl_Group::current()->resizable( o ); | |||
| } | |||
| o->end(); | |||
| } | |||
| o->end(); | |||
| } | |||
| Fl_Group::current()->resizable( this ); | |||
| end(); | |||
| deactivate(); | |||
| } | |||
| int min_h ( void ) | |||
| { | |||
| return 500; | |||
| } | |||
| void | |||
| ping ( void ) | |||
| { | |||
| if ( nsm_addr ) | |||
| { | |||
| osc_endpoint->send( nsm_addr, "/osc/ping" ); | |||
| } | |||
| if ( last_ping_response ) | |||
| { | |||
| if ( time(NULL) - last_ping_response > 10 ) | |||
| { | |||
| if ( active() ) | |||
| { | |||
| deactivate(); | |||
| fl_alert( "Server is not responding..." ); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| int init_osc ( void ) | |||
| { | |||
| osc_endpoint = new OSC::Endpoint(); | |||
| if ( int r = osc_endpoint->init() ) | |||
| return r; | |||
| osc_endpoint->owner = this; | |||
| osc_endpoint->url(); | |||
| osc_endpoint->add_method( "/error", "sis", osc_handler, osc_endpoint, "msg" ); | |||
| osc_endpoint->add_method( "/reply", "ss", osc_handler, osc_endpoint, "msg" ); | |||
| osc_endpoint->add_method( "/reply", "s", osc_handler, osc_endpoint, "" ); | |||
| osc_endpoint->add_method( "/nsm/gui/announce", "s", osc_handler, osc_endpoint, "msg" ); | |||
| osc_endpoint->add_method( "/nsm/gui/session/session", "s", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/gui/session/name", "s", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/gui/client/new", "ss", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/gui/client/status", "ss", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/gui/client/switch", "ss", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/gui/client/progress", "sf", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->add_method( "/nsm/gui/client/dirty", "si", osc_handler, osc_endpoint, "path,display_name" ); | |||
| osc_endpoint->start(); | |||
| } | |||
| void announce ( const char *nsm_url ) | |||
| { | |||
| nsm_addr = lo_address_new_from_url( nsm_url ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/gui/announce" ); | |||
| } | |||
| private: | |||
| static int osc_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) | |||
| { | |||
| OSC_DMSG(); | |||
| NSM_Controller *controller = (NSM_Controller*)((OSC::Endpoint*)user_data)->owner; | |||
| Fl::lock(); | |||
| if ( !strcmp( path, "/nsm/gui/session/session" ) ) | |||
| { | |||
| controller->add_session_to_list( &argv[0]->s ); | |||
| controller->sort_sessions(); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/announce" ) ) | |||
| { | |||
| controller->activate(); | |||
| if ( ! nsm_addr ) | |||
| nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/list" ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/session/name" )) | |||
| { | |||
| controller->session_name( &argv[0]->s ); | |||
| } | |||
| else if (!strcmp( path, "/error" )) | |||
| { | |||
| int err = argv[1]->i; | |||
| if ( err != 0 ) | |||
| fl_alert( "ERROR: %s failed with: %s", &argv[0]->s, &argv[2]->s ); | |||
| } | |||
| else if (!strcmp( path, "/reply" )) | |||
| { | |||
| if ( !strcmp( &argv[0]->s, "/nsm/server/list" ) ) | |||
| { | |||
| controller->add_session_to_list( &argv[1]->s ); | |||
| controller->sort_sessions(); | |||
| } | |||
| else if ( !strcmp( &argv[0]->s, "/osc/ping" ) ) | |||
| { | |||
| last_ping_response = time( NULL ); | |||
| } | |||
| else | |||
| MESSAGE( "%s says %s", &argv[0]->s, &argv[1]->s); | |||
| } | |||
| if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) ) | |||
| { | |||
| if ( !strcmp( path, "/nsm/gui/client/new" )) | |||
| { | |||
| controller->client_new( &argv[0]->s, &argv[1]->s ); | |||
| } | |||
| else | |||
| { | |||
| NSM_Client *c = controller->client_by_id( &argv[0]->s ); | |||
| if ( c ) | |||
| { | |||
| if ( !strcmp( path, "/nsm/gui/client/status" )) | |||
| { | |||
| controller->client_pending_command( c, &argv[1]->s ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/progress" )) | |||
| { | |||
| c->progress( argv[1]->f ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/dirty" )) | |||
| { | |||
| c->dirty( argv[1]->i ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/switch" ) ) | |||
| { | |||
| c->client_id( &argv[1]->s ); | |||
| } | |||
| } | |||
| else | |||
| MESSAGE( "Got message %s from unknown client", path ); | |||
| } | |||
| } | |||
| Fl::unlock(); | |||
| Fl::awake(); | |||
| return 0; | |||
| } | |||
| }; | |||
| static NSM_Controller *controller; | |||
| void | |||
| ping ( void *v ) | |||
| { | |||
| controller->ping(); | |||
| Fl::repeat_timeout( 1.0, ping, NULL ); | |||
| } | |||
| int | |||
| main (int argc, char **argv ) | |||
| { | |||
| Fl::get_system_colors(); | |||
| Fl::scheme( "plastic" ); | |||
| Fl::lock(); | |||
| Fl_Double_Window *main_window; | |||
| { | |||
| Fl_Double_Window *o = main_window = new Fl_Double_Window( 600, 800, APP_NAME ); | |||
| { | |||
| main_window->xclass( APP_NAME ); | |||
| Fl_Widget *o = controller = new NSM_Controller( 0, 0, main_window->w(), main_window->h(), NULL ); | |||
| Fl_Group::current()->resizable(o); | |||
| } | |||
| o->end(); | |||
| o->size_range( main_window->w(), controller->min_h(), 0, 0 ); | |||
| // o->callback( (Fl_Callback*)cb_main, main_window ); | |||
| o->show( argc, argv ); | |||
| // o->show(); | |||
| } | |||
| const char *nsm_url = getenv( "NSM_URL" ); | |||
| if ( nsm_url ) | |||
| { | |||
| MESSAGE( "Found NSM URL of \"%s\" in environment, attempting to connect.", nsm_url ); | |||
| if ( ! controller->init_osc() ) | |||
| { | |||
| controller->announce( nsm_url ); | |||
| } | |||
| else | |||
| FATAL( "Could not create OSC server" ); | |||
| } | |||
| else | |||
| { | |||
| if ( controller->init_osc() ) | |||
| FATAL( "Could not create OSC server" ); | |||
| /* start a new daemon... */ | |||
| MESSAGE( "Starting daemon..." ); | |||
| char *url = osc_endpoint->url(); | |||
| if ( ! fork() ) | |||
| { | |||
| char *args[] = { "nsmd", "--gui-url", url, NULL }; | |||
| if ( -1 == execvp( "nsmd", args ) ) | |||
| { | |||
| FATAL( "Error starting process: %s", strerror( errno ) ); | |||
| } | |||
| } | |||
| } | |||
| Fl::add_timeout( 1.0, ping, NULL ); | |||
| Fl::run(); | |||
| if ( ! nsm_url ) | |||
| { | |||
| MESSAGE( "Telling server to quit" ); | |||
| osc_endpoint->send( nsm_addr, "/nsm/server/quit" ); | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -27,5 +27,8 @@ require_command ar ar | |||
| require_command makedepend makedepend | |||
| require_package JACK 0.103.0 jack | |||
| require_package sndfile 1.0.17 sndfile | |||
| require_package liblo 0.23 liblo | |||
| test_version `version_of liblo` 0.26 || warn "Version $(version_of liblo) of liblo is slow to create servers. Consider upgrading to 0.26 or later" | |||
| end | |||
| @@ -10,7 +10,7 @@ Timeline_SRCS:=$(Timeline_SRCS:.fl=.C) | |||
| Timeline_SRCS:=$(sort $(Timeline_SRCS)) | |||
| Timeline_OBJS:=$(Timeline_SRCS:.C=.o) | |||
| Timeline_LIBS := $(FLTK_LIBS) $(JACK_LIBS) $(SNDFILE_LIBS) | |||
| Timeline_LIBS := $(FLTK_LIBS) $(JACK_LIBS) $(SNDFILE_LIBS) $(LIBLO_LIBS) | |||
| src/timeline: $(Timeline_OBJS) FL/libfl_widgets.a nonlib/libnonlib.a | |||
| @ echo -n Linking timeline... | |||
| @@ -34,6 +34,8 @@ | |||
| Engine::Engine ( ) : _thread( "RT" ) | |||
| { | |||
| _buffers_dropped = 0; | |||
| DMESSAGE( "Creating audio I/O engine" ); | |||
| } | |||
| Engine::~Engine ( ) | |||
| @@ -0,0 +1,83 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 "const.h" | |||
| #include "debug.h" | |||
| #include "Timeline.H" | |||
| #include "TLE.H" | |||
| #include "NSM.H" | |||
| #include "Project.H" | |||
| #define OSC_INTERVAL 0.2f | |||
| extern char *instance_name; | |||
| extern Timeline *timeline; | |||
| extern NSM_Client *nsm; | |||
| NSM_Client::NSM_Client ( ) | |||
| { | |||
| } | |||
| int command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ); | |||
| int command_save ( char **out_msg ); | |||
| int | |||
| NSM_Client::command_save ( char **out_msg ) | |||
| { | |||
| if ( timeline->command_save() ) | |||
| return ERR_OK; | |||
| else | |||
| { | |||
| *out_msg = strdup( "Failed to save for unknown reason"); | |||
| return ERR_GENERAL; | |||
| } | |||
| } | |||
| int | |||
| NSM_Client::command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ) | |||
| { | |||
| if ( instance_name ) | |||
| free( instance_name ); | |||
| instance_name = strdup( client_id ); | |||
| if ( Project::validate( name ) ) | |||
| { | |||
| if ( timeline->command_load( name, display_name ) ) | |||
| return ERR_OK; | |||
| else | |||
| { | |||
| *out_msg = strdup( "Failed to load for unknown reason" ); | |||
| return ERR_GENERAL; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if ( timeline->command_new( name, display_name ) ) | |||
| return ERR_OK; | |||
| else | |||
| { | |||
| *out_msg = strdup( "Failed to load for unknown reason" ); | |||
| return ERR_GENERAL; | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2012 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 | |||
| #include "NSM/Client.H" | |||
| class NSM_Client : public NSM::Client | |||
| { | |||
| public: | |||
| NSM_Client ( ); | |||
| ~NSM_Client ( ) { }; | |||
| protected: | |||
| int command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ); | |||
| int command_save ( char **out_msg ); | |||
| }; | |||
| @@ -44,10 +44,15 @@ | |||
| #include "file.h" | |||
| #include "Block_Timer.H" | |||
| #include "Transport.H" | |||
| extern Transport *transport; | |||
| extern TLE *tle; | |||
| const int PROJECT_VERSION = 1; | |||
| extern char *instance_name; | |||
| const char *Project::_errstr[] = | |||
| @@ -200,6 +205,9 @@ Project::close ( void ) | |||
| release_lock( &_lockfd, ".lock" ); | |||
| delete engine; | |||
| engine = NULL; | |||
| return true; | |||
| } | |||
| @@ -233,6 +241,25 @@ Project::validate ( const char *name ) | |||
| return r; | |||
| } | |||
| void | |||
| Project::make_engine ( void ) | |||
| { | |||
| if ( engine ) | |||
| FATAL( "Engine should be null!" ); | |||
| engine = new Engine; | |||
| if ( ! engine->init( instance_name, JACK::Client::SLOW_SYNC | JACK::Client::TIMEBASE_MASTER )) | |||
| FATAL( "Could not connect to JACK!" ); | |||
| timeline->sample_rate( engine->sample_rate() ); | |||
| /* always start stopped (please imagine for me a realistic | |||
| * scenario requiring otherwise */ | |||
| transport->stop(); | |||
| } | |||
| /** Try to open project /name/. Returns 0 if sucsessful, an error code | |||
| * otherwise */ | |||
| int | |||
| @@ -262,13 +289,19 @@ Project::open ( const char *name ) | |||
| if ( version != PROJECT_VERSION ) | |||
| return E_VERSION; | |||
| /* normally, engine will be NULL after a close or on an initial open, | |||
| but 'new' will have already created it to get the sample rate. */ | |||
| if ( ! engine ) | |||
| make_engine(); | |||
| { | |||
| Block_Timer timer( "Replayed journal" ); | |||
| if ( ! Loggable::open( "history" ) ) | |||
| return E_INVALID; | |||
| } | |||
| timeline->sample_rate( rate ); | |||
| /* /\* really a good idea? *\/ */ | |||
| /* timeline->sample_rate( rate ); */ | |||
| if ( creation_date ) | |||
| { | |||
| @@ -319,6 +352,9 @@ Project::create ( const char *name, const char *template_name ) | |||
| mkdir( "sources", 0777 ); | |||
| creat( "history", 0666 ); | |||
| if ( ! engine ) | |||
| make_engine(); | |||
| /* TODO: copy template */ | |||
| write_info(); | |||
| @@ -33,9 +33,9 @@ class Project | |||
| static bool write_info ( void ); | |||
| static bool read_info ( int *version, nframes_t *sample_rate, char **creation_date, char **created_by ); | |||
| static void set_name ( const char *name ); | |||
| static const char *_errstr[]; | |||
| static void make_engine ( void ); | |||
| public: | |||
| enum | |||
| @@ -47,6 +47,8 @@ public: | |||
| E_VERSION = -5 | |||
| }; | |||
| static void set_name ( const char *name ); | |||
| static const char *errstr ( int n ) { return _errstr[ ( 0 - n ) - 1 ]; } | |||
| static const char *name ( void ) { return Project::_name; } | |||
| @@ -67,43 +67,44 @@ decl {\#include "FL/About_Dialog.H"} {} | |||
| decl {extern char project_display_name[256];} {global | |||
| } | |||
| decl {\#include "NSM.H"} {} | |||
| decl {extern NSM_Client *nsm;} {global | |||
| } | |||
| decl {extern char *user_config_dir;} {global | |||
| } | |||
| class TLE {open | |||
| } { | |||
| decl {Fl_Color system_colors[3];} {} | |||
| Function {save_options()} {open | |||
| } { | |||
| code { | |||
| const char options_filename[] = "options"; | |||
| // const char state_filename[] = "state"; | |||
| // save options | |||
| char *path; | |||
| asprintf( &path, "%s/%s", user_config_dir, options_filename ); | |||
| ((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Options" ), path ); | |||
| free( path ); | |||
| } {} | |||
| } | |||
| Function {save()} {open | |||
| } { | |||
| code {const char options_filename[] = "options"; | |||
| // const char state_filename[] = "state"; | |||
| // save options | |||
| char *path; | |||
| asprintf( &path, "%s/%s", user_config_dir, options_filename ); | |||
| ((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Options" ), path ); | |||
| free( path );} {} | |||
| code { | |||
| timeline->command_save();} {} | |||
| } | |||
| Function {quit()} {} { | |||
| code {Project::close(); | |||
| save(); | |||
| while ( Fl::first_window() ) Fl::first_window()->hide();} {} | |||
| code { | |||
| timeline->command_quit();} {} | |||
| } | |||
| Function {open( const char *name )} {} { | |||
| code {if ( ! name ) | |||
| return; | |||
| int r = Project::open( name ); | |||
| if ( r < 0 ) | |||
| { | |||
| const char *s = Project::errstr( r ); | |||
| fl_alert( "Could not open project \\"%s\\":\\n\\n\\t%s", name, s ); | |||
| }} {} | |||
| code { | |||
| timeline->command_load( name, NULL );} {} | |||
| } | |||
| Function {save_timeline_settings()} {open | |||
| } { | |||
| @@ -219,7 +220,7 @@ Loggable::progress_callback( &TLE::progress_cb, this );} {} | |||
| Fl_Window main_window { | |||
| label {Non DAW : Timeline} | |||
| callback {if ( Fl::event_key() != FL_Escape ) | |||
| o->hide();} open | |||
| timeline->command_quit();} open | |||
| private xywh {705 125 1025 770} type Double resizable xclass Non_DAW visible | |||
| } { | |||
| Fl_Menu_Bar menubar {open | |||
| @@ -808,7 +809,7 @@ p->label( s );} {} | |||
| update_progress( capture_buffer_progress, cbp, timeline->total_input_buffer_percent() ); | |||
| update_progress( playback_buffer_progress, pbp, timeline->total_output_buffer_percent() ); | |||
| update_progress( cpu_load_progress, clp, engine->cpu_load() ); | |||
| update_progress( cpu_load_progress, clp, engine ? engine->cpu_load() : 0 ); | |||
| update_progress( disk_usage_progress, dup, percent_used( "." ) ); | |||
| @@ -820,15 +821,22 @@ if ( timeline->total_playback_xruns() ) | |||
| static char stats[100]; | |||
| if ( engine && ! engine->zombified() ) | |||
| { | |||
| snprintf( stats, sizeof( stats ), "latency: %.1fms, xruns: %d", | |||
| engine->frames_to_milliseconds( timeline->total_output_latency() ), | |||
| engine->xruns() ); | |||
| } | |||
| else | |||
| { | |||
| snprintf( stats, sizeof( stats ), "%s", "DISCONNECTED" ); | |||
| } | |||
| stats_box->label( stats ); | |||
| static bool zombie = false; | |||
| if ( engine->zombified() && ! zombie ) | |||
| if ( engine && engine->zombified() && ! zombie ) | |||
| { | |||
| zombie = true; | |||
| fl_alert( "Disconnected from JACK!" ); | |||
| @@ -840,6 +848,13 @@ sm_blinker->value( timeline->session_manager_name() != NULL ); | |||
| sm_blinker->tooltip( timeline->session_manager_name() ); | |||
| selected_blinker->value( timeline->nselected() ); | |||
| seek_blinker->value( timeline->seek_pending() ); | |||
| if ( timeline->session_manager_name() != NULL ) | |||
| { | |||
| find_item( menubar, "&Project/&New" )->deactivate(); | |||
| find_item( menubar, "&Project/&Open" )->deactivate(); | |||
| } | |||
| project_name->redraw();} {} | |||
| } | |||
| Function {update_cb( void *v )} {open private return_type {static void} | |||
| @@ -877,6 +892,7 @@ else if ( 0 == p ) | |||
| static char pat[10]; | |||
| nsm->progress( p / 100.0f ); | |||
| update_progress( progress, pat, p ); | |||
| progress->redraw(); | |||
| @@ -46,6 +46,14 @@ | |||
| #include "const.h" | |||
| #include "debug.h" | |||
| /* these headers are just for the NSM support */ | |||
| #include "Project.H" | |||
| #include "TLE.H" | |||
| /* */ | |||
| #include "NSM.H" | |||
| extern NSM_Client *nsm; | |||
| #ifdef USE_WIDGET_FOR_TIMELINE | |||
| #define BASE Fl_Group | |||
| #define redraw_overlay() | |||
| @@ -74,6 +82,9 @@ bool Timeline::center_playhead = true; | |||
| const float UPDATE_FREQ = 0.02f; | |||
| extern const char *instance_name; | |||
| extern TLE *tle; | |||
| /** return the combined height of all visible children of (veritcal) | |||
| @@ -1472,3 +1483,66 @@ Timeline::remove_track ( Track *track ) | |||
| /* FIXME: why is this necessary? doesn't the above add do DAMAGE_CHILD? */ | |||
| redraw(); | |||
| } | |||
| /************/ | |||
| /* Commands */ | |||
| /************/ | |||
| void | |||
| Timeline::command_quit ( ) | |||
| { | |||
| Project::close(); | |||
| command_save(); | |||
| while ( Fl::first_window() ) Fl::first_window()->hide(); | |||
| } | |||
| bool | |||
| Timeline::command_load ( const char *name, const char *display_name ) | |||
| { | |||
| if ( ! name ) | |||
| return false; | |||
| int r = Project::open( name ); | |||
| if ( r < 0 ) | |||
| { | |||
| const char *s = Project::errstr( r ); | |||
| fl_alert( "Could not open project \"%s\":\n\n\t%s", name, s ); | |||
| return false; | |||
| } | |||
| Project::set_name ( display_name ? display_name : name ); | |||
| return true; | |||
| } | |||
| bool | |||
| Timeline::command_save ( ) | |||
| { | |||
| tle->save_options(); | |||
| return true; | |||
| } | |||
| bool | |||
| Timeline::command_new ( const char *name, const char *display_name ) | |||
| { | |||
| return Project::create( name, NULL ); | |||
| Project::set_name ( display_name ); | |||
| /* FIXME: there's other stuff that needs to be done here! */ | |||
| /* tle->update_menu(); */ | |||
| /* tle->main_window->redraw(); */ | |||
| } | |||
| const char * | |||
| Timeline::session_manager_name ( void ) | |||
| { | |||
| return nsm->session_manager_name(); | |||
| } | |||
| @@ -31,6 +31,8 @@ | |||
| #include <assert.h> | |||
| #include <list> | |||
| #include "OSC/Endpoint.H" | |||
| class Fl_Scroll; | |||
| class Fl_Pack; | |||
| class Fl_Scrollbar; | |||
| @@ -149,7 +151,7 @@ public: | |||
| void update_tempomap ( void ); | |||
| const char *session_manager_name ( void ) { return NULL; } | |||
| const char *session_manager_name ( void ); | |||
| nframes_t fpp ( void ) const { return 1 << _fpp; } | |||
| void range ( nframes_t start, nframes_t length ); | |||
| @@ -221,6 +223,11 @@ public: | |||
| void wait_for_buffers ( void ); | |||
| bool seek_pending ( void ); | |||
| bool command_load ( const char *name, const char *display_name ); | |||
| bool command_new ( const char *name, const char *display_name ); | |||
| bool command_save ( void ); | |||
| void command_quit ( void ); | |||
| private: | |||
| static void snapshot ( void *v ) { ((Timeline*)v)->snapshot(); } | |||
| @@ -23,10 +23,6 @@ | |||
| #include "Engine/Engine.H" | |||
| // Transport transport; | |||
| #define client engine->jack_client() | |||
| Transport::Transport ( int X, int Y, int W, int H, const char *L ) | |||
| @@ -36,6 +32,18 @@ Transport::Transport ( int X, int Y, int W, int H, const char *L ) | |||
| rolling = false; | |||
| _stop_disables_record = true; | |||
| bar = 0; | |||
| beat = 0; | |||
| tick = 0; | |||
| beats_per_minute = 120; | |||
| ticks_per_beat = 1920; | |||
| beat_type = 4; | |||
| beats_per_bar = 4; | |||
| next_time = 0; | |||
| frame_time =0; | |||
| frame_rate = 48000; | |||
| frame = 0; | |||
| const int bw = W / 3; | |||
| type( HORIZONTAL ); | |||
| @@ -156,9 +164,10 @@ Transport::handle ( int m ) | |||
| void | |||
| Transport::poll ( void ) | |||
| { | |||
| jack_transport_state_t ts; | |||
| ts = jack_transport_query( client, this ); | |||
| ts = engine->transport_query( this ); | |||
| rolling = ts == JackTransportRolling; | |||
| } | |||
| @@ -166,9 +175,12 @@ Transport::poll ( void ) | |||
| void | |||
| Transport::locate ( nframes_t frame ) | |||
| { | |||
| if ( ! engine ) | |||
| return; | |||
| if ( ! recording ) | |||
| // don't allow seeking while record is in progress | |||
| jack_transport_locate( client, frame ); | |||
| engine->transport_locate( frame ); | |||
| } | |||
| @@ -182,7 +194,8 @@ Transport::start ( void ) | |||
| update_record_state(); | |||
| } | |||
| jack_transport_start( client ); | |||
| if ( engine ) | |||
| engine->transport_start(); | |||
| } | |||
| void | |||
| @@ -197,7 +210,8 @@ Transport::stop ( void ) | |||
| update_record_state(); | |||
| } | |||
| jack_transport_stop( client ); | |||
| if ( engine ) | |||
| engine->transport_stop(); | |||
| } | |||
| void | |||
| @@ -39,7 +39,7 @@ | |||
| #include "Track.H" | |||
| #include "TLE.H" | |||
| #include "Timeline.H" | |||
| #include "../FL/Boxtypes.H" | |||
| #include "Project.H" | |||
| @@ -48,10 +48,15 @@ | |||
| #include "Thread.H" | |||
| #include "NSM.H" | |||
| Engine *engine; | |||
| Timeline *timeline; | |||
| Transport *transport; | |||
| TLE *tle; | |||
| NSM_Client *nsm; | |||
| char *instance_name = NULL; | |||
| /* TODO: put these in a header */ | |||
| #define USER_CONFIG_DIR ".non-daw/" | |||
| @@ -60,6 +65,8 @@ const char APP_NAME[] = "Non-DAW"; | |||
| const char APP_TITLE[] = "The Non-DAW"; | |||
| const char COPYRIGHT[] = "Copyright (C) 2008-2010 Jonathan Moore Liles"; | |||
| #define OSC_INTERVAL 0.2f | |||
| #define PACKAGE "non" | |||
| @@ -96,6 +103,34 @@ shift ( char **argv, int *argc, int n ) | |||
| argc -= n; | |||
| } | |||
| extern Timeline *timeline; | |||
| void | |||
| check_osc ( void * v ) | |||
| { | |||
| nsm->check(); | |||
| Fl::repeat_timeout( OSC_INTERVAL, check_osc, v ); | |||
| } | |||
| static int got_sigterm = 0; | |||
| void | |||
| sigterm_handler ( int ) | |||
| { | |||
| got_sigterm = 1; | |||
| Fl::awake(); | |||
| } | |||
| void | |||
| check_sigterm ( void * ) | |||
| { | |||
| if ( got_sigterm ) | |||
| { | |||
| MESSAGE( "Got SIGTERM, quitting..." ); | |||
| timeline->command_quit(); | |||
| } | |||
| } | |||
| int | |||
| main ( int argc, char **argv ) | |||
| { | |||
| @@ -104,6 +139,8 @@ main ( int argc, char **argv ) | |||
| Thread thread( "UI" ); | |||
| thread.set(); | |||
| signal( SIGTERM, sigterm_handler ); | |||
| fl_register_images(); | |||
| /* welcome to C++ */ | |||
| @@ -129,32 +166,85 @@ main ( int argc, char **argv ) | |||
| tle = new TLE; | |||
| MESSAGE( "Initializing JACK" ); | |||
| instance_name = strdup( APP_NAME ); | |||
| /* we don't really need a pointer for this */ | |||
| engine = new Engine; | |||
| const char *jack_name; | |||
| if ( ! ( jack_name = engine->init( APP_NAME, JACK::Client::SLOW_SYNC | JACK::Client::TIMEBASE_MASTER ) ) ) | |||
| FATAL( "Could not connect to JACK!" ); | |||
| timeline->sample_rate( engine->sample_rate() ); | |||
| /* always start stopped (please imagine for me a realistic | |||
| * scenario requiring otherwise */ | |||
| transport->stop(); | |||
| MESSAGE( "Starting GUI" ); | |||
| tle->run(); | |||
| if ( argc > 1 ) | |||
| tle->open( argv[ 1 ] ); | |||
| // will be created on project new/open | |||
| engine = NULL; | |||
| nsm = new NSM_Client; | |||
| const char *osc_port = NULL; | |||
| { | |||
| int r = argc - 1; | |||
| int i = 1; | |||
| for ( ; i < argc; ++i, --r ) | |||
| if ( !strcmp( argv[i], "--osc-port" ) ) | |||
| { | |||
| if ( r > 1 ) | |||
| { | |||
| MESSAGE( "Using OSC port \"%s\"", argv[i+1] ); | |||
| osc_port = argv[i+1]; | |||
| --r; | |||
| ++i; | |||
| } | |||
| else | |||
| { | |||
| FATAL( "Missing OSC port" ); | |||
| } | |||
| } | |||
| MESSAGE( "Starting GUI" ); | |||
| tle->run(); | |||
| char *nsm_url = getenv( "NSM_URL" ); | |||
| if ( nsm_url ) | |||
| { | |||
| if ( ! nsm->init() ); | |||
| { | |||
| nsm->announce( nsm_url, APP_NAME, ":progress:switch:", argv[0] ); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if ( r >= 1 ) | |||
| { | |||
| MESSAGE( "Loading \"%s\"", argv[i] ); | |||
| tle->open( argv[i] ); | |||
| /* ) */ | |||
| /* { */ | |||
| /* fl_alert( "Error opening project specified on commandline" ); */ | |||
| /* } */ | |||
| } | |||
| } | |||
| } | |||
| /* poll so we can keep OSC handlers running in the GUI thread and avoid extra sync */ | |||
| Fl::add_timeout( OSC_INTERVAL, check_osc, NULL ); | |||
| Fl::add_check( check_sigterm ); | |||
| Fl::run(); | |||
| delete engine; | |||
| if ( engine ) | |||
| { | |||
| delete engine; | |||
| engine = NULL; | |||
| } | |||
| delete tle; | |||
| tle = NULL; | |||
| delete nsm; | |||
| nsm = NULL; | |||
| MESSAGE( "Your fun is over" ); | |||
| } | |||