Files
AudioSystem/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/BeatClock.cs
T

172 lines
6.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
internal void Restart(MusicContainer container, float inheritedBpm, double dspTime)
{
//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;
// BPM:优先用自己的,为 0 则用传入的继承值
float bpm = container.Bpm > 0f ? container.Bpm : inheritedBpm;
if (bpm <= 30f)
{
// BPM 无效,不启动任何回调
return;
}
this.m_secondsPerBeat = 60 / bpm;
// TimeSig:检查是否有效
bool hasTimeSig = !string.IsNullOrEmpty(container.TimeSig);
if (hasTimeSig)
{
this.m_beatsPerBar = MusicContainerConfig.GetBeatsPerBar(container.TimeSig);
}
// Grid:检查是否有效
bool hasGrid = container.Grid > 0;
if (hasGrid)
{
this.m_barsPerGrid = container.Grid;
}
// 分层启动协程
// Beat:只要 BPM 有效就启动
this.m_beatCoroutine = this.m_host.StartCoroutine(BeatCoroutine());
// BarTimeSig 有效时启动
if (hasTimeSig)
{
this.m_barCoroutine = this.m_host.StartCoroutine(BarCoroutine());
}
// GridTimeSig 和 Grid 都有效时启动
if (hasTimeSig && hasGrid)
{
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;
}
internal double GetNextDspTime(AlignMode mode)
{
double now = AudioSettings.dspTime;
if (this.m_blendError || this.m_stopped)
{
return now;
}
double elapsed = now - this.m_startDspTime;
double period = mode switch
{
AlignMode.Beat => this.m_secondsPerBeat,
AlignMode.Bar => this.m_secondsPerBeat * this.m_beatsPerBar,
_ => this.m_secondsPerBeat,
};
long next = (long)(elapsed / period) + 1L;
return this.m_startDspTime + next * period;
}
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++;
}
}
}
}