Index: apps/plugins/pitch.c =================================================================== --- apps/plugins/pitch.c (Revision 0) +++ apps/plugins/pitch.c (Revision 0) @@ -0,0 +1,600 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id:$ + * + * Copyright (C) 2008 Lechner Michael / smoking gnu + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * ---------------------------------------------------------------------------- + * + * INTRODUCTION: + * OK, this is an attempt to write an instrument tuner for rockbox. + * It uses a Schmitt trigger algorithm, which I copied from + * tuneit [ (c) 2004 Mario Lang ], for detecting the + * fundamental freqency of a sound. A FFT algorithm would be more accurate + * but also much slower. + * + * TODO: + * - Find someone who knows how recording actually works, and rewrite the + * recording code to use proper, gapless recording with a callback function + * that provides new buffer, instead of stopping and restarting recording + * everytime the buffer is full + * - Convert all floating point operations to fixed-point + * - Adapt the Yin FFT algorithm, which would reduce complexity from O(n^2) + * to O(nlogn), theoretically reducing latency by a factor of ~10. -David + * + * MAJOR CHANGES: + * 08.03.2008 Started coding + * 21.03.2008 Pitch detection works more or less + * Button definitions for most targets added + * 02.04.2008 Proper GUI added + * Todo, Major Changes and Current Limitations added + * 08.19.2009 Brought the code up to date with current plugin standards + * Made it work more nicely with color, BW and grayscale + * Changed pitch detection to use the Yin algorithm (better + * detection, but slower -- would be ~4x faster with + * fixed point math, I think). Code was poached from the + * Aubio sound processing library (aubio.org). -David + * + * CURRENT LIMITATIONS: + * - Everything is hard-coded: buttons, input type (mic, line), + * buffer size , Yin threshold -- nothing can be changed at runtime + * - No gapless recording (I think I know how to fix this but it would + * only save ~1/40th of a second per sample period -David) + * - Due to how the Yin algorithm works, there's high latency if there's no + * pitch being recorded or if the pitch is outside the algorithm's range. + * - Due to how the Yin algorithm works, latency is higher for lower + * frequencies. + * - In order to keep latency to a reasonable level I set the buffer + * size to 1024 samples, meaning the lowest detectable frequency is + * about SAMPLE_RATE / 512, or about 86Hz on devices with a 44.1k sample + * rate. In reality, it should properly detect a the pitch of a + * frequency of half that value if the signal includes any harmonics. + * - Untested on devices other than h120 -David + */ + +#include "plugin.h" + +PLUGIN_HEADER + +/* Button definitions and Samplerate defining*/ +#if CONFIG_KEYPAD == RECORDER_PAD /* Recorder */ +#define TUNER_QUIT BUTTON_OFF + +#elif CONFIG_KEYPAD == ARCHOS_AV300_PAD +#define TUNER_QUIT BUTTON_OFF + +#elif CONFIG_KEYPAD == ONDIO_PAD /* Ondio */ +#define TUNER_QUIT BUTTON_OFF + +#elif CONFIG_KEYPAD == PLAYER_PAD /* Player */ +#define TUNER_QUIT BUTTON_STOP + +#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) /* iRiver H1x0 && H3x0 */ +#define TUNER_QUIT BUTTON_OFF +#define SAMPLE_RATE 44100 +#define BW + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) /* iPods */ +#define TUNER_QUIT BUTTON_SELECT +#define SAMPLE_RATE 44100 +ifdecCL +#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD /* iAudio X5/M5 */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 44100 + +#elif CONFIG_KEYPAD == GIGABEAT_PAD /* GIGABEAT */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 44100 + +#elif CONFIG_KEYPAD == SANSA_E200_PAD /* Sansa E200 */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 22050 + +#elif CONFIG_KEYPAD == SANSA_C200_PAD /* Sansa C200 */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 22050 + +#elif CONFIG_KEYPAD == IRIVER_H10_PAD /* iriver H10 */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 44100 + +#elif CONFIG_KEYPAD == MROBE500_PAD /* M:robe 500 */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 44100 + +#elif CONFIG_KEYPAD == GIGABEAT_S_PAD /* Gigabeat S */ +#define TUNER_QUIT BUTTON_BACK +#define SAMPLE_RATE 44100 + +#elif CONFIG_KEYPAD == MROBE100_PAD /* M:robe 100 */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 44100 + +#else /* Everything else */ +#define TUNER_QUIT BUTTON_POWER +#define SAMPLE_RATE 44100 + +#endif + +/* Some constants for tuning */ +#define A_FREQ 440.0f +#define D_NOTE 1.059463094359f +#define LOG_D_NOTE 0.057762265047f +#define D_NOTE_SQRT 1.029302236643f +#define LOG_2 0.693147180559f + +/* The recording buffer size */ +/* This is how much is sampled at a time. */ +/* It also determines latency -- if BUFFER_SIZE == SAMPLE_RATE then */ +/* there'll be one sample per second, or a latency of one second. */ +/* Furthermore, the lowest detectable frequency will be about twice */ +/* the number of reads per second */ +/* For the Yin FFT algorithm this needs to be a power of 2 */ +#define BUFFER_SIZE 1024 + +#define LCD_FACTOR ((float)LCD_WIDTH / (float)100.0f) +/* The threshold for the YIN algorithm */ +#define YIN_THRESHOLD 0.15f + +/* Change to AUDIO_SRC_LINE if you want to record from line-in */ +#define INPUT_TYPE AUDIO_SRC_MIC + +static signed short audio_data[BUFFER_SIZE]; +float yin_buffer[BUFFER_SIZE / 2]; +static int recording=0; + +/* Frequencies of all the notes of the scale */ +static const float freqs[12] = +{ + 440.0f, /* A */ + 466.1637615f, /* A# */ + 493.8833013f, /* etc... */ + 523.2511306f, + 554.3652620f, + 587.3295358f, + 622.2539674f, + 659.2551138f, + 698.4564629f, + 739.9888454f, + 783.9908720f, + 830.6093952f +}; +/* logarithm of all the notes of the scale */ +static const float lfreqs[12] = +{ + 6.086774727f, + 6.144536992f, + 6.202299257f, + 6.260061522f, + 6.317823787f, + 6.375586052f, + 6.433348317f, + 6.491110582f, + 6.548872847f, + 6.606635112f, + 6.664397377f, + 6.722159642f +}; + +/* GUI */ +static unsigned back_color, front_color; +static int xx,yy; +static int bar_x_minus_50, bar_x_minus_20, bar_x_0, bar_x_20, bar_x_50; + +static const char *english_notes[12] = {"A","A#","B","C","C#","D","D#","E", +"F","F#", "G", "G#"}; +static const char gui_letters[8] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', '#'}; +static const char **notes = english_notes; + +/* Fixed-point Natural log */ +double log(double inp) +{ + int x; + long t,y; + + x = (int) ( inp * ( 1 << 16 ) ); + + y=0xa65af; + if(x<0x00008000) x<<=16, y-=0xb1721; + if(x<0x00800000) x<<= 8, y-=0x58b91; + if(x<0x08000000) x<<= 4, y-=0x2c5c8; + if(x<0x20000000) x<<= 2, y-=0x162e4; + if(x<0x40000000) x<<= 1, y-=0x0b172; + t=x+(x>>1); if((t&0x80000000)==0) x=t,y-=0x067cd; + t=x+(x>>2); if((t&0x80000000)==0) x=t,y-=0x03920; + t=x+(x>>3); if((t&0x80000000)==0) x=t,y-=0x01e27; + t=x+(x>>4); if((t&0x80000000)==0) x=t,y-=0x00f85; + t=x+(x>>5); if((t&0x80000000)==0) x=t,y-=0x007e1; + t=x+(x>>6); if((t&0x80000000)==0) x=t,y-=0x003f8; + t=x+(x>>7); if((t&0x80000000)==0) x=t,y-=0x001fe; + x=0x80000000-x; + y-=x>>15; + + return (float)y / (float)0x10000; +} + +/* The function name is pretty self-explaining ;) */ +void print_int_xy(int x, int y, int v) +{ + char temp[20]; + + rb->lcd_set_foreground(front_color); + rb->snprintf(temp,20,"%d",v); + rb->lcd_putsxy(x,y,temp); + rb->lcd_update(); +} + +/* Print out the frequency etc - will be removed later on */ +void print_str(char* s) +{ + rb->lcd_set_foreground(front_color); + rb->lcd_putsxy(0, 50, s); + rb->lcd_update(); +} + +/* What can I say? Read the function name... */ +void print_char_xy(int x, int y, char c) +{ + char temp[2]; + + temp[0]=c; + temp[1]=0; + rb->lcd_set_foreground(front_color); + + rb->lcd_putsxy(x, y, temp); +} + +/* Draw the red bar and the white lines */ +void draw_bar(float wrong_by_cents) +{ +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(LCD_RGBPACK(255,0,0)); /* Color screens */ +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(LCD_DARKGRAY); /* Greyscale screens */ +#else + rb->lcd_set_foreground(LCD_BLACK); /* Black and white screens */ +#endif + + if (wrong_by_cents > 0) + { + rb->lcd_fillrect(bar_x_0,90, (int)(wrong_by_cents * LCD_FACTOR), 20); + } + else + { + rb->lcd_fillrect(bar_x_0 + (int)(wrong_by_cents * LCD_FACTOR),90, + (int)(wrong_by_cents * LCD_FACTOR) * -1, 20); + } +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(LCD_RGBPACK(255,255,255)); /* Color screens */ +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(LCD_BLACK); /* Greyscale screens */ +#else + rb->lcd_set_foreground(LCD_BLACK); /* Black and white screens */ +#endif + + rb->lcd_hline(0,LCD_WIDTH-1, 85); + rb->lcd_hline(0,LCD_WIDTH-1, 115); + rb->lcd_vline(LCD_WIDTH / 2, 85, 115); + + print_int_xy(bar_x_minus_50 ,70, -50); + print_int_xy(bar_x_minus_20 ,70, -20); + print_int_xy(bar_x_0 ,70, 0); + print_int_xy(bar_x_20 ,70, 20); + print_int_xy(bar_x_50 ,70, 50); + + + + rb->lcd_update(); + +} + +/* Print the letters A-G and the # on the screen */ +void draw_letters(void) +{ + int i; + + rb->lcd_set_foreground(front_color); + + for (i=0; i<8; i++) + { + print_char_xy(i*(LCD_WIDTH / 8 ) + xx, 10, gui_letters[i]); + } +} + +/* Draw the yellow point(s) below the letters and the '#' */ +void draw_points(const char *s) +{ + int i; + + i = s[0]-'A'; +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(LCD_RGBPACK(255,255,0)); /* Color screens */ +#elif LCD_DEPTH > 1 + rb->lcd_set_foreground(LCD_DARKGRAY); /* Grey screens */ +#else + rb->lcd_set_foreground(LCD_BLACK); /* Black and White screens */ +#endif + + rb->lcd_fillrect(i*(LCD_WIDTH / 8 ) + xx, 25, yy, yy); + + if (s[1] == '#') + rb->lcd_fillrect(7*(LCD_WIDTH / 8 ) + xx, 25, yy, yy); + + rb->lcd_update(); +} + +/* Calculate how wrong the note is and draw the GUI */ +void display_frequency (double freq) +{ + double ldf, mldf; + double lfreq, nfreq; + int i, note = 0; + char str_buf[30]; + + if (freq < 1E-15) freq = 1E-15; + lfreq = log(freq); + while (lfreq < lfreqs[0]-LOG_D_NOTE/2.) lfreq += LOG_2; + while (lfreq >= lfreqs[0]+LOG_2-LOG_D_NOTE/2.) lfreq -= LOG_2; + mldf = LOG_D_NOTE; + for (i=0; i<12; i++) + { + ldf = ((lfreq-lfreqs[i]>0) ? lfreq-lfreqs[i] : -(lfreq-lfreqs[i])); + if (ldf < mldf) + { + mldf = ldf; + note = i; + } + } + nfreq = freqs[note]; + while (nfreq/freq > D_NOTE_SQRT) nfreq /= 2.0; + while (freq/nfreq > D_NOTE_SQRT) nfreq *= 2.0; + + ldf=1200*(log(freq/nfreq)/LOG_2); + + if ((int)freq == 0) /* prevents red bar at -32 cents when freq= 0*/ + { + ldf = 0; + } + + rb->snprintf(str_buf,30, "%s : %d cents (%dHz)", + notes[note], (int)ldf ,(int) freq); + + rb->lcd_clear_display(); + + draw_bar(ldf); /* The red bar */ + draw_letters(); /* The A-G letters and the # */ + draw_points(notes[note]); /* The yellow point(s) */ + print_str(str_buf); +} + +/*----------------------------------------------------------------------- + * Functions for the Yin algorithm + * + * These were all adapted from the versions in Aubio v0.3.2 + * Here's what the Aubio documentation has to say: + * + * This algorithm was developped by A. de Cheveigne and H. Kawahara and + * published in: + * + * de Cheveigné, A., Kawahara, H. (2002) "YIN, a fundamental frequency + * estimator for speech and music", J. Acoust. Soc. Am. 111, 1917-1930. + * + * see http://recherche.ircam.fr/equipes/pcm/pub/people/cheveign.html +-------------------------------------------------------------------------*/ + +/* Find the index of the minimum element of an array of floats */ +unsigned vec_min_elem(float *s, unsigned buflen) +{ + unsigned j, pos=0.0f; + float tmp = s[0]; + for (j=0; j < buflen; j++) + { + if(tmp > s[j]) + { + pos = j; + tmp = s[j]; + } + } + return pos; +} + +float aubio_quadfrac(float s0, float s1, float s2, float pf) +{ + float tmp = s0 + (pf/2.0f) * (pf * ( s0 - 2.0f*s1 + s2 ) - 3.0f*s0 + 4.0f*s1 - s2); + return tmp; +} + +float vec_quadint_min(float *x, unsigned bufsize, unsigned pos, unsigned span) +{ + float step = 1.0f/200.0f; + /* init resold to - something (in case x[pos+-span]<0)) */ + float res, frac, s0, s1, s2, exactpos = (float)pos, resold = 100000.0f; + if ((pos > span) && (pos < bufsize-span)) + { + s0 = x[pos-span]; + s1 = x[pos] ; + s2 = x[pos+span]; + /* increase frac */ + for (frac = 0.0f; frac < 2.0f; frac = frac + step) + { + res = aubio_quadfrac(s0, s1, s2, frac); + if (res < resold) + { + resold = res; + } + else + { + exactpos += (frac-step)*span - span/2.0f; + break; + } + } + } + return exactpos; +} + + +/* Calculate the period of the note in the + buffer using the YIN algorithm */ +/* The yin pointer is just a buffer that the algorithm uses as a work + space. It needs to be half the length of the input buffer. */ + +float pitchyin(int16_t *input, unsigned bufsize, float *yin) +{ + unsigned j,tau = 0; + unsigned yin_bufsize = bufsize / 2; + int period; + float tmp = 0.0f, tmp2 = 0.0f; + yin[0] = 1.0f; + for (tau = 1; tau < yin_bufsize; tau++) + { + yin[tau] = 0.0f; + for (j=0;j 4 && (yin[period] < YIN_THRESHOLD) && + (yin[period] < yin[period+1])) + { + return vec_quadint_min(yin, yin_bufsize, period, 1); + } + } + /*return vec_quadint_min(yin, yin_bufsize, vec_min_elem(yin, yin_bufsize), 1);*/ + return 0; +} + +/*-----------------------------------------------------------------*/ + +/* Stop the recording when the buffer is full */ +int recording_callback(int status) +{ + (void) status; + + rb->pcm_stop_recording(); + recording=0; + return -1; +} + +/* The main program loop */ +void record_and_get_pitch(void) +{ + int quit=0, button; + long timer; + char time_string[20]; + float period; + + rb->cpu_boost(true); + + while(!quit) + { + /* Start recording */ + rb->pcm_record_data(recording_callback, (void *) audio_data, + (size_t) BUFFER_SIZE*sizeof(signed short)); + recording=1; + + while (recording && !quit) /* wait for the buffer to be filled */ + { + rb->backlight_on(); + rb->yield(); + button=rb->button_get(false); + switch(button) + { + case TUNER_QUIT: + quit=true; + rb->yield(); + break; + + default: + rb->yield(); + + break; + } + } + + /*rec_peaks(audio_data, BUFFER_SIZE);*/ + /*schmitt_algorithm (BUFFER_SIZE, audio_data);*/ /* get the pitch */ + + /* Let's keep track of how long this takes */ + timer = *(rb->current_tick); + + /* This returns the period of the detected pitch in samples */ + period = pitchyin(audio_data, BUFFER_SIZE, yin_buffer); + /* Hz = sample rate / period */ + if(period != 0) + { + display_frequency((float)SAMPLE_RATE / period); + } + else + { + display_frequency(0); + } + + + /* Print out how long it took to find the pitch */ + rb->snprintf(time_string, 20, "latency: %ld", *(rb->current_tick) - timer); + rb->lcd_putsxy(0, 40, time_string); + rb->lcd_update(); + } + rb->pcm_close_recording(); + rb->cpu_boost(false); +} + +/* Init recording, tuning, and GUI */ +void init_everything(void) +{ + /* --------- Init the audio recording ----------------- */ + rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); + rb->audio_set_input_source(INPUT_TYPE, SRCF_RECORDING); + + /* This should really be wrapped in platform-specific #ifdefs */ + /* set to maximum gain as in as3514.c */ + rb->audio_set_recording_gain(39,39, AUDIO_GAIN_MIC); + + rb->pcm_set_frequency(SAMPLE_RATE); + rb->pcm_apply_settings(); + + rb->pcm_init_recording(); + + back_color = rb->lcd_get_background(); /* GUI */ + front_color = rb->lcd_get_foreground(); + rb->lcd_getstringsize("X", &xx, &yy); + + bar_x_minus_50=0; + bar_x_minus_20 = (LCD_WIDTH / 2) - + (int)(LCD_FACTOR * 20.0f) - xx; + bar_x_0 = LCD_WIDTH / 2; + bar_x_20 = (LCD_WIDTH / 2) + + (int)(LCD_FACTOR * 20.0f) - xx; + bar_x_50 = LCD_WIDTH - 2 * xx; + + /* print_int_xy(0,100,(int) *rb->rec_freq_sampr); + print_int_xy(0,110,(int) SAMPLE_RATE); */ +} + + +enum plugin_status plugin_start(const void* parameter) NO_PROF_ATTR +{ + (void)parameter; + + init_everything(); + record_and_get_pitch(); + + return 0; +} Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (Revision 22443) +++ apps/plugins/SOURCES (Arbeitskopie) @@ -1,4 +1,5 @@ /* plugins common to all models */ +pitch.c chessclock.c credits.c cube.c