Skip to content

Instantly share code, notes, and snippets.

@Frooxius
Created February 10, 2020 05:00
Show Gist options
  • Save Frooxius/659d9316e26059fa2de645b9afba6e3c to your computer and use it in GitHub Desktop.
Save Frooxius/659d9316e26059fa2de645b9afba6e3c to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CodeX;
using BaseX;
namespace FrooxEngine
{
[Category("Assets/Procedural Textures")]
public class AudioWaveformTexture : ProceduralTexture
{
public readonly AssetRef<AudioClip> Clip;
public readonly Sync<color> BackgroundColor;
public readonly Sync<color> ForegroundColor;
// cached spikes
AudioClip spikesClip;
float[] spikes;
// TODO!!! Handle multi channels
AudioClip _audio;
protected override void PrepareAssetUpdateData()
{
_audio = Clip.Asset;
}
protected override void ClearTextureData()
{
_audio = null;
spikes = null;
spikesClip = null;
}
protected override void UpdateTextureData(Bitmap2D tex2D)
{
if(_audio != null)
{
// compute new spikes if necessary
if (spikes == null || spikesClip != _audio || spikes.Length != tex2D.Size.x)
{
int samplesPerPixel = (int)(_audio.Samples / tex2D.Size.x);
spikes = spikes.EnsureExactSize(tex2D.Size.x);
int spikeGenStart = 0;
if (samplesPerPixel <= 1)
{
// simply read the whole audio and interpolate
var bufferList = Pool.BorrowRawValueList<float>();
if (bufferList.Capacity < _audio.Samples)
bufferList.Capacity = (int)_audio.Samples;
float[] audioData = bufferList.Elements;
_audio.Read(0, audioData, 0, (int)_audio.Samples, false);
SignalX.Resample(audioData, spikes);
Pool.Return(ref bufferList);
}
else
{
double startTime = Time.WorldTime;
var bufferList = Pool.BorrowRawValueList<float>();
if (bufferList.Capacity < 512)
bufferList.Capacity = 512;
float[] buffer = bufferList.Elements;
float max = 0f;
/*float sum = 0f;
int count = 0;*/
int spike = 0;
long position = 0;
for (int i = 0; i < _audio.Samples; i++)
{
var bufPos = i % 512;
if (bufPos == 0)
{
position = _audio.Read(position, buffer, 0, 512, false);
if((Time.WorldTime - startTime) >= 0.5f && spike > spikeGenStart)
{
// generate partial texture
GenerateTexture(spikes, spikeGenStart, spike);
spikeGenStart = spike;
startTime = Time.WorldTime;
}
}
var val = MathX.Abs(buffer[bufPos]);
if (val > max)
max = val;
/*sum += val;
count++;*/
if (i % samplesPerPixel == samplesPerPixel - 1)
{
spikes[spike++] = max;//(sum / count);
/*sum = 0f;
count = 0;*/
max = 0;
// Stop generating, a few samples won't be used, but that's acceptable for now
if (spike == spikes.Length)
break;
}
}
Pool.Return(ref bufferList);
}
// generate final texture from the spikes
GenerateTexture(spikes, spikeGenStart);
uploadHint.region = new IntRect(0, 0, 0, 0);
}
}
}
protected override void OnAwake()
{
base.OnAwake();
Size.Value = new int2(1024, 128);
Mipmaps.Value = false;
Format.Value = CodeX.TextureFormat.RGBA32;
ForegroundColor.Value = color.White;
BackgroundColor.Value = color.Black;
WrapModeU.Value = TextureWrapMode.Clamp;
WrapModeV.Value = TextureWrapMode.Clamp;
}
void GenerateTexture(float[] spikes, int from = 0, int to = int.MaxValue)
{
var width = tex2D.Size.x;
var height = tex2D.Size.y;
to = MathX.Min(width, to);
for(int y = 0; y < height; y++)
{
for(int x = from; x < to; x++)
{
float yf = MathX.Abs((y - height / 2f) / (height / 2f));
tex2D.SetPixel(x, y, yf > spikes[x] ? BackgroundColor.Value : ForegroundColor.Value);
}
}
// schedule the region for immediate upload
var hint = new TextureUploadHint();
hint.region = new IntRect(from, 0, to - from, height);
SetFromCurrentBitmap(hint);
}
}
public static class AudioWaveformTextureExtensions
{
public static AudioWaveformTexture GetWaveformTexture(this IAssetProvider<AudioClip> clip)
{
return clip.GetWaveformTexture(color.White, color.Black);
}
public static AudioWaveformTexture GetWaveformTexture(this IAssetProvider<AudioClip> clip,
color foreground, color background)
{
if (clip == null)
return null;
// check if there's one attached already
foreach (var waveTex in clip.Slot.GetComponents<AudioWaveformTexture>())
if (waveTex.Clip.Target == clip && waveTex.ForegroundColor.Value == foreground
&& waveTex.BackgroundColor.Value == background)
return waveTex;
// got here, didn't find one, attach
var newWaveTex = clip.Slot.AttachComponent<AudioWaveformTexture>();
newWaveTex.Clip.Target = clip;
newWaveTex.ForegroundColor.Value = foreground;
newWaveTex.BackgroundColor.Value = background;
return newWaveTex;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment