From 36db777fe93d309cb7effd7b82dcedcf16860404 Mon Sep 17 00:00:00 2001 From: Steven Dan Date: Mon, 23 Mar 2026 21:01:33 +0800 Subject: [PATCH] add app_usb_aud_phaten_golden_6ch --- .../CMakeLists.txt | 224 + .../gen_factory.bat | 1 + .../gen_update.bat | 2 + .../app_usb_aud_phaten_golden_6ch/loader.o | Bin 0 -> 36258 bytes .../src/.DS_Store | Bin 0 -> 10244 bytes .../src/core/PHATEN_GS.xn | 91 + .../src/core/synido.xn | 88 + .../src/core/xua_conf.h | 347 ++ .../src/extensions/XUD_HAL.xc | 12 + .../src/extensions/agc_interface.h | 7 + .../src/extensions/aizip_dnr.h | 64 + .../src/extensions/audio_pipeline_dsp.h | 17 + .../src/extensions/audiohw.xc | 1250 ++++++ .../src/extensions/audiostream.xc | 16 + .../src/extensions/biquad_standalone.c | 316 ++ .../src/extensions/biquad_standalone.h | 26 + .../src/extensions/br_wrapper.c | 48 + .../src/extensions/c_dsp.h | 22 + .../src/extensions/codec_ti3204.h | 77 + .../src/extensions/dnr/aizip_dnr.h | 55 + .../src/extensions/dnr/dnr_dsp.c | 52 + .../src/extensions/dnr/dnr_dsp_buf.c | 95 + .../src/extensions/dnr/dnr_dsp_buf.h | 39 + .../src/extensions/dnr/flash.h | 5 + .../src/extensions/dnr/ringbuf.h | 45 + .../src/extensions/dnr_dsp_buf.h | 69 + .../src/extensions/dsp.c | 1329 ++++++ .../src/extensions/dsp_core0.xc | 200 + .../src/extensions/dsp_user_impl.h | 18 + .../src/extensions/eq.c | 1443 +++++++ .../src/extensions/eq.h | 149 + .../extensions/eq_coefficient_calculator.c | 143 + .../extensions/eq_coefficient_calculator.h | 17 + .../src/extensions/eq_designer_new.py | 3634 +++++++++++++++++ .../src/extensions/eq_designer_new.spec | 38 + .../src/extensions/eq_flash_storage.c | 2017 +++++++++ .../src/extensions/eq_flash_storage.h | 423 ++ .../src/extensions/eq_hid_protocol.md | 1003 +++++ .../src/extensions/eq_params_44100.h | 1648 ++++++++ .../src/extensions/ex3d_0.h | 296 ++ .../src/extensions/ex3d_protocol.md | 909 +++++ .../src/extensions/filter_utils.py | 324 ++ .../src/extensions/hid_report_descriptor.h | 296 ++ .../src/extensions/hidbuttons.xc | 153 + .../src/extensions/host_os_detect.c | 102 + .../src/extensions/hostactive.xc | 21 + .../src/extensions/htr3236.h | 203 + .../src/extensions/htr3236.xc | 252 ++ .../src/extensions/keys.h | 17 + .../src/extensions/led_control.c | 265 ++ .../src/extensions/led_control.h | 161 + .../src/extensions/lfs_io.h | 13 + .../src/extensions/lfs_services.c | 233 ++ .../src/extensions/main.xc | 733 ++++ .../src/extensions/nau88c21.h | 98 + .../src/extensions/program_key.c | 652 +++ .../src/extensions/share_buffer.c | 95 + .../src/extensions/share_buffer.h | 10 + .../src/extensions/user_func.c | 233 ++ .../src/extensions/user_func.h | 52 + .../src/extensions/user_main.h | 134 + .../src/extensions/user_uart.h | 263 ++ 62 files changed, 20545 insertions(+) create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/CMakeLists.txt create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_factory.bat create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_update.bat create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/loader.o create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/.DS_Store create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/PHATEN_GS.xn create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/synido.xn create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/xua_conf.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/XUD_HAL.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/agc_interface.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/aizip_dnr.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audio_pipeline_dsp.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiohw.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiostream.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/br_wrapper.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/c_dsp.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/codec_ti3204.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/aizip_dnr.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/flash.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/ringbuf.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr_dsp_buf.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp_core0.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp_user_impl.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.py create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.spec create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_hid_protocol.md create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_params_44100.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_0.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_protocol.md create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/filter_utils.py create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hid_report_descriptor.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hidbuttons.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/host_os_detect.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hostactive.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/keys.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_io.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_services.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/main.xc create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/nau88c21.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/program_key.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.c create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_main.h create mode 100644 sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_uart.h diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/CMakeLists.txt b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/CMakeLists.txt new file mode 100644 index 0000000..8a62891 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/CMakeLists.txt @@ -0,0 +1,224 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_usb_aud_phaten_gs) + +set(APP_HW_TARGET synido.xn) +include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) +set(APP_PCA_ENABLE ON) + +###=========================================================================### +set(TARGET_HW_PHATEN_GSV2 ON) +set(CODEC_IS_AIC3204 ON) + +# Configure host os detection here... (Windows or Non-Windows) +#-----------------------------------------------------------------------------# +# Host OS detection, set HOST_OS_DETECTION to ON + +set(HOST_OS_DETECTION ON) +###=========================================================================### +# Customer +if (TARGET_HW_PHATEN_GSV2) + message("-- Building for PHATEN_GSv2 ---") + set(TARGET_BOARD PHATEN_GSV2) + set(APP_HW_TARGET synido.xn) +endif() + +# CODEC IC +if (CODEC_IS_AIC3204) + message("-- DAC: AIC3204 ---") + set(CODEC_IC CODEC_AIC3204) +endif() + +# Host OS detection +if (HOST_OS_DETECTION) + message("-- Host OS detection (Windows/Non-Windows) enabled ---") + set(EXTRA_BUILD_FLAGS ${EXTRA_BUILD_FLAGS} -DHOST_OS_DETECTION) +endif() + +set(SW_USB_AUDIO_FLAGS ${EXTRA_BUILD_FLAGS} -Os + -report + -L${CMAKE_CURRENT_LIST_DIR}/../../lib_ex3d/lib_ex3d/lib + -lquadflash + -g + #-fxscope + -DUSB_TILE=tile[0] + -DXUA_QUAD_SPI_FLASH=1 + -D${TARGET_BOARD} + -DBCD_DEVICE_J=1 + -DBCD_DEVICE_M=0 + -DBCD_DEVICE_N=0 + -D${CODEC_IC} + -DWINDOWS_OS_DESCRIPTOR_SUPPORT + -DDEBUG_PRINT_ENABLE=0) + + +LINK_DIRECTORIES(${CMAKE_CURRENT_LIST_DIR}/../../lib_dnr/lib_dnr) + +set(APP_COMPILER_FLAGS_ex3d_UAC1 ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 + -DI2S_CHANS_ADC=0 + -DAUDIO_CLASS=1 + -DMIN_FREQ=48000 + -DMAX_FREQ=48000 + -DUSE_EX3D + -DMIXER=0 + -DAIZIP_DNR=0 #-ldnr_50ms + -llib_ex3d_stereo_2k + -DNUM_USB_CHAN_OUT=2 + -DNUM_USB_CHAN_OUT_FS=2 + -DNUM_USB_CHAN_IN=0 + -DSTREAM_FORMAT_OUTPUT_1_RESOLUTION_BITS=16 + -DSTREAM_FORMAT_OUTPUT_2_RESOLUTION_BITS=16 + -DSTREAM_FORMAT_OUTPUT_3_RESOLUTION_BITS=16 + -DSTREAM_FORMAT_INPUT_1_RESOLUTION_BITS=16 + -DSTREAM_FORMAT_INPUT_2_RESOLUTION_BITS=16 + -DSTREAM_FORMAT_INPUT_3_RESOLUTION_BITS=16 + -DNUM_EX3D_CHAN_OUT=2 + -DINPUT_VOLUME_CONTROL=0 + -DUAC1_MODE=1 + -DOUTPUT_VOLUME_CONTROL=0 + -DXUA_DFU_EN=0 + -DSTEREO_2K + -DHID_CONTROLS=0) + + +#set(APP_COMPILER_FLAGS_ex3d_stereo_2k ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 +# -DI2S_CHANS_ADC=2 +# #-DAUDIO_CLASS=1 +# -DMIN_FREQ=48000 +# -DMAX_FREQ=48000 +# -DUSE_EX3D +# -DMIXER=0 +# -DAIZIP_DNR=0 #-ldnr_50ms +# -llib_ex3d_stereo_2k +# -DNUM_USB_CHAN_OUT=2 +# -DNUM_USB_CHAN_IN=2 +# -DEQ_EN=1 +# -DSTREAM_FORMAT_OUTPUT_1_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_OUTPUT_2_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_OUTPUT_3_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_INPUT_1_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_INPUT_2_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_INPUT_3_RESOLUTION_BITS=16 +# -DNUM_EX3D_CHAN_OUT=2 +# -DINPUT_VOLUME_CONTROL=1 +# -DOUTPUT_VOLUME_CONTROL=1 +# -DXUA_DFU_EN=1 +# -DSTEREO_2K +# -DHID_CONTROLS=1) +# +#set(APP_COMPILER_FLAGS_ex3d_stereo_8k ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 +# -DI2S_CHANS_ADC=0 +# -DMIN_FREQ=48000 +# -DMAX_FREQ=48000 +# -DUSE_EX3D +# -DMIXER=0 +# -DAIZIP_DNR=0 #-ldnr_50ms +# -llib_ex3d_stereo_8k +# -DNUM_USB_CHAN_OUT=2 +# -DNUM_USB_CHAN_IN=2 +# -DSTREAM_FORMAT_OUTPUT_1_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_OUTPUT_2_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_OUTPUT_3_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_INPUT_1_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_INPUT_2_RESOLUTION_BITS=16 +# -DSTREAM_FORMAT_INPUT_3_RESOLUTION_BITS=16 +# -DNUM_EX3D_CHAN_OUT=2 +# -DXUA_DFU_EN=1 +# -DINPUT_VOLUME_CONTROL=1 +# -DOUTPUT_VOLUME_CONTROL=1 +# -DSTEREO_8K +# -DHID_CONTROLS=1) + + +## IR switching mode +set(APP_COMPILER_FLAGS_ex3d_71_all ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 + -DI2S_CHANS_ADC=2 + -DMIN_FREQ=48000 + -DMAX_FREQ=48000 + -DUSE_EX3D + -DMIXER=0 + -ldnr_50ms + -llib_ex3d_all + -DEQ_EN=1 + -DEX3D_SF_NUM=3 + -DNUM_USB_CHAN_OUT=8 + -DNUM_USB_CHAN_IN=2 + -DNUM_EX3D_CHAN_OUT=2 + -DMIN_VOLUME=0xE000 + -DINPUT_VOLUME_CONTROL=1 + -DOUTPUT_VOLUME_CONTROL=1 + #-DDEBUG_MEMORY_LOG_ENABLED=1 + -DXUA_DFU_EN=1 + -DIR_SWITCHING_MODE + -DHID_CONTROLS=1) +set(APP_COMPILER_FLAGS_ex3d_71_game ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 + -DI2S_CHANS_ADC=2 + -DMIN_FREQ=48000 + -DMAX_FREQ=48000 + -DUSE_EX3D + -DMIXER=0 + -DAIZIP_DNR=0 #-ldnr_50ms + -llib_ex3d_game + -DEQ_EN=1 + -DNUM_USB_CHAN_OUT=8 + -DNUM_USB_CHAN_IN=2 + -DNUM_EX3D_CHAN_OUT=2 + -DMIN_VOLUME=0xE000 + -DINPUT_VOLUME_CONTROL=1 + -DOUTPUT_VOLUME_CONTROL=1 + #-DDEBUG_MEMORY_LOG_ENABLED=1 + -DXUA_DFU_EN=1 + -DSPATIAL_GAME + -DHID_CONTROLS=1) + +set(APP_COMPILER_FLAGS_ex3d_71_music ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 + -DI2S_CHANS_ADC=2 + -DMIN_FREQ=48000 + -DMAX_FREQ=48000 + -DUSE_EX3D + -DMIXER=0 + -DAIZIP_DNR=0 #-ldnr_50ms + -llib_ex3d_music + -DNUM_USB_CHAN_OUT=8 + -DNUM_USB_CHAN_IN=2 + -DNUM_EX3D_CHAN_OUT=2 + -DMIN_VOLUME=0xE000 + -DSPATIAL_MUSIC + -DHID_CONTROLS=1) + +set(APP_COMPILER_FLAGS_ex3d_71_movie ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 + -DI2S_CHANS_ADC=2 + -DMIN_FREQ=48000 + -DMAX_FREQ=48000 + -DUSE_EX3D + -DMIXER=0 + -DAIZIP_DNR=0 #-ldnr_50ms + -DEQ_EN=1 + -llib_ex3d_movie + -DNUM_USB_CHAN_OUT=8 + -DNUM_USB_CHAN_IN=2 + -DNUM_EX3D_CHAN_OUT=2 + -DMIN_VOLUME=0xE000 + -DXUA_DFU_EN=1 + -DSPATIAL_MOVIE + -DHID_CONTROLS=1) + +set(APP_COMPILER_FLAGS_ex3d_71_drama ${SW_USB_AUDIO_FLAGS} -DI2S_CHANS_DAC=2 + -DI2S_CHANS_ADC=2 + -DMIN_FREQ=48000 + -DMAX_FREQ=48000 + -DUSE_EX3D + -DMIXER=0 + -DAIZIP_DNR=0 #-ldnr_50ms + -llib_ex3d_drama + -DNUM_USB_CHAN_OUT=8 + -DNUM_USB_CHAN_IN=2 + -DNUM_EX3D_CHAN_OUT=2 + -DMIN_VOLUME=0xE000 + -DSPATIAL_DRAMA + -DHID_CONTROLS=1) + +set(APP_INCLUDES src src/core src/extensions ../../lib_dnr/lib_dnr) +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) + +XMOS_REGISTER_APP() diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_factory.bat b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_factory.bat new file mode 100644 index 0000000..3a2f0e7 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_factory.bat @@ -0,0 +1 @@ +xflash bin/factory/fact.xe --loader loader.o --upgrade 2 bin\ex3d_UAC1\app_usb_aud_phaten_gs_ex3d_UAC1.xe --upgrade 4 bin/ex3d_stereo_2k/app_usb_aud_phaten_gs_ex3d_stereo_2k.xe --upgrade 1 bin\ex3d_71_game\app_usb_aud_phaten_gs_ex3d_71_game.xe --upgrade 3 bin\ex3d_71_movie\app_usb_aud_phaten_gs_ex3d_71_movie.xe -o %1 diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_update.bat b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_update.bat new file mode 100644 index 0000000..1f280a7 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/gen_update.bat @@ -0,0 +1,2 @@ +xflash --factory-version 15.3 --target-file src/core/synido.xn --upgrade 2 bin\ex3d_UAC1\app_usb_aud_phaten_gs_ex3d_UAC1.xe --upgrade 4 bin/ex3d_stereo_2k/app_usb_aud_phaten_gs_ex3d_stereo_2k.xe --upgrade 1 bin\ex3d_71_game\app_usb_aud_phaten_gs_ex3d_71_game.xe --upgrade 3 bin\ex3d_71_movie\app_usb_aud_phaten_gs_ex3d_71_movie.xe -o %1 + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/loader.o b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/loader.o new file mode 100644 index 0000000000000000000000000000000000000000..b014a4bd238542cb631d4bb10c283c053f291a89 GIT binary patch literal 36258 zcmeHw37AyXweBit5CIvQra;8DWiU)u_v!BH0YsZwuoc22;?VR^Gz~r3U5!W-5hui1 zQ4w(VnyAF-Mq|_)(QDMGaX=G|i6)v{%`>@b#TzpiQ*ZrypIv9|)77U>-o4-V{NDH0 z=dQEj|lvrVzAR}hi&IZcn`VA$coZ zUJ;zJz+bbd04h?gd*zFgr7@%Ft0~gV(CEnNiMQG1Pv(!?PIaBwrrGHD+8S55CY=A` z4xXvkf8122rf?}YRR9{L^<6yGt0nqzo9wHG|B8N=(Ty{kg{18;B$e+?o1 zffvSKyU(A$9aS6?&uIehST{c4?z~}&cR%x@fjsT+S>|C6g9Q0|DWt#mnYY>vD!V_T zeq7fKhv<1#tH#;d0bZYCqJbfJgPAuX1n-ED{w9a?cMS8)et>v;YR6pW4GIz8;*jy3 z!o1uN{x&c#OnjZpJ2-^Db3)d8WypGOU|yJUyC}mRPxtRXm}dn2-JLf~jRSaQzGUZVe+Tm+$Bb`wp7uAKd13sG4e4(J z^UVH|yrHuRQH< zeXl(2ucKF<_P4oLp7yt`SDyB_qgS5xcWtjc?QeIlJnip}UU}Nzy}k0ZzlVF}X@8IR z%G3Uy>6NGbz1S;H`#aDpPy2hP7v55$0<7;3m}dkS>qwlP)-0uA8J@9&2i;&+GrSMk zpRot=KJJA#hmVow9K`;W0Q=YZ92%D4nR>x9x-Lm;!t=t_Tg8qK;q(c&;{>-7V1Im7 zd>V$mGETH01aE6d-t!@OL->I%OuZ{Y^7e-0{U#)DN_n{W&J4+WjCs)z{rpNu-fu(l z{uYuqtRh@|M}_1a6Oy+yByU|v-jsE zA$hAq^4df4E)L1t8 zImh%iZu%THhIvM^pTqR`lHi%|Cvo+k6LDUHeM(5)^pLzoA$hAq^45psZSH~hAN(VP zZ2J!8jplae@d36EUvZAbIr?)S?+)g{4&D&K>&u5O>F<8#9q;mT19)FBZ@J6M3*ar9 zNwkT-AVj@5-1p0?7XJ9F_a1o{3xAleJ^8yu_~WnWd*t0K{NbzAp8P$-Jj^GoUw*Xik@unC@uP5$ zyn+1iYy9yeT93S#;PH{MN8VEAnSG6KVLkG8GtbNe{QlBYdmk3OssP@Lg2zj~M}Hp( z-rNA*-vp1lvPXZp+*s2Nw7aKvOcA_A0le9QR~^7xEqEsd@Hzx6+2k`C}yi)^sj|<*u0len~@ALrP0l_;XfcKu@)dcW97Q8h9ye|Z=Hh|Y> zp*J3N0lXoCS0BI|BX|t~yrTp!5x^@JytM(mV+F4KN@OA|7J`udj0(gHDyvqZ4{rSfpvyQF^;N=S5 zl>xkb!MiGeH%0KS4&apu-ZcTd*@Aa%0PiHhyDosYTJUxT@YV|6^#QyN!Mh=Vw?*)F z1@Lw-56>}puDB_H_qgEg4&Z$(czXhPW2(J+_XhA53Es^Cyv>4li{Rz(A^jl6M($-E zzEAOspZCKacthBqEPsG`#|-qm<*x&#uX0=EVvBgG?saNVQ^&& z-~1-9&4^2<({8^npZdWwi~F~_k>QbvLq_!LJ7ie@!-l+n-e9h=l===?IO4d$(+ABM z#EvI0Ja*6^R&!)dpMjTD4dK7viA7fQuNwH#d4n1D9fC5)v6KG&sLwzR7mncG%di|q z0|s#!{*#na<^T_jMKXm^F@6*uG>D@fj0)irJ`Q1ee-92hJmQR4(SOyTQ zQ<3G7XgCKXO6SJNA2w>{zkd?30O$1+A<(K57cVO)DL%TcDOs>;PQjeEmiDIRL`T8w zuBPUOg5vU{%M<0b@%s3*g2na8ql@E57Z(*5Q*&EwL!u+Lsh$egwY4M)8@p;-8w=Yz z+SVuPlbwa_>uQsU)|$1o^~u7Hw&q0VSxw3Mb%nZA&87`CwOtKOZ8gPCSxrklwbwK! z8ak=Hts|-RVrpn#nds;Ox%TXu&P07%EBn)^rK!2O$#1IfXfvi|-TXAQHYKTkU7~)&oZ8lgrUq`x!j{^` zgo&tw{dY9jdDM|W_{$Q_-2Oy^;?r70wUKH$bQO!9r2HAfWCh}x>oz{goqlQ&Kcev@ zR;FSyru3+-=h0_X&FN7&lpeMJERBgV4e;YvKRaPv-hR&ctSY(Vvu~}76hWtRUcXyi z&l-_BEtCK1XVsbf1DX8O*9})?s*)$CT8(~AzjeBNj(gpcIxkbUziaEGuS>j`s#YP6 zaSx*GR*qr+Svf@}{wl$vRVafF(Q7_OFYo=Z7>Q)IjYwpmq8*eYOoQA#03A5IUuNr2 zO1~VDnd{3uD5oE)h@4@z6zVfL(m%2U#y-3bS=2`95(dC#QscO4vLa5e7LSai=LsxU6Mr<3M9ra=t!K%W>!&2CiXlSY} z>|9$jw`T6rg)8S3Zfa@kENpB_uIs8RY)m9;l5K6xorU_-iN}g#P9ZkD!d1)X6uLNB z+riAjrq=rAu7*TmvZ;kPwsoorJi+>@Id;xKSLtxC+@eh8Hj~-uWxCq+Iekp#8lKeJ z*=_E1Yd89*hHN|~yHWmPC?XP<^i{SjZbxR7@V`)@pp9(z=0~ zkTHB6toTLj_->K$SFM@9_4WF!nyn{w<1S{4I-v2@<1S@{dlJO6u3&@&`FdGbazv|C zTrx6zKZ^wM;>9sm9c4pU&u0!2#Pg8dx{WzV5RaSPdLeTzQcTI+&K7(}2=P4Ngp6<8 zjHFrm@-cAeU~|7RIsfxlJQYOxX}@?Lh#c(I5&6B=@rcuJnrDxm!Y3jYl#QByG6%gy z+mRPNN9e!My7Ds#t)kKkuvNEIi;IUc&p!MYex%O5OjIE_$G1VqR-oOYpO1{yyYcf+}y^}gs-SE8ru zm9_|Y|59}IoH&~buxCEBia<>ySHcL=>x@V96kr_bnlTE|+7Zq+)z z{ED8h^=jxi8M9PCzd-BLpkF9B7inGjyjbhX=OtQKWw&cxmAzE!@EhHsbye?WT37X6 zE;v_cU6s93>&pLCT37X6t##$+8m+5(uhqKpd7ai(y*st8{9G^iH)vheb)(jm&s|zq zb={U1%X?+40h~BRCxzO*>dNcGpwcY^z zF0F5c{x4cre(u&fn<@Hjt*h9+qjgp9Jz7`ox>xHew)?cM+O<#X+u`TCTE86n{aRQ4 z-_?2!12eZV4@cl9H5SZgqXS24J$|%K>netN;ZyuG{Ag0xAJF<>_<2y+9};#v>bVmA zS7HC2)>U1P2+n(2SMiJOHQM0WqY1(968fV;|CPt@EB&nX?9oQS-zfC&3;oxg9{E%^ zgum^j2%ctH(DukZN>yL2MDEo(9zi0{XnhRy`?OvFeV^7RK>w50r$PU&)=Qw@uk~{1 zf7Ci6i9Dcn+!7*x(K?zJarZGC@eN1uAJiPBKcsb4_6XfBRo3kvRo3Mw{==G&`4gF@ z`9q;UrtSF7$moYd|DLvwhdx&GCqqA4>qXEfYCR6!ef`8S(5GrU&U6tx=d&!}Wb_lE zAF4Sx0hzk+4MYTAZm`rrFVuPy^dG=V)WXT=UC@g)XEXE{_=5#srA7Wtsp_vYBO^5D zQrO379p`{Zk=A!YceJj4C3;Dhy&3jN+I~0m60KusBadqiy4dL7hpxUMBSQ6&pJ>k0 z(0{1)=b`^Y>&T9jYM*aHe?r^e7WxO!-Lk)f{-x$%`bM78I;LG@nD&G0NL=fE_!46@pVKIMW4ZhSvL{ECOUv z{%2}k`9D^0j??-e@T-JcHe$gj1I&&c~)SM8FV-wMuht*iKP&}YG(F!Xh$s*gxd986MIB1x?%KUjP$ z%FkO`SN(Rb*3~|PMahDFX6QRgRUeT;tt&s%h5a~TKS9`M3;SGQUn1;h3i~o)$7bV7 zB#eE%VRPTg_FgRP*i=|l{5!Rd&&Z8hSN1zRd-P;)OrxtjJ@S#}s6EpW`YfR@6?%iv zYlMEX(9aM$Hdhwqf3?=tJ;LQE-Sw$-*Qe6mvPzd_mkK{_y^14!N{%cm{mXi#A6ait zKBXVYmvu>>vMyOx+9hAcFJtJbu2rH>P7^wAXe_FK)+kl$38BlFWxX;sS+9&!#wOd< zDE#9_%A#V}DD<<1{?FR+K*pkcUM6(hNL`8iK+N-h(|Qi)MIO^S_KgS*6fA0Q+%Ndg z^uVuDbj|;f;6L93|C@sUqTs*W10RPA78TE{g8zCCeD|vc#eY-qf7Szkm+=2{kAr(j zA5QprYt^z6wXdMBV25q#yp^+Nb0y#$9OVyJjBQjF+Z17&$9&xDaF001En6jY*QCCo zJD34@xX(bUI2j!n@?#DR`#X-EGu?V%8_swbr>k7s!Q2SU5%BUx@dpcRxF3Lbx@(JY zS=jP88G-E(1|yAuudt!7{WjcpEE_if{zMm0gl&k$t&%mOr#T4lMk}O0U_(0&_1RE% zjL(KXI84}Paodh&TqAku@D=sKwm{fWFKovP8|q!ecp)d`K~OJj5Mx8V=)Vohsy|RKY|X-kdSPo3 zHq;AStFWQocE)X-kOx7%utAIs^+!srU>h3$M};4A8dZJV&6Uf3=WHq?6&;|n<< z4}yAOgBTm?#XPuDS@j3%h3zU~L%pzFEo`V4wrhk9^V@qVW8f?5h3!^hL%p{%zKs*|AgC8Mh_Qk9I^zSJ-arNmY;W@Qr<{HU z0}E_#@%3#^KZk(@ws-jY3r@d;fdw`kY2V}YD;QW{`!!!*mN9M3npYrt+PX7r53v7SnYl_onFtEV(C%*m{r_W(v zf$cAR{WGU8U|@mmOTPY%(_dj=f$blB{fg7yVPJtxJsR>gtxd{?$DY2N@T>*_506g$ zIYBozJboR_X@Icd5f0CCgM=AZ!y^pTKFnu;Fp~8=Q_3Hhf)x=gP^#hDX3^PA3W*9t9V3 zS|V&XT0G9_31Pz#+-Hw6zziJx$KMeuNRxP&=ff`=o-0ZwlS9*zvxGiQh3;pp%dr?&+UM}<3?v&-de z<-=2qb>0G$j7pD6!**;I+u|&?&Mda=S!_3Cu}u{=RtfbUlf`yo7TdpMvE7};_9VC0 zw2iY>9ZzSmJ(tDyQWo25S!_SeVtdDDo6UzI#POmyG{DA9?q(apwpV;S?4z&yY{S_0 zj?XrnZSVSQ*!w^7*+#JKbDs_OVt*Yt7knvr3@v$Cpd3o2%|5Qpsq{f2AE(au8JqDK z#@OtkhL1he2-;`%No`A|u}=wQIuSM2dLRQ(1zi3a^@|RAmsAtuS@vL_75V4Py_ws0zauZ2g@qdxra7G6%zyIA+fa(d0mA3<+gIFCN?;ZJ?|OU7RN^QaFy zHtQ9qeLS;y{gFq*P_Zj$e~hv0kA=Scgb!aJFrM6YSU8ow(!wKYw-0~Y!YgQ>i*bqY z-&Z_h<&UJtEgYp6eE6q6{61r^Kce(I#$JC!=~KpDe?;jE#-mM#@KfhMe0U(A!6%6P zDC21Y=lk#!#uXyJjPYE7XEUxAc##jUWV}k`*D-DoxQ%hMz?*#dBF0IPe}xb4X1rD8 zf1B}kfgkYUCmCNZ@_*#R2N>@Z`R_0`g8B7(#&UlB)t5h@zhPrsQt3kl#uNXM7EYy) zvhXM>_TefYo@e3Fw9JQVeYllzP9~($w2|>(fw%bZWsGqj*D{)}W9-eR(X_{hzvIIX zG9GJ+bIHeict2w!=%0@m%l;fBZurPA^5K&O#y2B17EYxbEPN=n`|uVYzQDp`=o%ls z)ra>n_WE-SeUGu%pJV7r!AJb>GM4T8(w9HRTij~C%n%sg=*+QjDt)|#52NKiT<61$ z79LAoK74@>U(MKS-&oqk*lXWdx=rxWzWt14``-2Cf9b=A;Y+=Y98Tjc%;zZ!A5Nt{ zY@ScFiIAY73u2r&#!KT5I6})Wx_g<8T07$hb=2>lj}k@Ewf3m;`x)OL@T-h(6Zk#GcM1HTjPDcp?~ET4cNuKc;aL>7@En?F;f1ts_kXV3&6e=6f>Ccqg~!g!>>Gkkpf4&XggmC{ni-hNU_ z4L<$`AMRjWn(-f}ZC3d>?ey`zdo>EJrae~vYT9SvRQe$c->AOJ$W+I_4(zw`htMx9 zoJ#+iu{ZyQ&|evQ?aQYDhuGsgim@lD^jHh$(^SS&GlYDaZsn)a$5}X^PV(_jxAIfz zIt%C1nHDaftrkwDFS2kx?X++K-D%-e`a2fRr|(<1fS$8(D*cj$^XVN67tsH*a4P+Y zh4bkv3y&lF&ty~S;rOy6Bl%Qd;c--G;Z(ZR!ud4E!sBSEg;VL1Eu2qlEj*68ESySj zws1aOYTRbIhFTbVTlg3{$KsEr%Pjs_+HLX2(mfVGmA>D?CG?b4emuQt<)_j=XZ(Doed8&` z*tVirD z7GC51u&&lbE%n0>4H>DUfflZ(p%!kSJPRl2Pz$f6BP`rV6D_=srdqg(@ahat)>FBK zH_%KAH`5#ow-DaX;Ylkkws0G*uy8w_YT+}f*1{dsXyHz3v2c<)E!;(CTX-X#XW_Hx zA`5S#%Pf30U1QV!dvMH3!hI~p?bhL$cQi+AHr%DUoK*w2l7gbw0m0rr&yT$LK(;0jB z<6U&7FaIoG`E#xOn`paL{wCVv>(T^=0rPnMxn%=VT7<$jbd{T@h6PuD0YfU7ZTGzHwtRYd?)mS6-+Pb=q z#72|T+|-&ddV5!0Yi&!SQ{d#;?H=w-c9^W@wtAy>@H>pasc(;UwDD_yYGd`C$x_a3 zkJWW{##q~wtZlBXOEf2I>zcW;WJhhg8&P9(TU~8)N20T>tE1klXl)0FA^sI_wTwZ}wrY%C4bP?~Zo5}lPgOOu zsV+tpfMQ17Kzo)7*OKwZ;CLR}_rQP1@65}lO2Am%9>etn_Ch&4C6#(XD87CDo zn`OXphcn7KadRDRZmhb(FjeD_Z5mewN2YPe;Krf7d_jltCEop&FIF47O;0pLw=rv$ z+s3SUb{o;YN=~MYGVTfh2W{g<7OO@&FnL3WhM87Y%2q<>cAfwv>Xu3m= zJ33?b?BW_}!;~>x#A z4jM3fc8S&bjyrIU@nFamL7SLtd(WG(H-m#YZa)Q+e7%R(t5%xcvpOdZq}uEC9=asc zdvUAx0JztR-R-E@$-eE&R%hL`Gc%y4T(_k?W!r7!mlSd%FfOKzRujrGoDPP|Ya>jV zHmbF4CPAic?hGj|&Gw=!Ye&jRYEP2b7nj-vWWFyl zHP2(>FFw`3BA@5K6~CH@G9U4Y)2w%|Z{|CFjXGNLk)pOEiSZD} zhfY66MKWJ+ff!qTl0`)_AETf#A5Hwu^y=^m>)8a%WPeTQqKqehwHQuWE&5Bg*PVr( z3DrRC9B_v#0%S9412h{89I_epu(`y|E

}j8?*IuAs3Q?cv9OlkgaTxej&)XO34) zL__(4x;cM29uov#W64LbiB>-HF)uSrYS4|(o#K2 z`UuDnO)u8NYHV?vqK;j>Jz)qk6g@&22nWS_d^0v*3FNBb5MK23$&^4psF4#CJ@gq0 z2C&$+mD%BzsTP6fc~Pf90THemY+aEZW+QYu{)y77aGD))5!BuZiicr za7*mU%WbFSDqOpF%Y7wOxORNyKB&UQhls@vw_Js5+bZmE)jh1l?%fJI+zN#2n`RYu zxD^OjO+7D`3On2iL|W?e>X_uHQ@^8*M2r3sFS3ljyjII z*E#xpt!_q+y5BhJmg1<@F=mn@2JV{s5$PasqLs4 z?Wl?DsQK%tY3r!j>ZnP|H$=2Z%_&Ds5vNqOw-oKIZ%P#Dvy~#D@yeuUr&4RzCVBET zCt4em>p)Ud%29J_Z3CCqRHZ5^-}enu^}73@geaKgczmbE&sSrjCZ(fhCG(3tesMRx z8Zk9%9W`y4U*hpg{CqX59W|{THLsap>hVjv@m0^MdGDx+&-}Q@k9Xs%epL&@Q7gky zOT$rX!%>UFQLDq@k=J_fdxuB63MQP)&l(!;~B0|XBC{W1>${$ zJDunA^vXboO=~Aj%6#2gFmqHbaMYfEEgNWYdKR*g{(*tz43AyH)P3Y)iN0vYp)->A>tv*_quf zl$yrG#2XTyG=$1%-9hCWhoZ3 zgzpM{K^gL8#Fvv?q6HK1swjC?@Q)b4t7Cb{_lx*)l1pBlfPeS^zi04ID8PEh_Cw5_ zK!nR64-g0tn27*AyO)v_aY%+3#pn0rsAaldbMu!Vl$4g0S755F*cTgd@-a{IvTmoA z*+aEH$FY)ycP+WwH1Y|hrpvKC&9sdSt6=H|y6hb=ZQaRtI$2wHS=|^nNSG4J!twFu z#!y?!L}O^YrL~>Dx3*7ANMdbx&4yixLsr^$9_14Ne-j{cr%mXw;@MaST4E_DJ<5H0 zKPa}PooLj*sSsoN6GLTZ&v#6<>wjq<9Vu>mn09d;#at|7TaJAI%jh`g5QB^n4|AKi z#6+Y~{wI*0b!Jmgmrs6sGXyL**Tq+$Bv#%KRLXs%O+Q z9oyY)WL%U9rP@XEZAm$HZ^oeZfs|uq)3%#X=A|?%m9;d_=&-DqdDOtNYB!Z}EUVXX zP1?+0+%v{JYMWt-54ldp>tcCcgDOuOpWniOb&WK{uZVgVXIDvG!YSGeVE1Fu{ zHb%O)?0Dc}wK8x10=ZTmLYCW}`JicJ1~oTj*t%&S=+B$FH!_ejT{CGJeYTc1qE-GiR9qfI%+Mw>C zmadF}x$ak3sopL3xj1=I(YWJkxmoTzgyAVEE-zUtuT%RiEooSiqVs;*<@M@7j^i&W z+j!w7xmJy1l%;WsuEfha)Gl>D?RLB7dOf3tO^0-W+wMc#kUOGfjGjDh5B`X@Vkuiv zET@&ab<=a05z*M$psItkxei@tb68ThxLS>={i5=jm{1hc&Y8F+;j;3bNeoBEn%UBX zBdholg$paifDT8vnMnAfLnW;wO16_>+yKYPaq=uVMcyXo$j9Uh@(uZsTqVB&Ksi*v z9H@jESOUwS5!OK)w8Op72|dsYyC4R8VFXM#2ri7l5g3PK@CZB(Prz|_3Z8)%;6-=| zPQh#NCcFh_;4Hik7vK~46h4Q`a0R}D@8M_o9sYo8LYc5gs1p_o4ML;PB&-(Ngmz)Q zutC@<^ayc56AlWFFvce&6gI+rg$erQM^Mflo^H;+q7_ch9(MKjZ{D(X+rKC5E>*EW ztEpYMD71XlntR$ebzCPTXK;bE#i79XKP74y{~14~arYvNNcqmCg%G?ElsriLF>s7A zk;bo5;d_Udi5NIanXj%`iRw0`oU8Rst3?b_MPRC2-?~=9P*h5MwW$qN;EKFqql7`J zl=|wO5mX6N>g%K3*vth*yj6_9L@tvb$S>p?GQJ8HKpor#&B*gk*bF-v(__#N`ym0t zkOUplV1omPk@Lr30v>`%co-S~By#=)JPjw|Id~p<|1!J+ufpr_2D1Jgco*J-58)#? z2j_kMe~s+_39e3K^p2^FhF@>S=;BZLm7@m}<@{4VEBn*VjnDeu`A+%7SM}22%-ZI( z&;8V42U>tYfIxsifIxu2ZHGV!A5n + + + tileref tile[2] + tileref usb_tile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/synido.xn b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/synido.xn new file mode 100644 index 0000000..a13e29d --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/synido.xn @@ -0,0 +1,88 @@ + + + Board + xcore.ai MC Audio Board + + + tileref tile[2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/xua_conf.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/xua_conf.h new file mode 100644 index 0000000..7d6dcda --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/core/xua_conf.h @@ -0,0 +1,347 @@ +/** + * @file xua_conf.h + * @brief Defines relating to device configuration and customisation. + * For xCORE.ai Audio MC Board + * @author Ross Owen, XMOS Limited + */ +#ifndef _XUA_CONF_H_ +#define _XUA_CONF_H_ + +#include "../../../shared/version.h" + +/* + * Device configuration option defines to override default defines found in lib_xua/api/xua_conf_default.h + * + * Build can be customised but changing and adding defines here + * + * Note, we check if they are already defined in Makefile + */ + +/*** Defines relating to basic functionality ***/ +/* Enable/Disable MIDI - Default is MIDI off */ +#ifndef MIDI +#define MIDI (0) +#endif + +/* Enable/Disable S/PDIF output - Default is S/PDIF off */ +#ifndef XUA_SPDIF_TX_EN +#define XUA_SPDIF_TX_EN (0) +#endif + +/* Enable/Disable S/PDIF input - Default is S/PDIF off */ +#ifndef XUA_SPDIF_RX_EN +#define XUA_SPDIF_RX_EN (0) +#endif + +/* Enable/Disable ADAT output - Default is ADAT off */ +#ifndef XUA_ADAT_TX_EN +#define XUA_ADAT_TX_EN (0) +#endif + +/* Enable/Disable ADAT input - Default is ADAT off */ +#ifndef XUA_ADAT_RX_EN +#define XUA_ADAT_RX_EN (0) +#endif + +/* Enable/Disable Mixing core(s) - Default is on */ +#ifndef MIXER +#define MIXER (0) +#endif + +#ifndef _XUA_BMAX_POWER +#define _XUA_BMAX_POWER (50) +#endif + +/* Set the number of mixes to perform - Default is 0 i.e mixing disabled */ +#ifndef MAX_MIX_COUNT +#define MAX_MIX_COUNT (0) +#endif + +/* Audio Class version - Default is 2.0 */ +#ifndef AUDIO_CLASS +#define AUDIO_CLASS (2) +#endif + +/*** Defines relating to channel counts ***/ +/* Number of I2S channels to DACs*/ +#ifndef I2S_CHANS_DAC +#define I2S_CHANS_DAC (8) +#endif + +/* Number of I2S channels from ADCs */ +#ifndef I2S_CHANS_ADC +#define I2S_CHANS_ADC (8) +#endif + +/* Number of USB streaming channels - by default calculate by counting audio interfaces */ +#ifndef NUM_USB_CHAN_IN +#define NUM_USB_CHAN_IN (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN + 8*XUA_ADAT_RX_EN) /* Device to Host */ +#endif + +#ifndef NUM_USB_CHAN_OUT +#define NUM_USB_CHAN_OUT (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN + 8*XUA_ADAT_TX_EN) /* Host to Device */ +#endif + +/*** Defines relating to channel arrangement/indices ***/ +/* Channel index of S/PDIF Tx channels: separate channels after analogue channels (if they fit) */ +#ifndef SPDIF_TX_INDEX + #if (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN) <= NUM_USB_CHAN_OUT + #define SPDIF_TX_INDEX (I2S_CHANS_DAC) + #else + #define SPDIF_TX_INDEX (0) + #endif +#endif + +/* Channel index of S/PDIF Rx channels: separate channels after analogue channels */ +#ifndef SPDIF_RX_INDEX +#define SPDIF_RX_INDEX (I2S_CHANS_ADC) +#endif + +/* Channel index of ADAT Tx channels: separate channels after S/PDIF channels (if they fit) */ +#ifndef ADAT_TX_INDEX + #define ADAT_TX_INDEX (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN) +#endif + +/* Channel index of ADAT Rx channels: separate channels after S/PDIF channels */ +#ifndef ADAT_RX_INDEX +#define ADAT_RX_INDEX (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN) +#endif + +/*** Defines relating to audio frequencies ***/ +/* Master clock defines (in Hz) */ +#ifndef MCLK_441 +#define MCLK_441 (256*44100) /* 44.1, 88.2 etc */ +#endif + +#ifndef MCLK_48 +#define MCLK_48 (256*48000) /* 48, 96 etc */ +#endif + +/* Minumum sample frequency device runs at */ +#ifndef MIN_FREQ +#define MIN_FREQ (48000) //(44100) +#endif + +/* Maximum sample frequency device runs at */ +#ifndef MAX_FREQ +#define MAX_FREQ (48000) +#endif + +/*** Defines relating to feature placement regarding tiles ***/ +#define XUD_TILE (0) +#define PLL_REF_TILE (0) + +#define AUDIO_IO_TILE (1) +#define MIDI_TILE (1) + +/*** Defines relating to USB descriptor strings and ID's ***/ +#define VENDOR_ID (0x20B1) /* XMOS Vendor ID*/ +#ifndef PID_AUDIO_2 +#if defined (STEREO_2K) +#define PID_AUDIO_2 (0x0017) +#elif defined (SPATIAL_MOVIE) +#define PID_AUDIO_2 (0x0018) +#else +#define PID_AUDIO_2 (0x0016) +#endif +#endif +#ifndef PID_AUDIO_1 +#define PID_AUDIO_1 (0x0017) +#endif + + +#define PRODUCT_STR_A2 "XMOS True AI7.1" +#define PRODUCT_STR_A1 "XMOS True AI7.1" + +/* Board power source - Default is bus-powered */ +#ifndef XUA_POWERMODE +#define XUA_POWERMODE XUA_POWERMODE_BUS +#endif + +/* Enable/Disable example HID code - Default is off */ +#ifndef HID_CONTROLS +#define HID_CONTROLS (0) +#endif + +#define EXCLUDE_USB_AUDIO_MAIN (1) +/* + * New spispec for QF60 board + * */ + +#define FL_QUADDEVICE_XT25F16F \ +{ \ + 0, /* XT25F16F - Just specify 0 as flash_id */ \ + 256, /* page size */ \ + 8192, /* num pages */ \ + 3, /* address size */ \ + 4, /* log2 clock divider */ \ + 0x9F, /* QSPI_RDID */ \ + 0, /* id dummy bytes */ \ + 3, /* id size in bytes */ \ + 0x0B4015, /* device id */ \ + 0x20, /* QSPI_SE */ \ + 4096, /* Sector erase is always 4KB */ \ + 0x06, /* QSPI_WREN */ \ + 0x04, /* QSPI_WRDI */ \ + PROT_TYPE_SR, /* Protection via SR */ \ + {{0x0,0x00},{0,0}}, /* QSPI_SP, QSPI_SU */ \ + 0x02, /* QSPI_PP */ \ + 0xEB, /* QSPI_READ_FAST */ \ + 1, /* 1 read dummy byte */ \ + SECTOR_LAYOUT_REGULAR, /* mad sectors */ \ + {4096,{0,{0}}}, /* regular sector sizes */ \ + 0x05, /* QSPI_RDSR */ \ + 0x01, /* QSPI_WRSR */ \ + 0x01, /* QSPI_WIP_BIT_MASK */ \ +} + +#define FL_QUADDEVICE_WINBOND_W25Q16JW \ +{ \ + 0, /* W25Q16JW - Just specify 0 as flash_id */ \ + 256, /* page size */ \ + 8192, /* num pages */ \ + 3, /* address size */ \ + 4, /* log2 clock divider */ \ + 0x9F, /* QSPI_RDID */ \ + 0, /* id dummy bytes */ \ + 3, /* id size in bytes */ \ + 0xEF4015, /* device id */ \ + 0x20, /* QSPI_SE */ \ + 4096, /* Sector erase is always 4KB */ \ + 0x06, /* QSPI_WREN */ \ + 0x04, /* QSPI_WRDI */ \ + PROT_TYPE_SR, /* Protection via SR */ \ + {{0x3C,0x00},{0,0}}, /* QSPI_SP, QSPI_SU */ \ + 0x02, /* QSPI_PP */ \ + 0xEB, /* QSPI_READ_FAST */ \ + 1, /* 1 read dummy byte */ \ + SECTOR_LAYOUT_REGULAR, /* mad sectors */ \ + {4096,{0,{0}}}, /* regular sector sizes */ \ + 0x05, /* QSPI_RDSR */ \ + 0x01, /* QSPI_WRSR */ \ + 0x01, /* QSPI_WIP_BIT_MASK */ \ +} + +#define FL_QUADDEVICE_UC25HQ16B \ +{ \ + 0, /* W25Q16JW - Just specify 0 as flash_id */ \ + 256, /* page size */ \ + 8192, /* num pages */ \ + 3, /* address size */ \ + 4, /* log2 clock divider */ \ + 0x9F, /* QSPI_RDID */ \ + 0, /* id dummy bytes */ \ + 3, /* id size in bytes */ \ + 0xB36015, /* device id */ \ + 0x20, /* QSPI_SE */ \ + 4096, /* Sector erase is always 4KB */ \ + 0x06, /* QSPI_WREN */ \ + 0x04, /* QSPI_WRDI */ \ + PROT_TYPE_SR, /* Protection via SR */ \ + {{0x3C,0x00},{0,0}}, /* QSPI_SP, QSPI_SU */ \ + 0x02, /* QSPI_PP */ \ + 0xEB, /* QSPI_READ_FAST */ \ + 1, /* 1 read dummy byte */ \ + SECTOR_LAYOUT_REGULAR, /* mad sectors */ \ + {4096,{0,{0}}}, /* regular sector sizes */ \ + 0x05, /* QSPI_RDSR */ \ + 0x01, /* QSPI_WRSR */ \ + 0x01, /* QSPI_WIP_BIT_MASK */ \ +} + +/* + * New spispec for QF60 3V3 board + * */ + +#define FL_QUADDEVICE_WINBOND_W25Q32JVxxxM \ +{ \ + 0, /* W25Q32JV - Just specify 0 as flash_id */ \ + 256, /* page size */ \ + 16384, /* num pages */ \ + 3, /* address size */ \ + 4, /* log2 clock divider */ \ + 0x9F, /* QSPI_RDID */ \ + 0, /* id dummy bytes */ \ + 3, /* id size in bytes */ \ + 0xEF4016, /* device id */ \ + 0x20, /* QSPI_SE */ \ + 4096, /* Sector erase is always 4KB */ \ + 0x06, /* QSPI_WREN */ \ + 0x04, /* QSPI_WRDI */ \ + PROT_TYPE_SR, /* Protection via SR */ \ + {{0x3C,0x00},{0,0}}, /* QSPI_SP, QSPI_SU */ \ + 0x02, /* QSPI_PP */ \ + 0xEB, /* QSPI_READ_FAST */ \ + 1, /* 1 read dummy byte */ \ + SECTOR_LAYOUT_REGULAR, /* mad sectors */ \ + {4096,{0,{0}}}, /* regular sector sizes */ \ + 0x05, /* QSPI_RDSR */ \ + 0x01, /* QSPI_WRSR */ \ + 0x01, /* QSPI_WIP_BIT_MASK */ \ +} + +/* + * New spispec for xcore-AI explorer board + * */ +#define FL_QUADDEVICE_MX25R3235 \ +{ \ + 0, /* MX25R3235 - Just specify 0 as flash_id */ \ + 256, /* page size */ \ + 16384, /* num pages */ \ + 3, /* address size */ \ + 3, /* log2 clock divider */ \ + 0x9F, /* QSPI_RDID */ \ + 0, /* id dummy bytes */ \ + 3, /* id size in bytes */ \ + 0xC22816, /* device id */ \ + 0x20, /* QSPI_SE */ \ + 4096, /* Sector erase is always 4KB */ \ + 0x06, /* QSPI_WREN */ \ + 0x04, /* QSPI_WRDI */ \ + PROT_TYPE_SR, /* Protection via SR */ \ + {{0x3C,0x00},{0,0}}, /* QSPI_SP, QSPI_SU */ \ + 0x02, /* QSPI_PP */ \ + 0xEB, /* QSPI_READ_FAST */ \ + 1, /* 1 read dummy byte */ \ + SECTOR_LAYOUT_REGULAR, /* mad sectors */ \ + {4096,{0,{0}}}, /* regular sector sizes */ \ + 0x05, /* QSPI_RDSR */ \ + 0x01, /* QSPI_WRSR */ \ + 0x01, /* QSPI_WIP_BIT_MASK */ \ +} + +#define FL_QUADDEVICE_MX25L160E \ +{ \ + 0, /* MX25R3235 - Just specify 0 as flash_id */ \ + 256, /* page size */ \ + 8192, /* num pages */ \ + 3, /* address size */ \ + 3, /* log2 clock divider */ \ + 0x9F, /* QSPI_RDID */ \ + 0, /* id dummy bytes */ \ + 3, /* id size in bytes */ \ + 0xC22017, /* device id */ \ + 0x20, /* QSPI_SE */ \ + 4096, /* Sector erase is always 4KB */ \ + 0x06, /* QSPI_WREN */ \ + 0x04, /* QSPI_WRDI */ \ + PROT_TYPE_SR, /* Protection via SR */ \ + {{0x3C,0x00},{0,0}}, /* QSPI_SP, QSPI_SU */ \ + 0x02, /* QSPI_PP */ \ + 0xEB, /* QSPI_READ_FAST */ \ + 1, /* 1 read dummy byte */ \ + SECTOR_LAYOUT_REGULAR, /* mad sectors */ \ + {4096,{0,{0}}}, /* regular sector sizes */ \ + 0x05, /* QSPI_RDSR */ \ + 0x01, /* QSPI_WRSR */ \ + 0x01, /* QSPI_WIP_BIT_MASK */ \ +} + + +// DFU_FLASH_DEVICE is a comma-separated list of flash spec structures +// This define is used in lib_xua/lib_xua/src/dfu/flashlib_user.c +#define DFU_FLASH_DEVICE FL_QUADDEVICE_XT25F16F, FL_QUADDEVICE_WINBOND_W25Q16JW, FL_QUADDEVICE_UC25HQ16B, FL_QUADDEVICE_WINBOND_W25Q32JVxxxM, FL_QUADDEVICE_MX25R3235, FL_QUADDEVICE_MX25L160E +//: +#include "user_main.h" + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/XUD_HAL.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/XUD_HAL.xc new file mode 100644 index 0000000..1bafd7b --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/XUD_HAL.xc @@ -0,0 +1,12 @@ +#include +#include "xua.h" + +on tile[XUD_TILE]: in port p_vbus = XS1_PORT_4C; + +unsigned int XUD_HAL_GetVBusState(void) +{ + unsigned vBus; + p_vbus :> vBus; + return !vBus; +} + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/agc_interface.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/agc_interface.h new file mode 100644 index 0000000..4b2c209 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/agc_interface.h @@ -0,0 +1,7 @@ +// Copyright 2011-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + + +void agc_init_interface(void); + + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/aizip_dnr.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/aizip_dnr.h new file mode 100644 index 0000000..714948a --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/aizip_dnr.h @@ -0,0 +1,64 @@ +#ifndef AZP_H_ +#define AZP_H_ +#include "stdint.h" +typedef enum +{ + errAZP_NoError = 0, + errAZP_NotReady, + errAZP_InvalidParam, + errAZP_InvalidLicense, + errAZP_BufferOverflow, + errAZP_BufferTooSmall, +}AZP_STATUS; + +typedef struct +{ + int8_t *buffer1; + int8_t *buffer2; + int32_t buffer1_size; + int32_t buffer2_size; + int32_t sample_rate; +}azp_dnr_pram_t; + + +/// @brief DNR initialization +/// @param param Configuration parameters +/// @return License status +/// @brief DNR initialization +/// @param param Configuration parameters +/// @return License status +AZP_STATUS AI_DNR_init(azp_dnr_pram_t *pram); + +/// @brief DNR processing +/// @param audio Q31 input and output +/// @return License status +AZP_STATUS AI_DNR_Processing(int32_t *audio); +/// @brief Get version number +/// @return If not licensed, returns NULL +char * AI_DNR_Version(); +/// @brief 初始化 +/// @param buffer 公共区buffer +/// @param buf_size 公共区buffer size +/// @param buffer2 私有区buffer +/// @param buffer2_size 私有区buffer size +/// @return +//AZP_STATUS azp_dnr_init(int8_t *buffer, int32_t buf_size,int8_t *buffer2,int32_t buffer2_size); +/// @brief DNR 处理 +/// @param audio 32bit Q31 单声道 +/// @return +//AZP_STATUS Aizip_DNR_Processing(int32_t *audio); +/// @brief 获取工作buffer大小 +/// @param buf_size +/// @param buf_size2 +void getBufferSize(int32_t *buf_size, int32_t *buf_size2);/// @brief 设置是否开启EQ ,默认开启 +/// @param status +//void setEQ_Status(int8_t status); +/// @brief 设置是否开启AGC ,默认开启 +/// @param status +//void setAGC_Status(int8_t status); +/// @brief Set the noise reduction depth +/// @param dB Range (-200 to 0 dB) +/// -200: Maximum noise reduction +/// 0: No noise reduction +void setNoisy_mix_factor(float dB); +#endif // AZP_H_ \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audio_pipeline_dsp.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audio_pipeline_dsp.h new file mode 100644 index 0000000..b9bb986 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audio_pipeline_dsp.h @@ -0,0 +1,17 @@ +// Copyright (c) 2022 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 + +#ifndef AUDIO_PIPELINE_DSP_H_ +#define AUDIO_PIPELINE_DSP_H_ + +#include + +//#include "agc_api.h" + + +//typedef struct agc_stage_ctx { +// agc_meta_data_t md; +// agc_state_t state; +//} agc_stage_ctx_t; + +#endif /* AUDIO_PIPELINE_DSP_H_ */ diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiohw.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiohw.xc new file mode 100644 index 0000000..5be9b2e --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiohw.xc @@ -0,0 +1,1250 @@ +#if UART_DEBUG || DEBUG_MEMORY_LOG_ENABLED +#define DEBUG_PRINT_ENABLE 1 +#else +#define DEBUG_PRINT_ENABLE 0 +#endif + +#include +#include +#include +#include "xassert.h" +#include "i2c.h" +#include "xua.h" +#include +#include "xc_ptr.h" +#include "user_uart.h" +#include "debug_print.h" + +#if defined (CODEC_AIC3204) +#include "eq_flash_storage.h" +#include "codec_ti3204.h" +#else +#error "No codec selected" +#endif + +extern "C" { + #include "sw_pll.h" +} + +#include "xc_ptr.h" +unsigned g_ch0_level = 0; +unsigned g_ch1_level = 0; + +unsigned g_extra_digial_gain_flag = 0; + + +#define NAU88C25 0 + +#if NAU88C25 +#include "nau88c25.h" +#else +#include "nau88c21.h" +#endif + +#if NAU88C25 +#define NAU88_I2C_DEVICE_ADDR 0x15 +#else +#define NAU88_I2C_DEVICE_ADDR 0x1B +#endif + +#include "htr3236.h" +#include "led_control.h" +#include "xmath/scalar/f32.h" + +#include "dnr_dsp_buf.h" + +#include "xua_hid_report.h" + +#if (XUA_PCM_FORMAT == XUA_PCM_FORMAT_TDM) && (XUA_I2S_N_BITS != 32) +#warning ADC only supports TDM operation at 32 bits +#endif + +#ifndef I2S_LOOPBACK +#define I2S_LOOPBACK (0) +#endif + + +#if UAC1 +unsigned g_uac_mode = 1; // 2 for UAC2.0, 1 for UAC1.0 +#else +unsigned g_uac_mode = 2; // 2 for UAC2.0, 1 for UAC1.0 +#endif + +unsafe client interface i2c_master_if i_i2c_client; +audio_sampling g_new_playback_format, g_playback_format; +audio_type g_new_audio_type, g_audio_type = 0; +unsigned g_mute_enable = 0; +unsigned g_mute_state = 0; +unsigned g_mute_state_old = 1; +unsigned g_unmute_dac_state, g_unmute_time, g_format_time; +unsigned g_volume_level = 60, g_saved_volume_level = 60; +unsigned g_request_volume_set = 0; +unsigned g_init_saved_settings = 0; +unsigned g_host_volume = 0x0; +unsigned g_dac_m_gain = 0; +unsigned g_unmute_delay_time = 0; +unsigned g_format_delay_time = 0; +unsigned g_mclk_select = 0; +// 改动原因:添加LED开关全局变量,控制LED是否显示(0=关闭,1=开启) +unsigned g_led_enable = 1; // LED开关,默认开启 +uint32_t get_reference_time(); +unsigned g_disable_i2c = 0; +unsigned g_dac_mode = 10; +unsigned g_new_dac_mode = 0; +unsigned g_samfreq = 48000; +// 改动原因:添加DSD模式全局变量,用于HID命令读取采样率和格式信息(已去掉DAC采样分辨率) +unsigned g_dsd_mode = 0; // DSD模式:0=PCM, >0=DSD +unsigned g_gain_mode = 0; // 0: 低阻, 1: 高阻 +// 改动原因:支持8种滤波器模式(0-7),与g_filter_map[8]数组定义一致 +unsigned g_filter_mode = 0; // 0-7: 对应8种滤波器模式 +// 改动原因:添加增益模式和滤波器模式的请求变量,用于HID命令设置模式 +unsigned g_request_gain_mode = -1; // -1表示无请求,0=低阻,1=高阻 +// 改动原因:支持8种滤波器模式(0-7),与g_filter_map[8]数组定义一致 +unsigned g_request_filter_mode = -1; // -1表示无请求,0-7=滤波器模式 +unsigned g_request_game_mode = -1; +#if EQ_EN +extern unsigned g_request_eq_mode, g_new_eq_mode; +#endif + +#define LED_OFF 0x7F // 修改:清除最高位,只保留低7位 +#define LED_RED 0b01011111 // 修改:清除最高位,只保留低7位 +#define LED_GREEN 0b01101111 // 修改:清除最高位,只保留低7am +#define LED_BLUE 0b00111111 // 修改:清除最高位,只保留低7位 +#define LED_YELLOW (LED_RED & LED_GREEN) +#define LED_CYAN (LED_GREEN & LED_BLUE) +#define LED_PURPLE (LED_RED & LED_BLUE) +#define LED_WHITE (LED_RED & LED_GREEN & LED_BLUE) +unsigned g_led_color = LED_OFF; +unsigned g_led_blink_count = 0; +unsigned g_led_blink_is_white = 0; + + +/* All on tile[0] */ +port p_scl = PORT_I2C_SCL; +port p_sda = PORT_I2C_SDA; + +//out port p_htr3235_sdb = PORT_HTR3236_SDB; /* 连接到HTR3236的SDB引脚,用于控制其电源状态 */ + +out port p_ctl_mute = PORT_CTL_MUTE; + + +/* Board setup for XU316 MC Audio (1v1) */ +void board_setup() +{ + +} + +// tile 0 +// called on tile 0 - start +#define NAU88L21_PGA_GAIN_REG_MIN_USED_VALUE 0x1 // 0x1=1, 0dB (0x0, -1dB which is not used in this design) +#define NAU88L21_PGA_GAIN_REG_MAX_VALUE 0x25+1 // 0x25=37, 36dB. +1 is for extra 1.5dB digital gain +#define NAU88L21_PGA_GAIN_REG_DEFAULT_VALUE (NAU88L21_PGA_GAIN_REG_MAX_VALUE) + +static inline void NAU88C22_REGREAD(unsigned reg, unsigned &val, client interface i2c_master_if i2c) +{ + i2c_regop_res_t result; + val = i2c.read_reg16(NAU88_I2C_DEVICE_ADDR, reg, result); +} + +static inline i2c_regop_res_t NAU88C22_REGWRITE(unsigned reg, unsigned val, client interface i2c_master_if i2c) +{ + i2c_regop_res_t result = i2c.write_reg16(NAU88_I2C_DEVICE_ADDR, reg, val); + + if(result != I2C_REGOP_SUCCESS) + { + debug_printf("I2C write failed: reg="); + printhex(reg); + debug_printf(", val="); + printhex(val); + debug_printf("\n"); + } + else + { + //debug_printf("I2C write success: reg="); + //printhex(reg); + //debug_printf(", val="); + //printhex(val); + //debug_printf("\n"); + //debug_printf("."); + } + + return result; +} + +void dac_volume(unsigned level) +{ + // unsigned tmp = 0xcf + level; + // NAU88C22_REGWRITE(0x33, tmp + 0x200, i2c); + // NAU88C22_REGWRITE(0x34, tmp, i2c); +} + +void mic_volume(unsigned level) +{ + unsigned temp; + + if(level == NAU88L21_PGA_GAIN_REG_MAX_VALUE) + { + temp = 1; + SET_SHARED_GLOBAL(g_extra_digial_gain_flag, temp); + + //NAU88C22_REGWRITE(0x007E, ((NAU88L21_PGA_GAIN_REG_MAX_VALUE-1) << 8), (client interface i2c_master_if)i_i2c_client); // HW is using PGA_GAINL + //NAU88C22_REGWRITE(0x007E, level, (client interface i2c_master_if)i_i2c_client); // PGA_GAINR is not used + } + else + { + temp = 0; + SET_SHARED_GLOBAL(g_extra_digial_gain_flag, temp); + + unsafe {NAU88C22_REGWRITE(0x007E, (level << 8), (client interface i2c_master_if)i_i2c_client);} // HW is using PGA_GAINL + //NAU88C22_REGWRITE(0x007E, level, (client interface i2c_master_if)i_i2c_client); // PGA_GAINR is not used + } +} + +void codec_init(void) +{ + /* + * Setup CODEC + */ + uint16_t addr = 0, val; + for (int i = 0; i < sizeof(nau88c22_registers) / sizeof(nau88c22_registers[0]); i++) { + addr = nau88c22_registers[i][1]; + val = (nau88c22_registers[i][2] << 8) | nau88c22_registers[i][3]; + unsafe {NAU88C22_REGWRITE(addr, val, (client interface i2c_master_if)i_i2c_client);} + } + + // ADCL PGA default setting + if(NAU88L21_PGA_GAIN_REG_DEFAULT_VALUE == NAU88L21_PGA_GAIN_REG_MAX_VALUE) + unsafe {NAU88C22_REGWRITE(0x007E, (NAU88L21_PGA_GAIN_REG_MAX_VALUE-1) << 8, (client interface i2c_master_if)i_i2c_client);} + else + unsafe {NAU88C22_REGWRITE(0x007E, NAU88L21_PGA_GAIN_REG_DEFAULT_VALUE << 8, (client interface i2c_master_if)i_i2c_client);} + + //debug_printfln("Codec init finished"); +} + +in port p_mic_gain_encoder1 = PORT_MIC_GAIN_ENCODER1; // 4F2 +in port p_mic_gain_encoder2 = PORT_MIC_GAIN_ENCODER2; // 4E2 + +in port p_hp_gain_encoder = PORT_HP_GAIN_ENCODER; // 8D4, 8D5 + +in port p_button_music_mode = PORT_BUTTON_MUSIC_MODE; // 1A +in port p_button_game_mode = PORT_BUTTON_GAME_MODE; // 1L +in port p_button_ai71_onoff = PORT_BUTTON_AI71_ONOFF; // 1M + +// +// +// +// +// +// +// +// +// + +#define KEY_POLLING_INTERVAL 10000000L // 100ms polling interval +#define TIMER_PERIOD (100000) + +unsigned g_windows_detect_done = 0; + +#define MUTE_ON 1 +#define MUTE_OFF 0 + +extern unsigned g_host_os; // 1 -> Windows, 2 -> Others +extern unsigned g_mute_on_off_t0; +extern unsigned g_uac_vol; + +void save_value(unsigned char * unsafe path, unsigned char value); +unsigned char load_value(unsigned char * unsafe path); + +enum {IR_OFF=0, IR_GAME=1, IR_MUSIC=2, IR_MOVIE=3, IR_7_1_GAME=4, IR_7_1_MUSIC=5, IR_7_1_MOVIE=6}; + +#ifndef HID_MAX_DATA_BYTES +#define HID_MAX_DATA_BYTES ( 64 ) +#endif +unsigned hidSendData_local[HID_MAX_DATA_BYTES / 4]; + +void button_task(chanend c_hidSendData, chanend cc_mic_level, chanend c_uac_vol) +{ + //hwtimer_t timer = hwtimer_alloc(); + unsigned time = 0; + timer tmr; + + static int uac_vol=0, last_uac_vol=0; +#if EQ_EN + + static unsigned eq_sync_counter = 0; // EQ参数存储计数器(每100ms递增,5次后保存) +#endif + // port_t p_ctrl_keys = XS1_PORT_8C; + + uint32_t port_ctrl_keys = 0, curr_ctrl_keys = 0, last_ctrl_keys = 0, keys_changed = 0; + uint32_t port_ctrl_keys2 = 0, curr_ctrl_keys2 = 0, last_ctrl_keys2 = 0, keys_changed2 = 0; + + int debounce_cnt = 0; + static unsigned isMute = 0; + uint8_t tmp = 0; + + // port_t p_button_music_mode = PORT_BUTTON_MUSIC_MODE; + // port_t p_button_game_mode = PORT_BUTTON_GAME_MODE; + // port_t p_button_ai71_onoff = PORT_BUTTON_AI71_ONOFF; + + //port_enable(p_button_music_mode); + + int codec_adc_pga_gain_reg_value = NAU88L21_PGA_GAIN_REG_DEFAULT_VALUE;// NAU88L21_PGA_GAIN_REG_MAX_VALUE;//0x01;//0x1f; + + int dac_level = 0; + + // mic endcoder 变量 + uint8_t prev_encode_input1 = 0; + uint8_t rotation_active = 0; + uint8_t sample_count = 0; + uint8_t last_valid_direction = 0; + uint8_t fast_rotation_mode = 0; // 快速旋转模式 + uint8_t consecutive_same_dir = 0; // 连续相同方向计数 + + // hp endcoder 变量 + uint8_t hp_prev_encode_input1 = 0; + uint8_t hp_rotation_active = 0; + uint8_t hp_sample_count = 0; + uint8_t hp_last_valid_direction = 0; + uint8_t hp_fast_rotation_mode = 0; // 快速旋转模式 + uint8_t hp_consecutive_same_dir = 0; // 连续相同方向计数 + + + + unsigned flag_music_mode = 0; + unsigned flag_ai71_onoff = 0; + unsigned flag_game_mode = 0; + unsigned flag_footsteps_enhancement = 0; + unsigned flag_aidenoise_onoff = 0; + + // Buttons state + unsigned push_button_music_mode_state_old = 1; // Active low + unsigned push_button_ai71_onoff_state_old = 1; // Active low + unsigned push_button_game_mode_state_old = 1; // Active low + unsigned push_button_footsteps_enhancement_state_old = 1; // Active low + unsigned push_button_aidenoise_onoff_state_old = 1; // Active low + + + + unsigned flag_mic_mute = 0; + unsigned flag_hp_mute = 0; + + unsigned push_button_mic_mute_state_old = 1; // Active low + unsigned push_button_hp_mute_state_old = 1; // Active low + + int current_mic_led_pos = 0; + + htr3236_t htr3236_dev; + + // LED控制上下文 + led_control_t led_ctx; + + // 初始化硬件 + htr3236_init(&htr3236_dev, HTR3236_ADDR_GND); + htr3236_hw_enable(&htr3236_dev); + //p_htr3235_sdb <: 1; + + unsafe + { + while(htr3236_software_wake(&htr3236_dev, (client interface i2c_master_if)i_i2c_client) != 0) { + // 错误处理 + debug_printf("HTR3236 LED driver wake failed\n"); + //return; + } + } + debug_printf("HTR3236 LED driver wake success 9\n"); + + // 设置PWM频率为22kHz (避免可闻噪声) + //htr3236_set_frequency(i2c, HTR3236_FREQ_22KHZ); + unsafe + { + while(htr3236_set_frequency(&htr3236_dev, (client interface i2c_master_if)i_i2c_client, HTR3236_FREQ_22KHZ) != 0) + { + debug_printf("HTR3236 set frequency failed\n"); + } + } + debug_printf("HTR3236 set frequency success 9\n"); + + // 初始化LED控制 + unsafe{led_control_init(&led_ctx, &htr3236_dev, (client interface i2c_master_if)i_i2c_client);} + + // led_on(&led_ctx, led_l_physical_map[0]); + // delay_milliseconds(100); + // led_update_all(&led_ctx); + + // led_on(&led_ctx, led_l_physical_map[1]); + // delay_milliseconds(100); + // led_update_all(&led_ctx); + + // led_on(&led_ctx, led_l_physical_map[2]); + // delay_milliseconds(100); + // led_update_all(&led_ctx); + + //led_waterfall(&led_ctx, 50); + + + // ADCL PGA default setting + mic_volume(codec_adc_pga_gain_reg_value); // make sure to write adc reg after initialize the codec, otherwise it does not take effect because of the hardware reset in the initial code + current_mic_led_pos = mic_gain_to_led[codec_adc_pga_gain_reg_value] - 1; + for(int i = 0; i <= current_mic_led_pos; i++) + { + if ((i < 15) && (i >= 0)) + // 点亮L系列当前位置 + led_on(&led_ctx, led_l_physical_map[i]); + } + //led_update_all(&led_ctx); + + current_mic_led_pos = mic_gain_to_led[dac_level] - 1; + for(int i = 0; i <= current_mic_led_pos; i++) + { + if ((i < 15) && (i >= 0)) + // 点亮D系列当前位置 + led_on(&led_ctx, led_d_physical_map[i]); + } + led_update_all(&led_ctx); + + + unsigned hidData0; + delay_milliseconds(200); + codec_init(); + + unsigned ex3d_key_verified; + delay_milliseconds(300); + debug_printf("button task start\n"); + + + // Windows模式:从 "game_mode" 加载(模式值 0-3) + // UAC1模式:从 "uac1_mode" 加载(等效模式值 0-2) + unsigned /* char */ saved_mode; + unsigned char saved_uac1_mode = 0; + unsigned char path[] = "game_mode"; + + unsigned host_os; + GET_SHARED_GLOBAL(host_os, g_host_os); + saved_mode = load_value(path); + debug_printf("Loaded game_mode from flash: %d\n", saved_mode); + +#if defined(UAC1_MODE) + if (saved_mode == 255) { + saved_mode = 0; + save_value(path, saved_mode); + debug_printf("Saved game_mode to flash: %d\n", saved_mode); + } +#else + if (saved_mode == 255) { + // 如果文件不存在,根据编译模式设置默认值 +#if defined(STEREO_2K) + saved_mode = 0; +#elif defined(SPATIAL_GAME) + saved_mode = 2; +#elif defined(SPATIAL_MOVIE) + saved_mode = 3; +#endif + save_value(path, saved_mode); + debug_printf("Saved game_mode to flash: %d\n", saved_mode); + } +#endif + //printf("host_os: %d\n", host_os); +#if 0 + if ((host_os == OS_OTHERS) || (host_os == 0)) { + +#if !UAC1_MODE + printf("Detected non-Windows OS (OS_OTHERS), switching to UAC1 mode\n"); + SetRoleSwitchFlag(MODE_UAC1); + debug_printf("Set role switch flag: 0x%04X (UAC1), device will restart\n", MODE_UAC1); + // 设备会重启,退出函数 + device_reboot(); + while(1); +#endif + } +#endif + +#if UAC1_MODE == 1 + if (host_os == OS_WIN) { + printf("Detected Windows OS (OS_WIN) saved_mode: %d\n", saved_mode); + +#if IR_SWITCHING_MODE + if (saved_mode == 0) { + SetRoleSwitchFlag(MODE_STEREO_2K); + } else { + SetRoleSwitchFlag(MODE_SPATIAL_GAME); + } +#else + { + switch (saved_mode) { + case 0: + SetRoleSwitchFlag(MODE_STEREO_2K); + break; + case 1: + SetRoleSwitchFlag(MODE_STEREO_2K); + break; + case 2: + SetRoleSwitchFlag(MODE_SPATIAL_GAME); + break; + case 3: + SetRoleSwitchFlag(MODE_SPATIAL_MOVIE); + break; + default: + SetRoleSwitchFlag(MODE_STEREO_2K); + break; + } + debug_printf("Switch to saved mode: %d, device will restart.\n", saved_mode); + delay_milliseconds(200); + device_reboot(); + while (1); + } +#endif + } + +#endif + +#if defined(STEREO_2K) + if ((saved_mode != 255) && (saved_mode != 0) && (saved_mode != 1)) { + saved_mode = 0; + } +#endif + +#if defined(SPATIAL_GAME) + if ((saved_mode != 255) && (saved_mode != 2)) { + saved_mode = 2; + } +#endif + +#if defined(SPATIAL_MOVIE) + if ((saved_mode != 255) && (saved_mode != 3)) { + saved_mode = 3; + } +#endif + + g_windows_detect_done = 1; + + + SET_SHARED_GLOBAL(g_game_mode, saved_mode); + debug_printf("Set new g_game_mode to %d\n", saved_mode); + + //chan_out_byte(cc_mic_level, 0xFC); // 音频模式传输命令 + //chan_out_byte(cc_mic_level, saved_mode); // 音频模式值(0=无音效,1=STEREO_2K,2=SPATIAL_GAME,3=SPATIAL_MOVIE) + cc_mic_level <: 0xFC; + cc_mic_level <: saved_mode; + debug_printf("Sent audio_mode %d to hid_button_task via cc_mic_level channel\n", saved_mode); + + // port_enable(p_ctrl_keys); + port_ctrl_keys = 0;//port_in(p_ctrl_keys); + if ((port_ctrl_keys & KEY_MUTE) == 0) { + // mute switch off (unmuted) + debug_printf("unmuted\n"); + isMute = 0; + SET_SHARED_GLOBAL(g_mute_on_off_t0, MUTE_OFF); // 初始化tile0静音状态 + } else { + // mute switch on (muted) + debug_printf("muted\n"); + isMute = 1; + SET_SHARED_GLOBAL(g_mute_on_off_t0, MUTE_ON); // 初始化tile0静音状态 + } + + last_ctrl_keys = ((port_ctrl_keys) & KEY_BITS); + last_ctrl_keys ^= KEY_MUTE; + + + //hwtimer_set_trigger_time(timer, hwtimer_get_time(timer) + KEY_POLLING_INTERVAL); + tmr :> time; /* Input the time */ + time += TIMER_PERIOD; /* Increment the time */ + + + + + while (1) { + select + { + case c_hidSendData :> hidData0: + { + unsigned *reportData = hidSendData_local; + reportData[0] = hidData0; + if (hidData0 == 0xffffffff) { + debug_printf("receive end data\n"); + + // 注意:UAC1模式下不会收到此命令(hid_button_task不发送) + // 读取当前g_game_mode值 + unsigned current_mode;// = chan_in_word(c_hidSendData); + c_hidSendData :> current_mode; + + + // 循环切换模式:0(STEREO_2K) -> 1(SPATIAL_GAME) -> 2(SPATIAL_MOVIE) -> 3(无音效) -> 0 + unsigned new_mode; + unsigned mode_flag; + debug_printf("current_mode: %d\n", current_mode); + if (current_mode == 0) { + // 当前是STEREO_2K,切换到STEREO_2K + new_mode = 1; +#if IR_SWITCHING_MODE + mode_flag = IR_GAME; +#else + mode_flag = MODE_STEREO_2K; +#endif + debug_printf("Switching from STEREO_2K (0) to STEREO_2K (1)\n"); + } else if (current_mode == 1) { + // 当前是STEREO_2K,切换到SPATIAL_GAME + new_mode = 2; +#if IR_SWITCHING_MODE + mode_flag = IR_7_1_GAME; +#else + mode_flag = MODE_SPATIAL_GAME; +#endif + debug_printf("Switching from STEREO_2K (1) to SPATIAL_GAME (2)\n"); + } else if (current_mode == 2) { + // 当前是SPATIAL_GAME,切换到SPATIAL_MOVIE + new_mode = 3; +#if IR_SWITCHING_MODE + mode_flag = IR_7_1_MOVIE; +#else + mode_flag = MODE_SPATIAL_MOVIE; // SPATIAL_MOVIE模式使用SPATIAL_MOVIE固件 +#endif + debug_printf("Switching from SPATIAL_GAME (2) to SPATIAL_MOVIE (3)\n"); + } // 更新g_game_mode + else if(current_mode == 3) { + new_mode = 0; +#if IR_SWITCHING_MODE + mode_flag = IR_OFF; +#else + mode_flag = MODE_STEREO_2K; +#endif + debug_printf("Switching from SPATIAL_MOVIE (3) to STEREO_2K (0)\n"); + } + + + unsigned char path[] = "game_mode"; + save_value(path, (unsigned char)new_mode); + debug_printf("Saved game_mode %d to flash\n", new_mode); + delay_milliseconds(20); + + + // 只有在需要切换固件时才reboot,同一固件内的模式切换(如0<->1)只更新模式和算法状态 +#if !UAC1_MODE + +#if IR_SWITCHING_MODE + debug_printf("Same firmware mode (0x%04X), updating mode and algorithm state without reboot\n", mode_flag); + SET_SHARED_GLOBAL(g_game_mode, new_mode); + //chan_out_byte(cc_mic_level, 0xFC); // 音频模式传输命令 + //chan_out_byte(cc_mic_level, new_mode); // 音频模式值 + cc_mic_level <: 0xFC; + cc_mic_level <: new_mode; + debug_printf("Sent audio_mode %d to hid_button_task via cc_mic_level channel\n", new_mode); +#else + if (new_mode != 1) { + + debug_printf("Set role switch flag: 0x%04X, switching firmware, rebooting...\n", mode_flag); + SetRoleSwitchFlag(mode_flag); + delay_milliseconds(20); + device_reboot(); + while (1); + } else { + + debug_printf("Same firmware mode (0x%04X), updating mode and algorithm state without reboot\n", mode_flag); + SET_SHARED_GLOBAL(g_game_mode, new_mode); + + // chan_out_byte(cc_mic_level, 0xFC); // 音频模式传输命令 + // chan_out_byte(cc_mic_level, new_mode); // 音频模式值 + cc_mic_level <: 0xFC; + cc_mic_level <: new_mode; + debug_printf("Sent audio_mode %d to hid_button_task via cc_mic_level channel\n", new_mode); + } +#endif +#endif + break; + } +#if (HID_CONTROLS == 1) + for (int i=1; i<(HID_MAX_DATA_BYTES/4); i++) { + //reportData[i] = chan_in_word(c_hidSendData); + c_hidSendData :> reportData[i]; + } + hidSetChangePending(1); +#endif + break; + } + + case tmr when timerafter(time) :> void : + { + //hwtimer_change_trigger_time(timer, hwtimer_get_time(timer) + KEY_POLLING_INTERVAL); + time += TIMER_PERIOD; + + + //hidData0 = 0; + +// port_t p_button_music_mode = PORT_BUTTON_MUSIC_MODE; +// port_t p_button_game_mode = PORT_BUTTON_GAME_MODE; +// port_t p_button_ai71_onoff = PORT_BUTTON_AI71_ONOFF; + +//p_button_music_mode :> button_music_mode; // 1A + + unsigned mic_encoder1, mic_encoder2, hp_encoder; + unsigned button_mic_mute, button_hp_mute; + unsigned button_music_mode, button_ai71_onoff, button_game_mode, button_footsteps_enhancement, button_aidenoise_onoff; + unsigned mode_change = 0; + + //button_music_mode = port_in(p_button_music_mode); + p_button_music_mode :> button_music_mode; // 1A + p_button_game_mode :> button_game_mode; // 1L + p_button_ai71_onoff :> button_ai71_onoff; // 1M + + p_mic_gain_encoder1 :> mic_encoder1; // 4F2 + p_mic_gain_encoder2 :> mic_encoder2; // 4E2 + + button_mic_mute = ((mic_encoder1 & 0b1000) != 0); // 4F3 + //button_hp_mute = ((hp_encoder & 0b01000000) != 0); // 8D6 + + button_footsteps_enhancement = ((mic_encoder1 & 0b0010) != 0); // 4F1 + button_aidenoise_onoff = ((mic_encoder2 & 0b1000) != 0); // 4E3 + + if(button_music_mode == 0) + { + if(push_button_music_mode_state_old == 1) + { + flag_music_mode ^= 1; + mode_change = 1; + } + } + push_button_music_mode_state_old = button_music_mode; + if(mode_change) + if(flag_music_mode) + { + led_on(&led_ctx, LED_MUSIC); + } + else + { + led_off(&led_ctx, LED_MUSIC); + } + + mode_change = 0; + if(button_ai71_onoff == 0) + { + if(push_button_ai71_onoff_state_old == 1) + { + flag_ai71_onoff ^= 1; + mode_change = 1; + } + + } + push_button_ai71_onoff_state_old = button_ai71_onoff; + if(mode_change) + if(flag_ai71_onoff) + { + led_on(&led_ctx, LED_AI7_1); + } + else + { + led_off(&led_ctx, LED_AI7_1); + } + + mode_change = 0; + if(button_game_mode == 0) + { + if(push_button_game_mode_state_old == 1) + { + flag_game_mode ^= 1; + mode_change = 1; + } + } + push_button_game_mode_state_old = button_game_mode; + if(mode_change) + if(flag_game_mode) + { + led_on(&led_ctx, LED_GAME_MODE); + } + else + { + led_off(&led_ctx, LED_GAME_MODE); + } + + mode_change = 0; + if(button_footsteps_enhancement == 0) + { + if(push_button_footsteps_enhancement_state_old == 1) + { + flag_footsteps_enhancement ^= 1; + mode_change = 1; + } + } + push_button_footsteps_enhancement_state_old = button_footsteps_enhancement; + if(mode_change) + if(flag_footsteps_enhancement) + { + led_on(&led_ctx, LED_FOOTSTEP_MODE); + } + else + { + led_off(&led_ctx, LED_FOOTSTEP_MODE); + } + + mode_change = 0; + if(button_aidenoise_onoff == 0) + { + if(push_button_aidenoise_onoff_state_old == 1) + { + flag_aidenoise_onoff ^= 1; + mode_change = 1; + } + } + push_button_aidenoise_onoff_state_old = button_aidenoise_onoff; + if(mode_change) + if(flag_aidenoise_onoff) + { + led_on(&led_ctx, LED_ANC); + } + else + { + led_off(&led_ctx, LED_ANC); + } + + //led_update_all(&led_ctx); + + + + + mode_change = 0; + if(button_mic_mute == 0) + { + if(push_button_mic_mute_state_old == 1) + { + flag_mic_mute ^= 1; + mode_change = 1; + } + } + push_button_mic_mute_state_old = button_mic_mute; + if(mode_change) + if(flag_mic_mute) + { + current_mic_led_pos = mic_gain_to_led[codec_adc_pga_gain_reg_value] - 1; + for(int i = 0; i <= current_mic_led_pos; i++) + { + if ((i < 15) && (i >= 0)) + // 点亮L系列当前位置 + led_off(&led_ctx, led_l_physical_map[i]); + } + } + else + { + current_mic_led_pos = mic_gain_to_led[codec_adc_pga_gain_reg_value] - 1; + for(int i = 0; i <= current_mic_led_pos; i++) + { + if ((i < 15) && (i >= 0)) + // 点亮L系列当前位置 + led_on(&led_ctx, led_l_physical_map[i]); + } + } + + + + // mic_gain = 0; + + // if((mic_encoder1 & 0b0100) != 0) mic_gain |= 0b0010; + // if((mic_encoder2 & 0b0100) != 0) mic_gain |= 0b1000; + + //mic_gain >>= 4; + + // if((mic_gain & 0b0100) == 0) + // { + // if(mute_down) + // { + // SET_SHARED_GLOBAL(g_reduct_dep, ++mic_mute % 4); + // } + // // if ((mic_mute % 4) == 0) + // // set_gpio(P_GPIO_LED, 0); + // // else + // // set_gpio(P_GPIO_LED, 1); + + // mute_down = 0; + // } + // else + // { + // mute_down = 1; + // } + + uint8_t rotating = (mic_encoder2 & 0b0100) == 0; // 4E2 + uint8_t encode_input1 = ((mic_encoder1 & 0b0100) != 0); // 4F2 + + if(rotating) { + if(!rotation_active) { + // 旋转开始 + prev_encode_input1 = encode_input1; + sample_count = 1; + rotation_active = 1; + + // 根据上次方向预判 + if(fast_rotation_mode) { + // 快速模式只需要2次采样 + sample_count = 2; + } + } else { + sample_count++; + + // 动态调整采样次数:快速模式用2次,普通模式用3次 + uint8_t required_samples = fast_rotation_mode ? 2 : 3; + + if(sample_count >= required_samples) { + if(encode_input1 != prev_encode_input1) { + uint8_t current_dir = encode_input1 ? 1 : 2; + + // 执行音量调节 + if(current_dir == 1) { // 顺时针 + if((codec_adc_pga_gain_reg_value < NAU88L21_PGA_GAIN_REG_MAX_VALUE) && (flag_mic_mute == 0)) { + uint8_t old_led_count = mic_gain_to_led[codec_adc_pga_gain_reg_value]; + + // 增加音量 + mic_volume(++codec_adc_pga_gain_reg_value); + + uint8_t new_led_count = mic_gain_to_led[codec_adc_pga_gain_reg_value]; + + // 如果LED数增加,点亮新增的那个LED + if(new_led_count > old_led_count) { + // 新点亮的LED索引 = new_led_count - 1 + led_on(&led_ctx, led_l_physical_map[new_led_count - 1]); + led_update_all(&led_ctx); + } + } + } else { // 逆时针 + if((codec_adc_pga_gain_reg_value > NAU88L21_PGA_GAIN_REG_MIN_USED_VALUE) && (flag_mic_mute == 0)) { + uint8_t old_led_count = mic_gain_to_led[codec_adc_pga_gain_reg_value]; + + // 减小音量 + mic_volume(--codec_adc_pga_gain_reg_value); + + uint8_t new_led_count = mic_gain_to_led[codec_adc_pga_gain_reg_value]; + + // 如果LED数减少,关闭减少的那个LED + if(new_led_count < old_led_count) { + // 关闭的LED索引 = old_led_count - 1 + led_off(&led_ctx, led_l_physical_map[old_led_count - 1]); + led_update_all(&led_ctx); + } + } + } + + // 检测是否快速旋转 + if(current_dir == last_valid_direction) { + consecutive_same_dir++; + if(consecutive_same_dir >= 3) { + fast_rotation_mode = 1; // 进入快速模式 + } + } else { + consecutive_same_dir = 0; + fast_rotation_mode = 0; // 方向变化,退出快速模式 + } + + last_valid_direction = current_dir; + rotation_active = 0; + sample_count = 0; + } + } + } + } else { + rotation_active = 0; + sample_count = 0; + // 停止旋转一段时间后退出快速模式 + static uint16_t idle_count = 0; + idle_count++; + if(idle_count > 50) { // 50ms无旋转 + fast_rotation_mode = 0; + consecutive_same_dir = 0; + idle_count = 0; + } + } + +#if 1 + // HP Encoder + p_hp_gain_encoder :> hp_encoder; // 8D4, 8D5 + + uint8_t hp_rotating = ((hp_encoder & 0b00100000) == 0); // 8D5 + uint8_t hp_encode_input1 = ((hp_encoder & 0b00010000) != 0); // 8D4 + + if(hp_rotating) { + if(!hp_rotation_active) { + // 旋转开始 + hp_prev_encode_input1 = hp_encode_input1; + hp_sample_count = 1; + hp_rotation_active = 1; + + // 根据上次方向预判 + if(hp_fast_rotation_mode) { + // 快速模式只需要2次采样 + hp_sample_count = 2; + } + } else { + hp_sample_count++; + + // 动态调整采样次数:快速模式用2次,普通模式用3次 + uint8_t hp_required_samples = hp_fast_rotation_mode ? 2 : 3; + + if(hp_sample_count >= hp_required_samples) { + if(hp_encode_input1 != hp_prev_encode_input1) { + uint8_t hp_current_dir = hp_encode_input1 ? 1 : 2; + + // 执行音量调节 + if(hp_current_dir == 1) { // 顺时针 + if(dac_level < NAU88L21_PGA_GAIN_REG_MAX_VALUE && flag_hp_mute == 0) { + uint8_t old_led_count = mic_gain_to_led[dac_level]; + + // 增加音量 + ++dac_level; + + uint8_t new_led_count = mic_gain_to_led[dac_level]; + + // 如果LED数增加,点亮新增的那个LED + if(new_led_count > old_led_count) { + // 新点亮的LED索引 = new_led_count - 1 + led_on(&led_ctx, led_d_physical_map[new_led_count - 1]); + led_update_all(&led_ctx); + } + } + } else { // 逆时针 + if(dac_level > NAU88L21_PGA_GAIN_REG_MIN_USED_VALUE && flag_hp_mute == 0) { + uint8_t old_led_count = mic_gain_to_led[dac_level]; + + // 减小音量 + --dac_level; + + uint8_t new_led_count = mic_gain_to_led[dac_level]; + + // 如果LED数减少,关闭减少的那个LED + if(new_led_count < old_led_count) { + // 关闭的LED索引 = old_led_count - 1 + led_off(&led_ctx, led_d_physical_map[old_led_count - 1]); + led_update_all(&led_ctx); + } + } + } + + // 检测是否快速旋转 + if(hp_current_dir == hp_last_valid_direction) { + hp_consecutive_same_dir++; + if(hp_consecutive_same_dir >= 3) { + hp_fast_rotation_mode = 1; // 进入快速模式 + } + } else { + hp_consecutive_same_dir = 0; + hp_fast_rotation_mode = 0; // 方向变化,退出快速模式 + } + + hp_last_valid_direction = hp_current_dir; + hp_rotation_active = 0; + hp_sample_count = 0; + } + } + } + } else { + hp_rotation_active = 0; + hp_sample_count = 0; + // 停止旋转一段时间后退出快速模式 + static uint16_t hp_idle_count = 0; + hp_idle_count++; + if(hp_idle_count > 50) { // 50ms无旋转 + hp_fast_rotation_mode = 0; + hp_consecutive_same_dir = 0; + hp_idle_count = 0; + } + } + + button_hp_mute = ((hp_encoder & 0b01000000) != 0); // 8D6 + + mode_change = 0; + if(button_hp_mute == 0) + { + if(push_button_hp_mute_state_old == 1) + { + flag_hp_mute ^= 1; + mode_change = 1; + } + } + push_button_hp_mute_state_old = button_hp_mute; + if(mode_change) + if(flag_hp_mute) + { + current_mic_led_pos = mic_gain_to_led[dac_level] - 1; + for(int i = 0; i <= current_mic_led_pos; i++) + { + if ((i < 15) && (i >= 0)) + // 点亮L系列当前位置 + led_off(&led_ctx, led_d_physical_map[i]); + } + } + else + { + current_mic_led_pos = mic_gain_to_led[dac_level] - 1; + for(int i = 0; i <= current_mic_led_pos; i++) + { + if ((i < 15) && (i >= 0)) + // 点亮L系列当前位置 + led_on(&led_ctx, led_d_physical_map[i]); + } + } + + led_update_all(&led_ctx); +#endif + +#if 0 + curr_ctrl_keys = (port_in(p_ctrl_keys)) & KEY_BITS; + + + if (curr_ctrl_keys != last_ctrl_keys) { + debounce_cnt++; + if (debounce_cnt >= 2) { + debounce_cnt = 0; + keys_changed = curr_ctrl_keys ^ last_ctrl_keys; + tmp = 0; + switch (keys_changed) { + case KEY_MIC_VOL_DN: + case KEY_MIC_VOL_UP: + + if ((isMute == 0) && ((curr_ctrl_keys & KEY_MIC_VOL_DN) == 0)) { + tmp = KEY_MIC_VOL_DN; + debug_printf("KEY_MIC_VOL_DN pressed\n"); + } else { + if ((isMute == 0) && ((curr_ctrl_keys & KEY_MIC_VOL_UP) == 0)) { + tmp = KEY_MIC_VOL_UP; + debug_printf("KEY_MIC_VOL_UP pressed\n"); + } + } + if (tmp) { + g_mic_vol_cmd_pending = (unsigned)tmp; + } + break; + case KEY_MUTE: + + if ((curr_ctrl_keys & KEY_MUTE) == 0) { + // mute switch off (unmuted) + isMute = 0; + SET_SHARED_GLOBAL(g_mute_on_off_t0, MUTE_OFF); // 更新tile0静音状态 + chan_out_byte(cc_mic_level, (uint8_t) UNMUTED_MIC); + debug_printf("MUTE: unmuted\n"); + } else { + // mute switch on (muted) + isMute = 1; + SET_SHARED_GLOBAL(g_mute_on_off_t0, MUTE_ON); // 更新tile0静音状态 + chan_out_byte(cc_mic_level, (uint8_t) MUTED_MIC); + debug_printf("MUTE: muted\n"); + } + break; +#if (HID_CONTROLS == 1) + case KEY_PLAY_VOL_DN: + + if ((curr_ctrl_keys & KEY_PLAY_VOL_DN) == 0) { + debug_printf("KEY_PLAY_VOL_DN pressed\n"); + update_button(HID_CONTROL_VOLDN); // 更新HID报告 + } + break; + case KEY_PLAY_VOL_UP: + + if ((curr_ctrl_keys & KEY_PLAY_VOL_UP) == 0) { + update_button(HID_CONTROL_VOLUP); // 更新HID报告 + } + break; +#endif + default: + break; + } + last_ctrl_keys = curr_ctrl_keys; // 更新上次按键状态 + } + } else { + debounce_cnt = 0; // 状态未变化,重置防抖计数器 + } +#endif + + GET_SHARED_GLOBAL(uac_vol, g_uac_vol); + if (uac_vol != last_uac_vol) { + unsigned conv_vol = (0xffffffff - uac_vol + 1) >> 8; + //debug_printf("vol: - %d\n", conv_vol); + //chan_out_word(c_uac_vol, conv_vol); + c_uac_vol <: conv_vol; + last_uac_vol = uac_vol; + } + +#if EQ_EN + + { + unsigned request_eq_mode, new_eq_mode; + unsigned force_request_mode_change; + GET_SHARED_GLOBAL(request_eq_mode, g_request_eq_mode); + GET_SHARED_GLOBAL(new_eq_mode, g_new_eq_mode); + GET_SHARED_GLOBAL(force_request_mode_change, g_force_request_eq_mode_change); + if (new_eq_mode != request_eq_mode) + { + // debug_printf("new eq mode != request eq mode\n"); + SET_SHARED_GLOBAL(g_new_eq_mode, request_eq_mode); + } + + if (force_request_mode_change == 1) + { + debug_printf("force request mode change\n"); + SET_SHARED_GLOBAL(g_force_eq_mode_change, 1); + SET_SHARED_GLOBAL(g_force_request_eq_mode_change, 0); + } + } + + + eq_sync_counter++; + if (eq_sync_counter >= 5) { // 5 * 100ms = 500ms + eq_sync_counter = 0; + + // 使用单参数存储系统保存脏参数(独立保存EQ参数) + if (eq_save_dirty_params() == 0) { + //debug_printf("Single EQ parameters synced to Flash successfully\n"); + } else { + //debug_printf("Failed to sync single EQ parameters to Flash\n"); + } + + // 独立保存脏的增益 + if (eq_save_dirty_gain() == 0) { + // debug_printf("Gain synced to Flash successfully\n"); + } else { + //debug_printf("Failed to sync gain to Flash\n"); + } + + // 独立保存脏的模式名称 + if (eq_save_dirty_names() == 0) { + // debug_printf("Mode names synced to Flash successfully\n"); + } else { + //debug_printf("Failed to sync mode names to Flash\n"); + } + } +#endif + + break; + } + } + } +} + +void AudioHwRemote(chanend c_hidSendData, chanend cc_mic_level, chanend c_uac_vol) +{ + i2c_master_if i2c[1]; + board_setup(); + par { + i2c_master(i2c, 1, p_scl, p_sda, 300); + { + unsafe {i_i2c_client = i2c[0];} + button_task(c_hidSendData, cc_mic_level, c_uac_vol); + } + } +} + + +/* Configures the external audio hardware at startup */ +void AudioHwInit() +{ + int result=0; + + sw_pll_fixed_clock(MCLK_48); + + p_ctl_mute <: 1; // pull high AMP ctl_mute pin to unmute + + // Wait for power supply to come up. + delay_milliseconds(200); +} + +/* Configures the external audio hardware for the required sample frequency */ +void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned sampRes_DAC, unsigned sampRes_ADC) +{ + g_samfreq = samFreq; + { + sw_pll_fixed_clock(mClk); + } +} + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiostream.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiostream.xc new file mode 100644 index 0000000..a8bae3d --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/audiostream.xc @@ -0,0 +1,16 @@ +#include + +//on tile[0]: out port p_leds = XS1_PORT_4F; + +void UserAudioStreamStart(void) +{ + /* Turn all LEDs on */ +// p_leds <: 0xF; +} + +void UserAudioStreamStop(void) +{ + /* Turn all LEDs off */ +// p_leds <: 0x0; +} + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.c new file mode 100644 index 0000000..5d4ffbc --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.c @@ -0,0 +1,316 @@ +#include "biquad_standalone.h" +#include +#include + +// 常量定义 +#define Q_factor 30 +#define BOOST_BSHIFT 2 + +// 如果没有定义M_PI,则手动定义 +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +static const float pi = (float)M_PI; +static const float log_2 = 0.69314718055f; +static const float db_2 = 6.02059991328f; // 20*log10(2) + +// 辅助函数实现 +static inline int32_t _float2fixed(float x, int32_t q) { + if (x < 0.0f) return (((float)(1u << q)) * x - 0.5f); + if (x > 0.0f) return (((float)((1u << q) - 1)) * x + 0.5f); + return 0; +} + +static inline int32_t _float2fixed_assert(float x, int32_t q) { + float max_val = (float)(1 << (31 - q)); + // 简单的断言检查,如果超出范围则返回边界值 + if (x > max_val) x = max_val; + if (x < -max_val) x = -max_val; + return _float2fixed(x, q); +} + +// 简单的三角函数实现,使用标准库 - 重命名以避免冲突 +static inline float biquad_sin(float x) { + return sinf(x); +} + +static inline float biquad_cos(float x) { + return cosf(x); +} + +static inline float _check_fc(float fc, float fs) { + float fc_sat = fc; + // saturate if > fs/2 + if (fc_sat >= fs / 2.0f) { + fc_sat = fs / 2.0f; + } + return fc_sat; +} + +static inline float _check_gain(float gain, float max_gain) { + float gain_sat = gain; + // saturate if > max_gain + if (gain_sat >= max_gain) { + gain_sat = max_gain; + } + return gain_sat; +} + +void eq_biquad_bypass(q2_30 coeffs[5]) { + coeffs[0] = 1 << Q_factor; + coeffs[1] = 0; + coeffs[2] = 0; + coeffs[3] = 0; + coeffs[4] = 0; +} + +void eq_biquad_mute(q2_30 coeffs[5]) { + coeffs[0] = 0; + coeffs[1] = 0; + coeffs[2] = 0; + coeffs[3] = 0; + coeffs[4] = 0; +} + +left_shift_t eq_biquad_gain(q2_30 coeffs[5], const float gain_db) { + float A = powf(10.0f, (gain_db * (1.0f / 20.0f))); + + coeffs[0] = _float2fixed_assert(A, Q_factor - BOOST_BSHIFT); + coeffs[1] = 0; + coeffs[2] = 0; + coeffs[3] = 0; + coeffs[4] = 0; + + return BOOST_BSHIFT; +} + +void eq_biquad_lowpass(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q) { + float fc_sat = _check_fc(fc, fs); + + // Compute common factors + float K = tanf(pi * fc_sat / fs); + float KK = K * K; + float KQ = K / filter_Q; + float norm = 1.0f / (1.0f + KQ + KK); + + // Compute coeffs + float b0 = KK * norm; + float b1 = 2.0f * b0; + float b2 = b0; + float a1 = 2.0f * (KK - 1.0f) * norm; + float a2 = (1.0f - KQ + KK) * norm; + + //DPRINTF("b0: %f \n b1: %f \n b2: %f \n a1: %f \n a2: %f\n", b0, b1, b2, a1, a2); + // Store as fixed-point values + coeffs[0] = _float2fixed_assert(b0, Q_factor); + coeffs[1] = _float2fixed_assert(b1, Q_factor); + coeffs[2] = _float2fixed_assert(b2, Q_factor); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); +} + +void eq_biquad_highpass(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q) { + float fc_sat = _check_fc(fc, fs); + + // Compute common factors + float K = tanf(pi * fc_sat / fs); + float KK = K * K; + float KQ = K / filter_Q; + float norm = 1.0f / (1.0f + KQ + KK); + + // Compute coeffs + float b0 = norm; + float b1 = -2.0f * b0; + float b2 = b0; + float a1 = 2.0f * (KK - 1.0f) * norm; + float a2 = (1.0f - KQ + KK) * norm; + + // Store as fixed-point values + coeffs[0] = _float2fixed_assert(b0, Q_factor); + coeffs[1] = _float2fixed_assert(b1, Q_factor); + coeffs[2] = _float2fixed_assert(b2, Q_factor); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); +} + +void eq_biquad_allpass(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q) { + float fc_sat = _check_fc(fc, fs); + + float K = tanf(pi * fc_sat / fs); + float norm = 1.0f / (1.0f + K / filter_Q + K * K); + + float b0 = (1.0f - K / filter_Q + K * K) * norm; + float b1 = 2.0f * (K * K - 1.0f) * norm; + float b2 = 1.0f; + float a1 = b1; + float a2 = b0; + + coeffs[0] = _float2fixed_assert(b0, Q_factor); + coeffs[1] = _float2fixed_assert(b1, Q_factor); + coeffs[2] = _float2fixed_assert(b2, Q_factor); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); +} + +// 动态计算bshift的函数 +static left_shift_t calculate_bshift(float b0, float b1, float b2) { + float max_b = fabsf(b0); + if (fabsf(b1) > max_b) max_b = fabsf(b1); + if (fabsf(b2) > max_b) max_b = fabsf(b2); + if (max_b == 0.0f) return 0; + int bshift = 0; + if (max_b >= 1.0f) { + float temp = max_b; + while (temp >= 2.0f) { temp /= 2.0f; bshift++; } + } else { + float temp = max_b; + while (temp < 1.0f) { temp *= 2.0f; bshift--; } + bshift--; + } + return (bshift >= 0) ? bshift : 0; +} + +left_shift_t eq_biquad_peaking(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q, const float gain_db) { + float gain_db_sat = _check_gain(gain_db, 20.0f * db_2); + float fc_sat = _check_fc(fc, fs); + float V = powf(10.0f, fabsf(gain_db_sat) / 20.0f); + float K = tanf(pi * fc_sat / fs); + float norm, b0, b1, b2, a1, a2; + + if (gain_db_sat >= 0) { + norm = 1.0f / (1.0f + 1.0f/filter_Q * K + K * K); + b0 = (1.0f + V/filter_Q * K + K * K) * norm; + b1 = 2.0f * (K * K - 1.0f) * norm; + b2 = (1.0f - V/filter_Q * K + K * K) * norm; + a1 = b1; + a2 = (1.0f - 1.0f/filter_Q * K + K * K) * norm; + } else { + norm = 1.0f / (1.0f + V/filter_Q * K + K * K); + b0 = (1.0f + 1.0f/filter_Q * K + K * K) * norm; + b1 = 2.0f * (K * K - 1.0f) * norm; + b2 = (1.0f - 1.0f/filter_Q * K + K * K) * norm; + a1 = b1; + a2 = (1.0f - V/filter_Q * K + K * K) * norm; + } + + left_shift_t bshift = calculate_bshift(b0, b1, b2); + coeffs[0] = _float2fixed_assert(b0, Q_factor - bshift); + coeffs[1] = _float2fixed_assert(b1, Q_factor - bshift); + coeffs[2] = _float2fixed_assert(b2, Q_factor - bshift); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); + return bshift; +} + +left_shift_t eq_biquad_lowshelf(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q, const float gain_db) { + float gain_db_sat = _check_gain(gain_db, 20.0f * db_2); + float fc_sat = _check_fc(fc, fs); + + // 使用与Python端BiquadFilterCalculator.calculate("lowshelf")完全一致的算法 + float V = powf(10.0f, fabsf(gain_db_sat) / 20.0f); // 与Python端一致 + float K = tanf(pi * fc_sat / fs); // 与Python端一致:K = tan(π * Fc / Fs) + float norm = 0.0f; + + // 根据增益正负选择不同的计算方式,与Python端完全一致 + if (gain_db_sat >= 0) { + norm = 1.0f / (1.0f + sqrtf(2.0f) * K + K * K); + float b0 = (1.0f + sqrtf(2.0f * V) * K + V * K * K) * norm; + float b1 = 2.0f * (V * K * K - 1.0f) * norm; + float b2 = (1.0f - sqrtf(2.0f * V) * K + V * K * K) * norm; + float a1 = 2.0f * (K * K - 1.0f) * norm; + float a2 = (1.0f - sqrtf(2.0f) * K + K * K) * norm; + + // DPRINTF("b0: %f \n b1: %f \n b2: %f \n a1: %f \n a2: %f\n", b0, b1, b2, a1, a2); + + // 动态计算bshift + left_shift_t bshift = calculate_bshift(b0, b1, b2); + + // Store as fixed-point values + coeffs[0] = _float2fixed_assert(b0, Q_factor - bshift); + coeffs[1] = _float2fixed_assert(b1, Q_factor - bshift); + coeffs[2] = _float2fixed_assert(b2, Q_factor - bshift); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); + + return bshift; + } else { + norm = 1.0f / (1.0f + sqrtf(2.0f * V) * K + V * K * K); + float b0 = (1.0f + sqrtf(2.0f) * K + K * K) * norm; + float b1 = 2.0f * (K * K - 1.0f) * norm; + float b2 = (1.0f - sqrtf(2.0f) * K + K * K) * norm; + float a1 = 2.0f * (V * K * K - 1.0f) * norm; + float a2 = (1.0f - sqrtf(2.0f * V) * K + V * K * K) * norm; + + // DPRINTF("b0: %f \n b1: %f \n b2: %f \n a1: %f \n a2: %f\n", b0, b1, b2, a1, a2); + + // 动态计算bshift + left_shift_t bshift = calculate_bshift(b0, b1, b2); + + // Store as fixed-point values + coeffs[0] = _float2fixed_assert(b0, Q_factor - bshift); + coeffs[1] = _float2fixed_assert(b1, Q_factor - bshift); + coeffs[2] = _float2fixed_assert(b2, Q_factor - bshift); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); + + return bshift; + } +} + +left_shift_t eq_biquad_highshelf(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q, const float gain_db) { + float gain_db_sat = _check_gain(gain_db, 20.0f * db_2); + float fc_sat = _check_fc(fc, fs); + + // 使用与Python端BiquadFilterCalculator.calculate("highshelf")完全一致的算法 + float V = powf(10.0f, fabsf(gain_db_sat) / 20.0f); // 与Python端一致 + float K = tanf(pi * fc_sat / fs); // 与Python端一致:K = tan(π * Fc / Fs) + float norm = 0.0f; + + // 根据增益正负选择不同的计算方式,与Python端完全一致 + if (gain_db_sat >= 0) { + norm = 1.0f / (1.0f + sqrtf(2.0f) * K + K * K); + float b0 = (V + sqrtf(2.0f * V) * K + K * K) * norm; + float b1 = 2.0f * (K * K - V) * norm; + float b2 = (V - sqrtf(2.0f * V) * K + K * K) * norm; + float a1 = 2.0f * (K * K - 1.0f) * norm; + float a2 = (1.0f - sqrtf(2.0f) * K + K * K) * norm; + + // DPRINTF("b0: %f \n b1: %f \n b2: %f \n a1: %f \n a2: %f\n", b0, b1, b2, a1, a2); + + // 动态计算bshift + left_shift_t bshift = calculate_bshift(b0, b1, b2); + + // Store as fixed-point values + coeffs[0] = _float2fixed_assert(b0, Q_factor - bshift); + coeffs[1] = _float2fixed_assert(b1, Q_factor - bshift); + coeffs[2] = _float2fixed_assert(b2, Q_factor - bshift); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); + + return bshift; + } else { + norm = 1.0f / (V + sqrtf(2.0f * V) * K + K * K); + float b0 = (1.0f + sqrtf(2.0f) * K + K * K) * norm; + float b1 = 2.0f * (K * K - 1.0f) * norm; + float b2 = (1.0f - sqrtf(2.0f) * K + K * K) * norm; + float a1 = 2.0f * (K * K - V) * norm; + float a2 = (V - sqrtf(2.0f * V) * K + K * K) * norm; + + // DPRINTF("b0: %f \n b1: %f \n b2: %f \n a1: %f \n a2: %f\n", b0, b1, b2, a1, a2); + + // 动态计算bshift + left_shift_t bshift = calculate_bshift(b0, b1, b2); + + // Store as fixed-point values + coeffs[0] = _float2fixed_assert(b0, Q_factor - bshift); + coeffs[1] = _float2fixed_assert(b1, Q_factor - bshift); + coeffs[2] = _float2fixed_assert(b2, Q_factor - bshift); + coeffs[3] = _float2fixed_assert(-a1, Q_factor); + coeffs[4] = _float2fixed_assert(-a2, Q_factor); + + return bshift; + } +} + +/* Removed unused filters: bandpass, bandstop, notch, const_q, linkwitz */ diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.h new file mode 100644 index 0000000..88d199b --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/biquad_standalone.h @@ -0,0 +1,26 @@ +#ifndef BIQUAD_STANDALONE_H +#define BIQUAD_STANDALONE_H + +#include +#include "xmath/types.h" // 包含 lib_xcore_math 的类型定义 + +// 注意:q2_30, left_shift_t, right_shift_t 已在 lib_xcore_math 中定义,不需要重复定义 + +// 函数声明 +void eq_biquad_bypass(q2_30 coeffs[5]); +void eq_biquad_mute(q2_30 coeffs[5]); +left_shift_t eq_biquad_gain(q2_30 coeffs[5], const float gain_db); +void eq_biquad_lowpass(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q); +void eq_biquad_highpass(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q); +void eq_biquad_allpass(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q); +left_shift_t eq_biquad_peaking(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q, const float gain_db); +left_shift_t eq_biquad_lowshelf(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q, const float gain_db); +left_shift_t eq_biquad_highshelf(q2_30 coeffs[5], const float fc, const float fs, const float filter_Q, const float gain_db); + +// 辅助函数声明 - 重命名以避免与系统库冲突 +static inline int32_t _float2fixed(float x, int32_t q); +static inline int32_t _float2fixed_assert(float x, int32_t q); +static inline float biquad_sin(float x); // 重命名为 biquad_sin +static inline float biquad_cos(float x); // 重命名为 biquad_cos + +#endif // BIQUAD_STANDALONE_H diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/br_wrapper.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/br_wrapper.c new file mode 100644 index 0000000..1d8416e --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/br_wrapper.c @@ -0,0 +1,48 @@ +// Copyright 2024 XMOS LIMITED. +#define DEBUG_PRINT_ENABLE 0 +#include "debug_print.h" +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include +#include +#include "xc_ptr.h" + +#include "xua_conf.h" + +#include "share_buffer.h" + +void dnr_exchange_buffer(int32_t *data); +void buffer_exchange(chanend_t c_data, unsigned sampsFromUsbToAudio[], unsigned sampsFromAudioToUsb[]) { + chan_out_buf_word (c_data, sampsFromUsbToAudio, 2); + chan_out_buf_word (c_data, sampsFromAudioToUsb, 2); + chan_in_buf_word (c_data , sampsFromUsbToAudio, 2); + chan_in_buf_word (c_data , sampsFromAudioToUsb, 2); +} + + +void dsp_main (chanend_t c_data) { + int play_input[NUM_USB_CHAN_OUT]; + int play_output[I2S_CHANS_DAC]; + int mic_input[NUM_USB_CHAN_IN]; + int mic_output[I2S_CHANS_ADC]; + int count = 0; + unsigned ch[1] = {2}; + while (1) { + chan_in_buf_word (c_data , play_input, 2) ; + chan_in_buf_word (c_data , mic_input, 2) ; + chan_out_buf_word (c_data , play_output, I2S_CHANS_DAC); + chan_out_buf_word (c_data , mic_output, I2S_CHANS_ADC); + write_to_ring_buffer(0, play_input[0]); + write_to_ring_buffer(1, play_input[1]); + play_output[0] = read_from_ring_buffer(2); + play_output[1] = read_from_ring_buffer(3); + dnr_exchange_buffer(mic_input); + mic_output[0] = mic_input[0]; + mic_output[1] = mic_input[1]; + // dnr_exchange_buffer((int32_t *)mic_input, (int32_t *)mic_output); + } +} \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/c_dsp.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/c_dsp.h new file mode 100644 index 0000000..e033660 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/c_dsp.h @@ -0,0 +1,22 @@ +#ifndef _C_DSP_H_ +#define _C_DSP_H_ + +#include + +// #define AUDIO_T_16 + +#if defined(AUDIO_T_16) +typedef int16_t AUDIO_T; +#else // defined(AUDIO_T_32) +typedef int32_t AUDIO_T; +#endif + +#define FRAME_SIZE 8 //128 samples / 48kHz = 2.6ms + +void hid_update(unsigned char hid_data); + +// mic level +#define MIN_MIC_LEVEL 0 +#define MAX_MIC_LEVEL 9 +#define DEFAULT_MIC_LEVEL 5 +#endif \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/codec_ti3204.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/codec_ti3204.h new file mode 100644 index 0000000..3f4781f --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/codec_ti3204.h @@ -0,0 +1,77 @@ +// Copyright 2020-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef AIC3204_H_ +#define AIC3204_H_ + +// TLV320AIC3204 Device I2C Address +#define AIC3204_I2C_DEVICE_ADDR 0x18 + +#define CODEC_I2C_DEVICE_ADDR AIC3204_I2C_DEVICE_ADDR + +// TLV320AIC3204 Register Addresses +// Page 0 +#define AIC3204_PAGE_CTRL 0x00 // Register 0 - Page Control +#define AIC3204_SW_RST 0x01 // Register 1 - Software Reset +#define AIC3204_NDAC 0x0B // Register 11 - NDAC Divider Value +#define AIC3204_MDAC 0x0C // Register 12 - MDAC Divider Value +#define AIC3204_DOSR_MSB 0x0d // Register 14 - DOSR Divider Value (MS Byte) +#define AIC3204_DOSR_LSB 0x0e // Register 14 - DOSR Divider Value (LS Byte) +#define AIC3204_NADC 0x12 // Register 18 - NADC Divider Value +#define AIC3204_MADC 0x13 // Register 19 - MADC Divider Value +#define AIC3204_AOSR 0x14 // Register 20 - AOSR Divider Value +#define AIC3204_CLK_MUL 0x19 // Register 25 +#define AIC3204_CLK_DIV 0x1A // Register 26 +#define AIC3204_CODEC_IF 0x1B // Register 27 - CODEC Interface Control +#define AIC3204_D_OFFSET 0x1C // Register 28 - +#define AIC3204_LOOPBACK 0x1D // Register 29 - +#define AIC3204_BCLK_DIV 0x1E // Register 30 - -------- +#define AIC3204_CLK_MUL 0x1F // Register 31 - +#define AIC3204_BCLK_SEL 0x20 // Register 32 - 0:Primary; 1:Second +#define AIC3204_DAC_SIG_PROC 0x3C // Register 60 - DAC Sig Processing Block Control +#define AIC3204_ADC_SIG_PROC 0x3D // Register 61 - ADC Sig Processing Block Control +#define AIC3204_DAC_CH_SET1 0x3F // Register 63 - DAC Channel Setup 1 +#define AIC3204_DAC_CH_SET2 0x40 // Register 64 - DAC Channel Setup 2 +#define AIC3204_DACL_VOL_D 0x41 // Register 65 - DAC Left Digital Vol Control +#define AIC3204_DACR_VOL_D 0x42 // Register 66 - DAC Right Digital Vol Control +#define AIC3204_HEADSET_DET 0x43 // Register 67 - Headset Detection configuration +#define AIC3204_DRC_CON1 0x44 // Register 68 - DRC control reg1 +#define AIC3204_DRC_CON2 0x45 // Register 69 - DRC control reg2 +#define AIC3204_DRC_CON3 0x46 // Register 70 - DRC control reg3 +#define AIC3204_ADC_CH_SET 0x51 // Register 81 - ADC Channel Setup +#define AIC3204_ADC_FGA_MUTE 0x52 // Register 82 - ADC Fine Gain Adjust/Mute +#define AIC3204_ADC_CH1_SET 0x53 // Register 83 - ADC L_CH vol +#define AIC3204_ADC_CH2_SET 0x54 // Register 84 - ADC R_CH vol +#define AIC3204_ADC_PHA_SET 0x55 // Register 84 - ADC Phase Adjust Reg +//0x56~0x5c: L-CH agc control regs +//0x5e~0x65: R-CH agc control regs +///0x66~0x67: DC measurement regs +//0x68~0x6d: DC Measurement output regs + +// Page 1 +#define AIC3204_PAGE_SEL1 0x00 // Register 0 - Page Select Reg +#define AIC3204_PWR_CFG 0x01 // Register 1 - Power Config +#define AIC3204_LDO_CTRL 0x02 // Register 2 - LDO Control +#define AIC3204_PLAY_CFG1 0x03 // Register 3 - Playback Config 1 +#define AIC3204_PLAY_CFG2 0x04 // Register 4 - Playback Config 2 +#define AIC3204_OP_PWR_CTRL 0x09 // Register 9 - Output Driver Power Control +#define AIC3204_CM_CTRL 0x0A // Register 10 - Common Mode Control +#define AIC3204_HPL_ROUTE 0x0C // Register 12 - HPL Routing Select +#define AIC3204_HPR_ROUTE 0x0D // Register 13 - HPR Routing Select +#define AIC3204_HPL_GAIN 0x10 // Register 16 - HPL Driver Gain +#define AIC3204_HPR_GAIN 0x11 // Register 17 - HPR Driver Gain +#define AIC3204_HP_START 0x14 // Register 20 - Headphone Driver Startup +#define AIC3204_LPGA_P_ROUTE 0x34 // Register 52 - Left PGA Positive Input Route +#define AIC3204_LPGA_N_ROUTE 0x36 // Register 54 - Left PGA Negative Input Route +#define AIC3204_RPGA_P_ROUTE 0x37 // Register 55 - Right PGA Positive Input Route +#define AIC3204_RPGA_N_ROUTE 0x39 // Register 57 - Right PGA Negative Input Route +#define AIC3204_LPGA_VOL 0x3B // Register 59 - Left PGA Volume +#define AIC3204_RPGA_VOL 0x3C // Register 60 - Right PGA Volume +#define AIC3204_ADC_PTM 0x3D // Register 61 - ADC Power Tune Config +#define AIC3204_ADC_VOL 0x3E // Register 62 - ADC Analog Gain Control Flag +#define AIC3204_AN_IN_CHRG 0x47 // Register 71 - Analog Input Quick Charging Config +#define AIC3204_REF_STARTUP 0x7B // Register 123 - Reference Power Up Config + +int aic3204_init(void); + +#endif /* AIC3204_H_ */ \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/aizip_dnr.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/aizip_dnr.h new file mode 100644 index 0000000..d666da9 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/aizip_dnr.h @@ -0,0 +1,55 @@ +#ifndef AZP_H_ +#define AZP_H_ + + +#include "stdint.h" + +typedef enum +{ + errAZP_NoError = 0, // No error + errAZP_NotReady, // Not ready + errAZP_InvalidParam, // Invalid parameter + errAZP_InvalidLicense, // Invalid license + errAZP_BufferOverflow, // Buffer overflow + errAZP_BufferTooSmall, // Buffer too small +} AZP_STATUS; + +typedef struct +{ + int8_t *buffer1; // Pointer to the first buffer + int8_t *buffer2; // Pointer to the second buffer + int32_t buffer1_size; // Size of the first buffer + int32_t buffer2_size; // Size of the second buffer + int32_t sample_rate; // Sample rate, only support 16000Hz +} azp_dnr_param_t; + +/// @brief DNR initialization +/// @param param Configuration parameters +/// @return License status +/// @brief DNR initialization +/// @param param Configuration parameters +/// @return License status +AZP_STATUS AI_DNR_init(azp_dnr_param_t *param); + +/// @brief DNR processing +/// @param audio Q31 input and output +/// @return License status +AZP_STATUS AI_DNR_Processing(int32_t *audio); + +/// @brief Get version number +/// @return If not licensed, returns NULL +char *AI_DNR_Version(); + +/// @brief Get the size of the working buffers +/// @param buf_size Size of the first buffer +/// @param buf_size2 Size of the second buffer +void getBufferSize(int32_t *buf_size, int32_t *buf_size2); + +/// @brief Set the noise reduction depth +/// @param dB Range (-200 to 0 dB) +/// -200: Maximum noise reduction +/// 0: No noise reduction +void setNoisy_mix_factor(float dB); + + +#endif // AZP_H_ diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp.c new file mode 100644 index 0000000..1f2a3c2 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp.c @@ -0,0 +1,52 @@ +// Copyright 2016-2021 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#if UART_DEBUG || DEBUG_MEMORY_LOG_ENABLED +#define DEBUG_PRINT_ENABLE 1 +#endif + +#include +#include +#include "ringbuf.h" +#include +#include +#include "dnr_dsp_buf.h" +#include "xc_ptr.h" +#include "debug_print.h" + +unsigned g_dsp_eof = 0; +void dnr_exchange_buffer(int32_t *data) { + static unsigned buff_index = 0; + write_to_sample_in_buf(data[1], buff_index, 0); + read_from_sample_out_buf(&data[1],buff_index, 0); + data[0] = data[1]; + + buff_index++; +#if DNR_11MS + if (buff_index == 128) + { +#else + if (buff_index == 512) + { +#endif + buf_slot_rotate(); + buff_index = 0; + SET_SHARED_GLOBAL(g_dsp_eof, 1); + } +} + + +void Aizip_DNR_init(void); + +void dnr_dsp_proc_task(void) +{ + Aizip_DNR_init(); + while(1) { + unsigned dsp_eof = 0; + GET_SHARED_GLOBAL(dsp_eof, g_dsp_eof); + if (dsp_eof) { + SET_SHARED_GLOBAL(g_dsp_eof, 0); + start_dsp_processing(); + } + } +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.c new file mode 100644 index 0000000..658dd12 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.c @@ -0,0 +1,95 @@ +// Copyright 2011-2023 XMOS LIMITED. +// +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#if UART_DEBUG || DEBUG_MEMORY_LOG_ENABLED +#define DEBUG_PRINT_ENABLE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include "dnr_dsp_buf.h" +#include "aizip_dnr.h" +#include "debug_print.h" +#include "flash.h" + +//for AGC +//static agc_stage_ctx_t DWORD_ALIGNED agc_stage_state = {}; + +static unsigned __attribute__((aligned (4))) dsp_dnr_frame_buf[BUF_SLOT_NUM][DSP_CH_NUM][DNR_DSP_FRAME_SIZE]; + +unsigned sample_in_buf_slot=0; +unsigned processing_buf_slot=2; +unsigned sample_out_buf_slot=1; + + +void dsp_buf_init() +{ + sample_in_buf_slot=0; + sample_out_buf_slot=1; + processing_buf_slot=2; +} + +static void buf_slot_inc(unsigned* buf_slot_input) +{ + if(*buf_slot_input == BUF_SLOT_NUM-1){ + *buf_slot_input = 0; + } + else{ + *buf_slot_input = *buf_slot_input+1; + } +} + +void buf_slot_rotate() +{ + buf_slot_inc(&sample_in_buf_slot); + buf_slot_inc(&sample_out_buf_slot); + buf_slot_inc(&processing_buf_slot); +} + +void write_to_sample_in_buf(unsigned data, unsigned index, unsigned ch) +{ + dsp_dnr_frame_buf[sample_in_buf_slot][ch][index]=data; +} + +void read_from_sample_out_buf(unsigned* read_data, unsigned index, unsigned ch) +{ + *read_data = dsp_dnr_frame_buf[sample_out_buf_slot][ch][index]; +} +#if DNR_11MS +int8_t buffer[4208] __attribute__((aligned(4))); +int8_t buffer2[31136] __attribute__((aligned(4))); +#else +int8_t buffer[21416] __attribute__((aligned(4))); +int8_t buffer2[31712] __attribute__((aligned(4))); +#endif +azp_dnr_param_t pram = { + .buffer1 = buffer, + .buffer1_size = sizeof(buffer), + .buffer2 = buffer2, + .buffer2_size = sizeof(buffer2), + .sample_rate = 48000}; + +unsigned g_dnr_init_flag = 0; +void Aizip_DNR_init(void) +{ + delay_microseconds(10000); + // setFlashPortPins(XS1_PORT_1B,XS1_PORT_1C,XS1_PORT_4B, XS1_CLKBLK_3); + + int sta = AI_DNR_init(&pram); + g_dnr_init_flag = 1; + debug_printf("AI_DNR_init status %d\n", sta); +} + +void start_dsp_processing(void) +{ + float level = -200; + + setNoisy_mix_factor(level); + + AI_DNR_Processing((int32_t *)&dsp_dnr_frame_buf[processing_buf_slot][0][0]); +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.h new file mode 100644 index 0000000..61d2edd --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/dnr_dsp_buf.h @@ -0,0 +1,39 @@ +// Copyright 2011-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef __DNR_DSP_BUF_H__ +#define __DNR_DSP_BUF_H__ + +#include +#include +#include +#include +#include +#include +#include +#if DNR_11MS +#define DNR_DSP_FRAME_SIZE 128 +#else +#define DNR_DSP_FRAME_SIZE 512 +#endif +#define BUF_SLOT_NUM 3 //one for input, one for output, one for processing +#define DSP_CH_NUM 2 + +#if __XC__ +extern "C" { +#endif + +void dnr_set_chan(chanend_t dnr_tile0_c, chanend_t dnr_tile1_c); + +void dsp_buf_init(); +void buf_slot_rotate(); +void write_to_sample_in_buf(unsigned data, unsigned index, unsigned ch); +void read_from_sample_out_buf(unsigned* read_data, unsigned index, unsigned ch); + +void start_dsp_processing(void); +void Aizip_DNR_init(void); + +#if __XC__ +} //extern c +#endif +#endif //RINGBUFFER_H + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/flash.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/flash.h new file mode 100644 index 0000000..9679c5b --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/flash.h @@ -0,0 +1,5 @@ +#ifndef __FLASH_H__ +#define __FLASH_H__ +#include "stdint.h" +void setFlashPortPins(uint32_t cs,uint32_t sclk,uint32_t sio,uint32_t clkblk); +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/ringbuf.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/ringbuf.h new file mode 100644 index 0000000..943584f --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr/ringbuf.h @@ -0,0 +1,45 @@ +// Copyright 2011-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef __RINGBUF_H__ +#define __RINGBUF_H__ + +#include +#include +#include +#include +#include + +#if __XC__ +extern "C" { +#endif + +#define FRAME_SIZE 120 //101623 - from 256 to 120 + +#define RF_CHAN_NUM 2 //stereo. Multichannel uses NUM_USB_CHAN_OUT + // +#define RB_SIZE FRAME_SIZE*RF_CHAN_NUM*16 //2 channel, 32-bit, 16 frame +#define RB_MASK (RB_SIZE - 1) + + +extern uint32_t r_buffer[RB_SIZE]; + +typedef struct { + uint32_t * buf_addr; + int next_empty; + int next_data; + int full; + int empty; +} ring_buffer_t; + +void rb_init(void); +void rb_put_word(uint32_t data); +uint32_t rb_get_word(void); +int rb_is_full(void); +int rb_is_empty(void); + +#if __XC__ +} //extern c +#endif + +#endif //RINGBUFFER_H + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr_dsp_buf.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr_dsp_buf.h new file mode 100644 index 0000000..333973f --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dnr_dsp_buf.h @@ -0,0 +1,69 @@ +// Copyright 2011-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef __DNR_DSP_BUF_H__ +#define __DNR_DSP_BUF_H__ + +#include +#include +#include +#include +#include +#include "c_dsp.h" + +#define DNR_50MS 1 +//#define DEBUG_DNR +#if (DNR_50MS == 1) +#define DNR_DSP_FRAME_SIZE 512 +#else +#define DNR_DSP_FRAME_SIZE 128 +#endif + +#define BUF_SLOT_NUM 3 //one for input, one for output, one for processing +#define DSP_CH_NUM 2 + +#if __XC__ +extern "C" { +#endif + + + +void dsp_buf_init(); +void buf_slot_rotate(); +void write_to_sample_in_buf(unsigned data, unsigned index, unsigned ch); +void read_from_sample_out_buf(unsigned* read_data, unsigned index, unsigned ch); +void start_dsp_processing(unsigned samFreq); + + +#if __XC__ +} //extern c +#endif + +#define AGC_ENABLED 1 +#define DNR_ENABLED 1 + +#define KEY_MIC_VOL_DN 0x08 +#define KEY_MUTE 0x10 +#define KEY_MIC_VOL_UP 0x20 +#define KEY_PLAY_VOL_DN 0x40 +#define KEY_PLAY_VOL_UP 0x80 +#define KEY_BITS (KEY_MIC_VOL_DN | KEY_MUTE | KEY_MIC_VOL_UP | KEY_PLAY_VOL_DN | KEY_PLAY_VOL_UP) + +#if defined (PHATEN_GSV2) +#define DEBOUNCE_TIMEOUT 10 // in units of 2.6ms; 128 samples/frame +#else +#error "Please define DEBOUNCE_TIMEOUT" +#endif + +#define MUTED_MIC 0x5A +#define UNMUTED_MIC 0xA5 + +// 改动原因:添加HID控制命令宏定义,用于button_task中的按键处理(从dnr_dsp_buf.c移到这里,以便dsp.c可以访问) +#define HID_CONTROL_PLAYPAUSE 0x01 +#define HID_CONTROL_NEXT 0x02 +#define HID_CONTROL_PREV 0x04 +#define HID_CONTROL_VOLUP 0x00 +#define HID_CONTROL_VOLDN 0x01 +#define HID_CONTROL_MUTE 0x20 + +#endif //RINGBUFFER_H + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp.c new file mode 100644 index 0000000..00eb82b --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp.c @@ -0,0 +1,1329 @@ +#define DEBUG_PRINT_ENABLE 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dsp_user_impl.h" +#include "DSBuild.h" +#include "filter.h" + +#include "xua_conf.h" +#include +#include "audio_ex3d_control.h" +#include "c_dsp.h" +#include +#include "xua_hid_report.h" +#include "xc_ptr.h" +#include "dnr_dsp_buf.h" +#include "roleswitchflag.h" +#include "debug_print.h" +// 改动原因:添加user_func.h头文件,用于调用save_value和load_value函数保存/加载模式到/从flash +extern void save_value(unsigned char *path, unsigned char value); +extern unsigned char load_value(unsigned char *path); +#if EQ_EN + +#include "eq_flash_storage.h" +extern unsigned g_request_eq_mode, g_new_eq_mode; +extern unsigned g_force_request_eq_mode_change, g_force_eq_mode_change; +#endif + +extern void device_reboot(void); +unsigned g_ex3d_key_verified = 0; +int32_t sys_vol = 0; +enum { OS_WIN = 1, OS_OTHERS = 2 }; +unsigned g_mic_vol_cmd_pending = 0; + +chanend_t uc_ex3d_to_ubm, uc_eq_data; + +#if (STEREO_8K == 1) || (STEREO_2K == 1) +#define UBM_TO_EX3D_CHANS (2) +#define EX3D_TO_UBM_CHANS (2) +#else +#define UBM_TO_EX3D_CHANS (NUM_USB_CHAN_OUT) +#define EX3D_TO_UBM_CHANS (2) +#endif + +// tile 1, mic level +unsigned g_mic_level_t1; + +// tile 1, 3d_on_off +#define LED_ON 0 +#define LED_OFF 1 +#define S3D_ON LED_ON +#define S3D_OFF LED_OFF + +#if defined(IR_SWITCHING_MODE) +enum {IR_OFF=0, IR_GAME=1, IR_MUSIC=2, IR_MOVIE=3, IR_7_1_GAME=4, IR_7_1_MUSIC=5, IR_7_1_MOVIE=6}; +unsigned g_3d_on_off_t1 = IR_OFF; // Default 3D off +#else +enum {A3D_OFF=0, A3D_VON=1, A3D_ON=2}; +unsigned g_3d_on_off_t1 = A3D_OFF; // Default 3D off +#endif +#define DNR_ON 1 +#define DNR_OFF 0 +unsigned g_dnr_on_off_t1 = DNR_OFF; // Default DNR off +#define MUTE_ON 1 +#define MUTE_OFF 0 +unsigned g_mute_on_off_t1 = MUTE_ON; + +unsigned g_mute_on_off_t0 = MUTE_OFF; + +unsigned g_game_mode = 0; + +// On UserBufferManagement Tile (1) +static unsigned frame_index = 0; +//static unsigned dnr_frame_idx = 0; +static int32_t ubm_egress[UBM_TO_EX3D_CHANS][DSP_BLOCK_LENGTH]; //leaving ubm +static int32_t ubm_ingress[EX3D_TO_UBM_CHANS][DSP_BLOCK_LENGTH]; //entering ubm +#if AIZIP_DNR == 1 +static int32_t ubm_micIn[DSP_CH_NUM][DNR_DSP_FRAME_SIZE]; //leaving ubm +static int32_t ubm_micOut[DSP_CH_NUM][DNR_DSP_FRAME_SIZE]; //entering ubm +#endif +chanend_t uc_dsp_to_dnr_t1; +//chanend_t uc_dsp_to_dnr_t0; +chanend_t uc_key_to_ubm_t0; +volatile EXTERN DS_BOOL m_hThread; +void SetEqDataChan (chanend_t c) { + uc_eq_data = c; +} +void key_receiver(chanend_t c) +{ + debug_printf("===> key_receiver\n"); + // tile0 + // load the license key + // This function must be called before audio_ex3d_activate_key. + audio_ex3d_load_key(c); + SET_SHARED_GLOBAL(g_ex3d_key_verified, 1); +} + +void key_sender(chanend_t c) +{ + debug_printf("===> key_sender\n"); + + // tile1 + // activate the license key + // if the keys match, the function returns 0. + int ret = audio_ex3d_activate_key(c); + if (ret == NO_ERR) { + debug_printf("License keys match.\n"); + } else { + debug_printf("License keys do not match. Error code: %d\n", ret); + } +} + +void UserBufferManagementInit(unsigned sampFreq) +{ + (void)sampFreq; + memset(ubm_ingress, 0, sizeof(ubm_ingress)); + memset(ubm_egress, 0, sizeof(ubm_egress)); +#if AIZIP_DNR == 1 + memset(ubm_micIn, 0, sizeof(ubm_micIn)); + memset(ubm_micOut, 0, sizeof(ubm_micOut)); +#endif + assert(uc_ex3d_to_ubm); + while( !m_hThread ){} + debug_printf("[UserBufferManagementInit] samfreq:%d\n\r", sampFreq); + int ret = audio_ex3d_change_parameter(NUM_USB_CHAN_OUT, NUM_USB_CHAN_OUT * FRAME_SIZE * sizeof(AUDIO_T), sampFreq, sizeof(AUDIO_T)); + if(ret != NO_ERR) { + debug_printf("audio_ex3d_change_parameter() error:%d\n\r", ret); + } else { + debug_printf("audio_ex3d_change_parameter() success\n\r"); + } +} +float fLevel[NUM_USB_CHAN_OUT] = {0,}; +enum {UBM_A3D_OFF=0, UBM_A3D_VON=1, UBM_A3D_ON=2}; +extern void buffer_exchange(chanend_t c_data, unsigned sampsFromUsbToAudio[], unsigned sampsFromAudioToUsb[]); +extern unsigned int is_eq_disabled(void); +void UserBufferManagement(unsigned sampsFromUsbToAudio[], unsigned sampsFromAudioToUsb[]) +{ + uint32_t is_3d; + uint32_t is_dnr; + uint32_t is_mute; + +#if defined(SPATIAL_DRAMA) || defined(SPATIAL_GAME) || defined(SPATIAL_MOVIE) || defined(SPATIAL_MUSIC) + // 6ch version + unsigned tmp[8]={0,0,0,0,0,0,0,0}, downmix[8]={0,0,0,0,0,0,0,0}; +#endif + + GET_SHARED_GLOBAL(is_3d, g_3d_on_off_t1); + GET_SHARED_GLOBAL(is_dnr, g_dnr_on_off_t1); + GET_SHARED_GLOBAL(is_mute, g_mute_on_off_t1); +#if (HID_CONTROLS > 0) + AUDIO_T absVal, maxVal; +#endif + //Frame the samples going from the usb to the DAC into frames of 8 + for(int ch=0;ch 0) + maxVal = 0; + absVal = ubm_egress[ch][frame_index]; + if(absVal < 0) absVal = -absVal; + if(maxVal < absVal) maxVal = absVal; +#if defined(AUDIO_T_16) + fLevel[ch] = (float)maxVal / 32768; +#else + fLevel[ch] = (float)maxVal / 2147483648; +#endif +#endif + } + +#if defined(SPATIAL_DRAMA) || defined(SPATIAL_GAME) || defined(SPATIAL_MOVIE) || defined(SPATIAL_MUSIC) || defined(IR_SWITCHING_MODE) + // 6ch version + // C and LFE -3dB mixed to L and R. + // L = L + C*-3dB + LFE*-3dB + // R = R + C*-3dB + LFE*-3dB + + int32_t input_L = (int32_t)sampsFromUsbToAudio[0]; + int32_t input_R = (int32_t)sampsFromUsbToAudio[1]; + int32_t input_C = (int32_t)sampsFromUsbToAudio[2]; + int32_t input_LFE = (int32_t)sampsFromUsbToAudio[3]; + + // -3dB Gain = 10^(-3/20) ~= 0.707946 + // Using 0.70710678 (1/sqrt(2)) approximation for -3dB + // Q31 factor: 0.70710678 * 2^31 = 1518500250 + const int32_t gain_minus_3db = 1518500250; + + int32_t C_weighted = (int32_t)(((int64_t)input_C * gain_minus_3db) >> 31); + int32_t LFE_weighted = (int32_t)(((int64_t)input_LFE * gain_minus_3db) >> 31); + + int64_t sum_L = (int64_t)input_L + C_weighted + LFE_weighted; + int64_t sum_R = (int64_t)input_R + C_weighted + LFE_weighted; + + // Saturation logic + if (sum_L > 2147483647) sum_L = 2147483647; + else if (sum_L < -2147483648) sum_L = -2147483648; + + if (sum_R > 2147483647) sum_R = 2147483647; + else if (sum_R < -2147483648) sum_R = -2147483648; + + // only for Stereo game, Stereo music and Stereo movie mode + if ((is_3d > IR_OFF) && (is_3d < IR_7_1_GAME)) { + // stereo mode + ubm_egress[0][frame_index] = 0; + ubm_egress[1][frame_index] = 0; + ubm_egress[2][frame_index] = 0; + ubm_egress[3][frame_index] = 0; + ubm_egress[4][frame_index] = 0; + ubm_egress[5][frame_index] = 0; + ubm_egress[6][frame_index] = (int32_t)sum_L; //SL + ubm_egress[7][frame_index] = (int32_t)sum_R; //SR + } else { + ubm_egress[0][frame_index] = (int32_t)sum_L; + ubm_egress[1][frame_index] = (int32_t)sum_R; + } + + // Zero out C (2) and LFE (3) since they are mixed into L/R + ubm_egress[2][frame_index] = 0; + ubm_egress[3][frame_index] = 0; +#endif +#if (NUM_USB_CHAN_IN == 2) + +#if AIZIP_DNR == 1 + //exchange mic in and mic out data + ubm_micIn[0][frame_index] = sampsFromAudioToUsb[1]; + ubm_micIn[1][frame_index] = sampsFromAudioToUsb[1]; + if (is_dnr == DNR_ON) { // extract the DNR data when it is on + sampsFromAudioToUsb[0] = ubm_micOut[0][frame_index]; + sampsFromAudioToUsb[1] = ubm_micOut[0][frame_index]; + } else { + if (is_mute == MUTE_ON) { + sampsFromAudioToUsb[0] = 0; + sampsFromAudioToUsb[1] = 0; + } else { + // DNR off + sampsFromAudioToUsb[0] = sampsFromAudioToUsb[1]; + } + } +#endif +#endif + + for(int ch=0;ch> 16) % 181; + EX3D_HAngle[ChNum] = (angle & 0xffff) % 360; + EX3DAudio_SetAngle(ChNum+1, EX3D_VAngle[ChNum], EX3D_HAngle[ChNum], true); + debug_printf("[%s] Set angle ch%d-%d,%d\n",__FUNCTION__, ChNum+1, EX3D_VAngle[ChNum], EX3D_HAngle[ChNum]); + } else { + uint8_t bApply = false; + debug_printf("[%s] Set angle(V,H):",__FUNCTION__); + ChNum = NUM_USB_CHAN_OUT; + for(int i=0; i> 16) % 181; + EX3D_HAngle[i] = (angle & 0xffff) % 360; + if(i == (ChNum - 1)) bApply = true; + EX3DAudio_SetAngle(i+1, EX3D_VAngle[i], EX3D_HAngle[i], bApply); + debug_printf(" ch%d-%d,%d,", i+1, EX3D_VAngle[i], EX3D_HAngle[i]); + } + debug_printf("\n"); + } + debug_printf("Set CMD_ANGLE : %u\r\n", pRcvBuf[1]); + } else { + debug_printf("Get CMD_ANGLE : %u\r\n", pRcvBuf[1]); + + uint32_t ChNum = pRcvBuf[1]; + if (ChNum < NUM_USB_CHAN_OUT) { + angle = EX3D_VAngle[ChNum] << 16; + angle |= EX3D_HAngle[ChNum]; + pSendBuf[idx] = angle; + } else { + ChNum = NUM_USB_CHAN_OUT; + for (int i = 0; i < ChNum; i++) { + angle = EX3D_VAngle[i] << 16; + angle |= EX3D_HAngle[i]; + debug_printf("%08x, ", angle); + pSendBuf[idx++] = angle; + } + } + debug_printf("\r\n"); + } + break; + + case CMD_MUTE: + if (CMD_SET(CMD_MUTE) == command_code) { + uint32_t ChNum = pRcvBuf[1]; + if (ChNum < NUM_USB_CHAN_OUT) { + EX3D_Mute[ChNum] = pRcvBuf[2]; + EX3DAudio_SetMute(ChNum+1, EX3D_Mute[ChNum]); + debug_printf("[%s] Set mute ch%d-%d,%d\n",__FUNCTION__, ChNum+1, EX3D_Mute[ChNum]); + } else { + ChNum = NUM_USB_CHAN_OUT; + debug_printf("[%s] Set mute:",__FUNCTION__); + for(int i=0; i IR_GAME/IR_7_1_GAME/IR_7_1_MOVIE + const char * sfIdxName = ""; + switch (sfIdx) { + case 0: sfIdxName = "STEREO GAME"; break; + case 1: sfIdxName = "SPATIAL GAME"; break; + case 2: sfIdxName = "SPATIAL MOVIE"; break; + default: break; + } +// if (cur_mode == IR_GAME) { +// sfIdxName = spatial_game; +// } else { +// if (cur_mode == IR_MUSIC) { +// sfIdxName = spatial_music; +// } else { +// sfIdxName = spatial_movie; +// } +// } +#else + const char * sfIdxName = "EXTREME"; +#endif + int32_t NameLen = strlen(sfIdxName); + pSendBuf[idx++] = NameLen; + memcpy(&pSendBuf[idx], sfIdxName, NameLen); + //debug_printf("%s\r\n", sfDispName[sfIdx]); + } else + pSendBuf[idx] = 0xFFFFFFFF; + } + break; + + case CMD_SOUND_FIELD: + if (CMD_SET(CMD_SOUND_FIELD) == command_code) { + int32_t sfIdx = pRcvBuf[1]; + debug_printf("Set CMD_SOUND_FIELD : "); + // 改动原因: + // - host UI 现在只暴露3个“带算法声场”(GAME/MUSIC/MOVIE 三选一)。 + // - 算法开关通过 CMD_ONOFF/其它接口单独控制;CMD_SOUND_FIELD 不能隐式把算法打开/关闭。 + // 因此:CMD_SOUND_FIELD 只接受 0~2(对应 GAME/MUSIC/MOVIE),并且在算法OFF(IR_OFF)时只缓存选择、不改共享变量。 + if (sfIdx >= 0 && sfIdx < 3) { + unsigned cur_mode; + GET_SHARED_GLOBAL(cur_mode, g_3d_on_off_t1); + + last_sf_idx_0_2 = (unsigned)sfIdx; + + if (cur_mode == IR_OFF) { + // 改动原因:算法已关闭时不写 g_3d_on_off_t1,避免 event_polling 误判“主机改变声场”而自动打开算法 + debug_printf("algorithm OFF, cache sound field idx=%d only (no implicit on/off)\n", sfIdx); + } else { + // 改动原因:算法已开启时才更新声场;因EX3D_SF_NUM=3不再区分6个族 + unsigned new_mode; + if (sfIdx == 1) new_mode = IR_7_1_GAME; + else if (sfIdx == 2) new_mode = IR_7_1_MOVIE; + else new_mode = IR_GAME; + SET_SHARED_GLOBAL(g_3d_on_off_t1, new_mode); + debug_printf("set sound field idx=%d -> g_3d_on_off_t1=%d (no implicit on/off)\n", sfIdx, new_mode); + } + } else { + // 改动原因:EX3D_SF_NUM=3后只支持0~2,超过范围返回错误 + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + // 改动原因:返回当前声场索引(0~2: GAME/MUSIC/MOVIE);若算法OFF则返回缓存值,避免依赖 g_3d_on_off_t1=IR_OFF + unsigned cur_mode; + GET_SHARED_GLOBAL(cur_mode, g_3d_on_off_t1); + if (cur_mode == IR_OFF) { + pSendBuf[idx] = last_sf_idx_0_2; + } else if (cur_mode == IR_7_1_MOVIE) { + pSendBuf[idx] = 2; + } else if (cur_mode == IR_7_1_GAME) { + pSendBuf[idx] = 1; + } else { + pSendBuf[idx] = 0; + } + } + break; + + case CMD_LEVEL: + { +#if 1 //TODO LISTS + uint32_t ChNum = pRcvBuf[1]; + uint32_t *pLevel = (uint32_t *)fLevel; + + if(ChNum > NUM_USB_CHAN_OUT) ChNum = NUM_USB_CHAN_OUT; + + // debug_printf("Get CMD_LEVEL : %d, %d\r\n", ChNum, NUM_USB_CHAN_OUT); + for (int i = 0; i < ChNum; i++) { + pSendBuf[idx++] = pLevel[i]; + // debug_printf("%08x, ", pLevel[i] ); + } + // debug_printf("\r\n"); +#endif + } + break; + + case CMD_ON_GAIN: + if (CMD_SET(CMD_ON_GAIN) == command_code) { + int32_t idBGain; + memcpy(&idBGain, &pRcvBuf[1], 4); + debug_printf("Set CMD_ON_GAIN : %d\r\n", idBGain); + if((-100 <= idBGain) && (idBGain <= 0)) { + Ex3dOnGain = idBGain; + EX3DAudio_SetOnGain(Ex3dOnGain); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_ON_GAIN : %d\r\n", Ex3dOnGain); + memcpy(&pSendBuf[idx], &Ex3dOnGain, 4); + } + break; + + case CMD_OFF_GAIN: + if (CMD_SET(CMD_OFF_GAIN) == command_code) { + int32_t idBGain; + memcpy(&idBGain, &pRcvBuf[1], 4); + debug_printf("Set CMD_OFF_GAIN : %d\r\n", idBGain); + if((-100 <= idBGain) && (idBGain <= 0)) { + Ex3dOffGain = idBGain; + EX3DAudio_SetOffGain(Ex3dOffGain); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_OFF_GAIN : %d\r\n", Ex3dOffGain); + memcpy(&pSendBuf[idx], &Ex3dOffGain, 4); + } + break; + + case CMD_LFE: + if (CMD_SET(CMD_LFE) == command_code) { + int32_t idBGain; + memcpy(&idBGain, &pRcvBuf[1], 4); + debug_printf("Set CMD_LFE : %d\r\n", idBGain); + if((-100 <= idBGain) && (idBGain <= 0)) { + Ex3dLfeGain = idBGain; + EX3DAudio_SetLFE(true, Ex3dLfeGain); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_LFE : %d\r\n", Ex3dLfeGain); + memcpy(&pSendBuf[idx], &Ex3dLfeGain, 4); + } + break; + + case CMD_LMT_THRESHOLD: + if (CMD_SET(CMD_LMT_THRESHOLD) == command_code) { + int32_t threshold; + memcpy(&threshold, &pRcvBuf[1], 4); + debug_printf("Set CMD_LMT_THRESHOLD : %d\r\n", threshold); + if((-35 <= threshold) && (threshold <= 0)) { + Ex3dLimiterThreshold = threshold; + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + + threshold = Ex3dLimiterThreshold + (sys_vol + Ex3dOnGain + Ex3dExpandGain); + + if((-35 <= threshold) && (threshold <= 0)) { + EX3DAudio_SetLimiterThreshold(threshold); + } + + } else { // Get + debug_printf("Get CMD_LMT_THRESHOLD : %d\r\n", Ex3dLimiterThreshold); + memcpy(&pSendBuf[idx], &Ex3dLimiterThreshold, 4); + } + break; + + case CMD_LMT_ATTACKK_TIME: + if (CMD_SET(CMD_LMT_ATTACKK_TIME) == command_code) { + unsigned AttackTime; + memcpy(&AttackTime, &pRcvBuf[1], 4); + debug_printf("Set CMD_LMT_ATTACKK_TIME : %d\r\n", AttackTime); + if(AttackTime <= 10) { + Ex3dLimiterAttackTime = AttackTime; + EX3DAudio_SetLimiterAttackTime(Ex3dLimiterAttackTime); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_LMT_ATTACKK_TIME : %d\r\n", Ex3dLimiterAttackTime); + memcpy(&pSendBuf[idx], &Ex3dLimiterAttackTime, 4); + } + break; + + case CMD_LMT_RELEASE_TIME: + if (CMD_SET(CMD_LMT_RELEASE_TIME) == command_code) { + unsigned ReleaseTime; + memcpy(&ReleaseTime, &pRcvBuf[1], 4); + debug_printf("Set CMD_LMT_RELEASE_TIME : %d\r\n", ReleaseTime); + if(ReleaseTime <= 100) { + Ex3dLimiterReleaseTime = ReleaseTime; + EX3DAudio_SetLimiterReleaseTime(Ex3dLimiterReleaseTime); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_LMT_RELEASE_TIME : %d\r\n", Ex3dLimiterReleaseTime); + memcpy(&pSendBuf[idx], &Ex3dLimiterReleaseTime, 4); + } + break; + + // dhlee++ + + case CMD_EXPAND_GAIN: + if (CMD_SET(CMD_EXPAND_GAIN) == command_code) { + int32_t idBGain; + memcpy(&idBGain, &pRcvBuf[1], 4); + debug_printf("Set CMD_EXPAND_GAIN : %d\r\n", idBGain); + if((0 <= idBGain) && (idBGain <= 20)) { + Ex3dExpandGain = idBGain; + EX3DAudio_SetExpandGain(Ex3dExpandGain); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_EXPAND_GAIN : %d\r\n", Ex3dExpandGain); + memcpy(&pSendBuf[idx], &Ex3dExpandGain, 4); + } + break; + + case CMD_REDUCE_GAIN: + if (CMD_SET(CMD_REDUCE_GAIN) == command_code) { + int32_t idBGain; + memcpy(&idBGain, &pRcvBuf[1], 4); + debug_printf("Set CMD_REDUCE_GAIN : %d\r\n", idBGain); + if((-20 <= idBGain) && (idBGain <= 0)) { + Ex3dReduceGain = idBGain; + EX3DAudio_SetReduceGain(Ex3dReduceGain); + } else { + pSendBuf[idx] = 0xFFFFFFFF; + } + } else { // Get + debug_printf("Get CMD_REDUCE_GAIN : %d\r\n", Ex3dReduceGain); + memcpy(&pSendBuf[idx], &Ex3dReduceGain, 4); + } + break; + + + default: + break; + } +} + +void LevelMeter(AUDIO_T AudioData[NUM_USB_CHAN_OUT][FRAME_SIZE]) +{ + unsigned ch; + AUDIO_T absVal, maxVal; + #if defined(DETECT_CHANNEL) + AUDIO_T DetectAudioData; + unsigned MaskAudioData = 0; + unsigned Mask = 0x01; + static unsigned Mode = 0; + #endif + + for(ch=0; ch 3) current_mode_local = 0; + debug_printf("hid_button_task received audio_mode: %d\n", current_mode_local); + + + if (current_mode_local == 0) { + // 无音效模式:灯灭,算法关闭 + audio_ex3d_onoff(0); +#if IR_SWITCHING_MODE + is_3d_on = IR_OFF; +#else + is_3d_on = A3D_OFF; +#endif + debug_printf("Mode 0 (NO_EFFECT): algorithm OFF\n"); + } else if (current_mode_local == 1) { + // STEREO_2K模式:红灯,算法开启 + audio_ex3d_onoff(1); +#if IR_SWITCHING_MODE + is_3d_on = IR_GAME; +#else + is_3d_on = A3D_ON; +#endif + debug_printf("Mode 1 (STEREO_2K): algorithm ON\n"); + } else if (current_mode_local == 2) { + // SPATIAL_GAME模式:蓝灯,算法开启 + audio_ex3d_onoff(1); +#if IR_SWITCHING_MODE + is_3d_on = IR_7_1_GAME; +#else + is_3d_on = A3D_ON; +#endif + debug_printf("Mode 2 (SPATIAL_GAME): algorithm ON\n"); + } else if (current_mode_local == 3) { + // SPATIAL_MOVIE模式:蓝灯闪烁,算法开启 + audio_ex3d_onoff(1); +#if IR_SWITCHING_MODE + is_3d_on = IR_7_1_MOVIE; +#else + is_3d_on = A3D_ON; +#endif + debug_printf("Mode 3 (SPATIAL_MOVIE): algorithm ON\n"); + } + SET_SHARED_GLOBAL(g_3d_on_off_t1, is_3d_on); + } else { + // Mic 静音状态命令 + switch (tmp) { + case MUTED_MIC: + SET_SHARED_GLOBAL(g_mute_on_off_t1, MUTE_ON); + debug_printf("MUTED_MIC\n"); + break; + + case UNMUTED_MIC: + SET_SHARED_GLOBAL(g_mute_on_off_t1, MUTE_OFF); + debug_printf("UNMUTED_MIC\n"); + break; + + default: + debug_printf("Unknown mic level command: 0x%02X\n", tmp); + break; + } + } + } + continue; + + event_hid: + { + //uint8_t junk=chan_in_byte(c_hid); + unsigned length = chan_in_byte(c_hidRcvData); + for(int i=0; i 3) { + ir_mode -= 4; + } else { + ir_mode -= 1; + } + } + for(int ch=0;ch +#include +#include +#include +#include "xmath/filter.h" +#include "stdio.h" +#include "xc_ptr.h" +#include "eq.h" +#include "share_buffer.h" +#include "debug_print.h" +#include "eq_flash_storage.h" + +#if 0 +#define DPRINTF(...) printf(__VA_ARGS__) +#else +#define DPRINTF(...) +#endif + +#define LEN_FADE_ (2024) + + // EQ Flash存储初始化标志 +int eq_flash_initialized = 0; +extern unsigned int is_mode_changed(void); +extern void change_eq_mode(unsigned sample_freq); +extern void clear_eq_status(unsigned sample_freq, unsigned ch_no); +extern unsigned int g_request_eq_mode; +extern unsigned int g_eq_enable; // 改动原因:添加EQ使能开关外部声明,用于初始化时检查EQ状态 +extern unsigned int g_saved_eq_mode; // 改动原因:添加保存模式外部声明,用于初始化时设置保存的模式 +extern void debug_print_eq_params(unsigned sample_freq); +extern uint32_t get_reference_time(void); +extern void update_eq_post_gain(void); +extern unsigned g_windows_detect_done; + +#pragma unsafe arrays +void dsp_core0(void) +{ + unsigned t0,t1; + static int audio_in_samples[2]; // idx 0: input_samples + static int audio_out_samples[2]; // idx 0: input_samples + static unsigned int usb_to_dsp_ratio = 1; + static int buffer_in_active = 0; + static int buffer_out_active = 0; + static unsigned int sample_freq = 48000; + unsigned b_fade_in = 0; + unsigned u_in_step; + unsigned b_fade_out = 0; + unsigned u_out_step; + + // EQ Flash存储初始化标志 + + delay_milliseconds(100); + unsigned windows_detect_done; + GET_SHARED_GLOBAL(windows_detect_done, g_windows_detect_done); + while (windows_detect_done == 0) { + asm("nop"); + GET_SHARED_GLOBAL(windows_detect_done, g_windows_detect_done); + } + +#if EQ_EN + + uint32_t time = get_reference_time(), time2; + // EQ Flash存储初始化 - 只在第一次运行时执行 + if (eq_flash_initialized == 0) { + // 延迟更长时间,确保QSPI Flash完全初始化 + debug_printf("Initializing EQ Flash storage system\n"); + + // 初始化EQ Flash存储系统 + if (eq_flash_init() == 0) { + debug_printf("EQ Flash storage initialized successfully\n"); + init_mode_info(); + eq_load_gain_and_names(); + // 检查Flash中是否有EQ参数数据 + debug_printf("Loading EQ parameters from Flash using simplified storage\n"); + // 简化加载逻辑:每个模式只加载一次,会自动应用到所有采样率和通道 + int loaded_modes = 0; + + for (int mode = EQ_USER_MODE_MIN; mode <= EQ_USER_MODE_MAX; mode++) { + if (eq_load_all_params_and_calculate_coefficients(44100, mode) == 0) { + loaded_modes++; + debug_printf("Loaded mode %d parameters from Flash (applied to all sample rates and channels)\n", mode); + } + } + debug_printf("Loaded %d EQ modes from Flash successfully\n", loaded_modes); + + // 从mode info里获取gain值,并相应的赋值到eq数组的post gain + update_eq_post_gain(); + // 从Flash加载并设置当前EQ模式和使能状态 + // 改动原因:修复EQ开关逻辑问题,确保从Flash加载模式时正确设置g_saved_eq_mode和g_eq_enable + // eq_flash_load_current_mode()会同时加载模式并设置g_eq_enable + int saved_mode_result = eq_flash_load_current_mode(); + + // 改动原因:确保g_eq_enable被正确加载,无论模式加载是否成功 + // eq_flash_load_current_mode()已经设置了g_eq_enable,这里读取确认 + unsigned int eq_enable = g_eq_enable; + + if (saved_mode_result >= 0 && saved_mode_result <= 9) { + uint8_t saved_mode = (uint8_t)saved_mode_result; + debug_printf("Loaded current EQ mode %d and enable %d from Flash\n", saved_mode, eq_enable); + + // 改动原因:将eq_enable独立为全局开关,所有模式共用一个开关 + // 无论EQ是否启用,都设置到Flash中保存的模式,eq_enable只控制EQ处理开关 + g_saved_eq_mode = saved_mode; + SET_SHARED_GLOBAL(g_request_eq_mode, saved_mode); + debug_printf("EQ initialization: Set mode to %d from Flash, EQ enable: %d (global switch, independent of mode)\n", + saved_mode, eq_enable); + } else { + // Flash加载失败时,使用默认模式0,EQ默认禁用 + // 改动原因:如果模式加载失败,确保g_eq_enable也被设置为默认值0 + g_saved_eq_mode = 0; + SET_SHARED_GLOBAL(g_request_eq_mode, 0); + g_eq_enable = 0; + eq_enable = 0; // 同步本地变量 + debug_printf("Failed to load EQ mode from Flash, using default mode 0, EQ disabled (enable: %d)\n", eq_enable); + } + + } else { + debug_printf("Failed to initialize EQ Flash storage system, continuing without Flash storage\n"); + // 即使Flash初始化失败,也标记为已初始化,避免重复尝试 + // 系统将继续运行,只是不使用Flash存储功能 + } + + eq_single_param_database_init(); + time2 = get_reference_time(); + debug_printf("EQ Flash storage initialized time %lu\n", time2 - time); + eq_flash_initialized = 1; + } + + while (1) + { + if(is_ring_buffer_empty(0) && is_ring_buffer_empty(1)) + { + // delay_microseconds(1); + } + else + { + sample_freq = 48000; + audio_in_samples[0] = read_from_ring_buffer(0); + audio_in_samples[1] = read_from_ring_buffer(1); + + if (is_mode_changed() && b_fade_out == 0 && b_fade_in == 0) + { + + debug_printf("mode changed ====================== \n"); + //debug_print_eq_params(sample_freq); + b_fade_out=1; + u_out_step=1; + } + + + audio_out_samples[0] = handler_eq_filter(sample_freq, 0, audio_in_samples[0]); + audio_out_samples[1] = handler_eq_filter(sample_freq, 1, audio_in_samples[1]); + + + if(b_fade_out) + { + audio_out_samples[0] /= LEN_FADE_; + audio_out_samples[0] *= (LEN_FADE_-u_out_step) ; + + audio_out_samples[1] /= LEN_FADE_; + audio_out_samples[1] *= (LEN_FADE_-u_out_step) ; + + ++u_out_step; + } + + if(b_fade_out == 1 && u_out_step == LEN_FADE_){ + b_fade_out = 0; + change_eq_mode(sample_freq); + clear_eq_status(sample_freq, 0); + clear_eq_status(sample_freq, 1); + clear_ring_buffer(0); + clear_ring_buffer(1); + b_fade_in=1; + u_in_step=1; + } + + + if (b_fade_in) + { + audio_out_samples[0] /= LEN_FADE_; + audio_out_samples[0] *= (u_in_step) ; + + audio_out_samples[1] /= LEN_FADE_; + audio_out_samples[1] *= (u_in_step); + + if(++u_in_step >= LEN_FADE_){ + b_fade_in = 0; + } + } + + write_to_ring_buffer(2, audio_out_samples[0]); + write_to_ring_buffer(3, audio_out_samples[1]); + } + } +#endif +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp_user_impl.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp_user_impl.h new file mode 100644 index 0000000..f7c9122 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/dsp_user_impl.h @@ -0,0 +1,18 @@ + + + + +#define DSP_BLOCK_LENGTH (8) //DO NOT CHANGE + + +#define DSP_TILE_0_CHANNEL_IN_COUNT (1) +#define DSP_TILE_1_CHANNEL_IN_COUNT (1) + +#define DSP_TILE_0_CHANNEL_OUT_COUNT (DSP_TILE_0_CHANNEL_IN_COUNT*2) +#define DSP_TILE_1_CHANNEL_OUT_COUNT (DSP_TILE_1_CHANNEL_IN_COUNT*2) + +#define DSP_TILE_0_WORKER_COUNT (DSP_TILE_0_CHANNEL_IN_COUNT) +#define DSP_TILE_1_WORKER_COUNT (DSP_TILE_1_CHANNEL_IN_COUNT) + + +#define DSP_MIXER_OUTPUT_CHANNEL_COUNT (2) \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.c new file mode 100644 index 0000000..8421ac5 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.c @@ -0,0 +1,1443 @@ +#if DEBUG_MEMORY_LOG_ENABLED +#define DEBUG_PRINT_ENABLE 1 +#else +#define DEBUG_PRINT_ENABLE 0 +#endif +#include "debug_print.h" +#include "xc_ptr.h" +#include // 添加数学函数支持 + +extern void device_reboot(void); + +// 常量定义 +#define EQ_DISABLED_MODE 10 // 禁用EQ的模式编号 + +#include "xmath/filter.h" +#include "eq.h" +#include +#include +#include +#include "eq_params_44100.h" + +#define RSP_HDR(r, cmd) do { (r)[0] = 0x77; (r)[1] = (uint8_t)(cmd); } while(0) + +#include "eq_coefficient_calculator.h" +#include "biquad_standalone.h" +#include "eq_flash_storage.h" +#include "user_func.h" + +static struct { + uint8_t pending_cmd; // 0x95/0x96/0x97/0x98/0x99 or 0 + uint8_t led_index; +} led_request = {0}; + +static struct { + uint8_t pending_cmd; // 0x9A/0x9C or 0 +} uac_request = {0}; + +static struct { + uint8_t pending_cmd; // 0xB0/0xB1 or 0 + uint8_t ex3d_params_len; + uint32_t ex3d_command_code; + uint8_t ex3d_params[56]; +} ex3d_request = {0}; + +#if EQ_EN + +unsigned int g_current_eq_mode = 0; +unsigned int g_request_eq_mode = 0; +unsigned int g_new_eq_mode = 9; +unsigned int g_eq_sample_rate = 0; +unsigned int g_force_eq_mode_change = 0; +unsigned int g_force_request_eq_mode_change = 0; +unsigned int g_eq_enable = 0; +unsigned int g_saved_eq_mode = 9; + +// 全局模式信息存储 - 只存储NUM_EQ_MODES条信息 +eq_mode_info_t g_mode_info = {0}; + +static struct { + uint8_t pending_cmd; // HID command code (0 = none pending) + uint8_t mode; + uint8_t eq_index; + uint8_t read_mode_request_mode; +} read_request = {0}; + +// Optimized: 51 entries for integer dB values 0 to -50 (was 183 entries at 0.5dB steps) +static const int32_t attenuation_table[51] = { + 0x01000000, // 0dB + 0x00E42905, // -1dB + 0x00CB5918, // -2dB + 0x00B53BEF, // -3dB + 0x00A1866B, // -4dB + 0x008FF599, // -5dB + 0x00804DCE, // -6dB + 0x007259DB, // -7dB + 0x0065EA59, // -8dB + 0x005AD50C, // -9dB + 0x0050F44D, // -10dB + 0x0048268D, // -11dB + 0x00404DE6, // -12dB + 0x00394FAE, // -13dB + 0x00331426, // -14dB + 0x002D8621, // -15dB + 0x002892C1, // -16dB + 0x00242934, // -17dB + 0x00203A7E, // -18dB + 0x001CB942, // -19dB + 0x00199999, // -20dB + 0x0016D0E6, // -21dB + 0x001455B5, // -22dB + 0x00121F97, // -23dB + 0x0010270A, // -24dB + 0x000E655C, // -25dB + 0x000CD494, // -26dB + 0x000B6F62, // -27dB + 0x000A3108, // -28dB + 0x0009154E, // -29dB + 0x0008186E, // -30dB + 0x0007370E, // -31dB + 0x00066E30, // -32dB + 0x0005BB2B, // -33dB + 0x00051B9D, // -34dB + 0x00048D69, // -35dB + 0x00040EAC, // -36dB + 0x00039DB8, // -37dB + 0x0003390C, // -38dB + 0x0002DF53, // -39dB + 0x00028F5C, // -40dB + 0x00024817, // -41dB + 0x00020892, // -42dB + 0x0001CFF5, // -43dB + 0x00019D81, // -44dB + 0x00017089, // -45dB + 0x00014875, // -46dB + 0x000124BD, // -47dB + 0x000104E7, // -48dB + 0x0000E887, // -49dB + 0x0000CF3E, // -50dB +}; + +static inline int get_gain_q24(int input, unsigned multipler){ + int result; + long long intermediate; + intermediate = (long long) input * (long long) multipler; + intermediate = intermediate >> 24; + + if (intermediate > INT32_MAX) { + intermediate = INT32_MAX; + } + else if (intermediate < INT32_MIN) { + intermediate = INT32_MIN; + } + + result = (int)(intermediate); + return result; +} + +static inline int32_t clamp_post_gain(int32_t gain) { + // 限制gain范围:0 >= gain >= -50 + if (gain > 0) { + return 0; + } else if (gain < -50) { + return -50; + } + return gain; +} + +// apply_gain: apply attenuation (Q24 format) +// gain_db is always in range [-50, 0] +static inline int32_t apply_gain(int32_t sample, int32_t gain_db) { + int32_t idx = -gain_db; + if (idx > 50) idx = 50; + return get_gain_q24(sample, attenuation_table[idx]); +} + +// Helper: check if name is empty (all NUL or all spaces) +static bool is_name_empty(const char *name, int len) { + if (name[0] == '\0') return true; + for (int i = 0; i < len && name[i] != '\0'; i++) { + if (name[i] != ' ') return false; + } + return true; +} + +// copy_params_from_44100: set sample rate flag for specified mode +static int copy_params_from_44100(uint32_t sample_rate, uint8_t mode) { + if (mode >= NUM_EQ_MODES) { + return -1; + } + + for (int ch = 0; ch < NUM_EQ_CHANS; ch++) { + sEQ_data_44100HZ[mode][ch].sample_rate = sample_rate; + sEQ_data_44100HZ[mode][ch].total_bshift = 0; + } + + return 0; +} + +// calculate_current_mode_coefficients: dynamically compute coefficients for all bands of a mode +int calculate_current_mode_coefficients(uint32_t sample_rate, uint8_t mode) { + copy_params_from_44100(sample_rate, mode); + + if (mode >= NUM_EQ_MODES) { + return -1; + } + + eq_mode_data_t *eq_data = &sEQ_data_44100HZ[mode][0]; + eq_mode_data_t *eq_data_2 = &sEQ_data_44100HZ[mode][1]; + + // 重置total_bshift + int total_bshift = 0; + + // 遍历所有band,计算系数 + for (int band_index = 0; band_index < MAX_EQ_BANDS; band_index++) { + filter_params_t *band = &eq_data->bands[band_index]; + + // 检查参数有效性 + if (isnan(band->fc) || isnan(band->q) || isnan(band->bw) || isnan(band->gain)) { + band->type = FILTER_TYPE_BYPASS; + band->fc = 1000.0f; + band->q = 0.707f; + band->bw = 1.0f; + band->gain = 0.0f; + } + + q2_30 coeffs[5]; + int calculated_bshift = 0; + + // 根据滤波器类型计算系数 + if (band->type == FILTER_TYPE_BYPASS) { + // bypass类型:直接设置bypass系数,不累加bshift + coeffs[0] = 1 << 30; // b0 = 1.0 in Q30 + coeffs[1] = 0; // b1 = 0 + coeffs[2] = 0; // b2 = 0 + coeffs[3] = 0; // a1 = 0 + coeffs[4] = 0; // a2 = 0 + calculated_bshift = 0; + } else { + // 非bypass类型:调用系数计算函数 + if (eq_calculate_coefficients_from_params(band, sample_rate, coeffs, &calculated_bshift) != 0) { + // 计算失败,使用bypass + coeffs[0] = 1 << 30; + coeffs[1] = 0; + coeffs[2] = 0; + coeffs[3] = 0; + coeffs[4] = 0; + calculated_bshift = 0; + } + // 累加非bypass滤波器的bshift + total_bshift += calculated_bshift; + } + + // 存储计算出的系数到两个通道 + for (int i = 0; i < 5; i++) { + eq_data->filter.coef[i][band_index] = coeffs[i]; + eq_data_2->filter.coef[i][band_index] = coeffs[i]; + } + // 同步band参数到第二个通道 + memcpy(&eq_data_2->bands[band_index], band, sizeof(filter_params_t)); + } + + // 更新total_bshift + eq_data->total_bshift = total_bshift; + eq_data_2->total_bshift = total_bshift; + + return 0; +} + +// Initialize EQ data for the specified sample rate +uint32_t init_eq_data(unsigned sample_freq) { + g_eq_sample_rate = sample_freq; + + // Clear EQ status for all sample rates, modes, and channels + for (int ch = 0; ch < NUM_EQ_CHANS; ch++) { + // Clear status for various sample rates + clear_eq_status(sample_freq, ch); + } + + // 为所有模式计算系数 (calculate_current_mode_coefficients already calls copy_params_from_44100) + for (int mode = 0; mode < NUM_EQ_MODES; mode++) { + calculate_current_mode_coefficients(sample_freq, mode); + } + + return 1; +} + +void update_eq_post_gain(void) { + for (int mode = EQ_USER_MODE_MIN; mode <= EQ_USER_MODE_MAX; mode++) { + int32_t gain = clamp_post_gain(g_mode_info.mode_gains[mode]); + if (gain != g_mode_info.mode_gains[mode]) { + g_mode_info.mode_gains[mode] = gain; + } + + if (sEQ_data_44100HZ[mode][0].post_gain_db != gain) { + sEQ_data_44100HZ[mode][0].post_gain_db = gain; + sEQ_data_44100HZ[mode][1].post_gain_db = gain; + } + } +} + +unsigned int is_mode_changed(void) +{ + if ((g_current_eq_mode != g_new_eq_mode) || (g_force_eq_mode_change == 1)) + { + g_force_eq_mode_change = 0; + return 1; + } + + return 0; +} + +void change_eq_mode(unsigned sample_freq) +{ + calculate_current_mode_coefficients(sample_freq, g_new_eq_mode); + g_current_eq_mode = g_new_eq_mode; +} + +unsigned int is_eq_disabled(void) +{ + return (g_eq_enable == 0); +} + +void clear_eq_status(unsigned sample_freq, unsigned ch_no) { + if(is_eq_disabled()) + { + return; + } + + // Clear status for the specified sample rate, mode, and channel + memset(sEQ_data_44100HZ[g_current_eq_mode][ch_no].filter.state, 0, + sizeof(sEQ_data_44100HZ[g_current_eq_mode][ch_no].filter.state)); +} + +// reset_eq_params: restore default EQ parameters for given mode +uint8_t reset_eq_params(uint8_t mode) { + // 检查模式号有效性 + if (mode != 0xFF && mode >= NUM_EQ_MODES) { + return 0x01; // 失败 + } + + // 删除Flash中的EQ参数文件 + delete_flash_eq_params(mode); + + // 删除Flash中的增益和模式名称文件(只删除相应模式的数据) + delete_flash_gain_and_names(mode); + + // 恢复头文件预设参数 + restore_default_eq_params(mode); + + // 恢复默认模式信息 + init_mode_info(); + + return 0x00; // 成功 +} + +int32_t handler_eq_filter(unsigned sample_freq, uint32_t ch, int32_t new_sample) { + int32_t temp_sample = new_sample; + + + if(is_eq_disabled()) + { + return temp_sample; + } + + + if (ch < NUM_EQ_CHANS) { + eq_mode_data_t *eq = &sEQ_data_44100HZ[g_current_eq_mode][ch]; + + // 应用增益或衰减 (Q24格式) + int32_t post_gain = eq->post_gain_db; + if (post_gain <= 0 && post_gain >= -50) { + temp_sample = apply_gain(temp_sample, post_gain); + } + + temp_sample = temp_sample >> 1; //Q30 + + // 应用均衡器滤波 + temp_sample = filter_biquads_s32(&eq->filter, 1, temp_sample); + + // 应用bshift补偿 + unsigned int bshift = eq->total_bshift; + + // Q2.30 -> Q31,溢出保护 + if (temp_sample > (INT32_MAX >> (bshift + 1))) { + temp_sample = INT32_MAX; + } else if (temp_sample < (INT32_MIN >> (bshift + 1))) { + temp_sample = INT32_MIN; + } else { + temp_sample = temp_sample << (bshift + 1); + } + } + + return temp_sample; +} + + + + +#endif + + +unsigned key_program = 0; + +extern unsigned short XUA_Endpoint0_getProductId(); +extern unsigned short XUA_Endpoint0_getVendorId(); +extern char* XUA_Endpoint0_getProductStr(); +extern char* XUA_Endpoint0_getVendorStr(); +extern char* XUA_Endpoint0_getSerialStr(); +extern unsigned g_request_volume_set; + +void get_device_info(unsigned char *response) { + unsigned short pid = XUA_Endpoint0_getProductId(); + unsigned short vid = XUA_Endpoint0_getVendorId(); + + // 产品ID (1字节,小端序,位置3-4) + response[2] = (uint8_t)(pid & 0xFF); + response[3] = (uint8_t)((pid >> 8) & 0xFF); + + // 厂商ID (1字节,小端序,位置5-6) + response[4] = (uint8_t)(vid & 0xFF); + response[5] = (uint8_t)((vid >> 8) & 0xFF); + + // 产品字符串 (15字节,UTF-8编码,位置7-22) + memcpy(&response[6], XUA_Endpoint0_getProductStr(), 16); + + // 厂商字符串 (15字节,UTF-8编码,位置23-38) + memcpy(&response[22], XUA_Endpoint0_getVendorStr(), 16); + + // 序列号字符串 (15字节,UTF-8编码,位置39-54) + memcpy(&response[38], XUA_Endpoint0_getSerialStr(), 16); +} + + + + +#if EQ_EN +// Shared EQ band param update logic — called from 0x8D HID handler and UART handler +static bool apply_eq_band_params(uint8_t mode, uint8_t band_index, uint8_t filter_type, + float fc, float q, float bw, float gain) +{ + if (isnan(fc) || isnan(q) || isnan(bw) || isnan(gain) || fc == 0) { + return false; + } + + filter_params_t new_params; + memset(&new_params, 0, sizeof(filter_params_t)); + new_params.index = band_index; + new_params.type = (filter_type_t)filter_type; + new_params.fc = fc; + new_params.q = q; + new_params.bw = bw; + new_params.gain = gain; + + filter_params_t *band_44100 = &sEQ_data_44100HZ[mode][0].bands[band_index]; + + if (eq_single_params_different(band_44100, &new_params)) { + *band_44100 = new_params; + memcpy(&sEQ_data_44100HZ[mode][1].bands[band_index], band_44100, sizeof(filter_params_t)); + + if (g_eq_enable == 1) { + SET_SHARED_GLOBAL(g_force_request_eq_mode_change, 1); + } + + uint32_t all_sample_rates[] = {48000, 88200, 96000, 176400, 192000}; + for (int i = 0; i < (int)(sizeof(all_sample_rates)/sizeof(all_sample_rates[0])); i++) { + calculate_current_mode_coefficients(all_sample_rates[i], mode); + } + calculate_current_mode_coefficients(44100, mode); + + eq_process_single_param_change(44100, mode, 0, band_index, band_44100); + eq_flash_mark_dirty(mode); + } + + return true; +} +#endif + +void program_key(unsigned char *, signed int); +#if (USE_EX3D == 1) && (HID_CONTROLS == 1) +extern void hid_receive_task_in_c(unsigned char * RcvData, unsigned * SendData); +#endif +unsigned char process_send_params(uint8_t data[], uint16_t len) { + if (len < 60) { + return false; + } + + if (data[0] == 0x77 && data[1] == 0x5B && key_program == 0) + { + key_program = 1; + program_key(data, len); + } + +#if EQ_EN + // Check sync header + if (data[0] != 0x77) { + return false; + } + + // Process read request (0x8E) - GET_EQ_PARAMS + if (data[1] == 0x8E) { + // Get mode value + read_request.mode = data[2]; + // Get EQ index + read_request.eq_index = data[3]; + read_request.pending_cmd = 0x8E; + } + + if (data[1] == 0x8a) + { + if (data[2] > EQ_MODE_MAX ) { + return false; + } + SET_SHARED_GLOBAL(g_request_eq_mode, data[2]); + return true; + } + + // 处理读取EQ模式信息命令 (0x8B) - GET_EQ_MODE + if (data[1] == 0x8b) { + uint8_t mode = data[2]; + + // 验证mode参数:0xFF表示获取当前模式,0-9表示获取指定模式 + if (mode == 0xFF) { + read_request.pending_cmd = 0x8B; + read_request.read_mode_request_mode = 0xFF; + } else if (mode <= EQ_MODE_MAX) { + // 获取指定模式信息 + read_request.pending_cmd = 0x8B; + read_request.read_mode_request_mode = mode; + } else { + return false; + } + return true; + } + // 处理设置EQ整体增益和名称命令 (0x8C) - SET_MODE_GAIN_AND_NAME + if (data[1] == 0x8c) { + // 获取模式值 + uint8_t mode = data[2]; + + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX ) { + return false; + } + // 获取增益值 (4字节,范围0到-50dB) + uint32_t gain = (uint32_t)data[3] | ((uint32_t)data[4] << 8) | ((uint32_t)data[5] << 16) | ((uint32_t)data[6] << 24); + + int32_t new_gain = clamp_post_gain((int32_t)gain); + + // 获取模式名称 (16字节,UTF-8编码) + char mode_name[17] = {0}; // 16字节 + 1个null终止符 + memcpy(mode_name, &data[7], 16); + mode_name[16] = '\0'; // 确保null终止 + + // 获取44100采样率下该模式的当前gain值,判断是否有变化 + int32_t current_gain_44100 = sEQ_data_44100HZ[mode][0].post_gain_db; + bool gain_changed = (current_gain_44100 != new_gain); + + // 获取当前模式名称,判断是否有变化 + char current_name[16] = {0}; + strncpy(current_name, g_mode_info.mode_names[mode], 15); + current_name[15] = '\0'; + + // 比较名称是否有变化 + bool name_changed = (strncmp(current_name, mode_name, 15) != 0); + + if (!is_name_empty(mode_name, 16) && name_changed) { + set_mode_name(mode, mode_name); + eq_mark_name_dirty(); + } + + if (gain_changed) { + if (mode < NUM_EQ_MODES) { + sEQ_data_44100HZ[mode][0].post_gain_db = new_gain; + sEQ_data_44100HZ[mode][1].post_gain_db = new_gain; + } + + set_mode_gain(mode, new_gain); + eq_mark_gain_dirty(); + } + + return true; + } + + // 处理获取设备信息命令 (0x8F) - GET_DEVICE_INFO + if (data[1] == 0x8f) { + read_request.pending_cmd = 0x8F; + return true; + } + + // 处理复位EQ参数命令 (0x90) - RESET_EQ_PARAMS + if (data[1] == 0x90) { + // 获取模式号 + uint8_t mode = data[2]; + + reset_eq_params(mode); + + // 设置读取请求标志,读取时只返回保存的状态 + read_request.pending_cmd = 0x90; + read_request.mode = mode; + + device_reboot(); + while(1); // 防止编译器警告 + + return true; + } + + if (data[1] == 0x91) + { + // 设置读取请求,返回EQ模式总数 + read_request.pending_cmd = 0x91; + return true; + } + + // 处理设置并保存EQ模式命令 (0x92) - SET_AND_SAVE_EQ_MODE + if (data[1] == 0x92) { + read_request.pending_cmd = 0x92; + + // 获取模式值 + uint8_t mode = data[2]; + + // 验证模式值范围 + if (mode > EQ_MODE_MAX ) { + read_request.mode = 0xFF; // 使用0xFF表示失败 + return true; + } + + // 设置当前模式 + SET_SHARED_GLOBAL(g_request_eq_mode, mode); + + // 更新保存的模式(用于0x9D启用时恢复) + g_saved_eq_mode = mode; + + // 保存到Flash(只保存模式,不影响使能状态) + eq_flash_save_current_mode(mode); + read_request.mode = mode; + + return true; + } + + // 处理设置音量命令 (0x93) - SET_VOLUME + if (data[1] == 0x93) { + // 获取音量级别 + uint8_t volume_level = data[2]; + + // 参数验证 + if (volume_level > 60) { + return false; + } + + // 设置全局变量(直接赋值,因为在C文件中) + extern unsigned g_volume_level; + g_volume_level = volume_level; + g_request_volume_set = 1; + + return true; + } + + // 处理读取音量命令 (0x94) - GET_VOLUME + if (data[1] == 0x94) { + read_request.pending_cmd = 0x94; + return true; + } + + // 处理获取LED信息命令 (0x95) - GET_LED_INFO + if (data[1] == 0x95) { + uint8_t led_index = data[2]; + if (led_index > 7) { + return false; + } + led_request.pending_cmd = 0x95; + led_request.led_index = led_index; + return true; + } + + // 处理设置LED开关命令 (0x96) - SET_LED_SWITCH + if (data[1] == 0x96) { + uint8_t led_index = data[2]; + uint8_t led_switch = data[3]; + + if (led_index > 7) { + return false; + } + if (led_switch > 1) { + return false; + } + + // 设置LED开关全局变量 + SET_SHARED_GLOBAL(g_led_enable, led_switch); + + // 设置SET命令的响应标志 + led_request.pending_cmd = 0x96; + led_request.led_index = led_index; + return true; + } + + // 处理获取LED开关命令 (0x97) - GET_LED_SWITCH + if (data[1] == 0x97) { + uint8_t led_index = data[2]; + if (led_index > 7) { + return false; + } + led_request.pending_cmd = 0x97; + led_request.led_index = led_index; + return true; + } + + // 处理获取LED状态命令 (0x98) - GET_LED_STATUS + if (data[1] == 0x98) { + uint8_t led_index = data[2]; + if (led_index > 7) { + return false; + } + led_request.pending_cmd = 0x98; + led_request.led_index = led_index; + return true; + } + + // 处理获取LED总数命令 (0x99) - GET_LED_COUNT + if (data[1] == 0x99) { + led_request.pending_cmd = 0x99; + return true; + } + + // 处理获取UAC模式信息命令 (0x9A) - GET_UAC_MODE_INFO + if (data[1] == 0x9A) { + uac_request.pending_cmd = 0x9A; + return true; + } + + // 处理设置UAC模式命令 (0x9B) - SET_UAC_MODE + if (data[1] == 0x9B) { + // 获取UAC模式值 + uint8_t uac_mode = data[2]; + + // 验证模式值范围 + if (uac_mode > 1) { + return true; + } + + // 读取当前UAC模式 + extern unsigned g_uac_mode; + unsigned current_uac_mode; + GET_SHARED_GLOBAL(current_uac_mode, g_uac_mode); + + // 如果目标模式与当前模式相同,无需操作 + if ((uac_mode == 0 && current_uac_mode == 2) || (uac_mode == 1 && current_uac_mode == 1)) { + return true; + } + + // 如果设置为UAC1.0,需要reboot到uac1.0 +#if (UAC2 == 1) + if (uac_mode == 1) { + SetRoleSwitchFlag(UAC1_IN_FLAG); + delay_milliseconds(1); + device_reboot(); + while(1); // 防止编译器警告 + } +#endif + +#if (UAC1 == 1) + if (uac_mode == 0) { + SetRoleSwitchFlag(0); + delay_milliseconds(1); + device_reboot(); + return true; + } +#endif + + return true; + } + + // 处理获取当前UAC模式命令 (0x9C) - GET_CURRENT_UAC_MODE + if (data[1] == 0x9C) { + uac_request.pending_cmd = 0x9C; + return true; + } + + // 处理设置EQ使能开关命令 (0x9D) - SET_EQ_ENABLE + if (data[1] == 0x9D) { + uint8_t enable = data[2]; // 0=OFF禁用, 1=ON启用 + + if (enable > 1) { + return false; + } + + g_eq_enable = enable; + + // 只保存EQ使能状态到Flash,不影响已保存的模式(0x92保存的模式) + eq_flash_save_eq_enable(enable); + + read_request.pending_cmd = 0x9D; + return true; + } + + // 处理获取EQ使能开关命令 (0x9E) - GET_EQ_ENABLE + if (data[1] == 0x9E) { + read_request.pending_cmd = 0x9E; + return true; + } + + // 处理获取采样率和格式命令 (0x9F) - GET_SAMPLE_FORMAT + if (data[1] == 0x9F) { + read_request.pending_cmd = 0x9F; + return true; + } + + // 处理设置游戏模式命令 (0xA4) - SET_GAME_MODE + if (data[1] == 0xA4) { + uint8_t game_mode = data[2]; + + // 参数验证 + if (game_mode > 2) { + return false; + } + + extern unsigned g_uac_mode; + unsigned uac_mode; + GET_SHARED_GLOBAL(uac_mode, g_uac_mode); + + if (uac_mode == 1) { + extern void request_game_mode_switch(unsigned game_mode); + //request_game_mode_switch(game_mode); + } else { + extern unsigned g_request_game_mode; + SET_SHARED_GLOBAL(g_request_game_mode, game_mode); + } + + return true; + } + + // 处理获取游戏模式命令 (0xA5) - GET_GAME_MODE + if (data[1] == 0xA5) { + read_request.pending_cmd = 0xA5; + return true; + } + + // 处理获取固件版本命令 (0xA6) - GET_FIRMWARE_VERSION + if (data[1] == 0xA6) { + read_request.pending_cmd = 0xA6; + return true; + } + + // 处理发送/读取EX3D命令 (0xB0/0xB1) - SET_EX3D_CMD / GET_EX3D_CMD + if (data[1] == 0xB0 || data[1] == 0xB1) { +#if (USE_EX3D == 1) && (HID_CONTROLS == 1) + // 提取EX3D命令码(4字节,小端序) + uint32_t ex3d_cmd = (uint32_t)data[2] | ((uint32_t)data[3] << 8) | + ((uint32_t)data[4] << 16) | ((uint32_t)data[5] << 24); + + // 提取参数(从data[6]开始,最多56字节) + ex3d_request.ex3d_command_code = ex3d_cmd; + ex3d_request.ex3d_params_len = (len > 62) ? 56 : (len - 6); + memcpy(ex3d_request.ex3d_params, &data[6], ex3d_request.ex3d_params_len); + ex3d_request.pending_cmd = data[1]; + return true; +#else + return false; +#endif + } + + if (data[1] == 0x8d) + { + uint8_t mode = data[2]; + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX) { + return false; + } + uint8_t band_index = data[3]; + if (band_index >= MAX_EQ_BANDS) { + return false; + } + // 解析浮点参数(小端序) + float fc, q, bw, gain; + uint32_t v; + v = data[5]|(data[6]<<8)|(data[7]<<16)|(data[8]<<24); memcpy(&fc, &v, 4); + v = data[9]|(data[10]<<8)|(data[11]<<16)|(data[12]<<24); memcpy(&q, &v, 4); + v = data[13]|(data[14]<<8)|(data[15]<<16)|(data[16]<<24);memcpy(&bw, &v, 4); + v = data[17]|(data[18]<<8)|(data[19]<<16)|(data[20]<<24);memcpy(&gain, &v, 4); + return apply_eq_band_params(mode, band_index, data[4], fc, q, bw, gain); + } + +#endif // #if EQ_EN (for process_send_params EQ commands section starting at line 1228) + return true; +} + +#if EQ_EN + +// 标记增益和模式名称为脏(延迟保存) +extern void eq_mark_gain_dirty(void); +extern void eq_mark_name_dirty(void); + +// set_mode_gain: set gain for specified mode +void set_mode_gain(uint8_t mode, int32_t gain) { + if (mode >= NUM_EQ_MODES) { + return; + } + + int32_t clamped_gain = clamp_post_gain(gain); + + // 设置模式增益 + g_mode_info.mode_gains[mode] = clamped_gain; + + // 注意:不在这里自动标记脏标志,由调用者根据实际情况决定是否标记 +} + +// set_mode_name: set name for specified mode +void set_mode_name(uint8_t mode, const char* name) { + if (mode >= NUM_EQ_MODES || name == NULL) { + return; + } + + // 设置模式名称 + strncpy(g_mode_info.mode_names[mode], name, 15); + g_mode_info.mode_names[mode][15] = '\0'; // 确保null终止 + + // 注意:不在这里自动标记脏标志,由调用者根据实际情况决定是否标记 +} + +// get_mode_gain: retrieve gain for specified mode +void get_mode_gain(uint8_t mode, int32_t* gain) { + if (mode >= NUM_EQ_MODES || gain == NULL) { + return; + } + + *gain = g_mode_info.mode_gains[mode]; +} + +// get_mode_name: retrieve name for specified mode +void get_mode_name(uint8_t mode, char* name) { + if (mode >= NUM_EQ_MODES || name == NULL) { + return; + } + + strncpy(name, g_mode_info.mode_names[mode], 15); + name[15] = '\0'; // 确保null终止 +} + +// init_mode_info: initialize mode info with default names and gains +void init_mode_info(void) { + // 初始化默认模式名称 + const char* default_names[NUM_EQ_MODES] = { + "JAZZ", "POP", "ROCK", "CLASIC", "R&B", + "3A Game", "FPS", "user 1", "user 2", "user3" + }; + + // 初始化默认增益(0dB) + for (int i = 0; i < NUM_EQ_MODES; i++) { + g_mode_info.mode_gains[i] = 0; + strncpy(g_mode_info.mode_names[i], default_names[i], 15); + g_mode_info.mode_names[i][15] = '\0'; + } +} + +#endif + +void get_key_ret(uint8_t *buffer); +extern unsigned char g_hid_status_report_data[63]; +extern unsigned int g_hid_status_report_index; +void user_read_hid_status(unsigned char hidPassData[]) +{ + if (g_hid_status_report_index > 0) { + memcpy(hidPassData, g_hid_status_report_data, 63); + g_hid_status_report_index = 0; + } else { + memset(hidPassData, 0, 63); + } +} + +#if (USE_EX3D == 1) && (HID_CONTROLS == 1) +static void ex3d_build_and_call(unsigned char rcv_data[64], unsigned send_data[16]) { + memset(rcv_data, 0, 64); + memset(send_data, 0, sizeof(unsigned) * 16); + rcv_data[0] = 0x01; + unsigned *pRcvBuf = (unsigned *)(rcv_data + 4); + pRcvBuf[0] = ex3d_request.ex3d_command_code; + for (int i = 0; i < ex3d_request.ex3d_params_len / 4 && i < 14; i++) { + pRcvBuf[i + 1] = (uint32_t)ex3d_request.ex3d_params[i * 4] | + ((uint32_t)ex3d_request.ex3d_params[i * 4 + 1] << 8) | + ((uint32_t)ex3d_request.ex3d_params[i * 4 + 2] << 16) | + ((uint32_t)ex3d_request.ex3d_params[i * 4 + 3] << 24); + } + hid_receive_task_in_c(rcv_data, send_data); +} +#endif + +// process_read_params: build HID response from pending request state +unsigned char process_read_params(uint8_t response[]) { + memset(response, 0, 63); + +#if EQ_EN + // 处理读取当前EQ模式的请求 (0x8B) - GET_EQ_MODE + if (read_request.pending_cmd == 0x8B) { + RSP_HDR(response, 0x8B); + + // 确定要返回的模式值 + uint8_t target_mode; + if (read_request.read_mode_request_mode == 0xFF) { + // 获取当前模式(eq_enable是全局开关,不影响模式返回) + unsigned int current_mode; + GET_SHARED_GLOBAL(current_mode, g_request_eq_mode); + target_mode = current_mode; + } else { + // 获取指定模式 + target_mode = read_request.read_mode_request_mode; + } + + // 模式值 + response[2] = target_mode; + + if (target_mode >= EQ_DISABLED_MODE || target_mode > EQ_MODE_MAX) { + // 返回全0 + memset(&response[3], 0, 20); // 整体增益(4字节) + 模式名称(16字节) = 20字节 + } else { + // 获取指定模式的整体增益 (从当前采样率获取) + int32_t target_post_gain = 0; + if (target_mode < NUM_EQ_MODES) { + target_post_gain = sEQ_data_44100HZ[target_mode][0].post_gain_db; + } + + // 整体增益 (4字节,位置3-6) + memcpy(&response[3], &target_post_gain, 4); + + // 模式名称 (16字节,UTF-8编码,位置7-22) + if (target_mode < NUM_EQ_MODES) { + memcpy(&response[7], g_mode_info.mode_names[target_mode], 16); + } else { + // 如果模式超出范围,填充0 + memset(&response[7], 0, 16); + } + } + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理获取设备信息的请求 (0x8F) - GET_DEVICE_INFO + if (read_request.pending_cmd == 0x8F) { + RSP_HDR(response, 0x8F); + + get_device_info(response); + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理复位EQ参数的请求 (0x90) - RESET_EQ_PARAMS + if (read_request.pending_cmd == 0x90) { + RSP_HDR(response, 0x90); + + if (read_request.mode != 0xFF && read_request.mode >= EQ_PRESET_MODE_MIN && read_request.mode <= EQ_USER_MODE_MAX) { + response[3] = 0x01; // 失败 + } else { + response[3] = 0x00; // 成功 + } + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理获取EQ模式总数的请求 (0x91) - GET_EQ_MODE_COUNT + if (read_request.pending_cmd == 0x91) { + RSP_HDR(response, 0x91); + + // 模式总数:预定义模式(0-5) + 用户模式(6-8) = 9,不包含禁用模式(10) + // 注意:NUM_EQ_MODES = 10,但实际可用模式是0-8共9个(不包含禁用模式10) + uint8_t mode_count = NUM_EQ_MODES; // 固定返回10 + response[2] = mode_count; + + // 预定义模式数量:0-6共7个预设模式 + uint8_t preset_mode_count = EQ_PRESET_MODE_MAX + 1; + response[3] = preset_mode_count; + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理设置并保存EQ模式的请求 (0x92) - SET_AND_SAVE_EQ_MODE + if (read_request.pending_cmd == 0x92) { + RSP_HDR(response, 0x92); + + // 状态码:0x00=成功, 0x01=失败 + uint8_t status = (read_request.mode == 0xFF) ? 0x01 : 0x00; + response[2] = status; + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + // 处理读取音量请求 (0x94) - GET_VOLUME + if (read_request.pending_cmd == 0x94) { + RSP_HDR(response, 0x94); + + // 读取全局变量(直接读取,因为在C文件中) + extern unsigned g_volume_level; + unsigned volume_level = g_volume_level; + + // 当前音量级别 (1字节,位置2) + response[2] = (uint8_t)volume_level; + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理获取LED信息请求 (0x95) - GET_LED_INFO + if (led_request.pending_cmd == 0x95) { + RSP_HDR(response, 0x95); + + // LED索引 + response[2] = led_request.led_index; + + // LED名称 (16字节,UTF-8编码) + // 当前设备只有1个LED,索引为0,名称为"Main LED" + const char* led_name = "Main LED"; + memcpy(&response[3], led_name, strlen(led_name)); + + led_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取LED开关请求 (0x97) - GET_LED_SWITCH + if (led_request.pending_cmd == 0x97) { + RSP_HDR(response, 0x97); + + // LED索引 + response[2] = led_request.led_index; + + // 读取LED开关全局变量 + extern unsigned g_led_enable; + unsigned led_enable; + GET_SHARED_GLOBAL(led_enable, g_led_enable); + + // LED开关状态 (0=OFF, 1=ON) + response[3] = (uint8_t)led_enable; + + led_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理设置LED开关响应 (0x96) - SET_LED_SWITCH响应 + if (led_request.pending_cmd == 0x96) { + RSP_HDR(response, 0x96); + + // 状态码 (0x00=成功, 0x01=失败) + // 由于SET命令已经在process_send_params中执行,这里直接返回成功 + response[2] = 0x00; // 成功 + + led_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取LED总数请求 (0x99) - GET_LED_COUNT + if (led_request.pending_cmd == 0x99) { + RSP_HDR(response, 0x99); + + // LED总数(当前设备只有1个LED) + uint8_t led_count = 1; + response[2] = led_count; + + led_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取LED状态请求 (0x98) - GET_LED_STATUS + if (led_request.pending_cmd == 0x98) { + RSP_HDR(response, 0x98); + + // LED索引 + response[2] = led_request.led_index; + + // 读取当前LED颜色 + extern unsigned g_led_color; + unsigned led_color; + GET_SHARED_GLOBAL(led_color, g_led_color); + + // 转换LED颜色为RGB值 + // LED颜色定义:bit 1=0表示红色开启,bit 2=0表示绿色开启,bit 3=0表示蓝色开启 + uint8_t r = 0, g = 0, b = 0; + + r = ((led_color & 0x20) == 0) ? 255 : 0; // bit 1 + g = ((led_color & 0x10) == 0) ? 255 : 0; // bit 2 + b = ((led_color & 0x40) == 0) ? 255 : 0; // bit 3 + + response[3] = (uint8_t)r; // R颜色值 + response[4] = (uint8_t)g; // G颜色值 + response[5] = (uint8_t)b; // B颜色值 + + extern unsigned g_gain_mode, g_uac_mode, g_game_mode; + unsigned gain_mode, uac_mode, game_mode; + GET_SHARED_GLOBAL(gain_mode, g_gain_mode); + GET_SHARED_GLOBAL(uac_mode, g_uac_mode); + GET_SHARED_GLOBAL(game_mode, g_game_mode); + + uint8_t led_status = 0; // LED_STATUS_OFF + unsigned is_blinking = 0; + unsigned is_fast_blinking = 0; + + if (uac_mode == 1) { // UAC 1.0 Game Mode + if (gain_mode == 1) { // High impedance + is_blinking = 1; + if (game_mode > 0) { + is_fast_blinking = 0; + } + } + } else { // UAC 2.0 HiFi Mode + if (gain_mode == 1) { // High impedance -> Blinking + is_blinking = 1; + } + } + + if (led_color == 0x7F) { // LED_OFF + led_status = 0; // LED_STATUS_OFF + } else if (is_blinking) { + led_status = is_fast_blinking ? 3 : 2; // LED_STATUS_FAST_BLINK or LED_STATUS_SLOW_BLINK + } else { + led_status = 1; // LED_STATUS_SOLID + } + + response[6] = led_status; // LED状态 + + led_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取UAC模式信息请求 (0x9A) - GET_UAC_MODE_INFO + if (uac_request.pending_cmd == 0x9A) { + RSP_HDR(response, 0x9A); + + // UAC模式总数(当前为2:UAC2.0和UAC1.0) + uint8_t uac_mode_count = 2; + response[2] = uac_mode_count; + + // 模式0名称:UAC2.0 (8字节), 模式1名称:UAC1.0 (8字节) + memcpy(&response[3], "UAC2.0", 6); + memcpy(&response[11], "UAC1.0", 6); + + uac_request.pending_cmd = 0; // Clear request flag + return true; + } + + // SET_UAC_MODE命令不返回响应 + + // 处理获取当前UAC模式请求 (0x9C) - GET_CURRENT_UAC_MODE + if (uac_request.pending_cmd == 0x9C) { + RSP_HDR(response, 0x9C); + + // 读取当前UAC模式 + extern unsigned g_uac_mode; + unsigned current_uac_mode; + GET_SHARED_GLOBAL(current_uac_mode, g_uac_mode); + + // 转换为协议格式:0=UAC2.0, 1=UAC1.0 + // g_uac_mode: 2=UAC2.0, 1=UAC1.0 + uint8_t uac_mode_value = (current_uac_mode == 1) ? 1 : 0; // 1=UAC1.0, 其他=UAC2.0(0) + response[2] = uac_mode_value; + + // 当前UAC模式名称 (8字节) + memcpy(&response[3], (uac_mode_value == 0) ? "UAC2.0" : "UAC1.0", 6); + + uac_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理设置EQ使能开关请求 (0x9D) - SET_EQ_ENABLE + if (read_request.pending_cmd == 0x9D) { + RSP_HDR(response, 0x9D); + + // 状态码:0x00=成功, 0x01=失败 + response[2] = 0x00; // 成功 + + // 当前EQ使能状态(全局开关,所有模式共用) + response[3] = g_eq_enable; + + read_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取EQ使能开关请求 (0x9E) - GET_EQ_ENABLE + if (read_request.pending_cmd == 0x9E) { + RSP_HDR(response, 0x9E); + + // EQ使能开关状态(全局开关,所有模式共用) + response[2] = g_eq_enable; + + read_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取采样率和格式请求 (0x9F) - GET_SAMPLE_FORMAT + if (read_request.pending_cmd == 0x9F) { + RSP_HDR(response, 0x9F); + + // 读取全局变量(使用SHARED_GLOBAL宏) + extern unsigned g_samfreq, g_dsd_mode; + unsigned samfreq, dsd_mode; + GET_SHARED_GLOBAL(samfreq, g_samfreq); + GET_SHARED_GLOBAL(dsd_mode, g_dsd_mode); + + // 采样率(32位,小端序,位置2-5) + memcpy(&response[2], &samfreq, 4); + + // DSD模式(1字节,位置6) + response[6] = (uint8_t)dsd_mode; + + read_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理获取游戏模式请求 (0xA5) - GET_GAME_MODE + if (read_request.pending_cmd == 0xA5) { + RSP_HDR(response, 0xA5); + + // 读取全局变量(使用SHARED_GLOBAL宏) + extern unsigned g_game_mode; + unsigned game_mode; + GET_SHARED_GLOBAL(game_mode, g_game_mode); + + // 当前游戏模式 (1字节,位置2) +#if UAC2 == 1 + response[2] = 0; +#else + response[2] = (uint8_t)game_mode; +#endif + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理获取固件版本请求 (0xA6) - GET_FIRMWARE_VERSION + if (read_request.pending_cmd == 0xA6) { + RSP_HDR(response, 0xA6); + + // 读取固件版本号(BCD格式) + response[2] = (uint8_t)BCD_DEVICE_J; // 主版本号 + response[3] = (uint8_t)BCD_DEVICE_M; // 次版本号 + response[4] = (uint8_t)BCD_DEVICE_N; // 修订版本号 + + read_request.pending_cmd = 0; // Clear read request flag + return true; + } + + // 处理发送EX3D命令响应 (0xB0) - SET_EX3D_CMD + if (ex3d_request.pending_cmd == 0xB0) { + RSP_HDR(response, 0xB0); + +#if (USE_EX3D == 1) && (HID_CONTROLS == 1) + unsigned char rcv_data[64]; + unsigned send_data[16]; + ex3d_build_and_call(rcv_data, send_data); + + // 状态码:检查send_data[1]是否有错误返回值(0xFFFFFFFF表示参数错误) + uint8_t status = 0x00; // 默认成功 + if (send_data[1] == 0xFFFFFFFF) { + status = 0x01; // 失败 + } + + response[2] = status; + + // 回显命令码(4字节) + response[3] = (uint8_t)(ex3d_request.ex3d_command_code & 0xFF); + response[4] = (uint8_t)((ex3d_request.ex3d_command_code >> 8) & 0xFF); + response[5] = (uint8_t)((ex3d_request.ex3d_command_code >> 16) & 0xFF); + response[6] = (uint8_t)((ex3d_request.ex3d_command_code >> 24) & 0xFF); + + // 返回send_data中的返回值(如果有) + if (send_data[1] != 0xFFFFFFFF) { + // 某些SET命令可能返回数据,将send_data[1]开始的数据复制到响应 + response[7] = (uint8_t)(send_data[1] & 0xFF); + response[8] = (uint8_t)((send_data[1] >> 8) & 0xFF); + response[9] = (uint8_t)((send_data[1] >> 16) & 0xFF); + response[10] = (uint8_t)((send_data[1] >> 24) & 0xFF); + } else { + // 错误时返回0xFFFFFFFF + response[7] = 0xFF; + response[8] = 0xFF; + response[9] = 0xFF; + response[10] = 0xFF; + } +#else + response[2] = 0x01; // 失败:EX3D支持未启用 +#endif + + ex3d_request.pending_cmd = 0; // Clear request flag + return true; + } + + // 处理读取EX3D命令响应 (0xB1) - GET_EX3D_CMD + if (ex3d_request.pending_cmd == 0xB1) { + RSP_HDR(response, 0xB1); + +#if (USE_EX3D == 1) && (HID_CONTROLS == 1) + unsigned char rcv_data[64]; + unsigned send_data[16]; + ex3d_build_and_call(rcv_data, send_data); + + // 回显命令码(4字节) + response[2] = (uint8_t)(ex3d_request.ex3d_command_code & 0xFF); + response[3] = (uint8_t)((ex3d_request.ex3d_command_code >> 8) & 0xFF); + response[4] = (uint8_t)((ex3d_request.ex3d_command_code >> 16) & 0xFF); + response[5] = (uint8_t)((ex3d_request.ex3d_command_code >> 24) & 0xFF); + + // 复制返回值(从send_data[1]开始,最多14个uint32值) + int response_idx = 6; + for (int i = 1; i < 16 && response_idx < 63; i++) { + if (response_idx + 3 < 63) { + response[response_idx++] = (uint8_t)(send_data[i] & 0xFF); + response[response_idx++] = (uint8_t)((send_data[i] >> 8) & 0xFF); + response[response_idx++] = (uint8_t)((send_data[i] >> 16) & 0xFF); + response[response_idx++] = (uint8_t)((send_data[i] >> 24) & 0xFF); + } + } +#else + // EX3D支持未启用,返回错误 + response[2] = 0xFF; + response[3] = 0xFF; + response[4] = 0xFF; + response[5] = 0xFF; +#endif + + ex3d_request.pending_cmd = 0; // Clear request flag + return true; + } + + if (read_request.pending_cmd == 0x8E) { + + // 检查EQ索引范围 + if (read_request.eq_index >= MAX_EQ_BANDS) { + return false; + } + + // Build response data + RSP_HDR(response, 0x8E); + + response[2] = read_request.mode; // Mode + response[3] = read_request.eq_index & 0xFF; // EQ index + + if (read_request.mode >= EQ_DISABLED_MODE || read_request.mode > EQ_MODE_MAX) { + // 返回全0 + response[4] = 0; // Filter type + memset(&response[5], 0, 16); // 所有参数(fc, q, bw, gain)都设为0 + } else { + // 优先使用44100Hz数据,因为串口设置参数时主要存储到44100Hz + filter_params_t *band = &sEQ_data_44100HZ[read_request.mode][0].bands[read_request.eq_index]; + response[4] = band->type; + + // Copy parameters - 直接写入float值 (位置调整) + memcpy(&response[5], &band->fc, 4); + memcpy(&response[9], &band->q, 4); + memcpy(&response[13], &band->bw, 4); + memcpy(&response[17], &band->gain, 4); + } + + // Copy coefficients + read_request.pending_cmd = 0; // Clear read request flag + + return true; + } +#endif +#if 1 + if (key_program == 1) + { + key_program = 0; + get_key_ret(response); + return true; + } +#endif + return false; +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.h new file mode 100644 index 0000000..a5b387b --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq.h @@ -0,0 +1,149 @@ +#ifndef __HEADER_EQ_HANDLER__ +#define __HEADER_EQ_HANDLER__ + +#pragma once + +#include "xmath/xmath.h" +#include +#include + +#define NUM_EQ_MODES 10 +#define NUM_EQ_CHANS 2 +#define MAX_EQ_BANDS 8 +#define EQ_DISABLED_MODE 10 // 禁用EQ的模式编号 +#define EQ_PRESET_MODE_MIN 0 // 预设模式最小编号 +#define EQ_PRESET_MODE_MAX 6 // 预设模式最大编号(preset1-preset6) +#define EQ_USER_MODE_MIN 7 // 用户模式最小编号 +#define EQ_USER_MODE_MAX 9 // 用户模式最大编号(user 1-4) +#define EQ_MODE_MAX (NUM_EQ_MODES) // 模式最大编号(user 1-4) + + +// Filter type definition +typedef enum { + FILTER_TYPE_BYPASS = 0x00, + FILTER_TYPE_ALLPASS = 0x01, + FILTER_TYPE_PEAKING = 0x02, + FILTER_TYPE_LOWPASS = 0x03, + FILTER_TYPE_HIGHPASS = 0x04, + FILTER_TYPE_BANDPASS = 0x05, + FILTER_TYPE_BANDSTOP = 0x06, + FILTER_TYPE_NOTCH = 0x07, + FILTER_TYPE_CONST_Q = 0x08, + FILTER_TYPE_LOWSHELF = 0x09, + FILTER_TYPE_HIGHSHELF = 0x0A +} filter_type_t; + + +// Filter parameter structure +typedef struct { + float fc; // Center frequency (Hz) + float q; // Q value + float bw; // Bandwidth (Hz) + float gain; // Gain (dB) + uint8_t index; // Filter index + filter_type_t type; // Filter type + uint8_t padding[2]; // 添加填充字节确保结构体对齐到4字节边界 +} filter_params_t; + +// Filter biquad structure for runtime calculation +typedef struct { + int32_t coef[5][8]; // 5×8×4 = 160字节 +} filter_biquad_s32_coef_t; + +// 注意:以下结构体定义暂时注释,因为它们可能在其他文件中定义 +// 如果需要使用,请确保包含相应的头文件 + +// 优化后的Flash存储结构 - 只存储非默认滤波器 +typedef struct { + uint8_t active_bands_count; // 实际使用的滤波器数量 + filter_params_t active_bands[MAX_EQ_BANDS]; // 只存储非bypass的滤波器 +} eq_flash_data_t; + +// 单个EQ参数变化记录结构(优化版) +typedef struct { + uint32_t sample_rate; // 采样率 + uint8_t mode; // 模式 + uint8_t channel; // 通道 + uint8_t band_index; // 滤波器索引 + filter_params_t params; // 单个滤波器参数(不包含系数) + uint32_t timestamp; // 时间戳 + uint8_t is_dirty; // 脏标志 +} __attribute__((packed)) eq_single_param_t; + +// 单参数存储数据库 +typedef struct { + eq_single_param_t params[MAX_EQ_BANDS * 8 * 2]; // 最多存储8个模式×2通道的所有参数 + uint16_t param_count; // 当前参数数量 + uint16_t max_params; // 最大参数数量 + uint32_t last_save_time; // 最后保存时间 +} eq_single_param_database_t; + +// 设备信息结构体 +typedef struct { + char mode_names[NUM_EQ_MODES][16]; // 模式名称数组,每个16字节 + int32_t mode_gains[NUM_EQ_MODES]; // 模式增益数组,每个4字节 +} __attribute__((packed)) eq_mode_info_t; + +// Define EQ data structure for each sample rate +typedef struct { + unsigned sample_rate; // Sample rate + int32_t total_bshift; + int32_t post_gain_db; + filter_params_t bands[MAX_EQ_BANDS]; // Filter parameters + filter_biquad_s32_t filter; // Filter data (运行时计算) +} __attribute__((packed)) eq_mode_data_t; + + +uint32_t init_eq_data(unsigned sample_freq); + +void clear_eq_status(unsigned sample_freq, unsigned ch_no); + +int32_t handler_eq_filter( unsigned sample_freq, uint32_t ch, int32_t new_sample); + +void init_eq_hid_params(void); + +// EQ参数处理函数 - 用于串口接收的EQ参数转换和存储 +int process_eq_params_from_uart(uint8_t eq_mode, uint8_t eq_index, uint32_t sample_rate, float coefficients[5]); + +// 模式信息处理函数 +#ifdef __XC__ +// 独立的增益和名称设置/获取函数 +void set_mode_gain(uint8_t mode, int32_t gain); +void set_mode_name(uint8_t mode, const char* unsafe name); +void get_mode_gain(uint8_t mode, int32_t* unsafe gain); +void get_mode_name(uint8_t mode, char* unsafe name); +uint8_t process_uart_set_eq_mode(uint8_t mode); +uint8_t process_uart_get_eq_mode(uint8_t * unsafe response, uint8_t response_size); +uint8_t process_uart_set_mode_gain_and_name(uint8_t * unsafe data); +uint8_t process_uart_set_eq_params(uint8_t * unsafe data); +uint8_t process_uart_get_eq_params(uint8_t * unsafe data, uint8_t * unsafe response, uint8_t response_size); +uint8_t process_uart_reset_eq_params(uint8_t * unsafe data); +uint8_t process_uart_get_eq_mode_count(uint8_t * unsafe response, uint8_t response_size); +uint8_t process_uart_set_and_save_eq_mode(uint8_t * unsafe data); + +#else +// 独立的增益和名称设置/获取函数 +void set_mode_gain(uint8_t mode, int32_t gain); +void set_mode_name(uint8_t mode, const char* name); +void get_mode_gain(uint8_t mode, int32_t* gain); +void get_mode_name(uint8_t mode, char* name); +uint8_t process_uart_set_eq_mode(uint8_t mode); +uint8_t process_uart_get_eq_mode(uint8_t *response, uint8_t response_size); +uint8_t process_uart_set_mode_gain_and_name(uint8_t *data); +uint8_t process_uart_set_eq_params(uint8_t *data); +uint8_t process_uart_get_eq_params(uint8_t *data, uint8_t *response, uint8_t response_size); +uint8_t process_uart_reset_eq_params(uint8_t *data); +uint8_t process_uart_get_eq_mode_count(uint8_t *response, uint8_t response_size); +uint8_t process_uart_set_and_save_eq_mode(uint8_t *data); + + +#endif +void init_mode_info(void); + +uint8_t reset_eq_params(uint8_t mode); + +// 动态系数计算函数声明 +int calculate_current_mode_coefficients(uint32_t sample_rate, uint8_t mode); + +// 串口EQ命令处理函数声明 +#endif //__HEADER_EQ_HANDLER__ diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.c new file mode 100644 index 0000000..dd4fbb3 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.c @@ -0,0 +1,143 @@ +#define DEBUG_PRINT_ENABLE 0 +#include "eq_coefficient_calculator.h" +#include "biquad_standalone.h" +#include + +#if 0 // DEBUG_PRINT_ENABLE +#define DPRINTF(...) printf(__VA_ARGS__) +#else +#define DPRINTF(...) +#endif + + +// 系数计算函数 +int eq_calculate_coefficients_from_params(filter_params_t *params, + uint32_t sample_rate, + q2_30 coeffs[5], + int *bshift) { + + if (!params || !coeffs || !bshift) { + return -1; + } + + // 根据滤波器类型调用相应的biquad计算函数 + switch (params->type) { + case FILTER_TYPE_BYPASS: + eq_biquad_bypass(coeffs); + *bshift = 0; + break; + + case FILTER_TYPE_LOWPASS: + eq_biquad_lowpass(coeffs, + params->fc, + (float)sample_rate, + params->q); + *bshift = 0; + break; + + case FILTER_TYPE_HIGHPASS: + eq_biquad_highpass(coeffs, + params->fc, + (float)sample_rate, + params->q); + *bshift = 0; + break; + + case FILTER_TYPE_BANDPASS: + case FILTER_TYPE_BANDSTOP: + case FILTER_TYPE_NOTCH: + case FILTER_TYPE_CONST_Q: + // These filter types have been removed to save memory. + // Fall through to bypass behavior. + eq_biquad_bypass(coeffs); + *bshift = 0; + break; + + case FILTER_TYPE_ALLPASS: + eq_biquad_allpass(coeffs, + params->fc, + (float)sample_rate, + params->q); + *bshift = 0; + break; + + case FILTER_TYPE_PEAKING: + *bshift = eq_biquad_peaking(coeffs, + params->fc, + (float)sample_rate, + params->q, + params->gain); + break; + + case FILTER_TYPE_LOWSHELF: + *bshift = eq_biquad_lowshelf(coeffs, + params->fc, + (float)sample_rate, + params->q, + params->gain); + break; + + case FILTER_TYPE_HIGHSHELF: + *bshift = eq_biquad_highshelf(coeffs, + params->fc, + (float)sample_rate, + params->q, + params->gain); + break; + + default: + // 默认使用bypass + eq_biquad_bypass(coeffs); + *bshift = 0; + break; + } + + #if 1 //DEBUG_PRINT_ENABLE == 1 + DPRINTF("--------------------------------\n"); + for(int i = 0; i < 5; i++) { + if (coeffs[i] < 0) { + DPRINTF("coeffs[%d]: -0x%08x\n", i, (unsigned int)(-coeffs[i])); + } else { + DPRINTF("coeffs[%d]: 0x%08x\n", i, (unsigned int)coeffs[i]); + } + } + DPRINTF("--------------------------------\n"); + #endif + + return 0; +} + +// 批量计算所有滤波器的系数 +int eq_calculate_all_coefficients(eq_mode_data_t *eq_data, + uint32_t sample_rate) { + + if (!eq_data) { + return -1; + } + + int total_bshift = 0; + + // 计算每个滤波器的系数 + for (int i = 0; i < MAX_EQ_BANDS; i++) { + q2_30 coeffs[5]; + int bshift; + + if (eq_calculate_coefficients_from_params(&eq_data->bands[i], + sample_rate, coeffs, &bshift) == 0) { + // 存储计算出的系数 + for (int j = 0; j < 5; j++) { + eq_data->filter.coef[j][i] = coeffs[j]; + } + total_bshift += bshift; + } + } + + // 更新总bshift + eq_data->total_bshift = total_bshift; + + #if DEBUG_PRINT_ENABLE == 1 + DPRINTF("total_bshift: %d\n", total_bshift); + #endif + + return 0; +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.h new file mode 100644 index 0000000..9539cd4 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_coefficient_calculator.h @@ -0,0 +1,17 @@ +#ifndef EQ_COEFFICIENT_CALCULATOR_H +#define EQ_COEFFICIENT_CALCULATOR_H + +#include "eq.h" +#include "biquad_standalone.h" + +// 系数计算函数 +int eq_calculate_coefficients_from_params(filter_params_t *params, + uint32_t sample_rate, + q2_30 coeffs[5], + int *bshift); + +// 批量计算所有滤波器的系数 +int eq_calculate_all_coefficients(eq_mode_data_t *eq_data, + uint32_t sample_rate); + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.py b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.py new file mode 100644 index 0000000..31bbda3 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.py @@ -0,0 +1,3634 @@ +from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, + QHBoxLayout, QPushButton, QComboBox, QLabel, + QScrollArea, QSplitter, QGroupBox, QFormLayout, + QDoubleSpinBox, QCheckBox, QLineEdit, QSpinBox, + QMessageBox, QMenuBar, QMenu) +from PySide6.QtCore import Qt, QObject, Signal, Slot, QTimer +import numpy as np +import matplotlib +# 设置字体为支持中文的字体 +matplotlib.rcParams['font.family'] = ['Microsoft YaHei', 'SimHei', 'SimSun'] +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg +from matplotlib.figure import Figure +import json +from pathlib import Path +import os +import hid # 添加hidapi导入 +import logging # 添加logging模块 +import struct # 添加struct模块用于float打包/解包 + +# 定义日志级别 +LOG_LEVEL_ERROR = 0 # 错误信息 +LOG_LEVEL_WARN = 1 # 警告信息 +LOG_LEVEL_INFO = 2 # 一般信息 +LOG_LEVEL_DEBUG = 3 # 调试信息 +LOG_LEVEL_VERBOSE = 4 # 详细信息 + +# 日志级别名称映射 +LOG_LEVEL_NAMES = { + LOG_LEVEL_ERROR: "错误", + LOG_LEVEL_WARN: "警告", + LOG_LEVEL_INFO: "信息", + LOG_LEVEL_DEBUG: "调试", + LOG_LEVEL_VERBOSE: "详细" +} + +def log_message(level, message, current_level=LOG_LEVEL_INFO): + """打印日志信息 + Args: + level: 消息的日志级别 + message: 要打印的消息 + current_level: 当前的日志级别设置 + """ + if level <= current_level: + prefix = f"[{LOG_LEVEL_NAMES[level]}]" + print(f"{prefix} {message}") + +# 导入滤波器计算器 +from filter_utils import (BiquadFilterCalculator, make_biquad_bypass, make_biquad_lowpass, + make_biquad_highpass, make_biquad_bandpass, make_biquad_bandstop, + make_biquad_notch, make_biquad_allpass, make_biquad_peaking, + make_biquad_constant_q, make_biquad_lowshelf, make_biquad_highshelf) + +# 常量定义 +MAX_EQ_BANDS = 8 # 最大EQ滤波器数量 (0-7) +NUM_EQ_MODES = 10 # EQ模式数量 +EQ_DISABLED_MODE = 10 # 禁用EQ的模式编号 + +class BandFilter(QGroupBox): + """单个滤波器带控件""" + def __init__(self, band_id, parent=None): + super().__init__(f"滤波器 {band_id}", parent) + self.band_id = band_id + self.parent_window = parent + self.calculator = BiquadFilterCalculator() # 创建滤波器计算器实例 + + layout = QFormLayout() + + # 启用/禁用复选框 - 默认启用但隐藏 + self.enabled = QCheckBox() + self.enabled.setChecked(True) + #self.enabled.hide() # 隐藏复选框 + + # 滤波器类型选择 - 默认为全通 + self.filter_type = QComboBox() + self.filter_type.addItems([ + "bypass滤波器", # 新增bypass类型 + "全通滤波器", "峰值滤波器", "低通滤波器", "高通滤波器", + "带通滤波器", "带阻滤波器", "陷波滤波器", "恒定Q值滤波器", + "低架滤波器", "高架滤波器" + ]) + self.filter_type.setCurrentText("bypass滤波器") # 设置默认值为bypass + self.filter_type.currentTextChanged.connect(self.on_filter_type_changed) + layout.addRow("类型:", self.filter_type) + + # 中心频率 + self.fc = QSpinBox() + self.fc.setRange(20, 96000) + self.fc.setValue(1000) + self.fc.setSuffix(" Hz") + self.fc.valueChanged.connect(self.on_parameter_changed) + layout.addRow("频率:", self.fc) + + # Q值 + self.Q = QDoubleSpinBox() + self.Q.setRange(0.01, 100) + self.Q.setValue(0.707) + self.Q.setSingleStep(0.1) + self.Q.valueChanged.connect(self.on_parameter_changed) + layout.addRow("Q值:", self.Q) + + # 带宽 + self.bw = QSpinBox() + self.bw.setRange(1, 192000) + self.bw.setValue(1) + self.bw.valueChanged.connect(self.on_parameter_changed) + layout.addRow("带宽:", self.bw) + + # 增益 + self.gain = QDoubleSpinBox() + self.gain.setRange(-80, 80) + self.gain.setValue(0) + self.gain.setSuffix(" dB") + self.gain.valueChanged.connect(self.on_parameter_changed) + layout.addRow("增益:", self.gain) + + # 添加bshift显示 + self.bshift_label = QLabel("bshift: 0") + #layout.addRow("bshift:", self.bshift_label) + self.bshift_label.hide() + + # 系数显示 + self.coeffs_label = QLineEdit() + self.coeffs_label.setReadOnly(True) # 设置为只读 + self.coeffs_label.setMinimumWidth(300) # 设置最小宽度 + self.coeffs_label.setStyleSheet(""" + QLineEdit { + background-color: #f0f0f0; + border: 1px solid #cccccc; + padding: 2px; + font-family: monospace; + } + """) + self.coeffs_label.setText("Coefficients: Not calculated") + self.coeffs_label.setToolTip("Filter coefficients in Q30 format") + #layout.addRow("系数:", self.coeffs_label) + self.coeffs_label.hide() + + self.setLayout(layout) + + # 初始化时更新系数 + if self.parent_window and self.parent_window.show_qshift_debug: + self.update_coefficients() + + def on_filter_type_changed(self): + """当滤波器类型改变时更新参数显示""" + filter_type = self.filter_type.currentText() + + # 隐藏所有参数 + self.fc.hide() + self.Q.hide() + self.bw.hide() + self.gain.hide() + + # 根据滤波器类型显示相应参数 + if filter_type in ["峰值滤波器", "低通滤波器", "高通滤波器", "陷波滤波器", + "全通滤波器", "恒定Q值滤波器", "低架滤波器", "高架滤波器"]: + self.fc.show() + self.Q.show() + if filter_type in ["峰值滤波器", "恒定Q值滤波器", "低架滤波器", "高架滤波器"]: + self.gain.show() + elif filter_type in ["带通滤波器", "带阻滤波器"]: + self.fc.show() + self.bw.show() + + elif filter_type == "bypass滤波器": + pass + + self.on_parameter_changed() + + def on_parameter_changed(self): + """当参数改变时更新系数和响应曲线""" + if self.parent_window and self.parent_window.show_qshift_debug: + self.update_coefficients() + if self.parent_window: + self.parent_window.update_eq_curve() + + def update_coefficients(self): + """更新滤波器系数""" + if not self.enabled.isChecked(): + if self.parent_window and self.parent_window.show_qshift_debug: + log_message(LOG_LEVEL_DEBUG, f"滤波器 {self.band_id} 已禁用,bshift = 0", self.parent_window.log_level) + return + + try: + fs = float(self.parent_window.sample_rate.currentText()) + coeffs = self.calculate_coefficients(fs) + + # 只有在显示系数时才更新显示 + if self.parent_window and self.parent_window.show_qshift_debug and coeffs is not None: + # 反转 b1 和 b2 的符号用于显示 + display_coeffs = [ + coeffs[0], # a0 + coeffs[1], # a1 + coeffs[2], # a2 + -coeffs[3], # b1 符号取反 + -coeffs[4] # b2 符号取反 + ] + + # 计算并更新bshift + bshift = self.calculate_bshift(display_coeffs) + + # 计算缩放后的系数 + scale_factor = 2 ** (-bshift) + + # 更新系数显示 + self.coeffs_label.setText( + f"Coefficients: {display_coeffs[0]:.16f}, {display_coeffs[1]:.16f}, " + f"{display_coeffs[2]:.16f}, {display_coeffs[3]:.16f}, {display_coeffs[4]:.16f}\n" + f"Scaled: {display_coeffs[0] * scale_factor:.16f}, {display_coeffs[1] * scale_factor:.16f}, " + f"{display_coeffs[2] * scale_factor:.16f}, {display_coeffs[3] * scale_factor:.16f}, {display_coeffs[4] * scale_factor:.16f}" + ) + + except Exception as e: + if self.parent_window and self.parent_window.show_qshift_debug: + self.coeffs_label.setText("Coefficients: Calculation error") + self.bshift_label.setText("bshift: 0") + + def calculate_coefficients(self, fs): + """计算滤波器系数""" + try: + filter_type = self.filter_type.currentText() + + # 定义filter_calculator支持的类型 + calculator_types = { + "峰值滤波器": "peak", + "低通滤波器": "lowpass", + "高通滤波器": "highpass", + "带通滤波器": "bandpass", + "陷波滤波器": "notch", + "低架滤波器": "lowshelf", + "高架滤波器": "highshelf" + } + + # 如果是filter_calculator支持的类型,使用filter_calculator + if filter_type in calculator_types: + coeffs = self.calculator.calculate( + calculator_types[filter_type], + self.fc.value(), + fs, + self.Q.value(), + self.gain.value(), + self.bw.value() if filter_type in ['bandpass', 'bandstop'] else None + ) + return [coeffs['a0'], coeffs['a1'], coeffs['a2'], coeffs['b1'], coeffs['b2']] + + # 对于不支持的类型,使用biquad.py中的算法 + if filter_type == "全通滤波器": + coeffs = make_biquad_allpass(fs, self.fc.value(), self.Q.value()) + elif filter_type == "带阻滤波器": + coeffs = make_biquad_bandstop(fs, self.fc.value(), self.bw.value()) + elif filter_type == "恒定Q值滤波器": + coeffs = make_biquad_constant_q(fs, self.fc.value(), self.Q.value(), self.gain.value()) + elif filter_type == "bypass滤波器": + coeffs = make_biquad_bypass(fs) + else: + # 如果都不支持,使用bypass + coeffs = make_biquad_bypass(fs) + + return coeffs + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"计算系数时出错: {str(e)}", self.parent_window.log_level if self.parent_window else LOG_LEVEL_ERROR) + return None + + def get_parameters(self): + """获取当前参数""" + params = { + "enabled": self.enabled.isChecked(), + "type": self.filter_type.currentText(), + "fc": self.fc.value(), + "Q": self.Q.value(), + "bw": self.bw.value(), + "gain": self.gain.value() + } + return params + + def set_parameters(self, params): + """设置滤波器参数""" + try: + # 设置启用状态 + self.enabled.setChecked(params.get("enabled", True)) + + # 设置滤波器类型 + filter_type = params.get("type", "全通滤波器") + self.filter_type.setCurrentText(filter_type) + + # 设置频率 + self.fc.setValue(params.get("fc", 1000)) + + # 设置Q值 + self.Q.setValue(params.get("Q", 0.707)) + + # 设置带宽 + self.bw.setValue(params.get("bw", 1)) + + # 设置增益 + self.gain.setValue(params.get("gain", 0)) + + # 更新显示 + self.on_parameter_changed() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置滤波器参数时出错: {str(e)}", self.parent_window.log_level if self.parent_window else LOG_LEVEL_ERROR) + + def calculate_bshift(self, coeffs): + """根据最大b系数的二进制位宽自动计算bshift(与_get_bshift一致,直接返回bshift)""" + if not self.enabled.isChecked(): + if self.parent_window and self.parent_window.show_qshift_debug: + log_message(LOG_LEVEL_DEBUG, f"滤波器 {self.band_id} 已禁用,bshift = 0", self.parent_window.log_level) + return 0 + b_coeffs = coeffs[:3] + max_b = max(abs(x) for x in b_coeffs) + if self.parent_window and self.parent_window.show_qshift_debug: + log_message(LOG_LEVEL_DEBUG, f"滤波器 {self.band_id} b系数最大值: {max_b:.6f}", self.parent_window.log_level) + if max_b != 0: + bshift = int(np.floor(np.log2(max_b))) + else: + bshift = 0 + bshift = bshift if bshift >= 0 else 0 + if self.parent_window and self.parent_window.show_qshift_debug: + log_message(LOG_LEVEL_DEBUG, f"滤波器 {self.band_id} bshift={bshift}", self.parent_window.log_level) + self.bshift_label.setText(f"bshift: {bshift}") + return bshift + + def get_bshift(self): + """获取当前滤波器的bshift""" + coeffs = self.calculate_coefficients(float(self.parent_window.sample_rate.currentText())) + if coeffs is None: + return 0 + bshift = self.calculate_bshift(coeffs) + return bshift + + def get_scale_factor(self): + """获取当前滤波器的scale_factor(由bshift计算)""" + bshift = self.get_bshift() + scale_factor = 2 ** (-bshift) + return scale_factor + +class MplCanvas(FigureCanvasQTAgg): + def __init__(self, parent=None, width=5, height=4, dpi=100): + fig = Figure(figsize=(width, height), dpi=dpi) + self.axes = fig.subplots(1, 1) # 改为1行1列 + + # 更新标题为中文 + fig.suptitle("PEQ响应曲线") + + # 设置子图属性 + self.axes.set_xscale('log') + self.axes.set_xlabel('频率 (Hz)') + self.axes.set_ylabel('增益 (dB)') + self.axes.grid(True, which='both', linestyle='-', alpha=0.3) + self.axes.set_xlim(20, 20000) + self.axes.set_ylim(-30, 30) + + super().__init__(fig) + + def update_plot(self, f, h, fs): + """更新响应曲线""" + try: + # 计算幅度响应 + h_db = 20 * np.log10(np.abs(h)) + + # 清除当前图形 + self.axes.clear() + + # 绘制新的响应曲线 + self.axes.semilogx(f, h_db, 'b-', linewidth=2, label='总响应') + + # 设置x轴范围为20Hz到采样率的一半 + x_max = fs / 2 + self.axes.set_xlim([20, x_max]) + + # 设置x轴刻度 + x_ticks = [20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000] + if x_max > 20000: + x_ticks.extend([40000, 80000]) + self.axes.set_xticks(x_ticks) + + # 设置网格 + self.axes.grid(True, which='both', linestyle='-', alpha=0.3) + + # 更新y轴和x轴标签 + self.axes.set_ylim([-30, 30]) + self.axes.set_ylabel("幅度 (dB)") + self.axes.set_xlabel("频率 (Hz)") + + # 添加图例 + self.axes.legend(prop={'family': ['Microsoft YaHei', 'SimHei', 'SimSun']}) + + # 重绘图形 + self.draw() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"更新响应曲线时出错: {str(e)}", self.parent_window.log_level if self.parent_window else LOG_LEVEL_ERROR) + +class EQDesigner(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("EQ参数设计器") + self.setMinimumSize(1200, 800) + + # 添加控制是否显示系数的变量 + self.show_qshift_debug = False # 默认不显示qshift调试信息 + self.current_mode = 0 # 初始化当前模式为0 + self.total_bshift = 0 # 初始化total_bshift为0 + self.log_level = LOG_LEVEL_VERBOSE # 设置默认日志级别 + + # 创建菜单栏 + # 改动原因:添加菜单栏,包含视图菜单用于隐藏/显示左侧面板的各组 + self.create_menu_bar() + + # 创建主窗口部件 + main_widget = QWidget() + self.setCentralWidget(main_widget) + + # 创建主布局 + main_layout = QHBoxLayout(main_widget) + + # 创建分割器 + splitter = QSplitter(Qt.Horizontal) + + # 左侧控制面板 + left_panel = QWidget() + left_layout = QVBoxLayout(left_panel) + left_layout.setContentsMargins(5, 5, 5, 5) # 设置边距 + + # 创建左侧面板内容的滚动区域,让所有内容都能滚动查看 + # 改动原因:解决左侧面板内容太多显示不下的问题,添加滚动条让所有内容都能查看 + left_scroll = QScrollArea() + left_scroll.setWidgetResizable(True) + left_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) # 需要时显示水平滚动条 + left_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # 需要时显示垂直滚动条 + + # 创建左侧内容容器 + left_content = QWidget() + left_content_layout = QVBoxLayout(left_content) + left_content_layout.setContentsMargins(5, 5, 5, 5) + + # 滤波器带滚动区域(仅用于滤波器带的滚动) + # 改动原因:增加滤波器参数的空间,设置最小高度以确保有足够的显示空间 + bands_scroll = QScrollArea() + bands_scroll.setWidgetResizable(True) + bands_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + bands_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + bands_scroll.setMinimumHeight(400) # 设置最小高度400像素,给滤波器参数更多显示空间 + self.bands_widget = QWidget() + self.bands_layout = QVBoxLayout(self.bands_widget) + bands_scroll.setWidget(self.bands_widget) + left_content_layout.addWidget(bands_scroll) + + # 采样率选择 + sample_rate_layout = QHBoxLayout() + sample_rate_layout.addWidget(QLabel("采样率:")) + self.sample_rate = QComboBox() + self.sample_rate.addItems(["44100", "48000", "88200", "96000", "176400", "192000"]) + self.sample_rate.currentTextChanged.connect(self.on_sample_rate_changed) + sample_rate_layout.addWidget(self.sample_rate) + left_content_layout.addLayout(sample_rate_layout) + + # 添加调试选项 + debug_layout = QHBoxLayout() + self.debug_checkbox = QCheckBox("显示bshift调试信息") + self.debug_checkbox.stateChanged.connect(self.on_debug_changed) + self.debug_checkbox.hide() + debug_layout.addWidget(self.debug_checkbox) + + # 添加日志级别选择 + log_level_layout = QHBoxLayout() + self.debug_label = QLabel("调试信息:") + self.debug_label.hide() + log_level_layout.addWidget(self.debug_label) + self.log_level_combo = QComboBox() + self.log_level_combo.addItems(["错误", "警告", "信息", "调试", "详细"]) + self.log_level_combo.setCurrentIndex(2) # 默认选择"信息"级别 + self.log_level_combo.currentIndexChanged.connect(self.on_log_level_changed) + self.log_level_combo.hide() + + log_level_layout.addWidget(self.log_level_combo) + debug_layout.addLayout(log_level_layout) + left_content_layout.addLayout(debug_layout) + + # 添加设备选择下拉框 + device_layout = QHBoxLayout() + device_layout.addWidget(QLabel("USB设备:")) + self.device_combo = QComboBox() + print(self.device_combo) + device_layout.addWidget(self.device_combo) + refresh_button = QPushButton("获取设备") + refresh_button.clicked.connect(self.refresh_devices) + device_layout.addWidget(refresh_button) + left_content_layout.addLayout(device_layout) + + # 保存各组引用,用于菜单控制可见性 + # 改动原因:保存各组引用,以便在菜单中控制可见性,EQ参数相关的组(bands_scroll和param_group)要始终显示 + self.ui_groups = {} + + # 添加设备信息显示组 + device_info_group = QGroupBox("设备信息") + self.ui_groups['device_info'] = device_info_group # 保存引用 + device_info_layout = QFormLayout(device_info_group) + + # 设备信息显示标签 + self.device_pid_label = QLabel("PID: 未知") + self.device_vid_label = QLabel("VID: 未知") + self.device_product_label = QLabel("产品: 未知") + self.device_vendor_label = QLabel("厂商: 未知") + self.device_serial_label = QLabel("序列号: 未知") + + device_info_layout.addRow("产品ID:", self.device_pid_label) + device_info_layout.addRow("厂商ID:", self.device_vid_label) + device_info_layout.addRow("产品名称:", self.device_product_label) + device_info_layout.addRow("厂商名称:", self.device_vendor_label) + device_info_layout.addRow("序列号:", self.device_serial_label) + + # 获取设备信息按钮 + self.get_device_info_btn = QPushButton("获取设备信息") + self.get_device_info_btn.clicked.connect(self.on_get_device_info) + device_info_layout.addRow(self.get_device_info_btn) + + left_content_layout.addWidget(device_info_group) + + + # 添加音效模式设置组 + mode_group = QGroupBox("音效模式设置") + self.ui_groups['mode'] = mode_group # 保存引用 + mode_layout = QFormLayout(mode_group) + self.mode_label = QLabel("音效模式:") + self.mode_combo = QComboBox() + self.mode_combo.addItems(["预设模式1", "预设模式2", "预设模式3", "预设模式4", "预设模式5", "预设模式6", "用户模式1", "用户模式2", "用户模式3", "bypass", "禁用EQ"]) + self.mode_combo.currentIndexChanged.connect(self.on_mode_changed) + mode_layout.addRow(self.mode_label, self.mode_combo) + + # 模式名称编辑 + self.mode_name_label = QLabel("模式名称:") + self.mode_name_edit = QLineEdit() + self.mode_name_edit.setMaxLength(15) # 限制16字节,但显示15个字符 + self.mode_name_edit.setPlaceholderText("输入模式名称(最多15个字符)") + self.mode_name_edit.textChanged.connect(self.on_mode_name_changed) + mode_layout.addRow(self.mode_name_label, self.mode_name_edit) + + # 切换音效模式按钮(只发送0x8A命令) + self.switch_mode_btn = QPushButton("切换音效模式") + self.switch_mode_btn.clicked.connect(self.on_switch_mode) + self.switch_mode_btn.hide() + mode_layout.addRow(self.switch_mode_btn) + + # 获取音效模式按钮(只处理0x8B命令) + # 改动原因:移除hide()调用,让按钮显示出来,方便用户获取当前模式 + self.get_mode_btn = QPushButton("获取当前音效模式") + self.get_mode_btn.clicked.connect(self.on_get_mode) + mode_layout.addRow(self.get_mode_btn) + + # 设置并保存音效模式按钮(发送0x92命令) + self.set_and_save_mode_btn = QPushButton("设置并保存音效模式") + self.set_and_save_mode_btn.clicked.connect(self.on_set_and_save_mode) + mode_layout.addRow(self.set_and_save_mode_btn) + + left_content_layout.addWidget(mode_group) + + # 添加音效参数配置组(EQ参数相关,可通过菜单隐藏) + # 改动原因:将音效参数配置组也添加到ui_groups中,使其可以通过菜单隐藏/显示 + param_group = QGroupBox("音效参数配置") + self.ui_groups['param'] = param_group # 保存引用,用于菜单控制可见性 + param_layout = QVBoxLayout(param_group) + + # 配置音效参数按钮 + self.config_params_btn = QPushButton("配置音效参数") + self.config_params_btn.clicked.connect(self.on_config_params) + param_layout.addWidget(self.config_params_btn) + + # 读取音效参数按钮 + self.read_params_btn = QPushButton("读取音效参数") + self.read_params_btn.hide() + self.read_params_btn.clicked.connect(self.on_read_params) + param_layout.addWidget(self.read_params_btn) + + # 设置整体增益按钮 + self.set_gain_btn = QPushButton("设置整体增益") + self.set_gain_btn.clicked.connect(self.on_set_gain) + param_layout.addWidget(self.set_gain_btn) + + # 复位EQ参数按钮 + self.reset_eq_btn = QPushButton("复位EQ参数") + self.reset_eq_btn.clicked.connect(self.on_reset_eq_params) + param_layout.addWidget(self.reset_eq_btn) + + # 获取EQ模式总数按钮 + self.get_mode_count_btn = QPushButton("获取EQ模式总数") + self.get_mode_count_btn.clicked.connect(self.on_get_mode_count) + param_layout.addWidget(self.get_mode_count_btn) + + left_content_layout.addWidget(param_group) + + # 添加音量控制组 + volume_group = QGroupBox("音量控制") + self.ui_groups['volume'] = volume_group # 保存引用 + volume_layout = QFormLayout(volume_group) + + # 音量级别显示和设置 + volume_control_layout = QHBoxLayout() + self.volume_label = QLabel("音量级别:") + self.volume_spin = QSpinBox() + self.volume_spin.setRange(0, 60) + self.volume_spin.setValue(60) + self.volume_spin.setSuffix(" (0=最小, 60=最大)") + volume_control_layout.addWidget(self.volume_label) + volume_control_layout.addWidget(self.volume_spin) + volume_layout.addRow(volume_control_layout) + + # 设置音量按钮 + self.set_volume_btn = QPushButton("设置音量") + self.set_volume_btn.clicked.connect(self.on_set_volume) + volume_layout.addRow(self.set_volume_btn) + + # 读取音量按钮 + self.get_volume_btn = QPushButton("读取音量") + self.get_volume_btn.clicked.connect(self.on_get_volume) + volume_layout.addRow(self.get_volume_btn) + + # 读取采样率和格式按钮 + self.get_sample_format_btn = QPushButton("读取采样率和格式") + self.get_sample_format_btn.clicked.connect(self.on_get_sample_format) + volume_layout.addRow(self.get_sample_format_btn) + + left_content_layout.addWidget(volume_group) + + # 添加设备模式控制组(增益模式、滤波器模式、游戏模式) + device_mode_group = QGroupBox("设备模式控制") + self.ui_groups['device_mode'] = device_mode_group # 保存引用 + device_mode_layout = QFormLayout(device_mode_group) + + # 增益模式 + gain_control_layout = QHBoxLayout() + self.gain_mode_combo = QComboBox() + self.gain_mode_combo.addItem("低阻模式 (0)", 0) + self.gain_mode_combo.addItem("高阻模式 (1)", 1) + gain_control_layout.addWidget(QLabel("增益模式:")) + gain_control_layout.addWidget(self.gain_mode_combo) + device_mode_layout.addRow(gain_control_layout) + + gain_btn_layout = QHBoxLayout() + self.set_gain_mode_btn = QPushButton("设置增益模式") + self.set_gain_mode_btn.clicked.connect(self.on_set_gain_mode) + self.get_gain_mode_btn = QPushButton("读取增益模式") + self.get_gain_mode_btn.clicked.connect(self.on_get_gain_mode) + gain_btn_layout.addWidget(self.set_gain_mode_btn) + gain_btn_layout.addWidget(self.get_gain_mode_btn) + device_mode_layout.addRow(gain_btn_layout) + + # 滤波器模式 + # 改动原因:添加第8种滤波器模式,支持0-7共8种模式 + filter_control_layout = QHBoxLayout() + self.filter_mode_combo = QComboBox() + self.filter_mode_combo.addItem("Minimum Phase filter (default) (0)", 0) + self.filter_mode_combo.addItem("Linear Phase Apodizing Fast Roll-off filter (1)", 1) + self.filter_mode_combo.addItem("Linear phase fast roll-off filter (2)", 2) + self.filter_mode_combo.addItem("Linear Phase Fast Roll-off low-ripple filter (3)", 3) + self.filter_mode_combo.addItem("Linear phase slow roll-off filter (4)", 4) + self.filter_mode_combo.addItem("Minimum phase fast roll-off filter (5)", 5) + self.filter_mode_combo.addItem("Minimum phase slow roll-off filter (6)", 6) + self.filter_mode_combo.addItem("Minimum Phase Fast Roll-Off Low Dispersion (7)", 7) + filter_control_layout.addWidget(QLabel("滤波器模式:")) + filter_control_layout.addWidget(self.filter_mode_combo) + device_mode_layout.addRow(filter_control_layout) + + filter_btn_layout = QHBoxLayout() + self.set_filter_mode_btn = QPushButton("设置滤波器模式") + self.set_filter_mode_btn.clicked.connect(self.on_set_filter_mode) + self.get_filter_mode_btn = QPushButton("读取滤波器模式") + self.get_filter_mode_btn.clicked.connect(self.on_get_filter_mode) + filter_btn_layout.addWidget(self.set_filter_mode_btn) + filter_btn_layout.addWidget(self.get_filter_mode_btn) + device_mode_layout.addRow(filter_btn_layout) + + # 游戏模式 + game_control_layout = QHBoxLayout() + self.game_mode_combo = QComboBox() + self.game_mode_combo.addItem("无音效 (0)", 0) + self.game_mode_combo.addItem("FPS模式 (1)", 1) + self.game_mode_combo.addItem("虚拟7.1 (2)", 2) + game_control_layout.addWidget(QLabel("游戏模式:")) + game_control_layout.addWidget(self.game_mode_combo) + device_mode_layout.addRow(game_control_layout) + + game_btn_layout = QHBoxLayout() + self.set_game_mode_btn = QPushButton("设置游戏模式") + self.set_game_mode_btn.clicked.connect(self.on_set_game_mode) + self.get_game_mode_btn = QPushButton("读取游戏模式") + self.get_game_mode_btn.clicked.connect(self.on_get_game_mode) + game_btn_layout.addWidget(self.set_game_mode_btn) + game_btn_layout.addWidget(self.get_game_mode_btn) + device_mode_layout.addRow(game_btn_layout) + + # 固件版本按钮 + firmware_btn_layout = QHBoxLayout() + self.get_firmware_version_btn = QPushButton("获取固件版本") + self.get_firmware_version_btn.clicked.connect(self.on_get_firmware_version) + firmware_btn_layout.addWidget(self.get_firmware_version_btn) + device_mode_layout.addRow(firmware_btn_layout) + + left_content_layout.addWidget(device_mode_group) + + # 添加LED控制组 + led_group = QGroupBox("LED控制") + self.ui_groups['led'] = led_group # 保存引用 + led_layout = QFormLayout(led_group) + + # LED索引选择 + led_index_layout = QHBoxLayout() + self.led_index_label = QLabel("LED索引:") + self.led_index_spin = QSpinBox() + self.led_index_spin.setRange(0, 7) + self.led_index_spin.setValue(0) + led_index_layout.addWidget(self.led_index_label) + led_index_layout.addWidget(self.led_index_spin) + led_layout.addRow(led_index_layout) + + # LED开关 + led_switch_layout = QHBoxLayout() + self.led_switch_label = QLabel("LED开关:") + self.led_switch_combo = QComboBox() + self.led_switch_combo.addItems(["OFF (关闭)", "ON (开启)"]) + self.led_switch_combo.setCurrentIndex(1) # 默认开启 + led_switch_layout.addWidget(self.led_switch_label) + led_switch_layout.addWidget(self.led_switch_combo) + led_layout.addRow(led_switch_layout) + + # LED信息显示 + self.led_info_label = QLabel("LED信息: 未获取") + led_layout.addRow("LED信息:", self.led_info_label) + + # LED状态显示 + self.led_status_label = QLabel("LED状态: 未获取") + led_layout.addRow("LED状态:", self.led_status_label) + + # LED控制按钮 + led_buttons_layout = QVBoxLayout() + + # 获取LED信息按钮 + self.get_led_info_btn = QPushButton("获取LED信息") + self.get_led_info_btn.clicked.connect(self.on_get_led_info) + led_buttons_layout.addWidget(self.get_led_info_btn) + + # 设置LED开关按钮 + self.set_led_switch_btn = QPushButton("设置LED开关") + self.set_led_switch_btn.clicked.connect(self.on_set_led_switch) + led_buttons_layout.addWidget(self.set_led_switch_btn) + + # 获取LED开关按钮 + self.get_led_switch_btn = QPushButton("获取LED开关") + self.get_led_switch_btn.clicked.connect(self.on_get_led_switch) + led_buttons_layout.addWidget(self.get_led_switch_btn) + + # 获取LED状态按钮 + self.get_led_status_btn = QPushButton("获取LED状态") + self.get_led_status_btn.clicked.connect(self.on_get_led_status) + led_buttons_layout.addWidget(self.get_led_status_btn) + + # 获取LED总数按钮 + self.get_led_count_btn = QPushButton("获取LED总数") + self.get_led_count_btn.clicked.connect(self.on_get_led_count) + led_buttons_layout.addWidget(self.get_led_count_btn) + + led_layout.addRow(led_buttons_layout) + + left_content_layout.addWidget(led_group) + + # 添加UAC控制组 + uac_group = QGroupBox("UAC模式控制") + self.ui_groups['uac'] = uac_group # 保存引用 + uac_layout = QFormLayout(uac_group) + + # UAC模式选择 + uac_mode_layout = QHBoxLayout() + self.uac_mode_label = QLabel("UAC模式:") + self.uac_mode_combo = QComboBox() + self.uac_mode_combo.addItems(["UAC2.0", "UAC1.0"]) + self.uac_mode_combo.setCurrentIndex(0) # 默认UAC2.0 + uac_mode_layout.addWidget(self.uac_mode_label) + uac_mode_layout.addWidget(self.uac_mode_combo) + uac_layout.addRow(uac_mode_layout) + + # 当前UAC模式显示 + self.current_uac_mode_label = QLabel("当前UAC模式: 未获取") + uac_layout.addRow("当前模式:", self.current_uac_mode_label) + + # UAC控制按钮 + uac_buttons_layout = QVBoxLayout() + + # 获取UAC模式信息按钮 + self.get_uac_mode_info_btn = QPushButton("获取UAC模式信息") + self.get_uac_mode_info_btn.clicked.connect(self.on_get_uac_mode_info) + uac_buttons_layout.addWidget(self.get_uac_mode_info_btn) + + # 设置UAC模式按钮 + self.set_uac_mode_btn = QPushButton("设置UAC模式") + self.set_uac_mode_btn.clicked.connect(self.on_set_uac_mode) + uac_buttons_layout.addWidget(self.set_uac_mode_btn) + + # 获取当前UAC模式按钮 + self.get_current_uac_mode_btn = QPushButton("获取当前UAC模式") + self.get_current_uac_mode_btn.clicked.connect(self.on_get_current_uac_mode) + uac_buttons_layout.addWidget(self.get_current_uac_mode_btn) + + uac_layout.addRow(uac_buttons_layout) + + left_content_layout.addWidget(uac_group) + + # 添加EQ使能控制组 + eq_enable_group = QGroupBox("EQ使能控制") + self.ui_groups['eq_enable'] = eq_enable_group # 保存引用 + eq_enable_layout = QFormLayout(eq_enable_group) + + # EQ使能开关 + eq_enable_switch_layout = QHBoxLayout() + self.eq_enable_label = QLabel("EQ使能开关:") + self.eq_enable_combo = QComboBox() + self.eq_enable_combo.addItems(["OFF (禁用)", "ON (启用)"]) + self.eq_enable_combo.setCurrentIndex(1) # 默认启用 + eq_enable_switch_layout.addWidget(self.eq_enable_label) + eq_enable_switch_layout.addWidget(self.eq_enable_combo) + eq_enable_layout.addRow(eq_enable_switch_layout) + + # 保存的模式显示 + self.saved_mode_label = QLabel("保存的模式: 未获取") + eq_enable_layout.addRow("保存的模式:", self.saved_mode_label) + + # EQ使能控制按钮 + eq_enable_buttons_layout = QVBoxLayout() + + # 设置EQ使能开关按钮 + self.set_eq_enable_btn = QPushButton("设置EQ使能开关") + self.set_eq_enable_btn.clicked.connect(self.on_set_eq_enable) + eq_enable_buttons_layout.addWidget(self.set_eq_enable_btn) + + # 获取EQ使能开关按钮 + self.get_eq_enable_btn = QPushButton("获取EQ使能开关") + self.get_eq_enable_btn.clicked.connect(self.on_get_eq_enable) + eq_enable_buttons_layout.addWidget(self.get_eq_enable_btn) + + eq_enable_layout.addRow(eq_enable_buttons_layout) + + left_content_layout.addWidget(eq_enable_group) + + # 添加EX3D控制组 + ex3d_group = QGroupBox("EX3D控制") + self.ui_groups['ex3d'] = ex3d_group # 保存引用 + ex3d_layout = QVBoxLayout(ex3d_group) + + # EX3D命令码选择 + ex3d_cmd_layout = QHBoxLayout() + ex3d_cmd_layout.addWidget(QLabel("EX3D命令码:")) + self.ex3d_cmd_combo = QComboBox() + # 添加SET命令选项(清除第9位) + self.ex3d_cmd_combo.addItem("CMD_OPEN (0x81)", 0x81) + self.ex3d_cmd_combo.addItem("CMD_ONOFF (0x82)", 0x82) + self.ex3d_cmd_combo.addItem("CMD_ANGLE (0x83)", 0x83) + self.ex3d_cmd_combo.addItem("CMD_SOUND_FIELD (0x84)", 0x84) + self.ex3d_cmd_combo.addItem("CMD_MUTE (0x85)", 0x85) + self.ex3d_cmd_combo.addItem("CMD_ON_GAIN (0x86)", 0x86) + self.ex3d_cmd_combo.addItem("CMD_LMT_THRESHOLD (0x87)", 0x87) + self.ex3d_cmd_combo.addItem("CMD_UPMIX (0x88)", 0x88) + self.ex3d_cmd_combo.addItem("CMD_LFE (0x89)", 0x89) + self.ex3d_cmd_combo.addItem("CMD_OFF_GAIN (0x8A)", 0x8A) + self.ex3d_cmd_combo.addItem("CMD_LMT_ATTACKK_TIME (0x8E)", 0x8E) + self.ex3d_cmd_combo.addItem("CMD_LMT_RELEASE_TIME (0x8F)", 0x8F) + self.ex3d_cmd_combo.addItem("CMD_EXPAND_GAIN (0x93)", 0x93) + self.ex3d_cmd_combo.addItem("CMD_REDUCE_GAIN (0x94)", 0x94) + ex3d_cmd_layout.addWidget(self.ex3d_cmd_combo) + ex3d_layout.addLayout(ex3d_cmd_layout) + + # EX3D命令类型选择(SET或GET) + ex3d_type_layout = QHBoxLayout() + ex3d_type_layout.addWidget(QLabel("命令类型:")) + self.ex3d_cmd_type_combo = QComboBox() + self.ex3d_cmd_type_combo.addItem("SET (设置)", "SET") + self.ex3d_cmd_type_combo.addItem("GET (读取)", "GET") + ex3d_type_layout.addWidget(self.ex3d_cmd_type_combo) + ex3d_layout.addLayout(ex3d_type_layout) + + # EX3D参数输入(最多3个参数) + ex3d_params_layout = QFormLayout() + self.ex3d_param1 = QSpinBox() + self.ex3d_param1.setRange(-2147483648, 2147483647) + self.ex3d_param1.setValue(0) + ex3d_params_layout.addRow("参数1 (int32):", self.ex3d_param1) + self.ex3d_param2 = QSpinBox() + self.ex3d_param2.setRange(-2147483648, 2147483647) + self.ex3d_param2.setValue(0) + ex3d_params_layout.addRow("参数2 (int32):", self.ex3d_param2) + self.ex3d_param3 = QSpinBox() + self.ex3d_param3.setRange(-2147483648, 2147483647) + self.ex3d_param3.setValue(0) + ex3d_params_layout.addRow("参数3 (int32):", self.ex3d_param3) + ex3d_layout.addLayout(ex3d_params_layout) + + # EX3D命令按钮 + ex3d_btn_layout = QHBoxLayout() + self.send_ex3d_btn = QPushButton("发送EX3D命令") + self.send_ex3d_btn.clicked.connect(self.on_send_ex3d_cmd) + self.get_ex3d_btn = QPushButton("读取EX3D参数") + self.get_ex3d_btn.clicked.connect(self.on_get_ex3d_cmd) + ex3d_btn_layout.addWidget(self.send_ex3d_btn) + ex3d_btn_layout.addWidget(self.get_ex3d_btn) + ex3d_layout.addLayout(ex3d_btn_layout) + + left_content_layout.addWidget(ex3d_group) + + # EQ后增益补偿 + post_gain_layout = QHBoxLayout() + post_gain_label = QLabel("EQ整体增益 (dB):") + self.post_gain_spin = QSpinBox() + self.post_gain_spin.setRange(-50, 0) + self.post_gain_spin.setSingleStep(1) + self.post_gain_spin.setValue(0) + post_gain_layout.addWidget(post_gain_label) + post_gain_layout.addWidget(self.post_gain_spin) + left_content_layout.addLayout(post_gain_layout) + + # 保存/加载按钮(始终显示) + save_load_layout = QHBoxLayout() + save_button = QPushButton("保存参数") + save_button.clicked.connect(self.save_parameters) + load_button = QPushButton("加载参数") + load_button.clicked.connect(self.load_parameters) + apply_all_button = QPushButton("保存到所有采样率") + apply_all_button.clicked.connect(self.apply_to_all_sample_rates) + save_load_layout.addWidget(save_button) + save_load_layout.addWidget(load_button) + save_load_layout.addWidget(apply_all_button) + left_content_layout.addLayout(save_load_layout) + + # 添加弹性空间,让内容靠上对齐 + left_content_layout.addStretch() + + # 将内容容器放入滚动区域 + left_scroll.setWidget(left_content) + left_layout.addWidget(left_scroll) + + # 右侧响应曲线 + right_panel = QWidget() + right_layout = QVBoxLayout(right_panel) + + # 创建matplotlib画布 + self.plot = MplCanvas(self, width=5, height=4, dpi=100) + right_layout.addWidget(self.plot) + + # 添加左右面板到分割器 + splitter.addWidget(left_panel) + splitter.addWidget(right_panel) + + # 设置分割器初始比例 + splitter.setStretchFactor(0, 1) + splitter.setStretchFactor(1, 2) + + # 添加分割器到主布局 + main_layout.addWidget(splitter) + + # 初始化变量 + self.bands = [] + self.visible_bands = 0 + + # 创建初始滤波器 + self.create_initial_filter() + self.refresh_devices() + + # 改动原因:默认隐藏所有视图选项,只显示EQ参数相关的组(bands_scroll,即滤波器带) + # 隐藏所有在ui_groups中的组,包括音效参数配置组 + for group_key in self.ui_groups.keys(): + self.ui_groups[group_key].setVisible(False) + + + def create_menu_bar(self): + """创建菜单栏和视图菜单,用于控制各组可见性""" + # 改动原因:添加菜单栏,包含视图菜单用于隐藏/显示左侧面板的各组 + # 改动原因:默认隐藏所有视图选项,只显示EQ参数相关的组(bands_scroll,即滤波器带) + menubar = self.menuBar() + + # 创建视图菜单 + view_menu = menubar.addMenu("视图") + + # 定义各组的中文名称和对应的key + # 改动原因:添加'param'(音效参数配置)到菜单控制列表中 + group_names = { + 'device_info': '设备信息', + 'mode': '音效模式设置', + 'param': '音效参数配置', + 'volume': '音量控制', + 'device_mode': '设备模式控制', + 'led': 'LED控制', + 'uac': 'UAC模式控制', + 'eq_enable': 'EQ使能控制', + 'ex3d': 'EX3D控制' + } + + # 为每组创建切换可见性的动作 + self.view_actions = {} + for key, name in group_names.items(): + action = view_menu.addAction(f"显示 {name}") + action.setCheckable(True) + action.setChecked(False) # 改动原因:默认隐藏所有视图选项,只显示EQ参数相关的组 + action.triggered.connect(lambda checked, k=key: self.toggle_group_visibility(k, checked)) + self.view_actions[key] = action + + def toggle_group_visibility(self, group_key, visible): + """切换组的可见性""" + # 改动原因:切换指定组的可见性,现在所有组都可以通过菜单控制(包括音效参数配置) + # 只有bands_scroll(滤波器带)始终显示,不受此函数控制 + if group_key in self.ui_groups: + self.ui_groups[group_key].setVisible(visible) + # 更新菜单项的文本 + if group_key in self.view_actions: + group_names = { + 'device_info': '设备信息', + 'mode': '音效模式设置', + 'param': '音效参数配置', + 'volume': '音量控制', + 'device_mode': '设备模式控制', + 'led': 'LED控制', + 'uac': 'UAC模式控制', + 'eq_enable': 'EQ使能控制', + 'ex3d': 'EX3D控制' + } + name = group_names.get(group_key, group_key) + self.view_actions[group_key].setText(f"{'显示' if visible else '隐藏'} {name}") + + def create_initial_filter(self): + """创建所有MAX_EQ_BANDS个滤波器""" + for i in range(MAX_EQ_BANDS): + band = BandFilter(i + 1, self) + self.bands.append(band) + self.bands_layout.addWidget(band) + self.visible_bands = MAX_EQ_BANDS + + def on_sample_rate_changed(self): + """采样率改变时更新所有滤波器""" + for band in self.bands: + if self.show_qshift_debug: + band.update_coefficients() + self.update_eq_curve() + + def on_log_level_changed(self, index): + """当日志级别改变时的处理""" + self.log_level = index + log_message(LOG_LEVEL_INFO, f"日志级别已更改为: {LOG_LEVEL_NAMES[index]}", self.log_level) + + def on_mode_changed(self, index): + """音效模式改变时的处理""" + # 防止循环调用:如果是由代码内部设置触发的,不执行操作 + if hasattr(self, '_mode_changing') and self._mode_changing: + return + + if index == NUM_EQ_MODES: # 禁用EQ选项 + self.current_mode = EQ_DISABLED_MODE + else: + self.current_mode = index + log_message(LOG_LEVEL_INFO, f"音效模式已更改为: {index}", self.log_level) + + # 切换模式 + self.on_switch_mode() + + # 等待设备处理模式切换(添加延迟确保设备完成切换) + import time + time.sleep(0.1) # 等待100ms让设备完成模式切换 + + # 读取参数 + self.read_parameters() + + # 获取当前模式状态(但不更新UI下拉框,避免循环调用) + self.on_get_mode(update_ui=False) + + def on_switch_mode(self): + """切换音效模式(只发送0x8A命令)""" + if self.device_combo.currentData() is not None: + device_info = self.device_combo.currentData() + log_message(LOG_LEVEL_DEBUG, f"正在打开设备: VID=0x{device_info['vendor_id']:04x}, PID=0x{device_info['product_id']:04x}", self.log_level) + + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x8A # 命令码 (SET_EQ_MODE) + data[2] = self.current_mode # 模式值 + # 其余字节保持为0 + log_message(LOG_LEVEL_DEBUG, f"切换音效模式到模式 {self.current_mode}", self.log_level) + log_message(LOG_LEVEL_DEBUG, f"数据包: {' '.join(f'0x{byte:02x}' for byte in data[:20])}...", self.log_level) + + # 发送命令 + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + h.write([0x01] + list(data)) # 0x01是report_id + + # 等待设备响应(可选:读取响应确认切换成功) + import time + time.sleep(0.05) # 等待50ms让设备处理命令 + + h.close() + log_message(LOG_LEVEL_INFO, f"切换音效模式到模式 {self.current_mode} 成功", self.log_level) + + def on_get_mode(self, update_ui=True, mode=0xFF): + """获取音效模式(只处理0x8B命令) + Args: + update_ui: 是否更新UI下拉框,False时只更新增益和名称,避免循环调用 + mode: 模式值,0xFF表示获取当前模式信息,0-9表示获取指定模式信息 + """ + if self.device_combo.currentData() is not None: + device_info = self.device_combo.currentData() + log_message(LOG_LEVEL_DEBUG, f"正在打开设备: VID=0x{device_info['vendor_id']:04x}, PID=0x{device_info['product_id']:04x}", self.log_level) + + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x8B # 命令码 (GET_EQ_MODE) + data[2] = mode # 改动原因:添加mode参数,0xFF表示获取当前模式,0-9表示获取指定模式信息 + # 其余字节保持为0 + + log_message(LOG_LEVEL_DEBUG, f"发送0x8B命令,mode=0x{mode:02x} ({'当前模式' if mode == 0xFF else f'模式{mode}'})", self.log_level) + h.write([0x01] + list(data)) # 0x01是report_id + + # 读取0x8B的响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x8B: + # 调试:打印原始响应数据 + log_message(LOG_LEVEL_DEBUG, f"0x8B响应原始数据: {' '.join(f'0x{byte:02x}' for byte in reply[:16])}", self.log_level) + + # 解析响应数据 - 根据简化协议 + current_mode = reply[3] # 模式值 + # 解析整体增益 (4字节,位置4-7) + post_gain = int.from_bytes(reply[4:8], 'little', signed=True) + + # 解析模式名称 (16字节,位置8-23) + mode_name_bytes = bytes(reply[8:24]) + mode_name = mode_name_bytes.decode('utf-8', errors='ignore').rstrip('\x00') + + # 调试:打印解析的字节 + log_message(LOG_LEVEL_DEBUG, f"整体增益原始字节: 0x{reply[4]:02x} 0x{reply[5]:02x} 0x{reply[6]:02x} 0x{reply[7]:02x}", self.log_level) + log_message(LOG_LEVEL_DEBUG, f"模式名称原始字节: {' '.join(f'0x{byte:02x}' for byte in reply[8:24])}", self.log_level) + + log_message(LOG_LEVEL_INFO, f"获取到当前EQ模式: {current_mode}, 整体增益: {post_gain}dB, 模式名称: '{mode_name}'", self.log_level) + + # 更新UI - 图像显示固定使用48000Hz + self.sample_rate.setCurrentText("48000") + + # 只有在update_ui=True时才更新下拉框,避免循环调用 + if update_ui: + # 设置标志防止循环调用 + self._mode_changing = True + try: + if current_mode == EQ_DISABLED_MODE: + self.mode_combo.setCurrentIndex(NUM_EQ_MODES) # 禁用EQ选项 + else: + self.mode_combo.setCurrentIndex(current_mode) + finally: + self._mode_changing = False + + # 更新当前模式值(但不触发UI更新) + self.current_mode = current_mode + # 更新整体增益显示 + self.post_gain_spin.setValue(post_gain) + # 更新模式名称显示 + self.mode_name_edit.setText(mode_name) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x8B响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x8B响应数据", self.log_level) + + h.close() + + + def update_eq_curve(self): + """更新EQ响应曲线""" + try: + fs = float(self.sample_rate.currentText()) + + # 创建频率点 + f = np.logspace(np.log10(20), np.log10(fs/2), 1000) + + # 计算总响应 + h_total = np.ones_like(f, dtype=complex) + + # 计算每个EQ的响应 + responses = [] + enabled_eqs = [] # 记录启用的EQ索引 + + for i, band in enumerate(self.bands): + if band.enabled.isChecked(): + # 获取滤波器系数 + coeffs = band.calculate_coefficients(fs) + if coeffs is not None: + # 计算频率响应 + w = 2 * np.pi * f / fs + z = np.exp(-1j * w) + h = (coeffs[0] + coeffs[1] * z + coeffs[2] * z**2) / \ + (1 + coeffs[3] * z + coeffs[4] * z**2) + responses.append(h) + enabled_eqs.append(i) + h_total *= h + + # 更新图形 + self.plot.update_plot(f, h_total, fs) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"更新EQ曲线时出错: {str(e)}", self.log_level) + + def save_parameters(self): + """保存参数到JSON文件和头文件""" + try: + fs = float(self.sample_rate.currentText()) + current_mode = self.current_mode + + # 生成JSON文件名 + json_filename = f"eq_parameters_{int(fs)}_mode{current_mode}.json" + + # 读取现有的JSON文件(如果存在) + all_params = {} + if os.path.exists(json_filename): + with open(json_filename, "r", encoding='utf-8') as f: + all_params = json.load(f) + + # 计算总的bshift + total_bshift = sum(band.get_bshift() for band in self.bands) + + # 更新当前mode的参数 + params = { + "sample_rate": fs, + "mode": current_mode, # 保存当前模式 + "total_bshift": total_bshift, + "post_gain_db": int(self.post_gain_spin.value()), + "bands": [] + } + + for band in self.bands: + band_params = band.get_parameters() + if band_params["enabled"]: + # 计算浮点系数 + coeffs = band.calculate_coefficients(fs) + if coeffs is not None: + # 反转 b1 和 b2 的符号 + coeffs = np.array([ + coeffs[0], # a0 + coeffs[1], # a1 + coeffs[2], # a2 + -coeffs[3], # b1 + -coeffs[4] # b2 + ]) + band_scale_factor = band.get_scale_factor() + # 只对 a0, a1, a2 乘以 scale_factor + coeffs_scaled = np.array([ + coeffs[0] * band_scale_factor, + coeffs[1] * band_scale_factor, + coeffs[2] * band_scale_factor, + coeffs[3], + coeffs[4] + ]) + coeffs_q30 = (coeffs_scaled * 2**30).astype(np.int32) + band_params["coefficients"] = { + "float": coeffs.tolist(), + "q30": [f"0x{int(x):08x}" if x >= 0 else f"-0x{int(-x):08x}" for x in coeffs_q30] + } + + params["bands"].append(band_params) + + # 保存当前mode的参数 + all_params[f"mode_{current_mode}"] = params + + # 保存JSON文件 + with open(json_filename, "w", encoding='utf-8') as f: + json.dump(all_params, f, indent=2, ensure_ascii=False) + + # 为当前模式生成头文件 + header_filename = f"eq_params_{int(fs)}_mode{current_mode}.h" + with open(header_filename, "w", encoding='utf-8') as f: + f.write(f"""// EQ参数头文件 - {int(fs)}Hz +// 自动生成,请勿手动修改 + +#include "eq.h" + +// Directly declare sEQ_data_{int(fs)}HZ, including coefficients +eq_mode_data_t sEQ_data_{int(fs)}HZ[NUM_EQ_MODES][NUM_EQ_CHANS] = {{ +""") + + # 写入当前模式的EQ参数 + f.write(f" [{current_mode}][0] = {{\n") # 左通道 + f.write(f" .sample_rate = {int(fs)},\n") + f.write(f" .total_bshift = {total_bshift},\n") + f.write(f" .post_gain_db = {int(self.post_gain_spin.value())}, // {self.post_gain_spin.value()} dB\n") + f.write(" .bands = {\n") + + # 写入每个滤波器的参数 + for i, band in enumerate(self.bands): + params = band.get_parameters() + if i < 8: # 最多8个滤波器 + f.write(" {\n") + f.write(f" .index = {i},\n") + + # 写入滤波器类型 + type_map = { + "bypass滤波器": "FILTER_TYPE_BYPASS", + "全通滤波器": "FILTER_TYPE_ALLPASS", + "峰值滤波器": "FILTER_TYPE_PEAKING", + "低通滤波器": "FILTER_TYPE_LOWPASS", + "高通滤波器": "FILTER_TYPE_HIGHPASS", + "带通滤波器": "FILTER_TYPE_BANDPASS", + "带阻滤波器": "FILTER_TYPE_BANDSTOP", + "陷波滤波器": "FILTER_TYPE_NOTCH", + "恒定Q值滤波器": "FILTER_TYPE_CONST_Q", + "低架滤波器": "FILTER_TYPE_LOWSHELF", + "高架滤波器": "FILTER_TYPE_HIGHSHELF" + } + f.write(f" .type = {type_map.get(params['type'], 'FILTER_TYPE_ALLPASS')},\n") + + # 写入参数 - 直接写入float值 + f.write(f" .fc = {params['fc']:.2f}f,\n") + f.write(f" .q = {params['Q']:.4f}f,\n") + f.write(f" .bw = {params['bw']:.2f}f,\n") + f.write(f" .gain = {params['gain']:.2f}f,\n") + f.write(" },\n") + + f.write(" },\n") + + # 写入filter结构 + f.write(" .filter = {\n") + f.write(" .biquad_count = MAX_EQ_BANDS,\n") + f.write(" .state = {{0}},\n") + f.write(" .coef = {\n") + + # 写入系数数组 + coeffs_all = [] + for band in self.bands: + coeffs = band.calculate_coefficients(fs) + if coeffs is not None: + coeffs = np.array([ + coeffs[0], # a0 + coeffs[1], # a1 + coeffs[2], # a2 + -coeffs[3], # b1 + -coeffs[4] # b2 + ]) + band_scale_factor = band.get_scale_factor() + coeffs_scaled = np.array([ + coeffs[0] * band_scale_factor, + coeffs[1] * band_scale_factor, + coeffs[2] * band_scale_factor, + coeffs[3], + coeffs[4] + ]) + coeffs_q30 = (coeffs_scaled * 2**30).astype(np.int32) + coeffs_all.extend(coeffs_q30) + else: + coeffs_all.extend([0] * 5) + + # 补足MAX_EQ_BANDS个band的系数 + while len(coeffs_all) < MAX_EQ_BANDS * 5: + coeffs_all.append(0) + + # 按5个系数一组写入 + for i in range(5): + f.write(" {") + for j in range(MAX_EQ_BANDS): + coeff = coeffs_all[j*5 + i] # 注意:i为系数序号,j为band序号 + f.write(f"{f'0x{coeff:08x}' if coeff >= 0 else f'-0x{-coeff:08x}'}") + if j < MAX_EQ_BANDS - 1: + f.write(", ") + f.write("},\n") + + f.write(" },\n") + f.write(" }\n") + f.write(" },\n") + + # 右通道与左通道相同 + f.write(f" [{current_mode}][1] = {{\n") # 右通道 + f.write(f" .sample_rate = {int(fs)},\n") + f.write(f" .total_bshift = {total_bshift},\n") + f.write(f" .post_gain_db = {int(self.post_gain_spin.value())}, // {self.post_gain_spin.value()} dB\n") + f.write(" .bands = {\n") + + # 写入每个滤波器的参数 + for i, band in enumerate(self.bands): + params = band.get_parameters() + if i < MAX_EQ_BANDS: # 最多MAX_EQ_BANDS个滤波器 + f.write(" {\n") + f.write(f" .index = {i},\n") + f.write(f" .type = {type_map.get(params['type'], 'FILTER_TYPE_ALLPASS')},\n") + f.write(f" .fc = {params['fc']:.2f}f,\n") + f.write(f" .q = {params['Q']:.4f}f,\n") + f.write(f" .bw = {params['bw']:.2f}f,\n") + f.write(f" .gain = {params['gain']:.2f}f,\n") + f.write(" },\n") + + f.write(" },\n") + f.write(" .filter = {\n") + f.write(" .biquad_count = MAX_EQ_BANDS,\n") + f.write(" .state = {{0}},\n") + f.write(" .coef = {\n") + + # 写入系数数组 + for i in range(5): + f.write(" {") + for j in range(MAX_EQ_BANDS): + coeff = coeffs_all[j*5 + i] # 注意:i为系数序号,j为band序号 + f.write(f"{f'0x{coeff:08x}' if coeff >= 0 else f'-0x{-coeff:08x}'}") + if j < MAX_EQ_BANDS - 1: + f.write(", ") + f.write("},\n") + + f.write(" },\n") + f.write(" }\n") + f.write(" },\n") + + f.write("};\n") + + log_message(LOG_LEVEL_INFO, f"参数已保存到 {json_filename}", self.log_level) + log_message(LOG_LEVEL_INFO, f"头文件已生成: {header_filename}", self.log_level) + + # 检查是否所有模式都已生成 + all_modes_generated = True + for mode in range(NUM_EQ_MODES): + mode_file = f"eq_params_{int(fs)}_mode{mode}.h" + if not os.path.exists(mode_file): + all_modes_generated = False + break + + # 如果所有模式都已生成,合并成一个完整的头文件 + if all_modes_generated: + self.merge_mode_files(fs) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"保存参数时出错: {str(e)}", self.log_level) + + def merge_mode_files(self, fs): + """合并所有模式的头文件""" + try: + # 创建完整的头文件 + header_filename = f"eq_params_{int(fs)}.h" + with open(header_filename, "w", encoding='utf-8') as f: + # 写入头文件头部 + f.write(f"""// EQ参数头文件 - {int(fs)}Hz +// 自动生成,请勿手动修改 + +#include "eq.h" + +// Directly declare sEQ_data_{int(fs)}HZ, including coefficients +eq_mode_data_t sEQ_data_{int(fs)}HZ[NUM_EQ_MODES][NUM_EQ_CHANS] = {{ +""") + + # 合并所有模式的文件 + for mode in range(NUM_EQ_MODES): + mode_file = f"eq_params_{int(fs)}_mode{mode}.h" + with open(mode_file, "r", encoding='utf-8') as mf: + content = mf.readlines() + # 截取第8行到第171行 + mode_data = content[7:171] + # 写入模式数据 + f.writelines(mode_data) + + # 写入结尾 + f.write("};\n") + + log_message(LOG_LEVEL_INFO, f"已合并所有模式生成完整头文件: {header_filename}", self.log_level) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"合并头文件时出错: {str(e)}", self.log_level) + + def load_parameters(self): + """从JSON文件加载参数""" + try: + fs = float(self.sample_rate.currentText()) + current_mode = self.current_mode + json_filename = f"eq_parameters_{int(fs)}_mode{current_mode}.json" + + if not os.path.exists(json_filename): + log_message(LOG_LEVEL_ERROR, f"未找到配置文件: {json_filename}", self.log_level) + return + + with open(json_filename, "r", encoding='utf-8') as f: + all_params = json.load(f) + + # 获取当前mode的参数 + mode_key = f"mode_{current_mode}" + if mode_key not in all_params: + log_message(LOG_LEVEL_ERROR, f"未找到模式 {current_mode} 的参数", self.log_level) + return + + params = all_params[mode_key] + + # 清除现有滤波器 + for band in self.bands: + self.bands_layout.removeWidget(band) + band.deleteLater() + self.bands.clear() + + # 添加新的滤波器 + for band_params in params["bands"]: + band = BandFilter(len(self.bands), self) + band.set_parameters(band_params) + self.bands.append(band) + self.bands_layout.addWidget(band) + + self.visible_bands = len(self.bands) + self.update_eq_curve() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"加载参数时出错: {str(e)}", self.log_level) + + def send_parameters(self): + """发送参数到设备""" + try: + if not self.device_combo.currentData(): + log_message(LOG_LEVEL_ERROR, "未选择设备", self.log_level) + return + + # 计算总的bshift + total_bshift = sum(band.get_bshift() for band in self.bands) + + device_info = self.device_combo.currentData() + log_message(LOG_LEVEL_DEBUG, f"正在打开设备: VID=0x{device_info['vendor_id']:04x}, PID=0x{device_info['product_id']:04x}", self.log_level) + + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + + # 设置非阻塞模式 + h.set_nonblocking(1) + + # 发送所有MAX_EQ_BANDS个滤波器的参数 - 自动应用到所有采样率 + for i in range(MAX_EQ_BANDS): + # 确保有足够的滤波器对象 + while len(self.bands) <= i: + self.add_filter() + + band = self.bands[i] + params = band.get_parameters() + log_message(LOG_LEVEL_DEBUG, f"正在处理滤波器 {i+1} 的参数:", self.log_level) + log_message(LOG_LEVEL_DEBUG, f" 类型: {params['type']}", self.log_level) + log_message(LOG_LEVEL_DEBUG, f" 频率: {params['fc']} Hz", self.log_level) + log_message(LOG_LEVEL_DEBUG, f" Q值: {params['Q']}", self.log_level) + log_message(LOG_LEVEL_DEBUG, f" 带宽: {params['bw']}", self.log_level) + log_message(LOG_LEVEL_DEBUG, f" 增益: {params['gain']} dB", self.log_level) + + # 构建HID数据包 (根据简化协议格式) + data = bytearray(63) # 63字节数据包 + + # 头部 (2字节) + data[0] = 0x77 # 同步头1 + data[1] = 0x8D # 命令码 (SET_EQ_PARAMS) + + # 模式值 (1字节) + mode = self.current_mode + data[2] = mode + log_message(LOG_LEVEL_VERBOSE, f" 模式值: {mode}", self.log_level) + + # 滤波器索引 (1字节) + data[3] = i + log_message(LOG_LEVEL_VERBOSE, f" 滤波器索引: {i}", self.log_level) + + # 滤波器类型 (1字节) + type_map = { + "bypass滤波器": 0x00, + "全通滤波器": 0x01, + "峰值滤波器": 0x02, + "低通滤波器": 0x03, + "高通滤波器": 0x04, + "带通滤波器": 0x05, + "带阻滤波器": 0x06, + "陷波滤波器": 0x07, + "恒定Q值滤波器": 0x08, + "低架滤波器": 0x09, + "高架滤波器": 0x0A + } + data[4] = type_map.get(params["type"], 0x01) + log_message(LOG_LEVEL_VERBOSE, f" 滤波器类型码: 0x{data[4]:02x}", self.log_level) + + # 参数直接作为float发送 + # fc (4字节) - 位置5-8 + fc = float(params["fc"]) + data[5:9] = struct.pack('= 0 else f"-0x{int(-x):08x}" for x in coeffs_q30] + } + + params["bands"].append(band_params) + + # 保存当前mode的参数 + all_params[f"mode_{current_mode}"] = params + + # 保存JSON文件 + with open(json_filename, "w", encoding='utf-8') as f: + json.dump(all_params, f, indent=2, ensure_ascii=False) + + # 生成头文件 + self.save_parameters() # 这会自动生成头文件 + + log_message(LOG_LEVEL_INFO, f"已保存采样率 {fs}Hz 的参数", self.log_level) + + # 恢复原始采样率 + self.sample_rate.setCurrentText(str(int(current_fs))) + + log_message(LOG_LEVEL_INFO, "所有采样率的参数已保存完成", self.log_level) + + # 检查是否所有模式都已生成 + all_modes_generated = True + for mode in range(NUM_EQ_MODES): + mode_file = f"eq_params_{int(current_fs)}_mode{mode}.h" + if not os.path.exists(mode_file): + all_modes_generated = False + break + + # 如果所有模式都已生成,合并成一个完整的头文件 + if all_modes_generated: + self.merge_mode_files(current_fs) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"应用到所有采样率时出错: {str(e)}", self.log_level) + import traceback + log_message(LOG_LEVEL_ERROR, "详细错误信息:", self.log_level) + traceback.print_exc() + + def on_config_params(self): + """配置音效参数(发送8个0x8D命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 弹出确认对话框 + sample_rate = self.sample_rate.currentText() + mode_name = self.mode_combo.currentText() + mode_index = self.mode_combo.currentIndex() + + reply = QMessageBox.question( + self, + "确认配置音效参数", + f"将要配置以下音效参数:\n\n" + f"模式: {mode_name} \n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.send_parameters() + + def on_read_params(self): + """读取音效参数(发送8个0x8E命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 弹出确认对话框 + sample_rate = self.sample_rate.currentText() + mode_name = self.mode_combo.currentText() + mode_index = self.mode_combo.currentIndex() + + reply = QMessageBox.question( + self, + "确认读取音效参数", + f"将要读取以下音效参数:\n\n" + f"采样率: {sample_rate} Hz\n" + f"模式: {mode_name} (索引: {mode_index})\n\n" + f"这将发送8个0x8E命令到设备。\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.read_parameters() + + def on_set_gain(self): + """设置整体增益(发送0x8C命令到所有采样率)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 弹出确认对话框 + sample_rate = self.sample_rate.currentText() + mode_name = self.mode_combo.currentText() + mode_index = self.mode_combo.currentIndex() + gain_value = self.post_gain_spin.value() + + reply = QMessageBox.question( + self, + "确认设置整体增益", + f"将要设置以下整体增益:\n\n" + f"模式: {mode_name} \n" + f"增益值: {gain_value} dB\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.send_gain_to_all_sample_rates(mode_index, gain_value) + + def send_gain_to_all_sample_rates(self, mode, gain_value): + """发送整体增益和名称到所有采样率""" + try: + # 获取当前模式名称 + mode_name = self.mode_name_edit.text() + if not mode_name: + mode_name = f"模式{mode}" + + log_message(LOG_LEVEL_INFO, f"正在设置模式 {mode} 整体增益为 {gain_value}dB, 名称为 '{mode_name}' (自动应用到所有采样率)", self.log_level) + + # 使用新的0x8C命令发送模式增益和名称 + self.send_mode_gain_and_name(mode, gain_value, mode_name) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置整体增益时出错: {str(e)}", self.log_level) + import traceback + log_message(LOG_LEVEL_ERROR, "详细错误信息:", self.log_level) + traceback.print_exc() + + def on_get_device_info(self): + """获取设备信息(发送0x8F命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x8F数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x8F # 命令码 (GET_DEVICE_INFO) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在获取设备信息...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x8F: + # 解析设备信息 + pid = int.from_bytes(reply[3:5], 'little') + vid = int.from_bytes(reply[5:7], 'little') + product_string = bytes(reply[7:23]).decode('utf-8', errors='ignore').rstrip('\x00') + vendor_string = bytes(reply[23:39]).decode('utf-8', errors='ignore').rstrip('\x00') + serial_string = bytes(reply[39:55]).decode('utf-8', errors='ignore').rstrip('\x00') + + # 更新UI显示 + self.device_pid_label.setText(f"PID: 0x{pid:04x}") + self.device_vid_label.setText(f"VID: 0x{vid:04x}") + self.device_product_label.setText(f"产品: {product_string}") + self.device_vendor_label.setText(f"厂商: {vendor_string}") + self.device_serial_label.setText(f"序列号: {serial_string}") + + log_message(LOG_LEVEL_INFO, f"设备信息获取成功:", self.log_level) + log_message(LOG_LEVEL_INFO, f" PID: 0x{pid:04x}", self.log_level) + log_message(LOG_LEVEL_INFO, f" VID: 0x{vid:04x}", self.log_level) + log_message(LOG_LEVEL_INFO, f" 产品: {product_string}", self.log_level) + log_message(LOG_LEVEL_INFO, f" 厂商: {vendor_string}", self.log_level) + log_message(LOG_LEVEL_INFO, f" 序列号: {serial_string}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x8F响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x8F响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取设备信息时出错: {str(e)}", self.log_level) + + def on_mode_name_changed(self): + """模式名称改变时的处理""" + mode_name = self.mode_name_edit.text() + log_message(LOG_LEVEL_DEBUG, f"模式名称已更改为: '{mode_name}'", self.log_level) + + def send_mode_gain_and_name(self, mode, gain, name): + """发送模式增益和名称(0x8C命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x8C数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x8C # 命令码 (SET_MODE_GAIN_AND_NAME) + + # 模式值 (1字节) + data[2] = mode + log_message(LOG_LEVEL_VERBOSE, f" 模式值: {mode}", self.log_level) + + # 增益值 (4字节,范围0到-50dB) + data[3:7] = gain.to_bytes(4, 'little', signed=True) + log_message(LOG_LEVEL_VERBOSE, f" 增益值: {gain} dB", self.log_level) + + # 模式名称 (16字节,UTF-8编码) + name_bytes = name.encode('utf-8')[:15] # 最多15字节 + data[7:7+len(name_bytes)] = name_bytes + # 剩余字节保持为0 + + log_message(LOG_LEVEL_VERBOSE, f" 模式名称: '{name}'", self.log_level) + + # 发送数据包 + log_message(LOG_LEVEL_DEBUG, f" 正在发送数据包...", self.log_level) + h.write([0x01] + list(data)) # 0x01是report_id + log_message(LOG_LEVEL_DEBUG, f" 数据包发送完成", self.log_level) + + h.close() + log_message(LOG_LEVEL_INFO, f"模式增益和名称设置完成 (模式 {mode}, 增益 {gain}dB, 名称 '{name}')", self.log_level) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置模式增益和名称时出错: {str(e)}", self.log_level) + + def on_reset_eq_params(self): + """复位EQ参数(发送0x90命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 弹出确认对话框 + mode_name = self.mode_combo.currentText() + mode_index = self.mode_combo.currentIndex() + + reply = QMessageBox.question( + self, + "确认复位EQ参数", + f"将要复位以下EQ参数:\n\n" + f"模式: {mode_name} (索引: {mode_index})\n\n" + f"这将删除Flash中存储的EQ参数并恢复头文件预设参数。\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.send_reset_eq_params(mode_index) + + def send_reset_eq_params(self, mode): + """发送复位EQ参数命令(0x90命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x90数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x90 # 命令码 (RESET_EQ_PARAMS) + + # 模式值 (1字节,0-9表示指定模式,0xFF表示所有模式) + data[2] = mode + log_message(LOG_LEVEL_VERBOSE, f" 复位模式: {mode}", self.log_level) + + # 发送数据包 + log_message(LOG_LEVEL_INFO, f"正在复位EQ参数 (模式 {mode})...", self.log_level) + h.write([0x01] + list(data)) # 0x01是report_id + + # 读取响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x90: + # 解析状态码 + status = reply[3] + if status == 0x00: + log_message(LOG_LEVEL_INFO, f"EQ参数复位成功 (模式 {mode})", self.log_level) + # 复位成功后重新读取当前模式信息 + self.on_get_mode() + self.read_parameters() + else: + log_message(LOG_LEVEL_ERROR, f"EQ参数复位失败 (模式 {mode}, 状态码: 0x{status:02x})", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x90响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x90响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"复位EQ参数时出错: {str(e)}", self.log_level) + + def on_get_mode_count(self): + """获取EQ模式总数(发送0x91命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x91数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x91 # 命令码 (GET_EQ_MODE_COUNT) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在获取EQ模式总数...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x91: + # 解析模式总数 + mode_count = reply[3] + # 解析预定义模式数量 + preset_mode_count = reply[4] + log_message(LOG_LEVEL_INFO, f"EQ模式总数: {mode_count}, 预定义模式数量: {preset_mode_count}", self.log_level) + QMessageBox.information( + self, + "EQ模式总数", + f"当前EQ模式总数为: {mode_count}\n" + f"预定义模式数量: {preset_mode_count}\n\n" + f"(包含预定义模式和用户模式,不包含禁用模式)" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x91响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x91响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取EQ模式总数时出错: {str(e)}", self.log_level) + + def on_set_and_save_mode(self): + """设置并保存EQ模式(发送0x92命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取当前选择的模式 + mode_index = self.mode_combo.currentIndex() + if mode_index == NUM_EQ_MODES: # 禁用EQ选项 + mode = EQ_DISABLED_MODE + else: + mode = mode_index + + # 验证模式范围 + if mode > 9: + QMessageBox.warning( + self, + "错误", + f"无效的模式值: {mode}\n\n" + f"有效范围: 0-9 (0-5预设模式, 6-8用户模式, 9: bypass)" + ) + return + + # 弹出确认对话框 + mode_name = self.mode_combo.currentText() + reply = QMessageBox.question( + self, + "确认设置并保存EQ模式", + f"将要设置并保存以下EQ模式:\n\n" + f"模式: {mode_name} (索引: {mode})\n\n" + f"该模式将被保存到Flash,开机时自动恢复。\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x92数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x92 # 命令码 (SET_AND_SAVE_EQ_MODE) + data[2] = mode # 模式值 + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置并保存EQ模式到 {mode}...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply_data = h.get_input_report(0x1, 64) # 64字节响应 + if reply_data and len(reply_data) == 64: + # 检查Report ID和同步头 + if reply_data[0] == 0x01 and reply_data[1] == 0x77 and reply_data[2] == 0x92: + # 解析状态码 + status = reply_data[3] + if status == 0x00: + log_message(LOG_LEVEL_INFO, f"EQ模式 {mode} 设置并保存成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"EQ模式 {mode} ({mode_name}) 已成功设置并保存到Flash。\n\n" + f"设备开机时将自动恢复该模式。" + ) + # 更新当前模式 + self.current_mode = mode + else: + log_message(LOG_LEVEL_ERROR, f"EQ模式设置并保存失败 (状态码: 0x{status:02x})", self.log_level) + QMessageBox.warning( + self, + "失败", + f"EQ模式设置并保存失败。\n\n" + f"状态码: 0x{status:02x}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x92响应同步头: 0x{reply_data[1]:02x} 0x{reply_data[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x92响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置并保存EQ模式时出错: {str(e)}", self.log_level) + + def on_set_volume(self): + """设置音量级别(发送0x93命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取音量值 + volume_level = self.volume_spin.value() + + # 弹出确认对话框 + reply = QMessageBox.question( + self, + "确认设置音量", + f"将要设置音量级别为: {volume_level}\n\n" + f"音量范围: 0-60 (0=最小音量, 60=最大音量)\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x93数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x93 # 命令码 (SET_VOLUME) + data[2] = volume_level # 音量级别 (0-60) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置音量级别为 {volume_level}...", self.log_level) + h.write([0x01] + list(data)) + + h.close() + log_message(LOG_LEVEL_INFO, f"音量级别 {volume_level} 设置成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"音量级别已设置为: {volume_level}" + ) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置音量时出错: {str(e)}", self.log_level) + + def on_get_volume(self): + """读取音量级别(发送0x94命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x94数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x94 # 命令码 (GET_VOLUME) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在读取音量级别...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x94: + # 解析音量级别 + volume_level = reply[3] + + # 更新UI显示 + self.volume_spin.setValue(volume_level) + + log_message(LOG_LEVEL_INFO, f"当前音量级别: {volume_level}", self.log_level) + QMessageBox.information( + self, + "音量级别", + f"当前音量级别: {volume_level}\n\n" + f"音量范围: 0-60 (0=最小音量, 60=最大音量)" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x94响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x94响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取音量时出错: {str(e)}", self.log_level) + + def on_get_sample_format(self): + """读取采样率和格式信息(发送0x9F命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x9F数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x9F # 命令码 (GET_SAMPLE_FORMAT) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在读取采样率和格式信息...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x9F: + # 解析采样率和格式信息(小端序) + # 响应格式:reply[0]=Report ID, reply[1]=0x77, reply[2]=0x9F + # reply[3-6]=采样率(32位小端序), reply[7]=DSD模式 + samfreq = reply[3] | (reply[4] << 8) | (reply[5] << 16) | (reply[6] << 24) + dsd_mode = reply[7] + + # 格式化显示信息 + format_type = "PCM" if dsd_mode == 0 else f"DSD (模式 {dsd_mode})" + + log_message(LOG_LEVEL_INFO, + f"采样率和格式信息: 采样率={samfreq}Hz, 格式={format_type}", + self.log_level) + QMessageBox.information( + self, + "采样率和格式信息", + f"采样率: {samfreq} Hz\n" + f"格式: {format_type}\n" + f"DSD模式: {dsd_mode} (0=PCM, >0=DSD)" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x9F响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x9F响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取采样率和格式时出错: {str(e)}", self.log_level) + + def on_get_led_info(self): + """获取LED信息(发送0x95命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x95数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x95 # 命令码 (GET_LED_INFO) + data[2] = self.led_index_spin.value() # LED索引 + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在获取LED信息 (索引: {self.led_index_spin.value()})...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x95: + # 解析LED信息 + led_index = reply[3] + led_name_bytes = bytes(reply[4:20]) + led_name = led_name_bytes.decode('utf-8', errors='ignore').rstrip('\x00') + + # 更新UI显示 + self.led_info_label.setText(f"LED信息: 索引={led_index}, 名称='{led_name}'") + + log_message(LOG_LEVEL_INFO, f"LED信息获取成功: 索引={led_index}, 名称='{led_name}'", self.log_level) + QMessageBox.information( + self, + "LED信息", + f"LED索引: {led_index}\n" + f"LED名称: {led_name}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x95响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x95响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取LED信息时出错: {str(e)}", self.log_level) + + def on_set_led_switch(self): + """设置LED开关(发送0x96命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取LED开关值 + led_switch = self.led_switch_combo.currentIndex() # 0=OFF, 1=ON + led_index = self.led_index_spin.value() + + # 弹出确认对话框 + reply = QMessageBox.question( + self, + "确认设置LED开关", + f"将要设置LED开关为: {'ON (开启)' if led_switch == 1 else 'OFF (关闭)'}\n\n" + f"LED索引: {led_index}\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x96数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x96 # 命令码 (SET_LED_SWITCH) + data[2] = led_index # LED索引 + data[3] = led_switch # LED开关 (0=OFF, 1=ON) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置LED开关为 {'ON' if led_switch == 1 else 'OFF'} (索引: {led_index})...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply_data = h.get_input_report(0x1, 64) # 64字节响应 + if reply_data and len(reply_data) == 64: + # 检查Report ID和同步头 + if reply_data[0] == 0x01 and reply_data[1] == 0x77 and reply_data[2] == 0x96: + # 解析状态码(根据协议,位置3是状态码) + status = reply_data[3] + if status == 0x00: + log_message(LOG_LEVEL_INFO, f"LED开关设置成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"LED开关已设置为: {'ON (开启)' if led_switch == 1 else 'OFF (关闭)'}\n\n" + f"LED索引: {led_index}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"LED开关设置失败 (状态码: 0x{status:02x})", self.log_level) + QMessageBox.warning( + self, + "失败", + f"LED开关设置失败。\n\n" + f"状态码: 0x{status:02x}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x96响应同步头: 0x{reply_data[1]:02x} 0x{reply_data[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x96响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置LED开关时出错: {str(e)}", self.log_level) + + def on_get_led_switch(self): + """获取LED开关(发送0x97命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x97数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x97 # 命令码 (GET_LED_SWITCH) + data[2] = self.led_index_spin.value() # LED索引 + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在获取LED开关状态 (索引: {self.led_index_spin.value()})...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x97: + # 解析LED开关状态 + led_index = reply[3] + led_switch = reply[4] # 0=OFF, 1=ON + + # 更新UI显示 + self.led_switch_combo.setCurrentIndex(led_switch) + + log_message(LOG_LEVEL_INFO, f"当前LED开关状态: {'ON (开启)' if led_switch == 1 else 'OFF (关闭)'} (索引: {led_index})", self.log_level) + QMessageBox.information( + self, + "LED开关状态", + f"LED索引: {led_index}\n" + f"LED开关: {'ON (开启)' if led_switch == 1 else 'OFF (关闭)'}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x97响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x97响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取LED开关时出错: {str(e)}", self.log_level) + + def on_get_led_status(self): + """获取LED状态(发送0x98命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x98数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x98 # 命令码 (GET_LED_STATUS) + data[2] = self.led_index_spin.value() # LED索引 + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在获取LED状态 (索引: {self.led_index_spin.value()})...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + log_message(LOG_LEVEL_INFO, f"LED状态响应: {reply}", self.log_level) + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x98: + # 解析LED状态 + led_index = reply[3] + r = reply[4] # R颜色值 + g = reply[5] # G颜色值 + b = reply[6] # B颜色值 + led_status = reply[7] # LED状态 + + # LED状态名称映射 + status_names = { + 0: "熄灭", + 1: "常亮", + 2: "慢闪", + 3: "快闪", + 4: "呼吸" + } + status_name = status_names.get(led_status, f"未知({led_status})") + + # 更新UI显示 + self.led_status_label.setText( + f"LED状态: RGB=({r},{g},{b}), 状态={status_name}" + ) + + log_message(LOG_LEVEL_INFO, + f"LED状态 - 索引: {led_index}, RGB: ({r},{g},{b}), 状态: {status_name}", + self.log_level) + QMessageBox.information( + self, + "LED状态", + f"LED索引: {led_index}\n" + f"RGB颜色: ({r}, {g}, {b})\n" + f"状态: {status_name}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x98响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x98响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取LED状态时出错: {str(e)}", self.log_level) + + def on_get_led_count(self): + """获取LED总数(发送0x99命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x99数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x99 # 命令码 (GET_LED_COUNT) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在获取LED总数...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x99: + # 解析LED总数 + led_count = reply[3] + + log_message(LOG_LEVEL_INFO, f"设备支持的LED总数: {led_count}", self.log_level) + QMessageBox.information( + self, + "LED总数", + f"设备支持的LED总数: {led_count}\n\n" + f"LED索引范围: 0 到 {led_count - 1}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x99响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x99响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取LED总数时出错: {str(e)}", self.log_level) + + def on_get_uac_mode_info(self): + """获取UAC模式信息(发送0x9A命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x9A数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x9A # 命令码 (GET_UAC_MODE_INFO) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在获取UAC模式信息...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x9A: + # 解析UAC模式信息 + uac_mode_count = reply[3] + mode0_name_bytes = bytes(reply[4:12]) + mode1_name_bytes = bytes(reply[12:20]) + mode0_name = mode0_name_bytes.decode('utf-8', errors='ignore').rstrip('\x00') + mode1_name = mode1_name_bytes.decode('utf-8', errors='ignore').rstrip('\x00') + + log_message(LOG_LEVEL_INFO, f"UAC模式信息获取成功: 总数={uac_mode_count}, 模式0='{mode0_name}', 模式1='{mode1_name}'", self.log_level) + QMessageBox.information( + self, + "UAC模式信息", + f"UAC模式总数: {uac_mode_count}\n\n" + f"模式0: {mode0_name}\n" + f"模式1: {mode1_name}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x9A响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x9A响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取UAC模式信息时出错: {str(e)}", self.log_level) + + def on_set_uac_mode(self): + """设置UAC模式(发送0x9B命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取UAC模式值 + uac_mode_index = self.uac_mode_combo.currentIndex() # 0=UAC2.0, 1=UAC1.0 + uac_mode_name = self.uac_mode_combo.currentText() + + # 弹出确认对话框 + reply = QMessageBox.question( + self, + "确认设置UAC模式", + f"将要设置UAC模式为: {uac_mode_name}\n\n" + f"注意:切换到UAC1.0模式时,设备将自动重启。\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x9B数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x9B # 命令码 (SET_UAC_MODE) + data[2] = uac_mode_index # UAC模式值 (0=UAC2.0, 1=UAC1.0) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置UAC模式为 {uac_mode_name}...", self.log_level) + h.write([0x01] + list(data)) + + # 改动原因:SET_UAC_MODE命令不返回响应 + # 如果设置为UAC1.0,设备会重启;如果设置为UAC2.0,设备保持当前状态 + if uac_mode_index == 1: # UAC1.0 - 设备会重启 + log_message(LOG_LEVEL_INFO, f"UAC模式设置为 {uac_mode_name},设备将重启...", self.log_level) + QMessageBox.information( + self, + "设备重启", + f"UAC模式已设置为: {uac_mode_name}\n\n" + f"设备正在重启,请稍候重新连接设备。" + ) + else: # UAC2.0 + log_message(LOG_LEVEL_INFO, f"UAC模式设置为 {uac_mode_name} 成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"UAC模式已设置为: {uac_mode_name}" + ) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置UAC模式时出错: {str(e)}", self.log_level) + + def on_get_current_uac_mode(self): + """获取当前UAC模式(发送0x9C命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x9C数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x9C # 命令码 (GET_CURRENT_UAC_MODE) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在获取当前UAC模式...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x9C: + # 解析当前UAC模式 + uac_mode_value = reply[3] + uac_mode_name_bytes = bytes(reply[4:12]) + uac_mode_name = uac_mode_name_bytes.decode('utf-8', errors='ignore').rstrip('\x00') + + # 更新UI显示 + self.current_uac_mode_label.setText(f"当前UAC模式: {uac_mode_name}") + # 更新下拉框选择 + self.uac_mode_combo.setCurrentIndex(uac_mode_value) + + log_message(LOG_LEVEL_INFO, f"当前UAC模式: {uac_mode_name} (值: {uac_mode_value})", self.log_level) + QMessageBox.information( + self, + "当前UAC模式", + f"当前UAC模式: {uac_mode_name}\n\n" + f"模式值: {uac_mode_value} (0=UAC2.0, 1=UAC1.0)" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x9C响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x9C响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取当前UAC模式时出错: {str(e)}", self.log_level) + + def on_set_eq_enable(self): + """设置EQ使能开关(发送0x9D命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取EQ使能开关值 + eq_enable_index = self.eq_enable_combo.currentIndex() # 0=OFF, 1=ON + eq_enable_name = self.eq_enable_combo.currentText() + + # 弹出确认对话框 + reply = QMessageBox.question( + self, + "确认设置EQ使能开关", + f"将要设置EQ使能开关为: {eq_enable_name}\n\n" + f"注意:eq_enable是全局开关,所有模式共用一个开关。\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x9D数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x9D # 命令码 (SET_EQ_ENABLE) + data[2] = eq_enable_index # EQ使能开关 (0=OFF, 1=ON) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置EQ使能开关为 {eq_enable_name}...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply_data = h.get_input_report(0x1, 64) # 64字节响应 + if reply_data and len(reply_data) == 64: + # 检查Report ID和同步头 + if reply_data[0] == 0x01 and reply_data[1] == 0x77 and reply_data[2] == 0x9D: + # 解析状态码和使能状态(改动原因:0x9D只返回状态码和使能状态,不返回模式信息) + status = reply_data[3] + current_enable = reply_data[4] + + if status == 0x00: + # 更新UI显示 + self.eq_enable_combo.setCurrentIndex(current_enable) + + log_message(LOG_LEVEL_INFO, f"EQ使能开关设置成功 - 状态: {current_enable} (全局开关)", self.log_level) + QMessageBox.information( + self, + "成功", + f"EQ使能开关已设置为: {eq_enable_name}\n\n" + f"当前状态: {'ON (启用)' if current_enable == 1 else 'OFF (禁用)'}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"EQ使能开关设置失败 (状态码: 0x{status:02x})", self.log_level) + QMessageBox.warning( + self, + "失败", + f"EQ使能开关设置失败。\n\n" + f"状态码: 0x{status:02x}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x9D响应同步头: 0x{reply_data[1]:02x} 0x{reply_data[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x9D响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置EQ使能开关时出错: {str(e)}", self.log_level) + + def on_get_eq_enable(self): + """获取EQ使能开关(发送0x9E命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0x9E数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0x9E # 命令码 (GET_EQ_ENABLE) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在获取EQ使能开关状态...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0x9E: + # 解析EQ使能开关状态(改动原因:0x9E只返回使能状态,不返回模式信息) + eq_enable = reply[3] + + # 更新UI显示 + self.eq_enable_combo.setCurrentIndex(eq_enable) + + log_message(LOG_LEVEL_INFO, f"当前EQ使能开关状态: {'ON (启用)' if eq_enable == 1 else 'OFF (禁用)'} (全局开关)", self.log_level) + QMessageBox.information( + self, + "EQ使能开关状态", + f"EQ使能开关: {'ON (启用)' if eq_enable == 1 else 'OFF (禁用)'}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0x9E响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0x9E响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"获取EQ使能开关时出错: {str(e)}", self.log_level) + + def on_set_gain_mode(self): + """设置增益模式(发送0xA0命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取增益模式值 + gain_mode = self.gain_mode_combo.currentData() + + # 弹出确认对话框 + reply = QMessageBox.question( + self, + "确认设置增益模式", + f"将要设置增益模式为: {self.gain_mode_combo.currentText()}\n\n" + f"增益模式: 0=低阻, 1=高阻\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA0数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA0 # 命令码 (SET_GAIN_MODE) + data[2] = gain_mode # 增益模式值 (0-1) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置增益模式为 {gain_mode}...", self.log_level) + h.write([0x01] + list(data)) + + h.close() + log_message(LOG_LEVEL_INFO, f"增益模式 {gain_mode} 设置成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"增益模式已设置为: {self.gain_mode_combo.currentText()}" + ) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置增益模式时出错: {str(e)}", self.log_level) + + def on_get_gain_mode(self): + """读取增益模式(发送0xA1命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA1数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA1 # 命令码 (GET_GAIN_MODE) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在读取增益模式...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0xA1: + # 解析增益模式 + gain_mode = reply[3] + + # 更新UI显示 + index = self.gain_mode_combo.findData(gain_mode) + if index >= 0: + self.gain_mode_combo.setCurrentIndex(index) + + log_message(LOG_LEVEL_INFO, f"当前增益模式: {gain_mode} ({'低阻' if gain_mode == 0 else '高阻'})", self.log_level) + QMessageBox.information( + self, + "增益模式", + f"当前增益模式: {gain_mode}\n\n" + f"{'低阻模式' if gain_mode == 0 else '高阻模式'}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0xA1响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0xA1响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取增益模式时出错: {str(e)}", self.log_level) + + def on_set_filter_mode(self): + """设置滤波器模式(发送0xA2命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取滤波器模式值 + filter_mode = self.filter_mode_combo.currentData() + + # 弹出确认对话框 + # 改动原因:更新滤波器模式范围为0-7(8种模式) + reply = QMessageBox.question( + self, + "确认设置滤波器模式", + f"将要设置滤波器模式为: {self.filter_mode_combo.currentText()}\n\n" + f"滤波器模式范围: 0-7 (8种模式)\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA2数据包 + # 改动原因:更新滤波器模式范围为0-7(8种模式) + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA2 # 命令码 (SET_FILTER_MODE) + data[2] = filter_mode # 滤波器模式值 (0-7) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置滤波器模式为 {filter_mode}...", self.log_level) + h.write([0x01] + list(data)) + + h.close() + log_message(LOG_LEVEL_INFO, f"滤波器模式 {filter_mode} 设置成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"滤波器模式已设置为: {self.filter_mode_combo.currentText()}" + ) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置滤波器模式时出错: {str(e)}", self.log_level) + + def on_get_filter_mode(self): + """读取滤波器模式(发送0xA3命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA3数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA3 # 命令码 (GET_FILTER_MODE) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在读取滤波器模式...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0xA3: + # 解析滤波器模式 + filter_mode = reply[3] + + # 更新UI显示 + index = self.filter_mode_combo.findData(filter_mode) + if index >= 0: + self.filter_mode_combo.setCurrentIndex(index) + + log_message(LOG_LEVEL_INFO, f"当前滤波器模式: {filter_mode}", self.log_level) + QMessageBox.information( + self, + "滤波器模式", + f"当前滤波器模式: {filter_mode}\n\n" + f"{self.filter_mode_combo.currentText()}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0xA3响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0xA3响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取滤波器模式时出错: {str(e)}", self.log_level) + + def on_set_game_mode(self): + """设置游戏模式(发送0xA4命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + # 获取游戏模式值 + game_mode = self.game_mode_combo.currentData() + + # 弹出确认对话框 + reply = QMessageBox.question( + self, + "确认设置游戏模式", + f"将要设置游戏模式为: {self.game_mode_combo.currentText()}\n\n" + f"游戏模式: 0=无音效, 1=FPS, 2=虚拟7.1\n\n" + f"是否继续?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA4数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA4 # 命令码 (SET_GAME_MODE) + data[2] = game_mode # 游戏模式值 (0-2) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在设置游戏模式为 {game_mode}...", self.log_level) + h.write([0x01] + list(data)) + + h.close() + log_message(LOG_LEVEL_INFO, f"游戏模式 {game_mode} 设置成功", self.log_level) + QMessageBox.information( + self, + "成功", + f"游戏模式已设置为: {self.game_mode_combo.currentText()}" + ) + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"设置游戏模式时出错: {str(e)}", self.log_level) + + def on_get_game_mode(self): + """读取游戏模式(发送0xA5命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA5数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA5 # 命令码 (GET_GAME_MODE) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在读取游戏模式...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0xA5: + # 解析游戏模式 + game_mode = reply[3] + + # 更新UI显示 + index = self.game_mode_combo.findData(game_mode) + if index >= 0: + self.game_mode_combo.setCurrentIndex(index) + + mode_names = ["无音效", "FPS模式", "虚拟7.1"] + mode_name = mode_names[game_mode] if game_mode < len(mode_names) else "未知" + + log_message(LOG_LEVEL_INFO, f"当前游戏模式: {game_mode} ({mode_name})", self.log_level) + QMessageBox.information( + self, + "游戏模式", + f"当前游戏模式: {game_mode}\n\n" + f"{mode_name}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0xA5响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0xA5响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取游戏模式时出错: {str(e)}", self.log_level) + + def on_get_firmware_version(self): + """读取固件版本(发送0xA6命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xA6数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xA6 # 命令码 (GET_FIRMWARE_VERSION) + # 其余字节保持为0 + + # 发送请求 + log_message(LOG_LEVEL_INFO, "正在读取固件版本...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0xA6: + # 解析固件版本(BCD格式) + major_version = reply[3] # 主版本号 + minor_version = reply[4] # 次版本号 + revision_version = reply[5] # 修订版本号 + + # 格式化版本字符串 + version_str = f"{major_version}.{minor_version}.{revision_version}" + + log_message(LOG_LEVEL_INFO, f"固件版本: {version_str} (BCD: 0x{major_version:02X} 0x{minor_version:02X} 0x{revision_version:02X})", self.log_level) + QMessageBox.information( + self, + "固件版本", + f"固件版本: {version_str}\n\n" + f"主版本号: {major_version}\n" + f"次版本号: {minor_version}\n" + f"修订版本号: {revision_version}\n\n" + f"BCD格式: 0x{major_version:02X} 0x{minor_version:02X} 0x{revision_version:02X}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0xA6响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0xA6响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取固件版本时出错: {str(e)}", self.log_level) + + def on_send_ex3d_cmd(self): + """发送EX3D SET命令(0xB0命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + # 获取命令码和类型 + base_cmd = self.ex3d_cmd_combo.currentData() + cmd_type = self.ex3d_cmd_type_combo.currentData() + + # 构建EX3D命令码:SET命令清除第9位,GET命令设置第9位 + if cmd_type == "SET": + ex3d_cmd = base_cmd & ~0x0100 # 清除第9位 + else: + ex3d_cmd = base_cmd | 0x0100 # 设置第9位 + # GET命令应该使用读取函数 + self.on_get_ex3d_cmd() + return + + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xB0数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xB0 # 命令码 (SET_EX3D_CMD) + + # EX3D命令码 (4字节,小端序) + data[2] = (ex3d_cmd >> 0) & 0xFF + data[3] = (ex3d_cmd >> 8) & 0xFF + data[4] = (ex3d_cmd >> 16) & 0xFF + data[5] = (ex3d_cmd >> 24) & 0xFF + + # 参数(最多14个uint32,这里使用前3个) + param1 = self.ex3d_param1.value() + param2 = self.ex3d_param2.value() + param3 = self.ex3d_param3.value() + + # 参数1 (4字节,小端序) + data[6] = (param1 >> 0) & 0xFF + data[7] = (param1 >> 8) & 0xFF + data[8] = (param1 >> 16) & 0xFF + data[9] = (param1 >> 24) & 0xFF + + # 参数2 (4字节,小端序) + data[10] = (param2 >> 0) & 0xFF + data[11] = (param2 >> 8) & 0xFF + data[12] = (param2 >> 16) & 0xFF + data[13] = (param2 >> 24) & 0xFF + + # 参数3 (4字节,小端序) + data[14] = (param3 >> 0) & 0xFF + data[15] = (param3 >> 8) & 0xFF + data[16] = (param3 >> 16) & 0xFF + data[17] = (param3 >> 24) & 0xFF + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在发送EX3D SET命令: 0x{ex3d_cmd:04X}...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0xB0: + # 解析状态码 + status = reply[3] + # 解析回显的命令码 + echo_cmd = reply[4] | (reply[5] << 8) | (reply[6] << 16) | (reply[7] << 24) + # 解析返回值 + return_value = reply[8] | (reply[9] << 8) | (reply[10] << 16) | (reply[11] << 24) + + if status == 0x00: + log_message(LOG_LEVEL_INFO, f"EX3D SET命令执行成功: 0x{echo_cmd:04X}, 返回值: 0x{return_value:08X}", self.log_level) + QMessageBox.information( + self, + "成功", + f"EX3D SET命令执行成功\n\n" + f"命令码: 0x{echo_cmd:04X}\n" + f"返回值: 0x{return_value:08X}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"EX3D SET命令执行失败: 0x{echo_cmd:04X}, 返回值: 0x{return_value:08X}", self.log_level) + QMessageBox.warning( + self, + "失败", + f"EX3D SET命令执行失败\n\n" + f"命令码: 0x{echo_cmd:04X}\n" + f"返回值: 0x{return_value:08X}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0xB0响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0xB0响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"发送EX3D SET命令时出错: {str(e)}", self.log_level) + + def on_get_ex3d_cmd(self): + """读取EX3D GET命令(0xB1命令)""" + if self.device_combo.currentData() is None: + log_message(LOG_LEVEL_ERROR, "请先选择设备", self.log_level) + return + + try: + # 获取命令码 + base_cmd = self.ex3d_cmd_combo.currentData() + cmd_type = self.ex3d_cmd_type_combo.currentData() + + # 构建EX3D命令码:GET命令设置第9位 + if cmd_type == "GET": + ex3d_cmd = base_cmd | 0x0100 # 设置第9位 + else: + # SET命令应该使用发送函数,但这里允许GET模式下读取 + ex3d_cmd = base_cmd | 0x0100 + + device_info = self.device_combo.currentData() + h = hid.device() + h.open(device_info['vendor_id'], device_info['product_id']) + h.set_nonblocking(1) + + # 构建0xB1数据包 + data = bytearray(63) # 63字节数据包 + data[0] = 0x77 # 同步头1 + data[1] = 0xB1 # 命令码 (GET_EX3D_CMD) + + # EX3D命令码 (4字节,小端序) + data[2] = (ex3d_cmd >> 0) & 0xFF + data[3] = (ex3d_cmd >> 8) & 0xFF + data[4] = (ex3d_cmd >> 16) & 0xFF + data[5] = (ex3d_cmd >> 24) & 0xFF + + # 参数(某些GET命令需要参数,如通道号、索引等) + param1 = self.ex3d_param1.value() + + # 参数1 (4字节,小端序) + data[6] = (param1 >> 0) & 0xFF + data[7] = (param1 >> 8) & 0xFF + data[8] = (param1 >> 16) & 0xFF + data[9] = (param1 >> 24) & 0xFF + + # 发送请求 + log_message(LOG_LEVEL_INFO, f"正在读取EX3D GET命令: 0x{ex3d_cmd:04X}...", self.log_level) + h.write([0x01] + list(data)) + + # 读取响应 + import time + time.sleep(0.05) # 等待设备响应 + reply = h.get_input_report(0x1, 64) # 64字节响应 + if reply and len(reply) == 64: + # 检查Report ID和同步头 + if reply[0] == 0x01 and reply[1] == 0x77 and reply[2] == 0xB1: + # 解析回显的命令码(位置3-6,4字节小端序) + echo_cmd = reply[3] | (reply[4] << 8) | (reply[5] << 16) | (reply[6] << 24) + + # 解析返回值(从reply[7]开始,最多14个uint32值) + return_values = [] + for i in range(0, 56, 4): # 最多14个uint32值 + idx = 7 + i + if idx + 3 < 64: + val = reply[idx] | (reply[idx + 1] << 8) | (reply[idx + 2] << 16) | (reply[idx + 3] << 24) + return_values.append(val) + if val == 0 and i > 16: # 遇到连续0值可能表示数据结束 + break + + log_message(LOG_LEVEL_INFO, f"EX3D GET命令响应: 0x{echo_cmd:04X}, 返回值: {return_values}", self.log_level) + + # 格式化返回值显示 + values_str = ", ".join([f"0x{val:08X}" for val in return_values[:4]]) # 只显示前4个值 + if len(return_values) > 4: + values_str += f" ... (共{len(return_values)}个值)" + + QMessageBox.information( + self, + "EX3D GET命令响应", + f"命令码: 0x{echo_cmd:04X}\n\n" + f"返回值:\n{values_str}" + ) + else: + log_message(LOG_LEVEL_ERROR, f"无效的0xB1响应同步头: 0x{reply[1]:02x} 0x{reply[2]:02x}", self.log_level) + else: + log_message(LOG_LEVEL_ERROR, "未收到0xB1响应数据", self.log_level) + + h.close() + + except Exception as e: + log_message(LOG_LEVEL_ERROR, f"读取EX3D GET命令时出错: {str(e)}", self.log_level) + +if __name__ == "__main__": + app = QApplication([]) + window = EQDesigner() + window.show() + app.exec() diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.spec b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.spec new file mode 100644 index 0000000..01b7e26 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_designer_new.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['eq_designer_new.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='eq_designer_new', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.c new file mode 100644 index 0000000..4fcf7ff --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.c @@ -0,0 +1,2017 @@ +#define DEBUG_PRINT_ENABLE 0 +#include +#include +#include +#include +#include +#include "xmath/xmath.h" +#include "eq.h" +#include "eq_flash_storage.h" +#include "lfs_io.h" +#include "lfs.h" +#include "eq_coefficient_calculator.h" +#include "debug_print.h" + +#if 0 // DEBUG_PRINT_ENABLE +#define DPRINTF(...) printf(__VA_ARGS__) +#else +#define DPRINTF(...) +#endif +// 外部函数声明 +extern uint32_t get_reference_time(void); +extern lfs_t lfs; +void delay_milliseconds(unsigned int); +#define FILE_PATH_MAX_LENGTH 30 + +// EQ Flash存储目录和文件名全局变量定义(默认值) +// 改动原因:统一管理所有EQ相关文件的存储路径,便于支持不同产品的参数存储 +#if UAC2 == 1 +char *g_eq_flash_base_dir = "uac2"; // 基础目录名 +#elif FPS == 1 +char *g_eq_flash_base_dir = "fps"; // 基础目录名 +#elif SPATIAL_MODE == 1 +char *g_eq_flash_base_dir = "spa"; // 基础目录名 +#else +char *g_eq_flash_base_dir = "default"; // 基础目录名 +#endif +char *g_eq_flash_gains_file = "gains"; // 增益文件名 +char *g_eq_flash_mode_file = "cur_mode"; // 当前模式文件名 +char *g_eq_flash_enable_file = "eq_enable"; // EQ使能开关文件名(独立存储) +char *g_eq_flash_name_prefix = "name_"; // 模式名称文件前缀 + +// 外部EQ数组声明 +// 改动原因:进一步优化EQ参数存储策略,所有采样率共用44100Hz数组 +extern eq_mode_data_t sEQ_data_44100HZ[NUM_EQ_MODES][NUM_EQ_CHANS]; +extern void debug_print_eq_mode(eq_mode_data_t *eq_data); +extern unsigned int g_eq_sample_rate; + +// 支持的采样率数组 +static const uint32_t supported_sample_rates[EQ_FLASH_MAX_SAMPLE_RATES] = EQ_SAMPLE_RATES; + +// 存储状态 +static eq_storage_status_t storage_status = {0}; + +// 脏标志数组,用于跟踪哪些模式需要同步到Flash (简化版本,不按采样率分组) +static int dirty_flags[EQ_FLASH_MAX_MODES] = {0}; + +// 增益和模式名称脏标志(分别独立) +static int gain_dirty = 0; +static int name_dirty = 0; + +// 单参数存储数据库 +static eq_single_param_database_t single_param_database = {0}; + +// 内部函数声明 +static int get_sample_rate_index(uint32_t sample_rate); +static int create_eq_directory_structure(void); +// 元数据相关函数已移除,简化存储系统 +static int verify_file_header(const eq_file_header_t *header, uint8_t mode, uint8_t channel); +static eq_mode_data_t (*get_eq_array_ptr(uint32_t sample_rate))[NUM_EQ_MODES][NUM_EQ_CHANS]; + +/** + * @brief 获取采样率在数组中的索引 + * @param sample_rate 采样率 + * @return 索引,-1表示不支持 + */ +static int get_sample_rate_index(uint32_t sample_rate) { + for (int i = 0; i < EQ_FLASH_MAX_SAMPLE_RATES; i++) { + if (supported_sample_rates[i] == sample_rate) { + return i; + } + } + return -1; +} + +/** + * @brief 获取对应采样率的EQ数组指针 + * @param sample_rate 采样率 + * @return EQ数组指针,NULL表示不支持的采样率 + */ +static eq_mode_data_t (*get_eq_array_ptr(uint32_t sample_rate))[NUM_EQ_MODES][NUM_EQ_CHANS] { + // 改动原因:所有采样率共用44100Hz数组 + return &sEQ_data_44100HZ; +} + +/** + * @brief 验证文件头 (简化版本,去除采样率字段) + * @param header 文件头指针 + * @param mode 期望的模式 + * @param channel 期望的通道 + * @return 1: 有效, 0: 无效 + */ +static int verify_file_header(const eq_file_header_t *header, uint8_t mode, uint8_t channel) { + if (!header) return 0; + + return (header->magic == EQ_FLASH_MAGIC && + header->version == EQ_FLASH_VERSION && + header->mode == mode && + header->channel == channel && + header->data_size == sizeof(eq_mode_data_t)); +} + +/** + * @brief 创建EQ目录结构(按需创建) + * @return 0: 成功, -1: 失败 + */ +static int create_eq_directory_structure(void) { + char dir_path[64]; + + DPRINTF("Creating EQ directory structure (on-demand)\n"); + + // 检查主目录是否存在,不存在才创建 + // 改动原因:使用全局变量代替硬编码目录名,支持不同产品 + if (!lfs_file_exists(g_eq_flash_base_dir)) { + lfs_mkdir(&lfs, g_eq_flash_base_dir); + DPRINTF("Created %s directory\n", g_eq_flash_base_dir); + } else { + DPRINTF("%s directory already exists\n", g_eq_flash_base_dir); + } + + // 不再为每个采样率创建子目录,因为同一模式的参数是相同的 + DPRINTF("EQ parameters will be stored without sample rate grouping\n"); + + DPRINTF("EQ directory structure creation completed\n"); + return 0; +} + +// 元数据保存函数已移除,简化存储系统 + +// 元数据加载函数已移除,简化存储系统 + +// 元数据迁移函数已移除,简化存储系统 + +/** + * @brief 初始化EQ Flash存储系统 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_init(void) { + DPRINTF("Initializing EQ Flash storage system\n"); + + // 初始化存储状态 + memset(&storage_status, 0, sizeof(storage_status)); + memset(dirty_flags, 0, sizeof(dirty_flags)); + + // 延迟一段时间,确保QSPI Flash已经初始化完成 + delay_milliseconds(100); + + // 尝试初始化LFS,最多重试3次 + int retry_count = 0; + int lfs_init_result = -1; + + while (retry_count < 3 && lfs_init_result != 0) { + lfs_init_result = lfs_init(); + if (lfs_init_result != 0) { + retry_count++; + DPRINTF("Failed to initialize LFS, retry %d/3\n", retry_count); + if (retry_count < 3) { + delay_milliseconds(200 * retry_count); // 递增延迟 + } + } + } + + if (lfs_init_result != 0) { + DPRINTF("Failed to initialize LFS after %d retries, continuing without Flash storage\n", retry_count); + // 即使LFS初始化失败,也标记为已初始化,系统继续运行 + storage_status.is_initialized = 1; + storage_status.has_flash_data = 0; + return 0; + } + + DPRINTF("LFS initialized successfully\n"); + + // 创建目录结构(按需创建) + create_eq_directory_structure(); + + // 检查是否有现有数据(通过检查目录是否存在) + // 改动原因:使用全局变量代替硬编码目录名 + storage_status.has_flash_data = lfs_file_exists(g_eq_flash_base_dir) ? 1 : 0; + storage_status.is_initialized = 1; + + DPRINTF("EQ Flash storage initialized, has_data: %d\n", storage_status.has_flash_data); + + lfs_deinit(); + return 0; +} + +/** + * @brief 反初始化EQ Flash存储系统 + */ +void eq_flash_deinit(void) { + DPRINTF("Deinitializing EQ Flash storage system\n"); + storage_status.is_initialized = 0; + storage_status.has_flash_data = 0; + memset(dirty_flags, 0, sizeof(dirty_flags)); +} + +// 注意:eq_flash_load_all 函数已移除,现在使用动态单参数存储系统 + +// 注意:eq_flash_save_all 函数已移除,现在使用动态单参数存储系统 + +/** + * @brief 从Flash加载指定模式的EQ参数 + * @param mode EQ模式 + * @return 0: 成功, -1: 失败 + */ +/** + * @brief Initialize eq_mode_data_t with default bypass values + * @param eq_data Target data to initialize + * @param sample_rate Sample rate to set + */ +static void init_eq_data_defaults(eq_mode_data_t *eq_data, uint32_t sample_rate) { + memset(eq_data, 0, sizeof(eq_mode_data_t)); + eq_data->sample_rate = sample_rate; + eq_data->total_bshift = 0; + eq_data->post_gain_db = 0; + for (int i = 0; i < MAX_EQ_BANDS; i++) { + eq_data->bands[i].index = i; + eq_data->bands[i].type = FILTER_TYPE_BYPASS; + eq_data->bands[i].fc = 1000.0f; + eq_data->bands[i].q = 0.707f; + eq_data->bands[i].bw = 1.0f; + eq_data->bands[i].gain = 0.0f; + } + for (int i = 0; i < 8; i++) { + eq_data->filter.coef[0][i] = 1 << 30; + eq_data->filter.coef[1][i] = 0; + eq_data->filter.coef[2][i] = 0; + eq_data->filter.coef[3][i] = 0; + eq_data->filter.coef[4][i] = 0; + } +} + +int eq_flash_load_mode(uint8_t mode) { + //DPRINTF("Loading EQ mode %d from Flash\n", mode); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: EQ Flash storage not initialized\n"); + return -1; + } + + if (mode >= NUM_EQ_MODES) { + DPRINTF(" Error: Invalid mode %d (max: %d)\n", mode, NUM_EQ_MODES - 1); + return -1; + } + + // 获取当前采样率的EQ数组指针 + eq_mode_data_t (*eq_array)[NUM_EQ_MODES][NUM_EQ_CHANS] = get_eq_array_ptr(g_eq_sample_rate); + if (!eq_array) { + DPRINTF(" Error: Failed to get EQ array for current sample rate %lu\n", (unsigned long)g_eq_sample_rate); + return -1; + } + + // 加载左右两个声道的数据 + for (int ch = 0; ch < NUM_EQ_CHANS; ch++) { + // 构建文件路径 + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/mode%d_ch%d.bin", g_eq_flash_base_dir, mode, ch); + DPRINTF(" File path (channel %d): %s\n", ch, file_path); + + // 检查文件是否存在 + if (!lfs_file_exists(file_path)) { + DPRINTF(" ****************** file is not exist (ch %d), initializing with default values\n", ch); + init_eq_data_defaults(&(*eq_array)[mode][ch], g_eq_sample_rate); + DPRINTF(" Initialized with default values for mode %d, channel %d\n", mode, ch); + continue; // 继续处理下一个通道 + } + + // 准备读取缓冲区 + eq_file_header_t header; + eq_flash_data_t flash_data; + uint8_t file_data[sizeof(header) + sizeof(flash_data)]; + + // 使用lfs_read_config读取文件 + DPRINTF(" Read file data, size: %zu bytes\n", sizeof(file_data)); + lfs_read_config(file_path, file_data, sizeof(file_data)); + + // 解析文件数据 + memcpy(&header, file_data, sizeof(header)); + memcpy(&flash_data, file_data + sizeof(header), sizeof(flash_data)); + + // 检查文件头基本信息 + DPRINTF(" File header: magic=0x%08X, version=%u, mode=%u\n", + header.magic, header.version, header.mode); + + // 验证文件头 + DPRINTF(" Verifying file header:\n"); + DPRINTF(" Expected: magic=0x%08X, version=%u, mode=%u, channel=%d, data_size=%zu\n", + EQ_FLASH_MAGIC, EQ_FLASH_VERSION, mode, ch, sizeof(eq_flash_data_t)); + DPRINTF(" Actual: magic=0x%08X, version=%u, mode=%u, channel=%u, data_size=%u\n", + header.magic, header.version, header.mode, header.channel, header.data_size); + + if (!verify_file_header(&header, mode, ch)) { + DPRINTF(" Invalid file header for mode %d, channel %d\n", mode, ch); + init_eq_data_defaults(&(*eq_array)[mode][ch], g_eq_sample_rate); + DPRINTF(" Initialized with default values for mode %d, channel %d\n", mode, ch); + continue; // 继续处理下一个通道 + } + + // 验证CRC32 + uint32_t calculated_crc = eq_flash_calculate_crc32((uint8_t*)&flash_data, sizeof(flash_data)); + if (calculated_crc != header.crc32) { + DPRINTF(" CRC32 mismatch for mode %d, sample rate %lu, channel %d\n", mode, (unsigned long)g_eq_sample_rate, ch); + init_eq_data_defaults(&(*eq_array)[mode][ch], g_eq_sample_rate); + DPRINTF(" Initialized with default values due to CRC32 mismatch for channel %d\n", ch); + continue; // 继续处理下一个通道 + } + + // 将Flash数据加载到EQ数组 + eq_mode_data_t *eq_data = &(*eq_array)[mode][ch]; + + // 检查Flash数据的完整性 + DPRINTF(" Flash data before restore (ch %d): active_bands_count=%d\n", + ch, flash_data.active_bands_count); + + DPRINTF(" EQ array after copy (ch %d): sample_rate=%u, post_gain_db=%ld\n", + ch, eq_data->sample_rate, (long)eq_data->post_gain_db); + DPRINTF(" EQ array total_bshift=%ld\n", + (long)eq_data->total_bshift); + + + // 检查内存地址是否有效 + if (eq_data == NULL) { + DPRINTF(" ERROR: eq_data is NULL\n"); + continue; // 继续处理下一个通道 + } + + DPRINTF(" eq_data address: %p\n", (void*)eq_data); + DPRINTF(" eq_data size: %zu bytes\n", sizeof(eq_mode_data_t)); + + // 检查bands数组地址是否有效 + DPRINTF(" bands array address: %p\n", (void*)&eq_data->bands); + DPRINTF(" bands array size: %zu bytes\n", sizeof(eq_data->bands)); + + // 检查bands数组偏移量 + DPRINTF(" bands offset in struct: %zu bytes\n", (size_t)&eq_data->bands - (size_t)eq_data); + + // 检查第一个band的地址 + DPRINTF(" bands[0] address: %p\n", (void*)&eq_data->bands[0]); + DPRINTF(" bands[0] size: %zu bytes\n", sizeof(eq_data->bands[0])); + + // 检查内存对齐 + DPRINTF(" eq_data alignment: %zu\n", (size_t)eq_data % 4); + DPRINTF(" bands alignment: %zu\n", (size_t)&eq_data->bands % 4); + + // 尝试安全地访问第一个band + DPRINTF(" Attempting to access bands[0]...\n"); + + // 先检查bands[0]的地址是否在合理范围内 + if ((size_t)&eq_data->bands[0] < (size_t)eq_data || + (size_t)&eq_data->bands[0] > (size_t)eq_data + sizeof(eq_mode_data_t)) { + DPRINTF(" ERROR: bands[0] address out of range\n"); + continue; // 继续处理下一个通道 + } + + // 暂时注释掉有问题的调试代码 + DPRINTF(" Accessing bands[0].index...\n"); + DPRINTF(" bands[0].index = %d\n", eq_data->bands[0].index); + + DPRINTF(" Accessing bands[0].type...\n"); + DPRINTF(" bands[0].type = %d\n", eq_data->bands[0].type); + + DPRINTF(" Accessing bands[0].fc...\n"); + DPRINTF(" bands[0].fc = %d.%02d Hz\n", (int)eq_data->bands[0].fc, (int)((eq_data->bands[0].fc - (int)eq_data->bands[0].fc) * 100)); + + DPRINTF(" Accessing bands[0].q...\n"); + DPRINTF(" bands[0].q = %d.%04d\n", (int)eq_data->bands[0].q, (int)((eq_data->bands[0].q - (int)eq_data->bands[0].q) * 10000)); + + DPRINTF(" Successfully loaded EQ parameters for mode %d, channel %d\n", mode, ch); + } + + return 0; +} + +/** + * @brief 检查滤波器是否为默认值(bypass) + * @param band 滤波器参数 + * @return 1: 是默认值, 0: 不是默认值 + */ +int is_filter_bypass(const filter_params_t *band) { + if (band == NULL) return 1; + + // 检查是否为bypass类型 + if (band->type != FILTER_TYPE_BYPASS) return 0; + + // 检查其他参数是否为默认值 + if (band->fc != 1000.0f) return 0; + if (band->q != 0.707f) return 0; + if (band->bw != 1.0f) return 0; + if (band->gain != 0.0f) return 0; + + return 1; +} + +/** + * @brief 从EQ数据中提取非默认滤波器到Flash存储格式 + * @param eq_data 源EQ数据 + * @param flash_data 目标Flash存储数据 + * @return 0: 成功, -1: 失败 + */ +int extract_active_filters(const eq_mode_data_t *eq_data, eq_flash_data_t *flash_data) { + if (eq_data == NULL || flash_data == NULL) return -1; + + flash_data->active_bands_count = 0; + + // 遍历所有滤波器,只保存非bypass的 + for (int i = 0; i < MAX_EQ_BANDS; i++) { + if (!is_filter_bypass(&eq_data->bands[i])) { + if (flash_data->active_bands_count >= MAX_EQ_BANDS) { + DPRINTF(" Warning: Too many active filters, truncating\n"); + break; + } + + // 复制非默认滤波器 + flash_data->active_bands[flash_data->active_bands_count] = eq_data->bands[i]; + flash_data->active_bands[flash_data->active_bands_count].index = flash_data->active_bands_count; + flash_data->active_bands_count++; + } + } + + DPRINTF(" Extracted %d active filters from %d total filters\n", + flash_data->active_bands_count, MAX_EQ_BANDS); + + return 0; +} + +/** + * @brief 从Flash存储格式恢复EQ数据 + * @param flash_data 源Flash存储数据 + * @param eq_data 目标EQ数据 + * @return 0: 成功, -1: 失败 + */ +int restore_eq_from_flash(const eq_flash_data_t *flash_data, eq_mode_data_t *eq_data) { + if (flash_data == NULL || eq_data == NULL) return -1; + + // 初始化所有滤波器为bypass + for (int i = 0; i < MAX_EQ_BANDS; i++) { + eq_data->bands[i].index = i; + eq_data->bands[i].type = FILTER_TYPE_BYPASS; + eq_data->bands[i].fc = 1000.0f; + eq_data->bands[i].q = 0.707f; + eq_data->bands[i].bw = 1.0f; + eq_data->bands[i].gain = 0.0f; + } + + // 恢复非默认滤波器 + for (int i = 0; i < flash_data->active_bands_count && i < MAX_EQ_BANDS; i++) { + int target_index = flash_data->active_bands[i].index; + if (target_index >= 0 && target_index < MAX_EQ_BANDS) { + eq_data->bands[target_index] = flash_data->active_bands[i]; + } + } + + DPRINTF(" Restored %d active filters to EQ data\n", flash_data->active_bands_count); + + return 0; +} + +// ==================== 单参数动态存储系统实现 ==================== + +/** + * @brief 初始化单参数存储数据库 + * @return 0: 成功, -1: 失败 + */ +int eq_single_param_database_init(void) { + DPRINTF("Initializing single parameter storage database\n"); + + memset(&single_param_database, 0, sizeof(single_param_database)); + single_param_database.max_params = MAX_EQ_BANDS * 8 * 2; // 最多8个模式×2通道的所有参数 + single_param_database.param_count = 0; + single_param_database.last_save_time = 0; + + DPRINTF(" Single parameter database initialized: max_params=%d\n", single_param_database.max_params); + return 0; +} + +/** + * @brief 处理0x8c/0x8d命令的单参数变化 + * @param data 接收到的数据 + * @param len 数据长度 + * @return 0: 成功, -1: 失败 + */ +int eq_process_single_param_change(uint32_t sample_rate, uint8_t mode, uint8_t channel, uint8_t band_index, filter_params_t *band) { + // 添加到单参数数据库 + if (eq_add_single_param(sample_rate, mode, channel, band_index, band) == 0) { + // 标记为脏数据 + dirty_flags[mode] = 1; + + return 0; + } else { + return -1; + } +} + +/** + * @brief 比较单个参数是否不同 + * @param old_params 旧参数 + * @param new_params 新参数 + * @return 1: 不同, 0: 相同 + */ +int eq_single_params_different(const filter_params_t *old_params, const filter_params_t *new_params) { + if (old_params == NULL || new_params == NULL) return 1; + + // 比较各个字段 + if (old_params->type != new_params->type) return 1; + if (fabsf(old_params->fc - new_params->fc) > 0.01f) return 1; + if (fabsf(old_params->q - new_params->q) > 0.0001f) return 1; + if (fabsf(old_params->bw - new_params->bw) > 0.01f) return 1; + if (fabsf(old_params->gain - new_params->gain) > 0.01f) return 1; + + return 0; +} + +/** + * @brief 添加单个参数变化到数据库 + * @param sample_rate 采样率 + * @param mode 模式 + * @param channel 通道 + * @param band_index 滤波器索引 + * @param new_params 新参数 + * @return 0: 成功, -1: 失败 + */ +int eq_add_single_param(uint32_t sample_rate, uint8_t mode, uint8_t channel, + uint8_t band_index, const filter_params_t *new_params) { + if (new_params == NULL) return -1; + + // 检查是否已存在相同的参数记录 + for (int i = 0; i < single_param_database.param_count; i++) { + eq_single_param_t *param = &single_param_database.params[i]; + if (param->sample_rate == sample_rate && + param->mode == mode && + param->channel == channel && + param->band_index == band_index) { + // 更新现有记录 + param->params = *new_params; + param->timestamp = get_reference_time(); + param->is_dirty = 1; + DPRINTF(" Updated existing single parameter: rate=%u, mode=%d, ch=%d, band=%d\n", + sample_rate, mode, channel, band_index); + return 0; + } + } + + // 检查数据库是否已满 + if (single_param_database.param_count >= single_param_database.max_params) { + DPRINTF(" Warning: Single parameter database full, overwriting oldest entry\n"); + // 移动所有条目向前一位,覆盖最旧的 + memmove(&single_param_database.params[0], &single_param_database.params[1], + (single_param_database.max_params - 1) * sizeof(eq_single_param_t)); + single_param_database.param_count--; + } + + // 添加新参数 + eq_single_param_t *param = &single_param_database.params[single_param_database.param_count]; + param->sample_rate = sample_rate; + param->mode = mode; + param->channel = channel; + param->band_index = band_index; + param->params = *new_params; + param->timestamp = get_reference_time(); + param->is_dirty = 1; + + single_param_database.param_count++; + + DPRINTF(" Added single parameter: rate=%u, mode=%d, ch=%d, band=%d, count=%d\n", + sample_rate, mode, channel, band_index, single_param_database.param_count); + + return 0; +} + +/** + * @brief 保存单个参数到Flash + * @param sample_rate 采样率 + * @param mode 模式 + * @param channel 通道 + * @param band_index 滤波器索引 + * @return 0: 成功, -1: 失败 + */ +int eq_save_single_param(uint32_t sample_rate, uint8_t mode, uint8_t channel, uint8_t band_index) { + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX ) { + return -1; + } + DPRINTF("Saving single parameter to Flash: mode=%d, ch=%d, band=%d (applying to all sample rates)\n", + mode, channel, band_index); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 构建单个参数文件路径 - 简化格式:只存储mode和band信息 + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/mode%d_band%d.bin", + g_eq_flash_base_dir, mode, band_index); + + // 改动原因:优化EQ参数存储策略,统一参数源为44100Hz + // 保存参数时,总是从44100Hz读取参数,确保参数一致性 + eq_mode_data_t (*eq_array)[NUM_EQ_MODES][NUM_EQ_CHANS] = get_eq_array_ptr(44100); + if (!eq_array) { + DPRINTF(" Error: Failed to get EQ array for sample rate 44100 (unified source)\n"); + return -1; + } + + // 从44100Hz统一参数源读取参数 + filter_params_t *param = &(*eq_array)[mode][channel].bands[band_index]; + + // 创建文件头 + eq_file_header_t header; + header.magic = EQ_FLASH_MAGIC; + header.version = EQ_FLASH_VERSION; + header.mode = mode; + header.channel = 0; // 固定为channel 0,因为只存储channel 0的参数 + header.reserved = band_index; // 使用reserved字段存储band_index + header.data_size = sizeof(filter_params_t); + header.crc32 = eq_flash_calculate_crc32((uint8_t*)param, sizeof(filter_params_t)); + + // 创建文件数据 + uint8_t file_data[sizeof(header) + sizeof(filter_params_t)]; + memcpy(file_data, &header, sizeof(header)); + memcpy(file_data + sizeof(header), param, sizeof(filter_params_t)); + + // 写入文件 + lfs_write_config(file_path, file_data, sizeof(file_data)); + + DPRINTF(" Successfully saved single parameter to %s\n", file_path); + + // 改动原因:优化EQ参数存储策略,参数保存后需要同步到44100Hz并更新所有采样率 + // 确保44100Hz作为统一参数源,然后通过copy_params_from_44100函数同步到其他采样率 + // 注意:这里只保存参数,系数会在切换采样率时动态计算 + // 参数已经保存到Flash,加载时会自动应用到所有采样率 + + return 0; +} + +/** + * @brief 从Flash加载单个参数 + * @param sample_rate 采样率 + * @param mode 模式 + * @param channel 通道 + * @param band_index 滤波器索引 + * @return 0: 成功, -1: 失败 + */ +int eq_load_single_param(uint32_t sample_rate, uint8_t mode, uint8_t channel, uint8_t band_index) { + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX ) { + return -1; + } + DPRINTF("Loading single parameter from Flash: mode=%d, ch=%d, band=%d (applying to all sample rates)\n", + mode, channel, band_index); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 构建单个参数文件路径 - 简化格式:只存储mode和band信息 + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/mode%d_band%d.bin", + g_eq_flash_base_dir, mode, band_index); + + // 检查文件是否存在 + if (!lfs_file_exists(file_path)) { + DPRINTF(" Single parameter file does not exist: %s\n", file_path); + return -1; + } + + // 准备读取缓冲区 + eq_file_header_t header; + filter_params_t param; + uint8_t file_data[sizeof(header) + sizeof(filter_params_t)]; + + // 读取文件 + lfs_read_config(file_path, file_data, sizeof(file_data)); + + // 解析文件数据 + memcpy(&header, file_data, sizeof(header)); + memcpy(¶m, file_data + sizeof(header), sizeof(filter_params_t)); + + // 验证文件头 + if (header.magic != EQ_FLASH_MAGIC || + header.version != EQ_FLASH_VERSION || + header.mode != mode || + header.channel != 0 || // 固定验证channel为0 + header.reserved != band_index) { + DPRINTF(" Error: Invalid single parameter file header\n"); + return -1; + } + + // 验证CRC32 + uint32_t calculated_crc = eq_flash_calculate_crc32((uint8_t*)¶m, sizeof(filter_params_t)); + if (calculated_crc != header.crc32) { + DPRINTF(" Error: CRC32 mismatch for single parameter\n"); + return -1; + } + + // 为所有采样率应用参数到两个通道并计算系数 + for (int i = 0; i < EQ_FLASH_MAX_SAMPLE_RATES; i++) { + uint32_t fs = supported_sample_rates[i]; + eq_mode_data_t (*eq_array)[NUM_EQ_MODES][NUM_EQ_CHANS] = get_eq_array_ptr(fs); + if (eq_array) { + // 应用到两个通道 + (*eq_array)[mode][0].bands[band_index] = param; + (*eq_array)[mode][1].bands[band_index] = param; + } + } + + return 0; +} + +/** + * @brief 保存所有脏参数到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_params(void) { + //DPRINTF("Saving dirty single parameters to Flash\n"); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + //DPRINTF(" Single param database count: %d\n", single_param_database.param_count); + + int saved_count = 0; + + // 遍历所有脏参数,但只保存一次(不按采样率分组) + bool saved_params[EQ_FLASH_MAX_MODES][NUM_EQ_CHANS][MAX_EQ_BANDS] = {0}; // 记录已保存的参数 + + for (int i = 0; i < single_param_database.param_count; i++) { + eq_single_param_t *param = &single_param_database.params[i]; + + DPRINTF(" Checking param %d: mode=%d, ch=%d, band=%d, dirty=%d\n", + i, param->mode, param->channel, param->band_index, param->is_dirty); + + if (param->is_dirty && !saved_params[param->mode][param->channel][param->band_index]) { + DPRINTF(" Saving dirty single parameter: mode=%d, ch=%d, band=%d (applying to all sample rates)\n", + param->mode, param->channel, param->band_index); + if (param->mode < EQ_USER_MODE_MIN || param->mode > EQ_USER_MODE_MAX ) { + printf(" Invalid mode %d, only user modes (%d-%d) can be saved\n", param->mode, EQ_USER_MODE_MIN, EQ_USER_MODE_MAX); + continue; + } + if (eq_save_single_param(param->sample_rate, param->mode, param->channel, param->band_index) == 0) { + // 标记所有相同模式的参数为已保存 + for (int j = 0; j < single_param_database.param_count; j++) { + eq_single_param_t *p = &single_param_database.params[j]; + if (p->mode == param->mode && p->channel == param->channel && p->band_index == param->band_index) { + p->is_dirty = 0; + } + } + saved_params[param->mode][param->channel][param->band_index] = true; + saved_count++; + DPRINTF(" Successfully saved single parameter\n"); + } else { + DPRINTF(" Error: Failed to save single parameter\n"); + } + } + } + + if (saved_count > 0) { + DPRINTF(" Saved %d dirty single parameters\n", saved_count); + } + return 0; +} +/** + * @brief 保存脏的增益到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_gain(void) { + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 检查是否有脏的增益 + if (!gain_dirty) { + DPRINTF(" No dirty gain to save\n"); + return 0; + } + + DPRINTF(" Saving dirty gain\n"); + // 使用新的独立保存函数 + if (eq_save_gain() == 0) { + gain_dirty = 0; // 只清除增益脏标志 + DPRINTF(" Successfully saved gain\n"); + return 0; + } else { + DPRINTF(" Error: Failed to save gain\n"); + return -1; + } +} +/** + * @brief 保存脏的模式名称到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_names(void) { + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 检查是否有脏的模式名称 + if (!name_dirty) { + DPRINTF(" No dirty mode names to save\n"); + return 0; + } + + DPRINTF(" Saving dirty mode names\n"); + // 使用新的独立保存函数,逐个保存每个用户模式的name + int saved_count = 0; + int skipped_count = 0; + + for (int i = EQ_USER_MODE_MIN; i <= EQ_USER_MODE_MAX; i++) { + int result = eq_save_name(i); + if (result == 0) { + saved_count++; + } else if (result == 1) { + skipped_count++; // 跳过(与初始值相同或为空) + } + // result == -1 表示错误,但继续处理其他模式 + } + + name_dirty = 0; // 清除名称脏标志(无论是否保存成功) + + if (saved_count > 0) { + DPRINTF(" Successfully saved %d mode name(s), skipped %d\n", saved_count, skipped_count); + return 0; + } else if (skipped_count > 0) { + DPRINTF(" All mode names skipped (same as default or empty), %d skipped\n", skipped_count); + return 0; // 跳过是正常的,不算错误 + } else { + DPRINTF(" Error: Failed to save mode names\n"); + return -1; + } +} + +/** + * @brief 保存用户模式的增益到Flash(只保存用户模式6-8) + * @return 0: 成功, -1: 失败 + */ +int eq_save_gain(void) { + DPRINTF("Saving user mode gains to Flash\n"); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 构建文件路径 + // 改动原因:使用全局变量代替硬编码文件名,统一文件路径管理,将文件存储到基础目录下 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s", g_eq_flash_base_dir, g_eq_flash_gains_file); + + // 准备数据(只存储用户模式6-8的gain) + eq_gain_data_t data = {0}; + + // 从全局模式信息中获取数据 + extern eq_mode_info_t g_mode_info; + + // 只复制用户模式(6-8)的增益数据 + for (int i = EQ_USER_MODE_MIN; i <= EQ_USER_MODE_MAX; i++) { + int idx = i - EQ_USER_MODE_MIN; // 转换为数组索引 0, 1, 2 + data.mode_gains[idx] = g_mode_info.mode_gains[i]; + debug_printf(" Saving user mode %d: gain=%d\n", i, data.mode_gains[idx]); + } + + // 创建文件头 + eq_gain_header_t header; + header.magic = 0x4551474E; // "EQGN" + header.version = EQ_FLASH_VERSION; + header.data_size = sizeof(eq_gain_data_t); + header.crc32 = eq_flash_calculate_crc32((uint8_t*)&data, sizeof(eq_gain_data_t)); + + // 创建文件数据 + uint8_t file_data[sizeof(header) + sizeof(eq_gain_data_t)]; + memcpy(file_data, &header, sizeof(header)); + memcpy(file_data + sizeof(header), &data, sizeof(eq_gain_data_t)); + + // 写入文件 + lfs_write_config(file_path, file_data, sizeof(file_data)); + + DPRINTF(" Successfully saved gains to %s\n", file_path); + return 0; +} + +/** + * @brief 保存单个用户模式的名称到Flash(只有与初始值不同且非空才保存) + * @param mode 模式号(必须是用户模式6-8) + * @return 0: 成功, -1: 失败, 1: 跳过(与初始值相同或为空) + */ +int eq_save_name(uint8_t mode) { + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX) { + DPRINTF(" Error: Invalid mode %d, only user modes (%d-%d) can be saved\n", + mode, EQ_USER_MODE_MIN, EQ_USER_MODE_MAX); + return -1; + } + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 从全局模式信息中获取当前名称 + extern eq_mode_info_t g_mode_info; + const char* current_name = g_mode_info.mode_names[mode]; + + // 获取初始默认名称 + const char* default_names[] = {"user 1", "user 2", "user 3"}; + int idx = mode - EQ_USER_MODE_MIN; // 转换为数组索引 0, 1, 2 + const char* default_name = default_names[idx]; + + // 检查是否与初始值相同 + if (strncmp(current_name, default_name, 15) == 0) { + DPRINTF(" Mode %d name '%s' is same as default, skip saving\n", mode, current_name); + return 1; // 跳过保存 + } + + // 检查是否为空(全0或全空格) + bool name_is_empty = true; + if (current_name[0] != '\0') { + for (int i = 0; i < 16 && current_name[i] != '\0'; i++) { + if (current_name[i] != ' ') { + name_is_empty = false; + break; + } + } + } + + if (name_is_empty) { + DPRINTF(" Mode %d name is empty, skip saving\n", mode); + // 如果文件存在,删除它 + // 改动原因:使用全局变量代替硬编码文件名,统一文件路径管理,将文件存储到基础目录下 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s%d", g_eq_flash_base_dir, g_eq_flash_name_prefix, mode); + if (lfs_file_exists(file_path)) { + // 删除文件(通过写入空文件或使用删除函数) + DPRINTF(" Deleting empty name file: %s\n", file_path); + // 注意:LittleFS可能没有直接的删除函数,可以通过写入空文件或标记删除 + } + return 1; // 跳过保存 + } + + DPRINTF("Saving user mode %d name to Flash: '%s'\n", mode, current_name); + + // 构建文件路径 + // 改动原因:使用全局变量代替硬编码文件名,统一文件路径管理,将文件存储到基础目录下 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s%d", g_eq_flash_base_dir, g_eq_flash_name_prefix, mode); + + // 准备数据 + eq_name_data_t data = {0}; + strncpy(data.mode_name, current_name, 15); + data.mode_name[15] = '\0'; // 确保字符串终止 + + // 创建文件头 + eq_name_header_t header; + header.magic = 0x45514E4D; // "EQNM" + header.version = EQ_FLASH_VERSION; + header.mode = mode; + header.reserved[0] = 0; + header.reserved[1] = 0; + header.reserved[2] = 0; + header.data_size = sizeof(eq_name_data_t); + header.crc32 = eq_flash_calculate_crc32((uint8_t*)&data, sizeof(eq_name_data_t)); + + // 创建文件数据 + uint8_t file_data[sizeof(header) + sizeof(eq_name_data_t)]; + memcpy(file_data, &header, sizeof(header)); + memcpy(file_data + sizeof(header), &data, sizeof(eq_name_data_t)); + + // 写入文件 + lfs_write_config(file_path, file_data, sizeof(file_data)); + + DPRINTF(" Successfully saved mode %d name to %s\n", mode, file_path); + return 0; +} + +/** + * @brief 保存增益和模式名称到Flash(兼容旧接口,内部调用新函数) + * @return 0: 成功, -1: 失败 + */ +int eq_save_gain_and_names(void) { + int ret = 0; + + // 保存gain + if (eq_save_gain() != 0) { + ret = -1; + } + + // 保存每个用户模式的name(如果与初始值不同且非空) + for (int i = EQ_USER_MODE_MIN; i <= EQ_USER_MODE_MAX; i++) { + int result = eq_save_name(i); + if (result == -1) { + ret = -1; // 错误 + } + // result == 1 表示跳过,这是正常的 + } + + return ret; +} + +/** + * @brief 标记增益为脏(需要保存) + */ +void eq_mark_gain_dirty(void) { + gain_dirty = 1; + DPRINTF("Marked gain as dirty for Flash sync\n"); +} + +/** + * @brief 标记模式名称为脏(需要保存) + */ +void eq_mark_name_dirty(void) { + name_dirty = 1; + DPRINTF("Marked mode names as dirty for Flash sync\n"); +} + +/** + * @brief 从Flash加载用户模式的增益 + * @return 0: 成功, -1: 失败 + */ +int eq_load_gain(void) { + DPRINTF("Loading user mode gains from Flash\n"); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 构建文件路径 + // 改动原因:使用全局变量代替硬编码文件名,统一文件路径管理,将文件存储到基础目录下 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s", g_eq_flash_base_dir, g_eq_flash_gains_file); + + // 检查文件是否存在 + if (!lfs_file_exists(file_path)) { + debug_printf(" Gains file does not exist: %s, using defaults\n", file_path); + return -1; // 文件不存在,使用默认值 + } + + // 准备读取缓冲区 + eq_gain_header_t header; + eq_gain_data_t data; + uint8_t file_data[sizeof(header) + sizeof(eq_gain_data_t)]; + + // 读取文件 + lfs_read_config(file_path, file_data, sizeof(file_data)); + + // 解析文件数据 + memcpy(&header, file_data, sizeof(header)); + memcpy(&data, file_data + sizeof(header), sizeof(eq_gain_data_t)); + + // 验证文件头 + if (header.magic != 0x4551474E) { // "EQGN" + DPRINTF(" Error: Invalid gains file magic\n"); + return -1; + } + + if (header.version != EQ_FLASH_VERSION) { + DPRINTF(" Error: Unsupported gains file version\n"); + return -1; + } + + // 验证CRC32 + uint32_t calculated_crc = eq_flash_calculate_crc32((uint8_t*)&data, sizeof(eq_gain_data_t)); + if (calculated_crc != header.crc32) { + DPRINTF(" Error: CRC32 mismatch for gains data\n"); + return -1; + } + + // 更新全局模式信息(只加载用户模式) + extern eq_mode_info_t g_mode_info; + + // 只加载用户模式(6-8)的增益 + for (int i = EQ_USER_MODE_MIN; i <= EQ_USER_MODE_MAX; i++) { + int idx = i - EQ_USER_MODE_MIN; // 转换为数组索引 0, 1, 2 + g_mode_info.mode_gains[i] = data.mode_gains[idx]; + debug_printf(" Loaded user mode %d: gain=%d\n", i, g_mode_info.mode_gains[i]); + } + + DPRINTF(" Successfully loaded gains\n"); + return 0; +} + +/** + * @brief 从Flash加载单个用户模式的名称 + * @param mode 模式号(必须是用户模式6-8) + * @return 0: 成功, -1: 失败(文件不存在或错误) + */ +int eq_load_name(uint8_t mode) { + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX) { + DPRINTF(" Error: Invalid mode %d, only user modes (%d-%d) can be loaded\n", + mode, EQ_USER_MODE_MIN, EQ_USER_MODE_MAX); + return -1; + } + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 构建文件路径 + // 改动原因:使用全局变量代替硬编码文件名,统一文件路径管理,将文件存储到基础目录下 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s%d", g_eq_flash_base_dir, g_eq_flash_name_prefix, mode); + + // 检查文件是否存在 + if (!lfs_file_exists(file_path)) { + DPRINTF(" Mode %d name file does not exist: %s, using default\n", mode, file_path); + return -1; // 文件不存在,使用默认值 + } + + // 准备读取缓冲区 + eq_name_header_t header; + eq_name_data_t data; + uint8_t file_data[sizeof(header) + sizeof(eq_name_data_t)]; + + // 读取文件 + lfs_read_config(file_path, file_data, sizeof(file_data)); + + // 解析文件数据 + memcpy(&header, file_data, sizeof(header)); + memcpy(&data, file_data + sizeof(header), sizeof(eq_name_data_t)); + + // 验证文件头 + if (header.magic != 0x45514E4D) { // "EQNM" + DPRINTF(" Error: Invalid name file magic for mode %d\n", mode); + return -1; + } + + if (header.mode != mode) { + DPRINTF(" Error: Mode mismatch in name file (expected %d, got %d)\n", mode, header.mode); + return -1; + } + + if (header.version != EQ_FLASH_VERSION) { + DPRINTF(" Error: Unsupported name file version for mode %d\n", mode); + return -1; + } + + // 验证CRC32 + uint32_t calculated_crc = eq_flash_calculate_crc32((uint8_t*)&data, sizeof(eq_name_data_t)); + if (calculated_crc != header.crc32) { + DPRINTF(" Error: CRC32 mismatch for mode %d name data\n", mode); + return -1; + } + + // 更新全局模式信息 + extern eq_mode_info_t g_mode_info; + strncpy(g_mode_info.mode_names[mode], data.mode_name, 15); + g_mode_info.mode_names[mode][15] = '\0'; // 确保字符串终止 + + DPRINTF(" Loaded user mode %d name: '%s'\n", mode, g_mode_info.mode_names[mode]); + return 0; +} + +/** + * @brief 从Flash加载增益和模式名称(兼容旧接口,内部调用新函数) + * @return 0: 成功, -1: 失败 + */ +int eq_load_gain_and_names(void) { + int ret = 0; + + // 加载gain + if (eq_load_gain() != 0) { + ret = -1; // 文件不存在是正常的,使用默认值 + } + + // 加载每个用户模式的name + for (int i = EQ_USER_MODE_MIN; i <= EQ_USER_MODE_MAX; i++) { + if (eq_load_name(i) != 0) { + // 文件不存在是正常的,使用默认值 + } + } + + DPRINTF("Successfully loaded gain and mode names\n"); + return ret; +} + +/** + * @brief 系统开机时加载所有参数并计算系数 + * @param sample_rate 采样率 + * @param mode 模式 + * @return 0: 成功, -1: 失败 + */ +int eq_load_all_params_and_calculate_coefficients(uint32_t sample_rate, uint8_t mode) { + //DPRINTF("Loading all parameters and calculating coefficients for rate=%u, mode=%d\n", + // sample_rate, mode); + + if (mode < EQ_USER_MODE_MIN || mode > EQ_USER_MODE_MAX ) { + return -1; + } + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 获取EQ数组指针 + eq_mode_data_t (*eq_array)[NUM_EQ_MODES][NUM_EQ_CHANS] = get_eq_array_ptr(sample_rate); + if (!eq_array) { + DPRINTF(" Error: Failed to get EQ array for sample rate %u\n", sample_rate); + return -1; + } + + int loaded_count = 0; + + // 简化加载逻辑:只从44100Hz channel 0加载参数,然后应用到所有采样率和通道 + for (int band = 0; band < MAX_EQ_BANDS; band++) { + if (eq_load_single_param(44100, mode, 0, band) == 0) { + loaded_count++; + DPRINTF(" Loaded parameter for band %d from 44100Hz channel 0\n", band); + } + } + + //DPRINTF(" Loaded %d single parameters and calculated coefficients for mode %d\n", + // loaded_count, mode); + + return 0; +} + +/** + * @brief 获取单参数存储统计 + * @param stats 输出统计信息 + * @return 0: 成功, -1: 失败 + */ +int eq_get_single_param_stats(eq_single_param_database_t *stats) { + if (stats == NULL) return -1; + + *stats = single_param_database; + return 0; +} + +/** + * @brief 清空单参数存储数据库 + * @return 0: 成功, -1: 失败 + */ +int eq_clear_single_param_database(void) { + DPRINTF("Clearing single parameter database\n"); + + memset(&single_param_database, 0, sizeof(single_param_database)); + single_param_database.max_params = MAX_EQ_BANDS * 8 * 2; + + DPRINTF(" Single parameter database cleared\n"); + return 0; +} + +/** + * @brief 保存指定模式的EQ参数到Flash + * @param mode EQ模式 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_save_mode(uint8_t mode) { + DPRINTF("Saving EQ mode %d to Flash\n", mode); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: EQ Flash storage not initialized\n"); + return -1; + } + + if (mode >= NUM_EQ_MODES) { + DPRINTF(" Error: Invalid mode %d (max: %d)\n", mode, NUM_EQ_MODES - 1); + return -1; + } + + // 获取当前采样率的EQ数组指针 + eq_mode_data_t (*eq_array)[NUM_EQ_MODES][NUM_EQ_CHANS] = get_eq_array_ptr(g_eq_sample_rate); + if (!eq_array) { + DPRINTF(" Error: Failed to get EQ array for current sample rate %lu\n", (unsigned long)g_eq_sample_rate); + return -1; + } + + // 保存左右两个声道的数据 + for (int ch = 0; ch < NUM_EQ_CHANS; ch++) { + // 构建文件路径 + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/mode%d_ch%d.bin", g_eq_flash_base_dir, mode, ch); + DPRINTF(" File path (channel %d): %s\n", ch, file_path); + + // 从EQ数组读取数据 + eq_mode_data_t *eq_data = &(*eq_array)[mode][ch]; + DPRINTF(" Reading EQ data from array (ch %d): sample_rate=%u, post_gain_db=%ld\n", + ch, eq_data->sample_rate, (long)eq_data->post_gain_db); + + // 提取非默认滤波器到优化存储格式 + eq_flash_data_t flash_data; + if (extract_active_filters(eq_data, &flash_data) != 0) { + DPRINTF(" Error: Failed to extract active filters\n"); + continue; + } + + // 准备文件头 + eq_file_header_t header; + header.magic = EQ_FLASH_MAGIC; + header.version = EQ_FLASH_VERSION; + header.mode = mode; + header.channel = ch; // 使用正确的通道号 + header.reserved = 0; + header.data_size = sizeof(eq_flash_data_t); + header.crc32 = eq_flash_calculate_crc32((uint8_t*)&flash_data, sizeof(eq_flash_data_t)); + + // 创建完整的数据包(文件头 + 优化EQ数据) + uint8_t file_data[sizeof(header) + sizeof(eq_flash_data_t)]; + uint8_t file_data2[sizeof(header) + sizeof(eq_flash_data_t)]; + memcpy(file_data, &header, sizeof(header)); + memcpy(file_data + sizeof(header), &flash_data, sizeof(eq_flash_data_t)); + + DPRINTF(" +++++++++++++++ data before save (ch %d) ++++++++++++++ \n", ch); + // debug_print_eq_mode(eq_data); + DPRINTF(" +++++++++++++++ data before save (ch %d) ++++++++++++++ \n", ch); + + // 使用lfs_write_config写入文件 + lfs_write_config(file_path, file_data, sizeof(file_data)); +#if 0 + lfs_read_config(file_path, file_data2, sizeof(file_data2)); + + if( memcmp(file_data, file_data2, sizeof(file_data)) != 0) + { + DPRINTF(" *********** ** \n %s, file_data is not equal to file_data2\n", file_path); + return -1; + } + #endif + DPRINTF(" Successfully saved EQ parameters for mode %d, channel %d\n", mode, ch); + } + + return 0; +} + +/** + * @brief 标记指定模式为脏(需要同步) + * @param mode EQ模式 + */ +void eq_flash_mark_dirty(uint8_t mode) { + if ((mode >= EQ_USER_MODE_MIN) && (mode <= EQ_USER_MODE_MAX )) { + dirty_flags[mode] = 1; + DPRINTF("Marked mode %d as dirty\n", mode); + } +} + +/** + * @brief 同步指定模式参数到Flash(延迟写入) + * @param mode 模式号 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_sync_mode(uint8_t mode) { + DPRINTF("Syncing EQ mode %d to Flash\n", mode); + + if (!storage_status.is_initialized) { + DPRINTF("EQ Flash storage not initialized\n"); + return -1; + } + + if (mode >= NUM_EQ_MODES) { + DPRINTF("Invalid mode %d\n", mode); + return -1; + } + + // 保存指定模式到Flash + if (eq_flash_save_mode(mode) != 0) { + DPRINTF("Failed to save mode %d to Flash\n", mode); + return -1; + } + + // 清除脏标志 + dirty_flags[mode] = 0; + DPRINTF("EQ mode %d synced to Flash\n", mode); + return 0; +} + +/** + * @brief 同步所有脏数据到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_flash_sync_all(void) { + DPRINTF("Syncing dirty EQ parameters to Flash\n"); + + if (!storage_status.is_initialized) { + DPRINTF("EQ Flash storage not initialized\n"); + return -1; + } + + // 简化实现 + memset(dirty_flags, 0, sizeof(dirty_flags)); + DPRINTF("EQ parameters synced to Flash (simplified)\n"); + return 0; +} + +/** + * @brief 检查Flash中是否有EQ数据 + * @return 1: 有数据, 0: 无数据 + */ +int eq_flash_has_data(void) { + return storage_status.has_flash_data; +} + +/** + * @brief 获取存储状态 + * @param status 输出存储状态结构 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_get_status(eq_storage_status_t *status) { + if (!status) { + return -1; + } + *status = storage_status; + return 0; +} + +/** + * @brief 清除所有Flash数据 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_clear_all(void) { + DPRINTF("Clearing all EQ Flash data\n"); + + if (!storage_status.is_initialized) { + DPRINTF("EQ Flash storage not initialized\n"); + return -1; + } + + // 简化实现 + storage_status.has_flash_data = 0; + memset(dirty_flags, 0, sizeof(dirty_flags)); + DPRINTF("All EQ Flash data cleared\n"); + return 0; +} + +/** + * @brief 验证指定模式的完整性 + * @param mode EQ模式 + * @return 1: 有效, 0: 无效 + */ +int eq_flash_verify_mode(uint8_t mode) { + DPRINTF("Verifying EQ mode %d\n", mode); + + if (!storage_status.is_initialized) { + return 0; + } + + // 简化实现 + return 1; +} + +/** + * @brief 获取文件路径 + * @param mode EQ模式 + * @param channel 通道 + * @param file_path 输出文件路径 + * @param max_len 最大长度 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_get_file_path(uint8_t mode, uint8_t channel, char *file_path, size_t max_len) { + if (!file_path || max_len == 0) { + return -1; + } + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + snprintf(file_path, max_len, "%s/mode%d_ch%d.bin", g_eq_flash_base_dir, mode, channel); + return 0; +} + +/** + * @brief 计算CRC32校验和 + * @param data 数据指针 + * @param length 数据长度 + * @return CRC32值 + */ +uint32_t eq_flash_calculate_crc32(const uint8_t *data, size_t length) { + // 简化的CRC32计算(实际应该使用标准CRC32算法) + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < length; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc >>= 1; + } + } + } + return ~crc; +} + +/** + * @brief 检查是否有需要同步的数据 + * @return 1: 需要同步, 0: 不需要同步 + */ +int eq_flash_needs_sync(void) { + for (int j = 0; j < NUM_EQ_MODES; j++) { + if (dirty_flags[j]) { + return 1; + } + } + return 0; +} + +/** + * @brief 删除Flash中的EQ参数文件 + * @param mode 模式号 (0-9, 0xFF表示所有模式) + * @return 0: 成功, -1: 失败 + */ +int delete_flash_eq_params(uint8_t mode) { + DPRINTF("Deleting Flash EQ params for mode %d\n", mode); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + int deleted_count = 0; + + if (mode == 0xFF) { + // 删除所有模式的参数文件 + DPRINTF(" Deleting all mode parameters\n"); + for (uint8_t m = 0; m < NUM_EQ_MODES; m++) { + for (uint8_t band = 0; band < MAX_EQ_BANDS; band++) { + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/mode%d_band%d.bin", g_eq_flash_base_dir, m, band); + + if (lfs_file_exists(file_path)) { + if (lfs_remove_file(file_path) == 0) { + deleted_count++; + DPRINTF(" Deleted: %s\n", file_path); + } else { + DPRINTF(" Failed to delete: %s\n", file_path); + } + } + } + } + } else { + // 删除指定模式的参数文件 + DPRINTF(" Deleting mode %d parameters\n", mode); + for (uint8_t band = 0; band < MAX_EQ_BANDS; band++) { + // 改动原因:使用全局变量代替硬编码目录名,统一文件路径管理 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/mode%d_band%d.bin", g_eq_flash_base_dir, mode, band); + + if (lfs_file_exists(file_path)) { + if (lfs_remove_file(file_path) == 0) { + deleted_count++; + DPRINTF(" Deleted: %s\n", file_path); + } else { + DPRINTF(" Failed to delete: %s\n", file_path); + } + } + } + } + + DPRINTF(" Deleted %d parameter files\n", deleted_count); + return 0; +} + +/** + * @brief 删除Flash中的增益和模式名称文件 + * @param mode 模式号 (0-9, 0xFF表示所有模式) + * @return 0: 成功, -1: 失败 + * + * 改动原因:修改为按模式删除,只删除指定模式的增益和名称,而不是删除所有文件 + * - 如果mode == 0xFF,删除所有用户模式的增益和名称文件 + * - 如果mode是用户模式(6-8),只删除该模式的名称文件,并将该模式的增益恢复为默认值(0) + * - 如果mode是预设模式(0-5),不需要删除(预设模式的增益和名称不存储在Flash中) + */ +int delete_flash_gain_and_names(uint8_t mode) { + DPRINTF("Deleting Flash gain and names for mode %d\n", mode); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 处理删除所有模式的情况 + if (mode == 0xFF) { + DPRINTF(" Deleting all user mode gains and names\n"); + + // 删除gains文件 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s", g_eq_flash_base_dir, g_eq_flash_gains_file); + if (lfs_file_exists(file_path)) { + if (lfs_remove_file(file_path) == 0) { + DPRINTF(" Successfully deleted gains file: %s\n", file_path); + } else { + DPRINTF(" Failed to delete gains file: %s\n", file_path); + } + } + + // 删除所有用户模式的名称文件 + for (int i = EQ_USER_MODE_MIN; i <= EQ_USER_MODE_MAX; i++) { + snprintf(file_path, sizeof(file_path), "%s/%s%d", g_eq_flash_base_dir, g_eq_flash_name_prefix, i); + if (lfs_file_exists(file_path)) { + if (lfs_remove_file(file_path) == 0) { + DPRINTF(" Successfully deleted name file for mode %d: %s\n", i, file_path); + } else { + DPRINTF(" Failed to delete name file for mode %d: %s\n", i, file_path); + } + } + } + + return 0; + } + + // 检查模式有效性 + if (mode >= NUM_EQ_MODES) { + DPRINTF(" Error: Invalid mode %d\n", mode); + return -1; + } + + // 预设模式(0-5)的增益和名称不存储在Flash中,不需要删除 + if (mode < EQ_USER_MODE_MIN) { + DPRINTF(" Mode %d is a preset mode, no Flash data to delete\n", mode); + return 0; + } + + // 只处理用户模式(6-8) + if (mode >= EQ_USER_MODE_MIN && mode <= EQ_USER_MODE_MAX) { + // 删除该模式的名称文件 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s/%s%d", g_eq_flash_base_dir, g_eq_flash_name_prefix, mode); + if (lfs_file_exists(file_path)) { + if (lfs_remove_file(file_path) == 0) { + DPRINTF(" Successfully deleted name file for mode %d: %s\n", mode, file_path); + } else { + DPRINTF(" Failed to delete name file for mode %d: %s\n", mode, file_path); + } + } else { + DPRINTF(" Name file for mode %d does not exist: %s\n", mode, file_path); + } + + // 从gains文件中移除该模式的增益(恢复为默认值0) + // 改动原因:gains文件存储了所有用户模式的增益,需要读取、修改、写回 + snprintf(file_path, sizeof(file_path), "%s/%s", g_eq_flash_base_dir, g_eq_flash_gains_file); + if (lfs_file_exists(file_path)) { + // 读取gains文件 + // 改动原因:lfs_read_config返回void,不能检查返回值,需要通过验证文件头来判断读取是否成功 + eq_gain_header_t header; + eq_gain_data_t data; + uint8_t file_data[sizeof(header) + sizeof(eq_gain_data_t)]; + + // 直接调用lfs_read_config(返回void,不能检查返回值) + lfs_read_config(file_path, file_data, sizeof(file_data)); + + // 解析文件数据 + memcpy(&header, file_data, sizeof(header)); + memcpy(&data, file_data + sizeof(header), sizeof(eq_gain_data_t)); + + // 验证文件头来判断读取是否成功 + if (header.magic == 0x4551474E && header.version == EQ_FLASH_VERSION) { + // 将对应模式的增益恢复为默认值(0) + int idx = mode - EQ_USER_MODE_MIN; // 转换为数组索引 0, 1, 2 + data.mode_gains[idx] = 0; + DPRINTF(" Reset gain for mode %d to default (0)\n", mode); + + // 重新计算CRC32 + header.crc32 = eq_flash_calculate_crc32((uint8_t*)&data, sizeof(eq_gain_data_t)); + + // 写回文件 + memcpy(file_data, &header, sizeof(header)); + memcpy(file_data + sizeof(header), &data, sizeof(eq_gain_data_t)); + lfs_write_config(file_path, file_data, sizeof(file_data)); + + DPRINTF(" Successfully updated gains file for mode %d\n", mode); + } else { + DPRINTF(" Error: Invalid gains file format or read failed\n"); + } + } else { + DPRINTF(" Gains file does not exist: %s\n", file_path); + } + + return 0; + } + + // 其他模式(如模式9)不需要处理 + DPRINTF(" Mode %d does not require Flash data deletion\n", mode); + return 0; +} + +/** + * @brief 恢复指定模式在指定采样率下的默认参数 + * @param mode 模式号 + * @param sample_rate 采样率 + */ +void restore_mode_default_params(uint8_t mode, uint32_t sample_rate) { + if (mode >= NUM_EQ_MODES) return; + + // 改动原因:所有采样率共用44100Hz数组 + eq_mode_data_t (*eq_array)[NUM_EQ_MODES][NUM_EQ_CHANS] = &sEQ_data_44100HZ; + + if (eq_array == NULL) return; + + // 恢复左右通道的默认参数 + for (int ch = 0; ch < NUM_EQ_CHANS; ch++) { + eq_mode_data_t *eq_data = &(*eq_array)[mode][ch]; + + // 恢复基本参数 + eq_data->sample_rate = sample_rate; + eq_data->total_bshift = 0; + eq_data->post_gain_db = 0; + + // 恢复所有滤波器的默认参数 + for (int i = 0; i < MAX_EQ_BANDS; i++) { + filter_params_t *band = &eq_data->bands[i]; + band->index = i; + band->type = FILTER_TYPE_BYPASS; + band->fc = 1000.0f; + band->q = 0.707f; + band->bw = 1.0f; + band->gain = 0.0f; + + // 恢复默认系数 (bypass滤波器) + eq_data->filter.coef[0][i] = 1 << 30; // b0 = 1.0 in Q30 + eq_data->filter.coef[1][i] = 0; // b1 = 0 + eq_data->filter.coef[2][i] = 0; // b2 = 0 + eq_data->filter.coef[3][i] = 0; // a1 = 0 + eq_data->filter.coef[4][i] = 0; // a2 = 0 + } + + eq_data->filter.biquad_count = MAX_EQ_BANDS; + memset(eq_data->filter.state, 0, sizeof(eq_data->filter.state)); + } + + DPRINTF(" Restored mode %d for sample rate %u Hz\n", mode, sample_rate); +} +/** + * @brief 恢复默认EQ参数 + * @param mode 模式号 (0-9, 0xFF表示所有模式) + * @return 0: 成功, -1: 失败 + */ +int restore_default_eq_params(uint8_t mode) { + DPRINTF("Restoring default EQ params for mode %d\n", mode); + + // 改动原因:所有采样率共用44100Hz数组,只需要恢复44100Hz数组 + if (mode == 0xFF) { + // 恢复所有模式的默认参数 + DPRINTF(" Restoring all modes to default parameters\n"); + for (uint8_t m = 0; m < NUM_EQ_MODES; m++) { + restore_mode_default_params(m, 44100); // 恢复44100Hz数组 + } + } else { + // 恢复指定模式的默认参数 + DPRINTF(" Restoring mode %d to default parameters\n", mode); + restore_mode_default_params(mode, 44100); // 恢复44100Hz数组 + } + + DPRINTF(" Default parameters restored successfully\n"); + return 0; +} + +/** + * @brief 保存EQ使能状态到Flash(独立文件存储,与模式存储完全分离) + * @param enable EQ使能状态 (0=禁用, 1=启用) + * @return 0: 成功, -1: 失败 + * + * 改动原因:将eq_enable存储独立为单独文件,与模式存储完全分离,互不影响 + */ +int eq_flash_save_eq_enable(uint8_t enable) +{ + DPRINTF("Saving EQ enable state %d to Flash (independent file)\n", enable); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 验证使能值范围 + if (enable > 1) { + DPRINTF(" Error: Invalid EQ enable value %d (valid range: 0-1)\n", enable); + return -1; + } + + // 构建独立文件路径 + // 改动原因:使用独立的文件名存储eq_enable,与模式存储完全分离 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s", g_eq_flash_enable_file); + + // 创建文件头 + eq_file_header_t header = {0}; + header.magic = EQ_FLASH_MAGIC; + header.version = EQ_FLASH_VERSION; + header.mode = 0; // 不使用模式字段 + header.channel = 0; // 不使用通道字段 + header.data_size = 1; // 数据大小:只有使能状态(1字节) + + // 准备数据(只有使能状态) + uint8_t enable_data = enable; + + header.crc32 = eq_flash_calculate_crc32(&enable_data, 1); + + // 准备文件数据 + uint8_t file_data[sizeof(header) + 1]; + memcpy(file_data, &header, sizeof(header)); + file_data[sizeof(header)] = enable; + + // 写入文件 + lfs_write_config(file_path, file_data, sizeof(file_data)); + DPRINTF(" Successfully saved EQ enable %d to Flash (independent file: %s)\n", enable, file_path); + return 0; +} + +/** + * @brief 保存当前EQ模式到Flash(独立文件存储,与使能状态存储完全分离) + * @param mode 模式值 (0-9) + * @return 0: 成功, -1: 失败 + * + * 改动原因:将模式存储独立为单独文件,与eq_enable存储完全分离,互不影响 + */ +int eq_flash_save_current_mode(uint8_t mode) +{ + DPRINTF("Saving current EQ mode %d to Flash (independent file)\n", mode); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 验证模式值范围 + if (mode > 9) { + DPRINTF(" Error: Invalid mode %d (valid range: 0-9)\n", mode); + return -1; + } + + // 构建独立文件路径 + // 改动原因:使用独立的文件名存储模式,与eq_enable存储完全分离 + char file_path[FILE_PATH_MAX_LENGTH]; + snprintf(file_path, sizeof(file_path), "%s", g_eq_flash_mode_file); + + // 创建文件头 + eq_file_header_t header = {0}; + header.magic = EQ_FLASH_MAGIC; + header.version = EQ_FLASH_VERSION; + header.mode = mode; // 模式值 + header.channel = 0; // 不使用通道字段 + header.data_size = 1; // 数据大小:只有模式值(1字节) + + // 准备数据(只有模式值) + uint8_t mode_data = mode; + + header.crc32 = eq_flash_calculate_crc32(&mode_data, 1); + + // 准备文件数据 + uint8_t file_data[sizeof(header) + 1]; + memcpy(file_data, &header, sizeof(header)); + file_data[sizeof(header)] = mode; + + // 写入文件 + lfs_write_config(file_path, file_data, sizeof(file_data)); + DPRINTF(" Successfully saved EQ mode %d to Flash (independent file: %s)\n", mode, file_path); + return 0; +} + +/** + * @brief 从Flash加载当前EQ模式和使能状态(从两个独立文件分别加载) + * @return 模式值: 成功, -1: 失败 + * + * 改动原因:从两个独立文件分别加载模式和使能状态,完全独立,互不影响 + */ +int eq_flash_load_current_mode(void) +{ + uint8_t mode = 0; + uint8_t eq_enable = 0; + DPRINTF("Loading current EQ mode and enable state from Flash (independent files)\n"); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 加载模式(从独立文件) + char mode_file_path[FILE_PATH_MAX_LENGTH]; + snprintf(mode_file_path, sizeof(mode_file_path), "%s", g_eq_flash_mode_file); + + if (lfs_file_exists(mode_file_path)) { + eq_file_header_t header; + uint8_t file_header_data[sizeof(header) + 1]; + lfs_read_config(mode_file_path, file_header_data, sizeof(file_header_data)); + memcpy(&header, file_header_data, sizeof(header)); + + if (header.magic == EQ_FLASH_MAGIC && header.version == EQ_FLASH_VERSION) { + mode = file_header_data[sizeof(header)]; + + // 验证模式值范围 + if (mode > 9) { + DPRINTF(" Error: Invalid mode value %d in file\n", mode); + mode = 0; // 使用默认值 + } else { + // 验证CRC32 + uint8_t mode_data = mode; + uint32_t calculated_crc = eq_flash_calculate_crc32(&mode_data, 1); + if (calculated_crc == header.crc32) { + DPRINTF(" Successfully loaded mode %d from Flash\n", mode); + } else { + DPRINTF(" Error: CRC32 mismatch for mode file, using default mode 0\n"); + mode = 0; + } + } + } else { + DPRINTF(" Error: Invalid mode file header, using default mode 0\n"); + mode = 0; + } + } else { + DPRINTF(" Mode file does not exist, using default mode 0\n"); + mode = 0; + } + + // 加载使能状态(从独立文件) + char enable_file_path[FILE_PATH_MAX_LENGTH]; + snprintf(enable_file_path, sizeof(enable_file_path), "%s", g_eq_flash_enable_file); + + if (lfs_file_exists(enable_file_path)) { + eq_file_header_t header; + uint8_t file_header_data[sizeof(header) + 1]; + lfs_read_config(enable_file_path, file_header_data, sizeof(file_header_data)); + memcpy(&header, file_header_data, sizeof(header)); + + if (header.magic == EQ_FLASH_MAGIC && header.version == EQ_FLASH_VERSION) { + eq_enable = file_header_data[sizeof(header)]; + + // 验证使能值范围 + if (eq_enable > 1) { + DPRINTF(" Warning: Invalid EQ enable value %d in file, defaulting to 0\n", eq_enable); + eq_enable = 0; + } else { + // 验证CRC32 + uint8_t enable_data = eq_enable; + uint32_t calculated_crc = eq_flash_calculate_crc32(&enable_data, 1); + if (calculated_crc == header.crc32) { + DPRINTF(" Successfully loaded EQ enable %d from Flash\n", eq_enable); + } else { + DPRINTF(" Error: CRC32 mismatch for enable file, using default enable 0\n"); + eq_enable = 0; + } + } + } else { + DPRINTF(" Error: Invalid enable file header, using default enable 0\n"); + eq_enable = 0; + } + } else { + DPRINTF(" Enable file does not exist, using default enable 0\n"); + eq_enable = 0; + } + + // 设置全局变量g_eq_enable + extern unsigned int g_eq_enable; + g_eq_enable = eq_enable; + + DPRINTF(" Successfully loaded mode %d and EQ enable %d from Flash (independent files)\n", mode, eq_enable); + return mode; +} + +/** + * @brief 从Flash加载EQ使能状态(独立文件加载,与模式加载完全分离) + * @return 使能状态值: 成功(0或1), -1: 失败 + * + * 改动原因:从独立文件(eq_enable)加载eq_enable,与模式加载完全分离,互不影响 + */ +int eq_flash_load_eq_enable(void) +{ + uint8_t eq_enable = 0; + DPRINTF("Loading EQ enable state from Flash (independent file)\n"); + + if (!storage_status.is_initialized) { + DPRINTF(" Error: Flash storage not initialized\n"); + return -1; + } + + // 加载使能状态(从独立文件) + char enable_file_path[FILE_PATH_MAX_LENGTH]; + snprintf(enable_file_path, sizeof(enable_file_path), "%s", g_eq_flash_enable_file); + + if (lfs_file_exists(enable_file_path)) { + eq_file_header_t header; + uint8_t file_header_data[sizeof(header) + 1]; + lfs_read_config(enable_file_path, file_header_data, sizeof(file_header_data)); + memcpy(&header, file_header_data, sizeof(header)); + + if (header.magic == EQ_FLASH_MAGIC && header.version == EQ_FLASH_VERSION) { + eq_enable = file_header_data[sizeof(header)]; + + // 验证使能值范围 + if (eq_enable > 1) { + DPRINTF(" Warning: Invalid EQ enable value %d in file, defaulting to 0\n", eq_enable); + eq_enable = 0; + } else { + // 验证CRC32 + uint8_t enable_data = eq_enable; + uint32_t calculated_crc = eq_flash_calculate_crc32(&enable_data, 1); + if (calculated_crc == header.crc32) { + DPRINTF(" Successfully loaded EQ enable %d from Flash\n", eq_enable); + } else { + DPRINTF(" Error: CRC32 mismatch for enable file, using default enable 0\n"); + eq_enable = 0; + } + } + } else { + DPRINTF(" Error: Invalid enable file header, using default enable 0\n"); + eq_enable = 0; + } + } else { + DPRINTF(" Enable file does not exist, using default enable 0\n"); + eq_enable = 0; + } + + // 设置全局变量g_eq_enable + extern unsigned int g_eq_enable; + g_eq_enable = eq_enable; + + DPRINTF(" Successfully loaded EQ enable %d from Flash (independent file)\n", eq_enable); + return eq_enable; +} + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.h new file mode 100644 index 0000000..1d7838e --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_flash_storage.h @@ -0,0 +1,423 @@ +#ifndef __EQ_FLASH_STORAGE_H__ +#define __EQ_FLASH_STORAGE_H__ + +#include +#include +#include "eq.h" + +// EQ Flash存储相关常量定义 +#define EQ_FLASH_MAGIC 0x45515041 // "EQPA" - EQ参数文件魔数 +#define EQ_FLASH_MAGIC_PARAMS 0x45515041 // "EQPA" - EQ参数文件魔数 +#define EQ_FLASH_VERSION 0x00020000 // 版本2.0.0 - 优化存储格式 +#define EQ_FLASH_MAX_SAMPLE_RATES 6 // 支持的采样率数量 +#define EQ_FLASH_MAX_MODES 10 // 最大模式数量 +#define EQ_FLASH_MAX_CHANNELS 2 // 最大通道数量 + +// 支持的采样率列表 +#define EQ_SAMPLE_RATES {44100, 48000, 88200, 96000, 176400, 192000} + +// EQ参数文件头结构 (简化版本,去除采样率字段) +typedef struct { + uint32_t magic; // 魔数: 0x45515041 ("EQPA") + uint32_t version; // 版本号 + uint8_t mode; // 模式号 + uint8_t channel; // 通道号 + uint16_t reserved; // 保留字段 + uint32_t data_size; // 数据大小 + uint32_t crc32; // CRC32校验 +} __attribute__((packed)) eq_file_header_t; + +// 元数据结构已移除,简化存储系统 + +// EQ存储状态结构 +typedef struct { + int is_initialized; // 是否已初始化 (0: 否, 1: 是) + int has_flash_data; // Flash中是否有数据 (0: 否, 1: 是) + uint32_t last_sync_time; // 最后同步时间 + uint32_t sync_count; // 同步次数 + uint32_t error_count; // 错误次数 +} eq_storage_status_t; + +// 增益存储结构(只存储用户模式) +typedef struct { + uint32_t magic; // 魔数: 0x4551474E ("EQGN" - EQ Gain) + uint32_t version; // 版本号 + uint32_t data_size; // 数据大小 + uint32_t crc32; // CRC32校验 +} __attribute__((packed)) eq_gain_header_t; + +// 模式增益数据(只存储用户模式6-8) +typedef struct { + int32_t mode_gains[3]; // 用户模式增益数组:mode 6, 7, 8,每个4字节 +} __attribute__((packed)) eq_gain_data_t; + +// 单个模式名称存储结构 +typedef struct { + uint32_t magic; // 魔数: 0x45514E4D ("EQNM" - EQ Name) + uint32_t version; // 版本号 + uint8_t mode; // 模式号 + uint8_t reserved[3]; // 保留字段 + uint32_t data_size; // 数据大小(16字节) + uint32_t crc32; // CRC32校验 +} __attribute__((packed)) eq_name_header_t; + +// 单个模式名称数据 +typedef struct { + char mode_name[16]; // 模式名称,16字节 +} __attribute__((packed)) eq_name_data_t; + +// 兼容旧格式的结构(保留用于向后兼容) +typedef struct { + uint32_t magic; // 魔数: 0x45514E47 ("EQNG" - EQ Name Gain) + uint32_t version; // 版本号 + uint32_t data_size; // 数据大小 + uint32_t crc32; // CRC32校验 +} __attribute__((packed)) eq_gain_name_header_t; + +// 模式增益和名称数据 +typedef struct { + int32_t mode_gains[NUM_EQ_MODES]; // 模式增益数组,每个4字节 + char mode_names[NUM_EQ_MODES][16]; // 模式名称数组,每个16字节 +} __attribute__((packed)) eq_gain_name_data_t; + +// 函数声明 + +/** + * @brief 初始化EQ Flash存储系统 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_init(void); + +/** + * @brief 反初始化EQ Flash存储系统 + */ +void eq_flash_deinit(void); + +// 注意:save all 和 load all 函数已移除,使用动态单参数存储系统 + +/** + * @brief 从Flash加载指定模式的EQ参数 + * @param mode 模式号 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_load_mode(uint8_t mode); + +/** + * @brief 保存指定模式的EQ参数到Flash + * @param mode 模式号 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_save_mode(uint8_t mode); + +/** + * @brief 同步指定模式参数到Flash(延迟写入) + * @param mode 模式号 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_sync_mode(uint8_t mode); + +/** + * @brief 同步所有待更新的参数到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_flash_sync_all(void); + +/** + * @brief 检查Flash中是否存在EQ参数数据 + * @return 1: 存在, 0: 不存在 + */ +int eq_flash_has_data(void); + +/** + * @brief 获取EQ存储状态 + * @param status 状态结构指针 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_get_status(eq_storage_status_t *status); + +/** + * @brief 清除Flash中的所有EQ参数数据 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_clear_all(void); + +/** + * @brief 验证EQ参数文件完整性 + * @param mode 模式号 + * @return 1: 完整, 0: 损坏 + */ +int eq_flash_verify_mode(uint8_t mode); + +/** + * @brief 获取文件路径 + * @param mode 模式号 + * @param channel 通道号 + * @param path 输出路径缓冲区 + * @param max_len 缓冲区最大长度 + * @return 0: 成功, -1: 失败 + */ +int eq_flash_get_file_path(uint8_t mode, uint8_t channel, + char *path, size_t max_len); + +/** + * @brief 计算CRC32校验值 + * @param data 数据指针 + * @param len 数据长度 + * @return CRC32值 + */ +uint32_t eq_flash_calculate_crc32(const uint8_t *data, size_t len); + +/** + * @brief 检查是否需要同步参数到Flash + * @return 1: 需要同步, 0: 不需要 + */ +int eq_flash_needs_sync(void); + +/** + * @brief 标记参数已修改,需要同步 + * @param mode 模式号 + */ +void eq_flash_mark_dirty(uint8_t mode); + +/** + * @brief 检查滤波器是否为默认值(bypass) + * @param band 滤波器参数 + * @return 1: 是默认值, 0: 不是默认值 + */ +int is_filter_bypass(const filter_params_t *band); + +/** + * @brief 从EQ数据中提取非默认滤波器到Flash存储格式 + * @param eq_data 源EQ数据 + * @param flash_data 目标Flash存储数据 + * @return 0: 成功, -1: 失败 + */ +int extract_active_filters(const eq_mode_data_t *eq_data, eq_flash_data_t *flash_data); + +/** + * @brief 从Flash存储格式恢复EQ数据 + * @param flash_data 源Flash存储数据 + * @param eq_data 目标EQ数据 + * @return 0: 成功, -1: 失败 + */ +int restore_eq_from_flash(const eq_flash_data_t *flash_data, eq_mode_data_t *eq_data); + +/** + * @brief 实时计算滤波器系数 + * @param eq_data EQ数据 + * @param sample_rate 采样率 + * @return 0: 成功, -1: 失败 + */ +int calculate_filter_coefficients(eq_mode_data_t *eq_data, uint32_t sample_rate); + +// ==================== 单参数动态存储系统 ==================== + +/** + * @brief 初始化单参数存储数据库 + * @return 0: 成功, -1: 失败 + */ +int eq_single_param_database_init(void); + +/** + * @brief 处理0x8c/0x8d命令的单参数变化 + * @param data 接收到的数据 + * @param len 数据长度 + * @return 0: 成功, -1: 失败 + */ +int eq_process_single_param_change(uint32_t sample_rate, uint8_t mode, uint8_t channel, uint8_t band_index, filter_params_t *band); + +/** + * @brief 比较单个参数是否不同 + * @param old_params 旧参数 + * @param new_params 新参数 + * @return 1: 不同, 0: 相同 + */ +int eq_single_params_different(const filter_params_t *old_params, const filter_params_t *new_params); + +/** + * @brief 添加单个参数变化到数据库 + * @param sample_rate 采样率 + * @param mode 模式 + * @param channel 通道 + * @param band_index 滤波器索引 + * @param new_params 新参数 + * @return 0: 成功, -1: 失败 + */ +int eq_add_single_param(uint32_t sample_rate, uint8_t mode, uint8_t channel, + uint8_t band_index, const filter_params_t *new_params); + +/** + * @brief 保存单个参数到Flash + * @param sample_rate 采样率 + * @param mode 模式 + * @param channel 通道 + * @param band_index 滤波器索引 + * @return 0: 成功, -1: 失败 + */ +int eq_save_single_param(uint32_t sample_rate, uint8_t mode, uint8_t channel, uint8_t band_index); + +/** + * @brief 从Flash加载单个参数 + * @param sample_rate 采样率 + * @param mode 模式 + * @param channel 通道 + * @param band_index 滤波器索引 + * @return 0: 成功, -1: 失败 + */ +int eq_load_single_param(uint32_t sample_rate, uint8_t mode, uint8_t channel, uint8_t band_index); + +/** + * @brief 保存所有脏参数到Flash(只保存EQ参数,不包括增益和名称) + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_params(void); + +/** + * @brief 保存脏的增益到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_gain(void); + +/** + * @brief 保存脏的模式名称到Flash + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_names(void); + +/** + * @brief 保存脏的增益和模式名称到Flash(兼容旧接口) + * @return 0: 成功, -1: 失败 + */ +int eq_save_dirty_gain_and_names(void); + +/** + * @brief 保存用户模式的增益到Flash(只保存用户模式6-8) + * @return 0: 成功, -1: 失败 + */ +int eq_save_gain(void); + +/** + * @brief 保存单个用户模式的名称到Flash(只有与初始值不同且非空才保存) + * @param mode 模式号(必须是用户模式6-8) + * @return 0: 成功, -1: 失败, 1: 跳过(与初始值相同或为空) + */ +int eq_save_name(uint8_t mode); + +/** + * @brief 从Flash加载用户模式的增益 + * @return 0: 成功, -1: 失败 + */ +int eq_load_gain(void); + +/** + * @brief 从Flash加载单个用户模式的名称 + * @param mode 模式号(必须是用户模式6-8) + * @return 0: 成功, -1: 失败(文件不存在或错误) + */ +int eq_load_name(uint8_t mode); + +/** + * @brief 保存增益和模式名称到Flash(兼容旧接口,内部调用新函数) + * @return 0: 成功, -1: 失败 + */ +int eq_save_gain_and_names(void); + +/** + * @brief 从Flash加载增益和模式名称(兼容旧接口,内部调用新函数) + * @return 0: 成功, -1: 失败 + */ +int eq_load_gain_and_names(void); + +/** + * @brief 标记增益为脏(需要保存) + */ +void eq_mark_gain_dirty(void); + +/** + * @brief 标记模式名称为脏(需要保存) + */ +void eq_mark_name_dirty(void); + +/** + * @brief 标记增益和模式名称为脏(需要保存)(兼容旧接口) + */ +void eq_mark_gain_name_dirty(void); + +/** + * @brief 删除Flash中的EQ参数文件 + * @param mode 模式号 (0-9, 0xFF表示所有模式) + * @return 0: 成功, -1: 失败 + */ +int delete_flash_eq_params(uint8_t mode); + +/** + * @brief 删除Flash中的增益和模式名称文件 + * @param mode 模式号 (0-9, 0xFF表示所有模式) + * @return 0: 成功, -1: 失败 + */ +int delete_flash_gain_and_names(uint8_t mode); + +/** + * @brief 恢复默认EQ参数 + * @param mode 模式号 (0-9, 0xFF表示所有模式) + * @return 0: 成功, -1: 失败 + */ +int restore_default_eq_params(uint8_t mode); + +/** + * @brief 系统开机时加载所有参数并计算系数 + * @param sample_rate 采样率 + * @param mode 模式 + * @return 0: 成功, -1: 失败 + */ +int eq_load_all_params_and_calculate_coefficients(uint32_t sample_rate, uint8_t mode); + +/** + * @brief 获取单参数存储统计 + * @param stats 输出统计信息 + * @return 0: 成功, -1: 失败 + */ +int eq_get_single_param_stats(eq_single_param_database_t *stats); + +/** + * @brief 清空单参数存储数据库 + * @return 0: 成功, -1: 失败 + */ +int eq_clear_single_param_database(void); + +/** + * @brief 保存EQ使能状态到Flash(独立文件存储,与模式存储完全分离) + * @param enable EQ使能状态 (0=禁用, 1=启用) + * @return 0: 成功, -1: 失败 + * + * 改动原因:将eq_enable存储独立为单独文件(eq_enable),与模式存储完全分离,互不影响 + */ +int eq_flash_save_eq_enable(uint8_t enable); + +/** + * @brief 从Flash加载EQ使能状态(独立文件加载,与模式加载完全分离) + * @return 使能状态值: 成功(0或1), -1: 失败 + * + * 改动原因:从独立文件(eq_enable)加载eq_enable,与模式加载完全分离,互不影响 + */ +int eq_flash_load_eq_enable(void); + +/** + * @brief 保存当前EQ模式到Flash(独立文件存储,与使能状态存储完全分离) + * @param mode 模式值 (0-9) + * @return 0: 成功, -1: 失败 + * + * 改动原因:将模式存储独立为单独文件(cur_mode),与eq_enable存储完全分离,互不影响 + */ +int eq_flash_save_current_mode(uint8_t mode); + +/** + * @brief 从Flash加载当前EQ模式(独立文件加载,与使能状态加载完全分离) + * @return 模式值: 成功(0-9), -1: 失败 + * + * 改动原因:从独立文件(cur_mode)加载模式,与eq_enable加载完全分离,互不影响 + * 注意:此函数同时加载模式和使能状态(从两个独立文件),用于初始化 + */ +int eq_flash_load_current_mode(void); + +#endif // __EQ_FLASH_STORAGE_H__ diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_hid_protocol.md b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_hid_protocol.md new file mode 100644 index 0000000..469793f --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_hid_protocol.md @@ -0,0 +1,1003 @@ +# EQ HID通信协议详细文档 + +## 1. 总体框架 + +### 1.1 协议概述 +该协议使用HID (Human Interface Device) 协议进行主机与设备之间的通信。协议支持EQ参数设置、读取、模式切换等功能。 + +### 1.2 支持的指令列表 +| 指令码 | 命令名称 | 功能 | 方向 | 描述 | +|--------|----------|------|------|------| +| 0x8A | SET_EQ_MODE | 切换EQ模式 | 主机→设备 | 切换EQ模式 | +| 0x8B | GET_EQ_MODE | 获取当前EQ模式信息 | 主机→设备 | 读取设备当前EQ模式和名称 | +| 0x8C | SET_MODE_GAIN_AND_NAME | 设置模式整体增益和名称 | 主机→设备 | 设置模式整体增益和名称 | +| 0x8D | SET_EQ_PARAMS | 发送EQ参数 | 主机→设备 | 发送单个滤波器的参数 | +| 0x8E | GET_EQ_PARAMS | 读取EQ参数 | 主机→设备 | 读取单个滤波器的参数 | +| 0x8F | GET_DEVICE_INFO | 获取设备信息 | 主机→设备 | 读取设备PID、VID、产品字符串等信息 | +| 0x90 | RESET_EQ_PARAMS | 复位EQ参数 | 主机→设备 | 删除Flash中的EQ参数并恢复头文件预设参数 | +| 0x91 | GET_EQ_MODE_COUNT | 获取EQ模式总数 | 主机→设备 | 返回预定义加用户模式的总数(不包含禁用模式) | +| 0x92 | SET_AND_SAVE_EQ_MODE | 设置并保存EQ模式 | 主机→设备 | 设置当前EQ模式(0-9)并保存到Flash,开机时自动恢复 | +| 0x93 | SET_VOLUME | 设置音量级别 | 主机→设备 | 设置设备音量级别(0-60) | +| 0x94 | GET_VOLUME | 获取音量级别 | 主机→设备 | 读取设备当前音量级别(0-60) | +| 0x95 | GET_LED_INFO | 获取LED信息 | 主机→设备 | 读取LED索引和LED名称 | +| 0x96 | SET_LED_SWITCH | 设置LED开关 | 主机→设备 | 设置LED开关状态(ON/OFF) | +| 0x97 | GET_LED_SWITCH | 获取LED开关 | 主机→设备 | 读取LED开关状态(ON/OFF) | +| 0x98 | GET_LED_STATUS | 获取LED状态 | 主机→设备 | 读取LED RGB颜色和状态(熄灭、常亮、慢闪、快闪、呼吸) | +| 0x99 | GET_LED_COUNT | 获取LED总数 | 主机→设备 | 读取设备支持的LED总数 | +| 0x9A | GET_UAC_MODE_INFO | 获取UAC模式信息 | 主机→设备 | 读取UAC模式总数和模式名称列表 | +| 0x9B | SET_UAC_MODE | 设置UAC模式 | 主机→设备 | 设置UAC模式(UAC1.0/UAC2.0) | +| 0x9C | GET_CURRENT_UAC_MODE | 获取当前UAC模式 | 主机→设备 | 读取当前UAC模式(UAC1.0/UAC2.0) | +| 0x9D | SET_EQ_ENABLE | 设置EQ使能开关 | 主机→设备 | 设置EQ使能开关(ON/OFF),禁用时保存当前模式,启用时恢复之前模式 | +| 0x9E | GET_EQ_ENABLE | 获取EQ使能开关 | 主机→设备 | 读取EQ使能开关状态(ON/OFF) | +| 0x9F | GET_SAMPLE_FORMAT | 获取采样率和格式 | 主机→设备 | 读取当前采样率、DSD模式和DAC采样分辨率 | +| 0xA0 | SET_GAIN_MODE | 设置增益模式 | 主机→设备 | 设置增益模式(0=低阻,1=高阻) | +| 0xA1 | GET_GAIN_MODE | 获取增益模式 | 主机→设备 | 读取当前增益模式(0=低阻,1=高阻) | +| 0xA2 | SET_FILTER_MODE | 设置滤波器模式 | 主机→设备 | 设置滤波器模式(0-7:8种滤波器模式) | +| 0xA3 | GET_FILTER_MODE | 获取滤波器模式 | 主机→设备 | 读取当前滤波器模式(0-7) | +| 0xA4 | SET_GAME_MODE | 设置游戏模式 | 主机→设备 | 设置游戏模式(0=无音效,1=FPS,2=虚拟7.1) | +| 0xA5 | GET_GAME_MODE | 获取游戏模式 | 主机→设备 | 读取当前游戏模式(0-2) | +| 0xA6 | GET_FIRMWARE_VERSION | 获取固件版本 | 主机→设备 | 读取设备固件版本号(BCD格式:主版本.次版本.修订版本) | +| 0xB0 | SET_EX3D_CMD | 发送EX3D命令 | 主机→设备 | 发送EX3D设置命令(封装所有EX3D SET命令) | +| 0xB1 | GET_EX3D_CMD | 读取EX3D命令 | 主机→设备 | 读取EX3D参数(封装所有EX3D GET命令) | + +### 1.3 数据包格式 +- **Report ID**: 0x01 (HID报告ID) +- **数据包大小**: 64字节 (1字节Report ID + 63字节数据) +- **同步头**: 0x77 (所有命令的第一个字节) +- **字节序**: 小端序 (Little Endian) + +## 2. 详细指令说明 + +### 2.1 0x8A - SET_EQ_MODE (切换EQ模式) +**功能**: 切换当前EQ模式 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x8A | 命令码 +2 | 1 | uint8 | 模式值 (0-5: 预设模式, 6-8: 用户模式, 9: bypass) +3-62 | 60 | 0x00 | 保留字节 +``` + + +## 2.2 0x8B - GET_EQ_MODE (读取EQ模式信息) +**功能**: 读取EQ模式信息 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x8B | 命令码 +2 | 1 | uint8 | 模式值 (0-9: 有效模式值, 0xFF: 获取当前模式信息) +3-62 | 60 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 如果mode值为0xFF,返回当前模式值、整体增益和模式名称 +- 如果mode值为有效模式值(0-9),返回指定模式值、整体增益和模式名称 + + + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x8B | 同步头2 +3 | 1 | uint8 | 模式值 (当前模式或指定模式) +4-7 | 4 | int32 | 增益值 (范围0到-50dB,有符号整数) +8-23 | 16 | char | 模式名称 (UTF-8编码,16字节) +24-62 | 39 | 0x00 | 保留字节 +``` + +**使用说明**: +- 发送mode=0xFF时,返回当前激活的EQ模式信息 +- 发送mode=0-9时,返回指定模式的增益和名称信息(不切换当前模式) + +### 2.3 0x8C - SET_MODE_GAIN_AND_NAME (设置模式整体增益和名称) +**功能**: 设置EQ模式整体增益和名称 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x8C | 命令码 +2 | 1 | uint8 | 模式值 (0-9) +3-6 | 4 | int32 | 增益值 (范围0到-50dB,有符号整数) +7-22 | 16 | char | 模式名称 (UTF-8编码,16字节) +23-62 | 40 | 0x00 | 保留字节 +``` + +**使用场景**: +- 需要为特定模式设置整体增益补偿和自定义名称 +- 自动应用到所有采样率以确保一致性 + + +### 2.4 0x8D - SET_EQ_PARAMS (发送EQ参数) +**功能**: 发送单个滤波器的参数 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x8D | 命令码 +2 | 1 | uint8 | 模式值 (0-9) +3 | 1 | uint8 | 滤波器Band索引 (0-7) +4 | 1 | uint8 | 滤波器类型 +5-8 | 4 | float | 中心频率 (Hz) +9-12 | 4 | float | Q值 +13-16 | 4 | float | 带宽 (Hz) +17-20 | 4 | float | 增益 (dB) +21-62 | 42 | 0x00 | 保留字节 +``` + +**滤波器类型码**: +| 类型码 | 滤波器类型 | +|--------|------------| +| 0x00 | bypass滤波器 | +| 0x01 | 全通滤波器 | +| 0x02 | 峰值滤波器 | +| 0x03 | 低通滤波器 | +| 0x04 | 高通滤波器 | +| 0x05 | 带通滤波器 | +| 0x06 | 带阻滤波器 | +| 0x07 | 陷波滤波器 | +| 0x08 | 恒定Q值滤波器 | +| 0x09 | 低架滤波器 | +| 0x0A | 高架滤波器 | + +### 2.5 0x8E - GET_EQ_PARAMS (读取EQ参数) +**功能**: 读取单个滤波器的参数 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x8E | 命令码 +2 | 1 | uint8 | 模式值 (0-9) +3 | 1 | uint8 | EQ索引 (0-7) +4-62 | 59 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x8E | 同步头2 +3 | 1 | uint8 | 模式值 (0-9) +4 | 1 | uint8 | 滤波器Band索引 (0-7) +5 | 1 | uint8 | 滤波器类型 +6-9 | 4 | float | 中心频率 (Hz) +10-13 | 4 | float | Q值 +14-17 | 4 | float | 带宽 (Hz) +18-21 | 4 | float | 增益 (dB) +22-62 | 41 | 0x00 | 保留字节 +``` + +### 2.6 0x8F - GET_DEVICE_INFO (获取设备信息) +**功能**: 获取设备基本信息 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x8F | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x8F | 同步头2 +3-4 | 2 | uint16 | 产品ID (PID) +5-6 | 2 | uint16 | 厂商ID (VID) +7-22 | 16 | char | 产品字符串 (UTF-8编码,16字节) +23-38 | 16 | char | 厂商字符串 (UTF-8编码,16字节) +39-54 | 16 | char | 序列号字符串 (UTF-8编码,16字节) +55-62 | 8 | 0x00 | 保留字节 +``` + +### 2.7 0x90 - RESET_EQ_PARAMS (复位EQ参数) +**功能**: 删除客户定制EQ参数并恢复预设参数 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x90 | 命令码 +2 | 1 | uint8 | 模式号 (0-9, 0xFF表示恢复所有包括EQ参数,总体增益,模式名称 ) +3-62 | 60 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x90 | 同步头2 +3 | 1 | uint8 | 状态码 (0x00=成功, 0x01=失败) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.8 0x91 - GET_EQ_MODE_COUNT (获取EQ模式总数) +**功能**: 获取预定义加用户模式的总数(不包含禁用模式) +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x91 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x91 | 同步头2 +3 | 1 | uint8 | 模式总数 (当前为9,包含0-8共9个模式,不包含禁用模式) +4 | 1 | uint8 | 预定义模式数量 (当前为6,包含0-5共6个预设模式) +5-62 | 58 | 0x00 | 保留字节 +``` + +### 2.9 0x92 - SET_AND_SAVE_EQ_MODE (设置并保存EQ模式) +**功能**: 设置当前EQ模式并保存到Flash,开机时自动恢复 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x92 | 命令码 +2 | 1 | uint8 | 模式值 (0-9: 0-5预设模式, 6-8用户模式, 9: bypass) +3-62 | 60 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 将模式值保存到Flash +- 开机时自动从Flash读取并恢复该模式 + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x92 | 同步头2 +3 | 1 | uint8 | 状态码 (0x00=成功, 0x01=失败) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.10 0x93 - SET_VOLUME (设置音量级别) +**功能**: 设置设备音量级别 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x93 | 命令码 +2 | 1 | uint8 | 音量级别 (0-60: 0=最小音量, 60=最大音量) +3-62 | 60 | 0x00 | 保留字节 +``` + +**参数说明**: +- **音量级别范围:** 0-60(共61级) +- **说明:** 控制设备的输出音量级别 + - 0: 最小音量(约-127.5dB) + - 60: 最大音量(0dB) + - 每级约2dB的衰减变化 + +**设备端处理**: +- 参数会通过现有的定时保存机制自动保存到Flash +- 如果参数超出范围(>60),固件将拒绝设置并返回false + +**返回值**: +无直接返回值。如需确认音量是否设置成功,请使用GET_VOLUME命令读取当前音量。 + +### 2.11 0x94 - GET_VOLUME (获取音量级别) +**功能**: 读取设备当前音量级别 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x94 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x94 | 同步头2 +3 | 1 | uint8 | 当前音量级别 (0-60) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.12 0x95 - GET_LED_INFO (获取LED信息) +**功能**: 读取LED索引和LED名称 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x95 | 命令码 +2 | 1 | uint8 | LED索引 (0-7,当前设备只有1个LED,索引为0) +3-62 | 60 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x95 | 同步头2 +3 | 1 | uint8 | LED索引 (0-7) +4-19 | 16 | char | LED名称 (UTF-8编码,16字节) +20-62 | 43 | 0x00 | 保留字节 +``` + +### 2.13 0x96 - SET_LED_SWITCH (设置LED开关) +**功能**: 设置LED开关状态 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x96 | 命令码 +2 | 1 | uint8 | LED索引 (0-7,当前设备只有1个LED,索引为0) +3 | 1 | uint8 | LED开关 (0=OFF关闭, 1=ON开启) +4-62 | 59 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 当LED开关为OFF时,所有LED输出都被禁用,不显示任何LED +- 当LED开关为ON时,LED正常显示 + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x96 | 同步头2 +3 | 1 | uint8 | 状态码 (0x00=成功, 0x01=失败) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.14 0x97 - GET_LED_SWITCH (获取LED开关) +**功能**: 读取LED开关状态 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x97 | 命令码 +2 | 1 | uint8 | LED索引 (0-7,当前设备只有1个LED,索引为0) +3-62 | 60 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x97 | 同步头2 +3 | 1 | uint8 | LED索引 (0-7) +4 | 1 | uint8 | LED开关 (0=OFF关闭, 1=ON开启) +5-62 | 58 | 0x00 | 保留字节 +``` + +### 2.15 0x98 - GET_LED_STATUS (获取LED状态) +**功能**: 读取LED RGB颜色和状态 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x98 | 命令码 +2 | 1 | uint8 | LED索引 (0-7,当前设备只有1个LED,索引为0) +3-62 | 60 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x98 | 同步头2 +3 | 1 | uint8 | LED索引 (0-7) +4 | 1 | uint8 | R颜色值 (0-255) +5 | 1 | uint8 | G颜色值 (0-255) +6 | 1 | uint8 | B颜色值 (0-255) +7 | 1 | uint8 | LED状态 (0=熄灭, 1=常亮, 2=慢闪, 3=快闪, 4=呼吸) +8-62 | 55 | 0x00 | 保留字节 +``` + +**LED状态枚举**: +- 0: LED_STATUS_OFF (熄灭) +- 1: LED_STATUS_SOLID (常亮) +- 2: LED_STATUS_SLOW_BLINK (慢闪) +- 3: LED_STATUS_FAST_BLINK (快闪) +- 4: LED_STATUS_BREATHE (呼吸) + +**注意**: +- RGB颜色值基于当前LED颜色定义转换(低电平有效) +- LED状态基于当前LED显示模式(常亮/闪烁)判断 + +### 2.16 0x99 - GET_LED_COUNT (获取LED总数) +**功能**: 读取设备支持的LED总数 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x99 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x99 | 同步头2 +3 | 1 | uint8 | LED总数 (当前设备为1) +4-62 | 59 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 返回设备支持的LED总数 +- 当前设备只有1个LED,所以返回1 +- LED索引范围为0到(LED总数-1) + +### 2.17 0x9A - GET_UAC_MODE_INFO (获取UAC模式信息) +**功能**: 读取UAC模式总数和模式名称列表 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x9A | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x9A | 同步头2 +3 | 1 | uint8 | UAC模式总数 (当前为2,包含UAC1.0和UAC2.0) +4-11 | 8 | char | 模式0名称 (UTF-8编码,8字节,如"UAC2.0") +12-19 | 8 | char | 模式1名称 (UTF-8编码,8字节,如"UAC1.0") +20-62 | 43 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 返回UAC模式总数(当前为2) +- 返回每个模式的名称(不超过8个字符) +- 模式0对应UAC2.0,模式1对应UAC1.0 + +### 2.18 0x9B - SET_UAC_MODE (设置UAC模式) +**功能**: 设置UAC模式(UAC1.0或UAC2.0) +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x9B | 命令码 +2 | 1 | uint8 | UAC模式值 (0=UAC2.0, 1=UAC1.0) +3-62 | 60 | 0x00 | 保留字节 +``` + +**设备端处理**: + - 注意:设备重启后不会返回响应 + +### 2.19 0x9C - GET_CURRENT_UAC_MODE (获取当前UAC模式) +**功能**: 读取当前UAC模式 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x9C | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x9C | 同步头2 +3 | 1 | uint8 | 当前UAC模式值 (0=UAC2.0, 1=UAC1.0) +4-11 | 8 | char | 当前UAC模式名称 (UTF-8编码,8字节,如"UAC2.0"或"UAC1.0") +12-62 | 51 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 返回当前UAC模式名称(不超过8个字符) + +### 2.20 0x9D - SET_EQ_ENABLE (设置EQ使能开关) +**功能**: 设置EQ使能开关(启用/禁用) +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x9D | 命令码 +2 | 1 | uint8 | EQ使能开关 (0=OFF禁用, 1=ON启用) +3-62 | 60 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 只保存EQ使能状态到Flash(独立文件存储),不影响已保存的模式 + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x9D | 同步头2 +3 | 1 | uint8 | 状态码 (0x00=成功, 0x01=失败) +4 | 1 | uint8 | 当前EQ使能状态 (0=OFF, 1=ON) +5-62 | 58 | 0x00 | 保留字节 +``` + +### 2.21 0x9E - GET_EQ_ENABLE (获取EQ使能开关) +**功能**: 读取EQ使能开关状态 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x9E | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x9E | 同步头2 +3 | 1 | uint8 | EQ使能开关状态 (0=OFF禁用, 1=ON启用) +4-62 | 59 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 返回当前EQ使能开关状态(0=OFF, 1=ON) + + +### 2.22 0x9F - GET_SAMPLE_FORMAT (获取采样率和格式) +**功能**: 读取当前采样率和DSD模式 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0x9F | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x9F | 同步头2 +3-6 | 4 | uint32 | 采样率 (samFreq,小端序,单位:Hz) +7 | 1 | uint8 | DSD模式 (dsdMode: 0=PCM, 1=DOP, 2=Native DSD) +8-62 | 55 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 返回当前采样率(32位无符号整数,小端序,单位:Hz) +- 返回DSD模式(0表示PCM格式,>0表示DSD格式) +- 设备会在采样率或DSD模式发生变化时自动上报此信息(通过HID状态报告) + +**自动上报机制**: +- 设备中监控采样率和DSD模式的变化 +- 当这些值发生变化时,设备会自动构建0x9F响应数据包并通过HID状态报告上报 +- 主机可以通过HID GET_REPORT接收这些自动上报的状态变化 + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0x9E | 同步头2 +3 | 1 | uint8 | EQ使能开关状态 (0=OFF禁用, 1=ON启用) +4 | 1 | uint8 | 保存的模式值 (0-9,如果之前有保存,0xFF表示未保存) +5-62 | 58 | 0x00 | 保留字节 +``` + +**设备端处理**: +- 返回当前EQ使能开关状态 +- 返回之前保存的模式值(如果存在) + +### 2.23 0xA0 - SET_GAIN_MODE (设置增益模式) +**功能**: 设置增益模式 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA0 | 命令码 +2 | 1 | uint8 | 增益模式值 (0=低阻, 1=高阻) +3-62 | 60 | 0x00 | 保留字节 +``` + +**参数说明**: +- **增益模式范围:** 0-1 +- **说明:** 控制设备的增益档位 + - 0: 低阻模式(适合高灵敏度耳机) + - 1: 高阻模式(适合高阻抗耳机) + +**设备端处理**: +- 参数会通过现有的定时保存机制自动保存到Flash + +**返回值**: +无直接返回值。如需确认增益模式是否设置成功,请使用GET_GAIN_MODE命令读取当前增益模式。 + +### 2.24 0xA1 - GET_GAIN_MODE (获取增益模式) +**功能**: 读取设备当前增益模式 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA1 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0xA1 | 同步头2 +3 | 1 | uint8 | 当前增益模式 (0=低阻, 1=高阻) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.25 0xA2 - SET_FILTER_MODE (设置滤波器模式) +**功能**: 设置滤波器模式 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA2 | 命令码 +2 | 1 | uint8 | 滤波器模式值 (0-7: 8种滤波器模式) +3-62 | 60 | 0x00 | 保留字节 +``` + +**参数说明**: +- **滤波器模式范围:** 0-7(共8种模式) +- **说明:** 选择DAC的数字滤波器类型 + - 0: Minimum Phase filter (default) + - 1: Linear Phase Apodizing Fast Roll-off filter + - 2: Linear phase fast roll-off filter + - 3: Linear Phase Fast Roll-off low-ripple filter + - 4: Linear phase slow roll-off filter + - 5: Minimum phase fast roll-off filter + - 6: Minimum phase slow roll-off filter + - 7: Minimum Phase Fast Roll-Off Low Dispersion + +**设备端处理**: +- 参数会通过现有的定时保存机制自动保存到Flash +- 如果参数超出范围(>7),固件将拒绝设置并返回false + +**返回值**: +无直接返回值。如需确认滤波器模式是否设置成功,请使用GET_FILTER_MODE命令读取当前滤波器模式。 + +### 2.26 0xA3 - GET_FILTER_MODE (获取滤波器模式) +**功能**: 读取设备当前滤波器模式 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA3 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0xA3 | 同步头2 +3 | 1 | uint8 | 当前滤波器模式 (0-7) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.27 0xA4 - SET_GAME_MODE (设置游戏模式) +**功能**: 设置游戏模式 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA4 | 命令码 +2 | 1 | uint8 | 游戏模式值 (0=无音效, 1=FPS, 2=虚拟7.1) +3-62 | 60 | 0x00 | 保留字节 +``` + +**参数说明**: +- **游戏模式范围:** 0-2 +- **说明:** 选择游戏音效模式 + - 0: 无音效(标准立体声) + - 1: FPS模式(增强定位感) + - 2: 虚拟7.1(环绕声效果) + +**设备端处理**: +- 参数会通过现有的定时保存机制自动保存到Flash +- 如果参数超出范围(>2),固件将拒绝设置并返回false + +**返回值**: +无直接返回值。如需确认游戏模式是否设置成功,请使用GET_GAME_MODE命令读取当前游戏模式。 + +### 2.28 0xA5 - GET_GAME_MODE (获取游戏模式) +**功能**: 读取设备当前游戏模式 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA5 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0xA5 | 同步头2 +3 | 1 | uint8 | 当前游戏模式 (0=无音效, 1=FPS, 2=虚拟7.1) +4-62 | 59 | 0x00 | 保留字节 +``` + +### 2.29 0xA6 - GET_FIRMWARE_VERSION (获取固件版本) +**功能**: 读取设备固件版本号 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xA6 | 命令码 +2-62 | 61 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0xA6 | 同步头2 +3 | 1 | uint8 | 主版本号 (BCD格式,如0x01表示1) +4 | 1 | uint8 | 次版本号 (BCD格式,如0x00表示0) +5 | 1 | uint8 | 修订版本号 (BCD格式,如0x0C表示12) +6-62 | 57 | 0x00 | 保留字节 +``` + +**版本号格式说明**: +- 版本号采用BCD(Binary Coded Decimal)格式 +- 3个字节分别表示:主版本号、次版本号、修订版本号 +- 例如:0x01 0x00 0x0C 表示版本 1.0.12 + +### 2.30 0xB0 - SET_EX3D_CMD (发送EX3D命令) +**功能**: 发送EX3D设置命令,封装所有EX3D SET命令 +**方向**: 主机→设备 +**数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xB0 | 命令码 +2-5 | 4 | uint32 | EX3D命令码(包含SET标志,参考ex3d_protocol.md) +6-9 | 4 | uint32 | 参数1(根据EX3D命令不同而不同) +10-13 | 4 | uint32 | 参数2(根据EX3D命令不同而不同) +14-17 | 4 | uint32 | 参数3(根据EX3D命令不同而不同) +18-62 | 45 | 0x00 | 保留字节或扩展参数 +``` + +**EX3D命令码说明**: +- 基础命令码范围: 0x80-0x94 +- SET命令格式: `基础命令码 & ~0x0100`(清除第9位) +- 支持的SET命令: + - 0x81: CMD_OPEN(打开EX3D) + - 0x82: CMD_ONOFF(设置EX3D开关) + - 0x83: CMD_ANGLE(设置角度) + - 0x84: CMD_SOUND_FIELD(设置声场模式) + - 0x85: CMD_MUTE(设置静音) + - 0x86: CMD_ON_GAIN(设置开启增益) + - 0x87: CMD_LMT_THRESHOLD(设置限制器阈值) + - 0x88: CMD_UPMIX(设置上混) + - 0x89: CMD_LFE(设置LFE增益) + - 0x8A: CMD_OFF_GAIN(设置关闭增益) + - 0x8E: CMD_LMT_ATTACKK_TIME(设置限制器攻击时间) + - 0x8F: CMD_LMT_RELEASE_TIME(设置限制器释放时间) + - 0x90: CMD_TEST_CANCEL(取消测试) + - 0x91: CMD_TEST_STEP(测试步骤) + - 0x92: CMD_TEST_ROTATE(测试旋转) + - 0x93: CMD_EXPAND_GAIN(设置扩展增益) + - 0x94: CMD_REDUCE_GAIN(设置减少增益) + +**设备端处理**: +- 解析EX3D命令码,提取基础命令码和参数 +- 调用相应的EX3D处理函数(hid_receive_task_in_c) +- 根据命令类型执行相应的设置操作 + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0xB0 | 同步头2 +3 | 1 | uint8 | 状态码 (0x00=成功, 0x01=失败) +4-7 | 4 | uint32 | EX3D命令码(回显) +8-11 | 4 | uint32 | 返回值(某些命令返回0xFFFFFFFF表示参数错误) +12-62 | 51 | 0x00 | 保留字节 +``` + +**使用说明**: +- 该命令用于统一封装所有EX3D设置命令 +- 参数格式与原始EX3D协议保持一致 +- 详细的EX3D命令说明请参考ex3d_protocol.md文档 + +### 2.31 0xB1 - GET_EX3D_CMD (读取EX3D命令) +**功能**: 读取EX3D参数,封装所有EX3D GET命令 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x77 | 同步头1 +1 | 1 | 0xB1 | 命令码 +2-5 | 4 | uint32 | EX3D命令码(包含GET标志,参考ex3d_protocol.md) +6-9 | 4 | uint32 | 参数1(根据EX3D命令不同而不同,如通道号、索引等) +10-62 | 53 | 0x00 | 保留字节或扩展参数 +``` + +**EX3D命令码说明**: +- 基础命令码范围: 0x80-0x94 +- GET命令格式: `基础命令码 | 0x0100`(设置第9位) +- 支持的GET命令: + - 0x180: CMD_CH_NUM(获取通道数量) + - 0x182: CMD_ONOFF(获取EX3D开关) + - 0x183: CMD_ANGLE(获取角度) + - 0x184: CMD_SOUND_FIELD(获取声场模式) + - 0x185: CMD_MUTE(获取静音) + - 0x186: CMD_ON_GAIN(获取开启增益) + - 0x187: CMD_LMT_THRESHOLD(获取限制器阈值) + - 0x188: CMD_UPMIX(获取上混) + - 0x189: CMD_LFE(获取LFE增益) + - 0x18A: CMD_OFF_GAIN(获取关闭增益) + - 0x18B: CMD_SOUND_FIELD_NUM(获取声场模式数量) + - 0x18C: CMD_SOUND_FIELD_NAME(获取声场模式名称) + - 0x18D: CMD_LEVEL(获取电平) + - 0x18E: CMD_LMT_ATTACKK_TIME(获取限制器攻击时间) + - 0x18F: CMD_LMT_RELEASE_TIME(获取限制器释放时间) + - 0x193: CMD_EXPAND_GAIN(获取扩展增益) + - 0x194: CMD_REDUCE_GAIN(获取减少增益) + +**设备端处理**: +- 解析EX3D命令码,提取基础命令码和参数 +- 调用相应的EX3D处理函数(hid_receive_task_in_c) +- 返回当前参数值 + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1 | 1 | 0x77 | 同步头1 +2 | 1 | 0xB1 | 同步头2 +3-6 | 4 | uint32 | EX3D命令码(回显) +7-10 | 4 | uint32 | 返回值1(根据命令不同而不同) +11-14 | 4 | uint32 | 返回值2(某些命令需要多个返回值,如角度、多通道数据等) +15-18 | 4 | uint32 | 返回值3(扩展返回值,如多通道数据) +... | ... | ... | 其他返回值(最多可返回14个uint32值,总长度不超过63字节) +``` + +**返回值说明**: +- 返回值数量和格式取决于具体的EX3D命令 +- 单值命令:返回1个uint32值 +- 多值命令(如角度、多通道数据):返回多个uint32值 +- 字符串命令(如声场名称):先返回长度(uint32),然后返回字符串数据 +- 详细的返回值格式请参考ex3d_protocol.md文档 + +**使用说明**: +- 该命令用于统一封装所有EX3D读取命令 +- 参数格式与原始EX3D协议保持一致 +- 详细的EX3D命令说明请参考ex3d_protocol.md文档 + + +## 5. 关键特性 + +### 5.1 设备端系数计算 +- 主机只发送参数 (fc, Q, bw, gain) +- 设备端根据参数实时计算滤波器系数 + +### 5.2 参数格式 +- **浮点参数**: fc, Q, bw, gain 使用IEEE 754 float格式传输 +- **字节序**: 所有多字节数据使用小端序 + +### 5.3 错误处理 +- 数据包长度检查 +- 同步头验证 +- 参数范围检查 +- 滤波器索引边界检查 + +### 5.4 模式管理 +- 支持10个EQ模式 (0-9: 0-5预设模式, 6-8用户模式, 9: bypass) +- 支持禁用EQ模式 (10) +- 模式切换时自动清除滤波器状态 + +## 7. 注意事项 + +1. **时序要求**: 发送参数后需要等待设备处理完成再发送下一条命令 +2. **数据完整性**: 所有8个滤波器的参数都需要发送,即使某些滤波器未使用 +3. **模式同步**: 设置模式后需要调用读取模式确保数据同步 +4. **错误恢复**: 通信失败时需要重新建立连接并重试 +5. **参数验证**: 设备端会验证参数范围,超出范围的值会被限制 + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_params_44100.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_params_44100.h new file mode 100644 index 0000000..bec730f --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/eq_params_44100.h @@ -0,0 +1,1648 @@ +// EQ参数头文件 - 44100Hz +// 自动生成,请勿手动修改 + +#include "eq.h" + +// Directly declare sEQ_data_44100HZ, including coefficients +eq_mode_data_t sEQ_data_44100HZ[NUM_EQ_MODES][NUM_EQ_CHANS] = { + [0][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -6, // -6 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 32.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 64.00f, + .gain = 4.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 2.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40ea71da, 0x41c96a66, 0x436723df, 0x40000000, 0x449012f8, 0x4a4d3a1a, 0x4d0a569a}, + {-0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14, -0x209c225f, 0x364b4fdd}, + {0x3e6a239c, 0x3bf3e36e, 0x381a7c14, 0x30f60f23, 0x2ad90a30, 0x18317347, 0x03c161bb, 0x065e7484}, + {0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14, 0x209c225f, -0x364b4fdd}, + {-0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f, -0x0e0e9bd5, -0x1368cb1f}, + }, + } + }, + [0][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -6, // -6 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 32.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 64.00f, + .gain = 4.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 2.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40ea71da, 0x41c96a66, 0x436723df, 0x40000000, 0x449012f8, 0x4a4d3a1a, 0x4d0a569a}, + {-0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14, -0x209c225f, 0x364b4fdd}, + {0x3e6a239c, 0x3bf3e36e, 0x381a7c14, 0x30f60f23, 0x2ad90a30, 0x18317347, 0x03c161bb, 0x065e7484}, + {0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14, 0x209c225f, -0x364b4fdd}, + {-0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f, -0x0e0e9bd5, -0x1368cb1f}, + }, + } + }, + [1][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -3, // -3 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 32.00f, + .gain = 3.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 64.00f, + .gain = 1.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -1.50f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -3.50f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.50f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.50f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 2.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x4015a1bd, 0x400cc15e, 0x3fb4c9f2, 0x3e84e60a, 0x42e317f3, 0x41fe5db4, 0x430c06de, 0x45c5d7f1}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7c37687b, -0x76c49365, -0x732f5c01, -0x668a08e1, -0x209c225f, 0x364b4fdd}, + {0x3f817f0c, 0x3f222c0f, 0x3c96cd54, 0x388cfd67, 0x317a1b0f, 0x28daac7c, 0x0b0294f6, 0x0da2f32e}, + {0x7f96c9e2, 0x7f2d92eb, 0x7c37687b, 0x76c49365, 0x732f5c01, 0x668a08e1, 0x209c225f, -0x364b4fdd}, + {-0x3f9720ca, -0x3f2eed6e, -0x3c4b9747, -0x3711e371, -0x345d3303, -0x2ad90a30, -0x0e0e9bd5, -0x1368cb1f}, + }, + } + }, + [1][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -3, // -3 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 32.00f, + .gain = 3.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 64.00f, + .gain = 1.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -1.50f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -3.50f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.50f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.50f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 2.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x4015a1bd, 0x400cc15e, 0x3fb4c9f2, 0x3e84e60a, 0x42e317f3, 0x41fe5db4, 0x430c06de, 0x45c5d7f1}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7c37687b, -0x76c49365, -0x732f5c01, -0x668a08e1, -0x209c225f, 0x364b4fdd}, + {0x3f817f0c, 0x3f222c0f, 0x3c96cd54, 0x388cfd67, 0x317a1b0f, 0x28daac7c, 0x0b0294f6, 0x0da2f32e}, + {0x7f96c9e2, 0x7f2d92eb, 0x7c37687b, 0x76c49365, 0x732f5c01, 0x668a08e1, 0x209c225f, -0x364b4fdd}, + {-0x3f9720ca, -0x3f2eed6e, -0x3c4b9747, -0x3711e371, -0x345d3303, -0x2ad90a30, -0x0e0e9bd5, -0x1368cb1f}, + }, + } + }, + [2][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -5, // -5 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 32.00f, + .gain = 1.50f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 64.00f, + .gain = 2.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -1.50f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.50f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x4009e261, 0x40348b39, 0x40ea71da, 0x3f6de57e, 0x4118c15a, 0x3fffffff, 0x4e9b0b4d, 0x4d0a569a}, + {-0x7f96c9e2, -0x7e6501c8, -0x7cca0ea9, -0x787f33eb, -0x732f5c01, -0x4e189e14, -0x209c225f, 0x364b4fdd}, + {0x3f8d3e69, 0x3e359862, 0x3bf3e36e, 0x395fbe9b, 0x334471a9, 0x1cc1863f, -0x008c6f78, 0x065e7484}, + {0x7f96c9e2, 0x7e6501c8, 0x7cca0ea9, 0x787f33eb, 0x732f5c01, 0x4e189e14, 0x209c225f, -0x364b4fdd}, + {-0x3f9720ca, -0x3e6a239c, -0x3cde5548, -0x38cda419, -0x345d3303, -0x1cc1863f, -0x0e0e9bd5, -0x1368cb1f}, + }, + } + }, + [2][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -5, // -5 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 32.00f, + .gain = 1.50f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 64.00f, + .gain = 2.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -1.50f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.50f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x4009e261, 0x40348b39, 0x40ea71da, 0x3f6de57e, 0x4118c15a, 0x3fffffff, 0x4e9b0b4d, 0x4d0a569a}, + {-0x7f96c9e2, -0x7e6501c8, -0x7cca0ea9, -0x787f33eb, -0x732f5c01, -0x4e189e14, -0x209c225f, 0x364b4fdd}, + {0x3f8d3e69, 0x3e359862, 0x3bf3e36e, 0x395fbe9b, 0x334471a9, 0x1cc1863f, -0x008c6f78, 0x065e7484}, + {0x7f96c9e2, 0x7e6501c8, 0x7cca0ea9, 0x787f33eb, 0x732f5c01, 0x4e189e14, 0x209c225f, -0x364b4fdd}, + {-0x3f9720ca, -0x3e6a239c, -0x3cde5548, -0x38cda419, -0x345d3303, -0x1cc1863f, -0x0e0e9bd5, -0x1368cb1f}, + }, + } + }, + [3][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -11, // -11 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 7.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 7.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 2.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -2.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40817dc4, 0x40fb5fcd, 0x40ea71da, 0x40000000, 0x3fffffff, 0x46773b39, 0x3ab46df2}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x4e189e14, -0x209c225f, 0x31cd5177}, + {0x3f9720ca, 0x3ead6fa9, 0x3d6ec3ce, 0x3bf3e36e, 0x39e3e67a, 0x1cc1863f, 0x0797609c, 0x11cdb028}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x4e189e14, 0x209c225f, -0x31cd5177}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x1cc1863f, -0x0e0e9bd5, -0x0c821e1b}, + }, + } + }, + [3][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -11, // -11 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 7.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 7.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 2.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = -2.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40817dc4, 0x40fb5fcd, 0x40ea71da, 0x40000000, 0x3fffffff, 0x46773b39, 0x3ab46df2}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x4e189e14, -0x209c225f, 0x31cd5177}, + {0x3f9720ca, 0x3ead6fa9, 0x3d6ec3ce, 0x3bf3e36e, 0x39e3e67a, 0x1cc1863f, 0x0797609c, 0x11cdb028}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x4e189e14, 0x209c225f, -0x31cd5177}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x1cc1863f, -0x0e0e9bd5, -0x0c821e1b}, + }, + } + }, + [4][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -10, // -10 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.80f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 6.30f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.80f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.70f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.20f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.70f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.30f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.20f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x401cc718, 0x406f5ef0, 0x4095b8e5, 0x40a91177, 0x4297702b, 0x4797b900, 0x4b49da50, 0x4b1f8f95}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14, -0x209c225f}, + {0x3f7a59b1, 0x3ebf8e7d, 0x3dd46ab6, 0x393ad503, 0x31c5c2d8, 0x2341512f, 0x1177abee, 0x02ef0c40}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14, 0x209c225f}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f, -0x0e0e9bd5}, + }, + } + }, + [4][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = -10, // -10 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.80f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 6.30f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.80f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 1.70f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.20f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.70f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 4.30f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 3.20f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x401cc718, 0x406f5ef0, 0x4095b8e5, 0x40a91177, 0x4297702b, 0x4797b900, 0x4b49da50, 0x4b1f8f95}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14, -0x209c225f}, + {0x3f7a59b1, 0x3ebf8e7d, 0x3dd46ab6, 0x393ad503, 0x31c5c2d8, 0x2341512f, 0x1177abee, 0x02ef0c40}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14, 0x209c225f}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f, -0x0e0e9bd5}, + }, + } + }, + [5][0] = { + .sample_rate = 44100, + .total_bshift = 1, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_LOWSHELF, + .fc = 64.00f, + .q = 1.2000f, + .bw = 53.00f, + .gain = 6.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 4.5000f, + .bw = 27.00f, + .gain = 4.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 6.7200f, + .bw = 37.00f, + .gain = 2.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7200f, + .bw = 1388.00f, + .gain = -3.50f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 2.5000f, + .bw = 800.00f, + .gain = -1.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 1.5000f, + .bw = 2666.00f, + .gain = 2.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_HIGHSHELF, + .fc = 8000.00f, + .q = 1.1500f, + .bw = 6956.00f, + .gain = 10.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.4500f, + .bw = 35555.00f, + .gain = 9.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x402ba321, 0x4012ecaa, 0x400b35f5, 0x3d455e1b, 0x3f9645d9, 0x4286aa03, 0x479b029d, 0x753f706d}, + {-0x7f2c0ce7, -0x7fba1a56, -0x7f94ad0b, -0x6e69ac2c, -0x738c9f3f, -0x5b57939c, -0x4c01bc94, 0x2d3271dc}, + {0x3f031d23, 0x3fac5d59, 0x3f9e31d1, 0x3245a3aa, 0x38d14d8b, 0x29f65a1f, 0x1b16b101, -0x2fd05d25}, + {0x7f2cb956, 0x7fba1a56, 0x7f94ad0b, 0x6e69ac2c, 0x738c9f3f, 0x5b57939c, 0x208ed3f6, -0x2d3271dc}, + {-0x3f2e13d7, -0x3fbf4a04, -0x3fa967c7, -0x2f8b01c6, -0x38679364, -0x2c7d0423, -0x0deec20a, -0x056f1347}, + }, + } + }, + [5][1] = { + .sample_rate = 44100, + .total_bshift = 1, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_LOWSHELF, + .fc = 64.00f, + .q = 1.2000f, + .bw = 53.00f, + .gain = 6.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 4.5000f, + .bw = 27.00f, + .gain = 4.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 6.7200f, + .bw = 37.00f, + .gain = 2.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7200f, + .bw = 1388.00f, + .gain = -3.50f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 2.5000f, + .bw = 800.00f, + .gain = -1.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 1.5000f, + .bw = 2666.00f, + .gain = 2.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_HIGHSHELF, + .fc = 8000.00f, + .q = 1.1500f, + .bw = 6956.00f, + .gain = 10.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.4500f, + .bw = 35555.00f, + .gain = 9.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x402ba321, 0x4012ecaa, 0x400b35f5, 0x3d455e1b, 0x3f9645d9, 0x4286aa03, 0x479b029d, 0x753f706d}, + {-0x7f2c0ce7, -0x7fba1a56, -0x7f94ad0b, -0x6e69ac2c, -0x738c9f3f, -0x5b57939c, -0x4c01bc94, 0x2d3271dc}, + {0x3f031d23, 0x3fac5d59, 0x3f9e31d1, 0x3245a3aa, 0x38d14d8b, 0x29f65a1f, 0x1b16b101, -0x2fd05d25}, + {0x7f2cb956, 0x7fba1a56, 0x7f94ad0b, 0x6e69ac2c, 0x738c9f3f, 0x5b57939c, 0x208ed3f6, -0x2d3271dc}, + {-0x3f2e13d7, -0x3fbf4a04, -0x3fa967c7, -0x2f8b01c6, -0x38679364, -0x2c7d0423, -0x0deec20a, -0x056f1347}, + }, + } + }, + [6][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7000f, + .bw = 45.00f, + .gain = -2.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 176.00f, + .gain = -1.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 707.00f, + .gain = 1.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 3.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 1.0000f, + .bw = 2000.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 5633.00f, + .gain = 4.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 11267.00f, + .gain = 2.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 22535.00f, + .gain = -2.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x3ff23dc1, 0x3fe746ae, 0x405f6cac, 0x42666efe, 0x40000000, 0x4a4e979f, 0x46773b39, 0x3ab46df2}, + {-0x7f79de0d, -0x7e342deb, -0x7994c1a0, -0x732f5c01, -0x6bb3556c, -0x4e189e14, -0x209c225f, 0x31cd5177}, + {0x3f87f720, 0x3e520715, 0x398479ce, 0x31f6c405, 0x3039dbd4, 0x1272eea0, 0x0797609c, 0x11cdb028}, + {0x7f79de0d, 0x7e342deb, 0x7994c1a0, 0x732f5c01, 0x6bb3556c, 0x4e189e14, 0x209c225f, -0x31cd5177}, + {-0x3f7a34e2, -0x3e394dc3, -0x39e3e67a, -0x345d3303, -0x3039dbd4, -0x1cc1863f, -0x0e0e9bd5, -0x0c821e1b}, + }, + } + }, + [6][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7000f, + .bw = 45.00f, + .gain = -2.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 176.00f, + .gain = -1.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 707.00f, + .gain = 1.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 3.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 1.0000f, + .bw = 2000.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 5633.00f, + .gain = 4.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 8000.00f, + .q = 0.7100f, + .bw = 11267.00f, + .gain = 2.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 16000.00f, + .q = 0.7100f, + .bw = 22535.00f, + .gain = -2.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x3ff23dc1, 0x3fe746ae, 0x405f6cac, 0x42666efe, 0x40000000, 0x4a4e979f, 0x46773b39, 0x3ab46df2}, + {-0x7f79de0d, -0x7e342deb, -0x7994c1a0, -0x732f5c01, -0x6bb3556c, -0x4e189e14, -0x209c225f, 0x31cd5177}, + {0x3f87f720, 0x3e520715, 0x398479ce, 0x31f6c405, 0x3039dbd4, 0x1272eea0, 0x0797609c, 0x11cdb028}, + {0x7f79de0d, 0x7e342deb, 0x7994c1a0, 0x732f5c01, 0x6bb3556c, 0x4e189e14, 0x209c225f, -0x31cd5177}, + {-0x3f7a34e2, -0x3e394dc3, -0x39e3e67a, -0x345d3303, -0x3039dbd4, -0x1cc1863f, -0x0e0e9bd5, -0x0c821e1b}, + }, + } + }, + [7][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 0.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x3fffffff}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14}, + {0x3f9720ca, 0x3f2eed6e, 0x3e6a239c, 0x3cde5548, 0x39e3e67a, 0x345d3303, 0x2ad90a30, 0x1cc1863f}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f}, + }, + } + }, + [7][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 0.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x3fffffff}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14}, + {0x3f9720ca, 0x3f2eed6e, 0x3e6a239c, 0x3cde5548, 0x39e3e67a, 0x345d3303, 0x2ad90a30, 0x1cc1863f}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f}, + }, + } + }, + [8][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 0.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x3fffffff}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14}, + {0x3f9720ca, 0x3f2eed6e, 0x3e6a239c, 0x3cde5548, 0x39e3e67a, 0x345d3303, 0x2ad90a30, 0x1cc1863f}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f}, + }, + } + }, + [8][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 0.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x3fffffff}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14}, + {0x3f9720ca, 0x3f2eed6e, 0x3e6a239c, 0x3cde5548, 0x39e3e67a, 0x345d3303, 0x2ad90a30, 0x1cc1863f}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f}, + }, + } + }, + [9][0] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 0.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x3fffffff}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14}, + {0x3f9720ca, 0x3f2eed6e, 0x3e6a239c, 0x3cde5548, 0x39e3e67a, 0x345d3303, 0x2ad90a30, 0x1cc1863f}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f}, + }, + } + }, + [9][1] = { + .sample_rate = 44100, + .total_bshift = 0, + .post_gain_db = 0, // 0 dB + .bands = { + { + .index = 0, + .type = FILTER_TYPE_PEAKING, + .fc = 32.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 1, + .type = FILTER_TYPE_PEAKING, + .fc = 64.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 2, + .type = FILTER_TYPE_PEAKING, + .fc = 125.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 3, + .type = FILTER_TYPE_PEAKING, + .fc = 250.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 4, + .type = FILTER_TYPE_PEAKING, + .fc = 500.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 5, + .type = FILTER_TYPE_PEAKING, + .fc = 1000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 6, + .type = FILTER_TYPE_PEAKING, + .fc = 2000.00f, + .q = 0.7100f, + .bw = 1.00f, + .gain = 0.00f, + }, + { + .index = 7, + .type = FILTER_TYPE_PEAKING, + .fc = 4000.00f, + .q = 0.7100f, + .bw = 1414.00f, + .gain = 0.00f, + }, + }, + .filter = { + .biquad_count = MAX_EQ_BANDS, + .state = {{0}}, + .coef = { + {0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x40000000, 0x3fffffff}, + {-0x7f96c9e2, -0x7f2d92eb, -0x7e6501c8, -0x7cca0ea9, -0x7994c1a0, -0x732f5c01, -0x668a08e1, -0x4e189e14}, + {0x3f9720ca, 0x3f2eed6e, 0x3e6a239c, 0x3cde5548, 0x39e3e67a, 0x345d3303, 0x2ad90a30, 0x1cc1863f}, + {0x7f96c9e2, 0x7f2d92eb, 0x7e6501c8, 0x7cca0ea9, 0x7994c1a0, 0x732f5c01, 0x668a08e1, 0x4e189e14}, + {-0x3f9720ca, -0x3f2eed6e, -0x3e6a239c, -0x3cde5548, -0x39e3e67a, -0x345d3303, -0x2ad90a30, -0x1cc1863f}, + }, + } + }, +}; diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_0.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_0.h new file mode 100644 index 0000000..b080553 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_0.h @@ -0,0 +1,296 @@ +#include "dsp/td_block_fir.h" + +int32_t __attribute__((aligned (8))) coefs_ex3d_0[1024] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 536870912 +}; +td_block_fir_filter_t td_block_fir_filter_ex3d_0 = { + .coefs = coefs_ex3d_0, + .block_count = 128, + .accu_shr = 0, + .accu_shl = -1, +}; + +td_block_fir_filter_t td_block_fir_filter_ex3d_1 = { + .coefs = coefs_ex3d_0, + .block_count = 128, + .accu_shr = 0, + .accu_shl = -1, +}; + +td_block_fir_filter_t td_block_fir_filter_ex3d_2 = { + .coefs = coefs_ex3d_0, + .block_count = 128, + .accu_shr = 0, + .accu_shl = -1, +}; + +td_block_fir_filter_t td_block_fir_filter_ex3d_3 = { + .coefs = coefs_ex3d_0, + .block_count = 128, + .accu_shr = 0, + .accu_shl = -1, +}; + +//This is the count of int32_t words to allocate for one data channel. +//i.e. int32_t channel_data[ex3d_0_DATA_BUFFER_ELEMENTS] = { 0 }; +#define ex3d_0_DATA_BUFFER_ELEMENTS (1040) + +#define ex3d_0_TD_BLOCK_LENGTH (8) +#define ex3d_0_BLOCK_COUNT (128) +#define ex3d_0_FRAME_ADVANCE (8) +#define ex3d_0_FRAME_OVERLAP (0) diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_protocol.md b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_protocol.md new file mode 100644 index 0000000..9a084d6 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/ex3d_protocol.md @@ -0,0 +1,909 @@ +# EX3D HID通信协议详细文档 + +## 1. 总体框架 + +### 1.1 协议概述 +该协议使用HID (Human Interface Device) 协议进行主机与设备之间的通信。协议支持EX3D(3D音频扩展)参数设置、读取等功能。 + +### 1.2 命令格式说明 + +EX3D命令使用特殊的命令格式: +- **基础命令码**: 0x80-0x94 (定义在PlbkCmdCode枚举中) +- **命令修饰符**: + - `CMD_SET(cmd)`: 清除第9位(0x0100),用于设置命令 + - `CMD_GET(cmd)`: 设置第9位(0x0100),用于读取命令 + - `CMD_MASK`: 提取基础命令码的掩码(~0x0F00) + +- **命令结构**: + - 设置命令: `基础命令码 & ~0x0100` (例如: CMD_ONOFF = 0x82, SET命令 = 0x82) + - 读取命令: `基础命令码 | 0x0100` (例如: CMD_ONOFF = 0x82, GET命令 = 0x182) + +### 1.3 数据包格式 +- **数据包大小**: 最大64字节 (1字节Report ID + 63字节数据) +- **数据对齐**: 使用32位(unsigned)对齐,数据从RcvData[4]开始,即pRcvBuf[0]开始 +- **字节序**: 小端序 (Little Endian) + +## 2. 详细命令说明 + +### 2.1 命令列表 + +| 基础命令码 | 命令名称 | SET命令码 | GET命令码 | 功能 | 方向 | +|-----------|----------|-----------|-----------|------|------| +| 0x80 | CMD_CH_NUM | - | 0x180 | 获取通道数量 | 主机→设备 | +| 0x81 | CMD_OPEN | 0x81 | - | 打开EX3D | 主机→设备 | +| 0x82 | CMD_ONOFF | 0x82 | 0x182 | 设置/获取EX3D开关 | 主机↔设备 | +| 0x83 | CMD_ANGLE | 0x83 | 0x183 | 设置/获取角度 | 主机↔设备 | +| 0x84 | CMD_SOUND_FIELD | 0x84 | 0x184 | 设置/获取声场模式 | 主机↔设备 | +| 0x85 | CMD_MUTE | 0x85 | 0x185 | 设置/获取静音 | 主机↔设备 | +| 0x86 | CMD_ON_GAIN | 0x86 | 0x186 | 设置/获取开启增益 | 主机↔设备 | +| 0x87 | CMD_LMT_THRESHOLD | 0x87 | 0x187 | 设置/获取限制器阈值 | 主机↔设备 | +| 0x88 | CMD_UPMIX | 0x88 | 0x188 | 设置/获取上混 | 主机↔设备 | +| 0x89 | CMD_LFE | 0x89 | 0x189 | 设置/获取LFE增益 | 主机↔设备 | +| 0x8A | CMD_OFF_GAIN | 0x8A | 0x18A | 设置/获取关闭增益 | 主机↔设备 | +| 0x8B | CMD_SOUND_FIELD_NUM | - | 0x18B | 获取声场模式数量 | 主机→设备 | +| 0x8C | CMD_SOUND_FIELD_NAME | - | 0x18C | 获取声场模式名称 | 主机→设备 | +| 0x8D | CMD_LEVEL | - | 0x18D | 获取电平 | 主机→设备 | +| 0x8E | CMD_LMT_ATTACKK_TIME | 0x8E | 0x18E | 设置/获取限制器攻击时间 | 主机↔设备 | +| 0x8F | CMD_LMT_RELEASE_TIME | 0x8F | 0x18F | 设置/获取限制器释放时间 | 主机↔设备 | +| 0x90 | CMD_TEST_CANCEL | 0x90 | - | 取消测试 | 主机→设备 | +| 0x91 | CMD_TEST_STEP | 0x91 | - | 测试步骤 | 主机→设备 | +| 0x92 | CMD_TEST_ROTATE | 0x92 | - | 测试旋转 | 主机→设备 | +| 0x93 | CMD_EXPAND_GAIN | 0x93 | 0x193 | 设置/获取扩展增益 | 主机↔设备 | +| 0x94 | CMD_REDUCE_GAIN | 0x94 | 0x194 | 设置/获取减少增益 | 主机↔设备 | + +### 2.2 数据包结构 + +#### 通用请求数据包格式 +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 保留 | 保留字节(跳过) +4-7 | 4 | uint32 | 命令码 (包含SET/GET标志) +8-11 | 4 | uint32 | 参数1 (根据命令不同而不同) +12-15 | 4 | uint32 | 参数2 (根据命令不同而不同) +... | ... | ... | 其他参数 +``` + +#### 通用响应数据包格式 +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 保留 | 保留字节(跳过) +4-7 | 4 | uint32 | 命令码 (回显) +8-11 | 4 | uint32 | 返回值/参数1 +12-15 | 4 | uint32 | 返回值/参数2 +... | ... | ... | 其他返回值 +``` + +### 2.3 详细命令说明 + +#### 2.3.1 CMD_CH_NUM (0x80) - 获取通道数量 +**功能**: 获取USB音频输出通道数量 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x180 = CMD_GET(CMD_CH_NUM)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x180,回显) +8-11 | 4 | uint32 | 通道数量 (NUM_USB_CHAN_OUT) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.2 CMD_OPEN (0x81) - 打开EX3D +**功能**: 打开EX3D系统 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x81 = CMD_SET(CMD_OPEN)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x81,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +#### 2.3.3 CMD_ONOFF (0x82) - 设置/获取EX3D开关 +**功能**: 设置或获取EX3D开关状态 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x82): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x82 = CMD_SET(CMD_ONOFF)) +8-11 | 4 | uint32 | 开关值 (0=关闭, 1=开启) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x82,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +**GET命令请求数据包格式** (0x182): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x182 = CMD_GET(CMD_ONOFF)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x182,回显) +8-11 | 4 | uint32 | 当前开关状态 (0=关闭, 1=开启) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.4 CMD_ANGLE (0x83) - 设置/获取角度 +**功能**: 设置或获取通道的角度(垂直角度和水平角度) +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x83): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x83 = CMD_SET(CMD_ANGLE)) +8-11 | 4 | uint32 | 通道号 (如果 >= NUM_USB_CHAN_OUT,表示设置所有通道) +12-15 | 4 | uint32 | 角度值1 (单通道) 或 角度值[0] (多通道) +16-19 | 4 | uint32 | 角度值2 (多通道,可选) +... | ... | ... | 其他通道角度值(最多NUM_USB_CHAN_OUT个) +``` +**角度值格式**: `(VAngle << 16) | HAngle` +- VAngle: 垂直角度 (0-180度,高16位) +- HAngle: 水平角度 (0-359度,低16位) + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x83,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +**GET命令请求数据包格式** (0x183): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x183 = CMD_GET(CMD_ANGLE)) +8-11 | 4 | uint32 | 通道号 (如果 >= NUM_USB_CHAN_OUT,表示获取所有通道) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x183,回显) +8-11 | 4 | uint32 | 角度值 (单通道) 或 角度值[0] (多通道) +12-15 | 4 | uint32 | 角度值[1] (多通道,可选) +... | ... | ... | 其他通道角度值(最多NUM_USB_CHAN_OUT个) +``` +**角度值格式**: `(VAngle << 16) | HAngle` + +#### 2.3.5 CMD_SOUND_FIELD (0x84) - 设置/获取声场模式 +**功能**: 设置或获取当前声场模式 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x84): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x84 = CMD_SET(CMD_SOUND_FIELD)) +8-11 | 4 | uint32 | 声场模式索引 (0到EX3D_SF_NUM-1) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x84,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=索引无效, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x184): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x184 = CMD_GET(CMD_SOUND_FIELD)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x184,回显) +8-11 | 4 | uint32 | 当前声场模式索引 (或0) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.6 CMD_MUTE (0x85) - 设置/获取静音 +**功能**: 设置或获取通道的静音状态 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x85): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x85 = CMD_SET(CMD_MUTE)) +8-11 | 4 | uint32 | 通道号 (如果 >= NUM_USB_CHAN_OUT,表示设置所有通道) +12-15 | 4 | uint32 | 静音值1 (单通道) 或 静音值[0] (多通道,0=不静音, 1=静音) +16-19 | 4 | uint32 | 静音值2 (多通道,可选) +... | ... | ... | 其他通道静音值(最多NUM_USB_CHAN_OUT个) +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x85,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +**GET命令请求数据包格式** (0x185): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x185 = CMD_GET(CMD_MUTE)) +8-11 | 4 | uint32 | 通道号 (如果 >= NUM_USB_CHAN_OUT,表示获取所有通道) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x185,回显) +8-11 | 4 | uint32 | 静音值 (单通道) 或 静音值[0] (多通道) +12-15 | 4 | uint32 | 静音值[1] (多通道,可选) +... | ... | ... | 其他通道静音值(最多NUM_USB_CHAN_OUT个) +``` + +#### 2.3.7 CMD_ON_GAIN (0x86) - 设置/获取开启增益 +**功能**: 设置或获取EX3D开启时的增益值 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x86): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x86 = CMD_SET(CMD_ON_GAIN)) +8-11 | 4 | int32 | 增益值 (范围-100到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x86,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x186): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x186 = CMD_GET(CMD_ON_GAIN)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x186,回显) +8-11 | 4 | int32 | 当前开启增益值 (范围-100到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.8 CMD_LMT_THRESHOLD (0x87) - 设置/获取限制器阈值 +**功能**: 设置或获取限制器阈值 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x87): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x87 = CMD_SET(CMD_LMT_THRESHOLD)) +8-11 | 4 | int32 | 阈值 (范围-35到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x87,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x187): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x187 = CMD_GET(CMD_LMT_THRESHOLD)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x187,回显) +8-11 | 4 | int32 | 当前限制器阈值 (范围-35到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.9 CMD_UPMIX (0x88) - 设置/获取上混 +**功能**: 设置或获取上混状态 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x88): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x88 = CMD_SET(CMD_UPMIX)) +8-11 | 4 | uint32 | 上混值 (0=关闭, 1=开启) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x88,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +**GET命令请求数据包格式** (0x188): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x188 = CMD_GET(CMD_UPMIX)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x188,回显) +8-11 | 4 | uint32 | 当前上混状态 (0=关闭, 1=开启) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.10 CMD_LFE (0x89) - 设置/获取LFE增益 +**功能**: 设置或获取LFE(低频效果)增益 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x89): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x89 = CMD_SET(CMD_LFE)) +8-11 | 4 | int32 | 增益值 (范围-100到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x89,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x189): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x189 = CMD_GET(CMD_LFE)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x189,回显) +8-11 | 4 | int32 | 当前LFE增益值 (范围-100到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.11 CMD_OFF_GAIN (0x8A) - 设置/获取关闭增益 +**功能**: 设置或获取EX3D关闭时的增益值 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x8A): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x8A = CMD_SET(CMD_OFF_GAIN)) +8-11 | 4 | int32 | 增益值 (范围-100到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x8A,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x18A): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18A = CMD_GET(CMD_OFF_GAIN)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18A,回显) +8-11 | 4 | int32 | 当前关闭增益值 (范围-100到0 dB,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.12 CMD_SOUND_FIELD_NUM (0x8B) - 获取声场模式数量 +**功能**: 获取可用的声场模式数量 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18B = CMD_GET(CMD_SOUND_FIELD_NUM)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18B,回显) +8-11 | 4 | uint32 | 声场模式数量 (EX3D_SF_NUM) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.13 CMD_SOUND_FIELD_NAME (0x8C) - 获取声场模式名称 +**功能**: 获取指定声场模式的名称 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18C = CMD_GET(CMD_SOUND_FIELD_NAME)) +8-11 | 4 | uint32 | 声场模式索引 +12-63 | 52 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18C,回显) +8-11 | 4 | uint32 | 名称长度 (如果索引无效则为0xFFFFFFFF) +12-15 | 4 | char | 名称字符[0-3] (UTF-8编码,如果索引有效) +16-19 | 4 | char | 名称字符[4-7] (UTF-8编码,可选) +... | ... | ... | 其他名称字符(最多16字节) +``` +**注意**: 如果索引无效,位置8-11返回0xFFFFFFFF,后续字节为0x00 + +#### 2.3.14 CMD_LEVEL (0x8D) - 获取电平 +**功能**: 获取通道的电平值 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18D = CMD_GET(CMD_LEVEL)) +8-11 | 4 | uint32 | 通道数量 +12-63 | 52 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18D,回显) +8-11 | 4 | float | 电平值[0] (通道0的电平值,IEEE 754格式,小端序) +12-15 | 4 | float | 电平值[1] (通道1的电平值,可选) +... | ... | ... | 其他通道电平值(最多NUM_USB_CHAN_OUT个,每个4字节float) +``` + +#### 2.3.15 CMD_LMT_ATTACKK_TIME (0x8E) - 设置/获取限制器攻击时间 +**功能**: 设置或获取限制器攻击时间 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x8E): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x8E = CMD_SET(CMD_LMT_ATTACKK_TIME)) +8-11 | 4 | uint32 | 攻击时间 (范围0到10,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x8E,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x18E): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18E = CMD_GET(CMD_LMT_ATTACKK_TIME)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18E,回显) +8-11 | 4 | uint32 | 当前限制器攻击时间 (范围0到10,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.16 CMD_LMT_RELEASE_TIME (0x8F) - 设置/获取限制器释放时间 +**功能**: 设置或获取限制器释放时间 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x8F): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x8F = CMD_SET(CMD_LMT_RELEASE_TIME)) +8-11 | 4 | uint32 | 释放时间 (范围0到100,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x8F,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x18F): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18F = CMD_GET(CMD_LMT_RELEASE_TIME)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x18F,回显) +8-11 | 4 | uint32 | 当前限制器释放时间 (范围0到100,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.17 CMD_EXPAND_GAIN (0x93) - 设置/获取扩展增益 +**功能**: 设置或获取扩展增益值 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x93): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x93 = CMD_SET(CMD_EXPAND_GAIN)) +8-11 | 4 | int32 | 增益值 (范围0到20,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x93,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x193): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x193 = CMD_GET(CMD_EXPAND_GAIN)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x193,回显) +8-11 | 4 | int32 | 当前扩展增益值 (范围0到20,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.18 CMD_REDUCE_GAIN (0x94) - 设置/获取减少增益 +**功能**: 设置或获取减少增益值 +**方向**: 主机↔设备 + +**SET命令请求数据包格式** (0x94): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x94 = CMD_SET(CMD_REDUCE_GAIN)) +8-11 | 4 | int32 | 增益值 (范围-20到0,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +**SET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x94,回显) +8-11 | 4 | uint32 | 状态码 (0xFFFFFFFF=参数超出范围, 其他=成功) +12-63 | 52 | 0x00 | 保留字节 +``` + +**GET命令请求数据包格式** (0x194): +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x194 = CMD_GET(CMD_REDUCE_GAIN)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**GET命令响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x194,回显) +8-11 | 4 | int32 | 当前减少增益值 (范围-20到0,小端序) +12-63 | 52 | 0x00 | 保留字节 +``` + +#### 2.3.19 CMD_TEST_CANCEL (0x90) - 取消测试 +**功能**: 取消EX3D测试 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x90 = CMD_SET(CMD_TEST_CANCEL)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x90,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +#### 2.3.20 CMD_TEST_STEP (0x91) - 测试步骤 +**功能**: 执行EX3D测试步骤 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x91 = CMD_SET(CMD_TEST_STEP)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x91,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +#### 2.3.21 CMD_TEST_ROTATE (0x92) - 测试旋转 +**功能**: 执行EX3D测试旋转 +**方向**: 主机→设备 +**请求数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x92 = CMD_SET(CMD_TEST_ROTATE)) +8-63 | 56 | 0x00 | 保留字节 +``` + +**响应数据包格式**: +``` +字节位置 | 长度 | 内容 | 描述 +---------|------|------|------ +0 | 1 | 0x01 | Report ID +1-3 | 3 | 0x00 | 保留字节 +4-7 | 4 | uint32 | 命令码 (0x92,回显) +8-63 | 56 | 0x00 | 保留字节(无返回值) +``` + +## 3. 注意事项 + +1. **数据对齐**: 所有数据使用32位(unsigned)对齐,从RcvData[4]开始 +2. **命令码格式**: SET命令清除第9位,GET命令设置第9位 +3. **错误处理**: 当参数超出范围时,返回0xFFFFFFFF表示错误 +4. **多通道处理**: ANGLE和MUTE命令支持单通道或多通道批量设置 +5. **字节序**: 所有多字节数据使用小端序 +6. **字符串编码**: 字符串使用UTF-8编码 + +## 4. 实现参考 + +参考文件: `sw_usb_audio/app_usb_aud_phaten_golden/src/extensions/dsp.c` +- 函数: `hid_receive_task_in_c()` +- 行号: 269-598 + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/filter_utils.py b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/filter_utils.py new file mode 100644 index 0000000..282b031 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/filter_utils.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +滤波器工具类 +包含双二阶滤波器计算器和各种滤波器类型的系数计算函数 +""" + +import math +import numpy as np +from typing import Dict, List, Union + +class BiquadFilterCalculator: + """双二阶滤波器计算器""" + def __init__(self): + self.coefficients = { + 'a0': 0.0, + 'a1': 0.0, + 'a2': 0.0, + 'b1': 0.0, + 'b2': 0.0 + } + + def calculate(self, filter_type: str, Fc: float, Fs: float, Q: float, peak_gain: float, bandwidth: float = None) -> Dict[str, float]: + """ + 计算滤波器系数 + :param filter_type: 滤波器类型 + :param Fc: 截止频率 + :param Fs: 采样率 + :param Q: 品质因数 + :param peak_gain: 峰值增益(dB) + :return: 滤波器系数字典 + """ + V = math.pow(10, abs(peak_gain) / 20) + K = math.tan(math.pi * Fc / Fs) + norm = 0.0 + + filter_type = filter_type.lower() + + if filter_type == "lowpass": + norm = 1 / (1 + K / Q + K * K) + self.coefficients['a0'] = K * K * norm + self.coefficients['a1'] = 2 * self.coefficients['a0'] + self.coefficients['a2'] = self.coefficients['a0'] + self.coefficients['b1'] = 2 * (K * K - 1) * norm + self.coefficients['b2'] = (1 - K / Q + K * K) * norm + + elif filter_type == "highpass": + norm = 1 / (1 + K / Q + K * K) + self.coefficients['a0'] = 1 * norm + self.coefficients['a1'] = -2 * self.coefficients['a0'] + self.coefficients['a2'] = self.coefficients['a0'] + self.coefficients['b1'] = 2 * (K * K - 1) * norm + self.coefficients['b2'] = (1 - K / Q + K * K) * norm + + elif filter_type == "bandpass": + # 使用lib_audio_dsp中biquad.c的算法 + # 使用bandwidth参数,如果没有提供则使用Q作为近似值 + bw = bandwidth if bandwidth is not None else Q + w0 = 2.0 * math.pi * Fc / Fs + sin_w0 = math.sin(w0) + alpha = sin_w0 * math.sinh(math.log(2) / 2.0 * bw * w0 / sin_w0) + + self.coefficients['a0'] = alpha + self.coefficients['a1'] = 0 + self.coefficients['a2'] = -alpha + self.coefficients['b1'] = -2.0 * math.cos(w0) + self.coefficients['b2'] = 1.0 - alpha + + # 归一化 + a0 = 1.0 + alpha + inv_a0 = 1.0 / a0 + self.coefficients['a0'] *= inv_a0 + self.coefficients['a1'] *= inv_a0 + self.coefficients['a2'] *= inv_a0 + self.coefficients['b1'] *= inv_a0 + self.coefficients['b2'] *= inv_a0 + + elif filter_type == "notch": + norm = 1 / (1 + K / Q + K * K) + self.coefficients['a0'] = (1 + K * K) * norm + self.coefficients['a1'] = 2 * (K * K - 1) * norm + self.coefficients['a2'] = self.coefficients['a0'] + self.coefficients['b1'] = self.coefficients['a1'] + self.coefficients['b2'] = (1 - K / Q + K * K) * norm + + elif filter_type == "peak": + if peak_gain >= 0: + norm = 1 / (1 + 1/Q * K + K * K) + self.coefficients['a0'] = (1 + V/Q * K + K * K) * norm + self.coefficients['a1'] = 2 * (K * K - 1) * norm + self.coefficients['a2'] = (1 - V/Q * K + K * K) * norm + self.coefficients['b1'] = self.coefficients['a1'] + self.coefficients['b2'] = (1 - 1/Q * K + K * K) * norm + else: + norm = 1 / (1 + V/Q * K + K * K) + self.coefficients['a0'] = (1 + 1/Q * K + K * K) * norm + self.coefficients['a1'] = 2 * (K * K - 1) * norm + self.coefficients['a2'] = (1 - 1/Q * K + K * K) * norm + self.coefficients['b1'] = self.coefficients['a1'] + self.coefficients['b2'] = (1 - V/Q * K + K * K) * norm + + elif filter_type == "lowshelf": + if peak_gain >= 0: + norm = 1 / (1 + math.sqrt(2) * K + K * K) + self.coefficients['a0'] = (1 + math.sqrt(2*V) * K + V * K * K) * norm + self.coefficients['a1'] = 2 * (V * K * K - 1) * norm + self.coefficients['a2'] = (1 - math.sqrt(2*V) * K + V * K * K) * norm + self.coefficients['b1'] = 2 * (K * K - 1) * norm + self.coefficients['b2'] = (1 - math.sqrt(2) * K + K * K) * norm + else: + norm = 1 / (1 + math.sqrt(2*V) * K + V * K * K) + self.coefficients['a0'] = (1 + math.sqrt(2) * K + K * K) * norm + self.coefficients['a1'] = 2 * (K * K - 1) * norm + self.coefficients['a2'] = (1 - math.sqrt(2) * K + K * K) * norm + self.coefficients['b1'] = 2 * (V * K * K - 1) * norm + self.coefficients['b2'] = (1 - math.sqrt(2*V) * K + V * K * K) * norm + + elif filter_type == "highshelf": + if peak_gain >= 0: + norm = 1 / (1 + math.sqrt(2) * K + K * K) + self.coefficients['a0'] = (V + math.sqrt(2*V) * K + K * K) * norm + self.coefficients['a1'] = 2 * (K * K - V) * norm + self.coefficients['a2'] = (V - math.sqrt(2*V) * K + K * K) * norm + self.coefficients['b1'] = 2 * (K * K - 1) * norm + self.coefficients['b2'] = (1 - math.sqrt(2) * K + K * K) * norm + else: + norm = 1 / (V + math.sqrt(2*V) * K + K * K) + self.coefficients['a0'] = (1 + math.sqrt(2) * K + K * K) * norm + self.coefficients['a1'] = 2 * (K * K - 1) * norm + self.coefficients['a2'] = (1 - math.sqrt(2) * K + K * K) * norm + self.coefficients['b1'] = 2 * (K * K - V) * norm + self.coefficients['b2'] = (V - math.sqrt(2*V) * K + K * K) * norm + + else: + raise ValueError("不支持的滤波器类型") + + return self.coefficients + +def _check_filter_freq(filter_freq: float, fs: int) -> float: + """检查滤波器频率是否有效""" + if filter_freq >= fs / 2: + raise ValueError(f"滤波器频率 ({filter_freq} Hz) 必须小于采样率的一半 ({fs/2} Hz)") + return filter_freq + +def make_biquad_bypass(fs: int) -> List[float]: + """创建旁路滤波器系数""" + return [1.0, 0.0, 0.0, 0.0, 0.0] + +def make_biquad_lowpass(fs: int, filter_freq: float, q_factor: float) -> List[float]: + """创建低通滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + K = math.tan(math.pi * filter_freq / fs) + norm = 1 / (1 + K / q_factor + K * K) + + b0 = K * K * norm + b1 = 2 * b0 + b2 = b0 + a1 = 2 * (K * K - 1) * norm + a2 = (1 - K / q_factor + K * K) * norm + + return [b0, b1, b2, -a1, -a2] + +def make_biquad_highpass(fs: int, filter_freq: float, q_factor: float) -> List[float]: + """创建高通滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + K = math.tan(math.pi * filter_freq / fs) + norm = 1 / (1 + K / q_factor + K * K) + + b0 = norm + b1 = -2 * b0 + b2 = b0 + a1 = 2 * (K * K - 1) * norm + a2 = (1 - K / q_factor + K * K) * norm + + return [b0, b1, b2, -a1, -a2] + +def make_biquad_bandpass(fs: int, filter_freq: float, bandwidth: float) -> List[float]: + """创建带通滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + w0 = 2.0 * math.pi * filter_freq / fs + alpha = math.sin(w0) * math.sinh(math.log(2) / 2 * bandwidth * w0 / math.sin(w0)) + + b0 = alpha + b1 = 0.0 + b2 = -alpha + a0 = 1.0 + alpha + a1 = -2.0 * math.cos(w0) + a2 = 1.0 - alpha + + return [b0/a0, b1/a0, b2/a0, -a1/a0, -a2/a0] + +def make_biquad_bandstop(fs: int, filter_freq: float, bandwidth: float) -> List[float]: + """创建带阻滤波器系数 - 使用lib_audio_dsp中biquad.c的算法""" + filter_freq = _check_filter_freq(filter_freq, fs) + w0 = 2.0 * math.pi * filter_freq / fs + sin_w0 = math.sin(w0) + alpha = sin_w0 * math.sinh(math.log(2) / 2.0 * bandwidth * w0 / sin_w0) + + b0 = 1.0 + b1 = -2.0 * math.cos(w0) + b2 = 1.0 + a0 = 1.0 + alpha + a1 = b1 # 与lib_audio_dsp一致 + a2 = 1.0 - alpha + + return [b0/a0, b1/a0, b2/a0, -a1/a0, -a2/a0] + +def make_biquad_notch(fs: int, filter_freq: float, q_factor: float) -> List[float]: + """创建陷波滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + K = math.tan(math.pi * filter_freq / fs) + norm = 1 / (1 + K / q_factor + K * K) + + b0 = (1 + K * K) * norm + b1 = 2 * (K * K - 1) * norm + b2 = b0 + a1 = b1 + a2 = (1 - K / q_factor + K * K) * norm + + return [b0, b1, b2, -a1, -a2] + +def make_biquad_allpass(fs: int, filter_freq: float, q_factor: float) -> List[float]: + """创建全通滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + K = math.tan(math.pi * filter_freq / fs) + norm = 1 / (1 + K / q_factor + K * K) + + b0 = (1 - K / q_factor + K * K) * norm + b1 = 2 * (K * K - 1) * norm + b2 = 1.0 + a1 = b1 + a2 = b0 + + return [b0, b1, b2, -a1, -a2] + +def make_biquad_peaking(fs: int, filter_freq: float, q_factor: float, boost_db: float) -> List[float]: + """创建峰值滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + A = math.sqrt(10 ** (boost_db / 20)) + w0 = 2.0 * math.pi * filter_freq / fs + alpha = math.sin(w0) / (2.0 * q_factor) + + b0 = 1.0 + alpha * A + b1 = -2.0 * math.cos(w0) + b2 = 1.0 - alpha * A + a0 = 1.0 + alpha / A + a1 = -2.0 * math.cos(w0) + a2 = 1.0 - alpha / A + + return [b0/a0, b1/a0, b2/a0, -a1/a0, -a2/a0] + +def make_biquad_constant_q(fs: int, filter_freq: float, q_factor: float, boost_db: float) -> List[float]: + """创建恒定Q值滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + V = 10 ** (boost_db / 20) + w0 = 2.0 * math.pi * filter_freq / fs + K = math.tan(w0 / 2) + + if boost_db > 0: + b0 = 1 + V * K / q_factor + K**2 + b1 = 2 * (K**2 - 1) + b2 = 1 - V * K / q_factor + K**2 + a0 = 1 + K / q_factor + K**2 + a1 = 2 * (K**2 - 1) + a2 = 1 - K / q_factor + K**2 + else: + V = 1 / V + b0 = 1 + K / q_factor + K**2 + b1 = 2 * (K**2 - 1) + b2 = 1 - K / q_factor + K**2 + a0 = 1 + V * K / q_factor + K**2 + a1 = 2 * (K**2 - 1) + a2 = 1 - V * K / q_factor + K**2 + + return [b0/a0, b1/a0, b2/a0, -a1/a0, -a2/a0] + +def make_biquad_lowshelf(fs: int, filter_freq: float, q_factor: float, boost_db: float) -> List[float]: + """创建低架滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + A = math.pow(10, boost_db / 40) + w0 = 2.0 * math.pi * filter_freq / fs + alpha = math.sin(w0) / 2 * math.sqrt((A + 1/A) * (1/q_factor - 1) + 2) + + if boost_db >= 0: + b0 = A * ((A+1) + (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha) + b1 = -2*A * ((A-1) + (A+1)*math.cos(w0)) + b2 = A * ((A+1) + (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha) + a0 = (A+1) - (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha + a1 = 2*((A-1) - (A+1)*math.cos(w0)) + a2 = (A+1) - (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha + else: + b0 = (A+1) - (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha + b1 = 2*((A-1) - (A+1)*math.cos(w0)) + b2 = (A+1) - (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha + a0 = A*((A+1) + (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha) + a1 = -2*A*((A-1) + (A+1)*math.cos(w0)) + a2 = A*((A+1) + (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha) + + return [b0/a0, b1/a0, b2/a0, -a1/a0, -a2/a0] + +def make_biquad_highshelf(fs: int, filter_freq: float, q_factor: float, boost_db: float) -> List[float]: + """创建高架滤波器系数""" + filter_freq = _check_filter_freq(filter_freq, fs) + A = math.pow(10, boost_db / 40) # 将dB转换为线性增益 + w0 = 2.0 * math.pi * filter_freq / fs # 归一化角频率 + alpha = math.sin(w0) / 2 * math.sqrt((A + 1/A) * (1/q_factor - 1) + 2) + + if boost_db >= 0: # 增益提升 + b0 = A*((A+1) + (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha) + b1 = -2*A*((A-1) + (A+1)*math.cos(w0)) + b2 = A*((A+1) + (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha) + a0 = (A+1) - (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha + a1 = 2*((A-1) - (A+1)*math.cos(w0)) + a2 = (A+1) - (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha + else: # 增益衰减 + b0 = (A+1) - (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha + b1 = 2*((A-1) - (A+1)*math.cos(w0)) + b2 = (A+1) - (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha + a0 = A*((A+1) + (A-1)*math.cos(w0) + 2*math.sqrt(A)*alpha) + a1 = -2*A*((A-1) + (A+1)*math.cos(w0)) + a2 = A*((A+1) + (A-1)*math.cos(w0) - 2*math.sqrt(A)*alpha) + + return coeffs \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hid_report_descriptor.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hid_report_descriptor.h new file mode 100644 index 0000000..8d522f6 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hid_report_descriptor.h @@ -0,0 +1,296 @@ +// Copyright 2021 XMOS LIMITED. +// This Software is subject to the terms of the XCORE VocalFusion Licence. + +#ifndef __hid_report_descriptor_h__ +#define __hid_report_descriptor_h__ + +#include "xua_hid_report.h" + +#if 0 +/* Existing static report descriptor kept for reference */ +#ifdef HID_CONTROLS +unsigned char hidReportDescriptor[] = +{ + 0x05, 0x0c, /* Usage Page (Consumer Device) */ + 0x09, 0x01, /* Usage (Consumer Control) */ + 0xa1, 0x01, /* Collection (Application) */ +#ifdef DFU_CONTROL_USB_HID + 0x85, 0x01, // HID_BUTTON_REPORT_ID // TODO: report ID uses the global definition +#endif + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x09, 0xb0, /* Usage (Play) */ + 0x09, 0xb5, /* Usage (Scan Next Track) */ + 0x09, 0xb6, /* Usage (Scan Previous Track) */ + 0x09, 0xe9, /* Usage (Volume Up) */ + 0x09, 0xea, /* Usage (Volume Down) */ + 0x09, 0xe2, /* Usage (Mute) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x06, /* Report Count (6) */ + 0x81, 0x02, /* Input (Data, Var, Abs) */ + 0x95, 0x02, /* Report Count (2) */ + 0x81, 0x01, /* Input (Cnst, Ary, Abs) */ + 0xc0 /* End collection */ + +#ifdef DFU_CONTROL_USB_HID + ,0x06, 0x82, 0xff, + 0x09, 0x01, + 0xa1, 0x01, + 0x09, 0x03, + 0x85, 0x41, // HID_DFU_REPORT_ID // TODO: report ID uses the global definition + 0x15, 0x00, + 0x26, 0xff, 0x00, + 0x75, 0x08, + 0x95, 0x3f, + 0x81, 0x02, + 0x09, 0x04, + 0x15, 0x00, + 0x26, 0xff, 0x00, + 0x75, 0x08, + 0x95, 0x3f, + 0x91, 0x02, + 0xc0 +#if 0 +Usage Page (Vendor-Defined 131) 06 82 FF +Usage (Vendor-Defined 1) 09 01 +Collection (Application) A1 01 + Usage (Vendor-Defined 3) 09 03 + Report ID (65) 85 41 + Logical Minimum (0) 15 00 + Logical Maximum (255) 26 FF 00 + Report Size (8) 75 08 + Report Count (63) 95 3F + Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 + Usage (Vendor-Defined 4) 09 04 + Logical Minimum (0) 15 00 + Logical Maximum (255) 26 FF 00 + Report Size (8) 75 08 + Report Count (63) 95 3F + Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 02 +End Collection C0 +#endif +#endif +}; +#endif +#endif + +/* + * Define non-configurable items in the HID Report descriptor. + */ +static const USB_HID_Short_Item_t hidCollectionApplication = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_MAIN, HID_REPORT_ITEM_TAG_COLLECTION), + .data = { 0x01, 0x00 } }; +static const USB_HID_Short_Item_t hidCollectionApplication2 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_MAIN, HID_REPORT_ITEM_TAG_COLLECTION), + .data = { 0x02, 0x00 } }; + +static const USB_HID_Short_Item_t hidCollectionEnd = { + .header = HID_REPORT_SET_HEADER(0, HID_REPORT_ITEM_TYPE_MAIN, HID_REPORT_ITEM_TAG_END_COLLECTION), + .data = { 0x00, 0x00 } }; + +static const USB_HID_Short_Item_t hidInputConstArray = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_MAIN, HID_REPORT_ITEM_TAG_INPUT), + .data = { 0x01, 0x00 } }; +static const USB_HID_Short_Item_t hidInputDataVar = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_MAIN, HID_REPORT_ITEM_TAG_INPUT), + .data = { 0x02, 0x00 } }; + +static const USB_HID_Short_Item_t hidInputFeatureVar = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_MAIN, HID_REPORT_ITEM_TAG_FEATURE), + .data = { 0x01, 0x00 } }; + +static const USB_HID_Short_Item_t hidLogicalMaximum0 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_LOGICAL_MAXIMUM), + .data = { 0x00, 0x00 } }; +static const USB_HID_Short_Item_t hidLogicalMaximum1 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_LOGICAL_MAXIMUM), + .data = { 0x01, 0x00 } }; +static const USB_HID_Short_Item_t hidLogicalMinimum0 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_LOGICAL_MINIMUM), + .data = { 0x00, 0x00 } }; + +static const USB_HID_Short_Item_t hidReportCount2 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_REPORT_COUNT), + .data = { 0x00, 0x00 } }; +static const USB_HID_Short_Item_t hidReportCount8 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_REPORT_COUNT), + .data = { 0x08, 0x00 } }; +static const USB_HID_Short_Item_t hidReportSize1 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_REPORT_SIZE), + .data = { 0x01, 0x00 } }; + +static const USB_HID_Short_Item_t hidUsageConsumerControl = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .data = { 0x01, 0x00 } }; + +static const USB_HID_Short_Item_t hidUsageConsumerControl2 = { + .header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .data = { 0x02, 0x00 } }; + + +/* + * Define the HID Report Descriptor Item, Usage Page, Report ID and length for each HID Report + * For internal purposes, a report element with ID of 0 must be included if report IDs are not being used. + */ +static const USB_HID_Report_Element_t hidReportPageConsumer = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_USAGE_PAGE), + .item.data = { USB_HID_USAGE_PAGE_ID_CONSUMER, 0x00 }, + .location = HID_REPORT_SET_LOC(0x1, 2, 0, 0 ) +}; + +static const USB_HID_Report_Element_t hidReportPageVendor = { + .item.header = HID_REPORT_SET_HEADER(2, HID_REPORT_ITEM_TYPE_GLOBAL, HID_REPORT_ITEM_TAG_USAGE_PAGE), + .item.data = {0x83, 0xff }, + .location = HID_REPORT_SET_LOC(0x2, 2, 0, 0 ) +}; + + +static const USB_HID_Short_Item_t hidUsagePageConsumer = { .header = 0x05, .data = { 0x0C, 0x00 }}; + +static const USB_HID_Short_Item_t hidUsagePageVendor = { .header = 0x06, .data = { 0x82, 0xff }}; + +static const USB_HID_Short_Item_t hidUsageConsumerVendor = { .header = 0x09, .data = { 0x03, 0x00 }}; + +static const USB_HID_Short_Item_t hidPassReportId = { .header = 0x85, .data = { 0x1, 0x00 }}; +static const USB_HID_Short_Item_t hidButtonReportId = { .header = 0x85, .data = { 0x2, 0x00 }}; + +static const USB_HID_Short_Item_t hidLogicalMaximum2 = { .header = 0x26, .data = { 0xFF, 0x00 }}; + +static const USB_HID_Short_Item_t hidReportSize = { .header = 0x75, .data = { 0x08, 0x00 }}; +#if DEBUG_MEMORY_LOG_ENABLED +static const USB_HID_Short_Item_t hidPassReportCount = { .header = 0x95, .data = { 0x3F, 0x00 }}; +static const USB_HID_Short_Item_t hidPassReportCount2 = { .header = 0x95, .data = { 0x3F, 0x00 }}; +static const USB_HID_Short_Item_t hidPassReportCount3 = { .header = 0x95, .data = { 0x3f, 0x00 }}; +#else +static const USB_HID_Short_Item_t hidPassReportCount = { .header = 0x95, .data = { 0x3f, 0x00 }}; +static const USB_HID_Short_Item_t hidPassReportCount2 = { .header = 0x95, .data = { 0x3f, 0x00 }}; +static const USB_HID_Short_Item_t hidPassReportCount3 = { .header = 0x95, .data = { 0x3f, 0x00 }}; +#endif +static const USB_HID_Short_Item_t hidButtonReportCount = { .header = 0x95, .data = { 0x04, 0x00 }}; +static const USB_HID_Short_Item_t hidUsageVendor = { .header = 0x09, .data = { 0x04, 0x00 }}; +static const USB_HID_Short_Item_t hidUsageVendor2 = { .header = 0x09, .data = { 0x05, 0x00 }}; +static const USB_HID_Short_Item_t hidOutputDataVar = { .header = 0x91, .data = { 0x02, 0x00 }}; + +/* + * Define configurable items in the HID Report descriptor. + */ + +static USB_HID_Report_Element_t hidUsageByte0Bit7 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xE2, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 5) +}; // Mute +static USB_HID_Report_Element_t hidUsageByte0Bit6 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xB4, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 5) +}; // rewind +static USB_HID_Report_Element_t hidUsageByte0Bit5 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xB3, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 5) +}; // forward +static USB_HID_Report_Element_t hidUsageByte0Bit4 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xB6, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 2) +}; // Scan Prev +static USB_HID_Report_Element_t hidUsageByte0Bit3 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xB5, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 1) +}; // Scan Next +static USB_HID_Report_Element_t hidUsageByte0Bit2 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xCD, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 0) +}; // Play +static USB_HID_Report_Element_t hidUsageByte0Bit1 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xEA, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 4) +}; // Vol- +static USB_HID_Report_Element_t hidUsageByte0Bit0 = { + .item.header = HID_REPORT_SET_HEADER(1, HID_REPORT_ITEM_TYPE_LOCAL, HID_REPORT_ITEM_TAG_USAGE), + .item.data = { 0xE9, 0x00 }, + .location = HID_REPORT_SET_LOC(0, 0, 0, 3) +}; // Vol+ + +/* + * List the configurable elements in the HID Report descriptor. + */ +static USB_HID_Report_Element_t* const hidConfigurableElements[] = { + &hidUsageByte0Bit0, + &hidUsageByte0Bit1, + &hidUsageByte0Bit2, + &hidUsageByte0Bit3, + &hidUsageByte0Bit4, + &hidUsageByte0Bit5, + &hidUsageByte0Bit6, + &hidUsageByte0Bit7 +}; + +/* + * List HID Reports, one per Report ID. This should be a usage page item with the relevant + * If not using report IDs - still have one with report ID 0 + */ +static const USB_HID_Report_Element_t* const hidReports[] = { + &hidReportPageVendor, + &hidReportPageConsumer +}; + +/* + * List all items in the HID Report descriptor. + */ +static const USB_HID_Short_Item_t* const hidReportDescriptorItems[] = { + &(hidReportPageVendor.item), + &hidUsageConsumerControl, + &hidCollectionApplication, + &hidUsageConsumerVendor, + &hidPassReportId, + &hidLogicalMinimum0, + &hidLogicalMaximum2, + &hidReportSize, + &hidPassReportCount2, + &hidInputDataVar, + &hidUsageVendor, + &hidLogicalMinimum0, + &hidLogicalMaximum2, + &hidReportSize, + &hidPassReportCount, + &hidOutputDataVar, + &hidUsageVendor2, + &hidReportSize, + &hidPassReportCount3, + &hidInputFeatureVar, + &hidCollectionEnd, + &(hidReportPageConsumer.item), + &hidUsageConsumerControl, + &hidCollectionApplication, + &hidButtonReportId, + &hidLogicalMinimum0, + &hidLogicalMaximum1, + &(hidUsageByte0Bit0.item), + &(hidUsageByte0Bit1.item), + &(hidUsageByte0Bit2.item), + &(hidUsageByte0Bit3.item), + &(hidUsageByte0Bit4.item), + &(hidUsageByte0Bit5.item), + &(hidUsageByte0Bit6.item), + &(hidUsageByte0Bit7.item), + &hidReportSize1, + &hidReportCount8, + &hidInputDataVar, + &hidReportCount2, + &hidInputConstArray, + &hidCollectionEnd, +}; + +/* + * Define the number of HID Reports + * Due to XC not supporting designated initializers, this constant has a hard-coded value. + * It must equal ( sizeof hidReports / sizeof ( USB_HID_Report_Element_t* )) + */ +#define HID_REPORT_COUNT ( 2 ) + +#endif // __hid_report_descriptor_h__ diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hidbuttons.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hidbuttons.xc new file mode 100644 index 0000000..538715a --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hidbuttons.xc @@ -0,0 +1,153 @@ +#define DEBUG_PRINT_ENABLE 1 +#include +#include +#include "xua_conf.h" + +#include "user_hid.h" +#include "xua_hid_report.h" + +#include "xc_ptr.h" + +#include "xud.h" +#include "hid.h" +#include "debug_print.h" + +#if HID_CONTROLS > 0 + +#ifdef XSCOPE +#include "print.h" +#endif + +#ifdef DFU_CONTROL_USB_HID +#include + +#define HID_DFU_REPORT_ID 0x41 +#endif + +#define P_GPI_BUTA_SHIFT 0x00 +#define P_GPI_BUTA_MASK (1<= 64) ? 64 : (debug_memory_log_buffer_index - log_index); + if (size > 0) + { + for (int i = 0; i < size; i++) + { + hidLogData[i] = logPtr[log_index + i]; + } + + log_index += size; + + if (log_index >= debug_memory_log_buffer_index) + { + log_index = 0; + SET_SHARED_GLOBAL(debug_memory_log_buffer_index, 0); + } + } + else + { + for (int i = 0; i < 64; i++) + { + hidLogData[i] = 0; + } + + } + + } +} + +#endif + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/host_os_detect.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/host_os_detect.c new file mode 100644 index 0000000..a6f10d7 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/host_os_detect.c @@ -0,0 +1,102 @@ +#ifndef _HOST_OS_DETECT_H_ +#define _HOST_OS_DETECT_H_ + +#include "xua.h" +#include "xc_ptr.h" +#include "xassert.h" +#if XUA_USB_EN +#ifndef WINDOWS_OS_DESCRIPTOR_SUPPORT +#define WINDOWS_OS_DESCRIPTOR_SUPPORT +#endif +#define EP0_MAX_REQUEST_SIZE 2048 +#include "xud.h" +#include "vendorrequests.h" + +unsigned g_host_os = 0; // 1 -> Windows, 0 -> Others + + +int VendorAudioRequests(XUD_ep ep0_out, XUD_ep ep0_in, unsigned char bRequest, unsigned char cs, unsigned char cn, + unsigned short unitId, unsigned char direction, NULLABLE_RESOURCE(chanend, c_aud_ctl), + NULLABLE_RESOURCE(chanend, c_mix_ctl), + NULLABLE_RESOURCE(chanend, c_clk_ctL)) +{ + + return XUD_RES_ERR; +} +enum { OS_WIN = 1, OS_OTHERS = 2 }; +#if 1 +int VendorRequests(XUD_ep ep0_out, XUD_ep ep0_in, REFERENCE_PARAM(USB_SetupPacket_t, sp) VENDOR_REQUESTS_PARAMS_DEC_) +{ + XUD_Result_t result = XUD_RES_ERR; + + unsigned char request_data[EP0_MAX_REQUEST_SIZE]; + unsigned len; + int a = 0; + unsigned k = 5; +//SET_SHARED_GLOBAL(g_host_os, k); + switch ((sp->bmRequestType.Direction << 7) | (sp->bmRequestType.Type << 5) | (sp->bmRequestType.Recipient)) { + case USB_BMREQ_H2D_VENDOR_DEV: + result = XUD_GetBuffer(ep0_out, request_data, len); + if (result == XUD_RES_OKAY) { + if (a/*control_process_usb_set_request(sp.wIndex, sp.wValue, sp.wLength, request_data, i_control) == CONTROL_SUCCESS*/) { + /* zero length data to indicate success + * on control error, go to standard requests, which will issue STALL + */ + result = XUD_DoSetRequestStatus(ep0_in); + } else { + result = XUD_RES_ERR; + } + } + break; + + case USB_BMREQ_D2H_VENDOR_DEV: + /* application retrieval latency inside the control library call + * XUD task defers further calls by NAKing USB transactions + */ +// if (a/*control_process_usb_get_request(sp.wIndex, sp.wValue, sp.wLength, request_data, i_control) == CONTROL_SUCCESS*/) { +// len = sp->wLength; +// result = XUD_DoGetRequest(ep0_out, ep0_in, request_data, len, len); +// /* on control error, go to standard requests, which will issue STALL */ +// } +#ifdef WINDOWS_OS_DESCRIPTOR_SUPPORT +// else +if(sp->bRequest == 0x01) // GET_MS_DESCRIPTOR, same as the OS_STR_DESC last byte + { + // dwLength, 4bytes: 16 bytes + request_data[0] = 0x10; request_data[1] = 0x00; request_data[2] = 0x00; request_data[3] = 0x00; + // bcdVersion, 2bytes: For version 1.00, bcdVersion is set to 0x0100. + request_data[4] = 0x00; request_data[5] = 0x01; + // wIndex, 2bytes: 0x04 for extended compat ID descriptors + request_data[6] = 0x04; request_data[7] = 0x00; + // bCount, 1byte: 0 + request_data[8] = 0x00; + // RESERVED, 7bytes: all 0 + request_data[9] = 0x00; request_data[10] = 0x00; request_data[11] = 0x00; + request_data[12] = 0x00; request_data[13] = 0x00; request_data[14] = 0x00; request_data[15] = 0x00; + + result = XUD_DoGetRequest(ep0_out, ep0_in, request_data, 16, sp->wLength); +//xassert(busSpeed == XUD_SPEED_HS); + k = OS_WIN; + SET_SHARED_GLOBAL(g_host_os, k); + } + + else { +//xassert(busSpeed == XUD_SPEED_FS); + k = OS_OTHERS; + SET_SHARED_GLOBAL(g_host_os, k); + result = XUD_RES_ERR; + } +#endif + break; + } + return result; +} +#endif +void VendorRequests_Init(VENDOR_REQUESTS_PARAMS_DEC) +{ + +} + +#endif /* XUA_USB_EN */ + +#endif \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hostactive.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hostactive.xc new file mode 100644 index 0000000..3f1ff9b --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/hostactive.xc @@ -0,0 +1,21 @@ +#include + +/* This is provided as simple example but disabled due to the port clash with audiostream.xc */ + +#if 0 +on tile[0]: out port p_leds = XS1_PORT_4F; + +void UserHostActive(int active) +{ + if(active) + { + /* Turn all LEDs on */ + p_leds <: 0xF; + } + else + { + /* Turn all LEDs off */ + p_leds <: 0x0; + } +} +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.h new file mode 100644 index 0000000..b6c51a4 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.h @@ -0,0 +1,203 @@ +/** + * @file htr3236.h + * @brief HTR3236 36-Channel LED PWM Driver Driver for XMOS Platform + * @version 1.1 + */ + +#ifndef HTR3236_H +#define HTR3236_H + +#include +#include +#include + +/*========================================================================= + I2C 寄存器地址定义 + -----------------------------------------------------------------------*/ +#define HTR3236_REG_SHUTDOWN 0x00 // 软件关断寄存器 +#define HTR3236_REG_PWM_START 0x01 // PWM 寄存器起始地址 (OUT1) +#define HTR3236_REG_PWM_END 0x24 // PWM 寄存器结束地址 (OUT36) +#define HTR3236_REG_PWM_UPDATE 0x25 // PWM 更新寄存器 +#define HTR3236_REG_LED_CTRL_START 0x26 // LED 控制寄存器起始地址 (OUT1) +#define HTR3236_REG_LED_CTRL_END 0x49 // LED 控制寄存器结束地址 (OUT36) +#define HTR3236_REG_GLOBAL_CTRL 0x4A // 全局控制寄存器 +#define HTR3236_REG_FREQ_SET 0x4B // 频率设置寄存器 +#define HTR3236_REG_RESET 0x4F // 复位寄存器 + +/*========================================================================= + 寄存器位定义 + -----------------------------------------------------------------------*/ +// 关断寄存器 (00h) +#define HTR3236_SHUTDOWN_MASK 0x01 +#define HTR3236_SHUTDOWN_SOFT 0x00 +#define HTR3236_NORMAL_OP 0x01 + +// LED 控制寄存器位 (26h~49h) +#define HTR3236_LED_OUT_MASK 0x01 +#define HTR3236_LED_CURRENT_SHIFT 1 +#define HTR3236_LED_CURRENT_MASK (0x03 << 1) + +// 全局控制寄存器 (4Ah) +#define HTR3236_GLOBAL_EN_MASK 0x01 + +// 频率设置寄存器 (4Bh) +// #define HTR3236_FREQ_3KHZ 0x00 +// #define HTR3236_FREQ_22KHZ 0x01 +// #define HTR3236_FREQ_MASK 0x01 + +/*========================================================================= + 数据类型定义 + -----------------------------------------------------------------------*/ +typedef enum { + HTR3236_CURRENT_FULL = 0, // IMAX + HTR3236_CURRENT_HALF = 1, // IMAX/2 + HTR3236_CURRENT_THIRD = 2, // IMAX/3 + HTR3236_CURRENT_QUARTER = 3 // IMAX/4 +} htr3236_current_t; + +typedef enum { + HTR3236_FREQ_3KHZ = 0, + HTR3236_FREQ_22KHZ = 1 +} htr3236_freq_t; + +typedef enum { + HTR3236_ADDR_GND = 0x3C, // AD = GND: 8位地址 0x78 + HTR3236_ADDR_SCL = 0x3D, // AD = SCL: 8位地址 0x7A + HTR3236_ADDR_SDA = 0x3E, // AD = SDA: 8位地址 0x7C + HTR3236_ADDR_VCC = 0x3F // AD = VCC: 8位地址 0x7E +} htr3236_addr_t; + +/** + * @brief HTR3236 设备结构体 + */ +typedef struct { + uint8_t i2c_addr; // I2C 设备地址 +} htr3236_t; + +/*========================================================================= + API 函数声明 + -----------------------------------------------------------------------*/ + +/** + * @brief 初始化 HTR3236 设备 + * @param dev 设备结构体指针 + * @param i2c I2C 主控接口 + * @param addr I2C 设备地址 + * @param sdb_port SDB 控制端口 + * @param sdb_pin SDB 引脚号 + */ +void htr3236_init(htr3236_t *dev, uint8_t addr); + +/** + * @brief 硬件使能 (SDB 拉高) + * @param dev 设备结构体指针 + */ +void htr3236_hw_enable(htr3236_t *dev); + +/** + * @brief 硬件关断 (SDB 拉低) + * @param dev 设备结构体指针 + */ +void htr3236_hw_disable(htr3236_t *dev); + +/** + * @brief 软件唤醒 (正常模式) + * @param dev 设备结构体指针 + * @return 0:成功, -1:失败 + */ +int htr3236_software_wake(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)); + +/** + * @brief 软件关断 + * @param dev 设备结构体指针 + * @return 0:成功, -1:失败 + */ +int htr3236_software_shutdown(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)); + +/** + * @brief 软件复位所有寄存器 + * @param dev 设备结构体指针 + * @return 0:成功, -1:失败 + */ +int htr3236_software_reset(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)); + +/** + * @brief 设置单路 PWM 亮度 + * @param dev 设备结构体指针 + * @param channel 通道号 (1-36) + * @param brightness 亮度值 (0-255) + * @return 0:成功, -1:失败 + */ +int htr3236_set_pwm(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t channel, uint8_t brightness); + +/** + * @brief 批量设置多路 PWM 亮度 (使用自动地址递增) + * @param dev 设备结构体指针 + * @param start_ch 起始通道号 (1-36) + * @param values 亮度值数组 + * @param len 通道数量 + * @return 0:成功, -1:失败 + */ +int htr3236_set_pwm_bulk(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t start_ch, + const uint8_t *values, uint8_t len); + +/** + * @brief 设置单路 LED 电流档位和使能状态 + * @param dev 设备结构体指针 + * @param channel 通道号 (1-36) + * @param current 电流档位 + * @param enable LED 使能 (1:开启, 0:关闭) + * @return 0:成功, -1:失败 + */ +int htr3236_set_led_config(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t channel, + htr3236_current_t current, uint8_t enable); + +/** + * @brief 批量设置多路 LED 配置 + * @param dev 设备结构体指针 + * @param start_ch 起始通道号 (1-36) + * @param configs 配置数据数组 (每个字节: bit0=使能, bit2-1=电流档位) + * @param len 通道数量 + * @return 0:成功, -1:失败 + */ +int htr3236_set_led_config_bulk(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t start_ch, + const uint8_t *configs, uint8_t len); + +/** + * @brief 更新 PWM 和 LED 配置 (写入 25h 寄存器) + * @param dev 设备结构体指针 + * @return 0:成功, -1:失败 + */ +int htr3236_update(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)); + +/** + * @brief 全局 LED 使能控制 + * @param dev 设备结构体指针 + * @param enable 1:关闭所有LED, 0:正常模式 + * @return 0:成功, -1:失败 + */ +int htr3236_global_enable(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t enable); + +/** + * @brief 设置 PWM 频率 + * @param dev 设备结构体指针 + * @param freq 频率 (0:3kHz, 1:22kHz) + * @return 0:成功, -1:失败 + */ +int htr3236_set_frequency(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), htr3236_freq_t freq); + +/** + * @brief Gamma 校正查找表 (32级) + * @param index Gamma 索引 (0-31) + * @return PWM 值 (0-255) + */ +uint8_t htr3236_gamma_32(uint8_t index); + +/** + * @brief Gamma 校正查找表 (64级) + * @param index Gamma 索引 (0-63) + * @return PWM 值 (0-255) + */ +uint8_t htr3236_gamma_64(uint8_t index); + +#endif /* HTR3236_H */ \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.xc new file mode 100644 index 0000000..3af71b4 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/htr3236.xc @@ -0,0 +1,252 @@ +/** + * @file htr3236.c + * @brief HTR3236 36-Channel LED PWM Driver Implementation for XMOS + * @version 1.1 + */ + +#include "htr3236.h" +#include + +#include + +out port p_htr3235_sdb = PORT_HTR3236_SDB; /* 连接到HTR3236的SDB引脚,用于控制其电源状态 */ + +/*========================================================================= + Gamma 校正查找表 + -----------------------------------------------------------------------*/ + +static const uint8_t gamma_table_32[32] = { + 0, 1, 2, 4, 6, 10, 13, 18, + 22, 28, 33, 39, 46, 53, 61, 69, + 78, 86, 96, 106, 116, 126, 138, 149, + 161, 173, 186, 199, 212, 226, 240, 255 +}; + +static const uint8_t gamma_table_64[64] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 10, 12, 14, 16, 18, 20, 22, + 24, 26, 29, 32, 35, 38, 41, 44, + 47, 50, 53, 57, 61, 65, 69, 73, + 77, 81, 85, 89, 94, 99, 104, 109, + 114, 119, 124, 129, 134, 140, 146, 152, + 158, 164, 170, 176, 182, 188, 195, 202, + 209, 216, 223, 230, 237, 244, 251, 255 +}; + +/*========================================================================= + 内部函数 + -----------------------------------------------------------------------*/ + +/** + * @brief 写单字节到寄存器 + */ +static int write_reg(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t reg, uint8_t data) +{ + uint8_t buf[2] = {reg, data}; + size_t n; + + i2c.write(dev->i2c_addr, buf, 2, n, 1); + + if (n == 0) + { + return I2C_REGOP_DEVICE_NACK; + } + if (n < 2) + { + return I2C_REGOP_INCOMPLETE; + } + + return I2C_REGOP_SUCCESS; +} + +/** + * @brief 写多字节到寄存器 (支持自动地址递增) + */ +static int write_reg_bulk(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t start_reg, + const uint8_t *data, uint8_t len) +{ + uint8_t buf[36 + 1]; + size_t n; + + buf[0] = start_reg; + + for (int i = 0; i < len; i++) { + buf[i + 1] = data[i]; + } + + i2c.write(dev->i2c_addr, buf, len + 1, n, 1); + + if (n == 0) + { + return I2C_REGOP_DEVICE_NACK; + } + if (n < 2) + { + return I2C_REGOP_INCOMPLETE; + } + + return I2C_REGOP_SUCCESS; +} + +/** + * @brief 读取寄存器值 + */ +static int read_reg(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t reg, i2c_regop_res_t &result) +{ + uint8_t a_reg[1] = {reg}; + uint8_t data[1] = {0}; + size_t n; + i2c_res_t res; + + unsafe + { + res = i2c.write(dev->i2c_addr, a_reg, 1, n, 0); + + if (n != 1) + { + result = I2C_REGOP_DEVICE_NACK; + i2c.send_stop_bit(); + return 0; + } + + res = i2c.read(dev->i2c_addr, data, 1, 1); + } + + if (res == I2C_ACK) + { + result = I2C_REGOP_SUCCESS; + } + else + { + result = I2C_REGOP_DEVICE_NACK; + } + return data[0]; +} + + + +/*========================================================================= + API 函数实现 + -----------------------------------------------------------------------*/ + +void htr3236_init(htr3236_t *dev, uint8_t addr) +{ + dev->i2c_addr = addr; + + // 默认 SDB 拉高,使能芯片 + //htr3236_hw_enable(dev); +} + +void htr3236_hw_enable(htr3236_t *dev) +{ + p_htr3235_sdb <: 1; +} + +void htr3236_hw_disable(htr3236_t *dev) +{ + p_htr3235_sdb <: 0; +} + +int htr3236_software_wake(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)) +{ + return write_reg(dev, i2c, HTR3236_REG_SHUTDOWN, HTR3236_NORMAL_OP); +} + +int htr3236_software_shutdown(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)) +{ + return write_reg(dev, i2c, HTR3236_REG_SHUTDOWN, HTR3236_SHUTDOWN_SOFT); +} + +int htr3236_software_reset(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)) +{ + return write_reg(dev, i2c, HTR3236_REG_RESET, 0x00); +} + +int htr3236_set_pwm(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t channel, uint8_t brightness) +{ + if (channel < 1 || channel > 36) { + return -1; + } + + uint8_t reg = HTR3236_REG_PWM_START + (channel - 1); + return write_reg(dev, i2c, reg, brightness); +} + +int htr3236_set_pwm_bulk(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t start_ch, + const uint8_t *values, uint8_t len) +{ + if (start_ch < 1 || start_ch > 36 || len == 0) { + return -1; + } + + // 检查是否会超出寄存器范围 + if (start_ch + len - 1 > 36) { + return -1; + } + + uint8_t start_reg = HTR3236_REG_PWM_START + (start_ch - 1); + return write_reg_bulk(dev, i2c, start_reg, values, len); +} + +int htr3236_set_led_config(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t channel, + htr3236_current_t current, uint8_t enable) +{ + if (channel < 1 || channel > 36) { + return -1; + } + + uint8_t reg = HTR3236_REG_LED_CTRL_START + (channel - 1); + uint8_t data = (enable ? 1 : 0) | (current << 1); + + return write_reg(dev, i2c, reg, data); +} + +int htr3236_set_led_config_bulk(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t start_ch, + const uint8_t *configs, uint8_t len) +{ + if (start_ch < 1 || start_ch > 36 || len == 0) { + return -1; + } + + // 检查是否会超出寄存器范围 + if (start_ch + len - 1 > 36) { + return -1; + } + + uint8_t start_reg = HTR3236_REG_LED_CTRL_START + (start_ch - 1); + return write_reg_bulk(dev, i2c, start_reg, configs, len); +} + +int htr3236_update(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c)) +{ + // 写入任意值到更新寄存器使配置生效 + return write_reg(dev, i2c, HTR3236_REG_PWM_UPDATE, 0x00); +} + +int htr3236_global_enable(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), uint8_t enable) +{ + uint8_t data = enable ? 1 : 0; + return write_reg(dev, i2c, HTR3236_REG_GLOBAL_CTRL, data); +} + +int htr3236_set_frequency(htr3236_t *dev, CLIENT_INTERFACE(i2c_master_if, i2c), htr3236_freq_t freq) +{ + uint8_t data = (freq == HTR3236_FREQ_22KHZ) ? 1 : 0; + return write_reg(dev, i2c, HTR3236_REG_FREQ_SET, data); +} + +uint8_t htr3236_gamma_32(uint8_t index) +{ + if (index >= 32) { + index = 31; + } + return gamma_table_32[index]; +} + +uint8_t htr3236_gamma_64(uint8_t index) +{ + if (index >= 64) { + index = 63; + } + return gamma_table_64[index]; +} \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/keys.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/keys.h new file mode 100644 index 0000000..ea164ce --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/keys.h @@ -0,0 +1,17 @@ +// Copyright 2011-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef __KEYS_H__ +#define __KEYS_H__ + + +#define KEY_MIC_VOL_DN 0x08 +#define KEY_MUTE 0x10 +#define KEY_MIC_VOL_UP 0x20 +#define KEY_PLAY_VOL_DN 0x40 +#define KEY_PLAY_VOL_UP 0x80 +#define KEY_BITS (KEY_MIC_VOL_DN | KEY_MUTE | KEY_MIC_VOL_UP | KEY_PLAY_VOL_DN | KEY_PLAY_VOL_UP) + +#define MUTED_MIC 0x5A +#define UNMUTED_MIC 0xA5 + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.c new file mode 100644 index 0000000..cee3a98 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.c @@ -0,0 +1,265 @@ +/** + * @file led_control.c + * @brief LED控制API实现 (基于HTR3236) + * @version 1.0 + */ + +#include "led_control.h" +#include +#include +#include + +/*========================================================================= + 内部函数 + -----------------------------------------------------------------------*/ + +/** + * @brief 检查LED ID是否有效 + */ +static int is_valid_led(led_id_t led) +{ + return (led >= 1 && led <= 36 && led != 36); // OUT36未使用 +} + +/** + * @brief 获取LED组起始和结束索引 + */ +static void get_group_range(led_group_t group, uint8_t *start, uint8_t *end) +{ + switch (group) { + case LED_GROUP_L_SERIES: + *start = LED_L1; + *end = LED_L15; + break; + case LED_GROUP_D_SERIES: + *start = LED_D1; + *end = LED_D15; + break; + // case LED_GROUP_FUNCTION: + // *start = LED_ANC; + // *end = LED_GAME_MODE; + // break; + case LED_GROUP_ALL: + default: + *start = 1; + *end = 35; // 排除OUT36 + break; + } +} + +/*========================================================================= + LED物理映射表 + -----------------------------------------------------------------------*/ +// 根据测试结果:LED_L1-L15的实际OUT口 +const uint8_t led_l_physical_map[15] = { + 15, // LED_L1 -> OUT15 + 14, // LED_L2 -> OUT14 + 13, // LED_L3 -> OUT13 + 11, // LED_L4 -> OUT11 + 10, // LED_L5 -> OUT10 + 9, // LED_L6 -> OUT9 + 8, // LED_L7 -> OUT8 + 7, // LED_L8 -> OUT7 + 6, // LED_L9 -> OUT6 + 5, // LED_L10 -> OUT5 + 4, // LED_L11 -> OUT4 + 12, // LED_L12 -> OUT12 * + 18, // LED_L13 -> OUT18 * + 17, // LED_L14 -> OUT17 + 16, // LED_L15 -> OUT16 +}; +// 根据测试结果:LED_D1-D15的实际OUT口 +const uint8_t led_d_physical_map[15] = { + 21, // LED_D1 -> OUT21 + 20, // LED_D2 -> OUT20 + 19, // LED_D3 -> OUT19 + 26, // LED_D4 -> OUT26 * + 33, // LED_D5 -> OUT33 * + 32, // LED_D6 -> OUT32 + 31, // LED_D7 -> OUT31 + 30, // LED_D8 -> OUT30 + 29, // LED_D9 -> OUT29 + 28, // LED_D10 -> OUT28 + 27, // LED_D11 -> OUT27 + 25, // LED_D12 -> OUT25 + 24, // LED_D13 -> OUT24 + 23, // LED_D14 -> OUT23 + 22, // LED_D15 -> OUT22 +}; + +const uint8_t mic_gain_to_led[39] = { + 0, // [-1] 增益-1: 0个LED,这个设计未启用 + 0, // [0] 增益0: 0个LED + 0, // [1] 增益1: 0个LED + 0, // [2] 增益2: 0个LED + 1, // [3] 增益3: 1个LED + 1, // [4] 增益4: 1个LED + 1, // [5] 增益5: 1个LED + 2, // [6] 增益6: 2个LED + 2, // [7] 增益7: 2个LED + 2, // [8] 增益8: 2个LED + 3, // [9] 增益9: 3个LED + 3, // [10] 增益10: 3个LED + 3, // [11] 增益11: 3个LED + 4, // [12] 增益12: 4个LED + 4, // [13] 增益13: 4个LED + 4, // [14] 增益14: 4个LED + 5, // [15] 增益15: 5个LED + 5, // [16] 增益16: 5个LED + 6, // [17] 增益17: 6个LED + 6, // [18] 增益18: 6个LED + 7, // [19] 增益19: 7个LED + 7, // [20] 增益20: 7个LED + 8, // [21] 增益21: 8个LED + 8, // [22] 增益22: 8个LED + 9, // [23] 增益23: 9个LED + 9, // [24] 增益24: 9个LED + 10, // [25] 增益25: 10个LED + 10, // [26] 增益26: 10个LED + 11, // [27] 增益27: 11个LED + 11, // [28] 增益28: 11个LED + 12, // [29] 增益29: 12个LED + 12, // [30] 增益30: 12个LED + 13, // [31] 增益31: 13个LED + 13, // [32] 增益32: 13个LED + 14, // [33] 增益33: 14个LED + 14, // [34] 增益34: 14个LED + 15, // [35] 增益35: 15个LED + 15, // [36] 增益36: 15个LED + 15 // [37] 增益36+1.5 digitial gain: 15个LED +}; + +/*========================================================================= + 内部函数 - 获取实际OUT口 + -----------------------------------------------------------------------*/ +static uint8_t get_physical_out(led_id_t led) +{ + // 如果是L系列LED + if (led >= LED_L1 && led <= LED_L15) { + uint8_t index = led - LED_L1; + return led_l_physical_map[index]; + } + else if (led >= LED_D1 && led <= LED_D15) { + uint8_t index = led - LED_D1; + return led_d_physical_map[index]; + } + + // 其他LED直接返回原值 + return (uint8_t)led; +} + +/*========================================================================= + 基础控制API实现 + -----------------------------------------------------------------------*/ + +void led_control_init(led_control_t *ctx, + htr3236_t *dev, + CLIENT_INTERFACE(i2c_master_if,i2c)) +{ + ctx->htr3236_dev = dev; + ctx->i2c = i2c; + + // 初始化亮度缓存 + memset(ctx->brightness, 0, sizeof(ctx->brightness)); + + // 配置所有LED通道的电流 (使用IMAX/2) + for (int led = 1; led <= 35; led++) { // 跳过OUT36 + htr3236_set_led_config(ctx->htr3236_dev, ctx->i2c, + led, HTR3236_CURRENT_HALF, 1); + } + + // 初始关闭所有LED + for (int led = 1; led <= 35; led++) { + htr3236_set_pwm(ctx->htr3236_dev, ctx->i2c, led, 0); + } + + htr3236_update(ctx->htr3236_dev, ctx->i2c); +} + +int led_set_brightness(led_control_t *ctx, + led_id_t led, + uint8_t brightness) +{ + if (!is_valid_led(led)) { + return -1; + } + + ctx->brightness[led - 1] = brightness; + return htr3236_set_pwm(ctx->htr3236_dev, ctx->i2c, led, brightness); +} + +int led_on(led_control_t *ctx, led_id_t led) +{ + return led_set_brightness(ctx, led, 1/*255*/); +} + +int led_off(led_control_t *ctx, led_id_t led) +{ + return led_set_brightness(ctx, led, 0); +} + +int led_toggle(led_control_t *ctx, led_id_t led) +{ + if (!is_valid_led(led)) { + return -1; + } + + uint8_t new_brightness = (ctx->brightness[led - 1] == 0) ? 255 : 0; + return led_set_brightness(ctx, led, new_brightness); +} + +int led_update_all(led_control_t *ctx) +{ + return htr3236_update(ctx->htr3236_dev, ctx->i2c); +} + +/*========================================================================= + 组控制API实现 + -----------------------------------------------------------------------*/ + +int led_group_set_brightness(led_control_t *ctx, + led_group_t group, + uint8_t brightness) +{ + uint8_t start, end; + get_group_range(group, &start, &end); + + if (group == LED_GROUP_FUNCTION) { + // 功能LED需要单独处理每个LED + led_id_t function_leds[] = { + LED_MUSIC, LED_ANC, LED_FOOTSTEP_MODE, LED_AI7_1, LED_GAME_MODE + }; + + for (int i = 0; i < 5; i++) { + led_id_t led = function_leds[i]; + uint8_t physical_out = get_physical_out(led); // 如果有映射 + ctx->brightness[physical_out - 1] = brightness; + htr3236_set_pwm(ctx->htr3236_dev, ctx->i2c, led, brightness); + } + } + else + { + for (uint8_t led = start; led <= end; led++) { + if (led == 36) continue; // 跳过未使用的OUT36 + if((group == LED_GROUP_L_SERIES) || (group == LED_GROUP_D_SERIES)) + { + uint8_t physical_out = get_physical_out(led); + ctx->brightness[physical_out - 1] = brightness; + } + else ctx->brightness[led - 1] = brightness; + htr3236_set_pwm(ctx->htr3236_dev, ctx->i2c, led, brightness); + } + } + + return htr3236_update(ctx->htr3236_dev, ctx->i2c); +} + +int led_group_on(led_control_t *ctx, led_group_t group) +{ + return led_group_set_brightness(ctx, group, 255); +} + +int led_group_off(led_control_t *ctx, led_group_t group) +{ + return led_group_set_brightness(ctx, group, 0); +} \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.h new file mode 100644 index 0000000..598644d --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/led_control.h @@ -0,0 +1,161 @@ +/** + * @file led_control.h + * @brief LED控制API (基于HTR3236) + * @version 1.0 + */ + +#ifndef LED_CONTROL_H +#define LED_CONTROL_H + +#include +#include "htr3236.h" + +#ifdef __XC__ +extern "C" { +#endif + +/*========================================================================= + LED类型定义 + -----------------------------------------------------------------------*/ +typedef enum { + // 功能LED (单个LED) + LED_MUSIC = 2, // OUT2 - 音乐模式 + LED_ANC = 1, // OUT1 - ANC模式 + LED_FOOTSTEP_MODE = 3, // OUT3 - 脚步模式 + LED_AI7_1 = 34, // OUT34 - AI7.1模式 + LED_GAME_MODE = 35, // OUT35 - 游戏模式 + // OUT36未使用 + + // L系列LED (OUT4-OUT18) + LED_L1 = 4, LED_L2 = 5, LED_L3 = 6, LED_L4 = 7, LED_L5 = 8, + LED_L6 = 9, LED_L7 = 10, LED_L8 = 11, LED_L9 = 12, LED_L10 = 13, + LED_L11 = 14, LED_L12 = 15, LED_L13 = 16, LED_L14 = 17, LED_L15 = 18, + + // D系列LED (OUT19-OUT33) + LED_D1 = 19, LED_D2 = 20, LED_D3 = 21, LED_D4 = 22, LED_D5 = 23, + LED_D6 = 24, LED_D7 = 25, LED_D8 = 26, LED_D9 = 27, LED_D10 = 28, + LED_D11 = 29, LED_D12 = 30, LED_D13 = 31, LED_D14 = 32, LED_D15 = 33, + + LED_MAX = 36 +} led_id_t; + +/*========================================================================= + LED组定义 + -----------------------------------------------------------------------*/ +typedef enum { + LED_GROUP_L_SERIES, // L1-L15 + LED_GROUP_D_SERIES, // D1-D15 + LED_GROUP_FUNCTION, // 功能LED (ANC, MUSIC, FOOTSTEP, AI7_7, GAME) + LED_GROUP_ALL // 所有LED +} led_group_t; + +/*========================================================================= + LED控制上下文 + -----------------------------------------------------------------------*/ +typedef struct { + htr3236_t *htr3236_dev; // HTR3236设备指针 + CLIENT_INTERFACE(i2c_master_if,i2c); // I2C接口 + uint8_t brightness[36]; // 当前亮度缓存 +} led_control_t; + +/*========================================================================= + LED物理映射表 + -----------------------------------------------------------------------*/ +// 逻辑LED编号到物理OUT口的映射表 +extern const uint8_t led_l_physical_map[15]; +extern const uint8_t led_d_physical_map[15]; + +extern const uint8_t mic_gain_to_led[39]; + +/*========================================================================= + 基础控制API + -----------------------------------------------------------------------*/ + +/** + * @brief 初始化LED控制系统 + * @param ctx 上下文指针 + * @param dev HTR3236设备指针 + * @param i2c I2C接口 + */ +void led_control_init(led_control_t *ctx, + htr3236_t *dev, + CLIENT_INTERFACE(i2c_master_if,i2c)); + +/** + * @brief 设置单个LED亮度 + * @param ctx 上下文指针 + * @param led LED ID + * @param brightness 亮度值 (0-255) + * @return 0:成功, -1:失败 + */ +int led_set_brightness(led_control_t *ctx, + led_id_t led, + uint8_t brightness); + +/** + * @brief 开启单个LED (最大亮度) + * @param ctx 上下文指针 + * @param led LED ID + * @return 0:成功, -1:失败 + */ +int led_on(led_control_t *ctx, led_id_t led); + +/** + * @brief 关闭单个LED + * @param ctx 上下文指针 + * @param led LED ID + * @return 0:成功, -1:失败 + */ +int led_off(led_control_t *ctx, led_id_t led); + +/** + * @brief 切换LED状态 + * @param ctx 上下文指针 + * @param led LED ID + * @return 0:成功, -1:失败 + */ +int led_toggle(led_control_t *ctx, led_id_t led); + +/** + * @brief 更新所有LED显示 (调用htr3236_update) + * @param ctx 上下文指针 + * @return 0:成功, -1:失败 + */ +int led_update_all(led_control_t *ctx); + +/*========================================================================= + 组控制API + -----------------------------------------------------------------------*/ + +/** + * @brief 设置一组LED的亮度 + * @param ctx 上下文指针 + * @param group LED组 + * @param brightness 亮度值 (0-255) + * @return 0:成功, -1:失败 + */ +int led_group_set_brightness(led_control_t *ctx, + led_group_t group, + uint8_t brightness); + +/** + * @brief 开启一组LED + * @param ctx 上下文指针 + * @param group LED组 + * @return 0:成功, -1:失败 + */ +int led_group_on(led_control_t *ctx, led_group_t group); + +/** + * @brief 关闭一组LED + * @param ctx 上下文指针 + * @param group LED组 + * @return 0:成功, -1:失败 + */ +int led_group_off(led_control_t *ctx, led_group_t group); + +#ifdef __XC__ +} +#endif + +#endif /* LED_CONTROL_H */ \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_io.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_io.h new file mode 100644 index 0000000..3939463 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_io.h @@ -0,0 +1,13 @@ +#ifndef _LFS_IO_H_ +#define _LFS_IO_H_ +int lfs_init(void); +void lfs_deinit(void); +void lfs_read_config(unsigned char * config, unsigned char * buffer, unsigned size); +void lfs_write_config(unsigned char * config, unsigned char * buffer, unsigned size); +// EQ参数专用函数 +void lfs_read_eq_config(const char * file_path, unsigned char * buffer, unsigned size); +void lfs_write_eq_config(const char * file_path, unsigned char * buffer, unsigned size); +int lfs_file_exists(const char * file_path); +int lfs_remove_file(const char * file_path); +int lfs_create_directory(const char * dir_path); +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_services.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_services.c new file mode 100644 index 0000000..4f91cca --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/lfs_services.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include "lfs.h" +#include "rtos_qspi_flash.h" +#include "swlock.h" +#include "debug_print.h" +// variables used by the filesystem +lfs_t lfs; +lfs_file_t file; +swlock_t lfs_lock = SWLOCK_INITIAL_VALUE; + +static rtos_qspi_flash_t qspi_flash_ctx_s; +#define FLASH_CLKBLK XS1_CLKBLK_3 +#ifndef FS_BASE_ADDR +#define FS_BASE_ADDR 0x1a0000 +#endif +#define SECTOR_SIZE 4096 +rtos_qspi_flash_t *qspi_flash_ctx = &qspi_flash_ctx_s; + +__attribute__((fptrgroup(" local_block_device_read_fptr_grp"))) +int local_block_device_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) +{ + unsigned address = (FS_BASE_ADDR + block * SECTOR_SIZE + off); + qspi_flash_ctx->read(qspi_flash_ctx, buffer, address, size); + return 0; +} + +__attribute__((fptrgroup(" local_block_device_prog_fptr_grp"))) +int local_block_device_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) +{ + unsigned address = (FS_BASE_ADDR + block * SECTOR_SIZE + off); + qspi_flash_ctx->write(qspi_flash_ctx, buffer, address, size); + return 0; +} + +__attribute__((fptrgroup(" local_block_device_erase_fptr_grp"))) +int local_block_device_erase(const struct lfs_config *c, lfs_block_t block) +{ + unsigned address = (FS_BASE_ADDR + block * SECTOR_SIZE); + qspi_flash_ctx->erase(qspi_flash_ctx, address, SECTOR_SIZE); + return 0; +} + +__attribute__((fptrgroup(" local_block_device_sync_fptr_grp"))) +int local_block_device_sync(const struct lfs_config *c) +{ + return 0; +} + +// configuration of the filesystem is provided by this struct +const struct lfs_config cfg = { + // block device operations + .read = local_block_device_read, + .prog = local_block_device_prog, + .erase = local_block_device_erase, + .sync = local_block_device_sync, + + // block device configuration + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .cache_size = 16, + .lookahead_size = 16, + .block_cycles = 500, +}; + +int lfs_init(void) { + swlock_acquire(&lfs_lock); + rtos_qspi_flash_init( + qspi_flash_ctx, + FLASH_CLKBLK, + XS1_PORT_1B, + XS1_PORT_1C, + XS1_PORT_4B, + NULL); + + + // mount the filesystem + int err = lfs_mount(&lfs, &cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err) { + debug_printf("no lfs partiton is found, formating ...\n"); + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + swlock_release(&lfs_lock); + return -1; + } + swlock_release(&lfs_lock); + return 0; + +} + +void lfs_deinit(void) { + swlock_acquire(&lfs_lock); + lfs_unmount(&lfs); + swlock_release(&lfs_lock); +} + +#pragma stackfunction 1300 +void lfs_read_config(unsigned char * config, unsigned char * buffer, unsigned size) +{ + swlock_acquire(&lfs_lock); + debug_printf("lfs_read_config: %s, size: %d\n", config, size); + int result = lfs_file_open(&lfs, &file, config, LFS_O_RDWR | LFS_O_CREAT); + debug_printf("lfs_read_config: %s, result: %d\n", config, result); + if (result != 0) { + debug_printf("lfs_read_config: %s, open file failed\n", config); + swlock_release(&lfs_lock); + return; + } + debug_printf("lfs_read_config: %s, file opened\n", config); + result = lfs_file_read(&lfs, &file, buffer, size); + debug_printf("lfs_read_config: %s, result: %d\n", config, result); + if (result < 0) { + debug_printf("lfs_read_config: %s, read file failed, error: %d\n", config, result); + // Update: Added lfs_file_close to ensure file is closed even on error. + // Missing this caused subsequent open calls to assert/crash. + lfs_file_close(&lfs, &file); + swlock_release(&lfs_lock); + return; + } + lfs_file_close(&lfs, &file); + swlock_release(&lfs_lock); +} + + +#pragma stackfunction 1300 +void lfs_write_config(unsigned char * config, unsigned char * buffer, unsigned size) +{ + swlock_acquire(&lfs_lock); + debug_printf("lfs_write_config: %s, size: %d\n", config, size); + int result = lfs_file_open(&lfs, &file, config, LFS_O_RDWR | LFS_O_CREAT); + if (result != 0) { + debug_printf("lfs_write_config: open file failed\n"); + swlock_release(&lfs_lock); + return; + } + result = lfs_file_rewind(&lfs, &file); + if (result != 0) { + debug_printf("lfs_write_config: rewind file failed\n"); + // Update: Added lfs_file_close to prevent file remaining open if rewind fails. + lfs_file_close(&lfs, &file); + swlock_release(&lfs_lock); + return; + } + lfs_file_write(&lfs, &file, buffer, size); + lfs_file_close(&lfs, &file); + swlock_release(&lfs_lock); +} + + +// 检查文件是否存在 +int lfs_file_exists(const char * file_path) +{ + swlock_acquire(&lfs_lock); + lfs_file_t file; + int result = lfs_file_open(&lfs, &file, file_path, LFS_O_RDONLY); + if (result == 0) { + lfs_file_close(&lfs, &file); + swlock_release(&lfs_lock); + return 1; // 文件存在 + } + swlock_release(&lfs_lock); + return 0; // 文件不存在 +} + +// EQ参数专用读写函数 +void lfs_read_eq_config(const char * file_path, unsigned char * buffer, unsigned size) +{ + lfs_file_open(&lfs, &file, file_path, LFS_O_RDONLY); + lfs_file_read(&lfs, &file, buffer, size); + lfs_file_close(&lfs, &file); +} + +void lfs_write_eq_config(const char * file_path, unsigned char * buffer, unsigned size) +{ + lfs_file_open(&lfs, &file, file_path, LFS_O_RDWR | LFS_O_CREAT); + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, buffer, size); + lfs_file_close(&lfs, &file); +} + + +// 删除文件 +int lfs_remove_file(const char * file_path) +{ + return lfs_remove(&lfs, file_path); +} + +// 创建目录(通过创建临时文件然后删除) +int lfs_create_directory(const char * dir_path) +{ + char temp_file[256]; + snprintf(temp_file, sizeof(temp_file), "%s/.dir", dir_path); + + lfs_file_t file; + int result = lfs_file_open(&lfs, &file, temp_file, LFS_O_RDWR | LFS_O_CREAT); + if (result == 0) { + lfs_file_close(&lfs, &file); + lfs_remove(&lfs, temp_file); + return 0; // 成功 + } + return -1; // 失败 +} + +#if 0 + // read current count + uint32_t boot_count = 0; + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); + + // update boot count + boot_count += 1; + lfs_file_rewind(&lfs, &file); + for (int i = 0; i < 100; i++) + lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); + + // remember the storage is not updated until the file is closed successfully + lfs_file_close(&lfs, &file); + + // release any resources we were using + lfs_unmount(&lfs); + + // print the boot count + printf("boot_count: %d\n", boot_count); + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/main.xc b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/main.xc new file mode 100644 index 0000000..a556961 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/main.xc @@ -0,0 +1,733 @@ +// Copyright 2012-2024 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "xua.h" /* Device specific defines */ +#ifdef EXCLUDE_USB_AUDIO_MAIN + +/** + * @file main.xc + * @brief Top level for XMOS USB 2.0 Audio 2.0 Reference Designs. + * @author Ross Owen, XMOS Semiconductor Ltd + */ +#include +#include +#include +#include +#include +#ifdef XSCOPE +#include +#endif + +#if XUA_USB_EN +#include "xud_device.h" /* XMOS USB Device Layer defines and functions */ +#include "xua_endpoint0.h" +#endif + +#include "uac_hwresources.h" + +#ifdef IAP +#include "i2c_shared.h" +#include "iap.h" +#endif + +#if (XUA_SPDIF_RX_EN || XUA_SPDIF_TX_EN) +#include "spdif.h" /* From lib_spdif */ +#endif + +#if (XUA_ADAT_RX_EN) +#include "adat_rx.h" +#endif + +#if (XUA_NUM_PDM_MICS > 0) +#include "xua_pdm_mic.h" +#endif + +#if (XUA_DFU_EN == 1) +[[distributable]] +void DFUHandler(server interface i_dfu i, chanend ?c_user_cmd); +#endif + +/* Audio I/O - Port declarations */ +#if I2S_WIRES_DAC > 0 +on tile[AUDIO_IO_TILE] : buffered out port:32 p_i2s_dac[I2S_WIRES_DAC] = + {PORT_I2S_DAC0, +#endif +#if I2S_WIRES_DAC > 1 + PORT_I2S_DAC1, +#endif +#if I2S_WIRES_DAC > 2 + PORT_I2S_DAC2, +#endif +#if I2S_WIRES_DAC > 3 + PORT_I2S_DAC3, +#endif +#if I2S_WIRES_DAC > 4 + PORT_I2S_DAC4, +#endif +#if I2S_WIRES_DAC > 5 + PORT_I2S_DAC5, +#endif +#if I2S_WIRES_DAC > 6 + PORT_I2S_DAC6, +#endif +#if I2S_WIRES_DAC > 7 +#error I2S_WIRES_DAC value is too large! +#endif +#if I2S_WIRES_DAC > 0 + }; +#else + #define p_i2s_dac null +#endif + +#if I2S_WIRES_ADC > 0 +on tile[AUDIO_IO_TILE] : buffered in port:32 p_i2s_adc[I2S_WIRES_ADC] = + {PORT_I2S_ADC0, +#endif +#if I2S_WIRES_ADC > 1 + PORT_I2S_ADC1, +#endif +#if I2S_WIRES_ADC > 2 + PORT_I2S_ADC2, +#endif +#if I2S_WIRES_ADC > 3 + PORT_I2S_ADC3, +#endif +#if I2S_WIRES_ADC > 4 + PORT_I2S_ADC4, +#endif +#if I2S_WIRES_ADC > 5 + PORT_I2S_ADC5, +#endif +#if I2S_WIRES_ADC > 6 + PORT_I2S_ADC6, +#endif +#if I2S_WIRES_ADC > 7 +#error I2S_WIRES_ADC value is too large! +#endif +#if I2S_WIRES_ADC > 0 + }; +#else + #define p_i2s_adc null +#endif + + +#if CODEC_MASTER +on tile[AUDIO_IO_TILE] : buffered in port:32 p_lrclk = PORT_I2S_LRCLK; +on tile[AUDIO_IO_TILE] : buffered in port:32 p_bclk = PORT_I2S_BCLK; +#else +on tile[AUDIO_IO_TILE] : buffered out port:32 p_lrclk = PORT_I2S_LRCLK; +on tile[AUDIO_IO_TILE] : buffered out port:32 p_bclk = PORT_I2S_BCLK; +#endif + +#if (!CODEC_MASTER) || XUA_SPDIF_TX_EN || XUA_ADAT_TX_EN || ((AUDIO_IO_TILE == XUD_TILE) && XUA_USB_EN) +/* Audio master clock input */ +on tile[AUDIO_IO_TILE] : in port p_mclk_in = PORT_MCLK_IN; +#else +#define p_mclk_in null +#endif + +#if (AUDIO_IO_TILE != XUD_TILE) && XUA_USB_EN +/* If audio I/O and USB running on different tiles we need a separate port for + * the master clock input (to use for USB async feedback calculation) */ +on tile[XUD_TILE] : in port p_mclk_in_usb = PORT_MCLK_IN_USB; +#endif + +#if XUA_USB_EN +on tile[XUD_TILE] : in port p_for_mclk_count = PORT_MCLK_COUNT; +#endif + +#if (XUA_SPDIF_TX_EN) +on tile[SPDIF_TX_TILE] : buffered out port:32 p_spdif_tx = PORT_SPDIF_OUT; +#endif + +#if (XUA_ADAT_TX_EN) +on stdcore[AUDIO_IO_TILE] : buffered out port:32 p_adat_tx = PORT_ADAT_OUT; +#endif + +#if (XUA_ADAT_RX_EN) +on stdcore[XUD_TILE] : buffered in port:32 p_adat_rx = PORT_ADAT_IN; +#endif + +#if (XUA_SPDIF_RX_EN) +on tile[XUD_TILE] : in port p_spdif_rx = PORT_SPDIF_IN; +#endif + +#if (XUA_SPDIF_RX_EN) || (XUA_ADAT_RX_EN) || (XUA_SYNCMODE == XUA_SYNCMODE_SYNC) +/* Reference to external clock multiplier */ +on tile[PLL_REF_TILE] : out port p_pll_ref = PORT_PLL_REF; +#ifdef __XS3A__ +on tile[AUDIO_IO_TILE] : port p_for_mclk_count_audio = PORT_MCLK_COUNT_2; +#else /* __XS3A__ */ +#define p_for_mclk_count_audio null +#endif /* __XS3A__ */ +#endif + +#ifdef MIDI +on tile[MIDI_TILE] : port p_midi_tx = PORT_MIDI_OUT; + +#if(MIDI_RX_PORT_WIDTH == 4) +on tile[MIDI_TILE] : buffered in port:4 p_midi_rx = PORT_MIDI_IN; +#elif(MIDI_RX_PORT_WIDTH == 1) +on tile[MIDI_TILE] : buffered in port:1 p_midi_rx = PORT_MIDI_IN; +#endif +#endif + + +#ifdef MIDI +on tile[MIDI_TILE] : clock clk_midi = CLKBLK_MIDI; +#endif + +#if (XUA_SPDIF_TX_EN || XUA_ADAT_TX_EN) +on tile[SPDIF_TX_TILE] : clock clk_mst_spd = CLKBLK_SPDIF_TX; +#endif + +#if (XUA_SPDIF_RX_EN) +on tile[XUD_TILE] : clock clk_spd_rx = CLKBLK_SPDIF_RX; +#endif + +on tile[AUDIO_IO_TILE] : clock clk_audio_mclk = CLKBLK_MCLK; /* Master clock */ + +#if (AUDIO_IO_TILE != XUD_TILE) && XUA_USB_EN +/* Separate clock/port for USB feedback calculation */ +on tile[XUD_TILE] : clock clk_audio_mclk_usb = CLKBLK_MCLK; /* Master clock */ +#endif + +on tile[AUDIO_IO_TILE] : clock clk_audio_bclk = CLKBLK_I2S_BIT; /* Bit clock */ + +#ifdef IAP +/* I2C ports - in a struct for use with module_i2c_shared & module_i2c_simple/module_i2c_single_port */ +#ifdef PORT_I2C +on tile [IAP_TILE] : struct r_i2c r_i2c = {PORT_I2C}; +#else +on tile [IAP_TILE] : struct r_i2c r_i2c = {PORT_I2C_SCL, PORT_I2C_SDA}; +#endif +#endif + +#if XUA_USB_EN +/* Endpoint type tables for XUD */ +XUD_EpType epTypeTableOut[ENDPOINT_COUNT_OUT] = { XUD_EPTYPE_CTL | XUD_STATUS_ENABLE, +#if (NUM_USB_CHAN_IN > 0) + XUD_EPTYPE_ISO, /* Audio */ +#endif +#ifdef MIDI + XUD_EPTYPE_BUL, /* MIDI */ +#endif +#if HID_OUT_REQUIRED + XUD_EPTYPE_INT, +#endif +#ifdef IAP + XUD_EPTYPE_BUL, /* iAP */ +#ifdef IAP_EA_NATIVE_TRANS + XUD_EPTYPE_BUL, /* EA Native Transport */ +#endif +#endif + }; + +XUD_EpType epTypeTableIn[ENDPOINT_COUNT_IN] = { XUD_EPTYPE_CTL | XUD_STATUS_ENABLE, +#if (NUM_USB_CHAN_IN > 0) + XUD_EPTYPE_ISO, +#endif +#if (NUM_USB_CHAN_OUT > 0) && ((NUM_USB_CHAN_IN == 0) || defined(UAC_FORCE_FEEDBACK_EP)) + XUD_EPTYPE_ISO, /* Async feedback endpoint */ +#endif +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + XUD_EPTYPE_INT, +#endif +#ifdef MIDI + XUD_EPTYPE_BUL, +#endif +#if XUA_OR_STATIC_HID_ENABLED + XUD_EPTYPE_INT, +#endif +#ifdef IAP + XUD_EPTYPE_BUL | XUD_STATUS_ENABLE, +#ifdef IAP_INT_EP + XUD_EPTYPE_BUL | XUD_STATUS_ENABLE, +#endif +#ifdef IAP_EA_NATIVE_TRANS + XUD_EPTYPE_BUL | XUD_STATUS_ENABLE, +#endif +#endif + }; +#endif /* XUA_USB_EN */ + +void thread_speed() +{ +#ifdef FAST_MODE +#warning Building with fast mode enabled + set_thread_fast_mode_on(); +#else + set_thread_fast_mode_off(); +#endif +} + +#ifdef XSCOPE +void xscope_user_init() +{ + xscope_register(0, 0, "", 0, ""); + + xscope_config_io(XSCOPE_IO_BASIC); +} +#endif + +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE) +void SpdifTxWrapper(chanend c_spdif_tx) +{ + unsigned portId; + //configure_clock_src(clk, p_mclk); + + // TODO could share clock block here.. + // NOTE, Assuming SPDIF tile == USB tile here.. + asm("ldw %0, dp[p_mclk_in_usb]":"=r"(portId)); + asm("setclk res[%0], %1"::"r"(clk_mst_spd), "r"(portId)); + configure_out_port_no_ready(p_spdif_tx, clk_mst_spd, 0); + set_clock_fall_delay(clk_mst_spd, 7); + start_clock(clk_mst_spd); + + while(1) + { + spdif_tx(p_spdif_tx, c_spdif_tx); + } +} +#endif + +void usb_audio_io(chanend ?c_aud_in, +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE) + chanend c_spdif_tx, +#endif +#if (MIXER) + chanend c_mix_ctl, +#endif + streaming chanend ?c_spdif_rx, + streaming chanend ?c_adat_rx, + chanend ?c_clk_ctl, + chanend ?c_clk_int +#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1) + , server interface i_dfu ?dfuInterface +#endif +#if (XUA_NUM_PDM_MICS > 0) + , chanend c_pdm_pcm +#endif +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + , client interface pll_ref_if i_pll_ref +#endif +#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC) + , chanend c_audio_rate_change +#endif +#if ((XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) && XUA_USE_SW_PLL) + , port p_for_mclk_count_aud + , chanend c_sw_pll +#endif +) +{ +#if (MIXER) + chan c_mix_out; +#endif + +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + chan c_dig_rx; + chan c_audio_rate_change; /* Notification of new mclk freq to clockgen and synch */ +#if XUA_USE_SW_PLL + /* Connect p_for_mclk_count_aud to clk_audio_mclk so we can count mclks/timestamp in digital rx*/ + unsigned x = 0; + asm("ldw %0, dp[clk_audio_mclk]":"=r"(x)); + asm("setclk res[%0], %1"::"r"(p_for_mclk_count_aud), "r"(x)); +#endif /* XUA_USE_SW_PLL */ +#endif /* (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) */ + + +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE == AUDIO_IO_TILE) + chan c_spdif_tx; + + /* Setup S/PDIF tx port - note this is done before par since sharing clock-block/port */ + spdif_tx_port_config(p_spdif_tx, clk_audio_mclk, p_mclk_in, 7); +#endif + + par + { +#if (MIXER && XUA_USB_EN) + /* Mixer cores(s) */ + { + thread_speed(); + mixer(c_aud_in, c_mix_out, c_mix_ctl); + } +#endif + +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE == AUDIO_IO_TILE) + while(1) + { + spdif_tx(p_spdif_tx, c_spdif_tx); + } +#endif + + /* Audio I/O core (pars additional S/PDIF TX Core) */ + { + thread_speed(); +#if (MIXER) +#define AUDIO_CHANNEL c_mix_out +#else +#define AUDIO_CHANNEL c_aud_in +#endif + XUA_AudioHub(AUDIO_CHANNEL, clk_audio_mclk, clk_audio_bclk, p_mclk_in, p_lrclk, p_bclk, p_i2s_dac, p_i2s_adc +#if (XUA_SPDIF_TX_EN) //&& (SPDIF_TX_TILE != AUDIO_IO_TILE) + , c_spdif_tx +#endif +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + , c_dig_rx +#endif +#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC || XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + , c_audio_rate_change +#endif +#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1) + , dfuInterface +#endif +#if (XUA_NUM_PDM_MICS > 0) + , c_pdm_pcm +#endif + ); + } + +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + { + /* ClockGen must currently run on same tile as AudioHub due to shared memory buffer + * However, due to the use of an interface the pll reference signal port can be on another tile + */ + thread_speed(); + clockGen( c_spdif_rx, + c_adat_rx, + i_pll_ref, + c_dig_rx, + c_clk_ctl, + c_clk_int, + c_audio_rate_change +#if XUA_USE_SW_PLL + , p_for_mclk_count_aud + , c_sw_pll +#endif + ); + } +#endif + + } // par +} + +#ifndef USER_MAIN_DECLARATIONS +#define USER_MAIN_DECLARATIONS +#endif + +#ifndef USER_MAIN_CORES +#define USER_MAIN_CORES +#endif + + +/* Main for USB Audio Applications */ +int main() +{ +#if !XUA_USB_EN + #define c_mix_out null +#else + chan c_mix_out; +#endif + +#ifdef MIDI + chan c_midi; +#endif +#ifdef IAP + chan c_iap; +#ifdef IAP_EA_NATIVE_TRANS + chan c_ea_data; +#endif +#endif + +#if (MIXER) + chan c_mix_ctl; +#endif + +#if (XUA_SPDIF_RX_EN) + streaming chan c_spdif_rx; +#else +#define c_spdif_rx null +#endif + +#if (XUA_ADAT_RX_EN) + streaming chan c_adat_rx; +#else +#define c_adat_rx null +#endif + +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE) + chan c_spdif_tx; +#endif + +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + chan c_clk_ctl; + chan c_clk_int; +#else +#define c_clk_int null +#define c_clk_ctl null +#endif + +#if (XUA_DFU_EN == 1) + interface i_dfu dfuInterface; +#else + #define dfuInterface null +#endif + +#if (XUA_NUM_PDM_MICS > 0) + chan c_pdm_pcm; +#endif + +#if (((XUA_SYNCMODE == XUA_SYNCMODE_SYNC && !XUA_USE_SW_PLL) || XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) ) + interface pll_ref_if i_pll_ref; +#endif + +#if ((XUA_SYNCMODE == XUA_SYNCMODE_SYNC || XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) && XUA_USE_SW_PLL) + chan c_sw_pll; +#endif +#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC) + chan c_audio_rate_change; /* Notification of new mclk freq to ep_buffer */ +#endif + chan c_sof; + chan c_xud_out[ENDPOINT_COUNT_OUT]; /* Endpoint channels for XUD */ + chan c_xud_in[ENDPOINT_COUNT_IN]; + + /* Used to communicate controls/setting from XUA_Endpoint0() to the Audio/Buffering sub-system */ + chan c_aud_ctl; + +#if (!MIXER) +#define c_mix_ctl null +#endif + +#ifdef IAP_EA_NATIVE_TRANS + chan c_EANativeTransport_ctrl; +#else +#define c_EANativeTransport_ctrl null +#endif + +//#if (HID_CONTROLS > 0) +// chan c_hid; +//#endif + + USER_MAIN_DECLARATIONS + + par + { + USER_MAIN_CORES + +#if (((XUA_SYNCMODE == XUA_SYNCMODE_SYNC && !XUA_USE_SW_PLL) || XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)) + on tile[PLL_REF_TILE]: PllRefPinTask(i_pll_ref, p_pll_ref); +#endif + on tile[XUD_TILE]: + par + { +#if XUA_USB_EN +#if ((XUD_TILE == 0) && (XUA_DFU_EN == 1)) + /* Check if USB is on the flash tile (tile 0) */ + /* Expect to be distrbuted into XUA_Endpoint0() */ + [[distribute]] + DFUHandler(dfuInterface, null); +#endif + + /* Core USB task, buffering, USB etc */ + { +#ifdef XUD_PRIORITY_HIGH + set_core_high_priority_on(); +#endif + /* Run UAC2.0 at high-speed, UAC1.0 at full-speed */ + unsigned usbSpeed = (AUDIO_CLASS == 2) ? XUD_SPEED_HS : XUD_SPEED_FS; + + unsigned xudPwrCfg = (XUA_POWERMODE == XUA_POWERMODE_SELF) ? XUD_PWR_SELF : XUD_PWR_BUS; + + /* USB interface core */ + XUD_Main(c_xud_out, ENDPOINT_COUNT_OUT, c_xud_in, ENDPOINT_COUNT_IN, + c_sof, epTypeTableOut, epTypeTableIn, usbSpeed, xudPwrCfg); + } + +#if (NUM_USB_CHAN_OUT > 0) || (NUM_USB_CHAN_IN > 0) || XUA_HID_ENABLED || defined(MIDI) + /* Core USB audio task, buffering, USB etc */ + { + unsigned x; + thread_speed(); + + /* Attach mclk count port to mclk clock-block (for feedback) */ + //set_port_clock(p_for_mclk_count, clk_audio_mclk); +#if(AUDIO_IO_TILE != XUD_TILE) + set_clock_src(clk_audio_mclk_usb, p_mclk_in_usb); + set_port_clock(p_for_mclk_count, clk_audio_mclk_usb); + start_clock(clk_audio_mclk_usb); +#else + /* Clock port from same clock-block as I2S */ + /* TODO remove asm() */ + asm("ldw %0, dp[clk_audio_mclk]":"=r"(x)); + asm("setclk res[%0], %1"::"r"(p_for_mclk_count), "r"(x)); +#endif + /* Endpoint & audio buffering cores - buffers all EP's other than 0 */ + XUA_Buffer( +#if (NUM_USB_CHAN_OUT > 0) + c_xud_out[ENDPOINT_NUMBER_OUT_AUDIO], /* Audio Out*/ +#endif +#if (NUM_USB_CHAN_IN > 0) + c_xud_in[ENDPOINT_NUMBER_IN_AUDIO], /* Audio In */ +#endif +#if (NUM_USB_CHAN_OUT > 0) && ((NUM_USB_CHAN_IN == 0) || defined(UAC_FORCE_FEEDBACK_EP)) + c_xud_in[ENDPOINT_NUMBER_IN_FEEDBACK], /* Audio FB */ +#endif +#ifdef MIDI + c_xud_out[ENDPOINT_NUMBER_OUT_MIDI], /* MIDI Out */ // 2 + c_xud_in[ENDPOINT_NUMBER_IN_MIDI], /* MIDI In */ // 4 + c_midi, +#endif +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + /* Audio Interrupt - only used for interrupts on external clock change */ + c_xud_in[ENDPOINT_NUMBER_IN_INTERRUPT], + c_clk_int, +#endif + c_sof, c_aud_ctl, p_for_mclk_count +#if (XUA_HID_ENABLED) + , c_xud_in[ENDPOINT_NUMBER_IN_HID] +#endif + , c_mix_out +#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC) + , c_audio_rate_change + #if (!XUA_USE_SW_PLL) + , i_pll_ref + #else + , c_sw_pll + #endif +#endif + ); + //: + } +#endif + + /* Endpoint 0 Core */ + { + thread_speed(); +#if (USE_EX3D == 1) && (HID_CONTROLS > 0) + XUA_Endpoint0( c_xud_out[0], c_xud_in[0], c_hidRcvData, c_aud_ctl, c_mix_ctl, c_clk_ctl, c_EANativeTransport_ctrl, dfuInterface VENDOR_REQUESTS_PARAMS_); +#else + XUA_Endpoint0( c_xud_out[0], c_xud_in[0], c_aud_ctl, c_mix_ctl, c_clk_ctl, c_EANativeTransport_ctrl, dfuInterface VENDOR_REQUESTS_PARAMS_); +#endif + } + +#endif /* XUA_USB_EN */ + } + +#if ((XUA_SYNCMODE == XUA_SYNCMODE_SYNC || XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) && XUA_USE_SW_PLL) + on tile[AUDIO_IO_TILE]: sw_pll_task(c_sw_pll); +#endif + + on tile[AUDIO_IO_TILE]: + { + /* Audio I/O task, includes mixing etc */ + usb_audio_io( +#if (NUM_USB_CHAN_OUT > 0) || (NUM_USB_CHAN_IN > 0) + /* Connect audio system to XUA_Buffer(); */ + c_mix_out +#else + /* Connect to XUA_Endpoint0() */ + c_aud_ctl +#endif +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE) + , c_spdif_tx +#endif +#if (MIXER) + , c_mix_ctl +#endif + , c_spdif_rx, c_adat_rx, c_clk_ctl, c_clk_int +#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1) + , dfuInterface +#endif +#if (XUA_NUM_PDM_MICS > 0) + , c_pdm_pcm +#endif +#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) + , i_pll_ref +#endif +#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC) + , c_audio_rate_change +#endif +#if ((XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) && XUA_USE_SW_PLL) + , p_for_mclk_count_audio + , c_sw_pll +#endif + ); + } + //: + +#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE) + on tile[SPDIF_TX_TILE]: + { + thread_speed(); + SpdifTxWrapper(c_spdif_tx); + } +#endif + +#if defined(MIDI) && defined(IAP) && (IAP_TILE == MIDI_TILE) + /* MIDI and IAP share a core */ + on tile[IAP_TILE]: + { + thread_speed(); + usb_midi(p_midi_rx, p_midi_tx, clk_midi, c_midi, 0, c_iap, null, null, null); + } +#else +#if defined(MIDI) + /* MIDI core */ + on tile[MIDI_TILE]: + { + thread_speed(); + usb_midi(p_midi_rx, p_midi_tx, clk_midi, c_midi, 0); + } +#endif +#if defined(IAP) + on tile[IAP_TILE]: + { + thread_speed(); + iAP(c_iap, null, null, null); + } +#endif +#endif + +#if (XUA_SPDIF_RX_EN) + on tile[XUD_TILE]: + { + thread_speed(); + spdif_rx(c_spdif_rx, p_spdif_rx, clk_spd_rx, 192000); + } +#endif + +#if (XUA_ADAT_RX_EN) + on stdcore[XUD_TILE] : + { + set_thread_fast_mode_on(); + + while (1) + { + adatReceiver48000(p_adat_rx, c_adat_rx); + adatReceiver44100(p_adat_rx, c_adat_rx); + } + } +#endif + + +#if XUA_USB_EN +#if (XUD_TILE != 0) && (AUDIO_IO_TILE != 0) && (XUA_DFU_EN == 1) + /* Run flash code on its own - hope it gets combined */ + //#warning Running DFU flash code on its own + on stdcore[0]: DFUHandler(dfuInterface, null); +#endif +#endif + +#if (XUA_NUM_PDM_MICS > 0) + /* PDM Mics running on a separate to AudioHub */ + on stdcore[PDM_TILE]: + { + mic_array_task(c_mic_pcm); + } +#endif /*XUA_NUM_PDM_MICS > 0*/ + } + + return 0; +} + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/nau88c21.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/nau88c21.h new file mode 100644 index 0000000..3f751a9 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/nau88c21.h @@ -0,0 +1,98 @@ +/* + * + * nau88c22.h + * + */ + + +#ifndef NAU88C22_H_ +#define NAU88C22_H_ + +uint16_t nau88c22_registers[][4] = { +{0x00, 0x00, 0x00, 0x00}, +{0x00, 0x01, 0x0D, 0xFF}, +{0x00, 0x03, 0x20, 0x50}, +{0x00, 0x04, 0x00, 0x00}, +{0x00, 0x05, 0x00, 0xBC}, +{0x00, 0x06, 0x00, 0x08}, +{0x00, 0x07, 0x00, 0x10}, +{0x00, 0x08, 0x40, 0x00}, +{0x00, 0x09, 0x69, 0x00}, +{0x00, 0x0A, 0x00, 0x31}, +{0x00, 0x0B, 0x26, 0xE9}, +{0x00, 0x0D, 0xC0, 0x00}, +{0x00, 0x0F, 0x00, 0x00}, +{0x00, 0x10, 0x00, 0x00}, +{0x00, 0x11, 0x00, 0x00}, +{0x00, 0x12, 0xFF, 0xFF}, +{0x00, 0x13, 0x00, 0x00}, +{0x00, 0x1A, 0x00, 0x00}, +{0x00, 0x1B, 0x00, 0x00}, +{0x00, 0x1C, 0x00, 0x0E}, +{0x00, 0x1D, 0x20, 0x02}, +{0x00, 0x1E, 0x00, 0x00}, +{0x00, 0x1F, 0x00, 0x00}, +{0x00, 0x21, 0x00, 0x00}, +{0x00, 0x22, 0x00, 0x00}, +{0x00, 0x23, 0x00, 0x00}, +{0x00, 0x24, 0x00, 0x00}, +{0x00, 0x25, 0x00, 0x00}, +{0x00, 0x26, 0x00, 0x00}, +{0x00, 0x27, 0x00, 0x00}, +{0x00, 0x28, 0x00, 0x00}, +{0x00, 0x29, 0x00, 0x00}, +{0x00, 0x2A, 0x00, 0x00}, +{0x00, 0x2B, 0x40, 0x02}, // ADC Right Channel Source Select, bit 3, set to 1, 3rd byte 0x40 = Latch left channel analog data input into the right channel filter +{0x00, 0x2C, 0x00, 0x82}, +{0x00, 0x2D, 0x00, 0x00}, +{0x00, 0x2F, 0x00, 0x00}, +{0x00, 0x30, 0x00, 0x40}, +{0x00, 0x31, 0x00, 0x00}, +{0x00, 0x32, 0x00, 0x00}, +{0x00, 0x34, 0xCF, 0xCF}, +{0x00, 0x35, 0xCF, 0xCF}, +{0x00, 0x36, 0x14, 0x86}, +{0x00, 0x37, 0x0F, 0x12}, +{0x00, 0x38, 0x25, 0xFF}, +{0x00, 0x39, 0x34, 0x57}, +{0x00, 0x3A, 0x14, 0x86}, +{0x00, 0x3B, 0x0F, 0x12}, +{0x00, 0x3C, 0x25, 0xF9}, +{0x00, 0x3D, 0x34, 0x57}, +{0x00, 0x41, 0x00, 0x00}, +{0x00, 0x42, 0x00, 0x00}, +{0x00, 0x43, 0x00, 0x00}, +{0x00, 0x44, 0x00, 0x00}, +{0x00, 0x45, 0x00, 0x00}, +{0x00, 0x46, 0x00, 0x00}, +{0x00, 0x47, 0x00, 0x00}, +{0x00, 0x48, 0x00, 0x00}, +{0x00, 0x49, 0x00, 0x00}, +{0x00, 0x4A, 0x00, 0x00}, +{0x00, 0x4B, 0x20, 0x07}, +{0x00, 0x4C, 0x00, 0x00}, +{0x00, 0x4D, 0x00, 0x00}, +{0x00, 0x53, 0x02, 0x78}, +{0x00, 0x54, 0x00, 0x02}, +{0x00, 0x55, 0x00, 0x00}, +{0x00, 0x58, 0x1B, 0x22}, +{0x00, 0x59, 0x20, 0x00}, +{0x00, 0x66, 0x00, 0x60}, +{0x00, 0x68, 0x00, 0x00}, +{0x00, 0x69, 0x00, 0x00}, +{0x00, 0x6A, 0x10, 0x03}, +{0x00, 0x6B, 0x00, 0x00}, +{0x00, 0x71, 0x00, 0x11}, +{0x00, 0x72, 0x04, 0x70}, +{0x00, 0x73, 0x33, 0x08}, +{0x00, 0x74, 0x15, 0x06}, +{0x00, 0x76, 0x31, 0x40}, +{0x00, 0x77, 0x00, 0x12}, +{0x00, 0x7E, 0x01, 0x01}, +{0x00, 0x7F, 0xC0, 0x3F}, +{0x00, 0x80, 0x07, 0x20}, +{0x00, 0x81, 0x00, 0x08}, +{0x00, 0x82, 0x00, 0x60}, +}; + +#endif diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/program_key.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/program_key.c new file mode 100644 index 0000000..98f048c --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/program_key.c @@ -0,0 +1,652 @@ +#define DEBUG_PRINT_ENABLE 0 +#include +#include "debug_print.h" +#include +#include +#include +#include "xua_conf.h" + +#define WRITE_ENABLE_COMMAND (0x06) +#define WRITE_DISABLE_COMMAND (0x04) + +#define ERASE_CHIP_COMMAND (0xC7) + + +#define READ_STATUS_REG_COMMAND (0x05) +#define READ_ID_COMMAND (0x9F) + +#define READ_UID_COMMAND (0x4B) + +#define WRITE_ENABLE_STATUS_COMMAND (0x50) + +#define WRITE_STATUS_REG1_COMMAND (0x01) +#define READ_STATUS_REG2_COMMAND (0x35) +#define WRITE_STATUS_REG2_COMMAND (0x31) +#define READ_STATUS_REG3_COMMAND (0x15) +#define WRITE_STATUS_REG3_COMMAND (0x11) + +#define RDSTATUS_COMMAND (0x35) +#define WRSTATUS_COMMAND (0x31) + +#define PRSCUR_COMMAND (0x42) +#define ERSCUR_COMMAND (0x44) +#define RDSCUR_COMMAND (0x48) +#define HMAC_LEN 60 +#define HMAC_GEN_LEN 20 + + +extern unsigned short XUA_Endpoint0_getProductId(); +extern unsigned short XUA_Endpoint0_getVendorId(); + +unsigned char hid_reply[64] = {0}; + +#if 0 +void printArrayHex1(uint8_t array[], int size, int elementsPerLine) { + for (int i = 0; i < size; i++) { + printf("0x%x, ", array[i]); + + // 在达到每行元素个数时换行 + if ((i + 1) % elementsPerLine == 0) { + printf("\n"); + } + } + printf("\n"); +} +#endif + +#define settw(a,b) {__asm__ __volatile__("settw res[%0], %1": : "r" (a) , "r" (b));} +#define setc(a,b) {__asm__ __volatile__("setc res[%0], %1": : "r" (a) , "r" (b));} +#define setclk(a,b) {__asm__ __volatile__("setclk res[%0], %1": : "r" (a) , "r" (b));} +#define portin(a,b) {__asm__ __volatile__("in %0, res[%1]": "=r" (b) : "r" (a));} +#define portout(a,b) {__asm__ __volatile__("out res[%0], %1": : "r" (a) , "r" (b));} + +#ifdef DFU_FLASH_DEVICE + +#if (XUA_QUAD_SPI_FLASH) +/* Using specified flash device rather than all supported in tools */ +fl_QuadDeviceSpec opt_flash_devices[] = {DFU_FLASH_DEVICE}; +#else +/* Using specified flash device rather than all supported in tools */ +fl_DeviceSpec opt_flash_devices[] = {DFU_FLASH_DEVICE}; +#endif +#endif + +/* +typedef struct { + out port qspiCS; + out port qspiSCLK; + out buffered port:32 qspiSIO; + clock qspiClkblk; +} fl_QSPIPorts; +*/ +fl_QSPIPorts p_opt_qflash_tile0 = +{ + XS1_PORT_1B, + XS1_PORT_1C, + XS1_PORT_4B, + XS1_CLKBLK_1 +}; + +fl_QSPIPorts p_opt_qflash_tile1 = +{ + XS1_PORT_1F, + XS1_PORT_1G, + XS1_PORT_4D, + XS1_CLKBLK_1 +}; + +#define KEY_ADDRESS 0xFFFD0 + +/* Store Flag to fixed address */ +static void SetKeyFlag(unsigned x) +{ + asm volatile("stw %0, %1[0]" :: "r"(x), "r"(KEY_ADDRESS)); +} + +/* Load flag from fixed address */ +unsigned GetKeyFlag() +{ + unsigned x; + asm volatile("ldw %0, %1[0]" : "=r"(x) : "r"(KEY_ADDRESS)); + return x; +} + +/* return 1 for opened ports successfully */ +int flash_opt_enable_ports(fl_QSPIPorts *p_qflash) +{ + int result = 0; + +#ifdef DFU_FLASH_DEVICE + result = fl_connectToDevice(p_qflash, opt_flash_devices, sizeof(opt_flash_devices) / sizeof(fl_QuadDeviceSpec)); +#else + /* Use default flash list */ + result = fl_connect(p_qflash); +#endif + if (!result) + { + /* All okay.. */ + return 1; + } + else + { + return 0; + } +} + +int flash_opt_disable_ports() +{ + fl_disconnect(); + return 1; +} + +void flash_cmd_opt(unsigned int cmd, + unsigned char input[], unsigned int num_in, + unsigned char output[], unsigned int num_out) +{ + debug_printf("flash_cmd_opt: cmd = %02x, num_in = %d, num_out = %d\n", cmd, num_in, num_out); + for (size_t i = 0; i < num_in; i++) + { + debug_printf("input[%d] = %02x\n", i, input[i]); + } + for (size_t i = 0; i < num_out; i++) + { + debug_printf("output[%d] = %02x\n", i, output[i]); + } + fl_command(cmd,input,num_in,output, num_out); +} + +void flash_opt_erase() +{ + uint8_t cmd[4] = {0, 0x10, 0x0, 0}; + uint8_t data[4] = {0, 0x10, 0x0, 0}; + flash_cmd_opt(WRITE_ENABLE_COMMAND, cmd, 0, data, 0); + delay_milliseconds(1); + flash_cmd_opt(ERSCUR_COMMAND, cmd, 3, data, 0); + delay_milliseconds(50); +} + +void flash_opt_read_did(uint8_t data[], unsigned char len) +{ + uint8_t cmd[3] = {0x3, 0x10, 0x0}; + + flash_cmd_opt(READ_ID_COMMAND, cmd, 0, data, len); + // printArrayHex1(data, len, 20); +} + + +void flash_opt_unlock(void) +{ + uint8_t cmd[1] = {0}; + uint8_t data[1] = {0}; + uint8_t sr1, sr2, sr3; + uint8_t did[3]; + + /* Reason: 使用 tile0 端口连接 Flash,保持原有逻辑 */ + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) { + debug_printf("flash_opt_unlock failed\n"); + return; + } + + /* Reason: 增加 Flash 厂商ID检查,仅针对 Winbond (0xEF) 执行特定解锁逻辑 */ + flash_opt_read_did(did, 3); + if (did[0] != 0xEF) + { + debug_printf("Not Winbond flash (DID: %02x), skip unlock\n", did[0]); + flash_opt_disable_ports(); + return; + } + + debug_printf("Unlock Winbond Flash...\n"); + + /* Reason: 解锁 Status Register 1。 + 参考 flash_api_ext.c 逻辑,清除 Bit 2-6 (BP0-BP4, TB, SEC) + Mask: 0x7C (0111 1100) */ + flash_cmd_opt(READ_STATUS_REG_COMMAND, cmd, 0, data, 1); + sr1 = data[0]; + if (sr1 & 0x7C) + { + debug_printf("Unlock SR1: %02x -> %02x\n", sr1, sr1 & ~0x7C); + flash_cmd_opt(WRITE_ENABLE_STATUS_COMMAND, cmd, 0, data, 0); + data[0] = sr1 & ~0x7C; + flash_cmd_opt(WRITE_STATUS_REG1_COMMAND, data, 1, cmd, 0); + delay_milliseconds(10); /* 等待写入完成 */ + } + + /* Reason: 解锁 Status Register 2。 + 参考 flash_api_ext.c 逻辑,清除 Bit 6 (CMP) + Mask: 0x40 (0100 0000) */ + flash_cmd_opt(READ_STATUS_REG2_COMMAND, cmd, 0, data, 1); + sr2 = data[0]; + if ((sr2 & 0x40) || ((sr2 & 0x2) == 0)) + { + debug_printf("Unlock SR2: %02x -> %02x\n", sr2, (sr2 & ~0x40) | 0x2); + flash_cmd_opt(WRITE_ENABLE_STATUS_COMMAND, cmd, 0, data, 0); + data[0] = (sr2 & ~0x40) | 0x2; + flash_cmd_opt(WRITE_STATUS_REG2_COMMAND, data, 1, cmd, 0); + delay_milliseconds(10); + } + + /* Reason: 解锁 Status Register 3。 + 参考 flash_api_ext.c 逻辑,清除 Bit 2 (WPS) + Mask: 0x04 (0000 0100) */ + flash_cmd_opt(READ_STATUS_REG3_COMMAND, cmd, 0, data, 1); + sr3 = data[0]; + if (sr3 & 0x04) + { + debug_printf("Unlock SR3: %02x -> %02x\n", sr3, sr3 & ~0x04); + flash_cmd_opt(WRITE_ENABLE_STATUS_COMMAND, cmd, 0, data, 0); + data[0] = sr3 & ~0x04; + flash_cmd_opt(WRITE_STATUS_REG3_COMMAND, data, 1, cmd, 0); + delay_milliseconds(10); + } + + flash_opt_disable_ports(); + debug_printf("Unlock Winbond Flash done\n"); +} + +void flash_opt_write2(uint8_t data[], unsigned char len) +{ + uint8_t d[1]; + uint8_t cmd[256] = {0, 0x10, 0x0}; + + for (int i = 0; i < len; i++) + { + cmd[i + 3] = data[i]; + } + + flash_opt_erase(); + flash_cmd_opt(WRITE_ENABLE_COMMAND, cmd, 0, d, 0); + flash_cmd_opt(PRSCUR_COMMAND, cmd, 3 + len, d, 0); + delay_milliseconds(50); +} + +void flash_opt_read(uint8_t data[], unsigned char len) +{ + uint8_t cmd[3] = {0x00, 0x10, 0x00}; + + flash_cmd_opt(RDSCUR_COMMAND, cmd, 3, data, len); + //printf("read sec key:\n"); + //printArrayHex1(data, len, 20); +} + +void flash_opt_read_uid(uint8_t data[], unsigned char len) +{ + uint8_t cmd[3] = {0x3, 0x10, 0x0}; + + flash_cmd_opt(READ_UID_COMMAND, cmd, 0, data, len); + // printArrayHex1(data, len, 20); +} + + + +unsigned compare_buff_diff(const uint8_t* actual, const uint8_t* expected, unsigned len) +{ + for (int i = 0; i < len; ++i) + { + if (actual[i] != expected[i]) + { + debug_printf("compare_buff_diff: %d %02x %02x\n", i, actual[i], expected[i]); + return 0; + } + } + return 1; +} + + +extern void hmac_sha1(const unsigned char *, unsigned long, const unsigned char * , unsigned long, unsigned char * ); + +void cal_hmac(uint8_t uid[], uint8_t did[], uint8_t hmac_bin[]) +{ + { + uint8_t key_bin[HMAC_GEN_LEN] = {0x76, 0x37, 0x0b, 0xb8, 0xbd, 0xe5, 0x54, 0x82, 0x8f, 0x13, 0x29, 0xa6, 0x26, 0x07, 0x2d, 0xe1, 0x11, 0x6c, 0xfb, 0x07}; + uint8_t msg_bin[HMAC_GEN_LEN]; + for (int i = 0; i < 3; i++) + { + msg_bin[i] = did[i]; + } + + for (int i = 3; i < 20; i++) + { + msg_bin[i] = uid[i]; + } + +#if 0 + printf("msg_bin: \n"); + printArrayHex1(msg_bin, HMAC_GEN_LEN, 20); +#endif + hmac_sha1(key_bin, HMAC_GEN_LEN, msg_bin, HMAC_GEN_LEN, hmac_bin); + +#if 0 + printf("hmac_bin: \n"); + printArrayHex1(hmac_bin, HMAC_LEN, 20); +#endif + } +} + +uint8_t key_verify_old(unsigned sec_write, uint8_t expected_bin[], unsigned offset) +{ + uint8_t read_bin[HMAC_LEN + 1]; + uint8_t comp_bin[HMAC_LEN + 1]; + uint8_t uid[20]; + uint8_t did[4]; + uint8_t ret = 0; + uint8_t hmac_bin[20]; + + flash_opt_read(read_bin, HMAC_LEN + 1); + flash_opt_read_uid(uid, 20); + flash_opt_read_did(did, 3); + + cal_hmac(uid, did, hmac_bin); + + if (sec_write) + { + if (offset == 0) + { + for (int i = 0; i < 20; i++) + { + read_bin[i + 1] = expected_bin[i]; + } + } + else if (offset == 1) + { + for (int i = 0; i < 20; i++) + { + read_bin[i + 21] = expected_bin[i]; + } + } + + flash_opt_write2(&read_bin[1], HMAC_LEN); + delay_milliseconds(10); + + flash_opt_read(comp_bin, HMAC_LEN + 1); + if (compare_buff_diff(&comp_bin[1], &read_bin[1], HMAC_LEN) == 1) + { + debug_printf("compare OK\n"); + ret = 1; + } + } + else + { + if (offset == 0) + { + flash_opt_read(read_bin, HMAC_LEN + 1); + if (compare_buff_diff(hmac_bin, &read_bin[1], 20) == 1) + { + debug_printf("key_verified\n"); + ret = 1; + } + } + else if (offset == 1) + { + flash_opt_read(read_bin, HMAC_LEN + 1); + if (compare_buff_diff(hmac_bin, &read_bin[21], 20) == 1) + { + debug_printf("key_verified\n"); + ret = 1; + } + } + } + + return ret; +} + +void flash_read_uid(uint8_t *uid) +{ + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + return; + } + + flash_opt_read_uid(uid, 20); + flash_opt_disable_ports(); +} + +void flash_read_did(uint8_t *did) +{ + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + return; + } + flash_opt_read_did(did, 3); + flash_opt_disable_ports(); +} + +void get_key_ret(uint8_t *buffer) +{ + //printf("get_key_ret2\n"); + for (int i = 0; i < 64; i ++) + { + buffer[i] = hid_reply[i]; + hid_reply[i] = 0; + } + + //printArrayHex1(buffer, 20, 20); +} + +unsigned char g_hid_pass_data[64]; +extern void hidSetChangePending(unsigned int); + +void user_read_hidpass(unsigned char * hidPassData) +{ + int i = 0; + for (i = 0; i < 64; i++) + { + hidPassData[i] = g_hid_pass_data[i]; + debug_printf("hidPassData[%d] = %02x\n", i, hidPassData[i]); + g_hid_pass_data[i] = 0; + } +} + +void user_set_hidpass(unsigned char *data) +{ +#if HID_CONTROLS + + for (int i = 0; i < 63; i++) + { + g_hid_pass_data[i] = data[i]; + debug_printf("g_hid_pass_data[%d] = %02x\n", i, g_hid_pass_data[i]); + } + + hidSetChangePending(0x1); +#endif +} + + +static chanend_t g_flash_opt_c; + +void flash_opt_set_chan(chanend_t c) { + g_flash_opt_c = c; + //debug_printf("flash_opt_set_chan\n"); +} + +void flash_opt_exchange_buffer(uint8_t in[64], uint8_t out[64]) { + //debug_printf("flash_opt_exchange_buffer\n"); + chan_out_buf_byte (g_flash_opt_c , in, 40) ; + //debug_printf("flash_opt_exchange_buffer1\n"); + chan_in_buf_byte(g_flash_opt_c, out, 40); +} + +void flash_opt_key(chanend_t c) { + uint8_t buffer[64], reply[40] = {0, 0, 0, 0}; + uint32_t ret; + delay_milliseconds(2); + while (1) { + chan_in_buf_byte(c, buffer, 40); + if (flash_opt_enable_ports(&p_opt_qflash_tile1) == 0) { + reply[0] = 0; + } + else + { + switch (buffer[3]) + { + case 9: + flash_opt_read_did(&reply[1], 3); + reply[0] = 0x55; + break; + case 10: + flash_opt_read_uid(&reply[1], 21); + reply[0] = 0x55; + break; + case 8: + ret = key_verify_old(1, &buffer[4], 0); + if (ret == 1) + { + debug_printf("write key ret 0x55\n"); + reply[0] = 0x55; + } + else + { + reply[0] = 0; + } + break; + + } + flash_opt_disable_ports(); + chan_out_buf_byte(c, reply, 40) ; + } + } + +} + +uint8_t key_validate(void) +{ + uint8_t ret = 0; + uint8_t data[1] = {0}; + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + return 0; + } + ret = key_verify_old(0, data, 0); + if (ret == 1) + { + SetKeyFlag(0x20241224); + } + + ret = key_verify_old(0, data, 1); + if (ret == 1) + { + SetKeyFlag(0x20241224); + } + flash_opt_disable_ports(); + return ret; +} + + +void program_key(uint8_t *buffer, int datalength) +{ + if (buffer[0] == 0x77 && buffer[1] == 0x5B) + { + uint8_t ret = 0; + switch (buffer[2]) + { + case 1: + debug_printf("verify key\n"); + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + hid_reply[0] = 0; + } + else + { + uint8_t data[1] = {0}; + ret = key_verify_old(0, data, 0); + if (ret == 1) + { + hid_reply[0] = 0x55; + } + else + { + hid_reply[0] = 0; + } + flash_opt_disable_ports(); + } + break; + case 2: + debug_printf("write key\n"); + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + hid_reply[0] = 0; + } + else + { + ret = key_verify_old(1, &buffer[3], 0); + + debug_printf("write key ret %d\n", ret); + if (ret == 1) + { + hid_reply[0] = 0x55; + } + else + { + hid_reply[0] = 0; + } + flash_opt_disable_ports(); + } + + break; + case 3: + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + hid_reply[0] = 0; + } + else + { + flash_opt_read_did(&hid_reply[1], 3); + debug_printf("return did\n"); + hid_reply[0] = 0x55; + flash_opt_disable_ports(); + } + break; + case 4: + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + hid_reply[0] = 0; + } + else + { + debug_printf("return uid\n"); + flash_opt_read_uid(&hid_reply[1], 21); + hid_reply[0] = 0x55; + flash_opt_disable_ports(); + } + break; + case 5: + if (flash_opt_enable_ports(&p_opt_qflash_tile0) == 0) + { + hid_reply[0] = 0; + } + else + { + debug_printf("erase key\n"); + flash_opt_erase(); + hid_reply[0] = 0x55; + flash_opt_disable_ports(); + } + break; + case 6: + debug_printf("get pid vid\n"); + unsigned short pid = XUA_Endpoint0_getProductId(); + unsigned short vid = XUA_Endpoint0_getVendorId(); + hid_reply[2] = (pid >> 8) & 0xff; + hid_reply[1] = pid & 0xff; + hid_reply[4] = (vid >> 8) & 0xff; + hid_reply[3] = vid & 0xff; + hid_reply[0] = 0x55; + break; + case 7: + break; + case 8: + case 9: + case 10: + //flash_opt_exchange_buffer(buffer, hid_reply); + // debug_printf("write aizip key tile 0 %02x\n", hid_reply[0]); + for (int i = 0; i < datalength; i++) + hid_reply[i] = 0; + + break; + } + } + else + { + //xmos_printf(&buffer[1], datalength - 1); + } +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.c new file mode 100644 index 0000000..f5d7d13 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.c @@ -0,0 +1,95 @@ +#include "share_buffer.h" +#define RING_BUFFER_SIZE 512 + +typedef struct { + int buffer[RING_BUFFER_SIZE]; + int head; + int tail; + int count; +} RingBuffer; + +// 两路环形缓冲区 +RingBuffer ring_buffers[4]; + +// 初始化环形缓冲区 +void init_ring_buffer(int channel) { + if (channel < 0 || channel > 1) return; + + ring_buffers[channel].head = 0; + ring_buffers[channel].tail = 0; + ring_buffers[channel].count = 0; +} + +// 初始化所有环形缓冲区 +void init_all_ring_buffers() { + init_ring_buffer(0); + init_ring_buffer(1); +} + +// 判断环形缓冲区是否为空 +int is_ring_buffer_empty(int channel) { + if (channel < 0 || channel > 3) return 1; + + return (ring_buffers[channel].count == 0); +} + +// 判断环形缓冲区是否已满 +int is_ring_buffer_full(int channel) { + if (channel < 0 || channel > 3) return 0; + + return (ring_buffers[channel].count == RING_BUFFER_SIZE); +} + +// 获取环形缓冲区中的数据数量 +int get_ring_buffer_count(int channel) { + if (channel < 0 || channel > 3) return 0; + + return ring_buffers[channel].count; +} + +// 向环形缓冲区写入数据 +int write_to_ring_buffer(int channel, int value) { + if (channel < 0 || channel > 3) return -1; + + if (is_ring_buffer_full(channel)) { + return -1; // 缓冲区已满 + } + + ring_buffers[channel].buffer[ring_buffers[channel].head] = value; + ring_buffers[channel].head = (ring_buffers[channel].head + 1) % RING_BUFFER_SIZE; + ring_buffers[channel].count++; + + return 0; // 写入成功 +} + +// 从环形缓冲区读取数据 +int read_from_ring_buffer(int channel) { + int value = 0; + if (channel == 0 || channel == 1 || channel == 2 || channel == 3) { + if (is_ring_buffer_empty(channel)) { + return 0; + } + + value = ring_buffers[channel].buffer[ring_buffers[channel].tail]; + ring_buffers[channel].tail = (ring_buffers[channel].tail + 1) % RING_BUFFER_SIZE; + ring_buffers[channel].count--; + } + return value; +} + +// 清空环形缓冲区 +void clear_ring_buffer(int channel) { + if (channel < 0 || channel > 3) return; + + ring_buffers[channel].head = 0; + ring_buffers[channel].tail = 0; + ring_buffers[channel].count = 0; +} + +// 清空所有环形缓冲区 +void clear_all_ring_buffers() { + clear_ring_buffer(0); + clear_ring_buffer(1); + clear_ring_buffer(2); + clear_ring_buffer(3); +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.h new file mode 100644 index 0000000..1db56cb --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/share_buffer.h @@ -0,0 +1,10 @@ + +void init_ring_buffer(int channel); +void init_all_ring_buffers(); +int is_ring_buffer_empty(int channel); +int is_ring_buffer_full(int channel); +int get_ring_buffer_count(int channel); +int write_to_ring_buffer(int channel, int value); +int read_from_ring_buffer(int channel); +void clear_ring_buffer(int channel); +void clear_all_ring_buffers(); \ No newline at end of file diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.c b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.c new file mode 100644 index 0000000..7064623 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.c @@ -0,0 +1,233 @@ + +#if UART_DEBUG || DEBUG_MEMORY_LOG_ENABLED +#define DEBUG_PRINT_ENABLE 1 +#endif +#include +#include +#include "user_func.h" +#include "lfs_io.h" +#include "debug_print.h" + +ProductInfo productInfo; +AppConfigs appPowerOnConf, appRunningState, appUserConf; +AudioMode appAudioMode; + +#define PRODUCT_INFO_PATH "product_info" +#define POWERON_INFO_PATH "poweron_info" + +uint32_t calculate_crc32(uint8_t *buf, int len) { + uint32_t crc = 0xFFFFFFFF; + uint32_t poly = 0xEDB88320; + for (int i = 0; i < len; i++) { + crc ^= (uint32_t)buf[i]; + for (int j = 8; j > 0; j--) { + if (crc & 1) { + crc = (crc >> 1) ^ poly; + } else { + crc >>= 1; + } + } + } + return ~crc; +} + +unsigned char check_sum(unsigned char *pbuf, unsigned len) +{ + unsigned char i; + unsigned short sum; + sum = 0; + for(i = 0; i < len; i++) + { + sum += pbuf[i]; + } + i = sum % 256; + return i; +} + +uint32_t get_reference_time(); +void load_configs(void) +{ + //uint32_t t = get_reference_time(); + if (lfs_init() == 0) + { + lfs_read_config(PRODUCT_INFO_PATH, (unsigned char*) &productInfo, sizeof(productInfo)); + lfs_read_config(POWERON_INFO_PATH, (unsigned char*) &appPowerOnConf, sizeof(appPowerOnConf)); + //printf("load configs\n"); + // printf("load pid 0x%04x vid 0x%04x\n", productInfo.pid2, productInfo.vid2); + } + lfs_deinit(); + //printf("bytes read time %lu\n", get_reference_time() - t); +} + +void save_configs(unsigned char save) +{ + if (save != 0) + { + //uint32_t t = get_reference_time(); + if (lfs_init() == 0) + { + if (save & 0x1) + lfs_write_config(PRODUCT_INFO_PATH, (unsigned char*) &productInfo, sizeof(productInfo)); + if (save & 0x2) + lfs_write_config(POWERON_INFO_PATH, (unsigned char*) &appPowerOnConf, sizeof(appPowerOnConf)); + } + + //printf("save ... pid 0x%04x vid 0x%04x\n", productInfo.pid2, productInfo.vid2); + lfs_deinit(); + //printf("bytes write time %lu\n", get_reference_time() - t); + } +} + +void save_value(unsigned char *path, unsigned char value) +{ + //uint32_t t = get_reference_time(); + if (lfs_init() == 0) + { + lfs_write_config(path, (unsigned char*) &value, sizeof(value)); + } + + lfs_deinit(); +} + +unsigned char load_value(unsigned char *path) +{ + unsigned char value = 255; + if (lfs_init() == 0) + { + lfs_read_config(path, (unsigned char*) &value, sizeof(value)); + } + else + { + debug_printf("lfs_init failed\n"); + } + + lfs_deinit(); + return value; +} + + +#if (NUM_USB_CHAN_OUT != 0) || (NUM_USB_CHAN_IN != 0) +unsigned short XUA_Endpoint0_getProductId(); +unsigned short XUA_Endpoint0_getVendorId(); +void XUA_Endpoint0_setProductId(unsigned short pid); +void XUA_Endpoint0_setVendorStr(char* vendor_str); +void XUA_Endpoint0_setProductStr(char* product_str); +void XUA_Endpoint0_setSerialStr(char* serial_str); +void XUA_Endpoint0_setVendorId(unsigned short vid); + +void get_pid_vid() +{ + unsigned crc; + if ((productInfo.vid1 != 0) || (productInfo.vid2 != 0) + || (productInfo.pid1 != 0 ) || (productInfo.pid2 != 0)) + { + unsigned char tmp[17]; + crc = calculate_crc32((unsigned char *) &productInfo, sizeof(productInfo) - 4); + + if (NTOHL(crc) == productInfo.crc || crc == productInfo.crc) + { + debug_printf("crc is correct\n"); +#if (AUDIO_CLASS == 1) + { + XUA_Endpoint0_setProductId(NTOHS(productInfo.pid1)); + XUA_Endpoint0_setVendorId(NTOHS(productInfo.vid1)); + debug_printf(" pid %04x vid %04x\n", NTOHS(productInfo.pid1), NTOHS(productInfo.vid1)); + } +#else + { + XUA_Endpoint0_setProductId(NTOHS(productInfo.pid2)); + XUA_Endpoint0_setVendorId(NTOHS(productInfo.vid2)); + debug_printf(" pid %04x vid %04x\n", NTOHS(productInfo.pid2), NTOHS(productInfo.vid2)); + } +#endif + + memcpy (tmp, productInfo.ManufactureName, 16); + tmp[16] = 0; + XUA_Endpoint0_setVendorStr(tmp); + + memcpy (tmp, productInfo.ProductName, 16); + XUA_Endpoint0_setProductStr(tmp); + + memcpy (tmp, productInfo.SerialNumber, 16); + XUA_Endpoint0_setSerialStr(tmp); + } + else + { + debug_printf("crc error %08x actual %08x\n", NTOHL(crc), productInfo.crc); + } + } + +} + +#endif +void fill_boot_cmd(unsigned char *buffer) +{ + int count = 0; + unsigned short vid, pid; + + buffer[count++] = 0; + if (productInfo.vid1 == 0 && productInfo.vid2 == 0 + && productInfo.pid1 == 0 && productInfo.pid2 == 0) + { + +#if (NUM_USB_CHAN_OUT != 0) || (NUM_USB_CHAN_IN != 0) + pid = XUA_Endpoint0_getProductId(); + vid = XUA_Endpoint0_getVendorId(); + + buffer[count++] = vid >> 8; + buffer[count++] = vid & 0xff; + buffer[count++] = pid >> 8; + buffer[count++] = pid & 0xff; + + buffer[count++] = vid >> 8; + buffer[count++] = vid & 0xff; + buffer[count++] = pid >> 8; + buffer[count++] = pid & 0xff; + + for(int i = 0; i < 8; i++) + buffer[count++] = 0; +#endif + } + else + { + buffer[count++] = productInfo.vid1 & 0xff; + buffer[count++] = productInfo.vid1 >> 8; + buffer[count++] = productInfo.pid1 & 0xff; + buffer[count++] = productInfo.pid1 >> 8; + + buffer[count++] = productInfo.vid2 & 0xff; + buffer[count++] = productInfo.vid2 >> 8; + buffer[count++] = productInfo.pid2 & 0xff; + buffer[count++] = productInfo.pid2 >> 8; + + buffer[count++] = productInfo.crc & 0xff; + buffer[count++] = (productInfo.crc >> 8) & 0xff; + buffer[count++] = (productInfo.crc >> 16) & 0xff; + buffer[count++] = (productInfo.crc >> 24) & 0xff; + + buffer[count++] = appPowerOnConf.crc & 0xff; + buffer[count++] = (appPowerOnConf.crc >> 8) & 0xff; + buffer[count++] = (appPowerOnConf.crc >> 16) & 0xff; + buffer[count++] = (appPowerOnConf.crc >> 24) & 0xff; + } + +} +void fill_finish_cmd(unsigned char *buffer) +{ + memcpy(buffer, (unsigned char *)&appUserConf, sizeof(appUserConf)); +} + +void save_product_info(unsigned char *buffer) +{ + memcpy((unsigned char *)&productInfo, buffer, sizeof(productInfo)); +} + +void save_powerup_info(unsigned char *buffer) +{ + memcpy((unsigned char *)&appPowerOnConf, buffer, sizeof(appPowerOnConf)); +} + +void save_userconfig_info(unsigned char *buffer) +{ + memcpy((unsigned char *)&appUserConf, buffer, sizeof(appUserConf)); +} diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.h new file mode 100644 index 0000000..e0d7262 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_func.h @@ -0,0 +1,52 @@ +#ifndef _USER_FUNC_H_ +#define _USER_FUNC_H_ + +#include +#include +#include +#include +#include +#include +#include "user_uart.h" + +// 32-bit macros for converting between host and network byte order +#define HTONL(value) ( \ + (((value) >> 24) & 0x000000FF) | \ + (((value) >> 8) & 0x0000FF00) | \ + (((value) << 8) & 0x00FF0000) | \ + (((value) << 24) & 0xFF000000) \ +) + +#define NTOHL(value) HTONL(value) // Since the transformation is the same + +// 16-bit macros for converting between host and network byte order +#define HTONS(value) ( \ + (((value) >> 8) & 0x00FF) | \ + (((value) << 8) & 0xFF00) \ +) + +#define NTOHS(value) HTONS(value) // Since the transformation is the same + +#ifdef __XC__ +uint32_t calculate_crc32(uint8_t *unsafe buf, int len); +unsigned char check_sum(uint8_t *unsafe pbuf, unsigned len); +void fill_boot_cmd(unsigned char *unsafe buffer); +void save_product_info(unsigned char *unsafe buffer); +void save_powerup_info(unsigned char *unsafe buffer); +void fill_finish_cmd(unsigned char *unsafe buffer); +void save_userconfig_info(unsigned char *unsafe buffer); +#else +uint32_t calculate_crc32(uint8_t *buf, int len); +unsigned char check_sum(uint8_t *pbuf, unsigned len); +void fill_boot_cmd(unsigned char *buffer); +void save_product_info(unsigned char *buffer); +void save_powerup_info(unsigned char *buffer); +void fill_finish_cmd(unsigned char *buffer); +void save_userconfig_info(unsigned char *buffer); +#endif +void load_configs(void); +void save_configs(unsigned char save); +void get_pid_vid(); + +#endif + diff --git a/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_main.h b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_main.h new file mode 100644 index 0000000..ea9a071 --- /dev/null +++ b/sw_usb_audio/app_usb_aud_phaten_golden_6ch/src/extensions/user_main.h @@ -0,0 +1,134 @@ + +#ifndef _USER_MAIN_H_ +#define _USER_MAIN_H_ + +#ifdef __XC__ + +#include "i2c.h" +#include +#include +#include +#include "DSBuild.h" + +//extern unsafe client interface i2c_master_if i_i2c_client; +//extern unsafe client interface i2c_master_if i_i2c_client_t0; +extern void interface_saver(client interface i2c_master_if i); +extern void dsp_core0(void); +extern void board_setup(); +extern void dsp_main (chanend c_data); +extern void SetEqDataChan (chanend c); + +/* I2C interface ports */ +extern port p_scl; +extern port p_sda; + +extern int dsp_worker_tile(chanend c_dsp_to_ex3d, int worker_id); +//extern int dsp_worker_tile_1(chanend c_dsp_to_ex3d, int worker_id); +extern void ex3d_task(); +extern void hid_button_task(chanend cc_mic_level, chanend c_hid, chanend c_hidSendData, chanend c_uac_vol); +extern void AudioHwRemote(chanend c_hidSendData, chanend cc_mic_level, chanend c_uac_vol); +extern void dnr_dsp_proc_task(void); + +//XUA_DFU_EN do i need it? + +// tile[0] +// 3 DSP processors +// XUD_Main +// Core USB audio task, buffering, USB etc +// Endpoint 0 Core +// i2c_master +// DFUHandler??? + + +//IAP?? + +//tile[1]: +// ex3d +// hid + buttons +// 4 DSP processors +// usb_audio_io +// + +extern unsafe chanend uc_dsp_to_ex3d[DSP_WORKER_COUNT]; +extern unsafe chanend uc_dsp_to_dnr_t1; +extern unsafe chanend uc_key_to_ubm_t0; +extern unsafe chanend uc_audiohw; +extern void key_sender(chanend c_key); +extern void key_receiver(chanend c_key); + +#if AIZIP_DNR == 1 +#define USER_MAIN_DECLARATIONS \ + chan c_dsp_to_ex3d[DSP_WORKER_COUNT]; \ + chan cc_dsp_in; chan cc_dsp_eof; chan cc_mic_level; chan c; + +#define USER_MAIN_CORES on tile[1]: {\ + par\ + {\ + ex3d_task();\ + par(int i=0;i +#include +#include +#include +#include + +typedef enum { + AUDIO_PCM_44100 = 0, + AUDIO_PCM_48000, + AUDIO_PCM_88200, + AUDIO_PCM_96000, + AUDIO_PCM_176400, + AUDIO_PCM_192000, + AUDIO_PCM_352800, + AUDIO_PCM_384000, + AUDIO_PCM_705600, + AUDIO_PCM_768000, + AUDIO_PCM_1441200, + AUDIO_PCM_1536000, + + AUDIO_PCM_32000, + AUDIO_PCM_64000, + AUDIO_PCM_128000, + AUDIO_PCM_256000, + AUDIO_PCM_512000, + + AUDIO_DSD_64, + AUDIO_DSD_128, + AUDIO_DSD_256, + AUDIO_DSD_512, + AUDIO_DSD_1024, + + AUDIO_MQA_44100, + AUDIO_MQA_88200, + AUDIO_MQA_176400, + AUDIO_MQA_352800, + AUDIO_MQA_705600, + AUDIO_MQA_1411200, + AUDIO_MQA_2822400, + AUDIO_MQA_5644800, + + AUDIO_MQA_48000, + AUDIO_MQA_96000, + AUDIO_MQA_192000, + AUDIO_MQA_384000, + AUDIO_MQA_768000, + AUDIO_MQA_1536000, + AUDIO_MQA_3072000, + AUDIO_MQA_6144000, + + AUDIO_MQA_64000, + AUDIO_MQA_128000, + AUDIO_MQA_256000, + AUDIO_MQA_512000, + AUDIO_MQA_1024000, + AUDIO_MQA_2048000, + AUDIO_MQA_4096000, + AUDIO_MQA_8192000, + + AUDIO_NO_USED = 0xFF, +} audio_sampling; + +typedef enum { + PCM = 0, /**< PCM */ + Reserve = 1, /**< Reserved */ + MQA = 2, /**< MQA */ + MQB = 3, /**< MQB */ + MQA_Studio = 4, /**< MQA Studio */ + DSD = 5, /**< DSD Native*/ +} audio_type; + +typedef enum +{ + USER_CMD_VOL_UP = 0x00, + USER_CMD_VOL_DOWN, + USER_CMD_PLAY, + USER_CMD_NEXT, + USER_CMD_PREV, + USER_CMD_FORWARD, + USER_CMD_REWIND, + USER_CMD_MUTE +} ButtonEvent; + +typedef enum { + AUDIO_FREQ_44100 = 0, + AUDIO_FREQ_48000, + AUDIO_FREQ_88200, + AUDIO_FREQ_96000, + AUDIO_FREQ_176400, + AUDIO_FREQ_192000, + AUDIO_FREQ_352800, + AUDIO_FREQ_384000, + AUDIO_FREQ_705600, + AUDIO_FREQ_768000, + AUDIO_FREQ_1411200, + AUDIO_FREQ_1536000 +} AudioFreq; + +typedef enum { + AUDIO_CLASS_UAC1 = 0, + AUDIO_CLASS_UAC2 +} AudioClass; + +typedef enum { + AUDIO_WIDTH_16BIT = 0, + AUDIO_WIDTH_24BIT = 1, + AUDIO_WIDTH_32BIT = 2 +} AudioWidth; + +typedef enum { + MODE_USB = 0, + MODE_OPT, + MODE_COAX, + MODE_BT, + MODE_HDMI_ARC, + MODE_WIFI, + MODE_AIRPLAY, + MODE_SPOTIFY +} UserMode; + +typedef enum { + START_BOOT = 0, + READ_PRODUCT = 1, + READ_POR_CONFIG = 2, + READ_AUDIO_MODE = 3, + READ_USER_CONFIG = 4, + START_FINISH = 5, + REPORT_APP_STATUS = 0x20, + MEDIA_CONTROL = 0x21, + SEND_AUDIO_FORMAT = 0x22, + SWITCH_AUDIO_MODE = 0x23, + SEND_OUT_VOLUME = 0x24, + SEND_IN_VOLUME = 0x25, + SET_DAC_VOLUME = 0x26, + SET_DAC_MODE = 0x27, + SET_DAC_M_GAIN = 0x32, + TEST_CMD = 0xF0, + FW_VERSION = 0xF1, + FLASH_ID = 0xF2, + // EQ命令定义 (0x40-0x5C) + SET_EQ_MODE = 0x40, // 切换EQ模式 + GET_EQ_MODE = 0x41, // 获取当前EQ模式信息 + SET_MODE_GAIN_AND_NAME = 0x42, // 设置模式整体增益和名称 + SET_EQ_PARAMS = 0x43, // 发送EQ参数 + GET_EQ_PARAMS = 0x44, // 读取EQ参数 + GET_DEVICE_INFO = 0x45, // 获取设备信息 + RESET_EQ_PARAMS = 0x46, // 复位EQ参数 + GET_EQ_MODE_COUNT = 0x47, // 获取EQ模式总数 + SET_AND_SAVE_EQ_MODE = 0x48, // 设置并保存EQ模式 + SET_VOLUME = 0x49, // 设置音量级别 + GET_VOLUME = 0x4A, // 获取音量级别 + GET_LED_INFO = 0x4B, // 获取LED信息 + SET_LED_SWITCH = 0x4C, // 设置LED开关 + GET_LED_SWITCH = 0x4D, // 获取LED开关 + GET_LED_STATUS = 0x4E, // 获取LED状态 + GET_LED_COUNT = 0x4F, // 获取LED总数 + GET_UAC_MODE_INFO = 0x50, // 获取UAC模式信息 + SET_UAC_MODE = 0x51, // 设置UAC模式 + GET_CURRENT_UAC_MODE = 0x52, // 获取当前UAC模式 + SET_EQ_ENABLE = 0x53, // 设置EQ使能开关 + GET_EQ_ENABLE = 0x54, // 获取EQ使能开关 + GET_SAMPLE_FORMAT = 0x55, // 获取采样率和格式 + SET_GAIN_MODE = 0x56, // 设置增益模式 + GET_GAIN_MODE = 0x57, // 获取增益模式 + SET_FILTER_MODE = 0x58, // 设置滤波器模式 + GET_FILTER_MODE = 0x59, // 获取滤波器模式 + SET_GAME_MODE = 0x5A, // 设置游戏模式 + GET_GAME_MODE = 0x5B, // 获取游戏模式 + GET_FIRMWARE_VERSION = 0x5C, // 获取固件版本 + CMD_NONE = 0xFF +} UartCmdType; + +#define CMD_HEAD1_POS 0 +#define CMD_HEAD2_POS 1 +#define CMD_VERSION_POS 2 +#define CMD_CMD_POS 3 +#define CMD_DATA_LEN_POS 4 +#define CMD_DATA_POS 5 + +#define BOOT_CMD_DATA_LEN 17 +#define FINISH_CMD_DATA_LEN 15 +#define PRODUCT_CMD_DATA_LEN 61 +#define CONFIG_CMD_DATA_LEN 14 +#define AUDIO_MODE_CMD_DATA_LEN 5 + +#define UART_HEAD1 0x55 +#define UART_HEAD2 0xAA +#define UART_HEAD 0x55 +#define UART_HEAD_LEN 0x05 + +#if !__XC__ +typedef struct { + uint8_t I2S_Mode : 1; // I2S Mode: 0 - Master, 1 - Slave + uint8_t Sync_Mode : 1; // Sync Mode: 0 - Async, 1 - Sync + uint8_t MIDI_Enable : 1; // MIDI Enable: 0 - Disable, 1 - Enable + uint8_t SPDIF_In : 1; // SPDIF Input: 0 - Disable, 1 - Enable + uint8_t SPDIF_Out : 1; // SPDIF Output: 0 - Disable, 1 - Enable + uint8_t ADAT_In : 1; // ADAT Input: 0 - Disable, 1 - Enable + uint8_t ADAT_Out : 1; // ADAT Output: 0 - Disable, 1 - Enable + uint8_t DSD_Out : 1; // DSD Output: 0 - Disable, 1 - Enable + + uint8_t Audio_Sample_Rate : 4; // Audio Sample Rate + uint8_t MQA_Enable : 1; // MQA Enable: 0 - Disable, 1 - Enable + uint8_t Audio_Class : 1; // Audio Class: 0 - UAC 1.0, 1 - USB UAC 2.0 + uint8_t Audio_Width : 2; // Audio Width: 0 - 16bit, 1 - 24bit, 2 - 32bit + + uint16_t Input_Channel : 6; // Input Channel Count + uint16_t Output_Channel : 6; // Output Channel Count + uint16_t Other_Cfg : 4; // Other Configuration + + UserMode Mode; // Mode +} AudioMode; + + + +typedef struct { + uint16_t vid1; + uint16_t pid1; + uint16_t vid2; + uint16_t pid2; + uint8_t ManufactureName[16]; + uint8_t ProductName[16]; + uint8_t SerialNumber[16]; + uint32_t crc; +} ProductInfo; + +typedef struct { + AudioMode audioMode; + uint16_t muteTime; + uint8_t micVol; + uint8_t dacLvol; + uint8_t dacRvol; + uint32_t crc; +} AppConfigs; + +#endif + +typedef enum { + USB_MODE = 1 , + OPT_MODE , + COAX_MODE , + BT_MODE , + HDMI_ARC_MODE , + WIFI_MODE , + AIRPLAY_MODE , + SPOTIFY_MODE , +} UsbMode; + + + +typedef struct { + unsigned char startFlag; + unsigned char startConfig; + unsigned char startApp; + unsigned char cmdCount; + unsigned char cmdPending; + unsigned char pendingReboot; +} UserCmdConfig; + +#endif