2023-11-07 09:04:22 +00:00
|
|
|
package funkin.audio.visualize;
|
2021-10-06 22:46:14 +00:00
|
|
|
|
2021-10-08 01:06:45 +00:00
|
|
|
import flixel.math.FlxMath;
|
2023-05-25 22:33:39 +00:00
|
|
|
import flixel.sound.FlxSound;
|
2023-11-07 09:04:22 +00:00
|
|
|
import funkin.audio.visualize.dsp.FFT;
|
2021-10-06 22:46:14 +00:00
|
|
|
import lime.utils.Int16Array;
|
2023-11-07 09:04:22 +00:00
|
|
|
import funkin.util.MathUtil;
|
2021-10-06 22:46:14 +00:00
|
|
|
|
2021-10-08 01:06:45 +00:00
|
|
|
using Lambda;
|
|
|
|
|
2021-10-06 22:46:14 +00:00
|
|
|
class VisShit
|
|
|
|
{
|
2023-01-23 03:25:45 +00:00
|
|
|
public var snd:FlxSound;
|
|
|
|
public var setBuffer:Bool = false;
|
|
|
|
public var audioData:Int16Array;
|
|
|
|
public var sampleRate:Int = 44100; // default, ez?
|
|
|
|
public var numSamples:Int = 0;
|
|
|
|
|
|
|
|
public function new(snd:FlxSound)
|
|
|
|
{
|
|
|
|
this.snd = snd;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function funnyFFT(samples:Array<Float>, ?skipped:Int = 1):Array<Array<Float>>
|
|
|
|
{
|
|
|
|
// nab multiple samples at once in while / for loops?
|
|
|
|
|
|
|
|
var fs:Float = 44100 / skipped; // sample rate shit?
|
|
|
|
|
|
|
|
final fftN = 1024;
|
|
|
|
final halfN = Std.int(fftN / 2);
|
|
|
|
final overlap = 0.5;
|
|
|
|
final hop = Std.int(fftN * (1 - overlap));
|
|
|
|
|
|
|
|
// window function to compensate for overlapping
|
|
|
|
final a0 = 0.5; // => Hann(ing) window
|
|
|
|
final window = (n:Int) -> a0 - (1 - a0) * Math.cos(2 * Math.PI * n / fftN);
|
|
|
|
|
|
|
|
// NOTE TO SELF FOR WHEN I WAKE UP
|
|
|
|
|
|
|
|
// helpers, note that spectrum indexes suppose non-negative frequencies
|
|
|
|
final binSize = fs / fftN;
|
2023-05-25 22:33:39 +00:00
|
|
|
final indexToFreq = function(k:Int) {
|
2023-11-07 09:04:22 +00:00
|
|
|
var powShit:Float = FlxMath.remapToRange(k, 0, halfN, 0, MathUtil.logBase(10, halfN)); // 4.3 is almost the log of 20Khz or so. Close enuf lol
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
return 1.0 * (Math.pow(10, powShit)); // we need the `1.0` to avoid overflows
|
|
|
|
};
|
|
|
|
|
|
|
|
// "melodic" band-pass filter
|
|
|
|
final minFreq = 20.70;
|
|
|
|
final maxFreq = 4000.01;
|
2023-05-25 22:33:39 +00:00
|
|
|
final melodicBandPass = function(k:Int, s:Float) {
|
2023-01-23 03:25:45 +00:00
|
|
|
final freq = indexToFreq(k);
|
|
|
|
final filter = freq > minFreq - binSize && freq < maxFreq + binSize ? 1 : 0;
|
|
|
|
return s;
|
|
|
|
};
|
|
|
|
|
|
|
|
var freqOutput:Array<Array<Float>> = [];
|
|
|
|
|
|
|
|
var c = 0; // index where each chunk begins
|
|
|
|
var indexOfArray:Int = 0;
|
|
|
|
while (c < samples.length)
|
|
|
|
{
|
|
|
|
// take a chunk (zero-padded if needed) and apply the window
|
|
|
|
final chunk = [
|
|
|
|
for (n in 0...fftN)
|
|
|
|
(c + n < samples.length ? samples[c + n] : 0.0) * window(n)
|
|
|
|
];
|
|
|
|
|
|
|
|
// compute positive spectrum with sampling correction and BP filter
|
|
|
|
final freqs = FFT.rfft(chunk).map(z -> z.scale(1 / fftN).magnitude).mapi(melodicBandPass);
|
|
|
|
|
|
|
|
freqOutput.push([]);
|
|
|
|
|
|
|
|
// find spectral peaks and their instantaneous frequencies
|
|
|
|
for (k => s in freqs)
|
|
|
|
{
|
|
|
|
final time = c / fs;
|
|
|
|
final freq = indexToFreq(k);
|
|
|
|
final power = s * s;
|
|
|
|
if (FlxG.keys.justPressed.I)
|
|
|
|
{
|
|
|
|
trace(k);
|
|
|
|
|
|
|
|
haxe.Log.trace('${time};${freq};${power}', null);
|
|
|
|
}
|
|
|
|
if (freq < maxFreq) freqOutput[indexOfArray].push(power);
|
|
|
|
//
|
|
|
|
}
|
|
|
|
|
|
|
|
indexOfArray++;
|
|
|
|
// move to next (overlapping) chunk
|
|
|
|
c += hop;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (FlxG.keys.justPressed.C) trace(freqOutput.length);
|
|
|
|
|
|
|
|
return freqOutput;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getCurAud(aud:Int16Array, index:Int):CurAudioInfo
|
|
|
|
{
|
|
|
|
var left = aud[index] / 32767;
|
|
|
|
var right = aud[index + 2] / 32767;
|
|
|
|
var balanced = (left + right) / 2;
|
|
|
|
|
|
|
|
var funny:CurAudioInfo = {left: left, right: right, balanced: balanced};
|
|
|
|
|
|
|
|
return funny;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function checkAndSetBuffer()
|
|
|
|
{
|
|
|
|
if (snd != null && snd.playing)
|
|
|
|
{
|
|
|
|
if (!setBuffer)
|
|
|
|
{
|
|
|
|
// Math.pow3
|
|
|
|
@:privateAccess
|
|
|
|
var buf = snd._channel.__source.buffer;
|
|
|
|
|
|
|
|
// @:privateAccess
|
|
|
|
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
|
|
|
sampleRate = buf.sampleRate;
|
|
|
|
|
|
|
|
trace('got audio buffer shit');
|
|
|
|
trace(sampleRate);
|
|
|
|
trace(buf.bitsPerSample);
|
|
|
|
|
|
|
|
setBuffer = true;
|
|
|
|
numSamples = Std.int(audioData.length / 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-06 22:46:14 +00:00
|
|
|
}
|
2022-02-10 19:23:45 +00:00
|
|
|
|
|
|
|
typedef CurAudioInfo =
|
|
|
|
{
|
2023-01-23 03:25:45 +00:00
|
|
|
var left:Float;
|
|
|
|
var right:Float;
|
|
|
|
var balanced:Float;
|
2022-02-10 19:23:45 +00:00
|
|
|
}
|