fix: fs/D in lowpass, add missing fs attr

This commit is contained in:
2026-04-27 23:50:55 +02:00
parent 975adcdee4
commit e506a3e580

View File

@@ -15,6 +15,7 @@ import numpy as np
from numpy.fft import fft from numpy.fft import fft
from hsh_signal.signal import lowpass_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): def viterbi_highest_frequency_path_vectorized(Scp2, jump_penalty=2.0, use_log_amplitude=True):
Scp2 = np.asarray(Scp2, dtype=float) 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.M = (self.L-self.W) // self.D + 1 #: number of time steps
self.fs = fs 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. Compute scalogram amplitudes from Viterbi path of highest-power frequencies.
NOTE: downsampled from the original 'fs'. NOTE: downsampled from the original 'fs'.
:returns: (fsd, sig): sampling rate, amplitude signal :returns: (fsd, sig): sampling rate, amplitude signal
""" """
t1 = time.time()
Spf = self._spectrogram() Spf = self._spectrogram()
t2 = time.time()
pto = self._pulse_train(Spf) pto = self._pulse_train(Spf)
t3 = time.time()
Spf2 = self._spectrogram_2() Spf2 = self._spectrogram_2()
t4 = time.time()
pms = self._scalogram_params(pto) pms = self._scalogram_params(pto)
t5 = time.time()
Spsi_ss = self._scalogram_wavelets(pms) Spsi_ss = self._scalogram_wavelets(pms)
t6 = time.time()
Scp2 = self._scalogram(Spf2, Spsi_ss) Scp2 = self._scalogram(Spf2, Spsi_ss)
t7 = time.time()
path = self._viterbi_path(Scp2) path = self._viterbi_path(Scp2)
t8 = time.time()
ampl = self._viterbi_ampl(Scp2, path) ampl = self._viterbi_ampl(Scp2, path)
t9 = time.time()
if not dbg_time:
return ampl return ampl
else:
return ampl, np.diff([t1, t2, t3, t4, t5, t6, t7, t8, t9])
def _spectrogram(self): def _spectrogram(self):
"""W-FFT (STFTs) to determine scalogram parameters""" """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. # 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 #A = np.mean(g_bar) # amplitude cutoff for pulse train
ip = int(fs) 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 A = g_bar_l
# compute transitions (pulse train) # compute transitions (pulse train)
@@ -202,6 +215,8 @@ class BassAnalyzer:
f2 = self.f2 f2 = self.f2
Wp, Mp, Dp = self.Wp, self.Mp, self.Dp Wp, Mp, Dp = self.Wp, self.Mp, self.Dp
# TODO(perf): 5.0 sec runtime
# #
# compute spectrogram: 'Spf2' (M x Wp) <- from 'f' # compute spectrogram: 'Spf2' (M x Wp) <- from 'f'
# #
@@ -266,12 +281,18 @@ class BassAnalyzer:
# T, Lp, Wp # T, Lp, Wp
T, Lp, Wp = self.T, self.Lp, self.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 # compute convolution with wavelets, by multiplication in freq-domain
# 'Scp2' (M x I*J) # 'Scp2' (M x I*J)
Scp2 = np.matmul(Spf2, Spsi_ss.T) * (T/(Lp-Wp)) Scp2 = np.matmul(Spf2, Spsi_ss.T) * (T/(Lp-Wp))
return Scp2 return Scp2
def _viterbi_path(self, 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 # TODO: check if we should re-weight freq-jumps, because of log-scale frequencies
path, dp, backptr = viterbi_highest_frequency_path_vectorized( path, dp, backptr = viterbi_highest_frequency_path_vectorized(
(np.abs(Scp2)**2).T, (np.abs(Scp2)**2).T,
@@ -313,6 +334,7 @@ class GuitarAnalyzer:
self.D = int(self.shift_sec * fs) #: spectrogram step self.D = int(self.shift_sec * fs) #: spectrogram step
self.L = self.f.shape[0] self.L = self.f.shape[0]
self.M = (self.L-self.W) // self.D + 1 #: number of time steps self.M = (self.L-self.W) // self.D + 1 #: number of time steps
self.fs = fs
def spectrogram_power_amplitudes(self): def spectrogram_power_amplitudes(self):
""" """