diff --git a/bank.h b/bank.h index 332e153..74c9ffe 100644 --- a/bank.h +++ b/bank.h @@ -15,7 +15,7 @@ typedef struct { typedef struct { U8 oevent_type; - U8 channel, octave, note, velocity, duration; + U8 channel, octave, note, velocity, duration : 7, mono : 1; } Oevent_midi_note; typedef struct { diff --git a/base.h b/base.h index a72010e..525eef9 100644 --- a/base.h +++ b/base.h @@ -106,6 +106,7 @@ static bool is_valid_glyph(Glyph c) { case '.': case '*': case ':': + case '%': case ';': case '=': case '#': diff --git a/osc_out.c b/osc_out.c index 301a674..934920f 100644 --- a/osc_out.c +++ b/osc_out.c @@ -223,9 +223,9 @@ void susnote_list_advance_time(Susnote_list *sl, double delta_time, buffer[i].remaining = sn.remaining; ++i; } else { - buffer[i] = buffer[count - 1]; - buffer[count - 1] = sn; --count; + buffer[i] = buffer[count]; + buffer[count] = sn; } } *start_removed = count; @@ -233,6 +233,27 @@ void susnote_list_advance_time(Susnote_list *sl, double delta_time, 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) { float soonest = 1.0f; Susnote const *buffer = sl->buffer; diff --git a/osc_out.h b/osc_out.h index f1985ea..f522b3d 100644 --- a/osc_out.h +++ b/osc_out.h @@ -48,6 +48,9 @@ void susnote_list_advance_time( Usz *restrict end_removed, // 1.0 if no notes remain or none are shorter than 1.0 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 double susnote_list_soonest_deadline(Susnote_list const *sl); diff --git a/sim.c b/sim.c index 9b37726..6043249 100644 --- a/sim.c +++ b/sim.c @@ -184,6 +184,7 @@ static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, #define UNIQUE_OPERATORS(_) \ _('!', midicc) \ _('#', comment) \ + _('%', midi) \ _('*', bang) \ _(':', midi) \ _(';', udp) \ @@ -345,7 +346,10 @@ BEGIN_OPERATOR(midi) oe->octave = octave_num; oe->note = note_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 BEGIN_OPERATOR(udp) diff --git a/tui_main.c b/tui_main.c index 430c81e..5b650a8 100644 --- a/tui_main.c +++ b/tui_main.c @@ -93,6 +93,7 @@ static Glyph_class glyph_class_of(Glyph glyph) { case ':': case ';': case '=': + case '%': case '?': return Glyph_class_lowercase; case '*': @@ -1036,6 +1037,13 @@ void ged_stop_all_sustained_notes(Ged *a) { 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, Susnote_list *susnote_list, Oevent const *events, 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 velocity; } 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_mono_on midi_mono_ons[16]; // Keep only a single one per channel Susnote new_susnotes[Midi_on_capacity]; Usz midi_note_count = 0; + Usz monofied_chans = 0; // bitset of channels with new mono notes for (Usz i = 0; i < count; ++i) { - if (midi_note_count == Midi_on_capacity) - break; Oevent const *e = events + i; switch ((Oevent_types)e->any.oevent_type) { case Oevent_type_midi_note: { + if (midi_note_count == Midi_on_capacity) + break; Oevent_midi_note const *em = &e->midi_note; Usz note_number = (Usz)(12u * em->octave + em->note); if (note_number > 127) note_number = 127; Usz channel = em->channel; - Usz duration = em->duration; - midi_note_ons[midi_note_count] = - (Midi_note_on){.channel = (U8)channel, - .note_number = (U8)note_number, - .velocity = em->velocity}; - new_susnotes[midi_note_count] = - (Susnote){.remaining = (float)(frame_secs * (double)duration), - .chan_note = (U16)((channel << 8u) | note_number)}; -#if 0 - fprintf(stderr, "bar div: %d, time: %f\n", (int)bar_div, - new_susnotes[midi_note_count].remaining); -#endif - ++midi_note_count; + if (channel > 15) + break; + if (em->mono) { + // 'mono' note-ons are strange. The more typical branch you'd expect to + // see, where you can play multiple notes per channel, is below. + monofied_chans |= 1u << (channel & 0xFu); + midi_mono_ons[channel] = (Midi_mono_on){.note_number = (U8)note_number, + .velocity = em->velocity, + .duration = em->duration}; + } else { + midi_note_ons[midi_note_count] = + (Midi_note_on){.channel = (U8)channel, + .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; } 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) { Usz start_note_offs, end_note_offs; 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; 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)); - } + (void)pme; break; } #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; }