2 * transport.c -- JACK transport master example client.
4 * Copyright (C) 2003 Jack O'Quin.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <readline/readline.h>
28 #include <readline/history.h>
29 #include <jack/jack.h>
30 #include <jack/transport.h>
33 char *package
; /* program name */
35 jack_client_t
*client
;
37 /* Time and tempo variables. These are global to the entire,
38 * transport timeline. There is no attempt to keep a true tempo map.
39 * The default time signature is: "march time", 4/4, 120bpm
41 float time_beats_per_bar
= 4.0;
42 float time_beat_type
= 4.0;
43 double time_ticks_per_beat
= 1920.0;
44 double time_beats_per_minute
= 120.0;
45 volatile int time_reset
= 1; /* true when time values change */
47 /* JACK timebase callback.
49 * Runs in the process thread. Realtime, must not wait.
51 static void timebase(jack_transport_state_t state
, jack_nframes_t nframes
,
52 jack_position_t
*pos
, int new_pos
, void *arg
)
54 double min
; /* minutes since frame 0 */
55 long abs_tick
; /* ticks since frame 0 */
56 long abs_beat
; /* beats since frame 0 */
58 if (new_pos
|| time_reset
) {
60 pos
->valid
= JackPositionBBT
;
61 pos
->beats_per_bar
= time_beats_per_bar
;
62 pos
->beat_type
= time_beat_type
;
63 pos
->ticks_per_beat
= time_ticks_per_beat
;
64 pos
->beats_per_minute
= time_beats_per_minute
;
66 time_reset
= 0; /* time change complete */
68 /* Compute BBT info from frame number. This is relatively
69 * simple here, but would become complex if we supported tempo
70 * or time signature changes at specific locations in the
71 * transport timeline. */
73 min
= pos
->frame
/ ((double) pos
->frame_rate
* 60.0);
74 abs_tick
= min
* pos
->beats_per_minute
* pos
->ticks_per_beat
;
75 abs_beat
= abs_tick
/ pos
->ticks_per_beat
;
77 pos
->bar
= abs_beat
/ pos
->beats_per_bar
;
78 pos
->beat
= abs_beat
- (pos
->bar
* pos
->beats_per_bar
) + 1;
79 pos
->tick
= abs_tick
- (abs_beat
* pos
->ticks_per_beat
);
80 pos
->bar_start_tick
= pos
->bar
* pos
->beats_per_bar
*
82 pos
->bar
++; /* adjust start to bar 1 */
85 /* some debug code... */
86 fprintf(stderr
, "\nnew position: %" PRIu32
"\tBBT: %3"
87 PRIi32
"|%" PRIi32
"|%04" PRIi32
"\n",
88 pos
->frame
, pos
->bar
, pos
->beat
, pos
->tick
);
93 /* Compute BBT info based on previous period. */
95 nframes
* pos
->ticks_per_beat
* pos
->beats_per_minute
96 / (pos
->frame_rate
* 60);
98 while (pos
->tick
>= pos
->ticks_per_beat
) {
99 pos
->tick
-= pos
->ticks_per_beat
;
100 if (++pos
->beat
> pos
->beats_per_bar
) {
103 pos
->bar_start_tick
+=
105 * pos
->ticks_per_beat
;
111 static void jack_shutdown(void *arg
)
113 #if defined(RL_READLINE_VERSION) && RL_READLINE_VERSION >= 0x0400
114 rl_cleanup_after_signal();
116 fprintf(stderr
, "JACK shut down, exiting ...\n");
120 static void signal_handler(int sig
)
122 jack_client_close(client
);
123 fprintf(stderr
, "signal received, exiting ...\n");
127 /* Command functions: see commands[] table following. */
129 static void com_activate(char *arg
)
131 if (jack_activate(client
)) {
132 fprintf(stderr
, "cannot activate client");
136 static void com_deactivate(char *arg
)
138 if (jack_deactivate(client
)) {
139 fprintf(stderr
, "cannot deactivate client");
143 static void com_exit(char *arg
)
148 static void com_help(char *); /* forward declaration */
150 static void com_locate(char *arg
)
152 jack_nframes_t frame
= 0;
157 jack_transport_locate(client
, frame
);
160 static void com_master(char *arg
)
162 int cond
= (*arg
!= '\0');
163 if (jack_set_timebase_callback(client
, cond
, timebase
, NULL
) != 0)
164 fprintf(stderr
, "Unable to take over timebase.\n");
167 static void com_play(char *arg
)
169 jack_transport_start(client
);
172 static void com_release(char *arg
)
174 jack_release_timebase(client
);
177 static void com_stop(char *arg
)
179 jack_transport_stop(client
);
182 /* Change the tempo for the entire timeline, not just from the current
184 static void com_tempo(char *arg
)
191 time_beats_per_minute
= tempo
;
195 /* Set sync timeout in seconds. */
196 static void com_timeout(char *arg
)
198 double timeout
= 2.0;
203 jack_set_sync_timeout(client
, (jack_time_t
) (timeout
*1000000));
207 /* Command parsing based on GNU readline info examples. */
209 typedef void cmd_function_t(char *); /* command function type */
211 /* Transport command table. */
213 char *name
; /* user printable name */
214 cmd_function_t
*func
; /* function to call */
215 char *doc
; /* documentation */
218 /* command table must be in alphabetical order */
219 command_t commands
[] = {
220 {"activate", com_activate
, "Call jack_activate()"},
221 {"exit", com_exit
, "Exit transport program"},
222 {"deactivate", com_deactivate
, "Call jack_deactivate()"},
223 {"help", com_help
, "Display help text [<command>]"},
224 {"locate", com_locate
, "Locate to frame <position>"},
225 {"master", com_master
, "Become timebase master "
226 "[<conditionally>]"},
227 {"play", com_play
, "Start transport rolling"},
228 {"quit", com_exit
, "Synonym for `exit'"},
229 {"release", com_release
, "Release timebase"},
230 {"stop", com_stop
, "Stop transport"},
231 {"tempo", com_tempo
, "Set beat tempo <beats_per_min>"},
232 {"timeout", com_timeout
, "Set sync timeout in <seconds>"},
233 {"?", com_help
, "Synonym for `help'" },
234 {(char *)NULL
, (cmd_function_t
*)NULL
, (char *)NULL
}
237 static command_t
*find_command(char *name
)
242 if ((name
== NULL
) || (*name
== '\0'))
243 return ((command_t
*)NULL
);
245 namelen
= strlen(name
);
246 for (i
= 0; commands
[i
].name
; i
++)
247 if (strncmp(name
, commands
[i
].name
, namelen
) == 0) {
249 /* make sure the match is unique */
250 if ((commands
[i
+1].name
) &&
251 (strncmp(name
, commands
[i
+1].name
, namelen
) == 0))
252 return ((command_t
*)NULL
);
254 return (&commands
[i
]);
257 return ((command_t
*)NULL
);
260 static void com_help(char *arg
)
266 /* print help for all commands */
267 for (i
= 0; commands
[i
].name
; i
++) {
268 printf("%s\t\t%s.\n", commands
[i
].name
,
272 } else if ((cmd
= find_command(arg
))) {
273 printf("%s\t\t%s.\n", cmd
->name
, cmd
->doc
);
278 printf("No `%s' command. Valid command names are:\n", arg
);
280 for (i
= 0; commands
[i
].name
; i
++) {
281 /* Print in six columns. */
287 printf ("%s\t", commands
[i
].name
);
291 printf("\n\nTry `help [command]\' for more information.\n");
295 static void execute_command(char *line
)
301 /* Isolate the command word. */
303 while (line
[i
] && whitespace(line
[i
]))
307 while (line
[i
] && !whitespace(line
[i
]))
313 command
= find_command(word
);
316 fprintf(stderr
, "%s: No such command. There is `help\'.\n",
321 /* Get argument to command, if any. */
322 while (whitespace(line
[i
]))
327 /* invoke the command function. */
328 (*command
->func
)(word
);
332 /* Strip whitespace from the start and end of string. */
333 static char *stripwhite(char *string
)
335 register char *s
, *t
;
338 while (whitespace(*s
))
344 t
= s
+ strlen (s
) - 1;
345 while (t
> s
&& whitespace(*t
))
352 static char *dupstr(char *s
)
354 char *r
= malloc(strlen(s
) + 1);
359 /* Readline generator function for command completion. */
360 static char *command_generator (const char *text
, int state
)
362 static int list_index
, len
;
365 /* If this is a new word to complete, initialize now. This
366 includes saving the length of TEXT for efficiency, and
367 initializing the index variable to 0. */
373 /* Return the next name which partially matches from the
375 while ((name
= commands
[list_index
].name
)) {
378 if (strncmp(name
, text
, len
) == 0)
382 return (char *) NULL
; /* No names matched. */
385 static void command_loop()
390 snprintf(prompt
, sizeof(prompt
), "%s> ", package
);
392 /* Allow conditional parsing of the ~/.inputrc file. */
393 rl_readline_name
= package
;
395 /* Define a custom completion function. */
396 rl_completion_entry_function
= command_generator
;
398 /* Read and execute commands until the user quits. */
401 line
= readline(prompt
);
403 if (line
== NULL
) { /* EOF? */
404 printf("\n"); /* close out prompt */
409 /* Remove leading and trailing whitespace from the line. */
410 cmd
= stripwhite(line
);
412 /* If anything left, add to history and execute it. */
416 execute_command(cmd
);
419 free(line
); /* realine() called malloc() */
423 int main(int argc
, char *argv
[])
425 jack_status_t status
;
428 package
= strrchr(argv
[0], '/');
434 /* open a connection to the JACK server */
435 client
= jack_client_open (package
, JackNullOption
, &status
);
436 if (client
== NULL
) {
437 fprintf (stderr
, "jack_client_open() failed, "
438 "status = 0x%2.0x\n", status
);
442 signal(SIGQUIT
, signal_handler
);
443 signal(SIGTERM
, signal_handler
);
444 signal(SIGHUP
, signal_handler
);
445 signal(SIGINT
, signal_handler
);
447 jack_on_shutdown(client
, jack_shutdown
, 0);
449 if (jack_activate(client
)) {
450 fprintf(stderr
, "cannot activate client");
454 /* execute commands until done */
457 jack_client_close(client
);