summaryrefslogtreecommitdiffstats
path: root/apps/recorder/peakmeter.c
blob: d184641c67be84e764be2d25659391d5fe0f25eb (plain)
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Philipp Pertermann
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "mas.h"
#include "thread.h"
#include "kernel.h"
#include "settings.h"
#include "lcd.h"
#include "wps-display.h"
#include "sprintf.h"
#include "button.h"
#include "system.h"
#include "font.h"
#include "icons.h"
#include "lang.h"
#include "peakmeter.h"

/* buffer the read peak value */
static int peak_meter_max_l;
static int peak_meter_max_r;

/* point in time when peak_meter_max_x becomes invalid */
static long peak_meter_timeout_l;
static long peak_meter_timeout_r;

/* when true a clip has occurred */
static bool peak_meter_l_clip = false;
static bool peak_meter_r_clip = false;

/* point in time when peak_meter_x_oveflow becomes invalid */
static long peak_meter_clip_timeout_l;
static long peak_meter_clip_timeout_r;

/* if set to true clip timeout is disabled */
static bool peak_meter_clip_eternal = false;

#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;

static int peak_meter_l;
static int peak_meter_r;

/* time out values for max */
static long max_time_out[] = {
    0 * HZ, HZ / 5, 30, HZ / 2, HZ, 2 * HZ, 
    3 * HZ, 4 * HZ, 5 * HZ, 6 * HZ, 7 * HZ, 8 * HZ,
    9 * HZ, 10 * HZ, 15 * HZ, 20 * HZ, 30 * HZ, 60 * HZ
};

/* time out values for clip */
static long clip_time_out[] = {
    0 * HZ, 1  * HZ, 2 * HZ, 3 * HZ, 4 * HZ, 5 * HZ, 
    6 * HZ, 7 * HZ, 8 * HZ, 9 * HZ, 10 * HZ, 15 * HZ,
    20 * HZ, 25 * HZ, 30 * HZ, 45 * HZ, 60 * HZ, 90 * HZ,
    120 * HZ, 180 * HZ, 300 * HZ, 600 * HZ, 1200 * HZ,
    2700 * HZ, 5400 * HZ
};

/**
 * Set the source of the peak meter to playback or to 
 * record.
 * @param: bool playback - If true playback peak meter is used.
 *         If false recording peak meter is used.
 */
void peak_meter_playback(bool playback) {
#ifdef SIMULATOR
    (void)playback;
#else
    if (playback) {
        peak_meter_src_l = MAS_REG_DQPEAK_L;
        peak_meter_src_r = MAS_REG_DQPEAK_R;
    } else {
        peak_meter_src_l = MAS_REG_QPEAK_L;
        peak_meter_src_r = MAS_REG_QPEAK_R;
    }
#endif
}

/**
 * Reads peak values from the MAS, and detects clips. The 
 * values are stored in peak_meter_l peak_meter_r for later 
 * evauluation. Consecutive calls to peak_meter_peek detect 
 * that ocurred. This function could be used by a thread for
 * busy reading the MAS.
 */
static void peak_meter_peek(void) {
#ifdef SIMULATOR
    int left = 8000;
    int right = 9000;
#else
   /* read the peak values */
    int left  = mas_codec_readreg(peak_meter_src_l);
    int right = mas_codec_readreg(peak_meter_src_r);
#endif

    /* check for clips
       An clip is assumed when two consecutive readouts
       of the volume are at full scale. This is proven
       to be inaccurate in both ways: it may detect clips
       when no clip occurred and it may fail to detect
       a real clip. */
    if ((left  == peak_meter_l) && 
        (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];
    }

    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];
    }

    /* peaks are searched -> we have to find the maximum */
    peak_meter_l = MAX(peak_meter_l, left);
    peak_meter_r = MAX(peak_meter_r, right);
}

/**
 * Reads out the peak volume of the left channel.
 * @return int - The maximum value that has been detected
 * since the last call of peak_meter_read_l. The value
 * is in the range 0 <= value < MAX_PEAK.
 */
static int peak_meter_read_l (void) {
    int retval = peak_meter_l;
#ifdef SIMULATOR
    peak_meter_l = 8000;
#else
    peak_meter_l = mas_codec_readreg(peak_meter_src_l);
#endif
    return retval;
}

/**
 * Reads out the peak volume of the right channel.
 * @return int - The maximum value that has been detected
 * since the last call of peak_meter_read_l. The value
 * is in the range 0 <= value < MAX_PEAK.
 */
static int peak_meter_read_r (void) {
    int retval = peak_meter_r;
#ifdef SIMULATOR
    peak_meter_l = 8000;
#else
    peak_meter_r = mas_codec_readreg(peak_meter_src_r);
#endif
    return retval;
}

/**
 * Reset the detected clips. This method is for
 * use by the user interface.
 * @param int unused - This parameter was added to
 * make the function compatible with set_int
 */
void peak_meter_set_clip_hold(int time) {
    peak_meter_clip_eternal = false;

    if (time <= 0) {
        peak_meter_l_clip = false;
        peak_meter_r_clip = false;
        peak_meter_clip_eternal = true;
    }
}


/**
 * Draws a peak meter in the specified size at the specified position.
 * @param int x - The x coordinate. 
 *                Make sure that 0 <= x and x + width < LCD_WIDTH
 * @param int y - The y coordinate. 
 *                Make sure that 0 <= y and y + height < LCD_HEIGHT
 * @param int width - The width of the peak meter. Note that for display
 *                    of clips a 3 pixel wide area is used ->
 *                    width > 3
 * @param int height - The height of the peak meter. height > 3
 */
void peak_meter_draw(int x, int y, int width, int height) {
    int left = 0, right = 0;
    static int last_left = 0, last_right = 0;
    int meterwidth = width - 3;
    int i;

    /* 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();


        /* scale the samples */
        left  /= (MAX_PEAK / meterwidth);
        right /= (MAX_PEAK / meterwidth);

        /* apply release */
        left  = MAX(left , last_left  - global_settings.peak_meter_release);
        right = MAX(right, last_right - global_settings.peak_meter_release);

        /* reset max values after timeout */
        if (TIME_AFTER(current_tick, peak_meter_timeout_l)){
            peak_meter_max_l = 0;
        }

        if (TIME_AFTER(current_tick, peak_meter_timeout_r)){
            peak_meter_max_r = 0;
        }

        if (!peak_meter_clip_eternal) {
            if (peak_meter_l_clip && 
                TIME_AFTER(current_tick, peak_meter_clip_timeout_l)){
                peak_meter_l_clip = false;
            }

            if (peak_meter_r_clip && 
                TIME_AFTER(current_tick, peak_meter_clip_timeout_r)){
                peak_meter_r_clip = false;
            }
        }

        /* check for new max values */
        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];
        }

        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];
        }
    }

    /* draw the peak meter */
    lcd_clearrect(x, y, width, height);

    /* draw left */
    lcd_fillrect (x, y, left, height / 2 - 2 );
    if (peak_meter_max_l > 0) {
        lcd_drawline(x + peak_meter_max_l, y, 
                     x + peak_meter_max_l, y + height / 2 - 2 );
    }
    if (peak_meter_l_clip) {
        lcd_fillrect(x + meterwidth, y, 3, height / 2 - 1);
    }

    /* draw right */
    lcd_fillrect(x, y + height / 2 + 1, right, height / 2 - 2);
    if (peak_meter_max_r > 0) {
        lcd_drawline( x + peak_meter_max_r, y + height / 2, 
                      x + peak_meter_max_r, y + height - 2);
    }
    if (peak_meter_r_clip) {
        lcd_fillrect(x + meterwidth, y + height / 2, 3, height / 2 - 1);
    }

    /* draw scale */
    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);
    }

    last_left = left;
    last_right = right;
}