--- soundtracker-0.6.7-pre1/app/drivers/jack-output.c	Sun Feb  2 17:12:18 2003
+++ soundtracker-0.6.7-pre6-kv2/app/drivers/jack-output.c	Tue Aug 19 03:26:17 2003
@@ -18,10 +18,27 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-// TODO: Clientname hardcoded, better declicking, transport?,
-// better config GUI, pop up on jack shutdown, 
-// ability to reconnect on open() if shutdown previously
-// is endianness an issue here?
+/*
+ * History: 
+ * 2003-xx-yy Anthony Van Groningen
+ *     - Initial version
+ * 2003-08-18 Kai Vehmanen
+ *     - Updated to use the new JACK transport API introduced 
+ *       in JACK-0.77.
+ */
+
+/*
+ * TODO: 
+ * scopes: I think this is an ST issue, but I don't think our clock updates enough.
+ * Clientname code could be improved. Max 10 clients soundtracker_0-9
+ * need better declicking?
+ * endianness?
+ * slave transport was removed
+ * should master transport always work? even for pattern? Can we determine this info anyway?
+ * general thread safety: d->state should be wrapped in state_mx locks as a matter of principle
+ *                        In practice this is needed only when we are waiting on state_cv.
+ * XRUN counter
+ */
 
 #include <config.h>
 
@@ -47,7 +64,7 @@
 #include "driver-out.h"
 #include "mixer.h"
 #include "errors.h"
-#include "gui-subs.h"
+#include "gui.h"
 #include "preferences.h"
 
 // suggested by Erik de Castro Lopo
@@ -63,35 +80,45 @@
 
 typedef enum {
 	JackDriverStateIsRolling,
-	JackDriverStateIsStopping,
+	JackDriverStateIsDeclicking,
+	JackDriverStateIsStopping,    // declicking is done, we want to transition to stopped
 	JackDriverStateIsStopped
 } jack_driver_state;
 
 typedef enum {
-	JackDriverTransportIsInternal = 0,
-	JackDriverTransportIsSlave = 1,
-	JackDriverTransportIsMaster = 2
+	JackDriverTransportIsDisabled = 0,
+	JackDriverTransportIsEnabled = 1
 } jack_driver_transport;
 
 typedef struct jack_driver {
+	// prefs stuff
 	GtkWidget *configwidget;
+	GtkWidget *client_name_label;
 	GtkWidget *status_label;
-	GtkWidget *transport_radio[3];
-	GMutex *configmutex;
-
-	nframes_t buffer_size;     // a constant now, I think
-	unsigned long samplerate;  
-	char *clientname;          // hardcoded right now, fix later
+	GtkWidget *transport_check;
+	guint transport_check_id;
+	GtkWidget *declick_check;
+	gboolean do_declick;
+
+	// jack + audio stuff
+	nframes_t buffer_size;     
+	unsigned long sample_rate;  
+	char client_name[15];      
 	jack_client_t *client;
 	jack_port_t *left,*right;  
-	nframes_t position;        // frames since ST called jack_open()
-	void *mix;                 // passed to audio_mix, big enough for stereo 16 bit nframes = nframes*4  
+	void *mix;                      // passed to audio_mix, big enough for stereo 16 bit nframes = nframes*4
 	STMixerFormat mf;
+	jack_transport_info_t ti;        
 
-	gboolean is_active;
+	// internal state stuff
+	jack_driver_state state;         
+	nframes_t position;              // frames since ST called open()
+	pthread_mutex_t *process_mx;     // try to lock this around process_core()
+	pthread_cond_t *state_cv;        // trigger after declicking if we have the lock
+	gboolean locked;                 // set true if we get it. then we can trigger any CV's during process_core()
+	gboolean is_active;              // jack seems to be running fine
 	jack_driver_transport transport; // who do we serve?
-	jack_transport_info_t ti;        
-	jack_driver_state state;
+
 } jack_driver;
 
 static inline float
@@ -102,120 +129,111 @@
 	return (float)current/(float)total;
 }
 
-static int
-jack_driver_process (nframes_t nframes, void *arg)
+static void
+jack_driver_process_core (nframes_t nframes, jack_driver *d)
 {
-	jack_driver *d = arg;
 	audio_t *lbuf,*rbuf;
 	gint16 *mix = d->mix;
 	nframes_t cnt = nframes;
 	float gain = 1.0f;
+	jack_driver_state state = d->state;
+
+	/* jack_position_t j_pos;  */
+	/* jack_transport_state_t j_state = jack_transport_query (d->client, &j_pos); */
 	
 	lbuf = (audio_t *) jack_port_get_buffer(d->left,nframes);
 	rbuf = (audio_t *) jack_port_get_buffer(d->right,nframes);
-
-	if (d->transport == JackDriverTransportIsSlave) {
-		// not clear what this means, since we may not be able
-		// control ST's engine from the driver
-		// We'll just get the external tran info right now
-		d->ti.valid = JackTransportPosition | JackTransportState;
-                jack_get_transport_info (d->client, &(d->ti));
-	}
 	
-	switch (d->state) {
+      	switch (state) {
+
 	case JackDriverStateIsRolling: 
-		audio_mix (mix, nframes, d->samplerate, d->mf);
+		audio_mix (mix, nframes, d->sample_rate, d->mf);
 		d->position += nframes;
 		while (cnt--) {
 			*(lbuf++) = sample_convert_s16_to_float (*mix++);
 			*(rbuf++) = sample_convert_s16_to_float (*mix++);
 		}
-		d->ti.state = JackTransportRolling; // redundant or reassuring?
 		break;
-
-	case JackDriverStateIsStopping:
-		audio_mix (mix, nframes, d->samplerate, d->mf);
+		
+	case JackDriverStateIsDeclicking:
+		audio_mix (mix, nframes, d->sample_rate, d->mf);
 		d->position += nframes;
 		while (cnt--) {
 			gain = jack_driver_declick_coeff (nframes, cnt);
 			*(lbuf++) = gain * sample_convert_s16_to_float (*mix++);
 			*(rbuf++) = gain * sample_convert_s16_to_float (*mix++);
 		}
-		d->state = JackDriverStateIsStopped;
-		d->ti.state = JackTransportStopped;
+		// safe because ST shouldn't call open() with pending release()
+		d->state = JackDriverStateIsStopping; 
 		break;
-		
+
+	case JackDriverStateIsStopping:
+		// if locked, then trigger change of state, otherwise keep silent
+		if (d->locked) {
+			d->state = JackDriverStateIsStopped;
+			pthread_cond_signal (d->state_cv);
+		}		
+		// fall down
+
 	case JackDriverStateIsStopped:
 	default:
 		memset (lbuf, 0, nframes * sizeof (audio_t));
 		memset (rbuf, 0, nframes * sizeof (audio_t));
-		d->ti.state = JackTransportStopped;
-	}
-		
-	if (d->transport == JackDriverTransportIsMaster) {
-		d->ti.position = d->position;
-		d->ti.valid = JackTransportPosition | JackTransportState;
-		jack_set_transport_info (d->client, &(d->ti));
 	}
-	return 0;
 }
 
-static void 
-jack_driver_prefs_restore_transport_radio (jack_driver *d)
+static int
+jack_driver_process_wrapper (nframes_t nframes, void *arg)
 {
-	int i;
-	
-	for (i = 0; i < 3; i++) {
-		if (i == d->transport) {
-			gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(d->transport_radio[i]),TRUE);
-		} else {
-			gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(d->transport_radio[i]),FALSE);
-		}
+	jack_driver *d = arg;
+
+	if (pthread_mutex_trylock (d->process_mx) == 0) {
+		d->locked = TRUE;
+		jack_driver_process_core (nframes, d);
+		pthread_mutex_unlock (d->process_mx);
+	} else {
+		d->locked = FALSE;
+		jack_driver_process_core (nframes, d);
 	}
+	return 0;
 }
 
-// FIX ME
 static void
 jack_driver_prefs_transport_callback (void *a, jack_driver *d)
 {
-	jack_driver_transport old = d->transport;
-	jack_driver_transport new = find_current_toggle (d->transport_radio,3);
-	
-	if (old == new)
-		return;
-
-	switch (new) {
-	case JackDriverTransportIsMaster:
-		if (jack_engine_takeover_timebase (d->client) != 0) { 
-			// will set back to old
-			jack_driver_prefs_restore_transport_radio (d);
-			// inform user somehow that we were'nt able to become master
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (d->transport_check))) {
+		if (d->is_active && (jack_engine_takeover_timebase (d->client) == 0)) {
+			d->transport = JackDriverTransportIsEnabled;
+			return;
 		} else {
-			d->transport = JackDriverTransportIsMaster;
+			// reset
+			// gtk_signal_handler_block (GTK_OBJECT(d->transport_check), d->transport_check_id);
+			// gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(d->transport_check), FALSE);
+			// gtk_signal_handler_unblock (GTK_OBJECT(d->transport_check), d->transport_check_id);
+			return;
 		}
-		break;
-	case JackDriverTransportIsSlave:
-		// d->transport = JackDriverTransportIsSlave;
-		// prevent right now
-		jack_driver_prefs_restore_transport_radio (d);
-		break;
-	case JackDriverTransportIsInternal:
-		// what if we were master, how do we release control?
-		d->transport = JackDriverTransportIsInternal;
-		break;
+	} else {
+		d->transport = JackDriverTransportIsDisabled;
 	}
-	
+}
+
+static void
+jack_driver_prefs_declick_callback (void *a, jack_driver *d)
+{
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (d->declick_check)))
+		d->do_declick = TRUE;
+	else
+		d->do_declick = FALSE;
 }
 
 static void
 jack_driver_make_config_widgets (jack_driver *d)
 {
 	GtkWidget *thing, *mainbox, *hbox;
-	static const char *transportlabels[] = {"Internal","Slave","Master"};
 	
 	d->configwidget = mainbox = gtk_vbox_new (FALSE,2);
 
-	thing = gtk_label_new (_("These changes won't take effect until you restart playing."));
+	d->client_name_label = thing = gtk_label_new ("");
 	gtk_box_pack_start (GTK_BOX(mainbox), thing, FALSE, TRUE, 0);
 	gtk_widget_show (thing);
 	
@@ -231,72 +249,110 @@
 	gtk_box_pack_start (GTK_BOX(mainbox), hbox, FALSE, TRUE, 0);
 	gtk_widget_show (hbox);
 
-	thing = gtk_label_new (_("Transport:"));
-	gtk_box_pack_start (GTK_BOX(hbox), thing, FALSE, TRUE, 0);
+ 	thing = d->transport_check = gtk_check_button_new_with_label (_("transport"));
+	gtk_box_pack_start (GTK_BOX(mainbox), thing, FALSE, TRUE, 0);
+	d->transport_check_id = gtk_signal_connect(GTK_OBJECT(thing), "clicked", GTK_SIGNAL_FUNC(jack_driver_prefs_transport_callback),d);
 	gtk_widget_show (thing);
-	add_empty_hbox (hbox);
-	make_radio_group_full (transportlabels, hbox, d->transport_radio, FALSE, TRUE,(void(*)())jack_driver_prefs_transport_callback, d);
 
+	thing = d->declick_check = gtk_check_button_new_with_label (_("declick"));
+	gtk_box_pack_start (GTK_BOX(mainbox), thing, FALSE, TRUE, 0);
+	gtk_signal_connect(GTK_OBJECT(thing), "clicked", GTK_SIGNAL_FUNC(jack_driver_prefs_declick_callback),d);
+	gtk_widget_show (thing);
 }
 
 static int  
-jack_driver_samplerate_callback (nframes_t nframes, void *arg)
+jack_driver_sample_rate_callback (nframes_t nframes, void *arg)
 {
 	jack_driver *d = arg;
-	d->samplerate = nframes;
+	d->sample_rate = nframes;
 	return 0;
 }
 
 static void
+jack_driver_prefs_update (jack_driver *d)
+{
+	char status_buf[64];
+
+	if (d->is_active) {
+		sprintf (status_buf, _("Running at %d Hz with %d frames"), (int)d->sample_rate, (int)d->buffer_size);
+		gtk_label_set_text (GTK_LABEL (d->client_name_label), d->client_name);
+	}
+	else 
+		sprintf (status_buf, _("Jack server not running?"));
+	gtk_label_set_text (GTK_LABEL (d->status_label), status_buf);
+       
+}
+
+static void
 jack_driver_server_has_shutdown (void *arg)
 {
 	jack_driver *d = arg;
 	d->is_active = FALSE;
+	jack_driver_prefs_update (d);
+}
+
+static void
+jack_driver_error (const char *s)
+{
+	
 }
 
 static void *
 jack_driver_new (void)
 {
 	jack_driver *d = g_new(jack_driver, 1);
+	int i;
 
+	d->mix = NULL;
 	d->mf = ST_MIXER_FORMAT_S16_LE | ST_MIXER_FORMAT_STEREO;
 	// d->mf = ST_MIXER_FORMAT_S16_BE | ST_MIXER_FORMAT_STEREO;
 	d->state = JackDriverStateIsStopped;
-	d->transport = JackDriverTransportIsInternal;
+	d->transport = JackDriverTransportIsEnabled;
 	d->position = 0;
 	d->is_active = FALSE;
-	d->configmutex = g_mutex_new ();
+	d->process_mx = (pthread_mutex_t*)malloc(sizeof (pthread_mutex_t));
+	d->state_cv = (pthread_cond_t *)malloc(sizeof (pthread_mutex_t));
+	d->do_declick = TRUE;
+	pthread_mutex_init (d->process_mx, NULL);
+	pthread_cond_init (d->state_cv, NULL);
 	jack_driver_make_config_widgets (d);	
 
-	if ((d->client = jack_client_new ("soundtracker")) == 0) {
+	jack_set_error_function (jack_driver_error);
+
+	// TODO: this should be improved, both error handling and saving the string
+	// I'm probably not taking advantage of libjack
+	sprintf (d->client_name, _("soundtracker"));
+	d->client_name[12] = '_';
+	d->client_name[14] = 0;
+	for (i = 0; i < 9; i++) {
+		d->client_name[13] = 48 + i; // "0"-"9"
+		if ((d->client = jack_client_new (d->client_name)) != 0) {
+			break;
+		}
+	}
+	if (d->client == NULL) {
 		// we've failed here, but we should have a working dummy driver
-		// i.e. ST shouldn't segfault, but no audio
+		// because ST will segfault on NULL return
 		return d;
 	}
-	
-	// Jack-dependent setup only now! 
-	d->samplerate = jack_get_sample_rate (d->client);
+
+	// Jack-dependent setup only 
+	d->sample_rate = jack_get_sample_rate (d->client);
 	d->buffer_size = jack_get_buffer_size (d->client);
 	d->mix = calloc(1, d->buffer_size * 4);
 	
-	d->left = jack_port_register (d->client,_("left"),JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput,0);
-	d->right = jack_port_register (d->client,_("right"),JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput,0);
+	d->left = jack_port_register (d->client,_("out_1"),JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput,0);
+	d->right = jack_port_register (d->client,_("out_2"),JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput,0);
 
-	jack_set_process_callback (d->client,jack_driver_process, d);
-	jack_set_sample_rate_callback (d->client,jack_driver_samplerate_callback, d);
+	jack_set_process_callback (d->client,jack_driver_process_wrapper, d);
+	jack_set_sample_rate_callback (d->client,jack_driver_sample_rate_callback, d);
 	jack_on_shutdown (d->client, jack_driver_server_has_shutdown, d);
 	
-	if (d->transport == JackDriverTransportIsMaster) {
-		if (jack_engine_takeover_timebase (d->client) != 0) { 
-			d->transport = JackDriverTransportIsInternal;
-		}
-	} else if (d->transport == JackDriverTransportIsSlave) {
-		// nothing?
+	if (jack_activate (d->client)) {
+		d->is_active = FALSE;
+	} else {
+		d->is_active = TRUE;
 	}
-
-	jack_activate (d->client);
-	d->is_active = TRUE;
-
 	return d;
 }
 
@@ -307,9 +363,10 @@
 	jack_driver *d = dp;
 
 	if (!d->is_active) {
-		// need a pop-up message, and bail out for now
+	        // TODO: need a pop-up message, and bail out for now
 		return FALSE;
 	}
+	jack_transport_start(d->client);
 	d->position = 0;
 	d->state = JackDriverStateIsRolling;
 	return TRUE;
@@ -319,40 +376,47 @@
 static void
 jack_driver_release (void *dp)
 {
-	// we fake it
 	jack_driver *d = dp;
-	d->state = JackDriverStateIsStopping;
+
+	jack_transport_stop(d->client);
+
+	pthread_mutex_lock (d->process_mx);
+	if (d->do_declick) {
+		d->state = JackDriverStateIsDeclicking;
+	} else {
+		d->state = JackDriverStateIsStopping;
+	}
+	pthread_cond_wait (d->state_cv,d->process_mx);
+	// at this point process() has set state to stopped
+	pthread_mutex_unlock (d->process_mx);
 }
 
 static void
 jack_driver_destroy (void *dp)
 {
 	jack_driver *d = dp;
-	
-	if (d->is_active)
+
+	printf("destroy in\n");
+
+	if (d->is_active) {
+		d->is_active = FALSE;
 		jack_client_close (d->client);
-	gtk_widget_destroy(d->configwidget);
-	g_mutex_free(d->configmutex);
-	free (d->mix);
+	}
+	gtk_widget_destroy (d->configwidget);
+	if (d->mix != NULL) {
+		free (d->mix);
+	}
+	pthread_mutex_destroy (d->process_mx);
+	pthread_cond_destroy (d->state_cv);
 	g_free(d);
-}
-
-static void
-jack_driver_update_status_label (jack_driver *d)
-{
-	char buf[64];
-	if (d->is_active) 
-		sprintf (buf, _("Running at %d Hz with %d frames"), (int)d->samplerate, (int)d->buffer_size);
-	else 
-		sprintf (buf, _("Jack server not running?"));
-	gtk_label_set_text (GTK_LABEL (d->status_label), buf);
+	printf("destroy out\n");
 }
 
 static GtkWidget *
 jack_driver_getwidget (void *dp)
 {
 	jack_driver *d = dp;
-	jack_driver_update_status_label (d);
+	jack_driver_prefs_update (d);
 	return d->configwidget;
 }
 
@@ -360,7 +424,9 @@
 jack_driver_loadsettings (void *dp, prefs_node *f)
 {
 	jack_driver *d = dp;
-	prefs_get_string (f, "jack_clientname", d->clientname);
+	// prefs_get_string (f, "jack_client_name", d->client_name);
+	prefs_get_int (f, "jack-declick", &(d->do_declick));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(d->declick_check), d->do_declick);
 	return TRUE;
 }
 
@@ -368,7 +434,8 @@
 jack_driver_savesettings (void *dp, prefs_node *f)
 {
 	jack_driver *d = dp;
-	prefs_put_string (f, "jack-clientname", d->clientname);
+	//	prefs_put_string (f, "jack-client_name", d->client_name);
+	prefs_put_int (f, "jack-declick", d->do_declick);
 	return TRUE;
 }
 
@@ -376,7 +443,7 @@
 jack_driver_get_play_time (void *dp)
 {
 	jack_driver * const d = dp;
-	return (double)d->position / (double)d->samplerate;
+	return (double)d->position / (double)d->sample_rate;
 }
 
 st_out_driver driver_out_jack = {
