Exemple #1
0
 void OnDisable()
 {
     EditorApplication.update -= Update;
     Undo.undoRedoPerformed   -= OnUndoRedo;
     AudioPlaybackToolEditor.DestroyAudioHelper();
     Undo.postprocessModifications -= ApplyHelperEffects;
 }
 public static void Init()
 {
     // Get existing open window or if none, make a new one:
     window = GetWindow <AudioPlaybackToolEditor>();
     window.Show();
     window.titleContent.text = "JSAM Playback Tool";
     // Refresh window contents
     window.OnSelectionChange();
     lastWindowSize = Window.position.size;
 }
Exemple #3
0
        new protected void OnEnable()
        {
            base.OnEnable();
            myScript = target as AudioFileMusicObject;

            SetupIcons();
            EditorApplication.update += Update;
            Undo.undoRedoPerformed   += OnUndoRedo;
            AudioPlaybackToolEditor.CreateAudioHelper(myScript.GetFile(), true);
            Undo.postprocessModifications += ApplyHelperEffects;

            loopMode          = FindProp("loopMode");
            clampToLoopPoints = FindProp("clampToLoopPoints");
            loopStartProperty = FindProp("loopStart");
            loopEndProperty   = FindProp("loopEnd");
        }
        private void OnDisable()
        {
            window = null;

            if (selectedSound)
            {
                Selection.activeObject       = null;
                EditorApplication.delayCall += () => Selection.activeObject = selectedSound;
            }
            else if (selectedMusic)
            {
                Selection.activeObject       = null;
                EditorApplication.delayCall += () => Selection.activeObject = selectedMusic;
            }

            DestroyAudioHelper();
        }
        public AudioClip DesignateRandomAudioClip()
        {
            AudioClip theClip = playingClip;

            if (!asset.IsLibraryEmpty())
            {
                List <AudioClip> files = asset.GetFiles();
                while (theClip == null || theClip == playingClip)
                {
                    theClip = files[Random.Range(0, files.Count)];
                }
            }
            playingClip   = theClip;
            playingRandom = true;
            AudioPlaybackToolEditor.DoForceRepaint(true);
            return(playingClip);
        }
        void Update()
        {
            if (asset == null)
            {
                return;                // This can happen on the same frame it's deleted
            }
            AudioClip clip = asset.GetFirstAvailableFile();

            if (playingClip != null && clip != null)
            {
                if (!AudioPlaybackToolEditor.WindowOpen)
                {
                    if (clip != cachedClip)
                    {
                        AudioPlaybackToolEditor.DoForceRepaint(true);
                        cachedClip  = asset.GetFirstAvailableFile();
                        playingClip = cachedClip;
                    }

                    if (!clipPlaying && playingRandom)
                    {
                        DesignateActiveAudioClip(asset);
                    }
                }

                if (clipPlaying)
                {
                    Repaint();
                }

                if (asset.fadeMode != FadeMode.None)
                {
                    HandleFading(asset);
                }
                else
                {
                    AudioPlaybackToolEditor.helperSource.volume = asset.relativeVolume;
                }
            }
            clipPlaying = (playingClip != null && AudioPlaybackToolEditor.helperSource.isPlaying);
        }
        new protected void OnEnable()
        {
            base.OnEnable();

            EditorApplication.update      += Update;
            Undo.undoRedoPerformed        += OnUndoRedo;
            Undo.postprocessModifications += ApplyHelperEffects;

            if (target.name.Length > 0) // Creating from right-click dialog throws error here because name is invalid when first selected
            {
                //safeName.stringValue = JSAMEditorHelper.ConvertToAlphanumeric(target.name);
            }

            neverRepeat = serializedObject.FindProperty("neverRepeat");

            fadeInDuration  = serializedObject.FindProperty("fadeInDuration");
            fadeOutDuration = serializedObject.FindProperty("fadeOutDuration");

            openIcon = EditorGUIUtility.TrIconContent("d_ScaleTool", "Click to open Playback Preview in a standalone window");

            AudioPlaybackToolEditor.CreateAudioHelper(asset.GetFirstAvailableFile());
        }
Exemple #8
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            RenderPresetDescription();

            EditorGUILayout.Space();

            RenderGeneratePresetButton();

            List <string> propertiesToExclude = new List <string>()
            {
                "spatialSound", "loopSound", "priority",
                "pitchShift", "loopMode", "fadeMode", "playReversed", "safeName", "maxDistance"
            };

            if (myScript.GetFile() == null)
            {
                propertiesToExclude.AddRange(new List <string>()
                {
                    "m_Script", "useLibrary", "files", "relativeVolume",
                    "fadeMode", "clampBetweenLoopPoints", "startingPitch", "playReversed", "spatialize", "ignoreTimeScale",
                    "delay",
                });
            }
            else
            {
                propertiesToExclude.AddRange(new List <string>()
                {
                    "m_Script", "useLibrary", "files", "maxDistance"
                });
            }

            DrawPropertiesExcluding(serializedObject, propertiesToExclude.ToArray());

            if (myScript.GetFile() == null)
            {
                EditorGUILayout.HelpBox("Error! Add an audio file before running!", MessageType.Error);
            }
            if (myScript.name.Contains("NEW AUDIO FILE") || myScript.name.Equals("None") || myScript.name.Equals("GameObject"))
            {
                EditorGUILayout.HelpBox("Warning! Change the name of this file to something different or things will break!", MessageType.Warning);
            }

            #region Loop Point Tools
            if (myScript.GetFile() != null)
            {
                float loopStart = myScript.loopStart;
                float loopEnd   = myScript.loopEnd;

                AudioClip music = myScript.GetFile();
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(loopMode);
                if (EditorGUI.EndChangeCheck())
                {
                    // This won't do, reset loop point positions
                    if (myScript.loopStart >= myScript.loopEnd)
                    {
                        loopStartProperty.floatValue = 0;
                        loopEndProperty.floatValue   = music.length;
                        loopStart = myScript.loopStart;
                        loopEnd   = myScript.loopEnd;
                        serializedObject.ApplyModifiedProperties();
                    }
                }
                using (new EditorGUI.DisabledScope(myScript.loopMode != LoopMode.LoopWithLoopPoints))
                    EditorGUILayout.PropertyField(clampToLoopPoints);

                if (!AudioPlaybackToolEditor.WindowOpen)
                {
                    DrawPlaybackTool(myScript);
                }

                using (new EditorGUI.DisabledScope(myScript.loopMode != LoopMode.LoopWithLoopPoints))
                {
                    blontent = new GUIContent("Loop Point Tools", "Customize where music will loop between. " +
                                              "Loops may not appear to be seamless in the inspector but rest assured, they will be seamless in-game!");
                    showLoopPointTool = EditorCompatability.SpecialFoldouts(showLoopPointTool, blontent);
                    if (showLoopPointTool && myScript.loopMode == LoopMode.LoopWithLoopPoints)
                    {
                        GUIContent[] contents = new GUIContent[] { new GUIContent("Slider"), new GUIContent("Time"), new GUIContent("Samples"), new GUIContent("BPM") };
                        EditorGUILayout.BeginHorizontal();
                        Color colorbackup = GUI.backgroundColor;
                        for (int i = 0; i < contents.Length; i++)
                        {
                            if (i == loopPointInputMode)
                            {
                                GUI.backgroundColor = buttonPressedColorLighter;
                            }
                            if (GUILayout.Button(contents[i], EditorStyles.miniButtonMid))
                            {
                                loopPointInputMode = i;
                            }
                            GUI.backgroundColor = colorbackup;
                        }
                        EditorGUILayout.EndHorizontal();

                        switch ((LoopPointTool)loopPointInputMode)
                        {
                        case LoopPointTool.Slider:
                            GUILayout.Label("Song Duration Samples: " + music.samples);
                            EditorGUILayout.MinMaxSlider(ref loopStart, ref loopEnd, 0, music.length);

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("Loop Point Start: " + AudioPlaybackToolEditor.TimeToString(loopStart), new GUILayoutOption[] { GUILayout.Width(180) });
                            GUILayout.FlexibleSpace();
                            GUILayout.Label("Loop Point Start (Samples): " + myScript.loopStart * music.frequency);
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("Loop Point End:   " + AudioPlaybackToolEditor.TimeToString(loopEnd), new GUILayoutOption[] { GUILayout.Width(180) });
                            GUILayout.FlexibleSpace();
                            GUILayout.Label("Loop Point End (Samples): " + myScript.loopEnd * music.frequency);
                            GUILayout.EndHorizontal();
                            break;

                        case LoopPointTool.TimeInput:
                            EditorGUILayout.Space();

                            // int casts are used instead of Mathf.RoundToInt so that we truncate the floats instead of rounding up
                            GUILayout.BeginHorizontal();
                            float theTime = loopStart * 1000f;
                            GUILayout.Label("Loop Point Start:");
                            int minutes = EditorGUILayout.IntField((int)(theTime / 60000f));
                            GUILayout.Label(":");
                            int seconds = Mathf.Clamp(EditorGUILayout.IntField((int)((theTime % 60000) / 1000f)), 0, 59);
                            GUILayout.Label(":");
                            float milliseconds = Mathf.Clamp(EditorGUILayout.IntField(Mathf.RoundToInt(theTime % 1000f)), 0, 999.0f);
                            milliseconds /= 1000.0f;     // Ensures that our milliseconds never leave their decimal place
                            loopStart     = (float)minutes * 60f + (float)seconds + milliseconds;
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            theTime = loopEnd * 1000f;
                            GUILayout.Label("Loop Point End:  ");
                            minutes = EditorGUILayout.IntField((int)(theTime / 60000f));
                            GUILayout.Label(":");
                            seconds = Mathf.Clamp(EditorGUILayout.IntField((int)((theTime % 60000) / 1000f)), 0, 59);
                            GUILayout.Label(":");
                            milliseconds  = Mathf.Clamp(EditorGUILayout.IntField(Mathf.RoundToInt(theTime % 1000f)), 0, 999.0f);
                            milliseconds /= 1000.0f;     // Ensures that our milliseconds never leave their decimal place
                            loopEnd       = (float)minutes * 60f + (float)seconds + milliseconds;
                            GUILayout.EndHorizontal();
                            break;

                        case LoopPointTool.TimeSamplesInput:
                            GUILayout.Label("Song Duration (Samples): " + music.samples);
                            EditorGUILayout.Space();

                            GUILayout.BeginHorizontal();
                            float samplesStart = EditorGUILayout.FloatField("Loop Point Start:", myScript.loopStart * music.frequency);
                            GUILayout.EndHorizontal();
                            loopStart = samplesStart / music.frequency;

                            GUILayout.BeginHorizontal();
                            float samplesEnd = JSAMExtensions.Clamp(EditorGUILayout.FloatField("Loop Point End:", myScript.loopEnd * music.frequency), 0, music.samples);
                            GUILayout.EndHorizontal();
                            loopEnd = samplesEnd / music.frequency;
                            break;

                        case LoopPointTool.BPMInput /*WithBeats*/:
                            Undo.RecordObject(myScript, "Modified song BPM");
                            myScript.bpm = EditorGUILayout.IntField("Song BPM: ", myScript.bpm /*, new GUILayoutOption[] { GUILayout.MaxWidth(30)}*/);

                            EditorGUILayout.Space();

                            float startBeat = loopStart / (60f / (float)myScript.bpm);
                            startBeat = EditorGUILayout.FloatField("Starting Beat:", startBeat);

                            float endBeat = loopEnd / (60f / (float)myScript.bpm);
                            endBeat = Mathf.Clamp(EditorGUILayout.FloatField("Ending Beat:", endBeat), 0, music.length / (60f / myScript.bpm));

                            loopStart = (float)startBeat * 60f / (float)myScript.bpm;
                            loopEnd   = (float)endBeat * 60f / (float)myScript.bpm;
                            break;
                            //case AudioFileMusic.LoopPointTool.BPMInputWithBars:
                            //    GUILayout.BeginHorizontal();
                            //    GUILayout.Label("Song Duration: " + TimeToString(music.length));
                            //    myScript.bpm = EditorGUILayout.IntField("Song BPM: ", myScript.bpm);
                            //    GUILayout.EndHorizontal();
                            //
                            //    int startBar = (int)(loopStart / (60f / (float)myScript.bpm));
                            //    startBar = EditorGUILayout.IntField("Starting Bar:", startBar);
                            //
                            //    int endBar = (int)(loopEnd / (60f / (float)myScript.bpm));
                            //    endBar = EditorGUILayout.IntField("Ending Bar:", endBar);
                            //
                            //    loopStart = startBar * 60f / myScript.bpm;
                            //    loopEnd = endBar * 60f / myScript.bpm;
                            //    break;
                        }

                        GUIContent buttonText = new GUIContent("Reset Loop Points", "Click to set loop points to the start and end of the track.");
                        if (GUILayout.Button(buttonText))
                        {
                            loopStart = 0;
                            loopEnd   = music.length;
                        }
                        using (new EditorGUI.DisabledScope(!myScript.IsWavFile()))
                        {
                            if (myScript.IsWavFile())
                            {
                                buttonText = new GUIContent("Import Loop Points from .WAV Metadata", "Using this option will overwrite existing loop point data. Check the quick reference guide for details!");
                            }
                            else
                            {
                                buttonText = new GUIContent("Import Loop Points from .WAV Metadata", "This option is exclusive to .WAV files. Using this option will overwrite existing loop point data. Check the quick reference guide for details!");
                            }
                            EditorGUILayout.BeginHorizontal();
                            if (GUILayout.Button(buttonText))
                            {
                                // Zeugma440 and his Audio Tools Library is a godsend
                                // https://github.com/Zeugma440/atldotnet/
                                string filePath     = AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets(myScript.file.name)[0]);
                                string trueFilePath = Application.dataPath.Remove(Application.dataPath.LastIndexOf("/") + 1) + filePath;

                                ATL.Track theTrack = new ATL.Track(trueFilePath);

                                float frequency = myScript.GetFile().frequency;

                                if (theTrack.AdditionalFields.ContainsKey("sample.SampleLoop[0].Start") && theTrack.AdditionalFields.ContainsKey("sample.SampleLoop[0].End"))
                                {
                                    loopStart = float.Parse(theTrack.AdditionalFields["sample.SampleLoop[0].Start"]) / frequency;
                                    loopEnd   = float.Parse(theTrack.AdditionalFields["sample.SampleLoop[0].End"]) / frequency;
                                }
                                else
                                {
                                    EditorUtility.DisplayDialog("Error Reading Metadata", "Could not find any loop point data in " + myScript.GetFile().name + ".wav!\n" +
                                                                "Are you sure you wrote loop points in this file?", "OK");
                                }
                            }
                            if (myScript.IsWavFile())
                            {
                                buttonText = new GUIContent("Embed Loop Points to File", "Clicking this will write the above start and end loop points into the actual file itself. Check the quick reference guide for details!");
                            }
                            else
                            {
                                buttonText = new GUIContent("Embed Loop Points to File", "This option is exclusive to .WAV files. Clicking this will write the above start and end loop points into the actual file itself. Check the quick reference guide for details!");
                            }
                            if (GUILayout.Button(buttonText))
                            {
                                string filePath     = AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets(myScript.file.name)[0]);
                                string trueFilePath = Application.dataPath.Remove(Application.dataPath.LastIndexOf("/") + 1) + filePath;

                                ATL.Track theTrack = new ATL.Track(trueFilePath);

                                float frequency = myScript.GetFile().frequency;

                                if (EditorUtility.DisplayDialog("Confirm Loop Point saving", "This will overwrite loop point Start/End loop point markers saved in this .WAV file, are you sure you want to continue?", "Yes", "Cancel"))
                                {
                                    theTrack.AdditionalFields["sample.MIDIUnityNote"]       = "60";
                                    theTrack.AdditionalFields["sample.NumSampleLoops"]      = "1";
                                    theTrack.AdditionalFields["sample.SampleLoop[0].Type"]  = "0";
                                    theTrack.AdditionalFields["sample.SampleLoop[0].Start"] = (Mathf.RoundToInt(myScript.loopStart * frequency)).ToString();
                                    theTrack.AdditionalFields["sample.SampleLoop[0].End"]   = (Mathf.RoundToInt(myScript.loopEnd * frequency)).ToString();
                                    theTrack.Save();
                                }
                            }
                            EditorGUILayout.EndHorizontal();
                        }

                        if (myScript.loopStart != loopStart || myScript.loopEnd != loopEnd)
                        {
                            Undo.RecordObject(myScript, "Modified loop point properties");
                            loopStartProperty.floatValue = Mathf.Clamp(loopStart, 0, music.length);
                            loopEndProperty.floatValue   = Mathf.Clamp(loopEnd, 0, Mathf.Ceil(music.length));
                            EditorUtility.SetDirty(myScript);
                            AudioPlaybackToolEditor.DoForceRepaint(true);
                        }
                    }
                    EditorCompatability.EndSpecialFoldoutGroup();
                }
            }
            #endregion

            DrawAudioEffectTools();

            if (serializedObject.hasModifiedProperties)
            {
                AudioPlaybackToolEditor.DoForceRepaint(true);
                serializedObject.ApplyModifiedProperties();

                // Manually fix variables
                if (myScript.delay < 0)
                {
                    myScript.delay = 0;
                    Undo.RecordObject(myScript, "Fixed negative delay");
                }
            }

            #region Quick Reference Guide
            showHowTo = EditorCompatability.SpecialFoldouts(showHowTo, "Quick Reference Guide");
            if (showHowTo)
            {
                JSAMEditorHelper.RenderHelpbox("Overview");

                JSAMEditorHelper.RenderHelpbox("Audio File Music Objects are containers that hold your music.");
                JSAMEditorHelper.RenderHelpbox("These Audio File objects are to be stored in an Audio Library where it is " +
                                               "then read and played through the Audio Manager");

                JSAMEditorHelper.RenderHelpbox("Tips");

                JSAMEditorHelper.RenderHelpbox("Relative volume only helps to reduce how loud a sound is. To increase how loud an individual sound is, you'll have to " +
                                               "edit it using a sound editor.");
                JSAMEditorHelper.RenderHelpbox("Before you cut up your music into an intro and looping portion, try using the loop point tools!");
                JSAMEditorHelper.RenderHelpbox("By designating your loop points in the loop point tools and setting your music's loop mode to " +
                                               "\"Loop with Loop Points\", you can easily get AudioManager to play your intro portion once and repeat the looping portion forever!");
                JSAMEditorHelper.RenderHelpbox("If your music is saved in the WAV format, you can use external programs to set loop points in the file itself! " +
                                               "After that, click the \"Import Loop Points from .WAV Metadata\" button above to have AudioManager to read them in.");
                JSAMEditorHelper.RenderHelpbox("You can designate loop points in your .WAV file using programs like Wavosaur and Goldwave! Click the links " +
                                               "below to learn more about how to get these free tools and create loop points with them!");

                EditorGUILayout.BeginHorizontal();
                GUIContent buttonC = new GUIContent("Wavosaur", "Click here to download Wavosaur!");
                if (GUILayout.Button(buttonC))
                {
                    Application.OpenURL("https://www.wavosaur.com/");
                }
                buttonC = new GUIContent("GoldWave", "Click here to download GoldWave!");
                if (GUILayout.Button(buttonC))
                {
                    Application.OpenURL("http://www.goldwave.com/release.php");
                }
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.BeginHorizontal();
                buttonC = new GUIContent("How to use Wavosaur", "Click here to learn how to set Loop Points in Wavosaur!");
                if (GUILayout.Button(buttonC))
                {
                    Application.OpenURL("https://www.wavosaur.com/quick-help/loop-points-edition.php");
                }
                buttonC = new GUIContent("How to use Goldwave", "Click here to learn how to set Loop Points in GoldWave!");
                if (GUILayout.Button(buttonC))
                {
                    Application.OpenURL("https://developer.valvesoftware.com/wiki/Looping_a_Sound");
                }
                EditorGUILayout.EndHorizontal();
                JSAMEditorHelper.RenderHelpbox("Otherwise, using BPM input to set your loop points is strongly recommended!");
                JSAMEditorHelper.RenderHelpbox("You can also choose to export your loop point data by clicking the \"Save Loop Points to File\" button " +
                                               "to use in other programs!");
            }
            EditorCompatability.EndSpecialFoldoutGroup();
            #endregion
        }
Exemple #9
0
        /// <summary>
        /// Code from these gents
        /// https://answers.unity.com/questions/189886/displaying-an-audio-waveform-in-the-editor.html
        /// </summary>
        public Texture2D PaintWaveformSpectrum(AudioClip audio, int width, int height, Color col)
        {
            if (Event.current.type != EventType.Repaint)
            {
                return(null);
            }

            Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);

            float[] samples = new float[audio.samples * audio.channels];
            // Copy sample data to array
            audio.GetData(samples, 0);

            Color lightShade = new Color(0.3f, 0.3f, 0.3f);
            int   halfHeight = height / 2;

            float leftValue  = AudioPlaybackToolEditor.CalculateZoomedLeftValue();
            float rightValue = AudioPlaybackToolEditor.CalculateZoomedRightValue();

            int leftSide  = Mathf.RoundToInt(leftValue * samples.Length);
            int rightSide = Mathf.RoundToInt(rightValue * samples.Length);

            float zoomLevel = AudioPlaybackToolEditor.scrollZoom / AudioPlaybackToolEditor.MAX_SCROLL_ZOOM;
            int   packSize  = Mathf.RoundToInt((int)samples.Length / (int)width * (float)zoomLevel) + 1;

            int s     = 0;
            int limit = Mathf.Min(rightSide, samples.Length);

            // Build waveform data
            float[] waveform = new float[limit];
            for (int i = leftSide; i < limit; i += packSize)
            {
                waveform[s] = Mathf.Abs(samples[i]);
                s++;
            }

            if (myScript.loopMode == LoopMode.LoopWithLoopPoints)
            {
                float beginning = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, myScript.loopStart / audio.length);
                float ending    = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, myScript.loopEnd / audio.length);
                float loopStart = beginning * width;
                float loopEnd   = ending * width;

                for (int x = 0; x < width; x++)
                {
                    // Here we limit the scope of the area based on loop points
                    if (x < loopStart || x > loopEnd)
                    {
                        for (int y = 0; y < height; y++)
                        {
                            tex.SetPixel(x, y, Color.black);
                        }
                    }
                    else
                    {
                        for (int y = 0; y < height; y++)
                        {
                            tex.SetPixel(x, y, lightShade);
                        }
                    }
                }
            }
            else
            {
                for (int x = 0; x < width; x++)
                {
                    for (int y = 0; y < height; y++)
                    {
                        tex.SetPixel(x, y, lightShade);
                    }
                }
            }

            for (int x = 0; x < Mathf.Clamp(rightSide, 0, width); x++)
            {
                // Scale the wave vertically relative to half the rect height and the relative volume
                float heightLimit = waveform[x] * halfHeight * myScript.relativeVolume;

                for (int y = (int)heightLimit; y >= 0; y--)
                {
                    Color currentPixelColour = tex.GetPixel(x, halfHeight + y);

                    tex.SetPixel(x, halfHeight + y, currentPixelColour + col * 0.75f);

                    // Get data from upper half offset by 1 unit due to int truncation
                    currentPixelColour = tex.GetPixel(x, halfHeight - (y + 1));
                    // Draw bottom half with data from upper half
                    tex.SetPixel(x, halfHeight - (y + 1), currentPixelColour + col * 0.75f);
                }
            }
            tex.Apply();

            return(tex);
        }
Exemple #10
0
 void OnUndoRedo()
 {
     AudioPlaybackToolEditor.DoForceRepaint(true);
 }
Exemple #11
0
        void Update()
        {
            AudioFileMusicObject myScript = (AudioFileMusicObject)target;

            if (myScript == null)
            {
                return;
            }
            AudioClip music = myScript.GetFile();

            if (music != cachedClip)
            {
                AudioPlaybackToolEditor.DoForceRepaint(true);
                cachedClip = music;
                AudioPlaybackToolEditor.helperSource.clip = cachedClip;
            }

            if (!AudioPlaybackToolEditor.helperSource.isPlaying && mouseDragging)
            {
                Repaint();
            }

            if ((clipPlaying && !clipPaused) || (mouseDragging && clipPlaying))
            {
                float clipPos = AudioPlaybackToolEditor.helperSource.timeSamples / (float)music.frequency;
                AudioPlaybackToolEditor.helperSource.volume = myScript.relativeVolume;
                AudioPlaybackToolEditor.helperSource.pitch  = myScript.startingPitch;

                Repaint();

                if (loopClip)
                {
                    EditorApplication.QueuePlayerLoopUpdate();
                    if (myScript.loopMode == LoopMode.LoopWithLoopPoints)
                    {
                        if (!AudioPlaybackToolEditor.helperSource.isPlaying && clipPlaying && !clipPaused)
                        {
                            if (freePlay)
                            {
                                AudioPlaybackToolEditor.helperSource.Play();
                            }
                            else
                            {
                                AudioPlaybackToolEditor.helperSource.Play();
                                AudioPlaybackToolEditor.helperSource.timeSamples = Mathf.CeilToInt(myScript.loopStart * music.frequency);
                            }
                            freePlay = false;
                        }
                        else if (myScript.clampToLoopPoints || !firstPlayback)
                        {
                            if (clipPos < myScript.loopStart || clipPos > myScript.loopEnd)
                            {
                                // CeilToInt to guarantee clip position stays within loop bounds
                                AudioPlaybackToolEditor.helperSource.timeSamples = Mathf.CeilToInt(myScript.loopStart * music.frequency);
                                firstPlayback = false;
                            }
                        }
                        else if (clipPos >= myScript.loopEnd)
                        {
                            AudioPlaybackToolEditor.helperSource.timeSamples = Mathf.CeilToInt(myScript.loopStart * music.frequency);
                            firstPlayback = false;
                        }
                    }
                }
                else if (!loopClip && myScript.loopMode == LoopMode.LoopWithLoopPoints)
                {
                    if ((!AudioPlaybackToolEditor.helperSource.isPlaying && !clipPaused) || clipPos > myScript.loopEnd)
                    {
                        clipPlaying = false;
                        AudioPlaybackToolEditor.helperSource.Stop();
                    }
                    else if (myScript.clampToLoopPoints && clipPos < myScript.loopStart)
                    {
                        AudioPlaybackToolEditor.helperSource.timeSamples = Mathf.CeilToInt(myScript.loopStart * music.frequency);
                    }
                }
            }

            if (myScript.loopMode != LoopMode.LoopWithLoopPoints)
            {
                if (!AudioPlaybackToolEditor.helperSource.isPlaying && !clipPaused && clipPlaying)
                {
                    AudioPlaybackToolEditor.helperSource.time = 0;
                    if (loopClip)
                    {
                        AudioPlaybackToolEditor.helperSource.Play();
                    }
                    else
                    {
                        clipPlaying = false;
                    }
                }
            }
        }
Exemple #12
0
        /// <summary>
        /// Draws a playback
        /// </summary>
        /// <param name="music"></param>
        public void DrawPlaybackTool(AudioFileMusicObject myScript)
        {
            GUIContent blontent = new GUIContent("Audio Playback Preview",
                                                 "Allows you to preview how your AudioFileMusicObject will sound during runtime right here in the inspector. " +
                                                 "Some effects, like spatialization, will not be available to preview");

            showPlaybackTool = EditorCompatability.SpecialFoldouts(showPlaybackTool, blontent);
            if (showPlaybackTool)
            {
                AudioClip music        = myScript.GetFile();
                Rect      progressRect = ProgressBar((float)AudioPlaybackToolEditor.helperSource.timeSamples / (float)AudioPlaybackToolEditor.helperSource.clip.samples, GetInfoString());

                EditorGUILayout.BeginHorizontal();

                Event evt = Event.current;

                if (evt.isMouse)
                {
                    switch (evt.type)
                    {
                    case EventType.MouseUp:
                        mouseDragging = false;
                        break;

                    case EventType.MouseDown:
                    case EventType.MouseDrag:
                        if (evt.type == EventType.MouseDown)
                        {
                            if (evt.mousePosition.y > progressRect.yMin && evt.mousePosition.y < progressRect.yMax)
                            {
                                mouseDragging = true;
                                mouseScrubbed = true;
                            }
                            else
                            {
                                mouseDragging = false;
                            }
                        }
                        if (!mouseDragging)
                        {
                            break;
                        }
                        float newProgress = Mathf.InverseLerp(progressRect.xMin, progressRect.xMax, evt.mousePosition.x);
                        AudioPlaybackToolEditor.helperSource.time = Mathf.Clamp((newProgress * music.length), 0, music.length - AudioManager.EPSILON);
                        if (myScript.loopMode == LoopMode.LoopWithLoopPoints && myScript.clampToLoopPoints)
                        {
                            float start = myScript.loopStart * myScript.GetFile().frequency;
                            float end   = myScript.loopEnd * myScript.GetFile().frequency;
                            AudioPlaybackToolEditor.helperSource.timeSamples = (int)Mathf.Clamp(AudioPlaybackToolEditor.helperSource.timeSamples, start, end - AudioManager.EPSILON);
                        }
                        break;
                    }
                }

                if (GUILayout.Button(s_BackIcon, new GUILayoutOption[] { GUILayout.MaxHeight(20) }))
                {
                    if (myScript.loopMode == LoopMode.LoopWithLoopPoints && myScript.clampToLoopPoints)
                    {
                        AudioPlaybackToolEditor.helperSource.timeSamples = Mathf.CeilToInt((myScript.loopStart * music.frequency));
                    }
                    else
                    {
                        AudioPlaybackToolEditor.helperSource.timeSamples = 0;
                    }
                    AudioPlaybackToolEditor.helperSource.Stop();
                    mouseScrubbed = false;
                    clipPaused    = false;
                    clipPlaying   = false;
                }

                Color      colorbackup = GUI.backgroundColor;
                GUIContent buttonIcon  = (clipPlaying) ? s_PlayIcons[1] : s_PlayIcons[0];
                if (clipPlaying)
                {
                    GUI.backgroundColor = buttonPressedColor;
                }
                if (GUILayout.Button(buttonIcon, new GUILayoutOption[] { GUILayout.MaxHeight(20) }))
                {
                    clipPlaying = !clipPlaying;
                    if (clipPlaying)
                    {
                        // Note: For some reason, reading from AudioPlaybackToolEditor.helperSource.time returns 0 even if timeSamples is not 0
                        // However, writing a value to AudioPlaybackToolEditor.helperSource.time changes timeSamples to the appropriate value just fine
                        AudioPlaybackToolEditor.helperHelper.PlayDebug(myScript, mouseScrubbed);
                        if (clipPaused)
                        {
                            AudioPlaybackToolEditor.helperSource.Pause();
                        }
                        firstPlayback = true;
                        freePlay      = false;
                    }
                    else
                    {
                        AudioPlaybackToolEditor.helperSource.Stop();
                        if (!mouseScrubbed)
                        {
                            AudioPlaybackToolEditor.helperSource.time = 0;
                        }
                        clipPaused = false;
                    }
                }

                GUI.backgroundColor = colorbackup;
                GUIContent theText = (clipPaused) ? s_PauseIcons[1] : s_PauseIcons[0];
                if (clipPaused)
                {
                    GUI.backgroundColor = buttonPressedColor;
                }
                if (GUILayout.Button(theText, new GUILayoutOption[] { GUILayout.MaxHeight(20) }))
                {
                    clipPaused = !clipPaused;
                    if (clipPaused)
                    {
                        AudioPlaybackToolEditor.helperSource.Pause();
                    }
                    else
                    {
                        AudioPlaybackToolEditor.helperSource.UnPause();
                    }
                }

                GUI.backgroundColor = colorbackup;
                buttonIcon          = (loopClip) ? s_LoopIcons[1] : s_LoopIcons[0];
                if (loopClip)
                {
                    GUI.backgroundColor = buttonPressedColor;
                }
                if (GUILayout.Button(buttonIcon, new GUILayoutOption[] { GUILayout.MaxHeight(20) }))
                {
                    loopClip = !loopClip;
                    // AudioPlaybackToolEditor.helperSource.loop = true;
                }
                GUI.backgroundColor = colorbackup;

                if (GUILayout.Button(openIcon, new GUILayoutOption[] { GUILayout.MaxHeight(19) }))
                {
                    AudioPlaybackToolEditor.Init();
                }

                // Reset loop point input mode if not using loop points so the duration shows up as time by default
                if (myScript.loopMode != LoopMode.LoopWithLoopPoints)
                {
                    loopPointInputMode = 0;
                }

                switch ((LoopPointTool)loopPointInputMode)
                {
                case LoopPointTool.Slider:
                case LoopPointTool.TimeInput:
                    blontent = new GUIContent(AudioPlaybackToolEditor.TimeToString((float)AudioPlaybackToolEditor.helperSource.timeSamples / music.frequency) + " / " + (AudioPlaybackToolEditor.TimeToString(music.length)),
                                              "The playback time in seconds");
                    break;

                case LoopPointTool.TimeSamplesInput:
                    blontent = new GUIContent(AudioPlaybackToolEditor.helperSource.timeSamples + " / " + music.samples, "The playback time in samples");
                    break;

                case LoopPointTool.BPMInput:
                    blontent = new GUIContent(string.Format("{0:0}", AudioPlaybackToolEditor.helperSource.time / (60f / myScript.bpm)) + " / " + music.length / (60f / myScript.bpm),
                                              "The playback time in beats");
                    break;
                }
                GUIStyle rightJustified = new GUIStyle(EditorStyles.label);
                rightJustified.alignment = TextAnchor.UpperRight;
                EditorGUILayout.LabelField(blontent, rightJustified);
                EditorGUILayout.EndHorizontal();

                EditorGUILayout.Space();
            }
            EditorCompatability.EndSpecialFoldoutGroup();
        }
        public override void OnInspectorGUI()
        {
            if (asset == null)
            {
                return;
            }

            serializedObject.Update();

            RenderPresetDescription();

            EditorGUILayout.Space();

            RenderGeneratePresetButton();

            List <string> excludedProperties = new List <string>()
            {
                "m_Script", "file", "files", "safeName",
                "relativeVolume", "spatialize", "maxDistance"
            };

            if (asset.UsingLibrary()) // Swap file with files
            {
#if UNITY_2019_3_OR_NEWER
                EditorGUILayout.PropertyField(files);
#else           // Property field on an array doesn't seem to work before 2019.3, so we have to make it ourselves
                EditorGUILayout.Space();
                EditorGUILayout.LabelField("Attach audio files here to use", EditorStyles.boldLabel);
                filesFoldout = EditorGUILayout.Foldout(filesFoldout, new GUIContent("Files"), true);
                if (filesFoldout)
                {
                    EditorGUI.indentLevel++;
                    files.arraySize = EditorGUILayout.IntField("Size", files.arraySize);
                    for (int i = 0; i < files.arraySize; i++)
                    {
                        EditorGUILayout.PropertyField(files.GetArrayElementAtIndex(i));
                    }
                    EditorGUI.indentLevel--;
                }
#endif
            }
            else
            {
                if (file != null)
                {
                    EditorGUILayout.PropertyField(file);
                }
            }

            GUIContent blontent = new GUIContent("Use Library", "If true, the single AudioFile will be changed to a list of AudioFiles. AudioManager will choose a random AudioClip from this list when you play this sound");
            bool       oldValue = asset.useLibrary;
            bool       newValue = EditorGUILayout.Toggle(blontent, oldValue);
            if (newValue != oldValue) // If you clicked the toggle
            {
                if (newValue)
                {
                    if (asset.files.Count == 0)
                    {
                        if (asset.file != null)
                        {
                            asset.files.Add(asset.file);
                        }
                    }
                    else if (asset.files.Count == 1)
                    {
                        if (asset.files[0] == null)
                        {
                            asset.files[0] = asset.file;
                        }
                    }
                }
                else
                {
                    if (asset.files.Count > 0 && asset.file == null)
                    {
                        asset.file = asset.files[0];
                    }
                }
                asset.useLibrary = newValue;
            }

            if (asset.useLibrary)
            {
                blontent = new GUIContent("Never Repeat", "Sometimes, AudioManager will allow the same sound from the Audio " +
                                          "library to play twice in a row, enabling this option will ensure that this audio file never plays the same " +
                                          "sound until after it plays a different sound.");
                EditorGUILayout.PropertyField(neverRepeat, blontent);
            }

            bool noFiles = asset.GetFile() == null && asset.IsLibraryEmpty();

            if (noFiles)
            {
                excludedProperties.AddRange(new List <string>()
                {
                    "loopSound",
                    "priority", "startingPitch", "pitchShift", "playReversed", "delay", "ignoreTimeScale", "fadeMode",
                    "safeName"
                });
            }
            else
            {
                EditorGUILayout.PropertyField(relativeVolume);
                EditorGUILayout.PropertyField(spatialize);
                using (new EditorGUI.DisabledScope(!spatialize.boolValue))
                {
                    EditorGUI.BeginChangeCheck();
                    EditorGUILayout.PropertyField(maxDistance);
                    if (EditorGUI.EndChangeCheck())
                    {
                        if (maxDistance.floatValue < 0)
                        {
                            maxDistance.floatValue = 0;
                        }
                    }
                }
            }

            DrawPropertiesExcluding(serializedObject, excludedProperties.ToArray());

            if (noFiles)
            {
                EditorGUILayout.HelpBox("Error! Add an audio file before running!", MessageType.Error);
            }
            if (asset.name.Contains("NEW AUDIO FILE") || asset.name.Equals("None") || asset.name.Equals("GameObject"))
            {
                EditorGUILayout.HelpBox("Warning! Change the name of the gameObject to something different or things will break!", MessageType.Warning);
            }

            if (playingClip == null)
            {
                DesignateActiveAudioClip(asset);
            }
            if (!noFiles && !AudioPlaybackToolEditor.WindowOpen)
            {
                DrawPlaybackTool();
            }

            #region Fade Tools
            using (new EditorGUI.DisabledScope(asset.fadeMode == FadeMode.None))
            {
                if (!asset.IsLibraryEmpty())
                {
                    showFadeTool = EditorCompatability.SpecialFoldouts(showFadeTool, new GUIContent("Fade Tools", "Show/Hide the Audio Fade previewer"));
                    if (showFadeTool && asset.fadeMode != FadeMode.None)
                    {
                        GUIContent fContent       = new GUIContent();
                        GUIStyle   rightJustified = new GUIStyle(EditorStyles.label);
                        rightJustified.alignment = TextAnchor.UpperRight;
                        rightJustified.padding   = new RectOffset(0, 15, 0, 0);
                        switch (asset.fadeMode)
                        {
                        case FadeMode.FadeIn:
                            EditorGUILayout.BeginHorizontal();
                            EditorGUILayout.LabelField(new GUIContent("Fade In Time:    " + JSAMEditorHelper.TimeToString(fadeInDuration.floatValue * playingClip.length), "Fade in time for this AudioClip in seconds"));
                            EditorGUILayout.LabelField(new GUIContent("Sound Length: " + JSAMEditorHelper.TimeToString(playingClip.length), "Length of the preview clip in seconds"), rightJustified);
                            EditorGUILayout.EndHorizontal();
                            EditorGUILayout.Slider(fadeInDuration, 0, 1);
                            break;

                        case FadeMode.FadeOut:
                            EditorGUILayout.BeginHorizontal();
                            EditorGUILayout.LabelField(new GUIContent("Fade Out Time: " + JSAMEditorHelper.TimeToString(fadeOutDuration.floatValue * playingClip.length), "Fade out time for this AudioClip in seconds"));
                            EditorGUILayout.LabelField(new GUIContent("Sound Length: " + JSAMEditorHelper.TimeToString(playingClip.length), "Length of the preview clip in seconds"), rightJustified);
                            EditorGUILayout.EndHorizontal();
                            EditorGUILayout.Slider(fadeOutDuration, 0, 1);
                            break;

                        case FadeMode.FadeInAndOut:
                            EditorGUILayout.BeginHorizontal();
                            EditorGUILayout.LabelField(new GUIContent("Fade In Time:    " + JSAMEditorHelper.TimeToString(fadeInDuration.floatValue * playingClip.length), "Fade in time for this AudioClip in seconds"));
                            EditorGUILayout.LabelField(new GUIContent("Sound Length: " + JSAMEditorHelper.TimeToString(playingClip.length), "Length of the preview clip in seconds"), rightJustified);
                            EditorGUILayout.EndHorizontal();
                            EditorGUILayout.LabelField(new GUIContent("Fade Out Time: " + JSAMEditorHelper.TimeToString(fadeOutDuration.floatValue * playingClip.length), "Fade out time for this AudioClip in seconds"));
                            float fid = fadeInDuration.floatValue;
                            float fod = fadeOutDuration.floatValue;
                            fContent = new GUIContent("Fade In Percentage", "The percentage of time the sound takes to fade-in relative to it's total length.");
                            fid      = Mathf.Clamp(EditorGUILayout.Slider(fContent, fid, 0, 1), 0, 1 - fod);
                            fContent = new GUIContent("Fade Out Percentage", "The percentage of time the sound takes to fade-out relative to it's total length.");
                            fod      = Mathf.Clamp(EditorGUILayout.Slider(fContent, fod, 0, 1), 0, 1 - fid);
                            fadeInDuration.floatValue  = fid;
                            fadeOutDuration.floatValue = fod;
                            EditorGUILayout.HelpBox("Note: The sum of your Fade-In and Fade-Out durations cannot exceed 1 (the length of the sound).", MessageType.None);
                            break;
                        }
                    }
                    EditorCompatability.EndSpecialFoldoutGroup();
                }
            }
            #endregion

            if (!noFiles)
            {
                DrawAudioEffectTools();
            }

            if (serializedObject.hasModifiedProperties)
            {
                AudioPlaybackToolEditor.DoForceRepaint(true);
                serializedObject.ApplyModifiedProperties();

                // Manually fix variables
                if (asset.delay < 0)
                {
                    asset.delay = 0;
                    Undo.RecordObject(asset, "Fixed negative delay");
                }
            }

            #region Quick Reference Guide
            string[] howToText = new string[]
            {
                "Overview",
                "Audio File Objects are containers that hold your sound files to be read by Audio Manager.",
                "No matter the filename or folder location, this Audio File will be referred to as it's name above",
                "Tips",
                "If your one sound has many different variations available, try enabling the \"Use Library\" option " +
                "just below the name field. This let's AudioManager play a random different sound whenever you choose to play from this audio file object.",
                "Relative volume only helps to reduce how loud a sound is. To increase how loud an individual sound is, you'll have to " +
                "edit it using a sound editor.",
                "You can always check what audio file objects you have loaded in AudioManager's library by selecting the AudioManager " +
                "in the inspector and clicking on the drop-down near the bottom.",
                "If you want to better organize your audio file objects in AudioManager's library, you can assign a " +
                "category to this audio file object."
            };

            showHowTo = JSAMEditorHelper.RenderQuickReferenceGuide(showHowTo, howToText);
            #endregion
        }
        /// <summary>
        /// Code from these gents
        /// https://answers.unity.com/questions/189886/displaying-an-audio-waveform-in-the-editor.html
        /// </summary>
        public Texture2D PaintWaveformSpectrum(AudioClip audio, int width, int height, Color col)
        {
            if (Event.current.type != EventType.Repaint)
            {
                return(null);
            }

            Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);

            float[] samples = new float[audio.samples * audio.channels];
            // Copy sample data to array
            audio.GetData(samples, 0);

            float leftValue  = AudioPlaybackToolEditor.CalculateZoomedLeftValue();
            float rightValue = AudioPlaybackToolEditor.CalculateZoomedRightValue();

            int leftSide  = Mathf.RoundToInt(leftValue * samples.Length);
            int rightSide = Mathf.RoundToInt(rightValue * samples.Length);

            float zoomLevel = AudioPlaybackToolEditor.scrollZoom / AudioPlaybackToolEditor.MAX_SCROLL_ZOOM;
            int   packSize  = Mathf.RoundToInt((int)samples.Length / (int)width * (float)zoomLevel) + 1;

            int s     = 0;
            int limit = Mathf.Min(rightSide, samples.Length);

            // Build waveform data
            float[] waveform = new float[limit];
            for (int i = leftSide; i < limit; i += packSize)
            {
                waveform[s] = Mathf.Abs(samples[i]);
                s++;
            }

            float fadeInDuration  = asset.fadeInDuration;
            float fadeOutDuration = asset.fadeOutDuration;

            Color lightShade = new Color(0.3f, 0.3f, 0.3f);
            int   halfHeight = height / 2;

            // The halved height limit of the wave at the left/right extremes
            float fadeStart = leftValue * halfHeight;
            float fadeEnd   = rightValue * halfHeight;

            switch (asset.fadeMode)
            {
            case FadeMode.None:
            {
                for (int x = 0; x < width; x++)
                {
                    for (int y = 0; y < height; y++)
                    {
                        tex.SetPixel(x, y, lightShade);
                    }
                }
            }
            break;

            case FadeMode.FadeIn:     // Paint the fade-in area
            {
                // Scope lol
                {
                    // Scale our fadeIn value by the current zoom
                    float fadeInRelative    = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, fadeInDuration);
                    float fadeStartRelative = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, 0);

                    // Get the length of the whole fade shape from the scaled fade duration relative to the rect width
                    float fadeWidth = width * fadeInRelative;
                    // Offset the lerp value by how much the left side bar is obscuring the start of the fade
                    int offset = Mathf.RoundToInt(width * Mathf.Abs(fadeStartRelative));

                    // Clamp the limit in case fadeWidth exceeds the right side of the bar
                    for (int x = 0; x + offset < Mathf.Clamp(fadeWidth, 0, width) + offset; x++)
                    {
                        // Paint amount of vertical black depending on progress
                        // Lerp from 0 to half height as those are the extremes we're working with
                        // amountToPaint is the amount of
                        float lerpValue     = (float)(x + offset) / (fadeWidth + offset);
                        int   amountToPaint = (int)Mathf.Lerp(0, halfHeight, lerpValue);
                        for (int y = halfHeight; y >= 0; y--)
                        {
                            switch (amountToPaint)
                            {
                            case 0:
                                tex.SetPixel(x, y, Color.black);
                                break;

                            default:
                                tex.SetPixel(x, y, lightShade);
                                amountToPaint--;
                                break;
                            }
                        }
                        // Paint the same on the lower half
                        for (int y = halfHeight; y < height; y++)
                        {
                            tex.SetPixel(x, halfHeight - y, tex.GetPixel(x, y - halfHeight));
                        }
                    }

                    for (int x = (int)Mathf.Clamp(fadeWidth, 0, width); x < width; x++)
                    {
                        for (int y = 0; y < height; y++)
                        {
                            tex.SetPixel(x, y, lightShade);
                        }
                    }
                }
            }
            break;

            case FadeMode.FadeOut:
            {
                float fadeStartRelative = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, 1 - fadeOutDuration);
                int   fadeStartX        = Mathf.RoundToInt(width * fadeStartRelative);

                for (int x = 0; x < fadeStartX; x++)
                {
                    for (int y = 0; y < height; y++)
                    {
                        tex.SetPixel(x, y, lightShade);
                    }
                }

                float fadeEndRelative = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, 1);

                float fadeWidth = width * (fadeEndRelative - fadeStartRelative);

                for (int x = fadeStartX; x < width; x++)
                {
                    float lerpValue     = (float)(x - fadeStartX) / (fadeWidth);
                    int   amountToPaint = (int)Mathf.Lerp(halfHeight, 0, lerpValue);
                    for (int y = halfHeight; y >= 0; y--)
                    {
                        switch (amountToPaint)
                        {
                        case 0:
                            tex.SetPixel(x, y, Color.black);
                            break;

                        default:
                            tex.SetPixel(x, y, lightShade);
                            break;
                        }
                        amountToPaint = Mathf.Clamp(amountToPaint - 1, 0, height);
                    }
                    for (int y = halfHeight; y < height; y++)
                    {
                        tex.SetPixel(x, halfHeight - y, tex.GetPixel(x, y - halfHeight));
                    }
                }
            }
            break;

            case FadeMode.FadeInAndOut:
            {
                // Scale our fadeIn value by the current zoom
                float fadeInRelative    = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, fadeInDuration);
                float fadeStartRelative = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, 0);

                // Get the length of the whole fade shape from the scaled fade duration relative to the rect width
                float fadeWidth = width * fadeInRelative;
                // Offset the lerp value by how much the left side bar is obscuring the start of the fade
                int offset = Mathf.RoundToInt(width * Mathf.Abs(fadeStartRelative));

                for (int x = 0; x + offset < Mathf.Clamp(fadeWidth, 0, width) + offset; x++)
                {
                    float lerpValue     = (float)(x + offset) / (fadeWidth + offset);
                    int   amountToPaint = (int)Mathf.Lerp(0, halfHeight, lerpValue);
                    for (int y = halfHeight; y >= 0; y--)
                    {
                        switch (amountToPaint)
                        {
                        case 0:
                            tex.SetPixel(x, y, Color.black);
                            break;

                        default:
                            tex.SetPixel(x, y, lightShade);
                            break;
                        }
                        amountToPaint = Mathf.Clamp(amountToPaint - 1, 0, height);
                    }
                    for (int y = halfHeight; y < height; y++)
                    {
                        tex.SetPixel(x, halfHeight - y, tex.GetPixel(x, y - halfHeight));
                    }
                }

                fadeStartRelative = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, 1 - fadeOutDuration);
                float fadeStartX = width * fadeStartRelative;
                // Paint the middle rectangle
                for (int x = (int)fadeWidth; x < Mathf.Clamp(fadeStartX, 0, width); x++)
                {
                    for (int y = 0; y < height; y++)
                    {
                        tex.SetPixel(x, y, lightShade);
                    }
                }

                float fadeEndRelative = JSAMExtensions.InverseLerpUnclamped(leftValue, rightValue, 1);

                fadeWidth = width * (fadeEndRelative - fadeStartRelative);
                // Paint the right-side triangle
                for (int x = (int)fadeStartX; x < width; x++)
                {
                    float lerpValue     = (float)(x - fadeStartX) / (fadeWidth);
                    int   amountToPaint = (int)Mathf.Lerp(halfHeight, 0, lerpValue);
                    for (int y = halfHeight; y >= 0; y--)
                    {
                        switch (amountToPaint)
                        {
                        case 0:
                            tex.SetPixel(x, y, Color.black);
                            break;

                        default:
                            tex.SetPixel(x, y, lightShade);
                            break;
                        }
                        amountToPaint = Mathf.Clamp(amountToPaint - 1, 0, height);
                    }
                    for (int y = halfHeight; y < height; y++)
                    {
                        tex.SetPixel(x, halfHeight - y, tex.GetPixel(x, y - halfHeight));
                    }
                }
            }
            break;
            }

            for (int x = 0; x < Mathf.Clamp(rightSide, 0, width); x++)
            {
                // Scale the wave vertically relative to half the rect height and the relative volume
                float heightLimit = waveform[x] * halfHeight * asset.relativeVolume;

                for (int y = (int)heightLimit; y >= 0; y--)
                {
                    Color currentPixelColour = tex.GetPixel(x, halfHeight + y);
                    if (currentPixelColour == Color.black)
                    {
                        continue;
                    }

                    tex.SetPixel(x, halfHeight + y, lightShade + col * 0.75f);

                    // Get data from upper half offset by 1 unit due to int truncation
                    tex.SetPixel(x, halfHeight - (y + 1), lightShade + col * 0.75f);
                }
            }
            tex.Apply();

            return(tex);
        }
        void DrawPlaybackTool()
        {
            GUIContent fContent = new GUIContent("Audio Playback Preview",
                                                 "Allows you to preview how your AudioFileObject will sound during runtime right here in the inspector. " +
                                                 "Some effects, like spatialization and delay, will not be available to preview");

            showPlaybackTool = EditorCompatability.SpecialFoldouts(showPlaybackTool, fContent);

            if (showPlaybackTool)
            {
                var helperSource = AudioPlaybackToolEditor.helperSource;
                ProgressBar(AudioPlaybackToolEditor.helperSource.time / playingClip.length, GetInfoString());

                EditorGUILayout.BeginHorizontal();
                Color colorbackup = GUI.backgroundColor;
                if (clipPlaying)
                {
                    GUI.backgroundColor = buttonPressedColor;
                    fContent            = new GUIContent("Stop", "Stop playback");
                }
                else
                {
                    fContent = new GUIContent("Play", "Play a preview of the sound with it's current sound settings.");
                }
                if (GUILayout.Button(fContent))
                {
                    AudioPlaybackToolEditor.helperSource.Stop();
                    if (playingClip != null && !clipPlaying)
                    {
                        if (playingRandom)
                        {
                            AudioPlaybackToolEditor.DoForceRepaint(true);
                            playingRandom = false;
                        }
                        StartFading();
                    }
                    else
                    {
                        clipPlaying = false;
                    }
                }
                GUI.backgroundColor = colorbackup;
                using (new EditorGUI.DisabledScope(asset.GetFileCount() < 2))
                {
                    if (GUILayout.Button(new GUIContent("Play Random", "Preview settings with a random track from your library. Only usable if this Audio File has \"Use Library\" enabled.")))
                    {
                        DesignateRandomAudioClip();
                        helperSource.Stop();
                        StartFading();
                    }
                }

                if (GUILayout.Button(openIcon, new GUILayoutOption[] { GUILayout.MaxHeight(19) }))
                {
                    AudioPlaybackToolEditor.Init();
                }
                GUILayout.FlexibleSpace();
                EditorGUILayout.EndHorizontal();
            }
            EditorCompatability.EndSpecialFoldoutGroup();
        }