summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices
diff options
context:
space:
mode:
author MooglyGuy <therealmogminer@gmail.com>2020-03-01 19:58:07 +0100
committer MooglyGuy <therealmogminer@gmail.com>2020-03-01 19:58:07 +0100
commitbcefd9527534b1197cd28b45dec4dec95ce3ee50 (patch)
treeac814b6f389b8e5ab520c3f6e96ab191230d5035 /src/devices
parentfd0af1342209a7a32cc773a004fdc8a6ae30b66b (diff)
-vgmplay: Added a visualizer. [Ryan Holtz, Justin Frankel]
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/sound/fft.cpp1206
-rw-r--r--src/devices/sound/fft.h82
-rw-r--r--src/devices/sound/vgm_visualizer.cpp483
-rw-r--r--src/devices/sound/vgm_visualizer.h89
4 files changed, 1860 insertions, 0 deletions
diff --git a/src/devices/sound/fft.cpp b/src/devices/sound/fft.cpp
new file mode 100644
index 00000000000..af5e6536feb
--- /dev/null
+++ b/src/devices/sound/fft.cpp
@@ -0,0 +1,1206 @@
+// license:BSD-3-Clause
+// copyright-holders:Justin Frankel
+/*
+ WDL - fft.cpp
+ Copyright (C) 2006 and later Cockos Incorporated
+ Copyright 1999 D. J. Bernstein
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+
+
+ This file implements the WDL FFT library. These routines are based on the
+ DJBFFT library, which are Copyright 1999 D. J. Bernstein, djb@pobox.com
+
+ The DJB FFT web page is: http://cr.yp.to/djbfft.html
+
+
+ This file is modified from the original; it has been reformatted for 4-space
+ tabs and to have the 'register' keyword omitted.
+*/
+
+
+// this is based on djbfft
+
+#include "emu.h"
+#include <math.h>
+#include "fft.h"
+
+
+#define FFT_MAXBITLEN 15
+
+#ifdef _MSC_VER
+#define inline __inline
+#endif
+
+#define PI 3.1415926535897932384626433832795
+
+static WDL_FFT_COMPLEX d16[3];
+static WDL_FFT_COMPLEX d32[7];
+static WDL_FFT_COMPLEX d64[15];
+static WDL_FFT_COMPLEX d128[31];
+static WDL_FFT_COMPLEX d256[63];
+static WDL_FFT_COMPLEX d512[127];
+static WDL_FFT_COMPLEX d1024[127];
+static WDL_FFT_COMPLEX d2048[255];
+static WDL_FFT_COMPLEX d4096[511];
+static WDL_FFT_COMPLEX d8192[1023];
+static WDL_FFT_COMPLEX d16384[2047];
+static WDL_FFT_COMPLEX d32768[4095];
+
+
+#define sqrthalf (d16[1].re)
+
+#define VOL *(volatile WDL_FFT_REAL *)&
+
+#define TRANSFORM(a0,a1,a2,a3,wre,wim) { \
+ t6 = a2.re; \
+ t1 = a0.re - t6; \
+ t6 += a0.re; \
+ a0.re = t6; \
+ t3 = a3.im; \
+ t4 = a1.im - t3; \
+ t8 = t1 - t4; \
+ t1 += t4; \
+ t3 += a1.im; \
+ a1.im = t3; \
+ t5 = wre; \
+ t7 = t8 * t5; \
+ t4 = t1 * t5; \
+ t8 *= wim; \
+ t2 = a3.re; \
+ t3 = a1.re - t2; \
+ t2 += a1.re; \
+ a1.re = t2; \
+ t1 *= wim; \
+ t6 = a2.im; \
+ t2 = a0.im - t6; \
+ t6 += a0.im; \
+ a0.im = t6; \
+ t6 = t2 + t3; \
+ t2 -= t3; \
+ t3 = t6 * wim; \
+ t7 -= t3; \
+ a2.re = t7; \
+ t6 *= t5; \
+ t6 += t8; \
+ a2.im = t6; \
+ t5 *= t2; \
+ t5 -= t1; \
+ a3.im = t5; \
+ t2 *= wim; \
+ t4 += t2; \
+ a3.re = t4; \
+ }
+
+#define TRANSFORMHALF(a0,a1,a2,a3) { \
+ t1 = a2.re; \
+ t5 = a0.re - t1; \
+ t1 += a0.re; \
+ a0.re = t1; \
+ t4 = a3.im; \
+ t8 = a1.im - t4; \
+ t1 = t5 - t8; \
+ t5 += t8; \
+ t4 += a1.im; \
+ a1.im = t4; \
+ t3 = a3.re; \
+ t7 = a1.re - t3; \
+ t3 += a1.re; \
+ a1.re = t3; \
+ t8 = a2.im; \
+ t6 = a0.im - t8; \
+ t2 = t6 + t7; \
+ t6 -= t7; \
+ t8 += a0.im; \
+ a0.im = t8; \
+ t4 = t6 + t5; \
+ t3 = sqrthalf; \
+ t4 *= t3; \
+ a3.re = t4; \
+ t6 -= t5; \
+ t6 *= t3; \
+ a3.im = t6; \
+ t7 = t1 - t2; \
+ t7 *= t3; \
+ a2.re = t7; \
+ t2 += t1; \
+ t2 *= t3; \
+ a2.im = t2; \
+ }
+
+#define TRANSFORMZERO(a0,a1,a2,a3) { \
+ t5 = a2.re; \
+ t1 = a0.re - t5; \
+ t5 += a0.re; \
+ a0.re = t5; \
+ t8 = a3.im; \
+ t4 = a1.im - t8; \
+ t7 = a3.re; \
+ t6 = t1 - t4; \
+ a2.re = t6; \
+ t1 += t4; \
+ a3.re = t1; \
+ t8 += a1.im; \
+ a1.im = t8; \
+ t3 = a1.re - t7; \
+ t7 += a1.re; \
+ a1.re = t7; \
+ t6 = a2.im; \
+ t2 = a0.im - t6; \
+ t7 = t2 + t3; \
+ a2.im = t7; \
+ t2 -= t3; \
+ a3.im = t2; \
+ t6 += a0.im; \
+ a0.im = t6; \
+ }
+
+#define UNTRANSFORM(a0,a1,a2,a3,wre,wim) { \
+ t6 = VOL wre; \
+ t1 = VOL a2.re; \
+ t1 *= t6; \
+ t8 = VOL wim; \
+ t3 = VOL a2.im; \
+ t3 *= t8; \
+ t2 = VOL a2.im; \
+ t4 = VOL a2.re; \
+ t5 = VOL a3.re; \
+ t5 *= t6; \
+ t7 = VOL a3.im; \
+ t1 += t3; \
+ t7 *= t8; \
+ t5 -= t7; \
+ t3 = t5 + t1; \
+ t5 -= t1; \
+ t2 *= t6; \
+ t6 *= a3.im; \
+ t4 *= t8; \
+ t2 -= t4; \
+ t8 *= a3.re; \
+ t6 += t8; \
+ t1 = a0.re - t3; \
+ t3 += a0.re; \
+ a0.re = t3; \
+ t7 = a1.im - t5; \
+ t5 += a1.im; \
+ a1.im = t5; \
+ t4 = t2 - t6; \
+ t6 += t2; \
+ t8 = a1.re - t4; \
+ t4 += a1.re; \
+ a1.re = t4; \
+ t2 = a0.im - t6; \
+ t6 += a0.im; \
+ a0.im = t6; \
+ a2.re = t1; \
+ a3.im = t7; \
+ a3.re = t8; \
+ a2.im = t2; \
+ }
+
+
+#define UNTRANSFORMHALF(a0,a1,a2,a3) { \
+ t6 = sqrthalf; \
+ t1 = a2.re; \
+ t2 = a2.im - t1; \
+ t2 *= t6; \
+ t1 += a2.im; \
+ t1 *= t6; \
+ t4 = a3.im; \
+ t3 = a3.re - t4; \
+ t3 *= t6; \
+ t4 += a3.re; \
+ t4 *= t6; \
+ t8 = t3 - t1; \
+ t7 = t2 - t4; \
+ t1 += t3; \
+ t2 += t4; \
+ t4 = a1.im - t8; \
+ a3.im = t4; \
+ t8 += a1.im; \
+ a1.im = t8; \
+ t3 = a1.re - t7; \
+ a3.re = t3; \
+ t7 += a1.re; \
+ a1.re = t7; \
+ t5 = a0.re - t1; \
+ a2.re = t5; \
+ t1 += a0.re; \
+ a0.re = t1; \
+ t6 = a0.im - t2; \
+ a2.im = t6; \
+ t2 += a0.im; \
+ a0.im = t2; \
+ }
+
+#define UNTRANSFORMZERO(a0,a1,a2,a3) { \
+ t2 = a3.im; \
+ t3 = a2.im - t2; \
+ t2 += a2.im; \
+ t1 = a2.re; \
+ t4 = a3.re - t1; \
+ t1 += a3.re; \
+ t5 = a0.re - t1; \
+ a2.re = t5; \
+ t6 = a0.im - t2; \
+ a2.im = t6; \
+ t7 = a1.re - t3; \
+ a3.re = t7; \
+ t8 = a1.im - t4; \
+ a3.im = t8; \
+ t1 += a0.re; \
+ a0.re = t1; \
+ t2 += a0.im; \
+ a0.im = t2; \
+ t3 += a1.re; \
+ a1.re = t3; \
+ t4 += a1.im; \
+ a1.im = t4; \
+ }
+
+static void c2(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1;
+
+ t1 = a[1].re;
+ a[1].re = a[0].re - t1;
+ a[0].re += t1;
+
+ t1 = a[1].im;
+ a[1].im = a[0].im - t1;
+ a[0].im += t1;
+}
+
+static inline void c4(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+
+ t5 = a[2].re;
+ t1 = a[0].re - t5;
+ t7 = a[3].re;
+ t5 += a[0].re;
+ t3 = a[1].re - t7;
+ t7 += a[1].re;
+ t8 = t5 + t7;
+ a[0].re = t8;
+ t5 -= t7;
+ a[1].re = t5;
+ t6 = a[2].im;
+ t2 = a[0].im - t6;
+ t6 += a[0].im;
+ t5 = a[3].im;
+ a[2].im = t2 + t3;
+ t2 -= t3;
+ a[3].im = t2;
+ t4 = a[1].im - t5;
+ a[3].re = t1 + t4;
+ t1 -= t4;
+ a[2].re = t1;
+ t5 += a[1].im;
+ a[0].im = t6 + t5;
+ t6 -= t5;
+ a[1].im = t6;
+}
+
+static void c8(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+
+ t7 = a[4].im;
+ t4 = a[0].im - t7;
+ t7 += a[0].im;
+ a[0].im = t7;
+
+ t8 = a[6].re;
+ t5 = a[2].re - t8;
+ t8 += a[2].re;
+ a[2].re = t8;
+
+ t7 = a[6].im;
+ a[6].im = t4 - t5;
+ t4 += t5;
+ a[4].im = t4;
+
+ t6 = a[2].im - t7;
+ t7 += a[2].im;
+ a[2].im = t7;
+
+ t8 = a[4].re;
+ t3 = a[0].re - t8;
+ t8 += a[0].re;
+ a[0].re = t8;
+
+ a[4].re = t3 - t6;
+ t3 += t6;
+ a[6].re = t3;
+
+ t7 = a[5].re;
+ t3 = a[1].re - t7;
+ t7 += a[1].re;
+ a[1].re = t7;
+
+ t8 = a[7].im;
+ t6 = a[3].im - t8;
+ t8 += a[3].im;
+ a[3].im = t8;
+ t1 = t3 - t6;
+ t3 += t6;
+
+ t7 = a[5].im;
+ t4 = a[1].im - t7;
+ t7 += a[1].im;
+ a[1].im = t7;
+
+ t8 = a[7].re;
+ t5 = a[3].re - t8;
+ t8 += a[3].re;
+ a[3].re = t8;
+
+ t2 = t4 - t5;
+ t4 += t5;
+
+ t6 = t1 - t4;
+ t8 = sqrthalf;
+ t6 *= t8;
+ a[5].re = a[4].re - t6;
+ t1 += t4;
+ t1 *= t8;
+ a[5].im = a[4].im - t1;
+ t6 += a[4].re;
+ a[4].re = t6;
+ t1 += a[4].im;
+ a[4].im = t1;
+
+ t5 = t2 - t3;
+ t5 *= t8;
+ a[7].im = a[6].im - t5;
+ t2 += t3;
+ t2 *= t8;
+ a[7].re = a[6].re - t2;
+ t2 += a[6].re;
+ a[6].re = t2;
+ t5 += a[6].im;
+ a[6].im = t5;
+
+ c4(a);
+}
+
+static void c16(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+
+ TRANSFORMZERO(a[0],a[4],a[8],a[12]);
+ TRANSFORM(a[1],a[5],a[9],a[13],d16[0].re,d16[0].im);
+ TRANSFORMHALF(a[2],a[6],a[10],a[14]);
+ TRANSFORM(a[3],a[7],a[11],a[15],d16[0].im,d16[0].re);
+ c4(a + 8);
+ c4(a + 12);
+
+ c8(a);
+}
+
+/* a[0...8n-1], w[0...2n-2]; n >= 2 */
+static void cpass(WDL_FFT_COMPLEX *a,const WDL_FFT_COMPLEX *w,unsigned int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ WDL_FFT_COMPLEX *a1;
+ WDL_FFT_COMPLEX *a2;
+ WDL_FFT_COMPLEX *a3;
+
+ a2 = a + 4 * n;
+ a1 = a + 2 * n;
+ a3 = a2 + 2 * n;
+ --n;
+
+ TRANSFORMZERO(a[0],a1[0],a2[0],a3[0]);
+ TRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im);
+
+ for (;;) {
+ TRANSFORM(a[2],a1[2],a2[2],a3[2],w[1].re,w[1].im);
+ TRANSFORM(a[3],a1[3],a2[3],a3[3],w[2].re,w[2].im);
+ if (!--n) break;
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+ w += 2;
+ }
+}
+
+static void c32(WDL_FFT_COMPLEX *a)
+{
+ cpass(a,d32,4);
+ c8(a + 16);
+ c8(a + 24);
+ c16(a);
+}
+
+static void c64(WDL_FFT_COMPLEX *a)
+{
+ cpass(a,d64,8);
+ c16(a + 32);
+ c16(a + 48);
+ c32(a);
+}
+
+static void c128(WDL_FFT_COMPLEX *a)
+{
+ cpass(a,d128,16);
+ c32(a + 64);
+ c32(a + 96);
+ c64(a);
+}
+
+static void c256(WDL_FFT_COMPLEX *a)
+{
+ cpass(a,d256,32);
+ c64(a + 128);
+ c64(a + 192);
+ c128(a);
+}
+
+static void c512(WDL_FFT_COMPLEX *a)
+{
+ cpass(a,d512,64);
+ c128(a + 384);
+ c128(a + 256);
+ c256(a);
+}
+
+/* a[0...8n-1], w[0...n-2]; n even, n >= 4 */
+static void cpassbig(WDL_FFT_COMPLEX *a,const WDL_FFT_COMPLEX *w,unsigned int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ WDL_FFT_COMPLEX *a1;
+ WDL_FFT_COMPLEX *a2;
+ WDL_FFT_COMPLEX *a3;
+ unsigned int k;
+
+ a2 = a + 4 * n;
+ a1 = a + 2 * n;
+ a3 = a2 + 2 * n;
+ k = n - 2;
+
+ TRANSFORMZERO(a[0],a1[0],a2[0],a3[0]);
+ TRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+
+ do {
+ TRANSFORM(a[0],a1[0],a2[0],a3[0],w[1].re,w[1].im);
+ TRANSFORM(a[1],a1[1],a2[1],a3[1],w[2].re,w[2].im);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+ w += 2;
+ } while (k -= 2);
+
+ TRANSFORMHALF(a[0],a1[0],a2[0],a3[0]);
+ TRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].im,w[0].re);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+
+ k = n - 2;
+ do {
+ TRANSFORM(a[0],a1[0],a2[0],a3[0],w[-1].im,w[-1].re);
+ TRANSFORM(a[1],a1[1],a2[1],a3[1],w[-2].im,w[-2].re);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+ w -= 2;
+ } while (k -= 2);
+}
+
+
+static void c1024(WDL_FFT_COMPLEX *a)
+{
+ cpassbig(a,d1024,128);
+ c256(a + 768);
+ c256(a + 512);
+ c512(a);
+}
+
+static void c2048(WDL_FFT_COMPLEX *a)
+{
+ cpassbig(a,d2048,256);
+ c512(a + 1536);
+ c512(a + 1024);
+ c1024(a);
+}
+
+static void c4096(WDL_FFT_COMPLEX *a)
+{
+ cpassbig(a,d4096,512);
+ c1024(a + 3072);
+ c1024(a + 2048);
+ c2048(a);
+}
+
+static void c8192(WDL_FFT_COMPLEX *a)
+{
+ cpassbig(a,d8192,1024);
+ c2048(a + 6144);
+ c2048(a + 4096);
+ c4096(a);
+}
+
+static void c16384(WDL_FFT_COMPLEX *a)
+{
+ cpassbig(a,d16384,2048);
+ c4096(a + 8192 + 4096);
+ c4096(a + 8192);
+ c8192(a);
+}
+
+static void c32768(WDL_FFT_COMPLEX *a)
+{
+ cpassbig(a,d32768,4096);
+ c8192(a + 16384 + 8192);
+ c8192(a + 16384);
+ c16384(a);
+}
+
+
+/* n even, n > 0 */
+void WDL_fft_complexmul(WDL_FFT_COMPLEX *a,WDL_FFT_COMPLEX *b,int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ if (n<2 || (n&1)) return;
+
+ do {
+ t1 = a[0].re * b[0].re;
+ t2 = a[0].im * b[0].im;
+ t3 = a[0].im * b[0].re;
+ t4 = a[0].re * b[0].im;
+ t5 = a[1].re * b[1].re;
+ t6 = a[1].im * b[1].im;
+ t7 = a[1].im * b[1].re;
+ t8 = a[1].re * b[1].im;
+ t1 -= t2;
+ t3 += t4;
+ t5 -= t6;
+ t7 += t8;
+ a[0].re = t1;
+ a[1].re = t5;
+ a[0].im = t3;
+ a[1].im = t7;
+ a += 2;
+ b += 2;
+ } while (n -= 2);
+}
+
+void WDL_fft_complexmul2(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_FFT_COMPLEX *b, int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ if (n<2 || (n&1)) return;
+
+ do {
+ t1 = a[0].re * b[0].re;
+ t2 = a[0].im * b[0].im;
+ t3 = a[0].im * b[0].re;
+ t4 = a[0].re * b[0].im;
+ t5 = a[1].re * b[1].re;
+ t6 = a[1].im * b[1].im;
+ t7 = a[1].im * b[1].re;
+ t8 = a[1].re * b[1].im;
+ t1 -= t2;
+ t3 += t4;
+ t5 -= t6;
+ t7 += t8;
+ c[0].re = t1;
+ c[1].re = t5;
+ c[0].im = t3;
+ c[1].im = t7;
+ a += 2;
+ b += 2;
+ c += 2;
+ } while (n -= 2);
+}
+
+void WDL_fft_complexmul3(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_FFT_COMPLEX *b, int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ if (n<2 || (n&1)) return;
+
+ do {
+ t1 = a[0].re * b[0].re;
+ t2 = a[0].im * b[0].im;
+ t3 = a[0].im * b[0].re;
+ t4 = a[0].re * b[0].im;
+ t5 = a[1].re * b[1].re;
+ t6 = a[1].im * b[1].im;
+ t7 = a[1].im * b[1].re;
+ t8 = a[1].re * b[1].im;
+ t1 -= t2;
+ t3 += t4;
+ t5 -= t6;
+ t7 += t8;
+ c[0].re += t1;
+ c[1].re += t5;
+ c[0].im += t3;
+ c[1].im += t7;
+ a += 2;
+ b += 2;
+ c += 2;
+ } while (n -= 2);
+}
+
+
+static inline void un4(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+
+ t1 = VOL a[1].re;
+ t3 = a[0].re - t1;
+ t6 = VOL a[2].re;
+ t1 += a[0].re;
+ t8 = a[3].re - t6;
+ t6 += a[3].re;
+ a[0].re = t1 + t6;
+ t1 -= t6;
+ a[2].re = t1;
+
+ t2 = VOL a[1].im;
+ t4 = a[0].im - t2;
+ t2 += a[0].im;
+ t5 = VOL a[3].im;
+ a[1].im = t4 + t8;
+ t4 -= t8;
+ a[3].im = t4;
+
+ t7 = a[2].im - t5;
+ t5 += a[2].im;
+ a[1].re = t3 + t7;
+ t3 -= t7;
+ a[3].re = t3;
+ a[0].im = t2 + t5;
+ t2 -= t5;
+ a[2].im = t2;
+}
+
+static void un8(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+
+ un4(a);
+
+ t1 = a[5].re;
+ a[5].re = a[4].re - t1;
+ t1 += a[4].re;
+
+ t3 = a[7].re;
+ a[7].re = a[6].re - t3;
+ t3 += a[6].re;
+
+ t8 = t3 - t1;
+ t1 += t3;
+
+ t6 = a[2].im - t8;
+ t8 += a[2].im;
+ a[2].im = t8;
+
+ t5 = a[0].re - t1;
+ a[4].re = t5;
+ t1 += a[0].re;
+ a[0].re = t1;
+
+ t2 = a[5].im;
+ a[5].im = a[4].im - t2;
+ t2 += a[4].im;
+
+ t4 = a[7].im;
+ a[7].im = a[6].im - t4;
+ t4 += a[6].im;
+
+ a[6].im = t6;
+
+ t7 = t2 - t4;
+ t2 += t4;
+
+ t3 = a[2].re - t7;
+ a[6].re = t3;
+ t7 += a[2].re;
+ a[2].re = t7;
+
+ t6 = a[0].im - t2;
+ a[4].im = t6;
+ t2 += a[0].im;
+ a[0].im = t2;
+
+ t6 = sqrthalf;
+
+ t1 = a[5].re;
+ t2 = a[5].im - t1;
+ t2 *= t6;
+ t1 += a[5].im;
+ t1 *= t6;
+ t4 = a[7].im;
+ t3 = a[7].re - t4;
+ t3 *= t6;
+ t4 += a[7].re;
+ t4 *= t6;
+
+ t8 = t3 - t1;
+ t1 += t3;
+ t7 = t2 - t4;
+ t2 += t4;
+
+ t4 = a[3].im - t8;
+ a[7].im = t4;
+ t5 = a[1].re - t1;
+ a[5].re = t5;
+ t3 = a[3].re - t7;
+ a[7].re = t3;
+ t6 = a[1].im - t2;
+ a[5].im = t6;
+
+ t8 += a[3].im;
+ a[3].im = t8;
+ t1 += a[1].re;
+ a[1].re = t1;
+ t7 += a[3].re;
+ a[3].re = t7;
+ t2 += a[1].im;
+ a[1].im = t2;
+}
+
+static void un16(WDL_FFT_COMPLEX *a)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+
+ un8(a);
+ un4(a + 8);
+ un4(a + 12);
+
+ UNTRANSFORMZERO(a[0],a[4],a[8],a[12]);
+ UNTRANSFORMHALF(a[2],a[6],a[10],a[14]);
+ UNTRANSFORM(a[1],a[5],a[9],a[13],d16[0].re,d16[0].im);
+ UNTRANSFORM(a[3],a[7],a[11],a[15],d16[0].im,d16[0].re);
+}
+
+/* a[0...8n-1], w[0...2n-2] */
+static void upass(WDL_FFT_COMPLEX *a,const WDL_FFT_COMPLEX *w,unsigned int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ WDL_FFT_COMPLEX *a1;
+ WDL_FFT_COMPLEX *a2;
+ WDL_FFT_COMPLEX *a3;
+
+ a2 = a + 4 * n;
+ a1 = a + 2 * n;
+ a3 = a2 + 2 * n;
+ n -= 1;
+
+ UNTRANSFORMZERO(a[0],a1[0],a2[0],a3[0]);
+ UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im);
+
+ for (;;) {
+ UNTRANSFORM(a[2],a1[2],a2[2],a3[2],w[1].re,w[1].im);
+ UNTRANSFORM(a[3],a1[3],a2[3],a3[3],w[2].re,w[2].im);
+ if (!--n) break;
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+ w += 2;
+ }
+}
+
+static void un32(WDL_FFT_COMPLEX *a)
+{
+ un16(a);
+ un8(a + 16);
+ un8(a + 24);
+ upass(a,d32,4);
+}
+
+static void un64(WDL_FFT_COMPLEX *a)
+{
+ un32(a);
+ un16(a + 32);
+ un16(a + 48);
+ upass(a,d64,8);
+}
+
+static void un128(WDL_FFT_COMPLEX *a)
+{
+ un64(a);
+ un32(a + 64);
+ un32(a + 96);
+ upass(a,d128,16);
+}
+
+static void un256(WDL_FFT_COMPLEX *a)
+{
+ un128(a);
+ un64(a + 128);
+ un64(a + 192);
+ upass(a,d256,32);
+}
+
+static void un512(WDL_FFT_COMPLEX *a)
+{
+ un256(a);
+ un128(a + 256);
+ un128(a + 384);
+ upass(a,d512,64);
+}
+
+
+/* a[0...8n-1], w[0...n-2]; n even, n >= 4 */
+static void upassbig(WDL_FFT_COMPLEX *a,const WDL_FFT_COMPLEX *w,unsigned int n)
+{
+ WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8;
+ WDL_FFT_COMPLEX *a1;
+ WDL_FFT_COMPLEX *a2;
+ WDL_FFT_COMPLEX *a3;
+ unsigned int k;
+
+ a2 = a + 4 * n;
+ a1 = a + 2 * n;
+ a3 = a2 + 2 * n;
+ k = n - 2;
+
+ UNTRANSFORMZERO(a[0],a1[0],a2[0],a3[0]);
+ UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].re,w[0].im);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+
+ do {
+ UNTRANSFORM(a[0],a1[0],a2[0],a3[0],w[1].re,w[1].im);
+ UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[2].re,w[2].im);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+ w += 2;
+ } while (k -= 2);
+
+ UNTRANSFORMHALF(a[0],a1[0],a2[0],a3[0]);
+ UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[0].im,w[0].re);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+
+ k = n - 2;
+ do {
+ UNTRANSFORM(a[0],a1[0],a2[0],a3[0],w[-1].im,w[-1].re);
+ UNTRANSFORM(a[1],a1[1],a2[1],a3[1],w[-2].im,w[-2].re);
+ a += 2;
+ a1 += 2;
+ a2 += 2;
+ a3 += 2;
+ w -= 2;
+ } while (k -= 2);
+}
+
+
+
+static void un1024(WDL_FFT_COMPLEX *a)
+{
+ un512(a);
+ un256(a + 512);
+ un256(a + 768);
+ upassbig(a,d1024,128);
+}
+
+static void un2048(WDL_FFT_COMPLEX *a)
+{
+ un1024(a);
+ un512(a + 1024);
+ un512(a + 1536);
+ upassbig(a,d2048,256);
+}
+
+
+static void un4096(WDL_FFT_COMPLEX *a)
+{
+ un2048(a);
+ un1024(a + 2048);
+ un1024(a + 3072);
+ upassbig(a,d4096,512);
+}
+
+static void un8192(WDL_FFT_COMPLEX *a)
+{
+ un4096(a);
+ un2048(a + 4096);
+ un2048(a + 6144);
+ upassbig(a,d8192,1024);
+}
+
+static void un16384(WDL_FFT_COMPLEX *a)
+{
+ un8192(a);
+ un4096(a + 8192);
+ un4096(a + 8192 + 4096);
+ upassbig(a,d16384,2048);
+}
+
+static void un32768(WDL_FFT_COMPLEX *a)
+{
+ un16384(a);
+ un8192(a + 16384);
+ un8192(a + 16384 + 8192 );
+ upassbig(a,d32768,4096);
+}
+
+
+static void __fft_gen(WDL_FFT_COMPLEX *buf, const WDL_FFT_COMPLEX *buf2, int sz, int isfull)
+{
+ int x;
+ double div=PI*0.25/(sz+1);
+
+ if (isfull) div*=2.0;
+
+ for (x = 0; x < sz; x ++)
+ {
+ if (!(x & 1) || !buf2)
+ {
+ buf[x].re = (WDL_FFT_REAL) cos((x+1)*div);
+ buf[x].im = (WDL_FFT_REAL) sin((x+1)*div);
+ }
+ else
+ {
+ buf[x].re = buf2[x >> 1].re;
+ buf[x].im = buf2[x >> 1].im;
+ }
+ }
+}
+
+#ifndef WDL_FFT_NO_PERMUTE
+
+static unsigned int fftfreq_c(unsigned int i,unsigned int n)
+{
+ unsigned int m;
+
+ if (n <= 2) return i;
+
+ m = n >> 1;
+ if (i < m) return fftfreq_c(i,m) << 1;
+
+ i -= m;
+ m >>= 1;
+ if (i < m) return (fftfreq_c(i,m) << 2) + 1;
+ i -= m;
+ return ((fftfreq_c(i,m) << 2) - 1) & (n - 1);
+}
+
+static int _idxperm[2<<FFT_MAXBITLEN];
+
+static void idx_perm_calc(int offs, int n)
+{
+ int i, j;
+ _idxperm[offs] = 0;
+ for (i = 1; i < n; ++i) {
+ j = fftfreq_c(i, n);
+ _idxperm[offs+n-j] = i;
+ }
+}
+
+int WDL_fft_permute(int fftsize, int idx)
+{
+ return _idxperm[fftsize+idx-2];
+}
+int *WDL_fft_permute_tab(int fftsize)
+{
+ return _idxperm + fftsize - 2;
+}
+
+
+#endif
+
+void WDL_fft_init()
+{
+ static int ffttabinit;
+ if (!ffttabinit)
+ {
+ int i, offs;
+ ffttabinit=1;
+
+#define fft_gen(x,y,z) __fft_gen(x,y,sizeof(x)/sizeof(x[0]),z)
+ fft_gen(d16,0,1);
+ fft_gen(d32,d16,1);
+ fft_gen(d64,d32,1);
+ fft_gen(d128,d64,1);
+ fft_gen(d256,d128,1);
+ fft_gen(d512,d256,1);
+ fft_gen(d1024,d512,0);
+ fft_gen(d2048,d1024,0);
+ fft_gen(d4096,d2048,0);
+ fft_gen(d8192,d4096,0);
+ fft_gen(d16384,d8192,0);
+ fft_gen(d32768,d16384,0);
+#undef fft_gen
+
+#ifndef WDL_FFT_NO_PERMUTE
+ offs = 0;
+ for (i = 2; i <= 32768; i *= 2)
+ {
+ idx_perm_calc(offs, i);
+ offs += i;
+ }
+#endif
+
+ }
+}
+
+void WDL_fft(WDL_FFT_COMPLEX *buf, int len, int isInverse)
+{
+ switch (len)
+ {
+ case 2: c2(buf); break;
+#define TMP(x) case x: if (!isInverse) c##x(buf); else un##x(buf); break;
+ TMP(4)
+ TMP(8)
+ TMP(16)
+ TMP(32)
+ TMP(64)
+ TMP(128)
+ TMP(256)
+ TMP(512)
+ TMP(1024)
+ TMP(2048)
+ TMP(4096)
+ TMP(8192)
+ TMP(16384)
+ TMP(32768)
+#undef TMP
+ }
+}
+
+static inline void r2(WDL_FFT_REAL *a)
+{
+ WDL_FFT_REAL t1, t2;
+
+ t1 = a[0] + a[1];
+ t2 = a[0] - a[1];
+ a[0] = t1 * 2;
+ a[1] = t2 * 2;
+}
+
+static inline void v2(WDL_FFT_REAL *a)
+{
+ WDL_FFT_REAL t1, t2;
+
+ t1 = a[0] + a[1];
+ t2 = a[0] - a[1];
+ a[0] = t1;
+ a[1] = t2;
+}
+
+static void two_for_one(WDL_FFT_REAL* buf, const WDL_FFT_COMPLEX *d, int len, int isInverse)
+{
+ const unsigned int half = (unsigned)len >> 1, quart = half >> 1, eighth = quart >> 1;
+ const int *permute = WDL_fft_permute_tab(half);
+ unsigned int i, j;
+
+ WDL_FFT_COMPLEX *p, *q, tw, sum, diff;
+ WDL_FFT_REAL tw1, tw2;
+
+ if (!isInverse)
+ {
+ WDL_fft((WDL_FFT_COMPLEX*)buf, half, isInverse);
+ r2(buf);
+ }
+ else
+ {
+ v2(buf);
+ }
+
+ /* Source: http://www.katjaas.nl/realFFT/realFFT2.html */
+
+ for (i = 1; i < quart; ++i)
+ {
+ p = (WDL_FFT_COMPLEX*)buf + permute[i];
+ q = (WDL_FFT_COMPLEX*)buf + permute[half - i];
+
+/* tw.re = cos(2*PI * i / len);
+ tw.im = sin(2*PI * i / len); */
+
+ if (i < eighth)
+ {
+ j = i - 1;
+ tw.re = d[j].re;
+ tw.im = d[j].im;
+ }
+ else if (i > eighth)
+ {
+ j = quart - i - 1;
+ tw.re = d[j].im;
+ tw.im = d[j].re;
+ }
+ else
+ {
+ tw.re = tw.im = sqrthalf;
+ }
+
+ if (!isInverse) tw.re = -tw.re;
+
+ sum.re = p->re + q->re;
+ sum.im = p->im + q->im;
+ diff.re = p->re - q->re;
+ diff.im = p->im - q->im;
+
+ tw1 = tw.re * sum.im + tw.im * diff.re;
+ tw2 = tw.im * sum.im - tw.re * diff.re;
+
+ p->re = sum.re - tw1;
+ p->im = diff.im - tw2;
+ q->re = sum.re + tw1;
+ q->im = -(diff.im + tw2);
+ }
+
+ p = (WDL_FFT_COMPLEX*)buf + permute[i];
+ p->re *= 2;
+ p->im *= -2;
+
+ if (isInverse) WDL_fft((WDL_FFT_COMPLEX*)buf, half, isInverse);
+}
+
+void WDL_real_fft(WDL_FFT_REAL* buf, int len, int isInverse)
+{
+ switch (len)
+ {
+ case 2: if (!isInverse) r2(buf); else v2(buf); break;
+ case 4: case 8: two_for_one(buf, 0, len, isInverse); break;
+#define TMP(x) case x: two_for_one(buf, d##x, len, isInverse); break;
+ TMP(16)
+ TMP(32)
+ TMP(64)
+ TMP(128)
+ TMP(256)
+ TMP(512)
+ TMP(1024)
+ TMP(2048)
+ TMP(4096)
+ TMP(8192)
+ TMP(16384)
+ TMP(32768)
+#undef TMP
+ }
+} \ No newline at end of file
diff --git a/src/devices/sound/fft.h b/src/devices/sound/fft.h
new file mode 100644
index 00000000000..a278b50a8c8
--- /dev/null
+++ b/src/devices/sound/fft.h
@@ -0,0 +1,82 @@
+// license:BSD-3-Clause
+// copyright-holders:Justin Frankel
+/*
+ WDL - fft.h
+ Copyright (C) 2006 and later Cockos Incorporated
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+
+
+ This file defines the interface to the WDL FFT library. These routines are based on the
+ DJBFFT library, which are Copyright 1999 D. J. Bernstein, djb@pobox.com
+
+ The DJB FFT web page is: http://cr.yp.to/djbfft.html
+
+
+ This file is modified from the original; it has been reformatted for 4-space
+ tabs.
+*/
+
+#ifndef WDL_FFT_H
+#define WDL_FFT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef WDL_FFT_REALSIZE
+#define WDL_FFT_REALSIZE 8
+#endif
+
+#if WDL_FFT_REALSIZE == 4
+typedef float WDL_FFT_REAL;
+#elif WDL_FFT_REALSIZE == 8
+typedef double WDL_FFT_REAL;
+#else
+#error invalid FFT item size
+#endif
+
+typedef struct {
+ WDL_FFT_REAL re;
+ WDL_FFT_REAL im;
+} WDL_FFT_COMPLEX;
+
+extern void WDL_fft_init();
+
+extern void WDL_fft_complexmul(WDL_FFT_COMPLEX *dest, WDL_FFT_COMPLEX *src, int len);
+extern void WDL_fft_complexmul2(WDL_FFT_COMPLEX *dest, WDL_FFT_COMPLEX *src, WDL_FFT_COMPLEX *src2, int len);
+extern void WDL_fft_complexmul3(WDL_FFT_COMPLEX *destAdd, WDL_FFT_COMPLEX *src, WDL_FFT_COMPLEX *src2, int len);
+
+/* Expects WDL_FFT_COMPLEX input[0..len-1] scaled by 1.0/len, returns
+WDL_FFT_COMPLEX output[0..len-1] order by WDL_fft_permute(len). */
+extern void WDL_fft(WDL_FFT_COMPLEX *, int len, int isInverse);
+
+/* Expects WDL_FFT_REAL input[0..len-1] scaled by 0.5/len, returns
+WDL_FFT_COMPLEX output[0..len/2-1], for len >= 4 order by
+WDL_fft_permute(len/2). Note that output[len/2].re is stored in
+output[0].im. */
+extern void WDL_real_fft(WDL_FFT_REAL *, int len, int isInverse);
+
+extern int WDL_fft_permute(int fftsize, int idx);
+extern int *WDL_fft_permute_tab(int fftsize);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/devices/sound/vgm_visualizer.cpp b/src/devices/sound/vgm_visualizer.cpp
new file mode 100644
index 00000000000..f555c4523fe
--- /dev/null
+++ b/src/devices/sound/vgm_visualizer.cpp
@@ -0,0 +1,483 @@
+// license:BSD-3-Clause
+// copyright-holders:Ryan Holtz
+/***************************************************************************
+
+ vgm_visualizer.cpp
+
+ Virtual VGM visualizer device.
+
+ Provides a waterfall view, spectrograph view, and VU view.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sound/vgm_visualizer.h"
+#include "fft.h"
+
+#include <cmath>
+
+
+static float lerp(float a, float b, float f)
+{
+ return (b - a) * f + a;
+}
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// device type definition
+DEFINE_DEVICE_TYPE(VGMVIZ, vgmviz_device, "vgmviz", "VGM Visualizer")
+
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// vgmviz_device - constructor
+//-------------------------------------------------
+
+vgmviz_device::vgmviz_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, VGMVIZ, tag, owner, clock)
+ , device_mixer_interface(mconfig, *this, 2)
+ , m_screen(*this, "screen")
+ , m_palette(*this, "palette")
+{
+}
+
+
+//-------------------------------------------------
+// ~vgmviz_device - destructor
+//-------------------------------------------------
+
+vgmviz_device::~vgmviz_device()
+{
+}
+
+
+//-------------------------------------------------
+// device_start - handle device startup
+//-------------------------------------------------
+
+void vgmviz_device::device_start()
+{
+ WDL_fft_init();
+ fill_window();
+}
+
+
+//-------------------------------------------------
+// fill_window - fill in the windowing data
+//-------------------------------------------------
+
+void vgmviz_device::fill_window()
+{
+ double window_pos_delta = (3.14159265358979 * 2) / FFT_LENGTH;
+ double power = 0;
+ for (int i = 0; i < (FFT_LENGTH / 2) + 1; i++)
+ {
+ double window_pos = i * window_pos_delta;
+ m_window[i] = 0.53836 - cos(window_pos) * 0.46164;
+ power += m_window[i];
+ }
+ power = 0.5 / (power * 2.0 - m_window[FFT_LENGTH / 2]);
+ for (int i = 0; i < (FFT_LENGTH / 2) + 1; i++)
+ {
+ m_window[i] *= power;
+ }
+}
+
+
+//-------------------------------------------------
+// fill_window - apply windowing data to the
+// mixed signal
+//-------------------------------------------------
+
+void vgmviz_device::apply_window(uint32_t buf_index)
+{
+ double *audio_l = m_audio_buf[buf_index][0];
+ double *audio_r = m_audio_buf[buf_index][1];
+ double *buf_l = m_fft_buf[0];
+ double *buf_r = m_fft_buf[1];
+ double *window = m_window;
+ for (int i = 0; i < (FFT_LENGTH / 2) + 1; i++)
+ {
+ *buf_l++ = *audio_l++ * *window;
+ *buf_r++ = *audio_r++ * *window;
+ window++;
+ }
+ for (int i = 0; i < (FFT_LENGTH / 2) - 1; i++)
+ {
+ window--;
+ *buf_l++ = *audio_l++ * *window;
+ *buf_r++ = *audio_r++ * *window;
+ }
+}
+
+
+//-------------------------------------------------
+// apply_fft - run the FFT on the windowed data
+//-------------------------------------------------
+
+void vgmviz_device::apply_fft()
+{
+ WDL_real_fft((WDL_FFT_REAL*)m_fft_buf[0], FFT_LENGTH, 0);
+ WDL_real_fft((WDL_FFT_REAL*)m_fft_buf[1], FFT_LENGTH, 0);
+
+ for (int i = 1; i < FFT_LENGTH/2; i++)
+ {
+ for (int chan = 0; chan < 2; chan++)
+ {
+ WDL_FFT_COMPLEX* cmpl = (WDL_FFT_COMPLEX*)m_fft_buf[chan] + i;
+ cmpl->re = sqrt(cmpl->re * cmpl->re + cmpl->im * cmpl->im);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// apply_waterfall - calculate the waterfall-view
+// data
+//-------------------------------------------------
+
+void vgmviz_device::apply_waterfall()
+{
+ int total_bars = FFT_LENGTH / 2;
+ int bar_step = total_bars / 256;
+ WDL_FFT_COMPLEX* bins[2] = { (WDL_FFT_COMPLEX*)m_fft_buf[0], (WDL_FFT_COMPLEX*)m_fft_buf[1] };
+ int bar_index = 0;
+ for (int bar = 0; bar < 256; bar++, bar_index += bar_step)
+ {
+ if (bar_index < 2)
+ {
+ continue;
+ }
+ double val = 0.0;
+ for (int i = 0; i < bar_step; i++)
+ {
+ int permuted = WDL_fft_permute(FFT_LENGTH / 2, (bar * bar_step) + i);
+ val = std::max<WDL_FFT_REAL>(bins[0][permuted].re + bins[1][permuted].re, val);
+ }
+ int level = (int)(log(val * 32768.0) * 31.0);
+ m_waterfall_buf[m_waterfall_length % (FFT_LENGTH / 2 + 16)][255 - bar] = (level < 0) ? 0 : (level > 255 ? 255 : level);
+ }
+ m_waterfall_length++;
+}
+
+
+//-------------------------------------------------
+// find_levels - find average and peak levels
+//-------------------------------------------------
+
+void vgmviz_device::find_levels()
+{
+ if (m_audio_frames_available < 2 || m_current_rate == 0)
+ {
+ m_curr_levels[0] = 0.0;
+ m_curr_levels[1] = 0.0;
+ m_curr_peaks[0] = 0.0;
+ m_curr_peaks[1] = 0.0;
+ return;
+ }
+
+ m_curr_levels[0] = 0.0;
+ m_curr_levels[1] = 0.0;
+
+ int read_index = m_audio_fill_index;
+ const int samples_needed = m_current_rate / 60;
+ int samples_remaining = samples_needed;
+ int samples_found = 0;
+ do
+ {
+ for (int i = std::min<int>(FFT_LENGTH - 1, m_audio_count[read_index]); i >= 0 && samples_remaining > 0; i--, samples_remaining--)
+ {
+ for (int chan = 0; chan < 2; chan++)
+ {
+ if (m_audio_buf[read_index][chan][i] > m_curr_levels[chan])
+ {
+ m_curr_levels[chan] += m_audio_buf[read_index][chan][i];
+ }
+ }
+ samples_found++;
+ samples_remaining--;
+ }
+ read_index = 1 - m_audio_fill_index;
+ } while (samples_remaining > 0 && read_index != m_audio_fill_index);
+
+ if (samples_found > 0)
+ {
+ for (int chan = 0; chan < 2; chan++)
+ {
+ if (m_curr_levels[chan] > m_curr_peaks[chan])
+ {
+ m_curr_peaks[chan] = m_curr_levels[chan];
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// device_reset - handle device reset
+//-------------------------------------------------
+
+void vgmviz_device::device_reset()
+{
+ for (int i = 0; i < 2; i++)
+ {
+ memset(m_audio_buf[i][0], 0, sizeof(double) * FFT_LENGTH);
+ memset(m_audio_buf[i][1], 0, sizeof(double) * FFT_LENGTH);
+ m_audio_count[i] = 0;
+ }
+ memset(m_fft_buf[0], 0, sizeof(double) * FFT_LENGTH);
+ memset(m_fft_buf[1], 0, sizeof(double) * FFT_LENGTH);
+ m_current_rate = 0;
+ m_audio_fill_index = 0;
+ m_audio_frames_available = 0;
+ memset(m_curr_levels, 0, sizeof(double) * 2);
+ memset(m_curr_peaks, 0, sizeof(double) * 2);
+
+ m_waterfall_length = 0;
+ for (int i = 0; i < 1024; i++)
+ {
+ memset(m_waterfall_buf[i], 0, sizeof(int) * 256);
+ }
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - update the outgoing
+// audio stream and process as necessary
+//-------------------------------------------------
+
+void vgmviz_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ // clear output buffers
+ for (int output = 0; output < m_outputs; output++)
+ std::fill_n(outputs[output], samples, 0);
+
+ m_current_rate = stream.sample_rate();
+
+ // loop over samples
+ const u8 *outmap = &m_outputmap[0];
+
+ // for each input, add it to the appropriate output
+ for (int pos = 0; pos < samples; pos++)
+ {
+ for (int inp = 0; inp < m_auto_allocated_inputs; inp++)
+ {
+ outputs[outmap[inp]][pos] += inputs[inp][pos];
+ }
+
+ for (int i = 0; i < m_outputs; i++)
+ {
+ m_audio_buf[m_audio_fill_index][i][m_audio_count[m_audio_fill_index]] = (outputs[i][pos] + 32768.0) / 65336.0;
+ }
+
+ m_audio_count[m_audio_fill_index]++;
+ if (m_audio_count[m_audio_fill_index] >= FFT_LENGTH)
+ {
+ apply_window(m_audio_fill_index);
+ apply_fft();
+ apply_waterfall();
+
+ m_audio_fill_index = 1 - m_audio_fill_index;
+ if (m_audio_frames_available < 2)
+ {
+ m_audio_frames_available++;
+ }
+ m_audio_count[m_audio_fill_index] = 0;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// init_palette - initialize the palette
+//-------------------------------------------------
+
+void vgmviz_device::init_palette(palette_device &palette) const
+{
+ for (int i = 0; i < 256; i++)
+ {
+ float percent = (float)i / 255.0f;
+ if (percent < 0.75f)
+ {
+ float r = lerp(0.0f, 1.0f, percent / 0.75f);
+ float g = 1.0f;
+ float b = 0.0f;
+ palette.set_pen_color(i, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255)));
+ }
+ else
+ {
+ float r = lerp(1.0f, 1.0f, (percent - 0.75f) / 0.25f);
+ float g = lerp(1.0f, 0.0f, (percent - 0.75f) / 0.25f);
+ float b = 0.0f;
+ palette.set_pen_color(i, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255)));
+ }
+ }
+
+ for (int i = 0; i < FFT_LENGTH / 2; i++)
+ {
+ double h = ((double)i / (FFT_LENGTH / 2)) * 360.0;
+ double s = 1.0;
+ double v = 1.0;
+
+ double c = s * v;
+ double x = c * (1 - abs(fmod(h / 60.0, 2.0) - 1.0));
+ double m = v - c;
+ double rs = 0.0;
+ double gs = 0.0;
+ double bs = 0.0;
+
+ if (h >= 0.0 && h < 60.0)
+ {
+ rs = c;
+ gs = x;
+ bs = 0.0;
+ }
+ else if (h >= 60.0 && h < 120.0)
+ {
+ rs = x;
+ gs = c;
+ bs = 0.0;
+ }
+ else if (h >= 120.0 && h < 180.0)
+ {
+ rs = 0.0;
+ gs = c;
+ bs = x;
+ }
+ else if (h >= 180.0 && h < 240.0)
+ {
+ rs = 0.0;
+ gs = x;
+ bs = c;
+ }
+ else if (h >= 240.0 && h < 300.0)
+ {
+ rs = x;
+ gs = 0.0;
+ bs = c;
+ }
+ else if (h < 360.0)
+ {
+ rs = c;
+ gs = 0.0;
+ bs = x;
+ }
+
+ palette.set_pen_color(i + 256, rgb_t((uint8_t)((rs + m) * 255), (uint8_t)((gs + m) * 255), (uint8_t)((bs + m) * 255)));
+ }
+
+ for (int y = 0; y < 256; y++)
+ {
+ float percent = (float)y / 255.0f;
+ if (percent < 0.75f)
+ {
+ float r = 0.0f;
+ float g = 0.0f;
+ float b = lerp(0.0f, 1.0f, percent / 0.5f);
+ palette.set_pen_color(y + 256 + FFT_LENGTH / 2, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255)));
+ }
+ else
+ {
+ float r = lerp(0.0f, 1.0f, (percent - 0.5f) / 0.5f);
+ float g = lerp(0.0f, 1.0f, (percent - 0.5f) / 0.5f);
+ float b = 1.0f;
+ palette.set_pen_color(y + 256 + FFT_LENGTH / 2, rgb_t((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255)));
+ }
+ }
+
+ palette.set_pen_color(512 + FFT_LENGTH / 2, rgb_t(0, 0, 0));
+}
+
+
+//-------------------------------------------------
+// device_add_mconfig - handle device setup
+//-------------------------------------------------
+
+void vgmviz_device::device_add_mconfig(machine_config &config)
+{
+ SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
+ m_screen->set_refresh_hz(60);
+ m_screen->set_vblank_time(ATTOSECONDS_IN_USEC(2500));
+ m_screen->set_size(FFT_LENGTH / 2 + 16, 768);
+ m_screen->set_visarea(0, FFT_LENGTH / 2 + 15, 0, 767);
+ m_screen->set_screen_update(FUNC(vgmviz_device::screen_update));
+
+ PALETTE(config, m_palette, FUNC(vgmviz_device::init_palette), 512 + FFT_LENGTH / 2 + 1);
+}
+
+
+//-------------------------------------------------
+// screen_update - update vu meters
+//-------------------------------------------------
+
+uint32_t vgmviz_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
+{
+ find_levels();
+
+ const pen_t *pal = m_palette->pens();
+ int chan_x = 0;
+ const int black_idx = (512 + FFT_LENGTH / 2);
+ for (int chan = 0; chan < 2; chan++)
+ {
+ int level = (int)(m_curr_levels[chan] * 255.0);
+ int peak = (int)(m_curr_peaks[chan] * 255.0);
+ for (int y = 0; y < 512; y++)
+ {
+ int bar_y = 255 - (y >> 1);
+ for (int x = 0; x < 7; x++)
+ {
+ uint32_t *line = &bitmap.pix32(y + 256);
+ bool lit = bar_y <= level || bar_y == peak;
+ line[chan_x + x] = pal[lit ? bar_y : black_idx];
+ }
+ }
+ chan_x += 8;
+ m_curr_peaks[chan] *= 0.99;
+ }
+
+ int total_bars = FFT_LENGTH / 2;
+ WDL_FFT_COMPLEX *bins[2] = { (WDL_FFT_COMPLEX *)m_fft_buf[0], (WDL_FFT_COMPLEX *)m_fft_buf[1] };
+ for (int bar = 0; bar < total_bars; bar++)
+ {
+ if (bar < 2)
+ {
+ continue;
+ }
+ int permuted = WDL_fft_permute(FFT_LENGTH/2, bar);
+ double val = (bins[0][permuted].re + bins[1][permuted].re) * 0.5;
+ int level = (int)(log(val * 32768.0) * 63.0);
+ for (int y = 0; y < 512; y++)
+ {
+ int bar_y = 511 - y;
+ uint32_t *line = &bitmap.pix32(y + 256);
+ bool lit = bar_y <= level;
+ line[bar + 16] = pal[lit ? (256 + bar) : black_idx];
+ }
+ }
+
+ const int width = FFT_LENGTH / 2 + 16;
+ for (int y = 0; y < 256; y++)
+ {
+ uint32_t* line = &bitmap.pix32(y);
+ for (int x = 0; x < width; x++)
+ {
+ if (m_waterfall_length < width)
+ {
+ const int sample = m_waterfall_buf[x][y];
+ *line++ = pal[256 + FFT_LENGTH / 2 + sample];
+ }
+ else
+ {
+ const int sample = m_waterfall_buf[((m_waterfall_length - width) + x) % width][y];
+ *line++ = pal[256 + FFT_LENGTH / 2 + sample];
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/devices/sound/vgm_visualizer.h b/src/devices/sound/vgm_visualizer.h
new file mode 100644
index 00000000000..68c8ff23cd5
--- /dev/null
+++ b/src/devices/sound/vgm_visualizer.h
@@ -0,0 +1,89 @@
+// license:BSD-3-Clause
+// copyright-holders:Ryan Holtz
+/***************************************************************************
+
+ vgm_visualizer.h
+
+ Virtual VGM visualizer device.
+
+ Provides a waterfall view, spectrograph view, and VU view.
+
+***************************************************************************/
+
+#ifndef MAME_SOUND_VGMVIZ_H
+#define MAME_SOUND_VGMVIZ_H
+
+#pragma once
+
+#include "screen.h"
+#include "emupal.h"
+
+#include <vector>
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// device type definition
+DECLARE_DEVICE_TYPE(VGMVIZ, vgmviz_device)
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// ======================> vgmviz_device
+
+class vgmviz_device : public device_t, public device_mixer_interface
+{
+public:
+ // construction/destruction
+ vgmviz_device(const machine_config &mconfig, const char *tag, device_t *owner)
+ : vgmviz_device(mconfig, tag, owner, 0)
+ {
+ }
+ vgmviz_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0);
+ virtual ~vgmviz_device();
+
+protected:
+ static constexpr int FFT_LENGTH = 1024;
+
+ // device-level overrides
+ virtual void device_add_mconfig(machine_config &config) override;
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+ // device_sound_interface-level overrides
+ void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
+
+ void init_palette(palette_device &palette) const;
+
+ uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
+
+ void fill_window();
+ void apply_window(uint32_t buf_index);
+ void apply_fft();
+ void apply_waterfall();
+ void find_levels();
+
+ required_device<screen_device> m_screen;
+ required_device<palette_device> m_palette;
+
+ double m_audio_buf[2][2][FFT_LENGTH];
+ double m_fft_buf[2][FFT_LENGTH];
+ int m_current_rate;
+ int m_audio_fill_index;
+ int m_audio_frames_available;
+ int m_audio_count[2];
+ bool m_audio_available;
+
+ int m_waterfall_length;
+ int m_waterfall_buf[1024][256];
+ double m_curr_levels[2];
+ double m_curr_peaks[2];
+ double m_window[FFT_LENGTH];
+ double m_power;
+};
+
+#endif // MAME_SOUND_VGMVIZ_H