From 6aed85c3c9b4a859a7e0574f8fbae83323c08845 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 20 Feb 2025 09:58:01 +0800 Subject: [PATCH] =?UTF-8?q?[feature]=20=E6=96=B0=E5=A2=9Efilter=20model?= =?UTF-8?q?=E4=B8=ADget/set=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../audio_filter_model.cpython-313.pyc | Bin 8222 -> 12543 bytes .../widget_filter/audio_filter_componet.py | 199 ++++++++++++------ component/widget_filter/audio_filter_model.py | 77 ++++++- 3 files changed, 204 insertions(+), 72 deletions(-) diff --git a/component/widget_filter/__pycache__/audio_filter_model.cpython-313.pyc b/component/widget_filter/__pycache__/audio_filter_model.cpython-313.pyc index d21318fe6f3a1690520097f7e7bd142150f08ae4..6c460445be20e788737820c49c6c3cb152127752 100644 GIT binary patch delta 6005 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 X_Q$#x3Emcm9*$n@jUw(<{YD delta 2375 zcmZ`)>u(fQ6rVef-PzsQWw-5?w%fT<-zB zw_9|z-R>gLz^qjTtM5Nnjlby!TVT#cgF~7goVdH0wUIueurYFugqbSt^an5-g2MX6 z21th@dsnv%)PjJ=qpbiB;m05P94re0D?Ih0IYnN*BMq4aU2&we zWz$Z4dAaBVY{5qhSVGHwOWBM?L4>Z5^|GQ>2B({i{5@ z#AcT8PZFK|<=?QY2VdBZ@G`;<09~Z*xJO?bFKV`Y%Ik>s98G1jrnM(UQ=@iUAvcg2 zP8XhM`M?1;T_^h`Fq7Ja5xNjwL3kD6HH2OOolqo}F4489n!mg!{~fCFkj~Pl(+$$x zC{MVaEemGH7hGd#(QDj*fz~6ugy2Eggy2QM1JTV0|9hcZ(TaeyvC{w_3ikuyQ@y`y zky9)b>Ic%#dZZ^J(eW!FT@aE2B?hp}AXGBMq-;n@rAx+^8rk6Y6t;Ni6ZpW}yFlk>dV_JW5N~~bV!_DzM{747G7Od`bFm92# z{UL?j4aeHC>-f_y>=P_`P8tF>B{&as#qAFROmr)IAyU^TJ>eNe^;3VpqYexVXNL;| z15Wj`)wRu;kh69(z)68!i>xFy?5DB@Kh}c3nf1(C9b@6L3f3FddWMKD7Nek_{^9zA zk1kDre)ZwS)6>_#ou2yU!B>|PK>Ph*bUdki-ppp^x}YC)^IgDv6|D_OV;h_Fq;Ox= z&TD72+iL8F8vC;EmfAvV*vX1Gd7ph1d~CH)`5hePxUPmey2~1Px>7E34Ui%X`_a%38K_>DrYdJq&(49&{6LTMR{c z)p!Sd_OKt9c6we1$<`rYEeltvOKxfnvq90ndFp)iIAxjzc`5# zy2?AtZ9dH8CgwSmv}LBKJI@Y*X*L-T=xBqSsRQX;FOB)#nXT`BUS+|iKVR!02 zfw4bQPsunB&3VTU#^aOYG=yP^;~CCo%xpnd9eKpgWf!m%V=gF3&H*nRIa3_X1DW|< z7trG^0EZ0nG4{;FcQiY7j2()vBKfJ)@oBPuky7{w*S$OpeH&xRRxXusuZ9=QhdNdb zf6ZFVIG>h|q1efOZmfj6zunkH#+cUh-8el4`h;G-=uG63kPpEF_%@H8#N81D6M@f3 zJ|ag^qzLaI@L_ohcZ+>}_M!oxSJ)0a`wM^%gpw#LZI{-R1X!-7AK_BcL}m45?U4XW zNtKl4m)1NIU@7?}r2}1IxjKfbj*_1}Zrf8fBl?u6`<=ri2|dKT9h-XjhuA#D)$0^lt%? G$M_e}+}ivA diff --git a/component/widget_filter/audio_filter_componet.py b/component/widget_filter/audio_filter_componet.py index 00a2ace..746961b 100644 --- a/component/widget_filter/audio_filter_componet.py +++ b/component/widget_filter/audio_filter_componet.py @@ -10,6 +10,7 @@ from Ui_widget import Ui_Widget # from component.widget_filter.checkbox_header import SCheckBoxHeaderView +from audio_filter_model import AudioFilterModel from checkbox_header import SCheckBoxHeaderView # from component.widget_filter.checkbox_header import SCheckBoxHeaderView @@ -371,16 +372,16 @@ class AudioFilterWidget(QWidget): # 参数输入框变化 self.ui.lineEdit_11.textChanged.connect( - lambda v: self._on_param_changed('delay_data1', v) + lambda v: self._on_param_changed('delay', v) ) self.ui.lineEdit_10.textChanged.connect( - lambda v: self._on_param_changed('ENC_volume_data1', v) + lambda v: self._on_param_changed('volume', v) ) self.ui.lineEdit_13.textChanged.connect( - lambda v: self._on_param_changed('ENT_mx_right_data', v) + lambda v: self._on_param_changed('mix_right', v) ) self.ui.lineEdit_12.textChanged.connect( - lambda v: self._on_param_changed('ENT_mix_left_data', v) + lambda v: self._on_param_changed('mix_left', v) ) # 按钮点击 @@ -409,8 +410,8 @@ class AudioFilterWidget(QWidget): """设置参数数据""" if 'delay_data1' in params: self.ui.lineEdit_11.setText(str(params['delay_data1'])) - if 'ENT_volume_data1' in params: - self.ui.lineEdit_10.setText(str(params['ENT_volume_data1'])) + if 'ENC_volume_data1' in params: + self.ui.lineEdit_10.setText(str(params['ENC_volume_data1'])) if 'ENT_mx_right_data' in params: self.ui.lineEdit_13.setText(str(params['ENT_mx_right_data'])) if 'ENT_mix_left_data' in params: @@ -431,7 +432,7 @@ class AudioFilterWidget(QWidget): # 私有方法 def _on_slider_changed(self, param: str, value: float): """处理滑块值变化""" - if self.current_filter_index >= 0: + if self.current_filter_index >= 0 and hasattr(self, 'model'): # 更新表格中的值 column = {'freq': 3, 'q': 4, 'gain': 5, 'slope': 6}.get(param) if column is not None: @@ -442,16 +443,39 @@ class AudioFilterWidget(QWidget): column, item ) - # 发送信号 - self.filter_changed.emit(self.current_filter_index, param, value) + + # 更新model中的滤波器参数 + filter_params = self.model.filters[self.current_filter_index] + if param == 'freq': + filter_params.frequency = value + elif param == 'q': + filter_params.q_value = value + elif param == 'gain': + filter_params.gain = value + elif param == 'slope': + filter_params.slope = value + + self.model.update_filter(self.current_filter_index, filter_params) def _on_param_changed(self, param: str, value: str): """处理参数值变化""" - try: - float_value = float(value) - self.param_changed.emit(param, float_value) - except ValueError: - pass + if hasattr(self, 'model'): + try: + float_value = float(value) + # 更新model中的通道参数 + channel_params = self.model.channel_params + if param == 'delay': + channel_params.delay = float_value + elif param == 'volume': + channel_params.volume = float_value + elif param == 'mix_right': + channel_params.mix_right = float_value + elif param == 'mix_left': + channel_params.mix_left = float_value + + self.model.set_channel_params(channel_params) + except ValueError: + pass def _on_selection_changed(self): """处理表格选择变化""" @@ -518,13 +542,14 @@ class AudioFilterWidget(QWidget): def _on_filter_type_changed(self, row: int, filter_type: str): """处理滤波器类型变化""" - # 更新表格中的滤波器名称 - # name_item = QTableWidgetItem(filter_type) - # name_item.setTextAlignment(Qt.AlignCenter) - # self.ui.tableWidget.setItem(row, 1, name_item) - - # 发送信号通知类型变化 - self.filter_added.emit(filter_type) + if hasattr(self, 'model'): + # 更新model中的滤波器类型 + filter_params = self.model.filters[row] + filter_params.filter_type = FilterType[filter_type] + self.model.update_filter(row, filter_params) + + # 发送信号通知类型变化 + self.filter_added.emit(filter_type) def _on_delete_filter_clicked(self): """处理删除滤波器按钮点击""" @@ -591,6 +616,12 @@ class AudioFilterWidget(QWidget): def _on_checkbox_clicked(self, row: int, checked: bool): """处理单个复选框点击事件""" + if hasattr(self, 'model'): + # 更新model中的滤波器启用状态 + filter_params = self.model.filters[row] + filter_params.enabled = checked + self.model.update_filter(row, filter_params) + self.filter_enabled_changed.emit(row, checked) # 更新表头复选框状态 @@ -674,6 +705,57 @@ class AudioFilterWidget(QWidget): self.ui.verticalSlider_3.setValue(int(data.get('gain', 0))) self.ui.verticalSlider_4.setValue(int(data.get('slope', 0))) + def setModel(self, model: AudioFilterModel): + """设置数据模型并建立连接""" + self.model = model + + # 连接模型信号到视图更新方法 + self.model.dataChanged.connect(self._updateFromModel) + self.model.filterAdded.connect(self._handleFilterAdded) + self.model.filterRemoved.connect(self._handleFilterRemoved) + self.model.filterUpdated.connect(self._handleFilterUpdated) + self.model.channelParamsChanged.connect(self._handleChannelParamsChanged) + + # 设置初始数据 + self.set_channel_id(model.channel_id) + self.set_channel_name(model.channel_name) + self._updateFromModel() + + def _updateFromModel(self): + """从模型更新整个UI""" + if not hasattr(self, 'model'): + return + + # 更新UI参数 + self.set_all_params(self.model.to_widget_params()) + + def _handleFilterAdded(self, index: int): + """处理滤波器添加事件""" + self._updateFromModel() + + def _handleFilterRemoved(self, index: int): + """处理滤波器删除事件""" + self._updateFromModel() + + def _handleFilterUpdated(self, index: int): + """处理滤波器更新事件""" + if index == self.current_filter_index: + filter_params = self.model.filters[index] + self._update_sliders({ + 'freq': filter_params.frequency, + 'q': filter_params.q_value, + 'gain': filter_params.gain, + 'slope': filter_params.slope + }) + + def _handleChannelParamsChanged(self): + """处理通道参数更新事件""" + params = self.model.channel_params + self.ui.lineEdit_11.setText(str(params.delay)) + self.ui.lineEdit_10.setText(str(params.volume)) + self.ui.lineEdit_13.setText(str(params.mix_right)) + self.ui.lineEdit_12.setText(str(params.mix_left)) + if __name__ == "__main__": import sys from PySide6.QtWidgets import QApplication @@ -681,50 +763,17 @@ if __name__ == "__main__": app = QApplication(sys.argv) - # Create and show widget - # widget = AudioFilterWidget() - # widget.setWindowTitle("Audio Filter Widget") - # widget.resize(800, 600) - - # widget.set_channel_id(2) - # widget.set_channel("channel_2") - - # test_case_3 = { - # 'delay_data2': 100.0, # 最大延迟 - # 'vol_data2': -80.0, # 最小音量 - # 'mix_right_data2': 100.0, # 最大混音 - # 'mix_left_data2': 0.0, # 最小混音 - # 'filterType2_1': 2, # 低架滤波器 - # 'fc2_1': 20000.0, # 最大频率 - # 'q2_1': 10.0, # 最大Q值 - # 'gain2_1': 12.0, # 最大增益 - # 'slope2_1': 4.0, # 最大斜率 - # 'filterType2_2': 3, # 高架滤波器 - # 'fc2_2': 20.0, # 最小频率 - # 'q2_2': 0.1, # 最小Q值 - # 'gain2_2': -12.0, # 最小增益 - # 'slope2_2': 1.0 # 最小斜率 - # } - - # widget.set_all_params(test_case_3) - - # # 获取所有参数 - # params = widget.get_all_params() - # print("params:", params) - - # widget.show() - - # 创建模型实例 + # 创建模型 model = AudioFilterModel(channel_id=2, channel_name="channel_2") - + # 设置通道参数 - model.channel_params = ChannelParams( + model.set_channel_params(ChannelParams( delay=100.0, volume=-80.0, mix_right=100.0, mix_left=0.0 - ) - + )) + # 添加滤波器 model.add_filter(FilterParams( filter_type=FilterType.HIGHPASS, @@ -741,17 +790,27 @@ if __name__ == "__main__": gain=-12.0, slope=1.0 )) - - widget_params = model.to_widget_params() - print("widget_params:", widget_params) - + + # 创建视图并设置模型 widget = AudioFilterWidget() - widget.set_channel_id(model.channel_id) - widget.set_channel_name(model.channel_name) - widget.set_all_params(widget_params) - - params = widget.get_all_params() - print("params:", params) + widget.setModel(model) widget.show() - + + # 测试模型更新 + def test_update(): + # model.set_channel_params(ChannelParams( + # delay=50.0, + # volume=-40.0, + # mix_right=50.0, + # mix_left=50.0 + # )) + widget_params = model.get_all_data() + print("widget_params:", widget_params) + + # 可以添加一个按钮来触发测试更新 + from PySide6.QtWidgets import QPushButton + test_button = QPushButton("Test Update") + test_button.clicked.connect(test_update) + test_button.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/component/widget_filter/audio_filter_model.py b/component/widget_filter/audio_filter_model.py index 48eb001..d3ed556 100644 --- a/component/widget_filter/audio_filter_model.py +++ b/component/widget_filter/audio_filter_model.py @@ -1,6 +1,7 @@ from dataclasses import dataclass, asdict from typing import List, Dict, Any, Optional from enum import Enum +from PySide6.QtCore import QObject, Signal class FilterType(Enum): PEAK = 0 @@ -65,26 +66,50 @@ class ChannelParams: mix_left=data.get(f'mix_left_data{channel_id}', 0.0) ) -class AudioFilterModel: +class AudioFilterModel(QObject): + # 定义信号 + dataChanged = Signal() # 数据更新信号 + filterAdded = Signal(int) # 新增滤波器信号,参数为索引 + filterRemoved = Signal(int) # 删除滤波器信号,参数为索引 + filterUpdated = Signal(int) # 滤波器更新信号,参数为索引 + channelParamsChanged = Signal() # 通道参数更新信号 + def __init__(self, channel_id: int, channel_name: str): + super().__init__() self.channel_id = channel_id self.channel_name = channel_name self.channel_params = ChannelParams(0.0, 0.0, 0.0, 0.0) self.filters: List[FilterParams] = [] + def updateData(self): + """触发数据更新信号""" + self.dataChanged.emit() + def add_filter(self, filter_params: FilterParams): """添加新的滤波器""" self.filters.append(filter_params) + self.filterAdded.emit(len(self.filters) - 1) + self.updateData() def remove_filter(self, index: int): """移除指定索引的滤波器""" if 0 <= index < len(self.filters): self.filters.pop(index) + self.filterRemoved.emit(index) + self.updateData() def update_filter(self, index: int, filter_params: FilterParams): """更新指定索引的滤波器参数""" if 0 <= index < len(self.filters): self.filters[index] = filter_params + self.filterUpdated.emit(index) + self.updateData() + + def set_channel_params(self, params: ChannelParams): + """设置通道参数""" + self.channel_params = params + self.channelParamsChanged.emit() + self.updateData() def to_widget_params(self) -> Dict[str, Any]: """转换为AudioFilterWidget兼容的参数格式""" @@ -131,4 +156,52 @@ class AudioFilterModel: model = cls(data['channel_id'], data['channel_name']) model.channel_params = ChannelParams(**data['channel_params']) model.filters = [FilterParams(**f) for f in data['filters']] - return model \ No newline at end of file + return model + + def get_channel_params(self) -> ChannelParams: + """获取通道参数""" + return self.channel_params + + def get_filter(self, index: int) -> Optional[FilterParams]: + """获取指定索引的滤波器参数""" + if 0 <= index < len(self.filters): + return self.filters[index] + return None + + def get_all_filters(self) -> List[FilterParams]: + """获取所有滤波器参数""" + return self.filters.copy() + + def get_filter_count(self) -> int: + """获取滤波器数量""" + return len(self.filters) + + def get_channel_info(self) -> Dict[str, Any]: + """获取通道基本信息""" + return { + 'channel_id': self.channel_id, + 'channel_name': self.channel_name + } + + def get_all_data(self) -> Dict[str, Any]: + """获取所有数据的完整快照""" + return { + 'channel_id': self.channel_id, + 'channel_name': self.channel_name, + 'channel_params': asdict(self.channel_params), + 'filters': [asdict(f) for f in self.filters] + } + + def is_filter_enabled(self, index: int) -> bool: + """检查指定滤波器是否启用""" + if 0 <= index < len(self.filters): + return self.filters[index].enabled + return False + + def get_enabled_filters(self) -> List[FilterParams]: + """获取所有启用的滤波器""" + return [f for f in self.filters if f.enabled] + + def get_widget_params(self) -> Dict[str, Any]: + """获取用于widget的参数格式(与to_widget_params相同)""" + return self.to_widget_params() \ No newline at end of file