def test_codec_Passthrough(): filter = Passthrough() output = json.dumps(filter.to_json()) assert output == '{"_type": "Passthrough", "fs": 1000}' decoded = filter_from_json(json.loads(output)) assert decoded is not None assert isinstance(decoded, Passthrough) assert filter.fs == decoded.fs
def pad_with_passthrough(filters, fs, required, optimise=False): ''' Pads to the required number of biquads. If the filter uses more than required and optimise is true, attempts to squeeze the biquad count. :param filters: the filters. :param fs: sample rate. :param required: no of required biquads. :param optimise: whether to try to reduce biquad count. :return: the raw biquad filters. ''' flattened_filters = [] for filt in filters: if isinstance(filt, PeakingEQ): flattened_filters.append(filt) elif isinstance(filt, Shelf): flattened_filters.extend(filt.flatten()) padding = required - len(flattened_filters) if padding > 0: pad_filters = [Passthrough(fs=fs)] * padding flattened_filters.extend(pad_filters) elif padding < 0: if optimise is True: from model.filter import optimise_filters padded = pad_with_passthrough(optimise_filters(filters, fs, -padding), fs, required, optimise=False) raise OptimisedFilters(padded) raise TooManyFilters(f"BEQ has too many filters for device (remove {abs(padding)} biquads)") return flattened_filters
def pad_with_passthrough(filters, fs, required): ''' Pads to the required number of biquads. :param filters: the filters. :param fs: sample rate. :param required: no of required biquads. :return: the raw biquad filters. ''' padding = required - len(filters) if padding > 0: pad_filters = [Passthrough(fs=fs, f_id=uuid4())] * padding filters.extend(pad_filters) return padding, filters
class FilterDialog(QDialog, Ui_editFilterDialog): ''' Add/Edit Filter dialog ''' is_shelf = ['Low Shelf', 'High Shelf'] gain_required = is_shelf + ['PEQ', 'Gain'] q_steps = [0.0001, 0.001, 0.01, 0.1] gain_steps = [0.1, 1.0] freq_steps = [0.1, 1.0, 2.0, 5.0] passthrough = Passthrough() def __init__(self, preferences, signal, filter_model, redraw_main, selected_filter=None, parent=None, valid_filter_types=None): self.__preferences = preferences super(FilterDialog, self).__init__(parent) if parent is not None else super( FilterDialog, self).__init__() self.__redraw_main = redraw_main # for shelf filter, allow input via Q or S not both self.__q_is_active = True # allow user to control the steps for different fields, default to reasonably quick moving values self.__q_step_idx = self.__get_step( self.q_steps, self.__preferences.get(DISPLAY_Q_STEP), 3) self.__s_step_idx = self.__get_step( self.q_steps, self.__preferences.get(DISPLAY_S_STEP), 3) self.__gain_step_idx = self.__get_step( self.gain_steps, self.__preferences.get(DISPLAY_GAIN_STEP), 0) self.__freq_step_idx = self.__get_step( self.freq_steps, self.__preferences.get(DISPLAY_FREQ_STEP), 1) # init the UI itself self.setupUi(self) self.__snapshot = FilterModel(self.snapshotFilterView, self.__preferences, on_update=self.__on_snapshot_change) self.__working = FilterModel(self.workingFilterView, self.__preferences, on_update=self.__on_working_change) self.__selected_id = None self.__decorate_ui() self.__set_q_step(self.q_steps[self.__q_step_idx]) self.__set_s_step(self.q_steps[self.__s_step_idx]) self.__set_gain_step(self.gain_steps[self.__gain_step_idx]) self.__set_freq_step(self.freq_steps[self.__freq_step_idx]) # underlying filter model self.__signal = signal self.__filter_model = filter_model if self.__filter_model.filter.listener is not None: logger.debug( f"Selected filter has listener {self.__filter_model.filter.listener.name}" ) # init the chart self.__magnitude_model = MagnitudeModel( 'preview', self.previewChart, preferences, self, 'Filter', db_range_calc=dBRangeCalculator(30, expand=True), fill_curves=True) # remove unsupported filter types if valid_filter_types: to_remove = [] for i in range(self.filterType.count()): if self.filterType.itemText(i) not in valid_filter_types: to_remove.append(i) for i1, i2 in enumerate(to_remove): self.filterType.removeItem(i2 - i1) # copy the filter into the working table self.__working.filter = self.__filter_model.clone() # and initialise the view for idx, f in enumerate(self.__working): selected = selected_filter is not None and f.id == selected_filter.id f.id = uuid4() if selected is True: self.__selected_id = f.id self.workingFilterView.selectRow(idx) if self.__selected_id is None: self.__add_working_filter() def __select_working_filter(self): ''' Loads the selected filter into the edit fields. ''' selection = self.workingFilterView.selectionModel() if selection.hasSelection(): idx = selection.selectedRows()[0].row() self.headerLabel.setText(f"Working Filter {idx+1}") self.__select_filter(self.__working[idx]) def __select_snapshot_filter(self): ''' Loads the selected filter into the edit fields. ''' selection = self.snapshotFilterView.selectionModel() if selection.hasSelection(): idx = selection.selectedRows()[0].row() self.headerLabel.setText(f"Snapshot Filter {idx+1}") self.__select_filter(self.__snapshot[idx]) def __on_snapshot_change(self, _): ''' makes the snapshot table visible when we have one. ''' self.snapshotFilterView.setVisible(len(self.__snapshot) > 0) self.snapshotViewButtonWidget.setVisible(len(self.__snapshot) > 0) self.__magnitude_model.redraw() return True def __on_working_change(self, visible_names): ''' ensure the graph redraws when a filter changes. ''' self.__magnitude_model.redraw() return True def __decorate_ui(self): ''' polishes the UI by setting tooltips, adding icons and connecting widgets to functions. ''' self.__set_tooltips() self.__set_icons() self.__connect_working_buttons() self.__connect_snapshot_buttons() self.__link_table_views() def __link_table_views(self): ''' Links the table views into the dialog. ''' self.snapshotFilterView.setVisible(False) self.snapshotViewButtonWidget.setVisible(False) self.snapshotFilterView.setModel(FilterTableModel(self.__snapshot)) self.snapshotFilterView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.snapshotFilterView.selectionModel().selectionChanged.connect( self.__select_snapshot_filter) self.workingFilterView.setModel(FilterTableModel(self.__working)) self.workingFilterView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.workingFilterView.selectionModel().selectionChanged.connect( self.__select_working_filter) def __set_icons(self): self.saveButton.setIcon(qta.icon('fa5s.save')) self.saveButton.setIconSize(QtCore.QSize(32, 32)) self.exitButton.setIcon(qta.icon('fa5s.sign-out-alt')) self.exitButton.setIconSize(QtCore.QSize(32, 32)) self.snapFilterButton.setIcon(qta.icon('fa5s.copy')) self.acceptSnapButton.setIcon(qta.icon('fa5s.check')) self.loadSnapButton.setIcon(qta.icon('fa5s.folder-open')) self.resetButton.setIcon(qta.icon('fa5s.undo')) self.optimiseButton.setIcon(qta.icon('fa5s.magic')) self.addWorkingRowButton.setIcon(qta.icon('fa5s.plus')) self.addSnapshotRowButton.setIcon(qta.icon('fa5s.plus')) self.removeWorkingRowButton.setIcon(qta.icon('fa5s.minus')) self.removeSnapshotRowButton.setIcon(qta.icon('fa5s.minus')) self.limitsButton.setIcon(qta.icon('fa5s.arrows-alt')) self.fullRangeButton.setIcon(qta.icon('fa5s.expand')) self.subOnlyButton.setIcon(qta.icon('fa5s.compress')) def __set_tooltips(self): self.addSnapshotRowButton.setToolTip('Add new filter to snapshot') self.removeSnapshotRowButton.setToolTip( 'Remove selected filter from snapshot') self.addWorkingRowButton.setToolTip('Add new filter') self.removeWorkingRowButton.setToolTip('Remove selected filter') self.snapFilterButton.setToolTip('Freeze snapshot') self.loadSnapButton.setToolTip('Load snapshot') self.acceptSnapButton.setToolTip('Apply snapshot') self.resetButton.setToolTip('Reset snapshot') self.optimiseButton.setToolTip('Optimise filters') self.targetBiquadCount.setToolTip( 'Optimised filter target biquad count') self.saveButton.setToolTip('Save') self.exitButton.setToolTip('Exit') self.limitsButton.setToolTip('Set Graph Limits') def __connect_working_buttons(self): ''' Connects the buttons associated with the working filter. ''' self.addWorkingRowButton.clicked.connect(self.__add_working_filter) self.removeWorkingRowButton.clicked.connect( self.__remove_working_filter) def __add_working_filter(self): ''' adds a new filter. ''' new_filter = self.__make_default_filter() self.__working.save(new_filter) for idx, f in enumerate(self.__working): if f.id == new_filter.id: self.__selected_id = f.id self.workingFilterView.selectRow(idx) def __remove_working_filter(self): ''' removes the selected filter. ''' selection = self.workingFilterView.selectionModel() if selection.hasSelection(): self.__working.delete([r.row() for r in selection.selectedRows()]) if len(self.__working) > 0: self.workingFilterView.selectRow(0) def __connect_snapshot_buttons(self): ''' Connects the buttons associated with the snapshot filter. ''' self.snapFilterButton.clicked.connect(self.__snap_filter) self.resetButton.clicked.connect(self.__clear_snapshot) self.acceptSnapButton.clicked.connect(self.__apply_snapshot) self.loadSnapButton.clicked.connect(self.__load_filter_as_snapshot) self.optimiseButton.clicked.connect(self.__optimise_filter) self.addSnapshotRowButton.clicked.connect(self.__add_snapshot_filter) self.removeSnapshotRowButton.clicked.connect( self.__remove_snapshot_filter) def __add_snapshot_filter(self): ''' adds a new filter. ''' new_filter = self.__make_default_filter() self.__snapshot.save(new_filter) for idx, f in enumerate(self.__snapshot): if f.id == new_filter.id: self.snapshotFilterView.selectRow(idx) def __make_default_filter(self): ''' Creates a new filter using the default preferences. ''' return LowShelf(self.__signal.fs, self.__preferences.get(FILTERS_DEFAULT_FREQ), self.__preferences.get(FILTERS_DEFAULT_Q), 0.0, f_id=uuid4()) def __remove_snapshot_filter(self): ''' removes the selected filter. ''' selection = self.snapshotFilterView.selectionModel() if selection.hasSelection(): self.__snapshot.delete([r.row() for r in selection.selectedRows()]) if len(self.__snapshot) > 0: self.snapshotFilterView.selectRow(0) def __snap_filter(self): ''' Captures the current filter as the snapshot. ''' self.__snapshot.filter = self.__working.clone() def __clear_snapshot(self): ''' Removes the current snapshot. ''' self.__snapshot.delete(range(len(self.__snapshot))) def __apply_snapshot(self): ''' Saves the snapshot as the filter. ''' if len(self.__snapshot) > 0: snap = self.__snapshot.filter self.__clear_snapshot() snap.description = self.__filter_model.filter.description self.__filter_model.filter = snap def __load_filter_as_snapshot(self): ''' Allows a filter to be loaded from a supported file format and set as the snapshot. ''' result = QMessageBox.question( self, 'Load Filter or XML?', f"Do you want to load from a filter or a minidsp beq file?" f"\n\nClick Yes to load from a filter or No for a beq file", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) load_xml = result == QMessageBox.No loaded_snapshot = None if load_xml is True: from model.minidsp import load_as_filter filters, _ = load_as_filter(self, self.__preferences, self.__signal.fs) if filters is not None: loaded_snapshot = CompleteFilter(fs=self.__signal.fs, filters=filters, description='Snapshot') else: loaded_snapshot = load_filter(self) if loaded_snapshot is not None: self.__snapshot.filter = loaded_snapshot def __optimise_filter(self): ''' Optimises the current filter and stores it as a snapshot. ''' current_filter = self.__working.clone() to_save = self.targetBiquadCount.value() - current_filter.biquads if to_save < 0: optimised_filter = CompleteFilter(fs=current_filter.fs, filters=optimise_filters( current_filter, current_filter.fs, -to_save), description='Optimised') self.__snapshot.filter = optimised_filter else: QMessageBox.information( self, 'Optimise Filters', f"Current filter uses {current_filter.biquads} biquads so no optimisation required" ) def show_limits(self): ''' shows the limits dialog for the filter chart. ''' self.__magnitude_model.show_limits() def show_full_range(self): ''' sets the limits to full range. ''' self.__magnitude_model.show_full_range() def show_sub_only(self): ''' sets the limits to sub only. ''' self.__magnitude_model.show_sub_only() def __select_filter(self, selected_filter): ''' Refreshes the params and display with the selected filter ''' from model.report import block_signals self.__selected_id = selected_filter.id # populate the fields with values if we're editing an existing filter if hasattr(selected_filter, 'gain'): with block_signals(self.filterGain): self.filterGain.setValue(selected_filter.gain) if hasattr(selected_filter, 'q'): with block_signals(self.filterQ): self.filterQ.setValue(selected_filter.q) if hasattr(selected_filter, 'freq'): with block_signals(self.freq): self.freq.setValue(selected_filter.freq) if hasattr(selected_filter, 'order'): with block_signals(self.filterOrder): self.filterOrder.setValue(selected_filter.order) if hasattr(selected_filter, 'type'): displayName = 'Butterworth' if selected_filter.type is FilterType.BUTTERWORTH else 'Linkwitz-Riley' with block_signals(self.passFilterType): self.passFilterType.setCurrentIndex( self.passFilterType.findText(displayName)) if hasattr(selected_filter, 'count') and issubclass( type(selected_filter), Shelf): with block_signals(self.filterCount): self.filterCount.setValue(selected_filter.count) with block_signals(self.filterType): self.filterType.setCurrentText(selected_filter.display_name) # configure visible/enabled fields for the current filter type self.enableFilterParams() with block_signals(self.freq): self.freq.setMaximum(self.__signal.fs / 2.0) @staticmethod def __get_step(steps, value, default_idx): for idx, val in enumerate(steps): if str(val) == value: return idx return default_idx def __write_to_filter_model(self): ''' Stores the filter in the model. ''' self.__filter_model.filter = self.__working.filter self.__signal.filter = self.__filter_model.filter self.__redraw_main() def accept(self): ''' Saves the filter. ''' self.previewFilter() self.__write_to_filter_model() def previewFilter(self): ''' creates a filter if the params are valid ''' active_model, active_view = self.__get_active() active_model.save(self.__create_filter()) self.__ensure_filter_is_selected(active_model, active_view) def __get_active(self): if self.headerLabel.text().startswith('Working') or len( self.headerLabel.text()) == 0: active_model = self.__working active_view = self.workingFilterView else: active_model = self.__snapshot active_view = self.snapshotFilterView return active_model, active_view def __ensure_filter_is_selected(self, active_model, active_view): ''' Filter model resets the model on every change, this clears the selection so we have to restore that selection to ensure the row remains visibly selected while also blocking signals to avoid a pointless update of the fields. ''' for idx, f in enumerate(active_model): if f.id == self.__selected_id: from model.report import block_signals with block_signals(active_view): active_view.selectRow(idx) def getMagnitudeData(self, reference=None): ''' preview of the filter to display on the chart ''' result = [] extra = 0 if len(self.__filter_model) > 0: result.append( self.__filter_model.getTransferFunction().getMagnitude( colour=get_filter_colour(len(result)))) else: extra += 1 if len(self.__working) > 0: result.append(self.__working.getTransferFunction().getMagnitude( colour=get_filter_colour(len(result)), linestyle='-')) else: extra += 1 if len(self.__snapshot) > 0: result.append(self.__snapshot.getTransferFunction().getMagnitude( colour=get_filter_colour(len(result) + extra), linestyle='-.')) else: extra += 1 active_model, _ = self.__get_active() for f in active_model: if self.showIndividual.isChecked() or f.id == self.__selected_id: style = '--' if f.id == self.__selected_id else ':' result.append(f.getTransferFunction().getMagnitude( colour=get_filter_colour(len(result) + extra), linestyle=style)) return result def create_shaping_filter(self): ''' Creates a filter of the specified type. :return: the filter. ''' filt = None if self.filterType.currentText() == 'Low Shelf': filt = LowShelf(self.__signal.fs, self.freq.value(), self.filterQ.value(), self.filterGain.value(), self.filterCount.value()) elif self.filterType.currentText() == 'High Shelf': filt = HighShelf(self.__signal.fs, self.freq.value(), self.filterQ.value(), self.filterGain.value(), self.filterCount.value()) elif self.filterType.currentText() == 'PEQ': filt = PeakingEQ(self.__signal.fs, self.freq.value(), self.filterQ.value(), self.filterGain.value()) elif self.filterType.currentText() == 'Gain': filt = Gain(self.__signal.fs, self.filterGain.value()) elif self.filterType.currentText() == 'Variable Q LPF': filt = SecondOrder_LowPass(self.__signal.fs, self.freq.value(), self.filterQ.value()) elif self.filterType.currentText() == 'Variable Q HPF': filt = SecondOrder_HighPass(self.__signal.fs, self.freq.value(), self.filterQ.value()) if filt is None: raise ValueError( f"Unknown filter type {self.filterType.currentText()}") else: filt.id = self.__selected_id return filt def __create_filter(self): ''' creates a filter from the currently selected parameters. ''' return self.create_pass_filter() if self.__is_pass_filter( ) else self.create_shaping_filter() def create_pass_filter(self): ''' Creates a predefined high or low pass filter. :return: the filter. ''' if self.filterType.currentText() == 'Low Pass': filt = ComplexLowPass( FilterType[self.passFilterType.currentText().upper().replace( '-', '_')], self.filterOrder.value(), self.__signal.fs, self.freq.value()) else: filt = ComplexHighPass( FilterType[self.passFilterType.currentText().upper().replace( '-', '_')], self.filterOrder.value(), self.__signal.fs, self.freq.value()) filt.id = self.__selected_id return filt def __is_pass_filter(self): ''' :return: true if the current options indicate a predefined high or low pass filter. ''' selected_filter = self.filterType.currentText() return selected_filter == 'Low Pass' or selected_filter == 'High Pass' def __is_gain_filter(self): ''' :return: true if the current options indicate a predefined high or low pass filter. ''' selected_filter = self.filterType.currentText() return selected_filter == 'Gain' def enableFilterParams(self): ''' Configures the various input fields for the currently selected filter type. ''' if self.__is_pass_filter(): self.passFilterType.setVisible(True) self.filterOrder.setVisible(True) self.orderLabel.setVisible(True) self.filterQ.setVisible(False) self.filterQLabel.setVisible(False) self.qStepButton.setVisible(False) self.filterGain.setVisible(False) self.gainStepButton.setVisible(False) self.gainLabel.setVisible(False) else: self.passFilterType.setVisible(False) self.filterOrder.setVisible(False) self.orderLabel.setVisible(False) if self.__is_gain_filter(): self.qStepButton.setVisible(False) self.filterQ.setVisible(False) self.filterQLabel.setVisible(False) self.freq.setVisible(False) self.freqStepButton.setVisible(False) self.freqLabel.setVisible(False) else: self.qStepButton.setVisible(True) self.filterQ.setVisible(True) self.filterQLabel.setVisible(True) self.freq.setVisible(True) self.freqStepButton.setVisible(True) self.freqLabel.setVisible(True) self.filterGain.setVisible(self.__is_gain_required()) self.gainStepButton.setVisible(self.__is_gain_required()) self.gainLabel.setVisible(self.__is_gain_required()) is_shelf_filter = self.__is_shelf_filter() if is_shelf_filter: self.filterQ.setEnabled(self.__q_is_active) self.filterS.setEnabled(not self.__q_is_active) # set icons inactive_icon = qta.icon('fa5s.chevron-circle-left') if self.__q_is_active is True: self.qStepButton.setText( str(self.q_steps[self.__q_step_idx % len(self.q_steps)])) self.sStepButton.setIcon(inactive_icon) else: self.qStepButton.setIcon(inactive_icon) self.sStepButton.setText( str(self.q_steps[self.__s_step_idx % len(self.q_steps)])) self.gainStepButton.setText( str(self.gain_steps[self.__gain_step_idx % len(self.gain_steps)])) self.freqStepButton.setText( str(self.freq_steps[self.__freq_step_idx % len(self.freq_steps)])) self.filterCountLabel.setVisible(is_shelf_filter) self.filterCount.setVisible(is_shelf_filter) self.sLabel.setVisible(is_shelf_filter) self.filterS.setVisible(is_shelf_filter) self.sStepButton.setVisible(is_shelf_filter) def changeOrderStep(self): ''' Sets the order step based on the type of high/low pass filter to ensure that LR only allows even orders. ''' if self.passFilterType.currentText() == 'Butterworth': self.filterOrder.setSingleStep(1) self.filterOrder.setMinimum(1) elif self.passFilterType.currentText() == 'Linkwitz-Riley': if self.filterOrder.value() % 2 != 0: self.filterOrder.setValue(max(2, self.filterOrder.value() - 1)) self.filterOrder.setSingleStep(2) self.filterOrder.setMinimum(2) def __is_gain_required(self): return self.filterType.currentText() in self.gain_required def __is_shelf_filter(self): return self.filterType.currentText() in self.is_shelf def recalcShelfFromQ(self, q): ''' Updates S based on the selected value of Q. :param q: the q. ''' gain = self.filterGain.value() if self.__q_is_active is True and not math.isclose(gain, 0.0): self.filterS.setValue(q_to_s(q, gain)) def recalcShelfFromGain(self, gain): ''' Updates S based on the selected gain. :param gain: the gain. ''' if not math.isclose(gain, 0.0): max_s = round(max_permitted_s(gain), 4) self.filterS.setMaximum(max_s) if self.__q_is_active is True: q = self.filterQ.value() self.filterS.setValue(q_to_s(q, gain)) else: if self.filterS.value() > max_s: self.filterS.blockSignals(True) self.filterS.setValue(max_s, 4) self.filterS.blockSignals(False) self.filterQ.setValue(s_to_q(self.filterS.value(), gain)) def recalcShelfFromS(self, s): ''' Updates the shelf based on a change in S :param s: the new S ''' gain = self.filterGain.value() if self.__q_is_active is False and not math.isclose(gain, 0.0): self.filterQ.setValue(s_to_q(s, gain)) def handleSToolButton(self): ''' Reacts to the S tool button click. ''' if self.__q_is_active is True: self.__q_is_active = False self.filterS.setEnabled(True) self.sStepButton.setIcon(QIcon()) self.filterQ.setEnabled(False) self.qStepButton.setIcon(qta.icon('fa5s.chevron-circle-left')) else: self.__s_step_idx += 1 self.__set_s_step(self.q_steps[self.__s_step_idx % len(self.q_steps)]) def __set_s_step(self, step_val): self.__preferences.set(DISPLAY_S_STEP, str(step_val)) self.sStepButton.setText(str(step_val)) self.filterS.setSingleStep(step_val) def handleQToolButton(self): ''' Reacts to the q tool button click. ''' if self.__q_is_active is True: self.__q_step_idx += 1 else: self.__q_is_active = True self.filterS.setEnabled(False) self.qStepButton.setIcon(QIcon()) self.filterQ.setEnabled(True) self.sStepButton.setIcon(qta.icon('fa5s.chevron-circle-left')) self.__set_q_step(self.q_steps[self.__q_step_idx % len(self.q_steps)]) def __set_q_step(self, step_val): self.__preferences.set(DISPLAY_Q_STEP, str(step_val)) self.qStepButton.setText(str(step_val)) self.filterQ.setSingleStep(step_val) def handleGainToolButton(self): ''' Reacts to the gain tool button click. ''' self.__gain_step_idx += 1 self.__set_gain_step(self.gain_steps[self.__gain_step_idx % len(self.gain_steps)]) def __set_gain_step(self, step_val): self.__preferences.set(DISPLAY_GAIN_STEP, str(step_val)) self.gainStepButton.setText(str(step_val)) self.filterGain.setSingleStep(step_val) def handleFreqToolButton(self): ''' Reacts to the frequency tool button click. ''' self.__freq_step_idx += 1 self.__set_freq_step(self.freq_steps[self.__freq_step_idx % len(self.freq_steps)]) def __set_freq_step(self, step_val): self.__preferences.set(DISPLAY_FREQ_STEP, str(step_val)) self.freqStepButton.setText(str(step_val)) self.freq.setSingleStep(step_val)
def filter_from_json(o): ''' Converts a dict (parsed from json) to a filter. :param o: the dict. :return: the filter. ''' from model.iir import Passthrough, PeakingEQ, LowShelf, HighShelf, FirstOrder_LowPass, \ FirstOrder_HighPass, SecondOrder_LowPass, SecondOrder_HighPass, AllPass, CompleteFilter, ComplexLowPass, \ FilterType, ComplexHighPass filt = None if '_type' not in o: raise ValueError(f"{o} is not a filter") if o['_type'] == Passthrough.__name__: if 'fs' in o: filt = Passthrough(fs=int(o['fs'])) else: filt = Passthrough() elif o['_type'] == Gain.__name__: filt = Gain(o['fs'], o['gain']) elif o['_type'] == PeakingEQ.__name__: filt = PeakingEQ(o['fs'], o['fc'], o['q'], o['gain']) elif o['_type'] == LowShelf.__name__: filt = LowShelf(o['fs'], o['fc'], o['q'], o['gain'], o['count']) elif o['_type'] == HighShelf.__name__: filt = HighShelf(o['fs'], o['fc'], o['q'], o['gain'], o['count']) elif o['_type'] == FirstOrder_LowPass.__name__: filt = FirstOrder_LowPass(o['fs'], o['fc']) elif o['_type'] == FirstOrder_HighPass.__name__: filt = FirstOrder_HighPass(o['fs'], o['fc']) elif o['_type'] == SecondOrder_LowPass.__name__: filt = SecondOrder_LowPass(o['fs'], o['fc'], o['q']) elif o['_type'] == SecondOrder_HighPass.__name__: filt = SecondOrder_HighPass(o['fs'], o['fc'], o['q']) elif o['_type'] == AllPass.__name__: filt = AllPass(o['fs'], o['fc'], o['q']) elif o['_type'] == CompleteFilter.__name__: kwargs = {} if 'fs' in o: kwargs['fs'] = o['fs'] filt = CompleteFilter(filters=[filter_from_json(x) for x in o['filters']], description=o['description'], **kwargs) elif o['_type'] == ComplexLowPass.__name__: filt = ComplexLowPass(FilterType(o['filter_type']), o['order'], o['fs'], o['fc']) elif o['_type'] == ComplexHighPass.__name__: filt = ComplexHighPass(FilterType(o['filter_type']), o['order'], o['fs'], o['fc']) if filt is None: raise ValueError(f"{o._type} is an unknown filter type") else: if filt.id == -1: filt.id = uuid4() return filt