You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
8.6KB

  1. /* main.c - chromatic guitar tuner
  2. *
  3. * Copyright (C) 2012 by Bjorn Roche
  4. *
  5. * Permission to use, copy, modify, and distribute this software and its
  6. * documentation for any purpose and without fee is hereby granted, provided
  7. * that the above copyright notice appear in all copies and that both that
  8. * copyright notice and this permission notice appear in supporting
  9. * documentation. This software is provided "as is" without express or
  10. * implied warranty.
  11. *
  12. */
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <string.h>
  16. #include <math.h>
  17. #include <signal.h>
  18. #include "libfft.h"
  19. #include <portaudio.h>
  20. /* -- some basic parameters -- */
  21. #define SAMPLE_RATE (8000)
  22. #define FFT_SIZE (8192)
  23. #define FFT_EXP_SIZE (13)
  24. #define NUM_SECONDS (20)
  25. /* -- functions declared and used here -- */
  26. void buildHammingWindow( float *window, int size );
  27. void buildHanWindow( float *window, int size );
  28. void applyWindow( float *window, float *data, int size );
  29. //a must be of length 2, and b must be of length 3
  30. void computeSecondOrderLowPassParameters( float srate, float f, float *a, float *b );
  31. //mem must be of length 4.
  32. float processSecondOrderFilter( float x, float *mem, float *a, float *b );
  33. void signalHandler( int signum ) ;
  34. static bool running = true;
  35. static char * NOTES[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
  36. /* -- main function -- */
  37. int main( int argc, char **argv ) {
  38. PaStreamParameters inputParameters;
  39. float a[2], b[3], mem1[4], mem2[4];
  40. float data[FFT_SIZE];
  41. float datai[FFT_SIZE];
  42. float window[FFT_SIZE];
  43. float freqTable[FFT_SIZE];
  44. char * noteNameTable[FFT_SIZE];
  45. float notePitchTable[FFT_SIZE];
  46. void * fft = NULL;
  47. PaStream *stream = NULL;
  48. PaError err = 0;
  49. struct sigaction action;
  50. // add signal listen so we know when to exit:
  51. action.sa_handler = signalHandler;
  52. sigemptyset (&action.sa_mask);
  53. action.sa_flags = 0;
  54. sigaction (SIGINT, &action, NULL);
  55. sigaction (SIGHUP, &action, NULL);
  56. sigaction (SIGTERM, &action, NULL);
  57. // build the window, fft, etc
  58. /*
  59. buildHanWindow( window, 30 );
  60. for( int i=0; i<30; ++i ) {
  61. for( int j=0; j<window[i]*50; ++j )
  62. printf( "*" );
  63. printf("\n");
  64. }
  65. exit(0);
  66. */
  67. buildHanWindow( window, FFT_SIZE );
  68. fft = initfft( FFT_EXP_SIZE );
  69. computeSecondOrderLowPassParameters( SAMPLE_RATE, 330, a, b );
  70. mem1[0] = 0; mem1[1] = 0; mem1[2] = 0; mem1[3] = 0;
  71. mem2[0] = 0; mem2[1] = 0; mem2[2] = 0; mem2[3] = 0;
  72. //freq/note tables
  73. for( int i=0; i<FFT_SIZE; ++i ) {
  74. freqTable[i] = ( SAMPLE_RATE * i ) / (float) ( FFT_SIZE );
  75. }
  76. for( int i=0; i<FFT_SIZE; ++i ) {
  77. noteNameTable[i] = NULL;
  78. notePitchTable[i] = -1;
  79. }
  80. for( int i=0; i<127; ++i ) {
  81. float pitch = ( 440.0 / 32.0 ) * pow( 2, (i-9.0)/12.0 ) ;
  82. if( pitch > SAMPLE_RATE / 2.0 )
  83. break;
  84. //find the closest frequency using brute force.
  85. float min = 1000000000.0;
  86. int index = -1;
  87. for( int j=0; j<FFT_SIZE; ++j ) {
  88. if( fabsf( freqTable[j]-pitch ) < min ) {
  89. min = fabsf( freqTable[j]-pitch );
  90. index = j;
  91. }
  92. }
  93. noteNameTable[index] = NOTES[i%12];
  94. notePitchTable[index] = pitch;
  95. //printf( "%f %d %s\n", pitch, index, noteNameTable[index] );
  96. }
  97. // initialize portaudio
  98. err = Pa_Initialize();
  99. if( err != paNoError ) goto error;
  100. inputParameters.device = Pa_GetDefaultInputDevice();
  101. inputParameters.channelCount = 1;
  102. inputParameters.sampleFormat = paFloat32;
  103. inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
  104. inputParameters.hostApiSpecificStreamInfo = NULL;
  105. printf( "Opening %s\n",
  106. Pa_GetDeviceInfo( inputParameters.device )->name );
  107. err = Pa_OpenStream( &stream,
  108. &inputParameters,
  109. NULL, //no output
  110. SAMPLE_RATE,
  111. FFT_SIZE,
  112. paClipOff,
  113. NULL,
  114. NULL );
  115. if( err != paNoError ) goto error;
  116. err = Pa_StartStream( stream );
  117. if( err != paNoError ) goto error;
  118. // this is the main loop where we listen to and
  119. // process audio.
  120. while( running )
  121. {
  122. // read some data
  123. err = Pa_ReadStream( stream, data, FFT_SIZE );
  124. if( err ) goto error; //FIXME: we don't want to err on xrun
  125. // low-pass
  126. //for( int i=0; i<FFT_SIZE; ++i )
  127. // printf( "in %f\n", data[i] );
  128. for( int j=0; j<FFT_SIZE; ++j ) {
  129. data[j] = processSecondOrderFilter( data[j], mem1, a, b );
  130. data[j] = processSecondOrderFilter( data[j], mem2, a, b );
  131. }
  132. // window
  133. applyWindow( window, data, FFT_SIZE );
  134. // do the fft
  135. for( int j=0; j<FFT_SIZE; ++j )
  136. datai[j] = 0;
  137. applyfft( fft, data, datai, false );
  138. //find the peak
  139. float maxVal = -1;
  140. int maxIndex = -1;
  141. for( int j=0; j<FFT_SIZE/2; ++j ) {
  142. float v = data[j] * data[j] + datai[j] * datai[j] ;
  143. /*
  144. printf( "%d: ", j*SAMPLE_RATE/(2*FFT_SIZE) );
  145. for( int i=0; i<sqrt(v)*100000000; ++i )
  146. printf( "*" );
  147. printf( "\n" );
  148. */
  149. if( v > maxVal ) {
  150. maxVal = v;
  151. maxIndex = j;
  152. }
  153. }
  154. float freq = freqTable[maxIndex];
  155. //find the nearest note:
  156. int nearestNoteDelta=0;
  157. while( true ) {
  158. if( nearestNoteDelta < maxIndex && noteNameTable[maxIndex-nearestNoteDelta] != NULL ) {
  159. nearestNoteDelta = -nearestNoteDelta;
  160. break;
  161. } else if( nearestNoteDelta + maxIndex < FFT_SIZE && noteNameTable[maxIndex+nearestNoteDelta] != NULL ) {
  162. break;
  163. }
  164. ++nearestNoteDelta;
  165. }
  166. char * nearestNoteName = noteNameTable[maxIndex+nearestNoteDelta];
  167. float nearestNotePitch = notePitchTable[maxIndex+nearestNoteDelta];
  168. float centsSharp = 1200 * log( freq / nearestNotePitch ) / log( 2.0 );
  169. // now output the results:
  170. printf("\033[2J\033[1;1H"); //clear screen, go to top left
  171. fflush(stdout);
  172. printf( "Tuner listening. Control-C to exit.\n" );
  173. printf( "%f Hz, %d : %f\n", freq, maxIndex, maxVal*1000 );
  174. printf( "Nearest Note: %s\n", nearestNoteName );
  175. if( nearestNoteDelta != 0 ) {
  176. if( centsSharp > 0 )
  177. printf( "%f cents sharp.\n", centsSharp );
  178. if( centsSharp < 0 )
  179. printf( "%f cents flat.\n", -centsSharp );
  180. } else {
  181. printf( "in tune!\n" );
  182. }
  183. printf( "\n" );
  184. int chars = 30;
  185. if( nearestNoteDelta == 0 || centsSharp >= 0 ) {
  186. for( int i=0; i<chars; ++i )
  187. printf( " " );
  188. } else {
  189. for( int i=0; i<chars+centsSharp; ++i )
  190. printf( " " );
  191. for( int i=chars+centsSharp<0?0:chars+centsSharp; i<chars; ++i )
  192. printf( "=" );
  193. }
  194. printf( " %2s ", nearestNoteName );
  195. if( nearestNoteDelta != 0 )
  196. for( int i=0; i<chars && i<centsSharp; ++i )
  197. printf( "=" );
  198. printf("\n");
  199. }
  200. err = Pa_StopStream( stream );
  201. if( err != paNoError ) goto error;
  202. // cleanup
  203. destroyfft( fft );
  204. Pa_Terminate();
  205. return 0;
  206. error:
  207. if( stream ) {
  208. Pa_AbortStream( stream );
  209. Pa_CloseStream( stream );
  210. }
  211. destroyfft( fft );
  212. Pa_Terminate();
  213. fprintf( stderr, "An error occured while using the portaudio stream\n" );
  214. fprintf( stderr, "Error number: %d\n", err );
  215. fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
  216. return 1;
  217. }
  218. void buildHammingWindow( float *window, int size )
  219. {
  220. for( int i=0; i<size; ++i )
  221. window[i] = .54 - .46 * cos( 2 * M_PI * i / (float) size );
  222. }
  223. void buildHanWindow( float *window, int size )
  224. {
  225. for( int i=0; i<size; ++i )
  226. window[i] = .5 * ( 1 - cos( 2 * M_PI * i / (size-1.0) ) );
  227. }
  228. void applyWindow( float *window, float *data, int size )
  229. {
  230. for( int i=0; i<size; ++i )
  231. data[i] *= window[i] ;
  232. }
  233. void computeSecondOrderLowPassParameters( float srate, float f, float *a, float *b )
  234. {
  235. float a0;
  236. float w0 = 2 * M_PI * f/srate;
  237. float cosw0 = cos(w0);
  238. float sinw0 = sin(w0);
  239. //float alpha = sinw0/2;
  240. float alpha = sinw0/2 * sqrt(2);
  241. a0 = 1 + alpha;
  242. a[0] = (-2*cosw0) / a0;
  243. a[1] = (1 - alpha) / a0;
  244. b[0] = ((1-cosw0)/2) / a0;
  245. b[1] = ( 1-cosw0) / a0;
  246. b[2] = b[0];
  247. }
  248. float processSecondOrderFilter( float x, float *mem, float *a, float *b )
  249. {
  250. float ret = b[0] * x + b[1] * mem[0] + b[2] * mem[1]
  251. - a[0] * mem[2] - a[1] * mem[3] ;
  252. mem[1] = mem[0];
  253. mem[0] = x;
  254. mem[3] = mem[2];
  255. mem[2] = ret;
  256. return ret;
  257. }
  258. void signalHandler( int signum ) { running = false; }