载入中...
搜索中...
未找到
AudioCapabilities.cpp
浏览该文件的文档.
7#include "audio/Audio.h"
8#include "audio/Source.h"
9#include "common/AudioQuery.h"
10#include "common/Capability.h"
12#include "sound/Sound.h"
13#include "sound/SoundData.h"
14
15#include <algorithm>
16#include <cstdint>
17#include <cmath>
18#include <cstring>
19#include <list>
20#include <limits>
21#include <map>
22#include <memory>
23#include <optional>
24#include <set>
25#include <string>
26#include <utility>
27#include <vector>
28
29namespace eve::audio {
30namespace {
31
32std::uint64_t stableAudioSeed(eve::action::ActionExecutionId executionId,
33 const eve::LogicalId& itemId, std::uint64_t salt = 0) {
34 std::uint64_t hash = 1469598103934665603ULL ^ executionId.value() ^ salt;
35 for (const unsigned char value : itemId.format()) {
36 hash ^= value;
37 hash *= 1099511628211ULL;
38 }
39 return hash;
40}
41
42const std::string& selectAudioUri(const eve::action::ActionAudioBinding& binding, std::uint64_t seed) {
43 if (binding.randomUris.empty()) return binding.uri;
44 return binding.randomUris[static_cast<std::size_t>(seed % binding.randomUris.size())];
45}
46
47double selectAudioPitch(const eve::action::ActionAudioBinding& binding, std::uint64_t seed) {
48 if (binding.randomPitchOffset <= 0.0) return binding.pitch;
49 constexpr double divisor = static_cast<double>(std::numeric_limits<std::uint64_t>::max());
50 const double unit = static_cast<double>(seed) / divisor;
51 return std::clamp(binding.pitch + (unit * 2.0 - 1.0) * binding.randomPitchOffset, 0.1, 3.0);
52}
53
54void configureAudioSource(Source& source, const eve::action::ActionAudioBinding& binding,
55 double pitch) {
56 source.setVolume(static_cast<float>(binding.volume));
57 source.setPitch(static_cast<float>(pitch));
58 source.setLooping(binding.looping);
59 source.setRelative(binding.spatialBlend <= 0.01);
60 source.setAttenuationDistances(static_cast<float>(binding.minDistance),
61 static_cast<float>(binding.maxDistance));
62}
63
64class AudioQueryImpl final : public eve::IAudioQuery {
65public:
66 float volume() const override {
67 auto *a = eve::ModuleManager::getInstance<Audio>("Audio");
68 return a ? a->getVolume() : 1.f;
69 }
70
71 void setVolume(float v) override {
72 if (auto *a = eve::ModuleManager::getInstance<Audio>("Audio")) a->setVolume(v);
73 }
74
75 void stopAll() override {
76 if (auto *a = eve::ModuleManager::getInstance<Audio>("Audio")) a->stopAll();
77 }
78};
79
80class ActionAudioWaveformProvider final : public eve::action::IActionAudioWaveformProvider {
81public:
84 if (request.bucketCount == 0 || request.bucketCount > kMaximumBuckets)
86 "Audio waveform bucket count is outside the bounded range", "bucketCount");
87 if (request.blockDuration <= eve::Duration::zero())
89 "Audio waveform block duration must be positive", "blockDuration");
90 if (request.binding.uri.empty())
92 "Audio waveform URI must be non-empty", "uri");
93 if (!std::isfinite(request.binding.pitch) || request.binding.pitch <= 0.0)
95 "Audio waveform pitch must be positive and finite", "pitch");
96 try {
97 auto* data = eve::sound::Sound::create()->newSoundDataFromFile(request.binding.uri);
98 if (!data || data->getSampleRate() <= 0 || data->getSampleCount() <= 0 ||
99 (data->getBitDepth() != 8 && data->getBitDepth() != 16) ||
100 (data->getChannelCount() != 1 && data->getChannelCount() != 2))
102 "Audio waveform requires bounded PCM8 or PCM16 data", "uri");
103 // The provider returns a cache-owned SoundData; pin it so a concurrent
104 // unload cannot destroy the payload while this request samples it.
105 auto retained = eve::ResourceManager::getInstance().pin(data);
106 if (!retained.ok())
107 return failure(eve::DiagnosticCode::NotFound, "audio waveform resource is no longer cached", "uri");
108 eve::ResourcePin keepAlive = std::move(retained).takeValue();
109 // The pin is the authority from here on; the borrowed pointer may have gone
110 // stale before the pin was taken.
111 data = static_cast<eve::sound::SoundData*>(keepAlive.get());
113 result.clipDurationSeconds = data->getDuration();
114 result.buckets.resize(request.bucketCount);
115 const double blockSeconds = request.blockDuration.seconds();
116 const auto frameCount = static_cast<std::size_t>(data->getSampleCount());
117 for (std::size_t bucket = 0; bucket < request.bucketCount; ++bucket) {
118 const double localStart = blockSeconds * static_cast<double>(bucket) /
119 static_cast<double>(request.bucketCount);
120 const double localEnd = blockSeconds * static_cast<double>(bucket + 1) /
121 static_cast<double>(request.bucketCount);
122 result.buckets[bucket] = sampleBucket(*data, request.binding, localStart, localEnd, frameCount);
123 }
125 } catch (const std::exception& error) {
126 return failure(eve::DiagnosticCode::Failed, error.what(), "uri");
127 }
128 }
129
130private:
131 static constexpr std::size_t kMaximumBuckets = 4096;
132 static constexpr std::size_t kMaximumSamplesPerBucket = 8192;
133
134 static float sampleAt(const eve::sound::SoundData& data, std::size_t frame, int channel) noexcept {
135 const auto channels = static_cast<std::size_t>(data.getChannelCount());
136 const auto index = frame * channels + static_cast<std::size_t>(channel);
137 const auto* bytes = static_cast<const std::uint8_t*>(data.getData());
138 if (data.getBitDepth() == 8)
139 return (static_cast<float>(bytes[index]) - 128.0f) / 128.0f;
140 std::int16_t sample = 0;
141 std::memcpy(&sample, bytes + index * sizeof(sample), sizeof(sample));
142 return static_cast<float>(sample) / 32768.0f;
143 }
144
145 static eve::action::ActionAudioWaveformBucket sampleBucket(
147 double localStart, double localEnd, std::size_t frameCount) noexcept {
149 result.minimum = 1.0f;
150 result.maximum = -1.0f;
151 const double duration = data.getDuration();
152 if (duration <= 0.0) return {};
153 double mediaStart = localStart * binding.pitch;
154 double mediaEnd = localEnd * binding.pitch;
155 if (!binding.looping && mediaStart >= duration) return {};
156 if (binding.looping) {
157 const double span = std::max(0.0, mediaEnd - mediaStart);
158 mediaStart = span >= duration ? 0.0 : std::fmod(mediaStart, duration);
159 mediaEnd = mediaStart + std::min(span, duration);
160 } else {
161 mediaStart = std::min(mediaStart, duration);
162 mediaEnd = std::min(mediaEnd, duration);
163 }
164 const double mediaSpan = std::max(0.0, mediaEnd - mediaStart);
165 const auto estimatedFrames = static_cast<std::size_t>(
166 std::ceil(mediaSpan * static_cast<double>(data.getSampleRate())));
167 const std::size_t stride = std::max<std::size_t>(
168 1, (estimatedFrames + kMaximumSamplesPerBucket - 1) / kMaximumSamplesPerBucket);
169 const std::size_t samples = std::max<std::size_t>(
170 1, std::min(estimatedFrames + 1, kMaximumSamplesPerBucket));
171 for (std::size_t offset = 0; offset < samples; ++offset) {
172 double media = mediaStart + static_cast<double>(offset * stride) /
173 static_cast<double>(data.getSampleRate());
174 if (binding.looping)
175 media = std::fmod(media, duration);
176 else
177 media = std::min(media, duration);
178 const auto frame = std::min(frameCount - 1, static_cast<std::size_t>(media * data.getSampleRate()));
179 for (int channel = 0; channel < data.getChannelCount(); ++channel) {
180 const float value = sampleAt(data, frame, channel);
181 result.minimum = std::min(result.minimum, value);
182 result.maximum = std::max(result.maximum, value);
183 }
184 }
185 if (result.minimum > result.maximum) return {};
186 return result;
187 }
188
190 eve::DiagnosticCode code, std::string message, std::string path) {
193 }
194};
195
196class AudioActionParameterSink final : public eve::action::IActionParameterSink {
197public:
198 bool supports(const eve::LogicalId& target) const noexcept override {
199 return target.format() == "audio:master-volume";
200 }
201
202 eve::Result<void> apply(const eve::action::ActionParameterSample& sample) override {
203 auto* audio = eve::ModuleManager::getInstance<Audio>("Audio");
204 if (!audio)
205 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "Audio parameter target is unavailable", "audio"));
206 const ActiveKey key{sample.executionId, sample.itemId.format()};
207 auto candidate = active_;
209 if (candidate.contains(key))
210 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "Audio parameter curve is already active", "itemId"));
211 if (candidate.empty()) baseline_ = audio->getVolume();
212 candidate.emplace(key, ActiveValue{sample.operation, sample.value});
214 const auto found = candidate.find(key);
215 if (found == candidate.end())
216 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "Audio parameter curve has no active state", "itemId"));
217 found->second = ActiveValue{sample.operation, sample.value};
218 } else {
219 if (!candidate.erase(key))
221 }
222 const double value = compose(candidate);
223 if (!std::isfinite(value) || value < 0.0 || value > static_cast<double>(std::numeric_limits<float>::max()))
224 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "Audio parameter curves produced an invalid master volume", "value"));
225 active_ = std::move(candidate);
226 audio->setVolume(static_cast<float>(value));
228 }
229
230private:
231 using ActiveKey = std::pair<eve::action::ActionExecutionId, std::string>;
232 struct ActiveValue {
234 double value = 0.0;
235 };
236
237 double compose(const std::map<ActiveKey, ActiveValue>& values) const noexcept {
238 double result = baseline_;
239 for (const auto& [key, active] : values) {
240 (void)key;
241 switch (active.operation) {
242 case eve::action::ActionParameterOperation::Replace: result = active.value; break;
243 case eve::action::ActionParameterOperation::Add: result += active.value; break;
244 case eve::action::ActionParameterOperation::Multiply: result *= active.value; break;
245 }
246 }
247 return result;
248 }
249
250
251 std::map<ActiveKey, ActiveValue> active_;
252 double baseline_ = 1.0;
253};
254
255using ActiveKey = std::pair<eve::action::ActionExecutionId, std::string>;
256
257class AudioActionHandler final : public eve::action::IActionNotifyHandler {
258public:
261 const ActiveKey key{context.executionId, event.itemId.format()};
263 const auto found = active_.find(key);
264 if (found == active_.end())
266 if (!context.interrupted && found->second.fadeOutOnExit && found->second.fadeOutDuration > 0.0) {
267 auto duration = eve::Duration::fromSeconds(found->second.fadeOutDuration);
268 if (!duration) return eve::Result<void>::failure(duration.status());
269 auto end = context.time.tryAdd(duration.value());
270 if (!end) return eve::Result<void>::failure(end.status());
271 fading_.push_back({context.executionId, context.time, std::move(end).takeValue(),
272 found->second.volume, std::move(found->second)});
273 } else {
274 found->second.source->stop();
275 }
276 active_.erase(found);
278 }
279 const bool instant = event.kind == eve::action::ActionTimelineEventKind::Notify;
281 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "audio state requires enter or exit", "event.kind"));
282 if (!instant && active_.contains(key))
283 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "audio state is already active", "itemId"));
286 if (!binding) return eve::Result<void>::failure(binding.status());
287 auto pose = resolvePose(binding.value().spatial, context);
288 if (!pose) return eve::Result<void>::failure(pose.status());
289 auto* audio = eve::ModuleManager::getInstance<Audio>("Audio");
290 if (!audio) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "Audio module is unavailable", "audio"));
291 try {
292 const auto seed = stableAudioSeed(context.executionId, event.itemId);
293 auto* data = eve::sound::Sound::create()->newSoundDataFromFile(selectAudioUri(binding.value(), seed));
294 auto dataPin = eve::ResourceManager::getInstance().pin(data);
295 if (!dataPin.ok()) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "audio resource is no longer cached", "uri"));
296 eve::ResourcePin keepAlive = std::move(dataPin).takeValue();
297 auto* live = static_cast<eve::sound::SoundData*>(keepAlive.get());
298 std::unique_ptr<Source> source(audio->newSource(live));
299 configureAudioSource(*source, binding.value(), selectAudioPitch(binding.value(), seed ^ 0x9e3779b97f4a7c15ULL));
300 applyPosition(*source, binding.value().spatial, pose.value());
301 source->play();
302 ActiveSource owned{std::move(keepAlive), std::move(source), binding.value().spatial,
303 std::move(pose).takeValue(), binding.value().volume, binding.value().fadeOutOnExit,
304 binding.value().fadeOutDuration};
305 if (instant) {
306 auto duration = eve::Duration::fromSeconds(owned.source->getDuration() / owned.source->getPitch());
307 if (!duration) return eve::Result<void>::failure(duration.status());
308 auto end = context.time.tryAdd(duration.value());
309 if (!end) return eve::Result<void>::failure(end.status());
310 transients_.push_back({context.executionId, std::move(end).takeValue(), std::move(owned)});
311 } else {
312 active_.emplace(key, std::move(owned));
313 }
314 } catch (const std::exception& error) {
316 }
318 }
319
322 const auto found = active_.find({context.executionId, block.itemId.format()});
323 if (found == active_.end()) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "active audio has no source", "itemId"));
325 auto pose = resolvePose(found->second.spatial, context);
326 if (!pose) return eve::Result<void>::failure(pose.status());
327 found->second.pose.positionX = pose.value().positionX;
328 found->second.pose.positionY = pose.value().positionY;
329 found->second.pose.positionZ = pose.value().positionZ;
331 found->second.pose = std::move(pose).takeValue();
332 }
333 applyPosition(*found->second.source, found->second.spatial, found->second.pose);
335 }
336
338 const eve::action::ActionNotifyContext&) const override {
341 if (!binding) return eve::Result<void>::failure(binding.status());
343 }
344
346 const auto before = transients_.size();
347 std::erase_if(transients_, [&](TransientSource& transient) {
348 if (transient.executionId != context.executionId || context.time < transient.endTime) return false;
349 transient.owned.source->stop();
350 return true;
351 });
352 const auto fadingBefore = fading_.size();
353 bool fadingAdvanced = false;
354 std::erase_if(fading_, [&](FadingSource& fading) {
355 if (fading.executionId != context.executionId) return false;
356 fadingAdvanced = true;
357 if (context.time < fading.startTime || context.time >= fading.endTime) {
358 fading.owned.source->stop();
359 return true;
360 }
361 const auto elapsed = context.time.nanoseconds() - fading.startTime.nanoseconds();
362 const auto duration = fading.endTime.nanoseconds() - fading.startTime.nanoseconds();
363 const double remaining = 1.0 - static_cast<double>(elapsed) / static_cast<double>(duration);
364 fading.owned.source->setVolume(static_cast<float>(fading.startVolume * remaining));
365 return false;
366 });
368 before == transients_.size() && fadingBefore == fading_.size() && !fadingAdvanced
370 : eve::StatusCode::Applied));
371 }
372
373private:
374 struct ActiveSource {
375 // Source borrows this data; the pin keeps it alive across cache unload.
377 std::unique_ptr<Source> source;
380 double volume = 1.0;
381 bool fadeOutOnExit = true;
382 double fadeOutDuration = 0.1;
383 };
384 struct TransientSource {
387 ActiveSource owned;
388 };
389 struct FadingSource {
394 ActiveSource owned;
395 };
396
397 static eve::Result<eve::EntitySpatialPose> resolvePose(
399 std::optional<ecs::EntityHandle> handle;
402 handle = context.source;
403 attachments = context.sourceAttachment;
404 } else {
405 if (spatial.targetIndex >= context.targets.size())
408 "audio target index is unavailable", "targetIndex"));
410 if (spatial.targetIndex < context.targetAttachments.size())
411 attachments = context.targetAttachments[spatial.targetIndex];
412 }
414 if (handle) {
416 if (!root) return root;
417 pose = std::move(root).takeValue();
418 }
419 if (spatial.bone.empty()) return eve::Result<eve::EntitySpatialPose>::success(std::move(pose));
420 if (!attachments)
423 "audio bone requires an attachment source", "bone"));
424 auto point = attachments->get().sampleAttachmentPoint(
425 spatial.bone, {static_cast<float>(spatial.positionOffset.x),
426 static_cast<float>(spatial.positionOffset.y),
427 static_cast<float>(spatial.positionOffset.z)});
429 pose.positionX = point.value().x;
430 pose.positionY = point.value().y;
431 pose.positionZ = point.value().z;
433 }
434
435 static void applyPosition(Source& source, const eve::action::ActionSpatialBinding& spatial,
437 const double offsetX = spatial.bone.empty() ? spatial.positionOffset.x : 0.0;
438 const double offsetY = spatial.bone.empty() ? spatial.positionOffset.y : 0.0;
439 const double offsetZ = spatial.bone.empty() ? spatial.positionOffset.z : 0.0;
440 source.setPosition(static_cast<float>(pose.positionX + offsetX),
441 static_cast<float>(pose.positionY + offsetY),
442 static_cast<float>(pose.positionZ + offsetZ));
443 }
444
445
446 std::map<ActiveKey, ActiveSource> active_;
447 std::vector<TransientSource> transients_;
448 std::vector<FadingSource> fading_;
449};
450
451class AudioActionPreviewSink final : public eve::action::IActionPreviewSink {
452public:
453 ~AudioActionPreviewSink() override {
454 discardPrepared();
455 stopAll(current_);
456 stopAll(transients_);
457 }
458
459 eve::Result<void> prepare(const eve::action::ActionPreviewFrame& frame) override {
460 discardPrepared();
461 const bool retainContinuous = frame.reason == eve::action::ActionPreviewReason::Advance;
462 for (const auto& block : frame.activeBlocks) {
463 if (block.type.format() != "presentation:audio-state") continue;
464 const std::string key = block.itemId.format();
467 if (!binding) return failPrepared(binding.status());
468 const auto current = current_.find(key);
469 if (retainContinuous && current != current_.end() && current->second.payload == block.payload) {
470 preparedRetained_.insert(key);
471 continue;
472 }
473 auto audio = makeAudio(binding.value(), block.localTime,
474 stableAudioSeed(eve::action::ActionExecutionId::zero(), block.itemId));
475 if (!audio) return failPrepared(audio.status());
476 if (audio.value())
477 preparedStates_.emplace(key, PreviewAudio{block.payload, std::move(*audio.value())});
478 }
479 for (const auto& cue : frame.cues) {
481 cue.type.format() != "presentation:audio")
482 continue;
485 if (!binding) return failPrepared(binding.status());
486 auto audio = makeAudio(binding.value(), eve::Duration::zero(),
487 stableAudioSeed(eve::action::ActionExecutionId::zero(), cue.itemId));
488 if (!audio) return failPrepared(audio.status());
489 if (audio.value()) preparedInstants_.push_back(std::move(*audio.value()));
490 }
492 }
493
494 void present(const eve::action::ActionPreviewFrame&) noexcept override {
495 for (auto it = current_.begin(); it != current_.end();) {
496 if (preparedRetained_.contains(it->first)) {
497 ++it;
498 continue;
499 }
500 it->second.audio.source->stop();
501 it = current_.erase(it);
502 }
503 current_.merge(preparedStates_);
504 for (auto& [key, state] : current_) {
505 if (preparedRetained_.contains(key)) continue;
506 state.audio.source->play();
507 }
508 preparedRetained_.clear();
509
510 std::erase_if(transients_, [](const OwnedAudio& audio) { return !audio.source->isPlaying(); });
511 for (auto& audio : preparedInstants_) audio.source->play();
512 transients_.splice(transients_.end(), preparedInstants_);
513 }
514
515 void discardPrepared() noexcept override {
516 preparedStates_.clear();
517 preparedInstants_.clear();
518 preparedRetained_.clear();
519 }
520
521private:
522 struct OwnedAudio {
523 OwnedAudio(eve::ResourcePin data, std::unique_ptr<Source> source)
524 : data(std::move(data)), source(std::move(source)) {}
525
526 // Source borrows this data; the pin keeps it alive across cache unload.
527 eve::ResourcePin data;
528 std::unique_ptr<Source> source;
529 };
530 struct PreviewAudio {
532 OwnedAudio audio;
533 };
534
535 eve::Result<void> failPrepared(const eve::Status& status) {
536 discardPrepared();
538 }
539
541 const eve::action::ActionAudioBinding& binding, eve::Duration localTime, std::uint64_t seed) {
542 auto* audio = eve::ModuleManager::getInstance<Audio>("Audio");
543 if (!audio)
545 eve::DiagnosticCode::NotFound, "Audio preview requires the Audio module", "audio"));
546 try {
547 auto* data = eve::sound::Sound::create()->newSoundDataFromFile(selectAudioUri(binding, seed));
548 auto dataPin = eve::ResourceManager::getInstance().pin(data);
549 if (!dataPin.ok()) return eve::Result<std::optional<OwnedAudio>>::failure(dataPin.status());
550 eve::ResourcePin keepAlive = std::move(dataPin).takeValue();
551 auto* live = static_cast<eve::sound::SoundData*>(keepAlive.get());
552 std::unique_ptr<Source> source(audio->newSource(live));
553 OwnedAudio owned(std::move(keepAlive), std::move(source));
554 configureAudioSource(*owned.source, binding,
555 selectAudioPitch(binding, seed ^ 0x9e3779b97f4a7c15ULL));
556 owned.source->setPosition(static_cast<float>(binding.spatial.positionOffset.x),
557 static_cast<float>(binding.spatial.positionOffset.y),
558 static_cast<float>(binding.spatial.positionOffset.z));
559 const double duration = owned.source->getDuration();
560 double mediaTime = localTime.seconds() * owned.source->getPitch();
561 if (duration > 0.0) {
562 if (binding.looping)
563 mediaTime = std::fmod(mediaTime, duration);
564 else if (mediaTime >= duration)
565 return eve::Result<std::optional<OwnedAudio>>::success(std::nullopt);
566 }
567 if (mediaTime > 0.0 && !owned.source->seek(mediaTime))
569 eve::DiagnosticCode::Unsupported, "Audio preview source does not support seeking", "uri"));
571 std::optional<OwnedAudio>(std::move(owned)));
572 } catch (const std::exception& error) {
575 }
576 }
577
578 static void stopAll(std::map<std::string, PreviewAudio>& values) noexcept {
579 for (auto& [key, value] : values) {
580 (void)key;
581 value.audio.source->stop();
582 }
583 values.clear();
584 }
585
586 static void stopAll(std::list<OwnedAudio>& values) noexcept {
587 for (auto& value : values) value.source->stop();
588 values.clear();
589 }
590
591 std::map<std::string, PreviewAudio> current_;
592 std::list<OwnedAudio> transients_;
593 std::map<std::string, PreviewAudio> preparedStates_;
594 std::list<OwnedAudio> preparedInstants_;
595 std::set<std::string> preparedRetained_;
596};
597
598class AudioActionProvider final : public eve::action::IActionNotifyProvider,
600public:
601 eve::Result<void> install(eve::action::ActionNotifyRegistry& registry) override {
602 auto handler = std::make_shared<AudioActionHandler>();
603 auto instant = registry.registerHandler("presentation:audio", handler);
604 if (!instant) return instant;
605 return registry.registerHandler("presentation:audio-state", std::move(handler));
606 }
607
608 eve::Result<std::unique_ptr<eve::action::IActionPreviewSink>> createActionPreviewSink() override {
609 if (!eve::ModuleManager::getInstance<Audio>("Audio"))
611 eve::DiagnosticCode::NotFound, "Audio preview requires the Audio module", "audio"));
613 std::make_unique<AudioActionPreviewSink>());
614 }
615};
616
617} // namespace
618
620 static AudioQueryImpl impl;
621 static AudioActionProvider actionProvider;
622 static ActionAudioWaveformProvider waveformProvider;
623 static AudioActionParameterSink parameterSink;
624 eve::cap::provide<eve::IAudioQuery>(&impl);
625 eve::cap::provide<eve::action::IActionAudioWaveformProvider>(&waveformProvider);
626 eve::cap::addListener<eve::action::IActionParameterSink>(&parameterSink);
627 eve::cap::addListener<eve::action::IActionNotifyProvider>(&actionProvider);
628 eve::cap::addListener<eve::action::IActionPreviewSinkProvider>(&actionProvider);
629}
630
631} // namespace eve::audio
Typed audio action-block payload contract.
Optional audio waveform projection for action editors.
LogicalId target
ActionParameterOperation operation
double value
Extensible notify descriptors, validation and runtime routing.
Typed continuous parameter-curve action block.
Presentation-neutral action preview frame contracts.
Typed spatial payload shared by presentation action blocks.
Value::Object payload
LogicalId itemId
bool & active
float duration
std::map< std::string, std::vector< Key >, std::less<> > channels
int root
Definition AnimSmr.cpp:119
eve::action::ActionExecutionId executionId
OwnedAudio audio
ActiveSource owned
double fadeOutDuration
double startVolume
eve::Duration endTime
double volume
eve::Duration startTime
eve::EntitySpatialPose pose
eve::action::ActionSpatialBinding spatial
bool fadeOutOnExit
void * impl
std::map< std::string, Var > values
Optional ECS-entity to world-pose capability.
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::string message
DiagnosticCode code
const GltfImportRequest & request
std::uint32_t key
ShaderImageInput shape
wgpu::PopErrorScopeStatus status
float v
size_t offset
std::uint64_t bytes
HSQOBJECT handler
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
eve::action::ActionVfxBinding binding
double mediaTime
std::string path
Definition PlayHost.cpp:110
std::uint32_t seed
Definition PointSet.cpp:807
PrimitiveHandle handle
bool found
double current
TacticalUnit * unit
float offsetX
float offsetY
uint32_t index
const UnitySourceAsset & source
const VegetationPresetContext & context
glm::vec3 point
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Signed, fixed-resolution simulation duration in nanoseconds.
Definition Time.h:43
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
static constexpr Duration zero() noexcept
Return zero duration.
Definition Time.h:60
double seconds() const noexcept
Return this duration as seconds for legacy/presentation APIs.
Definition Time.cpp:28
Audio control surface (provided by the audio module).
Definition AudioQuery.h:8
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
eve::Result< ResourcePin > pin(Resource *resource)
Keeps one cached resource alive across unload()/clear()/reload.
Definition Resource.cpp:311
static ResourceManager & getInstance()
Returns the instance.
Definition Resource.cpp:15
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
std::map< std::string, Value > Object
Definition Value.h:34
Canonical notify descriptor owner and runtime handler router.
Result< void > registerHandler(std::string_view type, std::shared_ptr< IActionNotifyHandler > handler)
Register an owning handler for an existing descriptor.
Optional synchronous audio waveform service consumed by the action editor.
Consumer-owned handler for one registered notify type.
Optional module provider that installs handlers into a new registry.
Optional open target for continuous action parameters.
Optional owner-thread provider of one independently owned action preview layer.
Host boundary for 3D animation, VFX, audio and camera preview.
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
RAII keep-alive for one RuntimeObjectRegistry entry.
T * get() const noexcept
Borrows the pinned object; valid until the pin is released.
Raw PCM audio buffer with format metadata (samples/rate/bit depth/channels).
Definition SoundData.h:15
ActionParameterOperation
Composition operation applied by a target-specific parameter sink.
@ FollowTarget
Re-evaluate position and rotation from the anchor every update.
@ WorldTransformAtStart
Capture the complete world transform once on Enter.
void registerAudioCapabilities()
Registers audio capabilities.
double sample(const Heightmap &map, double u, double v)
Sample.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
Result< EntitySpatialPose > resolveEntitySpatialPose(ecs::EntityHandle entity, std::string_view bone)
Resolve through registered providers in deterministic registration order.
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
std::optional< std::reference_wrapper< T > > OptionalRef
Optional borrowed reference; it does not extend T's lifetime.
Definition BorrowedRef.h:24
Renderer-neutral owning world pose resolved for an ECS entity.
Owning per-step sample of one currently active timeline state.
Owning validated settings shared by Audio runtime and editor preview.
static Result< ActionAudioBinding > fromPayload(const Value::Object &payload, ActionAudioShape shape)
Decode an audio payload transactionally.
One normalized min/max envelope bucket in timeline display order.
Owning request for a bounded audio-block waveform projection.
Owning waveform projection returned by an optional audio provider.
double clipDurationSeconds
Decoded source duration used for diagnostics and host labels.
std::vector< ActionAudioWaveformBucket > buckets
Min/max buckets in increasing block-local time.
Context linking a routed event to its action execution.
Owning continuous parameter sample emitted by ActionBlockRuntime.
Complete frame atomically prepared and then presented by a preview host.
Validated spatial settings decoded from a notify payload.
Owning event projection returned by the runtime sampler.
ActionTimelineEventKind kind
ActionSpatialBinding spatial
uint32_t bucket