e200v1/c200v1: Implement limited samplerate switching. Rates 24kHz and below are...
[kugel-rb.git] / apps / plugins / pitch_detector.c
blob36248a540d32dcafe7658bdc4e481619fa9dbcb0
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 * - Adapt the Yin FFT algorithm, which would reduce complexity from O(n^2)
29 * to O(nlogn), theoretically reducing latency by a factor of ~10. -David
31 * MAJOR CHANGES:
32 * 08.03.2008 Started coding
33 * 21.03.2008 Pitch detection works more or less
34 * Button definitions for most targets added
35 * 02.04.2008 Proper GUI added
36 * Todo, Major Changes and Current Limitations added
37 * 08.19.2009 Brought the code up to date with current plugin standards
38 * Made it work more nicely with color, BW and grayscale
39 * Changed pitch detection to use the Yin algorithm (better
40 * detection, but slower -- would be ~4x faster with
41 * fixed point math, I think). Code was poached from the
42 * Aubio sound processing library (aubio.org). -David
43 * 08.31.2009 Lots of changes:
44 * Added a menu to tweak settings
45 * Converted everything to fixed point (greatly improving
46 * latency)
47 * Improved the display
48 * Improved efficiency with judicious use of cpu_boost, the
49 * backlight, and volume detection to limit unneeded
50 * calculation
51 * Fixed a problem that caused an octave-off error
52 * -David
53 * 05.14.2010 Multibuffer continuous recording with two buffers
56 * CURRENT LIMITATIONS:
57 * - No gapless recording. Strictly speaking true gappless isn't possible,
58 * since the algorithm takes longer to calculate than the length of the
59 * sample, but latency could be improved a bit with proper use of the DMA
60 * recording functions.
61 * - Due to how the Yin algorithm works, latency is higher for lower
62 * frequencies.
65 #include "plugin.h"
66 #include "lib/pluginlib_actions.h"
67 #include "lib/picture.h"
68 #include "lib/helper.h"
69 #include "pluginbitmaps/pitch_notes.h"
71 PLUGIN_HEADER
72 PLUGIN_IRAM_DECLARE
74 /* Some fixed point calculation stuff */
75 typedef int32_t fixed_data;
76 struct _fixed
78 fixed_data a;
80 typedef struct _fixed fixed;
81 #define FIXED_PRECISION 18
82 #define FP_MAX ((fixed) {0x7fffffff})
83 #define FP_MIN ((fixed) {-0x80000000})
84 #define int2fixed(x) ((fixed){(x) << FIXED_PRECISION})
85 #define int2mantissa(x) ((fixed){x})
86 #define fixed2int(x) ((int)((x).a >> FIXED_PRECISION))
87 #define fixed2float(x) (((float)(x).a) / ((float)(1 << FIXED_PRECISION)))
88 #define float2fixed(x) \
89 ((fixed){(fixed_data)(x * (float)(1 << FIXED_PRECISION))})
90 /* I adapted these ones from the Rockbox fixed point library */
91 #define fp_mul(x, y) \
92 ((fixed){(((int64_t)((x).a)) * ((int64_t)((y).a))) >> (FIXED_PRECISION)})
93 #define fp_div(x, y) \
94 ((fixed){(((int64_t)((x).a)) << (FIXED_PRECISION)) / ((int64_t)((y).a))})
95 /* Operators for fixed point */
96 #define fp_add(x, y) ((fixed){(x).a + (y).a})
97 #define fp_sub(x, y) ((fixed){(x).a - (y).a})
98 #define fp_shl(x, y) ((fixed){(x).a << y})
99 #define fp_shr(x, y) ((fixed){(x).a >> y})
100 #define fp_neg(x) ((fixed){-(x).a})
101 #define fp_gt(x, y) ((x).a > (y).a)
102 #define fp_gte(x, y) ((x).a >= (y).a)
103 #define fp_lt(x, y) ((x).a < (y).a)
104 #define fp_lte(x, y) ((x).a <= (y).a)
105 #define fp_sqr(x) fp_mul((x), (x))
106 #define fp_equal(x, y) ((x).a == (y).a)
107 #define fp_round(x) (fixed2int(fp_add((x), float2fixed(0.5))))
108 #define fp_data(x) ((x).a)
109 #define fp_frac(x) (fp_sub((x), int2fixed(fixed2int(x))))
110 #define FP_ZERO ((fixed){0})
111 #define FP_LOW ((fixed){2})
113 /* Some defines for converting between period and frequency */
115 /* I introduce some divisors in this because the fixed point */
116 /* variables aren't big enough to hold higher than a certain */
117 /* value. This loses a bit of precision but it means we */
118 /* don't have to use 32.32 variables (yikes). */
119 /* With an 18-bit decimal precision, the max value in the */
120 /* integer part is 8192. Divide 44100 by 7 and it'll fit in */
121 /* that variable. */
122 #define fp_period2freq(x) fp_div(int2fixed(sample_rate / 7), \
123 fp_div((x),int2fixed(7)))
124 #define fp_freq2period(x) fp_period2freq(x)
125 #define period2freq(x) (sample_rate / (x))
126 #define freq2period(x) period2freq(x)
128 #define sqr(x) ((x)*(x))
130 /* Some constants for tuning */
131 #define A_FREQ float2fixed(440.0f)
132 #define D_NOTE float2fixed(1.059463094359f)
133 #define LOG_D_NOTE float2fixed(1.0f/12.0f)
134 #define D_NOTE_SQRT float2fixed(1.029302236643f)
135 #define LOG_2 float2fixed(1.0f)
137 /* The recording buffer size */
138 /* This is how much is sampled at a time. */
139 /* It also determines latency -- if BUFFER_SIZE == sample_rate then */
140 /* there'll be one sample per second, or a latency of one second. */
141 /* Furthermore, the lowest detectable frequency will be about twice */
142 /* the number of reads per second */
143 /* If we ever switch to Yin FFT algorithm then this needs to be
144 a power of 2 */
145 #define BUFFER_SIZE 4096
146 #define SAMPLE_SIZE 4096
147 #define SAMPLE_SIZE_MIN 1024
148 #define YIN_BUFFER_SIZE (BUFFER_SIZE / 4)
150 #define LCD_FACTOR (fp_div(int2fixed(LCD_WIDTH), int2fixed(100)))
151 /* The threshold for the YIN algorithm */
152 #define DEFAULT_YIN_THRESHOLD 5 /* 0.10 */
153 const fixed yin_threshold_table[] IDATA_ATTR =
155 float2fixed(0.01),
156 float2fixed(0.02),
157 float2fixed(0.03),
158 float2fixed(0.04),
159 float2fixed(0.05),
160 float2fixed(0.10),
161 float2fixed(0.15),
162 float2fixed(0.20),
163 float2fixed(0.25),
164 float2fixed(0.30),
165 float2fixed(0.35),
166 float2fixed(0.40),
167 float2fixed(0.45),
168 float2fixed(0.50),
171 /* Structure for the reference frequency (frequency of A)
172 * It's used for scaling the frequency before finding out
173 * the note. The frequency is scaled in a way that the main
174 * algorithm can assume the frequency of A to be 440 Hz.
176 struct freq_A_entry
178 const int frequency; /* Frequency in Hz */
179 const fixed ratio; /* 440/frequency */
180 const fixed logratio; /* log2(factor) */
183 const struct freq_A_entry freq_A[] =
185 {435, float2fixed(1.011363636), float2fixed( 0.016301812)},
186 {436, float2fixed(1.009090909), float2fixed( 0.013056153)},
187 {437, float2fixed(1.006818182), float2fixed( 0.009803175)},
188 {438, float2fixed(1.004545455), float2fixed( 0.006542846)},
189 {439, float2fixed(1.002272727), float2fixed( 0.003275132)},
190 {440, float2fixed(1.000000000), float2fixed( 0.000000000)},
191 {441, float2fixed(0.997727273), float2fixed(-0.003282584)},
192 {442, float2fixed(0.995454545), float2fixed(-0.006572654)},
193 {443, float2fixed(0.993181818), float2fixed(-0.009870244)},
194 {444, float2fixed(0.990909091), float2fixed(-0.013175389)},
195 {445, float2fixed(0.988636364), float2fixed(-0.016488123)},
198 /* Index of the entry for 440 Hz in the table (default frequency for A) */
199 #define DEFAULT_FREQ_A 5
200 #define NUM_FREQ_A (sizeof(freq_A)/sizeof(freq_A[0]))
202 /* How loud the audio has to be to start displaying pitch */
203 /* Must be between 0 and 100 */
204 #define VOLUME_THRESHOLD (50)
206 /* Change to AUDIO_SRC_LINEIN if you want to record from line-in */
207 #ifdef HAVE_MIC_IN
208 #define INPUT_TYPE AUDIO_SRC_MIC
209 #else
210 #define INPUT_TYPE AUDIO_SRC_LINEIN
211 #endif
213 /* How many decimal places to display for the Hz value */
214 #define DISPLAY_HZ_PRECISION 100
216 /* Where to put the various GUI elements */
217 int note_y;
218 int bar_grad_y;
219 #define LCD_RES_MIN (LCD_HEIGHT < LCD_WIDTH ? LCD_HEIGHT : LCD_WIDTH)
220 #define BAR_PADDING (LCD_RES_MIN / 32)
221 #define BAR_Y (LCD_HEIGHT * 3 / 4)
222 #define BAR_HEIGHT (LCD_RES_MIN / 4 - BAR_PADDING)
223 #define BAR_HLINE_Y (BAR_Y - BAR_PADDING)
224 #define BAR_HLINE_Y2 (BAR_Y + BAR_HEIGHT + BAR_PADDING - 1)
225 #define HZ_Y 0
226 #define GRADUATION 10 /* Subdivisions of the whole 100-cent scale */
228 /* Bitmaps for drawing the note names. These need to have height
229 <= (bar_grad_y - note_y), or 15/32 * LCD_HEIGHT
231 #define NUM_NOTE_IMAGES 9
232 #define NOTE_INDEX_A 0
233 #define NOTE_INDEX_B 1
234 #define NOTE_INDEX_C 2
235 #define NOTE_INDEX_D 3
236 #define NOTE_INDEX_E 4
237 #define NOTE_INDEX_F 5
238 #define NOTE_INDEX_G 6
239 #define NOTE_INDEX_SHARP 7
240 #define NOTE_INDEX_FLAT 8
241 const struct picture note_bitmaps =
243 pitch_notes,
244 BMPWIDTH_pitch_notes,
245 BMPHEIGHT_pitch_notes,
246 BMPHEIGHT_pitch_notes/NUM_NOTE_IMAGES
250 static unsigned int sample_rate;
251 static int audio_head = 0; /* which of the two buffers to use? */
252 static volatile int audio_tail = 0; /* which of the two buffers to record? */
253 /* It's stereo, so make the buffer twice as big */
254 #ifndef SIMULATOR
255 static int16_t audio_data[2][BUFFER_SIZE] __attribute__((aligned(CACHEALIGN_SIZE)));
256 static fixed yin_buffer[YIN_BUFFER_SIZE] IBSS_ATTR;
257 #ifdef PLUGIN_USE_IRAM
258 static int16_t iram_audio_data[BUFFER_SIZE] IBSS_ATTR;
259 #else
260 #define iram_audio_data audio_data[audio_head]
261 #endif
262 #endif
264 /* Description of a note of scale */
265 struct note_entry
267 const char *name; /* Name of the note, e.g. "A#" */
268 const fixed freq; /* Note frequency, Hz */
269 const fixed logfreq; /* log2(frequency) */
272 /* Notes within one (reference) scale */
273 static const struct note_entry notes[] =
275 {"A" , float2fixed(440.0000000f), float2fixed(8.781359714f)},
276 {"A#", float2fixed(466.1637615f), float2fixed(8.864693047f)},
277 {"B" , float2fixed(493.8833013f), float2fixed(8.948026380f)},
278 {"C" , float2fixed(523.2511306f), float2fixed(9.031359714f)},
279 {"C#", float2fixed(554.3652620f), float2fixed(9.114693047f)},
280 {"D" , float2fixed(587.3295358f), float2fixed(9.198026380f)},
281 {"D#", float2fixed(622.2539674f), float2fixed(9.281359714f)},
282 {"E" , float2fixed(659.2551138f), float2fixed(9.364693047f)},
283 {"F" , float2fixed(698.4564629f), float2fixed(9.448026380f)},
284 {"F#", float2fixed(739.9888454f), float2fixed(9.531359714f)},
285 {"G" , float2fixed(783.9908720f), float2fixed(9.614693047f)},
286 {"G#", float2fixed(830.6093952f), float2fixed(9.698026380f)},
289 /* GUI */
290 #if LCD_DEPTH > 1
291 static unsigned front_color;
292 #endif
293 static int font_w,font_h;
294 static int bar_x_0;
295 static int lbl_x_minus_50, lbl_x_minus_20, lbl_x_0, lbl_x_20, lbl_x_50;
297 /* Settings for the plugin */
298 struct tuner_settings
300 unsigned volume_threshold;
301 unsigned record_gain;
302 unsigned sample_size;
303 unsigned lowest_freq;
304 unsigned yin_threshold;
305 int freq_A; /* Index of the frequency of A */
306 bool use_sharps;
307 bool display_hz;
308 } tuner_settings;
310 /*=================================================================*/
311 /* Settings loading and saving(adapted from the clock plugin) */
312 /*=================================================================*/
314 #define SETTINGS_FILENAME PLUGIN_APPS_DIR "/.pitch_settings"
316 enum message
318 MESSAGE_LOADING,
319 MESSAGE_LOADED,
320 MESSAGE_ERRLOAD,
321 MESSAGE_SAVING,
322 MESSAGE_SAVED,
323 MESSAGE_ERRSAVE
326 enum settings_file_status
328 LOADED, ERRLOAD,
329 SAVED, ERRSAVE
332 /* The settings as they exist on the hard disk, so that
333 * we can know at saving time if changes have been made */
334 struct tuner_settings hdd_tuner_settings;
336 /*---------------------------------------------------------------------*/
338 bool settings_needs_saving(struct tuner_settings* settings)
340 return(rb->memcmp(settings, &hdd_tuner_settings, sizeof(*settings)));
343 /*---------------------------------------------------------------------*/
345 void tuner_settings_reset(struct tuner_settings* settings)
347 settings->volume_threshold = VOLUME_THRESHOLD;
348 settings->record_gain = rb->global_settings->rec_mic_gain;
349 settings->sample_size = BUFFER_SIZE;
350 settings->lowest_freq = period2freq(BUFFER_SIZE / 4);
351 settings->yin_threshold = DEFAULT_YIN_THRESHOLD;
352 settings->freq_A = DEFAULT_FREQ_A;
353 settings->use_sharps = true;
354 settings->display_hz = false;
357 /*---------------------------------------------------------------------*/
359 enum settings_file_status tuner_settings_load(struct tuner_settings* settings,
360 char* filename)
362 int fd = rb->open(filename, O_RDONLY);
363 if(fd >= 0){ /* does file exist? */
364 /* basic consistency check */
365 if(rb->filesize(fd) == sizeof(*settings)){
366 rb->read(fd, settings, sizeof(*settings));
367 rb->close(fd);
368 rb->memcpy(&hdd_tuner_settings, settings, sizeof(*settings));
369 return(LOADED);
372 /* Initializes the settings with default values at least */
373 tuner_settings_reset(settings);
374 return(ERRLOAD);
377 /*---------------------------------------------------------------------*/
379 enum settings_file_status tuner_settings_save(struct tuner_settings* settings,
380 char* filename)
382 int fd = rb->creat(filename, 0666);
383 if(fd >= 0){ /* does file exist? */
384 rb->write (fd, settings, sizeof(*settings));
385 rb->close(fd);
386 return(SAVED);
388 return(ERRSAVE);
391 /*---------------------------------------------------------------------*/
393 void load_settings(void)
395 tuner_settings_load(&tuner_settings, SETTINGS_FILENAME);
397 rb->storage_sleep();
400 /*---------------------------------------------------------------------*/
402 void save_settings(void)
404 if(!settings_needs_saving(&tuner_settings))
405 return;
407 tuner_settings_save(&tuner_settings, SETTINGS_FILENAME);
410 /*=================================================================*/
411 /* MENU */
412 /*=================================================================*/
414 /* Keymaps */
415 const struct button_mapping* plugin_contexts[]={
416 pla_main_ctx,
417 #if NB_SCREENS == 2
418 pla_remote_ctx,
419 #endif
421 #define PLA_ARRAY_COUNT sizeof(plugin_contexts)/sizeof(plugin_contexts[0])
423 /* Option strings */
425 /* This has to match yin_threshold_table */
426 static const struct opt_items yin_threshold_text[] =
428 { "0.01", -1 },
429 { "0.02", -1 },
430 { "0.03", -1 },
431 { "0.04", -1 },
432 { "0.05", -1 },
433 { "0.10", -1 },
434 { "0.15", -1 },
435 { "0.20", -1 },
436 { "0.25", -1 },
437 { "0.30", -1 },
438 { "0.35", -1 },
439 { "0.40", -1 },
440 { "0.45", -1 },
441 { "0.50", -1 },
444 static const struct opt_items accidental_text[] =
446 { "Flat", -1 },
447 { "Sharp", -1 },
450 void set_min_freq(int new_freq)
452 tuner_settings.sample_size = freq2period(new_freq) * 4;
454 /* clamp the sample size between min and max */
455 if(tuner_settings.sample_size <= SAMPLE_SIZE_MIN)
456 tuner_settings.sample_size = SAMPLE_SIZE_MIN;
457 else if(tuner_settings.sample_size >= BUFFER_SIZE)
458 tuner_settings.sample_size = BUFFER_SIZE;
460 /* sample size must be divisible by 4 - round up */
461 tuner_settings.sample_size = (tuner_settings.sample_size + 3) & ~3;
464 bool main_menu(void)
466 int selection = 0;
467 bool done = false;
468 bool exit_tuner = false;
469 int choice;
470 int freq_val;
471 bool reset;
473 backlight_use_settings();
474 #ifdef HAVE_SCHEDULER_BOOSTCTRL
475 rb->cancel_cpu_boost();
476 #endif
478 MENUITEM_STRINGLIST(menu,"Tuner Settings",NULL,
479 "Return to Tuner",
480 "Volume Threshold",
481 "Listening Volume",
482 "Lowest Frequency",
483 "Algorithm Pickiness",
484 "Accidentals",
485 "Display Frequency (Hz)",
486 "Frequency of A (Hz)",
487 "Reset Settings",
488 "Quit");
490 while(!done)
492 choice = rb->do_menu(&menu, &selection, NULL, false);
493 switch(choice)
495 case 1:
496 rb->set_int("Volume Threshold", "%", UNIT_INT,
497 &tuner_settings.volume_threshold,
498 NULL, 5, 5, 95, NULL);
499 break;
500 case 2:
501 rb->set_int("Listening Volume", "%", UNIT_INT,
502 &tuner_settings.record_gain,
503 NULL, 1, rb->sound_min(SOUND_MIC_GAIN),
504 rb->sound_max(SOUND_MIC_GAIN), NULL);
505 break;
506 case 3:
507 rb->set_int("Lowest Frequency", "Hz", UNIT_INT,
508 &tuner_settings.lowest_freq, set_min_freq, 1,
509 /* Range depends on the size of the buffer */
510 sample_rate / (BUFFER_SIZE / 4),
511 sample_rate / (SAMPLE_SIZE_MIN / 4), NULL);
512 break;
513 case 4:
514 rb->set_option(
515 "Algorithm Pickiness (Lower -> more discriminating)",
516 &tuner_settings.yin_threshold,
517 INT, yin_threshold_text,
518 sizeof(yin_threshold_text) / sizeof(yin_threshold_text[0]),
519 NULL);
520 break;
521 case 5:
522 rb->set_option("Display Accidentals As",
523 &tuner_settings.use_sharps,
524 BOOL, accidental_text, 2, NULL);
525 break;
526 case 6:
527 rb->set_bool("Display Frequency (Hz)",
528 &tuner_settings.display_hz);
529 break;
530 case 7:
531 freq_val = freq_A[tuner_settings.freq_A].frequency;
532 rb->set_int("Frequency of A (Hz)",
533 "Hz", UNIT_INT, &freq_val, NULL,
534 1, freq_A[0].frequency, freq_A[NUM_FREQ_A-1].frequency,
535 NULL);
536 tuner_settings.freq_A = freq_val - freq_A[0].frequency;
537 break;
538 case 8:
539 reset = false;
540 rb->set_bool("Reset Tuner Settings?", &reset);
541 if (reset)
542 tuner_settings_reset(&tuner_settings);
543 break;
544 case 9:
545 exit_tuner = true;
546 done = true;
547 break;
548 case 0:
549 default:
550 /* Return to the tuner */
551 done = true;
552 break;
556 backlight_force_on();
557 return exit_tuner;
560 /*=================================================================*/
561 /* Binary Log */
562 /*=================================================================*/
564 /* Fixed-point log base 2*/
565 /* Adapted from python code at
566 http://en.wikipedia.org/wiki/Binary_logarithm#Algorithm
568 fixed log(fixed inp)
570 fixed x = inp;
571 fixed fp = int2fixed(1);
572 fixed res = int2fixed(0);
574 if(fp_lte(x, FP_ZERO))
576 return FP_MIN;
579 /* Integer part*/
580 /* while x<1 */
581 while(fp_lt(x, int2fixed(1)))
583 res = fp_sub(res, int2fixed(1));
584 x = fp_shl(x, 1);
586 /* while x>=2 */
587 while(fp_gte(x, int2fixed(2)))
589 res = fp_add(res, int2fixed(1));
590 x = fp_shr(x, 1);
593 /* Fractional part */
594 /* while fp > 0 */
595 while(fp_gt(fp, FP_ZERO))
597 fp = fp_shr(fp, 1);
598 x = fp_mul(x, x);
599 /* if x >= 2 */
600 if(fp_gte(x, int2fixed(2)))
602 x = fp_shr(x, 1);
603 res = fp_add(res, fp);
607 return res;
610 /*=================================================================*/
611 /* GUI Stuff */
612 /*=================================================================*/
614 /* The function name is pretty self-explaining ;) */
615 void print_int_xy(int x, int y, int v)
617 char temp[20];
618 #if LCD_DEPTH > 1
619 rb->lcd_set_foreground(front_color);
620 #endif
621 rb->snprintf(temp,20,"%d",v);
622 rb->lcd_putsxy(x,y,temp);
625 /* Print out the frequency etc */
626 void print_str(char* s)
628 #if LCD_DEPTH > 1
629 rb->lcd_set_foreground(front_color);
630 #endif
631 rb->lcd_putsxy(0, HZ_Y, s);
634 /* What can I say? Read the function name... */
635 void print_char_xy(int x, int y, char c)
637 char temp[2];
639 temp[0]=c;
640 temp[1]=0;
641 #if LCD_DEPTH > 1
642 rb->lcd_set_foreground(front_color);
643 #endif
645 rb->lcd_putsxy(x, y, temp);
648 /* Draw the note bitmap */
649 void draw_note(const char *note)
651 int i;
652 int note_x = (LCD_WIDTH - BMPWIDTH_pitch_notes) / 2;
653 int accidental_index = NOTE_INDEX_SHARP;
655 i = note[0]-'A';
657 if(note[1] == '#')
659 if(!(tuner_settings.use_sharps))
661 i = (i + 1) % 7;
662 accidental_index = NOTE_INDEX_FLAT;
665 vertical_picture_draw_sprite(rb->screens[0],
666 &note_bitmaps,
667 accidental_index,
668 LCD_WIDTH / 2,
669 note_y);
670 note_x = LCD_WIDTH / 2 - BMPWIDTH_pitch_notes;
673 vertical_picture_draw_sprite(rb->screens[0], &note_bitmaps, i,
674 note_x,
675 note_y);
677 /* Draw the red bar and the white lines */
678 void draw_bar(fixed wrong_by_cents)
680 unsigned n;
681 int x;
683 #ifdef HAVE_LCD_COLOR
684 rb->lcd_set_foreground(LCD_RGBPACK(255,255,255)); /* Color screens */
685 #elif LCD_DEPTH > 1
686 rb->lcd_set_foreground(LCD_BLACK); /* Greyscale screens */
687 #endif
689 rb->lcd_hline(0,LCD_WIDTH-1, BAR_HLINE_Y);
690 rb->lcd_hline(0,LCD_WIDTH-1, BAR_HLINE_Y2);
692 /* Draw graduation lines on the off-by readout */
693 for(n = 0; n <= GRADUATION; n++)
695 x = (LCD_WIDTH * n + GRADUATION / 2) / GRADUATION;
696 if (x >= LCD_WIDTH)
697 x = LCD_WIDTH - 1;
698 rb->lcd_vline(x, BAR_HLINE_Y, BAR_HLINE_Y2);
701 print_int_xy(lbl_x_minus_50 ,bar_grad_y, -50);
702 print_int_xy(lbl_x_minus_20 ,bar_grad_y, -20);
703 print_int_xy(lbl_x_0 ,bar_grad_y, 0);
704 print_int_xy(lbl_x_20 ,bar_grad_y, 20);
705 print_int_xy(lbl_x_50 ,bar_grad_y, 50);
707 #ifdef HAVE_LCD_COLOR
708 rb->lcd_set_foreground(LCD_RGBPACK(255,0,0)); /* Color screens */
709 #elif LCD_DEPTH > 1
710 rb->lcd_set_foreground(LCD_DARKGRAY); /* Greyscale screens */
711 #endif
713 if (fp_gt(wrong_by_cents, FP_ZERO))
715 rb->lcd_fillrect(bar_x_0, BAR_Y,
716 fixed2int(fp_mul(wrong_by_cents, LCD_FACTOR)), BAR_HEIGHT);
718 else
720 rb->lcd_fillrect(bar_x_0 + fixed2int(fp_mul(wrong_by_cents,LCD_FACTOR)),
721 BAR_Y,
722 fixed2int(fp_mul(wrong_by_cents, LCD_FACTOR)) * -1,
723 BAR_HEIGHT);
727 /* Calculate how wrong the note is and draw the GUI */
728 void display_frequency (fixed freq)
730 fixed ldf, mldf;
731 fixed lfreq, nfreq;
732 fixed orig_freq;
733 int i, note = 0;
734 char str_buf[30];
736 if (fp_lt(freq, FP_LOW))
737 freq = FP_LOW;
739 /* We calculate the frequency and its log as if */
740 /* the reference frequency of A were 440 Hz. */
741 orig_freq = freq;
742 lfreq = fp_add(log(freq), freq_A[tuner_settings.freq_A].logratio);
743 freq = fp_mul(freq, freq_A[tuner_settings.freq_A].ratio);
745 /* This calculates a log freq offset for note A */
746 /* Get the frequency to within the range of our reference table, */
747 /* i.e. into the right octave. */
748 while (fp_lt(lfreq, fp_sub(notes[0].logfreq, fp_shr(LOG_D_NOTE, 1))))
749 lfreq = fp_add(lfreq, LOG_2);
750 while (fp_gte(lfreq, fp_sub(fp_add(notes[0].logfreq, LOG_2),
751 fp_shr(LOG_D_NOTE, 1))))
752 lfreq = fp_sub(lfreq, LOG_2);
753 mldf = LOG_D_NOTE;
754 for (i=0; i<12; i++)
756 ldf = fp_gt(fp_sub(lfreq,notes[i].logfreq), FP_ZERO) ?
757 fp_sub(lfreq,notes[i].logfreq) : fp_neg(fp_sub(lfreq,notes[i].logfreq));
758 if (fp_lt(ldf, mldf))
760 mldf = ldf;
761 note = i;
764 nfreq = notes[note].freq;
765 while (fp_gt(fp_div(nfreq, freq), D_NOTE_SQRT))
766 nfreq = fp_shr(nfreq, 1);
768 while (fp_gt(fp_div(freq, nfreq), D_NOTE_SQRT))
769 nfreq = fp_shl(nfreq, 1);
771 ldf = fp_mul(int2fixed(1200), log(fp_div(freq,nfreq)));
773 rb->lcd_clear_display();
774 draw_bar(ldf); /* The red bar */
775 if(fp_round(freq) != 0)
777 draw_note(notes[note].name);
778 if(tuner_settings.display_hz)
780 rb->snprintf(str_buf,30, "%s : %d cents (%d.%02dHz)",
781 notes[note].name, fp_round(ldf) ,fixed2int(orig_freq),
782 fp_round(fp_mul(fp_frac(orig_freq),
783 int2fixed(DISPLAY_HZ_PRECISION))));
784 print_str(str_buf);
787 rb->lcd_update();
790 /*-----------------------------------------------------------------------
791 * Functions for the Yin algorithm
793 * These were all adapted from the versions in Aubio v0.3.2
794 * Here's what the Aubio documentation has to say:
796 * This algorithm was developped by A. de Cheveigne and H. Kawahara and
797 * published in:
799 * de Cheveign?, A., Kawahara, H. (2002) "YIN, a fundamental frequency
800 * estimator for speech and music", J. Acoust. Soc. Am. 111, 1917-1930.
802 * see http://recherche.ircam.fr/equipes/pcm/pub/people/cheveign.html
803 -------------------------------------------------------------------------*/
805 /* Find the index of the minimum element of an array of floats */
806 unsigned vec_min_elem(fixed *s, unsigned buflen)
808 unsigned j, pos=0.0f;
809 fixed tmp = s[0];
810 for (j=0; j < buflen; j++)
812 if(fp_gt(tmp, s[j]))
814 pos = j;
815 tmp = s[j];
818 return pos;
822 static inline fixed aubio_quadfrac(fixed s0, fixed s1, fixed s2, fixed pf)
824 /* Original floating point version: */
825 /* tmp = s0 + (pf/2.0f) * (pf * ( s0 - 2.0f*s1 + s2 ) -
826 3.0f*s0 + 4.0f*s1 - s2);*/
827 /* Converted to explicit operator precedence: */
828 /* tmp = s0 + ((pf/2.0f) * ((((pf * ((s0 - (2*s1)) + s2)) -
829 (3*s0)) + (4*s1)) - s2)); */
831 /* I made it look like this so I could easily track the precedence and */
832 /* make sure it matched the original expression */
833 /* Oy, this is when I really wish I could do C++ operator overloading */
834 fixed tmp = fp_add
837 fp_mul
839 fp_shr(pf, 1),
840 fp_sub
842 fp_add
844 fp_sub
846 fp_mul
849 fp_add
851 fp_sub
854 fp_shl(s1, 1)
859 fp_mul
861 float2fixed(3.0f),
865 fp_shl(s1, 2)
871 return tmp;
874 #define QUADINT_STEP float2fixed(1.0f/200.0f)
876 fixed ICODE_ATTR vec_quadint_min(fixed *x, unsigned bufsize, unsigned pos, unsigned span)
878 fixed res, frac, s0, s1, s2;
879 fixed exactpos = int2fixed(pos);
880 /* init resold to something big (in case x[pos+-span]<0)) */
881 fixed resold = FP_MAX;
883 if ((pos > span) && (pos < bufsize-span))
885 s0 = x[pos-span];
886 s1 = x[pos] ;
887 s2 = x[pos+span];
888 /* increase frac */
889 for (frac = float2fixed(0.0f);
890 fp_lt(frac, float2fixed(2.0f));
891 frac = fp_add(frac, QUADINT_STEP))
893 res = aubio_quadfrac(s0, s1, s2, frac);
894 if (fp_lt(res, resold))
896 resold = res;
898 else
900 /* exactpos += (frac-QUADINT_STEP)*span - span/2.0f; */
901 exactpos = fp_add(exactpos,
902 fp_sub(
903 fp_mul(
904 fp_sub(frac, QUADINT_STEP),
905 int2fixed(span)
907 int2fixed(span)
910 break;
914 return exactpos;
918 /* Calculate the period of the note in the
919 buffer using the YIN algorithm */
920 /* The yin pointer is just a buffer that the algorithm uses as a work
921 space. It needs to be half the length of the input buffer. */
923 fixed ICODE_ATTR pitchyin(int16_t *input, fixed *yin)
925 fixed retval;
926 unsigned j,tau = 0;
927 int period;
928 unsigned yin_size = tuner_settings.sample_size / 4;
930 fixed tmp = FP_ZERO, tmp2 = FP_ZERO;
931 yin[0] = int2fixed(1);
932 for (tau = 1; tau < yin_size; tau++)
934 yin[tau] = FP_ZERO;
935 for (j = 0; j < yin_size; j++)
937 tmp = fp_sub(int2mantissa(input[2 * j]),
938 int2mantissa(input[2 * (j + tau)]));
939 yin[tau] = fp_add(yin[tau], fp_mul(tmp, tmp));
941 tmp2 = fp_add(tmp2, yin[tau]);
942 if(!fp_equal(tmp2, FP_ZERO))
944 yin[tau] = fp_mul(yin[tau], fp_div(int2fixed(tau), tmp2));
946 period = tau - 3;
947 if(tau > 4 && fp_lt(yin[period],
948 yin_threshold_table[tuner_settings.yin_threshold])
949 && fp_lt(yin[period], yin[period+1]))
951 retval = vec_quadint_min(yin, yin_size, period, 1);
952 return retval;
955 retval = vec_quadint_min(yin, yin_size,
956 vec_min_elem(yin, yin_size), 1);
957 return retval;
958 /*return FP_ZERO;*/
961 /*-----------------------------------------------------------------*/
963 uint32_t ICODE_ATTR buffer_magnitude(int16_t *input)
965 unsigned n;
966 uint64_t tally = 0;
967 const unsigned size = tuner_settings.sample_size;
969 /* Operate on only one channel of the stereo signal */
970 for(n = 0; n < size; n+=2)
972 int s = input[n];
973 tally += s * s;
976 tally /= size / 2;
978 /* now tally holds the average of the squares of all the samples */
979 /* It must be between 0 and 0x7fff^2, so it fits in 32 bits */
980 return (uint32_t)tally;
983 /* Stop the recording when the buffer is full */
984 #ifndef SIMULATOR
985 void recording_callback(int status, void **start, size_t *size)
987 int tail = audio_tail ^ 1;
989 /* Do not overrun the reader. Reuse current buffer if full. */
990 if (tail != audio_head)
991 audio_tail = tail;
993 /* Always record full buffer, even if not required */
994 *start = audio_data[tail];
995 *size = BUFFER_SIZE * sizeof (int16_t);
997 (void)status;
999 #endif
1001 /* Start recording */
1002 static void record_data(void)
1004 #ifndef SIMULATOR
1005 /* Always record full buffer, even if not required */
1006 rb->pcm_record_data(recording_callback, audio_data[audio_tail],
1007 BUFFER_SIZE * sizeof (int16_t));
1008 #endif
1011 /* The main program loop */
1012 void record_and_get_pitch(void)
1014 int quit=0, button;
1015 bool redraw = true;
1016 /* For tracking the latency */
1018 long timer;
1019 char debug_string[20];
1021 #ifndef SIMULATOR
1022 fixed period;
1023 bool waiting = false;
1024 #else
1025 audio_tail = 1;
1026 #endif
1028 backlight_force_on();
1030 record_data();
1032 while(!quit)
1034 while (audio_head == audio_tail && !quit) /* wait for the buffer to be filled */
1036 button=pluginlib_getaction(HZ/100, plugin_contexts, PLA_ARRAY_COUNT);
1038 switch(button)
1040 case PLA_EXIT:
1041 quit=true;
1042 break;
1044 case PLA_CANCEL:
1045 rb->pcm_stop_recording();
1046 quit = main_menu() != 0;
1047 if(!quit)
1049 redraw = true;
1050 record_data();
1052 break;
1054 break;
1058 if(!quit)
1060 #ifndef SIMULATOR
1061 /* Only do the heavy lifting if the volume is high enough */
1062 if(buffer_magnitude(audio_data[audio_head]) >
1063 sqr(tuner_settings.volume_threshold *
1064 rb->sound_max(SOUND_MIC_GAIN)))
1066 waiting = false;
1067 redraw = false;
1069 #ifdef HAVE_SCHEDULER_BOOSTCTRL
1070 rb->trigger_cpu_boost();
1071 #endif
1072 #ifdef PLUGIN_USE_IRAM
1073 rb->memcpy(iram_audio_data, audio_data[audio_head],
1074 tuner_settings.sample_size * sizeof (int16_t));
1075 #endif
1076 /* This returns the period of the detected pitch in samples */
1077 period = pitchyin(iram_audio_data, yin_buffer);
1078 /* Hz = sample rate / period */
1079 if(fp_gt(period, FP_ZERO))
1081 display_frequency(fp_period2freq(period));
1083 else
1085 display_frequency(FP_ZERO);
1088 else if(redraw || !waiting)
1090 waiting = true;
1091 redraw = false;
1092 display_frequency(FP_ZERO);
1093 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1094 rb->cancel_cpu_boost();
1095 #endif
1098 /* Move to next buffer if not empty (but empty *shouldn't* happen
1099 * here). */
1100 if (audio_head != audio_tail)
1101 audio_head ^= 1;
1102 #else /* SIMULATOR */
1103 /* Display a preselected frequency */
1104 display_frequency(int2fixed(445));
1105 #endif
1108 rb->pcm_close_recording();
1109 rb->pcm_set_frequency(REC_SAMPR_DEFAULT | SAMPR_TYPE_REC);
1110 #ifdef HAVE_SCHEDULER_BOOSTCTRL
1111 rb->cancel_cpu_boost();
1112 #endif
1114 backlight_use_settings();
1117 /* Init recording, tuning, and GUI */
1118 void init_everything(void)
1120 /* Disable all talking before initializing IRAM */
1121 rb->talk_disable(true);
1123 PLUGIN_IRAM_INIT(rb);
1125 load_settings();
1127 /* Stop all playback (if no IRAM, otherwise IRAM_INIT would have) */
1128 rb->plugin_get_audio_buffer(NULL);
1130 /* --------- Init the audio recording ----------------- */
1131 rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
1132 rb->audio_set_input_source(INPUT_TYPE, SRCF_RECORDING);
1134 /* set to maximum gain */
1135 rb->audio_set_recording_gain(tuner_settings.record_gain,
1136 tuner_settings.record_gain,
1137 AUDIO_GAIN_MIC);
1139 /* Highest C on piano is approx 4.186 kHz, so we need just over
1140 * 8.372 kHz to pass it. */
1141 sample_rate = rb->round_value_to_list32(9000, rb->rec_freq_sampr,
1142 REC_NUM_FREQ, false);
1143 sample_rate = rb->rec_freq_sampr[sample_rate];
1144 rb->pcm_set_frequency(sample_rate | SAMPR_TYPE_REC);
1145 rb->pcm_init_recording();
1147 /* GUI */
1148 #if LCD_DEPTH > 1
1149 front_color = rb->lcd_get_foreground();
1150 #endif
1151 rb->lcd_getstringsize("X", &font_w, &font_h);
1153 bar_x_0 = LCD_WIDTH / 2;
1154 lbl_x_minus_50 = 0;
1155 lbl_x_minus_20 = (LCD_WIDTH / 2) -
1156 fixed2int(fp_mul(LCD_FACTOR, int2fixed(20))) - font_w;
1157 lbl_x_0 = (LCD_WIDTH - font_w) / 2;
1158 lbl_x_20 = (LCD_WIDTH / 2) +
1159 fixed2int(fp_mul(LCD_FACTOR, int2fixed(20))) - font_w;
1160 lbl_x_50 = LCD_WIDTH - 2 * font_w;
1162 bar_grad_y = BAR_Y - BAR_PADDING - font_h;
1163 /* Put the note right between the top and bottom text elements */
1164 note_y = ((font_h + bar_grad_y - note_bitmaps.slide_height) / 2);
1166 rb->talk_disable(false);
1170 enum plugin_status plugin_start(const void* parameter) NO_PROF_ATTR
1172 (void)parameter;
1174 init_everything();
1175 record_and_get_pitch();
1176 save_settings();
1178 return 0;