Initial attempt at rendering the waveform inside the offsets toolbox.

This commit is contained in:
EliteMasterEric 2024-01-25 20:23:18 -05:00
parent 25aff02784
commit c9163986d5
6 changed files with 141 additions and 23 deletions
assetshmm.json
source/funkin
audio
ui
debug/charting/toolboxes
haxeui/components

2
assets

@ -1 +1 @@
Subproject commit 3d92b497682727d34eaa55e564e0bd9faea1c9d7
Subproject commit 685c9481c26020563333852d9401fe4c6ee10257

View file

@ -54,14 +54,14 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
"ref": "bb904f8b4b205755a310c23ff25219f9dcd62711",
"ref": "5b2d5b8e7e470cf637953e1369c80a1f42016a75",
"url": "https://github.com/haxeui/haxeui-core"
},
{
"name": "haxeui-flixel",
"type": "git",
"dir": null,
"ref": "1ec470c297afd7758a90dc9399aa1e3a4ea6ca0b",
"ref": "e9f880522e27134b29df4067f82df7d7e5237b70",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{

View file

@ -2,6 +2,8 @@ package funkin.audio;
import funkin.audio.FunkinSound;
import flixel.group.FlxGroup.FlxTypedGroup;
import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser;
class VoicesGroup extends SoundGroup
{
@ -104,6 +106,40 @@ class VoicesGroup extends SoundGroup
return opponentVolume = volume;
}
public function buildPlayerVoiceWaveform():Null<WaveformData>
{
if (playerVoices.members.length == 0) return null;
return WaveformDataParser.interpretFlxSound(playerVoices.members[0]);
}
public function buildOpponentVoiceWaveform():Null<WaveformData>
{
if (opponentVoices.members.length == 0) return null;
return WaveformDataParser.interpretFlxSound(opponentVoices.members[0]);
}
/**
* The length of the player's vocal track, in milliseconds.
*/
public function getPlayerVoiceLength():Float
{
if (playerVoices.members.length == 0) return 0.0;
return playerVoices.members[0].length;
}
/**
* The length of the opponent's vocal track, in milliseconds.
*/
public function getOpponentVoiceLength():Float
{
if (opponentVoices.members.length == 0) return 0.0;
return opponentVoices.members[0].length;
}
public override function clear():Void
{
playerVoices.clear();

View file

@ -215,17 +215,17 @@ class WaveformSprite extends MeshRender
{
var pixelPos:Int = Std.int((i - startIndex) * pixelsPerIndex);
var isOutsideClipRect:Bool = (clipRect != null) && if (orientation == HORIZONTAL)
{
pixelPos < clipRect.x
|| pixelPos > (clipRect.x + clipRect.width);
} else
{
pixelPos < clipRect.y || pixelPos > (clipRect.y + clipRect.height);
};
var isBeforeClipRect:Bool = (clipRect != null) && ((orientation == HORIZONTAL) ? pixelPos < clipRect.x : pixelPos < clipRect.y);
// This index is outside the clipRect, so we can just skip rendering it. Fantastic!
if (isOutsideClipRect) continue;
if (isBeforeClipRect) continue;
var isAfterClipRect:Bool = (clipRect != null)
&& ((orientation == HORIZONTAL) ? pixelPos > (clipRect.x + clipRect.width) : pixelPos > (clipRect.y + clipRect.height));
if (isAfterClipRect)
{
break;
};
var sampleMax:Float = Math.min(waveformData.channel(0).maxSampleMapped(i) * amplitude, 1.0);
var sampleMin:Float = Math.max(waveformData.channel(0).minSampleMapped(i) * amplitude, -1.0);
@ -299,16 +299,17 @@ class WaveformSprite extends MeshRender
{
var pixelPos:Int = i;
var isOutsideClipRect:Bool = (clipRect != null) && if (orientation == HORIZONTAL)
var isBeforeClipRect:Bool = (clipRect != null) && ((orientation == HORIZONTAL) ? pixelPos < clipRect.x : pixelPos < clipRect.y);
if (isBeforeClipRect) continue;
var isAfterClipRect:Bool = (clipRect != null)
&& ((orientation == HORIZONTAL) ? pixelPos > (clipRect.x + clipRect.width) : pixelPos > (clipRect.y + clipRect.height));
if (isAfterClipRect)
{
pixelPos < clipRect.x
|| pixelPos > (clipRect.x + clipRect.width);
} else
{
pixelPos < clipRect.y || pixelPos > (clipRect.y + clipRect.height);
break;
};
// This index is outside the clipRect, so we can just skip rendering it. Fantastic!
if (isOutsideClipRect) continue;
// 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);
@ -369,8 +370,6 @@ class WaveformSprite extends MeshRender
prevVertexBottomIndex = vertexBottomIndex;
}
}
trace('Rendering waveform of ${duration} seconds with ${this.vertex_count} vertices.');
}
function buildClippedVertex(x:Int, y:Int, topLeftVertexIndex:Int, topRightVertexIndex:Int, bottomLeftVertexIndex:Int, bottomRightVertexIndex:Int):Int

View file

@ -15,9 +15,12 @@ import haxe.ui.components.NumberStepper;
import haxe.ui.components.Slider;
import haxe.ui.components.TextField;
import funkin.play.stage.Stage;
import funkin.ui.haxeui.components.WaveformPlayer;
import funkin.audio.waveform.WaveformDataParser;
import haxe.ui.containers.Box;
import haxe.ui.containers.Frame;
import haxe.ui.events.UIEvent;
import funkin.audio.waveform.WaveformData;
/**
* The toolbox which allows modifying information like Song Title, Scroll Speed, Characters/Stages, and starting BPM.
@ -27,6 +30,14 @@ import haxe.ui.events.UIEvent;
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/offsets.xml"))
class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
{
var waveformPlayer:WaveformPlayer;
var waveformOpponent:WaveformPlayer;
var waveformInstrumental:WaveformPlayer;
var offsetButtonZoomIn:Button;
var offsetButtonZoomOut:Button;
var waveformScale:Int = 64;
public function new(chartEditorState2:ChartEditorState)
{
super(chartEditorState2);
@ -48,12 +59,67 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
this.x = 150;
this.y = 250;
offsetButtonZoomIn.onClick = (_) -> {
zoomWaveformIn();
};
offsetButtonZoomOut.onClick = (_) -> {
zoomWaveformOut();
};
// Build player waveform.
waveformPlayer.waveform.forceUpdate = true;
waveformPlayer.waveform.waveformData = chartEditorState.audioVocalTrackGroup.buildPlayerVoiceWaveform();
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
waveformPlayer.waveform.duration = 5.0; // chartEditorState.audioVocalTrackGroup.getPlayerVoiceLength() / 1000;
// Build opponent waveform.
waveformOpponent.waveform.forceUpdate = true;
waveformOpponent.waveform.waveformData = chartEditorState.audioVocalTrackGroup.buildOpponentVoiceWaveform();
waveformOpponent.waveform.duration = 5.0; // chartEditorState.audioVocalTrackGroup.getOpponentVoiceLength() / 1000;
// Build instrumental waveform.
waveformInstrumental.waveform.forceUpdate = true;
waveformInstrumental.waveform.waveformData = WaveformDataParser.interpretFlxSound(chartEditorState.audioInstTrack);
waveformInstrumental.waveform.duration = 5.0; // chartEditorState.audioInstTrack.length / 1000;
refresh();
}
public function zoomWaveformIn():Void
{
if (waveformScale > 1)
{
waveformScale = Std.int(waveformScale / 2);
}
else
{
waveformScale = 1;
}
refresh();
}
public function zoomWaveformOut():Void
{
waveformScale = Std.int(waveformScale * 2);
refresh();
}
public override function refresh():Void
{
super.refresh();
// Set the width based on the waveformScale value.
waveformPlayer.waveform.width = waveformPlayer.waveform.waveformData.length / waveformScale;
trace('Player duration: ${waveformPlayer.waveform.duration}, width: ${waveformPlayer.waveform.width}');
waveformOpponent.waveform.width = waveformOpponent.waveform.waveformData.length / waveformScale;
trace('Opponent duration: ${waveformOpponent.waveform.duration}, width: ${waveformOpponent.waveform.width}');
waveformInstrumental.waveform.width = waveformInstrumental.waveform.waveformData.length / waveformScale;
trace('Instrumental duration: ${waveformInstrumental.waveform.duration}, width: ${waveformInstrumental.waveform.width}');
}
public static function build(chartEditorState:ChartEditorState):ChartEditorOffsetsToolbox

View file

@ -0,0 +1,17 @@
package funkin.ui.haxeui.components;
import funkin.audio.waveform.WaveformSprite;
import funkin.audio.waveform.WaveformData;
import haxe.ui.backend.flixel.components.SpriteWrapper;
class WaveformPlayer extends SpriteWrapper
{
public var waveform(default, null):WaveformSprite;
public function new(?waveformData:WaveformData)
{
super();
this.waveform = new WaveformSprite(waveformData);
this.sprite = waveform;
}
}