├── .gitignore ├── LICENSE ├── README.md └── UnityAudioSync ├── AudioActor.cs ├── AudioBand.cs ├── AudioWatcher.cs └── Editor ├── AudioActorInspector.cs └── AudioActorInspector.cs.meta /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | 6 | # Autogenerated VS/MD solution and project files 7 | /*.csproj 8 | /*.unityproj 9 | /*.sln 10 | /*.suo 11 | /*.user 12 | /*.userprefs 13 | /*.pidb 14 | /*.booproj 15 | 16 | #Unity3D Generated File On Crash Reports 17 | sysinfo.txt 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 NuclearHorseStudios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UnityAudioSync 2 | ============== 3 | 4 | Released by Nuclear Horse Studios http://nuclearhorsestudios.com 5 | 6 | Some code for syncing GameObject behaviors to real-time audio events 7 | 8 | 9 | Copy / Paste from Blog Post Announcement: 10 | 11 | Unity provides a few useful ways of inspecting audio in real time, namely [AudioSource.GetOutputData](http://docs.unity3d.com/ScriptReference/AudioSource.GetOutputData.html) and [AudioSource.GetSpectrumData](http://docs.unity3d.com/ScriptReference/AudioSource.GetSpectrumData.html). These methods are minimally documented, and I hadn’t seen many working examples besides a few small implementations on Unity Answers and forums, so I decided to do a working example of using this functionality in a generalized way in an actual game. 12 | 13 | Please keep in mind I’m not an Audio Engineer, just a nerd. Feel free to let me know of any errors in logic, nomenclature, or morality expressed here. 14 | 15 | 16 | ##MusicWars 17 | The little demo game I made (originally for the first LawlessJam) is called MusicWars, you can try it out below (Unity Web Player required). Control the ship with WASD, shoot with the primary mouse button. Dodge enemy bullets, but don’t worry if you get hit because you can’t die. When you get hit you only lose score. Powerups will randomly spawn when enemies are shot. The goal is to get the highest score you can during the selected song. 18 | 19 | 20 | 21 | ####A few notes about the Demo 22 | * It’s just a demo, and I wanted to keep dependencies to a minimum. Therefor it uses the old crappy Unity GUI. 23 | * The art was all done quickly in Blender and isn’t supposed to be great, with the exception of those awesome explosions which come from the [Detonator Explosion Framework](http://u3d.as/content/ben-throop/detonator-explosion-framework/1qK) 24 | * The music included in the demo is sourced from the [Open Goldberg Variations](http://www.opengoldbergvariations.org/) and [NoCopyrightSounds](https://soundcloud.com/nocopyrightsounds) on [SoundCloud](http://soundcloud.com). 25 | * Code for the game itself was mostly thrown together to showcase the Audio Synchronization stuff, so don’t look to it as an example of Unity Best-Practices. 26 | # 27 | 28 | So how do we sync the firing to the music? That’s what the code I’m releasing does, and it does so in a pretty generalized way which allows one to sync any behavior to in-game audio. 29 | 30 | ###AudioWatcher 31 | 32 | The core of the sync functionality is in the AudioWatcher class. Attach an AudioWatcher to a GameObject somewhere in the scene. Specific channels to watch can be specified, but generally the channels should just be set to [0, 1] for the L/R stereo channels. 33 | 34 | FFTWindowType will be passed to Unity’s FFT implementation. You can generally keep this as BlackmanHarris, which should provide the best quality results. I’ve never seen the window type become a bottleneck in performance, so the high quality BlackmanHarris envelope can be used without much issue. 35 | 36 | The AudioWatcher will instantiate 8 AudioBand objects, and during each FixedUpdate step will populate those AudioBand objects with band-relevant data from AudioSource.GetOuptputData and AudioSource.GetSpectrumData. 37 | 38 | The AudioWatcher also defines an enum with Event Types. Currently there are two event types, but this can be expanded in the future. The event detection is implemented in AudioBand. The event types are currently: 39 | 40 | * Beat: Active when the current Median of the spectrum values for the respective band is greater than the Median of the spectrum values in the past multipled by some constant (beatThreshold). This works best with low-mid frequencies, and will generally fire in time with the “pulse” of that band. 41 | * Vibration: In higher frequencies where the spectrum bandwidth is much greater, the Beat event can break down and not be so reliable. In this case, the Vibration Event might better suffice. It fires more on the “texture” of the audio. Best for rhythmic cymbals like hi-hats or percussive shakers. 42 | 43 | ###AudioBand 44 | 45 | As data is being populated from the AudioWatcher, the AudioBands will do some simple processing on the data (mostly summing values for Mean calculations and such). The AudioBand class also defines a boolean property for each AudioWatcher event type, which will be true if that event is considered active for that band. 46 | 47 | ###AudioActor 48 | 49 | The AudioActor is the main component one works with when defining behavior for music synced objects. Once attached, the AudioActor component will constantly scan the GameObject’s components looking for methods which match the specific signature for audio events: 50 | 51 | public void SomeMethod(AudioBand bandData) 52 | 53 | Therefor, if one adds a method that matches that signature to any of the other components on the GameObject, it’ll show up as a selection in the AudioActor method list. For MusicWars, the EWing enemy type defines the method: 54 | 55 | public class EWing : MonoBehaviour 56 | { 57 | <<...snipped code…>> 58 | 59 | public void Fire(AudioBand bandData) 60 | { 61 | <<...snipped code…>> 62 | } 63 | 64 | <<...snipped code…>> 65 | } 66 | 67 | 68 | Once the EWing component has been attached to a GameObject along with an AudioActor object you’ll see: 69 | 70 | 71 | 72 | # 73 | In this case it will call EWing::Fire anytime the Vibration event fires on the LOW, MID, HIGH_MID, or HIGH bands. 74 | 75 | One can attach one or more AudioActor components to a GameObject. Choose which bands, and event types should trigger your methods, and then choose which method(s) to call when those events are active. AudioActor will pass the AudioBand object that the event happened on to the event handling method, allowing one to do whatever they like with the data from there. In the case of MusicWars, I’m using a derivative value of the current SPL of the music to decide how much power and velocity the enemy bullets should have. 76 | 77 | ###Problems / Bugs / Gotchas / Todos / Shitty code 78 | * In this release the AudioWatcher object is acting kind of like a Singleton. There’s no good reason to have the Watcher be a singleton (though in most cases I’d assume that there would only be one in the scene). The only reason it is currently implemented as a singleton is for my convenience while getting things working. This will be changed in a followup release, and shouldn’t be hard to change on your own if you need multiple watchers in a scene. 79 | * Event Type definitions and implementations are spread out among all three main classes, and should probably be decoupled and generalized. This would allow more complex and interesting event types to be defined easily, and make the rest of the code a little cleaner by consolidating all the event stuff in one place. 80 | * I’m using Unity Free, so currently I cannot profile this code. That said, performance has been good, and the audio processing only takes a couple milliseconds. 81 | * Initially the number of bands was going to be configurable. That was too complex and a stupid idea, so now the number of bands is hard set to 8. There still are some remnants of the old configurable code though, so you might see me doing math when i shouldn’t have to somewhere. 82 | * The custom EditorWindow stuff is kind of buggy, and if you do something like remove all methods from an AudioActor it can get wacky and freak out. For now, just remove the component and re-add it. This will be adressed in a point release soon. 83 | 84 | ##Licensing 85 | All the Audio Synchronization code and MusicWars code are released under the MIT License. Any other code or assets included in the example project - including but not limited to Detonator Explosion Framework and Songs - are included for convenience and retain their original licenses. 86 | 87 | ##Other Useful Resources 88 | * [Unity Script Reference: AudioSource.GetOutputData](http://docs.unity3d.com/ScriptReference/AudioSource.GetOutputData.html) 89 | * [Unity Script Reference: AudioSource.GetSpectrumData](http://docs.unity3d.com/ScriptReference/AudioSource.GetSpectrumData.html) 90 | * [Simple example of using AudioSource.GetOutputData and AudioSource.GetSpectrumData](http://answers.unity3d.com/questions/157940/getoutputdata-and-getspectrumdata-they-represent-t.html) 91 | * [Master Handbook of Acoustics](http://www.amazon.com/Master-Handbook-Acoustics-Alton-Everest/dp/0071603328?tag=donations09-20) 92 | 93 | ##Credits 94 | * Music in the demo provided by [Open Goldberg Variations](http://www.opengoldbergvariations.org/) and [NoCopyrightSounds](https://soundcloud.com/nocopyrightsounds) 95 | * Awesome explosions from [Detonator Explosion Framework](http://u3d.as/content/ben-throop/detonator-explosion-framework/1qK) 96 | 97 | ##Download Source Code 98 | * [Github: Full MusicWars Source](https://github.com/NuclearHorseStudios/MusicWars) 99 | * [Github: Audio Code Only](https://github.com/NuclearHorseStudios/UnityAudioSync) -------------------------------------------------------------------------------- /UnityAudioSync/AudioActor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | public class AudioActor : MonoBehaviour 8 | { 9 | public int channel = 0; 10 | public AudioWatcher audioWatcher; 11 | public AudioWatcher.Bands[] bands; 12 | public AudioWatcher.EventTypes[] eventTypes; 13 | 14 | public struct DelegateData 15 | { 16 | public string methodName; 17 | public Component component; 18 | public OnEventTrigger method; 19 | 20 | public DelegateData(string n, Component c, OnEventTrigger m) 21 | { 22 | methodName = n; 23 | component = c; 24 | method = m; 25 | } 26 | } 27 | 28 | public string[] MethodNames 29 | { 30 | get { return methodNames; } 31 | set { methodNames = value; } 32 | } 33 | 34 | public int[] MethodIdx 35 | { 36 | get { return methodIdx; } 37 | set { methodIdx = value; } 38 | } 39 | 40 | public int NumMethodsAttached 41 | { 42 | get { return numMethodsAttached; } 43 | set { numMethodsAttached = value; } 44 | } 45 | 46 | public Dictionary MethodDict 47 | { 48 | get { return mDict; } 49 | set { mDict = value; } 50 | } 51 | 52 | public List TriggerList 53 | { 54 | get { return onEventTrigger; } 55 | set { onEventTrigger = value; } 56 | } 57 | 58 | [SerializeField, HideInInspector] 59 | private List onEventTrigger; 60 | 61 | [SerializeField, HideInInspector] 62 | private string[] methodNames = new string[0] {}; 63 | 64 | [SerializeField, HideInInspector] 65 | private int[] methodIdx = new int[0] {}; 66 | 67 | [SerializeField, HideInInspector] 68 | private int numMethodsAttached = 0; 69 | 70 | [HideInInspector] 71 | public Dictionary mDict = 72 | new Dictionary(); 73 | 74 | [Serializable] 75 | public delegate void OnEventTrigger(AudioBand bandData); 76 | 77 | void OnEnable () 78 | { 79 | FindMethods(gameObject); 80 | RegisterMethods(); 81 | } 82 | 83 | void FixedUpdate () 84 | { 85 | if (audioWatcher == null) 86 | { 87 | audioWatcher = AudioWatcher.Instance; 88 | return; 89 | } 90 | 91 | foreach (AudioWatcher.Bands b in bands) 92 | { 93 | foreach (AudioWatcher.EventTypes e in eventTypes) 94 | { 95 | InvokeEvents(b, e); 96 | } 97 | } 98 | } 99 | 100 | void InvokeEvents (AudioWatcher.Bands b, AudioWatcher.EventTypes e) 101 | { 102 | if (!audioWatcher.EventIsActive(b, channel, e)) { return; } 103 | 104 | foreach(OnEventTrigger f in onEventTrigger) 105 | { 106 | f.Invoke(audioWatcher.GetBand(b, channel)); 107 | } 108 | } 109 | 110 | void AddMatchingMethods (Component comp) 111 | { 112 | foreach (MethodInfo method in comp.GetType().GetMethods()) 113 | { 114 | if (MatchesAudioEventSignature(method)) { AddMethod(comp, method); } 115 | } 116 | } 117 | 118 | public void FindMethods (GameObject go) 119 | { 120 | MethodNames = new string[0] {}; 121 | foreach (Component comp in go.GetComponents()) 122 | { 123 | AddMatchingMethods(comp); 124 | } 125 | } 126 | 127 | public void RegisterMethods () 128 | { 129 | var tList = new List(); 130 | 131 | for (int i = 0; i < methodIdx.Length; i++) 132 | { 133 | tList.Add(MethodDict[MethodNames[MethodIdx[i]]].method); 134 | } 135 | 136 | RegisterEventMethods(tList); 137 | } 138 | 139 | public void AddMethod (Component c, MethodInfo method) 140 | { 141 | Type type = typeof(OnEventTrigger); 142 | string name = method.Name; 143 | string strIdx = c.name + "::" + method.Name; 144 | var mDelegate = (OnEventTrigger) 145 | Delegate.CreateDelegate(type, c, method); 146 | 147 | MethodDict[strIdx] = new DelegateData(name, c, mDelegate); 148 | 149 | AddMethodName(strIdx); 150 | } 151 | 152 | public void RegisterEventMethods (List tList) 153 | { 154 | onEventTrigger = tList; 155 | } 156 | 157 | public void SetMethodNames (string[] names) 158 | { 159 | methodNames = names; 160 | } 161 | 162 | public void SetMethodIdx (int[] idx) 163 | { 164 | methodIdx = idx; 165 | } 166 | 167 | public void SetNumMethods (int n) 168 | { 169 | numMethodsAttached = n; 170 | } 171 | 172 | void AddMethodName (string name) 173 | { 174 | string[] nNames = new string[MethodNames.Length + 1]; 175 | 176 | Array.Copy(MethodNames, 0, nNames, 0, MethodNames.Length); 177 | 178 | nNames[MethodNames.Length] = name; 179 | 180 | MethodNames = nNames; 181 | } 182 | 183 | static bool MatchesAudioEventSignature (MethodInfo method) 184 | { 185 | ParameterInfo[] pInfo = method.GetParameters(); 186 | 187 | if (pInfo.Length != 1) { return false; } 188 | if (!(method.ReturnType == typeof(void))) { return false; } 189 | 190 | return pInfo[0].ParameterType == typeof(AudioBand); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /UnityAudioSync/AudioBand.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | 5 | [Serializable] 6 | public class AudioBand 7 | { 8 | public int Channel { get { return channel; } } 9 | public float SpectrumMax { get { return spectrumMax; } } 10 | public float SpectrumMin { get { return spectrumMin; } } 11 | public float SpectrumMean { get { return spectrumMean; } } 12 | public float SpecMeanAvg { get { return specMeanAvg; } } 13 | public float SpecMedianAvg { get { return specMedianAvg; } } 14 | public float OutputMean { get { return outputMean; } } 15 | public float SPL { get { return spl; } } 16 | public float SpectrumMedian { get { return spectrumMedian; } } 17 | 18 | public bool Active 19 | { 20 | get { return SpectrumMax > activationLevel; } 21 | } 22 | 23 | public bool Vibration 24 | { 25 | get { return !wasActive && Active; } 26 | } 27 | 28 | public bool Beat 29 | { 30 | get 31 | { 32 | return SpectrumMedian > SpecMedianAvg * beatThreshold && Active; 33 | } 34 | } 35 | 36 | public float OutputMedian 37 | { 38 | get 39 | { 40 | if (outputMedian == 0) { outputMedian = GetMedian(output); } 41 | return outputMedian; 42 | } 43 | } 44 | 45 | public AudioWatcher.Bands band; 46 | 47 | private int bandWidth; 48 | private int channel; 49 | private int specMeanHistoryLength = 8; 50 | private int specMedianHistoryLength = 8; 51 | 52 | [SerializeField] 53 | private float spl; 54 | 55 | [SerializeField] 56 | private float spectrumMax; 57 | 58 | [SerializeField] 59 | private float spectrumMin; 60 | 61 | [SerializeField] 62 | private float spectrumMedian = 0; 63 | 64 | [SerializeField] 65 | private float spectrumMean = 0; 66 | 67 | [SerializeField] 68 | private float outputMedian = 0; 69 | 70 | [SerializeField] 71 | private float outputMean = 0; 72 | 73 | [SerializeField] 74 | private float spectrumSum = 0; 75 | 76 | [SerializeField] 77 | private float outputSum = 0; 78 | 79 | [SerializeField] 80 | private float[] spectrum; 81 | 82 | [SerializeField] 83 | private float[] output; 84 | 85 | [SerializeField] 86 | private int spectrumCnt = 0; 87 | 88 | [SerializeField] 89 | private int outputCnt = 0; 90 | 91 | [SerializeField] 92 | private float activationLevel = 0.01f; 93 | 94 | [SerializeField] 95 | private float beatThreshold = 2.2f; // magic and arbitrary 96 | 97 | [SerializeField] 98 | private bool wasActive; 99 | 100 | [SerializeField] 101 | private bool beat; 102 | 103 | [SerializeField] 104 | private bool active; 105 | 106 | [SerializeField] 107 | private float specMeanAvg = 0; 108 | 109 | [SerializeField] 110 | private float specMedianAvg = 0; 111 | 112 | [SerializeField] 113 | private float[] specMeanHistory; 114 | 115 | [SerializeField] 116 | private float[] specMedianHistory; 117 | 118 | public AudioBand(int bw, int c, AudioWatcher.Bands b) 119 | { 120 | bandWidth = bw; 121 | channel = c; 122 | band = b; 123 | specMeanHistory = new float[specMeanHistoryLength]; 124 | specMedianHistory = new float[specMedianHistoryLength]; 125 | spectrum = new float[bandWidth]; 126 | output = new float[bandWidth]; 127 | 128 | Reset(); 129 | } 130 | 131 | public void Reset () 132 | { 133 | UpdateSpecMeanHistory(); 134 | UpdateSpecMedianHistory(); 135 | 136 | //tmp 137 | beat = Beat; 138 | wasActive = active; 139 | active = Active; 140 | 141 | spl = GetSPL(); 142 | spectrumMedian = GetMedian(spectrum); 143 | specMedianAvg = GetSpecMedianAvg(); 144 | specMeanAvg = GetSpecMeanAvg(); 145 | outputMean = GetOutputMean(); 146 | spectrumMean = GetSpectrumMean(); 147 | spectrumCnt = 0; 148 | spectrumSum = 0; 149 | outputCnt = 0; 150 | outputSum = 0; 151 | spectrumMax = 0; 152 | outputMedian = 0; 153 | spectrumMin = Mathf.Infinity; 154 | } 155 | 156 | public void AddSpectrumData (float val) 157 | { 158 | spectrum[spectrumCnt++] = val; 159 | 160 | spectrumSum += val; 161 | 162 | if (val > spectrumMax) { spectrumMax = val; } 163 | else if (val < spectrumMin) { spectrumMin = val; } 164 | } 165 | 166 | public void AddOutputData (float val) 167 | { 168 | output[outputCnt++] = val; 169 | outputSum += val; 170 | } 171 | 172 | void UpdateSpecMeanHistory () 173 | { 174 | Array.Copy( specMeanHistory, 175 | 0, 176 | specMeanHistory, 177 | 1, 178 | specMeanHistoryLength - 1); 179 | 180 | specMeanHistory[0] = SpectrumMean; 181 | } 182 | 183 | void UpdateSpecMedianHistory () 184 | { 185 | Array.Copy( specMedianHistory, 186 | 0, 187 | specMedianHistory, 188 | 1, 189 | specMedianHistoryLength - 1); 190 | 191 | specMedianHistory[0] = SpectrumMedian; 192 | } 193 | 194 | float GetMedian (float[] data) 195 | { 196 | // Just take a half value instead of sorting the array 197 | return (spectrumMax - spectrumMin) * 0.5f; 198 | } 199 | 200 | float GetSpectrumMean () 201 | { 202 | spectrumMean = spectrumSum / bandWidth; 203 | 204 | return spectrumMean; 205 | } 206 | 207 | float GetSpecMeanAvg () 208 | { 209 | float sum = 0; 210 | 211 | for (int i = 0; i < specMeanHistoryLength; i++) 212 | { 213 | sum += specMeanHistory[i]; 214 | } 215 | 216 | return sum / specMeanHistoryLength; 217 | } 218 | 219 | float GetSpecMedianAvg () 220 | { 221 | float sum = 0; 222 | 223 | for (int i = 0; i < specMedianHistoryLength; i++) 224 | { 225 | sum += specMedianHistory[i]; 226 | } 227 | 228 | return sum / specMedianHistoryLength; 229 | } 230 | 231 | float GetSPL () 232 | { 233 | float rms = Mathf.Sqrt(OutputMean); 234 | float nSpl = 20f * Mathf.Log10(rms); 235 | 236 | if (nSpl < -(160f) || float.IsNaN(nSpl)) { nSpl = -(160f); } 237 | 238 | return nSpl; 239 | } 240 | 241 | float GetOutputMean () 242 | { 243 | return outputSum / bandWidth;; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /UnityAudioSync/AudioWatcher.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | public class AudioWatcher : MonoBehaviour 6 | { 7 | public static AudioWatcher Instance { get { return instance; } } 8 | 9 | public enum Bands 10 | { 11 | ULTRA_LOW = 0, 12 | LOW = 1, 13 | LOW_MID = 2, 14 | MID = 3, 15 | HIGH_MID = 4, 16 | LOW_HIGH = 5, 17 | HIGH = 6, 18 | ULTRA_HIGH = 7 19 | // CURR_MAX = 8, 20 | // CURR_MIN = 9, 21 | // ANY = 10, 22 | // ALL = 11 23 | } 24 | 25 | public enum EventTypes 26 | { 27 | BEAT = 1, 28 | VIBRATION = 2 29 | } 30 | 31 | public int[] channels; 32 | 33 | public FFTWindow fftWindowType = FFTWindow.BlackmanHarris; 34 | 35 | private AudioSource audioSource; 36 | 37 | [SerializeField] 38 | private AudioBand[] bandData; 39 | private List actors; 40 | private int[] bandWidth; 41 | private float[] spectrumData; 42 | private float[] outputData; 43 | private int baseBands; 44 | private int numBands = 8; 45 | private int spectrumDensity = 2048; 46 | private bool initialized = false; 47 | private int[,] bandOffsets; 48 | 49 | private static AudioWatcher instance; 50 | 51 | void Awake () 52 | { 53 | instance = this; 54 | actors = new List(); 55 | bandOffsets = new int[channels.Length, numBands]; 56 | spectrumData = new float[spectrumDensity]; 57 | outputData = new float[spectrumDensity]; 58 | audioSource = gameObject.GetComponent(); 59 | baseBands = (int) Mathf.Ceil(Mathf.Log(spectrumDensity)); 60 | } 61 | 62 | void Start () 63 | { 64 | CalcBandwidth(); 65 | InitializeBandData(); 66 | 67 | initialized = true; 68 | } 69 | 70 | void FixedUpdate () 71 | { 72 | if (!initialized) { return; } 73 | 74 | PopulateBandData(); 75 | } 76 | 77 | void InitializeBandData () 78 | { 79 | bandData = new AudioBand[numBands * channels.Length]; 80 | 81 | foreach (int c in channels) 82 | { 83 | for (int band = 0; band < numBands; band++) 84 | { 85 | int idx = band + (numBands * c); 86 | bandData[idx] = new AudioBand(bandWidth[band], c, (Bands) band); 87 | bandOffsets[c,band] = idx; 88 | } 89 | } 90 | } 91 | 92 | void CalcBandwidth () 93 | { 94 | // A smarter person can do this with maths 95 | bandWidth = new int[numBands]; 96 | for (int i = 1; i < spectrumDensity - 1; i++) 97 | { 98 | int band = (int) Mathf.Floor(Mathf.Log(i)); 99 | bandWidth[band]++; 100 | } 101 | } 102 | 103 | void PopulateBandData () 104 | { 105 | foreach (int c in channels) 106 | { 107 | audioSource.GetSpectrumData(spectrumData, c, fftWindowType); 108 | audioSource.GetOutputData(outputData, c); 109 | 110 | int channelOffset = numBands * c; 111 | int prevBand = -1; 112 | for (int i = 1; i < spectrumDensity - 1; i++) 113 | { 114 | int loopBand = (int) Mathf.Floor(Mathf.Log(i)) + channelOffset; 115 | 116 | if (loopBand != prevBand) 117 | { 118 | prevBand = loopBand; 119 | bandData[loopBand].Reset(); 120 | } 121 | 122 | bandData[loopBand].AddSpectrumData(spectrumData[i]); 123 | bandData[loopBand].AddOutputData(outputData[i]); 124 | } 125 | } 126 | } 127 | 128 | public AudioBand GetBand (Bands b, int c) 129 | { 130 | int channelOffset = numBands * c; 131 | 132 | return bandData[((int) b) + channelOffset]; 133 | } 134 | 135 | public bool EventIsActive (Bands b, int c, EventTypes e) 136 | { 137 | if (e == EventTypes.BEAT) 138 | { 139 | return bandData[bandOffsets[c, (int) b]].Beat; 140 | } 141 | else if (e == EventTypes.VIBRATION) 142 | { 143 | return bandData[bandOffsets[c, (int) b]].Vibration; 144 | } 145 | 146 | return false; 147 | } 148 | 149 | public float CurrentSPL (int channel) 150 | { 151 | float splSum = 0; 152 | int offset = channel * numBands; 153 | 154 | for (int band = 0; band < numBands; band++) 155 | { 156 | splSum += bandData[band + offset].SPL; 157 | } 158 | 159 | return splSum / numBands; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /UnityAudioSync/Editor/AudioActorInspector.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | [CustomEditor(typeof(AudioActor))] 8 | public class AudioActorInspector : Editor 9 | { 10 | 11 | public AudioActor Target 12 | { 13 | get { return (AudioActor) target; } 14 | } 15 | 16 | public GameObject Go 17 | { 18 | get { return Target.gameObject; } 19 | } 20 | 21 | private AudioActor targetActor; 22 | private GameObject go; 23 | private SerializedProperty onTriggerEventList; 24 | 25 | public override void OnInspectorGUI () 26 | { 27 | serializedObject.Update(); 28 | 29 | DrawDefaultInspector(); 30 | 31 | if (Time.realtimeSinceStartup % 5 == 0) 32 | { 33 | Target.FindMethods(Go); 34 | Target.RegisterMethods(); 35 | } 36 | 37 | DrawLayout(); 38 | 39 | EditorUtility.SetDirty(Target); 40 | } 41 | 42 | void DrawLayout () 43 | { 44 | GUILayoutOption expandWidth = GUILayout.ExpandWidth(false); 45 | int removeMethod = -1; 46 | int[] methodIdx = Target.MethodIdx; 47 | string[] methodNames = Target.MethodNames; 48 | 49 | for (int i = 0; i < Target.NumMethodsAttached; i++) 50 | { 51 | methodIdx[i] = EditorGUILayout.Popup(methodIdx[i], methodNames); 52 | 53 | if(GUILayout.Button("-", expandWidth)) { removeMethod = i; } 54 | } 55 | 56 | if (removeMethod != -1) 57 | { 58 | RemoveMethod(removeMethod); 59 | removeMethod = -1; 60 | } 61 | 62 | if (GUILayout.Button("+", expandWidth)) { AddMethod(); } 63 | } 64 | 65 | void AddMethod () 66 | { 67 | int[] nMethodIdx = new int[Target.MethodIdx.Length + 1]; 68 | Target.MethodIdx.CopyTo(nMethodIdx, 0); 69 | 70 | Target.NumMethodsAttached++; 71 | 72 | Target.MethodIdx = nMethodIdx; 73 | } 74 | 75 | void RemoveMethod (int mIdx) 76 | { 77 | int newLength = Target.MethodIdx.Length - 1; 78 | int lowerLength = newLength - (newLength - mIdx); 79 | int upperLength = newLength - mIdx; 80 | int[] nMethodIdx = new int[newLength]; 81 | int[] cMethodIdx = Target.MethodIdx; 82 | 83 | if (mIdx != 0) 84 | { 85 | Array.Copy(cMethodIdx, 0, nMethodIdx, 0, lowerLength); 86 | } 87 | 88 | Array.Copy(cMethodIdx, mIdx + 1, nMethodIdx, mIdx, upperLength); 89 | 90 | Target.NumMethodsAttached--; 91 | Target.MethodIdx = nMethodIdx; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /UnityAudioSync/Editor/AudioActorInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c51535a4239933e4b9c2080a98f8414e 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | --------------------------------------------------------------------------------