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