annotate matlab/MATLAB-Chroma-Toolbox_2.0/estimateTuning.m @ 60:1ea2aed23d4a tip

Fix version
author Chris Cannam
date Thu, 13 Feb 2020 13:37:36 +0000
parents b54ee0a0be67
children
rev   line source
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