Chris@0
|
1 function [shiftFB,centerA4,tuningSemitones,sideinfo] = estimateTuning(f_input,parameter,sideinfo)
|
Chris@0
|
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
Chris@0
|
3 % Name: estimateTuning
|
Chris@0
|
4 % Date of Revision: 2011-03
|
Chris@0
|
5 % Programmer: Sebastian Ewert
|
Chris@0
|
6 %
|
Chris@0
|
7 % Description:
|
Chris@0
|
8 % - input is either a mono audio signal or a real valued spectrogram (magnitude or power)
|
Chris@0
|
9 % - if input is a spectrogram you have to set parameter.timeinfo and
|
Chris@0
|
10 % parameter.freqinfo (vectors defining the time and freq centers for each spectrogram bin)
|
Chris@0
|
11 % - if input is an audio signal, a sampling freq of 22050 Hz is assumed
|
Chris@0
|
12 % - guesses the tuning according to a simple energy maximizing criterion
|
Chris@0
|
13 % - output is either: what shiftFB is best to use (shiftFB \in [0:5]).
|
Chris@0
|
14 % Alternatively, the center freq for A4 is given which can be used to
|
Chris@0
|
15 % specify a filterbank on your own. The second option is more fine
|
Chris@0
|
16 % grained.
|
Chris@0
|
17 % Alternatively, it gives a tuning in semitones, which can
|
Chris@0
|
18 % easily be shifted cyclicly. For example: a tuning of -19/20 is more likely to be
|
Chris@0
|
19 % +1/20 Tuning difference.
|
Chris@0
|
20 % - parameter.numAdditionalTunings: how many tunings besides the fixed shiftFB ones
|
Chris@0
|
21 % to test. For example: If set to 3, than three additional tuning settings are
|
Chris@0
|
22 % tested for, located at 1/4, 2/4 and 3/4 semitones below the reference
|
Chris@0
|
23 % tuning. If set to 5, then at 1/6, 2/6,..., 5/6 semitones.
|
Chris@0
|
24 % - parameter.pitchRange specifies which pitches are considered for the
|
Chris@0
|
25 % tuning estimation.
|
Chris@0
|
26 % - parameter.pitchWeights: each pitch is considered according to a weight
|
Chris@0
|
27 % - Middle pitches are considered as being more important per default because
|
Chris@0
|
28 % here the frequency resolution is high enough. Additionally the piano has
|
Chris@0
|
29 % a consistent tuning only for middle pitches.
|
Chris@0
|
30 %
|
Chris@0
|
31 % Input:
|
Chris@0
|
32 % f_input
|
Chris@0
|
33 % parameter.numAdditionalTunings = 0;
|
Chris@0
|
34 % parameter.pitchRange = [21:108];
|
Chris@0
|
35 % parameter.pitchWeights = gausswin(length(parameter.pitchRange)).^2;
|
Chris@0
|
36 % parameter.fftWindowLength = 8192;
|
Chris@0
|
37 % parameter.windowFunction = @hanning;
|
Chris@0
|
38 % sideinfo
|
Chris@0
|
39 %
|
Chris@0
|
40 % Output:
|
Chris@0
|
41 % shiftFB
|
Chris@0
|
42 % centerA4
|
Chris@0
|
43 % tuningSemitones
|
Chris@0
|
44 % sideinfo
|
Chris@0
|
45 %
|
Chris@0
|
46 % License:
|
Chris@0
|
47 % This file is part of 'Chroma Toolbox'.
|
Chris@0
|
48 %
|
Chris@0
|
49 % 'Chroma Toolbox' is free software: you can redistribute it and/or modify
|
Chris@0
|
50 % it under the terms of the GNU General Public License as published by
|
Chris@0
|
51 % the Free Software Foundation, either version 2 of the License, or
|
Chris@0
|
52 % (at your option) any later version.
|
Chris@0
|
53 %
|
Chris@0
|
54 % 'Chroma Toolbox' is distributed in the hope that it will be useful,
|
Chris@0
|
55 % but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Chris@0
|
56 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Chris@0
|
57 % GNU General Public License for more details.
|
Chris@0
|
58 %
|
Chris@0
|
59 % You should have received a copy of the GNU General Public License
|
Chris@0
|
60 % along with 'Chroma Toolbox'. If not, see <http://www.gnu.org/licenses/>.
|
Chris@0
|
61 %
|
Chris@0
|
62 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
Chris@0
|
63 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
Chris@0
|
64 % Check parameters
|
Chris@0
|
65 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
Chris@0
|
66
|
Chris@0
|
67 if nargin<2
|
Chris@0
|
68 parameter=[];
|
Chris@0
|
69 end
|
Chris@0
|
70 if nargin<1
|
Chris@0
|
71 error('Please specify input data');
|
Chris@0
|
72 end
|
Chris@0
|
73
|
Chris@0
|
74 if isfield(parameter,'numAdditionalTunings')==0
|
Chris@0
|
75 parameter.numAdditionalTunings = 0;
|
Chris@0
|
76 end
|
Chris@0
|
77 if isfield(parameter,'pitchRange')==0
|
Chris@0
|
78 % Which pitches to consider during the estimation
|
Chris@0
|
79 parameter.pitchRange = [21:108];
|
Chris@0
|
80 end
|
Chris@0
|
81 if isfield(parameter,'pitchWeights')==0
|
Chris@0
|
82 % assign a weight to each pitch specified in parameter.pitchRange to
|
Chris@0
|
83 % specify it's importance
|
Chris@0
|
84 parameter.pitchWeights = gausswin(length(parameter.pitchRange)).^2;
|
Chris@0
|
85 end
|
Chris@0
|
86
|
Chris@0
|
87 % the following parameters are only for audio signal input
|
Chris@0
|
88 if isfield(parameter,'fftWindowLength')==0
|
Chris@0
|
89 parameter.fftWindowLength = 8192;
|
Chris@0
|
90 end
|
Chris@0
|
91 if isfield(parameter,'windowFunction')==0
|
Chris@0
|
92 parameter.windowFunction = @hanning; % only tested with hanning.
|
Chris@0
|
93 end
|
Chris@0
|
94
|
Chris@0
|
95 if min(size(f_input)) == 1
|
Chris@0
|
96 inputIsAudioSignal = 1;
|
Chris@0
|
97 else
|
Chris@0
|
98 inputIsAudioSignal = 0;
|
Chris@0
|
99 end
|
Chris@0
|
100
|
Chris@0
|
101 if ~inputIsAudioSignal
|
Chris@0
|
102 if isfield(parameter,'freqinfo')==0
|
Chris@0
|
103 error('When using a spectrogram input you have to set parameter.freqinfo');
|
Chris@0
|
104 end
|
Chris@0
|
105 end
|
Chris@0
|
106
|
Chris@0
|
107 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
Chris@0
|
108 % Main program
|
Chris@0
|
109 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
Chris@0
|
110
|
Chris@0
|
111 numTunings = 6 + parameter.numAdditionalTunings;
|
Chris@0
|
112 referenceFreqsA4 = zeros(numTunings,1);
|
Chris@0
|
113 tunings = zeros(numTunings,1);
|
Chris@0
|
114
|
Chris@0
|
115 tunings(1) = 0;
|
Chris@0
|
116 tunings(2) = -1/4;
|
Chris@0
|
117 tunings(3) = -1/3;
|
Chris@0
|
118 tunings(4) = -1/2;
|
Chris@0
|
119 tunings(5) = -2/3;
|
Chris@0
|
120 tunings(6) = -3/4;
|
Chris@0
|
121 for k=1:parameter.numAdditionalTunings
|
Chris@0
|
122 tunings(k+6) = -k/(parameter.numAdditionalTunings+1);
|
Chris@0
|
123 end
|
Chris@0
|
124 referenceFreqsA4 = 2.^((69-69+tunings)/12) * 440;
|
Chris@0
|
125
|
Chris@0
|
126 if inputIsAudioSignal
|
Chris@0
|
127 [s,f,t] = spectrogram(f_input, parameter.windowFunction(parameter.fftWindowLength), parameter.fftWindowLength/2, parameter.fftWindowLength, 22050);
|
Chris@0
|
128 else
|
Chris@0
|
129 s = f_input;
|
Chris@0
|
130 f = parameter.freqinfo;
|
Chris@0
|
131 end
|
Chris@0
|
132 s = abs(s);
|
Chris@0
|
133
|
Chris@0
|
134 directFreqBinSearch = 0;
|
Chris@0
|
135 if all( (f(2:end)-f(1:end-1)) - (f(2)-f(1)) < eps )
|
Chris@0
|
136 directFreqBinSearch = 1;
|
Chris@0
|
137 end
|
Chris@0
|
138
|
Chris@0
|
139 averagedPowerSpectrogram = sum(s.^2,2);
|
Chris@0
|
140 totalPitchEnergyViaSpec = zeros(numTunings,1);
|
Chris@0
|
141 for tu=1:numTunings
|
Chris@0
|
142 centerfreqs = 2.^((parameter.pitchRange-69)/12) * referenceFreqsA4(tu);
|
Chris@0
|
143 upperborderfreqs = 2.^((parameter.pitchRange-68.5)/12) * referenceFreqsA4(tu);
|
Chris@0
|
144 lowerborderfreqs = 2.^((parameter.pitchRange-69.5)/12) * referenceFreqsA4(tu);
|
Chris@0
|
145
|
Chris@0
|
146 % build triangular filterbank for magnitude spectrogram
|
Chris@0
|
147 spectrogramFilter = zeros(length(f),1);
|
Chris@0
|
148 for k=1:length(parameter.pitchRange)
|
Chris@0
|
149 c = getCorrespondingBin(f,centerfreqs(k),directFreqBinSearch);
|
Chris@0
|
150 u = getCorrespondingBin(f,upperborderfreqs(k),directFreqBinSearch);
|
Chris@0
|
151 l = getCorrespondingBin(f,lowerborderfreqs(k),directFreqBinSearch);
|
Chris@0
|
152
|
Chris@0
|
153 % order is important here. If third parameter is < 2, then linspace
|
Chris@0
|
154 % returns the second parameter
|
Chris@0
|
155 spectrogramFilter(c:u) = parameter.pitchWeights(k) * linspace(1,0,u-c+1);
|
Chris@0
|
156 spectrogramFilter(l:c) = parameter.pitchWeights(k) * linspace(0,1,c-l+1);
|
Chris@0
|
157 end
|
Chris@0
|
158
|
Chris@0
|
159 totalPitchEnergyViaSpec(tu) = sum(spectrogramFilter.^2 .* averagedPowerSpectrogram);
|
Chris@0
|
160 end
|
Chris@0
|
161
|
Chris@0
|
162 [ignoreMe, maxIndex] = max(totalPitchEnergyViaSpec(1:6));
|
Chris@0
|
163 shiftFB = maxIndex-1;
|
Chris@0
|
164
|
Chris@0
|
165 [ignoreMe, maxIndex] = max(totalPitchEnergyViaSpec);
|
Chris@0
|
166 centerA4 = referenceFreqsA4(maxIndex);
|
Chris@0
|
167 tuningSemitones = tunings(maxIndex);
|
Chris@0
|
168
|
Chris@0
|
169 sideinfo.tuning.shiftFB = shiftFB;
|
Chris@0
|
170 sideinfo.tuning.centerA4 = centerA4;
|
Chris@0
|
171 sideinfo.tuning.tuningSemitones = tuningSemitones;
|
Chris@0
|
172 sideinfo.tuning.method = 'estimateTuningV1';
|
Chris@0
|
173 sideinfo.tuning.numAdditionalTunings = parameter.numAdditionalTunings;
|
Chris@0
|
174 sideinfo.tuning.pitchRange = parameter.pitchRange;
|
Chris@0
|
175 sideinfo.tuning.pitchWeights = parameter.pitchWeights;
|
Chris@0
|
176 sideinfo.tuning.fftWindowLength = parameter.fftWindowLength;
|
Chris@0
|
177 sideinfo.tuning.windowFunction = parameter.windowFunction;
|
Chris@0
|
178 sideinfo.tuning.inputWasAudioSignal = inputIsAudioSignal;
|
Chris@0
|
179
|
Chris@0
|
180 end
|
Chris@0
|
181
|
Chris@0
|
182 function index = getCorrespondingBin(x,sval,directSearch)
|
Chris@0
|
183 % - Finds the entry in x with the smallest absolute distance to sval.
|
Chris@0
|
184 % - x is assumed to be sorted (ascending)
|
Chris@0
|
185 % - 'directSearch' means that all values in x are equally spaced
|
Chris@0
|
186 % - x is assumed to be at least of length 2.
|
Chris@0
|
187 % - If directSearch==0 then we use binary seach to find the entry
|
Chris@0
|
188 %
|
Chris@0
|
189 % You can test the correctness of this procedure by comparing it against
|
Chris@0
|
190 % the result of: [ignoreMe index] = min(abs(x-sval))
|
Chris@0
|
191 %
|
Chris@0
|
192 % Author: Sebastian Ewert
|
Chris@0
|
193
|
Chris@0
|
194 if sval >= x(end)
|
Chris@0
|
195 index = length(x);
|
Chris@0
|
196 return;
|
Chris@0
|
197 elseif sval <= x(1)
|
Chris@0
|
198 index = 1;
|
Chris@0
|
199 return;
|
Chris@0
|
200 end
|
Chris@0
|
201
|
Chris@0
|
202
|
Chris@0
|
203 if directSearch
|
Chris@0
|
204 index = round( (sval-x(1)) / (x(2)-x(1))) + 1;
|
Chris@0
|
205 else
|
Chris@0
|
206 from=1;
|
Chris@0
|
207 to=length(x);
|
Chris@0
|
208
|
Chris@0
|
209 while from<=to
|
Chris@0
|
210 mid = round((from + to)/2);
|
Chris@0
|
211 diff = x(mid)-sval;
|
Chris@0
|
212 if diff<0 % x(mid) < sval
|
Chris@0
|
213 from=mid;
|
Chris@0
|
214 else % x(mid) => sval
|
Chris@0
|
215 to=mid;
|
Chris@0
|
216 end
|
Chris@0
|
217
|
Chris@0
|
218 if to-from==1
|
Chris@0
|
219 break;
|
Chris@0
|
220 end
|
Chris@0
|
221 end
|
Chris@0
|
222 if abs(x(from)-sval) < abs(x(to)-sval)
|
Chris@0
|
223 index = from;
|
Chris@0
|
224 else
|
Chris@0
|
225 index = to;
|
Chris@0
|
226 end
|
Chris@0
|
227 end
|
Chris@0
|
228
|
Chris@0
|
229 end
|
Chris@0
|
230
|