diff --git a/.vscode/settings.json b/.vscode/settings.json index a414c6bf2..790c9dc4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,7 @@ "type_traits": "cpp", "stdexcept": "cpp", "fstream": "cpp", - "__locale": "cpp" + "__locale": "cpp", + "__string": "cpp" } } diff --git a/Makefile b/Makefile index 35a1e3606..004612643 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,8 @@ MODULES += \ src/emucore/tia/frame-manager \ src/gui \ src/common \ - src/common/tv_filters + src/common/tv_filters \ + src/common/audio ###################################################################### # The build rules follow - normally you should have no need to diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 84e2f7d62..df43df023 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -30,6 +30,7 @@ #include "SoundSDL2.hxx" #include "AudioQueue.hxx" #include "EmulationTiming.hxx" +#include "audio/SimpleResampler.hxx" namespace { inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) @@ -45,8 +46,7 @@ SoundSDL2::SoundSDL2(OSystem& osystem) myVolume(100), myVolumeFactor(0xffff), myAudioQueue(0), - myCurrentFragment(0), - myFragmentBufferSize(0) + myCurrentFragment(0) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); @@ -132,14 +132,6 @@ void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulati myAudioQueue = audioQueue; myUnderrun = true; myCurrentFragment = 0; - myTimeIndex = 0; - myFragmentIndex = 0; - myFragmentBufferSize = static_cast( - ceil( - 1.5 * static_cast(myHardwareSpec.samples) / static_cast(myAudioQueue->fragmentSize()) - * static_cast(myAudioQueue->sampleRate()) / static_cast(myHardwareSpec.freq) - ) - ); // Adjust volume to that defined in settings setVolume(myOSystem.settings().getInt("volume")); @@ -154,6 +146,8 @@ void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulati << endl; myOSystem.logMessage(buf.str(), 1); + initResampler(); + // And start the SDL sound subsystem ... mute(false); @@ -243,62 +237,33 @@ uInt32 SoundSDL2::getSampleRate() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) { - if (myUnderrun && myAudioQueue->size() > emulationTiming->prebufferFragmentCount()) { - myUnderrun = false; - myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); - myFragmentIndex = 0; - } + myResampler->fillFragment(stream, length); - if (!myCurrentFragment) { - memset(stream, 0, 2 * length); - return; - } + for (uInt32 i = 0; i < length; i++) stream[i] = applyVolume(stream[i], myVolumeFactor); +} - const bool isStereoTIA = myAudioQueue->isStereo(); - const bool isStereo = myHardwareSpec.channels == 2; - const uInt32 sampleRateTIA = myAudioQueue->sampleRate(); - const uInt32 sampleRate = myHardwareSpec.freq; - const uInt32 fragmentSize = myAudioQueue->fragmentSize(); - const uInt32 outputSamples = isStereo ? (length >> 1) : length; +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL2::initResampler() +{ + Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* { + Int16* nextFragment = 0; - for (uInt32 i = 0; i < outputSamples; i++) { - myTimeIndex += sampleRateTIA; + if (myUnderrun) + nextFragment = myAudioQueue->size() > emulationTiming->prebufferFragmentCount() ? myAudioQueue->dequeue(myCurrentFragment) : 0; + else + nextFragment = myAudioQueue->dequeue(myCurrentFragment); - if (myTimeIndex >= sampleRate) { - myFragmentIndex += myTimeIndex / sampleRate; - myTimeIndex %= sampleRate; - } + myUnderrun = nextFragment == 0; + if (nextFragment) myCurrentFragment = nextFragment; - if (myFragmentIndex >= fragmentSize) { - myFragmentIndex %= fragmentSize; + return nextFragment; + }; - Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment); - if (nextFragment) - myCurrentFragment = nextFragment; - else { - myUnderrun = true; - (cout << "audio underrun!\n").flush(); - } - } - - if (isStereo) { - if (isStereoTIA) { - stream[2*i] = applyVolume(myCurrentFragment[2*myFragmentIndex], myVolumeFactor); - stream[2*i + 1] = applyVolume(myCurrentFragment[2*myFragmentIndex + 1], myVolumeFactor); - } else { - stream[2*i] = stream[2*i + 1] = applyVolume(myCurrentFragment[myFragmentIndex], myVolumeFactor); - } - } else { - if (isStereoTIA) { - stream[i] = applyVolume( - (myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2), - myVolumeFactor - ); - } else { - stream[i] = applyVolume(myCurrentFragment[myFragmentIndex], myVolumeFactor); - } - } - } + myResampler = make_unique( + Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()), + Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1), + nextFragmentCallback + ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 12a878640..9874c1197 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -28,6 +28,7 @@ class EmulationTiming; #include "bspf.hxx" #include "Sound.hxx" +#include "audio/Resampler.hxx" /** This class implements the sound API for SDL. @@ -112,6 +113,10 @@ class SoundSDL2 : public Sound */ void processFragment(Int16* stream, uInt32 length); + private: + + void initResampler(); + private: // Indicates if the sound device was successfully initialized bool myIsInitializedFlag; @@ -128,11 +133,10 @@ class SoundSDL2 : public Sound EmulationTiming* emulationTiming; Int16* myCurrentFragment; - uInt32 myTimeIndex; - uInt32 myFragmentIndex; - uInt32 myFragmentBufferSize; bool myUnderrun; + unique_ptr myResampler; + private: // Callback function invoked by the SDL Audio library when it needs data static void callback(void* udata, uInt8* stream, int len); diff --git a/src/common/audio/Resampler.hxx b/src/common/audio/Resampler.hxx new file mode 100644 index 000000000..2926272a4 --- /dev/null +++ b/src/common/audio/Resampler.hxx @@ -0,0 +1,79 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef RESAMPLER_HXX +#define RESAMPLER_HXX + +#include + +#include "bspf.hxx" + +class Resampler { + public: + + using NextFragmentCallback = std::function; + + class Format { + public: + + Format(uInt32 sampleRate, uInt32 fragmentSize, bool stereo) : + sampleRate(sampleRate), + fragmentSize(fragmentSize), + stereo(stereo) + {} + + public: + + uInt32 sampleRate; + uInt32 fragmentSize; + bool stereo; + + private: + + Format() = delete; + }; + + public: + + Resampler(Format formatFrom, Format formatTo, NextFragmentCallback nextFragmentCallback) : + myFormatFrom(formatFrom), + myFormatTo(formatTo), + myNextFragmentCallback(nextFragmentCallback) + {} + + virtual void fillFragment(Int16* fragment, uInt32 length) = 0; + + virtual ~Resampler() {} + + protected: + + Format myFormatFrom; + Format myFormatTo; + + NextFragmentCallback myNextFragmentCallback; + + private: + + Resampler() = delete; + Resampler(const Resampler&) = delete; + Resampler(Resampler&&) = delete; + Resampler& operator=(const Resampler&) = delete; + Resampler& operator=(Resampler&&) = delete; + +}; + +#endif // RESAMPLER_HXX diff --git a/src/common/audio/SimpleResampler.cxx b/src/common/audio/SimpleResampler.cxx new file mode 100644 index 000000000..de3b6e535 --- /dev/null +++ b/src/common/audio/SimpleResampler.cxx @@ -0,0 +1,91 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "SimpleResampler.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SimpleResampler::SimpleResampler( + Resampler::Format formatFrom, + Resampler::Format formatTo, + Resampler::NextFragmentCallback nextFragmentCallback) +: + Resampler(formatFrom, formatTo, nextFragmentCallback), + myCurrentFragment(0), + myTimeIndex(0), + myFragmentIndex(0), + myIsUnderrun(true) +{} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SimpleResampler::fillFragment(Int16* fragment, uInt32 length) +{ + if (myIsUnderrun) { + Int16* nextFragment = myNextFragmentCallback(); + + if (nextFragment) { + myCurrentFragment = nextFragment; + myFragmentIndex = 0; + myIsUnderrun = false; + } + } + + if (!myCurrentFragment) { + memset(fragment, 0, 2 * length); + return; + } + + const uInt32 outputSamples = myFormatTo.stereo ? (length >> 1) : length; + + // For the following math, remember that myTimeIndex = time * myFormatFrom.sampleRate * myFormatTo.sampleRate + for (uInt32 i = 0; i < outputSamples; i++) { + // time += 1 / myFormatTo.sampleRate + myTimeIndex += myFormatFrom.sampleRate; + + // time >= 1 / myFormatFrom.sampleRate + if (myTimeIndex >= myFormatTo.sampleRate) { + // myFragmentIndex += time * myFormatFrom.sampleRate + myFragmentIndex += myTimeIndex / myFormatTo.sampleRate; + myTimeIndex %= myFormatTo.sampleRate; + } + + if (myFragmentIndex >= myFormatFrom.fragmentSize) { + myFragmentIndex %= myFormatFrom.fragmentSize; + + Int16* nextFragment = myNextFragmentCallback(); + if (nextFragment) + myCurrentFragment = nextFragment; + else { + (cerr << "audio buffer underrun\n").flush(); + myIsUnderrun = true; + } + } + + if (myFormatTo.stereo) { + if (myFormatFrom.stereo) { + fragment[2*i] = myCurrentFragment[2*myFragmentIndex]; + fragment[2*i + 1] = myCurrentFragment[2*myFragmentIndex + 1]; + } + else + fragment[2*i] = fragment[2*i + 1] = myCurrentFragment[myFragmentIndex]; + } else { + if (myFormatFrom.stereo) + fragment[i] = (myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2); + else + fragment[i] = myCurrentFragment[myFragmentIndex]; + } + } +} diff --git a/src/common/audio/SimpleResampler.hxx b/src/common/audio/SimpleResampler.hxx new file mode 100644 index 000000000..a9b155226 --- /dev/null +++ b/src/common/audio/SimpleResampler.hxx @@ -0,0 +1,50 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef SIMPLE_RESAMPLER_HXX +#define SIMPLE_RESAMPLER_HXX + +#include "Resampler.hxx" + +class SimpleResampler : public Resampler { + public: + SimpleResampler( + Resampler::Format formatFrom, + Resampler::Format formatTo, + Resampler::NextFragmentCallback NextFragmentCallback + ); + + virtual void fillFragment(Int16* fragment, uInt32 length); + + private: + + Int16* myCurrentFragment; + uInt32 myTimeIndex; + uInt32 myFragmentIndex; + bool myIsUnderrun; + + private: + + SimpleResampler() = delete; + SimpleResampler(const SimpleResampler&) = delete; + SimpleResampler(SimpleResampler&&) = delete; + SimpleResampler& operator=(const SimpleResampler&) = delete; + SimpleResampler& operator=(const SimpleResampler&&) = delete; + +}; + +#endif // SIMPLE_RESAMPLER_HXX diff --git a/src/common/audio/module.mk b/src/common/audio/module.mk new file mode 100644 index 000000000..890196f3c --- /dev/null +++ b/src/common/audio/module.mk @@ -0,0 +1,10 @@ +MODULE := src/common/audio + +MODULE_OBJS := \ + src/common/audio/SimpleResampler.o + +MODULE_DIRS += \ + src/emucore/tia + +# Include common rules +include $(srcdir)/common.rules diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx index a18acc6b0..163df9088 100644 --- a/src/emucore/EmulationTiming.hxx +++ b/src/emucore/EmulationTiming.hxx @@ -60,6 +60,13 @@ class EmulationTiming { uInt32 myPlaybackPeriod; + private: + + EmulationTiming(const EmulationTiming&) = delete; + EmulationTiming(EmulationTiming&&) = delete; + EmulationTiming& operator=(const EmulationTiming&) = delete; + EmulationTiming& operator=(EmulationTiming&&) = delete; + }; #endif // EMULATION_TIMING_HXX