Browse Source

Merge branch 'portmidi'

master
cancel 6 years ago
parent
commit
995656f727
  1. 2
      osc_out.c
  2. 2
      sim.c
  3. 22
      tool
  4. 223
      tui_main.c

2
osc_out.c

@ -216,7 +216,7 @@ void susnote_list_advance_time(Susnote_list* sl, double delta_time,
for (Usz i = 0; i < count;) { for (Usz i = 0; i < count;) {
Susnote sn = buffer[i]; Susnote sn = buffer[i];
sn.remaining -= delta_float; sn.remaining -= delta_float;
if (sn.remaining > 0) { if (sn.remaining > 0.001) {
if (sn.remaining < soonest) if (sn.remaining < soonest)
soonest = sn.remaining; soonest = sn.remaining;
buffer[i].remaining = sn.remaining; buffer[i].remaining = sn.remaining;

2
sim.c

@ -366,7 +366,7 @@ BEGIN_OPERATOR(midi)
oe->octave = (U8)usz_clamp(octave_num, 1, 9); oe->octave = (U8)usz_clamp(octave_num, 1, 9);
oe->note = note_num; oe->note = note_num;
oe->velocity = midi_velocity_of(velocity_g); 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 END_OPERATOR
BEGIN_OPERATOR(osc) BEGIN_OPERATOR(osc)

22
tool

@ -26,6 +26,10 @@ Options:
Note: --pie and --static cannot be mixed. Note: --pie and --static cannot be mixed.
-s Print statistics about compile time and binary size. -s Print statistics about compile time and binary size.
-h or --help Print this message and exit. -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 EOF
} }
@ -45,6 +49,7 @@ protections_enabled=0
stats_enabled=0 stats_enabled=0
pie_enabled=0 pie_enabled=0
static_enabled=0 static_enabled=0
portmidi_enabled=0
while getopts c:dhsv-: opt_val; do while getopts c:dhsv-: opt_val; do
case "$opt_val" in case "$opt_val" in
@ -53,6 +58,7 @@ while getopts c:dhsv-: opt_val; do
help) print_usage; exit 0;; help) print_usage; exit 0;;
static) static_enabled=1;; static) static_enabled=1;;
pie) pie_enabled=1;; pie) pie_enabled=1;;
portmidi) portmidi_enabled=1;;
*) *)
echo "Unknown long option --$OPTARG" >&2 echo "Unknown long option --$OPTARG" >&2
print_usage >&2 print_usage >&2
@ -289,6 +295,16 @@ build_target() {
add libraries "-L$ncurses_dir/lib" add libraries "-L$ncurses_dir/lib"
add cc_flags "-I$ncurses_dir/include" add cc_flags "-I$ncurses_dir/include"
# todo mach time stuff for mac? # 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 # librt and high-res posix timers on Linux
@ -297,8 +313,10 @@ build_target() {
;; ;;
esac esac
add libraries -lmenuw -lformw -lncursesw add libraries -lmenuw -lformw -lncursesw
# If we wanted wide chars, use -lncursesw on Linux, and still just if [[ $portmidi_enabled = 1 ]]; then
# -lncurses on Mac. add libraries -lportmidi
add cc_flags -DFEAT_PORTMIDI
fi
;; ;;
esac esac
try_make_dir "$build_dir" try_make_dir "$build_dir"

223
tui_main.c

@ -13,6 +13,10 @@
#include "sokol_time.h" #include "sokol_time.h"
#undef SOKOL_IMPL #undef SOKOL_IMPL
#ifdef FEAT_PORTMIDI
#include <portmidi.h>
#endif
#define TIME_DEBUG 0 #define TIME_DEBUG 0
#if TIME_DEBUG #if TIME_DEBUG
static int spin_track_timeout = 0; static int spin_track_timeout = 0;
@ -21,34 +25,46 @@ static int spin_track_timeout = 0;
static void usage(void) { static void usage(void) {
// clang-format off // clang-format off
fprintf(stderr, fprintf(stderr,
"Usage: orca [options] [file]\n\n" "Usage: orca [options] [file]\n\n"
"General options:\n" "General options:\n"
" --margins <number> Set cosmetic margins.\n" " --margins <number> Set cosmetic margins.\n"
" Default: 2\n" " Default: 2\n"
" --undo-limit <number> Set the maximum number of undo steps.\n" " --undo-limit <number> Set the maximum number of undo steps.\n"
" If you plan to work with large files,\n" " If you plan to work with large files,\n"
" set this to a low number.\n" " set this to a low number.\n"
" Default: 100\n" " Default: 100\n"
" -h or --help Print this message and exit.\n" " -h or --help Print this message and exit.\n"
"\n" "\n"
"OSC/MIDI options:\n" "OSC/MIDI options:\n"
" --osc-server <address>\n" " --strict-timing\n"
" Hostname or IP address to send OSC messages to.\n" " Reduce the timing jitter of outgoing MIDI and OSC messages.\n"
" Default: loopback (this machine)\n" " Uses more CPU time.\n"
"\n" "\n"
" --osc-port <number or service name>\n" " --osc-server <address>\n"
" UDP port (or service name) to send OSC messages to.\n" " Hostname or IP address to send OSC messages to.\n"
" This option must be set for OSC output to be enabled.\n" " Default: loopback (this machine)\n"
" Default: none\n" "\n"
"\n" " --osc-port <number or service name>\n"
" --osc-midi-bidule <path>\n" " UDP port (or service name) to send OSC messages to.\n"
" Set MIDI to be sent via OSC formatted for Plogue Bidule.\n" " This option must be set for OSC output to be enabled.\n"
" The path argument is the path of the Plogue OSC MIDI device.\n" " Default: none\n"
" Example: /OSC_MIDI_0/MIDI\n" "\n"
"\n" " --osc-midi-bidule <path>\n"
" --strict-timing\n" " Set MIDI to be sent via OSC formatted for Plogue Bidule.\n"
" Reduce the timing jitter of outgoing MIDI and OSC messages.\n" " The path argument is the path of the Plogue OSC MIDI device.\n"
" Uses more CPU time.\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 // clang-format on
} }
@ -633,6 +649,9 @@ bool ged_resize_grid_snap_ruler(Field* field, Markmap_reusable* markmap,
typedef enum { typedef enum {
Midi_mode_type_null, Midi_mode_type_null,
Midi_mode_type_osc_bidule, Midi_mode_type_osc_bidule,
#ifdef FEAT_PORTMIDI
Midi_mode_type_portmidi,
#endif
} Midi_mode_type; } Midi_mode_type;
typedef struct { typedef struct {
@ -644,16 +663,52 @@ typedef struct {
char const* path; char const* path;
} Midi_mode_osc_bidule; } Midi_mode_osc_bidule;
#ifdef FEAT_PORTMIDI
typedef struct {
Midi_mode_type type;
PmDeviceID device_id;
PortMidiStream* stream;
} Midi_mode_portmidi;
#endif
typedef union { typedef union {
Midi_mode_any any; Midi_mode_any any;
Midi_mode_osc_bidule osc_bidule; Midi_mode_osc_bidule osc_bidule;
#ifdef FEAT_PORTMIDI
Midi_mode_portmidi portmidi;
#endif
} Midi_mode; } Midi_mode;
void midi_mode_init(Midi_mode* mm) { mm->any.type = Midi_mode_type_null; } void midi_mode_init_null(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_osc_bidule(Midi_mode* mm, char const* path) {
mm->osc_bidule.type = Midi_mode_type_osc_bidule; mm->osc_bidule.type = Midi_mode_type_osc_bidule;
mm->osc_bidule.path = path; 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 { typedef struct {
Field field; Field field;
@ -786,6 +841,8 @@ void send_midi_note_offs(Oosc_dev* oosc_dev, Midi_mode const* midi_mode,
case Midi_mode_type_null: case Midi_mode_type_null:
break; break;
case Midi_mode_type_osc_bidule: { case Midi_mode_type_osc_bidule: {
if (!oosc_dev)
continue;
I32 ints[3]; I32 ints[3];
ints[0] = (0x8 << 4) | (U8)chan; // status ints[0] = (0x8 << 4) | (U8)chan; // status
ints[1] = (I32)note; // note number ints[1] = (I32)note; // note number
@ -793,6 +850,15 @@ void send_midi_note_offs(Oosc_dev* oosc_dev, Midi_mode const* midi_mode,
oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints, oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
ORCA_ARRAY_COUNTOF(ints)); ORCA_ARRAY_COUNTOF(ints));
} break; } 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 @@ void send_output_events(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, Usz bpm,
Susnote_list* susnote_list, Oevent const* events, Susnote_list* susnote_list, Oevent const* events,
Usz count) { Usz count) {
Midi_mode_type midi_mode_type = midi_mode->any.type; 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 }; enum { Midi_on_capacity = 512 };
typedef struct { typedef struct {
@ -867,6 +933,9 @@ void send_output_events(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, Usz bpm,
++midi_note_count; ++midi_note_count;
} break; } break;
case Oevent_type_osc_ints: { case Oevent_type_osc_ints: {
// kinda lame
if (!oosc_dev)
continue;
Oevent_osc_ints const* eo = &e->osc_ints; Oevent_osc_ints const* eo = &e->osc_ints;
char path_buff[3]; char path_buff[3];
path_buff[0] = '/'; path_buff[0] = '/';
@ -897,6 +966,8 @@ void send_output_events(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, Usz bpm,
case Midi_mode_type_null: case Midi_mode_type_null:
break; break;
case Midi_mode_type_osc_bidule: { case Midi_mode_type_osc_bidule: {
if (!oosc_dev)
continue; // not sure if needed
I32 ints[3]; I32 ints[3];
ints[0] = (0x9 << 4) | mno.channel; // status ints[0] = (0x9 << 4) | mno.channel; // status
ints[1] = mno.note_number; // note number ints[1] = mno.note_number; // note number
@ -904,6 +975,19 @@ void send_output_events(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, Usz bpm,
oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints, oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
ORCA_ARRAY_COUNTOF(ints)); ORCA_ARRAY_COUNTOF(ints));
} break; } 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 @@ void ged_do_stuff(Ged* a) {
double secs = stm_sec(stm_since(a->clock)); double secs = stm_sec(stm_since(a->clock));
a->meter_level -= (float)secs; a->meter_level -= (float)secs;
a->meter_level = float_clamp(a->meter_level, 0.0f, 1.0f); 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) if (!a->is_playing)
return; return;
bool do_play = false; bool do_play = false;
@ -984,6 +1066,8 @@ void ged_do_stuff(Ged* a) {
} }
#endif #endif
if (do_play) { 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, 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->field.width, a->tick_num, &a->oevent_list, a->piano_bits);
++a->tick_num; ++a->tick_num;
@ -992,7 +1076,7 @@ void ged_do_stuff(Ged* a) {
a->is_draw_dirty = true; a->is_draw_dirty = true;
Usz count = a->oevent_list.count; 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, send_output_events(oosc_dev, midi_mode, a->bpm, &a->susnote_list,
a->oevent_list.buffer, count); a->oevent_list.buffer, count);
} }
@ -1720,6 +1804,10 @@ enum {
Argopt_osc_port, Argopt_osc_port,
Argopt_osc_midi_bidule, Argopt_osc_midi_bidule,
Argopt_strict_timing, Argopt_strict_timing,
#ifdef FEAT_PORTMIDI
Argopt_portmidi_list_devices,
Argopt_portmidi_output_device,
#endif
}; };
int main(int argc, char** argv) { int main(int argc, char** argv) {
@ -1731,6 +1819,11 @@ int main(int argc, char** argv) {
{"osc-port", required_argument, 0, Argopt_osc_port}, {"osc-port", required_argument, 0, Argopt_osc_port},
{"osc-midi-bidule", required_argument, 0, Argopt_osc_midi_bidule}, {"osc-midi-bidule", required_argument, 0, Argopt_osc_midi_bidule},
{"strict-timing", no_argument, 0, Argopt_strict_timing}, {"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}}; {NULL, 0, NULL, 0}};
char* input_file = NULL; char* input_file = NULL;
int margin_thickness = 2; int margin_thickness = 2;
@ -1739,7 +1832,7 @@ int main(int argc, char** argv) {
char const* osc_port = NULL; char const* osc_port = NULL;
bool strict_timing = false; bool strict_timing = false;
Midi_mode midi_mode; Midi_mode midi_mode;
midi_mode_init(&midi_mode); midi_mode_init_null(&midi_mode);
for (;;) { for (;;) {
int c = getopt_long(argc, argv, "h", tui_options, NULL); int c = getopt_long(argc, argv, "h", tui_options, NULL);
if (c == -1) if (c == -1)
@ -1747,7 +1840,10 @@ int main(int argc, char** argv) {
switch (c) { switch (c) {
case 'h': case 'h':
usage(); usage();
return 0; exit(0);
case '?':
usage();
exit(1);
case Argopt_margins: { case Argopt_margins: {
margin_thickness = atoi(optarg); margin_thickness = atoi(optarg);
if (margin_thickness < 0 || if (margin_thickness < 0 ||
@ -1756,7 +1852,7 @@ int main(int argc, char** argv) {
"Bad margins argument %s.\n" "Bad margins argument %s.\n"
"Must be 0 or positive integer.\n", "Must be 0 or positive integer.\n",
optarg); optarg);
return 1; exit(1);
} }
} break; } break;
case Argopt_undo_limit: { case Argopt_undo_limit: {
@ -1767,7 +1863,7 @@ int main(int argc, char** argv) {
"Bad undo-limit argument %s.\n" "Bad undo-limit argument %s.\n"
"Must be 0 or positive integer.\n", "Must be 0 or positive integer.\n",
optarg); optarg);
return 1; exit(1);
} }
} break; } break;
case Argopt_osc_server: { case Argopt_osc_server: {
@ -1777,14 +1873,49 @@ int main(int argc, char** argv) {
osc_port = optarg; osc_port = optarg;
} break; } break;
case Argopt_osc_midi_bidule: { 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; } break;
case Argopt_strict_timing: { case Argopt_strict_timing: {
strict_timing = true; strict_timing = true;
} break; } break;
case '?': #ifdef FEAT_PORTMIDI
usage(); case Argopt_portmidi_list_devices: {
return 1; 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
} }
} }
@ -1792,7 +1923,7 @@ int main(int argc, char** argv) {
input_file = argv[optind]; input_file = argv[optind];
} else if (optind < argc - 1) { } else if (optind < argc - 1) {
fprintf(stderr, "Expected only 1 file argument.\n"); fprintf(stderr, "Expected only 1 file argument.\n");
return 1; exit(1);
} }
qnav_init(); qnav_init();
@ -1851,7 +1982,7 @@ int main(int argc, char** argv) {
fprintf(stderr, "File load error: %s.\n", errstr); fprintf(stderr, "File load error: %s.\n", errstr);
ged_deinit(&ged_state); ged_deinit(&ged_state);
qnav_deinit(); qnav_deinit();
return 1; exit(1);
} }
heapstr_init_cstr(&file_name, input_file); heapstr_init_cstr(&file_name, input_file);
} else { } else {
@ -2267,5 +2398,9 @@ quit:
endwin(); endwin();
ged_deinit(&ged_state); ged_deinit(&ged_state);
heapstr_deinit(&file_name); heapstr_deinit(&file_name);
midi_mode_deinit(&midi_mode);
#ifdef FEAT_PORTMIDI
Pm_Terminate();
#endif
return 0; return 0;
} }

Loading…
Cancel
Save