FDS: Sound support

This commit is contained in:
Souryo 2016-01-30 14:57:50 -05:00
parent 9bfa62129a
commit ea36115941
17 changed files with 1031 additions and 424 deletions

View file

@ -182,7 +182,7 @@ void APU::ExecStatic()
void APU::Exec()
{
_currentCycle++;
if(_currentCycle == 10000) {
if(_currentCycle == SoundMixer::CycleLength - 1) {
EndFrame();
} else if(NeedToRun(_currentCycle)) {
Run();
@ -234,4 +234,10 @@ void APU::StreamState(bool saving)
Stream(_deltaModulationChannel.get());
Stream(_frameCounter.get());
Stream(_mixer.get());
}
void APU::AddExpansionAudioDelta(AudioChannel channel, int8_t delta)
{
Instance->_mixer->SetExpansionAudioType(channel);
Instance->_mixer->AddExpansionAudioDelta(Instance->_currentCycle, delta);
}

View file

@ -4,6 +4,7 @@
#include "IMemoryHandler.h"
#include "IAudioDevice.h"
#include "Snapshotable.h"
#include "EmulationSettings.h"
class MemoryManager;
class SquareChannel;
@ -61,4 +62,6 @@ class APU : public Snapshotable, public IMemoryHandler
static void ExecStatic();
static void StaticRun();
static void AddExpansionAudioDelta(AudioChannel channel, int8_t delta);
};

91
Core/BaseFdsChannel.h Normal file
View file

@ -0,0 +1,91 @@
#pragma once
#include "stdafx.h"
#include "Snapshotable.h"
class BaseFdsChannel : public Snapshotable
{
protected:
uint8_t _speed = 0;
uint8_t _gain = 0;
bool _envelopeOff = false;
bool _volumeIncrease = false;
uint16_t _frequency = 0;
uint32_t _timer = 0;
uint8_t _masterSpeed = 0;
void StreamState(bool saving)
{
Stream<uint8_t>(_speed);
Stream<uint8_t>(_gain);
Stream<bool>(_envelopeOff);
Stream<bool>(_volumeIncrease);
Stream<uint16_t>(_frequency);
Stream<uint32_t>(_timer);
Stream<uint8_t>(_masterSpeed);
}
public:
void SetMasterEnvelopeSpeed(uint8_t masterSpeed)
{
_masterSpeed = masterSpeed;
}
virtual void WriteReg(uint16_t addr, uint8_t value)
{
switch(addr & 0x03) {
case 0:
_speed = value & 0x3F;
_volumeIncrease = (value & 0x40) == 0x40;
_envelopeOff = (value & 0x80) == 0x80;
//"Writing to this register immediately resets the clock timer that ticks the volume envelope (delaying the next tick slightly)."
_timer = 8 * (_speed + 1) * _masterSpeed;
break;
case 2:
_frequency = (_frequency & 0x0F00) | value;
break;
case 3:
_frequency = (_frequency & 0xFF) | ((value & 0x0F) << 8);
break;
}
}
void TickEnvelope(bool envelopesDisabled)
{
if(_envelopeOff) {
_gain = _speed;
} else if(!envelopesDisabled && _speed > 0) {
if(_timer > 0) {
_timer--;
} else {
_timer = 8 * (_speed + 1) * _masterSpeed;
if(_volumeIncrease) {
if(_gain < 32) {
_gain++;
}
} else {
if(_gain > 0) {
_gain--;
}
}
}
}
}
uint8_t GetGain()
{
return _gain;
}
uint16_t GetFrequency()
{
return _frequency;
}
};

View file

@ -359,6 +359,7 @@
<ClInclude Include="APU.h" />
<ClInclude Include="AutoRomTest.h" />
<ClInclude Include="Bandai74161_7432.h" />
<ClInclude Include="BaseFdsChannel.h" />
<ClInclude Include="BaseMapper.h" />
<ClInclude Include="BF9096.h" />
<ClInclude Include="BF909x.h" />
@ -367,6 +368,7 @@
<ClInclude Include="DefaultVideoFilter.h" />
<ClInclude Include="ExpressionEvaluator.h" />
<ClInclude Include="FDS.h" />
<ClInclude Include="FdsAudio.h" />
<ClInclude Include="FdsLoader.h" />
<ClInclude Include="HdVideoFilter.h" />
<ClInclude Include="iNesLoader.h" />
@ -404,6 +406,7 @@
<ClInclude Include="MMC3_49.h" />
<ClInclude Include="MMC3_52.h" />
<ClInclude Include="MMC3_ChrRam.h" />
<ClInclude Include="ModChannel.h" />
<ClInclude Include="RomData.h" />
<ClInclude Include="NtdecTc112.h" />
<ClInclude Include="Rambo1.h" />

View file

@ -524,6 +524,15 @@
<ClInclude Include="FDS.h">
<Filter>Nes\Mappers\FDS</Filter>
</ClInclude>
<ClInclude Include="FdsAudio.h">
<Filter>Nes\Mappers\FDS</Filter>
</ClInclude>
<ClInclude Include="BaseFdsChannel.h">
<Filter>Nes\Mappers\FDS</Filter>
</ClInclude>
<ClInclude Include="ModChannel.h">
<Filter>Nes\Mappers\FDS</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">

View file

@ -20,7 +20,7 @@ uint32_t EmulationSettings::PpuPaletteArgb[64] = {
uint32_t EmulationSettings::_flags = 0;
uint32_t EmulationSettings::_audioLatency = 20000;
double EmulationSettings::_channelVolume[5] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
double EmulationSettings::_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 EmulationSettings::_masterVolume = 1.0;
uint32_t EmulationSettings::_sampleRate = 44100;

View file

@ -23,7 +23,13 @@ enum class AudioChannel
Square2 = 1,
Triangle = 2,
Noise = 3,
DMC = 4
DMC = 4,
FDS = 5,
MMC5 = 6,
VRC6 = 7,
VRC7 = 8,
Namco163 = 9,
Sunsoft5B = 10
};
enum class NesModel
@ -70,7 +76,7 @@ private:
static uint32_t _flags;
static uint32_t _audioLatency;
static double _channelVolume[5];
static double _channelVolume[11];
static double _masterVolume;
static uint32_t _sampleRate;

View file

@ -1,4 +1,403 @@
#include "stdafx.h"
#include "FDS.h"
#include "FdsAudio.h"
FDS* FDS::Instance = nullptr;
FDS* FDS::Instance = nullptr;
void FDS::InitMapper()
{
_diskNumber = EmulationSettings::CheckFlag(EmulationFlags::FdsAutoLoadDisk) ? 0 : FDS::NoDiskInserted;
//FDS BIOS
SetCpuMemoryMapping(0xE000, 0xFFFF, 0, PrgMemoryType::PrgRom, MemoryAccessType::Read);
//Work RAM
SetCpuMemoryMapping(0x6000, 0xDFFF, 0, PrgMemoryType::WorkRam, MemoryAccessType::ReadWrite);
//8k of CHR RAM
SelectCHRPage(0, 0);
}
void FDS::InitMapper(RomData &romData)
{
_romFilepath = romData.Filename;
_fdsDiskSides = romData.FdsDiskData;
_fdsRawData = romData.RawData;
}
uint32_t FDS::GetFdsDiskSideSize(uint8_t side)
{
assert(side < _fdsDiskSides.size());
return (uint32_t)_fdsDiskSides[side].size();
}
uint8_t FDS::ReadFdsDisk()
{
assert(_diskNumber < _fdsDiskSides.size());
assert(_diskPosition < _fdsDiskSides[_diskNumber].size());
return _fdsDiskSides[_diskNumber][_diskPosition];
}
void FDS::WriteFdsDisk(uint8_t value)
{
assert(_diskNumber < _fdsDiskSides.size());
assert(_diskPosition < _fdsDiskSides[_diskNumber].size());
if(_fdsDiskSides[_diskNumber][_diskPosition - 2] != value) {
_isDirty = true;
}
_fdsDiskSides[_diskNumber][_diskPosition - 2] = value;
}
void FDS::ClockIrq()
{
if(_needIrq) {
CPU::SetIRQSource(IRQSource::External);
_needIrq = false;
}
if(_irqEnabled && _irqCounter > 0) {
_irqCounter--;
if(_irqCounter == 0) {
_needIrq = true;
if(_irqReloadEnabled) {
_irqCounter = _irqReloadValue;
} else {
_irqEnabled = false;
_irqReloadValue = 0;
}
}
}
}
void FDS::ProcessCpuClock()
{
ClockIrq();
_audio->Clock();
if(EmulationSettings::CheckFlag(EmulationFlags::FdsFastForwardOnLoad)) {
EmulationSettings::SetEmulationSpeed(_scanningDisk ? 0 : 100);
}
if(_newDiskInsertDelay > 0) {
//Insert new disk after delay expires, to allow games to notice the disk was ejected
_newDiskInsertDelay--;
_diskNumber = FDS::NoDiskInserted;
} else {
_diskNumber = _newDiskNumber;
}
if(_diskNumber == FDS::NoDiskInserted || !_motorOn) {
//Disk has been ejected
_endOfHead = true;
_scanningDisk = false;
return;
}
if(_resetTransfer && !_scanningDisk) {
return;
}
if(_endOfHead) {
_delay = 50000;
_endOfHead = false;
_diskPosition = 0;
_gapEnded = false;
return;
}
if(_delay > 0) {
_delay--;
} else {
_scanningDisk = true;
uint8_t diskData = 0;
bool needIrq = _diskIrqEnabled;
if(_readMode) {
diskData = ReadFdsDisk();
if(!_previousCrcControlFlag) {
UpdateCrc(diskData);
}
if(!_diskReady) {
_gapEnded = false;
_crcAccumulator = 0;
} else if(diskData && !_gapEnded) {
_gapEnded = true;
needIrq = false;
}
if(_gapEnded) {
_transferComplete = true;
_readDataReg = diskData;
if(needIrq) {
CPU::SetIRQSource(IRQSource::FdsDisk);
}
}
} else {
if(!_crcControl) {
_transferComplete = true;
diskData = _writeDataReg;
if(needIrq) {
CPU::SetIRQSource(IRQSource::FdsDisk);
}
}
if(!_diskReady) {
diskData = 0x00;
}
if(!_crcControl) {
UpdateCrc(diskData);
} else {
if(!_previousCrcControlFlag) {
//Finish CRC calculation
UpdateCrc(0x00);
UpdateCrc(0x00);
}
diskData = _crcAccumulator & 0xFF;
_crcAccumulator >>= 8;
}
WriteFdsDisk(diskData);
_gapEnded = false;
}
_previousCrcControlFlag = _crcControl;
_diskPosition++;
if(_diskPosition >= GetFdsDiskSideSize(_diskNumber)) {
_motorOn = false;
} else {
_delay = 150;
}
}
}
void FDS::UpdateCrc(uint8_t value)
{
for(uint16_t n = 0x01; n <= 0x80; n <<= 1) {
uint8_t carry = (_crcAccumulator & 1);
_crcAccumulator >>= 1;
if(carry) {
_crcAccumulator ^= 0x8408;
}
if(value & n) {
_crcAccumulator ^= 0x8000;
}
}
}
void FDS::WriteRegister(uint16_t addr, uint8_t value)
{
if(!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026 || !_soundRegEnabled && addr >= 0x4040) {
return;
}
switch(addr) {
case 0x4020:
_irqReloadValue = (_irqReloadValue & 0xFF00) | value;
CPU::ClearIRQSource(IRQSource::External);
break;
case 0x4021:
_irqReloadValue = (_irqReloadValue & 0x00FF) | (value << 8);
CPU::ClearIRQSource(IRQSource::External);
break;
case 0x4022:
_irqReloadEnabled = (value & 0x01) == 0x01;
_irqEnabled = (value & 0x02) == 0x02;
_irqCounter = _irqReloadValue;
CPU::ClearIRQSource(IRQSource::External);
break;
case 0x4023:
_diskRegEnabled = (value & 0x01) == 0x01;
_soundRegEnabled = (value & 0x02) == 0x02;
break;
case 0x4024:
_writeDataReg = value;
_transferComplete = false;
CPU::ClearIRQSource(IRQSource::FdsDisk);
break;
case 0x4025:
_motorOn = (value & 0x01) == 0x01;
_resetTransfer = (value & 0x02) == 0x02;
_readMode = (value & 0x04) == 0x04;
SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical);
_crcControl = (value & 0x10) == 0x10;
//Bit 6 is not used, always 1
_diskReady = (value & 0x40) == 0x40;
_diskIrqEnabled = (value & 0x80) == 0x80;
break;
case 0x4026:
_extConWriteReg = value;
break;
default:
if(addr >= 0x4040) {
_audio->WriteRegister(addr, value);
}
break;
}
}
bool FDS::IsDiskInserted()
{
return _diskNumber != FDS::NoDiskInserted;
}
uint8_t FDS::ReadRegister(uint16_t addr)
{
uint8_t value = 0;
if(_diskRegEnabled && addr < 0x4040 || _soundRegEnabled && addr >= 0x4040) {
switch(addr) {
case 0x4030:
value |= CPU::HasIRQSource(IRQSource::External) ? 0x01 : 0x00;
value |= _transferComplete ? 0x02 : 0x00;
value |= _badCrc ? 0x10 : 0x00;
//value |= _endOfHead ? 0x40 : 0x00;
//value |= _diskRegEnabled ? 0x80 : 0x00;
_transferComplete = false;
CPU::ClearIRQSource(IRQSource::External);
CPU::ClearIRQSource(IRQSource::FdsDisk);
return value;
case 0x4031:
_transferComplete = false;
CPU::ClearIRQSource(IRQSource::FdsDisk);
return _readDataReg;
case 0x4032:
value |= !IsDiskInserted() ? 0x01 : 0x00; //Disk not in drive
value |= !IsDiskInserted() || !_scanningDisk ? 0x02 : 0x00; //Disk not ready
value |= !IsDiskInserted() ? 0x04 : 0x00; //Disk not writable
return value;
case 0x4033:
//Always return good battery
return 0x80 & _extConWriteReg;
default:
return _audio->ReadRegister(addr);
}
}
//Return open bus
return (addr & 0xFF00) >> 8;
}
void FDS::StreamState(bool saving)
{
BaseMapper::StreamState(saving);
Stream<uint16_t>(_irqReloadValue);
Stream<uint16_t>(_irqCounter);
Stream<bool>(_irqEnabled);
Stream<bool>(_irqReloadEnabled);
Stream<bool>(_diskRegEnabled);
Stream<bool>(_soundRegEnabled);
Stream<uint8_t>(_writeDataReg);
Stream<bool>(_motorOn);
Stream<bool>(_resetTransfer);
Stream<bool>(_readMode);
Stream<bool>(_crcControl);
Stream<bool>(_diskReady);
Stream<bool>(_diskIrqEnabled);
Stream<uint8_t>(_extConWriteReg);
Stream<bool>(_badCrc);
Stream<bool>(_endOfHead);
Stream<bool>(_readWriteEnabled);
Stream<uint8_t>(_readDataReg);
Stream<bool>(_diskWriteProtected);
Stream<uint32_t>(_diskNumber);
Stream<uint32_t>(_newDiskNumber);
Stream<uint32_t>(_newDiskInsertDelay);
Stream<uint32_t>(_diskPosition);
Stream<uint32_t>(_delay);
Stream<bool>(_previousCrcControlFlag);
Stream<bool>(_gapEnded);
Stream<bool>(_scanningDisk);
Stream<bool>(_needIrq);
Stream<bool>(_transferComplete);
Stream<bool>(_isDirty);
}
FDS::FDS()
{
FDS::Instance = this;
_audio.reset(new FdsAudio());
}
FDS::~FDS()
{
if(_isDirty) {
FdsLoader loader;
loader.SaveIpsFile(_romFilepath, _fdsRawData, _fdsDiskSides);
}
if(FDS::Instance == this) {
FDS::Instance = nullptr;
}
}
uint32_t FDS::GetSideCount()
{
if(FDS::Instance) {
return (uint32_t)FDS::Instance->_fdsDiskSides.size();
} else {
return 0;
}
}
void FDS::InsertDisk(uint32_t diskNumber)
{
if(FDS::Instance) {
Console::Pause();
FDS::Instance->_newDiskNumber = diskNumber;
FDS::Instance->_newDiskInsertDelay = FDS::DiskInsertDelay;
Console::Resume();
MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged);
}
}
void FDS::SwitchDiskSide()
{
if(FDS::Instance) {
Console::Pause();
FDS::Instance->_newDiskNumber = (FDS::Instance->_diskNumber & 0x01) ? (FDS::Instance->_diskNumber & 0xFE) : (FDS::Instance->_diskNumber | 0x01);
FDS::Instance->_newDiskInsertDelay = FDS::DiskInsertDelay;
Console::Resume();
MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged);
}
}
void FDS::EjectDisk()
{
if(FDS::Instance) {
Console::Pause();
FDS::Instance->_diskNumber = NoDiskInserted;
FDS::Instance->_newDiskInsertDelay = 0;
Console::Resume();
MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged);
}
}

View file

@ -7,6 +7,8 @@
#include "FdsLoader.h"
#include "Console.h"
class FdsAudio;
class FDS : public BaseMapper
{
private:
@ -15,6 +17,8 @@ private:
static FDS* Instance;
unique_ptr<FdsAudio> _audio;
//Write registers
uint16_t _irqReloadValue = 0;
uint16_t _irqCounter = 0;
@ -71,390 +75,32 @@ protected:
uint16_t RegisterEndAddress() { return 0x4092; }
bool AllowRegisterRead() { return true; }
void InitMapper()
{
_diskNumber = EmulationSettings::CheckFlag(EmulationFlags::FdsAutoLoadDisk) ? 0 : FDS::NoDiskInserted;
void InitMapper();
void InitMapper(RomData &romData);
//FDS BIOS
SetCpuMemoryMapping(0xE000, 0xFFFF, 0, PrgMemoryType::PrgRom, MemoryAccessType::Read);
uint32_t GetFdsDiskSideSize(uint8_t side);
uint8_t ReadFdsDisk();
void WriteFdsDisk(uint8_t value);
//Work RAM
SetCpuMemoryMapping(0x6000, 0xDFFF, 0, PrgMemoryType::WorkRam, MemoryAccessType::ReadWrite);
void ClockIrq();
//8k of CHR RAM
SelectCHRPage(0, 0);
}
void ProcessCpuClock();
void UpdateCrc(uint8_t value);
void InitMapper(RomData &romData)
{
_romFilepath = romData.Filename;
_fdsDiskSides = romData.FdsDiskData;
_fdsRawData = romData.RawData;
}
bool IsDiskInserted();
uint32_t GetFdsDiskSideSize(uint8_t side)
{
assert(side < _fdsDiskSides.size());
return (uint32_t)_fdsDiskSides[side].size();
}
void WriteRegister(uint16_t addr, uint8_t value);
uint8_t ReadRegister(uint16_t addr);
uint8_t ReadFdsDisk()
{
assert(_diskNumber < _fdsDiskSides.size());
assert(_diskPosition < _fdsDiskSides[_diskNumber].size());
return _fdsDiskSides[_diskNumber][_diskPosition];
}
void WriteFdsDisk(uint8_t value)
{
assert(_diskNumber < _fdsDiskSides.size());
assert(_diskPosition < _fdsDiskSides[_diskNumber].size());
if(_fdsDiskSides[_diskNumber][_diskPosition - 2] != value) {
_isDirty = true;
}
_fdsDiskSides[_diskNumber][_diskPosition - 2] = value;
}
void ClockIrq()
{
if(_needIrq) {
CPU::SetIRQSource(IRQSource::External);
_needIrq = false;
}
if(_irqEnabled && _irqCounter > 0) {
_irqCounter--;
if(_irqCounter == 0) {
_needIrq = true;
if(_irqReloadEnabled) {
_irqCounter = _irqReloadValue;
} else {
_irqEnabled = false;
_irqReloadValue = 0;
}
}
}
}
void ProcessCpuClock()
{
ClockIrq();
if(EmulationSettings::CheckFlag(EmulationFlags::FdsFastForwardOnLoad)) {
EmulationSettings::SetEmulationSpeed(_scanningDisk ? 0 : 100);
}
if(_newDiskInsertDelay > 0) {
//Insert new disk after delay expires, to allow games to notice the disk was ejected
_newDiskInsertDelay--;
_diskNumber = FDS::NoDiskInserted;
} else {
_diskNumber = _newDiskNumber;
}
if(_diskNumber == FDS::NoDiskInserted || !_motorOn) {
//Disk has been ejected
_endOfHead = true;
_scanningDisk = false;
return;
}
if(_resetTransfer && !_scanningDisk) {
return;
}
if(_endOfHead) {
_delay = 50000;
_endOfHead = false;
_diskPosition = 0;
_gapEnded = false;
return;
}
if(_delay > 0) {
_delay--;
} else {
_scanningDisk = true;
uint8_t diskData = 0;
bool needIrq = _diskIrqEnabled;
if(_readMode) {
diskData = ReadFdsDisk();
if(!_previousCrcControlFlag) {
UpdateCrc(diskData);
}
if(!_diskReady) {
_gapEnded = false;
_crcAccumulator = 0;
} else if(diskData && !_gapEnded) {
_gapEnded = true;
needIrq = false;
}
if(_gapEnded) {
_transferComplete = true;
_readDataReg = diskData;
if(needIrq) {
CPU::SetIRQSource(IRQSource::FdsDisk);
}
}
} else {
if(!_crcControl) {
_transferComplete = true;
diskData = _writeDataReg;
if(needIrq) {
CPU::SetIRQSource(IRQSource::FdsDisk);
}
}
if(!_diskReady) {
diskData = 0x00;
}
if(!_crcControl) {
UpdateCrc(diskData);
} else {
if(!_previousCrcControlFlag) {
//Finish CRC calculation
UpdateCrc(0x00);
UpdateCrc(0x00);
}
diskData = _crcAccumulator & 0xFF;
_crcAccumulator >>= 8;
}
WriteFdsDisk(diskData);
_gapEnded = false;
}
_previousCrcControlFlag = _crcControl;
_diskPosition++;
if(_diskPosition >= GetFdsDiskSideSize(_diskNumber)) {
_motorOn = false;
} else {
_delay = 150;
}
}
}
void UpdateCrc(uint8_t value)
{
for(uint16_t n = 0x01; n <= 0x80; n <<= 1) {
uint8_t carry = (_crcAccumulator & 1);
_crcAccumulator >>= 1;
if(carry) {
_crcAccumulator ^= 0x8408;
}
if(value & n) {
_crcAccumulator ^= 0x8000;
}
}
}
void WriteRegister(uint16_t addr, uint8_t value)
{
if(!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) {
return;
}
switch(addr) {
case 0x4020:
_irqReloadValue = (_irqReloadValue & 0xFF00) | value;
CPU::ClearIRQSource(IRQSource::External);
break;
case 0x4021:
_irqReloadValue = (_irqReloadValue & 0x00FF) | (value << 8);
CPU::ClearIRQSource(IRQSource::External);
break;
case 0x4022:
_irqReloadEnabled = (value & 0x01) == 0x01;
_irqEnabled = (value & 0x02) == 0x02;
_irqCounter = _irqReloadValue;
CPU::ClearIRQSource(IRQSource::External);
break;
case 0x4023:
_diskRegEnabled = (value & 0x01) == 0x01;
_soundRegEnabled = (value & 0x02) == 0x02;
break;
case 0x4024:
_writeDataReg = value;
_transferComplete = false;
CPU::ClearIRQSource(IRQSource::FdsDisk);
break;
case 0x4025:
_motorOn = (value & 0x01) == 0x01;
_resetTransfer = (value & 0x02) == 0x02;
_readMode = (value & 0x04) == 0x04;
SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical);
_crcControl = (value & 0x10) == 0x10;
//Bit 6 is not used, always 1
_diskReady = (value & 0x40) == 0x40;
_diskIrqEnabled = (value & 0x80) == 0x80;
break;
case 0x4026:
_extConWriteReg = value;
break;
}
}
bool IsDiskInserted()
{
return _diskNumber != FDS::NoDiskInserted;
}
uint8_t ReadRegister(uint16_t addr)
{
uint8_t value = 0;
if(_diskRegEnabled) {
switch(addr) {
case 0x4030:
value |= CPU::HasIRQSource(IRQSource::External) ? 0x01 : 0x00;
value |= _transferComplete ? 0x02 : 0x00;
value |= _badCrc ? 0x10 : 0x00;
//value |= _endOfHead ? 0x40 : 0x00;
//value |= _diskRegEnabled ? 0x80 : 0x00;
_transferComplete = false;
CPU::ClearIRQSource(IRQSource::External);
CPU::ClearIRQSource(IRQSource::FdsDisk);
return value;
case 0x4031:
_transferComplete = false;
CPU::ClearIRQSource(IRQSource::FdsDisk);
return _readDataReg;
case 0x4032:
value |= !IsDiskInserted() ? 0x01 : 0x00; //Disk not in drive
value |= !IsDiskInserted() || !_scanningDisk ? 0x02 : 0x00; //Disk not ready
value |= !IsDiskInserted() ? 0x04 : 0x00; //Disk not writable
return value;
case 0x4033:
//Always return good battery
return 0x80 & _extConWriteReg;
}
}
//Return open bus
return (addr & 0xFF00) >> 8;
}
void StreamState(bool saving)
{
BaseMapper::StreamState(saving);
Stream<uint16_t>(_irqReloadValue);
Stream<uint16_t>(_irqCounter);
Stream<bool>(_irqEnabled);
Stream<bool>(_irqReloadEnabled);
Stream<bool>(_diskRegEnabled);
Stream<bool>(_soundRegEnabled);
Stream<uint8_t>(_writeDataReg);
Stream<bool>(_motorOn);
Stream<bool>(_resetTransfer);
Stream<bool>(_readMode);
Stream<bool>(_crcControl);
Stream<bool>(_diskReady);
Stream<bool>(_diskIrqEnabled);
Stream<uint8_t>(_extConWriteReg);
Stream<bool>(_badCrc);
Stream<bool>(_endOfHead);
Stream<bool>(_readWriteEnabled);
Stream<uint8_t>(_readDataReg);
Stream<bool>(_diskWriteProtected);
Stream<uint32_t>(_diskNumber);
Stream<uint32_t>(_newDiskNumber);
Stream<uint32_t>(_newDiskInsertDelay);
Stream<uint32_t>(_diskPosition);
Stream<uint32_t>(_delay);
Stream<bool>(_previousCrcControlFlag);
Stream<bool>(_gapEnded);
Stream<bool>(_scanningDisk);
Stream<bool>(_needIrq);
Stream<bool>(_transferComplete);
Stream<bool>(_isDirty);
}
void StreamState(bool saving);
public:
FDS()
{
FDS::Instance = this;
}
FDS();
~FDS();
~FDS()
{
if(_isDirty) {
FdsLoader loader;
loader.SaveIpsFile(_romFilepath, _fdsRawData, _fdsDiskSides);
}
static uint32_t GetSideCount();
if(FDS::Instance == this) {
FDS::Instance = nullptr;
}
}
static uint32_t GetSideCount()
{
if(FDS::Instance) {
return (uint32_t)FDS::Instance->_fdsDiskSides.size();
} else {
return 0;
}
}
static void InsertDisk(uint32_t diskNumber)
{
if(FDS::Instance) {
Console::Pause();
FDS::Instance->_newDiskNumber = diskNumber;
FDS::Instance->_newDiskInsertDelay = FDS::DiskInsertDelay;
Console::Resume();
MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged);
}
}
static void SwitchDiskSide()
{
if(FDS::Instance) {
Console::Pause();
FDS::Instance->_newDiskNumber = (FDS::Instance->_diskNumber & 0x01) ? (FDS::Instance->_diskNumber & 0xFE) : (FDS::Instance->_diskNumber | 0x01);
FDS::Instance->_newDiskInsertDelay = FDS::DiskInsertDelay;
Console::Resume();
MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged);
}
}
static void EjectDisk()
{
if(FDS::Instance) {
Console::Pause();
FDS::Instance->_diskNumber = NoDiskInserted;
FDS::Instance->_newDiskInsertDelay = 0;
Console::Resume();
MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged);
}
}
static void InsertDisk(uint32_t diskNumber);
static void SwitchDiskSide();
static void EjectDisk();
};

151
Core/FdsAudio.h Normal file
View file

@ -0,0 +1,151 @@
#pragma once
#include "stdafx.h"
#include "Snapshotable.h"
#include "EmulationSettings.h"
#include "APU.h"
#include "BaseFdsChannel.h"
#include "ModChannel.h"
#include <algorithm>
class FdsAudio : public Snapshotable
{
private:
const uint32_t WaveVolumeTable[4] = { 36, 24, 17, 14 };
//Register values
uint8_t _waveTable[64];
bool _waveWriteEnabled = false;
BaseFdsChannel _volume;
ModChannel _mod;
bool _disableEnvelopes = false;
bool _haltWaveform = false;
uint8_t _masterVolume = 0;
//Internal values
uint16_t _waveOverflowCounter = 0;
int32_t _wavePitch = 0;
uint8_t _wavePosition = 0;
uint8_t _lastOutput = 0;
protected:
void StreamState(bool saving)
{
Stream(&_volume);
Stream(&_mod);
StreamArray<uint8_t>(_waveTable, 64);
Stream<bool>(_waveWriteEnabled);
Stream<bool>(_disableEnvelopes);
Stream<bool>(_haltWaveform);
Stream<uint8_t>(_masterVolume);
//Internal values
Stream<uint16_t>(_waveOverflowCounter);
Stream<int32_t>(_wavePitch);
Stream<uint8_t>(_wavePosition);
Stream<uint8_t>(_lastOutput);
}
public:
void Clock()
{
//"The envelopes are not ticked while the waveform is halted."
_volume.TickEnvelope(_disableEnvelopes || _haltWaveform);
_mod.TickEnvelope(_disableEnvelopes || _haltWaveform);
if(_mod.IsEnabled()) {
if(_mod.TickModulator()) {
//Modulator was ticked, update wave pitch
_wavePitch = _mod.GetWavePitch(_volume.GetFrequency());
}
} else {
_wavePitch = 0;
}
if(_haltWaveform) {
//"The high bit of this register halts the waveform and resets its phase to 0. Note that if halted it will output the constant value at $4040"
//"writes to the volume register $4080 or master volume $4089 will affect the output."
_wavePosition = 0;
}
int32_t freq = _volume.GetFrequency() + _wavePitch;
if(freq > 0 && !_waveWriteEnabled) {
_waveOverflowCounter += freq;
if(_waveOverflowCounter < freq) {
//Overflow, tick
uint32_t level = std::min((int)_volume.GetGain(), 32) * WaveVolumeTable[_masterVolume];
uint8_t outputLevel = (_waveTable[_wavePosition] * level) / 1152;
APU::AddExpansionAudioDelta(AudioChannel::FDS, outputLevel - _lastOutput);
_lastOutput = outputLevel;
_wavePosition = (_wavePosition + 1) & 0x3F;
}
}
}
uint8_t ReadRegister(uint16_t addr)
{
if(addr <= 0x407F) {
return 0x40 | _waveTable[addr & 0x3F];
} else {
switch(addr) {
case 0x4090: return 0x40 | _volume.GetGain();
case 0x4092: return 0x40 | _mod.GetGain();
}
}
//Open bus
return (addr >> 8);
}
void WriteRegister(uint16_t addr, uint8_t value)
{
if(addr <= 0x407F) {
if(_waveWriteEnabled) {
_waveTable[addr & 0x3F] = value & 0x3F;
}
} else {
switch(addr) {
case 0x4080:
case 0x4082:
_volume.WriteReg(addr, value);
break;
case 0x4083:
_disableEnvelopes = (value & 0x40) == 0x40;
_haltWaveform = (value & 0x80) == 0x80;
_volume.WriteReg(addr, value);
break;
case 0x4084:
case 0x4085:
case 0x4086:
case 0x4087:
_mod.WriteReg(addr, value);
break;
case 0x4088:
_mod.WriteModTable(value);
break;
case 0x4089:
_masterVolume = value & 0x03;
_waveWriteEnabled = (value & 0x80) == 0x80;
break;
case 0x408A:
_volume.SetMasterEnvelopeSpeed(value);
_mod.SetMasterEnvelopeSpeed(value);
break;
}
}
}
};

117
Core/ModChannel.h Normal file
View file

@ -0,0 +1,117 @@
#pragma once
#include "stdafx.h"
#include "BaseFdsChannel.h"
class ModChannel : public BaseFdsChannel
{
private:
const int32_t ModReset = 0xFF;
const int32_t _modLut[8] = { 0,1,2,4,ModReset,-4,-2,-1 };
int8_t _counter = 0;
bool _modulationDisabled = false;
uint8_t _modTable[64];
uint8_t _modTablePosition = 0;
uint16_t _overflowCounter = 0;
protected:
void StreamState(bool saving)
{
BaseFdsChannel::StreamState(saving);
Stream<int8_t>(_counter);
Stream<bool>(_modulationDisabled);
Stream<uint8_t>(_modTablePosition);
Stream<uint16_t>(_overflowCounter);
StreamArray<uint8_t>(_modTable, 64);
}
public:
virtual void WriteReg(uint16_t addr, uint8_t value)
{
switch(addr & 0x03) {
case 1: UpdateCounter(value & 0x7F); break;
case 3: _modulationDisabled = (value & 0x80) == 0x80; break;
}
BaseFdsChannel::WriteReg(addr, value);
}
void WriteModTable(uint8_t value)
{
//"This register has no effect unless the mod unit is disabled via the high bit of $4087."
if(_modulationDisabled) {
_modTable[_modTablePosition & 0x3F] = value & 0x07;
_modTable[(_modTablePosition + 1) & 0x3F] = value & 0x07;
_modTablePosition = (_modTablePosition + 2) & 0x3F;
}
}
void UpdateCounter(int8_t value)
{
_counter = value;
if(_counter >= 64) {
_counter -= 128;
} else if(_counter < -64) {
_counter += 128;
}
}
bool IsEnabled()
{
return !_modulationDisabled && _frequency > 0;
}
bool TickModulator()
{
_overflowCounter += _frequency;
if(_overflowCounter < _frequency) {
//Overflowed, tick the modulator
int32_t offset = _modLut[_modTable[_modTablePosition]];
UpdateCounter(offset == ModReset ? 0 : _counter + offset);
_modTablePosition = (_modTablePosition + 1) & 0x3F;
return true;
}
return false;
}
int32_t GetWavePitch(uint16_t volumePitch)
{
//Code from NesDev Wiki
// pitch = $4082/4083 (12-bit unsigned pitch value)
// counter = $4085 (7-bit signed mod counter)
// gain = $4084 (6-bit unsigned mod gain)
// 1. multiply counter by gain, lose lowest 4 bits of result but "round" in a strange way
int32_t temp = _counter * _gain;
int32_t remainder = temp & 0xF;
temp >>= 4;
if(remainder > 0 && (temp & 0x80) == 0) {
temp += _counter < 0 ? -1 : 2;
}
// 2. wrap if a certain range is exceeded
if(temp >= 192) {
temp -= 256;
} else if(temp < -64) {
temp += 256;
}
// 3. multiply result by pitch, then round to nearest while dropping 6 bits
temp = volumePitch * temp;
remainder = temp & 0x3F;
temp >>= 6;
if(remainder >= 32) {
temp += 1;
}
// final mod result is in temp
return temp;
}
};

View file

@ -27,6 +27,7 @@ void SoundMixer::StreamState(bool saving)
{
Stream<uint32_t>(_clockRate);
Stream<uint32_t>(_sampleRate);
Stream<AudioChannel>(_expansionAudioType);
if(!saving) {
Reset();
@ -34,7 +35,7 @@ void SoundMixer::StreamState(bool saving)
}
Stream<int16_t>(_previousOutput);
StreamArray<int8_t>(_currentOutput, 5);
StreamArray<int8_t>(_currentOutput, MaxChannelCount);
}
void SoundMixer::RegisterAudioDevice(IAudioDevice *audioDevice)
@ -60,7 +61,7 @@ void SoundMixer::Reset()
_timestamps.clear();
for(int i = 0; i < 5; i++) {
for(int i = 0; i < MaxChannelCount; i++) {
_volumes[0] = 0;
}
memset(_channelOutput, 0, sizeof(_channelOutput));
@ -110,13 +111,32 @@ int16_t SoundMixer::GetOutputVolume()
{
int16_t squareOutput = _lupSquare[(int)(_currentOutput[(int)AudioChannel::Square1] * _volumes[(int)AudioChannel::Square1] + _currentOutput[(int)AudioChannel::Square2] * _volumes[(int)AudioChannel::Square2])];
int16_t tndOutput = _lupTnd[(int)(3 * _currentOutput[(int)AudioChannel::Triangle] * _volumes[(int)AudioChannel::Triangle] + 2 * _currentOutput[(int)AudioChannel::Noise] * _volumes[(int)AudioChannel::Noise] + _currentOutput[(int)AudioChannel::DMC] * _volumes[(int)AudioChannel::DMC])];
return squareOutput + tndOutput;
int16_t expansionOutput = 0;
switch(_expansionAudioType) {
case AudioChannel::FDS: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 20); break;
}
return squareOutput + tndOutput + expansionOutput;
}
void SoundMixer::AddDelta(AudioChannel channel, uint32_t time, int8_t delta)
{
_timestamps.push_back(time);
_channelOutput[(int)channel][time] += delta;
if(delta != 0) {
_timestamps.push_back(time);
_channelOutput[(int)channel][time] += delta;
}
}
void SoundMixer::AddExpansionAudioDelta(uint32_t time, int8_t delta)
{
if(delta != 0) {
_timestamps.push_back(time);
_channelOutput[ExpansionAudioIndex][time] += delta;
}
}
void SoundMixer::SetExpansionAudioType(AudioChannel channel)
{
_expansionAudioType = channel;
}
void SoundMixer::EndFrame(uint32_t time)
@ -127,11 +147,9 @@ void SoundMixer::EndFrame(uint32_t time)
for(size_t i = 0, len = _timestamps.size(); i < len; i++) {
uint32_t time = _timestamps[i];
_currentOutput[0] += _channelOutput[0][time];
_currentOutput[1] += _channelOutput[1][time];
_currentOutput[2] += _channelOutput[2][time];
_currentOutput[3] += _channelOutput[3][time];
_currentOutput[4] += _channelOutput[4][time];
for(int j = 0; j < MaxChannelCount; j++) {
_currentOutput[j] += _channelOutput[j][time];
}
int16_t currentOutput = GetOutputVolume();
blip_add_delta(_blipBuf, time, (int)((currentOutput - _previousOutput) * masterVolume));
@ -140,8 +158,8 @@ void SoundMixer::EndFrame(uint32_t time)
blip_end_frame(_blipBuf, time);
//Reset everything
for(int i = 0; i < 5; i++) {
_volumes[i] = EmulationSettings::GetChannelVolume((AudioChannel)i);
for(int i = 0; i < MaxChannelCount; i++) {
_volumes[i] = EmulationSettings::GetChannelVolume(i < 5 ? (AudioChannel)i : _expansionAudioType);
}
_timestamps.clear();

View file

@ -7,23 +7,31 @@
class SoundMixer : public Snapshotable
{
public:
static const uint32_t CycleLength = 10000;
static const uint32_t BitsPerSample = 16;
private:
static IAudioDevice* AudioDevice;
static const uint32_t MaxSampleRate = 48000;
static const uint32_t MaxSamplesPerFrame = MaxSampleRate / 60;
static const uint32_t MaxChannelCount = 6;
static const uint32_t ExpansionAudioIndex = MaxChannelCount - 1;
AudioChannel _expansionAudioType;
int16_t _previousOutput = 0;
vector<uint32_t> _timestamps;
int8_t _channelOutput[5][15000];
int8_t _currentOutput[5];
int8_t _channelOutput[MaxChannelCount][CycleLength];
int8_t _currentOutput[MaxChannelCount];
int16_t _lupSquare[31];
int16_t _lupTnd[203];
blip_t* _blipBuf;
int16_t *_outputBuffer;
double _volumes[5];
double _volumes[MaxChannelCount];
uint32_t _sampleRate;
uint32_t _clockRate;
@ -38,8 +46,6 @@ protected:
virtual void StreamState(bool saving);
public:
static const uint32_t BitsPerSample = 16;
SoundMixer();
~SoundMixer();
@ -47,7 +53,10 @@ public:
void Reset();
void PlayAudioBuffer(uint32_t cycle);
void AddDelta(AudioChannel channel, uint32_t time, int8_t output);
void AddDelta(AudioChannel channel, uint32_t time, int8_t delta);
void SetExpansionAudioType(AudioChannel channel);
void AddExpansionAudioDelta(uint32_t time, int8_t delta);
static void StopAudio(bool clearBuffer = false);
static void RegisterAudioDevice(IAudioDevice *audioDevice);

View file

@ -20,6 +20,12 @@ namespace Mesen.GUI.Config
public UInt32 TriangleVolume = 100;
public UInt32 NoiseVolume = 100;
public UInt32 DmcVolume = 100;
public UInt32 FdsVolume = 100;
public UInt32 Mmc5Volume = 100;
public UInt32 Vrc6Volume = 100;
public UInt32 Vrc7Volume = 100;
public UInt32 Namco163Volume = 100;
public UInt32 Sunsoft5bVolume = 100;
public UInt32 SampleRate = 44100;
public AudioInfo()
@ -41,11 +47,17 @@ namespace Mesen.GUI.Config
InteropEmu.SetAudioDevice(audioInfo.AudioDevice);
InteropEmu.SetAudioLatency(audioInfo.AudioLatency);
InteropEmu.SetMasterVolume(audioInfo.MasterVolume / 10d);
InteropEmu.SetChannelVolume(0, ConvertVolume(audioInfo.Square1Volume));
InteropEmu.SetChannelVolume(1, ConvertVolume(audioInfo.Square2Volume));
InteropEmu.SetChannelVolume(2, ConvertVolume(audioInfo.TriangleVolume));
InteropEmu.SetChannelVolume(3, ConvertVolume(audioInfo.NoiseVolume));
InteropEmu.SetChannelVolume(4, ConvertVolume(audioInfo.DmcVolume));
InteropEmu.SetChannelVolume(AudioChannel.Square1, ConvertVolume(audioInfo.Square1Volume));
InteropEmu.SetChannelVolume(AudioChannel.Square2, ConvertVolume(audioInfo.Square2Volume));
InteropEmu.SetChannelVolume(AudioChannel.Triangle, ConvertVolume(audioInfo.TriangleVolume));
InteropEmu.SetChannelVolume(AudioChannel.Noise, ConvertVolume(audioInfo.NoiseVolume));
InteropEmu.SetChannelVolume(AudioChannel.DMC, ConvertVolume(audioInfo.DmcVolume));
InteropEmu.SetChannelVolume(AudioChannel.FDS, ConvertVolume(audioInfo.FdsVolume));
InteropEmu.SetChannelVolume(AudioChannel.MMC5, ConvertVolume(audioInfo.FdsVolume));
InteropEmu.SetChannelVolume(AudioChannel.VRC6, ConvertVolume(audioInfo.FdsVolume));
InteropEmu.SetChannelVolume(AudioChannel.VRC7, ConvertVolume(audioInfo.FdsVolume));
InteropEmu.SetChannelVolume(AudioChannel.Namco163, ConvertVolume(audioInfo.FdsVolume));
InteropEmu.SetChannelVolume(AudioChannel.Sunsoft5B, ConvertVolume(audioInfo.FdsVolume));
InteropEmu.SetSampleRate(audioInfo.SampleRate);
}
}

View file

@ -35,6 +35,12 @@
this.trkSquare2Vol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkSquare1Vol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkMaster = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkFdsVol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkMmc5Vol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkVrc6Vol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkVrc7Vol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkNamco163Vol = new Mesen.GUI.Controls.ctrlTrackbar();
this.trkSunsoft5b = new Mesen.GUI.Controls.ctrlTrackbar();
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
this.chkEnableAudio = new System.Windows.Forms.CheckBox();
this.lblSampleRate = new System.Windows.Forms.Label();
@ -57,8 +63,8 @@
// baseConfigPanel
//
this.baseConfigPanel.Controls.Add(this.btnReset);
this.baseConfigPanel.Location = new System.Drawing.Point(0, 295);
this.baseConfigPanel.Size = new System.Drawing.Size(470, 29);
this.baseConfigPanel.Location = new System.Drawing.Point(0, 457);
this.baseConfigPanel.Size = new System.Drawing.Size(464, 29);
this.baseConfigPanel.Controls.SetChildIndex(this.btnReset, 0);
//
// grpVolume
@ -67,7 +73,7 @@
this.grpVolume.Controls.Add(this.tableLayoutPanel1);
this.grpVolume.Location = new System.Drawing.Point(3, 107);
this.grpVolume.Name = "grpVolume";
this.grpVolume.Size = new System.Drawing.Size(462, 185);
this.grpVolume.Size = new System.Drawing.Size(462, 351);
this.grpVolume.TabIndex = 2;
this.grpVolume.TabStop = false;
this.grpVolume.Text = "Volume";
@ -87,19 +93,27 @@
this.tableLayoutPanel1.Controls.Add(this.trkSquare2Vol, 2, 0);
this.tableLayoutPanel1.Controls.Add(this.trkSquare1Vol, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.trkMaster, 0, 0);
this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 19);
this.tableLayoutPanel1.Controls.Add(this.trkFdsVol, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.trkMmc5Vol, 1, 1);
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.Location = new System.Drawing.Point(3, 16);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 160F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(451, 160);
this.tableLayoutPanel1.RowCount = 3;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(456, 332);
this.tableLayoutPanel1.TabIndex = 2;
//
// trkDmcVol
//
this.trkDmcVol.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkDmcVol.Caption = "DMC";
this.trkDmcVol.Location = new System.Drawing.Point(381, 0);
this.trkDmcVol.Location = new System.Drawing.Point(384, 0);
this.trkDmcVol.Margin = new System.Windows.Forms.Padding(0);
this.trkDmcVol.Maximum = 100;
this.trkDmcVol.MaximumSize = new System.Drawing.Size(63, 160);
@ -185,6 +199,101 @@
this.trkMaster.Value = 50;
this.trkMaster.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// trkFdsVol
//
this.trkFdsVol.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkFdsVol.Caption = "FDS";
this.trkFdsVol.Location = new System.Drawing.Point(6, 160);
this.trkFdsVol.Margin = new System.Windows.Forms.Padding(0);
this.trkFdsVol.Maximum = 100;
this.trkFdsVol.MaximumSize = new System.Drawing.Size(63, 160);
this.trkFdsVol.MinimumSize = new System.Drawing.Size(63, 160);
this.trkFdsVol.Name = "trkFdsVol";
this.trkFdsVol.Size = new System.Drawing.Size(63, 160);
this.trkFdsVol.TabIndex = 17;
this.trkFdsVol.Value = 50;
this.trkFdsVol.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// trkMmc5Vol
//
this.trkMmc5Vol.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkMmc5Vol.Caption = "MMC5";
this.trkMmc5Vol.Enabled = false;
this.trkMmc5Vol.Location = new System.Drawing.Point(81, 160);
this.trkMmc5Vol.Margin = new System.Windows.Forms.Padding(0);
this.trkMmc5Vol.Maximum = 100;
this.trkMmc5Vol.MaximumSize = new System.Drawing.Size(63, 160);
this.trkMmc5Vol.MinimumSize = new System.Drawing.Size(63, 160);
this.trkMmc5Vol.Name = "trkMmc5Vol";
this.trkMmc5Vol.Size = new System.Drawing.Size(63, 160);
this.trkMmc5Vol.TabIndex = 18;
this.trkMmc5Vol.Value = 50;
this.trkMmc5Vol.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// trkVrc6Vol
//
this.trkVrc6Vol.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkVrc6Vol.Caption = "VRC6";
this.trkVrc6Vol.Enabled = false;
this.trkVrc6Vol.Location = new System.Drawing.Point(156, 160);
this.trkVrc6Vol.Margin = new System.Windows.Forms.Padding(0);
this.trkVrc6Vol.Maximum = 100;
this.trkVrc6Vol.MaximumSize = new System.Drawing.Size(63, 160);
this.trkVrc6Vol.MinimumSize = new System.Drawing.Size(63, 160);
this.trkVrc6Vol.Name = "trkVrc6Vol";
this.trkVrc6Vol.Size = new System.Drawing.Size(63, 160);
this.trkVrc6Vol.TabIndex = 19;
this.trkVrc6Vol.Value = 50;
this.trkVrc6Vol.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// trkVrc7Vol
//
this.trkVrc7Vol.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkVrc7Vol.Caption = "VRC7";
this.trkVrc7Vol.Enabled = false;
this.trkVrc7Vol.Location = new System.Drawing.Point(231, 160);
this.trkVrc7Vol.Margin = new System.Windows.Forms.Padding(0);
this.trkVrc7Vol.Maximum = 100;
this.trkVrc7Vol.MaximumSize = new System.Drawing.Size(63, 160);
this.trkVrc7Vol.MinimumSize = new System.Drawing.Size(63, 160);
this.trkVrc7Vol.Name = "trkVrc7Vol";
this.trkVrc7Vol.Size = new System.Drawing.Size(63, 160);
this.trkVrc7Vol.TabIndex = 20;
this.trkVrc7Vol.Value = 50;
this.trkVrc7Vol.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// trkNamco163Vol
//
this.trkNamco163Vol.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkNamco163Vol.Caption = "Namco";
this.trkNamco163Vol.Enabled = false;
this.trkNamco163Vol.Location = new System.Drawing.Point(306, 160);
this.trkNamco163Vol.Margin = new System.Windows.Forms.Padding(0);
this.trkNamco163Vol.Maximum = 100;
this.trkNamco163Vol.MaximumSize = new System.Drawing.Size(63, 160);
this.trkNamco163Vol.MinimumSize = new System.Drawing.Size(63, 160);
this.trkNamco163Vol.Name = "trkNamco163Vol";
this.trkNamco163Vol.Size = new System.Drawing.Size(63, 160);
this.trkNamco163Vol.TabIndex = 21;
this.trkNamco163Vol.Value = 50;
this.trkNamco163Vol.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// trkSunsoft5b
//
this.trkSunsoft5b.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.trkSunsoft5b.Caption = "Sunsoft";
this.trkSunsoft5b.Enabled = false;
this.trkSunsoft5b.Location = new System.Drawing.Point(384, 160);
this.trkSunsoft5b.Margin = new System.Windows.Forms.Padding(0);
this.trkSunsoft5b.Maximum = 100;
this.trkSunsoft5b.MaximumSize = new System.Drawing.Size(63, 160);
this.trkSunsoft5b.MinimumSize = new System.Drawing.Size(63, 160);
this.trkSunsoft5b.Name = "trkSunsoft5b";
this.trkSunsoft5b.Size = new System.Drawing.Size(63, 160);
this.trkSunsoft5b.TabIndex = 22;
this.trkSunsoft5b.Value = 50;
this.trkSunsoft5b.ValueChanged += new System.EventHandler(this.AudioConfig_ValueChanged);
//
// tableLayoutPanel2
//
this.tableLayoutPanel2.ColumnCount = 2;
@ -195,9 +304,9 @@
this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel2, 1, 3);
this.tableLayoutPanel2.Controls.Add(this.lblAudioLatency, 0, 3);
this.tableLayoutPanel2.Controls.Add(this.cboSampleRate, 1, 2);
this.tableLayoutPanel2.Controls.Add(this.grpVolume, 0, 4);
this.tableLayoutPanel2.Controls.Add(this.lblAudioDevice, 0, 1);
this.tableLayoutPanel2.Controls.Add(this.cboAudioDevice, 1, 1);
this.tableLayoutPanel2.Controls.Add(this.grpVolume, 0, 4);
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
@ -207,7 +316,7 @@
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.Size = new System.Drawing.Size(470, 324);
this.tableLayoutPanel2.Size = new System.Drawing.Size(464, 486);
this.tableLayoutPanel2.TabIndex = 3;
//
// chkEnableAudio
@ -333,7 +442,7 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(470, 324);
this.ClientSize = new System.Drawing.Size(464, 486);
this.Controls.Add(this.tableLayoutPanel2);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
@ -359,23 +468,29 @@
#endregion
private System.Windows.Forms.GroupBox grpVolume;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
private System.Windows.Forms.CheckBox chkEnableAudio;
private System.Windows.Forms.Label lblAudioLatency;
private System.Windows.Forms.NumericUpDown nudLatency;
private System.Windows.Forms.Label lblLatencyMs;
private System.Windows.Forms.Button btnReset;
private Controls.ctrlTrackbar trkMaster;
private Controls.ctrlTrackbar trkDmcVol;
private Controls.ctrlTrackbar trkNoiseVol;
private Controls.ctrlTrackbar trkTriangleVol;
private Controls.ctrlTrackbar trkSquare2Vol;
private Controls.ctrlTrackbar trkSquare1Vol;
private System.Windows.Forms.Label lblSampleRate;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2;
private System.Windows.Forms.ComboBox cboSampleRate;
private System.Windows.Forms.Label lblAudioDevice;
private System.Windows.Forms.ComboBox cboAudioDevice;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private Controls.ctrlTrackbar trkDmcVol;
private Controls.ctrlTrackbar trkNoiseVol;
private Controls.ctrlTrackbar trkTriangleVol;
private Controls.ctrlTrackbar trkSquare2Vol;
private Controls.ctrlTrackbar trkSquare1Vol;
private Controls.ctrlTrackbar trkMaster;
private Controls.ctrlTrackbar trkFdsVol;
private Controls.ctrlTrackbar trkMmc5Vol;
private Controls.ctrlTrackbar trkVrc6Vol;
private Controls.ctrlTrackbar trkVrc7Vol;
private Controls.ctrlTrackbar trkNamco163Vol;
private Controls.ctrlTrackbar trkSunsoft5b;
}
}

View file

@ -28,6 +28,13 @@ namespace Mesen.GUI.Forms.Config
AddBinding("TriangleVolume", trkTriangleVol);
AddBinding("NoiseVolume", trkNoiseVol);
AddBinding("DmcVolume", trkDmcVol);
AddBinding("FdsVolume", trkFdsVol);
AddBinding("Mmc5Volume", trkMmc5Vol);
AddBinding("Vrc6Volume", trkVrc6Vol);
AddBinding("Vrc7Volume", trkVrc7Vol);
AddBinding("Namco163Volume", trkNamco163Vol);
AddBinding("Sunsoft5bVolume", trkSunsoft5b);
AddBinding("AudioLatency", nudLatency);
AddBinding("SampleRate", cboSampleRate);
AddBinding("AudioDevice", cboAudioDevice);

View file

@ -79,7 +79,7 @@ namespace Mesen.GUI
[DllImport(DLLPath)] private static extern void SetFlags(EmulationFlags flags);
[DllImport(DLLPath)] private static extern void ClearFlags(EmulationFlags flags);
[DllImport(DLLPath)] public static extern void SetMasterVolume(double volume);
[DllImport(DLLPath)] public static extern void SetChannelVolume(UInt32 channel, double volume);
[DllImport(DLLPath)] public static extern void SetChannelVolume(AudioChannel channel, double volume);
[DllImport(DLLPath)] public static extern void SetSampleRate(UInt32 sampleRate);
[DllImport(DLLPath)] public static extern void SetAudioLatency(UInt32 msLatency);
[DllImport(DLLPath)] public static extern void SetNesModel(NesModel model);
@ -510,6 +510,21 @@ namespace Mesen.GUI
PAL = 2
}
public enum AudioChannel
{
Square1 = 0,
Square2 = 1,
Triangle = 2,
Noise = 3,
DMC = 4,
FDS = 5,
MMC5 = 6,
VRC6 = 7,
VRC7 = 8,
Namco163 = 9,
Sunsoft5B = 10
}
public enum VideoFilterType
{
None = 0,