Implement AudioSubmix

This commit is contained in:
Jack Andersen 2016-05-06 18:28:32 -10:00
parent 4ecea8ac3f
commit 4b969fd475
12 changed files with 396 additions and 43 deletions

View File

@ -187,10 +187,14 @@ add_library(boo
lib/audiodev/AudioVoiceEngine.cpp
lib/audiodev/AudioVoice.hpp
lib/audiodev/AudioVoice.cpp
lib/audiodev/AudioSubmix.hpp
lib/audiodev/AudioSubmix.cpp
lib/audiodev/IAudioHost.hpp
include/boo/inputdev/IHIDListener.hpp
include/boo/IGraphicsContext.hpp
include/boo/graphicsdev/IGraphicsDataFactory.hpp
include/boo/graphicsdev/IGraphicsCommandQueue.hpp
include/boo/audiodev/IAudioSubmix.hpp
include/boo/audiodev/IAudioVoice.hpp
include/boo/audiodev/IAudioVoiceEngine.hpp
include/boo/IWindow.hpp

View File

@ -0,0 +1,46 @@
#ifndef BOO_IAUDIOSUBMIX_HPP
#define BOO_IAUDIOSUBMIX_HPP
#include <stddef.h>
#include <stdint.h>
#include <memory>
namespace boo
{
class IAudioVoice;
class IAudioVoiceCallback;
struct ChannelMap;
struct IAudioSubmixCallback;
struct IAudioSubmix
{
virtual ~IAudioSubmix() = default;
/** Same as the IAudioVoice allocator, but produces audio within the submix */
virtual std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch=false)=0;
/** Same as allocateNewMonoVoice, but source audio is stereo-interleaved */
virtual std::unique_ptr<IAudioVoice> allocateNewStereoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch=false)=0;
/** Same as the IAudioVoice allocator, but produces audio recursively within the submix */
virtual std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb=nullptr)=0;
};
struct IAudioSubmixCallback
{
/** Client-provided claim to implement / is ready to call applyEffect() */
virtual bool canApplyEffect() const=0;
/** Client-provided effect solution for interleaved, master sample-rate audio */
virtual void applyEffect(int16_t* audio, const ChannelMap& chanMap, double sampleRate) const=0;
virtual void applyEffect(int32_t* audio, const ChannelMap& chanMap, double sampleRate) const=0;
virtual void applyEffect(float* audio, const ChannelMap& chanMap, double sampleRate) const=0;
};
}
#endif // BOO_IAUDIOVOICE_HPP

View File

@ -2,6 +2,7 @@
#define BOO_IAUDIOVOICEENGINE_HPP
#include "IAudioVoice.hpp"
#include "IAudioSubmix.hpp"
#include <memory>
namespace boo
@ -18,7 +19,8 @@ struct IAudioVoiceEngine
* ChannelLayout automatically reduces to maximum-supported layout by HW.
*
* Client must be prepared to supply audio frames via the callback when this is called;
* the backing audio-buffers are primed with initial data for low-latency playback start */
* the backing audio-buffers are primed with initial data for low-latency playback start
*/
virtual std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch=false)=0;
@ -28,6 +30,9 @@ struct IAudioVoiceEngine
IAudioVoiceCallback* cb,
bool dynamicPitch=false)=0;
/** Client calls this to allocate a Submix for gathering audio together for effects processing */
virtual std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb=nullptr)=0;
/** Client may use this to determine current speaker-setup */
virtual AudioChannelSet getAvailableSet()=0;

View File

@ -1,38 +1,10 @@
#include "AudioMatrix.hpp"
#include "AudioVoiceEngine.hpp"
#include <string.h>
#include <limits.h>
namespace boo
{
static inline int16_t Clamp16(float in)
{
if (in < SHRT_MIN)
return SHRT_MIN;
else if (in > SHRT_MAX)
return SHRT_MAX;
return in;
}
static inline int32_t Clamp32(float in)
{
if (in < INT_MIN)
return INT_MIN;
else if (in > INT_MAX)
return INT_MAX;
return in;
}
static inline float ClampFlt(float in)
{
if (in < -1.f)
return -1.f;
else if (in > 1.f)
return 1.f;
return in;
}
void AudioMatrixMono::setDefaultMatrixCoefficients(AudioChannelSet acSet)
{
memset(m_coefs, 0, sizeof(m_coefs));

View File

@ -4,11 +4,39 @@
#include "boo/audiodev/IAudioVoice.hpp"
#include <vector>
#include <stdint.h>
#include <limits.h>
namespace boo
{
struct AudioVoiceEngineMixInfo;
static inline int16_t Clamp16(float in)
{
if (in < SHRT_MIN)
return SHRT_MIN;
else if (in > SHRT_MAX)
return SHRT_MAX;
return in;
}
static inline int32_t Clamp32(float in)
{
if (in < INT_MIN)
return INT_MIN;
else if (in > INT_MAX)
return INT_MAX;
return in;
}
static inline float ClampFlt(float in)
{
if (in < -1.f)
return -1.f;
else if (in > 1.f)
return 1.f;
return in;
}
class AudioMatrixMono
{
float m_coefs[8];

View File

@ -0,0 +1,166 @@
#include "AudioSubmix.hpp"
#include "AudioVoiceEngine.hpp"
#include "AudioVoice.hpp"
#include <string.h>
namespace boo
{
static std::vector<int16_t> scratch16;
static std::vector<int32_t> scratch32;
static std::vector<float> scratchFlt;
AudioSubmix::AudioSubmix(IAudioHost& parent, IAudioSubmixCallback* cb)
: m_parent(parent), m_cb(cb) {}
AudioSubmix::~AudioSubmix()
{
unbindSubmix();
}
void AudioSubmix::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
{
const AudioVoiceEngineMixInfo& info = mixInfo();
size_t sampleCount = frames * info.m_channelMap.m_channelCount;
if (scratch16.size() < sampleCount)
scratch16.resize(sampleCount);
/* Clear target buffer */
memset(scratch16.data(), 0, sizeof(int16_t) * sampleCount);
/* Pump child voices */
for (AudioVoice* vox : m_activeVoices)
if (vox->m_running)
vox->pumpAndMix(m_parent.mixInfo(), frames, scratch16.data());
/* Pump child submixes */
for (AudioSubmix* smx : m_activeSubmixes)
smx->_pumpAndMixVoices(frames, scratch16.data());
/* Apply submix effect (if available) */
if (m_cb && m_cb->canApplyEffect())
m_cb->applyEffect(scratch16.data(), info.m_channelMap, info.m_sampleRate);
/* Merge into output mix */
auto it = scratch16.begin();
for (size_t f=0 ; f<frames ; ++f)
for (size_t c=0 ; c<info.m_channelMap.m_channelCount ; ++c)
*dataOut++ = Clamp16(*it++ * m_gains[c]);
}
void AudioSubmix::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
{
const AudioVoiceEngineMixInfo& info = mixInfo();
size_t sampleCount = frames * info.m_channelMap.m_channelCount;
if (scratch32.size() < sampleCount)
scratch32.resize(sampleCount);
/* Clear target buffer */
memset(scratch32.data(), 0, sizeof(int32_t) * sampleCount);
/* Pump child voices */
for (AudioVoice* vox : m_activeVoices)
if (vox->m_running)
vox->pumpAndMix(m_parent.mixInfo(), frames, scratch32.data());
/* Pump child submixes */
for (AudioSubmix* smx : m_activeSubmixes)
smx->_pumpAndMixVoices(frames, scratch32.data());
/* Apply submix effect (if available) */
if (m_cb && m_cb->canApplyEffect())
m_cb->applyEffect(scratch32.data(), info.m_channelMap, info.m_sampleRate);
/* Merge into output mix */
auto it = scratch32.begin();
for (size_t f=0 ; f<frames ; ++f)
for (size_t c=0 ; c<info.m_channelMap.m_channelCount ; ++c)
*dataOut++ = Clamp32(*it++ * m_gains[c]);
}
void AudioSubmix::_pumpAndMixVoices(size_t frames, float* dataOut)
{
const AudioVoiceEngineMixInfo& info = mixInfo();
size_t sampleCount = frames * info.m_channelMap.m_channelCount;
if (scratchFlt.size() < sampleCount)
scratchFlt.resize(sampleCount);
/* Clear target buffer */
memset(scratchFlt.data(), 0, sizeof(float) * sampleCount);
/* Pump child voices */
for (AudioVoice* vox : m_activeVoices)
if (vox->m_running)
vox->pumpAndMix(m_parent.mixInfo(), frames, scratchFlt.data());
/* Pump child submixes */
for (AudioSubmix* smx : m_activeSubmixes)
smx->_pumpAndMixVoices(frames, scratchFlt.data());
/* Apply submix effect (if available) */
if (m_cb && m_cb->canApplyEffect())
m_cb->applyEffect(scratchFlt.data(), info.m_channelMap, info.m_sampleRate);
/* Merge into output mix */
auto it = scratchFlt.begin();
for (size_t f=0 ; f<frames ; ++f)
for (size_t c=0 ; c<info.m_channelMap.m_channelCount ; ++c)
*dataOut++ = ClampFlt(*it++ * m_gains[c]);
}
void AudioSubmix::_unbindFrom(std::list<AudioVoice*>::iterator it)
{
m_activeVoices.erase(it);
}
void AudioSubmix::_unbindFrom(std::list<AudioSubmix*>::iterator it)
{
m_activeSubmixes.erase(it);
}
std::unique_ptr<IAudioVoice> AudioSubmix::allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch)
{
std::unique_ptr<IAudioVoice> ret =
std::make_unique<AudioVoiceMono>(*this, cb, sampleRate, dynamicPitch);
AudioVoiceMono* retMono = static_cast<AudioVoiceMono*>(ret.get());
retMono->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retMono));
return ret;
}
std::unique_ptr<IAudioVoice> AudioSubmix::allocateNewStereoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch)
{
std::unique_ptr<IAudioVoice> ret =
std::make_unique<AudioVoiceStereo>(*this, cb, sampleRate, dynamicPitch);
AudioVoiceStereo* retStereo = static_cast<AudioVoiceStereo*>(ret.get());
retStereo->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retStereo));
return ret;
}
std::unique_ptr<IAudioSubmix> AudioSubmix::allocateNewSubmix(IAudioSubmixCallback* cb)
{
std::unique_ptr<IAudioSubmix> ret =
std::make_unique<AudioSubmix>(*this, cb);
AudioSubmix* retIntern = static_cast<AudioSubmix*>(ret.get());
retIntern->bindSubmix(m_activeSubmixes.insert(m_activeSubmixes.end(), retIntern));
return ret;
}
void AudioSubmix::unbindSubmix()
{
if (m_bound)
{
m_parent._unbindFrom(m_parentIt);
m_bound = false;
}
}
const AudioVoiceEngineMixInfo& AudioSubmix::mixInfo() const
{
return m_parent.mixInfo();
}
}

View File

@ -0,0 +1,65 @@
#ifndef BOO_AUDIOSUBMIX_HPP
#define BOO_AUDIOSUBMIX_HPP
#include "boo/audiodev/IAudioSubmix.hpp"
#include "IAudioHost.hpp"
#include <list>
namespace boo
{
class BaseAudioVoiceEngine;
class AudioVoice;
class AudioSubmix : public IAudioSubmix, public IAudioHost
{
friend class BaseAudioVoiceEngine;
/* Mixer-engine relationships */
IAudioHost& m_parent;
std::list<AudioSubmix*>::iterator m_parentIt;
bool m_bound = false;
void bindSubmix(std::list<AudioSubmix*>::iterator pIt)
{
m_bound = true;
m_parentIt = pIt;
}
/* Callback (effect source, optional) */
IAudioSubmixCallback* m_cb;
/* Audio sources */
std::list<AudioVoice*> m_activeVoices;
std::list<AudioSubmix*> m_activeSubmixes;
/* Output gains for each channel */
float m_gains[8];
void _pumpAndMixVoices(size_t frames, int16_t* dataOut);
void _pumpAndMixVoices(size_t frames, int32_t* dataOut);
void _pumpAndMixVoices(size_t frames, float* dataOut);
void _unbindFrom(std::list<AudioVoice*>::iterator it);
void _unbindFrom(std::list<AudioSubmix*>::iterator it);
public:
~AudioSubmix();
AudioSubmix(IAudioHost& parent, IAudioSubmixCallback* cb);
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch=false);
std::unique_ptr<IAudioVoice> allocateNewStereoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch=false);
virtual std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb=nullptr);
void unbindSubmix();
const AudioVoiceEngineMixInfo& mixInfo() const;
};
}
#endif // BOO_AUDIOSUBMIX_HPP

View File

@ -11,7 +11,7 @@ static std::vector<int16_t> scratch16;
static std::vector<int32_t> scratch32;
static std::vector<float> scratchFlt;
AudioVoice::AudioVoice(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb, bool dynamicRate)
AudioVoice::AudioVoice(IAudioHost& parent, IAudioVoiceCallback* cb, bool dynamicRate)
: m_parent(parent), m_cb(cb), m_dynamicRate(dynamicRate) {}
AudioVoice::~AudioVoice()
@ -47,12 +47,12 @@ void AudioVoice::unbindVoice()
{
if (m_bound)
{
m_parent.m_activeVoices.erase(m_parentIt);
m_parent._unbindFrom(m_parentIt);
m_bound = false;
}
}
AudioVoiceMono::AudioVoiceMono(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
AudioVoiceMono::AudioVoiceMono(IAudioHost& parent, IAudioVoiceCallback* cb,
double sampleRate, bool dynamicRate)
: AudioVoice(parent, cb, dynamicRate)
{
@ -142,7 +142,7 @@ void AudioVoiceMono::setStereoMatrixCoefficients(const float coefs[8][2])
m_matrix.setMatrixCoefficients(newCoefs);
}
AudioVoiceStereo::AudioVoiceStereo(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
AudioVoiceStereo::AudioVoiceStereo(IAudioHost& parent, IAudioVoiceCallback* cb,
double sampleRate, bool dynamicRate)
: AudioVoice(parent, cb, dynamicRate)
{

View File

@ -10,14 +10,16 @@ namespace boo
{
class BaseAudioVoiceEngine;
struct AudioVoiceEngineMixInfo;
class IAudioHost;
class AudioVoice : public IAudioVoice
{
friend class BaseAudioVoiceEngine;
friend class AudioSubmix;
protected:
/* Mixer-engine relationships */
BaseAudioVoiceEngine& m_parent;
IAudioHost& m_parent;
std::list<AudioVoice*>::iterator m_parentIt;
bool m_bound = false;
void bindVoice(std::list<AudioVoice*>::iterator pIt)
@ -39,7 +41,7 @@ protected:
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf)=0;
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf)=0;
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf)=0;
AudioVoice(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb, bool dynamicRate);
AudioVoice(IAudioHost& parent, IAudioVoiceCallback* cb, bool dynamicRate);
public:
~AudioVoice();
@ -62,7 +64,7 @@ class AudioVoiceMono : public AudioVoice
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf);
public:
AudioVoiceMono(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
AudioVoiceMono(IAudioHost& parent, IAudioVoiceCallback* cb,
double sampleRate, bool dynamicRate);
void setDefaultMatrixCoefficients();
void setMonoMatrixCoefficients(const float coefs[8]);
@ -81,7 +83,7 @@ class AudioVoiceStereo : public AudioVoice
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf);
public:
AudioVoiceStereo(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
AudioVoiceStereo(IAudioHost& parent, IAudioVoiceCallback* cb,
double sampleRate, bool dynamicRate);
void setDefaultMatrixCoefficients();
void setMonoMatrixCoefficients(const float coefs[8]);

View File

@ -10,6 +10,8 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
for (AudioVoice* vox : m_activeVoices)
if (vox->m_running)
vox->pumpAndMix(m_mixInfo, frames, dataOut);
for (AudioSubmix* smx : m_activeSubmixes)
smx->_pumpAndMixVoices(frames, dataOut);
}
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
@ -18,6 +20,8 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
for (AudioVoice* vox : m_activeVoices)
if (vox->m_running)
vox->pumpAndMix(m_mixInfo, frames, dataOut);
for (AudioSubmix* smx : m_activeSubmixes)
smx->_pumpAndMixVoices(frames, dataOut);
}
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
@ -26,12 +30,24 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
for (AudioVoice* vox : m_activeVoices)
if (vox->m_running)
vox->pumpAndMix(m_mixInfo, frames, dataOut);
for (AudioSubmix* smx : m_activeSubmixes)
smx->_pumpAndMixVoices(frames, dataOut);
}
void BaseAudioVoiceEngine::_unbindFrom(std::list<AudioVoice*>::iterator it)
{
m_activeVoices.erase(it);
}
void BaseAudioVoiceEngine::_unbindFrom(std::list<AudioSubmix*>::iterator it)
{
m_activeSubmixes.erase(it);
}
std::unique_ptr<IAudioVoice>
BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch)
IAudioVoiceCallback* cb,
bool dynamicPitch)
{
std::unique_ptr<IAudioVoice> ret =
std::make_unique<AudioVoiceMono>(*this, cb, sampleRate, dynamicPitch);
@ -42,8 +58,8 @@ BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate,
std::unique_ptr<IAudioVoice>
BaseAudioVoiceEngine::allocateNewStereoVoice(double sampleRate,
IAudioVoiceCallback* cb,
bool dynamicPitch)
IAudioVoiceCallback* cb,
bool dynamicPitch)
{
std::unique_ptr<IAudioVoice> ret =
std::make_unique<AudioVoiceStereo>(*this, cb, sampleRate, dynamicPitch);
@ -52,4 +68,19 @@ BaseAudioVoiceEngine::allocateNewStereoVoice(double sampleRate,
return ret;
}
std::unique_ptr<IAudioSubmix>
BaseAudioVoiceEngine::allocateNewSubmix(IAudioSubmixCallback* cb)
{
std::unique_ptr<IAudioSubmix> ret =
std::make_unique<AudioSubmix>(*this, cb);
AudioSubmix* retIntern = static_cast<AudioSubmix*>(ret.get());
retIntern->bindSubmix(m_activeSubmixes.insert(m_activeSubmixes.end(), retIntern));
return ret;
}
const AudioVoiceEngineMixInfo& BaseAudioVoiceEngine::mixInfo() const
{
return m_mixInfo;
}
}

View File

@ -3,6 +3,8 @@
#include "boo/audiodev/IAudioVoiceEngine.hpp"
#include "AudioVoice.hpp"
#include "AudioSubmix.hpp"
#include "IAudioHost.hpp"
namespace boo
{
@ -19,17 +21,22 @@ struct AudioVoiceEngineMixInfo
};
/** Base class for managing mixing and sample-rate-conversion amongst active voices */
class BaseAudioVoiceEngine : public IAudioVoiceEngine
class BaseAudioVoiceEngine : public IAudioVoiceEngine, public IAudioHost
{
protected:
friend class AudioVoice;
friend class AudioSubmix;
AudioVoiceEngineMixInfo m_mixInfo;
std::list<AudioVoice*> m_activeVoices;
std::list<AudioSubmix*> m_activeSubmixes;
void _pumpAndMixVoices(size_t frames, int16_t* dataOut);
void _pumpAndMixVoices(size_t frames, int32_t* dataOut);
void _pumpAndMixVoices(size_t frames, float* dataOut);
void _unbindFrom(std::list<AudioVoice*>::iterator it);
void _unbindFrom(std::list<AudioSubmix*>::iterator it);
public:
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb,
@ -39,7 +46,9 @@ public:
IAudioVoiceCallback* cb,
bool dynamicPitch=false);
const AudioVoiceEngineMixInfo& mixInfo() const {return m_mixInfo;}
std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb);
const AudioVoiceEngineMixInfo& mixInfo() const;
AudioChannelSet getAvailableSet() {return m_mixInfo.m_channels;}
void pumpAndMixVoices() {}
};

View File

@ -0,0 +1,25 @@
#ifndef BOO_IAUDIOHOST_HPP
#define BOO_IAUDIOHOST_HPP
#include <list>
namespace boo
{
class AudioVoiceEngineMixInfo;
class AudioVoice;
class AudioSubmix;
/** Entity that mixes audio from several child sources (engine root or submix) */
class IAudioHost
{
friend class AudioVoice;
friend class AudioSubmix;
virtual void _unbindFrom(std::list<AudioVoice*>::iterator it)=0;
virtual void _unbindFrom(std::list<AudioSubmix*>::iterator it)=0;
public:
virtual const AudioVoiceEngineMixInfo& mixInfo() const=0;
};
}
#endif // BOO_IAUDIOHOST_HPP