FDS: Sound support
This commit is contained in:
parent
9bfa62129a
commit
ea36115941
17 changed files with 1031 additions and 424 deletions
|
@ -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);
|
||||
}
|
|
@ -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
91
Core/BaseFdsChannel.h
Normal 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;
|
||||
}
|
||||
};
|
|
@ -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" />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
401
Core/FDS.cpp
401
Core/FDS.cpp
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
398
Core/FDS.h
398
Core/FDS.h
|
@ -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
151
Core/FdsAudio.h
Normal 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
117
Core/ModChannel.h
Normal 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;
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
153
GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
generated
153
GUI.NET/Forms/Config/frmAudioConfig.Designer.cs
generated
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue