Files
Dino_Love_Simulation/Assets/Easy Audio Cutter/Scripts/Editor/EasyAudioCutter.cs

967 lines
35 KiB
C#

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
namespace EasyAudioCutter
{
public class EasyAudioCutter : EditorWindow
{
private enum Tab { Trim, Merge, AdjustVolume }
[SerializeField]
private Tab currentTab = Tab.Trim;
[SerializeField]
private AudioClip sourceClip;
[SerializeField]
private float trimStart = 0f;
[SerializeField]
private float trimEnd = 1f;
[SerializeField]
private float fadeInDuration = 0f;
[SerializeField]
private float fadeOutDuration = 0f;
[SerializeField]
private AnimationCurve fadeInCurve = AnimationCurve.Linear(0, 0, 1, 1);
[SerializeField]
private AnimationCurve fadeOutCurve = AnimationCurve.Linear(0, 1, 1, 0);
[SerializeField]
private bool reverseAudio = false;
[SerializeField]
private List<AudioClip> mergeClips = new List<AudioClip>();
[SerializeField]
private AudioClip volumeClip;
private AudioSource previewAudioSource;
private AudioClip previewClip;
private float[] waveformSamples;
private const int waveformWidth = 400;
private const float minWaveformHeight = 2f;
[SerializeField]
private bool loopPreview = false;
private double previewStartTime = -1;
[SerializeField]
private float volumeIncrease = 0f;
[SerializeField]
private float previewVolume = 1f;
private const string PREF_PREVIEW_VOLUME = "EasyAudioCutter_PreviewVolume";
[MenuItem("Tools/Easy Audio Cutter")]
public static void ShowWindow()
{
var window = GetWindow<EasyAudioCutter>("Easy Audio Cutter");
window.minSize = new Vector2(400, 400);
window.Show();
}
[MenuItem("Assets/Easy Audio Cutter", false, 20)]
public static void EditSelectedAudioClip()
{
AudioClip selectedClip = Selection.activeObject as AudioClip;
if (selectedClip != null)
{
OpenWithClip(selectedClip);
}
}
[MenuItem("Assets/Easy Audio Cutter", true)]
public static bool ValidateEditSelectedAudioClip()
{
return Selection.activeObject is AudioClip;
}
[MenuItem("CONTEXT/AudioClip/Easy Audio Cutter")]
public static void EditContextAudioClip(MenuCommand command)
{
AudioClip clip = command.context as AudioClip;
if (clip != null)
{
OpenWithClip(clip);
}
}
public static void OpenWithClip(AudioClip clip)
{
var window = GetWindow<EasyAudioCutter>("Easy Audio Cutter");
window.minSize = new Vector2(400, 400);
window.Initialize(clip);
window.Show();
window.Focus();
}
public void Initialize(AudioClip clip)
{
sourceClip = clip;
trimStart = 0f;
trimEnd = sourceClip != null ? sourceClip.length : 1f;
fadeInDuration = 0f;
fadeOutDuration = 0f;
reverseAudio = false;
mergeClips.Clear();
volumeClip = null;
if (currentTab == Tab.Merge && mergeClips.Count == 0) mergeClips.Add(clip);
if (currentTab == Tab.AdjustVolume) volumeClip = clip;
previewVolume = EditorPrefs.GetFloat(PREF_PREVIEW_VOLUME, 1f);
if (previewAudioSource != null)
{
previewAudioSource.Stop();
DestroyPreviewClip();
previewAudioSource.volume = previewVolume;
}
UpdateWaveform();
Repaint();
}
private void OnEnable()
{
previewVolume = EditorPrefs.GetFloat(PREF_PREVIEW_VOLUME, 1f);
GameObject go = new GameObject("AudioPreviewPlayer");
go.hideFlags = HideFlags.HideAndDontSave;
previewAudioSource = go.AddComponent<AudioSource>();
previewAudioSource.playOnAwake = false;
previewAudioSource.loop = false;
previewAudioSource.volume = previewVolume;
Undo.undoRedoPerformed += OnUndoRedo;
}
private void OnDisable()
{
if (previewAudioSource != null)
DestroyImmediate(previewAudioSource.gameObject);
DestroyPreviewClip();
Undo.undoRedoPerformed -= OnUndoRedo;
}
private void OnUndoRedo()
{
if (currentTab == Tab.Trim)
{
UpdateWaveform();
}
Repaint();
}
private void OnGUI()
{
EasyAudioCutterTheme.EnsureStyles();
if (previewAudioSource != null)
{
if (Mathf.Abs(previewAudioSource.volume - previewVolume) > 0.01f)
{
previewAudioSource.volume = previewVolume;
}
if (previewAudioSource.isPlaying)
{
Repaint();
}
}
Color bgColor = EditorGUIUtility.isProSkin ? new Color(0.07f, 0.1f, 0.2f) : new Color(0.8f, 0.8f, 0.8f);
EditorGUI.DrawRect(new Rect(0, 0, position.width, position.height), bgColor);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("Help", EasyAudioCutterTheme.ButtonStyle, GUILayout.Width(100)))
{
DrawHelpDialog();
}
EditorGUILayout.Space(10);
EditorGUI.BeginChangeCheck();
Tab newTab = (Tab)GUILayout.Toolbar((int)currentTab, new string[] { "Trim", "Merge", "Adjust Volume" });
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Tab");
currentTab = newTab;
}
EditorGUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Preview Volume", EasyAudioCutterTheme.SliderLabelStyle, GUILayout.Width(100));
EditorGUI.BeginChangeCheck();
float newVol = EditorGUILayout.Slider(previewVolume, 0f, 1f);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Preview Volume");
previewVolume = newVol;
EditorPrefs.SetFloat(PREF_PREVIEW_VOLUME, previewVolume);
if (previewAudioSource != null) previewAudioSource.volume = previewVolume;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EasyAudioCutterTheme.DrawSeparator();
EditorGUILayout.Space();
switch (currentTab)
{
case Tab.Trim: DrawTrimTab(); break;
case Tab.Merge: DrawMergeTab(); break;
case Tab.AdjustVolume: DrawAdjustVolumeTab(); break;
}
}
private void DrawHelpDialog()
{
string message = "Easy Audio Cutter - Help\n\n" +
"**Trim Tab**\n" +
"• Allows trimming a specific section with fade in/out effects.\n" +
"• Toggle 'Reverse Audio' to play the section backwards.\n" +
"• Click anywhere on the waveform to seek/jump to that time.\n" +
"• Supports adjustable start/end times and custom fade curves.\n" +
"• Real-time waveform visualization with a playhead tracker.\n" +
"• Tip: Use the curve editor for creative fade transitions.\n\n" +
"**Merge Tab**\n" +
"• Combines multiple audio files into a single clip.\n" +
"• Use '↑' and '↓' buttons to reorder clips in the list.\n" +
"• All audio files must have the same format (sample rate/channels).\n" +
"• Provides preview functionality for the merged sequence.\n" +
"• Tip: Verify formats beforehand to avoid merge errors.\n\n" +
"**Adjust Volume Tab**\n" +
"• Adjusts the volume of a single selected audio clip.\n" +
"• Use -1 to 1 range: negative decreases, positive increases volume.\n" +
"• Real-time preview of volume changes before saving.\n" +
"• Saves modified clip with '_Adjusted' suffix.\n" +
"• Tip: Avoid values close to -1 or 1 to prevent clipping.\n\n" +
"**General Information**\n" +
"• Shortcuts: Right-click any Audio Asset or use the '✂' button in the Inspector.\n" +
"• Supports Undo/Redo (Ctrl+Z) for all slider and toggle actions.\n" +
"• Global 'Preview Volume' slider allows adjusting playback level.\n" +
"• Saves all processed audio in WAV format.\n" +
"• Developed and updated as of July 05, 2025.\n";
EditorUtility.DisplayDialog("Easy Audio Cutter - Help", message, "OK");
}
private void DrawTrimTab()
{
EditorGUILayout.LabelField("Trim AudioClip", EasyAudioCutterTheme.HeaderStyle);
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
AudioClip newSource = (AudioClip)EditorGUILayout.ObjectField("Source Clip", sourceClip, typeof(AudioClip), false);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Source Clip");
Initialize(newSource);
}
if (sourceClip == null)
{
EditorGUILayout.HelpBox("Please assign an AudioClip to trim.", MessageType.Info);
return;
}
EditorGUILayout.LabelField($"Length: {sourceClip.length:F2} seconds", EasyAudioCutterTheme.LabelStyle);
EditorGUILayout.Space();
float oldTrimStart = trimStart;
float oldTrimEnd = trimEnd;
float oldFadeIn = fadeInDuration;
float oldFadeOut = fadeOutDuration;
bool oldReverse = reverseAudio;
EditorGUI.BeginChangeCheck();
float newTrimStart = EditorGUILayout.Slider("Start Time", trimStart, 0f, sourceClip.length);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Trim Start");
trimStart = newTrimStart;
}
EditorGUI.BeginChangeCheck();
float newTrimEnd = EditorGUILayout.Slider("End Time", trimEnd, 0f, sourceClip.length);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Trim End");
trimEnd = newTrimEnd;
}
trimEnd = Mathf.Max(trimStart, trimEnd);
EditorGUI.BeginChangeCheck();
float newFadeIn = EditorGUILayout.Slider("Fade In Duration", fadeInDuration, 0f, trimEnd - trimStart);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Fade In");
fadeInDuration = newFadeIn;
}
EditorGUI.BeginChangeCheck();
float newFadeOut = EditorGUILayout.Slider("Fade Out Duration", fadeOutDuration, 0f, trimEnd - trimStart);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Fade Out");
fadeOutDuration = newFadeOut;
}
EditorGUI.BeginChangeCheck();
bool newReverse = EditorGUILayout.Toggle("Reverse Audio", reverseAudio);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Toggle Reverse");
reverseAudio = newReverse;
}
if (!Mathf.Approximately(oldTrimStart, trimStart) ||
!Mathf.Approximately(oldTrimEnd, trimEnd) ||
!Mathf.Approximately(oldFadeIn, fadeInDuration) ||
!Mathf.Approximately(oldFadeOut, fadeOutDuration) ||
oldReverse != reverseAudio)
{
UpdateWaveform();
}
EditorGUILayout.Space();
Rect waveformRect = GUILayoutUtility.GetRect(position.width - 20, 100);
waveformRect.x += 10;
waveformRect.width -= 20;
DrawWaveform(waveformRect);
if (Event.current.type == EventType.MouseDown && waveformRect.Contains(Event.current.mousePosition))
{
if (previewAudioSource != null && previewClip != null)
{
float clickPercent = (Event.current.mousePosition.x - waveformRect.x) / waveformRect.width;
clickPercent = Mathf.Clamp01(clickPercent);
if (!previewAudioSource.isPlaying)
{
CreatePreviewClipAndPlay();
}
previewAudioSource.time = clickPercent * previewClip.length;
Repaint();
}
}
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
bool newLoop = EditorGUILayout.Toggle("Loop Preview", loopPreview);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Toggle Loop");
loopPreview = newLoop;
}
if (GUILayout.Button(previewAudioSource.isPlaying ? "Stop Preview" : "Play Preview", EasyAudioCutterTheme.ButtonStyle))
{
if (previewAudioSource.isPlaying)
{
previewAudioSource.Stop();
DestroyPreviewClip();
}
else
{
CreatePreviewClipAndPlay();
}
}
EditorGUILayout.Space();
if (GUILayout.Button("Process and Save Trimmed Clip", EasyAudioCutterTheme.ButtonStyle))
{
ProcessAndSaveTrimmedClip();
}
}
private void DrawMergeTab()
{
EditorGUILayout.LabelField("Merge AudioClips", EasyAudioCutterTheme.HeaderStyle);
EditorGUILayout.Space();
for (int i = 0; i < mergeClips.Count; i++)
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("↑", GUILayout.Width(20)))
{
if (i > 0)
{
Undo.RecordObject(this, "Move Clip Up");
var temp = mergeClips[i];
mergeClips[i] = mergeClips[i - 1];
mergeClips[i - 1] = temp;
}
}
if (GUILayout.Button("↓", GUILayout.Width(20)))
{
if (i < mergeClips.Count - 1)
{
Undo.RecordObject(this, "Move Clip Down");
var temp = mergeClips[i];
mergeClips[i] = mergeClips[i + 1];
mergeClips[i + 1] = temp;
}
}
EditorGUI.BeginChangeCheck();
AudioClip newClip = (AudioClip)EditorGUILayout.ObjectField(mergeClips[i], typeof(AudioClip), false);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Merge Clip");
mergeClips[i] = newClip;
}
if (GUILayout.Button("X", EasyAudioCutterTheme.ButtonStyle, GUILayout.Width(20)))
{
Undo.RecordObject(this, "Remove Merge Clip");
mergeClips.RemoveAt(i);
i--;
}
EditorGUILayout.EndHorizontal();
}
if (GUILayout.Button("+ Add AudioClip", EasyAudioCutterTheme.ButtonStyle))
{
Undo.RecordObject(this, "Add Merge Clip");
mergeClips.Add(null);
}
EditorGUILayout.Space();
if (mergeClips.Count >= 2)
{
if (GUILayout.Button(previewAudioSource.isPlaying ? "Stop Preview" : "Play Merged Preview", EasyAudioCutterTheme.ButtonStyle))
{
if (previewAudioSource.isPlaying)
{
previewAudioSource.Stop();
DestroyPreviewClip();
}
else
{
CreateMergedPreviewClipAndPlay();
}
}
EditorGUILayout.Space();
if (GUILayout.Button("Process and Save Merged Clip", EasyAudioCutterTheme.ButtonStyle))
{
ProcessAndSaveMergedClip();
}
}
else
{
EditorGUILayout.HelpBox("Add at least two AudioClips to merge.", MessageType.Info);
}
}
private void DrawAdjustVolumeTab()
{
EditorGUILayout.LabelField("Adjust Volume", EasyAudioCutterTheme.HeaderStyle);
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
AudioClip newVolumeClip = (AudioClip)EditorGUILayout.ObjectField("Audio Clip", volumeClip, typeof(AudioClip), false);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Volume Clip");
volumeClip = newVolumeClip;
}
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Volume Adjustment", EasyAudioCutterTheme.SliderLabelStyle);
float newVolume = EditorGUILayout.Slider(volumeIncrease, -1f, 1f);
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "Change Volume Amount");
volumeIncrease = newVolume;
}
EditorGUILayout.LabelField("Note: -1 to 1 range.", EditorStyles.helpBox);
if (volumeClip != null)
{
if (GUILayout.Button("Apply Volume Adjustment (Preview)", EasyAudioCutterTheme.ButtonStyle))
{
ApplyVolumeIncrease();
}
EditorGUILayout.Space();
if (GUILayout.Button("Process and Save Adjusted Clip", EasyAudioCutterTheme.ButtonStyle))
{
ProcessAndSaveAdjustedClips();
}
}
else
{
EditorGUILayout.HelpBox("Add an AudioClip to adjust volume.", MessageType.Info);
}
}
private void DestroyPreviewClip()
{
if (previewClip != null)
{
DestroyImmediate(previewClip);
previewClip = null;
previewStartTime = -1;
}
}
private void CreatePreviewClipAndPlay()
{
if (sourceClip == null || trimEnd <= trimStart) return;
previewStartTime = EditorApplication.timeSinceStartup;
int freq = sourceClip.frequency;
int channels = sourceClip.channels;
int startSample = Mathf.FloorToInt(trimStart * freq) * channels;
int sampleLength = Mathf.FloorToInt((trimEnd - trimStart) * freq) * channels;
float[] allData = new float[sourceClip.samples * channels];
sourceClip.GetData(allData, 0);
float[] trimmedData = new float[sampleLength];
System.Array.Copy(allData, startSample, trimmedData, 0, sampleLength);
if (reverseAudio)
{
System.Array.Reverse(trimmedData);
}
ApplyFades(trimmedData, freq, channels);
previewClip = AudioClip.Create("PreviewTrim", sampleLength / channels, channels, freq, false);
previewClip.SetData(trimmedData, 0);
previewAudioSource.clip = previewClip;
previewAudioSource.volume = previewVolume;
previewAudioSource.loop = loopPreview;
previewAudioSource.Play();
}
private void ProcessAndSaveTrimmedClip()
{
if (sourceClip == null) return;
int freq = sourceClip.frequency;
int channels = sourceClip.channels;
int startSample = Mathf.FloorToInt(trimStart * freq) * channels;
int sampleLength = Mathf.FloorToInt((trimEnd - trimStart) * freq) * channels;
float[] srcData = new float[sourceClip.samples * channels];
sourceClip.GetData(srcData, 0);
float[] trimmedData = new float[sampleLength];
System.Array.Copy(srcData, startSample, trimmedData, 0, sampleLength);
if (reverseAudio)
{
System.Array.Reverse(trimmedData);
}
ApplyFades(trimmedData, freq, channels);
SaveWav(trimmedData, freq, channels, "Trimmed_" + sourceClip.name + (reverseAudio ? "_Reverse" : ""));
}
private void ApplyFades(float[] data, int freq, int channels)
{
int fadeInSamples = Mathf.FloorToInt(fadeInDuration * freq) * channels;
for (int i = 0; i < fadeInSamples && i < data.Length; i++)
{
float t = (float)i / fadeInSamples;
data[i] *= fadeInCurve.Evaluate(t);
}
int fadeOutSamples = Mathf.FloorToInt(fadeOutDuration * freq) * channels;
int startFadeOut = data.Length - fadeOutSamples;
for (int i = 0; i < fadeOutSamples && (startFadeOut + i) < data.Length; i++)
{
float t = (float)i / fadeOutSamples;
data[startFadeOut + i] *= fadeOutCurve.Evaluate(t);
}
}
private void CreateMergedPreviewClipAndPlay()
{
if (mergeClips.Count < 2) return;
int freq = mergeClips[0].frequency;
int channels = mergeClips[0].channels;
List<float> mergedSamples = new List<float>();
foreach (var clip in mergeClips)
{
if (clip == null || clip.frequency != freq || clip.channels != channels)
{
EditorUtility.DisplayDialog("Error", "All clips must have same format.", "OK");
return;
}
float[] data = new float[clip.samples * channels];
clip.GetData(data, 0);
mergedSamples.AddRange(data);
}
float[] mergedData = mergedSamples.ToArray();
int samples = mergedData.Length / channels;
DestroyPreviewClip();
previewClip = AudioClip.Create("PreviewMerged", samples, channels, freq, false);
previewClip.SetData(mergedData, 0);
previewAudioSource.clip = previewClip;
previewAudioSource.volume = previewVolume;
previewAudioSource.loop = loopPreview;
previewAudioSource.Play();
}
private void ProcessAndSaveMergedClip()
{
if (mergeClips.Count < 2) return;
int freq = mergeClips[0].frequency;
int channels = mergeClips[0].channels;
List<float> mergedSamples = new List<float>();
foreach (var clip in mergeClips)
{
if (clip == null || clip.frequency != freq || clip.channels != channels)
{
EditorUtility.DisplayDialog("Error", "All clips must have same format.", "OK");
return;
}
float[] data = new float[clip.samples * channels];
clip.GetData(data, 0);
mergedSamples.AddRange(data);
}
SaveWav(mergedSamples.ToArray(), freq, channels, "MergedAudio");
}
private void ApplyVolumeIncrease()
{
if (previewAudioSource.isPlaying)
{
previewAudioSource.Stop();
DestroyPreviewClip();
}
if (volumeClip != null)
{
float[] data = new float[volumeClip.samples * volumeClip.channels];
volumeClip.GetData(data, 0);
for (int i = 0; i < data.Length; i++)
{
data[i] += data[i] * volumeIncrease;
if (data[i] > 1f) data[i] = 1f;
if (data[i] < -1f) data[i] = -1f;
}
int samples = data.Length / volumeClip.channels;
AudioClip tempClip = AudioClip.Create(volumeClip.name + "_Temp", samples, volumeClip.channels, volumeClip.frequency, false);
tempClip.SetData(data, 0);
previewAudioSource.clip = tempClip;
previewAudioSource.volume = previewVolume;
previewAudioSource.loop = false;
previewAudioSource.Play();
}
}
private void ProcessAndSaveAdjustedClips()
{
if (volumeClip != null)
{
float[] data = new float[volumeClip.samples * volumeClip.channels];
volumeClip.GetData(data, 0);
for (int i = 0; i < data.Length; i++)
{
data[i] += data[i] * volumeIncrease;
if (data[i] > 1f) data[i] = 1f;
if (data[i] < -1f) data[i] = -1f;
}
SaveWav(data, volumeClip.frequency, volumeClip.channels, volumeClip.name + "_Adjusted");
EditorUtility.DisplayDialog("Done", "Adjusted clip saved!", "OK");
}
}
private void SaveWav(float[] data, int frequency, int channels, string defaultName)
{
int samples = data.Length / channels;
AudioClip clip = AudioClip.Create(defaultName, samples, channels, frequency, false);
clip.SetData(data, 0);
string path = EditorUtility.SaveFilePanelInProject("Save Audio", defaultName + ".wav", "wav", "Choose location");
if (!string.IsNullOrEmpty(path))
{
byte[] bytes = WavUtility.FromAudioClip(clip);
File.WriteAllBytes(path, bytes);
AssetDatabase.ImportAsset(path);
EditorUtility.DisplayDialog("Done", "Audio saved!", "OK");
}
}
private void UpdateWaveform()
{
if (sourceClip == null)
{
waveformSamples = null;
return;
}
int freq = sourceClip.frequency;
int channels = sourceClip.channels;
int startSample = Mathf.FloorToInt(trimStart * freq) * channels;
int lengthSamples = Mathf.FloorToInt((trimEnd - trimStart) * freq) * channels;
float[] allData = new float[sourceClip.samples * channels];
sourceClip.GetData(allData, 0);
float[] trimmedData = new float[lengthSamples];
System.Array.Copy(allData, startSample, trimmedData, 0, lengthSamples);
if (reverseAudio)
{
System.Array.Reverse(trimmedData);
}
ApplyFades(trimmedData, freq, channels);
int samplesPerPixel = Mathf.Max(1, trimmedData.Length / waveformWidth);
waveformSamples = new float[waveformWidth];
for (int i = 0; i < waveformWidth; i++)
{
float max = 0f;
int start = i * samplesPerPixel;
int end = Mathf.Min(start + samplesPerPixel, trimmedData.Length);
for (int j = start; j < end; j += channels)
{
float val = Mathf.Abs(trimmedData[j]);
if (val > max) max = val;
}
waveformSamples[i] = max;
}
Repaint();
}
private void DrawWaveform(Rect rect)
{
if (waveformSamples == null || waveformSamples.Length == 0) return;
Color waveBgColor = EditorGUIUtility.isProSkin ? new Color(0.05f, 0.08f, 0.15f) : new Color(0.9f, 0.9f, 0.9f);
EditorGUI.DrawRect(rect, waveBgColor);
float midY = rect.y + rect.height / 2f;
Handles.BeginGUI();
Handles.color = new Color(1f, 0.6f, 0f);
float totalWaveformWidth = waveformSamples.Length;
float xOffset = (rect.width - totalWaveformWidth) / 2f;
for (int i = 0; i < waveformSamples.Length; i++)
{
float x = rect.x + xOffset + i;
float height = Mathf.Max(minWaveformHeight, waveformSamples[i] * rect.height);
Handles.DrawLine(
new Vector3(x, midY - height / 2),
new Vector3(x, midY + height / 2)
);
}
float startX = rect.x + xOffset;
float endX = rect.x + xOffset + waveformSamples.Length;
Handles.color = EditorGUIUtility.isProSkin ? Color.white : Color.black;
Handles.DrawLine(new Vector3(startX, rect.y), new Vector3(startX, rect.y + rect.height));
Handles.DrawLine(new Vector3(endX, rect.y), new Vector3(endX, rect.y + rect.height));
if (previewAudioSource != null && previewAudioSource.isPlaying && previewClip != null)
{
float progress = previewAudioSource.time / previewClip.length;
float playheadX = rect.x + (progress * rect.width);
Handles.color = Color.red;
Handles.DrawLine(new Vector3(playheadX, rect.y), new Vector3(playheadX, rect.y + rect.height));
}
Handles.EndGUI();
}
}
[CustomEditor(typeof(AudioClip))]
public class AudioClipInspectorOverride : Editor
{
private Editor _defaultEditor;
private void OnEnable()
{
var type = typeof(Editor).Assembly.GetType("UnityEditor.AudioClipInspector");
if (type != null)
_defaultEditor = CreateEditor(target, type);
}
private void OnDisable()
{
if (_defaultEditor != null) DestroyImmediate(_defaultEditor);
}
public override void OnInspectorGUI()
{
if (_defaultEditor != null)
{
_defaultEditor.OnInspectorGUI();
}
else
{
base.OnInspectorGUI();
}
}
public override bool HasPreviewGUI()
{
return _defaultEditor != null && _defaultEditor.HasPreviewGUI();
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
if (_defaultEditor != null) _defaultEditor.OnPreviewGUI(r, background);
}
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
{
if (_defaultEditor != null) _defaultEditor.OnInteractivePreviewGUI(r, background);
}
public override void OnPreviewSettings()
{
if (_defaultEditor != null)
{
_defaultEditor.OnPreviewSettings();
}
if (GUILayout.Button(new GUIContent("✂", "Open in Easy Audio Cutter"), EditorStyles.toolbarButton, GUILayout.Width(30)))
{
EasyAudioCutter.OpenWithClip((AudioClip)target);
}
}
}
public static class EasyAudioCutterTheme
{
public static GUIStyle HeaderStyle { get; private set; }
public static GUIStyle ButtonStyle { get; private set; }
public static GUIStyle SliderLabelStyle { get; private set; }
public static GUIStyle LabelStyle { get; private set; }
private static bool isInitialized = false;
private static bool wasProSkin = false;
public static void EnsureStyles()
{
if (!isInitialized || wasProSkin != EditorGUIUtility.isProSkin)
{
SetupStyles();
}
}
public static void DrawSeparator()
{
var rect = GUILayoutUtility.GetRect(1f, 1f);
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.5f));
}
private static void SetupStyles()
{
wasProSkin = EditorGUIUtility.isProSkin;
bool isDark = wasProSkin;
HeaderStyle = new GUIStyle(EditorStyles.boldLabel);
HeaderStyle.normal.textColor = isDark ? new Color(0.4f, 0.8f, 1f) : new Color(0.1f, 0.3f, 0.5f);
HeaderStyle.fontSize = 14;
ButtonStyle = new GUIStyle(GUI.skin.button);
ButtonStyle.normal.textColor = isDark ? Color.white : Color.black;
ButtonStyle.fontSize = 12;
ButtonStyle.padding = new RectOffset(6, 6, 4, 4);
SliderLabelStyle = new GUIStyle(EditorStyles.label);
SliderLabelStyle.normal.textColor = isDark ? Color.cyan : new Color(0.0f, 0.4f, 0.7f);
LabelStyle = new GUIStyle(EditorStyles.label);
LabelStyle.normal.textColor = isDark ? Color.white : Color.black;
isInitialized = true;
}
}
public static class WavUtility
{
public static byte[] FromAudioClip(AudioClip clip)
{
using (var memoryStream = new MemoryStream())
{
using (var writer = new BinaryWriter(memoryStream))
{
var hz = clip.frequency;
var channels = clip.channels;
var samples = clip.samples;
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
writer.Write(36 + samples * channels * 2);
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
writer.Write(Encoding.ASCII.GetBytes("fmt "));
writer.Write(16);
writer.Write((ushort)1);
writer.Write((ushort)channels);
writer.Write(hz);
writer.Write(hz * channels * 2);
writer.Write((ushort)(channels * 2));
writer.Write((ushort)16);
writer.Write(Encoding.ASCII.GetBytes("data"));
writer.Write(samples * channels * 2);
float[] data = new float[samples * channels];
clip.GetData(data, 0);
foreach (var sample in data)
{
writer.Write((short)(sample * 32767f));
}
}
return memoryStream.ToArray();
}
}
}
}