Skip to content

Commit ebce1c9

Browse files
committed
Freeplay Visualizer
1 parent 9b0ab02 commit ebce1c9

9 files changed

Lines changed: 507 additions & 291 deletions

File tree

.hxpkg

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,18 @@
6565
"name": "flixel-controls",
6666
"link": "https://github.com/TechnikTil/FlxControls",
6767
"branch": "aca3217338ed7b30c9582f4295d886379273f072"
68+
},
69+
{
70+
"name": "funkin.vis",
71+
"link": "https://github.com/FunkinCrew/funkVis",
72+
"branch": "1966f8fbbbc509ed90d4b520f3c49c084fc92fd6"
73+
},
74+
{
75+
"name": "grig.audio",
76+
"link": "https://gitlab.com/haxe-grig/grig.audio.git",
77+
"branch": "cbf91e2180fd2e374924fe74844086aab7891666",
78+
"dir": "src"
6879
}
6980
]
7081
}
71-
]
82+
]

Project.hxp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ class Project extends HXProject
142142
addHaxelib('hxdiscord_rpc', '1.3.0', FUNKIN_DISCORD_RPC.isEnabled(this));
143143
addHaxelib('thx.core', '0.44.0');
144144
addHaxelib('thx.semver', '0.2.2');
145+
addHaxelib('funkin.vis', 'git');
146+
addHaxelib('grig.audio');
145147
}
146148

147149
public function setupHaxeDefines():Void

assets/ui/freeplay/freeplay-boyfriend/spritemap1.json

Lines changed: 281 additions & 281 deletions
Large diffs are not rendered by default.
526 KB
Loading

src/funkin/objects/ui/Alphabet.hx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Alphabet extends FlxTypedSpriteGroup<AlphabetLetter>
2626
*/
2727
public var letterType(default, set):LetterType;
2828

29+
// TODO: make this work with animate atlases
2930
@:allow(funkin.objects.ui.AlphabetLetter)
3031
var textFrames:FlxFramesCollection;
3132

@@ -152,6 +153,7 @@ class Alphabet extends FlxTypedSpriteGroup<AlphabetLetter>
152153
letter.kill();
153154
});
154155
}
156+
155157
var _lastTextWidth:Float = 0;
156158
var _lastTextHeight:Float = 0;
157159

src/funkin/objects/ui/freeplay/FreeplayDJ.hx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class FreeplayDJ extends FunkinSprite
2626
{
2727
super(x, y);
2828

29+
// todo: softcode this
2930
switch (id)
3031
{
3132
case 'bf':
@@ -50,6 +51,7 @@ class FreeplayDJ extends FunkinSprite
5051
case Intro:
5152
currentState = Idle;
5253
introDone.dispatch();
54+
cast(atlas, FreeplayDJAtlas)?.initVisualizer(FlxG.sound.music);
5355
default:
5456
}
5557
}
@@ -119,22 +121,33 @@ class FreeplayDJ extends FunkinSprite
119121
#end
120122
}
121123

122-
override public function draw():Void
124+
override public function loadFrames(path:String, ?forcedType:Null<String>):FunkinSprite
123125
{
124-
drawVisualizer();
125-
super.draw();
126-
}
126+
if (atlas != null)
127+
{
128+
atlas.destroy();
129+
atlas = null;
130+
}
127131

128-
public function drawVisualizer():Void
129-
{
130-
// TODO: work on this when my animate decides to work
131-
// bro is your fucking animate ever gonna work lil brah
132+
if (Paths.location.exists(path + '/Animation.json'))
133+
{
134+
atlas = new FreeplayDJAtlas(0, 0, Paths.location.get(path), {
135+
ShowPivot: false
136+
});
137+
138+
return this;
139+
}
140+
else
141+
{
142+
return super.loadFrames(path, forcedType);
143+
}
132144
}
133145

134146
override public function playAnimation(name:String, ?restart:Bool = false, ?stunAnimations:Bool = false, ?reversed:Bool = false):Void
135147
{
136148
super.playAnimation(name, restart, stunAnimations, reversed);
137149

150+
// TODO: softcode this
138151
switch (name)
139152
{
140153
case 'idle', 'confirm':
@@ -145,7 +158,7 @@ class FreeplayDJ extends FunkinSprite
145158
offset.set();
146159
}
147160
}
148-
} // its an abstract now cuz of animation names.
161+
}
149162

150163
enum FreeplayDJState
151164
{
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package funkin.objects.ui.freeplay;
2+
3+
import flixel.FlxCamera;
4+
import flixel.math.FlxMatrix;
5+
import flixel.math.FlxPoint;
6+
import flxanimate.FlxAnimate;
7+
import flxanimate.animate.FlxElement;
8+
import funkin.util.visualizer.AnalyzerAnimationHelper;
9+
import openfl.geom.ColorTransform;
10+
11+
class FreeplayDJAtlas extends FlxAnimate
12+
{
13+
var analyzerHelper:AnalyzerAnimationHelper;
14+
15+
public function new(?X:Null<Float>, ?Y:Null<Float>, ?Path:Null<String>, ?Settings:Null<Null<Null<flxanimate.FlxAnimate.Settings>>>)
16+
{
17+
super(X, Y, Path, Settings);
18+
analyzerHelper = new AnalyzerAnimationHelper(null, 3, 4);
19+
}
20+
21+
/**
22+
* Initializes the visualizer with `snd`.
23+
* @param snd The sound to initialize with.
24+
*/
25+
public function initVisualizer(snd:FlxSound):Void
26+
{
27+
analyzerHelper.snd = snd;
28+
analyzerHelper.initAnalyzer(35);
29+
}
30+
31+
override public function draw():Void
32+
{
33+
if (analyzerHelper.ready)
34+
{
35+
analyzerHelper.updateFFT();
36+
}
37+
38+
super.draw();
39+
}
40+
41+
override public function parseElement(instance:FlxElement, m:FlxMatrix, colorFilter:ColorTransform, ?filterInstance:
42+
{
43+
?instance:FlxElement
44+
} = null, ?cameras:Array<FlxCamera> = null, ?scrollFactor:FlxPoint = null):Void
45+
{
46+
if (instance?.symbol?.name?.startsWith('tt lights') && analyzerHelper.ready)
47+
{
48+
var nameSuffix:String = instance.symbol.name.substring('tt lights'.length);
49+
trace(nameSuffix);
50+
51+
var index:Null<Int> = Std.parseInt(nameSuffix);
52+
if (index == null)
53+
return;
54+
55+
instance.symbol.firstFrame = analyzerHelper.frameMap[index];
56+
}
57+
58+
super.parseElement(instance, m, colorFilter, filterInstance, cameras, scrollFactor);
59+
}
60+
}

src/funkin/states/ui/FreeplayState.hx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ class FreeplayState extends FunkinState
8282
*/
8383
public var blockInputs:Bool = false;
8484

85+
/**
86+
* Is the random song playing?
87+
*/
88+
public var isRandomPlaying:Bool = false;
89+
90+
/**
91+
* The Song Position of the last song. To be used when switching between random and normal songs.
92+
*/
93+
public var lastSongPos:Float = 0;
94+
8595
override public function create():Void
8696
{
8797
#if FUNKIN_DISCORD_RPC
@@ -297,6 +307,27 @@ class FreeplayState extends FunkinState
297307
else if (curSelected < 0)
298308
curSelected = songs.length;
299309

310+
if (curSelected == 0)
311+
{
312+
conductor.bpm = 145;
313+
var songPosToSetTo:Float = lastSongPos;
314+
lastSongPos = FlxG.sound.music.time;
315+
FlxG.sound.playMusic(Paths.content.audio('ui/freeplay/freeplayRandom'));
316+
FlxG.sound.music?.time = songPosToSetTo;
317+
FlxG.sound.music?.fadeIn(2, 0, 1);
318+
isRandomPlaying = true;
319+
}
320+
else if (isRandomPlaying)
321+
{
322+
conductor.bpm = 102;
323+
var songPosToSetTo:Float = lastSongPos;
324+
lastSongPos = FlxG.sound.music?.time;
325+
FlxG.sound.playMusic(Paths.content.audio('ui/menu/freakyMenu'));
326+
FlxG.sound.music?.time = songPosToSetTo;
327+
FlxG.sound.music?.fadeIn(2, 0, 1);
328+
isRandomPlaying = false;
329+
}
330+
300331
for (i => capsule in grpCapsules.members)
301332
{
302333
i += 1;
@@ -353,4 +384,18 @@ class FreeplayState extends FunkinState
353384
if (shouldUpdateCapsules)
354385
generateCapsules();
355386
}
387+
388+
override public function destroy():Void
389+
{
390+
if (isRandomPlaying)
391+
{
392+
conductor.bpm = 102;
393+
var songPosToSetTo:Float = lastSongPos;
394+
lastSongPos = FlxG.sound.music?.time;
395+
FlxG.sound.playMusic(Paths.content.audio('ui/menu/freakyMenu'));
396+
FlxG.sound.music?.time = songPosToSetTo;
397+
FlxG.sound.music?.fadeIn(2, 0, 1);
398+
isRandomPlaying = false;
399+
}
400+
}
356401
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package funkin.util.visualizer;
2+
3+
import funkin.vis.dsp.SpectralAnalyzer;
4+
5+
class AnalyzerAnimationHelper
6+
{
7+
var analyzer:SpectralAnalyzer;
8+
9+
var barCount:Int;
10+
var barHeight:Int;
11+
12+
/**
13+
* The sound to use for this analyzer.
14+
*/
15+
public var snd:FlxSound;
16+
17+
/**
18+
* Whether this analyzer is ready or not.
19+
*/
20+
public var ready(get, null):Bool;
21+
22+
function get_ready():Bool
23+
{
24+
return snd != null && analyzer != null;
25+
}
26+
27+
public function new(snd:FlxSound, barCount:Int, barHeight:Int)
28+
{
29+
this.snd = snd;
30+
this.barCount = barCount;
31+
this.barHeight = barHeight;
32+
}
33+
34+
public function initAnalyzer(?peakHold:Int = 30):Void
35+
{
36+
@:privateAccess
37+
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, barCount, 0.1, peakHold);
38+
39+
#if desktop
40+
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
41+
// So we want to manually change it!
42+
analyzer.fftN = 256;
43+
#end
44+
}
45+
46+
public var frameMap:Array<Int> = [];
47+
48+
public function updateFFT():Void
49+
{
50+
var levels;
51+
try
52+
{
53+
levels = analyzer.getLevels();
54+
}
55+
catch (e)
56+
{
57+
trace('Couldn\'t load levels! $e');
58+
return;
59+
}
60+
61+
frameMap = [];
62+
63+
var len:Int = cast Math.min(barCount, levels.length);
64+
65+
for (i in 0...len)
66+
{
67+
var animFrame:Int = Math.round(levels[i].value * barHeight);
68+
69+
#if desktop
70+
// Web version scales with the Flixel volume level.
71+
// This line brings platform parity but looks worse.
72+
// animFrame = Math.round(animFrame * FlxG.sound.volume);
73+
#end
74+
75+
animFrame = Math.floor(Math.min(barHeight, animFrame));
76+
animFrame = Math.floor(Math.max(0, animFrame));
77+
78+
animFrame = Std.int(Math.abs(animFrame - barHeight)); // shitty dumbass flip, cuz dave got da shit backwards lol!
79+
80+
frameMap[i] = animFrame;
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)