mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-22 18:09:33 +00:00
audiovis reorganize + MeshRender obj in progres
This commit is contained in:
parent
96540fc09e
commit
c73e8e1a4d
|
@ -1,5 +1,6 @@
|
|||
package;
|
||||
|
||||
import audiovis.SpectogramSprite;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import dsp.FFT;
|
||||
package audiovis;
|
||||
|
||||
import audiovis.dsp.FFT;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.plugin.taskManager.FlxTask;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package audiovis;
|
||||
|
||||
import dsp.FFT;
|
||||
import audiovis.dsp.FFT;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package audiovis;
|
||||
|
||||
import dsp.FFT;
|
||||
import audiovis.dsp.FFT;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.system.FlxSound;
|
||||
import lime.utils.Int16Array;
|
|
@ -1,4 +1,4 @@
|
|||
package dsp;
|
||||
package audiovis.dsp;
|
||||
|
||||
/**
|
||||
Complex number representation.
|
|
@ -1,16 +1,17 @@
|
|||
package dsp;
|
||||
package audiovis.dsp;
|
||||
|
||||
import dsp.Complex;
|
||||
import audiovis.dsp.Complex;
|
||||
|
||||
using audiovis.dsp.OffsetArray;
|
||||
using audiovis.dsp.Signal;
|
||||
|
||||
// these are only used for testing, down in FFT.main()
|
||||
using dsp.OffsetArray;
|
||||
using dsp.Signal;
|
||||
|
||||
|
||||
/**
|
||||
Fast/Finite Fourier Transforms.
|
||||
**/
|
||||
class FFT {
|
||||
class FFT
|
||||
{
|
||||
/**
|
||||
Computes the Discrete Fourier Transform (DFT) of a `Complex` sequence.
|
||||
|
||||
|
@ -19,7 +20,7 @@ class FFT {
|
|||
samples from the Discrete-Time Fourier Transform (DTFT) - which is Fs-periodic -
|
||||
with a spacing of Fs/N Hz between them and a scaling factor of Fs.
|
||||
**/
|
||||
public static function fft(input:Array<Complex>) : Array<Complex>
|
||||
public static function fft(input:Array<Complex>):Array<Complex>
|
||||
return do_fft(input, false);
|
||||
|
||||
/**
|
||||
|
@ -28,7 +29,8 @@ class FFT {
|
|||
Since the input time signal is real, its frequency representation is
|
||||
Hermitian-symmetric so we only return the positive frequencies.
|
||||
**/
|
||||
public static function rfft(input:Array<Float>) : Array<Complex> {
|
||||
public static function rfft(input:Array<Float>):Array<Complex>
|
||||
{
|
||||
final s = fft(input.map(Complex.fromReal));
|
||||
return s.slice(0, Std.int(s.length / 2) + 1);
|
||||
}
|
||||
|
@ -40,11 +42,12 @@ class FFT {
|
|||
from each other, the result will consist of N data points as sampled
|
||||
from a time signal at intervals of 1/Fs with a scaling factor of 1/Fs.
|
||||
**/
|
||||
public static function ifft(input:Array<Complex>) : Array<Complex>
|
||||
public static function ifft(input:Array<Complex>):Array<Complex>
|
||||
return do_fft(input, true);
|
||||
|
||||
// Handles padding and scaling for forwards and inverse FFTs.
|
||||
private static function do_fft(input:Array<Complex>, inverse:Bool) : Array<Complex> {
|
||||
private static function do_fft(input:Array<Complex>, inverse:Bool):Array<Complex>
|
||||
{
|
||||
final n = nextPow2(input.length);
|
||||
var ts = [for (i in 0...n) if (i < input.length) input[i] else Complex.zero];
|
||||
var fs = [for (_ in 0...n) Complex.zero];
|
||||
|
@ -54,36 +57,41 @@ class FFT {
|
|||
}
|
||||
|
||||
// Radix-2 Decimation-In-Time variant of Cooley–Tukey's FFT, recursive.
|
||||
private static function ditfft2(
|
||||
time:Array<Complex>, t:Int,
|
||||
freq:Array<Complex>, f:Int,
|
||||
n:Int, step:Int, inverse: Bool
|
||||
) : Void {
|
||||
if (n == 1) {
|
||||
private static function ditfft2(time:Array<Complex>, t:Int, freq:Array<Complex>, f:Int, n:Int, step:Int, inverse:Bool):Void
|
||||
{
|
||||
if (n == 1)
|
||||
{
|
||||
freq[f] = time[t].copy();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
final halfLen = Std.int(n / 2);
|
||||
ditfft2(time, t, freq, f, halfLen, step * 2, inverse);
|
||||
ditfft2(time, t, freq, f, halfLen, step * 2, inverse);
|
||||
ditfft2(time, t + step, freq, f + halfLen, halfLen, step * 2, inverse);
|
||||
for (k in 0...halfLen) {
|
||||
for (k in 0...halfLen)
|
||||
{
|
||||
final twiddle = Complex.exp((inverse ? 1 : -1) * 2 * Math.PI * k / n);
|
||||
final even = freq[f + k].copy();
|
||||
final odd = freq[f + k + halfLen].copy();
|
||||
freq[f + k] = even + twiddle * odd;
|
||||
freq[f + k] = even + twiddle * odd;
|
||||
freq[f + k + halfLen] = even - twiddle * odd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Naive O(n^2) DFT, used for testing purposes.
|
||||
private static function dft(ts:Array<Complex>, ?inverse:Bool) : Array<Complex> {
|
||||
if (inverse == null) inverse = false;
|
||||
private static function dft(ts:Array<Complex>, ?inverse:Bool):Array<Complex>
|
||||
{
|
||||
if (inverse == null)
|
||||
inverse = false;
|
||||
final n = ts.length;
|
||||
var fs = new Array<Complex>();
|
||||
fs.resize(n);
|
||||
for (f in 0...n) {
|
||||
for (f in 0...n)
|
||||
{
|
||||
var sum = Complex.zero;
|
||||
for (t in 0...n) {
|
||||
for (t in 0...n)
|
||||
{
|
||||
sum += ts[t] * Complex.exp((inverse ? 1 : -1) * 2 * Math.PI * f * t / n);
|
||||
}
|
||||
fs[f] = inverse ? sum.scale(1 / n) : sum;
|
||||
|
@ -94,17 +102,22 @@ class FFT {
|
|||
/**
|
||||
Finds the power of 2 that is equal to or greater than the given natural.
|
||||
**/
|
||||
static function nextPow2(x:Int) : Int {
|
||||
if (x < 2) return 1;
|
||||
else if ((x & (x-1)) == 0) return x;
|
||||
static function nextPow2(x:Int):Int
|
||||
{
|
||||
if (x < 2)
|
||||
return 1;
|
||||
else if ((x & (x - 1)) == 0)
|
||||
return x;
|
||||
var pow = 2;
|
||||
x--;
|
||||
while ((x >>= 1) != 0) pow <<= 1;
|
||||
while ((x >>= 1) != 0)
|
||||
pow <<= 1;
|
||||
return pow;
|
||||
}
|
||||
|
||||
// testing, but also acts like an example
|
||||
static function main() {
|
||||
static function main()
|
||||
{
|
||||
// sampling and buffer parameters
|
||||
final Fs = 44100.0;
|
||||
final N = 512;
|
||||
|
@ -116,39 +129,39 @@ class FFT {
|
|||
|
||||
// get positive spectrum and use its symmetry to reconstruct negative domain
|
||||
final fs_pos = rfft(ts);
|
||||
final fs_fft = new OffsetArray(
|
||||
[for (k in -(halfN - 1) ... 0) fs_pos[-k].conj()].concat(fs_pos),
|
||||
-(halfN - 1)
|
||||
);
|
||||
final fs_fft = new OffsetArray([for (k in -(halfN - 1)...0) fs_pos[-k].conj()].concat(fs_pos), -(halfN - 1));
|
||||
|
||||
// double-check with naive DFT
|
||||
final fs_dft = new OffsetArray(
|
||||
dft(ts.map(Complex.fromReal)).circShift(halfN - 1),
|
||||
-(halfN - 1)
|
||||
);
|
||||
final fs_err = [for (k in -(halfN - 1) ... halfN) fs_fft[k] - fs_dft[k]];
|
||||
final fs_dft = new OffsetArray(dft(ts.map(Complex.fromReal)).circShift(halfN - 1), -(halfN - 1));
|
||||
final fs_err = [for (k in -(halfN - 1)...halfN) fs_fft[k] - fs_dft[k]];
|
||||
final max_fs_err = fs_err.map(z -> z.magnitude).max();
|
||||
if (max_fs_err > 1e-6) haxe.Log.trace('FT Error: ${max_fs_err}', null);
|
||||
if (max_fs_err > 1e-6)
|
||||
haxe.Log.trace('FT Error: ${max_fs_err}', null);
|
||||
// else for (k => s in fs_fft) haxe.Log.trace('${k * Fs / N};${s.scale(1 / Fs).magnitude}', null);
|
||||
|
||||
// find spectral peaks to detect signal frequencies
|
||||
final freqis = fs_fft.array.map(z -> z.magnitude)
|
||||
.findPeaks()
|
||||
.map(k -> (k - (halfN - 1)) * Fs / N)
|
||||
.filter(f -> f >= 0);
|
||||
if (freqis.length != freqs.length) {
|
||||
.findPeaks()
|
||||
.map(k -> (k - (halfN - 1)) * Fs / N)
|
||||
.filter(f -> f >= 0);
|
||||
if (freqis.length != freqs.length)
|
||||
{
|
||||
trace('Found frequencies: ${freqis}');
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
final freqs_err = [for (i in 0...freqs.length) freqis[i] - freqs[i]];
|
||||
final max_freqs_err = freqs_err.map(Math.abs).max();
|
||||
if (max_freqs_err > Fs / N) trace('Frequency Errors: ${freqs_err}');
|
||||
if (max_freqs_err > Fs / N)
|
||||
trace('Frequency Errors: ${freqs_err}');
|
||||
}
|
||||
|
||||
// recover time signal from the frequency domain
|
||||
final ts_ifft = ifft(fs_fft.array.circShift(-(halfN - 1)).map(z -> z.scale(1 / Fs)));
|
||||
final ts_err = [for (n in 0...N) ts_ifft[n].scale(Fs).real - ts[n]];
|
||||
final max_ts_err = ts_err.map(Math.abs).max();
|
||||
if (max_ts_err > 1e-6) haxe.Log.trace('IFT Error: ${max_ts_err}', null);
|
||||
if (max_ts_err > 1e-6)
|
||||
haxe.Log.trace('IFT Error: ${max_ts_err}', null);
|
||||
// else for (n in 0...ts_ifft.length) haxe.Log.trace('${n / Fs};${ts_ifft[n].scale(Fs).real}', null);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package dsp;
|
||||
package audiovis.dsp;
|
||||
|
||||
/**
|
||||
A view into an Array with an indexing offset.
|
||||
|
@ -7,28 +7,30 @@ package dsp;
|
|||
**/
|
||||
@:forward(array, offset)
|
||||
abstract OffsetArray<T>({
|
||||
final array : Array<T>;
|
||||
final offset : Int;
|
||||
}) {
|
||||
final array:Array<T>;
|
||||
final offset:Int;
|
||||
})
|
||||
{
|
||||
public inline function new(array:Array<T>, offset:Int)
|
||||
this = { array: array, offset: offset };
|
||||
this = {array: array, offset: offset};
|
||||
|
||||
public var length(get, never):Int;
|
||||
|
||||
public var length(get,never) : Int;
|
||||
inline function get_length()
|
||||
return this.array.length;
|
||||
|
||||
@:arrayAccess
|
||||
public inline function get(index:Int) : T
|
||||
public inline function get(index:Int):T
|
||||
return this.array[index - this.offset];
|
||||
|
||||
@:arrayAccess
|
||||
public inline function set(index:Int, value:T) : Void
|
||||
public inline function set(index:Int, value:T):Void
|
||||
this.array[index - this.offset] = value;
|
||||
|
||||
/**
|
||||
Iterates through items in their original order while providing the altered indexes as keys.
|
||||
**/
|
||||
public inline function keyValueIterator() : KeyValueIterator<Int,T>
|
||||
public inline function keyValueIterator():KeyValueIterator<Int, T>
|
||||
return new OffsetArrayIterator(this.array, this.offset);
|
||||
|
||||
@:from
|
||||
|
@ -44,35 +46,42 @@ abstract OffsetArray<T>({
|
|||
same order but shifted by `n` positions (to the right if positive and to
|
||||
the left if negative) in **circular** fashion (no elements discarded).
|
||||
**/
|
||||
public static function circShift<T>(array:Array<T>, n:Int) : Array<T> {
|
||||
if (n < 0) return circShift(array, array.length + n);
|
||||
public static function circShift<T>(array:Array<T>, n:Int):Array<T>
|
||||
{
|
||||
if (n < 0)
|
||||
return circShift(array, array.length + n);
|
||||
|
||||
var shifted = new Array<T>();
|
||||
|
||||
n = n % array.length;
|
||||
for (i in array.length - n ... array.length) shifted.push(array[i]);
|
||||
for (i in 0 ... array.length - n) shifted.push(array[i]);
|
||||
for (i in array.length - n...array.length)
|
||||
shifted.push(array[i]);
|
||||
for (i in 0...array.length - n)
|
||||
shifted.push(array[i]);
|
||||
|
||||
return shifted;
|
||||
}
|
||||
}
|
||||
|
||||
private class OffsetArrayIterator<T> {
|
||||
private final array : Array<T>;
|
||||
private final offset : Int;
|
||||
private var enumeration : Int;
|
||||
private class OffsetArrayIterator<T>
|
||||
{
|
||||
private final array:Array<T>;
|
||||
private final offset:Int;
|
||||
private var enumeration:Int;
|
||||
|
||||
public inline function new(array:Array<T>, offset:Int) {
|
||||
public inline function new(array:Array<T>, offset:Int)
|
||||
{
|
||||
this.array = array;
|
||||
this.offset = offset;
|
||||
this.enumeration = 0;
|
||||
}
|
||||
|
||||
public inline function next() : {key:Int, value:T} {
|
||||
public inline function next():{key:Int, value:T}
|
||||
{
|
||||
final i = this.enumeration++;
|
||||
return { key: i + this.offset, value: this.array[i] };
|
||||
return {key: i + this.offset, value: this.array[i]};
|
||||
}
|
||||
|
||||
public inline function hasNext() : Bool
|
||||
public inline function hasNext():Bool
|
||||
return this.enumeration < this.array.length;
|
||||
}
|
|
@ -1,24 +1,31 @@
|
|||
package dsp;
|
||||
package audiovis.dsp;
|
||||
|
||||
using Lambda;
|
||||
|
||||
|
||||
/**
|
||||
Signal processing miscellaneous utilities.
|
||||
**/
|
||||
class Signal {
|
||||
class Signal
|
||||
{
|
||||
/**
|
||||
Returns a smoothed version of the input array using a moving average.
|
||||
**/
|
||||
public static function smooth(y:Array<Float>, n:Int) : Array<Float> {
|
||||
if (n <= 0) {
|
||||
public static function smooth(y:Array<Float>, n:Int):Array<Float>
|
||||
{
|
||||
if (n <= 0)
|
||||
{
|
||||
return null;
|
||||
} else if (n == 1) {
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
return y.copy();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
var smoothed = new Array<Float>();
|
||||
smoothed.resize(y.length);
|
||||
for (i in 0...y.length) {
|
||||
for (i in 0...y.length)
|
||||
{
|
||||
var m = i + 1 < n ? i : n - 1;
|
||||
smoothed[i] = sum(y.slice(i - m, i + 1));
|
||||
}
|
||||
|
@ -32,23 +39,19 @@ class Signal {
|
|||
@param threshold Minimal peak height wrt. its neighbours, defaults to 0.
|
||||
@param minHeight Minimal peak height wrt. the whole input, defaults to global minimum.
|
||||
**/
|
||||
public static function findPeaks(
|
||||
y:Array<Float>,
|
||||
?threshold:Float,
|
||||
?minHeight:Float
|
||||
) : Array<Int> {
|
||||
public static function findPeaks(y:Array<Float>, ?threshold:Float, ?minHeight:Float):Array<Int>
|
||||
{
|
||||
threshold = threshold == null ? 0.0 : Math.abs(threshold);
|
||||
minHeight = minHeight == null ? Signal.min(y) : minHeight;
|
||||
|
||||
var peaks = new Array<Int>();
|
||||
|
||||
final dy = [for (i in 1...y.length) y[i] - y[i-1]];
|
||||
for (i in 1...dy.length) {
|
||||
final dy = [for (i in 1...y.length) y[i] - y[i - 1]];
|
||||
for (i in 1...dy.length)
|
||||
{
|
||||
// peak: function growth positive to its left and negative to its right
|
||||
if (
|
||||
dy[i-1] > threshold && dy[i] < -threshold &&
|
||||
y[i] > minHeight
|
||||
) {
|
||||
if (dy[i - 1] > threshold && dy[i] < -threshold && y[i] > minHeight)
|
||||
{
|
||||
peaks.push(i);
|
||||
}
|
||||
}
|
||||
|
@ -61,17 +64,18 @@ class Signal {
|
|||
|
||||
This function tries to minimize floating-point precision errors.
|
||||
**/
|
||||
public static function sum(array:Array<Float>) : Float {
|
||||
public static function sum(array:Array<Float>):Float
|
||||
{
|
||||
// Neumaier's "improved Kahan-Babuska algorithm":
|
||||
|
||||
var sum = 0.0;
|
||||
var c = 0.0; // running compensation for lost precision
|
||||
|
||||
for (v in array) {
|
||||
for (v in array)
|
||||
{
|
||||
var t = sum + v;
|
||||
c += Math.abs(sum) >= Math.abs(v)
|
||||
? (sum - t) + v // sum is bigger => low-order digits of v are lost
|
||||
: (v - t) + sum; // v is bigger => low-order digits of sum are lost
|
||||
c += Math.abs(sum) >= Math.abs(v) ? (sum - t) + v // sum is bigger => low-order digits of v are lost
|
||||
: (v - t) + sum; // v is bigger => low-order digits of sum are lost
|
||||
sum = t;
|
||||
}
|
||||
|
||||
|
@ -81,30 +85,30 @@ class Signal {
|
|||
/**
|
||||
Returns the average value of an array.
|
||||
**/
|
||||
public static function mean(y:Array<Float>) : Float
|
||||
public static function mean(y:Array<Float>):Float
|
||||
return sum(y) / y.length;
|
||||
|
||||
/**
|
||||
Returns the global maximum.
|
||||
**/
|
||||
public static function max(y:Array<Float>) : Float
|
||||
public static function max(y:Array<Float>):Float
|
||||
return y.fold(Math.max, y[0]);
|
||||
|
||||
/**
|
||||
Returns the global maximum's index.
|
||||
**/
|
||||
public static function maxi(y:Array<Float>) : Int
|
||||
public static function maxi(y:Array<Float>):Int
|
||||
return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0);
|
||||
|
||||
/**
|
||||
Returns the global minimum.
|
||||
**/
|
||||
public static function min(y:Array<Float>) : Float
|
||||
public static function min(y:Array<Float>):Float
|
||||
return y.fold(Math.min, y[0]);
|
||||
|
||||
/**
|
||||
Returns the global minimum's index.
|
||||
**/
|
||||
public static function mini(y:Array<Float>) : Int
|
||||
public static function mini(y:Array<Float>):Int
|
||||
return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0);
|
||||
}
|
|
@ -4,6 +4,8 @@ import Conductor.BPMChangeEvent;
|
|||
import Note.NoteData;
|
||||
import Section.SwagSection;
|
||||
import SongLoad.SwagSong;
|
||||
import audiovis.ABotVis;
|
||||
import audiovis.SpectogramSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -26,6 +28,7 @@ import lime.utils.Assets;
|
|||
import openfl.events.Event;
|
||||
import openfl.events.IOErrorEvent;
|
||||
import openfl.net.FileReference;
|
||||
import rendering.MeshRender;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
|
83
source/rendering/MeshRender.hx
Normal file
83
source/rendering/MeshRender.hx
Normal file
|
@ -0,0 +1,83 @@
|
|||
package rendering;
|
||||
|
||||
import flixel.FlxStrip;
|
||||
|
||||
/**
|
||||
* Yoinked from AustinEast, thanks hopefully u dont mind me using some of ur good code
|
||||
* instead of my dumbass ugly code bro
|
||||
*/
|
||||
class MeshRender extends FlxStrip
|
||||
{
|
||||
public var vertex_count(default, null):Int = 0;
|
||||
public var index_count(default, null):Int = 0;
|
||||
|
||||
var tri_offset:Int = 0;
|
||||
|
||||
public function new(x, y)
|
||||
{
|
||||
super(x, y);
|
||||
makeGraphic(1, 1);
|
||||
}
|
||||
|
||||
public inline function start()
|
||||
{
|
||||
tri_offset = vertex_count;
|
||||
}
|
||||
|
||||
public inline function add_vertex(x:Float, y:Float, u:Float = 0, v:Float = 0)
|
||||
{
|
||||
final pos = vertex_count << 1;
|
||||
|
||||
vertices[pos] = x;
|
||||
vertices[pos + 1] = y;
|
||||
|
||||
uvtData[pos] = u;
|
||||
uvtData[pos + 1] = v;
|
||||
|
||||
vertex_count++;
|
||||
}
|
||||
|
||||
public function add_tri(a:Int, b:Int, c:Int)
|
||||
{
|
||||
indices[index_count] = a + tri_offset;
|
||||
indices[index_count + 1] = b + tri_offset;
|
||||
indices[index_count + 2] = c + tri_offset;
|
||||
|
||||
index_count += 3;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* top left - a
|
||||
*
|
||||
* top right - b
|
||||
*
|
||||
* bottom left - c
|
||||
*
|
||||
* bottom right - d
|
||||
*/
|
||||
public function add_quad(ax:Float, ay:Float, bx:Float, by:Float, cx:Float, cy:Float, dx:Float, dy:Float, au:Float = 0, av:Float = 0, bu:Float = 0,
|
||||
bv:Float = 0, cu:Float = 0, cv:Float = 0, du:Float = 0, dv:Float = 0)
|
||||
{
|
||||
start();
|
||||
// top left
|
||||
add_vertex(bx, by, bu, bv);
|
||||
// top right
|
||||
add_vertex(ax, ay, au, av);
|
||||
// bottom left
|
||||
add_vertex(cx, cy, cu, cv);
|
||||
// bottom right
|
||||
add_vertex(dx, dy, du, dv);
|
||||
|
||||
add_tri(0, 1, 2);
|
||||
add_tri(0, 2, 3);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
vertices.length = 0;
|
||||
indices.length = 0;
|
||||
vertex_count = 0;
|
||||
index_count = 0;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue