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 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 onBeat, Action onBar, Action 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() { long index = 0; // 从第 1 拍开始,第 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() { long index = 0; while (true) { double nextTime = this.m_startDspTime + index * this.m_secondsPerBeat * this.m_beatsPerBar; 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() { long index = 0; while (true) { double nextTime = this.m_startDspTime + index * this.m_secondsPerBeat * this.m_beatsPerBar * this.m_barsPerGrid; 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++; } } } }