/* synaescope.cpp * Copyright (C) 1999,2002 Richard Boulton * * Much code copied from Synaesthesia - a program to display sound * graphically, by Paul Francis Harrison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "synaescope.h" #ifndef G_OS_WIN32 #include #include #else #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #endif #include #include #include #include #include #include #include #include #include #define SCOPE_BG_RED 0 #define SCOPE_BG_GREEN 0 #define SCOPE_BG_BLUE 0 #define FFT_BUFFER_SIZE_LOG 9 #define FFT_BUFFER_SIZE (1 << FFT_BUFFER_SIZE_LOG) #define syn_width 320 #define syn_height 200 #define brightMin 200 #define brightMax 2000 #define brightDec 10 #define brightInc 6 #define brTotTargetLow 5000 #define brTotTargetHigh 15000 static int autobrightness = 1; /* Whether to use automatic brightness adjust */ static unsigned int brightFactor = 400; static unsigned char output[syn_width * syn_height * 2]; static guint32 display[syn_width * syn_height]; static gint16 pcmt_l[FFT_BUFFER_SIZE]; static gint16 pcmt_r[FFT_BUFFER_SIZE]; static gint16 pcm_l[FFT_BUFFER_SIZE]; static gint16 pcm_r[FFT_BUFFER_SIZE]; static double fftout_l[FFT_BUFFER_SIZE]; static double fftout_r[FFT_BUFFER_SIZE]; static double fftmult[FFT_BUFFER_SIZE / 2 + 1]; static double corr_l[FFT_BUFFER_SIZE]; static double corr_r[FFT_BUFFER_SIZE]; static int clarity[FFT_BUFFER_SIZE]; /* Surround sound */ static double cosTable[FFT_BUFFER_SIZE]; static double negSinTable[FFT_BUFFER_SIZE]; static int bitReverse[FFT_BUFFER_SIZE]; static int scaleDown[256]; static void synaes_fft (double *x, double *y); static void synaescope_coreGo (void); #define SYNAESCOPE_DOLOOP() \ while (running) { \ gint bar; \ guint val; \ gint val2; \ unsigned char *outptr = output; \ int w; \ \ synaescope_coreGo(); \ \ outptr = output; \ for (w=0; w < syn_width * syn_height; w++) { \ bits[w] = colEq[(outptr[0] >> 4) + (outptr[1] & 0xf0)]; \ outptr += 2; \ } \ \ GDK_THREADS_ENTER(); \ gdk_draw_image(win,gc,image,0,0,0,0,-1,-1); \ gdk_flush(); \ GDK_THREADS_LEAVE(); \ dosleep(SCOPE_SLEEP); \ } static inline void addPixel (unsigned char *output, int x, int y, int br1, int br2) { unsigned char *p; if (x < 0 || x >= syn_width || y < 0 || y >= syn_height) return; p = output + x * 2 + y * syn_width * 2; if (p[0] < 255 - br1) p[0] += br1; else p[0] = 255; if (p[1] < 255 - br2) p[1] += br2; else p[1] = 255; } static inline void addPixelFast (unsigned char *p, int br1, int br2) { if (p[0] < 255 - br1) p[0] += br1; else p[0] = 255; if (p[1] < 255 - br2) p[1] += br2; else p[1] = 255; } static void synaescope_coreGo (void) { int i, j; register unsigned long *ptr; register unsigned long *end; int heightFactor; int actualHeight; int heightAdd; double brightFactor2; long int brtot; memcpy (pcm_l, pcmt_l, sizeof (pcm_l)); memcpy (pcm_r, pcmt_r, sizeof (pcm_r)); for (i = 0; i < FFT_BUFFER_SIZE; i++) { fftout_l[i] = pcm_l[i]; fftout_r[i] = pcm_r[i]; } synaes_fft (fftout_l, fftout_r); for (i = 0 + 1; i < FFT_BUFFER_SIZE; i++) { double x1 = fftout_l[bitReverse[i]]; double y1 = fftout_r[bitReverse[i]]; double x2 = fftout_l[bitReverse[FFT_BUFFER_SIZE - i]]; double y2 = fftout_r[bitReverse[FFT_BUFFER_SIZE - i]]; double aa, bb; corr_l[i] = sqrt (aa = (x1 + x2) * (x1 + x2) + (y1 - y2) * (y1 - y2)); corr_r[i] = sqrt (bb = (x1 - x2) * (x1 - x2) + (y1 + y2) * (y1 + y2)); clarity[i] = (int) ( ((x1 + x2) * (x1 - x2) + (y1 + y2) * (y1 - y2)) / (aa + bb) * 256); } /* Asger Alstrupt's optimized 32 bit fade */ /* (alstrup@diku.dk) */ ptr = (unsigned long *) output; end = (unsigned long *) (output + syn_width * syn_height * 2); do { /*Bytewize version was: *(ptr++) -= *ptr+(*ptr>>1)>>4; */ if (*ptr) { if (*ptr & 0xf0f0f0f0) { *ptr = *ptr - ((*ptr & 0xf0f0f0f0) >> 4) - ((*ptr & 0xe0e0e0e0) >> 5); } else { *ptr = (*ptr * 14 >> 4) & 0x0f0f0f0f; /*Should be 29/32 to be consistent. Who cares. This is totally */ /* hacked anyway. */ /*unsigned char *subptr = (unsigned char*)(ptr++); */ /*subptr[0] = (int)subptr[0] * 29 / 32; */ /*subptr[1] = (int)subptr[0] * 29 / 32; */ /*subptr[2] = (int)subptr[0] * 29 / 32; */ /*subptr[3] = (int)subptr[0] * 29 / 32; */ } } ptr++; } while (ptr < end); heightFactor = FFT_BUFFER_SIZE / 2 / syn_height + 1; actualHeight = FFT_BUFFER_SIZE / 2 / heightFactor; heightAdd = (syn_height + actualHeight) >> 1; /* Correct for window size */ brightFactor2 = (brightFactor / 65536.0 / FFT_BUFFER_SIZE) * sqrt (actualHeight * syn_width / (320.0 * 200.0)); brtot = 0; for (i = 1; i < FFT_BUFFER_SIZE / 2; i++) { /*int h = (int)( corr_r[i]*280 / (corr_l[i]+corr_r[i]+0.0001)+20 ); */ if (corr_l[i] > 0 || corr_r[i] > 0) { int h = (int) (corr_r[i] * syn_width / (corr_l[i] + corr_r[i])); /* int h = (int)( syn_width - 1 ); */ int br1, br2, br = (int) ((corr_l[i] + corr_r[i]) * i * brightFactor2); int px = h, py = heightAdd - i / heightFactor; brtot += br; br1 = br * (clarity[i] + 128) >> 8; br2 = br * (128 - clarity[i]) >> 8; if (br1 < 0) br1 = 0; else if (br1 > 255) br1 = 255; if (br2 < 0) br2 = 0; else if (br2 > 255) br2 = 255; /*unsigned char *p = output+ h*2+(164-((i<<8)>>FFT_BUFFER_SIZE_LOG))*(syn_width*2); */ if (px < 30 || py < 30 || px > syn_width - 30 || py > syn_height - 30) { addPixel (output, px, py, br1, br2); for (j = 1; br1 > 0 || br2 > 0; j++, br1 = scaleDown[br1], br2 = scaleDown[br2]) { addPixel (output, px + j, py, br1, br2); addPixel (output, px, py + j, br1, br2); addPixel (output, px - j, py, br1, br2); addPixel (output, px, py - j, br1, br2); } } else { unsigned char *p = output + px * 2 + py * syn_width * 2, *p1 = p, *p2 = p, *p3 = p, *p4 = p; addPixelFast (p, br1, br2); for (; br1 > 0 || br2 > 0; br1 = scaleDown[br1], br2 = scaleDown[br2]) { p1 += 2; addPixelFast (p1, br1, br2); p2 -= 2; addPixelFast (p2, br1, br2); p3 += syn_width * 2; addPixelFast (p3, br1, br2); p4 -= syn_width * 2; addPixelFast (p4, br1, br2); } } } } /* Apply autoscaling: makes quiet bits brighter, and loud bits * darker, but still keeps loud bits brighter than quiet bits. */ if (brtot != 0 && autobrightness) { long int brTotTarget = brTotTargetHigh; if (brightMax != brightMin) { brTotTarget -= ((brTotTargetHigh - brTotTargetLow) * (brightFactor - brightMin)) / (brightMax - brightMin); } if (brtot < brTotTarget) { brightFactor += brightInc; if (brightFactor > brightMax) brightFactor = brightMax; } else { brightFactor -= brightDec; if (brightFactor < brightMin) brightFactor = brightMin; } /* printf("brtot: %ld\tbrightFactor: %d\tbrTotTarget: %d\n", brtot, brightFactor, brTotTarget); */ } } #define BOUND(x) ((x) > 255 ? 255 : (x)) #define PEAKIFY(x) BOUND((x) - (x)*(255-(x))/255/2) static void synaescope32 () { unsigned char *outptr; guint32 colEq[256]; int i; guint32 bg_color; for (i = 0; i < 256; i++) { int red = PEAKIFY ((i & 15 * 16)); int green = PEAKIFY ((i & 15) * 16 + (i & 15 * 16) / 4); int blue = PEAKIFY ((i & 15) * 16); colEq[i] = (red << 16) + (green << 8) + blue; } bg_color = (SCOPE_BG_RED << 16) + (SCOPE_BG_GREEN << 8) + SCOPE_BG_BLUE; synaescope_coreGo (); outptr = output; for (i = 0; i < syn_width * syn_height; i++) { display[i] = colEq[(outptr[0] >> 4) + (outptr[1] & 0xf0)]; outptr += 2; } } #if 0 static void synaescope16 (void *data) { guint16 *bits; guint16 colEq[256]; int i; GdkWindow *win; GdkColormap *c; GdkVisual *v; GdkGC *gc; GdkColor bg_color; win = (GdkWindow *) data; GDK_THREADS_ENTER (); c = gdk_colormap_get_system (); gc = gdk_gc_new (win); v = gdk_window_get_visual (win); for (i = 0; i < 256; i++) { GdkColor color; color.red = PEAKIFY ((i & 15 * 16)) << 8; color.green = PEAKIFY ((i & 15) * 16 + (i & 15 * 16) / 4) << 8; color.blue = PEAKIFY ((i & 15) * 16) << 8; gdk_color_alloc (c, &color); colEq[i] = color.pixel; } /* Create render image */ if (image) { gdk_image_destroy (image); image = NULL; } image = gdk_image_new (GDK_IMAGE_FASTEST, v, syn_width, syn_height); bg_color.red = SCOPE_BG_RED << 8; bg_color.green = SCOPE_BG_GREEN << 8; bg_color.blue = SCOPE_BG_BLUE << 8; gdk_color_alloc (c, &bg_color); GDK_THREADS_LEAVE (); assert (image); assert (image->bpp == 2); bits = (guint16 *) image->mem; running = 1; SYNAESCOPE_DOLOOP (); } static void synaescope8 (void *data) { unsigned char *outptr; guint8 *bits; guint8 colEq[256]; int i; GdkWindow *win; GdkColormap *c; GdkVisual *v; GdkGC *gc; GdkColor bg_color; win = (GdkWindow *) data; GDK_THREADS_ENTER (); c = gdk_colormap_get_system (); gc = gdk_gc_new (win); v = gdk_window_get_visual (win); for (i = 0; i < 64; i++) { GdkColor color; color.red = PEAKIFY ((i & 7 * 8) * 4) << 8; color.green = PEAKIFY ((i & 7) * 32 + (i & 7 * 8) * 2) << 8; color.blue = PEAKIFY ((i & 7) * 32) << 8; gdk_color_alloc (c, &color); colEq[i * 4] = color.pixel; colEq[i * 4 + 1] = color.pixel; colEq[i * 4 + 2] = color.pixel; colEq[i * 4 + 3] = color.pixel; } /* Create render image */ if (image) { gdk_image_destroy (image); image = NULL; } image = gdk_image_new (GDK_IMAGE_FASTEST, v, syn_width, syn_height); bg_color.red = SCOPE_BG_RED << 8; bg_color.green = SCOPE_BG_GREEN << 8; bg_color.blue = SCOPE_BG_BLUE << 8; gdk_color_alloc (c, &bg_color); GDK_THREADS_LEAVE (); assert (image); assert (image->bpp == 1); bits = (guint8 *) image->mem; running = 1; SYNAESCOPE_DOLOOP (); } #endif #if 0 static void run_synaescope (void *data) { switch (depth) { case 8: synaescope8 (win); break; case 16: synaescope16 (win); break; case 24: case 32: synaescope32 (win); break; } } static void start_synaescope (void *data) { init_synaescope_window (); } #endif static int bitReverser (int i) { int sum = 0; int j; for (j = 0; j < FFT_BUFFER_SIZE_LOG; j++) { sum = (i & 1) + sum * 2; i >>= 1; } return sum; } static void init_synaescope () { int i; for (i = 0; i <= FFT_BUFFER_SIZE / 2 + 1; i++) { double mult = (double) 128 / ((FFT_BUFFER_SIZE * 16384) ^ 2); /* Result now guaranteed (well, almost) to be in range 0..128 */ /* Low values represent more frequencies, and thus get more */ /* intensity - this helps correct for that. */ mult *= log (i + 1) / log (2); mult *= 3; /* Adhoc parameter, looks about right for me. */ fftmult[i] = mult; } for (i = 0; i < FFT_BUFFER_SIZE; i++) { negSinTable[i] = -sin (M_PI * 2 / FFT_BUFFER_SIZE * i); cosTable[i] = cos (M_PI * 2 / FFT_BUFFER_SIZE * i); bitReverse[i] = bitReverser (i); } for (i = 0; i < 256; i++) scaleDown[i] = i * 200 >> 8; memset (output, 0, syn_width * syn_height * 2); } static void synaes_fft (double *x, double *y) { int n2 = FFT_BUFFER_SIZE; int n1; int twoToTheK; int j; for (twoToTheK = 1; twoToTheK < FFT_BUFFER_SIZE; twoToTheK *= 2) { n1 = n2; n2 /= 2; for (j = 0; j < n2; j++) { double c = cosTable[j * twoToTheK & (FFT_BUFFER_SIZE - 1)]; double s = negSinTable[j * twoToTheK & (FFT_BUFFER_SIZE - 1)]; int i; for (i = j; i < FFT_BUFFER_SIZE; i += n1) { int l = i + n2; double xt = x[i] - x[l]; double yt = y[i] - y[l]; x[i] = (x[i] + x[l]); y[i] = (y[i] + y[l]); x[l] = xt * c - yt * s; y[l] = xt * s + yt * c; } } } } static void synaescope_set_data (gint16 data[2][512]) { int i; gint16 *newset_l = pcmt_l; gint16 *newset_r = pcmt_r; for (i = 0; i < FFT_BUFFER_SIZE; i++) { newset_l[i] = data[0][i]; newset_r[i] = data[1][i]; } } void synaesthesia_init (guint32 resx, guint32 resy) { init_synaescope (); } guint32 * synaesthesia_update (gint16 data[2][512]) { synaescope_set_data (data); synaescope32 (); return display; } void synaesthesia_close () { }