diff --git a/doc/midi/MIDI Control Change Messages.pdf b/doc/midi/MIDI Control Change Messages.pdf new file mode 100644 index 000000000..8d80663d3 Binary files /dev/null and b/doc/midi/MIDI Control Change Messages.pdf differ diff --git a/sdl2-hyper-sonic-drivers/CMakeLists.txt b/sdl2-hyper-sonic-drivers/CMakeLists.txt index 74943eb13..7080d6075 100644 --- a/sdl2-hyper-sonic-drivers/CMakeLists.txt +++ b/sdl2-hyper-sonic-drivers/CMakeLists.txt @@ -101,12 +101,14 @@ target_sources(${LIB_NAME} PRIVATE # --- # ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/PCMDriver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/MIDDriver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/IMidiDriver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/IMidiChannel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/westwood/ADLDriver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibInstrument.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.cpp diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/MIDIEvent.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/MIDIEvent.hpp index a83f53e9d..ed6157b34 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/MIDIEvent.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/MIDIEvent.hpp @@ -7,6 +7,7 @@ namespace HyperSonicDrivers::audio::midi { + // TODO: store it as uint32_t class MIDIEvent { public: @@ -25,6 +26,7 @@ namespace HyperSonicDrivers::audio::midi b += (data[1] << 16); return b; } + // removed abs_time as it is not a MIDIEvent. //uint32_t abs_time = 0; /// absolute ticks time derived from delta_time used for conversion. }; diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/types.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/types.hpp index 29dc01764..ab8918d90 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/types.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/audio/midi/types.hpp @@ -8,6 +8,7 @@ namespace HyperSonicDrivers::audio::midi { constexpr uint8_t MIDI_MAX_CHANNELS = 16; constexpr uint8_t MIDI_PERCUSSION_CHANNEL = 9; // standard MIDI percussion channel + constexpr uint16_t MIDI_PITCH_BEND_DEFAULT = 0x2000; // pitch bend default value (zero) typedef std::vector midi_vector_t; @@ -16,10 +17,8 @@ namespace HyperSonicDrivers::audio::midi SINGLE_TRACK = 0, SIMULTANEOUS_TRACK = 1, MULTI_TRACK = 2 - //TODO: add XMI_FORMAT? }; - // move to MIDIEvent class ? typedef union MIDI_EVENT_type_u { uint8_t val; @@ -79,18 +78,16 @@ namespace HyperSonicDrivers::audio::midi SYS_EX7 = static_cast((std::byte(MIDI_EVENT_TYPES_HIGH::META_SYSEX) << 4) | std::byte(MIDI_META_EVENT_TYPES_LOW::SYS_EX7)), }; - /* - // TODO: too many repetitions to be an enum enum class MIDI_EVENT_CONTROLLER_TYPES : uint8_t { - BANK_SELECT = 0, + BANK_SELECT_MSB = 0, MODULATION_WHEEL = 1, BREATH_CONTROL = 2, FOOT_CONTROLLER = 4, PORTAMENTO_TIME = 5, - DATA_ENTRY = 6, - CHANNEL_VOLUME = 7, // (MAIN VOLUME) - BALANCE = 8, // (PAN) + DATA_ENTRY_MSB = 6, + CHANNEL_VOLUME = 7, + BALANCE = 8, PAN = 10, EXPRESSION_CONTROLLER = 11, EFFECT_CONTROL_1 = 12, @@ -99,15 +96,68 @@ namespace HyperSonicDrivers::audio::midi GENERAL_PURPOSE_CONTROLLER_2 = 17, GENERAL_PURPOSE_CONTROLLER_3 = 18, GENERAL_PURPOSE_CONTROLLER_4 = 19, - BANK_SELECT_2 = 32, + BANK_SELECT_LSB = 32, MODULATION_WHEEL_2 = 33, BREATH_CONTROL_2 = 34, + DATA_ENTRY_LSB = 38, + SUSTAIN = 64, + REVERB = 91, + TREMOLO = 92, + CHORUS = 93, + DETUNE = 94, + PHASER = 95, + RPN_LSB = 100, + RPN_MSB = 101, + ALL_SOUND_OFF = 120, + RESET_ALL_CONTROLLERS = 121, + ALL_NOTES_OFF = 123, + // eXtended MIDI + CHANNEL_LOCK = 110, + CHANNEL_LOCK_PROTECT = 111, + VOICE_PROTECT = 112, + TIMBRE_PROTECT = 113, + PATCH_BANK_SELECT = 114, + INDIRECT_CONTROLLER_PREFIX = 115, + FOR_LOOP_CONTROLLER = 116, + NEXT_BREAK_LOOP_CONTROLLER = 117, + CLEAR_BEAT_BAR_COUNT = 118, + CALLBACK_TRIGGER = 119, + SEQUENCE_BRANCH_INDEX = 120, }; - */ + enum class MIDI_RPN_TYPES : uint16_t + { + PITCH_BEND_SENSITIVITY = 0x0000, + FINE_TUNING = 0x0001, + COARSE_TUNING = 0x0002, + TUNING_PROGRAM_SELECT = 0x0003, + TUNING_BANK_SELECT = 0x0004, + RPN_NULL = 0x7F7F + }; + + enum class MIDI_RPN_TYPES_LSB : uint8_t + { + PITCH_BEND_SENSITIVITY = 0x00, + FINE_TUNING = 0x01, + COARSE_TUNING = 0x02, + TUNING_PROGRAM_SELECT = 0x03, + TUNING_BANK_SELECT = 0x04, + RPN_NULL = 0x7F + }; + + enum class MIDI_RPN_TYPES_MSB : uint8_t + { + PITCH_BEND_SENSITIVITY = 0x00, + FINE_TUNING = 0x00, + COARSE_TUNING = 0x00, + TUNING_PROGRAM_SELECT = 0x00, + TUNING_BANK_SELECT = 0x00, + RPN_NULL = 0x7F + }; constexpr MIDI_EVENT_TYPES_HIGH TO_HIGH(const uint8_t x) { return static_cast(x); } constexpr MIDI_META_EVENT_TYPES_LOW TO_META_LOW(const uint8_t x) { return static_cast(x); } constexpr MIDI_META_EVENT TO_META(const uint8_t x) { return static_cast(x); } + constexpr MIDI_EVENT_CONTROLLER_TYPES TO_CTRL(const uint8_t x) { return static_cast(x); } } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/MIDDriver.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/MIDDriver.cpp index d7da259e7..023f56ed7 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/MIDDriver.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/MIDDriver.cpp @@ -88,8 +88,6 @@ namespace HyperSonicDrivers::drivers } m_midiDriver.reset(); - - //auto opl = std::dynamic_pointer_cast(m_device)->getOpl(); auto opl_drv = std::make_unique(std::dynamic_pointer_cast(m_device)); opl_drv->setOP2Bank(op2Bank); m_midiDriver = std::move(opl_drv); diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannel.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannel.cpp new file mode 100644 index 000000000..d29aa1025 --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannel.cpp @@ -0,0 +1,10 @@ +#include +#include + +namespace HyperSonicDrivers::drivers::midi +{ + IMidiChannel::IMidiChannel(const uint8_t channel) : + channel(channel), + isPercussion(channel == audio::midi::MIDI_PERCUSSION_CHANNEL) + {} +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannel.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannel.hpp new file mode 100644 index 000000000..d48d713b3 --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannel.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace HyperSonicDrivers::drivers::midi +{ + /** + * Interface for MIDI operation to a specific MIDI Channel + **/ + class IMidiChannel + { + public: + explicit IMidiChannel(const uint8_t channel); + virtual ~IMidiChannel() = default; + + const uint8_t channel; // MIDI channel number + uint8_t volume = 0; // channel volume + uint8_t pan = 64; // pan, 64=center + uint16_t pitch = 0; // pitch wheel, 0=normal + uint8_t sustain = 0; // sustain pedal value + uint8_t modulation = 0; // modulation pot value + uint8_t program = 0; // instrument number + const bool isPercussion; + }; +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.cpp new file mode 100644 index 000000000..11f6d8000 --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +namespace HyperSonicDrivers::drivers::midi +{ + uint8_t IMidiChannelVoice::getChannelNum() const noexcept + { + return m_channel->channel; + } + + void IMidiChannelVoice::setVolumes(const uint8_t volume) noexcept + { + m_volume = volume; + m_real_volume = calcVolume_(); + } + + uint8_t IMidiChannelVoice::calcVolume_() const noexcept + { + return std::min(static_cast(m_volume * m_channel->volume / 127), 127); + } +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.hpp new file mode 100644 index 000000000..23c0d7c37 --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiChannelVoice.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace HyperSonicDrivers::drivers::midi +{ + class IMidiChannel; + + /** + * Interface for Midi Channel Voice message to have a polyphonic MidiChannel. + * this is mono-phonic channel, multiple combination of this gives a polyphonic MidiChannel + **/ + class IMidiChannelVoice + { + public: + IMidiChannelVoice() = default; + virtual ~IMidiChannelVoice() = default; + + inline IMidiChannel* getChannel() const noexcept { return m_channel; } + uint8_t getChannelNum() const noexcept; + inline uint8_t getNote() const noexcept { return m_note; } + //inline uint8_t getVolume() const noexcept { return m_volume; }; + void setVolumes(const uint8_t volume) noexcept; + inline bool isFree() const noexcept { return m_free; } + inline bool isSustain() const noexcept { return m_sustain; }; + inline bool isVibrato() const noexcept { return m_vibrato; } + + protected: + IMidiChannel* m_channel = nullptr; // MIDI channel + uint8_t m_note = 0; /* note number */ + uint8_t m_volume = 0; /* note volume */ + uint8_t m_real_volume = 0; /* adjusted note volume */ + int16_t m_pitch_factor = 0; /* pitch-wheel value */ + bool m_free = true; + bool m_sustain = false; // this are Opl exclusive or are midi? + bool m_vibrato = false; // "" + + private: + /// + /// The volume is between 0-127 as a per MIDI specification. + /// OPLWriter expect a MIDI volume value and converts to OPL value. + /// OPL chips has a volume attenuation (inverted values) + /// range from 0-64 inverted (0 is max, 64 is muted). + /// + uint8_t calcVolume_() const noexcept; + }; + +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.cpp new file mode 100644 index 000000000..f63fb75b9 --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +namespace HyperSonicDrivers::drivers::midi +{ + using audio::midi::TO_HIGH; + using utils::logW; + + void IMidiDriver::send(const audio::midi::MIDIEvent& e) noexcept + { + // TODO: sysEx must be reviewed if ok, probably shoudl check + // also if last byte is meta sysEx end ... + using audio::midi::TO_HIGH; + using audio::midi::MIDI_EVENT_TYPES_HIGH; + + if (TO_HIGH(e.type.high) == MIDI_EVENT_TYPES_HIGH::META_SYSEX) + sysEx(e.data.data(), static_cast(e.data.size())); + else + send(e.toUint32()); + } + + void IMidiDriver::send(int8_t channel, uint32_t msg) noexcept + { + using audio::midi::MIDI_EVENT_type_u; + using audio::midi::MIDI_EVENT_TYPES_HIGH; + + const uint8_t param2 = static_cast((msg >> 16) & 0xFF); + const uint8_t param1 = static_cast((msg >> 8) & 0xFF); + MIDI_EVENT_type_u cmd; + cmd.high = static_cast((msg >> 4) & 0xF); + + send(TO_HIGH(cmd.high), channel, param1, param2); + } + + void IMidiDriver::send(uint32_t msg) noexcept + { + send(msg & 0xF, msg & 0xFFFFFFF0); + } + + void IMidiDriver::send(const audio::midi::MIDI_EVENT_TYPES_HIGH type, const uint8_t channel, const uint8_t data1, const uint8_t data2) + { + using audio::midi::TO_CTRL; + + switch (type) + { + using enum audio::midi::MIDI_EVENT_TYPES_HIGH; + + case NOTE_OFF:// Note Off + noteOff(channel, data1); + break; + case NOTE_ON: // Note On + noteOn(channel, data1, data2); + break; + case AFTERTOUCH: // Aftertouch + break; // Not supported. + case CONTROLLER: // Control Change + controller(channel, TO_CTRL(data1), data2); + break; + case PROGRAM_CHANGE: // Program Change + programChange(channel, data1); + break; + case CHANNEL_AFTERTOUCH: // Channel Pressure + break; // Not supported. + case PITCH_BEND: // Pitch Bend + { + const auto bend = static_cast((data1 | (data2 << 7)) - audio::midi::MIDI_PITCH_BEND_DEFAULT); + pitchBend(channel, bend); + } + break; + case META_SYSEX: // SysEx + // We should never get here! SysEx information has to be + // sent via high-level semantic methods. + logW("Receiving SysEx command on a send() call"); + break; + + default: + logW(std::format("Unknown send() command {:#0x}", static_cast(type))); + } + } + + void IMidiDriver::controller(const uint8_t chan, const audio::midi::MIDI_EVENT_CONTROLLER_TYPES ctrl_type, uint8_t value) noexcept + { + // MIDI_EVENT_CONTROLLER_TYPES + switch (ctrl_type) + { + using enum audio::midi::MIDI_EVENT_CONTROLLER_TYPES; + case BANK_SELECT_MSB: + logW(std::format("bank select value {}", value)); + break; + case MODULATION_WHEEL: + ctrl_modulationWheel(chan, value); + break; + case CHANNEL_VOLUME: + ctrl_volume(chan, value); + break; + case PAN: + ctrl_panPosition(chan, value); + break; + case GENERAL_PURPOSE_CONTROLLER_1: + //pitchBendFactor(value); + logW(std::format("pitchBendFactor value {}", value)); + break; + case GENERAL_PURPOSE_CONTROLLER_2: + //detune(value); + logW(std::format("detune value {}", value)); + break; + case GENERAL_PURPOSE_CONTROLLER_3: + //priority(value); + logW(std::format("priority value {}", value)); + break; + case SUSTAIN: + ctrl_sustain(chan, value); + break; + case REVERB: + ctrl_reverb(chan, value); + break; + case CHORUS: + ctrl_chorus(chan, value); + break; + case RESET_ALL_CONTROLLERS: + // reset all controllers + logW("reset all controllers value not implemented"); + break; + case ALL_NOTES_OFF: + ctrl_allNotesOff(); + break; + default: + logW(std::format("OplDriver: Unknown control change message {:d} {:d}", static_cast(ctrl_type), value)); + } + } + + void IMidiDriver::programChange(const uint8_t chan, const uint8_t program) noexcept + { + if (program > 127) + { + logW(std::format("Progam change value >= 127 -> {}", program)); + } + + m_channels[chan]->program = program; + } +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.hpp index fbe4652c1..2ab815a14 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/IMidiDriver.hpp @@ -1,8 +1,12 @@ #pragma once #include +#include +#include #include #include +#include +#include namespace HyperSonicDrivers::drivers::midi { @@ -24,17 +28,49 @@ namespace HyperSonicDrivers::drivers::midi inline bool isOpen() const noexcept { return m_isOpen; } - virtual void send(const audio::midi::MIDIEvent& e) /*const*/ noexcept = 0; - virtual void send(uint32_t msg) = 0; - virtual void send(int8_t channel, uint32_t msg) = 0; - + virtual void send(const audio::midi::MIDIEvent& e) noexcept; + virtual void send(const int8_t channel, const uint32_t msg) noexcept; + virtual void send(const uint32_t msg) noexcept; + virtual void send(const audio::midi::MIDI_EVENT_TYPES_HIGH type, const uint8_t channel, const uint8_t data1, const uint8_t data2); virtual void pause() const noexcept = 0; virtual void resume() const noexcept = 0; protected: bool m_isOpen = false; - + std::array, audio::midi::MIDI_MAX_CHANNELS> m_channels; virtual void onCallback() noexcept = 0; + // MIDI events + virtual void noteOff(const uint8_t chan, const uint8_t note) noexcept = 0; + virtual void noteOn(const uint8_t chan, const uint8_t note, const uint8_t vol) noexcept = 0; + virtual void controller(const uint8_t chan, const audio::midi::MIDI_EVENT_CONTROLLER_TYPES ctrl_type, uint8_t value) noexcept; + virtual void programChange(const uint8_t chan, const uint8_t program) noexcept; + virtual void pitchBend(const uint8_t chan, const uint16_t bend) noexcept = 0; + /** + * Transmit a SysEx to the MIDI device. + * + * The given msg MUST NOT contain the usual SysEx frame, i.e. + * do NOT include the leading 0xF0 and the trailing 0xF7. + * + * Furthermore, the maximal supported length of a SysEx + * is 264 bytes. Passing longer buffers can lead to + * undefined behavior (most likely, a crash). + * TODO: review this method + */ + virtual void sysEx(const uint8_t* msg, uint16_t length) noexcept = 0; + + // MIDI Controller Events + virtual void ctrl_modulationWheel(const uint8_t chan, const uint8_t value) noexcept = 0; + virtual void ctrl_volume(const uint8_t chan, const uint8_t value) noexcept = 0; + virtual void ctrl_panPosition(const uint8_t chan, const uint8_t value) noexcept = 0; + virtual void ctrl_sustain(const uint8_t chan, const uint8_t value) noexcept = 0; + virtual void ctrl_reverb(const uint8_t chan, const uint8_t value) noexcept = 0; + virtual void ctrl_chorus(const uint8_t chan, const uint8_t value) noexcept = 0; + virtual void ctrl_allNotesOff() noexcept = 0; + + //virtual void pitchBendFactor(uint8_t value) noexcept = 0; + //virtual void transpose(int8_t value) noexcept = 0; + //virtual void detune(uint8_t value) noexcept = 0; //{ controlChange(17, value); } + //virtual void priority(uint8_t value) noexcept = 0; //{ } }; } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/mt32/MT32Driver.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/mt32/MT32Driver.hpp index f0bc76aea..cbaa4c2a2 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/mt32/MT32Driver.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/mt32/MT32Driver.hpp @@ -14,14 +14,30 @@ namespace HyperSonicDrivers::drivers::midi::mt32 const uint8_t pan) override { return true; }; void close() override { /*TODO*/ }; - void send(const audio::midi::MIDIEvent& e) noexcept override { /*TODO*/ }; - void send(uint32_t msg) override { /*TODO*/ }; - void send(int8_t channel, uint32_t msg) override { /*TODO*/ }; + //void send(const audio::midi::MIDIEvent& e) noexcept override { /*TODO*/ }; + //void send(const uint32_t msg) noexcept override { /*TODO*/ }; + //void send(const int8_t channel, const uint32_t msg) noexcept override { /*TODO*/ }; void pause() const noexcept override { /*TODO*/ }; void resume() const noexcept override { /*TODO*/ }; + protected: void onCallback() noexcept override { /*TODO*/ }; + // MIDI events + void noteOff(const uint8_t chan, const uint8_t note) noexcept override {/*TODO*/}; + void noteOn(const uint8_t chan, const uint8_t note, const uint8_t vol) noexcept override {/*TODO*/}; + void controller(const uint8_t chan, const audio::midi::MIDI_EVENT_CONTROLLER_TYPES ctrl_type, uint8_t value) noexcept override {/*TODO*/}; + void programChange(const uint8_t chan, const uint8_t program) noexcept override {/*TODO*/}; + void pitchBend(const uint8_t chan, const uint16_t bend) noexcept override {/*TODO*/ }; + void sysEx(const uint8_t* msg, uint16_t length) noexcept override {/*TODO*/ }; + // MIDI Controller Events + void ctrl_modulationWheel(const uint8_t chan, const uint8_t value) noexcept override {/*TODO*/}; + void ctrl_volume(const uint8_t chan, const uint8_t value) noexcept override {/*TODO*/}; + void ctrl_panPosition(const uint8_t chan, uint8_t value) noexcept override {/*TODO*/}; + void ctrl_sustain(const uint8_t chan, uint8_t value) noexcept override {/*TODO*/ }; + void ctrl_reverb(const uint8_t chan, uint8_t value) noexcept override { /*TODO*/ }; + void ctrl_chorus(const uint8_t chan, uint8_t value) noexcept override { /*TODO*/ }; + void ctrl_allNotesOff() noexcept override {/*TODO*/ }; }; } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.cpp index a1a72f11b..15804efc2 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.cpp @@ -1,26 +1,11 @@ -#include -#include #include -#include +#include +#include namespace HyperSonicDrivers::drivers::midi::opl { - using audio::midi::MIDI_PERCUSSION_CHANNEL; - using utils::logW; - - OplChannel::OplChannel(const uint8_t channel_) : - channel(channel_), _isPercussion(channel_ == MIDI_PERCUSSION_CHANNEL) + OplChannel::OplChannel(const uint8_t channel) : + IMidiChannel(channel) { } - - void OplChannel::programChange(const uint8_t program) - { - if (program > 127) - { - logW(std::format("Progam change value >= 127 -> {}", program)); - } - - // NOTE: if program is not changed shouldn't be required to do anything ... - _program = program; - } } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.hpp index 01ea9aa42..7bcf4cfc6 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplChannel.hpp @@ -1,34 +1,15 @@ #pragma once #include -#include +#include #include +#include namespace HyperSonicDrivers::drivers::midi::opl { - /// - /// More than one note can be played at once in one channel. - /// It means more than one OplVoice can be associated to OplChannel. - /// - class OplChannel + class OplChannel : public IMidiChannel { public: - OplChannel() = delete; - explicit OplChannel(const uint8_t channel_); - ~OplChannel() = default; - - const uint8_t channel; // MIDI channel, not used - uint8_t volume = 0; // volume - uint8_t pan = 0; // pan, 0=normal - uint8_t pitch = 0; // pitch wheel, 0=normal - uint8_t sustain = 0; // sustain pedal value - uint8_t modulation = 0; // modulation pot value - - // Regular messages - void programChange(const uint8_t program); - inline uint8_t getProgram() const noexcept { return _program; } - private: - const bool _isPercussion; - uint8_t _program = 0; // instrument number + explicit OplChannel(const uint8_t channel); }; } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.cpp index 15b29d95f..f6ee7a562 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -10,17 +11,11 @@ namespace HyperSonicDrivers::drivers::midi::opl using hardware::opl::opl2_num_channels; using hardware::opl::opl3_num_channels; - using audio::midi::MIDI_PERCUSSION_CHANNEL; - using audio::midi::MIDI_EVENT_TYPES_HIGH; - using audio::midi::TO_HIGH; - using hardware::opl::OPL2instrument_t; using hardware::opl::OplType; using utils::logW; using utils::logE; using utils::logC; - // TODO: allocateVoice and getFreeSlot should be merged into 1 function - OplDriver::OplDriver(const std::shared_ptr& opl) : m_opl([&opl] { if (opl == nullptr) @@ -32,15 +27,15 @@ namespace HyperSonicDrivers::drivers::midi::opl { m_oplWriter = std::make_unique(m_opl, m_opl3_mode); - for (uint8_t i = 0; i < audio::midi::MIDI_MAX_CHANNELS; ++i) { - m_channels[i] = std::make_unique(i); - } - m_voices.resize(m_oplNumChannels); for (uint8_t i = 0; i < m_oplNumChannels; ++i) { m_voices[i] = std::make_unique(i, m_oplWriter.get()); m_voicesFreeIndex.push_back(i); } + + for (uint8_t i = 0; i < audio::midi::MIDI_MAX_CHANNELS; ++i) { + m_channels[i] = std::make_unique(i); + } } OplDriver::~OplDriver() @@ -97,40 +92,6 @@ namespace HyperSonicDrivers::drivers::midi::opl // but the same logic of the thread will be performed here. } - void OplDriver::send(const audio::midi::MIDIEvent& e) noexcept - { - switch (TO_HIGH(e.type.high)) - { - case MIDI_EVENT_TYPES_HIGH::NOTE_OFF: - noteOff(e.type.low, e.data[0]); - break; - case MIDI_EVENT_TYPES_HIGH::NOTE_ON: - noteOn(e.type.low, e.data[0], e.data[1]); - break; - case MIDI_EVENT_TYPES_HIGH::AFTERTOUCH: - logW("AFTERTOUCH not supported"); - break; - case MIDI_EVENT_TYPES_HIGH::CONTROLLER: - controller(e.type.low, e.data[0], e.data[1]); - break; - case MIDI_EVENT_TYPES_HIGH::PROGRAM_CHANGE: - programChange(e.type.low, e.data[0]); - break; - case MIDI_EVENT_TYPES_HIGH::CHANNEL_AFTERTOUCH: - logW("CHANNEL_AFTERTOUCH not supported"); - break; - case MIDI_EVENT_TYPES_HIGH::PITCH_BEND: - pitchBend(e.type.low, static_cast((e.data[0] | (e.data[1] << 7) - 0x2000) >> 6)); - break; - case MIDI_EVENT_TYPES_HIGH::META_SYSEX: - logW("META_SYSEX not supported"); - break; - default: - logW(std::format("OplDriver: Unknown send() command {:#0x}", e.type.val)); - break; - } - } - void OplDriver::pause() const noexcept { for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) { @@ -153,9 +114,10 @@ namespace HyperSonicDrivers::drivers::midi::opl { const uint8_t sustain = m_channels[chan]->sustain; - for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end();) { - // TODO: this noteOff is masking the voice Release, not nice. - if (m_voices[*it]->noteOff(chan, note, sustain)) { + for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end();) + { + if (m_voices[*it]->getChannelNum() == chan && m_voices[*it]->noteOff(note, sustain)) + { m_voicesFreeIndex.push_back(*it); it = m_voicesInUseIndex.erase(it); } @@ -168,17 +130,17 @@ namespace HyperSonicDrivers::drivers::midi::opl { using audio::opl::banks::OP2Bank; - const bool isPercussion = chan == MIDI_PERCUSSION_CHANNEL; + const bool isPercussion = m_channels[chan]->isPercussion; int8_t freeSlot = getFreeOplVoiceIndex(!isPercussion); - if (freeSlot != -1) { const uint8_t instr_index = isPercussion ? OP2Bank::getPercussionIndex(note) : - m_channels[chan]->getProgram(); + m_channels[chan]->program; const auto instr = m_op2Bank->getInstrumentPtr(instr_index); + utils::logD(std::format("channel#={} - midi_ch={}", chan, m_channels[chan]->channel)); allocateVoice(freeSlot, chan, note, vol, instr, false); if (m_opl3_mode && OP2Bank::supportOpl3(instr)) @@ -194,128 +156,74 @@ namespace HyperSonicDrivers::drivers::midi::opl } } - void OplDriver::controller(const uint8_t chan, const uint8_t control, uint8_t value) const noexcept + void OplDriver::pitchBend(const uint8_t chan, const uint16_t bend) noexcept { - // MIDI_EVENT_CONTROLLER_TYPES - switch (control) + m_channels[chan]->pitch = bend; + for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) { - case 0: - case 32: - // Bank select. Not supported - logW(std::format("bank select value {}", value)); - break; - case 1: - ctrl_modulationWheel(chan, value); - //spdlog::debug("modwheel value {}", value); - break; - case 7: - ctrl_volume(chan, value); - break; - case 10: - // Not Available on OPL2/AdLib. - if (m_opl3_mode) - ctrl_panPosition(chan, value); - break; - case 16: - //pitchBendFactor(value); - logW(std::format("pitchBendFactor value {}", value)); - break; - case 17: - //detune(value); - logW(std::format("detune value {}", value)); - break; - case 18: - //priority(value); - logW(std::format("priority value {}", value)); - break; - case 64: - ctrl_sustain(chan, value); - break; - case 91: - // Effects level. Not supported. - //effectLevel(value); - logW(std::format("effect level value {}", value)); - break; - case 93: - // Chorus level. Not supported. - //chorusLevel(value); - logW(std::format("chorus level value {}", value)); - break; - case 119: - // Unknown, used in Simon the Sorcerer 2 - logW(std::format("unknown value {}", value)); - break; - case 121: - // reset all controllers - logW("reset all controllers value"); - //modulationWheel(0); - //pitchBendFactor(0); - //detune(0); - //sustain(false); - break; - case 123: - //spdlog::debug("all notes off"); - m_oplWriter->stopAll(); - break; - default: - logW(std::format("OplDriver: Unknown control change message {:d} {:d}", control, value)); + if (m_voices[*it]->getChannelNum() == chan) + m_voices[*it]->pitchBend(bend); } } - void OplDriver::programChange(const uint8_t chan, const uint8_t program) const noexcept + void OplDriver::sysEx(const uint8_t* msg, uint16_t length) noexcept { - m_channels[chan]->programChange(program); + // TODO ... } - void OplDriver::pitchBend(const uint8_t chan, const uint16_t bend) const noexcept - { - //spdlog::debug("PITCH_BEND {}", bend); - // OPLPitchWheel - m_channels[chan]->pitch = static_cast(bend); - - for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) - m_voices[*it]->pitchBend(chan, bend); - } - - - void OplDriver::ctrl_modulationWheel(const uint8_t chan, const uint8_t value) const noexcept + void OplDriver::ctrl_modulationWheel(const uint8_t chan, const uint8_t value) noexcept { m_channels[chan]->modulation = value; for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) - m_voices[*it]->ctrl_modulationWheel(chan, value); + { + if (m_voices[*it]->getChannelNum() == chan) + m_voices[*it]->ctrl_modulationWheel(value); + } } - void OplDriver::ctrl_volume(const uint8_t chan, const uint8_t value) const noexcept + void OplDriver::ctrl_volume(const uint8_t chan, const uint8_t value) noexcept { - //spdlog::debug("volume value {} -ch={}", value, chan); - m_channels[chan]->volume = value; for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) - m_voices[*it]->ctrl_volume(chan, value/*, abs_time*/); + { + if (m_voices[*it]->getChannelNum() == chan) + m_voices[*it]->ctrl_volume(value); + } } - void OplDriver::ctrl_panPosition(const uint8_t chan, uint8_t value) const noexcept + void OplDriver::ctrl_panPosition(const uint8_t chan, const uint8_t value) noexcept { - //spdlog::debug("panPosition value {}", value); + if (!m_opl3_mode) + return; - m_channels[chan]->pan = value -= 64; + m_channels[chan]->pan = value; for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) - m_voices[*it]->ctrl_panPosition(chan, value); + { + if (m_voices[*it]->getChannelNum() == chan) + m_voices[*it]->ctrl_panPosition(value); + } } - void OplDriver::ctrl_sustain(const uint8_t chan, uint8_t value) const noexcept + void OplDriver::ctrl_sustain(const uint8_t chan, const uint8_t value) noexcept { - //spdlog::debug("sustain value {}", value); m_channels[chan]->sustain = value; - if (value < SUSTAIN_THRESHOLD) + if (value < opl_sustain_threshold) releaseSustain(chan); } + void OplDriver::ctrl_allNotesOff() noexcept + { + m_oplWriter->stopAll(); + } + void OplDriver::releaseSustain(const uint8_t channel) const noexcept { for (auto it = m_voicesInUseIndex.begin(); it != m_voicesInUseIndex.end(); ++it) - m_voices[*it]->releaseSustain(channel); + { + if (m_voices[*it]->getChannelNum() == channel) + m_voices[*it]->releaseSustain(); + } } uint8_t OplDriver::releaseVoice(const uint8_t slot, const bool forced) @@ -330,11 +238,8 @@ namespace HyperSonicDrivers::drivers::midi::opl const audio::opl::banks::Op2BankInstrument_t* instrument, const bool secondary) { - const OplChannel* ch = m_channels[channel].get(); - return m_voices[slot]->allocate( - channel, note, volume, instrument, secondary, - ch->modulation, ch->volume, ch->pitch, ch->pan + m_channels[channel].get(), note, volume, instrument, secondary ); } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.hpp index 0b82046bc..db30cd8f9 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplDriver.hpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -38,10 +37,6 @@ namespace HyperSonicDrivers::drivers::midi::opl inline void setOP2Bank(const std::shared_ptr& op2Bank) noexcept { m_op2Bank = op2Bank; }; - void send(const audio::midi::MIDIEvent& e) /*const*/ noexcept override; - void send(uint32_t msg) override { /*TODO*/ }; - void send(int8_t channel, uint32_t msg) override { /*TODO*/ } - void pause() const noexcept override; void resume() const noexcept override; @@ -50,6 +45,21 @@ namespace HyperSonicDrivers::drivers::midi::opl protected: void onCallback() noexcept override; + // MIDI Events + void noteOff(const uint8_t chan, const uint8_t note) noexcept override; + void noteOn(const uint8_t chan, const uint8_t note, const uint8_t vol) noexcept override; + void pitchBend(const uint8_t chan, const uint16_t bend) noexcept override; + void sysEx(const uint8_t* msg, uint16_t length) noexcept override; + + // MIDI Controller Events + void ctrl_modulationWheel(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_volume(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_panPosition(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_sustain(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_reverb(const uint8_t chan, const uint8_t value) noexcept override {/*NOT SUPPORTED*/}; + void ctrl_chorus(const uint8_t chan, const uint8_t value) noexcept override {/*NOT SUPPORTED*/}; + void ctrl_allNotesOff() noexcept override; + private: std::shared_ptr m_opl; std::shared_ptr m_op2Bank; @@ -57,29 +67,14 @@ namespace HyperSonicDrivers::drivers::midi::opl const bool m_opl3_mode; const uint8_t m_oplNumChannels; - std::array, audio::midi::MIDI_MAX_CHANNELS> m_channels; std::vector> m_voices; std::unique_ptr m_oplWriter; // TODO review to make this index more efficient (and its complementary) + // TODO store this in a list in IMidiChannel instead, so the voices to a channel are already all there, no need of an index std::list m_voicesInUseIndex; std::list m_voicesFreeIndex; - // MIDI Events - void noteOff(const uint8_t chan, const uint8_t note) noexcept; - void noteOn(const uint8_t chan, const uint8_t note, const uint8_t vol) noexcept; - void controller(const uint8_t chan, const uint8_t ctrl, uint8_t value) const noexcept; - void programChange(const uint8_t chan, const uint8_t program) const noexcept; - void pitchBend(const uint8_t chan, const uint16_t bend) const noexcept; - - // MIDI Controller Events - void ctrl_modulationWheel(const uint8_t chan, const uint8_t value) const noexcept; - void ctrl_volume(const uint8_t chan, const uint8_t value) const noexcept; - void ctrl_panPosition(const uint8_t chan, uint8_t value) const noexcept; - void ctrl_sustain(const uint8_t chan, uint8_t value) const noexcept; - - //void onTimer(); - void releaseSustain(const uint8_t channel) const noexcept; uint8_t releaseVoice(const uint8_t slot, const bool forced); int allocateVoice(const uint8_t slot, const uint8_t channel, diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.cpp index 4778ff524..8370cf4dc 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.cpp @@ -1,97 +1,99 @@ #include #include #include +#include +#include namespace HyperSonicDrivers::drivers::midi::opl { + using audio::midi::MIDI_PERCUSSION_CHANNEL; using hardware::opl::OPL2instrument_t; - using audio::midi::MIDI_PERCUSSION_CHANNEL; constexpr int VIBRATO_THRESHOLD = 40; constexpr int8_t HIGHEST_NOTE = 127; - OplVoice::OplVoice(const uint8_t slot, const drivers::opl::OplWriter* oplWriter) : - _slot(slot), _oplWriter(oplWriter) + m_oplWriter(oplWriter), m_slot(slot) { } - bool OplVoice::noteOff(const uint8_t channel, const uint8_t note, const uint8_t sustain) noexcept + bool OplVoice::noteOff(const uint8_t note, const uint8_t sustain) noexcept { - if (isChannelBusy(channel) && _note == note) + if(!isFree() && m_note == note) { - if (sustain < SUSTAIN_THRESHOLD) { + if (sustain < opl_sustain_threshold) + { release(false); return true; } else - _sustain = true; + m_sustain = true; } return false; } - bool OplVoice::pitchBend(const uint8_t channel, const uint16_t bend) noexcept + bool OplVoice::pitchBend(const uint16_t bend) noexcept { - const bool b = isChannelBusy(channel); + const bool b = !isFree(); if (b) { - _pitch = static_cast(_finetune + bend); + m_pitch_factor = static_cast(m_finetune + (bend >> 6)); playNote(true); } return b; } - bool OplVoice::ctrl_modulationWheel(const uint8_t channel, const uint8_t value) noexcept + bool OplVoice::ctrl_modulationWheel(const uint8_t value) noexcept { - const bool b = isChannelBusy(channel); + const bool b = !isFree(); if (b) { if (value >= VIBRATO_THRESHOLD) { - if (!_vibrato) - _oplWriter->writeModulation(_slot, _instr, true); - _vibrato = true; + if (!m_vibrato) + m_oplWriter->writeModulation(m_slot, m_instr, true); + m_vibrato = true; } else { - if (_vibrato) - _oplWriter->writeModulation(_slot, _instr, false); - _vibrato = false; + if (m_vibrato) + m_oplWriter->writeModulation(m_slot, m_instr, false); + m_vibrato = false; } } return b; } - bool OplVoice::ctrl_volume(const uint8_t channel, const uint8_t value) noexcept + bool OplVoice::ctrl_volume(const uint8_t value) noexcept { - const bool b = isChannelBusy(channel); + const bool b = !isFree(); if (b) { - setRealVolume(value); - _oplWriter->writeVolume(_slot, _instr, getRealVolume()); + setVolumes(value); + m_oplWriter->writeVolume(m_slot, m_instr, m_real_volume); } return b; } - bool OplVoice::ctrl_panPosition(const uint8_t channel, const uint8_t value) noexcept + bool OplVoice::ctrl_panPosition(const uint8_t value) noexcept { - const bool b = isChannelBusy(channel); + const bool b = !isFree(); if (b) { - _pan = value; - _oplWriter->writePan(_slot, _instr, value); + m_channel->pan = value; + m_oplWriter->writePan(m_slot, m_instr, value); } return b; } - bool OplVoice::releaseSustain(const uint8_t channel) noexcept + bool OplVoice::releaseSustain() noexcept { - const bool b = isChannelBusy(channel) && _sustain; + const bool b = !isFree() && m_sustain; if (b) release(false); @@ -100,92 +102,95 @@ namespace HyperSonicDrivers::drivers::midi::opl void OplVoice::playNote(const bool keyOn) const noexcept { - _oplWriter->writeNote(_slot, _realnote, _pitch, keyOn); + m_oplWriter->writeNote(m_slot, m_real_note, m_pitch_factor, keyOn); } int OplVoice::allocate( - const uint8_t channel, + IMidiChannel* channel, const uint8_t note, const uint8_t volume, const audio::opl::banks::Op2BankInstrument_t* instrument, - const bool secondary, - const uint8_t chan_modulation, - const uint8_t chan_vol, - const uint8_t chan_pitch, - const uint8_t chan_pan + const bool secondary ) noexcept { using audio::opl::banks::OP2Bank; int16_t note_ = note; - _channel = channel; - _note = note; - _free = false; - _secondary = secondary; - _pan = chan_pan; + m_channel = channel; + m_note = note; + m_free = false; + m_secondary = secondary; - if (chan_modulation >= VIBRATO_THRESHOLD) - _vibrato = true; + if (m_channel->modulation >= VIBRATO_THRESHOLD) + m_vibrato = true; - setVolumes(chan_vol, volume); + setVolumes(volume); if (OP2Bank::isPercussion(instrument)) note_ = instrument->noteNum; - else if (channel == MIDI_PERCUSSION_CHANNEL) + else if (channel->isPercussion) note_ = 60; // C-5 if (secondary && OP2Bank::supportOpl3(instrument)) - _finetune = instrument->fineTune - 0x80; + m_finetune = instrument->fineTune - 0x80; else - _finetune = 0; + m_finetune = 0; - _pitch = _finetune + chan_pitch; + m_pitch_factor = static_cast(m_finetune + m_channel->pitch); - _instr = &instrument->voices[secondary ? 1 : 0]; + setInstrument(&instrument->voices[secondary ? 1 : 0]); - if ((note_ += _instr->basenote) < 0) + if ((note_ += m_instr->basenote) < 0) while ((note_ += 12) < 0) {} else if (note_ > HIGHEST_NOTE) while ((note_ -= 12) > HIGHEST_NOTE); - _realnote = static_cast(note_); + m_real_note = static_cast(note_); - _oplWriter->writeInstrument(_slot, _instr); - if (_vibrato) - _oplWriter->writeModulation(_slot, _instr, true); - _oplWriter->writePan(_slot, _instr, chan_pan); - _oplWriter->writeVolume(_slot, _instr, getRealVolume()); + m_oplWriter->writeInstrument(m_slot, m_instr); + if (m_vibrato) + m_oplWriter->writeModulation(m_slot, m_instr, true); + m_oplWriter->writePan(m_slot, m_instr, m_channel->pan); + m_oplWriter->writeVolume(m_slot, m_instr, m_real_volume); playNote(true); - return _slot; + return m_slot; } uint8_t OplVoice::release(const bool forced) noexcept { playNote(false); - _free = true; + m_free = true; if (forced) { - _oplWriter->writeChannel(0x80, _slot, 0x0F, 0x0F); // release rate - fastest - _oplWriter->writeChannel(0x40, _slot, 0x3F, 0x3F); // no volume + m_oplWriter->writeChannel(0x80, m_slot, 0x0F, 0x0F); // release rate - fastest + m_oplWriter->writeChannel(0x40, m_slot, 0x3F, 0x3F); // no volume } - return _slot; + return m_slot; } void OplVoice::pause() const noexcept { - _oplWriter->writeVolume(_slot, _instr, 0); - _oplWriter->writeChannel(0x60, _slot, 0, 0); // attack, decay - _oplWriter->writeChannel(0x80, _slot, - _instr->sust_rel_1 & 0xF0, - _instr->sust_rel_2 & 0xF0); // sustain, release + m_oplWriter->writeVolume(m_slot, m_instr, 0); + m_oplWriter->writeChannel(0x60, m_slot, 0, 0); // attack, decay + m_oplWriter->writeChannel(0x80, m_slot, + m_instr->sust_rel_1 & 0xF0, + m_instr->sust_rel_2 & 0xF0); // sustain, release } void OplVoice::resume() const noexcept { - _oplWriter->writeChannel(0x60, _slot, _instr->att_dec_1, _instr->att_dec_2); - _oplWriter->writeChannel(0x80, _slot, _instr->sust_rel_1, _instr->sust_rel_2); - _oplWriter->writeVolume(_slot, _instr, getRealVolume()); - _oplWriter->writePan(_slot, getInstrument(), _pan); + m_oplWriter->writeChannel(0x60, m_slot, m_instr->att_dec_1, m_instr->att_dec_2); + m_oplWriter->writeChannel(0x80, m_slot, m_instr->sust_rel_1, m_instr->sust_rel_2); + m_oplWriter->writeVolume(m_slot, m_instr, m_real_volume); + m_oplWriter->writePan(m_slot, getInstrument(), m_channel->pan); + } + + void OplVoice::setInstrument(const hardware::opl::OPL2instrument_t* instr) noexcept + { + if (instr == nullptr) + utils::throwLogC("OPL2instrument_t is null"); + + m_instr = instr; } } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.hpp index 66dc1b1b9..6625954df 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/opl/OplVoice.hpp @@ -1,117 +1,70 @@ #pragma once #include +#include #include #include #include namespace HyperSonicDrivers::drivers::midi::opl { - constexpr int SUSTAIN_THRESHOLD = 64; + constexpr int opl_sustain_threshold = 64; + /// - /// This is only the execution for a OplChannel: a Voice (OplVoice) + /// This is only the execution for a MidiChannel /// used in a MIDI Channel -> Opl Channel /// - class OplVoice + class OplVoice : public IMidiChannelVoice { public: explicit OplVoice(const uint8_t slot, const drivers::opl::OplWriter* oplWriter); - ~OplVoice() = default; + ~OplVoice() override = default; /// /// It might release the note depending on sustains value /// - /// /// /// /// true = voice released. false=voice sutained - bool noteOff(const uint8_t channel, const uint8_t note, const uint8_t sustain) noexcept; - bool pitchBend(const uint8_t channel, const uint16_t bend) noexcept; - bool ctrl_modulationWheel(const uint8_t channel, const uint8_t value) noexcept; - bool ctrl_volume(const uint8_t channel, const uint8_t value) noexcept; - bool ctrl_panPosition(const uint8_t channel, const uint8_t value) noexcept; - bool releaseSustain(const uint8_t channel) noexcept; + bool noteOff(const uint8_t note, const uint8_t sustain) noexcept; + bool pitchBend(const uint16_t bend) noexcept; + bool ctrl_modulationWheel(const uint8_t value) noexcept; + bool ctrl_volume(const uint8_t value) noexcept; + bool ctrl_panPosition(const uint8_t value) noexcept; + bool releaseSustain() noexcept; void playNote(const bool keyOn) const noexcept; /// /// This works only with OP2Bank. /// TODO: need to generalize OplBank instruments ... /// - int allocate(const uint8_t channel, + int allocate(IMidiChannel* channel, const uint8_t note, const uint8_t volume, const audio::opl::banks::Op2BankInstrument_t* instrument, - const bool secondary, - const uint8_t chan_modulation, - const uint8_t chan_vol, - const uint8_t chan_pitch, - const uint8_t chan_pan + const bool secondary ) noexcept; uint8_t release(const bool forced) noexcept; void pause() const noexcept; void resume() const noexcept; - inline void setVolumes(const uint8_t channelVolume, const uint8_t volume) noexcept { - _volume = volume; - setRealVolume(channelVolume); - } - inline void setRealVolume(const uint8_t channelVolume) noexcept { _realvolume = _calcVolume(channelVolume); } - inline uint8_t getRealVolume() const noexcept { return _realvolume; } - inline uint8_t getChannel() const noexcept { return _channel; } - inline const hardware::opl::OPL2instrument_t* getInstrument() const noexcept { return _instr; } + inline const hardware::opl::OPL2instrument_t* getInstrument() const noexcept { return m_instr; } // Methods to get private variables, not really used - inline uint8_t getSlot() const noexcept { return _slot; } - inline bool isFree() const noexcept { return _free; } - inline bool isSecondary() const noexcept { return _secondary; } - inline bool isChannel(const uint8_t channel) const noexcept { return _channel == channel; } - inline bool isChannelBusy(const uint8_t channel) const noexcept { return isChannel(channel) && !_free; } - inline bool isChannelFree(uint8_t channel) const noexcept { return isChannel(channel) && _free; } - inline bool isVibrato() const noexcept { return _vibrato; } - inline uint8_t getPan() const noexcept { return _pan; } - inline uint8_t getNote() const noexcept { return _note; } - inline uint8_t getVolume() const noexcept { return _volume; } - inline uint8_t getPitch() const noexcept { return _pitch; } + inline uint8_t getSlot() const noexcept { return m_slot; } + inline bool isSecondary() const noexcept { return m_secondary; } protected: - // Methods to Mock the class, not really used except for mocking - inline void setChannel(const uint8_t channel) noexcept { _channel = channel; } - inline void setFree(const bool free) noexcept { _free = free; }; - inline void setInstrument(const hardware::opl::OPL2instrument_t* instr) noexcept { _instr = instr; } - inline void setVibrato(const bool vibrato) noexcept { _vibrato = vibrato; }; + void setInstrument(const hardware::opl::OPL2instrument_t* instr) noexcept; private: - const uint8_t _slot; /* OPL channel number */ - - uint8_t _volume = 0; /* note volume */ - uint8_t _realvolume = 0; /* adjusted note volume */ - uint8_t _channel = 0; // MIDI channel number - uint8_t _note = 0; /* note number */ - uint8_t _realnote = 0; /* adjusted note number */ - int8_t _finetune = 0; /* frequency fine-tune */ - int16_t _pitch = 0; /* pitch-wheel value */ - uint8_t _pan = 64; /* pan value */ - - const hardware::opl::OPL2instrument_t* _instr = nullptr; /* current instrument */ - - //uint32_t _time = 0; /* note start time */ - // Channel flags - bool _free = true; - bool _secondary = false; - bool _sustain = false; - bool _vibrato = false; + const drivers::opl::OplWriter* m_oplWriter; + const uint8_t m_slot; /* OPL channel number */ + uint8_t m_real_note = 0; /* adjusted note number */ + int8_t m_finetune = 0; /* frequency fine-tune */ + const hardware::opl::OPL2instrument_t* m_instr = nullptr; /* current instrument */ - const drivers::opl::OplWriter* _oplWriter; - - /// - /// The volume is between 0-127 as a per MIDI specification. - /// OPLWriter expect a MIDI volume value and converts to OPL value. - /// OPL chips has a volume attenuation (inverted values) - /// range from 0-64 inverted (0 is max, 64 is muted). - /// - inline uint8_t _calcVolume(const uint8_t channelVolume) const noexcept { - return std::min((static_cast(channelVolume) * _volume / 127), 127); - } + bool m_secondary = false; }; } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.cpp new file mode 100644 index 000000000..fa70ac607 --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +namespace HyperSonicDrivers::drivers::midi::scummvm +{ + using utils::logW; + + AdLibChannel::AdLibChannel(const uint8_t channel) : + IMidiChannel(channel) + { + memset(&_partInstr, 0, sizeof(_partInstr)); + memset(&_partInstrSecondary, 0, sizeof(_partInstrSecondary)); + } + + void AdLibChannel::setInstr(const bool isOpl3) noexcept + { + if (isOpl3) + { + memcpy(&_partInstr, &g_gmInstrumentsOPL3[program][0], sizeof(AdLibInstrument)); + memcpy(&_partInstrSecondary, &g_gmInstrumentsOPL3[program][1], sizeof(AdLibInstrument)); + } + else + { + memcpy(&_partInstr, &g_gmInstruments[program], sizeof(AdLibInstrument)); + } + } + + void AdLibChannel::setCustomInstr(const uint8_t* instr) noexcept + { + memcpy(&_partInstr, instr, sizeof(AdLibInstrument)); + } +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.hpp new file mode 100644 index 000000000..0402aa47d --- /dev/null +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibChannel.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace HyperSonicDrivers::drivers::midi::scummvm +{ + struct AdLibVoice; + + class AdLibChannel : public IMidiChannel + { + public: + explicit AdLibChannel(const uint8_t channel); + + inline const AdLibInstrument* getInstr() const noexcept { return &_partInstr; }; + inline const AdLibInstrument* getInstrSecondary() const noexcept { return &_partInstrSecondary; }; + void setInstr(const bool isOpl3) noexcept; + virtual void setCustomInstr(const uint8_t* instr) noexcept; + + AdLibVoice* voice = nullptr; + uint8_t pitchBendFactor = 2; + int8_t detuneEff = 0; + uint8_t priority = 127; + //int8_t _transposeEff; + + protected: + AdLibInstrument _partInstr; + AdLibInstrument _partInstrSecondary; + }; +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibInstrument.h b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibInstrument.h index 6717b2cdd..a437ae58e 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibInstrument.h +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibInstrument.h @@ -20,19 +20,20 @@ namespace HyperSonicDrivers::drivers::midi::scummvm struct AdLibInstrument { + // TODO: divide this struct in OPLInstrument and the extra used in the driver // NOTE: the level_1 and level_2 are computed instead of stored in the instrument. - uint8_t modCharacteristic; // trem_vibr_1 - uint8_t modScalingOutputLevel; // scale_1 - uint8_t modAttackDecay; // att_dec_1 - uint8_t modSustainRelease; // sust_rel_1 - uint8_t modWaveformSelect; // wave_1 - uint8_t carCharacteristic; // trem_vibr_2 - uint8_t carScalingOutputLevel; // scale_2 - uint8_t carAttackDecay; // att_dec_2 - uint8_t carSustainRelease; // sust_rel_2 - uint8_t carWaveformSelect; // wave_2 - uint8_t feedback; // feedback - uint8_t flagsA; // unused? + uint8_t modCharacteristic; // trem_vibr_1, 00 + uint8_t modScalingOutputLevel; // scale_1, 04 + uint8_t modAttackDecay; // att_dec_1, 01 + uint8_t modSustainRelease; // sust_rel_1, 02 + uint8_t modWaveformSelect; // wave_1, 03 + uint8_t carCharacteristic; // trem_vibr_2 07 + uint8_t carScalingOutputLevel; // scale_2 0B + uint8_t carAttackDecay; // att_dec_2 08 + uint8_t carSustainRelease; // sust_rel_2 09 + uint8_t carWaveformSelect; // wave_2 0A + uint8_t feedback; // feedback 06 + uint8_t flagsA; // unused? InstrumentExtra extraA; uint8_t flagsB; InstrumentExtra extraB; diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.cpp deleted file mode 100644 index d5961dbf0..000000000 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.cpp +++ /dev/null @@ -1,265 +0,0 @@ -#include -#include -#include -#include -#include - -namespace HyperSonicDrivers::drivers::midi::scummvm -{ - using utils::logW; - - void AdLibPart::init(MidiDriver_ADLIB* owner, uint8_t channel) - { - _owner = owner; - _channel = channel; - _priEff = 127; - programChange(0); - } - - void AdLibPart::allocate() - { - _allocated = true; - } - - AdLibPart::AdLibPart() - { - memset(&_partInstr, 0, sizeof(_partInstr)); - memset(&_partInstrSecondary, 0, sizeof(_partInstrSecondary)); - } - - MidiDriver* AdLibPart::device() { - return _owner; - } - - uint8_t AdLibPart::getNumber() - { - return _channel; - } - - void AdLibPart::release() - { - _allocated = false; - } - - void AdLibPart::send(uint32_t b) { - _owner->send(_channel, b); - } - - void AdLibPart::noteOff(uint8_t note) { -#ifdef DEBUG_ADLIB - debug(6, "%10d: noteOff(%d)", g_tick, note); -#endif - _owner->partKeyOff(this, note); - } - - void AdLibPart::noteOn(uint8_t note, uint8_t velocity) { -#ifdef DEBUG_ADLIB - debug(6, "%10d: noteOn(%d,%d)", g_tick, note, velocity); -#endif - _owner->partKeyOn(this, &_partInstr, note, velocity, - &_partInstrSecondary, - _pan); - } - - void AdLibPart::programChange(uint8_t program) { - if (program > 127) - return; - - _program = program; - if (!_owner->m_opl3Mode) { - memcpy(&_partInstr, &g_gmInstruments[program], sizeof(AdLibInstrument)); - } - else { - memcpy(&_partInstr, &g_gmInstrumentsOPL3[program][0], sizeof(AdLibInstrument)); - memcpy(&_partInstrSecondary, &g_gmInstrumentsOPL3[program][1], sizeof(AdLibInstrument)); - } - - //spdlog::debug("Program {} {}", _channel, program); - } - - void AdLibPart::pitchBend(int16_t bend) - { - _pitchBend = bend; - for (AdLibVoice* voice = _voice; voice; voice = voice->_next) - { - if (!_owner->m_opl3Mode) - { - _owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/, - (_pitchBend * _pitchBendFactor >> 6) + _detuneEff); - } - else - { - _owner->adlibNoteOn(voice->_channel, voice->_note, _pitchBend >> 1); - } - } - } - - void AdLibPart::controlChange(uint8_t control, uint8_t value) { - switch (control) { - case 0: - case 32: - // Bank select. Not supported - break; - case 1: - modulationWheel(value); - //spdlog::debug("modwheel value {}", value); - break; - case 7: - volume(value); - //spdlog::debug("volume value {}", value); - break; - case 10: - panPosition(value); - break; - case 16: - pitchBendFactor(value); - break; - case 17: - detune(value); - break; - case 18: - priority(value); - break; - case 64: - sustain(value > 0); - break; - case 91: - // Effects level. Not supported. - effectLevel(value); - break; - case 93: - // Chorus level. Not supported. - chorusLevel(value); - break; - case 119: - // Unknown, used in Simon the Sorcerer 2 - break; - case 121: - // reset all controllers - modulationWheel(0); - pitchBendFactor(0); - detune(0); - sustain(false); - break; - case 123: - allNotesOff(); - break; - default: - logW(std::format("Unknown control change message {:d} {:d}", control, value)); - } - } - - void AdLibPart::modulationWheel(uint8_t value) - { - _modWheel = value; - for (AdLibVoice* voice = _voice; voice; voice = voice->_next) - { - if (voice->_s10a.active && voice->_s11a.flag0x40) - voice->_s10a.modWheel = _modWheel >> 2; - if (voice->_s10b.active && voice->_s11b.flag0x40) - voice->_s10b.modWheel = _modWheel >> 2; - } - } - - void AdLibPart::volume(uint8_t value) - { - _volEff = value; - for (AdLibVoice* voice = _voice; voice; voice = voice->_next) - { - if (!_owner->m_opl3Mode) - { - _owner->adlibSetParam(voice->_channel, 0, g_volumeTable[g_volumeLookupTable[voice->_vol2][_volEff >> 2]]); - if (voice->_twoChan) { - _owner->adlibSetParam(voice->_channel, 13, g_volumeTable[g_volumeLookupTable[voice->_vol1][_volEff >> 2]]); - } - } - else - { - _owner->adlibSetParam(voice->_channel, 0, g_volumeTable[((voice->_vol2 + 1) * _volEff) >> 7], true); - _owner->adlibSetParam(voice->_channel, 0, g_volumeTable[((voice->_secVol2 + 1) * _volEff) >> 7], false); - if (voice->_twoChan) { - _owner->adlibSetParam(voice->_channel, 13, g_volumeTable[((voice->_vol1 + 1) * _volEff) >> 7], true); - } - if (voice->_secTwoChan) { - _owner->adlibSetParam(voice->_channel, 13, g_volumeTable[((voice->_secVol1 + 1) * _volEff) >> 7], false); - } - } - } - } - - void AdLibPart::panPosition(uint8_t value) { - _pan = value; - } - - void AdLibPart::pitchBendFactor(uint8_t value) - { - // Not supported in OPL3 mode. - if (_owner->m_opl3Mode) { - return; - } - - _pitchBendFactor = value; - for (AdLibVoice* voice = _voice; voice; voice = voice->_next) - { - _owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/, - (_pitchBend * _pitchBendFactor >> 6) + _detuneEff); - } - } - - void AdLibPart::detune(uint8_t value) - { - // Sam&Max's OPL3 driver uses this for a completly different purpose. It - // is related to voice allocation. We ignore this for now. - // TODO: We probably need to look how the interpreter side of Sam&Max's - // iMuse version handles all this too. Implementing the driver side here - // would be not that hard. - if (_owner->m_opl3Mode) { - //_maxNotes = value; - return; - } - - _detuneEff = value; - for (AdLibVoice* voice = _voice; voice; voice = voice->_next) - { - _owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/, - (_pitchBend * _pitchBendFactor >> 6) + _detuneEff); - } - } - - void AdLibPart::priority(uint8_t value) { - _priEff = value; - } - - void AdLibPart::sustain(bool value) - { - _pedal = value; - if (!value) { - for (AdLibVoice* voice = _voice; voice; voice = voice->_next) - { - if (voice->_waitForPedal) - _owner->mcOff(voice); - } - } - } - - void AdLibPart::allNotesOff() - { - while (_voice) - _owner->mcOff(_voice); - } - - void AdLibPart::sysEx_customInstrument(uint32_t type, const uint8_t* instr) - { - // Sam&Max allows for instrument overwrites, but we will not support it - // until we can find any track actually using it. - if (_owner->m_opl3Mode) - { - logW("Used in OPL3 mode, not supported"); - return; - } - - if (type == static_cast('ADL ')) { - memcpy(&_partInstr, instr, sizeof(AdLibInstrument)); - } - } -} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.hpp deleted file mode 100644 index f0df8c2f2..000000000 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPart.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -namespace HyperSonicDrivers::drivers::midi::scummvm -{ - struct AdLibVoice; - class MidiDriver_ADLIB; - - class AdLibPart : public MidiChannel - { - friend class MidiDriver_ADLIB; - - protected: - // AdLibPart *_prev, *_next; - AdLibVoice* _voice = nullptr; - int16_t _pitchBend = 0; - uint8_t _pitchBendFactor = 2; - //int8_t _transposeEff; - uint8_t _volEff = 0; - int8_t _detuneEff = 0; - uint8_t _modWheel = 0; - bool _pedal = false; - uint8_t _program = 0; - uint8_t _priEff = 0; - uint8_t _pan = 64; - AdLibInstrument _partInstr; - AdLibInstrument _partInstrSecondary; - - MidiDriver_ADLIB* _owner = nullptr; - bool _allocated = false; - uint8_t _channel = 0; - - void init(MidiDriver_ADLIB* owner, uint8_t channel); - void allocate(); - - public: - AdLibPart(); - - MidiDriver* device() override; - uint8_t getNumber() override; - void release() override; - - void send(uint32_t b) override; - - // Regular messages - void noteOff(uint8_t note) override; - void noteOn(uint8_t note, uint8_t velocity) override; - void programChange(uint8_t program) override; - void pitchBend(int16_t bend) override; - - // Control Change messages - void controlChange(uint8_t control, uint8_t value) override; - void modulationWheel(uint8_t value) override; - void volume(uint8_t value) override; - void panPosition(uint8_t value) override; - void pitchBendFactor(uint8_t value) override; - void detune(uint8_t value) override; - void priority(uint8_t value) override; - void sustain(bool value) override; - void effectLevel(uint8_t value) override { return; } // Not supported - void chorusLevel(uint8_t value) override { return; } // Not supported - void allNotesOff() override; - - // SysEx messages - void sysEx_customInstrument(uint32_t type, const uint8_t* instr) override; - }; -} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.cpp index 89cca0a89..9b123fc00 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.cpp @@ -10,103 +10,70 @@ namespace HyperSonicDrivers::drivers::midi::scummvm using utils::logD; using utils::logW; - void AdLibPercussionChannel::init(MidiDriver_ADLIB* owner, uint8_t channel) + AdLibPercussionChannel::AdLibPercussionChannel() : + AdLibChannel(audio::midi::MIDI_PERCUSSION_CHANNEL) { - AdLibPart::init(owner, channel); - _priEff = 0; - _volEff = 127; + priority = 0; + volume = 127; // Initialize the custom instruments data - std::ranges::fill(_notes, 0); - std::ranges::fill(_customInstruments, nullptr); + std::ranges::fill(m_notes, 0); + std::ranges::fill(m_customInstruments, nullptr); } - void AdLibPercussionChannel::noteOff(uint8_t note) + uint8_t AdLibPercussionChannel::getNote(const uint8_t note) const noexcept { - if (_customInstruments[note]) - { - note = _notes[note]; - } - - // This used to ignore note off events, since the builtin percussion - // instrument data has a duration value, which causes the percussion notes - // to stop automatically. This is not the case for (Groovie's) custom - // percussion instruments though. Also the OPL3 driver of Sam&Max actually - // does not handle the duration value, so we need it there too. - _owner->partKeyOff(this, note); + if (m_customInstruments[note]) + return m_notes[note]; + return note; } - void AdLibPercussionChannel::noteOn(uint8_t note, uint8_t velocity) { - const AdLibInstrument* inst = nullptr; - const AdLibInstrument* sec = nullptr; - - // The custom instruments have priority over the default mapping - // We do not support custom instruments in OPL3 mode though. - if (!_owner->m_opl3Mode) - { - inst = _customInstruments[note].get(); - if (inst) - note = _notes[note]; - } - - if (!inst) - { - // Use the default GM to FM mapping as a fallback - uint8_t key = g_gmPercussionInstrumentMap[note]; - if (key != 0xFF) { - if (!_owner->m_opl3Mode) - { - inst = &g_gmPercussionInstruments[key]; - } - else - { - inst = &g_gmPercussionInstrumentsOPL3[key][0]; - sec = &g_gmPercussionInstrumentsOPL3[key][1]; - } - } - } + AdLibInstrument* AdLibPercussionChannel::getCustomInstrument(const uint8_t note) const noexcept + { + return m_customInstruments[note].get(); + } - if (!inst) - { - logD(std::format("No instrument FM definition for GM percussion key {:d}", note)); - return; - } + //void AdLibPercussionChannel::setInstr(const bool isOpl3) noexcept + //{ + // // Use the default GM to FM mapping as a fallback + // const uint8_t key = g_gmPercussionInstrumentMap[program]; + // if (key != 0xFF) + // { + // if (isOpl3) + // { + // memcpy(&_partInstr, &g_gmPercussionInstrumentsOPL3[program][0], sizeof(AdLibInstrument)); + // memcpy(&_partInstrSecondary, &g_gmPercussionInstrumentsOPL3[program][1], sizeof(AdLibInstrument)); + // } + // else + // { + // memcpy(&_partInstr, &g_gmPercussionInstruments[program], sizeof(AdLibInstrument)); + // } + // } + //} - _owner->partKeyOn(this, inst, note, velocity, sec, _pan); - } + void AdLibPercussionChannel::setCustomInstr(const uint8_t* instr) noexcept + { + const uint8_t note = instr[0]; + m_notes[note] = instr[1]; - void AdLibPercussionChannel::sysEx_customInstrument(uint32_t type, const uint8_t* instr) { - // We do not allow custom instruments in OPL3 mode right now. - if (_owner->m_opl3Mode) + // Allocate memory for the new instruments + if (!m_customInstruments[note]) { - logW("Used in OPL3 mode"); - return; + m_customInstruments[note] = std::make_unique(); + memset(m_customInstruments[note].get(), 0, sizeof(AdLibInstrument)); } - if (type == static_cast('ADLP')) - { - uint8_t note = instr[0]; - _notes[note] = instr[1]; - - // Allocate memory for the new instruments - if (!_customInstruments[note]) - { - _customInstruments[note] = std::make_unique(); - memset(_customInstruments[note].get(), 0, sizeof(AdLibInstrument)); - } - - // Save the new instrument data - _customInstruments[note]->modCharacteristic = instr[2]; - _customInstruments[note]->modScalingOutputLevel = instr[3]; - _customInstruments[note]->modAttackDecay = instr[4]; - _customInstruments[note]->modSustainRelease = instr[5]; - _customInstruments[note]->modWaveformSelect = instr[6]; - _customInstruments[note]->carCharacteristic = instr[7]; - _customInstruments[note]->carScalingOutputLevel = instr[8]; - _customInstruments[note]->carAttackDecay = instr[9]; - _customInstruments[note]->carSustainRelease = instr[10]; - _customInstruments[note]->carWaveformSelect = instr[11]; - _customInstruments[note]->feedback = instr[12]; - } + // Save the new instrument data + m_customInstruments[note]->modCharacteristic = instr[2]; + m_customInstruments[note]->modScalingOutputLevel = instr[3]; + m_customInstruments[note]->modAttackDecay = instr[4]; + m_customInstruments[note]->modSustainRelease = instr[5]; + m_customInstruments[note]->modWaveformSelect = instr[6]; + m_customInstruments[note]->carCharacteristic = instr[7]; + m_customInstruments[note]->carScalingOutputLevel = instr[8]; + m_customInstruments[note]->carAttackDecay = instr[9]; + m_customInstruments[note]->carSustainRelease = instr[10]; + m_customInstruments[note]->carWaveformSelect = instr[11]; + m_customInstruments[note]->feedback = instr[12]; } } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.hpp index 3cddf905a..85e8942df 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdLibPercussionChannel.hpp @@ -3,44 +3,28 @@ #include #include #include -#include +#include #include namespace HyperSonicDrivers::drivers::midi::scummvm { - // FYI (Jamieson630) - // It is assumed that any invocation to AdLibPercussionChannel - // will be done through the MidiChannel base class as opposed to the - // AdLibPart base class. If this were NOT the case, all the functions - // listed below would need to be virtual in AdLibPart as well as MidiChannel. - class AdLibPercussionChannel : public AdLibPart + /** + * TODO: It should be derived from IMidiChannel as it has very little in common from AdLibChannel class + **/ + class AdLibPercussionChannel : public AdLibChannel { - friend class MidiDriver_ADLIB; - - protected: - void init(MidiDriver_ADLIB* owner, uint8_t channel); - public: - AdLibPercussionChannel() = default; + AdLibPercussionChannel(); ~AdLibPercussionChannel() override = default; - void noteOff(uint8_t note) override; - void noteOn(uint8_t note, uint8_t velocity) override; - void programChange(uint8_t program) override { } - - // Control Change messages - void modulationWheel(uint8_t value) override { } - void pitchBendFactor(uint8_t value) override { } - void detune(uint8_t value) override { } - void priority(uint8_t value) override { } - void sustain(bool value) override { } + uint8_t getNote(const uint8_t note) const noexcept; + AdLibInstrument* getCustomInstrument(const uint8_t note) const noexcept; - // SysEx messages - void sysEx_customInstrument(uint32_t type, const uint8_t* instr) override; + void setCustomInstr(const uint8_t* instr) noexcept override; private: - std::array _notes = { 0 }; - std::array, 256> _customInstruments; + std::array m_notes = { 0 }; + std::array, 256> m_customInstruments; }; } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdlibVoice.h b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdlibVoice.h index 07071e265..2361579ec 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdlibVoice.h +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/AdlibVoice.h @@ -2,7 +2,8 @@ #include #include -#include +#include +#include namespace HyperSonicDrivers::drivers::midi::scummvm { @@ -36,26 +37,32 @@ namespace HyperSonicDrivers::drivers::midi::scummvm Struct10* s10; }; - struct AdLibVoice + class AdLibVoice : public IMidiChannelVoice { - AdLibPart* _part; - AdLibVoice* _next, * _prev; - uint8_t _waitForPedal; - uint8_t _note; - uint8_t _channel; - uint8_t _twoChan; - uint8_t _vol1, _vol2; - int16_t _duration; - - Struct10 _s10a; - Struct11 _s11a; - Struct10 _s10b; - Struct11 _s11b; - - uint8_t _secTwoChan; - uint8_t _secVol1, _secVol2; - - AdLibVoice() { memset(this, 0, sizeof(AdLibVoice)); } - }; -} + public: + AdLibVoice* next = nullptr; + AdLibVoice* prev = nullptr; + + uint8_t slot = 0; // NOTE: this is between 0 and 9, this is the "slot" (OPL channel number, not the MIDI Channel) + uint8_t twoChan = 0; + uint8_t vol1 = 0; // mod volume + uint8_t vol2 = 0; // car volume + int16_t duration = 0; + + Struct10 _s10a = { 0 }; + Struct11 _s11a = { 0 }; + Struct10 _s10b = { 0 }; + Struct11 _s11b = { 0 }; + // related to OPL3 + uint8_t secTwoChan = 0; + uint8_t secVol1 = 0; + uint8_t secVol2 = 0; + + AdLibVoice() = default; + inline void setNote(const uint8_t note) { m_note = note; } + inline void setFree(const bool free) { m_free = free; }; + inline void setChannel(AdLibChannel* chan) { m_channel = chan; }; + inline void setWaitForPedal(const bool waitForPedal) { m_sustain = waitForPedal; }; + }; +} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiChannel.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiChannel.hpp deleted file mode 100644 index 12bf7feec..000000000 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiChannel.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include -#include - -namespace HyperSonicDrivers::drivers::midi::scummvm -{ - class MidiChannel - { - public: - virtual ~MidiChannel() = default; - - virtual MidiDriver* device() = 0; - virtual uint8_t getNumber() = 0; - virtual void release() = 0; - - virtual void send(uint32_t b) = 0; // 4-bit channel portion is ignored - - // Regular messages - virtual void noteOff(uint8_t note) = 0; - virtual void noteOn(uint8_t note, uint8_t velocity) = 0; - virtual void programChange(uint8_t program) = 0; - virtual void pitchBend(int16_t bend) = 0; // -0x2000 to +0x1FFF - - // Control Change messages - virtual void controlChange(uint8_t control, uint8_t value) = 0; - virtual void modulationWheel(uint8_t value) { controlChange(MidiDriver::MIDI_CONTROLLER_MODULATION, value); } - virtual void volume(uint8_t value) { controlChange(MidiDriver::MIDI_CONTROLLER_VOLUME, value); } - virtual void panPosition(uint8_t value) { controlChange(MidiDriver::MIDI_CONTROLLER_PANNING, value); } - virtual void pitchBendFactor(uint8_t value) = 0; - virtual void transpose(int8_t value) {} - virtual void detune(uint8_t value) { controlChange(17, value); } - virtual void priority(uint8_t value) { } - virtual void sustain(bool value) { controlChange(MidiDriver::MIDI_CONTROLLER_SUSTAIN, value ? 1 : 0); } - virtual void effectLevel(uint8_t value) { controlChange(MidiDriver::MIDI_CONTROLLER_REVERB, value); } - virtual void chorusLevel(uint8_t value) { controlChange(MidiDriver::MIDI_CONTROLLER_CHORUS, value); } - virtual void allNotesOff() { controlChange(MidiDriver::MIDI_CONTROLLER_ALL_NOTES_OFF, 0); } - - // SysEx messages - virtual void sysEx_customInstrument(uint32_t type, const uint8_t* instr) = 0; - }; -} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver.hpp index 74c17ca67..ae7c7222c 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver.hpp @@ -2,19 +2,18 @@ #include #include -#include +#include #include +#include namespace HyperSonicDrivers::drivers::midi::scummvm { - class MidiChannel; - /** * Abstract MIDI Driver Class * * @todo Rename MidiDriver to MusicDriver */ - class MidiDriver : public MidiDriver_BASE + class MidiDriver : public IMidiDriver { public: ~MidiDriver() override = default; @@ -102,23 +101,6 @@ namespace HyperSonicDrivers::drivers::midi::scummvm PROP_MILES_VERSION = 9 }; - /** - * Open the midi driver. - * @return 0 if successful, otherwise an error code. - */ - //virtual bool open( - // const audio::mixer::eChannelGroup group, - // const uint8_t volume, - // const uint8_t pan) = 0; - - /** - * Check whether the midi driver has already been opened. - */ - //virtual bool isOpen() const = 0; - - /** Close the midi driver. */ - //virtual void close() = 0; - /** Get or set a property. */ virtual uint32_t property(int prop, uint32_t param) = 0; @@ -126,13 +108,14 @@ namespace HyperSonicDrivers::drivers::midi::scummvm //static const char* getErrorName(int error_code); // HIGH-LEVEL SEMANTIC METHODS - virtual void setPitchBendRange(uint8_t channel, unsigned int range) { - send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8); - send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF); - send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_DATA_ENTRY_MSB, range); // Semi-tones - send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0); // Cents - send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL >> 8); - send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL & 0xFF); + virtual void setPitchBendRange(uint8_t channel, unsigned int range) + { + controller(channel, audio::midi::MIDI_EVENT_CONTROLLER_TYPES::RPN_MSB, static_cast(audio::midi::MIDI_RPN_TYPES_MSB::PITCH_BEND_SENSITIVITY)); + controller(channel, audio::midi::MIDI_EVENT_CONTROLLER_TYPES::RPN_LSB, static_cast(audio::midi::MIDI_RPN_TYPES_LSB::PITCH_BEND_SENSITIVITY)); + controller(channel, audio::midi::MIDI_EVENT_CONTROLLER_TYPES::DATA_ENTRY_MSB, range); // Semi-tones + controller(channel, audio::midi::MIDI_EVENT_CONTROLLER_TYPES::DATA_ENTRY_LSB, 0); // Cents + controller(channel, audio::midi::MIDI_EVENT_CONTROLLER_TYPES::RPN_MSB, static_cast(audio::midi::MIDI_RPN_TYPES_MSB::RPN_NULL)); + controller(channel, audio::midi::MIDI_EVENT_CONTROLLER_TYPES::RPN_LSB, static_cast(audio::midi::MIDI_RPN_TYPES_LSB::PITCH_BEND_SENSITIVITY)); } /** @@ -151,11 +134,7 @@ namespace HyperSonicDrivers::drivers::midi::scummvm //virtual void setTimerCallback(void* timer_param, Common::TimerManager::TimerProc timer_proc) = 0; /** The time in microseconds between invocations of the timer callback. */ - virtual uint32_t getBaseTempo() = 0; - - // Channel allocation functions - virtual MidiChannel* allocateChannel() = 0; - virtual MidiChannel* getPercussionChannel() = 0; + //virtual uint32_t getBaseTempo() = 0; // Allow an engine to supply its own soundFont data. This stream will be destroyed after use. //virtual void setEngineSoundFont(Common::SeekableReadStream* soundFontData) { } diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.cpp index 8147ca320..1af4a0c5c 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -13,19 +13,19 @@ namespace HyperSonicDrivers::drivers::midi::scummvm using utils::logD; using utils::logW; - static const uint8_t g_operator1Offsets[9] = { + constexpr const uint8_t g_operator1Offsets[9] = { 0, 1, 2, 8, 9, 10, 16, 17, 18 }; - static const uint8_t g_operator2Offsets[9] = { + constexpr const uint8_t g_operator2Offsets[9] = { 3, 4, 5, 11, 12, 13, 19, 20, 21 }; - static const AdLibSetParams g_setParamTable[] = { + constexpr const AdLibSetParams g_setParamTable[] = { // reg, offs, val1, val2 ? {0x40, 0, 63, 63}, // level {0xE0, 2, 0, 0}, // unused @@ -44,21 +44,21 @@ namespace HyperSonicDrivers::drivers::midi::scummvm {0xC0, 1, 14, 0} // feedback }; - static const uint8_t g_paramTable1[16] = { + constexpr const uint8_t g_paramTable1[16] = { 29, 28, 27, 0, 3, 4, 7, 8, 13, 16, 17, 20, 21, 30, 31, 0 }; - static const uint16_t g_maxValTable[16] = { + constexpr const uint16_t g_maxValTable[16] = { 0x2FF, 0x1F, 0x7, 0x3F, 0x0F, 0x0F, 0x0F, 0x3, 0x3F, 0x0F, 0x0F, 0x0F, 0x3, 0x3E, 0x1F, 0 }; - static const uint16_t g_numStepsTable[] = { + constexpr const uint16_t g_numStepsTable[] = { 1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, @@ -69,7 +69,7 @@ namespace HyperSonicDrivers::drivers::midi::scummvm 600, 860, 1200, 1600 }; - static const uint8_t g_noteFrequencies[] = { + constexpr const uint8_t g_noteFrequencies[] = { 90, 91, 92, 92, 93, 94, 94, 95, 96, 96, 97, 98, 98, 99, 100, 101, 101, 102, 103, 104, 104, 105, 106, 107, @@ -90,6 +90,16 @@ namespace HyperSonicDrivers::drivers::midi::scummvm 242, 243, 245, 247, 249, 251, 252, 254 }; + constexpr AdLibChannel* toAdlibPart(IMidiChannel* mc) + { + return dynamic_cast(mc); + } + + static AdLibChannel* toAdlibPart(const std::unique_ptr& ap) + { + return toAdlibPart(ap.get()); + } + MidiDriver_ADLIB::MidiDriver_ADLIB(const std::shared_ptr& opl) : m_opl([&opl]{ if (opl == nullptr) @@ -99,13 +109,19 @@ namespace HyperSonicDrivers::drivers::midi::scummvm }()), m_opl3Mode(m_opl->type != OplType::OPL2) { + using audio::midi::MIDI_PERCUSSION_CHANNEL; + using audio::midi::MIDI_MAX_CHANNELS; + std::ranges::fill(_curNotTable, 0); - for (size_t i = 0; i < _parts.size(); ++i) { - _parts[i].init(this, static_cast(i + ((i >= 9) ? 1 : 0))); - } + for (int i = 0; i < MIDI_PERCUSSION_CHANNEL; ++i) + m_channels[i] = std::make_unique(static_cast(i)); + + m_channels[MIDI_PERCUSSION_CHANNEL] = std::make_unique(); + for (int i = MIDI_PERCUSSION_CHANNEL + 1; i < MIDI_MAX_CHANNELS; i++) + m_channels[i] = std::make_unique(static_cast(i)); std::ranges::fill(_channelTable2, 0); - _percussion.init(this, 9); + m_percussion = dynamic_cast(m_channels[MIDI_PERCUSSION_CHANNEL].get()); } MidiDriver_ADLIB::~MidiDriver_ADLIB() @@ -124,10 +140,10 @@ namespace HyperSonicDrivers::drivers::midi::scummvm m_isOpen = true; - for (size_t i = 0; i != _voices.size(); i++) + for (size_t i = 0; i != m_voices.size(); i++) { - AdLibVoice* voice = &_voices[i]; - voice->_channel = static_cast(i); + AdLibVoice* voice = &m_voices[i]; + voice->slot = static_cast(i); voice->_s11a.s10 = &voice->_s10b; voice->_s11b.s10 = &voice->_s10a; } @@ -163,9 +179,9 @@ namespace HyperSonicDrivers::drivers::midi::scummvm // Stop the OPL timer m_opl->stop(); - for (auto& v : _voices) + for (auto& v : m_voices) { - if (v._part != nullptr) + if (v.getChannel() != nullptr) mcOff(&v); } @@ -173,74 +189,6 @@ namespace HyperSonicDrivers::drivers::midi::scummvm free(_regCacheSecondary); } - void MidiDriver_ADLIB::send(const audio::midi::MIDIEvent& e) /*const*/ noexcept - { - using audio::midi::TO_HIGH; - using audio::midi::MIDI_EVENT_TYPES_HIGH; - - switch (TO_HIGH(e.type.high)) - { - case MIDI_EVENT_TYPES_HIGH::META_SYSEX: - sysEx(e.data.data(), static_cast(e.data.size())); - break; - default: - send(e.toUint32()); - } - } - - void MidiDriver_ADLIB::send(uint32_t b) { - send(b & 0xF, b & 0xFFFFFFF0); - } - - void MidiDriver_ADLIB::send(int8_t chan, uint32_t b) { - using audio::midi::MIDI_EVENT_type_u; - using audio::midi::MIDI_EVENT_TYPES_HIGH; - - uint8_t param2 = (uint8_t)((b >> 16) & 0xFF); - uint8_t param1 = (uint8_t)((b >> 8) & 0xFF); - //uint8_t cmd = (uint8_t)(b & 0xF0); - MIDI_EVENT_type_u cmd; - cmd.val = static_cast(b & 0xFF); - - AdLibPart* part; - if (chan == 9) - part = &_percussion; - else - part = &_parts[chan]; - - switch (static_cast(cmd.high)) { - case MIDI_EVENT_TYPES_HIGH::NOTE_OFF:// Note Off - part->noteOff(param1); - logD(std::format("noteOff {} {}", chan, param1)); - break; - case MIDI_EVENT_TYPES_HIGH::NOTE_ON: // Note On - part->noteOn(param1, param2); - logD(std::format("noteOn {} {}", param1, param2)); - break; - case MIDI_EVENT_TYPES_HIGH::AFTERTOUCH: // Aftertouch - break; // Not supported. - case MIDI_EVENT_TYPES_HIGH::CONTROLLER: // Control Change - part->controlChange(param1, param2); - break; - case MIDI_EVENT_TYPES_HIGH::PROGRAM_CHANGE: // Program Change - part->programChange(param1); - break; - case MIDI_EVENT_TYPES_HIGH::CHANNEL_AFTERTOUCH: // Channel Pressure - break; // Not supported. - case MIDI_EVENT_TYPES_HIGH::PITCH_BEND: // Pitch Bend - part->pitchBend(static_cast((param1 | (param2 << 7)) - 0x2000)); - break; - case MIDI_EVENT_TYPES_HIGH::META_SYSEX: // SysEx - // We should never get here! SysEx information has to be - // sent via high-level semantic methods. - logW("Receiving SysEx command on a send() call"); - break; - - default: - logW(std::format("Unknown send() command {:#0x}", cmd.val)); - } - } - uint32_t MidiDriver_ADLIB::property(int prop, uint32_t param) { switch (prop) { @@ -267,42 +215,44 @@ namespace HyperSonicDrivers::drivers::midi::scummvm return 0; } - void MidiDriver_ADLIB::setPitchBendRange(uint8_t channel, unsigned int range) { + void MidiDriver_ADLIB::setPitchBendRange(uint8_t channel, unsigned int range) + { // Not supported in OPL3 mode. - if (m_opl3Mode) { + if (m_opl3Mode) return; - } - AdLibPart* part = &_parts[channel]; - part->_pitchBendFactor = range; - for (AdLibVoice* voice = part->_voice; voice; voice = voice->_next) + AdLibChannel* part = toAdlibPart(m_channels[channel]); + part->pitchBendFactor = range; + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) { - adlibNoteOn(voice->_channel, voice->_note/* + part->_transposeEff*/, - (part->_pitchBend * part->_pitchBendFactor >> 6) + part->_detuneEff); + adlibNoteOn(voice->slot, voice->getNote()/* + part->_transposeEff*/, + (part->pitch * part->pitchBendFactor >> 6) + part->detuneEff); } } void MidiDriver_ADLIB::sysEx_customInstrument(uint8_t channel, uint32_t type, const uint8_t* instr) { - _parts[channel].sysEx_customInstrument(type, instr); - } + if (m_opl3Mode) + { + logW("Used in OPL3 mode, not supported"); + return; + } - MidiChannel* MidiDriver_ADLIB::allocateChannel() - { - for(auto& part : _parts) + auto part = getChannel(channel); + + if (part->isPercussion) { - if (!part._allocated) - { - part.allocate(); - return ∂ + if (type == static_cast('ADLP')) + m_percussion->setCustomInstr(instr); + } + else + { + if (type == static_cast('ADL ')) { + part->setCustomInstr(instr); } } - - return nullptr; } - // All the code brought over from IMuseAdLib - void MidiDriver_ADLIB::adlibWrite(uint8_t reg, uint8_t value) { if (_regCache[reg] == value) @@ -312,15 +262,13 @@ namespace HyperSonicDrivers::drivers::midi::scummvm m_opl->writeReg(reg, value); } - void MidiDriver_ADLIB::adlibWriteSecondary(uint8_t reg, uint8_t value) { + void MidiDriver_ADLIB::adlibWriteSecondary(uint8_t reg, uint8_t value) + { assert(m_opl3Mode); if (_regCacheSecondary[reg] == value) { return; } -#ifdef DEBUG_ADLIB - debug(6, "%10d: adlibWriteSecondary[%x] = %x", g_tick, reg, value); -#endif _regCacheSecondary[reg] = value; m_opl->writeReg(reg | 0x100, value); @@ -335,19 +283,16 @@ namespace HyperSonicDrivers::drivers::midi::scummvm _timerCounter += _timerIncrease; while (_timerCounter >= _timerThreshold) { _timerCounter -= _timerThreshold; -#ifdef DEBUG_ADLIB - g_tick++; -#endif // Sam&Max's OPL3 driver does not have any timer handling like this. if (m_opl3Mode) continue; - for(auto& voice : _voices) + for(auto& voice : m_voices) { - if (voice._part == nullptr) + if (voice.isFree()) continue; - if (voice._duration && (voice._duration -= 0x11) <= 0) + if (voice.duration && (voice.duration -= 0x11) <= 0) { mcOff(&voice); return; @@ -365,6 +310,297 @@ namespace HyperSonicDrivers::drivers::midi::scummvm } } + void MidiDriver_ADLIB::noteOff(const uint8_t chan, const uint8_t note) noexcept + { + auto part = getChannel(chan); + uint8_t note_ = note; + if (part->isPercussion) + { + note_ = m_percussion->getNote(note_); + } + + partKeyOff(part, note_); + logD(std::format("noteOff {} {}", chan, note_)); + } + + void MidiDriver_ADLIB::noteOn(const uint8_t chan, const uint8_t note, const uint8_t vol) noexcept + { + auto part = getChannel(chan); + + if (part->isPercussion) + { + const AdLibInstrument* inst = nullptr; + const AdLibInstrument* sec = nullptr; + + // The custom instruments have priority over the default mapping + // We do not support custom instruments in OPL3 mode though. + if (!m_opl3Mode) + { + inst = m_percussion->getCustomInstrument(note); + } + + if (inst == nullptr) + { + // Use the default GM to FM mapping as a fallback + const uint8_t key = g_gmPercussionInstrumentMap[note]; + if (key != 0xFF) + { + if (!m_opl3Mode) + { + inst = &g_gmPercussionInstruments[key]; + } + else + { + inst = &g_gmPercussionInstrumentsOPL3[key][0]; + sec = &g_gmPercussionInstrumentsOPL3[key][1]; + } + } + } + + if (!inst) + { + logD(std::format("No instrument FM definition for GM percussion key {:d}", note)); + return; + } + + partKeyOn(part, inst, note, vol, sec, part->pan); + } + else + { + partKeyOn(part, part->getInstr(), note, vol, + part->getInstrSecondary(), + part->pan); + } + + logD(std::format("noteOn {} {}", note, vol)); + } + + void MidiDriver_ADLIB::controller(const uint8_t chan, const audio::midi::MIDI_EVENT_CONTROLLER_TYPES ctrl_type, uint8_t value) noexcept + { + // TODO: mostly duplicate code, it should bring into the parent class i guess... + switch (ctrl_type) + { + using enum audio::midi::MIDI_EVENT_CONTROLLER_TYPES; + case BANK_SELECT_MSB: + case BANK_SELECT_LSB: + // Bank select. Not supported + break; + case MODULATION_WHEEL: + ctrl_modulationWheel(chan, value); + break; + case CHANNEL_VOLUME: + ctrl_volume(chan, value); + break; + case PAN: + ctrl_panPosition(chan, value); + break; + case GENERAL_PURPOSE_CONTROLLER_1: + ctrl_pitchBendFactor(chan, value); + break; + case GENERAL_PURPOSE_CONTROLLER_2: + ctrl_detune(chan, value); + break; + case GENERAL_PURPOSE_CONTROLLER_3: + ctrl_priority(chan, value); + break; + case SUSTAIN: + ctrl_sustain(chan, value); + break; + case REVERB: + // Effects level. Not supported. + ctrl_reverb(chan, value); + break; + case CHORUS: + // Chorus level. Not supported. + ctrl_chorus(chan, value); + break; + case RESET_ALL_CONTROLLERS: + // reset all controllers + ctrl_modulationWheel(chan, 0); + ctrl_pitchBendFactor(chan, 0); + ctrl_detune(chan, 0); + ctrl_sustain(chan, 0); + break; + case ALL_NOTES_OFF: + ctrl_allNotesOff(); + break; + default: + logW(std::format("Unknown control change message {:d} {:d}", static_cast(ctrl_type), value)); + } + } + + void MidiDriver_ADLIB::programChange(const uint8_t chan, const uint8_t program) noexcept + { + if (program > 127) + return; + + auto part = getChannel(chan); + part->program = program; + if (!part->isPercussion) + part->setInstr(m_opl3Mode); + else + int i = 0; + } + + void MidiDriver_ADLIB::pitchBend(const uint8_t chan, const uint16_t bend) noexcept + { + auto part = getChannel(chan); + + part->pitch = bend; + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) + { + if (!m_opl3Mode) + { + adlibNoteOn(voice->slot, voice->getNote()/* + _transposeEff*/, + (part->pitch * part->pitchBendFactor >> 6) + part->detuneEff); + } + else + { + adlibNoteOn(voice->slot, voice->getNote(), part->pitch >> 1); + } + } + } + + void MidiDriver_ADLIB::sysEx(const uint8_t* msg, uint16_t length) noexcept + { + } + + void MidiDriver_ADLIB::ctrl_modulationWheel(const uint8_t chan, const uint8_t value) noexcept + { + auto part = getChannel(chan); + + if (part->isPercussion) + return; + + part->modulation = value; + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) + { + if (voice->_s10a.active && voice->_s11a.flag0x40) + voice->_s10a.modWheel = part->modulation >> 2; + if (voice->_s10b.active && voice->_s11b.flag0x40) + voice->_s10b.modWheel = part->modulation >> 2; + } + } + + void MidiDriver_ADLIB::ctrl_volume(const uint8_t chan, const uint8_t value) noexcept + { + auto part = getChannel(chan); + + part->volume = value; + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) + { + if (!m_opl3Mode) + { + adlibSetParam(voice->slot, 0, g_volumeTable[g_volumeLookupTable[voice->vol2][part->volume >> 2]]); + if (voice->twoChan) { + adlibSetParam(voice->slot, 13, g_volumeTable[g_volumeLookupTable[voice->vol1][part->volume >> 2]]); + } + } + else + { + adlibSetParam(voice->slot, 0, g_volumeTable[((voice->vol2 + 1) * part->volume) >> 7], true); + adlibSetParam(voice->slot, 0, g_volumeTable[((voice->secVol2 + 1) * part->volume) >> 7], false); + if (voice->twoChan) { + adlibSetParam(voice->slot, 13, g_volumeTable[((voice->vol1 + 1) * part->volume) >> 7], true); + } + if (voice->secTwoChan) { + adlibSetParam(voice->slot, 13, g_volumeTable[((voice->secVol1 + 1) * part->volume) >> 7], false); + } + } + } + } + + void MidiDriver_ADLIB::ctrl_panPosition(const uint8_t chan, uint8_t value) noexcept + { + auto part = getChannel(chan); + part->pan = value; + } + + void MidiDriver_ADLIB::ctrl_pitchBendFactor(const uint8_t chan, const uint8_t value) noexcept + { + auto part = getChannel(chan); + + if (part->isPercussion) + return; + + if (m_opl3Mode) + return; + + part->pitchBendFactor = value; + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) + { + adlibNoteOn(voice->slot, voice->getNote()/* + _transposeEff*/, + (part->pitch * part->pitchBendFactor >> 6) + part->detuneEff); + } + } + + void MidiDriver_ADLIB::ctrl_detune(const uint8_t chan, const uint8_t value) noexcept + { + auto part = getChannel(chan); + + if (part->isPercussion) + return; + + // Sam&Max's OPL3 driver uses this for a completly different purpose. It + // is related to voice allocation. We ignore this for now. + // TODO: We probably need to look how the interpreter side of Sam&Max's + // iMuse version handles all this too. Implementing the driver side here + // would be not that hard. + if (m_opl3Mode) { + //_maxNotes = value; + return; + } + + part->detuneEff = value; + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) + { + adlibNoteOn(voice->slot, voice->getNote()/* + _transposeEff*/, + (part->pitch * part->pitchBendFactor >> 6) + part->detuneEff); + } + } + + void MidiDriver_ADLIB::ctrl_priority(const uint8_t chan, const uint8_t value) noexcept + { + auto part = getChannel(chan); + if (part->isPercussion) + return; + + part->priority = value; + } + + void MidiDriver_ADLIB::ctrl_sustain(const uint8_t chan, uint8_t value) noexcept + { + auto part = getChannel(chan); + + if (part->isPercussion) + return; + + part->sustain = value; + if (value != 0) { + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) + { + if (voice->isSustain()) + mcOff(voice); + } + } + } + + void MidiDriver_ADLIB::ctrl_reverb(const uint8_t chan, uint8_t value) noexcept + { + // Not Supported + } + + void MidiDriver_ADLIB::ctrl_chorus(const uint8_t chan, uint8_t value) noexcept + { + // Not Supported + } + + void MidiDriver_ADLIB::ctrl_allNotesOff() noexcept + { + for (AdLibVoice v : m_voices) + mcOff(&v); + } + //void MidiDriver_ADLIB::setTimerCallback(void* timerParam, /*Common::TimerManager::TimerProc*/ void* timerProc) { // _adlibTimerProc = timerProc; // _adlibTimerParam = timerParam; @@ -374,49 +610,48 @@ namespace HyperSonicDrivers::drivers::midi::scummvm { AdLibVoice* tmp; - adlibKeyOff(voice->_channel); + adlibKeyOff(voice->slot); - tmp = voice->_prev; + tmp = voice->prev; - if (voice->_next) - voice->_next->_prev = tmp; + if (voice->next) + voice->next->prev = tmp; if (tmp) - tmp->_next = voice->_next; + tmp->next = voice->next; else - voice->_part->_voice = voice->_next; + toAdlibPart(voice->getChannel())->voice = voice->next; - voice->_part = nullptr; + voice->setChannel(nullptr); + voice->setFree(true); } void MidiDriver_ADLIB::mcIncStuff(AdLibVoice* voice, Struct10* s10, Struct11* s11) { - uint8_t code; - AdLibPart* part = voice->_part; - - code = struct10OnTimer(s10, s11); + const AdLibChannel* part = toAdlibPart(voice->getChannel()); + const uint8_t code = struct10OnTimer(s10, s11); if (code & 1) { switch (s11->param) { case 0: - voice->_vol2 = s10->startValue + s11->modifyVal; + voice->vol2 = s10->startValue + s11->modifyVal; if (!_scummSmallHeader) { - adlibSetParam(voice->_channel, 0, - g_volumeTable[g_volumeLookupTable[voice->_vol2] - [part->_volEff >> 2]]); + adlibSetParam(voice->slot, 0, + g_volumeTable[g_volumeLookupTable[voice->vol2] + [part->volume >> 2]]); } else { - adlibSetParam(voice->_channel, 0, voice->_vol2); + adlibSetParam(voice->slot, 0, voice->vol2); } break; case 13: - voice->_vol1 = s10->startValue + s11->modifyVal; - if (voice->_twoChan && !_scummSmallHeader) { - adlibSetParam(voice->_channel, 13, - g_volumeTable[g_volumeLookupTable[voice->_vol1] - [part->_volEff >> 2]]); + voice->vol1 = s10->startValue + s11->modifyVal; + if (voice->twoChan && !_scummSmallHeader) { + adlibSetParam(voice->slot, 13, + g_volumeTable[g_volumeLookupTable[voice->vol1] + [part->volume >> 2]]); } else { - adlibSetParam(voice->_channel, 13, voice->_vol1); + adlibSetParam(voice->slot, 13, voice->vol1); } break; case 30: @@ -426,17 +661,18 @@ namespace HyperSonicDrivers::drivers::midi::scummvm s11->s10->unk3 = (char)s11->modifyVal; break; default: - adlibSetParam(voice->_channel, s11->param, + adlibSetParam(voice->slot, s11->param, s10->startValue + s11->modifyVal); break; } } if (code & 2 && s11->flag0x10) - adlibKeyOnOff(voice->_channel); + adlibKeyOnOff(voice->slot); } - void MidiDriver_ADLIB::adlibKeyOff(int chan) { + void MidiDriver_ADLIB::adlibKeyOff(int chan) + { uint8_t reg = chan + 0xB0; adlibWrite(reg, adlibGetRegValue(reg) & ~0x20); if (m_opl3Mode) { @@ -444,7 +680,8 @@ namespace HyperSonicDrivers::drivers::midi::scummvm } } - uint8_t MidiDriver_ADLIB::struct10OnTimer(Struct10* s10, Struct11* s11) { + uint8_t MidiDriver_ADLIB::struct10OnTimer(Struct10* s10, Struct11* s11) + { uint8_t result = 0; int i; @@ -489,7 +726,8 @@ namespace HyperSonicDrivers::drivers::midi::scummvm return result; } - void MidiDriver_ADLIB::adlibSetParam(int channel, uint8_t param, int value, bool primary) { + void MidiDriver_ADLIB::adlibSetParam(int channel, uint8_t param, int value, bool primary) + { const AdLibSetParams* as; uint8_t reg; @@ -647,24 +885,29 @@ namespace HyperSonicDrivers::drivers::midi::scummvm return _randSeed * a >> 8; } - void MidiDriver_ADLIB::partKeyOff(AdLibPart* part, uint8_t note) + void MidiDriver_ADLIB::partKeyOff(AdLibChannel* part, uint8_t note) { - for (AdLibVoice* voice = part->_voice; voice; voice = voice->_next) + for (AdLibVoice* voice = part->voice; voice; voice = voice->next) { - if (voice->_note == note) + if (voice->getNote() == note) { - if (part->_pedal) - voice->_waitForPedal = true; + if (part->sustain) + voice->setWaitForPedal(true); else mcOff(voice); } } } - void MidiDriver_ADLIB::partKeyOn(AdLibPart* part, const AdLibInstrument* instr, uint8_t note, uint8_t velocity, const AdLibInstrument* second, uint8_t pan) { + AdLibChannel* MidiDriver_ADLIB::getChannel(const uint8_t channel) const noexcept + { + return toAdlibPart(m_channels[channel]); + } + + void MidiDriver_ADLIB::partKeyOn(AdLibChannel* part, const AdLibInstrument* instr, uint8_t note, uint8_t velocity, const AdLibInstrument* second, uint8_t pan) { AdLibVoice* voice; - voice = allocateVoice(part->_priEff); + voice = allocateVoice(part->priority); if (!voice) return; @@ -681,12 +924,17 @@ namespace HyperSonicDrivers::drivers::midi::scummvm { if (++_voiceIndex >= 9) _voiceIndex = 0; - ac = &_voices[_voiceIndex]; - if (!ac->_part) + + ac = &m_voices[_voiceIndex]; + if (ac->getChannel() == nullptr) return ac; - if (!ac->_next) { - if (ac->_part->_priEff <= pri) { - pri = ac->_part->_priEff; + + if (ac->next == nullptr) + { + const auto priEff_ = toAdlibPart(ac->getChannel())->priority; + if (priEff_ <= pri) + { + pri = priEff_; best = ac; } } @@ -701,28 +949,30 @@ namespace HyperSonicDrivers::drivers::midi::scummvm return best; } - void MidiDriver_ADLIB::linkMc(AdLibPart* part, AdLibVoice* voice) + void MidiDriver_ADLIB::linkMc(AdLibChannel* part, AdLibVoice* voice) { - voice->_part = part; - voice->_next = part->_voice; - part->_voice = voice; - voice->_prev = nullptr; - - if (voice->_next) - voice->_next->_prev = voice; + voice->setFree(false); + voice->setChannel(part); + voice->next = part->voice; + part->voice = voice; + voice->prev = nullptr; + + if (voice->next != nullptr) + voice->next->prev = voice; } - void MidiDriver_ADLIB::mcKeyOn(AdLibVoice* voice, const AdLibInstrument* instr, uint8_t note, uint8_t velocity, const AdLibInstrument* second, uint8_t pan) { - AdLibPart* part = voice->_part; + void MidiDriver_ADLIB::mcKeyOn(AdLibVoice* voice, const AdLibInstrument* instr, uint8_t note, uint8_t velocity, const AdLibInstrument* second, uint8_t pan) + { + const AdLibChannel* part = toAdlibPart(voice->getChannel()); uint8_t vol1, vol2; uint8_t secVol1 = 0, secVol2 = 0; - voice->_twoChan = instr->feedback & 1; - voice->_note = note; - voice->_waitForPedal = false; - voice->_duration = instr->duration; - if (voice->_duration != 0) - voice->_duration *= 63; + voice->twoChan = instr->feedback & 1; + voice->setNote(note); + voice->setWaitForPedal(false); + voice->duration = instr->duration; + if (voice->duration != 0) + voice->duration *= 63; if (!_scummSmallHeader) { @@ -739,7 +989,7 @@ namespace HyperSonicDrivers::drivers::midi::scummvm if (vol1 > 0x3F) vol1 = 0x3F; - voice->_vol1 = vol1; + voice->vol1 = vol1; if (!_scummSmallHeader) { @@ -754,45 +1004,45 @@ namespace HyperSonicDrivers::drivers::midi::scummvm if (vol2 > 0x3F) vol2 = 0x3F; - voice->_vol2 = vol2; + voice->vol2 = vol2; if (m_opl3Mode) { - voice->_secTwoChan = second->feedback & 1; + voice->secTwoChan = second->feedback & 1; secVol1 = (second->modScalingOutputLevel & 0x3F) + (velocity * ((second->modWaveformSelect >> 3) + 1)) / 64; if (secVol1 > 0x3F) { secVol1 = 0x3F; } - voice->_secVol1 = secVol1; + voice->secVol1 = secVol1; secVol2 = (second->carScalingOutputLevel & 0x3F) + (velocity * ((second->carWaveformSelect >> 3) + 1)) / 64; if (secVol2 > 0x3F) { secVol2 = 0x3F; } - voice->_secVol2 = secVol2; + voice->secVol2 = secVol2; } if (!_scummSmallHeader) { if (!m_opl3Mode) { - int c = part->_volEff >> 2; + int c = part->volume >> 2; vol2 = g_volumeTable[g_volumeLookupTable[vol2][c]]; - if (voice->_twoChan) + if (voice->twoChan) vol1 = g_volumeTable[g_volumeLookupTable[vol1][c]]; } else { - vol2 = g_volumeTable[((vol2 + 1) * part->_volEff) >> 7]; - secVol2 = g_volumeTable[((secVol2 + 1) * part->_volEff) >> 7]; - if (voice->_twoChan) - vol1 = g_volumeTable[((vol1 + 1) * part->_volEff) >> 7]; - if (voice->_secTwoChan) - secVol1 = g_volumeTable[((secVol1 + 1) * part->_volEff) >> 7]; + vol2 = g_volumeTable[((vol2 + 1) * part->volume) >> 7]; + secVol2 = g_volumeTable[((secVol2 + 1) * part->volume) >> 7]; + if (voice->twoChan) + vol1 = g_volumeTable[((vol1 + 1) * part->volume) >> 7]; + if (voice->secTwoChan) + secVol1 = g_volumeTable[((secVol1 + 1) * part->volume) >> 7]; } } - adlibSetupChannel(voice->_channel, instr, vol1, vol2); + adlibSetupChannel(voice->slot, instr, vol1, vol2); if (!m_opl3Mode) { - adlibNoteOnEx(voice->_channel, /*part->_transposeEff + */note, part->_detuneEff + (part->_pitchBend * part->_pitchBendFactor >> 6)); + adlibNoteOnEx(voice->slot, /*part->_transposeEff + */note, part->detuneEff + (part->pitch * part->pitchBendFactor >> 6)); if (instr->flagsA & 0x80) { mcInitStuff(voice, &voice->_s10a, &voice->_s11a, instr->flagsA, &instr->extraA); @@ -809,8 +1059,8 @@ namespace HyperSonicDrivers::drivers::midi::scummvm } } else { - adlibSetupChannelSecondary(voice->_channel, second, secVol1, secVol2, pan); - adlibNoteOnEx(voice->_channel, note, part->_pitchBend >> 1); + adlibSetupChannelSecondary(voice->slot, second, secVol1, secVol2, pan); + adlibNoteOnEx(voice->slot, note, part->pitch >> 1); } } @@ -878,8 +1128,9 @@ namespace HyperSonicDrivers::drivers::midi::scummvm } void MidiDriver_ADLIB::mcInitStuff(AdLibVoice* voice, Struct10* s10, - Struct11* s11, uint8_t flags, const InstrumentExtra* ie) { - AdLibPart* part = voice->_part; + Struct11* s11, uint8_t flags, const InstrumentExtra* ie) + { + const AdLibChannel* part = toAdlibPart(voice->getChannel()); s11->modifyVal = 0; s11->flag0x40 = flags & 0x40; s10->loop = flags & 0x20; @@ -888,7 +1139,7 @@ namespace HyperSonicDrivers::drivers::midi::scummvm s10->maxValue = g_maxValTable[flags & 0xF]; s10->unk3 = 31; if (s11->flag0x40) { - s10->modWheel = part->_modWheel >> 2; + s10->modWheel = part->modulation >> 2; } else { s10->modWheel = 31; @@ -896,10 +1147,10 @@ namespace HyperSonicDrivers::drivers::midi::scummvm switch (s11->param) { case 0: - s10->startValue = voice->_vol2; + s10->startValue = voice->vol2; break; case 13: - s10->startValue = voice->_vol1; + s10->startValue = voice->vol1; break; case 30: s10->startValue = 31; @@ -910,7 +1161,7 @@ namespace HyperSonicDrivers::drivers::midi::scummvm s11->s10->unk3 = 0; break; default: - s10->startValue = adlibGetRegValueParam(voice->_channel, s11->param); + s10->startValue = adlibGetRegValueParam(voice->slot, s11->param); } struct10Init(s10, ie); @@ -980,7 +1231,8 @@ namespace HyperSonicDrivers::drivers::midi::scummvm return val; } - void MidiDriver_ADLIB::adlibNoteOn(int chan, uint8_t note, int mod) { + void MidiDriver_ADLIB::adlibNoteOn(int chan, uint8_t note, int mod) + { if (m_opl3Mode) { adlibNoteOnEx(chan, note, mod); return; diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.hpp index 93da90713..042df939b 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_ADLIB.hpp @@ -5,10 +5,9 @@ #include #include #include -#include #include #include -#include +#include #include @@ -23,9 +22,6 @@ namespace HyperSonicDrivers::drivers::midi::scummvm class MidiDriver_ADLIB : public MidiDriver { - friend class AdLibPart; - friend class AdLibPercussionChannel; - public: explicit MidiDriver_ADLIB(const std::shared_ptr& opl); ~MidiDriver_ADLIB() override; @@ -35,25 +31,42 @@ namespace HyperSonicDrivers::drivers::midi::scummvm const uint8_t pan) override; void close() override; - void send(const audio::midi::MIDIEvent& e) /*const*/ noexcept override; - void send(uint32_t b) override; - void send(int8_t channel, uint32_t b) override; // Supports higher than channel 15 - void pause() const noexcept override { /*TODO*/ }; void resume() const noexcept override {/*TODO*/ }; uint32_t property(int prop, uint32_t param) override; - //bool isOpen() const override { return _isOpen; } - uint32_t getBaseTempo() override { return 1000000 / hardware::opl::default_opl_callback_freq; } + //uint32_t getBaseTempo() override { return 1000000 / hardware::opl::default_opl_callback_freq; } void setPitchBendRange(uint8_t channel, unsigned int range) override; void sysEx_customInstrument(uint8_t channel, uint32_t type, const uint8_t* instr) override; - MidiChannel* allocateChannel() override; - MidiChannel* getPercussionChannel() override { return &_percussion; } // Percussion partially supported - //virtual void setTimerCallback(void* timerParam, /*Common::TimerManager::TimerProc*/ void* timerProc); + protected: + void onCallback() noexcept override; + + // MIDI Events + void noteOff(const uint8_t chan, const uint8_t note) noexcept override; + void noteOn(const uint8_t chan, const uint8_t note, const uint8_t vol) noexcept override; + void controller(const uint8_t chan, const audio::midi::MIDI_EVENT_CONTROLLER_TYPES ctrl_type, uint8_t value) noexcept override; + void programChange(const uint8_t chan, const uint8_t program) noexcept override; + void pitchBend(const uint8_t chan, const uint16_t bend) noexcept override; + void sysEx(const uint8_t* msg, uint16_t length) noexcept override; + + // MIDI Controller Events + void ctrl_modulationWheel(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_volume(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_panPosition(const uint8_t chan, const uint8_t value) noexcept override; + // SCUMM GM Midi driver ctrl exclusive? + void ctrl_pitchBendFactor(const uint8_t chan, const uint8_t value) noexcept; + void ctrl_detune(const uint8_t chan, const uint8_t value) noexcept; + void ctrl_priority(const uint8_t chan, const uint8_t value) noexcept; + // --- + void ctrl_sustain(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_reverb(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_chorus(const uint8_t chan, const uint8_t value) noexcept override; + void ctrl_allNotesOff() noexcept override; + private: std::shared_ptr m_opl; bool _scummSmallHeader = false; // FIXME: This flag controls a special mode for SCUMM V3 games @@ -69,19 +82,16 @@ namespace HyperSonicDrivers::drivers::midi::scummvm std::array _channelTable2; std::array _curNotTable; - std::array _parts; - std::array _voices; - AdLibPercussionChannel _percussion; + std::array m_voices; + AdLibPercussionChannel* m_percussion; int _voiceIndex = -1; int _timerIncrease = 0xD69; int _timerThreshold = 0x411B; - //bool _isOpen = false; + AdLibChannel* getChannel(const uint8_t channel) const noexcept; - //void onTimer(); - void onCallback() noexcept override; - void partKeyOn(AdLibPart* part, const AdLibInstrument* instr, uint8_t note, uint8_t velocity, const AdLibInstrument* second, uint8_t pan); - void partKeyOff(AdLibPart* part, uint8_t note); + void partKeyOn(AdLibChannel* part, const AdLibInstrument* instr, uint8_t note, uint8_t velocity, const AdLibInstrument* second, uint8_t pan); + void partKeyOff(AdLibChannel* part, uint8_t note); void adlibKeyOff(int chan); void adlibNoteOn(int chan, uint8_t note, int mod); @@ -101,7 +111,7 @@ namespace HyperSonicDrivers::drivers::midi::scummvm void mcOff(AdLibVoice* voice); - static void linkMc(AdLibPart* part, AdLibVoice* voice); + static void linkMc(AdLibChannel* part, AdLibVoice* voice); void mcIncStuff(AdLibVoice* voice, Struct10* s10, Struct11* s11); void mcInitStuff(AdLibVoice* voice, Struct10* s10, Struct11* s11, uint8_t flags, const InstrumentExtra* ie); diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.cpp deleted file mode 100644 index 1eb0777ac..000000000 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include - -namespace HyperSonicDrivers::drivers::midi::scummvm -{ - void MidiDriver_BASE::send(uint8_t status, uint8_t firstOp, uint8_t secondOp) { - send(status | ((uint32_t)firstOp << 8) | ((uint32_t)secondOp << 16)); - } - - void MidiDriver_BASE::send(int8_t source, uint8_t status, uint8_t firstOp, uint8_t secondOp) { - send(source, status | ((uint32_t)firstOp << 8) | ((uint32_t)secondOp << 16)); - } - - void MidiDriver_BASE::stopAllNotes(bool stopSustainedNotes) { - for (int i = 0; i < 16; ++i) { - send(0xB0 | i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0); - if (stopSustainedNotes) - send(0xB0 | i, MIDI_CONTROLLER_SUSTAIN, 0); // Also send a sustain off event (bug #5524) - } - } -} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.hpp deleted file mode 100644 index b0a2ab702..000000000 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/midi/scummvm/MidiDriver_BASE.hpp +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include -#include - -namespace HyperSonicDrivers::drivers::midi::scummvm -{ - /** - * TODO: Document this, give it a better name. - */ - class MidiDriver_BASE : public IMidiDriver - { - public: - static const uint8_t MIDI_CHANNEL_COUNT = 16; - static const uint8_t MIDI_RHYTHM_CHANNEL = 9; - - static const uint8_t MIDI_COMMAND_NOTE_OFF = 0x80; - static const uint8_t MIDI_COMMAND_NOTE_ON = 0x90; - static const uint8_t MIDI_COMMAND_POLYPHONIC_AFTERTOUCH = 0xA0; - static const uint8_t MIDI_COMMAND_CONTROL_CHANGE = 0xB0; - static const uint8_t MIDI_COMMAND_PROGRAM_CHANGE = 0xC0; - static const uint8_t MIDI_COMMAND_CHANNEL_AFTERTOUCH = 0xD0; - static const uint8_t MIDI_COMMAND_PITCH_BEND = 0xE0; - static const uint8_t MIDI_COMMAND_SYSTEM = 0xF0; - - static const uint8_t MIDI_CONTROLLER_BANK_SELECT_MSB = 0x00; - static const uint8_t MIDI_CONTROLLER_MODULATION = 0x01; - static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_MSB = 0x06; - static const uint8_t MIDI_CONTROLLER_VOLUME = 0x07; - static const uint8_t MIDI_CONTROLLER_PANNING = 0x0A; - static const uint8_t MIDI_CONTROLLER_EXPRESSION = 0x0B; - static const uint8_t MIDI_CONTROLLER_BANK_SELECT_LSB = 0x20; - static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_LSB = 0x26; - static const uint8_t MIDI_CONTROLLER_SUSTAIN = 0x40; - static const uint8_t MIDI_CONTROLLER_REVERB = 0x5B; - static const uint8_t MIDI_CONTROLLER_CHORUS = 0x5D; - static const uint8_t MIDI_CONTROLLER_RPN_LSB = 0x64; - static const uint8_t MIDI_CONTROLLER_RPN_MSB = 0x65; - static const uint8_t MIDI_CONTROLLER_ALL_SOUND_OFF = 0x78; - static const uint8_t MIDI_CONTROLLER_RESET_ALL_CONTROLLERS = 0x79; - static const uint8_t MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B; - static const uint8_t MIDI_CONTROLLER_OMNI_ON = 0x7C; - static const uint8_t MIDI_CONTROLLER_OMNI_OFF = 0x7D; - static const uint8_t MIDI_CONTROLLER_MONO_ON = 0x7E; - static const uint8_t MIDI_CONTROLLER_POLY_ON = 0x7F; - - static const uint16_t MIDI_RPN_PITCH_BEND_SENSITIVITY = 0x0000; - static const uint16_t MIDI_RPN_MASTER_TUNING_FINE = 0x0001; - static const uint16_t MIDI_RPN_MASTER_TUNING_COARSE = 0x0002; - static const uint16_t MIDI_RPN_NULL = 0x7F7F; - - static const uint8_t MIDI_META_END_OF_TRACK = 0x2F; - static const uint8_t MIDI_META_SEQUENCER = 0x7F; - - static const uint16_t MIDI_PITCH_BEND_DEFAULT = 0x2000; - static const uint8_t MIDI_PANNING_DEFAULT = 0x40; - static const uint8_t MIDI_EXPRESSION_DEFAULT = 0x7F; - static const uint16_t MIDI_MASTER_TUNING_FINE_DEFAULT = 0x2000; - static const uint8_t MIDI_MASTER_TUNING_COARSE_DEFAULT = 0x40; - - static const uint8_t GM_PITCH_BEND_SENSITIVITY_DEFAULT = 0x02; - - static const uint8_t GS_RHYTHM_FIRST_NOTE = 0x1B; - static const uint8_t GS_RHYTHM_LAST_NOTE = 0x58; - - MidiDriver_BASE() = default; - virtual ~MidiDriver_BASE() = default; - - using IMidiDriver::send; - - /** - * Output a packed midi command to the midi stream. - * The 'lowest' uint8_t (i.e. b & 0xFF) is the status - * code, then come (if used) the first and second - * opcode. - */ - //virtual void send(uint32_t b) = 0; - - /** - * Send a MIDI command from a specific source. If the MIDI driver - * does not support multiple sources, the source parameter is - * ignored. - */ - //virtual void send(int8_t channel, uint32_t b) { send(b); } - - /** - * Output a midi command to the midi stream. Convenience wrapper - * around the usual 'packed' send method. - * - * Do NOT use this for sysEx transmission; instead, use the sysEx() - * method below. - */ - void send(uint8_t status, uint8_t firstOp, uint8_t secondOp); - - /** - * Send a MIDI command from a specific source. If the MIDI driver - * does not support multiple sources, the source parameter is - * ignored. - */ - void send(int8_t source, uint8_t status, uint8_t firstOp, uint8_t secondOp); - - /** - * Transmit a SysEx to the MIDI device. - * - * The given msg MUST NOT contain the usual SysEx frame, i.e. - * do NOT include the leading 0xF0 and the trailing 0xF7. - * - * Furthermore, the maximal supported length of a SysEx - * is 264 bytes. Passing longer buffers can lead to - * undefined behavior (most likely, a crash). - */ - virtual void sysEx(const uint8_t* msg, uint16_t length) { } - - /** - * Transmit a SysEx to the MIDI device and return the necessary - * delay until the next SysEx event in milliseconds. - * - * This can be used to implement an alternate delay method than the - * OSystem::delayMillis function used by most sysEx implementations. - * Note that not every driver needs a delay, or supports this method. - * In this case, 0 is returned and the driver itself will do a delay - * if necessary. - * - * For information on the SysEx data requirements, see the sysEx method. - */ - virtual uint16_t sysExNoDelay(const uint8_t* msg, uint16_t length) { sysEx(msg, length); return 0; } - - // TODO: Document this. - virtual void metaEvent(uint8_t type, uint8_t* data, uint16_t length) { } - - /** - * Send a meta event from a specific source. If the MIDI driver - * does not support multiple sources, the source parameter is - * ignored. - */ - virtual void metaEvent(int8_t source, uint8_t type, uint8_t* data, uint16_t length) { metaEvent(type, data, length); } - - /** - * Stops all currently active notes. Specify stopSustainedNotes if - * the MIDI data makes use of the sustain controller to make sure - * sustained notes are also stopped. - * - * Usually, the MIDI parser tracks active notes and terminates them - * when playback is stopped. This method should be used as a backup - * to silence the MIDI output in case the MIDI parser makes a - * mistake when tracking acive notes. It can also be used when - * quitting or pausing a game. - * - * By default, this method sends an All Notes Off message and, if - * stopSustainedNotes is true, a Sustain off message on all MIDI - * channels. Driver implementations can override this if they want - * to implement this functionality in a different way. - */ - virtual void stopAllNotes(bool stopSustainedNotes = false); - - /** - * A driver implementation might need time to prepare playback of - * a track. Use this function to check if the driver is ready to - * receive MIDI events. - */ - virtual bool isReady() { return true; } - }; -} diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.cpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.cpp index 892d20759..3c6cbf761 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.cpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.cpp @@ -101,7 +101,7 @@ namespace HyperSonicDrivers::drivers::opl instr->trem_vibr_2 | state); } - void OplWriter::writePan(const uint8_t channel, const hardware::opl::OPL2instrument_t* instr, const int8_t pan) const noexcept + void OplWriter::writePan(const uint8_t channel, const hardware::opl::OPL2instrument_t* instr, const uint8_t pan) const noexcept { // OPL2 is mono // TODO: what about DUAL_OPL2 ? (use an OPL3 emulator :) @@ -109,8 +109,8 @@ namespace HyperSonicDrivers::drivers::opl return; uint8_t bits; - if (pan < -36) bits = 0x10; // left - else if (pan > 36) bits = 0x20; // right + if (pan < 64) bits = 0x10; // left + else if (pan > 64) bits = 0x20; // right else bits = 0x30; // both writeValue(0xC0, channel, instr->feedback | bits); diff --git a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.hpp b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.hpp index 79951dea9..75d31d885 100644 --- a/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.hpp +++ b/sdl2-hyper-sonic-drivers/src/HyperSonicDrivers/drivers/opl/OplWriter.hpp @@ -61,7 +61,7 @@ namespace HyperSonicDrivers::drivers::opl /* * Write pan (balance) data to a channel */ - void writePan(const uint8_t channel, const hardware::opl::OPL2instrument_t* instr, const int8_t pan) const noexcept; + void writePan(const uint8_t channel, const hardware::opl::OPL2instrument_t* instr, const uint8_t pan) const noexcept; /* * Write volume data to a channel diff --git a/sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/drivers/midi/opl/TestOplVoice.cpp b/sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/drivers/midi/opl/TestOplVoice.cpp index 43906e82f..6cfa27af2 100644 --- a/sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/drivers/midi/opl/TestOplVoice.cpp +++ b/sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/drivers/midi/opl/TestOplVoice.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace HyperSonicDrivers::drivers::midi::opl { @@ -28,13 +29,17 @@ namespace HyperSonicDrivers::drivers::midi::opl delete instr; } - void setChannel(const uint8_t ch) { - OplVoice::setChannel(ch); - } + inline void setChannel(IMidiChannel* channel) noexcept { m_channel = channel; } + inline IMidiChannel* getChannel() const noexcept { return m_channel; }; void setFree(const bool f) { - OplVoice::setFree(f); + m_free = f; } + + inline uint8_t getVolume() const { return m_volume; }; + inline uint8_t getPan() const { return m_channel->pan; }; + inline uint8_t getPitch() const { return m_channel->pitch; }; + inline uint8_t getPitchFactor() const { return m_pitch_factor; }; }; TEST(OplVoice, ctrl_modulation_wheel) @@ -42,23 +47,26 @@ namespace HyperSonicDrivers::drivers::midi::opl auto opl = std::make_shared(); const bool opl3_mode = false; auto ow = std::make_unique < drivers::opl::OplWriter>(opl, opl3_mode); + IMidiChannel midi_channel(0); OplVoiceMock v((uint8_t)0, ow.get()); - v.setChannel(0); + v.setChannel(&midi_channel); EXPECT_FALSE(v.isVibrato()); - EXPECT_FALSE(v.ctrl_modulationWheel(0, 40 + 1)); + EXPECT_FALSE(v.ctrl_modulationWheel(40 + 1)); v.setFree(false); - EXPECT_TRUE(v.ctrl_modulationWheel(0, 40 + 1)); + EXPECT_TRUE(v.ctrl_modulationWheel(40 + 1)); EXPECT_TRUE(v.isVibrato()); - EXPECT_TRUE(v.ctrl_modulationWheel(0, 40 - 1)); + EXPECT_TRUE(v.ctrl_modulationWheel(40 - 1)); EXPECT_FALSE(v.isVibrato()); } TEST(OplVoice, allocate) { files::dmx::OP2File f("../fixtures/GENMIDI.OP2"); + IMidiChannel midi_channel0(0); + IMidiChannel midi_channel1(1); auto b = f.getBank(); auto opl = std::make_shared(); @@ -67,57 +75,60 @@ namespace HyperSonicDrivers::drivers::midi::opl OplVoiceMock v1((uint8_t)0, ow.get()); OplVoiceMock v2((uint8_t)1, ow.get()); - v1.setChannel(0); - v2.setChannel(0); + v1.setChannel(&midi_channel0); + v2.setChannel(&midi_channel0); - EXPECT_EQ(v1.getChannel(), 0); - EXPECT_EQ(v2.getChannel(), 0); + EXPECT_EQ(v1.getChannel(), &midi_channel0); + EXPECT_EQ(v2.getChannel(), &midi_channel0); - const uint8_t ch = 1; + IMidiChannel* ch = &midi_channel1; const uint8_t note = 100; const uint8_t vol = 80; + const uint8_t vol2 = 79; //const bool secondary = false; const uint8_t ch_mod = 64; const uint8_t ch_vol = 100; const uint8_t ch_pitch = 16; const uint8_t ch_pan = 32; + ch->modulation = ch_mod; + ch->volume = ch_vol; + ch->pitch = ch_pitch; + ch->pan = ch_pan; // A Channel with 2 Voices... - const uint8_t slot1 = v1.allocate(ch, note, vol, b->getInstrumentPtr(0), false, ch_mod, ch_vol, ch_pitch, ch_pan); - const uint8_t slot2 = v2.allocate(ch, note, vol, b->getInstrumentPtr(0), true, ch_mod, ch_vol, ch_pitch, ch_pan); + const uint8_t slot1 = v1.allocate(ch, note, vol, b->getInstrumentPtr(0), false); + const uint8_t slot2 = v2.allocate(ch, note, vol2, b->getInstrumentPtr(0), true); // 1st voice EXPECT_FALSE(v1.isFree()); - EXPECT_TRUE(v1.isChannelBusy(ch)); - EXPECT_FALSE(v1.isChannelFree(ch)); EXPECT_EQ(v1.getChannel(), ch); EXPECT_EQ(v1.getSlot(), 0); EXPECT_EQ(v1.getSlot(), slot1); EXPECT_EQ(v1.getNote(), note); EXPECT_EQ(v1.getVolume(), vol); + EXPECT_EQ(v1.getChannel()->volume, ch_vol); const int cmpInstr1 = memcmp(&b->getInstrumentPtr(0)->voices[0], v1.getInstrument(), sizeof(hardware::opl::OPL2instrument_t)); EXPECT_EQ(cmpInstr1, 0); EXPECT_TRUE(v1.isVibrato()); - EXPECT_EQ(v1.getRealVolume(), ch_vol * vol / 127); EXPECT_EQ(v1.getPitch(), ch_pitch); + EXPECT_EQ(v1.getPitchFactor(), ch_pitch); EXPECT_EQ(v1.getPan(), ch_pan); // 2nd voice EXPECT_FALSE(v2.isFree()); - EXPECT_TRUE(v2.isChannelBusy(ch)); - EXPECT_FALSE(v2.isChannelFree(ch)); EXPECT_EQ(v2.getChannel(), ch); EXPECT_EQ(v2.getSlot(), 1); EXPECT_EQ(v2.getSlot(), slot2); EXPECT_EQ(v2.getNote(), note); - EXPECT_EQ(v2.getVolume(), vol); + EXPECT_EQ(v2.getVolume(), vol2); + EXPECT_EQ(v2.getChannel()->volume, ch_vol); const int cmpInstr2 = memcmp(&b->getInstrumentPtr(0)->voices[1], v2.getInstrument(), sizeof(hardware::opl::OPL2instrument_t)); EXPECT_EQ(cmpInstr2, 0); EXPECT_TRUE(v2.isVibrato()); - EXPECT_EQ(v2.getRealVolume(), ch_vol * vol / 127); EXPECT_EQ(v2.getPitch(), ch_pitch); + EXPECT_EQ(v2.getPitchFactor(), ch_pitch); EXPECT_EQ(v2.getPan(), ch_pan); } }