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); }