diff --git a/Makefile b/Makefile index c6d86df..65c5e7a 100644 --- a/Makefile +++ b/Makefile @@ -57,9 +57,9 @@ CFLAGS+=-DINSTALL_PREFIX=\"$(prefix)\" \ -DDOCUMENT_PATH=\"$(DOCUMENT_PATH)\" \ -DPIXMAP_PATH=\"$(PIXMAP_PATH)\" -CXXFLAGS:=$(CFLAGS) $(CXXFLAGS) $(FLTK_CFLAGS) $(SIGCPP_CFLAGS) $(LASH_CFLAGS) $(XPM_CFLAGS) +CXXFLAGS:=$(CFLAGS) $(CXXFLAGS) $(FLTK_CFLAGS) $(SIGCPP_CFLAGS) $(LIBLO_CFLAGS) $(XPM_CFLAGS) -LIBS:=$(FLTK_LIBS) $(JACK_LIBS) $(LASH_LIBS) $(SIGCPP_LIBS) $(XPM_LIBS) +LIBS:=$(FLTK_LIBS) $(JACK_LIBS) $(SIGCPP_LIBS) $(LIBLO_LIBS) $(XPM_LIBS) ifeq ($(JACK_MIDI_PROTO_API),yes) CXXFLAGS+=-DJACK_MIDI_PROTO_API @@ -68,7 +68,7 @@ endif # uncomment this line to print each playback event to the console (not RT safe) # CXXFLAGS+= -DDEBUG_EVENTS -SRCS:=$(wildcard src/*.C src/gui/*.fl src/gui/*.C) +SRCS:=$(wildcard src/*.C src/gui/*.fl src/gui/*.C src/NSM/*.C) SRCS:=$(SRCS:.fl=.C) SRCS:=$(sort $(SRCS)) diff --git a/configure b/configure index a165ebc..af47c4b 100755 --- a/configure +++ b/configure @@ -10,7 +10,6 @@ begin begin_options ask "Installation prefix" prefix /usr/local -ask "Use the LASH Audio Session Handler" USE_LASH yes ask "Build for debugging" USE_DEBUG no begin_tests @@ -20,9 +19,10 @@ require_command FLUID fluid require_package JACK 0.103.0 jack suggest_package XPM 2.0.0 xpm test_version `version_of jack` 0.105.0 || append "JACK_MIDI_PROTO_API=yes" +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" require_package sigcpp 2.0.0 sigc++-2.0 -using LASH && require_package LASH 0.5.4 lash-1.0 - end diff --git a/src/NSM.C b/src/NSM.C new file mode 100644 index 0000000..b79e84e --- /dev/null +++ b/src/NSM.C @@ -0,0 +1,142 @@ + +/*******************************************************************************/ +/* 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 "NSM.H" + + +#include +#include +#include +#include + +#include "common.h" +#include "config.h" +#include "non.H" +#include "jack.H" +#include "transport.H" + +#include "gui/ui.H" + +#define OSC_INTERVAL 0.2f + +extern Transport transport; +extern char *instance_name; + +extern NSM_Client *nsm; + +extern UI *ui; + +NSM_Client::NSM_Client ( ) +{ + project_filename = 0; +} + +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 ( transport.rolling ) + { + *out_msg = strdup( "Cannot save while transport is running." ); + return ERR_NOT_NOW; + } + else + { + save_song( nsm->project_filename ); + return ERR_OK; + } +} + +int +NSM_Client::command_open ( const char *name, const char *display_name, const char *client_id, char **out_msg ) +{ + if ( transport.rolling ) + { + *out_msg = strdup( "Cannot open while transport is running." ); + + return ERR_NOT_NOW; + } + + if ( song.dirty() ) + { + *out_msg = strdup( "Song has unsaved changes!" ); + + return ERR_UNSAVED_CHANGES; + } + + if ( instance_name ) + free( instance_name ); + + instance_name = strdup( client_id ); + + if ( ! midi_is_active() ) + { + setup_jack(); + } + else + { + midi_all_sound_off(); + midi_shutdown(); + setup_jack(); + } + + char *new_filename; + + asprintf( &new_filename, "%s.non", name ); + + struct stat st; + + if ( 0 == stat( new_filename, &st ) ) + { + if ( ! load_song( new_filename ) ) + { + *out_msg = strdup( "Could not open file" ); + return ERR_GENERAL; + } + } + else + { + save_song( new_filename ); + } + + if ( nsm->project_filename ) + free( nsm->project_filename ); + + nsm->project_filename = new_filename; + + return ERR_OK; +} + +void +NSM_Client::command_active ( bool b ) +{ + if ( b ) + { + ui->sm_indicator->activate(); + ui->sm_indicator->tooltip( session_manager_name() ); + } + else + { + ui->sm_indicator->tooltip( NULL ); + ui->sm_indicator->deactivate(); + } +} diff --git a/src/lash.H b/src/NSM.H similarity index 75% rename from src/lash.H rename to src/NSM.H index df8f6ca..0f787d8 100644 --- a/src/lash.H +++ b/src/NSM.H @@ -1,6 +1,6 @@ /*******************************************************************************/ -/* Copyright (C) 2008 Jonathan Moore Liles */ +/* 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 */ @@ -19,22 +19,23 @@ #pragma once -#include "config.h" +#include "NSM/Client.H" -#ifdef HAVE_LASH -#include -#endif - -class Lash +class NSM_Client : public NSM::Client { -#ifdef HAVE_LASH - lash_client_t *_client; -#endif + char *project_filename; public: - Lash ( ); - bool init ( int *argc, char ***argv, const char *jack_name ); - void process ( void ); + 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 ); + int command_quit ( char **out_msg ); + + void command_active ( bool ); }; diff --git a/src/NSM/Client.C b/src/NSM/Client.C new file mode 100644 index 0000000..585d277 --- /dev/null +++ b/src/NSM/Client.C @@ -0,0 +1,298 @@ + +/*******************************************************************************/ +/* 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 +#include +#include +#include + +#pragma GCC diagnostic ignored "-Wunused-parameter" + +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 *application_name, const char *capabilities, const char *process_name ) + { + MESSAGE( "Announcing to NSM" ); + + lo_address to = lo_address_new_from_url( nsm_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::broadcast ( lo_message msg ) + { + if ( nsm_is_active ) + { + lo_send_message_from( nsm_addr, _server, "/nsm/server/broadcast", msg ); + } + } + + void + Client::check ( int timeout ) + { + if ( lo_server_wait( _server, timeout ) ) + while ( 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 ( const char *nsm_url ) + { + this->nsm_url = nsm_url; + + lo_address addr = lo_address_new_from_url( nsm_url ); + int proto = lo_address_get_protocol( addr ); + lo_address_free( addr ); + + _server = lo_server_new_with_proto( NULL, proto, 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 ); + lo_server_add_method( _server, "/nsm/client/session_is_loaded", "", &Client::osc_session_is_loaded, this ); + lo_server_add_method( _server, NULL, NULL, &Client::osc_broadcast, this ); + + return 0; + } + + int + Client::init_thread ( const char *nsm_url ) + { + this->nsm_url = nsm_url; + + lo_address addr = lo_address_new_from_url( nsm_url ); + int proto = lo_address_get_protocol( addr ); + lo_address_free( addr ); + + _st = lo_server_thread_new_with_proto( NULL, proto, 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 ); + lo_server_thread_add_method( _st, "/nsm/client/session_is_loaded", "", &Client::osc_session_is_loaded, this ); + lo_server_thread_add_method( _st, NULL, NULL, &Client::osc_broadcast, this ); + + return 0; + } + +/************************/ +/* OSC Message Handlers */ +/************************/ + + int + Client::osc_broadcast ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) + { + return ((NSM::Client*)user_data)->command_broadcast( path, msg ); + } + + 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; + } +}; diff --git a/src/NSM/Client.H b/src/NSM/Client.H new file mode 100644 index 0000000..f417944 --- /dev/null +++ b/src/NSM/Client.H @@ -0,0 +1,109 @@ + +/*******************************************************************************/ +/* 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 + +namespace NSM +{ + + class Client + { + + private: + + const char *nsm_url; + + 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 *appliction_name, const char *capabilities, const char *process_name ); + + void broadcast ( lo_message msg ); + + /* init without threading */ + int init ( const char *nsm_url ); + /* init with threading */ + int init_thread ( const char *nsm_url ); + + /* call this periodically to check for new messages */ + void check ( int timeout = 0 ); + + /* 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 ) { } + + virtual void command_session_is_loaded ( void ) { } + + /* invoked when an unrecognized message is received. Should return 0 if you handled it, -1 otherwise. */ + virtual int command_broadcast ( const char *, lo_message ) { return -1; } + + 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 ); + static int osc_broadcast ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ); + + }; +}; diff --git a/src/gui/ui.fl b/src/gui/ui.fl index 8fb32ec..5dadb73 100644 --- a/src/gui/ui.fl +++ b/src/gui/ui.fl @@ -40,9 +40,12 @@ decl {\#include "event_edit.H"} {private local decl {\#include "../jack.H"} {private local } -decl {\#include "../lash.H"} {private local +decl {\#include "../NSM.H"} {private local } +decl {extern NSM_Client *nsm;} {private local +} + decl {extern const char *BUILD_ID;} {private local } @@ -73,8 +76,6 @@ Function {update_transport( void * )} {open return_type void handle_midi_input(); -lash.process(); - ui->progress_group->do_callback(); ui->vmetro_widget->update(); @@ -103,6 +104,16 @@ if ( transport.rolling != oldstate ) } } +if ( nsm && nsm->is_active() ) +{ + if ( ui->menu_new->active() ) + { + ui->menu_new->deactivate(); + ui->menu_open->deactivate(); + ui->menu_save_as->deactivate(); + } +} + // JUST A TEST if ( transport.rolling ) { @@ -137,6 +148,8 @@ Fl::scheme( "plastic" ); canvas_background_color = FL_GREEN; +playback_mode_menu = NULL; + main_window = make_main_window(); seq_window = make_seq_window(); @@ -235,7 +248,7 @@ if ( name ) code0 {song.signal_dirty.connect( sigc::mem_fun( o, &Fl_Menu_Item::activate ) );} code1 {song.signal_clean.connect( sigc::mem_fun( o, &Fl_Menu_Item::deactivate ) );} } - MenuItem {} { + MenuItem menu_save_as { label {Save &As} callback {save_dialog( NULL );} xywh {0 0 40 25} @@ -1032,6 +1045,10 @@ else xywh {0 0 40 24} } } + Fl_Box sm_indicator { + label SM selected + xywh {805 6 50 17} box ROUNDED_BOX color 50 labelcolor 3 deactivate + } } Fl_Group {} {open xywh {-1 772 869 33} diff --git a/src/jack.C b/src/jack.C index 2a7454d..5c968ef 100644 --- a/src/jack.C +++ b/src/jack.C @@ -88,10 +88,19 @@ static port_t input[2]; /* contro jack_nframes_t nframes; /* for compatibility with older jack */ +bool +midi_is_active ( void ) +{ + return client != NULL; +} + /** get next recorded event, if any--runs in UI thread */ bool midi_input_event ( int port, midievent *me ) { + if ( ! midi_is_active() ) + return NULL; + if ( jack_ringbuffer_read_space( input[ port ].ring_buf ) >= sizeof( midievent ) ) { if ( jack_ringbuffer_read( input[ port ].ring_buf, (char *)me, sizeof( midievent ) ) ) @@ -100,11 +109,15 @@ midi_input_event ( int port, midievent *me ) return false; } + /** * Queue an event for output. /tick/ is relative to the current cycle! */ void midi_output_event ( int port, const midievent *e ) { + if ( ! midi_is_active() ) + return; + event *fe = freelist.first(); if ( ! fe ) @@ -150,6 +163,9 @@ midi_output_event ( int port, const midievent *e ) void midi_output_event ( int port, const midievent *e, tick_t duration ) { + if ( ! midi_is_active() ) + return; + if ( duration ) { note_duration[ port ][ e->channel() ][ e->note() ] = (duration + e->timestamp()) * subticks_per_tick; @@ -194,6 +210,9 @@ midi_write_event ( int port, const midievent *e ) void midi_output_immediate_event ( int port, const midievent *e ) { + if ( ! midi_is_active() ) + return; + if ( jack_ringbuffer_write( output[ port ].ring_buf, (const char *)e, sizeof( midievent ) ) != sizeof( midievent ) ) WARNING( "output ringbuffer overrun" ); else @@ -208,6 +227,9 @@ midi_output_immediate_event ( int port, const midievent *e ) void midi_all_sound_off ( void ) { + if ( ! midi_is_active() ) + return; + MESSAGE( "stopping all sound" ); midievent e; @@ -525,14 +547,11 @@ schedule: } const char * -midi_init ( void ) +midi_init ( const char *name ) { MESSAGE( "Initializing Jack MIDI" ); -/* if (( client = jack_client_new ( APP_NAME )) == 0 ) */ -/* return 0; */ - - if (( client = jack_client_open ( APP_NAME, (jack_options_t)0, NULL )) == 0 ) + if (( client = jack_client_open ( name, (jack_options_t)0, NULL )) == 0 ) return NULL; /* create output ports */ @@ -605,6 +624,10 @@ void midi_shutdown ( void ) { // TODO: wait for all queued events to play. - - jack_deactivate( client ); + if ( client ) + { + jack_deactivate( client ); + jack_client_close( client ); + client = NULL; + } } diff --git a/src/jack.H b/src/jack.H index 97ce95e..3fdb857 100644 --- a/src/jack.H +++ b/src/jack.H @@ -8,9 +8,11 @@ enum { CONTROL, PERFORMANCE }; class midievent; bool midi_input_event ( int port, midievent *e ); +bool midi_is_active ( void ); +midievent * midi_input_event ( int port ); void midi_output_event ( int port, const midievent *e ); void midi_output_event ( int port, const midievent *e, tick_t duration ); void midi_all_sound_off ( void ); -const char * midi_init ( void ); +const char * midi_init ( const char *name ); void midi_shutdown ( void ); void midi_output_immediate_event ( int port, const midievent *e ); diff --git a/src/lash.C b/src/lash.C deleted file mode 100644 index a16686f..0000000 --- a/src/lash.C +++ /dev/null @@ -1,109 +0,0 @@ - -/*******************************************************************************/ -/* Copyright (C) 2008 Jonathan Moore Liles */ -/* */ -/* This program is free software; you can redistribute it and/or modify it */ -/* under the terms of the GNU General Public License as published by the */ -/* Free Software Foundation; either version 2 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ -/* more details. */ -/* */ -/* You should have received a copy of the GNU General Public License along */ -/* with This program; see the file COPYING. If not,write to the Free Software */ -/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -/*******************************************************************************/ - -#include "lash.H" -#include "config.h" -#include "common.h" -#include "non.H" - -// TODO: produce Save_Project events... - -#ifndef HAVE_LASH - -Lash::Lash ( ) {} -bool Lash::init ( int *argc, char ***argv, const char *jack_name ) { return true; } -void Lash::process ( void ) {} - -#else - -Lash::Lash ( ) -{ - _client = NULL; -} - -bool -Lash::init ( int *argc, char ***argv, const char *jack_name ) -{ - MESSAGE( "Initializing LASH" ); - - if ( ! ( _client = lash_init( lash_extract_args( argc, argv ), APP_NAME, - LASH_Config_File, LASH_PROTOCOL( 2, 0 ) ) ) ) - return false; - - /* register name */ - lash_jack_client_name( _client, jack_name ); - - lash_event_t *e = lash_event_new_with_type( LASH_Client_Name ); - lash_event_set_string( e, APP_TITLE ); - lash_send_event( _client, e ); - - return true; -} - -/** process any queued events */ -void -Lash::process ( void ) -{ - lash_event_t *e; - - char *name; - - while ( ( e = lash_get_event( _client ) ) ) - { - asprintf( &name, "%s/%s", lash_event_get_string( e ), "song.non" ); - - const int t = lash_event_get_type ( e ); - - switch ( t ) - { - case LASH_Save_File: - { - MESSAGE( "LASH wants us to save \"%s\"", name ); - - save_song( name ); - - lash_send_event( _client, lash_event_new_with_type( LASH_Save_File ) ); - - break; - - } - case LASH_Restore_File: - { - MESSAGE( "LASH wants us to load \"%s\"", name ); - - if ( ! load_song( name ) ) - /* FIXME: should we tell lash that we couldn't load the song? */; - - lash_send_event( _client, lash_event_new_with_type( LASH_Restore_File ) ); - - break; - } - case LASH_Quit: - MESSAGE( "LASH wants us to quit" ); - quit(); - break; - default: - WARNING( "unhandled LASH event (%d)", t ); - } - - lash_event_destroy( e ); - } -} - -#endif diff --git a/src/main.C b/src/main.C index cf97e9f..85becd5 100644 --- a/src/main.C +++ b/src/main.C @@ -25,7 +25,7 @@ // #include "gui/input.H" #include "gui/ui.H" #include "jack.H" -#include "lash.H" +#include "NSM.H" #include "pattern.H" #include "phrase.H" @@ -41,6 +41,8 @@ extern const char *BUILD_ID; extern const char *VERSION; +const double NSM_CHECK_INTERVAL = 0.25f; + Canvas *pattern_c, *phrase_c, *trigger_c; sequence *playlist; @@ -48,7 +50,9 @@ sequence *playlist; global_settings config; song_settings song; -Lash lash; +NSM_Client *nsm; + +char *instance_name; /* default to pattern mode */ @@ -78,9 +82,9 @@ quit ( void ) } void -init_song ( void ) +clear_song ( void ) { - song.filename = NULL; +// song.filename = NULL; pattern_c->grid( NULL ); phrase_c->grid( NULL ); @@ -93,6 +97,21 @@ init_song ( void ) song.dirty( false ); } +void +init_song ( void ) +{ + if ( ! midi_is_active() ) + setup_jack(); + + if ( !( nsm && nsm->is_active() ) ) + song.filename = NULL; + + clear_song(); + + if ( nsm && nsm->is_active() ) + save_song( song.filename ); +} + void handle_midi_input ( void ) { @@ -106,6 +125,9 @@ handle_midi_input ( void ) bool load_song ( const char *name ) { + if ( ! midi_is_active() ) + setup_jack(); + MESSAGE( "loading song \"%s\"", name ); Grid *pattern_grid = pattern_c->grid(); @@ -148,6 +170,52 @@ save_song ( const char *name ) return true; } + +void +setup_jack ( ) +{ + const char *jack_name; + + jack_name = midi_init( instance_name ); + if ( ! jack_name ) + ASSERTION( "Could not initialize MIDI system! (is Jack running and with MIDI ports enabled?)" ); + + if ( ! transport.valid ) + { + if ( transport.master ) + ASSERTION( "The version of JACK you are using does not appear to be capable of passing BBT positional information." ); + else + ASSERTION( "Either the version of JACK you are using does pass BBT information, or the current timebase master does not provide it." ); + } +} + +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..." ); + quit(); + } +} + + +void +check_nsm ( void * v ) +{ + nsm->check(); + Fl::repeat_timeout( NSM_CHECK_INTERVAL, check_nsm, v ); +} + int main ( int argc, char **argv ) { @@ -177,26 +245,16 @@ main ( int argc, char **argv ) phrase_c = new Canvas; trigger_c = new Canvas; - init_song(); + nsm = new NSM_Client; + + song.filename = NULL; + clear_song(); pattern::signal_create_destroy.connect( mem_fun( phrase_c, &Canvas::v_zoom_fit ) ); pattern::signal_create_destroy.connect( mem_fun( song, &song_settings::set_dirty ) ); phrase::signal_create_destroy.connect( mem_fun( song, &song_settings::set_dirty ) ); - const char *jack_name; - - jack_name = midi_init(); - if ( ! jack_name ) - ASSERTION( "Could not initialize MIDI system! (is Jack running and with MIDI ports enabled?)" ); - - if ( ! transport.valid ) - { - if ( transport.master ) - ASSERTION( "The version of JACK you are using does not appear to be capable of passing BBT positional information." ); - else - ASSERTION( "Either the version of JACK you are using does pass BBT information, or the current timebase master does not provide it." ); - } - + // song.dirty( false ); init_colors(); @@ -210,18 +268,39 @@ main ( int argc, char **argv ) #endif ui->main_window->show( argc, argv ); - if ( ! lash.init( &argc, &argv, jack_name ) ) - WARNING( "error initializing LASH" ); + instance_name = strdup( APP_NAME ); - if ( argc > 1 ) + const char *nsm_url = getenv( "NSM_URL" ); + + if ( nsm_url ) { - /* maybe a filename on the commandline */ - if ( ! load_song( argv[ 1 ] ) ) - ASSERTION( "Could not load song \"%s\" specified on command line", argv[ 1 ] ); + if ( ! nsm->init( nsm_url ) ) + { + nsm->announce( APP_NAME, ":switch:dirty:", argv[0] ); + + song.signal_dirty.connect( sigc::mem_fun( nsm, &NSM_Client::is_dirty ) ); + song.signal_clean.connect( sigc::mem_fun( nsm, &NSM_Client::is_clean ) ); + + // poll so we can keep OSC handlers running in the GUI thread and avoid extra sync + Fl::add_timeout( NSM_CHECK_INTERVAL, check_nsm, NULL ); + } + else + WARNING( "Error initializing NSM" ); + } + else + { + if ( argc > 1 ) + { + /* maybe a filename on the commandline */ + if ( ! load_song( argv[ 1 ] ) ) + ASSERTION( "Could not load song \"%s\" specified on command line", argv[ 1 ] ); + } } MESSAGE( "Initializing GUI" ); + Fl::add_check( check_sigterm ); + ui->run(); return 0; diff --git a/src/non.H b/src/non.H index 5420600..48c9e79 100644 --- a/src/non.H +++ b/src/non.H @@ -42,7 +42,7 @@ void init_song ( void ); void handle_midi_input ( void ); bool load_song ( const char *name ); bool save_song ( const char *name ); - +void setup_jack ( void ); #include "common.h" #include "const.h"