Files
AudioSystem/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/BeatClock.cs
T
Oliver 2b34d0bf94 feat: SyncPoint.SameAsCurrentSegment music transition
- Add SameAsCurrentSegment mode to align new container's timeSamples with the old container's playback position, accounting for FadeInOffset
- Fix BeatClock callback burst when Restart is called with a past dspTime
- Add GetFirstLeafSource() for resolving playback position across nested containers
- Manual BeatClock.Restart replaces OnContainerEntered subscription for accurate timing with SyncPoint
2026-04-16 20:50:34 +08:00

127 lines
5.1 KiB
C#

using System;
using System.Collections;
using UnityEngine;
namespace OCES.Audio
{
public class BeatClock
{
int m_beatsPerBar; // 从 TimeSig 解析
int m_barsPerGrid; // 从 Grid 字段读取
double m_startDspTime, m_secondsPerBeat; // 本次计数周期的起点
Coroutine m_beatCoroutine, m_barCoroutine, m_gridCoroutine;
readonly Action<uint> m_onBeat, m_onBar, m_onGrid;
readonly MonoBehaviour m_host;
uint m_containerId;
bool m_stopped, m_blendError; // 检测到 Blend 多 BPM 情况时置为 true
internal BeatClock(MonoBehaviour host, Action<uint> onBeat, Action<uint> onBar, Action<uint> onGrid)
{
this.m_host = host;
this.m_onBeat = onBeat;
this.m_onBar = onBar;
this.m_onGrid = onGrid;
}
public void Restart(MusicContainer container, float inheritedBpm, double dspTime)
{
//Debug.Log($"[BeatClock] Restart called, container={container.Id}, bpm={container.Bpm}, inherited={inheritedBpm}");
StopAll();
this.m_blendError = this.m_stopped = false;
this.m_containerId = container.Id;
this.m_startDspTime = dspTime;
// BPM:优先用自己的,为 0 则用传入的继承值,最终回退 120
float bpm = container.Bpm > 0f ? container.Bpm : inheritedBpm;
if (bpm <= 30f) bpm = 120f; // 没见过速度小于30bpm的音乐,小于30要么是数填错了,要么是有奇怪的情况。要是真有这么慢的音乐再处理吧。
this.m_secondsPerBeat = 60 / bpm;
this.m_beatsPerBar = MusicContainerConfig.GetBeatsPerBar(container.TimeSig); // 沿用现有的解析逻辑
this.m_barsPerGrid = container.Grid > 0 ? container.Grid : 4;
this.m_beatCoroutine = this.m_host.StartCoroutine(BeatCoroutine());
this.m_barCoroutine = this.m_host.StartCoroutine(BarCoroutine());
this.m_gridCoroutine = this.m_host.StartCoroutine(GridCoroutine());
}
internal void OnBlendError(MusicContainer container)
{
this.m_blendError = true;
Debug.LogWarning($"[Music System] Blend Container {container.Id} 包含多个不同 BPM 的子 Container,节拍回调已停止。");
}
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;
}
IEnumerator BeatCoroutine()
{
double elapsed = AudioSettings.dspTime - this.m_startDspTime;
long index = elapsed > 0.05 ? (long)(elapsed / this.m_secondsPerBeat) : 0;
while (true)
{
double nextTime = this.m_startDspTime + index * this.m_secondsPerBeat;
yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);
while (AudioSettings.dspTime < nextTime)
yield return null;
if (this.m_blendError || this.m_stopped){
yield break;
}
this.m_onBeat?.Invoke(this.m_containerId);
index++;
}
}
IEnumerator BarCoroutine()
{
double elapsed = AudioSettings.dspTime - this.m_startDspTime;
double secondsPerBar = this.m_secondsPerBeat * this.m_beatsPerBar;
long index = elapsed > 0.05 ? (long)(elapsed / secondsPerBar) : 0;
while (true)
{
double nextTime = this.m_startDspTime + index * secondsPerBar;
yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);
while (AudioSettings.dspTime < nextTime)
yield return null;
if (this.m_blendError || this.m_stopped){
yield break;
}
this.m_onBar?.Invoke(this.m_containerId);
index++;
}
}
IEnumerator GridCoroutine()
{
double elapsed = AudioSettings.dspTime - this.m_startDspTime;
double secondsPerGrid = this.m_secondsPerBeat * this.m_beatsPerBar * this.m_barsPerGrid;
long index = elapsed > 0.05 ? (long)(elapsed / secondsPerGrid) : 0;
while (true)
{
double nextTime = this.m_startDspTime + index * secondsPerGrid;
yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);
while (AudioSettings.dspTime < nextTime)
yield return null;
if (this.m_blendError || this.m_stopped){
yield break;
}
this.m_onGrid?.Invoke(this.m_containerId);
index++;
}
}
}
}