New plugin: pitch detector (FS#8768) by Michael Lechner and David Johnston
[kugel-rb.git] / apps / plugins / pitch_detector.c
blob8d0b3b7957f3b48f64ae3012bc756c4b3ac69ab7
1 /**************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id:$
10 * Copyright (C) 2008 Lechner Michael / smoking gnu
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.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 * ----------------------------------------------------------------------------
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.
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
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
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.
69 #include "plugin.h"
70 #include "lib/pluginlib_actions.h"
72 PLUGIN_HEADER
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
83 /* Some fixed point calculation stuff */
84 typedef int32_t fixed_data;
85 struct _fixed
87 fixed_data a;
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})
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)
136 #define sqr(x) ((x)*(x))
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)
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)
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)
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
168 /* How many decimal places to display for the Hz value */
169 #define DISPLAY_HZ_PRECISION 100
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;
177 /* Frequencies of all the notes of the scale */
178 static const fixed freqs[12] =
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),
193 /* logarithm of all the notes of the scale */
194 static const fixed lfreqs[12] =
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),
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 */
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;
229 /*Settings for the plugin */
231 struct tuner_settings
233 unsigned volume_threshold;
234 unsigned record_gain;
235 unsigned sample_size;
236 unsigned lowest_freq;
237 unsigned yin_threshold;
238 } tuner_settings;
240 /*=================================================================*/
241 /* MENU */
242 /*=================================================================*/
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
253 #define PLA_ARRAY_COUNT sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
255 fixed yin_threshold_table[] =
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),
273 #define DEFAULT_YIN_THRESHOLD 5 /* 0.10 */
275 /* Option strings */
276 static const struct opt_items yin_threshold_text[] =
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 },
294 void set_min_freq(int new_freq)
296 tuner_settings.sample_size = freq2period(new_freq) * 4;
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);
308 bool main_menu(void)
310 int selection=0;
311 bool done = false;
312 bool exit_tuner=false;
313 int choice;
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");
323 while(!done)
325 choice = rb->do_menu(&menu, &selection, NULL, false);
326 switch(choice)
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;
366 return(exit_tuner);
369 /*=================================================================*/
370 /* Settings loading and saving(adapted from the clock plugin) */
371 /*=================================================================*/
373 #define SETTINGS_FILENAME PLUGIN_APPS_DIR "/.pitch_settings"
375 enum message
377 MESSAGE_LOADING,
378 MESSAGE_LOADED,
379 MESSAGE_ERRLOAD,
380 MESSAGE_SAVING,
381 MESSAGE_SAVED,
382 MESSAGE_ERRSAVE
385 enum settings_file_status
387 LOADED, ERRLOAD,
388 SAVED, ERRSAVE
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;
395 /*---------------------------------------------------------------------*/
397 bool settings_needs_saving(struct tuner_settings* settings)
399 return(rb->memcmp(settings, &hdd_tuner_settings, sizeof(*settings)));
402 /*---------------------------------------------------------------------*/
404 void tuner_settings_reset(struct tuner_settings* settings)
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;
413 /*---------------------------------------------------------------------*/
415 enum settings_file_status tuner_settings_load(struct tuner_settings* settings,
416 char* filename)
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);
428 /* Initializes the settings with default values at least */
429 tuner_settings_reset(settings);
430 return(ERRLOAD);
433 /*---------------------------------------------------------------------*/
435 enum settings_file_status tuner_settings_save(struct tuner_settings* settings,
436 char* filename)
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);
444 return(ERRSAVE);
447 /*---------------------------------------------------------------------*/
449 void load_settings(void)
451 tuner_settings_load(&tuner_settings, SETTINGS_FILENAME);
453 rb->storage_sleep();
456 /*---------------------------------------------------------------------*/
458 void save_settings(void)
460 if(!settings_needs_saving(&tuner_settings))
461 return;
463 tuner_settings_save(&tuner_settings, SETTINGS_FILENAME);
466 /*=================================================================*/
467 /* Binary Log */
468 /*=================================================================*/
470 /* Fixed-point log base 2*/
471 /* Adapted from python code at
472 http://en.wikipedia.org/wiki/Binary_logarithm#Algorithm
474 fixed log(fixed inp)
476 fixed x = inp;
477 fixed fp = int2fixed(1);
478 fixed res = int2fixed(0);
480 if(fp_lte(x, FP_ZERO))
482 return FP_MIN;
485 /* Integer part*/
486 /* while x<1 */
487 while(fp_lt(x, int2fixed(1)))
489 res = fp_sub(res, int2fixed(1));
490 x = fp_shl(x, 1);
492 /* while x>=2 */
493 while(fp_gte(x, int2fixed(2)))
495 res = fp_add(res, int2fixed(1));
496 x = fp_shr(x, 1);
499 /* Fractional part */
500 /* while fp > 0 */
501 while(fp_gt(fp, FP_ZERO))
503 fp = fp_shr(fp, 1);
504 x = fp_mul(x, x);
505 /* if x >= 2 */
506 if(fp_gte(x, int2fixed(2)))
508 x = fp_shr(x, 1);
509 res = fp_add(res, fp);
513 return res;
516 /*=================================================================*/
517 /* GUI Stuff */
518 /*=================================================================*/
520 /* The function name is pretty self-explaining ;) */
521 void print_int_xy(int x, int y, int v)
523 char temp[20];
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();
531 /* Print out the frequency etc - will be removed later on */
532 void print_str(char* s)
534 rb->lcd_set_foreground(front_color);
535 rb->lcd_putsxy(0, freq_y, s);
536 rb->lcd_update();
539 /* What can I say? Read the function name... */
540 void print_char_xy(int x, int y, char c)
542 char temp[2];
544 temp[0]=c;
545 temp[1]=0;
546 rb->lcd_set_foreground(front_color);
548 rb->lcd_putsxy(x, y, temp);
551 /* Draw the red bar and the white lines */
552 void draw_bar(fixed wrong_by_cents)
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
562 if (fp_gt(wrong_by_cents, FP_ZERO))
564 rb->lcd_fillrect(bar_x_0, error_bar_y,
565 fixed2int(fp_mul(wrong_by_cents, LCD_FACTOR)), bar_h);
567 else
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);
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
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);
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);
593 /* Print the letters A-G and the # on the screen */
594 void draw_letters(void)
596 int i;
598 rb->lcd_set_foreground(front_color);
600 for (i=0; i<8; i++)
602 print_char_xy(i*(LCD_WIDTH / 8 ) + font_w, letter_y, gui_letters[i]);
606 /* Draw the yellow point(s) below the letters and the '#' */
607 void draw_points(const char *s)
609 int i;
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
620 rb->lcd_fillrect(i*(LCD_WIDTH / 8 ) + font_w, note_bar_y, font_w, font_h);
622 if (s[1] == '#')
623 rb->lcd_fillrect(7*(LCD_WIDTH / 8 ) + font_w, note_bar_y, font_w, font_h);
626 /* Calculate how wrong the note is and draw the GUI */
627 void display_frequency (fixed freq)
629 fixed ldf, mldf;
630 fixed lfreq, nfreq;
631 int i, note = 0;
632 bool draw_freq;
633 char str_buf[30];
635 if (fp_lt(freq, FP_LOW))
636 freq = FP_LOW;
637 lfreq = log(freq);
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++)
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))
652 mldf = ldf;
653 note = i;
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))
661 nfreq = fp_shl(nfreq, 1);
664 ldf=fp_mul(int2fixed(1200), log(fp_div(freq,nfreq)));
666 draw_freq = (fp_round(freq) != 0);
667 if (draw_freq)
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))));
674 else
676 ldf = FP_ZERO; /* prevents red bar at -32 cents when freq= 0*/
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)
684 draw_points(notes[note]); /* The yellow point(s) */
685 print_str(str_buf);
687 rb->lcd_update();
690 /*-----------------------------------------------------------------------
691 * Functions for the Yin algorithm
693 * These were all adapted from the versions in Aubio v0.3.2
694 * Here's what the Aubio documentation has to say:
696 * This algorithm was developped by A. de Cheveigne and H. Kawahara and
697 * published in:
699 * de Cheveign?, A., Kawahara, H. (2002) "YIN, a fundamental frequency
700 * estimator for speech and music", J. Acoust. Soc. Am. 111, 1917-1930.
702 * see http://recherche.ircam.fr/equipes/pcm/pub/people/cheveign.html
703 -------------------------------------------------------------------------*/
705 /* Find the index of the minimum element of an array of floats */
706 unsigned vec_min_elem(fixed *s, unsigned buflen)
708 unsigned j, pos=0.0f;
709 fixed tmp = s[0];
710 for (j=0; j < buflen; j++)
712 if(fp_gt(tmp, s[j]))
714 pos = j;
715 tmp = s[j];
718 return pos;
722 fixed aubio_quadfrac(fixed s0, fixed s1, fixed s2, fixed pf)
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)); */
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
737 fp_mul
739 fp_shr(pf, 1),
740 fp_sub
742 fp_add
744 fp_sub
746 fp_mul
749 fp_add
751 fp_sub
754 fp_shl(s1, 1)
759 fp_mul
761 float2fixed(3.0f),
765 fp_shl(s1, 2)
771 return tmp;
774 #define QUADINT_STEP float2fixed(1.0f/200.0f)
776 fixed vec_quadint_min(fixed *x, unsigned bufsize, unsigned pos, unsigned span)
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;
783 if ((pos > span) && (pos < bufsize-span))
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))
793 res = aubio_quadfrac(s0, s1, s2, frac);
794 if (fp_lt(res, resold))
796 resold = res;
798 else
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)
807 int2fixed(span)
810 break;
814 return exactpos;
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. */
823 fixed pitchyin(audio_sample_type *input, fixed *yin)
825 fixed retval;
826 unsigned j,tau = 0;
827 int period;
828 unsigned yin_size = tuner_settings.sample_size / 4;
830 fixed tmp = FP_ZERO, tmp2 = FP_ZERO;
831 yin[0] = int2fixed(1);
832 for (tau = 1; tau < yin_size; tau++)
834 yin[tau] = FP_ZERO;
835 for (j = 0; j < yin_size; j++)
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));
841 tmp2 = fp_add(tmp2, yin[tau]);
842 if(!fp_equal(tmp2, FP_ZERO))
844 yin[tau] = fp_mul(yin[tau], fp_div(int2fixed(tau), tmp2));
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]))
851 retval = vec_quadint_min(yin, yin_size, period, 1);
852 return retval;
855 retval = vec_quadint_min(yin, yin_size,
856 vec_min_elem(yin, yin_size), 1);
857 return retval;
858 /*return FP_ZERO;*/
861 /*-----------------------------------------------------------------*/
863 uint32_t buffer_magnitude(audio_sample_type *input)
865 unsigned n;
866 uint64_t tally = 0;
868 /* Operate on only one channel of the stereo signal */
869 for(n = 0; n < tuner_settings.sample_size; n+=2)
871 tally += (uint64_t)input[n] * (uint64_t)input[n];
874 tally /= tuner_settings.sample_size / 2;
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;
881 /* Stop the recording when the buffer is full */
882 int recording_callback(int status)
884 (void) status;
886 rb->pcm_stop_recording();
887 recording=0;
888 return -1;
891 /* The main program loop */
892 void record_and_get_pitch(void)
894 int quit=0, button;
895 bool redraw = true;
896 /* For tracking the latency */
898 long timer;
899 char debug_string[20];
901 fixed period;
902 bool waiting = false;
904 while(!quit)
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;
912 while (recording && !quit) /* wait for the buffer to be filled */
914 rb->yield();
915 button=pluginlib_getaction(0, plugin_contexts, PLA_ARRAY_COUNT);
916 switch(button)
918 case PLA_QUIT:
919 quit=true;
920 rb->yield();
921 break;
923 case PLA_MENU:
924 if(main_menu())
925 quit=true;
926 rb->yield();
927 redraw = true;
928 break;
930 default:
931 rb->yield();
933 break;
937 /* Let's keep track of how long this takes */
938 /* timer = *(rb->current_tick); */
940 if(!quit)
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)))
947 if(waiting)
949 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
950 rb->cpu_boost(true);
951 #endif
952 waiting = false;
955 rb->backlight_on();
956 redraw = false;
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))
963 display_frequency(fp_period2freq(period));
965 else
967 display_frequency(FP_ZERO);
970 else if(redraw || !waiting)
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);
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();
987 /* Print out how long it took to find the pitch */
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();
996 rb->pcm_close_recording();
997 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
998 rb->cpu_boost(false);
999 #endif
1002 /* Init recording, tuning, and GUI */
1003 void init_everything(void)
1005 load_settings();
1007 /* --------- Init the audio recording ----------------- */
1008 rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
1009 rb->audio_set_input_source(INPUT_TYPE, SRCF_RECORDING);
1011 /* set to maximum gain */
1012 rb->audio_set_recording_gain(tuner_settings.record_gain,
1013 tuner_settings.record_gain,
1014 AUDIO_GAIN_MIC);
1016 rb->pcm_set_frequency(SAMPLE_RATE);
1017 rb->pcm_apply_settings();
1019 rb->pcm_init_recording();
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);
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;
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;
1046 enum plugin_status plugin_start(const void* parameter) NO_PROF_ATTR
1048 (void)parameter;
1050 init_everything();
1051 record_and_get_pitch();
1052 save_settings();
1054 return 0;