using System.Collections; using UnityEngine; namespace OCES.Audio { /// /// 音乐通道播放器。 /// 负责:切换目标 Container、等待节拍对齐、执行淡入淡出 Transition。 /// public class MusicChannelPlayer { readonly MusicContainerConfig m_containerConfig; readonly MusicTransitionConfig m_transitionConfig; readonly MonoBehaviour m_coroutineHost; readonly ChannelFader m_fader; // 当前正在播放的句柄 ContainerPlayHandle m_currentHandle; uint m_currentContainerId; float m_currentVolume = 1f; // 当前播放的 Container(用于读取 bpm/timeSig 做节拍对齐) MusicContainer m_currentContainer; // 当前播放开始的时间(用于计算当前播到哪个拍子) double m_playStartTime; // 正在进行的 transition 协程(防止重叠) Coroutine m_transitionCoroutine; public MusicChannelPlayer( MusicContainerConfig containerConfig, MusicTransitionConfig transitionConfig, MusicContainerPlayer player, MonoBehaviour coroutineHost) { this.m_containerConfig = containerConfig; this.m_transitionConfig = transitionConfig; this.m_coroutineHost = coroutineHost; this.m_fader = new ChannelFader(player, coroutineHost); } // ───────────────────────────────────────────── // 公开接口 // ───────────────────────────────────────────── /// /// 切换到新的 Container。 /// fromPathId / toPathId 用于查询 Transition 配置。 /// internal void SwitchTo(uint newContainerId, uint fromPathId, uint toPathId) { if (newContainerId == this.m_currentContainerId && this.m_currentHandle != null) return; // 已经在播目标,无需切换 MusicTransition transition = ResolveTransition(fromPathId, toPathId); if (this.m_transitionCoroutine != null) this.m_coroutineHost.StopCoroutine(this.m_transitionCoroutine); this.m_transitionCoroutine = this.m_coroutineHost.StartCoroutine( DoTransition(newContainerId, transition)); } /// /// 立即停止当前播放(无淡出) /// public void Stop() { if (this.m_transitionCoroutine != null) this.m_coroutineHost.StopCoroutine(this.m_transitionCoroutine); this.m_fader.StopCurrent(); } // ───────────────────────────────────────────── // Transition 流程 // ───────────────────────────────────────────── IEnumerator DoTransition(uint newContainerId, MusicTransition transition) { // ── 1. 等待节拍对齐(由 Transition 的 AlignMode 决定)── if (transition != null && this.m_currentContainer != null) { yield return this.m_coroutineHost.StartCoroutine( WaitForAlignment(transition.AlignMode, this.m_currentContainer)); } // ── 2 & 3. 淡出与淡入并行:两条分支从同一时刻起算各自的 Offset,互不等待 ── ContainerPlayHandle outgoing = this.m_fader.CurrentHandle; float outVol = this.m_fader.CurrentVolume; this.m_coroutineHost.StartCoroutine( this.m_fader.FadeOutBranch(outgoing, outVol, transition)); yield return this.m_coroutineHost.StartCoroutine( this.m_fader.FadeInBranch(newContainerId, transition, onContainerStarted: () => { // Music 独有:记录新 container 和播放起始时间 this.m_currentContainer = this.m_containerConfig.QueryById(newContainerId); this.m_playStartTime = AudioSettings.dspTime; })); } // ───────────────────────────────────────────── // 节拍对齐等待 // ───────────────────────────────────────────── IEnumerator WaitForAlignment(AlignMode mode, MusicContainer container) { if (mode == AlignMode.Immediate || container.Bpm <= 0f) yield break; double now = AudioSettings.dspTime; double elapsed = now - this.m_playStartTime; double secondsPerBeat = 60.0 / container.Bpm; if (mode == AlignMode.Beat) { double beatsElapsed = elapsed / secondsPerBeat; double nextBeat = System.Math.Ceiling(beatsElapsed); double waitSeconds = (nextBeat - beatsElapsed) * secondsPerBeat; if (waitSeconds > 0.001) yield return new WaitForSeconds((float)waitSeconds); } else if (mode == AlignMode.Bar) { int beatsPerBar = ParseBeatsPerBar(container.TimeSig); double secondsPerBar = secondsPerBeat * beatsPerBar; double barsElapsed = elapsed / secondsPerBar; double nextBar = System.Math.Ceiling(barsElapsed); double waitSeconds = (nextBar - barsElapsed) * secondsPerBar; if (waitSeconds > 0.001) yield return new WaitForSeconds((float)waitSeconds); } } /// /// 解析拍号字符串(如 "4/4", "3/4"),返回每小节拍数。 /// static int ParseBeatsPerBar(string timeSig) { if (string.IsNullOrEmpty(timeSig)) return 4; string[] parts = timeSig.Split('/'); if (parts.Length >= 1 && int.TryParse(parts[0], out int beats)) return beats; return 4; } // ───────────────────────────────────────────── // 工具 // ───────────────────────────────────────────── /// /// 查询 Transition 配置,支持精确匹配和 999 通配符。 /// ID 规则:FromPathId × 1000 + ToPathId,999 表示任意。 /// MusicTransition ResolveTransition(uint fromPathId, uint toPathId) { // 优先精确匹配 uint exactId = fromPathId * 1000 + toPathId; MusicTransition exact = this.m_transitionConfig.QueryById(exactId); if (exact != null) return exact; // From 为任意 uint fromWildcard = 999u * 1000 + toPathId; MusicTransition fromWild = this.m_transitionConfig.QueryById(fromWildcard); if (fromWild != null) return fromWild; // To 为任意 uint toWildcard = fromPathId * 1000 + 999u; MusicTransition toWild = this.m_transitionConfig.QueryById(toWildcard); if (toWild != null) return toWild; // 全通配 const uint allWild = 999u * 1000 + 999u; return this.m_transitionConfig.QueryById(allWild); } } }