mirror of https://github.com/PCSX2/pcsx2.git
SPU2: Namespace logging/debugging
This commit is contained in:
parent
339c483a4b
commit
fe1bebc12d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 //
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -43,7 +43,7 @@ namespace WaveDump
|
|||
{
|
||||
if (!IsDevBuild)
|
||||
return;
|
||||
if (!WaveLog())
|
||||
if (!SPU2::WaveLog())
|
||||
return;
|
||||
|
||||
for (uint cidx = 0; cidx < 2; cidx++)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue