/*************************************************************************** * __________ __ ___. * 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 out how to change the recording samplerate -> should make it work * on the sansas, which only support 22050 Hz * - 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 * - Use a fixed-point FFT algorithm instead of a Schmitt trigger algorithm * * 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 * * CURRENT LIMITATIONS: * - Tuning is not very accurate, except when the tone is quite loud * - Everything is hard-coded: buttons, input type (mic, line), * buffer size -> nothing can be changed at runtime * - No menu * - No gapless recording (well, that's not really a problem, but anyway..) * - Samplerate is 88200, no matter what I do :( * - Does not work on Sansas (due to the fixed samplerate of 88200) */ #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 #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 #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 #define A_FREQ 440.0 /* Some constants for tuning */ #define D_NOTE 1.059463094359 #define LOG_D_NOTE 0.057762265047 #define D_NOTE_SQRT 1.029302236643 #define LOG_2 0.693147180559 #define LATENCY 2 /* Measurements per second */ #define B_SIZE (SAMPLE_RATE / LATENCY) #ifndef SIMULATOR #define BUFFER_SIZE 44100 /* The recording buffer */ #else #define BUFFER_SIZE 88200 #endif #define LCD_FACTOR (LCD_WIDTH/100.0) /* Change to AUDIO_SRC_LINE if you want to record from line-in */ #define INPUT_TYPE AUDIO_SRC_MIC static struct plugin_api* rb; static int block_size; static signed short audio_data[BUFFER_SIZE]; static signed short int schmitt_buffer[B_SIZE]; static signed short int *schmitt_pointer = NULL; static int recording=0; static double freqs[12]; static double lfreqs[12]; /* 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 ((double) y / ( 1 << 16 )); } /* 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(double wrong_by_cents) { rb->lcd_set_foreground(LCD_RGBPACK(255,0,0)); 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); rb->lcd_set_foreground(LCD_RGBPACK(255,255,255)); 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'; rb->lcd_set_foreground(LCD_RGBPACK(255,255,0)); 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(); } /* Raise the gain of the recorded sample - seems to make the pitch-detection a little bit more accurate */ void rec_peaks(signed short *data, int size) { int i, max, min; float min_fakt, max_fakt; for(max=0, min=0, i=0; imax) ? data[i] : max; min = (data[i]0) data[i] = (signed short) (max_fakt * data[i]); else data[i] = (signed short) (min_fakt * data[i]); } } /* 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); 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); } /* Calculate the pitch of the note in the buffer */ void schmitt_algorithm (int nframes, signed short int *indata) { int i, j; double trigfact = 0.6; for (i=0; i= block_size ) { int endpoint, startpoint, t1, t2, A1, A2, tc, schmitt_triggered; schmitt_pointer = schmitt_buffer; for (j=0,A1=0,A2=0; j0 && A1=t2 && schmitt_buffer[j+1]< t2) && j= t1); } else if (schmitt_buffer[j]>=t2 && schmitt_buffer[j+1] startpoint) { display_frequency(SAMPLE_RATE*(tc/(double)(endpoint-startpoint))); } } } } /* 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; while(!quit) { #ifndef SIMULATOR /* Start recording */ rb->pcm_record_data(recording_callback, (void *) audio_data, (size_t) BUFFER_SIZE*sizeof(signed short)); #endif 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 */ } rb->pcm_close_recording(); #ifdef SIMULATOR rb->sleep(700); #endif } /* Init recording, tuning, and GUI */ void init_everything(void) { int i; freqs[0]=A_FREQ; /* Tuning */ lfreqs[0]=log(freqs[0]); for (i=1; i<12; i++) { freqs[i] = freqs[i-1] * D_NOTE; lfreqs[i] = lfreqs[i-1] + LOG_D_NOTE; } block_size = SAMPLE_RATE/LATENCY; /* Recording */ schmitt_pointer = schmitt_buffer; rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); rb->audio_set_input_source(INPUT_TYPE, SRCF_RECORDING); rb->sound_set(SOUND_MIC_GAIN, 100); rb->audio_set_recording_gain(100,100, 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)-xx; bar_x_0=(LCD_WIDTH/2); bar_x_20=(LCD_WIDTH/2)+(int)(LCD_FACTOR*20)-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); */ } #ifdef SIMULATOR /* The simulator does not support recording, so I have to load a raw PCM file that contains the note; otherwise I couldn't test the GUI etc.. */ void init_sim(void) { int raw; raw=rb->open("/tones.raw", O_RDONLY); rb->read(raw, (void *) audio_data, BUFFER_SIZE*2); rb->close(raw); } #endif enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { (void)parameter; rb=api; #ifdef SIMULATOR init_sim(); #endif init_everything(); record_and_get_pitch(); return 0; }