diff --git a/Assets/Fungus/Thirdparty/Usfxr.meta b/Assets/Fungus/Thirdparty/Usfxr.meta new file mode 100644 index 00000000..0551e993 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 4f1beb4da32d26942b2bd76f66381b8e +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Commands.meta b/Assets/Fungus/Thirdparty/Usfxr/Commands.meta new file mode 100644 index 00000000..92953486 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Commands.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: edce1a19c5a5986429e418daa65f023a +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Commands/PlayUsfxrSound.cs b/Assets/Fungus/Thirdparty/Usfxr/Commands/PlayUsfxrSound.cs new file mode 100644 index 00000000..f7110ec0 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Commands/PlayUsfxrSound.cs @@ -0,0 +1,46 @@ +namespace Fungus { + using System; + using UnityEngine; + + [CommandInfo("Audio", + "Play Usfxr Sound", + "Plays a usfxr synth sound. Use the usfxr editor [Window->Generate usfxr Sound Effects] to create the SettingsString. Set a ParentTransform if using positional sound.")] + public class PlayUsfxrSound : Command { + protected SfxrSynth _synth = new SfxrSynth(); + public Transform ParentTransform = null; + public String SettingsString = ""; + + //Call this if the settings have changed + protected void UpdateCache() { + if (SettingsString != null) { + _synth.parameters.SetSettingsString(SettingsString); + _synth.CacheSound(); + } + } + + public void Awake() { + //Always build the cache on awake + UpdateCache(); + } + + public override void OnEnter() { + _synth.SetParentTransform(ParentTransform); + _synth.Play(); + Continue(); + } + + public override string GetSummary() { + if (String.IsNullOrEmpty(SettingsString)) { + return "Settings String hasn't been set!"; + } + if (ParentTransform != null) { + return "" + ParentTransform.name + ": " + SettingsString; + } + return "Camera.main: " + SettingsString; + } + + public override Color GetButtonColor() { + return new Color32(128, 200, 200, 255); + } + } +} diff --git a/Assets/Fungus/Thirdparty/Usfxr/Commands/PlayUsfxrSound.cs.meta b/Assets/Fungus/Thirdparty/Usfxr/Commands/PlayUsfxrSound.cs.meta new file mode 100644 index 00000000..d999fa84 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Commands/PlayUsfxrSound.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a3ff412ad89846a47a70a620a222cbf8 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts.meta new file mode 100644 index 00000000..9f6858ed --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: f76a1f2cd86ff87428f9c3b6efd85739 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor.meta new file mode 100644 index 00000000..41383153 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: b6702fc1288299d48a75cbafe75be68c +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor/SfxrGenerator.cs b/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor/SfxrGenerator.cs new file mode 100644 index 00000000..b6c47acd --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor/SfxrGenerator.cs @@ -0,0 +1,486 @@ +//----------------------------------------------------------------------- +// +// SfxrEditor implements a Unity window to generate sounds with usfxr +// using a more friendly GUI. +// +// +// Copyright 2013 Tiaan Geldenhuys, 2014 Zeh Fernando +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- +using System; +using System.IO; +using UnityEditor; +using UnityEngine; + +/// +/// Implements a Unity window to generate sounds and their parameters with usfxr. +/// +/// +/// Open the generator from the Window menu. You can then create a sound and +/// when you are ready, copy the equivalent parameters to the clipboard to be +/// used inside your game. +/// +public class SfxrGenerator : EditorWindow { + + /// + /// Open the usfxr's sound-effects generator window. + /// + + // Enums + public enum WaveType : uint { + Square = 0, + Sawtooth = 1, + Sine = 2, + Noise = 3, + Triangle = 4, + PinkNoise = 5, + Tan = 6, + Whistle = 7, + Breaker = 8 + } + + // Properties + private Vector2 scrollPosition; // Position of the scroll window + private Vector2 scrollPositionRoot; + private SfxrParams soundParameters; + + private string suggestedName; + + private SfxrSynth synth; + + // ================================================================================================================ + // PUBLIC INTERFACE ----------------------------------------------------------------------------------------------- + + [MenuItem("Window/Generate usfxr Sound Effects")] + public static void Initialize() { + var window = ScriptableObject.CreateInstance(); + window.title = window.name = "Sound Effects"; + window.Show(); + } + + protected virtual void OnGUI() { + // Initializations + if (soundParameters == null) { + soundParameters = new SfxrParams(); + soundParameters.Randomize(); + } + + if (synth == null) { + synth = new SfxrSynth(); + } + + bool soundChanged = false; + + // Begin UI + scrollPositionRoot = GUILayout.BeginScrollView(scrollPositionRoot); + GUILayout.BeginHorizontal(); + + // Left column (generator buttons, copy & paste) + soundChanged = RenderLeftColumn(soundParameters) || soundChanged; + + // Main settings column + soundChanged = RenderSettingsColumn(soundParameters) || soundChanged; + + // Ends the UI + GUILayout.EndHorizontal(); + GUILayout.EndScrollView(); + + // Play sound if necessary + if (soundChanged) { + synth.parameters.SetSettingsString(soundParameters.GetSettingsString()); + PlaySound(); + CreateWavePreview(); + } + + } + + public void PlaySound() { + // Just play the current sound + synth.Play(); + } + + public void CreateWavePreview() { + // Creates an image with a preview of the wave + + /* + // Create the texture and set its colour. + Texture2D blackTexture = new Texture2D(1,1); + blackTexture.SetPixel(0,0,Color.black); + blackTexture.Apply(); + ... + // Use the texture. + GUI.DrawTexture(new Rect(0,0,Screen.width,Screen.height), blackTexture); + + // Anti alias line: http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm + */ + + } + + public bool RenderLeftColumn(SfxrParams parameters) { + bool soundChanged = false; + + // Begin generator column + GUILayout.BeginVertical("box", GUILayout.Width(110)); + GUILayout.Label("GENERATOR", EditorStyles.boldLabel); + GUILayout.Space(8); + + if (GUILayout.Button("PICKUP/COIN")) { + suggestedName = "PickupCoin"; + parameters.GeneratePickupCoin(); + soundChanged = true; + } + if (GUILayout.Button("LASER/SHOOT")) { + suggestedName = "LaserShoot"; + parameters.GenerateLaserShoot(); + soundChanged = true; + } + if (GUILayout.Button("EXPLOSION")) { + suggestedName = "Explosion"; + parameters.GenerateExplosion(); + soundChanged = true; + } + if (GUILayout.Button("POWERUP")) { + suggestedName = "Powerup"; + parameters.GeneratePowerup(); + soundChanged = true; + } + if (GUILayout.Button("HIT/HURT")) { + suggestedName = "HitHurt"; + parameters.GenerateHitHurt(); + soundChanged = true; + } + if (GUILayout.Button("JUMP")) { + suggestedName = "Jump"; + parameters.GenerateJump(); + soundChanged = true; + } + if (GUILayout.Button("BLIP/SELECT")) { + suggestedName = "BlipSelect"; + parameters.GenerateBlipSelect(); + soundChanged = true; + } + + GUILayout.Space(30); + + if (GUILayout.Button("MUTATE")) { + parameters.Mutate(); + soundChanged = true; + } + if (GUILayout.Button("RANDOMIZE")) { + suggestedName = "Random"; + parameters.Randomize(); + soundChanged = true; + } + + GUILayout.Space(30); + + if (GUILayout.Button("COPY (OLD)")) { + EditorGUIUtility.systemCopyBuffer = parameters.GetSettingsStringLegacy(); + } + if (GUILayout.Button("COPY")) { + EditorGUIUtility.systemCopyBuffer = parameters.GetSettingsString(); + } + if (GUILayout.Button("PASTE")) { + suggestedName = null; + parameters.SetSettingsString(EditorGUIUtility.systemCopyBuffer); + soundChanged = true; + } + + GUILayout.Space(30); + + if (GUILayout.Button("PLAY SOUND")) { + PlaySound(); + } + + GUILayout.Space(30); + + if (GUILayout.Button("EXPORT WAV")) { + var path = EditorUtility.SaveFilePanel("Export as WAV", "", getSuggestedName() + ".wav", "wav"); + if (path.Length != 0) { + SfxrSynth synth = new SfxrSynth(); + synth.parameters.SetSettingsString(parameters.GetSettingsString()); + File.WriteAllBytes(path, synth.GetWavFile()); + } + } + + // End generator column + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + return soundChanged; + } + + public bool RenderSettingsColumn(SfxrParams parameters) { + bool soundChanged = false; + + // Begin manual settings column + GUILayout.BeginVertical("box"); + GUILayout.Label("MANUAL SETTINGS", EditorStyles.boldLabel); + GUILayout.Space(8); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition); + soundChanged = RenderParameters(soundParameters) || soundChanged; + GUILayout.EndScrollView(); + + // End manual settings column + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + return soundChanged; + } + + /// + /// Renders the specified SFXR parameters in the editor. + /// + /// The current parameters to be rendered. + /// + /// This method is called automatically for the standalone editor window + /// when a game-object with parameters is selected. However, this public + /// method can also be called by CustomEditor implementations for specific + /// game-components to render the editor in the Inspector window + /// (see UnityEditor.Editor for details). Also, this method can be used + /// from PropertyDrawer implementations; future releases of the code may + /// include such a default drawer (once SfxrSynth and SfxrParams supports + /// native serialization for Unity). + /// + public bool RenderParameters(SfxrParams parameters) { + bool soundChanged = false; + + GUIStyle waveTypeStyle = EditorStyles.popup; + waveTypeStyle.fontSize = 12; + waveTypeStyle.fixedHeight = 22; + + EditorGUI.BeginChangeCheck(); + try { + WaveType waveTypeAsEnum = (WaveType)parameters.waveType; + waveTypeAsEnum = (WaveType)EditorGUILayout.EnumPopup(new GUIContent("Wave Type", "Shape of the wave"), waveTypeAsEnum, waveTypeStyle); + parameters.waveType = (uint)waveTypeAsEnum; + GUILayout.Space(12); + + //RenderPopup(waveTypeOptions, ((int)(parameters.waveType)), (value => parameters.waveType = ((uint)(value))), new GUIContent("Wave Type", "Shape of the wave")); + bool isSquareWaveType = (parameters.waveType == 0); + RenderSlider(+0, +1, parameters.masterVolume, (value => parameters.masterVolume = value), new GUIContent("Volume", "Overall volume of the sound (0 to 1)")); + + RenderHeading("Wave Envelope"); + RenderSlider(+0, +1, parameters.attackTime, (value => parameters.attackTime = value), new GUIContent("Attack Time", "Length of the volume envelope attack (0 to 1)")); + RenderSlider(+0, +1, parameters.sustainTime, (value => parameters.sustainTime = value), new GUIContent("Sustain Time", "Length of the volume envelope sustain (0 to 1)")); + RenderSlider(+0, +1, parameters.sustainPunch, (value => parameters.sustainPunch = value), new GUIContent("Sustain Punch", "Tilts the sustain envelope for more 'pop' (0 to 1)")); + RenderSlider(+0, +1, parameters.decayTime, (value => parameters.decayTime = value), new GUIContent("Decay Time", "Length of the volume envelope decay (yes, I know it's called release) (0 to 1)")); + + // BFXR + RenderSlider(+0, +1, parameters.compressionAmount, (value => parameters.compressionAmount = value), new GUIContent("Compression", "Pushes amplitudes together into a narrower range to make them stand out more. Very good for sound effects, where you want them to stick out against background music (0 to 1)")); + + RenderHeading("Frequency"); + RenderSlider(+0, +1, parameters.startFrequency, (value => parameters.startFrequency = value), new GUIContent("Start Frequency", "Base note of the sound (0 to 1)")); + RenderSlider(+0, +1, parameters.minFrequency, (value => parameters.minFrequency = value), new GUIContent("Minimum Frequency", "If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1)")); + RenderSlider(-1, +1, parameters.slide, (value => parameters.slide = value), new GUIContent("Slide", "Slides the note up or down (-1 to 1)")); + RenderSlider(-1, +1, parameters.deltaSlide, (value => parameters.deltaSlide = value), new GUIContent("Delta Slide", "Accelerates the slide (-1 to 1)")); + RenderSlider(+0, +1, parameters.vibratoDepth, (value => parameters.vibratoDepth = value), new GUIContent("Vibrato Depth", "Strength of the vibrato effect (0 to 1)")); + RenderSlider(+0, +1, parameters.vibratoSpeed, (value => parameters.vibratoSpeed = value), new GUIContent("Vibrato Speed", "Speed of the vibrato effect (i.e. frequency) (0 to 1)")); + + // BFXR + RenderSlider(+0, +1, parameters.overtones, (value => parameters.overtones = value), new GUIContent("Harmonics", "Overlays copies of the waveform with copies and multiples of its frequency. Good for bulking out or otherwise enriching the texture of the sounds (warning: this is the number 1 cause of usfxr slowdown!) (0 to 1)")); + RenderSlider(+0, +1, parameters.overtoneFalloff, (value => parameters.overtoneFalloff = value), new GUIContent("Harmonics falloff", "The rate at which higher overtones should decay (0 to 1)")); + + RenderHeading("Tone Change/Pitch Jump"); + // BFXR + RenderSlider(+0, +1, parameters.changeRepeat, (value => parameters.changeRepeat = value), new GUIContent("Change Repeat Speed", "Larger Values means more pitch jumps, which can be useful for arpeggiation (0 to 1)")); + + RenderSlider(-1, +1, parameters.changeAmount, (value => parameters.changeAmount = value), new GUIContent("Change Amount 1", "Shift in note, either up or down (-1 to 1)")); + RenderSlider(+0, +1, parameters.changeSpeed, (value => parameters.changeSpeed = value), new GUIContent("Change Speed 1", "How fast the note shift happens (only happens once) (0 to 1)")); + + // BFXR + RenderSlider(-1, +1, parameters.changeAmount2, (value => parameters.changeAmount2 = value), new GUIContent("Change Amount 2", "Shift in note, either up or down (-1 to 1)")); + RenderSlider(+0, +1, parameters.changeSpeed2, (value => parameters.changeSpeed2 = value), new GUIContent("Change Speed 2", "How fast the note shift happens (only happens once) (0 to 1)")); + + RenderHeading("Square Waves"); + RenderSlider(+0, +1, parameters.squareDuty, (value => parameters.squareDuty = value), new GUIContent("Square Duty", "Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1)"), isSquareWaveType); + RenderSlider(-1, +1, parameters.dutySweep, (value => parameters.dutySweep = value), new GUIContent("Duty Sweep", "Sweeps the duty up or down (-1 to 1)"), isSquareWaveType); + + RenderHeading("Repeats"); + RenderSlider(+0, +1, parameters.repeatSpeed, (value => parameters.repeatSpeed = value), new GUIContent("Repeat Speed", "Speed of the note repeating - certain variables are reset each time (0 to 1)")); + + RenderHeading("Phaser"); + RenderSlider(-1, +1, parameters.phaserOffset, (value => parameters.phaserOffset = value), new GUIContent("Phaser Offset", "Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1)")); + RenderSlider(-1, +1, parameters.phaserSweep, (value => parameters.phaserSweep = value), new GUIContent("Phaser Sweep", "Sweeps the phase up or down (-1 to 1)")); + + RenderHeading("Filters"); + RenderSlider(+0, +1, parameters.lpFilterCutoff, (value => parameters.lpFilterCutoff = value), new GUIContent("Low-Pass Cutoff", "Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1)")); + RenderSlider(-1, +1, parameters.lpFilterCutoffSweep, (value => parameters.lpFilterCutoffSweep = value), new GUIContent("Low-Pass Cutoff Sweep", "Sweeps the low-pass cutoff up or down (-1 to 1)")); + RenderSlider(+0, +1, parameters.lpFilterResonance, (value => parameters.lpFilterResonance = value), new GUIContent("Low-Pass Resonance", "Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1)")); + RenderSlider(+0, +1, parameters.hpFilterCutoff, (value => parameters.hpFilterCutoff = value), new GUIContent("High-Pass Cutoff", "Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1)")); + RenderSlider(-1, +1, parameters.hpFilterCutoffSweep, (value => parameters.hpFilterCutoffSweep = value), new GUIContent("High-Pass Cutoff Sweep", "Sweeps the high-pass cutoff up or down (-1 to 1)")); + + RenderHeading("Bit Crushing"); + + // BFXR + RenderSlider(+0, +1, parameters.bitCrush, (value => parameters.bitCrush = value), new GUIContent("Bit Crush", "Resamples the audio at a lower frequency (0 to 1)")); + RenderSlider(-1, +1, parameters.bitCrushSweep, (value => parameters.bitCrushSweep = value), new GUIContent("Bit Crush Sweep", "Sweeps the Bit Crush filter up or down (-1 to 1)")); + } finally { + if (EditorGUI.EndChangeCheck()) { + parameters.paramsDirty = true; + soundChanged = true; + } + } + + return soundChanged; + } + + protected static void RenderHeading(string heading) { + EditorGUILayout.LabelField(heading, EditorStyles.boldLabel); + } + + protected static bool RenderButton( + GUIContent content = null, + Action valueChangeAction = null, + bool? isEnabled = null, + params GUILayoutOption[] options) +{ + if (content == null) + { + content = GUIContent.none; + } + + bool isClicked = false; + return RenderGenericEditor( + ref isClicked, + () => GUILayout.Button(content, options), + valueChangeAction, + isEnabled); + } + + protected static bool RenderButton( + string text, + Action valueChangeAction = null, + bool? isEnabled = null, + params GUILayoutOption[] options) + { + return RenderButton( + new GUIContent(text), valueChangeAction, isEnabled, options); + } + + protected static bool RenderPopup( + GUIContent[] selectionOptions, + int value, + Action valueChangeAction = null, + GUIContent label = null, + bool? isEnabled = null) + { + if (label == null) + { + label = GUIContent.none; + } + + return RenderGenericEditor( + ref value, + () => EditorGUILayout.Popup(label, value, selectionOptions), + valueChangeAction, + isEnabled); + } + + protected static bool RenderSlider( + float minValue, + float maxValue, + float value, + Action valueChangeAction = null, + GUIContent label = null, + bool? isEnabled = null) + { + if (label == null) + { + label = GUIContent.none; + } + + return RenderGenericEditor( + ref value, + () => EditorGUILayout.Slider(label, value, minValue, maxValue), + valueChangeAction, + isEnabled); + } + + private static bool RenderGenericEditor( + ref T value, + Func valueEditFunction, + Action valueChangeAction = null, + bool? isEnabled = null) + { + bool isChanged; + if (valueEditFunction == null) + { + isChanged = false; + } + else + { + bool? wasEnabled; + if (isEnabled.HasValue) + { + wasEnabled = GUI.enabled; + GUI.enabled = isEnabled.Value; + } + else + { + wasEnabled = null; + } + + try + { + EditorGUI.BeginChangeCheck(); + try + { + value = valueEditFunction(); + } + finally + { + isChanged = EditorGUI.EndChangeCheck(); + } + + if (isChanged + && (valueChangeAction != null)) + { + valueChangeAction(value); + } + } + finally + { + if (wasEnabled.HasValue) + { + GUI.enabled = wasEnabled.Value; + } + } + } + + return isChanged; + } + + private static bool RenderGenericEditor( + ref T value, + Func valueEditFunction, + Action valueChangeAction, + bool? isEnabled = null) + { + Action valueChangeActionWrapped = null; + if (valueChangeAction != null) + { + valueChangeActionWrapped = (dummyValue) => valueChangeAction(); + } + + return RenderGenericEditor( + ref value, valueEditFunction, valueChangeActionWrapped, isEnabled); + } + + private string getSuggestedName() { + return suggestedName != null && suggestedName.Length > 0 ? suggestedName : "Audio"; + } +} diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor/SfxrGenerator.cs.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor/SfxrGenerator.cs.meta new file mode 100644 index 00000000..d259869e --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/Editor/SfxrGenerator.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9fd61eafd549871438902c3f466d460c +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrAudioPlayer.cs b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrAudioPlayer.cs new file mode 100644 index 00000000..6774b4a5 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrAudioPlayer.cs @@ -0,0 +1,125 @@ +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; + +#if UNITY_EDITOR +[ExecuteInEditMode] +#endif +public class SfxrAudioPlayer : MonoBehaviour { + + /** + * usfxr + * + * Copyright 2013 Zeh Fernando + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + /** + * SfxrAudioPlayer + * This is the (internal) behavior script responsible for streaming audio to the engine + * + * @author Zeh Fernando + */ + + + // Properties + private bool isDestroyed = false; // If true, this instance has been destroyed and shouldn't do anything yes + private bool needsToDestroy = false; // If true, it has been scheduled for destruction (from outside the main thread) + private bool runningInEditMode = false; // If true, it is running from the editor and NOT playing + + // Instances + private SfxrSynth sfxrSynth; // SfxrSynth instance that will generate the audio samples used by this + + + // ================================================================================================================ + // INTERNAL INTERFACE --------------------------------------------------------------------------------------------- + + void Start() { + // Creates an empty audio source so this GameObject can receive audio events + AudioSource soundSource = gameObject.AddComponent(); + soundSource.clip = new AudioClip(); + soundSource.volume = 1f; + soundSource.pitch = 1f; + soundSource.priority = 128; + } + + void Update() { + // Destroys self in case it has been queued for deletion + if (sfxrSynth == null) { + // Rogue object (leftover) + // When switching between play and edit mode while the sound is playing, the object is restarted + // So, queues for destruction + needsToDestroy = true; + } + + if (needsToDestroy) { + needsToDestroy = false; + Destroy(); + } + } + + void OnAudioFilterRead(float[] __data, int __channels) { + // Requests the generation of the needed audio data from SfxrSynth + + if (!isDestroyed && !needsToDestroy && sfxrSynth != null) { + bool hasMoreSamples = sfxrSynth.GenerateAudioFilterData(__data, __channels); + + // If no more samples are needed, there's no more need for this GameObject so schedule a destruction (cannot do this in this thread) + if (!hasMoreSamples) { + needsToDestroy = true; + if (runningInEditMode) { + // When running in edit mode, Update() is not called on every frame + // We can't call Destroy() directly either, since Destroy() must be ran from the main thread + // So we just attach our Update() to the editor's update event + #if UNITY_EDITOR + EditorApplication.update += Update; + #endif + } + } + } + } + + + // ================================================================================================================ + // PUBLIC INTERFACE ----------------------------------------------------------------------------------------------- + + public void SetSfxrSynth(SfxrSynth __sfxrSynth) { + // Sets the SfxrSynth instance that will generate the audio samples used by this + sfxrSynth = __sfxrSynth; + } + + public void SetRunningInEditMode(bool __runningInEditMode) { + // Sets the SfxrSynth instance that will generate the audio samples used by this + runningInEditMode = __runningInEditMode; + } + + public void Destroy() { + // Stops audio immediately and destroys self + if (!isDestroyed) { + isDestroyed = true; + sfxrSynth = null; + if (runningInEditMode || !Application.isPlaying) { + // Since we're running in the editor, we need to remove the update event, AND destroy immediately + #if UNITY_EDITOR + EditorApplication.update -= Update; + #endif + UnityEngine.Object.DestroyImmediate(gameObject); + } else { + UnityEngine.Object.Destroy(gameObject); + } + } + } +} diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrAudioPlayer.cs.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrAudioPlayer.cs.meta new file mode 100644 index 00000000..5324071a --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrAudioPlayer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 47e4830592571a54e9860971f86054d9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrCacheSurrogate.cs b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrCacheSurrogate.cs new file mode 100644 index 00000000..15353f55 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrCacheSurrogate.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; +using UnityEngine; + +public class SfxrCacheSurrogate : MonoBehaviour { + + /** + * usfxr + * + * Copyright 2013 Zeh Fernando + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + /** + * SfxrCacheSurrogate + * This is the (internal) behavior script responsible for calling Coroutines for asynchronous audio generation + * + * @author Zeh Fernando + */ + + // ================================================================================================================ + // PUBLIC INTERFACE ----------------------------------------------------------------------------------------------- + + public void CacheSound(SfxrSynth __synth, Action __callback) { + StartCoroutine(CacheSoundAsynchronously(__synth, __callback)); + } + + private IEnumerator CacheSoundAsynchronously(SfxrSynth __synth, Action __callback) { + yield return null; + __synth.CacheSound(null, true); + __callback(); + UnityEngine.Object.Destroy(gameObject); + } + + public void CacheMutations(SfxrSynth __synth, uint __mutationsNum, float __mutationAmount, Action __callback) { + StartCoroutine(CacheMutationsAsynchronously(__synth, __mutationsNum, __mutationAmount, __callback)); + } + + private IEnumerator CacheMutationsAsynchronously(SfxrSynth __synth, uint __mutationsNum, float __mutationAmount, Action __callback) { + yield return null; + __synth.CacheMutations(__mutationsNum, __mutationAmount, null, true); + __callback(); + UnityEngine.Object.Destroy(gameObject); + } +} diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrCacheSurrogate.cs.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrCacheSurrogate.cs.meta new file mode 100644 index 00000000..fea54ed8 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrCacheSurrogate.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 084cd040515e00d4e9456ef01b29f022 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrParams.cs b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrParams.cs new file mode 100644 index 00000000..3d7c7542 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrParams.cs @@ -0,0 +1,882 @@ +using UnityEngine; + +public class SfxrParams { + + /** + * SfxrSynth + * + * Copyright 2010 Thomas Vian + * Copyright 2013 Zeh Fernando + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + /** + * SfxrParams + * Holds parameters used by SfxrSynth + * + * @author Zeh Fernando + */ + + // Properties + public bool paramsDirty; // Whether the parameters have been changed since last time (shouldn't used cached sound) + + private uint _waveType = 0; // Shape of wave to generate (see enum WaveType) + + private float _masterVolume = 0.5f; // Overall volume of the sound (0 to 1) + + private float _attackTime = 0.0f; // Length of the volume envelope attack (0 to 1) + private float _sustainTime = 0.0f; // Length of the volume envelope sustain (0 to 1) + private float _sustainPunch = 0.0f; // Tilts the sustain envelope for more 'pop' (0 to 1) + private float _decayTime = 0.0f; // Length of the volume envelope decay (yes, I know it's called release) (0 to 1) + + private float _startFrequency = 0.0f; // Base note of the sound (0 to 1) + private float _minFrequency = 0.0f; // If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1) + + private float _slide = 0.0f; // Slides the note up or down (-1 to 1) + private float _deltaSlide = 0.0f; // Accelerates the slide (-1 to 1) + + private float _vibratoDepth = 0.0f; // Strength of the vibrato effect (0 to 1) + private float _vibratoSpeed = 0.0f; // Speed of the vibrato effect (i.e. frequency) (0 to 1) + + private float _changeAmount = 0.0f; // Shift in note, either up or down (-1 to 1) + private float _changeSpeed = 0.0f; // How fast the note shift happens (only happens once) (0 to 1) + + private float _squareDuty = 0.0f; // Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1) + private float _dutySweep = 0.0f; // Sweeps the duty up or down (-1 to 1) + + private float _repeatSpeed = 0.0f; // Speed of the note repeating - certain variables are reset each time (0 to 1) + + private float _phaserOffset = 0.0f; // Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1) + private float _phaserSweep = 0.0f; // Sweeps the phase up or down (-1 to 1) + + private float _lpFilterCutoff = 0.0f; // Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1) + private float _lpFilterCutoffSweep = 0.0f; // Sweeps the low-pass cutoff up or down (-1 to 1) + private float _lpFilterResonance = 0.0f; // Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1) + + private float _hpFilterCutoff = 0.0f; // Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1) + private float _hpFilterCutoffSweep = 0.0f; // Sweeps the high-pass cutoff up or down (-1 to 1) + + // From BFXR + private float _changeRepeat = 0.0f; // Pitch Jump Repeat Speed: larger Values means more pitch jumps, which can be useful for arpeggiation (0 to 1) + private float _changeAmount2 = 0.0f; // Shift in note, either up or down (-1 to 1) + private float _changeSpeed2 = 0.0f; // How fast the note shift happens (only happens once) (0 to 1) + + private float _compressionAmount = 0.0f; // Compression: pushes amplitudes together into a narrower range to make them stand out more. Very good for sound effects, where you want them to stick out against background music (0 to 1) + + private float _overtones = 0.0f; // Harmonics: overlays copies of the waveform with copies and multiples of its frequency. Good for bulking out or otherwise enriching the texture of the sounds (warning: this is the number 1 cause of usfxr slowdown!) (0 to 1) + private float _overtoneFalloff = 0.0f; // Harmonics falloff: the rate at which higher overtones should decay (0 to 1) + + private float _bitCrush = 0.0f; // Bit crush: resamples the audio at a lower frequency (0 to 1) + private float _bitCrushSweep = 0.0f; // Bit crush sweep: sweeps the Bit Crush filter up or down (-1 to 1) + + + // ================================================================================================================ + // ACCESSOR INTERFACE --------------------------------------------------------------------------------------------- + + /** Shape of the wave (0:square, 1:sawtooth, 2:sin, 3:noise) */ + public uint waveType { + get { return _waveType; } + set { _waveType = value > 8 ? 0 : value; paramsDirty = true; } + } + + /** Overall volume of the sound (0 to 1) */ + public float masterVolume { + get { return _masterVolume; } + set { _masterVolume = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Length of the volume envelope attack (0 to 1) */ + public float attackTime { + get { return _attackTime; } + set { _attackTime = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Length of the volume envelope sustain (0 to 1) */ + public float sustainTime { + get { return _sustainTime; } + set { _sustainTime = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Tilts the sustain envelope for more 'pop' (0 to 1) */ + public float sustainPunch { + get { return _sustainPunch; } + set { _sustainPunch = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Length of the volume envelope decay (yes, I know it's called release) (0 to 1) */ + public float decayTime { + get { return _decayTime; } + set { _decayTime = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Base note of the sound (0 to 1) */ + public float startFrequency { + get { return _startFrequency; } + set { _startFrequency = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1) */ + public float minFrequency { + get { return _minFrequency; } + set { _minFrequency = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Slides the note up or down (-1 to 1) */ + public float slide { + get { return _slide; } + set { _slide = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** Accelerates the slide (-1 to 1) */ + public float deltaSlide { + get { return _deltaSlide; } + set { _deltaSlide = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** Strength of the vibrato effect (0 to 1) */ + public float vibratoDepth { + get { return _vibratoDepth; } + set { _vibratoDepth = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Speed of the vibrato effect (i.e. frequency) (0 to 1) */ + public float vibratoSpeed { + get { return _vibratoSpeed; } + set { _vibratoSpeed = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Shift in note, either up or down (-1 to 1) */ + public float changeAmount { + get { return _changeAmount; } + set { _changeAmount = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** How fast the note shift happens (only happens once) (0 to 1) */ + public float changeSpeed { + get { return _changeSpeed; } + set { _changeSpeed = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1) */ + public float squareDuty { + get { return _squareDuty; } + set { _squareDuty = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Sweeps the duty up or down (-1 to 1) */ + public float dutySweep { + get { return _dutySweep; } + set { _dutySweep = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** Speed of the note repeating - certain variables are reset each time (0 to 1) */ + public float repeatSpeed { + get { return _repeatSpeed; } + set { _repeatSpeed = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1) */ + public float phaserOffset { + get { return _phaserOffset; } + set { _phaserOffset = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** Sweeps the phase up or down (-1 to 1) */ + public float phaserSweep { + get { return _phaserSweep; } + set { _phaserSweep = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1) */ + public float lpFilterCutoff { + get { return _lpFilterCutoff; } + set { _lpFilterCutoff = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Sweeps the low-pass cutoff up or down (-1 to 1) */ + public float lpFilterCutoffSweep { + get { return _lpFilterCutoffSweep; } + set { _lpFilterCutoffSweep = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1) */ + public float lpFilterResonance { + get { return _lpFilterResonance; } + set { _lpFilterResonance = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1) */ + public float hpFilterCutoff { + get { return _hpFilterCutoff; } + set { _hpFilterCutoff = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Sweeps the high-pass cutoff up or down (-1 to 1) */ + public float hpFilterCutoffSweep { + get { return _hpFilterCutoffSweep; } + set { _hpFilterCutoffSweep = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + // From BFXR + + /** Pitch Jump Repeat Speed: larger Values means more pitch jumps, which can be useful for arpeggiation (0 to 1) */ + public float changeRepeat { + get { return _changeRepeat; } + set { _changeRepeat = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Shift in note, either up or down (-1 to 1) */ + public float changeAmount2 { + get { return _changeAmount2; } + set { _changeAmount2 = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + /** How fast the note shift happens (only happens once) (0 to 1) */ + public float changeSpeed2 { + get { return _changeSpeed2; } + set { _changeSpeed2 = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Pushes amplitudes together into a narrower range to make them stand out more. Very good for sound effects, where you want them to stick out against background music (0 to 1) */ + public float compressionAmount { + get { return _compressionAmount; } + set { _compressionAmount = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Harmonics: overlays copies of the waveform with copies and multiples of its frequency. Good for bulking out or otherwise enriching the texture of the sounds (warning: this is the number 1 cause of bfxr slowdown!) (0 to 1) */ + public float overtones { + get { return _overtones; } + set { _overtones = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Harmonics falloff: The rate at which higher overtones should decay (0 to 1) */ + public float overtoneFalloff { + get { return _overtoneFalloff; } + set { _overtoneFalloff = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Bit crush: resamples the audio at a lower frequency (0 to 1) */ + public float bitCrush { + get { return _bitCrush; } + set { _bitCrush = Mathf.Clamp(value, 0, 1); paramsDirty = true; } + } + + /** Bit crush sweep: sweeps the Bit Crush filter up or down (-1 to 1) */ + public float bitCrushSweep { + get { return _bitCrushSweep; } + set { _bitCrushSweep = Mathf.Clamp(value, -1, 1); paramsDirty = true; } + } + + + // ================================================================================================================ + // PUBLIC INTERFACE ----------------------------------------------------------------------------------------------- + + // Generator methods + + /** + * Sets the parameters to generate a pickup/coin sound + */ + public void GeneratePickupCoin() { + resetParams(); + + _startFrequency = 0.4f + GetRandom() * 0.5f; + + _sustainTime = GetRandom() * 0.1f; + _decayTime = 0.1f + GetRandom() * 0.4f; + _sustainPunch = 0.3f + GetRandom() * 0.3f; + + if (GetRandomBool()) { + _changeSpeed = 0.5f + GetRandom() * 0.2f; + int cnum = (int)(GetRandom()*7f) + 1; + int cden = cnum + (int)(GetRandom()*7f) + 2; + _changeAmount = (float)cnum / (float)cden; + } + } + + /** + * Sets the parameters to generate a laser/shoot sound + */ + public void GenerateLaserShoot() { + resetParams(); + + _waveType = (uint)(GetRandom() * 3); + if (_waveType == 2 && GetRandomBool()) _waveType = (uint)(GetRandom() * 2f); + + _startFrequency = 0.5f + GetRandom() * 0.5f; + _minFrequency = _startFrequency - 0.2f - GetRandom() * 0.6f; + if (_minFrequency < 0.2f) _minFrequency = 0.2f; + + _slide = -0.15f - GetRandom() * 0.2f; + + if (GetRandom() < 0.33f) { + _startFrequency = 0.3f + GetRandom() * 0.6f; + _minFrequency = GetRandom() * 0.1f; + _slide = -0.35f - GetRandom() * 0.3f; + } + + if (GetRandomBool()) { + _squareDuty = GetRandom() * 0.5f; + _dutySweep = GetRandom() * 0.2f; + } else { + _squareDuty = 0.4f + GetRandom() * 0.5f; + _dutySweep = -GetRandom() * 0.7f; + } + + _sustainTime = 0.1f + GetRandom() * 0.2f; + _decayTime = GetRandom() * 0.4f; + if (GetRandomBool()) _sustainPunch = GetRandom() * 0.3f; + + if (GetRandom() < 0.33f) { + _phaserOffset = GetRandom() * 0.2f; + _phaserSweep = -GetRandom() * 0.2f; + } + + if (GetRandomBool()) _hpFilterCutoff = GetRandom() * 0.3f; + } + + /** + * Sets the parameters to generate an explosion sound + */ + public void GenerateExplosion() { + resetParams(); + + _waveType = 3; + + if (GetRandomBool()) { + _startFrequency = 0.1f + GetRandom() * 0.4f; + _slide = -0.1f + GetRandom() * 0.4f; + } else { + _startFrequency = 0.2f + GetRandom() * 0.7f; + _slide = -0.2f - GetRandom() * 0.2f; + } + + _startFrequency *= _startFrequency; + + if (GetRandom() < 0.2f) _slide = 0.0f; + if (GetRandom() < 0.33f) _repeatSpeed = 0.3f + GetRandom() * 0.5f; + + _sustainTime = 0.1f + GetRandom() * 0.3f; + _decayTime = GetRandom() * 0.5f; + _sustainPunch = 0.2f + GetRandom() * 0.6f; + + if (GetRandomBool()) { + _phaserOffset = -0.3f + GetRandom() * 0.9f; + _phaserSweep = -GetRandom() * 0.3f; + } + + if (GetRandom() < 0.33f) { + _changeSpeed = 0.6f + GetRandom() * 0.3f; + _changeAmount = 0.8f - GetRandom() * 1.6f; + } + } + + /** + * Sets the parameters to generate a powerup sound + */ + public void GeneratePowerup() { + resetParams(); + + if (GetRandomBool()) { + _waveType = 1; + } else { + _squareDuty = GetRandom() * 0.6f; + } + + if (GetRandomBool()) { + _startFrequency = 0.2f + GetRandom() * 0.3f; + _slide = 0.1f + GetRandom() * 0.4f; + _repeatSpeed = 0.4f + GetRandom() * 0.4f; + } else { + _startFrequency = 0.2f + GetRandom() * 0.3f; + _slide = 0.05f + GetRandom() * 0.2f; + + if (GetRandomBool()) { + _vibratoDepth = GetRandom() * 0.7f; + _vibratoSpeed = GetRandom() * 0.6f; + } + } + + _sustainTime = GetRandom() * 0.4f; + _decayTime = 0.1f + GetRandom() * 0.4f; + } + + /** + * Sets the parameters to generate a hit/hurt sound + */ + public void GenerateHitHurt() { + resetParams(); + + _waveType = (uint)(GetRandom() * 3f); + if (_waveType == 2) { + _waveType = 3; + } else if (_waveType == 0) { + _squareDuty = GetRandom() * 0.6f; + } + + _startFrequency = 0.2f + GetRandom() * 0.6f; + _slide = -0.3f - GetRandom() * 0.4f; + + _sustainTime = GetRandom() * 0.1f; + _decayTime = 0.1f + GetRandom() * 0.2f; + + if (GetRandomBool()) _hpFilterCutoff = GetRandom() * 0.3f; + } + + /** + * Sets the parameters to generate a jump sound + */ + public void GenerateJump() { + resetParams(); + + _waveType = 0; + _squareDuty = GetRandom() * 0.6f; + _startFrequency = 0.3f + GetRandom() * 0.3f; + _slide = 0.1f + GetRandom() * 0.2f; + + _sustainTime = 0.1f + GetRandom() * 0.3f; + _decayTime = 0.1f + GetRandom() * 0.2f; + + if (GetRandomBool()) _hpFilterCutoff = GetRandom() * 0.3f; + if (GetRandomBool()) _lpFilterCutoff = 1.0f - GetRandom() * 0.6f; + } + + /** + * Sets the parameters to generate a blip/select sound + */ + public void GenerateBlipSelect() { + resetParams(); + + _waveType = (uint)(GetRandom() * 2f); + if (_waveType == 0) _squareDuty = GetRandom() * 0.6f; + + _startFrequency = 0.2f + GetRandom() * 0.4f; + + _sustainTime = 0.1f + GetRandom() * 0.1f; + _decayTime = GetRandom() * 0.2f; + _hpFilterCutoff = 0.1f; + } + + /** + * Resets the parameters, used at the start of each generate function + */ + protected void resetParams() { + paramsDirty = true; + + _waveType = 0; + _startFrequency = 0.3f; + _minFrequency = 0.0f; + _slide = 0.0f; + _deltaSlide = 0.0f; + _squareDuty = 0.0f; + _dutySweep = 0.0f; + + _vibratoDepth = 0.0f; + _vibratoSpeed = 0.0f; + + _attackTime = 0.0f; + _sustainTime = 0.3f; + _decayTime = 0.4f; + _sustainPunch = 0.0f; + + _lpFilterResonance = 0.0f; + _lpFilterCutoff = 1.0f; + _lpFilterCutoffSweep = 0.0f; + _hpFilterCutoff = 0.0f; + _hpFilterCutoffSweep = 0.0f; + + _phaserOffset = 0.0f; + _phaserSweep = 0.0f; + + _repeatSpeed = 0.0f; + + _changeSpeed = 0.0f; + _changeAmount = 0.0f; + + // From BFXR + _changeRepeat = 0.0f; + _changeAmount2 = 0.0f; + _changeSpeed2 = 0.0f; + + _compressionAmount = 0.3f; + + _overtones = 0.0f; + _overtoneFalloff = 0.0f; + + _bitCrush = 0.0f; + _bitCrushSweep = 0.0f; + } + + + // Randomization methods + + /** + * Randomly adjusts the parameters ever so slightly + */ + public void Mutate(float __mutation = 0.05f) { + if (GetRandomBool()) startFrequency += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) minFrequency += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) slide += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) deltaSlide += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) squareDuty += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) dutySweep += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) vibratoDepth += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) vibratoSpeed += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) attackTime += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) sustainTime += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) decayTime += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) sustainPunch += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) lpFilterCutoff += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) lpFilterCutoffSweep += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) lpFilterResonance += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) hpFilterCutoff += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) hpFilterCutoffSweep += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) phaserOffset += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) phaserSweep += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) repeatSpeed += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) changeSpeed += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) changeAmount += GetRandom() * __mutation * 2f - __mutation; + + // From BFXR + if (GetRandomBool()) changeRepeat += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) changeAmount2 += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) changeSpeed2 += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) compressionAmount += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) overtones += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) overtoneFalloff += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) bitCrush += GetRandom() * __mutation * 2f - __mutation; + if (GetRandomBool()) bitCrushSweep += GetRandom() * __mutation * 2f - __mutation; + } + + /** + * Sets all parameters to random values + */ + public void Randomize() { + resetParams(); + + _waveType = (uint)(GetRandom() * 9f); + + _attackTime = Pow(GetRandom() * 2f - 1f, 4); + _sustainTime = Pow(GetRandom() * 2f - 1f, 2); + _sustainPunch = Pow(GetRandom() * 0.8f, 2); + _decayTime = GetRandom(); + + _startFrequency = (GetRandomBool()) ? Pow(GetRandom() * 2f - 1f, 2) : (Pow(GetRandom() * 0.5f, 3) + 0.5f); + _minFrequency = 0.0f; + + _slide = Pow(GetRandom() * 2f - 1f, 3); + _deltaSlide = Pow(GetRandom() * 2f - 1f, 3); + + _vibratoDepth = Pow(GetRandom() * 2f - 1f, 3); + _vibratoSpeed = GetRandom() * 2f - 1f; + + _changeAmount = GetRandom() * 2f - 1f; + _changeSpeed = GetRandom() * 2f - 1f; + + _squareDuty = GetRandom() * 2f - 1f; + _dutySweep = Pow(GetRandom() * 2f - 1f, 3); + + _repeatSpeed = GetRandom() * 2f - 1f; + + _phaserOffset = Pow(GetRandom() * 2f - 1f, 3); + _phaserSweep = Pow(GetRandom() * 2f - 1f, 3); + + _lpFilterCutoff = 1f - Pow(GetRandom(), 3); + _lpFilterCutoffSweep = Pow(GetRandom() * 2f - 1f, 3); + _lpFilterResonance = GetRandom() * 2f - 1f; + + _hpFilterCutoff = Pow(GetRandom(), 5); + _hpFilterCutoffSweep = Pow(GetRandom() * 2f - 1f, 5); + + if (_attackTime + _sustainTime + _decayTime < 0.2f) { + _sustainTime = 0.2f + GetRandom() * 0.3f; + _decayTime = 0.2f + GetRandom() * 0.3f; + } + + if ((_startFrequency > 0.7f && _slide > 0.2) || (_startFrequency < 0.2 && _slide < -0.05)) { + _slide = -_slide; + } + + if (_lpFilterCutoff < 0.1f && _lpFilterCutoffSweep < -0.05f) { + _lpFilterCutoffSweep = -_lpFilterCutoffSweep; + } + + // From BFXR + _changeRepeat = GetRandom(); + _changeAmount2 = GetRandom() * 2f - 1f; + _changeSpeed2 = GetRandom(); + + _compressionAmount = GetRandom(); + + _overtones = GetRandom(); + _overtoneFalloff = GetRandom(); + + _bitCrush = GetRandom(); + _bitCrushSweep = GetRandom() * 2f - 1f; + } + + + // Setting string methods + + /** + * Returns a string representation of the parameters for copy/paste sharing in the old format (24 parameters, SFXR/AS3SFXR compatible) + * @return A comma-delimited list of parameter values + */ + public string GetSettingsStringLegacy() { + string str = ""; + + // 24 params + + str += waveType.ToString() + ","; + str += To4DP(_attackTime) + ","; + str += To4DP(_sustainTime) + ","; + str += To4DP(_sustainPunch) + ","; + str += To4DP(_decayTime) + ","; + str += To4DP(_startFrequency) + ","; + str += To4DP(_minFrequency) + ","; + str += To4DP(_slide) + ","; + str += To4DP(_deltaSlide) + ","; + str += To4DP(_vibratoDepth) + ","; + str += To4DP(_vibratoSpeed) + ","; + str += To4DP(_changeAmount) + ","; + str += To4DP(_changeSpeed) + ","; + str += To4DP(_squareDuty) + ","; + str += To4DP(_dutySweep) + ","; + str += To4DP(_repeatSpeed) + ","; + str += To4DP(_phaserOffset) + ","; + str += To4DP(_phaserSweep) + ","; + str += To4DP(_lpFilterCutoff) + ","; + str += To4DP(_lpFilterCutoffSweep) + ","; + str += To4DP(_lpFilterResonance) + ","; + str += To4DP(_hpFilterCutoff) + ","; + str += To4DP(_hpFilterCutoffSweep) + ","; + str += To4DP(_masterVolume); + + return str; + } + + /** + * Returns a string representation of the parameters for copy/paste sharing in the new format (32 parameters, BFXR compatible) + * @return A comma-delimited list of parameter values + */ + public string GetSettingsString() { + string str = ""; + + // 32 params + + str += waveType.ToString() + ","; + str += To4DP(_masterVolume) + ","; + str += To4DP(_attackTime) + ","; + str += To4DP(_sustainTime) + ","; + str += To4DP(_sustainPunch) + ","; + str += To4DP(_decayTime) + ","; + str += To4DP(_compressionAmount) + ","; + str += To4DP(_startFrequency) + ","; + str += To4DP(_minFrequency) + ","; + str += To4DP(_slide) + ","; + str += To4DP(_deltaSlide) + ","; + str += To4DP(_vibratoDepth) + ","; + str += To4DP(_vibratoSpeed) + ","; + str += To4DP(_overtones) + ","; + str += To4DP(_overtoneFalloff) + ","; + str += To4DP(_changeRepeat) + ","; // _changeRepeat? + str += To4DP(_changeAmount) + ","; + str += To4DP(_changeSpeed) + ","; + str += To4DP(_changeAmount2) + ","; // changeamount2 + str += To4DP(_changeSpeed2) + ","; // changespeed2 + str += To4DP(_squareDuty) + ","; + str += To4DP(_dutySweep) + ","; + str += To4DP(_repeatSpeed) + ","; + str += To4DP(_phaserOffset) + ","; + str += To4DP(_phaserSweep) + ","; + str += To4DP(_lpFilterCutoff) + ","; + str += To4DP(_lpFilterCutoffSweep) + ","; + str += To4DP(_lpFilterResonance) + ","; + str += To4DP(_hpFilterCutoff) + ","; + str += To4DP(_hpFilterCutoffSweep) + ","; + str += To4DP(_bitCrush) + ","; + str += To4DP(_bitCrushSweep); + + return str; + } + + /** + * Parses a settings string into the parameters + * @param string Settings string to parse + * @return If the string successfully parsed + */ + public bool SetSettingsString(string __string) { + string[] values = __string.Split(new char[] { ',' }); + + if (values.Length == 24) { + // Old format (SFXR): 24 parameters + resetParams(); + + waveType = ParseUint(values[0]); + attackTime = ParseFloat(values[1]); + sustainTime = ParseFloat(values[2]); + sustainPunch = ParseFloat(values[3]); + decayTime = ParseFloat(values[4]); + startFrequency = ParseFloat(values[5]); + minFrequency = ParseFloat(values[6]); + slide = ParseFloat(values[7]); + deltaSlide = ParseFloat(values[8]); + vibratoDepth = ParseFloat(values[9]); + vibratoSpeed = ParseFloat(values[10]); + changeAmount = ParseFloat(values[11]); + changeSpeed = ParseFloat(values[12]); + squareDuty = ParseFloat(values[13]); + dutySweep = ParseFloat(values[14]); + repeatSpeed = ParseFloat(values[15]); + phaserOffset = ParseFloat(values[16]); + phaserSweep = ParseFloat(values[17]); + lpFilterCutoff = ParseFloat(values[18]); + lpFilterCutoffSweep = ParseFloat(values[19]); + lpFilterResonance = ParseFloat(values[20]); + hpFilterCutoff = ParseFloat(values[21]); + hpFilterCutoffSweep = ParseFloat(values[22]); + masterVolume = ParseFloat(values[23]); + } else if (values.Length >= 32) { + // New format (BFXR): 32 parameters (or more, but locked parameters are ignored) + resetParams(); + + waveType = ParseUint(values[0]); + masterVolume = ParseFloat(values[1]); + attackTime = ParseFloat(values[2]); + sustainTime = ParseFloat(values[3]); + sustainPunch = ParseFloat(values[4]); + decayTime = ParseFloat(values[5]); + compressionAmount = ParseFloat(values[6]); + startFrequency = ParseFloat(values[7]); + minFrequency = ParseFloat(values[8]); + slide = ParseFloat(values[9]); + deltaSlide = ParseFloat(values[10]); + vibratoDepth = ParseFloat(values[11]); + vibratoSpeed = ParseFloat(values[12]); + overtones = ParseFloat(values[13]); + overtoneFalloff = ParseFloat(values[14]); + changeRepeat = ParseFloat(values[15]); + changeAmount = ParseFloat(values[16]); + changeSpeed = ParseFloat(values[17]); + changeAmount2 = ParseFloat(values[18]); + changeSpeed2 = ParseFloat(values[19]); + squareDuty = ParseFloat(values[20]); + dutySweep = ParseFloat(values[21]); + repeatSpeed = ParseFloat(values[22]); + phaserOffset = ParseFloat(values[23]); + phaserSweep = ParseFloat(values[24]); + lpFilterCutoff = ParseFloat(values[25]); + lpFilterCutoffSweep = ParseFloat(values[26]); + lpFilterResonance = ParseFloat(values[27]); + hpFilterCutoff = ParseFloat(values[28]); + hpFilterCutoffSweep = ParseFloat(values[29]); + bitCrush = ParseFloat(values[30]); + bitCrushSweep = ParseFloat(values[31]); + } else { + Debug.LogError("Could not paste settings string: parameters contain " + values.Length + " values (was expecting 24 or >32)"); + return false; + } + + return true; + } + + + // Copying methods + + /** + * Returns a copy of this SfxrParams with all settings duplicated + * @return A copy of this SfxrParams + */ + public SfxrParams Clone() { + SfxrParams outp = new SfxrParams(); + outp.CopyFrom(this); + + return outp; + } + + /** + * Copies parameters from another instance + * @param params Instance to copy parameters from + */ + public void CopyFrom(SfxrParams __params, bool __makeDirty = false) { + bool wasDirty = paramsDirty; + SetSettingsString(GetSettingsString()); + paramsDirty = wasDirty || __makeDirty; + } + + + // Utility methods + + /** + * Faster power function; this function takes about 36% of the time Mathf.Pow() would take in our use cases + * @param base Base to raise to power + * @param power Power to raise base by + * @return The calculated power + */ + private float Pow(float __pbase, int __power) { + switch(__power) { + case 2: return __pbase * __pbase; + case 3: return __pbase * __pbase * __pbase; + case 4: return __pbase * __pbase * __pbase * __pbase; + case 5: return __pbase * __pbase * __pbase * __pbase * __pbase; + } + + return 1f; + } + + + // ================================================================================================================ + // INTERNAL INTERFACE --------------------------------------------------------------------------------------------- + + /** + * Returns the number as a string to 4 decimal places + * @param value Number to convert + * @return Number to 4dp as a string + */ + private string To4DP(float __value) { + if (__value < 0.0001f && __value > -0.0001f) return ""; + return __value.ToString("#.####"); + } + + /** + * Parses a string into an uint value; also returns 0 if the string is empty, rather than an error + */ + private uint ParseUint(string __value) { + if (__value.Length == 0) return 0; + return uint.Parse(__value); + } + + /** + * Parses a string into a float value; also returns 0 if the string is empty, rather than an error + */ + private float ParseFloat(string __value) { + if (__value.Length == 0) return 0; + return float.Parse(__value); + } + + /** + * Returns a random value: 0 <= n < 1 + * This function is needed so we can follow the original code more strictly; Unity's Random.value returns 0 <= n <= 1 + */ + private float GetRandom() { + return UnityEngine.Random.value % 1; + } + + /** + * Returns a boolean value + */ + private bool GetRandomBool() { + return UnityEngine.Random.value > 0.5f; + } +} \ No newline at end of file diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrParams.cs.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrParams.cs.meta new file mode 100644 index 00000000..5d36417d --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrParams.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9b74ec2f9e7c92e46ac8f3d36c3df2a5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrSynth.cs b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrSynth.cs new file mode 100644 index 00000000..f635e5a5 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrSynth.cs @@ -0,0 +1,1090 @@ +using System; +using UnityEngine; + +public class SfxrSynth { + + /** + * SfxrSynth + * + * Copyright 2010 Thomas Vian + * Copyright 2013 Zeh Fernando + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + /** + * SfxrSynth + * Generates and plays all necessary audio + * + * @author Zeh Fernando + */ + + + // Constants + private const int LO_RES_NOISE_PERIOD = 8; // Should be < 32 + private enum Endian { + BIG_ENDIAN, + LITTLE_ENDIAN + } + + // Unity specific objects + private SfxrParams _params = new SfxrParams(); // Params instance + + private GameObject _gameObject; // Game object that will contain the audio player script + private SfxrAudioPlayer _audioPlayer; // Audio player script that will be attached to a GameObject to play the sound + + private Transform _parentTransform; // Parent that will contain the audio (for positional audio) + + // Sound properties + private bool _mutation; // If the current sound playing or caching is a mutation + + private float[] _cachedWave; // Cached wave data from a cacheSound() call + private uint _cachedWavePos; // Equivalent to _cachedWave.position in the old code + private bool _cachingNormal; // If the synth is caching a normal sound + + private int _cachingMutation; // Current caching ID + private float[] _cachedMutation; // Current caching wave data for mutation + private uint _cachedMutationPos; // Equivalent to _cachedMutation.position in the old code + private float[][] _cachedMutations; // Cached mutated wave data + private uint _cachedMutationsNum; // Number of cached mutations + private float _cachedMutationAmount; // Amount to mutate during cache + + private bool _cachingAsync; // If the synth is currently caching asynchronously + + private float[] _waveData; // Full wave, read out in chuncks by the onSampleData method + private uint _waveDataPos; // Current position in the waveData + + private SfxrParams _original; // Copied properties for mutation base + + // Synth properies + private bool _finished; // If the sound has finished + + private float _masterVolume; // masterVolume * masterVolume (for quick calculations) + + private uint _waveType; // Shape of wave to generate (see enum WaveType) + + private float _envelopeVolume; // Current volume of the envelope + private int _envelopeStage; // Current stage of the envelope (attack, sustain, decay, end) + private float _envelopeTime; // Current time through current enelope stage + private float _envelopeLength; // Length of the current envelope stage + private float _envelopeLength0; // Length of the attack stage + private float _envelopeLength1; // Length of the sustain stage + private float _envelopeLength2; // Length of the decay stage + private float _envelopeOverLength0; // 1 / _envelopeLength0 (for quick calculations) + private float _envelopeOverLength1; // 1 / _envelopeLength1 (for quick calculations) + private float _envelopeOverLength2; // 1 / _envelopeLength2 (for quick calculations) + private uint _envelopeFullLength; // Full length of the volume envelop (and therefore sound) + + private float _sustainPunch; // The punch factor (louder at begining of sustain) + + private int _phase; // Phase through the wave + private float _pos; // Phase expresed as a Number from 0-1, used for fast sin approx + private float _period; // Period of the wave + private float _periodTemp; // Period modified by vibrato + private int _periodTempInt; // Period modified by vibrato (as an Int) + private float _maxPeriod; // Maximum period before sound stops (from minFrequency) + + private float _slide; // Note slide + private float _deltaSlide; // Change in slide + private float _minFrequency; // Minimum frequency before stopping + + private float _vibratoPhase; // Phase through the vibrato sine wave + private float _vibratoSpeed; // Speed at which the vibrato phase moves + private float _vibratoAmplitude; // Amount to change the period of the wave by at the peak of the vibrato wave + + private float _changeAmount; // Amount to change the note by + private int _changeTime; // Counter for the note change + private int _changeLimit; // Once the time reaches this limit, the note changes + + private float _squareDuty; // Offset of center switching point in the square wave + private float _dutySweep; // Amount to change the duty by + + private int _repeatTime; // Counter for the repeats + private int _repeatLimit; // Once the time reaches this limit, some of the variables are reset + + private bool _phaser; // If the phaser is active + private float _phaserOffset; // Phase offset for phaser effect + private float _phaserDeltaOffset; // Change in phase offset + private int _phaserInt; // Integer phaser offset, for bit maths + private int _phaserPos; // Position through the phaser buffer + private float[] _phaserBuffer; // Buffer of wave values used to create the out of phase second wave + + private bool _filters; // If the filters are active + private float _lpFilterPos; // Adjusted wave position after low-pass filter + private float _lpFilterOldPos; // Previous low-pass wave position + private float _lpFilterDeltaPos; // Change in low-pass wave position, as allowed by the cutoff and damping + private float _lpFilterCutoff; // Cutoff multiplier which adjusts the amount the wave position can move + private float _lpFilterDeltaCutoff; // Speed of the low-pass cutoff multiplier + private float _lpFilterDamping; // Damping muliplier which restricts how fast the wave position can move + private bool _lpFilterOn; // If the low pass filter is active + + private float _hpFilterPos; // Adjusted wave position after high-pass filter + private float _hpFilterCutoff; // Cutoff multiplier which adjusts the amount the wave position can move + private float _hpFilterDeltaCutoff; // Speed of the high-pass cutoff multiplier + + // From BFXR + private float _changePeriod; + private int _changePeriodTime; + + private bool _changeReached; + + private float _changeAmount2; // Amount to change the note by + private int _changeTime2; // Counter for the note change + private int _changeLimit2; // Once the time reaches this limit, the note changes + private bool _changeReached2; + + private int _overtones; // Minimum frequency before stopping + private float _overtoneFalloff; // Minimum frequency before stopping + + private float _bitcrushFreq; // Inversely proportional to the number of samples to skip + private float _bitcrushFreqSweep; // Change of the above + private float _bitcrushPhase; // Samples when this > 1 + private float _bitcrushLast; // Last sample value + + private float _compressionFactor; + + // Pre-calculated data + private float[] _noiseBuffer; // Buffer of random values used to generate noise + private float[] _pinkNoiseBuffer; // Buffer of random values used to generate pink noise + private PinkNumber _pinkNumber; // Used to generate pink noise + private float[] _loResNoiseBuffer; // Buffer of random values used to generate Tan waveform + + // Temp + private float _superSample; // Actual sample writen to the wave + private float _sample; // Sub-sample calculated 8 times per actual sample, averaged out to get the super sample + private float _sample2; // Used in other calculations + private float amp; // Used in other calculations + + private System.Random randomGenerator = new System.Random(); // Used to generate random numbers safely + + + // ================================================================================================================ + // ACCESSOR INTERFACE --------------------------------------------------------------------------------------------- + + /** The sound parameters */ + public SfxrParams parameters { + get { return _params; } + set { _params = value; _params.paramsDirty = true; } + } + + + // ================================================================================================================ + // PUBLIC INTERFACE ----------------------------------------------------------------------------------------------- + + /** + * Plays the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later. + * If they're not, plays from the cached sound. + * Won't play if caching asynchronously. + */ + public void Play() { + if (_cachingAsync) return; + + Stop(); + + _mutation = false; + + if (_params.paramsDirty || _cachingNormal || _cachedWave == null) { + // Needs to cache new data + _cachedWavePos = 0; + _cachingNormal = true; + _waveData = null; + Reset(true); + _cachedWave = new float[_envelopeFullLength]; + } else { + // Play from cached data + _waveData = _cachedWave; + _waveDataPos = 0; + } + + createGameObject(); + + } + + /** + * Plays a mutation of the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later. + * If they're not, plays from the cached sound. + * Won't play if caching asynchronously. + * @param mutationAmount Amount of mutation + * @param mutationsNum The number of mutations to cache before picking from them + */ + public void PlayMutated(float __mutationAmount = 0.05f, uint __mutationsNum = 15) { + Stop(); + + if (_cachingAsync) return; + + _mutation = true; + + _cachedMutationsNum = __mutationsNum; + + if (_params.paramsDirty || _cachedMutations == null) { + // New set of mutations + _cachedMutations = new float[_cachedMutationsNum][]; + _cachingMutation = 0; + } + + if (_cachingMutation != -1) { + // Continuing caching new mutations + Reset(true); // To get _envelopeFullLength + + _cachedMutation = new float[_envelopeFullLength]; + _cachedMutationPos = 0; + _cachedMutations[_cachingMutation] = _cachedMutation; + _waveData = null; + + _original = _params.Clone(); + _params.Mutate(__mutationAmount); + + Reset(true); + } else { + // Play from random cached mutation + _waveData = _cachedMutations[(uint)(_cachedMutations.Length * getRandom())]; + _waveDataPos = 0; + } + + createGameObject(); + } + + /** + * Stops the currently playing sound + */ + public void Stop() { + if (_audioPlayer != null) { + _audioPlayer.Destroy(); + _audioPlayer = null; + } + + if (_original != null) { + _params.CopyFrom(_original); + _original = null; + } + } + + private int WriteSamples(float[] __originSamples, int __originPos, float[] __targetSamples, int __targetChannels) { + // Writes raw samples to Unity's format and return number of samples actually written + int samplesToWrite = __targetSamples.Length / __targetChannels; + + if (__originPos + samplesToWrite > __originSamples.Length) samplesToWrite = __originSamples.Length - __originPos; + + if (samplesToWrite > 0) { + // Interlaced filling of sample datas (faster?) + int i, j; + for (i = 0; i < __targetChannels; i++) { + for (j = 0; j < samplesToWrite; j++) { + __targetSamples[(j * __targetChannels) + i] = __originSamples[j + __originPos]; + } + } + } + + return samplesToWrite; + } + + /** + * If there is a cached sound to play, reads out of the data. + * If there isn't, synthesises new chunch of data, caching it as it goes. + * @param data Float[] to write data to + * @param channels Number of channels used + * @return Whether it needs to continue (there are samples left) or not + */ + public bool GenerateAudioFilterData(float[] __data, int __channels) { + bool endOfSamples = false; + + if (_waveData != null) { + int samplesWritten = WriteSamples(_waveData, (int)_waveDataPos, __data, __channels); + _waveDataPos += (uint)samplesWritten; + if (samplesWritten == 0) endOfSamples = true; + } else { + if (_mutation) { + if (_original != null) { + _waveDataPos = _cachedMutationPos; + + int samplesNeeded = (int)Mathf.Min((__data.Length / __channels), _cachedMutation.Length - _cachedMutationPos); + + if (SynthWave(_cachedMutation, (int)_cachedMutationPos, (uint)samplesNeeded) || samplesNeeded == 0) { + // Finished + _params.CopyFrom(_original); + _original = null; + + _cachingMutation++; + + endOfSamples = true; + + if (_cachingMutation >= _cachedMutationsNum) _cachingMutation = -1; + } else { + _cachedMutationPos += (uint)samplesNeeded; + } + + WriteSamples(_cachedMutation, (int)_waveDataPos, __data, __channels); + } + } else { + if (_cachingNormal) { + _waveDataPos = _cachedWavePos; + + int samplesNeeded = (int)Mathf.Min((__data.Length / __channels), _cachedWave.Length - _cachedWavePos); + + if (SynthWave(_cachedWave, (int)_cachedWavePos, (uint)samplesNeeded) || samplesNeeded == 0) { + _cachingNormal = false; + endOfSamples = true; + } else { + _cachedWavePos += (uint)samplesNeeded; + } + + WriteSamples(_cachedWave, (int)_waveDataPos, __data, __channels); + } + } + } + + return !endOfSamples; + } + + + // Cache sound methods + + /** + * Cache the sound for speedy playback. + * If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds + * per frame to cache, them calling the callback when it's done. + * If not, the whole sound is cached immediately - can freeze the player for a few seconds, especially in debug mode. + * @param callback Function to call when the caching is complete + * @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame + */ + public void CacheSound(Action __callback = null, bool __isFromCoroutine = false) { + Stop(); + + if (_cachingAsync && !__isFromCoroutine) return; + + if (__callback != null) { + _mutation = false; + _cachingNormal = true; + _cachingAsync = true; + + GameObject _surrogateObj = new GameObject("SfxrGameObjectSurrogate-" + (Time.realtimeSinceStartup)); + SfxrCacheSurrogate _surrogate = _surrogateObj.AddComponent(); + _surrogate.CacheSound(this, __callback); + } else { + Reset(true); + + _cachedWave = new float[_envelopeFullLength]; + + SynthWave(_cachedWave, 0, _envelopeFullLength); + + _cachingNormal = false; + _cachingAsync = false; + } + } + + /** + * Caches a series of mutations on the source sound. + * If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds + * per frame to cache, them calling the callback when it's done. + * If not, the whole sound is cached immediately - can freeze the player for a few seconds, especially in debug mode. + * @param mutationsNum Number of mutations to cache + * @param mutationAmount Amount of mutation + * @param callback Function to call when the caching is complete + * @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame + */ + public void CacheMutations(uint __mutationsNum = 15, float __mutationAmount = 0.05f, Action __callback = null, bool __isFromCoroutine = false) { + Stop(); + + if (_cachingAsync && !__isFromCoroutine) return; + + _cachedMutationsNum = __mutationsNum; + _cachedMutations = new float[_cachedMutationsNum][]; + + if (__callback != null) { + _mutation = true; + _cachingAsync = true; + + GameObject _surrogateObj = new GameObject("SfxrGameObjectSurrogate-" + (Time.realtimeSinceStartup)); + SfxrCacheSurrogate _surrogate = _surrogateObj.AddComponent(); + _surrogate.CacheMutations(this, __mutationsNum, __mutationAmount, __callback); + } else { + Reset(true); + + SfxrParams original = _params.Clone(); + + for (uint i = 0; i < _cachedMutationsNum; i++) { + _params.Mutate(__mutationAmount); + CacheSound(); + _cachedMutations[i] = _cachedWave; + _params.CopyFrom(original); + } + + _cachingAsync = false; + _cachingMutation = -1; + } + } + + /** + * Sets the parent transform of this audio, for positional audio + * @param __transform The transform object of the parent + */ + public void SetParentTransform(Transform __transform) { + _parentTransform = __transform; + } + + /** + * Returns a ByteArray of the wave in the form of a .wav file, ready to be saved out + * @param __sampleRate Sample rate to generate the .wav data at (44100 or 22050, default 44100) + * @param __bitDepth Bit depth to generate the .wav at (8 or 16, default 16) + * @return Wave data (in .wav format) as a byte array + */ + public byte[] GetWavFile(uint __sampleRate = 44100, uint __bitDepth = 16) { + Stop(); + + Reset(true); + + if (__sampleRate != 44100) __sampleRate = 22050; + if (__bitDepth != 16) __bitDepth = 8; + + uint soundLength = _envelopeFullLength; + if (__bitDepth == 16) soundLength *= 2; + if (__sampleRate == 22050) soundLength /= 2; + + uint fileSize = 36 + soundLength; + uint blockAlign = __bitDepth / 8; + uint bytesPerSec = __sampleRate * blockAlign; + + // The file size is actually 8 bytes more than the fileSize + byte[] wav = new byte[fileSize + 8]; + + int bytePos = 0; + + // Header + + // Chunk ID "RIFF" + writeUintToBytes(wav, ref bytePos, 0x52494646, Endian.BIG_ENDIAN); + + // Chunck Data Size + writeUintToBytes(wav, ref bytePos, fileSize, Endian.LITTLE_ENDIAN); + + // RIFF Type "WAVE" + writeUintToBytes(wav, ref bytePos, 0x57415645, Endian.BIG_ENDIAN); + + // Format Chunk + + // Chunk ID "fmt " + writeUintToBytes(wav, ref bytePos, 0x666D7420, Endian.BIG_ENDIAN); + + // Chunk Data Size + writeUintToBytes(wav, ref bytePos, 16, Endian.LITTLE_ENDIAN); + + // Compression Code PCM + writeShortToBytes(wav, ref bytePos, 1, Endian.LITTLE_ENDIAN); + // Number of channels + writeShortToBytes(wav, ref bytePos, 1, Endian.LITTLE_ENDIAN); + // Sample rate + writeUintToBytes(wav, ref bytePos, __sampleRate, Endian.LITTLE_ENDIAN); + // Average bytes per second + writeUintToBytes(wav, ref bytePos, bytesPerSec, Endian.LITTLE_ENDIAN); + // Block align + writeShortToBytes(wav, ref bytePos, (short)blockAlign, Endian.LITTLE_ENDIAN); + // Significant bits per sample + writeShortToBytes(wav, ref bytePos, (short)__bitDepth, Endian.LITTLE_ENDIAN); + + // Data Chunk + + // Chunk ID "data" + writeUintToBytes(wav, ref bytePos, 0x64617461, Endian.BIG_ENDIAN); + // Chunk Data Size + writeUintToBytes(wav, ref bytePos, soundLength, Endian.LITTLE_ENDIAN); + + // Generate normal synth data + float[] audioData = new float[_envelopeFullLength]; + SynthWave(audioData, 0, _envelopeFullLength); + + // Write data as bytes + int sampleCount = 0; + float bufferSample = 0f; + for (int i = 0; i < audioData.Length; i++) { + bufferSample += audioData[i]; + sampleCount++; + + if (__sampleRate == 44100 || sampleCount == 2) { + bufferSample /= sampleCount; + sampleCount = 0; + + if (__bitDepth == 16) { + writeShortToBytes(wav, ref bytePos, (short)Math.Round(32000f * bufferSample), Endian.LITTLE_ENDIAN); + } else { + writeBytes(wav, ref bytePos, new byte[]{ (byte)(Math.Round(bufferSample * 127f) + 128) }, Endian.LITTLE_ENDIAN); + } + + bufferSample = 0f; + } + } + + return wav; + } + + + // ================================================================================================================ + // INTERNAL INTERFACE --------------------------------------------------------------------------------------------- + + /** + * Resets the runing variables from the params + * Used once at the start (total reset) and for the repeat effect (partial reset) + * @param totalReset If the reset is total + */ + private void Reset(bool __totalReset) { + // Shorter reference + SfxrParams p = _params; + + _period = 100.0f / (p.startFrequency * p.startFrequency + 0.001f); + _maxPeriod = 100.0f / (p.minFrequency * p.minFrequency + 0.001f); + + _slide = 1.0f - p.slide * p.slide * p.slide * 0.01f; + _deltaSlide = -p.deltaSlide * p.deltaSlide * p.deltaSlide * 0.000001f; + + if (p.waveType == 0) { + _squareDuty = 0.5f - p.squareDuty * 0.5f; + _dutySweep = -p.dutySweep * 0.00005f; + } + + _changePeriod = Mathf.Max(((1f - p.changeRepeat) + 0.1f) / 1.1f) * 20000f + 32f; + _changePeriodTime = 0; + + if (p.changeAmount > 0.0) { + _changeAmount = 1.0f - p.changeAmount * p.changeAmount * 0.9f; + } else { + _changeAmount = 1.0f + p.changeAmount * p.changeAmount * 10.0f; + } + + _changeTime = 0; + _changeReached = false; + + if (p.changeSpeed == 1.0f) { + _changeLimit = 0; + } else { + _changeLimit = (int)((1f - p.changeSpeed) * (1f - p.changeSpeed) * 20000f + 32f); + } + + if (p.changeAmount2 > 0f) { + _changeAmount2 = 1f - p.changeAmount2 * p.changeAmount2 * 0.9f; + } else { + _changeAmount2 = 1f + p.changeAmount2 * p.changeAmount2 * 10f; + } + + _changeTime2 = 0; + _changeReached2 = false; + + if (p.changeSpeed2 == 1.0f) { + _changeLimit2 = 0; + } else { + _changeLimit2 = (int)((1f - p.changeSpeed2) * (1f - p.changeSpeed2) * 20000f + 32f); + } + + _changeLimit = (int)(_changeLimit * ((1f - p.changeRepeat + 0.1f) / 1.1f)); + _changeLimit2 = (int)(_changeLimit2 * ((1f - p.changeRepeat + 0.1f) / 1.1f)); + + if (__totalReset) { + p.paramsDirty = false; + + _masterVolume = p.masterVolume * p.masterVolume; + + _waveType = p.waveType; + + if (p.sustainTime < 0.01) p.sustainTime = 0.01f; + + float totalTime = p.attackTime + p.sustainTime + p.decayTime; + if (totalTime < 0.18f) { + float multiplier = 0.18f / totalTime; + p.attackTime *= multiplier; + p.sustainTime *= multiplier; + p.decayTime *= multiplier; + } + + _sustainPunch = p.sustainPunch; + + _phase = 0; + + _overtones = (int)(p.overtones * 10f); + _overtoneFalloff = p.overtoneFalloff; + + _minFrequency = p.minFrequency; + + _bitcrushFreq = 1f - Mathf.Pow(p.bitCrush, 1f / 3f); + _bitcrushFreqSweep = -p.bitCrushSweep * 0.000015f; + _bitcrushPhase = 0; + _bitcrushLast = 0; + + _compressionFactor = 1f / (1f + 4f * p.compressionAmount); + + _filters = p.lpFilterCutoff != 1.0 || p.hpFilterCutoff != 0.0; + + _lpFilterPos = 0.0f; + _lpFilterDeltaPos = 0.0f; + _lpFilterCutoff = p.lpFilterCutoff * p.lpFilterCutoff * p.lpFilterCutoff * 0.1f; + _lpFilterDeltaCutoff = 1.0f + p.lpFilterCutoffSweep * 0.0001f; + _lpFilterDamping = 5.0f / (1.0f + p.lpFilterResonance * p.lpFilterResonance * 20.0f) * (0.01f + _lpFilterCutoff); + if (_lpFilterDamping > 0.8f) _lpFilterDamping = 0.8f; + _lpFilterDamping = 1.0f - _lpFilterDamping; + _lpFilterOn = p.lpFilterCutoff != 1.0f; + + _hpFilterPos = 0.0f; + _hpFilterCutoff = p.hpFilterCutoff * p.hpFilterCutoff * 0.1f; + _hpFilterDeltaCutoff = 1.0f + p.hpFilterCutoffSweep * 0.0003f; + + _vibratoPhase = 0.0f; + _vibratoSpeed = p.vibratoSpeed * p.vibratoSpeed * 0.01f; + _vibratoAmplitude = p.vibratoDepth * 0.5f; + + _envelopeVolume = 0.0f; + _envelopeStage = 0; + _envelopeTime = 0; + _envelopeLength0 = p.attackTime * p.attackTime * 100000.0f; + _envelopeLength1 = p.sustainTime * p.sustainTime * 100000.0f; + _envelopeLength2 = p.decayTime * p.decayTime * 100000.0f + 10f; + _envelopeLength = _envelopeLength0; + _envelopeFullLength = (uint)(_envelopeLength0 + _envelopeLength1 + _envelopeLength2); + + _envelopeOverLength0 = 1.0f / _envelopeLength0; + _envelopeOverLength1 = 1.0f / _envelopeLength1; + _envelopeOverLength2 = 1.0f / _envelopeLength2; + + _phaser = p.phaserOffset != 0.0f || p.phaserSweep != 0.0f; + + _phaserOffset = p.phaserOffset * p.phaserOffset * 1020.0f; + if (p.phaserOffset < 0.0f) _phaserOffset = -_phaserOffset; + _phaserDeltaOffset = p.phaserSweep * p.phaserSweep * p.phaserSweep * 0.2f; + _phaserPos = 0; + + if (_phaserBuffer == null) _phaserBuffer = new float[1024]; + if (_noiseBuffer == null) _noiseBuffer = new float[32]; + if (_pinkNoiseBuffer == null) _pinkNoiseBuffer = new float[32]; + if (_pinkNumber == null) _pinkNumber = new PinkNumber(); + if (_loResNoiseBuffer == null) _loResNoiseBuffer = new float[32]; + + uint i; + for (i = 0; i < 1024; i++) _phaserBuffer[i] = 0.0f; + for (i = 0; i < 32; i++) _noiseBuffer[i] = getRandom() * 2.0f - 1.0f; + for (i = 0; i < 32; i++) _pinkNoiseBuffer[i] = _pinkNumber.getNextValue(); + for (i = 0; i < 32; i++) _loResNoiseBuffer[i] = ((i % LO_RES_NOISE_PERIOD) == 0) ? getRandom() * 2.0f - 1.0f : _loResNoiseBuffer[i-1]; + + _repeatTime = 0; + + if (p.repeatSpeed == 0.0) { + _repeatLimit = 0; + } else { + _repeatLimit = (int)((1.0-p.repeatSpeed) * (1.0-p.repeatSpeed) * 20000) + 32; + } + } + } + + /** + * Writes the wave to the supplied buffer array of floats (it'll contain the mono audio) + * @param buffer A float[] to write the wave to + * @param waveData If the wave should be written for the waveData + * @return If the wave is finished + */ + private bool SynthWave(float[] __buffer, int __bufferPos, uint __length) { + _finished = false; + + int i, j, n, k; + int l = (int)__length; + float overtoneStrength, tempPhase, sampleTotal; + + for (i = 0; i < l; i++) { + if (_finished) return true; + + // Repeats every _repeatLimit times, partially resetting the sound parameters + if (_repeatLimit != 0) { + if (++_repeatTime >= _repeatLimit) { + _repeatTime = 0; + Reset(false); + } + } + + _changePeriodTime++; + if (_changePeriodTime >= _changePeriod) { + _changeTime = 0; + _changeTime2 = 0; + _changePeriodTime = 0; + if (_changeReached) { + _period /= _changeAmount; + _changeReached = false; + } + if (_changeReached2) { + _period /= _changeAmount2; + _changeReached2 = false; + } + } + + // If _changeLimit is reached, shifts the pitch + if (!_changeReached) { + if (++_changeTime >= _changeLimit) { + _changeReached = true; + _period *= _changeAmount; + } + } + + // If _changeLimit is reached, shifts the pitch + if (!_changeReached2) { + if (++_changeTime2 >= _changeLimit2) { + _changeReached2 = true; + _period *= _changeAmount2; + } + } + + // Acccelerate and apply slide + _slide += _deltaSlide; + _period *= _slide; + + // Checks for frequency getting too low, and stops the sound if a minFrequency was set + if (_period > _maxPeriod) { + _period = _maxPeriod; + if (_minFrequency > 0) _finished = true; + } + + _periodTemp = _period; + + // Applies the vibrato effect + if (_vibratoAmplitude > 0) { + _vibratoPhase += _vibratoSpeed; + _periodTemp = _period * (1.0f + Mathf.Sin(_vibratoPhase) * _vibratoAmplitude); + } + + _periodTempInt = (int)_periodTemp; + if (_periodTemp < 8) _periodTemp = _periodTempInt = 8; + + // Sweeps the square duty + if (_waveType == 0) { + _squareDuty += _dutySweep; + if (_squareDuty < 0.0) { + _squareDuty = 0.0f; + } else if (_squareDuty > 0.5) { + _squareDuty = 0.5f; + } + } + + // Moves through the different stages of the volume envelope + if (++_envelopeTime > _envelopeLength) { + _envelopeTime = 0; + + switch(++_envelopeStage) { + case 1: _envelopeLength = _envelopeLength1; break; + case 2: _envelopeLength = _envelopeLength2; break; + } + } + + // Sets the volume based on the position in the envelope + switch(_envelopeStage) { + case 0: _envelopeVolume = _envelopeTime * _envelopeOverLength0; break; + case 1: _envelopeVolume = 1.0f + (1.0f - _envelopeTime * _envelopeOverLength1) * 2.0f * _sustainPunch; break; + case 2: _envelopeVolume = 1.0f - _envelopeTime * _envelopeOverLength2; break; + case 3: _envelopeVolume = 0.0f; _finished = true; break; + } + + // Moves the phaser offset + if (_phaser) { + _phaserOffset += _phaserDeltaOffset; + _phaserInt = (int)_phaserOffset; + if (_phaserInt < 0) { + _phaserInt = -_phaserInt; + } else if (_phaserInt > 1023) { + _phaserInt = 1023; + } + } + + // Moves the high-pass filter cutoff + if (_filters && _hpFilterDeltaCutoff != 0) { + _hpFilterCutoff *= _hpFilterDeltaCutoff; + if (_hpFilterCutoff < 0.00001f) { + _hpFilterCutoff = 0.00001f; + } else if (_hpFilterCutoff > 0.1f) { + _hpFilterCutoff = 0.1f; + } + } + + _superSample = 0; + for (j = 0; j < 8; j++) { + // Cycles through the period + _phase++; + if (_phase >= _periodTempInt) { + _phase = _phase % _periodTempInt; + + // Generates new random noise for this period + if (_waveType == 3) { + // Noise + for (n = 0; n < 32; n++) _noiseBuffer[n] = getRandom() * 2.0f - 1.0f; + } else if (_waveType == 5) { + // Pink noise + for (n = 0; n < 32; n++) _pinkNoiseBuffer[n] = _pinkNumber.getNextValue(); + } else if (_waveType == 6) { + // Tan + for (n = 0; n < 32; n++) _loResNoiseBuffer[n] = ((n % LO_RES_NOISE_PERIOD) == 0) ? getRandom() * 2.0f - 1.0f : _loResNoiseBuffer[n-1]; + } + } + + _sample = 0; + sampleTotal = 0; + overtoneStrength = 1f; + + for (k = 0; k <= _overtones; k++) { + tempPhase = (float)((_phase * (k + 1))) % _periodTemp; + + // Gets the sample from the oscillator + switch (_waveType) { + case 0: + // Square + _sample = ((tempPhase / _periodTemp) < _squareDuty) ? 0.5f : -0.5f; + break; + case 1: + // Sawtooth + _sample = 1.0f - (tempPhase / _periodTemp) * 2.0f; + break; + case 2: + // Sine: fast and accurate approx + _pos = tempPhase / _periodTemp; + _pos = _pos > 0.5f ? (_pos - 1.0f) * 6.28318531f : _pos * 6.28318531f; + _sample = _pos < 0 ? 1.27323954f * _pos + 0.405284735f * _pos * _pos : 1.27323954f * _pos - 0.405284735f * _pos * _pos; + _sample = _sample < 0 ? 0.225f * (_sample * -_sample - _sample) + _sample : 0.225f * (_sample * _sample - _sample) + _sample; + break; + case 3: + // Noise + _sample = _noiseBuffer[(uint)(tempPhase * 32f / _periodTempInt) % 32]; + break; + case 4: + // Triangle + _sample = Math.Abs(1f - (tempPhase / _periodTemp) * 2f) - 1f; + break; + case 5: + // Pink noise + _sample = _pinkNoiseBuffer[(uint)(tempPhase * 32f / _periodTempInt) % 32]; + break; + case 6: + // Tan + _sample = (float)Math.Tan(Math.PI * tempPhase / _periodTemp); + break; + case 7: + // Whistle + // Sine wave code + _pos = tempPhase / _periodTemp; + _pos = _pos > 0.5f ? (_pos - 1.0f) * 6.28318531f : _pos * 6.28318531f; + _sample = _pos < 0 ? 1.27323954f * _pos + 0.405284735f * _pos * _pos : 1.27323954f * _pos - 0.405284735f * _pos * _pos; + _sample = 0.75f * (_sample < 0 ? 0.225f * (_sample * -_sample - _sample) + _sample : 0.225f * (_sample * _sample - _sample) + _sample); + // Then whistle (essentially an overtone with frequencyx20 and amplitude0.25 + _pos = ((tempPhase * 20f) % _periodTemp) / _periodTemp; + _pos = _pos > 0.5f ? (_pos - 1.0f) * 6.28318531f : _pos * 6.28318531f; + _sample2 = _pos < 0 ? 1.27323954f * _pos + .405284735f * _pos * _pos : 1.27323954f * _pos - 0.405284735f * _pos * _pos; + _sample += 0.25f * (_sample2 < 0 ? .225f * (_sample2 * -_sample2 - _sample2) + _sample2 : .225f * (_sample2 * _sample2 - _sample2) + _sample2); + break; + case 8: + // Breaker + amp = tempPhase / _periodTemp; + _sample = Math.Abs(1f - amp * amp * 2f) - 1f; + break; + } + + sampleTotal += overtoneStrength * _sample; + overtoneStrength *= (1f - _overtoneFalloff); + } + + _sample = sampleTotal; + + // Applies the low and high pass filters + if (_filters) { + _lpFilterOldPos = _lpFilterPos; + _lpFilterCutoff *= _lpFilterDeltaCutoff; + if (_lpFilterCutoff < 0.0) { + _lpFilterCutoff = 0.0f; + } else if (_lpFilterCutoff > 0.1) { + _lpFilterCutoff = 0.1f; + } + + if (_lpFilterOn) { + _lpFilterDeltaPos += (_sample - _lpFilterPos) * _lpFilterCutoff; + _lpFilterDeltaPos *= _lpFilterDamping; + } else { + _lpFilterPos = _sample; + _lpFilterDeltaPos = 0.0f; + } + + _lpFilterPos += _lpFilterDeltaPos; + + _hpFilterPos += _lpFilterPos - _lpFilterOldPos; + _hpFilterPos *= 1.0f - _hpFilterCutoff; + _sample = _hpFilterPos; + } + + // Applies the phaser effect + if (_phaser) { + _phaserBuffer[_phaserPos & 1023] = _sample; + _sample += _phaserBuffer[(_phaserPos - _phaserInt + 1024) & 1023]; + _phaserPos = (_phaserPos + 1) & 1023; + } + + _superSample += _sample; + } + + // Averages out the super samples and applies volumes + _superSample = _masterVolume * _envelopeVolume * _superSample * 0.125f; + + // Bit crush + _bitcrushPhase += _bitcrushFreq; + if (_bitcrushPhase > 1f) { + _bitcrushPhase = 0; + _bitcrushLast = _superSample; + } + _bitcrushFreq = Mathf.Max(Mathf.Min(_bitcrushFreq + _bitcrushFreqSweep, 1f), 0f); + + _superSample = _bitcrushLast; + + // Compressor + if (_superSample > 0f) { + _superSample = Mathf.Pow(_superSample, _compressionFactor); + } else { + _superSample = -Mathf.Pow(-_superSample, _compressionFactor); + } + + // BFXR leftover: + //if (_muted) { + // _superSample = 0; + //} + + // Clipping if too loud + if (_superSample < -1f) { + _superSample = -1f; + } else if (_superSample > 1f) { + _superSample = 1f; + } + + // Writes value to list, ignoring left/right sound channels (this is applied when filtering the audio later) + __buffer[i + __bufferPos] = _superSample; + } + + return false; + } + + private void createGameObject() { + // Create a game object to handle playback + _gameObject = new GameObject("SfxrGameObject-" + (Time.realtimeSinceStartup)); + fixGameObjectParent(); + + // Create actual audio player + _audioPlayer = _gameObject.AddComponent(); + _audioPlayer.SetSfxrSynth(this); + _audioPlayer.SetRunningInEditMode(Application.isEditor && !Application.isPlaying); + } + + + private void fixGameObjectParent() { + // Sets the parent of the game object to be the wanted object + Transform transformToUse = _parentTransform; + + // If no parent assigned, use main camera by default + if (transformToUse == null) transformToUse = Camera.main.transform; + + // If has any parent (assigned, or main camera exist) assigns it + if (transformToUse != null) _gameObject.transform.parent = transformToUse; + + // Center in parent (or scene if no parent) + _gameObject.transform.localPosition = new Vector3(0, 0, 0); + } + + /** + * Returns a random value: 0 <= n < 1 + * This function is needed so we can follow the original code more strictly; Unity's Random.value returns 0 <= n <= 1 + */ + private float getRandom() { + // We can't use Unity's Random.value because it cannot be called from a separate thread + // (We get the error "get_value can only be called from the main thread" when this is called to generate the sound data) + return (float)(randomGenerator.NextDouble() % 1); + } + + /** + * Writes a short (Int16) to a byte array. + * This is an aux function used when creating the WAV data. + */ + private void writeShortToBytes(byte[] __bytes, ref int __position, short __newShort, Endian __endian) { + writeBytes(__bytes, ref __position, new byte[2] { (byte)((__newShort >> 8) & 0xff), (byte)(__newShort & 0xff) }, __endian); + } + + /** + * Writes a uint (UInt32) to a byte array. + * This is an aux function used when creating the WAV data. + */ + private void writeUintToBytes(byte[] __bytes, ref int __position, uint __newUint, Endian __endian) { + writeBytes(__bytes, ref __position, new byte[4] { (byte)((__newUint >> 24) & 0xff), (byte)((__newUint >> 16) & 0xff), (byte)((__newUint >> 8) & 0xff), (byte)(__newUint & 0xff) }, __endian); + } + + /** + * Writes any number of bytes into a byte array, at a given position. + * This is an aux function used when creating the WAV data. + */ + private void writeBytes(byte[] __bytes, ref int __position, byte[] __newBytes, Endian __endian) { + // Writes __newBytes to __bytes at position __position, increasing the position depending on the length of __newBytes + for (int i = 0; i < __newBytes.Length; i++) { + __bytes[__position] = __newBytes[__endian == Endian.BIG_ENDIAN ? i : __newBytes.Length - i - 1]; + __position++; + } + } +} + + +// ================================================================================================================ +// AUX CLASSES ---------------------------------------------------------------------------------------------------- + +/* +Pink Number +----------- +From BFXR +Class taken from http://www.firstpr.com.au/dsp/pink-noise/#Filtering +*/ + +public class PinkNumber { + // Properties + private int max_key; + private int key; + private uint[] white_values; + private uint range; + private System.Random randomGenerator; + + // Temp + private float rangeBy5; + private int last_key; + private uint sum; + private int diff; + private int i; + + public PinkNumber() { + max_key = 0x1f; // Five bits set + range = 128; + rangeBy5 = (float)range / 5f; + key = 0; + white_values = new uint[5]; + randomGenerator = new System.Random(); + for (i = 0; i < 5; i++) white_values[i] = (uint)((randomGenerator.NextDouble() % 1) * rangeBy5); + } + + public float getNextValue() { + // Returns a number between -1 and 1 + last_key = key; + sum = 0; + + key++; + if (key > max_key) key = 0; + + // Exclusive-Or previous value with current value. This gives + // a list of bits that have changed. + diff = last_key ^ key; + sum = 0; + for (i = 0; i < 5; i++) { + // If bit changed get new random number for corresponding + // white_value + if ((diff & (1 << i)) > 0) white_values[i] = (uint)((randomGenerator.NextDouble() % 1) * rangeBy5);; + sum += white_values[i]; + } + return (float)sum / 64f - 1f; + } +}; diff --git a/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrSynth.cs.meta b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrSynth.cs.meta new file mode 100644 index 00000000..9a6c2568 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/Scripts/SfxrSynth.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 580964bf015f08146a3a3686c599e07d +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Fungus/Thirdparty/Usfxr/exampleScene.unity b/Assets/Fungus/Thirdparty/Usfxr/exampleScene.unity new file mode 100644 index 00000000..ea2e86ec Binary files /dev/null and b/Assets/Fungus/Thirdparty/Usfxr/exampleScene.unity differ diff --git a/Assets/Fungus/Thirdparty/Usfxr/exampleScene.unity.meta b/Assets/Fungus/Thirdparty/Usfxr/exampleScene.unity.meta new file mode 100644 index 00000000..c6c56a34 --- /dev/null +++ b/Assets/Fungus/Thirdparty/Usfxr/exampleScene.unity.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 745ab88c2c0cadc4ea99b7f15a185f5c +DefaultImporter: + userData: