From b852edfec870320ae121fde977d24d277b0f085b Mon Sep 17 00:00:00 2001 From: Souryo Date: Sat, 25 Jun 2016 20:46:54 -0400 Subject: [PATCH] NSF/NSFe support --- Core/APU.cpp | 3 +- Core/BaseFdsChannel.h | 3 +- Core/BaseMapper.cpp | 14 +- Core/BaseMapper.h | 3 +- Core/CPU.cpp | 16 +- Core/CPU.h | 3 + Core/Console.cpp | 22 +- Core/Console.h | 3 + Core/Core.vcxproj | 21 + Core/Core.vcxproj.filters | 15 + Core/EmulationSettings.cpp | 4 + Core/EmulationSettings.h | 26 + Core/IMemoryHandler.h | 11 + Core/MMC5Audio.h | 1 + Core/MapperFactory.cpp | 5 + Core/MapperFactory.h | 1 + Core/MemoryManager.cpp | 8 +- Core/MemoryManager.h | 3 +- Core/NsfLoader.h | 160 ++++++ Core/NsfMapper.cpp | 389 ++++++++++++++ Core/NsfMapper.h | 279 ++++++++++ Core/NsfPpu.h | 22 + Core/NsfeLoader.h | 194 +++++++ Core/PPU.cpp | 27 +- Core/PPU.h | 8 + Core/RomData.h | 33 +- Core/RomLoader.cpp | 16 +- Core/SoundMixer.cpp | 76 ++- Core/SoundMixer.h | 11 +- GUI.NET/Config/PreferenceInfo.cs | 12 + GUI.NET/Controls/ctrlNsfPlayer.Designer.cs | 484 ++++++++++++++++++ GUI.NET/Controls/ctrlNsfPlayer.cs | 281 ++++++++++ GUI.NET/Controls/ctrlNsfPlayer.resx | 126 +++++ GUI.NET/Dependencies/resources.en.xml | 8 +- GUI.NET/Dependencies/resources.fr.xml | 23 +- GUI.NET/Dependencies/resources.ja.xml | 21 +- .../Forms/Config/frmPreferences.Designer.cs | 273 ++++++++-- GUI.NET/Forms/Config/frmPreferences.cs | 8 + GUI.NET/Forms/ResourceHelper.cs | 2 +- GUI.NET/Forms/frmMain.Designer.cs | 12 + GUI.NET/Forms/frmMain.cs | 84 ++- GUI.NET/GUI.NET.csproj | 12 + GUI.NET/InteropEmu.cs | 102 +++- GUI.NET/Properties/Resources.Designer.cs | 30 ++ GUI.NET/Properties/Resources.resx | 9 + GUI.NET/Resources/NextTrack.png | Bin 0 -> 618 bytes GUI.NET/Resources/NsfBackground.png | Bin 0 -> 10105 bytes GUI.NET/Resources/PrevTrack.png | Bin 0 -> 618 bytes InteropDLL/ConsoleWrapper.cpp | 31 +- 49 files changed, 2773 insertions(+), 122 deletions(-) create mode 100644 Core/NsfLoader.h create mode 100644 Core/NsfMapper.cpp create mode 100644 Core/NsfMapper.h create mode 100644 Core/NsfPpu.h create mode 100644 Core/NsfeLoader.h create mode 100644 GUI.NET/Controls/ctrlNsfPlayer.Designer.cs create mode 100644 GUI.NET/Controls/ctrlNsfPlayer.cs create mode 100644 GUI.NET/Controls/ctrlNsfPlayer.resx create mode 100644 GUI.NET/Resources/NextTrack.png create mode 100644 GUI.NET/Resources/NsfBackground.png create mode 100644 GUI.NET/Resources/PrevTrack.png diff --git a/Core/APU.cpp b/Core/APU.cpp index 490f56c1..743252b3 100644 --- a/Core/APU.cpp +++ b/Core/APU.cpp @@ -234,6 +234,5 @@ void APU::StreamState(bool saving) void APU::AddExpansionAudioDelta(AudioChannel channel, int16_t delta) { - Instance->_mixer->SetExpansionAudioType(channel); - Instance->_mixer->AddExpansionAudioDelta(Instance->_currentCycle, delta); + Instance->_mixer->AddDelta(channel, Instance->_currentCycle, delta); } \ No newline at end of file diff --git a/Core/BaseFdsChannel.h b/Core/BaseFdsChannel.h index cfadd2d3..3f1e40ef 100644 --- a/Core/BaseFdsChannel.h +++ b/Core/BaseFdsChannel.h @@ -13,7 +13,8 @@ protected: uint32_t _timer = 0; - uint8_t _masterSpeed = 0; + //"Few FDS NSFs write to this register. The BIOS initializes this to $FF." + uint8_t _masterSpeed = 0xFF; void StreamState(bool saving) { diff --git a/Core/BaseMapper.cpp b/Core/BaseMapper.cpp index 43191506..ff121e13 100644 --- a/Core/BaseMapper.cpp +++ b/Core/BaseMapper.cpp @@ -65,11 +65,23 @@ void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, int16 } source = &source[pageNumber * pageSize]; + accessType = accessType != -1 ? accessType : defaultAccessType; + SetCpuMemoryMapping(startAddr, endAddr, source, accessType); +} + +void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t *source, int8_t accessType) +{ + #ifdef _DEBUG + if((startAddr & 0xFF) || (endAddr & 0xFF) != 0xFF) { + throw new std::runtime_error("Start/End address must be multiples of 256/0x100"); + } + #endif + startAddr >>= 8; endAddr >>= 8; for(uint16_t i = startAddr; i <= endAddr; i++) { _prgPages[i] = source; - _prgPageAccessType[i] = accessType != -1 ? accessType : defaultAccessType; + _prgPageAccessType[i] = accessType != -1 ? accessType : MemoryAccessType::Read; source += 0x100; } diff --git a/Core/BaseMapper.h b/Core/BaseMapper.h index bac77217..49cd91cc 100644 --- a/Core/BaseMapper.h +++ b/Core/BaseMapper.h @@ -124,6 +124,7 @@ protected: void SelectPrgPage2x(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom); virtual void SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType = PrgMemoryType::PrgRom); void SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, int16_t pageNumber, PrgMemoryType type, int8_t accessType = -1); + void SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t *source, int8_t accessType = -1); virtual void SelectChrPage8x(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default); virtual void SelectChrPage4x(uint16_t slot, uint16_t page, ChrMemoryType memoryType = ChrMemoryType::Default); @@ -165,7 +166,7 @@ public: virtual void ProcessCpuClock() { } virtual void NotifyVRAMAddressChange(uint16_t addr); void ProcessNotification(ConsoleNotificationType type, void* parameter); - void GetMemoryRanges(MemoryRanges &ranges); + virtual void GetMemoryRanges(MemoryRanges &ranges); void ApplyCheats(); diff --git a/Core/CPU.cpp b/Core/CPU.cpp index 4657cb3d..f86f3910 100644 --- a/Core/CPU.cpp +++ b/Core/CPU.cpp @@ -5,6 +5,7 @@ #include "DeltaModulationChannel.h" #include "TraceLogger.h" #include "Debugger.h" +#include "NsfMapper.h" CPU* CPU::Instance = nullptr; @@ -68,6 +69,9 @@ void CPU::Reset(bool softReset) _dmcCounter = -1; _dmcDmaRunning = false; + //Used by NSF code to disable Frame Counter & DMC interrupts + _irqMask = 0xFF; + //Use _memoryManager->Read() directly to prevent clocking the PPU/APU when setting PC at reset _state.PC = _memoryManager->Read(CPU::ResetVector) | _memoryManager->Read(CPU::ResetVector+1) << 8; @@ -118,8 +122,16 @@ uint16_t CPU::FetchOperand() case AddrMode::AbsYW: return GetAbsYAddr(true); default: break; } + Debugger::BreakIfDebugging(); - throw std::runtime_error("Invalid OP code - CPU crashed"); + + if(NsfMapper::GetInstance()) { + //Don't stop emulation on CPU crash when playing NSFs, reset cpu instead + Reset(false); + return 0; + } else { + throw std::runtime_error("Invalid OP code - CPU crashed"); + } } void CPU::IncCycleCount() @@ -148,7 +160,7 @@ void CPU::IncCycleCount() //"it's really the status of the interrupt lines at the end of the second-to-last cycle that matters." //Keep the irq lines values from the previous cycle. The before-to-last cycle's values will be used _prevRunIrq = _runIrq; - _runIrq = _state.NMIFlag || (_state.IRQFlag > 0 && !CheckFlag(PSFlags::Interrupt)); + _runIrq = _state.NMIFlag || ((_state.IRQFlag & _irqMask) > 0 && !CheckFlag(PSFlags::Interrupt)); } } diff --git a/Core/CPU.h b/Core/CPU.h index 60ac17a2..aeaf2293 100644 --- a/Core/CPU.h +++ b/Core/CPU.h @@ -79,6 +79,8 @@ private: bool _cpuWrite = false; uint16_t _writeAddr = 0; + uint8_t _irqMask; + State _state; MemoryManager *_memoryManager = nullptr; @@ -873,6 +875,7 @@ public: static int32_t GetCycleCount() { return CPU::Instance->_cycleCount; } static void SetNMIFlag() { CPU::Instance->_state.NMIFlag = true; } static void ClearNMIFlag() { CPU::Instance->_state.NMIFlag = false; } + static void SetIRQMask(uint8_t mask) { CPU::Instance->_irqMask = mask; } static void SetIRQSource(IRQSource source) { CPU::Instance->_state.IRQFlag |= (int)source; } static bool HasIRQSource(IRQSource source) { return (CPU::Instance->_state.IRQFlag & (int)source) != 0; } static void ClearIRQSource(IRQSource source) { CPU::Instance->_state.IRQFlag &= ~(int)source; } diff --git a/Core/Console.cpp b/Core/Console.cpp index c7868e98..2a372889 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -12,7 +12,9 @@ #include "../Utilities/Timer.h" #include "../Utilities/FolderUtilities.h" #include "HdPpu.h" +#include "NsfPpu.h" #include "SoundMixer.h" +#include "NsfMapper.h" shared_ptr Console::Instance(new Console()); @@ -53,6 +55,9 @@ void Console::Initialize(string romFilename, stringstream *filestream, string ip _cpu.reset(new CPU(_memoryManager.get())); if(HdNesPack::HasHdPack(_romFilepath)) { _ppu.reset(new HdPpu(_memoryManager.get())); + } else if(NsfMapper::GetInstance()) { + //Disable most of the PPU for NSFs + _ppu.reset(new NsfPpu(_memoryManager.get())); } else { _ppu.reset(new PPU(_memoryManager.get())); } @@ -61,10 +66,10 @@ void Console::Initialize(string romFilename, stringstream *filestream, string ip _controlManager.reset(_mapper->GetGameSystem() == GameSystem::VsUniSystem ? new VsControlManager() : new ControlManager()); _controlManager->UpdateControlDevices(); - _memoryManager->RegisterIODevice(_mapper.get()); _memoryManager->RegisterIODevice(_ppu.get()); _memoryManager->RegisterIODevice(_apu.get()); _memoryManager->RegisterIODevice(_controlManager.get()); + _memoryManager->RegisterIODevice(_mapper.get()); UpdateNesModel(false); @@ -180,12 +185,12 @@ void Console::ResetComponents(bool softReset) Movie::Stop(); SoundMixer::StopRecording(); - _memoryManager->Reset(softReset); _ppu->Reset(); _apu->Reset(softReset); _cpu->Reset(softReset); _controlManager->Reset(softReset); - + _memoryManager->Reset(softReset); + SoundMixer::StopAudio(true); if(softReset) { @@ -257,6 +262,12 @@ void Console::Run() //Sleep until we're ready to start the next frame clockTimer.WaitUntil(targetTime); + + if(_resetRequested) { + //Used by NSF player to reset console after changing track + ResetComponents(true); + _resetRequested = false; + } if(!_pauseLock.IsFree()) { //Need to temporarely pause the emu (to save/load a state, etc.) @@ -411,6 +422,11 @@ void Console::StopDebugger() _debugger.reset(); } +void Console::RequestReset() +{ + Instance->_resetRequested = true; +} + NesModel Console::GetNesModel() { return Instance->_model; diff --git a/Core/Console.h b/Core/Console.h index d3a67e5c..ec97940f 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -33,6 +33,8 @@ class Console bool _stop = false; bool _reset = false; + + atomic _resetRequested = false; bool _initialized = false; @@ -45,6 +47,7 @@ class Console ~Console(); void Run(); void Stop(); + static void RequestReset(); static void Reset(bool softReset = true); //Used to pause the emu loop to perform thread-safe operations diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 03a3966f..91dc896e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -186,6 +186,8 @@ true true false + + Console @@ -215,6 +217,8 @@ true true false + + Console @@ -243,6 +247,8 @@ MultiThreadedDLL true true + + Console @@ -269,6 +275,8 @@ MultiThreaded true true + + Console @@ -295,6 +303,8 @@ MultiThreaded true true + + Console @@ -321,6 +331,8 @@ MultiThreadedDLL true true + + Console @@ -347,6 +359,8 @@ MultiThreaded true true + + Console @@ -373,6 +387,8 @@ MultiThreaded true true + + Console @@ -452,6 +468,10 @@ + + + + @@ -610,6 +630,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 0f389704..866c611e 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -670,6 +670,18 @@ Nes\Controllers + + Nes\RomLoader + + + Nes\Mappers + + + Nes\RomLoader + + + Nes + @@ -831,5 +843,8 @@ Nes\Controllers + + Nes\Mappers + \ No newline at end of file diff --git a/Core/EmulationSettings.cpp b/Core/EmulationSettings.cpp index 2827d7e1..6c9848e6 100644 --- a/Core/EmulationSettings.cpp +++ b/Core/EmulationSettings.cpp @@ -34,6 +34,10 @@ double EmulationSettings::_effectiveOverclockRateSound = 100; bool EmulationSettings::_overclockAdjustApu = true; bool EmulationSettings::_disableOverclocking = false; +int32_t EmulationSettings::_nsfAutoDetectSilenceDelay = 3000; +int32_t EmulationSettings::_nsfMoveToNextTrackTime = 120; +bool EmulationSettings::_nsfDisableApuIrqs = true; + OverscanDimensions EmulationSettings::_overscan; VideoFilterType EmulationSettings::_videoFilterType = VideoFilterType::None; VideoResizeFilter EmulationSettings::_resizeFilter = VideoResizeFilter::NearestNeighbor; diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 7e6fc498..3f1519cf 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -266,6 +266,10 @@ private: static KeyMappingSet _controllerKeys[4]; static bool _needControllerUpdate; + static int32_t _nsfAutoDetectSilenceDelay; + static int32_t _nsfMoveToNextTrackTime; + static bool _nsfDisableApuIrqs; + public: static uint32_t GetMesenVersion() { @@ -694,4 +698,26 @@ public: { return _controllerTypes[0] == ControllerType::ArkanoidController || _controllerTypes[1] == ControllerType::ArkanoidController || (_consoleType == ConsoleType::Famicom && _expansionDevice == ExpansionPortDevice::ArkanoidController); } + + static void SetNsfConfig(int32_t autoDetectSilence, int32_t moveToNextTrackTime, bool disableApuIrqs) + { + _nsfAutoDetectSilenceDelay = autoDetectSilence; + _nsfMoveToNextTrackTime = moveToNextTrackTime; + _nsfDisableApuIrqs = disableApuIrqs; + } + + static int32_t GetNsfAutoDetectSilenceDelay() + { + return _nsfAutoDetectSilenceDelay; + } + + static int32_t GetNsfMoveToNextTrackTime() + { + return _nsfMoveToNextTrackTime; + } + + static bool GetNsfDisableApuIrqs() + { + return _nsfDisableApuIrqs; + } }; diff --git a/Core/IMemoryHandler.h b/Core/IMemoryHandler.h index 7444a536..70c659cf 100644 --- a/Core/IMemoryHandler.h +++ b/Core/IMemoryHandler.h @@ -14,11 +14,22 @@ class MemoryRanges private: vector _ramReadAddresses; vector _ramWriteAddresses; + bool _allowOverride = false; public: vector* GetRAMReadAddresses() { return &_ramReadAddresses; } vector* GetRAMWriteAddresses() { return &_ramWriteAddresses; } + bool GetAllowOverride() + { + return _allowOverride; + } + + void SetAllowOverride() + { + _allowOverride = true; + } + void AddHandler(MemoryOperation operation, uint16_t start, uint16_t end = 0) { if(end == 0) { diff --git a/Core/MMC5Audio.h b/Core/MMC5Audio.h index beacc3db..bb0a9801 100644 --- a/Core/MMC5Audio.h +++ b/Core/MMC5Audio.h @@ -2,6 +2,7 @@ #include "stdafx.h" #include "SquareChannel.h" #include "BaseExpansionAudio.h" +#include "CPU.h" class MMC5Square : public SquareChannel { diff --git a/Core/MapperFactory.cpp b/Core/MapperFactory.cpp index 4c3389b3..93183064 100644 --- a/Core/MapperFactory.cpp +++ b/Core/MapperFactory.cpp @@ -76,6 +76,7 @@ #include "Nina01.h" #include "Nina03_06.h" #include "NROM.h" +#include "NsfMapper.h" #include "NtdecTc112.h" #include "Rambo1.h" #include "Sachen_143.h" @@ -109,6 +110,9 @@ #include "Waixing164.h" #include "Waixing176.h" +const uint16_t MapperFactory::FdsMapperID; +const uint16_t MapperFactory::NsfMapperID; + BaseMapper* MapperFactory::GetMapperFromID(RomData &romData) { #ifdef _DEBUG @@ -256,6 +260,7 @@ BaseMapper* MapperFactory::GetMapperFromID(RomData &romData) case 243: return new Sachen74LS374N(); case 246: return new Mapper246(); + case MapperFactory::NsfMapperID: return new NsfMapper(); case MapperFactory::FdsMapperID: return new FDS(); } diff --git a/Core/MapperFactory.h b/Core/MapperFactory.h index db850dff..914146a2 100644 --- a/Core/MapperFactory.h +++ b/Core/MapperFactory.h @@ -10,5 +10,6 @@ class MapperFactory public: static const uint16_t FdsMapperID = 65535; + static const uint16_t NsfMapperID = 65534; static shared_ptr InitializeFromFile(string romFilename, stringstream *filestream, string ipsFilename, int32_t archiveFileIndex); }; diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 27927262..a9023471 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -60,10 +60,10 @@ void MemoryManager::WriteRegister(uint16_t addr, uint8_t value) } } -void MemoryManager::InitializeMemoryHandlers(IMemoryHandler** memoryHandlers, IMemoryHandler* handler, vector *addresses) +void MemoryManager::InitializeMemoryHandlers(IMemoryHandler** memoryHandlers, IMemoryHandler* handler, vector *addresses, bool allowOverride) { for(uint16_t address : *addresses) { - if(memoryHandlers[address] != nullptr) { + if(!allowOverride && memoryHandlers[address] != nullptr) { throw std::runtime_error("Not supported"); } memoryHandlers[address] = handler; @@ -75,8 +75,8 @@ void MemoryManager::RegisterIODevice(IMemoryHandler *handler) MemoryRanges ranges; handler->GetMemoryRanges(ranges); - InitializeMemoryHandlers(_ramReadHandlers, handler, ranges.GetRAMReadAddresses()); - InitializeMemoryHandlers(_ramWriteHandlers, handler, ranges.GetRAMWriteAddresses()); + InitializeMemoryHandlers(_ramReadHandlers, handler, ranges.GetRAMReadAddresses(), ranges.GetAllowOverride()); + InitializeMemoryHandlers(_ramWriteHandlers, handler, ranges.GetRAMWriteAddresses(), ranges.GetAllowOverride()); } uint8_t* MemoryManager::GetInternalRAM() diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index 4f0411d7..6ec65774 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -33,6 +33,7 @@ class MemoryManager: public Snapshotable uint8_t ReadRegister(uint16_t addr); void WriteRegister(uint16_t addr, uint8_t value); + void InitializeMemoryHandlers(IMemoryHandler** memoryHandlers, IMemoryHandler* handler, vector *addresses, bool allowOverride); protected: void StreamState(bool saving); @@ -42,8 +43,6 @@ class MemoryManager: public Snapshotable ~MemoryManager(); void Reset(bool softReset); - - void InitializeMemoryHandlers(IMemoryHandler** memoryHandlers, IMemoryHandler* handler, vector *addresses); void RegisterIODevice(IMemoryHandler *handler); uint8_t DebugRead(uint16_t addr); diff --git a/Core/NsfLoader.h b/Core/NsfLoader.h new file mode 100644 index 00000000..c071e064 --- /dev/null +++ b/Core/NsfLoader.h @@ -0,0 +1,160 @@ +#pragma once +#include "stdafx.h" +#include "RomData.h" + +class NsfLoader +{ +private: + void Read(uint8_t* &data, uint8_t& dest) + { + dest = data[0]; + data++; + } + + void Read(uint8_t* &data, uint16_t& dest) + { + dest = data[0] | (data[1] << 8); + data += 2; + } + + void Read(uint8_t* &data, char* dest, size_t len) + { + memcpy(dest, data, len); + data += len; + } +protected: + void InitializeFromHeader(RomData& romData) + { + NsfHeader &header = romData.NsfHeader; + + romData.MapperID = MapperFactory::NsfMapperID; + + if(header.LoadAddress < 0x6000) { + romData.Error = true; + } + + if(header.Flags == 0x01) { + romData.System = GameSystem::NesPal; + } + + //Log window output + MessageManager::Log("[NSF] Region: " + string(header.Flags == 0x00 ? "NTSC" : (header.Flags == 0x01 ? "PAL" : "NTSC & PAL"))); + if(header.PlaySpeedNtsc > 0) { + MessageManager::Log("[NSF] Play speed (NTSC): " + std::to_string(1000000.0 / (double)header.PlaySpeedNtsc) + " Hz"); + } + if(header.PlaySpeedPal > 0) { + MessageManager::Log("[NSF] Play speed (PAL): " + std::to_string(1000000.0 / (double)header.PlaySpeedPal) + " Hz"); + } + MessageManager::Log("[NSF] Title: " + string(header.SongName)); + MessageManager::Log("[NSF] Artist: " + string(header.ArtistName)); + MessageManager::Log("[NSF] Copyright: " + string(header.CopyrightHolder)); + MessageManager::Log("[NSF] Ripper: " + string(header.RipperName)); + + stringstream ss; + ss << "[NSF] Load Address: 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << header.LoadAddress; + MessageManager::Log(ss.str()); + ss = stringstream(); + ss << "[NSF] Init Address: 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << header.InitAddress; + MessageManager::Log(ss.str()); + ss = stringstream(); + ss << "[NSF] Play Address: 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << header.PlayAddress; + MessageManager::Log(ss.str()); + + vector chips; + if(header.SoundChips & 0x01) { + chips.push_back("VRC6"); + } + if(header.SoundChips & 0x02) { + chips.push_back("VRC7"); + } + if(header.SoundChips & 0x04) { + chips.push_back("FDS"); + } + if(header.SoundChips & 0x08) { + chips.push_back("MMC5"); + } + if(header.SoundChips & 0x10) { + chips.push_back("Namco 163"); + } + if(header.SoundChips & 0x20) { + chips.push_back("Sunsoft 5B"); + } + if(chips.empty()) { + chips.push_back(""); + } + + ss = stringstream(); + for(size_t i = 0; i < chips.size(); i++) { + if(i > 0) { + ss << ", "; + } + ss << chips[i]; + } + + MessageManager::Log("[NSF] Sound Chips: " + ss.str()); + MessageManager::Log("[NSF] ROM size: " + std::to_string(romData.PrgRom.size() / 1024) + " KB"); + } + + void InitHeader(NsfHeader& header) + { + memset(&header, 0, sizeof(NsfHeader)); + for(int i = 0; i < 256; i++) { + //Used by NSFE + header.TrackLength[i] = -1; + header.TrackFade[i] = -1; + } + } + +public: + RomData LoadRom(vector& romFile) + { + RomData romData; + NsfHeader &header = romData.NsfHeader; + + InitHeader(header); + + uint8_t* data = romFile.data(); + Read(data, header.Header, 5); + Read(data, header.Version); + Read(data, header.TotalSongs); + Read(data, header.StartingSong); + Read(data, header.LoadAddress); + Read(data, header.InitAddress); + Read(data, header.PlayAddress); + Read(data, header.SongName, 32); + Read(data, header.ArtistName, 32); + Read(data, header.CopyrightHolder, 32); + Read(data, header.PlaySpeedNtsc); + Read(data, (char*)header.BankSetup, 8); + Read(data, header.PlaySpeedPal); + Read(data, header.Flags); + Read(data, header.SoundChips); + Read(data, (char*)header.Padding, 4); + + //Ensure strings are null terminated + header.SongName[31] = 0; + header.ArtistName[31] = 0; + header.CopyrightHolder[31] = 0; + + if(header.PlaySpeedNtsc == 16666 || header.PlaySpeedNtsc == 16667) { + header.PlaySpeedNtsc = 16639; + } + if(header.PlaySpeedPal == 20000) { + header.PlaySpeedPal = 19997; + } + + //Pad start of file to make the first block start at a multiple of 4k + romData.PrgRom.insert(romData.PrgRom.end(), header.LoadAddress % 4096, 0); + + romData.PrgRom.insert(romData.PrgRom.end(), data, data + romFile.size() - 0x80); + + //Pad out the last block to be a multiple of 4k + if(romData.PrgRom.size() % 4096 != 0) { + romData.PrgRom.insert(romData.PrgRom.end(), 4096 - (romData.PrgRom.size() % 4096), 0); + } + + InitializeFromHeader(romData); + + return romData; + } +}; \ No newline at end of file diff --git a/Core/NsfMapper.cpp b/Core/NsfMapper.cpp new file mode 100644 index 00000000..bd995dad --- /dev/null +++ b/Core/NsfMapper.cpp @@ -0,0 +1,389 @@ +#include "stdafx.h" +#include "NsfMapper.h" +#include "CPU.h" +#include "Console.h" + +NsfMapper* NsfMapper::_instance; + +NsfMapper::NsfMapper() +{ + _instance = this; + EmulationSettings::DisableOverclocking(true); +} + +NsfMapper::~NsfMapper() +{ + if(_instance == this) { + _instance = nullptr; + EmulationSettings::DisableOverclocking(false); + } +} + +NsfMapper * NsfMapper::GetInstance() +{ + return _instance; +} + +void NsfMapper::InitMapper() +{ + SetCpuMemoryMapping(0x3F00, 0x3FFF, GetWorkRam() + 0x2000, MemoryAccessType::Read); + memcpy(GetWorkRam() + 0x2000, _nsfBios, 0x100); + + //Clear all register settings + RemoveRegisterRange(0x0000, 0xFFFF, MemoryOperation::Any); + + AddRegisterRange(0x3E00, 0x3E13, MemoryOperation::Read); + AddRegisterRange(0x3E10, 0x3E13, MemoryOperation::Write); + + //NSF registers + AddRegisterRange(0x5FF6, 0x5FFF, MemoryOperation::Write); +} + +void NsfMapper::InitMapper(RomData& romData) +{ + _nsfHeader = romData.NsfHeader; + + _hasBankSwitching = HasBankSwitching(); + if(!_hasBankSwitching) { + //Update bank config to allow BIOS to select the right banks on init + int8_t startBank = (_nsfHeader.LoadAddress / 0x1000); + for(int32_t i = 0; i < (int32_t)GetPRGPageCount(); i++) { + if((startBank + i) > 0x0F) { + break; + } + if(startBank + i - 8 >= 0) { + _nsfHeader.BankSetup[startBank + i - 8] = i; + } + } + } + + _songNumber = _nsfHeader.StartingSong - 1; + _ntscSpeed = _nsfHeader.PlaySpeedNtsc * (CPU::ClockRateNtsc / 1000000.0); + _palSpeed = _nsfHeader.PlaySpeedPal * (CPU::ClockRatePal / 1000000.0); + + if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) { + AddRegisterRange(0x5000, 0x5015, MemoryOperation::Write); //Registers + AddRegisterRange(0x5205, 0x5206, MemoryOperation::Any); //Multiplication + SetCpuMemoryMapping(0x5C00, 0x5FFF, GetWorkRam() + 0x3000, MemoryAccessType::ReadWrite); //Exram + } + + if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) { + AddRegisterRange(0x9000, 0x9003, MemoryOperation::Write); + AddRegisterRange(0xA000, 0xA002, MemoryOperation::Write); + AddRegisterRange(0xB000, 0xB002, MemoryOperation::Write); + } + + if(_nsfHeader.SoundChips & NsfSoundChips::Namco) { + AddRegisterRange(0x4800, 0x4FFF, MemoryOperation::Any); + AddRegisterRange(0xF800, 0xFFFF, MemoryOperation::Write); + } + + if(_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) { + AddRegisterRange(0xC000, 0xFFFF, MemoryOperation::Write); + } + + if(_nsfHeader.SoundChips & NsfSoundChips::FDS) { + AddRegisterRange(0x4040, 0x4092, MemoryOperation::Any); + } +} + +void NsfMapper::Reset(bool softReset) +{ + if(EmulationSettings::GetNsfDisableApuIrqs()) { + CPU::SetIRQMask((uint8_t)IRQSource::External); + } + + if(!softReset) { + _songNumber = _nsfHeader.StartingSong - 1; + } + + _needInit = true; + _irqEnabled = false; + _irqCounter = 0; + _irqReloadValue = 0; + _irqStatus = NsfIrqType::None; + + _allowSilenceDetection = false; + _trackEndCounter = -1; + _trackEnded = false; + _trackFadeCounter = -1; + + InternalSelectTrack(_songNumber, false); + + //Reset/IRQ vector + AddRegisterRange(0xFFFC, 0xFFFF, MemoryOperation::Read); +} + +void NsfMapper::GetMemoryRanges(MemoryRanges& ranges) +{ + BaseMapper::GetMemoryRanges(ranges); + + //Allows us to override the PPU's range (0x3E00 - 0x3FFF) + ranges.SetAllowOverride(); + ranges.AddHandler(MemoryOperation::Read, 0x3E00, 0x3FFF); + ranges.AddHandler(MemoryOperation::Write, 0x3E00, 0x3FFF); +} + +bool NsfMapper::HasBankSwitching() +{ + for(int i = 0; i < 8; i++) { + if(_nsfHeader.BankSetup[i] != 0) { + return true; + } + } + return false; +} + +void NsfMapper::TriggerIrq(NsfIrqType type) +{ + if(type == NsfIrqType::Init) { + _trackEnded = false; + } + + _irqStatus = type; + CPU::SetIRQSource(IRQSource::External); +} + +void NsfMapper::ClearIrq() +{ + _irqStatus = NsfIrqType::None; + CPU::ClearIRQSource(IRQSource::External); +} + +void NsfMapper::ClockLengthAndFadeCounters() +{ + if(_trackEndCounter > 0) { + _trackEndCounter--; + if(_trackEndCounter == 0) { + _trackEnded = true; + } + } + + if((_trackEndCounter < 0 || _allowSilenceDetection) && EmulationSettings::GetNsfAutoDetectSilenceDelay() > 0) { + //No track length specified + if(SoundMixer::GetMuteFrameCount() * SoundMixer::CycleLength > _silenceDetectDelay) { + //Auto detect end of track after AutoDetectSilenceDelay (in ms) has gone by without sound + _trackEnded = true; + SoundMixer::ResetMuteFrameCount(); + } + } + + if(_trackEnded) { + if(_trackFadeCounter > 0) { + if(_fadeLength != 0) { + double fadeRatio = (double)_trackFadeCounter / (double)_fadeLength * 1.2; + SoundMixer::SetFadeRatio(std::max(0.0, fadeRatio - 0.2)); + } + _trackFadeCounter--; + } + + if(_trackFadeCounter <= 0) { + _songNumber = (_songNumber + 1) % _nsfHeader.TotalSongs; + InternalSelectTrack(_songNumber); + _trackEnded = false; + } + } +} + +void NsfMapper::ProcessCpuClock() +{ + if(_needInit) { + TriggerIrq(NsfIrqType::Init); + _needInit = false; + } + + if(_irqEnabled) { + _irqCounter--; + if(_irqCounter == 0) { + _irqCounter = _irqReloadValue; + TriggerIrq(NsfIrqType::Play); + } + } + + ClockLengthAndFadeCounters(); + + if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) { + _mmc5Audio.Clock(); + } + if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) { + _vrc6Audio.Clock(); + } + if(_nsfHeader.SoundChips & NsfSoundChips::Namco) { + _namcoAudio.Clock(); + } + if(_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) { + _sunsoftAudio.Clock(); + } + if(_nsfHeader.SoundChips & NsfSoundChips::FDS) { + _fdsAudio.Clock(); + } +} + +uint8_t NsfMapper::ReadRegister(uint16_t addr) +{ + if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) { + return _fdsAudio.ReadRegister(addr); + } else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && addr >= 0x4800 && addr <= 0x4FFF) { + return _namcoAudio.ReadRegister(addr); + } else { + switch(addr) { + case 0x3E00: return _nsfHeader.InitAddress & 0xFF; break; + case 0x3E01: return (_nsfHeader.InitAddress >> 8) & 0xFF; break; + case 0x3E02: return _nsfHeader.PlayAddress & 0xFF; break; + case 0x3E03: return (_nsfHeader.PlayAddress >> 8) & 0xFF; break; + case 0x3E04: return _ntscSpeed & 0xFF; break; + case 0x3E06: return (_ntscSpeed >> 8) & 0xFF; break; + case 0x3E05: return _palSpeed & 0xFF; break; + case 0x3E07: return (_palSpeed >> 8) & 0xFF; break; + + case 0x3E08: case 0x3E09: case 0x3E0A: case 0x3E0B: + case 0x3E0C: case 0x3E0D: case 0x3E0E: case 0x3E0F: + return _nsfHeader.BankSetup[addr & 0x07]; break; + + case 0x3E10: return _songNumber; break; + case 0x3E11: return _nsfHeader.Flags == 0x01 ? 0x01 : 0x00; break; + case 0x3E12: { + NsfIrqType result = _irqStatus; + ClearIrq(); + return (uint8_t)result; + } + + case 0x3E13: return _nsfHeader.SoundChips & 0x3F; break; + + case 0x5205: return (_mmc5MultiplierValues[0] * _mmc5MultiplierValues[1]) & 0xFF; + case 0x5206: return (_mmc5MultiplierValues[0] * _mmc5MultiplierValues[0]) >> 8; + + //Reset/irq vectors + case 0xFFFC: case 0xFFFD: case 0xFFFE: case 0xFFFF: + return _nsfBios[addr & 0xFF]; + } + } + + //Open bus + return (addr >> 8); +} + +void NsfMapper::WriteRegister(uint16_t addr, uint8_t value) +{ + if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) { + _fdsAudio.WriteRegister(addr, value); + } else if((_nsfHeader.SoundChips & NsfSoundChips::MMC5) && addr >= 0x5000 && addr <= 0x5015) { + _mmc5Audio.WriteRegister(addr, value); + } else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && (addr >= 0x4800 && addr <= 0x4FFF || addr >= 0xF800 && addr <= 0xFFFF)) { + _namcoAudio.WriteRegister(addr, value); + + //Technically we should call this, but doing so breaks some NSFs + /*if(addr >= 0xF800 && _nsfHeader.SoundChips & NsfSoundChips::Sunsoft) { + _sunsoftAudio.WriteRegister(addr, value); + }*/ + } else if((_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) && addr >= 0xC000 && addr <= 0xFFFF) { + _sunsoftAudio.WriteRegister(addr, value); + } else { + switch(addr) { + case 0x3E10: _irqReloadValue = (_irqReloadValue & 0xFF00) | value; break; + case 0x3E11: _irqReloadValue = (_irqReloadValue & 0xFF) | (value << 8); break; + + case 0x3E12: + _irqCounter = _irqReloadValue * 5; + _irqEnabled = (value > 0); + break; + + case 0x3E13: + _irqCounter = _irqReloadValue; + break; + + //MMC5 multiplication + case 0x5205: _mmc5MultiplierValues[0] = value; break; + case 0x5206: _mmc5MultiplierValues[1] = value; break; + + case 0x5FF6: case 0x5FF7: { + uint16_t addrOffset = (addr == 0x5FF7 ? 0x1000 : 0x0000); + if(value == 0xFF || value == 0xFE) { + if(!_hasBankSwitching && _nsfHeader.LoadAddress < 0x7000) { + //Load address = 0x6000, put bank 0 at $6000 + SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, value & 0x01, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite); + } else if(!_hasBankSwitching && addrOffset == 0x1000 && _nsfHeader.LoadAddress < 0x8000) { + //Load address = 0x7000, put bank 0 at $7000 + SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, 0, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite); + } else { + //Set ram at $6000/7000 (default behavior) + SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, value & 0x01, PrgMemoryType::WorkRam); + } + } else { + SetCpuMemoryMapping(0x6000 + addrOffset, 0x6FFF + addrOffset, value, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite); + } + break; + } + + case 0x5FF8: case 0x5FF9: case 0x5FFA: case 0x5FFB: + case 0x5FFC: case 0x5FFD: case 0x5FFE: case 0x5FFF: + SetCpuMemoryMapping(0x8000 + (addr & 0x07) * 0x1000, 0x8FFF + (addr & 0x07) * 0x1000, value, PrgMemoryType::PrgRom, (addr <= 0x5FFD && (_nsfHeader.SoundChips & NsfSoundChips::FDS)) ? MemoryAccessType::ReadWrite : MemoryAccessType::Read); + break; + + case 0x9000: case 0x9001: case 0x9002: case 0x9003: case 0xA000: case 0xA001: case 0xA002: case 0xB000: case 0xB001: case 0xB002: + _vrc6Audio.WriteRegister(addr, value); + break; + } + } +} + +uint32_t NsfMapper::GetClockRate() +{ + return ((_nsfHeader.Flags & 0x01) ? CPU::ClockRatePal : CPU::ClockRateNtsc); +} + +void NsfMapper::InternalSelectTrack(uint8_t trackNumber, bool requestReset) +{ + _songNumber = trackNumber; + if(requestReset) { + //Need to change track while running + //Some NSFs keep the interrupt flag on at all times, preventing us from triggering an IRQ to change tracks + //Forcing the console to reset ensures changing tracks always works, even with a bad NSF file + Console::RequestReset(); + } else { + //Selecting tracking after a reset + SoundMixer::SetFadeRatio(1.0); + + //Set track length/fade counters (NSFe) + if(_nsfHeader.TrackLength[trackNumber] >= 0) { + _trackEndCounter = (double)_nsfHeader.TrackLength[trackNumber] / 1000.0 * GetClockRate(); + _allowSilenceDetection = false; + } else if(_nsfHeader.TotalSongs > 1) { + //Only apply a maximum duration to multi-track NSFs + //Single track NSFs will loop or restart after a portion of silence + //Substract 1 sec from default track time to account for 1 sec default fade time + _trackEndCounter = (EmulationSettings::GetNsfMoveToNextTrackTime() - 1) * GetClockRate(); + _allowSilenceDetection = true; + } + if(_nsfHeader.TrackFade[trackNumber] >= 0) { + _trackFadeCounter = (double)_nsfHeader.TrackFade[trackNumber] / 1000.0 * GetClockRate(); + } else { + //Default to 1 sec fade if none is specified (negative number) + _trackFadeCounter = GetClockRate(); + } + + _silenceDetectDelay = (double)EmulationSettings::GetNsfAutoDetectSilenceDelay() / 1000.0 * GetClockRate(); + + _fadeLength = _trackFadeCounter; + TriggerIrq(NsfIrqType::Init); + } +} + +void NsfMapper::SelectTrack(uint8_t trackNumber) +{ + if(trackNumber < _nsfHeader.TotalSongs) { + Console::Pause(); + InternalSelectTrack(trackNumber); + Console::Resume(); + } +} + +uint8_t NsfMapper::GetCurrentTrack() +{ + return _songNumber; +} + +NsfHeader NsfMapper::GetNsfHeader() +{ + return _nsfHeader; +} diff --git a/Core/NsfMapper.h b/Core/NsfMapper.h new file mode 100644 index 00000000..65114096 --- /dev/null +++ b/Core/NsfMapper.h @@ -0,0 +1,279 @@ +#pragma once +#include "BaseMapper.h" +#include "MMC5Audio.h" +#include "Vrc6Audio.h" +#include "FdsAudio.h" +#include "Namco163Audio.h" +#include "Sunsoft5bAudio.h" + +enum class NsfIrqType +{ + Init = 0, + Stop = 1, + Play = 2, + None = 0xFF +}; + +class NsfMapper : public BaseMapper +{ +private: + static NsfMapper *_instance; + + enum NsfSoundChips + { + VRC6 = 0x01, + VRC7 = 0x02, + FDS = 0x04, + MMC5 = 0x08, + Namco = 0x10, + Sunsoft = 0x20 + }; + + NsfHeader _nsfHeader; + MMC5Audio _mmc5Audio; + Vrc6Audio _vrc6Audio; + FdsAudio _fdsAudio; + Namco163Audio _namcoAudio; + Sunsoft5bAudio _sunsoftAudio; + + bool _needInit = false; + bool _irqEnabled = false; + uint32_t _irqReloadValue = 0; + uint32_t _irqCounter = 0; + NsfIrqType _irqStatus = NsfIrqType::None; + uint8_t _mmc5MultiplierValues[2]; + + int32_t _trackEndCounter; + int32_t _trackFadeCounter; + int32_t _fadeLength; + uint32_t _silenceDetectDelay; + bool _trackEnded; + bool _allowSilenceDetection; + + bool _hasBankSwitching; + + uint16_t _ntscSpeed; + uint16_t _palSpeed; + + uint8_t _songNumber = 0; + + /***************************************** + * NSF BIOS by Quietust, all credits to him! + * Taken from the Nintendulator source code: + * http://www.qmtpro.com/~nes/nintendulator/ + * See below for assembly source code + ******************************************/ + uint8_t _nsfBios[256] = { + 0xFF,0xFF,0xFF,0x78,0xA2,0xFF,0x8E,0x17,0x40,0xE8,0x20,0x30,0x3F,0x8E,0x00,0x20, + 0x8E,0x01,0x20,0x8E,0x12,0x3E,0x58,0x4C,0x17,0x3F,0x48,0x8A,0x48,0x98,0x48,0xAE, + 0x12,0x3E,0xF0,0x59,0xCA,0xF0,0xDC,0x20,0xF9,0x3F,0x68,0xA8,0x68,0xAA,0x68,0x40, + 0x8E,0x15,0x40,0xAD,0x13,0x3E,0x4A,0x90,0x09,0x8E,0x02,0x90,0x8E,0x02,0xA0,0x8E, + 0x02,0xB0,0x4A,0x90,0x0D,0xA0,0x20,0x8C,0x10,0x90,0x8E,0x30,0x90,0xC8,0xC0,0x26, + 0xD0,0xF5,0x4A,0x90,0x0B,0xA0,0x80,0x8C,0x83,0x40,0x8C,0x87,0x40,0x8C,0x89,0x40, + 0x4A,0x90,0x03,0x8E,0x15,0x50,0x4A,0x90,0x08,0xCA,0x8E,0x00,0xF8,0xE8,0x8E,0x00, + 0x48,0x4A,0x90,0x08,0xA0,0x07,0x8C,0x00,0xC0,0x8C,0x00,0xE0,0x60,0x20,0x30,0x3F, + 0x8A,0xCA,0x9A,0x8E,0xF7,0x5F,0xCA,0x8E,0xF6,0x5F,0xA2,0x7F,0x85,0x00,0x86,0x01, + 0xA8,0xA2,0x27,0x91,0x00,0xC8,0xD0,0xFB,0xCA,0x30,0x0A,0xC6,0x01,0xE0,0x07,0xD0, + 0xF2,0x86,0x01,0xF0,0xEE,0xA2,0x14,0xCA,0x9D,0x00,0x40,0xD0,0xFA,0xA2,0x07,0xBD, + 0x08,0x3E,0x9D,0xF8,0x5F,0xCA,0x10,0xF7,0xA0,0x0F,0x8C,0x15,0x40,0xAD,0x13,0x3E, + 0x29,0x04,0xF0,0x10,0xAD,0x0E,0x3E,0xF0,0x03,0x8D,0xF6,0x5F,0xAD,0x0F,0x3E,0xF0, + 0x03,0x8D,0xF7,0x5F,0xAE,0x11,0x3E,0xBD,0x04,0x3E,0x8D,0x10,0x3E,0xBD,0x06,0x3E, + 0x8D,0x11,0x3E,0x8C,0x12,0x3E,0xAD,0x12,0x3E,0x58,0xAD,0x10,0x3E,0x20,0xF6,0x3F, + 0x8D,0x13,0x3E,0x4C,0x17,0x3F,0x6C,0x00,0x3E,0x6C,0x02,0x3E,0x03,0x3F,0x1A,0x3F + }; + +private: + void TriggerIrq(NsfIrqType type); + void ClearIrq(); + + bool HasBankSwitching(); + uint32_t GetClockRate(); + + void InternalSelectTrack(uint8_t trackNumber, bool requestReset = true); + void ClockLengthAndFadeCounters(); + +protected: + uint16_t GetPRGPageSize() { return 0x1000; } + uint16_t GetCHRPageSize() { return 0x2000; } + uint32_t GetWorkRamSize() { return 0x4000; } + uint32_t GetWorkRamPageSize() { return 0x1000; } + bool AllowRegisterRead() { return true; } + + void InitMapper(); + void InitMapper(RomData& romData); + void Reset(bool softReset); + void GetMemoryRanges(MemoryRanges &ranges); + + void ProcessCpuClock(); + uint8_t ReadRegister(uint16_t addr); + void WriteRegister(uint16_t addr, uint8_t value); + +public: + NsfMapper(); + ~NsfMapper(); + + static NsfMapper* GetInstance(); + + void SelectTrack(uint8_t trackNumber); + uint8_t GetCurrentTrack(); + NsfHeader GetNsfHeader(); +}; + +/* +;BIOS Source Code +;NSF BIOS by Quietust, all credits to him! +;Taken from the Nintendulator source code: +;http://www.qmtpro.com/~nes/nintendulator/ + +;NSF Mapper +;R $3E00-$3E01 - INIT address +;R $3E02-$3E03 - PLAY address +;R $3E04/$3E06 - NTSC speed value +;R $3E05/$3E07 - PAL speed value +;R $3E08-$3E0F - Initial bankswitch values +;R $3E10 - Song Number (start at 0) +;R $3E11 - NTSC/PAL setting (0 for NTSC, 1 for PAL) +;W $3E10-$3E11 - IRQ counter (for PLAY) +;R $3E12 - IRQ status (0=INIT, 1=STOP, 2+=PLAY) +;W $3E12 - IRQ enable (for PLAY) +;R $3E13 - Sound chip info +;W $3E13 - clear watchdog timer + +.org $3F00 +.db $FF,$FF,$FF ;pad to 256 bytes +reset:SEI + LDX #$FF + STX $4017 ;kill frame IRQs + INX + JSR silence ;silence all sound channels + STX $2000 ;we don't rely on the presence of a PPU + STX $2001 ;but we don't want it getting in the way + STX $3E12 ;kill PLAY timer + CLI +loop: JMP loop + +irq: PHA + TXA + PHA + TYA + PHA + LDX $3E12 ;check IRQ status + BEQ init + DEX + BEQ reset ;"Proper" way to play a tune + JSR song_play ;1) Call the play address of the music at periodic intervals determined by the speed words. + PLA + TAY + PLA + TAX + PLA + RTI + +.module silence +silence: ;X=0 coming in, must also be coming out + STX $4015 ;silence all sound channels + LDA $3E13 + LSR A + BCC _1 + STX $9002 + STX $A002 + STX $B002 ;stop VRC6 +_1: LSR A + BCC _2 + LDY #$20 +__2: STY $9010 + STX $9030 ;stop VRC7 + INY + CPY #$26 + BNE __2 +_2: LSR A + BCC _3 + LDY #$80 + STY $4083 + STY $4087 + STY $4089 ;stop FDS +_3: LSR A + BCC _4 + STX $5015 ;stop MMC5 +_4: LSR A + BCC _5 + DEX + STX $F800 + INX + STX $4800 ;stop Namco-163 +_5: LSR A + BCC _6 + LDY #$07 + STY $C000 + STY $E000 ;stop SUN5 +_6: RTS + +.module init ;"Proper" way to init a tune +init: JSR silence + TXA ;(X=0) + DEX + TXS ;clear the stack + STX $5FF7 ;1.5) Map RAM to $6000-$7FFF + DEX + STX $5FF6 ;(banks FE/FF are treated as RAM) + + LDX #$7F + STA $00 + STX $01 + TAY + LDX #$27 +_1: STA ($00),Y ;1) Clear all RAM at $0000-$07FF. + INY ;2) Clear all RAM at $6000-$7FFF. + BNE _1 + DEX + BMI _2 + DEC $01 + CPX #$07 + BNE _1 + STX $01 + BEQ _1 + +_2: LDX #$14 +_3: DEX + STA $4000,X ;3) Init the sound registers by writing $00 to $4000-$4013 + BNE _3 + + LDX #$07 +_4: LDA $3E08,X ;5) If this is a banked tune, load the bank values from the header into $5FF8-$5FFF. + STA $5FF8,X ;For this player, *all* tunes are considered banked (the loader will fill in appropriate values) + DEX + BPL _4 + + LDY #$0F ;4) Set volume register $4015 to $0F. + STY $4015 + + LDA $3E13 ;For FDS games, banks E/F also get mapped to 6/7 + AND #$04 ;For non-FDS games, we don't want to do this + BEQ _6 + LDA $3E0E ;if these are zero, let's leave them as plain RAM + BEQ _5 + STA $5FF6 +_5: LDA $3E0F ;TODO - need to allow FDS NSF data to be writeable + BEQ _6 + STA $5FF7 + +_6: LDX $3E11 ;4.5) Set up the PLAY timer. Which word to use is determined by which mode you are in - PAL or NTSC. + LDA $3E04,X ;X is 0 for NTSC and 1 for PAL, as needed for #6 below + STA $3E10 + LDA $3E06,X + STA $3E11 + STY $3E12 + LDA $3E12 ;if we have a pending IRQ here, we need to cancel it NOW + CLI ;re-enable interrupts now - this way, we can allow the init routine to not return (ex: for raw PCM) + + LDA $3E10 ;6) Set the accumulator and X registers for the desired song. + JSR song_init ;7) Call the music init routine. + STA $3E13 ;kill the watchdog timer (and fire a 'Play' interrupt after 1 frame; otherwise, it'll wait 4 frames) + JMP loop ;we don't actually RTI from here; this way, the init code is faster + +.module song +song_init: JMP ($3E00) +song_play: JMP ($3E02) +.dw reset,irq ;no NMI vector +.end +*/ diff --git a/Core/NsfPpu.h b/Core/NsfPpu.h new file mode 100644 index 00000000..c6e8d922 --- /dev/null +++ b/Core/NsfPpu.h @@ -0,0 +1,22 @@ +#pragma once +#include "stdafx.h" +#include "PPU.h" + +class NsfPpu : public PPU +{ +protected: + void DrawPixel() + { + } + + void SendFrame() + { + MessageManager::SendNotification(ConsoleNotificationType::PpuFrameDone); + } + +public: + NsfPpu(MemoryManager* memoryManager) : PPU(memoryManager) + { + _simpleMode = true; + } +}; \ No newline at end of file diff --git a/Core/NsfeLoader.h b/Core/NsfeLoader.h new file mode 100644 index 00000000..f8bb90e1 --- /dev/null +++ b/Core/NsfeLoader.h @@ -0,0 +1,194 @@ +#pragma once +#include "stdafx.h" +#include "RomData.h" +#include +#include "NsfLoader.h" + +class NsfeLoader : public NsfLoader +{ +private: + void Read(uint8_t* &data, uint8_t& dest) + { + dest = data[0]; + data++; + } + + void Read(uint8_t* &data, uint16_t& dest) + { + dest = data[0] | (data[1] << 8); + data += 2; + } + + void Read(uint8_t* &data, uint32_t& dest) + { + dest = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + data += 4; + } + + void Read(uint8_t* &data, int32_t& dest) + { + dest = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + data += 4; + } + + void Read(uint8_t* &data, char* dest, size_t len) + { + memcpy(dest, data, len); + data += len; + } + + vector ReadStrings(uint8_t* &data, uint8_t* chunkEnd) + { + vector strings; + stringstream ss; + while(data < chunkEnd) { + if(data[0] == 0) { + //end of string + strings.push_back(ss.str()); + ss = stringstream(); + } else { + ss << (char)data[0]; + } + data++; + } + + //truncate all strings to 255 characters + null + for(int i = 0; i < strings.size(); i++) { + strings[i] = strings[i].substr(0, std::min((int)strings[i].size(), 255)); + } + + return strings; + } + + string ReadFourCC(uint8_t* &data) + { + stringstream ss; + for(int i = 0; i < 4; i++) { + ss << (char)data[i]; + } + data += 4; + return ss.str(); + } + + bool ReadChunk(uint8_t* &data, uint8_t* dataEnd, RomData& romData) + { + NsfHeader& header = romData.NsfHeader; + + uint32_t length; + Read(data, length); + + uint8_t* chunkEnd = data + 4 + length; + + if(chunkEnd > dataEnd) { + return false; + } + + string fourCC = ReadFourCC(data); + if(fourCC.compare("INFO") == 0) { + Read(data, header.LoadAddress); + Read(data, header.InitAddress); + Read(data, header.PlayAddress); + Read(data, header.Flags); + Read(data, header.SoundChips); + Read(data, header.TotalSongs); + Read(data, header.StartingSong); + + header.PlaySpeedNtsc = 16639; + header.PlaySpeedPal = 19997; + + //Adjust to match NSF spec + header.StartingSong++; + } else if(fourCC.compare("DATA") == 0) { + //Pad start of file to make the first block start at a multiple of 4k + romData.PrgRom.insert(romData.PrgRom.end(), header.LoadAddress % 4096, 0); + + romData.PrgRom.insert(romData.PrgRom.end(), (uint8_t*)data, data+length); + + //Pad out the last block to be a multiple of 4k + if(romData.PrgRom.size() % 4096 != 0) { + romData.PrgRom.insert(romData.PrgRom.end(), 4096 - (romData.PrgRom.size() % 4096), 0); + } + } else if(fourCC.compare("NEND") == 0) { + //End of file + romData.Error = false; + return false; + } else if(fourCC.compare("BANK") == 0) { + memset(header.BankSetup, 0, sizeof(header.BankSetup)); + Read(data, (char*)header.BankSetup, std::min(8, (int)length)); + } else if(fourCC.compare("plst") == 0) { + //not supported + } else if(fourCC.compare("time") == 0) { + int i = 0; + while(data < chunkEnd) { + Read(data, header.TrackLength[i]); + i++; + } + } else if(fourCC.compare("fade") == 0) { + int i = 0; + while(data < chunkEnd) { + Read(data, header.TrackFade[i]); + i++; + } + } else if(fourCC.compare("tlbl") == 0) { + vector trackNames = ReadStrings(data, chunkEnd); + stringstream ss; + for(string &trackName : trackNames) { + ss << trackName; + ss << "[!|!]"; + } + strcpy_s(header.TrackName, ss.str().c_str()); + + } else if(fourCC.compare("auth") == 0) { + vector infoStrings = ReadStrings(data, chunkEnd); + + if(infoStrings.size() > 0) { + strcpy_s(header.SongName, infoStrings[0].c_str()); + if(infoStrings.size() > 1) { + strcpy_s(header.ArtistName, infoStrings[1].c_str()); + if(infoStrings.size() > 2) { + strcpy_s(header.CopyrightHolder, infoStrings[2].c_str()); + if(infoStrings.size() > 3) { + strcpy_s(header.RipperName, infoStrings[3].c_str()); + } + } + } + } + } else if(fourCC.compare("text") == 0) { + //not supported + } else { + if(fourCC[0] >= 'A' && fourCC[0] <= 'Z') { + //unknown required block, can't read file + return false; + } + } + + data = chunkEnd; + + return true; + } + +public: + RomData LoadRom(vector& romFile) + { + RomData romData; + NsfHeader &header = romData.NsfHeader; + + InitHeader(header); + + uint8_t* data = romFile.data() + 4; + + memset(header.SongName, 0, sizeof(header.SongName)); + memset(header.ArtistName, 0, sizeof(header.ArtistName)); + memset(header.CopyrightHolder, 0, sizeof(header.CopyrightHolder)); + + //Will be set to false when we read NEND block + romData.Error = true; + while(ReadChunk(data, data + romFile.size(), romData)) { + //Read all chunks + } + + InitializeFromHeader(romData); + + return romData; + } +}; \ No newline at end of file diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 0ef71cd8..b59030f0 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -25,6 +25,8 @@ PPU::PPU(MemoryManager *memoryManager) memset(_spriteRAM, 0xFF, 0x100); memset(_secondarySpriteRAM, 0xFF, 0x20); + _simpleMode = false; + Reset(); } @@ -877,14 +879,23 @@ void PPU::Exec() Debugger::ProcessPpuCycle(); - if(_scanline != -1 && _scanline < 240) { - ProcessVisibleScanline(); - } else if(_scanline == -1) { - ProcessPrerenderScanline(); - } else if(_scanline == _nmiScanline) { - BeginVBlank(); - } else if(_scanline == _vblankEnd) { - EndVBlank(); + if(!_simpleMode) { + if(_scanline != -1 && _scanline < 240) { + ProcessVisibleScanline(); + } else if(_scanline == -1) { + ProcessPrerenderScanline(); + } else if(_scanline == _nmiScanline) { + BeginVBlank(); + } else if(_scanline == _vblankEnd) { + EndVBlank(); + } + } else { + //Used by NSF player to speed things up + if(_scanline == _nmiScanline) { + BeginVBlank(); + } else if(_scanline == _vblankEnd) { + EndVBlank(); + } } //Rendering enabled flag is apparently set with a 1 cycle delay (i.e setting it at cycle 5 will render cycle 6 like cycle 5 and then take the new settings for cycle 7) diff --git a/Core/PPU.h b/Core/PPU.h index 07faafff..e7e40008 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -152,6 +152,9 @@ class PPU : public IMemoryHandler, public Snapshotable double _cyclesNeeded; + //Used by NSF player for higher performance + bool _simpleMode; + //Used to resolve a race condition when the 2nd write to $2006 occurs at cycle 255 (i.e approx. the same time as the PPU tries to increase Y scrolling) bool _skipScrollingIncrement; @@ -204,6 +207,11 @@ class PPU : public IMemoryHandler, public Snapshotable } } + void SetSimpleMode() + { + _simpleMode = true; + } + void StreamState(bool saving); public: diff --git a/Core/RomData.h b/Core/RomData.h index f9dd1a3a..0417597a 100644 --- a/Core/RomData.h +++ b/Core/RomData.h @@ -196,17 +196,43 @@ struct NESHeader } }; +struct NsfHeader +{ + char Header[5]; + uint8_t Version; + uint8_t TotalSongs; + uint8_t StartingSong; + uint16_t LoadAddress; + uint16_t InitAddress; + uint16_t PlayAddress; + char SongName[256]; + char ArtistName[256]; + char CopyrightHolder[256]; + uint16_t PlaySpeedNtsc; + uint8_t BankSetup[8]; + uint16_t PlaySpeedPal; + uint8_t Flags; + uint8_t SoundChips; + uint8_t Padding[4]; + + //NSFe extensions + char RipperName[256]; + char TrackName[20000]; + int32_t TrackLength[256]; + int32_t TrackFade[256]; +}; + struct RomData { string RomName; string Filename; - uint16_t MapperID; + uint16_t MapperID = 0; uint8_t SubMapperID = 0; GameSystem System = GameSystem::Unknown; bool HasBattery = false; bool HasTrainer = false; - MirroringType MirroringType; + MirroringType MirroringType = MirroringType::Horizontal; int32_t ChrRamSize = -1; bool IsNes20Header = false; @@ -217,9 +243,10 @@ struct RomData vector> FdsDiskData; vector RawData; - uint32_t Crc32; + uint32_t Crc32 = 0; bool Error = false; NESHeader NesHeader; + NsfHeader NsfHeader; }; \ No newline at end of file diff --git a/Core/RomLoader.cpp b/Core/RomLoader.cpp index 1350d5d9..7e03d48f 100644 --- a/Core/RomLoader.cpp +++ b/Core/RomLoader.cpp @@ -5,6 +5,8 @@ #include "RomLoader.h" #include "iNesLoader.h" #include "FdsLoader.h" +#include "NsfLoader.h" +#include "NsfeLoader.h" vector RomLoader::GetArchiveRomList(string filename) { @@ -17,11 +19,11 @@ vector RomLoader::GetArchiveRomList(string filename) if(memcmp(header, "PK", 2) == 0) { ZipReader reader; reader.LoadArchive(filename); - return reader.GetFileList({ ".nes", ".fds" }); + return reader.GetFileList({ ".nes", ".fds", ".nsf", ".nsfe" }); } else if(memcmp(header, "7z", 2) == 0) { SZReader reader; reader.LoadArchive(filename); - return reader.GetFileList({ ".nes", ".fds" }); + return reader.GetFileList({ ".nes", ".fds", ".nsf", ".nsfe" }); } } return{}; @@ -36,7 +38,7 @@ bool RomLoader::LoadFromArchive(istream &zipFile, ArchiveReader& reader, int32_t reader.LoadArchive(buffer, fileSize); - vector fileList = reader.GetFileList({ ".nes", ".fds" }); + vector fileList = reader.GetFileList({ ".nes", ".fds", ".nsf", ".nsfe" }); int32_t currentIndex = 0; if(archiveFileIndex > (int32_t)fileList.size()) { return false; @@ -110,6 +112,12 @@ bool RomLoader::LoadFromMemory(uint8_t* buffer, size_t length, string romName) } else if(memcmp(buffer, "FDS\x1a", 4) == 0 || memcmp(buffer, "\x1*NINTENDO-HVC*", 15) == 0) { FdsLoader loader; _romData = loader.LoadRom(fileData, _filename); + } else if(memcmp(buffer, "NESM\x1a", 5) == 0) { + NsfLoader loader; + _romData = loader.LoadRom(fileData); + } else if(memcmp(buffer, "NSFE", 4) == 0) { + NsfeLoader loader; + _romData = loader.LoadRom(fileData); } else { MessageManager::Log("Invalid rom file."); _romData.Error = true; @@ -159,7 +167,7 @@ bool RomLoader::LoadFile(string filename, istream *filestream, string ipsFilenam } else if(memcmp(header, "7z", 2) == 0) { SZReader reader; return LoadFromArchive(*input, reader, archiveFileIndex); - } else if(memcmp(header, "NES\x1a", 4) == 0 || memcmp(header, "FDS\x1a", 4) == 0 || memcmp(header, "\x1*NINTENDO-HVC*", 15) == 0) { + } else if(memcmp(header, "NES\x1a", 4) == 0 || memcmp(header, "NESM\x1a", 5) == 0 || memcmp(header, "NSFE", 4) == 0 || memcmp(header, "FDS\x1a", 4) == 0 || memcmp(header, "\x1*NINTENDO-HVC*", 15) == 0) { if(archiveFileIndex > 0) { return false; } diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index aeae4b0d..f03946bc 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -6,6 +6,8 @@ IAudioDevice* SoundMixer::AudioDevice = nullptr; unique_ptr SoundMixer::_waveRecorder; SimpleLock SoundMixer::_waveRecorderLock; +double SoundMixer::_fadeRatio; +uint32_t SoundMixer::_muteFrameCount; SoundMixer::SoundMixer() { @@ -27,7 +29,7 @@ SoundMixer::~SoundMixer() void SoundMixer::StreamState(bool saving) { - Stream(_clockRate, _sampleRate, _expansionAudioType); + Stream(_clockRate, _sampleRate); if(!saving) { Reset(); @@ -56,6 +58,9 @@ void SoundMixer::StopAudio(bool clearBuffer) void SoundMixer::Reset() { + _fadeRatio = 1.0; + _muteFrameCount = 0; + _previousOutput = 0; blip_clear(_blipBuf); @@ -153,15 +158,12 @@ int16_t SoundMixer::GetOutputVolume() uint16_t squareVolume = (uint16_t)(95.52 / (8128.0 / squareOutput + 100.0) * 5000); uint16_t tndVolume = (uint16_t)(163.67 / (24329.0 / tndOutput + 100.0) * 5000); - int16_t expansionOutput = 0; - switch(_expansionAudioType) { - case AudioChannel::FDS: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 20); break; - case AudioChannel::MMC5: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 40); break; - case AudioChannel::Namco163: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 20); break; - case AudioChannel::Sunsoft5B: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 20); break; - case AudioChannel::VRC6: expansionOutput = (int16_t)(_currentOutput[ExpansionAudioIndex] * _volumes[ExpansionAudioIndex] * 75); break; - } - return squareVolume + tndVolume + expansionOutput; + return (int16_t)(squareVolume + tndVolume + + GetChannelOutput(AudioChannel::FDS) * 20 + + GetChannelOutput(AudioChannel::MMC5) * 40 + + GetChannelOutput(AudioChannel::Namco163) * 20 + + GetChannelOutput(AudioChannel::Sunsoft5B) * 15 + + GetChannelOutput(AudioChannel::VRC6) * 75); } void SoundMixer::AddDelta(AudioChannel channel, uint32_t time, int16_t delta) @@ -172,25 +174,14 @@ void SoundMixer::AddDelta(AudioChannel channel, uint32_t time, int16_t delta) } } -void SoundMixer::AddExpansionAudioDelta(uint32_t time, int16_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) { double masterVolume = EmulationSettings::GetMasterVolume(); sort(_timestamps.begin(), _timestamps.end()); _timestamps.erase(std::unique(_timestamps.begin(), _timestamps.end()), _timestamps.end()); + bool muteFrame = true; + int16_t originalOutput = _previousOutput; for(size_t i = 0, len = _timestamps.size(); i < len; i++) { uint32_t stamp = _timestamps[i]; for(int j = 0; j < MaxChannelCount; j++) { @@ -198,14 +189,32 @@ void SoundMixer::EndFrame(uint32_t time) } int16_t currentOutput = GetOutputVolume(); - blip_add_delta(_blipBuf, stamp, (int)((currentOutput - _previousOutput) * masterVolume)); - _previousOutput = currentOutput; + blip_add_delta(_blipBuf, stamp, (int)((currentOutput - _previousOutput) * masterVolume * _fadeRatio)); + + if(currentOutput != _previousOutput) { + if(std::abs(currentOutput - _previousOutput) > 100) { + muteFrame = false; + } + _previousOutput = currentOutput; + } } + + if(std::abs(originalOutput - _previousOutput) > 1500) { + //Count mute frames (10000 cycles each) - used by NSF player + muteFrame = false; + } + blip_end_frame(_blipBuf, time); + if(muteFrame) { + _muteFrameCount++; + } else { + _muteFrameCount = 0; + } + //Reset everything for(int i = 0; i < MaxChannelCount; i++) { - _volumes[i] = EmulationSettings::GetChannelVolume(i < 5 ? (AudioChannel)i : _expansionAudioType); + _volumes[i] = EmulationSettings::GetChannelVolume((AudioChannel)i); } _timestamps.clear(); @@ -227,4 +236,19 @@ void SoundMixer::StopRecording() bool SoundMixer::IsRecording() { return _waveRecorder.get() != nullptr; +} + +void SoundMixer::SetFadeRatio(double fadeRatio) +{ + _fadeRatio = fadeRatio; +} + +uint32_t SoundMixer::GetMuteFrameCount() +{ + return _muteFrameCount; +} + +void SoundMixer::ResetMuteFrameCount() +{ + _muteFrameCount = 0; } \ No newline at end of file diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h index 235545b6..1940dc15 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -20,14 +20,14 @@ public: private: static unique_ptr _waveRecorder; static SimpleLock _waveRecorderLock; + static double _fadeRatio; + static uint32_t _muteFrameCount; static IAudioDevice* AudioDevice; static const uint32_t MaxSampleRate = 48000; static const uint32_t MaxSamplesPerFrame = MaxSampleRate / 60 * 4; //x4 to allow CPU overclocking up to 10x - static const uint32_t MaxChannelCount = 6; - static const uint32_t ExpansionAudioIndex = MaxChannelCount - 1; + static const uint32_t MaxChannelCount = 11; - AudioChannel _expansionAudioType; LowPassFilter _lowPassFilter; StereoPanningFilter _stereoPanning; StereoDelayFilter _stereoDelay; @@ -73,6 +73,11 @@ public: static void StopRecording(); static bool IsRecording(); + //For NSF/NSFe + static uint32_t GetMuteFrameCount(); + static void ResetMuteFrameCount(); + static void SetFadeRatio(double fadeRatio); + static void StopAudio(bool clearBuffer = false); static void RegisterAudioDevice(IAudioDevice *audioDevice); }; diff --git a/GUI.NET/Config/PreferenceInfo.cs b/GUI.NET/Config/PreferenceInfo.cs index 528fe321..96d5cfe5 100644 --- a/GUI.NET/Config/PreferenceInfo.cs +++ b/GUI.NET/Config/PreferenceInfo.cs @@ -30,6 +30,14 @@ namespace Mesen.GUI.Config public bool AssociateFdsFiles = false; public bool AssociateMmoFiles = false; public bool AssociateMstFiles = false; + public bool AssociateNsfFiles = false; + public bool AssociateNsfeFiles = false; + + public bool NsfDisableApuIrqs = true; + public bool NsfMoveToNextTrackAfterTime = true; + public Int32 NsfMoveToNextTrackTime = 120; + public bool NsfAutoDetectSilence = true; + public Int32 NsfAutoDetectSilenceDelay = 3000; public bool PauseOnMovieEnd = true; public bool AutomaticallyCheckForUpdates = true; @@ -68,6 +76,8 @@ namespace Mesen.GUI.Config UpdateFileAssociation("fds", preferenceInfo.AssociateFdsFiles); UpdateFileAssociation("mmo", preferenceInfo.AssociateMmoFiles); UpdateFileAssociation("mst", preferenceInfo.AssociateMstFiles); + UpdateFileAssociation("nsf", preferenceInfo.AssociateNsfFiles); + UpdateFileAssociation("nsfe", preferenceInfo.AssociateNsfeFiles); InteropEmu.SetFlag(EmulationFlags.Mmc3IrqAltBehavior, preferenceInfo.UseAlternativeMmc3Irq); InteropEmu.SetFlag(EmulationFlags.AllowInvalidInput, preferenceInfo.AllowInvalidInput); @@ -78,6 +88,8 @@ namespace Mesen.GUI.Config InteropEmu.SetFlag(EmulationFlags.AllowBackgroundInput, preferenceInfo.AllowBackgroundInput); InteropEmu.SetFlag(EmulationFlags.PauseWhenInBackground, preferenceInfo.PauseWhenInBackground); InteropEmu.SetFlag(EmulationFlags.DisableGameDatabase, preferenceInfo.DisableGameDatabase); + + InteropEmu.NsfSetNsfConfig(preferenceInfo.NsfAutoDetectSilence ? preferenceInfo.NsfAutoDetectSilenceDelay : 0, preferenceInfo.NsfMoveToNextTrackAfterTime ? preferenceInfo.NsfMoveToNextTrackTime : -1, preferenceInfo.NsfDisableApuIrqs); } } } diff --git a/GUI.NET/Controls/ctrlNsfPlayer.Designer.cs b/GUI.NET/Controls/ctrlNsfPlayer.Designer.cs new file mode 100644 index 00000000..d33d8988 --- /dev/null +++ b/GUI.NET/Controls/ctrlNsfPlayer.Designer.cs @@ -0,0 +1,484 @@ +namespace Mesen.GUI.Controls +{ + partial class ctrlNsfPlayer + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.btnPrevious = new System.Windows.Forms.Button(); + this.btnPause = new System.Windows.Forms.Button(); + this.btnNext = new System.Windows.Forms.Button(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.lblCopyrightValue = new System.Windows.Forms.Label(); + this.lblArtistValue = new System.Windows.Forms.Label(); + this.lblTitleValue = new System.Windows.Forms.Label(); + this.lblArtist = new System.Windows.Forms.Label(); + this.lblTitle = new System.Windows.Forms.Label(); + this.lblCopyright = new System.Windows.Forms.Label(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.lblMmc5 = new System.Windows.Forms.Label(); + this.lblFds = new System.Windows.Forms.Label(); + this.lblNamco = new System.Windows.Forms.Label(); + this.lblSunsoft = new System.Windows.Forms.Label(); + this.lblVrc6 = new System.Windows.Forms.Label(); + this.lblVrc7 = new System.Windows.Forms.Label(); + this.lblSoundChips = new System.Windows.Forms.Label(); + this.picBackground = new System.Windows.Forms.PictureBox(); + this.trkVolume = new System.Windows.Forms.TrackBar(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.cboTrack = new System.Windows.Forms.ComboBox(); + this.lblTrackTotal = new System.Windows.Forms.Label(); + this.lblTime = new System.Windows.Forms.Label(); + this.tmrFastForward = new System.Windows.Forms.Timer(this.components); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picBackground)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.trkVolume)).BeginInit(); + this.flowLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 5; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.Controls.Add(this.btnPrevious, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.btnPause, 2, 2); + this.tableLayoutPanel1.Controls.Add(this.btnNext, 3, 2); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.picBackground, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.trkVolume, 4, 2); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.lblTime, 0, 3); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + 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()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(371, 281); + this.tableLayoutPanel1.TabIndex = 0; + // + // btnPrevious + // + this.btnPrevious.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.btnPrevious.Image = global::Mesen.GUI.Properties.Resources.PrevTrack; + this.btnPrevious.Location = new System.Drawing.Point(126, 220); + this.btnPrevious.Name = "btnPrevious"; + this.btnPrevious.Size = new System.Drawing.Size(33, 25); + this.btnPrevious.TabIndex = 1; + this.btnPrevious.UseVisualStyleBackColor = true; + this.btnPrevious.Click += new System.EventHandler(this.btnPrevious_Click); + // + // btnPause + // + this.btnPause.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.btnPause.Image = global::Mesen.GUI.Properties.Resources.Pause; + this.btnPause.Location = new System.Drawing.Point(165, 216); + this.btnPause.Name = "btnPause"; + this.btnPause.Size = new System.Drawing.Size(40, 33); + this.btnPause.TabIndex = 0; + this.btnPause.UseVisualStyleBackColor = true; + this.btnPause.Click += new System.EventHandler(this.btnPause_Click); + // + // btnNext + // + this.btnNext.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.btnNext.Image = global::Mesen.GUI.Properties.Resources.NextTrack; + this.btnNext.Location = new System.Drawing.Point(211, 220); + this.btnNext.Name = "btnNext"; + this.btnNext.Size = new System.Drawing.Size(33, 25); + this.btnNext.TabIndex = 2; + this.btnNext.UseVisualStyleBackColor = true; + this.btnNext.Click += new System.EventHandler(this.btnNext_Click); + this.btnNext.MouseDown += new System.Windows.Forms.MouseEventHandler(this.btnNext_MouseDown); + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.AutoSize = true; + this.tableLayoutPanel2.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.tableLayoutPanel2.ColumnCount = 4; + this.tableLayoutPanel1.SetColumnSpan(this.tableLayoutPanel2, 5); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 250F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.Controls.Add(this.lblCopyrightValue, 2, 2); + this.tableLayoutPanel2.Controls.Add(this.lblArtistValue, 2, 1); + this.tableLayoutPanel2.Controls.Add(this.lblTitleValue, 2, 0); + this.tableLayoutPanel2.Controls.Add(this.lblArtist, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.lblTitle, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.lblCopyright, 1, 2); + this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel3, 2, 3); + this.tableLayoutPanel2.Controls.Add(this.lblSoundChips, 1, 3); + this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom; + this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 121); + this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 4; + 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()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.Size = new System.Drawing.Size(371, 86); + this.tableLayoutPanel2.TabIndex = 3; + // + // lblCopyrightValue + // + this.lblCopyrightValue.AutoSize = true; + this.lblCopyrightValue.ForeColor = System.Drawing.Color.White; + this.lblCopyrightValue.Location = new System.Drawing.Point(101, 42); + this.lblCopyrightValue.Name = "lblCopyrightValue"; + this.lblCopyrightValue.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblCopyrightValue.Size = new System.Drawing.Size(63, 21); + this.lblCopyrightValue.TabIndex = 5; + this.lblCopyrightValue.Text = "[[Copyright]]"; + // + // lblArtistValue + // + this.lblArtistValue.AutoSize = true; + this.lblArtistValue.ForeColor = System.Drawing.Color.White; + this.lblArtistValue.Location = new System.Drawing.Point(101, 21); + this.lblArtistValue.Name = "lblArtistValue"; + this.lblArtistValue.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblArtistValue.Size = new System.Drawing.Size(42, 21); + this.lblArtistValue.TabIndex = 4; + this.lblArtistValue.Text = "[[Artist]]"; + // + // lblTitleValue + // + this.lblTitleValue.AutoSize = true; + this.lblTitleValue.ForeColor = System.Drawing.Color.White; + this.lblTitleValue.Location = new System.Drawing.Point(101, 0); + this.lblTitleValue.Name = "lblTitleValue"; + this.lblTitleValue.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblTitleValue.Size = new System.Drawing.Size(39, 21); + this.lblTitleValue.TabIndex = 3; + this.lblTitleValue.Text = "[[Title]]"; + // + // lblArtist + // + this.lblArtist.AutoSize = true; + this.lblArtist.ForeColor = System.Drawing.Color.White; + this.lblArtist.Location = new System.Drawing.Point(25, 21); + this.lblArtist.Name = "lblArtist"; + this.lblArtist.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblArtist.Size = new System.Drawing.Size(33, 21); + this.lblArtist.TabIndex = 2; + this.lblArtist.Text = "Artist:"; + // + // lblTitle + // + this.lblTitle.AutoSize = true; + this.lblTitle.ForeColor = System.Drawing.Color.White; + this.lblTitle.Location = new System.Drawing.Point(25, 0); + this.lblTitle.Name = "lblTitle"; + this.lblTitle.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblTitle.Size = new System.Drawing.Size(30, 21); + this.lblTitle.TabIndex = 0; + this.lblTitle.Text = "Title:"; + // + // lblCopyright + // + this.lblCopyright.AutoSize = true; + this.lblCopyright.ForeColor = System.Drawing.Color.White; + this.lblCopyright.Location = new System.Drawing.Point(25, 42); + this.lblCopyright.Name = "lblCopyright"; + this.lblCopyright.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblCopyright.Size = new System.Drawing.Size(54, 21); + this.lblCopyright.TabIndex = 1; + this.lblCopyright.Text = "Copyright:"; + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.ColumnCount = 6; + this.tableLayoutPanel2.SetColumnSpan(this.tableLayoutPanel3, 2); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.Controls.Add(this.lblMmc5, 1, 0); + this.tableLayoutPanel3.Controls.Add(this.lblFds, 0, 0); + this.tableLayoutPanel3.Controls.Add(this.lblNamco, 2, 0); + this.tableLayoutPanel3.Controls.Add(this.lblSunsoft, 3, 0); + this.tableLayoutPanel3.Controls.Add(this.lblVrc6, 4, 0); + this.tableLayoutPanel3.Controls.Add(this.lblVrc7, 5, 0); + this.tableLayoutPanel3.Location = new System.Drawing.Point(101, 66); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 1; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 17F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 17F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(197, 17); + this.tableLayoutPanel3.TabIndex = 6; + // + // lblMmc5 + // + this.lblMmc5.AutoSize = true; + this.lblMmc5.BackColor = System.Drawing.Color.Transparent; + this.lblMmc5.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblMmc5.ForeColor = System.Drawing.Color.White; + this.lblMmc5.Location = new System.Drawing.Point(25, 0); + this.lblMmc5.Margin = new System.Windows.Forms.Padding(0); + this.lblMmc5.Name = "lblMmc5"; + this.lblMmc5.Size = new System.Drawing.Size(35, 15); + this.lblMmc5.TabIndex = 2; + this.lblMmc5.Text = "MMC5"; + // + // lblFds + // + this.lblFds.AutoSize = true; + this.lblFds.BackColor = System.Drawing.Color.Transparent; + this.lblFds.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblFds.ForeColor = System.Drawing.Color.White; + this.lblFds.Location = new System.Drawing.Point(0, 0); + this.lblFds.Margin = new System.Windows.Forms.Padding(0); + this.lblFds.Name = "lblFds"; + this.lblFds.Size = new System.Drawing.Size(25, 15); + this.lblFds.TabIndex = 0; + this.lblFds.Text = "FDS"; + // + // lblNamco + // + this.lblNamco.AutoSize = true; + this.lblNamco.BackColor = System.Drawing.Color.Transparent; + this.lblNamco.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblNamco.ForeColor = System.Drawing.Color.White; + this.lblNamco.Location = new System.Drawing.Point(60, 0); + this.lblNamco.Margin = new System.Windows.Forms.Padding(0); + this.lblNamco.Name = "lblNamco"; + this.lblNamco.Size = new System.Drawing.Size(37, 15); + this.lblNamco.TabIndex = 1; + this.lblNamco.Text = "Namco"; + // + // lblSunsoft + // + this.lblSunsoft.AutoSize = true; + this.lblSunsoft.BackColor = System.Drawing.Color.Transparent; + this.lblSunsoft.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblSunsoft.ForeColor = System.Drawing.Color.White; + this.lblSunsoft.Location = new System.Drawing.Point(97, 0); + this.lblSunsoft.Margin = new System.Windows.Forms.Padding(0); + this.lblSunsoft.Name = "lblSunsoft"; + this.lblSunsoft.Size = new System.Drawing.Size(37, 15); + this.lblSunsoft.TabIndex = 5; + this.lblSunsoft.Text = "Sunsoft"; + // + // lblVrc6 + // + this.lblVrc6.AutoSize = true; + this.lblVrc6.BackColor = System.Drawing.Color.Transparent; + this.lblVrc6.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblVrc6.ForeColor = System.Drawing.Color.White; + this.lblVrc6.Location = new System.Drawing.Point(134, 0); + this.lblVrc6.Margin = new System.Windows.Forms.Padding(0); + this.lblVrc6.Name = "lblVrc6"; + this.lblVrc6.Size = new System.Drawing.Size(32, 15); + this.lblVrc6.TabIndex = 4; + this.lblVrc6.Text = "VRC6"; + // + // lblVrc7 + // + this.lblVrc7.AutoSize = true; + this.lblVrc7.BackColor = System.Drawing.Color.Transparent; + this.lblVrc7.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblVrc7.ForeColor = System.Drawing.Color.White; + this.lblVrc7.Location = new System.Drawing.Point(166, 0); + this.lblVrc7.Margin = new System.Windows.Forms.Padding(0); + this.lblVrc7.Name = "lblVrc7"; + this.lblVrc7.Size = new System.Drawing.Size(32, 15); + this.lblVrc7.TabIndex = 3; + this.lblVrc7.Text = "VRC7"; + // + // lblSoundChips + // + this.lblSoundChips.AutoSize = true; + this.lblSoundChips.ForeColor = System.Drawing.Color.White; + this.lblSoundChips.Location = new System.Drawing.Point(25, 63); + this.lblSoundChips.Name = "lblSoundChips"; + this.lblSoundChips.Padding = new System.Windows.Forms.Padding(0, 4, 0, 4); + this.lblSoundChips.Size = new System.Drawing.Size(70, 21); + this.lblSoundChips.TabIndex = 7; + this.lblSoundChips.Text = "Sound Chips:"; + // + // picBackground + // + this.picBackground.Anchor = System.Windows.Forms.AnchorStyles.None; + this.picBackground.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; + this.tableLayoutPanel1.SetColumnSpan(this.picBackground, 5); + this.picBackground.Image = global::Mesen.GUI.Properties.Resources.NsfBackground; + this.picBackground.Location = new System.Drawing.Point(66, 13); + this.picBackground.Margin = new System.Windows.Forms.Padding(10); + this.picBackground.MaximumSize = new System.Drawing.Size(334, 380); + this.picBackground.Name = "picBackground"; + this.picBackground.Size = new System.Drawing.Size(238, 95); + this.picBackground.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.picBackground.TabIndex = 5; + this.picBackground.TabStop = false; + // + // trkVolume + // + this.trkVolume.Location = new System.Drawing.Point(257, 210); + this.trkVolume.Margin = new System.Windows.Forms.Padding(10, 3, 3, 3); + this.trkVolume.Maximum = 100; + this.trkVolume.Name = "trkVolume"; + this.trkVolume.Size = new System.Drawing.Size(104, 45); + this.trkVolume.TabIndex = 6; + this.trkVolume.TickFrequency = 10; + this.trkVolume.TickStyle = System.Windows.Forms.TickStyle.Both; + this.trkVolume.ValueChanged += new System.EventHandler(this.trkVolume_ValueChanged); + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.flowLayoutPanel1.AutoSize = true; + this.flowLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.flowLayoutPanel1.Controls.Add(this.cboTrack); + this.flowLayoutPanel1.Controls.Add(this.lblTrackTotal); + this.flowLayoutPanel1.Location = new System.Drawing.Point(28, 219); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0, 0, 15, 0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(80, 27); + this.flowLayoutPanel1.TabIndex = 9; + // + // cboTrack + // + this.cboTrack.BackColor = System.Drawing.Color.Black; + this.cboTrack.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboTrack.DropDownWidth = 200; + this.cboTrack.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.cboTrack.ForeColor = System.Drawing.Color.White; + this.cboTrack.FormattingEnabled = true; + this.cboTrack.Items.AddRange(new object[] { + "1", + "2", + "3", + "4", + "5", + "6"}); + this.cboTrack.Location = new System.Drawing.Point(3, 3); + this.cboTrack.Name = "cboTrack"; + this.cboTrack.Size = new System.Drawing.Size(47, 21); + this.cboTrack.TabIndex = 8; + this.cboTrack.DropDown += new System.EventHandler(this.cboTrack_DropDown); + this.cboTrack.SelectedIndexChanged += new System.EventHandler(this.cboTrack_SelectedIndexChanged); + this.cboTrack.DropDownClosed += new System.EventHandler(this.cboTrack_DropDownClosed); + // + // lblTrackTotal + // + this.lblTrackTotal.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblTrackTotal.AutoSize = true; + this.lblTrackTotal.ForeColor = System.Drawing.Color.White; + this.lblTrackTotal.Location = new System.Drawing.Point(53, 7); + this.lblTrackTotal.Margin = new System.Windows.Forms.Padding(0); + this.lblTrackTotal.Name = "lblTrackTotal"; + this.lblTrackTotal.Size = new System.Drawing.Size(27, 13); + this.lblTrackTotal.TabIndex = 4; + this.lblTrackTotal.Text = "/ 24"; + // + // lblTime + // + this.lblTime.Anchor = System.Windows.Forms.AnchorStyles.None; + this.lblTime.AutoSize = true; + this.tableLayoutPanel1.SetColumnSpan(this.lblTime, 5); + this.lblTime.ForeColor = System.Drawing.Color.White; + this.lblTime.Location = new System.Drawing.Point(168, 258); + this.lblTime.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10); + this.lblTime.Name = "lblTime"; + this.lblTime.Size = new System.Drawing.Size(34, 13); + this.lblTime.TabIndex = 10; + this.lblTime.Text = "00:00"; + this.lblTime.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // tmrFastForward + // + this.tmrFastForward.Interval = 500; + this.tmrFastForward.Tick += new System.EventHandler(this.tmrFastForward_Tick); + // + // ctrlNsfPlayer + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Black; + this.Controls.Add(this.tableLayoutPanel1); + this.Name = "ctrlNsfPlayer"; + this.Size = new System.Drawing.Size(371, 281); + this.VisibleChanged += new System.EventHandler(this.ctrlNsfPlayer_VisibleChanged); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picBackground)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.trkVolume)).EndInit(); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Button btnPause; + private System.Windows.Forms.Button btnPrevious; + private System.Windows.Forms.Button btnNext; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.Label lblCopyrightValue; + private System.Windows.Forms.Label lblArtistValue; + private System.Windows.Forms.Label lblTitleValue; + private System.Windows.Forms.Label lblArtist; + private System.Windows.Forms.Label lblTitle; + private System.Windows.Forms.Label lblCopyright; + private System.Windows.Forms.Label lblTrackTotal; + private System.Windows.Forms.PictureBox picBackground; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.Label lblSunsoft; + private System.Windows.Forms.Label lblVrc6; + private System.Windows.Forms.Label lblVrc7; + private System.Windows.Forms.Label lblMmc5; + private System.Windows.Forms.Label lblNamco; + private System.Windows.Forms.Label lblFds; + private System.Windows.Forms.Label lblSoundChips; + private System.Windows.Forms.TrackBar trkVolume; + private System.Windows.Forms.ComboBox cboTrack; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.Label lblTime; + private System.Windows.Forms.Timer tmrFastForward; + private System.Windows.Forms.ToolTip toolTip; + } +} diff --git a/GUI.NET/Controls/ctrlNsfPlayer.cs b/GUI.NET/Controls/ctrlNsfPlayer.cs new file mode 100644 index 00000000..0936a05e --- /dev/null +++ b/GUI.NET/Controls/ctrlNsfPlayer.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Mesen.GUI.Forms; +using Mesen.GUI.Config; + +namespace Mesen.GUI.Controls +{ + public partial class ctrlNsfPlayer : UserControl + { + private List _trackList = new List(); + private int _frameCount = 0; + private bool _fastForwarding = false; + private UInt32 _originalSpeed = 100; + + public ctrlNsfPlayer() + { + InitializeComponent(); + } + + public void ResetCount() + { + _frameCount = 0; + this.BeginInvoke((MethodInvoker)(() => this.UpdateTimeDisplay(_frameCount))); + } + + public void CountFrame() + { + _frameCount++; + if(_frameCount % 30 == 0) { + this.BeginInvoke((MethodInvoker)(() => this.UpdateTimeDisplay(_frameCount))); + } + } + + private void UpdateTimeDisplay(int frameCount) + { + if(!InteropEmu.IsNsf()) { + _frameCount = 0; + return; + } + + NsfHeader header = InteropEmu.NsfGetHeader(); + int currentTrack = InteropEmu.NsfGetCurrentTrack(); + + TimeSpan time = TimeSpan.FromSeconds((double)frameCount / ((header.Flags & 0x01) == 0x01 ? 50.006978 : 60.098812)); + string label = time.ToString(time.TotalHours < 1 ? @"mm\:ss" : @"hh\:mm\:ss"); + + TimeSpan trackTime = GetTrackLength(header, currentTrack); + if(trackTime.Ticks > 0) { + label += " / " + trackTime.ToString(trackTime.TotalHours < 1 ? @"mm\:ss" : @"hh\:mm\:ss"); + } + + string[] trackNames = header.GetTrackNames(); + if(trackNames.Length > 1 && trackNames.Length > currentTrack) { + label += Environment.NewLine + (string.IsNullOrWhiteSpace(trackNames[currentTrack]) ? ResourceHelper.GetMessage("NsfUnnamedTrack") : trackNames[currentTrack]); + } + + lblTime.Text = label; + } + + private TimeSpan GetTrackLength(NsfHeader header, int track) + { + int trackLength = header.TrackLength[track]; + if(header.TotalSongs > 1 && trackLength < 0 && ConfigManager.Config.PreferenceInfo.NsfMoveToNextTrackAfterTime) { + trackLength = (ConfigManager.Config.PreferenceInfo.NsfMoveToNextTrackTime - 1) * 1000; + } + + if(trackLength >= 0) { + int trackFade = header.TrackFade[track]; + if(trackFade < 0) { + //1 sec by default + trackFade = 1000; + } + trackLength += trackFade; + + return TimeSpan.FromSeconds((double)trackLength / 1000); + } + + return TimeSpan.FromSeconds(0); + } + + private void UpdateTrackDisplay() + { + NsfHeader header = InteropEmu.NsfGetHeader(); + int currentTrack = InteropEmu.NsfGetCurrentTrack(); + + string[] trackNames = header.GetTrackNames(); + + if(header.TotalSongs != cboTrack.Items.Count) { + _trackList = new List(); + for(int i = 0; i < header.TotalSongs; i++) { + string trackName = (i + 1).ToString(); + if(trackNames.Length > 1 && trackNames.Length > i) { + trackName += " - " + (string.IsNullOrWhiteSpace(trackNames[i]) ? ResourceHelper.GetMessage("NsfUnnamedTrack") : trackNames[i]); + } + TimeSpan trackTime = GetTrackLength(header, i); + if(trackTime.Ticks > 0) { + trackName += " (" + trackTime.ToString(trackTime.TotalHours < 1 ? @"mm\:ss" : @"hh\:mm\:ss") + ")"; + } + _trackList.Add(new ComboboxItem { Value = i +1, Description = trackName }); + } + cboTrack.DataSource = _trackList; + cboTrack.DisplayMember = "Value"; + } + cboTrack.SelectedIndex = currentTrack; + lblTrackTotal.Text = "/ " + header.TotalSongs.ToString(); + } + + public void UpdateText() + { + if(this.InvokeRequired) { + this.BeginInvoke((MethodInvoker)(() => UpdateText())); + } else { + UpdateTrackDisplay(); + + toolTip.SetToolTip(btnNext, ResourceHelper.GetMessage("NsfNextTrack")); + + NsfHeader header = InteropEmu.NsfGetHeader(); + trkVolume.Value = (int)ConfigManager.Config.AudioInfo.MasterVolume; + + lblTitleValue.Text = header.GetSongName(); + lblArtistValue.Text = header.GetArtistName(); + lblCopyrightValue.Text = header.GetCopyrightHolder(); + + lblVrc6.ForeColor = (header.SoundChips & 0x01) == 0x01 ? Color.White : Color.Gray; + lblVrc7.ForeColor = (header.SoundChips & 0x02) == 0x02 ? Color.White : Color.Gray; + lblFds.ForeColor = (header.SoundChips & 0x04) == 0x04 ? Color.White : Color.Gray; + lblMmc5.ForeColor = (header.SoundChips & 0x08) == 0x08 ? Color.White : Color.Gray; + lblNamco.ForeColor = (header.SoundChips & 0x10) == 0x10 ? Color.White : Color.Gray; + lblSunsoft.ForeColor = (header.SoundChips & 0x20) == 0x20 ? Color.White : Color.Gray; + + if(InteropEmu.IsPaused()) { + btnPause.Image = Properties.Resources.Play; + } else { + btnPause.Image = Properties.Resources.Pause; + } + } + } + + private void btnPause_Click(object sender, EventArgs e) + { + if(InteropEmu.IsPaused()) { + InteropEmu.Resume(); + btnPause.Image = Properties.Resources.Pause; + } else { + InteropEmu.Pause(); + btnPause.Image = Properties.Resources.Play; + } + } + + private void btnNext_Click(object sender, EventArgs e) + { + if(!_fastForwarding) { + int soundCount = InteropEmu.NsfGetHeader().TotalSongs; + int currentTrack = InteropEmu.NsfGetCurrentTrack(); + currentTrack = (currentTrack + 1) % soundCount; + InteropEmu.NsfSelectTrack((byte)currentTrack); + _frameCount = 0; + } + } + + private void btnPrevious_Click(object sender, EventArgs e) + { + int soundCount = InteropEmu.NsfGetHeader().TotalSongs; + int currentTrack = InteropEmu.NsfGetCurrentTrack(); + if(_frameCount < 120) { + //Reload current track if it has been playing for more than 2 seconds + currentTrack--; + if(currentTrack < 0) { + currentTrack = soundCount - 1; + } + } + InteropEmu.NsfSelectTrack((byte)currentTrack); + _frameCount = 0; + } + + private void trkVolume_ValueChanged(object sender, EventArgs e) + { + ConfigManager.Config.AudioInfo.MasterVolume = (uint)trkVolume.Value; + ConfigManager.ApplyChanges(); + AudioInfo.ApplyConfig(); + } + + private void cboTrack_SelectedIndexChanged(object sender, EventArgs e) + { + int currentTrack = InteropEmu.NsfGetCurrentTrack(); + if(currentTrack != cboTrack.SelectedIndex) { + InteropEmu.NsfSelectTrack((byte)cboTrack.SelectedIndex); + _frameCount = 0; + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if(keyData == Keys.Left) { + btnPrevious_Click(null, null); + return true; + } else if(keyData == Keys.Right) { + btnNext_Click(null, null); + return true; + } else if(keyData == Keys.Up) { + trkVolume.Value = Math.Min(trkVolume.Value+5, 100); + return true; + } else if(keyData == Keys.Down) { + trkVolume.Value = Math.Max(trkVolume.Value-5, 0); + return true; + } else if(keyData == Keys.Space) { + btnPause_Click(null, null); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } + + private void ctrlNsfPlayer_VisibleChanged(object sender, EventArgs e) + { + btnPause.Focus(); + } + + private void btnNext_MouseDown(object sender, MouseEventArgs e) + { + if(e.Button == MouseButtons.Left) { + tmrFastForward.Start(); + _originalSpeed = ConfigManager.Config.EmulationInfo.EmulationSpeed; + } + } + + private void tmrFastForward_Tick(object sender, EventArgs e) + { + if(Control.MouseButtons.HasFlag(MouseButtons.Left)) { + if(!_fastForwarding) { + tmrFastForward.Interval = 50; + _fastForwarding = true; + ConfigManager.Config.EmulationInfo.EmulationSpeed = 0; + ConfigManager.ApplyChanges(); + EmulationInfo.ApplyConfig(); + } + } else { + tmrFastForward.Interval = 500; + tmrFastForward.Stop(); + ConfigManager.Config.EmulationInfo.EmulationSpeed = _originalSpeed; + ConfigManager.ApplyChanges(); + EmulationInfo.ApplyConfig(); + _fastForwarding = false; + } + } + + private void cboTrack_DropDown(object sender, EventArgs e) + { + cboTrack.DisplayMember = "Description"; + int scrollBarWidth = (cboTrack.Items.Count>cboTrack.MaxDropDownItems) ? SystemInformation.VerticalScrollBarWidth : 0; + + int width = 100; + using(Graphics g = cboTrack.CreateGraphics()) { + foreach(ComboboxItem item in ((ComboBox)sender).Items) { + width = Math.Max(width, (int)g.MeasureString(item.Description, cboTrack.Font).Width + scrollBarWidth); + } + } + cboTrack.DropDownWidth = Math.Min(width, 300); + } + + private void cboTrack_DropDownClosed(object sender, EventArgs e) + { + int index = cboTrack.SelectedIndex; + cboTrack.DisplayMember = "Value"; + cboTrack.SelectedIndex = index; + btnPause.Focus(); + } + } + + public class ComboboxItem + { + public int Value { get; set; } + public string Description { get; set; } + } +} diff --git a/GUI.NET/Controls/ctrlNsfPlayer.resx b/GUI.NET/Controls/ctrlNsfPlayer.resx new file mode 100644 index 00000000..e794cb43 --- /dev/null +++ b/GUI.NET/Controls/ctrlNsfPlayer.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 154, 17 + + \ No newline at end of file diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index 752352c6..3dec523a 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -5,8 +5,8 @@ Movie files (*.mmo)|*.mmo|All Files (*.*)|*.* Wave files (*.wav)|*.wav|All Files (*.*)|*.* Palette Files (*.pal)|*.pal|All Files (*.*)|*.* - All supported formats (*.nes, *.zip, *.7z, *.fds)|*.NES;*.ZIP;*.7z;*.FDS|NES Roms (*.nes)|*.NES|Famicom Disk System Roms (*.fds)|*.FDS|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* - All supported formats (*.nes, *.zip, *.7z, *.fds, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS|NES Roms (*.nes)|*.NES|Famicom Disk System Roms (*.fds)|*.FDS|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|IPS Patches (*.ips)|*.IPS|All (*.*)|*.* + All supported formats (*.nes, *.zip, *.7z, *.nsf, *.nsfe, *.fds)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE|NES Roms (*.nes)|*.NES|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* + All supported formats (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE|NES Roms (*.nes)|*.NES|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|IPS Patches (*.ips)|*.IPS|All (*.*)|*.* Test files (*.mtp)|*.mtp|All (*.*)|*.* Resume @@ -19,6 +19,10 @@ {0} roms found + Next Track (Hold to fast forward) + <no name> + <unknown> + The Visual Studio Runtime could not be installed properly. <empty> An error has occurred while trying to check for updates. Error details: {0} diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 9d2015ea..47f2fe6e 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -87,6 +87,12 @@ Aide Recherche de mises-à-jour À propos de... + + + Titre + Artiste + Copyright + Puces audio
Fermer @@ -263,7 +269,14 @@ Resynchroniser Désactiver la base de données des jeux - + + NSF / NSFe + Jouer la piste suivante après + millisecondes de silence + Limiter la durée des pistes à + secondes + Désactiver les IRQs du APU + OK Annuler
@@ -344,8 +357,8 @@ Films (*.mmo)|*.mmo|Tous les fichiers (*.*)|*.* Fichiers wave (*.wav)|*.wav|Tous les fichiers (*.*)|*.* Fichier de palette (*.pal)|*.pal|Tous les fichiers (*.*)|*.* - Tous les formats supportés (*.nes, *.zip, *.7z, *.fds)|*.NES;*.ZIP;*.7z;*.FDS|Roms de NES (*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers ZIP (*.zip)|*.ZIP|Fichiers 7-Zip (*.7z)|*.7z|Tous les fichiers (*.*)|*.* - Tous les formats supportés (*.nes, *.zip, *.7z, *.fds, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS|Roms de NES(*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers ZIP (*.zip)|*.ZIP|Fichiers 7-Zip (*.7z)|*.7z|Fichiers IPS (*.ips)|*.IPS|Tous les fichiers (*.*)|*.* + Tous les formats supportés (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE|Roms de NES (*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Fichiers ZIP (*.zip)|*.ZIP|Fichiers 7-Zip (*.7z)|*.7z|Tous les fichiers (*.*)|*.* + Tous les formats supportés (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE|Roms de NES(*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Fichiers ZIP (*.zip)|*.ZIP|Fichiers 7-Zip (*.7z)|*.7z|Fichiers IPS (*.ips)|*.IPS|Tous les fichiers (*.*)|*.* Fichiers de test (*.mtp)|*.mtp|Tous les fichiers (*.*)|*.* Continuer @@ -358,6 +371,10 @@ {0} roms trouvés + Piste Suivante (Garder enfoncé pour jouer plus rapidement) + [sans nom] + [inconnu] + Le package Redistribuable Visual C++ pour Visual Studio 2015 n'a pas été installé correctement. <aucune sauvegarde> Une erreur s'est produite lors de la recherche de mises-à-jour. Détails de l'erreur : {0} diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index f2a1b94e..a0f3e24d 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -87,6 +87,12 @@ ヘルプ アップデートの確認 Mesenとは + + + タイトル + 作者 + コピーライト + 音源チップ
閉じる @@ -263,6 +269,13 @@ ゲームデータベースを無効にする + NSF / NSFe + 無音検出: + ミリ秒間で無音の場合、次の曲を再生 + 曲の最大長さを + 秒に固定する + APUのIRQを無効にする + OK キャンセル
@@ -336,8 +349,8 @@ 動画 (*.mmo)|*.mmo|すべてのファイル (*.*)|*.* WAVファイル (*.wav)|*.wav|すべてのファイル (*.*)|*.* パレットファイル (*.pal)|*.pal|すべてのファイル (*.*)|*.* - 対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds)|*.NES;*.ZIP;*.FDS;*.7z|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|すべてのファイル (*.*)|*.* - 対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|IPSファイル (*.ips)|*.IPS|すべてのファイル (*.*)|*.* + 対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe)|*.NES;*.ZIP;*.FDS;*.7z;*.NSF;*.NSFE|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|NSFファイル (*.nsf, *.nsfe)|*.NSF;*.NSFE|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|すべてのファイル (*.*)|*.* + 対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|NSFファイル (*.nsf, *.nsfe)|*.NSF;*.NSFE|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|IPSファイル (*.ips)|*.IPS|すべてのファイル (*.*)|*.* テストファイル (*.mtp)|*.mtp|すべてのファイル (*.*)|*.* 再開 @@ -350,6 +363,10 @@ {0}個 + 次の曲 (長押しで早送り) + [名無し] + [不明] + Microsoft Visual Studio 2015のVisual C++再頒布可能パッケージはインストールできませんでした。 <なし> アップデートを確認する時にエラーが発生しました。 エラーの詳細: {0} diff --git a/GUI.NET/Forms/Config/frmPreferences.Designer.cs b/GUI.NET/Forms/Config/frmPreferences.Designer.cs index c3217272..abd1abe0 100644 --- a/GUI.NET/Forms/Config/frmPreferences.Designer.cs +++ b/GUI.NET/Forms/Config/frmPreferences.Designer.cs @@ -58,19 +58,32 @@ this.lblLastSync = new System.Windows.Forms.Label(); this.lblLastSyncDateTime = new System.Windows.Forms.Label(); this.btnResync = new System.Windows.Forms.Button(); + this.tpgNsf = new System.Windows.Forms.TabPage(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.flowLayoutPanel7 = new System.Windows.Forms.FlowLayoutPanel(); + this.chkNsfAutoDetectSilence = new System.Windows.Forms.CheckBox(); + this.nudNsfAutoDetectSilenceDelay = new System.Windows.Forms.NumericUpDown(); + this.lblNsfMillisecondsOfSilence = new System.Windows.Forms.Label(); + this.flowLayoutPanel5 = new System.Windows.Forms.FlowLayoutPanel(); + this.chkNsfMoveToNextTrackAfterTime = new System.Windows.Forms.CheckBox(); + this.nudNsfMoveToNextTrackTime = new System.Windows.Forms.NumericUpDown(); + this.lblNsfSeconds = new System.Windows.Forms.Label(); + this.chkNsfDisableApuIrqs = new System.Windows.Forms.CheckBox(); this.tpgFileAssociations = new System.Windows.Forms.TabPage(); this.grpFileAssociations = new System.Windows.Forms.GroupBox(); this.tlpFileFormat = new System.Windows.Forms.TableLayoutPanel(); + this.chkNsfeFormat = new System.Windows.Forms.CheckBox(); this.chkNesFormat = new System.Windows.Forms.CheckBox(); this.chkFdsFormat = new System.Windows.Forms.CheckBox(); this.chkMmoFormat = new System.Windows.Forms.CheckBox(); this.chkMstFormat = new System.Windows.Forms.CheckBox(); + this.chkNsfFormat = new System.Windows.Forms.CheckBox(); this.tpgAdvanced = new System.Windows.Forms.TabPage(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); - this.chkDisableGameDatabase = new System.Windows.Forms.CheckBox(); - this.chkFdsAutoLoadDisk = new System.Windows.Forms.CheckBox(); - this.chkFdsFastForwardOnLoad = new System.Windows.Forms.CheckBox(); this.tmrSyncDateTime = new System.Windows.Forms.Timer(this.components); + this.chkFdsFastForwardOnLoad = new System.Windows.Forms.CheckBox(); + this.chkFdsAutoLoadDisk = new System.Windows.Forms.CheckBox(); + this.chkDisableGameDatabase = new System.Windows.Forms.CheckBox(); this.tlpMain.SuspendLayout(); this.flowLayoutPanel2.SuspendLayout(); this.tabMain.SuspendLayout(); @@ -82,11 +95,16 @@ this.flowLayoutPanel3.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.picOK)).BeginInit(); this.flowLayoutPanel4.SuspendLayout(); + this.tpgNsf.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.flowLayoutPanel7.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudNsfAutoDetectSilenceDelay)).BeginInit(); + this.flowLayoutPanel5.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudNsfMoveToNextTrackTime)).BeginInit(); this.tpgFileAssociations.SuspendLayout(); this.grpFileAssociations.SuspendLayout(); this.tlpFileFormat.SuspendLayout(); this.tpgAdvanced.SuspendLayout(); - this.tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // // baseConfigPanel @@ -252,6 +270,7 @@ // this.tabMain.Controls.Add(this.tpgGeneral); this.tabMain.Controls.Add(this.tpgCloudSave); + this.tabMain.Controls.Add(this.tpgNsf); this.tabMain.Controls.Add(this.tpgFileAssociations); this.tabMain.Controls.Add(this.tpgAdvanced); this.tabMain.Dock = System.Windows.Forms.DockStyle.Fill; @@ -440,6 +459,149 @@ this.btnResync.UseVisualStyleBackColor = true; this.btnResync.Click += new System.EventHandler(this.btnResync_Click); // + // tpgNsf + // + this.tpgNsf.Controls.Add(this.tableLayoutPanel2); + this.tpgNsf.Location = new System.Drawing.Point(4, 22); + this.tpgNsf.Name = "tpgNsf"; + this.tpgNsf.Padding = new System.Windows.Forms.Padding(3); + this.tpgNsf.Size = new System.Drawing.Size(479, 256); + this.tpgNsf.TabIndex = 4; + this.tpgNsf.Text = "NSF / NSFe"; + this.tpgNsf.UseVisualStyleBackColor = true; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 1; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel7, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel5, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.chkNsfDisableApuIrqs, 0, 2); + this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 3); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 3; + 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(473, 250); + this.tableLayoutPanel2.TabIndex = 0; + // + // flowLayoutPanel7 + // + this.flowLayoutPanel7.Controls.Add(this.chkNsfAutoDetectSilence); + this.flowLayoutPanel7.Controls.Add(this.nudNsfAutoDetectSilenceDelay); + this.flowLayoutPanel7.Controls.Add(this.lblNsfMillisecondsOfSilence); + this.flowLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel7.Location = new System.Drawing.Point(0, 0); + this.flowLayoutPanel7.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel7.Name = "flowLayoutPanel7"; + this.flowLayoutPanel7.Size = new System.Drawing.Size(473, 24); + this.flowLayoutPanel7.TabIndex = 5; + // + // chkNsfAutoDetectSilence + // + this.chkNsfAutoDetectSilence.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.chkNsfAutoDetectSilence.AutoSize = true; + this.chkNsfAutoDetectSilence.Location = new System.Drawing.Point(3, 4); + this.chkNsfAutoDetectSilence.Name = "chkNsfAutoDetectSilence"; + this.chkNsfAutoDetectSilence.Size = new System.Drawing.Size(139, 17); + this.chkNsfAutoDetectSilence.TabIndex = 1; + this.chkNsfAutoDetectSilence.Text = "Move to next track after"; + this.chkNsfAutoDetectSilence.UseVisualStyleBackColor = true; + // + // nudNsfAutoDetectSilenceDelay + // + this.nudNsfAutoDetectSilenceDelay.Location = new System.Drawing.Point(145, 3); + this.nudNsfAutoDetectSilenceDelay.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); + this.nudNsfAutoDetectSilenceDelay.Maximum = new decimal(new int[] { + 999999, + 0, + 0, + 0}); + this.nudNsfAutoDetectSilenceDelay.Minimum = new decimal(new int[] { + 200, + 0, + 0, + 0}); + this.nudNsfAutoDetectSilenceDelay.Name = "nudNsfAutoDetectSilenceDelay"; + this.nudNsfAutoDetectSilenceDelay.Size = new System.Drawing.Size(57, 20); + this.nudNsfAutoDetectSilenceDelay.TabIndex = 3; + this.nudNsfAutoDetectSilenceDelay.Value = new decimal(new int[] { + 3000, + 0, + 0, + 0}); + // + // lblNsfMillisecondsOfSilence + // + this.lblNsfMillisecondsOfSilence.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblNsfMillisecondsOfSilence.AutoSize = true; + this.lblNsfMillisecondsOfSilence.Location = new System.Drawing.Point(205, 6); + this.lblNsfMillisecondsOfSilence.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); + this.lblNsfMillisecondsOfSilence.Name = "lblNsfMillisecondsOfSilence"; + this.lblNsfMillisecondsOfSilence.Size = new System.Drawing.Size(111, 13); + this.lblNsfMillisecondsOfSilence.TabIndex = 4; + this.lblNsfMillisecondsOfSilence.Text = "milliseconds of silence"; + // + // flowLayoutPanel5 + // + this.flowLayoutPanel5.Controls.Add(this.chkNsfMoveToNextTrackAfterTime); + this.flowLayoutPanel5.Controls.Add(this.nudNsfMoveToNextTrackTime); + this.flowLayoutPanel5.Controls.Add(this.lblNsfSeconds); + this.flowLayoutPanel5.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel5.Location = new System.Drawing.Point(0, 24); + this.flowLayoutPanel5.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel5.Name = "flowLayoutPanel5"; + this.flowLayoutPanel5.Size = new System.Drawing.Size(473, 24); + this.flowLayoutPanel5.TabIndex = 4; + // + // chkNsfMoveToNextTrackAfterTime + // + this.chkNsfMoveToNextTrackAfterTime.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.chkNsfMoveToNextTrackAfterTime.AutoSize = true; + this.chkNsfMoveToNextTrackAfterTime.Location = new System.Drawing.Point(3, 4); + this.chkNsfMoveToNextTrackAfterTime.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3); + this.chkNsfMoveToNextTrackAfterTime.Name = "chkNsfMoveToNextTrackAfterTime"; + this.chkNsfMoveToNextTrackAfterTime.Size = new System.Drawing.Size(126, 17); + this.chkNsfMoveToNextTrackAfterTime.TabIndex = 2; + this.chkNsfMoveToNextTrackAfterTime.Text = "Limit track run time to"; + this.chkNsfMoveToNextTrackAfterTime.UseVisualStyleBackColor = true; + // + // nudNsfMoveToNextTrackTime + // + this.nudNsfMoveToNextTrackTime.Location = new System.Drawing.Point(129, 3); + this.nudNsfMoveToNextTrackTime.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); + this.nudNsfMoveToNextTrackTime.Maximum = new decimal(new int[] { + 999, + 0, + 0, + 0}); + this.nudNsfMoveToNextTrackTime.Name = "nudNsfMoveToNextTrackTime"; + this.nudNsfMoveToNextTrackTime.Size = new System.Drawing.Size(44, 20); + this.nudNsfMoveToNextTrackTime.TabIndex = 3; + // + // lblNsfSeconds + // + this.lblNsfSeconds.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblNsfSeconds.AutoSize = true; + this.lblNsfSeconds.Location = new System.Drawing.Point(176, 6); + this.lblNsfSeconds.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); + this.lblNsfSeconds.Name = "lblNsfSeconds"; + this.lblNsfSeconds.Size = new System.Drawing.Size(47, 13); + this.lblNsfSeconds.TabIndex = 4; + this.lblNsfSeconds.Text = "seconds"; + // + // chkNsfDisableApuIrqs + // + this.chkNsfDisableApuIrqs.AutoSize = true; + this.chkNsfDisableApuIrqs.Location = new System.Drawing.Point(3, 51); + this.chkNsfDisableApuIrqs.Name = "chkNsfDisableApuIrqs"; + this.chkNsfDisableApuIrqs.Size = new System.Drawing.Size(113, 17); + this.chkNsfDisableApuIrqs.TabIndex = 6; + this.chkNsfDisableApuIrqs.Text = "Disable APU IRQs"; + this.chkNsfDisableApuIrqs.UseVisualStyleBackColor = true; + // // tpgFileAssociations // this.tpgFileAssociations.Controls.Add(this.grpFileAssociations); @@ -467,21 +629,33 @@ this.tlpFileFormat.ColumnCount = 2; this.tlpFileFormat.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); this.tlpFileFormat.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tlpFileFormat.Controls.Add(this.chkNsfeFormat, 0, 3); this.tlpFileFormat.Controls.Add(this.chkNesFormat, 0, 0); this.tlpFileFormat.Controls.Add(this.chkFdsFormat, 0, 1); this.tlpFileFormat.Controls.Add(this.chkMmoFormat, 1, 0); this.tlpFileFormat.Controls.Add(this.chkMstFormat, 1, 1); + this.tlpFileFormat.Controls.Add(this.chkNsfFormat, 0, 2); this.tlpFileFormat.Dock = System.Windows.Forms.DockStyle.Fill; this.tlpFileFormat.Location = new System.Drawing.Point(3, 16); this.tlpFileFormat.Name = "tlpFileFormat"; this.tlpFileFormat.RowCount = 4; this.tlpFileFormat.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tlpFileFormat.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tlpFileFormat.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tlpFileFormat.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tlpFileFormat.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpFileFormat.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tlpFileFormat.Size = new System.Drawing.Size(467, 231); this.tlpFileFormat.TabIndex = 0; // + // chkNsfeFormat + // + this.chkNsfeFormat.AutoSize = true; + this.chkNsfeFormat.Location = new System.Drawing.Point(3, 72); + this.chkNsfeFormat.Name = "chkNsfeFormat"; + this.chkNsfeFormat.Size = new System.Drawing.Size(226, 17); + this.chkNsfeFormat.TabIndex = 15; + this.chkNsfeFormat.Text = ".NSFE (Nintendo Sound Format Extended)"; + this.chkNsfeFormat.UseVisualStyleBackColor = true; + // // chkNesFormat // this.chkNesFormat.AutoSize = true; @@ -523,6 +697,16 @@ this.chkMstFormat.Text = ".MST (Mesen Savestate)"; this.chkMstFormat.UseVisualStyleBackColor = true; // + // chkNsfFormat + // + this.chkNsfFormat.AutoSize = true; + this.chkNsfFormat.Location = new System.Drawing.Point(3, 49); + this.chkNsfFormat.Name = "chkNsfFormat"; + this.chkNsfFormat.Size = new System.Drawing.Size(171, 17); + this.chkNsfFormat.TabIndex = 14; + this.chkNsfFormat.Text = ".NSF (Nintendo Sound Format)"; + this.chkNsfFormat.UseVisualStyleBackColor = true; + // // tpgAdvanced // this.tpgAdvanced.Controls.Add(this.tableLayoutPanel1); @@ -538,9 +722,6 @@ // this.tableLayoutPanel1.ColumnCount = 1; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Controls.Add(this.chkDisableGameDatabase, 0, 0); - this.tableLayoutPanel1.Controls.Add(this.chkFdsAutoLoadDisk, 0, 1); - this.tableLayoutPanel1.Controls.Add(this.chkFdsFastForwardOnLoad, 0, 2); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; @@ -555,25 +736,10 @@ this.tableLayoutPanel1.Size = new System.Drawing.Size(473, 250); this.tableLayoutPanel1.TabIndex = 0; // - // chkDisableGameDatabase + // tmrSyncDateTime // - this.chkDisableGameDatabase.AutoSize = true; - this.chkDisableGameDatabase.Location = new System.Drawing.Point(3, 3); - this.chkDisableGameDatabase.Name = "chkDisableGameDatabase"; - this.chkDisableGameDatabase.Size = new System.Drawing.Size(170, 17); - this.chkDisableGameDatabase.TabIndex = 6; - this.chkDisableGameDatabase.Text = "Disable built-in game database"; - this.chkDisableGameDatabase.UseVisualStyleBackColor = true; - // - // chkFdsAutoLoadDisk - // - this.chkFdsAutoLoadDisk.AutoSize = true; - this.chkFdsAutoLoadDisk.Location = new System.Drawing.Point(3, 26); - this.chkFdsAutoLoadDisk.Name = "chkFdsAutoLoadDisk"; - this.chkFdsAutoLoadDisk.Size = new System.Drawing.Size(303, 17); - this.chkFdsAutoLoadDisk.TabIndex = 3; - this.chkFdsAutoLoadDisk.Text = "Automatically insert disk 1 side A when starting FDS games"; - this.chkFdsAutoLoadDisk.UseVisualStyleBackColor = true; + this.tmrSyncDateTime.Enabled = true; + this.tmrSyncDateTime.Tick += new System.EventHandler(this.tmrSyncDateTime_Tick); // // chkFdsFastForwardOnLoad // @@ -585,10 +751,25 @@ this.chkFdsFastForwardOnLoad.Text = "Automatically fast forward FDS games when disk or BIOS is loading"; this.chkFdsFastForwardOnLoad.UseVisualStyleBackColor = true; // - // tmrSyncDateTime + // chkFdsAutoLoadDisk // - this.tmrSyncDateTime.Enabled = true; - this.tmrSyncDateTime.Tick += new System.EventHandler(this.tmrSyncDateTime_Tick); + this.chkFdsAutoLoadDisk.AutoSize = true; + this.chkFdsAutoLoadDisk.Location = new System.Drawing.Point(3, 26); + this.chkFdsAutoLoadDisk.Name = "chkFdsAutoLoadDisk"; + this.chkFdsAutoLoadDisk.Size = new System.Drawing.Size(303, 17); + this.chkFdsAutoLoadDisk.TabIndex = 3; + this.chkFdsAutoLoadDisk.Text = "Automatically insert disk 1 side A when starting FDS games"; + this.chkFdsAutoLoadDisk.UseVisualStyleBackColor = true; + // + // chkDisableGameDatabase + // + this.chkDisableGameDatabase.AutoSize = true; + this.chkDisableGameDatabase.Location = new System.Drawing.Point(3, 3); + this.chkDisableGameDatabase.Name = "chkDisableGameDatabase"; + this.chkDisableGameDatabase.Size = new System.Drawing.Size(170, 17); + this.chkDisableGameDatabase.TabIndex = 6; + this.chkDisableGameDatabase.Text = "Disable built-in game database"; + this.chkDisableGameDatabase.UseVisualStyleBackColor = true; // // frmPreferences // @@ -621,13 +802,20 @@ ((System.ComponentModel.ISupportInitialize)(this.picOK)).EndInit(); this.flowLayoutPanel4.ResumeLayout(false); this.flowLayoutPanel4.PerformLayout(); + this.tpgNsf.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.flowLayoutPanel7.ResumeLayout(false); + this.flowLayoutPanel7.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudNsfAutoDetectSilenceDelay)).EndInit(); + this.flowLayoutPanel5.ResumeLayout(false); + this.flowLayoutPanel5.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudNsfMoveToNextTrackTime)).EndInit(); this.tpgFileAssociations.ResumeLayout(false); this.grpFileAssociations.ResumeLayout(false); this.tlpFileFormat.ResumeLayout(false); this.tlpFileFormat.PerformLayout(); this.tpgAdvanced.ResumeLayout(false); - this.tableLayoutPanel1.ResumeLayout(false); - this.tableLayoutPanel1.PerformLayout(); this.ResumeLayout(false); } @@ -649,10 +837,6 @@ private System.Windows.Forms.CheckBox chkFdsFormat; private System.Windows.Forms.CheckBox chkMmoFormat; private System.Windows.Forms.CheckBox chkMstFormat; - private System.Windows.Forms.TabPage tpgAdvanced; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; - private System.Windows.Forms.CheckBox chkFdsAutoLoadDisk; - private System.Windows.Forms.CheckBox chkFdsFastForwardOnLoad; private System.Windows.Forms.CheckBox chkAllowBackgroundInput; private System.Windows.Forms.CheckBox chkPauseOnMovieEnd; private System.Windows.Forms.Button btnOpenMesenFolder; @@ -675,6 +859,23 @@ private System.Windows.Forms.Label lblLastSyncDateTime; private System.Windows.Forms.Timer tmrSyncDateTime; private System.Windows.Forms.Button btnResync; + private System.Windows.Forms.CheckBox chkNsfeFormat; + private System.Windows.Forms.CheckBox chkNsfFormat; + private System.Windows.Forms.TabPage tpgAdvanced; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TabPage tpgNsf; + private System.Windows.Forms.CheckBox chkFdsFastForwardOnLoad; + private System.Windows.Forms.CheckBox chkFdsAutoLoadDisk; private System.Windows.Forms.CheckBox chkDisableGameDatabase; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel5; + private System.Windows.Forms.CheckBox chkNsfMoveToNextTrackAfterTime; + private System.Windows.Forms.NumericUpDown nudNsfMoveToNextTrackTime; + private System.Windows.Forms.Label lblNsfSeconds; + private System.Windows.Forms.CheckBox chkNsfAutoDetectSilence; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel7; + private System.Windows.Forms.NumericUpDown nudNsfAutoDetectSilenceDelay; + private System.Windows.Forms.Label lblNsfMillisecondsOfSilence; + private System.Windows.Forms.CheckBox chkNsfDisableApuIrqs; } } \ No newline at end of file diff --git a/GUI.NET/Forms/Config/frmPreferences.cs b/GUI.NET/Forms/Config/frmPreferences.cs index 75292d25..35343e29 100644 --- a/GUI.NET/Forms/Config/frmPreferences.cs +++ b/GUI.NET/Forms/Config/frmPreferences.cs @@ -29,6 +29,14 @@ namespace Mesen.GUI.Forms.Config AddBinding("AssociateFdsFiles", chkFdsFormat); AddBinding("AssociateMmoFiles", chkMmoFormat); AddBinding("AssociateMstFiles", chkMstFormat); + AddBinding("AssociateNsfFiles", chkNsfFormat); + AddBinding("AssociateNsfeFiles", chkNsfeFormat); + + AddBinding("NsfAutoDetectSilence", chkNsfAutoDetectSilence); + AddBinding("NsfMoveToNextTrackAfterTime", chkNsfMoveToNextTrackAfterTime); + AddBinding("NsfMoveToNextTrackTime", nudNsfMoveToNextTrackTime); + AddBinding("NsfAutoDetectSilenceDelay", nudNsfAutoDetectSilenceDelay); + AddBinding("NsfDisableApuIrqs", chkNsfDisableApuIrqs); AddBinding("FdsAutoLoadDisk", chkFdsAutoLoadDisk); AddBinding("FdsFastForwardOnLoad", chkFdsFastForwardOnLoad); diff --git a/GUI.NET/Forms/ResourceHelper.cs b/GUI.NET/Forms/ResourceHelper.cs index 753dbc26..3d1192da 100644 --- a/GUI.NET/Forms/ResourceHelper.cs +++ b/GUI.NET/Forms/ResourceHelper.cs @@ -64,7 +64,7 @@ namespace Mesen.GUI.Forms } } - public static string GetMessage(string id, params string[] args) + public static string GetMessage(string id, params object[] args) { var baseNode = _resources.SelectSingleNode("/Resources/Messages/Message[@ID='" + id + "']"); if(baseNode != null) { diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index bceb233e..f8cdead8 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -34,6 +34,7 @@ namespace Mesen.GUI.Forms this.menuTimer = new System.Windows.Forms.Timer(this.components); this.panelRenderer = new System.Windows.Forms.Panel(); this.ctrlRenderer = new Mesen.GUI.Controls.ctrlRenderer(); + this.ctrlNsfPlayer = new Mesen.GUI.Controls.ctrlNsfPlayer(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.mnuFile = new System.Windows.Forms.ToolStripMenuItem(); this.mnuOpen = new System.Windows.Forms.ToolStripMenuItem(); @@ -168,6 +169,7 @@ namespace Mesen.GUI.Forms // panelRenderer // this.panelRenderer.BackColor = System.Drawing.Color.Black; + this.panelRenderer.Controls.Add(this.ctrlNsfPlayer); this.panelRenderer.Controls.Add(this.ctrlRenderer); this.panelRenderer.Dock = System.Windows.Forms.DockStyle.Fill; this.panelRenderer.Location = new System.Drawing.Point(0, 24); @@ -191,6 +193,15 @@ namespace Mesen.GUI.Forms this.ctrlRenderer.MouseClick += new System.Windows.Forms.MouseEventHandler(this.ctrlRenderer_MouseClick); this.ctrlRenderer.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ctrlRenderer_MouseMove); // + // ctrlNsfPlayer + // + this.ctrlNsfPlayer.Dock = System.Windows.Forms.DockStyle.Fill; + this.ctrlNsfPlayer.Location = new System.Drawing.Point(0, 0); + this.ctrlNsfPlayer.Name = "ctrlNsfPlayer"; + this.ctrlNsfPlayer.Size = new System.Drawing.Size(304, 218); + this.ctrlNsfPlayer.TabIndex = 2; + this.ctrlNsfPlayer.Visible = false; + // // menuStrip // this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1350,6 +1361,7 @@ namespace Mesen.GUI.Forms private System.Windows.Forms.ToolStripMenuItem mnuScale6x; private System.Windows.Forms.ToolStripMenuItem mnuLogWindow; private System.Windows.Forms.ToolStripMenuItem mnuEmulationConfig; + private Controls.ctrlNsfPlayer ctrlNsfPlayer; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 7357cfe1..9766f139 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -33,6 +33,7 @@ namespace Mesen.GUI.Forms private bool _fullscreenMode = false; private double _regularScale = ConfigManager.Config.VideoInfo.VideoScale; private bool _needScaleUpdate = false; + private bool _isNsfPlayerMode = false; public frmMain(string[] args) { @@ -254,8 +255,9 @@ namespace Mesen.GUI.Forms InteropEmu.ScreenSize size = InteropEmu.GetScreenSize(false); if(!_customSize && this.WindowState != FormWindowState.Maximized) { + Size sizeGap = this.Size - this.ClientSize; this.Resize -= frmMain_Resize; - this.ClientSize = new Size(size.Width, size.Height + menuStrip.Height); + this.ClientSize = new Size(Math.Max(this.MinimumSize.Width - sizeGap.Width, size.Width), Math.Max(this.MinimumSize.Height - sizeGap.Height, size.Height + menuStrip.Height)); this.Resize += frmMain_Resize; } @@ -268,6 +270,8 @@ namespace Mesen.GUI.Forms { if(this.WindowState != FormWindowState.Minimized) { SetScaleBasedOnWindowSize(); + ctrlRenderer.Left = (panelRenderer.Width - ctrlRenderer.Width) / 2; + ctrlRenderer.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2; } } @@ -324,6 +328,7 @@ namespace Mesen.GUI.Forms switch(e.NotificationType) { case InteropEmu.ConsoleNotificationType.GameLoaded: _currentGame = InteropEmu.GetRomInfo().GetRomName(); + InitializeNsfMode(); InitializeFdsDiskMenu(); InitializeVsSystemMenu(); CheatInfo.ApplyCheats(); @@ -333,6 +338,16 @@ namespace Mesen.GUI.Forms this.StartEmuThread(); break; + case InteropEmu.ConsoleNotificationType.PpuFrameDone: + if(InteropEmu.IsNsf()) { + this.ctrlNsfPlayer.CountFrame(); + } + break; + + case InteropEmu.ConsoleNotificationType.GameReset: + InitializeNsfMode(); + break; + case InteropEmu.ConsoleNotificationType.DisconnectedFromServer: ConfigManager.Config.ApplyConfig(); break; @@ -353,7 +368,10 @@ namespace Mesen.GUI.Forms })); break; } - UpdateMenus(); + + if(e.NotificationType != InteropEmu.ConsoleNotificationType.PpuFrameDone) { + UpdateMenus(); + } } private void mnuOpen_Click(object sender, EventArgs e) @@ -475,6 +493,11 @@ namespace Mesen.GUI.Forms mnuPause.Text = InteropEmu.IsPaused() ? ResourceHelper.GetMessage("Resume") : ResourceHelper.GetMessage("Pause"); mnuPause.Image = InteropEmu.IsPaused() ? Mesen.GUI.Properties.Resources.Play : Mesen.GUI.Properties.Resources.Pause; + if(InteropEmu.IsNsf()) { + mnuSaveState.Enabled = false; + mnuLoadState.Enabled = false; + } + bool netPlay = InteropEmu.IsServerRunning() || isNetPlayClient; mnuStartServer.Enabled = !isNetPlayClient; @@ -588,6 +611,8 @@ namespace Mesen.GUI.Forms } else { InteropEmu.Pause(); } + + ctrlNsfPlayer.UpdateText(); } private void ResetEmu() @@ -644,7 +669,7 @@ namespace Mesen.GUI.Forms ToolStripMenuItem item = (ToolStripMenuItem)menu.DropDownItems.Add(label); uint stateIndex = i; item.Click += (object sender, EventArgs e) => { - if(_emuThread != null) { + if(_emuThread != null && !InteropEmu.IsNsf()) { if(forSave) { InteropEmu.SaveState(stateIndex); } else { @@ -938,12 +963,14 @@ namespace Mesen.GUI.Forms private void mnuPreferences_Click(object sender, EventArgs e) { - new frmPreferences().ShowDialog(sender); - ResourceHelper.LoadResources(ConfigManager.Config.PreferenceInfo.DisplayLanguage); - ResourceHelper.UpdateEmuLanguage(); - ResourceHelper.ApplyResources(this); - UpdateMenus(); - InitializeFdsDiskMenu(); + if(new frmPreferences().ShowDialog(sender) == DialogResult.OK) { + ResourceHelper.LoadResources(ConfigManager.Config.PreferenceInfo.DisplayLanguage); + ResourceHelper.UpdateEmuLanguage(); + ResourceHelper.ApplyResources(this); + UpdateMenus(); + InitializeFdsDiskMenu(); + InitializeNsfMode(); + } } private void mnuRegion_Click(object sender, EventArgs e) @@ -1300,10 +1327,14 @@ namespace Mesen.GUI.Forms private void InitializeVsSystemMenu() { - sepVsSystem.Visible = InteropEmu.IsVsSystem(); - mnuInsertCoin1.Visible = InteropEmu.IsVsSystem(); - mnuInsertCoin2.Visible = InteropEmu.IsVsSystem(); - mnuVsGameConfig.Visible = InteropEmu.IsVsSystem(); + if(this.InvokeRequired) { + this.BeginInvoke((MethodInvoker)(() => InitializeVsSystemMenu())); + } else { + sepVsSystem.Visible = InteropEmu.IsVsSystem(); + mnuInsertCoin1.Visible = InteropEmu.IsVsSystem(); + mnuInsertCoin2.Visible = InteropEmu.IsVsSystem(); + mnuVsGameConfig.Visible = InteropEmu.IsVsSystem(); + } } private void mnuInsertCoin1_Click(object sender, EventArgs e) @@ -1352,5 +1383,32 @@ namespace Mesen.GUI.Forms new frmEmulationConfig().ShowDialog(sender); UpdateEmulationSpeedMenu(); } + + + private void InitializeNsfMode() + { + if(this.InvokeRequired) { + this.BeginInvoke((MethodInvoker)(() => this.InitializeNsfMode())); + } else { + if(InteropEmu.IsNsf()) { + if(!this._isNsfPlayerMode) { + this.Size = new Size(380, 320); + this.MinimumSize = new Size(380, 320); + } + this._isNsfPlayerMode = true; + this.ctrlNsfPlayer.UpdateText(); + this.ctrlNsfPlayer.ResetCount(); + this.ctrlNsfPlayer.Visible = true; + this.ctrlNsfPlayer.Focus(); + + _currentGame = InteropEmu.NsfGetHeader().GetSongName(); + } else { + this.MinimumSize = new Size(335, 320); + this.SetScale(_regularScale); + this._isNsfPlayerMode = false; + this.ctrlNsfPlayer.Visible = false; + } + } + } } } diff --git a/GUI.NET/GUI.NET.csproj b/GUI.NET/GUI.NET.csproj index 2a03e6b1..f8988072 100644 --- a/GUI.NET/GUI.NET.csproj +++ b/GUI.NET/GUI.NET.csproj @@ -227,6 +227,12 @@ ctrlHorizontalTrackbar.cs + + UserControl + + + ctrlNsfPlayer.cs + UserControl @@ -508,6 +514,9 @@ ctrlHorizontalTrackbar.cs + + ctrlNsfPlayer.cs + ctrlTrackbar.cs @@ -715,6 +724,9 @@ Always + + + diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index dda1bf41..0df1fedc 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -30,7 +30,7 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void AddKnowGameFolder([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string folder); [DllImport(DLLPath, EntryPoint = "GetArchiveRomList")] private static extern IntPtr GetArchiveRomListWrapper([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string filename); - public static List GetArchiveRomList(string filename) { return new List(PtrToStringUtf8(InteropEmu.GetArchiveRomListWrapper(filename)).Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)); } + public static List GetArchiveRomList(string filename) { return new List(PtrToStringUtf8(InteropEmu.GetArchiveRomListWrapper(filename)).Split(new string[] { "[!|!]" }, StringSplitOptions.RemoveEmptyEntries)); } [DllImport(DLLPath)] public static extern void SetMousePosition(double x, double y); [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool HasZapper(); @@ -96,6 +96,12 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void LoadState(UInt32 stateIndex); [DllImport(DLLPath)] public static extern Int64 GetStateInfo(UInt32 stateIndex); + [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsNsf(); + [DllImport(DLLPath)] public static extern void NsfSelectTrack(Byte trackNumber); + [DllImport(DLLPath)] public static extern Int32 NsfGetCurrentTrack(); + [DllImport(DLLPath, EntryPoint = "NsfGetHeader")] private static extern void NsfGetHeaderWrapper(out NsfHeader header); + [DllImport(DLLPath)] public static extern void NsfSetNsfConfig(Int32 autoDetectSilenceDelay, Int32 moveToNextTrackTime, [MarshalAs(UnmanagedType.I1)]bool disableApuIrqs); + [DllImport(DLLPath)] public static extern UInt32 FdsGetSideCount(); [DllImport(DLLPath)] public static extern void FdsEjectDisk(); [DllImport(DLLPath)] public static extern void FdsInsertDisk(UInt32 diskNumber); @@ -260,6 +266,13 @@ namespace Mesen.GUI } } + public static NsfHeader NsfGetHeader() + { + NsfHeader header = new NsfHeader(); + NsfGetHeaderWrapper(out header); + return header; + } + public static RomInfo GetRomInfo(string filename = "", Int32 archiveFileIndex = -1) { InteropRomInfo romInfo = new InteropRomInfo(); @@ -644,6 +657,93 @@ namespace Mesen.GUI public byte[] Condition; } + public struct NsfHeader + { + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 5)] + public Byte[] Header; + + public Byte Version; + public Byte TotalSongs; + public Byte StartingSong; + public UInt16 LoadAddress; + public UInt16 InitAddress; + public UInt16 PlayAddress; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256)] + public Byte[] SongName; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256)] + public Byte[] ArtistName; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256)] + public Byte[] CopyrightHolder; + + public UInt16 PlaySpeedNtsc; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 8)] + public Byte[] BankSetup; + + public UInt16 PlaySpeedPal; + public Byte Flags; + public Byte SoundChips; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4)] + public Byte[] Padding; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256)] + public Byte[] RipperName; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 20000)] + public Byte[] TrackName; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256)] + public Int32[] TrackLength; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256)] + public Int32[] TrackFade; + + private string ConvertString(Byte[] input) + { + string output = Encoding.UTF8.GetString(input, 0, Array.IndexOf(input, (Byte)0)); + if(output[0] == 0xFFFD) { + //Patch to convert an invalid character at index 0 to a copyright sign + //This is usually the case for NSFe files (not sure what the encoding for NSF/NSFe is meant to be. Is it properly defined?) + return "©" + output.Substring(1); + } + + if(output == "") { + return ResourceHelper.GetMessage("NsfUnknownField"); + } + + return output; + } + + public string GetSongName() + { + return ConvertString(this.SongName); + } + + public string GetArtistName() + { + return ConvertString(this.ArtistName); + } + + public string GetCopyrightHolder() + { + return ConvertString(this.CopyrightHolder); + } + + public string GetRipperName() + { + return ConvertString(this.RipperName); + } + + public string[] GetTrackNames() + { + return Encoding.UTF8.GetString(this.TrackName, 0, Array.IndexOf(this.TrackName, (Byte)0)).Split(new string[] { "[!|!]" }, StringSplitOptions.None); + } + } + [Flags] public enum BreakpointType { diff --git a/GUI.NET/Properties/Resources.Designer.cs b/GUI.NET/Properties/Resources.Designer.cs index 32c8fa3d..cfb936bd 100644 --- a/GUI.NET/Properties/Resources.Designer.cs +++ b/GUI.NET/Properties/Resources.Designer.cs @@ -290,6 +290,26 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap NextTrack { + get { + object obj = ResourceManager.GetObject("NextTrack", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap NsfBackground { + get { + object obj = ResourceManager.GetObject("NsfBackground", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -320,6 +340,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap PrevTrack { + get { + object obj = ResourceManager.GetObject("PrevTrack", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/GUI.NET/Properties/Resources.resx b/GUI.NET/Properties/Resources.resx index 7898ca20..4dbe1259 100644 --- a/GUI.NET/Properties/Resources.resx +++ b/GUI.NET/Properties/Resources.resx @@ -214,4 +214,13 @@ ..\Resources\LogWindow.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\NextTrack.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\NsfBackground.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\PrevTrack.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/GUI.NET/Resources/NextTrack.png b/GUI.NET/Resources/NextTrack.png new file mode 100644 index 0000000000000000000000000000000000000000..2f4f91f47d7818fcb3428b25d70fbe7d9ad29454 GIT binary patch literal 618 zcmV-w0+s!VP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;VI^GGzb&0ozGLK~y+TWBC97Kg}@1#EBEdii?Y-BO)UB zu_*vyV$@*63_U$PT5<6)YeGXq!>z2Wq@|>!n9&sxqXvjo!y`j~hlU6L2VsyHLv3xX zNmO*i-<-Vc?_m)k6P1*dR2Uc-xR5jvr3Q%AfQJA1_3IbH@VdG>ljO9dpTGb7{y%%} z%%2g_;TKg^Rc(QqMSz$Qq=*SM->rphmc=hc6r_Ue%|M~mp|H)G){zS)wza~ZvPQw$E zPH6iVc5cWTfv2)F1}L;ABRO8k~klMo0XqtgiST z856NaM@L7CnVFdfssW6NQ3J%7hIe&!>BJ|*oC*&Q&9%3;myws3XF<{c#>A)rVsQ44 zhzk1yH+=f^>EcyYRkCq$ae_z+Q7|!Tuwkr%)WQE502L3AxZdx0B>(^b07*qoM6N<$ Ef-r^~(f|Me literal 0 HcmV?d00001 diff --git a/GUI.NET/Resources/NsfBackground.png b/GUI.NET/Resources/NsfBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..296f965e3c2f1f32e70bfc3885b970f6e693374e GIT binary patch literal 10105 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 Ef)SY)c>n+a literal 0 HcmV?d00001 diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index 2861dbbb..c2a52e1c 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -2,9 +2,6 @@ #include "../Core/MessageManager.h" #include "../Core/Console.h" -#include "../Windows/Renderer.h" -#include "../Windows/SoundManager.h" -#include "../Windows/WindowsKeyManager.h" #include "../Core/GameServer.h" #include "../Core/GameClient.h" #include "../Core/ClientConnectionData.h" @@ -17,6 +14,10 @@ #include "../Core/VsControlManager.h" #include "../Core/SoundMixer.h" #include "../Core/RomLoader.h" +#include "../Core/NsfMapper.h" +#include "../Windows/Renderer.h" +#include "../Windows/SoundManager.h" +#include "../Windows/WindowsKeyManager.h" NES::Renderer *_renderer = nullptr; SoundManager *_soundManager = nullptr; @@ -82,7 +83,7 @@ namespace InteropEmu { DllExport const char* __stdcall GetArchiveRomList(char* filename) { std::ostringstream out; for(string romName : RomLoader::GetArchiveRomList(filename)) { - out << romName << "/"; + out << romName << "[!|!]"; } _returnString = out.str(); return _returnString.c_str(); @@ -325,6 +326,28 @@ namespace InteropEmu { DllExport void __stdcall GetScreenSize(ScreenSize &size, bool ignoreScale) { VideoDecoder::GetInstance()->GetScreenSize(size, ignoreScale); } + //NSF functions + DllExport bool __stdcall IsNsf() { return NsfMapper::GetInstance() != nullptr; } + DllExport void __stdcall NsfSelectTrack(uint8_t trackNumber) { + if(NsfMapper::GetInstance()) { + NsfMapper::GetInstance()->SelectTrack(trackNumber); + } + } + DllExport int32_t __stdcall NsfGetCurrentTrack(uint8_t trackNumber) { + if(NsfMapper::GetInstance()) { + return NsfMapper::GetInstance()->GetCurrentTrack(); + } + return -1; + } + DllExport void __stdcall NsfGetHeader(NsfHeader* header) { + if(NsfMapper::GetInstance()) { + *header = NsfMapper::GetInstance()->GetNsfHeader(); + } + } + DllExport void __stdcall NsfSetNsfConfig(int32_t autoDetectSilenceDelay, int32_t moveToNextTrackTime, bool disableApuIrqs) { + EmulationSettings::SetNsfConfig(autoDetectSilenceDelay, moveToNextTrackTime, disableApuIrqs); + } + //FDS functions DllExport uint32_t __stdcall FdsGetSideCount() { return FDS::GetSideCount(); } DllExport void __stdcall FdsEjectDisk() { FDS::EjectDisk(); }