Skip to content

Commit 93a3ddf

Browse files
committed
feat: support dumping FM channel parameters
1 parent 536f98b commit 93a3ddf

File tree

6 files changed

+109
-0
lines changed

6 files changed

+109
-0
lines changed

.cursor/rules/ym2612-tips.mdc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
- When the YM2612 regs are being changed the Z80 bus is requested. Failing tests which report "No entries for symbol __wrap_Z80_getAndRequestBus" are probably doing so as the YM2612 is being written to. For example, note on, FM parameter change.

src/midi.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,65 @@ static void send_preset_data(u8 type, u8 program, const FmPreset* preset)
736736
midi_tx_send_sysex(sysexData, index);
737737
}
738738

739+
static void send_channel_data(u8 type, u8 midiChannel, const FmPreset* preset)
740+
{
741+
u8 sysexData[4 + 2 + 4 + (MAX_FM_OPERATORS * 11)];
742+
u16 index = 0;
743+
744+
sysexData[index++] = SYSEX_MANU_EXTENDED;
745+
sysexData[index++] = SYSEX_MANU_REGION;
746+
sysexData[index++] = SYSEX_MANU_ID;
747+
sysexData[index++] = SYSEX_COMMAND_CHANNEL_DATA;
748+
749+
sysexData[index++] = type;
750+
sysexData[index++] = midiChannel;
751+
752+
sysexData[index++] = preset->algorithm;
753+
sysexData[index++] = preset->feedback;
754+
sysexData[index++] = preset->ams;
755+
sysexData[index++] = preset->fms;
756+
757+
for (u8 i = 0; i < MAX_FM_OPERATORS; i++) {
758+
sysexData[index++] = preset->operators[i].multiple;
759+
sysexData[index++] = preset->operators[i].detune;
760+
sysexData[index++] = preset->operators[i].attackRate;
761+
sysexData[index++] = preset->operators[i].rateScaling;
762+
sysexData[index++] = preset->operators[i].decayRate;
763+
sysexData[index++] = preset->operators[i].amplitudeModulation;
764+
sysexData[index++] = preset->operators[i].sustainLevel;
765+
sysexData[index++] = preset->operators[i].sustainRate;
766+
sysexData[index++] = preset->operators[i].releaseRate;
767+
sysexData[index++] = preset->operators[i].totalLevel;
768+
sysexData[index++] = preset->operators[i].ssgEg;
769+
}
770+
771+
midi_tx_send_sysex(sysexData, index);
772+
}
773+
774+
static void dump_channel_request(const u8* data, u16 length)
775+
{
776+
u8 type = data[0];
777+
u8 midiChannel = data[1];
778+
779+
switch (type) {
780+
case STORE_PROGRAM_TYPE_FM: {
781+
DeviceChannel* devChan = deviceChannelByMidiChannel(midiChannel);
782+
if (devChan == NULL || devChan->ops != &FM_VTable) {
783+
log_warn("Ch %d: No FM channel assigned", midiChannel + 1);
784+
return;
785+
}
786+
FmPreset currentPreset;
787+
synth_extract_preset(devChan->num, &currentPreset);
788+
send_channel_data(type, midiChannel, &currentPreset);
789+
log_info("Ch %d: FM %d dumped", midiChannel + 1, devChan->num);
790+
break;
791+
}
792+
default:
793+
log_warn("Invalid dump channel request type: %d", type);
794+
break;
795+
}
796+
}
797+
739798
static void dump_preset_request(const u8* data, u16 length)
740799
{
741800
u8 type = data[0];
@@ -781,6 +840,8 @@ static const SysexCommand SYSEX_COMMANDS[] = {
781840
{ SYSEX_COMMAND_CLEAR_ALL_PROGRAMS, clear_all_programs, 1, true },
782841
{ SYSEX_COMMAND_DUMP_PRESET, dump_preset_request, 2, true },
783842
{ SYSEX_COMMAND_PRESET_DATA, NULL, 0, false },
843+
{ SYSEX_COMMAND_DUMP_CHANNEL, dump_channel_request, 2, true },
844+
{ SYSEX_COMMAND_CHANNEL_DATA, NULL, 0, false },
784845
};
785846

786847
void midi_sysex(const u8* data, u16 length)

src/midi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@
130130
#define SYSEX_COMMAND_CLEAR_ALL_PROGRAMS 0x0C
131131
#define SYSEX_COMMAND_DUMP_PRESET 0x0D
132132
#define SYSEX_COMMAND_PRESET_DATA 0x0E
133+
#define SYSEX_COMMAND_DUMP_CHANNEL 0x0F
134+
#define SYSEX_COMMAND_CHANNEL_DATA 0x10
133135

134136
#define STORE_PROGRAM_TYPE_FM 0x00
135137
#define STORE_PROGRAM_TYPE_PSG 0x01

tests/system/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ int main(void)
2828
e2e_test(test_midi_changing_program_retains_pan),
2929
e2e_test(test_midi_portamento_glides_note),
3030
e2e_test(test_dump_preset_to_callee),
31+
e2e_test(test_dump_channel_parameters_to_callee),
3132
// WIP: e2e_test(test_midi_pitch_bend_range_configurable_per_channel),
3233
// clang-format on
3334
};

tests/system/test_e2e.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "mocks/mock_comm.h"
1111
#include "mocks/mock_sgdk.h"
1212
#include "mocks/mock_psg.h"
13+
#include "mocks/mock_log.h"
1314
#include "ym2612_regs.h"
1415
#include "test_helpers.h"
1516

@@ -476,3 +477,40 @@ void test_dump_preset_to_callee(void** state)
476477

477478
midi_rx_read();
478479
}
480+
481+
void test_dump_channel_parameters_to_callee(void** state)
482+
{
483+
stub_everdrive_as_present();
484+
485+
const u8 midiChannel = 0;
486+
487+
// Change the algorithm via CC
488+
expect_ym2612_write_reg(YM_CH1, YM_REG(YM_BASE_ALGORITHM_FEEDBACK, YM_CH1), 7);
489+
stub_usb_receive_cc(MIDI_CHANNEL_1, CC_GENMDM_FM_ALGORITHM, 127);
490+
midi_rx_read();
491+
492+
// Change the feedback via CC
493+
expect_ym2612_write_reg(YM_CH1, YM_REG(YM_BASE_ALGORITHM_FEEDBACK, YM_CH1), 0x3F);
494+
stub_usb_receive_cc(MIDI_CHANNEL_1, CC_GENMDM_FM_FEEDBACK, 127);
495+
midi_rx_read();
496+
497+
const u8 dumpChannelRequestSeq[] = { SYSEX_START, SYSEX_MANU_EXTENDED, SYSEX_MANU_REGION,
498+
SYSEX_MANU_ID, 0x0F, STORE_PROGRAM_TYPE_FM, midiChannel, SYSEX_END };
499+
500+
for (u16 i = 0; i < sizeof(dumpChannelRequestSeq); i++) {
501+
stub_usb_receive_byte(dumpChannelRequestSeq[i]);
502+
}
503+
504+
// Expect MDMI to send back the channel data with command 0x10
505+
const u8 dumpChannelResponseSeq[] = { SYSEX_START, SYSEX_MANU_EXTENDED, SYSEX_MANU_REGION,
506+
SYSEX_MANU_ID, 0x10, STORE_PROGRAM_TYPE_FM, midiChannel, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00,
507+
0x1A, 0x01, 0x07, 0x00, 0x07, 0x04, 0x01, 0x27, 0x00, 0x02, 0x07, 0x1F, 0x03, 0x17, 0x00,
508+
0x09, 0x0F, 0x01, 0x04, 0x00, 0x04, 0x06, 0x18, 0x01, 0x09, 0x00, 0x06, 0x09, 0x07, 0x24,
509+
0x00, 0x01, 0x03, 0x1B, 0x02, 0x04, 0x00, 0x0A, 0x04, 0x06, 0x02, 0x00, SYSEX_END };
510+
511+
for (u16 i = 0; i < sizeof(dumpChannelResponseSeq); i++) {
512+
expect_usb_sent_byte(dumpChannelResponseSeq[i]);
513+
}
514+
515+
midi_rx_read();
516+
}

tests/system/test_e2e.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ void test_midi_portamento_glides_note(void** state);
2323
void test_pitch_bends_ch3_special_mode_operators(void** state);
2424
void test_midi_pitch_bend_range_configurable_per_channel(void** state);
2525
void test_dump_preset_to_callee(void** state);
26+
void test_dump_channel_parameters_to_callee(void** state);

0 commit comments

Comments
 (0)