Implement the Non-Session-Manager (NSM)

This commit is contained in:
Jonathan Moore Liles 2010-01-20 16:33:02 -08:00
parent cff2c15583
commit 4a22d675e2
43 changed files with 5256 additions and 214 deletions

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@
const float METER_UPDATE_FREQ = 0.1f; const float METER_UPDATE_FREQ = 0.2f;

View File

@ -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);
const char *s = "APP_TITLE\n"; lo_address reply = lo_address_new_from_url( &argv[0]->s );
/* if ( 1 >= lo_send_from( src, ((Mixer*)user_data)->osc_endpoint, LO_TT_IMMEDIATE, "/finger-reply", "s", s ) ) */ ep->send( reply,
/* { */ "/reply",
/* DMESSAGE( "Failed to send reply" ); */ path,
/* } */ ep->url(),
APP_NAME,
VERSION,
instance_name );
lo_address_free( 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;
osc_endpoint->add_method( "/nsm/quit", "", OSC_NAME( quit ), this, "" ); printf( "OSC=%s\n", osc_endpoint->url() );
osc_endpoint->add_method( "/nsm/load", "ss", OSC_NAME( load ), this, "path,display_name" );
osc_endpoint->add_method( "/nsm/save", "", OSC_NAME( save ), this, "" ); osc_endpoint->add_method( "/non/finger", "s", OSC_NAME( finger ), osc_endpoint, "" );
osc_endpoint->add_method( "/nsm/new", "ss", OSC_NAME( new ), this, "path,display_name" ); osc_endpoint->add_method( "/non/mixer/add_strip", "", OSC_NAME( add_strip ), osc_endpoint, "" );
// 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;
} }

View File

@ -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,6 +38,7 @@ class Mixer : public Fl_Group
public: public:
OSC::Endpoint *osc_endpoint; OSC::Endpoint *osc_endpoint;
Fl_Blinker *sm_blinker;
private: private:
@ -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:

View File

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

99
mixer/src/NSM.C Normal file
View File

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

38
mixer/src/NSM.H Normal file
View File

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

View File

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

View File

@ -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,6 +226,17 @@ main ( int argc, char **argv )
mixer->init_osc( osc_port ); mixer->init_osc( osc_port );
char *nsm_url = getenv( "NSM_URL" );
if ( nsm_url )
{
if ( ! nsm->init() )
{
nsm->announce( nsm_url, APP_NAME, ":switch:dirty:", argv[0] );
}
}
else
{
if ( r >= 1 ) if ( r >= 1 )
{ {
MESSAGE( "Loading \"%s\"", argv[i] ); MESSAGE( "Loading \"%s\"", argv[i] );
@ -194,8 +246,13 @@ main ( int argc, char **argv )
fl_alert( "Error opening project specified on commandline" ); 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();

View File

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

View File

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

264
nonlib/NSM/Client.C Normal file
View File

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

101
nonlib/NSM/Client.H Normal file
View File

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

View File

@ -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" );
} }
} }
@ -105,66 +176,138 @@ namespace OSC
} }
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 )
{
// DMESSAGE( "Deleted OSC method %s (%s)", path, typespec );
Method_Data *meth = const_cast<Method_Data*>( (const Method_Data*)mh );
lo_server_del_method( _server, meth->path, meth->typespec );
delete meth;
_methods.remove( meth );
/* for ( std::list<Method_Data *>::iterator i = _methods.begin(); i != _methods.end(); ++i ) */
/* { */ /* { */
/* ((Endpoint*)arg)->osc_thread(); */ /* if ( ! (*i)->path ) */
/* continue; */
/* return NULL; */ /* if ( ! strcmp( path, (*i)->path ) && */
/* } */ /* ! strcmp( typespec, (*i)->typespec ) ) */
/* void */
/* Endpoint::osc_thread ( void ) */
/* { */ /* { */
/* _thread.name( "OSC" ); */ /* delete *i; */
/* i = _methods.erase( i ); */
/* DMESSAGE( "OSC Thread running" ); */ /* break; */
/* for ( ;; ) */
/* { */
/* lo_server_recv( _sever ); */
/* } */ /* } */
/* } */ /* } */
}
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 );
}
} }

View File

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

View File

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

View File

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

1
session/FL Symbolic link
View File

@ -0,0 +1 @@
../FL

137
session/Makefile Normal file
View File

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

27
session/configure vendored Executable file
View File

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

492
session/doc/NSM.mu Normal file
View File

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

449
session/doc/mup.css Normal file
View File

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

28
session/makefile.inc Normal file
View File

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

1
session/nonlib Symbolic link
View File

@ -0,0 +1 @@
../nonlib/

1
session/scripts Symbolic link
View File

@ -0,0 +1 @@
../scripts

1
session/src/FL Symbolic link
View File

@ -0,0 +1 @@
../../FL

1756
session/src/nsmd.C Normal file

File diff suppressed because it is too large Load Diff

112
session/src/send_osc.C Normal file
View File

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

View File

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

3
timeline/configure vendored
View File

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

View File

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

View File

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

83
timeline/src/NSM.C Normal file
View File

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

36
timeline/src/NSM.H Normal file
View File

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

View File

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

View File

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

View File

@ -67,15 +67,22 @@ 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()} {open Function {save_options()} {open
} { } {
code {const char options_filename[] = "options"; code {
const char options_filename[] = "options";
// const char state_filename[] = "state"; // const char state_filename[] = "state";
// save options // save options
@ -83,27 +90,21 @@ class TLE {open
char *path; char *path;
asprintf( &path, "%s/%s", user_config_dir, options_filename ); asprintf( &path, "%s/%s", user_config_dir, options_filename );
((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Options" ), path ); ((Fl_Menu_Settings*)menubar)->dump( menubar->find_item( "&Options" ), path );
free( path );} {} free( path );
} {}
}
Function {save()} {open
} {
code {
timeline->command_save();} {}
} }
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();

View File

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

View File

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

View File

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

View File

@ -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 )
if ( !strcmp( argv[i], "--osc-port" ) )
{
if ( r > 1 )
{
MESSAGE( "Using OSC port \"%s\"", argv[i+1] );
osc_port = argv[i+1];
--r;
++i;
}
else
{
FATAL( "Missing OSC port" );
}
}
/* always start stopped (please imagine for me a realistic
* scenario requiring otherwise */
transport->stop();
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; delete engine;
engine = NULL;
}
delete tle;
tle = NULL;
delete nsm;
nsm = NULL;
MESSAGE( "Your fun is over" ); MESSAGE( "Your fun is over" );
} }