Some refactoring of SoundSDL2, placing WAV handling in its own inner class.

This commit is contained in:
Stephen Anthony 2022-10-01 21:06:12 -02:30
parent cff0821f33
commit d893b12807
2 changed files with 196 additions and 159 deletions

View File

@ -74,11 +74,6 @@ SoundSDL2::~SoundSDL2()
if(!myIsInitializedFlag) if(!myIsInitializedFlag)
return; return;
if(myWavDevice)
{
SDL_CloseAudioDevice(myWavDevice);
SDL_FreeWAV(myWavBuffer);
}
SDL_CloseAudioDevice(myDevice); SDL_CloseAudioDevice(myDevice);
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
} }
@ -165,7 +160,7 @@ void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
openDevice(); openDevice();
myEmulationTiming = emulationTiming; myEmulationTiming = emulationTiming;
myWavSpeed = 262 * 60 * 2. / myEmulationTiming->audioSampleRate(); myWavHandler.setSpeed(262 * 60 * 2. / myEmulationTiming->audioSampleRate());
Logger::debug("SoundSDL2::open started ..."); Logger::debug("SoundSDL2::open started ...");
@ -214,11 +209,7 @@ void SoundSDL2::mute(bool state)
{ {
myAudioSettings.setEnabled(!state); myAudioSettings.setEnabled(!state);
if(state) if(state)
{
SDL_LockAudioDevice(myDevice);
myVolumeFactor = 0; myVolumeFactor = 0;
SDL_UnlockAudioDevice(myDevice);
}
else else
setVolume(myAudioSettings.volume()); setVolume(myAudioSettings.volume());
} }
@ -242,10 +233,10 @@ bool SoundSDL2::pause(bool state)
const bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED; const bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED;
if(myIsInitializedFlag) if(myIsInitializedFlag)
{
SDL_PauseAudioDevice(myDevice, state ? 1 : 0); SDL_PauseAudioDevice(myDevice, state ? 1 : 0);
if(myWavDevice) myWavHandler.pause(state);
SDL_PauseAudioDevice(myWavDevice, state ? 1 : 0); }
return oldstate; return oldstate;
} }
@ -255,10 +246,7 @@ void SoundSDL2::setVolume(uInt32 volume)
if(myIsInitializedFlag && (volume <= 100)) if(myIsInitializedFlag && (volume <= 100))
{ {
myAudioSettings.setVolume(volume); myAudioSettings.setVolume(volume);
SDL_LockAudioDevice(myDevice);
myVolumeFactor = myAudioSettings.enabled() ? static_cast<float>(volume) / 100.F : 0; myVolumeFactor = myAudioSettings.enabled() ? static_cast<float>(volume) / 100.F : 0;
SDL_UnlockAudioDevice(myDevice);
} }
} }
@ -336,15 +324,6 @@ string SoundSDL2::about() const
return buf.str(); return buf.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::processFragment(float* stream, uInt32 length)
{
myResampler->fillFragment(stream, length);
for(uInt32 i = 0; i < length; ++i)
stream[i] *= myVolumeFactor;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::initResampler() void SoundSDL2::initResampler()
{ {
@ -395,12 +374,21 @@ void SoundSDL2::initResampler()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::callback(void* udata, uInt8* stream, int len) void SoundSDL2::callback(void* object, uInt8* stream, int len)
{ {
auto* self = static_cast<SoundSDL2*>(udata); auto* self = static_cast<SoundSDL2*>(object);
if(self->myAudioQueue) if(self->myAudioQueue)
self->processFragment(reinterpret_cast<float*>(stream), len >> 2); {
// The stream is 32-bit float (even though this callback is 8-bits), since
// the resampler and TIA audio subsystem always generate float samples
auto* s = reinterpret_cast<float*>(stream);
const uInt32 length = len >> 2;
self->myResampler->fillFragment(s, length);
for(uInt32 i = 0; i < length; ++i) // TODO - perhaps move into Resampler
s[i] *= SoundSDL2::myVolumeFactor;
}
else else
SDL_memset(stream, 0, len); SDL_memset(stream, 0, len);
} }
@ -409,128 +397,160 @@ void SoundSDL2::callback(void* udata, uInt8* stream, int len)
bool SoundSDL2::playWav(const string& fileName, const uInt32 position, bool SoundSDL2::playWav(const string& fileName, const uInt32 position,
const uInt32 length) const uInt32 length)
{ {
// Load WAV file const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr;
if(fileName != myWavFilename || myWavBuffer == nullptr)
{
if(myWavBuffer)
{
SDL_FreeWAV(myWavBuffer);
myWavBuffer = nullptr;
}
if(SDL_LoadWAV(fileName.c_str(), &myWavSpec, &myWavBuffer, &myWavLength) == nullptr)
return false;
// Set the callback function
myWavSpec.callback = wavCallback;
myWavSpec.userdata = nullptr;
//myWavSpec.samples = 4096; // decrease for smaller samples;
}
if(position > myWavLength)
return false;
myWavFilename = fileName; return myWavHandler.play(fileName, device, position, length);
myWavLen = length
? std::min(length, myWavLength - position)
: myWavLength;
myWavPos = myWavBuffer + position;
// Open audio device
if(!myWavDevice)
{
const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr;
myWavDevice = SDL_OpenAudioDevice(device, 0, &myWavSpec, nullptr, 0);
if(!myWavDevice)
return false;
// Play audio
SDL_PauseAudioDevice(myWavDevice, 0);
}
return true;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::stopWav() void SoundSDL2::stopWav()
{ {
if(myWavBuffer) myWavHandler.stop();
{
// Clean up
myWavLen = 0;
SDL_CloseAudioDevice(myWavDevice);
myWavDevice = 0;
SDL_FreeWAV(myWavBuffer);
myWavBuffer = nullptr;
}
if(myWavCvtBuffer)
{
myWavCvtBuffer.reset();
myWavCvtBufferSize = 0;
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 SoundSDL2::wavSize() const uInt32 SoundSDL2::wavSize() const
{ {
return myWavBuffer ? myWavLen : 0; return myWavHandler.size();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::wavCallback(void* udata, uInt8* stream, int len) bool SoundSDL2::WavHandlerSDL2::play(
const string& fileName, const char* device,
const uInt32 position, const uInt32 length
)
{ {
SDL_memset(stream, myWavSpec.silence, len); // Load WAV file
if(myWavLen) if(fileName != myFilename || myBuffer == nullptr)
{ {
if(myWavSpeed != 1.0) if(myBuffer)
{ {
const int origLen = len; SDL_FreeWAV(myBuffer);
len = std::round(len / myWavSpeed); myBuffer = nullptr;
const int newFreq =
std::round(static_cast<double>(myWavSpec.freq) * origLen / len);
if(static_cast<uInt32>(len) > myWavLen)
len = myWavLen;
SDL_AudioCVT cvt;
SDL_BuildAudioCVT(&cvt, myWavSpec.format, myWavSpec.channels, myWavSpec.freq,
myWavSpec.format, myWavSpec.channels, newFreq);
SDL_assert(cvt.needed); // Obviously, this one is always needed.
cvt.len = len * myWavSpec.channels; // Mono 8 bit sample frames
if(!myWavCvtBuffer ||
myWavCvtBufferSize < static_cast<uInt32>(cvt.len * cvt.len_mult))
{
myWavCvtBufferSize = cvt.len * cvt.len_mult;
myWavCvtBuffer = make_unique<uInt8[]>(myWavCvtBufferSize);
}
cvt.buf = myWavCvtBuffer.get();
// Read original data into conversion buffer
SDL_memcpy(cvt.buf, myWavPos, cvt.len);
SDL_ConvertAudio(&cvt);
// Mix volume adjusted WAV data into silent buffer
SDL_MixAudioFormat(stream, cvt.buf, myWavSpec.format, cvt.len_cvt,
SDL_MIX_MAXVOLUME * myVolumeFactor);
} }
else SDL_zero(mySpec);
{ if(SDL_LoadWAV(fileName.c_str(), &mySpec, &myBuffer, &myLength) == nullptr)
if(static_cast<uInt32>(len) > myWavLen) return false;
len = myWavLen;
// Mix volume adjusted WAV data into silent buffer // Set the callback function
SDL_MixAudioFormat(stream, myWavPos, myWavSpec.format, len, mySpec.callback = callback;
SDL_MIX_MAXVOLUME * myVolumeFactor); mySpec.userdata = this;
} }
myWavPos += len; if(position > myLength)
myWavLen -= len; return false;
myFilename = fileName;
myRemaining = length
? std::min(length, myLength - position)
: myLength;
myPos = myBuffer + position;
// Open audio device
if(!myDevice)
{
myDevice = SDL_OpenAudioDevice(device, 0, &mySpec, nullptr, 0);
if(!myDevice)
return false;
// Play audio
SDL_PauseAudioDevice(myDevice, 0);
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::WavHandlerSDL2::stop()
{
if(myBuffer)
{
// Clean up
myRemaining = 0;
SDL_CloseAudioDevice(myDevice); myDevice = 0;
SDL_FreeWAV(myBuffer); myBuffer = nullptr;
}
if(myCvtBuffer)
{
myCvtBuffer.reset();
myCvtBufferSize = 0;
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::WavHandlerSDL2::processWav(uInt8* stream, uInt32 len)
{
SDL_memset(stream, mySpec.silence, len);
if(myRemaining)
{
if(mySpeed != 1.0)
{
const int origLen = len;
len = std::round(len / mySpeed);
const int newFreq =
std::round(static_cast<double>(mySpec.freq) * origLen / len);
if(static_cast<uInt32>(len) > myRemaining)
len = myRemaining;
SDL_AudioCVT cvt;
SDL_BuildAudioCVT(&cvt, mySpec.format, mySpec.channels, mySpec.freq,
mySpec.format, mySpec.channels, newFreq);
SDL_assert(cvt.needed); // Obviously, this one is always needed.
cvt.len = len * mySpec.channels; // Mono 8 bit sample frames
if(!myCvtBuffer ||
myCvtBufferSize < static_cast<uInt32>(cvt.len * cvt.len_mult))
{
myCvtBufferSize = cvt.len * cvt.len_mult;
myCvtBuffer = make_unique<uInt8[]>(myCvtBufferSize);
}
cvt.buf = myCvtBuffer.get();
// Read original data into conversion buffer
SDL_memcpy(cvt.buf, myPos, cvt.len);
SDL_ConvertAudio(&cvt);
// Mix volume adjusted WAV data into silent buffer
SDL_MixAudioFormat(stream, cvt.buf, mySpec.format, cvt.len_cvt,
SDL_MIX_MAXVOLUME * SoundSDL2::myVolumeFactor);
}
else
{
if(static_cast<uInt32>(len) > myRemaining)
len = myRemaining;
// Mix volume adjusted WAV data into silent buffer
SDL_MixAudioFormat(stream, myPos, mySpec.format, len,
SDL_MIX_MAXVOLUME * myVolumeFactor);
}
myPos += len;
myRemaining -= len;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::WavHandlerSDL2::callback(void* object, uInt8* stream, int len)
{
static_cast<WavHandlerSDL2*>(object)->processWav(
stream, static_cast<uInt32>(len));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::WavHandlerSDL2::~WavHandlerSDL2()
{
if(myDevice)
{
SDL_CloseAudioDevice(myDevice);
SDL_FreeWAV(myBuffer);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::WavHandlerSDL2::pause(bool state) const
{
if(myDevice)
SDL_PauseAudioDevice(myDevice, state ? 1 : 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float SoundSDL2::myVolumeFactor = 0.F; float SoundSDL2::myVolumeFactor = 0.F;
SDL_AudioSpec SoundSDL2::myWavSpec; // audio output format
uInt8* SoundSDL2::myWavPos = nullptr; // pointer to the audio buffer to be played
uInt32 SoundSDL2::myWavLen = 0; // remaining length of the sample we have to play
double SoundSDL2::myWavSpeed = 1.0;
unique_ptr<uInt8[]> SoundSDL2::myWavCvtBuffer;
uInt32 SoundSDL2::myWavCvtBufferSize = 0;
#endif // SOUND_SUPPORT #endif // SOUND_SUPPORT

View File

@ -125,7 +125,7 @@ class SoundSDL2 : public Sound
@param position The position to start playing @param position The position to start playing
@param length The played length @param length The played length
@return True, if the WAV file can be played @return True if the WAV file can be played, else false
*/ */
bool playWav(const string& fileName, const uInt32 position = 0, bool playWav(const string& fileName, const uInt32 position = 0,
const uInt32 length = 0) override; const uInt32 length = 0) override;
@ -142,7 +142,7 @@ class SoundSDL2 : public Sound
*/ */
uInt32 wavSize() const override; uInt32 wavSize() const override;
protected: private:
/** /**
This method is called to query the audio devices. This method is called to query the audio devices.
@ -150,17 +150,6 @@ class SoundSDL2 : public Sound
*/ */
void queryHardware(VariantList& devices) override; void queryHardware(VariantList& devices) override;
/**
Invoked by the sound callback to process the next sound fragment.
The stream is 16-bits (even though the callback is 8-bits), since
the TIASnd class always generates signed 16-bit stereo samples.
@param stream Pointer to the start of the fragment
@param length Length of the fragment
*/
void processFragment(float* stream, uInt32 length);
private:
/** /**
The actual sound device is opened only when absolutely necessary. The actual sound device is opened only when absolutely necessary.
Typically this will only happen once per program run, but it can also Typically this will only happen once per program run, but it can also
@ -171,47 +160,75 @@ class SoundSDL2 : public Sound
void initResampler(); void initResampler();
private: private:
AudioSettings& myAudioSettings;
// Indicates if the sound device was successfully initialized // Indicates if the sound device was successfully initialized
bool myIsInitializedFlag{false}; bool myIsInitializedFlag{false};
// Audio specification structure // Audio specification structure
SDL_AudioSpec myHardwareSpec; SDL_AudioSpec myHardwareSpec;
SDL_AudioDeviceID myDevice{0};
uInt32 myDeviceId{0}; uInt32 myDeviceId{0};
SDL_AudioDeviceID myDevice{0};
shared_ptr<AudioQueue> myAudioQueue; shared_ptr<AudioQueue> myAudioQueue;
unique_ptr<Resampler> myResampler;
EmulationTiming* myEmulationTiming{nullptr}; EmulationTiming* myEmulationTiming{nullptr};
Int16* myCurrentFragment{nullptr}; Int16* myCurrentFragment{nullptr};
bool myUnderrun{false}; bool myUnderrun{false};
unique_ptr<Resampler> myResampler; string myAboutString;
AudioSettings& myAudioSettings; /**
This class implements WAV file playback using the SDL2 sound API.
*/
class WavHandlerSDL2
{
public:
explicit WavHandlerSDL2() = default;
~WavHandlerSDL2();
// WAV file sound variables bool play(const string& fileName, const char* device,
string myWavFilename; const uInt32 position, const uInt32 length);
uInt32 myWavLength{0}; void stop();
SDL_AudioDeviceID myWavDevice{0}; uInt32 size() const { return myBuffer ? myRemaining : 0; }
uInt8* myWavBuffer{nullptr};
void setSpeed(const double speed) { mySpeed = speed; }
void pause(bool state) const;
private:
string myFilename;
uInt32 myLength{0};
SDL_AudioDeviceID myDevice{0};
uInt8* myBuffer{nullptr};
double mySpeed{1.0};
unique_ptr<uInt8[]> myCvtBuffer;
uInt32 myCvtBufferSize{0};
SDL_AudioSpec mySpec; // audio output format
uInt8* myPos{nullptr}; // pointer to the audio buffer to be played
uInt32 myRemaining{0}; // remaining length of the sample we have to play
private:
// Callback function invoked by the SDL Audio library when it needs data
void processWav(uInt8* stream, uInt32 len);
static void callback(void* object, uInt8* stream, int len);
// Following constructors and assignment operators not supported
WavHandlerSDL2(const WavHandlerSDL2&) = delete;
WavHandlerSDL2(WavHandlerSDL2&&) = delete;
WavHandlerSDL2& operator=(const WavHandlerSDL2&) = delete;
WavHandlerSDL2& operator=(WavHandlerSDL2&&) = delete;
};
WavHandlerSDL2 myWavHandler;
static float myVolumeFactor; // Current volume level (0 - 100) static float myVolumeFactor; // Current volume level (0 - 100)
static double myWavSpeed;
static unique_ptr<uInt8[]> myWavCvtBuffer;
static uInt32 myWavCvtBufferSize;
static SDL_AudioSpec myWavSpec; // audio output format
static uInt8* myWavPos; // pointer to the audio buffer to be played
static uInt32 myWavLen; // remaining length of the sample we have to play
string myAboutString;
private: private:
// Callback functions invoked by the SDL Audio library when it needs data // Callback functions invoked by the SDL Audio library when it needs data
static void callback(void* udata, uInt8* stream, int len); static void callback(void* object, uInt8* stream, int len);
static void wavCallback(void* udata, uInt8* stream, int len);
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
SoundSDL2() = delete; SoundSDL2() = delete;