Implement track lengths for SPC + apply track length & silence detection to all audio players, not just NSF

This commit is contained in:
Sour 2021-05-07 20:42:25 -04:00
parent 4d092bc07b
commit b964eb2e09
20 changed files with 244 additions and 265 deletions

View file

@ -12,8 +12,9 @@ class GbsCart : public GbCart
{
private:
uint8_t _prgBank = 1;
uint16_t _currentTrack = 0;
GbsHeader _header;
uint8_t _currentTrack = 0;
uint64_t _startClock = 0;
GbsHeader _header = {};
public:
GbsCart(GbsHeader header)
@ -27,7 +28,7 @@ public:
_memoryManager->MapRegisters(0x2000, 0x3FFF, RegisterAccess::Write);
}
void InitPlayback(uint16_t selectedTrack)
void InitPlayback(uint8_t selectedTrack)
{
_currentTrack = selectedTrack;
@ -62,7 +63,7 @@ public:
state = {};
state.SP = _header.StackPointer[0] | (_header.StackPointer[1] << 8);
state.PC = 0;
state.A = selectedTrack;
state.A = (uint8_t)selectedTrack;
state.IME = true; //enable CPU interrupts
//Disable boot room
@ -81,6 +82,7 @@ public:
//Clear all IRQ requests (needed when switching tracks)
_memoryManager->ClearIrqRequest(0xFF);
_startClock = _gameboy->GetMasterClock();
_prgBank = 1;
RefreshMappings();
}
@ -93,7 +95,9 @@ public:
info.Comment = string(_header.Copyright, 32);
info.TrackNumber = _currentTrack + 1;
info.TrackCount = _header.TrackCount;
info.Position = (double)_gameboy->GetMasterClock() / _gameboy->GetMasterClockRate();
info.Position = (double)(_gameboy->GetMasterClock() - _startClock) / _gameboy->GetMasterClockRate();
info.Length = 0;
info.FadeLength = 0;
return info;
}
@ -102,7 +106,11 @@ public:
int selectedTrack = _currentTrack;
switch(p.Action) {
case AudioPlayerAction::NextTrack: selectedTrack++; break;
case AudioPlayerAction::PrevTrack: selectedTrack--; break;
case AudioPlayerAction::PrevTrack:
if(GetAudioTrackInfo().Position < 2) {
selectedTrack--;
}
break;
case AudioPlayerAction::SelectTrack: selectedTrack = (int)p.TrackNumber; break;
}
@ -112,8 +120,14 @@ public:
selectedTrack = 0;
}
auto lock = _gameboy->GetEmulator()->AcquireLock();
InitPlayback(selectedTrack);
//Asynchronously move to the next file
//Can't do this in the current thread in some contexts (e.g when track reaches end)
//because this is called from the emulation thread, which may cause infinite recursion
thread switchTrackTask([this, selectedTrack]() {
auto lock = _gameboy->GetEmulator()->AcquireLock();
InitPlayback(selectedTrack);
});
switchTrackTask.detach();
}
void RefreshMappings() override
@ -132,6 +146,6 @@ public:
void Serialize(Serializer& s) override
{
s.Stream(_prgBank, _currentTrack);
s.Stream(_prgBank, _currentTrack, _startClock);
}
};

View file

@ -148,19 +148,12 @@ void NsfMapper::Reset(bool softReset)
_console->GetCpu()->SetIrqMask((uint8_t)IRQSource::External);
_irqCounter = 0;
_allowSilenceDetection = false;
_trackEndCounter = -1;
_trackEnded = false;
_trackFadeCounter = -1;
_mmc5Audio.reset(new Mmc5Audio(_console));
_vrc6Audio.reset(new Vrc6Audio(_console));
_vrc7Audio.reset(new Vrc7Audio(_console));
_fdsAudio.reset(new FdsAudio(_console));
_namcoAudio.reset(new Namco163Audio(_console));
_sunsoftAudio.reset(new Sunsoft5bAudio(_console));
InternalSelectTrack(_songNumber);
}
void NsfMapper::GetMemoryRanges(MemoryRanges& ranges)
@ -203,41 +196,6 @@ void NsfMapper::ClearIrq()
_console->GetCpu()->ClearIrqSource(IRQSource::External);
}
void NsfMapper::ClockLengthAndFadeCounters()
{
NesSoundMixer* mixer = _console->GetSoundMixer();
if(_trackEndCounter > 0) {
_trackEndCounter--;
if(_trackEndCounter == 0) {
_trackEnded = true;
}
}
if((_trackEndCounter < 0 || _allowSilenceDetection) && _silenceDetectDelay > 0) {
//No track length specified
if(mixer->GetMuteFrameCount() * NesSoundMixer::CycleLength > _silenceDetectDelay) {
//Auto detect end of track after AutoDetectSilenceDelay (in ms) has gone by without sound
_trackEnded = true;
_trackFadeCounter = 0;
mixer->ResetMuteFrameCount();
}
}
if(_trackEnded) {
if(_trackFadeCounter > 0) {
if(_fadeLength != 0) {
double fadeRatio = (double)_trackFadeCounter / (double)_fadeLength * 1.2;
mixer->SetFadeRatio(std::max(0.0, fadeRatio - 0.2));
}
_trackFadeCounter--;
}
if(_trackFadeCounter <= 0) {
SelectNextTrack();
}
}
}
void NsfMapper::SelectNextTrack()
{
if(!_settings->GetAudioPlayerConfig().Repeat) {
@ -251,7 +209,6 @@ void NsfMapper::SelectNextTrack()
}
}
SelectTrack(_songNumber);
_trackEnded = false;
}
void NsfMapper::ProcessCpuClock()
@ -284,8 +241,6 @@ void NsfMapper::ProcessCpuClock()
}
}
ClockLengthAndFadeCounters();
if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) {
_mmc5Audio->Clock();
}
@ -381,48 +336,6 @@ void NsfMapper::WriteRegister(uint16_t addr, uint8_t value)
}
}
void NsfMapper::InternalSelectTrack(uint8_t trackNumber)
{
_songNumber = trackNumber;
//Selecting tracking after a reset
_console->GetSoundMixer()->SetFadeRatio(1.0);
NesConfig& cfg = _console->GetNesConfig();
uint32_t clockRate = _emu->GetMasterClockRate();
//Set track length/fade counters (NSFe)
if(_nsfHeader.TrackLength[trackNumber] >= 0) {
_trackEndCounter = (int32_t)((double)_nsfHeader.TrackLength[trackNumber] / 1000.0 * clockRate);
_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
if(cfg.NsfMoveToNextTrackAfterTime) {
_trackEndCounter = (cfg.NsfMoveToNextTrackTime - 1) * clockRate;
_allowSilenceDetection = true;
} else {
_trackEndCounter = 0;
_allowSilenceDetection = false;
}
}
if(_nsfHeader.TrackFade[trackNumber] >= 0) {
_trackFadeCounter = (int32_t)((double)_nsfHeader.TrackFade[trackNumber] / 1000.0 * clockRate);
} else {
//Default to 1 sec fade if none is specified (negative number)
_trackFadeCounter = clockRate;
}
if(cfg.NsfAutoDetectSilence) {
_silenceDetectDelay = (uint32_t)((double)cfg.NsfAutoDetectSilenceDelay / 1000.0 * clockRate);
} else {
_silenceDetectDelay = 0;
}
_fadeLength = _trackFadeCounter;
}
void NsfMapper::SelectTrack(uint8_t trackNumber)
{
if(trackNumber < _nsfHeader.TotalSongs) {
@ -452,7 +365,7 @@ ConsoleFeatures NsfMapper::GetAvailableFeatures()
AudioTrackInfo NsfMapper::GetAudioTrackInfo()
{
NesConfig& cfg = _console->GetNesConfig();
NesConfig& cfg = _console->GetNesConfig();
AudioTrackInfo track = {};
track.Artist = _nsfHeader.ArtistName;
@ -462,9 +375,8 @@ AudioTrackInfo NsfMapper::GetAudioTrackInfo()
track.SongTitle = _nsfHeader.TrackNames.size() > _songNumber ? _nsfHeader.TrackNames[_songNumber] : "";
track.Position = _console->GetPpuFrame().FrameCount / _console->GetFps();
if(_nsfHeader.TrackLength[_songNumber] > 0) {
track.Length = _nsfHeader.TrackLength[_songNumber] / 1000 + _nsfHeader.TrackFade[_songNumber] / 1000;
} else if(cfg.NsfMoveToNextTrackAfterTime) {
track.Length = cfg.NsfMoveToNextTrackTime;
track.Length = _nsfHeader.TrackLength[_songNumber] / 1000.0 + _nsfHeader.TrackFade[_songNumber] / 1000.0;
track.FadeLength = _nsfHeader.TrackFade[_songNumber] / 1000.0;
}
track.TrackNumber = _songNumber + 1;
track.TrackCount = _nsfHeader.TotalSongs;
@ -476,7 +388,12 @@ void NsfMapper::ProcessAudioPlayerAction(AudioPlayerActionParams p)
int selectedTrack = _songNumber;
switch(p.Action) {
case AudioPlayerAction::NextTrack: selectedTrack++; break;
case AudioPlayerAction::PrevTrack: selectedTrack--; break;
case AudioPlayerAction::PrevTrack:
if(GetAudioTrackInfo().Position < 2) {
selectedTrack--;
}
break;
case AudioPlayerAction::SelectTrack: selectedTrack = (int)p.TrackNumber; break;
}
@ -501,7 +418,6 @@ void NsfMapper::Serialize(Serializer& s)
s.Stream(_sunsoftAudio.get());
s.Stream(
_irqCounter, _mmc5MultiplierValues[0], _mmc5MultiplierValues[1], _trackEndCounter, _trackFadeCounter,
_fadeLength, _silenceDetectDelay, _trackEnded, _allowSilenceDetection, _hasBankSwitching, _songNumber
_irqCounter, _mmc5MultiplierValues[0], _mmc5MultiplierValues[1], _hasBankSwitching, _songNumber
);
}

View file

@ -22,9 +22,9 @@ private:
Sunsoft = 0x20
};
EmuSettings* _settings;
EmuSettings* _settings = nullptr;
NsfHeader _nsfHeader;
NsfHeader _nsfHeader = {};
unique_ptr<Mmc5Audio> _mmc5Audio;
unique_ptr<Vrc6Audio> _vrc6Audio;
unique_ptr<Vrc7Audio> _vrc7Audio;
@ -32,17 +32,11 @@ private:
unique_ptr<Namco163Audio> _namcoAudio;
unique_ptr<Sunsoft5bAudio> _sunsoftAudio;
uint8_t _mmc5MultiplierValues[2];
uint8_t _mmc5MultiplierValues[2] = {};
uint32_t _irqCounter = 0;
int32_t _trackEndCounter;
int32_t _trackFadeCounter;
int32_t _fadeLength;
uint32_t _silenceDetectDelay;
bool _trackEnded;
bool _allowSilenceDetection;
bool _hasBankSwitching;
bool _hasBankSwitching = false;
uint8_t _songNumber = 0;
@ -74,8 +68,6 @@ private:
bool HasBankSwitching();
void InternalSelectTrack(uint8_t trackNumber);
void ClockLengthAndFadeCounters();
void SelectNextTrack();
protected:

View file

@ -49,8 +49,6 @@ void NesSoundMixer::Reset()
/*if(_oggMixer) {
_oggMixer->Reset(_settings->GetSampleRate());
}*/
_fadeRatio = 1.0;
_muteFrameCount = 0;
_sampleCount = 0;
_previousOutputLeft = 0;
@ -223,21 +221,16 @@ void NesSoundMixer::EndFrame(uint32_t time)
for(size_t i = 0, len = _timestamps.size(); i < len; i++) {
uint32_t stamp = _timestamps[i];
for(uint32_t j = 0; j < MaxChannelCount; j++) {
if(_channelOutput[j][stamp] != 0) {
//Assume any change in output means sound is playing, disregarding volume options
//NSF tracks that mute the triangle channel by setting it to a high-frequency value will not be considered silent
muteFrame = false;
}
_currentOutput[j] += _channelOutput[j][stamp];
}
int16_t currentOutput = GetOutputVolume(false) * 4;
blip_add_delta(_blipBufLeft, stamp, (int)((currentOutput - _previousOutputLeft) * _fadeRatio));
blip_add_delta(_blipBufLeft, stamp, (int)(currentOutput - _previousOutputLeft));
_previousOutputLeft = currentOutput;
if(_hasPanning) {
currentOutput = GetOutputVolume(true) * 4;
blip_add_delta(_blipBufRight, stamp, (int)((currentOutput - _previousOutputRight) * _fadeRatio));
blip_add_delta(_blipBufRight, stamp, (int)(currentOutput - _previousOutputRight));
_previousOutputRight = currentOutput;
}
}
@ -247,31 +240,11 @@ void NesSoundMixer::EndFrame(uint32_t time)
blip_end_frame(_blipBufRight, time);
}
if(muteFrame) {
_muteFrameCount++;
} else {
_muteFrameCount = 0;
}
//Reset everything
_timestamps.clear();
memset(_channelOutput, 0, sizeof(_channelOutput));
}
void NesSoundMixer::SetFadeRatio(double fadeRatio)
{
_fadeRatio = fadeRatio;
}
uint32_t NesSoundMixer::GetMuteFrameCount()
{
return _muteFrameCount;
}
void NesSoundMixer::ResetMuteFrameCount()
{
_muteFrameCount = 0;
}
//TODO
/*
OggMixer* NesSoundMixer::GetOggMixer()

View file

@ -23,11 +23,9 @@ private:
static constexpr uint32_t MaxSamplesPerFrame = MaxSampleRate / 60 * 4 * 2; //x4 to allow CPU overclocking up to 10x, x2 for panning stereo
static constexpr uint32_t MaxChannelCount = 11;
NesConsole* _console;
EmuSettings* _settings;
SoundMixer* _mixer;
double _fadeRatio;
uint32_t _muteFrameCount;
NesConsole* _console = nullptr;
EmuSettings* _settings = nullptr;
SoundMixer* _mixer = nullptr;
//TODO
//unique_ptr<OggMixer> _oggMixer;
@ -40,20 +38,20 @@ private:
int16_t _previousOutputRight = 0;
vector<uint32_t> _timestamps;
int16_t _channelOutput[MaxChannelCount][CycleLength];
int16_t _currentOutput[MaxChannelCount];
int16_t _channelOutput[MaxChannelCount][CycleLength] = {};
int16_t _currentOutput[MaxChannelCount] = {};
blip_t* _blipBufLeft;
blip_t* _blipBufRight;
int16_t* _outputBuffer;
blip_t* _blipBufLeft = nullptr;
blip_t* _blipBufRight = nullptr;
int16_t* _outputBuffer = nullptr;
size_t _sampleCount = 0;
double _volumes[MaxChannelCount];
double _panning[MaxChannelCount];
double _volumes[MaxChannelCount] = {};
double _panning[MaxChannelCount] = {};
uint32_t _sampleRate;
uint32_t _clockRate;
uint32_t _sampleRate = 0;
uint32_t _clockRate = 0;
bool _hasPanning;
bool _hasPanning = false;
__forceinline double GetChannelOutput(AudioChannel channel, bool forRightChannel);
__forceinline int16_t GetOutputVolume(bool forRightChannel);
@ -73,11 +71,6 @@ public:
void PlayAudioBuffer(uint32_t cycle);
void AddDelta(AudioChannel channel, uint32_t time, int16_t delta);
//For NSF/NSFe
uint32_t GetMuteFrameCount();
void ResetMuteFrameCount();
void SetFadeRatio(double fadeRatio);
//OggMixer* GetOggMixer();
void Serialize(Serializer& s) override;

View file

@ -257,7 +257,8 @@ AudioTrackInfo Console::GetAudioTrackInfo()
track.GameTitle = spc->GameTitle;
track.SongTitle = spc->SongTitle;
track.Position = _ppu->GetFrameCount() / GetFps();
track.Length = -1;
track.Length = spc->TrackLength + (spc->FadeLength / 1000.0);
track.FadeLength = spc->FadeLength / 1000.0;
track.TrackNumber = _spcTrackNumber + 1;
track.TrackCount = (uint32_t)_spcPlaylist.size();
@ -270,7 +271,11 @@ void Console::ProcessAudioPlayerAction(AudioPlayerActionParams p)
if(_spcTrackNumber >= 0) {
int i = (int)_spcTrackNumber;
switch(p.Action) {
case AudioPlayerAction::PrevTrack: i--; break;
case AudioPlayerAction::PrevTrack:
if(GetAudioTrackInfo().Position < 2) {
i--;
}
break;
case AudioPlayerAction::NextTrack: i++; break;
}
@ -280,7 +285,13 @@ void Console::ProcessAudioPlayerAction(AudioPlayerActionParams p)
i = 0;
}
_emu->LoadRom(VirtualFile(_spcPlaylist[i]), VirtualFile());
//Asynchronously move to the next file
//Can't do this in the current thread in some contexts (e.g when track reaches end)
//because this is called from the emulation thread.
thread switchTrackTask([this, i]() {
_emu->LoadRom(VirtualFile(_spcPlaylist[i]), VirtualFile());
});
switchTrackTask.detach();
}
}

View file

@ -27,6 +27,9 @@ public:
uint8_t DspRegs[128];
uint8_t SpcRam[0x10000];
uint32_t TrackLength;
uint32_t FadeLength;
SpcFileData(uint8_t* spcData)
{
SongTitle = string(spcData + 0x2E, spcData + 0x2E + 0x20);
@ -35,6 +38,20 @@ public:
Artist = string(spcData + 0xB1, spcData + 0xB1 + 0x20);
Comment = string(spcData + 0x7E, spcData + 0x7E + 0x20);
string strTrackLength = string(spcData + 0xA9, spcData + 0xA9 + 0x03);
string strFadeLength = string(spcData + 0xAC, spcData + 0xAC + 0x05);
bool isStringValue = true;
for(char c : strTrackLength) {
if(c != 0 && (c < '0' || c > '9')) {
isStringValue = false;
}
}
if(isStringValue) {
TrackLength = std::stoi(strTrackLength);
FadeLength = std::stoi(strFadeLength);
}
memcpy(SpcRam, spcData + 0x100, 0xFFC0);
memcpy(SpcRam + 0xFFC0, spcData + 0x101C0, 0x40);

View file

@ -3,6 +3,7 @@
#include "Shared/Audio/SoundMixer.h"
#include "Shared/Video/DebugHud.h"
#include "Shared/Emulator.h"
#include "Shared/EmuSettings.h"
#include "Shared/Video/DrawStringCommand.h"
static constexpr double PI = 3.14159265358979323846;
@ -30,6 +31,9 @@ string AudioPlayerHud::FormatSeconds(uint32_t s)
void AudioPlayerHud::Draw()
{
AudioTrackInfo trackInfo = _emu->GetAudioTrackInfo();
if(trackInfo.Position <= 1) {
_changeTrackPending = false;
}
_hud->DrawRectangle(0, 0, 256, 240, 0, true, 1);
@ -64,7 +68,7 @@ void AudioPlayerHud::Draw()
if(trackInfo.Length <= 0) {
_hud->DrawString(215, 208, " " + position + " ", 0xFFFFFF, 0, 1);
} else {
position += " / " + FormatSeconds(trackInfo.Length);
position += " / " + FormatSeconds((uint32_t)trackInfo.Length);
_hud->DrawString(177, 208, " " + position + " ", 0xFFFFFF, 0, 1);
constexpr int barWidth = 222;
@ -116,6 +120,7 @@ void AudioPlayerHud::Draw()
_hud->DrawString(228, top + 10, "20kHz", fgColor, bgColor, 1);
if(_amplitudes.size() >= N / 2) {
bool silent = true;
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 32; j++) {
double freqRange = ranges[i][1] - ranges[i][0];
@ -133,14 +138,57 @@ void AudioPlayerHud::Draw()
avgAmp *= ranges[i][2];
avgAmp = std::min<double>(maxVal, avgAmp);
if(avgAmp >= 1) {
silent = false;
}
int red = std::min(255, (int)(256 * (avgAmp / maxVal) * 2));
int green = std::max(0, std::min(255, (int)(256 * ((maxVal - avgAmp) / maxVal) * 2)));
_hud->DrawRectangle(i*32+j, 190, 1, (int)-avgAmp, red << 16 | green << 8, true, 1);
}
}
if(!silent) {
_silenceTimer.Reset();
} else {
AudioConfig audioCfg = _emu->GetSettings()->GetAudioConfig();
if(audioCfg.AudioPlayerAutoDetectSilence && _silenceTimer.GetElapsedMS() >= audioCfg.AudioPlayerSilenceDelay * 1000) {
//Silence detected, move to next track
_silenceTimer.Reset();
MoveToNextTrack();
}
}
}
}
void AudioPlayerHud::MoveToNextTrack()
{
if(!_changeTrackPending) {
_changeTrackPending = true;
AudioPlayerActionParams params = {};
params.Action = AudioPlayerAction::NextTrack;
_emu->ProcessAudioPlayerAction(params);
}
}
uint32_t AudioPlayerHud::GetVolume()
{
AudioTrackInfo info = _emu->GetAudioTrackInfo();
if(info.Length > 0) {
if(info.Position >= info.Length) {
//Switch to next track
MoveToNextTrack();
return 0;
} else if(info.Position >= info.Length - info.FadeLength) {
double fadeStart = info.Length - info.FadeLength;
double ratio = 1.0 - ((info.Position - fadeStart) / info.FadeLength);
return (uint32_t)(ratio * _emu->GetSettings()->GetAudioPlayerConfig().Volume);
}
}
return _emu->GetSettings()->GetAudioPlayerConfig().Volume;
}
void AudioPlayerHud::ProcessSamples(int16_t* samples, size_t sampleCount, uint32_t sampleRate)
{
_sampleRate = sampleRate;

View file

@ -2,6 +2,7 @@
#include "stdafx.h"
#include <complex>
#include "Utilities/kissfft.h"
#include "Utilities/Timer.h"
class Emulator;
class SoundMixer;
@ -20,16 +21,21 @@ private:
std::vector<double> _amplitudes;
std::deque<int16_t> _samples;
Timer _silenceTimer;
bool _changeTrackPending = false;
uint32_t _sampleRate;
double _hannWindow[N] = {};
double _input[N] = {};
std::complex<double> _out[N] = {};
string FormatSeconds(uint32_t s);
void MoveToNextTrack();
public:
AudioPlayerHud(Emulator* emu);
void Draw();
uint32_t GetVolume();
void ProcessSamples(int16_t* samples, size_t sampleCount, uint32_t sampleRate);
};

View file

@ -9,7 +9,8 @@ struct AudioTrackInfo
string Comment;
double Position;
int32_t Length;
double Length;
double FadeLength;
uint32_t TrackNumber;
uint32_t TrackCount;

View file

@ -66,11 +66,11 @@ void SoundMixer::StopAudio(bool clearBuffer)
void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount, uint32_t sourceRate)
{
EmuSettings* settings = _emu->GetSettings();
bool isAudioPlayer = _emu->GetAudioPlayerHud() ? true : false;
AudioPlayerHud* audioPlayer = _emu->GetAudioPlayerHud();
AudioConfig cfg = settings->GetAudioConfig();
uint32_t masterVolume = isAudioPlayer ? settings->GetAudioPlayerConfig().Volume : cfg.MasterVolume;
if(!isAudioPlayer && settings->CheckFlag(EmulationFlags::InBackground)) {
uint32_t masterVolume = audioPlayer ? audioPlayer->GetVolume() : cfg.MasterVolume;
if(!audioPlayer && settings->CheckFlag(EmulationFlags::InBackground)) {
if(cfg.MuteSoundInBackground) {
masterVolume = 0;
} else if(cfg.ReduceSoundInBackground) {
@ -95,8 +95,8 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount, uint32_
ProcessEqualizer(out, count, targetRate);
}
if(_emu->GetAudioPlayerHud()) {
_emu->GetAudioPlayerHud()->ProcessSamples(out, count, targetRate);
if(audioPlayer) {
audioPlayer->ProcessSamples(out, count, targetRate);
}
if(cfg.ReverbEnabled) {

View file

@ -96,8 +96,8 @@ void Emulator::Run()
return;
}
auto emulationLock = _emulationLock.AcquireSafe();
auto lock = _runLock.AcquireSafe();
auto emulationLock = _emulationLock.AcquireSafe();
_stopFlag = false;
_isRunAheadFrame = false;
@ -337,6 +337,8 @@ void Emulator::PowerCycle()
bool Emulator::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom, bool forPowerCycle)
{
auto lock = _loadLock.AcquireSafe();
if(!romFile.IsValid()) {
return false;
}
@ -418,7 +420,7 @@ bool Emulator::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom,
_cheatManager->ClearCheats(false);
auto lock = _debuggerLock.AcquireSafe();
auto debuggerLock = _debuggerLock.AcquireSafe();
if(_debugger) {
//Reset debugger if it was running before
_debugger->Release();
@ -682,16 +684,18 @@ void Emulator::WaitForLock()
_threadPaused = true;
//Spin wait until we are allowed to start again
while(_lockCounter > 0) {}
while(_lockCounter > 0 && !_stopFlag) {}
shared_ptr<Debugger> debugger = _debugger;
if(debugger) {
while(debugger->HasBreakRequest()) {}
while(debugger->HasBreakRequest() && !_stopFlag) {}
}
_threadPaused = false;
if(!_stopFlag) {
_threadPaused = false;
_runLock.Acquire();
_runLock.Acquire();
}
}
}
@ -833,7 +837,13 @@ ConsoleMemoryInfo Emulator::GetMemory(SnesMemoryType type)
AudioTrackInfo Emulator::GetAudioTrackInfo()
{
return _console->GetAudioTrackInfo();
AudioTrackInfo track = _console->GetAudioTrackInfo();
AudioConfig audioCfg = _settings->GetAudioConfig();
if(track.Length <= 0 && audioCfg.AudioPlayerEnableTrackLength) {
track.Length = audioCfg.AudioPlayerTrackLength;
track.FadeLength = 1;
}
return track;
}
void Emulator::ProcessAudioPlayerAction(AudioPlayerActionParams p)

View file

@ -73,6 +73,7 @@ private:
atomic<uint32_t> _lockCounter;
SimpleLock _runLock;
SimpleLock _emulationLock;
SimpleLock _loadLock;
SimpleLock _debuggerLock;
atomic<bool> _stopFlag;

View file

@ -139,6 +139,11 @@ struct AudioConfig
double Band18Gain = 0;
double Band19Gain = 0;
double Band20Gain = 0;
bool AudioPlayerEnableTrackLength = true;
uint32_t AudioPlayerTrackLength = 120;
bool AudioPlayerAutoDetectSilence = true;
uint32_t AudioPlayerSilenceDelay = 3;
};
//Update ControllerTypeNames when changing this
@ -407,10 +412,6 @@ struct NesConfig
bool FdsAutoInsertDisk = false;
VsDualOutputOption VsDualVideoOutput = VsDualOutputOption::Both;
VsDualOutputOption VsDualAudioOutput = VsDualOutputOption::Both;
bool NsfMoveToNextTrackAfterTime = true;
uint32_t NsfMoveToNextTrackTime = 120;
bool NsfAutoDetectSilence = true;
uint32_t NsfAutoDetectSilenceDelay = 3000;
bool SpritesEnabled = true;
bool BackgroundEnabled = true;

View file

@ -226,7 +226,7 @@ public:
_backColor = (~backColor & 0xFF000000) | (backColor & 0xFFFFFF);
}
static TextSize MeasureString(string text, int maxWidth = 0)
static TextSize MeasureString(string text, uint32_t maxWidth = 0)
{
uint32_t maxX = 0;
uint32_t x = 0;

View file

@ -53,49 +53,59 @@ namespace Mesen.GUI.Config
[Reactive] [MinMax(-20.0, 20.0)] public double Band19Gain { get; set; } = 0;
[Reactive] [MinMax(-20.0, 20.0)] public double Band20Gain { get; set; } = 0;
[Reactive] public bool AudioPlayerEnableTrackLength { get; set; } = true;
[Reactive] public UInt32 AudioPlayerTrackLength { get; set; } = 120;
[Reactive] public bool AudioPlayerAutoDetectSilence { get; set; } = true;
[Reactive] public UInt32 AudioPlayerSilenceDelay { get; set; } = 3;
public void ApplyConfig()
{
ConfigApi.SetAudioConfig(new InteropAudioConfig() {
AudioDevice = this.AudioDevice,
EnableAudio = this.EnableAudio,
DisableDynamicSampleRate = this.DisableDynamicSampleRate,
AudioDevice = AudioDevice,
EnableAudio = EnableAudio,
DisableDynamicSampleRate = DisableDynamicSampleRate,
MasterVolume = this.MasterVolume,
SampleRate = (UInt32)this.SampleRate,
AudioLatency = this.AudioLatency,
MasterVolume = MasterVolume,
SampleRate = (UInt32)SampleRate,
AudioLatency = AudioLatency,
MuteSoundInBackground = this.MuteSoundInBackground,
ReduceSoundInBackground = this.ReduceSoundInBackground,
ReduceSoundInFastForward = this.ReduceSoundInFastForward,
VolumeReduction = this.VolumeReduction,
MuteSoundInBackground = MuteSoundInBackground,
ReduceSoundInBackground = ReduceSoundInBackground,
ReduceSoundInFastForward = ReduceSoundInFastForward,
VolumeReduction = VolumeReduction,
ReverbEnabled = this.ReverbEnabled,
ReverbStrength = this.ReverbStrength,
ReverbDelay = this.ReverbDelay,
CrossFeedEnabled = this.CrossFeedEnabled,
CrossFeedRatio = this.CrossFeedRatio,
ReverbEnabled = ReverbEnabled,
ReverbStrength = ReverbStrength,
ReverbDelay = ReverbDelay,
CrossFeedEnabled = CrossFeedEnabled,
CrossFeedRatio = CrossFeedRatio,
EnableEqualizer = this.EnableEqualizer,
Band1Gain = this.Band1Gain,
Band2Gain = this.Band2Gain,
Band3Gain = this.Band3Gain,
Band4Gain = this.Band4Gain,
Band5Gain = this.Band5Gain,
Band6Gain = this.Band6Gain,
Band7Gain = this.Band7Gain,
Band8Gain = this.Band8Gain,
Band9Gain = this.Band9Gain,
Band10Gain = this.Band10Gain,
Band11Gain = this.Band11Gain,
Band12Gain = this.Band12Gain,
Band13Gain = this.Band13Gain,
Band14Gain = this.Band14Gain,
Band15Gain = this.Band15Gain,
Band16Gain = this.Band16Gain,
Band17Gain = this.Band17Gain,
Band18Gain = this.Band18Gain,
Band19Gain = this.Band19Gain,
Band20Gain = this.Band20Gain
EnableEqualizer = EnableEqualizer,
Band1Gain = Band1Gain,
Band2Gain = Band2Gain,
Band3Gain = Band3Gain,
Band4Gain = Band4Gain,
Band5Gain = Band5Gain,
Band6Gain = Band6Gain,
Band7Gain = Band7Gain,
Band8Gain = Band8Gain,
Band9Gain = Band9Gain,
Band10Gain = Band10Gain,
Band11Gain = Band11Gain,
Band12Gain = Band12Gain,
Band13Gain = Band13Gain,
Band14Gain = Band14Gain,
Band15Gain = Band15Gain,
Band16Gain = Band16Gain,
Band17Gain = Band17Gain,
Band18Gain = Band18Gain,
Band19Gain = Band19Gain,
Band20Gain = Band20Gain,
AudioPlayerEnableTrackLength = AudioPlayerEnableTrackLength,
AudioPlayerTrackLength = AudioPlayerTrackLength,
AudioPlayerAutoDetectSilence = AudioPlayerAutoDetectSilence,
AudioPlayerSilenceDelay = AudioPlayerSilenceDelay
});
}
}
@ -144,6 +154,11 @@ namespace Mesen.GUI.Config
public double Band18Gain;
public double Band19Gain;
public double Band20Gain;
[MarshalAs(UnmanagedType.I1)] public bool AudioPlayerEnableTrackLength;
public UInt32 AudioPlayerTrackLength;
[MarshalAs(UnmanagedType.I1)] public bool AudioPlayerAutoDetectSilence;
public UInt32 AudioPlayerSilenceDelay;
}
public enum AudioSampleRate

View file

@ -23,10 +23,6 @@ namespace Mesen.GUI.Config
[Reactive] public bool FdsAutoInsertDisk { get; set; } = false;
[Reactive] public VsDualOutputOption VsDualVideoOutput { get; set; } = VsDualOutputOption.Both;
[Reactive] public VsDualOutputOption VsDualAudioOutput { get; set; } = VsDualOutputOption.Both;
[Reactive] public bool NsfMoveToNextTrackAfterTime { get; set; } = true;
[Reactive] public UInt32 NsfMoveToNextTrackTime { get; set; } = 120;
[Reactive] public bool NsfAutoDetectSilence { get; set; } = true;
[Reactive] public UInt32 NsfAutoDetectSilenceDelay { get; set; } = 3000;
//Video
[Reactive] public bool DisableSprites { get; set; } = false;
@ -130,10 +126,6 @@ namespace Mesen.GUI.Config
FdsAutoInsertDisk = FdsAutoInsertDisk,
VsDualVideoOutput = VsDualVideoOutput,
VsDualAudioOutput = VsDualAudioOutput,
NsfMoveToNextTrackAfterTime = NsfMoveToNextTrackAfterTime,
NsfMoveToNextTrackTime = NsfMoveToNextTrackTime,
NsfAutoDetectSilence = NsfAutoDetectSilence,
NsfAutoDetectSilenceDelay = NsfAutoDetectSilenceDelay,
SpritesEnabled = !DisableSprites,
BackgroundEnabled = !DisableBackground,
@ -260,10 +252,6 @@ namespace Mesen.GUI.Config
[MarshalAs(UnmanagedType.I1)] public bool FdsAutoInsertDisk;
public VsDualOutputOption VsDualVideoOutput;
public VsDualOutputOption VsDualAudioOutput;
[MarshalAs(UnmanagedType.I1)] public bool NsfMoveToNextTrackAfterTime;
public UInt32 NsfMoveToNextTrackTime;
[MarshalAs(UnmanagedType.I1)] public bool NsfAutoDetectSilence;
public UInt32 NsfAutoDetectSilenceDelay;
[MarshalAs(UnmanagedType.I1)] public bool SpritesEnabled;
[MarshalAs(UnmanagedType.I1)] public bool BackgroundEnabled;

View file

@ -101,16 +101,6 @@
<!-- Archive Load Message -->
<Control ID="lblExtractingFile">Extracting file, please wait...</Control>
<!-- NSF Player -->
<Control ID="lblTitle">Title:</Control>
<Control ID="lblArtist">Artist:</Control>
<Control ID="lblCopyright">Copyright:</Control>
<Control ID="lblSoundChips">Sound Chips:</Control>
<Control ID="lblRecording">REC</Control>
<Control ID="lblSlowMotion">Slow Motion</Control>
<Control ID="lblRewinding">Rewinding</Control>
<Control ID="lblFastForward">Fast Forward</Control>
</Form>
<Form ID="frmLogWindow" Title="Log Window">
<Control ID="btnClose">Close</Control>
@ -133,6 +123,13 @@
<Control ID="grpVolume">Volume</Control>
<Control ID="btnReset">Reset to Defaults</Control>
<Control ID="lblAudioPlayerSettings">Audio Player Settings</Control>
<Control ID="lblAudioPlayerHint">When a track has an unknown duration:</Control>
<Control ID="chkAudioPlayerAutoDetectSilence">Move to the next track after</Control>
<Control ID="chkAudioPlayerEnableTrackLength">Limit track duration to</Control>
<Control ID="lblMillisecondsOfSilence">seconds of silence</Control>
<Control ID="lblSeconds">seconds</Control>
<Control ID="tpgAdvanced">Advanced</Control>
<Control ID="chkDisableDynamicSampleRate">Disable dynamic sample rate</Control>
<Control ID="chkReverbEnabled">Enable reverb</Control>
@ -328,12 +325,6 @@
<Control ID="chkFdsFastForwardOnLoad">Automatically fast forward FDS games when disk or BIOS is loading</Control>
<Control ID="chkFdsAutoInsertDisk">Automatically switch disks for FDS games</Control>
<Control ID="lblNsfSettings">NSF Settings</Control>
<Control ID="chkNsfAutoDetectSilence">Move to next track after</Control>
<Control ID="lblNsfMillisecondsOfSilence">milliseconds of silence</Control>
<Control ID="chkNsfMoveToNextTrackAfterTime">Limit track run time to</Control>
<Control ID="lblNsfSeconds">seconds</Control>
<Control ID="lblVsDualSystem">VS. DualSystem Settings</Control>
<Control ID="lblVsDualPlayAudio">Play audio for:</Control>
<Control ID="lblVsDualShowVideo">Show video for:</Control>

View file

@ -74,8 +74,7 @@
Value="{CompiledBinding Config.MasterVolume}"
HorizontalAlignment="Left"
/>
</Grid>
</Grid>
<c:OptionSection Header="{l:Translate lblVolumeReductionSettings}">
<CheckBox Content="{l:Translate chkMuteSoundInBackground}" IsChecked="{CompiledBinding Config.MuteSoundInBackground}" />
@ -93,6 +92,22 @@
/>
</StackPanel>
</c:OptionSection>
<c:OptionSection Header="{l:Translate lblAudioPlayerSettings}">
<TextBlock Text="{l:Translate lblAudioPlayerHint}" />
<StackPanel Margin="10 5 0 0">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{CompiledBinding Config.AudioPlayerAutoDetectSilence}" Content="{l:Translate chkAudioPlayerAutoDetectSilence}" />
<NumericUpDown Margin="5 1" Maximum="999999" Width="85" Value="{CompiledBinding Config.AudioPlayerSilenceDelay}" />
<TextBlock Text="{l:Translate lblMillisecondsOfSilence}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{CompiledBinding Config.AudioPlayerEnableTrackLength}" Content="{l:Translate chkAudioPlayerEnableTrackLength}" />
<NumericUpDown Margin="5 1" Maximum="9999" Width="80" Value="{CompiledBinding Config.AudioPlayerTrackLength}" />
<TextBlock Text="{l:Translate lblSeconds}" />
</StackPanel>
</StackPanel>
</c:OptionSection>
</StackPanel>
</TabItem>

View file

@ -74,19 +74,6 @@
<CheckBox IsChecked="{CompiledBinding Config.FdsAutoInsertDisk}" Content="{l:Translate chkFdsAutoInsertDisk}" />
</c:OptionSection>
<c:OptionSection Header="{l:Translate lblNsfSettings}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{CompiledBinding Config.NsfAutoDetectSilence}" Content="{l:Translate chkNsfAutoDetectSilence}" />
<NumericUpDown Margin="5 1" Maximum="999999" Width="85" Value="{CompiledBinding Config.NsfAutoDetectSilenceDelay}" />
<TextBlock Text="{l:Translate lblNsfMillisecondsOfSilence}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{CompiledBinding Config.NsfMoveToNextTrackAfterTime}" Content="{l:Translate chkNsfMoveToNextTrackAfterTime}" />
<NumericUpDown Margin="5 1" Maximum="9999" Width="80" Value="{CompiledBinding Config.NsfMoveToNextTrackTime}" />
<TextBlock Text="{l:Translate lblNsfSeconds}" />
</StackPanel>
</c:OptionSection>
<c:OptionSection Header="{l:Translate lblVsDualSystem}">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto">
<TextBlock Text="{l:Translate lblVsDualPlayAudio}" />