Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Jack support #74

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Vim/ctags files
tags

# Prerequisites
*.d

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
12 changes: 11 additions & 1 deletion tool
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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;;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
140 changes: 139 additions & 1 deletion tui_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
#include <portmidi.h>
#endif

#ifdef FEAT_JACKMIDI
#include <jack/jack.h>
#include <jack/ringbuffer.h>
#include <jack/midiport.h>
#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
Expand Down Expand Up @@ -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 {
Expand All @@ -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; }
Expand Down Expand Up @@ -847,14 +877,55 @@ 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:
case Midi_mode_type_osc_bidule:
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
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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...");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down