mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Implement track lengths for SPC + apply track length & silence detection to all audio players, not just NSF
This commit is contained in:
parent
4d092bc07b
commit
b964eb2e09
20 changed files with 244 additions and 265 deletions
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -9,7 +9,8 @@ struct AudioTrackInfo
|
|||
string Comment;
|
||||
|
||||
double Position;
|
||||
int32_t Length;
|
||||
double Length;
|
||||
double FadeLength;
|
||||
|
||||
uint32_t TrackNumber;
|
||||
uint32_t TrackCount;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -73,6 +73,7 @@ private:
|
|||
atomic<uint32_t> _lockCounter;
|
||||
SimpleLock _runLock;
|
||||
SimpleLock _emulationLock;
|
||||
SimpleLock _loadLock;
|
||||
|
||||
SimpleLock _debuggerLock;
|
||||
atomic<bool> _stopFlag;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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}" />
|
||||
|
|
Loading…
Add table
Reference in a new issue