public override void OnInspectorGUI()
        {
            serializedObject.Update();

            List <string> excludedProperties = new List <string> {
                "m_Script"
            };

            GUIContent blontent;

            //if (!showAdvancedSettings)
            //{
            //    excludedProperties.AddRange(new List<string>
            //    {
            //
            //    });
            //
            //    blontent = new GUIContent("↓ Show Advanced Settings ↓", "Toggle this if you're an experienced Unity user");
            //}
            //else
            //{
            //    blontent = new GUIContent("↑ Hide Advanced Settings ↑", "Toggle this if you're an experienced Unity user");
            //}
            excludedProperties.AddRange(new List <string> {
                "listener", "sourcePrefab"
            });

            //if (GUILayout.Button(blontent))
            //{
            //    showAdvancedSettings = !showAdvancedSettings;
            //}

            //EditorGUILayout.Space();

            blontent           = new GUIContent("Volume Controls", "Change the volume levels of all AudioManager-controlled audio channels here");
            showVolumeSettings = EditorCompatability.SpecialFoldouts(showVolumeSettings, blontent);
            if (showVolumeSettings)
            {
                DrawAdvancedVolumeControls(myScript);
            }
            EditorCompatability.EndSpecialFoldoutGroup();

            EditorGUILayout.Space();

            EditorGUILayout.BeginHorizontal();
            blontent = new GUIContent("Library");
            EditorGUI.BeginChangeCheck();
            libraryIndex = EditorGUILayout.Popup(blontent, libraryIndex, AudioLibraryEditor.projectLibrariesNames.ToArray());
            if (EditorGUI.EndChangeCheck())
            {
                library.objectReferenceValue = AudioLibraryEditor.projectLibraries[libraryIndex];
            }
            blontent = new GUIContent(" Open ");
            if (GUILayout.Button(blontent, new GUILayoutOption[] { GUILayout.ExpandWidth(false) }))
            {
                AudioLibraryEditor.Init();
            }
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            blontent = new GUIContent("Settings");
            EditorGUILayout.PropertyField(settings);
            blontent = new GUIContent(" Open ");
            if (GUILayout.Button(blontent, new GUILayoutOption[] { GUILayout.ExpandWidth(false) }))
            {
                AudioManagerSettingsEditor.Init();
            }
            EditorGUILayout.EndHorizontal();

            DrawPropertiesExcluding(serializedObject, excludedProperties.ToArray());

            //if (myScript.GetListenerInternal() == null || showAdvancedSettings)
            {
                EditorGUILayout.PropertyField(listener);
            }

            #region Source Prefab Helper
            if (!myScript.SourcePrefabExists())
            {
                EditorGUILayout.PropertyField(sourcePrefab);

                EditorGUILayout.HelpBox("Reference to Source Prefab is missing! This prefab is required to make " +
                                        "AudioManager function. Click the button below to have AudioManager reapply the default reference.", MessageType.Warning);
                GUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                if (GUILayout.Button("Reapply Default AudioSource Prefab"))
                {
                    string[] GUIDs = AssetDatabase.FindAssets("Audio Channel t:GameObject");

                    GameObject fallback = null;

                    foreach (string s in GUIDs)
                    {
                        GameObject theObject = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(s), typeof(GameObject)) as GameObject;
                        if (theObject.GetComponent <AudioSource>())
                        {
                            fallback = theObject;
                            break;
                        }
                    }
                    if (fallback != null) // Check has succeeded in finding the default reference
                    {
                        sourcePrefab.objectReferenceValue = fallback;
                    }
                    else // Check has failed to turn up results
                    {
                        GameObject  newPrefab = new GameObject("Audio Channel");
                        AudioSource theSource = newPrefab.AddComponent <AudioSource>();
                        theSource.rolloffMode = AudioRolloffMode.Logarithmic;
                        theSource.minDistance = 0.5f;
                        theSource.maxDistance = 7;
                        newPrefab.AddComponent <AudioChannelHelper>();

                        // Look for AudioManager so we can put the new prefab next to it
                        string assetPath = AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("Audio Manager t:GameObject")[0]);
                        assetPath  = assetPath.Substring(0, assetPath.LastIndexOf("/") + 1);
                        assetPath += "Audio Channel.prefab";
                        bool success = false;
                        PrefabUtility.SaveAsPrefabAsset(newPrefab, assetPath, out success);
                        if (success)
                        {
                            sourcePrefab.objectReferenceValue = newPrefab;
                            EditorUtility.DisplayDialog("Success", "AudioManager's default source prefab was missing. So a new one was recreated in it's place. " +
                                                        "If AudioManager doesn't immediately update with the Audio Source prefab in place, click the button again or recompile your code.", "OK");
                        }
                        DestroyImmediate(newPrefab);
                    }
                }
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
            }
            else if (myScript.SourcePrefabExists() /* && showAdvancedSettings*/)
            {
                EditorGUILayout.PropertyField(sourcePrefab);
            }
            #endregion

            EditorGUILayout.Space();

            if (serializedObject.hasModifiedProperties)
            {
                serializedObject.ApplyModifiedProperties();
            }

            if (myScript.GetMasterVolumeInternal() == 0)
            {
                EditorGUILayout.HelpBox("Note: Master Volume is set to 0!", MessageType.Info);
            }
            if (myScript.GetSoundVolumeInternal() == 0)
            {
                EditorGUILayout.HelpBox("Note: Sound is set to 0!", MessageType.Info);
            }
            if (myScript.GetMusicVolumeInternal() == 0)
            {
                EditorGUILayout.HelpBox("Note: Music is set to 0!", MessageType.Info);
            }

            #region Quick Reference Guide
            showHowTo = EditorCompatability.SpecialFoldouts(showHowTo, "Quick Reference Guide");
            if (showHowTo)
            {
                JSAMEditorHelper.RenderHelpbox("Overview");
                JSAMEditorHelper.RenderHelpbox("This component is the backbone of the entire JSAM Audio Manager system and ideally should occupy it's own gameobject.");
                JSAMEditorHelper.RenderHelpbox("Remember to mouse over the various menu options in this and other JSAM windows to learn more about them!");
                JSAMEditorHelper.RenderHelpbox("Please ensure that you don't have multiple AudioManagers in one scene.");
                JSAMEditorHelper.RenderHelpbox(
                    "If you have any questions, suggestions or bug reports, feel free to open a new issue " +
                    "on Github repository's Issues page or send me an email directly!"
                    );

                EditorGUILayout.Space();

                JSAMEditorHelper.RenderHelpbox("Tips");
                JSAMEditorHelper.RenderHelpbox(
                    "The Github Repository is usually more up to date with bug fixes " +
                    "than what's shown on the Unity Asset Store, so give it a look just in case!"
                    );
                JSAMEditorHelper.RenderHelpbox(
                    "Here are some helpful links, more of which can be found under\nWindows -> JSAM -> JSAM Startup"
                    );
                EditorGUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                if (GUILayout.Button(new GUIContent("Report a Bug", "Click on me to go to the bug report page in a new browser window"), new GUILayoutOption[] { GUILayout.MinWidth(100) }))
                {
                    Application.OpenURL("https://github.com/jackyyang09/Simple-Unity-Audio-Manager/issues");
                }
                GUILayout.FlexibleSpace();
                if (GUILayout.Button(new GUIContent("Github Releases", "Click on me to check out the latest releases in a new browser window"), new GUILayoutOption[] { GUILayout.MinWidth(100) }))
                {
                    Application.OpenURL("https://github.com/jackyyang09/Simple-Unity-Audio-Manager/releases");
                }
                GUILayout.FlexibleSpace();
                if (GUILayout.Button(new GUIContent("Email", "You can find me at [email protected]"), new GUILayoutOption[] { GUILayout.MinWidth(100) }))
                {
                    Application.OpenURL("mailto:[email protected]");
                }
                GUILayout.FlexibleSpace();
                EditorGUILayout.EndHorizontal();
            }
            EditorCompatability.EndSpecialFoldoutGroup();
            #endregion
        }
Exemple #2
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
        }