From 677d4976b2a8a6754a6992f36e5509d78b0d417c Mon Sep 17 00:00:00 2001 From: Jonathan Moore Liles Date: Tue, 28 Feb 2012 23:02:10 -0800 Subject: [PATCH] NSM: For lack of a better place to put it, add 'jackpatch' program to the repository. --- session-manager/Makefile | 2 + session-manager/configure | 2 +- session-manager/makefile.inc | 13 +- session-manager/src/jackpatch.c | 650 ++++++++++++++++++++++++++++++++ 4 files changed, 661 insertions(+), 6 deletions(-) create mode 100644 session-manager/src/jackpatch.c diff --git a/session-manager/Makefile b/session-manager/Makefile index 0b69079..16db9d4 100644 --- a/session-manager/Makefile +++ b/session-manager/Makefile @@ -111,6 +111,7 @@ install: all @ echo -n "Installing..." @ install src/nsmd "$(DESTDIR)$(prefix)"/bin/nsmd @ install src/session-manager "$(DESTDIR)$(prefix)"/bin/non-session-manager + @ install src/jackpatch "$(DESTDIR)$(prefix)"/bin/jackpatch @ mkdir -p "$(DESTDIR)$(SYSTEM_PATH)"/non-session-manager @ $(MAKE) -s -C doc install @ install -d "$(DESTDIR)$(PIXMAP_PATH)/non-session-manager" @@ -122,6 +123,7 @@ install: all @ echo "$(DONE)" ifneq ($(USE_DEBUG),yes) @ echo -n "Stripping..." + @ strip "$(DESTDIR)$(prefix)"/bin/jackpatch @ strip "$(DESTDIR)$(prefix)"/bin/nsmd @ strip "$(DESTDIR)$(prefix)"/bin/non-session-manager @ echo "$(DONE)" diff --git a/session-manager/configure b/session-manager/configure index f0616a1..b2ff068 100755 --- a/session-manager/configure +++ b/session-manager/configure @@ -18,7 +18,7 @@ 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 +suggest_package JACK 0.103.0 jack require_package liblo 0.23 liblo require_package sigcpp 2.0.0 sigc++-2.0 suggest_package XPM 2.0.0 xpm diff --git a/session-manager/makefile.inc b/session-manager/makefile.inc index ef8dd88..33926a0 100644 --- a/session-manager/makefile.inc +++ b/session-manager/makefile.inc @@ -14,16 +14,19 @@ Session_CFLAGS := $(LIBLO_CFLAGS) $(SIGCPP_CFLAGS) $(XPM_CFLAGS) $(FLTK_CLFAGS) 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) + @ $(CXX) $(CXXFLAGS) $(Session_LIBS) src/nsmd.o -o $@ -Lnonlib -lnonlib && echo $(DONE) src/session-manager: src/session-manager.o nonlib/libnonlib.a @ echo -n Linking session handler. - @ $(CXX) $(CXXFLAGS) $(FLTK_LIBS) $(XPM_LIBS) $(Session_LIBS) src/session-manager.o -o $@ -Lnonlib -lnonlib -ldl && echo $(DONE) + @ $(CXX) $(CXXFLAGS) $(FLTK_LIBS) $(XPM_LIBS) $(Session_LIBS) src/session-manager.o -o $@ -Lnonlib -lnonlib && 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) + @ $(CXX) $(CXXFLAGS) $(Session_LIBS) src/send_osc.o -o $@ -Lnonlib -lnonlib && echo $(DONE) -Session: src/send_osc src/nsmd src/session-manager +src/jackpatch: src/jackpatch.o + @ $(CXX) $(CXXFLAGS) $(JACK_LIBS) $(LIBLO_LIBS) src/jackpatch.o -o $@ && echo $(DONE) + +Session: src/send_osc src/nsmd src/session-manager src/jackpatch Session_clean: - rm -f $(Session_OBJS) src/nsmd + rm -f $(Session_OBJS) src/nsmd src/session-manager src/jackpatch diff --git a/session-manager/src/jackpatch.c b/session-manager/src/jackpatch.c new file mode 100644 index 0000000..0dabeab --- /dev/null +++ b/session-manager/src/jackpatch.c @@ -0,0 +1,650 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +/* jackpatch.c + + This program is just like ASSPatch, except that it works with Jack ports (audio and MIDI). + + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static const char **all_ports = NULL; + +jack_client_t *client; + +lo_server losrv; +lo_address nsm_addr; +int nsm_is_active; + +char *project_file; + +#define APP_TITLE "JACKPatch" +#define VERSION "0.2" + +struct patch_record { + struct { + char *client; + char *port; + } src , dst; + struct patch_record *next; +}; + +static struct patch_record *patch_list = NULL; + +/** + * Pretty-print patch relationship of /pr/ + */ +void +print_patch ( struct patch_record *pr, int mode ) +{ + printf( "%s from '%s:%s' to '%s:%s'\n", mode ? ">>" : "::", + pr->src.client, pr->src.port, pr->dst.client, pr->dst.port ); + +} + + +void +enqueue ( struct patch_record *p ) +{ + p->next = patch_list; + patch_list = p; +} + +void +dequeue ( struct patch_record *pr ) +{ + if ( !pr ) + return; + + free( pr->src.port ); + free( pr->dst.port ); + free( pr->src.client ); + free( pr->dst.client ); + + free( pr ); +} + +int +process_patch ( const char *patch ) +{ + struct patch_record *pr; + char *leftc, *rightc, *leftp, *rightp; + char dir[3]; + + int retval; + + retval = sscanf( patch, " %a[^:]:%a[^|] |%1[<>|] %a[^:]:%a[^\n]", + &leftc, &leftp, dir, &rightc, &rightp ); + + if ( retval == EOF ) + return -1; + + if ( retval != 5 ) + return 0; + + /* trim space */ + int j; + for ( j = strlen( leftp ) - 1; j > 0; j-- ) + { + if ( leftp[j] == ' ' || leftp[j] == '\t' ) + leftp[j] = 0; + else + break; + } + + dir[2] = 0; + + pr = malloc( sizeof( struct patch_record ) ); + + switch ( *dir ) + { + case '<': + pr->src.client = rightc; + pr->src.port = rightp; + + pr->dst.client = leftc; + pr->dst.port = leftp; + + enqueue( pr ); + break; + case '>': + pr->src.client = leftc; + pr->src.port = leftp; + + pr->dst.client = rightc; + pr->dst.port = rightp; + + enqueue( pr ); + break; + case '|': + pr->src.client = rightc; + pr->src.port = rightp; + + pr->dst.client = leftc; + pr->dst.port = leftp; + + enqueue( pr ); + + pr = malloc( sizeof( struct patch_record ) ); + + pr->src.client = strdup( leftc ); + pr->src.port = strdup( leftp ); + + pr->dst.client = strdup( rightc ); + pr->dst.port = strdup( rightp ); + + enqueue( pr ); + break; + default: +// fprintf( stderr, "Invalid token '|%s' at line %i of %s!", dir, i, file ); + free( pr ); + return 0; + } + + + print_patch( pr, 1 ); + + return 1; +} + +void +clear_all_patches ( ) +{ + struct patch_record *pr; + + while ( patch_list ) + { + pr = patch_list; + patch_list = pr->next; + dequeue( pr ); + } +} + +/** + * Crudely parse configuration file named by /file/ using fscanf + */ +int +read_config ( const char *file ) +{ + FILE *fp; + int i = 0; + struct patch_record *pr; + + if ( NULL == ( fp = fopen( file, "r" ) ) ) + return 0; + + clear_all_patches(); + + while ( !feof( fp ) && !ferror( fp ) ) + { + int retval; + int k; + char buf[BUFSIZ]; + + i++; + + for ( k = 0; k < sizeof( buf ) - 1; k++ ) + { + retval = fread( buf + k, 1, 1, fp ); + if ( retval != 1 ) + break; + if ( buf[k] == '\n' ) + { + if ( k == 0 ) + continue; + else + break; + } + } + + if ( retval == 0 ) + break; + + retval = process_patch( buf ); + + if ( retval < 0 ) + break; + + if ( retval == 0 ) + { + printf( "bad line %i.\n", i ); + continue; + } + } + + fclose( fp ); + + return 1; +} + + + +/* returns 0 if connection failed, true if succeeded. Already connected + * is not considered failure */ +int +connect_path ( struct patch_record *pr ) +{ + int r = 0; + + char srcport[512]; + char dstport[512]; + + snprintf( srcport, 512, "%s:%s", pr->src.client, pr->src.port ); + snprintf( dstport, 512, "%s:%s", pr->dst.client, pr->dst.port ); + + printf( "Connecting %s |> %s\n", srcport, dstport ); + + r = jack_connect( client, srcport, dstport ); + + print_patch( pr, r ); + + if ( r == 0 || r == EEXIST ) + return 1; + else + { + printf( "Error is %i", r ); + return 0; + } +} + + +int +activate_patch ( const char *portname ) +{ + struct patch_record *pr; + int r = 0; + + char client[512]; + char port[512]; + + sscanf( portname, "%[^:]:%s", client, port ); + + for ( pr = patch_list; pr; pr = pr->next ) + { +// printf( "checking %s:%s agains %s:%s\n", pr->src.client, pr->src.port, client, port ); + + if ( ( !strcmp( client, pr->src.client ) && !strcmp( port, pr->src.port ) ) || + ( !strcmp( client, pr->dst.client ) && !strcmp( port, pr->dst.port ) ) ) + { + return connect_path( pr ); + } + } + + return 0; +} + +/** + * Attempt to activate all connections in patch list + */ +void +activate_all_patches ( void ) +{ + struct patch_record *pr; + + for ( pr = patch_list; pr; pr = pr->next ) + connect_path( pr ); +} + +int +find_port ( const char *portname ) +{ + if ( ! all_ports || ! portname ) + return 0; + + const char **port; + for ( port = all_ports; *port; port++ ) + { + if ( 0 == strcmp( *port, portname ) ) + return 1; + } + + return 0; +} + +/** called for every new port */ +int +handle_new_port ( const char *portname ) +{ + printf( "New endpoint '%s' registered.\n", portname ); + /* this is a new port */ + return activate_patch( portname ); +} + +void +wipe_ports ( void ) +{ + if ( all_ports ) + free( all_ports ); + + all_ports = NULL; +} + +void +check_for_new_ports ( void ) +{ + const char **port; + const char **ports = jack_get_ports( client, NULL, NULL, 0 ); + + if ( ! ports ) + { + printf( "error, no ports" ); + return; + } + + for ( port = ports; *port; port++ ) + if ( ! find_port( *port ) ) + if ( ! handle_new_port( *port ) ) + { + /* failed to connect. Probably because client is inactive. Mark it as invalid and try again later. */ +// *port = "RETRY"; + } + + if ( all_ports ) + free( all_ports ); + + all_ports = ports; +} + +void +snapshot ( const char *file ) +{ + FILE *fp; + + const char **port; + const char **ports = jack_get_ports( client, NULL, NULL, JackPortIsOutput ); + + if ( ! ports ) + return; + + if ( NULL == ( fp = fopen( file, "w" ) ) ) + { + fprintf( stderr, "Error opening snapshot file for writing" ); + return; + } + + for ( port = ports; *port; port++ ) + { + jack_port_t *p; + + p = jack_port_by_name( client, *port ); + + const char **connections; + const char **connection; + + connections = jack_port_get_all_connections( client, p ); + + if ( ! connections ) + continue; + + for ( connection = connections; *connection; connection++ ) + { + fprintf( fp, "%-40s |> %s\n", *port, *connection ); + printf( "++ %s |> %s\n", *port, *connection ); + } + + free( connections ); + } + + free( ports ); + + fclose( fp ); +} + +static int die_now = 0; + +void +signal_handler ( int x ) +{ + die_now = 1; +} + +void +die ( void ) +{ + jack_deactivate( client ); + jack_client_close( client ); + client = NULL; + exit( 0 ); +} + +/** set_traps + * + * Handle signals + */ +void +set_traps ( void ) +{ +// signal( SIGHUP, signal_handler ); + signal( SIGINT, signal_handler ); +// signal( SIGQUIT, signal_handler ); +// signal( SIGSEGV, signal_handler ); +// signal( SIGPIPE, signal_handler ); + signal( SIGTERM, signal_handler ); +} + +/****************/ +/* OSC HANDLERS */ +/****************/ + +int +osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + if ( strcmp( "/nsm/server/announce", &argv[0]->s ) ) + return -1; + + printf( "Failed to register with NSM: %s\n", &argv[2]->s ); + nsm_is_active = 0; + + return 0; +} + + +int +osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + if ( strcmp( "/nsm/server/announce", &argv[0]->s ) ) + return -1; + + printf( "Successfully registered. NSM says: %s", &argv[1]->s ); + + nsm_is_active = 1; + nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) ); + + return 0; +} + +int +osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + snapshot( project_file ); + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" ); + + return 0; +} + +int +osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + const char *new_path = &argv[0]->s; + const char *display_name = &argv[1]->s; + + char *new_filename; + + asprintf( &new_filename, "%s.jackpatch", new_path ); + + struct stat st; + + if ( 0 == stat( new_filename, &st ) ) + { + if ( read_config( new_filename ) ) + { + printf( "Reading patch definitions from: %s\n", new_filename ); + wipe_ports(); + check_for_new_ports(); +// activate_all_patches(); + } + else + { + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" ); + return 0; + } + } + else + { + clear_all_patches(); + } + + if ( project_file ) + free( project_file ); + + project_file = new_filename; + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" ); + + return 0; +} + +void +announce ( const char *nsm_url, const char *client_name, const char *process_name ) +{ + printf( "Announcing to NSM\n" ); + + lo_address to = lo_address_new_from_url( nsm_url ); + + int pid = (int)getpid(); + + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii", + client_name, + ":switch:", + process_name, + 0, /* api_major_version */ + 8, /* api_minor_version */ + pid ); + + lo_address_free( to ); +} + +void +init_osc ( const char *osc_port ) +{ + losrv = lo_server_new( osc_port, NULL ); +//error_handler ); + + char *url = lo_server_get_url(losrv); + printf("OSC: %s\n",url); + free(url); + + lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL ); + lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL ); + lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL ); + lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL ); +} + + +/* */ + +int +main ( int argc, char **argv ) +{ + + /* get_args( argc, argv ); */ + + jack_status_t status; + + client = jack_client_open( APP_TITLE, JackNullOption, &status ); + + if ( ! client ) + { + fprintf( stderr, "Could not register JACK client\n" ); + exit(1); + + } + + jack_activate( client ); + +// activate_all_patches(); + + set_traps(); + + if ( argc > 1 ) + { + if ( ! strcmp( argv[1], "--save" ) ) + { + if ( argc > 2 ) + { + printf( "Saving current graph to: %s\n", argv[2] ); + snapshot( argv[2] ); + exit(0); + } + } + else + { + read_config( argv[1] ); + printf( "Monitoring...\n" ); + for ( ;; ) + { + check_for_new_ports(); + usleep( 50000 ); + } + } + } + + init_osc( NULL ); + + const char *nsm_url = getenv( "NSM_URL" ); + + if ( nsm_url ) + { + announce( nsm_url, APP_TITLE, argv[0] ); + } + else + { + fprintf( stderr, "Could not register as NSM client.\n" ); + exit(1); + } + + for ( ;; ) + { + lo_server_recv_noblock( losrv, 500 ); + + check_for_new_ports(); + + if ( die_now ) + die(); + } +}