Files
AudioSystem/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/MusicChannelPlayer.cs
T
Oliver b6393449c9 解决重复切换State的时候会导致重复播放对应Segment的问题。
修复FadeIn读取了FadeOut参数的问题。
增加Initial Delay功能。
重构AudioScheduler.ConfigureSource() -> SetupSource(), RegisterActiveSound(), StartPlayBack()。
移动长音频相关功能至LongAudio文件夹。
2026-03-25 17:01:38 +08:00

202 lines
8.7 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.Collections;
using UnityEngine;
namespace OCES.Audio
{
/// <summary>
/// 音乐通道播放器。
/// 负责:切换目标 Container、等待节拍对齐、执行淡入淡出 Transition。
/// </summary>
public class MusicChannelPlayer
{
readonly MusicContainerConfig m_containerConfig;
readonly MusicTransitionConfig m_transitionConfig;
readonly MonoBehaviour m_coroutineHost;
readonly ChannelFader m_fader;
Coroutine m_currentFadeInCoroutine;
Coroutine m_currentFadeOutCoroutine;
// 当前正在播放的句柄
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);
}
// ─────────────────────────────────────────────
// 公开接口
// ─────────────────────────────────────────────
/// <summary>
/// 切换到新的 Container。
/// fromPathId / toPathId 用于查询 Transition 配置。
/// </summary>
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));
}
/// <summary>
/// 立即停止当前播放(无淡出)
/// </summary>
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)
{
if (this.m_currentFadeInCoroutine != null)
{
this.m_coroutineHost.StopCoroutine(this.m_currentFadeInCoroutine);
this.m_currentFadeInCoroutine = null;
}
if (this.m_currentFadeOutCoroutine != null)
{
this.m_coroutineHost.StopCoroutine(this.m_currentFadeOutCoroutine);
this.m_currentFadeOutCoroutine = null;
}
// ── 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,互不等待 ──
if (newContainerId == this.m_fader.CurrentContainerId && this.m_fader.CurrentHandle != null) yield break;
// 如果等待期间被切回来了,就不淡变了
ContainerPlayHandle outgoing = this.m_fader.CurrentHandle;
float outVol = this.m_fader.CurrentVolume;
this.m_currentFadeOutCoroutine = this.m_coroutineHost.StartCoroutine(
this.m_fader.FadeOutBranch(outgoing, outVol, transition));
this.m_currentFadeInCoroutine = 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;
}));
yield return this.m_currentFadeInCoroutine;
this.m_currentFadeInCoroutine = null;
}
// ─────────────────────────────────────────────
// 节拍对齐等待
// ─────────────────────────────────────────────
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);
}
}
/// <summary>
/// 解析拍号字符串(如 "4/4", "3/4"),返回每小节拍数。
/// </summary>
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;
}
// ─────────────────────────────────────────────
// 工具
// ─────────────────────────────────────────────
/// <summary>
/// 查询 Transition 配置,支持精确匹配和 999 通配符。
/// ID 规则:FromPathId × 1000 + ToPathId999 表示任意。
/// </summary>
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);
}
}
}