diff options
Diffstat (limited to 'al/source.cpp')
-rw-r--r-- | al/source.cpp | 395 |
1 files changed, 234 insertions, 161 deletions
diff --git a/al/source.cpp b/al/source.cpp index 93f3285b..c0ad3b81 100644 --- a/al/source.cpp +++ b/al/source.cpp @@ -31,6 +31,7 @@ #include <cmath> #include <cstdint> #include <functional> +#include <inttypes.h> #include <iterator> #include <limits> #include <memory> @@ -2431,6 +2432,178 @@ bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const return false; } + +void StartSources(ALCcontext *context, const al::span<ALsource*> srchandles, + nanoseconds start_time=nanoseconds::min()) +{ + ALCdevice *device{context->mALDevice.get()}; + /* If the device is disconnected, and voices stop on disconnect, go right + * to stopped. + */ + if(unlikely(!device->Connected.load(std::memory_order_acquire))) + { + if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + { + for(ALsource *source : srchandles) + { + /* TODO: Send state change event? */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + } + return; + } + } + + /* Count the number of reusable voices. */ + auto voicelist = context->getVoicesSpan(); + size_t free_voices{0}; + for(const Voice *voice : voicelist) + { + free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false); + if(free_voices == srchandles.size()) + break; + } + if(unlikely(srchandles.size() != free_voices)) + { + const size_t inc_amount{srchandles.size() - free_voices}; + auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); + if(inc_amount > allvoices.size() - voicelist.size()) + { + /* Increase the number of voices to handle the request. */ + context->allocVoices(inc_amount - (allvoices.size() - voicelist.size())); + } + context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release); + voicelist = context->getVoicesSpan(); + } + + auto voiceiter = voicelist.begin(); + ALuint vidx{0}; + VoiceChange *tail{}, *cur{}; + for(ALsource *source : srchandles) + { + /* Check that there is a queue containing at least one valid, non zero + * length buffer. + */ + auto BufferList = source->mQueue.begin(); + for(;BufferList != source->mQueue.end();++BufferList) + { + if(BufferList->mSampleLen != 0 || BufferList->mCallback) + break; + } + + /* If there's nothing to play, go right to stopped. */ + if(unlikely(BufferList == source->mQueue.end())) + { + /* NOTE: A source without any playable buffers should not have a + * Voice since it shouldn't be in a playing or paused state. So + * there's no need to look up its voice and clear the source. + */ + source->Offset = 0.0; + source->OffsetType = AL_NONE; + source->state = AL_STOPPED; + continue; + } + + if(!cur) + cur = tail = GetVoiceChanger(context); + else + { + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); + cur = cur->mNext.load(std::memory_order_relaxed); + } + + Voice *voice{GetSourceVoice(source, context)}; + switch(GetSourceState(source, voice)) + { + case AL_PAUSED: + /* A source that's paused simply resumes. If there's no voice, it + * was lost from a disconnect, so just start over with a new one. + */ + cur->mOldVoice = nullptr; + if(!voice) break; + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Play; + source->state = AL_PLAYING; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX + continue; + + case AL_PLAYING: + /* A source that's already playing is restarted from the beginning. + * Stop the current voice and start a new one so it properly cross- + * fades back to the beginning. + */ + if(voice) + voice->mPendingChange.store(true, std::memory_order_relaxed); + cur->mOldVoice = voice; + voice = nullptr; + break; + + default: + assert(voice == nullptr); + cur->mOldVoice = nullptr; +#ifdef ALSOFT_EAX + if(source->eax_is_initialized()) + source->eax_commit(); +#endif // ALSOFT_EAX + break; + } + + /* Find the next unused voice to play this source with. */ + for(;voiceiter != voicelist.end();++voiceiter,++vidx) + { + Voice *v{*voiceiter}; + if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped + && v->mSourceID.load(std::memory_order_relaxed) == 0u + && v->mPendingChange.load(std::memory_order_relaxed) == false) + { + voice = v; + break; + } + } + ASSUME(voice != nullptr); + + voice->mPosition.store(0, std::memory_order_relaxed); + voice->mPositionFrac.store(0, std::memory_order_relaxed); + voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); + voice->mStartTime = start_time; + voice->mFlags.reset(); + /* A source that's not playing or paused has any offset applied when it + * starts playing. + */ + if(const ALenum offsettype{source->OffsetType}) + { + const double offset{source->Offset}; + source->OffsetType = AL_NONE; + source->Offset = 0.0; + if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset)) + { + voice->mPosition.store(vpos->pos, std::memory_order_relaxed); + voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); + voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); + if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) + voice->mFlags.set(VoiceIsFading); + } + } + InitVoice(voice, source, std::addressof(*BufferList), context, device); + + source->VoiceIdx = vidx; + source->state = AL_PLAYING; + + cur->mVoice = voice; + cur->mSourceID = source->id; + cur->mState = VChangeState::Play; + } + if(likely(tail)) + SendVoiceChanges(context, tail); +} + } // namespace AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) @@ -3030,7 +3203,35 @@ END_API_FUNC AL_API void AL_APIENTRY alSourcePlay(ALuint source) START_API_FUNC -{ alSourcePlayv(1, &source); } +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + std::lock_guard<std::mutex> _{context->mSourceLock}; + ALsource *srchandle{LookupSource(context.get(), source)}; + if(!srchandle) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", source); + + StartSources(context.get(), {&srchandle, 1}); +} +END_API_FUNC + +void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; + + if(unlikely(start_time < 0)) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid time point %" PRId64, start_time); + + std::lock_guard<std::mutex> _{context->mSourceLock}; + ALsource *srchandle{LookupSource(context.get(), source)}; + if(!srchandle) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", source); + + StartSources(context.get(), {&srchandle, 1}, nanoseconds{start_time}); +} END_API_FUNC AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) @@ -3063,172 +3264,44 @@ START_API_FUNC ++sources; } - ALCdevice *device{context->mALDevice.get()}; - /* If the device is disconnected, and voices stop on disconnect, go right - * to stopped. - */ - if UNLIKELY(!device->Connected.load(std::memory_order_acquire)) - { - if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) - { - for(ALsource *source : srchandles) - { - /* TODO: Send state change event? */ - source->Offset = 0.0; - source->OffsetType = AL_NONE; - source->state = AL_STOPPED; - } - return; - } - } - - /* Count the number of reusable voices. */ - auto voicelist = context->getVoicesSpan(); - size_t free_voices{0}; - for(const Voice *voice : voicelist) - { - free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped - && voice->mSourceID.load(std::memory_order_relaxed) == 0u - && voice->mPendingChange.load(std::memory_order_relaxed) == false); - if(free_voices == srchandles.size()) - break; - } - if UNLIKELY(srchandles.size() != free_voices) - { - const size_t inc_amount{srchandles.size() - free_voices}; - auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); - if(inc_amount > allvoices.size() - voicelist.size()) - { - /* Increase the number of voices to handle the request. */ - context->allocVoices(inc_amount - (allvoices.size() - voicelist.size())); - } - context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release); - voicelist = context->getVoicesSpan(); - } - - auto voiceiter = voicelist.begin(); - ALuint vidx{0}; - VoiceChange *tail{}, *cur{}; - for(ALsource *source : srchandles) - { - /* Check that there is a queue containing at least one valid, non zero - * length buffer. - */ - auto BufferList = source->mQueue.begin(); - for(;BufferList != source->mQueue.end();++BufferList) - { - if(BufferList->mSampleLen != 0 || BufferList->mCallback) - break; - } - - /* If there's nothing to play, go right to stopped. */ - if UNLIKELY(BufferList == source->mQueue.end()) - { - /* NOTE: A source without any playable buffers should not have a - * Voice since it shouldn't be in a playing or paused state. So - * there's no need to look up its voice and clear the source. - */ - source->Offset = 0.0; - source->OffsetType = AL_NONE; - source->state = AL_STOPPED; - continue; - } - - if(!cur) - cur = tail = GetVoiceChanger(context.get()); - else - { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); - cur = cur->mNext.load(std::memory_order_relaxed); - } - - Voice *voice{GetSourceVoice(source, context.get())}; - switch(GetSourceState(source, voice)) - { - case AL_PAUSED: - /* A source that's paused simply resumes. If there's no voice, it - * was lost from a disconnect, so just start over with a new one. - */ - cur->mOldVoice = nullptr; - if(!voice) break; - cur->mVoice = voice; - cur->mSourceID = source->id; - cur->mState = VChangeState::Play; - source->state = AL_PLAYING; -#ifdef ALSOFT_EAX - if(source->eax_is_initialized()) - source->eax_commit(); -#endif // ALSOFT_EAX - continue; + StartSources(context.get(), srchandles); +} +END_API_FUNC - case AL_PLAYING: - /* A source that's already playing is restarted from the beginning. - * Stop the current voice and start a new one so it properly cross- - * fades back to the beginning. - */ - if(voice) - voice->mPendingChange.store(true, std::memory_order_relaxed); - cur->mOldVoice = voice; - voice = nullptr; - break; +void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) +START_API_FUNC +{ + ContextRef context{GetContextRef()}; + if UNLIKELY(!context) return; - default: - assert(voice == nullptr); - cur->mOldVoice = nullptr; -#ifdef ALSOFT_EAX - if(source->eax_is_initialized()) - source->eax_commit(); -#endif // ALSOFT_EAX - break; - } + if UNLIKELY(n < 0) + context->setError(AL_INVALID_VALUE, "Playing %d sources", n); + if UNLIKELY(n <= 0) return; - /* Find the next unused voice to play this source with. */ - for(;voiceiter != voicelist.end();++voiceiter,++vidx) - { - Voice *v{*voiceiter}; - if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped - && v->mSourceID.load(std::memory_order_relaxed) == 0u - && v->mPendingChange.load(std::memory_order_relaxed) == false) - { - voice = v; - break; - } - } - ASSUME(voice != nullptr); + if(unlikely(start_time < 0)) + SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid time point %" PRId64, start_time); - voice->mPosition.store(0, std::memory_order_relaxed); - voice->mPositionFrac.store(0, std::memory_order_relaxed); - voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); - voice->mStartTime = std::chrono::nanoseconds::min(); - voice->mFlags.reset(); - /* A source that's not playing or paused has any offset applied when it - * starts playing. - */ - if(const ALenum offsettype{source->OffsetType}) - { - const double offset{source->Offset}; - source->OffsetType = AL_NONE; - source->Offset = 0.0; - if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset)) - { - voice->mPosition.store(vpos->pos, std::memory_order_relaxed); - voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); - if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) - voice->mFlags.set(VoiceIsFading); - } - } - InitVoice(voice, source, std::addressof(*BufferList), context.get(), device); - - source->VoiceIdx = vidx; - source->state = AL_PLAYING; + al::vector<ALsource*> extra_sources; + std::array<ALsource*,8> source_storage; + al::span<ALsource*> srchandles; + if LIKELY(static_cast<ALuint>(n) <= source_storage.size()) + srchandles = {source_storage.data(), static_cast<ALuint>(n)}; + else + { + extra_sources.resize(static_cast<ALuint>(n)); + srchandles = {extra_sources.data(), extra_sources.size()}; + } - cur->mVoice = voice; - cur->mSourceID = source->id; - cur->mState = VChangeState::Play; + std::lock_guard<std::mutex> _{context->mSourceLock}; + for(auto &srchdl : srchandles) + { + srchdl = LookupSource(context.get(), *sources); + if(!srchdl) + SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); + ++sources; } - if LIKELY(tail) - SendVoiceChanges(context.get(), tail); + + StartSources(context.get(), srchandles, nanoseconds{start_time}); } END_API_FUNC |