/* * * XMMS waterfall spectrum analyzer * waterfall.so * * Author: Seth Golub - Nov 1999 * * 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, 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 (see the file COPYING); if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA * */ #include #include #include #include #include #include "waterfall.h" #define TITLE "Waterfall spectrum display" #define MAX_DATA 255 #define FG_STEPS 16 #define DEFAULT_WIDTH 150 #define DEFAULT_HEIGHT 100 #define PAN_STEPS 9 #define MID_PAN_STEP 4 /* For each hue mode, defines the max intensity low, mid, and high colors */ /* { {low_r,low_g,low_b}, {mid_r,mid_g,mid_b}, {high_r,high_g,high_b} } */ static gushort colors[][3][3] = { {{0,0xFFFF,0}, {0,0xFFFF,0}, {0,0xFFFF,0}}, /* HUE_CONSTANT */ {{0,0xFFFF,0}, {0xb504,0xb504,0}, {0xFFFF,0,0}}, /* HUE_INTENSITY */ {{0xFFFF,0,0}, {0,0xFFFF,0}, {0,0,0xFFFF}}, /* HUE_STEREO */ {{0,0,0xFFFF}, {0,0xFFFF,0}, {0xFFFF,0,0}}, /* HUE_ONSET */ {{0,0,0xFFFF}, {0,0xFFFF,0}, {0xFFFF,0,0}}, /* HUE_ENTROPY */ }; #ifndef MAX #define MAX(a,b) ((a>b) ? (a) : (b)) #endif #ifndef MIN #define MIN(a,b) ((agradient = LINEAR_GRADIENT; wconf->hue_mode = HUE_STEREO; wconf->layout = LAYOUT_STEREO; wconf->line_thickness = 1; wconf->scrolling = TRUE; wconf->width = DEFAULT_WIDTH; wconf->height = DEFAULT_HEIGHT; wconf->orientation = ORIENT_BOTTOM; wconf->persistent_position = FALSE; wconf->x = 0; wconf->y = 0; wconf->freq_smooth_width = 1; } void set_size( gint new_width, gint new_height ) { if ( (new_width == 0) || (new_height == 0) ) return; wconf.height = new_height; wconf.width = new_width; if ( fg_pixmap[0] != NULL ) gdk_pixmap_unref( fg_pixmap[0] ); if ( fg_pixmap[1] != NULL ) gdk_pixmap_unref( fg_pixmap[1] ); fg_pixmap[0] = gdk_pixmap_new( window->window, new_width, new_height, gdk_visual_get_best_depth() ); fg_pixmap[1] = gdk_pixmap_new( window->window, new_width, new_height, gdk_visual_get_best_depth() ); gdk_gc_set_foreground( gc, &black ); gdk_draw_rectangle( fg_pixmap[0], gc, TRUE, 0, 0, new_width, new_height); gdk_draw_rectangle( fg_pixmap[1], gc, TRUE, 0, 0, new_width, new_height); which_pm = 0; gdk_window_set_back_pixmap( area->window, fg_pixmap[0], 0 ); gdk_window_clear( area->window ); } static void waterfall_resize_cb( GtkContainer *container, gpointer user_data ) { gint new_width, new_height; if ( !window ) return; gdk_window_get_size( window->window, &new_width, &new_height); if ( (new_width == wconf.width) && (new_height == wconf.height) && (fg_pixmap[0] != NULL) && (fg_pixmap[1] != NULL) ) return; set_size( new_width, new_height ); } gint linear_step( gint one, gint two, gint i, gint totalsteps ) { return one + i * (two - one) / totalsteps; } void make_fg_colors() { int i, j; float scale; /* Copy the pixel values for black from the black we already * allocated. We're using enough colors already; we can be frugal * for those poor fools on 8-bit displays */ for ( j=0; j < PAN_STEPS; j++ ) { fgcolor[0][j].red = 0; fgcolor[0][j].blue = 0; fgcolor[0][j].green = 0; fgcolor[0][j].pixel = black.pixel; } /* First, take care of the center, left, and right colors. */ for ( i=1; i < FG_STEPS; i++ ) { if ( wconf.gradient == SQRT_GRADIENT ) scale = sqrt(((float) (i+1)) / FG_STEPS); /* Dark gets lighter */ else scale = ((float) (i+1)) / FG_STEPS; /* Center */ fgcolor[i][MID_PAN_STEP].red = colors[wconf.hue_mode][1][0] * scale; fgcolor[i][MID_PAN_STEP].green = colors[wconf.hue_mode][1][1] * scale; fgcolor[i][MID_PAN_STEP].blue = colors[wconf.hue_mode][1][2] * scale; gdk_color_alloc( gdk_colormap_get_system(), &fgcolor[i][MID_PAN_STEP] ); if ( wconf.hue_mode != HUE_CONSTANT ) { /* Left */ fgcolor[i][0].red = colors[wconf.hue_mode][0][0] * scale; fgcolor[i][0].green = colors[wconf.hue_mode][0][1] * scale; fgcolor[i][0].blue = colors[wconf.hue_mode][0][2] * scale; gdk_color_alloc( gdk_colormap_get_system(), &fgcolor[i][0] ); /* Right */ fgcolor[i][PAN_STEPS-1].red = colors[wconf.hue_mode][2][0] * scale; fgcolor[i][PAN_STEPS-1].green = colors[wconf.hue_mode][2][1] * scale; fgcolor[i][PAN_STEPS-1].blue = colors[wconf.hue_mode][2][2] * scale; gdk_color_alloc(gdk_colormap_get_system(), &fgcolor[i][PAN_STEPS-1]); } } if ( wconf.hue_mode != HUE_CONSTANT ) { /* Iterate through each brightness level except the lowest (all black) */ for ( i=1; i < FG_STEPS-1; i++ ) { /* Iterate through each pan step, from extreme side to center, not including those boundaries */ for ( j=1; j < MID_PAN_STEP; j++ ) { scale = ((float) (j+1)) / MID_PAN_STEP; /* Left */ fgcolor[i][j].red = linear_step( fgcolor[i][0].red, fgcolor[i][MID_PAN_STEP].red, j, MID_PAN_STEP ); fgcolor[i][j].green = linear_step( fgcolor[i][0].green, fgcolor[i][MID_PAN_STEP].green, j, MID_PAN_STEP ); fgcolor[i][j].blue = linear_step( fgcolor[i][0].blue, fgcolor[i][MID_PAN_STEP].blue, j, MID_PAN_STEP ); gdk_color_alloc(gdk_colormap_get_system(), &fgcolor[i][j]); /* Right */ fgcolor[i][PAN_STEPS-1-j].red = linear_step( fgcolor[i][PAN_STEPS-1].red, fgcolor[i][MID_PAN_STEP].red, j, MID_PAN_STEP ); fgcolor[i][PAN_STEPS-1-j].green = linear_step( fgcolor[i][PAN_STEPS-1].green, fgcolor[i][MID_PAN_STEP].green, j, MID_PAN_STEP ); fgcolor[i][PAN_STEPS-1-j].blue = linear_step( fgcolor[i][PAN_STEPS-1].blue, fgcolor[i][MID_PAN_STEP].blue, j, MID_PAN_STEP ); gdk_color_alloc(gdk_colormap_get_system(), &fgcolor[i][PAN_STEPS-1-j]); } } } } void read_config(void) { ConfigFile *cfgfile; gint lwidth, lheight; gchar *filename; gint x; gboolean xb; filename = g_strconcat( g_get_home_dir(), "/.xmms/config", NULL ); cfgfile = xmms_cfg_open_file( filename ); if ( cfgfile ) { if ( xmms_cfg_read_int( cfgfile, "waterfall", "width", &lwidth ) && xmms_cfg_read_int( cfgfile, "waterfall", "height", &lheight ) ) { gtk_widget_set_usize( window, lwidth, lheight ); set_size( lwidth, lheight ); } if ( xmms_cfg_read_int( cfgfile, "waterfall", "hue_mode", &x ) ) wconf.hue_mode = x; if ( xmms_cfg_read_int( cfgfile, "waterfall", "gradient", &x ) ) wconf.gradient = x; if ( xmms_cfg_read_int( cfgfile, "waterfall", "layout", &x ) ) wconf.layout = x; if ( xmms_cfg_read_int( cfgfile, "waterfall", "line_thickness", &x ) ) wconf.line_thickness = x; if ( xmms_cfg_read_boolean( cfgfile, "waterfall", "scroll", &xb ) ) wconf.scrolling = xb; if ( xmms_cfg_read_int( cfgfile, "waterfall", "freq_smooth_width", &x ) ) { if ( x < 1 ) x = 1; else if ( x > NUM_BANDS ) x = NUM_BANDS; wconf.freq_smooth_width = x; } if ( xmms_cfg_read_int( cfgfile, "waterfall", "time_smooth_weight", &x )) { if ( x < 0 ) x = 0; else if ( x > 100 ) x = 100; wconf.time_smooth_weight = x; } if ( xmms_cfg_read_boolean( cfgfile, "waterfall", "persistent_position", &xb )) { wconf.persistent_position = xb; if ( xmms_cfg_read_int( cfgfile, "waterfall", "x", &x ) ) wconf.x = x; if ( xmms_cfg_read_int( cfgfile, "waterfall", "y", &x ) ) wconf.y = x; } if ( xmms_cfg_read_int( cfgfile, "waterfall", "orientation", &x ) ) wconf.orientation = x; xmms_cfg_free( cfgfile ); } g_free( filename ); } void write_config(void) { ConfigFile *cfgfile; gchar *filename; filename = g_strconcat( g_get_home_dir(), "/.xmms/config", NULL ); cfgfile = xmms_cfg_open_file( filename ); if ( !cfgfile ) cfgfile = xmms_cfg_new(); if ( wconf.persistent_position && window) gdk_window_get_position( window->window, &wconf.x, &wconf.y ); else wconf.x = wconf.y = 0; xmms_cfg_write_int( cfgfile, "waterfall", "width", wconf.width ); xmms_cfg_write_int( cfgfile, "waterfall", "height", wconf.height ); xmms_cfg_write_int( cfgfile, "waterfall", "hue_mode", wconf.hue_mode ); xmms_cfg_write_int( cfgfile, "waterfall", "gradient", wconf.gradient ); xmms_cfg_write_int( cfgfile, "waterfall", "layout", wconf.layout ); xmms_cfg_write_int( cfgfile, "waterfall", "line_thickness", wconf.line_thickness ); xmms_cfg_write_int( cfgfile, "waterfall", "orientation", wconf.orientation ); xmms_cfg_write_int( cfgfile, "waterfall", "freq_smooth_width", wconf.freq_smooth_width ); xmms_cfg_write_int( cfgfile, "waterfall", "time_smooth_weight", wconf.time_smooth_weight ); xmms_cfg_write_boolean( cfgfile, "waterfall", "scroll", wconf.scrolling ); xmms_cfg_write_boolean( cfgfile, "waterfall", "persistent_position", wconf.persistent_position ); xmms_cfg_write_int( cfgfile, "waterfall", "x", wconf.x ); xmms_cfg_write_int( cfgfile, "waterfall", "y", wconf.y ); xmms_cfg_write_file( cfgfile, filename ); xmms_cfg_free( cfgfile ); g_free( filename ); } static void waterfall_init(void) { gint desired_width, desired_height, b; if ( window ) return; window = gtk_window_new( GTK_WINDOW_DIALOG ); gtk_window_set_title( GTK_WINDOW( window ), TITLE ); gtk_window_set_policy( GTK_WINDOW( window ), TRUE, TRUE, FALSE ); gtk_widget_realize( window ); gtk_signal_connect( GTK_OBJECT( window ), "destroy", GTK_SIGNAL_FUNC( waterfall_destroy_cb ), NULL ); gtk_signal_connect( GTK_OBJECT( window ), "destroy", GTK_SIGNAL_FUNC( gtk_widget_destroyed ), &window ); gtk_signal_connect( GTK_OBJECT( window ), "check-resize", GTK_SIGNAL_FUNC( waterfall_resize_cb ), &window ); if ( !initialized ) /* This is our first time through here */ set_config_defaults( &wconf ); desired_width = wconf.width; desired_height = wconf.height; gtk_widget_set_usize( window, wconf.width, wconf.height ); gc = gdk_gc_new( window->window ); area = gtk_drawing_area_new(); gtk_container_add( GTK_CONTAINER( window ), area ); gtk_widget_realize( area ); if ( !initialized ) { black.red = 0; black.blue = 0; black.green = 0; gdk_color_alloc(gdk_colormap_get_system(),&black); } gdk_gc_set_background( gc, &black ); gtk_widget_show( area ); gtk_widget_show( window ); gdk_window_clear( window->window ); gdk_window_clear( area->window ); set_size( desired_width, desired_height ); if ( !initialized ) /* This is our first time through here */ { read_config(); make_fg_colors(); bands_left = &(bands[0]); bands_right = &(bands[1]); bands_tmp = &(bands[2]); hue_left = &(bands[3]); hue_right = &(bands[4]); wconf.prev_bands_left = &(bands[5]); wconf.prev_bands_right = &(bands[6]); wconf.prev_hue_left = &(bands[7]); wconf.prev_hue_right = &(bands[8]); memset( *(wconf.prev_bands_left), 0, NUM_BANDS * sizeof(int) ); memset( *(wconf.prev_bands_right), 0, NUM_BANDS * sizeof(int) ); memset( *(wconf.prev_hue_left), 0, NUM_BANDS * sizeof(int) ); memset( *(wconf.prev_hue_right), 0, NUM_BANDS * sizeof(int) ); } if ( wconf.persistent_position ) gdk_window_move( window->window, wconf.x, wconf.y ); initialized = TRUE; } static void waterfall_cleanup(void) { write_config(); if ( window ) { gtk_widget_destroy( window ); window = NULL; } if ( gc ) { gdk_gc_unref( gc ); gc = NULL; } if ( fg_pixmap[0] ) { gdk_pixmap_unref( fg_pixmap[0] ); fg_pixmap[0] = NULL; } if ( fg_pixmap[1] ) { gdk_pixmap_unref( fg_pixmap[1] ); fg_pixmap[1] = NULL; } } static void waterfall_playback_start(void) { } static void waterfall_playback_stop(void) { if(GTK_WIDGET_REALIZED(area)) { gdk_gc_set_foreground( gc, &black ); gdk_draw_rectangle( fg_pixmap[which_pm], gc, TRUE, 0, 0, wconf.width, wconf.height); gdk_window_clear(area->window); } } /* After this, the data will be in [0..MAX_DATA] */ gint scale_band( gint data ) { data >>= 7; if ( data <= 0 ) return 0; /* That ugly numerical constant is log(256). I think it's related to the FFT splitting the audio data into 256 frequency divisions. */ data = (gint)(log(data) * (MAX_DATA+1) / 5.54517744447956); if ( data > MAX_DATA ) return MAX_DATA; return data; } /* value is 0..255, hue is -255..255 */ GdkColor *get_color( gint value, gint hue ) { gint value_index, hue_index; value_index = value * FG_STEPS / 256; hue_index = (hue * PAN_STEPS / 256 + PAN_STEPS )/2; return &fgcolor[value_index][hue_index]; } void render_output( const gint left[], const gint right[], const gint left_hue[], const gint right_hue[] ) { gint b, tmp; GdkColor *left_color_p, *right_color_p; gint xPosL, xPosR; /* Dynamic positions of L and R rectangles */ gint band_width; GDK_THREADS_ENTER(); /* Initialize the pixel column positions. */ if ( wconf.layout == LAYOUT_MONO ) xPosL = xPosR = 0; else xPosL = xPosR = wconf.width / 2; /* Position is inside, not left edge */ if ( wconf.scrolling ) { if ( wconf.orientation == ORIENT_BOTTOM ) { /* Copy portion of displayed pixmap onto off-screen pixmap */ gdk_draw_pixmap( fg_pixmap[1-which_pm], gc, fg_pixmap[which_pm], 0, wconf.line_thickness, 0, 0, wconf.width, wconf.height - wconf.line_thickness ); line = wconf.height - 1 - wconf.line_thickness; } else { /* Copy portion of displayed pixmap onto off-screen pixmap */ gdk_draw_pixmap( fg_pixmap[1-which_pm], gc, fg_pixmap[which_pm], 0, 0, 0, wconf.line_thickness, wconf.width, wconf.height - wconf.line_thickness ); line = 0; } } else { if ( wconf.orientation == ORIENT_BOTTOM ) { line += wconf.line_thickness; if ( line >= wconf.height - wconf.line_thickness ) line = 0; } else { line -= wconf.line_thickness; if ( line < 0 ) line = wconf.height - wconf.line_thickness; } which_pm = 1-which_pm; /* So we keep using the foreground */ } for(b = 0; b < NUM_BANDS; b++) { /* Pick the intensity and hue value */ switch ( wconf.layout ) { case LAYOUT_STEREO: left_color_p = get_color( left[b], left_hue[b] ); right_color_p = get_color( right[b], right_hue[b] ); break; case LAYOUT_MIRRORED_MONO: tmp = MAX( left[b], right[b] ); left_color_p = get_color( tmp, left_hue[b] ); right_color_p = get_color( tmp, right_hue[b] ); case LAYOUT_MONO: tmp = MAX( left[b], right[b] ); /* this isn't quite right, but how to choose the hue? */ right_color_p = left_color_p = get_color( tmp, left_hue[b] ); break; } /* Do the drawing */ /* How many pixels in this particular band, after roundoff error? */ if ( wconf.layout == LAYOUT_MONO ) band_width = ((int) ((b+1) * wconf.width / NUM_BANDS) - (int) (b * wconf.width / NUM_BANDS)); else band_width = ((int) ((b+1) * wconf.width / (NUM_BANDS*2)) - (int) (b * wconf.width / (NUM_BANDS*2))); switch ( wconf.layout ) { case LAYOUT_MIRRORED_MONO: case LAYOUT_STEREO: /* draw left */ xPosL = xPosL - band_width; gdk_gc_set_foreground( gc, left_color_p ); gdk_draw_rectangle( fg_pixmap[1-which_pm], gc, TRUE, xPosL, line, band_width, wconf.line_thickness ); /* deliberately fall through. Everyone does the right side. */ case LAYOUT_MONO: /* draw right */ gdk_gc_set_foreground(gc, right_color_p ); gdk_draw_rectangle( fg_pixmap[1-which_pm], gc, TRUE, xPosR, line, band_width, wconf.line_thickness ); xPosR = xPosR + band_width; } } which_pm = 1-which_pm; if ( wconf.scrolling ) gdk_window_set_back_pixmap( area->window, fg_pixmap[which_pm], 0 ); gdk_window_clear(area->window); GDK_THREADS_LEAVE(); } /* Smooth out the data by averaging it with some of its neighbors. * This is an unweighted average, where each included neighbor has the * same effect on the data regardless of its distance. This is a * specific kind of convolution. It might be interesting to allow * arbitrary convolutions, though we'd lose our optimization * (described below). */ void freq_smooth( const gint data[], gint result[], gint width ) { gint sum=0, src_leading=0, src_trailing=0, dst=0; /* Rather than compute sum(nearby points)/width for each destination point, it's faster to compute that for the first point, then just add the next source point (on the leading edge of the moving window) and subject the source point on the trailing edge to keep the window a constant size. This saves us from doing an extra NUM_BANDS*(width-2) arithmetic operations (*2 for stereo). The cost is code complexity. */ /* Handle the boundary cases on the low end */ /* Expand the window to half its full width */ for ( ; src_leading < width/2; src_leading++ ) sum += data[src_leading]; /* Handle results for which we don't have a full window */ for ( ; src_leading < width; src_leading++, dst++ ) { sum += data[src_leading]; result[dst] = sum / (src_leading + 1); } /* Middle (normal) cases */ for ( ; src_leading < NUM_BANDS; dst++, src_leading++, src_trailing++ ) { sum = sum + data[src_leading] - data[src_trailing]; result[dst] = sum / width; } /* Boundary cases on leading end. Window is shrinking. */ for ( ; dst < NUM_BANDS; src_trailing++, dst++ ) { sum -= data[src_trailing]; width--; result[dst] = sum / width; } } /* x[n] = MAX( x[n-k], ... , x[n+k] ) */ void freq_smooth_max( const gint data[], gint result[], gint k ) { gint n, m, max, a, b; /* This could be more efficient with three times as much code. We could unroll the loop into 3 loops with ranges of 0..k-1, k..N-k-1, N-k-1..N-1. It would be slightly faster, since we wouldn't have to do the MAX and MIN checks. Doesn't seem worth the bloat and complexity though. */ for ( n=0; n < NUM_BANDS; n++ ) { a = MAX(0,n-k); b = MIN(NUM_BANDS-1, n+k); max = data[a]; for ( m = a+1; m <= b; m++ ) if ( data[m] > max ) max = data[m]; result[n] = max; } } void time_smooth( gint prev[], gint current[], gint result[], gint weight_of_prev ) { int i; int weight_of_current = 100 - weight_of_prev; for ( i=0; i < NUM_BANDS; i++ ) { result[i] = ((weight_of_current * current[i] + weight_of_prev * prev[i]) / 100 ); } } void hue_from_intensity( gint left_hue[], gint right_hue[], const gint left[], const gint right[] ) { gint b; for ( b=0; b < NUM_BANDS; b++ ) { left_hue[b] = left[b] * 2 - MAX_DATA; right_hue[b] = right[b] * 2 - MAX_DATA; } } void hue_from_entropy( gint left_hue[], gint right_hue[], const gint left[], const gint right[] ) { static float logN = 0.0; float entropyL = 0.0, entropyR = 0.0; float sumL = 0.0, sumR = 0.0; int b, hueL, hueR; if ( logN == 0.0 ) /* initialize this once */ logN = log( NUM_BANDS ); for ( b=0; b < NUM_BANDS; b++ ) { sumL += left[b]; sumR += right[b]; } for ( b=0; b < NUM_BANDS; b++ ) { if ( right[b] > 0 ) entropyR -= (((float) right[b]) / sumR) * log((float) right[b] / sumR); if ( left[b] > 0 ) entropyL -= (((float) left[b]) / sumL) * log((float) left[b] / sumL); } hueL = entropyL / logN * MAX_DATA * 2 - MAX_DATA; hueR = entropyR / logN * MAX_DATA * 2 - MAX_DATA; for ( b=0; b < NUM_BANDS; b++ ) { left_hue[b] = hueL; right_hue[b] = hueR; } } gint hue_from_onset_single( gint val, gint prev_val, gint prev_hue ) { if ( ((float) val/prev_val) < 2 ) return prev_hue * 0.9; return MIN( 10*(val - prev_val), MAX_DATA ) ; } void hue_from_onset( gint hue[], const gint data[], const gint prev_data[], const gint prev_hue[] ) { gint b; gint smoothed[NUM_BANDS]; freq_smooth_max( prev_data, smoothed, 1 ); for ( b=0; b < NUM_BANDS; b++ ) hue[b] = hue_from_onset_single( data[b], smoothed[b], prev_hue[b] ); } inline void hue_from_constant( gint left_hue[], gint right_hue[] ) { memset( left_hue, 0, NUM_BANDS * sizeof(int) ); memset( right_hue, 0, NUM_BANDS * sizeof(int) ); } void hue_from_stereo( gint left_hue[], gint right_hue[], gint left[], gint right[] ) { gint b; for ( b=0; b < NUM_BANDS; b++ ) { if (( left[b] == 0 ) && ( right[b] == 0 )) left_hue[b] = right_hue[b] = 0; else left_hue[b] = right_hue[b] = (MAX_DATA * (right[b] - left[b]) / (left[b] + right[b])); } } inline void swap( gint (**a)[], gint (**b)[] ) { gint (*tmp)[] = *a; *a = *b; *b = tmp; } static void waterfall_render_freq( gint16 data[2][256] ) { #ifdef FEWBANDS static int band_divisions[] = { 0,1,2,3,4,5,6,7,8,11,15,20,27,36,47,62,82,107,141,184,255 }; /* 20 intervals. Copied from vis.c */ #else static int band_divisions[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18, 19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34, 35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51, 52,53,54,55,56,57,58,61,66,71,76,81,87,93,100,107, 114,122,131,140,150,161,172,184, 255 }; /* 75 intervals. Copied from visualization.c */ #endif gint b, d; if ( !window ) return; /* Collect the 256 evenly-spaced data bands into NUM_BANDS logarithmically-spaced data bands, and scale the data logarithmically. */ for ( b = 0; b < NUM_BANDS; b++ ) { (*bands_left)[b] = 0; (*bands_right)[b] = 0; for ( d = band_divisions[b]; d < band_divisions[b+1]; d++ ) { /* display the maximum freq signal on each side */ if ( (*bands_right)[b] < data[1][d] ) (*bands_right)[b] = data[1][d]; if ( (*bands_left)[b] < data[0][d] ) (*bands_left)[b] = data[0][d]; } /* Scale the band data */ (*bands_left)[b] = scale_band( (*bands_left)[b] ); (*bands_right)[b] = scale_band( (*bands_right)[b] ); } /************************/ /* Data Transformations */ /* Frequency smoothing */ if ( wconf.freq_smooth_width > 1 ) { freq_smooth( *bands_left, *bands_tmp, wconf.freq_smooth_width ); swap( &bands_left, &bands_tmp ); freq_smooth( *bands_right, *bands_tmp, wconf.freq_smooth_width ); swap( &bands_right, &bands_tmp ); } /* Time smoothing */ if ( wconf.time_smooth_weight > 0 ) { time_smooth( *(wconf.prev_bands_left), *bands_left, *bands_tmp, wconf.time_smooth_weight ); swap( &bands_left, &bands_tmp ); time_smooth( *(wconf.prev_bands_right), *bands_right, *bands_tmp, wconf.time_smooth_weight ); swap( &bands_right, &bands_tmp ); } /************************/ /* Hue Assignment */ switch ( wconf.hue_mode ) { case HUE_STEREO: hue_from_stereo( *hue_left, *hue_right, *bands_left, *bands_right ); break; case HUE_INTENSITY: hue_from_intensity( *hue_left, *hue_right, *bands_left, *bands_right ); break; case HUE_ONSET: hue_from_onset( *hue_left, *bands_left, *(wconf.prev_bands_left), *(wconf.prev_hue_left) ); hue_from_onset( *hue_right, *bands_right, *(wconf.prev_bands_right), *(wconf.prev_hue_right) ); break; case HUE_ENTROPY: hue_from_entropy( *hue_left, *hue_right, *bands_left, *bands_right ); break; case HUE_CONSTANT: default: hue_from_constant( *hue_left, *hue_right ); } /************************/ /* Rendering */ render_output( *bands_left, *bands_right, *hue_left, *hue_right ); /* Save the data for the next round */ /* We can't just do the obvious prev = current, because we'd clobber prev the next time this function got called. They need to stay separate. */ swap( &(wconf.prev_bands_left), &bands_left ); swap( &(wconf.prev_bands_right), &bands_right ); swap( &(wconf.prev_hue_left), &hue_left ); swap( &(wconf.prev_hue_right), &hue_right ); return; }