您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

604 行
19 KiB

// #define SYNTHESIZER_PARANOIA
using System.Collections.Generic;
#if UNITY_EDITOR
using System.Reflection;
#endif
using UnityEngine;
using UnityEngine.Audio;
namespace AxelF {
public static class Synthesizer {
static float _masterVolume = 1f;
public static float masterVolume {
get { return _masterVolume; }
set { _masterVolume = Mathf.Clamp01(value); }
}
[System.Serializable]
public struct SourceInfo {
public Transform transform;
public AudioSource audioSource;
public AudioLowPassFilter lowPassFilter;
public AudioHighPassFilter highPassFilter;
public Occlusion occlusion;
public void Enable(Parameters p) {
audioSource.enabled = true;
if (p.occlusion.function != OcclusionFunction.None && p.spatial.blend > 0f) {
lowPassFilter.enabled = true;
highPassFilter.enabled = true;
occlusion.enabled = true;
}
occlusion.occlusion = p.occlusion;
occlusion.spatial = p.spatial;
}
public void Disable() {
audioSource.enabled = false;
lowPassFilter.enabled = false;
highPassFilter.enabled = false;
occlusion.enabled = false;
}
}
[System.Serializable]
public struct ActiveSource {
public uint handle;
public float keyOn;
public bool keyOff;
#if UNITY_EDITOR
public Patch patch;
#endif
public SourceInfo info;
public Transform target;
public Vector3 localPosition;
public float volume;
public float modVolume;
public Envelope envelope;
public float GetVolume() {
float v = _masterVolume;
v *= volume;
v *= modVolume;
v *= envelope.GetValue();
return v;
}
public bool Check(out bool playing) {
if (info.audioSource) {
playing = info.audioSource.isPlaying || (info.audioSource.isVirtual && keyOn > 0f);
return true;
}
playing = false;
return false;
}
public void UpdatePosition() {
if (target)
info.transform.position = target.position + localPosition;
}
public void UpdateEnvelope(float dt, ref bool playing) {
if (keyOff) {
if (envelope.UpdateRelease(dt) >= 1f)
playing = false;
} else if ((keyOn = Mathf.Max(0f, keyOn - dt)) <= 0f)
envelope.UpdateAttack(dt);
}
public void UpdateVolume() {
info.audioSource.volume = GetVolume();
}
}
public static Stack<SourceInfo> freeSources = new Stack<SourceInfo>(64);
public static List<ActiveSource> activeSources0 = new List<ActiveSource>(64);
public static List<ActiveSource> activeSources1 = new List<ActiveSource>(64);
public static uint activeHandle;
#if UNITY_EDITOR
public static int sourceIndex;
#endif
public static void Reset() {
StopAll();
freeSources.Clear();
activeSources0.Clear();
activeSources1.Clear();
}
static SourceInfo CreateSource() {
var o = new GameObject(
#if UNITY_EDITOR
string.Format("AxelF.Source #{0:X2}", sourceIndex++)
#endif
);
o.transform.parent = Heartbeat.hierarchyTransform;
var i = new SourceInfo() {
transform = o.transform,
audioSource = o.AddComponent<AudioSource>(),
lowPassFilter = o.AddComponent<AudioLowPassFilter>(),
highPassFilter = o.AddComponent<AudioHighPassFilter>(),
occlusion = o.AddComponent<Occlusion>()
};
i.audioSource.playOnAwake = false;
return i;
}
static bool ActivateStatic(
AudioMixerGroup g, AudioClip c, Parameters p, AudioSource s,
float delay, float volume, float pitch) {
s.clip = c;
s.volume = volume;
s.pitch = p.GetPitch() * pitch * Time.timeScale;
s.panStereo = p.panning;
s.loop = p.loop;
s.spatialBlend = p.spatial.blend;
s.minDistance = p.spatial.distance.min;
s.maxDistance = p.spatial.distance.max;
s.dopplerLevel = p.spatial.doppler;
s.priority = (int) p.runtime.priority;
s.outputAudioMixerGroup = g;
if (delay <= Mathf.Epsilon)
s.Play();
else
s.PlayDelayed(delay);
#if SYNTHESIZER_PARANOIA
Debug.LogFormat(
Time.frameCount.ToString("X4") +
" Synthesizer.ActivateStatic: {0} {1} ({2}) : {3:N2} {4:N2} {5:N2} -> {6}",
g, c.name, s.name, delay, volume, pitch, s.isPlaying);
#endif
return p.loop;
}
#if UNITY_EDITOR
static void PlayClip(AudioClip c, bool loop) {
var a = typeof(UnityEditor.EditorApplication).Assembly;
var t = a.GetType("UnityEditor.AudioUtil");
var m = t.GetMethod(
"PlayClip", new System.Type[] {
typeof(AudioClip), typeof(int), typeof(bool)});
m.Invoke(null, new object[] {c, 0, loop});
}
static void StopAllClips() {
var a = typeof(UnityEditor.EditorApplication).Assembly;
var t = a.GetType("UnityEditor.AudioUtil");
var m = t.GetMethod("StopAllClips", new System.Type[0]);
m.Invoke(null, new object[0]);
}
#endif
internal static bool Activate(
AudioMixerGroup g, AudioClip c, Parameters p, ActivationParams ap
#if UNITY_EDITOR
, Patch patch
#endif
) {
float r = p.randomization.distance.GetRandomValue();
if (!Mathf.Approximately(r, 0f)) {
float a = Randomizer.plusMinusOne * Mathf.PI;
r *= (p.spatial.distance.max - p.spatial.distance.min);
r += p.spatial.distance.min;
ap.position.x += Mathf.Sin(a) * r;
ap.position.z += Mathf.Cos(a) * r;
}
var pos2 = ap.position;
if (ap.transform != null)
pos2 += ap.transform.position;
bool looping = ActivateInternal(
g, c, p, ap, pos2, 1f
#if UNITY_EDITOR
, patch
#endif
);
if (!looping && p.occlusion.function != OcclusionFunction.None && p.slapback.patch != null) {
AudioSlapback s;
Vector3 pos3, d3;
if ((bool) (s = AudioSlapback.FindClosest(pos2, out pos3, out d3))) {
var patch2 = p.slapback.patch;
var p2 = patch2.program.parameters;
var lpos = Heartbeat.listenerTransform.position;
var d = pos3 - lpos;
var pos4 = pos3 + d.normalized * s.radius;
float dt = s.radius / OcclusionSettings.instance.speedOfSound;
p2.randomization.distance.min = 0f;
p2.randomization.distance.max = 0f;
p2.occlusion.function = OcclusionFunction.Slapback;
float gain;
var clip = patch2.program.GetClip(out gain);
if (!clip)
Debug.LogError("Activate: Null AudioClip from patch: " + patch2, patch2);
Activate(
patch2.program.mixerGroup, clip, p2,
new ActivationParams {
transform = null,
position = pos4,
delay = ap.delay + dt,
volume = ap.volume * (1f + gain),
modVolume = ap.modVolume,
handle = ap.handle
}
#if UNITY_EDITOR
, patch2
#endif
);
}
}
return looping;
}
internal static bool ActivateInternal(
AudioMixerGroup g, AudioClip c, Parameters p,
ActivationParams ap, Vector3 pos2, float pitch
#if UNITY_EDITOR
, Patch patch
#endif
) {
#if UNITY_EDITOR
if (!Application.isPlaying) {
UnityEditor.EditorApplication.CallbackFunction f = null;
float n = Time.realtimeSinceStartup;
f = () => {
if (Time.realtimeSinceStartup - n >= ap.delay) {
UnityEditor.EditorApplication.update -= f;
PlayClip(c, p.loop);
}
};
UnityEditor.EditorApplication.update += f;
return false;
}
#endif
var i = freeSources.Count > 0 ? freeSources.Pop() : CreateSource();
i.transform.position = pos2;
i.Enable(p);
var z = new ActiveSource {
handle = ap.handle,
keyOn = ap.delay,
keyOff = false,
#if UNITY_EDITOR
patch = patch,
#endif
info = i,
target = (ap.transform == null || ap.transform.gameObject.isStatic) ? null : ap.transform,
localPosition = ap.position,
volume = p.GetVolume() * Mathf.Clamp01(ap.volume),
modVolume = ap.modVolume,
envelope = Envelope.instant
};
#if SYNTHESIZER_PARANOIA
Debug.LogFormat(
Time.frameCount.ToString("X4") +
" Synthesizer.ActivateInternal: {0} {1} ({2})",
g, c.name, z.target);
#endif
if (p.envelope.attack > Mathf.Epsilon)
z.envelope.SetAttack(p.envelope.attack);
if (p.envelope.release > Mathf.Epsilon)
z.envelope.SetRelease(p.envelope.release);
ActivateStatic(g, c, p, i.audioSource, ap.delay, z.GetVolume(), pitch);
activeSources0.Add(z);
return p.loop;
}
internal static bool Activate(Patch patch, AudioSource src, float delay, float volume) {
var p = patch.program.parameters;
float gain;
var clip = patch.program.GetClip(out gain);
if (!clip)
Debug.LogError("Activate: Null AudioClip from patch: " + patch, patch);
bool looping = ActivateStatic(
patch.program.mixerGroup, clip, p, src, delay, volume * (1f + gain), 1f);
return looping;
}
internal static bool Activate(
AudioProgram a, ActivationParams ap
#if UNITY_EDITOR
, Patch patch
#endif
) {
var p = a.parameters;
float gain;
var clip = a.GetClip(out gain);
#if UNITY_EDITOR
if (!clip)
Debug.LogError("Activate: Null AudioClip from patch: " + patch, patch);
#endif
ap.volume *= 1f + gain;
bool looping = Activate(
a.mixerGroup, clip, p, ap
#if UNITY_EDITOR
, patch
#endif
);
return looping;
}
internal static bool Activate(
AudioProgram a, Parameters.EnvelopeParams ep, ActivationParams ap
#if UNITY_EDITOR
, Patch patch
#endif
) {
var p = a.parameters;
p.envelope = ep;
float gain;
var clip = a.GetClip(out gain);
#if UNITY_EDITOR
if (!clip)
Debug.LogError("Activate: Null AudioClip from patch: " + patch, patch);
#endif
ap.volume *= 1f + gain;
bool looping = Activate(
a.mixerGroup, clip, p, ap
#if UNITY_EDITOR
, patch
#endif
);
return looping;
}
internal static bool Activate(AudioSequence s, ActivationParams ap) {
bool looping = false;
for (int i = 0, n = s.timing.Length; i < n; ++i) {
var g = s.timing[i];
if (Randomizer.zeroToOne <= g.randomization.chance) {
var ap2 = ap;
ap2.delay += g.GetDelay();
if (g.patch != null)
looping |= g.patch.Activate(ap2);
else
looping |= Activate(s.patch.program, ap2
#if UNITY_EDITOR
, s.patch
#endif
);
}
}
return looping;
}
internal static uint GetNextHandle() {
activeHandle = (activeHandle << 16) | ((activeHandle & 0xffff) + 1);
return activeHandle;
}
public static bool IsKeyedOn(uint handle) {
if (handle == 0) {
Debug.LogErrorFormat("AxelF.Synthesizer IsKeyedOn: bad handle");
return false;
}
foreach (var i in activeSources0)
if (i.handle == handle)
return true;
return false;
}
public static uint KeyOn(
AudioMixerGroup g, AudioClip c, Parameters p, Transform t = null,
Vector3 pos = new Vector3(), float delay = 0f, float volume = 1f,
float modVolume = 1f) {
if (c == null) {
Debug.LogErrorFormat("AxelF.Synthesizer KeyOn: missing audio clip reference");
return 0;
}
uint handle = GetNextHandle();
Activate(g, c, p,
new ActivationParams {
transform = t,
position = pos,
delay = delay,
volume = volume,
modVolume = modVolume,
handle = handle
}
#if UNITY_EDITOR
, null
#endif
);
return handle;
}
public static void KeyOn(Patch patch, AudioSource src, float delay = 0f, float volume = 1f) {
if (patch == null) {
Debug.LogErrorFormat("AxelF.Synthesizer KeyOn: missing patch reference");
return;
}
Activate(patch, src, delay, volume);
}
public static uint KeyOn(
out bool looping, Patch patch, Transform t = null,
Vector3 pos = new Vector3(), float delay = 0f, float volume = 1f,
float modVolume = 1f) {
if (patch == null) {
Debug.LogErrorFormat("AxelF.Synthesizer KeyOn: missing patch reference");
looping = false;
return 0;
}
uint handle = GetNextHandle();
looping = patch.Activate(
new ActivationParams {
transform = t,
position = pos,
delay = delay,
volume = volume,
modVolume = modVolume,
handle = handle
});
return handle;
}
public static uint KeyOn(
out bool looping, Patch patch, Parameters.EnvelopeParams ep,
Transform t = null, Vector3 pos = new Vector3(), float delay = 0f, float volume = 1f,
float modVolume = 1f) {
if (patch == null) {
Debug.LogErrorFormat("AxelF.Synthesizer KeyOn: missing patch reference");
looping = false;
return 0;
}
uint handle = GetNextHandle();
looping = patch.Activate(
new ActivationParams {
transform = t,
position = pos,
delay = delay,
volume = volume,
modVolume = modVolume,
handle = handle
}, ep);
return handle;
}
public static void KeyOff(uint handle, float release = 0f, EnvelopeMode mode = EnvelopeMode.None) {
if (handle == 0) {
Debug.LogErrorFormat("AxelF.Synthesizer KeyOff: bad handle");
return;
}
for (var x = activeSources0.GetEnumerator(); x.MoveNext();) {
var z = x.Current;
if (z.handle == handle) {
#if SYNTHESIZER_PARANOIA
Debug.LogFormat(
Time.frameCount.ToString("X4") +
" Synthesizer.KeyOff: {0} ({1}) : {2} {3}",
z.info.audioSource.clip.name, z.info.audioSource.name,
release, mode);
#endif
switch (mode) {
case EnvelopeMode.Exact:
z.envelope.SetRelease(release);
break;
case EnvelopeMode.Min:
z.envelope.SetRelease(Mathf.Min(z.envelope.releaseTime, release));
break;
case EnvelopeMode.Max:
z.envelope.SetRelease(Mathf.Max(z.envelope.releaseTime, release));
break;
}
z.keyOff = true;
}
activeSources1.Add(z);
}
Swap(ref activeSources0, ref activeSources1);
activeSources1.Clear();
}
internal static void SetModVolume(uint handle, float volume) {
if (handle == 0) {
Debug.LogErrorFormat("AxelF.Synthesizer SetModVolume: bad handle");
return;
}
for (var x = activeSources0.GetEnumerator(); x.MoveNext();) {
var z = x.Current;
if (z.handle == handle)
z.modVolume = volume;
activeSources1.Add(z);
}
Swap(ref activeSources0, ref activeSources1);
activeSources1.Clear();
}
public static void Stop(uint handle) {
if (handle == 0) {
Debug.LogErrorFormat("AxelF.Synthesizer Stop: bad handle");
return;
}
for (var x = activeSources0.GetEnumerator(); x.MoveNext();) {
var z = x.Current;
if (z.handle == handle && z.info.audioSource) {
#if SYNTHESIZER_PARANOIA
Debug.LogFormat(
Time.frameCount.ToString("X4") +
" Synthesizer.Update: {0} ({1}) : stopped by envelope",
z.info.audioSource.clip.name, z.info.audioSource.name);
#endif
z.info.audioSource.Stop();
}
}
}
public static void StopAll() {
#if SYNTHESIZER_PARANOIA
Debug.LogFormat("Synthesizer.StopAll");
#endif
#if UNITY_EDITOR
if (!Application.isPlaying) {
StopAllClips();
return;
}
#endif
for (var x = activeSources0.GetEnumerator(); x.MoveNext();) {
var z = x.Current;
if (z.info.audioSource)
z.info.audioSource.Stop();
}
}
static void Swap<T>(ref List<T> a, ref List<T> b) {
var y = a;
a = b;
b = y;
}
internal static void Update(float dt) {
for (var x = activeSources0.GetEnumerator(); x.MoveNext();) {
var z = x.Current;
bool playing;
if (z.Check(out playing)) {
if (playing) {
z.UpdatePosition();
z.UpdateEnvelope(dt, ref playing);
if (playing) {
z.UpdateVolume();
activeSources1.Add(z);
} else {
#if SYNTHESIZER_PARANOIA
Debug.LogFormat(
Time.frameCount.ToString("X4") +
" Synthesizer.Update: {0} ({1}) : stopped by envelope",
z.info.audioSource.clip.name, z.info.audioSource.name);
#endif
z.info.audioSource.Stop();
}
}
if (!playing) {
#if SYNTHESIZER_PARANOIA
Debug.LogFormat(
Time.frameCount.ToString("X4") +
" Synthesizer.Update: {0} ({1}) : freed",
z.info.audioSource.clip.name, z.info.audioSource.name);
#endif
z.info.Disable();
freeSources.Push(z.info);
}
}
}
Swap(ref activeSources0, ref activeSources1);
activeSources1.Clear();
}
}
} // AxelF