├── 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 | ![Male voice signal tracking results (/kara/).](http://www.music.mcgill.ca/~julian/wp-content/uploads/2020/11/partials_kara.png) 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 | --------------------------------------------------------------------------------