Factor out resampling.

This commit is contained in:
Christian Speckner 2018-05-09 00:38:01 +02:00
parent c1679d6883
commit 3bac41dd46
9 changed files with 273 additions and 65 deletions

View File

@ -42,6 +42,7 @@
"type_traits": "cpp", "type_traits": "cpp",
"stdexcept": "cpp", "stdexcept": "cpp",
"fstream": "cpp", "fstream": "cpp",
"__locale": "cpp" "__locale": "cpp",
"__string": "cpp"
} }
} }

View File

@ -101,7 +101,8 @@ MODULES += \
src/emucore/tia/frame-manager \ src/emucore/tia/frame-manager \
src/gui \ src/gui \
src/common \ src/common \
src/common/tv_filters src/common/tv_filters \
src/common/audio
###################################################################### ######################################################################
# The build rules follow - normally you should have no need to # The build rules follow - normally you should have no need to

View File

@ -30,6 +30,7 @@
#include "SoundSDL2.hxx" #include "SoundSDL2.hxx"
#include "AudioQueue.hxx" #include "AudioQueue.hxx"
#include "EmulationTiming.hxx" #include "EmulationTiming.hxx"
#include "audio/SimpleResampler.hxx"
namespace { namespace {
inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) inline Int16 applyVolume(Int16 sample, Int32 volumeFactor)
@ -45,8 +46,7 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
myVolume(100), myVolume(100),
myVolumeFactor(0xffff), myVolumeFactor(0xffff),
myAudioQueue(0), myAudioQueue(0),
myCurrentFragment(0), myCurrentFragment(0)
myFragmentBufferSize(0)
{ {
myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2);
@ -132,14 +132,6 @@ void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue, EmulationTiming* emulati
myAudioQueue = audioQueue; myAudioQueue = audioQueue;
myUnderrun = true; myUnderrun = true;
myCurrentFragment = 0; myCurrentFragment = 0;
myTimeIndex = 0;
myFragmentIndex = 0;
myFragmentBufferSize = static_cast<uInt32>(
ceil(
1.5 * static_cast<double>(myHardwareSpec.samples) / static_cast<double>(myAudioQueue->fragmentSize())
* static_cast<double>(myAudioQueue->sampleRate()) / static_cast<double>(myHardwareSpec.freq)
)
);
// Adjust volume to that defined in settings // Adjust volume to that defined in settings
setVolume(myOSystem.settings().getInt("volume")); setVolume(myOSystem.settings().getInt("volume"));
@ -154,6 +146,8 @@ void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue, EmulationTiming* emulati
<< endl; << endl;
myOSystem.logMessage(buf.str(), 1); myOSystem.logMessage(buf.str(), 1);
initResampler();
// And start the SDL sound subsystem ... // And start the SDL sound subsystem ...
mute(false); mute(false);
@ -243,62 +237,33 @@ uInt32 SoundSDL2::getSampleRate() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::processFragment(Int16* stream, uInt32 length) void SoundSDL2::processFragment(Int16* stream, uInt32 length)
{ {
if (myUnderrun && myAudioQueue->size() > emulationTiming->prebufferFragmentCount()) { myResampler->fillFragment(stream, length);
myUnderrun = false;
myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); for (uInt32 i = 0; i < length; i++) stream[i] = applyVolume(stream[i], myVolumeFactor);
myFragmentIndex = 0;
} }
if (!myCurrentFragment) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
memset(stream, 0, 2 * length); void SoundSDL2::initResampler()
return; {
} Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
Int16* nextFragment = 0;
const bool isStereoTIA = myAudioQueue->isStereo(); if (myUnderrun)
const bool isStereo = myHardwareSpec.channels == 2; nextFragment = myAudioQueue->size() > emulationTiming->prebufferFragmentCount() ? myAudioQueue->dequeue(myCurrentFragment) : 0;
const uInt32 sampleRateTIA = myAudioQueue->sampleRate(); else
const uInt32 sampleRate = myHardwareSpec.freq; nextFragment = myAudioQueue->dequeue(myCurrentFragment);
const uInt32 fragmentSize = myAudioQueue->fragmentSize();
const uInt32 outputSamples = isStereo ? (length >> 1) : length;
for (uInt32 i = 0; i < outputSamples; i++) { myUnderrun = nextFragment == 0;
myTimeIndex += sampleRateTIA; if (nextFragment) myCurrentFragment = nextFragment;
if (myTimeIndex >= sampleRate) { return nextFragment;
myFragmentIndex += myTimeIndex / sampleRate; };
myTimeIndex %= sampleRate;
}
if (myFragmentIndex >= fragmentSize) { myResampler = make_unique<SimpleResampler>(
myFragmentIndex %= fragmentSize; Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()),
Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1),
Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment); nextFragmentCallback
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);
}
}
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -28,6 +28,7 @@ class EmulationTiming;
#include "bspf.hxx" #include "bspf.hxx"
#include "Sound.hxx" #include "Sound.hxx"
#include "audio/Resampler.hxx"
/** /**
This class implements the sound API for SDL. This class implements the sound API for SDL.
@ -112,6 +113,10 @@ class SoundSDL2 : public Sound
*/ */
void processFragment(Int16* stream, uInt32 length); void processFragment(Int16* stream, uInt32 length);
private:
void initResampler();
private: private:
// Indicates if the sound device was successfully initialized // Indicates if the sound device was successfully initialized
bool myIsInitializedFlag; bool myIsInitializedFlag;
@ -128,11 +133,10 @@ class SoundSDL2 : public Sound
EmulationTiming* emulationTiming; EmulationTiming* emulationTiming;
Int16* myCurrentFragment; Int16* myCurrentFragment;
uInt32 myTimeIndex;
uInt32 myFragmentIndex;
uInt32 myFragmentBufferSize;
bool myUnderrun; bool myUnderrun;
unique_ptr<Resampler> myResampler;
private: private:
// Callback function invoked by the SDL Audio library when it needs data // Callback function invoked by the SDL Audio library when it needs data
static void callback(void* udata, uInt8* stream, int len); static void callback(void* udata, uInt8* stream, int len);

View File

@ -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 <functional>
#include "bspf.hxx"
class Resampler {
public:
using NextFragmentCallback = std::function<Int16*()>;
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

View File

@ -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];
}
}
}

View File

@ -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

View File

@ -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

View File

@ -60,6 +60,13 @@ class EmulationTiming {
uInt32 myPlaybackPeriod; uInt32 myPlaybackPeriod;
private:
EmulationTiming(const EmulationTiming&) = delete;
EmulationTiming(EmulationTiming&&) = delete;
EmulationTiming& operator=(const EmulationTiming&) = delete;
EmulationTiming& operator=(EmulationTiming&&) = delete;
}; };
#endif // EMULATION_TIMING_HXX #endif // EMULATION_TIMING_HXX