ref: 995656f727fc4a70e1e868bb223564bdce73101f
parent: 640c58cc289a23e6bc192b3e9bfd03d3b9339cf8
parent: f11a1911dc769ff6353bf3ac4ab1a9ec13471bb4
author: cancel <cancel@cancel.fm>
date: Mon Dec 31 05:29:46 EST 2018
Merge branch 'portmidi'
--- a/osc_out.c
+++ b/osc_out.c
@@ -216,7 +216,7 @@
for (Usz i = 0; i < count;) {
Susnote sn = buffer[i];
sn.remaining -= delta_float;
- if (sn.remaining > 0) {
+ if (sn.remaining > 0.001) {
if (sn.remaining < soonest)
soonest = sn.remaining;
buffer[i].remaining = sn.remaining;
--- a/sim.c
+++ b/sim.c
@@ -366,7 +366,7 @@
oe->octave = (U8)usz_clamp(octave_num, 1, 9);
oe->note = note_num;
oe->velocity = midi_velocity_of(velocity_g);
- oe->bar_divisor = (U8)usz_clamp(index_of(length_g), 1, Glyphs_index_count);
+ oe->bar_divisor = (U8)(index_of(length_g) + 1);
END_OPERATOR
BEGIN_OPERATOR(osc)
--- a/tool
+++ b/tool
@@ -26,6 +26,10 @@
Note: --pie and --static cannot be mixed.
-s Print statistics about compile time and binary size.
-h or --help Print this message and exit.
+Optional Features:
+ --portmidi Enable hardware MIDI output support with PortMIDI.
+ Default: not enabled
+ Note: PortMIDI has memory leaks and bugs.
EOF
}
@@ -45,6 +49,7 @@
stats_enabled=0
pie_enabled=0
static_enabled=0
+portmidi_enabled=0
while getopts c:dhsv-: opt_val; do
case "$opt_val" in
@@ -53,6 +58,7 @@
help) print_usage; exit 0;;
static) static_enabled=1;;
pie) pie_enabled=1;;
+ portmidi) portmidi_enabled=1;;
*)
echo "Unknown long option --$OPTARG" >&2
print_usage >&2
@@ -289,6 +295,16 @@
add libraries "-L$ncurses_dir/lib"
add cc_flags "-I$ncurses_dir/include"
# todo mach time stuff for mac?
+ if [[ $portmidi_enabled = 1 ]]; then
+ local portmidi_dir="$brew_prefix/opt/portmidi"
+ if ! [[ -d "$portmidi_dir" ]]; then
+ echo "Error: PortMIDI directory not found at $portmidi_dir" >&2
+ echo "Install with: brew install portmidi" >&2
+ exit 1
+ fi
+ add libraries "-L$portmidi_dir/lib"
+ add cc_flags "-I$portmidi_dir/include"
+ fi
;;
*)
# librt and high-res posix timers on Linux
@@ -297,8 +313,10 @@
;;
esac
add libraries -lmenuw -lformw -lncursesw
- # If we wanted wide chars, use -lncursesw on Linux, and still just
- # -lncurses on Mac.
+ if [[ $portmidi_enabled = 1 ]]; then
+ add libraries -lportmidi
+ add cc_flags -DFEAT_PORTMIDI
+ fi
;;
esac
try_make_dir "$build_dir"
--- a/tui_main.c
+++ b/tui_main.c
@@ -13,6 +13,10 @@
#include "sokol_time.h"
#undef SOKOL_IMPL
+#ifdef FEAT_PORTMIDI
+#include <portmidi.h>
+#endif
+
#define TIME_DEBUG 0
#if TIME_DEBUG
static int spin_track_timeout = 0;
@@ -21,34 +25,46 @@
static void usage(void) {
// clang-format off
fprintf(stderr,
- "Usage: orca [options] [file]\n\n"
- "General options:\n"
- " --margins <number> Set cosmetic margins.\n"
- " Default: 2\n"
- " --undo-limit <number> Set the maximum number of undo steps.\n"
- " If you plan to work with large files,\n"
- " set this to a low number.\n"
- " Default: 100\n"
- " -h or --help Print this message and exit.\n"
- "\n"
- "OSC/MIDI options:\n"
- " --osc-server <address>\n"
- " Hostname or IP address to send OSC messages to.\n"
- " Default: loopback (this machine)\n"
- "\n"
- " --osc-port <number or service name>\n"
- " UDP port (or service name) to send OSC messages to.\n"
- " This option must be set for OSC output to be enabled.\n"
- " Default: none\n"
- "\n"
- " --osc-midi-bidule <path>\n"
- " Set MIDI to be sent via OSC formatted for Plogue Bidule.\n"
- " The path argument is the path of the Plogue OSC MIDI device.\n"
- " Example: /OSC_MIDI_0/MIDI\n"
- "\n"
- " --strict-timing\n"
- " Reduce the timing jitter of outgoing MIDI and OSC messages.\n"
- " Uses more CPU time.\n"
+"Usage: orca [options] [file]\n\n"
+"General options:\n"
+" --margins <number> Set cosmetic margins.\n"
+" Default: 2\n"
+" --undo-limit <number> Set the maximum number of undo steps.\n"
+" If you plan to work with large files,\n"
+" set this to a low number.\n"
+" Default: 100\n"
+" -h or --help Print this message and exit.\n"
+"\n"
+"OSC/MIDI options:\n"
+" --strict-timing\n"
+" Reduce the timing jitter of outgoing MIDI and OSC messages.\n"
+" Uses more CPU time.\n"
+"\n"
+" --osc-server <address>\n"
+" Hostname or IP address to send OSC messages to.\n"
+" Default: loopback (this machine)\n"
+"\n"
+" --osc-port <number or service name>\n"
+" UDP port (or service name) to send OSC messages to.\n"
+" This option must be set for OSC output to be enabled.\n"
+" Default: none\n"
+"\n"
+" --osc-midi-bidule <path>\n"
+" Set MIDI to be sent via OSC formatted for Plogue Bidule.\n"
+" The path argument is the path of the Plogue OSC MIDI device.\n"
+" Example: /OSC_MIDI_0/MIDI\n"
+#ifdef FEAT_PORTMIDI
+"\n"
+" --portmidi-list-devices\n"
+" List the MIDI output devices available through PortMIDI,\n"
+" along with each associated device ID number, and then exit.\n"
+" Do this to figure out which ID to use with\n"
+" --portmidi-output-device\n"
+"\n"
+" --portmidi-output-device <number>\n"
+" Set MIDI to be sent via PortMIDI on a specified device ID.\n"
+" Example: 1\n"
+#endif
);
// clang-format on
}
@@ -633,6 +649,9 @@
typedef enum {
Midi_mode_type_null,
Midi_mode_type_osc_bidule,
+#ifdef FEAT_PORTMIDI
+ Midi_mode_type_portmidi,
+#endif
} Midi_mode_type;
typedef struct {
@@ -644,16 +663,52 @@
char const* path;
} Midi_mode_osc_bidule;
+#ifdef FEAT_PORTMIDI
+typedef struct {
+ Midi_mode_type type;
+ PmDeviceID device_id;
+ PortMidiStream* stream;
+} Midi_mode_portmidi;
+#endif
+
typedef union {
Midi_mode_any any;
Midi_mode_osc_bidule osc_bidule;
+#ifdef FEAT_PORTMIDI
+ Midi_mode_portmidi portmidi;
+#endif
} Midi_mode;
-void midi_mode_init(Midi_mode* mm) { mm->any.type = Midi_mode_type_null; }
-void midi_mode_set_osc_bidule(Midi_mode* mm, char const* path) {
+void midi_mode_init_null(Midi_mode* mm) { mm->any.type = Midi_mode_type_null; }
+void midi_mode_init_osc_bidule(Midi_mode* mm, char const* path) {
mm->osc_bidule.type = Midi_mode_type_osc_bidule;
mm->osc_bidule.path = path;
}
+#ifdef FEAT_PORTMIDI
+PmError midi_mode_init_portmidi(Midi_mode* mm, PmDeviceID dev_id) {
+ PmError e = Pm_Initialize();
+ if (e)
+ return e;
+ e = Pm_OpenOutput(&mm->portmidi.stream, dev_id, NULL, 0, NULL, NULL, 0);
+ if (e)
+ return e;
+ mm->portmidi.type = Midi_mode_type_portmidi;
+ mm->portmidi.device_id = dev_id;
+ return pmNoError;
+}
+#endif
+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: {
+ Pm_Close(mm->portmidi.stream);
+ } break;
+#endif
+ }
+}
typedef struct {
Field field;
@@ -786,6 +841,8 @@
case Midi_mode_type_null:
break;
case Midi_mode_type_osc_bidule: {
+ if (!oosc_dev)
+ continue;
I32 ints[3];
ints[0] = (0x8 << 4) | (U8)chan; // status
ints[1] = (I32)note; // note number
@@ -793,6 +850,15 @@
oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
ORCA_ARRAY_COUNTOF(ints));
} break;
+#ifdef FEAT_PORTMIDI
+ case Midi_mode_type_portmidi: {
+ int istatus = (0x8 << 4) | (int)chan;
+ int inote = (int)note;
+ int ivel = 0;
+ Pm_WriteShort(midi_mode->portmidi.stream, 0,
+ Pm_Message(istatus, inote, ivel));
+ } break;
+#endif
}
}
}
@@ -830,7 +896,7 @@
Susnote_list* susnote_list, Oevent const* events,
Usz count) {
Midi_mode_type midi_mode_type = midi_mode->any.type;
- double bar_secs = (double)bpm / 60.0;
+ double bar_secs = 60.0 / (double)bpm * 4.0;
enum { Midi_on_capacity = 512 };
typedef struct {
@@ -867,6 +933,9 @@
++midi_note_count;
} break;
case Oevent_type_osc_ints: {
+ // kinda lame
+ if (!oosc_dev)
+ continue;
Oevent_osc_ints const* eo = &e->osc_ints;
char path_buff[3];
path_buff[0] = '/';
@@ -897,6 +966,8 @@
case Midi_mode_type_null:
break;
case Midi_mode_type_osc_bidule: {
+ if (!oosc_dev)
+ continue; // not sure if needed
I32 ints[3];
ints[0] = (0x9 << 4) | mno.channel; // status
ints[1] = mno.note_number; // note number
@@ -904,6 +975,19 @@
oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
ORCA_ARRAY_COUNTOF(ints));
} break;
+#ifdef FEAT_PORTMIDI
+ case Midi_mode_type_portmidi: {
+ int istatus = (0x9 << 4) | (int)mno.channel;
+ int inote = (int)mno.note_number;
+ int ivel = (int)mno.velocity;
+ PmError pme = Pm_WriteShort(midi_mode->portmidi.stream, 0,
+ Pm_Message(istatus, inote, ivel));
+ // todo bad
+ if (pme) {
+ fprintf(stderr, "PortMIDI error: %s\n", Pm_GetErrorText(pme));
+ }
+ } break;
+#endif
}
}
}
@@ -944,8 +1028,6 @@
double secs = stm_sec(stm_since(a->clock));
a->meter_level -= (float)secs;
a->meter_level = float_clamp(a->meter_level, 0.0f, 1.0f);
- apply_time_to_sustained_notes(oosc_dev, midi_mode, secs, &a->susnote_list,
- &a->time_to_next_note_off);
if (!a->is_playing)
return;
bool do_play = false;
@@ -984,6 +1066,8 @@
}
#endif
if (do_play) {
+ apply_time_to_sustained_notes(oosc_dev, midi_mode, secs_span,
+ &a->susnote_list, &a->time_to_next_note_off);
orca_run(a->field.buffer, a->markmap_r.buffer, a->field.height,
a->field.width, a->tick_num, &a->oevent_list, a->piano_bits);
++a->tick_num;
@@ -992,7 +1076,7 @@
a->is_draw_dirty = true;
Usz count = a->oevent_list.count;
- if (oosc_dev && count > 0) {
+ if (count > 0) {
send_output_events(oosc_dev, midi_mode, a->bpm, &a->susnote_list,
a->oevent_list.buffer, count);
}
@@ -1720,6 +1804,10 @@
Argopt_osc_port,
Argopt_osc_midi_bidule,
Argopt_strict_timing,
+#ifdef FEAT_PORTMIDI
+ Argopt_portmidi_list_devices,
+ Argopt_portmidi_output_device,
+#endif
};
int main(int argc, char** argv) {
@@ -1731,6 +1819,11 @@
{"osc-port", required_argument, 0, Argopt_osc_port},
{"osc-midi-bidule", required_argument, 0, Argopt_osc_midi_bidule},
{"strict-timing", no_argument, 0, Argopt_strict_timing},
+#ifdef FEAT_PORTMIDI
+ {"portmidi-list-devices", no_argument, 0, Argopt_portmidi_list_devices},
+ {"portmidi-output-device", required_argument, 0,
+ Argopt_portmidi_output_device},
+#endif
{NULL, 0, NULL, 0}};
char* input_file = NULL;
int margin_thickness = 2;
@@ -1739,7 +1832,7 @@
char const* osc_port = NULL;
bool strict_timing = false;
Midi_mode midi_mode;
- midi_mode_init(&midi_mode);
+ midi_mode_init_null(&midi_mode);
for (;;) {
int c = getopt_long(argc, argv, "h", tui_options, NULL);
if (c == -1)
@@ -1747,7 +1840,10 @@
switch (c) {
case 'h':
usage();
- return 0;
+ exit(0);
+ case '?':
+ usage();
+ exit(1);
case Argopt_margins: {
margin_thickness = atoi(optarg);
if (margin_thickness < 0 ||
@@ -1756,7 +1852,7 @@
"Bad margins argument %s.\n"
"Must be 0 or positive integer.\n",
optarg);
- return 1;
+ exit(1);
}
} break;
case Argopt_undo_limit: {
@@ -1767,7 +1863,7 @@
"Bad undo-limit argument %s.\n"
"Must be 0 or positive integer.\n",
optarg);
- return 1;
+ exit(1);
}
} break;
case Argopt_osc_server: {
@@ -1777,15 +1873,50 @@
osc_port = optarg;
} break;
case Argopt_osc_midi_bidule: {
- midi_mode_set_osc_bidule(&midi_mode, optarg);
+ midi_mode_deinit(&midi_mode);
+ midi_mode_init_osc_bidule(&midi_mode, optarg);
} break;
case Argopt_strict_timing: {
strict_timing = true;
} break;
- case '?':
- usage();
- return 1;
+#ifdef FEAT_PORTMIDI
+ case Argopt_portmidi_list_devices: {
+ Pm_Initialize();
+ int num = Pm_CountDevices();
+ int output_devices = 0;
+ for (int i = 0; i < num; ++i) {
+ PmDeviceInfo const* info = Pm_GetDeviceInfo(i);
+ if (!info || !info->output)
+ continue;
+ printf("ID: %-4d Name: %s\n", i, info->name);
+ ++output_devices;
+ }
+ if (output_devices == 0) {
+ printf("No PortMIDI output devices detected.\n");
+ }
+ Pm_Terminate();
+ exit(0);
}
+ case Argopt_portmidi_output_device: {
+ int dev_id = atoi(optarg);
+ if (dev_id < 0 || (dev_id == 0 && strcmp(optarg, "0"))) {
+ fprintf(stderr,
+ "Bad portmidi-output-device argument %s.\n"
+ "Must be 0 or positive integer.\n",
+ optarg);
+ exit(1);
+ }
+ midi_mode_deinit(&midi_mode);
+ PmError pme = midi_mode_init_portmidi(&midi_mode, dev_id);
+ if (pme) {
+ fprintf(stderr, "PortMIDI error: %s\n", Pm_GetErrorText(pme));
+ exit(1);
+ }
+ // todo a bunch of places where we don't terminate pm on exit. Guess we
+ // should make a wrapper.
+ }
+#endif
+ }
}
if (optind == argc - 1) {
@@ -1792,7 +1923,7 @@
input_file = argv[optind];
} else if (optind < argc - 1) {
fprintf(stderr, "Expected only 1 file argument.\n");
- return 1;
+ exit(1);
}
qnav_init();
@@ -1851,7 +1982,7 @@
fprintf(stderr, "File load error: %s.\n", errstr);
ged_deinit(&ged_state);
qnav_deinit();
- return 1;
+ exit(1);
}
heapstr_init_cstr(&file_name, input_file);
} else {
@@ -2267,5 +2398,9 @@
endwin();
ged_deinit(&ged_state);
heapstr_deinit(&file_name);
+ midi_mode_deinit(&midi_mode);
+#ifdef FEAT_PORTMIDI
+ Pm_Terminate();
+#endif
return 0;
}