Browse Source

Add MIDI mono operator (%)

Fixes #38
master
cancel 5 years ago
parent
commit
c2b096521c
  1. 2
      bank.h
  2. 1
      base.h
  3. 25
      osc_out.c
  4. 3
      osc_out.h
  5. 6
      sim.c
  6. 86
      tui_main.c

2
bank.h

@ -15,7 +15,7 @@ typedef struct {
typedef struct { typedef struct {
U8 oevent_type; U8 oevent_type;
U8 channel, octave, note, velocity, duration; U8 channel, octave, note, velocity, duration : 7, mono : 1;
} Oevent_midi_note; } Oevent_midi_note;
typedef struct { typedef struct {

1
base.h

@ -106,6 +106,7 @@ static bool is_valid_glyph(Glyph c) {
case '.': case '.':
case '*': case '*':
case ':': case ':':
case '%':
case ';': case ';':
case '=': case '=':
case '#': case '#':

25
osc_out.c

@ -223,9 +223,9 @@ void susnote_list_advance_time(Susnote_list *sl, double delta_time,
buffer[i].remaining = sn.remaining; buffer[i].remaining = sn.remaining;
++i; ++i;
} else { } else {
buffer[i] = buffer[count - 1];
buffer[count - 1] = sn;
--count; --count;
buffer[i] = buffer[count];
buffer[count] = sn;
} }
} }
*start_removed = count; *start_removed = count;
@ -233,6 +233,27 @@ void susnote_list_advance_time(Susnote_list *sl, double delta_time,
sl->count = count; sl->count = count;
} }
void susnote_list_remove_by_chan_mask(Susnote_list *sl, Usz chan_mask,
Usz *restrict start_removed,
Usz *restrict end_removed) {
Susnote *restrict buffer = sl->buffer;
Usz count = sl->count;
*end_removed = count;
for (Usz i = 0; i < count;) {
Susnote sn = buffer[i];
Usz chan = sn.chan_note >> 8;
if (chan_mask & 1u << chan) {
--count;
buffer[i] = buffer[count];
buffer[count] = sn;
} else {
++i;
}
}
*start_removed = count;
sl->count = count;
}
double susnote_list_soonest_deadline(Susnote_list const *sl) { double susnote_list_soonest_deadline(Susnote_list const *sl) {
float soonest = 1.0f; float soonest = 1.0f;
Susnote const *buffer = sl->buffer; Susnote const *buffer = sl->buffer;

3
osc_out.h

@ -48,6 +48,9 @@ void susnote_list_advance_time(
Usz *restrict end_removed, Usz *restrict end_removed,
// 1.0 if no notes remain or none are shorter than 1.0 // 1.0 if no notes remain or none are shorter than 1.0
double *soonest_deadline); double *soonest_deadline);
void susnote_list_remove_by_chan_mask(Susnote_list *sl, Usz chan_mask,
Usz *restrict start_removed,
Usz *restrict end_removed);
// Returns 1.0 if no notes remain or none are shorter than 1.0 // Returns 1.0 if no notes remain or none are shorter than 1.0
double susnote_list_soonest_deadline(Susnote_list const *sl); double susnote_list_soonest_deadline(Susnote_list const *sl);

6
sim.c

@ -184,6 +184,7 @@ static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer,
#define UNIQUE_OPERATORS(_) \ #define UNIQUE_OPERATORS(_) \
_('!', midicc) \ _('!', midicc) \
_('#', comment) \ _('#', comment) \
_('%', midi) \
_('*', bang) \ _('*', bang) \
_(':', midi) \ _(':', midi) \
_(';', udp) \ _(';', udp) \
@ -345,7 +346,10 @@ BEGIN_OPERATOR(midi)
oe->octave = octave_num; oe->octave = octave_num;
oe->note = note_num; oe->note = note_num;
oe->velocity = (U8)vel_num; oe->velocity = (U8)vel_num;
oe->duration = (U8)(index_of(length_g)); // Mask used here to suppress bad GCC Wconversion for bitfield. This is bad
// -- we should do something smarter than this.
oe->duration = (U8)(index_of(length_g) & 0x7Fu);
oe->mono = This_oper_char == '%' ? 1 : 0;
END_OPERATOR END_OPERATOR
BEGIN_OPERATOR(udp) BEGIN_OPERATOR(udp)

86
tui_main.c

@ -93,6 +93,7 @@ static Glyph_class glyph_class_of(Glyph glyph) {
case ':': case ':':
case ';': case ';':
case '=': case '=':
case '%':
case '?': case '?':
return Glyph_class_lowercase; return Glyph_class_lowercase;
case '*': case '*':
@ -1036,6 +1037,13 @@ void ged_stop_all_sustained_notes(Ged *a) {
a->time_to_next_note_off = 1.0; a->time_to_next_note_off = 1.0;
} }
// The way orca handles MIDI sustains, timing, and overlapping note-ons (plus
// the 'mono' thing being added) has changed multiple times over time. Now we
// are in a situation where this function is a complete mess and needs an
// overhaul. If you see something in the function below and think, "wait, that
// seems redundant/weird", that's because it is, not because there's a good
// reason.
void send_output_events(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, Usz bpm, 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) {
@ -1048,34 +1056,47 @@ void send_output_events(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, Usz bpm,
U8 note_number; U8 note_number;
U8 velocity; U8 velocity;
} Midi_note_on; } Midi_note_on;
typedef struct {
U8 note_number;
U8 velocity;
U8 duration;
} Midi_mono_on;
Midi_note_on midi_note_ons[Midi_on_capacity]; Midi_note_on midi_note_ons[Midi_on_capacity];
Midi_mono_on midi_mono_ons[16]; // Keep only a single one per channel
Susnote new_susnotes[Midi_on_capacity]; Susnote new_susnotes[Midi_on_capacity];
Usz midi_note_count = 0; Usz midi_note_count = 0;
Usz monofied_chans = 0; // bitset of channels with new mono notes
for (Usz i = 0; i < count; ++i) { for (Usz i = 0; i < count; ++i) {
if (midi_note_count == Midi_on_capacity)
break;
Oevent const *e = events + i; Oevent const *e = events + i;
switch ((Oevent_types)e->any.oevent_type) { switch ((Oevent_types)e->any.oevent_type) {
case Oevent_type_midi_note: { case Oevent_type_midi_note: {
if (midi_note_count == Midi_on_capacity)
break;
Oevent_midi_note const *em = &e->midi_note; Oevent_midi_note const *em = &e->midi_note;
Usz note_number = (Usz)(12u * em->octave + em->note); Usz note_number = (Usz)(12u * em->octave + em->note);
if (note_number > 127) if (note_number > 127)
note_number = 127; note_number = 127;
Usz channel = em->channel; Usz channel = em->channel;
Usz duration = em->duration; if (channel > 15)
midi_note_ons[midi_note_count] = break;
(Midi_note_on){.channel = (U8)channel, if (em->mono) {
.note_number = (U8)note_number, // 'mono' note-ons are strange. The more typical branch you'd expect to
.velocity = em->velocity}; // see, where you can play multiple notes per channel, is below.
new_susnotes[midi_note_count] = monofied_chans |= 1u << (channel & 0xFu);
(Susnote){.remaining = (float)(frame_secs * (double)duration), midi_mono_ons[channel] = (Midi_mono_on){.note_number = (U8)note_number,
.chan_note = (U16)((channel << 8u) | note_number)}; .velocity = em->velocity,
#if 0 .duration = em->duration};
fprintf(stderr, "bar div: %d, time: %f\n", (int)bar_div, } else {
new_susnotes[midi_note_count].remaining); midi_note_ons[midi_note_count] =
#endif (Midi_note_on){.channel = (U8)channel,
++midi_note_count; .note_number = (U8)note_number,
.velocity = em->velocity};
new_susnotes[midi_note_count] =
(Susnote){.remaining = (float)(frame_secs * (double)em->duration),
.chan_note = (U16)((channel << 8u) | note_number)};
++midi_note_count;
}
break; break;
} }
case Oevent_type_midi_cc: { case Oevent_type_midi_cc: {
@ -1170,6 +1191,7 @@ void send_output_events(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, Usz bpm,
} }
} }
do_note_ons:
if (midi_note_count > 0) { if (midi_note_count > 0) {
Usz start_note_offs, end_note_offs; Usz start_note_offs, end_note_offs;
susnote_list_add_notes(susnote_list, new_susnotes, midi_note_count, susnote_list_add_notes(susnote_list, new_susnotes, midi_note_count,
@ -1202,16 +1224,42 @@ void send_output_events(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, Usz bpm,
int ivel = (int)mno.velocity; int ivel = (int)mno.velocity;
PmError pme = Pm_WriteShort(midi_mode->portmidi.stream, 0, PmError pme = Pm_WriteShort(midi_mode->portmidi.stream, 0,
Pm_Message(istatus, inote, ivel)); Pm_Message(istatus, inote, ivel));
// todo bad (void)pme;
if (pme) {
fprintf(stderr, "PortMidi error: %s\n", Pm_GetErrorText(pme));
}
break; break;
} }
#endif #endif
} }
} }
} }
if (monofied_chans) {
// The behavior we end up with is that if regular note-ons are played in
// the same frame/step as a mono, the regular note-ons will have the actual
// MIDI note on sent, followed immediately by a MIDI note off. I don't know
// if this is good or not.
Usz start_note_offs, end_note_offs;
susnote_list_remove_by_chan_mask(susnote_list, monofied_chans,
&start_note_offs, &end_note_offs);
if (start_note_offs != end_note_offs) {
Susnote const *restrict susnotes_off = susnote_list->buffer;
send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_note_offs,
susnotes_off + end_note_offs);
}
midi_note_count = 0; // We're going to use this list again. Reset it.
for (Usz i = 0; i < 16; i++) { // Add these notes to list of note-ons
if (!(monofied_chans & 1u << i))
continue;
midi_note_ons[midi_note_count] =
(Midi_note_on){.channel = (U8)i,
.note_number = midi_mono_ons[i].note_number,
.velocity = midi_mono_ons[i].velocity};
new_susnotes[midi_note_count] = (Susnote){
.remaining = (float)(frame_secs * (double)midi_mono_ons[i].duration),
.chan_note = (U16)((i << 8u) | midi_mono_ons[i].note_number)};
midi_note_count++;
}
monofied_chans = false;
goto do_note_ons; // lol super wasteful for doing susnotes again
}
} }
static double ms_to_sec(double ms) { return ms / 1000.0; } static double ms_to_sec(double ms) { return ms / 1000.0; }

Loading…
Cancel
Save