diff --git a/Core/Console.cpp b/Core/Console.cpp index fe90120..df6067c 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -33,6 +33,7 @@ #include "MovieManager.h" #include "SystemActionManager.h" #include "SpcHud.h" +#include "Msu1.h" #include "../Utilities/Serializer.h" #include "../Utilities/Timer.h" #include "../Utilities/VirtualFile.h" @@ -241,6 +242,7 @@ void Console::Stop(bool sendNotification) _controlManager.reset(); _memoryManager.reset(); _dmaController.reset(); + _msu1.reset(); _soundMixer->StopAudio(true); @@ -331,6 +333,8 @@ bool Console::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom) _dmaController.reset(new DmaController(_memoryManager.get())); _spc.reset(new Spc(this)); + _msu1.reset(Msu1::Init(romFile, _spc.get())); + if(_cart->GetSpcData()) { _spc->LoadSpcFile(_cart->GetSpcData()); _spcHud.reset(new SpcHud(this, _cart->GetSpcData())); @@ -553,6 +557,9 @@ void Console::Serialize(ostream &out) serializer.Stream(_cart.get()); serializer.Stream(_controlManager.get()); serializer.Stream(_spc.get()); + if(_msu1) { + serializer.Stream(_msu1.get()); + } serializer.Save(out); } @@ -567,7 +574,9 @@ void Console::Deserialize(istream &in, uint32_t fileFormatVersion) serializer.Stream(_cart.get()); serializer.Stream(_controlManager.get()); serializer.Stream(_spc.get()); - + if(_msu1) { + serializer.Stream(_msu1.get()); + } _notificationManager->SendNotification(ConsoleNotificationType::StateLoaded); } @@ -666,6 +675,11 @@ shared_ptr Console::GetDmaController() return _dmaController; } +shared_ptr Console::GetMsu1() +{ + return _msu1; +} + shared_ptr Console::GetDebugger(bool autoStart) { shared_ptr debugger = _debugger; diff --git a/Core/Console.h b/Core/Console.h index bd9fa6c..d3906c8 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -30,6 +30,7 @@ class MovieManager; class SpcHud; class FrameLimiter; class DebugStats; +class Msu1; enum class MemoryOperationType; enum class SnesMemoryType; @@ -49,6 +50,8 @@ private: shared_ptr _internalRegisters; shared_ptr _controlManager; shared_ptr _dmaController; + + shared_ptr _msu1; shared_ptr _debugger; @@ -143,6 +146,7 @@ public: shared_ptr GetInternalRegisters(); shared_ptr GetControlManager(); shared_ptr GetDmaController(); + shared_ptr GetMsu1(); shared_ptr GetDebugger(bool autoStart = true); void StopDebugger(); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index d8b0570..926376d 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -114,6 +114,7 @@ + @@ -124,6 +125,7 @@ + @@ -261,12 +263,14 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 0f3b24c..6bd6c3e 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -479,6 +479,12 @@ Debugger\Debuggers + + SNES\Coprocessors\MSU1 + + + SNES\Coprocessors\MSU1 + @@ -761,6 +767,12 @@ Netplay + + SNES\Coprocessors\MSU1 + + + SNES\Coprocessors\MSU1 + @@ -829,5 +841,8 @@ {c58f689e-a6af-4b9c-bb77-0be9d2b1142c} + + {9fb8db1f-a7b5-4570-99ca-bbc15d262632} + \ No newline at end of file diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 9a091e7..1c6ac96 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -272,7 +272,7 @@ uint8_t MemoryManager::ReadDma(uint32_t addr, bool forBusA) uint8_t value; IMemoryHandler* handler = _mappings.GetHandler(addr); if(handler) { - if(forBusA && handler == _registerHandlerB.get()) { + if(forBusA && addr >= 0x2100 && addr <= 0x21FF && handler == _registerHandlerB.get()) { //Trying to read from bus B using bus A returns open bus value = _openBus; } else if(handler == _registerHandlerA.get()) { diff --git a/Core/Msu1.cpp b/Core/Msu1.cpp new file mode 100644 index 0000000..e969bec --- /dev/null +++ b/Core/Msu1.cpp @@ -0,0 +1,117 @@ +#include "stdafx.h" +#include "Msu1.h" +#include "Spc.h" +#include "../Utilities/Serializer.h" +#include "../Utilities/FolderUtilities.h" + +Msu1* Msu1::Init(VirtualFile romFile, Spc* spc) +{ + string romFolder = romFile.GetFolderPath(); + string romName = FolderUtilities::GetFilename(romFile.GetFileName(), false); + if(ifstream(FolderUtilities::CombinePath(romFolder, romName + ".msu"))) { + return new Msu1(romFile, spc); + } else if(ifstream(FolderUtilities::CombinePath(romFolder, "msu1.rom"))) { + return new Msu1(romFile, spc); + } else { + return nullptr; + } +} + +Msu1::Msu1(VirtualFile romFile, Spc* spc) +{ + _spc = spc; + _romFolder = romFile.GetFolderPath(); + _romName = FolderUtilities::GetFilename(romFile.GetFileName(), false); + _dataFile.open(FolderUtilities::CombinePath(_romFolder, _romName) + ".msu", ios::binary); + if(_dataFile) { + _trackPath = FolderUtilities::CombinePath(_romFolder, _romName); + } else { + _dataFile.open(FolderUtilities::CombinePath(_romFolder, "msu1.rom"), ios::binary); + _trackPath = FolderUtilities::CombinePath(_romFolder, "track"); + } + + if(_dataFile) { + _dataFile.seekg(0, ios::end); + _dataSize = (uint32_t)_dataFile.tellg(); + } else { + _dataSize = 0; + } +} + +void Msu1::Write(uint16_t addr, uint8_t value) +{ + switch(addr) { + case 0x2000: _tmpDataPointer = (_tmpDataPointer & 0xFFFFFF00) | value; break; + case 0x2001: _tmpDataPointer = (_tmpDataPointer & 0xFFFF00FF) | (value << 8); break; + case 0x2002: _tmpDataPointer = (_tmpDataPointer & 0xFF00FFFF) | (value << 16); break; + case 0x2003: + _tmpDataPointer = (_tmpDataPointer & 0x00FFFFFF) | (value << 24); + _dataPointer = _tmpDataPointer; + _dataFile.seekg(_dataPointer, ios::beg); + break; + + case 0x2004: _trackSelect = (_trackSelect & 0xFF00) | value; break; + case 0x2005: + _trackSelect = (_trackSelect & 0xFF) | (value << 8); + LoadTrack(); + break; + + case 0x2006: _volume = value; break; + case 0x2007: + if(!_audioBusy) { + _repeat = (value & 0x02) != 0; + _paused = (value & 0x01) == 0; + _pcmReader.SetLoopFlag(_repeat); + } + break; + } +} + +uint8_t Msu1::Read(uint16_t addr) +{ + switch(addr) { + case 0x2000: + //status + return (_dataBusy << 7) | (_audioBusy << 6) | (_repeat << 5) | ((!_paused) << 4) | (_trackMissing << 3) | 0x01; + + case 0x2001: + //data + if(!_dataBusy && _dataPointer < _dataSize) { + _dataPointer++; + return (uint8_t)_dataFile.get(); + } + return 0; + + case 0x2002: return 'S'; + case 0x2003: return '-'; + case 0x2004: return 'M'; + case 0x2005: return 'S'; + case 0x2006: return 'U'; + case 0x2007: return '1'; + } + + return 0; +} + +void Msu1::MixAudio(int16_t* buffer, size_t sampleCount, uint32_t sampleRate) +{ + if(!_paused) { + _pcmReader.SetSampleRate(sampleRate); + _pcmReader.ApplySamples(buffer, sampleCount, _spc->IsMuted() ? 0 : _volume); + } +} + +void Msu1::LoadTrack(uint32_t startOffset) +{ + _trackMissing = !_pcmReader.Init(_trackPath + "-" + std::to_string(_trackSelect) + ".pcm", _repeat, startOffset); +} + +void Msu1::Serialize(Serializer &s) +{ + uint32_t offset = _pcmReader.GetOffset(); + s.Stream(_trackSelect, _tmpDataPointer, _dataPointer, _repeat, _paused, _volume, _trackMissing, _audioBusy, _dataBusy, offset); + if(!s.IsSaving()) { + _dataFile.seekg(_dataPointer, ios::beg); + LoadTrack(offset); + } +} diff --git a/Core/Msu1.h b/Core/Msu1.h new file mode 100644 index 0000000..c1f723b --- /dev/null +++ b/Core/Msu1.h @@ -0,0 +1,44 @@ +#pragma once +#include "stdafx.h" +#include "PcmReader.h" +#include "../Utilities/ISerializable.h" +#include "../Utilities/VirtualFile.h" + +class Spc; + +class Msu1 : public ISerializable +{ +private: + Spc * _spc; + PcmReader _pcmReader; + uint8_t _volume = 100; + uint16_t _trackSelect = 0; + uint32_t _tmpDataPointer = 0; + uint32_t _dataPointer = 0; + string _romName; + string _romFolder; + string _trackPath; + + bool _repeat = false; + bool _paused = false; + bool _audioBusy = false; //Always false + bool _dataBusy = false; //Always false + bool _trackMissing = false; + + ifstream _dataFile; + uint32_t _dataSize; + + void LoadTrack(uint32_t startOffset = 8); + +public: + Msu1(VirtualFile romFile, Spc* spc); + + static Msu1* Init(VirtualFile romFile, Spc* spc); + + void Write(uint16_t addr, uint8_t value); + uint8_t Read(uint16_t addr); + + void MixAudio(int16_t *buffer, size_t sampleCount, uint32_t sampleRate); + + void Serialize(Serializer &s); +}; \ No newline at end of file diff --git a/Core/PcmReader.cpp b/Core/PcmReader.cpp new file mode 100644 index 0000000..8f0052e --- /dev/null +++ b/Core/PcmReader.cpp @@ -0,0 +1,150 @@ +#include "stdafx.h" +#include "PcmReader.h" +#include "../Utilities/VirtualFile.h" + +PcmReader::PcmReader() +{ + _done = true; + _loop = false; + _prevLeft = 0; + _prevRight = 0; + _loopOffset = 8; + _sampleRate = 48000; + _blipLeft = blip_new(10000); + _blipRight = blip_new(10000); + _outputBuffer = new int16_t[20000]; +} + +PcmReader::~PcmReader() +{ + blip_delete(_blipLeft); + blip_delete(_blipRight); + delete[] _outputBuffer; +} + +bool PcmReader::Init(string filename, bool loop, uint32_t startOffset) +{ + if(_file) { + _file.close(); + } + + _file.open(filename, ios::binary); + if(_file) { + _file.seekg(0, ios::end); + _fileSize = (uint32_t)_file.tellg(); + if(_fileSize < 12) { + return false; + } + + _file.seekg(4, ios::beg); + uint32_t loopOffset = (uint8_t)_file.get(); + loopOffset |= ((uint8_t)_file.get()) << 8; + loopOffset |= ((uint8_t)_file.get()) << 16; + loopOffset |= ((uint8_t)_file.get()) << 24; + + _loopOffset = (uint32_t)loopOffset; + + _prevLeft = 0; + _prevRight = 0; + _done = false; + _loop = loop; + _fileOffset = startOffset; + _file.seekg(_fileOffset, ios::beg); + + blip_clear(_blipLeft); + blip_clear(_blipRight); + + return true; + } else { + _done = true; + return false; + } +} + +bool PcmReader::IsPlaybackOver() +{ + return _done; +} + +void PcmReader::SetSampleRate(uint32_t sampleRate) +{ + if(sampleRate != _sampleRate) { + _sampleRate = sampleRate; + + blip_clear(_blipLeft); + blip_clear(_blipRight); + blip_set_rates(_blipLeft, PcmReader::PcmSampleRate, _sampleRate); + blip_set_rates(_blipRight, PcmReader::PcmSampleRate, _sampleRate); + } +} + +void PcmReader::SetLoopFlag(bool loop) +{ + _loop = loop; +} + +void PcmReader::ReadSample(int16_t &left, int16_t &right) +{ + uint8_t val[4]; + _file.get(((char*)val)[0]); + _file.get(((char*)val)[1]); + _file.get(((char*)val)[2]); + _file.get(((char*)val)[3]); + + left = val[0] | (val[1] << 8); + right = val[2] | (val[3] << 8); +} + +void PcmReader::LoadSamples(uint32_t samplesToLoad) +{ + uint32_t samplesRead = 0; + + int16_t left = 0; + int16_t right = 0; + for(uint32_t i = _fileOffset; i < _fileSize && samplesRead < samplesToLoad; i+=4) { + ReadSample(left, right); + + blip_add_delta(_blipLeft, samplesRead, left - _prevLeft); + blip_add_delta(_blipRight, samplesRead, right - _prevRight); + + _prevLeft = left; + _prevRight = right; + + _fileOffset += 4; + samplesRead++; + + if(samplesRead < samplesToLoad && i + 4 >= _fileSize) { + if(_loop) { + i = _loopOffset * 4 + 8; + _fileOffset = i; + _file.seekg(_fileOffset, ios::beg); + } else { + _done = true; + } + } + } + + blip_end_frame(_blipLeft, samplesRead); + blip_end_frame(_blipRight, samplesRead); +} + +void PcmReader::ApplySamples(int16_t *buffer, size_t sampleCount, uint8_t volume) +{ + if(_done) { + return; + } + + LoadSamples((uint32_t)sampleCount * PcmReader::PcmSampleRate / _sampleRate + 1 - blip_samples_avail(_blipLeft)); + + int samplesRead = blip_read_samples(_blipLeft, _outputBuffer, (int)sampleCount, 1); + blip_read_samples(_blipRight, _outputBuffer + 1, (int)sampleCount, 1); + + for(size_t i = 0, len = samplesRead * 2; i < len; i++) { + buffer[i] += (int16_t)((int32_t)_outputBuffer[i] * volume / 255); + } +} + +uint32_t PcmReader::GetOffset() +{ + return _fileOffset; +} \ No newline at end of file diff --git a/Core/PcmReader.h b/Core/PcmReader.h new file mode 100644 index 0000000..933184b --- /dev/null +++ b/Core/PcmReader.h @@ -0,0 +1,43 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/stb_vorbis.h" +#include "../Utilities/blip_buf.h" + +class PcmReader +{ +private: + static constexpr int PcmSampleRate = 44100; + static constexpr int SamplesToRead = 100; + + int16_t* _outputBuffer; + + ifstream _file; + uint32_t _fileOffset; + uint32_t _fileSize; + uint32_t _loopOffset; + + int16_t _prevLeft; + int16_t _prevRight; + + bool _loop; + bool _done; + + blip_t* _blipLeft; + blip_t* _blipRight; + + uint32_t _sampleRate = 0; + + void LoadSamples(uint32_t samplesToLoad); + void ReadSample(int16_t &left, int16_t &right); + +public: + PcmReader(); + ~PcmReader(); + + bool Init(string filename, bool loop, uint32_t startOffset = 0); + bool IsPlaybackOver(); + void SetSampleRate(uint32_t sampleRate); + void SetLoopFlag(bool loop); + void ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume); + uint32_t GetOffset(); +}; diff --git a/Core/RegisterHandlerB.cpp b/Core/RegisterHandlerB.cpp index 04ac009..fe67ce2 100644 --- a/Core/RegisterHandlerB.cpp +++ b/Core/RegisterHandlerB.cpp @@ -5,6 +5,7 @@ #include "Spc.h" #include "BaseCartridge.h" #include "Sa1.h" +#include "Msu1.h" #include "CheatManager.h" #include "../Utilities/Serializer.h" @@ -15,6 +16,7 @@ RegisterHandlerB::RegisterHandlerB(Console *console, Ppu * ppu, Spc * spc, uint8 _sa1 = console->GetCartridge()->GetSa1(); _ppu = ppu; _spc = spc; + _msu1 = console->GetMsu1().get(); _workRam = workRam; _wramPosition = 0; _memoryType = SnesMemoryType::Register; @@ -33,6 +35,8 @@ uint8_t RegisterHandlerB::Read(uint32_t addr) return value; } else if(addr >= 0x2300 && addr <= 0x23FF && _console->GetCartridge()->GetSa1()) { return _console->GetCartridge()->GetSa1()->CpuRegisterRead(addr); + } else if(_msu1 && addr <= 0x2007) { + return _msu1->Read(addr); } else { return _ppu->Read(addr); } @@ -69,6 +73,8 @@ void RegisterHandlerB::Write(uint32_t addr, uint8_t value) } } else if(addr >= 0x2200 && addr <= 0x22FF && _console->GetCartridge()->GetSa1()) { _console->GetCartridge()->GetSa1()->CpuRegisterWrite(addr, value); + } else if(_msu1 && addr <= 0x2007) { + return _msu1->Write(addr, value); } else { _ppu->Write(addr, value); } diff --git a/Core/RegisterHandlerB.h b/Core/RegisterHandlerB.h index dd79b0c..ca37c98 100644 --- a/Core/RegisterHandlerB.h +++ b/Core/RegisterHandlerB.h @@ -7,6 +7,7 @@ class Console; class Ppu; class Spc; class Sa1; +class Msu1; class CheatManager; class RegisterHandlerB : public IMemoryHandler, public ISerializable @@ -17,6 +18,7 @@ private: Ppu *_ppu; Spc *_spc; Sa1 *_sa1; + Msu1 *_msu1; uint8_t *_workRam; uint32_t _wramPosition; diff --git a/Core/SPC_DSP.h b/Core/SPC_DSP.h index cc472f3..f97f938 100644 --- a/Core/SPC_DSP.h +++ b/Core/SPC_DSP.h @@ -44,7 +44,7 @@ public: // a pair of samples is be generated. void run(); - void setEchoWriteEnabled(bool enabled); + bool isMuted() { return (m.regs[r_flg] & 0x40) != 0; } // Sound control diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index 75c4d0c..185992f 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -6,6 +6,7 @@ #include "RewindManager.h" #include "VideoRenderer.h" #include "WaveRecorder.h" +#include "Msu1.h" #include "../Utilities/Equalizer.h" #include "../Utilities/blip_buf.h" @@ -69,25 +70,30 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount) _leftSample = samples[0]; _rightSample = samples[1]; + int16_t *out = nullptr; + uint32_t count = 0; + if(cfg.SampleRate == SoundResampler::SpcSampleRate && cfg.DisableDynamicSampleRate) { + out = samples; + count = sampleCount; + } else { + count = _resampler->Resample(samples, sampleCount, cfg.SampleRate, _sampleBuffer); + out = _sampleBuffer; + } + + shared_ptr msu1 = _console->GetMsu1(); + if(msu1) { + msu1->MixAudio(out, count, cfg.SampleRate); + } + if(masterVolume < 100) { //Apply volume if not using the default value - for(uint32_t i = 0; i < sampleCount * 2; i++) { - samples[i] = (int32_t)samples[i] * (int32_t)masterVolume / 100; + for(uint32_t i = 0; i < count * 2; i++) { + out[i] = (int32_t)out[i] * (int32_t)masterVolume / 100; } } shared_ptr rewindManager = _console->GetRewindManager(); - if(rewindManager && rewindManager->SendAudio(samples, (uint32_t)sampleCount)) { - int16_t *out = nullptr; - uint32_t count = 0; - if(cfg.SampleRate == SoundResampler::SpcSampleRate && cfg.DisableDynamicSampleRate) { - out = samples; - count = sampleCount; - } else { - count = _resampler->Resample(samples, sampleCount, cfg.SampleRate, _sampleBuffer); - out = _sampleBuffer; - } - + if(rewindManager && rewindManager->SendAudio(out, count)) { bool isRecording = _waveRecorder || _console->GetVideoRenderer()->IsRecording(); if(isRecording) { if(_waveRecorder) { diff --git a/Core/Spc.cpp b/Core/Spc.cpp index d1d731a..4105fd1 100644 --- a/Core/Spc.cpp +++ b/Core/Spc.cpp @@ -342,6 +342,11 @@ SpcState Spc::GetState() return _state; } +bool Spc::IsMuted() +{ + return _dsp->isMuted(); +} + AddressInfo Spc::GetAbsoluteAddress(uint16_t addr) { if(addr < 0xFFC0 || !_state.RomEnabled) { diff --git a/Core/Spc.h b/Core/Spc.h index af625ed..3437b41 100644 --- a/Core/Spc.h +++ b/Core/Spc.h @@ -298,6 +298,7 @@ public: void ProcessEndFrame(); SpcState GetState(); + bool IsMuted(); AddressInfo GetAbsoluteAddress(uint16_t addr); int GetRelativeAddress(AddressInfo & absAddress);