NSF/NSFe support

This commit is contained in:
Souryo 2016-06-25 20:46:54 -04:00
parent c74903799c
commit b852edfec8
49 changed files with 2773 additions and 122 deletions

View file

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

View file

@ -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)
{

View file

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

View file

@ -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();

View file

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

View file

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

View file

@ -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> 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;

View file

@ -33,6 +33,8 @@ class Console
bool _stop = false;
bool _reset = false;
atomic<bool> _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

View file

@ -186,6 +186,8 @@
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -215,6 +217,8 @@
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -243,6 +247,8 @@
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -269,6 +275,8 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -295,6 +303,8 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -321,6 +331,8 @@
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -347,6 +359,8 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -373,6 +387,8 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<DisableLanguageExtensions>true</DisableLanguageExtensions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ShowIncludes>
</ShowIncludes>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -452,6 +468,10 @@
<ClInclude Include="ModChannel.h" />
<ClInclude Include="Namco163.h" />
<ClInclude Include="Namco163Audio.h" />
<ClInclude Include="NsfeLoader.h" />
<ClInclude Include="NsfLoader.h" />
<ClInclude Include="NsfMapper.h" />
<ClInclude Include="NsfPpu.h" />
<ClInclude Include="PlayerListMessage.h" />
<ClInclude Include="ReverbFilter.h" />
<ClInclude Include="RomData.h" />
@ -610,6 +630,7 @@
<ClCompile Include="GameServerConnection.cpp" />
<ClCompile Include="HdVideoFilter.cpp" />
<ClCompile Include="iNesLoader.cpp" />
<ClCompile Include="NsfMapper.cpp" />
<ClCompile Include="NtscFilter.cpp" />
<ClCompile Include="ReverbFilter.cpp" />
<ClCompile Include="RomLoader.cpp" />

View file

@ -670,6 +670,18 @@
<ClInclude Include="VsZapper.h">
<Filter>Nes\Controllers</Filter>
</ClInclude>
<ClInclude Include="NsfLoader.h">
<Filter>Nes\RomLoader</Filter>
</ClInclude>
<ClInclude Include="NsfMapper.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="NsfeLoader.h">
<Filter>Nes\RomLoader</Filter>
</ClInclude>
<ClInclude Include="NsfPpu.h">
<Filter>Nes</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -831,5 +843,8 @@
<ClCompile Include="VsZapper.cpp">
<Filter>Nes\Controllers</Filter>
</ClCompile>
<ClCompile Include="NsfMapper.cpp">
<Filter>Nes\Mappers</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -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;

View file

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

View file

@ -14,11 +14,22 @@ class MemoryRanges
private:
vector<uint16_t> _ramReadAddresses;
vector<uint16_t> _ramWriteAddresses;
bool _allowOverride = false;
public:
vector<uint16_t>* GetRAMReadAddresses() { return &_ramReadAddresses; }
vector<uint16_t>* 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) {

View file

@ -2,6 +2,7 @@
#include "stdafx.h"
#include "SquareChannel.h"
#include "BaseExpansionAudio.h"
#include "CPU.h"
class MMC5Square : public SquareChannel
{

View file

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

View file

@ -10,5 +10,6 @@ class MapperFactory
public:
static const uint16_t FdsMapperID = 65535;
static const uint16_t NsfMapperID = 65534;
static shared_ptr<BaseMapper> InitializeFromFile(string romFilename, stringstream *filestream, string ipsFilename, int32_t archiveFileIndex);
};

View file

@ -60,10 +60,10 @@ void MemoryManager::WriteRegister(uint16_t addr, uint8_t value)
}
}
void MemoryManager::InitializeMemoryHandlers(IMemoryHandler** memoryHandlers, IMemoryHandler* handler, vector<uint16_t> *addresses)
void MemoryManager::InitializeMemoryHandlers(IMemoryHandler** memoryHandlers, IMemoryHandler* handler, vector<uint16_t> *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()

View file

@ -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<uint16_t> *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<uint16_t> *addresses);
void RegisterIODevice(IMemoryHandler *handler);
uint8_t DebugRead(uint16_t addr);

160
Core/NsfLoader.h Normal file
View file

@ -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<string> 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("<none>");
}
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<uint8_t>& 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;
}
};

389
Core/NsfMapper.cpp Normal file
View file

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

279
Core/NsfMapper.h Normal file
View file

@ -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
*/

22
Core/NsfPpu.h Normal file
View file

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

194
Core/NsfeLoader.h Normal file
View file

@ -0,0 +1,194 @@
#pragma once
#include "stdafx.h"
#include "RomData.h"
#include <algorithm>
#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<string> ReadStrings(uint8_t* &data, uint8_t* chunkEnd)
{
vector<string> 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<string> 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<string> 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<uint8_t>& 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;
}
};

View file

@ -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)

View file

@ -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:

View file

@ -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<vector<uint8_t>> FdsDiskData;
vector<uint8_t> RawData;
uint32_t Crc32;
uint32_t Crc32 = 0;
bool Error = false;
NESHeader NesHeader;
NsfHeader NsfHeader;
};

View file

@ -5,6 +5,8 @@
#include "RomLoader.h"
#include "iNesLoader.h"
#include "FdsLoader.h"
#include "NsfLoader.h"
#include "NsfeLoader.h"
vector<string> RomLoader::GetArchiveRomList(string filename)
{
@ -17,11 +19,11 @@ vector<string> 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<string> fileList = reader.GetFileList({ ".nes", ".fds" });
vector<string> 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;
}

View file

@ -6,6 +6,8 @@
IAudioDevice* SoundMixer::AudioDevice = nullptr;
unique_ptr<WaveRecorder> 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;
}

View file

@ -20,14 +20,14 @@ public:
private:
static unique_ptr<WaveRecorder> _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);
};

View file

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

484
GUI.NET/Controls/ctrlNsfPlayer.Designer.cs generated Normal file
View file

@ -0,0 +1,484 @@
namespace Mesen.GUI.Controls
{
partial class ctrlNsfPlayer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View file

@ -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<ComboboxItem> _trackList = new List<ComboboxItem>();
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<ComboboxItem>();
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; }
}
}

View file

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="tmrFastForward.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>154, 17</value>
</metadata>
</root>

View file

@ -5,8 +5,8 @@
<Message ID="FilterMovie">Movie files (*.mmo)|*.mmo|All Files (*.*)|*.*</Message>
<Message ID="FilterWave">Wave files (*.wav)|*.wav|All Files (*.*)|*.*</Message>
<Message ID="FilterPalette">Palette Files (*.pal)|*.pal|All Files (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>
<Message ID="FilterTest">Test files (*.mtp)|*.mtp|All (*.*)|*.*</Message>
<Message ID="Resume">Resume</Message>
@ -19,6 +19,10 @@
<Message ID="RomsFound">{0} roms found</Message>
<Message ID="NsfNextTrack">Next Track (Hold to fast forward)</Message>
<Message ID="NsfUnnamedTrack">&lt;no name&gt;</Message>
<Message ID="NsfUnknownField">&lt;unknown&gt;</Message>
<Message ID="CouldNotInstallRuntime">The Visual Studio Runtime could not be installed properly.</Message>
<Message ID="EmptyState">&lt;empty&gt;</Message>
<Message ID="ErrorWhileCheckingUpdates">An error has occurred while trying to check for updates.&#xA;&#xA;Error details:&#xA;{0}</Message>

View file

@ -87,6 +87,12 @@
<Control ID="mnuHelp">Aide</Control>
<Control ID="mnuCheckForUpdates">Recherche de mises-à-jour</Control>
<Control ID="mnuAbout">À propos de...</Control>
<!-- NSF Player -->
<Control ID="lblTitle">Titre</Control>
<Control ID="lblArtist">Artiste</Control>
<Control ID="lblCopyright">Copyright</Control>
<Control ID="lblSoundChips">Puces audio</Control>
</Form>
<Form ID="frmLogWindow" Title="Fenêtre de log">
<Control ID="btnClose">Fermer</Control>
@ -263,7 +269,14 @@
<Control ID="btnResync">Resynchroniser</Control>
<Control ID="chkDisableGameDatabase">Désactiver la base de données des jeux</Control>
<Control ID="tpgNsf">NSF / NSFe</Control>
<Control ID="chkNsfAutoDetectSilence">Jouer la piste suivante après</Control>
<Control ID="lblNsfMillisecondsOfSilence">millisecondes de silence</Control>
<Control ID="chkNsfMoveToNextTrackAfterTime">Limiter la durée des pistes à</Control>
<Control ID="lblNsfSeconds">secondes</Control>
<Control ID="chkNsfDisableApuIrqs">Désactiver les IRQs du APU</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Annuler</Control>
</Form>
@ -344,8 +357,8 @@
<Message ID="FilterMovie">Films (*.mmo)|*.mmo|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterWave">Fichiers wave (*.wav)|*.wav|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterPalette">Fichier de palette (*.pal)|*.pal|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>
<Message ID="FilterTest">Fichiers de test (*.mtp)|*.mtp|Tous les fichiers (*.*)|*.*</Message>
<Message ID="Resume">Continuer</Message>
@ -358,6 +371,10 @@
<Message ID="RomsFound">{0} roms trouvés</Message>
<Message ID="NsfNextTrack">Piste Suivante (Garder enfoncé pour jouer plus rapidement)</Message>
<Message ID="NsfUnnamedTrack">[sans nom]</Message>
<Message ID="NsfUnknownField">[inconnu]</Message>
<Message ID="CouldNotInstallRuntime">Le package Redistribuable Visual C++ pour Visual Studio 2015 n'a pas été installé correctement.</Message>
<Message ID="EmptyState">&lt;aucune sauvegarde&gt;</Message>
<Message ID="ErrorWhileCheckingUpdates">Une erreur s'est produite lors de la recherche de mises-à-jour.&#xA;&#xA;Détails de l'erreur :&#xA;{0}</Message>

View file

@ -87,6 +87,12 @@
<Control ID="mnuHelp">ヘルプ</Control>
<Control ID="mnuCheckForUpdates">アップデートの確認</Control>
<Control ID="mnuAbout">Mesenとは</Control>
<!-- NSF Player -->
<Control ID="lblTitle">タイトル</Control>
<Control ID="lblArtist">作者</Control>
<Control ID="lblCopyright">コピーライト</Control>
<Control ID="lblSoundChips">音源チップ</Control>
</Form>
<Form ID="frmLogWindow" Title="ログウィンドウ">
<Control ID="btnClose">閉じる</Control>
@ -263,6 +269,13 @@
<Control ID="chkDisableGameDatabase">ゲームデータベースを無効にする</Control>
<Control ID="tpgNsf">NSF / NSFe</Control>
<Control ID="chkNsfAutoDetectSilence">無音検出:</Control>
<Control ID="lblNsfMillisecondsOfSilence">ミリ秒間で無音の場合、次の曲を再生</Control>
<Control ID="chkNsfMoveToNextTrackAfterTime">曲の最大長さを</Control>
<Control ID="lblNsfSeconds">秒に固定する</Control>
<Control ID="chkNsfDisableApuIrqs">APUのIRQを無効にする</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">キャンセル</Control>
</Form>
@ -336,8 +349,8 @@
<Message ID="FilterMovie">動画 (*.mmo)|*.mmo|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterWave">WAVファイル (*.wav)|*.wav|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterPalette">パレットファイル (*.pal)|*.pal|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterRom">対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds)|*.NES;*.ZIP;*.FDS;*.7z|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterRomIps">対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|IPSファイル (*.ips)|*.IPS|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterRom">対応するすべてのファイル (*.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|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterRomIps">対応するすべてのファイル (*.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|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterTest">テストファイル (*.mtp)|*.mtp|すべてのファイル (*.*)|*.*</Message>
<Message ID="Resume">再開</Message>
@ -350,6 +363,10 @@
<Message ID="RomsFound">{0}個</Message>
<Message ID="NsfNextTrack">次の曲 (長押しで早送り)</Message>
<Message ID="NsfUnnamedTrack">[名無し]</Message>
<Message ID="NsfUnknownField">[不明]</Message>
<Message ID="CouldNotInstallRuntime">Microsoft Visual Studio 2015のVisual C++再頒布可能パッケージはインストールできませんでした。</Message>
<Message ID="EmptyState">&lt;なし&gt;</Message>
<Message ID="ErrorWhileCheckingUpdates">アップデートを確認する時にエラーが発生しました。&#xA;&#xA;エラーの詳細:&#xA;{0}</Message>

View file

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

View file

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

View file

@ -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) {

View file

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

View file

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

View file

@ -227,6 +227,12 @@
<Compile Include="Controls\ctrlHorizontalTrackbar.Designer.cs">
<DependentUpon>ctrlHorizontalTrackbar.cs</DependentUpon>
</Compile>
<Compile Include="Controls\ctrlNsfPlayer.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Controls\ctrlNsfPlayer.Designer.cs">
<DependentUpon>ctrlNsfPlayer.cs</DependentUpon>
</Compile>
<Compile Include="Controls\ctrlTrackbar.cs">
<SubType>UserControl</SubType>
</Compile>
@ -508,6 +514,9 @@
<EmbeddedResource Include="Controls\ctrlHorizontalTrackbar.resx">
<DependentUpon>ctrlHorizontalTrackbar.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\ctrlNsfPlayer.resx">
<DependentUpon>ctrlNsfPlayer.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\ctrlTrackbar.resx">
<DependentUpon>ctrlTrackbar.cs</DependentUpon>
</EmbeddedResource>
@ -715,6 +724,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Icon.ico" />
<None Include="Resources\NsfBackground.png" />
<None Include="Resources\PrevTrack.png" />
<None Include="Resources\NextTrack.png" />
<None Include="Resources\LogWindow.png" />
<None Include="Resources\format-justify-fill.png" />
<None Include="Resources\Record.png" />

View file

@ -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<string> GetArchiveRomList(string filename) { return new List<string>(PtrToStringUtf8(InteropEmu.GetArchiveRomListWrapper(filename)).Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)); }
public static List<string> GetArchiveRomList(string filename) { return new List<string>(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
{

View file

@ -290,6 +290,26 @@ namespace Mesen.GUI.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap NextTrack {
get {
object obj = ResourceManager.GetObject("NextTrack", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap NsfBackground {
get {
object obj = ResourceManager.GetObject("NsfBackground", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -320,6 +340,16 @@ namespace Mesen.GUI.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap PrevTrack {
get {
object obj = ResourceManager.GetObject("PrevTrack", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View file

@ -214,4 +214,13 @@
<data name="LogWindow" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\LogWindow.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="NextTrack" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\NextTrack.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="NsfBackground" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\NsfBackground.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="PrevTrack" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\PrevTrack.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View file

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