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)
return;
if(myWavDevice)
{
SDL_CloseAudioDevice(myWavDevice);
SDL_FreeWAV(myWavBuffer);
}
SDL_CloseAudioDevice(myDevice);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
@ -165,7 +160,7 @@ void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
openDevice();
myEmulationTiming = emulationTiming;
myWavSpeed = 262 * 60 * 2. / myEmulationTiming->audioSampleRate();
myWavHandler.setSpeed(262 * 60 * 2. / myEmulationTiming->audioSampleRate());
Logger::debug("SoundSDL2::open started ...");
@ -214,11 +209,7 @@ void SoundSDL2::mute(bool state)
{
myAudioSettings.setEnabled(!state);
if(state)
{
SDL_LockAudioDevice(myDevice);
myVolumeFactor = 0;
SDL_UnlockAudioDevice(myDevice);
}
else
setVolume(myAudioSettings.volume());
}
@ -242,10 +233,10 @@ bool SoundSDL2::pause(bool state)
const bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED;
if(myIsInitializedFlag)
{
SDL_PauseAudioDevice(myDevice, state ? 1 : 0);
if(myWavDevice)
SDL_PauseAudioDevice(myWavDevice, state ? 1 : 0);
myWavHandler.pause(state);
}
return oldstate;
}
@ -255,10 +246,7 @@ void SoundSDL2::setVolume(uInt32 volume)
if(myIsInitializedFlag && (volume <= 100))
{
myAudioSettings.setVolume(volume);
SDL_LockAudioDevice(myDevice);
myVolumeFactor = myAudioSettings.enabled() ? static_cast<float>(volume) / 100.F : 0;
SDL_UnlockAudioDevice(myDevice);
}
}
@ -336,15 +324,6 @@ string SoundSDL2::about() const
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()
{
@ -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)
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
SDL_memset(stream, 0, len);
}
@ -408,129 +396,161 @@ void SoundSDL2::callback(void* udata, uInt8* stream, int len)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SoundSDL2::playWav(const string& fileName, const uInt32 position,
const uInt32 length)
{
// Load WAV file
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;
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;
return myWavHandler.play(fileName, device, position, length);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::stopWav()
{
if(myWavBuffer)
{
// Clean up
myWavLen = 0;
SDL_CloseAudioDevice(myWavDevice);
myWavDevice = 0;
SDL_FreeWAV(myWavBuffer);
myWavBuffer = nullptr;
}
if(myWavCvtBuffer)
{
myWavCvtBuffer.reset();
myWavCvtBufferSize = 0;
}
myWavHandler.stop();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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);
if(myWavLen)
// Load WAV file
if(fileName != myFilename || myBuffer == nullptr)
{
if(myWavSpeed != 1.0)
if(myBuffer)
{
SDL_FreeWAV(myBuffer);
myBuffer = nullptr;
}
SDL_zero(mySpec);
if(SDL_LoadWAV(fileName.c_str(), &mySpec, &myBuffer, &myLength) == nullptr)
return false;
// Set the callback function
mySpec.callback = callback;
mySpec.userdata = this;
}
if(position > myLength)
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 / myWavSpeed);
len = std::round(len / mySpeed);
const int newFreq =
std::round(static_cast<double>(myWavSpec.freq) * origLen / len);
std::round(static_cast<double>(mySpec.freq) * origLen / len);
if(static_cast<uInt32>(len) > myWavLen)
len = myWavLen;
if(static_cast<uInt32>(len) > myRemaining)
len = myRemaining;
SDL_AudioCVT cvt;
SDL_BuildAudioCVT(&cvt, myWavSpec.format, myWavSpec.channels, myWavSpec.freq,
myWavSpec.format, myWavSpec.channels, newFreq);
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 * myWavSpec.channels; // Mono 8 bit sample frames
cvt.len = len * mySpec.channels; // Mono 8 bit sample frames
if(!myWavCvtBuffer ||
myWavCvtBufferSize < static_cast<uInt32>(cvt.len * cvt.len_mult))
if(!myCvtBuffer ||
myCvtBufferSize < static_cast<uInt32>(cvt.len * cvt.len_mult))
{
myWavCvtBufferSize = cvt.len * cvt.len_mult;
myWavCvtBuffer = make_unique<uInt8[]>(myWavCvtBufferSize);
myCvtBufferSize = cvt.len * cvt.len_mult;
myCvtBuffer = make_unique<uInt8[]>(myCvtBufferSize);
}
cvt.buf = myWavCvtBuffer.get();
cvt.buf = myCvtBuffer.get();
// Read original data into conversion buffer
SDL_memcpy(cvt.buf, myWavPos, cvt.len);
SDL_memcpy(cvt.buf, myPos, 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);
SDL_MixAudioFormat(stream, cvt.buf, mySpec.format, cvt.len_cvt,
SDL_MIX_MAXVOLUME * SoundSDL2::myVolumeFactor);
}
else
{
if(static_cast<uInt32>(len) > myWavLen)
len = myWavLen;
if(static_cast<uInt32>(len) > myRemaining)
len = myRemaining;
// Mix volume adjusted WAV data into silent buffer
SDL_MixAudioFormat(stream, myWavPos, myWavSpec.format, len,
SDL_MixAudioFormat(stream, myPos, mySpec.format, len,
SDL_MIX_MAXVOLUME * myVolumeFactor);
}
myWavPos += len;
myWavLen -= len;
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;
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

View File

@ -125,7 +125,7 @@ class SoundSDL2 : public Sound
@param position The position to start playing
@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,
const uInt32 length = 0) override;
@ -142,7 +142,7 @@ class SoundSDL2 : public Sound
*/
uInt32 wavSize() const override;
protected:
private:
/**
This method is called to query the audio devices.
@ -150,17 +150,6 @@ class SoundSDL2 : public Sound
*/
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.
Typically this will only happen once per program run, but it can also
@ -171,47 +160,75 @@ class SoundSDL2 : public Sound
void initResampler();
private:
AudioSettings& myAudioSettings;
// Indicates if the sound device was successfully initialized
bool myIsInitializedFlag{false};
// Audio specification structure
SDL_AudioSpec myHardwareSpec;
SDL_AudioDeviceID myDevice{0};
uInt32 myDeviceId{0};
SDL_AudioDeviceID myDevice{0};
shared_ptr<AudioQueue> myAudioQueue;
unique_ptr<Resampler> myResampler;
EmulationTiming* myEmulationTiming{nullptr};
Int16* myCurrentFragment{nullptr};
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
string myWavFilename;
uInt32 myWavLength{0};
SDL_AudioDeviceID myWavDevice{0};
uInt8* myWavBuffer{nullptr};
bool play(const string& fileName, const char* device,
const uInt32 position, const uInt32 length);
void stop();
uInt32 size() const { return myBuffer ? myRemaining : 0; }
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 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:
// Callback functions invoked by the SDL Audio library when it needs data
static void callback(void* udata, uInt8* stream, int len);
static void wavCallback(void* udata, uInt8* stream, int len);
static void callback(void* object, uInt8* stream, int len);
// Following constructors and assignment operators not supported
SoundSDL2() = delete;