WIP: Music callback
- 头几拍会抖动一下,导致对不上拍子
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace OCES.Audio
|
||||
{
|
||||
@@ -10,7 +12,7 @@ namespace OCES.Audio
|
||||
/// 被 MusicChannelPlayer 和 AmbienceChannelPlayer 共同使用。
|
||||
/// 不持有状态机逻辑,只负责"把这个 Container 播完"。
|
||||
/// </summary>
|
||||
public class MusicContainerPlayer
|
||||
class MusicContainerPlayer
|
||||
{
|
||||
readonly MusicContainerConfig m_containerConfig;
|
||||
readonly MusicSegmentConfig m_segmentConfig;
|
||||
@@ -18,6 +20,12 @@ namespace OCES.Audio
|
||||
readonly MonoBehaviour m_coroutineHost;
|
||||
UnityEngine.Audio.AudioMixerGroup m_mixerGroup;
|
||||
|
||||
/// <summary>
|
||||
/// 开始播放Container时触发 音乐回调系统
|
||||
/// container本身,继承来的bpm,进入时刻的dspTime
|
||||
/// </summary>
|
||||
internal event Action<MusicContainer, float, double> OnContainerEntered;
|
||||
internal event Action<MusicContainer> OnBlendError;
|
||||
|
||||
// Sequence Step 模式的全局游标,key = containerId
|
||||
readonly Dictionary<uint, int> m_sequenceStepIndex = new();
|
||||
@@ -45,7 +53,7 @@ namespace OCES.Audio
|
||||
/// 开始播放指定 Container,播放完毕后调用 onFinished。
|
||||
/// 返回可用于外部停止的句柄。
|
||||
/// </summary>
|
||||
public ContainerPlayHandle Play(uint containerId, System.Action onFinished = null)
|
||||
internal ContainerPlayHandle Play(uint containerId, Action onFinished = null, float inheritedBpm = 0f)
|
||||
{
|
||||
MusicContainer container = this.m_containerConfig.QueryById(containerId);
|
||||
if (container == null)
|
||||
@@ -58,7 +66,7 @@ namespace OCES.Audio
|
||||
ContainerPlayHandle handle = new();
|
||||
handle.TargetVolume = 1f;
|
||||
handle.Coroutine = this.m_coroutineHost.StartCoroutine(
|
||||
PlayContainerCoroutine(container, handle, onFinished));
|
||||
PlayContainerCoroutine(container, handle, inheritedBpm, onFinished));
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -93,14 +101,19 @@ namespace OCES.Audio
|
||||
IEnumerator PlayContainerCoroutine(
|
||||
MusicContainer container,
|
||||
ContainerPlayHandle handle,
|
||||
System.Action onFinished)
|
||||
float inheritedBpm,
|
||||
Action onFinished)
|
||||
{
|
||||
float effectiveBpm = container.Bpm > 0f ? container.Bpm : inheritedBpm;
|
||||
Debug.Log($"[MusicContainerPlayer] OnContainerEntered firing, container={container.Id}, subscribers={OnContainerEntered != null}");
|
||||
OnContainerEntered?.Invoke(container, effectiveBpm, AudioSettings.dspTime);
|
||||
|
||||
int loopsCompleted = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return this.m_coroutineHost.StartCoroutine(
|
||||
PlayContainerOnce(container, handle.TargetVolume, handle));
|
||||
PlayContainerOnce(container, handle.TargetVolume, handle, effectiveBpm));
|
||||
|
||||
if (handle.Cancelled) yield break;
|
||||
|
||||
@@ -126,7 +139,7 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 播放一个 Container 一轮(不含循环逻辑)
|
||||
/// </summary>
|
||||
IEnumerator PlayContainerOnce(MusicContainer container, float volumeScale, ContainerPlayHandle handle)
|
||||
IEnumerator PlayContainerOnce(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
{
|
||||
if (container.Segments == null || container.Segments.Count == 0)
|
||||
yield break;
|
||||
@@ -134,15 +147,15 @@ namespace OCES.Audio
|
||||
switch (container.ContainerType)
|
||||
{
|
||||
case ContainerType.Blend:
|
||||
yield return PlayBlend(container, volumeScale, handle);
|
||||
yield return PlayBlend(container, volumeScale, handle, inheritedBpm);
|
||||
break;
|
||||
|
||||
case ContainerType.Sequence:
|
||||
yield return PlaySequence(container, volumeScale, handle);
|
||||
yield return PlaySequence(container, volumeScale, handle, inheritedBpm);
|
||||
break;
|
||||
|
||||
case ContainerType.Random:
|
||||
yield return PlayRandom(container, volumeScale, handle);
|
||||
yield return PlayRandom(container, volumeScale, handle, inheritedBpm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -151,10 +164,21 @@ namespace OCES.Audio
|
||||
// Blend(同时播放)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
IEnumerator PlayBlend(MusicContainer container, float volumeScale, ContainerPlayHandle handle)
|
||||
IEnumerator PlayBlend(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
{
|
||||
IEnumerable<float> tempos = container.Segments.Select(id =>
|
||||
{
|
||||
MusicContainer c = this.m_containerConfig.QueryById(id);
|
||||
return c?.Bpm ?? 0f;
|
||||
}).Where(b => b > 0f).Distinct();
|
||||
|
||||
if (tempos.Count() > 1)
|
||||
{
|
||||
OnBlendError?.Invoke(container);
|
||||
}
|
||||
|
||||
// 同时启动所有子元素,等待全部结束
|
||||
var childHandles = new List<ContainerPlayHandle>();
|
||||
List<ContainerPlayHandle> childHandles = new();
|
||||
bool allDone = false;
|
||||
int remaining = container.Segments.Count;
|
||||
|
||||
@@ -166,7 +190,7 @@ namespace OCES.Audio
|
||||
{
|
||||
remaining--;
|
||||
if (remaining <= 0) allDone = true;
|
||||
});
|
||||
}, inheritedBpm);
|
||||
if (childHandle != null)
|
||||
{
|
||||
childHandles.Add(childHandle);
|
||||
@@ -187,7 +211,7 @@ namespace OCES.Audio
|
||||
// Sequence
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
IEnumerator PlaySequence(MusicContainer container, float volumeScale, ContainerPlayHandle handle)
|
||||
IEnumerator PlaySequence(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
{
|
||||
bool isStep = container.ContainerPlayMode;
|
||||
|
||||
@@ -195,7 +219,7 @@ namespace OCES.Audio
|
||||
{
|
||||
// Step: 每次只播一个,游标全局推进
|
||||
int index = GetNextSequenceIndex(container);
|
||||
yield return PlayChildAndWait(container.Segments[index], volumeScale, handle);
|
||||
yield return PlayChildAndWait(container.Segments[index], volumeScale, handle, inheritedBpm);
|
||||
|
||||
}
|
||||
else
|
||||
@@ -209,7 +233,7 @@ namespace OCES.Audio
|
||||
if (i > 0 && container.StrategyParam > 0)
|
||||
yield return new WaitForSeconds(container.StrategyParam);
|
||||
|
||||
yield return PlayChildAndWait(container.Segments[i], volumeScale, handle);
|
||||
yield return PlayChildAndWait(container.Segments[i], volumeScale, handle, inheritedBpm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +242,7 @@ namespace OCES.Audio
|
||||
// Random
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
IEnumerator PlayRandom(MusicContainer container, float volumeScale, ContainerPlayHandle handle)
|
||||
IEnumerator PlayRandom(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
|
||||
{
|
||||
bool isStep = container.ContainerPlayMode; // 同上,音乐系统默认 Continuous
|
||||
|
||||
@@ -226,7 +250,7 @@ namespace OCES.Audio
|
||||
{
|
||||
// Step Random: 随机选一个播放,算一次 loopCount
|
||||
uint chosen = PickRandomChild(container);
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle);
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -244,7 +268,7 @@ namespace OCES.Audio
|
||||
if (container.StrategyParam > 0 && remaining.Count < container.Segments.Count - 1)
|
||||
yield return new WaitForSeconds(container.StrategyParam);
|
||||
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle);
|
||||
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,10 +280,10 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 播放一个子元素(segment 或 container),等待其完成后返回。
|
||||
/// </summary>
|
||||
IEnumerator PlayChildAndWait(uint id, float volumeScale, ContainerPlayHandle parentHandle)
|
||||
IEnumerator PlayChildAndWait(uint id, float volumeScale, ContainerPlayHandle parentHandle, float inheritedBpm)
|
||||
{
|
||||
bool done = false;
|
||||
ContainerPlayHandle child = PlayChild(id, volumeScale, () => done = true);
|
||||
ContainerPlayHandle child = PlayChild(id, volumeScale, () => done = true, inheritedBpm);
|
||||
if (child != null)
|
||||
parentHandle.ChildHandles.Add(child);
|
||||
|
||||
@@ -272,17 +296,17 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 启动一个子元素的播放,不等待,返回句柄。
|
||||
/// </summary>
|
||||
ContainerPlayHandle PlayChild(uint id, float volumeScale, System.Action onDone)
|
||||
ContainerPlayHandle PlayChild(uint id, float volumeScale, Action onDone, float inheritedBpm)
|
||||
{
|
||||
// ID < 1000000 是 MusicSegment,否则是嵌套 Container
|
||||
return id < 1000000u ? PlaySegment(id, volumeScale, onDone) : Play(id, onDone);
|
||||
return id < 1000000u ? PlaySegment(id, volumeScale, onDone) : Play(id, onDone, inheritedBpm: inheritedBpm);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Segment 播放
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
ContainerPlayHandle PlaySegment(uint segmentId, float volumeScale, System.Action onFinished)
|
||||
ContainerPlayHandle PlaySegment(uint segmentId, float volumeScale, Action onFinished)
|
||||
{
|
||||
MusicSegment segment = this.m_segmentConfig.QueryById(segmentId);
|
||||
if (segment == null)
|
||||
@@ -313,7 +337,7 @@ namespace OCES.Audio
|
||||
return handle;
|
||||
}
|
||||
|
||||
IEnumerator WaitSegmentFinish(AudioSource source, ContainerPlayHandle handle, System.Action onFinished)
|
||||
IEnumerator WaitSegmentFinish(AudioSource source, ContainerPlayHandle handle, Action onFinished)
|
||||
{
|
||||
yield return new WaitWhile(() => source.isPlaying && !handle.Cancelled);
|
||||
|
||||
@@ -370,18 +394,18 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 一次 Container 播放的句柄,用于外部停止或淡出时访问正在播放的 AudioSource。
|
||||
/// </summary>
|
||||
public class ContainerPlayHandle
|
||||
class ContainerPlayHandle
|
||||
{
|
||||
public Coroutine Coroutine;
|
||||
public bool Cancelled;
|
||||
public float TargetVolume = 1f;
|
||||
public List<AudioSource> ActiveSources = new();
|
||||
public List<ContainerPlayHandle> ChildHandles = new();
|
||||
internal Coroutine Coroutine;
|
||||
internal bool Cancelled;
|
||||
internal float TargetVolume = 1f;
|
||||
internal List<AudioSource> ActiveSources = new();
|
||||
internal List<ContainerPlayHandle> ChildHandles = new();
|
||||
|
||||
/// <summary>
|
||||
/// 递归收集所有正在发声的 AudioSource(用于淡出)
|
||||
/// </summary>
|
||||
public void CollectActiveSources(List<AudioSource> result)
|
||||
internal void CollectActiveSources(List<AudioSource> result)
|
||||
{
|
||||
result.AddRange(this.ActiveSources);
|
||||
foreach (ContainerPlayHandle child in this.ChildHandles)
|
||||
|
||||
Reference in New Issue
Block a user