diff --git a/.gitignore b/.gitignore index 0fc719f..477c242 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Vim/ctags files +tags + # Prerequisites *.d diff --git a/README.md b/README.md index a787067..2306c3a 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,13 @@ $ ./tool build --portmidi orca # compile orca using build script $ build/orca # run orca ``` +### Example: build and run `orca` livecoding environment with JACK + +```sh +$ ./tool build --jackmidi orca # compile orca using build script +$ build/orca # run orca +``` + ### `orca` Livecoding Environment Controls ``` diff --git a/tool b/tool index e32b94d..a4eeab8 100755 --- a/tool +++ b/tool @@ -31,6 +31,9 @@ Options: -v Print important commands as they're executed. -h or --help Print this message and exit. Optional Features: + --jackmidi Enable or disable virtual MIDI output support with + --no-jackmidi JACK. + Default: disabled. --portmidi Enable or disable hardware MIDI output support with --no-portmidi PortMidi. Note: PortMidi has memory leaks and bugs. Default: disabled. @@ -88,6 +91,7 @@ stats_enabled=0 pie_enabled=0 static_enabled=0 portmidi_enabled=0 +jackmidi_enabled=0 mouse_disabled=0 config_mode=release @@ -100,6 +104,8 @@ while getopts c:dhsv-: opt_val; do pie) pie_enabled=1;; portmidi) portmidi_enabled=1;; no-portmidi|noportmidi) portmidi_enabled=0;; + jackmidi) jackmidi_enabled=1;; + no-jackmidi|nojackmidi) jackmidi_enabled=0;; mouse) mouse_disabled=0;; no-mouse|nomouse) mouse_disabled=1;; *) printf 'Unknown option --%s\n' "$OPTARG" >&2; exit 1;; @@ -270,7 +276,7 @@ build_target() { fi case $config_mode in debug) - add cc_flags -DDEBUG -ggdb + add cc_flags -DDEBUG -g # cygwin gcc doesn't seem to have this stuff, so just elide for now if [ $os != cygwin ]; then if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then @@ -416,6 +422,10 @@ in debug builds. These are probably not bugs in orca. EOF fi fi + if [ $jackmidi_enabled = 1 ]; then + add libraries -ljack + add cc_flags -DFEAT_JACKMIDI + fi if [ $mouse_disabled = 1 ]; then add cc_flags -DFEAT_NOMOUSE fi diff --git a/tui_main.c b/tui_main.c index d525d05..ba21f12 100644 --- a/tui_main.c +++ b/tui_main.c @@ -18,6 +18,13 @@ #include #endif +#ifdef FEAT_JACKMIDI +#include +#include +#include +#define JACK_RINGBUFFER_SIZE (16384) // Default size for ringbuffer +#endif + #if NCURSES_VERSION_PATCH < 20081122 int _nc_has_mouse(void); #define has_mouse _nc_has_mouse @@ -732,6 +739,9 @@ typedef enum { #ifdef FEAT_PORTMIDI Midi_mode_type_portmidi, #endif +#ifdef FEAT_JACKMIDI + Midi_mode_type_jackmidi, +#endif } Midi_mode_type; typedef struct { @@ -754,12 +764,32 @@ typedef struct { static bool portmidi_is_initialized = false; #endif +#ifdef FEAT_JACKMIDI +// TODO: Fix that assumption +// Assumes jack always process the same numbers of frames. +struct jack_orca_midi_event{ + jack_nframes_t event_timestamp; + unsigned char event_data[4]; +}; + +typedef struct { + Midi_mode_type type; + jack_client_t *client; + jack_port_t *output_port; //Put the ports in an array + jack_ringbuffer_t *jack_rb; +} Midi_mode_jackmidi; +static bool jackmidi_is_initialized = false; +#endif + typedef union { Midi_mode_any any; Midi_mode_osc_bidule osc_bidule; #ifdef FEAT_PORTMIDI Midi_mode_portmidi portmidi; #endif +#ifdef FEAT_JACKMIDI + Midi_mode_jackmidi jackmidi; +#endif } Midi_mode; void midi_mode_init_null(Midi_mode *mm) { mm->any.type = Midi_mode_type_null; } @@ -847,6 +877,47 @@ static bool portmidi_find_name_of_device_id(PmDeviceID id, PmError *out_pmerror, return true; } #endif +#ifdef FEAT_JACKMIDI +// We could be using mutex, but those can be quite slow. +// The advantage would be thread synchronisation would be better +// guaranteed. +static int orca_jack_process(jack_nframes_t nframes, void *arg) { + Midi_mode *mm = (Midi_mode*)arg; + jack_nframes_t start_frame = jack_last_frame_time(mm->jackmidi.client); + void* port_buf = jack_port_get_buffer(mm->jackmidi.output_port, nframes); + unsigned char* buffer; + struct jack_orca_midi_event midi_event; + jack_midi_clear_buffer(port_buf); + + while(jack_ringbuffer_read_space(mm->jackmidi.jack_rb) >= sizeof(struct jack_orca_midi_event)) { + jack_ringbuffer_peek(mm->jackmidi.jack_rb, (char *)&midi_event, sizeof(struct jack_orca_midi_event)); + jack_ringbuffer_read_advance(mm->jackmidi.jack_rb, sizeof(struct jack_orca_midi_event)); + buffer = jack_midi_event_reserve(port_buf, midi_event.event_timestamp, 3); + buffer[2] = midi_event.event_data[2]; + buffer[1] = midi_event.event_data[1]; + buffer[0] = midi_event.event_data[0]; + } +end_loop: + return 0; +} +staticni int midi_mode_init_jackmidi(Midi_mode *mm) { + if((mm->jackmidi.client = jack_client_open("orca", JackNullOption, NULL)) == 0) { + fprintf (stderr, "JACK server not running?\n"); + return 1; + } + jack_set_process_callback(mm->jackmidi.client, orca_jack_process, mm); + mm->jackmidi.output_port = jack_port_register(mm->jackmidi.client, "out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + mm->jackmidi.jack_rb = jack_ringbuffer_create(JACK_RINGBUFFER_SIZE); + if (jack_activate(mm->jackmidi.client)) { + fprintf (stderr, "cannot activate client"); + return 1; + } + mm->jackmidi.type = Midi_mode_type_jackmidi; + + return 0; +} +#endif + staticni void midi_mode_deinit(Midi_mode *mm) { switch (mm->any.type) { case Midi_mode_type_null: @@ -854,7 +925,7 @@ staticni void midi_mode_deinit(Midi_mode *mm) { break; #ifdef FEAT_PORTMIDI case Midi_mode_type_portmidi: - // Because PortMidi seems to work correctly ony more platforms when using + // Because PortMidi seems to work correctly on more platforms when using // its timing stuff, we are using it. And because we are using it, and // because it may be buffering events for sending 'later', we might have // pending outgoing MIDI events. We'll need to wait until they finish being @@ -868,6 +939,12 @@ staticni void midi_mode_deinit(Midi_mode *mm) { sleep(0); Pm_Close(mm->portmidi.stream); break; +#endif +#ifdef FEAT_JACKMIDI + case Midi_mode_type_jackmidi: + jack_client_close(mm->jackmidi.client); + jack_ringbuffer_free(mm->jackmidi.jack_rb); + break; #endif } } @@ -994,6 +1071,17 @@ staticni void send_midi_3bytes(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, (void)pme; break; } +#endif +#ifdef FEAT_JACKMIDI + case Midi_mode_type_jackmidi: { + struct jack_orca_midi_event midi_event; + midi_event.event_timestamp = jack_frames_since_cycle_start(midi_mode->jackmidi.client); + midi_event.event_data[0] = (unsigned char)status; + midi_event.event_data[1] = (unsigned char)byte1; + midi_event.event_data[2] = (unsigned char)byte2; + jack_ringbuffer_write(midi_mode->jackmidi.jack_rb, (const char *)&midi_event, sizeof(struct jack_orca_midi_event)); + break; + } #endif } } @@ -1987,6 +2075,9 @@ enum { #ifdef FEAT_PORTMIDI Portmidi_output_device_menu_id, #endif +#ifdef FEAT_JACKMIDI + JACKmidi_output_device_menu_id, +#endif }; enum { Autofit_nicely_id = 1, @@ -2014,6 +2105,9 @@ enum { #ifdef FEAT_PORTMIDI Main_menu_choose_portmidi_output, #endif +#ifdef FEAT_JACKMIDI + Main_menu_choose_jackmidi_output, +#endif }; static void push_main_menu(void) { @@ -2031,6 +2125,9 @@ static void push_main_menu(void) { qmenu_add_choice(qm, Main_menu_osc, "OSC Output..."); #ifdef FEAT_PORTMIDI qmenu_add_choice(qm, Main_menu_choose_portmidi_output, "MIDI Output..."); +#endif +#ifdef FEAT_JACKMIDI + qmenu_add_choice(qm, Main_menu_choose_jackmidi_output, "MIDI Output..."); #endif qmenu_add_spacer(qm); qmenu_add_choice(qm, Main_menu_playback, "Clock & Timing..."); @@ -2632,6 +2729,16 @@ staticni void tui_load_conf(Tui *t) { } } } +#endif +#ifdef FEAT_JACKMIDI + int init_error; + if (t->ged.midi_mode.any.type == Midi_mode_type_null) { + midi_mode_deinit(&t->ged.midi_mode); + init_error = midi_mode_init_jackmidi(&t->ged.midi_mode); + if (init_error) { + // todo stuff + } + } #endif t->prefs_touched |= touched; osofree(portmidi_output_device); @@ -2663,6 +2770,12 @@ staticni void tui_save_prefs(Tui *t) { Confopt_portmidi_output_device); break; } +#endif +#ifdef FEAT_JACKMIDI + case Midi_mode_type_jackmidi: { + // TODO + break; + } #endif } // Add all conf items touched by user that we want to write to config file. @@ -2935,6 +3048,11 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { case Main_menu_choose_portmidi_output: push_portmidi_output_device_menu(&t->ged.midi_mode); break; +#endif +#ifdef FEAT_JACKMIDI + case Main_menu_choose_jackmidi_output: + //push_portmidi_output_device_menu(&t->ged.midi_mode); + break; #endif } break; @@ -3076,6 +3194,22 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { } break; } +#endif +#ifdef FEAT_JACKMIDI + case JACKmidi_output_device_menu_id: { + ged_stop_all_sustained_notes(&t->ged); + midi_mode_deinit(&t->ged.midi_mode); + int jie = midi_mode_init_jackmidi(&t->ged.midi_mode); + qnav_stack_pop(); + if (jie) { + qmsg_printf_push("JACK Initialization Error", + "Error setting PortMidi output device:\n%s", + "this ain't PortMidi"); + } else { + tui_save_prefs(t); + } + break; + } #endif } break; @@ -3868,6 +4002,10 @@ event_loop:; if (portmidi_is_initialized) Pm_Terminate(); #endif +#ifdef FEAT_JACKMIDI + if (jackmidi_is_initialized) + jack_client_close(t.ged.midi_mode.jackmidi.client); +#endif } #undef TOUCHFLAG