├── .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.
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-2014 Timo Rantalainen
19 | */
20 |
21 | package timo.tuner.DrawImage;
22 | import java.awt.*;
23 | import java.awt.event.*;
24 | import javax.swing.*;
25 | import java.util.Vector;
26 | import java.awt.BasicStroke;
27 | import java.awt.image.*;
28 | import java.awt.image.WritableRaster;
29 | import java.text.DecimalFormat; //For rounding text
30 |
31 | public class DrawImage extends JPanel{
32 |
33 | public BufferedImage bufferedImage; /**Draw on this bi off screen, render in paint*/
34 | int[] emptyImage;
35 | int colorIndex;
36 | int zero;
37 | int maxBitValue = 0;
38 | int[][] brushColor;
39 | Dimension imageSize;
40 | double width;
41 | double height;
42 | public String f0s;
43 | DecimalFormat dfo;
44 | private int[][] coordinates;
45 | private BasicStroke basicStroke;
46 | private Graphics2D g2;
47 |
48 | public DrawImage(Dimension imageSize, int maxBitValue){
49 | this(imageSize);
50 | this.maxBitValue = maxBitValue;
51 | zero = maxBitValue/2;
52 | }
53 |
54 | public DrawImage(Dimension imageSize){
55 | this.imageSize = imageSize;
56 | setPreferredSize(imageSize);
57 | dfo = new DecimalFormat("0.0");
58 | coordinates = null;
59 | bufferedImage = new BufferedImage(imageSize.width,imageSize.height,BufferedImage.TYPE_INT_ARGB);
60 | emptyImage = new int[imageSize.width*imageSize.height];
61 | /*Set pixels to fully opaque black*/
62 | for (int i = 0; i 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 |
--------------------------------------------------------------------------------