#include "AudioSubmix.hpp" #include "AudioVoiceEngine.hpp" #include "AudioVoice.hpp" #include <string.h> #include <algorithm> #undef min #undef max namespace boo { AudioSubmix::AudioSubmix(BaseAudioVoiceEngine& root, IAudioSubmixCallback* cb, int busId, bool mainOut) : ListNode<AudioSubmix, BaseAudioVoiceEngine*, IAudioSubmix>(&root), m_busId(busId), m_cb(cb), m_mainOut(mainOut) { if (mainOut) setSendLevel(m_head->m_mainSubmix.get(), 1.f, false); } AudioSubmix::~AudioSubmix() { m_head->m_submixesDirty = true; } AudioSubmix*& AudioSubmix::_getHeadPtr(BaseAudioVoiceEngine* head) { return head->m_submixHead; } std::unique_lock<std::recursive_mutex> AudioSubmix::_getHeadLock(BaseAudioVoiceEngine* head) { return std::unique_lock<std::recursive_mutex>{head->m_dataMutex}; } std::unique_lock<std::recursive_mutex> AudioSubmix::destructorLock() { return std::unique_lock<std::recursive_mutex>{m_head->m_dataMutex}; } bool AudioSubmix::_isDirectDependencyOf(AudioSubmix* send) { return m_sendGains.find(send) != m_sendGains.cend(); } 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 = {{}}; if (m_head->m_submixHead) for (AudioSubmix& smx : *m_head->m_submixHead) { 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; } template <typename T> void AudioSubmix::_zeroFill() { if (_getScratch<T>().size()) std::fill(_getScratch<T>().begin(), _getScratch<T>().end(), 0); } template void AudioSubmix::_zeroFill<int16_t>(); template void AudioSubmix::_zeroFill<int32_t>(); template void AudioSubmix::_zeroFill<float>(); template <typename T> T* AudioSubmix::_getMergeBuf(size_t frames) { if (_getRedirect<T>()) return _getRedirect<T>(); size_t sampleCount = frames * m_head->clientMixInfo().m_channelMap.m_channelCount; if (_getScratch<T>().size() < sampleCount) _getScratch<T>().resize(sampleCount); return _getScratch<T>().data(); } template int16_t* AudioSubmix::_getMergeBuf<int16_t>(size_t frames); template int32_t* AudioSubmix::_getMergeBuf<int32_t>(size_t frames); template float* AudioSubmix::_getMergeBuf<float>(size_t frames); template <typename T> static inline T ClampInt(float in) { if (std::is_floating_point<T>()) { return in; // Allow subsequent mixing stages to work with over-saturated values } else { constexpr T MAX = std::numeric_limits<T>::max(); constexpr T MIN = std::numeric_limits<T>::min(); if (in < MIN) return MIN; else if (in > MAX) return MAX; else return in; } } template <typename T> size_t AudioSubmix::_pumpAndMix(size_t frames) { const ChannelMap& chMap = m_head->clientMixInfo().m_channelMap; size_t chanCount = chMap.m_channelCount; if (_getRedirect<T>()) { if (m_cb && m_cb->canApplyEffect()) m_cb->applyEffect(_getRedirect<T>(), frames, chMap, m_head->mixInfo().m_sampleRate); _getRedirect<T>() += chanCount * frames; } else { size_t sampleCount = frames * chanCount; if (_getScratch<T>().size() < sampleCount) _getScratch<T>().resize(sampleCount); if (m_cb && m_cb->canApplyEffect()) m_cb->applyEffect(_getScratch<T>().data(), frames, chMap, m_head->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 = _getScratch<T>().begin(); T* dataOut = sm._getMergeBuf<T>(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 = ClampInt<T>(*dataOut + *it * (smx.second[1] * t + smx.second[0] * omt)); ++it; ++dataOut; } ++curSlewFrame; } else { for (unsigned c=0 ; c<chanCount ; ++c) { *dataOut = ClampInt<T>(*dataOut + *it * smx.second[1]); ++it; ++dataOut; } } } } m_curSlewFrame += curSlewFrame; } return frames; } template size_t AudioSubmix::_pumpAndMix<int16_t>(size_t frames); template size_t AudioSubmix::_pumpAndMix<int32_t>(size_t frames); template size_t AudioSubmix::_pumpAndMix<float>(size_t frames); void AudioSubmix::_resetOutputSampleRate() { if (m_cb) m_cb->resetOutputSampleRate(m_head->mixInfo().m_sampleRate); } void AudioSubmix::resetSendLevels() { if (m_sendGains.empty()) return; m_sendGains.clear(); m_head->m_submixesDirty = true; } void AudioSubmix::setSendLevel(IAudioSubmix* submix, float level, bool slew) { auto search = m_sendGains.find(submix); if (search == m_sendGains.cend()) { search = m_sendGains.emplace(submix, std::array<float, 2>{1.f, 1.f}).first; m_head->m_submixesDirty = true; } m_slewFrames = slew ? m_head->m_5msFrames : 0; m_curSlewFrame = 0; search->second[0] = search->second[1]; search->second[1] = level; } const AudioVoiceEngineMixInfo& AudioSubmix::mixInfo() const { return m_head->mixInfo(); } double AudioSubmix::getSampleRate() const { return mixInfo().m_sampleRate; } SubmixFormat AudioSubmix::getSampleFormat() const { switch (mixInfo().m_sampleFormat) { case SOXR_INT16_I: default: return SubmixFormat::Int16; case SOXR_INT32_I: return SubmixFormat::Int32; case SOXR_FLOAT32_I: return SubmixFormat::Float; } } }