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 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 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 void signal_handler(int sig
)
122 jack_client_close(client
);
123 fprintf(stderr
, "signal received, exiting ...\n");
128 /* Command functions: see commands[] table following. */
130 void com_activate(char *arg
)
132 if (jack_activate(client
)) {
133 fprintf(stderr
, "cannot activate client");
137 void com_deactivate(char *arg
)
139 if (jack_deactivate(client
)) {
140 fprintf(stderr
, "cannot deactivate client");
144 void com_exit(char *arg
)
149 void com_help(char *); /* forward declaration */
151 void com_locate(char *arg
)
153 jack_nframes_t frame
= 0;
158 jack_transport_locate(client
, frame
);
161 void com_master(char *arg
)
163 int cond
= (*arg
!= '\0');
164 if (jack_set_timebase_callback(client
, cond
, timebase
, NULL
) != 0)
165 fprintf(stderr
, "Unable to take over timebase.\n");
168 void com_play(char *arg
)
170 jack_transport_start(client
);
173 void com_release(char *arg
)
175 jack_release_timebase(client
);
178 void com_stop(char *arg
)
180 jack_transport_stop(client
);
183 /* Change the tempo for the entire timeline, not just from the current
185 void com_tempo(char *arg
)
192 time_beats_per_minute
= tempo
;
196 /* Set sync timeout in seconds. */
197 void com_timeout(char *arg
)
199 double timeout
= 2.0;
204 jack_set_sync_timeout(client
, (jack_time_t
) (timeout
*1000000));
208 /* Command parsing based on GNU readline info examples. */
210 typedef void cmd_function_t(char *); /* command function type */
212 /* Transport command table. */
214 char *name
; /* user printable name */
215 cmd_function_t
*func
; /* function to call */
216 char *doc
; /* documentation */
219 /* command table must be in alphabetical order */
220 command_t commands
[] = {
221 {"activate", com_activate
, "Call jack_activate()"},
222 {"exit", com_exit
, "Exit transport program"},
223 {"deactivate", com_deactivate
, "Call jack_deactivate()"},
224 {"help", com_help
, "Display help text [<command>]"},
225 {"locate", com_locate
, "Locate to frame <position>"},
226 {"master", com_master
, "Become timebase master "
227 "[<conditionally>]"},
228 {"play", com_play
, "Start transport rolling"},
229 {"quit", com_exit
, "Synonym for `exit'"},
230 {"release", com_release
, "Release timebase"},
231 {"stop", com_stop
, "Stop transport"},
232 {"tempo", com_tempo
, "Set beat tempo <beats_per_min>"},
233 {"timeout", com_timeout
, "Set sync timeout in <seconds>"},
234 {"?", com_help
, "Synonym for `help'" },
235 {(char *)NULL
, (cmd_function_t
*)NULL
, (char *)NULL
}
238 command_t
*find_command(char *name
)
243 if ((name
== NULL
) || (*name
== '\0'))
244 return ((command_t
*)NULL
);
246 namelen
= strlen(name
);
247 for (i
= 0; commands
[i
].name
; i
++)
248 if (strncmp(name
, commands
[i
].name
, namelen
) == 0) {
250 /* make sure the match is unique */
251 if ((commands
[i
+1].name
) &&
252 (strncmp(name
, commands
[i
+1].name
, namelen
) == 0))
253 return ((command_t
*)NULL
);
255 return (&commands
[i
]);
258 return ((command_t
*)NULL
);
261 void com_help(char *arg
)
267 /* print help for all commands */
268 for (i
= 0; commands
[i
].name
; i
++) {
269 printf("%s\t\t%s.\n", commands
[i
].name
,
273 } else if ((cmd
= find_command(arg
))) {
274 printf("%s\t\t%s.\n", cmd
->name
, cmd
->doc
);
279 printf("No `%s' command. Valid command names are:\n", arg
);
281 for (i
= 0; commands
[i
].name
; i
++) {
282 /* Print in six columns. */
288 printf ("%s\t", commands
[i
].name
);
292 printf("\n\nTry `help [command]\' for more information.\n");
296 void execute_command(char *line
)
302 /* Isolate the command word. */
304 while (line
[i
] && whitespace(line
[i
]))
308 while (line
[i
] && !whitespace(line
[i
]))
314 command
= find_command(word
);
317 fprintf(stderr
, "%s: No such command. There is `help\'.\n",
322 /* Get argument to command, if any. */
323 while (whitespace(line
[i
]))
328 /* invoke the command function. */
329 (*command
->func
)(word
);
333 /* Strip whitespace from the start and end of string. */
334 char *stripwhite(char *string
)
336 register char *s
, *t
;
339 while (whitespace(*s
))
345 t
= s
+ strlen (s
) - 1;
346 while (t
> s
&& whitespace(*t
))
353 char *dupstr(char *s
)
355 char *r
= malloc(strlen(s
) + 1);
360 /* Readline generator function for command completion. */
361 char *command_generator (const char *text
, int state
)
363 static int list_index
, len
;
366 /* If this is a new word to complete, initialize now. This
367 includes saving the length of TEXT for efficiency, and
368 initializing the index variable to 0. */
374 /* Return the next name which partially matches from the
376 while ((name
= commands
[list_index
].name
)) {
379 if (strncmp(name
, text
, len
) == 0)
383 return (char *) NULL
; /* No names matched. */
391 snprintf(prompt
, sizeof(prompt
), "%s> ", package
);
393 /* Allow conditional parsing of the ~/.inputrc file. */
394 rl_readline_name
= package
;
396 /* Define a custom completion function. */
397 rl_completion_entry_function
= command_generator
;
399 /* Read and execute commands until the user quits. */
402 line
= readline(prompt
);
404 if (line
== NULL
) { /* EOF? */
405 printf("\n"); /* close out prompt */
410 /* Remove leading and trailing whitespace from the line. */
411 cmd
= stripwhite(line
);
413 /* If anything left, add to history and execute it. */
417 execute_command(cmd
);
420 free(line
); /* realine() called malloc() */
424 int main(int argc
, char *argv
[])
426 jack_status_t status
;
429 package
= strrchr(argv
[0], '/');
435 /* open a connection to the JACK server */
436 client
= jack_client_open (package
, JackNullOption
, &status
);
437 if (client
== NULL
) {
438 fprintf (stderr
, "jack_client_open() failed, "
439 "status = 0x%2.0x\n", status
);
443 signal(SIGQUIT
, signal_handler
);
444 signal(SIGTERM
, signal_handler
);
445 signal(SIGHUP
, signal_handler
);
446 signal(SIGINT
, signal_handler
);
448 jack_on_shutdown(client
, jack_shutdown
, 0);
450 if (jack_activate(client
)) {
451 fprintf(stderr
, "cannot activate client");
455 /* execute commands until done */
458 jack_client_close(client
);