boo/lib/audiodev/AudioVoice.cpp
Lioncash 84f62a0f2c BooObject: Remove destructorLock()
Now that we have the fencing and atomic operations in place to ensure
access to data on other threads will always occur before the use of
delete, we can remove the destructor lock. This will be useful for
making ObjToken's move assignment operator noexcept.
2019-08-17 14:12:23 -04:00

303 lines
9.9 KiB
C++

#include "AudioVoice.hpp"
#include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp"
#include <cmath>
namespace boo {
static logvisor::Module Log("boo::AudioVoice");
static AudioMatrixMono DefaultMonoMtx;
static AudioMatrixStereo DefaultStereoMtx;
AudioVoice::AudioVoice(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb, bool dynamicRate)
: ListNode<AudioVoice, BaseAudioVoiceEngine*, IAudioVoice>(&root), m_cb(cb), m_dynamicRate(dynamicRate) {}
AudioVoice::~AudioVoice() { soxr_delete(m_src); }
AudioVoice*& AudioVoice::_getHeadPtr(BaseAudioVoiceEngine* head) { return head->m_voiceHead; }
std::unique_lock<std::recursive_mutex> AudioVoice::_getHeadLock(BaseAudioVoiceEngine* head) {
return std::unique_lock<std::recursive_mutex>{head->m_dataMutex};
}
void AudioVoice::_setPitchRatio(double ratio, bool slew) {
if (m_dynamicRate) {
m_sampleRatio = ratio * m_sampleRateIn / m_sampleRateOut;
soxr_error_t err = soxr_set_io_ratio(m_src, m_sampleRatio, slew ? m_head->m_5msFrames : 0);
if (err) {
Log.report(logvisor::Fatal, fmt("unable to set resampler rate: {}"), soxr_strerror(err));
m_setPitchRatio = false;
return;
}
}
m_setPitchRatio = false;
}
void AudioVoice::_midUpdate() {
if (m_resetSampleRate)
_resetSampleRate(m_deferredSampleRate);
if (m_setPitchRatio)
_setPitchRatio(m_pitchRatio, m_slew);
}
void AudioVoice::setPitchRatio(double ratio, bool slew) {
m_setPitchRatio = true;
m_pitchRatio = ratio;
m_slew = slew;
}
void AudioVoice::resetSampleRate(double sampleRate) {
m_resetSampleRate = true;
m_deferredSampleRate = sampleRate;
}
void AudioVoice::start() { m_running = true; }
void AudioVoice::stop() { m_running = false; }
AudioVoiceMono::AudioVoiceMono(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb, double sampleRate, bool dynamicRate)
: AudioVoice(root, cb, dynamicRate) {
_resetSampleRate(sampleRate);
}
void AudioVoiceMono::_resetSampleRate(double sampleRate) {
soxr_delete(m_src);
double rateOut = m_head->mixInfo().m_sampleRate;
soxr_datatype_t formatOut = m_head->mixInfo().m_sampleFormat;
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_error_t err;
m_src = soxr_create(sampleRate, rateOut, 1, &err, &ioSpec, &qSpec, nullptr);
if (err) {
Log.report(logvisor::Fatal, fmt("unable to create soxr resampler: {}"), soxr_strerror(err));
m_resetSampleRate = false;
return;
}
m_sampleRateIn = sampleRate;
m_sampleRateOut = rateOut;
m_sampleRatio = m_sampleRateIn / m_sampleRateOut;
soxr_set_input_fn(m_src, soxr_input_fn_t(SRCCallback), this, 0);
_setPitchRatio(m_pitchRatio, false);
m_resetSampleRate = false;
}
size_t AudioVoiceMono::SRCCallback(AudioVoiceMono* ctx, int16_t** data, size_t frames) {
std::vector<int16_t>& scratchIn = ctx->m_head->m_scratchIn;
if (scratchIn.size() < frames)
scratchIn.resize(frames);
*data = scratchIn.data();
if (ctx->m_silentOut) {
memset(scratchIn.data(), 0, frames * 2);
return frames;
} else
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
}
bool AudioVoiceMono::isSilent() const {
if (m_sendMatrices.size()) {
for (auto& mtx : m_sendMatrices)
if (!mtx.second.isSilent())
return false;
return true;
} else {
return DefaultMonoMtx.isSilent();
}
}
template <typename T>
size_t AudioVoiceMono::_pumpAndMix(size_t frames) {
auto& scratchPre = m_head->_getScratchPre<T>();
if (scratchPre.size() < frames)
scratchPre.resize(frames + 2);
auto& scratchPost = m_head->_getScratchPost<T>();
if (scratchPost.size() < frames)
scratchPost.resize(frames + 2);
double dt = frames / m_sampleRateOut;
m_cb->preSupplyAudio(*this, dt);
_midUpdate();
if (isSilent()) {
int16_t* dummy;
SRCCallback(this, &dummy, size_t(std::ceil(frames * m_sampleRatio)));
return 0;
}
size_t oDone = soxr_output(m_src, scratchPre.data(), frames);
if (oDone) {
if (m_sendMatrices.size()) {
for (auto& mtx : m_sendMatrices) {
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
m_cb->routeAudio(oDone, 1, dt, smx.m_busId, scratchPre.data(), scratchPost.data());
mtx.second.mixMonoSampleData(m_head->clientMixInfo(), scratchPost.data(), smx._getMergeBuf<T>(oDone), oDone);
}
} else {
AudioSubmix& smx = *m_head->m_mainSubmix;
m_cb->routeAudio(oDone, 1, dt, m_head->m_mainSubmix->m_busId, scratchPre.data(), scratchPost.data());
DefaultMonoMtx.mixMonoSampleData(m_head->clientMixInfo(), scratchPost.data(), smx._getMergeBuf<T>(oDone), oDone);
}
}
return oDone;
}
void AudioVoiceMono::resetChannelLevels() {
m_head->m_submixesDirty = true;
m_sendMatrices.clear();
}
void AudioVoiceMono::setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew) {
if (!submix)
submix = m_head->m_mainSubmix.get();
auto search = m_sendMatrices.find(submix);
if (search == m_sendMatrices.cend())
search = m_sendMatrices.emplace(submix, AudioMatrixMono{}).first;
search->second.setMatrixCoefficients(coefs, slew ? m_head->m_5msFrames : 0);
}
void AudioVoiceMono::setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew) {
float newCoefs[8] = {coefs[0][0], coefs[1][0], coefs[2][0], coefs[3][0],
coefs[4][0], coefs[5][0], coefs[6][0], coefs[7][0]};
if (!submix)
submix = m_head->m_mainSubmix.get();
auto search = m_sendMatrices.find(submix);
if (search == m_sendMatrices.cend())
search = m_sendMatrices.emplace(submix, AudioMatrixMono{}).first;
search->second.setMatrixCoefficients(newCoefs, slew ? m_head->m_5msFrames : 0);
}
AudioVoiceStereo::AudioVoiceStereo(BaseAudioVoiceEngine& root, IAudioVoiceCallback* cb, double sampleRate,
bool dynamicRate)
: AudioVoice(root, cb, dynamicRate) {
_resetSampleRate(sampleRate);
}
void AudioVoiceStereo::_resetSampleRate(double sampleRate) {
soxr_delete(m_src);
double rateOut = m_head->mixInfo().m_sampleRate;
soxr_datatype_t formatOut = m_head->mixInfo().m_sampleFormat;
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_error_t err;
m_src = soxr_create(sampleRate, rateOut, 2, &err, &ioSpec, &qSpec, nullptr);
if (!m_src) {
Log.report(logvisor::Fatal, fmt("unable to create soxr resampler: {}"), soxr_strerror(err));
m_resetSampleRate = false;
return;
}
m_sampleRateIn = sampleRate;
m_sampleRateOut = rateOut;
m_sampleRatio = m_sampleRateIn / m_sampleRateOut;
soxr_set_input_fn(m_src, soxr_input_fn_t(SRCCallback), this, 0);
_setPitchRatio(m_pitchRatio, false);
m_resetSampleRate = false;
}
size_t AudioVoiceStereo::SRCCallback(AudioVoiceStereo* ctx, int16_t** data, size_t frames) {
std::vector<int16_t>& scratchIn = ctx->m_head->m_scratchIn;
size_t samples = frames * 2;
if (scratchIn.size() < samples)
scratchIn.resize(samples);
*data = scratchIn.data();
if (ctx->m_silentOut) {
memset(scratchIn.data(), 0, samples * 2);
return frames;
} else
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
}
bool AudioVoiceStereo::isSilent() const {
if (m_sendMatrices.size()) {
for (auto& mtx : m_sendMatrices)
if (!mtx.second.isSilent())
return false;
return true;
} else {
return DefaultStereoMtx.isSilent();
}
}
template <typename T>
size_t AudioVoiceStereo::_pumpAndMix(size_t frames) {
size_t samples = frames * 2;
auto& scratchPre = m_head->_getScratchPre<T>();
if (scratchPre.size() < samples)
scratchPre.resize(samples + 4);
auto& scratchPost = m_head->_getScratchPost<T>();
if (scratchPost.size() < samples)
scratchPost.resize(samples + 4);
double dt = frames / m_sampleRateOut;
m_cb->preSupplyAudio(*this, dt);
_midUpdate();
if (isSilent()) {
int16_t* dummy;
SRCCallback(this, &dummy, size_t(std::ceil(frames * m_sampleRatio)));
return 0;
}
size_t oDone = soxr_output(m_src, scratchPre.data(), frames);
if (oDone) {
if (m_sendMatrices.size()) {
for (auto& mtx : m_sendMatrices) {
AudioSubmix& smx = *reinterpret_cast<AudioSubmix*>(mtx.first);
m_cb->routeAudio(oDone, 2, dt, smx.m_busId, scratchPre.data(), scratchPost.data());
mtx.second.mixStereoSampleData(m_head->clientMixInfo(), scratchPost.data(), smx._getMergeBuf<T>(oDone), oDone);
}
} else {
AudioSubmix& smx = *m_head->m_mainSubmix;
m_cb->routeAudio(oDone, 2, dt, m_head->m_mainSubmix->m_busId, scratchPre.data(), scratchPost.data());
DefaultStereoMtx.mixStereoSampleData(m_head->clientMixInfo(), scratchPost.data(), smx._getMergeBuf<T>(oDone),
oDone);
}
}
return oDone;
}
void AudioVoiceStereo::resetChannelLevels() {
m_head->m_submixesDirty = true;
m_sendMatrices.clear();
}
void AudioVoiceStereo::setMonoChannelLevels(IAudioSubmix* submix, const float coefs[8], bool slew) {
float newCoefs[8][2] = {{coefs[0], coefs[0]}, {coefs[1], coefs[1]}, {coefs[2], coefs[2]}, {coefs[3], coefs[3]},
{coefs[4], coefs[4]}, {coefs[5], coefs[5]}, {coefs[6], coefs[6]}, {coefs[7], coefs[7]}};
if (!submix)
submix = m_head->m_mainSubmix.get();
auto search = m_sendMatrices.find(submix);
if (search == m_sendMatrices.cend())
search = m_sendMatrices.emplace(submix, AudioMatrixStereo{}).first;
search->second.setMatrixCoefficients(newCoefs, slew ? m_head->m_5msFrames : 0);
}
void AudioVoiceStereo::setStereoChannelLevels(IAudioSubmix* submix, const float coefs[8][2], bool slew) {
if (!submix)
submix = m_head->m_mainSubmix.get();
auto search = m_sendMatrices.find(submix);
if (search == m_sendMatrices.cend())
search = m_sendMatrices.emplace(submix, AudioMatrixStereo{}).first;
search->second.setMatrixCoefficients(coefs, slew ? m_head->m_5msFrames : 0);
}
} // namespace boo