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-recorder, Standard MIDI File recorder 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 INPUT_PORT_NAME "midi_in"
56 #define PROGRAM_NAME "jack-smf-recorder"
57 #define PROGRAM_VERSION PACKAGE_VERSION
58
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. */
64
65 #ifdef WITH_LASH
66 lash_client_t *lash_client;
67 #endif
68
69 /* Will emit a warning if time between jack callbacks is longer than this. */
70 #define MAX_TIME_BETWEEN_CALLBACKS 0.1
71
72 /* Will emit a warning if execution of jack callback takes longer than this. */
73 #define MAX_PROCESSING_TIME 0.01
74
75 double
get_time(void)76 get_time(void)
77 {
78 double seconds;
79 int ret;
80 struct timeval tv;
81
82 ret = gettimeofday(&tv, NULL);
83
84 if (ret) {
85 perror("gettimeofday");
86 exit(EX_OSERR);
87 }
88
89 seconds = tv.tv_sec + tv.tv_usec / 1000000.0;
90
91 return seconds;
92 }
93
94 double
get_delta_time(void)95 get_delta_time(void)
96 {
97 static double previously = -1.0;
98 double now;
99 double delta;
100
101 now = get_time();
102
103 if (previously == -1.0) {
104 previously = now;
105
106 return 0;
107 }
108
109 delta = now - previously;
110 previously = now;
111
112 assert(delta >= 0.0);
113
114 return delta;
115 }
116
117 static gboolean
warning_async(gpointer s)118 warning_async(gpointer s)
119 {
120 const char *str = (const char *)s;
121
122 g_warning(str);
123
124 return FALSE;
125 }
126
127 static void
warn_from_jack_thread_context(const char * str)128 warn_from_jack_thread_context(const char *str)
129 {
130 g_idle_add(warning_async, (gpointer)str);
131 }
132
133 static double
nframes_to_ms(jack_nframes_t nframes)134 nframes_to_ms(jack_nframes_t nframes)
135 {
136 jack_nframes_t sr;
137
138 sr = jack_get_sample_rate(jack_client);
139
140 assert(sr > 0);
141
142 return (nframes * 1000.0) / (double)sr;
143 }
144
145 static double
nframes_to_seconds(jack_nframes_t nframes)146 nframes_to_seconds(jack_nframes_t nframes)
147 {
148 return nframes_to_ms(nframes) / 1000.0;
149 }
150
151 void
process_midi_input(jack_nframes_t nframes)152 process_midi_input(jack_nframes_t nframes)
153 {
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;
159
160 last_frame_time = jack_last_frame_time(jack_client);
161
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;
166 }
167
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
173
174 for (i = 0; i < events; i++) {
175 smf_event_t *smf_event;
176
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;
185 }
186
187 /* Ignore realtime messages. */
188 if (event.buffer[0] >= 0xF8)
189 continue;
190
191 /* First event received? */
192 if (time_of_first_event == -1)
193 time_of_first_event = last_frame_time + event.time;
194
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;
199 }
200
201 assert(smf_event->midi_buffer_length >= 1);
202 channel = smf_event->midi_buffer[0] & 0x0F;
203
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));
206 }
207 }
208
209 static int
process_callback(jack_nframes_t nframes,void * notused)210 process_callback(jack_nframes_t nframes, void *notused)
211 {
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?");
215 }
216 #endif
217
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;
222 }
223
224 process_midi_input(nframes);
225
226 #ifdef MEASURE_TIME
227 if (get_delta_time() > MAX_PROCESSING_TIME) {
228 warn_from_jack_thread_context("Processing took too long; scheduling problem?");
229 }
230 #endif
231
232 return 0;
233 }
234
235 /* Connects to the specified input port, disconnecting already connected ports. */
236 int
connect_to_output_port(const char * port)237 connect_to_output_port(const char *port)
238 {
239 int ret;
240
241 ret = jack_port_disconnect(jack_client, input_port);
242
243 if (ret) {
244 g_warning("Cannot disconnect MIDI port.");
245
246 return -3;
247 }
248
249 ret = jack_connect(jack_client, port, jack_port_name(input_port));
250
251 if (ret) {
252 g_warning("Cannot connect to %s.", port);
253
254 return -4;
255 }
256
257 g_warning("Connected to %s.", port);
258
259 return 0;
260 }
261
262 void
init_jack(void)263 init_jack(void)
264 {
265 int err;
266
267 #ifdef WITH_LASH
268 lash_event_t *event;
269 #endif
270
271 jack_client = jack_client_open(PROGRAM_NAME, JackNullOption, NULL);
272
273 if (jack_client == NULL) {
274 g_critical("Could not connect to the JACK server; run jackd first?");
275 exit(EX_UNAVAILABLE);
276 }
277
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);
283
284 lash_jack_client_name(lash_client, jack_get_client_name(jack_client));
285 #endif
286
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);
291 }
292
293 input_port = jack_port_register(jack_client, INPUT_PORT_NAME, JACK_DEFAULT_MIDI_TYPE,
294 JackPortIsInput, 0);
295
296 if (input_port == NULL) {
297 g_critical("Could not register JACK input port.");
298 exit(EX_UNAVAILABLE);
299 }
300
301 if (jack_activate(jack_client)) {
302 g_critical("Cannot activate JACK client.");
303 exit(EX_UNAVAILABLE);
304 }
305 }
306
307 #ifdef WITH_LASH
308
309 static gboolean
lash_callback(gpointer notused)310 lash_callback(gpointer notused)
311 {
312 lash_event_t *event;
313
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;
319
320 case LASH_Quit:
321 g_warning("Exiting due to LASH request.");
322 ctrl_c_pressed = 1;
323 break;
324
325 default:
326 g_warning("Receieved unknown LASH event of type %d.", lash_event_get_type(event));
327 lash_event_destroy(event);
328 }
329 }
330
331 return TRUE;
332 }
333
334 static void
init_lash(lash_args_t * args)335 init_lash(lash_args_t *args)
336 {
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));
339
340 if (!lash_server_connected(lash_client)) {
341 g_critical("Cannot initialize LASH. Continuing anyway.");
342 /* exit(EX_UNAVAILABLE); */
343
344 return;
345 }
346
347 /* Schedule a function to process LASH events, ten times per second. */
348 g_timeout_add(100, lash_callback, NULL);
349 }
350
351 #endif /* WITH_LASH */
352
353 gboolean
writer_timeout(gpointer file_name_gpointer)354 writer_timeout(gpointer file_name_gpointer)
355 {
356 int i;
357 char *file_name = (char *)file_name_gpointer;
358
359 /*
360 * XXX: It should be done like this: http://wwwtcs.inf.tu-dresden.de/~tews/Gtk/x2992.html
361 */
362 if (ctrl_c_pressed == 0)
363 return TRUE;
364
365 jack_deactivate(jack_client);
366
367 /* Get rid of empty tracks. */
368 smf_rewind(smf);
369
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]);
374 }
375 }
376
377 if (smf->number_of_tracks == 0) {
378 g_message("No events recorded, not saving anything.");
379 exit(0);
380 }
381
382 if (smf_save(smf, file_name)) {
383 g_critical("Could not save file '%s', sorry.", file_name);
384 exit(-1);
385 }
386
387 g_message("File '%s' saved successfully.", file_name);
388
389 exit(0);
390 }
391
392 void
ctrl_c_handler(int signum)393 ctrl_c_handler(int signum)
394 {
395 ctrl_c_pressed = 1;
396 }
397
398 static void
log_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer notused)399 log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
400 {
401 fprintf(stderr, "%s: %s\n", log_domain, message);
402 }
403
404 static void
show_version(void)405 show_version(void)
406 {
407 fprintf(stdout, "%s %s, libsmf %s\n", PROGRAM_NAME, PROGRAM_VERSION, smf_get_version());
408
409 exit(EX_OK);
410 }
411
412 static void
usage(void)413 usage(void)
414 {
415 fprintf(stderr, "usage: jack-smf-recorder [-V] [ -a <output port>] file_name\n");
416
417 exit(EX_USAGE);
418 }
419
420 int
main(int argc,char * argv[])421 main(int argc, char *argv[])
422 {
423 int ch, i;
424 char *file_name, *autoconnect_port_name = NULL;
425
426 #ifdef WITH_LASH
427 lash_args_t *lash_args;
428 #endif
429
430 g_thread_init(NULL);
431
432 #ifdef WITH_LASH
433 lash_args = lash_extract_args(&argc, &argv);
434 #endif
435
436 g_log_set_default_handler(log_handler, NULL);
437
438 while ((ch = getopt(argc, argv, "a:V")) != -1) {
439 switch (ch) {
440 case 'a':
441 autoconnect_port_name = strdup(optarg);
442 break;
443
444 case 'V':
445 show_version();
446 break;
447
448 case '?':
449 default:
450 usage();
451 }
452 }
453
454 argc -= optind;
455 argv += optind;
456
457 if (argc == 0)
458 usage();
459
460 file_name = argv[0];
461
462 smf = smf_new();
463
464 if (smf == NULL)
465 exit(-1);
466
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]);
472 }
473
474 #ifdef WITH_LASH
475 init_lash(lash_args);
476 #endif
477
478 init_jack();
479
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);
484 }
485 }
486
487 g_timeout_add(100, writer_timeout, (gpointer)argv[0]);
488 signal(SIGINT, ctrl_c_handler);
489
490 g_message("Recording will start at the first received note; press ^C to write the file and exit.");
491
492 g_main_loop_run(g_main_loop_new(NULL, TRUE));
493
494 /* Not reached. */
495
496 return 0;
497 }
498
499