├── README.md
├── demo_sound.wav
├── jun_demo.m
├── jun_read_partials.m
├── jun_synthesize_partials.m
├── jun_track_partials.m
├── jun_write_partials.m
├── jun_write_sdif.m
└── utilities
├── jun_bhwindow.m
├── jun_ddm.m
├── jun_framph.m
├── jun_hannwindow.m
├── jun_match_sets.m
├── jun_pick_peaks.m
├── jun_plot_partials.m
└── munkres.m
/README.md:
--------------------------------------------------------------------------------
1 | # Fast Partial Tracking
2 |
3 | Matlab code that fully implements the partial tracking method published in the following paper:
4 |
5 | J. Neri and P. Depalle, "**Fast Partial Tracking of Audio with Real-Time Capability through Linear Programming**," In *Proceedings of the 21st International Conference on Digital Audio Effects (DAFx-18)*, Aveiro, Portugal, pp. 326-333, Sep. 2018.
6 |
7 | ## Summary
8 |
9 | The script `jun_demo.m` runs a demonstration of the partial tracking method, followed by a re-synthesis of the signal from the detected partials. Instantaneous parameters of the partials are written to a binary file after each analysis frame, which can be read into Matlab again with the corresponding read function.
10 |
11 | Partial trajectory data is written to a Sound Description Interchange Format (SDIF) text file. The text file can be converted to a binary SDIF file using the 'tosdif' executable that is compiled with the SDIF package, which can be downloaded from https://sourceforge.net/projects/sdif/files/sdif/.
12 |
13 | 
14 |
15 |
--------------------------------------------------------------------------------
/demo_sound.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jundsp/Fast-Partial-Tracking/851a78a0bae09c6feae45dcf876e978f7020ba2a/demo_sound.wav
--------------------------------------------------------------------------------
/jun_demo.m:
--------------------------------------------------------------------------------
1 | % ********************************************************************* %
2 | % Demo of fast partial tracking, synthesis, read and write functions.
3 | %
4 | % Reference:
5 | %
6 | % J. Neri and P. Depalle, "Fast Partial Tracking of Audio with Real-Time
7 | % Capability through Linear Programming", In Proceedings of the 21st
8 | % International Conference on Digital Audio Effects (DAFx-18), Aveiro,
9 | % Portugal, pp. 325-333, Sep. 2018.
10 | %
11 | % Julian Neri, 210101
12 | % McGill University, Montreal, Canada
13 | % ********************************************************************* %
14 |
15 | clc;clear;close all;
16 | addpath('utilities')
17 |
18 | % Input audio
19 | input_filename = 'demo_sound.wav';
20 |
21 | [x, fs] = audioread(input_filename);
22 |
23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PARAMETERS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
24 | % Analysis Window Length
25 | N = 2^nextpow2(.025*fs)-1;
26 | % Oversampling Factor
27 | OverSample = 2;
28 | % Hop Size Factor (HopSize = N/HopFactor)
29 | HopFactor = 4;
30 | % Magnitude Threshold for Peak-Picking (dB)
31 | Peak_dB = -50;
32 | % Polynomial Order Q of the short-term sinusoidal model
33 | % 1 = Frequency, Damping
34 | % 2 = Frequency Derivative, Damping Derivative
35 | % 3 = Frequency 2nd Derivative, Damping 2nd Derivative, and so on..
36 | Q = 2;
37 | % These parameters control the assignment cost function
38 | delta = .2; % 0 0 && num_active_last == 0
24 | % Fade in all
25 | if m == 1
26 | time_m = [1 time(m)];
27 | else
28 | time_m = [time(m-1) time(m)];
29 | end
30 | H = abs(diff(time_m));
31 | % Fade out all
32 | n_unmatched = num_active;
33 | for idx = 1:n_unmatched
34 | % Fade in
35 | Omega_m = [Partials{m}(idx,1) Partials{m}(idx,1)];
36 | Amp_m = [-inf Partials{m}(idx,2)];
37 | Phase_m = [(Partials{m}(idx,3) - Omega_m(2)*H) Partials{m}(idx,3)];
38 |
39 | ytemp = jun_synthesize_one_partial(Omega_m, Amp_m, Phase_m, H);
40 | out(time_m(1):time_m(2)-1,1) = out(time_m(1):time_m(2)-1,1) + ytemp(1:end-1);
41 | end
42 | elseif num_active == 0 && num_active_last > 0
43 | time_m = [time(m-1) time(m)];
44 | H = abs(diff(time_m));
45 | % Fade out all
46 | n_unmatched_last = num_active_last;
47 | for idx = 1:n_unmatched_last
48 | Omega_m = [Partials{m-1}(idx,1) Partials{m-1}(idx,1)];
49 | Amp_m = [Partials{m-1}(idx,2) -inf];
50 | Phase_m = [Partials{m-1}(idx,3) (Partials{m-1}(idx,3)+Omega_m(1)*H)];
51 |
52 | ytemp = jun_synthesize_one_partial(Omega_m, Amp_m, Phase_m, H);
53 | out(time_m(1):time_m(2),1) = out(time_m(1):time_m(2),1) + ytemp;
54 | end
55 | elseif num_active > 0 && num_active_last > 0
56 | [matches, unmatched_last, unmatched] = jun_match_sets(Partials{m-1}(1:num_active_last,4), Partials{m}(1:num_active,4));
57 | n_matches = size(matches,1);
58 | n_unmatched_last = length(unmatched_last);
59 | n_unmatched = length(unmatched);
60 |
61 | time_m = [time(m-1) time(m)];
62 | H = abs(diff(time_m));
63 |
64 | for idx = 1:n_unmatched
65 | % Fade in
66 | Omega_m = [Partials{m}(unmatched(idx),1) Partials{m}(unmatched(idx),1)];
67 | Amp_m = [-inf Partials{m}(unmatched(idx),2)];
68 | Phase_m = [(Partials{m}(unmatched(idx),3) - Omega_m(2)*H) Partials{m}(unmatched(idx),3)];
69 |
70 | ytemp = jun_synthesize_one_partial(Omega_m, Amp_m, Phase_m, H);
71 | out(time_m(1):time_m(2)-1,1) = out(time_m(1):time_m(2)-1,1) + ytemp(1:end-1);
72 | end
73 |
74 | for idx = 1:n_unmatched_last
75 | % Fade out
76 | Omega_m = [Partials{m-1}(unmatched_last(idx),1) Partials{m-1}(unmatched_last(idx),1)];
77 | Amp_m = [Partials{m-1}(unmatched_last(idx),2) -inf];
78 | Phase_m = [Partials{m-1}(unmatched_last(idx),3) Partials{m-1}(unmatched_last(idx),3)+Omega_m(1)*H];
79 |
80 |
81 | ytemp = jun_synthesize_one_partial(Omega_m, Amp_m, Phase_m, H);
82 | out(time_m(1):time_m(2),1) = out(time_m(1):time_m(2),1) + ytemp;
83 | end
84 |
85 | for idx = 1:n_matches
86 | % Continue
87 | Omega_m = [Partials{m-1}(matches(idx,1),1) Partials{m}(matches(idx,2),1)];
88 | Amp_m = [Partials{m-1}(matches(idx,1),2) Partials{m}(matches(idx,2),2)];
89 | Phase_m = [Partials{m-1}(matches(idx,1),3) Partials{m}(matches(idx,2),3)];
90 |
91 | ytemp = jun_synthesize_one_partial(Omega_m, Amp_m, Phase_m, H);
92 | out(time_m(1):time_m(2)-1,1) = out(time_m(1):time_m(2)-1,1) + ytemp(1:end-1);
93 | end
94 | end
95 | end
96 |
97 | end
98 |
99 |
100 | function [ out ] = jun_synthesize_one_partial(Omega, Amplitude, Phase, H)
101 | % Synthesizes one partial, given two points for frequency, amplitude and
102 | % phase, H samples apart in time.
103 |
104 | t_span = [0 H];
105 | t = (t_span(1):t_span(2))';
106 |
107 | % Linear Amplitude
108 | Amp_ = interp1(t_span, exp(Amplitude), t);
109 |
110 | % (for eq. 34)
111 | T_mx = [ 3/H^2 -1/H
112 | -2/H^3 1/H^2];
113 |
114 | % (eq. 36)
115 | M_star = (Phase(1) + Omega(1)*H - Phase(2))+(Omega(2) - Omega(1))*H/2;
116 | M_star = round(1/(2*pi)*M_star);
117 |
118 | % (for eq. 34)
119 | rowv = [Phase(2) - Phase(1) - Omega(1)*H + 2*pi*M_star
120 | Omega(2) - Omega(1)];
121 |
122 | % (eq. 34)
123 | ab = T_mx*rowv;
124 | alpha = ab(1);
125 | beta = ab(2);
126 |
127 | % (eq. 37)
128 | Phase_ = Phase(1) + Omega(1)*t + alpha*t.^2 + beta*t.^3;
129 |
130 |
131 | % Synthesize the Partial
132 | out = 2*real(exp(log(Amp_) + 1j*Phase_));
133 |
134 | end
--------------------------------------------------------------------------------
/jun_track_partials.m:
--------------------------------------------------------------------------------
1 | function [Partials, time, padding, L, S] = jun_track_partials(y, fs, varargin)
2 | % ********************************************************************* %
3 | % Fast partial tracking of audio.
4 | %
5 | % Involves the following two main procedures for each short-term frame
6 | %
7 | % 1. Short-term sinusoidal model parameters are estimated using the
8 | % Distribution Derivative Method (DDM).
9 | % 2. Peaks are connected over consecutive analysis frames by solving an
10 | % assignment problem, using the Hungarian algorithm (munkres.m).
11 | %
12 | %
13 | % INPUTS
14 | % y: input audio signal
15 | % fs: sampling rate (Hz)
16 | % Variable inputs:
17 | % N: length of short-term analysis window
18 | % HopFactor: Hop Size = N/HopFactor
19 | % OverSample: Oversampling factor
20 | % G_g: Peaks below this magnitude threshold (in dB) are not considered.
21 | % Q: short-term model polynomial order (DDM)
22 | % delta: controls the relative preference towards useful assignments.
23 | % larger values of delta promote useful assignments (0 9
67 | error('Requires at most 9 optional inputs');
68 | end
69 |
70 | % Defaults
71 | N = 2^11-1;
72 | HopFactor = 4;
73 | OverSample = 1;
74 | G_g = -40;
75 | Q = 2;
76 | delta = .2;
77 | zetaF = 50;
78 | zetaA = 15;
79 |
80 | % Use given parameters if available
81 | optargs = {N HopFactor OverSample G_g Q delta zetaF zetaA};
82 | optargs(1:numvarargs) = varargin;
83 | [N, HopFactor, OverSample, G_g, Q, delta, zetaF, zetaA] = optargs{:};
84 |
85 | % Check/Set Values so they don't produce errors later on.
86 | N = max(4,round(N));
87 | HopFactor = 2^nextpow2(max(1,HopFactor));
88 | OverSample = 2^nextpow2(max(1,OverSample));
89 | G_g = abs(G_g);
90 | Q = max(1,round(Q));
91 | % These can't be <= 0
92 | zetaF(zetaF<1e-1) = 1e-1;
93 | zetaA(zetaA<1e-1) = 1e-1;
94 | % Convert to log from dB
95 | zetaA = zetaA/20*log(10);
96 |
97 | %%%%%%%%%%%%%%%%%%%%%%%%%%%% SETUP %%%%%%%%%%%%%%%%%%%%%%%
98 |
99 | % Convert to mono
100 | y = y(:,1);
101 | L = length(y);
102 |
103 | % Calculate variance (sigma^2) according to given zeta
104 | varF = -zetaF^2*log((delta-1)/(delta-2));
105 | varA = -zetaA^2*log((delta-1)/(delta-2));
106 |
107 | % Analysis Windows
108 | if OverSample > 1
109 | win = jun_bhwindow(N,1); % Window
110 | winD = jun_bhwindow(N,2); % Window Derivative
111 | HopFactor = max(8,HopFactor);
112 | else
113 | win = jun_hannwindow(N,1); % Window
114 | winD = jun_hannwindow(N,2); % Window Derivative
115 | HopFactor = max(4,HopFactor);
116 | end
117 |
118 | % Calculate Hop Size from Hop Factor and N
119 | H = round((N-mod(N,2))/HopFactor);
120 | % Calculate the size of the DFT from Oversampling Factor and N
121 | Ndft = 2^(round(log2(OverSample*2^round(log2(N)))));
122 | if Ndft < N
123 | Ndft = 2^round(log2(N)+1);
124 | end
125 | ndft = (0:Ndft-1)';
126 |
127 | % Calculate the number of short-term frames and zero padding.
128 | padding = zeros(2,1);
129 | padding(1) = (Ndft-H);
130 | M = ceil((Ndft+L - 2*H-1)/H + 1);
131 | padding(2) = ((M-1)*H + Ndft) - (padding(1) + L);
132 |
133 | % Zero-pad the signal
134 | ypad = [zeros(padding(1),1); y; zeros(padding(2),1)];
135 |
136 | % time, frequency, and STFT arrays
137 | time = zeros(M,1);
138 | omega = 2*pi*ndft/Ndft;
139 | S = zeros(Ndft, M);
140 |
141 | % Center of analysis window
142 | n_center = (N - mod(N,2))/2+1;
143 |
144 | % Zero-centered time vector of DFT
145 | ndft = ndft - n_center;
146 | % For centering the spectra (DDM)
147 | centering = exp(1j*omega*n_center);
148 | % DFT Matrix for alpha0 estimation (DDM)
149 | ft_mat = exp(1j*ndft*omega(1:Ndft/2)');
150 | % Number of Bins for DDM esimtate
151 | Rddm = Q*OverSample;
152 |
153 | % DDM Polynomial Array
154 | % Zero-centered time vector (n in paper)
155 | n = (0:N-1)' - n_center;
156 | i = (0:Q);
157 | % Polynomial time vector
158 | p = bsxfun(@power, n, i);
159 | % Derivative of p time vector
160 | pD = bsxfun(@times, bsxfun(@power, n, (0:Q-1)), 1:Q);
161 | % Time index of mid-point between analysis frames (H/2 in paper)
162 | n_midpoint = [-floor(H/2) ceil(H/2)] + n_center;
163 |
164 | % Approximate max in the STFT, without computing it directly
165 | % (for thresholding the peak selection)
166 | [~, max_sample] = max(abs(ypad));
167 | maxS = 20*log10(max(abs(fft(win.*ypad(max_sample + (0:N-1)-n_center),Ndft))));
168 | % Set relative amplitude threshold for peak selection
169 | G_g = maxS - G_g;
170 |
171 | % For storing the short-term estimates (alpha_ij in paper)
172 | Alpha = 0;
173 | num_peaks = 0;
174 |
175 | % For storing the parameters/index of the partials for each frame
176 | Partials = cell(M,1);
177 | pairs_ = 0;
178 | tracks_ = 0;
179 | num_tracks = 0;
180 | num_active = 0;
181 |
182 | % Write header information to file
183 | fwrite(fileID,num_tracks,'uint');
184 | fwrite(fileID,L,'uint');
185 | fwrite(fileID,fs,'double');
186 | fwrite(fileID,M,'uint');
187 | fwrite(fileID,padding(1),'uint');
188 | fwrite(fileID,padding(2),'uint');
189 |
190 |
191 | %%%%%%%%%%%%%%%%%%%%% PARTIAL TRACKING STARTS HERE %%%%%%%%%%%%%%%%%%%%%%%
192 |
193 | % For every short-term analysis frame 'm'
194 | for m = 1:M
195 |
196 | %%%%%%%%%%%%%%%%%% SHORT-TERM PARAMETER ESTIMATION %%%%%%%%%%%%%%%%%%
197 | % Time at the center of the short-term analysis frame
198 | time(m) = 1 + (m-1)*H + n_center;
199 | % Short-term signal
200 | yst = ypad(time(m)-n_center:time(m)+N-1-n_center);
201 |
202 | % Save estimates from the previous frame
203 | Alpha_last = Alpha;
204 | num_peaks_last = num_peaks;
205 |
206 | % Estimate short-term model parameters of each peak using DDM
207 | [Alpha, num_peaks, S(:,m)] = jun_ddm(yst, Q, Rddm, G_g, win, winD, ...
208 | p, pD, centering, Ndft, ft_mat,omega);
209 |
210 | %%%%%%%%%%%%%%%%%% TRACKING %%%%%%%%%%%%%%%%%%
211 | Partials{m}(:,1) = 0;
212 | if m > 1
213 | %%%%%%%%%%%%%%%%%% PEAK-to-PEAK ASSIGNMENTS %%%%%%%%%%%%%%%%%%
214 | num_assignments = 0;
215 | if num_peaks > 0 && num_peaks_last > 0
216 |
217 | % Amplitudes/Frequencies at midpoint of analysis frames
218 | % a(+H/2) [k-1]
219 | mA1 = real(p(n_midpoint(2),:)*Alpha_last);
220 | % a(-H/2) [k]
221 | mA2 = real(p(n_midpoint(1),:)*Alpha);
222 | % f(+H/2) [k]
223 | mF1 = fs/(2*pi)*imag(pD(n_midpoint(2),:)*Alpha_last(2:end,:));
224 | % f(-H/2) [k]
225 | mF2 = fs/(2*pi)*imag(pD(n_midpoint(1),:)*Alpha(2:end,:));
226 |
227 | % DELTA_a and DELTA_f
228 | % eq. (10)
229 | DeltaA = bsxfun(@minus, mA1', mA2);
230 | % eq. (11)
231 | DeltaF = bsxfun(@minus, mF1', mF2);
232 |
233 | % USEFUL COST
234 | % eq. (8)
235 | A_useful = 1-exp(-DeltaF.^2/varF - DeltaA.^2/varA);
236 | % SPURIOUS COST
237 | % eq. (9)
238 | B_spurious = 1-(1-delta)*A_useful;
239 |
240 | % COST MATRIX
241 | % eq. (14)
242 | [Costs, Type] = min([B_spurious(:) A_useful(:)],[],2);
243 |
244 | C_mtx = reshape(abs(Costs), num_peaks_last, num_peaks);
245 |
246 | % HUNGARIAN ALGORITHM SOLVES THE ASSIGNMENT PROBLEM
247 | jj = munkres(C_mtx);
248 |
249 | ii = find(jj);
250 | ind = ii + num_peaks_last*(jj(ii)-1);
251 | ii = ii(Type(ind)==2);
252 |
253 | % Stores the useful assignments
254 | num_assignments = length(ii);
255 | Assignments = [ii' jj(ii)'];
256 | end
257 |
258 | %%%%%%%%%%%%%%%%%% TRAJECTORY LABELING %%%%%%%%%%%%%%%%%%
259 | num_active_last = num_active;
260 | pairs_last = pairs_;
261 | tracks_last = tracks_;
262 |
263 | num_active = 0;
264 | pairs_ = zeros(num_assignments,1);
265 | tracks_ = zeros(num_assignments,1);
266 |
267 | if num_assignments > 0
268 | % An assignment either continues an existing partial or starts
269 | % a new one (birth).
270 | [toContinue, ~, toBirth] = jun_match_sets(pairs_last, Assignments(:,1));
271 |
272 | num_toContinue = length(toContinue(:,1));
273 | num_toBirth = length(toBirth);
274 | num_active = num_toContinue + num_toBirth;
275 |
276 | pairs_ = Assignments([toContinue(:,2); toBirth],2);
277 | tracks_ = [tracks_last(toContinue(:,1)); num_tracks + (1:num_toBirth)'];
278 |
279 | ii_active_last = num_active_last + (1:num_toBirth);
280 |
281 | % Save partials for frame m-1 and m
282 | Partials{m-1}(ii_active_last,1:3) = jun_framph(Alpha_last(:,Assignments(toBirth,1)), p(n_center+1,:), pD(n_center+1,:));
283 | Partials{m-1}(ii_active_last,4) = tracks_(num_toContinue+1:num_active);
284 |
285 | Partials{m}(1:num_active,1:3) = jun_framph(Alpha(:,pairs_), p(n_center+1,:), pD(n_center+1,:));
286 | Partials{m}(1:num_active,4) = tracks_;
287 |
288 | num_active_last = num_active_last + num_toBirth;
289 | num_tracks = num_tracks + num_toBirth;
290 | end
291 | % Write data into file for frame m-1
292 | jun_write_partials(fileID, m-1, time(m-1), num_active_last, Partials{m-1});
293 | time_sdif = time(m-1) - (padding(1)+1);
294 | if time_sdif >= 0 && time_sdif <= (L + H)
295 | jun_write_sdif(sdifFileID, fs,time_sdif/fs, num_active_last, Partials{m-1});
296 | end
297 | end
298 | end
299 | % Write data into file for frame M
300 | jun_write_partials(fileID, M, time(M), num_active, Partials{M});
301 | % Write total number of partials detected
302 | fseek(fileID,0,'bof');
303 | fwrite(fileID,num_tracks,'uint');
304 | fclose(fileID);
305 |
306 | % SDIF Footer
307 | fprintf(sdifFileID,'ENDC\nENDF');
308 | % Close SDIF text File
309 | fclose(sdifFileID);
310 | end
311 | % eof
--------------------------------------------------------------------------------
/jun_write_partials.m:
--------------------------------------------------------------------------------
1 | function jun_write_partials(fileID,frame,time,rows,Partials)
2 | % ********************************************************************* %
3 | % Writes frame block into file.
4 | %
5 | %
6 | % Julian Neri, 180914
7 | % McGill University, Montreal, Canada
8 | % ********************************************************************* %
9 |
10 | fwrite(fileID,frame,'uint');
11 | fwrite(fileID,time,'uint');
12 | fwrite(fileID,rows,'uint');
13 | for r = 1:rows
14 | fwrite(fileID,Partials(r,1),'double');
15 | fwrite(fileID,Partials(r,2),'double');
16 | fwrite(fileID,Partials(r,3),'double');
17 | fwrite(fileID,Partials(r,4),'uint');
18 | end
19 |
20 | end
--------------------------------------------------------------------------------
/jun_write_sdif.m:
--------------------------------------------------------------------------------
1 | function jun_write_sdif(fileID,fs,time,num_tracks,Partials)
2 | % ********************************************************************* %
3 | % Writes frame block to sdif text file.
4 | %
5 | %
6 | % Julian Neri, 210101
7 | % McGill University, Montreal, Canada
8 | % ********************************************************************* %
9 |
10 | fprintf(fileID,'1TRC\t1\t0\t%f\n',time);
11 | fprintf(fileID,' 1TRC\t0x0004\t%d\t4\n',num_tracks);
12 | for i = 1:num_tracks
13 | f = Partials(i,1)*fs/(2*pi);
14 | a = exp(Partials(i,2));
15 | p = Partials(i,3);
16 | track_id = Partials(i,4);
17 | fprintf(fileID,'\t%d\t%f\t%f\t%f\n',track_id,f,a,p);
18 | end
19 | fprintf(fileID,'\n');
20 |
21 | end
22 |
23 |
--------------------------------------------------------------------------------
/utilities/jun_bhwindow.m:
--------------------------------------------------------------------------------
1 | function [win, n] = jun_bhwindow(N,d)
2 | % Blackman-Harris Window
3 | %
4 | % N: length of window
5 | % d: order of derivative
6 | %
7 | % Julian Neri, 180914
8 | % McGill University, Montreal, Canada
9 |
10 | if nargin < 2
11 | d = 1;
12 | end
13 |
14 | if mod(N,2)
15 | Nwinhf = (N-1)/2;
16 | n = (-Nwinhf:Nwinhf)';
17 | else
18 | Nwinhf = N/2-1;
19 | n = (-Nwinhf-1:Nwinhf)';
20 | end
21 |
22 | a = [0.35875 0.48829 0.14128 0.01168];
23 | in = [2 4 6]*pi/(N);
24 |
25 | switch(d)
26 | case 1
27 | win = a(1) + a(2)*cos(in(1)*n)+a(3)*cos(in(2)*n) + a(4)*cos(in(3)*n);
28 | case 2
29 | win = -a(2)*in(1)*sin(in(1)*n) - a(3)*in(2)*sin(in(2)*n) - a(4)*in(3)*sin(in(3)*n);
30 | case 3
31 | win = -a(2)*in(1)^2*cos(in(1)*n) - a(3)*in(2)^2*cos(in(2)*n) - a(4)*in(3)^2*cos(in(3)*n);
32 | end
33 |
34 | end
--------------------------------------------------------------------------------
/utilities/jun_ddm.m:
--------------------------------------------------------------------------------
1 | function [Alpha, num_peaks, S] = jun_ddm(y, Q, R, G_g, win, winD, ...
2 | p, pD, centering, Ndft, ft_mat,omega)
3 | % Distribution Derivative Method of estimating sinusoidal model parameters
4 | %
5 | % Reference:
6 | %
7 | % M. Betser, "Sinusoidal Polynomial Parameter Estimation Using the
8 | % Distribution Derivative", IEEE Transactions on Signal Processing, vol.
9 | % 57, no. 12, pp. 4633-4645, Dec. 2009.
10 | %
11 | %
12 | % y: input signal
13 | % Q: polynomial order
14 | % R: number of peaks to use for estimation
15 | % G_g: peak amplitude threshold (dB)
16 | % win: window
17 | % winD: derivative of window
18 | % p: time vector n^i, where i = 0:Q
19 | % pD: time derivative of p
20 | %
21 | % Julian Neri, 180914
22 | % McGill University, Montreal, Canada
23 |
24 | % Input info
25 | N = length(y);
26 | max_y = max(abs(y))*2;
27 |
28 | % Windowed signal
29 | yst = win.*y;
30 | % DFT (one frame of the signal's STFT)
31 | S = fft(yst, Ndft);
32 |
33 | % Pick Peaks
34 | [peakBin, num_peaks, LeftBin, RightBin] = jun_pick_peaks(S(1:Ndft/2), G_g);
35 |
36 | % Estimate parameters for each peak (alpha_ij in paper), DDM
37 | Alpha = 0;
38 | if num_peaks > 0
39 | % Stores the various DFTs involved with the DDM method
40 | Sp = zeros(Ndft, Q-1);
41 | % Polynomials * signal
42 | Sp(:,1) = S;
43 | Sp(:,1) = Sp(:,1).*centering;
44 | for i = 2:Q
45 | Sp(:,i) = fft(yst.*pD(:,i), Ndft);
46 | Sp(:,i) = Sp(:,i).*centering;
47 | end
48 |
49 | % Derivative Windowed Signal
50 | yDst = winD.*y;
51 | SD = fft(yDst, Ndft);
52 | SD = SD.*centering;
53 | SD = SD + Sp(:,1).*(-1j*omega);
54 |
55 | alpha_hat = zeros(Q+1,num_peaks);
56 | useful = 0;
57 | for jj = 1:num_peaks
58 |
59 | % Use the highest energy bins around the peak for DDM
60 | pbl = max(LeftBin(jj), peakBin(jj)-(R-1));
61 | pbr = min([RightBin(jj) peakBin(jj)+(R-1) Ndft/2]);
62 | pb_sides = sort(pbl:pbr, 'Descend');
63 | pbs = [peakBin(jj) pb_sides];
64 |
65 | % Define matrix A and vector b
66 | A = Sp(pbs, 1:Q);
67 | b = -SD(pbs);
68 |
69 | % Solve for alpha using least squares
70 | alpha_temp = [0; A\b];
71 |
72 |
73 | % Check edges
74 | if any(isnan(alpha_temp))
75 | fprintf('NAN estimate, skipping peak %0.0f of frame %0.0f \n', jj, m);
76 | elseif any(abs(p([1 N],2:end)*alpha_temp(2:end))>1e100)
77 | fprintf('Infinite edges, skipping peak %0.0f of frame %0.0f \n', jj, m)
78 | else
79 | % Solve for alpha0 (approximate)
80 | gam = win.*exp(p(:,2:end)*alpha_temp(2:end));
81 | T_gam = ft_mat(1:N,peakBin(jj))'*gam;
82 |
83 | alpha0 = log(Sp(peakBin(jj),1)) - log(T_gam);
84 | alpha_temp(1) = alpha0;
85 |
86 | % Final check and save
87 | if all(p([1 round(N/2) N],:)*real(alpha_temp) <= max_y)
88 | useful = useful + 1;
89 | alpha_hat(:,useful) = alpha_temp;
90 | end
91 | end
92 | end
93 |
94 | % Save the estimates
95 | num_peaks = useful;
96 | Alpha = alpha_hat(:,1:num_peaks);
97 | end
98 | end
--------------------------------------------------------------------------------
/utilities/jun_framph.m:
--------------------------------------------------------------------------------
1 | function out = jun_framph(alpha, p, p_prime)
2 | % Calculates the frequency, amplitude, and phase from alpha.
3 | %
4 | % p = n^i for i=0:Q
5 | % pD = dp/dn
6 | %
7 | % Julian Neri, 180914
8 | % McGill University, Montreal, Canada
9 |
10 | % eq. (7)
11 | frequency = imag(p_prime*alpha(2:end,:));
12 | % eq. (5)
13 | amplitude = real(p*alpha);
14 | % eq. (6)
15 | phase = imag(p*alpha);
16 |
17 | out = [frequency' amplitude' phase'];
18 |
19 | end
--------------------------------------------------------------------------------
/utilities/jun_hannwindow.m:
--------------------------------------------------------------------------------
1 | function win = jun_hannwindow(N,derivative)
2 | % Hann Window
3 | %
4 | % N: length of window
5 | % d: order of derivative
6 | %
7 | % Julian Neri, 180914
8 | % McGill University, Montreal, Canada
9 |
10 | if nargin < 2
11 | derivative = 1;
12 | end
13 |
14 | if mod(N,2) == 1
15 | Nhf = (N-1)/2;
16 |
17 | n = (0:N-1)' - Nhf;
18 | in = 2*pi/(N-1);
19 |
20 | switch(derivative)
21 | case 1
22 | win = .5 + .5*cos(in*n);
23 | case 2
24 | win = -.5*in*sin(in*n);
25 | case 3
26 | win = -.5*in^2*cos(in*n);
27 | end
28 |
29 | else
30 | n = (0:N-1)';
31 | in = 2*pi/(N-1);
32 |
33 | switch(derivative)
34 | case 1
35 | win = .5 - .5*cos(in*n);
36 | case 2
37 | win = .5*in*sin(in*n);
38 | case 3
39 | win = .5*in^2*cos(in*n);
40 | end
41 | end
42 | end
43 |
44 |
--------------------------------------------------------------------------------
/utilities/jun_match_sets.m:
--------------------------------------------------------------------------------
1 | function [match, nomatch_A, nomatch_B] = jun_match_sets(A, B)
2 | % Matches the elements of A and B.
3 | %
4 | %
5 | % Julian Neri, 180914
6 | % McGill University, Montreal, Canada
7 |
8 | nA = length(A);
9 | nB = length(B);
10 |
11 | if length(unique(A)) ~= nA
12 | disp('A must be unique');
13 | return
14 | elseif length(unique(B)) ~= nB
15 | disp('B must be unique');
16 | return
17 | end
18 |
19 | validA = true(nA,1);
20 | validB = true(nB,1);
21 |
22 | match = zeros(min(nA,nB), 2);
23 | counter = 0;
24 | for a = 1:nA
25 | ind = find(A(a) == B(validB));
26 |
27 | if ind
28 | counter = counter + 1;
29 |
30 | bs = find(validB);
31 | b = bs(ind);
32 |
33 | match(counter,:) = [a b];
34 | validA(a) = false;
35 | validB(b) = false;
36 | end
37 | end
38 |
39 | match = match(1:counter,:);
40 | nomatch_A = find(validA);
41 | nomatch_B = find(validB);
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/utilities/jun_pick_peaks.m:
--------------------------------------------------------------------------------
1 | function [peak_inds, n_peaks, left_inds, right_inds] = jun_pick_peaks(X, G_g)
2 | % Simple peak-picking routine
3 | %
4 | % A peak is a local maximum.
5 | %
6 | % X: spectrum
7 | % G_g: a peak whose magnitude is less than G_g is disregarded.
8 | %
9 | % Julian Neri, 180914
10 | % McGill University, Montreal, Canada
11 |
12 | N = size(X,1);
13 | M = size(X,2);
14 |
15 | idx_peaks = zeros(N, M);
16 | n_peaks = zeros(1, M);
17 |
18 | LeftBin = zeros(N,M);
19 | RightBin = zeros(N,M);
20 |
21 | X = 20*log10(abs(X));
22 |
23 | for m = 1:M
24 | num = 0;
25 | num_temp = 0;
26 | for n = 2:N-1
27 | if X(n, m) > G_g
28 | if X(n, m) > X(n-1, m) && X(n, m) > X(n+1, m)
29 |
30 | num_temp = num_temp + 1;
31 |
32 | left = n-1;
33 | while (left-1 > 0) && (X( left, m)>X( left-1, m))
34 | left = left - 1;
35 | end
36 |
37 | right = n+1;
38 | while (right+1 < N+1) && (X( right, m) > X( right+1, m))
39 | right = right + 1;
40 | end
41 |
42 | num = num + 1;
43 | idx_peaks(num, m) = n;
44 | LeftBin(num,m) = left;
45 | RightBin(num,m) = right;
46 | end
47 | end
48 | end
49 | n_peaks(m) = num;
50 | end
51 |
52 | maxpeaks = max(n_peaks);
53 | peak_inds = idx_peaks(1:maxpeaks,:);
54 | left_inds = LeftBin(1:maxpeaks,:);
55 | right_inds = RightBin(1:maxpeaks,:);
56 |
57 | end
58 |
--------------------------------------------------------------------------------
/utilities/jun_plot_partials.m:
--------------------------------------------------------------------------------
1 | function jun_plot_partials(Partials, time, fs, num_tracks)
2 | % Plots the partials
3 | %
4 | %
5 | % Julian Neri, 180914
6 | % McGill University, Montreal, Canada
7 |
8 | M = length(time);
9 |
10 | track_F = -inf+zeros(M,num_tracks);
11 | track_A = -inf+zeros(M,num_tracks);
12 |
13 |
14 | for m = 1:M
15 | active = Partials{m}(:,4);
16 |
17 | track_F(m,active) = Partials{m}(:,1);
18 | track_A(m,active) = Partials{m}(:,2);
19 | end
20 |
21 | plot(time/fs, fs/(2*pi)*track_F, 'linewidth',1.5);
22 | axis tight; grid on; box on;
23 |
24 |
25 | end
--------------------------------------------------------------------------------
/utilities/munkres.m:
--------------------------------------------------------------------------------
1 | function [assignment,cost] = munkres(costMat)
2 | % MUNKRES Munkres (Hungarian) Algorithm for Linear Assignment Problem.
3 | %
4 | % [ASSIGN,COST] = munkres(COSTMAT) returns the optimal column indices,
5 | % ASSIGN assigned to each row and the minimum COST based on the assignment
6 | % problem represented by the COSTMAT, where the (i,j)th element represents the cost to assign the jth
7 | % job to the ith worker.
8 | %
9 | % Partial assignment: This code can identify a partial assignment is a full
10 | % assignment is not feasible. For a partial assignment, there are some
11 | % zero elements in the returning assignment vector, which indicate
12 | % un-assigned tasks. The cost returned only contains the cost of partially
13 | % assigned tasks.
14 |
15 | % This is vectorized implementation of the algorithm. It is the fastest
16 | % among all Matlab implementations of the algorithm.
17 |
18 | % Examples
19 | % Example 1: a 5 x 5 example
20 | %{
21 | [assignment,cost] = munkres(magic(5));
22 | disp(assignment); % 3 2 1 5 4
23 | disp(cost); %15
24 | %}
25 | % Example 2: 400 x 400 random data
26 | %{
27 | n=400;
28 | A=rand(n);
29 | tic
30 | [a,b]=munkres(A);
31 | toc % about 2 seconds
32 | %}
33 | % Example 3: rectangular assignment with inf costs
34 | %{
35 | A=rand(10,7);
36 | A(A>0.7)=Inf;
37 | [a,b]=munkres(A);
38 | %}
39 | % Example 4: an example of partial assignment
40 | %{
41 | A = [1 3 Inf; Inf Inf 5; Inf Inf 0.5];
42 | [a,b]=munkres(A)
43 | %}
44 | % a = [1 0 3]
45 | % b = 1.5
46 | % Reference:
47 | % "Munkres' Assignment Algorithm, Modified for Rectangular Matrices",
48 | % http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
49 |
50 | % version 2.3 by Yi Cao at Cranfield University on 11th September 2011
51 |
52 | assignment = zeros(1,size(costMat,1));
53 | cost = 0;
54 |
55 | validMat = costMat == costMat & costMat < Inf;
56 | bigM = 10^(ceil(log10(sum(costMat(validMat))))+1);
57 | costMat(~validMat) = bigM;
58 |
59 | % costMat(costMat~=costMat)=Inf;
60 | % validMat = costMat0)
106 | break
107 | end
108 | coverColumn = false(1,n);
109 | coverColumn(starZ(starZ>0))=true;
110 | coverRow = false(n,1);
111 | primeZ = zeros(n,1);
112 | [rIdx, cIdx] = find(dMat(~coverRow,~coverColumn)==bsxfun(@plus,minR(~coverRow),minC(~coverColumn)));
113 | while 1
114 | %**************************************************************************
115 | % STEP 4: Find a noncovered zero and prime it. If there is no starred
116 | % zero in the row containing this primed zero, Go to Step 5.
117 | % Otherwise, cover this row and uncover the column containing
118 | % the starred zero. Continue in this manner until there are no
119 | % uncovered zeros left. Save the smallest uncovered value and
120 | % Go to Step 6.
121 | %**************************************************************************
122 | cR = find(~coverRow);
123 | cC = find(~coverColumn);
124 | rIdx = cR(rIdx);
125 | cIdx = cC(cIdx);
126 | Step = 6;
127 | while ~isempty(cIdx)
128 | uZr = rIdx(1);
129 | uZc = cIdx(1);
130 | primeZ(uZr) = uZc;
131 | stz = starZ(uZr);
132 | if ~stz
133 | Step = 5;
134 | break;
135 | end
136 | coverRow(uZr) = true;
137 | coverColumn(stz) = false;
138 | z = rIdx==uZr;
139 | rIdx(z) = [];
140 | cIdx(z) = [];
141 | cR = find(~coverRow);
142 | z = dMat(~coverRow,stz) == minR(~coverRow) + minC(stz);
143 | rIdx = [rIdx(:);cR(z)];
144 | cIdx = [cIdx(:);stz(ones(sum(z),1))];
145 | end
146 | if Step == 6
147 | % *************************************************************************
148 | % STEP 6: Add the minimum uncovered value to every element of each covered
149 | % row, and subtract it from every element of each uncovered column.
150 | % Return to Step 4 without altering any stars, primes, or covered lines.
151 | %**************************************************************************
152 | [minval,rIdx,cIdx]=outerplus(dMat(~coverRow,~coverColumn),minR(~coverRow),minC(~coverColumn));
153 | minC(~coverColumn) = minC(~coverColumn) + minval;
154 | minR(coverRow) = minR(coverRow) - minval;
155 | else
156 | break
157 | end
158 | end
159 | %**************************************************************************
160 | % STEP 5:
161 | % Construct a series of alternating primed and starred zeros as
162 | % follows:
163 | % Let Z0 represent the uncovered primed zero found in Step 4.
164 | % Let Z1 denote the starred zero in the column of Z0 (if any).
165 | % Let Z2 denote the primed zero in the row of Z1 (there will always
166 | % be one). Continue until the series terminates at a primed zero
167 | % that has no starred zero in its column. Unstar each starred
168 | % zero of the series, star each primed zero of the series, erase
169 | % all primes and uncover every line in the matrix. Return to Step 3.
170 | %**************************************************************************
171 | rowZ1 = find(starZ==uZc);
172 | starZ(uZr)=uZc;
173 | while rowZ1>0
174 | starZ(rowZ1)=0;
175 | uZc = primeZ(rowZ1);
176 | uZr = rowZ1;
177 | rowZ1 = find(starZ==uZc);
178 | starZ(uZr)=uZc;
179 | end
180 | end
181 |
182 | % Cost of assignment
183 | rowIdx = find(validRow);
184 | colIdx = find(validCol);
185 | starZ = starZ(1:nRows);
186 | vIdx = starZ <= nCols;
187 | assignment(rowIdx(vIdx)) = colIdx(starZ(vIdx));
188 | pass = assignment(assignment>0);
189 | pass(~diag(validMat(assignment>0,pass))) = 0;
190 | assignment(assignment>0) = pass;
191 | cost = trace(costMat(assignment>0,assignment(assignment>0)));
192 |
193 | function [minval,rIdx,cIdx]=outerplus(M,x,y)
194 | ny=size(M,2);
195 | minval=inf;
196 | for c=1:ny
197 | M(:,c)=M(:,c)-(x+y(c));
198 | minval = min(minval,min(M(:,c)));
199 | end
200 | [rIdx,cIdx]=find(M==minval);
201 |
--------------------------------------------------------------------------------