Editor/AudioWaveForm.cs
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
namespace Ardi;
public class AudioWaveForm : GraphicsItem
{
private struct Column
{
public float top;
public float bottom;
}
private readonly AudioTimelineView TimelineView;
private readonly List<Column> Columns = new List<Column>();
private bool isDirty;
public short[] Samples { get; private set; }
public float Duration { get; private set; }
public int SampleCount => Samples?.Length ?? 0;
public int SampleRate { get; private set; }
private short MinSample = short.MaxValue;
private short MaxSample = short.MinValue;
private int SamplesPerColumn;
private const float LineWidth = 1f;
private float LineSize => 1f;
public AudioWaveForm( AudioTimelineView view ) : base( null )
{
TimelineView = view;
ZIndex = -1f;
}
protected override void OnPaint()
{
base.OnPaint();
if ( isDirty )
{
Analyse();
}
Paint.Antialiasing = false;
Paint.ClearPen();
Paint.SetBrush( Theme.ControlBackground );
Paint.DrawRect( LocalRect );
if ( Columns.Count > 0 )
{
Paint.ClearPen();
Paint.SetBrush( Theme.Primary );
var height = LocalRect.Height;
var startCol = (int)(TimelineView.VisibleRect.Left / LineSize);
var endCol = (int)(TimelineView.VisibleRect.Right / LineSize);
for ( int i = startCol; i <= endCol && i <= Columns.Count - 1; i++ )
{
var column = Columns[i];
var top = height * column.top;
var bottom = height * column.bottom;
var rect = new Rect(
new Vector2( i * LineSize, bottom ),
new Vector2( 1f, Math.Max( 1f, top - bottom ) )
);
Paint.DrawRect( rect );
}
}
if ( TimelineView.IsSplitMode )
{
Paint.SetPen( Theme.Red );
foreach ( var point in TimelineView.SplitPoints )
{
var x = (float)point / SampleCount * Width;
if ( x >= TimelineView.VisibleRect.Left && x <= TimelineView.VisibleRect.Right )
{
Paint.DrawLine( new Vector2( x, 0 ), new Vector2( x, LocalRect.Height ) );
}
}
}
else
{
Paint.SetPen( Theme.Green );
foreach ( var point in TimelineView.LoopPoints )
{
var x = (float)point / SampleCount * Width;
if ( x >= TimelineView.VisibleRect.Left && x <= TimelineView.VisibleRect.Right )
{
Paint.DrawLine( new Vector2( x, 0 ), new Vector2( x, LocalRect.Height ) );
}
}
}
}
public void SetSamples( short[] samples, float duration )
{
Samples = samples;
Duration = duration;
SampleRate = (int)(samples.Length / duration);
Width = Duration * LineSize;
isDirty = true;
}
public void Analyse()
{
isDirty = false;
MinSample = short.MaxValue;
MaxSample = short.MinValue;
Columns.Clear();
if ( Samples == null || Samples.Length == 0 ) return;
var sampleCount = Samples.Length;
for ( int i = 0; i < sampleCount; i++ )
{
var sample = Samples[i];
MinSample = Math.Min( sample, MinSample );
MaxSample = Math.Max( sample, MaxSample );
}
var maxRange = Math.Max( Math.Abs( MinSample ), Math.Abs( MaxSample ) );
var minRange = -maxRange;
var range = minRange - maxRange;
var columnCount = MathX.FloorToInt( TimelineView.PositionFromTime( TimelineView.Duration ) / LineSize );
SamplesPerColumn = sampleCount / columnCount;
for ( int j = 0; j < columnCount - 1; j++ )
{
var startIndex = j * SamplesPerColumn;
var endIndex = (j + 1) * SamplesPerColumn;
GetAverages( Samples, startIndex, endIndex, out var posAvg, out var negAvg );
Columns.Add( new Column
{
top = range != 0f ? (negAvg - maxRange) / range : 0.5f,
bottom = range != 0f ? (posAvg - maxRange) / range : 0.5f
} );
}
Update();
}
private static void GetAverages( short[] data, int startIndex, int endIndex, out float posAvg, out float negAvg )
{
posAvg = 0f;
negAvg = 0f;
int posCount = 0;
int negCount = 0;
for ( int i = startIndex; i < endIndex && i < data.Length; i++ )
{
if ( data[i] > 0 )
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
if ( posCount > 0 ) posAvg /= posCount;
if ( negCount > 0 ) negAvg /= negCount;
}
}