From f84755b14780c3ebc8be810fc77069061b7d71d7 Mon Sep 17 00:00:00 2001 From: indigodarkwolf Date: Sun, 18 Jul 2021 02:45:06 -0500 Subject: [PATCH] Prototype YM control in MIDI --- build/Makefile | 2 +- build/vs2019/box16.vcxproj | 4 + build/vs2019/box16.vcxproj.filters | 12 + src/bitutils.cpp | 1 + src/bitutils.h | 27 ++ src/midi.cpp | 164 ++++++++- src/midi.h | 9 + src/overlay/midi_overlay.cpp | 18 + src/overlay/overlay.cpp | 512 +--------------------------- src/overlay/ym2151_overlay.cpp | 517 +++++++++++++++++++++++++++++ src/overlay/ym2151_overlay.h | 36 ++ src/ym2151/ym2151.cpp | 497 +++++++++++++++++++++++++-- src/ym2151/ym2151.h | 96 +++++- 13 files changed, 1333 insertions(+), 562 deletions(-) create mode 100644 src/bitutils.cpp create mode 100644 src/bitutils.h create mode 100644 src/overlay/ym2151_overlay.cpp create mode 100644 src/overlay/ym2151_overlay.h diff --git a/build/Makefile b/build/Makefile index 135f68c..7f66a00 100644 --- a/build/Makefile +++ b/build/Makefile @@ -150,7 +150,7 @@ $(BOX16_OBJDIR)/%.o: $(BOX16_SRCDIR)/%.cpp | $(BOX16_OBJDIRS) $(BOX16_OBJDIRS): mkdir -p $@ -$(OUTDIR)/box16: $(NFD_OBJS) $(LPNG_OBJS) $(RTMIDI_OBJS) $(YMFM_OBJS) $(BOX16_OBJS) +$(OUTDIR)/box16: $(BOX16_OBJS) $(NFD_OBJS) $(LPNG_OBJS) $(RTMIDI_OBJS) $(YMFM_OBJS) mkdir -p $(OUTDIR) g++ $^ -o $@ $(BOX16_LDFLAGS) $(NFD_LDFLAGS) cp $(REPODIR)/resources/*.png $(OUTDIR)/ diff --git a/build/vs2019/box16.vcxproj b/build/vs2019/box16.vcxproj index 19ad88b..5152347 100644 --- a/build/vs2019/box16.vcxproj +++ b/build/vs2019/box16.vcxproj @@ -214,6 +214,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y + @@ -245,6 +246,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y + @@ -264,6 +266,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y + @@ -304,6 +307,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y + diff --git a/build/vs2019/box16.vcxproj.filters b/build/vs2019/box16.vcxproj.filters index 40acd1f..dbe105e 100644 --- a/build/vs2019/box16.vcxproj.filters +++ b/build/vs2019/box16.vcxproj.filters @@ -185,6 +185,12 @@ Source Files\imgui + + Source Files + + + Source Files\overlay + @@ -385,6 +391,12 @@ Vendor Source Files\lodepng + + Source Files + + + Source Files\overlay + diff --git a/src/bitutils.cpp b/src/bitutils.cpp new file mode 100644 index 0000000..32b0ad9 --- /dev/null +++ b/src/bitutils.cpp @@ -0,0 +1 @@ +#include "bitutils.h" diff --git a/src/bitutils.h b/src/bitutils.h new file mode 100644 index 0000000..c51a388 --- /dev/null +++ b/src/bitutils.h @@ -0,0 +1,27 @@ +#pragma once +#if !defined(BITUTILS_H) +# define BITUTILS_H + +template +uint8_t get_bit_field(const uint8_t value) +{ + static_assert(msb >= lsb); + constexpr const uint8_t mask = (2 << (msb - lsb)) - 1; + return (value >> lsb) & mask; +} + +template +uint8_t set_bit_field(const uint8_t src, const uint8_t value) +{ + static_assert(msb >= lsb); + constexpr const uint8_t mask = (2 << (msb - lsb)) - 1; + return (src & ~(mask << lsb)) | (value << lsb); +} + +template +constexpr T bit_set_or_res(const T val, const T mask, bool cond) +{ + return cond ? (val | mask) : (val & ~mask); +} + +#endif \ No newline at end of file diff --git a/src/midi.cpp b/src/midi.cpp index ec5d9d9..b62691d 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -1,12 +1,12 @@ #include "midi.h" -#include -#include "math.h" #include "RtMidi.h" - -#include "vera/vera_psg.h" +#include "math.h" +#include #include +#include "vera/vera_psg.h" +#include "ym2151/ym2151.h" #define MAX_MIDI_KEYS (128) @@ -37,10 +37,15 @@ struct psg_midi_mapping { midi_channel channel; }; +struct ym_midi_mapping { + midi_channel channel; +}; + const midi_port_descriptor INVALID_MIDI_PORT{ RtMidi::Api::NUM_APIS, 0xffff }; const midi_channel INVALID_MIDI_CHANNEL{ INVALID_MIDI_PORT, 0xff }; static psg_midi_mapping Psg_midi_mappings[PSG_NUM_CHANNELS]; +static ym_midi_mapping Ym_midi_mappings[MAX_YM2151_VOICES]; static RtMidiIn Midi_in_api; @@ -181,6 +186,18 @@ static uint16_t Psg_frequency_table[MAX_MIDI_KEYS] = { 33672, }; +midi_ym_patch_entry Ym_default_patch[] = { + { 0x20, 0xc0 }, // YM_R_L_FB_CONN_OFFSET + { 0x58, 0x01 }, // YM_DT1_MUL_OFFSET voice 4 slot 3 + { 0x68, 0x00 }, // YM_TL_OFFSET voice 4 slot 1 + { 0x70, 0x00 }, // YM_TL_OFFSET voice 4 slot 2 + { 0x78, 0x00 }, // YM_TL_OFFSET voice 4 slot 3 + { 0x60, 0x00 }, // YM_TL_OFFSET voice 4 slot 4 + { 0x98, 0x1f }, // YM_KS_AR_OFFSET voice 4 slot 3 + { 0xb8, 0x0d }, // YM_A_D1R_OFFSET voice 4 slot 3 + { 0xf8, 0xf6 } // YM_D1L_RR_OFFSET voice 4 slot 3 +}; + // ------------------- // // midi_port_descriptor @@ -258,6 +275,48 @@ static uint8_t alloc_psg_voice() return INVALID_VOICE; } +// ------------------- +// +// ym helpers +// +// ------------------- + +static uint8_t alloc_ym_voice() +{ + for (uint8_t i = 0; i < MAX_YM2151_VOICES; ++i) { + if (Ym_midi_mappings[i].channel == INVALID_MIDI_CHANNEL) { + return i; + } + } + return INVALID_VOICE; +} + +static void apply_ym_patch(uint8_t ym_channel, midi_ym_patch_entry *patch_data, int num_entries) +{ + for (int i = 0; i < num_entries; ++i) { + YM_write(0, patch_data[i].addr + ym_channel); + YM_write(1, patch_data[i].value); + } +} + +uint8_t midi_ym_note(uint8_t midikey) +{ + const uint8_t chroma = (midikey + 11) % 12; + return chroma + (chroma / 3); +} + +uint8_t midi_ym_octave(uint8_t midikey) +{ + if (midikey < 13) { + return 0; + } + const uint8_t result = (midikey - 13) / 12; + if (result > 7) { + return 7; + } + return result; +} + // ------------------- // // midi message helpers @@ -290,10 +349,16 @@ static uint16_t get_bent_frequency(int keynum, int bend) return (uint16_t)(f0 + ((diff * bend) >> 13)); } -static uint8_t get_velocitated_volume(int volume, int velocity) +static uint8_t get_psg_velocitated_volume(int volume, int velocity) +{ + const float vv = sqrtf((float)(volume * velocity) / (127.0f * 127.0f)); + return (uint8_t)(vv * 63.0f); +} + +static uint8_t get_ym_velocitated_volume(int volume, int velocity) { - const int vv = (const int)sqrtf((float)(volume * velocity)); // max 7 bits - return (uint8_t)(vv >> 1); + const int vv = (const int)sqrtf((float)(volume * velocity) / (127.0f * 127.0f)); + return (uint8_t)(vv * 127.0f); } static void note_off(open_midi_port &port, uint8_t channel, int keynum, int velocity) @@ -309,7 +374,8 @@ static void note_off(open_midi_port &port, uint8_t channel, int keynum, int velo psg_set_channel_volume(key.voice, 0); break; case midi_playback_device::ym2151: - // TODO: Implement me. + YM_key_on(key.voice, false, false, false, false); + Ym_midi_mappings[key.voice].channel = INVALID_MIDI_CHANNEL; break; default: break; @@ -349,10 +415,27 @@ static void note_on(open_midi_port &port, uint8_t channel, int keynum, int veloc psg_set_channel_pulse_width(key.voice, (uint8_t)(settings.modulation >> 1)); psg_set_channel_left(key.voice, settings.pan < 96); psg_set_channel_right(key.voice, settings.pan > 32); - psg_set_channel_volume(key.voice, settings.use_velocity ? get_velocitated_volume(settings.volume, velocity) : (uint8_t)(settings.volume >> 1)); + psg_set_channel_volume(key.voice, settings.use_velocity ? get_psg_velocitated_volume(settings.volume, velocity) : (uint8_t)(settings.volume >> 1)); break; case midi_playback_device::ym2151: - // TODO: Implement me. + if (key.voice == INVALID_VOICE) { + key.voice = alloc_ym_voice(); + } + if (key.voice == INVALID_VOICE) { + return; + } + Ym_midi_mappings[key.voice].channel = { port.descriptor, channel }; + apply_ym_patch(key.voice, settings.device.ym2151.patch_bytes, settings.device.ym2151.patch_size); + for (int i = 0; i < 4; ++i) { + const uint8_t tl = YM_get_operator_total_level(key.voice, i); + const uint8_t vv = settings.use_velocity ? (get_psg_velocitated_volume(settings.volume, velocity) << 1) : (uint8_t)(settings.volume); + const uint8_t fv = 127 - (uint8_t)(((uint16_t)vv * (uint16_t)(127 - tl)) >> 7); + YM_set_operator_total_level(key.voice, i, fv); + } + YM_set_voice_key_fraction(key.voice, settings.pitch_bend >> 8); + YM_set_voice_octave(key.voice, midi_ym_octave(keynum)); + YM_set_voice_note(key.voice, midi_ym_note(keynum)); + YM_key_on(key.voice); break; default: break; @@ -643,7 +726,12 @@ static void pitch_bend(open_midi_port &port, uint8_t channel, int bend) } break; case midi_playback_device::ym2151: - // TODO: Implement me. + for (int i = 0; i < MAX_MIDI_KEYS; ++i) { + const midi_key &key = port.channels[channel].keys_on[i]; + if (key.voice != INVALID_VOICE) { + YM_set_voice_key_fraction(key.voice, bend >> 8); + } + } break; default: break; @@ -716,6 +804,9 @@ void midi_init() for (uint8_t i = 0; i < PSG_NUM_CHANNELS; ++i) { Psg_midi_mappings[i].channel = INVALID_MIDI_CHANNEL; } + for (uint8_t i = 0; i < MAX_YM2151_VOICES; ++i) { + Ym_midi_mappings[i].channel = INVALID_MIDI_CHANNEL; + } } void midi_process() @@ -826,7 +917,7 @@ void midi_port_set_channel_playback_device(midi_port_descriptor port, uint8_t ch auto &[port_number, open_port] = *value; midi_channel_state &state = open_port.channels[channel]; - midi_key keys_on[MAX_MIDI_KEYS]; + midi_key keys_on[MAX_MIDI_KEYS]; memcpy(keys_on, state.keys_on, sizeof(midi_key) * MAX_MIDI_KEYS); for (uint8_t i = 0; i < MAX_MIDI_KEYS; ++i) { @@ -844,6 +935,11 @@ void midi_port_set_channel_playback_device(midi_port_descriptor port, uint8_t ch } } } + + if (d == midi_playback_device::ym2151) { + memcpy(state.settings.device.ym2151.patch_bytes, Ym_default_patch, sizeof(Ym_default_patch)); + state.settings.device.ym2151.patch_size = sizeof(Ym_default_patch) / sizeof(Ym_default_patch[0]); + } } } } @@ -855,7 +951,7 @@ void midi_port_set_channel_use_velocity(midi_port_descriptor port, uint8_t chann if (value != Open_midi_ports.end()) { auto &[port_number, open_port] = *value; - midi_channel_state &state = open_port.channels[channel]; + midi_channel_state &state = open_port.channels[channel]; state.settings.use_velocity = use_velocity; } } @@ -876,7 +972,47 @@ void midi_port_set_channel_psg_waveform(midi_port_descriptor port, uint8_t chann } } - state.settings.device.psg.waveform = waveform; + state.settings.device.psg.waveform = waveform; + } + } +} + +void midi_port_set_channel_ym2151_patch_byte(midi_port_descriptor port, uint8_t channel, uint8_t addr, uint8_t data) +{ + if (channel < MAX_MIDI_CHANNELS) { + auto value = Open_midi_ports.find(port); + if (value != Open_midi_ports.end()) { + auto &[port_number, open_port] = *value; + + midi_channel_state &state = open_port.channels[channel]; + + midi_ym2151_settings &settings = state.settings.device.ym2151; + for (int i = 0; i < settings.patch_size; ++i) { + if (settings.patch_bytes[i].addr == addr) { + settings.patch_bytes[i].value = data; + return; + } + } + settings.patch_bytes[settings.patch_size].addr = addr; + settings.patch_bytes[settings.patch_size].value = data; + ++settings.patch_size; + } + } +} + +void midi_port_get_channel_ym2151_patch(midi_port_descriptor port, uint8_t channel, uint8_t *bytes) +{ + if (channel < MAX_MIDI_CHANNELS) { + auto value = Open_midi_ports.find(port); + if (value != Open_midi_ports.end()) { + auto &[port_number, open_port] = *value; + + midi_channel_state &state = open_port.channels[channel]; + + midi_ym2151_settings &settings = state.settings.device.ym2151; + for (int i = 0; i < settings.patch_size; ++i) { + bytes[settings.patch_bytes[i].addr] = settings.patch_bytes[i].value; + } } } } diff --git a/src/midi.h b/src/midi.h index 7121289..5ec485a 100644 --- a/src/midi.h +++ b/src/midi.h @@ -33,7 +33,14 @@ struct midi_psg_settings { uint8_t waveform = 0; }; +struct midi_ym_patch_entry { + uint8_t addr; + uint8_t value; +}; + struct midi_ym2151_settings { + midi_ym_patch_entry patch_bytes[256]; + int patch_size = 0; }; struct midi_channel_settings { @@ -73,5 +80,7 @@ void midi_port_set_channel_playback_device(midi_port_descriptor port, uint8_t ch void midi_port_set_channel_use_velocity(midi_port_descriptor port, uint8_t channel, bool use_velocity); void midi_port_set_channel_psg_waveform(midi_port_descriptor port, uint8_t channel, uint8_t waveform); +void midi_port_set_channel_ym2151_patch_byte(midi_port_descriptor port, uint8_t channel, uint8_t addr, uint8_t value); +void midi_port_get_channel_ym2151_patch(midi_port_descriptor port, uint8_t channel, uint8_t *bytes); #endif diff --git a/src/overlay/midi_overlay.cpp b/src/overlay/midi_overlay.cpp index 31410ec..154b95e 100644 --- a/src/overlay/midi_overlay.cpp +++ b/src/overlay/midi_overlay.cpp @@ -4,6 +4,8 @@ #include "midi.h" #include "vera/vera_psg.h" +#include "ym2151/ym2151.h" +#include "ym2151_overlay.h" void draw_midi_overlay() { @@ -61,6 +63,22 @@ void draw_midi_overlay() if (ImGui::Combo("Waveform", &wf, waveforms, IM_ARRAYSIZE(waveforms))) { midi_port_set_channel_psg_waveform(port, i, (uint8_t)wf); } + } else if (settings->playback_device == midi_playback_device::ym2151) { + uint8_t ym_regs[256]; + memset(ym_regs, 0, sizeof(ym_regs)); + + YM_get_modulation_regs(ym_regs); + midi_port_get_channel_ym2151_patch(port, i, ym_regs); + if (ImGui::TreeNodeEx("LFO & Noise", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { + debugger_draw_ym_lfo_and_noise(ym_regs); + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Channel Data", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { + static ym_channel_data channel; + static ym_keyon_state keyon; + debugger_draw_ym_voices(ym_regs, &channel, &keyon, 1, [port, i](uint8_t addr, uint8_t value) { midi_port_set_channel_ym2151_patch_byte(port, i, addr, value); }); + ImGui::TreePop(); + } } bool velocity = settings->use_velocity; diff --git a/src/overlay/overlay.cpp b/src/overlay/overlay.cpp index 4babd26..0d47757 100644 --- a/src/overlay/overlay.cpp +++ b/src/overlay/overlay.cpp @@ -3,8 +3,8 @@ #include #include -#include #include +#include #include "imgui/imgui.h" #include "imgui/imgui_impl_sdl.h" @@ -16,21 +16,22 @@ #include "vram_dump.h" #include "audio.h" +#include "bitutils.h" #include "cpu/fake6502.h" #include "debugger.h" #include "display.h" #include "glue.h" #include "joystick.h" #include "keyboard.h" -#include "options_menu.h" #include "midi_overlay.h" +#include "options_menu.h" #include "smc.h" #include "symbols.h" #include "timing.h" #include "vera/sdcard.h" #include "vera/vera_psg.h" #include "vera/vera_video.h" -#include "ym2151/ym2151.h" +#include "ym2151_overlay.h" bool Show_options = false; bool Show_imgui_demo = false; @@ -1164,9 +1165,9 @@ static void draw_debugger_vera_psg() for (unsigned int i = 0; i < 16; ++i) { ImGui::TableNextRow(); if (i == 0) { - ImGui::TableSetColumnIndex(2); // freq + ImGui::TableSetColumnIndex(2); // freq ImGui::PushItemWidth(-FLT_MIN); // Right-aligned - ImGui::TableSetColumnIndex(3); // wave + ImGui::TableSetColumnIndex(3); // wave ImGui::PushItemWidth(-FLT_MIN); ImGui::TableSetColumnIndex(4); // width ImGui::PushItemWidth(-FLT_MIN); @@ -1284,505 +1285,6 @@ static void draw_debugger_vera_psg() } } -static void ym2151_reg_input(uint8_t *regs, uint8_t idx) -{ - ImGui::TableSetColumnIndex((idx & 0xf) + 1); - if (ImGui::InputHex(idx, regs[idx])) { - YM_debug_write(idx, regs[idx]); - } -} - -template -constexpr T bit_set_or_res(T val, T mask, bool cond) -{ - return cond ? (val | mask) : (val & ~mask); -} - -static void draw_debugger_ym2151() -{ - uint8_t regs[256]; - uint8_t status = YM_read_status(); - for (int i = 0; i < 256; i++) { - regs[i] = YM_debug_read(i); - } - if (ImGui::TreeNodeEx("Interface", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { - uint8_t addr = YM_last_address(); - uint8_t data = YM_last_data(); - if (ImGui::InputHexLabel("Address", addr)) { - YM_write(0, addr); - } - ImGui::SameLine(); - if (ImGui::InputHexLabel("Data", data)) { - YM_write(1, data); - } - ImGui::SameLine(); - ImGui::InputHexLabel("Status", status); - - ImGui::TreePop(); - } - if (ImGui::TreeNodeEx("Raw Bytes", ImGuiTreeNodeFlags_Framed)) { - if (ImGui::BeginTable("ym raw bytes", 17, ImGuiTableFlags_SizingFixedFit)) { - static const char *row_txts[16] = { - "0x", "1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x", "9x", "Ax", "Bx", "Cx", "Dx", "Ex", "Fx" - }; - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::Text(row_txts[0]); - ym2151_reg_input(regs, 0x01); // TEST - ym2151_reg_input(regs, 0x08); // KEYON - ym2151_reg_input(regs, 0x0F); // NOISE - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::Text(row_txts[1]); - ym2151_reg_input(regs, 0x10); // CLKA1 - ym2151_reg_input(regs, 0x11); // CLKA2 - ym2151_reg_input(regs, 0x12); // CLKB - ym2151_reg_input(regs, 0x14); // CONTROL - ym2151_reg_input(regs, 0x18); // LFRQ - ym2151_reg_input(regs, 0x19); // PMD/AMD - ym2151_reg_input(regs, 0x1B); // CT/W - // no unused registers at this point - for (int i = 2; i < 16; i++) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::Text(row_txts[i]); - for (int j = 0; j < 16; j++) { - ym2151_reg_input(regs, i * 16 + j); - } - } - ImGui::EndTable(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNodeEx("Timer & Control", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("ym timer & control", 7)) { - struct { - bool en, irq, ovf; - int reload, cur; - } timer[2]; - bool csm = regs[0x14] & (1 << 7); - bool ct1 = regs[0x1B] & (1 << 6); - bool ct2 = regs[0x1B] & (1 << 7); - - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - - for (int i = 0; i < 2; i++) { - const uint8_t EN_MASK = 1 << (i + 0); - const uint8_t IRQ_MASK = 1 << (i + 2); - const uint8_t RES_MASK = 1 << (i + 4); - const uint8_t OVF_MASK = 1 << (i + 0); - const int TIM_MAX = i ? 255 : 1023; - auto tim = &timer[i]; - tim->en = regs[0x14] & EN_MASK; - tim->irq = regs[0x14] & IRQ_MASK; - tim->ovf = status & OVF_MASK; - tim->reload = i ? regs[0x12] : regs[0x10] * 4 + (regs[0x11] & 0x03); - tim->cur = YM_get_timer_counter(i); - - ImGui::PushID(i); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(i ? "Timer B" : "Timer A"); - ImGui::TableNextColumn(); - if (ImGui::Checkbox("Enable", &tim->en)) { - YM_debug_write(0x14, bit_set_or_res(regs[0x14], EN_MASK, tim->en)); - } - ImGui::TableNextColumn(); - if (ImGui::Checkbox("IRQ Enable", &tim->irq)) { - YM_debug_write(0x14, bit_set_or_res(regs[0x14], IRQ_MASK, tim->irq)); - } - ImGui::TableNextColumn(); - ImGui::Checkbox("Overflow", &tim->ovf); - ImGui::TableNextColumn(); - if (ImGui::Button("Reset")) { - YM_debug_write(0x14, regs[0x14] | RES_MASK); - } - ImGui::TableNextColumn(); - if (ImGui::SliderInt("Reload", &tim->reload, 0, TIM_MAX)) { - if (i) { - // timer b - YM_debug_write(0x12, tim->reload); - } else { - // timer a - YM_debug_write(0x10, tim->reload >> 2); - YM_debug_write(0x11, regs[0x11] & ~0x03 | (tim->reload & 0x03)); - } - } - ImGui::TableNextColumn(); - char buf[5]; - std::sprintf(buf, "%d", tim->cur); - ImGui::ProgressBar(tim->cur / (float)TIM_MAX, ImVec2(0, 0), buf); - ImGui::SameLine(); - ImGui::Text("Counter"); - - ImGui::PopID(); - } - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(1); - if (ImGui::Checkbox("CSM", &csm)) { - YM_debug_write(0x14, bit_set_or_res(regs[0x14], (uint8_t)(1 << 7), csm)); - } - ImGui::TableNextColumn(); - if (ImGui::Checkbox("CT1", &ct1)) { - YM_debug_write(0x1B, bit_set_or_res(regs[0x1B], (uint8_t)(1 << 6), ct1)); - } - ImGui::TableNextColumn(); - if (ImGui::Checkbox("CT2", &ct2)) { - YM_debug_write(0x1B, bit_set_or_res(regs[0x1B], (uint8_t)(1 << 7), ct2)); - } - ImGui::EndTable(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNodeEx("LFO & Noise", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("ym lfo & noise", 2, ImGuiTableFlags_SizingStretchSame)) { - static const char *waveforms[] = { - "Sawtooth", - "Square", - "Triangle", - "Noise" - }; - const uint8_t LRES_MASK = (1 << 1); - const uint8_t LW_MASK = 0x03; - bool lres = regs[0x01] & LRES_MASK; - int lw = regs[0x1B] & LW_MASK; - int lfrq = regs[0x18]; - float lcnt = YM_get_LFO_phase(); - int amd = YM_get_AMD(); - int pmd = YM_get_PMD(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("LFO"); - ImGui::SameLine(72); - if (ImGui::Checkbox("Reset", &lres)) { - YM_debug_write(0x01, bit_set_or_res(regs[0x01], LRES_MASK, lres)); - } - ImGui::TableNextColumn(); - if (ImGui::Combo("Waveform", &lw, waveforms, 4)) { - YM_debug_write(0x1B, regs[0x1B] & ~LW_MASK | lw); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::SliderInt("LFO Freq", &lfrq, 0, 255)) { - YM_debug_write(0x18, lfrq); - } - ImGui::TableNextColumn(); - char buf[4]; - std::sprintf(buf, "%d", (int)(lcnt * 256)); - ImGui::ProgressBar(lcnt, ImVec2(0, 0), buf); - ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text("Cur. Phase"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::SliderInt("AMD", &amd, 0, 127)) { - YM_debug_write(0x19, amd); - } - ImGui::TableNextColumn(); - if (ImGui::SliderInt("PMD", &pmd, 0, 127)) { - YM_debug_write(0x19, pmd | 0x80); - } - - const uint8_t NEN_MASK = (1 << 7); - const uint8_t NFRQ_MASK = 0x1F; - bool nen = regs[0x0F] & NEN_MASK; - int nfrq = regs[0x0F] & NFRQ_MASK; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Noise"); - ImGui::SameLine(72); - if (ImGui::Checkbox("Enable", &nen)) { - YM_debug_write(0x0F, bit_set_or_res(regs[0x0F], NEN_MASK, nen)); - } - ImGui::TableNextColumn(); - if (ImGui::SliderInt("Frequency", &nfrq, 31, 0)) { - YM_debug_write(0x0F, regs[0x0F] & ~NFRQ_MASK | nfrq); - } - - ImGui::EndTable(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNodeEx("Channels", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("ym channels", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersInnerV)) { - static const uint8_t slot_map[4] = { 0, 16, 8, 24 }; - static struct { - int con, fb; - bool l, r; - float kc; - int ams, pms; - bool debug_kon[4] = { 1, 1, 1, 1}; - int dkob_state = 0; - struct { - int dt1, dt2, mul; - int ar, d1r, d1l, d2r, rr, ks; - int tl; - bool ame; - } slot[4]; - } channel[8]; - - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // chan num - ImGui::TableSetupColumn("", 0, 0.4f); // chan regs - ImGui::TableSetupColumn(""); // slot regs - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // slot con - - for (int i = 0; i < 8; i++) { - const uint8_t confb = 0x20 + i; - const uint8_t kc = 0x28 + i; - const uint8_t kf = 0x30 + i; - const uint8_t amspms = 0x38 + i; - const uint8_t tmp_kc = regs[kc] & 0x7f; - auto ch = &channel[i]; - - ch->l = regs[confb] & (1 << 6); - ch->r = regs[confb] & (1 << 7); - ch->con = regs[confb] & 0x07; - ch->fb = (regs[confb] >> 3) & 0x07; - ch->kc = (tmp_kc - ((tmp_kc + 1) >> 2)) + (regs[kf] / 256.f); - ch->ams = regs[amspms] & 0x03; - ch->pms = (regs[amspms] >> 4) & 0x07; - int fpkc = (int)(ch->kc * 256); - - ImGui::PushID(i); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%d", i); - - // Channel - ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 8); - if (ImGui::BeginTable("confb", 4)) { - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(12); - if (ImGui::Checkbox("L", &ch->l)) { - YM_debug_write(confb, bit_set_or_res(regs[confb], (uint8_t)(1 << 6), ch->l)); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(12); - if (ImGui::Checkbox("R", &ch->r)) { - YM_debug_write(confb, bit_set_or_res(regs[confb], (uint8_t)(1 << 7), ch->r)); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(12); - if (ImGui::DragInt("CON", &ch->con, 1, 0, 7)) { - YM_debug_write(confb, regs[confb] & ~0x07 | ch->con); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-28); - if (ImGui::SliderInt("FB", &ch->fb, 0, 7)) { - YM_debug_write(confb, regs[confb] & ~0x38 | (ch->fb << 3)); - } - ImGui::EndTable(); - } - - if (ImGui::BeginTable("amspms", 2)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-28); - if (ImGui::SliderInt("AMS", &ch->ams, 0, 3)) { - YM_debug_write(amspms, regs[amspms] & ~0x03 | ch->ams); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-28); - if (ImGui::SliderInt("PMS", &ch->pms, 0, 7)) { - YM_debug_write(amspms, regs[amspms] & ~0x70 | (ch->pms << 4)); - } - ImGui::EndTable(); - } - - const char notes[] = "C-C#D-D#E-F-F#G-G#A-A#B-"; - float cents = (fpkc & 0xFF) * 100.f / 256.f; - if (cents > 50) { - cents = cents - 100; - } - const uint8_t note = (fpkc >> 8) + (cents < 0) + 1; - const uint8_t ni = (note % 12) * 2; - const uint8_t oct = note / 12; - // C#8 +00.0 - char kcinfo[12]; - std::sprintf(kcinfo, "%c%c%d %+05.1f", notes[ni], notes[ni + 1], oct, cents); - ImGui::SetNextItemWidth(-28); - if (ImGui::SliderFloat("KC", &ch->kc, 0, 96, kcinfo, ImGuiSliderFlags_NoRoundToFormat)) { - fpkc = std::min((int)(ch->kc * 256), (96 * 256) - 1); - YM_debug_write(kc, (fpkc >> 8) * 4 / 3); - YM_debug_write(kf, fpkc & 0xFF); - } - - ImGui::Button("KeyOn"); - ch->dkob_state = (ch->dkob_state << 1) | (int)ImGui::IsItemActive(); - switch (ch->dkob_state & 0b11) { - case 0b01: // keyon checked slots - YM_debug_write(0x08, i | (ch->debug_kon[0] << 3) | (ch->debug_kon[1] << 4) | (ch->debug_kon[2] << 5) | (ch->debug_kon[3] << 6)); - break; - case 0b10: // keyoff all slots - YM_debug_write(0x08, i); - break; - } - ImGui::PushID("konslots"); - for (int j = 0; j < 4; j++) { - ImGui::PushID(j); - ImGui::SameLine(); - ImGui::Checkbox("", &ch->debug_kon[j]); - ImGui::PopID(); - } - ImGui::PopID(); - ImGui::PopStyleVar(3); - - // Slot - ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 2)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 6); - if (ImGui::BeginTable("slot", 15)) { - ImGui::TableSetupColumn("Sl", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("DT1"); - ImGui::TableSetupColumn("DT2"); - ImGui::TableSetupColumn("MUL"); - ImGui::TableSetupColumn("=Freq", ImGuiTableColumnFlags_WidthFixed, 44); - ImGui::TableSetupColumn("AR"); - ImGui::TableSetupColumn("D1R"); - ImGui::TableSetupColumn("D1L"); - ImGui::TableSetupColumn("D2R"); - ImGui::TableSetupColumn("RR"); - ImGui::TableSetupColumn("KS"); - ImGui::TableSetupColumn("Env"); - ImGui::TableSetupColumn("TL"); - ImGui::TableSetupColumn("AM", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("Out"); - ImGui::TableHeadersRow(); - - for (int j = 0; j < 4; j++) { - const int slnum = slot_map[j] + i; - const uint8_t muldt1 = 0x40 + slnum; - const uint8_t tl = 0x60 + slnum; - const uint8_t arks = 0x80 + slnum; - const uint8_t d1rame = 0xA0 + slnum; - const uint8_t d2rdt2 = 0xC0 + slnum; - const uint8_t rrd1l = 0xE0 + slnum; - auto slot = &(channel[i].slot[j]); - - slot->mul = regs[muldt1] & 0x0F; - slot->dt1 = (regs[muldt1] >> 4) & 0x07; - slot->tl = regs[tl] & 0x7F; - slot->ar = regs[arks] & 0x1F; - slot->ks = regs[arks] >> 6; - slot->d1r = regs[d1rame] & 0x1F; - slot->ame = regs[d1rame] & 0x80; - slot->d2r = regs[d2rdt2] & 0x1F; - slot->dt2 = regs[d2rdt2] >> 6; - slot->rr = regs[rrd1l] & 0x0F; - slot->d1l = regs[rrd1l] >> 4; - - ImGui::PushID(j); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%d", slot_map[j] + i); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("dt1", &slot->dt1, 0, 7)) { - YM_debug_write(muldt1, regs[muldt1] & ~0x70 | (slot->dt1 << 4)); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("dt2", &slot->dt2, 0, 3)) { - YM_debug_write(d2rdt2, regs[d2rdt2] & ~0xC0 | (slot->dt2 << 6)); - } - ImGui::TableNextColumn(); - char buf[11] = ".5"; - if (slot->mul > 0) { - std::sprintf(buf, "%d", slot->mul); - } - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("mul", &slot->mul, 0, 15, buf)) { - YM_debug_write(muldt1, regs[muldt1] & ~0x0F | slot->mul); - } - ImGui::TableNextColumn(); - ImGui::Text("%d", YM_get_freq(slnum)); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("ar", &slot->ar, 31, 0)) { - YM_debug_write(arks, regs[arks] & ~0x1F | slot->ar); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("d1r", &slot->d1r, 31, 0)) { - YM_debug_write(d1rame, regs[d1rame] & ~0x1F | slot->d1r); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("d1l", &slot->d1l, 15, 0)) { - YM_debug_write(rrd1l, regs[rrd1l] & ~0xF0 | (slot->d1l << 4)); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("d2r", &slot->d2r, 31, 0)) { - YM_debug_write(d2rdt2, regs[d2rdt2] & ~0x1F | slot->d2r); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("rr", &slot->rr, 15, 0)) { - YM_debug_write(rrd1l, regs[rrd1l] & ~0x0F | slot->rr); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("ks", &slot->ks, 0, 3)) { - YM_debug_write(arks, regs[arks] & ~0xC0 | (slot->ks << 6)); - } - ImGui::TableNextColumn(); - char envstate_txt[] = " "; - envstate_txt[0] = " ADSR"[YM_get_env_state(slnum)]; - ImGui::ProgressBar(YM_get_EG_output(slnum), ImVec2(-FLT_MIN, 0), envstate_txt); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::SliderInt("tl", &slot->tl, 127, 0)) { - YM_debug_write(tl, regs[tl] & ~0x7F | slot->tl); - } - ImGui::TableNextColumn(); - ImGui::PushID("amelia"); - if (ImGui::Checkbox("", &slot->ame)) { - YM_debug_write(d1rame, bit_set_or_res(regs[d1rame], (uint8_t) 0x80, slot->ame)); - } - ImGui::PopID(); - float out = YM_get_final_env(slnum); - char buf2[5]; - std::sprintf(buf2, "%d", (int)((1 - out) * 1024)); - ImGui::TableNextColumn(); - ImGui::ProgressBar(out, ImVec2(-FLT_MIN, 0), buf2); - - ImGui::PopID(); - } - ImGui::EndTable(); - } - ImGui::PopStyleVar(3); - - // CON gfx - ImGui::TableNextColumn(); - ImGui::Dummy(ImVec2(16, 15)); - // this is so ugly - ImGui::Tile((display_icons)((int)ICON_FM_ALG + ch->con), ImVec2(16, 64)); - - ImGui::PopID(); - } - ImGui::EndTable(); - } - ImGui::TreePop(); - } -} - static void draw_menu_bar() { if (ImGui::BeginMainMenuBar()) { @@ -2066,7 +1568,7 @@ void overlay_draw() if (ImGui::Begin("MIDI Control", &Show_midi_overlay)) { draw_midi_overlay(); } - ImGui::End(); + ImGui::End(); } } diff --git a/src/overlay/ym2151_overlay.cpp b/src/overlay/ym2151_overlay.cpp new file mode 100644 index 0000000..1991248 --- /dev/null +++ b/src/overlay/ym2151_overlay.cpp @@ -0,0 +1,517 @@ +#include "ym2151_overlay.h" + +#include "bitutils.h" +#include "display.h" +#include "imgui/imgui.h" +#include "util.h" +#include "ym2151/ym2151.h" + +static void ym2151_reg_input(uint8_t *regs, uint8_t idx) +{ + ImGui::TableSetColumnIndex((idx & 0xf) + 1); + if (ImGui::InputHex(idx, regs[idx])) { + YM_debug_write(idx, regs[idx]); + } +} + +void draw_debugger_ym2151() +{ + uint8_t regs[256]; + uint8_t status = YM_read_status(); + for (int i = 0; i < 256; i++) { + regs[i] = YM_debug_read(i); + } + if (ImGui::TreeNodeEx("Interface", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { + uint8_t addr = YM_last_address(); + uint8_t data = YM_last_data(); + if (ImGui::InputHexLabel("Address", addr)) { + YM_write(0, addr); + } + ImGui::SameLine(); + if (ImGui::InputHexLabel("Data", data)) { + YM_write(1, data); + } + ImGui::SameLine(); + ImGui::InputHexLabel("Status", status); + + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Raw Bytes", ImGuiTreeNodeFlags_Framed)) { + if (ImGui::BeginTable("ym raw bytes", 17, ImGuiTableFlags_SizingFixedFit)) { + static const char *row_txts[16] = { + "0x", "1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x", "9x", "Ax", "Bx", "Cx", "Dx", "Ex", "Fx" + }; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(row_txts[0]); + ym2151_reg_input(regs, 0x01); // TEST + ym2151_reg_input(regs, 0x08); // KEYON + ym2151_reg_input(regs, 0x0F); // NOISE + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(row_txts[1]); + ym2151_reg_input(regs, 0x10); // CLKA1 + ym2151_reg_input(regs, 0x11); // CLKA2 + ym2151_reg_input(regs, 0x12); // CLKB + ym2151_reg_input(regs, 0x14); // CONTROL + ym2151_reg_input(regs, 0x18); // LFRQ + ym2151_reg_input(regs, 0x19); // PMD/AMD + ym2151_reg_input(regs, 0x1B); // CT/W + // no unused registers at this point + for (int i = 2; i < 16; i++) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(row_txts[i]); + for (int j = 0; j < 16; j++) { + ym2151_reg_input(regs, i * 16 + j); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Timer & Control", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::BeginTable("ym timer & control", 7)) { + struct { + bool en, irq, ovf; + int reload, cur; + } timer[2]; + bool csm = regs[0x14] & (1 << 7); + bool ct1 = regs[0x1B] & (1 << 6); + bool ct2 = regs[0x1B] & (1 << 7); + + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + + for (int i = 0; i < 2; i++) { + const uint8_t EN_MASK = 1 << (i + 0); + const uint8_t IRQ_MASK = 1 << (i + 2); + const uint8_t RES_MASK = 1 << (i + 4); + const uint8_t OVF_MASK = 1 << (i + 0); + const int TIM_MAX = i ? 255 : 1023; + auto tim = &timer[i]; + tim->en = regs[0x14] & EN_MASK; + tim->irq = regs[0x14] & IRQ_MASK; + tim->ovf = status & OVF_MASK; + tim->reload = i ? regs[0x12] : regs[0x10] * 4 + (regs[0x11] & 0x03); + tim->cur = YM_get_timer_counter(i); + + ImGui::PushID(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(i ? "Timer B" : "Timer A"); + ImGui::TableNextColumn(); + if (ImGui::Checkbox("Enable", &tim->en)) { + YM_debug_write(0x14, bit_set_or_res(regs[0x14], EN_MASK, tim->en)); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("IRQ Enable", &tim->irq)) { + YM_debug_write(0x14, bit_set_or_res(regs[0x14], IRQ_MASK, tim->irq)); + } + ImGui::TableNextColumn(); + ImGui::Checkbox("Overflow", &tim->ovf); + ImGui::TableNextColumn(); + if (ImGui::Button("Reset")) { + YM_debug_write(0x14, regs[0x14] | RES_MASK); + } + ImGui::TableNextColumn(); + if (ImGui::SliderInt("Reload", &tim->reload, 0, TIM_MAX)) { + if (i) { + // timer b + YM_debug_write(0x12, tim->reload); + } else { + // timer a + YM_debug_write(0x10, tim->reload >> 2); + YM_debug_write(0x11, regs[0x11] & ~0x03 | (tim->reload & 0x03)); + } + } + ImGui::TableNextColumn(); + char buf[5]; + std::sprintf(buf, "%d", tim->cur); + ImGui::ProgressBar(tim->cur / (float)TIM_MAX, ImVec2(0, 0), buf); + ImGui::SameLine(); + ImGui::Text("Counter"); + + ImGui::PopID(); + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(1); + if (ImGui::Checkbox("CSM", &csm)) { + YM_debug_write(0x14, bit_set_or_res(regs[0x14], (uint8_t)(1 << 7), csm)); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("CT1", &ct1)) { + YM_debug_write(0x1B, bit_set_or_res(regs[0x1B], (uint8_t)(1 << 6), ct1)); + } + ImGui::TableNextColumn(); + if (ImGui::Checkbox("CT2", &ct2)) { + YM_debug_write(0x1B, bit_set_or_res(regs[0x1B], (uint8_t)(1 << 7), ct2)); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("LFO & Noise", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { + debugger_draw_ym_lfo_and_noise(regs); + ImGui::TreePop(); + } + + static ym_channel_data channel[8]; + static ym_keyon_state keyon[8]; + + if (ImGui::TreeNodeEx("Channels", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) { + debugger_draw_ym_voices(regs, channel, keyon, IM_ARRAYSIZE(channel), [](uint8_t addr, uint8_t value) { YM_debug_write(addr, value); }); + ImGui::TreePop(); + } +} + +void debugger_draw_ym_lfo_and_noise(uint8_t *regs) +{ + if (ImGui::BeginTable("ym lfo & noise", 2, ImGuiTableFlags_SizingStretchSame)) { + static const char *waveforms[] = { + "Sawtooth", + "Square", + "Triangle", + "Noise" + }; + const uint8_t LRES_MASK = (1 << 1); + const uint8_t LW_MASK = 0x03; + bool lres = regs[0x01] & LRES_MASK; + int lw = regs[0x1B] & LW_MASK; + int lfrq = regs[0x18]; + + ym_modulation_state mod_data; + YM_get_modulation_state(mod_data); + + float lcnt = mod_data.LFO_phase; + int amd = mod_data.amplitude_modulation; + int pmd = mod_data.phase_modulation; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + ImGui::Text("LFO"); + ImGui::SameLine(72); + if (ImGui::Checkbox("Reset", &lres)) { + YM_debug_write(0x01, bit_set_or_res(regs[0x01], LRES_MASK, lres)); + } + ImGui::EndGroup(); + ImGui::TableNextColumn(); + if (ImGui::Combo("Waveform", &lw, waveforms, 4)) { + YM_debug_write(0x1B, regs[0x1B] & ~LW_MASK | lw); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::SliderInt("LFO Freq", &lfrq, 0, 255)) { + YM_debug_write(0x18, lfrq); + } + ImGui::TableNextColumn(); + char buf[4]; + std::sprintf(buf, "%d", (int)(lcnt * 256)); + ImGui::ProgressBar(lcnt, ImVec2(0, 0), buf); + ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Cur. Phase"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::SliderInt("AMD", &amd, 0, 127)) { + YM_debug_write(0x19, amd); + } + ImGui::TableNextColumn(); + if (ImGui::SliderInt("PMD", &pmd, 0, 127)) { + YM_debug_write(0x19, pmd | 0x80); + } + + const uint8_t NEN_MASK = (1 << 7); + const uint8_t NFRQ_MASK = 0x1F; + bool nen = regs[0x0F] & NEN_MASK; + int nfrq = regs[0x0F] & NFRQ_MASK; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + ImGui::Text("Noise"); + ImGui::SameLine(72); + if (ImGui::Checkbox("Enable", &nen)) { + YM_debug_write(0x0F, bit_set_or_res(regs[0x0F], NEN_MASK, nen)); + } + ImGui::EndGroup(); + ImGui::TableNextColumn(); + if (ImGui::SliderInt("Frequency", &nfrq, 31, 0)) { + YM_debug_write(0x0F, regs[0x0F] & ~NFRQ_MASK | nfrq); + } + + ImGui::EndTable(); + } +} + +void debugger_draw_ym_voices(uint8_t *regs, ym_channel_data *channel, ym_keyon_state *keyons, int num_channels, std::function apply_byte) +{ + if (ImGui::BeginTable("ym channels", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // chan num + ImGui::TableSetupColumn("", 0, 0.4f); // chan regs + ImGui::TableSetupColumn(""); // slot regs + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // slot con + + for (int i = 0; i < num_channels; i++) { + ImGui::PushID(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d", i); + + debugger_draw_ym_voice(i, regs, channel[i], keyons ? &keyons[i] : nullptr, apply_byte); + + ImGui::PopID(); + } + ImGui::EndTable(); + } +} + +void debugger_draw_ym_voice(int i, uint8_t *regs, ym_channel_data &ch, ym_keyon_state *keyon, std::function apply_byte) +{ + static const uint8_t slot_map[4] = { 0, 16, 8, 24 }; + + const uint8_t confb = 0x20 + i; + const uint8_t kc = 0x28 + i; + const uint8_t kf = 0x30 + i; + const uint8_t amspms = 0x38 + i; + const uint8_t tmp_kc = regs[kc] & 0x7f; + + ch.l = regs[confb] & (1 << 6); + ch.r = regs[confb] & (1 << 7); + ch.con = regs[confb] & 0x07; + ch.fb = (regs[confb] >> 3) & 0x07; + ch.kc = (tmp_kc - ((tmp_kc + 1) >> 2)) + (regs[kf] / 256.f); + ch.ams = regs[amspms] & 0x03; + ch.pms = (regs[amspms] >> 4) & 0x07; + int fpkc = (int)(ch.kc * 256); + + // Channel + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 8); + if (ImGui::BeginTable("confb", 4)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(12); + if (ImGui::Checkbox("L", &ch.l)) { + apply_byte(confb, bit_set_or_res(regs[confb], (uint8_t)(1 << 6), ch.l)); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(12); + if (ImGui::Checkbox("R", &ch.r)) { + apply_byte(confb, bit_set_or_res(regs[confb], (uint8_t)(1 << 7), ch.r)); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(12); + if (ImGui::DragInt("CON", &ch.con, 1, 0, 7)) { + apply_byte(confb, regs[confb] & ~0x07 | ch.con); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-28); + if (ImGui::SliderInt("FB", &ch.fb, 0, 7)) { + apply_byte(confb, regs[confb] & ~0x38 | (ch.fb << 3)); + } + ImGui::EndTable(); + } + + if (ImGui::BeginTable("amspms", 2)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-28); + if (ImGui::SliderInt("AMS", &ch.ams, 0, 3)) { + apply_byte(amspms, regs[amspms] & ~0x03 | ch.ams); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-28); + if (ImGui::SliderInt("PMS", &ch.pms, 0, 7)) { + apply_byte(amspms, regs[amspms] & ~0x70 | (ch.pms << 4)); + } + ImGui::EndTable(); + } + + if (keyon) { + const char notes[] = "C-C#D-D#E-F-F#G-G#A-A#B-"; + float cents = (fpkc & 0xFF) * 100.f / 256.f; + if (cents > 50) { + cents = cents - 100; + } + const uint8_t note = (fpkc >> 8) + (cents < 0) + 1; + const uint8_t ni = (note % 12) * 2; + const uint8_t oct = note / 12; + // C#8 +00.0 + char kcinfo[12]; + std::sprintf(kcinfo, "%c%c%d %+05.1f", notes[ni], notes[ni + 1], oct, cents); + ImGui::SetNextItemWidth(-28); + if (ImGui::SliderFloat("KC", &ch.kc, 0, 96, kcinfo, ImGuiSliderFlags_NoRoundToFormat)) { + fpkc = std::min((int)(ch.kc * 256), (96 * 256) - 1); + apply_byte(kc, (fpkc >> 8) * 4 / 3); + apply_byte(kf, fpkc & 0xFF); + } + + ImGui::Button("KeyOn"); + keyon->dkob_state = (keyon->dkob_state << 1) | (int)ImGui::IsItemActive(); + switch (keyon->dkob_state & 0b11) { + case 0b01: // keyon checked slots + apply_byte(0x08, i | (keyon->debug_kon[0] << 3) | (keyon->debug_kon[1] << 4) | (keyon->debug_kon[2] << 5) | (keyon->debug_kon[3] << 6)); + break; + case 0b10: // keyoff all slots + apply_byte(0x08, i); + break; + } + ImGui::PushID("konslots"); + for (int j = 0; j < 4; j++) { + ImGui::PushID(j); + ImGui::SameLine(); + ImGui::Checkbox("", &keyon->debug_kon[j]); + ImGui::PopID(); + } + ImGui::PopID(); + } + ImGui::PopStyleVar(3); + + // Slot + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 6); + if (ImGui::BeginTable("slot", 15)) { + ImGui::TableSetupColumn("Sl", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("DT1"); + ImGui::TableSetupColumn("DT2"); + ImGui::TableSetupColumn("MUL"); + ImGui::TableSetupColumn("=Freq", ImGuiTableColumnFlags_WidthFixed, 44); + ImGui::TableSetupColumn("AR"); + ImGui::TableSetupColumn("D1R"); + ImGui::TableSetupColumn("D1L"); + ImGui::TableSetupColumn("D2R"); + ImGui::TableSetupColumn("RR"); + ImGui::TableSetupColumn("KS"); + ImGui::TableSetupColumn("Env"); + ImGui::TableSetupColumn("TL"); + ImGui::TableSetupColumn("AM", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Out"); + ImGui::TableHeadersRow(); + + for (int j = 0; j < 4; j++) { + const int slnum = slot_map[j] + i; + const uint8_t muldt1 = 0x40 + slnum; + const uint8_t tl = 0x60 + slnum; + const uint8_t arks = 0x80 + slnum; + const uint8_t d1rame = 0xA0 + slnum; + const uint8_t d2rdt2 = 0xC0 + slnum; + const uint8_t rrd1l = 0xE0 + slnum; + auto & slot = ch.slot[j]; + + slot.mul = regs[muldt1] & 0x0F; + slot.dt1 = (regs[muldt1] >> 4) & 0x07; + slot.tl = regs[tl] & 0x7F; + slot.ar = regs[arks] & 0x1F; + slot.ks = regs[arks] >> 6; + slot.d1r = regs[d1rame] & 0x1F; + slot.ame = regs[d1rame] & 0x80; + slot.d2r = regs[d2rdt2] & 0x1F; + slot.dt2 = regs[d2rdt2] >> 6; + slot.rr = regs[rrd1l] & 0x0F; + slot.d1l = regs[rrd1l] >> 4; + + ym_slot_state slot_state; + YM_get_slot_state(slnum, slot_state); + + ImGui::PushID(j); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d", slot_map[j] + i); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("dt1", &slot.dt1, 0, 7)) { + apply_byte(muldt1, regs[muldt1] & ~0x70 | (slot.dt1 << 4)); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("dt2", &slot.dt2, 0, 3)) { + apply_byte(d2rdt2, regs[d2rdt2] & ~0xC0 | (slot.dt2 << 6)); + } + ImGui::TableNextColumn(); + char buf[11] = ".5"; + if (slot.mul > 0) { + std::sprintf(buf, "%d", slot.mul); + } + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("mul", &slot.mul, 0, 15, buf)) { + apply_byte(muldt1, regs[muldt1] & ~0x0F | slot.mul); + } + ImGui::TableNextColumn(); + ImGui::Text("%d", slot_state.frequency); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("ar", &slot.ar, 31, 0)) { + apply_byte(arks, regs[arks] & ~0x1F | slot.ar); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("d1r", &slot.d1r, 31, 0)) { + apply_byte(d1rame, regs[d1rame] & ~0x1F | slot.d1r); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("d1l", &slot.d1l, 15, 0)) { + apply_byte(rrd1l, regs[rrd1l] & ~0xF0 | (slot.d1l << 4)); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("d2r", &slot.d2r, 31, 0)) { + apply_byte(d2rdt2, regs[d2rdt2] & ~0x1F | slot.d2r); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("rr", &slot.rr, 15, 0)) { + apply_byte(rrd1l, regs[rrd1l] & ~0x0F | slot.rr); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("ks", &slot.ks, 0, 3)) { + apply_byte(arks, regs[arks] & ~0xC0 | (slot.ks << 6)); + } + ImGui::TableNextColumn(); + char envstate_txt[] = " "; + envstate_txt[0] = " ADSR"[slot_state.env_state]; + ImGui::ProgressBar(slot_state.eg_output, ImVec2(-FLT_MIN, 0), envstate_txt); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::SliderInt("tl", &slot.tl, 127, 0)) { + apply_byte(tl, regs[tl] & ~0x7F | slot.tl); + } + ImGui::TableNextColumn(); + ImGui::PushID("amelia"); + if (ImGui::Checkbox("", &slot.ame)) { + apply_byte(d1rame, bit_set_or_res(regs[d1rame], (uint8_t)0x80, slot.ame)); + } + ImGui::PopID(); + float out = slot_state.final_env; + char buf2[5]; + std::sprintf(buf2, "%d", (int)((1 - out) * 1024)); + ImGui::TableNextColumn(); + ImGui::ProgressBar(out, ImVec2(-FLT_MIN, 0), buf2); + + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(3); + + // CON gfx + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(16, 15)); + // this is so ugly + ImGui::Tile((display_icons)((int)ICON_FM_ALG + ch.con), ImVec2(16, 64)); +} diff --git a/src/overlay/ym2151_overlay.h b/src/overlay/ym2151_overlay.h new file mode 100644 index 0000000..da8660c --- /dev/null +++ b/src/overlay/ym2151_overlay.h @@ -0,0 +1,36 @@ +#pragma once +#if !defined(YM2151_OVERLAY_H) +#define YM2151_OVERLAY_H + +#include + +struct ym_slot_data { + int dt1, dt2, mul; + int ar, d1r, d1l, d2r, rr, ks; + int tl; + bool ame; +}; + +struct ym_keyon_state { + bool debug_kon[4] = { 1, 1, 1, 1 }; + int dkob_state = 0; +}; + +struct ym_channel_data { + int con, fb; + bool l, r; + float kc; + int ams, pms; + + ym_slot_data slot[4]; +}; + +void draw_debugger_ym2151(); + +void debugger_draw_ym_voice(int i, uint8_t *regs, ym_channel_data &channel, ym_keyon_state *keyon, std::function apply_byte); +void debugger_draw_ym_voices(uint8_t *regs, ym_channel_data *channels, ym_keyon_state *keyons, int num_channels, std::function apply_byte); +void debugger_draw_ym_lfo_and_noise(uint8_t *regs); + + + +#endif diff --git a/src/ym2151/ym2151.cpp b/src/ym2151/ym2151.cpp index 425be97..adc8b1b 100644 --- a/src/ym2151/ym2151.cpp +++ b/src/ym2151/ym2151.cpp @@ -1,7 +1,11 @@ #include "ym2151.h" #include "ymfm_opm.h" + +#include "ymfm_fm.ipp" + #include "audio.h" +#include "bitutils.h" class ym2151_interface : public ymfm::ymfm_interface { @@ -70,9 +74,10 @@ class ym2151_interface : public ymfm::ymfm_interface } } - void write(uint8_t offset, uint8_t value) + void write(uint8_t addr, uint8_t value) { - m_chip.write(offset, value); + m_chip.write_address(addr); + m_chip.write_data(value, false); } void debug_write(uint8_t addr, uint8_t value) @@ -126,11 +131,11 @@ class ym2151_interface : public ymfm::ymfm_interface uint8_t get_env_state(uint8_t slnum) { switch (m_chip.get_debug_op(slnum)->debug_eg_state()) { - case ymfm::EG_ATTACK: return 1; - case ymfm::EG_DECAY: return 2; + case ymfm::EG_ATTACK: return 1; + case ymfm::EG_DECAY: return 2; case ymfm::EG_SUSTAIN: return 3; case ymfm::EG_RELEASE: return 4; - default: return 0; + default: return 0; } } @@ -149,9 +154,10 @@ class ym2151_interface : public ymfm::ymfm_interface uint32_t m_chip_sample_rate; }; -ym2151_interface Ym_interface; -uint8_t last_address; -uint8_t last_data; +static ym2151_interface Ym_interface; +static uint8_t Last_address = 0; +static uint8_t Last_data = 0; +static uint8_t Ym_registers[256]; void YM_render(int16_t *stream, uint32_t samples, uint32_t buffer_sample_rate) { @@ -160,79 +166,500 @@ void YM_render(int16_t *stream, uint32_t samples, uint32_t buffer_sample_rate) void YM_write(uint8_t offset, uint8_t value) { - audio_lock_scope lock; // save the hassle to add interface to dig into opm_registers by caching the writes here if (offset & 1) { // data port - last_data = value; + Last_data = value; + Ym_registers[Last_address] = Last_data; + + audio_lock_scope lock; + Ym_interface.write(Last_address, Last_data); } else { // address port - last_address = value; + Last_address = value; } - Ym_interface.write(offset, value); +} + +uint8_t YM_read_status() +{ + audio_lock_scope lock; + return Ym_interface.read_status(); } void YM_debug_write(uint8_t addr, uint8_t value) { + Ym_registers[addr] = value; + audio_lock_scope lock; Ym_interface.debug_write(addr, value); } uint8_t YM_debug_read(uint8_t addr) { - return Ym_interface.debug_read(addr); + return Ym_registers[addr]; } -uint8_t YM_read_status() +uint8_t YM_last_address() +{ + return Last_address; +} + +uint8_t YM_last_data() +{ + return Last_data; +} + +void YM_get_modulation_regs(uint8_t *regs) +{ + regs[0x01] = Ym_registers[0x01]; + regs[0x0F] = Ym_registers[0x0F]; + regs[0x18] = Ym_registers[0x18]; + regs[0x19] = Ym_registers[0x19]; + regs[0x1B] = Ym_registers[0x1B]; +} + +void YM_get_voice_regs(uint8_t voice, uint8_t *regs) +{ + regs[YM_R_L_FB_CONN_OFFSET + voice] = Ym_registers[YM_R_L_FB_CONN_OFFSET + voice]; + regs[YM_KC_OFFSET + voice] = Ym_registers[YM_KC_OFFSET + voice]; + regs[YM_KF_OFFSET + voice] = Ym_registers[YM_KF_OFFSET + voice]; + regs[YM_PMS_AMS_OFFSET + voice] = Ym_registers[YM_PMS_AMS_OFFSET + voice]; +} + +void YM_get_slot_regs(uint8_t voice, uint8_t slot, uint8_t *regs) +{ + regs[YM_DT1_MUL_OFFSET + (slot * 8) + voice] = Ym_registers[YM_DT1_MUL_OFFSET + (slot * 8) + voice]; + regs[YM_TL_OFFSET + (slot * 8) + voice] = Ym_registers[YM_TL_OFFSET + (slot * 8) + voice]; + regs[YM_KS_AR_OFFSET + (slot * 8) + voice] = Ym_registers[YM_KS_AR_OFFSET + (slot * 8) + voice]; + regs[YM_A_D1R_OFFSET + (slot * 8) + voice] = Ym_registers[YM_A_D1R_OFFSET + (slot * 8) + voice]; + regs[YM_DT2_D2R_OFFSET + (slot * 8) + voice] = Ym_registers[YM_DT2_D2R_OFFSET + (slot * 8) + voice]; + regs[YM_D1L_RR_OFFSET + (slot * 8) + voice] = Ym_registers[YM_D1L_RR_OFFSET + (slot * 8) + voice]; +} + +void YM_get_modulation_state(ym_modulation_state &data) { audio_lock_scope lock; - return Ym_interface.read_status(); + data.amplitude_modulation = Ym_interface.get_AMD(); + data.phase_modulation = Ym_interface.get_PMD(); + data.LFO_phase = (Ym_interface.get_LFO_phase() & ((1 << 30) - 1)) / (float)(1 << 30); } -uint8_t YM_last_address() +void YM_get_slot_state(uint8_t slnum, ym_slot_state &data) { - return last_address; + audio_lock_scope lock; + data.frequency = Ym_interface.get_freq(slnum); + data.eg_output = (1024 - Ym_interface.get_EG_output(slnum)) / 1024.f; + data.final_env = (1024 - Ym_interface.get_final_env(slnum)) / 1024.f; + data.env_state = Ym_interface.get_env_state(slnum); } -uint8_t YM_last_data() +uint16_t YM_get_timer_counter(uint8_t tnum) { - return last_data; + audio_lock_scope lock; + return Ym_interface.get_timer_counter(tnum); } -uint8_t YM_get_AMD() +// +// Field Accessors +// + +uint8_t YM_get_last_key_on() { - return Ym_interface.get_AMD(); + return Ym_registers[0x08]; } -uint8_t YM_get_PMD() +uint8_t YM_get_lfo_frequency() { - return Ym_interface.get_PMD(); + return Ym_registers[0x18]; } -float YM_get_LFO_phase() +uint8_t YM_get_modulation_depth() { - return (Ym_interface.get_LFO_phase() & ((1 << 30) - 1)) / (float) (1 << 30); + return get_bit_field<6, 0>(Ym_registers[0x19]); } -uint32_t YM_get_freq(uint8_t slnum) +uint8_t YM_get_modulation_type() { - return Ym_interface.get_freq(slnum); + return get_bit_field<7>(Ym_registers[0x19]); } -float YM_get_EG_output(uint8_t slnum) +uint8_t YM_get_waveform() { - return (1024 - Ym_interface.get_EG_output(slnum)) / 1024.f; + return get_bit_field<1, 0>(Ym_registers[0x1b]); } -float YM_get_final_env(uint8_t slnum) +uint8_t YM_get_control_output_1() { - return (1024 - Ym_interface.get_final_env(slnum)) / 1024.f; + return get_bit_field<6>(Ym_registers[0x1b]); } -uint8_t YM_get_env_state(uint8_t slnum) +uint8_t YM_get_control_output_2() { - return Ym_interface.get_env_state(slnum); + return get_bit_field<7>(Ym_registers[0x1b]); } -uint16_t YM_get_timer_counter(uint8_t tnum) +uint8_t YM_get_voice_connection_type(uint8_t voice) { - return Ym_interface.get_timer_counter(tnum); + if (voice < 8) { + return get_bit_field<2, 0>(Ym_registers[YM_R_L_FB_CONN_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_self_feedback_level(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<5, 3>(Ym_registers[YM_R_L_FB_CONN_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_left_enable(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<6>(Ym_registers[YM_R_L_FB_CONN_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_right_enable(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<7>(Ym_registers[YM_R_L_FB_CONN_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_note(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<3, 0>(Ym_registers[YM_KC_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_octave(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<6, 4>(Ym_registers[YM_KC_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_key_fraction(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<7, 2>(Ym_registers[YM_KF_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_amplitude_modulation_sensitivity(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<2, 1>(Ym_registers[YM_PMS_AMS_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_voice_phase_modulation_sensitivity(uint8_t voice) +{ + if (voice < 8) { + return get_bit_field<6, 4>(Ym_registers[YM_PMS_AMS_OFFSET + voice]); + } + + return 0; +} + +uint8_t YM_get_operator_phase_multiply(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<3, 0>(Ym_registers[YM_DT1_MUL_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_detune_1(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<6, 4>(Ym_registers[YM_DT1_MUL_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_total_level(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<6, 0>(Ym_registers[YM_TL_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_attack_rate(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<4, 0>(Ym_registers[YM_KS_AR_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_key_scaling(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<7, 6>(Ym_registers[YM_KS_AR_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_decay_rate_1(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<7, 4>(Ym_registers[YM_A_D1R_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_ams_enabled(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<7>(Ym_registers[YM_A_D1R_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_decay_rate_2(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<4, 0>(Ym_registers[YM_DT2_D2R_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_detune_2(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<7, 6>(Ym_registers[YM_DT2_D2R_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_release_rate(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<3, 0>(Ym_registers[YM_D1L_RR_OFFSET + (op * 8) + voice]); + } + return 0; +} + +uint8_t YM_get_operator_decay_1_level(uint8_t voice, uint8_t op) +{ + if (voice < 8 && op < 4) { + return get_bit_field<7, 4>(Ym_registers[YM_D1L_RR_OFFSET + (op * 8) + voice]); + } + return 0; +} + +// +// Field Mutators +// + +void YM_key_on(uint8_t channel, bool m1, bool c1, bool m2, bool c2) +{ + YM_debug_write(0x08, channel | (m1 ? 0x8 : 0) | (c1 ? 0x10 : 0) | (m2 ? 0x20 : 0) | (c2 ? 0x40 : 0)); +} + +void YM_set_lfo_frequency(uint8_t freq) +{ + YM_debug_write(0x18, freq); +} + +void YM_set_modulation_depth(uint8_t depth) +{ + YM_debug_write(0x19, set_bit_field<6, 0>(Ym_registers[0x19], depth)); +} + +void YM_set_modulation_type(uint8_t mtype) +{ + YM_debug_write(0x19, set_bit_field<7>(Ym_registers[0x19], mtype)); +} + +void YM_set_waveform(uint8_t wf) +{ + YM_debug_write(0x1b, set_bit_field<1, 0>(Ym_registers[0x1b], wf)); +} + +void YM_set_control_output_1(bool enabled) +{ + YM_debug_write(0x1b, set_bit_field<6>(Ym_registers[0x1b], enabled)); +} + +void YM_set_control_output_2(bool enabled) +{ + YM_debug_write(0x1b, set_bit_field<7>(Ym_registers[0x1b], enabled)); +} + +void YM_set_voice_connection_type(uint8_t voice, uint8_t ctype) +{ + if (voice < 8) { + const uint8_t addr = YM_R_L_FB_CONN_OFFSET + voice; + YM_debug_write(addr, set_bit_field<2, 0>(Ym_registers[addr], ctype & 0x7)); + } +} + +void YM_set_voice_self_feedback_level(uint8_t voice, uint8_t fl) +{ + if (voice < 8) { + const uint8_t addr = YM_R_L_FB_CONN_OFFSET + voice; + YM_debug_write(addr, set_bit_field<5, 3>(Ym_registers[addr], fl)); + } +} + +void YM_set_voice_left_enable(uint8_t voice, bool enable) +{ + if (voice < 8) { + const uint8_t addr = YM_R_L_FB_CONN_OFFSET + voice; + YM_debug_write(addr, set_bit_field<6>(Ym_registers[addr], enable)); + } +} + +void YM_set_voice_right_enable(uint8_t voice, bool enable) +{ + if (voice < 8) { + const uint8_t addr = YM_R_L_FB_CONN_OFFSET + voice; + YM_debug_write(addr, set_bit_field<7>(Ym_registers[addr], enable)); + } +} + +void YM_set_voice_note(uint8_t voice, uint8_t note) +{ + if (voice < 8) { + const uint8_t addr = YM_KC_OFFSET + voice; + YM_debug_write(addr, set_bit_field<3, 0>(Ym_registers[addr], note)); + } +} + +void YM_set_voice_octave(uint8_t voice, uint8_t octave) +{ + if (voice < 8) { + const uint8_t addr = YM_KC_OFFSET + voice; + YM_debug_write(addr, set_bit_field<6, 4>(Ym_registers[addr], octave)); + } +} + +void YM_set_voice_key_fraction(uint8_t voice, uint8_t fraction) +{ + if (voice < 8) { + const uint8_t addr = YM_KF_OFFSET + voice; + YM_debug_write(addr, set_bit_field<7, 2>(Ym_registers[addr], fraction)); + } +} + +void YM_set_voice_amplitude_modulation_sensitivity(uint8_t voice, uint8_t ams) +{ + if (voice < 8) { + const uint8_t addr = YM_PMS_AMS_OFFSET + voice; + YM_debug_write(addr, set_bit_field<1, 0>(Ym_registers[addr], ams)); + } +} + +void YM_set_voice_phase_modulation_sensitivity(uint8_t voice, uint8_t pms) +{ + if (voice < 8) { + const uint8_t addr = YM_PMS_AMS_OFFSET + voice; + YM_debug_write(addr, set_bit_field<6, 4>(Ym_registers[addr], pms)); + } +} + +void YM_set_operator_phase_multiply(uint8_t voice, uint8_t op, uint8_t mul) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_DT1_MUL_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<3, 0>(Ym_registers[addr], mul)); + } +} + +void YM_set_operator_detune_1(uint8_t voice, uint8_t op, uint8_t dt1) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_DT1_MUL_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<6, 4>(Ym_registers[addr], dt1)); + } +} + +void YM_set_operator_total_level(uint8_t voice, uint8_t op, uint8_t tl) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_TL_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<6, 0>(Ym_registers[addr], tl)); + } +} + +void YM_set_operator_attack_rate(uint8_t voice, uint8_t op, uint8_t ar) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_KS_AR_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<4, 0>(Ym_registers[addr], ar)); + } +} + +void YM_set_operator_key_scaling(uint8_t voice, uint8_t op, uint8_t ks) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_KS_AR_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<7, 6>(Ym_registers[addr], ks)); + } +} + +void YM_set_operator_decay_rate_1(uint8_t voice, uint8_t op, uint8_t dr1) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_A_D1R_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<4, 0>(Ym_registers[addr], dr1)); + } +} + +void YM_set_operator_ams_enabled(uint8_t voice, uint8_t op, bool enable) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_A_D1R_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<7>(Ym_registers[addr], enable)); + } +} + +void YM_set_operator_decay_rate_2(uint8_t voice, uint8_t op, uint8_t dr2) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_DT2_D2R_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<4, 0>(Ym_registers[addr], dr2)); + } +} + +void YM_set_operator_detune_2(uint8_t voice, uint8_t op, uint8_t dt2) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_DT2_D2R_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<7, 6>(Ym_registers[addr], dt2)); + } +} + +void YM_set_operator_release_rate(uint8_t voice, uint8_t op, uint8_t rr) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_D1L_RR_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<3, 0>(Ym_registers[addr], rr)); + } +} + +void YM_set_operator_decay_1_level(uint8_t voice, uint8_t op, uint8_t d1l) +{ + if (voice < 8 && op < 4) { + const uint8_t addr = YM_D1L_RR_OFFSET + (op * 8) + voice; + YM_debug_write(addr, set_bit_field<7, 4>(Ym_registers[addr], d1l)); + } } diff --git a/src/ym2151/ym2151.h b/src/ym2151/ym2151.h index c53054f..61de511 100644 --- a/src/ym2151/ym2151.h +++ b/src/ym2151/ym2151.h @@ -11,6 +11,21 @@ // //--------------------------------------------- +# define MAX_YM2151_VOICES (8) +# define MAX_YM2151_SLOTS (MAX_YM2151_VOICES * 4) + +# define YM_R_L_FB_CONN_OFFSET 0x20 +# define YM_KC_OFFSET 0x28 +# define YM_KF_OFFSET 0x30 +# define YM_PMS_AMS_OFFSET 0x38 + +# define YM_DT1_MUL_OFFSET 0x40 +# define YM_TL_OFFSET 0x60 +# define YM_KS_AR_OFFSET 0x80 +# define YM_A_D1R_OFFSET 0xA0 +# define YM_DT2_D2R_OFFSET 0xC0 +# define YM_D1L_RR_OFFSET 0xE0 + void YM_render(int16_t *stream, uint32_t samples, uint32_t buffer_sample_rate); void YM_write(uint8_t offset, uint8_t value); @@ -19,16 +34,83 @@ uint8_t YM_read_status(); // debug stuff void YM_debug_write(uint8_t addr, uint8_t value); uint8_t YM_debug_read(uint8_t addr); + uint8_t YM_last_address(); uint8_t YM_last_data(); -uint8_t YM_get_AMD(); -uint8_t YM_get_PMD(); -float YM_get_LFO_phase(); -uint32_t YM_get_freq(uint8_t slnum); -float YM_get_EG_output(uint8_t slnum); -float YM_get_final_env(uint8_t slnum); -uint8_t YM_get_env_state(uint8_t slnum); +struct ym_modulation_state { + uint8_t amplitude_modulation; + uint8_t phase_modulation; + float LFO_phase; +}; + +struct ym_slot_state { + uint32_t frequency; + float eg_output; + float final_env; + uint8_t env_state; +}; + +void YM_get_modulation_regs(uint8_t *regs); +void YM_get_slot_regs(uint8_t slnum, uint8_t *regs); +void YM_get_modulation_state(ym_modulation_state &data); +void YM_get_slot_state(uint8_t slnum, ym_slot_state &data); uint16_t YM_get_timer_counter(uint8_t tnum); +uint8_t YM_get_last_key_on(); +uint8_t YM_get_lfo_frequency(); +uint8_t YM_get_modulation_depth(); +uint8_t YM_get_modulation_type(); +uint8_t YM_get_waveform(); +uint8_t YM_get_control_output_1(); +uint8_t YM_get_control_output_2(); +uint8_t YM_get_voice_connection_type(uint8_t voice); +uint8_t YM_get_voice_self_feedback_level(uint8_t voice); +uint8_t YM_get_voice_left_enable(uint8_t voice); +uint8_t YM_get_voice_right_enable(uint8_t voice); +uint8_t YM_get_voice_note(uint8_t voice); +uint8_t YM_get_voice_octave(uint8_t voice); +uint8_t YM_get_voice_key_fraction(uint8_t voice); +uint8_t YM_get_voice_amplitude_modulation_sensitivity(uint8_t voice); +uint8_t YM_get_voice_phase_modulation_sensitivity(uint8_t voice); +uint8_t YM_get_operator_phase_multiply(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_detune_1(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_total_level(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_attack_rate(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_key_scaling(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_decay_rate_1(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_ams_enabled(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_decay_rate_2(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_detune_2(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_release_rate(uint8_t voice, uint8_t op); +uint8_t YM_get_operator_decay_1_level(uint8_t voice, uint8_t op); + +void YM_key_on(uint8_t channel, bool m1 = true, bool c1 = true, bool m2 = true, bool c2 = true); +void YM_set_lfo_frequency(uint8_t freq); +void YM_set_modulation_depth(uint8_t depth); +void YM_set_modulation_type(uint8_t mtype); +void YM_set_waveform(uint8_t wf); +void YM_set_control_output_1(bool enabled); +void YM_set_control_output_2(bool enabled); +void YM_set_voice_connection_type(uint8_t voice, uint8_t ctype); +void YM_set_voice_self_feedback_level(uint8_t voice, uint8_t fl); +void YM_set_voice_left_enable(uint8_t voice, bool enable); +void YM_set_voice_right_enable(uint8_t voice, bool enable); +void YM_set_voice_note(uint8_t voice, uint8_t note); +void YM_set_voice_octave(uint8_t voice, uint8_t octave); +void YM_set_voice_key_fraction(uint8_t voice, uint8_t fraction); +void YM_set_voice_amplitude_modulation_sensitivity(uint8_t voice, uint8_t ams); +void YM_set_voice_phase_modulation_sensitivity(uint8_t voice, uint8_t pms); +void YM_set_operator_phase_multiply(uint8_t voice, uint8_t op, uint8_t mul); +void YM_set_operator_detune_1(uint8_t voice, uint8_t op, uint8_t dt1); +void YM_set_operator_total_level(uint8_t voice, uint8_t op, uint8_t tl); +void YM_set_operator_attack_rate(uint8_t voice, uint8_t op, uint8_t ar); +void YM_set_operator_key_scaling(uint8_t voice, uint8_t op, uint8_t ks); +void YM_set_operator_decay_rate_1(uint8_t voice, uint8_t op, uint8_t dr1); +void YM_set_operator_ams_enabled(uint8_t voice, uint8_t op, bool enable); +void YM_set_operator_decay_rate_2(uint8_t voice, uint8_t op, uint8_t dr2); +void YM_set_operator_detune_2(uint8_t voice, uint8_t op, uint8_t dt2); +void YM_set_operator_release_rate(uint8_t voice, uint8_t op, uint8_t rr); +void YM_set_operator_decay_1_level(uint8_t voice, uint8_t op, uint8_t d1l); + #endif