mirror of https://github.com/AxioDL/boo.git
Merge branch 'submix-refactor'
This commit is contained in:
commit
b68ae901c9
|
@ -205,7 +205,6 @@ add_library(boo
|
||||||
lib/audiodev/AudioVoice.cpp
|
lib/audiodev/AudioVoice.cpp
|
||||||
lib/audiodev/AudioSubmix.hpp
|
lib/audiodev/AudioSubmix.hpp
|
||||||
lib/audiodev/AudioSubmix.cpp
|
lib/audiodev/AudioSubmix.cpp
|
||||||
lib/audiodev/IAudioMix.hpp
|
|
||||||
lib/audiodev/MIDIEncoder.cpp
|
lib/audiodev/MIDIEncoder.cpp
|
||||||
lib/audiodev/MIDIDecoder.cpp
|
lib/audiodev/MIDIDecoder.cpp
|
||||||
lib/audiodev/MIDICommon.hpp
|
lib/audiodev/MIDICommon.hpp
|
||||||
|
|
|
@ -23,21 +23,11 @@ struct IAudioSubmix
|
||||||
{
|
{
|
||||||
virtual ~IAudioSubmix() = default;
|
virtual ~IAudioSubmix() = default;
|
||||||
|
|
||||||
/** Same as the IAudioVoice allocator, but produces audio within the submix */
|
/** Reset channel-levels to silence; unbind all submixes */
|
||||||
virtual std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
|
virtual void resetSendLevels()=0;
|
||||||
IAudioVoiceCallback* cb,
|
|
||||||
bool dynamicPitch=false)=0;
|
|
||||||
|
|
||||||
/** Same as allocateNewMonoVoice, but source audio is stereo-interleaved */
|
/** Set channel-levels for target submix (AudioChannel enum for array index) */
|
||||||
virtual std::unique_ptr<IAudioVoice> allocateNewStereoVoice(double sampleRate,
|
virtual void setSendLevel(IAudioSubmix* submix, float level, bool slew)=0;
|
||||||
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;
|
|
||||||
|
|
||||||
/** Sets gain factors for each channel once accumulated by the submix */
|
|
||||||
virtual void setChannelGains(const float gains[8])=0;
|
|
||||||
|
|
||||||
/** Gets fixed sample rate of submix this way */
|
/** Gets fixed sample rate of submix this way */
|
||||||
virtual double getSampleRate() const=0;
|
virtual double getSampleRate() const=0;
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
|
class IAudioSubmix;
|
||||||
|
|
||||||
enum class AudioChannelSet
|
enum class AudioChannelSet
|
||||||
{
|
{
|
||||||
|
@ -59,20 +61,14 @@ struct IAudioVoice
|
||||||
/** Set sample rate into voice (may result in audio discontinuities) */
|
/** Set sample rate into voice (may result in audio discontinuities) */
|
||||||
virtual void resetSampleRate(double sampleRate)=0;
|
virtual void resetSampleRate(double sampleRate)=0;
|
||||||
|
|
||||||
/** Reset channel-gains to voice defaults */
|
/** Reset channel-levels to silence; unbind all submixes */
|
||||||
virtual void setDefaultMatrixCoefficients()=0;
|
virtual void resetChannelLevels()=0;
|
||||||
|
|
||||||
/** Set channel-gains for mono audio source (AudioChannel enum for array index) */
|
/** Set channel-levels for mono audio source (AudioChannel enum for array index) */
|
||||||
virtual void setMonoMatrixCoefficients(const float coefs[8], bool slew)=0;
|
virtual void setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew)=0;
|
||||||
|
|
||||||
/** Set channel-gains for stereo audio source (AudioChannel enum for array index) */
|
/** Set channel-levels for stereo audio source (AudioChannel enum for array index) */
|
||||||
virtual void setStereoMatrixCoefficients(const float coefs[8][2], bool slew)=0;
|
virtual void setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew)=0;
|
||||||
|
|
||||||
/** Set submix-channel-gains for mono audio source (AudioChannel enum for array index) */
|
|
||||||
virtual void setMonoSubmixMatrixCoefficients(const float coefs[8], bool slew)=0;
|
|
||||||
|
|
||||||
/** Set submix-channel-gains for stereo audio source (AudioChannel enum for array index) */
|
|
||||||
virtual void setStereoSubmixMatrixCoefficients(const float coefs[8][2], bool slew)=0;
|
|
||||||
|
|
||||||
/** Called by client to dynamically adjust the pitch of voices with dynamic pitch enabled */
|
/** Called by client to dynamically adjust the pitch of voices with dynamic pitch enabled */
|
||||||
virtual void setPitchRatio(double ratio, bool slew)=0;
|
virtual void setPitchRatio(double ratio, bool slew)=0;
|
||||||
|
@ -96,6 +92,23 @@ struct IAudioVoiceCallback
|
||||||
/** boo calls this on behalf of the audio platform to request more audio
|
/** boo calls this on behalf of the audio platform to request more audio
|
||||||
* frames from the client */
|
* frames from the client */
|
||||||
virtual size_t supplyAudio(IAudioVoice& voice, size_t frames, int16_t* data)=0;
|
virtual size_t supplyAudio(IAudioVoice& voice, size_t frames, int16_t* data)=0;
|
||||||
|
|
||||||
|
/** after resampling, boo calls this for each submix that this voice targets;
|
||||||
|
* client performs volume processing and bus-routing this way */
|
||||||
|
virtual void routeAudio(size_t frames, double dt, int busId, int16_t* in, int16_t* out)
|
||||||
|
{
|
||||||
|
memmove(out, in, frames * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void routeAudio(size_t frames, double dt, int busId, int32_t* in, int32_t* out)
|
||||||
|
{
|
||||||
|
memmove(out, in, frames * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void routeAudio(size_t frames, double dt, int busId, float* in, float* out)
|
||||||
|
{
|
||||||
|
memmove(out, in, frames * 4);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ struct IAudioVoiceEngine
|
||||||
bool dynamicPitch=false)=0;
|
bool dynamicPitch=false)=0;
|
||||||
|
|
||||||
/** Client calls this to allocate a Submix for gathering audio together for effects processing */
|
/** Client calls this to allocate a Submix for gathering audio together for effects processing */
|
||||||
virtual std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb=nullptr)=0;
|
virtual std::unique_ptr<IAudioSubmix> allocateNewSubmix(bool mainOut, IAudioSubmixCallback* cb, int busId)=0;
|
||||||
|
|
||||||
/** Client may optionally register a 200-virtual-updates each second callback for the stream */
|
/** Client may optionally register a 200-virtual-updates each second callback for the stream */
|
||||||
virtual void register5MsCallback(std::function<void(double dt)>&& callback)=0;
|
virtual void register5MsCallback(std::function<void(double dt)>&& callback)=0;
|
||||||
|
|
|
@ -7,189 +7,347 @@
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
|
|
||||||
AudioSubmix::AudioSubmix(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioSubmixCallback* cb)
|
AudioSubmix::AudioSubmix(BaseAudioVoiceEngine& root, IAudioSubmixCallback* cb, int busId, bool mainOut)
|
||||||
: m_root(root), m_parent(parent), m_cb(cb)
|
: m_root(root), m_busId(busId), m_cb(cb), m_mainOut(mainOut)
|
||||||
{
|
{
|
||||||
std::fill(std::begin(m_gains), std::end(m_gains), 1.f);
|
if (mainOut)
|
||||||
|
setSendLevel(&m_root.m_mainSubmix, 1.f, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioSubmix::~AudioSubmix()
|
AudioSubmix::~AudioSubmix()
|
||||||
{
|
{
|
||||||
while (m_activeVoices.size())
|
|
||||||
m_activeVoices.front()->unbindVoice();
|
|
||||||
while (m_activeSubmixes.size())
|
|
||||||
m_activeSubmixes.front()->unbindSubmix();
|
|
||||||
unbindSubmix();
|
unbindSubmix();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::_pumpAndMixVoices(size_t frames, int16_t* dataOut, int16_t* mainOut)
|
bool AudioSubmix::_isDirectDependencyOf(AudioSubmix* send)
|
||||||
{
|
{
|
||||||
const AudioVoiceEngineMixInfo& info = mixInfo();
|
return m_sendGains.find(send) != m_sendGains.cend();
|
||||||
size_t sampleCount = frames * info.m_channelMap.m_channelCount;
|
}
|
||||||
|
|
||||||
|
bool AudioSubmix::_mergeC3(std::list<AudioSubmix*>& output,
|
||||||
|
std::vector<std::list<AudioSubmix*>>& lists)
|
||||||
|
{
|
||||||
|
for (auto outerIt = lists.begin() ; outerIt != lists.cend() ; ++outerIt)
|
||||||
|
{
|
||||||
|
if (outerIt->empty())
|
||||||
|
continue;
|
||||||
|
AudioSubmix* smx = outerIt->front();
|
||||||
|
bool found = false;
|
||||||
|
for (auto innerIt = lists.begin() ; innerIt != lists.cend() ; ++innerIt)
|
||||||
|
{
|
||||||
|
if (innerIt->empty() || outerIt == innerIt)
|
||||||
|
continue;
|
||||||
|
if (smx == innerIt->front())
|
||||||
|
{
|
||||||
|
innerIt->pop_front();
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
outerIt->pop_front();
|
||||||
|
output.push_back(smx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<AudioSubmix*> AudioSubmix::_linearizeC3()
|
||||||
|
{
|
||||||
|
std::vector<std::list<AudioSubmix*>> lists = {{}};
|
||||||
|
for (AudioSubmix* smx : m_root.m_activeSubmixes)
|
||||||
|
{
|
||||||
|
if (smx == this)
|
||||||
|
continue;
|
||||||
|
if (smx->_isDirectDependencyOf(this))
|
||||||
|
lists[0].push_back(smx);
|
||||||
|
}
|
||||||
|
lists.reserve(lists[0].size() + 1);
|
||||||
|
for (AudioSubmix* smx : lists[0])
|
||||||
|
lists.push_back(smx->_linearizeC3());
|
||||||
|
|
||||||
|
std::list<AudioSubmix*> ret = {this};
|
||||||
|
while (_mergeC3(ret, lists)) {}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSubmix::_zeroFill16()
|
||||||
|
{
|
||||||
|
if (m_scratch16.size())
|
||||||
|
std::fill(m_scratch16.begin(), m_scratch16.end(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSubmix::_zeroFill32()
|
||||||
|
{
|
||||||
|
if (m_scratch32.size())
|
||||||
|
std::fill(m_scratch32.begin(), m_scratch32.end(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSubmix::_zeroFillFlt()
|
||||||
|
{
|
||||||
|
if (m_scratchFlt.size())
|
||||||
|
std::fill(m_scratchFlt.begin(), m_scratchFlt.end(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t* AudioSubmix::_getMergeBuf16(size_t frames)
|
||||||
|
{
|
||||||
|
if (m_redirect16)
|
||||||
|
return m_redirect16;
|
||||||
|
|
||||||
|
size_t sampleCount = frames * m_root.m_mixInfo.m_channelMap.m_channelCount;
|
||||||
if (m_scratch16.size() < sampleCount)
|
if (m_scratch16.size() < sampleCount)
|
||||||
m_scratch16.resize(sampleCount);
|
m_scratch16.resize(sampleCount);
|
||||||
|
|
||||||
/* Clear target buffer */
|
return m_scratch16.data();
|
||||||
memset(m_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, mainOut, m_scratch16.data());
|
|
||||||
|
|
||||||
/* Pump child submixes */
|
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
|
||||||
smx->_pumpAndMixVoices(frames, m_scratch16.data(), mainOut);
|
|
||||||
|
|
||||||
/* Apply submix effect (if available) */
|
|
||||||
if (m_cb && m_cb->canApplyEffect())
|
|
||||||
m_cb->applyEffect(m_scratch16.data(), frames, info.m_channelMap, info.m_sampleRate);
|
|
||||||
|
|
||||||
/* Merge into output mix */
|
|
||||||
auto it = m_scratch16.begin();
|
|
||||||
for (size_t f=0 ; f<frames ; ++f)
|
|
||||||
for (size_t c=0 ; c<info.m_channelMap.m_channelCount ; ++c)
|
|
||||||
{
|
|
||||||
*dataOut = Clamp16(*dataOut + *it++ * m_gains[c]);
|
|
||||||
++dataOut;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::_pumpAndMixVoices(size_t frames, int32_t* dataOut, int32_t* mainOut)
|
int32_t* AudioSubmix::_getMergeBuf32(size_t frames)
|
||||||
{
|
{
|
||||||
const AudioVoiceEngineMixInfo& info = mixInfo();
|
if (m_redirect32)
|
||||||
size_t sampleCount = frames * info.m_channelMap.m_channelCount;
|
return m_redirect32;
|
||||||
|
|
||||||
|
size_t sampleCount = frames * m_root.m_mixInfo.m_channelMap.m_channelCount;
|
||||||
if (m_scratch32.size() < sampleCount)
|
if (m_scratch32.size() < sampleCount)
|
||||||
m_scratch32.resize(sampleCount);
|
m_scratch32.resize(sampleCount);
|
||||||
|
|
||||||
/* Clear target buffer */
|
return m_scratch32.data();
|
||||||
memset(m_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, mainOut, m_scratch32.data());
|
|
||||||
|
|
||||||
/* Pump child submixes */
|
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
|
||||||
smx->_pumpAndMixVoices(frames, m_scratch32.data(), mainOut);
|
|
||||||
|
|
||||||
/* Apply submix effect (if available) */
|
|
||||||
if (m_cb && m_cb->canApplyEffect())
|
|
||||||
m_cb->applyEffect(m_scratch32.data(), frames, info.m_channelMap, info.m_sampleRate);
|
|
||||||
|
|
||||||
/* Merge into output mix */
|
|
||||||
auto it = m_scratch32.begin();
|
|
||||||
for (size_t f=0 ; f<frames ; ++f)
|
|
||||||
for (size_t c=0 ; c<info.m_channelMap.m_channelCount ; ++c)
|
|
||||||
{
|
|
||||||
*dataOut = Clamp32(*dataOut + *it++ * m_gains[c]);
|
|
||||||
++dataOut;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::_pumpAndMixVoices(size_t frames, float* dataOut, float* mainOut)
|
float* AudioSubmix::_getMergeBufFlt(size_t frames)
|
||||||
{
|
{
|
||||||
const AudioVoiceEngineMixInfo& info = mixInfo();
|
if (m_redirectFlt)
|
||||||
size_t sampleCount = frames * info.m_channelMap.m_channelCount;
|
return m_redirectFlt;
|
||||||
|
|
||||||
|
size_t sampleCount = frames * m_root.m_mixInfo.m_channelMap.m_channelCount;
|
||||||
if (m_scratchFlt.size() < sampleCount)
|
if (m_scratchFlt.size() < sampleCount)
|
||||||
m_scratchFlt.resize(sampleCount);
|
m_scratchFlt.resize(sampleCount);
|
||||||
|
|
||||||
/* Clear target buffer */
|
return m_scratchFlt.data();
|
||||||
memset(m_scratchFlt.data(), 0, sizeof(float) * sampleCount);
|
}
|
||||||
|
|
||||||
/* Pump child voices */
|
size_t AudioSubmix::_pumpAndMix16(size_t frames)
|
||||||
for (AudioVoice* vox : m_activeVoices)
|
{
|
||||||
if (vox->m_running)
|
ChannelMap& chMap = m_root.m_mixInfo.m_channelMap;
|
||||||
vox->pumpAndMix(m_parent.mixInfo(), frames, mainOut, m_scratchFlt.data());
|
size_t chanCount = chMap.m_channelCount;
|
||||||
|
|
||||||
/* Pump child submixes */
|
if (m_redirect16)
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
{
|
||||||
smx->_pumpAndMixVoices(frames, m_scratchFlt.data(), mainOut);
|
if (m_cb && m_cb->canApplyEffect())
|
||||||
|
m_cb->applyEffect(m_redirect16, frames, chMap, m_root.m_mixInfo.m_sampleRate);
|
||||||
|
m_redirect16 += chanCount * frames;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t sampleCount = frames * chanCount;
|
||||||
|
if (m_scratch16.size() < sampleCount)
|
||||||
|
m_scratch16.resize(sampleCount);
|
||||||
|
if (m_cb && m_cb->canApplyEffect())
|
||||||
|
m_cb->applyEffect(m_scratch16.data(), frames, chMap, m_root.m_mixInfo.m_sampleRate);
|
||||||
|
|
||||||
/* Apply submix effect (if available) */
|
size_t curSlewFrame = m_slewFrames;
|
||||||
if (m_cb && m_cb->canApplyEffect())
|
for (auto& smx : m_sendGains)
|
||||||
m_cb->applyEffect(m_scratchFlt.data(), frames, info.m_channelMap, info.m_sampleRate);
|
|
||||||
|
|
||||||
/* Merge into output mix */
|
|
||||||
auto it = m_scratchFlt.begin();
|
|
||||||
for (size_t f=0 ; f<frames ; ++f)
|
|
||||||
for (size_t c=0 ; c<info.m_channelMap.m_channelCount ; ++c)
|
|
||||||
{
|
{
|
||||||
*dataOut = *dataOut + *it++ * m_gains[c];
|
curSlewFrame = m_curSlewFrame;
|
||||||
++dataOut;
|
AudioSubmix& sm = *reinterpret_cast<AudioSubmix*>(smx.first);
|
||||||
|
auto it = m_scratch16.begin();
|
||||||
|
int16_t* dataOut = sm._getMergeBuf16(frames);
|
||||||
|
|
||||||
|
for (size_t f=0 ; f<frames ; ++f)
|
||||||
|
{
|
||||||
|
if (m_slewFrames && curSlewFrame < m_slewFrames)
|
||||||
|
{
|
||||||
|
double t = curSlewFrame / double(m_slewFrames);
|
||||||
|
double omt = 1.0 - t;
|
||||||
|
|
||||||
|
for (unsigned c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
*dataOut = Clamp16(*dataOut + *it * (smx.second[1] * t + smx.second[0] * omt));
|
||||||
|
++it;
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
++curSlewFrame;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
*dataOut = Clamp16(*dataOut + *it * smx.second[1]);
|
||||||
|
++it;
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
m_curSlewFrame += curSlewFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::_unbindFrom(std::list<AudioVoice*>::iterator it)
|
size_t AudioSubmix::_pumpAndMix32(size_t frames)
|
||||||
{
|
{
|
||||||
m_activeVoices.erase(it);
|
ChannelMap& chMap = m_root.m_mixInfo.m_channelMap;
|
||||||
|
size_t chanCount = chMap.m_channelCount;
|
||||||
|
|
||||||
|
if (m_redirect32)
|
||||||
|
{
|
||||||
|
if (m_cb && m_cb->canApplyEffect())
|
||||||
|
m_cb->applyEffect(m_redirect32, frames, chMap, m_root.m_mixInfo.m_sampleRate);
|
||||||
|
m_redirect32 += chanCount * frames;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t sampleCount = frames * chanCount;
|
||||||
|
if (m_scratch32.size() < sampleCount)
|
||||||
|
m_scratch32.resize(sampleCount);
|
||||||
|
if (m_cb && m_cb->canApplyEffect())
|
||||||
|
m_cb->applyEffect(m_scratch32.data(), frames, chMap, m_root.m_mixInfo.m_sampleRate);
|
||||||
|
|
||||||
|
size_t curSlewFrame = m_slewFrames;
|
||||||
|
for (auto& smx : m_sendGains)
|
||||||
|
{
|
||||||
|
curSlewFrame = m_curSlewFrame;
|
||||||
|
AudioSubmix& sm = *reinterpret_cast<AudioSubmix*>(smx.first);
|
||||||
|
auto it = m_scratch32.begin();
|
||||||
|
int32_t* dataOut = sm._getMergeBuf32(frames);
|
||||||
|
|
||||||
|
for (size_t f=0 ; f<frames ; ++f)
|
||||||
|
{
|
||||||
|
if (m_slewFrames && curSlewFrame < m_slewFrames)
|
||||||
|
{
|
||||||
|
double t = curSlewFrame / double(m_slewFrames);
|
||||||
|
double omt = 1.0 - t;
|
||||||
|
|
||||||
|
for (unsigned c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
*dataOut = Clamp32(*dataOut + *it * (smx.second[1] * t + smx.second[0] * omt));
|
||||||
|
++it;
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
++curSlewFrame;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
*dataOut = Clamp32(*dataOut + *it * smx.second[1]);
|
||||||
|
++it;
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_curSlewFrame += curSlewFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::_unbindFrom(std::list<AudioSubmix*>::iterator it)
|
size_t AudioSubmix::_pumpAndMixFlt(size_t frames)
|
||||||
{
|
{
|
||||||
m_activeSubmixes.erase(it);
|
ChannelMap& chMap = m_root.m_mixInfo.m_channelMap;
|
||||||
|
size_t chanCount = chMap.m_channelCount;
|
||||||
|
|
||||||
|
if (m_redirectFlt)
|
||||||
|
{
|
||||||
|
if (m_cb && m_cb->canApplyEffect())
|
||||||
|
m_cb->applyEffect(m_redirectFlt, frames, chMap, m_root.m_mixInfo.m_sampleRate);
|
||||||
|
m_redirectFlt += chanCount * frames;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t sampleCount = frames * chanCount;
|
||||||
|
if (m_scratchFlt.size() < sampleCount)
|
||||||
|
m_scratchFlt.resize(sampleCount);
|
||||||
|
if (m_cb && m_cb->canApplyEffect())
|
||||||
|
m_cb->applyEffect(m_scratchFlt.data(), frames, chMap, m_root.m_mixInfo.m_sampleRate);
|
||||||
|
|
||||||
|
size_t curSlewFrame = m_slewFrames;
|
||||||
|
for (auto& smx : m_sendGains)
|
||||||
|
{
|
||||||
|
curSlewFrame = m_curSlewFrame;
|
||||||
|
AudioSubmix& sm = *reinterpret_cast<AudioSubmix*>(smx.first);
|
||||||
|
auto it = m_scratchFlt.begin();
|
||||||
|
float* dataOut = sm._getMergeBufFlt(frames);
|
||||||
|
|
||||||
|
for (size_t f=0 ; f<frames ; ++f)
|
||||||
|
{
|
||||||
|
if (m_slewFrames && curSlewFrame < m_slewFrames)
|
||||||
|
{
|
||||||
|
double t = curSlewFrame / double(m_slewFrames);
|
||||||
|
double omt = 1.0 - t;
|
||||||
|
|
||||||
|
for (unsigned c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
*dataOut = *dataOut + *it * (smx.second[1] * t + smx.second[0] * omt);
|
||||||
|
++it;
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
++curSlewFrame;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
*dataOut = *dataOut + *it * smx.second[1];
|
||||||
|
++it;
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_curSlewFrame += curSlewFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::_resetOutputSampleRate()
|
void AudioSubmix::_resetOutputSampleRate()
|
||||||
{
|
{
|
||||||
for (AudioVoice* vox : m_activeVoices)
|
|
||||||
vox->_resetSampleRate(vox->m_sampleRateIn);
|
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
|
||||||
smx->_resetOutputSampleRate();
|
|
||||||
if (m_cb)
|
if (m_cb)
|
||||||
m_cb->resetOutputSampleRate(m_parent.mixInfo().m_sampleRate);
|
m_cb->resetOutputSampleRate(m_root.mixInfo().m_sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoice> AudioSubmix::allocateNewMonoVoice(double sampleRate,
|
void AudioSubmix::resetSendLevels()
|
||||||
IAudioVoiceCallback* cb,
|
|
||||||
bool dynamicPitch)
|
|
||||||
{
|
{
|
||||||
std::unique_ptr<IAudioVoice> ret =
|
if (m_sendGains.empty())
|
||||||
std::make_unique<AudioVoiceMono>(m_root, *this, cb, sampleRate, dynamicPitch);
|
return;
|
||||||
AudioVoiceMono* retMono = static_cast<AudioVoiceMono*>(ret.get());
|
m_sendGains.clear();
|
||||||
retMono->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retMono));
|
m_root.m_submixesDirty = true;
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoice> AudioSubmix::allocateNewStereoVoice(double sampleRate,
|
void AudioSubmix::setSendLevel(IAudioSubmix* submix, float level, bool slew)
|
||||||
IAudioVoiceCallback* cb,
|
|
||||||
bool dynamicPitch)
|
|
||||||
{
|
{
|
||||||
std::unique_ptr<IAudioVoice> ret =
|
auto search = m_sendGains.find(submix);
|
||||||
std::make_unique<AudioVoiceStereo>(m_root, *this, cb, sampleRate, dynamicPitch);
|
if (search == m_sendGains.cend())
|
||||||
AudioVoiceStereo* retStereo = static_cast<AudioVoiceStereo*>(ret.get());
|
{
|
||||||
retStereo->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retStereo));
|
search = m_sendGains.emplace(submix, std::array<float, 2>{1.f, 1.f}).first;
|
||||||
return ret;
|
m_root.m_submixesDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IAudioSubmix> AudioSubmix::allocateNewSubmix(IAudioSubmixCallback* cb)
|
m_slewFrames = slew ? m_root.m_5msFrames : 0;
|
||||||
{
|
m_curSlewFrame = 0;
|
||||||
std::unique_ptr<IAudioSubmix> ret =
|
|
||||||
std::make_unique<AudioSubmix>(m_root, *this, cb);
|
|
||||||
AudioSubmix* retIntern = static_cast<AudioSubmix*>(ret.get());
|
|
||||||
retIntern->bindSubmix(m_activeSubmixes.insert(m_activeSubmixes.end(), retIntern));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioSubmix::setChannelGains(const float gains[8])
|
search->second[0] = search->second[1];
|
||||||
{
|
search->second[1] = level;
|
||||||
for (int i=0 ; i<8 ; ++i)
|
|
||||||
m_gains[i] = gains[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSubmix::unbindSubmix()
|
void AudioSubmix::unbindSubmix()
|
||||||
{
|
{
|
||||||
if (m_bound)
|
if (m_bound)
|
||||||
{
|
{
|
||||||
m_parent._unbindFrom(m_parentIt);
|
m_root._unbindFrom(m_parentIt);
|
||||||
m_bound = false;
|
m_bound = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AudioVoiceEngineMixInfo& AudioSubmix::mixInfo() const
|
const AudioVoiceEngineMixInfo& AudioSubmix::mixInfo() const
|
||||||
{
|
{
|
||||||
return m_parent.mixInfo();
|
return m_root.mixInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
double AudioSubmix::getSampleRate() const
|
double AudioSubmix::getSampleRate() const
|
||||||
|
|
|
@ -2,9 +2,13 @@
|
||||||
#define BOO_AUDIOSUBMIX_HPP
|
#define BOO_AUDIOSUBMIX_HPP
|
||||||
|
|
||||||
#include "boo/audiodev/IAudioSubmix.hpp"
|
#include "boo/audiodev/IAudioSubmix.hpp"
|
||||||
#include "IAudioMix.hpp"
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#if __SSE__
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
struct AudioUnitVoiceEngine;
|
struct AudioUnitVoiceEngine;
|
||||||
struct VSTVoiceEngine;
|
struct VSTVoiceEngine;
|
||||||
|
@ -14,10 +18,14 @@ namespace boo
|
||||||
{
|
{
|
||||||
class BaseAudioVoiceEngine;
|
class BaseAudioVoiceEngine;
|
||||||
class AudioVoice;
|
class AudioVoice;
|
||||||
|
struct AudioVoiceEngineMixInfo;
|
||||||
|
/* Output gains for each mix-send/channel */
|
||||||
|
|
||||||
class AudioSubmix : public IAudioSubmix, public IAudioMix
|
class AudioSubmix : public IAudioSubmix
|
||||||
{
|
{
|
||||||
friend class BaseAudioVoiceEngine;
|
friend class BaseAudioVoiceEngine;
|
||||||
|
friend class AudioVoiceMono;
|
||||||
|
friend class AudioVoiceStereo;
|
||||||
friend struct WASAPIAudioVoiceEngine;
|
friend struct WASAPIAudioVoiceEngine;
|
||||||
friend struct ::AudioUnitVoiceEngine;
|
friend struct ::AudioUnitVoiceEngine;
|
||||||
friend struct ::VSTVoiceEngine;
|
friend struct ::VSTVoiceEngine;
|
||||||
|
@ -25,8 +33,9 @@ class AudioSubmix : public IAudioSubmix, public IAudioMix
|
||||||
|
|
||||||
/* Mixer-engine relationships */
|
/* Mixer-engine relationships */
|
||||||
BaseAudioVoiceEngine& m_root;
|
BaseAudioVoiceEngine& m_root;
|
||||||
IAudioMix& m_parent;
|
int m_busId;
|
||||||
std::list<AudioSubmix*>::iterator m_parentIt;
|
std::list<AudioSubmix*>::iterator m_parentIt;
|
||||||
|
bool m_mainOut;
|
||||||
bool m_bound = false;
|
bool m_bound = false;
|
||||||
void bindSubmix(std::list<AudioSubmix*>::iterator pIt)
|
void bindSubmix(std::list<AudioSubmix*>::iterator pIt)
|
||||||
{
|
{
|
||||||
|
@ -37,41 +46,52 @@ class AudioSubmix : public IAudioSubmix, public IAudioMix
|
||||||
/* Callback (effect source, optional) */
|
/* Callback (effect source, optional) */
|
||||||
IAudioSubmixCallback* m_cb;
|
IAudioSubmixCallback* m_cb;
|
||||||
|
|
||||||
/* Audio sources */
|
/* Slew state for output gains */
|
||||||
std::list<AudioVoice*> m_activeVoices;
|
size_t m_slewFrames = 0;
|
||||||
std::list<AudioSubmix*> m_activeSubmixes;
|
size_t m_curSlewFrame = 0;
|
||||||
|
|
||||||
/* Output gains for each channel */
|
/* Output gains for each mix-send/channel */
|
||||||
float m_gains[8];
|
std::unordered_map<IAudioSubmix*, std::array<float, 2>> m_sendGains;
|
||||||
|
|
||||||
/* Temporary scratch buffers for accumulating submix audio */
|
/* Temporary scratch buffers for accumulating submix audio */
|
||||||
std::vector<int16_t> m_scratch16;
|
std::vector<int16_t> m_scratch16;
|
||||||
std::vector<int32_t> m_scratch32;
|
std::vector<int32_t> m_scratch32;
|
||||||
std::vector<float> m_scratchFlt;
|
std::vector<float> m_scratchFlt;
|
||||||
|
|
||||||
void _pumpAndMixVoices(size_t frames, int16_t* dataOut, int16_t* mainOut);
|
/* Override scratch buffers with alternate destination */
|
||||||
void _pumpAndMixVoices(size_t frames, int32_t* dataOut, int32_t* mainOut);
|
int16_t* m_redirect16 = nullptr;
|
||||||
void _pumpAndMixVoices(size_t frames, float* dataOut, float* mainOut);
|
int32_t* m_redirect32 = nullptr;
|
||||||
|
float* m_redirectFlt = nullptr;
|
||||||
|
|
||||||
void _unbindFrom(std::list<AudioVoice*>::iterator it);
|
/* C3-linearization support (to mitigate a potential diamond problem on 'clever' submix routes) */
|
||||||
void _unbindFrom(std::list<AudioSubmix*>::iterator it);
|
bool _isDirectDependencyOf(AudioSubmix* send);
|
||||||
|
std::list<AudioSubmix*> _linearizeC3();
|
||||||
|
static bool _mergeC3(std::list<AudioSubmix*>& output,
|
||||||
|
std::vector<std::list<AudioSubmix*>>& lists);
|
||||||
|
|
||||||
|
/* Fill scratch buffers with silence for new mix cycle */
|
||||||
|
void _zeroFill16();
|
||||||
|
void _zeroFill32();
|
||||||
|
void _zeroFillFlt();
|
||||||
|
|
||||||
|
/* Receive audio from a single voice / submix */
|
||||||
|
int16_t* _getMergeBuf16(size_t frames);
|
||||||
|
int32_t* _getMergeBuf32(size_t frames);
|
||||||
|
float* _getMergeBufFlt(size_t frames);
|
||||||
|
|
||||||
|
/* Mix scratch buffers into sends */
|
||||||
|
size_t _pumpAndMix16(size_t frames);
|
||||||
|
size_t _pumpAndMix32(size_t frames);
|
||||||
|
size_t _pumpAndMixFlt(size_t frames);
|
||||||
|
|
||||||
void _resetOutputSampleRate();
|
void _resetOutputSampleRate();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~AudioSubmix();
|
~AudioSubmix();
|
||||||
AudioSubmix(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioSubmixCallback* cb);
|
AudioSubmix(BaseAudioVoiceEngine& root, IAudioSubmixCallback* cb, int busId, bool mainOut);
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
|
void resetSendLevels();
|
||||||
IAudioVoiceCallback* cb,
|
void setSendLevel(IAudioSubmix* submix, float level, bool slew);
|
||||||
bool dynamicPitch=false);
|
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoice> allocateNewStereoVoice(double sampleRate,
|
|
||||||
IAudioVoiceCallback* cb,
|
|
||||||
bool dynamicPitch=false);
|
|
||||||
|
|
||||||
std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb=nullptr);
|
|
||||||
void setChannelGains(const float gains[8]);
|
|
||||||
void unbindSubmix();
|
void unbindSubmix();
|
||||||
const AudioVoiceEngineMixInfo& mixInfo() const;
|
const AudioVoiceEngineMixInfo& mixInfo() const;
|
||||||
double getSampleRate() const;
|
double getSampleRate() const;
|
||||||
|
|
|
@ -6,9 +6,9 @@ namespace boo
|
||||||
{
|
{
|
||||||
static logvisor::Module Log("boo::AudioVoice");
|
static logvisor::Module Log("boo::AudioVoice");
|
||||||
|
|
||||||
AudioVoice::AudioVoice(BaseAudioVoiceEngine& root, IAudioMix& parent,
|
AudioVoice::AudioVoice(BaseAudioVoiceEngine& root,
|
||||||
IAudioVoiceCallback* cb, bool dynamicRate)
|
IAudioVoiceCallback* cb, bool dynamicRate)
|
||||||
: m_root(root), m_parent(parent), m_cb(cb), m_dynamicRate(dynamicRate) {}
|
: m_root(root), m_cb(cb), m_dynamicRate(dynamicRate) {}
|
||||||
|
|
||||||
AudioVoice::~AudioVoice()
|
AudioVoice::~AudioVoice()
|
||||||
{
|
{
|
||||||
|
@ -66,14 +66,14 @@ void AudioVoice::unbindVoice()
|
||||||
{
|
{
|
||||||
if (m_bound)
|
if (m_bound)
|
||||||
{
|
{
|
||||||
m_parent._unbindFrom(m_parentIt);
|
m_root._unbindFrom(m_parentIt);
|
||||||
m_bound = false;
|
m_bound = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioVoiceMono::AudioVoiceMono(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioVoiceCallback* cb,
|
AudioVoiceMono::AudioVoiceMono(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb,
|
||||||
double sampleRate, bool dynamicRate)
|
double sampleRate, bool dynamicRate)
|
||||||
: AudioVoice(root, parent, cb, dynamicRate)
|
: AudioVoice(root, cb, dynamicRate)
|
||||||
{
|
{
|
||||||
_resetSampleRate(sampleRate);
|
_resetSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,8 @@ void AudioVoiceMono::_resetSampleRate(double sampleRate)
|
||||||
{
|
{
|
||||||
soxr_delete(m_src);
|
soxr_delete(m_src);
|
||||||
|
|
||||||
double rateOut = m_parent.mixInfo().m_sampleRate;
|
double rateOut = m_root.mixInfo().m_sampleRate;
|
||||||
soxr_datatype_t formatOut = m_parent.mixInfo().m_sampleFormat;
|
soxr_datatype_t formatOut = m_root.mixInfo().m_sampleFormat;
|
||||||
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_INT16_I, formatOut);
|
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_INT16_I, formatOut);
|
||||||
soxr_quality_spec_t qSpec = soxr_quality_spec(SOXR_20_BITQ, m_dynamicRate ? SOXR_VR : 0);
|
soxr_quality_spec_t qSpec = soxr_quality_spec(SOXR_20_BITQ, m_dynamicRate ? SOXR_VR : 0);
|
||||||
|
|
||||||
|
@ -120,82 +120,108 @@ size_t AudioVoiceMono::SRCCallback(AudioVoiceMono* ctx, int16_t** data, size_t f
|
||||||
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
|
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioVoiceMono::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
size_t AudioVoiceMono::pumpAndMix16(size_t frames)
|
||||||
size_t frames, int16_t* buf, int16_t* rbuf)
|
|
||||||
{
|
{
|
||||||
std::vector<int16_t>& scratch16 = m_root.m_scratch16;
|
std::vector<int16_t>& scratch16Pre = m_root.m_scratch16Pre;
|
||||||
if (scratch16.size() < frames)
|
if (scratch16Pre.size() < frames)
|
||||||
scratch16.resize(frames);
|
scratch16Pre.resize(frames);
|
||||||
|
|
||||||
m_cb->preSupplyAudio(*this, frames / m_sampleRateOut);
|
std::vector<int16_t>& scratch16Post = m_root.m_scratch16Post;
|
||||||
|
if (scratch16Post.size() < frames)
|
||||||
|
scratch16Post.resize(frames);
|
||||||
|
|
||||||
|
double dt = frames / m_sampleRateOut;
|
||||||
|
m_cb->preSupplyAudio(*this, dt);
|
||||||
_midUpdate();
|
_midUpdate();
|
||||||
size_t oDone = soxr_output(m_src, scratch16.data(), frames);
|
size_t oDone = soxr_output(m_src, scratch16Pre.data(), frames);
|
||||||
|
|
||||||
if (oDone)
|
if (oDone)
|
||||||
{
|
{
|
||||||
m_matrix.mixMonoSampleData(mixInfo, scratch16.data(), buf, oDone);
|
for (auto& mtx : m_sendMatrices)
|
||||||
if (rbuf)
|
{
|
||||||
m_subMatrix.mixMonoSampleData(mixInfo, scratch16.data(), rbuf, oDone);
|
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
|
||||||
|
m_cb->routeAudio(oDone, dt, smx.m_busId, scratch16Pre.data(), scratch16Post.data());
|
||||||
|
mtx.second.mixMonoSampleData(m_root.m_mixInfo, scratch16Post.data(), smx._getMergeBuf16(oDone), oDone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oDone;
|
return oDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioVoiceMono::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
size_t AudioVoiceMono::pumpAndMix32(size_t frames)
|
||||||
size_t frames, int32_t* buf, int32_t* rbuf)
|
|
||||||
{
|
{
|
||||||
std::vector<int32_t>& scratch32 = m_root.m_scratch32;
|
std::vector<int32_t>& scratch32Pre = m_root.m_scratch32Pre;
|
||||||
if (scratch32.size() < frames)
|
if (scratch32Pre.size() < frames)
|
||||||
scratch32.resize(frames);
|
scratch32Pre.resize(frames);
|
||||||
|
|
||||||
m_cb->preSupplyAudio(*this, frames / m_sampleRateOut);
|
std::vector<int32_t>& scratch32Post = m_root.m_scratch32Post;
|
||||||
|
if (scratch32Post.size() < frames)
|
||||||
|
scratch32Post.resize(frames);
|
||||||
|
|
||||||
|
double dt = frames / m_sampleRateOut;
|
||||||
|
m_cb->preSupplyAudio(*this, dt);
|
||||||
_midUpdate();
|
_midUpdate();
|
||||||
size_t oDone = soxr_output(m_src, scratch32.data(), frames);
|
size_t oDone = soxr_output(m_src, scratch32Pre.data(), frames);
|
||||||
|
|
||||||
if (oDone)
|
if (oDone)
|
||||||
{
|
{
|
||||||
m_matrix.mixMonoSampleData(mixInfo, scratch32.data(), buf, oDone);
|
for (auto& mtx : m_sendMatrices)
|
||||||
if (rbuf)
|
{
|
||||||
m_subMatrix.mixMonoSampleData(mixInfo, scratch32.data(), rbuf, oDone);
|
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
|
||||||
|
m_cb->routeAudio(oDone, dt, smx.m_busId, scratch32Pre.data(), scratch32Post.data());
|
||||||
|
mtx.second.mixMonoSampleData(m_root.m_mixInfo, scratch32Post.data(), smx._getMergeBuf32(oDone), oDone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oDone;
|
return oDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioVoiceMono::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
size_t AudioVoiceMono::pumpAndMixFlt(size_t frames)
|
||||||
size_t frames, float* buf, float* rbuf)
|
|
||||||
{
|
{
|
||||||
std::vector<float>& scratchFlt = m_root.m_scratchFlt;
|
std::vector<float>& scratchFltPre = m_root.m_scratchFltPre;
|
||||||
if (scratchFlt.size() < frames)
|
if (scratchFltPre.size() < frames)
|
||||||
scratchFlt.resize(frames + 2);
|
scratchFltPre.resize(frames + 2);
|
||||||
|
|
||||||
m_cb->preSupplyAudio(*this, frames / m_sampleRateOut);
|
std::vector<float>& scratchFltPost = m_root.m_scratchFltPost;
|
||||||
|
if (scratchFltPost.size() < frames)
|
||||||
|
scratchFltPost.resize(frames + 2);
|
||||||
|
|
||||||
|
double dt = frames / m_sampleRateOut;
|
||||||
|
m_cb->preSupplyAudio(*this, dt);
|
||||||
_midUpdate();
|
_midUpdate();
|
||||||
size_t oDone = soxr_output(m_src, scratchFlt.data(), frames);
|
size_t oDone = soxr_output(m_src, scratchFltPre.data(), frames);
|
||||||
|
|
||||||
if (oDone)
|
if (oDone)
|
||||||
{
|
{
|
||||||
m_matrix.mixMonoSampleData(mixInfo, scratchFlt.data(), buf, oDone);
|
for (auto& mtx : m_sendMatrices)
|
||||||
if (rbuf)
|
{
|
||||||
m_subMatrix.mixMonoSampleData(mixInfo, scratchFlt.data(), rbuf, oDone);
|
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
|
||||||
|
m_cb->routeAudio(oDone, dt, smx.m_busId, scratchFltPre.data(), scratchFltPost.data());
|
||||||
|
mtx.second.mixMonoSampleData(m_root.m_mixInfo, scratchFltPost.data(), smx._getMergeBufFlt(oDone), oDone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oDone;
|
return oDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVoiceMono::setDefaultMatrixCoefficients()
|
void AudioVoiceMono::resetChannelLevels()
|
||||||
{
|
{
|
||||||
m_matrix.setDefaultMatrixCoefficients(m_parent.mixInfo().m_channels);
|
m_root.m_submixesDirty = true;
|
||||||
float zero[8] = {};
|
m_sendMatrices.clear();
|
||||||
m_subMatrix.setMatrixCoefficients(zero);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVoiceMono::setMonoMatrixCoefficients(const float coefs[8], bool slew)
|
void AudioVoiceMono::setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew)
|
||||||
{
|
{
|
||||||
m_matrix.setMatrixCoefficients(coefs, slew ? m_root.m_5msFrames : 0);
|
auto search = m_sendMatrices.find(submix);
|
||||||
|
if (search == m_sendMatrices.cend())
|
||||||
|
{
|
||||||
|
search = m_sendMatrices.emplace(submix, AudioMatrixMono{}).first;
|
||||||
|
m_root.m_submixesDirty = true;
|
||||||
|
}
|
||||||
|
search->second.setMatrixCoefficients(coefs, slew ? m_root.m_5msFrames : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVoiceMono::setStereoMatrixCoefficients(const float coefs[8][2], bool slew)
|
void AudioVoiceMono::setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew)
|
||||||
{
|
{
|
||||||
float newCoefs[8] =
|
float newCoefs[8] =
|
||||||
{
|
{
|
||||||
|
@ -208,33 +234,19 @@ void AudioVoiceMono::setStereoMatrixCoefficients(const float coefs[8][2], bool s
|
||||||
coefs[6][0],
|
coefs[6][0],
|
||||||
coefs[7][0]
|
coefs[7][0]
|
||||||
};
|
};
|
||||||
m_matrix.setMatrixCoefficients(newCoefs, slew ? m_root.m_5msFrames : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioVoiceMono::setMonoSubmixMatrixCoefficients(const float coefs[8], bool slew)
|
auto search = m_sendMatrices.find(submix);
|
||||||
{
|
if (search == m_sendMatrices.cend())
|
||||||
m_subMatrix.setMatrixCoefficients(coefs, slew ? m_root.m_5msFrames : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioVoiceMono::setStereoSubmixMatrixCoefficients(const float coefs[8][2], bool slew)
|
|
||||||
{
|
|
||||||
float newCoefs[8] =
|
|
||||||
{
|
{
|
||||||
coefs[0][0],
|
search = m_sendMatrices.emplace(submix, AudioMatrixMono{}).first;
|
||||||
coefs[1][0],
|
m_root.m_submixesDirty = true;
|
||||||
coefs[2][0],
|
}
|
||||||
coefs[3][0],
|
search->second.setMatrixCoefficients(newCoefs, slew ? m_root.m_5msFrames : 0);
|
||||||
coefs[4][0],
|
|
||||||
coefs[5][0],
|
|
||||||
coefs[6][0],
|
|
||||||
coefs[7][0]
|
|
||||||
};
|
|
||||||
m_subMatrix.setMatrixCoefficients(newCoefs, slew ? m_root.m_5msFrames : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioVoiceStereo::AudioVoiceStereo(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioVoiceCallback* cb,
|
AudioVoiceStereo::AudioVoiceStereo(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb,
|
||||||
double sampleRate, bool dynamicRate)
|
double sampleRate, bool dynamicRate)
|
||||||
: AudioVoice(root, parent, cb, dynamicRate)
|
: AudioVoice(root, cb, dynamicRate)
|
||||||
{
|
{
|
||||||
_resetSampleRate(sampleRate);
|
_resetSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
@ -243,8 +255,8 @@ void AudioVoiceStereo::_resetSampleRate(double sampleRate)
|
||||||
{
|
{
|
||||||
soxr_delete(m_src);
|
soxr_delete(m_src);
|
||||||
|
|
||||||
double rateOut = m_parent.mixInfo().m_sampleRate;
|
double rateOut = m_root.mixInfo().m_sampleRate;
|
||||||
soxr_datatype_t formatOut = m_parent.mixInfo().m_sampleFormat;
|
soxr_datatype_t formatOut = m_root.mixInfo().m_sampleFormat;
|
||||||
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_INT16_I, formatOut);
|
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_INT16_I, formatOut);
|
||||||
soxr_quality_spec_t qSpec = soxr_quality_spec(SOXR_20_BITQ, m_dynamicRate ? SOXR_VR : 0);
|
soxr_quality_spec_t qSpec = soxr_quality_spec(SOXR_20_BITQ, m_dynamicRate ? SOXR_VR : 0);
|
||||||
|
|
||||||
|
@ -282,80 +294,103 @@ size_t AudioVoiceStereo::SRCCallback(AudioVoiceStereo* ctx, int16_t** data, size
|
||||||
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
|
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioVoiceStereo::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
size_t AudioVoiceStereo::pumpAndMix16(size_t frames)
|
||||||
size_t frames, int16_t* buf, int16_t* rbuf)
|
|
||||||
{
|
{
|
||||||
std::vector<int16_t>& scratch16 = m_root.m_scratch16;
|
|
||||||
size_t samples = frames * 2;
|
size_t samples = frames * 2;
|
||||||
if (scratch16.size() < samples)
|
|
||||||
scratch16.resize(samples);
|
|
||||||
|
|
||||||
m_cb->preSupplyAudio(*this, frames / m_sampleRateOut);
|
std::vector<int16_t>& scratch16Pre = m_root.m_scratch16Pre;
|
||||||
|
if (scratch16Pre.size() < samples)
|
||||||
|
scratch16Pre.resize(samples);
|
||||||
|
|
||||||
|
std::vector<int16_t>& scratch16Post = m_root.m_scratch16Post;
|
||||||
|
if (scratch16Post.size() < samples)
|
||||||
|
scratch16Post.resize(samples);
|
||||||
|
|
||||||
|
double dt = frames / m_sampleRateOut;
|
||||||
|
m_cb->preSupplyAudio(*this, dt);
|
||||||
_midUpdate();
|
_midUpdate();
|
||||||
size_t oDone = soxr_output(m_src, scratch16.data(), frames);
|
size_t oDone = soxr_output(m_src, scratch16Pre.data(), frames);
|
||||||
|
|
||||||
if (oDone)
|
if (oDone)
|
||||||
{
|
{
|
||||||
m_matrix.mixStereoSampleData(mixInfo, scratch16.data(), buf, oDone);
|
for (auto& mtx : m_sendMatrices)
|
||||||
if (rbuf)
|
{
|
||||||
m_subMatrix.mixStereoSampleData(mixInfo, scratch16.data(), rbuf, oDone);
|
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
|
||||||
|
m_cb->routeAudio(oDone, dt, smx.m_busId, scratch16Pre.data(), scratch16Post.data());
|
||||||
|
mtx.second.mixStereoSampleData(m_root.m_mixInfo, scratch16Post.data(), smx._getMergeBuf16(oDone), oDone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oDone;
|
return oDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioVoiceStereo::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
size_t AudioVoiceStereo::pumpAndMix32(size_t frames)
|
||||||
size_t frames, int32_t* buf, int32_t* rbuf)
|
|
||||||
{
|
{
|
||||||
std::vector<int32_t>& scratch32 = m_root.m_scratch32;
|
|
||||||
size_t samples = frames * 2;
|
size_t samples = frames * 2;
|
||||||
if (scratch32.size() < samples)
|
|
||||||
scratch32.resize(samples);
|
|
||||||
|
|
||||||
m_cb->preSupplyAudio(*this, frames / m_sampleRateOut);
|
std::vector<int32_t>& scratch32Pre = m_root.m_scratch32Pre;
|
||||||
|
if (scratch32Pre.size() < samples)
|
||||||
|
scratch32Pre.resize(samples);
|
||||||
|
|
||||||
|
std::vector<int32_t>& scratch32Post = m_root.m_scratch32Post;
|
||||||
|
if (scratch32Post.size() < samples)
|
||||||
|
scratch32Post.resize(samples);
|
||||||
|
|
||||||
|
double dt = frames / m_sampleRateOut;
|
||||||
|
m_cb->preSupplyAudio(*this, dt);
|
||||||
_midUpdate();
|
_midUpdate();
|
||||||
size_t oDone = soxr_output(m_src, scratch32.data(), frames);
|
size_t oDone = soxr_output(m_src, scratch32Pre.data(), frames);
|
||||||
|
|
||||||
if (oDone)
|
if (oDone)
|
||||||
{
|
{
|
||||||
m_matrix.mixStereoSampleData(mixInfo, scratch32.data(), buf, oDone);
|
for (auto& mtx : m_sendMatrices)
|
||||||
if (rbuf)
|
{
|
||||||
m_subMatrix.mixStereoSampleData(mixInfo, scratch32.data(), rbuf, oDone);
|
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
|
||||||
|
m_cb->routeAudio(oDone, dt, smx.m_busId, scratch32Pre.data(), scratch32Post.data());
|
||||||
|
mtx.second.mixStereoSampleData(m_root.m_mixInfo, scratch32Post.data(), smx._getMergeBuf32(oDone), oDone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oDone;
|
return oDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioVoiceStereo::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
size_t AudioVoiceStereo::pumpAndMixFlt(size_t frames)
|
||||||
size_t frames, float* buf, float* rbuf)
|
|
||||||
{
|
{
|
||||||
std::vector<float>& scratchFlt = m_root.m_scratchFlt;
|
|
||||||
size_t samples = frames * 2;
|
size_t samples = frames * 2;
|
||||||
if (scratchFlt.size() < samples)
|
|
||||||
scratchFlt.resize(samples + 4);
|
|
||||||
|
|
||||||
m_cb->preSupplyAudio(*this, frames / m_sampleRateOut);
|
std::vector<float>& scratchFltPre = m_root.m_scratchFltPre;
|
||||||
|
if (scratchFltPre.size() < samples)
|
||||||
|
scratchFltPre.resize(samples + 4);
|
||||||
|
|
||||||
|
std::vector<float>& scratchFltPost = m_root.m_scratchFltPost;
|
||||||
|
if (scratchFltPost.size() < samples)
|
||||||
|
scratchFltPost.resize(samples + 4);
|
||||||
|
|
||||||
|
double dt = frames / m_sampleRateOut;
|
||||||
|
m_cb->preSupplyAudio(*this, dt);
|
||||||
_midUpdate();
|
_midUpdate();
|
||||||
size_t oDone = soxr_output(m_src, scratchFlt.data(), frames);
|
size_t oDone = soxr_output(m_src, scratchFltPre.data(), frames);
|
||||||
|
|
||||||
if (oDone)
|
if (oDone)
|
||||||
{
|
{
|
||||||
m_matrix.mixStereoSampleData(mixInfo, scratchFlt.data(), buf, oDone);
|
for (auto& mtx : m_sendMatrices)
|
||||||
if (rbuf)
|
{
|
||||||
m_subMatrix.mixStereoSampleData(mixInfo, scratchFlt.data(), rbuf, oDone);
|
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
|
||||||
|
m_cb->routeAudio(oDone, dt, smx.m_busId, scratchFltPre.data(), scratchFltPost.data());
|
||||||
|
mtx.second.mixStereoSampleData(m_root.m_mixInfo, scratchFltPost.data(), smx._getMergeBufFlt(oDone), oDone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oDone;
|
return oDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVoiceStereo::setDefaultMatrixCoefficients()
|
void AudioVoiceStereo::resetChannelLevels()
|
||||||
{
|
{
|
||||||
m_matrix.setDefaultMatrixCoefficients(m_parent.mixInfo().m_channels);
|
m_root.m_submixesDirty = true;
|
||||||
float zero[8][2] = {{}};
|
m_sendMatrices.clear();
|
||||||
m_subMatrix.setMatrixCoefficients(zero);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVoiceStereo::setMonoMatrixCoefficients(const float coefs[8], bool slew)
|
void AudioVoiceStereo::setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew)
|
||||||
{
|
{
|
||||||
float newCoefs[8][2] =
|
float newCoefs[8][2] =
|
||||||
{
|
{
|
||||||
|
@ -368,33 +403,25 @@ void AudioVoiceStereo::setMonoMatrixCoefficients(const float coefs[8], bool slew
|
||||||
{coefs[6], coefs[6]},
|
{coefs[6], coefs[6]},
|
||||||
{coefs[7], coefs[7]}
|
{coefs[7], coefs[7]}
|
||||||
};
|
};
|
||||||
m_matrix.setMatrixCoefficients(newCoefs, slew ? m_root.m_5msFrames : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioVoiceStereo::setStereoMatrixCoefficients(const float coefs[8][2], bool slew)
|
auto search = m_sendMatrices.find(submix);
|
||||||
{
|
if (search == m_sendMatrices.cend())
|
||||||
m_matrix.setMatrixCoefficients(coefs, slew ? m_root.m_5msFrames : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioVoiceStereo::setMonoSubmixMatrixCoefficients(const float coefs[8], bool slew)
|
|
||||||
{
|
|
||||||
float newCoefs[8][2] =
|
|
||||||
{
|
{
|
||||||
{coefs[0], coefs[0]},
|
search = m_sendMatrices.emplace(submix, AudioMatrixStereo{}).first;
|
||||||
{coefs[1], coefs[1]},
|
m_root.m_submixesDirty = true;
|
||||||
{coefs[2], coefs[2]},
|
}
|
||||||
{coefs[3], coefs[3]},
|
search->second.setMatrixCoefficients(newCoefs, slew ? m_root.m_5msFrames : 0);
|
||||||
{coefs[4], coefs[4]},
|
|
||||||
{coefs[5], coefs[5]},
|
|
||||||
{coefs[6], coefs[6]},
|
|
||||||
{coefs[7], coefs[7]}
|
|
||||||
};
|
|
||||||
m_subMatrix.setMatrixCoefficients(newCoefs, slew ? m_root.m_5msFrames : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVoiceStereo::setStereoSubmixMatrixCoefficients(const float coefs[8][2], bool slew)
|
void AudioVoiceStereo::setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew)
|
||||||
{
|
{
|
||||||
m_subMatrix.setMatrixCoefficients(coefs, slew ? m_root.m_5msFrames : 0);
|
auto search = m_sendMatrices.find(submix);
|
||||||
|
if (search == m_sendMatrices.cend())
|
||||||
|
{
|
||||||
|
search = m_sendMatrices.emplace(submix, AudioMatrixStereo{}).first;
|
||||||
|
m_root.m_submixesDirty = true;
|
||||||
|
}
|
||||||
|
search->second.setMatrixCoefficients(coefs, slew ? m_root.m_5msFrames : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <soxr.h>
|
#include <soxr.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <unordered_map>
|
||||||
#include "boo/audiodev/IAudioVoice.hpp"
|
#include "boo/audiodev/IAudioVoice.hpp"
|
||||||
#include "AudioMatrix.hpp"
|
#include "AudioMatrix.hpp"
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ namespace boo
|
||||||
{
|
{
|
||||||
class BaseAudioVoiceEngine;
|
class BaseAudioVoiceEngine;
|
||||||
struct AudioVoiceEngineMixInfo;
|
struct AudioVoiceEngineMixInfo;
|
||||||
class IAudioMix;
|
class IAudioSubmix;
|
||||||
|
|
||||||
class AudioVoice : public IAudioVoice
|
class AudioVoice : public IAudioVoice
|
||||||
{
|
{
|
||||||
|
@ -28,7 +29,6 @@ class AudioVoice : public IAudioVoice
|
||||||
protected:
|
protected:
|
||||||
/* Mixer-engine relationships */
|
/* Mixer-engine relationships */
|
||||||
BaseAudioVoiceEngine& m_root;
|
BaseAudioVoiceEngine& m_root;
|
||||||
IAudioMix& m_parent;
|
|
||||||
std::list<AudioVoice*>::iterator m_parentIt;
|
std::list<AudioVoice*>::iterator m_parentIt;
|
||||||
bool m_bound = false;
|
bool m_bound = false;
|
||||||
void bindVoice(std::list<AudioVoice*>::iterator pIt)
|
void bindVoice(std::list<AudioVoice*>::iterator pIt)
|
||||||
|
@ -63,10 +63,11 @@ protected:
|
||||||
/* Mid-pump update */
|
/* Mid-pump update */
|
||||||
void _midUpdate();
|
void _midUpdate();
|
||||||
|
|
||||||
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf, int16_t* rbuf)=0;
|
virtual size_t pumpAndMix16(size_t frames)=0;
|
||||||
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf, int32_t* rbuf)=0;
|
virtual size_t pumpAndMix32(size_t frames)=0;
|
||||||
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf, float* rbuf)=0;
|
virtual size_t pumpAndMixFlt(size_t frames)=0;
|
||||||
AudioVoice(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioVoiceCallback* cb, bool dynamicRate);
|
|
||||||
|
AudioVoice(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb, bool dynamicRate);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~AudioVoice();
|
~AudioVoice();
|
||||||
|
@ -81,50 +82,44 @@ public:
|
||||||
|
|
||||||
class AudioVoiceMono : public AudioVoice
|
class AudioVoiceMono : public AudioVoice
|
||||||
{
|
{
|
||||||
AudioMatrixMono m_matrix;
|
std::unordered_map<IAudioSubmix*, AudioMatrixMono> m_sendMatrices;
|
||||||
AudioMatrixMono m_subMatrix;
|
|
||||||
bool m_silentOut = false;
|
bool m_silentOut = false;
|
||||||
void _resetSampleRate(double sampleRate);
|
void _resetSampleRate(double sampleRate);
|
||||||
|
|
||||||
static size_t SRCCallback(AudioVoiceMono* ctx,
|
static size_t SRCCallback(AudioVoiceMono* ctx,
|
||||||
int16_t** data, size_t requestedLen);
|
int16_t** data, size_t requestedLen);
|
||||||
|
|
||||||
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf, int16_t* rbuf);
|
size_t pumpAndMix16(size_t frames);
|
||||||
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf, int32_t* rbuf);
|
size_t pumpAndMix32(size_t frames);
|
||||||
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf, float* rbuf);
|
size_t pumpAndMixFlt(size_t frames);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioVoiceMono(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioVoiceCallback* cb,
|
AudioVoiceMono(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb,
|
||||||
double sampleRate, bool dynamicRate);
|
double sampleRate, bool dynamicRate);
|
||||||
void setDefaultMatrixCoefficients();
|
void resetChannelLevels();
|
||||||
void setMonoMatrixCoefficients(const float coefs[8], bool slew);
|
void setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew);
|
||||||
void setStereoMatrixCoefficients(const float coefs[8][2], bool slew);
|
void setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew);
|
||||||
void setMonoSubmixMatrixCoefficients(const float coefs[8], bool slew);
|
|
||||||
void setStereoSubmixMatrixCoefficients(const float coefs[8][2], bool slew);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioVoiceStereo : public AudioVoice
|
class AudioVoiceStereo : public AudioVoice
|
||||||
{
|
{
|
||||||
AudioMatrixStereo m_matrix;
|
std::unordered_map<IAudioSubmix*, AudioMatrixStereo> m_sendMatrices;
|
||||||
AudioMatrixStereo m_subMatrix;
|
|
||||||
bool m_silentOut = false;
|
bool m_silentOut = false;
|
||||||
void _resetSampleRate(double sampleRate);
|
void _resetSampleRate(double sampleRate);
|
||||||
|
|
||||||
static size_t SRCCallback(AudioVoiceStereo* ctx,
|
static size_t SRCCallback(AudioVoiceStereo* ctx,
|
||||||
int16_t** data, size_t requestedLen);
|
int16_t** data, size_t requestedLen);
|
||||||
|
|
||||||
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf, int16_t* rbuf);
|
size_t pumpAndMix16(size_t frames);
|
||||||
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf, int32_t* rbuf);
|
size_t pumpAndMix32(size_t frames);
|
||||||
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf, float* rbuf);
|
size_t pumpAndMixFlt(size_t frames);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioVoiceStereo(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioVoiceCallback* cb,
|
AudioVoiceStereo(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb,
|
||||||
double sampleRate, bool dynamicRate);
|
double sampleRate, bool dynamicRate);
|
||||||
void setDefaultMatrixCoefficients();
|
void resetChannelLevels();
|
||||||
void setMonoMatrixCoefficients(const float coefs[8], bool slew);
|
void setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew);
|
||||||
void setStereoMatrixCoefficients(const float coefs[8][2], bool slew);
|
void setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew);
|
||||||
void setMonoSubmixMatrixCoefficients(const float coefs[8], bool slew);
|
|
||||||
void setStereoSubmixMatrixCoefficients(const float coefs[8][2], bool slew);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,13 @@ BaseAudioVoiceEngine::~BaseAudioVoiceEngine()
|
||||||
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
|
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
|
||||||
{
|
{
|
||||||
memset(dataOut, 0, sizeof(int16_t) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
memset(dataOut, 0, sizeof(int16_t) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
m_mainSubmix.m_redirect16 = dataOut;
|
||||||
|
|
||||||
|
if (m_submixesDirty)
|
||||||
|
{
|
||||||
|
m_linearizedSubmixes = m_mainSubmix._linearizeC3();
|
||||||
|
m_submixesDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t remFrames = frames;
|
size_t remFrames = frames;
|
||||||
while (remFrames)
|
while (remFrames)
|
||||||
|
@ -33,11 +40,16 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
|
||||||
m_5msCallback(5.0 / 1000.0);
|
m_5msCallback(5.0 / 1000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = m_linearizedSubmixes.rbegin() ; it != m_linearizedSubmixes.rend() ; ++it)
|
||||||
|
(*it)->_zeroFill16();
|
||||||
|
|
||||||
for (AudioVoice* vox : m_activeVoices)
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
if (vox->m_running)
|
if (vox->m_running)
|
||||||
vox->pumpAndMix(m_mixInfo, thisFrames, dataOut, nullptr);
|
vox->pumpAndMix16(thisFrames);
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
|
||||||
smx->_pumpAndMixVoices(thisFrames, dataOut, dataOut);
|
for (auto it = m_linearizedSubmixes.rbegin() ; it != m_linearizedSubmixes.rend() ; ++it)
|
||||||
|
(*it)->_pumpAndMix16(thisFrames);
|
||||||
|
|
||||||
remFrames -= thisFrames;
|
remFrames -= thisFrames;
|
||||||
dataOut += thisFrames * m_mixInfo.m_channelMap.m_channelCount;
|
dataOut += thisFrames * m_mixInfo.m_channelMap.m_channelCount;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +58,13 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
|
||||||
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
|
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
|
||||||
{
|
{
|
||||||
memset(dataOut, 0, sizeof(int32_t) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
memset(dataOut, 0, sizeof(int32_t) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
m_mainSubmix.m_redirect32 = dataOut;
|
||||||
|
|
||||||
|
if (m_submixesDirty)
|
||||||
|
{
|
||||||
|
m_linearizedSubmixes = m_mainSubmix._linearizeC3();
|
||||||
|
m_submixesDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t remFrames = frames;
|
size_t remFrames = frames;
|
||||||
while (remFrames)
|
while (remFrames)
|
||||||
|
@ -64,11 +83,16 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
|
||||||
m_5msCallback(5.0 / 1000.0);
|
m_5msCallback(5.0 / 1000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = m_linearizedSubmixes.rbegin() ; it != m_linearizedSubmixes.rend() ; ++it)
|
||||||
|
(*it)->_zeroFill32();
|
||||||
|
|
||||||
for (AudioVoice* vox : m_activeVoices)
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
if (vox->m_running)
|
if (vox->m_running)
|
||||||
vox->pumpAndMix(m_mixInfo, thisFrames, dataOut, nullptr);
|
vox->pumpAndMix32(thisFrames);
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
|
||||||
smx->_pumpAndMixVoices(thisFrames, dataOut, dataOut);
|
for (auto it = m_linearizedSubmixes.rbegin() ; it != m_linearizedSubmixes.rend() ; ++it)
|
||||||
|
(*it)->_pumpAndMix32(thisFrames);
|
||||||
|
|
||||||
remFrames -= thisFrames;
|
remFrames -= thisFrames;
|
||||||
dataOut += thisFrames * m_mixInfo.m_channelMap.m_channelCount;
|
dataOut += thisFrames * m_mixInfo.m_channelMap.m_channelCount;
|
||||||
}
|
}
|
||||||
|
@ -77,6 +101,13 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
|
||||||
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
|
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
|
||||||
{
|
{
|
||||||
memset(dataOut, 0, sizeof(float) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
memset(dataOut, 0, sizeof(float) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
m_mainSubmix.m_redirectFlt = dataOut;
|
||||||
|
|
||||||
|
if (m_submixesDirty)
|
||||||
|
{
|
||||||
|
m_linearizedSubmixes = m_mainSubmix._linearizeC3();
|
||||||
|
m_submixesDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t remFrames = frames;
|
size_t remFrames = frames;
|
||||||
while (remFrames)
|
while (remFrames)
|
||||||
|
@ -95,11 +126,16 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
|
||||||
m_5msCallback(5.0 / 1000.0);
|
m_5msCallback(5.0 / 1000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = m_linearizedSubmixes.rbegin() ; it != m_linearizedSubmixes.rend() ; ++it)
|
||||||
|
(*it)->_zeroFillFlt();
|
||||||
|
|
||||||
for (AudioVoice* vox : m_activeVoices)
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
if (vox->m_running)
|
if (vox->m_running)
|
||||||
vox->pumpAndMix(m_mixInfo, thisFrames, dataOut, nullptr);
|
vox->pumpAndMixFlt(thisFrames);
|
||||||
for (AudioSubmix* smx : m_activeSubmixes)
|
|
||||||
smx->_pumpAndMixVoices(thisFrames, dataOut, dataOut);
|
for (auto it = m_linearizedSubmixes.rbegin() ; it != m_linearizedSubmixes.rend() ; ++it)
|
||||||
|
(*it)->_pumpAndMixFlt(thisFrames);
|
||||||
|
|
||||||
remFrames -= thisFrames;
|
remFrames -= thisFrames;
|
||||||
dataOut += thisFrames * m_mixInfo.m_channelMap.m_channelCount;
|
dataOut += thisFrames * m_mixInfo.m_channelMap.m_channelCount;
|
||||||
}
|
}
|
||||||
|
@ -112,6 +148,10 @@ void BaseAudioVoiceEngine::_unbindFrom(std::list<AudioVoice*>::iterator it)
|
||||||
|
|
||||||
void BaseAudioVoiceEngine::_unbindFrom(std::list<AudioSubmix*>::iterator it)
|
void BaseAudioVoiceEngine::_unbindFrom(std::list<AudioSubmix*>::iterator it)
|
||||||
{
|
{
|
||||||
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
m_activeSubmixes.erase(it);
|
m_activeSubmixes.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +161,7 @@ BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate,
|
||||||
bool dynamicPitch)
|
bool dynamicPitch)
|
||||||
{
|
{
|
||||||
std::unique_ptr<IAudioVoice> ret =
|
std::unique_ptr<IAudioVoice> ret =
|
||||||
std::make_unique<AudioVoiceMono>(*this, *this, cb, sampleRate, dynamicPitch);
|
std::make_unique<AudioVoiceMono>(*this, cb, sampleRate, dynamicPitch);
|
||||||
AudioVoiceMono* retMono = static_cast<AudioVoiceMono*>(ret.get());
|
AudioVoiceMono* retMono = static_cast<AudioVoiceMono*>(ret.get());
|
||||||
retMono->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retMono));
|
retMono->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retMono));
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -133,17 +173,16 @@ BaseAudioVoiceEngine::allocateNewStereoVoice(double sampleRate,
|
||||||
bool dynamicPitch)
|
bool dynamicPitch)
|
||||||
{
|
{
|
||||||
std::unique_ptr<IAudioVoice> ret =
|
std::unique_ptr<IAudioVoice> ret =
|
||||||
std::make_unique<AudioVoiceStereo>(*this, *this, cb, sampleRate, dynamicPitch);
|
std::make_unique<AudioVoiceStereo>(*this, cb, sampleRate, dynamicPitch);
|
||||||
AudioVoiceStereo* retStereo = static_cast<AudioVoiceStereo*>(ret.get());
|
AudioVoiceStereo* retStereo = static_cast<AudioVoiceStereo*>(ret.get());
|
||||||
retStereo->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retStereo));
|
retStereo->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retStereo));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IAudioSubmix>
|
std::unique_ptr<IAudioSubmix>
|
||||||
BaseAudioVoiceEngine::allocateNewSubmix(IAudioSubmixCallback* cb)
|
BaseAudioVoiceEngine::allocateNewSubmix(bool mainOut, IAudioSubmixCallback* cb, int busId)
|
||||||
{
|
{
|
||||||
std::unique_ptr<IAudioSubmix> ret =
|
std::unique_ptr<IAudioSubmix> ret = std::make_unique<AudioSubmix>(*this, cb, busId, mainOut);
|
||||||
std::make_unique<AudioSubmix>(*this, *this, cb);
|
|
||||||
AudioSubmix* retIntern = static_cast<AudioSubmix*>(ret.get());
|
AudioSubmix* retIntern = static_cast<AudioSubmix*>(ret.get());
|
||||||
retIntern->bindSubmix(m_activeSubmixes.insert(m_activeSubmixes.end(), retIntern));
|
retIntern->bindSubmix(m_activeSubmixes.insert(m_activeSubmixes.end(), retIntern));
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
||||||
#include "AudioVoice.hpp"
|
#include "AudioVoice.hpp"
|
||||||
#include "AudioSubmix.hpp"
|
#include "AudioSubmix.hpp"
|
||||||
#include "IAudioMix.hpp"
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace boo
|
namespace boo
|
||||||
|
@ -22,7 +21,7 @@ struct AudioVoiceEngineMixInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Base class for managing mixing and sample-rate-conversion amongst active voices */
|
/** Base class for managing mixing and sample-rate-conversion amongst active voices */
|
||||||
class BaseAudioVoiceEngine : public IAudioVoiceEngine, public IAudioMix
|
class BaseAudioVoiceEngine : public IAudioVoiceEngine
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
friend class AudioVoice;
|
friend class AudioVoice;
|
||||||
|
@ -37,9 +36,16 @@ protected:
|
||||||
|
|
||||||
/* Shared scratch buffers for accumulating audio data for resampling */
|
/* Shared scratch buffers for accumulating audio data for resampling */
|
||||||
std::vector<int16_t> m_scratchIn;
|
std::vector<int16_t> m_scratchIn;
|
||||||
std::vector<int16_t> m_scratch16;
|
std::vector<int16_t> m_scratch16Pre;
|
||||||
std::vector<int32_t> m_scratch32;
|
std::vector<int32_t> m_scratch32Pre;
|
||||||
std::vector<float> m_scratchFlt;
|
std::vector<float> m_scratchFltPre;
|
||||||
|
std::vector<int16_t> m_scratch16Post;
|
||||||
|
std::vector<int32_t> m_scratch32Post;
|
||||||
|
std::vector<float> m_scratchFltPost;
|
||||||
|
|
||||||
|
AudioSubmix m_mainSubmix;
|
||||||
|
std::list<AudioSubmix*> m_linearizedSubmixes;
|
||||||
|
bool m_submixesDirty = true;
|
||||||
|
|
||||||
void _pumpAndMixVoices(size_t frames, int16_t* dataOut);
|
void _pumpAndMixVoices(size_t frames, int16_t* dataOut);
|
||||||
void _pumpAndMixVoices(size_t frames, int32_t* dataOut);
|
void _pumpAndMixVoices(size_t frames, int32_t* dataOut);
|
||||||
|
@ -49,6 +55,7 @@ protected:
|
||||||
void _unbindFrom(std::list<AudioSubmix*>::iterator it);
|
void _unbindFrom(std::list<AudioSubmix*>::iterator it);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
BaseAudioVoiceEngine() : m_mainSubmix(*this, nullptr, -1, false) {}
|
||||||
~BaseAudioVoiceEngine();
|
~BaseAudioVoiceEngine();
|
||||||
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
|
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
|
||||||
IAudioVoiceCallback* cb,
|
IAudioVoiceCallback* cb,
|
||||||
|
@ -58,7 +65,7 @@ public:
|
||||||
IAudioVoiceCallback* cb,
|
IAudioVoiceCallback* cb,
|
||||||
bool dynamicPitch=false);
|
bool dynamicPitch=false);
|
||||||
|
|
||||||
std::unique_ptr<IAudioSubmix> allocateNewSubmix(IAudioSubmixCallback* cb);
|
std::unique_ptr<IAudioSubmix> allocateNewSubmix(bool mainOut, IAudioSubmixCallback* cb, int busId);
|
||||||
|
|
||||||
void register5MsCallback(std::function<void(double dt)>&& callback);
|
void register5MsCallback(std::function<void(double dt)>&& callback);
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
#ifndef BOO_IAUDIOMIX_HPP
|
|
||||||
#define BOO_IAUDIOMIX_HPP
|
|
||||||
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
namespace boo
|
|
||||||
{
|
|
||||||
struct AudioVoiceEngineMixInfo;
|
|
||||||
class AudioVoice;
|
|
||||||
class AudioSubmix;
|
|
||||||
|
|
||||||
/** Entity that mixes audio from several child sources (engine root or submix) */
|
|
||||||
class IAudioMix
|
|
||||||
{
|
|
||||||
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_IAUDIOMIX_HPP
|
|
Loading…
Reference in New Issue