2 * Copyright (c) 2007, 2008 Edward Tomasz NapieraĆa <trasz@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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>.
36 #include <sys/types.h>
44 #include <jack/jack.h>
45 #include <jack/midiport.h>
52 #include <lash/lash.h>
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;
63 smf_track_t
*tracks
[16]; /* We allocate one track per MIDI channel. */
66 lash_client_t
*lash_client
;
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
82 ret
= gettimeofday(&tv
, NULL
);
85 perror("gettimeofday");
89 seconds
= tv
.tv_sec
+ tv
.tv_usec
/ 1000000.0;
97 static double previously
= -1.0;
103 if (previously
== -1.0) {
109 delta
= now
- previously
;
112 assert(delta
>= 0.0);
118 warning_async(gpointer s
)
120 const char *str
= (const char *)s
;
128 warn_from_jack_thread_context(const char *str
)
130 g_idle_add(warning_async
, (gpointer
)str
);
134 nframes_to_ms(jack_nframes_t nframes
)
138 sr
= jack_get_sample_rate(jack_client
);
142 return (nframes
* 1000.0) / (double)sr
;
146 nframes_to_seconds(jack_nframes_t nframes
)
148 return nframes_to_ms(nframes
) / 1000.0;
152 process_midi_input(jack_nframes_t nframes
)
154 int read
, events
, i
, channel
;
156 jack_midi_event_t event
;
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.");
168 #ifdef JACK_MIDI_NEEDS_NFRAMES
169 events
= jack_midi_get_event_count(port_buffer
, nframes
);
171 events
= jack_midi_get_event_count(port_buffer
);
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
);
180 read
= jack_midi_event_get(&event
, port_buffer
, i
);
183 warn_from_jack_thread_context("jack_midi_event_get failed, RECEIVED NOTE LOST.");
187 /* Ignore realtime messages. */
188 if (event
.buffer
[0] >= 0xF8)
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.");
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
));
210 process_callback(jack_nframes_t nframes
, void *notused
)
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?");
218 /* Check for impossible condition that actually happened to me, caused by some problem between jackd and OSS4. */
220 warn_from_jack_thread_context("Process callback called with nframes = 0; bug in JACK?");
224 process_midi_input(nframes
);
227 if (get_delta_time() > MAX_PROCESSING_TIME
) {
228 warn_from_jack_thread_context("Processing took too long; scheduling problem?");
235 /* Connects to the specified input port, disconnecting already connected ports. */
237 connect_to_output_port(const char *port
)
241 ret
= jack_port_disconnect(jack_client
, input_port
);
244 g_warning("Cannot disconnect MIDI port.");
249 ret
= jack_connect(jack_client
, port
, jack_port_name(input_port
));
252 g_warning("Cannot connect to %s.", port
);
257 g_warning("Connected to %s.", port
);
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
);
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
));
287 err
= jack_set_process_callback(jack_client
, process_callback
, 0);
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
,
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
);
310 lash_callback(gpointer notused
)
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
:
321 g_warning("Exiting due to LASH request.");
326 g_warning("Receieved unknown LASH event of type %d.", lash_event_get_type(event
));
327 lash_event_destroy(event
);
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); */
347 /* Schedule a function to process LASH events, ten times per second. */
348 g_timeout_add(100, lash_callback
, NULL
);
351 #endif /* WITH_LASH */
354 writer_timeout(gpointer file_name_gpointer
)
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)
365 jack_deactivate(jack_client
);
367 /* Get rid of empty tracks. */
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.");
382 if (smf_save(smf
, file_name
)) {
383 g_critical("Could not save file '%s', sorry.", file_name
);
387 g_message("File '%s' saved successfully.", file_name
);
393 ctrl_c_handler(int signum
)
399 log_handler(const gchar
*log_domain
, GLogLevelFlags log_level
, const gchar
*message
, gpointer notused
)
401 fprintf(stderr
, "%s: %s\n", log_domain
, message
);
407 fprintf(stdout
, "%s %s, libsmf %s\n", PROGRAM_NAME
, PROGRAM_VERSION
, smf_get_version());
415 fprintf(stderr
, "usage: jack-smf-recorder [-V] [ -a <output port>] file_name\n");
421 main(int argc
, char *argv
[])
424 char *file_name
, *autoconnect_port_name
= NULL
;
427 lash_args_t
*lash_args
;
433 lash_args
= lash_extract_args(&argc
, &argv
);
436 g_log_set_default_handler(log_handler
, NULL
);
438 while ((ch
= getopt(argc
, argv
, "a:V")) != -1) {
441 autoconnect_port_name
= strdup(optarg
);
467 for (i
= 0; i
< 16; i
++) {
468 tracks
[i
] = smf_track_new();
469 if (tracks
[i
] == NULL
)
471 smf_add_track(smf
, tracks
[i
]);
475 init_lash(lash_args
);
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
));