Implement the Non-Session-Manager (NSM)
This commit is contained in:
parent
cff2c15583
commit
4a22d675e2
2
Makefile
2
Makefile
|
@ -17,7 +17,7 @@
|
||||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
SUBDIRS=nonlib FL timeline mixer
|
SUBDIRS=nonlib FL timeline mixer session
|
||||||
|
|
||||||
all:
|
all:
|
||||||
@ for dir in $(SUBDIRS); do $(MAKE) -s -C $$dir; done
|
@ 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
|
unsigned int
|
||||||
Chain::maximum_name_length ( void )
|
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
|
/* rename chain... we have to let our modules know our name has
|
||||||
|
@ -432,7 +432,7 @@ void
|
||||||
Chain::name ( const char *name )
|
Chain::name ( const char *name )
|
||||||
{
|
{
|
||||||
char ename[512];
|
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 )
|
if ( ! _engine )
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,14 +71,14 @@ Controller_Module::Controller_Module ( bool is_default ) : Module( is_default, 5
|
||||||
|
|
||||||
end();
|
end();
|
||||||
|
|
||||||
Fl::add_timeout( CONTROL_UPDATE_FREQ, update_cb, this );
|
// Fl::add_timeout( CONTROL_UPDATE_FREQ, update_cb, this );
|
||||||
|
|
||||||
log_create();
|
log_create();
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller_Module::~Controller_Module ( )
|
Controller_Module::~Controller_Module ( )
|
||||||
{
|
{
|
||||||
Fl::remove_timeout( update_cb, this );
|
// Fl::remove_timeout( update_cb, this );
|
||||||
|
|
||||||
log_destroy();
|
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_Menu_Bar.H>
|
||||||
#include <FL/fl_ask.H>
|
#include <FL/fl_ask.H>
|
||||||
#include <FL/Fl.H>
|
#include <FL/Fl.H>
|
||||||
#include <FL/Fl_File_Chooser.H>
|
|
||||||
#include "New_Project_Dialog.H"
|
#include "New_Project_Dialog.H"
|
||||||
#include "Engine/Engine.H"
|
#include "Engine/Engine.H"
|
||||||
#include "FL/Fl_Flowpack.H"
|
#include "FL/Fl_Flowpack.H"
|
||||||
#include "Project.H"
|
#include "Project.H"
|
||||||
#include "FL/Fl_Menu_Settings.H"
|
#include "FL/Fl_Menu_Settings.H"
|
||||||
#include "About_Dialog.H"
|
#include "About_Dialog.H"
|
||||||
|
#include <FL/Fl_File_Chooser.H>
|
||||||
|
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "FL/color_scheme.H"
|
#include "FL/color_scheme.H"
|
||||||
#include "OSC/Endpoint.H"
|
#include "OSC/Endpoint.H"
|
||||||
#include <lo/lo.h>
|
#include <lo/lo.h>
|
||||||
|
#include "FL/Fl_Blinker.H"
|
||||||
|
|
||||||
const double STATUS_UPDATE_FREQ = 0.2f;
|
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 *user_config_dir;
|
||||||
|
extern char *instance_name;
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
|
#include "NSM.H"
|
||||||
|
|
||||||
|
extern NSM_Client *nsm;
|
||||||
|
|
||||||
/* static void update_cb( void *v ) { */
|
/* static void update_cb( void *v ) { */
|
||||||
/* Fl::repeat_timeout( STATUS_UPDATE_FREQ, update_cb, v ); */
|
/* Fl::repeat_timeout( STATUS_UPDATE_FREQ, update_cb, v ); */
|
||||||
|
|
||||||
|
@ -65,69 +73,19 @@ extern char *user_config_dir;
|
||||||
/* OSC Message Handlers */
|
/* OSC Message Handlers */
|
||||||
/************************/
|
/************************/
|
||||||
|
|
||||||
|
#undef OSC_REPLY_OK
|
||||||
|
#undef OSC_REPLY_ERR
|
||||||
|
#undef OSC_REPLY
|
||||||
|
|
||||||
OSC_HANDLER( quit )
|
#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 )
|
||||||
OSC_DMSG();
|
#define OSC_REPLY_ERR(errcode, value) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path,errcode, value )
|
||||||
|
|
||||||
((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 )
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
|
|
||||||
OSC_HANDLER( add_strip )
|
OSC_HANDLER( add_strip )
|
||||||
{
|
{
|
||||||
OSC_DMSG();
|
OSC_DMSG();
|
||||||
|
|
||||||
((Mixer*)user_data)->command_add_strip();
|
((Mixer*)(OSC_ENDPOINT())->owner)->command_add_strip();
|
||||||
|
|
||||||
OSC_REPLY_OK();
|
OSC_REPLY_OK();
|
||||||
|
|
||||||
|
@ -138,20 +96,45 @@ OSC_HANDLER( finger )
|
||||||
{
|
{
|
||||||
OSC_DMSG();
|
OSC_DMSG();
|
||||||
|
|
||||||
lo_address src = lo_message_get_source( msg );
|
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 );
|
||||||
|
|
||||||
const char *s = "APP_TITLE\n";
|
lo_address_free( reply );
|
||||||
|
|
||||||
/* if ( 1 >= lo_send_from( src, ((Mixer*)user_data)->osc_endpoint, LO_TT_IMMEDIATE, "/finger-reply", "s", s ) ) */
|
|
||||||
/* { */
|
|
||||||
/* DMESSAGE( "Failed to send reply" ); */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
return 0;
|
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) {
|
void Mixer::cb_menu(Fl_Widget* o) {
|
||||||
Fl_Menu_Bar *menu = (Fl_Menu_Bar*)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->align( FL_ALIGN_INSIDE | FL_ALIGN_CENTER );
|
||||||
o->labeltype( FL_SHADOW_LABEL );
|
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 );
|
{ Fl_Scroll *o = scroll = new Fl_Scroll( X, Y + 24, W, H - 24 );
|
||||||
o->box( FL_NO_BOX );
|
o->box( FL_NO_BOX );
|
||||||
// o->type( Fl_Scroll::HORIZONTAL_ALWAYS );
|
// 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();
|
load_options();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
Mixer::init_osc ( const char *osc_port )
|
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( "/non/finger", "s", OSC_NAME( finger ), osc_endpoint, "" );
|
||||||
osc_endpoint->add_method( "/nsm/load", "ss", OSC_NAME( load ), this, "path,display_name" );
|
osc_endpoint->add_method( "/non/mixer/add_strip", "", OSC_NAME( add_strip ), osc_endpoint, "" );
|
||||||
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->start();
|
// osc_endpoint->start();
|
||||||
|
|
||||||
|
@ -680,6 +674,8 @@ Mixer::command_save ( void )
|
||||||
bool
|
bool
|
||||||
Mixer::command_load ( const char *path, const char *display_name )
|
Mixer::command_load ( const char *path, const char *display_name )
|
||||||
{
|
{
|
||||||
|
mixer->hide();
|
||||||
|
|
||||||
if ( int err = Project::open( path ) )
|
if ( int err = Project::open( path ) )
|
||||||
{
|
{
|
||||||
// fl_alert( "Error opening project specified on commandline: %s", Project::errstr( err ) );
|
// 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();
|
update_menu();
|
||||||
|
|
||||||
|
mixer->show();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <FL/Fl_Pack.H>
|
#include <FL/Fl_Pack.H>
|
||||||
#include "Mixer_Strip.H"
|
#include "Mixer_Strip.H"
|
||||||
|
|
||||||
|
class Fl_Blinker;
|
||||||
class Fl_Flowpack;
|
class Fl_Flowpack;
|
||||||
class Fl_Menu_Bar;
|
class Fl_Menu_Bar;
|
||||||
|
|
||||||
|
@ -37,9 +38,10 @@ class Mixer : public Fl_Group
|
||||||
public:
|
public:
|
||||||
|
|
||||||
OSC::Endpoint *osc_endpoint;
|
OSC::Endpoint *osc_endpoint;
|
||||||
|
Fl_Blinker *sm_blinker;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
int _rows;
|
int _rows;
|
||||||
|
|
||||||
Fl_Color system_colors[3];
|
Fl_Color system_colors[3];
|
||||||
|
@ -93,9 +95,11 @@ public:
|
||||||
|
|
||||||
static void check_osc ( void * v );
|
static void check_osc ( void * v );
|
||||||
|
|
||||||
void announce ( const char *nash_url );
|
void announce ( const char *nash_url, const char *process_name );
|
||||||
|
|
||||||
void init_osc ( const char* osc_port );
|
int init_osc ( const char* osc_port );
|
||||||
|
|
||||||
|
void sm_active ( bool b );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -213,11 +213,9 @@ Module::Port::generate_osc_path ()
|
||||||
int n = module()->chain()->get_module_instance_number( module() );
|
int n = module()->chain()->get_module_instance_number( module() );
|
||||||
|
|
||||||
if ( n > 0 )
|
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
|
else
|
||||||
asprintf( &path, "/mixer/strip/%s/control/%s/%s", module()->chain()->name(), p->module()->label(), p->name() );
|
asprintf( &path, "/non/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() );
|
|
||||||
|
|
||||||
// Hack to keep spaces out of OSC URL... Probably need to handle other special characters similarly.
|
// Hack to keep spaces out of OSC URL... Probably need to handle other special characters similarly.
|
||||||
for ( int i = strlen( path ); i--; )
|
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, "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" );
|
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 ) )
|
if ( mkdir( name, 0777 ) )
|
||||||
{
|
{
|
||||||
WARNING( "Cannot create project directory" );
|
WARNING( "Cannot create project directory: %s", name );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,14 +52,21 @@
|
||||||
#include "Chain.H"
|
#include "Chain.H"
|
||||||
#include "Mixer_Strip.H"
|
#include "Mixer_Strip.H"
|
||||||
|
|
||||||
|
#include "NSM.H"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
/* TODO: put these in a header */
|
/* TODO: put these in a header */
|
||||||
#define USER_CONFIG_DIR ".non-mixer/"
|
#define USER_CONFIG_DIR ".non-mixer/"
|
||||||
|
|
||||||
|
|
||||||
|
const double OSC_INTERVAL = 0.1f;
|
||||||
|
|
||||||
char *user_config_dir;
|
char *user_config_dir;
|
||||||
Mixer *mixer;
|
Mixer *mixer;
|
||||||
|
NSM_Client *nsm;
|
||||||
|
|
||||||
const char *instance_name;
|
char *instance_name;
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
@ -83,6 +90,32 @@ static void cb_main ( Fl_Double_Window *o, void *)
|
||||||
mixer->command_quit();
|
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
|
int
|
||||||
main ( int argc, char **argv )
|
main ( int argc, char **argv )
|
||||||
{
|
{
|
||||||
|
@ -93,6 +126,8 @@ main ( int argc, char **argv )
|
||||||
|
|
||||||
ensure_dirs();
|
ensure_dirs();
|
||||||
|
|
||||||
|
signal( SIGTERM, sigterm_handler );
|
||||||
|
|
||||||
Fl_Tooltip::color( FL_BLACK );
|
Fl_Tooltip::color( FL_BLACK );
|
||||||
Fl_Tooltip::textcolor( FL_YELLOW );
|
Fl_Tooltip::textcolor( FL_YELLOW );
|
||||||
Fl_Tooltip::size( 14 );
|
Fl_Tooltip::size( 14 );
|
||||||
|
@ -119,6 +154,8 @@ main ( int argc, char **argv )
|
||||||
Fl::get_system_colors();
|
Fl::get_system_colors();
|
||||||
Fl::scheme( "plastic" );
|
Fl::scheme( "plastic" );
|
||||||
|
|
||||||
|
Fl::lock();
|
||||||
|
|
||||||
Plugin_Module::spawn_discover_thread();
|
Plugin_Module::spawn_discover_thread();
|
||||||
|
|
||||||
Fl_Double_Window *main_window;
|
Fl_Double_Window *main_window;
|
||||||
|
@ -142,6 +179,10 @@ main ( int argc, char **argv )
|
||||||
|
|
||||||
const char *osc_port = NULL;
|
const char *osc_port = NULL;
|
||||||
|
|
||||||
|
nsm = new NSM_Client;
|
||||||
|
|
||||||
|
instance_name = strdup( APP_NAME );
|
||||||
|
|
||||||
{
|
{
|
||||||
int r = argc - 1;
|
int r = argc - 1;
|
||||||
int i = 1;
|
int i = 1;
|
||||||
|
@ -152,7 +193,7 @@ main ( int argc, char **argv )
|
||||||
if ( r > 1 )
|
if ( r > 1 )
|
||||||
{
|
{
|
||||||
MESSAGE( "Using instance name \"%s\"", argv[i+1] );
|
MESSAGE( "Using instance name \"%s\"", argv[i+1] );
|
||||||
instance_name = argv[i+1];
|
instance_name = strdup( argv[i+1] );
|
||||||
--r;
|
--r;
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
@ -185,17 +226,33 @@ main ( int argc, char **argv )
|
||||||
|
|
||||||
mixer->init_osc( osc_port );
|
mixer->init_osc( osc_port );
|
||||||
|
|
||||||
if ( r >= 1 )
|
char *nsm_url = getenv( "NSM_URL" );
|
||||||
|
|
||||||
|
if ( nsm_url )
|
||||||
{
|
{
|
||||||
MESSAGE( "Loading \"%s\"", argv[i] );
|
if ( ! nsm->init() )
|
||||||
|
|
||||||
if ( ! mixer->command_load( argv[i] ) )
|
|
||||||
{
|
{
|
||||||
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();
|
Fl::run();
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace JACK
|
||||||
jack_client_close( _client );
|
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 */
|
* an inheriting class' destructor */
|
||||||
void
|
void
|
||||||
Client::deactivate ( )
|
Client::deactivate ( )
|
||||||
|
@ -201,6 +201,15 @@ namespace JACK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Client::close ( void )
|
||||||
|
{
|
||||||
|
jack_deactivate( _client );
|
||||||
|
jack_client_close( _client );
|
||||||
|
|
||||||
|
_client = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
Client::name ( const char *s )
|
Client::name ( const char *s )
|
||||||
{
|
{
|
||||||
|
@ -222,4 +231,28 @@ namespace JACK
|
||||||
|
|
||||||
return s;
|
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 * init ( const char *client_name, unsigned int opts = 0 );
|
||||||
const char * name ( const char * );
|
const char * name ( const char * );
|
||||||
|
|
||||||
|
void close ( void );
|
||||||
nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); }
|
nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); }
|
||||||
float frame_rate ( void ) const { return jack_get_sample_rate( _client ); }
|
float frame_rate ( void ) const { return jack_get_sample_rate( _client ); }
|
||||||
static nframes_t sample_rate ( void ) { return _sample_rate; }
|
static nframes_t sample_rate ( void ) { return _sample_rate; }
|
||||||
|
@ -98,6 +99,13 @@ namespace JACK
|
||||||
bool zombified ( void ) const { return _zombified; }
|
bool zombified ( void ) const { return _zombified; }
|
||||||
float cpu_load ( void ) const { return jack_cpu_load( _client ); }
|
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(); }
|
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 <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include "Endpoint.H"
|
#include "Endpoint.H"
|
||||||
|
|
||||||
|
#include "Thread.H"
|
||||||
|
|
||||||
namespace OSC
|
namespace OSC
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Endpoint::error_handler(int num, const char *msg, const char *path)
|
Endpoint::error_handler(int num, const char *msg, const char *path)
|
||||||
{
|
{
|
||||||
WARNING( "LibLO server error %d in path %s: %s\n", num, path, msg);
|
WARNING( "LibLO server error %d in path %s: %s\n", num, path, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint::Endpoint ( const char *port )
|
Endpoint::Endpoint ( )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
Endpoint::init ( const char *port )
|
||||||
{
|
{
|
||||||
DMESSAGE( "Creating OSC server" );
|
DMESSAGE( "Creating OSC server" );
|
||||||
|
|
||||||
// _st = lo_server_thread_new( s, error_handler );
|
|
||||||
// _server = lo_server_thread_get_server( _st );
|
|
||||||
|
|
||||||
_server = lo_server_new( port, error_handler );
|
_server = lo_server_new( port, error_handler );
|
||||||
|
|
||||||
if ( ! _server )
|
if ( ! _server )
|
||||||
FATAL( "Error creating OSC server" );
|
{
|
||||||
|
WARNING( "Error creating OSC server" );
|
||||||
char *url = lo_server_get_url(_server);
|
return -1;
|
||||||
printf("OSC: %s\n",url);
|
}
|
||||||
free(url);
|
|
||||||
|
|
||||||
|
// 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, "" );
|
add_method( NULL, "", &Endpoint::osc_generic, this, "" );
|
||||||
|
|
||||||
// _path_names = new std::list<const char*>();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Endpoint::~Endpoint ( )
|
Endpoint::~Endpoint ( )
|
||||||
{
|
{
|
||||||
// lo_server_thread_free( _st );
|
// lo_server_thread_free( _st );
|
||||||
lo_server_free( _server );
|
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
|
int
|
||||||
Endpoint::osc_generic ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
|
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 ] != '/' )
|
if ( path[ strlen(path) - 1 ] != '/' )
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -87,16 +156,18 @@ namespace OSC
|
||||||
char *r = (char*)malloc( 1024 );
|
char *r = (char*)malloc( 1024 );
|
||||||
r[0] = 0;
|
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;
|
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" );
|
strcat( r, "\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,67 +175,139 @@ namespace OSC
|
||||||
return r;
|
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 )
|
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 );
|
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
|
void
|
||||||
Endpoint::del_method ( const char *path, const char *typespec )
|
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 );
|
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;
|
continue;
|
||||||
|
|
||||||
if ( ! strncmp( path, *i, index( *i, ' ' ) - *i ) )
|
if ( ! strcmp( path, (*i)->path ) &&
|
||||||
|
! strcmp( typespec, (*i)->typespec ) )
|
||||||
{
|
{
|
||||||
free( *i );
|
delete *i;
|
||||||
i = _path_names.erase( i );
|
i = _methods.erase( i );
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* void * */
|
void
|
||||||
/* Endpoint::osc_thread ( void * arg ) */
|
Endpoint::del_method ( const method_handle mh )
|
||||||
/* { */
|
{
|
||||||
/* ((Endpoint*)arg)->osc_thread(); */
|
// DMESSAGE( "Deleted OSC method %s (%s)", path, typespec );
|
||||||
|
|
||||||
/* return NULL; */
|
Method_Data *meth = const_cast<Method_Data*>( (const Method_Data*)mh );
|
||||||
/* } */
|
|
||||||
|
|
||||||
/* void */
|
lo_server_del_method( _server, meth->path, meth->typespec );
|
||||||
/* Endpoint::osc_thread ( void ) */
|
|
||||||
/* { */
|
|
||||||
/* _thread.name( "OSC" ); */
|
|
||||||
|
|
||||||
/* DMESSAGE( "OSC Thread running" ); */
|
delete meth;
|
||||||
|
|
||||||
/* for ( ;; ) */
|
_methods.remove( meth );
|
||||||
/* { */
|
|
||||||
/* lo_server_recv( _sever ); */
|
|
||||||
/* } */
|
/* 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 ) ) */
|
||||||
|
/* { */
|
||||||
|
/* 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
|
void
|
||||||
Endpoint::start ( void )
|
Endpoint::start ( void )
|
||||||
{
|
{
|
||||||
|
|
||||||
/* if ( !_thread.clone( &Endpoint::osc_thread, this ) ) */
|
if ( !_thread.clone( &Endpoint::osc_thread, this ) )
|
||||||
/* FATAL( "Could not create OSC thread" ); */
|
FATAL( "Could not create OSC thread" );
|
||||||
|
|
||||||
/* lo_server_thread_start( _st ); */
|
/* lo_server_thread_start( _st ); */
|
||||||
|
|
||||||
|
@ -173,6 +316,7 @@ namespace OSC
|
||||||
void
|
void
|
||||||
Endpoint::stop ( void )
|
Endpoint::stop ( void )
|
||||||
{
|
{
|
||||||
|
_thread.join();
|
||||||
// lo_server_thread_stop( _st );
|
// lo_server_thread_stop( _st );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,9 +371,11 @@ namespace OSC
|
||||||
switch ( ov->type() )
|
switch ( ov->type() )
|
||||||
{
|
{
|
||||||
case 'f':
|
case 'f':
|
||||||
|
DMESSAGE( "Adding float %f", ((OSC_Float*)ov)->value() );
|
||||||
lo_message_add_float( m, ((OSC_Float*)ov)->value() );
|
lo_message_add_float( m, ((OSC_Float*)ov)->value() );
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
|
DMESSAGE( "Adding int %i", ((OSC_Int*)ov)->value() );
|
||||||
lo_message_add_int32( m, ((OSC_Int*)ov)->value() );
|
lo_message_add_int32( m, ((OSC_Int*)ov)->value() );
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
|
@ -287,10 +433,83 @@ namespace OSC
|
||||||
return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "s", v );
|
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
|
int
|
||||||
Endpoint::send ( lo_address to, const char *path, const char * v1, const char *v2 )
|
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 );
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <lo/lo.h>
|
#include <lo/lo.h>
|
||||||
//#include "util/Thread.H"
|
#include "Thread.H"
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
namespace OSC
|
namespace OSC
|
||||||
{
|
{
|
||||||
|
typedef void * method_handle;
|
||||||
|
|
||||||
class OSC_Value
|
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_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;
|
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:
|
public:
|
||||||
|
|
||||||
Endpoint ( const char *port = 0 );
|
int init ( const char *port = 0 );
|
||||||
|
Endpoint ( );
|
||||||
|
|
||||||
~Endpoint ( );
|
~Endpoint ( );
|
||||||
|
|
||||||
char *get_paths ( const char *prefix );
|
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 char *path, const char *typespec );
|
||||||
|
void del_method ( const method_handle );
|
||||||
void start ( void );
|
void start ( void );
|
||||||
void stop ( void );
|
void stop ( void );
|
||||||
int port ( void ) const;
|
int port ( void ) const;
|
||||||
char * url ( 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 check ( void ) const;
|
||||||
void wait ( int timeout ) 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, double v );
|
||||||
int send ( lo_address to, const char *path, int 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, 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 *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 );
|
||||||
|
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 */
|
/* helper macros for defining OSC handlers */
|
||||||
#define OSC_NAME( name ) osc_ ## name
|
#define OSC_NAME( name ) osc_ ## name
|
||||||
#define OSC_DMSG() DMESSAGE( "Got OSC message: %s", path );
|
#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_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_OK() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), 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( value ) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, value )
|
||||||
#define OSC_REPLY_ERR() ((Mixer*)user_data)->osc_endpoint->send( lo_message_get_source( msg ), "/reply", path, "err" )
|
#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; -*-
|
# -*- 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_SRCS:=$(sort $(nonlib_SRCS))
|
||||||
nonlib_OBJS:=$(nonlib_SRCS:.C=.o)
|
nonlib_OBJS:=$(nonlib_SRCS:.C=.o)
|
||||||
|
|
|
@ -282,18 +282,18 @@ require_package ()
|
||||||
|
|
||||||
_test_version ()
|
_test_version ()
|
||||||
{
|
{
|
||||||
if [ $# == 6 ]
|
if [ $# = 6 ]
|
||||||
then
|
then
|
||||||
[ $1 -gt $4 ] && return 0
|
[ $1 -gt $4 ] && return 0
|
||||||
[ $1 -eq $4 ] && [ $2 -gt $5 ] && 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 -gt $6 ] && return 0
|
||||||
[ $1 -eq $4 ] && [ $2 -eq $5 ] && [ $3 -eq $6 ] && return 0
|
[ $1 -eq $4 ] && [ $2 -eq $5 ] && [ $3 -eq $6 ] && return 0
|
||||||
return 1
|
return 1
|
||||||
elif [ $# == 4 ]
|
elif [ $# = 4 ]
|
||||||
then
|
then
|
||||||
[ $1 -gt $3 ] && return 0
|
[ $1 -gt $3 ] && return 0
|
||||||
[ $1 -eq $3 ] && [ $2 -eq $4 ] && return 0
|
[ $1 -eq $3 ] && [ $2 -eq $4 ] && return 0
|
||||||
return 1;
|
return 1
|
||||||
fi
|
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
|
File diff suppressed because it is too large
Load Diff
|
@ -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_command makedepend makedepend
|
||||||
require_package JACK 0.103.0 jack
|
require_package JACK 0.103.0 jack
|
||||||
require_package sndfile 1.0.17 sndfile
|
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
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ Timeline_SRCS:=$(Timeline_SRCS:.fl=.C)
|
||||||
Timeline_SRCS:=$(sort $(Timeline_SRCS))
|
Timeline_SRCS:=$(sort $(Timeline_SRCS))
|
||||||
Timeline_OBJS:=$(Timeline_SRCS:.C=.o)
|
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
|
src/timeline: $(Timeline_OBJS) FL/libfl_widgets.a nonlib/libnonlib.a
|
||||||
@ echo -n Linking timeline...
|
@ echo -n Linking timeline...
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
Engine::Engine ( ) : _thread( "RT" )
|
Engine::Engine ( ) : _thread( "RT" )
|
||||||
{
|
{
|
||||||
_buffers_dropped = 0;
|
_buffers_dropped = 0;
|
||||||
|
|
||||||
|
DMESSAGE( "Creating audio I/O engine" );
|
||||||
}
|
}
|
||||||
|
|
||||||
Engine::~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 "file.h"
|
||||||
#include "Block_Timer.H"
|
#include "Block_Timer.H"
|
||||||
|
|
||||||
|
#include "Transport.H"
|
||||||
|
|
||||||
|
extern Transport *transport;
|
||||||
extern TLE *tle;
|
extern TLE *tle;
|
||||||
|
|
||||||
const int PROJECT_VERSION = 1;
|
const int PROJECT_VERSION = 1;
|
||||||
|
|
||||||
|
extern char *instance_name;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const char *Project::_errstr[] =
|
const char *Project::_errstr[] =
|
||||||
|
@ -200,6 +205,9 @@ Project::close ( void )
|
||||||
|
|
||||||
release_lock( &_lockfd, ".lock" );
|
release_lock( &_lockfd, ".lock" );
|
||||||
|
|
||||||
|
delete engine;
|
||||||
|
engine = NULL;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +241,25 @@ Project::validate ( const char *name )
|
||||||
return r;
|
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
|
/** Try to open project /name/. Returns 0 if sucsessful, an error code
|
||||||
* otherwise */
|
* otherwise */
|
||||||
int
|
int
|
||||||
|
@ -262,13 +289,19 @@ Project::open ( const char *name )
|
||||||
if ( version != PROJECT_VERSION )
|
if ( version != PROJECT_VERSION )
|
||||||
return E_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" );
|
Block_Timer timer( "Replayed journal" );
|
||||||
if ( ! Loggable::open( "history" ) )
|
if ( ! Loggable::open( "history" ) )
|
||||||
return E_INVALID;
|
return E_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeline->sample_rate( rate );
|
/* /\* really a good idea? *\/ */
|
||||||
|
/* timeline->sample_rate( rate ); */
|
||||||
|
|
||||||
if ( creation_date )
|
if ( creation_date )
|
||||||
{
|
{
|
||||||
|
@ -319,6 +352,9 @@ Project::create ( const char *name, const char *template_name )
|
||||||
mkdir( "sources", 0777 );
|
mkdir( "sources", 0777 );
|
||||||
creat( "history", 0666 );
|
creat( "history", 0666 );
|
||||||
|
|
||||||
|
if ( ! engine )
|
||||||
|
make_engine();
|
||||||
|
|
||||||
/* TODO: copy template */
|
/* TODO: copy template */
|
||||||
|
|
||||||
write_info();
|
write_info();
|
||||||
|
|
|
@ -33,9 +33,9 @@ class Project
|
||||||
|
|
||||||
static bool write_info ( void );
|
static bool write_info ( void );
|
||||||
static bool read_info ( int *version, nframes_t *sample_rate, char **creation_date, char **created_by );
|
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 const char *_errstr[];
|
||||||
|
|
||||||
|
static void make_engine ( void );
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum
|
enum
|
||||||
|
@ -47,6 +47,8 @@ public:
|
||||||
E_VERSION = -5
|
E_VERSION = -5
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void set_name ( const char *name );
|
||||||
|
|
||||||
static const char *errstr ( int n ) { return _errstr[ ( 0 - n ) - 1 ]; }
|
static const char *errstr ( int n ) { return _errstr[ ( 0 - n ) - 1 ]; }
|
||||||
|
|
||||||
static const char *name ( void ) { return Project::_name; }
|
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 {extern char project_display_name[256];} {global
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decl {\#include "NSM.H"} {}
|
||||||
|
|
||||||
|
decl {extern NSM_Client *nsm;} {global
|
||||||
|
}
|
||||||
|
|
||||||
decl {extern char *user_config_dir;} {global
|
decl {extern char *user_config_dir;} {global
|
||||||
}
|
}
|
||||||
|
|
||||||
class TLE {open
|
class TLE {open
|
||||||
} {
|
} {
|
||||||
decl {Fl_Color system_colors[3];} {}
|
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
|
Function {save()} {open
|
||||||
} {
|
} {
|
||||||
code {const char options_filename[] = "options";
|
code {
|
||||||
// const char state_filename[] = "state";
|
timeline->command_save();} {}
|
||||||
|
|
||||||
// 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 {quit()} {} {
|
Function {quit()} {} {
|
||||||
code {Project::close();
|
code {
|
||||||
|
timeline->command_quit();} {}
|
||||||
save();
|
|
||||||
|
|
||||||
while ( Fl::first_window() ) Fl::first_window()->hide();} {}
|
|
||||||
}
|
}
|
||||||
Function {open( const char *name )} {} {
|
Function {open( const char *name )} {} {
|
||||||
code {if ( ! name )
|
code {
|
||||||
return;
|
timeline->command_load( name, NULL );} {}
|
||||||
|
|
||||||
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 );
|
|
||||||
}} {}
|
|
||||||
}
|
}
|
||||||
Function {save_timeline_settings()} {open
|
Function {save_timeline_settings()} {open
|
||||||
} {
|
} {
|
||||||
|
@ -219,7 +220,7 @@ Loggable::progress_callback( &TLE::progress_cb, this );} {}
|
||||||
Fl_Window main_window {
|
Fl_Window main_window {
|
||||||
label {Non DAW : Timeline}
|
label {Non DAW : Timeline}
|
||||||
callback {if ( Fl::event_key() != FL_Escape )
|
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
|
private xywh {705 125 1025 770} type Double resizable xclass Non_DAW visible
|
||||||
} {
|
} {
|
||||||
Fl_Menu_Bar menubar {open
|
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( capture_buffer_progress, cbp, timeline->total_input_buffer_percent() );
|
||||||
update_progress( playback_buffer_progress, pbp, timeline->total_output_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( "." ) );
|
update_progress( disk_usage_progress, dup, percent_used( "." ) );
|
||||||
|
|
||||||
|
@ -820,15 +821,22 @@ if ( timeline->total_playback_xruns() )
|
||||||
|
|
||||||
static char stats[100];
|
static char stats[100];
|
||||||
|
|
||||||
|
if ( engine && ! engine->zombified() )
|
||||||
|
{
|
||||||
snprintf( stats, sizeof( stats ), "latency: %.1fms, xruns: %d",
|
snprintf( stats, sizeof( stats ), "latency: %.1fms, xruns: %d",
|
||||||
engine->frames_to_milliseconds( timeline->total_output_latency() ),
|
engine->frames_to_milliseconds( timeline->total_output_latency() ),
|
||||||
engine->xruns() );
|
engine->xruns() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf( stats, sizeof( stats ), "%s", "DISCONNECTED" );
|
||||||
|
}
|
||||||
|
|
||||||
stats_box->label( stats );
|
stats_box->label( stats );
|
||||||
|
|
||||||
static bool zombie = false;
|
static bool zombie = false;
|
||||||
|
|
||||||
if ( engine->zombified() && ! zombie )
|
if ( engine && engine->zombified() && ! zombie )
|
||||||
{
|
{
|
||||||
zombie = true;
|
zombie = true;
|
||||||
fl_alert( "Disconnected from JACK!" );
|
fl_alert( "Disconnected from JACK!" );
|
||||||
|
@ -840,6 +848,13 @@ sm_blinker->value( timeline->session_manager_name() != NULL );
|
||||||
sm_blinker->tooltip( timeline->session_manager_name() );
|
sm_blinker->tooltip( timeline->session_manager_name() );
|
||||||
selected_blinker->value( timeline->nselected() );
|
selected_blinker->value( timeline->nselected() );
|
||||||
seek_blinker->value( timeline->seek_pending() );
|
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();} {}
|
project_name->redraw();} {}
|
||||||
}
|
}
|
||||||
Function {update_cb( void *v )} {open private return_type {static void}
|
Function {update_cb( void *v )} {open private return_type {static void}
|
||||||
|
@ -877,6 +892,7 @@ else if ( 0 == p )
|
||||||
|
|
||||||
static char pat[10];
|
static char pat[10];
|
||||||
|
|
||||||
|
nsm->progress( p / 100.0f );
|
||||||
update_progress( progress, pat, p );
|
update_progress( progress, pat, p );
|
||||||
|
|
||||||
progress->redraw();
|
progress->redraw();
|
||||||
|
|
|
@ -46,6 +46,14 @@
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
#include "debug.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
|
#ifdef USE_WIDGET_FOR_TIMELINE
|
||||||
#define BASE Fl_Group
|
#define BASE Fl_Group
|
||||||
#define redraw_overlay()
|
#define redraw_overlay()
|
||||||
|
@ -74,6 +82,9 @@ bool Timeline::center_playhead = true;
|
||||||
|
|
||||||
const float UPDATE_FREQ = 0.02f;
|
const float UPDATE_FREQ = 0.02f;
|
||||||
|
|
||||||
|
extern const char *instance_name;
|
||||||
|
extern TLE *tle;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** return the combined height of all visible children of (veritcal)
|
/** 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? */
|
/* FIXME: why is this necessary? doesn't the above add do DAMAGE_CHILD? */
|
||||||
redraw();
|
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 <assert.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
|
#include "OSC/Endpoint.H"
|
||||||
|
|
||||||
class Fl_Scroll;
|
class Fl_Scroll;
|
||||||
class Fl_Pack;
|
class Fl_Pack;
|
||||||
class Fl_Scrollbar;
|
class Fl_Scrollbar;
|
||||||
|
@ -149,7 +151,7 @@ public:
|
||||||
|
|
||||||
void update_tempomap ( void );
|
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; }
|
nframes_t fpp ( void ) const { return 1 << _fpp; }
|
||||||
void range ( nframes_t start, nframes_t length );
|
void range ( nframes_t start, nframes_t length );
|
||||||
|
@ -221,6 +223,11 @@ public:
|
||||||
void wait_for_buffers ( void );
|
void wait_for_buffers ( void );
|
||||||
bool seek_pending ( 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:
|
private:
|
||||||
|
|
||||||
static void snapshot ( void *v ) { ((Timeline*)v)->snapshot(); }
|
static void snapshot ( void *v ) { ((Timeline*)v)->snapshot(); }
|
||||||
|
|
|
@ -23,10 +23,6 @@
|
||||||
|
|
||||||
#include "Engine/Engine.H"
|
#include "Engine/Engine.H"
|
||||||
|
|
||||||
// Transport transport;
|
|
||||||
|
|
||||||
#define client engine->jack_client()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Transport::Transport ( int X, int Y, int W, int H, const char *L )
|
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;
|
rolling = false;
|
||||||
_stop_disables_record = true;
|
_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;
|
const int bw = W / 3;
|
||||||
|
|
||||||
type( HORIZONTAL );
|
type( HORIZONTAL );
|
||||||
|
@ -156,9 +164,10 @@ Transport::handle ( int m )
|
||||||
void
|
void
|
||||||
Transport::poll ( void )
|
Transport::poll ( void )
|
||||||
{
|
{
|
||||||
|
|
||||||
jack_transport_state_t ts;
|
jack_transport_state_t ts;
|
||||||
|
|
||||||
ts = jack_transport_query( client, this );
|
ts = engine->transport_query( this );
|
||||||
|
|
||||||
rolling = ts == JackTransportRolling;
|
rolling = ts == JackTransportRolling;
|
||||||
}
|
}
|
||||||
|
@ -166,9 +175,12 @@ Transport::poll ( void )
|
||||||
void
|
void
|
||||||
Transport::locate ( nframes_t frame )
|
Transport::locate ( nframes_t frame )
|
||||||
{
|
{
|
||||||
|
if ( ! engine )
|
||||||
|
return;
|
||||||
|
|
||||||
if ( ! recording )
|
if ( ! recording )
|
||||||
// don't allow seeking while record is in progress
|
// 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();
|
update_record_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
jack_transport_start( client );
|
if ( engine )
|
||||||
|
engine->transport_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -197,7 +210,8 @@ Transport::stop ( void )
|
||||||
update_record_state();
|
update_record_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
jack_transport_stop( client );
|
if ( engine )
|
||||||
|
engine->transport_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
#include "Track.H"
|
#include "Track.H"
|
||||||
|
|
||||||
#include "TLE.H"
|
#include "TLE.H"
|
||||||
|
#include "Timeline.H"
|
||||||
#include "../FL/Boxtypes.H"
|
#include "../FL/Boxtypes.H"
|
||||||
|
|
||||||
#include "Project.H"
|
#include "Project.H"
|
||||||
|
@ -48,10 +48,15 @@
|
||||||
|
|
||||||
#include "Thread.H"
|
#include "Thread.H"
|
||||||
|
|
||||||
|
#include "NSM.H"
|
||||||
|
|
||||||
Engine *engine;
|
Engine *engine;
|
||||||
Timeline *timeline;
|
Timeline *timeline;
|
||||||
Transport *transport;
|
Transport *transport;
|
||||||
TLE *tle;
|
TLE *tle;
|
||||||
|
NSM_Client *nsm;
|
||||||
|
|
||||||
|
char *instance_name = NULL;
|
||||||
|
|
||||||
/* TODO: put these in a header */
|
/* TODO: put these in a header */
|
||||||
#define USER_CONFIG_DIR ".non-daw/"
|
#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 APP_TITLE[] = "The Non-DAW";
|
||||||
const char COPYRIGHT[] = "Copyright (C) 2008-2010 Jonathan Moore Liles";
|
const char COPYRIGHT[] = "Copyright (C) 2008-2010 Jonathan Moore Liles";
|
||||||
|
|
||||||
|
#define OSC_INTERVAL 0.2f
|
||||||
|
|
||||||
#define PACKAGE "non"
|
#define PACKAGE "non"
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,6 +103,34 @@ shift ( char **argv, int *argc, int n )
|
||||||
argc -= 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
|
int
|
||||||
main ( int argc, char **argv )
|
main ( int argc, char **argv )
|
||||||
{
|
{
|
||||||
|
@ -104,6 +139,8 @@ main ( int argc, char **argv )
|
||||||
Thread thread( "UI" );
|
Thread thread( "UI" );
|
||||||
thread.set();
|
thread.set();
|
||||||
|
|
||||||
|
signal( SIGTERM, sigterm_handler );
|
||||||
|
|
||||||
fl_register_images();
|
fl_register_images();
|
||||||
|
|
||||||
/* welcome to C++ */
|
/* welcome to C++ */
|
||||||
|
@ -129,32 +166,85 @@ main ( int argc, char **argv )
|
||||||
|
|
||||||
tle = new TLE;
|
tle = new TLE;
|
||||||
|
|
||||||
MESSAGE( "Initializing JACK" );
|
instance_name = strdup( APP_NAME );
|
||||||
|
|
||||||
/* we don't really need a pointer for this */
|
/* we don't really need a pointer for this */
|
||||||
engine = new Engine;
|
// will be created on project new/open
|
||||||
|
engine = NULL;
|
||||||
|
|
||||||
const char *jack_name;
|
nsm = new NSM_Client;
|
||||||
|
|
||||||
if ( ! ( jack_name = engine->init( APP_NAME, JACK::Client::SLOW_SYNC | JACK::Client::TIMEBASE_MASTER ) ) )
|
const char *osc_port = NULL;
|
||||||
FATAL( "Could not connect to JACK!" );
|
|
||||||
|
|
||||||
timeline->sample_rate( engine->sample_rate() );
|
{
|
||||||
|
int r = argc - 1;
|
||||||
|
int i = 1;
|
||||||
|
for ( ; i < argc; ++i, --r )
|
||||||
|
|
||||||
/* always start stopped (please imagine for me a realistic
|
if ( !strcmp( argv[i], "--osc-port" ) )
|
||||||
* scenario requiring otherwise */
|
{
|
||||||
transport->stop();
|
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" );
|
MESSAGE( "Starting GUI" );
|
||||||
|
|
||||||
tle->run();
|
tle->run();
|
||||||
|
|
||||||
if ( argc > 1 )
|
char *nsm_url = getenv( "NSM_URL" );
|
||||||
tle->open( argv[ 1 ] );
|
|
||||||
|
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();
|
Fl::run();
|
||||||
|
|
||||||
|
if ( engine )
|
||||||
|
{
|
||||||
|
delete engine;
|
||||||
|
engine = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
delete engine;
|
delete tle;
|
||||||
|
tle = NULL;
|
||||||
|
|
||||||
|
delete nsm;
|
||||||
|
nsm = NULL;
|
||||||
|
|
||||||
MESSAGE( "Your fun is over" );
|
MESSAGE( "Your fun is over" );
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue