1 /*-
2 * Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * ALTHOUGH THIS SOFTWARE IS MADE OF SCIENCE AND WIN, IT IS PROVIDED BY THE
15 * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18 * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
21 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *
26 */
27
28 /*
29 * This is jack-smf-player, Standard MIDI File player for JACK MIDI.
30 *
31 * For questions and comments, contact Edward Tomasz Napierala <trasz@FreeBSD.org>.
32 */
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <sys/types.h>
37 #include <sys/time.h>
38 #include <unistd.h>
39 #include <assert.h>
40 #include <string.h>
41 #include <sysexits.h>
42 #include <errno.h>
43 #include <signal.h>
44 #include <jack/jack.h>
45 #include <jack/midiport.h>
46 #include <glib.h>
47
48 #include "config.h"
49 #include "smf.h"
50
51 #ifdef WITH_LASH
52 #include <lash/lash.h>
53 #endif
54
55 #define PROGRAM_NAME "jack-smf-player"
56 #define PROGRAM_VERSION PACKAGE_VERSION
57
58 #define MIDI_CONTROLLER 0xB0
59 #define MIDI_ALL_SOUND_OFF 120
60
61 #define MAX_NUMBER_OF_TRACKS 128
62
63 jack_port_t *output_ports[MAX_NUMBER_OF_TRACKS];
64 int drop_messages = 0;
65 jack_client_t *jack_client = NULL;
66 double rate_limit = 0;
67 int just_one_output = 0;
68 int start_stopped = 0;
69 int use_transport = 1;
70 int be_quiet = 0;
71 volatile int playback_started = -1, song_position = 0, ctrl_c_pressed = 0;
72 smf_t *smf = NULL;
73
74 #ifdef WITH_LASH
75 lash_client_t *lash_client;
76 #endif
77
78 /* Will emit a warning if time between jack callbacks is longer than this. */
79 #define MAX_TIME_BETWEEN_CALLBACKS 0.1
80
81 /* Will emit a warning if execution of jack callback takes longer than this. */
82 #define MAX_PROCESSING_TIME 0.01
83
84 double
get_time(void)85 get_time(void)
86 {
87 double seconds;
88 int ret;
89 struct timeval tv;
90
91 ret = gettimeofday(&tv, NULL);
92
93 if (ret) {
94 perror("gettimeofday");
95 exit(EX_OSERR);
96 }
97
98 seconds = tv.tv_sec + tv.tv_usec / 1000000.0;
99
100 return seconds;
101 }
102
103 double
get_delta_time(void)104 get_delta_time(void)
105 {
106 static double previously = -1.0;
107 double now;
108 double delta;
109
110 now = get_time();
111
112 if (previously == -1.0) {
113 previously = now;
114
115 return 0;
116 }
117
118 delta = now - previously;
119 previously = now;
120
121 assert(delta >= 0.0);
122
123 return delta;
124 }
125
126 static gboolean
warning_async(gpointer s)127 warning_async(gpointer s)
128 {
129 const char *str = (const char *)s;
130
131 g_warning(str);
132
133 return FALSE;
134 }
135
136 static void
warn_from_jack_thread_context(const char * str)137 warn_from_jack_thread_context(const char *str)
138 {
139 g_idle_add(warning_async, (gpointer)str);
140 }
141
142 static double
nframes_to_ms(jack_nframes_t nframes)143 nframes_to_ms(jack_nframes_t nframes)
144 {
145 jack_nframes_t sr;
146
147 sr = jack_get_sample_rate(jack_client);
148
149 assert(sr > 0);
150
151 return (nframes * 1000.0) / (double)sr;
152 }
153
154 static double
nframes_to_seconds(jack_nframes_t nframes)155 nframes_to_seconds(jack_nframes_t nframes)
156 {
157 return nframes_to_ms(nframes) / 1000.0;
158 }
159
160 static jack_nframes_t
ms_to_nframes(double ms)161 ms_to_nframes(double ms)
162 {
163 jack_nframes_t sr;
164
165 sr = jack_get_sample_rate(jack_client);
166
167 assert(sr > 0);
168
169 return ((double)sr * ms) / 1000.0;
170 }
171
172 static jack_nframes_t
seconds_to_nframes(double seconds)173 seconds_to_nframes(double seconds)
174 {
175 return ms_to_nframes(seconds * 1000.0);
176 }
177
178 static void
send_all_sound_off(void * port_buffers[MAX_NUMBER_OF_TRACKS],jack_nframes_t nframes)179 send_all_sound_off(void *port_buffers[MAX_NUMBER_OF_TRACKS], jack_nframes_t nframes)
180 {
181 int i, channel;
182 unsigned char *buffer;
183
184 for (i = 0; i <= smf->number_of_tracks; i++) {
185 for (channel = 0; channel < 16; channel++) {
186 #ifdef JACK_MIDI_NEEDS_NFRAMES
187 buffer = jack_midi_event_reserve(port_buffers[i], 0, 3, nframes);
188 #else
189 buffer = jack_midi_event_reserve(port_buffers[i], 0, 3);
190 #endif
191 if (buffer == NULL) {
192 warn_from_jack_thread_context("jack_midi_event_reserve failed, cannot send All Sound Off.");
193 break;
194 }
195
196 buffer[0] = MIDI_CONTROLLER | channel;
197 buffer[1] = MIDI_ALL_SOUND_OFF;
198 buffer[2] = 0;
199 }
200
201 if (just_one_output)
202 break;
203 }
204 }
205
206 static void
process_midi_output(jack_nframes_t nframes)207 process_midi_output(jack_nframes_t nframes)
208 {
209 int i, t, bytes_remaining, track_number;
210 unsigned char *buffer, tmp_status;
211 void *port_buffers[MAX_NUMBER_OF_TRACKS];
212 jack_nframes_t last_frame_time;
213 jack_transport_state_t transport_state;
214 static jack_transport_state_t previous_transport_state = JackTransportStopped;
215
216 for (i = 0; i <= smf->number_of_tracks; i++) {
217 port_buffers[i] = jack_port_get_buffer(output_ports[i], nframes);
218
219 if (port_buffers[i] == NULL) {
220 warn_from_jack_thread_context("jack_port_get_buffer failed, cannot send anything.");
221 return;
222 }
223
224 #ifdef JACK_MIDI_NEEDS_NFRAMES
225 jack_midi_clear_buffer(port_buffers[i], nframes);
226 #else
227 jack_midi_clear_buffer(port_buffers[i]);
228 #endif
229
230 if (just_one_output)
231 break;
232 }
233
234 if (ctrl_c_pressed) {
235 send_all_sound_off(port_buffers, nframes);
236
237 /* The idea here is to exit at the second time process_midi_output gets called.
238 Otherwise, All Sound Off won't be delivered. */
239 ctrl_c_pressed++;
240 if (ctrl_c_pressed >= 3)
241 exit(0);
242
243 return;
244 }
245
246 if (use_transport) {
247 transport_state = jack_transport_query(jack_client, NULL);
248 if (transport_state == JackTransportStopped) {
249 if (previous_transport_state == JackTransportRolling)
250 send_all_sound_off(port_buffers, nframes);
251
252 previous_transport_state = transport_state;
253
254 return;
255 }
256
257 previous_transport_state = transport_state;
258 }
259
260 last_frame_time = jack_last_frame_time(jack_client);
261
262 /* End of song already? */
263 if (playback_started < 0)
264 return;
265
266 /* We may push at most one byte per 0.32ms to stay below 31.25 Kbaud limit. */
267 bytes_remaining = nframes_to_ms(nframes) * rate_limit;
268
269 for (;;) {
270 smf_event_t *event = smf_peek_next_event(smf);
271
272 if (event == NULL) {
273 if (!be_quiet)
274 g_debug("End of song.");
275 playback_started = -1;
276
277 if (!use_transport)
278 ctrl_c_pressed = 1;
279
280 break;
281 }
282
283 /* Skip over metadata events. */
284 if (smf_event_is_metadata(event)) {
285 char *decoded = smf_event_decode(event);
286 if (decoded && !be_quiet)
287 g_debug("Metadata: %s", decoded);
288
289 smf_get_next_event(smf);
290 continue;
291 }
292
293 bytes_remaining -= event->midi_buffer_length;
294
295 if (rate_limit > 0.0 && bytes_remaining <= 0) {
296 warn_from_jack_thread_context("Rate limiting in effect.");
297 break;
298 }
299
300 t = seconds_to_nframes(event->time_seconds) + playback_started - song_position + nframes - last_frame_time;
301
302 /* If computed time is too much into the future, we'll need
303 to send it later. */
304 if (t >= (int)nframes)
305 break;
306
307 /* If computed time is < 0, we missed a cycle because of xrun. */
308 if (t < 0)
309 t = 0;
310
311 assert(event->track->track_number >= 0 && event->track->track_number <= MAX_NUMBER_OF_TRACKS);
312
313 /* We will send this event; remove it from the queue. */
314 smf_get_next_event(smf);
315
316 /* First, send it via midi_out. */
317 track_number = 0;
318
319 #ifdef JACK_MIDI_NEEDS_NFRAMES
320 buffer = jack_midi_event_reserve(port_buffers[track_number], t, event->midi_buffer_length, nframes);
321 #else
322 buffer = jack_midi_event_reserve(port_buffers[track_number], t, event->midi_buffer_length);
323 #endif
324
325 if (buffer == NULL) {
326 warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST.");
327 break;
328 }
329
330 memcpy(buffer, event->midi_buffer, event->midi_buffer_length);
331
332 /* Ignore per-track outputs? */
333 if (just_one_output)
334 continue;
335
336 /* Send it via proper output port. */
337 track_number = event->track->track_number;
338
339 #ifdef JACK_MIDI_NEEDS_NFRAMES
340 buffer = jack_midi_event_reserve(port_buffers[track_number], t, event->midi_buffer_length, nframes);
341 #else
342 buffer = jack_midi_event_reserve(port_buffers[track_number], t, event->midi_buffer_length);
343 #endif
344
345 if (buffer == NULL) {
346 warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST.");
347 break;
348 }
349
350 /* Before sending, reset channel to 0. XXX: Not very pretty. */
351 assert(event->midi_buffer_length >= 1);
352
353 tmp_status = event->midi_buffer[0];
354
355 if (event->midi_buffer[0] >= 0x80 && event->midi_buffer[0] <= 0xEF)
356 event->midi_buffer[0] &= 0xF0;
357
358 memcpy(buffer, event->midi_buffer, event->midi_buffer_length);
359
360 event->midi_buffer[0] = tmp_status;
361 }
362 }
363
364 static int
process_callback(jack_nframes_t nframes,void * notused)365 process_callback(jack_nframes_t nframes, void *notused)
366 {
367 #ifdef MEASURE_TIME
368 if (get_delta_time() > MAX_TIME_BETWEEN_CALLBACKS) {
369 warn_from_jack_thread_context("Had to wait too long for JACK callback; scheduling problem?");
370 }
371 #endif
372
373 /* Check for impossible condition that actually happened to me, caused by some problem between jackd and OSS4. */
374 if (nframes <= 0) {
375 warn_from_jack_thread_context("Process callback called with nframes = 0; bug in JACK?");
376 return 0;
377 }
378
379 process_midi_output(nframes);
380
381 #ifdef MEASURE_TIME
382 if (get_delta_time() > MAX_PROCESSING_TIME) {
383 warn_from_jack_thread_context("Processing took too long; scheduling problem?");
384 }
385 #endif
386
387 return 0;
388 }
389
390 static int
sync_callback(jack_transport_state_t state,jack_position_t * position,void * notused)391 sync_callback(jack_transport_state_t state, jack_position_t *position, void *notused)
392 {
393 assert(jack_client);
394
395 /* XXX: We should probably adapt to external tempo changes. */
396
397 if (state == JackTransportStarting) {
398 song_position = position->frame;
399 smf_seek_to_seconds(smf, nframes_to_seconds(position->frame));
400
401 if (!be_quiet)
402 g_debug("Seeking to %f seconds.", nframes_to_seconds(position->frame));
403
404 playback_started = jack_frame_time(jack_client);
405
406 } else if (state == JackTransportStopped) {
407 playback_started = -1;
408 }
409
410 return TRUE;
411 }
412
timebase_callback(jack_transport_state_t state,jack_nframes_t nframes,jack_position_t * pos,int new_pos,void * notused)413 void timebase_callback(jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos, int new_pos, void *notused)
414 {
415 double min; /* Minutes since frame 0. */
416 long abs_tick; /* Ticks since frame 0. */
417 long abs_beat; /* Beats since frame 0. */
418 smf_tempo_t *tempo;
419 static smf_tempo_t *previous_tempo = NULL;
420
421 smf_event_t *event = smf_peek_next_event(smf);
422 if (event == NULL)
423 return;
424
425 tempo = smf_get_tempo_by_pulses(smf, event->time_pulses);
426
427 assert(tempo);
428
429 if (new_pos || previous_tempo != tempo) {
430 pos->valid = JackPositionBBT;
431 pos->beats_per_bar = tempo->numerator;
432 pos->beat_type = 1.0 / (double)tempo->denominator;
433 pos->ticks_per_beat = event->track->smf->ppqn; /* XXX: Is this right? */
434 pos->beats_per_minute = 60000000.0 / (double)tempo->microseconds_per_quarter_note;
435
436 min = pos->frame / ((double) pos->frame_rate * 60.0);
437 abs_tick = min * pos->beats_per_minute * pos->ticks_per_beat;
438 abs_beat = abs_tick / pos->ticks_per_beat;
439
440 pos->bar = abs_beat / pos->beats_per_bar;
441 pos->beat = abs_beat - (pos->bar * pos->beats_per_bar) + 1;
442 pos->tick = abs_tick - (abs_beat * pos->ticks_per_beat);
443 pos->bar_start_tick = pos->bar * pos->beats_per_bar * pos->ticks_per_beat;
444 pos->bar++; /* adjust start to bar 1 */
445
446 previous_tempo = tempo;
447
448 } else {
449 /* Compute BBT info based on previous period. */
450 pos->tick += nframes * pos->ticks_per_beat * pos->beats_per_minute / (pos->frame_rate * 60);
451
452 while (pos->tick >= pos->ticks_per_beat) {
453 pos->tick -= pos->ticks_per_beat;
454 if (++pos->beat > pos->beats_per_bar) {
455 pos->beat = 1;
456 ++pos->bar;
457 pos->bar_start_tick += pos->beats_per_bar * pos->ticks_per_beat;
458 }
459 }
460 }
461 }
462
463 /* Connects to the specified input port, disconnecting already connected ports. */
464 int
connect_to_input_port(const char * port)465 connect_to_input_port(const char *port)
466 {
467 int ret;
468
469 ret = jack_port_disconnect(jack_client, output_ports[0]);
470
471 if (ret) {
472 g_warning("Cannot disconnect MIDI port.");
473
474 return -3;
475 }
476
477 ret = jack_connect(jack_client, jack_port_name(output_ports[0]), port);
478
479 if (ret) {
480 g_warning("Cannot connect to %s.", port);
481
482 return -4;
483 }
484
485 g_warning("Connected to %s.", port);
486
487 return 0;
488 }
489
490 static void
init_jack(void)491 init_jack(void)
492 {
493 int i, err;
494
495 #ifdef WITH_LASH
496 lash_event_t *event;
497 #endif
498
499 jack_client = jack_client_open(PROGRAM_NAME, JackNullOption, NULL);
500
501 if (jack_client == NULL) {
502 g_critical("Could not connect to the JACK server; run jackd first?");
503 exit(EX_UNAVAILABLE);
504 }
505
506 #ifdef WITH_LASH
507 event = lash_event_new_with_type(LASH_Client_Name);
508 assert (event); /* Documentation does not say anything about return value. */
509 lash_event_set_string(event, jack_get_client_name(jack_client));
510 lash_send_event(lash_client, event);
511
512 lash_jack_client_name(lash_client, jack_get_client_name(jack_client));
513 #endif
514
515 err = jack_set_process_callback(jack_client, process_callback, 0);
516 if (err) {
517 g_critical("Could not register JACK process callback.");
518 exit(EX_UNAVAILABLE);
519 }
520
521 if (use_transport) {
522 err = jack_set_sync_callback(jack_client, sync_callback, 0);
523 if (err) {
524 g_critical("Could not register JACK sync callback.");
525 exit(EX_UNAVAILABLE);
526 }
527 #if 0
528 err = jack_set_timebase_callback(jack_client, 1, timebase_callback, 0);
529 if (err) {
530 g_critical("Could not register JACK timebase callback.");
531 exit(EX_UNAVAILABLE);
532 }
533 #endif
534 }
535
536 assert(smf->number_of_tracks >= 1);
537
538 /* We are allocating number_of_tracks + 1 output ports. */
539 for (i = 0; i <= smf->number_of_tracks; i++) {
540 char port_name[32];
541
542 if (i == 0)
543 snprintf(port_name, sizeof(port_name), "midi_out");
544 else
545 snprintf(port_name, sizeof(port_name), "track_%d_midi_out", i);
546
547 output_ports[i] = jack_port_register(jack_client, port_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
548
549 if (output_ports[i] == NULL) {
550 g_critical("Could not register JACK output port '%s'.", port_name);
551 exit(EX_UNAVAILABLE);
552 }
553
554 if (just_one_output)
555 break;
556 }
557
558 if (jack_activate(jack_client)) {
559 g_critical("Cannot activate JACK client.");
560 exit(EX_UNAVAILABLE);
561 }
562 }
563
564 #ifdef WITH_LASH
565
566 static gboolean
lash_callback(gpointer notused)567 lash_callback(gpointer notused)
568 {
569 lash_event_t *event;
570
571 while ((event = lash_get_event(lash_client))) {
572 switch (lash_event_get_type(event)) {
573 case LASH_Restore_Data_Set:
574 case LASH_Save_Data_Set:
575 break;
576
577 case LASH_Quit:
578 g_warning("Exiting due to LASH request.");
579 ctrl_c_pressed = 1;
580 break;
581
582 default:
583 g_warning("Receieved unknown LASH event of type %d.", lash_event_get_type(event));
584 lash_event_destroy(event);
585 }
586 }
587
588 return TRUE;
589 }
590
591 static void
init_lash(lash_args_t * args)592 init_lash(lash_args_t *args)
593 {
594 /* XXX: Am I doing the right thing wrt protocol version? */
595 lash_client = lash_init(args, PROGRAM_NAME, LASH_Config_Data_Set, LASH_PROTOCOL(2, 0));
596
597 if (!lash_server_connected(lash_client)) {
598 g_critical("Cannot initialize LASH. Continuing anyway.");
599 /* exit(EX_UNAVAILABLE); */
600
601 return;
602 }
603
604 /* Schedule a function to process LASH events, ten times per second. */
605 g_timeout_add(100, lash_callback, NULL);
606 }
607
608 #endif /* WITH_LASH */
609
610 /*
611 * This is neccessary for exiting due to jackd being killed, when exit(0)
612 * in process_callback won't get called for obvious reasons.
613 */
614 gboolean
emergency_exit_timeout(gpointer notused)615 emergency_exit_timeout(gpointer notused)
616 {
617 if (ctrl_c_pressed == 0)
618 return TRUE;
619
620 exit(0);
621 }
622
623 void
ctrl_c_handler(int signum)624 ctrl_c_handler(int signum)
625 {
626 ctrl_c_pressed = 1;
627 }
628
629 static void
log_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer notused)630 log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
631 {
632 fprintf(stderr, "%s: %s\n", log_domain, message);
633 }
634
635 static void
show_version(void)636 show_version(void)
637 {
638 fprintf(stdout, "%s %s, libsmf %s\n", PROGRAM_NAME, PROGRAM_VERSION, smf_get_version());
639
640 exit(EX_OK);
641 }
642
643 static void
usage(void)644 usage(void)
645 {
646 fprintf(stderr, "usage: jack-smf-player [-dnqstV] [ -a <input port>] [-r <rate>] file_name\n");
647
648 exit(EX_USAGE);
649 }
650
651 int
main(int argc,char * argv[])652 main(int argc, char *argv[])
653 {
654 int ch;
655 char *file_name, *autoconnect_port_name = NULL;
656
657 #ifdef WITH_LASH
658 lash_args_t *lash_args;
659 #endif
660
661 g_thread_init(NULL);
662
663 #ifdef WITH_LASH
664 lash_args = lash_extract_args(&argc, &argv);
665 #endif
666
667 g_log_set_default_handler(log_handler, NULL);
668
669 while ((ch = getopt(argc, argv, "a:dnqr:stV")) != -1) {
670 switch (ch) {
671 case 'a':
672 autoconnect_port_name = strdup(optarg);
673 break;
674
675 case 'd':
676 drop_messages = 1;
677 break;
678
679 case 'n':
680 start_stopped = 1;
681 break;
682
683 case 'q':
684 be_quiet = 1;
685 break;
686
687 case 'r':
688 rate_limit = strtod(optarg, NULL);
689 if (rate_limit <= 0.0) {
690 g_critical("Invalid rate limit specified.\n");
691
692 exit(EX_USAGE);
693 }
694
695 break;
696
697 case 's':
698 just_one_output = 1;
699 break;
700
701 case 't':
702 use_transport = 0;
703 break;
704
705 case 'V':
706 show_version();
707 break;
708
709 case '?':
710 default:
711 usage();
712 }
713 }
714
715 argc -= optind;
716 argv += optind;
717
718 if (argv[0] == NULL) {
719 g_critical("No file name given.");
720 usage();
721 }
722
723 file_name = argv[0];
724
725 smf = smf_load(file_name);
726
727 if (smf == NULL) {
728 g_critical("Loading SMF file failed.");
729
730 exit(-1);
731 }
732
733 if (!be_quiet)
734 g_message("%s.", smf_decode(smf));
735
736 if (smf->number_of_tracks > MAX_NUMBER_OF_TRACKS) {
737 g_warning("Number of tracks (%d) exceeds maximum for per-track output; implying '-s' option.", smf->number_of_tracks);
738 just_one_output = 1;
739 }
740
741 #ifdef WITH_LASH
742 init_lash(lash_args);
743 #endif
744
745 g_timeout_add(1000, emergency_exit_timeout, (gpointer)0);
746 signal(SIGINT, ctrl_c_handler);
747
748 init_jack();
749
750 if (autoconnect_port_name) {
751 if (connect_to_input_port(autoconnect_port_name)) {
752 g_critical("Couldn't connect to '%s', exiting.", autoconnect_port_name);
753 exit(EX_UNAVAILABLE);
754 }
755 }
756
757 if (use_transport && !start_stopped) {
758 jack_transport_locate(jack_client, 0);
759 jack_transport_start(jack_client);
760 }
761
762 if (!use_transport)
763 playback_started = jack_frame_time(jack_client);
764
765 g_main_loop_run(g_main_loop_new(NULL, TRUE));
766
767 /* Not reached. */
768
769 return 0;
770 }
771
772