diff options
Diffstat (limited to 'apps/plugins')
-rw-r--r-- | apps/plugins/CATEGORIES | 1 | ||||
-rw-r--r-- | apps/plugins/SOURCES | 4 | ||||
-rw-r--r-- | apps/plugins/pitch_detector.c | 1055 |
3 files changed, 1060 insertions, 0 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 3dc11b636e..ae764ca125 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES | |||
@@ -58,6 +58,7 @@ pacbox,games | |||
58 | pdbox,viewers | 58 | pdbox,viewers |
59 | pegbox,games | 59 | pegbox,games |
60 | pictureflow,demos | 60 | pictureflow,demos |
61 | pitch_detector,apps | ||
61 | plasma,demos | 62 | plasma,demos |
62 | png,viewers | 63 | png,viewers |
63 | pong,games | 64 | pong,games |
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 95ffb1c9de..b9061ee74b 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES | |||
@@ -29,6 +29,10 @@ firmware_flash.c | |||
29 | rockbox_flash.c | 29 | rockbox_flash.c |
30 | #endif /* CONFIG_CPU */ | 30 | #endif /* CONFIG_CPU */ |
31 | 31 | ||
32 | #if (CONFIG_CODEC == SWCODEC) && defined(HAVE_RECORDING) && \ | ||
33 | (defined(HAVE_LINE_IN) || defined(HAVE_MIC_IN)) | ||
34 | pitch_detector.c | ||
35 | #endif | ||
32 | 36 | ||
33 | #if (CONFIG_CODEC == SWCODEC) || !defined(SIMULATOR) | 37 | #if (CONFIG_CODEC == SWCODEC) || !defined(SIMULATOR) |
34 | metronome.c | 38 | metronome.c |
diff --git a/apps/plugins/pitch_detector.c b/apps/plugins/pitch_detector.c new file mode 100644 index 0000000000..8d0b3b7957 --- /dev/null +++ b/apps/plugins/pitch_detector.c | |||
@@ -0,0 +1,1055 @@ | |||
1 | /************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id:$ | ||
9 | * | ||
10 | * Copyright (C) 2008 Lechner Michael / smoking gnu | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | * ---------------------------------------------------------------------------- | ||
19 | * | ||
20 | * INTRODUCTION: | ||
21 | * OK, this is an attempt to write an instrument tuner for rockbox. | ||
22 | * It uses a Schmitt trigger algorithm, which I copied from | ||
23 | * tuneit [ (c) 2004 Mario Lang <mlang@delysid.org> ], for detecting the | ||
24 | * fundamental freqency of a sound. A FFT algorithm would be more accurate | ||
25 | * but also much slower. | ||
26 | * | ||
27 | * TODO: | ||
28 | * - Find someone who knows how recording actually works, and rewrite the | ||
29 | * recording code to use proper, gapless recording with a callback function | ||
30 | * that provides new buffer, instead of stopping and restarting recording | ||
31 | * everytime the buffer is full | ||
32 | * - Convert all floating point operations to fixed-point | ||
33 | * - Adapt the Yin FFT algorithm, which would reduce complexity from O(n^2) | ||
34 | * to O(nlogn), theoretically reducing latency by a factor of ~10. -David | ||
35 | * | ||
36 | * MAJOR CHANGES: | ||
37 | * 08.03.2008 Started coding | ||
38 | * 21.03.2008 Pitch detection works more or less | ||
39 | * Button definitions for most targets added | ||
40 | * 02.04.2008 Proper GUI added | ||
41 | * Todo, Major Changes and Current Limitations added | ||
42 | * 08.19.2009 Brought the code up to date with current plugin standards | ||
43 | * Made it work more nicely with color, BW and grayscale | ||
44 | * Changed pitch detection to use the Yin algorithm (better | ||
45 | * detection, but slower -- would be ~4x faster with | ||
46 | * fixed point math, I think). Code was poached from the | ||
47 | * Aubio sound processing library (aubio.org). -David | ||
48 | * 08.31.2009 Lots of changes: | ||
49 | * Added a menu to tweak settings | ||
50 | * Converted everything to fixed point (greatly improving | ||
51 | * latency) | ||
52 | * Improved the display | ||
53 | * Improved efficiency with judicious use of cpu_boost, the | ||
54 | * backlight, and volume detection to limit unneeded | ||
55 | * calculation | ||
56 | * Fixed a problem that caused an octave-off error | ||
57 | * -David | ||
58 | * | ||
59 | * | ||
60 | * CURRENT LIMITATIONS: | ||
61 | * - No gapless recording. Strictly speaking true gappless isn't possible, | ||
62 | * since the algorithm takes longer to calculate than the length of the | ||
63 | * sample, but latency could be improved a bit with proper use of the DMA | ||
64 | * recording functions. | ||
65 | * - Due to how the Yin algorithm works, latency is higher for lower | ||
66 | * frequencies. | ||
67 | */ | ||
68 | |||
69 | #include "plugin.h" | ||
70 | #include "lib/pluginlib_actions.h" | ||
71 | |||
72 | PLUGIN_HEADER | ||
73 | |||
74 | /* First figure out what sample rate we're going to use */ | ||
75 | #if (REC_SAMPR_CAPS & SAMPR_CAP_44) | ||
76 | #define SAMPLE_RATE SAMPR_44 | ||
77 | #elif (REC_SAMPR_CAPS & SAMPR_CAP_22) | ||
78 | #define SAMPLE_RATE SAMPR_22 | ||
79 | #elif (REC_SAMPR_CAPS & SAMPR_CAP_11) | ||
80 | #define SAMPLE_RATE SAMPR_11 | ||
81 | #endif | ||
82 | |||
83 | /* Some fixed point calculation stuff */ | ||
84 | typedef int32_t fixed_data; | ||
85 | struct _fixed | ||
86 | { | ||
87 | fixed_data a; | ||
88 | }; | ||
89 | typedef struct _fixed fixed; | ||
90 | #define FIXED_PRECISION 18 | ||
91 | #define FP_MAX ((fixed) {0x7fffffff}) | ||
92 | #define FP_MIN ((fixed) {-0x80000000}) | ||
93 | #define int2fixed(x) ((fixed){(x) << FIXED_PRECISION}) | ||
94 | #define int2mantissa(x) ((fixed){x}) | ||
95 | #define fixed2int(x) ((int)((x).a >> FIXED_PRECISION)) | ||
96 | #define fixed2float(x) (((float)(x).a) / ((float)(1 << FIXED_PRECISION))) | ||
97 | #define float2fixed(x) \ | ||
98 | ((fixed){(fixed_data)(x * (float)(1 << FIXED_PRECISION))}) | ||
99 | /* I adapted these ones from the Rockbox fixed point library */ | ||
100 | #define fp_mul(x, y) \ | ||
101 | ((fixed){(((int64_t)((x).a)) * ((int64_t)((y).a))) >> (FIXED_PRECISION)}) | ||
102 | #define fp_div(x, y) \ | ||
103 | ((fixed){(((int64_t)((x).a)) << (FIXED_PRECISION)) / ((int64_t)((y).a))}) | ||
104 | /* Operators for fixed point */ | ||
105 | #define fp_add(x, y) ((fixed){(x).a + (y).a}) | ||
106 | #define fp_sub(x, y) ((fixed){(x).a - (y).a}) | ||
107 | #define fp_shl(x, y) ((fixed){(x).a << y}) | ||
108 | #define fp_shr(x, y) ((fixed){(x).a >> y}) | ||
109 | #define fp_neg(x) ((fixed){-(x).a}) | ||
110 | #define fp_gt(x, y) ((x).a > (y).a) | ||
111 | #define fp_gte(x, y) ((x).a >= (y).a) | ||
112 | #define fp_lt(x, y) ((x).a < (y).a) | ||
113 | #define fp_lte(x, y) ((x).a <= (y).a) | ||
114 | #define fp_sqr(x) fp_mul((x), (x)) | ||
115 | #define fp_equal(x, y) ((x).a == (y).a) | ||
116 | #define fp_round(x) (fixed2int(fp_add((x), float2fixed(0.5)))) | ||
117 | #define fp_data(x) ((x).a) | ||
118 | #define fp_frac(x) (fp_sub((x), int2fixed(fixed2int(x)))) | ||
119 | #define FP_ZERO ((fixed){0}) | ||
120 | #define FP_LOW ((fixed){1}) | ||
121 | |||
122 | /* Some defines for converting between period and frequency */ | ||
123 | /* I introduce some divisors in this because the fixed point */ | ||
124 | /* variables aren't big enough to hold higher than a certain */ | ||
125 | /* vallue. This loses a bit of precision but it means we */ | ||
126 | /* don't have to use 32.32 variables (yikes). */ | ||
127 | /* With an 18-bit decimal precision, the max value in the */ | ||
128 | /* integer part is 8192. Divide 44100 by 7 and it'll fit in */ | ||
129 | /* that variable. */ | ||
130 | #define fp_period2freq(x) fp_div(int2fixed(SAMPLE_RATE / 7), \ | ||
131 | fp_div((x),int2fixed(7))) | ||
132 | #define fp_freq2period(x) fp_period2freq(x) | ||
133 | #define period2freq(x) (SAMPLE_RATE / (x)) | ||
134 | #define freq2period(x) period2freq(x) | ||
135 | |||
136 | #define sqr(x) ((x)*(x)) | ||
137 | |||
138 | /* Some constants for tuning */ | ||
139 | #define A_FREQ float2fixed(440.0f) | ||
140 | #define D_NOTE float2fixed(1.059463094359f) | ||
141 | #define LOG_D_NOTE float2fixed(1.0f/12.0f) | ||
142 | #define D_NOTE_SQRT float2fixed(1.029302236643f) | ||
143 | #define LOG_2 float2fixed(1.0f) | ||
144 | |||
145 | /* The recording buffer size */ | ||
146 | /* This is how much is sampled at a time. */ | ||
147 | /* It also determines latency -- if BUFFER_SIZE == SAMPLE_RATE then */ | ||
148 | /* there'll be one sample per second, or a latency of one second. */ | ||
149 | /* Furthermore, the lowest detectable frequency will be about twice */ | ||
150 | /* the number of reads per second */ | ||
151 | /* If we ever switch to Yin FFT algorithm then this needs to be | ||
152 | a power of 2 */ | ||
153 | #define BUFFER_SIZE 4096 | ||
154 | #define SAMPLE_SIZE 4096 | ||
155 | #define SAMPLE_SIZE_MIN 1024 | ||
156 | #define YIN_BUFFER_SIZE (BUFFER_SIZE / 4) | ||
157 | |||
158 | #define LCD_FACTOR (fp_div(int2fixed(LCD_WIDTH), int2fixed(100))) | ||
159 | /* The threshold for the YIN algorithm */ | ||
160 | #define YIN_THRESHOLD float2fixed(0.05f) | ||
161 | |||
162 | /* How loud the audio has to be to start displaying pitch */ | ||
163 | /* Must be between 0 and 100 */ | ||
164 | #define VOLUME_THRESHOLD (50) | ||
165 | /* Change to AUDIO_SRC_LINEIN if you want to record from line-in */ | ||
166 | #define INPUT_TYPE AUDIO_SRC_MIC | ||
167 | |||
168 | /* How many decimal places to display for the Hz value */ | ||
169 | #define DISPLAY_HZ_PRECISION 100 | ||
170 | |||
171 | typedef signed short audio_sample_type; | ||
172 | /* It's stereo, so make the buffer twice as big */ | ||
173 | audio_sample_type audio_data[BUFFER_SIZE]; | ||
174 | fixed yin_buffer[YIN_BUFFER_SIZE]; | ||
175 | static int recording=0; | ||
176 | |||
177 | /* Frequencies of all the notes of the scale */ | ||
178 | static const fixed freqs[12] = | ||
179 | { | ||
180 | float2fixed(440.0f), /* A */ | ||
181 | float2fixed(466.1637615f), /* A# */ | ||
182 | float2fixed(493.8833013f), /* etc... */ | ||
183 | float2fixed(523.2511306f), | ||
184 | float2fixed(554.3652620f), | ||
185 | float2fixed(587.3295358f), | ||
186 | float2fixed(622.2539674f), | ||
187 | float2fixed(659.2551138f), | ||
188 | float2fixed(698.4564629f), | ||
189 | float2fixed(739.9888454f), | ||
190 | float2fixed(783.9908720f), | ||
191 | float2fixed(830.6093952f), | ||
192 | }; | ||
193 | /* logarithm of all the notes of the scale */ | ||
194 | static const fixed lfreqs[12] = | ||
195 | { | ||
196 | float2fixed(8.781359714f), | ||
197 | float2fixed(8.864693047f), | ||
198 | float2fixed(8.948026380f), | ||
199 | float2fixed(9.031359714f), | ||
200 | float2fixed(9.114693047f), | ||
201 | float2fixed(9.198026380f), | ||
202 | float2fixed(9.281359714f), | ||
203 | float2fixed(9.364693047f), | ||
204 | float2fixed(9.448026380f), | ||
205 | float2fixed(9.531359714f), | ||
206 | float2fixed(9.614693047f), | ||
207 | float2fixed(9.698026380f), | ||
208 | }; | ||
209 | |||
210 | /* GUI */ | ||
211 | static unsigned back_color, front_color; | ||
212 | static int font_w, font_h; | ||
213 | static int bar_x_minus_50, bar_x_minus_20, bar_x_0, bar_x_20, bar_x_50; | ||
214 | static int letter_y; /* y of the notes letters */ | ||
215 | static int note_bar_y; /* y of the yellow bars (under the notes) */ | ||
216 | static int bar_h; /* height of the yellow (note) and red (error) bars */ | ||
217 | static int freq_y; /* y of the line with the frequency */ | ||
218 | static int error_ticks_y; /* y of the error values (-50, -20, ...) */ | ||
219 | static int error_hline_y; /* y of the top line for error bar */ | ||
220 | static int error_hline_y2;/* y of the bottom line for error bar */ | ||
221 | static int error_bar_y; /* y of the error bar */ | ||
222 | static int error_bar_margin; /* distance between the error bar and the hline */ | ||
223 | |||
224 | static const char *english_notes[12] = {"A","A#","B","C","C#","D","D#","E", | ||
225 | "F","F#", "G", "G#"}; | ||
226 | static const char gui_letters[8] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', '#'}; | ||
227 | static const char **notes = english_notes; | ||
228 | |||
229 | /*Settings for the plugin */ | ||
230 | |||
231 | struct tuner_settings | ||
232 | { | ||
233 | unsigned volume_threshold; | ||
234 | unsigned record_gain; | ||
235 | unsigned sample_size; | ||
236 | unsigned lowest_freq; | ||
237 | unsigned yin_threshold; | ||
238 | } tuner_settings; | ||
239 | |||
240 | /*=================================================================*/ | ||
241 | /* MENU */ | ||
242 | /*=================================================================*/ | ||
243 | |||
244 | /* Keymaps */ | ||
245 | const struct button_mapping* plugin_contexts[]={ | ||
246 | generic_actions, | ||
247 | generic_increase_decrease, | ||
248 | generic_directions, | ||
249 | #if NB_SCREENS == 2 | ||
250 | remote_directions | ||
251 | #endif | ||
252 | }; | ||
253 | #define PLA_ARRAY_COUNT sizeof(plugin_contexts)/sizeof(plugin_contexts[0]) | ||
254 | |||
255 | fixed yin_threshold_table[] = | ||
256 | { | ||
257 | float2fixed(0.01), | ||
258 | float2fixed(0.02), | ||
259 | float2fixed(0.03), | ||
260 | float2fixed(0.04), | ||
261 | float2fixed(0.05), | ||
262 | float2fixed(0.10), | ||
263 | float2fixed(0.15), | ||
264 | float2fixed(0.20), | ||
265 | float2fixed(0.25), | ||
266 | float2fixed(0.30), | ||
267 | float2fixed(0.35), | ||
268 | float2fixed(0.40), | ||
269 | float2fixed(0.45), | ||
270 | float2fixed(0.50), | ||
271 | }; | ||
272 | |||
273 | #define DEFAULT_YIN_THRESHOLD 5 /* 0.10 */ | ||
274 | |||
275 | /* Option strings */ | ||
276 | static const struct opt_items yin_threshold_text[] = | ||
277 | { | ||
278 | { "0.01", -1 }, | ||
279 | { "0.02", -1 }, | ||
280 | { "0.03", -1 }, | ||
281 | { "0.04", -1 }, | ||
282 | { "0.05", -1 }, | ||
283 | { "0.10", -1 }, | ||
284 | { "0.15", -1 }, | ||
285 | { "0.20", -1 }, | ||
286 | { "0.25", -1 }, | ||
287 | { "0.30", -1 }, | ||
288 | { "0.35", -1 }, | ||
289 | { "0.40", -1 }, | ||
290 | { "0.45", -1 }, | ||
291 | { "0.50", -1 }, | ||
292 | }; | ||
293 | |||
294 | void set_min_freq(int new_freq) | ||
295 | { | ||
296 | tuner_settings.sample_size = freq2period(new_freq) * 4; | ||
297 | |||
298 | /* clamp the sample size between min and max */ | ||
299 | if(tuner_settings.sample_size <= SAMPLE_SIZE_MIN) | ||
300 | tuner_settings.sample_size = SAMPLE_SIZE_MIN; | ||
301 | else if(tuner_settings.sample_size >= BUFFER_SIZE) | ||
302 | tuner_settings.sample_size = BUFFER_SIZE; | ||
303 | /* sample size must be divisible by 4 */ | ||
304 | else if(tuner_settings.sample_size % 4 != 0) | ||
305 | tuner_settings.sample_size += 4 - (tuner_settings.sample_size % 4); | ||
306 | } | ||
307 | |||
308 | bool main_menu(void) | ||
309 | { | ||
310 | int selection=0; | ||
311 | bool done = false; | ||
312 | bool exit_tuner=false; | ||
313 | int choice; | ||
314 | |||
315 | MENUITEM_STRINGLIST(menu,"Tuner Settings",NULL, | ||
316 | "Return to Tuner", | ||
317 | "Volume Threshold", | ||
318 | "Listening Volume", | ||
319 | "Lowest Frequency", | ||
320 | "Algorithm Pickiness", | ||
321 | "Quit"); | ||
322 | |||
323 | while(!done) | ||
324 | { | ||
325 | choice = rb->do_menu(&menu, &selection, NULL, false); | ||
326 | switch(choice) | ||
327 | { | ||
328 | case 1: | ||
329 | rb->set_int("Volume Threshold", "%", UNIT_INT, | ||
330 | &tuner_settings.volume_threshold, | ||
331 | NULL, 5, 5, 95, NULL); | ||
332 | break; | ||
333 | case 2: | ||
334 | rb->set_int("Listening Volume", "%", UNIT_INT, | ||
335 | &tuner_settings.record_gain, | ||
336 | NULL, 1, rb->sound_min(SOUND_MIC_GAIN), | ||
337 | rb->sound_max(SOUND_MIC_GAIN), NULL); | ||
338 | break; | ||
339 | case 3: | ||
340 | rb->set_int("Lowest Frequency", "Hz", UNIT_INT, | ||
341 | &tuner_settings.lowest_freq, set_min_freq, 1, | ||
342 | /* Range depends on the size of the buffer */ | ||
343 | SAMPLE_RATE / (BUFFER_SIZE / 4), | ||
344 | SAMPLE_RATE / (SAMPLE_SIZE_MIN / 4), NULL); | ||
345 | break; | ||
346 | case 4: | ||
347 | rb->set_option( | ||
348 | "Algorithm Pickiness (Lower -> more discriminating)", | ||
349 | &tuner_settings.yin_threshold, | ||
350 | INT, yin_threshold_text, | ||
351 | sizeof(yin_threshold_text) / | ||
352 | sizeof(yin_threshold_text[0]), | ||
353 | NULL); | ||
354 | break; | ||
355 | case 5: | ||
356 | exit_tuner = true; | ||
357 | done = true; | ||
358 | break; | ||
359 | case 0: | ||
360 | default: /* Return to the tuner */ | ||
361 | done = true; | ||
362 | break; | ||
363 | |||
364 | } | ||
365 | } | ||
366 | return(exit_tuner); | ||
367 | } | ||
368 | |||
369 | /*=================================================================*/ | ||
370 | /* Settings loading and saving(adapted from the clock plugin) */ | ||
371 | /*=================================================================*/ | ||
372 | |||
373 | #define SETTINGS_FILENAME PLUGIN_APPS_DIR "/.pitch_settings" | ||
374 | |||
375 | enum message | ||
376 | { | ||
377 | MESSAGE_LOADING, | ||
378 | MESSAGE_LOADED, | ||
379 | MESSAGE_ERRLOAD, | ||
380 | MESSAGE_SAVING, | ||
381 | MESSAGE_SAVED, | ||
382 | MESSAGE_ERRSAVE | ||
383 | }; | ||
384 | |||
385 | enum settings_file_status | ||
386 | { | ||
387 | LOADED, ERRLOAD, | ||
388 | SAVED, ERRSAVE | ||
389 | }; | ||
390 | |||
391 | /* The settings as they exist on the hard disk, so that | ||
392 | * we can know at saving time if changes have been made */ | ||
393 | struct tuner_settings hdd_tuner_settings; | ||
394 | |||
395 | /*---------------------------------------------------------------------*/ | ||
396 | |||
397 | bool settings_needs_saving(struct tuner_settings* settings) | ||
398 | { | ||
399 | return(rb->memcmp(settings, &hdd_tuner_settings, sizeof(*settings))); | ||
400 | } | ||
401 | |||
402 | /*---------------------------------------------------------------------*/ | ||
403 | |||
404 | void tuner_settings_reset(struct tuner_settings* settings) | ||
405 | { | ||
406 | settings->volume_threshold = VOLUME_THRESHOLD; | ||
407 | settings->record_gain = rb->global_settings->rec_mic_gain; | ||
408 | settings->sample_size = BUFFER_SIZE; | ||
409 | settings->lowest_freq = period2freq(BUFFER_SIZE / 4); | ||
410 | settings->yin_threshold = DEFAULT_YIN_THRESHOLD; | ||
411 | } | ||
412 | |||
413 | /*---------------------------------------------------------------------*/ | ||
414 | |||
415 | enum settings_file_status tuner_settings_load(struct tuner_settings* settings, | ||
416 | char* filename) | ||
417 | { | ||
418 | int fd = rb->open(filename, O_RDONLY); | ||
419 | if(fd >= 0){ /* does file exist? */ | ||
420 | /* basic consistency check */ | ||
421 | if(rb->filesize(fd) == sizeof(*settings)){ | ||
422 | rb->read(fd, settings, sizeof(*settings)); | ||
423 | rb->close(fd); | ||
424 | rb->memcpy(&hdd_tuner_settings, settings, sizeof(*settings)); | ||
425 | return(LOADED); | ||
426 | } | ||
427 | } | ||
428 | /* Initializes the settings with default values at least */ | ||
429 | tuner_settings_reset(settings); | ||
430 | return(ERRLOAD); | ||
431 | } | ||
432 | |||
433 | /*---------------------------------------------------------------------*/ | ||
434 | |||
435 | enum settings_file_status tuner_settings_save(struct tuner_settings* settings, | ||
436 | char* filename) | ||
437 | { | ||
438 | int fd = rb->creat(filename); | ||
439 | if(fd >= 0){ /* does file exist? */ | ||
440 | rb->write (fd, settings, sizeof(*settings)); | ||
441 | rb->close(fd); | ||
442 | return(SAVED); | ||
443 | } | ||
444 | return(ERRSAVE); | ||
445 | } | ||
446 | |||
447 | /*---------------------------------------------------------------------*/ | ||
448 | |||
449 | void load_settings(void) | ||
450 | { | ||
451 | tuner_settings_load(&tuner_settings, SETTINGS_FILENAME); | ||
452 | |||
453 | rb->storage_sleep(); | ||
454 | } | ||
455 | |||
456 | /*---------------------------------------------------------------------*/ | ||
457 | |||
458 | void save_settings(void) | ||
459 | { | ||
460 | if(!settings_needs_saving(&tuner_settings)) | ||
461 | return; | ||
462 | |||
463 | tuner_settings_save(&tuner_settings, SETTINGS_FILENAME); | ||
464 | } | ||
465 | |||
466 | /*=================================================================*/ | ||
467 | /* Binary Log */ | ||
468 | /*=================================================================*/ | ||
469 | |||
470 | /* Fixed-point log base 2*/ | ||
471 | /* Adapted from python code at | ||
472 | http://en.wikipedia.org/wiki/Binary_logarithm#Algorithm | ||
473 | */ | ||
474 | fixed log(fixed inp) | ||
475 | { | ||
476 | fixed x = inp; | ||
477 | fixed fp = int2fixed(1); | ||
478 | fixed res = int2fixed(0); | ||
479 | |||
480 | if(fp_lte(x, FP_ZERO)) | ||
481 | { | ||
482 | return FP_MIN; | ||
483 | } | ||
484 | |||
485 | /* Integer part*/ | ||
486 | /* while x<1 */ | ||
487 | while(fp_lt(x, int2fixed(1))) | ||
488 | { | ||
489 | res = fp_sub(res, int2fixed(1)); | ||
490 | x = fp_shl(x, 1); | ||
491 | } | ||
492 | /* while x>=2 */ | ||
493 | while(fp_gte(x, int2fixed(2))) | ||
494 | { | ||
495 | res = fp_add(res, int2fixed(1)); | ||
496 | x = fp_shr(x, 1); | ||
497 | } | ||
498 | |||
499 | /* Fractional part */ | ||
500 | /* while fp > 0 */ | ||
501 | while(fp_gt(fp, FP_ZERO)) | ||
502 | { | ||
503 | fp = fp_shr(fp, 1); | ||
504 | x = fp_mul(x, x); | ||
505 | /* if x >= 2 */ | ||
506 | if(fp_gte(x, int2fixed(2))) | ||
507 | { | ||
508 | x = fp_shr(x, 1); | ||
509 | res = fp_add(res, fp); | ||
510 | } | ||
511 | } | ||
512 | |||
513 | return res; | ||
514 | } | ||
515 | |||
516 | /*=================================================================*/ | ||
517 | /* GUI Stuff */ | ||
518 | /*=================================================================*/ | ||
519 | |||
520 | /* The function name is pretty self-explaining ;) */ | ||
521 | void print_int_xy(int x, int y, int v) | ||
522 | { | ||
523 | char temp[20]; | ||
524 | |||
525 | rb->lcd_set_foreground(front_color); | ||
526 | rb->snprintf(temp,20,"%d",v); | ||
527 | rb->lcd_putsxy(x,y,temp); | ||
528 | rb->lcd_update(); | ||
529 | } | ||
530 | |||
531 | /* Print out the frequency etc - will be removed later on */ | ||
532 | void print_str(char* s) | ||
533 | { | ||
534 | rb->lcd_set_foreground(front_color); | ||
535 | rb->lcd_putsxy(0, freq_y, s); | ||
536 | rb->lcd_update(); | ||
537 | } | ||
538 | |||
539 | /* What can I say? Read the function name... */ | ||
540 | void print_char_xy(int x, int y, char c) | ||
541 | { | ||
542 | char temp[2]; | ||
543 | |||
544 | temp[0]=c; | ||
545 | temp[1]=0; | ||
546 | rb->lcd_set_foreground(front_color); | ||
547 | |||
548 | rb->lcd_putsxy(x, y, temp); | ||
549 | } | ||
550 | |||
551 | /* Draw the red bar and the white lines */ | ||
552 | void draw_bar(fixed wrong_by_cents) | ||
553 | { | ||
554 | #ifdef HAVE_LCD_COLOR | ||
555 | rb->lcd_set_foreground(LCD_RGBPACK(255,0,0)); /* Color screens */ | ||
556 | #elif LCD_DEPTH > 1 | ||
557 | rb->lcd_set_foreground(LCD_DARKGRAY); /* Greyscale screens */ | ||
558 | #else | ||
559 | rb->lcd_set_foreground(LCD_BLACK); /* Black and white screens */ | ||
560 | #endif | ||
561 | |||
562 | if (fp_gt(wrong_by_cents, FP_ZERO)) | ||
563 | { | ||
564 | rb->lcd_fillrect(bar_x_0, error_bar_y, | ||
565 | fixed2int(fp_mul(wrong_by_cents, LCD_FACTOR)), bar_h); | ||
566 | } | ||
567 | else | ||
568 | { | ||
569 | rb->lcd_fillrect(bar_x_0 + fixed2int(fp_mul(wrong_by_cents,LCD_FACTOR)), | ||
570 | error_bar_y, | ||
571 | fixed2int(fp_mul(wrong_by_cents, LCD_FACTOR)) * -1, | ||
572 | bar_h); | ||
573 | } | ||
574 | #ifdef HAVE_LCD_COLOR | ||
575 | rb->lcd_set_foreground(LCD_RGBPACK(255,255,255)); /* Color screens */ | ||
576 | #elif LCD_DEPTH > 1 | ||
577 | rb->lcd_set_foreground(LCD_BLACK); /* Greyscale screens */ | ||
578 | #else | ||
579 | rb->lcd_set_foreground(LCD_BLACK); /* Black and white screens */ | ||
580 | #endif | ||
581 | |||
582 | rb->lcd_hline(0,LCD_WIDTH-1, error_hline_y); | ||
583 | rb->lcd_hline(0,LCD_WIDTH-1, error_hline_y2); | ||
584 | rb->lcd_vline(LCD_WIDTH / 2, error_hline_y, error_hline_y2); | ||
585 | |||
586 | print_int_xy(bar_x_minus_50 , error_ticks_y, -50); | ||
587 | print_int_xy(bar_x_minus_20 , error_ticks_y, -20); | ||
588 | print_int_xy(bar_x_0 , error_ticks_y, 0); | ||
589 | print_int_xy(bar_x_20 , error_ticks_y, 20); | ||
590 | print_int_xy(bar_x_50 , error_ticks_y, 50); | ||
591 | } | ||
592 | |||
593 | /* Print the letters A-G and the # on the screen */ | ||
594 | void draw_letters(void) | ||
595 | { | ||
596 | int i; | ||
597 | |||
598 | rb->lcd_set_foreground(front_color); | ||
599 | |||
600 | for (i=0; i<8; i++) | ||
601 | { | ||
602 | print_char_xy(i*(LCD_WIDTH / 8 ) + font_w, letter_y, gui_letters[i]); | ||
603 | } | ||
604 | } | ||
605 | |||
606 | /* Draw the yellow point(s) below the letters and the '#' */ | ||
607 | void draw_points(const char *s) | ||
608 | { | ||
609 | int i; | ||
610 | |||
611 | i = s[0]-'A'; | ||
612 | #ifdef HAVE_LCD_COLOR | ||
613 | rb->lcd_set_foreground(LCD_RGBPACK(255,255,0)); /* Color screens */ | ||
614 | #elif LCD_DEPTH > 1 | ||
615 | rb->lcd_set_foreground(LCD_DARKGRAY); /* Grey screens */ | ||
616 | #else | ||
617 | rb->lcd_set_foreground(LCD_BLACK); /* Black and White screens */ | ||
618 | #endif | ||
619 | |||
620 | rb->lcd_fillrect(i*(LCD_WIDTH / 8 ) + font_w, note_bar_y, font_w, font_h); | ||
621 | |||
622 | if (s[1] == '#') | ||
623 | rb->lcd_fillrect(7*(LCD_WIDTH / 8 ) + font_w, note_bar_y, font_w, font_h); | ||
624 | } | ||
625 | |||
626 | /* Calculate how wrong the note is and draw the GUI */ | ||
627 | void display_frequency (fixed freq) | ||
628 | { | ||
629 | fixed ldf, mldf; | ||
630 | fixed lfreq, nfreq; | ||
631 | int i, note = 0; | ||
632 | bool draw_freq; | ||
633 | char str_buf[30]; | ||
634 | |||
635 | if (fp_lt(freq, FP_LOW)) | ||
636 | freq = FP_LOW; | ||
637 | lfreq = log(freq); | ||
638 | |||
639 | /* Get the frequency to within the range of our reference table */ | ||
640 | while (fp_lt(lfreq, fp_sub(lfreqs[0], fp_shr(LOG_D_NOTE, 1)))) | ||
641 | lfreq = fp_add(lfreq, LOG_2); | ||
642 | while (fp_gte(lfreq, fp_sub(fp_add(lfreqs[0], LOG_2), | ||
643 | fp_shr(LOG_D_NOTE, 1)))) | ||
644 | lfreq = fp_sub(lfreq, LOG_2); | ||
645 | mldf = LOG_D_NOTE; | ||
646 | for (i=0; i<12; i++) | ||
647 | { | ||
648 | ldf = fp_gt(fp_sub(lfreq,lfreqs[i]), FP_ZERO) ? | ||
649 | fp_sub(lfreq,lfreqs[i]) : fp_neg(fp_sub(lfreq,lfreqs[i])); | ||
650 | if (fp_lt(ldf, mldf)) | ||
651 | { | ||
652 | mldf = ldf; | ||
653 | note = i; | ||
654 | } | ||
655 | } | ||
656 | nfreq = freqs[note]; | ||
657 | while (fp_gt(fp_div(nfreq, freq), D_NOTE_SQRT)) | ||
658 | nfreq = fp_shr(nfreq, 1); | ||
659 | while (fp_gt(fp_div(freq, nfreq), D_NOTE_SQRT)) | ||
660 | { | ||
661 | nfreq = fp_shl(nfreq, 1); | ||
662 | } | ||
663 | |||
664 | ldf=fp_mul(int2fixed(1200), log(fp_div(freq,nfreq))); | ||
665 | |||
666 | draw_freq = (fp_round(freq) != 0); | ||
667 | if (draw_freq) | ||
668 | { | ||
669 | rb->snprintf(str_buf,30, "%s : %d cents (%d.%dHz)", | ||
670 | notes[note], fp_round(ldf) ,fixed2int(freq), | ||
671 | fp_round(fp_mul(fp_frac(freq), | ||
672 | int2fixed(DISPLAY_HZ_PRECISION)))); | ||
673 | } | ||
674 | else | ||
675 | { | ||
676 | ldf = FP_ZERO; /* prevents red bar at -32 cents when freq= 0*/ | ||
677 | } | ||
678 | |||
679 | rb->lcd_clear_display(); | ||
680 | draw_bar(ldf); /* The red bar */ | ||
681 | draw_letters(); /* The A-G letters and the # */ | ||
682 | if (draw_freq) | ||
683 | { | ||
684 | draw_points(notes[note]); /* The yellow point(s) */ | ||
685 | print_str(str_buf); | ||
686 | } | ||
687 | rb->lcd_update(); | ||
688 | } | ||
689 | |||
690 | /*----------------------------------------------------------------------- | ||
691 | * Functions for the Yin algorithm | ||
692 | * | ||
693 | * These were all adapted from the versions in Aubio v0.3.2 | ||
694 | * Here's what the Aubio documentation has to say: | ||
695 | * | ||
696 | * This algorithm was developped by A. de Cheveigne and H. Kawahara and | ||
697 | * published in: | ||
698 | * | ||
699 | * de Cheveign?, A., Kawahara, H. (2002) "YIN, a fundamental frequency | ||
700 | * estimator for speech and music", J. Acoust. Soc. Am. 111, 1917-1930. | ||
701 | * | ||
702 | * see http://recherche.ircam.fr/equipes/pcm/pub/people/cheveign.html | ||
703 | -------------------------------------------------------------------------*/ | ||
704 | |||
705 | /* Find the index of the minimum element of an array of floats */ | ||
706 | unsigned vec_min_elem(fixed *s, unsigned buflen) | ||
707 | { | ||
708 | unsigned j, pos=0.0f; | ||
709 | fixed tmp = s[0]; | ||
710 | for (j=0; j < buflen; j++) | ||
711 | { | ||
712 | if(fp_gt(tmp, s[j])) | ||
713 | { | ||
714 | pos = j; | ||
715 | tmp = s[j]; | ||
716 | } | ||
717 | } | ||
718 | return pos; | ||
719 | } | ||
720 | |||
721 | |||
722 | fixed aubio_quadfrac(fixed s0, fixed s1, fixed s2, fixed pf) | ||
723 | { | ||
724 | /* Original floating point version: */ | ||
725 | /* tmp = s0 + (pf/2.0f) * (pf * ( s0 - 2.0f*s1 + s2 ) - | ||
726 | 3.0f*s0 + 4.0f*s1 - s2);*/ | ||
727 | /* Converted to explicit operator precedence: */ | ||
728 | /* tmp = s0 + ((pf/2.0f) * ((((pf * ((s0 - (2*s1)) + s2)) - | ||
729 | (3*s0)) + (4*s1)) - s2)); */ | ||
730 | |||
731 | /* I made it look like this so I could easily track the precedence and */ | ||
732 | /* make sure it matched the original expression */ | ||
733 | /* Oy, this is when I really wish I could do C++ operator overloading */ | ||
734 | fixed tmp = fp_add | ||
735 | ( | ||
736 | s0, | ||
737 | fp_mul | ||
738 | ( | ||
739 | fp_shr(pf, 1), | ||
740 | fp_sub | ||
741 | ( | ||
742 | fp_add | ||
743 | ( | ||
744 | fp_sub | ||
745 | ( | ||
746 | fp_mul | ||
747 | ( | ||
748 | pf, | ||
749 | fp_add | ||
750 | ( | ||
751 | fp_sub | ||
752 | ( | ||
753 | s0, | ||
754 | fp_shl(s1, 1) | ||
755 | ), | ||
756 | s2 | ||
757 | ) | ||
758 | ), | ||
759 | fp_mul | ||
760 | ( | ||
761 | float2fixed(3.0f), | ||
762 | s0 | ||
763 | ) | ||
764 | ), | ||
765 | fp_shl(s1, 2) | ||
766 | ), | ||
767 | s2 | ||
768 | ) | ||
769 | ) | ||
770 | ); | ||
771 | return tmp; | ||
772 | } | ||
773 | |||
774 | #define QUADINT_STEP float2fixed(1.0f/200.0f) | ||
775 | |||
776 | fixed vec_quadint_min(fixed *x, unsigned bufsize, unsigned pos, unsigned span) | ||
777 | { | ||
778 | fixed res, frac, s0, s1, s2; | ||
779 | fixed exactpos = int2fixed(pos); | ||
780 | /* init resold to something big (in case x[pos+-span]<0)) */ | ||
781 | fixed resold = FP_MAX; | ||
782 | |||
783 | if ((pos > span) && (pos < bufsize-span)) | ||
784 | { | ||
785 | s0 = x[pos-span]; | ||
786 | s1 = x[pos] ; | ||
787 | s2 = x[pos+span]; | ||
788 | /* increase frac */ | ||
789 | for (frac = float2fixed(0.0f); | ||
790 | fp_lt(frac, float2fixed(2.0f)); | ||
791 | frac = fp_add(frac, QUADINT_STEP)) | ||
792 | { | ||
793 | res = aubio_quadfrac(s0, s1, s2, frac); | ||
794 | if (fp_lt(res, resold)) | ||
795 | { | ||
796 | resold = res; | ||
797 | } | ||
798 | else | ||
799 | { | ||
800 | /* exactpos += (frac-QUADINT_STEP)*span - span/2.0f; */ | ||
801 | exactpos = fp_add(exactpos, | ||
802 | fp_sub( | ||
803 | fp_mul( | ||
804 | fp_sub(frac, QUADINT_STEP), | ||
805 | int2fixed(span) | ||
806 | ), | ||
807 | int2fixed(span) | ||
808 | ) | ||
809 | ); | ||
810 | break; | ||
811 | } | ||
812 | } | ||
813 | } | ||
814 | return exactpos; | ||
815 | } | ||
816 | |||
817 | |||
818 | /* Calculate the period of the note in the | ||
819 | buffer using the YIN algorithm */ | ||
820 | /* The yin pointer is just a buffer that the algorithm uses as a work | ||
821 | space. It needs to be half the length of the input buffer. */ | ||
822 | |||
823 | fixed pitchyin(audio_sample_type *input, fixed *yin) | ||
824 | { | ||
825 | fixed retval; | ||
826 | unsigned j,tau = 0; | ||
827 | int period; | ||
828 | unsigned yin_size = tuner_settings.sample_size / 4; | ||
829 | |||
830 | fixed tmp = FP_ZERO, tmp2 = FP_ZERO; | ||
831 | yin[0] = int2fixed(1); | ||
832 | for (tau = 1; tau < yin_size; tau++) | ||
833 | { | ||
834 | yin[tau] = FP_ZERO; | ||
835 | for (j = 0; j < yin_size; j++) | ||
836 | { | ||
837 | tmp = fp_sub(int2mantissa(input[2 * j]), | ||
838 | int2mantissa(input[2 * (j + tau)])); | ||
839 | yin[tau] = fp_add(yin[tau], fp_mul(tmp, tmp)); | ||
840 | } | ||
841 | tmp2 = fp_add(tmp2, yin[tau]); | ||
842 | if(!fp_equal(tmp2, FP_ZERO)) | ||
843 | { | ||
844 | yin[tau] = fp_mul(yin[tau], fp_div(int2fixed(tau), tmp2)); | ||
845 | } | ||
846 | period = tau - 3; | ||
847 | if(tau > 4 && fp_lt(yin[period], | ||
848 | yin_threshold_table[tuner_settings.yin_threshold]) | ||
849 | && fp_lt(yin[period], yin[period+1])) | ||
850 | { | ||
851 | retval = vec_quadint_min(yin, yin_size, period, 1); | ||
852 | return retval; | ||
853 | } | ||
854 | } | ||
855 | retval = vec_quadint_min(yin, yin_size, | ||
856 | vec_min_elem(yin, yin_size), 1); | ||
857 | return retval; | ||
858 | /*return FP_ZERO;*/ | ||
859 | } | ||
860 | |||
861 | /*-----------------------------------------------------------------*/ | ||
862 | |||
863 | uint32_t buffer_magnitude(audio_sample_type *input) | ||
864 | { | ||
865 | unsigned n; | ||
866 | uint64_t tally = 0; | ||
867 | |||
868 | /* Operate on only one channel of the stereo signal */ | ||
869 | for(n = 0; n < tuner_settings.sample_size; n+=2) | ||
870 | { | ||
871 | tally += (uint64_t)input[n] * (uint64_t)input[n]; | ||
872 | } | ||
873 | |||
874 | tally /= tuner_settings.sample_size / 2; | ||
875 | |||
876 | /* now tally holds the average of the squares of all the samples */ | ||
877 | /* It must be between 0 and 0x7fff^2, so it fits in 32 bits */ | ||
878 | return (uint32_t)tally; | ||
879 | } | ||
880 | |||
881 | /* Stop the recording when the buffer is full */ | ||
882 | int recording_callback(int status) | ||
883 | { | ||
884 | (void) status; | ||
885 | |||
886 | rb->pcm_stop_recording(); | ||
887 | recording=0; | ||
888 | return -1; | ||
889 | } | ||
890 | |||
891 | /* The main program loop */ | ||
892 | void record_and_get_pitch(void) | ||
893 | { | ||
894 | int quit=0, button; | ||
895 | bool redraw = true; | ||
896 | /* For tracking the latency */ | ||
897 | /* | ||
898 | long timer; | ||
899 | char debug_string[20]; | ||
900 | */ | ||
901 | fixed period; | ||
902 | bool waiting = false; | ||
903 | |||
904 | while(!quit) | ||
905 | { | ||
906 | /* Start recording */ | ||
907 | rb->pcm_record_data(recording_callback, (void *) audio_data, | ||
908 | (size_t) tuner_settings.sample_size * | ||
909 | sizeof(audio_sample_type)); | ||
910 | recording=1; | ||
911 | |||
912 | while (recording && !quit) /* wait for the buffer to be filled */ | ||
913 | { | ||
914 | rb->yield(); | ||
915 | button=pluginlib_getaction(0, plugin_contexts, PLA_ARRAY_COUNT); | ||
916 | switch(button) | ||
917 | { | ||
918 | case PLA_QUIT: | ||
919 | quit=true; | ||
920 | rb->yield(); | ||
921 | break; | ||
922 | |||
923 | case PLA_MENU: | ||
924 | if(main_menu()) | ||
925 | quit=true; | ||
926 | rb->yield(); | ||
927 | redraw = true; | ||
928 | break; | ||
929 | |||
930 | default: | ||
931 | rb->yield(); | ||
932 | |||
933 | break; | ||
934 | } | ||
935 | } | ||
936 | |||
937 | /* Let's keep track of how long this takes */ | ||
938 | /* timer = *(rb->current_tick); */ | ||
939 | |||
940 | if(!quit) | ||
941 | { | ||
942 | /* Only do the heavy lifting if the volume is high enough */ | ||
943 | if(buffer_magnitude(audio_data) > | ||
944 | sqr(tuner_settings.volume_threshold * | ||
945 | rb->sound_max(SOUND_MIC_GAIN))) | ||
946 | { | ||
947 | if(waiting) | ||
948 | { | ||
949 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
950 | rb->cpu_boost(true); | ||
951 | #endif | ||
952 | waiting = false; | ||
953 | } | ||
954 | |||
955 | rb->backlight_on(); | ||
956 | redraw = false; | ||
957 | |||
958 | /* This returns the period of the detected pitch in samples */ | ||
959 | period = pitchyin(audio_data, yin_buffer); | ||
960 | /* Hz = sample rate / period */ | ||
961 | if(fp_gt(period, FP_ZERO)) | ||
962 | { | ||
963 | display_frequency(fp_period2freq(period)); | ||
964 | } | ||
965 | else | ||
966 | { | ||
967 | display_frequency(FP_ZERO); | ||
968 | } | ||
969 | } | ||
970 | else if(redraw || !waiting) | ||
971 | { | ||
972 | waiting = true; | ||
973 | redraw = false; | ||
974 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
975 | rb->cpu_boost(false); | ||
976 | #endif | ||
977 | /*rb->backlight_off();*/ | ||
978 | display_frequency(FP_ZERO); | ||
979 | } | ||
980 | |||
981 | /*rb->snprintf(debug_string, 20, "%x,%x", | ||
982 | audio_data[50], audio_data[52]); | ||
983 | rb->lcd_putsxy(0, 40, debug_string); | ||
984 | rb->lcd_update(); | ||
985 | */ | ||
986 | |||
987 | /* Print out how long it took to find the pitch */ | ||
988 | /* | ||
989 | rb->snprintf(debug_string, 20, "latency: %ld", | ||
990 | *(rb->current_tick) - timer); | ||
991 | rb->lcd_putsxy(0, 40, debug_string); | ||
992 | rb->lcd_update(); | ||
993 | */ | ||
994 | } | ||
995 | } | ||
996 | rb->pcm_close_recording(); | ||
997 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
998 | rb->cpu_boost(false); | ||
999 | #endif | ||
1000 | } | ||
1001 | |||
1002 | /* Init recording, tuning, and GUI */ | ||
1003 | void init_everything(void) | ||
1004 | { | ||
1005 | load_settings(); | ||
1006 | |||
1007 | /* --------- Init the audio recording ----------------- */ | ||
1008 | rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); | ||
1009 | rb->audio_set_input_source(INPUT_TYPE, SRCF_RECORDING); | ||
1010 | |||
1011 | /* set to maximum gain */ | ||
1012 | rb->audio_set_recording_gain(tuner_settings.record_gain, | ||
1013 | tuner_settings.record_gain, | ||
1014 | AUDIO_GAIN_MIC); | ||
1015 | |||
1016 | rb->pcm_set_frequency(SAMPLE_RATE); | ||
1017 | rb->pcm_apply_settings(); | ||
1018 | |||
1019 | rb->pcm_init_recording(); | ||
1020 | |||
1021 | /* GUI */ | ||
1022 | back_color = rb->lcd_get_background(); | ||
1023 | front_color = rb->lcd_get_foreground(); | ||
1024 | rb->lcd_getstringsize("X", &font_w, &font_h); | ||
1025 | |||
1026 | bar_x_minus_50 = 0; | ||
1027 | bar_x_minus_20 = (LCD_WIDTH / 2) - | ||
1028 | fixed2int(fp_mul(LCD_FACTOR, int2fixed(20))) - font_w; | ||
1029 | bar_x_0 = LCD_WIDTH / 2; | ||
1030 | bar_x_20 = (LCD_WIDTH / 2) + | ||
1031 | fixed2int(fp_mul(LCD_FACTOR, int2fixed(20))) - font_w; | ||
1032 | bar_x_50 = LCD_WIDTH - 2 * font_w; | ||
1033 | |||
1034 | letter_y = 10; | ||
1035 | note_bar_y = letter_y + font_h; | ||
1036 | bar_h = font_h; | ||
1037 | freq_y = note_bar_y + bar_h + 3; | ||
1038 | error_ticks_y = freq_y + font_h + 8; | ||
1039 | error_hline_y = error_ticks_y + font_h + 2; | ||
1040 | error_bar_margin = 2; | ||
1041 | error_bar_y = error_hline_y + error_bar_margin; | ||
1042 | error_hline_y2 = error_bar_y + bar_h + error_bar_margin; | ||
1043 | } | ||
1044 | |||
1045 | |||
1046 | enum plugin_status plugin_start(const void* parameter) NO_PROF_ATTR | ||
1047 | { | ||
1048 | (void)parameter; | ||
1049 | |||
1050 | init_everything(); | ||
1051 | record_and_get_pitch(); | ||
1052 | save_settings(); | ||
1053 | |||
1054 | return 0; | ||
1055 | } | ||