mirror of https://github.com/stella-emu/stella.git
Factor out resampling.
This commit is contained in:
parent
c1679d6883
commit
3bac41dd46
|
@ -42,6 +42,7 @@
|
|||
"type_traits": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"fstream": "cpp",
|
||||
"__locale": "cpp"
|
||||
"__locale": "cpp",
|
||||
"__string": "cpp"
|
||||
}
|
||||
}
|
||||
|
|
3
Makefile
3
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
|
||||
|
|
|
@ -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> audioQueue, EmulationTiming* emulati
|
|||
myAudioQueue = audioQueue;
|
||||
myUnderrun = true;
|
||||
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
|
||||
setVolume(myOSystem.settings().getInt("volume"));
|
||||
|
@ -154,6 +146,8 @@ void SoundSDL2::open(shared_ptr<AudioQueue> 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<SimpleResampler>(
|
||||
Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()),
|
||||
Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1),
|
||||
nextFragmentCallback
|
||||
);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -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<Resampler> myResampler;
|
||||
|
||||
private:
|
||||
// Callback function invoked by the SDL Audio library when it needs data
|
||||
static void callback(void* udata, uInt8* stream, int len);
|
||||
|
|
|
@ -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
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue