- Home /
Displaying an audio waveform in the editor
Unity's UI includes a display of audio waveforms in it's preview. Is there a simple way to display this, without parsing out the audio file myself?
Answer by CleverToucan · Jan 30, 2019 at 01:32 PM
I made a simpler script to create a waveform texture for GUI elements:
public Texture2D PaintWaveformSpectrum(AudioClip audio, float saturation, int width, int height, Color col) {
Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
float[] samples = new float[audio.samples];
float[] waveform = new float[width];
audio.GetData(samples, 0);
int packSize = ( audio.samples / width ) + 1;
int s = 0;
for (int i = 0; i < audio.samples; i += packSize) {
waveform[s] = Mathf.Abs(samples[i]);
s++;
}
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
tex.SetPixel(x, y, Color.black);
}
}
for (int x = 0; x < waveform.Length; x++) {
for (int y = 0; y <= waveform[x] * ((float)height * .75f); y++) {
tex.SetPixel(x, ( height / 2 ) + y, col);
tex.SetPixel(x, ( height / 2 ) - y, col);
}
}
tex.Apply();
return tex;
}
It's a modified version of @Breakypower's code in this thread: https://answers.unity.com/questions/699595/how-to-generate-waveform-from-audioclip.html
It produces very lovely results for my purposes:
@CleverToucan - great solution, works perfectly! BTW, you never use the saturation param, so that can be removed
Great solution! one comment - the audio.samples property should be multiplied by the audio.channels property anywhere it appears. Otherwise, the waveform might be only partial.
Answer by Unity-Paradox · Jul 01, 2016 at 03:47 PM
What I did was use AudioListener.GetOutputData, and used the output array in a GL drawing function to draw the waveform to the screen. Here's the script:
using UnityEngine;
using System.Collections;
public class ScopeViewer : MonoBehaviour {
float[][] audioData = new float[2][];
Material glMat;
void Start () {
if (!glMat) {
audioData[0] = new float[2048];
audioData[1] = new float[2048];
Shader glSh = Shader.Find("Hidden/Internal-Colored");
glMat = new Material(glSh);
glMat.hideFlags = HideFlags.HideAndDontSave;
glMat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
glMat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
glMat.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
glMat.SetInt("_ZWrite", 0);
}
}
void OnRenderObject () {
AudioListener.GetOutputData(audioData[0], 0);
AudioListener.GetOutputData(audioData[1], 1);
float[] highestOutput = new float[2];
highestOutput[0] = 0;
highestOutput[1] = 0;
for (int i=0; i<audioData[0].Length; i++) {
if (Mathf.Abs(audioData[0][i]) > highestOutput[0]) highestOutput[0] = Mathf.Abs(audioData[0][i]);
if (Mathf.Abs(audioData[1][i]) > highestOutput[1]) highestOutput[1] = Mathf.Abs(audioData[1][i]);
}
if (highestOutput[0] == 0) highestOutput[0] = 1;
if (highestOutput[1] == 0) highestOutput[1] = 1;
glMat.SetPass(0);
GL.PushMatrix();
GL.MultMatrix(transform.localToWorldMatrix);
GL.Begin(GL.QUADS);
GL.Color(Color.white);
GL.Vertex(new Vector3(-0.525f, 0.55f, 0));
GL.Vertex(new Vector3(0.525f, 0.55f, 0));
GL.Vertex(new Vector3(0.525f, -0.55f, 0));
GL.Vertex(new Vector3(-0.525f, -0.55f, 0));
GL.Color(Color.black);
GL.Vertex(new Vector3(-0.5f, 0.5f, 0));
GL.Vertex(new Vector3(0.5f, 0.5f, 0));
GL.Vertex(new Vector3(0.5f, -0.5f, 0));
GL.Vertex(new Vector3(-0.5f, -0.5f, 0));
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.white);
for (int i=0; i<audioData[0].Length/5-1; i++) {
GL.Vertex(new Vector3((float)i/(audioData[0].Length/5)-0.5f, 0.5f*audioData[0][i]/highestOutput[0], 0));
GL.Vertex(new Vector3((float)(i+1)/(audioData[0].Length/5)-0.5f, 0.5f*audioData[0][i+1]/highestOutput[0], 0));
}
for (int i=0; i<audioData[1].Length/5-1; i++) {
GL.Vertex(new Vector3((float)i/(audioData[1].Length/5)-0.5f, 0.5f*audioData[1][i]/highestOutput[1], 0));
GL.Vertex(new Vector3((float)(i+1)/(audioData[1].Length/5)-0.5f, 0.5f*audioData[1][i+1]/highestOutput[1], 0));
}
GL.End();
GL.PopMatrix();
}
}
Please note, this code IS somewhat complex and a bit specialized to what I was using it for, but if you know your stuff, it shouldn't be too much of an issue to change the code to suit yourself :)
Answer by Bunny83 · Jul 01, 2016 at 01:01 PM
Since the question got bumped here are the two ways the default inspector draws the wave preview:
AssetPreview.GetAssetPreview(audioClip);
AudioUtil.GetWaveFormFast(audioClip, 1, 0, audioClip.samples, r.width, r.height);
Both methods will return a Texture2D. The first one is used when the inspector width is smaller than 100 pixels, the second is used when it's greater or equal to 100.
However the AudioUtil class is an internal class of the editor so you can't use it directly without reflection.
For anyone wondering how to use AudioUtil, I found this very helpful thread including a wrapper class: https://forum.unity3d.com/threads/reflected-audioutil-class-for-making-audio-based-editor-extensions.308133/
Unfortunately I am unable to get this to work. Trying to get the method GetWaveFormFast returns null. I've used Get$$anonymous$$ethods to see a full list of methods for AudioUtil, and GetWaveFormFast is not present. Has this been moved elsewhere, or is there a better way to draw audio waveforms in the inspector? Or am I doing something wrong here?
$$anonymous$$ethodInfo[] methods = audioUtilClass.Get$$anonymous$$ethods(BindingFlags.Static | BindingFlags.Public);
string log = "$$anonymous$$ethods:"+methods.Length+"\n";
foreach($$anonymous$$ethodInfo mi in methods) {
log += mi+"\n";
}
Debug.Log(log);
$$anonymous$$ethodInfo method = audioUtilClass.Get$$anonymous$$ethod("GetWaveFormFast", BindingFlags.Static | BindingFlags.Public);
if(method == null) {
Debug.LogWarning("Failed getting method GetWaveFormFast");
}
else {
texture = (Texture2D)method.Invoke(null, new object[] {clip, channel, fromSample, toSample, width, height});
}
Ugh, don't use reflection to poke around classes. Download ILSpy and simply open the UnityEditor.dll
inside your Unity installation folder. `\Unity\Editor\Data\$$anonymous$$anaged`. Visual Studio as wellas $$anonymous$$onoDevelop also have an assembly browser integrated, but they don't show the actual code, just the signature of the methods. Also i'm not sure if they actually display internal / private classes / members.
Answer by Rijicho_nl · Jan 28, 2020 at 02:00 PM
My solution (Japanese page):
AudioUtil's wrapper: https://qiita.com/Rijicho_nl/items/c7e6a5cb9cf56e52588a
How to render the waveform: https://qiita.com/Rijicho_nl/items/3c51befd7bbe63c00878
Your answer
