WIP: Music callback
- 头几拍会抖动一下,导致对不上拍子
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24597b5de367d47a6a05f83442f5150c
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 7
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db8d11d4ce8214c5c944ba9c66732b94
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 7
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 762bc1c75d1ed4a02bbf0aba8d774c94
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 7
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -66,6 +66,9 @@ public static class AudioObjectDefinitions
|
||||
{ "Chinese Number 08", 49 },
|
||||
{ "Chinese Number 09", 49 },
|
||||
{ "Chinese Number 10", 49 },
|
||||
{ "Bar", 52 },
|
||||
{ "Beat", 53 },
|
||||
{ "Grid", 54 },
|
||||
{ "sfx_amb_desert", 2000 },
|
||||
{ "sfx_amb_forest", 2001 },
|
||||
{ "sfx_anim_common_item_fly", 3000 },
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace OCES.Audio
|
||||
// 公开接口
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
public event Action<uint> OnBeat;
|
||||
|
||||
public event Action<uint> OnBar;
|
||||
public event Action<uint> OnGrid;
|
||||
|
||||
public void Play(uint audioId, Action onPlay = null)
|
||||
{
|
||||
AudioObject obj = this.m_audioObjects.QueryById(audioId);
|
||||
@@ -170,6 +175,10 @@ namespace OCES.Audio
|
||||
musicPool,
|
||||
ambiencePool);
|
||||
|
||||
this.m_musicSystem.OnBeat += id => this.OnBeat?.Invoke(id);
|
||||
this.m_musicSystem.OnBar += id => this.OnBar?.Invoke(id);
|
||||
this.m_musicSystem.OnGrid += id => this.OnGrid?.Invoke(id);
|
||||
|
||||
// ── 注册 StateGroup ──
|
||||
EnumIds.RegisterAllGameState();
|
||||
|
||||
|
||||
@@ -84,4 +84,19 @@ namespace OCES.Audio
|
||||
|
||||
public partial class MusicPath : IPathEntry { }
|
||||
public partial class AmbiencePath : IPathEntry { }
|
||||
|
||||
public partial class MusicContainerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析拍号字符串(如 "4/4", "3/4"),返回每小节拍数。
|
||||
/// </summary>
|
||||
public static int GetBeatsPerBar(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace OCES.Audio
|
||||
/// 环境音通道播放器。
|
||||
/// 与 MusicChannelPlayer 逻辑相同,但使用 AmbienceTransition 表,不涉及节拍对齐。
|
||||
/// </summary>
|
||||
public class AmbienceChannelPlayer
|
||||
class AmbienceChannelPlayer
|
||||
{
|
||||
readonly AmbienceTransitionConfig m_transitionConfig;
|
||||
readonly MonoBehaviour m_coroutineHost;
|
||||
@@ -20,7 +20,7 @@ namespace OCES.Audio
|
||||
|
||||
uint m_currentContainerId;
|
||||
|
||||
public AmbienceChannelPlayer(
|
||||
internal AmbienceChannelPlayer(
|
||||
AmbienceTransitionConfig transitionConfig,
|
||||
MusicContainerPlayer player,
|
||||
MonoBehaviour coroutineHost)
|
||||
@@ -34,7 +34,7 @@ namespace OCES.Audio
|
||||
// 公开接口
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
public void SwitchTo(uint newContainerId, uint fromPathId, uint toPathId)
|
||||
internal void SwitchTo(uint newContainerId, uint fromPathId, uint toPathId)
|
||||
{
|
||||
if (newContainerId == this.m_currentContainerId && this.m_currentHandle != null)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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;
|
||||
if (index == 0)
|
||||
{
|
||||
Debug.Log($"[BeatClock] BeatCoroutine waiting, nextTime={nextTime}, now={AudioSettings.dspTime}");
|
||||
}
|
||||
yield return new WaitUntil(() => AudioSettings.dspTime >= nextTime - 0.02);
|
||||
|
||||
while (AudioSettings.dspTime < nextTime)
|
||||
yield return null;
|
||||
|
||||
if (this.m_blendError || this.m_stopped){
|
||||
Debug.Log($"[BeatClock] Coroutine exiting early, blendError={m_blendError}, stopped={m_stopped}");
|
||||
yield break;
|
||||
}
|
||||
this.m_onBeat?.Invoke(this.m_containerId);
|
||||
Debug.Log($"[Beat] index={index}, nextTime={nextTime:F4}, actualDspTime={AudioSettings.dspTime:F4}, diff={(AudioSettings.dspTime - nextTime)*1000:F1}ms");
|
||||
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){
|
||||
Debug.Log($"[BeatClock] Coroutine exiting early, blendError={m_blendError}, stopped={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){
|
||||
Debug.Log($"[BeatClock] Coroutine exiting early, blendError={m_blendError}, stopped={m_stopped}");
|
||||
yield break;
|
||||
}
|
||||
this.m_onGrid?.Invoke(this.m_containerId);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cb2b4c3391444bca4cd30f3015d0f91
|
||||
timeCreated: 1775200354
|
||||
@@ -14,11 +14,11 @@ namespace OCES.Audio
|
||||
readonly MusicContainerPlayer m_player;
|
||||
readonly MonoBehaviour m_coroutineHost;
|
||||
|
||||
public ContainerPlayHandle CurrentHandle { get; private set; }
|
||||
internal ContainerPlayHandle CurrentHandle { get; private set; }
|
||||
public uint CurrentContainerId { get; private set; }
|
||||
public float CurrentVolume { get; private set; }
|
||||
|
||||
public ChannelFader(MusicContainerPlayer player, MonoBehaviour coroutineHost)
|
||||
internal ChannelFader(MusicContainerPlayer player, MonoBehaviour coroutineHost)
|
||||
{
|
||||
this.m_player = player;
|
||||
this.m_coroutineHost = coroutineHost;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -7,12 +8,13 @@ namespace OCES.Audio
|
||||
/// 音乐通道播放器。
|
||||
/// 负责:切换目标 Container、等待节拍对齐、执行淡入淡出 Transition。
|
||||
/// </summary>
|
||||
public class MusicChannelPlayer
|
||||
class MusicChannelPlayer
|
||||
{
|
||||
readonly MusicContainerConfig m_containerConfig;
|
||||
readonly MusicTransitionConfig m_transitionConfig;
|
||||
readonly MonoBehaviour m_coroutineHost;
|
||||
readonly ChannelFader m_fader;
|
||||
readonly BeatClock m_beatClock;
|
||||
|
||||
Coroutine m_currentFadeInCoroutine;
|
||||
Coroutine m_currentFadeOutCoroutine;
|
||||
@@ -30,16 +32,23 @@ namespace OCES.Audio
|
||||
// 正在进行的 transition 协程(防止重叠)
|
||||
Coroutine m_transitionCoroutine;
|
||||
|
||||
public MusicChannelPlayer(
|
||||
internal MusicChannelPlayer(
|
||||
MusicContainerConfig containerConfig,
|
||||
MusicTransitionConfig transitionConfig,
|
||||
MusicContainerPlayer player,
|
||||
MonoBehaviour coroutineHost)
|
||||
MonoBehaviour coroutineHost,
|
||||
Action<uint> onBeat,
|
||||
Action<uint> onBar,
|
||||
Action<uint> onGrid)
|
||||
{
|
||||
this.m_containerConfig = containerConfig;
|
||||
this.m_transitionConfig = transitionConfig;
|
||||
this.m_coroutineHost = coroutineHost;
|
||||
this.m_fader = new ChannelFader(player, coroutineHost);
|
||||
this.m_containerConfig = containerConfig;
|
||||
this.m_transitionConfig = transitionConfig;
|
||||
this.m_coroutineHost = coroutineHost;
|
||||
this.m_fader = new ChannelFader(player, coroutineHost);
|
||||
this.m_beatClock = new BeatClock(coroutineHost, onBeat, onBar, onGrid);
|
||||
|
||||
player.OnContainerEntered += this.m_beatClock.Restart;
|
||||
player.OnBlendError += this.m_beatClock.OnBlendError;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
@@ -67,8 +76,10 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 立即停止当前播放(无淡出)
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
internal void Stop()
|
||||
{
|
||||
this.m_beatClock.StopAll();
|
||||
|
||||
if (this.m_transitionCoroutine != null)
|
||||
this.m_coroutineHost.StopCoroutine(this.m_transitionCoroutine);
|
||||
|
||||
@@ -146,7 +157,7 @@ namespace OCES.Audio
|
||||
}
|
||||
else if (mode == AlignMode.Bar)
|
||||
{
|
||||
int beatsPerBar = ParseBeatsPerBar(container.TimeSig);
|
||||
int beatsPerBar = MusicContainerConfig.GetBeatsPerBar(container.TimeSig);
|
||||
double secondsPerBar = secondsPerBeat * beatsPerBar;
|
||||
double barsElapsed = elapsed / secondsPerBar;
|
||||
double nextBar = System.Math.Ceiling(barsElapsed);
|
||||
@@ -155,18 +166,7 @@ namespace OCES.Audio
|
||||
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;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 工具
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,17 +7,21 @@ namespace OCES.Audio
|
||||
/// 音乐与环境音系统。由 AudioSystem 持有并初始化。
|
||||
/// 对外只暴露 OnStateChanged,由 AudioSystem.SetState 转发调用。
|
||||
/// </summary>
|
||||
public class MusicSystem : MonoBehaviour
|
||||
class MusicSystem : MonoBehaviour
|
||||
{
|
||||
MusicStateRouter m_stateRouter;
|
||||
MusicChannelPlayer m_musicChannel;
|
||||
AmbienceChannelPlayer m_ambienceChannel;
|
||||
|
||||
internal event Action<uint> OnBeat;
|
||||
internal event Action<uint> OnBar;
|
||||
internal event Action<uint> OnGrid;
|
||||
|
||||
// 记录上一次两个通道各自匹配到的 PathId,用于查 Transition 表
|
||||
uint m_lastMusicPathId;
|
||||
uint m_lastAmbiencePathId;
|
||||
|
||||
public void Initialize(
|
||||
internal void Initialize(
|
||||
MusicSegmentConfig segments,
|
||||
MusicContainerConfig containers,
|
||||
MusicPathConfig musicPaths,
|
||||
@@ -31,14 +35,20 @@ namespace OCES.Audio
|
||||
MusicContainerPlayer ambientContainerPlayer = new(containers, segments, ambiencePool, this);
|
||||
|
||||
this.m_stateRouter = new MusicStateRouter(musicPaths, ambiencePaths);
|
||||
this.m_musicChannel = new MusicChannelPlayer(containers, musicTransitions, musicContainerPlayer, this);
|
||||
this.m_musicChannel = new MusicChannelPlayer(
|
||||
containers, musicTransitions, musicContainerPlayer, this,
|
||||
id => OnBeat?.Invoke(id),
|
||||
id => OnBar?.Invoke(id),
|
||||
id => OnGrid?.Invoke(id));
|
||||
this.m_ambienceChannel = new AmbienceChannelPlayer(ambienceTransitions, ambientContainerPlayer, this);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由 AudioSystem.SetState 调用,更新状态并驱动两个通道切换。
|
||||
/// </summary>
|
||||
public void OnStateChanged<TEnum>(TEnum state) where TEnum : Enum
|
||||
internal void OnStateChanged<TEnum>(TEnum state) where TEnum : Enum
|
||||
{
|
||||
this.m_stateRouter.SetState(
|
||||
state,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using OCES.Audio;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OCES
|
||||
{
|
||||
public class CallBackTest : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
AudioSystem.Instance.OnBeat += u =>
|
||||
{
|
||||
AudioSystem.Instance.Play(52);
|
||||
Debug.Log($"Container {u} is OnBeat");
|
||||
};
|
||||
AudioSystem.Instance.OnBar += u =>
|
||||
{
|
||||
AudioSystem.Instance.Play(53);
|
||||
Debug.Log($"Container {u} is OnBar");
|
||||
};
|
||||
AudioSystem.Instance.OnGrid += u =>
|
||||
{
|
||||
AudioSystem.Instance.Play(54);
|
||||
Debug.Log($"Container {u} is OnGrid");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3d8ad76f12545caa287f26889c674e3
|
||||
timeCreated: 1775210256
|
||||
Reference in New Issue
Block a user