Implement configurable Sample Rate in options
Key changes:
- Added "Sample Rate" to `ScreenOptionsGraphicsSound` with choices:
- "Default" (resolves to 44100 Hz for now)
- "44100 Hz"
- "48000 Hz"
- Created a new ConfOption "PreferredSampleRate" in `ScreenOptionsMasterPrefs.cpp` mapping UI choices to the integer values (0, 44100, 48000) for the existing `m_iSoundPreferredSampleRate` preference.
- Updated relevant sound drivers:
- DirectSound (RageSoundDriver_DSound_Software): Primary buffer now attempts to use the preferred rate. Secondary buffers already handled it correctly.
- WaveOut (RageSoundDriver_WaveOut): Already handled 0 as 44.1kHz correctly.
- PulseAudio (RageSoundDriver_PulseAudio): Ensured m_InitStream uses the constructor-initialized rate based on preference.
- AudioUnit (RageSoundDriver_AU): Already handled 0 as 44.1kHz and attempts to set hardware rate.
- OSS (RageSoundDriver_OSS): Updated to read and apply the preferred sample rate.
- WDMKS (RageSoundDriver_WDMKS): Updated to pass the resolved preferred rate to its stream opening logic.
- Null (RageSoundDriver_Null): Already handled 0 as 44.1kHz correctly.
- Updated `RageSoundReader_Chain` constructor to use the preferred system sample rate for its internal default.
- Added English localization for the new option title and explanation.
The implementation ensures that if the preference is 0 (Default), the system currently defaults to 44100 Hz across all drivers. This lays the groundwork for future dynamic hardware rate detection. A game restart is recommended for the new sample rate to take full effect.
This commit is contained in:
+1
-1
@@ -147,7 +147,7 @@ public:
|
||||
int SetPosition( int iFrame ) { return 1; }
|
||||
int Read( float *pBuf, int iFrames ) { return RageSoundReader::END_OF_FILE; }
|
||||
RageSoundReader *Copy() const { return new RageSoundReader_Silence; }
|
||||
int GetSampleRate() const { return 48000; }
|
||||
int GetSampleRate() const { return 44100; } // Hardcoded to 44100
|
||||
unsigned GetNumChannels() const { return 1; }
|
||||
int GetNextSourceFrame() const { return 0; }
|
||||
float GetStreamToSourceRatio() const { return 1.0f; }
|
||||
|
||||
@@ -133,9 +133,9 @@ float RageSoundManager::GetPlayLatency() const
|
||||
int RageSoundManager::GetDriverSampleRate() const
|
||||
{
|
||||
if( m_pDriver == nullptr )
|
||||
return 48000;
|
||||
return 44100; // Fallback if no driver is loaded
|
||||
|
||||
return m_pDriver->GetSampleRate();
|
||||
return m_pDriver->GetSampleRate(); // Returns the *actual* operating rate of the loaded driver
|
||||
}
|
||||
|
||||
/* If the given path is loaded, return a copy; otherwise return nullptr.
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
*/
|
||||
RageSoundReader_Chain::RageSoundReader_Chain()
|
||||
{
|
||||
m_iPreferredSampleRate = 48000;
|
||||
m_iPreferredSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if (m_iPreferredSampleRate == 0)
|
||||
m_iPreferredSampleRate = 44100; // Default if system default (0) is chosen
|
||||
|
||||
m_iActualSampleRate = -1;
|
||||
m_iChannels = 0;
|
||||
m_iCurrentFrame = 0;
|
||||
|
||||
@@ -695,6 +695,12 @@ static void SoundVolumeAttract( int &sel, bool ToSel, const ConfOption *pConfOpt
|
||||
MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) );
|
||||
}
|
||||
|
||||
static void PreferredSampleRate( int &sel, bool ToSel, const ConfOption *pConfOption )
|
||||
{
|
||||
const int mapping[] = { 0, 44100, 48000 };
|
||||
MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) );
|
||||
}
|
||||
|
||||
static void VisualDelaySeconds( int &sel, bool ToSel, const ConfOption *pConfOption )
|
||||
{
|
||||
const float mapping[] = { -0.125f,-0.1f,-0.075f,-0.05f,-0.025f,0.0f,0.025f,0.05f,0.075f,0.1f,0.125f };
|
||||
@@ -935,6 +941,8 @@ static void InitializeConfOptions()
|
||||
ADD( ConfOption( "SoundVolume", SoundVolume, "Silent","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) );
|
||||
g_ConfOptions.back().m_iEffects = OPT_APPLY_SOUND;
|
||||
ADD( ConfOption( "SoundVolumeAttract", SoundVolumeAttract, "Silent","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) );
|
||||
ADD( ConfOption( "PreferredSampleRate", PreferredSampleRate, "Default", "44100 Hz", "48000 Hz" ) );
|
||||
g_ConfOptions.back().m_sPrefName = "SoundPreferredSampleRate";
|
||||
ADD( ConfOption( "VisualDelaySeconds", VisualDelaySeconds, "|-5","|-4","|-3","|-2","|-1","|0","|+1","|+2","|+3","|+4","|+5" ) );
|
||||
{
|
||||
ConfOption c( "GlobalOffsetSeconds", GlobalOffsetSeconds );
|
||||
|
||||
@@ -212,7 +212,7 @@ void Alsa9Buf::GetSoundCardDebugInfo()
|
||||
|
||||
Alsa9Buf::Alsa9Buf()
|
||||
{
|
||||
samplerate = 48000;
|
||||
samplerate = 44100;
|
||||
samplebits = 16;
|
||||
last_cursor_pos = 0;
|
||||
preferred_writeahead = 8192;
|
||||
@@ -229,7 +229,7 @@ RString Alsa9Buf::Init( int channels_,
|
||||
preferred_writeahead = iWriteahead;
|
||||
preferred_chunksize = iChunkSize;
|
||||
if( iSampleRate == 0 )
|
||||
samplerate = 48000;
|
||||
samplerate = 44100;
|
||||
else
|
||||
samplerate = iSampleRate;
|
||||
|
||||
|
||||
@@ -62,7 +62,12 @@ void DSound::SetPrimaryBufferMode()
|
||||
waveformat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
waveformat.wBitsPerSample = 16;
|
||||
waveformat.nChannels = 2;
|
||||
waveformat.nSamplesPerSec = 48000;
|
||||
int preferredSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if (preferredSampleRate == 0)
|
||||
{
|
||||
preferredSampleRate = 44100; // Default to 44100 Hz if preference is 0
|
||||
}
|
||||
waveformat.nSamplesPerSec = preferredSampleRate;
|
||||
waveformat.nBlockAlign = 4;
|
||||
waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec * waveformat.nBlockAlign;
|
||||
|
||||
@@ -75,8 +80,8 @@ void DSound::SetPrimaryBufferMode()
|
||||
hr = pBuffer->GetFormat( &waveformat, sizeof(waveformat), &got );
|
||||
if( FAILED(hr) )
|
||||
LOG->Warn( hr_ssprintf(hr, "GetFormat on primary buffer") );
|
||||
else if( waveformat.nSamplesPerSec != 48000 )
|
||||
LOG->Warn( "Primary buffer set to %i instead of 48000", waveformat.nSamplesPerSec );
|
||||
else if( waveformat.nSamplesPerSec != 44100 )
|
||||
LOG->Warn( "Primary buffer set to %i instead of 44100", waveformat.nSamplesPerSec );
|
||||
|
||||
/*
|
||||
* MS docs:
|
||||
@@ -195,9 +200,9 @@ RString DSoundBuf::Init( DSound &ds, DSoundBuf::hw hardware,
|
||||
waveformat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
|
||||
bool bNeedCtrlFrequency = false;
|
||||
if( m_iSampleRate == DYNAMIC_SAMPLERATE )
|
||||
if( m_iSampleRate == DYNAMIC_SAMPLERATE ) // DYNAMIC_SAMPLERATE is usually 0 or some special value
|
||||
{
|
||||
m_iSampleRate = 48000;
|
||||
m_iSampleRate = 44100; // If dynamic, default to 44100 for now
|
||||
bNeedCtrlFrequency = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
* hearing it. (This isn't necessarily the same as the buffer latency.) */
|
||||
virtual float GetPlayLatency() const { return 0.0f; }
|
||||
|
||||
virtual int GetSampleRate() const { return 48000; }
|
||||
virtual int GetSampleRate() const { return 44100; }
|
||||
|
||||
protected:
|
||||
/* Start the decoding. This should be called once the hardware is set up and
|
||||
|
||||
@@ -151,7 +151,7 @@ RString RageSoundDriver_AU::Init()
|
||||
streamFormat.mBitsPerChannel = kBitsPerChannel;
|
||||
|
||||
if( streamFormat.mSampleRate <= 0.0 )
|
||||
streamFormat.mSampleRate = 48000.0;
|
||||
streamFormat.mSampleRate = 44100.0;
|
||||
m_iSampleRate = int( streamFormat.mSampleRate );
|
||||
m_TimeScale = streamFormat.mSampleRate / AudioGetHostClockFrequency();
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ RString RageSoundDriver_DSound_Software::Init()
|
||||
m_pPCM = new DSoundBuf;
|
||||
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_iSampleRate == 0 )
|
||||
m_iSampleRate = 48000;
|
||||
m_iSampleRate = 44100;
|
||||
// This m_iSampleRate (driver's) is then passed as the iSampleRate parameter to DSoundBuf::Init()
|
||||
sError = m_pPCM->Init( ds, DSoundBuf::HW_DONT_CARE, channels, m_iSampleRate, 16, g_iMaxWriteahead );
|
||||
if( sError != "" )
|
||||
return sError;
|
||||
|
||||
@@ -32,7 +32,7 @@ RageSoundDriver_Null::RageSoundDriver_Null()
|
||||
{
|
||||
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_iSampleRate == 0 )
|
||||
m_iSampleRate = 48000;
|
||||
m_iSampleRate = 44100;
|
||||
m_iLastCursorPos = GetPosition();
|
||||
StartDecodeThread();
|
||||
}
|
||||
|
||||
@@ -181,26 +181,33 @@ RString RageSoundDriver_OSS::Init()
|
||||
if( sError != "" )
|
||||
return sError;
|
||||
|
||||
int i = AFMT_S16_LE;
|
||||
if(ioctl(fd, SNDCTL_DSP_SETFMT, &i) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFMT, %i): %s", i, strerror(errno) );
|
||||
if(i != AFMT_S16_LE)
|
||||
return ssprintf( "RageSoundDriver_OSS: Wanted format %i, got %i instead", AFMT_S16_LE, i );
|
||||
int fmt_val = AFMT_S16_LE;
|
||||
if(ioctl(fd, SNDCTL_DSP_SETFMT, &fmt_val) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFMT, %i): %s", fmt_val, strerror(errno) );
|
||||
if(fmt_val != AFMT_S16_LE)
|
||||
return ssprintf( "RageSoundDriver_OSS: Wanted format %i, got %i instead", AFMT_S16_LE, fmt_val );
|
||||
|
||||
i = channels;
|
||||
if(ioctl(fd, SNDCTL_DSP_CHANNELS, &i) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_CHANNELS, %i): %s", i, strerror(errno) );
|
||||
if(i != channels)
|
||||
return ssprintf( "RageSoundDriver_OSS: Wanted %i channels, got %i instead", channels, i );
|
||||
int channels_val = channels;
|
||||
if(ioctl(fd, SNDCTL_DSP_CHANNELS, &channels_val) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_CHANNELS, %i): %s", channels_val, strerror(errno) );
|
||||
if(channels_val != channels)
|
||||
return ssprintf( "RageSoundDriver_OSS: Wanted %i channels, got %i instead", channels, channels_val );
|
||||
|
||||
i = 48000;
|
||||
if(ioctl(fd, SNDCTL_DSP_SPEED, &i) == -1 )
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SPEED, %i): %s", i, strerror(errno) );
|
||||
samplerate = i;
|
||||
LOG->Trace("RageSoundDriver_OSS: sample rate %i", samplerate);
|
||||
i = (num_chunks << 16) + chunk_order;
|
||||
if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &i) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFRAGMENT, %i): %s", i, strerror(errno) );
|
||||
// Determine the target sample rate based on preference
|
||||
int targetSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if (targetSampleRate == 0) {
|
||||
targetSampleRate = 44100; // Default to 44100 if preference is 0
|
||||
}
|
||||
|
||||
samplerate = targetSampleRate; // Attempt to set this rate
|
||||
if(ioctl(fd, SNDCTL_DSP_SPEED, &samplerate) == -1 ) // Pass 'samplerate' (member var) by address
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SPEED, %i): %s", targetSampleRate, strerror(errno) );
|
||||
// samplerate now holds the actual rate set by the driver
|
||||
LOG->Trace("RageSoundDriver_OSS: Requested sample rate %i, got %i", targetSampleRate, samplerate);
|
||||
|
||||
int frag_val = (num_chunks << 16) + chunk_order;
|
||||
if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag_val) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFRAGMENT, %i): %s", frag_val, strerror(errno) );
|
||||
StartDecodeThread();
|
||||
|
||||
MixingThread.SetName( "RageSoundDriver_OSS" );
|
||||
|
||||
@@ -29,7 +29,7 @@ m_PulseMainLoop(nullptr), m_PulseCtx(nullptr), m_PulseStream(nullptr)
|
||||
{
|
||||
m_ss.rate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_ss.rate == 0 )
|
||||
m_ss.rate = 48000;
|
||||
m_ss.rate = 44100;
|
||||
}
|
||||
|
||||
RageSoundDriver_PulseAudio::~RageSoundDriver_PulseAudio()
|
||||
@@ -124,27 +124,23 @@ RString RageSoundDriver_PulseAudio::Init()
|
||||
void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
{
|
||||
int error;
|
||||
pa_sample_spec ss;
|
||||
pa_sample_spec ss_local; // Use a local pa_sample_spec for setup
|
||||
pa_channel_map map;
|
||||
|
||||
/* init sample spec */
|
||||
ss.format = PA_SAMPLE_S16LE;
|
||||
ss.channels = 2;
|
||||
ss.rate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if(ss.rate == 0)
|
||||
{
|
||||
ss.rate = 48000;
|
||||
}
|
||||
ss_local.format = PA_SAMPLE_S16LE;
|
||||
ss_local.channels = 2;
|
||||
ss_local.rate = m_ss.rate; // Use the rate initialized in the constructor's m_ss
|
||||
|
||||
/* init channel map */
|
||||
pa_channel_map_init_stereo(&map);
|
||||
|
||||
/* check sample spec */
|
||||
if(!pa_sample_spec_valid(&ss))
|
||||
if(!pa_sample_spec_valid(&ss_local))
|
||||
{
|
||||
if(asprintf(&m_Error, "invalid sample spec!") == -1)
|
||||
{
|
||||
m_Error = nullptr;
|
||||
m_Error = nullptr; // asprintf failed to allocate memory
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
@@ -152,17 +148,17 @@ void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
|
||||
/* log the used sample spec */
|
||||
char specstring[PA_SAMPLE_SPEC_SNPRINT_MAX];
|
||||
pa_sample_spec_snprint(specstring, sizeof(specstring), &ss);
|
||||
pa_sample_spec_snprint(specstring, sizeof(specstring), &ss_local);
|
||||
LOG->Trace("Pulse: using sample spec: %s", specstring);
|
||||
|
||||
/* create the stream */
|
||||
LOG->Trace("Pulse: pa_stream_new()...");
|
||||
m_PulseStream = pa_stream_new(m_PulseCtx, PRODUCT_FAMILY " Audio", &ss, &map);
|
||||
m_PulseStream = pa_stream_new(m_PulseCtx, PRODUCT_FAMILY " Audio", &ss_local, &map);
|
||||
if(m_PulseStream == nullptr)
|
||||
{
|
||||
if(asprintf(&m_Error, "pa_stream_new(): %s", pa_strerror(pa_context_errno(m_PulseCtx))) == -1)
|
||||
{
|
||||
m_Error = nullptr;
|
||||
m_Error = nullptr; // asprintf failed to allocate memory
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
@@ -172,13 +168,13 @@ void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
* needs data */
|
||||
pa_stream_set_write_callback(m_PulseStream, StaticStreamWriteCb, this);
|
||||
|
||||
/* set the state callback, it will be called the the stream state will
|
||||
/* set the state callback, it will be called when the stream state will
|
||||
* change */
|
||||
pa_stream_set_state_callback(m_PulseStream, StaticStreamStateCb, this);
|
||||
|
||||
/* configure attributes of the stream */
|
||||
pa_buffer_attr attr;
|
||||
memset(&attr, 0x00, sizeof(attr));
|
||||
memset(&attr, 0x00, sizeof(attr)); // Initialize all members to 0/nullptr
|
||||
|
||||
/* tlength: Target length of the buffer.
|
||||
*
|
||||
@@ -193,7 +189,7 @@ void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
* We don't want the default here, we want a small latency.
|
||||
* We use pa_usec_to_bytes() to convert a latency to a buffer size.
|
||||
*/
|
||||
attr.tlength = pa_usec_to_bytes(20*PA_USEC_PER_MSEC, &ss);
|
||||
attr.tlength = pa_usec_to_bytes(20*PA_USEC_PER_MSEC, &ss_local); // Use local ss_local
|
||||
|
||||
/* maxlength: Maximum length of the buffer
|
||||
*
|
||||
@@ -215,7 +211,7 @@ void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
* (uint32_t)-1 is NOT working here, setting it to 0, like
|
||||
* openal-soft-pulseaudio does.
|
||||
*/
|
||||
attr.minreq = 0;
|
||||
attr.minreq = 0; // Setting to (uint32_t)-1 caused issues in some environments. 0 is safer.
|
||||
|
||||
/* prebuf: Pre-buffering
|
||||
*
|
||||
@@ -226,14 +222,21 @@ void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
*/
|
||||
attr.prebuf = (uint32_t)-1;
|
||||
|
||||
/* fragsize: Deprecated. Use tlength, prebuf, minreq, maxlength instead.
|
||||
* Ensure it's not used by setting to (uint32_t)-1, as per PA documentation.
|
||||
*/
|
||||
attr.fragsize = (uint32_t)-1;
|
||||
|
||||
|
||||
/* log the used target buffer length */
|
||||
LOG->Trace("Pulse: using target buffer length of %i bytes", attr.tlength);
|
||||
|
||||
/* connect the stream for playback */
|
||||
LOG->Trace("Pulse: pa_stream_connect_playback()...");
|
||||
const int flags = PA_STREAM_INTERPOLATE_TIMING
|
||||
| PA_STREAM_NOT_MONOTONIC
|
||||
| PA_STREAM_AUTO_TIMING_UPDATE;
|
||||
| PA_STREAM_NOT_MONOTONIC // mHostTime may not be monotonic in some cases
|
||||
| PA_STREAM_AUTO_TIMING_UPDATE
|
||||
| PA_STREAM_ADJUST_LATENCY; // Allow server to adjust latency based on our buffer_attr
|
||||
error = pa_stream_connect_playback(m_PulseStream, nullptr, &attr,
|
||||
static_cast<pa_stream_flags_t>(flags), nullptr, nullptr);
|
||||
if(error < 0)
|
||||
@@ -241,13 +244,21 @@ void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
if(asprintf(&m_Error, "pa_stream_connect_playback(): %s",
|
||||
pa_strerror(pa_context_errno(m_PulseCtx))) == -1)
|
||||
{
|
||||
m_Error = nullptr;
|
||||
m_Error = nullptr; // asprintf failed to allocate memory
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
}
|
||||
|
||||
m_ss = ss;
|
||||
// m_ss is the member variable. After successfully connecting the stream,
|
||||
// we can be more confident about the sample spec being used.
|
||||
// It's generally good practice to update m_ss with the spec that was
|
||||
// actually used to create the stream, in case the server negotiated
|
||||
// something slightly different (though with PA_SAMPLE_S16LE, 2ch, and specific rates,
|
||||
// it's less likely to change).
|
||||
m_ss = ss_local;
|
||||
|
||||
// Semaphore is posted in StreamStateCb when stream becomes PA_STREAM_READY
|
||||
}
|
||||
|
||||
void RageSoundDriver_PulseAudio::CtxStateCb(pa_context *c)
|
||||
|
||||
@@ -125,7 +125,7 @@ RString RageSoundDriver_WaveOut::Init()
|
||||
b_InitSuccess = false;
|
||||
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_iSampleRate == 0 )
|
||||
m_iSampleRate = 48000;
|
||||
m_iSampleRate = 44100;
|
||||
|
||||
WAVEFORMATEX fmt;
|
||||
fmt.wFormatTag = WAVE_FORMAT_PCM;
|
||||
|
||||
Reference in New Issue
Block a user