diff --git a/friture/pitch_tracker.py b/friture/pitch_tracker.py index a93180bd..f1ff860e 100644 --- a/friture/pitch_tracker.py +++ b/friture/pitch_tracker.py @@ -27,7 +27,7 @@ from PyQt5.QtQuick import QQuickWindow from PyQt5.QtQuickWidgets import QQuickWidget from PyQt5.QtQml import QQmlComponent, QQmlEngine -from typing import Optional +from typing import Any, no_type_check, Optional from friture.audiobackend import SAMPLING_RATE from friture.audioproc import audioproc @@ -37,8 +37,8 @@ DEFAULT_MAX_FREQ, DEFAULT_DURATION, DEFAULT_FFT_SIZE, - DEFAULT_MIN_SNR, - PitchTrackerSettingsDialog + DEFAULT_MIN_DB, + PitchTrackerSettingsDialog, ) from friture.plotting.coordinateTransform import CoordinateTransform import friture.plotting.frequency_scales as fscales @@ -127,15 +127,17 @@ def __init__(self, parent, engine): self.min_freq = DEFAULT_MIN_FREQ self.max_freq = DEFAULT_MAX_FREQ - self._pitch_tracker_data.vertical_axis.setRange( + self._pitch_tracker_data.vertical_axis.setRange( # type: ignore self.min_freq, self.max_freq) - self._pitch_tracker_data.vertical_axis.setScale(fscales.Octave) + self._pitch_tracker_data.vertical_axis.setScale( # type: ignore + fscales.Octave) self.vertical_transform = CoordinateTransform( self.min_freq, self.max_freq, 1, 0, 0) self.vertical_transform.setScale(fscales.Octave) self.duration = DEFAULT_DURATION - self._pitch_tracker_data.horizontal_axis.setRange(-self.duration, 0.) + self._pitch_tracker_data.horizontal_axis.setRange( # type: ignore + -self.duration, 0.) self.settings_dialog = PitchTrackerSettingsDialog(self) @@ -185,8 +187,8 @@ def set_duration(self, value): self.duration = value self._pitch_tracker_data.horizontal_axis.setRange(-self.duration, 0.) - def set_min_snr(self, value: float): - self.tracker.min_snr = value + def set_min_db(self, value: float): + self.tracker.min_db = value # slot def settings_called(self, checked): @@ -222,14 +224,14 @@ def pitch(self, pitch: float): self._pitch = pitch self.pitch_changed.emit(pitch) - @pyqtProperty(str, notify=pitch_changed) + @pyqtProperty(str, notify=pitch_changed) # type: ignore def pitch_unit(self) -> str: if self._pitch >= 1000.0: return "kHz" else: return "Hz" - @pyqtProperty(str, notify=pitch_changed) + @pyqtProperty(str, notify=pitch_changed) # type: ignore def note(self) -> str: if not self._pitch or np.isnan(self._pitch): return '--' @@ -244,12 +246,12 @@ def __init__( fft_size: int = DEFAULT_FFT_SIZE, overlap: float = 0.75, sample_rate: int = SAMPLING_RATE, - min_snr: float = DEFAULT_MIN_SNR, + min_db: float = DEFAULT_MIN_DB, ): self.fft_size = fft_size self.overlap = overlap self.sample_rate = sample_rate - self.min_snr = min_snr + self.min_db = min_db self.input_buf = input_buf self.input_buf.grow_if_needed(fft_size) @@ -307,11 +309,10 @@ def estimate_pitch(self, frame: np.ndarray) -> Optional[float]: # try to take the log of zero. return None - # Compute SNR for the detected pitch; if it's too low presume it's + # Compute dB for the detected fundamental; if it's too low presume it's # a false detection and return no result. - variance = np.mean(spectrum ** 2) - snr = 10 * np.log10((spectrum[pitch_idx] ** 2) / variance) - if snr < self.min_snr: + db = 10 * np.log10(spectrum[pitch_idx] ** 2 / self.fft_size ** 2) + if db < self.min_db: return None else: return self.proc.freq[pitch_idx] diff --git a/friture/pitch_tracker_settings.py b/friture/pitch_tracker_settings.py index 72b1e670..7c4a6850 100644 --- a/friture/pitch_tracker_settings.py +++ b/friture/pitch_tracker_settings.py @@ -25,7 +25,7 @@ DEFAULT_MIN_FREQ = 80 DEFAULT_MAX_FREQ = 1000 DEFAULT_DURATION = 30 -DEFAULT_MIN_SNR = 3.0 +DEFAULT_MIN_DB = -70.0 DEFAULT_FFT_SIZE = 16384 class PitchTrackerSettingsDialog(QtWidgets.QDialog): @@ -64,15 +64,15 @@ def __init__(self, parent): self.duration.valueChanged.connect(self.parent().set_duration) self.form_layout.addRow("Duration:", self.duration) - self.min_snr = QtWidgets.QDoubleSpinBox(self) - self.min_snr.setMinimum(0) - self.min_snr.setMaximum(50) - self.min_snr.setSingleStep(1) - self.min_snr.setValue(DEFAULT_MIN_SNR) - self.min_snr.setSuffix(" dB") - self.min_snr.setObjectName("min_snr") - self.min_snr.valueChanged.connect(self.parent().set_min_snr) - self.form_layout.addRow("Min SNR:", self.min_snr) + self.min_db = QtWidgets.QDoubleSpinBox(self) + self.min_db.setMinimum(-100) + self.min_db.setMaximum(0) + self.min_db.setSingleStep(1) + self.min_db.setValue(DEFAULT_MIN_DB) + self.min_db.setSuffix(" dB") + self.min_db.setObjectName("min_db") + self.min_db.valueChanged.connect(self.parent().set_min_db) # type: ignore + self.form_layout.addRow("Min Amplitude:", self.min_db) self.setLayout(self.form_layout) @@ -80,7 +80,7 @@ def save_state(self, settings): settings.setValue("min_freq", self.min_freq.value()) settings.setValue("max_freq", self.max_freq.value()) settings.setValue("duration", self.duration.value()) - settings.setValue("min_snr", self.min_snr.value()) + settings.setValue("min_db", self.min_db.value()) def restore_state(self, settings): self.min_freq.setValue( @@ -89,5 +89,6 @@ def restore_state(self, settings): settings.value("max_freq", DEFAULT_MAX_FREQ, type=int)) self.duration.setValue( settings.value("duration", DEFAULT_DURATION, type=int)) - self.min_snr.setValue( - settings.value("min_snr", DEFAULT_MIN_SNR, type=float)) + self.min_db.setValue( + settings.value("min_db", DEFAULT_MIN_DB, type=float)) +