Import of jack-smf-utils 1.0.
[jack-smf-utils.git] / src / jack-smf-recorder.c
blobb599964a2bdb7eaf1aad6a9bd6caf208cbb783ac
1 /*-
2 * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa <trasz@FreeBSD.org>
3 * All rights reserved.
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.
14 * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, 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.
29 * This is jack-smf-recorder, Standard MIDI File recorder for JACK MIDI.
31 * For questions and comments, contact Edward Tomasz Napierala <trasz@FreeBSD.org>.
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>
48 #include "config.h"
49 #include "smf.h"
51 #ifdef WITH_LASH
52 #include <lash/lash.h>
53 #endif
55 #define INPUT_PORT_NAME "midi_in"
56 #define PROGRAM_NAME "jack-smf-recorder"
57 #define PROGRAM_VERSION PACKAGE_VERSION
59 jack_client_t *jack_client = NULL;
60 jack_port_t *input_port;
61 volatile int ctrl_c_pressed = 0;
62 smf_t *smf = NULL;
63 smf_track_t *tracks[16]; /* We allocate one track per MIDI channel. */
65 #ifdef WITH_LASH
66 lash_client_t *lash_client;
67 #endif
69 /* Will emit a warning if time between jack callbacks is longer than this. */
70 #define MAX_TIME_BETWEEN_CALLBACKS 0.1
72 /* Will emit a warning if execution of jack callback takes longer than this. */
73 #define MAX_PROCESSING_TIME 0.01
75 double
76 get_time(void)
78 double seconds;
79 int ret;
80 struct timeval tv;
82 ret = gettimeofday(&tv, NULL);
84 if (ret) {
85 perror("gettimeofday");
86 exit(EX_OSERR);
89 seconds = tv.tv_sec + tv.tv_usec / 1000000.0;
91 return seconds;
94 double
95 get_delta_time(void)
97 static double previously = -1.0;
98 double now;
99 double delta;
101 now = get_time();
103 if (previously == -1.0) {
104 previously = now;
106 return 0;
109 delta = now - previously;
110 previously = now;
112 assert(delta >= 0.0);
114 return delta;
117 static gboolean
118 warning_async(gpointer s)
120 const char *str = (const char *)s;
122 g_warning(str);
124 return FALSE;
127 static void
128 warn_from_jack_thread_context(const char *str)
130 g_idle_add(warning_async, (gpointer)str);
133 static double
134 nframes_to_ms(jack_nframes_t nframes)
136 jack_nframes_t sr;
138 sr = jack_get_sample_rate(jack_client);
140 assert(sr > 0);
142 return (nframes * 1000.0) / (double)sr;
145 static double
146 nframes_to_seconds(jack_nframes_t nframes)
148 return nframes_to_ms(nframes) / 1000.0;
151 void
152 process_midi_input(jack_nframes_t nframes)
154 int read, events, i, channel;
155 void *port_buffer;
156 jack_midi_event_t event;
157 int last_frame_time;
158 static int time_of_first_event = -1;
160 last_frame_time = jack_last_frame_time(jack_client);
162 port_buffer = jack_port_get_buffer(input_port, nframes);
163 if (port_buffer == NULL) {
164 warn_from_jack_thread_context("jack_port_get_buffer failed, cannot receive anything.");
165 return;
168 #ifdef JACK_MIDI_NEEDS_NFRAMES
169 events = jack_midi_get_event_count(port_buffer, nframes);
170 #else
171 events = jack_midi_get_event_count(port_buffer);
172 #endif
174 for (i = 0; i < events; i++) {
175 smf_event_t *smf_event;
177 #ifdef JACK_MIDI_NEEDS_NFRAMES
178 read = jack_midi_event_get(&event, port_buffer, i, nframes);
179 #else
180 read = jack_midi_event_get(&event, port_buffer, i);
181 #endif
182 if (read) {
183 warn_from_jack_thread_context("jack_midi_event_get failed, RECEIVED NOTE LOST.");
184 continue;
187 /* Ignore realtime messages. */
188 if (event.buffer[0] >= 0xF8)
189 continue;
191 /* First event received? */
192 if (time_of_first_event == -1)
193 time_of_first_event = last_frame_time + event.time;
195 smf_event = smf_event_new_from_pointer(event.buffer, event.size);
196 if (smf_event == NULL) {
197 warn_from_jack_thread_context("smf_event_from_pointer failed, RECEIVED NOTE LOST.");
198 continue;
201 assert(smf_event->midi_buffer_length >= 1);
202 channel = smf_event->midi_buffer[0] & 0x0F;
204 smf_track_add_event_seconds(tracks[channel], smf_event,
205 nframes_to_seconds(jack_last_frame_time(jack_client) + event.time - time_of_first_event));
209 static int
210 process_callback(jack_nframes_t nframes, void *notused)
212 #ifdef MEASURE_TIME
213 if (get_delta_time() > MAX_TIME_BETWEEN_CALLBACKS) {
214 warn_from_jack_thread_context("Had to wait too long for JACK callback; scheduling problem?");
216 #endif
218 /* Check for impossible condition that actually happened to me, caused by some problem between jackd and OSS4. */
219 if (nframes <= 0) {
220 warn_from_jack_thread_context("Process callback called with nframes = 0; bug in JACK?");
221 return 0;
224 process_midi_input(nframes);
226 #ifdef MEASURE_TIME
227 if (get_delta_time() > MAX_PROCESSING_TIME) {
228 warn_from_jack_thread_context("Processing took too long; scheduling problem?");
230 #endif
232 return 0;
235 /* Connects to the specified input port, disconnecting already connected ports. */
237 connect_to_output_port(const char *port)
239 int ret;
241 ret = jack_port_disconnect(jack_client, input_port);
243 if (ret) {
244 g_warning("Cannot disconnect MIDI port.");
246 return -3;
249 ret = jack_connect(jack_client, port, jack_port_name(input_port));
251 if (ret) {
252 g_warning("Cannot connect to %s.", port);
254 return -4;
257 g_warning("Connected to %s.", port);
259 return 0;
262 void
263 init_jack(void)
265 int err;
267 #ifdef WITH_LASH
268 lash_event_t *event;
269 #endif
271 jack_client = jack_client_open(PROGRAM_NAME, JackNullOption, NULL);
273 if (jack_client == NULL) {
274 g_critical("Could not connect to the JACK server; run jackd first?");
275 exit(EX_UNAVAILABLE);
278 #ifdef WITH_LASH
279 event = lash_event_new_with_type(LASH_Client_Name);
280 assert (event); /* Documentation does not say anything about return value. */
281 lash_event_set_string(event, jack_get_client_name(jack_client));
282 lash_send_event(lash_client, event);
284 lash_jack_client_name(lash_client, jack_get_client_name(jack_client));
285 #endif
287 err = jack_set_process_callback(jack_client, process_callback, 0);
288 if (err) {
289 g_critical("Could not register JACK process callback.");
290 exit(EX_UNAVAILABLE);
293 input_port = jack_port_register(jack_client, INPUT_PORT_NAME, JACK_DEFAULT_MIDI_TYPE,
294 JackPortIsInput, 0);
296 if (input_port == NULL) {
297 g_critical("Could not register JACK input port.");
298 exit(EX_UNAVAILABLE);
301 if (jack_activate(jack_client)) {
302 g_critical("Cannot activate JACK client.");
303 exit(EX_UNAVAILABLE);
307 #ifdef WITH_LASH
309 static gboolean
310 lash_callback(gpointer notused)
312 lash_event_t *event;
314 while ((event = lash_get_event(lash_client))) {
315 switch (lash_event_get_type(event)) {
316 case LASH_Restore_Data_Set:
317 case LASH_Save_Data_Set:
318 break;
320 case LASH_Quit:
321 g_warning("Exiting due to LASH request.");
322 ctrl_c_pressed = 1;
323 break;
325 default:
326 g_warning("Receieved unknown LASH event of type %d.", lash_event_get_type(event));
327 lash_event_destroy(event);
331 return TRUE;
334 static void
335 init_lash(lash_args_t *args)
337 /* XXX: Am I doing the right thing wrt protocol version? */
338 lash_client = lash_init(args, PROGRAM_NAME, LASH_Config_Data_Set, LASH_PROTOCOL(2, 0));
340 if (!lash_server_connected(lash_client)) {
341 g_critical("Cannot initialize LASH. Continuing anyway.");
342 /* exit(EX_UNAVAILABLE); */
344 return;
347 /* Schedule a function to process LASH events, ten times per second. */
348 g_timeout_add(100, lash_callback, NULL);
351 #endif /* WITH_LASH */
353 gboolean
354 writer_timeout(gpointer file_name_gpointer)
356 int i;
357 char *file_name = (char *)file_name_gpointer;
360 * XXX: It should be done like this: http://wwwtcs.inf.tu-dresden.de/~tews/Gtk/x2992.html
362 if (ctrl_c_pressed == 0)
363 return TRUE;
365 jack_deactivate(jack_client);
367 /* Get rid of empty tracks. */
368 smf_rewind(smf);
370 for (i = 0; i < 16; i++) {
371 if (tracks[i]->number_of_events == 0) {
372 smf_remove_track(tracks[i]);
373 smf_track_delete(tracks[i]);
377 if (smf->number_of_tracks == 0) {
378 g_message("No events recorded, not saving anything.");
379 exit(0);
382 if (smf_save(smf, file_name)) {
383 g_critical("Could not save file '%s', sorry.", file_name);
384 exit(-1);
387 g_message("File '%s' saved successfully.", file_name);
389 exit(0);
392 void
393 ctrl_c_handler(int signum)
395 ctrl_c_pressed = 1;
398 static void
399 log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
401 fprintf(stderr, "%s: %s\n", log_domain, message);
404 static void
405 show_version(void)
407 fprintf(stdout, "%s %s, libsmf %s\n", PROGRAM_NAME, PROGRAM_VERSION, smf_get_version());
409 exit(EX_OK);
412 static void
413 usage(void)
415 fprintf(stderr, "usage: jack-smf-recorder [-V] [ -a <output port>] file_name\n");
417 exit(EX_USAGE);
420 int
421 main(int argc, char *argv[])
423 int ch, i;
424 char *file_name, *autoconnect_port_name = NULL;
426 #ifdef WITH_LASH
427 lash_args_t *lash_args;
428 #endif
430 g_thread_init(NULL);
432 #ifdef WITH_LASH
433 lash_args = lash_extract_args(&argc, &argv);
434 #endif
436 g_log_set_default_handler(log_handler, NULL);
438 while ((ch = getopt(argc, argv, "a:V")) != -1) {
439 switch (ch) {
440 case 'a':
441 autoconnect_port_name = strdup(optarg);
442 break;
444 case 'V':
445 show_version();
446 break;
448 case '?':
449 default:
450 usage();
454 argc -= optind;
455 argv += optind;
457 if (argc == 0)
458 usage();
460 file_name = argv[0];
462 smf = smf_new();
464 if (smf == NULL)
465 exit(-1);
467 for (i = 0; i < 16; i++) {
468 tracks[i] = smf_track_new();
469 if (tracks[i] == NULL)
470 exit(-1);
471 smf_add_track(smf, tracks[i]);
474 #ifdef WITH_LASH
475 init_lash(lash_args);
476 #endif
478 init_jack();
480 if (autoconnect_port_name) {
481 if (connect_to_output_port(autoconnect_port_name)) {
482 g_critical("Couldn't connect to '%s', exiting.", autoconnect_port_name);
483 exit(EX_UNAVAILABLE);
487 g_timeout_add(100, writer_timeout, (gpointer)argv[0]);
488 signal(SIGINT, ctrl_c_handler);
490 g_message("Recording will start at the first received note; press ^C to write the file and exit.");
492 g_main_loop_run(g_main_loop_new(NULL, TRUE));
494 /* Not reached. */
496 return 0;