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