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<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);
 	}
 }
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<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;
 }
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<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);
 }
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;
+	}
+}