122 lines
4.8 KiB
C#
122 lines
4.8 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()
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
}
|