Funkin/source/funkin/ui/debug/WaveformTestState.hx
2024-01-20 14:07:48 -05:00

202 lines
6 KiB
Haxe

package funkin.ui.debug;
import flixel.FlxSprite;
import flixel.util.FlxColor;
import funkin.audio.FunkinSound;
import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser;
import funkin.graphics.rendering.MeshRender;
class WaveformTestState extends MusicBeatState
{
public function new()
{
super();
}
var waveformData:WaveformData;
var waveformAudio:FunkinSound;
var meshRender:MeshRender;
var timeMarker:FlxSprite;
public override function create():Void
{
super.create();
waveformData = WaveformDataParser.parseWaveformData(Paths.json("waveform/dadbattle-erect/dadbattle-erect.waveform"));
waveformAudio = FunkinSound.load(Paths.music('dadbattle-erect/dadbattle-erect'));
var lightBlue:FlxColor = FlxColor.fromString("#ADD8E6");
meshRender = new MeshRender(0, 0, lightBlue);
add(meshRender);
timeMarker = new FlxSprite(0, FlxG.height * 1 / 6);
timeMarker.makeGraphic(1, Std.int(FlxG.height * 2 / 3), FlxColor.RED);
add(timeMarker);
drawWaveform(time, duration);
}
/**
* @param offsetX Horizontal offset to draw the waveform at, in samples.
*/
function drawWaveform(timeSeconds:Float, duration:Float):Void
{
meshRender.clear();
var offsetX:Int = waveformData.secondsToIndex(timeSeconds);
var waveformHeight:Int = Std.int(FlxG.height * (2 / 3));
var waveformWidth:Int = FlxG.width;
var waveformCenterPos:Int = Std.int(FlxG.height / 2);
var oneSecondInIndices:Int = waveformData.secondsToIndex(1);
var startTime:Float = -1.0;
var endTime:Float = startTime + duration;
var startIndex:Int = Std.int(offsetX + (oneSecondInIndices * startTime));
var endIndex:Int = Std.int(offsetX + (oneSecondInIndices * (startTime + duration)));
var pixelsPerIndex:Float = waveformWidth / (endIndex - startIndex);
var indexesPerPixel:Float = (endIndex - startIndex) / waveformWidth;
if (pixelsPerIndex >= 1.0)
{
// Each index is at least one pixel wide, so we render each index.
var prevVertexTopIndex:Int = -1;
var prevVertexBottomIndex:Int = -1;
for (i in startIndex...endIndex)
{
var pixelPos:Int = Std.int((i - startIndex) * pixelsPerIndex);
var vertexTopY:Int = Std.int(waveformCenterPos - (waveformData.channel(0).maxSampleMapped(i) * waveformHeight / 2));
var vertexBottomY:Int = Std.int(waveformCenterPos + (-waveformData.channel(0).minSampleMapped(i) * waveformHeight / 2));
var vertexTopIndex:Int = meshRender.build_vertex(pixelPos, vertexTopY);
var vertexBottomIndex:Int = meshRender.build_vertex(pixelPos, vertexBottomY);
if (prevVertexTopIndex != -1 && prevVertexBottomIndex != -1)
{
meshRender.add_quad(prevVertexTopIndex, vertexTopIndex, vertexBottomIndex, prevVertexBottomIndex);
}
else
{
trace('Skipping quad at index ${i}');
}
prevVertexTopIndex = vertexTopIndex;
prevVertexBottomIndex = vertexBottomIndex;
}
}
else
{
// Indexes are less than one pixel wide, so for each pixel we render the maximum of the samples that fall within it.
var prevVertexTopIndex:Int = -1;
var prevVertexBottomIndex:Int = -1;
for (i in 0...waveformWidth)
{
// Wrap Std.int around the whole range calculation, not just indexesPerPixel, otherwise you get weird issues with zooming.
var rangeStart:Int = Std.int(i * indexesPerPixel + startIndex);
var rangeEnd:Int = Std.int((i + 1) * indexesPerPixel + startIndex);
var vertexTopY:Int = Std.int(waveformCenterPos - (waveformData.channel(0).maxSampleRangeMapped(rangeStart, rangeEnd) * waveformHeight / 2));
var vertexBottomY:Int = Std.int(waveformCenterPos + (-waveformData.channel(0).minSampleRangeMapped(rangeStart, rangeEnd) * waveformHeight / 2));
// trace('Drawing index ${rangeStart} at pixel ${i} with MAX ${vertexTopY} and MIN ${vertexBottomY}');
var vertexTopIndex:Int = meshRender.build_vertex(i, vertexTopY);
var vertexBottomIndex:Int = meshRender.build_vertex(i, vertexBottomY);
if (prevVertexTopIndex != -1 && prevVertexBottomIndex != -1)
{
meshRender.add_quad(prevVertexTopIndex, vertexTopIndex, vertexBottomIndex, prevVertexBottomIndex);
}
else
{
trace('Skipping quad at index ${i}');
}
prevVertexTopIndex = vertexTopIndex;
prevVertexBottomIndex = vertexBottomIndex;
}
}
trace('Drawing ${duration} seconds of waveform with ${meshRender.vertex_count} vertices');
var oneSecondInPixels:Float = waveformWidth / duration;
timeMarker.x = Std.int(oneSecondInPixels);
// For each sample in the waveform...
// Add a MAX vertex and a MIN vertex.
// If previous MAX/MIN is empty, store.
// If previous MAX/MIN is not empty, draw a quad using current and previous MAX/MIN. Then store current MAX/MIN.
// Continue until end of waveform.
}
public override function update(elapsed:Float):Void
{
super.update(elapsed);
if (FlxG.keys.justPressed.SPACE)
{
if (waveformAudio.isPlaying)
{
waveformAudio.stop();
}
else
{
waveformAudio.play();
}
}
if (waveformAudio.isPlaying)
{
var songTimeSeconds:Float = waveformAudio.time / 1000;
drawWaveform(songTimeSeconds, duration);
}
if (FlxG.keys.justPressed.UP)
{
trace('Zooming out');
duration += 1.0;
drawTheWaveform();
}
if (FlxG.keys.justPressed.DOWN)
{
trace('Zooming in');
duration -= 1.0;
drawTheWaveform();
}
if (FlxG.keys.justPressed.LEFT)
{
trace('Seeking back');
time -= 1.0;
drawTheWaveform();
}
if (FlxG.keys.justPressed.RIGHT)
{
trace('Seeking forward');
time += 1.0;
drawTheWaveform();
}
}
var time:Float = 0.0;
var duration:Float = 5.0;
function drawTheWaveform():Void
{
drawWaveform(time, duration);
}
public override function destroy():Void
{
super.destroy();
}
}