WIP: StartOffset
This commit is contained in:
@@ -11,6 +11,7 @@ namespace OCES.Audio
|
||||
double m_startDspTime, m_secondsPerBeat; // 本次计数周期的起点
|
||||
|
||||
Coroutine m_beatCoroutine, m_barCoroutine, m_gridCoroutine;
|
||||
Coroutine m_delayCoroutine;
|
||||
readonly Action<uint> m_onBeat, m_onBar, m_onGrid;
|
||||
|
||||
readonly MonoBehaviour m_host;
|
||||
@@ -26,13 +27,13 @@ namespace OCES.Audio
|
||||
this.m_onGrid = onGrid;
|
||||
}
|
||||
|
||||
internal void Restart(MusicContainer container, float inheritedBpm, double dspTime)
|
||||
internal void Restart(MusicContainer container, float inheritedBpm, double dspTime, double startOffset = 0d)
|
||||
{
|
||||
//Debug.Log($"[BeatClock] Restarting {container.Id}, inheritedBpm = {inheritedBpm}, dspTime = {dspTime}");
|
||||
StopAll();
|
||||
this.m_blendError = this.m_stopped = false;
|
||||
this.m_containerId = container.Id;
|
||||
this.m_startDspTime = dspTime;
|
||||
this.m_startDspTime = dspTime + startOffset;
|
||||
|
||||
// BPM:优先用自己的,为 0 则用传入的继承值
|
||||
float bpm = container.Bpm > 0f ? container.Bpm : inheritedBpm;
|
||||
@@ -57,6 +58,32 @@ namespace OCES.Audio
|
||||
this.m_barsPerGrid = container.Grid;
|
||||
}
|
||||
|
||||
double delay = this.m_startDspTime - AudioSettings.dspTime;
|
||||
if (delay > 0)
|
||||
{
|
||||
this.m_delayCoroutine = this.m_host.StartCoroutine(DelayedStart(delay, hasTimeSig, hasGrid));
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCoroutines(hasTimeSig, hasGrid);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator DelayedStart(double delay, bool hasTimeSig, bool hasGrid)
|
||||
{
|
||||
yield return new WaitUntil(() => AudioSettings.dspTime >= this.m_startDspTime - 0.02);
|
||||
|
||||
while (AudioSettings.dspTime < this.m_startDspTime)
|
||||
yield return null;
|
||||
|
||||
if (this.m_stopped || this.m_blendError) yield break;
|
||||
|
||||
this.m_delayCoroutine = null;
|
||||
StartCoroutines(hasTimeSig, hasGrid);
|
||||
}
|
||||
|
||||
void StartCoroutines(bool hasTimeSig, bool hasGrid)
|
||||
{
|
||||
// 分层启动协程
|
||||
// Beat:只要 BPM 有效就启动
|
||||
this.m_beatCoroutine = this.m_host.StartCoroutine(BeatCoroutine());
|
||||
@@ -82,10 +109,11 @@ namespace OCES.Audio
|
||||
internal void StopAll()
|
||||
{
|
||||
this.m_stopped = true;
|
||||
if (this.m_beatCoroutine != null) this.m_host.StopCoroutine(this.m_beatCoroutine);
|
||||
if (this.m_barCoroutine != null) this.m_host.StopCoroutine(this.m_barCoroutine);
|
||||
if (this.m_gridCoroutine != null) this.m_host.StopCoroutine(this.m_gridCoroutine);
|
||||
this.m_beatCoroutine = this.m_barCoroutine = this.m_gridCoroutine = null;
|
||||
if (this.m_delayCoroutine != null) this.m_host.StopCoroutine(this.m_delayCoroutine);
|
||||
if (this.m_beatCoroutine != null) this.m_host.StopCoroutine(this.m_beatCoroutine);
|
||||
if (this.m_barCoroutine != null) this.m_host.StopCoroutine(this.m_barCoroutine);
|
||||
if (this.m_gridCoroutine != null) this.m_host.StopCoroutine(this.m_gridCoroutine);
|
||||
this.m_delayCoroutine = this.m_beatCoroutine = this.m_barCoroutine = this.m_gridCoroutine = null;
|
||||
}
|
||||
|
||||
internal double GetNextDspTime(AlignMode mode)
|
||||
@@ -98,6 +126,11 @@ namespace OCES.Audio
|
||||
}
|
||||
|
||||
double elapsed = now - this.m_startDspTime;
|
||||
if (elapsed < 0)
|
||||
{
|
||||
return this.m_startDspTime;
|
||||
}
|
||||
|
||||
double period = mode switch
|
||||
{
|
||||
AlignMode.Beat => this.m_secondsPerBeat,
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace OCES.Audio
|
||||
public double BaseDspTime; // 读取Source Segment 的 dspTime
|
||||
public int SampleRate; // Source Segment 的采样率
|
||||
public bool Ready;
|
||||
public double SourceStartOffset;
|
||||
public double TargetAudioTime;
|
||||
public double StartPlayTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,10 +44,12 @@ namespace OCES.Audio
|
||||
CurrentContainerId = containerId;
|
||||
CurrentVolume = startVolume;
|
||||
CurrentHandle = this.m_player.Play(containerId);
|
||||
Debug.Log($"[ChannelFader] StartNew: containerId={containerId}, CurrentHandle={CurrentHandle}");
|
||||
}
|
||||
|
||||
public void StopCurrent()
|
||||
{
|
||||
Debug.Log($"[ChannelFader] StopCurrent called! CurrentHandle={CurrentHandle}, stack=\n{Environment.StackTrace}");
|
||||
StopHandle(CurrentHandle);
|
||||
CurrentHandle = null;
|
||||
CurrentContainerId = 0;
|
||||
@@ -84,7 +89,8 @@ namespace OCES.Audio
|
||||
/// 淡入分支:等待 FadeInOffset 后启动新音乐并淡入。
|
||||
/// 主协程 yield return 此分支,以便 DoTransition 在新音乐就绪后才结束。
|
||||
/// </summary>
|
||||
internal IEnumerator FadeInBranch(uint newContainerId, ITransitionConfig transition, SyncPointState syncState = null, Action onContainerStarted = null)
|
||||
internal IEnumerator FadeInBranch(uint newContainerId, ITransitionConfig transition, SyncPointState syncState = null,
|
||||
Action onContainerStarted = null)
|
||||
{
|
||||
if (transition?.FadeInOffset > 0f)
|
||||
{
|
||||
@@ -94,7 +100,7 @@ namespace OCES.Audio
|
||||
|
||||
if (newContainerId == 0)
|
||||
{
|
||||
CurrentHandle = null;
|
||||
CurrentHandle = null;
|
||||
CurrentContainerId = 0;
|
||||
if (syncState != null) syncState.Ready = true;
|
||||
yield break;
|
||||
@@ -102,43 +108,63 @@ namespace OCES.Audio
|
||||
|
||||
float startVolume = transition?.FadeInTime > 0f ? 0f : 1f;
|
||||
StartNew(newContainerId, startVolume);
|
||||
onContainerStarted?.Invoke();
|
||||
|
||||
// SyncPoint: 获取新 Segment 的 AudioSource 并设置 timeSamples
|
||||
if (syncState is { Mode: SyncPoint.SameAsCurrentSegment })
|
||||
switch (syncState)
|
||||
{
|
||||
AudioSource newSource = CurrentHandle?.GetFirstLeafSource();
|
||||
if (newSource && newSource.clip)
|
||||
// SyncPoint: 获取新 Segment 的 AudioSource 并设置 timeSamples
|
||||
case { Mode: SyncPoint.SameAsCurrentSegment }:
|
||||
{
|
||||
// 计算从读取Source Segment 到现在经过的 samples
|
||||
double elapsedSeconds = AudioSettings.dspTime - syncState.BaseDspTime;
|
||||
int elapsedSamples = (int)(elapsedSeconds * syncState.SampleRate);
|
||||
int targetTimeSamples = syncState.BaseTimeSamples + elapsedSamples;
|
||||
|
||||
if (targetTimeSamples < newSource.clip.samples)
|
||||
AudioSource newSource = CurrentHandle?.GetFirstLeafSource();
|
||||
if (newSource && newSource.clip)
|
||||
{
|
||||
newSource.timeSamples = targetTimeSamples;
|
||||
double targetAudioTime = syncState.TargetAudioTime;
|
||||
if (targetAudioTime > 0)
|
||||
{
|
||||
int targetTimeSamples = (int)(targetAudioTime * newSource.clip.frequency);
|
||||
if (targetTimeSamples >= 0 && targetTimeSamples < newSource.clip.samples)
|
||||
{
|
||||
newSource.timeSamples = targetTimeSamples;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[ChannelFader] SyncPoint: 目标 samples({targetTimeSamples}) 超出范围 [0, {newSource.clip.samples}),降级为 Start");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[ChannelFader] SyncPoint.SameAsCurrentSegment: 新音频 samples({newSource.clip.samples}) <= 目标 samples({targetTimeSamples}),降级为 Start");
|
||||
Debug.LogError($"[ChannelFader] SyncPoint.SameAsCurrentSegment: 未能获取新 Segment 的 AudioSource,降级为 Start");
|
||||
}
|
||||
syncState.Ready = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
case { Mode: SyncPoint.Start, StartPlayTime: > 0 }:
|
||||
{
|
||||
Debug.LogError($"[ChannelFader] SyncPoint.SameAsCurrentSegment: 未能获取新 Segment 的 AudioSource,降级为 Start");
|
||||
AudioSource newSource = CurrentHandle?.GetFirstLeafSource();
|
||||
if (!newSource || !newSource.clip)
|
||||
yield break;
|
||||
|
||||
//在赋值的时候减过fade in time了
|
||||
int targetTimeSamples = (int)(syncState.StartPlayTime * newSource.clip.frequency);
|
||||
|
||||
if (targetTimeSamples >= 0 && targetTimeSamples < newSource.clip.samples)
|
||||
{
|
||||
newSource.timeSamples = targetTimeSamples;
|
||||
}
|
||||
break;
|
||||
}
|
||||
syncState.Ready = true;
|
||||
}
|
||||
|
||||
onContainerStarted?.Invoke();
|
||||
|
||||
|
||||
|
||||
if (transition?.FadeInTime > 0f)
|
||||
{
|
||||
yield return this.m_coroutineHost.StartCoroutine(
|
||||
FadeIn(CurrentHandle, transition.FadeInTime));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IEnumerator FadeOut(ContainerPlayHandle handle, float fromVolume, float duration)
|
||||
{
|
||||
//Debug.Log($"Fading out in {duration} seconds.");
|
||||
|
||||
@@ -112,8 +112,9 @@ namespace OCES.Audio
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool isLoop = loopsCompleted > 0;
|
||||
yield return this.m_coroutineHost.StartCoroutine(
|
||||
PlayContainerOnce(container, handle.TargetVolume, handle, effectiveBpm));
|
||||
PlayContainerOnce(container, handle.TargetVolume, handle, effectiveBpm, isLoop));
|
||||
|
||||
if (handle.Cancelled) yield break;
|
||||
|
||||
@@ -139,7 +140,12 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 播放一个 Container 一轮(不含循环逻辑)
|
||||
/// </summary>
|
||||
IEnumerator PlayContainerOnce(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
IEnumerator PlayContainerOnce(
|
||||
MusicContainer container,
|
||||
float volumeScale,
|
||||
ContainerPlayHandle handle,
|
||||
float inheritedBpm,
|
||||
bool isLoop = false)
|
||||
{
|
||||
if (container.Segments == null || container.Segments.Count == 0)
|
||||
yield break;
|
||||
@@ -151,11 +157,11 @@ namespace OCES.Audio
|
||||
break;
|
||||
|
||||
case ContainerType.Sequence:
|
||||
yield return PlaySequence(container, volumeScale, handle, inheritedBpm);
|
||||
yield return PlaySequence(container, volumeScale, handle, inheritedBpm, isLoop);
|
||||
break;
|
||||
|
||||
case ContainerType.Random:
|
||||
yield return PlayRandom(container, volumeScale, handle, inheritedBpm);
|
||||
yield return PlayRandom(container, volumeScale, handle, inheritedBpm, isLoop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -211,7 +217,12 @@ namespace OCES.Audio
|
||||
// Sequence
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
IEnumerator PlaySequence(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
IEnumerator PlaySequence(
|
||||
MusicContainer container,
|
||||
float volumeScale,
|
||||
ContainerPlayHandle handle,
|
||||
float inheritedBpm,
|
||||
bool isLoop = false)
|
||||
{
|
||||
bool isStep = container.ContainerPlayMode;
|
||||
|
||||
@@ -219,7 +230,7 @@ namespace OCES.Audio
|
||||
{
|
||||
// Step: 每次只播一个,游标全局推进
|
||||
int index = GetNextSequenceIndex(container);
|
||||
yield return PlayChildAndWait(container.Segments[index], volumeScale, handle, inheritedBpm);
|
||||
yield return PlayChildAndWait(container.Segments[index], volumeScale, handle, inheritedBpm, isLoop);
|
||||
|
||||
}
|
||||
else
|
||||
@@ -233,7 +244,7 @@ namespace OCES.Audio
|
||||
if (i > 0 && container.StrategyParam > 0)
|
||||
yield return new WaitForSeconds(container.StrategyParam);
|
||||
|
||||
yield return PlayChildAndWait(container.Segments[i], volumeScale, handle, inheritedBpm);
|
||||
yield return PlayChildAndWait(container.Segments[i], volumeScale, handle, inheritedBpm, isLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +253,12 @@ namespace OCES.Audio
|
||||
// Random
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
IEnumerator PlayRandom(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
IEnumerator PlayRandom(
|
||||
MusicContainer container,
|
||||
float volumeScale,
|
||||
ContainerPlayHandle handle,
|
||||
float inheritedBpm,
|
||||
bool isLoop = false)
|
||||
{
|
||||
bool isStep = container.ContainerPlayMode; // 同上,音乐系统默认 Continuous
|
||||
|
||||
@@ -250,7 +266,7 @@ namespace OCES.Audio
|
||||
{
|
||||
// Step Random: 随机选一个播放,算一次 loopCount
|
||||
uint chosen = PickRandomChild(container);
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm);
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm, isLoop);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -268,7 +284,7 @@ namespace OCES.Audio
|
||||
if (container.StrategyParam > 0 && remaining.Count < container.Segments.Count - 1)
|
||||
yield return new WaitForSeconds(container.StrategyParam);
|
||||
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm);
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm, isLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,10 +296,14 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 播放一个子元素(segment 或 container),等待其完成后返回。
|
||||
/// </summary>
|
||||
IEnumerator PlayChildAndWait(uint id, float volumeScale, ContainerPlayHandle parentHandle, float inheritedBpm)
|
||||
IEnumerator PlayChildAndWait(uint id,
|
||||
float volumeScale,
|
||||
ContainerPlayHandle parentHandle,
|
||||
float inheritedBpm,
|
||||
bool isLoop = false)
|
||||
{
|
||||
bool done = false;
|
||||
ContainerPlayHandle child = PlayChild(id, volumeScale, () => done = true, inheritedBpm);
|
||||
ContainerPlayHandle child = PlayChild(id, volumeScale, () => done = true, inheritedBpm, isLoop);
|
||||
if (child != null)
|
||||
parentHandle.ChildHandles.Add(child);
|
||||
|
||||
@@ -299,17 +319,17 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 启动一个子元素的播放,不等待,返回句柄。
|
||||
/// </summary>
|
||||
ContainerPlayHandle PlayChild(uint id, float volumeScale, Action onDone, float inheritedBpm)
|
||||
ContainerPlayHandle PlayChild(uint id, float volumeScale, Action onDone, float inheritedBpm, bool isLoop = false)
|
||||
{
|
||||
// ID < 1000000 是 MusicSegment,否则是嵌套 Container
|
||||
return id < 1000000u ? PlaySegment(id, volumeScale, onDone) : Play(id, onDone, inheritedBpm: inheritedBpm);
|
||||
return id < 1000000u ? PlaySegment(id, volumeScale, onDone, isLoop) : Play(id, onDone, inheritedBpm);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Segment 播放
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
ContainerPlayHandle PlaySegment(uint segmentId, float volumeScale, Action onFinished)
|
||||
ContainerPlayHandle PlaySegment(uint segmentId, float volumeScale, Action onFinished, bool isLoop = false)
|
||||
{
|
||||
MusicSegment segment = this.m_segmentConfig.QueryById(segmentId);
|
||||
if (segment == null)
|
||||
@@ -333,7 +353,12 @@ namespace OCES.Audio
|
||||
source.volume = volumeScale;
|
||||
source.Play();
|
||||
|
||||
var handle = new ContainerPlayHandle();
|
||||
if (isLoop && segment.StartOffset > 0)
|
||||
{
|
||||
source.time = (float)segment.StartOffset;
|
||||
}
|
||||
|
||||
ContainerPlayHandle handle = new();
|
||||
handle.ActiveSources.Add(source);
|
||||
handle.Coroutine = this.m_coroutineHost.StartCoroutine(
|
||||
WaitSegmentFinish(source, handle, onFinished, segment.EndOffset));
|
||||
@@ -342,7 +367,7 @@ namespace OCES.Audio
|
||||
|
||||
IEnumerator WaitSegmentFinish(AudioSource source, ContainerPlayHandle handle, Action onFinished, double endOffset)
|
||||
{
|
||||
double effectiveTime = endOffset > 0f ? source.clip.length - endOffset : 0f;
|
||||
double effectiveTime = endOffset > 0f ? source.clip.length - endOffset : source.clip.length;
|
||||
|
||||
// 1. 等待"逻辑结束"(EndOffset 或自然结束)
|
||||
yield return new WaitWhile(() =>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace OCES.Audio
|
||||
{
|
||||
readonly MusicContainerConfig m_containerConfig;
|
||||
readonly MusicTransitionConfig m_transitionConfig;
|
||||
readonly MusicSegmentConfig m_segmentConfig;
|
||||
readonly MonoBehaviour m_coroutineHost;
|
||||
readonly ChannelFader m_fader;
|
||||
readonly BeatClock m_beatClock;
|
||||
@@ -30,6 +31,7 @@ namespace OCES.Audio
|
||||
|
||||
internal MusicChannelPlayer(
|
||||
MusicContainerConfig containerConfig,
|
||||
MusicSegmentConfig segmentConfig,
|
||||
MusicTransitionConfig transitionConfig,
|
||||
LongAudioContainerPlayer player,
|
||||
MonoBehaviour coroutineHost,
|
||||
@@ -39,6 +41,7 @@ namespace OCES.Audio
|
||||
{
|
||||
this.m_containerConfig = containerConfig;
|
||||
this.m_transitionConfig = transitionConfig;
|
||||
this.m_segmentConfig = segmentConfig;
|
||||
this.m_coroutineHost = coroutineHost;
|
||||
this.m_fader = new ChannelFader(player, coroutineHost);
|
||||
this.m_beatClock = new BeatClock(coroutineHost, onBeat, onBar, onGrid);
|
||||
@@ -55,6 +58,7 @@ namespace OCES.Audio
|
||||
/// </summary>
|
||||
internal void SwitchTo(uint newContainerId)
|
||||
{
|
||||
Debug.Log($"[MusicChannelPlayer] SwitchTo({newContainerId}): CurrentContainerId={this.m_fader.CurrentContainerId}, CurrentHandle={this.m_fader.CurrentHandle}");
|
||||
if (newContainerId == this.m_fader.CurrentContainerId && this.m_fader.CurrentHandle != null)
|
||||
return; // 已经在播目标,无需切换
|
||||
|
||||
@@ -110,6 +114,7 @@ namespace OCES.Audio
|
||||
|
||||
if (syncState.Mode == SyncPoint.SameAsCurrentSegment)
|
||||
{
|
||||
Debug.Log($"[MusicChannelPlayer] DoTransition L117: CurrentHandle={this.m_fader.CurrentHandle}, CurrentContainerId={this.m_fader.CurrentContainerId}");
|
||||
// 旧 Container 不存在(如游戏首次启动),静默降级为 Start
|
||||
if (this.m_fader.CurrentHandle == null)
|
||||
{
|
||||
@@ -129,6 +134,7 @@ namespace OCES.Audio
|
||||
syncState.BaseTimeSamples = oldSource.timeSamples;
|
||||
syncState.BaseDspTime = AudioSettings.dspTime;
|
||||
syncState.SampleRate = oldSource.clip.frequency;
|
||||
syncState.SourceStartOffset = GetEffectiveStartOffset(this.m_currentContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,23 +167,46 @@ namespace OCES.Audio
|
||||
{
|
||||
MusicContainer container = this.m_containerConfig.QueryById(newContainerId);
|
||||
float bpm = container.Bpm;
|
||||
|
||||
// SyncPoint: BeatClock 用调整后的 dspTime,对齐到音频实际播放位置
|
||||
|
||||
double newStartOffset = GetEffectiveStartOffset(container);
|
||||
double dspTime;
|
||||
|
||||
if (syncState is { Mode: SyncPoint.SameAsCurrentSegment })
|
||||
{
|
||||
double elapsedSeconds = AudioSettings.dspTime - syncState.BaseDspTime;
|
||||
int elapsedSamples = (int)(elapsedSeconds * syncState.SampleRate);
|
||||
double audioTime = (double)(syncState.BaseTimeSamples + elapsedSamples) / syncState.SampleRate;
|
||||
dspTime = AudioSettings.dspTime - audioTime;
|
||||
double currentAudioTime = (double)(syncState.BaseTimeSamples + elapsedSamples) / syncState.SampleRate;
|
||||
// 当前的source播到了哪里
|
||||
|
||||
double currentLogicalTime = Math.Max(0, currentAudioTime - syncState.SourceStartOffset);
|
||||
// EntryCue之后播了多少秒。要是小于0说明pre-entry还没播完。
|
||||
|
||||
double newAudioTime = currentLogicalTime + newStartOffset;
|
||||
// 新Audio应该从多少秒开始播放
|
||||
|
||||
bool isPlayPreEntry = true; //TODO transition 是否播放pre-entry
|
||||
syncState.TargetAudioTime = isPlayPreEntry ? currentLogicalTime + newStartOffset : currentLogicalTime;
|
||||
dspTime = AudioSettings.dspTime - newAudioTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
double startPlayTime;
|
||||
if (this.m_currentContainer == null)
|
||||
{
|
||||
startPlayTime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double fadeInTime = transition?.FadeInTime ?? 0f;
|
||||
startPlayTime = newStartOffset - fadeInTime;
|
||||
startPlayTime = Math.Clamp(startPlayTime, 0, double.PositiveInfinity);
|
||||
}
|
||||
syncState.StartPlayTime = startPlayTime;
|
||||
dspTime = AudioSettings.dspTime;
|
||||
}
|
||||
|
||||
this.m_currentContainer = container;
|
||||
this.m_beatClock.Restart(container, bpm, dspTime);
|
||||
this.m_beatClock.Restart(container, bpm, dspTime, newStartOffset);
|
||||
}));
|
||||
yield return this.m_currentFadeInCoroutine;
|
||||
this.m_currentFadeInCoroutine = null;
|
||||
@@ -227,5 +256,26 @@ namespace OCES.Audio
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
double GetEffectiveStartOffset(MusicContainer container)
|
||||
{
|
||||
if (container.ContainerType == ContainerType.Blend)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (container.Segments is not { Count: > 0 })
|
||||
return 0.0;
|
||||
uint firstId = container.Segments[0];
|
||||
if (firstId < 1000000u)
|
||||
{
|
||||
MusicSegment segment = this.m_segmentConfig.QueryById(firstId);
|
||||
return segment?.StartOffset ?? 0.0;
|
||||
}
|
||||
|
||||
MusicContainer child = this.m_containerConfig.QueryById(firstId);
|
||||
return child != null ? GetEffectiveStartOffset(child) : 0.0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace OCES.Audio
|
||||
{
|
||||
public partial class MusicSegmentConfig
|
||||
{
|
||||
// TODO: 运行前边界验证
|
||||
// - MusicSegment.StartOffset <= AudioClip.length
|
||||
// - MusicSegment.EndOffset <= AudioClip.length
|
||||
// - MusicSegment.StartOffset + MusicSegment.EndOffset <= AudioClip.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c388a0ba2794e1ca09ccdde5bd5f99a
|
||||
timeCreated: 1778049644
|
||||
@@ -40,7 +40,7 @@ namespace OCES.Audio
|
||||
|
||||
this.m_stateRouter = new MusicStateRouter(musicPaths, ambiencePaths);
|
||||
this.m_musicChannel = new MusicChannelPlayer(
|
||||
containers, musicTransitions, longAudioContainerPlayer, this,
|
||||
containers, segments, musicTransitions, longAudioContainerPlayer, this,
|
||||
id => OnBeat?.Invoke(id),
|
||||
id => OnBar?.Invoke(id),
|
||||
id => OnGrid?.Invoke(id));
|
||||
|
||||
Reference in New Issue
Block a user