From 12de31831e7e380a38e27156a0840700f05d37a2 Mon Sep 17 00:00:00 2001 From: 3ll3d00d Date: Fri, 28 Dec 2018 22:17:15 +0000 Subject: [PATCH] allow user to set fractional octave preference #108 fix resample #173 --- src/main/python/model/preferences.py | 5 ++ src/main/python/model/signal.py | 43 ++++++++++---- src/main/python/ui/preferences.py | 38 ++++++++----- src/main/python/ui/preferences.ui | 85 ++++++++++++++++++---------- src/main/python/ui/signal.py | 1 + src/main/python/ui/signal.ui | 19 ++++++- 6 files changed, 136 insertions(+), 55 deletions(-) diff --git a/src/main/python/model/preferences.py b/src/main/python/model/preferences.py index 2d79027a..53bf91c9 100644 --- a/src/main/python/model/preferences.py +++ b/src/main/python/model/preferences.py @@ -88,6 +88,7 @@ DISPLAY_GAIN_STEP = 'display/gain_step' DISPLAY_LINE_STYLE = 'display/line_style' DISPLAY_SMOOTH_FULL_RANGE = 'display/smooth_full' +DISPLAY_SMOOTH_FRACTION = 'display/fraction' GRAPH_X_AXIS_SCALE = 'graph/x_axis' GRAPH_X_MIN = 'graph/x_min' @@ -161,6 +162,7 @@ DISPLAY_GAIN_STEP: '0.1', DISPLAY_LINE_STYLE: True, DISPLAY_SMOOTH_FULL_RANGE: True, + DISPLAY_SMOOTH_FRACTION: 3, EXTRACTION_OUTPUT_DIR: os.path.expanduser('~'), EXTRACTION_MIX_MONO: False, EXTRACTION_COMPRESS: False, @@ -212,6 +214,7 @@ DISPLAY_SHOW_LEGEND: bool, DISPLAY_LINE_STYLE: bool, DISPLAY_SMOOTH_FULL_RANGE: bool, + DISPLAY_SMOOTH_FRACTION: int, EXTRACTION_MIX_MONO: bool, EXTRACTION_COMPRESS: bool, EXTRACTION_DECIMATE: bool, @@ -408,6 +411,7 @@ def __init__(self, preferences, style_root, main_chart_limits, parent=None): self.bmlpfFreq.setValue(self.__preferences.get(BASS_MANAGEMENT_LPF_FS)) self.smoothFullRange.setChecked(self.__preferences.get(DISPLAY_SMOOTH_FULL_RANGE)) + self.fractionalOctave.setValue(self.__preferences.get(DISPLAY_SMOOTH_FRACTION)) self.__count_beq_files() @@ -503,6 +507,7 @@ def accept(self): self.__preferences.set(BEQ_DOWNLOAD_DIR, self.beqFiltersDir.text()) self.__preferences.set(BASS_MANAGEMENT_LPF_FS, self.bmlpfFreq.value()) self.__preferences.set(DISPLAY_SMOOTH_FULL_RANGE, self.smoothFullRange.isChecked()) + self.__preferences.set(DISPLAY_SMOOTH_FRACTION, self.fractionalOctave.value()) QDialog.accept(self) diff --git a/src/main/python/model/signal.py b/src/main/python/model/signal.py index 954ddfe8..9c87b5d4 100644 --- a/src/main/python/model/signal.py +++ b/src/main/python/model/signal.py @@ -796,7 +796,8 @@ def raw(self): """ return self.samples - def spectrum(self, ref=SPECLAB_REFERENCE, resolution_shift=0, window=None, smooth_full=True, **kwargs): + def spectrum(self, ref=SPECLAB_REFERENCE, resolution_shift=0, window=None, smooth_full=True, smooth_fraction=3, + **kwargs): """ analyses the source to generate the linear spectrum. :param ref: the reference value for dB purposes. @@ -817,7 +818,7 @@ def spectrum(self, ref=SPECLAB_REFERENCE, resolution_shift=0, window=None, smoot f = results[0][0] if self.fs > 24000 and smooth_full: from acoustics.smooth import fractional_octaves - fob, Pxx_spec = fractional_octaves(f, Pxx_spec) + fob, Pxx_spec = fractional_octaves(f, Pxx_spec, fraction=smooth_fraction) f = fob.center # a 3dB adjustment is required to account for the change in nperseg Pxx_spec = amplitude_to_db(np.sqrt(Pxx_spec), ref * SPECLAB_REFERENCE) @@ -829,7 +830,7 @@ def __segment_spectrum(self, segment, resolution_shift=0, window=None, **kwargs) window=window if window else 'hann', **kwargs) return f, Pxx_spec - def peakSpectrum(self, ref=SPECLAB_REFERENCE, resolution_shift=0, window=None, smooth_full=True): + def peakSpectrum(self, ref=SPECLAB_REFERENCE, resolution_shift=0, window=None, smooth_full=True, smooth_fraction=3): """ analyses the source to generate the max values per bin per segment :param resolution_shift: allows resolution to go down (if positive) or up (if negative). @@ -850,7 +851,7 @@ def peakSpectrum(self, ref=SPECLAB_REFERENCE, resolution_shift=0, window=None, s f = results[0][0] if self.fs > 24000 and smooth_full: from acoustics.smooth import fractional_octaves - fob, Pxy_max = fractional_octaves(f, Pxy_max) + fob, Pxy_max = fractional_octaves(f, Pxy_max, fraction=smooth_fraction) f = fob.center # a 3dB adjustment is required to account for the change in nperseg Pxy_max = amplitude_to_db(Pxy_max, ref=ref * SPECLAB_REFERENCE) @@ -976,15 +977,19 @@ def calculate_peak_average(self, preferences): ''' caches the peak and avg spectrum. ''' - from model.preferences import ANALYSIS_RESOLUTION, ANALYSIS_PEAK_WINDOW, ANALYSIS_AVG_WINDOW, DISPLAY_SMOOTH_FULL_RANGE + from model.preferences import ANALYSIS_RESOLUTION, ANALYSIS_PEAK_WINDOW, ANALYSIS_AVG_WINDOW, \ + DISPLAY_SMOOTH_FULL_RANGE, DISPLAY_SMOOTH_FRACTION resolution_shift = math.log(preferences.get(ANALYSIS_RESOLUTION), 2) peak_wnd = self.__get_window(preferences, ANALYSIS_PEAK_WINDOW) avg_wnd = self.__get_window(preferences, ANALYSIS_AVG_WINDOW) smooth_full = preferences.get(DISPLAY_SMOOTH_FULL_RANGE) + smooth_fraction = preferences.get(DISPLAY_SMOOTH_FRACTION) logger.debug( f"Analysing {self.name} at {resolution_shift}x resolution using {peak_wnd}/{avg_wnd} peak/avg windows") - self.__avg = self.spectrum(resolution_shift=resolution_shift, window=avg_wnd, smooth_full=smooth_full) - self.__peak = self.peakSpectrum(resolution_shift=resolution_shift, window=peak_wnd, smooth_full=smooth_full) + self.__avg = self.spectrum(resolution_shift=resolution_shift, window=avg_wnd, smooth_full=smooth_full, + smooth_fraction=smooth_fraction) + self.__peak = self.peakSpectrum(resolution_shift=resolution_shift, window=peak_wnd, smooth_full=smooth_full, + smooth_fraction=smooth_fraction) self.__cached = [XYData(self.name, 'avg', self.__avg[0], self.__avg[1]), XYData(self.name, 'peak', self.__peak[0], self.__peak[1])] @@ -1142,9 +1147,12 @@ def auto_load(self, name_provider, decimate, offset=0.0): start = time.time() try: for x in range(0, self.info.channels): - self.prepare(channel=x + 1, name=name_provider(x, self.info.channels), channel_count=self.info.channels, decimate=decimate) + self.prepare(channel=x + 1, name=name_provider(x, self.info.channels), + channel_count=self.info.channels, + decimate=decimate) if self.info.channels > 1: - signals = [self.get_signal(x, name_provider(x-1, self.info.channels), offset=offset) for x in self.__cache.keys()] + signals = [self.get_signal(x, name_provider(x-1, self.info.channels), offset=offset) + for x in self.__cache.keys()] lpf_fs = self.__preferences.get(BASS_MANAGEMENT_LPF_FS) lpf_position = self.__preferences.get(BASS_MANAGEMENT_LPF_POSITION) return BassManagedSignalData(signals, lpf_fs, lpf_position) @@ -1229,6 +1237,9 @@ def has_signal(self): ''' return len(self.__cache) > 0 + def toggle_decimate(self): + pass + class DialogWavLoaderBridge: ''' @@ -1242,6 +1253,10 @@ def __init__(self, dialog, preferences, allow_multichannel=True): self.__duration = 0 self.__allow_multichannel = allow_multichannel + def toggle_decimate(self, channel_idx): + self.__auto_loader.clear_cache() + self.prepare_signal(channel_idx) + def select_wav_file(self): file = select_file(self.__dialog, ['wav', 'flac']) if file is not None: @@ -1297,7 +1312,8 @@ def prepare_signal(self, channel_idx): ''' Reads the actual file and calculates the relevant peak/avg spectrum. ''' - self.__auto_loader.prepare(name=self.__dialog.wavSignalName.text(), channel=channel_idx, + self.__auto_loader.prepare(name=self.__dialog.wavSignalName.text(), + channel=channel_idx, decimate=self.__dialog.decimate.isChecked()) self.__dialog.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) self.__dialog.gainOffset.setEnabled(True) @@ -1615,6 +1631,13 @@ def __apply_default_filter(self, signal): for s in signal: s.filter = self.__signal_model.default_signal.filter.resample(s.fs) + def toggleDecimate(self, state): + ''' toggles whether to decimate ''' + from app import wait_cursor + with (wait_cursor()): + self.__loaders[self.__loader_idx].toggle_decimate(int(self.wavChannelSelector.currentText())) + self.__magnitudeModel.redraw() + def read_wav_data(input_file, start=None, end=None): ''' diff --git a/src/main/python/ui/preferences.py b/src/main/python/ui/preferences.py index 7fbf8ae9..53fc006e 100644 --- a/src/main/python/ui/preferences.py +++ b/src/main/python/ui/preferences.py @@ -180,6 +180,15 @@ def setupUi(self, preferencesDialog): self.xmin.setProperty("value", 2) self.xmin.setObjectName("xmin") self.graphPane.addWidget(self.xmin, 2, 1, 1, 1) + self.xminmaxLabel = QtWidgets.QLabel(preferencesDialog) + self.xminmaxLabel.setObjectName("xminmaxLabel") + self.graphPane.addWidget(self.xminmaxLabel, 2, 0, 1, 1) + self.xmax = QtWidgets.QSpinBox(preferencesDialog) + self.xmax.setMinimum(2) + self.xmax.setMaximum(24000) + self.xmax.setProperty("value", 160) + self.xmax.setObjectName("xmax") + self.graphPane.addWidget(self.xmax, 2, 2, 1, 1) self.freqIsLogScale = QtWidgets.QCheckBox(preferencesDialog) self.freqIsLogScale.setChecked(True) self.freqIsLogScale.setObjectName("freqIsLogScale") @@ -194,19 +203,22 @@ def setupUi(self, preferencesDialog): self.graphLabel.setAlignment(QtCore.Qt.AlignCenter) self.graphLabel.setObjectName("graphLabel") self.graphPane.addWidget(self.graphLabel, 0, 0, 1, 3) - self.xminmaxLabel = QtWidgets.QLabel(preferencesDialog) - self.xminmaxLabel.setObjectName("xminmaxLabel") - self.graphPane.addWidget(self.xminmaxLabel, 2, 0, 1, 1) - self.xmax = QtWidgets.QSpinBox(preferencesDialog) - self.xmax.setMinimum(2) - self.xmax.setMaximum(24000) - self.xmax.setProperty("value", 160) - self.xmax.setObjectName("xmax") - self.graphPane.addWidget(self.xmax, 2, 2, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.fractionalOctaveLabel = QtWidgets.QLabel(preferencesDialog) + self.fractionalOctaveLabel.setObjectName("fractionalOctaveLabel") + self.horizontalLayout.addWidget(self.fractionalOctaveLabel) + self.fractionalOctave = QtWidgets.QSpinBox(preferencesDialog) + self.fractionalOctave.setMinimum(1) + self.fractionalOctave.setMaximum(24) + self.fractionalOctave.setProperty("value", 3) + self.fractionalOctave.setObjectName("fractionalOctave") + self.horizontalLayout.addWidget(self.fractionalOctave) self.smoothFullRange = QtWidgets.QCheckBox(preferencesDialog) self.smoothFullRange.setChecked(True) self.smoothFullRange.setObjectName("smoothFullRange") - self.graphPane.addWidget(self.smoothFullRange, 1, 1, 1, 1) + self.horizontalLayout.addWidget(self.smoothFullRange) + self.graphPane.addLayout(self.horizontalLayout, 1, 1, 1, 2) self.panes.addLayout(self.graphPane) self.filterPane = QtWidgets.QGridLayout() self.filterPane.setObjectName("filterPane") @@ -325,8 +337,7 @@ def setupUi(self, preferencesDialog): preferencesDialog.setTabOrder(self.extractCompleteAudioFilePicker, self.themePicker) preferencesDialog.setTabOrder(self.themePicker, self.freqIsLogScale) preferencesDialog.setTabOrder(self.freqIsLogScale, self.xmin) - preferencesDialog.setTabOrder(self.xmin, self.xmax) - preferencesDialog.setTabOrder(self.xmax, self.ffmpegDirectory) + preferencesDialog.setTabOrder(self.xmin, self.ffmpegDirectory) preferencesDialog.setTabOrder(self.ffmpegDirectory, self.defaultOutputDirectory) preferencesDialog.setTabOrder(self.defaultOutputDirectory, self.extractCompleteAudioFile) preferencesDialog.setTabOrder(self.extractCompleteAudioFile, self.ffprobeDirectory) @@ -370,9 +381,10 @@ def retranslateUi(self, preferencesDialog): self.themePicker.setItemText(0, _translate("preferencesDialog", "default")) self.styleLabel.setText(_translate("preferencesDialog", "Style")) self.speclabLineStyle.setText(_translate("preferencesDialog", "Speclab Line Colours?")) + self.xminmaxLabel.setText(_translate("preferencesDialog", "x min/max")) self.freqIsLogScale.setText(_translate("preferencesDialog", "Frequency Axis Log Scale?")) self.graphLabel.setText(_translate("preferencesDialog", "Graph")) - self.xminmaxLabel.setText(_translate("preferencesDialog", "x min/max")) + self.fractionalOctaveLabel.setText(_translate("preferencesDialog", "Fractional Octave:")) self.smoothFullRange.setText(_translate("preferencesDialog", "Smooth Full Range?")) self.filterQLabel.setText(_translate("preferencesDialog", "Default Q")) self.filterFreqLabel.setText(_translate("preferencesDialog", "Default Freq")) diff --git a/src/main/python/ui/preferences.ui b/src/main/python/ui/preferences.ui index c793f8c6..0b62ab2b 100644 --- a/src/main/python/ui/preferences.ui +++ b/src/main/python/ui/preferences.ui @@ -367,7 +367,7 @@ - + @@ -381,6 +381,26 @@ + + + + x min/max + + + + + + + 2 + + + 24000 + + + 160 + + + @@ -413,35 +433,39 @@ - - - - x min/max - - - - - - - 2 - - - 24000 - - - 160 - - - - - - - Smooth Full Range? - - - true - - + + + + + + Fractional Octave: + + + + + + + 1 + + + 24 + + + 3 + + + + + + + Smooth Full Range? + + + true + + + + @@ -664,7 +688,6 @@ themePicker freqIsLogScale xmin - xmax ffmpegDirectory defaultOutputDirectory extractCompleteAudioFile diff --git a/src/main/python/ui/signal.py b/src/main/python/ui/signal.py index ec261b9c..9a298162 100644 --- a/src/main/python/ui/signal.py +++ b/src/main/python/ui/signal.py @@ -191,6 +191,7 @@ def setupUi(self, addSignalDialog): self.applyTimeRangeButton.clicked.connect(addSignalDialog.limitTimeRange) self.wavStartTime.timeChanged['QTime'].connect(addSignalDialog.enableLimitTimeRangeButton) self.wavEndTime.timeChanged['QTime'].connect(addSignalDialog.enableLimitTimeRangeButton) + self.decimate.stateChanged['int'].connect(addSignalDialog.toggleDecimate) QtCore.QMetaObject.connectSlotsByName(addSignalDialog) addSignalDialog.setTabOrder(self.signalTypeTabs, self.wavFilePicker) addSignalDialog.setTabOrder(self.wavFilePicker, self.wavChannelSelector) diff --git a/src/main/python/ui/signal.ui b/src/main/python/ui/signal.ui index 7f72aa5a..6e219125 100644 --- a/src/main/python/ui/signal.ui +++ b/src/main/python/ui/signal.ui @@ -526,7 +526,7 @@ enableOk() - 159 + 161 193 @@ -615,6 +615,22 @@ + + decimate + stateChanged(int) + addSignalDialog + toggleDecimate() + + + 118 + 351 + + + 50 + 588 + + + selectFile() @@ -630,5 +646,6 @@ limitTimeRange() enableLimitTimeRangeButton() offsetGain() + toggleDecimate()