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: