diff --git a/src/midi.c b/src/midi.c index 0b71c8f..382f1da 100644 --- a/src/midi.c +++ b/src/midi.c @@ -26,6 +26,8 @@ struct MidiChannel { u8 program; u8 pan; u16 pitchBend; + u8 prevPitch; + u8 pitch; DeviceSelect deviceSelect; }; @@ -121,6 +123,8 @@ static void initMidiChannel(u8 midiChan) chan->pan = DEFAULT_MIDI_PAN; chan->volume = MAX_MIDI_VOLUME; chan->pitchBend = DEFAULT_MIDI_PITCH_BEND; + chan->pitch = 0; + chan->prevPitch = 0; chan->deviceSelect = Auto; } @@ -404,6 +408,15 @@ void midi_note_on(u8 chan, u8 pitch, u8 velocity) log_warn("Ch %d: Dropped note %d", chan + 1, pitch); return; } + + if (!dynamicMode) { + MidiChannel* midiChannel = &midiChannels[chan]; + if (midiChannel->pitch != 0) { + midiChannel->prevPitch = midiChannel->pitch; + } + midiChannel->pitch = pitch; + } + devChan->midiChannel = chan; updateDeviceChannelFromAssociatedMidiChannel(devChan); devChan->pitch = pitch; @@ -413,11 +426,26 @@ void midi_note_on(u8 chan, u8 pitch, u8 velocity) void midi_note_off(u8 chan, u8 pitch) { + MidiChannel* midiChannel = &midiChannels[chan]; DeviceChannel* devChan; while ((devChan = findChannelPlayingNote(chan, pitch)) != NULL) { - devChan->noteOn = false; - devChan->pitch = 0; - devChan->ops->noteOff(devChan->number, pitch); + if (!dynamicMode && midiChannel->prevPitch != 0) { + devChan->pitch = midiChannel->prevPitch; + devChan->noteOn = true; + devChan->ops->noteOn(devChan->number, midiChannel->prevPitch, + 127); // TODO: Remember velocity! + midiChannel->pitch = midiChannel->prevPitch; + midiChannel->prevPitch = 0; + } else { + devChan->noteOn = false; + devChan->pitch = 0; + devChan->ops->noteOff(devChan->number, pitch); + midiChannel->pitch = 0; + } + } + + if (midiChannel->prevPitch == pitch) { + midiChannel->prevPitch = 0; } } diff --git a/tests/asserts.c b/tests/asserts.c index 7f7f5fd..0ca73de 100644 --- a/tests/asserts.c +++ b/tests/asserts.c @@ -25,6 +25,13 @@ void stub_usb_receive_note_on(u8 chan, u8 key, u8 velocity) stub_usb_receive_byte(velocity); } +void stub_usb_receive_note_off(u8 chan, u8 key) +{ + stub_usb_receive_byte(0x80 + chan); + stub_usb_receive_byte(key); + stub_usb_receive_byte(0); +} + void stub_usb_receive_pitch_bend(u8 chan, u16 bend) { u8 lower = bend & 0x007F; diff --git a/tests/asserts.h b/tests/asserts.h index fe5c2b3..e6eae3a 100644 --- a/tests/asserts.h +++ b/tests/asserts.h @@ -8,6 +8,7 @@ void expect_usb_sent_byte(u8 value); void stub_usb_receive_byte(u8 value); void stub_usb_receive_cc(u8 chan, u8 cc, u8 value); void stub_usb_receive_note_on(u8 chan, u8 key, u8 velocity); +void stub_usb_receive_note_off(u8 chan, u8 key); void stub_usb_receive_pitch_bend(u8 chan, u16 bend); void stub_comm_read_returns_midi_event(u8 status, u8 data, u8 data2); void expect_ym2612_write_reg_any_data(u8 part, u8 reg); diff --git a/tests/system/main.c b/tests/system/main.c index ca48425..ab5beab 100644 --- a/tests/system/main.c +++ b/tests/system/main.c @@ -25,7 +25,9 @@ int main(void) e2e_test(test_sets_separate_ch3_operator_frequencies), e2e_test(test_pitch_bends_ch3_special_mode_operators), e2e_test(test_write_directly_to_ym2612_regs_via_sysex), - e2e_test(test_plays_pcm_sample) + e2e_test(test_plays_pcm_sample), + e2e_test(test_midi_last_note_played_priority_respected_on_fm), + e2e_test(test_midi_last_note_played_cleared_when_released_on_fm) // clang-format on }; diff --git a/tests/system/test_e2e.c b/tests/system/test_e2e.c index 037b6db..a265e69 100644 --- a/tests/system/test_e2e.c +++ b/tests/system/test_e2e.c @@ -297,3 +297,46 @@ static void test_plays_pcm_sample(void** state) expect_value(__wrap_SND_startPlay_PCM, loop, 0); midi_receiver_read(); } + +static void test_midi_last_note_played_priority_respected_on_fm(void** state) +{ + stub_usb_receive_note_on(TEST_MIDI_CHANNEL_1, 48, 127); + expect_ym2612_write_channel(0, 0xA4, 0x1A); + expect_ym2612_write_channel(0, 0xA0, 0x84); + expect_ym2612_write_reg(0, 0x28, 0xF0); + midi_receiver_read(); + + stub_usb_receive_note_on(TEST_MIDI_CHANNEL_1, 50, 127); + expect_ym2612_write_channel(0, 0xA4, 0x1A); + expect_ym2612_write_channel(0, 0xA0, 0xD2); + expect_ym2612_write_reg(0, 0x28, 0xF0); + midi_receiver_read(); + + stub_usb_receive_note_off(TEST_MIDI_CHANNEL_1, 50); + expect_ym2612_write_channel(0, 0xA4, 0x1A); + expect_ym2612_write_channel(0, 0xA0, 0x84); + expect_ym2612_write_reg(0, 0x28, 0xF0); + midi_receiver_read(); +} + +static void test_midi_last_note_played_cleared_when_released_on_fm(void** state) +{ + stub_usb_receive_note_on(TEST_MIDI_CHANNEL_1, 48, 127); + expect_ym2612_write_channel(0, 0xA4, 0x1A); + expect_ym2612_write_channel(0, 0xA0, 0x84); + expect_ym2612_write_reg(0, 0x28, 0xF0); + midi_receiver_read(); + + stub_usb_receive_note_on(TEST_MIDI_CHANNEL_1, 50, 127); + expect_ym2612_write_channel(0, 0xA4, 0x1A); + expect_ym2612_write_channel(0, 0xA0, 0xD2); + expect_ym2612_write_reg(0, 0x28, 0xF0); + midi_receiver_read(); + + stub_usb_receive_note_off(TEST_MIDI_CHANNEL_1, 48); + midi_receiver_read(); + + stub_usb_receive_note_off(TEST_MIDI_CHANNEL_1, 50); + expect_ym2612_write_reg(0, 0x28, 0x0); + midi_receiver_read(); +}