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