@@ -57,9 +57,9 @@ CFLAGS+=-DINSTALL_PREFIX=\"$(prefix)\" \ | |||||
-DDOCUMENT_PATH=\"$(DOCUMENT_PATH)\" \ | -DDOCUMENT_PATH=\"$(DOCUMENT_PATH)\" \ | ||||
-DPIXMAP_PATH=\"$(PIXMAP_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) | ifeq ($(JACK_MIDI_PROTO_API),yes) | ||||
CXXFLAGS+=-DJACK_MIDI_PROTO_API | CXXFLAGS+=-DJACK_MIDI_PROTO_API | ||||
@@ -68,7 +68,7 @@ endif | |||||
# uncomment this line to print each playback event to the console (not RT safe) | # uncomment this line to print each playback event to the console (not RT safe) | ||||
# CXXFLAGS+= -DDEBUG_EVENTS | # 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:=$(SRCS:.fl=.C) | ||||
SRCS:=$(sort $(SRCS)) | SRCS:=$(sort $(SRCS)) | ||||
@@ -10,7 +10,6 @@ begin | |||||
begin_options | begin_options | ||||
ask "Installation prefix" prefix /usr/local | ask "Installation prefix" prefix /usr/local | ||||
ask "Use the LASH Audio Session Handler" USE_LASH yes | |||||
ask "Build for debugging" USE_DEBUG no | ask "Build for debugging" USE_DEBUG no | ||||
begin_tests | begin_tests | ||||
@@ -20,9 +19,10 @@ require_command FLUID fluid | |||||
require_package JACK 0.103.0 jack | require_package JACK 0.103.0 jack | ||||
suggest_package XPM 2.0.0 xpm | suggest_package XPM 2.0.0 xpm | ||||
test_version `version_of jack` 0.105.0 || append "JACK_MIDI_PROTO_API=yes" | test_version `version_of jack` 0.105.0 || append "JACK_MIDI_PROTO_API=yes" | ||||
require_package liblo 0.23 liblo | |||||
require_package sigcpp 2.0.0 sigc++-2.0 | |||||
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" | |||||
using LASH && require_package LASH 0.5.4 lash-1.0 | |||||
require_package sigcpp 2.0.0 sigc++-2.0 | |||||
end | end |
@@ -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 <stdio.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/types.h> | |||||
#include <unistd.h> | |||||
#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(); | |||||
} | |||||
} |
@@ -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 */ | /* 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 */ | /* under the terms of the GNU General Public License as published by the */ | ||||
@@ -19,22 +19,23 @@ | |||||
#pragma once | #pragma once | ||||
#include "config.h" | |||||
#include "NSM/Client.H" | |||||
#ifdef HAVE_LASH | |||||
#include <lash/lash.h> | |||||
#endif | |||||
class Lash | |||||
class NSM_Client : public NSM::Client | |||||
{ | { | ||||
#ifdef HAVE_LASH | |||||
lash_client_t *_client; | |||||
#endif | |||||
char *project_filename; | |||||
public: | 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 ); | |||||
}; | }; |
@@ -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 <string.h> | |||||
#include <sys/types.h> | |||||
#include <unistd.h> | |||||
#include <stdlib.h> | |||||
#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; | |||||
} | |||||
}; |
@@ -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 <lo/lo.h> | |||||
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 ); | |||||
}; | |||||
}; |
@@ -40,9 +40,12 @@ decl {\#include "event_edit.H"} {private local | |||||
decl {\#include "../jack.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 | decl {extern const char *BUILD_ID;} {private local | ||||
} | } | ||||
@@ -73,8 +76,6 @@ Function {update_transport( void * )} {open return_type void | |||||
handle_midi_input(); | handle_midi_input(); | ||||
lash.process(); | |||||
ui->progress_group->do_callback(); | ui->progress_group->do_callback(); | ||||
ui->vmetro_widget->update(); | 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 | // JUST A TEST | ||||
if ( transport.rolling ) | if ( transport.rolling ) | ||||
{ | { | ||||
@@ -137,6 +148,8 @@ Fl::scheme( "plastic" ); | |||||
canvas_background_color = FL_GREEN; | canvas_background_color = FL_GREEN; | ||||
playback_mode_menu = NULL; | |||||
main_window = make_main_window(); | main_window = make_main_window(); | ||||
seq_window = make_seq_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 ) );} | 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 ) );} | code1 {song.signal_clean.connect( sigc::mem_fun( o, &Fl_Menu_Item::deactivate ) );} | ||||
} | } | ||||
MenuItem {} { | |||||
MenuItem menu_save_as { | |||||
label {Save &As} | label {Save &As} | ||||
callback {save_dialog( NULL );} | callback {save_dialog( NULL );} | ||||
xywh {0 0 40 25} | xywh {0 0 40 25} | ||||
@@ -1032,6 +1045,10 @@ else | |||||
xywh {0 0 40 24} | 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 | Fl_Group {} {open | ||||
xywh {-1 772 869 33} | xywh {-1 772 869 33} | ||||
@@ -88,10 +88,19 @@ static port_t input[2]; /* contro | |||||
jack_nframes_t nframes; /* for compatibility with older jack */ | 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 */ | /** get next recorded event, if any--runs in UI thread */ | ||||
bool | bool | ||||
midi_input_event ( int port, midievent *me ) | 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_space( input[ port ].ring_buf ) >= sizeof( midievent ) ) | ||||
{ | { | ||||
if ( jack_ringbuffer_read( input[ port ].ring_buf, (char *)me, 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; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Queue an event for output. /tick/ is relative to the current cycle! */ | * Queue an event for output. /tick/ is relative to the current cycle! */ | ||||
void | void | ||||
midi_output_event ( int port, const midievent *e ) | midi_output_event ( int port, const midievent *e ) | ||||
{ | { | ||||
if ( ! midi_is_active() ) | |||||
return; | |||||
event *fe = freelist.first(); | event *fe = freelist.first(); | ||||
if ( ! fe ) | if ( ! fe ) | ||||
@@ -150,6 +163,9 @@ midi_output_event ( int port, const midievent *e ) | |||||
void | void | ||||
midi_output_event ( int port, const midievent *e, tick_t duration ) | midi_output_event ( int port, const midievent *e, tick_t duration ) | ||||
{ | { | ||||
if ( ! midi_is_active() ) | |||||
return; | |||||
if ( duration ) | if ( duration ) | ||||
{ | { | ||||
note_duration[ port ][ e->channel() ][ e->note() ] = (duration + e->timestamp()) * subticks_per_tick; | 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 | void | ||||
midi_output_immediate_event ( int port, const midievent *e ) | 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 ) ) | if ( jack_ringbuffer_write( output[ port ].ring_buf, (const char *)e, sizeof( midievent ) ) != sizeof( midievent ) ) | ||||
WARNING( "output ringbuffer overrun" ); | WARNING( "output ringbuffer overrun" ); | ||||
else | else | ||||
@@ -208,6 +227,9 @@ midi_output_immediate_event ( int port, const midievent *e ) | |||||
void | void | ||||
midi_all_sound_off ( void ) | midi_all_sound_off ( void ) | ||||
{ | { | ||||
if ( ! midi_is_active() ) | |||||
return; | |||||
MESSAGE( "stopping all sound" ); | MESSAGE( "stopping all sound" ); | ||||
midievent e; | midievent e; | ||||
@@ -525,14 +547,11 @@ schedule: | |||||
} | } | ||||
const char * | const char * | ||||
midi_init ( void ) | |||||
midi_init ( const char *name ) | |||||
{ | { | ||||
MESSAGE( "Initializing Jack MIDI" ); | 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; | return NULL; | ||||
/* create output ports */ | /* create output ports */ | ||||
@@ -605,6 +624,10 @@ void | |||||
midi_shutdown ( void ) | midi_shutdown ( void ) | ||||
{ | { | ||||
// TODO: wait for all queued events to play. | // TODO: wait for all queued events to play. | ||||
jack_deactivate( client ); | |||||
if ( client ) | |||||
{ | |||||
jack_deactivate( client ); | |||||
jack_client_close( client ); | |||||
client = NULL; | |||||
} | |||||
} | } |
@@ -8,9 +8,11 @@ enum { CONTROL, PERFORMANCE }; | |||||
class midievent; | class midievent; | ||||
bool midi_input_event ( int port, midievent *e ); | 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 ); | ||||
void midi_output_event ( int port, const midievent *e, tick_t duration ); | void midi_output_event ( int port, const midievent *e, tick_t duration ); | ||||
void midi_all_sound_off ( void ); | void midi_all_sound_off ( void ); | ||||
const char * midi_init ( void ); | |||||
const char * midi_init ( const char *name ); | |||||
void midi_shutdown ( void ); | void midi_shutdown ( void ); | ||||
void midi_output_immediate_event ( int port, const midievent *e ); | void midi_output_immediate_event ( int port, const midievent *e ); |
@@ -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 |
@@ -25,7 +25,7 @@ | |||||
// #include "gui/input.H" | // #include "gui/input.H" | ||||
#include "gui/ui.H" | #include "gui/ui.H" | ||||
#include "jack.H" | #include "jack.H" | ||||
#include "lash.H" | |||||
#include "NSM.H" | |||||
#include "pattern.H" | #include "pattern.H" | ||||
#include "phrase.H" | #include "phrase.H" | ||||
@@ -41,6 +41,8 @@ | |||||
extern const char *BUILD_ID; | extern const char *BUILD_ID; | ||||
extern const char *VERSION; | extern const char *VERSION; | ||||
const double NSM_CHECK_INTERVAL = 0.25f; | |||||
Canvas *pattern_c, *phrase_c, *trigger_c; | Canvas *pattern_c, *phrase_c, *trigger_c; | ||||
sequence *playlist; | sequence *playlist; | ||||
@@ -48,7 +50,9 @@ sequence *playlist; | |||||
global_settings config; | global_settings config; | ||||
song_settings song; | song_settings song; | ||||
Lash lash; | |||||
NSM_Client *nsm; | |||||
char *instance_name; | |||||
/* default to pattern mode */ | /* default to pattern mode */ | ||||
@@ -78,9 +82,9 @@ quit ( void ) | |||||
} | } | ||||
void | void | ||||
init_song ( void ) | |||||
clear_song ( void ) | |||||
{ | { | ||||
song.filename = NULL; | |||||
// song.filename = NULL; | |||||
pattern_c->grid( NULL ); | pattern_c->grid( NULL ); | ||||
phrase_c->grid( NULL ); | phrase_c->grid( NULL ); | ||||
@@ -93,6 +97,21 @@ init_song ( void ) | |||||
song.dirty( false ); | 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 | void | ||||
handle_midi_input ( void ) | handle_midi_input ( void ) | ||||
{ | { | ||||
@@ -106,6 +125,9 @@ handle_midi_input ( void ) | |||||
bool | bool | ||||
load_song ( const char *name ) | load_song ( const char *name ) | ||||
{ | { | ||||
if ( ! midi_is_active() ) | |||||
setup_jack(); | |||||
MESSAGE( "loading song \"%s\"", name ); | MESSAGE( "loading song \"%s\"", name ); | ||||
Grid *pattern_grid = pattern_c->grid(); | Grid *pattern_grid = pattern_c->grid(); | ||||
@@ -148,6 +170,52 @@ save_song ( const char *name ) | |||||
return true; | 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 | int | ||||
main ( int argc, char **argv ) | main ( int argc, char **argv ) | ||||
{ | { | ||||
@@ -177,26 +245,16 @@ main ( int argc, char **argv ) | |||||
phrase_c = new Canvas; | phrase_c = new Canvas; | ||||
trigger_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( phrase_c, &Canvas::v_zoom_fit ) ); | ||||
pattern::signal_create_destroy.connect( mem_fun( song, &song_settings::set_dirty ) ); | pattern::signal_create_destroy.connect( mem_fun( song, &song_settings::set_dirty ) ); | ||||
phrase::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 ); | song.dirty( false ); | ||||
init_colors(); | init_colors(); | ||||
@@ -210,18 +268,39 @@ main ( int argc, char **argv ) | |||||
#endif | #endif | ||||
ui->main_window->show( argc, argv ); | ui->main_window->show( argc, argv ); | ||||
if ( ! lash.init( &argc, &argv, jack_name ) ) | |||||
WARNING( "error initializing LASH" ); | |||||
instance_name = strdup( APP_NAME ); | |||||
const char *nsm_url = getenv( "NSM_URL" ); | |||||
if ( nsm_url ) | |||||
{ | |||||
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 ) ); | |||||
if ( argc > 1 ) | |||||
// 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 | |||||
{ | { | ||||
/* maybe a filename on the commandline */ | |||||
if ( ! load_song( argv[ 1 ] ) ) | |||||
ASSERTION( "Could not load song \"%s\" specified on command line", argv[ 1 ] ); | |||||
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" ); | MESSAGE( "Initializing GUI" ); | ||||
Fl::add_check( check_sigterm ); | |||||
ui->run(); | ui->run(); | ||||
return 0; | return 0; | ||||
@@ -42,7 +42,7 @@ void init_song ( void ); | |||||
void handle_midi_input ( void ); | void handle_midi_input ( void ); | ||||
bool load_song ( const char *name ); | bool load_song ( const char *name ); | ||||
bool save_song ( const char *name ); | bool save_song ( const char *name ); | ||||
void setup_jack ( void ); | |||||
#include "common.h" | #include "common.h" | ||||
#include "const.h" | #include "const.h" | ||||