diff --git a/rhythm.py b/rhythm.py index 2a231a4..6c3e9c3 100644 --- a/rhythm.py +++ b/rhythm.py @@ -15,6 +15,7 @@ import numpy as np from numpy.fft import fft from hsh_signal.signal import lowpass_fft +import time def viterbi_highest_frequency_path_vectorized(Scp2, jump_penalty=2.0, use_log_amplitude=True): Scp2 = np.asarray(Scp2, dtype=float) @@ -127,21 +128,33 @@ class BassAnalyzer: self.M = (self.L-self.W) // self.D + 1 #: number of time steps self.fs = fs - def viterbi_wavelet_scalogram_amplitudes(self): + def viterbi_wavelet_scalogram_amplitudes(self, dbg_time=False): """ Compute scalogram amplitudes from Viterbi path of highest-power frequencies. NOTE: downsampled from the original 'fs'. :returns: (fsd, sig): sampling rate, amplitude signal """ + t1 = time.time() Spf = self._spectrogram() + t2 = time.time() pto = self._pulse_train(Spf) + t3 = time.time() Spf2 = self._spectrogram_2() + t4 = time.time() pms = self._scalogram_params(pto) + t5 = time.time() Spsi_ss = self._scalogram_wavelets(pms) + t6 = time.time() Scp2 = self._scalogram(Spf2, Spsi_ss) + t7 = time.time() path = self._viterbi_path(Scp2) + t8 = time.time() ampl = self._viterbi_ampl(Scp2, path) - return ampl + t9 = time.time() + if not dbg_time: + return ampl + else: + return ampl, np.diff([t1, t2, t3, t4, t5, t6, t7, t8, t9]) def _spectrogram(self): """W-FFT (STFTs) to determine scalogram parameters""" @@ -171,7 +184,7 @@ class BassAnalyzer: # TODO: check if 'A' needs to be a smooth signal slowly varying over time, not a const. #A = np.mean(g_bar) # amplitude cutoff for pulse train ip = int(fs) - g_bar_l = lowpass_fft(np.pad(g_bar, (ip, ip), mode='edge'), fps=fs, cf=0.5, tw=0.05)[ip:-ip] + g_bar_l = lowpass_fft(np.pad(g_bar, (ip, ip), mode='edge'), fps=fs/self.D, cf=0.5, tw=0.05)[ip:-ip] A = g_bar_l # compute transitions (pulse train) @@ -202,6 +215,8 @@ class BassAnalyzer: f2 = self.f2 Wp, Mp, Dp = self.Wp, self.Mp, self.Dp + # TODO(perf): 5.0 sec runtime + # # compute spectrogram: 'Spf2' (M x Wp) <- from 'f' # @@ -266,12 +281,18 @@ class BassAnalyzer: # T, Lp, Wp T, Lp, Wp = self.T, self.Lp, self.Wp + # TODO(perf): reduce num of wavelets, and/or parallelize into freq slices + # TODO(perf): 3.5 sec runtime + # compute convolution with wavelets, by multiplication in freq-domain # 'Scp2' (M x I*J) Scp2 = np.matmul(Spf2, Spsi_ss.T) * (T/(Lp-Wp)) return Scp2 def _viterbi_path(self, Scp2): + # TODO(perf): parallelize into time slices + # TODO(perf): 4.5 sec runtime + # TODO: check if we should re-weight freq-jumps, because of log-scale frequencies path, dp, backptr = viterbi_highest_frequency_path_vectorized( (np.abs(Scp2)**2).T, @@ -313,6 +334,7 @@ class GuitarAnalyzer: self.D = int(self.shift_sec * fs) #: spectrogram step self.L = self.f.shape[0] self.M = (self.L-self.W) // self.D + 1 #: number of time steps + self.fs = fs def spectrogram_power_amplitudes(self): """