diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index f3abf2e2..46d56014 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -558,6 +558,7 @@ + @@ -982,6 +983,7 @@ + @@ -1098,6 +1100,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 135b29fe..fa3520e2 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -116,6 +116,9 @@ {13af8497-e820-43f1-9888-85797a29b551} + + {c9c48ea8-7684-4959-9266-c0b4f0f86956} + @@ -1502,6 +1505,12 @@ Nes\Input\Controllers + + Nes\Mappers\EPSG + + + Nes\Mappers\EPSG + @@ -1804,5 +1813,8 @@ Misc + + Nes\Mappers\EPSG + \ No newline at end of file diff --git a/Core/EPSGAudio.h b/Core/EPSGAudio.h new file mode 100644 index 00000000..763ccea5 --- /dev/null +++ b/Core/EPSGAudio.h @@ -0,0 +1,163 @@ +#pragma once +#include "stdafx.h" +#include "Snapshotable.h" +#include "APU.h" +#include "BaseExpansionAudio.h" +#include "SSGAudio.h" +#include "Console.h" + +#include +#include "ym3438.h" + +using EPSGSSGAudio = SSGAudio; + +class EPSGAudio : public EPSGSSGAudio +{ +private: + ym3438_t _chip; + + int16_t _lastOutputs[2]; + int16_t _currentOutputs[2]; + + double _clock; + + static constexpr uint8_t cycleCount = 24; + + struct InputEntry + { + uint8_t addr = 0; + uint8_t data = 0; + uint8_t cycle = 0; + uint8_t wrote = 0; + }; + + static constexpr uint8_t INPUT_BUFFER_SIZE = cycleCount; + using InputBuffer = std::array; + InputBuffer _inputBuffer; + + void UpdateOutputLevel() + { + int16_t summedOutput = 0; + for (size_t x = 0; x < 2; x++) + { + _console->GetApu()->AddExpansionAudioDelta(x == 0 ? AudioChannel::EPSG_L : AudioChannel::EPSG_R, _currentOutputs[x] - _lastOutputs[x]); + _lastOutputs[x] = _currentOutputs[x]; + } + } + + uint8_t GetCurrentCycle() const + { + return static_cast(std::floor(_clock)) % cycleCount; + } + + void WriteToChip(uint8_t a, uint8_t d) + { + const auto cycle = GetCurrentCycle(); + + if (_inputBuffer[cycle].wrote) + { + std::cout << "DOUBLE WRITE" << std::endl; + } + + _inputBuffer[cycle] = { + a, + d, + cycle, + true + }; + } + + uint32_t getClockFrequency() + { + return _console->GetSettings()->GetEPSGClockFrequency() / 6; + } + +protected: + void StreamState(bool saving) override + { + EPSGSSGAudio::StreamState(saving); + + ArrayInfo lastOutputs{ _lastOutputs, 2 }; + ArrayInfo currentOutputs{ _currentOutputs, 2 }; + ArrayInfo inputBuffer{ &_inputBuffer }; + ValueInfo chip{ &_chip }; + ValueInfo clock { &_clock }; + Stream(lastOutputs, currentOutputs, inputBuffer, chip, clock); + } + + void ClockAudio() override + { + EPSGSSGAudio::ClockAudio(); + + _clock += getClockFrequency() / (double)_console->GetCpu()->GetClockRate(_console->GetModel()); + + while (_clock >= cycleCount) + { + for (uint8_t x = 0; x < 2; x++) + { + _currentOutputs[x] = 0; + } + + for (uint8_t cycle = 0; cycle < cycleCount; cycle++) + { + _clock--; + + int16_t samples[2]; + OPN2_Clock(&_chip, samples); + + for (uint8_t x = 0; x < 2; x++) + { + _currentOutputs[x] += samples[x]; + } + + auto& input = _inputBuffer[cycle]; + if(input.wrote) + { + input.wrote = false; + + OPN2_Write(&_chip, input.addr, input.data); + } + } + + for (uint8_t x = 0; x < 2; x++) + { + _currentOutputs[x] *= 11; + } + + UpdateOutputLevel(); + } + } + +public: + EPSGAudio(shared_ptr console) : EPSGSSGAudio(console) + { + memset(_lastOutputs, 0, sizeof(_lastOutputs)); + memset(_currentOutputs, 0, sizeof(_currentOutputs)); + _inputBuffer = {}; + + _clock = 0; + + OPN2_Reset(&_chip); + OPN2_SetChipType(ym3438_mode_ym2612 | ym3438_mode_readmode); + //OPN2_SetChipType(0); + } + + void WriteRegister(uint16_t addr, uint8_t value) + { + EPSGSSGAudio::WriteRegister(addr, value); + + switch(addr) { + case 0xC000: + case 0xE000: + case 0xC002: + case 0xE002: + + const uint8_t a0 = (addr & 0xF000) == 0xE000; + const uint8_t a1 = !!(addr & 0xF); + + WriteToChip(a0 | (a1 << 1), value); + + break; + } + } +}; \ No newline at end of file diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 018b0289..25160a9a 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -116,7 +116,9 @@ enum class AudioChannel VRC6 = 7, VRC7 = 8, Namco163 = 9, - Sunsoft5B = 10 + Sunsoft5B = 10, + EPSG_L = 11, + EPSG_R = 12, }; enum class EqualizerFilterType @@ -657,8 +659,9 @@ private: bool _audioSettingsChanged = false; uint32_t _audioLatency = 50; - double _channelVolume[11] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; - double _channelPanning[11] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; + uint32_t _EPSGClockFrequency = 3579545; + double _channelVolume[13] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; + double _channelPanning[13] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 2.0 }; EqualizerFilterType _equalizerFilterType = EqualizerFilterType::None; vector _bandGains = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; vector _bands = { { 40,56,80,113,160,225,320,450,600,750,1000,2000,3000,4000,5000,6000,7000,10000,12500,15000 } }; @@ -987,6 +990,12 @@ public: _audioSettingsChanged = true; } + void SetEPSGClockFrequency(uint32_t clockFrequency) + { + _EPSGClockFrequency = clockFrequency; + _audioSettingsChanged = true; + } + void SetAudioFilterSettings(AudioFilterSettings settings) { _audioFilterSettings = settings; @@ -1158,6 +1167,11 @@ public: return _audioLatency; } + uint32_t GetEPSGClockFrequency() + { + return _EPSGClockFrequency; + } + void SetVideoFilterType(VideoFilterType videoFilterType) { _videoFilterType = videoFilterType; diff --git a/Core/MMC3.h b/Core/MMC3.h index f47926fa..ab1e43ad 100644 --- a/Core/MMC3.h +++ b/Core/MMC3.h @@ -5,9 +5,29 @@ #include "CPU.h" #include "EmulationSettings.h" #include "A12Watcher.h" +#include "EPSGAudio.h" +#include "Sunsoft5bAudio.h" + +#ifndef MMC3_DEFAULT_AUDIO +#define MMC3_USE_EPSG +#endif + +#ifdef MMC3_USE_EPSG +using AudioClass = EPSGAudio; +#else +using AudioClass = Sunsoft5bAudio; +#endif class MMC3 : public BaseMapper { +public: + unique_ptr _audio; + + void ProcessCpuClock() override + { + _audio->Clock(); + } + private: enum class MMC3Registers { @@ -188,9 +208,10 @@ class MMC3 : public BaseMapper BaseMapper::StreamState(saving); ArrayInfo registers = { _registers, 8 }; SnapshotInfo a12Watcher{ &_a12Watcher }; + SnapshotInfo audio{ _audio.get() }; Stream(_state.Reg8000, _state.RegA000, _state.RegA001, _currentRegister, _chrMode, _prgMode, _irqReloadValue, _irqCounter, _irqReload, _irqEnabled, a12Watcher, - _wramEnabled, _wramWriteProtected, registers); + _wramEnabled, _wramWriteProtected, registers, audio); } virtual uint16_t GetPRGPageSize() override { return 0x2000; } @@ -200,6 +221,8 @@ class MMC3 : public BaseMapper virtual void InitMapper() override { + _audio.reset(new AudioClass(_console)); + //Force MMC3A irqs for boards that are known to use the A revision. //Some MMC3B boards also have the A behavior, but currently no way to tell them apart. _forceMmc3RevAIrqs = _romInfo.DatabaseInfo.Chip.substr(0, 5).compare("MMC3A") == 0; @@ -255,6 +278,13 @@ class MMC3 : public BaseMapper _irqEnabled = true; break; } + + switch (addr & 0xE000) { + case 0xC000: + case 0xE000: + _audio->WriteRegister(addr, value); + break; + } } virtual void TriggerIrq() diff --git a/Core/SSGAudio.h b/Core/SSGAudio.h new file mode 100644 index 00000000..c20e12f5 --- /dev/null +++ b/Core/SSGAudio.h @@ -0,0 +1,135 @@ +#pragma once +#include "stdafx.h" +#include "Snapshotable.h" +#include "APU.h" +#include "BaseExpansionAudio.h" +#include "Console.h" + + +template +class SSGAudio : public BaseExpansionAudio +{ +private: + uint8_t _volumeLut[0x10]; + uint8_t _currentRegister; + uint8_t _registers[0x10]; + int16_t _lastOutput; + int16_t _timer[3]; + uint8_t _toneStep[3]; + bool _processTick; + + uint16_t GetPeriod(int channel) + { + return _registers[channel * 2] | (_registers[channel * 2 + 1] << 8); + } + + uint16_t GetEnvelopePeriod() + { + return _registers[0x0B] | (_registers[0x0C] << 8); + } + + uint8_t GetNoisePeriod() + { + return _registers[6]; + } + + uint8_t GetVolume(int channel) + { + return _volumeLut[_registers[8 + channel] & 0x0F]; + } + + bool IsEnvelopeEnabled(int channel) + { + return (_registers[8 + channel] & 0x10) == 0x10; + } + + bool IsToneEnabled(int channel) + { + return ((_registers[7] >> channel) & 0x01) == 0x00; + } + + bool IsNoiseEnabled(int channel) + { + return ((_registers[7] >> (channel + 3)) & 0x01) == 0x00; + } + + void UpdateChannel(int channel) + { + _timer[channel]--; + if(_timer[channel] <= 0) { + _timer[channel] = GetPeriod(channel); + _toneStep[channel] = (_toneStep[channel] + 1) & 0x0F; + } + } + + void UpdateOutputLevel() + { + int16_t summedOutput = 0; + for(int i = 0; i < 3; i++) { + if(IsToneEnabled(i) && _toneStep[i] < 0x08) { + summedOutput += GetVolume(i); + } + } + + const auto delta = (summedOutput - _lastOutput) * 15; + (_console->GetApu()->AddExpansionAudioDelta(channels, summedOutput - _lastOutput), ...); + _lastOutput = summedOutput; + } + +protected: + void StreamState(bool saving) override + { + BaseExpansionAudio::StreamState(saving); + + ArrayInfo timer{ _timer, 3 }; + ArrayInfo registers{ _registers, 0x10 }; + ArrayInfo toneStep{ _toneStep, 3 }; + Stream(timer, registers, toneStep, _currentRegister, _lastOutput, _processTick); + } + + void ClockAudio() override + { + if(_processTick) { + for(int i = 0; i < 3; i++) { + UpdateChannel(i); + } + UpdateOutputLevel(); + } + _processTick = !_processTick; + } + +public: + SSGAudio(shared_ptr console) : BaseExpansionAudio(console) + { + memset(_timer, 0, sizeof(_timer)); + memset(_registers, 0, sizeof(_registers)); + memset(_toneStep, 0, sizeof(_toneStep)); + _currentRegister = 0; + _lastOutput = 0; + _processTick = false; + + double output = 1.0; + _volumeLut[0] = 0; + for(int i = 1; i < 0x10; i++) { + //+1.5 dB 2x for every 1 step in volume + output *= 1.1885022274370184377301224648922; + output *= 1.1885022274370184377301224648922; + + _volumeLut[i] = (uint8_t)output; + } + } + + void WriteRegister(uint16_t addr, uint8_t value) + { + switch(addr) { + case 0xC000: + _currentRegister = value; + break; + + case 0xE000: + if(_currentRegister <= 0xF) + _registers[_currentRegister] = value; + break; + } + } +}; \ No newline at end of file diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index b05899bb..466362b9 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -264,9 +264,12 @@ int16_t SoundMixer::GetOutputVolume(bool forRightChannel) GetChannelOutput(AudioChannel::FDS, forRightChannel) * 20 + GetChannelOutput(AudioChannel::MMC5, forRightChannel) * 43 + GetChannelOutput(AudioChannel::Namco163, forRightChannel) * 20 + - GetChannelOutput(AudioChannel::Sunsoft5B, forRightChannel) * 15 + + GetChannelOutput(AudioChannel::Sunsoft5B, forRightChannel) + GetChannelOutput(AudioChannel::VRC6, forRightChannel) * 75 + - GetChannelOutput(AudioChannel::VRC7, forRightChannel)); + GetChannelOutput(AudioChannel::VRC7, forRightChannel) + + GetChannelOutput(AudioChannel::EPSG_L, forRightChannel) + + GetChannelOutput(AudioChannel::EPSG_R, forRightChannel) + ); } void SoundMixer::AddDelta(AudioChannel channel, uint32_t time, int16_t delta) diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h index 2bf2135a..61a06541 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -30,7 +30,7 @@ public: private: static constexpr uint32_t MaxSampleRate = 96000; static constexpr uint32_t MaxSamplesPerFrame = MaxSampleRate / 60 * 4 * 2; //x4 to allow CPU overclocking up to 10x, x2 for panning stereo - static constexpr uint32_t MaxChannelCount = 11; + static constexpr uint32_t MaxChannelCount = 13; IAudioDevice* _audioDevice; EmulationSettings* _settings; diff --git a/Core/Sunsoft5bAudio.h b/Core/Sunsoft5bAudio.h index 93ddbae8..b78c67b7 100644 --- a/Core/Sunsoft5bAudio.h +++ b/Core/Sunsoft5bAudio.h @@ -2,130 +2,7 @@ #include "stdafx.h" #include "Snapshotable.h" #include "APU.h" -#include "BaseExpansionAudio.h" +#include "SSGAudio.h" #include "Console.h" -class Sunsoft5bAudio : public BaseExpansionAudio -{ -private: - uint8_t _volumeLut[0x10]; - uint8_t _currentRegister; - uint8_t _registers[0x10]; - int16_t _lastOutput; - int16_t _timer[3]; - uint8_t _toneStep[3]; - bool _processTick; - - uint16_t GetPeriod(int channel) - { - return _registers[channel * 2] | (_registers[channel * 2 + 1] << 8); - } - - uint16_t GetEnvelopePeriod() - { - return _registers[0x0B] | (_registers[0x0C] << 8); - } - - uint8_t GetNoisePeriod() - { - return _registers[6]; - } - - uint8_t GetVolume(int channel) - { - return _volumeLut[_registers[8 + channel] & 0x0F]; - } - - bool IsEnvelopeEnabled(int channel) - { - return (_registers[8 + channel] & 0x10) == 0x10; - } - - bool IsToneEnabled(int channel) - { - return ((_registers[7] >> channel) & 0x01) == 0x00; - } - - bool IsNoiseEnabled(int channel) - { - return ((_registers[7] >> (channel + 3)) & 0x01) == 0x00; - } - - void UpdateChannel(int channel) - { - _timer[channel]--; - if(_timer[channel] <= 0) { - _timer[channel] = GetPeriod(channel); - _toneStep[channel] = (_toneStep[channel] + 1) & 0x0F; - } - } - - void UpdateOutputLevel() - { - int16_t summedOutput = 0; - for(int i = 0; i < 3; i++) { - if(IsToneEnabled(i) && _toneStep[i] < 0x08) { - summedOutput += GetVolume(i); - } - } - - _console->GetApu()->AddExpansionAudioDelta(AudioChannel::Sunsoft5B, summedOutput - _lastOutput); - _lastOutput = summedOutput; - } - -protected: - void StreamState(bool saving) override - { - BaseExpansionAudio::StreamState(saving); - - ArrayInfo timer{ _timer, 3 }; - ArrayInfo registers{ _registers, 0x10 }; - ArrayInfo toneStep{ _toneStep, 3 }; - Stream(timer, registers, toneStep, _currentRegister, _lastOutput, _processTick); - } - - void ClockAudio() override - { - if(_processTick) { - for(int i = 0; i < 3; i++) { - UpdateChannel(i); - } - UpdateOutputLevel(); - } - _processTick = !_processTick; - } - -public: - Sunsoft5bAudio(shared_ptr console) : BaseExpansionAudio(console) - { - memset(_timer, 0, sizeof(_timer)); - memset(_registers, 0, sizeof(_registers)); - memset(_toneStep, 0, sizeof(_toneStep)); - _currentRegister = 0; - _lastOutput = 0; - _processTick = false; - - double output = 1.0; - _volumeLut[0] = 0; - for(int i = 1; i < 0x10; i++) { - //+1.5 dB 2x for every 1 step in volume - output *= 1.1885022274370184377301224648922; - output *= 1.1885022274370184377301224648922; - - _volumeLut[i] = (uint8_t)output; - } - } - - void WriteRegister(uint16_t addr, uint8_t value) - { - switch(addr & 0xE000) { - case 0xC000: - _currentRegister = value & 0x0F; - break; - - case 0xE000: - _registers[_currentRegister] = value; - break; - } - } -}; \ No newline at end of file +using Sunsoft5bAudio = SSGAudio; \ No newline at end of file diff --git a/Core/SunsoftFme7.h b/Core/SunsoftFme7.h index 09cbffb8..d3c2e0b5 100644 --- a/Core/SunsoftFme7.h +++ b/Core/SunsoftFme7.h @@ -3,11 +3,22 @@ #include "BaseMapper.h" #include "CPU.h" #include "Sunsoft5bAudio.h" +#include "EPSGAudio.h" + +#ifndef SUNSOFT_DEFAULT_AUDIO +#define SUNSOFT_USE_EPSG +#endif + +#ifdef SUNSOFT_USE_EPSG +using AudioClass = EPSGAudio; +#else +using AudioClass = Sunsoft5bAudio; +#endif class SunsoftFme7 : public BaseMapper { private: - unique_ptr _audio; + unique_ptr _audio; uint8_t _command; uint8_t _workRamValue; bool _irqEnabled; @@ -24,7 +35,7 @@ protected: void InitMapper() override { - _audio.reset(new Sunsoft5bAudio(_console)); + _audio.reset(new AudioClass(_console)); _command = 0; _workRamValue = 0; diff --git a/Core/ym3438.cpp b/Core/ym3438.cpp new file mode 100644 index 00000000..1b6d7599 --- /dev/null +++ b/Core/ym3438.cpp @@ -0,0 +1,1429 @@ +/* + * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + * Nuked OPN2(Yamaha YM3438) emulator. + * Thanks: + * Silicon Pr0n: + * Yamaha YM3438 decap and die shot(digshadow). + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.0.10 + */ + +#include "stdafx.h" + +#include +#include "ym3438.h" + +enum { + eg_num_attack = 0, + eg_num_decay = 1, + eg_num_sustain = 2, + eg_num_release = 3 +}; + +/* logsin table */ +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +/* exp table */ +static const Bit16u exprom[256] = { + 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, + 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, + 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, + 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, + 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, + 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, + 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, + 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, + 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, + 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, + 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, + 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, + 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, + 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, + 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, + 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, + 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, + 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, + 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, + 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, + 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, + 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, + 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, + 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, + 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, + 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, + 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, + 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, + 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, + 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, + 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, + 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa +}; + +/* Note table */ +static const Bit32u fn_note[16] = { + 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3 +}; + +/* Envelope generator */ +static const Bit32u eg_stephi[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +static const Bit8u eg_am_shift[4] = { + 7, 3, 1, 0 +}; + +/* Phase generator */ +static const Bit32u pg_detune[8] = { 16, 17, 19, 20, 22, 24, 27, 29 }; + +static const Bit32u pg_lfo_sh1[8][8] = { + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 7, 7, 1, 1 }, + { 7, 7, 7, 7, 1, 1, 1, 1 }, + { 7, 7, 7, 1, 1, 1, 1, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 } +}; + +static const Bit32u pg_lfo_sh2[8][8] = { + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 2, 2, 2, 2 }, + { 7, 7, 7, 2, 2, 2, 7, 7 }, + { 7, 7, 2, 2, 7, 7, 2, 2 }, + { 7, 7, 2, 7, 7, 7, 2, 7 }, + { 7, 7, 7, 2, 7, 7, 2, 1 }, + { 7, 7, 7, 2, 7, 7, 2, 1 }, + { 7, 7, 7, 2, 7, 7, 2, 1 } +}; + +/* Address decoder */ +static const Bit32u op_offset[12] = { + 0x000, /* Ch1 OP1/OP2 */ + 0x001, /* Ch2 OP1/OP2 */ + 0x002, /* Ch3 OP1/OP2 */ + 0x100, /* Ch4 OP1/OP2 */ + 0x101, /* Ch5 OP1/OP2 */ + 0x102, /* Ch6 OP1/OP2 */ + 0x004, /* Ch1 OP3/OP4 */ + 0x005, /* Ch2 OP3/OP4 */ + 0x006, /* Ch3 OP3/OP4 */ + 0x104, /* Ch4 OP3/OP4 */ + 0x105, /* Ch5 OP3/OP4 */ + 0x106 /* Ch6 OP3/OP4 */ +}; + +static const Bit32u ch_offset[6] = { + 0x000, /* Ch1 */ + 0x001, /* Ch2 */ + 0x002, /* Ch3 */ + 0x100, /* Ch4 */ + 0x101, /* Ch5 */ + 0x102 /* Ch6 */ +}; + +/* LFO */ +static const Bit32u lfo_cycles[8] = { + 108, 77, 71, 67, 62, 44, 8, 5 +}; + +/* FM algorithm */ +static const Bit32u fm_algorithm[4][6][8] = { + { + { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_0 */ + { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_1 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 1 } /* Out */ + }, + { + { 0, 1, 0, 0, 0, 1, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 1, 1, 1, 0, 0, 0, 0, 0 }, /* OP2 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 1, 1, 1 } /* Out */ + }, + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ + { 1, 0, 0, 1, 1, 1, 1, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 1, 1, 1, 1 } /* Out */ + }, + { + { 0, 0, 1, 0, 0, 1, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 0, 0, 0, 1, 0, 0, 0, 0 }, /* OP2 */ + { 1, 1, 0, 1, 1, 0, 0, 0 }, /* Last operator */ + { 0, 0, 1, 0, 0, 0, 0, 0 }, /* Last operator */ + { 1, 1, 1, 1, 1, 1, 1, 1 } /* Out */ + } +}; + +static Bit32u chip_type = ym3438_mode_readmode; + +void OPN2_DoIO(ym3438_t *chip) +{ + /* Write signal check */ + chip->write_a_en = (chip->write_a & 0x03) == 0x01; + chip->write_d_en = (chip->write_d & 0x03) == 0x01; + chip->write_a <<= 1; + chip->write_d <<= 1; + /* Busy counter */ + chip->busy = chip->write_busy; + chip->write_busy_cnt += chip->write_busy; + chip->write_busy = (chip->write_busy && !(chip->write_busy_cnt >> 5)) || chip->write_d_en; + chip->write_busy_cnt &= 0x1f; +} + +void OPN2_DoRegWrite(ym3438_t *chip) +{ + Bit32u i; + Bit32u slot = chip->cycles % 12; + Bit32u address; + Bit32u channel = chip->channel; + /* Update registers */ + if (chip->write_fm_data) + { + /* Slot */ + if (op_offset[slot] == (chip->address & 0x107)) + { + if (chip->address & 0x08) + { + /* OP2, OP4 */ + slot += 12; + } + address = chip->address & 0xf0; + switch (address) + { + case 0x30: /* DT, MULTI */ + chip->multi[slot] = chip->data & 0x0f; + if (!chip->multi[slot]) + { + chip->multi[slot] = 1; + } + else + { + chip->multi[slot] <<= 1; + } + chip->dt[slot] = (chip->data >> 4) & 0x07; + break; + case 0x40: /* TL */ + chip->tl[slot] = chip->data & 0x7f; + break; + case 0x50: /* KS, AR */ + chip->ar[slot] = chip->data & 0x1f; + chip->ks[slot] = (chip->data >> 6) & 0x03; + break; + case 0x60: /* AM, DR */ + chip->dr[slot] = chip->data & 0x1f; + chip->am[slot] = (chip->data >> 7) & 0x01; + break; + case 0x70: /* SR */ + chip->sr[slot] = chip->data & 0x1f; + break; + case 0x80: /* SL, RR */ + chip->rr[slot] = chip->data & 0x0f; + chip->sl[slot] = (chip->data >> 4) & 0x0f; + chip->sl[slot] |= (chip->sl[slot] + 1) & 0x10; + break; + case 0x90: /* SSG-EG */ + chip->ssg_eg[slot] = chip->data & 0x0f; + break; + default: + break; + } + } + + /* Channel */ + if (ch_offset[channel] == (chip->address & 0x103)) + { + address = chip->address & 0xfc; + switch (address) + { + case 0xa0: + chip->fnum[channel] = (chip->data & 0xff) | ((chip->reg_a4 & 0x07) << 8); + chip->block[channel] = (chip->reg_a4 >> 3) & 0x07; + chip->kcode[channel] = (chip->block[channel] << 2) | fn_note[chip->fnum[channel] >> 7]; + break; + case 0xa4: + chip->reg_a4 = chip->data & 0xff; + break; + case 0xa8: + chip->fnum_3ch[channel] = (chip->data & 0xff) | ((chip->reg_ac & 0x07) << 8); + chip->block_3ch[channel] = (chip->reg_ac >> 3) & 0x07; + chip->kcode_3ch[channel] = (chip->block_3ch[channel] << 2) | fn_note[chip->fnum_3ch[channel] >> 7]; + break; + case 0xac: + chip->reg_ac = chip->data & 0xff; + break; + case 0xb0: + chip->connect[channel] = chip->data & 0x07; + chip->fb[channel] = (chip->data >> 3) & 0x07; + break; + case 0xb4: + chip->pms[channel] = chip->data & 0x07; + chip->ams[channel] = (chip->data >> 4) & 0x03; + chip->pan_l[channel] = (chip->data >> 7) & 0x01; + chip->pan_r[channel] = (chip->data >> 6) & 0x01; + break; + default: + break; + } + } + } + + if (chip->write_a_en || chip->write_d_en) + { + /* Data */ + if (chip->write_a_en) + { + chip->write_fm_data = 0; + } + + if (chip->write_fm_address && chip->write_d_en) + { + chip->write_fm_data = 1; + } + + /* Address */ + if (chip->write_a_en) + { + if ((chip->write_data & 0xf0) != 0x00) + { + /* FM Write */ + chip->address = chip->write_data; + chip->write_fm_address = 1; + } + else + { + /* SSG write */ + chip->write_fm_address = 0; + } + } + + /* FM Mode */ + /* Data */ + if (chip->write_d_en && (chip->write_data & 0x100) == 0) + { + switch (chip->write_fm_mode_a) + { + case 0x21: /* LSI test 1 */ + for (i = 0; i < 8; i++) + { + chip->mode_test_21[i] = (chip->write_data >> i) & 0x01; + } + break; + case 0x22: /* LFO control */ + if ((chip->write_data >> 3) & 0x01) + { + chip->lfo_en = 0x7f; + } + else + { + chip->lfo_en = 0; + } + chip->lfo_freq = chip->write_data & 0x07; + break; + case 0x24: /* Timer A */ + chip->timer_a_reg &= 0x03; + chip->timer_a_reg |= (chip->write_data & 0xff) << 2; + break; + case 0x25: + chip->timer_a_reg &= 0x3fc; + chip->timer_a_reg |= chip->write_data & 0x03; + break; + case 0x26: /* Timer B */ + chip->timer_b_reg = chip->write_data & 0xff; + break; + case 0x27: /* CSM, Timer control */ + chip->mode_ch3 = (chip->write_data & 0xc0) >> 6; + chip->mode_csm = chip->mode_ch3 == 2; + chip->timer_a_load = chip->write_data & 0x01; + chip->timer_a_enable = (chip->write_data >> 2) & 0x01; + chip->timer_a_reset = (chip->write_data >> 4) & 0x01; + chip->timer_b_load = (chip->write_data >> 1) & 0x01; + chip->timer_b_enable = (chip->write_data >> 3) & 0x01; + chip->timer_b_reset = (chip->write_data >> 5) & 0x01; + break; + case 0x28: /* Key on/off */ + for (i = 0; i < 4; i++) + { + chip->mode_kon_operator[i] = (chip->write_data >> (4 + i)) & 0x01; + } + if ((chip->write_data & 0x03) == 0x03) + { + /* Invalid address */ + chip->mode_kon_channel = 0xff; + } + else + { + chip->mode_kon_channel = (chip->write_data & 0x03) + ((chip->write_data >> 2) & 1) * 3; + } + break; + case 0x2a: /* DAC data */ + chip->dacdata &= 0x01; + chip->dacdata |= (chip->write_data ^ 0x80) << 1; + break; + case 0x2b: /* DAC enable */ + chip->dacen = chip->write_data >> 7; + break; + case 0x2c: /* LSI test 2 */ + for (i = 0; i < 8; i++) + { + chip->mode_test_2c[i] = (chip->write_data >> i) & 0x01; + } + chip->dacdata &= 0x1fe; + chip->dacdata |= chip->mode_test_2c[3]; + chip->eg_custom_timer = !chip->mode_test_2c[7] && chip->mode_test_2c[6]; + break; + default: + break; + } + } + + /* Address */ + if (chip->write_a_en) + { + chip->write_fm_mode_a = chip->write_data & 0x1ff; + } + } + + if (chip->write_fm_data) + { + chip->data = chip->write_data & 0xff; + } +} + +void OPN2_PhaseCalcIncrement(ym3438_t *chip) +{ + Bit32u chan = chip->channel; + Bit32u slot = chip->cycles; + Bit32u fnum = chip->pg_fnum; + Bit32u fnum_h = fnum >> 4; + Bit32u fm; + Bit32u basefreq; + Bit8u lfo = chip->lfo_pm; + Bit8u lfo_l = lfo & 0x0f; + Bit8u pms = chip->pms[chan]; + Bit8u dt = chip->dt[slot]; + Bit8u dt_l = dt & 0x03; + Bit8u detune = 0; + Bit8u block, note; + Bit8u sum, sum_h, sum_l; + Bit8u kcode = chip->pg_kcode; + + fnum <<= 1; + /* Apply LFO */ + if (lfo_l & 0x08) + { + lfo_l ^= 0x0f; + } + fm = (fnum_h >> pg_lfo_sh1[pms][lfo_l]) + (fnum_h >> pg_lfo_sh2[pms][lfo_l]); + if (pms > 5) + { + fm <<= pms - 5; + } + fm >>= 2; + if (lfo & 0x10) + { + fnum -= fm; + } + else + { + fnum += fm; + } + fnum &= 0xfff; + + basefreq = (fnum << chip->pg_block) >> 2; + + /* Apply detune */ + if (dt_l) + { + if (kcode > 0x1c) + { + kcode = 0x1c; + } + block = kcode >> 2; + note = kcode & 0x03; + sum = block + 9 + ((dt_l == 3) | (dt_l & 0x02)); + sum_h = sum >> 1; + sum_l = sum & 0x01; + detune = pg_detune[(sum_l << 2) | note] >> (9 - sum_h); + } + if (dt & 0x04) + { + basefreq -= detune; + } + else + { + basefreq += detune; + } + basefreq &= 0x1ffff; + chip->pg_inc[slot] = (basefreq * chip->multi[slot]) >> 1; + chip->pg_inc[slot] &= 0xfffff; +} + +void OPN2_PhaseGenerate(ym3438_t *chip) +{ + Bit32u slot; + /* Mask increment */ + slot = (chip->cycles + 20) % 24; + if (chip->pg_reset[slot]) + { + chip->pg_inc[slot] = 0; + } + /* Phase step */ + slot = (chip->cycles + 19) % 24; + if (chip->pg_reset[slot] || chip->mode_test_21[3]) + { + chip->pg_phase[slot] = 0; + } + chip->pg_phase[slot] += chip->pg_inc[slot]; + chip->pg_phase[slot] &= 0xfffff; +} + +void OPN2_EnvelopeSSGEG(ym3438_t *chip) +{ + Bit32u slot = chip->cycles; + Bit8u direction = 0; + chip->eg_ssg_pgrst_latch[slot] = 0; + chip->eg_ssg_repeat_latch[slot] = 0; + chip->eg_ssg_hold_up_latch[slot] = 0; + chip->eg_ssg_inv[slot] = 0; + if (chip->ssg_eg[slot] & 0x08) + { + direction = chip->eg_ssg_dir[slot]; + if (chip->eg_level[slot] & 0x200) + { + /* Reset */ + if ((chip->ssg_eg[slot] & 0x03) == 0x00) + { + chip->eg_ssg_pgrst_latch[slot] = 1; + } + /* Repeat */ + if ((chip->ssg_eg[slot] & 0x01) == 0x00) + { + chip->eg_ssg_repeat_latch[slot] = 1; + } + /* Inverse */ + if ((chip->ssg_eg[slot] & 0x03) == 0x02) + { + direction ^= 1; + } + if ((chip->ssg_eg[slot] & 0x03) == 0x03) + { + direction = 1; + } + } + /* Hold up */ + if (chip->eg_kon_latch[slot] + && ((chip->ssg_eg[slot] & 0x07) == 0x05 || (chip->ssg_eg[slot] & 0x07) == 0x03)) + { + chip->eg_ssg_hold_up_latch[slot] = 1; + } + direction &= chip->eg_kon[slot]; + chip->eg_ssg_inv[slot] = (chip->eg_ssg_dir[slot] ^ ((chip->ssg_eg[slot] >> 2) & 0x01)) + & chip->eg_kon[slot]; + } + chip->eg_ssg_dir[slot] = direction; + chip->eg_ssg_enable[slot] = (chip->ssg_eg[slot] >> 3) & 0x01; +} + +void OPN2_EnvelopeADSR(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 22) % 24; + + Bit8u nkon = chip->eg_kon_latch[slot]; + Bit8u okon = chip->eg_kon[slot]; + Bit8u kon_event; + Bit8u koff_event; + Bit8u eg_off; + Bit16s level; + Bit16s nextlevel = 0; + Bit16s ssg_level; + Bit8u nextstate = chip->eg_state[slot]; + Bit16s inc = 0; + chip->eg_read[0] = chip->eg_read_inc; + chip->eg_read_inc = chip->eg_inc > 0; + + /* Reset phase generator */ + chip->pg_reset[slot] = (nkon && !okon) || chip->eg_ssg_pgrst_latch[slot]; + + /* KeyOn/Off */ + kon_event = (nkon && !okon) || (okon && chip->eg_ssg_repeat_latch[slot]); + koff_event = okon && !nkon; + + ssg_level = level = (Bit16s)chip->eg_level[slot]; + + if (chip->eg_ssg_inv[slot]) + { + /* Inverse */ + ssg_level = 512 - level; + ssg_level &= 0x3ff; + } + if (koff_event) + { + level = ssg_level; + } + if (chip->eg_ssg_enable[slot]) + { + eg_off = level >> 9; + } + else + { + eg_off = (level & 0x3f0) == 0x3f0; + } + nextlevel = level; + if (kon_event) + { + nextstate = eg_num_attack; + /* Instant attack */ + if (chip->eg_ratemax) + { + nextlevel = 0; + } + else if (chip->eg_state[slot] == eg_num_attack && level != 0 && chip->eg_inc && nkon) + { + inc = (~level << chip->eg_inc) >> 5; + } + } + else + { + switch (chip->eg_state[slot]) + { + case eg_num_attack: + if (level == 0) + { + nextstate = eg_num_decay; + } + else if(chip->eg_inc && !chip->eg_ratemax && nkon) + { + inc = (~level << chip->eg_inc) >> 5; + } + break; + case eg_num_decay: + if ((level >> 5) == chip->eg_sl[1]) + { + nextstate = eg_num_sustain; + } + else if (!eg_off && chip->eg_inc) + { + inc = 1 << (chip->eg_inc - 1); + if (chip->eg_ssg_enable[slot]) + { + inc <<= 2; + } + } + break; + case eg_num_sustain: + case eg_num_release: + if (!eg_off && chip->eg_inc) + { + inc = 1 << (chip->eg_inc - 1); + if (chip->eg_ssg_enable[slot]) + { + inc <<= 2; + } + } + break; + default: + break; + } + if (!nkon) + { + nextstate = eg_num_release; + } + } + if (chip->eg_kon_csm[slot]) + { + nextlevel |= chip->eg_tl[1] << 3; + } + + /* Envelope off */ + if (!kon_event && !chip->eg_ssg_hold_up_latch[slot] && chip->eg_state[slot] != eg_num_attack && eg_off) + { + nextstate = eg_num_release; + nextlevel = 0x3ff; + } + + nextlevel += inc; + + chip->eg_kon[slot] = chip->eg_kon_latch[slot]; + chip->eg_level[slot] = (Bit16u)nextlevel & 0x3ff; + chip->eg_state[slot] = nextstate; +} + +void OPN2_EnvelopePrepare(ym3438_t *chip) +{ + Bit8u rate; + Bit8u sum; + Bit8u inc = 0; + Bit32u slot = chip->cycles; + Bit8u rate_sel; + + /* Prepare increment */ + rate = (chip->eg_rate << 1) + chip->eg_ksv; + + if (rate > 0x3f) + { + rate = 0x3f; + } + + sum = ((rate >> 2) + chip->eg_shift_lock) & 0x0f; + if (chip->eg_rate != 0 && chip->eg_quotient == 2) + { + if (rate < 48) + { + switch (sum) + { + case 12: + inc = 1; + break; + case 13: + inc = (rate >> 1) & 0x01; + break; + case 14: + inc = rate & 0x01; + break; + default: + break; + } + } + else + { + inc = eg_stephi[rate & 0x03][chip->eg_timer_low_lock] + (rate >> 2) - 11; + if (inc > 4) + { + inc = 4; + } + } + } + chip->eg_inc = inc; + chip->eg_ratemax = (rate >> 1) == 0x1f; + + /* Prepare rate & ksv */ + rate_sel = chip->eg_state[slot]; + if ((chip->eg_kon[slot] && chip->eg_ssg_repeat_latch[slot]) + || (!chip->eg_kon[slot] && chip->eg_kon_latch[slot])) + { + rate_sel = eg_num_attack; + } + switch (rate_sel) + { + case eg_num_attack: + chip->eg_rate = chip->ar[slot]; + break; + case eg_num_decay: + chip->eg_rate = chip->dr[slot]; + break; + case eg_num_sustain: + chip->eg_rate = chip->sr[slot]; + break; + case eg_num_release: + chip->eg_rate = (chip->rr[slot] << 1) | 0x01; + break; + default: + break; + } + chip->eg_ksv = chip->pg_kcode >> (chip->ks[slot] ^ 0x03); + if (chip->am[slot]) + { + chip->eg_lfo_am = chip->lfo_am >> eg_am_shift[chip->ams[chip->channel]]; + } + else + { + chip->eg_lfo_am = 0; + } + /* Delay TL & SL value */ + chip->eg_tl[1] = chip->eg_tl[0]; + chip->eg_tl[0] = chip->tl[slot]; + chip->eg_sl[1] = chip->eg_sl[0]; + chip->eg_sl[0] = chip->sl[slot]; +} + +void OPN2_EnvelopeGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 23) % 24; + Bit16u level; + + level = chip->eg_level[slot]; + + if (chip->eg_ssg_inv[slot]) + { + /* Inverse */ + level = 512 - level; + } + if (chip->mode_test_21[5]) + { + level = 0; + } + level &= 0x3ff; + + /* Apply AM LFO */ + level += chip->eg_lfo_am; + + /* Apply TL */ + if (!(chip->mode_csm && chip->channel == 2 + 1)) + { + level += chip->eg_tl[0] << 3; + } + if (level > 0x3ff) + { + level = 0x3ff; + } + chip->eg_out[slot] = level; +} + +void OPN2_UpdateLFO(ym3438_t *chip) +{ + if ((chip->lfo_quotient & lfo_cycles[chip->lfo_freq]) == lfo_cycles[chip->lfo_freq]) + { + chip->lfo_quotient = 0; + chip->lfo_cnt++; + } + else + { + chip->lfo_quotient += chip->lfo_inc; + } + chip->lfo_cnt &= chip->lfo_en; +} + +void OPN2_FMPrepare(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 6) % 24; + Bit32u channel = chip->channel; + Bit16s mod, mod1, mod2; + Bit32u op = slot / 6; + Bit8u connect = chip->connect[channel]; + Bit32u prevslot = (chip->cycles + 18) % 24; + + /* Calculate modulation */ + mod1 = mod2 = 0; + + if (fm_algorithm[op][0][connect]) + { + mod2 |= chip->fm_op1[channel][0]; + } + if (fm_algorithm[op][1][connect]) + { + mod1 |= chip->fm_op1[channel][1]; + } + if (fm_algorithm[op][2][connect]) + { + mod1 |= chip->fm_op2[channel]; + } + if (fm_algorithm[op][3][connect]) + { + mod2 |= chip->fm_out[prevslot]; + } + if (fm_algorithm[op][4][connect]) + { + mod1 |= chip->fm_out[prevslot]; + } + mod = mod1 + mod2; + if (op == 0) + { + /* Feedback */ + mod = mod >> (10 - chip->fb[channel]); + if (!chip->fb[channel]) + { + mod = 0; + } + } + else + { + mod >>= 1; + } + chip->fm_mod[slot] = mod; + + slot = (chip->cycles + 18) % 24; + /* OP1 */ + if (slot / 6 == 0) + { + chip->fm_op1[channel][1] = chip->fm_op1[channel][0]; + chip->fm_op1[channel][0] = chip->fm_out[slot]; + } + /* OP2 */ + if (slot / 6 == 2) + { + chip->fm_op2[channel] = chip->fm_out[slot]; + } +} + +void OPN2_ChGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 18) % 24; + Bit32u channel = chip->channel; + Bit32u op = slot / 6; + Bit32u test_dac = chip->mode_test_2c[5]; + Bit16s acc = chip->ch_acc[channel]; + Bit16s add = test_dac; + Bit16s sum = 0; + if (op == 0 && !test_dac) + { + acc = 0; + } + if (fm_algorithm[op][5][chip->connect[channel]] && !test_dac) + { + add += chip->fm_out[slot] >> 5; + } + sum = acc + add; + /* Clamp */ + if (sum > 255) + { + sum = 255; + } + else if(sum < -256) + { + sum = -256; + } + + if (op == 0 || test_dac) + { + chip->ch_out[channel] = chip->ch_acc[channel]; + } + chip->ch_acc[channel] = sum; +} + +void OPN2_ChOutput(ym3438_t *chip) +{ + Bit32u cycles = chip->cycles; + Bit32u slot = chip->cycles; + Bit32u channel = chip->channel; + Bit32u test_dac = chip->mode_test_2c[5]; + Bit16s out; + Bit16s sign; + Bit32u out_en; + chip->ch_read = chip->ch_lock; + if (slot < 12) + { + /* Ch 4,5,6 */ + channel++; + } + if ((cycles & 3) == 0) + { + if (!test_dac) + { + /* Lock value */ + chip->ch_lock = chip->ch_out[channel]; + } + chip->ch_lock_l = chip->pan_l[channel]; + chip->ch_lock_r = chip->pan_r[channel]; + } + /* Ch 6 */ + if (((cycles >> 2) == 1 && chip->dacen) || test_dac) + { + out = (Bit16s)chip->dacdata; + out <<= 7; + out >>= 7; + } + else + { + out = chip->ch_lock; + } + chip->mol = 0; + chip->mor = 0; + + if (chip_type & ym3438_mode_ym2612) + { + out_en = ((cycles & 3) == 3) || test_dac; + /* YM2612 DAC emulation(not verified) */ + sign = out >> 8; + if (out >= 0) + { + out++; + sign++; + } + if (chip->ch_lock_l && out_en) + { + chip->mol = out; + } + else + { + chip->mol = sign; + } + if (chip->ch_lock_r && out_en) + { + chip->mor = out; + } + else + { + chip->mor = sign; + } + /* Amplify signal */ + chip->mol *= 3; + chip->mor *= 3; + } + else + { + out_en = ((cycles & 3) != 0) || test_dac; + if (chip->ch_lock_l && out_en) + { + chip->mol = out; + } + if (chip->ch_lock_r && out_en) + { + chip->mor = out; + } + } +} + +void OPN2_FMGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 19) % 24; + /* Calculate phase */ + Bit16u phase = (chip->fm_mod[slot] + (chip->pg_phase[slot] >> 10)) & 0x3ff; + Bit16u quarter; + Bit16u level; + Bit16s output; + if (phase & 0x100) + { + quarter = (phase ^ 0xff) & 0xff; + } + else + { + quarter = phase & 0xff; + } + level = logsinrom[quarter]; + /* Apply envelope */ + level += chip->eg_out[slot] << 2; + /* Transform */ + if (level > 0x1fff) + { + level = 0x1fff; + } + output = ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 2) >> (level >> 8); + if (phase & 0x200) + { + output = ((~output) ^ (chip->mode_test_21[4] << 13)) + 1; + } + else + { + output = output ^ (chip->mode_test_21[4] << 13); + } + output <<= 2; + output >>= 2; + chip->fm_out[slot] = output; +} + +void OPN2_DoTimerA(ym3438_t *chip) +{ + Bit16u time; + Bit8u load; + load = chip->timer_a_overflow; + if (chip->cycles == 2) + { + /* Lock load value */ + load |= (!chip->timer_a_load_lock && chip->timer_a_load); + chip->timer_a_load_lock = chip->timer_a_load; + if (chip->mode_csm) + { + /* CSM KeyOn */ + chip->mode_kon_csm = load; + } + else + { + chip->mode_kon_csm = 0; + } + } + /* Load counter */ + if (chip->timer_a_load_latch) + { + time = chip->timer_a_reg; + } + else + { + time = chip->timer_a_cnt; + } + chip->timer_a_load_latch = load; + /* Increase counter */ + if ((chip->cycles == 1 && chip->timer_a_load_lock) || chip->mode_test_21[2]) + { + time++; + } + /* Set overflow flag */ + if (chip->timer_a_reset) + { + chip->timer_a_reset = 0; + chip->timer_a_overflow_flag = 0; + } + else + { + chip->timer_a_overflow_flag |= chip->timer_a_overflow & chip->timer_a_enable; + } + chip->timer_a_overflow = (time >> 10); + chip->timer_a_cnt = time & 0x3ff; +} + +void OPN2_DoTimerB(ym3438_t *chip) +{ + Bit16u time; + Bit8u load; + load = chip->timer_b_overflow; + if (chip->cycles == 2) + { + /* Lock load value */ + load |= (!chip->timer_b_load_lock && chip->timer_b_load); + chip->timer_b_load_lock = chip->timer_b_load; + } + /* Load counter */ + if (chip->timer_b_load_latch) + { + time = chip->timer_b_reg; + } + else + { + time = chip->timer_b_cnt; + } + chip->timer_b_load_latch = load; + /* Increase counter */ + if (chip->cycles == 1) + { + chip->timer_b_subcnt++; + } + if ((chip->timer_b_subcnt == 0x10 && chip->timer_b_load_lock) || chip->mode_test_21[2]) + { + time++; + } + chip->timer_b_subcnt &= 0x0f; + /* Set overflow flag */ + if (chip->timer_b_reset) + { + chip->timer_b_reset = 0; + chip->timer_b_overflow_flag = 0; + } + else + { + chip->timer_b_overflow_flag |= chip->timer_b_overflow & chip->timer_b_enable; + } + chip->timer_b_overflow = (time >> 8); + chip->timer_b_cnt = time & 0xff; +} + +void OPN2_KeyOn(ym3438_t*chip) +{ + Bit32u slot = chip->cycles; + Bit32u chan = chip->channel; + /* Key On */ + chip->eg_kon_latch[slot] = chip->mode_kon[slot]; + chip->eg_kon_csm[slot] = 0; + if (chip->channel == 2 && chip->mode_kon_csm) + { + /* CSM Key On */ + chip->eg_kon_latch[slot] = 1; + chip->eg_kon_csm[slot] = 1; + } + if (chip->cycles == chip->mode_kon_channel) + { + /* OP1 */ + chip->mode_kon[chan] = chip->mode_kon_operator[0]; + /* OP2 */ + chip->mode_kon[chan + 12] = chip->mode_kon_operator[1]; + /* OP3 */ + chip->mode_kon[chan + 6] = chip->mode_kon_operator[2]; + /* OP4 */ + chip->mode_kon[chan + 18] = chip->mode_kon_operator[3]; + } +} + +void OPN2_Reset(ym3438_t *chip) +{ + Bit32u i; + memset(chip, 0, sizeof(ym3438_t)); + for (i = 0; i < 24; i++) + { + chip->eg_out[i] = 0x3ff; + chip->eg_level[i] = 0x3ff; + chip->eg_state[i] = eg_num_release; + chip->multi[i] = 1; + } + for (i = 0; i < 6; i++) + { + chip->pan_l[i] = 1; + chip->pan_r[i] = 1; + } +} + +void OPN2_SetChipType(Bit32u type) +{ + chip_type = type; +} + +void OPN2_Clock(ym3438_t *chip, Bit16s *buffer) +{ + Bit32u slot = chip->cycles; + chip->lfo_inc = chip->mode_test_21[1]; + chip->pg_read >>= 1; + chip->eg_read[1] >>= 1; + chip->eg_cycle++; + /* Lock envelope generator timer value */ + if (chip->cycles == 1 && chip->eg_quotient == 2) + { + if (chip->eg_cycle_stop) + { + chip->eg_shift_lock = 0; + } + else + { + chip->eg_shift_lock = chip->eg_shift + 1; + } + chip->eg_timer_low_lock = chip->eg_timer & 0x03; + } + /* Cycle specific functions */ + switch (chip->cycles) + { + case 0: + chip->lfo_pm = chip->lfo_cnt >> 2; + if (chip->lfo_cnt & 0x40) + { + chip->lfo_am = chip->lfo_cnt & 0x3f; + } + else + { + chip->lfo_am = chip->lfo_cnt ^ 0x3f; + } + chip->lfo_am <<= 1; + break; + case 1: + chip->eg_quotient++; + chip->eg_quotient %= 3; + chip->eg_cycle = 0; + chip->eg_cycle_stop = 1; + chip->eg_shift = 0; + chip->eg_timer_inc |= chip->eg_quotient >> 1; + chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; + chip->eg_timer_inc = chip->eg_timer >> 12; + chip->eg_timer &= 0xfff; + break; + case 2: + chip->pg_read = chip->pg_phase[21] & 0x3ff; + chip->eg_read[1] = chip->eg_out[0]; + break; + case 13: + chip->eg_cycle = 0; + chip->eg_cycle_stop = 1; + chip->eg_shift = 0; + chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; + chip->eg_timer_inc = chip->eg_timer >> 12; + chip->eg_timer &= 0xfff; + break; + case 23: + chip->lfo_inc |= 1; + break; + } + chip->eg_timer &= ~(chip->mode_test_21[5] << chip->eg_cycle); + if (((chip->eg_timer >> chip->eg_cycle) | (chip->pin_test_in & chip->eg_custom_timer)) & chip->eg_cycle_stop) + { + chip->eg_shift = chip->eg_cycle; + chip->eg_cycle_stop = 0; + } + + OPN2_DoIO(chip); + + OPN2_DoTimerA(chip); + OPN2_DoTimerB(chip); + OPN2_KeyOn(chip); + + OPN2_ChOutput(chip); + OPN2_ChGenerate(chip); + + OPN2_FMPrepare(chip); + OPN2_FMGenerate(chip); + + OPN2_PhaseGenerate(chip); + OPN2_PhaseCalcIncrement(chip); + + OPN2_EnvelopeADSR(chip); + OPN2_EnvelopeGenerate(chip); + OPN2_EnvelopeSSGEG(chip); + OPN2_EnvelopePrepare(chip); + + /* Prepare fnum & block */ + if (chip->mode_ch3) + { + /* Channel 3 special mode */ + switch (slot) + { + case 1: /* OP1 */ + chip->pg_fnum = chip->fnum_3ch[1]; + chip->pg_block = chip->block_3ch[1]; + chip->pg_kcode = chip->kcode_3ch[1]; + break; + case 7: /* OP3 */ + chip->pg_fnum = chip->fnum_3ch[0]; + chip->pg_block = chip->block_3ch[0]; + chip->pg_kcode = chip->kcode_3ch[0]; + break; + case 13: /* OP2 */ + chip->pg_fnum = chip->fnum_3ch[2]; + chip->pg_block = chip->block_3ch[2]; + chip->pg_kcode = chip->kcode_3ch[2]; + break; + case 19: /* OP4 */ + default: + chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; + chip->pg_block = chip->block[(chip->channel + 1) % 6]; + chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; + break; + } + } + else + { + chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; + chip->pg_block = chip->block[(chip->channel + 1) % 6]; + chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; + } + + OPN2_UpdateLFO(chip); + OPN2_DoRegWrite(chip); + chip->cycles = (chip->cycles + 1) % 24; + chip->channel = chip->cycles % 6; + + buffer[0] = chip->mol; + buffer[1] = chip->mor; + + if (chip->status_time) + chip->status_time--; +} + +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data) +{ + port &= 3; + chip->write_data = ((port << 7) & 0x100) | data; + if (port & 1) + { + /* Data */ + chip->write_d |= 1; + } + else + { + /* Address */ + chip->write_a |= 1; + } +} + +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value) +{ + chip->pin_test_in = value & 1; +} + +Bit32u OPN2_ReadTestPin(ym3438_t *chip) +{ + if (!chip->mode_test_2c[7]) + { + return 0; + } + return chip->cycles == 23; +} + +Bit32u OPN2_ReadIRQPin(ym3438_t *chip) +{ + return chip->timer_a_overflow_flag | chip->timer_b_overflow_flag; +} + +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port) +{ + if ((port & 3) == 0 || (chip_type & ym3438_mode_readmode)) + { + if (chip->mode_test_21[6]) + { + /* Read test data */ + Bit32u slot = (chip->cycles + 18) % 24; + Bit16u testdata = ((chip->pg_read & 0x01) << 15) + | ((chip->eg_read[chip->mode_test_21[0]] & 0x01) << 14); + if (chip->mode_test_2c[4]) + { + testdata |= chip->ch_read & 0x1ff; + } + else + { + testdata |= chip->fm_out[slot] & 0x3fff; + } + if (chip->mode_test_21[7]) + { + chip->status = testdata & 0xff; + } + else + { + chip->status = testdata >> 8; + } + } + else + { + chip->status = (chip->busy << 7) | (chip->timer_b_overflow_flag << 1) + | chip->timer_a_overflow_flag; + } + if (chip_type & ym3438_mode_ym2612) + { + chip->status_time = 300000; + } + else + { + chip->status_time = 40000000; + } + } + if (chip->status_time) + { + return chip->status; + } + return 0; +} diff --git a/Core/ym3438.h b/Core/ym3438.h new file mode 100644 index 00000000..090a57cf --- /dev/null +++ b/Core/ym3438.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + * Nuked OPN2(Yamaha YM3438) emulator. + * Thanks: + * Silicon Pr0n: + * Yamaha YM3438 decap and die shot(digshadow). + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.0.9 + */ + +#ifndef YM3438_H +#define YM3438_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + ym3438_mode_ym2612 = 0x01, /* Enables YM2612 emulation (MD1, MD2 VA2) */ + ym3438_mode_readmode = 0x02 /* Enables status read on any port (TeraDrive, MD1 VA7, MD2, etc) */ +}; + +#include + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint64_t Bit64u; +typedef int64_t Bit64s; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +typedef struct +{ + Bit32u cycles; + Bit32u channel; + Bit16s mol, mor; + /* IO */ + Bit16u write_data; + Bit8u write_a; + Bit8u write_d; + Bit8u write_a_en; + Bit8u write_d_en; + Bit8u write_busy; + Bit8u write_busy_cnt; + Bit8u write_fm_address; + Bit8u write_fm_data; + Bit16u write_fm_mode_a; + Bit16u address; + Bit8u data; + Bit8u pin_test_in; + Bit8u pin_irq; + Bit8u busy; + /* LFO */ + Bit8u lfo_en; + Bit8u lfo_freq; + Bit8u lfo_pm; + Bit8u lfo_am; + Bit8u lfo_cnt; + Bit8u lfo_inc; + Bit8u lfo_quotient; + /* Phase generator */ + Bit16u pg_fnum; + Bit8u pg_block; + Bit8u pg_kcode; + Bit32u pg_inc[24]; + Bit32u pg_phase[24]; + Bit8u pg_reset[24]; + Bit32u pg_read; + /* Envelope generator */ + Bit8u eg_cycle; + Bit8u eg_cycle_stop; + Bit8u eg_shift; + Bit8u eg_shift_lock; + Bit8u eg_timer_low_lock; + Bit16u eg_timer; + Bit8u eg_timer_inc; + Bit16u eg_quotient; + Bit8u eg_custom_timer; + Bit8u eg_rate; + Bit8u eg_ksv; + Bit8u eg_inc; + Bit8u eg_ratemax; + Bit8u eg_sl[2]; + Bit8u eg_lfo_am; + Bit8u eg_tl[2]; + Bit8u eg_state[24]; + Bit16u eg_level[24]; + Bit16u eg_out[24]; + Bit8u eg_kon[24]; + Bit8u eg_kon_csm[24]; + Bit8u eg_kon_latch[24]; + Bit8u eg_csm_mode[24]; + Bit8u eg_ssg_enable[24]; + Bit8u eg_ssg_pgrst_latch[24]; + Bit8u eg_ssg_repeat_latch[24]; + Bit8u eg_ssg_hold_up_latch[24]; + Bit8u eg_ssg_dir[24]; + Bit8u eg_ssg_inv[24]; + Bit32u eg_read[2]; + Bit8u eg_read_inc; + /* FM */ + Bit16s fm_op1[6][2]; + Bit16s fm_op2[6]; + Bit16s fm_out[24]; + Bit16u fm_mod[24]; + /* Channel */ + Bit16s ch_acc[6]; + Bit16s ch_out[6]; + Bit16s ch_lock; + Bit8u ch_lock_l; + Bit8u ch_lock_r; + Bit16s ch_read; + /* Timer */ + Bit16u timer_a_cnt; + Bit16u timer_a_reg; + Bit8u timer_a_load_lock; + Bit8u timer_a_load; + Bit8u timer_a_enable; + Bit8u timer_a_reset; + Bit8u timer_a_load_latch; + Bit8u timer_a_overflow_flag; + Bit8u timer_a_overflow; + + Bit16u timer_b_cnt; + Bit8u timer_b_subcnt; + Bit16u timer_b_reg; + Bit8u timer_b_load_lock; + Bit8u timer_b_load; + Bit8u timer_b_enable; + Bit8u timer_b_reset; + Bit8u timer_b_load_latch; + Bit8u timer_b_overflow_flag; + Bit8u timer_b_overflow; + + /* Register set */ + Bit8u mode_test_21[8]; + Bit8u mode_test_2c[8]; + Bit8u mode_ch3; + Bit8u mode_kon_channel; + Bit8u mode_kon_operator[4]; + Bit8u mode_kon[24]; + Bit8u mode_csm; + Bit8u mode_kon_csm; + Bit8u dacen; + Bit16s dacdata; + + Bit8u ks[24]; + Bit8u ar[24]; + Bit8u sr[24]; + Bit8u dt[24]; + Bit8u multi[24]; + Bit8u sl[24]; + Bit8u rr[24]; + Bit8u dr[24]; + Bit8u am[24]; + Bit8u tl[24]; + Bit8u ssg_eg[24]; + + Bit16u fnum[6]; + Bit8u block[6]; + Bit8u kcode[6]; + Bit16u fnum_3ch[6]; + Bit8u block_3ch[6]; + Bit8u kcode_3ch[6]; + Bit8u reg_a4; + Bit8u reg_ac; + Bit8u connect[6]; + Bit8u fb[6]; + Bit8u pan_l[6], pan_r[6]; + Bit8u ams[6]; + Bit8u pms[6]; + Bit8u status; + Bit32u status_time; +} ym3438_t; + +void OPN2_Reset(ym3438_t *chip); +void OPN2_SetChipType(Bit32u type); +void OPN2_Clock(ym3438_t *chip, Bit16s *buffer); +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data); +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value); +Bit32u OPN2_ReadTestPin(ym3438_t *chip); +Bit32u OPN2_ReadIRQPin(ym3438_t *chip); +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/GUI.NET/Config/AudioInfo.cs b/GUI.NET/Config/AudioInfo.cs index c7b3cb79..dbd9cdbb 100644 --- a/GUI.NET/Config/AudioInfo.cs +++ b/GUI.NET/Config/AudioInfo.cs @@ -23,6 +23,10 @@ namespace Mesen.GUI.Config [MinMax(0, 100)] public UInt32 Vrc7Volume = 100; [MinMax(0, 100)] public UInt32 Namco163Volume = 100; [MinMax(0, 100)] public UInt32 Sunsoft5bVolume = 100; + [MinMax(0, 100)] public UInt32 EPSGVolume_L = 100; + [MinMax(0, 100)] public UInt32 EPSGVolume_R = 100; + + [MinMax(10000, 32000000)] public UInt32 EPSGClockFrequency = 3579545; [MinMax(-100, 100)] public Int32 Square1Panning = 0; [MinMax(-100, 100)] public Int32 Square2Panning = 0; @@ -35,6 +39,8 @@ namespace Mesen.GUI.Config [MinMax(-100, 100)] public Int32 Vrc7Panning = 0; [MinMax(-100, 100)] public Int32 Namco163Panning = 0; [MinMax(-100, 100)] public Int32 Sunsoft5bPanning = 0; + [MinMax(-100, 100)] public Int32 EPSGPanning_L = -100; + [MinMax(-100, 100)] public Int32 EPSGPanning_R = 100; [ValidValues(11025, 22050, 44100, 48000, 96000)] public UInt32 SampleRate = 48000; public bool ReduceSoundInBackground = true; @@ -87,7 +93,7 @@ namespace Mesen.GUI.Config { } - static private double ConvertVolume(UInt32 volume) + static public double ConvertVolume(UInt32 volume) { return ((double)volume / 100d); } @@ -114,6 +120,8 @@ namespace Mesen.GUI.Config InteropEmu.SetChannelVolume(AudioChannel.VRC7, ConvertVolume(audioInfo.Vrc7Volume)); InteropEmu.SetChannelVolume(AudioChannel.Namco163, ConvertVolume(audioInfo.Namco163Volume)); InteropEmu.SetChannelVolume(AudioChannel.Sunsoft5B, ConvertVolume(audioInfo.Sunsoft5bVolume)); + InteropEmu.SetChannelVolume(AudioChannel.EPSG_L, ConvertVolume(audioInfo.EPSGVolume_L)); + InteropEmu.SetChannelVolume(AudioChannel.EPSG_R, ConvertVolume(audioInfo.EPSGVolume_R)); InteropEmu.SetChannelPanning(AudioChannel.Square1, ConvertPanning(audioInfo.Square1Panning)); InteropEmu.SetChannelPanning(AudioChannel.Square2, ConvertPanning(audioInfo.Square2Panning)); @@ -126,6 +134,10 @@ namespace Mesen.GUI.Config InteropEmu.SetChannelPanning(AudioChannel.VRC7, ConvertPanning(audioInfo.Vrc7Panning)); InteropEmu.SetChannelPanning(AudioChannel.Namco163, ConvertPanning(audioInfo.Namco163Panning)); InteropEmu.SetChannelPanning(AudioChannel.Sunsoft5B, ConvertPanning(audioInfo.Sunsoft5bPanning)); + InteropEmu.SetChannelPanning(AudioChannel.EPSG_L, ConvertPanning(audioInfo.EPSGPanning_L)); + InteropEmu.SetChannelPanning(AudioChannel.EPSG_R, ConvertPanning(audioInfo.EPSGPanning_R)); + + InteropEmu.SetEPSGClockFrequency(audioInfo.EPSGClockFrequency); InteropEmu.SetEqualizerFilterType(audioInfo.EnableEqualizer ? audioInfo.EqualizerFilterType : EqualizerFilterType.None); diff --git a/GUI.NET/Debugger/frmApuViewer.Designer.cs b/GUI.NET/Debugger/frmApuViewer.Designer.cs index d05fd226..84e6f53f 100644 --- a/GUI.NET/Debugger/frmApuViewer.Designer.cs +++ b/GUI.NET/Debugger/frmApuViewer.Designer.cs @@ -41,6 +41,7 @@ this.chkVrc7 = new System.Windows.Forms.CheckBox(); this.chkDmc = new System.Windows.Forms.CheckBox(); this.chkSunsoft = new System.Windows.Forms.CheckBox(); + this.chkEPSG = new System.Windows.Forms.CheckBox(); this.chkVrc6 = new System.Windows.Forms.CheckBox(); this.grpSquare1 = new System.Windows.Forms.GroupBox(); this.ctrlSquareInfo1 = new Mesen.GUI.Debugger.Controls.ctrlSquareInfo(); @@ -118,6 +119,7 @@ this.tableLayoutPanel2.Controls.Add(this.chkVrc7, 2, 1); this.tableLayoutPanel2.Controls.Add(this.chkDmc, 0, 4); this.tableLayoutPanel2.Controls.Add(this.chkSunsoft, 1, 3); + this.tableLayoutPanel2.Controls.Add(this.chkEPSG, 1, 4); this.tableLayoutPanel2.Controls.Add(this.chkVrc6, 2, 0); this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 16); @@ -271,6 +273,20 @@ this.chkSunsoft.UseVisualStyleBackColor = true; this.chkSunsoft.CheckedChanged += new System.EventHandler(this.chkSoundChannel_CheckedChanged); // + // chkEPSG + // + this.chkEPSG.AutoSize = true; + this.chkEPSG.Checked = true; + this.chkEPSG.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkEPSG.Location = new System.Drawing.Point(99, 80); + this.chkEPSG.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3); + this.chkEPSG.Name = "chkEPSG"; + this.chkEPSG.Size = new System.Drawing.Size(78, 17); + this.chkEPSG.TabIndex = 10; + this.chkEPSG.Text = "EPSG"; + this.chkEPSG.UseVisualStyleBackColor = true; + this.chkEPSG.CheckedChanged += new System.EventHandler(this.chkSoundChannel_CheckedChanged); + // // chkVrc6 // this.chkVrc6.AutoSize = true; @@ -459,5 +475,6 @@ private System.Windows.Forms.CheckBox chkVrc7; private System.Windows.Forms.CheckBox chkVrc6; private System.Windows.Forms.CheckBox chkSunsoft; + private System.Windows.Forms.CheckBox chkEPSG; } } \ No newline at end of file diff --git a/GUI.NET/Debugger/frmApuViewer.cs b/GUI.NET/Debugger/frmApuViewer.cs index a6f0526e..6d0b5761 100644 --- a/GUI.NET/Debugger/frmApuViewer.cs +++ b/GUI.NET/Debugger/frmApuViewer.cs @@ -54,7 +54,9 @@ namespace Mesen.GUI.Debugger } private void chkSoundChannel_CheckedChanged(object sender, EventArgs e) - { + { + AudioInfo audioInfo = ConfigManager.Config.AudioInfo; + InteropEmu.SetChannelVolume(AudioChannel.Square1, chkSquare1.Checked ? 1 : 0); InteropEmu.SetChannelVolume(AudioChannel.Square2, chkSquare2.Checked ? 1 : 0); InteropEmu.SetChannelVolume(AudioChannel.Triangle, chkTriangle.Checked ? 1 : 0); @@ -66,6 +68,8 @@ namespace Mesen.GUI.Debugger InteropEmu.SetChannelVolume(AudioChannel.VRC7, chkVrc7.Checked ? 1 : 0); InteropEmu.SetChannelVolume(AudioChannel.MMC5, chkMmc5.Checked ? 1 : 0); InteropEmu.SetChannelVolume(AudioChannel.Sunsoft5B, chkSunsoft.Checked ? 1 : 0); - } + InteropEmu.SetChannelVolume(AudioChannel.EPSG_L, chkEPSG.Checked ? AudioInfo.ConvertVolume(audioInfo.EPSGVolume_L) : 0); + InteropEmu.SetChannelVolume(AudioChannel.EPSG_R, chkEPSG.Checked ? AudioInfo.ConvertVolume(audioInfo.EPSGVolume_R) : 0); + } } } diff --git a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs index 64bb96bc..32bedb70 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs @@ -43,6 +43,8 @@ namespace Mesen.GUI.Forms.Config this.trkVrc7Vol = new Mesen.GUI.Controls.ctrlTrackbar(); this.trkNamco163Vol = new Mesen.GUI.Controls.ctrlTrackbar(); this.trkSunsoft5b = new Mesen.GUI.Controls.ctrlTrackbar(); + this.trkEPSGVol_L = new Mesen.GUI.Controls.ctrlTrackbar(); + this.trkEPSGVol_R = new Mesen.GUI.Controls.ctrlTrackbar(); this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); this.lblVolumeReductionSettings = new System.Windows.Forms.Label(); this.chkEnableAudio = new System.Windows.Forms.CheckBox(); @@ -78,6 +80,8 @@ namespace Mesen.GUI.Forms.Config this.trkVrc7Pan = new Mesen.GUI.Controls.ctrlHorizontalTrackbar(); this.trkNamcoPan = new Mesen.GUI.Controls.ctrlHorizontalTrackbar(); this.trkSunsoftPan = new Mesen.GUI.Controls.ctrlHorizontalTrackbar(); + this.trkEPSGPan_L = new Mesen.GUI.Controls.ctrlHorizontalTrackbar(); + this.trkEPSGPan_R = new Mesen.GUI.Controls.ctrlHorizontalTrackbar(); this.tpgEqualizer = new System.Windows.Forms.TabPage(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.chkEnableEqualizer = new System.Windows.Forms.CheckBox(); @@ -142,6 +146,8 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); this.chkDisableDynamicSampleRate = new Mesen.GUI.Controls.ctrlRiskyOption(); this.chkDisableNoiseModeFlag = new Mesen.GUI.Controls.ctrlRiskyOption(); + this.nudEPSGClockFrequency = new Mesen.GUI.Controls.MesenNumericUpDown(); + this.lblEPSGClockFrequency = new System.Windows.Forms.Label(); this.chkSilenceTriangleHighFreq = new System.Windows.Forms.CheckBox(); this.chkSwapDutyCycles = new Mesen.GUI.Controls.ctrlRiskyOption(); this.chkReduceDmcPopping = new System.Windows.Forms.CheckBox(); @@ -196,7 +202,7 @@ namespace Mesen.GUI.Forms.Config // // tableLayoutPanel1 // - this.tableLayoutPanel1.ColumnCount = 6; + this.tableLayoutPanel1.ColumnCount = 7; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); @@ -214,8 +220,10 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel1.Controls.Add(this.trkVrc6Vol, 2, 1); this.tableLayoutPanel1.Controls.Add(this.trkVrc7Vol, 3, 1); this.tableLayoutPanel1.Controls.Add(this.trkNamco163Vol, 4, 1); - this.tableLayoutPanel1.Controls.Add(this.trkSunsoft5b, 5, 1); - this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Controls.Add(this.trkSunsoft5b, 6, 0); + this.tableLayoutPanel1.Controls.Add(this.trkEPSGVol_L, 5, 1); + this.tableLayoutPanel1.Controls.Add(this.trkEPSGVol_R, 6, 1); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 16); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 3; @@ -404,10 +412,40 @@ namespace Mesen.GUI.Forms.Config this.trkSunsoft5b.TabIndex = 22; this.trkSunsoft5b.Text = "Sunsoft"; this.trkSunsoft5b.Value = 50; - // - // tableLayoutPanel2 - // - this.tableLayoutPanel2.ColumnCount = 2; + // + // trkEPSGVol_L + // + this.trkEPSGVol_L.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.trkEPSGVol_L.Location = new System.Drawing.Point(387, 160); + this.trkEPSGVol_L.Margin = new System.Windows.Forms.Padding(0); + this.trkEPSGVol_L.Maximum = 100; + this.trkEPSGVol_L.MaximumSize = new System.Drawing.Size(63, 160); + this.trkEPSGVol_L.Minimum = 0; + this.trkEPSGVol_L.MinimumSize = new System.Drawing.Size(63, 160); + this.trkEPSGVol_L.Name = "trkEPSGVol_L"; + this.trkEPSGVol_L.Size = new System.Drawing.Size(63, 160); + this.trkEPSGVol_L.TabIndex = 22; + this.trkEPSGVol_L.Text = "EPSG Left"; + this.trkEPSGVol_L.Value = 100; + // + // trkEPSGVol_R + // + this.trkEPSGVol_R.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.trkEPSGVol_R.Location = new System.Drawing.Point(387, 160); + this.trkEPSGVol_R.Margin = new System.Windows.Forms.Padding(0); + this.trkEPSGVol_R.Maximum = 100; + this.trkEPSGVol_R.MaximumSize = new System.Drawing.Size(63, 160); + this.trkEPSGVol_R.Minimum = 0; + this.trkEPSGVol_R.MinimumSize = new System.Drawing.Size(63, 160); + this.trkEPSGVol_R.Name = "trkEPSGVol_R"; + this.trkEPSGVol_R.Size = new System.Drawing.Size(63, 160); + this.trkEPSGVol_R.TabIndex = 22; + this.trkEPSGVol_R.Text = "EPSG Right"; + this.trkEPSGVol_R.Value = 100; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 2; this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel2.Controls.Add(this.lblVolumeReductionSettings, 0, 4); @@ -696,7 +734,7 @@ namespace Mesen.GUI.Forms.Config this.tabMain.Location = new System.Drawing.Point(0, 0); this.tabMain.Name = "tabMain"; this.tabMain.SelectedIndex = 0; - this.tabMain.Size = new System.Drawing.Size(477, 373); + this.tabMain.Size = new System.Drawing.Size(477, 389); this.tabMain.TabIndex = 4; // // tpgGeneral @@ -727,7 +765,7 @@ namespace Mesen.GUI.Forms.Config this.tpgPanning.Location = new System.Drawing.Point(4, 22); this.tpgPanning.Name = "tpgPanning"; this.tpgPanning.Padding = new System.Windows.Forms.Padding(3); - this.tpgPanning.Size = new System.Drawing.Size(469, 347); + this.tpgPanning.Size = new System.Drawing.Size(469, 389); this.tpgPanning.TabIndex = 4; this.tpgPanning.Text = "Panning"; this.tpgPanning.UseVisualStyleBackColor = true; @@ -747,11 +785,13 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel6.Controls.Add(this.trkVrc6Pan, 1, 2); this.tableLayoutPanel6.Controls.Add(this.trkVrc7Pan, 1, 3); this.tableLayoutPanel6.Controls.Add(this.trkNamcoPan, 1, 4); - this.tableLayoutPanel6.Controls.Add(this.trkSunsoftPan, 1, 5); + this.tableLayoutPanel6.Controls.Add(this.trkSunsoftPan, 0, 6); + this.tableLayoutPanel6.Controls.Add(this.trkEPSGPan_L, 0, 5); + this.tableLayoutPanel6.Controls.Add(this.trkEPSGPan_R, 1, 5); this.tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel6.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel6.Name = "tableLayoutPanel6"; - this.tableLayoutPanel6.RowCount = 7; + this.tableLayoutPanel6.RowCount = 8; this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle()); @@ -760,7 +800,7 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel6.Size = new System.Drawing.Size(463, 341); + this.tableLayoutPanel6.Size = new System.Drawing.Size(463, 389); this.tableLayoutPanel6.TabIndex = 3; // // trkSquare1Pan @@ -928,6 +968,36 @@ namespace Mesen.GUI.Forms.Config this.trkSunsoftPan.Text = "Sunsoft"; this.trkSunsoftPan.Value = 0; // + // trkEPSGPan_L + // + this.trkEPSGPan_L.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.trkEPSGPan_L.Location = new System.Drawing.Point(244, 275); + this.trkEPSGPan_L.Margin = new System.Windows.Forms.Padding(0); + this.trkEPSGPan_L.Maximum = 100; + this.trkEPSGPan_L.MaximumSize = new System.Drawing.Size(63, 160); + this.trkEPSGPan_L.Minimum = -100; + this.trkEPSGPan_L.MinimumSize = new System.Drawing.Size(206, 55); + this.trkEPSGPan_L.Name = "trkEPSGPan_L"; + this.trkEPSGPan_L.Size = new System.Drawing.Size(206, 55); + this.trkEPSGPan_L.TabIndex = 22; + this.trkEPSGPan_L.Text = "EPSG Left"; + this.trkEPSGPan_L.Value = -100; + // + // trkEPSGPan_R + // + this.trkEPSGPan_R.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.trkEPSGPan_R.Location = new System.Drawing.Point(244, 275); + this.trkEPSGPan_R.Margin = new System.Windows.Forms.Padding(0); + this.trkEPSGPan_R.Maximum = 100; + this.trkEPSGPan_R.MaximumSize = new System.Drawing.Size(63, 160); + this.trkEPSGPan_R.Minimum = -100; + this.trkEPSGPan_R.MinimumSize = new System.Drawing.Size(206, 55); + this.trkEPSGPan_R.Name = "trkEPSGPan_R"; + this.trkEPSGPan_R.Size = new System.Drawing.Size(206, 55); + this.trkEPSGPan_R.TabIndex = 22; + this.trkEPSGPan_R.Text = "EPSG Right"; + this.trkEPSGPan_R.Value = 100; + // // tpgEqualizer // this.tpgEqualizer.Controls.Add(this.groupBox1); @@ -1897,13 +1967,16 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel3.Controls.Add(this.chkDisableDynamicSampleRate, 0, 2); this.tableLayoutPanel3.Controls.Add(this.chkDisableNoiseModeFlag, 0, 4); + this.tableLayoutPanel3.Controls.Add(this.nudEPSGClockFrequency, 0, 5); + this.tableLayoutPanel3.Controls.Add(this.lblEPSGClockFrequency, 0, 5); this.tableLayoutPanel3.Controls.Add(this.chkSilenceTriangleHighFreq, 0, 0); this.tableLayoutPanel3.Controls.Add(this.chkSwapDutyCycles, 0, 3); this.tableLayoutPanel3.Controls.Add(this.chkReduceDmcPopping, 0, 1); this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel3.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel3.Name = "tableLayoutPanel3"; - this.tableLayoutPanel3.RowCount = 6; + this.tableLayoutPanel3.RowCount = 7; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); @@ -1932,10 +2005,36 @@ namespace Mesen.GUI.Forms.Config this.chkDisableNoiseModeFlag.Size = new System.Drawing.Size(463, 24); this.chkDisableNoiseModeFlag.TabIndex = 3; this.chkDisableNoiseModeFlag.Text = "Disable noise channel mode flag"; + // + // nudEPSGClockFrequency + // + this.nudEPSGClockFrequency.Dock = System.Windows.Forms.DockStyle.Fill; + this.nudEPSGClockFrequency.Location = new System.Drawing.Point(0, 96); + this.nudEPSGClockFrequency.Size = new System.Drawing.Size(463, 24); + this.nudEPSGClockFrequency.TabIndex = 3; + this.nudEPSGClockFrequency.Text = "EPSG Clock Frquency"; + this.nudEPSGClockFrequency.Name = "nudEPSGClockFrequency"; + this.nudEPSGClockFrequency.Increment = new decimal(100000); + this.nudEPSGClockFrequency.Minimum = new decimal(10000); + this.nudEPSGClockFrequency.Maximum = new decimal(32000000); + this.nudEPSGClockFrequency.Value = new decimal(0); + this.nudEPSGClockFrequency.MaximumSize = new System.Drawing.Size(10000, 20); + this.nudEPSGClockFrequency.MinimumSize = new System.Drawing.Size(200, 21); + this.nudEPSGClockFrequency.DecimalPlaces = 0; // - // chkSilenceTriangleHighFreq + // lblEPSGClockFrequency // - this.chkSilenceTriangleHighFreq.AutoSize = true; + this.lblEPSGClockFrequency.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblEPSGClockFrequency.AutoSize = true; + this.lblEPSGClockFrequency.Location = new System.Drawing.Point(163, 6); + this.lblEPSGClockFrequency.Name = "lblEPSGClockFrequency"; + this.lblEPSGClockFrequency.Size = new System.Drawing.Size(15, 13); + this.lblEPSGClockFrequency.TabIndex = 3; + this.lblEPSGClockFrequency.Text = "EPSG Clock Frequency (default is 3579545Hz)"; + // + // chkSilenceTriangleHighFreq + // + this.chkSilenceTriangleHighFreq.AutoSize = true; this.chkSilenceTriangleHighFreq.Location = new System.Drawing.Point(3, 3); this.chkSilenceTriangleHighFreq.Name = "chkSilenceTriangleHighFreq"; this.chkSilenceTriangleHighFreq.Size = new System.Drawing.Size(337, 17); @@ -1968,7 +2067,7 @@ namespace Mesen.GUI.Forms.Config // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(477, 402); + this.ClientSize = new System.Drawing.Size(477, 450); this.Controls.Add(this.tabMain); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.MaximizeBox = false; @@ -2051,7 +2150,9 @@ namespace Mesen.GUI.Forms.Config private Controls.ctrlTrackbar trkVrc7Vol; private Controls.ctrlTrackbar trkNamco163Vol; private Controls.ctrlTrackbar trkSunsoft5b; - private System.Windows.Forms.TabControl tabMain; + private Controls.ctrlTrackbar trkEPSGVol_L; + private Controls.ctrlTrackbar trkEPSGVol_R; + private System.Windows.Forms.TabControl tabMain; private System.Windows.Forms.TabPage tpgGeneral; private System.Windows.Forms.TabPage tpgVolume; private System.Windows.Forms.CheckBox chkReduceSoundInBackground; @@ -2091,6 +2192,8 @@ namespace Mesen.GUI.Forms.Config private Controls.ctrlHorizontalTrackbar trkVrc7Pan; private Controls.ctrlHorizontalTrackbar trkNamcoPan; private Controls.ctrlHorizontalTrackbar trkSunsoftPan; + private Controls.ctrlHorizontalTrackbar trkEPSGPan_L; + private Controls.ctrlHorizontalTrackbar trkEPSGPan_R; private Controls.ctrlHorizontalTrackbar trkSquare1Pan; private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel5; private System.Windows.Forms.CheckBox chkCrossFeedEnabled; @@ -2098,6 +2201,8 @@ namespace Mesen.GUI.Forms.Config private System.Windows.Forms.Label lblCrossFeedRatio; private Controls.ctrlHorizontalTrackbar trkTrianglePan; private ctrlRiskyOption chkDisableNoiseModeFlag; + private MesenNumericUpDown nudEPSGClockFrequency; + private System.Windows.Forms.Label lblEPSGClockFrequency; private System.Windows.Forms.TabPage tpgEqualizer; private System.Windows.Forms.TableLayoutPanel tlpEqualizer; private ctrlTrackbar trkBand6Gain; diff --git a/GUI.NET/Forms/Config/frmAudioConfig.cs b/GUI.NET/Forms/Config/frmAudioConfig.cs index 7045cc97..d9dd48d8 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.cs @@ -43,6 +43,8 @@ namespace Mesen.GUI.Forms.Config AddBinding("Vrc7Volume", trkVrc7Vol); AddBinding("Namco163Volume", trkNamco163Vol); AddBinding("Sunsoft5bVolume", trkSunsoft5b); + AddBinding("EPSGVolume_L", trkEPSGVol_L); + AddBinding("EPSGVolume_R", trkEPSGVol_R); AddBinding("Square1Panning", trkSquare1Pan); AddBinding("Square2Panning", trkSquare2Pan); @@ -55,6 +57,10 @@ namespace Mesen.GUI.Forms.Config AddBinding("Vrc7Panning", trkVrc7Pan); AddBinding("Namco163Panning", trkNamcoPan); AddBinding("Sunsoft5bPanning", trkSunsoftPan); + AddBinding("EPSGPanning_L", trkEPSGPan_L); + AddBinding("EPSGPanning_R", trkEPSGPan_R); + + AddBinding("EPSGClockFrequency", nudEPSGClockFrequency); AddBinding("AudioLatency", nudLatency); AddBinding("SampleRate", cboSampleRate); diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 5df81602..b559bdb8 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -208,6 +208,7 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void SetBandGain(int band, double gain); [DllImport(DLLPath)] public static extern void SetSampleRate(UInt32 sampleRate); [DllImport(DLLPath)] public static extern void SetAudioLatency(UInt32 msLatency); + [DllImport(DLLPath)] public static extern void SetEPSGClockFrequency(UInt32 clockFrequency); [DllImport(DLLPath)] public static extern void SetAudioFilterSettings(AudioFilterSettings settings); [DllImport(DLLPath)] public static extern void SetRunAheadFrames(UInt32 frameCount); @@ -2297,8 +2298,10 @@ namespace Mesen.GUI VRC6 = 7, VRC7 = 8, Namco163 = 9, - Sunsoft5B = 10 - } + Sunsoft5B = 10, + EPSG_L = 11, + EPSG_R= 12 + } public enum EqualizerFilterType { diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index b415d5f5..467f5018 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -633,6 +633,7 @@ namespace InteropEmu { DllExport void __stdcall SetMasterVolume(double volume, double volumeReduction, ConsoleId consoleId) { GetConsoleById(consoleId)->GetSettings()->SetMasterVolume(volume, volumeReduction); } DllExport void __stdcall SetSampleRate(uint32_t sampleRate) { _settings->SetSampleRate(sampleRate); } DllExport void __stdcall SetAudioLatency(uint32_t msLatency) { _settings->SetAudioLatency(msLatency); } + DllExport void __stdcall SetEPSGClockFrequency(uint32_t clockFrequency) { _settings->SetEPSGClockFrequency(clockFrequency); } DllExport void __stdcall SetAudioFilterSettings(AudioFilterSettings settings) { _settings->SetAudioFilterSettings(settings); } DllExport void __stdcall SetRunAheadFrames(uint32_t frameCount) { _settings->SetRunAheadFrames(frameCount); }