Created
February 10, 2020 05:00
-
-
Save Frooxius/659d9316e26059fa2de645b9afba6e3c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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