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",
|
"type_traits": "cpp",
|
||||||
"stdexcept": "cpp",
|
"stdexcept": "cpp",
|
||||||
"fstream": "cpp",
|
"fstream": "cpp",
|
||||||
"__locale": "cpp"
|
"__locale": "cpp",
|
||||||
|
"__string": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -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
|
||||||
|
|
|
@ -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);
|
|
||||||
myFragmentIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!myCurrentFragment) {
|
for (uInt32 i = 0; i < length; i++) stream[i] = applyVolume(stream[i], myVolumeFactor);
|
||||||
memset(stream, 0, 2 * length);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool isStereoTIA = myAudioQueue->isStereo();
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
const bool isStereo = myHardwareSpec.channels == 2;
|
void SoundSDL2::initResampler()
|
||||||
const uInt32 sampleRateTIA = myAudioQueue->sampleRate();
|
{
|
||||||
const uInt32 sampleRate = myHardwareSpec.freq;
|
Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
|
||||||
const uInt32 fragmentSize = myAudioQueue->fragmentSize();
|
Int16* nextFragment = 0;
|
||||||
const uInt32 outputSamples = isStereo ? (length >> 1) : length;
|
|
||||||
|
|
||||||
for (uInt32 i = 0; i < outputSamples; i++) {
|
if (myUnderrun)
|
||||||
myTimeIndex += sampleRateTIA;
|
nextFragment = myAudioQueue->size() > emulationTiming->prebufferFragmentCount() ? myAudioQueue->dequeue(myCurrentFragment) : 0;
|
||||||
|
else
|
||||||
|
nextFragment = myAudioQueue->dequeue(myCurrentFragment);
|
||||||
|
|
||||||
if (myTimeIndex >= sampleRate) {
|
myUnderrun = nextFragment == 0;
|
||||||
myFragmentIndex += myTimeIndex / sampleRate;
|
if (nextFragment) myCurrentFragment = nextFragment;
|
||||||
myTimeIndex %= sampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myFragmentIndex >= fragmentSize) {
|
return nextFragment;
|
||||||
myFragmentIndex %= fragmentSize;
|
};
|
||||||
|
|
||||||
Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment);
|
myResampler = make_unique<SimpleResampler>(
|
||||||
if (nextFragment)
|
Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()),
|
||||||
myCurrentFragment = nextFragment;
|
Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1),
|
||||||
else {
|
nextFragmentCallback
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
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
|
||||||
|
|
Loading…
Reference in New Issue