From 8dddc3fd48015566164ccd91fa5350ffa9a7164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=98=8C?= <392871505@qq.com> Date: Sun, 17 May 2026 22:16:20 +0800 Subject: [PATCH] refactor(acoustic): Add docs, gitignore, cleanup deps, enhance WAV reader - Add README.md with module overview, build instructions, ROS integration - Add .gitignore for build artifacts, IDE files, Python cache - Remove kiss_fft references from build_demo.bat and CMakeLists.txt - Enhance wav_file_source: support 8/16/24/32-bit PCM and 32-bit float WAV - Verify 20/20 validation accuracy maintained after all changes --- src/drone-software/src/acoustic/.gitignore | 19 +++ .../src/acoustic/CMakeLists.txt | 6 +- src/drone-software/src/acoustic/README.md | 127 ++++++++++++++++++ .../src/acoustic/build/demo_offline.exe | Bin 210855 -> 211439 bytes .../src/acoustic/build_demo.bat | 3 +- .../src/acoustic/src/io/wav_file_source.cpp | 63 +++++++-- 6 files changed, 205 insertions(+), 13 deletions(-) create mode 100644 src/drone-software/src/acoustic/.gitignore create mode 100644 src/drone-software/src/acoustic/README.md diff --git a/src/drone-software/src/acoustic/.gitignore b/src/drone-software/src/acoustic/.gitignore new file mode 100644 index 0000000..98b8aaa --- /dev/null +++ b/src/drone-software/src/acoustic/.gitignore @@ -0,0 +1,19 @@ +# Build artifacts +build/*.o +build/*.obj +CMakeCache.txt +CMakeFiles/ +cmake-build-*/ + +# IDE +.vs/ +.vscode/ +*.user + +# Python cache +scripts/__pycache__/ +*.pyc + +# Temporary files +*.tmp +*.log diff --git a/src/drone-software/src/acoustic/CMakeLists.txt b/src/drone-software/src/acoustic/CMakeLists.txt index 0055e85..a0150e6 100644 --- a/src/drone-software/src/acoustic/CMakeLists.txt +++ b/src/drone-software/src/acoustic/CMakeLists.txt @@ -94,7 +94,7 @@ else() message(WARNING "ONNX Runtime NOT found. ONNX tests disabled.") endif() -set(KISSFFT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/kiss_fft" CACHE PATH "Kiss FFT root") + # ── Core library sources ───────────────────────────────────────── set(CORE_BASE_SOURCES @@ -126,7 +126,7 @@ target_include_directories(${PROJECT_NAME}_core_base PUBLIC $ $ ${EIGEN3_INCLUDE_DIR} - ${KISSFFT_DIR} + ) # Full core library @@ -137,7 +137,7 @@ if(ONNXRuntime_FOUND) $ ${ONNXRuntime_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIR} - ${KISSFFT_DIR} + ) if(YAML_CPP_FOUND) target_include_directories(${PROJECT_NAME}_core PUBLIC ${yaml-cpp_INCLUDE_DIRS}) diff --git a/src/drone-software/src/acoustic/README.md b/src/drone-software/src/acoustic/README.md new file mode 100644 index 0000000..514eb7a --- /dev/null +++ b/src/drone-software/src/acoustic/README.md @@ -0,0 +1,127 @@ +# Acoustic Analyzer Module + +无人机声学威胁检测模块,基于深度学习(CNN-GRU)的枪声/爆炸声分类器,结合麦克风阵列 GCC-PHAT 定位与 SPL 衰减模型测距。 + +## 模块结构 + +``` +acoustic/ +├── include/acoustic_analyzer/ # 公共头文件 +│ ├── core/ # 核心算法 +│ │ ├── audio_buffer.h # 音频循环缓冲区 +│ │ ├── distance_estimator.h # SPL 距离估计器 +│ │ ├── feature_extractor.h # Mel 频谱图提取 +│ │ ├── fft_utils.h # FFT 工具 +│ │ ├── gcc_phat_localizer.h # GCC-PHAT 声源定位 +│ │ ├── gunshot_classifier.h # ONNX 分类器 +│ │ ├── pipeline.h # 完整处理流水线 +│ │ ├── threat_tracker.h # 威胁跟踪/去重 +│ │ └── types.h # 公共类型定义 +│ ├── io/ # 音频输入源 +│ │ ├── audio_source.h +│ │ ├── mobile_phone_source.h +│ │ └── wav_file_source.h # WAV 文件读取 +│ └── ros/ # ROS 包装器 +│ ├── acoustic_node.h +│ └── threat_publisher.h +├── src/ # 实现文件 +│ ├── core/ +│ ├── io/ +│ └── ros/ +├── tests/ # 测试程序 +│ ├── demo_offline.cpp # 离线推理演示 +│ ├── test_core_lib.cpp +│ ├── test_classifier_cpp.cpp +│ └── extract_mel_cpp.cpp +├── scripts/ # Python 训练/导出脚本 +│ ├── train_binary_classifier.py +│ ├── export_onnx.py +│ ├── generate_dataset.py +│ └── verify_val_accuracy.py +├── models/ # 模型文件 +│ ├── gunshot_classifier.onnx +│ ├── label_map.json +│ └── mel_filter_bank.bin +├── config/ +│ └── acoustic_params.yaml # 流水线配置 +├── third_party/ # 头文件级依赖 +│ ├── eigen-3.4.0/ # 矩阵运算 +│ └── onnxruntime/ # ONNX Runtime C++ API +├── CMakeLists.txt +├── build_demo.bat # Windows 一键编译 +├── batch_test.bat # Windows 一键测试 +└── package.xml # ROS 包描述 +``` + +## 快速开始(Windows + MinGW) + +### 1. 编译离线演示程序 + +```bat +build_demo.bat +``` + +### 2. 运行单文件测试 + +```bat +build\demo_offline.exe dataset\binary\val\ambient\drone_noise_val_000.wav +build\demo_offline.exe dataset\binary\val\threat\synth_threat_val_000.wav +``` + +### 3. 批量验证整个数据集 + +```bat +batch_test.bat +:: 或直接传目录(支持递归子目录) +build\demo_offline.exe dataset\binary\val +``` + +### 4. 使用自定义模型 + +```bat +build\demo_offline.exe some_audio.wav --model my_model.onnx --label_map my_labels.json +``` + +## 模型训练(Python) + +```bash +# 1. 生成合成数据集(或准备真实音频) +cd scripts +python generate_dataset.py + +# 2. 训练二分类器 +python train_binary_classifier.py --data_dir ../dataset/binary --epochs 50 + +# 3. 导出 ONNX +python export_onnx.py --checkpoint checkpoints/best.pth --output ../models/gunshot_classifier.onnx +``` + +## ROS 集成(Ubuntu) + +```bash +cd ~/catkin_ws/src +git clone +cd ~/catkin_ws +catkin_make -DCMAKE_BUILD_TYPE=Release +source devel/setup.bash +roslaunch acoustic_analyzer acoustic_node.launch +``` + +## 依赖 + +| 依赖 | 用途 | 状态 | +|------|------|------| +| Eigen 3.4.0 | 矩阵运算 | Bundled | +| ONNX Runtime 1.17+ | 模型推理 | Bundled headers + DLL/lib | +| yaml-cpp | 配置解析 | Ubuntu: `apt install libyaml-cpp-dev` | +| kiss_fft | ~~FFT~~ | **已移除**,使用内联实现 | + +## 已知限制 + +- 当前分类器基于合成数据集训练,仅用于代码集成验证。实际部署需真实威胁音频重新训练。 +- Windows 下完整流水线(Pipeline 类)需要 yaml-cpp,离线 demo 已绕过此依赖。 +- 距离估计依赖 SPL 校准,合成音频的归一化幅度会导致距离值偏大。 + +## 许可证 + +与父项目保持一致。 diff --git a/src/drone-software/src/acoustic/build/demo_offline.exe b/src/drone-software/src/acoustic/build/demo_offline.exe index 6d0464a2c9e0de44199e5aca4af4a435ecd2219c..4aa585407872771226605da7c6b787b986a531c2 100644 GIT binary patch delta 35627 zcmb5X30zfG_Xd3S#tVqby(%E0!W9wG9B>9lP|ypa;yf!(IH97Vl9^yYidXdFHjk+l znx#3Cn3!3DvyEhvWEqy_LDW#woRaTZ=Nynz@AvzEAHVn9wbrxOUVH7e*B;Jb@2)>< z|9P*rW3|_gFKW(~K4H?u+LBZ*1xmFzZzBa{zuAp{@N}OdaSZi^cwP-mOCDs1IB&ncn;BTPBY17Q*CIPyETf*qg%HsRhdsZSlK9jC5)t-f{8}*-jqph}`KSyAubkW~NjAG@ z_n}O(*UPk2%C#!x4Gu}ljJ%fi@-^v2w+~&ZUW@m|FD{LOw{4}Grq5zgQ%ZYSvDody z3j8UbY8Kxf!l-yHk<3En+8)u$b&zL5?tjd^m@Been>QJX5;*c3%%R%nL{M!!P$ho7 z_{wz-AG$~Ms2LdKV`k)$-Spl*CS?>XcC$xbgXU)kCCP4jUBgVu+Jkv(YIb7$%LAgo zEsEzI5VdNx;A0PnPPHa;mjgnsHJV56&%0A=4(n0i%%s#8L!_jJ=1+$7wPhA7D>C6s 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 delta 35133 zcmb6C2V7N0^FNNCUATw{Ttq-blnbI_zhVO`Am~L=iCtsCh7A=J4OUPvkz6lcU3G0q z&=_M&L`}plR_r0BJZgN5nnVv`K~0PrBfs~ab0{ZyzOT>!&nt(Seedkd?CfkgMe^ji z>*r5g?Mr=5U#dDuIzpsES4k?78c5YBZ6*b+__#gw-?`#O50c92lgTa~fkcu#BuNTl z>q!$5!p@S0q%nI;8a5h?no2(rl9b;(dF-f^QIeEe6%}^WyMPX5ovELDjwje0Kwh9@ zSt9KN3;St9vYy?-?~TmKp&>Ikc(F(aFOtIsIy5AE*+TsOifu*BXQ;V@ngi?wN`E-i z*k^WpPHAK=t6wFMu35({RT@|MaEBzxzinZ2s+hVR-YrSRE(e8=@^0{Q5GpNJ?+$&5 zWcAGQC@WVhD?iJYq^y`6W?YoGyB!8BRHRe}HHi4wouujDc^20CoeOR?Mq0PTw`7W`v z#uuzwwTAT2F4nr*C^~)@*rqJwK5Z;)@U@ch(>TVJ(D#z1{#N>LcuIqPv!VbHp1A<7+8T_m%TFCdZXERH=|tc zmHa~nyKZb`+);+bWxbL=%3#$!8X0GlVV0~Sd0YmI^=M{nTZYBKMxzY21afZ|Yx?3K z_jEBk>e1P24={^$S#UaBRD6;@SJ%kNiDj4w(vTD-NX;v-A~`Tc3DTXJ{|wSMGuh7pA9U!3 zigG8zh18OtW}UWJ`BkPvrP@xw~j%b+A|?WQ+AH zhZ0dMR2c3{Qu;znqCn%ZfyyKrK2azWlPKif5k=imG6tkW9N<%V0*VNVQ`t<4yu#`< zZA!lz&w4hEr3qHHrs+MJzmzR(){=hoKHJkQv`5w@G_}PA$IE{vs+?K=yPP{UUXn8N zjhWT3wpnaR!3h>y|6sEi`gT~r7Do5=1o_outZveM?EL3zIj1E-44YxJ2$ zuW0n)FRWjSWV&Z8JJVu09Xy5&GzXG#EY+My7P6nsE2!fbHlt;9_`uP^Qq4GPcjq|U zmS71ziOaeZOA6(U=THN`OaiifrBJyP;;5IajA2h&`cQs`RSE4*kBwsSp+1ebjuN{1 znc}QnLqw*nK{@h(*nlvWIbO=@YRM{)7mi}~P#?d<%GGl#s(X)O`Jt`+VqvPhb-Rjc zGK#si3ZNdNS@TvtVdoNr%_?eY?5d)s#v@~<{Wel#L6~IBbaFrv92GF{*pmnfu zq9URV^ZX3fw{;|aHIl7v-Gn|I!A`agre{YmxphRhUq=XCN~A@G{2+>1XDn8LBCU@Y zRNqUDnX7NX_cWw+it*SW_+2E+!U+7nHj+(kQ-_j~%+{v6YmQ>NipA!(@HD&KW(+;J zh{d#R;U+0~45IG#-&1T++r#|>Z%R_pB>Y17i^4S8K%><)`s63IezjIUsL?2muF~js z(4sLnnY~>L`rErKvSVHHJ-gj5m2OO9L&GezZyMVY*421sX!)XZT|O|By$I_`4-aLg z_BGkiaCf?FA)6GwoL(Hl{tB;0e;k5}`gHdK*1i2K8n%EPYk!a~n9t^P*hCv#WBwiM z(Q$*=iFS3#H!PuJ9~v`{?dcdn9p+)e?BnaWB#FQBQZY+;vzY7M`~VJXoV5G_f})G>gz=?9y2 z=!lDoQSbK@qu zdeo;+E$nEIT7GMAuoDY|lP2V@(Y8HUNe>^ozXx;fS%-dLVGVn>=;WgbzaOh=&IXOc z`WX?^@_bCod`s31aZIp_T1-CShc@}{$W2s5Z@RO+JtL@REc?A@YoD7IA&IR=>^~Dy zB84MsV_E08+F=(_)sYx8i?z-$o#?FNVr@;3mjD%w7AUkxo@HSx;&##9$5@wMOX+Vh z?0heOS`fqT_e!VU``N_aCR#6sS$p@PqxZ3!z4xTGJtKN@wrKD_Xo6Or_757OmCYI* zrO_9sRXy)&<$GGWr&jI-s_kfzgCYk;4v6eOV9>yR`&{FHBGfIB6}=Nr507IV`p&1F z#<7Ea<7x3&R;}MMI(#f!*Dsz{8_SCO&8N08Y-0aDwBs0ds6VHlj%NJ^MAPA;*}4G` z8O@3Zj7&S(NyJoJST|}5>rckaXUDKO%@Ygj5O1-t_Ume~&PVcLb^WHZaQ=jR95nKP zzZf=~_ggG$qH@OnL*GZ``mT9Z(w8RMq-hGLDGD2_3a@GkgZ>{16U!A2s;p4xYoX~2 zQ1p4I`o7TgUF|4I(OIvB-2F~cm>ACT#d)I>gph>jzeBnDb2T)7F@t^-&YT#S7+HMCBT6mM`&k+-NVqOd#YTk zQ1;WPjdZ~PHfQvZszC#=*ja}KTiCZl{Ma9(qv+B8EPPBno!p;U$IK^>Sk5>z3mY3i zKkCPZj9p0U^kdh@_Mw;hvc}`u(gwL~^teuRKwq|LTnoB?H)}t(4mI>;Psi1Eor{G% zt7J*OacS~y=9^fLK5foAB{mJ(4_sX|Hen#uMMDJbVsq8O!_ApBv4P{e&D0>%eP6P| zL?4$#t$JW{R+3ns@}{ij_&}P{l(ip!lrH~*y&7MS4sOQkPO#AIK5W8-wsc@0mNTI_ zZP14mg1qRxa$*p@+M5MWY(RJPW^ofubZKukbz(QVbS+ynJeb{>*xogviHM>D#-p>H znI_eDZBY)m+1Wb)-eT!dC2lTfive6N2X-!J`v7b#2j(tkzXF(C4#X~J-jnOQwl4>K zm$PmFs+I%yv)Ow9t~DyNvOk+`1Mqn{uq2yZpFGq*cDo4IsiHxkMK`oU4^jBf_I-Vl z90{!&!v;)Q5*pQ22o|J^WjYYafW9q1hxBh;3IARkdTnl8;GSKX>r{)|xhQ2DjI)~M zqfum&(FZT1c{?`3UJ^i-J(uW=@@ zG&(7NypE+ON78xq*s0{{v?PeNogUn6Qa#a`{@90b$0BZG|#zLW>CDl^*9mw~MF zj2U!G5ZgPWz9l9QT|5cj;>0?r_!i2AfdNu)o8uQE62;q#o*?zbn7!7hd#+l3qm^sq zvVfTx?OaTvC7#$=tQM1H$@vr?i!~m%k^*UTK`su^st*l@TFiai;f59c5Wud_9NKD5 zS>^w3JG892tgUPbV2LTS>AAY>R!T4LmeYjB4$v6!?ue16gtJY*WD&Fcsp(xdV%GK= zbI_NbHvd?Q&Go;cSMstt%x`uKjj6*%&2CMnMKV5n5X~OKN@j=AOn=t+y-eD87t4Jw zj2@}Y9=vxm@SY!H@oiD@4q@q3(M5$8l$T3)us%t(R?hLHEB%<)oc45tAB&&UoqGH2 zTQ{d8q2oKS|IF=5L&KT>yfxIN{l2f}l@fZ;hkd;uh}LhfC|wEiQ-s8XkQD9q zVx>{ zK0C~iv1=$<{k=iS>M5RV&!RrYOQIRFIz-HkZ#{6a;7&9|-B&qa=jIbbM z=8_!dlo{f+&?q*SxZqTThQx+nH!dEK!=f@f(EA=NEwc_S^k5%nHl`ouunU=A(dOXMvrRrny7rK6bdZ1O!cM)v)f$cj-^oCmOmB}(HmH!5B)txRuve|q9CVjnjoGw%{r?-+!`4<;uH=GMVAzvXN!hxV23^oqb|+Z?;pm{R#jOCYfsvsDO+ka z)pSIz7y}{@p3`Bc?1{~d0h#Ey#GY79p}&dEF5@LJF8LJDqWQt*zOmi98JDb9OJTUn zh%qkt0Hwr?`N7d-RvPQM%+E0b`q;E(J~XWf`(RmMS`yU6S$l>=9zcRghs0q^EV51I zuA2Uo&y>(&%g8vLAl4c6He2q1dShPCu$EUX1zjy@CS`T)Xw2(sPCQZMV9Yyj+Q9BcSsj4)~fOev9o638Pt;eltv;^4q%ba z%ujJu=Cfwl0%zsrLb9N1h%l>oW_tG&R7;8FF_M_w){1Omr)|;HaUFBXuH$tX6U&nE zmOL|hBn5}ycl{=3ScmL}O`kOqF~B{wm;h!`QG(kyRjik-KYi1y(S^9IZvnLVd0%$6 zKS}G0IN9x$@8r;(!Ex4!cz*VcCF?t+u`#oggHSu&VzEw)h->smT=$Ptlt3AAD?&W7=pEA%Yta#9 zwTfJHuM~T2?1{S$&y5~#w@!#qBn6vejCq&(*oL){T0T;fUI`4&2uDTGEjF)^mifiK zkam$TPCKR=u;oX2c?ZuzYEqcmlj#%2@{iv!X-E*vX@aY>vqfanVzR4RRq7B3peGzJgoS2bjmQq@_d{OMK_E?Au2}IgrbDWKvY;^mi{DW9=5n)Tl zSrT57hI>heG>dhDv!zLh<;35#=nJ7sameZt3D2Y8`J2eBo^OhNhe@%dMH%zrLf<9= zds}o{!IY&9p)UGPJmP9}QY-*dXNi&QV=lTZwho(P-(I%X`MtvbFlHVB5SR7BZn3(H zQMP>0ErgWqze~L2ONy@CU|B;RyzSTEgX74~p)I z5;g_SaHI)HE3Ln|Bf%6eOYGA6JKBqKY5nyY{ZONGH9AS7Lp9n%qwO`?Sff|NRXry) zx>=(uHM&^PG#Hwp0i!kASEErH4b^CnMm;r3HTo<}HFQ^_S2cQ4qkA>FS)(g8x)`*L z=;;g%7_HI18jaFus78Y{>bY;s>RzPT087?yVsS67i+8g>=U_CfQfVRh?*yNb?`+93 zoKs-Ki5&I?uS5HuWS4lbf76rLL#|q^j)h_s`qdiqCM)J;u@_o;o@B0TO@TdUm6qDE zZkmUmDl=j={1l+P*)SMuUFgBXZ20=w zbk1S+!}^|dSRIzIu`Y|);7nT!wssYZZyb6ecptWSLje87pMAQaQT3cd!k3fsIgx-? zw@v;KsOe$zA{J{*i6tw_1aPhZYq2^TYtv}K`mp$o?zFC8n;8pQ5fNX6tP4c%u)q!e z>;S6I9%Sb>4kLpB__9tPxs#THGpd@eL9++MU4Z#9+edyaKMNIVpv4bSGb;t$8QKhn zgTlWP@(-AmRzsd2kuvg0mf=f3cI~4evY3tB=;v<5yLDq`x&%q9+fV^jiM9&$;o#xr z#|BapTurv%W54QOw-knoi9I9c4Y3+-)!Mi1<1U06^7a*d5=N-QepYYOHk$P{yS(XW zy|>8Eek! z%yNAv6(I~LE8OYN#&2uau^Z~p2wR4sM!8t5B359i4@3-6@d8;!&RY$>0C13~!c z9_+@p(7>BUbr1^-7c>s4Hh@)ou&DuSwtY_MLI6q>g>aELH^57igtMbfX=e$KF{Sw9 z8QXtR2kfse*tP9F!>?nXR`q;dUmMXE0@9D@F9Na|c9je7sLzJ&$m-JtK!wrkI-^!0 zTxRrR>_kv-1SIl6Hz9{vRD_(h8kh~4D^kj(K5M-*)Ho1jJbrkoVBZF_Sv%)fySPk~ zi3(X%5uc}FyUG>mViT1CeLs;JtSw%Kw zjMcCi?UgRns>kYo5tKFyQVe}>hHo&CbyL;kR`52J(j{rUwynz*@x_1(RV zhVEc*cK4w7wlhm^U%GNT+nBqRKG?>h_p}vX5Y68+l}2r25BL1&(irQW*c1Ky*rmNY z+t>eG^j^~TA*;dgkMeI@E7xZ{M4)@&F@@o%f$iDXh^{p3ySZ;Fp?f#8xUXYeCT~{m zHVl(Dv%-_LTv8vEmf~K+VA;%$eZ7eOu!%+PH`5K9L?v6c-;>VW#B%mGq%kUTcYni> z8k;bmI^pnYa06#-9^)|=2TKn_X^vW&p{{8!2n>ftA!Z;f#Hl))(>T* zu-N0t0G1xZHD#P}n79c+F#wBA#BG_Y5m1D)tHsh z54Cby8S{V9m0E-K|3A{rn%w_E$JGC)z+SE4|3wdJa{o7FHxK?4u;e50yj2WFZ84s+ z%ZjsH5%>uou^$h0roJCBpTk}0cN^KehX=YdYz0N}PRi7JZzDT?c&u+DK+lXLVmuNp z#)bJs;^`1t|Fwa2II_%r$_B`{SD{54*bhg7Xs-?I)e(R9?xN-)Zc`y#Zv(4$G}Qg) z^?-J%&@b!Rn4>{y_W>mu2c39ktR7QR>;f-iJQQB|yI!em9#f*aZ!Ie-?kf*XqM%H) zDZwwd$x3~T6SEs*68gg1srhn4EO%HeMSuIFx~RHpJxuThM~OKe^KthRh5O>R)oQ4_ zo`oEnPWP>28;<$Wymc(^*lH@TWxbELrZ?8IOps-3*`edD-4oZMRSl}NYQL7fIUeNR zSwO#F#egEewXA9WB$qXKt}D87i?f`3Q@tZ77DYlrIPHs}G3*60cS?EjQ_gE;Am$oa!)(q>C$7_(YuH~WM!H1d?px@MT7yaH#ZphY(SSAVgOgoce#G+^ zQFCiGY686vqgdQ}(SkP;25FWEupxIfrnL|AKb7oLh})a%C$F8qb@%$|Yrot&f9u3g z%`VMin@^3SU$4T_P?t44-I0!3#m1cuqXDbfC#PGtdBxC&EX-2jnul=h36R;*vibMY z(gG<+tx;B+W2g`ok1@qd|1)l`8vqrz72tNp+MMZ?HVHtYvHyv(Nqf>oowR#3x>=(u zHM&@%Gc-C{qkT0RrO{B025Hn&qg12Msw#GB*T1VW^{;C5q(=8v-RFNcn9$QJSX4o} zDQZR8T=Ey*w<;^naudbi73^+7gX)ez)l^o{zwNBnxq39;&N`nPOXu3z7LefUjpQ-5(5P26o_T6h15d>uPHBSkdy4c!s1ry~764=FS2({rhFK#rl1v-!~&9fi?SaC!IfU z-;E#L94zC0#yY>`CvjKRACHM~ci!r-xMfivNu}T*u_`zn*(dgbT3SuA*k+U(gj=a8 zJ;Ykv=-;U57H;41;3;IA(U{o^b0hMA*m`hQdoojT(v%{cy!r>*b)!L(^^g`%tF{@T z+?d$|t4O*0$Ey4{Af?`m{$PLK@FPo@?@x7|x2{tkLao(m31caz*Mn#*u34;`ff`7%M$Up4BxnvS3ef-U2JX*mFnxL{zvmG zYJC-@{-CHA$M9TbVOkcPPuPz?*N-5mg_6dKPzr?P-K+3qB;-UEUWUbq2h0{c2MUq5 z|Dbd|LVOi$7LV#eFi>@B&tM&H`=@=TS`r4)5h#f~kg7(}s@6m+>fb)8R5M+zQ4Rm9 z)T~t73{q>-K}x+Zphh;pCE0{ukp~QFXf{A0+`gpf9kEquu9>W*HovMgU#8S(jmE2u z)+&u=pau_Git$#;&3y`t#C|2RC=-D}h1FROye6LP6v?$%>pPtrgnlbd7iWu)$fn0| zrZ#?w*7*XU z`0a#OgWaWgW~w{~#4~o2#o8=-CfjkZ2`!kx{&O!he#4g{6m@pZfM>R`!FY0d$71X1 zt31yI6Jg293@*W7$(~Szws~&X5KVtaq!KLaJrB|R5jO7;h zqZ4~E!^2_Z6dV0;5ZxEY4nOQumnVu&W(MOpfF2mKwPKD4+}qDt$fK^5#IY%lnpFS3 zCte?%FSR?Xr;jr|+4e_4bW=}u`B5mH(vy+L;WV@->-e}1xyXj%w;M}++=8Cz!L~mR zC9&-0nIYkTmAk| zV(6BsFA>q_Le}x2`!=fnRBjpNq7z z8ivF$@85%Jwo&SyYIT39b*32B`}aV)=$_u<5eee-*(T0uEzR)9!|Z*;H_;BCqS zsOcBYYX4E+w`sKSu$NT2@+BP9#snYvvcZ4UA$e@}AI+M^Vm%T;ipDQXR$*~l(AcaW zpksGez1yF3BsUoF{r|Orw<7e z-b*5d)8bq^(#Pi`8MHKP3>pJjw zqmZ49yI=d^AGBO}9ZYVqC$HZliTEANtbYZ%nAbxc0#>r0?f+|b)wMX3 zU{M+}hP8PUO1@?j-h_~7w&G1k(uG}l)4_akE$Ta@1jnaD+jVa(4ChCSzF}fVuWsc1{Bfs)M_S?i8M10qS zQ_QQltg*qSIJ{5`kvj<^LQvj`miR|e!3y9#V7So!N}i?YRrUq5yc8nE4$7>1B~Mmr zuFDQME!b)s27h!vf@1*6)3j0d_37a3WMCBG<=yN(S}D567I`F24#bE5$Bn$MHMxcw(fGLVr|PHc z3Cb5AUHJodVj@Ajnvt9!3H-8=Oebx57Y}m1LvVGijrf$qRJqmb47J7_7yinac}ZEy z#TF{Q(GlN~+{o)!o$NxW_tzkSByG$hIE)SC(^l}lvnbf*{}ibzB9JZeRbUv`PqDcx z^CdGl9T_cuCtysOu2{kBavfF6GUbB~x!nWNMEl1eLzVW+)K+yw4Njvm3W$GDlqOeG zghdj!)KoqdOB>QUUVb7RQ4-u+tNQ_Ua98`f?dNf-p+ezNVhkHXip$exaU2f=wkQ*L0`#3 zc$_z>A)Sip+bw~08p{!wu7kBA@o7C z7^(R7R1N?xf|e^r!Ou^tjY4hlc_HttA!h!C52+K^0~JN{OW{kdaA>FawN?DuBz|oa zzqt6dQv9-sUrWWW4Do9rej!<3hQd-0@a(|EARbnev`PqcK@CPfUX0w>mv5DVNfa7Z zQEN&R_e24{yKE5jRPk~7_5y{o*mecyL!vkX`s<3x@VThK*e2kNAn)ZDYm$h_>El%_ zRW8+PljT1YPD^lEgX85}8oX4P{Z6ZIel?we=e@U{iU+ zS|qz}7X({q7NM#1SV5>xm4o1q@!;%SuGvKdq-(wy! z05_K}(I=rvgjD2YJa9o%@f85X`)cBM<^o0pBjl0e`A9$F->y0Y)o-I9Hm`-+`i&6F zD8MI#Z)bf6+(~P`OyJP!`a9q2M_SYO&0O{)^@i+M+Fh3eQ7>jTj>#ut8$FE6x@S4@ z7AKKs@(02noA)Zsz{@$RqEsAuEe8@B&K@oPiwnPPTs-s*swTgfAi{MbZ)|N+gOIOz zsy{K&XfM9rpG+dAJVPB)Oh`xmv@Y=`4S1CRk{Nuqv2gyWOxBl{;+bqrboe7u8 zcZYJ>6JVJ{32?SV+jv3chM$w)%`H|LG(kr2ARD(_#9^k{yojpCp~Lpb;$ zaz;3KP0NNtEgLQ{IVDQ+#9Z8k->>YOSl@@DiR@Y;v0iW z?SX9+3+e)|-YjRTpECan4I&9I*~C(a#SlBIv)EO;q5}64uapma?H7$CKCV7#(q-c`)voJ5 z?Pj3jl>CEIeM$t}Bo7wG!G*|EH8+0b-_$3y;~HzlAV|yJ_+>8|H43sew_D9%s;5w5 zbGs_|?%-pyt|1N1p$POxfd&c*XpWbw@U{&|BWi2KM>QZ`0bN7|!qN~28<7^vr^3Qw z{IVC748scPvDBCEYCz1jt3i#T*Ud*L90d`isn}%d@;@7pz>xl)BA48CHxEzcw^M}S znYej)kx~aI`R1O=ZCTZ83T~+G$%i*2%|kbdV#Zlh@l80Re9bFo3kVb8?T11`Pm8wY z_8TldESf$=oD3ZYL1yK6@3hXK+Bd-0<(4Rj&2keOD(6aNxHBGxq<4y`j)6-t;6NR6 z>a3w1N<`k9R9bpa;H4gTkJcJo@y|#Pn;Q|M=nrK|>3I{B+)=XN3r@V(HmaZEDdSe} zICO|8wx}>!RQtEL73S^sygh@72gx%v#+fWF`LGlr=3d!P;Xlf^|4}aaM_D?ewr{1; z2#uEM%gNX4H=fXy^Nu$mlijEkwj$7n;}fO^j)gf=1dlXRue|hdVs@mrcksQPNOMo$ zPjn3>^T&D5IuUp3Y2Kx;DBP6i89}Z%c-{VwT467v1WTCE9G5(nD4kAsQfa(Kmufb0 zHEP%BHjS>;Xu3voKoyNWO3E}=(KKrG|9_2nd!tEq4HvsvRV20KJqD9*)enB9c1DCp zj8_(aCz{mE%Uw@m9rDO9($gXBKY06e953gTBTU z3|^tTB*_dOqb}|E68{(!d@krayVEe~QFv#!BzfjaQh)G*?!lD+UeGW0pckS6=p9_C z;7dR+?u9|{g0|R)m)qb4ZTpoZZ3HhU20+>dUeE#i(PQulpc8TBgHKKa^AOiL6a-z8 zCrRIf7qsUA^bWkB8*n`YFK98Ym*55Ec+=sy2SWgQ5tj#eK|3CjB!BRN*1-p6A>c7< zrE|DigU5`OFiWMh2owZ^DJogO3ySF}#e)|VQ&buZUQkR^X)Jg_-^G;#J^^$&t`zWg z(7$k{gBLXMC~^XPGHC2E*6m)jK!K^0zHIp`8IBY4CozP&frVD zG5$6mBDF>XJ1F;uLhw1DTXD7CqSAffBfx(JdJk8B@Pa=`U!I-@FK8PRkzRrq^lw~o2$vK{q{mH(v=+{l zfDUhlXrTuQpi^84}|Ut=xbbN@KQ@68E|dni^h?(6=v5piNvdI#0eZjBuQ%Ef%L*97Fj{7 zIy)Gg`#L(1c+4kFW&rTXicDUQM3P8o@<)8{coHS(i}9qhplv6R#eyE1K$;8sK%;&W zNjFg*J&~*t^z}qlZs8=-UXhVkNvgitNu+^j_i+*l67*z} z+Kx^ky+yh26f#24y;I1Sf{va_W(fM@RMJ<_#?w?khfX7HMS0~k)sM5&)b>8fs=k5A zBvI5KOC~P_-9KHm|8}}+H)w`RyUkGb&7MK#3b|)9RK1O7s($vJsg^&SN$QIF9W%*L zL7h^_4wqzX)upAxv7LdROd&0J%y?3xh6cK>BnC;kK(-lvs={l|A`N+u$t0+}NtOC} zlV*`Mjw{U0`{xpm235`4sV#IxV9h*p4!J|9`EXv1%>;kX2TYg!8WMDXSlnpB?>y-Y zdH`w{H2!z~<_xLpv@(?%~G2M9lM z38J941-1Xl9WSd?>Au|?o3w$)2@Ev(GU?_J?XGq6$IA#MNz_8Xpb z1ua1BpbqJ8CI0ml5=hhEs!_Gp#m?l!-v}X4(>L(4piJ$KZxFqHM#V@i{`eb$mz&iI zUvY-isZtOqNo_n-`+4mPNxFgNxaJ-CfjoAgQgy!TI@wF}4LsontmYfkMtt)PMAPKR zO9X0iRMEW9pYV*?p~PQPw(BNYNN9oyo*?5YAy1H{zC8tQ9_-(^k0d)f!dKDMQAH1Qnt!gJd-!oRXyb|ilF^=0XIE> z1gPS`=m!Ap1$>zRL30I7KFhz?s3PQ3jQ-qy$j26omJgMdd0UHxY4b~d=^;?il80D2 zavt-rM<|0T-h1J(X!>2AE-+9<-N{F=ng5bM6dTSpX=)sMDl6u6ITN}{;<8)XU zYm_9c7trIuTRz2P{Q_XH6F1+(ONNjCB*8Sn#!vrA<`WB#d`Y^~WE;O-hX(P5e$<6W zyddrPlj*by|NSMg&>99FF@s|K{+Emszxaww5q0fflfg9A##afNZsU&yU25Z&zd-Fa zzFE+2Jq1TD1jcnW&W#&-yM+s5%H;09CK#zzQRV&ew{eQD#K zj-XODPZHEQo1YcbBb%EHpuX9BuAruD{=J|f*}Sn|G`NYRTqn z1dY$;C4vsl<~^K26SDahK__JMw}K{T^TAa?Q?vPALDRFjs|)DTY@R5nJ)7qXx;C5p zRRhh*<|%@1%jQ=E&CTW^uAm39`C>uyv-wRy&t>zrZlHzP+%D*~Z2myd+u1zY9aPTd z9}8NN&0h-oGMo1`f=bKzEHuz-;D!lqzu@)@&cg?shae%$(FMdN`t@kF*U5A;K)xSgA0X+5%>5A&mws-=`q>0pk3 z15k^Yd9feuPTKHRwW;`6Q=PzbYttmsh2O7?;jic=kv@EbzbcpSPlF8kc5LUo*J$d? zOa1W|QP=W5bzrPNAK8S~<7?{BU^0Xs21E-L&DkV_PpeD2I<A7~)hqaxdNjLQetD$))ktNkothhr zP95f-1fz|_gn_2KB$$pSXLvvSeKZXV;8kHeIO$h=Ae8sPLEI)PRR$1#!@ zd1M1x+xLAyGSDix9F$R931plq#3LF|Cl`Tk1K4$q($eFb@`#-TTAM2{^AQlXn^lnG%wNVo&m(ZK^@~q2!uv{GD;HKm=gi~ijd(`G~yWX_iV zV(rSlMVxtbGbCIXpV$nc$lX*Pilhb>7*{JZTueE+I4}*1R}x*$F($NKuR{LK@k6HLjBna z^x|eU2*+9=2wwqc!6TbfZ;$@vQda=B-J)jmz;g61(1TleVF9UYcmh72-#JG7d7tk{ z6=O+x7slb9sDp@|5C69H^2FpB<7SRak$@sm z>XEJ7J(NzO`5rtT=M|cwMIb+v27A1Z78POj8pPw9Q!kplRgJtkls4x>TVb?v`MOrf zzmP$=Y0->*Sl*KkKp$%8vU0Qs&?TQMjv2Gd(UCxV@;j}NC4Q|Du=yx&P|KY~c@@gV zO4(yod85w(^6hFL*Oa4qKrgB&-yzz551=ET-nuLUled;7!WW2QRW$=6+R+G}9E#lt zheSWVF%n`Ho6a=Y7>V_&A|1!_7F}pPr(43mUF!5{Ar>^{hYkVR zrQ`%p>VmaD5I`$UtW~*KjEX8NCdB0~I2R1AUUt;?Qg%D)@64BB@#PyLXuSsS=zLfu z%;T0L-60h_QDTEA_k-Q&yixD?owyu zE*=tv;Ym@3hp&MIzYztonY=U#V)k8Y0;so9>BG#4Q^!o3nJE3GG{v3aE}qmCyGXj| zgSw0Cj-u7MV>D*z65cf$qo2(2NU1iz9Y?E@FZi-(nvJuH$OJN0^=WhrjB$=XR8x<} zf2nRKpoKAz+@dt}1%imqr=SZ=-j>6Ia)!q5o?vTyn#oZz6 zk;~(Iz+A4{&m%moJ0$K|AW^`p;eG+-> zkA*~oo{+fDd-g;|?Z80fKq>_Tkz3|eQ{hxSp5GJUxXpj=fi~UZu+TYHt(;lUhVZ#@ zG|1@?R21f_sr9ftX9|EC_o!h(?SnYP`(Z`A#jZ^cC@B-fH};~zG-3~r=uKPm7rm&3 zT9m?k8n1!RdIKygB=^gZ0%m(CYd zv3s{Kz@=Itp>086w&#JecG z)C$K?z|;2=y=WWWaTs`Kl|LkS50wuaUcv7dyswJ49s%B@@_PjzqVndE;LR%krQpL< zzG(t@Pddaqi6ZtRdT6N$t9efU!Y^l#=w3z{=*oUNk60}A!yyP zG@JT+@`P^KIAs3qSe*5S@mk|>d*K6{IfvCGj#G?|OHEdcHanus4xHe3j6)8G=soXpfs95`n);v>-)~)xjb9I}8?oQ{E z=Rm_4Xed0T1jZ5p!-PjHRDe`*oT}%)>U%`ObM_N~t!Ch2=KBhP(q`F_R9*?PX~y zMrrGOxcw==IG@%Fu%A(Vsj#%R0+}|RcbZEZ85AKty^YqN);G%sW6YfKQzlLw-)zi` zlyOtX3fII>2HX=rj=0qO?-fIN{$spUF`p(?$yqg&M8LM%2L`JRQ?Q5Vmr zCc|fFZsEJ1lX~1gjfSS}McLd$Ri&yEh~-~M|9>IkFRF?b6|Kadhtnge|3WhLNE(dU z^$?Ja|3bF?3;F6_NdCW&OaDTy{R6@0A;10u!Xfux$eVv5&Q1T>SFe8|rhg$#%aP_2 z#s9h|m|sk%_tUPR&r5A0Sxm|et0esbWE&nr;CY6IG=3M3;Z~*s=?-Kqjyx5}93b}W z%6d)$NyY*ohDF>8LkE!C-j$Jnv|*Bzpi|TxNLf~>%@Tp+>txmfIZ&&z`SUXY>hu&tCS8ZT8ZO~+j^>~0qciW4LA}$q>EvpTkR+4NgJ2*pbz{^Oh(#BGp+L^* z!e#>!a!g$qRCC*bbc6#HeLDjrIZZc6*8!PoR1VT#K=PMY9_%h75dfWwaX{8WZ-wfi zK*S@XiV^vD8cl}Gx$Lycs@DPX(B;t%AjvxOuYiajc%Tioa{`R%dfyaCx~>~Nf%xiT zHWJ8zDiZ%Tlh#SIqi{}__BVjU>s&F8lB8?Cm3tWhWUa2%ARv;i)kGi($0`rRY$2m_ zWosIsLfsg@0phGv?K=9Olf?w2P-m5s0%cew%?@E05O5G2>DvreF|K_f#H; zD-iO~HR~`2#&k1kl0bCEHUg1!t*!w{AmUqsvcW15GMEY#NY%0Ud@EPi`**Z`2ySX) zJRV5A&czu(&gohe02!uRDqbjhOqGmzj8oqNR+w)JByg#=Vp15Cq|SiM`h4cqKES5_ z0V;g`DyP6lKoSgwt z5D(q7Jq#pO*M-YzfF#`%xC`W^u2;{2=zH!k9*WA7L&Ty6kij~qgMf%XBJ6-#HC_=w ze03{AuklE!+q#~OgV0i)&~zX6TDPxl7pt!ekYT6-_iwvHj?&_edIBOJaqY63M5^(A$U!IYMs;Vfq1}Jm2zYKfLwE`JRBbZS*x1|cYqi} z(8jsESsT76i+VQ@AADA9ze6V?d327i12Q<9AIYM1)2@S0*I9C(gvjgU{RDz&RTv5e za!u#W1R&136(|KrNqMW0qh`(=HzP$#*7>$54MHB!TcLV25WCL&k3e#C{iKu8C0z*p zfLtp#hJZ*7fnX#mY3>9>{8WUw=VY-a>CBIVkX>i&Qy{52hfV`2(T&%)KoWFG_5#Q` zo#yIEn1(t%nMp{XFx`y$96};7E8_9BKy*vlWgr%v>f1nW>n6+ZK)%xTRs6|`RGsEp zK+>FDDjRDC2&q#k?V|*u8`s`I!gR>HKqlyNhzUg3*I$5m=v;gQ9S#l zkkJL-1|(S*vrRyJb@A}S6OdFL(i=#`9`Q*rBA~7?V*r_SLhFFYw#t(^4@g3J7)Ons zhAD-Z=_b`R$mHlEM5p81W>e*Xt_{Qv&G@R)1oQoqW=19@O&jB@8`mZf%GHIj11H(E zURpdV@^zkW1R{Ra9dBmJjFf4}K=aUFr^y*0*L1D!3mM($)|rVOmxot$s40;A>Xoyk<4h!tU1w}Igl_8; zWdbQ&Ub(OPgp5v6F%bRCCK$}y04sK#T0pkxV%9DNN%K-?tPg~8b<-vRNCHlg6}i1Z z$doG@GfIp$Vz#HUi>DxC)(xv+7QE5vX$2%L!mYCEPJqI6&C-A*dsUYC0*F49jswZp ztw)!D6zV$m3lLx30`)|+3iYn6+Hp3fziv1pfE>`N?!~{gQ}2oSy6#Sfm;}EoW*`GH z-&A>?>;Y1!>)Y=@%$}7UXn<#`2XtfIm-ksg8yZcz)Skqbtiarv2kDTVmBaoa5V7D_ ztobZuQGHTe zkfd%vQUfZxI1I>OUH_)=MJv(2IjHdRtt`3}NRH0rM?m6rgLnyuM|pa}w&aI1*{`4# zb0HLn{`?sQB%(&;u^5vCC|L(B0%Fqj?_(fgI^>{`fiYyC*5iLC;~B`9b;JA(U(RrE z?XkD=Xx@OBhpum>K+my#p|M(PW4+LxjNM~=E38-m4~q@kiojBMFI)aZ=vyk@^!N#8Hir> zCqN3zr)Sc%@h#1;CF`<#7i1E2dd>i`>s83y{ARf9!Wg!rY z4%q}`f)2R=#H`DrVj%Il-qQup3>PCS7FjPqw{_+l0SVJ3O%#xL-HI}u-&u_`dZ`P* z5{TuLk0atEtpQ@vd6^63c6lI$%uyiQbjSrEqstJ(LI~;t84RQ(O$YS@l&{+?j{^xQ z_d9XMxKZM^_ngk@P;s2Ygsr&mjS+jLPV;miIpumFFC7Mw;EZ1guQrZ9p;V$X|L>cC zmyk)-$vCDXv2=M|3y4MMVnZMvI`cz?%rVJTs@MlocpAP+Ogo@!nFhI~waUE2ZKL?A zMRL~7tlf|icLo)URsoQB-8fcTgwX0nBoN3;-Kd#?n9_8sXcs^wIz>r99Hq+9)*n|B zQ^qCX5Qcw%R(bL-LMwB5H&89@1`?qQ$vq&~^ds^Dkoc(>BjaMs8l6LNKnCkFG#N-! zzp_?|W0I!L9EasV=g)Z|CM!S zAyGwP9G}L@iHH;{hM<}@S`eD3XrLgeXnBanC@^|(pbTxXNm`*8i=7g`dXS_yB)D57{w?F`n>CrC(!{e%Q1 zm!smOX@uLLAf!axSPpqBdq*r@_`y=hJ;0J7NW=1p^x#4&d`6v zsg;ld9Kws7Q>Bm>V$2ChS`4;gpxKOp=7!ydghX{GBq^)m6{Ji?y@T}TbPUGyGb9bk zw>9@evNHYHv#Yr(hlapeGC0P4J_Jd{dAK zNSro6d}8$Ob8N3IlJGkS^~$g(kf`kL-yp%e?yl7jNn(vz`PuC7ZQ{Ld<=;S$>Xn{n4OREikh10JyfY!?MKK&6|vl6n-6&*1}%XEMP@T3 zD&#sOtvv(sYor{=euD!1IFFSV5sT(raw[0]) - 128; + return val / 128.0f; + } else if (bytes == 2) { + int16_t val; + std::memcpy(&val, raw, 2); + return val / 32768.0f; + } else if (bytes == 3) { + int32_t val = (static_cast(raw[2]) << 16) + | (static_cast(raw[1]) << 8) + | static_cast(raw[0]); + // Sign-extend 24-bit to 32-bit + if (val & 0x800000) val |= 0xFF000000; + return val / 8388608.0f; + } else if (bytes >= 4) { + int32_t val; + std::memcpy(&val, raw, 4); + return std::max(-1.0f, std::min(1.0f, val / 2147483648.0f)); + } + return 0.0f; +} + +} // anonymous namespace + void WavFileSource::resample_if_needed(std::vector& mono, int src_rate, int dst_rate) { if (src_rate == dst_rate || dst_rate <= 0) return; double ratio = static_cast(dst_rate) / src_rate; @@ -86,7 +131,10 @@ size_t WavFileSource::read(std::vector>& out, size_t max_samp if (samples_to_read == 0) return 0; int bytes_per_sample = bits_per_sample_ / 8; + if (bytes_per_sample == 0) bytes_per_sample = 1; int block_align = num_channels_ * bytes_per_sample; + bool is_float = (bits_per_sample_ == 32); // Assume 32-bit is float if audio_format==3 + std::vector raw(samples_to_read * block_align); fseek(fp_, static_cast(data_start_ + read_pos_ * block_align), SEEK_SET); size_t read_blocks = fread(raw.data(), block_align, samples_to_read, fp_); @@ -99,9 +147,8 @@ size_t WavFileSource::read(std::vector>& out, size_t max_samp for (size_t i = 0; i < read_blocks; ++i) { for (int ch = 0; ch < num_channels_; ++ch) { - int16_t sample = 0; - std::memcpy(&sample, &raw[i * block_align + ch * bytes_per_sample], bytes_per_sample); - out[ch][i] = sample / 32768.0f; + out[ch][i] = convert_sample(&raw[i * block_align + ch * bytes_per_sample], + bytes_per_sample, is_float); } }