#include "AudioSubmix.hpp" #include "AudioVoiceEngine.hpp" #include "AudioVoice.hpp" #include #include #undef min #undef max namespace boo { AudioSubmix::AudioSubmix(BaseAudioVoiceEngine& root, IAudioSubmixCallback* cb, int busId, bool mainOut) : ListNode(&root), m_busId(busId), m_mainOut(mainOut), m_cb(cb) { 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 AudioSubmix::_getHeadLock(BaseAudioVoiceEngine* head) { return std::unique_lock{head->m_dataMutex}; } std::unique_lock AudioSubmix::destructorLock() { return std::unique_lock{m_head->m_dataMutex}; } bool AudioSubmix::_isDirectDependencyOf(AudioSubmix* send) { return m_sendGains.find(send) != m_sendGains.cend(); } bool AudioSubmix::_mergeC3(std::list& output, std::vector>& 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::_linearizeC3() { std::vector> 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 ret = {this}; while (_mergeC3(ret, lists)) {} return ret; } template void AudioSubmix::_zeroFill() { if (_getScratch().size()) std::fill(_getScratch().begin(), _getScratch().end(), 0); } template void AudioSubmix::_zeroFill(); template void AudioSubmix::_zeroFill(); template void AudioSubmix::_zeroFill(); template T* AudioSubmix::_getMergeBuf(size_t frames) { if (_getRedirect()) return _getRedirect(); size_t sampleCount = frames * m_head->clientMixInfo().m_channelMap.m_channelCount; if (_getScratch().size() < sampleCount) _getScratch().resize(sampleCount); return _getScratch().data(); } template int16_t* AudioSubmix::_getMergeBuf(size_t frames); template int32_t* AudioSubmix::_getMergeBuf(size_t frames); template float* AudioSubmix::_getMergeBuf(size_t frames); template constexpr T ClampInt(float in) { if (std::is_floating_point()) { return in; // Allow subsequent mixing stages to work with over-saturated values } else { constexpr T MAX = std::numeric_limits::max(); constexpr T MIN = std::numeric_limits::min(); if (in < MIN) return MIN; else if (in > MAX) return MAX; else return in; } } template size_t AudioSubmix::_pumpAndMix(size_t frames) { const ChannelMap& chMap = m_head->clientMixInfo().m_channelMap; size_t chanCount = chMap.m_channelCount; if (_getRedirect()) { if (m_cb && m_cb->canApplyEffect()) m_cb->applyEffect(_getRedirect(), frames, chMap, m_head->mixInfo().m_sampleRate); _getRedirect() += chanCount * frames; } else { size_t sampleCount = frames * chanCount; if (_getScratch().size() < sampleCount) _getScratch().resize(sampleCount); if (m_cb && m_cb->canApplyEffect()) m_cb->applyEffect(_getScratch().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(smx.first); auto it = _getScratch().begin(); T* dataOut = sm._getMergeBuf(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(*dataOut + *it * (smx.second[1] * t + smx.second[0] * omt)); ++it; ++dataOut; } ++curSlewFrame; } else { for (unsigned c = 0; c < chanCount; ++c) { *dataOut = ClampInt(*dataOut + *it * smx.second[1]); ++it; ++dataOut; } } } } m_curSlewFrame += curSlewFrame; } return frames; } template size_t AudioSubmix::_pumpAndMix(size_t frames); template size_t AudioSubmix::_pumpAndMix(size_t frames); template size_t AudioSubmix::_pumpAndMix(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{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; } } } // namespace boo