├── .gitignore ├── manifest └── MANIFEST.MF ├── octaveTest ├── chord.wav ├── sandersAndWeissGuitaSynthesization │ ├── excite-picked-nodamp.wav │ ├── lagrange.m │ ├── notes.m │ ├── getexcitesignal.m │ └── kspluck.m ├── getSalience.m ├── cancelPitch.m ├── whiten.m ├── README ├── testPitchDetect.m ├── polyphonicPitchDetect.m ├── detectF0s.m ├── testPitchDetect2.m ├── testChord.m └── createConstants.m ├── src └── timo │ └── tuner │ ├── Analysis │ ├── Functions.java │ ├── Analysis.java │ ├── FFT.java │ ├── Complex.java │ ├── FFT_fit.java │ └── Klapuri.java │ ├── DrawImage │ └── DrawImage.java │ ├── Capture │ └── Capture.java │ └── ui │ └── PolyphonicPitchDetection.java └── README /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | -------------------------------------------------------------------------------- /manifest/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Main-Class: timo.tuner.ui.PolyphonicPitchDetection 2 | Permissions: all-permissions 3 | 4 | -------------------------------------------------------------------------------- /octaveTest/chord.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjrantal/PolyphonicPitchDetection/HEAD/octaveTest/chord.wav -------------------------------------------------------------------------------- /octaveTest/sandersAndWeissGuitaSynthesization/excite-picked-nodamp.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjrantal/PolyphonicPitchDetection/HEAD/octaveTest/sandersAndWeissGuitaSynthesization/excite-picked-nodamp.wav -------------------------------------------------------------------------------- /octaveTest/getSalience.m: -------------------------------------------------------------------------------- 1 | function salience = getSalience(whitened,constants) 2 | salience = zeros(1,length(constants.f0cands)); 3 | for i= 1:length(constants.f0cands) 4 | findices = constants.f0candsFreqBins(i).binIndices; 5 | j=1:length(findices); 6 | salience(i) = sum((constants.samplingRate*constants.freq(findices(j))+constants.alpha)./(j.*constants.samplingRate.*constants.freq(findices(j))+constants.beta).*whitened(findices(j))); 7 | end 8 | endfunction; 9 | -------------------------------------------------------------------------------- /octaveTest/sandersAndWeissGuitaSynthesization/lagrange.m: -------------------------------------------------------------------------------- 1 | function h = lagrange(N, delay) 2 | %code taken from http://ccrma-www.stanford.edu/~jos/waveguide/Matlab_Code_Lagrange_Interpolation.html 3 | %LAGRANGE h=lagrange(N, delay) returns order N FIR 4 | % filter h which implements given delay 5 | % (in samples). For best results 6 | % delay should be near N/2 +/- 1. 7 | 8 | n=0:N; 9 | h=ones(1,N+1); 10 | for k=0:N 11 | index = find(n ~= k); 12 | h(index) = h(index) * (delay-k)./(n(index)-k); 13 | end -------------------------------------------------------------------------------- /octaveTest/sandersAndWeissGuitaSynthesization/notes.m: -------------------------------------------------------------------------------- 1 | A = [27.5 55.00 110.00 220.00 440.00 880.00]; 2 | Asharp = [29.14 58.27 116.54 233.08 466.16 932.32]; 3 | B = [30.87 61.74 123.48 246.96 493.92 987.84]; 4 | C = [32.71 65.41 130.82 261.64 523.28 1046.56]; 5 | Csharp = [34.65 69.30 138.60 277.20 554.40 1108.80]; 6 | D = [36.71 73.42 146.84 293.68 587.36 1174.72]; 7 | Dsharp = [38.89 77.78 155.56 311.12 622.24 1244.48]; 8 | E = [44.21 82.41 164.82 329.64 659.28 1318.56]; 9 | F = [43.66 87.31 174.62 349.24 698.48 1396.96]; 10 | Fsharp = [46.25 92.50 185.00 370.00 740.00 1480.00]; 11 | G = [49.00 98.00 196.00 392.00 784.00 1568.00]; 12 | -------------------------------------------------------------------------------- /octaveTest/cancelPitch.m: -------------------------------------------------------------------------------- 1 | function [whitened detectedFreqs] = cancelPitch(whitened,detectedFreqs,constants,index) 2 | %Frequency cancellation 3 | binsToCancel = constants.f0candsFreqBins(index).binIndices; 4 | for j = 1:length(binsToCancel) 5 | %cancel adjacent bins as well 6 | for k =-1:1 7 | detectedFreqs(binsToCancel(j)+k) = detectedFreqs(binsToCancel(j)+k)+((constants.samplingRate*constants.freq(binsToCancel(j)+k)+constants.alpha)/(j*constants.samplingRate*constants.freq(binsToCancel(j)+k)+constants.beta))*whitened(binsToCancel(j)+k); 8 | subtract = detectedFreqs(binsToCancel(j)+k)*constants.dee; 9 | whitened(binsToCancel(j)+k)=max([0 whitened(binsToCancel(j)+k)-subtract]); 10 | end 11 | end 12 | endfunction 13 | -------------------------------------------------------------------------------- /octaveTest/sandersAndWeissGuitaSynthesization/getexcitesignal.m: -------------------------------------------------------------------------------- 1 | function e = getexcitesignal(B, A, W, f, fs) 2 | %Takes a sound sample and puts it through an inverse filter 3 | %to find the corresponding excitation signal 4 | % 5 | %B = numerator coeeficients of loop filter 6 | %A = denominator coeeficients of loop filter 7 | %W = sound sample to filter 8 | %f = fundamental frequency of W 9 | %fs = sampling frequency 10 | % 11 | %NOTE: the excitation signal returned will be the same length as the input 12 | % signal. Since it dies away so quickly it makes sense to only save 13 | % the first 300-500 ms. 14 | 15 | %generate excitation signal by inverse filtering of recorded guitar note 16 | 17 | N=fix(fs/f); 18 | 19 | 20 | %inverse filter: A(z) = 1 - H(z)z^-N 21 | 22 | %H(z) is IIR, so break up into numerator and denominator: 23 | hnum = B; 24 | hden = A; 25 | 26 | %A(z) = (Hd(z) - Hn(z)z^-N)/Hd(z) 27 | n = [hden zeros(1, N - length(hden)) -hnum]; 28 | d = hden; 29 | 30 | 31 | e = filter(n, d, W); 32 | -------------------------------------------------------------------------------- /octaveTest/whiten.m: -------------------------------------------------------------------------------- 1 | 2 | %Signal whitening approach from Kalpuri 2006 page 2, heading 2.1 3 | function whitened = whiten(dataIn,constants) 4 | whitened = zeros(1,length(dataIn)); 5 | %Calculate standard deviations and bandwise compression coefficients within frequencybands 6 | stdb = zeros(1,size(constants.Hb,2)); 7 | gammab = zeros(1,size(constants.Hb,2)); 8 | for i = 1:size(constants.Hb,2) 9 | stdb(i) = sqrt(sum(constants.Hb(:,i)'.*(dataIn.^2))/length(dataIn)); 10 | gammab(i) = stdb(i)^(0.33-1); 11 | end 12 | %Interpolate gammab 13 | gamma = zeros(1,constants.fftWindow+1); 14 | %Set the compression coefficients for prior to below the first centre band and above the final 15 | gamma(1:find(constants.freq >= constants.cb(2),1,'first')) = gammab(1); 16 | gamma(find(constants.freq >= constants.cb(31),1,'first'):length(gamma)) = gammab(length(gammab)); 17 | for i = 1:(length(gammab)-1) 18 | initF = find(constants.freq >= constants.cb(i+1),1,'first'); 19 | endF = find(constants.freq >= constants.cb(i+2),1,'first'); 20 | gamma(initF:endF) = linspace(gammab(i),gammab(i+1),endF-initF+1); 21 | end 22 | %Whiten the signal 23 | whitened = gamma.*dataIn; 24 | endfunction 25 | -------------------------------------------------------------------------------- /src/timo/tuner/Analysis/Functions.java: -------------------------------------------------------------------------------- 1 | /* 2 | This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 16 | unmodified. I have not attached a copy of the GNU license to the source... 17 | 18 | Copyright (C) 2011-2012 Timo Rantalainen 19 | */ 20 | 21 | 22 | package timo.tuner.Analysis; 23 | import java.util.*; 24 | public class Functions{ 25 | public static double max(double[] data){ 26 | double[] temp = (double[]) data.clone(); 27 | Arrays.sort(temp); 28 | return temp[temp.length-1]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /octaveTest/README: -------------------------------------------------------------------------------- 1 | Octave implementation of the Java Polyphonic pitch detection software to try to find the bugs. 2 | 3 | Copied Sanders and Weis "Synthesizing a Guitar Using Physical Modeling Techniques" (http://www.ee.columbia.edu/~ronw/dsp/ visited 9th November 2013) matlab code that synthesizes (acoustic) guitar based on waveguide model and a recorded excitation signal (which I copied without permission as well the "excite-picked-nodamp.wav"-file). Made minimal modifications to produce test signals for polyphonic pitch detection. 4 | 5 | The candidate frequencies need to be re-worked to correspond to actual notes. I used the fft bins, which probably makes the script unable to detect the actual pitch. 6 | 7 | Candidate frequencies created with the equation (taken from http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html) 8 | 9 | f = f0*a^n 10 | 11 | where f0 = 55 Hz (a fixed note, which must be defined. I used 55 Hz, since that's the lowest frequency I'm interested in instead of 440 Hz) 12 | a = 2^(1/12) 13 | n = the number of half steps from the fixed note 14 | 15 | Usage: 16 | testChord %To create the chord.wav for pitch detection 17 | testPitchDetect %To detect pitch from the chord.wav 18 | 19 | Written by Timo Rantalainen tjrantal at gmail dot com 2013. GPL 3.0 or above applies to the code. 20 | -------------------------------------------------------------------------------- /octaveTest/sandersAndWeissGuitaSynthesization/kspluck.m: -------------------------------------------------------------------------------- 1 | function Y=kspluck(f,length, fs, excitation, B, A, p) 2 | %Karplus-Strong model with additional paremeters 3 | % ks(f,length) 4 | % f=frequency 5 | % length=time (seconds) 6 | % fs = sampling freqency 7 | % excitation=string excitation signal 8 | % B = numerator coefficients of loop filter 9 | % A = denominator coefficients of loop filter 10 | % p = pluck position along waveguide (0 < p < 1 - fraction of waveguide length) 11 | 12 | N=fix(fs/f); 13 | 14 | %modify length of excitation signal to match desired duration 15 | if(max(size(excitation)) <= length*fs) 16 | X = [excitation zeros(1,length*fs - max(size(excitation)))]; 17 | else 18 | X = excitation(1:length*fs) ; 19 | end 20 | 21 | hnum = B; 22 | hden = A; 23 | 24 | %lagrange interpolation filter to account for fractional delay 25 | % (keeps the string in tune with the desired frequency) 26 | l=lagrange(3,f/fs); 27 | 28 | % Hd(z)L(z)z^-N 29 | b1=[zeros(1,N) conv(l, hden)]; 30 | 31 | % Hd(z) - Hl(z)L(z)z^-N 32 | a1=[hden zeros(1,N-max(size(hden))) -1*conv(hnum,l)]; 33 | 34 | %pluck location: 35 | p = round(p*N); 36 | 37 | P = filter([1 zeros(1,p-1) -1], 1, X); 38 | 39 | %no initial conditions 40 | y=filter(b1,a1,P); 41 | 42 | Y = y; 43 | 44 | -------------------------------------------------------------------------------- /octaveTest/testPitchDetect.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | close all; 3 | clc; 4 | 5 | test = wavread('chord.wav'); 6 | constants = struct(); 7 | constants.samplingRate = 44100; %Hz 8 | constants.desiredPitchDetectionSampleLength = 0.1; %Seconds 9 | constants.fftWindow = 2^nextpow2(constants.samplingRate*constants.desiredPitchDetectionSampleLength); 10 | constants.actualSL = constants.fftWindow/constants.samplingRate; 11 | constants = createConstants(constants); 12 | constants.fh = figure('position',[10 10 1000 500]); 13 | constants.sp(1) = subplot(3,1,1); 14 | plot(test); 15 | hold on; 16 | constants.overlayH = plot(1:constants.fftWindow,test(1:constants.fftWindow),'r'); 17 | constants.sp(2) = subplot(3,1,2); 18 | constants.freqVisualizationIndices = find(constants.freq <=10000); 19 | constants.fftH = plot(constants.freq(constants.freqVisualizationIndices),zeros(1,length(constants.freqVisualizationIndices))); 20 | constants.sp(3) = subplot(3,1,3); 21 | constants.whitenedH = plot(constants.freq(constants.freqVisualizationIndices),zeros(1,length(constants.freqVisualizationIndices))); 22 | hold on; 23 | constants.detectedH = plot(constants.freq(constants.freqVisualizationIndices),zeros(1,length(constants.freqVisualizationIndices)),'r'); 24 | for i = 1:constants.fftWindow/2:length(test)-constants.fftWindow; 25 | constants.epoch = i:i+constants.fftWindow-1; 26 | detectedF0s = polyphonicPitchDetect(test(constants.epoch),constants); 27 | printF0s = ""; 28 | for j = 1:length(detectedF0s) 29 | printF0s = [printF0s ' F' num2str(j-1) ' ' num2str(detectedF0s(j))]; 30 | end 31 | disp(printF0s); 32 | end 33 | -------------------------------------------------------------------------------- /octaveTest/polyphonicPitchDetect.m: -------------------------------------------------------------------------------- 1 | function pitches = polyphonicPitchDetect(signalIn,constants) 2 | %Apply hann windowing 3 | hannWindowed = signalIn.*hanning(length(signalIn)); 4 | %Double the signal length by appending zeroes 5 | if size(hannWindowed,1) > size(hannWindowed,2) 6 | hannWindowed = hannWindowed'; 7 | end 8 | appended = [hannWindowed zeros(1,length(hannWindowed))]; 9 | fftSignal = fft(appended); 10 | fftSignal= fftSignal./(length(fftSignal)/2+1); 11 | fftSignal(1) = fftSignal(1)/2; 12 | fftAmp = abs(fftSignal(1:(constants.fftWindow+1))); %Ignore the second half of the fft 13 | whitened = whiten(fftAmp,constants); 14 | %Implement estimating polyphony and frequency cancellation here 15 | pitches = []; 16 | detectedF0s = 0; 17 | detectedFreqs = zeros(size(whitened,1),size(whitened,2)); 18 | smax = 0; 19 | S = []; 20 | S(1) = 0; 21 | %Loop while smax is increasing 22 | while S(length(S)) >=smax 23 | salience = getSalience(whitened,constants); 24 | [salmax index] = max(salience); 25 | [whitened detectedFreqs] = cancelPitch(whitened,detectedFreqs,constants,index); 26 | %estimate smax here 27 | detectedF0s = detectedF0s+1; 28 | sumDetected = sum(detectedFreqs); 29 | S = [S sumDetected/(detectedF0s^0.7)]; 30 | if S(length(S)) > smax 31 | smax = S(length(S)); 32 | pitches(detectedF0s ) = constants.f0cands(index); 33 | end 34 | end 35 | %Polyphony and pitches estimated 36 | set(constants.overlayH,'xdata',constants.epoch,'ydata',hannWindowed); 37 | set(constants.fftH,'ydata',fftAmp(constants.freqVisualizationIndices)); 38 | set(constants.whitenedH,'ydata',whitened(constants.freqVisualizationIndices)); 39 | set(constants.detectedH,'ydata',detectedFreqs(constants.freqVisualizationIndices)); 40 | drawnow(); 41 | endfunction 42 | -------------------------------------------------------------------------------- /octaveTest/detectF0s.m: -------------------------------------------------------------------------------- 1 | function F0s = detectF0s(whitened, constants) 2 | Vector F0s = new Vector(); 3 | Vector S = new Vector(); 4 | S.add(0.0); 5 | %Begin extracting F0s 6 | double smax=0; 7 | int index =0; 8 | int detectedF0s = 0; 9 | %F0 detection 10 | resultsk = zeros(1,length(constants.freq)); 11 | double[] salience; 12 | double summa; 13 | while (S.lastElement() >= smax){ 14 | %Calculating the salience function (the hard way...) 15 | salience = getSalience(whitened,constants); 16 | [salmax index] = max(salience); 17 | 18 | %Salience calculated 19 | ++detectedF0s; 20 | F0s.add(constants.freq[index]); %First F0 21 | 22 | %Frequency cancellation 23 | for (int j = 1; j<=constants.harmonics;++j){ 24 | if (index*j+1 0){ 28 | whitened[index*j+i]= whitened[index*j+i]-resultsk[index*j+i]*dee; 29 | }else{ 30 | whitened[index*j+i]=0; 31 | } 32 | } 33 | } 34 | } 35 | %requency cancellation done 36 | %Polyphony estimation 37 | if (S.size() < detectedF0s){ 38 | S.add(0.0); 39 | } 40 | summa = 0; 41 | for (int i = 0; i< resultsk.length;++i){ 42 | summa += resultsk[i]; 43 | } 44 | S.set(S.size()-1,summa/Math.pow(detectedF0s,0.7)); 45 | if (S.lastElement() > smax){ 46 | smax = S.lastElement(); 47 | } 48 | %Polyphony estimated 49 | } 50 | %The last F0 is extra... 51 | %System.out.println("Remove extra"); 52 | if (F0s.size() > 1){ 53 | F0s.remove(F0s.size()-1); 54 | } 55 | return F0s; 56 | endfunction; 57 | -------------------------------------------------------------------------------- /octaveTest/testPitchDetect2.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | close all; 3 | clc; 4 | 5 | constants = struct(); 6 | constants.samplingRate = 44100; %Hz 7 | tempTime = [0:(constants.samplingRate-1)]/constants.samplingRate; 8 | test= zeros(size(tempTime,1),size(tempTime,2)); 9 | for i = 1:20 10 | test = test+(1/(2+i))*(sin(2*pi*82.4*i*tempTime) ... 11 | +sin(2*pi*123.5*i*tempTime) ... 12 | +sin(2*pi*164.8*i*tempTime)); 13 | end 14 | test = test'; 15 | wavwrite(test,constants.samplingRate,16,'testSound.wav'); 16 | constants.desiredPitchDetectionSampleLength = 0.08; %Seconds 17 | constants.fftWindow = 2^nextpow2(constants.samplingRate*constants.desiredPitchDetectionSampleLength); 18 | constants.actualSL = constants.fftWindow/constants.samplingRate; 19 | constants = createConstants(constants); 20 | constants.fh = figure('position',[10 10 1000 500]); 21 | constants.sp(1) = subplot(3,1,1); 22 | plot(test); 23 | %keyboard; 24 | hold on; 25 | constants.overlayH = plot(1:constants.fftWindow,test(1:constants.fftWindow),'r'); 26 | constants.sp(2) = subplot(3,1,2); 27 | constants.freqVisualizationIndices = find(constants.freq <=10000); 28 | constants.fftH = plot(constants.freq(constants.freqVisualizationIndices),zeros(1,length(constants.freqVisualizationIndices))); 29 | constants.sp(3) = subplot(3,1,3); 30 | constants.whitenedH = plot(constants.freq(constants.freqVisualizationIndices),zeros(1,length(constants.freqVisualizationIndices))); 31 | hold on; 32 | constants.detectedH = plot(constants.freq(constants.freqVisualizationIndices),zeros(1,length(constants.freqVisualizationIndices)),'r'); 33 | for i = 1:constants.fftWindow/2:length(test)-constants.fftWindow; 34 | constants.epoch = i:i+constants.fftWindow-1; 35 | detectedF0s = polyphonicPitchDetect(test(constants.epoch),constants); 36 | printF0s = ""; 37 | for j = 1:length(detectedF0s) 38 | printF0s = [printF0s ' F' num2str(j-1) ' ' num2str(detectedF0s(j))]; 39 | end 40 | disp(printF0s); 41 | keyboard; 42 | end 43 | -------------------------------------------------------------------------------- /octaveTest/testChord.m: -------------------------------------------------------------------------------- 1 | % This program is free software: you can redistribute it and/or modify 2 | % it under the terms of the GNU General Public License as published by 3 | % the Free Software Foundation, either version 3 of the License, or 4 | % (at your option) any later version. 5 | % 6 | % This program is distributed in the hope that it will be useful, 7 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | % GNU General Public License for more details. 10 | % 11 | % You should have received a copy of the GNU General Public License 12 | % along with this program. If not, see . 13 | % 14 | % N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 15 | % unmodified. I have not attached a copy of the GNU license to the source... 16 | % 17 | % Copyright (C) 2013 Timo Rantalainen 18 | % 19 | %Modified from mary.m downloaded from http://www.ee.columbia.edu/~ronw/dsp/ 20 | 21 | close all; 22 | clear all; 23 | clc; 24 | addpath('sandersAndWeissGuitaSynthesization'); 25 | 26 | notes; %load note frequencies 27 | %getexcitesignal; 28 | e = wavread('sandersAndWeissGuitaSynthesization/excite-picked-nodamp.wav'); 29 | e = e'; 30 | 31 | fs = 44100; 32 | 33 | %loop filter: 34 | Bpoles = [0.8995 0.1087]; 35 | Apoles = [1 0.0136]; 36 | noteOnMultiplier = 3;%1;% 37 | 38 | 39 | o = 2; %octave 40 | nd = .3; %note duration 41 | p = .9; %pluck position 42 | 43 | %testNote = kspluck(E(o), nd, fs, e, B, A, p); 44 | note1 = kspluck(E(o), noteOnMultiplier*nd, fs, e, Bpoles, Apoles, p); %82.410Hz 45 | note2 = kspluck(B(o+1), noteOnMultiplier*nd, fs, e, Bpoles, Apoles, p); %123.48 46 | note3 = kspluck(E(o+1), noteOnMultiplier*nd, fs, e, Bpoles, Apoles, p); %164.82 47 | 48 | %Add delays... 49 | delayPadding = zeros(1,int32(fs*nd/noteOnMultiplier)); 50 | note1 = [note1 delayPadding delayPadding]; 51 | note2 = [delayPadding note2 delayPadding]; 52 | note3 = [delayPadding delayPadding note3]; 53 | 54 | L = mean([note1; note2; note3]); 55 | 56 | figure 57 | plot(L) 58 | wavwrite(L',fs,16,'chord.wav'); 59 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | An open source (licensed with GPL 2.0 and above) Java-program to do polyphonic pitch detection from standard audio in (uses the default audio in with 16-bit mono at 44100 Hz, modify Capture.java to suit your setup). 2 | 3 | The project is an attempt to implement the polyphonic pitch detection method described in Anssi Klapuri's (list of publications http://www.cs.tut.fi/~klap/iiro/ and http://www.elec.qmul.ac.uk/people/anssik/publications.htm) congress publication 4 | Klapuri, A., " Multiple fundamental frequency estimation by summing harmonic amplitudes," 7th International Conference on Music Information Retrieval (ISMIR-06), Victoria, Canada, Oct. 2006. 5 | http://www.cs.tut.fi/sgn/arg/klap/klap2006ismir.pdf 6 | and doctoral thesis: 7 | Klapuri, A. " Signal processing methods for the automatic transcription of music," Ph.D. thesis, Tampere University of Technology, Finland, April 2004. 8 | http://www.cs.tut.fi/sgn/arg/klap/phd/klap_phd.pdf 9 | 10 | The current version needs to be restarted sometimes in order to initiate the sound capture (could be specific to my latop and operating system). The current version is able to detect two notes from my electric guitar consistently. I am not likely to improve the implementation substantially, unless I notice some bugs. 11 | 12 | I started the project with the hopes of implementing polyphonic pitch detection to be used in a Frets of Fire/Guitar Hero type game by pluggin an actual electric guitar into the audio in. Also, tuning guitar with polyphonic pitch detection would be pretty nifty (http://www.tcelectronic.com/polytune.asp), or the pitch detection could be used for off-line transcription. The current version has a massive lag on my HP mini laptop, which makes it unsuitable for real-time stuff. N.B. the octave implementation in the octaveTest folder might be more useful for off-line transcription (although frequency cancellation isn't implemented similarly to the Java version). 13 | 14 | Contributions from other people taken from the internet (in addition to Java-tutorials for GUI, sound capture etc.); 15 | FFT-transform http://introcs.cs.princeton.edu/java/97data/FFT.java.html 16 | 17 | Written by Timo Rantalainen tjrantal at gmail dot com 2011 - 2014. Please let me know if you improve the code and make it work! 18 | 19 | Use the ant build.xml to build the project and to create a jar. 20 | 21 | Execute JAR: 22 | java -jar PolyphonicPitchDetection.jar 23 | -------------------------------------------------------------------------------- /src/timo/tuner/Analysis/Analysis.java: -------------------------------------------------------------------------------- 1 | /* 2 | This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 16 | unmodified. I have not attached a copy of the GNU license to the source... 17 | 18 | Copyright (C) 2011-2012 Timo Rantalainen 19 | */ 20 | 21 | package timo.tuner.Analysis; 22 | import timo.tuner.ui.*; 23 | public class Analysis{ 24 | /*Implement analysis here*/ 25 | public double[] amplitudes; 26 | public double[] hanData; 27 | public double maximum; 28 | public double whitenedMaximum; 29 | public Klapuri klapuri; 30 | public Analysis(short[] dataIn,PolyphonicPitchDetection mainProgram){ 31 | /*Apply Hann windowing function*/ 32 | hanData = hannWindow(dataIn); 33 | /*Append zeros*/ 34 | Complex[] x = new Complex[hanData.length*2]; 35 | for (int i = 0; i < hanData.length*2.0; ++i) { 36 | if (i >= hanData.length){ 37 | x[i] = new Complex(0.0,0.0); //Zero padding 38 | }else{ 39 | x[i] = new Complex(hanData[i], 0); 40 | } 41 | } 42 | Complex[] y = FFT.fft(x); //Calculate FFT 43 | amplitudes = FFT.calculateAmplitudes(y); 44 | maximum = Functions.max(amplitudes); 45 | klapuri = new Klapuri(amplitudes,maximum,mainProgram); //Klapuri picth detection 46 | whitenedMaximum = Functions.max(klapuri.whitened); 47 | if (whitenedMaximum == 0){ 48 | whitenedMaximum = 1.0; 49 | } 50 | } 51 | 52 | /*Hann window function taken from http://en.wikipedia.org/wiki/Window_function 53 | w(n) = 0.5(1-cos(2pin/(N-1)) 54 | */ 55 | double[] hannWindow(short[] dataIn){ 56 | double[] hanData = new double[dataIn.length]; 57 | double N = (double) dataIn.length; 58 | for (int i = 0;i 0 3 | constants = constantsIn; 4 | else 5 | constants = struct(); 6 | constants.fftWindow = 2^12; %Data points 7 | constants.samplingRate = 44100; %Hz 8 | end 9 | %Frequencies, signal will be zero padded to twice its length 10 | constants.freq = zeros(1,constants.fftWindow); 11 | b = 1:(constants.fftWindow+1); 12 | constants.freq= (b-1)*(constants.samplingRate/2)/constants.fftWindow; 13 | %Create constants required for polyphonicPitchDetect 14 | %CB filtebank 15 | constants.cb = zeros(1,32); 16 | %CB filterbank has always the same values 17 | b = 1:32; 18 | constants.cb = 229.0*(10.0.^(b/21.4)-1.0); %frequency division 19 | %Pre-calculate the frequency bank 20 | %Signal whitening approach from Kalpuri 2006 page 2, heading 2.1 21 | %Create the bandpass filterbank, 30 triangular power response filters 22 | constants.Hb = zeros(constants.fftWindow+1,length(constants.cb)-2); 23 | for i=2:(length(constants.cb)-1) %Consider the centrebands, triangle from (i-1:i+1) 24 | kk=find(constants.freq >=constants.cb(i-1),1,'first'); %The lower border 25 | while (constants.freq(kk) <= constants.cb(i+1)) %loop till the higher border 26 | if constants.freq(kk) <= constants.cb(i) 27 | constants.Hb(kk,i-1) = (1-abs(constants.cb(i)-constants.freq(kk))/(constants.cb(i)-constants.cb(i-1))); 28 | else %Descending limb 29 | constants.Hb(kk,i-1) = (1-abs(constants.cb(i)-constants.freq(kk))/(constants.cb(i+1)-constants.cb(i))); 30 | end 31 | kk = kk+1; 32 | end 33 | end 34 | 35 | 36 | %constants.f0index = find(constants.freq <= 1500 & constants.freq >=60); 37 | %constants.f0cands = constants.freq(constants.f0index); 38 | %Create actual candidate notes (http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html) 39 | n = (1:(5*12))-1; %Five octaves of candidate notes. Use half step to get out of tune freqs 40 | f0 = 55.0; %Hz, A three octaves below A above the middle C 41 | a = 2.0^(1.0/12.0); 42 | constants.f0cands = f0*a.^n; 43 | %Pre-calculate frequency bins to include for a specific f0 candidate 44 | constants.harmonics = 20; 45 | halfBinWidth = ((constants.samplingRate/2)/constants.fftWindow)/2; 46 | for i = 1:length(constants.f0cands) 47 | binIndices = []; 48 | for h = 1:constants.harmonics 49 | testi = find(constants.freq > (constants.f0cands(i)*h-halfBinWidth) & constants.freq < (constants.f0cands(i)*h+halfBinWidth)); 50 | binIndices = [binIndices testi]; 51 | end 52 | constants.f0candsFreqBins(i).binIndices = binIndices; 53 | end 54 | 55 | constants.alpha = 52.0; %Hz 56 | constants.beta = 320.0; %Hz 57 | constants.dee = 0.89; 58 | 59 | endfunction 60 | -------------------------------------------------------------------------------- /src/timo/tuner/Analysis/FFT.java: -------------------------------------------------------------------------------- 1 | /*Copied from http://introcs.cs.princeton.edu/java/97data/FFT.java.html 15.07.2011 2 | linked from http://introcs.cs.princeton.edu/java/97data/ 3 | Modified minimally by Timo Rantalainen 2011 4 | */ 5 | 6 | /* 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 21 | unmodified. I have not attached a copy of the GNU license to the source... 22 | Also, The licensing might not apply to the work I've simply copied from the 23 | web page indicated on the top of this file. 24 | 25 | Copyright (C) 2011 Timo Rantalainen 26 | */ 27 | 28 | 29 | /************************************************************************* 30 | * Compilation: javac FFT.java 31 | * Execution: java FFT N 32 | * Dependencies: Complex.java 33 | * 34 | * Compute the FFT and inverse FFT of a length N complex sequence. 35 | * Bare bones implementation that runs in O(N log N) time. Our goal 36 | * is to optimize the clarity of the code, rather than performance. 37 | * 38 | * Limitations 39 | * ----------- 40 | * - assumes N is a power of 2 41 | * 42 | * - not the most memory efficient algorithm (because it uses 43 | * an object type for representing complex numbers and because 44 | * it re-allocates memory for the subarray, instead of doing 45 | * in-place or reusing a single temporary array) 46 | * 47 | *************************************************************************/ 48 | 49 | 50 | 51 | package timo.tuner.Analysis; 52 | 53 | public class FFT { 54 | 55 | // compute the FFT of x[], assuming its length is a power of 2 56 | public static Complex[] fft(Complex[] x) { 57 | int N = x.length; 58 | 59 | // base case 60 | if (N == 1) return new Complex[] { x[0] }; 61 | 62 | // radix 2 Cooley-Tukey FFT 63 | if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } 64 | 65 | // fft of even terms 66 | Complex[] even = new Complex[N/2]; 67 | for (int k = 0; k < N/2; k++) { 68 | even[k] = x[2*k]; 69 | } 70 | Complex[] q = fft(even); 71 | 72 | // fft of odd terms 73 | Complex[] odd = even; // reuse the array 74 | for (int k = 0; k < N/2; k++) { 75 | odd[k] = x[2*k + 1]; 76 | } 77 | Complex[] r = fft(odd); 78 | 79 | // combine 80 | Complex[] y = new Complex[N]; 81 | for (int k = 0; k < N/2; k++) { 82 | double kth = -2 * k * Math.PI / N; 83 | Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); 84 | y[k] = q[k].plus(wk.times(r[k])); 85 | y[k + N/2] = q[k].minus(wk.times(r[k])); 86 | } 87 | return y; 88 | } 89 | 90 | 91 | /*Reconstruct the original signal from N first coefficients*/ 92 | public static double[] reconstruct(Complex[] y,int N){ 93 | int length = y.length; 94 | double[] reconstructed = new double[length]; 95 | double t; 96 | for (int i = 0; i < length; ++i){ //reconstruct the signal 97 | reconstructed[i] = y[0].re()/((double)length); 98 | t = ((double)i)/(length); 99 | for (int j = 1; j F0s){ 84 | Graphics2D g2 = bufferedImage.createGraphics(); 85 | g2.setColor(new Color(255,255,255)); 86 | g2.setFont(new Font("Helvetica",Font.PLAIN,24)); 87 | f0s = ""; 88 | if (F0s.size() > 0){ 89 | for (int i = 0; i= limit){val = limit-1;} 179 | if (val < 0){val = 0;} 180 | return val; 181 | } 182 | 183 | public void paint(Graphics g) { 184 | g.drawImage(bufferedImage,0,0,null); 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/timo/tuner/Analysis/FFT_fit.java: -------------------------------------------------------------------------------- 1 | /*Copied from http://introcs.cs.princeton.edu/java/97data/FFT.java.html 15.07.2011 2 | linked from http://introcs.cs.princeton.edu/java/97data/ 3 | Modified for reconstructing the signal from N first coefficients by 4 | Timo Rantalainen 2011 5 | */ 6 | 7 | /* 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see . 20 | 21 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 22 | unmodified. I have not attached a copy of the GNU license to the source... 23 | Also, The licensing might not apply to the work I've simply copied from the 24 | web page indicated on the top of this file. 25 | 26 | Copyright (C) 2011 Timo Rantalainen 27 | */ 28 | 29 | 30 | /************************************************************************* 31 | * Compilation: javac FFT.java 32 | * Execution: java FFT N 33 | * Dependencies: Complex.java 34 | * 35 | * Compute the FFT and inverse FFT of a length N complex sequence. 36 | * Bare bones implementation that runs in O(N log N) time. Our goal 37 | * is to optimize the clarity of the code, rather than performance. 38 | * 39 | * Limitations 40 | * ----------- 41 | * - assumes N is a power of 2 42 | * 43 | * - not the most memory efficient algorithm (because it uses 44 | * an object type for representing complex numbers and because 45 | * it re-allocates memory for the subarray, instead of doing 46 | * in-place or reusing a single temporary array) 47 | * 48 | *************************************************************************/ 49 | 50 | 51 | 52 | package timo.tuner.Analysis; 53 | 54 | public class FFT_fit { 55 | 56 | // compute the FFT of x[], assuming its length is a power of 2 57 | public static Complex[] fft(Complex[] x) { 58 | int N = x.length; 59 | 60 | // base case 61 | if (N == 1) return new Complex[] { x[0] }; 62 | 63 | // radix 2 Cooley-Tukey FFT 64 | if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } 65 | 66 | // fft of even terms 67 | Complex[] even = new Complex[N/2]; 68 | for (int k = 0; k < N/2; k++) { 69 | even[k] = x[2*k]; 70 | } 71 | Complex[] q = fft(even); 72 | 73 | // fft of odd terms 74 | Complex[] odd = even; // reuse the array 75 | for (int k = 0; k < N/2; k++) { 76 | odd[k] = x[2*k + 1]; 77 | } 78 | Complex[] r = fft(odd); 79 | 80 | // combine 81 | Complex[] y = new Complex[N]; 82 | for (int k = 0; k < N/2; k++) { 83 | double kth = -2 * k * Math.PI / N; 84 | Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); 85 | y[k] = q[k].plus(wk.times(r[k])); 86 | y[k + N/2] = q[k].minus(wk.times(r[k])); 87 | } 88 | return y; 89 | } 90 | 91 | 92 | // compute the inverse FFT of x[], assuming its length is a power of 2 93 | public static Complex[] ifft(Complex[] x) { 94 | int N = x.length; 95 | Complex[] y = new Complex[N]; 96 | 97 | // take conjugate 98 | for (int i = 0; i < N; i++) { 99 | y[i] = x[i].conjugate(); 100 | } 101 | 102 | // compute forward FFT 103 | y = fft(y); 104 | 105 | // take conjugate again 106 | for (int i = 0; i < N; i++) { 107 | y[i] = y[i].conjugate(); 108 | } 109 | 110 | // divide by N 111 | for (int i = 0; i < N; i++) { 112 | y[i] = y[i].times(1.0 / N); 113 | } 114 | 115 | return y; 116 | 117 | } 118 | 119 | // display an array of Complex numbers to standard output 120 | public static void show(Complex[] x, String title) { 121 | System.out.println(title); 122 | System.out.println("-------------------"); 123 | for (int i = 0; i < x.length; i++) { 124 | System.out.println(x[i]); 125 | } 126 | System.out.println(); 127 | } 128 | 129 | public static void show(double[] x, String title) { 130 | System.out.println(title); 131 | System.out.println("-------------------"); 132 | for (int i = 0; i < x.length; i++) { 133 | System.out.println(x[i]); 134 | } 135 | System.out.println(); 136 | } 137 | 138 | /*Reconstruct the original signal from N first coefficients*/ 139 | public static double[] reconstruct(Complex[] y,int N){ 140 | int length = y.length; 141 | double[] reconstructed = new double[length]; 142 | double t; 143 | for (int i = 0; i < length; ++i){ //reconstruct the signal 144 | reconstructed[i] = y[0].re()/((double)length); 145 | t = ((double)i)/(length); 146 | for (int j = 1; j. 14 | 15 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 16 | unmodified. I have not attached a copy of the GNU license to the source... 17 | 18 | Copyright (C) 2011-2012 Timo Rantalainen 19 | */ 20 | 21 | package timo.tuner.Analysis; 22 | import java.util.*; 23 | import timo.tuner.ui.*; 24 | public class Klapuri{ 25 | public double[] whitened; 26 | public double[] gammaCoeff; 27 | public Vector f0s; 28 | PolyphonicPitchDetection mainProgram; 29 | int harmonics = 20; 30 | int surroundingBins = 1; 31 | double alpha = 52.0; //Hz 32 | double beta = 320.0; //Hz 33 | double dee = 0.89; 34 | public Klapuri(double[] data, double max,PolyphonicPitchDetection mainProgram){ 35 | //whitened = (double[]) data.clone(); 36 | this.mainProgram = mainProgram; 37 | /*Whiten the data*/ 38 | whitened = whiten(data,mainProgram); 39 | f0s = detectF0s(whitened,mainProgram); 40 | } 41 | 42 | Vector detectF0s(double[] whitened, PolyphonicPitchDetection mainProgram){ 43 | Vector F0s = new Vector(); 44 | Vector S = new Vector(); 45 | S.add(0.0); 46 | //Begin extracting F0s 47 | double smax=0; 48 | int index =0; 49 | int detectedF0s = 0; 50 | //F0 detection 51 | double[] resultsk = new double [mainProgram.freq.length]; 52 | double[] salience; 53 | double summa; 54 | while (S.lastElement() >= smax){ 55 | //Calculating the salience function (the hard way...) 56 | salience = new double [mainProgram.f0cands.length]; 57 | double salmax = 0; 58 | 59 | for (int i= 0;i salmax){ 66 | index= i; 67 | salmax = salience[i]; 68 | } 69 | } 70 | 71 | //Salience calculated 72 | ++detectedF0s; 73 | F0s.add(mainProgram.f0cands[index]); //First F0 74 | 75 | /*Replace this with using f0cands indices at some point!*/ 76 | //Frequency cancellation 77 | //System.out.println("To cancellation "+mainProgram.f0index[index].size()+" "+mainProgram.f0indHarm[index].size()); 78 | int[] tempCancelled = new int[resultsk.length]; 79 | for (int j = 0; j 0){ 93 | whitened[mainProgram.f0index[index].get(j)+i]= 94 | whitened[mainProgram.f0index[index].get(j)+i] 95 | -resultsk[mainProgram.f0index[index].get(j)+i]*dee; 96 | }else{ 97 | whitened[mainProgram.f0index[index].get(j)+i]=0; 98 | } 99 | tempCancelled[mainProgram.f0index[index].get(j)+i] = 1; 100 | } 101 | 102 | } 103 | 104 | } 105 | //System.out.println("Cancellation done"); 106 | //requency cancellation done 107 | //Polyphony estimation 108 | if (S.size() < detectedF0s){ 109 | S.add(0.0); 110 | } 111 | summa = 0; 112 | for (int i = 0; i< resultsk.length;++i){ 113 | summa += resultsk[i]; 114 | } 115 | S.set(S.size()-1,summa/Math.pow(detectedF0s,0.7)); 116 | if (S.lastElement() > smax){ 117 | smax = S.lastElement(); 118 | } 119 | //Polyphony estimated 120 | } 121 | //The last F0 is extra... 122 | //System.out.println("Remove extra"); 123 | if (F0s.size() > 1){ 124 | F0s.remove(F0s.size()-1); 125 | } 126 | return F0s; 127 | } 128 | 129 | double[] whiten(double[] dataIn,PolyphonicPitchDetection mainProgram){ 130 | double[] whitened = new double[dataIn.length]; 131 | 132 | /*Calculate signal energies in filter windows??*/ 133 | Vector gammab = new Vector(); 134 | Vector stdb = new Vector(); 135 | 136 | int kk; 137 | /*The filter bank Hb could be pre-calculated, to be implemented...*/ 138 | for (int i = 0;i. 14 | 15 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 16 | unmodified. I have not attached a copy of the GNU license to the source... 17 | 18 | Copyright (C) 2011-2012 Timo Rantalainen 19 | */ 20 | 21 | package timo.tuner.Capture; 22 | 23 | import timo.tuner.ui.*; /*Import ui*/ 24 | import timo.tuner.Analysis.*; /*Import analysis*/ 25 | import timo.tuner.DrawImage.*; /*Import DrawImage*/ 26 | import java.io.*; /*ByteArrayStream*/ 27 | import javax.sound.sampled.*; /*Sound capture*/ 28 | /*Debugging, write signal into a file*/ 29 | import java.nio.*; 30 | 31 | public class Capture implements Runnable{ 32 | /*Implement analysis here*/ 33 | 34 | AudioFormat aFormat; 35 | TargetDataLine line; 36 | DataLine.Info info; 37 | PolyphonicPitchDetection mainProgram; 38 | int bitDepth; 39 | int bitSelection; 40 | int stereo; 41 | /*Constructor*/ 42 | public Capture(int bitDepthIn, PolyphonicPitchDetection mainProgram){ 43 | bitDepth = bitDepthIn; 44 | bitSelection = bitDepth/8; 45 | this.mainProgram = mainProgram; 46 | mainProgram.rawFigure.f0s = null; 47 | stereo = 1; /*Capture mono*/ 48 | } 49 | 50 | public void run() { 51 | aFormat = new AudioFormat(mainProgram.samplingRate,bitDepth,stereo,true,false); 52 | info = new DataLine.Info(TargetDataLine.class, aFormat); 53 | System.out.println(info); 54 | try{ 55 | line = (TargetDataLine) AudioSystem.getLine(info); 56 | line.open(aFormat,line.getBufferSize()); 57 | line.start(); //Start capturing 58 | int bufferSize = mainProgram.fftWindow*bitSelection*stereo; 59 | byte buffer[] = new byte[bufferSize]; 60 | int testC = 0; 61 | while (mainProgram.continueCapturing) { 62 | int count = line.read(buffer, 0, buffer.length); /*Blocking call to read*/ 63 | //System.out.println("Got data "+count); 64 | if (count > 0) { 65 | if (bitSelection ==1){ 66 | //mainProgram.rawFigure.drawImage(buffer,mainProgram.imWidth,mainProgram.imHeight); 67 | /*Add pitch detection here for 8 bit, not implemented...*/ 68 | } 69 | if (bitSelection ==2){ 70 | short[] data = byteArrayToShortArray(buffer); 71 | 72 | if (false){ 73 | /*Build test signal*/ 74 | double[] tempSignal = new double[data.length]; 75 | for (int i = 0;i. 14 | 15 | N.B. the above text was copied from http://www.gnu.org/licenses/gpl.html 16 | unmodified. I have not attached a copy of the GNU license to the source... 17 | 18 | Copyright (C) 2011-2012 Timo Rantalainen 19 | */ 20 | 21 | /* 22 | Written by Timo Rantalainen tjrantal@gmail.com 2010 (C++ version) - 2012 (Java version) 23 | Based on Anssi Klapuri's (list of publications http://www.cs.tut.fi/~klap/iiro/ and http://www.elec.qmul.ac.uk/people/anssik/publications.htm) congress publication 24 | Klapuri, A., " Multiple fundamental frequency estimation by summing harmonic amplitudes," 7th International Conference on Music Information Retrieval (ISMIR-06), Victoria, Canada, Oct. 2006. 25 | http://www.cs.tut.fi/sgn/arg/klap/klap2006ismir.pdf 26 | and doctoral thesis: 27 | Klapuri, A. " Signal processing methods for the automatic transcription of music," Ph.D. thesis, Tampere University of Technology, Finland, April 2004. 28 | http://www.cs.tut.fi/sgn/arg/klap/phd/klap_phd.pdf 29 | 30 | Contributions from other people taken from the internet (in addition to Java-tutorials for GUI, sound capture etc.) 31 | FFT-transform 32 | 33 | Required class files (in addition to this one..). 34 | ReadStratecFile.java //Stratec pQCT file reader 35 | DrawImage.java //Visualize image in a panel 36 | SelectROI.java 37 | AnalyzeRoi.java //Analysis calculations 38 | 39 | JAVA compilation: 40 | javac -cp '.:' ui/PolyphonicPitchDetection.java \ 41 | Capture/Capture.java \ 42 | DrawImage/DrawImage.java \ 43 | Analysis/Analysis.java \ 44 | Analysis/Complex.java \ 45 | Analysis/FFT.java \ 46 | Analysis/Functions.java \ 47 | Analysis/Klapuri.java 48 | JAR building: 49 | jar cfe PolyphonicPitchDetection.jar ui.PolyphonicPitchDetection ui DrawImage Analysis Capture 50 | 51 | */ 52 | package timo.tuner.ui; 53 | import javax.swing.*; //GUI commands swing 54 | import java.awt.event.*; //Events & Actionlistener 55 | import java.io.*; //File IO 56 | import java.lang.Math; 57 | import java.awt.*; 58 | import java.awt.geom.Line2D; 59 | import javax.swing.event.*; 60 | import javax.swing.border.*; 61 | import java.util.Vector; 62 | import java.util.ArrayList; 63 | import java.util.Enumeration; 64 | import java.io.*; 65 | import javax.sound.sampled.*; 66 | import java.awt.font.*; 67 | import java.text.*; 68 | import java.awt.image.*; 69 | import java.awt.image.DataBuffer; 70 | 71 | import timo.tuner.Analysis.*; //Polyphonic analysis 72 | import timo.tuner.Capture.*; //Sound capture 73 | import timo.tuner.DrawImage.*; //Drawing images 74 | 75 | public class PolyphonicPitchDetection extends JPanel implements ActionListener { 76 | JButton beginPitchDetection; 77 | JButton endPitchDetection; 78 | public DrawImage fftFigure; 79 | public DrawImage rawFigure; 80 | public DrawImage whitenedFftFigure; 81 | public int fftWindow = 4096; /*FFT window width ~0.1 s -> Max ~600 bpm*/ 82 | public float samplingRate = 44100; 83 | public static int imWidth =800; 84 | public static int imHeight =250; 85 | public static int harmonics = 20; 86 | public boolean continueCapturing; 87 | public static int w; 88 | public static int h; 89 | static int traces = 2; /*how many traces are we plotting...*/ 90 | public double[] cb; /*Klapuri whitening ranges*/ 91 | public ArrayList[] Hb; /*filter bank for whitening*/ 92 | public ArrayList[] hbIndices; /*filter bank indices for whitening*/ 93 | public double[] freq; /*FFT fequency bins*/ 94 | public double[] f0cands; /*Klapuri F0 candidates*/ 95 | public ArrayList[] f0index; /*Klapuri F0 candidate indices*/ 96 | public ArrayList[] f0indHarm; /*Klapuri F0 candidate indices harmonics*/ 97 | public PolyphonicPitchDetection(){ /*Constructor*/ 98 | 99 | JPanel buttons = new JPanel(); /*Panel for start and stop*/ 100 | /*Begin button*/ 101 | beginPitchDetection= new JButton("Begin pitch detection"); 102 | beginPitchDetection.setMnemonic(KeyEvent.VK_B); 103 | beginPitchDetection.setActionCommand("beginPitchDetection"); 104 | beginPitchDetection.addActionListener(this); 105 | beginPitchDetection.setToolTipText("Press to Begin pitch detection"); 106 | 107 | /*End button*/ 108 | buttons.add(beginPitchDetection); 109 | endPitchDetection= new JButton("End pitch detection"); 110 | endPitchDetection.setMnemonic(KeyEvent.VK_E); 111 | endPitchDetection.setActionCommand("endPitchDetection"); 112 | endPitchDetection.addActionListener(this); 113 | endPitchDetection.setToolTipText("Press to End pitch detection"); 114 | endPitchDetection.setEnabled(false); 115 | buttons.add(endPitchDetection); 116 | add(buttons); 117 | 118 | 119 | /*Figure for captured sound*/ 120 | rawFigure = new DrawImage(new Dimension(imWidth,imHeight)); 121 | //rawFigure.setBackground(new Dimension(imWidth,imHeight)); 122 | //rawFigure.setPreferredSize(new Dimension(imWidth,imHeight)); 123 | rawFigure.setOpaque(true); 124 | add(rawFigure); 125 | 126 | /*Figure for whitened fft*/ 127 | whitenedFftFigure = new DrawImage(new Dimension(imWidth,imHeight)); 128 | //whitenedFftFigure.setBackground(new Dimension(imWidth,imHeight)); 129 | //whitenedFftFigure.setPreferredSize(new Dimension(imWidth,imHeight)); 130 | whitenedFftFigure.setOpaque(true); 131 | add(whitenedFftFigure); 132 | } 133 | 134 | public void actionPerformed(ActionEvent e) { 135 | if ("beginPitchDetection".equals(e.getActionCommand())) { 136 | endPitchDetection.setEnabled(true); 137 | beginPitchDetection.setEnabled(false); 138 | /*Create constant arrays for Klapuri*/ 139 | cb = new double[32]; 140 | /*CB filterbank always the same values, could be included from somewhere...*/ 141 | for (int b = 0;b<32;++b){ 142 | cb[b] = 229.0*(Math.pow(10.0,(((double) (b+1.0))/21.4))-1.0); //frequency division 143 | } 144 | /*Frequencies, always the same after capture init... 145 | captured signal will be zero padded to twice its length, so valid fft bins are equal to original epoch length 146 | */ 147 | freq = new double[(int) Math.floor((double) fftWindow)]; 148 | for (int b = 0;b(); 157 | hbIndices[i-1] = new ArrayList(); 158 | int kk=Klapuri.ind(freq,cb[i-1]); 159 | while (freq[kk] <= cb[i+1]){ 160 | hbIndices[i-1] .add(kk); 161 | if (freq[kk]<=cb[i]){ 162 | Hb[i-1].add(1-Math.abs(cb[i]-freq[kk])/(cb[i]-cb[i-1])); 163 | }else{ 164 | Hb[i-1].add(1-Math.abs(cb[i]-freq[kk])/(cb[i+1]-cb[i])); 165 | } 166 | ++kk; 167 | } 168 | } 169 | 170 | /* 171 | *Create candidate frequencies here (http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html) 172 | *Five octaves of candidate notes. Use quarter a half-step to get out of tune freqs 173 | *Lowest freq (f0) = 55.0 Hz, A three octaves below A above the middle C 174 | */ 175 | double f0Init = 55; //Hz 176 | double a = Math.pow(2.0,(1.0/12.0)); 177 | f0cands = new double[5*12*4]; //5 octaves, 12 half-steps per octave, quarter half-steps 178 | for (int kk = 0;kk tempInd =find(freq,f0cands[k]*((double)h+1.0)-halfBinWidth,f0cands[k]*((double)h+1.0)+halfBinWidth); 193 | f0index[k].addAll(tempInd); 194 | for (int t = 0;t find(double[] arr, double lower, double upper){ 217 | ArrayList b = new ArrayList(); 218 | for (int i = 0; i=lower && arr[i] <=upper){ 220 | b.add(i); 221 | } 222 | } 223 | return b; 224 | } 225 | public static void initAndShowGUI(){ 226 | JFrame f = new JFrame("Polyphonic Pitch Detection"); 227 | f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 228 | JComponent newContentPane = new PolyphonicPitchDetection(); 229 | newContentPane.setOpaque(true); //content panes must be opaque 230 | f.setContentPane(newContentPane); 231 | f.pack(); 232 | Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 233 | 234 | if (screenSize.width < imWidth+40){w = screenSize.width-40;}else{w=imWidth+40;} 235 | if (screenSize.height < imHeight*traces+100){h = screenSize.height-40;}else{h=imHeight*traces+100;} 236 | f.setLocation(20, 20); 237 | //f.setLocation(screenSize.width/2 - w/2, screenSize.height/2 - h/2); 238 | f.setSize(w, h); 239 | f.setVisible(true); 240 | } 241 | 242 | 243 | public static void main(String[] args){ 244 | javax.swing.SwingUtilities.invokeLater(new Runnable() { 245 | public void run(){ 246 | initAndShowGUI(); 247 | } 248 | } 249 | ); 250 | } 251 | } 252 | 253 | 254 | --------------------------------------------------------------------------------