summaryrefslogtreecommitdiffstats
path: root/apps/recorder/peakmeter.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/recorder/peakmeter.c')
-rw-r--r--apps/recorder/peakmeter.c631
1 files changed, 615 insertions, 16 deletions
diff --git a/apps/recorder/peakmeter.c b/apps/recorder/peakmeter.c
index df7c672617..e1850a4bb0 100644
--- a/apps/recorder/peakmeter.c
+++ b/apps/recorder/peakmeter.c
@@ -46,24 +46,52 @@ static bool peak_meter_r_clip = false;
static long peak_meter_clip_timeout_l;
static long peak_meter_clip_timeout_r;
+static int peak_meter_clip_hold;
+
+/* specifies the value range in peak volume values */
+unsigned short peak_meter_range_min;
+unsigned short peak_meter_range_max;
+
/* if set to true clip timeout is disabled */
static bool peak_meter_clip_eternal = false;
+static bool peak_meter_use_dbfs = true;
+
+
#ifndef SIMULATOR
static int peak_meter_src_l = MAS_REG_DQPEAK_L;
static int peak_meter_src_r = MAS_REG_DQPEAK_R;
#endif
/* temporarily en- / disables peak meter. This is
-
especially for external applications to detect
-
if the peak_meter is in use and needs drawing at all */
bool peak_meter_enabled = true;
+/*
+bool peak_meter_use_thread = false;
+static char peak_meter_stack[DEFAULT_STACK_SIZE];
+*/
+/* used in wps.c to set the display frame rate of the peak meter */
+int peak_meter_fps = 20;
+
static int peak_meter_l;
static int peak_meter_r;
+static int peak_meter_hold = 1;
+static int peak_meter_release = 8;
+
+/* debug only */
+#ifdef PM_DEBUG
+static int peek_calls = 0;
+
+#define PEEKS_PER_DRAW_SIZE 40
+static unsigned int peeks_per_redraw[PEEKS_PER_DRAW_SIZE];
+
+#define TICKS_PER_DRAW_SIZE 20
+static unsigned int ticks_per_redraw[TICKS_PER_DRAW_SIZE];
+#endif
+
/* time out values for max */
static long max_time_out[] = {
0 * HZ, HZ / 5, 30, HZ / 2, HZ, 2 * HZ,
@@ -80,6 +108,353 @@ static long clip_time_out[] = {
2700 * HZ, 5400 * HZ
};
+/* precalculated peak values that represent magical
+ dBfs values. Used to draw the scale */
+#if 0
+static int db_scale_src_values[] = {
+ 32767, /* 0 db */
+ 23197, /* - 3 db */
+ 16422, /* - 6 db */
+ 11626, /* - 9 db */
+ 8231, /* -12 db */
+ 4125, /* -18 db */
+ 2067, /* -24 db */
+ 1036, /* -30 db */
+ 328, /* -40 db */
+ 104, /* -50 db */
+ 33, /* -60 db */
+};
+#else
+static int db_scale_src_values[] = {
+ 32752, /* 0 db */
+ 22784, /* - 3 db */
+ 14256, /* - 6 db */
+ 11752, /* - 9 db */
+ 9256, /* -12 db */
+ 4256, /* -18 db */
+ 2186, /* -24 db */
+ 1186, /* -30 db */
+ 373, /* -40 db */
+ 102, /* -50 db */
+ 33, /* -60 db */
+};
+#endif
+
+int db_scale_count = sizeof db_scale_src_values / sizeof (int);
+
+/* if db_scale_valid is false the content of
+ db_scale_lcd_coord needs recalculation */
+static bool db_scale_valid = false;
+
+/* contains the lcd x coordinates of the magical
+ scale values in db_scale_src_values */
+static int db_scale_lcd_coord[sizeof db_scale_src_values / sizeof (int)];
+
+
+/**
+ * Calculates dB Value for the peak meter, uses peak value as input
+ * @param int sample - The input value
+ * Make sure that 0 <= value < SAMPLE_RANGE
+ *
+ * @return int - The 2 digit fixed comma result of the euation
+ * 20 * log (sample / SAMPLE_RANGE) + 90
+ * Output range is 0-8961 (that is 0,0 - 89,6 dB).
+ * Normally 0dB is full scale, here it is shifted +90dB.
+ * The calculation is based on the results of a linear
+ * approximation tool written specifically for this problem
+ * by Andreas Zwirtes (radhard@gmx.de). The result hat an
+ * accurracy of better than 2%. It is highly runtime optimized,
+ * the cascading if-clauses do an successive approximation on
+ * the input value. This avoids big lookup-tables and
+ * for-loops.
+ */
+
+int calc_db (int isample) {
+ /* return n+m*(isample-istart)/100 */
+ int n;
+ long m;
+ int istart;
+
+ /* Range 1-4 */
+ if (isample < 119) {
+
+ /* Range 1-2 */
+ if (isample < 5) {
+
+ /* Range 1 */
+ if (isample < 1) {
+ istart = 0;
+ n = 0;
+ m = 5900;
+ }
+
+ /* Range 2 */
+ else {
+ istart = 1;
+ n = 59;
+ m = 34950;
+ }
+ }
+
+ /* Range 3-4 */
+ else {
+
+ /* Range 3 */
+ if (isample < 24) {
+ istart = 5;
+ n = 1457;
+ m = 7168;
+ }
+
+ /* Range 4 */
+ else {
+ istart = 24;
+ n = 2819;
+ m = 1464;
+ }
+ }
+ }
+
+ /* Range 5-8 */
+ else {
+
+ /* Range 5-6 */
+ if (isample < 2918) {
+
+ /* Range 5 */
+ if (isample < 592) {
+ istart = 119;
+ n = 4210;
+ m = 295;
+ }
+
+ /* Range 6 */
+ else {
+ istart = 592;
+ n = 5605;
+ m = 60;
+ }
+ }
+
+ /* Range 7-8 */
+ else {
+
+ /* Range 7 */
+ if (isample < 15352) {
+ istart = 2918;
+ n = 7001;
+ m = 12;
+ }
+
+ /* Range 8 */
+ else {
+ istart = 15352;
+ n = 8439;
+ m = 3;
+ }
+ }
+ }
+
+ return n + (m * (long)(isample - istart)) / 100L;
+}
+
+
+/**
+ * A helper function for db_to_sample. Don't call it separately but
+ * use db_to_sample. If one or both of min and max are outside the
+ * range 0 <= min (or max) < 8961 the behaviour of this function is
+ * undefined. It may not return.
+ * @param int min - The minimum of the value range that is searched.
+ * @param int max - The maximum of the value range that is searched.
+ * @param int db - The value in dBfs * (-100) for which the according
+ * minimal peak sample is searched.
+ * @return int - A linear volume value with 0 <= value < MAX_PEAK
+ */
+static int db_to_sample_bin_search(int min, int max, int db){
+ int test = min + (max - min) / 2;
+
+ if (min < max) {
+ if (calc_db(test) < db) {
+ test = db_to_sample_bin_search(test, max, db);
+ } else {
+ if (calc_db(test-1) > db) {
+ test = db_to_sample_bin_search(min, test, db);
+ }
+ }
+ }
+ return test;
+}
+
+/**
+ * Converts a value representing dBfs to a linear
+ * scaled volume info as it is used by the MAS.
+ * An incredibly inefficiant function which is
+ * the vague inverse of calc_db. This really
+ * should be replaced by something better soon.
+ *
+ * @param int db - A dBfs * 100 value with
+ * -9000 < value <= 0
+ * @return int - The return value is in the range of
+ * 0 <= return value < MAX_PEAK
+ */
+static int db_to_sample(int db) {
+ int retval = 0;
+
+ /* what is the maximum pseudo db value */
+ int max_peak_db = calc_db(MAX_PEAK - 1);
+
+ /* range check: db value to big */
+ if (max_peak_db + db < 0) {
+ retval = 0;
+ }
+
+ /* range check: db value too small */
+ else if (max_peak_db + db >= max_peak_db) {
+ retval = MAX_PEAK -1;
+ }
+
+ /* value in range: find the matching linear value */
+ else {
+ retval = db_to_sample_bin_search(0, MAX_PEAK, max_peak_db + db);
+
+ /* as this is a dirty function anyway, we want to adjust the
+ full scale hit manually to avoid users complaining that when
+ they adjust maximum for 0 dBfs and display it in percent it
+ shows 99%. That is due to precision loss and this is the
+ optical fix */
+ }
+
+ return retval;
+}
+
+/**
+ * Set the min value for restriction of the value range.
+ * @param int newmin - depending wether dBfs is used
+ * newmin is a value in dBfs * 100 or in linear percent values.
+ * for dBfs: -9000 < newmin <= 0
+ * for linear: 0 <= newmin <= 100
+ */
+void peak_meter_set_min(int newmin) {
+ if (peak_meter_use_dbfs) {
+ peak_meter_range_min = db_to_sample(newmin);
+
+ } else {
+ if (newmin < peak_meter_range_max) {
+ peak_meter_range_min = newmin * MAX_PEAK / 100;
+ }
+ }
+ db_scale_valid = false;
+}
+
+/**
+ * Returns the minimum value of the range the meter
+ * displays. If the scale is set to dBfs it returns
+ * dBfs values * 100 or linear percent values.
+ * @return: using dBfs : -9000 < value <= 0
+ * using linear scale: 0 <= value <= 100
+ */
+int peak_meter_get_min(void) {
+ int retval = 0;
+ if (peak_meter_use_dbfs) {
+ retval = calc_db(peak_meter_range_min) - calc_db(MAX_PEAK - 1);
+ } else {
+ retval = peak_meter_range_min * 100 / MAX_PEAK;
+ }
+ return retval;
+}
+
+/**
+ * Set the max value for restriction of the value range.
+ * @param int newmax - depending wether dBfs is used
+ * newmax is a value in dBfs * 100 or in linear percent values.
+ * for dBfs: -9000 < newmax <= 0
+ * for linear: 0 <= newmax <= 100
+ */
+void peak_meter_set_max(int newmax) {
+ if (peak_meter_use_dbfs) {
+ peak_meter_range_max = db_to_sample(newmax);
+ } else {
+ if (newmax > peak_meter_range_min) {
+ peak_meter_range_max = newmax * MAX_PEAK / 100;
+ }
+ }
+ db_scale_valid = false;
+}
+
+/**
+ * Returns the minimum value of the range the meter
+ * displays. If the scale is set to dBfs it returns
+ * dBfs values * 100 or linear percent values
+ * @return: using dBfs : -9000 < value <= 0
+ * using linear scale: 0 <= value <= 100
+ */
+int peak_meter_get_max(void) {
+ int retval = 0;
+ if (peak_meter_use_dbfs) {
+ retval = calc_db(peak_meter_range_max) - calc_db(MAX_PEAK - 1);
+ } else {
+ retval = peak_meter_range_max * 100 / MAX_PEAK;
+ }
+ return retval;
+}
+
+/**
+ * Returns 1 if the meter currently is
+ * displaying dBfs values, 0 if the meter
+ * displays percent values.
+ * @return int - returns 0 or 1.
+ */
+int peak_meter_get_use_dbfs(void) {
+ return peak_meter_use_dbfs ? 1 : 0;
+}
+
+/**
+ * Specifies wether the values displayed are scaled
+ * as dBfs or as linear percent values.
+ * @param int - Set to 0 for linear percent scale. Any other value
+ * switches on dBfs.
+ */
+void peak_meter_set_use_dbfs(int use){
+ peak_meter_use_dbfs = ((use & 1) == 1);
+ db_scale_valid = false;
+}
+
+/**
+ * Initialize the range of the meter. Only values
+ * that are in the range of [range_min ... range_max]
+ * are displayed.
+ * @param bool dbfs - set to true for dBfs,
+ * set to false for linear scaling in percent
+ * @param int range_min - Specifies the lower value of the range.
+ * Pass a value dBfs * 100 when dbfs is set to true.
+ * Pass a percent value when dbfs is set to false.
+ * @param int range_max - Specifies the upper value of the range.
+ * Pass a value dBfs * 100 when dbfs is set to true.
+ * Pass a percent value when dbfs is set to false.
+ */
+void peak_meter_init_range( bool dbfs, int range_min, int range_max) {
+ peak_meter_use_dbfs = dbfs;
+ peak_meter_set_min(range_min);
+ peak_meter_set_max(range_max);
+}
+
+/**
+ * Initialize the peak meter with all relevant values concerning times.
+ * @param int release - Set the maximum amount of pixels the meter is allowed
+ * to decrease with each redraw
+ * @param int hold - Select the time preset for the time the peak indicator
+ * is reset after a peak occurred. The preset values are
+ * stored in max_time_out.
+ * @param int clip_hold - Select the time preset for the time the peak
+ * indicator is reset after a peak occurred. The preset
+ * values are stored in clip_time_out.
+ */
+void peak_meter_init_times(int release, int hold, int clip_hold) {
+ peak_meter_hold = hold;
+ peak_meter_release = release;
+ peak_meter_clip_hold = clip_hold;
+}
+
/**
* Set the source of the peak meter to playback or to
* record.
@@ -127,20 +502,58 @@ void peak_meter_peek(void) {
(left == MAX_PEAK - 1)) {
peak_meter_l_clip = true;
peak_meter_clip_timeout_l =
- current_tick + clip_time_out[global_settings.peak_meter_clip_hold];
+ current_tick + clip_time_out[peak_meter_clip_hold];
}
if ((right == peak_meter_r) &&
(right == MAX_PEAK - 1)) {
peak_meter_r_clip = true;
peak_meter_clip_timeout_r =
- current_tick + clip_time_out[global_settings.peak_meter_clip_hold];
+ current_tick + clip_time_out[peak_meter_clip_hold];
}
- /* peaks are searched -> we have to find the maximum */
+ /* peaks are searched -> we have to find the maximum. When
+ many calls of peak_meter_peek the maximum value will be
+ stored in peak_meter_x. This maximum is reset by the
+ functions peak_meter_read_x. */
peak_meter_l = MAX(peak_meter_l, left);
peak_meter_r = MAX(peak_meter_r, right);
+
+#ifdef PM_DEBUG
+ peek_calls++;
+#endif
+}
+
+
+/**
+ * The thread function for the peak meter calls peak_meter_peek
+ * to reas out the mas and find maxima, clips, etc. No display
+ * is performed.
+ */
+/*
+void peak_meter_thread(void) {
+ sleep(5000);
+ while (1) {
+ if (peak_meter_enabled && peak_meter_use_thread){
+ peak_meter_peek();
+ }
+ yield();
+ }
+}
+*/
+
+/**
+ * Creates the peak meter thread
+ */
+/*
+void peak_meter_init(void) {
+ create_thread(
+ peak_meter_thread,
+ peak_meter_stack,
+ sizeof peak_meter_stack,
+ "peakmeter");
}
+*/
/**
* Reads out the peak volume of the left channel.
@@ -149,10 +562,20 @@ void peak_meter_peek(void) {
* is in the range 0 <= value < MAX_PEAK.
*/
static int peak_meter_read_l (void) {
+ /* peak_meter_l contains the maximum of
+ all peak values that were read by peak_meter_peek
+ since the last call of peak_meter_read_r */
int retval = peak_meter_l;
+#ifdef PM_DEBUG
+ peek_calls = 0;
+#endif
+
#ifdef SIMULATOR
peak_meter_l = 8000;
#else
+ /* reset peak_meter_l so that subsequent calls of
+ peak_meter_peek doesn't get fooled by an old
+ maximum value */
peak_meter_l = mas_codec_readreg(peak_meter_src_l);
#endif
return retval;
@@ -165,10 +588,20 @@ static int peak_meter_read_l (void) {
* is in the range 0 <= value < MAX_PEAK.
*/
static int peak_meter_read_r (void) {
+ /* peak_meter_r contains the maximum of
+ all peak values that were read by peak_meter_peek
+ since the last call of peak_meter_read_r */
int retval = peak_meter_r;
+#ifdef PM_DEBUG
+ peek_calls = 0;
+#endif
+
#ifdef SIMULATOR
peak_meter_l = 8000;
#else
+ /* reset peak_meter_r so that subsequent calls of
+ peak_meter_peek doesn't get fooled by an old
+ maximum value */
peak_meter_r = mas_codec_readreg(peak_meter_src_r);
#endif
return retval;
@@ -190,6 +623,51 @@ void peak_meter_set_clip_hold(int time) {
}
}
+/**
+ * Scales a peak value as read from the MAS to the range of meterwidth.
+ * The scaling is performed according to the scaling method (dBfs / linear)
+ * and the range (peak_meter_range_min .. peak_meter_range_max).
+ * @param unsigned short val - The volume value. Range: 0 <= val < MAX_PEAK
+ * @param int meterwidht - The widht of the meter in pixel
+ * @return unsigned short - A value 0 <= return value <= meterwidth
+ */
+unsigned short peak_meter_scale_value(unsigned short val, int meterwidth){
+ int range;
+ int retval;
+
+ if (val <= peak_meter_range_min) {
+ return 0;
+ }
+
+ if (val >= peak_meter_range_max) {
+ return meterwidth;
+ }
+
+ retval = val;
+
+ /* different scaling is used for dBfs and linear percent */
+ if (peak_meter_use_dbfs) {
+
+ /* needed the offset in 'zoomed' meters */
+ int dbmin = calc_db(peak_meter_range_min);
+
+ range = calc_db(peak_meter_range_max) - dbmin;
+
+ /* scale the samples dBfs */
+ retval = (calc_db(retval) - dbmin) * meterwidth / range;
+ }
+
+ /* Scale for linear percent display */
+ else
+ {
+ range =(peak_meter_range_max - peak_meter_range_min);
+
+ /* scale the samples */
+ retval = ((retval - peak_meter_range_min) * meterwidth) / range;
+ }
+ return retval;
+}
+
/**
* Draws a peak meter in the specified size at the specified position.
@@ -208,22 +686,66 @@ void peak_meter_draw(int x, int y, int width, int height) {
int meterwidth = width - 3;
int i;
+#ifdef PM_DEBUG
+ static long pm_tick = 0;
+ int tmp = peek_calls;
+#endif
+
/* if disabled only draw the peak meter */
if (peak_meter_enabled) {
+
/* read the volume info from MAS */
left = peak_meter_read_l();
right = peak_meter_read_r();
-
peak_meter_peek();
+ /* restrict the range to avoid drawing outside the lcd */
+ left = MAX(peak_meter_range_min, left);
+ left = MIN(peak_meter_range_max, left);
+
+ right = MAX(peak_meter_range_min, right);
+ right = MIN(peak_meter_range_max, right);
+
+ /* scale the samples dBfs */
+ left = peak_meter_scale_value(left, meterwidth);
+ right = peak_meter_scale_value(right, meterwidth);
+
+ /* if the scale has changed -> recalculate the scale
+ (The scale becomes invalid when the range changed.) */
+ if (!db_scale_valid){
+
+ if (peak_meter_use_dbfs) {
+ db_scale_count = sizeof db_scale_src_values / sizeof (int);
+ for (i = 0; i < db_scale_count; i++){
+ /* find the real x-coords for predefined interesting
+ dBfs values. These only are recalculated when the
+ scaling of the meter changed. */
+ db_scale_lcd_coord[i] =
+ peak_meter_scale_value(
+ db_scale_src_values[i],
+ meterwidth - 1);
+ }
+ }
- /* scale the samples */
- left /= (MAX_PEAK / meterwidth);
- right /= (MAX_PEAK / meterwidth);
+ /* when scaling linear we simly make 10% steps */
+ else {
+ int range = peak_meter_range_max - peak_meter_range_min;
+ db_scale_count = 10;
+ for (i = 0; i < db_scale_count; i++) {
+ db_scale_lcd_coord[i] =
+ (i * (MAX_PEAK / 10) - peak_meter_range_min) *
+ meterwidth / range;
+ }
+ }
+
+ /* mark scale valid to avoid recalculating dBfs values
+ of the scale. */
+ db_scale_valid = true;
+ }
/* apply release */
- left = MAX(left , last_left - global_settings.peak_meter_release);
- right = MAX(right, last_right - global_settings.peak_meter_release);
+ left = MAX(left , last_left - peak_meter_release);
+ right = MAX(right, last_right - peak_meter_release);
/* reset max values after timeout */
if (TIME_AFTER(current_tick, peak_meter_timeout_l)){
@@ -250,13 +772,13 @@ void peak_meter_draw(int x, int y, int width, int height) {
if (left > peak_meter_max_l) {
peak_meter_max_l = left - 1;
peak_meter_timeout_l =
- current_tick + max_time_out[global_settings.peak_meter_hold];
+ current_tick + max_time_out[peak_meter_hold];
}
if (right > peak_meter_max_r) {
peak_meter_max_r = right - 1;
peak_meter_timeout_r =
- current_tick + max_time_out[global_settings.peak_meter_hold];
+ current_tick + max_time_out[peak_meter_hold];
}
}
@@ -283,13 +805,90 @@ void peak_meter_draw(int x, int y, int width, int height) {
lcd_fillrect(x + meterwidth, y + height / 2, 3, height / 2 - 1);
}
- /* draw scale */
+ /* draw scale end */
lcd_drawline(x + meterwidth, y,
x + meterwidth, y + height - 2);
- for (i = 0; i < 10; i++) {
- lcd_invertpixel(x + meterwidth * i / 10, y + height / 2 - 1);
+
+ /* draw dots for scale marks */
+ for (i = 0; i < db_scale_count; i++) {
+ /* The x-coordinates of interesting scale mark points
+ have been calculated before */
+ lcd_invertpixel(db_scale_lcd_coord[i], y + height / 2 - 1);
+ }
+
+#ifdef PM_DEBUG
+ /* display a bar to show how many calls to peak_meter_peek
+ have ocurred since the last display */
+ lcd_invertrect(x, y, tmp, 3);
+
+ if (tmp < PEEKS_PER_DRAW_SIZE) {
+ peeks_per_redraw[tmp]++;
+ }
+
+ tmp = current_tick - pm_tick;
+ if (tmp < TICKS_PER_DRAW_SIZE ){
+ ticks_per_redraw[tmp] ++;
}
+ /* display a bar to show how many ticks have passed since
+ the last redraw */
+ lcd_invertrect(x, y + height / 2, current_tick - pm_tick, 2);
+ pm_tick = current_tick;
+#endif
+
last_left = left;
last_right = right;
}
+
+#ifdef PM_DEBUG
+static void peak_meter_clear_histogram(void) {
+ int i = 0;
+ for (i = 0; i < TICKS_PER_DRAW_SIZE; i++) {
+ ticks_per_redraw[i] = (unsigned int)0;
+ }
+
+ for (i = 0; i < PEEKS_PER_DRAW_SIZE; i++) {
+ peeks_per_redraw[i] = (unsigned int)0;
+ }
+}
+
+bool peak_meter_histogram(void) {
+ int i;
+ int btn = BUTTON_NONE;
+ while ((btn & BUTTON_OFF) != BUTTON_OFF )
+ {
+ unsigned int max = 0;
+ int y = 0;
+ int x = 0;
+ lcd_clear_display();
+
+ for (i = 0; i < PEEKS_PER_DRAW_SIZE; i++) {
+ max = MAX(max, peeks_per_redraw[i]);
+ }
+
+ for (i = 0; i < PEEKS_PER_DRAW_SIZE; i++) {
+ x = peeks_per_redraw[i] * (LCD_WIDTH - 1)/ max;
+ lcd_drawline(0, y + i, x, y + i);
+ }
+
+ y = PEEKS_PER_DRAW_SIZE + 1;
+ max = 0;
+
+ for (i = 0; i < TICKS_PER_DRAW_SIZE; i++) {
+ max = MAX(max, ticks_per_redraw[i]);
+ }
+
+ for (i = 0; i < TICKS_PER_DRAW_SIZE; i++) {
+ x = ticks_per_redraw[i] * (LCD_WIDTH - 1)/ max;
+ lcd_drawline(0, y + i, x, y + i);
+ }
+ lcd_update();
+
+ btn = button_get(true);
+ if (btn == BUTTON_PLAY) {
+ peak_meter_clear_histogram();
+ }
+ }
+ return false;
+}
+#endif