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 {
U8 oevent_type;
U8 channel, octave, note, velocity, duration;
U8 channel, octave, note, velocity, duration : 7, mono : 1;
} Oevent_midi_note;
typedef struct {

1
base.h

@ -106,6 +106,7 @@ static bool is_valid_glyph(Glyph c) {
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;
++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;

3
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);

6
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)

86
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; }

Loading…
Cancel
Save