aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2022-11-03 03:09:30 -0700
committerChris Robinson <[email protected]>2022-11-03 03:09:30 -0700
commit551a18a15c66440e3a5478c8b3d6b973f36c33d3 (patch)
tree9a198cdb7e57e93128eb90aaeb48a1d9f56b2778
parentd8361bdd6fa807a4200e18e8ef7ffd13ab849b74 (diff)
Add functions to start sources at a particular device time
This starts a source at a particular device clock time, rounded to the nearest sample (really, 4th sample for SIMD reasons), allowing to start a sound at a particular point in the output instead of the next update. Unlike using negative offsets, this is not affected by pitch/velocity.
-rw-r--r--al/source.cpp395
-rw-r--r--alc/alc.cpp3
-rw-r--r--alc/context.cpp1
-rw-r--r--alc/inprogext.h9
4 files changed, 247 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
diff --git a/alc/alc.cpp b/alc/alc.cpp
index 9305bf3d..f6385e27 100644
--- a/alc/alc.cpp
+++ b/alc/alc.cpp
@@ -452,6 +452,9 @@ const struct {
DECL(alAuxiliaryEffectSlotPlayvSOFT),
DECL(alAuxiliaryEffectSlotStopSOFT),
DECL(alAuxiliaryEffectSlotStopvSOFT),
+
+ DECL(alSourcePlayAtTimeSOFT),
+ DECL(alSourcePlayAtTimevSOFT),
#ifdef ALSOFT_EAX
}, eaxFunctions[] = {
DECL(EAXGet),
diff --git a/alc/context.cpp b/alc/context.cpp
index 86c76aaa..a892bb6d 100644
--- a/alc/context.cpp
+++ b/alc/context.cpp
@@ -78,6 +78,7 @@ constexpr ALchar alExtList[] =
"AL_SOFT_source_length "
"AL_SOFT_source_resampler "
"AL_SOFT_source_spatialize "
+ "AL_SOFTX_source_start_delay "
"AL_SOFT_UHJ";
} // namespace
diff --git a/alc/inprogext.h b/alc/inprogext.h
index 9af80f12..5dfc22cb 100644
--- a/alc/inprogext.h
+++ b/alc/inprogext.h
@@ -54,6 +54,15 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *
#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB
#endif
+#ifndef AL_SOFT_source_start_delay
+#define AL_SOFT_source_start_delay
+typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time);
+typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time);
+#ifdef AL_ALEXT_PROTOTYPES
+void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time);
+void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time);
+#endif
+#endif
/* Non-standard export. Not part of any extension. */
AL_API const ALchar* AL_APIENTRY alsoft_get_version(void);