From c73e8e1a4dbade783dcd72fa2db49f6c0f0b833b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 10 Feb 2022 13:17:46 -0500 Subject: [PATCH] audiovis reorganize + MeshRender obj in progres --- source/TitleState.hx | 1 + source/{ => audiovis}/ABotVis.hx | 4 +- source/{ => audiovis}/SpectogramSprite.hx | 4 +- source/{ => audiovis}/VisShit.hx | 4 +- source/{ => audiovis}/dsp/Complex.hx | 2 +- source/{ => audiovis}/dsp/FFT.hx | 105 ++++++++++++---------- source/{ => audiovis}/dsp/OffsetArray.hx | 51 ++++++----- source/{ => audiovis}/dsp/Signal.hx | 62 +++++++------ source/charting/ChartingState.hx | 3 + source/rendering/MeshRender.hx | 83 +++++++++++++++++ 10 files changed, 217 insertions(+), 102 deletions(-) rename source/{ => audiovis}/ABotVis.hx (98%) rename source/{ => audiovis}/SpectogramSprite.hx (99%) rename source/{ => audiovis}/VisShit.hx (98%) rename source/{ => audiovis}/dsp/Complex.hx (98%) rename source/{ => audiovis}/dsp/FFT.hx (68%) rename source/{ => audiovis}/dsp/OffsetArray.hx (57%) rename source/{ => audiovis}/dsp/Signal.hx (63%) create mode 100644 source/rendering/MeshRender.hx diff --git a/source/TitleState.hx b/source/TitleState.hx index 76871872d..e3feea22d 100644 --- a/source/TitleState.hx +++ b/source/TitleState.hx @@ -1,5 +1,6 @@ package; +import audiovis.SpectogramSprite; import flixel.FlxObject; import flixel.FlxSprite; import flixel.FlxState; diff --git a/source/ABotVis.hx b/source/audiovis/ABotVis.hx similarity index 98% rename from source/ABotVis.hx rename to source/audiovis/ABotVis.hx index 968960b4c..c6411c4d8 100644 --- a/source/ABotVis.hx +++ b/source/audiovis/ABotVis.hx @@ -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; diff --git a/source/SpectogramSprite.hx b/source/audiovis/SpectogramSprite.hx similarity index 99% rename from source/SpectogramSprite.hx rename to source/audiovis/SpectogramSprite.hx index 06cee5465..24e630834 100644 --- a/source/SpectogramSprite.hx +++ b/source/audiovis/SpectogramSprite.hx @@ -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; diff --git a/source/VisShit.hx b/source/audiovis/VisShit.hx similarity index 98% rename from source/VisShit.hx rename to source/audiovis/VisShit.hx index 129f07215..71189d8a9 100644 --- a/source/VisShit.hx +++ b/source/audiovis/VisShit.hx @@ -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; diff --git a/source/dsp/Complex.hx b/source/audiovis/dsp/Complex.hx similarity index 98% rename from source/dsp/Complex.hx rename to source/audiovis/dsp/Complex.hx index d99b4490a..8dc8c9b7a 100644 --- a/source/dsp/Complex.hx +++ b/source/audiovis/dsp/Complex.hx @@ -1,4 +1,4 @@ -package dsp; +package audiovis.dsp; /** Complex number representation. diff --git a/source/dsp/FFT.hx b/source/audiovis/dsp/FFT.hx similarity index 68% rename from source/dsp/FFT.hx rename to source/audiovis/dsp/FFT.hx index b07b9def4..d46c8ffa1 100644 --- a/source/dsp/FFT.hx +++ b/source/audiovis/dsp/FFT.hx @@ -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) : Array + public static function fft(input:Array):Array 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) : Array { + public static function rfft(input:Array):Array + { 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) : Array + public static function ifft(input:Array):Array return do_fft(input, true); // Handles padding and scaling for forwards and inverse FFTs. - private static function do_fft(input:Array, inverse:Bool) : Array { + private static function do_fft(input:Array, inverse:Bool):Array + { 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, t:Int, - freq:Array, f:Int, - n:Int, step:Int, inverse: Bool - ) : Void { - if (n == 1) { + private static function ditfft2(time:Array, t:Int, freq:Array, 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, ?inverse:Bool) : Array { - if (inverse == null) inverse = false; + private static function dft(ts:Array, ?inverse:Bool):Array + { + if (inverse == null) + inverse = false; final n = ts.length; var fs = new Array(); 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); } } diff --git a/source/dsp/OffsetArray.hx b/source/audiovis/dsp/OffsetArray.hx similarity index 57% rename from source/dsp/OffsetArray.hx rename to source/audiovis/dsp/OffsetArray.hx index ab5dd2b22..ec5ddf539 100644 --- a/source/dsp/OffsetArray.hx +++ b/source/audiovis/dsp/OffsetArray.hx @@ -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({ - final array : Array; - final offset : Int; -}) { + final array:Array; + final offset:Int; +}) +{ public inline function new(array:Array, 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 + public inline function keyValueIterator():KeyValueIterator return new OffsetArrayIterator(this.array, this.offset); @:from @@ -44,35 +46,42 @@ abstract OffsetArray({ 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(array:Array, n:Int) : Array { - if (n < 0) return circShift(array, array.length + n); + public static function circShift(array:Array, n:Int):Array + { + if (n < 0) + return circShift(array, array.length + n); var shifted = new Array(); 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 { - private final array : Array; - private final offset : Int; - private var enumeration : Int; +private class OffsetArrayIterator +{ + private final array:Array; + private final offset:Int; + private var enumeration:Int; - public inline function new(array:Array, offset:Int) { + public inline function new(array:Array, 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; } diff --git a/source/dsp/Signal.hx b/source/audiovis/dsp/Signal.hx similarity index 63% rename from source/dsp/Signal.hx rename to source/audiovis/dsp/Signal.hx index dcc752f16..7c5631c21 100644 --- a/source/dsp/Signal.hx +++ b/source/audiovis/dsp/Signal.hx @@ -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, n:Int) : Array { - if (n <= 0) { + public static function smooth(y:Array, n:Int):Array + { + if (n <= 0) + { return null; - } else if (n == 1) { + } + else if (n == 1) + { return y.copy(); - } else { + } + else + { var smoothed = new Array(); 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, - ?threshold:Float, - ?minHeight:Float - ) : Array { + public static function findPeaks(y:Array, ?threshold:Float, ?minHeight:Float):Array + { threshold = threshold == null ? 0.0 : Math.abs(threshold); minHeight = minHeight == null ? Signal.min(y) : minHeight; var peaks = new Array(); - 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 { + public static function sum(array:Array):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 + public static function mean(y:Array):Float return sum(y) / y.length; /** Returns the global maximum. **/ - public static function max(y:Array) : Float + public static function max(y:Array):Float return y.fold(Math.max, y[0]); /** Returns the global maximum's index. **/ - public static function maxi(y:Array) : Int + public static function maxi(y:Array):Int return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0); /** Returns the global minimum. **/ - public static function min(y:Array) : Float + public static function min(y:Array):Float return y.fold(Math.min, y[0]); /** Returns the global minimum's index. **/ - public static function mini(y:Array) : Int + public static function mini(y:Array):Int return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0); } diff --git a/source/charting/ChartingState.hx b/source/charting/ChartingState.hx index 1157f88e4..a746dd8ee 100644 --- a/source/charting/ChartingState.hx +++ b/source/charting/ChartingState.hx @@ -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; diff --git a/source/rendering/MeshRender.hx b/source/rendering/MeshRender.hx new file mode 100644 index 000000000..25cbbf96f --- /dev/null +++ b/source/rendering/MeshRender.hx @@ -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; + } +}