diff --git a/envy24control/levelmeters.c b/envy24control/levelmeters.c index fa44979..dd8f75e 100644 --- a/envy24control/levelmeters.c +++ b/envy24control/levelmeters.c @@ -1,6 +1,8 @@ /***************************************************************************** levelmeters.c - Stereo level meters Copyright (C) 2000 by Jaroslav Kysela + Missing peak-level meter feature implented by NPM: + Copyright (C) 2010 by Niels Mayer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,11 +21,10 @@ #include "envy24control.h" -static GdkGC *penGreenShadow[21] = { NULL, }; +static GdkGC *penWhiteLight[21] = { NULL, }; static GdkGC *penGreenLight[21] = { NULL, }; -static GdkGC *penOrangeShadow[21] = { NULL, }; +static GdkGC *penBackground[21] = { NULL, }; static GdkGC *penOrangeLight[21] = { NULL, }; -static GdkGC *penRedShadow[21] = { NULL, }; static GdkGC *penRedLight[21] = { NULL, }; static GdkPixmap *pixmap[21] = { NULL, }; static snd_ctl_elem_value_t *peaks; @@ -38,16 +39,49 @@ static void update_peak_switch(void) g_print("Unable to read peaks: %s\n", snd_strerror(err)); } +/* + * Niels Mayer (NPM) Jul-11-10: Fixing https://bugzilla.redhat.com/show_bug.cgi?id=602903 + * by implementing peak-level meters. The http://alsa.cybermirror.org/manuals/icensemble/envy24.pdf + * soundchip provides hardware level meters and these are returned via ALSA as such: + * >> amixer -c M66 cget iface=PCM,name='Multi Track Peak',numid=45 + * ; type=INTEGER,access=r-------,values=22,min=0,max=255,step=0 + * : values=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,198,255,198 + * These globals store the peak levels of the meters across callbacks. + */ +#define RESET 9 +#define MULTI_TRACK_PEAK_CHANNELS 22 +// NPM: Note special case in original envy24control code, which puts stereo mix track at +// "index 0" which is actually "(peaks, 20)" and "(peaks, 21)" cases below +// thus peak_lmix <--> peak_levels[IDX_LMIX] ; peak_rmix <--> peak_levels[IDX_RMIX] +#define IDX_LMIX 20 +#define IDX_RMIX 21 +static int peak_levels[MULTI_TRACK_PEAK_CHANNELS] = {0,}; +static int peak_changed[MULTI_TRACK_PEAK_CHANNELS] = {0,}; +static int previous_levels[MULTI_TRACK_PEAK_CHANNELS] = {0,}; +/* NPM: changed for https://bugzilla.redhat.com/show_bug.cgi?id=602903 */ static void get_levels(int idx, int *l1, int *l2) { - *l1 = *l2 = 0; - - if (idx == 0) { - *l1 = snd_ctl_elem_value_get_integer(peaks, 20); - *l2 = snd_ctl_elem_value_get_integer(peaks, 21); - } else { - *l1 = *l2 = snd_ctl_elem_value_get_integer(peaks, idx - 1); - } + *l1 = *l2 = 0; + if (idx == 0) { //special case idx=0 as digital mix pair + if ((peak_changed[IDX_LMIX] != RESET) && (peak_changed[IDX_RMIX] != RESET)) { /* don't change values if doing "Reset Peaks" */ + if ((*l1 = snd_ctl_elem_value_get_integer(peaks, 20)) > peak_levels[IDX_LMIX]) { + peak_levels[IDX_LMIX] = (*l1); + peak_changed[IDX_LMIX] = TRUE; + } + if ((*l2 = snd_ctl_elem_value_get_integer(peaks, 21)) > peak_levels[IDX_RMIX]) { + peak_levels[IDX_RMIX] = (*l2); + peak_changed[IDX_RMIX] = TRUE; + } + } + } + else { + if (peak_changed[idx-1] != RESET) { + if ((*l1 = snd_ctl_elem_value_get_integer(peaks, idx - 1)) > peak_levels[idx - 1]) { + peak_levels[idx - 1] = (*l1); + peak_changed[idx - 1] = TRUE; + } + } + } } static GdkGC *get_pen(int idx, int nRed, int nGreen, int nBlue) @@ -79,73 +113,216 @@ static int get_index(gchar *name) return result; } +/* + * NPM: per http://alsa.cybermirror.org/manuals/icensemble/envy24.pdf the + * the values in each entry of iface=PCM,name='Multi Track Peak',numid=45 + * represent: "Peak data derived from the absolute value of 9 msb. 00h + * min - FFh max volume. Reading the register resets the meter to 00h." + */ +#define NUM_METERING_LEVELS 256 +#define GET_COLOR_FOR_PEAKLEVEL(level) \ + (level > 224) \ + ? (penRedLight[idx]) \ + : ((level > 192) \ + ? (penOrangeLight[idx]) \ + : ((level > 128) \ + ? (penWhiteLight[idx]) \ + : (penGreenLight[idx]))) + +/* + * NPM: Called by redraw_meters(), this is the normal case + * where we draw the meter and peaks, if changed. + */ +static void draw_meters_and_peaks(int idx, int width, int height, int level1, int level2, + int stereo, int segment_width) +{ + int meter1 = (stereo) + ? (int) ( (((double)peak_levels[IDX_LMIX]) + / ((double)NUM_METERING_LEVELS)) + * ((double)height)) + : (int) ( (((double)peak_levels[idx - 1]) + / ((double)NUM_METERING_LEVELS)) + * ((double)height)); + int meter2 = (stereo) + ? (int) ( (((double)peak_levels[IDX_RMIX]) + / ((double)NUM_METERING_LEVELS)) + * ((double)height)) + : 0; + int peak1 = height - meter1; + int peak2 = (stereo) ? height - meter2: 0; + /* + * draw the peaks, if changed + */ + if ( (stereo && (peak_changed[IDX_LMIX] || peak_changed[IDX_RMIX])) + || peak_changed[idx-1]) { + int peak2_level, peak1_level = (stereo) ? peak_levels[IDX_LMIX] : peak_levels[idx-1]; + //POTENTIAL X-EFFICIENCY HACK: skip drawing the line: instead of drawing + //a line, just don't refresh the part of therectangle that created the peak, + //and always clear from below that peak using Y=peak1+1 so as to leave behind the + //top line of pixels from the peak that caused the peak. Not as if that's a big efficiency + //win, not like setting proper clip regions and computing "damage areas".. + gdk_draw_line(pixmap[idx], + GET_COLOR_FOR_PEAKLEVEL(peak1_level), + //X1 + 6, + //Y1 + peak1 - 1, + //X2 + 5 + segment_width, + //Y2 + peak1 - 1 + ); + if (stereo && peak_changed[IDX_RMIX]) { //for stereo, draw RMIX, but skip redraw if same + peak2_level = peak_levels[IDX_RMIX]; + gdk_draw_line(pixmap[idx], + GET_COLOR_FOR_PEAKLEVEL(peak2_level), + //X1 + 2 + (width / 2), + //Y1 + peak2 - 1, + //X2 + 1 + (width / 2) + segment_width, + //Y2 + peak2 - 1 + ); + peak_changed[IDX_RMIX] = FALSE; + } + if (stereo) { + if (peak_changed[IDX_LMIX]) + peak_changed[IDX_LMIX] = FALSE; + } + else + peak_changed[idx-1] = FALSE; + } + + /* + * draw the meters + * level1 at 0 --> no dspl + * level1 at 1 --> draw rectange at (1/255)*height + * level1 at 255 --> draw rectangle (255/255)*height + */ + meter1 = (int) ((((double)level1) + / ((double)NUM_METERING_LEVELS)) + * (double)height); + if (stereo) + meter2 = (int) ((((double)level2) + / ((double)NUM_METERING_LEVELS)) + * (double)height); + if (level1 != (stereo ? previous_levels[IDX_LMIX] : previous_levels[idx-1]) ) { //skip redraw if same + gdk_draw_rectangle(pixmap[idx], + penBackground[idx], + TRUE, + //X + 6, // draw black downward from peak + //Y + peak1, + //WIDTH + segment_width, + //HEIGHT + height - meter1 - peak1 + ); + gdk_draw_rectangle(pixmap[idx], + penGreenLight[idx], + TRUE, + //X + 6, + //Y + height - meter1, + //WIDTH + segment_width, + //HEIGHT + meter1 + ); + /* save current level value, skip redraw next time if no change */ + if (stereo) + previous_levels[IDX_LMIX] = level1; + else + previous_levels[idx-1] = level1; + } + if (stereo && (level2 != previous_levels[IDX_RMIX])) { //for stereo, draw RMIX, but skip redraw if same + gdk_draw_rectangle(pixmap[idx], + penBackground[idx], + TRUE, + //X + 2 + (width / 2), + //Y + peak2, + //WIDTH + segment_width, + //HEIGHT + height - meter2 - peak2 + ); + gdk_draw_rectangle(pixmap[idx], + penOrangeLight[idx], + TRUE, + //X + 2 + (width / 2), + //Y + height - meter2, + //WIDTH + segment_width, + //HEIGHT + meter2 + ); + /* save current level value, skip redraw next time if no change */ + previous_levels[IDX_RMIX] = level2; + } +} + +/* + * NPM: Called by redraw_meters(), this is a special case for when "Reset + * Peaks" is clicked: then just refresh meters, ignore value, clear RESET + * and let the next pass-through draw for the first time in the cleared + * meter... + */ +static void draw_meters_reset(int idx, int width, int height, int level1, int level2, + int stereo, int segment_width) +{ + gdk_draw_rectangle(pixmap[idx], + penBackground[idx], + TRUE, + //X + 6, + //Y + 0, + //WIDTH + segment_width, + //HEIGHT + height + ); + if (stereo) { + gdk_draw_rectangle(pixmap[idx], + penBackground[idx], + TRUE, + //X + 2 + (width / 2), + //Y + 0, + //WIDTH + segment_width, + //HEIGHT + height + ); + peak_changed[IDX_LMIX] = FALSE; + peak_changed[IDX_RMIX] = FALSE; + } + else { + peak_changed[idx-1] = FALSE; + } +} + static void redraw_meters(int idx, int width, int height, int level1, int level2) { - int stereo = idx == 0; - int segment_width = stereo ? (width / 2) - 8 : width - 12; - int segments = (height - 6) / 4; - int green_segments = (segments / 4) * 3; - int red_segments = 2; - int orange_segments = segments - green_segments - red_segments; - int seg; - int segs_on1 = ((segments * level1) + 128) / 255; - int segs_on2 = ((segments * level2) + 128) / 255; + int stereo = (idx == 0); + int segment_width = (stereo) + ? (width / 2) - 8 + : width - 12; - // g_print("segs_on1 = %i (%i), segs_on2 = %i (%i)\n", segs_on1, level1, segs_on2, level2); - for (seg = 0; seg < green_segments; seg++) { - gdk_draw_rectangle(pixmap[idx], - segs_on1 > 0 ? penGreenLight[idx] : penGreenShadow[idx], - TRUE, - 6, 3 + ((segments - seg - 1) * 4), - segment_width, - 3); - if (stereo) - gdk_draw_rectangle(pixmap[idx], - segs_on2 > 0 ? penGreenLight[idx] : penGreenShadow[idx], - TRUE, - 2 + (width / 2), - 3 + ((segments - seg - 1) * 4), - segment_width, - 3); - segs_on1--; - segs_on2--; - } - for (seg = green_segments; seg < green_segments + orange_segments; seg++) { - gdk_draw_rectangle(pixmap[idx], - segs_on1 > 0 ? penOrangeLight[idx] : penOrangeShadow[idx], - TRUE, - 6, 3 + ((segments - seg - 1) * 4), - segment_width, - 3); - if (stereo) - gdk_draw_rectangle(pixmap[idx], - segs_on2 > 0 ? penOrangeLight[idx] : penOrangeShadow[idx], - TRUE, - 2 + (width / 2), - 3 + ((segments - seg - 1) * 4), - segment_width, - 3); - segs_on1--; - segs_on2--; - } - for (seg = green_segments + orange_segments; seg < segments; seg++) { - gdk_draw_rectangle(pixmap[idx], - segs_on1 > 0 ? penRedLight[idx] : penRedShadow[idx], - TRUE, - 6, 3 + ((segments - seg - 1) * 4), - segment_width, - 3); - if (stereo) - gdk_draw_rectangle(pixmap[idx], - segs_on2 > 0 ? penRedLight[idx] : penRedShadow[idx], - TRUE, - 2 + (width / 2), - 3 + ((segments - seg - 1) * 4), - segment_width, - 3); - segs_on1--; - segs_on2--; - } + if ( (stereo && ((peak_changed[IDX_LMIX] == RESET) || (peak_changed[IDX_RMIX] == RESET))) + || peak_changed[idx-1] == RESET) //needs full refresh, reset peaks button was clicked + draw_meters_reset(idx, width, height, level1, level2, stereo, segment_width); + else + draw_meters_and_peaks(idx, width, height, level1, level2, stereo, segment_width); } gint level_meters_configure_event(GtkWidget *widget, GdkEventConfigure *event) @@ -158,11 +335,10 @@ gint level_meters_configure_event(GtkWidget *widget, GdkEventConfigure *event) widget->allocation.width, widget->allocation.height, -1); - penGreenShadow[idx] = get_pen(idx, 0, 0x77ff, 0); + penWhiteLight[idx] = get_pen(idx, 0xffff, 0xffff, 0xffff); penGreenLight[idx] = get_pen(idx, 0, 0xffff, 0); - penOrangeShadow[idx] = get_pen(idx, 0xddff, 0x55ff, 0); - penOrangeLight[idx] = get_pen(idx, 0xffff, 0x99ff, 0); - penRedShadow[idx] = get_pen(idx, 0xaaff, 0, 0); + penBackground[idx] = get_pen(idx, 0x1111, 0x5511, 0); + penOrangeLight[idx] = get_pen(idx, 0xff11, 0x9911, 0); penRedLight[idx] = get_pen(idx, 0xffff, 0, 0); gdk_draw_rectangle(pixmap[idx], widget->style->black_gc, @@ -170,6 +346,9 @@ gint level_meters_configure_event(GtkWidget *widget, GdkEventConfigure *event) 0, 0, widget->allocation.width, widget->allocation.height); + + level_meters_reset_peaks(NULL, NULL); /* NPM */ + // g_print("configure: %i:%i\n", widget->allocation.width, widget->allocation.height); redraw_meters(idx, widget->allocation.width, widget->allocation.height, 0, 0); return TRUE; @@ -255,8 +434,18 @@ gint level_meters_timeout_callback(gpointer data) return TRUE; } + +/* NPM fixed lack of implementation ( https://bugzilla.redhat.com/show_bug.cgi?id=602903 )*/ void level_meters_reset_peaks(GtkButton *button, gpointer data) { + int i; + for (i = 0; i < MULTI_TRACK_PEAK_CHANNELS ; i++) { + peak_levels[i] = 0; + previous_levels[i] = 0; + peak_changed[i] = RESET; + } + + level_meters_timeout_callback((gpointer) data); } void level_meters_init(void)