Skip to content

Commit

Permalink
Implement 10 slot note priority system.
Browse files Browse the repository at this point in the history
Overwrite oldest note when additional notes come in.
  • Loading branch information
rhargreaves committed Jul 17, 2024
1 parent 483fe47 commit 197d7c9
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 51 deletions.
50 changes: 5 additions & 45 deletions src/midi.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,14 @@
#include "midi_sender.h"
#include "synth.h"
#include "ui_fm.h"
#include "note_priority.h"
#include <stdbool.h>

#define MIN_MIDI_VELOCITY 0
#define RANGE(value, range) (value / (128 / range))
#define CHANNEL_UNASSIGNED 0xFF
#define LENGTH_OF(x) (sizeof(x) / sizeof(x[0]))

#define NOTE_PRIORITY_STACK_SIZE 20

typedef struct NotePriorityStack {
u8 slot[NOTE_PRIORITY_STACK_SIZE];
s16 top;
} NotePriorityStack;

typedef enum DeviceSelect DeviceSelect;
enum DeviceSelect { Auto, FM, PSG_Tone, PSG_Noise };

Expand Down Expand Up @@ -131,8 +125,7 @@ static void initMidiChannel(u8 midiChan)
chan->volume = MAX_MIDI_VOLUME;
chan->pitchBend = DEFAULT_MIDI_PITCH_BEND;
chan->prevVelocity = 0;
chan->notePriority.top = -1;
memset(chan->notePriority.slot, 0, NOTE_PRIORITY_STACK_SIZE);
note_priority_init(&chan->notePriority);
chan->deviceSelect = Auto;
}

Expand Down Expand Up @@ -402,39 +395,6 @@ static DeviceChannel* findSuitableDeviceChannel(u8 midiChan)
: deviceChannelByMidiChannel(midiChan);
}

// 0 1 2 3
// Oldest -> Most Recent
static void notePriorityPush(NotePriorityStack* notePriority, u8 pitch)
{
if (notePriority->top == NOTE_PRIORITY_STACK_SIZE - 1) {
// rewrite array
}
notePriority->slot[++notePriority->top] = pitch;
}

static u8 notePriorityPop(NotePriorityStack* notePriority)
{
if (notePriority->top == -1) {
return 0;
}

return notePriority->slot[notePriority->top--];
}

static void notePriorityRemove(NotePriorityStack* notePriority, u8 pitch)
{
s16 writeCursor = -1;
for (u16 i = 0; i <= notePriority->top; i++) {
u8 item = notePriority->slot[i];
if (item == pitch) {
continue;
} else {
notePriority->slot[++writeCursor] = item;
}
}
notePriority->top = writeCursor;
}

void midi_note_on(u8 chan, u8 pitch, u8 velocity)
{
if (velocity == MIN_MIDI_VELOCITY) {
Expand All @@ -452,7 +412,7 @@ void midi_note_on(u8 chan, u8 pitch, u8 velocity)

if (!dynamicMode) {
MidiChannel* midiChannel = &midiChannels[chan];
notePriorityPush(&midiChannel->notePriority, pitch);
note_priority_push(&midiChannel->notePriority, pitch);
midiChannel->prevVelocity = velocity;
}

Expand All @@ -466,11 +426,11 @@ void midi_note_on(u8 chan, u8 pitch, u8 velocity)
void midi_note_off(u8 chan, u8 pitch)
{
MidiChannel* midiChannel = &midiChannels[chan];
notePriorityRemove(&midiChannel->notePriority, pitch);
note_priority_remove(&midiChannel->notePriority, pitch);

DeviceChannel* devChan;
while ((devChan = findChannelPlayingNote(chan, pitch)) != NULL) {
u8 nextMostRecentPitch = notePriorityPop(&midiChannel->notePriority);
u8 nextMostRecentPitch = note_priority_pop(&midiChannel->notePriority);
if (!dynamicMode && nextMostRecentPitch != 0) {
devChan->pitch = nextMostRecentPitch;
devChan->noteOn = true;
Expand Down
45 changes: 45 additions & 0 deletions src/note_priority.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "note_priority.h"
#include <memory.h>

void note_priority_init(NotePriorityStack* notePriority)
{
notePriority->top = -1;
memset(notePriority->slot, 0, NOTE_PRIORITY_LENGTH);
}

// 0 1 2 ...
// Oldest -> Most Recent
void note_priority_push(NotePriorityStack* notePriority, u8 pitch)
{
if (notePriority->top == NOTE_PRIORITY_LENGTH - 1) {
// rewrite array, evicting oldest item
for (u16 i = 0; i < notePriority->top; i++) {
notePriority->slot[i] = notePriority->slot[i + 1];
}
notePriority->top--;
}
notePriority->slot[++notePriority->top] = pitch;
}

u8 note_priority_pop(NotePriorityStack* notePriority)
{
if (notePriority->top == -1) {
return 0;
}

return notePriority->slot[notePriority->top--];
}

void note_priority_remove(NotePriorityStack* notePriority, u8 pitch)
{
s16 writeCursor = -1;
for (u16 i = 0; i <= notePriority->top; i++) {
u8 item = notePriority->slot[i];
if (item == pitch) {
continue;
} else {
notePriority->slot[++writeCursor] = item;
}
}
notePriority->top = writeCursor;
}
14 changes: 14 additions & 0 deletions src/note_priority.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once
#include <types.h>

#define NOTE_PRIORITY_LENGTH 10

typedef struct NotePriorityStack {
u8 slot[NOTE_PRIORITY_LENGTH];
s16 top;
} NotePriorityStack;

void note_priority_init(NotePriorityStack* notePriority);
void note_priority_push(NotePriorityStack* notePriority, u8 pitch);
u8 note_priority_pop(NotePriorityStack* notePriority);
void note_priority_remove(NotePriorityStack* notePriority, u8 pitch);
7 changes: 6 additions & 1 deletion tests/unit/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "test_synth.c"
#include "test_vstring.c"
#include "test_buffer.c"
#include "test_note_priority.c"

#define midi_test(test) cmocka_unit_test_setup(test, test_midi_setup)
#define midi_pcm_test(test) cmocka_unit_test_setup(test, test_midi_setup)
Expand All @@ -33,6 +34,8 @@
#define buffer_test(test) cmocka_unit_test_setup(test, test_buffer_setup)
#define midi_receiver_test(test) \
cmocka_unit_test_setup(test, test_midi_receiver_setup)
#define note_priority_test(test) \
cmocka_unit_test_setup(test, test_note_priority_setup)

int main(void)
{
Expand Down Expand Up @@ -363,7 +366,9 @@ int main(void)
buffer_test(test_buffer_available_returns_correct_value_when_empty),
buffer_test(test_buffer_available_returns_correct_value_when_full),
buffer_test(test_buffer_returns_cannot_write_if_full),
buffer_test(test_buffer_returns_can_write_if_empty)
buffer_test(test_buffer_returns_can_write_if_empty),

note_priority_test(test_note_priority_evicts_old_items)
// clang-format on
};

Expand Down
14 changes: 9 additions & 5 deletions tests/unit/test_midi_fm.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "test_midi.h"
#include "debug.h"

static void test_midi_triggers_synth_note_on(UNUSED void** state)
{
Expand Down Expand Up @@ -734,12 +735,12 @@ static void test_midi_note_priority_respected_for_multiple_notes(
static void test_midi_note_priority_removes_oldest_when_full(
UNUSED void** state)
{
const u16 expectedFreqNum[NOTE_PRIORITY_OVERFLOW_LENGTH] = { 0x284, 0x2a9,
0x2d2, 0x2fd, 0x32a, 0x35a, 0x38e, 0x3c4, 0x3fd, 0x439, 0x47a };
const u16 expectedFreqNum[NOTE_PRIORITY_OVERFLOW_LENGTH] = { 0x25f, 0x284,
0x2a9, 0x2d2, 0x2fd, 0x32a, 0x35a, 0x38e, 0x3c4, 0x3fd, 0x439 };
const u8 expectedOctave = 4;

for (u16 i = 0; i < NOTE_PRIORITY_OVERFLOW_LENGTH; i++) {
u8 pitch = 60 + i;
u8 pitch = 59 + i;
u16 freqNum = expectedFreqNum[i];

print_message("noteOn: pitch = %d, freqNum = 0x%x, octave = %d\n",
Expand All @@ -751,8 +752,8 @@ static void test_midi_note_priority_removes_oldest_when_full(
__real_midi_note_on(0, pitch, MAX_MIDI_VOLUME);
}

for (s16 i = NOTE_PRIORITY_OVERFLOW_LENGTH - 1; i >= 1; i--) {
u8 pitch = 60 + i;
for (s16 i = NOTE_PRIORITY_OVERFLOW_LENGTH - 1; i >= 2; i--) {
u8 pitch = 59 + i;
u16 freqNum = expectedFreqNum[i - 1];

print_message("noteOff: pitch = %d, freqNum = 0x%x, octave = %d\n",
Expand All @@ -763,4 +764,7 @@ static void test_midi_note_priority_removes_oldest_when_full(
expect_value(__wrap_synth_noteOn, channel, 0);
__real_midi_note_off(0, pitch);
}

expect_value(__wrap_synth_noteOff, channel, 0);
__real_midi_note_off(0, 60);
}
29 changes: 29 additions & 0 deletions tests/unit/test_note_priority.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "cmocka_inc.h"

#include "note_priority.h"

static NotePriorityStack testStack;

static int test_note_priority_setup(UNUSED void** state)
{
note_priority_init(&testStack);
return 0;
}

static void test_note_priority_evicts_old_items(UNUSED void** state)
{
const u16 additive = 50;

for (u16 i = 0; i <= 11; i++) {
note_priority_push(&testStack, i + additive);
}

for (s16 i = 11; i > 1; i--) {
u8 item = note_priority_pop(&testStack);
u8 expected = i + additive;
assert_int_equal(item, expected);
}

u8 nilPop = note_priority_pop(&testStack);
assert_int_equal(nilPop, 0);
}

0 comments on commit 197d7c9

Please sign in to comment.