X-Git-Url: https://git.sesse.net/?p=pitch;a=blobdiff_plain;f=pitch.cpp;h=97238f5f05b1a99113837029199f44434b1f220e;hp=8080748e17a01d29fa0b8f414c131e4173489176;hb=f4906bf100acb8c5875196b7c2306effdd6a091d;hpb=0564163ef86509fe45a4a4fdd5004be2bc3b0e6c diff --git a/pitch.cpp b/pitch.cpp index 8080748..97238f5 100644 --- a/pitch.cpp +++ b/pitch.cpp @@ -17,12 +17,19 @@ * http://www-ccrma.stanford.edu/~jos/parshl/Choice_Hop_Size.html). */ +#define EQUAL_TEMPERAMENT 0 +#define WELL_TEMPERED_GUITAR 1 + +#define TUNING WELL_TEMPERED_GUITAR + int get_dsp_fd(); void read_chunk(int fd, double *in, unsigned num_samples); void apply_window(double *in, double *out, unsigned num_samples); std::pair find_peak(double *in, unsigned num_samples); void find_peak_magnitudes(std::complex *in, double *out, unsigned num_samples); +std::pair adjust_for_overtones(std::pair base, double *in, unsigned num_samples); double bin_to_freq(double bin, unsigned num_samples); +double freq_to_bin(double freq, unsigned num_samples); std::string freq_to_tonename(double freq); std::pair interpolate_peak(double ym1, double y0, double y1); void print_spectrogram(double freq, double amp); @@ -53,9 +60,14 @@ int main() fftw_execute(p); find_peak_magnitudes(out, bins, FFT_LENGTH); std::pair peak = find_peak(bins, FFT_LENGTH); + peak = adjust_for_overtones(peak, bins, FFT_LENGTH); if (peak.first < 50.0 || peak.second - log10(FFT_LENGTH) < 0.0) { +#if TUNING == WELL_TEMPERED_GUITAR + printf("......\n"); +#else printf("............\n"); +#endif } else { print_spectrogram(peak.first, peak.second - log10(FFT_LENGTH)); } @@ -78,9 +90,14 @@ int get_dsp_fd() int chan = 1; ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &chan); - int rate = 22050; + int rate = SAMPLE_RATE; ioctl(fd, SOUND_PCM_WRITE_RATE, &rate); + int max_fragments = 2; + int frag_shift = ffs(FFT_LENGTH / OVERLAP) - 1; + int fragments = (max_fragments << 16) | frag_shift; + ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fragments); + ioctl(3, SNDCTL_DSP_SYNC, 0); return fd; @@ -217,6 +234,9 @@ std::pair find_peak(double *in, unsigned num_samples) } } + if (best_bin == 0 || best_bin == num_samples / 2) { + return std::make_pair(-1.0, 0.0); + } std::pair peak = interpolate_peak(in[best_bin - 1], in[best_bin], @@ -225,10 +245,65 @@ std::pair find_peak(double *in, unsigned num_samples) return std::make_pair(bin_to_freq(double(best_bin) + peak.first, num_samples), peak.second); } +// it's perhaps not ideal to _first_ find the peak and _then_ the harmonics -- +// ideally, one should find the set of all peaks and then determine the likely +// base from that... something for later. :-) +std::pair adjust_for_overtones(std::pair base, double *in, unsigned num_samples) +{ + double mu = base.first, var = 1.0 / (base.second * base.second); + + //printf("Base at %.2f (amp=%5.2fdB)\n", base.first, base.second); + + for (unsigned i = 2; i < 10; ++i) { + unsigned middle = unsigned(floor(freq_to_bin(base.first, num_samples) * i + 0.5)); + unsigned lower = middle - (i+1)/2, upper = middle + (i+1)/2; + + if (upper >= num_samples) + upper = num_samples - 2; + + // printf("Searching in [%u,%u] = %f..%f\n", lower, upper, bin_to_freq(lower, num_samples), bin_to_freq(upper, num_samples)); + + // search for a peak in this interval + double best_harmonics_freq = -1.0; + double best_harmonics_amp = -1.0; + for (unsigned j = lower; j <= upper; ++j) { + if (in[j] > in[j-1] && in[j] > in[j+1]) { + std::pair peak = + interpolate_peak(in[j - 1], + in[j], + in[j + 1]); + + if (peak.second > best_harmonics_amp) { + best_harmonics_freq = bin_to_freq(j + peak.first, num_samples); + best_harmonics_amp = peak.second; + } + } + } + + if (best_harmonics_amp <= 0.0) + continue; + + //printf("Found overtone %u at %.2f (amp=%5.2fdB)\n", i, best_harmonics_freq, + // best_harmonics_amp); + + double this_mu = best_harmonics_freq / double(i); + double this_var = 1.0 / (best_harmonics_amp * best_harmonics_amp); + + double k = var / (var + this_var); + mu = (1.0 - k) * mu + k * this_mu; + var *= (1.0 - k); + } + return std::make_pair(mu, base.second); +} + double bin_to_freq(double bin, unsigned num_samples) { return bin * SAMPLE_RATE / double(num_samples); } +double freq_to_bin(double freq, unsigned num_samples) +{ + return freq * double(num_samples) / double(SAMPLE_RATE); +} /* * Given three bins, find the interpolated real peak based @@ -301,6 +376,7 @@ std::string freq_to_tonename(double freq) return buf; } +#if TUNING == EQUAL_TEMPERAMENT void print_spectrogram(double freq, double amp) { std::string notenames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; @@ -332,3 +408,52 @@ void print_spectrogram(double freq, double amp) printf("]\n"); } +#else +struct note { + char notename[16]; + double freq; +}; +static note notes[] = { + { "E-3", 110.0 * (3.0/4.0) }, + { "A-3", 110.0 }, + { "D-4", 110.0 * (4.0/3.0) }, + { "G-4", 110.0 * (4.0/3.0)*(4.0/3.0) }, + { "B-4", 440.0 * (3.0/4.0)*(3.0/4.0) }, + { "E-5", 440.0 * (3.0/4.0) } +}; + +void print_spectrogram(double freq, double amp) +{ + double best_away = 999999999.9; + unsigned best_away_ind = 0; + + for (unsigned i = 0; i < sizeof(notes)/sizeof(note); ++i) { + double half_notes_away = 12.0 * log2(freq / notes[i].freq); + if (fabs(half_notes_away) < fabs(best_away)) { + best_away = half_notes_away; + best_away_ind = i; + } + } + + for (unsigned i = 0; i < sizeof(notes)/sizeof(note); ++i) + if (i == best_away_ind) + printf("#"); + else + printf("."); + + printf(" (%s %+.2f, %5.2fHz) [%5.2fdB] [", notes[best_away_ind].notename, best_away, freq, amp); + + for (int i = -10; i <= 10; ++i) { + if (best_away >= (i-0.5) * 0.05 && best_away < (i+0.5) * 0.05) { + printf("#"); + } else { + if (i == 0) { + printf("|"); + } else { + printf("-"); + } + } + } + printf("]\n"); +} +#endif