WIP: StartOffset

This commit is contained in:
2026-05-07 12:09:16 +08:00
parent 0f30f98846
commit af4cd4da3f
51 changed files with 843 additions and 64 deletions
@@ -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;
}
}
}