Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions src/funkin/vis/AudioBuffer.hx

This file was deleted.

6 changes: 3 additions & 3 deletions src/funkin/vis/AudioClip.hx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package funkin.vis;
*/
interface AudioClip
{
public var audioBuffer(default, null):AudioBuffer;
public var currentFrame(get, never):Int;
public var source:Dynamic;
public var channels(get, never):Int;
public var sampleRate(get, never):Int;
public function getTimeDomainData(fftN:Int):Array<Float>;
}
102 changes: 78 additions & 24 deletions src/funkin/vis/_internal/html5/AnalyzerNode.hx
Original file line number Diff line number Diff line change
@@ -1,51 +1,105 @@
package funkin.vis._internal.html5;

import funkin.vis.AudioBuffer;
import lime.utils.Float32Array;
import funkin.vis.audioclip.frontends.LimeAudioClip;

#if lime_howlerjs
import lime.media.howlerjs.Howl;
import js.html.audio.AnalyserNode as AnalyseWebAudio;
import js.html.audio.AudioNode;
import js.html.audio.AnalyserNode as JSAnalyserNode;
import js.html.audio.BaseAudioContext;
#end

// note: analyze and analyse are both correct spellings of the word,
// but "AnalyserNode" is the correct class name in the Web Audio API
// and we use the Z variant here...
class AnalyzerNode
{

#if lime_howlerjs
public var analyzer:AnalyseWebAudio;
public var analyser:JSAnalyserNode;
public var maxDecibels:Float = -30;
public var minDecibels:Float = -100;
public var fftSize:Int = 2048;

public var limeAudioClip:LimeAudioClip;

var audioNode:AudioNode;
var array:Float32Array;
#end

// #region yoooo
public function new(?audioClip:AudioClip)
public function new(?limeAudioClip:LimeAudioClip)
{
trace("Loading audioClip");

#if lime_howlerjs
analyzer = new AnalyseWebAudio(audioClip.source._sounds[0]._node.context);
audioClip.source._sounds[0]._node.connect(analyzer);
// trace(audioClip.source._sounds[0]._node.context.sampleRate);
// trace(analyzer);
// trace(analyzer.fftSize);
// howler = cast buffer.source;
// trace(howler);
getFloatFrequencyData();
#end
this.limeAudioClip = limeAudioClip;
#end
}

public function getFloatFrequencyData():Array<Float>
public function getFloatFrequencyData():Float32Array
{
#if lime_howlerjs
var array:js.lib.Float32Array = new js.lib.Float32Array(analyzer.frequencyBinCount);
analyzer.fftSize = fftSize;
analyzer.minDecibels = minDecibels;
analyzer.maxDecibels = maxDecibels;
analyzer.getFloatFrequencyData(array);
return cast array;
var desiredLength = fftSize >> 1;
if (array == null || array.length != desiredLength) array = new Float32Array(desiredLength);

// really a crime to read this code but this gets the audioNode.
var previousAudioNode = audioNode;
@:privateAccess
{
#if lime_funkin
var howlSound = limeAudioClip.audioSource.__backend.howlSound;
if (howlSound != null)
#else
var howl = limeAudioClip.audioSource.buffer.__srcHowl;
if (howl == null) return cast array;

var howlSound = untyped limeAudioClip.audioSource.buffer.__srcHowl._soundById(limeAudioClip.audioSource.__backend.id);
if (!(untyped howlSound)) howlSound = untyped limeAudioClip.audioSource.buffer.__srcHowl._sounds[0];

if (untyped howlSound)
#end
{
if (untyped howlSound._node)
{
if (untyped howlSound._node.bufferSource)
{
audioNode = untyped howlSound._node.bufferSource;
}
else
{
audioNode = untyped howlSound._node;
}
}
else
{
audioNode = null;
}
}
else
{
audioNode = null;
}
}

if (audioNode == null) return cast array;

if (previousAudioNode != audioNode)
{
var context:BaseAudioContext = audioNode.context;
if (analyser == null || context != analyser.context) analyser = new JSAnalyserNode(context);
else analyser.disconnect();

audioNode.connect(analyser);
}

analyser.smoothingTimeConstant = 0.1;
analyser.fftSize = fftSize;
analyser.maxDecibels = maxDecibels;
analyser.minDecibels = minDecibels;

analyser.getFloatFrequencyData(array);
return array;
#else
return new Float32Array(0);
#end
return [];
}
}
127 changes: 106 additions & 21 deletions src/funkin/vis/audioclip/frontends/LimeAudioClip.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ package funkin.vis.audioclip.frontends;

import flixel.FlxG;
import flixel.math.FlxMath;
import funkin.vis.AudioBuffer;
import lime.media.AudioContextType;
import lime.media.AudioSource;
import lime.media.AudioManager;

#if lime_funkin
import lime.utils.Float32Array;
#elseif !js
import lime.utils.ArrayBufferView.ArrayBufferIO;
#end

/**
* Implementation of AudioClip for Lime.
Expand All @@ -17,39 +24,117 @@ import lime.media.AudioSource;
*/
class LimeAudioClip implements funkin.vis.AudioClip
{
public var audioBuffer(default, null):AudioBuffer;
public var currentFrame(get, never):Int;
public var source:Dynamic;
public var audioSource:AudioSource;
public var channels(get, never):Int;
public var sampleRate(get, never):Int;

public function new(audioSource:AudioSource)
{
var data:lime.utils.UInt16Array = cast audioSource.buffer.data;
this.audioSource = audioSource;
}

#if web
var sampleRate:Float = audioSource.buffer.src._sounds[0]._node.context.sampleRate;
inline function get_channels():Int
{
#if (js && html5 && howlerjs)
if (audioSource.buffer != null && audioSource.buffer.channels > 0) return audioSource.buffer.channels;
return 2;
#else
var sampleRate = audioSource.buffer.sampleRate;
return audioSource.buffer != null ? audioSource.buffer.channels : 0;
#end

this.audioBuffer = new AudioBuffer(data, sampleRate);
this.source = audioSource.buffer.src;
}

private function get_currentFrame():Int
inline function get_sampleRate():Int
{
var dataLength:Int = 0;

#if web
dataLength = source.length;
#if (js && html5 && howlerjs)
if (audioSource.buffer != null && audioSource.buffer.sampleRate > 0) return audioSource.buffer.sampleRate;
else if (AudioManager.context != null && AudioManager.context.type == AudioContextType.WEB) return Std.int(AudioManager.context.web.sampleRate);
return 44100;
#else
dataLength = audioBuffer.data.length;
return audioSource.buffer != null ? audioSource.buffer.sampleRate : 0;
#end
}

// Prevents a memory leak by reusing array
var _buffer:Array<Float> = [];

#if lime_funkin
var _array:Float32Array;

public function getTimeDomainData(fftN:Int):Array<Float>
{
// Allocate it first!
if (fftN > _buffer.length) _buffer.resize(fftN);

if (_array == null || fftN > _array.length) _array = new Float32Array(fftN);

var len = audioSource.getFloatTimeDomainData(_array, fftN);

var value = Std.int(FlxMath.remapToRange(FlxG.sound.music.time, 0, FlxG.sound.music.length, 0, dataLength));
for (i in 0...len) _buffer[i] = _array[i];
for (i in len...fftN) _buffer[i] = 0;
return _buffer;
}
#elseif !js
public function getTimeDomainData(fftN:Int):Array<Float>
{
// Allocate it first!
if (fftN > _buffer.length) _buffer.resize(fftN);

inline function empty()
{
for (i in 0...fftN) _buffer[i] = 0;
return _buffer;
}

var audioBuffer = audioSource.buffer;
if (audioBuffer == null) return empty();

var byteRate:Int = audioBuffer.bitsPerSample >> 3;
var currentFrame:Int = (Std.int(audioSource.currentTime / 1000 * audioBuffer.sampleRate) - fftN) * audioBuffer.channels * byteRate;

if (currentFrame <= 0) return empty();

if (value < 0)
return -1;
var valueSize:Int = 1 << (audioBuffer.bitsPerSample - 1);
var buffer = audioBuffer.data.buffer, pos = 0, v = 0, c = 0;

return value;
inline function idiv(num:Int, denom:Int):Int
{
return #if (cpp && !cppia) cpp.NativeMath.idiv(num, denom) #else Std.int(num / denom) #end;
}

while (pos < fftN)
{
switch (byteRate)
{
case 2: v = idiv(ArrayBufferIO.getInt16(buffer, currentFrame), audioBuffer.channels);
case 3:
v = ArrayBufferIO.getUint16(buffer, currentFrame) | (buffer.get(currentFrame + 2) << 16);
if (v & 0x800000 != 0) v -= 0x1000000;
v = idiv(v, audioBuffer.channels);
case 4: v = idiv(ArrayBufferIO.getInt32(buffer, currentFrame), audioBuffer.channels);
default: v = idiv(ArrayBufferIO.getInt8(buffer, currentFrame), audioBuffer.channels);
}

c++;
if (c == audioBuffer.channels)
{
_buffer[pos++] = v / valueSize;
c = v = 0;
}

currentFrame += byteRate;
if (currentFrame >= buffer.length)
{
for (i in pos...fftN) _buffer[i] = 0;
break;
}
}

return _buffer;
}
#else
public function getTimeDomainData(fftN:Int):Array<Float>
{
throw "Your not supposed to use this in js!";
}
#end
}
Loading