diff --git a/scripts/src/sound.lua b/scripts/src/sound.lua index 88b26e71285..d1028cceeb6 100644 --- a/scripts/src/sound.lua +++ b/scripts/src/sound.lua @@ -1541,3 +1541,17 @@ if (SOUNDS["SWP30"]~=null) then MAME_DIR .. "src/devices/sound/swp30.h", } end + +--------------------------------------------------- +-- +--@src/devices/sound/vgm_visualizer.h,SOUNDS["VGMVIZ"] = true +--------------------------------------------------- + +if (SOUNDS["VGMVIZ"]~=null) then + files { + MAME_DIR .. "src/devices/sound/vgm_visualizer.cpp", + MAME_DIR .. "src/devices/sound/vgm_visualizer.h", + MAME_DIR .. "src/devices/sound/fft.cpp", + MAME_DIR .. "src/devices/sound/fft.h", + } +end 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 +#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<> 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 + + +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(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(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 + +//************************************************************************** +// 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 m_screen; + required_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 diff --git a/src/mame/drivers/vgmplay.cpp b/src/mame/drivers/vgmplay.cpp index c4dce21a729..56bc13a7204 100644 --- a/src/mame/drivers/vgmplay.cpp +++ b/src/mame/drivers/vgmplay.cpp @@ -44,6 +44,7 @@ #include "sound/segapcm.h" #include "sound/sn76496.h" #include "sound/upd7759.h" +#include "sound/vgm_visualizer.h" #include "sound/x1_010.h" #include "sound/ym2151.h" #include "sound/ym2413.h" @@ -447,6 +448,7 @@ public: private: std::vector m_file_data; required_device m_vgmplay; + required_device m_mixer; required_device m_lspeaker; required_device m_rspeaker; required_device_array m_sn76489; @@ -2548,6 +2550,7 @@ READ8_MEMBER(vgmplay_device::ga20_rom_r) vgmplay_state::vgmplay_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_vgmplay(*this, "vgmplay") + , m_mixer(*this, "mixer") , m_lspeaker(*this, "lspeaker") , m_rspeaker(*this, "rspeaker") , m_sn76489(*this, "sn76489.%d", 0) @@ -3464,184 +3467,184 @@ void vgmplay_state::vgmplay(machine_config &config) config.set_default_layout(layout_vgmplay); SN76489(config, m_sn76489[0], 0); - m_sn76489[0]->add_route(0, "lspeaker", 0.5); - m_sn76489[0]->add_route(0, "rspeaker", 0.5); + m_sn76489[0]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_sn76489[0]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); SN76489(config, m_sn76489[1], 0); - m_sn76489[1]->add_route(0, "lspeaker", 0.5); - m_sn76489[1]->add_route(0, "rspeaker", 0.5); + m_sn76489[1]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_sn76489[1]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); YM2413(config, m_ym2413[0], 0); - m_ym2413[0]->add_route(ALL_OUTPUTS, "lspeaker", 1); - m_ym2413[0]->add_route(ALL_OUTPUTS, "rspeaker", 1); + m_ym2413[0]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ym2413[0]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 1); YM2413(config, m_ym2413[1], 0); - m_ym2413[1]->add_route(ALL_OUTPUTS, "lspeaker", 1); - m_ym2413[1]->add_route(ALL_OUTPUTS, "rspeaker", 1); + m_ym2413[1]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ym2413[1]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 1); YM2612(config, m_ym2612[0], 0); - m_ym2612[0]->add_route(0, "lspeaker", 1); - m_ym2612[0]->add_route(1, "rspeaker", 1); + m_ym2612[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ym2612[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); YM2612(config, m_ym2612[1], 0); - m_ym2612[1]->add_route(0, "lspeaker", 1); - m_ym2612[1]->add_route(1, "rspeaker", 1); + m_ym2612[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ym2612[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); YM2151(config, m_ym2151[0], 0); - m_ym2151[0]->add_route(0, "lspeaker", 1); - m_ym2151[0]->add_route(1, "rspeaker", 1); + m_ym2151[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ym2151[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); YM2151(config, m_ym2151[1], 0); - m_ym2151[1]->add_route(0, "lspeaker", 1); - m_ym2151[1]->add_route(1, "rspeaker", 1); + m_ym2151[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ym2151[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); SEGAPCM(config, m_segapcm[0], 0); m_segapcm[0]->set_addrmap(0, &vgmplay_state::segapcm_map<0>); - m_segapcm[0]->add_route(0, "lspeaker", 1); - m_segapcm[0]->add_route(1, "rspeaker", 1); + m_segapcm[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_segapcm[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); SEGAPCM(config, m_segapcm[1], 0); m_segapcm[1]->set_addrmap(0, &vgmplay_state::segapcm_map<1>); - m_segapcm[1]->add_route(0, "lspeaker", 1); - m_segapcm[1]->add_route(1, "rspeaker", 1); + m_segapcm[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_segapcm[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); RF5C68(config, m_rf5c68, 0); m_rf5c68->set_addrmap(0, &vgmplay_state::rf5c68_map<0>); - m_rf5c68->add_route(0, "lspeaker", 1); - m_rf5c68->add_route(1, "rspeaker", 1); + m_rf5c68->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_rf5c68->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); // TODO: prevent error.log spew YM2203(config, m_ym2203[0], 0); - m_ym2203[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.25); - m_ym2203[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.25); + m_ym2203[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ym2203[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); YM2203(config, m_ym2203[1], 0); - m_ym2203[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.25); - m_ym2203[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.25); + m_ym2203[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ym2203[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); // TODO: prevent error.log spew YM2608(config, m_ym2608[0], 0); m_ym2608[0]->set_addrmap(0, &vgmplay_state::ym2608_map<0>); - m_ym2608[0]->add_route(0, "lspeaker", 0.25); - m_ym2608[0]->add_route(0, "rspeaker", 0.25); - m_ym2608[0]->add_route(1, "lspeaker", 1.00); - m_ym2608[0]->add_route(2, "rspeaker", 1.00); + m_ym2608[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ym2608[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ym2608[0]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ym2608[0]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); YM2608(config, m_ym2608[1], 0); m_ym2608[1]->set_addrmap(0, &vgmplay_state::ym2608_map<1>); - m_ym2608[1]->add_route(0, "lspeaker", 0.25); - m_ym2608[1]->add_route(0, "rspeaker", 0.25); - m_ym2608[1]->add_route(1, "lspeaker", 1.00); - m_ym2608[1]->add_route(2, "rspeaker", 1.00); + m_ym2608[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ym2608[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ym2608[1]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ym2608[1]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); // TODO: prevent error.log spew YM2610(config, m_ym2610[0], 0); m_ym2610[0]->set_addrmap(0, &vgmplay_state::ym2610_adpcm_a_map<0>); m_ym2610[0]->set_addrmap(1, &vgmplay_state::ym2610_adpcm_b_map<0>); - m_ym2610[0]->add_route(0, "lspeaker", 0.25); - m_ym2610[0]->add_route(0, "rspeaker", 0.25); - m_ym2610[0]->add_route(1, "lspeaker", 0.50); - m_ym2610[0]->add_route(2, "rspeaker", 0.50); + m_ym2610[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ym2610[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ym2610[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_ym2610[0]->add_route(2, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); YM2610(config, m_ym2610[1], 0); m_ym2610[1]->set_addrmap(0, &vgmplay_state::ym2610_adpcm_a_map<1>); m_ym2610[1]->set_addrmap(1, &vgmplay_state::ym2610_adpcm_b_map<1>); - m_ym2610[1]->add_route(0, "lspeaker", 0.25); - m_ym2610[1]->add_route(0, "rspeaker", 0.25); - m_ym2610[1]->add_route(1, "lspeaker", 0.50); - m_ym2610[1]->add_route(2, "rspeaker", 0.50); + m_ym2610[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ym2610[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ym2610[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_ym2610[1]->add_route(2, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); YM3812(config, m_ym3812[0], 0); - m_ym3812[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_ym3812[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_ym3812[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_ym3812[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); YM3812(config, m_ym3812[1], 0); - m_ym3812[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_ym3812[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_ym3812[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_ym3812[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); YM3526(config, m_ym3526[0], 0); - m_ym3526[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_ym3526[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_ym3526[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_ym3526[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); YM3526(config, m_ym3526[1], 0); - m_ym3526[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_ym3526[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_ym3526[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_ym3526[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); Y8950(config, m_y8950[0], 0); m_y8950[0]->set_addrmap(0, &vgmplay_state::y8950_map<0>); - m_y8950[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.40); - m_y8950[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.40); + m_y8950[0]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 0); + m_y8950[0]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 1); Y8950(config, m_y8950[1], 0); m_y8950[1]->set_addrmap(0, &vgmplay_state::y8950_map<1>); - m_y8950[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.40); - m_y8950[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.40); + m_y8950[1]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 0); + m_y8950[1]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 1); YMF262(config, m_ymf262[0], 0); - m_ymf262[0]->add_route(0, "lspeaker", 1.00); - m_ymf262[0]->add_route(1, "rspeaker", 1.00); - m_ymf262[0]->add_route(2, "lspeaker", 1.00); - m_ymf262[0]->add_route(3, "rspeaker", 1.00); + m_ymf262[0]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf262[0]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf262[0]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf262[0]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); YMF262(config, m_ymf262[1], 0); - m_ymf262[1]->add_route(0, "lspeaker", 1.00); - m_ymf262[1]->add_route(1, "rspeaker", 1.00); - m_ymf262[1]->add_route(2, "lspeaker", 1.00); - m_ymf262[1]->add_route(3, "rspeaker", 1.00); + m_ymf262[1]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf262[1]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf262[1]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf262[1]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); // TODO: prevent error.log spew YMF278B(config, m_ymf278b[0], 0); m_ymf278b[0]->set_addrmap(0, &vgmplay_state::ymf278b_map<0>); - m_ymf278b[0]->add_route(0, "lspeaker", 1.00); - m_ymf278b[0]->add_route(1, "rspeaker", 1.00); - m_ymf278b[0]->add_route(2, "lspeaker", 1.00); - m_ymf278b[0]->add_route(3, "rspeaker", 1.00); - m_ymf278b[0]->add_route(4, "lspeaker", 1.00); - m_ymf278b[0]->add_route(5, "rspeaker", 1.00); + m_ymf278b[0]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf278b[0]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf278b[0]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf278b[0]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf278b[0]->add_route(4, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf278b[0]->add_route(5, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); YMF278B(config, m_ymf278b[1], 0); m_ymf278b[1]->set_addrmap(0, &vgmplay_state::ymf278b_map<1>); - m_ymf278b[1]->add_route(0, "lspeaker", 1.00); - m_ymf278b[1]->add_route(1, "rspeaker", 1.00); - m_ymf278b[1]->add_route(2, "lspeaker", 1.00); - m_ymf278b[1]->add_route(3, "rspeaker", 1.00); - m_ymf278b[1]->add_route(4, "lspeaker", 1.00); - m_ymf278b[1]->add_route(5, "rspeaker", 1.00); + m_ymf278b[1]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf278b[1]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf278b[1]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf278b[1]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf278b[1]->add_route(4, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_ymf278b[1]->add_route(5, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); YMF271(config, m_ymf271[0], 0); m_ymf271[0]->set_addrmap(0, &vgmplay_state::ymf271_map<0>); - m_ymf271[0]->add_route(0, "lspeaker", 0.25); - m_ymf271[0]->add_route(1, "rspeaker", 0.25); - m_ymf271[0]->add_route(2, "lspeaker", 0.25); - m_ymf271[0]->add_route(3, "rspeaker", 0.25); + m_ymf271[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ymf271[0]->add_route(1, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ymf271[0]->add_route(2, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ymf271[0]->add_route(3, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); YMF271(config, m_ymf271[1], 0); m_ymf271[1]->set_addrmap(0, &vgmplay_state::ymf271_map<0>); - m_ymf271[1]->add_route(0, "lspeaker", 0.25); - m_ymf271[1]->add_route(1, "rspeaker", 0.25); - m_ymf271[1]->add_route(2, "lspeaker", 0.25); - m_ymf271[1]->add_route(3, "rspeaker", 0.25); + m_ymf271[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ymf271[1]->add_route(1, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ymf271[1]->add_route(2, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ymf271[1]->add_route(3, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); // TODO: prevent error.log spew YMZ280B(config, m_ymz280b[0], 0); m_ymz280b[0]->set_addrmap(0, &vgmplay_state::ymz280b_map<0>); - m_ymz280b[0]->add_route(0, "lspeaker", 0.25); - m_ymz280b[0]->add_route(1, "rspeaker", 0.25); + m_ymz280b[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ymz280b[0]->add_route(1, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); YMZ280B(config, m_ymz280b[1], 0); m_ymz280b[1]->set_addrmap(0, &vgmplay_state::ymz280b_map<1>); - m_ymz280b[1]->add_route(0, "lspeaker", 0.25); - m_ymz280b[1]->add_route(1, "rspeaker", 0.25); + m_ymz280b[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_ymz280b[1]->add_route(1, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); RF5C164(config, m_rf5c164, 0); m_rf5c164->set_addrmap(0, &vgmplay_state::rf5c164_map<0>); - m_rf5c164->add_route(0, "lspeaker", 1); - m_rf5c164->add_route(1, "rspeaker", 1); + m_rf5c164->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_rf5c164->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); /// TODO: rewrite to generate audio without using DAC devices SEGA_32X_NTSC(config, m_sega32x, 0, "sega32x_maincpu", "sega32x_scanline_timer"); - m_sega32x->add_route(0, "lspeaker", 1.00); - m_sega32x->add_route(1, "rspeaker", 1.00); + m_sega32x->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); + m_sega32x->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); auto& sega32x_maincpu(M68000(config, "sega32x_maincpu", 0)); sega32x_maincpu.set_disable(); @@ -3653,228 +3656,232 @@ void vgmplay_state::vgmplay(machine_config &config) // TODO: prevent error.log spew AY8910(config, m_ay8910[0], 0); - m_ay8910[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.33); - m_ay8910[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.33); + m_ay8910[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); + m_ay8910[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); AY8910(config, m_ay8910[1], 0); - m_ay8910[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.33); - m_ay8910[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.33); + m_ay8910[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); + m_ay8910[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); DMG_APU(config, m_dmg[0], 0); - m_dmg[0]->add_route(0, "lspeaker", 1); - m_dmg[0]->add_route(0, "rspeaker", 1); + m_dmg[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_dmg[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 1); DMG_APU(config, m_dmg[1], 0); - m_dmg[1]->add_route(0, "lspeaker", 1); - m_dmg[1]->add_route(0, "rspeaker", 1); + m_dmg[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_dmg[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 1); N2A03(config, m_nescpu[0], 0); m_nescpu[0]->set_addrmap(AS_PROGRAM, &vgmplay_state::nescpu_map<0>); m_nescpu[0]->set_disable(); - m_nescpu[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.50); - m_nescpu[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.50); + m_nescpu[0]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_nescpu[0]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); N2A03(config, m_nescpu[1], 0); m_nescpu[1]->set_addrmap(AS_PROGRAM, &vgmplay_state::nescpu_map<1>); m_nescpu[1]->set_disable(); - m_nescpu[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.50); - m_nescpu[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.50); + m_nescpu[1]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_nescpu[1]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); MULTIPCM(config, m_multipcm[0], 0); m_multipcm[0]->set_addrmap(0, &vgmplay_state::multipcm_map<0>); - m_multipcm[0]->add_route(0, "lspeaker", 1); - m_multipcm[0]->add_route(1, "rspeaker", 1); + m_multipcm[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_multipcm[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); MULTIPCM(config, m_multipcm[1], 0); m_multipcm[1]->set_addrmap(0, &vgmplay_state::multipcm_map<1>); - m_multipcm[1]->add_route(0, "lspeaker", 1); - m_multipcm[1]->add_route(1, "rspeaker", 1); + m_multipcm[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_multipcm[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); UPD7759(config, m_upd7759[0], 0); m_upd7759[0]->drq().set(FUNC(vgmplay_state::upd7759_drq_w<0>)); m_upd7759[0]->set_addrmap(0, &vgmplay_state::upd7759_map<0>); - m_upd7759[0]->add_route(ALL_OUTPUTS, "lspeaker", 1.0); - m_upd7759[0]->add_route(ALL_OUTPUTS, "rspeaker", 1.0); + m_upd7759[0]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); + m_upd7759[0]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); UPD7759(config, m_upd7759[1], 0); m_upd7759[1]->drq().set(FUNC(vgmplay_state::upd7759_drq_w<1>)); m_upd7759[1]->set_addrmap(0, &vgmplay_state::upd7759_map<1>); - m_upd7759[1]->add_route(ALL_OUTPUTS, "lspeaker", 1.0); - m_upd7759[1]->add_route(ALL_OUTPUTS, "rspeaker", 1.0); + m_upd7759[1]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); + m_upd7759[1]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); OKIM6258(config, m_okim6258[0], 0); - m_okim6258[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_okim6258[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_okim6258[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_okim6258[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); OKIM6258(config, m_okim6258[1], 0); - m_okim6258[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_okim6258[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_okim6258[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_okim6258[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); OKIM6295(config, m_okim6295[0], 0, okim6295_device::PIN7_HIGH); m_okim6295[0]->set_addrmap(0, &vgmplay_state::okim6295_map<0>); - m_okim6295[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.25); - m_okim6295[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.25); + m_okim6295[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_okim6295[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); OKIM6295(config, m_okim6295[1], 0, okim6295_device::PIN7_HIGH); m_okim6295[1]->set_addrmap(0, &vgmplay_state::okim6295_map<1>); - m_okim6295[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.25); - m_okim6295[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.25); + m_okim6295[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); + m_okim6295[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); K051649(config, m_k051649[0], 0); - m_k051649[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.33); - m_k051649[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.33); + m_k051649[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); + m_k051649[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); K051649(config, m_k051649[1], 0); - m_k051649[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.33); - m_k051649[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.33); + m_k051649[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); + m_k051649[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); K054539(config, m_k054539[0], 0); m_k054539[0]->set_addrmap(0, &vgmplay_state::k054539_map<0>); - m_k054539[0]->add_route(0, "lspeaker", 1); - m_k054539[0]->add_route(1, "rspeaker", 1); + m_k054539[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_k054539[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); K054539(config, m_k054539[1], 0); m_k054539[1]->set_addrmap(0, &vgmplay_state::k054539_map<1>); - m_k054539[1]->add_route(0, "lspeaker", 1); - m_k054539[1]->add_route(1, "rspeaker", 1); + m_k054539[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_k054539[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); // TODO: prevent error.log spew H6280(config, m_huc6280[0], 0); m_huc6280[0]->set_disable(); - m_huc6280[0]->add_route(0, "lspeaker", 1); - m_huc6280[0]->add_route(1, "rspeaker", 1); + m_huc6280[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_huc6280[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); H6280(config, m_huc6280[1], 0); m_huc6280[1]->set_disable(); - m_huc6280[1]->add_route(0, "lspeaker", 1); - m_huc6280[1]->add_route(1, "rspeaker", 1); + m_huc6280[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_huc6280[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); C140(config, m_c140[0], 0); m_c140[0]->set_addrmap(0, &vgmplay_state::c140_map<0>); - m_c140[0]->add_route(0, "lspeaker", 0.50); - m_c140[0]->add_route(1, "rspeaker", 0.50); + m_c140[0]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_c140[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); C140(config, m_c140[1], 0); m_c140[1]->set_addrmap(0, &vgmplay_state::c140_map<1>); - m_c140[1]->add_route(0, "lspeaker", 0.50); - m_c140[1]->add_route(1, "rspeaker", 0.50); + m_c140[1]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_c140[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); K053260(config, m_k053260[0], 0); m_k053260[0]->set_addrmap(0, &vgmplay_state::k053260_map<0>); - m_k053260[0]->add_route(0, "lspeaker", 1); - m_k053260[0]->add_route(1, "rspeaker", 1); + m_k053260[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_k053260[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); K053260(config, m_k053260[1], 0); m_k053260[1]->set_addrmap(0, &vgmplay_state::k053260_map<1>); - m_k053260[1]->add_route(0, "lspeaker", 1); - m_k053260[1]->add_route(1, "rspeaker", 1); + m_k053260[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_k053260[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); POKEY(config, m_pokey[0], 0); - m_pokey[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_pokey[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_pokey[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_pokey[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); POKEY(config, m_pokey[1], 0); - m_pokey[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_pokey[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_pokey[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_pokey[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); QSOUND(config, m_qsound, 0); m_qsound->set_addrmap(0, &vgmplay_state::qsound_map<0>); - m_qsound->add_route(0, "lspeaker", 1); - m_qsound->add_route(1, "rspeaker", 1); + m_qsound->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_qsound->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); SCSP(config, m_scsp[0], 0); m_scsp[0]->set_addrmap(0, &vgmplay_state::scsp_map<0>); - m_scsp[0]->add_route(0, "lspeaker", 1); - m_scsp[0]->add_route(1, "rspeaker", 1); + m_scsp[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_scsp[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); SCSP(config, m_scsp[1], 0); m_scsp[1]->set_addrmap(0, &vgmplay_state::scsp_map<1>); - m_scsp[1]->add_route(0, "lspeaker", 1); - m_scsp[1]->add_route(1, "rspeaker", 1); + m_scsp[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_scsp[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); WSWAN_SND(config, m_wswan[0], 0); m_wswan[0]->set_addrmap(0, &vgmplay_state::wswan_map<0>); - m_wswan[0]->add_route(0, "lspeaker", 0.50); - m_wswan[0]->add_route(1, "rspeaker", 0.50); + m_wswan[0]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_wswan[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); WSWAN_SND(config, m_wswan[1], 0); m_wswan[1]->set_addrmap(0, &vgmplay_state::wswan_map<1>); - m_wswan[1]->add_route(0, "lspeaker", 0.50); - m_wswan[1]->add_route(1, "rspeaker", 0.50); + m_wswan[1]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); + m_wswan[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); VBOYSND(config, m_vsu_vue[0], 0); - m_vsu_vue[0]->add_route(0, "lspeaker", 1.0); - m_vsu_vue[0]->add_route(1, "rspeaker", 1.0); + m_vsu_vue[0]->add_route(0, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); + m_vsu_vue[0]->add_route(1, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); VBOYSND(config, m_vsu_vue[1], 0); - m_vsu_vue[1]->add_route(0, "lspeaker", 1.0); - m_vsu_vue[1]->add_route(1, "rspeaker", 1.0); + m_vsu_vue[1]->add_route(0, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); + m_vsu_vue[1]->add_route(1, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); SAA1099(config, m_saa1099[0], 0); - m_saa1099[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_saa1099[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_saa1099[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_saa1099[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); SAA1099(config, m_saa1099[1], 0); - m_saa1099[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_saa1099[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_saa1099[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_saa1099[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); ES5503(config, m_es5503[0], 0); m_es5503[0]->set_channels(2); m_es5503[0]->set_addrmap(0, &vgmplay_state::es5503_map<0>); - m_es5503[0]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_es5503[0]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_es5503[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_es5503[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); ES5503(config, m_es5503[1], 0); m_es5503[1]->set_channels(2); m_es5503[1]->set_addrmap(0, &vgmplay_state::es5503_map<1>); - m_es5503[1]->add_route(ALL_OUTPUTS, "lspeaker", 0.5); - m_es5503[1]->add_route(ALL_OUTPUTS, "rspeaker", 0.5); + m_es5503[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_es5503[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); ES5505(config, m_es5505[0], 0); // TODO m_es5505[0]->set_addrmap(0, &vgmplay_state::es5505_map<0>); m_es5505[0]->set_channels(1); - m_es5505[0]->add_route(0, "lspeaker", 0.5); - m_es5505[0]->add_route(1, "rspeaker", 0.5); + m_es5505[0]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_es5505[0]->add_route(1, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); ES5505(config, m_es5505[1], 0); // TODO m_es5505[1]->set_addrmap(0, &vgmplay_state::es5505_map<1>); m_es5505[1]->set_channels(1); - m_es5505[1]->add_route(0, "lspeaker", 0.5); - m_es5505[1]->add_route(1, "rspeaker", 0.5); + m_es5505[1]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); + m_es5505[1]->add_route(1, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); X1_010(config, m_x1_010[0], 0); m_x1_010[0]->set_addrmap(0, &vgmplay_state::x1_010_map<0>); - m_x1_010[0]->add_route(0, "lspeaker", 1); - m_x1_010[0]->add_route(1, "rspeaker", 1); + m_x1_010[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_x1_010[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); X1_010(config, m_x1_010[1], 0); m_x1_010[1]->set_addrmap(0, &vgmplay_state::x1_010_map<1>); - m_x1_010[1]->add_route(0, "lspeaker", 1); - m_x1_010[1]->add_route(1, "rspeaker", 1); + m_x1_010[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_x1_010[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); C352(config, m_c352[0], 0, 1); m_c352[0]->set_addrmap(0, &vgmplay_state::c352_map<0>); - m_c352[0]->add_route(0, "lspeaker", 1); - m_c352[0]->add_route(1, "rspeaker", 1); - m_c352[0]->add_route(2, "lspeaker", 1); - m_c352[0]->add_route(3, "rspeaker", 1); + m_c352[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_c352[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_c352[0]->add_route(2, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_c352[0]->add_route(3, m_mixer, 1, AUTO_ALLOC_INPUT, 1); C352(config, m_c352[1], 0, 1); m_c352[1]->set_addrmap(0, &vgmplay_state::c352_map<1>); - m_c352[1]->add_route(0, "lspeaker", 1); - m_c352[1]->add_route(1, "rspeaker", 1); - m_c352[1]->add_route(2, "lspeaker", 1); - m_c352[1]->add_route(3, "rspeaker", 1); + m_c352[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_c352[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_c352[1]->add_route(2, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_c352[1]->add_route(3, m_mixer, 1, AUTO_ALLOC_INPUT, 1); IREMGA20(config, m_ga20[0], 0); m_ga20[0]->set_addrmap(0, &vgmplay_state::ga20_map<0>); - m_ga20[0]->add_route(0, "lspeaker", 1); - m_ga20[0]->add_route(1, "rspeaker", 1); + m_ga20[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ga20[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); IREMGA20(config, m_ga20[1], 0); m_ga20[1]->set_addrmap(0, &vgmplay_state::ga20_map<1>); - m_ga20[1]->add_route(0, "lspeaker", 1); - m_ga20[1]->add_route(1, "rspeaker", 1); + m_ga20[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); + m_ga20[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + + VGMVIZ(config, m_mixer, 0); + m_mixer->add_route(0, "lspeaker", 1); + m_mixer->add_route(1, "rspeaker", 1); SPEAKER(config, m_lspeaker).front_left(); SPEAKER(config, m_rspeaker).front_right(); diff --git a/src/mame/layout/vgmplay.lay b/src/mame/layout/vgmplay.lay index 8148b12cb83..bb065cc7591 100644 --- a/src/mame/layout/vgmplay.lay +++ b/src/mame/layout/vgmplay.lay @@ -215,9 +215,7 @@ - - - + @@ -237,5 +235,14 @@ + + + + + + + + +