SPU2: Namespace logging/debugging

This commit is contained in:
Stenzek 2022-12-30 15:35:16 +10:00 committed by refractionpcsx2
parent 339c483a4b
commit fe1bebc12d
35 changed files with 1168 additions and 1794 deletions

View File

@ -277,9 +277,6 @@ set(pcsx2CDVDHeaders
# SPU2 sources
set(pcsx2SPU2Sources
SPU2/ADSR.cpp
SPU2/Config.cpp
SPU2/ConfigDebug.cpp
SPU2/ConfigSoundTouch.cpp
SPU2/Debug.cpp
SPU2/DplIIdecoder.cpp
SPU2/Dma.cpp
@ -292,13 +289,11 @@ set(pcsx2SPU2Sources
SPU2/SndOut.cpp
SPU2/spu2freeze.cpp
SPU2/spu2sys.cpp
SPU2/Timestretcher.cpp
SPU2/Wavedump_wav.cpp
)
# SPU2 headers
set(pcsx2SPU2Headers
SPU2/Config.h
SPU2/Debug.h
SPU2/defs.h
SPU2/Dma.h

View File

@ -794,27 +794,49 @@ struct Pcsx2Config
NoSync,
};
static constexpr s32 MAX_VOLUME = 200;
static constexpr s32 MIN_LATENCY = 3;
static constexpr s32 MIN_LATENCY_TIMESTRETCH = 15;
static constexpr s32 MAX_LATENCY = 750;
static constexpr s32 MIN_SEQUENCE_LEN = 20;
static constexpr s32 MAX_SEQUENCE_LEN = 100;
static constexpr s32 MIN_SEEKWINDOW = 10;
static constexpr s32 MAX_SEEKWINDOW = 30;
static constexpr s32 MIN_OVERLAP = 5;
static constexpr s32 MAX_OVERLAP = 15;
BITFIELD32()
bool
AdvancedVolumeControl : 1;
DebugEnabled : 1,
MsgToConsole : 1,
MsgKeyOnOff : 1,
MsgVoiceOff : 1,
MsgDMA : 1,
MsgAutoDMA : 1,
MsgOverruns : 1,
MsgCache : 1,
AccessLog : 1,
DMALog : 1,
WaveLog : 1,
CoresDump : 1,
MemDump : 1,
RegDump : 1,
VisualDebugEnabled : 1;
BITFIELD_END
InterpolationMode Interpolation = InterpolationMode::Gaussian;
SynchronizationMode SynchMode = SynchronizationMode::TimeStretch;
s32 FinalVolume = 100;
s32 Latency{100};
s32 SpeakerConfiguration{0};
s32 DplDecodingLevel{0};
s32 Latency = 100;
s32 SpeakerConfiguration = 0;
s32 DplDecodingLevel = 0;
float VolumeAdjustC{ 0.0f };
float VolumeAdjustFL{ 0.0f };
float VolumeAdjustFR{ 0.0f };
float VolumeAdjustBL{ 0.0f };
float VolumeAdjustBR{ 0.0f };
float VolumeAdjustSL{ 0.0f };
float VolumeAdjustSR{ 0.0f };
float VolumeAdjustLFE{ 0.0f };
s32 SequenceLenMS = 30;
s32 SeekWindowMS = 20;
s32 OverlapMS = 10;
std::string OutputModule;
std::string BackendName;
@ -835,14 +857,9 @@ struct Pcsx2Config
OpEqu(SpeakerConfiguration) &&
OpEqu(DplDecodingLevel) &&
OpEqu(VolumeAdjustC) &&
OpEqu(VolumeAdjustFL) &&
OpEqu(VolumeAdjustFR) &&
OpEqu(VolumeAdjustBL) &&
OpEqu(VolumeAdjustBR) &&
OpEqu(VolumeAdjustSL) &&
OpEqu(VolumeAdjustSR) &&
OpEqu(VolumeAdjustLFE) &&
OpEqu(SequenceLenMS) &&
OpEqu(SeekWindowMS) &&
OpEqu(OverlapMS) &&
OpEqu(OutputModule) &&
OpEqu(BackendName);
@ -1295,6 +1312,9 @@ namespace EmuFolders
void SetDefaults(SettingsInterface& si);
void LoadConfig(SettingsInterface& si);
bool EnsureFoldersExist();
/// Opens the specified log file for writing.
std::FILE* OpenLogFile(const std::string_view& name, const char* mode);
} // namespace EmuFolders
/////////////////////////////////////////////////////////////////////////////////////////

View File

@ -349,7 +349,7 @@ static double AdjustToHostRefreshRate(double vertical_frequency, double frame_li
{
if (!EmuConfig.GS.SyncToHostRefreshRate || EmuConfig.GS.LimitScalar != 1.0f)
{
SPU2SetDeviceSampleRateMultiplier(1.0);
SPU2::SetDeviceSampleRateMultiplier(1.0);
s_use_vsync_for_timing = false;
return frame_limit;
}
@ -358,7 +358,7 @@ static double AdjustToHostRefreshRate(double vertical_frequency, double frame_li
if (!g_host_display->GetHostRefreshRate(&host_refresh_rate))
{
Console.Warning("Cannot sync to host refresh since the query failed.");
SPU2SetDeviceSampleRateMultiplier(1.0);
SPU2::SetDeviceSampleRateMultiplier(1.0);
s_use_vsync_for_timing = false;
return frame_limit;
}
@ -374,7 +374,7 @@ static double AdjustToHostRefreshRate(double vertical_frequency, double frame_li
return frame_limit;
frame_limit *= ratio;
SPU2SetDeviceSampleRateMultiplier(ratio);
SPU2::SetDeviceSampleRateMultiplier(ratio);
return frame_limit;
}

View File

@ -45,7 +45,6 @@ static void HotkeyAdjustTargetSpeed(double delta)
const double min_speed = Achievements::ChallengeModeActive() ? 1.0 : 0.1;
EmuConfig.Framerate.NominalScalar = std::max(min_speed, EmuConfig.GS.LimitScalar + delta);
VMManager::SetLimiterMode(LimiterModeType::Nominal);
gsUpdateFrequency(EmuConfig);
Host::AddIconOSDMessage("SpeedChanged", ICON_FA_CLOCK,
fmt::format("Target speed set to {:.0f}%.", std::round(EmuConfig.Framerate.NominalScalar * 100.0)), Host::OSD_QUICK_DURATION);
}

View File

@ -68,7 +68,7 @@ void hwReset()
// Sets SPU2 sample rate to PS2 standard (48KHz) whenever emulator is reset.
// For PSX mode sample rate setting, see HwWrite.cpp
SPU2reset(PS2Modes::PS2);
SPU2::Reset(false);
sifReset();

View File

@ -186,7 +186,7 @@ void _hwWrite32( u32 mem, u32 value )
//pgifInit();
psxReset();
PSXCLK = 33868800;
SPU2reset(PS2Modes::PSX);
SPU2::Reset(true);
setPs1CDVDSpeed(cdvd.Speed);
psxHu32(0x1f801450) = 0x8;
psxHu32(0x1f801078) = 1;

View File

@ -776,20 +776,49 @@ Pcsx2Config::SPU2Options::SPU2Options()
void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
{
{
SettingsWrapSection("SPU2/Debug");
SettingsWrapBitBoolEx(DebugEnabled, "Global_Enable");
SettingsWrapBitBoolEx(MsgToConsole, "Show_Messages");
SettingsWrapBitBoolEx(MsgKeyOnOff, "Show_Messages_Key_On_Off");
SettingsWrapBitBoolEx(MsgVoiceOff, "Show_Messages_Voice_Off");
SettingsWrapBitBoolEx(MsgDMA, "Show_Messages_DMA_Transfer");
SettingsWrapBitBoolEx(MsgAutoDMA, "Show_Messages_AutoDMA");
SettingsWrapBitBoolEx(MsgOverruns, "Show_Messages_Overruns");
SettingsWrapBitBoolEx(MsgCache, "Show_Messages_CacheStats");
SettingsWrapBitBoolEx(AccessLog, "Log_Register_Access");
SettingsWrapBitBoolEx(DMALog, "Log_DMA_Transfers");
SettingsWrapBitBoolEx(WaveLog, "Log_WAVE_Output");
SettingsWrapBitBoolEx(CoresDump, "Dump_Info");
SettingsWrapBitBoolEx(MemDump, "Dump_Memory");
SettingsWrapBitBoolEx(RegDump, "Dump_Regs");
// If the global switch is off, save runtime checks.
if (wrap.IsLoading() && !DebugEnabled)
{
MsgToConsole = false;
MsgKeyOnOff = false;
MsgVoiceOff = false;
MsgDMA = false;
MsgAutoDMA = false;
MsgOverruns = false;
MsgCache = false;
AccessLog = false;
DMALog = false;
WaveLog = false;
CoresDump = false;
MemDump = false;
RegDump = false;
}
}
{
SettingsWrapSection("SPU2/Mixing");
Interpolation = static_cast<InterpolationMode>(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, "Interpolation", static_cast<int>(Interpolation), static_cast<int>(Interpolation)));
SettingsWrapEntry(FinalVolume);
SettingsWrapEntry(VolumeAdjustC);
SettingsWrapEntry(VolumeAdjustFL);
SettingsWrapEntry(VolumeAdjustFR);
SettingsWrapEntry(VolumeAdjustBL);
SettingsWrapEntry(VolumeAdjustBR);
SettingsWrapEntry(VolumeAdjustSL);
SettingsWrapEntry(VolumeAdjustSR);
SettingsWrapEntry(VolumeAdjustLFE);
}
{
@ -802,6 +831,8 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap)
SettingsWrapEntry(SpeakerConfiguration);
SettingsWrapEntry(DplDecodingLevel);
}
// clampy clamp
}
const char* Pcsx2Config::DEV9Options::NetApiNames[] = {
@ -1445,3 +1476,12 @@ bool EmuFolders::EnsureFoldersExist()
result = FileSystem::CreateDirectoryPath(InputProfiles.c_str(), false) && result;
return result;
}
std::FILE* EmuFolders::OpenLogFile(const std::string_view& name, const char* mode)
{
if (name.empty())
return nullptr;
const std::string path(Path::Combine(Logs, name));
return FileSystem::OpenCFile(path.c_str(), mode);
}

View File

@ -1,120 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "common/StringUtil.h"
#include <cmath>
#include "SPU2/Global.h"
#include "SPU2/Config.h"
#include "HostSettings.h"
int AutoDMAPlayRate[2] = {0, 0};
// Default settings.
// MIXING
int Interpolation = 5;
/* values:
0: No interpolation (uses nearest)
1. Linear interpolation
2. Cubic interpolation
3. Hermite interpolation
4. Catmull-Rom interpolation
5. Gaussian interpolation
*/
float FinalVolume; // global
bool AdvancedVolumeControl;
float VolumeAdjustFLdb; // Decibels settings, because audiophiles love that.
float VolumeAdjustCdb;
float VolumeAdjustFRdb;
float VolumeAdjustBLdb;
float VolumeAdjustBRdb;
float VolumeAdjustSLdb;
float VolumeAdjustSRdb;
float VolumeAdjustLFEdb;
float VolumeAdjustFL; // Linear coefficients calculated from decibels,
float VolumeAdjustC;
float VolumeAdjustFR;
float VolumeAdjustBL;
float VolumeAdjustBR;
float VolumeAdjustSL;
float VolumeAdjustSR;
float VolumeAdjustLFE;
bool _visual_debug_enabled = false; // Windows-only feature
// OUTPUT
u32 OutputModule = 0;
int SndOutLatencyMS = 100;
int SynchMode = 0; // Time Stretch, Async or Disabled.
int numSpeakers = 0;
int dplLevel = 0;
bool temp_debug_state;
/*****************************************************************************/
void ReadSettings()
{
Interpolation = Host::GetIntSettingValue("SPU2/Mixing", "Interpolation", 5);
FinalVolume = ((float)Host::GetIntSettingValue("SPU2/Mixing", "FinalVolume", 100)) / 100;
if (FinalVolume > 2.0f)
FinalVolume = 2.0f;
AdvancedVolumeControl = Host::GetBoolSettingValue("SPU2/Mixing", "AdvancedVolumeControl", false);
VolumeAdjustCdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustC", 0);
VolumeAdjustFLdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustFL", 0);
VolumeAdjustFRdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustFR", 0);
VolumeAdjustBLdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustBL", 0);
VolumeAdjustBRdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustBR", 0);
VolumeAdjustSLdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustSL", 0);
VolumeAdjustSRdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustSR", 0);
VolumeAdjustLFEdb = Host::GetFloatSettingValue("SPU2/Mixing", "VolumeAdjustLFE", 0);
VolumeAdjustC = powf(10, VolumeAdjustCdb / 10);
VolumeAdjustFL = powf(10, VolumeAdjustFLdb / 10);
VolumeAdjustFR = powf(10, VolumeAdjustFRdb / 10);
VolumeAdjustBL = powf(10, VolumeAdjustBLdb / 10);
VolumeAdjustBR = powf(10, VolumeAdjustBRdb / 10);
VolumeAdjustSL = powf(10, VolumeAdjustSLdb / 10);
VolumeAdjustSR = powf(10, VolumeAdjustSRdb / 10);
VolumeAdjustLFE = powf(10, VolumeAdjustLFEdb / 10);
const std::string modname(Host::GetStringSettingValue("SPU2/Output", "OutputModule", "cubeb"));
OutputModule = FindOutputModuleById(modname.c_str()); // Find the driver index of this module...
SndOutLatencyMS = Host::GetIntSettingValue("SPU2/Output", "Latency", 100);
SynchMode = Host::GetIntSettingValue("SPU2/Output", "SynchMode", 0);
numSpeakers = Host::GetIntSettingValue("SPU2/Output", "SpeakerConfiguration", 0);
dplLevel = Host::GetIntSettingValue("SPU2/Output", "DplDecodingLevel", 0);
SoundtouchCfg::ReadSettings();
DebugConfig::ReadSettings();
// Sanity Checks
// -------------
Clampify(SndOutLatencyMS, LATENCY_MIN, LATENCY_MAX);
if (mods[OutputModule] == nullptr)
{
Console.Warning("* SPU2: Unknown output module '%s' specified in configuration file.", modname.c_str());
Console.Warning("* SPU2: Defaulting to Cubeb (%s).", CubebOut->GetIdent());
OutputModule = FindOutputModuleById(CubebOut->GetIdent());
}
}

View File

@ -1,125 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Global.h"
#include <string>
namespace soundtouch
{
class SoundTouch;
}
extern bool DebugEnabled;
extern bool _MsgToConsole;
extern bool _MsgKeyOnOff;
extern bool _MsgVoiceOff;
extern bool _MsgDMA;
extern bool _MsgAutoDMA;
extern bool _MsgOverruns;
extern bool _MsgCache;
extern bool _AccessLog;
extern bool _DMALog;
extern bool _WaveLog;
extern bool _CoresDump;
extern bool _MemDump;
extern bool _RegDump;
extern bool _visual_debug_enabled;
static __forceinline bool MsgToConsole() { return _MsgToConsole & DebugEnabled; }
static __forceinline bool MsgKeyOnOff() { return _MsgKeyOnOff & MsgToConsole(); }
static __forceinline bool MsgVoiceOff() { return _MsgVoiceOff & MsgToConsole(); }
static __forceinline bool MsgDMA() { return _MsgDMA & MsgToConsole(); }
static __forceinline bool MsgAutoDMA() { return _MsgAutoDMA & MsgToConsole(); }
static __forceinline bool MsgOverruns() { return _MsgOverruns & MsgToConsole(); }
static __forceinline bool MsgCache() { return _MsgCache & MsgToConsole(); }
static __forceinline bool AccessLog() { return _AccessLog & DebugEnabled; }
static __forceinline bool DMALog() { return _DMALog & DebugEnabled; }
static __forceinline bool WaveLog() { return _WaveLog & DebugEnabled; }
static __forceinline bool CoresDump() { return _CoresDump & DebugEnabled; }
static __forceinline bool MemDump() { return _MemDump & DebugEnabled; }
static __forceinline bool RegDump() { return _RegDump & DebugEnabled; }
static __forceinline bool VisualDebug() { return _visual_debug_enabled & DebugEnabled; }
extern std::string AccessLogFileName;
extern std::string DMA4LogFileName;
extern std::string DMA7LogFileName;
extern std::string CoresDumpFileName;
extern std::string MemDumpFileName;
extern std::string RegDumpFileName;
extern int Interpolation;
extern int numSpeakers;
extern float FinalVolume; // Global / pre-scale
extern bool AdvancedVolumeControl;
extern float VolumeAdjustFLdb;
extern float VolumeAdjustCdb;
extern float VolumeAdjustFRdb;
extern float VolumeAdjustBLdb;
extern float VolumeAdjustBRdb;
extern float VolumeAdjustSLdb;
extern float VolumeAdjustSRdb;
extern float VolumeAdjustLFEdb;
extern int dplLevel;
extern int AutoDMAPlayRate[2];
extern u32 OutputModule;
extern int SndOutLatencyMS;
extern int SynchMode;
#ifdef PCSX2_DEVBUILD
const int LATENCY_MAX = 3000;
#else
const int LATENCY_MAX = 750;
#endif
const int LATENCY_MIN = 3;
const int LATENCY_MIN_TIMESTRETCH = 15;
namespace SoundtouchCfg
{
extern const int SequenceLen_Min;
extern const int SequenceLen_Max;
extern const int SeekWindow_Min;
extern const int SeekWindow_Max;
extern const int Overlap_Min;
extern const int Overlap_Max;
extern int SequenceLenMS;
extern int SeekWindowMS;
extern int OverlapMS;
extern void ReadSettings();
extern void ApplySettings(soundtouch::SoundTouch& sndtouch);
}; // namespace SoundtouchCfg
namespace DebugConfig
{
extern void ReadSettings();
} // namespace DebugConfig
extern void ReadSettings();

View File

@ -1,100 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "pcsx2/Config.h"
#include "SPU2/Global.h"
#include "SPU2/Config.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "HostSettings.h"
bool DebugEnabled = false;
bool _MsgToConsole = false;
bool _MsgKeyOnOff = false;
bool _MsgVoiceOff = false;
bool _MsgDMA = false;
bool _MsgAutoDMA = false;
bool _MsgOverruns = false;
bool _MsgCache = false;
bool _AccessLog = false;
bool _DMALog = false;
bool _WaveLog = false;
bool _CoresDump = false;
bool _MemDump = false;
bool _RegDump = false;
std::string AccessLogFileName;
std::string WaveLogFileName;
std::string DMA4LogFileName;
std::string DMA7LogFileName;
std::string CoresDumpFileName;
std::string MemDumpFileName;
std::string RegDumpFileName;
FILE* OpenBinaryLog(const char* logfile)
{
return FileSystem::OpenCFile(Path::Combine(EmuFolders::Logs, logfile).c_str(), "wb");
}
FILE* OpenLog(const char* logfile)
{
return FileSystem::OpenCFile(Path::Combine(EmuFolders::Logs, logfile).c_str(), "w");
}
FILE* OpenDump(const char* logfile)
{
return FileSystem::OpenCFile(Path::Combine(EmuFolders::Logs, logfile).c_str(), "w");
}
namespace DebugConfig
{
static const char* Section = "SPU/Debug";
void ReadSettings()
{
DebugEnabled = Host::GetBoolSettingValue(Section, "Global_Enable", 0);
_MsgToConsole = Host::GetBoolSettingValue(Section, "Show_Messages", 0);
_MsgKeyOnOff = Host::GetBoolSettingValue(Section, "Show_Messages_Key_On_Off", 0);
_MsgVoiceOff = Host::GetBoolSettingValue(Section, "Show_Messages_Voice_Off", 0);
_MsgDMA = Host::GetBoolSettingValue(Section, "Show_Messages_DMA_Transfer", 0);
_MsgAutoDMA = Host::GetBoolSettingValue(Section, "Show_Messages_AutoDMA", 0);
_MsgOverruns = Host::GetBoolSettingValue(Section, "Show_Messages_Overruns", 0);
_MsgCache = Host::GetBoolSettingValue(Section, "Show_Messages_CacheStats", 0);
_AccessLog = Host::GetBoolSettingValue(Section, "Log_Register_Access", 0);
_DMALog = Host::GetBoolSettingValue(Section, "Log_DMA_Transfers", 0);
_WaveLog = Host::GetBoolSettingValue(Section, "Log_WAVE_Output", 0);
_CoresDump = Host::GetBoolSettingValue(Section, "Dump_Info", 0);
_MemDump = Host::GetBoolSettingValue(Section, "Dump_Memory", 0);
_RegDump = Host::GetBoolSettingValue(Section, "Dump_Regs", 0);
AccessLogFileName = Host::GetStringSettingValue(Section, "Access_Log_Filename", "SPU2Log.txt");
WaveLogFileName = Host::GetStringSettingValue(Section, "WaveLog_Filename", "SPU2log.wav");
DMA4LogFileName = Host::GetStringSettingValue(Section, "DMA4Log_Filename", "SPU2dma4.dat");
DMA7LogFileName = Host::GetStringSettingValue(Section, "DMA7Log_Filename", "SPU2dma7.dat");
CoresDumpFileName = Host::GetStringSettingValue(Section, "Info_Dump_Filename", "SPU2Cores.txt");
MemDumpFileName = Host::GetStringSettingValue(Section, "Mem_Dump_Filename", "SPU2mem.dat");
RegDumpFileName = Host::GetStringSettingValue(Section, "Reg_Dump_Filename", "SPU2regs.dat");
}
} // namespace DebugConfig

View File

@ -1,62 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "SPU2/Global.h"
#include "SPU2/Config.h"
#include "SoundTouch.h"
#include "HostSettings.h"
namespace SoundtouchCfg
{
// Timestretch Slider Bounds, Min/Max
const int SequenceLen_Min = 20;
const int SequenceLen_Max = 100;
const int SeekWindow_Min = 10;
const int SeekWindow_Max = 30;
const int Overlap_Min = 5;
const int Overlap_Max = 15;
int SequenceLenMS = 30;
int SeekWindowMS = 20;
int OverlapMS = 10;
static void ClampValues()
{
Clampify(SequenceLenMS, SequenceLen_Min, SequenceLen_Max);
Clampify(SeekWindowMS, SeekWindow_Min, SeekWindow_Max);
Clampify(OverlapMS, Overlap_Min, Overlap_Max);
}
void ApplySettings(soundtouch::SoundTouch& sndtouch)
{
sndtouch.setSetting(SETTING_SEQUENCE_MS, SequenceLenMS);
sndtouch.setSetting(SETTING_SEEKWINDOW_MS, SeekWindowMS);
sndtouch.setSetting(SETTING_OVERLAP_MS, OverlapMS);
}
void ReadSettings()
{
SequenceLenMS = Host::GetIntSettingValue("Soundtouch", "SequenceLengthMS", 30);
SeekWindowMS = Host::GetIntSettingValue("Soundtouch", "SeekWindowMS", 20);
OverlapMS = Host::GetIntSettingValue("Soundtouch", "OverlapMS", 10);
ClampValues();
}
} // namespace SoundtouchCfg

View File

@ -14,65 +14,68 @@
*/
#include "PrecompiledHeader.h"
#include "Global.h"
#include "SPU2/Global.h"
#include "Config.h"
#include "common/FileSystem.h"
#include <cstdarg>
int crazy_debug = 0;
#ifdef PCSX2_DEVBUILD
char s[4096];
static FILE* spu2Log = nullptr;
FILE* spu2Log = nullptr;
void FileLog(const char* fmt, ...)
void SPU2::OpenFileLog()
{
#ifdef SPU2_LOG
va_list list;
if (!AccessLog())
if (spu2Log)
return;
spu2Log = EmuFolders::OpenLogFile("SPU2Log.txt", "w");
setvbuf(spu2Log, nullptr, _IONBF, 0);
}
void SPU2::CloseFileLog()
{
if (!spu2Log)
return;
va_start(list, fmt);
vsprintf(s, fmt, list);
va_end(list);
std::fclose(spu2Log);
spu2Log = nullptr;
}
fputs(s, spu2Log);
fflush(spu2Log);
void SPU2::FileLog(const char* fmt, ...)
{
if (!spu2Log)
return;
#if 0
if(crazy_debug)
{
fputs(s,stderr);
fflush(stderr);
}
#endif
#endif
std::va_list ap;
va_start(ap, fmt);
std::vfprintf(spu2Log, fmt, ap);
std::fflush(spu2Log);
va_end(ap);
}
//Note to developer on the usage of ConLog:
// while ConLog doesn't print anything if messages to console are disabled at the GUI,
// it's still better to outright not call it on tight loop scenarios, by testing MsgToConsole() (which is inline and very quick).
// Else, there's some (small) overhead in calling and returning from ConLog.
void ConLog(const char* fmt, ...)
void SPU2::ConLog(const char* fmt, ...)
{
if (!MsgToConsole())
if (!SPU2::MsgToConsole())
return;
va_list list;
va_start(list, fmt);
vsprintf(s, fmt, list);
va_end(list);
fputs(s, stderr);
fflush(stderr);
std::va_list ap;
va_start(ap, fmt);
Console.FormatV(fmt, ap);
va_end(ap);
if (spu2Log)
{
fputs(s, spu2Log);
fflush(spu2Log);
va_start(ap, fmt);
std::vfprintf(spu2Log, fmt, ap);
std::fflush(spu2Log);
va_end(ap);
}
}
@ -97,24 +100,22 @@ void V_VolumeLR::DebugDump(FILE* dump, const char* title)
fprintf(dump, "Volume for %s (%s Channel):\t%x\n", title, "Right", Right);
}
void DoFullDump()
void SPU2::DoFullDump()
{
#ifdef _MSC_VER
#ifdef SPU2_LOG
FILE* dump;
if (MemDump())
if (SPU2::MemDump())
{
dump = FileSystem::OpenCFile(MemDumpFileName.c_str(), "wb");
dump = EmuFolders::OpenLogFile("SPU2mem.dat", "wb");
if (dump)
{
fwrite(_spu2mem, 0x200000, 1, dump);
fclose(dump);
}
}
if (RegDump())
if (SPU2::RegDump())
{
dump = FileSystem::OpenCFile(RegDumpFileName.c_str(), "wb");
dump = EmuFolders::OpenLogFile("SPU2regs.dat", "wb");
if (dump)
{
fwrite(spu2regs, 0x2000, 1, dump);
@ -122,9 +123,9 @@ void DoFullDump()
}
}
if (!CoresDump())
if (!SPU2::CoresDump())
return;
dump = FileSystem::OpenCFile(CoresDumpFileName.c_str(), "wt");
dump = EmuFolders::OpenLogFile("SPU2Cores.txt", "wt");
if (dump)
{
for (u8 c = 0; c < 2; c++)
@ -217,7 +218,7 @@ void DoFullDump()
fclose(dump);
}
dump = fopen("logs/effects.txt", "wt");
dump = EmuFolders::OpenLogFile("SPU2effects.txt", "wt");
if (dump)
{
for (u8 c = 0; c < 2; c++)
@ -264,6 +265,6 @@ void DoFullDump()
}
fclose(dump);
}
#endif
#endif
}
#endif

View File

@ -13,19 +13,64 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DEBUG_H_INCLUDED
#define DEBUG_H_INCLUDED
#pragma once
extern FILE* spu2Log;
#include "Config.h"
extern void FileLog(const char* fmt, ...);
extern void ConLog(const char* fmt, ...);
namespace SPU2
{
#ifdef PCSX2_DEVBUILD
__fi static bool MsgToConsole()
{
return EmuConfig.SPU2.DebugEnabled;
}
extern void DoFullDump();
__fi static bool MsgKeyOnOff() { return EmuConfig.SPU2.MsgKeyOnOff; }
__fi static bool MsgVoiceOff() { return EmuConfig.SPU2.MsgVoiceOff; }
__fi static bool MsgDMA() { return EmuConfig.SPU2.MsgDMA; }
__fi static bool MsgAutoDMA() { return EmuConfig.SPU2.MsgAutoDMA; }
__fi static bool MsgOverruns() { return EmuConfig.SPU2.MsgOverruns; }
__fi static bool MsgCache() { return EmuConfig.SPU2.MsgCache; }
extern FILE* OpenBinaryLog(const char* logfile);
extern FILE* OpenLog(const char* logfile);
extern FILE* OpenDump(const char* logfile);
__fi static bool AccessLog() { return EmuConfig.SPU2.AccessLog; }
__fi static bool DMALog() { return EmuConfig.SPU2.DMALog; }
__fi static bool WaveLog() { return EmuConfig.SPU2.WaveLog; }
__fi static bool CoresDump() { return EmuConfig.SPU2.CoresDump; }
__fi static bool MemDump() { return EmuConfig.SPU2.MemDump; }
__fi static bool RegDump() { return EmuConfig.SPU2.RegDump; }
__fi static bool VisualDebug() { return EmuConfig.SPU2.VisualDebugEnabled; }
extern void OpenFileLog();
extern void CloseFileLog();
extern void FileLog(const char* fmt, ...);
extern void ConLog(const char* fmt, ...);
extern void DoFullDump();
#else
__fi static constexpr bool MsgToConsole() { return false; }
__fi static constexpr bool MsgKeyOnOff() { return false; }
__fi static constexpr bool MsgVoiceOff() { return false; }
__fi static constexpr bool MsgDMA() { return false; }
__fi static constexpr bool MsgAutoDMA() { return false; }
__fi static constexpr bool MsgOverruns() { return false; }
__fi static constexpr bool MsgCache() { return false; }
__fi static constexpr bool AccessLog() { return false; }
__fi static constexpr bool DMALog() { return false; }
__fi static constexpr bool WaveLog() { return false; }
__fi static constexpr bool CoresDump() { return false; }
__fi static constexpr bool MemDump() { return false; }
__fi static constexpr bool RegDump() { return false; }
__fi static constexpr bool VisualDebug() { return false; }
__fi static void FileLog(const char* fmt, ...) {}
__fi static void ConLog(const char* fmt, ...) {}
#endif
} // namespace SPU2
namespace WaveDump
{
@ -65,5 +110,3 @@ using WaveDump::CoreSrc_Input;
using WaveDump::CoreSrc_PostReverb;
using WaveDump::CoreSrc_PreReverb;
using WaveDump::CoreSrc_WetVoiceMix;
#endif // DEBUG_H_INCLUDED //

View File

@ -14,12 +14,12 @@
*/
#include "PrecompiledHeader.h"
#include "Global.h"
#include "Dma.h"
#include "SPU2/Global.h"
#include "SPU2/Dma.h"
#include "SPU2/spu2.h"
#include "R3000A.h"
#include "IopHw.h"
#include "spu2.h" // temporary until I resolve cyclePtr/TimeUpdate dependencies.
#include "Config.h"
extern u8 callirq;
@ -33,18 +33,18 @@ static FILE* REGWRTLogFile[2] = {0, 0};
void DMALogOpen()
{
if (!DMALog())
if (!SPU2::DMALog())
return;
DMA4LogFile = OpenBinaryLog(DMA4LogFileName.c_str());
DMA7LogFile = OpenBinaryLog(DMA7LogFileName.c_str());
ADMA4LogFile = OpenBinaryLog("adma4.raw");
ADMA7LogFile = OpenBinaryLog("adma7.raw");
ADMAOutLogFile = OpenBinaryLog("admaOut.raw");
DMA4LogFile = EmuFolders::OpenLogFile("SPU2dma4.dat", "wb");
DMA7LogFile = EmuFolders::OpenLogFile("SPU2dma7.dat", "wb");
ADMA4LogFile = EmuFolders::OpenLogFile("adma4.raw", "wb");
ADMA7LogFile = EmuFolders::OpenLogFile("adma7.raw", "wb");
ADMAOutLogFile = EmuFolders::OpenLogFile("admaOut.raw", "wb");
}
void DMA4LogWrite(void* lpData, u32 ulSize)
{
if (!DMALog())
if (!SPU2::DMALog())
return;
if (!DMA4LogFile)
return;
@ -53,7 +53,7 @@ void DMA4LogWrite(void* lpData, u32 ulSize)
void DMA7LogWrite(void* lpData, u32 ulSize)
{
if (!DMALog())
if (!SPU2::DMALog())
return;
if (!DMA7LogFile)
return;
@ -62,7 +62,7 @@ void DMA7LogWrite(void* lpData, u32 ulSize)
void ADMAOutLogWrite(void* lpData, u32 ulSize)
{
if (!DMALog())
if (!SPU2::DMALog())
return;
if (!ADMAOutLogFile)
return;
@ -71,7 +71,7 @@ void ADMAOutLogWrite(void* lpData, u32 ulSize)
void RegWriteLog(u32 core, u16 value)
{
if (!DMALog())
if (!SPU2::DMALog())
return;
if (!REGWRTLogFile[core])
return;
@ -91,7 +91,7 @@ void DMALogClose()
void V_Core::LogAutoDMA(FILE* fp)
{
if (!DMALog() || !fp || !DMAPtr)
if (!SPU2::DMALog() || !fp || !DMAPtr)
return;
fwrite(DMAPtr + InputDataProgress, 0x400, 1, fp);
}
@ -157,9 +157,11 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
TimeUpdate(psxRegs.cycle);
if (MsgAutoDMA())
ConLog("* SPU2: DMA%c AutoDMA Transfer of %d bytes to %x (%02x %x %04x).OutPos %x\n",
if (SPU2::MsgAutoDMA())
{
SPU2::ConLog("* SPU2: DMA%c AutoDMA Transfer of %d bytes to %x (%02x %x %04x).OutPos %x\n",
GetDmaIndexChar(), size << 1, ActiveTSA, DMABits, AutoDMACtrl, (~Regs.ATTR) & 0xffff, OutPos);
}
InputDataProgress = 0;
TADR = MADR + (size << 1);
@ -191,7 +193,8 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
}
else
{
ConLog("ADMA%c Error Size of %x too small\n", GetDmaIndexChar(), size);
if (SPU2::MsgToConsole())
SPU2::ConLog("ADMA%c Error Size of %x too small\n", GetDmaIndexChar(), size);
InputDataLeft = 0;
DMAICounter = size * 4;
LastClock = psxRegs.cycle;
@ -200,7 +203,7 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
void V_Core::PlainDMAWrite(u16* pMem, u32 size)
{
if (MsgToConsole())
if (SPU2::MsgToConsole())
{
// Don't need this anymore. Target may still be good to know though.
/*if((uptr)pMem & 15)
@ -210,7 +213,7 @@ void V_Core::PlainDMAWrite(u16* pMem, u32 size)
if (ActiveTSA & 7)
{
ConLog("* SPU2 DMA Write > Misaligned target. Core: %d IOP: %p TSA: 0x%x Size: 0x%x\n", Index, (void*)DMAPtr, ActiveTSA, ReadSize);
SPU2::ConLog("* SPU2 DMA Write > Misaligned target. Core: %d IOP: %p TSA: 0x%x Size: 0x%x\n", Index, (void*)DMAPtr, ActiveTSA, ReadSize);
}
}
@ -224,10 +227,12 @@ void V_Core::PlainDMAWrite(u16* pMem, u32 size)
Regs.STATX |= 0x400;
TADR = MADR + (size << 1);
if (MsgDMA())
ConLog("* SPU2: DMA%c Write Transfer of %d bytes to %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
if (SPU2::MsgDMA())
{
SPU2::ConLog("* SPU2: DMA%c Write Transfer of %d bytes to %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
GetDmaIndexChar(), size << 1, ActiveTSA, DMABits, AutoDMACtrl, Regs.ATTR & 0xffff,
Cores[Index].IRQEnable, Cores[Index].IRQA);
}
FinishDMAwrite();
}
@ -480,10 +485,12 @@ void V_Core::DoDMAread(u16* pMem, u32 size)
psxNextCounter = psxCounters[6].CycleT;
}
if (MsgDMA())
ConLog("* SPU2: DMA%c Read Transfer of %d bytes from %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
if (SPU2::MsgDMA())
{
SPU2::ConLog("* SPU2: DMA%c Read Transfer of %d bytes from %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
GetDmaIndexChar(), size << 1, ActiveTSA, DMABits, AutoDMACtrl, Regs.ATTR & 0xffff,
Cores[Index].IRQEnable, Cores[Index].IRQA);
}
}
void V_Core::DoDMAwrite(u16* pMem, u32 size)
@ -505,11 +512,11 @@ void V_Core::DoDMAwrite(u16* pMem, u32 size)
DebugCores[Index].dmaFlag = 2;
}
if (MsgToConsole())
if (SPU2::MsgToConsole())
{
if (TSA > 0xfffff)
{
ConLog("* SPU2: Transfer Start Address out of bounds. TSA is %x\n", TSA);
SPU2::ConLog("* SPU2: Transfer Start Address out of bounds. TSA is %x\n", TSA);
}
}

View File

@ -19,6 +19,7 @@
#define TADR (Index == 0 ? HW_DMA4_TADR : HW_DMA7_TADR)
extern void DMALogOpen();
extern void ADMAOutLogWrite(void* lpData, u32 ulSize);
extern void DMA4LogWrite(void* lpData, u32 ulSize);
extern void DMA7LogWrite(void* lpData, u32 ulSize);
extern void DMALogClose();

View File

@ -17,42 +17,15 @@
#include "PrecompiledHeader.h"
extern bool psxmode;
struct StereoOut16;
struct StereoOut32;
struct StereoOutFloat;
struct V_Core;
namespace soundtouch
{
class SoundTouch;
}
template <typename T>
static __forceinline void Clampify(T& src, T min, T max)
{
src = std::min(std::max(src, min), max);
}
template <typename T>
static __forceinline T GetClamped(T src, T min, T max)
{
return std::min(std::max(src, min), max);
}
// Uncomment to enable debug keys on numpad (0 to 5)
//#define DEBUG_KEYS
#ifdef PCSX2_DEVBUILD
#define SPU2_LOG
#endif
#include "defs.h"
#include "regs.h"
#include "Config.h"
#include "Debug.h"
#include "Mixer.h"
#include "SndOut.h"

View File

@ -14,12 +14,11 @@
*/
#include "PrecompiledHeader.h"
#include "Global.h"
#include "common/Assertions.h"
void ADMAOutLogWrite(void* lpData, u32 ulSize);
#include "interpolate_table.h"
#include "SPU2/Global.h"
#include "SPU2/spu2.h"
#include "SPU2/interpolate_table.h"
static const s32 tbl_XA_Factor[16][2] =
{
@ -29,6 +28,7 @@ static const s32 tbl_XA_Factor[16][2] =
{98, -55},
{122, -60}};
float SPU2::FinalVolume = 1.0f;
// Performs a 64-bit multiplication between two values and returns the
// high 32 bits as a result (discarding the fractional 32 bits).
@ -51,7 +51,7 @@ static __forceinline s32 MulShr32(s32 srcval, s32 mulval)
__forceinline s32 clamp_mix(s32 x, u8 bitshift)
{
assert(bitshift <= 15);
return GetClamped(x, -(0x8000 << bitshift), 0x7fff << bitshift);
return std::clamp(x, -(0x8000 << bitshift), 0x7fff << bitshift);
}
#if _MSC_VER
@ -70,8 +70,8 @@ __forceinline
// modules or sound drivers could (will :p) overshoot with that. So giving it a small safety.
return StereoOut32(
GetClamped(sample.Left, -(0x7f00 << bitshift), 0x7f00 << bitshift),
GetClamped(sample.Right, -(0x7f00 << bitshift), 0x7f00 << bitshift));
std::clamp(sample.Left, -(0x7f00 << bitshift), 0x7f00 << bitshift),
std::clamp(sample.Right, -(0x7f00 << bitshift), 0x7f00 << bitshift));
}
static void __forceinline XA_decode_block(s16* buffer, const s16* block, s32& prev1, s32& prev2)
@ -79,8 +79,8 @@ static void __forceinline XA_decode_block(s16* buffer, const s16* block, s32& pr
const s32 header = *block;
const s32 shift = (header & 0xF) + 16;
const int id = header >> 4 & 0xF;
if (id > 4 && MsgToConsole())
ConLog("* SPU2: Unknown ADPCM coefficients table id %d\n", id);
if (id > 4 && SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: Unknown ADPCM coefficients table id %d\n", id);
const s32 pred1 = tbl_XA_Factor[id][0];
const s32 pred2 = tbl_XA_Factor[id][1];
@ -92,13 +92,13 @@ static void __forceinline XA_decode_block(s16* buffer, const s16* block, s32& pr
s32 data = ((*blockbytes) << 28) & 0xF0000000;
s32 pcm = (data >> shift) + (((pred1 * prev1) + (pred2 * prev2) + 32) >> 6);
Clampify(pcm, -0x8000, 0x7fff);
pcm = std::clamp<s32>(pcm, -0x8000, 0x7fff);
*(buffer++) = pcm;
data = ((*blockbytes) << 24) & 0xF0000000;
s32 pcm2 = (data >> shift) + (((pred1 * pcm) + (pred2 * prev1) + 32) >> 6);
Clampify(pcm2, -0x8000, 0x7fff);
pcm2 = std::clamp<s32>(pcm2, -0x8000, 0x7fff);
*(buffer++) = pcm2;
prev2 = pcm;
@ -159,11 +159,15 @@ static __forceinline s32 GetNextDataBuffered(V_Core& thiscore, uint voiceidx)
if (vc.LoopCycle < vc.PlayCycle)
{
vc.LoopStartA = vc.PendingLoopStartA;
ConLog("Core %d Voice %d Loop Written by HW within 4T of Key On, Now Applying\n", thiscore.Index, voiceidx);
if (SPU2::MsgToConsole())
SPU2::ConLog("Core %d Voice %d Loop Written by HW within 4T of Key On, Now Applying\n", thiscore.Index, voiceidx);
vc.LoopMode = 1;
}
else
ConLog("Loop point from waveform set within 4T's, ignoring HW write\n");
{
if (SPU2::MsgToConsole())
SPU2::ConLog("Loop point from waveform set within 4T's, ignoring HW write\n");
}
vc.PendingLoopStart = false;
}
@ -182,8 +186,8 @@ static __forceinline s32 GetNextDataBuffered(V_Core& thiscore, uint voiceidx)
if (IsDevBuild)
{
if (MsgVoiceOff())
ConLog("* SPU2: Voice Off by EndPoint: %d \n", voiceidx);
if (SPU2::MsgVoiceOff())
SPU2::ConLog("* SPU2: Voice Off by EndPoint: %d \n", voiceidx);
}
}
}
@ -327,7 +331,7 @@ static void __forceinline UpdatePitch(uint coreidx, uint voiceidx)
if ((vc.Modulated == 0) || (voiceidx == 0))
pitch = vc.Pitch;
else
pitch = GetClamped((vc.Pitch * (32768 + Cores[coreidx].Voices[voiceidx - 1].OutX)) >> 15, 0, 0x3fff);
pitch = std::clamp((vc.Pitch * (32768 + Cores[coreidx].Voices[voiceidx - 1].OutX)) >> 15, 0, 0x3fff);
pitch = std::min(pitch, 0x3FFF);
vc.SP += pitch;
@ -348,8 +352,8 @@ static __forceinline void CalculateADSR(V_Core& thiscore, uint voiceidx)
{
if (IsDevBuild)
{
if (MsgVoiceOff())
ConLog("* SPU2: Voice Off by ADSR: %d \n", voiceidx);
if (SPU2::MsgVoiceOff())
SPU2::ConLog("* SPU2: Voice Off by ADSR: %d \n", voiceidx);
}
vc.Stop();
}
@ -588,24 +592,24 @@ static __forceinline StereoOut32 MixVoice(uint coreidx, uint voiceidx)
// Optimization : Forceinline'd Templated Dispatch Table. Any halfwit compiler will
// turn this into a clever jump dispatch table (no call/rets, no compares, uber-efficient!)
switch (Interpolation)
switch (EmuConfig.SPU2.Interpolation)
{
case 0:
case Pcsx2Config::SPU2Options::InterpolationMode::Nearest:
Value = GetVoiceValues<0>(thiscore, voiceidx);
break;
case 1:
case Pcsx2Config::SPU2Options::InterpolationMode::Linear:
Value = GetVoiceValues<1>(thiscore, voiceidx);
break;
case 2:
case Pcsx2Config::SPU2Options::InterpolationMode::Cubic:
Value = GetVoiceValues<2>(thiscore, voiceidx);
break;
case 3:
case Pcsx2Config::SPU2Options::InterpolationMode::Hermite:
Value = GetVoiceValues<3>(thiscore, voiceidx);
break;
case 4:
case Pcsx2Config::SPU2Options::InterpolationMode::CatmullRom:
Value = GetVoiceValues<4>(thiscore, voiceidx);
break;
case 5:
case Pcsx2Config::SPU2Options::InterpolationMode::Gaussian:
Value = GetVoiceValues<5>(thiscore, voiceidx);
break;
@ -813,8 +817,8 @@ __forceinline
}
// Configurable output volume
Out.Left *= FinalVolume;
Out.Right *= FinalVolume;
Out.Left *= SPU2::FinalVolume;
Out.Right *= SPU2::FinalVolume;
// Final Clamp!
// Like any good audio system, the PS2 pumps the volume and incurs some distortion in its
@ -842,11 +846,13 @@ __forceinline
if (p_cachestat_counter > (48000 * 10))
{
p_cachestat_counter = 0;
if (MsgCache())
ConLog(" * SPU2 > CacheStats > Hits: %d Misses: %d Ignores: %d\n",
if (SPU2::MsgCache())
{
SPU2::ConLog(" * SPU2 > CacheStats > Hits: %d Misses: %d Ignores: %d\n",
g_counter_cache_hits,
g_counter_cache_misses,
g_counter_cache_ignores);
}
g_counter_cache_hits =
g_counter_cache_misses =
@ -854,3 +860,8 @@ __forceinline
}
}
}
void SPU2::SetOutputVolume(s32 volume)
{
FinalVolume = static_cast<float>(std::clamp<s32>(volume, 0, Pcsx2Config::SPU2Options::MAX_VOLUME)) / 100.0f;
}

View File

@ -15,9 +15,10 @@
#pragma once
// Implemented in Config.cpp
extern float VolumeAdjustFL;
extern float VolumeAdjustFR;
namespace SPU2
{
extern float FinalVolume;
}
struct StereoOut32
{
@ -74,56 +75,6 @@ struct StereoOut32
this->Left = src.Left << 2;
this->Right = src.Right << 2;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
}
};
struct FrequencyResponseFilter
{
static FrequencyResponseFilter Empty;
StereoOut32 History_One_In;
StereoOut32 History_One_Out;
StereoOut32 History_Two_In;
StereoOut32 History_Two_Out;
s32 lx1;
s32 lx2;
s32 ly1;
s32 ly2;
float la0, la1, la2, lb1, lb2;
float ha0, ha1, ha2, hb1, hb2;
FrequencyResponseFilter()
: History_One_In(0, 0)
, History_One_Out(0, 0)
, History_Two_In(0, 0)
, History_Two_Out(0, 0)
, lx1(0)
, lx2(0)
, ly1(0)
, ly2(0)
, la0(1.00320890889339290000f)
, la1(-1.97516434134506300000f)
, la2(0.97243484967313087000f)
, lb1(-1.97525280404731810000f)
, lb2(0.97555529586426892000f)
, ha0(1.52690772687271160000f)
, ha1(-1.62653918974914990000f) //-1.72 = "common equilizer curve" --____--
, ha2(0.57997976029249387000f)
, hb1(-0.80955590379048203000f)
, hb2(0.28990420120653748000f)
{
}
};
extern void Mix();

View File

@ -32,8 +32,8 @@
//
StereoOut32 V_Core::ReadInput_HiFi()
{
if (psxmode)
ConLog("ReadInput_HiFi!!!!!\n");
if (SPU2::IsRunningPSXMode() && SPU2::MsgToConsole())
SPU2::ConLog("ReadInput_HiFi!!!!!\n");
u16 ReadIndex = (OutPos * 2) & 0x1FF;
@ -84,11 +84,11 @@ StereoOut32 V_Core::ReadInput_HiFi()
{
if (IsDevBuild)
{
FileLog("[%10d] AutoDMA%c block end.\n", Cycles, GetDmaIndexChar());
SPU2::FileLog("[%10d] AutoDMA%c block end.\n", Cycles, GetDmaIndexChar());
if (InputDataLeft > 0)
{
if (MsgAutoDMA())
ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
if (SPU2::MsgAutoDMA())
SPU2::ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
}
}
@ -159,11 +159,11 @@ StereoOut32 V_Core::ReadInput()
{
if (IsDevBuild)
{
FileLog("[%10d] AutoDMA%c block end.\n", Cycles, GetDmaIndexChar());
SPU2::FileLog("[%10d] AutoDMA%c block end.\n", Cycles, GetDmaIndexChar());
if (InputDataLeft > 0)
{
if (MsgAutoDMA())
ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
if (SPU2::MsgAutoDMA())
SPU2::ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
}
}

View File

@ -22,7 +22,7 @@ const char* AddressNames[6] = {"SSAH", "SSAL", "LSAH", "LSAL", "NAXH", "NAXL"};
__forceinline void _RegLog_(const char* action, int level, const char* RName, u32 mem, u32 core, u16 value)
{
if (level > 1)
FileLog("[%10d] SPU2 %s mem %08x (core %d, register %s) value %04x\n",
SPU2::FileLog("[%10d] SPU2 %s mem %08x (core %d, register %s) value %04x\n",
Cycles, action, mem, core, RName, value);
}
@ -143,28 +143,28 @@ void SPU2writeLog(const char* action, u32 rmem, u16 value)
RegLog(2, "SPDIF_IRQINFO", rmem, -1, value);
break;
case 0x7c4:
if (Spdif.Unknown1 != value)
ConLog("* SPU2: SPDIF Unknown Register 1 set to %04x\n", value);
if (Spdif.Unknown1 != value && SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: SPDIF Unknown Register 1 set to %04x\n", value);
RegLog(2, "SPDIF_UNKNOWN1", rmem, -1, value);
break;
case SPDIF_MODE:
if (Spdif.Mode != value)
ConLog("* SPU2: SPDIF Mode set to %04x\n", value);
if (Spdif.Mode != value && SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: SPDIF Mode set to %04x\n", value);
RegLog(2, "SPDIF_MODE", rmem, -1, value);
break;
case SPDIF_MEDIA:
if (Spdif.Media != value)
ConLog("* SPU2: SPDIF Media set to %04x\n", value);
if (Spdif.Media != value && SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: SPDIF Media set to %04x\n", value);
RegLog(2, "SPDIF_MEDIA", rmem, -1, value);
break;
case 0x7ca:
if (Spdif.Unknown2 != value)
ConLog("* SPU2: SPDIF Unknown Register 2 set to %04x\n", value);
if (Spdif.Unknown2 != value && SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: SPDIF Unknown Register 2 set to %04x\n", value);
RegLog(2, "SPDIF_UNKNOWN2", rmem, -1, value);
break;
case SPDIF_PROTECT:
if (Spdif.Protection != value)
ConLog("* SPU2: SPDIF Copy set to %04x\n", value);
if (Spdif.Protection != value && SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: SPDIF Copy set to %04x\n", value);
RegLog(2, "SPDIF_PROTECT", rmem, -1, value);
break;
}

View File

@ -107,7 +107,7 @@ s32 __forceinline V_Core::ReverbDownsample(bool right)
out += RevbDownBuf[right][((RevbSampleBufPos - NUM_TAPS) + 19) & 63] * filter_coefs[19];
out >>= 15;
Clampify(out, (s32)INT16_MIN, (s32)INT16_MAX);
out = std::clamp<s32>(out, INT16_MIN, INT16_MAX);
return out;
}
@ -136,9 +136,9 @@ StereoOut32 __forceinline V_Core::ReverbUpsample(bool phase)
}
ls >>= 14;
Clampify(ls, (s32)INT16_MIN, (s32)INT16_MAX);
ls = std::clamp<s32>(ls, INT16_MIN, INT16_MAX);
rs >>= 14;
Clampify(rs, (s32)INT16_MIN, (s32)INT16_MAX);
rs = std::clamp<s32>(rs, INT16_MIN, INT16_MAX);
return StereoOut32(ls, rs);
}

View File

@ -14,9 +14,14 @@
*/
#include "PrecompiledHeader.h"
#include "Global.h"
#include "common/Assertions.h"
#include "SPU2/Global.h"
#include "SPU2/spu2.h"
#include "common/Assertions.h"
#include "common/Timer.h"
#include "SoundTouch.h"
StereoOut32 StereoOut32::Empty(0, 0);
@ -46,7 +51,7 @@ StereoOut32 StereoOut16::UpSample() const
Right << SndOutVolumeShift);
}
namespace {
class NullOutModule final : public SndOutModule
{
public:
@ -70,11 +75,19 @@ public:
return nullptr;
}
};
}
static NullOutModule s_NullOut;
SndOutModule* NullOut = &s_NullOut;
static SndOutModule* NullOut = &s_NullOut;
SndOutModule* mods[] =
#ifdef _WIN32
extern SndOutModule* XAudio2Out;
#endif
#if defined(SPU2X_CUBEB)
extern SndOutModule* CubebOut;
#endif
static SndOutModule* mods[] =
{
NullOut,
#ifdef _WIN32
@ -83,19 +96,19 @@ SndOutModule* mods[] =
#if defined(SPU2X_CUBEB)
CubebOut,
#endif
nullptr // signals the end of our list
};
int FindOutputModuleById(const char* omodid)
static SndOutModule* s_output_module;
static SndOutModule* FindOutputModule(const char* name)
{
int modcnt = 0;
while (mods[modcnt] != nullptr)
for (u32 i = 0; i < std::size(mods); i++)
{
if (std::strcmp(mods[modcnt]->GetIdent(), omodid) == 0)
break;
++modcnt;
if (std::strcmp(mods[i]->GetIdent(), name) == 0)
return mods[i];
}
return modcnt;
return nullptr;
}
const char* const* GetOutputModuleBackends(const char* omodid)
@ -135,7 +148,7 @@ bool SndBuffer::CheckUnderrunStatus(int& nSamples, int& quietSampleCount)
int data = _GetApproximateDataInBuffer();
if (m_underrun_freeze)
{
int toFill = m_size / ((SynchMode == 2) ? 32 : 400); // TimeStretch and Async off?
int toFill = m_size / ((EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::NoSync) ? 32 : 400); // TimeStretch and Async off?
toFill = GetAlignedBufferSize(toFill);
// toFill is now aligned to a SndOutPacket
@ -148,8 +161,8 @@ bool SndBuffer::CheckUnderrunStatus(int& nSamples, int& quietSampleCount)
}
m_underrun_freeze = false;
if (MsgOverruns())
ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize);
if (SPU2::MsgOverruns())
SPU2::ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize);
lastPct = 0.0; // normalize timestretcher
}
else if (data < nSamples)
@ -158,7 +171,7 @@ bool SndBuffer::CheckUnderrunStatus(int& nSamples, int& quietSampleCount)
nSamples = data;
m_underrun_freeze = true;
if (SynchMode == 0) // TimeStrech on
if (EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch) // TimeStrech on
timeStretchUnderrun();
return nSamples != 0;
@ -260,29 +273,15 @@ void SndBuffer::ReadSamples(T* bData, int nSamples)
if (b1 > nSamples)
b1 = nSamples;
if (AdvancedVolumeControl)
{
// First part
for (int i = 0; i < b1; i++)
bData[i].AdjustFrom(m_buffer[i + m_rpos]);
// Second part
int b2 = nSamples - b1;
for (int i = 0; i < b2; i++)
bData[i + b1].AdjustFrom(m_buffer[i]);
}
else
{
// First part
for (int i = 0; i < b1; i++)
bData[i].ResampleFrom(m_buffer[i + m_rpos]);
// Second part
int b2 = nSamples - b1;
for (int i = 0; i < b2; i++)
bData[i + b1].ResampleFrom(m_buffer[i]);
}
// First part
for (int i = 0; i < b1; i++)
bData[i].ResampleFrom(m_buffer[i + m_rpos]);
// Second part
int b2 = nSamples - b1;
for (int i = 0; i < b2; i++)
bData[i + b1].ResampleFrom(m_buffer[i]);
_DropSamples_Internal(nSamples);
}
@ -357,8 +356,8 @@ void SndBuffer::_WriteSamples(StereoOut32* bData, int nSamples)
ConLog(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize );
lastPct = 0.0; // normalize the timestretcher
#else
if (MsgOverruns())
ConLog(" * SPU2 > Overrun! 1 packet tossed)\n");
if (SPU2::MsgOverruns())
SPU2::ConLog(" * SPU2 > Overrun! 1 packet tossed)\n");
lastPct = 0.0; // normalize the timestretcher
return;
#endif
@ -367,9 +366,10 @@ void SndBuffer::_WriteSamples(StereoOut32* bData, int nSamples)
_WriteSamples_Safe(bData, nSamples);
}
bool SndBuffer::Init()
bool SndBuffer::Init(const char* modname)
{
if (!mods[OutputModule])
s_output_module = FindOutputModule(modname);
if (!s_output_module)
return false;
// initialize sound buffer
@ -379,7 +379,7 @@ bool SndBuffer::Init()
m_rpos = 0;
m_wpos = 0;
const float latencyMS = SndOutLatencyMS * 16;
const float latencyMS = EmuConfig.SPU2.Latency * 16;
m_size = GetAlignedBufferSize((int)(latencyMS * SampleRate / 1000.0f));
m_buffer = new StereoOut32[m_size];
m_underrun_freeze = false;
@ -391,7 +391,7 @@ bool SndBuffer::Init()
soundtouchInit(); // initializes the timestretching
// initialize module
if (!mods[OutputModule]->Init())
if (!s_output_module->Init())
{
Cleanup();
return false;
@ -402,7 +402,11 @@ bool SndBuffer::Init()
void SndBuffer::Cleanup()
{
mods[OutputModule]->Close();
if (s_output_module)
{
s_output_module->Close();
s_output_module = nullptr;
}
soundtouchCleanup();
@ -422,9 +426,15 @@ void SndBuffer::ClearContents()
SndBuffer::ssFreeze = 256; //Delays sound output for about 1 second.
}
void SndBuffer::SetPaused(bool paused)
void SndBuffer::ResetBuffers()
{
mods[OutputModule]->SetPaused(paused);
m_rpos = 0;
m_wpos = 0;
}
void SPU2::SetOutputPaused(bool paused)
{
s_output_module->SetPaused(paused);
}
void SndBuffer::Write(const StereoOut32& Sample)
@ -435,9 +445,6 @@ void SndBuffer::Write(const StereoOut32& Sample)
if (WavRecordEnabled)
RecordWrite(Sample.DownSample());
if (mods[OutputModule] == NullOut) // null output doesn't need buffering or stretching! :p
return;
sndTempBuffer[sndTempProgress++] = Sample;
// If we haven't accumulated a full packet yet, do nothing more:
@ -454,9 +461,557 @@ void SndBuffer::Write(const StereoOut32& Sample)
}
else
{
if (SynchMode == 0) // TimeStrech on
if (EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch) // TimeStrech on
timeStretchWrite();
else
_WriteSamples(sndTempBuffer, SndOutPacketSize);
}
}
//////////////////////////////////////////////////////////////////////////
// Time Stretching
//////////////////////////////////////////////////////////////////////////
//Uncomment the next line to use the old time stretcher
//#define SPU2X_USE_OLD_STRETCHER
static std::unique_ptr<soundtouch::SoundTouch> pSoundTouch = nullptr;
// data prediction amount, used to "commit" data that hasn't
// finished timestretch processing.
s32 SndBuffer::m_predictData;
// records last buffer status (fill %, range -100 to 100, with 0 being 50% full)
float SndBuffer::lastPct;
float SndBuffer::lastEmergencyAdj;
float SndBuffer::cTempo = 1;
float SndBuffer::eTempo = 1;
void SndBuffer::PredictDataWrite(int samples)
{
m_predictData += samples;
}
// Calculate the buffer status percentage.
// Returns range from -1.0 to 1.0
// 1.0 = buffer overflow!
// 0.0 = buffer nominal (50% full)
// -1.0 = buffer underflow!
float SndBuffer::GetStatusPct()
{
// Get the buffer status of the output driver too, so that we can
// obtain a more accurate overall buffer status.
int drvempty = s_output_module->GetEmptySampleCount(); // / 2;
//ConLog( "Data %d >>> driver: %d predict: %d\n", m_data, drvempty, m_predictData );
int data = _GetApproximateDataInBuffer();
float result = (float)(data + m_predictData - drvempty) - (m_size / 16);
result /= (m_size / 16);
return result;
}
//Alternative simple tempo adjustment. Based only on the soundtouch buffer state.
//Base algorithm: aim at specific average number of samples at the buffer (by GUI), and adjust tempo simply by current/target.
//An extra mechanism is added to keep adjustment at perfect 1:1 ratio (when emulation speed is stable around 100%)
// to prevent constant stretching/shrinking of packets if possible.
// This mechanism is triggered when the adjustment is close to 1:1 for long enough (defaults to 100 iterations within hys_ok_factor - defaults to 3%).
// 1:1 state is aborted when required adjustment goes beyond hys_bad_factor (defaults to 20%).
//
//To compensate for wide variation of the <num of samples> ratio due to relatively small size of the buffer,
// The required tempo is a running average of STRETCH_AVERAGE_LEN (defaults to 50) last calculations.
// This averaging slows down the respons time of the algorithm, but greatly stablize it towards steady stretching.
//
//Keeping the buffer at required latency:
// This algorithm stabilises when the actual latency is <speed>*<required_latency>. While this is just fine at 100% speed,
// it's problematic especially for slow speeds, as the number of actual samples at the buffer gets very small on that case,
// which may lead to underruns (or just too much latency when running very fast).
//To compensate for that, the algorithm has a slowly moving compensation factor which will eventually bring the actual latency to the required one.
//compensationDivider defines how slow this compensation changes. By default it's set to 100,
// which will finalize the compensation after about 200 iterations.
//
// Note, this algorithm is intentionally simplified by not taking extreme actions at extreme scenarios (mostly underruns when speed drops sharply),
// and let's the overrun/underrun protections do what they should (doesn't happen much though in practice, even at big FPS variations).
//
// These params were tested to show good respond and stability, on all audio systems (dsound, wav, port audio, xaudio2),
// even at extreme small latency of 50ms which can handle 50%-100% variations without audible glitches.
int targetIPS = 750;
//Dynamic tuning changes the values of the base algorithm parameters (derived from targetIPS) to adapt, in real time, to
// different number of invocations/sec (mostly affects number of iterations to average).
// Dynamic tuning can have a slight negative effect on the behavior of the algorithm, so it's preferred to have it off.
//Currently it looks like it's around 750/sec on all systems when playing at 100% speed (50/60fps),
// and proportional to that speed otherwise.
//If changes are made to SPU2 which affects this number (but it's still the same on all systems), then just change targetIPS.
//If we find out that some systems are very different, we can turn on dynamic tuning by uncommenting the next line.
//#define NEWSTRETCHER_USE_DYNAMIC_TUNING
//Additional performance note: since MAX_STRETCH_AVERAGE_LEN = 128 (or any power of 2), the '%' below
//could be replaced with a faster '&'. The compiler is highly likely to do it since all the values are unsigned.
#define AVERAGING_BUFFER_SIZE 256U
unsigned int AVERAGING_WINDOW = 50.0 * targetIPS / 750;
#define STRETCHER_RESET_THRESHOLD 5
int gRequestStretcherReset = STRETCHER_RESET_THRESHOLD;
//Adds a value to the running average buffer, and return the new running average.
static float addToAvg(float val)
{
static float avg_fullness[AVERAGING_BUFFER_SIZE];
static unsigned int nextAvgPos = 0;
static unsigned int available = 0; // Make sure we're not averaging AVERAGING_WINDOW items if we inserted less.
if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
available = 0;
if (available < AVERAGING_BUFFER_SIZE)
available++;
avg_fullness[nextAvgPos] = val;
nextAvgPos = (nextAvgPos + 1U) % AVERAGING_BUFFER_SIZE;
unsigned int actualWindow = std::min(available, AVERAGING_WINDOW);
unsigned int first = (nextAvgPos - actualWindow + AVERAGING_BUFFER_SIZE) % AVERAGING_BUFFER_SIZE;
// Possible optimization: if we know that actualWindow hasn't changed since
// last invocation, we could calculate the running average in O(1) instead of O(N)
// by keeping a running sum between invocations, and then
// do "runningSum = runningSum + val - avg_fullness[(first-1)%...]" instead of the following loop.
// Few gotchas: val overwrites first-1, handling actualWindow changes, etc.
// However, this isn't hot code, so unless proven otherwise, we can live with unoptimized code.
float sum = 0;
for (unsigned int i = first; i < first + actualWindow; i++)
{
sum += avg_fullness[i % AVERAGING_BUFFER_SIZE];
}
sum = sum / actualWindow;
return sum ? sum : 1; // 1 because that's the 100% perfect speed value
}
template <class T>
static bool IsInRange(const T& val, const T& min, const T& max)
{
return (min <= val && val <= max);
}
//actual stretch algorithm implementation
void SndBuffer::UpdateTempoChangeSoundTouch2()
{
long targetSamplesReservoir = 48 * EmuConfig.SPU2.Latency; //48000*SndOutLatencyMS/1000
//base aim at buffer filled %
float baseTargetFullness = (double)targetSamplesReservoir; ///(double)m_size;//0.05;
//state vars
static bool inside_hysteresis; //=false;
static int hys_ok_count; //=0;
static float dynamicTargetFullness; //=baseTargetFullness;
if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
{
if (SPU2::MsgOverruns())
SPU2::ConLog("______> stretch: Reset.\n");
inside_hysteresis = false;
hys_ok_count = 0;
dynamicTargetFullness = baseTargetFullness;
}
int data = _GetApproximateDataInBuffer();
float bufferFullness = (float)data; ///(float)m_size;
#ifdef NEWSTRETCHER_USE_DYNAMIC_TUNING
{ //test current iterations/sec every 0.5s, and change algo params accordingly if different than previous IPS more than 30%
static long iters = 0;
static wxDateTime last = wxDateTime::UNow();
wxDateTime unow = wxDateTime::UNow();
wxTimeSpan delta = unow.Subtract(last);
if (delta.GetMilliseconds() > 500)
{
int pot_targetIPS = 1000.0 / delta.GetMilliseconds().ToDouble() * iters;
if (!IsInRange(pot_targetIPS, int((float)targetIPS / 1.3f), int((float)targetIPS * 1.3f)))
{
if (SPU2::MsgOverruns())
SPU2::ConLog("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
targetIPS = pot_targetIPS;
AVERAGING_WINDOW = std::clamp((int)(50.0f * (float)targetIPS / 750.0f), 3, (int)AVERAGING_BUFFER_SIZE);
}
last = unow;
iters = 0;
}
iters++;
}
#endif
//Algorithm params: (threshold params (hysteresis), etc)
const float hys_ok_factor = 1.04f;
const float hys_bad_factor = 1.2f;
int hys_min_ok_count = std::clamp((int)(50.0 * (float)targetIPS / 750.0), 2, 100); //consecutive iterations within hys_ok before going to 1:1 mode
int compensationDivider = std::clamp((int)(100.0 * (float)targetIPS / 750), 15, 150);
float tempoAdjust = bufferFullness / dynamicTargetFullness;
float avgerage = addToAvg(tempoAdjust);
tempoAdjust = avgerage;
// Dampen the adjustment to avoid overshoots (this means the average will compensate to the other side).
// This is different than simply bigger averaging window since bigger window also has bigger "momentum",
// so it's slower to slow down when it gets close to the equilibrium state and can therefore resonate.
// The dampening (sqrt was chosen for no very good reason) manages to mostly prevent that.
tempoAdjust = sqrt(tempoAdjust);
tempoAdjust = std::clamp(tempoAdjust, 0.05f, 10.0f);
if (tempoAdjust < 1)
baseTargetFullness /= sqrt(tempoAdjust); // slightly increase latency when running slow.
dynamicTargetFullness += (baseTargetFullness / tempoAdjust - dynamicTargetFullness) / (double)compensationDivider;
if (IsInRange(tempoAdjust, 0.9f, 1.1f) && IsInRange(dynamicTargetFullness, baseTargetFullness * 0.9f, baseTargetFullness * 1.1f))
dynamicTargetFullness = baseTargetFullness;
if (!inside_hysteresis)
{
if (IsInRange(tempoAdjust, 1.0f / hys_ok_factor, hys_ok_factor))
hys_ok_count++;
else
hys_ok_count = 0;
if (hys_ok_count >= hys_min_ok_count)
{
inside_hysteresis = true;
if (SPU2::MsgOverruns())
SPU2::ConLog("======> stretch: None (1:1)\n");
}
}
else if (!IsInRange(tempoAdjust, 1.0f / hys_bad_factor, hys_bad_factor))
{
if (SPU2::MsgOverruns())
SPU2::ConLog("~~~~~~> stretch: Dynamic\n");
inside_hysteresis = false;
hys_ok_count = 0;
}
if (inside_hysteresis)
tempoAdjust = 1.0;
if (SPU2::MsgOverruns())
{
static int iters = 0;
static u64 last = 0;
const u64 now = Common::Timer::GetCurrentValue();
if (Common::Timer::ConvertValueToSeconds(now - last) > 1.0f)
{ //report buffers state and tempo adjust every second
SPU2::ConLog("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d) reset:%d\n",
(int)(data / 48), (double)(100.0 * bufferFullness / baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness / baseTargetFullness), iters, (int)targetIPS, AVERAGING_WINDOW, hys_min_ok_count, compensationDivider, gRequestStretcherReset);
last = now;
iters = 0;
}
iters++;
}
pSoundTouch->setTempo(tempoAdjust);
if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
gRequestStretcherReset = 0;
return;
}
void SndBuffer::UpdateTempoChangeSoundTouch()
{
float statusPct = GetStatusPct();
float pctChange = statusPct - lastPct;
float tempoChange;
float emergencyAdj = 0;
float newcee = cTempo; // workspace var. for cTempo
// IMPORTANT!
// If you plan to tweak these values, make sure you're using a release build
// OUTSIDE THE DEBUGGER to test it! The Visual Studio debugger can really cause
// erratic behavior in the audio buffers, and makes the timestretcher seem a
// lot more inconsistent than it really is.
// We have two factors.
// * Distance from nominal buffer status (50% full)
// * The change from previous update to this update.
// Prediction based on the buffer change:
// (linear seems to work better here)
tempoChange = pctChange * 0.75f;
if (statusPct * tempoChange < 0.0f)
{
// only apply tempo change if it is in synch with the buffer status.
// In other words, if the buffer is high (over 0%), and is decreasing,
// ignore it. It'll just muck things up.
tempoChange = 0;
}
// Sudden spikes in framerate can cause the nominal buffer status
// to go critical, in which case we have to enact an emergency
// stretch. The following cubic formulas do that. Values near
// the extremeites give much larger results than those near 0.
// And the value is added only this time, and does not accumulate.
// (otherwise a large value like this would cause problems down the road)
// Constants:
// Weight - weights the statusPct's "emergency" consideration.
// higher values here will make the buffer perform more drastic
// compensations at the outer edges of the buffer (at -75 or +75%
// or beyond, for example).
// Range - scales the adjustment to the given range (more or less).
// The actual range is dependent on the weight used, so if you increase
// Weight you'll usually want to decrease Range somewhat to compensate.
// Prediction based on the buffer fill status:
const float statusWeight = 2.99f;
const float statusRange = 0.068f;
// "non-emergency" deadzone: In this area stretching will be strongly discouraged.
// Note: due tot he nature of timestretch latency, it's always a wee bit harder to
// cope with low fps (underruns) than it is high fps (overruns). So to help out a
// little, the low-end portions of this check are less forgiving than the high-sides.
if (cTempo < 0.965f || cTempo > 1.060f ||
pctChange < -0.38f || pctChange > 0.54f ||
statusPct < -0.42f || statusPct > 0.70f ||
eTempo < 0.89f || eTempo > 1.19f)
{
//printf("Emergency stretch: cTempo = %f eTempo = %f pctChange = %f statusPct = %f\n",cTempo,eTempo,pctChange,statusPct);
emergencyAdj = (pow(statusPct * statusWeight, 3.0f) * statusRange);
}
// Smooth things out by factoring our previous adjustment into this one.
// It helps make the system 'feel' a little smarter by giving it at least
// one packet worth of history to help work off of:
emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f);
lastEmergencyAdj = emergencyAdj;
lastPct = statusPct;
// Accumulate a fraction of the tempo change into the tempo itself.
// This helps the system run "smarter" to games that run consistently
// fast or slow by altering the base tempo to something closer to the
// game's active speed. In tests most games normalize within 2 seconds
// at 100ms latency, which is pretty good (larger buffers normalize even
// quicker).
newcee += newcee * (tempoChange + emergencyAdj) * 0.03f;
// Apply tempoChange as a scale of cTempo. That way the effect is proportional
// to the current tempo. (otherwise tempos rate of change at the extremes would
// be too drastic)
float newTempo = newcee + (emergencyAdj * cTempo);
// ... and as a final optimization, only stretch if the new tempo is outside
// a nominal threshold. Keep this threshold check small, because it could
// cause some serious side effects otherwise. (enlarging the cTempo check above
// is usually better/safer)
if (newTempo < 0.970f || newTempo > 1.045f)
{
cTempo = (float)newcee;
if (newTempo < 0.10f)
newTempo = 0.10f;
else if (newTempo > 10.0f)
newTempo = 10.0f;
if (cTempo < 0.15f)
cTempo = 0.15f;
else if (cTempo > 7.5f)
cTempo = 7.5f;
pSoundTouch->setTempo(eTempo = (float)newTempo);
/*ConLog("* SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
//(relation < 0.0) ? "Normalize" : "",
(int)(tempoChange * 100.0 * 0.03),
(int)(emergencyAdj * 100.0),
(int)(cTempo * 100.0),
(int)(newTempo * 100.0),
(int)(statusPct * 100.0)
);*/
}
else
{
// Nominal operation -- turn off stretching.
// note: eTempo 'slides' toward 1.0 for smoother audio and better
// protection against spikes.
if (cTempo != 1.0f)
{
cTempo = 1.0f;
eTempo = (1.0f + eTempo) * 0.5f;
pSoundTouch->setTempo(eTempo);
}
else
{
if (eTempo != cTempo)
pSoundTouch->setTempo(eTempo = cTempo);
}
}
}
extern uint TickInterval;
void SndBuffer::UpdateTempoChangeAsyncMixing()
{
float statusPct = GetStatusPct();
lastPct = statusPct;
if (statusPct < -0.1f)
{
TickInterval -= 4;
if (statusPct < -0.3f)
TickInterval = 64;
if (TickInterval < 64)
TickInterval = 64;
//printf("-- %d, %f\n",TickInterval,statusPct);
}
else if (statusPct > 0.2f)
{
TickInterval += 1;
if (TickInterval >= 7000)
TickInterval = 7000;
//printf("++ %d, %f\n",TickInterval,statusPct);
}
else
TickInterval = 768;
}
void SndBuffer::timeStretchUnderrun()
{
gRequestStretcherReset++;
// timeStretcher failed it's job. We need to slow down the audio some.
cTempo -= (cTempo * 0.12f);
eTempo -= (eTempo * 0.30f);
if (eTempo < 0.1f)
eTempo = 0.1f;
// pSoundTouch->setTempo( eTempo );
//pSoundTouch->setTempoChange(-30); // temporary (until stretcher is called) slow down
}
s32 SndBuffer::timeStretchOverrun()
{
// If we overran it means the timestretcher failed. We need to speed
// up audio playback.
cTempo += cTempo * 0.12f;
eTempo += eTempo * 0.40f;
if (eTempo > 7.5f)
eTempo = 7.5f;
//pSoundTouch->setTempo( eTempo );
//pSoundTouch->setTempoChange(30);// temporary (until stretcher is called) speed up
// Throw out just a little bit (two packets worth) to help
// give the TS some room to work:
gRequestStretcherReset++;
return SndOutPacketSize * 2;
}
static void CvtPacketToFloat(StereoOut32* srcdest)
{
StereoOutFloat* dest = (StereoOutFloat*)srcdest;
const StereoOut32* src = (StereoOut32*)srcdest;
for (uint i = 0; i < SndOutPacketSize; ++i, ++dest, ++src)
*dest = (StereoOutFloat)*src;
}
// Parameter note: Size should always be a multiple of 128, thanks!
static void CvtPacketToInt(StereoOut32* srcdest, uint size)
{
//pxAssume( (size & 127) == 0 );
const StereoOutFloat* src = (StereoOutFloat*)srcdest;
StereoOut32* dest = srcdest;
for (uint i = 0; i < size; ++i, ++dest, ++src)
*dest = (StereoOut32)*src;
}
void SndBuffer::timeStretchWrite()
{
// data prediction helps keep the tempo adjustments more accurate.
// The timestretcher returns packets in belated "clump" form.
// Meaning that most of the time we'll get nothing back, and then
// suddenly we'll get several chunks back at once. Thus we use
// data prediction to make the timestretcher more responsive.
PredictDataWrite((int)(SndOutPacketSize / eTempo));
CvtPacketToFloat(sndTempBuffer);
pSoundTouch->putSamples((float*)sndTempBuffer, SndOutPacketSize);
int tempProgress;
while (tempProgress = pSoundTouch->receiveSamples((float*)sndTempBuffer, SndOutPacketSize),
tempProgress != 0)
{
// Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as
// long as the SSE optimizations are enabled), which means we can do our own SSE opts here.
CvtPacketToInt(sndTempBuffer, tempProgress);
_WriteSamples(sndTempBuffer, tempProgress);
}
#ifdef SPU2X_USE_OLD_STRETCHER
UpdateTempoChangeSoundTouch();
#else
UpdateTempoChangeSoundTouch2();
#endif
}
void SndBuffer::soundtouchInit()
{
pSoundTouch = std::make_unique<soundtouch::SoundTouch>();
pSoundTouch->setSampleRate(SampleRate);
pSoundTouch->setChannels(2);
pSoundTouch->setSetting(SETTING_USE_QUICKSEEK, 0);
pSoundTouch->setSetting(SETTING_USE_AA_FILTER, 0);
pSoundTouch->setSetting(SETTING_SEQUENCE_MS, EmuConfig.SPU2.SequenceLenMS);
pSoundTouch->setSetting(SETTING_SEEKWINDOW_MS, EmuConfig.SPU2.SeekWindowMS);
pSoundTouch->setSetting(SETTING_OVERLAP_MS, EmuConfig.SPU2.OverlapMS);
pSoundTouch->setTempo(1);
// some timestretch management vars:
cTempo = 1.0;
eTempo = 1.0;
lastPct = 0;
lastEmergencyAdj = 0;
m_predictData = 0;
}
// reset timestretch management vars, and delay updates a bit:
void SndBuffer::soundtouchClearContents()
{
if (pSoundTouch == nullptr)
return;
pSoundTouch->clear();
pSoundTouch->setTempo(1);
cTempo = 1.0;
eTempo = 1.0;
lastPct = 0;
lastEmergencyAdj = 0;
m_predictData = 0;
}
void SndBuffer::soundtouchCleanup()
{
pSoundTouch.reset();
}

View File

@ -32,22 +32,10 @@ static const int SndOutVolumeShift32 = 16 - SndOutVolumeShift; // shift up, not
// is too problematic. :)
extern int SampleRate;
extern int FindOutputModuleById(const char* omodid);
// Returns a null-terminated list of backends for the specified module.
// nullptr is returned if the specified module does not have multiple backends.
extern const char* const* GetOutputModuleBackends(const char* omodid);
// Implemented in Config.cpp
extern float VolumeAdjustFL;
extern float VolumeAdjustC;
extern float VolumeAdjustFR;
extern float VolumeAdjustBL;
extern float VolumeAdjustBR;
extern float VolumeAdjustSL;
extern float VolumeAdjustSR;
extern float VolumeAdjustLFE;
struct Stereo51Out16DplII;
struct Stereo51Out32DplII;
@ -90,14 +78,6 @@ struct StereoOut16
// Use StereoOut32's built in conversion
*this = src.DownSample();
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s16)(Left * VolumeAdjustFL);
Right = (s16)(Right * VolumeAdjustFR);
}
};
struct StereoOutFloat
@ -142,15 +122,6 @@ struct Stereo21Out16
Right = src.Right >> SndOutVolumeShift;
LFE = (src.Left + src.Right) >> (SndOutVolumeShift + 1);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s16)(Left * VolumeAdjustFL);
Right = (s16)(Right * VolumeAdjustFR);
LFE = (s16)(LFE * VolumeAdjustLFE);
}
};
struct Stereo40Out16
@ -167,16 +138,6 @@ struct Stereo40Out16
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s16)(Left * VolumeAdjustFL);
Right = (s16)(Right * VolumeAdjustFR);
LeftBack = (s16)(LeftBack * VolumeAdjustBL);
RightBack = (s16)(RightBack * VolumeAdjustBR);
}
};
struct Stereo40Out32
@ -193,16 +154,6 @@ struct Stereo40Out32
LeftBack = src.Left << SndOutVolumeShift32;
RightBack = src.Right << SndOutVolumeShift32;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
}
};
struct Stereo41Out16
@ -221,17 +172,6 @@ struct Stereo41Out16
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo51Out16
@ -257,18 +197,6 @@ struct Stereo51Out16
LeftBack = src.Left >> SndOutVolumeShift;
RightBack = src.Right >> SndOutVolumeShift;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s16)(Left * VolumeAdjustFL);
Right = (s16)(Right * VolumeAdjustFR);
LeftBack = (s16)(LeftBack * VolumeAdjustBL);
RightBack = (s16)(RightBack * VolumeAdjustBR);
Center = (s16)(Center * VolumeAdjustC);
LFE = (s16)(LFE * VolumeAdjustLFE);
}
};
struct Stereo51Out16DplII
@ -284,18 +212,6 @@ struct Stereo51Out16DplII
{
ProcessDplIISample16(src, this);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
Center = (s32)(Center * VolumeAdjustC);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo51Out32DplII
@ -311,18 +227,6 @@ struct Stereo51Out32DplII
{
ProcessDplIISample32(src, this);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
Center = (s32)(Center * VolumeAdjustC);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo51Out16Dpl
@ -338,18 +242,6 @@ struct Stereo51Out16Dpl
{
ProcessDplSample16(src, this);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
Center = (s32)(Center * VolumeAdjustC);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo51Out32Dpl
@ -365,18 +257,6 @@ struct Stereo51Out32Dpl
{
ProcessDplSample32(src, this);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
Center = (s32)(Center * VolumeAdjustC);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo71Out16
@ -402,20 +282,6 @@ struct Stereo71Out16
LeftSide = src.Left >> (SndOutVolumeShift + 1);
RightSide = src.Right >> (SndOutVolumeShift + 1);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s16)(Left * VolumeAdjustFL);
Right = (s16)(Right * VolumeAdjustFR);
LeftBack = (s16)(LeftBack * VolumeAdjustBL);
RightBack = (s16)(RightBack * VolumeAdjustBR);
LeftSide = (s16)(LeftBack * VolumeAdjustSL);
RightSide = (s16)(RightBack * VolumeAdjustSR);
Center = (s16)(Center * VolumeAdjustC);
LFE = (s16)(LFE * VolumeAdjustLFE);
}
};
struct Stereo71Out32
@ -441,20 +307,6 @@ struct Stereo71Out32
LeftSide = src.Left << (SndOutVolumeShift32 - 1);
RightSide = src.Right << (SndOutVolumeShift32 - 1);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
LeftSide = (s32)(LeftBack * VolumeAdjustSL);
RightSide = (s32)(RightBack * VolumeAdjustSR);
Center = (s32)(Center * VolumeAdjustC);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo20Out32
@ -467,14 +319,6 @@ struct Stereo20Out32
Left = src.Left << SndOutVolumeShift32;
Right = src.Right << SndOutVolumeShift32;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
}
};
struct Stereo21Out32
@ -489,15 +333,6 @@ struct Stereo21Out32
Right = src.Right << SndOutVolumeShift32;
LFE = (src.Left + src.Right) << (SndOutVolumeShift32 - 1);
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo41Out32
@ -517,17 +352,6 @@ struct Stereo41Out32
LeftBack = src.Left << SndOutVolumeShift32;
RightBack = src.Right << SndOutVolumeShift32;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
struct Stereo51Out32
@ -548,18 +372,6 @@ struct Stereo51Out32
LeftBack = src.Left << SndOutVolumeShift32;
RightBack = src.Right << SndOutVolumeShift32;
}
void AdjustFrom(const StereoOut32& src)
{
ResampleFrom(src);
Left = (s32)(Left * VolumeAdjustFL);
Right = (s32)(Right * VolumeAdjustFR);
LeftBack = (s32)(LeftBack * VolumeAdjustBL);
RightBack = (s32)(RightBack * VolumeAdjustBR);
Center = (s32)(Center * VolumeAdjustC);
LFE = (s32)(LFE * VolumeAdjustLFE);
}
};
// Developer Note: This is a static class only (all static members).
@ -617,11 +429,11 @@ private:
public:
static void UpdateTempoChangeAsyncMixing();
static bool Init();
static bool Init(const char* modname);
static void Cleanup();
static void Write(const StereoOut32& Sample);
static void ClearContents();
static void SetPaused(bool paused);
static void ResetBuffers();
// Note: When using with 32 bit output buffers, the user of this function is responsible
// for shifting the values to where they need to be manually. The fixed point depth of
@ -659,15 +471,6 @@ public:
virtual int GetEmptySampleCount() = 0;
};
extern SndOutModule* NullOut;
#ifdef _WIN32
extern SndOutModule* XAudio2Out;
#endif
#if defined(SPU2X_CUBEB)
extern SndOutModule* CubebOut;
#endif
extern SndOutModule* mods[];
// =====================================================================================================
extern bool WavRecordEnabled;

View File

@ -15,8 +15,9 @@
#include "PrecompiledHeader.h"
#include "Global.h"
#include "SndOut.h"
#include "SPU2/Global.h"
#include "SPU2/SndOut.h"
#include "Host.h"
#include "common/Console.h"
#include "common/StringUtil.h"
@ -150,7 +151,7 @@ public:
m_COMInitializedByUs = SUCCEEDED(hr);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
{
Console.Error("(Cubeb) Failed to initialize COM");
Host::ReportErrorAsync("Cubeb Error", "Failed to initialize COM");
return false;
}
#endif
@ -162,11 +163,11 @@ public:
int rv = cubeb_init(&m_context, "PCSX2", m_Backend.empty() ? nullptr : m_Backend.c_str());
if (rv != CUBEB_OK)
{
Console.Error("(Cubeb) Could not initialize cubeb context: %d", rv);
Host::ReportFormattedErrorAsync("Cubeb Error", "Could not initialize cubeb context: %d", rv);
return false;
}
switch (numSpeakers) // speakers = (numSpeakers + 1) *2; ?
switch (EmuConfig.SPU2.SpeakerConfiguration) // speakers = (numSpeakers + 1) *2; ?
{
case 0:
channels = 2;
@ -213,7 +214,7 @@ public:
case 6:
case 7:
switch (dplLevel)
switch (EmuConfig.SPU2.DplDecodingLevel)
{
case 0:
Console.WriteLn("(Cubeb) 5.1 speaker expansion enabled.");

View File

@ -240,7 +240,7 @@ public:
int speakers;
// speakers = (numSpeakers + 1) *2; ?
switch (numSpeakers)
switch (EmuConfig.SPU2.SpeakerConfiguration)
{
case 0: // Stereo
speakers = 2;
@ -270,41 +270,41 @@ public:
switch (speakers)
{
case 2:
ConLog("* SPU2 > Using normal 2 speaker stereo output.\n");
Console.WriteLn("* SPU2 > Using normal 2 speaker stereo output.");
m_voiceContext = std::make_unique<StreamingVoice<StereoOut16>>();
break;
case 3:
ConLog("* SPU2 > 2.1 speaker expansion enabled.\n");
Console.WriteLn("* SPU2 > 2.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo21Out16>>();
break;
case 4:
ConLog("* SPU2 > 4 speaker expansion enabled [quadraphenia]\n");
Console.WriteLn("* SPU2 > 4 speaker expansion enabled [quadraphenia]");
m_voiceContext = std::make_unique<StreamingVoice<Stereo40Out16>>();
break;
case 5:
ConLog("* SPU2 > 4.1 speaker expansion enabled.\n");
Console.WriteLn("* SPU2 > 4.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo41Out16>>();
break;
case 6:
case 7:
switch (dplLevel)
switch (EmuConfig.SPU2.DplDecodingLevel)
{
case 0: // "normal" stereo upmix
ConLog("* SPU2 > 5.1 speaker expansion enabled.\n");
Console.WriteLn("* SPU2 > 5.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16>>();
break;
case 1: // basic Dpl decoder without rear stereo balancing
ConLog("* SPU2 > 5.1 speaker expansion with basic ProLogic dematrixing enabled.\n");
Console.WriteLn("* SPU2 > 5.1 speaker expansion with basic ProLogic dematrixing enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16Dpl>>();
break;
case 2: // gigas PLII
ConLog("* SPU2 > 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.\n");
Console.WriteLn("* SPU2 > 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16DplII>>();
break;
}
break;
default: // anything 8 or more gets the 7.1 treatment!
ConLog("* SPU2 > 7.1 speaker expansion enabled.\n");
Console.WriteLn("* SPU2 > 7.1 speaker expansion enabled.");
m_voiceContext = std::make_unique<StreamingVoice<Stereo51Out16>>();
break;
}

View File

@ -1,562 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Global.h"
#include "SoundTouch.h"
#include "common/Timer.h"
#include <algorithm>
#include <cmath>
//Uncomment the next line to use the old time stretcher
//#define SPU2X_USE_OLD_STRETCHER
static std::unique_ptr<soundtouch::SoundTouch> pSoundTouch = nullptr;
// data prediction amount, used to "commit" data that hasn't
// finished timestretch processing.
s32 SndBuffer::m_predictData;
// records last buffer status (fill %, range -100 to 100, with 0 being 50% full)
float SndBuffer::lastPct;
float SndBuffer::lastEmergencyAdj;
float SndBuffer::cTempo = 1;
float SndBuffer::eTempo = 1;
void SndBuffer::PredictDataWrite(int samples)
{
m_predictData += samples;
}
// Calculate the buffer status percentage.
// Returns range from -1.0 to 1.0
// 1.0 = buffer overflow!
// 0.0 = buffer nominal (50% full)
// -1.0 = buffer underflow!
float SndBuffer::GetStatusPct()
{
// Get the buffer status of the output driver too, so that we can
// obtain a more accurate overall buffer status.
int drvempty = mods[OutputModule]->GetEmptySampleCount(); // / 2;
//ConLog( "Data %d >>> driver: %d predict: %d\n", m_data, drvempty, m_predictData );
int data = _GetApproximateDataInBuffer();
float result = (float)(data + m_predictData - drvempty) - (m_size / 16);
result /= (m_size / 16);
return result;
}
//Alternative simple tempo adjustment. Based only on the soundtouch buffer state.
//Base algorithm: aim at specific average number of samples at the buffer (by GUI), and adjust tempo simply by current/target.
//An extra mechanism is added to keep adjustment at perfect 1:1 ratio (when emulation speed is stable around 100%)
// to prevent constant stretching/shrinking of packets if possible.
// This mechanism is triggered when the adjustment is close to 1:1 for long enough (defaults to 100 iterations within hys_ok_factor - defaults to 3%).
// 1:1 state is aborted when required adjustment goes beyond hys_bad_factor (defaults to 20%).
//
//To compensate for wide variation of the <num of samples> ratio due to relatively small size of the buffer,
// The required tempo is a running average of STRETCH_AVERAGE_LEN (defaults to 50) last calculations.
// This averaging slows down the respons time of the algorithm, but greatly stablize it towards steady stretching.
//
//Keeping the buffer at required latency:
// This algorithm stabilises when the actual latency is <speed>*<required_latency>. While this is just fine at 100% speed,
// it's problematic especially for slow speeds, as the number of actual samples at the buffer gets very small on that case,
// which may lead to underruns (or just too much latency when running very fast).
//To compensate for that, the algorithm has a slowly moving compensation factor which will eventually bring the actual latency to the required one.
//compensationDivider defines how slow this compensation changes. By default it's set to 100,
// which will finalize the compensation after about 200 iterations.
//
// Note, this algorithm is intentionally simplified by not taking extreme actions at extreme scenarios (mostly underruns when speed drops sharply),
// and let's the overrun/underrun protections do what they should (doesn't happen much though in practice, even at big FPS variations).
//
// These params were tested to show good respond and stability, on all audio systems (dsound, wav, port audio, xaudio2),
// even at extreme small latency of 50ms which can handle 50%-100% variations without audible glitches.
int targetIPS = 750;
//Dynamic tuning changes the values of the base algorithm parameters (derived from targetIPS) to adapt, in real time, to
// different number of invocations/sec (mostly affects number of iterations to average).
// Dynamic tuning can have a slight negative effect on the behavior of the algorithm, so it's preferred to have it off.
//Currently it looks like it's around 750/sec on all systems when playing at 100% speed (50/60fps),
// and proportional to that speed otherwise.
//If changes are made to SPU2 which affects this number (but it's still the same on all systems), then just change targetIPS.
//If we find out that some systems are very different, we can turn on dynamic tuning by uncommenting the next line.
//#define NEWSTRETCHER_USE_DYNAMIC_TUNING
//Additional performance note: since MAX_STRETCH_AVERAGE_LEN = 128 (or any power of 2), the '%' below
//could be replaced with a faster '&'. The compiler is highly likely to do it since all the values are unsigned.
#define AVERAGING_BUFFER_SIZE 256U
unsigned int AVERAGING_WINDOW = 50.0 * targetIPS / 750;
#define STRETCHER_RESET_THRESHOLD 5
int gRequestStretcherReset = STRETCHER_RESET_THRESHOLD;
//Adds a value to the running average buffer, and return the new running average.
float addToAvg(float val)
{
static float avg_fullness[AVERAGING_BUFFER_SIZE];
static unsigned int nextAvgPos = 0;
static unsigned int available = 0; // Make sure we're not averaging AVERAGING_WINDOW items if we inserted less.
if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
available = 0;
if (available < AVERAGING_BUFFER_SIZE)
available++;
avg_fullness[nextAvgPos] = val;
nextAvgPos = (nextAvgPos + 1U) % AVERAGING_BUFFER_SIZE;
unsigned int actualWindow = std::min(available, AVERAGING_WINDOW);
unsigned int first = (nextAvgPos - actualWindow + AVERAGING_BUFFER_SIZE) % AVERAGING_BUFFER_SIZE;
// Possible optimization: if we know that actualWindow hasn't changed since
// last invocation, we could calculate the running average in O(1) instead of O(N)
// by keeping a running sum between invocations, and then
// do "runningSum = runningSum + val - avg_fullness[(first-1)%...]" instead of the following loop.
// Few gotchas: val overwrites first-1, handling actualWindow changes, etc.
// However, this isn't hot code, so unless proven otherwise, we can live with unoptimized code.
float sum = 0;
for (unsigned int i = first; i < first + actualWindow; i++)
{
sum += avg_fullness[i % AVERAGING_BUFFER_SIZE];
}
sum = sum / actualWindow;
return sum ? sum : 1; // 1 because that's the 100% perfect speed value
}
template <class T>
bool IsInRange(const T& val, const T& min, const T& max)
{
return (min <= val && val <= max);
}
//actual stretch algorithm implementation
void SndBuffer::UpdateTempoChangeSoundTouch2()
{
long targetSamplesReservoir = 48 * SndOutLatencyMS; //48000*SndOutLatencyMS/1000
//base aim at buffer filled %
float baseTargetFullness = (double)targetSamplesReservoir; ///(double)m_size;//0.05;
//state vars
static bool inside_hysteresis; //=false;
static int hys_ok_count; //=0;
static float dynamicTargetFullness; //=baseTargetFullness;
if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
{
ConLog("______> stretch: Reset.\n");
inside_hysteresis = false;
hys_ok_count = 0;
dynamicTargetFullness = baseTargetFullness;
}
int data = _GetApproximateDataInBuffer();
float bufferFullness = (float)data; ///(float)m_size;
#ifdef NEWSTRETCHER_USE_DYNAMIC_TUNING
{ //test current iterations/sec every 0.5s, and change algo params accordingly if different than previous IPS more than 30%
static long iters = 0;
static wxDateTime last = wxDateTime::UNow();
wxDateTime unow = wxDateTime::UNow();
wxTimeSpan delta = unow.Subtract(last);
if (delta.GetMilliseconds() > 500)
{
int pot_targetIPS = 1000.0 / delta.GetMilliseconds().ToDouble() * iters;
if (!IsInRange(pot_targetIPS, int((float)targetIPS / 1.3f), int((float)targetIPS * 1.3f)))
{
if (MsgOverruns())
ConLog("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
targetIPS = pot_targetIPS;
AVERAGING_WINDOW = GetClamped((int)(50.0f * (float)targetIPS / 750.0f), 3, (int)AVERAGING_BUFFER_SIZE);
}
last = unow;
iters = 0;
}
iters++;
}
#endif
//Algorithm params: (threshold params (hysteresis), etc)
const float hys_ok_factor = 1.04f;
const float hys_bad_factor = 1.2f;
int hys_min_ok_count = GetClamped((int)(50.0 * (float)targetIPS / 750.0), 2, 100); //consecutive iterations within hys_ok before going to 1:1 mode
int compensationDivider = GetClamped((int)(100.0 * (float)targetIPS / 750), 15, 150);
float tempoAdjust = bufferFullness / dynamicTargetFullness;
float avgerage = addToAvg(tempoAdjust);
tempoAdjust = avgerage;
// Dampen the adjustment to avoid overshoots (this means the average will compensate to the other side).
// This is different than simply bigger averaging window since bigger window also has bigger "momentum",
// so it's slower to slow down when it gets close to the equilibrium state and can therefore resonate.
// The dampening (sqrt was chosen for no very good reason) manages to mostly prevent that.
tempoAdjust = sqrt(tempoAdjust);
tempoAdjust = GetClamped(tempoAdjust, 0.05f, 10.0f);
if (tempoAdjust < 1)
baseTargetFullness /= sqrt(tempoAdjust); // slightly increase latency when running slow.
dynamicTargetFullness += (baseTargetFullness / tempoAdjust - dynamicTargetFullness) / (double)compensationDivider;
if (IsInRange(tempoAdjust, 0.9f, 1.1f) && IsInRange(dynamicTargetFullness, baseTargetFullness * 0.9f, baseTargetFullness * 1.1f))
dynamicTargetFullness = baseTargetFullness;
if (!inside_hysteresis)
{
if (IsInRange(tempoAdjust, 1.0f / hys_ok_factor, hys_ok_factor))
hys_ok_count++;
else
hys_ok_count = 0;
if (hys_ok_count >= hys_min_ok_count)
{
inside_hysteresis = true;
if (MsgOverruns())
ConLog("======> stretch: None (1:1)\n");
}
}
else if (!IsInRange(tempoAdjust, 1.0f / hys_bad_factor, hys_bad_factor))
{
if (MsgOverruns())
ConLog("~~~~~~> stretch: Dynamic\n");
inside_hysteresis = false;
hys_ok_count = 0;
}
if (inside_hysteresis)
tempoAdjust = 1.0;
if (MsgOverruns())
{
static int iters = 0;
static u64 last = 0;
const u64 now = Common::Timer::GetCurrentValue();
if (Common::Timer::ConvertValueToSeconds(now - last) > 1.0f)
{ //report buffers state and tempo adjust every second
ConLog("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d) reset:%d\n",
(int)(data / 48), (double)(100.0 * bufferFullness / baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness / baseTargetFullness), iters, (int)targetIPS, AVERAGING_WINDOW, hys_min_ok_count, compensationDivider, gRequestStretcherReset);
last = now;
iters = 0;
}
iters++;
}
pSoundTouch->setTempo(tempoAdjust);
if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
gRequestStretcherReset = 0;
return;
}
void SndBuffer::UpdateTempoChangeSoundTouch()
{
float statusPct = GetStatusPct();
float pctChange = statusPct - lastPct;
float tempoChange;
float emergencyAdj = 0;
float newcee = cTempo; // workspace var. for cTempo
// IMPORTANT!
// If you plan to tweak these values, make sure you're using a release build
// OUTSIDE THE DEBUGGER to test it! The Visual Studio debugger can really cause
// erratic behavior in the audio buffers, and makes the timestretcher seem a
// lot more inconsistent than it really is.
// We have two factors.
// * Distance from nominal buffer status (50% full)
// * The change from previous update to this update.
// Prediction based on the buffer change:
// (linear seems to work better here)
tempoChange = pctChange * 0.75f;
if (statusPct * tempoChange < 0.0f)
{
// only apply tempo change if it is in synch with the buffer status.
// In other words, if the buffer is high (over 0%), and is decreasing,
// ignore it. It'll just muck things up.
tempoChange = 0;
}
// Sudden spikes in framerate can cause the nominal buffer status
// to go critical, in which case we have to enact an emergency
// stretch. The following cubic formulas do that. Values near
// the extremeites give much larger results than those near 0.
// And the value is added only this time, and does not accumulate.
// (otherwise a large value like this would cause problems down the road)
// Constants:
// Weight - weights the statusPct's "emergency" consideration.
// higher values here will make the buffer perform more drastic
// compensations at the outer edges of the buffer (at -75 or +75%
// or beyond, for example).
// Range - scales the adjustment to the given range (more or less).
// The actual range is dependent on the weight used, so if you increase
// Weight you'll usually want to decrease Range somewhat to compensate.
// Prediction based on the buffer fill status:
const float statusWeight = 2.99f;
const float statusRange = 0.068f;
// "non-emergency" deadzone: In this area stretching will be strongly discouraged.
// Note: due tot he nature of timestretch latency, it's always a wee bit harder to
// cope with low fps (underruns) than it is high fps (overruns). So to help out a
// little, the low-end portions of this check are less forgiving than the high-sides.
if (cTempo < 0.965f || cTempo > 1.060f ||
pctChange < -0.38f || pctChange > 0.54f ||
statusPct < -0.42f || statusPct > 0.70f ||
eTempo < 0.89f || eTempo > 1.19f)
{
//printf("Emergency stretch: cTempo = %f eTempo = %f pctChange = %f statusPct = %f\n",cTempo,eTempo,pctChange,statusPct);
emergencyAdj = (pow(statusPct * statusWeight, 3.0f) * statusRange);
}
// Smooth things out by factoring our previous adjustment into this one.
// It helps make the system 'feel' a little smarter by giving it at least
// one packet worth of history to help work off of:
emergencyAdj = (emergencyAdj * 0.75f) + (lastEmergencyAdj * 0.25f);
lastEmergencyAdj = emergencyAdj;
lastPct = statusPct;
// Accumulate a fraction of the tempo change into the tempo itself.
// This helps the system run "smarter" to games that run consistently
// fast or slow by altering the base tempo to something closer to the
// game's active speed. In tests most games normalize within 2 seconds
// at 100ms latency, which is pretty good (larger buffers normalize even
// quicker).
newcee += newcee * (tempoChange + emergencyAdj) * 0.03f;
// Apply tempoChange as a scale of cTempo. That way the effect is proportional
// to the current tempo. (otherwise tempos rate of change at the extremes would
// be too drastic)
float newTempo = newcee + (emergencyAdj * cTempo);
// ... and as a final optimization, only stretch if the new tempo is outside
// a nominal threshold. Keep this threshold check small, because it could
// cause some serious side effects otherwise. (enlarging the cTempo check above
// is usually better/safer)
if (newTempo < 0.970f || newTempo > 1.045f)
{
cTempo = (float)newcee;
if (newTempo < 0.10f)
newTempo = 0.10f;
else if (newTempo > 10.0f)
newTempo = 10.0f;
if (cTempo < 0.15f)
cTempo = 0.15f;
else if (cTempo > 7.5f)
cTempo = 7.5f;
pSoundTouch->setTempo(eTempo = (float)newTempo);
/*ConLog("* SPU2: [Nominal %d%%] [Emergency: %d%%] (baseTempo: %d%% ) (newTempo: %d%%) (buffer: %d%%)\n",
//(relation < 0.0) ? "Normalize" : "",
(int)(tempoChange * 100.0 * 0.03),
(int)(emergencyAdj * 100.0),
(int)(cTempo * 100.0),
(int)(newTempo * 100.0),
(int)(statusPct * 100.0)
);*/
}
else
{
// Nominal operation -- turn off stretching.
// note: eTempo 'slides' toward 1.0 for smoother audio and better
// protection against spikes.
if (cTempo != 1.0f)
{
cTempo = 1.0f;
eTempo = (1.0f + eTempo) * 0.5f;
pSoundTouch->setTempo(eTempo);
}
else
{
if (eTempo != cTempo)
pSoundTouch->setTempo(eTempo = cTempo);
}
}
}
extern uint TickInterval;
void SndBuffer::UpdateTempoChangeAsyncMixing()
{
float statusPct = GetStatusPct();
lastPct = statusPct;
if (statusPct < -0.1f)
{
TickInterval -= 4;
if (statusPct < -0.3f)
TickInterval = 64;
if (TickInterval < 64)
TickInterval = 64;
//printf("-- %d, %f\n",TickInterval,statusPct);
}
else if (statusPct > 0.2f)
{
TickInterval += 1;
if (TickInterval >= 7000)
TickInterval = 7000;
//printf("++ %d, %f\n",TickInterval,statusPct);
}
else
TickInterval = 768;
}
void SndBuffer::timeStretchUnderrun()
{
gRequestStretcherReset++;
// timeStretcher failed it's job. We need to slow down the audio some.
cTempo -= (cTempo * 0.12f);
eTempo -= (eTempo * 0.30f);
if (eTempo < 0.1f)
eTempo = 0.1f;
// pSoundTouch->setTempo( eTempo );
//pSoundTouch->setTempoChange(-30); // temporary (until stretcher is called) slow down
}
s32 SndBuffer::timeStretchOverrun()
{
// If we overran it means the timestretcher failed. We need to speed
// up audio playback.
cTempo += cTempo * 0.12f;
eTempo += eTempo * 0.40f;
if (eTempo > 7.5f)
eTempo = 7.5f;
//pSoundTouch->setTempo( eTempo );
//pSoundTouch->setTempoChange(30);// temporary (until stretcher is called) speed up
// Throw out just a little bit (two packets worth) to help
// give the TS some room to work:
gRequestStretcherReset++;
return SndOutPacketSize * 2;
}
static void CvtPacketToFloat(StereoOut32* srcdest)
{
StereoOutFloat* dest = (StereoOutFloat*)srcdest;
const StereoOut32* src = (StereoOut32*)srcdest;
for (uint i = 0; i < SndOutPacketSize; ++i, ++dest, ++src)
*dest = (StereoOutFloat)*src;
}
// Parameter note: Size should always be a multiple of 128, thanks!
static void CvtPacketToInt(StereoOut32* srcdest, uint size)
{
//pxAssume( (size & 127) == 0 );
const StereoOutFloat* src = (StereoOutFloat*)srcdest;
StereoOut32* dest = srcdest;
for (uint i = 0; i < size; ++i, ++dest, ++src)
*dest = (StereoOut32)*src;
}
void SndBuffer::timeStretchWrite()
{
// data prediction helps keep the tempo adjustments more accurate.
// The timestretcher returns packets in belated "clump" form.
// Meaning that most of the time we'll get nothing back, and then
// suddenly we'll get several chunks back at once. Thus we use
// data prediction to make the timestretcher more responsive.
PredictDataWrite((int)(SndOutPacketSize / eTempo));
CvtPacketToFloat(sndTempBuffer);
pSoundTouch->putSamples((float*)sndTempBuffer, SndOutPacketSize);
int tempProgress;
while (tempProgress = pSoundTouch->receiveSamples((float*)sndTempBuffer, SndOutPacketSize),
tempProgress != 0)
{
// Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as
// long as the SSE optimizations are enabled), which means we can do our own SSE opts here.
CvtPacketToInt(sndTempBuffer, tempProgress);
_WriteSamples(sndTempBuffer, tempProgress);
}
#ifdef SPU2X_USE_OLD_STRETCHER
UpdateTempoChangeSoundTouch();
#else
UpdateTempoChangeSoundTouch2();
#endif
}
void SndBuffer::soundtouchInit()
{
pSoundTouch = std::make_unique<soundtouch::SoundTouch>();
pSoundTouch->setSampleRate(SampleRate);
pSoundTouch->setChannels(2);
pSoundTouch->setSetting(SETTING_USE_QUICKSEEK, 0);
pSoundTouch->setSetting(SETTING_USE_AA_FILTER, 0);
SoundtouchCfg::ApplySettings(*pSoundTouch);
pSoundTouch->setTempo(1);
// some timestretch management vars:
cTempo = 1.0;
eTempo = 1.0;
lastPct = 0;
lastEmergencyAdj = 0;
m_predictData = 0;
}
// reset timestretch management vars, and delay updates a bit:
void SndBuffer::soundtouchClearContents()
{
if (pSoundTouch == nullptr)
return;
pSoundTouch->clear();
pSoundTouch->setTempo(1);
cTempo = 1.0;
eTempo = 1.0;
lastPct = 0;
lastEmergencyAdj = 0;
m_predictData = 0;
}
void SndBuffer::soundtouchCleanup()
{
pSoundTouch.reset();
}

View File

@ -43,7 +43,7 @@ namespace WaveDump
{
if (!IsDevBuild)
return;
if (!WaveLog())
if (!SPU2::WaveLog())
return;
for (uint cidx = 0; cidx < 2; cidx++)

View File

@ -71,7 +71,10 @@ public:
void Update();
void RegSet(u16 src); // used to set the volume from a register source (16 bit signed)
#ifdef PCSX2_DEVBUILD
void DebugDump(FILE* dump, const char* title, const char* nameLR);
#endif
};
struct V_VolumeSlideLR
@ -95,7 +98,9 @@ public:
Right.Update();
}
#ifdef PCSX2_DEVBUILD
void DebugDump(FILE* dump, const char* title);
#endif
};
struct V_ADSR

View File

@ -19,13 +19,26 @@
#include "SPU2/Dma.h"
#include "R3000A.h"
static int ConsoleSampleRate = 48000;
namespace SPU2
{
static int GetConsoleSampleRate();
static void InitSndBuffer();
static void UpdateSampleRate();
static void InternalReset(bool psxmode);
}
static double s_device_sample_rate_multiplier = 1.0;
static bool s_psxmode = false;
int SampleRate = 48000;
static double DeviceSampleRateMultiplier = 1.0;
u32 lClocks = 0;
int SPU2::GetConsoleSampleRate()
{
return s_psxmode ? 44100 : 48000;
}
// --------------------------------------------------------------------------------------
// DMA 4/7 Callbacks from Core Emulator
// --------------------------------------------------------------------------------------
@ -35,7 +48,7 @@ void SPU2readDMA4Mem(u16* pMem, u32 size) // size now in 16bit units
{
TimeUpdate(psxRegs.cycle);
FileLog("[%10d] SPU2 readDMA4Mem size %x\n", Cycles, size << 1);
SPU2::FileLog("[%10d] SPU2 readDMA4Mem size %x\n", Cycles, size << 1);
Cores[0].DoDMAread(pMem, size);
}
@ -43,14 +56,14 @@ void SPU2writeDMA4Mem(u16* pMem, u32 size) // size now in 16bit units
{
TimeUpdate(psxRegs.cycle);
FileLog("[%10d] SPU2 writeDMA4Mem size %x at address %x\n", Cycles, size << 1, Cores[0].TSA);
SPU2::FileLog("[%10d] SPU2 writeDMA4Mem size %x at address %x\n", Cycles, size << 1, Cores[0].TSA);
Cores[0].DoDMAwrite(pMem, size);
}
void SPU2interruptDMA4()
{
FileLog("[%10d] SPU2 interruptDMA4\n", Cycles);
SPU2::FileLog("[%10d] SPU2 interruptDMA4\n", Cycles);
if (Cores[0].DmaMode)
Cores[0].Regs.STATX |= 0x80;
Cores[0].Regs.STATX &= ~0x400;
@ -59,7 +72,7 @@ void SPU2interruptDMA4()
void SPU2interruptDMA7()
{
FileLog("[%10d] SPU2 interruptDMA7\n", Cycles);
SPU2::FileLog("[%10d] SPU2 interruptDMA7\n", Cycles);
if (Cores[1].DmaMode)
Cores[1].Regs.STATX |= 0x80;
Cores[1].Regs.STATX &= ~0x400;
@ -70,7 +83,7 @@ void SPU2readDMA7Mem(u16* pMem, u32 size)
{
TimeUpdate(psxRegs.cycle);
FileLog("[%10d] SPU2 readDMA7Mem size %x\n", Cycles, size << 1);
SPU2::FileLog("[%10d] SPU2 readDMA7Mem size %x\n", Cycles, size << 1);
Cores[1].DoDMAread(pMem, size);
}
@ -78,51 +91,49 @@ void SPU2writeDMA7Mem(u16* pMem, u32 size)
{
TimeUpdate(psxRegs.cycle);
FileLog("[%10d] SPU2 writeDMA7Mem size %x at address %x\n", Cycles, size << 1, Cores[1].TSA);
SPU2::FileLog("[%10d] SPU2 writeDMA7Mem size %x at address %x\n", Cycles, size << 1, Cores[1].TSA);
Cores[1].DoDMAwrite(pMem, size);
}
static void SPU2InitSndBuffer()
void SPU2::InitSndBuffer()
{
Console.WriteLn("Initializing SndBuffer at sample rate of %u...", SampleRate);
if (SndBuffer::Init())
if (SndBuffer::Init(EmuConfig.SPU2.OutputModule.c_str()))
return;
if (SampleRate != ConsoleSampleRate)
if (SampleRate != GetConsoleSampleRate())
{
// TODO: Resample on our side...
// It'll get stretched instead..
const int original_sample_rate = SampleRate;
Console.Error("Failed to init SPU2 at adjusted sample rate %u, trying console rate.", SampleRate);
SampleRate = ConsoleSampleRate;
if (SndBuffer::Init())
SampleRate = GetConsoleSampleRate();
if (SndBuffer::Init(EmuConfig.SPU2.OutputModule.c_str()))
return;
SampleRate = original_sample_rate;
}
// just use nullout
OutputModule = FindOutputModuleById(NullOut->GetIdent());
if (!SndBuffer::Init())
if (!SndBuffer::Init("nullout"))
pxFailRel("Failed to initialize nullout.");
}
static void SPU2UpdateSampleRate()
void SPU2::UpdateSampleRate()
{
const int new_sample_rate = static_cast<int>(std::round(static_cast<double>(ConsoleSampleRate) * DeviceSampleRateMultiplier));
const int new_sample_rate = static_cast<int>(std::round(static_cast<double>(GetConsoleSampleRate()) * s_device_sample_rate_multiplier));
if (SampleRate == new_sample_rate)
return;
SndBuffer::Cleanup();
SampleRate = new_sample_rate;
SPU2InitSndBuffer();
InitSndBuffer();
}
static void SPU2InternalReset(PS2Modes isRunningPSXMode)
void SPU2::InternalReset(bool psxmode)
{
ConsoleSampleRate = (isRunningPSXMode == PS2Modes::PSX) ? 44100 : 48000;
if (isRunningPSXMode == PS2Modes::PS2)
s_psxmode = psxmode;
if (!s_psxmode)
{
memset(spu2regs, 0, 0x010000);
memset(_spu2mem, 0, 0x200000);
@ -136,22 +147,28 @@ static void SPU2InternalReset(PS2Modes isRunningPSXMode)
}
}
void SPU2reset(PS2Modes isRunningPSXMode)
void SPU2::Reset(bool psxmode)
{
SPU2InternalReset(isRunningPSXMode);
SPU2UpdateSampleRate();
InternalReset(psxmode);
UpdateSampleRate();
}
void SPU2SetDeviceSampleRateMultiplier(double multiplier)
void SPU2::OnTargetSpeedChanged()
{
if (DeviceSampleRateMultiplier == multiplier)
if (EmuConfig.SPU2.SynchMode != Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch)
SndBuffer::ResetBuffers();
}
void SPU2::SetDeviceSampleRateMultiplier(double multiplier)
{
if (s_device_sample_rate_multiplier == multiplier)
return;
DeviceSampleRateMultiplier = multiplier;
SPU2UpdateSampleRate();
s_device_sample_rate_multiplier = multiplier;
UpdateSampleRate();
}
bool SPU2init()
bool SPU2::Initialize()
{
pxAssert(regtable[0x400] == nullptr);
spu2regs = (s16*)malloc(0x010000);
@ -190,17 +207,11 @@ bool SPU2init()
}
bool SPU2open(PS2Modes isRunningPSXMode)
bool SPU2::Open()
{
ReadSettings();
#ifdef SPU2_LOG
if (AccessLog())
{
spu2Log = OpenLog(AccessLogFileName.c_str());
setvbuf(spu2Log, nullptr, _IONBF, 0);
FileLog("SPU2init\n");
}
#ifdef PCSX2_DEVBUILD
if (SPU2::AccessLog())
SPU2::OpenFileLog();
#endif
DMALogOpen();
@ -209,131 +220,45 @@ bool SPU2open(PS2Modes isRunningPSXMode)
lClocks = psxRegs.cycle;
SPU2InternalReset(isRunningPSXMode);
InternalReset(false);
try
{
SampleRate = static_cast<int>(std::round(static_cast<double>(ConsoleSampleRate) * DeviceSampleRateMultiplier));
SPU2InitSndBuffer();
WaveDump::Open();
}
catch (std::exception& ex)
{
Console.Error("SPU2 Error: Could not initialize device, or something.\nReason: %s", ex.what());
SPU2close();
return false;
}
SampleRate = static_cast<int>(std::round(static_cast<double>(GetConsoleSampleRate()) * s_device_sample_rate_multiplier));
InitSndBuffer();
WaveDump::Open();
return true;
}
void SPU2close()
void SPU2::Close()
{
FileLog("[%10d] SPU2 Close\n", Cycles);
SndBuffer::Cleanup();
}
void SPU2shutdown()
{
ConLog("* SPU2: Shutting down.\n");
SPU2close();
DoFullDump();
#ifdef STREAM_DUMP
fclose(il0);
fclose(il1);
#endif
#ifdef EFFECTS_DUMP
fclose(el0);
fclose(el1);
#endif
WaveDump::Close();
DMALogClose();
#ifdef PCSX2_DEVBUILD
DoFullDump();
CloseFileLog();
#endif
}
void SPU2::Shutdown()
{
safe_free(spu2regs);
safe_free(_spu2mem);
safe_free(pcm_cache_data);
#ifdef SPU2_LOG
if (!AccessLog())
return;
FileLog("[%10d] SPU2shutdown\n", Cycles);
if (spu2Log)
fclose(spu2Log);
#endif
}
bool SPU2IsRunningPSXMode()
bool SPU2::IsRunningPSXMode()
{
return (ConsoleSampleRate == 44100);
return s_psxmode;
}
void SPU2SetOutputPaused(bool paused)
{
SndBuffer::SetPaused(paused);
}
#ifdef DEBUG_KEYS
static u32 lastTicks;
static bool lState[6];
#endif
void SPU2async(u32 cycles)
{
TimeUpdate(psxRegs.cycle);
#ifdef DEBUG_KEYS
u32 curTicks = GetTickCount();
if ((curTicks - lastTicks) >= 50)
{
int oldI = Interpolation;
bool cState[6];
for (int i = 0; i < 6; i++)
{
cState[i] = !!(GetAsyncKeyState(VK_NUMPAD0 + i) & 0x8000);
if ((cState[i] && !lState[i]) && i != 5)
Interpolation = i;
lState[i] = cState[i];
}
if (Interpolation != oldI)
{
printf("Interpolation set to %d", Interpolation);
switch (Interpolation)
{
case 0:
printf(" - Nearest.\n");
break;
case 1:
printf(" - Linear.\n");
break;
case 2:
printf(" - Cubic.\n");
break;
case 3:
printf(" - Hermite.\n");
break;
case 4:
printf(" - Catmull-Rom.\n");
break;
case 5:
printf(" - Gaussian.\n");
break;
default:
printf(" (unknown).\n");
break;
}
}
lastTicks = curTicks;
}
#endif
}
u16 SPU2read(u32 rmem)
@ -370,7 +295,8 @@ u16 SPU2read(u32 rmem)
else if (mem >= 0x800)
{
ret = spu2Ru16(mem);
ConLog("* SPU2: Read from reg>=0x800: %x value %x\n", mem, ret);
if (SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: Read from reg>=0x800: %x value %x\n", mem, ret);
}
else
{
@ -450,3 +376,43 @@ s32 SPU2freeze(FreezeAction mode, freezeData* data)
// technically unreachable, but kills a warning:
return 0;
}
void SPU2::CheckForConfigChanges(const Pcsx2Config& old_config)
{
if (EmuConfig.SPU2 == old_config.SPU2)
return;
const Pcsx2Config::SPU2Options& opts = EmuConfig.SPU2;
const Pcsx2Config::SPU2Options& oldopts = old_config.SPU2;
// No need to reinit for volume change.
if (opts.FinalVolume != oldopts.FinalVolume)
SetOutputVolume(opts.FinalVolume);
// Wipe buffer out when changing sync mode, so e.g. TS->none doesn't have a huge delay.
if (opts.SynchMode != oldopts.SynchMode)
SndBuffer::ResetBuffers();
// Things which require re-initialzing the output.
if (opts.Latency != oldopts.Latency ||
opts.SpeakerConfiguration != oldopts.SpeakerConfiguration ||
opts.DplDecodingLevel != oldopts.DplDecodingLevel ||
opts.SequenceLenMS != oldopts.SequenceLenMS ||
opts.SeekWindowMS != oldopts.SeekWindowMS ||
opts.OverlapMS != oldopts.OverlapMS)
{
SndBuffer::Cleanup();
InitSndBuffer();
}
#ifdef PCSX2_DEVBUILD
// AccessLog controls file output.
if (opts.AccessLog != oldopts.AccessLog)
{
if (AccessLog())
OpenFileLog();
else
CloseFileLog();
}
#endif
}

View File

@ -19,20 +19,40 @@
#include "IopCounters.h"
#include <mutex>
enum class PS2Modes
{
PS2,
PSX,
};
struct Pcsx2Config;
namespace SPU2
{
/// Initialization/cleanup, call at process startup/shutdown.
bool Initialize();
void Shutdown();
/// Open/close, call at VM startup/shutdown.
bool Open();
void Close();
/// Reset, rebooting VM or going into PSX mode.
void Reset(bool psxmode);
/// Identifies any configuration changes and applies them.
void CheckForConfigChanges(const Pcsx2Config& old_config);
/// Directly updates the output volume without going through the configuration.
void SetOutputVolume(s32 volume);
/// Pauses/resumes the output stream.
void SetOutputPaused(bool paused);
/// Clears output buffers in no-sync mode, prevents long delays after fast forwarding.
void OnTargetSpeedChanged();
/// Adjusts the premultiplier on the output sample rate. Used for syncing to host refresh rate.
void SetDeviceSampleRateMultiplier(double multiplier);
/// Returns true if we're currently running in PSX mode.
bool IsRunningPSXMode();
} // namespace SPU2
bool SPU2init();
void SPU2reset(PS2Modes isRunningPSXMode = PS2Modes::PS2);
bool SPU2open(PS2Modes isRunningPSXMode = PS2Modes::PS2);
void SPU2close();
void SPU2shutdown();
bool SPU2IsRunningPSXMode();
void SPU2SetOutputPaused(bool paused);
void SPU2SetDeviceSampleRateMultiplier(double multiplier);
void SPU2write(u32 mem, u16 value);
u16 SPU2read(u32 mem);

View File

@ -43,10 +43,10 @@ u32 Cycles;
int PlayMode;
bool has_to_call_irq[2] = { false, false };
bool has_to_call_irq_dma[2] = { false, false };
static bool has_to_call_irq[2] = { false, false };
static bool has_to_call_irq_dma[2] = { false, false };
bool psxmode = false;
static bool psxmode = false;
void SetIrqCall(int core)
{
@ -90,8 +90,8 @@ __forceinline void spu2M_Write(u32 addr, s16 value)
const int cacheIdx = addr / pcm_WordsPerBlock;
pcm_cache_data[cacheIdx].Validated = false;
if (MsgToConsole() && MsgCache())
ConLog("* SPU2: PcmCache Block Clear at 0x%x (cacheIdx=0x%x)\n", addr, cacheIdx);
if (SPU2::MsgToConsole() && SPU2::MsgCache())
SPU2::ConLog("* SPU2: PcmCache Block Clear at 0x%x (cacheIdx=0x%x)\n", addr, cacheIdx);
}
*GetMemPtr(addr) = value;
}
@ -126,7 +126,9 @@ V_Core::~V_Core() throw()
void V_Core::Init(int index)
{
ConLog("* SPU2: Init SPU2 core %d \n", index);
if (SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: Init SPU2 core %d \n", index);
//memset(this, 0, sizeof(V_Core));
// Explicitly initializing variables instead.
Mute = false;
@ -229,36 +231,36 @@ void V_Core::Init(int index)
void V_Core::AnalyzeReverbPreset()
{
ConLog("Reverb Parameter Update for Core %d:\n", Index);
ConLog("----------------------------------------------------------\n");
SPU2::ConLog("Reverb Parameter Update for Core %d:\n", Index);
SPU2::ConLog("----------------------------------------------------------\n");
ConLog(" IN_COEF_L, IN_COEF_R 0x%08x, 0x%08x\n", Revb.IN_COEF_L, Revb.IN_COEF_R);
ConLog(" APF1_SIZE, APF2_SIZE 0x%08x, 0x%08x\n", Revb.APF1_SIZE, Revb.APF2_SIZE);
ConLog(" APF1_VOL, APF2_VOL 0x%08x, 0x%08x\n", Revb.APF1_VOL, Revb.APF2_VOL);
SPU2::ConLog(" IN_COEF_L, IN_COEF_R 0x%08x, 0x%08x\n", Revb.IN_COEF_L, Revb.IN_COEF_R);
SPU2::ConLog(" APF1_SIZE, APF2_SIZE 0x%08x, 0x%08x\n", Revb.APF1_SIZE, Revb.APF2_SIZE);
SPU2::ConLog(" APF1_VOL, APF2_VOL 0x%08x, 0x%08x\n", Revb.APF1_VOL, Revb.APF2_VOL);
ConLog(" COMB1_VOL 0x%08x\n", Revb.COMB1_VOL);
ConLog(" COMB2_VOL 0x%08x\n", Revb.COMB2_VOL);
ConLog(" COMB3_VOL 0x%08x\n", Revb.COMB3_VOL);
ConLog(" COMB4_VOL 0x%08x\n", Revb.COMB4_VOL);
SPU2::ConLog(" COMB1_VOL 0x%08x\n", Revb.COMB1_VOL);
SPU2::ConLog(" COMB2_VOL 0x%08x\n", Revb.COMB2_VOL);
SPU2::ConLog(" COMB3_VOL 0x%08x\n", Revb.COMB3_VOL);
SPU2::ConLog(" COMB4_VOL 0x%08x\n", Revb.COMB4_VOL);
ConLog(" COMB1_L_SRC, COMB1_R_SRC 0x%08x, 0x%08x\n", Revb.COMB1_L_SRC, Revb.COMB1_R_SRC);
ConLog(" COMB2_L_SRC, COMB2_R_SRC 0x%08x, 0x%08x\n", Revb.COMB2_L_SRC, Revb.COMB2_R_SRC);
ConLog(" COMB3_L_SRC, COMB3_R_SRC 0x%08x, 0x%08x\n", Revb.COMB3_L_SRC, Revb.COMB3_R_SRC);
ConLog(" COMB4_L_SRC, COMB4_R_SRC 0x%08x, 0x%08x\n", Revb.COMB4_L_SRC, Revb.COMB4_R_SRC);
SPU2::ConLog(" COMB1_L_SRC, COMB1_R_SRC 0x%08x, 0x%08x\n", Revb.COMB1_L_SRC, Revb.COMB1_R_SRC);
SPU2::ConLog(" COMB2_L_SRC, COMB2_R_SRC 0x%08x, 0x%08x\n", Revb.COMB2_L_SRC, Revb.COMB2_R_SRC);
SPU2::ConLog(" COMB3_L_SRC, COMB3_R_SRC 0x%08x, 0x%08x\n", Revb.COMB3_L_SRC, Revb.COMB3_R_SRC);
SPU2::ConLog(" COMB4_L_SRC, COMB4_R_SRC 0x%08x, 0x%08x\n", Revb.COMB4_L_SRC, Revb.COMB4_R_SRC);
ConLog(" SAME_L_SRC, SAME_R_SRC 0x%08x, 0x%08x\n", Revb.SAME_L_SRC, Revb.SAME_R_SRC);
ConLog(" DIFF_L_SRC, DIFF_R_SRC 0x%08x, 0x%08x\n", Revb.DIFF_L_SRC, Revb.DIFF_R_SRC);
ConLog(" SAME_L_DST, SAME_R_DST 0x%08x, 0x%08x\n", Revb.SAME_L_DST, Revb.SAME_R_DST);
ConLog(" DIFF_L_DST, DIFF_R_DST 0x%08x, 0x%08x\n", Revb.DIFF_L_DST, Revb.DIFF_R_DST);
ConLog(" IIR_VOL, WALL_VOL 0x%08x, 0x%08x\n", Revb.IIR_VOL, Revb.WALL_VOL);
SPU2::ConLog(" SAME_L_SRC, SAME_R_SRC 0x%08x, 0x%08x\n", Revb.SAME_L_SRC, Revb.SAME_R_SRC);
SPU2::ConLog(" DIFF_L_SRC, DIFF_R_SRC 0x%08x, 0x%08x\n", Revb.DIFF_L_SRC, Revb.DIFF_R_SRC);
SPU2::ConLog(" SAME_L_DST, SAME_R_DST 0x%08x, 0x%08x\n", Revb.SAME_L_DST, Revb.SAME_R_DST);
SPU2::ConLog(" DIFF_L_DST, DIFF_R_DST 0x%08x, 0x%08x\n", Revb.DIFF_L_DST, Revb.DIFF_R_DST);
SPU2::ConLog(" IIR_VOL, WALL_VOL 0x%08x, 0x%08x\n", Revb.IIR_VOL, Revb.WALL_VOL);
ConLog(" APF1_L_DST 0x%08x\n", Revb.APF1_L_DST);
ConLog(" APF1_R_DST 0x%08x\n", Revb.APF1_R_DST);
ConLog(" APF2_L_DST 0x%08x\n", Revb.APF2_L_DST);
ConLog(" APF2_R_DST 0x%08x\n", Revb.APF2_R_DST);
SPU2::ConLog(" APF1_L_DST 0x%08x\n", Revb.APF1_L_DST);
SPU2::ConLog(" APF1_R_DST 0x%08x\n", Revb.APF1_R_DST);
SPU2::ConLog(" APF2_L_DST 0x%08x\n", Revb.APF2_L_DST);
SPU2::ConLog(" APF2_R_DST 0x%08x\n", Revb.APF2_R_DST);
ConLog(" EffectsBufferSize 0x%x\n", EffectsBufferSize);
ConLog("----------------------------------------------------------\n");
SPU2::ConLog(" EffectsBufferSize 0x%x\n", EffectsBufferSize);
SPU2::ConLog("----------------------------------------------------------\n");
}
s32 V_Core::EffectsBufferIndexer(s32 offset) const
{
@ -288,7 +290,7 @@ void V_Core::UpdateEffectsBufferSize()
return;
// debug: shows reverb parameters in console
if (MsgToConsole())
if (SPU2::MsgToConsole())
AnalyzeReverbPreset();
// Rebuild buffer indexers.
@ -396,13 +398,13 @@ __forceinline void TimeUpdate(u32 cClocks)
if (dClocks > (u32)(TickInterval * SanityInterval))
{
if (MsgToConsole())
ConLog(" * SPU2 > TimeUpdate Sanity Check (Tick Delta: %d) (PS2 Ticks: %d)\n", dClocks / TickInterval, cClocks / TickInterval);
if (SPU2::MsgToConsole())
SPU2::ConLog(" * SPU2 > TimeUpdate Sanity Check (Tick Delta: %d) (PS2 Ticks: %d)\n", dClocks / TickInterval, cClocks / TickInterval);
dClocks = TickInterval * SanityInterval;
lClocks = cClocks - dClocks;
}
if (SynchMode == 1) // AsyncMix on
if (EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::ASync)
SndBuffer::UpdateTempoChangeAsyncMixing();
else
TickInterval = 768; // Reset to default, in case the user hotswitched from async to something else.
@ -552,10 +554,10 @@ __forceinline void UpdateSpdifMode()
{
int OPM = PlayMode;
if (Spdif.Out & 0x4) // use 24/32bit PCM data streaming
if (Spdif.Out & 0x4 && SPU2::MsgToConsole()) // use 24/32bit PCM data streaming
{
PlayMode = 8;
ConLog("* SPU2: WARNING: Possibly CDDA mode set!\n");
SPU2::ConLog("* SPU2: WARNING: Possibly CDDA mode set!\n");
return;
}
@ -573,9 +575,9 @@ __forceinline void UpdateSpdifMode()
PlayMode = 1;
}
}
if (OPM != PlayMode)
if (OPM != PlayMode && SPU2::MsgToConsole())
{
ConLog("* SPU2: Play Mode Set to %s (%d).\n",
SPU2::ConLog("* SPU2: Play Mode Set to %s (%d).\n",
(PlayMode == 0) ? "Normal" : ((PlayMode == 1) ? "PCM Clone" : ((PlayMode == 2) ? "PCM Bypass" : "BitStream Bypass")), PlayMode);
}
}
@ -628,8 +630,8 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
thisvol.Mode = (value & 0xF000) >> 12;
thisvol.Increment = (value & 0x7F);
// We're not sure slides work 100%
if (IsDevBuild)
ConLog("* SPU2: Voice uses Slides in Mode = %x, Increment = %x\n", thisvol.Mode, thisvol.Increment);
if (SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: Voice uses Slides in Mode = %x, Increment = %x\n", thisvol.Mode, thisvol.Increment);
}
else
{
@ -665,7 +667,8 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
case 0xc: // Voice 0..23 ADSR Current Volume
// not commonly set by games
Voices[voice].ADSR.Value = value * 0x10001U;
ConLog("voice %x ADSR.Value write: %x\n", voice, Voices[voice].ADSR.Value);
if (SPU2::MsgToConsole())
SPU2::ConLog("voice %x ADSR.Value write: %x\n", voice, Voices[voice].ADSR.Value);
break;
case 0xe:
Voices[voice].LoopStartA = map_spu1to2(value);
@ -713,27 +716,27 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
case 0x1d90: // Channel FM (pitch lfo) mode (0-15)
SPU2_FastWrite(REG_S_PMON, value);
if (value != 0)
ConLog("SPU2 warning: wants to set Pitch Modulation reg1 to %x \n", value);
if (value != 0 && SPU2::MsgToConsole())
SPU2::ConLog("SPU2 warning: wants to set Pitch Modulation reg1 to %x \n", value);
break;
case 0x1d92: // Channel FM (pitch lfo) mode (16-23)
SPU2_FastWrite(REG_S_PMON + 2, value);
if (value != 0)
ConLog("SPU2 warning: wants to set Pitch Modulation reg2 to %x \n", value);
if (value != 0 && SPU2::MsgToConsole())
SPU2::ConLog("SPU2 warning: wants to set Pitch Modulation reg2 to %x \n", value);
break;
case 0x1d94: // Channel Noise mode (0-15)
SPU2_FastWrite(REG_S_NON, value);
if (value != 0)
ConLog("SPU2 warning: wants to set Channel Noise mode reg1 to %x\n", value);
if (value != 0 && SPU2::MsgToConsole())
SPU2::ConLog("SPU2 warning: wants to set Channel Noise mode reg1 to %x\n", value);
break;
case 0x1d96: // Channel Noise mode (16-23)
SPU2_FastWrite(REG_S_NON + 2, value);
if (value != 0)
ConLog("SPU2 warning: wants to set Channel Noise mode reg2 to %x\n", value);
if (value != 0 && SPU2::MsgToConsole())
SPU2::ConLog("SPU2 warning: wants to set Channel Noise mode reg2 to %x\n", value);
break;
case 0x1d98: // 1F801D98h - Voice 0..23 Reverb mode aka Echo On (EON) (R/W)
@ -762,12 +765,14 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
//break;
case 0x1d9c: // Voice 0..15 ON/OFF (status) (ENDX) (R) // writeable but hw overrides it shortly after
//Regs.ENDX &= 0xff0000;
ConLog("SPU2 warning: wants to set ENDX reg1 to %x \n", value);
if (SPU2::MsgToConsole())
SPU2::ConLog("SPU2 warning: wants to set ENDX reg1 to %x \n", value);
break;
case 0x1d9e: // // Voice 15..23 ON/OFF (status) (ENDX) (R) // writeable but hw overrides it shortly after
//Regs.ENDX &= 0xffff;
ConLog("SPU2 warning: wants to set ENDX reg2 to %x \n", value);
if (SPU2::MsgToConsole())
SPU2::ConLog("SPU2 warning: wants to set ENDX reg2 to %x \n", value);
break;
case 0x1da2: // Reverb work area start
@ -786,7 +791,8 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
case 0x1da6:
TSA = map_spu1to2(value);
ConLog("SPU2 Setting TSA to %x \n", TSA);
if (SPU2::MsgToConsole())
SPU2::ConLog("SPU2 Setting TSA to %x \n", TSA);
break;
case 0x1da8: // Spu Write to Memory
@ -806,7 +812,8 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
break;
case 0x1dac: // 1F801DACh - Sound RAM Data Transfer Control (should be 0004h)
ConLog("SPU Sound RAM Data Transfer Control (should be 4) : value = %x \n", value);
if (SPU2::MsgToConsole())
SPU2::ConLog("SPU Sound RAM Data Transfer Control (should be 4) : value = %x \n", value);
psxSoundDataTransferControl = value;
break;
@ -932,7 +939,7 @@ void V_Core::WriteRegPS1(u32 mem, u16 value)
}
if (show)
FileLog("[%10d] (!) SPU write mem %08x value %04x\n", Cycles, mem, value);
SPU2::FileLog("[%10d] (!) SPU write mem %08x value %04x\n", Cycles, mem, value);
spu2Ru16(mem) = value;
}
@ -1078,7 +1085,7 @@ u16 V_Core::ReadRegPS1(u32 mem)
}
if (show)
FileLog("[%10d] (!) SPU read mem %08x value %04x\n", Cycles, mem, value);
SPU2::FileLog("[%10d] (!) SPU read mem %08x value %04x\n", Cycles, mem, value);
return value;
}
@ -1127,8 +1134,8 @@ static void RegWrite_VoiceParams(u16 value)
thisvol.Mode = (value & 0xF000) >> 12;
thisvol.Increment = (value & 0x7F);
// We're not sure slides work 100%
if (IsDevBuild)
ConLog("* SPU2: Voice uses Slides in Mode = %x, Increment = %x\n", thisvol.Mode, thisvol.Increment);
if (SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: Voice uses Slides in Mode = %x, Increment = %x\n", thisvol.Mode, thisvol.Increment);
}
else
{
@ -1323,14 +1330,14 @@ static void RegWrite_Core(u16 value)
if (value & 0x000E)
{
if (MsgToConsole())
ConLog("* SPU2: Core %d ATTR unknown bits SET! value=%04x\n", core, value);
if (SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: Core %d ATTR unknown bits SET! value=%04x\n", core, value);
}
if (thiscore.AttrBit0 != bit0)
{
if (MsgToConsole())
ConLog("* SPU2: ATTR bit 0 set to %d\n", thiscore.AttrBit0);
if (SPU2::MsgToConsole())
SPU2::ConLog("* SPU2: ATTR bit 0 set to %d\n", thiscore.AttrBit0);
}
if (thiscore.IRQEnable != irqe)
{
@ -1513,11 +1520,13 @@ static void RegWrite_Core(u16 value)
break;
case REG_S_ADMAS:
if (MsgToConsole())
ConLog("* SPU2: Core %d AutoDMAControl set to %d (at cycle %d)\n", core, value, Cycles);
if (SPU2::MsgToConsole())
{
SPU2::ConLog("* SPU2: Core %d AutoDMAControl set to %d (at cycle %d)\n", core, value, Cycles);
if (psxmode)
ConLog("* SPU2: Writing to REG_S_ADMAS while in PSX mode! value: %x", value);
if (psxmode)
SPU2::ConLog("* SPU2: Writing to REG_S_ADMAS while in PSX mode! value: %x", value);
}
// hack for ps1driver which writes -1 (and never turns the adma off after psxlogo).
// adma isn't available in psx mode either
if (value == 32767)
@ -1966,7 +1975,8 @@ void StartVoices(int core, u32 value)
if (value == 0)
return;
ConLog("KeyOn Write %x\n", value);
if (SPU2::MsgToConsole())
SPU2::ConLog("KeyOn Write %x\n", value);
Cores[core].KeyOn |= value;
Cores[core].Regs.ENDX &= ~value;
@ -1978,7 +1988,8 @@ void StartVoices(int core, u32 value)
if ((Cycles - Cores[core].Voices[vc].PlayCycle) < 2)
{
ConLog("Attempt to start voice %d on core %d in less than 2T since last KeyOn\n", vc, core);
if (SPU2::MsgToConsole())
SPU2::ConLog("Attempt to start voice %d on core %d in less than 2T since last KeyOn\n", vc, core);
continue;
}
@ -1986,11 +1997,11 @@ void StartVoices(int core, u32 value)
if (IsDevBuild)
{
if (MsgKeyOnOff())
if (SPU2::MsgKeyOnOff())
{
V_Voice& thisvc(Cores[core].Voices[vc]);
ConLog("* SPU2: KeyOn: C%dV%02d: SSA: %8x; M: %s%s%s%s; H: %04x; P: %04x V: %04x/%04x; ADSR: %04x%04x\n",
SPU2::ConLog("* SPU2: KeyOn: C%dV%02d: SSA: %8x; M: %s%s%s%s; H: %04x; P: %04x V: %04x/%04x; ADSR: %04x%04x\n",
core, vc, thisvc.StartA,
(Cores[core].VoiceGates[vc].DryL) ? "+" : "-", (Cores[core].VoiceGates[vc].DryR) ? "+" : "-",
(Cores[core].VoiceGates[vc].WetL) ? "+" : "-", (Cores[core].VoiceGates[vc].WetR) ? "+" : "-",
@ -2008,7 +2019,8 @@ void StopVoices(int core, u32 value)
if (value == 0)
return;
ConLog("KeyOff Write %x\n", value);
if (SPU2::MsgToConsole())
SPU2::ConLog("KeyOff Write %x\n", value);
for (u8 vc = 0; vc < V_Core::NumVoices; vc++)
{
@ -2017,12 +2029,13 @@ void StopVoices(int core, u32 value)
if (Cycles - Cores[core].Voices[vc].PlayCycle < 2)
{
ConLog("Attempt to stop voice %d on core %d in less than 2T since KeyOn\n", vc, core);
if (SPU2::MsgToConsole())
SPU2::ConLog("Attempt to stop voice %d on core %d in less than 2T since KeyOn\n", vc, core);
continue;
}
Cores[core].Voices[vc].ADSR.Releasing = true;
if (MsgKeyOnOff())
ConLog("* SPU2: KeyOff: Core %d; Voice %d.\n", core, vc);
if (SPU2::MsgKeyOnOff())
SPU2::ConLog("* SPU2: KeyOff: Core %d; Voice %d.\n", core, vc);
}
}

View File

@ -354,7 +354,7 @@ static int SysState_MTGSFreeze(FreezeAction mode, freezeData* fP)
return sstate.retval;
}
static constexpr SysState_Component SPU2{ "SPU2", SPU2freeze };
static constexpr SysState_Component SPU2_{ "SPU2", SPU2freeze };
static constexpr SysState_Component PAD_{ "PAD", PADfreeze };
static constexpr SysState_Component GS{ "GS", SysState_MTGSFreeze };
@ -601,8 +601,8 @@ public:
virtual ~SavestateEntry_SPU2() = default;
const char* GetFilename() const { return "SPU2.bin"; }
void FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeIn(zf, SPU2); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, SPU2); }
void FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeIn(zf, SPU2_); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, SPU2_); }
bool IsRequired() const { return true; }
};

View File

@ -83,7 +83,6 @@ namespace VMManager
static void CheckForGSConfigChanges(const Pcsx2Config& old_config);
static void CheckForFramerateConfigChanges(const Pcsx2Config& old_config);
static void CheckForPatchConfigChanges(const Pcsx2Config& old_config);
static void CheckForSPU2ConfigChanges(const Pcsx2Config& old_config);
static void CheckForDEV9ConfigChanges(const Pcsx2Config& old_config);
static void CheckForMemoryCardConfigChanges(const Pcsx2Config& old_config);
static void EnforceAchievementsChallengeModeSettings();
@ -204,7 +203,7 @@ void VMManager::SetState(VMState state)
frameLimitReset();
}
SPU2SetOutputPaused(state == VMState::Paused);
SPU2::SetOutputPaused(state == VMState::Paused);
if (state == VMState::Paused)
Host::OnVMPaused();
else
@ -268,9 +267,9 @@ bool VMManager::Internal::InitializeGlobals()
return false;
}
if (!SPU2init())
if (!SPU2::Initialize())
{
Host::ReportErrorAsync("Error", "Failed to initialize SPU2 (SPU2init()).");
Host::ReportErrorAsync("Error", "Failed to initialize SPU2.");
return false;
}
@ -286,7 +285,7 @@ bool VMManager::Internal::InitializeGlobals()
void VMManager::Internal::ReleaseGlobals()
{
USBshutdown();
SPU2shutdown();
SPU2::Shutdown();
GSshutdown();
#ifdef _WIN32
@ -972,12 +971,12 @@ bool VMManager::Initialize(VMBootParameters boot_params)
};
Console.WriteLn("Opening SPU2...");
if (!SPU2open())
if (!SPU2::Open())
{
Host::ReportErrorAsync("Startup Error", "Failed to initialize SPU2.");
return false;
}
ScopedGuard close_spu2(&SPU2close);
ScopedGuard close_spu2(&SPU2::Close);
Console.WriteLn("Opening PAD...");
if (PADinit() != 0 || PADopen(g_host_display->GetWindowInfo()) != 0)
@ -1129,7 +1128,7 @@ void VMManager::Shutdown(bool save_resume_state)
R3000A::ioman::reset();
vtlb_Shutdown();
USBclose();
SPU2close();
SPU2::Close();
PADclose();
DEV9close();
DoCDVDclose();
@ -1445,6 +1444,7 @@ void VMManager::SetLimiterMode(LimiterModeType type)
EmuConfig.LimiterMode = type;
gsUpdateFrequency(EmuConfig);
SPU2::OnTargetSpeedChanged();
}
void VMManager::FrameAdvance(u32 num_frames /*= 1*/)
@ -1700,47 +1700,6 @@ void VMManager::CheckForPatchConfigChanges(const Pcsx2Config& old_config)
ReloadPatches(true, true);
}
void VMManager::CheckForSPU2ConfigChanges(const Pcsx2Config& old_config)
{
if (EmuConfig.SPU2 == old_config.SPU2)
return;
// TODO: Don't reinit on volume changes.
Console.WriteLn("Updating SPU2 configuration");
// kinda lazy, but until we move spu2 over...
freezeData fd = {};
if (SPU2freeze(FreezeAction::Size, &fd) != 0)
{
Console.Error("(CheckForSPU2ConfigChanges) Failed to get SPU2 freeze size");
return;
}
std::unique_ptr<u8[]> fd_data = std::make_unique<u8[]>(fd.size);
fd.data = fd_data.get();
if (SPU2freeze(FreezeAction::Save, &fd) != 0)
{
Console.Error("(CheckForSPU2ConfigChanges) Failed to freeze SPU2");
return;
}
const bool psxmode = SPU2IsRunningPSXMode();
SPU2close();
if (!SPU2open(psxmode ? PS2Modes::PSX : PS2Modes::PS2))
{
Console.Error("(CheckForSPU2ConfigChanges) Failed to reopen SPU2, we'll probably crash :(");
return;
}
if (SPU2freeze(FreezeAction::Load, &fd) != 0)
{
Console.Error("(CheckForSPU2ConfigChanges) Failed to unfreeze SPU2");
return;
}
}
void VMManager::CheckForDEV9ConfigChanges(const Pcsx2Config& old_config)
{
if (EmuConfig.DEV9 == old_config.DEV9)
@ -1808,7 +1767,7 @@ void VMManager::CheckForConfigChanges(const Pcsx2Config& old_config)
CheckForCPUConfigChanges(old_config);
CheckForFramerateConfigChanges(old_config);
CheckForPatchConfigChanges(old_config);
CheckForSPU2ConfigChanges(old_config);
SPU2::CheckForConfigChanges(old_config);
CheckForDEV9ConfigChanges(old_config);
CheckForMemoryCardConfigChanges(old_config);
USB::CheckForConfigChanges(old_config);

View File

@ -243,9 +243,6 @@
<ClCompile Include="Recording\InputRecordingFile.cpp" />
<ClCompile Include="Recording\PadData.cpp" />
<ClCompile Include="Recording\Utilities\InputRecordingLogger.cpp" />
<ClCompile Include="SPU2\Config.cpp" />
<ClCompile Include="SPU2\ConfigDebug.cpp" />
<ClCompile Include="SPU2\ConfigSoundTouch.cpp" />
<ClCompile Include="SPU2\Debug.cpp" />
<ClCompile Include="SPU2\Dma.cpp" />
<ClCompile Include="SPU2\DplIIdecoder.cpp" />
@ -254,7 +251,6 @@
<ClCompile Include="SPU2\SndOut_XAudio2.cpp" />
<ClCompile Include="SPU2\wavedump_wav.cpp" />
<ClCompile Include="SPU2\SndOut.cpp" />
<ClCompile Include="SPU2\Timestretcher.cpp" />
<ClCompile Include="SPU2\RegTable.cpp" />
<ClCompile Include="SPU2\spu2freeze.cpp" />
<ClCompile Include="SPU2\spu2sys.cpp" />
@ -599,7 +595,6 @@
<ClInclude Include="Recording\Utilities\InputRecordingLogger.h" />
<ClInclude Include="ShaderCacheVersion.h" />
<ClInclude Include="SioTypes.h" />
<ClInclude Include="SPU2\Config.h" />
<ClInclude Include="SPU2\Debug.h" />
<ClInclude Include="SPU2\Dma.h" />
<ClInclude Include="SPU2\Global.h" />

View File

@ -866,9 +866,6 @@
<ClCompile Include="SPU2\SndOut.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\Timestretcher.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\Wavedump_wav.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
@ -1398,15 +1395,6 @@
<ClCompile Include="USB\usb-pad\usb-pad-sdl-ff.cpp">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClCompile>
<ClCompile Include="SPU2\Config.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\ConfigDebug.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\ConfigSoundTouch.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="SPU2\Debug.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
@ -1781,9 +1769,6 @@
<ClInclude Include="SPU2\interpolate_table.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\Config.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="SPU2\defs.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>