From 3565d2fa17f86a31fab5918a199ecb9670bb4fdd Mon Sep 17 00:00:00 2001 From: e2002 Date: Fri, 4 Feb 2022 17:30:12 +0300 Subject: [PATCH] code upload --- README.md | 3 +- images/page1.jpg | Bin 0 -> 124458 bytes images/page2.jpg | Bin 0 -> 99901 bytes images/page3.jpg | Bin 0 -> 206628 bytes images/page4.jpg | Bin 0 -> 83076 bytes yoRadio/audiohandlers.ino | 39 + yoRadio/config.cpp | 328 + yoRadio/config.h | 78 + yoRadio/controls.cpp | 101 + yoRadio/controls.h | 18 + yoRadio/data/data/wifi.csv | 0 yoRadio/data/www/dragpl.js | 40 + yoRadio/data/www/elogo.png | Bin 0 -> 3691 bytes yoRadio/data/www/elogo100.png | Bin 0 -> 17854 bytes yoRadio/data/www/index.html | 130 + yoRadio/data/www/script.js | 347 + yoRadio/data/www/style.css | 505 + yoRadio/display.cpp | 311 + yoRadio/display.h | 81 + yoRadio/displayDummy.cpp | 163 + yoRadio/displayDummy.h | 80 + yoRadio/displayST7735.cpp | 321 + yoRadio/displayST7735.h | 83 + yoRadio/fonts/DS_DIGI28pt7b.h | 109 + yoRadio/fonts/bootlogo.h | 157 + yoRadio/fonts/glcdfont.c | 273 + yoRadio/netserver.cpp | 384 + yoRadio/netserver.h | 31 + yoRadio/network.cpp | 55 + yoRadio/network.h | 21 + yoRadio/options.h | 47 + yoRadio/player.cpp | 128 + yoRadio/player.h | 36 + yoRadio/src/audioI2S/Audio.cpp | 4538 +++++++ yoRadio/src/audioI2S/AudioEx.h | 480 + .../src/audioI2S/aac_decoder/aac_decoder.cpp | 10224 ++++++++++++++++ .../src/audioI2S/aac_decoder/aac_decoder.h | 583 + .../audioI2S/flac_decoder/flac_decoder.cpp | 556 + .../src/audioI2S/flac_decoder/flac_decoder.h | 175 + .../src/audioI2S/mp3_decoder/mp3_decoder.cpp | 3822 ++++++ .../src/audioI2S/mp3_decoder/mp3_decoder.h | 513 + yoRadio/telnet.cpp | 269 + yoRadio/telnet.h | 33 + yoRadio/yoRadio.ino | 42 + 44 files changed, 25103 insertions(+), 1 deletion(-) create mode 100644 images/page1.jpg create mode 100644 images/page2.jpg create mode 100644 images/page3.jpg create mode 100644 images/page4.jpg create mode 100644 yoRadio/audiohandlers.ino create mode 100644 yoRadio/config.cpp create mode 100644 yoRadio/config.h create mode 100644 yoRadio/controls.cpp create mode 100644 yoRadio/controls.h create mode 100644 yoRadio/data/data/wifi.csv create mode 100644 yoRadio/data/www/dragpl.js create mode 100644 yoRadio/data/www/elogo.png create mode 100644 yoRadio/data/www/elogo100.png create mode 100644 yoRadio/data/www/index.html create mode 100644 yoRadio/data/www/script.js create mode 100644 yoRadio/data/www/style.css create mode 100644 yoRadio/display.cpp create mode 100644 yoRadio/display.h create mode 100644 yoRadio/displayDummy.cpp create mode 100644 yoRadio/displayDummy.h create mode 100644 yoRadio/displayST7735.cpp create mode 100644 yoRadio/displayST7735.h create mode 100644 yoRadio/fonts/DS_DIGI28pt7b.h create mode 100644 yoRadio/fonts/bootlogo.h create mode 100644 yoRadio/fonts/glcdfont.c create mode 100644 yoRadio/netserver.cpp create mode 100644 yoRadio/netserver.h create mode 100644 yoRadio/network.cpp create mode 100644 yoRadio/network.h create mode 100644 yoRadio/options.h create mode 100644 yoRadio/player.cpp create mode 100644 yoRadio/player.h create mode 100644 yoRadio/src/audioI2S/Audio.cpp create mode 100644 yoRadio/src/audioI2S/AudioEx.h create mode 100644 yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp create mode 100644 yoRadio/src/audioI2S/aac_decoder/aac_decoder.h create mode 100644 yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp create mode 100644 yoRadio/src/audioI2S/flac_decoder/flac_decoder.h create mode 100644 yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp create mode 100644 yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h create mode 100644 yoRadio/telnet.cpp create mode 100644 yoRadio/telnet.h create mode 100644 yoRadio/yoRadio.ino diff --git a/README.md b/README.md index 7246567..f52fb7a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ -# yoradio +# ёRadio +![Logo](yoRadio/data/www/elogo100.png) Web-radio based on ESP32-audioI2S library -- diff --git a/images/page1.jpg b/images/page1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7728dd1bff29fdacddde9392fd8bde055d92ca53 GIT binary patch literal 124458 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 JLH6I={{uGs2>}2A literal 0 HcmV?d00001 diff --git a/images/page2.jpg b/images/page2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6dce9ae9809ce612595f6cde165f67465889761 GIT binary patch literal 99901 zcmdpd1wb9iw(Y?!xCVE3cXtWy9^5TJa6)i*cXxM9aCg_>4#5ff$;=(OGxz1reed1> z-v51?uD#bf zi%H1HJud--0KhLAAfWF@;O`qK$oC@{C@2UhI2btiPk#_lkl+wd5a8gDFpyBt-?ta* z6%6#N@6PuiKW7C70|5bph5(27N%Aipo;v}EkRbRVdmuoF0ANHQ5JaHoZU7Dd2mlNW z^3sN1H*g40Fklc!AgC7_`-S+<{*DF#1p)>Ghk$%u1iS)yVG%$O005we=U?;x?=?XE z%P*`A>s0MKunXK%Fpv(B_d5TT+Iprv7);7?ES$}?Z_j8CK0O1z(Pd6ZbnMD5xDRVQ z;Qy6a>h4LF*4$6lSJR(E%JCA)8b|C0{Y^gPmA_{l&3?1Lpx<_xJ*SG z8O2{+**^V;#k~+PU1MrN%Tond{I28QbximN#sbp|)0dcV2_Xsgk2Y<*j-SdLt~GVz z&aJGEWNNob(@F09*wco-A?mDMwB<{yX6l4&azU?*po{hW6d`K~+=~CZ^}`K>7A}EW zqRZ3?wZ#p3Wz6c9qD%+->wWEBhvG)|wPB09SM%&{k($$P@%^lw(QaR}Cmt&o$4EoX`kqZ_~_c_c8L;*{$g|hfH7pb5YTwn;h2rJ>xK(Ve`WuZlWsvAX zK8i)4S1q1QPL4(C=Tv$B)o4JWi#k%5JTCj51XFEVW`!`$BdeEvV{R>tRS{&R8k=g4 z>`o?pHV__UQQH~1(tkB90B2%pKROej`DBpec3a+lQh(on6~`ab7eVPZDi*`j$NrZi zy$Da|7@w3=`<;%K)G9<_amxOcy#GEe3`7E}28Sx+7G8-aDKkt?idzr~+gg#yS?L#v zARO&4ZRD1#Ji!e$I;yf(N=1G&U(#S1cKpT08pumO#URkeY9=x+NjH)W&LLVP3v(iP z|MQvx3#lKDLCuzOwH|VfU4A8fzq@wUoN_nR^NvDRW+?4z^M!fTrBwv)oojsonN^w_ zRK&Xd^{!sZZOersHNtJFtoPmjwh+H#as#zcowl{B2aG5MjMn15b80I)W* zMuf3E8*{=MqVv&F#nej>20nq+zn9UjJYVaCz>KWV7$e%i0o>)Vei1F(xZaA>AxTE2 zj%j~#jZMbYsgK5RbV_Ze)WYPaA?vS{_*XUcMiGQG_v;yc2VBtT7A05I8ctU&BCBH( z*`tg%vjWtvi|CGupNHrYRa~T~RjXZA;Lu9Mn3wP@ZJ2@&wad#kOS0n)h&?n;E7Ca} zIULI7LT77$N2y;E%^udLypEaohXJh-l0m5}o%km;qW!kM4qA<2GN+rfG}NJXC5 z6Wuqi1m_}3N87J>;Y@ah=Y0k5W%RZd51V=E_LNlqQ_?EClERHqh;jXn-vM|x?uRAu z2PVA%Tx)gvP2SjODcc^si=aSw&P@IA&wRDkG+^=&QtHO1Bs9g*M&ahNB)p~I10F$LEB!(BX+6P`4LUR@JQ=U9cFUD?{PV-N zw8$VWg9e)MtRq@9KY`pswS(qs1lRa!q-1g~>K2-*~HH)_!G?@X9)pjDe2}E)6&8wcU0*b zl@MudZkMg0dExf(td`Qra+rr)I`X6K!u2@M1jzEX1mCNrHWl-3yETj~=04)+Ch4Tx z&!GQ$a~uv%uFn^7UDf0)j&6?0I+wtUejAbte>4N**>+?NUX5snqEct^yZq5N8{p}U zv9Z^scVBG|ZH(nJTX-zz@`vLafU+U=Hcj*FI?{-fNr0XF=$mLknx_mB43w`37VDQTyF zaK->Y8EBSphKo{|f_I4CfJo(4BSCSi+s*4*0DPk?NA<2)@=XyM>n-3_8k4^L_U9M` z-dJ5m02G+^juNr|S~#BtKLGiSdrI^D+#|a!$)_I`^}mV3a(UZZWMcNV)sy4)IP;G+ zlVF>Yc-6Rc@JIZ2K5>1?ni7qrVT58{>Plw7cB0D#{_p4)6Ub`8pJ}8Cy5{HSn7=3g zlO9jR#ZyHNw~bVTmiOO61YVVVo7%(v#RaLxo=juUe8l(#_?ztSl1If`n}Sc)zhL}s zaoiDCzq$aDRo4-SJ&RxHe~<#5!Y5u^!?TQU{6hL(TevvyyVL*82bLd;FKEbfnD+fe z@DEb>M#+7N74JVXu&`;44(5ME|MNa<>-b2K#@te{wx%T)rYgcf!J6D3qEQbJde7B- zB-Z-_{yP`Mgqdm4>8>Dvk^4ArHY38Frt){}cN26&ZL^Nr-1tW`1ni^sNd}L_ZE-vqbr~EqcE%<;6DH)uJ)nOymCp{Cl_k zp=V6!8*YSk({HH7?quw>*7#2Vob67~iRzTvkH%{F>59NR>%^_!BEW*}waQ=1GXIQ_ zJUPvP?G=0f#{ba*x|2A21_q7GeF4U3i^oewBj?UM)|i<)1DAOL{# zx;Oer@28-cD`)c8hu*&x@L#k*T$njfj`fD{tnCWstg-a|5DtiGwDOpEvp(|=`0rd` zfwwUEY3VC9LDbBCnwU+$j#9QA+Y|7?IehP_fM) z(MXtxp4%B(`FV%~*}>#4g1gfDOAQ5~lD<>@U|fEw%>T1~L7=`bf(Pe^->83!47{+R zc0c>K7TFKN^#ND+KD4Q9ZXYTqmMKOT0*;ri|PEl54Pqep+LS66YRcb}X#U{rIqP z!wPJ1NwS`qO|LtuJVSuv;@bUekLtH5KeBM+FId-k?iLrWepaE8A@wMmh@(s5|0vh5 z+{;ty8d~p=wD(VlM`!(h*XyRtU+Mn`44{;JPn!=z0Kk2~aKI;f_SDL630->-%k0~i z*!H_6JG$^m_Mhy2o?k({r`VUi{Gd@b1Y=6`f8rq|2C)xGKm7^+4OmrK5;Kwe&HtMP zyKPnLj{$+ z)=w!Cu-comwZA9+U?Xv+^wI2*JnS3B=FW_T&Kvf{qoU!Semx zObyzR#>(11a%-TgMo5kH&Yw23-+IbE={jtO7HDP6z87}x&BtppDd_0nJ3LD1Mr$rcuAV+lG-vd zmBq7B2f{f;6XSO*D^m$R+RDuo1Yb~mEdKb1F{&{Pm zjEki@f2zS!5h~LPbYJLq@^S zgG4~WXC>g5qqp@YVPGaCB_bwgVquGXSr3MMStkYp1AYcj=R|Q}*J&(rPV;AQGttqH zsK3dW;QO2xW&d~z9?{U5lULYBaK?qeA9^549IVU5K_=H!#92)`j4866z8N}=M%>m0 z(FJ<+=w*NsPaMa9P*Zptn?S6d_L{~fP_~Acc7~9+rU)D<$?M3#(a4YXO?_Z(WPDSb zn?{C7f@yz=t3pvD*#!*KH8qmSZD$=iNo;25MgOp`>q=F2KMAY!hN`U}klzwajLbU5N0E!Wab6;Fg;q+0&%o7SS6ocv0ey7LWCOLMe%o467 z(Al5b*r%aXq)__|Fj2kpJAtu%d50cy2 zm%4~Zxy98fj9hFSw}mNN@)w2^VNA3ek9F@y` z-(o%kCXyoebDbDq>2G~`h^6PmsPaX>+#r|+b7!+BV%P3smK9A%hnp9?b2Ty??HY$F?qpY; zxQx~(k0JQ}QX)zbQrfjK8y&mJ&x|y(IJCG7{5-)>js)3`(Zg%Oejz8Tr*UGGrgUvhdk(S~D8(yRp)boaP zg*n?CHA$r5p~HFcwK2*4$IXxH^5Kkkn-1tAg9nd+k>G&Vz$T zmdM*N>D<^P$np#_2!bGCo%C3;9>xW2g_YMIq9ux9qf{yMF{CUKFsh-fl-AIE_IihM z--?J9+4!apPb#V1jgexk%7e!-jFKDCHh4uaG%yRPVdaj+#~GK`IMXH&H*at$v+>Q9 zN{7J}4u6cC!K6j_sLm3jod6Wop;DM7k&y$1nbh_f-Hc#;2cw825A4<)f2%&swzY^l z94@lP3^cch=Z))byi>(9fL|psc8dHqK{~UAkUxt{tP8a$K46?|zsMG&F0;BhQFCH1 zSI=GEq7l!}8M`x8xQ)o9i@HJP_yqMCFh;5|U^Hn}GR^tGs&aR`23J+a(vwVO;uPvB zg%~D!Gz9BrVA37Y9gSL4x;&)mBWuenkv2amvZFx$cAC|9rtX4D(qWxZ9Cy=Vb}SF3 zm&3wevdpY`r=TeG!((I7Rskq{HQ4}Vy#=sV=k&(i-ci}P;65Sut|}^QS&Rggltt`@ z!qJ>x0qf_WMGHb#Q$sI@@Zz5B{@NnTfoS$iYa`OY^iy&-cPfeQlmRowsJ(9#N&P#) zK~?!+_08NIZRAgfHG-HrC2>{cTuM`%D^2MfzLFTy5_csQ5wh!6G)NRJ+a5@iES#aA z)*XUM-vGgC9HaWNsWR;0!dLbi8q*%aI+ksgIlUT0TX>D;{+18f!6{TsUIh`d)nf+( zzuzD{x(D^6nS5a^CdLh6Sg(QgxSAz0lp>BJruUFis|n`tHVdzhp1M*LQ*)hOTA_%=yMDbJ%6r*|ZE8CDPW4@RDoTCO&;g z!byqADE!Kr`UR-hTK&gFr&oq!zA%CM`y89FwW;BP7R0F(8z1(!-2(!FA^m?v@S+(XY^<=XQ6VBNks-89r;OOy0MX(A>55AEIDnGz}ozJa2Ln% zTV9x`h3Eoo6-Q(QdqsDICUW9eWz5h8s}A;9nMKpfd<%?nX6J;#jBvrPM{_AwjSgAJ zi3E0&p0=h0_JmqxUCI4gUyt@vgxi%m?afNx_DyOcJNdP_Pxj`p_BsgCHw(d%`mPJ@_cVyyf2?5C~T>UVd5gYkX5(vMb)E^z+ zEZASodP_7MLL3p_8b%S~t5Fdxg~=;lb6)j@kFu-{M4t}sgzm%_!8KS;Y~ zrRT%sv>#-+?p%-pm5yiRfWIJ#O24734Z;8sYh@B^RG%g^SuCacQSp#=Gd7c- zxUu85qjc}vVvC3Xqfvtmj;yM1by16*d_1xdr$KIaa`Y>ed4p#_p;w5OtBOF3_Gl!c z{{v2L19DKflTFvMhmZK6749ZR+8Z;qhY-~rh`LKd$a!UK1 zk(iA)hW2rN=&X&wE`6*+jM5*$lknZ=EORcWwCYOtSO*_ZEEU7e2quIGU(bhji|tY; zpS%kgobMaxPk!e#^EofRUlS*gOEYS>NmM1N$gd+JHR7JKrcV{ameYX1SEVj(3?ZSg z^oa*>v|Oyj*xja~o{?F{-X?7rC4}irDRNH6w*ZhQbt*yy%?No#?!f zxL}tSHZDf#4hS=R_dZolp;za0>?~u5Rm-I17V)uNG_&^Fse6heKHrOSFAHT4XtNiRZ!&CEZ9M{=+MAjQSE?|+vvows&Tgq zzE!ymlfz9<&3)6se6F?KjqFNImQmOEOr@&>vSu8`e}OLOkZ58_;uDMIF~7o$hJ2RnynMjn<$QA8!FNf<(p^D%>h zKfezR1I2bZ--Duys5m|=Q#M)C+znw)LE=PJ^hOqi+fd}HmjC${TLM9A6YO@s4Xu!i zJ=R|V-)vWdn^rKZ8zfU#ji9ORgit$(#B*d1VNV!^n^yPzYec9?n0;$0x%#*SUiC6F z8HksSi6{Jhh>b29{86)iuSGJ0;c5%4d+@}TN(5o83u;b(8Lo)wIh9zH2KQe9tJGI! zyIOF~x&F&L=D}{NRE@EOG5qRKA9ZDa(9u4Hdze0)Xk`GAnov|JLH{hmmIb6ei!TRe zC-(mg!NJSZy8YO%XGODpEaDIqT<6V~Yf**o^5u#;5c81S%Rn2;`H2<}SMX$bH~1BH zTkK#5T0uY&x4x!yxd>bo(mjW%Qj_Hd(vd^BskHioCQ48nh}1=t4ZP=2xX2dL9I)r> zU6+LYXmcJ$8L@nc!pi{`+$7SRDdK5Y!{i9*82NuA_2ug0$5}XdHFh4oe_-MjCFM}U z8w+Odf03u#+&m|H-xW6Ay_ht%;6g{SPIrz@np`i|mY}RHDx{ELrzB=DREMy=7-)Yi z%;A)c-69XCT^K++EtFtR@b4g47Xz*85SGjd>ZgUQ3jLSlVe7N8tb~7sbBlBQHl7~# zN#8$hMK6~L5{H&crA$1fvM>enhBDAv8DY7Yph-!{zR-VL9(FJrD^8g0KZMPYi%FZrF>nmv zafb$goeVqL;ymf@sZz9Y44#I3B__uRb$>!I9vnB`Y}&Wg<8B`{4wnnS+tY0rJBRb; z@8nD`PrIfhM<_bTA6T37$c%^;H5Fd5vEcsKDLI?&+E3?ApY?XZKBl6<*{X5;1=F*I zbj&JaIk3xK%mu}Vd1vs88_aeUtzx+@pptCwNPFT7kFp5*VjYo_vQ)^yN$R&86*9KR zVv1Zy^&j41H6f_sfc=+2G%GXK$PU}Kcy^s3Y&xTG^y)sC1p~jMs?KY*bvNpNLSW35 zRY_aE&bml~e+C4N&vQL&cQ?)mVPJ&`W&R8O=B1ZS-&v`>FG6N#b4!CJp_MYGj1kuC zFrGZw=w7#~VPhw{VZn&ZtFQ5?L9h5GM#NDb(k+h=&nO(XEueTM`Su)x=-@^boekY5 z#qcCAH$j+q>=5fU3RgeQk!YEQp+!raQQ)v^{MS*J?v{}`&M4Y@m}kJKBJNJf8dSI% zA_>6Er zn!n|Ovq+seUB0s@=d9D7>b-Q|sCDr9JPHHBPL2Jg@}HYEXM{40F~f1sfTS(de6v;> z2*1obFB$qh=B~^;KG~ehJ1=e9gBNGK{bLuN_}9C`%cth`X;WQ+OV185`FtFRNfx@o zIk=oXT(pnUuJF0$y7KF=^m{B_g!#e_BmYAP9DNGAa!NRTP#~O3 zRUtq@?BDnixJy{a3bB&mOXwWzWn@?42%@ z&(RbJT8crdv!8YHrB&~}>)k|L-UU9-dM~k{Zeoy`w~HFo8l45%vHQ- zplZz6gxyDhU~1$jnNo;ad@@OZwz>hB!9~xO1dU;_7kCKYV&iLZVl2hG!)948VL6=zGG<==O-idoaZ(kD1-*oG+St z#mL0g8@f*`~cFWcpFij^9%?B_qkx0P){ncv*9o5Q0m$EhC_qY zMNs55fe~-$V9r*51-)WO1vMN$70*iWHM+oSeEFT+r`Vl2=^7P#Fc`!Z7GWsn9z^@F zLBZUBn%Jsxn!1&RBbuy7YGJl8u|iR@`g8A*iYvx z1_*nsDIaFb`Myp+5gipi12%R0+5c9z#5IKatP{xb|5ZE30lka0wbdY2M{{1helNSo z)(GfzXw7ia>uBTxvq8r>^4JpWjjyTcL`Sv`#J25=Q3r}lyl}MShfaBBB$239+a!Iu zq4v=;E6sUQLdC>`DM17=?4_xBjFOkVD0UbMLGp-b*d$0!7?;`_qf|tyEKsB3Im zC+)IHSx$sdLrVNXCGCF>U?3EyjZR@Kh??kv>Kwl-ZkgBm{EYFeh z+G@1q!tvb=gJ_~eIIT4u4DY74HoU-%q02(tliD-jWl8A`A^-^Z`yvty7$_7dFv!ch za4!c?L?i+Rd1Mp;{VXs-1-lPX;0UOUf{J=?t3I#e6ERJE<(IRMhG4e!_Q|g9JVPU< z7b2Nt5&n6Z>E%BX`GB6T#jUI{i3MU4K}OCRsOa~K72$^3$I^Kk&Ag}d2_b{7KV@&o zS8mRIVzBpn#k(4`kzs}~RDueOOnCMf6d*L>2ie$1-k9tQ8E4CUEUiKV8l31dLY1dm zMUa5*9rnh6TtO*D=*Zi}vNDBhLq~rX50EjRtXZhKvI(7<%!c}Yg8m!~$glM<-R@pO zGQOA$BYCS`K8jzfOOl@>*lSbJM_CbyM50au(t52-x$~pM%?SKk5hb^*SMRwOeWb$KJ*ItclwvxGUGP5V?}CQVQ6$wt z12>c7XNYFUs@xx~huTN@Ts%Z@7nv=^vY&n!I3HNR46T>jCC&Wik7Lv;gL=)4`&BWH zn;4E(C&;R0Iu8>FsTvviVjwOKZ8}FlrfMj3;1-6Km?W%d2ENrHOIPGI_9(p{1uj@A zhim$K^=AOIOwN-Yp}wCKx26iwRo62ByJWrY!tbro72^uFcsmp&3vXWyeZB}mU;II_ zz9L;2tHd*4h+<%0Q=@yZlqG$}T6}5Fdz6Iqv%K_23PDjSZ8ZMjXmY;JNY~A1ZX%)O z;_hhwC@MJ#4evUS84>P5l^jd^>6~ip(uW%VJZQqUs{Yng4wxu-+F|%sT^8Td#ob#c zn|2Z`4IP*s!_9WSJi{zFiNbH!GVetMf<*{SrGlyp9X_8Z3j6E^llNbS&2YRLQ+ki{ zWwDib+n2tgtAhPx=*su~+a{<}NHMZ=E`HYVw}KudR$K=LoqZ`^$v%s=;>Pp2pK?sH zJrzw8-LXY&od|3NjjFpV&jy^YXz~~z+?c>9?fPPu7NEGCAW3jBk=Y^+E>{hdsaeDv zlwn%t2`eN;((RH@FS1*rt1vtR`tvB%?s*2zM`))m@cctPHmis#yo)04!_|{1)vIG8 zzGe~8MJ~Rzx1-Ia|5QEx$>fyEs|yGhq@OOWHGiuC#6_4B3^R-57 z&I!oHC$RwR(m9IrRezlw3`}Mqzxj*=!}qWQAh9l5(tHlo4zq92^|++p>#w6fiVR65 z;rXY;OtW5h7Njba))FiPeIu)NfXwP&Hi-O&hi<)3+$2*3LJShc5#$ z`2yZV1Zn@kFwfw{ZEc92A*Yy2`nvCWQhKkz>fsCCsRO~5k|1S8QrO`h1PEA7z5b{Ih9DzihduGa; z^2@r&kwG0kU)?0cFS(%^WcIp`ip>2ozJ&z%Z=1(4Z8vdY>_ON3SCQgIF^N;8IF2(9 zzYujjX-fA|hD*fCB)Qn}gQK-4iuveQfVsa;+*S=(#R|~rzS&%`Is4S~yV>o^YgP^d)9pd~+(5O%*=ne;f5RCDu^K!*+&QV}OiH3xX>pDCe$Uk`t%2Cs?hvVdfNtw?+Bqn;ZXv$77RX*TRSjb)gg$wUE9Fs6^wKg6N+zd0g>U8M0Vyk4 zO&L+z43cn|b$}OW*i~5%evrp9+Y4Jm_LLvuMI4ls;oNFgV)id4jSoNmWQ26`xHTXal7SGf*fVQwwvyZ$dxf+m2W~=}xEU_irT}xLYKbt9)-2J^a6Zufzb0 zn%U&Vk`~Kn=Fg`~8uHyXy|Et|lDmu>mu-f>z&a1tO%VH0eP;x6`Zz`g$Q}Uog$i)2`yiRESb5%iTn_!b8l-b{@u&oXd;yAyX26 zlu~1jUDap~QxL;tAq-lKYkltSTUU@#ht&CLZ^}M0tSO%6`vqcWL6XTPnYRu40XmQ4 z;^IYqQbqMb;o4f@YjcO})GAuM3&oxgUY3N~l(AEDR`g3=+mM1vle4lZZ&-_-63rFg z^weUVKU&>B$oUrKQKwS$cEqv{XmT3Zd-CX3#FEsq$Dx#rpmvnPRE=Wjad)m%q(s1v zGMiGPetci!v<)oK2a~B&$%OB`f{fIRHohSv*r5w8oLgT#p=~Ca{)wtvhYb;_dOw+} zJ>KGz3Z|fz(})xDG8Lrx6xcIBw06}?+bfk5VG*g)9DYa4V}gw~N!Uj@u%g>NM}^#! zX4T3T%Rn@Gz>W;$rGHn7Dg5PPHyBe#i9m7yb1J#M_Qy%T54N=Zr}E#3v{NHO$E*@Om;rEhT>Db>$-20(g+ z>@+G!h_nS!RCstoF$}tYne<2VT9*A37?sUIF+&BS;l(zdXp+{SpGKFw-f=K`mE*1L zNz~3+ZkIDykq7BvVZ^pM_=bh*DKa^q;p64XO*B5P)~uGmeQ0kVQrT7T3MPBp$6 zu#2RZ{wDF!N|-x~eq8|!AStUnfO2|^5&u*_c9!!fNX(xYqm>;-;bH&!nsgk*HeA+0 zs6(l$g7u(xMbaALs*J?s|Npr5@mxU`NwXRl+9R#&fLj+ zwHIIUB3w0-I>sAtL)}NIn=RKlHvFV_-E-~i*}=;@USG%DBGCs4O`%;CN^zrd5}C58 z;Z9xNkk+8uPHCmF1a7oZqTIpLc6%FrUt}XnAI6kSgF84^kF+rv0`jU}aK-KuaJ`Uq zLf0T__?-+0IJW!NZP!*P}8;fNzp=PO-a`Zfr0V`%;P@j5}Mqb?s&T)ZZ%<+gsc0^?BJq^Xc1voZ| zB!A=(e$3s`8rgIPTPX`RVDFq+K^ZnJ*>}XKQUt~u*?iHBQAy~z&YQ2~$Llhfwb)9| z->lqdzj{2}ZtG2%=9u-0E~uklQDxLz(3gBt$QZ@5?<37O<2rdlmJI9D572=0FqK(n z>G0;c=Oecci8eYeLER7=Bzd|jh^-J1;MuQx<*~IBxUO&hq~YmFk)YH?^11qAroopY zH(bt1OApzM>+Wu96h~MH)o#I%>KS0ZKMjTNPSuJCEAK!;21BBGLV{z3NISI2_UQHv?M;+9~0{q2(q zIWlqLWG(wazKz1kI&x)NL30Du`iOBpVs{ zuNPr+4z5BAagcJ4{cqcbHbGjGRwNzpPWDYjffRN0Mbl<+oWcL?pEowUW@%%|B=I-1)6 zTZoejHKTCtkU-V7=HaU!5bM|<2F0~L=BfBbEojN!ih?k*f`6EnsQm!a zbUsnpwo=SWLF`jkk%r;H+=k1FR0SJWIS{*%nnucbZEL6dcx6uWru?e)3izHStf;(S zuy6L@jmwtfMX-LgB6a?>#puonO8eu^V+XlO3a?)(unJ!i3E^wNnVF&7dHyy~HzE zJA3bnM|csyFhN{0H+3du_qy1m3sd}jxUBG>0reQ1#GDJCg~_e)<-(UO(w5evvh>Xs z>om9uQ}CU2-kU&61zQ$j&jIaZd}#47CzxD{dBaYdM}1n6>TLa*LLJgHO`)?&fYmv7 z6gVm!S*4Va~QHMaV=)EWIpv=mxu? z^LyC(0#pI8ZYGEKyU=zP-JcXAIo=0oRE^4P9x>0f+m|C4Q!lS{LdTZs)6!sI zz!4m#_OaX5u@>W1BKV7FwQhgw+`-YAFY=Y<46Bq2@Y8JE{^sGEC#4LE%0_2zqeL-> z9^@_VJ4_p^y@CBw&m8!Gs?dg9`^bxA5pDmmYR|{yW7)3WPr&6k85jz|Gm1bkD!|mV zwYexo?17TOsPgdMreBuM&MHncATd%shhR`EOJC8DKvVD%6PF1PlA=-Rv%@1Rlyim% z^+vRtf9}@b)B{q58w;fnF)s?$?^PNvIwKK*E)4j@Y-@FU+qcZ5U3|+gA)grrvVyz< zJH%eMz$TcYpr62O-T_;A;mSsFq>0X2XMKj2>Z+aj@?eBvEKy&#zbqk@Rmb4(bLq)LmiO4j~! zjElzz(sYs*y2v;6e%9ACDN7p8?%WzCul1|HH^tl$4jLXk2o?O!;Fk7qO+K84^E?mnl*T@v#zMsdRscG1RmJlq|qYwf?i_@5ejDAF~b*F!JJ6?RJ_Zk z47#Wtdb-f4jj*E2l6xnr(<9VsdZDtFK;fVVJunL)K2--8V6AiEnk7l4EISV))EUyp zt_eqEDA$Q$=Z0VQDnGD~uW~8aN*O^@Xs#h#E`K&v7_@L5D1@!aHbA>!M5gPA`8eFX z0zs|pcojcS=;fVM6<`ok;~R!SY`Zs<1xH|6>mr+}Gy#a!Xj+&ysHtOWRpH(;d|^>J!jHUs=%o)b}4N?*~48 zPJDmtBhhyZEQ&yii>23oUd3niz1Pp`j-qVL1>KvuOn)&@cR%*Yqry84yhtV;JP$~j z;LJ5NgX72noPxAY;)sS#hITlk<`D>gNf{Wr67rX;rKnTq?HO64Y|lXs7T4-4VK-8! z^6>JX$?=%qbRXwr1DZc~t*G*zhowIb37|kV{8}Mj6Vc)USd9K!rM2nny^2Fkoun`LD`ymMeMx|45=0lumAfJDDUmjK$kY8 z{iC-OCf7l02ZN7Rk9nt`ZRQ6!VvAVcFvz1K{*xWqyIAW3CVDd|Gu=CBQ=PrU@wN$u zEVpa}!5iUTzmuRam=)yzJnbt*hq_F#3liJ{bYvYjv5zsuGs%$emTMq=Bi@^I5*5a@ zLWF73EXq9gF4Qdjef%psnMoD2_^YS_wERD;<^#F@XYVSB6+;GFG#9^hs zrq!>$faYI>^r(6b62489OiELs9djiF#7DkTzGN zqG?6+mYS+PGf+AC!pjLoLnx3pj*G(LqQb6*Z*!Z$9{b}-BZyVkJ zND{0&Ya52rQM!&v;icZmtngIhYnu0H0y!adH;R~@aFt3VLU+h6`u8%Rc=NMS`Z@~p zTCdtHm;8e>kR1Zd)h)1qc*YM* zAG#zi$zNU~IoNE+)zGZnrkwzr@}hqRG+a;oNuRKY#sEYAfK4cNdre`6Uu{#tYepllW+IhRo>M-S}jR;YzYWx z9ECZ1`6en$`5{k|EzWpEOCBq<6Hesc(kMfKHxH1&Oo9@Hh-kr$Ms!>-hDK~1Di!jP zVQrS{g)bBuW0SV5dst7eRdep_39J*^~$V<3#H;|$`xb2PC zojD7m?kL&BRQFQt{a0i_w8ve^`lbq~hW8kYf(U@erf0w>Rr$xTCkIpAs^~EY&0mlp z*rFbJ2KwA-e9LPFIGP%zPZ{oFv3M)Ea$fIT1Rf{0A#VPCu0Y27U<4xd8{`$tvBvbh zS&!};(V7#oW|j;)o{7@?pp!6@W!ZNYyc=#ge^~^bWG9i2qq`_|NW+*H7I~+z6jG2J zE#jKFOsN-{tZMoFr_39xOcWY@+f(LFv=kVMq4}oRxFPCSp)%#=yc;%>-XPyTm+?%` zQO>Lt!eZhj6-_r$5C#6?j(n(r3q)r4 z-!cKx2gQA9>E@lcQq}Va6@m7$+Q#d{(XHmWrMZ7i!5WzY<6WCrOE?}PNCWv-6EbzX zIRWtn%-ml2VrirQYyzxBcq7fZ6ZbM6-KH-|y&D&Mtns`wBl+*_x<3sB4b&cR^lH#D z`C*>C(*TKF-pu(4vW9EnB5`G5Uf^h)H)|2K=h-#DDCWhtTT68#L}!$oMLSo@{Bw?p zapJVpg)Tv5Y%u~Wp|2CLz>M!BI`J?orOf)3c4W>dHxd}=K04O_jU+BJtBYfi|!RaLp_PwnxmIGN%(K5T1cdzbKtv#ab z`qWiO)2O(?7U&`{qs*o+Gu_pU&ZM5kC_)u}x48x;bA(qhcTklFZAGqDmJ*;MHC=0yRh1nU>E6_oF3o|Ed&Ry-5_}(>6MIBn+J{3n@V+egb)C%rf7VZe5Eb> z&9AIMUC11rgw<|g1#^N)@_nd+;ygbdxlWi21#{o}%3Ut^y-k~y#E`YB5!wgf?7O+@ z!*-spO5u)6*2|1%-=f=07C%VDPEBI#ODMp?A6Zq3*S5sKHRYD}9VA~=)w1XWao@__@O-ZV z?2;#@Utnn(9^F6K7F&vfMIfxH>A@&Ags`lZ3^_zBMWuUI z0W&$A+hB!qtO|m6_ab6)fYaDmtzhfvvamN62YgZhG>mEf%hL!R;se9d`8BoY>!WYC zgRvr1sLZ**qKJwbpNUhxG}Z(KCo7=jQ6ZzUnpFeg&%V40z}F1Y<6Tk+dvC8hDo8n zfB})9P>p~4^>7dnmqL{&t4ifBz=$5FhD$sCrPvrn(MPCdEhV@K>p0@e4hYgv=EugS zT;!@ZMEfwfD*vl*L#Hyf3;$oL7 z$?qvvVeyOKwRVHkK0cG3-2Ka6>o35Ewx7C(+hR5!1zV@9?TMjgXZV2FnM+cs%E$Fy z-e^327WC6NAW`>x>oU4Ejkfqdbs>5%vSy(DKoH1s2J~HWZ^KVB3DqXlXT&+|qIzz& z-=DWkn>8DO(&^+iz4$qz=f)TEhvkG%a5G;`Z3!y#5|oZj;?GQUdJgi zA`p4gdzs00HrDRjn<Ay}!kf9dw~(#qO$EL3HzmYfSg^k-c@_N9x=1sk$fS>QKj@VO}S4K35{J<__Z)8 zn&fB5$+)NtJdGsHVYbS)+!97?dyUoSx&Cuus)Ai2K~qmh$kJ7<_-b7#Rmh#B_A{08 zx~-_}icY{S#-2F(#Wdguv*sayhuGd_U3*#0tD;IsJ21MK&8lXGtEp8er4H+81*mU5 zK)*@U8EUU2u;%r4{WR>$>;l(@5XTRx?`+QM<1ZsE%Ll%%payJg%RK$V6G=hA`yN$0 z`e6v^#CG1L%2u8aDEKDBV*}@zpeOb(prDNtjVasQ^i%_y>*ZQ!8A0W*0@+31Bk8m2 z$<*mckzezgO@O1m!gK3o2wOaEc(NocsfgNil}Up>3N~w; z`E<9-OqXT)r{oPnbWF!VzYxgR4dwbfe8SsjnQk7`r8Eov;d_d+rE~0QJ=u6V?s-AF z>HibyhKT+`;g5`jhKPdlUy*Jo1jO{b(pu1@8A1{Ui1|OkZhQrW^}VwjM2!40+Hb93 zMW=KEnjumaeLRA)E#rnTp!NtrrK?%fIGRh!I|js zNbhnC^+1hLBEwCJVQ#+-t_|Tonc0(G2jJ_=#yFbW3NSJmxY_uAbG(4ZO9XZ$d#+_L zY&h<*6=YGp*~m#>Pe@_!47_DmkRO+DRpsz-kDZqDre@eg+@0rT$2*v{>=Q-f#Nt$T zrbr&6&U;0G*GhsH41u9H>|lS&T}1;aNOV01o zPdBmwINv{&vkCsx_8Uk90NBK#y3{FRd*~PX4~mtEtX9j5K3)B9sYNw>ov8q3qKdUemDF~-Gd{~I6|*AJd(5(3iXo}6 z+rU)54IE3NDGiMUI=2Xk;2Vj>mK}}+r1W_=^af5{)AZcj5<)qOcLT{CoRa8{l)5I6 z9Xe+B0luB`VY7F)NPN*VR7Eon#l&ja?nt$?nVC2)Qvf6Cls}V4QGWqgp$glLItRM+ zU3Ucm1glPeLaJv>mLkNMD<&H~{yuiQX}3Z@%963Y`W zM^i_iF8{~8Y)5sHs6xU|?}`*13{(5BtG`YJZ3;VJC9Cp1ajzC4l(cF#CLOBcye?FqhaM!fHN_Xs_92+OeD##$t zjW_19L|s;9<>d7kZ00Gop5ruyw1cl-IlC*j_PzGr#usYlh|mP7D&117qxibobPy(Z z?p?@!L0^t zeYsP0@zKtI0Vd)ZuG?i4-61Tvgw3I{BXWkA{Eg$@*>btZKW#T=$D9hqX|0cllPn$GU{s8N`D{m){V>ny2xF9GPB2)XG%4r0NNxzk32v%(Ky4x)*N6=^OHnTH1Vm@sdjJ)~$ zIg8~RWVCY1pJ_0Wqr#3^tPZIEOZQlL+~31fcKgc>YP$ES>Y6IJ1_(QTJs$A+FqBg^ zri${4XO8FmqtcAU%R$PT-BS| zPTa5Q?naTIhQgo0@1Y>IpQEHc9r_2pTI)|}YH;B`P~8Zt4r3j31d|&X%-gV>KL7b- z&Tfdppzd*p(KI0p_Ze~}5Sj?jU$|a~wMJu$i1OO|F~IK}r8{1mk@9YI;Vg33x+Gao zmvGU-&^l^JwmP%;>8x=tw-0 zSqBk_u9VCUq@zmSJ|7~Y1C`&xT_FiMBTdnmFxO!z7033x%5J%2Uqo9cn6uWah`+5d zLrdmsxNK$i8M15}7-*cW?6`Nz&AG$YES{J7!_I+^n)u7FYZcQ&oi3~rUdxfPezRdK z8`V*ug{!YCTUt`EtaVuI>NK=)4_?POg$*j-bDe8c_Y**1Ju=@lG-)rV7s~rvijrhO zQm-HCeJff5VOkHDTsSzt{4-pPzxm+enCK>9v9mSt&+i}qmGv|L!E8h9~L0amYjP>F2+hxR? zw(K(>^R$&$Y9>{=43s+#Vez)211d)96NX2Lzk+}@YA49J&Z;2mv)5mdF(J)$YJAXD z9B+ke7OP##_Sopf`~+?7gCfJ-R1*EW-)=@=Ki@-T^-^1JhsIs>bA%J zRa9T|iqh{sm1a!Z6tT*zQi6j?Ic){wc9({?;sV@u-7%xxlTkkHX)G%0LVyx!%r0Cp zTYRyHhS{9~mNW7y)yrv%p%$(8EsdUsNif~+_Kpt)4}PuorOGW;P$_39^ZWE|y10?N zmYl>U%9LNhJz&ZeHPL!Oo(C%Gc_i<(aj*re^bDC?yLh`U0cHY%pa9u96u(aWqv}JY z`Oc|3r*3|Blaj8MiAZ;%rQuj}I<$mTN?_dh>pq2r>38jiO4Tf{q-=S)HRAmN<=;_0 z!_=8jTw(WOq+*ICRxd@ppX99*P3`)`q7f-nQeJn33wB2=c2Sq~GQXL1vy1ZRMdsO8 zX$adQ9oY8;kxMyAh$drL1)9y2Fi44$Dc)yh@-K3FMb{Vrhfydsr4NVA1f|1)Y|4-Z z85tQ@s~E|@fJG=Z_^ObTz$6W3(8H;u^TyP7+H*K~OdZ#dKRojWy`v%?2b?t$&u;Oj z?k1XJ!>gY>dS9;wm2ah6>8gsfd|)kMvW{sJml=)vrLTVIGEN8Z{yLsNnp5ZJj~bdK zG+lsnUhKpp&FTkOr}1)_Tt7dUKR_IX&Y9RUsXPh>M_JX4*SZsh!nymU;FSmYL%_;| z7;$oC1?wE~&M(zv3+p6l=l0L*4`FJB7Q?4;k8K0D0*}M6cE!iyfg@nrX|7uFHU{lc zJUXwpJWEk>^2o1_c`ugb0=Ze!t`%JCI-sL?hn;Jrb7*Yb%G! zSR=bBZrda*Id*4E71i(+sniZW_!2weF|4@1V4)1oA07IW&AduXt?bq%vrd^=v1^gk z(%>lLPFlIA^t>u5(_L2Ay^3F4Ou7fGU7<*7$IH(#v1&}2AvSUa!R};M(jsOXoiWxt zNu+xmNjFqGOZchFEv_~)rXhrP?~L#;o=TROnx?aj?W@dmHyZj5OsdjU7ze>Xz9A#v zt9aW$D3`fJK5X19s^+^1o=R@)B{;}`!*oBvHJ)fRc+{jdl?43@u)`bZPf5#) zU(whbjLDViHL`p9mncAUF8An5W*xB3O1)Q62X!IybUnP9wHu^&Z()+qDZi{k%QRbb z{&R?TiyXb0=gcEwu|TGh!qbvEyIuClQAnLq*|L6MB8Vir zweLe;{JkG!5^e3*MRtx)=M>vURAb4m&tXSxtU*ub3%M8}kzG_P;a>oXAhpHGFJCw~ zu!Xh?Hd$>A`F#)4@h1!1EfFz6CY+9EsIx9a)h+5>$GlKmVcQt&jJ) zY!uX4tr+VEW;qbPrBr9_tZ7?piU}(hlvaAvtjRd>%0X4DT}L8&%<;8N*dzU6%UAuW z&$)dm?3123qOQ_1a+i&w*~+X=yWGZRIt!))GSP}^M2gv%cjvO7;!{QX)VNyGKrl;X z9b7e{I#v290_oO1B@htVw(B2FBoOPNqp_y%U->r5?r@f{tILZ(mp#B}bwr}Nx~U7F zz+E*&@|*a^T_xD=lVt*;x;99PM=a;+iH0=u*(M_KQf!X=Y7LE2pK*>Cn4$w6d9ODI zi?Qw$n94a zq#p#D{YZW;GPI4*p3`l7<*ywKoh2#p{TN|nib8O7FDF!7d0p+WE1wtEwoB_8Ro#}Nd#i3QW_qxcg5lmNAj}s zlJ_8@G5Kj=JiS0fzpTob_ zu5XUMGV+9?#(4WaX|9Kn@JAB97=(-Tt&|?Z3HD-=QNxsW!l|}m=HJjcY^H=Gtu@MJ zg%;p)4ac+^snn)uI^j2}b2 zt}V?4_4M5F1j_~ZWf%Lrr(wwL@)y9AOq%npfKho3TOYF2rd-rl2X64hPDss^Duf`b z8Z;1E#I?M7*UACRI3i3P<>Jp_9aEZ3KK=`sP?-=E81X35&iiHjW3BqYUUkjE+B7%p z&r^mG$yCZP?7XRpJmzBwkWiO1M=)+@!A_kKJu9oh1V^26GCO8wy5=vSek&)(LOSHA zhrk_OK2OyCar>?#b~65o>U1DByTNwut*2}(&uLK%cQoWQSBPd(P(uEUF1z z#%kQ-sLM_ePRwb1PR?X1)6aV=wlqMEc1tQO{9ep3S=fkNudLwpUx1D7U}K5vkFFZf zki{ktEVmHzPR+92;{~mPZcdU5QKaZLi>mQN>ka2LtlOWhmxVa++dhzY$Bs=cmHFOi;0&|I1Z!Nk8yRc zZ33{Dgi~V@N5!jzaoAZ~wY`|eX;U`gdl%(<)&_TO$-uZ}k74+a7=>szZW#rA^N5lR z93eL3H?LW_s%qQdxk<==9g3kFBHC-ChS7R*f}%j4su~d@Uv!=a9MuLx7j3fo9M`1n zn9@pT`q|{cAE)_$Tiyf82gby4;TrQw4d<+tN~e zAXiyhB165!ShbMN`n?`+Zg9GH%`0>!hXeTMj^x}tZ7LnTI6D#0tm>?IB+kOsuI^s| zb;D6Su$|-+(XMG&?B{+Q6+Q|FO>{_7SPDB`jMyr?=_k|?DTb`pHL0j|;NN}2p+lGE znqKz(jPcp7wdtdY@fKqbX(0I&J(tJ27m7o#xAJJf`t(?&5IFY-S$Qd(Cq=cX$+!{L|M$ewbQE z*SQ(fcbNG6Vk%Z=TLQZO4ziv5$X#@p-NN1*m!xKcBgs zKPX09?sm~z%N~`;cG5<>&Q27?VSmltAR&~_qE9T=>!I>C5*1|T<=DdP@CiH^r?Rtv zjro+_;Tr1*5V~)R`M$EY7(XqAfj?&44cOiH|GXN=KCb=?2p4C^?8-&@3mDyvt^aky zqULZxQhfI;!HwPyv3-}@RQByOHqr4`u8PYt$gy+i#9j?hBQ^0pxsc;SBZo$$nz14K zQfk?kKbcId6h5g58XN_nV+nST{F8*!Y4)`^W-4y5(8d(Pg?GvGhd$EzfI>*_qiYgp zJjPnn&MoSUD=U}SyqCp6%o{Z{jw`{00_8VBwEDcZ8W?BiHu3aBi_!-)BMmmh!Zrej z4e;ozf#FGSnX$+vaHOXsZD@8o!L>&P_+oCEFp%j^^iF3r;}0aKc`%hWd7EfN?2Vw`!aV-KJ8c6_6b$SU6bwo z@Y+jP!>c31#R)Mw{@K%cs#fu6?U45$a+}$|ot}BijwH+e7%VXjjJ1wOcOYMtao!== z_7Nx;Oe!N`EGy(7YDs-Zqsv^-09-P>o=0!AqO@x0=Ge`0)P+%wdc6OmbY01^ z?P8!(e$Qg$R$C^Z?*K9#vBn&C_c?o6gZ~d5y!3Mv;*>j3`C@7HU;0^%>s>e4bBRPh zK=-S!Pe#NpUc#CvU1|EK6OsD#yo}QTuwH~?=~67NlDhBZi>2t`0mQUp1CNwbWqlv? z{YNk7Tp(v8VoLqop*JlEA^AJv~xt z%EHiuAnCxGoB+6)nf5Z5W|+!j%Et&aEW-m2j0SuaEH|6{Xymq+uvdPY4%AQ4^plH2 z!F?^6)>_*F&fIl;UXS9QJh)1KQ`L1EQZOgApb^V5FMw^K= ztnjy3&wn0+V#=>W{ky$~d)rP=Ew%*g}=zJ%Kxr>QhpT)YDb zxR-eys~C~^O|jqT6zIpA$+PQcv~b)x=>1`raZQ&iUaQ{sbTM(TQpinlddj0ZPUb%* z$-WIOmSNpJ?#ap}P0r|jF32R$fv2k(xzK!f-8CdO3_9YudWqygq07bh4Jbe2eP=MF zBfpp8zhEuVJM%~qo^1oCvDTwjmpy)ix6nkPha#SfV;E3z$g?`p0SZT&H$PZbc3&6< zoP>WQU?Rhn^?)iz-lW~k?&9zhve)IbpI@$O{g>rjM~j;n&VD=?}`j|%^stH z_!9U01>kerj!a@U{gUj)D>IU+{6zoYWXv9#N(H)PWwje65OVAN3rLxwoVrUA!1)Vs z%dt+I^B4#bGuK5)DEd@qdTTM(Sn|opHsJm*AkG2|d4Hh!%E~ohxW=1Xbe-TFlGGy? z^jb28OKO||;M;1|hz z!1bB|T5VIAnW}0gP&z;+gzJWN^e;g0^!-viv7PYcxXj&^#!f}a++=&25XddCBtKXZ zbaoCwXO$=YF!id?T`r$K+q+Wv57R^7u3FCq_AqXR9n_r_UvzXoT*szzm{iUL-(7y@ zzjd{~sZ20H*4a z>K4xA53x_4iC-eIx^1%d25ZL3*)sk%2Z5Lk0tdW>pYPwdKF>X~j3EeB@2i8$x?%&} z#OdpElhU>OEo!Q0wYBBt{u`HK=pTW}+La%gNHqL+s^&)grk@JF@YH0~DNL`E=}s&a z**eFv+q|D?5*{LM6x?5-+`?SWymZ7l3wN0G6^gj+Yq9C3l=b@$te9~cqUraeZmJE2 za3aPqng|0MpO)5g{M0je$Ver&SsoXJ0+;baswZWH6!v>{Z3-wE-QQO6|6{i0(a9)R z7+E6&P0TY|ImHs&*yA?{D_}MXqAybxp%`R_IeeP=-xdF@a%3Ur=YIimLAeDQcgb8U zCjM-q8?6}0jywKu?Nqm!2sNzVYL&_Vc2PbWJsON5HfL(UjN|QN(^KTslfJ@UE>N4b z-rn6HN&K!+?kPh;@_N{l`vC4JPLUQx_9y#bR73Fi`$(DeyD(1HuyNHu*v=0eiInUi zf@QfXoL9G2Hzr8SrYr7!pO%xhe2A*nbc8=rzZ0D_jV3}27rfH$e3H=8q~23^%GSU; zPc5p3%st&nUwD30JB0O=(vQar{JKx86j;nEccn7*r2Lfu7Vw|Q%uY>m4)fQOWADe} zpieD*Pq3{zSV1y;EfE#ydECn`LkjA{dS1@k$shx?BA)Pvf9Ai*;`qz z8;|HfzNBS_noX#VADnAubis*p0v??2&FqS95GlEM^! zC?pm(aQS1*FNF=^spz+L>#svcVwZ`mTAx_<-YN&$!nzw)KUdi5{Jruzm2u0JM3UDq zDQCAvan9M`xHI(Ne*s|_XHR`Vp(VOepv9Bi5MImk;C)mqz2j5N6(O0->I3N^3r~3K zHTS%#IhOOWS$TRN$@=#mE#7~xT6n9op_$MLxq>f6Rh68I3O`$)fvpzfvo#G^T6FTY zotYJTQbJKersvMJG!fuH;AH$bzFHwveddWTZ1DXJmktKZoK4qFDd{EDUCaJ2Am-yW zvgOf^M_?-RY-=cbt)Q3@Yd*^F>X+cfTI&v&vv}e{)YsPv0qGkeK1}V&dkWkqo;gL5 z>bB`@_I3;GN8-PJGu>TykVzTJ6S+UWwoAL$-_9@NOXxVR3@xJJe`-7MC8KMS*eS4g z)JsmKyi545IDhUd=}tXOfG94Vdk6&%Q-TM)d%N; zQXc=->-9O+w-3%IJ*DZr*Xyf+YP_K*JuT0fmT0Sij!yzZuUej0@9qA-Rm1Z9(Wr>N zL!;n}$>zQvWtaMn?3I9h@(u4&8qax-uFgw-mAHn#fS;<*?)SsDugN7@bFxx3l2Q;Q z+w^{0*U;_ch?mauQdw3#dZay{zq=?~l6zwzH%`_C&5 zyJdV=Ag%2BHZJk^5aJ_rH4y!)S@Z|z4Kl1V4aY}DgaF`4PuN@T!8fg^u_(7?EzkHt zJw(^*eLSR_xvRGd|F5g0eP$y25U!(J0}^~W^y51zS(15yz&JOpZH#H6=2*(%YUKxE zR;f;>Fxz!y=golw9A2DBFLW_!4Ft|CJ+K`jxGGaRT;_`!*K9oy;l*8dd*Io#t7pQu zM~rMjtj>i&7n?yX65#^XEP!WPlt(;v0QKuO%@(QPTZ|%OeEymW`l*-%`^Zqd!6!lH zCJQeLBHlU9*Sw)cLg38^q$qRxZ=jIwKsOybgAoDfDD4l%|Jl~-f4sf?KjeuKrDW;E zcG4yaJ#Kzr@x4Zt)G0;|LOt)5JCZpKX-OO@@L3V0ydR zf-k zrFBMke@if`=2E9aWYXfjKe5w)jL@W`{aa>Z7D(%UK#Q@IV-$J${0Fst(I-V^$>-6F z#jxhwwLNv?AnP}p#LiRa$sX{_^OOz=X(#{x@I{4|vGpZ$m$Zp3(gM!P@9{43CVZFd zXd3C0$<>>J;TK4m{Rr=1B3H#Q4}2J2N9P93ddWr>mY!zgm^4?IKbWvJ`-khB_k0nB zI^2)XfA2KPx|4WQOne*N`tux6Y67^`^ez-yk&cU1lT{)(QO9;o;gH@;ZCJWfB3)?A z(ydHf=HxBpS98gU(E)iML#o)qVO2N-zcc>YRg#=W4G;j>j;@jn6!D$M7Gpl1`)y}h zM%h@Why+;dpxwEK(?nlbC>VEI^PXGzo%{uW;Dg?al~ce>P6MFA0=FEtQ}pJz(ZC`L zl#`)X)?E?ZE*~@YU7KEAEr;T*-Aih;&E<|4kLC(}T|%q1s+ltx6i&}dB5s_|3$0~= zM3!t5HIO*7(&Q>%_kRRu0vU-6I8|!L-vVQMjFQW^@`O*P{MN$=#%T39yfB~kT9_9E zZNye!a|>}C2kr##E9zv!FRC4MnQIJ9q6e|iN>+y(n-v*Zct?JR9M`V~FDYOmRyIy|c z{W?XgqmG@CyeD2{CIDW(NJ^41z>U?f@}hm3ZYkBk@EFq@9o+tN5)|3?e$X$|s?=Zb z+Iv1gX9H6PCXj5}Z1ekJdJpUEM8$mccpZ7!yqIr~KkR}-O}LM*s{fJdsWx@#+{e^t zm}hdfypc&lYLLjj*-~pZ;Ii}AQ}$Li1A`91=+)4O`ovMI5S7d+ z@qgv-pGz!jNhyu{&YkoZfE)}3-`ot3YnE!}^H$-}cH^H9(24e0u#F!VlShTYb;#n? zH3Y;8BgP_Z?d{DET+TIlFCQ6qTHghz7o^c&%U%9TvJ9HR*WFm z;Nuv<6p&WR`ipC@R7ODW8(}Q)O4MykZB}g1!gW1mIZo9kAE`_4TTJ6UrDI!5w$h*u zJKLn`L_JevVxLR)R%R|!pm@(%p5gOO-Q9M8j_=HtET@n%SgM*CT|pYlu~oc4pG{hA zmRAvZZ5%#Wh|kG2Q;COWMUp94sUY6X6R~P1AeA39XJ_(@w=cumwxc+Nj)_S#P+sw3 z{Qva*wDXCNFRmJp|NDp3p1s67og>Ao|LdbSeN<$@5ZFilInFT#b8WggVu9;)CIlz} zG!O)?WRw027|tYl{5IZ(oT$<~I;Y0Nl4EbhcY5A{e;8q7!rhSulyj<0w?2p}y_M5M zN@{y5WVQ*=L}!RrF2ENQIc}OoIL9^pn3K*oyJI(=DPh<&-l8s8NB6YEB=K-wa~e~i z?u+?BRduSJ<4|4qoA+q^H{7<47tD;-HDOsHOI~)GlrD#TsciOD|8KR=V~e)7AYSUz za5Z34Z4otp;B&!XNfU7uU4(H)x`PNC!f4(KeTqow;je<>qbTKGfs$xQZc#%2w+R+k z>{7U|#zmdaT67K=!d|K6YVGG;l8{gbGST;viAVn~DRpKi7|{4t`v{1P!( z`uH=t8fy?VnyAxEK&7KKt4r!w@cI>jWUkqcMol1W>G50=)NOmFS^-Qm5X0t6n6Khz zgu`PkImHx}UQVgrGMCPan%2cBc_@JWxoTu$2HZTT$|^}NfX%%i8pRgs7j)@VYwsP~ zkh)X3{r14X+}k_tE4k#8Mo{XSuy(dKFWqctT$em|wuuZ2-hBT~3RT7RS7=p|Vcq~F zDYr5EzU$0A)w+~0mXX``TJEAeJ(W|y8E7c!osXL3)1rJ|9Jq>Zy=+h{z}spV7pq9J z-<%3>iq5M}Z_-Vq+ZCUl-A7Kr5G5>S|hxk`%&>3DO~>VN2sF(A|aZq@5yoQFHPG2NDWDm zE0rHm_;#4z+zNTiS&rtWN0A*(^IrD1bk<-5X3>f%oi{*>kqifNilb7*BT;5&YqPyO z&lJZ>ytznjJ4*|5&NvE- zWqc9d^wqOT=&pMCmCTWv@L%7b%Q8DRg(Qv{YJZj-%bcT0nVz;I8yUxGi!{PgmyQ1; z9)D32V15+!A&!rN_2|vmj1RkW9VjF&CcPj(GIh>5nlTjm{?K6ZjyKVGmpNB6)53ga z@{8odyX0y6B!mN7s#9_(Y|Nq=XgAMnfY6my7%HfSSqUH-)K4#5K@pwSEZ)8!6LqJ& z6gdQbB+J=}?BiW`#iEvqRV-ml;@8A>NrQx|)af=8@K1+XORFN96`%(3?hNP*`|VBzw=Gk3m4eg2sOQ7LPw{T& zeots9)Au#ze#UoCajV-tI1r5NNq@=G;P?WPx13IEdo5oPoSak=UzI#dNjEsbcih+%diHeSd@lW=B zWB|$w{XQxouTaV?5d$B;w3d0|95JJSFyyUkFub6!1PxD6#v&=XxU{}uhJ=nuN>&rv zdwRwUbN2{oB9#MYs~`B;9i6^x}*_BzOX#`?xo$&mh> zfQ)Mj`xqIBvkkc%BSA;pkiuGkWLaXHVSgSBVWwZB((-w?o{m@|9Fek%u3SAyr`4&g zx}zOS3bYAAn4%}AJ27bmMX?!ySX|nAUlq$yBzA0KEVWUN@8bPQ6zz&ZQ!UbYALHUN zBJ&9~v;%JgA!zwR+0rm>TbFloKQ%XSBI}z08&WJC;x@uhc|EJbK)hTl4s=SNN@I%TgdB3C%Ar18i6C{B)sP$_!vO>;@PCbgHq zEGd}>1XsVWv>6VgsF>|K_&Ak&g?3qAEk6Q=1XU@Jf?ky4ofw&pqt6daW;UuenN?w7 z=JAf~#>QuPMK?VZ*mzb3OGsBSADr)cT_5F8=_3lvtcq({6RJo+{g^FFr^kl2Ys8JV zYu~E5L@^}mE#*W@MPC0oeE=yOo2^Be-_HV$OsB2c$H1tCzi}O+$J`gL^rJH@MKwBD z6!AL_QZI^gSh`GASd2FV3dpLCItc)H76j1YcDf~?G3UR~7LR#JCbII{MtA#E78pj7 zp-SV8OLq|VmGFcYr)qad+3X=Pd2qx@i_RZe(FOtRUB3##AJ~plVkzbH_~jkXmx1W! zXV?LlQ%old87Rq#WA7>;tYjZH(QlBv*luzgGweUM*!)=3_1tV~{sBi~8$$d*MSRXK zy3DPGGOo$2ZNLw>*IY-}@;lSxP;z72u*THhXCg>|7w?HNfg3O5&F) z_KP5Kei7BUPTeSEuv2{ftF(>2d{_{&@blrmgs z)Z{+BDZyXEy$5rppigqeZt{>*6Irxe1dJmPOa4`)GEQ;IDdo)z!r`M^fmN}p&>@u* zI_hMM!;m1BIvJ|k0QPb3QtEF}L~Kk`c2O)Ex}Kw8)n!@k`!_K{X~c&LSRC@!cJ)N~ zV_Jd(2sizxDp)SQrd!tVuQN8|GP5P@ z@e5t6gH9*6`s0<%TYBGYYg`6@mqL^u=tD}Ql=={j*EWirq?jBb8E9~k$W&74#sq^D zz)2b*ibI@CeBY~wUM)cfJtulfRP7&tFiK=Z)O~YNEH{q@nYmD8(JT&P9fn6!CEkhG z);Lh~Q)#HB?e~Ud`U#>k?#L&Q(VNW}3kY)35Jd|*IVM%kAIzhdT)|`FTDF@gK$&L- zWD6}UV1(4x4w+NB3((dN!4D+kY*C)Rd&{$jqa(*bP{SOo8jiEv-}l-K@9Ssh4`tnU z1gM5Cu|S~=kan^fuUKWd01!+X)rDu1{)$d&pJ*P<4KaO44~~vV;^%QD#T=$D*4r0Y z46@507MC^jDd;>V*VbK@E60D~UmQYv=8mfn)9?FWZY2kMntqyz1#}3&py}Qbr?-WS zo9M%3FVvP%uEE|L%0)jam>u%PsXqHO*33*UM)Mr%U&)fvdb1%I{W5A~3s&@WvljMu z52@GqXior9S&@vbc96*Mqt|suAZf9WIg$hF}~( zDcL^l;*$yd4%nh758uPf$Lk)I4Wd^i|0+)9!z2BUF6ppQYOe)l72Q$R&CskL)%^mM z8vhXZiR@T*i~Xw*-_U5EI8DGLBTh4s`}Dz5uVy6Q!l{2B-}W-XEX=bMh`hsNj5?ao zKZ!AJ_(vHH?G*h9G{V+h(BgzVnbaNOvRoMJR%zi#3G5_L1m$)pp@@F3 zl`8d!|7r20yJuzYg2XF;H(e9fA-*6z40$xL<;^WRh!UDQT0t_ef9Qvpn(K{Rj;xdw z7L`C}5^pVz+d@6+{j2ulYYQrD8Zvy~Z_2OcV}Ddpu&rgf=qZK#X1?cd_pP-F(W^yK z=$2N5oRwvwXJu&5Z>mjkBJWdm?&^Op?j8NQ6iSq)jEis1@d7_9H?puARS|sQ6EP2R zAON<-lQE=rA#{X`v;|td6k-8KEcfkd{=}!a*=fvk#=zNFi+4lZaXw9J$K|%ye=J+Y zZxc3hG^Bo0^Ng2&`>U!_tsO=|+Urxh!2Fd1q*-3<@*`U^Hsw=1e`@-E`la+Hf*rGW zxj52@@G#WyBu*IQa??LRm7pw!+Ww}YkLLC-;QCX1=sByiS|3hU`sMB@rjkq4#}?0z zMA~xds3nELA*Y>mkpV#Dl8;mi+{Scf#J=`{Rct4bzk?9kN`Bz+p|uo8+jd7d%W;&O zw-bBg{Gtlod`Vev6iY6{*T>l_jSM&D?fpteI&ID$fjN%6Sd$Y_R*I(ilfvr$R}6jd zY)GY1FJv6jL?b4}WE4$~X%s>>2{tLJFp%l8p`Sq7CAis&0qr5)2=$PQD&WHp^VKT3 zRD7e*@NVQUA~S`wD-NBY8+|BFIgHHIX5tl{%yhxL673CqQj zlgf|kUqiUgx3(!8MK!xtmMAf=*Imy-iqH}$ygk7oh!tw}yy1A5emq-IO+6@|tB)k% ztvW*4^BVEf^j1q3w;kXsE{Pe z{%q(K5vwEw_KWECgmhLYUcP3&Ii>#i`I(HjYpC0EMLv*NXZm}3W3e~D#eE8R-zE(t zR9*5%EucuCQ6Zs$=8}YF2N7(ov;H5FkE{j^JcNif9oeY(cmT>s^3%e%J1-Q^h=VO6 zC5&_uJe=^H@0#{+BSh4~<^)%n`FUFi95XECz-E5-+4*MT;9{0OnR58|ifC?%mL`-I zS2_T^&y@YPK%DHoknYd-`A`FtC>aUs8kzl&AuDr=GGGi$KmLv{2#N!aa|+a z76~s+3$Jw0rPO$V$@Ml{LEF@=+>u|1R=bUe=3i1rFP#u@zQiVXF%srk-X!ljtpPH8 zyoEve=&8+Y9(^R6nMZA?#P%D!3{my^Wo)PDWcA_tMy)5iMG_S-6k|{T*MbUbzSc2w#WEw zW%)_8mK-+ySCJxQp%$W{$y5SZ5KB%#n<^<1Cx-{O%`2n8@v>H2&5MDT=+wg$JMAHh zqS;clQ7~N!m?|w`Kk*w`#V3nK*-c`W-DZUQib=FU&mu^m(fO2YQ zaU3k9gHChvXq_LZ&+viIQ&7umPxR5gj7&8|FS`oFpt<=uTSE_T#Y3RD`82#w`xVcH ztT^_yQg4##_HUyX7@Ix}ovnfDZeZdbqXn6d6Xjswdt1c7 zlL0^tD|=F8O5{~lEsnO0*7x(5$T%A-GI2bf4xWAWw)Il^Od>G26B+8C>{1MblTD)* zH3Gxm)ujO9oiOXFZZ*%75frD;(kPI2|F7UNfw0qr3v9~)hfN&=0xH7B_68^lmO3WN zwBC2{WgH36AtZl+k0M)#UgmRlfn5Q$kczMJgFtq$IN5Aw0^x&46^J2GCj%=uDd~H_SM)N z4>P|29~LE?#fiAWIjFZ9tf06$c;K2-0M`XUK@YFjJP(9c1h4E^MfVehuL_BCkM~a{ z2_Gk`Kh5QA@966iP^c1sZ^|Gf!9V7wC+X}ZPMnCQOGz2{j-bAnCyolD+n8gt2qEKq z6-}ofbQrhlNDZ!W!k==$X~m;G6LKiNnzm$_bEQeZ6?AG~2S^h zOAzk3D{7zLT=6>rUu5(y!&CDGb0YVRrkv*=RYw9(hfL?wglrwV9HG{{QX=orskiig z3rxJFepQ$*msAOUsQX;;3tvV%M=#K6WVf!>*TCnS-GWF1^PSl60RyA-t9P>z=AX%}`OY$4i1@8g|LDG!LHp4Q}z}TD`yIjF!exJq)&ODoif$9X+2?BNm zH_cLRcYF-MH)e>!JJ2rqT@87Hdg=)GL<|H!xmuAoM_V#Xio=$brbqKsI_s;CtKlY? z7+#YTxanD{r$n6Jw|di|YnG?!=k=FoVrDY##-M)`Y>L9Qt~*rn`G1)lf1rbH&C~OS zL-eHp1XtU!M;d1Gn*5LUdAj|L$cb8IGl66QNF2#lSyBy=5Sm1o@Uwqz-4`VkWVk{S z0Pxy@rOh~aaOtv|8{H*g%-@&E5OH5Er{x^dpM+71d)FezYmw<+NAxr^P`s07Y&vM3 zB1z?nU06C+T3g6V5gMWLW&jDx z$t@MtmZ8IoneH@$Voi1_nv(jnA^{@Ggeb`)dnjb87+k8M7 z%L&ld#v+2T6WWzdN0K7|AtQ{SoRLI2>lm|)j4i*EKP^ZkPxd}05WW>-sHI45`@TGq z4C}sOev2@TZRX4t?{?zlPV$EHN|_Xt5XS&E@IZ>Es9N&V9BauS+(l-^)k5_Iu`#`_ zQ*3MAk?p3n(~&yHC^r$a(;(!wA`6kJ<@WrYOLFCkL2b}S= zB@~rK%hZOF9AyF+aPCsxo#opxL1n7Y@!zF7IIib1&(sh=004~YV}l}scJJK;ec0Xc zJ$s6?Kl+oigk5l^^v!eoM8@~o<%)dhdPK0W3Y8hp>iQxKzDW2@pvG3F&Uj4`s3;x(iDtPf zgneqxGx=rdW68Xpc0yfYWEC`@it~&c*Lu2``AFwL?}ZVAvkWr_W~5*|J|cBVxP>Tl z@X}EBI>o@j$Nen~mI(P=X}%VVCNqq#m&O^l*g>;+^DbE46@%IOG#VId1Pl)4a?#vOk8c1@!x(X`|b)T7rq&@{|0$}P3201HC}yVH5E6*8n(c^XY03BEF+$#z_**rIrrk%fH5 z_iR?+rN*BOv|WI!>F?tCN2GL^U7`qD5h}wkcbjbMF9Kud#7vU(#E*vCsp4oCXB}V% zyfSBuV#!Wj;po`*C3VF}ew%Lbd=YpkQGOWDlLw@?hroO@$0~`eoYPMivJMtI+R?{- zBBkq(`zspA@ycX-uD``IzA$t)`5|ZtQ2umhBJ)_>{$PdcOnitpMr&1N)a>trOF}By zKp3|wkS6XqhkZC`q1ci=Zn0?w=sLiSQAyg$<=IUKolPiWzx>8>8{wQNVK!3iicCi5_XdQUDKL2tsCXiN2<36z;T54Zr>M z9wsxiTQxprZd$ciLCaAJjXvT*=ZqY~-K=!}c-1W?7GKNzYA(^i`+)R`tcg5C(M* zoTv`o(}J4Ph-a!9daNJakZXQccU}F)*aYwns#{RO{R|GQHWCVqeRHa?%8}texDr!NCq0t@7A+ha0E@o1myGWT zRbI6w-sE&1^n_`u?ZgQ!=MV(Hr2A&>RBe%u3*>E|$N-fIt7{DA*cNUFx=3h9Aj)8IJg0FxocD*WZT`A%3(GPBQb^N7 z+OEPQhF|#yrYP^NO#PfhQSvhU7?pKIdSL59@I@A3(pDn?Iu0}8rY}ji(|I-hd@8IsZkDKD~ zt@>6dCb_4ctfk;Z)ggSX^|Hu_d4iTAK4EbGWkCoPy_K_-pQOKIp|pItp%n2zZ`xMt z$dRgiH1BNs0uQqQFM2A-Z%jWN75@(ai>J}Abel@4X5MgKiHJd}Ry5nV9pcw~QdZ6X0TyXH8_FU+|9ku-l zAmF}XPZ8=#tJ_w+%J<(!!nDL$ZQL)fvJ<6tj9CVYhvDLX@mMsRfXn%U4j*))_3pXU z#agihukGt(kJD{qPZ@})oE~x<^Q1k{s`6Z96`{VHs&g$BCS`L1MV@3#vA*7<2^ugN zqV<5t=O2>uG{FqMMikwmY<=uju0)$B@8mNoo$1uhQA2hJ@2td z1C6M=g^1lP%I}UXDc#^(YMD-f5FbUVV^hN#Q~-!7_NL$qo}$e5;r4f`hrOij|Q6q z%F}DIF%hn zFH|4bdKkWbm*Xx2dq-Q<6n-`H;n$VW`J?N~QTNqJYa+&_v9%YISpr)7=bBLI z6Br&h8EkQ+7ox2PcYAtkV>4@HX2dbw)K%`{yg9glq+Kx zc*WeSpj@qa0n*i@YJiWabX*&|Q_gaY-y=bz8PW2 zjznB;B?Kjy#pQGV4}-hz1?QZY;!Ihu&^y2a|D5J(w5v__(=`d$i&|?-?bYc#ZV(=I zdUyUSz_}+z%g^aF@XPwWeoph0X!)ZR)aLwARcp1BDmN>qw#wBn6Go9V z`H|iG#i^)m!$p9Fps@EKe+3tRgRT4h$+2Ut=L1rI*gs?UK90kmctq{!XwaDo9f!@x z{e@<~EA!*9Y{#H-(;altpK=aZHLQsr`ubLDzu>*swMG(tbiMZwvyR{Fx?j^z#Hb2d zd-CV!n_C{$e*k|-?hY8m+T8p7f6cn*bZb|7?yJq#WDLthYcHk^B}iA#wQ_^%3JJM+ zs?6+J8ST!a3ZCBXg#8V<pG<7bAi5NHO-z1bI3TeWf4+D-zXw(zt!u_(o&2-LuDnC_n5s9=k4FSj?1688XU_rL%1+0$`t%L>GO|s>zu~?l_TzT;}JtW z3`YDz5DNK`T47aUcJUCwW>sCS@EADMhd0#bsPz}I$!w2xP*UKF!1M-T zKgDRa(J59!1;fKqOilbphO^i^NMrv_a{7e3o_FfRMj3m%=ewSa=iuDXk5rzvUp-_k z?lvhdW@`JC@gBiz1-ZhR)>rNe|60{~D?EXbj?p)dMC}ruyHALPmN?Qn)RW41QaT?k zZRo2P)PY&U$RpsyU+Y#a;gcrM4rmDMKt9)`5# zm>LuwT3OTRGW6nre=U!f;{Uq7b<^zk%R_h~(kBzi3s~|`f1(`kZ}R)EAFy_x`0+gTeiXgtrtspqm2;ONr!R#xo16@8&@A)) z&vS07Szg)JxOhdk-$bpZ;_o#I2i~D3H7$6aGUi9&!OZPGnS}$LhxB2kC-;ZA_nSmt z771`v6Q3l;e@~jvkNy?6o*Tlfx6JGDSu=LnWY<@4>g}DYD=xrYAn~v5eX_bB!j`eF zX(HWwQ}I4;CKAOa^HlnN5}lD>vr}Tp)9CDo9KgaBsqr@xzMLsW31T!i~9w3Tr49|7GihJJF5_REq5cig$s` zJQp_SVt)*>e7GQ}G>B7fi6d$)p1w)YY`j_#DUi}3o_wZt=P;5Thl5h(ir%kNe0giT zoFVPA(BCIw&ZuozU%)Uboe?eW&QNvr55QC-a?Bv?5ThF**zUhLXs6zCERH{fjdu$N zo@4~@palS?`s^_p)kH{g7#oYxwX}bxdL^0m)9N9uf#`(EQfDTG#hY>Eu(OE~;&Beqtrruf#9t*)lO;Bb8UY6H}vbvTR(|X${GeBe%deedzHWgdh9L3`y2oyP% z$D3$}lWS<%X4HLCm>qe=Yoxs)RR5b&`aD|JU8>^hU#|BU=I_mjyQ2W6-Q60xqeVX3 zXjQuW#}PrqqWh~@Qxedr5FNmMK_AmfnX| zat4Vki-K;QApDTmpXs?Wq>!QfmfG(b)C78rC|&2U@Z^9;WR~2VHkNbW8$`NDbxP#Y zZDVOwA)(nrl-srLZ{#~f_JGyr>n>V*%RquoVReF#_vTZjMAo+#MtF9>_-&UoK zSjK?BJu6i8cle>qaRqLptT9r3Bts-T#ud}wsMmXK|I_#Bm+>dObqMsC!*4fLCSnJ0 zvI>kp5T85-eKQ&AI*{!y;7#`@LF4r*Akz1CEs00Q&~V509`vuUjNJ*!kUWvYhbJx> zPMS*J$rQFyW^e)+WxJ-ArJ=-Bgj$9PAZBCl9Gh_FpOjC(G(JJ{!}ZS`lC8JRvfg~9 zeIuXyxA0ZBFUw>tah9CvgL|}mgK;itqFlUdJV7b+kin6tBM%y#etmmKT_?USgC0wL z;oE-zyVPa5-!*%?5G>lG>D5HE15e z|2C}tLJ}V4jZ5i1;sO)UF@KE#1fS#c;*Jpc<5e6+VvfjYo+mGOO&tMP{t;@CWN`qQ zEvd$7mcFjnpu^MXMZ#@m`qlaw!5aUjafC<|-&?0c3wAm0`~!dQ`$hEeHuX{pH!cI< zX^C{1weF+D$Ev*GqEOSQ$<5VaMgE*4-&32cvhmucP3?~rC&F6$nl9!kql|Jhqb|{e z+STe=EF@JtHjS!B?oPs`-+xF3`C{mfv?U+o#_G~n-s?0a(^r`%n)~y%*P1b;_Sft; zC+OoJx4f)+%pvraH-6Z6;FMbSnET!ALh{~i{{8g-Xw%fn z5YBOCIaS?;51tWR+f-OQi#}rULW^iDWiJ23gfD+WGxN>Pcj4-G9z0Hshw_&ayZj_=(5Kcz|dtOaQvV<;&&=>oSWz8XBmcM6?Xul}L-cv}4F zYyW>2L2#OKe0BkWS}MTymr?M%9v zJPt@MUcfV-*M2X#8d@K3eAcY3XZ#^E=X$L6?7e?}_w4^s{A)-6!Nc40P2^GgxaW6! za&JyXwq+E7?^bnUiP>t^d=wXN^B~gKedS%CNREHgl!Mx0{)qy`gOgA`Im7BOtvq_u zZmcvDu3D+LT{lY*Sbru}sPRE?SX73*wX5l{gd`p!U{cP%9Pjq*F0Jk=VzJxZlu=)| zs}4K~SK6r1XC9r|iz&Ja^Vvx0l5CE z!OYTMnxpEon2pIgY|;~(-Q6d6zNYi;Ws&DzVuDKSL@jLvKl$H{QCD?m`+_t8KwyDF zWwU30VhVU)&*CyO0F3Z7pcWi!MdR&)K3M@XQ@Q37FrJaYQ<$P=H!d_U!J1i7Wc#mF z#D3h|YOPB`uAJ-p!3mCB{={)kyLJz>qSy9zbiPUCyb3}Uy(l3)_TtJ;=Y4HXs`R4JSfL;x z=}frSYR%~)r5*WWj<_kv;jRpaf=Q)1;{@CJ$bISenp8Q+*->AO&?dc9u?&^{M1P;c zjC=eBrOA6~w{|aK{|k0HNsltK31DwmBXNmbNL__;RuExi9c*%PSv3A9RTPq*YAur0 zF@`?8KB;&_JdPJn@|P4B!R#^Aye)Jp{sAZ7CT-{ zIvRcUwCS12hd0JTwTxFsBUk@^rt5KsKhOT>i$z96YGah0-qQuVXQ1lx5Q>V;>kC+QcCQ!38i(aC!dr7-F`F?(zhYO*NzHf+aYpXbwoJ?M;hX^C{xlH2jg>Lg|? z^5VHVCM4tJsM%sovCQOUVvEkrPNSaRx0(X-9s8QF@0cXOvLvPcdHezD_30|{=%C*s zF)OU2W`;2hW1f`B#q~&a4uMibmoW1PcJfBY!aCX&4po5+UZ%Bn3ox&1iqModzQUkO zsh?m0y>IIX7@jM6674Rjr}U$BYKV+gadwdWg9<#eepOMqe=76Jq9JVj>$?Ca0=D{o zRHZ7MrH$C$jFy=bm(lCp!!0RLQ5D(bnzj92AIy3_m)jytXJ`njum@8pQ7U!ABrONI z*efHx-~wsP1C)C_KJaX2+=M=QZXjj&-a!6$3Vf8rmtv%6gtYaE1SoruQxn%N8n}q` zHIpw;tAwW58aEu2%UB=`3!!F}9+(%LrKZ)@qr$;M!A%;?^@&lfuG!Id$5bJjPUAD6 zL`dkBkG_$v^JgjIzqM)}0_ODF-4XtV%^(NdcBD*m{HKqPw^^y>WRZ?FoJm5KK4VT@ zpd6qGj*h7rE!JjY3`=guYIZ-T zpE0m>Q>>TLUkmwyclIA$G;&0ICVHKJu(ciP7eKKQ zpSmI^+?(jXfbre9lu)88m?O>`kgKiGyC+B%pF z)f@l}oyGNzwk&C_TXJY+;e;`4Y>uI-4FFBkx;>H)guuJYKs8wqIJ9J8O zV8FTGlc6+y4#L?ff)Sqgrr1l1O-~=e|pRIkuZ-HnQN8mxb0jACvk@$@Xy+4FeK$kfUgqsv_En>%Eq)AMT)Y^eYVK_-u0bb*xU zBbS=?%k=eJ@^FCnz!X{ZchzwKaR*^#htfmU_Q1Q$egG^cH~%{u74tz(XH z*GJ=jB6p=%$+@91B#g#=v}n6%bLU99OsyXKm_>=lPU+}ZD5z8PgGAqZVp4CieS}Tt zX}wuI{Wg#Fw7*t{!(UWMJwq7P0GX86 zMOv$J(h*Zbe-Crr@N@%kBRbSu{ zL>#-oM@g4chU^eMfDkKOW_QZ;OcSixI4oi)RVoG1cj409%$LxHIwy|LYJ9bf2{N;Z zN27Ln{&w9>|3~q!Aps=s815I3u~`(^QN8Y6>QohRP(ApLAxLyguuN3ih*as`PfTi) zQ%jwCv`;82V4Vg1+7^bkZ(h7r`Y&y=g>c@WZt=(v@#zeYd5l-TciPKwMMqPm)lxOC4nn}!zh-C#*?fmW> zjmjT}#c86!siT*Jvdr_uU%hnLo8Ail=ZI|F%?W}MqOC~$bvDsY&O?KqCe!_|*xxXA zTnq>7O8yN>?REO}fqFT4{dVX6SwXm*x|NvM=lJ7MkKbo3LI3|uYd%gGT-P&KhT%LL zdA4o80YJxpy2~XyAsO=WIs8L;HzkU7z{C-GJqqglRy5_xa+nhnLE7HkMEv$bH z?xHwF8-Uk(qr2}>YMeZO?*QvmQ+qtcS)SxhrdNtt{dtUr4rGYSh|=W~l^ z-xuX8b4}a-E2<*oP|a*tlJ#z|aHLC=yujXr9eW2Tup!6bG0roiKeC=jdX0QoW={;V}wLobWS!jzR zn|jqJ8zl?pLTp{vc|^G4$%rbQS}E_V)#q_32Sl&Z={n%+xq8Ol6w1iHP4&Vp0>(JC z%(6%UPZ@rw_`vuX%4O4K+^fpY;!o#sGi5j&4IudQSerF`usHgl-P+9H8qH&D;5xcRWg zu8={1T)&@RFWVy)g0EA#2OS9ISqu2Hz{qB*h{igj3#x?&SG!5aW@ zW344KnoA>WKRFAC9mi*x%C?%Jxau4LYEkO(h&cMTVLGzjTSS$xl7d^YIfIPKiM_hQ zI+pDLNpy;$=0LU@xek?mx=a$>E^QkgVwb_DqWnkJB6BI^qs|t&Bi>wMFEaK6_0tuD zp-=&N&By}M%hud_uRGv+kq>I5MITYRDJ;HGGHrVXkwx2 z8>EnCup}w%Aw3`z)Zkc`=Z3t1bp%(~! ziDJtu)bPnomwZQ&#n$ZA^YazY%3jW@H7nODbbG_V=kbqUZuZ|WA!2O$@YAxC9j}xp z&nWh2874XgC6POMHH|ZG=E$+s--bS1F=98PQkDcUyk_AK-u(k0Iei;ep_%?xos59I z1E(X6vVlo?HLXZIq!w_#SE3;rG#1yF^sh)fl)vqw;zDwdqQt;>piM3~^ent-l!Qu1 zf6PkOd@Co8CFsN}$_y5;+tx?o?6aJ@bi-NvpP&-Z_EsTWt^RK0##BEb;;~-}uWzC~R|I*J8RppNxNg4N3+mk8DQOQR-$dt6J?2PUy@H>h2*?qKL4AjBAdNGW2r{>(U%6_1y=jdrvDCps>^5o2LRjs%Kp)O_8)+=XacY1xOyGdfekm;igf}sK88zqT%US?EwVj z56X~^CRZmMZH5FI0psf`lK|r9?|kz|Euv5!<7}I92n)aw8mp7__Ip17Bwq zTThdF9B)6#)zf+{%qw9aSf#$o_ik~R@k4CP}Ht^1KP{g=%(P+`k-pYyU{+; zm;@pz8?{i;2+aANw&HkQMLrKQGoiv|Y?1w`u>L66Hx%~&JW+G@?%m--0%B4gb|(;P z*N4Y`&WmLvWQywzvU_uy=ZHT(ga6xKlI;>^Ksk)zLEXP7eock{>FQXqzDoFQZ6W&i zEZ~~?N6tThUw&SV{jC{fk##Q>c~+Rn?`O8t&FcdsVjyU8{gyFr3{aH5#I^{Z3*Q{7 ziUH9nPFUTO3ovzfA#$z9J7EkU&u%!NlE~o+Ylc&TA0gcaK||z*4yvk;B4iCAxM`<|?(geFWeD|i|>g5KgOkZaZ zzRbM)xH{C&BThg@2)e?5R?Tk%GZs-4h6Xhgu-S3hSDcBVu`+{`(a%-|nTy{~X_`A{ z6aieVo$-Ek*`gFvpU&%}AEpYfC`#82IbqqO>$(wZ3*{lDggaycmxGteTlE} zWu!cWp!sMxhKKDogCA@FPWK=4m*Nklxv& zG|AP_MbVq;(;r2;?npQU>;3lEs=?Hd> z$Mz`386TFzw#w@9D2M>zOnC@OR*B+xp6%(%t}nT~y+ju*q%&30b&`%6EDpa15z9do z-Lt#Pk(A}xDr1W*(a2Bet#|Q`$#q}CNRIbp2)eO#2?0YN-In-pdP}!-W9AScHE`GY zF_L#qnvD2q%xa=B<-wp-*t>w6qrEQhJ=tEV$csBCz}wo6 zEh0%>v}p^}@L#TOH$V@$=q3_016-FDW!Q*nVzlxLyzqJVOSmiv?y&a`QM1ku3|Z~` zV+_{_O6|@`I`R7M)v#!pOPZ}&XK`@L*WReW9pkh?+QydLZTh?s^MWQEXDMx9G!MsA zyp+#YtR1$?Z#K$C5<}g0&t^Rn$Y4n9KQN@*X#IGIAYW%r6bZ+Z_vYbZa-fvHKge84 z{MR9k?hH`05<`byQAEsuBLUPVLoyn9pFDbg9(~i->G)^o_^Z@+fxGkP2cK-im$=WK zc~AWsSzht_XdX+oKiQzVsH+IEh_cg%!=$xbzwK@it?9fZ4~h7tnOXeY`<{!azsNVx z371~1{J^uaiJ7rZs@A;w&>z6-Ol}p{ErmIUL;L@|yVzjFWBw2&I_t$UPjO~kr6c%w zsuI5`DG3)6!|(;9Si^tRk#H#8fd-ITbktDxgm%h4_xwasmzP5-Y2@CM=qf9@H1d8z zF6g$#WRyAaH8wJ9p+a#tDse^OvVGk>!_3vY=0WR3*NrI~n4Bp$41;&~GT9$k0;Zgf zxBaK1#QyWDu9bf^bS2nGqQ{Y8xJ6J2#kV@YP_}5riZQTnWa5{E}f0>46&x_~+K?x{P5}~3C zTsaFWq*;SbzsoGwkjH0riWWua-CQi-5VhqyMWvFMwU(=hqP+`xpB5}~X(gtsn9w^hz>9qxj(>vgd4gz#HU4s$P%TQ(dO=a9`NdDk}0zTkhS4oI>S&;A-+Cjp*F=6prT3SHqC)N4QTFKfm$wtmgl)XH6dR_tmQ6S!(~ zVnH)8FP*i|3Z%T9`s4hPCh#9X4e|}P{sny*a@ZVC+4Z$Mfc_1D^bv>Few~v;K1k+g z!-7Qu><+W4oHpSxK;%wei_2hc%#^qv_d-Ro-J3ymN#CWn4SM@U~Y%OGk{)Z1!aW#btF8Md%I45Ro< zT+pKIZ}_wv?i?jJQj?{NigVAM!S~v$vV`jN1Ro+7NwiUBkdsBA;g@gGet~+d(y#El zEL=0QS6}vo>PMJGp^9tKm0U|r{C{UY{{s-@MC9p+#i|ib7OJ>9B~+1C?zW1ba#72> zzxM`M@VqSAHpm10RgQ;#mgs38GWfOjO^rTFx6#w0*<;@e?RlefNWM3Qtir=oD|UDr zM~IhiYv69@^UiIe^k#i;d`QD|9nBI%iSxIBJ*kkF4PPsDn-d!rM%b!KiuJXlyIXQN z@LwrFo=0D_u_lN(33ur%Q8=_xES7y~FY+jUH;b8h|LA@NV2hR_!dC2)H8&qXw{v+|h#hM9!Ytq~pNFO=oaLw0Fd|@c+&A1$=M^ z*Kfy&QMY_&uF19;qLG$6#IWJZsG)umzE1lbLYv^~M+apB^C0$zKai?8!v3+OAGLp;4Y7nwMt%Jkc0DnK9T?_m8;TLgU#o5EzfmkqE<9bq2DO(s_HbE+NDZso+;|I2cp5ifMBNIu@*jYV##4?mopI%Dp|0-2 zS70;kv=4;cp46=>Sssu2XacQ%tJ1<5hYl}6=1&}k`JKxnG&MQRXooFu!i2#h;-$0B zx&?-eOm8mf^_xh7$=+5e_kdOR`EH5?$Zrtk?e;=+5AZW^+htprWU8>bSL2332{W{t zbB3NO2Ed&UFDYBe9;_{=Nl#P1PEkNhr|2Q ze*nwE62-{IW{52>bm}~v!FkI0?MLWwtpEX>UI$|Uk-RZ6D27EbkqhX z%09RX4k%tEpP~`73v}kl7peh!#;0WJI@Qtn#&{88O(Z3+oVlcXcmsH*2HIqx4s{y|!>t_!b zo?V!{5?DojtxCxh(K16n8}icbTc3S@kXvrojId=xNeCG1FiR&z&l zwwb)pGLllY@q3|-winobQ$*8TIBHxnitjoT2xaSFv?SAeb?Fa{QD<&V3PCNK)we~+ zuN0Wr!f}QSNV+6|j&s}XRi<{RZG}Q0Qhhp_33MnA?uqz8*b8)Ktd_G(djgh`P%-PH zszt`jNUON^iIKe-y7#0UG_qgU^(96n#=e?Z;V7UCA%(Y*eB570T(FR%A2{CKz0ez$ ze()s3z=;a#mH$VB*I9J674L`W2#BCD3u}CMej+k}F8x8+kgOfBoJp&zUxdHyUnEQe z1dbP3aI!X;NUKFZ_u7NPwhr_J#6>tTyrGt~^I|yC=<{ZAsEA4RW2Ki6<+El&7i_G~ zC)Q4%bl&p`-xxf%?@qOyu9?Ja20u7zH#C!jxu|~5=m^&mPX+@Ac9pc3%&?x%*%RSW zu2-Xj&D&+8E{dG1h!*?-er79PA8-^jVS0|Q#=!H=5z`cEopEO+(ezB>7iTy@7yu-U zqi~;x2a-gkgr}um_Bj*|gR)HD=JvCrsw+#TD3Fs1gXG{O4ed7Rx)D|&0$-f5>mi5g zK(AqZrv*m4uU&CknJ#C)$-v?AGh3<+&H|sS>-`Z`yut`$w;+Y0NOXQ;JsTriyINk6 ztc@zUTaUy*r5wT0liH)4wik&ZUmlKGsWGUS8;cD5D6tBSXa0hIiDJRa&}>8s5x`k5 z$JjJ3XskHfuU?sQaHOqFlhVYijSQfo33vr3w%>x%;#C!jljb!d8_7f*$Rq1jUCFBC z@h7O)VoW}fV4p5Xx>xi_PyYxRu^e(wOJh`F9xq!5?YZM!$QmS*cr2U#yf->hDlxN1 zt9Ixgo8B7^qoV-)1VO8My=1-GMg z(B70W((TQxUOXE%wGlT$a8-rXHR$|4(V0_{bP40wjnR?7yy~$Z3@)fg7}L>b;hI#t zkl5<$d#>SIfeBW2Qbv!vla!(r86yVQUUJFmr_gO#iZ5W+XhTRVHL3{61lg#lf{nG% zIuzy0FnN_H%sFhWG~^`SE`7Ea5PkQilMl%^d*5RQdb`U;b!YOiY^O3rT(@Wvm)O!b0r|Y$95uvQ{2*15hl-N6aKVFKdn#T35t=9X1jCO2M86w3 zD>QRiZYjv7Rj#Hb^|Q(bcpzN3oCMNhYC>JQ@bWtNT~ewzi7=8>xSo?SNhzHmP%&Nb z6O*Bv_LP1q#`hJSM}n^`T$QBGv*Kx&b5}K<9*C{{3a_UqmMkH!m3MCF5%rg>kC7^A zGQyd(q9H~zr%vPr1D<#zmgWd>kx^MrPDxo(=1*f($%O=D!CqBSj?ckO)AeV&1d}-a z?(;dRAD5BNv`FYe*}25P5BzvV$7njp_fXd*#@tEXpOx=z;uA2+PU@sBjln@=3y}dk z2GQfzzI$Tx-EGxwk#r<%=dZ47^IO4`AJUT?O?0G7eLwYU@(?6&=3CVBuNcM#(mg%o zdh?2@sIz>I=&2Oj)|PYZsah+nZOj8l49Oj_kqHS`m8;?m(Jy%0j@5@Jx_s9>kia$L zhZ1bC(@CzASQ zYY2jRf2JjEO}z9((hRnIL~6Xwx|H@6B-mODOFa+aEXpWr)sZ#M?CM$PY=1pUy#zj2U{; znkND_z2lK2nCOqDXQfW5z(pkJ0E$wZcxSc`?5tAk)E&UFNjy9JgG(%>?|xDFt;U5d zKbgRI+4ZBL4i@i}Tz4(smq$V^H2Gi#oj;eTI-8cjZBm=_?2_8%J%i@czI!ZUBQ@jI zV2#c0xe(v2y&qJbBt}<_Dr^ZH0R<6wiAhX|V-*HOt z6GFfgOOn@I1v=iQ!V$z|eC5m{_!&)y8k;r?B^Q{j{N2fFdTJ|O=W(6 zFm}e;HTU4T#3x|`(QO1hB5hgISe`ac?deKDTF(~kbcvj?v!bL1 zw;Kpem8d&BWl8e-SEig>b9wD%GJPuQ1F8DJC}Xcm?eCQ)lLI%MTx=L6Q|Edd11J1iEf343_abafhmvJj zVW&Gfe2%!ye)X_8G&qZk{K|S!hoNg%kwq=;V_IW#4^vB&rBPu;bp(>Gh*;k(L*lNCi4nOr)ExR3SaYe;$M({)8)gKW`<2#-pKj9xYE(>=r` zs3g1@DwWJ{+BdSE$|H)em_{j1$?Tqx8Bt?@CTX~()*Ry3M$kuSifC6IGZhX^Shd26 zSV$MZj4$_G4dmi>{^Uf(|ITD_7~^>R7OF!#GaPaPRT@FA2lB)_1FNIe&srzjZ%IGx z@0f;FjJ=DA70CW&`7q(+(-t0J0ATm}iP9FWh7!B5RJGj&_w5j^t1Owu89qCH^5EKY z+V%c%js&d!G(XoCzsT46k0*Ui(RyR5Bkz6uZVHW(28Ujow+ce|I0UqSI5z1AN~UbC z=msF)K-sVRljh#XEajpcAZN#}}H_VrGU4BB8{dvxbI|Fw(YW(B^4(`rTeY-6yiaz&SfD`@tYv)1I&h!iufF+ zPor~q@R~L1Am)c^zpYgmIx|vh z;P_gcmS1>hlD*340VDHnR`(cvVkG)$^7)U~)bDd7E&Qb0@-&$=Kb^-qjYn{=AGVAB zjTtJW{jR>pL3jxejkxAiv!r3LvqMO<5b---&Z6X1T*}{Tl5fA&xY{+NLK&D)ICPy8 z$~;^mJ}tC;^53}o4!EY8bnhet2-OgJ5kqgGcLWVpy7XQG(tB?Tgx&>0uhNV3-jy!W zq$3DQmm-P^f(rWa?z?-}b$8u&zkBa@@9#V3A@h{~Oy(SB=9J9yOeONY=#_Sol4;HD zd(lrPU}jzuq+HDBde-eN8`Y#j9IfPE31IOz^iGgpS5e#yeYrvV0% zRO4VwK8Hw_?~abHLLNJS3YVgLT|btEM|WsV^o(DNr4tb3iJY_IZ`G74P$S(UbZC5~SJ=Xq=6 zhKb=qnsp^y$raXB*gH#X;6;q5P*0=exdZ<2p!(6e#;H+g-0AC68KG>JPM*8eeq7NB z?g}Q%MIDSQW&`?OVMga0&^(;SGXA|EeT2;AxAAsarCO^?Hpsr>*hM>l=LU+;_KnW1 zqJmSRExv?ZucxGL)qiKrw2=~=q<9MwqiW%7h8K<^&7{?jz4WZV^u$G0TR`C@aMDcU z=^YX2n*ik+v_9GOSN@$J=DQ3}o=$hoyWh!u{*9#+J5ct%ktN)cj0c0D$#f&2yHbci zw{T>Dlhuk_KQ^jI&l1z^YK>L&T>qUAkLb z76L6S81xNU2V8(Y<&zoYB$w+9c~_Hdn~WT%>AM)mnQ)rjI<%sqC441RAhDCk4PdA> zrO_U;P-vYF@4j73e*M*kKB<^-{mY10-%-kXDXuq^8%bFOp4Xn*NlfrGrB@xK$Fsr8 zlGXgDhyZ9lK%hJ|jRQadhro>fxW}pXsoatK6`guL_nT-3gJN3y!gP>6W#{-u!dY1r6)wWAAE{t)Y^fDlvPlobO&=r^SGp6;<#7$OR zzC__(+*gWfwA(grE$LOVdXO9z)1IlXG-g+wqR~~f#u!<^A3VjBWrxn^B51stBBd;3 zKC2Mbm9fu0GOGdN<1;!sEj_UIQDzH^_QOBLP`Hv?`;6jdWQx&TJ-u~WU3nvG5Z=tGUzKUajScK@S zEgj{otEaX#@bm0Sz{xkyv4{R^xjj|cTc<;}5f$5^ZlAs(^JCG8;<7(}D85sv`1WInp73qG{{s)DiZsI&tyvoU6vLsR?A}PO;Y0CdCA>F+P^4LSt`G(uQw_ivvG)gGP1qf2F zhu@#Pw)<^8vbC_&Q3UTecGN5Ca%A;WG`i7j$JsP`Q#yDLtpr4Pmi#HgqsjEsXbX%O+UcH~-bUeoeJvld6twZ1L2pv{y_^sQ=UKBYqdNEu90;o?z8+xi-T+Z`!F>Qj< z5)I$roA<^3F(t0OTMT<_MttFS{p268xB8W4oODc9`I5Eoi(nxEG{Elw7AY?28fNW+ zwL1n??gKxSuSTBUiF7kP$cFgJ&dwY6>9I$uZWjtYni<3 zL}tkARg?0~M0Jp!nG%|!>^>)o^IAQLx2QKSCXETdu0y4)uPo;}k&0PKTCRF6c5W(W zW2YXI3?doQeyKtM^H`I)TyWqiJ;`OrXbcCFw`1F9f8k{kCLn#Olpu%uu?59ERmE^L zHLED2K=c*L9rkNYUyJ|I2|MrQRNpGPBfNIF7}jP+yy|z|`wy^l$nnw@lQi@|XrB>; zWU|O1{FdgiEkb*pEvM>Rc&TlQ+D8A#nhtVZlIn6+qjKGbKN}(K)Sb0~ zuoYrQ<{RoDe&w9a`9^Lj}L^XLWi(JI06habLpH_@bAS7&IriY3B`Q}KylV zC>gb}F9z|>>L8#0YpGN>b&ddZM7e$;{?l0X;?|w04g9&&0p))) zrb8E+wg;`=^AA91jYThi<}~BvzV?mnoQA15-Pt>mKqyu}T17+qnpOk$5X)Enf1|lh z9nsTss6^$-8%M3Yzj>Cx$d_Mkd51V{jzeaa@JNaKtB_0739A_U*Xz|xR<(4euT5j{ z`U2G{WhS2870&EPstgLMk*!8BT{X^PPxOz85iSs?@G7w{SWXmM^|IQN@6t#R?Z3qa z#)j<3gYO~wMAR)))5rQ@3=9R+fq3AVgnVTVk$z7j3htU1v8Bt^^~8Ec86=%6mZbt} zw_!ppmLpt(-CJ%?Z4)33Jw|p!$?K(}Wb;^8?cf!d=SppAegs!zcTJ zbz61HY87~lzAM6uyOKvonNk}xz=Sw2oS_F6ntjtx$3)$q=-w7bVU1E!m==Xd?%_gE z=@H9Q)@zKPSzKupXVh@4Vz?+#a;8;)%XguVBgPGaIswgpCds3<-BhPlPZrF{i|oZD~;2g9r(% zh1m^VW)Si@2VWj79@|iQysXoq3K^Ifa3^@1R@Q2#<>Itq{KftiUfHwDNvI;3u?cEx z!mj>anOAhKDl^k2$Un(H;QR70{f$Fyu>9Y4_k;!P>q)FlfEf?By{L;Rk2LXw$ z#k4E0bA{K!{z-ycEIzx^l^++)&)jyXf6(%!CaB8%;o|;(J2PwJTsKcXNDVBIamvhu zj$#FO*$8w<5t5{Kv8vA#0a%{YIcY(|V#)-vf%`xP85QFY#mt465o7yQbt6LR;&%Jb z8@(94t8Ujdywz2Y(=WSnzgc+$G%&JWqoiKo%C&-HukQdQiKj#aW0kS?l5GHGZM^*S zk$_WYU{9*QMi5t`tbV)n@PqNqfON^rP`oPY-{wwI6sa&RkLJrNJ`c^T)^lBBZEuw- zmfRcv;uCw7_qBq0@VY@8jA$ZUV_8t)1~29++G4YC_d7r@f|@4ECNd$$S+hM{CCt+O z_JCXqRi+*Laxq@#(*PW+zR(SqY{hA2p?CNNcR(XqaVKVTVGOCOMqhb`q7As-J^OGp zrbc1oZWdJEa#b25Gw;`xw((X!uEUIH`+tmOoGZK0)2u(ZcI;hL{eq^DUA0)9kDi*r zzk9|3YE+wcWvQ2s4Sw-Zth)PU+;Y*O8{Qf+N~VT)4;>HRsR4G@uPwXUR}=fEytT_t z6DbH+Me}5C-VRJKV8@CLtAX*{CWAu25%NRq!>sOpjsXP4tw825&l-l%8=H6VFF42Z z!U+q*v*wGh3&!D*Y}(BL=)KwFUX0|p!BZbNCfJaGusjxZ7fRe5~u76q%`0({L0aL?Xm%?Nj zW+gl3RbkFgY*!gHK*Hqxvr4hjV#rW~vzpd2(tOK8sSEXK+*>;-Gd%H@nM{PGfYf6| zwM)K`B~7{o=`@~PI>m6Tj@()J&YG*D3Vv|19Z{=CQkuVArAsykh1zi4Xvpmjp0yz! zCa72{J;O55ID4#K#T^q?0c=ZV@nr`kAs<;W2*FcK`b*UY291q%zem#NP(zeFqP@Py zNS-5tl}?VfN&CeOPOI>pB}K0;>fORrm&ZUCI$*zluY!9j9=vQpjUJOXO`c8*DYg$7{5e=~!2sc}or zOn>9SzsbMefS+u^eBGmeuKlmEmsSB;dbCUU$iKw>8E|uge0NXmkD>uES<1O`gFg%h zKyJ>IqyH!zi=bB%tM8AZ0f0dOlmDOpU4JYSumSbQfdAbn))nHQ8)LbLjSv1vg}^tP zMqjYoUiD`+LjHl8(ZaJ0R&zl$HHmq>e*pe(K~k;o$|~F=p-Tm|!`KY@>E=Hh#6fgPfKgoY1^8VIS|MC9YY5nag{!cg5-&u11PV-j< z@UQG&II6!e{~*OG_y@}WIu&s3r?SB4pAe>K5>XCZ&p{2d2G{#Erm3vBR$7|t`hd|$N1xC>MwE; z|Gn*3S6ZlS@6grfKjz^>{wV##QxU?))$4yy0;y0xNI$bAj6-&M$iLD4t@Jl0K;vJB zfj{3v#S2@E`3z@qhp12mIAVf#&BM7U$N2pGy4Cidf4%tU;+9xfXw{ASwy)1KRoy({Fza zuKLGd|Kq4Dzj4F8md;xVyfAPSwnASXU4@oY34@EVs!!`Pld~WF9J+LnQ zv~EH#BL#Dzh2en%XZ9pfdtRF(=cRa@S3LqSfk!50yj?XMu^JHLo7>lT2_Bh0E~*o! zOO;=9UN3K3$ofgtq*Xd9XBHj6BA|fw*GYWIGRnMaUfp!K#bv%dK>U09=dl($ zA7zvi66R?HOJfC7s;hJ&Dg^Wo&6X)dL^OnW#l$?GTuF{O~#C?DNs z;m#R3p@z(IsJ$ARSp~{ro zydN(e0`=?|5H^N9mBOVU#HZVHp)R3BF`6u7nfdG;jE3Mcj>@i5Jrd3nUM*kw<&Cg{ zqceF`IhMJyVBwsR69%i#HDoj+#Bk?_;ZB0)6 zT4|XVvyqW0{J6+Op@A@YpK4dAcC9$zS*7x4&Cjqtdvj3vRG&1EIfXfXWk^u6-OswQ zCYNZPy-0m42^7OG(c(Et^&Wjb80S z$TTE}(1cyNoD-0gWK8hPum-Pe#1PEP`rgnL5V!z_HEU?JQnsca4fk)`)srat4<1}E zsXx~20p~uWZz%XiHoQ!VE<1=@Wg`RDW(^ns^ov|(ltc_~%YxL=AV3RO2tZ3!IE+Ht z7O1e=k5{HVqS~^;eVu&OM#VRS0A-d!Hpoa7lK?f$iPOt1>}Xurzj|sq*GSVD!JCke zks#*^;j5rvCBTXX@hIB7i8xNQljf80>^jROWrDPICfdkO8cP0 zx$j-QwITS>f!?@+LR*9q<0MvV zq?U7}bcZ@yd_r8&ArC_pzJXld-37sy8tK-c!5R-^2tVrTYV;8j_MsH)q^Ey-2|8&Q%WmNl}P(U1)Mc!-u{jXQ)0=B7If@qU?R`)ycFZYH8 z9*3AY6SoRitn9%oiB+l; zRJjdXEeVVWHjH=q;C#t1;;^P$E~ioCG53MzuTphn7DDG|21H2131T6L#_};>vubvXT4i^Y%lri(*I}tS4 z<+ofypc>Qp71&@nM3B8qOHYbSe7t~)An-fEoEI5o`;Cg&@%h0>T|03fFo zo}tHEJ$GFOI}{5XK0H@pz+{27N$6_d2ZY2YTq9`;V~tQ4v;w3H0>uQUcC|^VA-w)Q zb#AUxySmX~_yA>9PEvdCbFwlS$gxVoA(Lk*mW>S(iyaQ{RJ!+)!g4G1xs6p8bq@7R zWVJ;4D;2r5zEW`+3ImWSE9j0=X17haaN`>0p~V>IVP6QEQz} zC!(;K+GyIoR5AIPTs%^%NlH+E;7Bw&a{5sc652?8qg!;5Kn~LA!fVolFguoc41Cp`01A4pq1>dsH|_40?J33=PYv-IZ|=oFY|OiR0v^mdhZ8_li&#r?oM0 zAVIARnu-E+f;!0fLLMZm-DA@d#4V6v5_zpgG)IyG79Zf)&|b@2=y^+KqY%bMu}|Vq z{yD&k=XzG@hDJT7Zt5F$CPa}Yv3_^BMtHwgd|6Ec&9o0T!;}UCh@UPRE1(B^7b?fV z1&*}FHHOgZb&^yOoN@8l#Me?)W@-)N@eV4JwaSQB2(jg33_@nGr27zsh$3bRcB{}5 zenT?|frXx&UBt*`=u!7>XebHB1Rk|q5Hq@@7VE~tr4|pK9EMqG-cOeXaVTRWYF%D_ z0T=Vtn}$xLW=5ONADK1Gm@FsdM)XRxL?Ool_Xi_XCxm~48oX4Je5ccZNbtanWbe+mYE2k2!(z0K3!YzB(QoH_iSVmMfrG14sMO)DIOB&W6BpMNag)+_%gUVRpfnDTg# zIAOKebXJOzJuj3o>Z`l+QF=5em^^Vi?;2;z&8!LAfzN5YWWwJ8^bX9LuihLyp$WM3 z2eoRA|J+epYV#oD`ko$qMwRD$HZ1%b)~Jb>>p)|l#Z-ent3#jrbcf0)-HGc!+rjgX zKGo;@!Rw_gh|jaIl>W?HJ(X)yAuI#mu73vr@ntj4MaxLuIh-{QmcB13O_XSQU+^7J z*7E$+^Xv+T`}AYtG3+RrA(N~s>?aD_yzHLykBMJZhNV?O9eHqF>_7WHN^OqU#VmVn z?bOj7>~~6DWMP+^V+psVU%j&N_O$KdM)~??=Mb~M5u>IEm&WagtL%QMBp(8Cf3o>@GoYFd8rsiLt?=H!ZCYz$GH`|M7N70S|b!LsecK&6(E*OB`t)4U}5 zx+#?h_MI`CSFj#9M6`M>o*?M}=Ph>&uh?Hs;cbm9-g7NVymnqHa2VjZ8T>$HQ~IQB zYJUDofR`(MdV!Hja7(1{hRHKwz85r-z7F-;3S5I&FZMR155HE=#cSN59z1Y*r&$CE zy7{zl+EVh{{fW(mIm_Mh$1_uNf!u}#9yHFRma`8wJZsa(~IV- z6!!4%0EbhV8`Iw|oQY%?E@M2P%AV?Y{Q3Bi&=r*nB2$vna;$|wSB;sQ4yPEo zlahqElC8oT&mA|Jm|m&qmu zocMhekFh&M-ZW#UGJl;N+j1>t$fo?WWpu>(H%Fd}#33e5^R4&_N8*bI6C?y@=X$~E z4xDpAY~|EDiR;KU06I$`Mql<`;Hgy$1$<(5jt#%2v&P;SmM4 z3*+bamP6Oq#8~p>EzNnbU|ug-;H?N{!&uTa0mNEd@D+}fhl!7P+Od2BvNm}Y2lYNp8*b%HF_6C{)(9E5uN@$vD!0t)1!^9 z^&#JZ*ndOK0+a}6J#G`Q3P@Z+Q$4x3mT(q4dsjSa#`zPfE##f7qj6ftuL=34wINXX zEGqUtPkI??n>FO|VESt|mZ8%7!XK~vZ=nYdD1tvJrDpP$)eajxR++MO%bG$NwB+<7 z`&%patqbc}WQIfVQO26GU6h0rI7JUF+>$9qlo(^&blP+n-_9^92>?XfnMYVRAS187 z3_1(YGbFJpDKLl<3{GLnoNVcRkCFnyJ{jfV5zNEF731TFo~VUo@L9&BQ>h7f41m|_ zDZR*t*P?8>RTasFE_&($H9yX`g4e91fBZnR0Q{CAofzUbdM zu6?p+nmE&St<>kH@|&Qv=NDXmQR}^tsQfBaN$I(H5n>Zr#Ef&NxNZr(s`PEJdW-yH z(D>YUfV&Fs8^7A4_onL`T?0eDQL*o?kiVqo>Xy{^`!81e9N{kfDp4n>b&!j5>3WBYBq+PV#IVocJk_uqe=_-t(V06nAs|KE)iW=_y zdIduu>pnC{g~@DnDM7HX%vwD}pmA zeBbNH`{1^Gh_`e~|0#xA&$^)dN(u>~F{@Kg?8o+r4Gsd42AphpMeqJ?#-z45sC|hB zd+30bVY0V_z@0)f9AY#WKmh;{0I?re@d+q?j?RzPYGJR7^L^9zI?+>>_pN{JhmWS) zJ6&TFz8Tr?c!gh@Id*q1`u``?J>!%-;U~4T|7tINyQ{+VZH6KIs~{Ej_A+o3iDh~TB9g7DZYf2A1_nRfX_Z=8c+!=9d3R<^|JL!&>V(tX zj+#VY;fA;W6()NaR?~#fO@}u@^Up7;8ouh^`BeMl&@^$m>sre{SKmi@kz6(;&wh%p zrk@I*NI{r0+%Fe8EJz5k^vW?F-foka#v5eLla7!Gvmabe$!3BqkurLJYh7Tn;u(Q7 zS}1kosI4!_E3phyHC4{OX}qt4RYe3GVeqk{(%_(!77|KY#P&D9g3(a{r?l}5xfn}W zJOEZEnW0Dh#%*S83ttX$ZlBZ+hB)Gs-VKSd>NblSaV(k{YHodC6T|2){Qe60{|+L1 z!@qHBsc2I7oRgc&vh|NqK0p@VVR2mY|AMB35s7Cfg@Q>beP8*p`M(2Pn}f_hvyo;7 zvc9sRQjC|-fC+aQxBMZ}+JRdM2eiCWcYKt)7${!*#nbe7r)zJ*cQ*T7x$v*l^Xb5X z(ZkyoK-BJ~@6(_;rR*?fmdlZD(SOnY*ZTrvBKVtqGcbAJ0dFTBVM~!9PaW=gBt0w& zf>3|Z8uS|8^_4(*OfRTWDH&IxHb2Cu-KMlBQ_!9G7Q1r{N2)aYOKXbVSVnVJiae=d z0%VGKD3TR|VyP}+KY{b=V2mF+*vDJ8o699^R<|i+NWt3$anyRB=qaUb=&emf7n9oO zj&?ZgV-%`&X&&{rbE?)SPZ{_?8S^oenWpVy93rEkRA@&Pa2y5A=K+yWO0x)Fl3EDBX26@&2EG1`JBZa)JPz&~i~ zX3;S?w40b!habZqg;(1kR3R`zamGe|5zBxYB4+?wq4Lmkb&wl?kW(319Wi7{u#AJE z7#}H?)yM*fG4@?QE_2!9hy$32GR|RZz0IX!Lm5MwFQfH*AD4r_;&{J*NQ4 z^=LG1j-FO+@?nuo5%nsZI*`aZjSS6zdFm(Av6n=48z71hwZVv$6bEmOa#DmCBRF4$ z3`3H%vah~}IUHI25*~+1#4*p&zYNgGG$p>`k-Lw z_PKk;l0|ULx9&@CAl1xA6DOrrml ztn^(Lp*U8PGhsxfz!~gv#po)kRQaQkW<`b(!sto*A_@|mICdRg(I{5Y7AypHA@_J@ zgZ0NySM`T)VS-l0)iTa8_nf8Xry6cl_)n&WYtfs02gp@dv-PGvE;iy}73R70@qM9Y zkyPjQ?Z(*Mx`BOQyZM+>*|gQu3Wp?u6rzIC01-oTUDUFT=_4!fY_GL_Ix8;0C+gXdUyb zPOcY)dc4Z>6};DuXO&$&+pw7|ppP_z)hsM7CUqXV*rm z=PUHs4Glimn=MI>qA=Kw2()eao=)^tcq&ZT^|^$$49Wt8}^x z{m>Iln;C!O$x?h3E=tAWxdJM19r8QC1iQQjN1&P<=G#EJa5|F(W;>Tt;$Tv#!n+I% zE-=}6>&u1I;`%4!)u3xK?4z3NBf^9fCSzCB??)vk5m;Ae1wi%7wonn6NO~-8T3@HGFBn_ijT7gfdQ@VEGM?#Sa|w~oxQ(h zR&I~Od~`!y!QW)2k5A2`mR!Bhf~|gItO^B*G6&zB)GQX|)X9sQ#k%y6tp;#(9&3B= zmK{v1{g0?aWEhQ`GN12#2ejldJedV6F;FwYes3xZ!~j*1)iMW0ZUhEa3Lz0xgS)2gK5|l|TvWDB zRcwC)jLbF{hm1xI)-&WB>Z91V?JW5t!XPqF^?Aba@UUZSnVAAy#F%Bd%a%(>?OXOh zN!D5sIQTEMEh^%sj%ubI$IWSOBr0otmWJ%HRo| z*faOq6JQmspBWGtg0Hi`BMq>2(t?r4$|cFzFgIMkz48upOCbuI4_J?qz=?eDOnDIA z521WWLGj)xSYVK^={4{MANUwc_B){Z@elJvaDbN+MShCZ3q>UtP9HT~#_8?*El}_K z-{*;dexE1e)G9-oQDFK;2rl!+41K*BkNF-|v^Y?`A#8vwerT)`@Pz+$j3j=Pw9|Fd zRsCMwXwqGT7b|qTEEbs~ptD8jG$5O0KgFq;EX+9TOf?0N0CRk+hA_ZB>^(eZ;JX4)(kkhj|h$i=R8I z*hMNoVGMD>{cW|?@PG#5N*O9Q^j(a` zI>6M_43SN#Z#BgPNRA=`CXdgPs6ov#)>I+PRv-Kd(iO`@^}#Q6r<&B$sc^8>>Y{o$ zqXCG19C@VM^P4C|;@$_kAqTSX^R#z>yEDi0>!_DI)}iX7$=DsZ$OjgUb9QP)U(I_| z@QyCMZUc80Dg_T@iU(u883c^qSS3#f%X-qH(#jHnh&}aDedm2zsuC`_6NprLxMk4S z%jg|9tli>mzzjV60!DHqA|;&Ks8&Wg<#8FRBQ#)_LNb3q+6{s!EorZL-GZGwhTY1* zZyiQGT0_Ox-xI=y`VN>608VY&&+^QnH8zl4fU#9IXJo4dXs8b65$#nd0=Uy6C-fdi z;skqzOxO2_=T6s)PO|C`9}fE(K94B|YIxV5Pp`QV-#STuK=VM?(~Cz`FLk}$cP7&# zl;GiWVh2!wYF}@m-sKXU;-$owA;8-)dALRZY5#VjCsl?WH4H@5&p#K0NfNH4dSZ2y zOEe#Pc~PBsS2HX6`nVYdidMU3d{cik&_XtRxk#eo9#TgEw{q-Rf!V#e0u>{R;*i9Q z^rZW4FSb*j&-S%_$#X5A!F(HQqCRc0Fj%ffIioHkC%)KQ^z}hAS{d|&DU-R7Y$XO3S$11lCH^VqylvPg%$KsP`&PRas8YL3_@_{YOx)CH))8KBY|Le$`>& zPZw{=UFE1i=CC)h4sYF0umgJRx@=1kxXN)K@GW{6cc_p@8qcMVCJ0(=XwFTQF!w}q01z+ZMu zRJdyZ35UHAw9d515ul3}s;T#tLZ;y-h_PFN+XRHchhnAB=t41!<(-$;brkValL(+) zIAjaQ1XK- zeV*QGu7ldBS1L`#QDr2Is1(-}Xd&o;IRO%+fd;2K2)ux4hdR;5($wmimHg`JMHC8d zF>K83PZy8^s-5PX9Br8f`m9G1ETBD>`uXHSZIso!NhEqFOBI!=u;I;x&t0@I(hf13 zW>KQ>1By*v#j#laqOAeYbpl?xOHKSXV=8(^?C9RQ%Gz^?JCBu94QsrkpVgkr>y<4U-YZA{P0c_b*IWj{MdL~T$+E8Td^L!A_oB8W7pBu1$$ zP+ZWaUEc~zv2A^tnXZ29iU(j`0ZOSS^{W;w96)zf*xY;WK4f@}77`}46oKen(=utm z?01;z**Su3b4^_WQVDYk@8g<9^r-C>+ccBOixVp`8m1<9(I7DzLwVBHhETbjGF^bYiPbxxkI;-dxP<9EoQm629u_$JkM{T3nA1P#7lh@+d!@xotAzq zh2&Od_l>cb%#58LAi83|wqe;RnQ4TmXsMxSis@K0VN*;z{(GmbdrE&23 zH_+@dILQ7=?>^;eOmf*^KPf=Tk2AHsnF_NuC%>&qcjU3{ff%8_tU^{ z68YU7I69#H%F?=N83yWJOTdx|*L|DaAztT9j#5qY9 zvARN&3PkU5o!H2dhuicsEmM1vpq-HNHqHo7!322n68?_Ow7$YFv9z%qjzxVISSyi| z?|D7YdeTeL(3r#)9OvF$jGYuik-BEtOY;#m#VPG0M?%7?6hnrD;k+f;j-V80CV`aD z!W%h_TsYG^U^ZI5@i=5&`2n#0==-v5LI#vsP24u;a)Vpc2_IOFTCl^15OH=z6|xGw zjG?)cnFWN3gf@8E>`O+B6{#gn)D-K;UUo(#{xENG1q(XKDh>{`hTAt9B)i!oaP*Pk zm#bYc+Y8CkkY;r===S38F=U9VHhHzu++1cHY#m~5Bt?+Gxam?nX2LPD*l|9F+u8-U zk8d#xl(3KpnB%{t$O*yG>qT%R<2wgV_G6DhdQ$*%Ra{X_dPdk7LaRuC1D$*!Un#3~ z{R{Rq6)NC-8r)a45BaH+r{97_p81tVS-)bN$<}26KZ9g;=AI<>)eb$1O?EmNMWRYE zraOcWv)x}bMzm*|0A#J2x+Hd)&=#uPO)-hvF(>&pz9vd1weSQNq#BqY`9fJY%dTY`ui0ReS4;)A{BP2-RG58}G@B|GuJ)sIC12SBoB#twT z3L{?+geo!NSv1HR^mi8k0c8rR;V)5`F}g+|se3f-aj_Dhw zwGPt9+?`zTB934x^jr65r;s<+$2Hfb9vHKK8^k_-*0!&q5_?;~?j{R9-u0V68HMFS zDV3m7TSUbby}1f2r}GH&kB#SK@RVy+H;mzTsROyxpV=;q-fRE{3Lbu0$`_)(s>C9P z>wYS2G8cZum5qWNPu;hxRV~wrCI8+EoduoXppHEin!u;F#|yVUWU0uqJLUu+YQG|V zG;+~zA>_OnoANk*U-05lcncyatEcnf`Yw4nlO1Di5>-nlqxHTpTQJq!=9fT~0NbJE z1{6;`PStmSv6i6LUZ_y)-W;P?yDVF8{u`Cma-d1?H)X$zh_jagO7Vk>7M`=gx-VH- z8qYpaN{Sv|En`$EeDaoi*nP%ihczCXA9%(|eM9b(rTPSu5Kk79VQm7AZ4N<;&B$|V zBGPZ^?}wCL4SQ36dnIF(uBiHQRoCqpe>S<9L|>86Xq80Zgyyo^4(=B>N_zx1aR<0g zxzO`1q{3Ub3b2~g$LdV6-M1^n`;BReTM#VGMQom0bojO2lOOPK}GQ4i3;sXk&YPFTa#M7Lo7dLvbYp{ zex@LrohM+mSyIZF%pN{4D_OiMfn&x_jK=!44gqo6)On2Bd_Y8AwO`CrH!<}L1Ik-B zOW^Xewl_A}MU%!15LhwOTLaG`RX{sJ;%=TYbE~56Z@^03#8>dX@(nTpFwz{3^^+xM zBqMxO^ywcN_qX5MJ+<*RtGshJRXGX}IF>fLT8;$j*5l|tFA|Fn#VhqdHLf2p#IneS z2W^dPxoGPtiR$iC_%v)dX@i8ENHtz((F#KWsUQOu6bo`YmXd6O$|zcf+0c&Lt3V5& zffVEzb&uQVL^?8geB~p~^kUCpcz$RXu(B?hF0d8(DX(t5;EYn*mV+9XDf0QE(y!?oyn zI*|tsUMweT-vL3mobj7CgZEQmmvNL2B0?I8<(gt*QuB5By@}^3fZ`){bQS`R*R>M_ z68Z@Ch1~aEx}W@?%Dw}tiErIL2_X<_0-=M3(5v)b0t5(Ex^(H%1ylqq1PCP*>C%xd zy(3-eO^{v%q^VQ|1O-L>bMOC^`+McS_tx5Lok=pYa?Y3Ue3O~8XK$^OBbxJj9X0B5 ze7l_fq&W^Z%(m@)1!481oPJA9A!K4WtXLyM30uT?SUtiMS zKW!^-t~y+GmLE`xIXeVj_55xIG;*!>-DW3|pHgpuc#Q{CMiFt&>PpEM7>WI+r8DJrvZQvV};rH1s*=bxr zo1NSkw?+ud=ov~bt?j`WGl_4%-_!q(0o1ocCx$ai#obz7jvhJp%T!g*tk&-!`&DB~hgvGfTWL3x zm;}iQGh4%BHw>K?2U`+_fA|Fmn@7da$S0wNw!R`s6{xmQt~p+x3}?m(v|NPApzyKk zNq$zs4{llxm$)+nx%ky36YUrDN1<|R~Z zuY?+lry)j!S9+^I(ke)RTYcyd;J^?6p{BQ-%~YPke$TuN9mG+}(c8w@PJE3+wWu9B zh^w7PUB$QS$;ihCcQ-TBuiM=J{ObvkyOo;J-2OA$7)vARB>tbw49#{Tr`pe7Fn!ce zepyF8eT{1@i(qFjar11cCcqQQfvt|S8kaVIbw;~9yqZQSMg10=+}Ny`u}ZP#(qy6x zY{)wh6){(MqTbS3Mbw zWnkr5=GN3WQBV(IuPgPOuDu$m;tY>pL7$6>vZT=4n6PY)tQmAZ;EwCp*OodDj_;CU zp0tJP5J~{G z+NlJYYiN)}x5WOX%B_PhFSr26!CmUt`B%GJs_hl0Tz>-i`T4`?u=6VWmu`1m#o7Jx zbtpjb%`%qIH!j2dY73f<>sxZThZuC>2{b0q?PaT6%FvEG7t(^Uble~Z>J(hgbuN6` z?Vacc4HU1`E7faXAw$B49Nb@li8s5{4d52wRd;YixmG>c#G{pUgUJ3tVhBw}4xiX2 zMV2rJ$dHRRUXyapWSD>)ro<}u3)gZR_L zqj}|rb2fv@%a1?KMRoJF0xJvaWPD5RNi_{yr~Xr-Rc9&l)1K-Unyvc`De547O7Ik#xdrB+Lh*<;efqF*B zCEy6wXhSzfjg4pOH!d7``TM)H-{OY&!?TT&;B;C2KjzbFlHQ&I9Yv&{xwSplX=8Rd zk7Z(fUclV6nVSNm0EbR6@-VDus;e3K=%IwnIt|&~n)F>kotV|1nU18TN=#N->|d zQNr-=aOsd%1>LqlslcHKoEK|r;F@y6HR!H<6>actM&|F-_P`K1;`!kWT@n}NL_03V zD{IpMoxQDrfzWS-gcw3dXJuvr(hvEAy7@5Ub8g^&jTf^=^ZyjHE#4cFP^YinAJY>U zsYkw4&EL96Z8VTi*cg63N65NGL2VE=DQ{793lM~7#b$W9oN^x|)~ot^UD%^2$WEZP zIR0%~G)PQI=Q`&NW7_wULx2Nrg^%)I$cg0OYDfyZRzrrS7y}&#a*;dShXfzKTC`w~ z@GdE+8K-WqhX(+8Zf0MD#4CxY(z314oQ zNg8%)DNxc;nh;r9{$P_G_jXbcSi?a(Z=Zm-t01Sa$0RvtI5+mcx2b4yma<%of-#6? z%-NOZG4Mul$k0ExXf~QzVH#d_>7+5UTUjandiS8M;(=AggZAK!uNrFQR69J}Sl4%; zRo!?St=axG8@uOiYbIe`VJlZM(VgQ8-~KaZJL>zuecU`W4{9nt|6ftorWmon04ZL@ z7x_`dZ~1Es7jBLAhR@zPr#Ek;u*-mS6E_|^X*1a%-nT~PqJra{D8g8(drcQEZUwx@ zhkm0u(Um%O^tCFi#Vz34lbVC$#BQ39^20dFkJxkXF z7=cZL_sGQ`RVj!*2sO%~;6`0N34;O?xX-_CH48(>4SB0atoughv-^;3Od>sN#TMRx z5ZMs|3>#$-`kc$hOwhn(7(z}j4sWb$zwq?MYV3zHQH;=f4@AyTJ@e{kddhKDzI%Ku4{k^=4>`!2b@sk3&;Y0V(FG&B_h`p=f30sE5eg znH2>kNgLGou*xnuaVuoRq2dkS9P@_qf7=hv+Unyzd#IsO+N`=+WT#43&tcv4_J0SI zBFKtEEJE|e;*Sqc{vE5&#n2Ch78aqON#zs0{a$RZbzWI&_I>kAWuUH|KXjj{6#$&- zK;LsSXYF3h&wl$qVLFII?HXN8@ilr?uAu6(U89Ci{;S0LUD`j5kQtnPEkQ92t|`Cs zhb1`9rF03tt@?Q=vFALg4@+|I1PF)K=Z-myMxv?V+pJEt@Q+PVGyMV>L3=-=wFC0v zCRZ>L&gD#sNht8?@euLD1DK!*;Pf`r%y1%gYNrZ!q-%LevfWFnMaoXF+Gg=Raj(>Sx6Vf1-^h`Vm_ zM^>Sa$WhEp7VqgjE0iT3FJvI%tA2qsI~A(U*pAPg(YEJVeK=#&xf%2 z)(INGJ7QoO0cSzV;W$Q@Q~Eu!BBQ#vk|~8aQBjB)bAfEqKuhS1(hd^Fr5gvpv1V9d z=v`zpD$1S@(0jH>U;gp$4#ae2Sqo2VS|4eWL)l$OTc1&HnJ)UK}_(`(S$#M zIUH$)VX#?9au^a zuut(rWYWejrb`{^h4G{>0an$0EEOX9B-M}N)+_~ibHAO zAfK_kS0^GP+rqkUAE}sR**TSPh9NtXj6&})kYXMd6=ox{1o_}gBl?YUBlPs#&{5@R z4-4N}5@99XPC7Qf3RQBANzv{i7|dIQU)sGMl$@?# zG_!e^cx#;NUtD|_d|IXF z2N(PlCfc+(B#pSzu}IS6T&*-G8Y0*`?Y4GsM|kf?K1z&PE%Ad>rG0q1W-Q@&Ca^r~ zhgjBDX&{tT+K?KpaCz>$`jUH-*HE4bNj@j8%}1LK3YzNi=?gnAW%Y?_^kOWv7GF^R z=4l&UFA4ZX_uv@^2)a7=I+d{3?d?`Z>MMY&B3ZN~RJ+0;Y66anh{Zp-jxc!YX8 z8}GAZP_jCX$D_MCMZ61}u9u^!5yD=dpCKYHN=xg`#x2NZU={$hB?D|Ra&i%8m_QCz z`3GjKk>*KGaXOaY^lSM`MX+s#f?1T52jGIGHI;K0cZ>J509>`F>O$v@`2r{*k*80* zTHw|qjbKk$DYxJzq65u;Xy%iEDpW@!I*3@BuF>E{onWm=OwFn_DNcUI%Ws-Q*`Y=` zkrI5g;lm&uenQy|%O@>F;4I;h(T(bWHM^!+fAY2#-V-R4RM44SR&ejJL8$K2k;Oi% zFS^GmfZ_h=yGny^l1C6Ob~D?vIGppgc8`<9dx?+wtw{`?oa<0Vn{slENe8EE8(ju8 zv|Q+{%}U$!tVF6(q>Rl*_ZW;7cRNEddm{A8h~8YUuO-Q7fClmosK@QXGWqy5X1Shm zdECA6$hbD7qf7Rq%5x-Wo3u74^uk~X3`&2#$4LBfCsRxp<8bDf;HAfOSx$p2hJob5N36*89Wpu znOvfFStV(cBJ^#(_K@Bg3tm+z>zkom*_F-TyoO#U`!N>F>A=$RDbKwmW(7aPWIn}e z5P*XX(eN_@=B*g=mC-@)U!14qD5q#b1XDOaqe_uw*Tz&qom=p*IvJw13jkN-(C@f9 z^&8}?{ym%i=f>#sm6GUbPLS`VnUYxB)dTT#>*cyw=gz9CvLGC^z|ro$IdiS~mgTmO z78FxTD`EA7S4t(EnaJ5wVyK7D6G$wF3YA8%&W(n$iVr!fBo0(=L|aL0YRRg~Vf z>?E6a^62(pL%80Ipq-JSih1CCcWDIN{Z#OGK@VATE{L5j#h0U^pb>oC_DQO_I);_jEZ_T9zt5b9BSQ? zwD*#_{q`U}e1f53&qu{HB!zkVo!{_;89$&)?L*=!5MuAJwxsF)=!pZ>I~;x8s-EB8Lo{bt*aG~<_#6R_njD_0)Fc{_ z0xF|7ktoQ>+V2nn1Qp&d@`+eq_J;2oc*18CV80g~4W8oe`wXtD7q^-q`4AS!b~Hi~ zF{k(;Nkvdzon8fVSAINs3)}lUt+6E^EI>5`%Hn&21^M8pv)tQl#3Y9j0c{G&w&Pnd z!r*dTgb7$kI*vVr@f-S4%vZwtKzX^msj2N#fG&UrM=|kuJi&zhAoXMn8l4dFg8sewg3WI|Mqk@~*!JE4*kmHF4% zw|^3lrKH@PMe6Gj>Qrd>dVg`ZMPLp9lJ*w86U<5ncsKyyEKMG|p04?t3IS1@a{TU) zl@c@CEmmi@5Y2PxU&~H%6BNhhWXkbrtLmJ)j{k|$W73*YpG6;c%hYd_hApkxG#a!< zSF5{2Gf&A2=EfNE&B%#lNrJR@zRQ1r5F2OM{e1cObBx%iQWfjdyQ7TxyDS*u8jhFC z!4bt()Tn3Ql+`-*^NeAAw zow_Ho>(V{@a*#ckz$~h|51gI&P7yqJ?RKFkBJ8-IkH*p}k9&P%@=Gq^th7qA3`)FwEX=T`UIh(CbuboW`F z#3K5%NW7YE-9!MwqKo<4Z*Oo*uAX|!QZNhbth4Ls-r^`>K4Ivp*ni24RPZE9ecUX- zt9vow_v9HxhyHWV_>0fn$vqTR-|`-=r0@rxIa*ApBan(@2W**}2Eq>)Z zyCuJe#=WwHUeTnek+ZHlnzHI}ay~{fJc>e8&qAKE-s-UOWBJi%&`rzTqADyPl4eL_7C@>sJYjw?FX~X*ivu z_3h>MnK*Or;?=4Wx0W&^M8A_)OAK7PG3DCOKDGa?g~J;0a)j^Wk-OFEOut2-Q|kj0 zyEc;XBg0$BIpo(E^2m4Rdt2`y!DKzGsv%3Nem=_P{3se2dS;%fav1<T{RpnHx*WIqpVcmT3f-F&A>(GbmJ@o??)+ z+ay&jtngh2lSCFo?b}Gjmz)hw|MLpJ!I0_9SDAUlx@3`RZ6vo0w8;$XnfFS>9Z#M2askf z{QCp9we8u-$oXPaOAs^TU_A-Hi)W`kYn9+$O}lYnkyWhiTxpSfE`vy>{tP05rIBY(D`N{GBuW#jFu$-ALi z5=w@X&f<}05N$bOeNRG8J=U7ymi2O(tuMj>#b{7g`R z)1#Jv!4I0(m_$E5%+fr!kD65_8fl}be~aU_b_1O82d#GD6^|RXWqZ|>-mw2{sp5He zWo4*6Om@>H-j~&~S?ZMe_1b;bU_G+Oi7KAnV+!b=5jqR|)gsDQjvcfw9g(veT{7fH zj{#aF42Xzh^~|&_d3_(%Y$DG|y9IJG_KhF-HrKT}H0OQ+W1P6wVO1W$%etVLR@I}& z?=@VgVq-lt3j}hd7q+PLhsS6BpA}fDdl=6|$+Kw5-Xg7jw zrG8py-6=(K^6C<@go3jp5==Qws=7G*SkyHl&EF6A9*VF*k^3syZYunxuckgQUWb~* zaMEn=p1SaoR|kdp+-rB~a#=yR+A)K-`M>&bnJ=XzBw~3#rFF3NT@!~;s3r~wOqn=l zfRqknpJ;&m04=kr_R$pk&@#vDuR~QdyV8B&aRcW2(YL&C{eW=LPDt$bXt)Fk&3MY) zJL9Bqg$(~!kZG3^dl}ZAPd88KZwR$@rC}cQl{cw*46yqqG?mF)xnIyJ43?o3Ie2U= zr76yB?di0&j z2DC_)QCe)07e#6Z=M2Nwa z+o({>SYKD!gKzv^9bct&>bre1aHWENec_%xm_uN|CVaHI()cw#Z-r?ZKG8$nRS6WZ zP7He|FFdRT6ZJWai(J1=#k4fg4rA03=Riw{-jD2xLNP(fLq2q^1ITe#jsqYFscb$J zLskwnV+qcpX3!FqVD9sWiZ0}EY0|;qbRu7_p_ZXdkd=qDzus^8;vv!d@_Grvd)EU> zLFhacNZyTNNSS3AnA?Yj8^khuW3>MTl_*#4azd-%-HvHHfcwu|@snT?A~;a(gCjnt-3$(U~2w0YYmOxbJui@k*3 zQW&QtVzW1+eEpDxfWNN_tLhbDeA)+}TEnJP-fI@-$8z)tXvJ~_SMR!l~LakK&`TzXR^lOH z=BH{zrho*MlPQk7qO@LHb02Q`eO27hM$ji;ynLcO!;{kHXh}>>S+TFHba@HO9HQ0^ zQ43=LV|S(yWRp48K5|wGZV~*!E%#)(?+}LH^=90Vbjr{Qm3Y#dc|e90bhW2WS?A-E z>#Q}8+2 z0ERDq<)<-BTcl@tE#g-dVlefK&76njOKfr-@gd&W_9gMJp7(L%9xPh7~SR?#k>yBGpuI&w6Jwak_eSz)Gc$Nj0FNF z^PKPZHb1{nIc^pAEQ2&%^LJft(=ULkeA=YI+WBKBX^_YO#@`FC7 z;iYixP<&BWM}mMa%BLHVK+LU_bILh3p&^~79IbrC!uCnm{CNxJ$o(jU_hwb{p+LlB zd~zpUpM*~~8+4uLiJX4g5=94litAOcvbF7b8j z)uYc$+{gSyoN?b3H6!@$C7);GHk%wW|-LU#ipqR%B_VObcUE!>cMO zZMD}g54lnD@RGYwYOE0wGU>Y54wKCjoMI=}W&4)((WtAUv!#0VwlK4Kj4;6DHtYKwyHC{h0Hj;D zD~NCe{%8(t>REW8WBfetxWh2j;V3L*OA-7q@wgUplL&kE^VhK(F)n8plHktED8 zD{jB|!X-cKygM3eE8?e6Qabz9ODNK$PS&A@uaD`<`x3Ko5g4`~#m_8ij>CQ2veDEk zrF++F(sEES8SMRK!N(_H>{R&hq{riPcEpm0%{9E;`ixa5i$!f-D_YK-@5JdwvYWt{ zo_EcsDKqh#DaY7VZQN7J&MHdEx1hL}VH@8U#JD(;V?Ly?mIfV`0(|UhuY`lixS1NAZ1>a69pjD7aLU z;~Bg6O91mkVo|gfWK6Sg`n_L}612f^+vmIWvr$!$@>7XVhQ(+r2(3Z$XnbIb*>ZuX z5?zWKNGh63W0LdohF2KMNLHrvTxBG(h73mnA%_4cI2zdOlUe}$d5#a-W+#eLzzNnw z)fhnemfC!XA#Ef>Ay`AsDry0>aq53IxKS!*$1@uQnFzU;U5#hbAXQTh5PP~8^Jajf zp=o^%jgARIxO4FaEg8CxLu?80Zl7t!f#gEMa)oiER35jDC!b`qkT9r%=`(NQyW(Hw zFh0^6r6uiua@4Hh*+g95z|u5YTwP9Y)WF{A*t)aaPd>}v!pjUoR+kp3v}C=ZyxlIP zkR8Pakf=LD0V7@1Af~-9LdidRl9WaP8T35SHZ!&Az2=#PTB)! z*&?e(<%#O^tC;Nf_7kyYMNLyyp~iu7L>^&XFx8>(JUUn-y6efd9>`PkA$6_kUEG8t z8u7q={h33?CuV?HAzuv@(N`n)H23xv!`#}a<2-^WBSG#yjb3q+m1*_6hX5%|M|dfV z^dbD6D{mhZQhr5wQ_C`r?xLjlXB0EKCmI=J?Df@%TA}qA{B`-X&s>_WDl? zVm`*1Lw0tCJOPO)^@)mWRHHK5!TLX1L(eUDlYO#QE7aZ)-ZI%5Tq7)%?^f5z4!Ga` zo;X$!@vciZpS9;U#)^JzQ69yP=|s7rlJ-$~bS?`Q;3zRO^{MpTl!~(L0*}_B&)X_Q z9{Fd=+ddn;q4qaKt$CV}b2h%#s-dkD8np>)&&pLzT`;i7rnD6R%^FnHKmx2{Gf67o z^H2%!SZ>+=oBE=P=4~qS0pA=It3g)nGwr{1&M#|8z@sK~*;R_uZS5`rHzeKE_+ z#3Gf5xp%JOjv8^L2*pibIuEZx4AYd<*-UH!(c$-J%_JvR_k=jGq(-L3z#nB-*Eu%2 zN|UC!MCdpK-AaCJTyDXfB)lV=Wyj}w5G~@WB6gQfLlg7(0MQWmtuG1IW`s6oKi?|< zq=(TI8i|^2S3!4k?FTTw)cqLxx1b~F!%H8({`Zbs9N0rtuD3q-UK%yx>d58MWO!gt zQq8|?Q^`zAema{;lwd%rBKo_cm>g-mD0*AK#>m_Fk-FD|qbZ}@-RSRZL)@i~@a6=7~GR+;{xILi}$C57Sof>MJ__Cp%vTNVawkG7tOnu2FeOWMC` zldFC^PbUX_RG=460x9EaDD9T+xzh#(DDmIzMH$HiD4wYgU;F{!7UIds%}Af2er88l zu{wjgZv>wO>;BYx7`a2)`Md`Ts59o=`%YFJYb^-mc)M2ww!Mj{$)+aU(j(Oz0U#UQ zrw=_`4mev&9!L4FoL^M-f0q%)pRrPE=9e90u$FN5SiO<+wlhfeJ>ieU$ju$e~Jm6H-WlT&u8q z^AMCFRhnGPhUbTs&wQyPcZU^+AvNGCU*5V?;zY;_)be zS;QOwc&s9$5w-7=k3F1-=%-Rk7gM2tAh_rcglu2k{*h9-R{tXki0eto8*3_@%XlOa zdlu2l!!ad;`Dj_ubQbxp_&y&%H9LxwRA84;hRGf86>H?nF`PBgxX=8s(pwAOg3n^F zUJPgM-K8HBdNx$+^?D-8?2elOHh#@x-b5eo;0Ggz4Q#Xm8z`x`7xLe;Bw@VsxqI%` zJ7;4LT~35DA!A=z8{YTxr>Hv_!W?eqjBFdBD`idT7}lUY5}J zk$m1}H}})}KY)(nJH%-n=&PX=L%gc4HNIUey3;4cBzrBfhlhXGre-kx&ZsK;OS)2! zu+FW82M>E%7pg3t{Q)e0zBtbG^9vlS|I#_q)p+#Ikd%&ozBC%uR$XgEYZo#Jpo$7r zWQF$AxJWS2zK=Btol$omR^rc*2}W@S(A=dW8u@-H6*c02%_g#~>Z|fdc+vN#bC2xN z&hjKwy(^F(XCXHiz>~BNid!8+mR>ZH-9%32^ruA5lFu`)fMTaWm6H=ol~lY2094*3 z+W8AiPKz~y;EXyKt8jLX1{BFn{k*nC=o~X}e7-kUC*|6dedZ@^(o^k>_C?WO8ZSPU zhY|l4qT}z`+&+hE6xyjUXB&SDd8C}d=X9V*yNgI#Z{}hw%+G@Y_KpvGDMbE)({ChU z2T}u;p4UhJ6Op7h+CB{1!v-cJV*f6m1=sUpp3!EQbl4NZhsrT4zA^miNYhKG8~ug9X#LNwi|h;Y>E4X&l+d+{p8vvu&1pv8QxO6- z=w?9BNG^qhB3dNG+0p@gMS#4JpY~!$%&;=lW|=%MoiWEYAXEa<$QmRjKBQXPu0`2R zZqO6f^z-b!zC?WR^cU;a|KKkD@ok#$zrNYhWV4b>GXS5J49JJSGAtK*Y1%_ijY^fh110L_^ya(*qUyz_mcawld>dJ|A# z0K66Z^xZSVp&X?cbA7j%5l$Ok!Lkar z(-e$W7j4R(PVo#U&rV^$2%$OfbCu#q6d+>N6EEL+ug)Si)nN*aA*xskU z`|jTfGyf2`KY;VC_m6CtJOeJ${v~{W4QHo7gFo3kn z0s`r4b{WG}G3(-Q&O5e0?skTG1&OG)^sugeZyEBA+@-knxSpCwYT9GVl-TIPIA@Zr zq>x-~^5N%Or3h23BnS#q5efOGaV6DwJ?Fp6r2+W`7@=*e-A1m_HK-4@mua`_o$aGf?YUEk=n-AQ2T-d1B;rCPI?-y4jW0O7wErf;3 z|8*%)WF)z#Ef`t1Jv}NxW2xaYK%x6aHi&0G>>K{Sv4-__+1S?PWj3dZAI|#^*0GqE z_}J|xQ>#r4HLgw@E$%r_Lqu-OJx#-)(=xWF1yT1L$NZ~LNQO`BMA0YvUzd)S-5>hdGia&puOt0e?@u;iOrtfQOVu07V*1=`;_%` za2XAh=M#M#)miT9#uFdsr0}^^Y@JQq#4b$swy=J%@7}21h{x z&GY9YY_`?Y#9XdmJ|EdY4?Lh(ue>ntjeuVZ)N%ns#&+gN4NC}#UiwN?l@uGfpPurg zw~O|h7>&V0E*X2WV>HMLR<6;(U+#r*Fk_*U&w-8@jPj#U9ih$iap!I}{j_MeddH?( z*QgMBdZ~5Byfg2~&ry{=UNa)4W=q1mbv%6wMkWSdQopHvD3?Wfx0bkW2xc7P8 zR&MAPI$Y{I?9F_1v@P12C)z8RP5^4Q326uAGq0flleCc}bJ4WDbvQ^KP&|d&$)@JT z*56MA|2B2K6Ypv_BOehC!nF;a1m3{f?6m)Ol{Sbn@OyIS;E-hBTs$i2;MdzUwrk!` zrY(G43cOgJyS>8l`uI~Syy?zYVRnx$il0d`NqQeH^2V}u-uxgEVdFZcu6Nk*YnrDg zS|9(q7i_)CzX1FWjp5z^UN7T_w&w``MkE}l;JBZ0N%aRnbMqj!mVW1rPwRu})bTbC zFd^6G*V{y=Uss1Dgl7j=O2;5LE8Ya&`!3iZzVdpNw<<-|H53Q&>t2hhNLnIB4u z{2vidLTRJmQuXz<~`6 zyVqdsR4N}{jHKmLPT&fwInn%j0Qx<#HsAu(aL7zjR_J|BB^_N+7V#Z)5o4PSWh862 zdLK^cf$+kldwV6R0}Blz4LsT{hFK;y-W~&}Wb@=qa@?gxv2PyZY-hjyzFMHaXzAG+ zB4ad@GCFLTpd?Xuz%quDG_NbmTz>fZQ_$enD3Ll~1UFUq6 z{pirSJil01<9+yJK((CB?boKzyF^(pFro}Suk>omY;BL1VYd~kYR2d2&XnI_tEY#5 z#==y<{}Uh;M349(5yvk^+kYY7)cXy=(y7h*MjUhV7PhIP1AO)*9^5BsYY0Gptj7f<(9g));_AkLj_AIPwwW{h2vJR z-P&d^8JTiagn&doPN|;-i!1r}6$?KEDxM}*3=u4=I-QsauV>V6P^FuF3$D%Y-)}#{ z+j9!(tISnER>Nsd1QC)wF=QI|o=w`LcsR~sl@?z%B8fN_C0&2qfp8VW4SoXX%j3x#DRbKb-nO~t@1UHy3Q4fTyZ z_L>*>G?+ki5_)TVqX_^m_F7xN+8tG|h@ahBHg~y*&3s5^3M5`RWOjzRZeTP&Kx}K+ zG-L!$`8qh}wDU!feSN$8edF#p71WyNtT_YVHI+XSfepI-@ z$H})bEV;t#C6Ny~v8H_n=#soZ7tga>C>UE_!uNsqE5`a~MBoC&yI{kf1)_W~f*5YY z6k~`h)f1@OgPfD@sp1g~NypAVERd>HKd`is2wwT3xhXQk)njpg3D;OGQ0=Kv{rd;d zV}9m5VuN12eyGIt;Grz%^e|Ivr8K6lh0S!1?mTxSikRvd^(S41?<$YM`>VBukW&0X z+0SPy?fwVvo4x-RP1*)&$gHjC9u17t5`!?UC;4Wfd?*26H$+ob#BV;e(~iaPVnj4- z-+#?HW$HAtkGi;{6q&;NmC~dfKK|L>j+}%zJ_8lK0^v$!iL=a3(j>lvWcG=FoW#kC z0L|ArknHq1m|*X#3_{W^?kDbZuhWxb%M02jP_9HR98HJ5g%pOgE>Wt?XJVAZ0OW!+ z%_T*Q8n{Rc=P7d%Ix_g)RN@2UGOx}P81ZMJ1eRCf?CUHt7|q_?ldNlgRIif$0DO3+XSQ3^;R^#+ z%e`a9bcX<$sqUZmHMZN-YOhpRpE|m~5I{1zEK4iGiPW}+WT*~;W~1LxE%#18++Rwt zPMjsd)k~G$xt0!$+HL_)f6(|2^!3UdmPwyv;^G5XTlujw--+Vg^!a>bo629*4)5Dz zsd1jceNy`jXxt?Gak>_&5ZVbcl(y6&XvfKH+WkgglS5PC6O|!9aio*Ik$_K9Q}$#V zUB9f6T?WHUc4ABtCd7IoNz!lBsUpq8ZKF#bvKDvSvp%t;ctozKpW%e0vkN7Sqa_=- li3=qGlx-fBc^`0LZ`v!?Y<^&R4wh=S6W*iVefwwe{{fDj+))4k literal 0 HcmV?d00001 diff --git a/images/page3.jpg b/images/page3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f0fa09458d67e7ba8e8c24e70ad8aec72d1b466 GIT binary patch literal 206628 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
tqTjV)Pe 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 KdUeb9(SHC?Dp1M* literal 0 HcmV?d00001 diff --git a/images/page4.jpg b/images/page4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4f502ad77f54d10ea5915b26443cf7e23c95f719 GIT binary patch literal 83076 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 $rVh~$-=`x7AKxD-u!{$lgxIN z=t0=HxMk9}CqS6qoN=k$d$L-HaKqeaoj{O?nY--v)GMJqgIWf$Sfeqv+AiFz(tAQ_2}#p;A;UIu%w+xg0YXY>N+cpW zv!@~Bqyj~sDTAf5@_Sxh#)#S-N|3%t!HSrB+i&$fuZXX9qY%R3?6`1$4$@_Kw0|QI<>#&{oleeWlNTCJ>t#H8mg-t zG{J-5cm*_xP82}Pjlx@=Hs~Wt29mLrvxlXtl>VbaqH>iGx1G(@Nxa)+&Q5P~U*arH zlPj@W+P6j%*ny`t8b10?X2TKP()E|mK9ABQvgN#SCr z5&ip9sYFi= z^#^_8-sl(Zayp|OOrNA5WSJTVR+bbb8mOwrRr4`#a#?_jBvQx(k!?m94oAuh6Q2q2 zMt5FRzJ!8F-Ou4S*$#|Q$a2Hau9)Zf-&dLK%fX+BR%QkMEo{H^DYM)8{`m5+2sXE8 zYbUW7WdTYP82&+p*v2xi6wT=)?nM!jFhNGv-RHH&}90tUzchM%?-pb zs};1*uPen`DpJ~@`@F6vyXEo5zn)X=zN}^K)>$54AbD^t`eLM7ri}%%wkVOk4TC3@ zhm&#imYN%+KqKNSol70_bE(b_$Z)Nx(KN?&OA0d-02sC2MbSkybRaTFW1%0Om&qp7zp<0wkkd8+8kt8vzkcy>!S=8;K6KX%bJ`8eE zsH*I{VaY=shvQt2!9BP)?njJ}q#V1v&iP6%6<6r|sb>spo)lNaz%;}PB{Yk>IOMTMXa z&TX;_^vO_QV%mP#%S!!5zE}2o)?sgsn3t-1Ty({MJ5hls{k#06lLr#bJ9RAIc?3#n z4D0tvdX0k+s*w6H@(%@sc9q9ep-^SsKLG?R#fDhU?VlI;6lV9KO5`U1`XI_Q<#nFi zJH6n@D6E0*?=o>+XOIa-rsp3lTGhkK``{`|-lrtuJj2W^1*m%04`maHLor$0`{}IT zuag-%b0sro{Fn}stK&7f(gO{EoYLcRL^*jbR1+fc*);a}&4YmV6=QvMuljS6B!sO6 z6RL@r28Y^yHSRkOHoXy9?Q1_pwQW93*`+I4X11!`iF)?ZJ{u~Pv*)nM2#TZkW0ccZyRjT7wa z#&WUT7Kh(SBreAI_EXsGECeR>1(H0JW?ckuWV!zmDIAp5uvfTbFgt)H+NdFA5(hihTiy+2`(x5 zShkBjjJVnkG5eDhp0Mc}t~l~Mr~laBYy`atWf6(fFmL_0G5*pa0wv^F`K^<$0r;2cA|=;MVhJ&oI|X~Lg$&A#5hcQ z4zZT9asS6ngz`jPre(Rsf1BVxI|i&vv#DL$!P^2`5ZF{0Oo^5KXtg>tDa|Yf>4$c5 zJPWE2sJ?!nmiVwyaAW>WL3E=)&qni(VQAQ1>^`eBaA*zIB5}Jz0ncGtpf5;XOllYH z&X}ejGXL$+A=yMU+FPxTIu4gB19&B#qVY5xtsErJf~G>F<7kKt7GtxZX-V0rHO~EQ zY0S6BM7AJXeh{g`#t4blaKa5RTatr}JMBij{D?)MPirHXFCEf{A+3JUz$uX52P+aw zTvv$u7G-8Mtku`)oU}X>-vf4k=WjL=kCn2J^#^ATxc<8sf9;Ty7!d6Vz~M`Vt;!TY zCW)Hx|WlKvfm8^lV?d*ZBC!K{lF{A@o5dcK$jdTJ=+dejze!P7DkS z5>1dQd7{Mp<0U-j;wHx3Uilp{mdQttFaui3jn!Ps-+IQ_OQINtn6tYuI^NwcRn!-BX_(at(tI}|Dt-c3F8WTl$~*xG&G<-4+w8o_-#dPa@N?9sXSDP0 z!=0=p2i0K+j!_w69aG1E;YSSy@5Pf3{^UYlX* zr%5VF5{nf-)V=4YlBZh?KWtR3omO#*&U@MLd>=3w*YXA7V>(iF*O;Wql7qd_@Ct8C zB5S;xJsKCtr(wK4OHV$&kA`0-n~+g4JiefJaMECNkNNur){Se3dyCk!0Us_S!*URR zeIH+2OBc^eOZ$}AP0EnV0H;3o43{80VfNg;r#E|xosW>YV&^6_T>Pu0E5Cuk*nb%L zjGOiQ46q~KF9 zVpJdxf-6I*$v`E|Rwh?Jx?4i|eR=ejsg8&sm-#0Yl2qYxV;VP1QO+6UT44+ysGP7x#{^hg4?iD%Vpfo+4? zNbEu^=-Qv^&A!qpIh$bC&c(p4-7RVJeO1guH3&`)?iksyD9+pwi0w@U=`Mvo z-?~;r0f2!+K)}GjLczj9K|SBOepUgHD5%8DP-udRdUhY6(Meb$Gb>1ifbaC%$4-#n z*!!%)5HY=u%Bt+ZAY)|{R`Nc@v~`FWCl`s%So`Ic_VX`R1i+pEsuEV#IAnq`2@u8S zb+k-7MM_A6ts`0{82JE9h zpid2}PZGn!smPGUQcRf=H?i$Bc)_N>S05_SJr{5@RUJ{%(6iBd$&%wsf*oxYZC&pn zSQ7l&c(LQ7U_kX<$BT4?M#Fg%>ESbrd!?f|bKDe?;Uqz3fVy3@sHX{K19dEy1fEt4>Viw5 z6e~EnAqC}jG%XE%p_osS0mEy`K6N`{IxD#XkT@UwO~Is7iXJ?W3-j~l9%$9%C}&=v z8B&I+&?MNr%l*pJ-~5W+f>5M*wdUMU-{^v62~VOGj)qM@_=?^4;DRmDkA)hEw@yU~ zKGDOhQhwKn#Z`5NGf~CAW>>WK`V zM_5zMh^hfIzbguIqSgpyj?2Z57#JDkG*`%??O|b%!+t@6*Tlq|bxTKS9nXIhTGy3r zA>IDIB*nRRQ=LjU%AC)QqGe1$KT<*ibFC+eHBaO#aFUgNPW4U7bGL(V9&~;ZYPF(qBfJ;k8z=6(jTJF_5&VW0YM>c!Mfc`W6#AeA^@9%ccuOKUxJ?$90<0uhmWpURxop+QQySVB<&tUj(s zg-tthnpt61mB}#~Wjl)Mbze_~C6UgM@1D{HUofV$*%ldj-*NYnaG?z2HrzxSwdhe| z*l<4fjS)uUC6iJzU8<#b=V(GGoQF+#q%A4NvLw$5Q#Hg<+z#@~yeF(oJL}=%8<50o z;NyuhiBn3;ga@CxHD~YodR|3`ZH6o)uP$Cv?&Itn?Ia))OATM(6Su`EJK)E<#*7Tc zl;{L3t``o=8}ij#-r(%`M7R%LW?co_0L?BJwC+)Ghh6krR?8*Y9;IGeOLb zn&JB)X#b7b(h6Q&ADVmZCbiaTmSlBRxh(YKDqh>5S;9az*#O1Y=K2hX7rOo^{Y*3D zpx4Z?Is^)f96R#Xd`TpF-R#N)Y)*qSm5m(x2QzpL16=HGG$f{t@z_GBxK@-VsXIaI zIjgl~AlVp09S;{eUaE{tI~;~4L=-mG`}OXb=Lh^b5QWcoJtp718g3@QTYNS*)1zq)K#ojs`22vuXxl$9(T3*GWI~4oZV>5 z$C14HZX_^#Ab<)bw(?t4Jilbvrzvd#Sn4n_Lu{?oXsu05MS0N*WkeHQtIk?%3##v( zF|U@_iw+0@q~AZVgq*ig8hWYrVXVmV_-(t}qtQx9Zr;xlQFKH_Sr?TxALkoEa**wF z2@fbbyigM*n$M)f03P8tfvjS4zXP%Q5pbmqb&KY@HC0x%Z80Sa1H3d!AL|Y0YIC&} z993ROO3rvj)_d#aRYEqFHm;>yzNwne*06EaInB=jf3F@qXNJ z&6mGys2IfohI&5s(Ewvj*I!``NF}A-r}j%rffp4ljDUK#7>; zC$Yazz7SL2#LGFKD3i_=<}}YTGd-AvE{L}s;>NXN6W}-s6O-vT>~4!Wwbte+9#?Om zmK)+8ag^P3k{c@1n~$Z7GDxp0Lbsx6ZLs&E9KgWKj1j1+o3Al_v$AcDN6Ki_(5hgB zA=~!FRALlnjH-E~o(UgB9|MJq6tsj?8LwISPGX4uywZ0{iWPkVtzRbKF`oK4iaCLO z3*;Tq<5-)r(M`2PUri+W(9n4b>9FH^JD+0L7pYiZYI2Hz8L$xh&Zq`v-oST;AcY!7 zS4j*NY7iyb+s9CAk@<8LK5z5vHlg<`m6Vmi6Wv7T3dvEC-h`Yf@)M*?*xIr0^wY)3 z5{3ns)?FI?V-#5DI}S6@V2!Xb%czj-vS;T#N;N8aA``4@a@4^omBcOfgG{y~rp*`% zC{<*61h`e-SJQF5_oYV)%9sYD?w~#gQclhn3h#d*!}%n0Fz&vyeWnv{`;mv$eMhN` zvBcsD;M6r*ACp|{(z%KjwWdH8tr2v8DO{=7;J%}x2P>63=)0SD_kqtljA^kFFM1oZ zEQ8Zx9o2sEBmxokS{2(YJ`Hg`*V&VqoQ@I>Fz%oFr9Ic$KZ$`gU$!4Q#R=(ph+L+y z3lA38rrc?TH4tR@RXSd#m*qQYguib6%(jlcCA0gAxnXv9NUNrWGOd z?J&{I>b04_rtk+Nu{cq;T?Cem!8pZhSOq=G(w=yCx6DiB&O#g1BA1}fubF;vlVuT2 zMlN@|Xo$uXm@`=t2N#iMwMpXg)P`dwno|D4dQYJLL1XVW`9e*4H$$Tg^AH8p43w!YAE<4DfHy$p#2J$f`X zhsuW+QG;N%_W5PA5SrDOP}{SYOlL0V(cZJq*Nx%EOe&NyQ1Q%sZQ)^KXr^`zu`q~o zY%`ksz0>L@UmNiy&OXm&YG)lypxQX7f4=No*t#TGdpFssgpl<{8s{71QV)w`r@c9i z$_3jIIED-~N^~A>G!4zT#Udn*>W)=USC;zQW(~F}6*SrG%Exo=IP$m!m)LckS4i#d zQZj7xG1A?ROO;MbuwEma5!iT4 z9zj|xAdd3;rL#Y-@tyLYIXG51O$NqR@5e=+IkMbXt6SnBQdd3!UZya`CMXwj1MP56 zZ`MPQtmDVOm5YB9Z^cg@9?8AcVzt;ozuKK6oQnCT@(mo3LpOZYSBAeZJ~&`29_jcZ zCRU~7I2p*pK)EeKGs>Xe+MbW+&i)1NwR(nirHc0Cb%&(#XV)ZU0mE@vOkj0Vu29Co z?OZ8?e{-3ot64P-~_~^Snds zS~H#i%@pb5U~fTe5Jh8fjZN)2EM`P8<9Bj6HXLsx%>3s|Erakc$R_IcjaLD2<~3CfyU>N-dK)DubJfRU&>y zI$?Ja8Mfj8`e^vohr0)lGxC6D+;kUkIT>XJ#sw zB_^^an^{`%MUCNy_9G@~{3KCuM{)XjLWFtL=scAcrh@gfx!0PA~4L` zXb2n7=XdIq5*dhFir^*}KDwS1@xjsRpx>o9S{SLfyX_Fv=n!KbS{gAfbSKf?#-oyG zcAs!(9}LSV1YZTqjeEUpNgah#&~^Bx{7$r*Nl!(5RrY&@v<#XBA1 zS}iAXNh~3CJ6RJz(-f&ArY<~%@}y~74lO}h?6`&UY|mU-_d7MY?J;hox6H`zdZY4oc&89Qp(ENP{Qd15EAmQAl{^=_ z|CrR}gb3+?Mt-u?>CG{UO;)a{96vvDrZ~LlIbf+^bpZ}D#fo&Kv-Qxaz5}r~reZ4z z|GeDyMIQu?#v=RW4e$<&Q@6@3RPxc%ma zpfID5Zr)M@GAY|Ci(yihw%{_sp}+BaqKat|N@x*4a9u;$otVf=!aM!@lyjC4H!mX| zu^(8p(&gf*Z$=Hxpg4NoLwvLx#_b#ttWZO9Hc~3$hr4QCM+@JNv`oHD`5EOc9?o+r z)CZrk=7*`VJ_@C}fWP#h;YZGZ*rUg#ffHRoG>>APB}u2CfbWELst^r}Wkb%se|x0- z)=UX1bMn3N!I>ZXR#AF2aUERZ`*MsNrS~cl#|1N47^C_|-S2WBLvv8wSZ$iL2z>kC zyE2I>k6uWwXB^~6pTA9jx$k-cK(a-=TN5tS}xj@rHZ>#6B%yfpZOHt)dXgARn>yi`bX!eB2;RF zGRAfqmdY!UvD^z`Wnm%1ux5NQD!b}6{IaD|lR3H;sP(>osA^W*`=&f9X61YUd{Bs? z7x8BD&6R@u;H$1ETBHp{16ynBM{Js#8G=qvQz?3ytlfm(rVL3hmGPsr)h^K``}mT41i{1tS47gRb4Th0g4pR0wT7 zuO+01@=fL#V2vy)wC@uD*8kJ<2|b?`&BCZWasjopG(JV6yB!`+#nvAi`}1wL2g)%I zuR8%lVTV@l-XSCFi9OfKMdbxEK*<@o>-$`S=Hx!EmM01$i_x6YI|H42_jdvhB7;gO za)el}X7bYhyEz13iFa$bHJb_~4;%558TbRJr_fKY+mk6Q_+-)ZcFaMtvebJ{q53+e zgPD?fY{$Mb=u()}p-UG56v`WU#!2N%XlO>4<4JmZL0WeBO7aBdRsHN14LcJzT75J*xi!g=YOF{Jq!SEx-&3kITP-Jx}W980FIfd|< zKjz*C>o~_?Ew(Sas!7x<8t<`aB_Q{D4e8M7GS(QqX>xKv9oeS7$yTu;P-8oHLfOu2 zepJMjRo=nyH72sOomx;(WCf=6*X@!3e9uBZXrrP`)qd5}abo3l5B8-{;syI7Ue}H> z_+>z|dBHccN@TzQTpo6=k`sztpJ^@^6NaAE3YL&^AUkr2kQ*awIlPBmF0|1sNXNpU zg|!?E0erCiMdOO7uIYuvr3CB}9E}kJ9eiF3aW$C^o0j9EStYy_K5)J*E9he`8ER@q zKK$XrOGfrVC3;W`69?6#Rp~=Tk_5{Ik+$p!Wa z0Bt4y0^qS1yo7nZ^qch%QF82v~5=rhXYj)$TR!nV2 z0iHA869!ylQ~MuLP#lS{TBM`{%gRilr)7N!vDLS)Fp5zCXU3hz2tFDSc-I0H89WSv zfHDUjM!RF@5175BA&;6#vbhrHNtjuE0ZdWJ#a&f;NzX3nzigK&k^l?}mA|!uL=%Mc zE~dn)EOuty^A_?O6@(7hH}q!ohW8UOI6Euo&eRZUgbu3jM$Aa10E5ExN4Cy&>}aH} zwhgB77VLnfX5}x;$!&0BaH5e=EsMQa%(NS;7tWOBJh9b~RFg_V2b7%(F&y-u>F%Ph z=#mv*1d*hUjsr^)z(3a>fAWnjll5anP2)^b^W$$|u;gIa9UVQEWt^*$k=FVq_yl-$ zT_LvLNNL8DM2w_?8IkSzSxDS8^Ods!eC@)(+g+a+i2CIy_9UFnVwon(uO#PLrtW!OFTM=t0Hl$v{k~p zCXD zKID9(h_uZ*p2GGCuSiU*A!VI!N03kuJ%2h1i9aWay zHRCKZZXkSQ#F^<#G!`;dlCKbEWq)HpCE2;oCrEpG!aP|b;}8o?Z8AR3hxX&U=&TH_ z$l4f~`MH3HriIEFb@j|@2{qi}#yBTDw$5tla+q!!yK}Q3`B1Tz(bpfTeM%h6*X^l1 zojR#tDDn>**1^o3RpM2GqCFS-%5gj_y+u#yq!c=au`_ke3$ItS#6HEgsLEgaWV@OJ zp_?SDT@tXX176poO-~8>f9Bl_b=e89qA-$ruK`8`RGzm{{U6|>dyt$R`~w^LRQ zLHlxov>ZD@99FclImcGtL-Cbv9x|@x(TDd|a8(OZ#~FuY>+ri3EFXOo&^e;S*ITb4 z&w@1|%nD}Az=NjV*p$`BNy#)l0jvbt5vk=Q{b;_U=ghoC&o?sDRqjAb8Y(*V0+n(o zx6`OJiPeV6-GUu?TQ=iOCbN!7`|g@r=hyqhX%MNwIOgG{q5OPF3bJU%B63K3a~` z@p@>PZ!5igY|s^&u!3TGFc#7l%YdB}o%Ek4pGWcAupV_;M9PrMMkKHgz?qmK{K~mr z-J!fLli8&Yccoq5V;6TOpP_{*%cd0}jvx-}6dQn;+*AJ!ud~)>?`{>$*eI6@czy+Z z0;GPf)n+I<^pa_P!NZ^Uati+mkhG)M3t_$Jh=+)@bjwSNSl?Fl#PQI`Xs#kYHeFCOkyE)I7xCP-`-kL~@Hl&@Mg!paA ze7DDHI@puj;)D~leN`rsw}wlI4Z`{7UA{pHPM}`+7Hz93R%@R`7e>h{yp;v~ilLV3 z%U<@&i3Plc1$j#c8!Ns)ZQ{UDC7D2QvSG@_z)AnA@7$O5O@_sNXXN!R?7#A-JP0>| zhDPIhNca#Q2vF`#QpxaY^E(xiz!`#`D6sN9FPRwbv|`TNk(U5xUFzELJ@12?@Simq z`08t#bqDmxTwOt=qMt!71tYU6%pv%s$ZwW8>x3@`PIV%5)EZ!dcqI(H=?!*j$NW48 z6u{@~n9GKB#EsP%`{d*2gC;$UOm3n7SP6g9Y2wUHGEkzCI~o=RclqgIGTj}6oa)7> zx!5Qp>x>kZ6o#vcy$`U+lJyuvg4Alt|2}5`duGXw?^2cnU*RkVscG8j0)ac$f4t&Ri6kxdwRD>(RTTf@ zbt162DkLousG7P{Ey%MM;W@kms>UQREB3o&e+47&ox6bPBoq^m8E-jWY+7Jok70W9 z%6GS|rZ^R->i#XGbaE`Tn?1VqR-~dTp~lI3O>RMOfPh@>&^s;qOIRZSx1-oI9SgpO z)27HWuSt0-nMS}ohss>FFJ414S!%}bVvYK(Q5W$VdecX93D!9mu(}S^J89)6-`OfV zh&P!n+>AG~3t#hD*jV$1J1myw^^jO!>rW#TVqAv6%nuc;s*VPkQ?v21*>vDO09)rC zp}$q*McMhe3G$@uJL8<#c7_+4YqjH(%*wK#OVt7knmo>P$iWi36jdZNLQ8iQ%DtCr zuahKQ)m25|O6P0b*U@qd1J~??;*~Bf%+uY9dzI9g4=+-@Gle0e!?(je5t+4xZ?8Ux0 zh4P#HzP;-N=^yGj0O?Gk-2Yb}I&*!(bObpL;W#Ge7RVHBQE- z4a>7)sZCW|InsI37>6VEOO2Ric-2q)_avVKFy}i)O`m@>F_gH^S2t|vdU4;OuUDul zU6w>9q@GrLzD!>0MCKU7UT`O{MSd^`Cn%N-QM`~sofk)y^YP^@aHgSOX^7F>eji&T zH0w&>TeQ!9V=a{x!LjhxVH1mJ$HK6puqx9P-9gVS9I%P+3BXcqs8zL%-qDM=u8e{g z7HJ3OV2(u#)@Bx_*vDn6ZZ7@4kXyL;2)l5V^>#lFRBA^=X;88_VSEDpMQ!PXt%G6;1$O)KV5hsvk$8*z|Rb1(J# zLcJU@CKePbG?&nw1W4q+#OfN_`W|!f`Tk9AO`VWR7oaHOvh=)Ee<@w2?+eQre zrrR(ju}EWhF;(#+*9Hv8(V)Kc?0tPygw?b=R;5-0>O?TiijyRL zHR`VVuC6h%$Y$k`>5dxinblV6PSP>nu@hivPFOQ@XAmqwV>se2TM;Vkb{TkjH-#B9 zyx*WIrgWCHPXz|PjWy6sxfFvP^B3q+Th(Fc*i!n4*M8;_w)%Ky)0plkZQtrtCF$Sc zfAF%}MKs-9Qp0LlPfM(#%hPx9j>YlC*9}Pe$?B<#ZzSq+8s^(-9}S4>Kt5pAqP#Kd zGq4I8<^oFnLvY~3)zp#~GYcp7KD{a#-CMDaH_G_LtqD=Qk(lH$Lz!&q9LmYn&DKku zcJQ33{O(WgcT1`Vc73_ zES!3uH@&TLM_IMl*1Kxv|F)bxNqO>g;Y6eA5rRH5IH;Y}?4IOn)Cn9}O}Q>wdbV|` zuqaphkb#&sI`&n9RU_`eFg=Gb3oTF6ssjOiWt5y_XNaZ}ys`H44>nYzT0Sn!p<11V zEpx#pYs3jtVNj5Pa8}q{?TnkyBKBwY0zjn3{7XiQ3i}237oD}1UvmZ$-BgF%iNT;; zAk~%hg+P_|s^LvDzKW`;xwhwu>G+XW&HI5eO*)GCs|b~GNvJ!0CO0c+_SQ63b@TZ6 z3jT4qx(<9v0ef5I^GSx)J4G_&yZdN@-?U7C0g|LwEzdUsP%mSAXN%<^W)6nY_a!iZ z-T{Ym7K@Vw+iDeDPAP?`m=5hvfEOtosZ$lC@2+nDKjPjxD6S}I^n_r+U4sN?26qo` z12e$j9^4b$JxFjJ++lEMAZSQ%Cos4rxP{;vk}U6gQu}K6tyR5!f6c91J;&#q9XZ=*c)UN>$}A=rI;$gwOsvhBvVu^)CM4TU6L#>*-D_`fF&XKyvA5x;K9 zt4h*rS>+}Ou>2B-@3k~ys;P;%IJc3&Q5(`DtE9?q+ht|tndpGghM`j-)B!2#Pd|8I zR-B0hhZ*mVf!>1?cX~liw@R67Rat-+k@=ckbZNGO<+i>Y@+7McMN5-L6gIQ9i8!~u z^8S)m#q)wEZ=QWzwn*>t$i(FU@q7)h7b3Yqr0Ghj?mhY*^vj0ebo*+X1$*;3tID;PU%O9MN)SO zur1u;5j5Hh&s(#uJ}SrZw!xEoe-+!B08^=NNR>Kg`q}%TZzb9od;@CGnzvKa<;2r9 zdAw9U-w>4H{<@FDw0rdHe1^xSMn2@fcaFNQxPCO z&*tsM7KGp0WPd3beC7M@?~SJI<|hA^4yL~b>x(cnlQm~K_0BER%W{y(%+#t=+P*BH z9(*Q*oQel+222+!Haon!F6l*Vj3~3Q*HfFoU0$TgvhV^^IO8yv5v~Vy57(f~a89^T zd#-g^J88hLOt+hI#eXQ_O?eY=Fc^i_!F|L=_|0~u6#4M4nj?L0Dk47VKn%u-085Hb z@eiqiu0JJ2dp1(+D|7Q5vy3S9ReQ*dwMh39Q@TN3tal`%9V_~*y7lXaJO!zY?;yqk zwZWN~v2fm<6fuYQYj~@I=@2+x3qME?T7w#Bpt#8OS4(462iLYjNvAFJ9}3ZFqrg;l zb(^4ic|_hCx$o34*Mzd&eA;=W2!^v3uI;ps(wk3?A4c~-hqGj^2Nw$%e zOlQ@4SkMu64r?v}{ivzQ=fJW`Q16~;%98p)_`W<&-c%0MB1Y9>r#JRn`@z7S=2Mkd z$nveiN6+-z-|r>dEL8uz%hJ-%!cod4tH3=b<9W5wOFW-~{tR*61kLYCTa*rAzEy6t*r^@G)0;+k!q& z2zUIPKZF%1(V=|XYf4dG=Uk{Y-E#Ao_q3|SJO5kjO6X;Tali_yP@)VDR=%L}ek-a} zfzrxQncc8M8}~Qa_vG2H%r^&x#l74ux^8`v*`O^)G1T-9E#My`JhbbmecEUHDMQ$t zoo#(vEgx`xM$;{#6ypL!X4?|{oyGQewlLT$oJGg_W>)6U!*#0J3ag3=-%VQ=x{(xo zzhW45tz}hpA!D@pRwQx3Zc$)i!{)TPyFqmM9w8}_)PU8(!eC{m_KEs1HTrbJJZ*CM zbkg_~Qp952^B|pl%f=ng+HJ&*;SX#)e^6+ADU7Df;^M5VzvAI-;LyFSyXChu6>mO_ zaDAoLnPG-XHkt2GyXo?pJ}p|Nz)z{(+E^s2Vtt$^EWT@6NbgMKKV~s2Hzs>QF#H7! zskj7{t#>aOuv~cAYU(q8esJW^6pPkK;sA87Fj8n2uITnMX4edXK6qO=~YSAr)Bz<(Z)^9%+(Zb ztUG@!Jbu=W5qF|5!IJAqmb+PbW#v#CV=>nK{ofG-~5*%)rc7V=_B{ zukIk(BlHi$rRQi#tM-xFqIqgUoa7rIWRG=|j*B^y9#3lQYaLrTnNz;DaL+qX8tqtr7jzeYR$=}@+Vqf&CW3zqN`q8~54UtXmLF6#V@ ztLCYO|0r4aV0OO-PrHJ+dsC=5?0~sjPlh_(R8H{ryqrNI>^}>-A3#^n`HdO*=YlWRkpBoocF)|;!ZU?5`KM#8AjhzlD2J|Mp=%nC$|jn zO6{SaB`GPNGV4grU>SY@;U zH1+f0Ohaerr86UIOd>ya)hXH<2@txQ3vKO^?3GgZ^vc+Aj_l^c&q!eprB1JWE#=H_ z7=VYh{vxz-V`^v$P*!M%L}8d@>#JCU-*K-j!9*U@TuxzDt1-{3$P|i_M9dZ$F$I!e z@Igsd0WTIo-O79b0k4^j}(# zBQ&9K>3-?|&Y_X(BIeZOjou*R2!~z<5Sgm@1IGEFP^RyhVw=*-|NN}}tcouD zbsS%x`QA^{&b0)G@ns3=6oy{Wi?;R<_IFOo+p8Wnrq2!PIO7saVY>*;cgHMl91p zxxLc+-1F+me<%r2@mbL}IH?PP?b&lA@#7%xTH=1hRyK1DZ!(tpnkw0nj?Pa$E&t1J zgIg!$)W0IRxPnfZE~{}xNpw9x4F$u9GqR|s9N9N|s>l6kHuW5fbpf>y>IjJvbnfy- z&J_bG!Jk#9QLhr}2X=FbmwdVbX5LXO@ar~&t`9QFUEAYf9Pw)U*I()Z*Xpsz)>M6y z8{dN#@voCtpTQ?>%T6kFAC2FMW%&2^>M{2ydBv%gP(QLALqCDcM0GO~xtHGKoYF%! za!dNM6ad+jPAPI%PO8jdOyyKPt@d#BN4cbLXc4`B^c~*92uf4F_fma!*+4?bn@11h zFM%0wLArG$$R&V8DTWRs$y}E9$1IqazzQ-@l$>~9e7y+hg z{V%$5Bfu9xx#)TLcv&I28uZX(P=~nX{Pkc9^ix5myPkYa{F)r~4a4P##8IZ=Fht~C zQnODFTCw<-fhyEY9z0?{;ub_{uYs?n;oGR`v0}R@7~Pj|T6gDE$B_#j-p|%`z3YD{ z#9h4znqm18t^1zeLXR>}@i7@$m5ayxlv=r`04k)97{gz1mMdYEV7-cT9^aWn*yvz- zZGb7jv%p zI&h^hofJIDR{83a(wl1Wi`X?~*Bnee^?0|)+}+%Rds%QR$1$1dK?<1QBYMbrMHQ(h zy+E#07l*v+x+?$cjW$W9*l=O_5@S;hT-ysfZYRwNR?`h(i+V}nFFxN|_g57M^0!R~ zUUPU-JBiDIn*}N6DyU?=EA|&{j>yt%9e(47sVHsAtJU0@FLa z;rMDp0L?LAjh>Qo(sOBOs%@a1QWUr*H^haWpQyw*mg5sAzlh;cJrfDEGUJJk8`^m; zL^eB1XQ;T@&*{1B^~vrUs8*Be3~k4NAf5&eqPPL`^T#)zXe`y`-xCR}q0;8(+V?|0 z+XnoqiKJ(U=8KoTFNxADy)=l3UD!_(eQb?EGBWKrwbIQ2Up$j*%02v*bXqbM>>;Hx z<5ojOsa7KnV#i9DvpArQ)`G%{SU<5hRpP&`LM&sC#`Lqb^kGl@#b|4!o~NriI2i=! zLirRbtL-K@-51$$>|P*`N- z1zN}8RM`AfUCvx<6#knA!y5$=G>tX3kiRXCJ!dNDD=?hh?S**ed)NlK_}R{j$yT{4TLzoJ z(uj;`TU+=T_iMIp6e5>yzT8aWTa_*v#|mwC#5WhJ(CCGTFtvHkh4y^@tps#{Zbi?h z8>Q<^w9jg{UqK(DdV@Q6Hn5HLb1M%V)wGMFIM_+s&&;R!>Pb+SjJ@qq+tN@b_g*p% z55$h{^<5oD6lZ2^jndEyR~Q{ca+vw%i66}S?$N|9J1(^l!Nd+7yb>(HYpb=8-V`Xb zslc9i<2$OH-o|0>8gEZ=0p$y&pgA601G}Wp>m|7m&({uh&UQQ>5R%C0-AJ|cRm#stE)pea*A-o33YAoM16#@3>N60lzr4W5w z=I@CT8E*viUkw+CeC(X*TIKZinw5-83iPVYWNvt6OqV5ZN;c%h>`-h#P2vH8^}cNeD0uSe6ln8`yBekv~3MSx+M#v{CO+Xb=g?z z`~b=1Q`15@{t)*<_@#c4I@QF1r4m~yX>qAeIA3x)c>V`d#37`FUKLV;g1$#nS}yNJ z?k`4#!a^i`(Dws{MJwR-fLhQx9rLo-U$?>R41dig3g4TZ@RJ$j@CFxIz1U8r4DAF1_aRO6=}t^d;$T-;1hyI(KfNO7ax+=VhS%=U`K=>&p%$d8hX}NqVvIdTMEo zi>c*XC}>gGYyDC@i!lEej@STgxV9#Up9i;^aPo; zUNJ!}1wE?#2vV>K&eW^#+9%ih^))3B!&%SroN~MvnJSi)=sDs^eWrV?AQY zcA`r!x4D!VDl$x6L1v-_3 zRy;`5KaX<+%!Fw3Td%Z2u98HP4I63fm#{%vdDRn7BDHd&uneuaX6>n{V~HDi1U@-R z#2X(}?cxYoAsLK?=dWF86T-xcn{{Jrb}on8;kklFgVc8`5H^Fb@v=QbBYZEls;df&UYXMX%b|q5u8?MkwEl=0eDIGt-i!-Ty;hwTvQ+Z)V#>$5)p2EI!WRhFDEdQGL%7qUh@y}@++Q0Y{yBMatx4CJx)O*X?M z^m|$*cEb=Wh*7fklOfqO1B*tB%IrUs-RFJ3Jul3^KKWeX?j zwq`4G%$7ah>8J#Uojyta)X&l9^hMf{4#}sZyg0H&RAHcGBdO*_#WTTwx_PgY73cMQ zm6XJAk`T?w+$w=sKUpQo-16{;vIE`4^LaHIA@Ofe;7Y4j#xAYujd+a1h+mAJx>CUT zdzoKI4Ah*FWlbgA*KWmcYMxG86v96GVPI)Vm>!iZzgW=z#0ALG1;2eUa$|S2NiGYS zq?EG|8W{(GGn)Z)bRGH1r9rv0hX6b?-7?3wb4OydoGB%n1@~6o{n}exeLSg3G`TK@ zb8yi;a5UMKlXp%J(kE^$!hd$jr}}ZMoaeVoOMx>G$ZSM55CuMVn`aComSjUbr~@Rw zlvMxPT44CYq+Ji;h;qT!?kQx^!Oz|y#d$-rbQk(SNyy5rS~S7M42UN35yRch;W zpdR}r1@l$9Pmcp5nM=DCz)i~yJ$1#fL%i_)UE})(ncwuwoCZ>6RGfrb#WP-vsAva{ zqI!moN?Gw`0-&z3#vq}{HXfr%vW*cNRuCwq-jS@)NME8=@--_XqS8{kp%CAv)!jHzc?aSB*5@R8UB?qUP$2)4^Vy6GD*_%R zT$Q(u@2tHVkoY=p9F*ipc9Xtom8+yUpFdNI+SP7gS|yfl+7&LXwU)oSUq^nS`Jn77*e@IQVx z47%P38WySF>HSJhSY)63vk+LeBOH6(=l%aefOK4)SmpLJ{Mo~}lC5<6>}d^&Wn`+L z+rvm0ud0mE({%g)UiSa6b(!tYXKpjcS@Y@VDDHy;JM)}#YM!EuMxnreC{ipx9RXmV zxp^BWDGKT%oDr8yt4?FN8uai$I@Q@->Pt*vTe3BkQD^2Z#^LfrI3O%Ancr3D_3dSw zYjwBw6tkj}=7Hej8dk=;2{Ec1VdagY7~ir+F=|H$oqY4yt!m1_64Gl^+CShkMWuld z+)u(O#S!YL(~wPb5o@3M2rSWbBCz-Ck@AYs^^`pTo&qLP((wPEx>FcYNmH<+vp``b+5_%lBUq$UI{-d z%g_Eb8k$Bsz9rv~DXoaoFukGy2`PeAD@&P02hKlzgk1X>;+>X*cZBZ`E3^^gC3hL} zx~gR4hX-{b0i}-R{Cw@-YQNClp2b$9F+CDJR*|7rjY!>poA*}y8J>X%BBR;HG+F{$ zH*{FzkE%;pbRBgq*T35bViiQYZL*D%dshOY|V%Xv+ za!;rCuD$HGI*kQtKN|-ogo>y{LVZ_5m6d!l5NW#64~quI6)lj-@yE*R_ zH+5HSQvxaj`naSgQfyg-i#Ka|Zj19LCI5tI6?*iq!IgFM8dXna7VN{lX!Ci?r`Yk- z1UdetkxL4;L5SZ>j-*N%_48(EcnwQ}O}34-*Z%ZrT%|r$cZ4b~@cS-Heb2mc(emSM z88fa%n$=B`q7`ASf@ASQ2120A!t@xH$q{q6#YwK?$Oe8pP{XLjY9>_y%^;*t#s23l zH$_9^3jt<6UwJ#%PJFDqgY*OTH_i0Y5_{&q84~>Nesg}ezDTD3hhiJ}GlYNV`_IPU z@s+nSw#rJJe)&6j4g zAK2Q_^yt~OqK%x)ir1{dLZX_-WG^S?{1`+H^hZ60+H!@sYO4&or1TvBy!bnIrxI5$ zQ*5#F=I}zV@*4Ly`vyG_@i#ow=6BhUe$hAHpG97NHONPdKk2`c$&Ev0`&xo$w`(;I zDQ`9Zk40Fg-}dxZ(J&{`U07WP$8&kc&3V3?e(u~q6y_f|iRgIxB!c2{uQ5n>w|(&| z$cyy?$KbvOz>5Ve#T0KVU;HLj5GYB3pf{EOQUE>!yu^YgGR|j+tPU&x@ipuGhl0RU zMH2vuCiaL9oL#wGeH`KT`jVRHi|0Gg-d&F!n$7@JJ+cM=pjl)&WzneK730Z-dF{l! zBi@4cEJ+5Lt!YD+GuwL@Uy7piv?Qf0)ak>1F!DBi!gbW@5jLPQ>^g=D7jxFtJN`p) zh!m-$+aLDE{sBRugH=UrzbkPZ!Ud{IBD<}k{}aDT@RU(gxS}Dl;HY*?IpRQb{OWx^ zyBm9xJ}XvK`^IM4Q4Edq$=NtHXK*=LA}d@S?M}zoy3I@Il`@Mxen*T6E<}YvZ8x;A z?ItEjece4}_KnV}2`{Vb#gv?i?IdGyOeXSu9CMzI^N-frV=E=^>`Z@o$JiK^yr57Y zFJGGSFkb{g2VQljEx&OF;+Ui^yeF^pgB}>C28FD@nO_Nk<6T^$HZiU}W7(&WsKkfK z?lrJQ_1ztp7L|(bh=y4YWtikxtJ?by&AjT8JaJ|tOM>c5f~QiqG$Nl;k0n@zmplo( zTd_d#EZ#}q$_dPvl%@}ZBI-Bv7DzKar*q65OJC^2IaPJdznk7{y-Y#a?iOV3H|A~0 z>CqT2R???@kD;+z$a6qITjJLJ8emlA2{$;2*Rjq5!No%+hdui4bfilp6uKvb>d!$- zx@N5A-cO&-gk$>hnSUdmiM?yqU+-p}-rAPJgKZp==AnH(DVFUojO>hWJ1wbgdKU?$*XnxF50qYA- zDsPuRg0YPT`XR`*zg+(bq_QM>Ca2I*F|l6a;b3B6VE-483WJygla!HPX!bc7hn#j= zI=Q90XLxDz77??E>@!l8T2l6P4vT_CP(jPe16KZ;PgE!3oYl?y8L7IU6t*5fDk|A{ zrDPN%`u|JHX-t_H|4;&%nbR|3D%Q~w^Jqx7*kz-@GHDR9V$*0l8g~l;T@8wLHBg$1 z98GVyHhlLg&ek-B*4PB++?wf}p+Vc!w)Ig~9(h%sddByMou$&$< zd6Q}B_EVlkiS}1cvXMQrzrQTgK3qSQWtHfxseTEwqeWA1Fi9w*u~O%KeG{9Hq`6`*Y*AJQ)CYHSxXgv6u25axeez=h>ysV9o%^N2K;$g@|eiYj*RiiUpl zsyt-g#M5@TgdgQZZMh=8WWdKK^f{|mfrMqG0yX#b?D@%w|4>w?^_;JG{!aUh9VZg! zXXbmincpcys(5&O*6Ugi-{Y4Vh6Wy)H(6gGne3K?RgBNO)`|+!jP7yIyciyfQl}OU zjU*~r%|5@gd|!esA|h2P{xy8yIAhGDP|aW6AU7}g&1F`@m>7XO%A2rIp}meVeoGP~ z298Vp-In>Mz0WT`w&-lN{dLEiTqscYK)Tcz9b|Zk#tm|Q{rGa0KvVN`w4p6kq5r_! z$m=2b&*LuZgI~EKgnrPFUZb9&S~$lVjN_?gx_M-0(2O1eWt4i8NR`Ky)*X)h_2mh} zK~4Pm9+Bjh<-$U%$&=)m`T=^wrka3H+L0dzr1DSSxQgdyv_#GAsF){RgS9?nuYj7z zyDwJtGm7$deN@ZXvooNT_aWY%+~j0GvS(eHcd{$yYGD~Qdh561K6@szBDOY8l#5tB z9p;+z8(HqgbA7xsNzy++Zk0XEP~{SOx+dJ-l-kjV(`Sn2ujVQq6=IJ zB7LLzT%?F{g38A!?Pwq=qaYKdN4H{>Q25)|+z;NxbcQiE&}(#ucR3naS*{Eeqgbc0 zpfrr1M-tU^- z_KQqU%8;)B8UEy<#{yy4bUc8!!~jG(_l896zVhf+K>u?ukFtaFv+L=G4=tJa;!1WAvfz6w|NVAbVtv6okd$@wgq{-0|ClAX5lFXl4 z(6Z=NjM#&_Xwk*Y^tr9y!owt2(+tZ>Wt}ZJ!Gg7r@DKD1c~2j~>KrX8=$y~HZVH6; zLAM_JAptX#BJ+U(o?YNed`$MSwUZ_p$G+mt6Tn~EI5*X69Zo9Z-G~{_5zI%`FlO|L z5CIl)7di{KutfgBs80YWSSM{yhrcj35}St`i5!Hp^2k@rOLv~dZW0ymr(KVJ-K7cU zGtqBue9YuLoZEop@CvN{75;{`^3x6|z~tD53!*grglTkueyA$7k87l&oE{n(h3&=U z(82GOQl3b6f$TufjU;9D7Hk&t~W*>TH>(o0Kvm;T$8smt}|c{(h3->%qUis{Kl3 zW1A8t4(Z}2##hTYAZgpuiGvieD@yWg$8s1^@{Ofr@It`dxs|PRjfp>qcDosQAw6Ti zqGS_!yvn_pkpz5&a{M*IDipM3i@URe9QUo0;#GGea%TfS)iq+u>nOP%v_6wov){%v zkc>A*Jb5sO&B4FO6QH9LXh$Ck9N#V}1EOB$)JpN3s2$&xsB7oX#l zogC&Eqm?|B1)c_5n^ zuUok(K76ZZ6kf$#0kh(mN|71V{NTk^`C#AV^(cc|f;c`*uCrYQ`wdw+Hn# z0b8p|ZQPp~J7=^7LN&lT?50r9M};s84QhX(l=KPXw%W&bPF?@f$;qw@k?ptOxqW!m z&B7_!-BMHzJjf0&FL4=qtN_-L7kweH!*- z>@eS%`gTt8_y;iL?*2*D9P5+-Ud_#Y1dWeFBf8B%ix3!zrC!(5u=ku$T|7GbcDd(E zPc{!ic6K11v;a-;iU>uIrIeLeS$d%3K@=EbGdRa?)`2m_XAYjT{4)FdeOk8 zfko>LR8Yls4Dt1bUJ}LjCzJj2=TErt8LF_N{U}!K1z@cb zU7E~(mw3C5@0Oof+8aZ^ydAm~rO!6&vSz-RR2WW5t!LDgagx389a&7s<@R&TxC2Ud z{~ki-NI|#d*T2lI4sWTB$>4(##)%HVL|3-Edi4JkTKVyov8ynnQH-Lju8^J4~kPy~Dkk-zhaC4IeCdyOJclfed2-jpUVIz8ot} zsMErduWBy79(yft!+0hkeEdRZO3D0Zbo}cp<^t0(`frF2=?<0Xq8#2QTrgmVRQ-66 zkwbD`=4xzj35Dv4wvYaL=Frd|wj*j~SXaMT-DIVDBEg4?^y}nOF+HGnXXeBFc;ow*i_9YXRV~agN|bvm z*t5k;eG)v@b?#q5HxdpUXjm1lG?)nI2k^D$YqGl53@STzUCzr~n1E)ci>>30HJ@+B znAwO(9%g+W_>GD7B6({J^KMarqPXk+j5qK9)_xr8Tz#zm^@b430%IDZ%CX;X48hF8 zNv(YEs3xcT+^MK&o*?ll=1NVGs*LQXw;q8}F!WUB@E3~*!vO(I${l|^Y$U~wkzWthx`Jtwbs^)J-ESTjmfwf`>=CNNEYSJ5oFu!+2;*gv` zQ$v86d8E6Y(Je^8acx?9u3sBVG^^)yk8-P3d6-nwqVA~eE1k4t@cs*}S@rsSQL#C1 z20)(%^Lg4|qbWDx*CgWrY#!W3th>df@3HHpCAR5j(##*k8HLsHx!@q|A-CfgDQJtg z(R==Z8kd%qHKDL_zDg1@5;$dyCSR;ea=q=-#CwerpyG&ow4+$;iv#J9cONzWcyigj z@*n$@^A4(?NvBV-5JR5@MTr$3|78jWkk+)?nJ2(3=e;&AkXVdxk1>0(e)Eh1C5c0C z*5duT14_BGuVaAqiX1ng^4QpN9TiF#2LxVI0Sg5oD-M8?@iRxE~w0m2D@V#etnuHD&R#y&H9$e|^=?te&UZ(8Q-i zW3s92_n5nlzYfE#H`A5oR3(9^ax`)fNBbQ2;jPUS`!gEC$!nL^dGrFw1H5g~B`f>U zrL=Vh5DKbifb3Z; zn2h2tLE_BgE1#kz^O%02MTf3Ds~N)`-3bm( zSjpBa{#GAQ`-jrc5B+l+x>pK(z=t(p%vBs~!r{GeE`YiO;ZKILgSd!Y^0)$NolMih z0*)4jE~zYHmMK~kWZGQkTvCuCz}(B1K2aJNo6oA|WC?=27G91_rJJkmngThgg~i2t z9BcPala-rhB#I2&_ScHP(*5OXe^mp*B)2}&33?163GvH^>x)#dk!6h@=-oWQbD!wS zdDr$MQcfSZ>3}MRg!x0}AS0mkY}%Ju&BKj-i%Gp}SyD(nM<2DSGShQwnA1qGYY?Y0 z!r@CcYyJC}V%sFMR%ST!8n%p$p%~Xy_Z#YI?`$t2?Zg%W zPD@HE@DyGw{f{R%*8yn3d{XF_*93mT_pF z`WMwNr(s@o1}tUjNl-6y22+2s8pqo0ZrBvJe^q&4qB;oW=cMsk_e694m^F@9T&n>Z zp90rM=VzwQnBX_)jey&Z=e(#IC+~u7w8{7~)FRkOOurEF#v^CQKGjg`i6kAX#fB+y zkRfp5yneghlphoA(G-^h)KI})vt4xZQFG;YAMch4sv^H9KzXcc)#0{G`)t($OsR#mo67TnxcaFd9hgXATk{l?i?$j`P&q7|Cv>>N6F+x`k(^#O43?XxgU@5EA{|+= z1l*}uy>XPl(>>PF^VNuw2m+nQ8cA*Evy;Oj>(JxbEAtg^m_5}kAPsc_~+B|`EdwLnvmqV4> z_r{Uxdw5Z5Bh|ma=l1IYi{3H9N6MUzG#%fC3|Jif;hoMZA>NU z$ZFie)WRZX+LF#;Xx;%)gL{nGk4c+7s~kaaUcNi2ms8IP07q3ykBlqPdW+!Wgj!HR zQRX}C^Kr*1X_1WTFZ4K!jLN&t3Her#Huc*$3F92-ab3cAYpsMb=P?sCm~QT8U6gc= zhy|eCPbAO!Q_%PCmWb8eCssqn1&UbnR1$A=zNpWG1B|$+h(nXIY~rfy!qJ@eqq#X9 z4`)jHjYk!dcMY-BT2kE5`3wDybvoV{o1K-OiC@o>qAxaqzmNMPJ}&B@D*uq zPGaol_(j`|@VFW^kv0wTAc@EVAhBLE@YRFqydf?gh z=MCAiDijTnPJ^WffNZIJ$3|SgP3xn3B^D0vsxHdDSr*Y0X*RDPwcL_m zaw^g+75h}4SfQnx@k5#hlAT?vb2u&boG9sL9hbPy@aT=IxNa`!9_7y)Mx^taS@-!+ zNI=pyD;syN&kp#;VUbBM7luE)AWeP!qzrIxDD>Rw`B}*I`<0um>+T|XfrjPv zQ10)fceJZhZEJn_wn7`M4ru|)^c@EaWrlZYQUb_YB+&a;_gJO* zWGI|1$5k!bG-`rzvZlOJG*tpPxpx0(3nXvjmSuFkvK>k?>ZLPf>k$ za;@HuwQ0-vRm(Hl?ny!__+B6ixlSNp?6hPt%+C$O)$Ov#kW&DcG_WT1QXZW5Uk(C3 z@a-7Eg-mXH=qP&sW*0UzwNsqRuUvO6Q7YFOUMc=8&pYLek*G8^N!(B~ZmZ_oR~FbY zCI3>s!+p^YL2S~DK2%cws7Fswd!JNXAY0_mJQQ4c^ZWno+xfPH_L(d^=%7Y~xBf$+H&94E$~1Zy`3zyF^qf~okKUid8OJf=Pf2Xpp+c5VL; zx`pLw$dzY&_YcJ&4Xm-`c*!%b9S5!+62pz{MA_hYZTi7E+pa{~D6O)U?=dIK!>!Yv zEYE6}QUAq`THHiUtr~kr{D2U@_{h$WF0Mq009O(M@SZK8;Yd9O{vqnG8mk1vX>5*A zN>aH_ouYzT#F#gfHz@EYBLqFdA2k2Qp(xX?o>AnFh^dSyH`gbAcq!^If641aW?g2W z?#QlluE3V>y-(l6Ih7s!KQ*FR;K9jkY;G@8so8_57S+9c@Qk1aq~6RZ{?#nct**UN z=nz5%qgRtC4awp)s}$>`CZJAi>wx|?d~u%-mLQZaEy{4J9`71ba#pac^UDLYzpkxR z{`>?%IFWS5c}MQvEakeY{J3-{4W>ZLWv#)VnUG`^y0sOYvYvv^#5M4|4k~iC`5-dJm|c`w8i4UsW%;EG{=8KW`6EWCK-123pH4(mVR4 zT-ihC{71$lz=WJFMq`g$a+$3VquwX8`{o1as1^KTV!2Is!E z3vo-BuozxbO>}}t8zJL}Op^JbW~RuoKl^26 zrsOG(pj4=jOA4%XnYm-{5c!V?|K9wKkBESCEVDA3>I!(oD5-mr8`-hW9~*1??^4Wq z;pU;bR}AE`|3kr;)M2|y22oH$zhhwg=hbt54I|qyeD>X{;g9Nh(2XxpSh+g{Xie0L z1J9dSfixz1^_%=A5RgtN)nP<2bwP%A{w=>1w7XK3S#_8RQUaQem5okV&--Pq+oc~b z_EqW@{bS9pWO|}FZ+atJh|*d}u%q^H@^c>9Ct|I!F?rMKxPJS1<}AJh40gid*GHd1 z$kTop@>nuvl1pL=6Uq}7xXJWqStC%0_tLHtiY8%~EqP0%=e14IZ zZ<%<+vgwy7fd)}JOzJsBC zp1t6myU~#Q9IYyvc_JOLuw3A|x6j@_uI=q{!}cd|(npOVe0MLHs;?4nkRWgL?fI3G zd}aDIt4;+V+f*kA0D!JF@SuLWth92Co*jll1r2~wAEBb8G(aQUJ5xsL z#3e2Bi9J`c0gz5h#)+LXnZNPK?-YHkBQzGhxA}xTv5DmU;=ffs1FbOPO?H@WDnF2- zTzU!D(B4lKIAAh^9^)E>Ie$=5VzQY^^!OTOFVVF%29QuhHVOuvfSXFo{Zxb`1zDdf zYsdI*;*$32{93-I9%&&^C1N^i^8@RyO&*x6W1-j@Jv9kPBWgrlz!#TP&$bnO@srXB zFzv_NfZnU0|I{+>j{as=?%7h;L%jVN`S|BQj%K3}%~<_SFGd{<(W&o+m>Tnk@APe< z`{=PNv?tD*5YMmQXUS9V-_%vd=6Ywba*fR${$Y}DJP#1s1OlE|c7LdoTyh#arIUzf znYek#iclf0l$Apf-~XZHXIJIm6&83&O4O|)x$4+8Ayv*yc{%ch?^zeXoH(=nIPasM zb!1!gKTb+3Z?9Wysnt(2U>yeKxYww7w#3C6GYk8#Mwt3pGb;8_=eJMm&Cqh`^o)37 zVO-TeFJ0gGe zik&2}uIqm>XjGA+{qRd&pMzsd>@T+H?)BZa4l7K}_A`Fo;9t(2ueI@0WSw7?EMYo! zXp1B9Vtq}DnR*q`bLiDvZGG57;;>GWw414OXk)L}9E74~{(*w<~kL!$S431}FOxY|1WOMe?KJWSw=WbN*$XFH-0mod(=InY(|6-#9YCrA<#Td z8=VwCLA-N7A=sK)I|v66Vh1tdA881q}GL3Bx+Ye}wH(^V~`fQCsolNF1= zMVupDHw(v`W!cdBbBBj=h>=UrTvG=!wk0k2_7%7R9L;JN%rDTn>KvL12j;2^CQ4MK zc>~nsW6}Ds>vHtm^rVc`*O$NaezwLAQOcC3^%Yv(XQ#2@4kZ^K!abqsdZu=uQ|;C4 zt%AT1$M$bg>j`4>#d(KPPqUH`0dD*1%asSj`C>=?GOg_P4lZ%c<2IyKWWX`As1L&p zEK=vQRQxL5ck9?5UD?A(tb&Zq4@8Z zD2!U9C9&-VToya%9I1Hv5v;`ALZz9s0Tly&9~ZgcA7*eW7Fi5<9)(N3P9eWP=k`kT zGKGz=I?o}MUj8EYP|rxwcZ(`NG?s7tPPD0EyFu-#JESSa;^cwT+Vabk)z3duoE^`0 zkNYtO#{}>eXtg*UqE#ThL(MVR4Kj%@VPSkp$#Sp}O|28|>2cj@SEG)3m;G|b^!k`y zGeqA8pxXxYsC+$HmRx@lwwn8Hi=(}@%!ps|ULdP5c%(xFAfnOqlGJ4z8mE^G;gRqT zGXDrGv7N$Ls!pEluyG=>n;Gvc=2@ShE#%U#^4HV+wL#}C8(W>3lTG_Z&t&1rwH-%=KGr= zcix@3VVVGqj1S_cstGOT>WGFT>a-0bLf-$4y|)aH zBgwLcOU%r&&|+q^#gfI$%*& z*nFxg&%JRXGcqbO@&;bCtrQ$>L9nx`@SECjma`GzqA0!3;oYWC!^e#C2iZ)}1F zuYa>{4n9326;v**+i_QviFX+etFmt^Z)PiMw%=V{^`w_f^7aQPN46^YIKq{VtD31Z zZ<1bjI0QMCm3;ONzdt7e*9Xz%vXi`phDgR-KMS&@EuJR^glB&KDe(WD!4WCvBY1 z$@!l2#S7iosHo}>Z-$mk$c(M)(dwF3V3$`v2in-a75;xiK*gMcm7I*C5hNJGmkR22 z<$~$|H>Bdng_5wZDzRzBGfevGG0}k9Y+#@ID@OUVVP}igx*#)J}!V=i?Sj$3+|NHAh?}Sg6U!s#)z=A|I%~O1EK7 z;c#+S>rg4GzcTs?+0o@N1OnKm>R`Pm$JcDPBNoC@}iRUcw@}_|5ljdRF zf;oYeff2TZwBKu8!dssJ&TDT3!=2U5fzenL*(y)LkzrcNt)ghG{@E;Oz2&-(kH=x{ zixM(ZdfA0%#Z+gy5{k824mWrC!ZCbWSsotV)bH!LF~{3e%BZqLod+f;_Je3OuQ`jT z6zf`kn329^EuC?{8C=SH2h;tN9f^%6;Up9~tD-(kre8#*RLn%$N(esmU2&y(lRC!ShfESn3Pe) zH?1$Cbr+(MXyY}^Oj7JPamQ{`C9$5|9t=2$EXwxM>;eSaxr2H%V3SQTN8Y9ICMRz; z?a~Q18GuF;rPE@rz`&>5KGP`G5%NlOhlu}3zjx3AZQ_1cP^|)jps`R_om=ICnUkXi zE#1JVCS1Ggx$Iq}Ds_S;SJ=oBfwEyOGiqKQZ8vj}iEybD8#!85ZLgR5@LV`KtDZJh zI8hSAE-eJ0zXpXrwLn(NWCpH;pTHdj@)(~xtc6|qqN*~-p^kb)B&{cNC+zTOW&*`D zy%gEOoT5Pxkh#Un{e*L{gcW`@Bv;9y&}_nr5r%yr&{+xU)3T>@#JgGCm9U{gN-As8 zPZhbtJcW`TdIch}sk9O(KL~KkywhcyJj*?B4m931 zI_{uUYfxcqPBKC&YRok{<`!%VoTIrO%u7Vzgh$Wc{1VTYLyn`Xsg!6}M14%P5ik@Z z6zpZ?%@3OQ5dI*4@@={;19chFkPofNJ$(IzdTQ0ozYTyj6Ad^6FXV-SGc{|afjQZn zH7wc%R+m`q|6YLJ_KpRxUSoF$YE{a78JEy7CowmioTxQCO>%=D!owA~qJys~U3USS z>e!CClR#a`W+GLwDlgpV=vA9C7w^g%RDzC8u2UZ(F@vi@|EAXa8@?r!lw!S7Tw!lo zg~{Gyg#l0}H9-q-5l2AAUb&)%`re{jlxSy$^586k&VP(%%&U)}L~@35J=J98;6<`h za)75L_DnG)g`AxHp(#eVWfyFROmq?g=zQa!*NH5P&ECG#Nl}pQZg<~pqb-<#tI!X? zb2aC6=iSD<3s)JXBzXLYU?^mD2FtWi@8O{erXh7efel1?69=1>FcKb6*>=~W9j_H% z0|p^Ot7n?Txr3A(R6IZ&@1iB$P(4#IOAT8%k2zLoG?Ig%mF5 zy=-_*LTP)i)6hijXDx&2ShKnCbv(}yE;Ti+Y<4ASybQn|(YQ%;Lm{gD#t)+n5FS~P zz*>1WVul*R)^ zn4C2+p$fjFTH@Fs4|G|+m=^wO>o6Xzp!zW@q5MO>s&m!GlvMk&FiwejufTT9Kzn)& z;z-&g2H(&E{Wece4ggmDHorkHxi&MShBIs@%hI5*jOJ<0#}7eQJc%I@u{gcR8aLrI zjPMjNq@_xO%;>n%HVLPYqU8`KiVpjBiTf%7B7cY)-}ff*>$8fV^)SRtd_}*D)KQ`>GakU0Sb0KHMSdyz z*o};U#dhcJ9>^ef+|>l`I#90iShgh#y^m=bf157BHSM6U!}!+2(EjzOI_{J3_Cya! zknP0T%C$>*7Fkp(!@9QvZ?m0XwWg~sM?f`7cAlQa8hq$Tqq%|X55{|2Xn0Se>|Z!P zs;~UPc2uXmmO*ROE}AhfS9A{Awj_b~*26@0r16=HpR1XC`Iy=K69&|<1o*cm;4}b2 z5F%G}?=lGoq2Y)oQ~xG6RziR|1c~1q)*Y9~Rsvom>_|y9_s&RnxERe++5N3DBXAbw z3s}feSY{pvq(j-z{Mu?OnFi?2!HndVpFo_IDqLV>jnW_r2M z#i`Gyz58zi5Rq(whk$*e03^;jhZ2yPXw&7ytDb+eyktdY zW8H+-%&{8!5&u-wHtOyFBLXV=+>zpB6r9HEA&}!Ypeq+u|Gyy>KQ5G{aYuzsYu0|! zckLjBE>2UT+*VHv+bQ~f%x1oIP$RxdObU8Y-D5QmkB>C*%igJN+I8#4YV=@p4nzcv zc_pEx0(l?S>-K6VHIw8N=02V2q;dm&^^k+e%G|M`pFe&VYMhT7%M-1)TV(SppSgLI&GLvK}EYHSds52SI69h$;Rgi zNTRoTIkdEM{B_VNDltjC{%sA1(&WqH^)m`%tQDn%Lh>Ex-A)vRi@ta86;3qqX{n|U zd26*;uWO_u{Mr5HH_*vGax`84OXr$5uzm}ENF$5jmb=&NooqJq>7nw8KB;7o}FDGW^&X7yaAfbo4AHv%u z`GJ9lOO}m&9&$`B@qOmBUA%V#d?+7HpoGfB+Z^8F1MCYkGv~Ik3Q5Skfi*E*jC!XA zx+=qMdtRJSLp9P6cp$##49VJo)a?2fn^rT8O1=@u3W-ULV@kqG)q{vR6xtI_(BLtc zuDA-aIscsF`#UD8|Y2+?5wd55O z?FwPjry96|qHKs|a=x-B0fTjpE4DvmT0%Uaw@ zm(;iZc=Wsc zdsNdzrqW%n93weLn`jJP{JT^04_QJ{ELExK!xeJ9uU2!*J2Ik`*v{rXpW>LiGoQt| z6A&5Yk>8f0iAG9AAzvab)ux<);+O6{5NRQJSYNZ>{ZnQ756W&YC2yi^~oQ-gtpc4aPY`3r&w-w*HSJ&d*9wrJU$Tn@QwKL4{yde=}*=D(}1Cl zSjDH2X4NAdJp2icJN1yG1AJ-$`75O{q%!NDEO{pxxGbBw?KwE(>GNvR3W>3v=xfs_{oPRq!5z=P1(P+}Q@o8|mf z`DnI-^%;q8Hpq#Vow=VRiJ_UW`0A>#2R+9S1?^$?lN!&v#DLi#EdkJ=Qdsn?rQ!;) zjoAm{#?|W${I_rHgJuuovWh`d-`%nU9z=3hQgRhjCuL-6;9;1LsX93NL<`&XvuVEF z-JlIqCt-B941uUR6btX=(4+WyqU?YzV|&?xIag@ET<>Nw^OmSaJ1@n2=LN zD%aq6V7yUNitYGft4x5gD%3qR==tuHII_VtWEv9G>#NK#669FB(|I16lrOoI1($A8 z!CS`SLT$8@xciiy?WUvwT0!!)Dh(&qi1X*gdLQG+b~XIKFaZeJOk%DE<y&zypB%(tG1yiz$|MH>^;i>=w&>UH$9nK3rtv40W$z{|+Hk&# zPWV!li7geaxe~zLI9t&QEqGB5L=ftnMK+>WkzwY!)%C%_k!eZQ-c*%SH!juN>By*_ z7fEnnm|-RwH(hEOu6PtNBr7G7H=baK^}OHMmBO&r@cIsz$+A)fV~{mWvn9(QrkfnR zaEf95O3_}~p5z1EF{%#XxDlG$qorDw4&v&oSj9<7rkIr9^0ZJCSl1OW*qHL@gj zNQL|;%{R-?$yaF$nYT29MjRP;h`cn7clp<7kCu}>nY5OZ3aub};La$H_p^Tr?W6z4 z^b~h;(r;svx&rJ9hlQVTkE^BL`z=_TaF;+OZOUHt#Xg2NkQ`+_!6%&8eQLyzE2g<9 zKONrF{8++KJt}#(lf$Q2nC9Bv7j428053v;Tl+B_4)t+wAKy$j86#|1-%vtc5a0dl z=Y5&EOuq#AW?y$Th?SgVg2Dx<7+`bM@I70EoGLt$Peu@2rkx3?w2+{}KtkTYant)5 z$-JeUl;jH4IP(lg{ExEdbWpv!0H|*JP6G5A<)jHyf-cgGHx`tJ6-Z*@i8)0qJaN>C z>P}{q$||-~{(e5M_(~;P#C9n(%T$#5MAv#Xw>9(Q(s=D)$z)_dDAMV$R|(zt(lH&l zKmt`HmjYv73(T0pF0L~)*7Qf2pWg9=Q4UzKFx<)%BT6$Ag@2S>96QDyd??3$ZtfA^ z@ho1@TAh9p>T-8?ITl9o%HkhOw94p3Z&FA!$8;f``Ed6|JkeL@TB(FJS;MftMMn?h zCvrt<2VQinYd+vift}o|$196%zCHxsm78*(2*=Kl7)zL2mzes&kF`sezc9Yo?koyj$D9g~cSoVT|1~;`>@#M)4pk1D@+5KkO!f#zd8azWYKgu< zzMjriU=}U&sa6$d%K@<-m9AdBZ|*t_lhVm4S@?nw1p2IijTaMgHzG{Y#YSeVZ+99J zaoXFMcP{ojw%$iK@7_F9E^YizME{dEbtY!pK|6^gczvli5i* z4RfHic)WgXU2&3mX;hWaFgww@ZQC?1#Q-LaKg-CTeFeuf7#{4_SR$eW=N1m#q_r9% zL*AKiSvJhjrx>nk^!Yo$y&~a3D=(JSD(_B$QvZVE!2hGOZL!nhbMPCjF_5 zbI{ddra(rGt_hFkU^TuNFHAC!%PdpFk-AS3-!%&~twbaLgZ#vNT3KVCsj1c^6ld|8 zBDdh6qd%^f0+oa8Ypo=GC|yuNF6`nM<0g{0GMsHV(6}vyMqL70REG!mP2BcD_~UoL zE`5(x;0xML`XFAc2U-mplB+?!HkbwebtN%2CDUeDO-cN4_T|U3a&bp0Hz}yK6q)jf zFhXwELB#+K_ZiuE!?VUY9McqrK}E>m(^e~bZ10v31y^G!^|fm z8oaHow#Ry1E7=zu zhuwAIrKlw7zdXKLq4x@!0#4!vw z52m}c3Uh~NFJIH;br_YV8PMjFLqLwyZ&X8xQ)E&~dL${F-6-RGt%2z@lBATb@vSfT zOVc^9-Hu)FoY9xSGfTB-qN~HY*da)^xv|hwYg7(OliO_(`z2L5>xx7$)~k31U+;?LKEy_3pkoj6@DonqpbTDmwO$tvb6Da1Jlbdgok0Q($#WDHSJXHtjT3 z$rN%s6b;s9C+#*A95b97FiuSo?t)8>s$&sZ?4#FC@Fj>+F-^2ucUo85D{o|`BBz+nKl6+0=-7pIO3DN^ zp294B#hP~KV2856U0BXzYhm(ifkwPSva5-sD)mRd#mCmD4y4fPG08=MoQm3Vs;iZu zZ)`ngQw32pOZzj@9VJnrYQ0_NC8)!5k>W7C*uPk%vAm~pgSy6GS!0BVkskn{fbc$L9WRNdzzf((izeba6q$_L>gm~|Fe8gB zBRD8A4jDVFEhLMRw5csxBmerw$$)GTsw1yX^E>A&+((KWuu7&G4n$P1ym2et44CFU zTKls1)O#2e5z377aH$w9qO9VoHv^~3L@<^+E9$aBu2AnS!^Fl` zvo`01-#q&c&%ea!PC!FQ?EVC~?*dm9D&=bxdd@xLF|m-PA*1L57RR<|>#0C#cUnhN zA-JT9=^}6{J^0XZnIOv8^P)pQn5XxU{!31S1$6VpSgF>G3?MmKtC1>n#QzBCk;73u ze>#J^l;cIs7JY%Gqz|-iZqVD(h;!@bT&cOng(z-s5k=Y(c&rzaDrY(qkn1jO` z{G)?vyBrXewy}-MF2ZziAEsMbD1;*@x#)!;v0>RK#K=+2INf|pC8`(b%9kgEq}APP zPM4_;{9CrWTj|@yxB4HYPZ`4}sO>t?`;RPL1In5Y+I$;iBj86@c#*jgyQ^yW1B~ni zkM9K-&v!o>z}iW}o?d%eA^=LI2C)c`(1GCdvnC>0BhY@OvFm7v4qD3U(o7?Lx-Hyl*?TFYqQH zXNXeg+2h-A2x-4U``2KgZj(r59S5E;=0?n0-z>FAmhrD-d9{`DYUS(-z&r%Ri%*_V z#bDM1#GGbG*hcTzQH15w03LXZVA0&H8H&5MPSl!^wqgvB=Wv z_w5ol_oyP)TCelj&s-yC&sz| z7;(il)1C!b+L~pr7{3F)C=(J{bbq;I&t}*Ld9Vmm>@$m#)!!Q6AuFyEN&2?mPZI z#ca!zJ1uVv)3t@~fOpBpM8P-Z4>HeW9xswNv72`jp2n=GD#3_Elq z9?Mz*p`)NZ7zguQh5$|Th$a{(7-bi(wBBo5P|+Rgk~XxRbT<6{8f@p=vaU~~ltYzw zY{5FAC2X`{8=3C?^BL_WeSKuJn(Bu3+_xA|6#JFs!>9~Nk$fBZ z#6bJ-HoNbATZE8KUlRrH;+hdmHWZY_QCk!Gir6*5=hcjF$;crr3m@d6w8y6#@{|H_ z8j)O%-&kR-9S4EU*X}$}GRw@7u@u#qk>}dXK+qY|eganwOo$VIYpTpV68%KcVlClQ z;a+|Xnj@kGwym8EMd4e}s4sJjRv!Dgv$`_b^R^RQ3tD;C0%x4#VLeqTFv%~M{Uc(%I9F?dhpNu7rr9{~vfgtxR=7GA_a+r%P&6~vX_2fz zW9mVFjTg8Fv!*Rk^#y315+{nD)I4>rOKco2v#Wkq{@(#HYf)Q; zDBmR8r-fAPh)|Oulb!u`6=bt9fl*<`RB<>LJ$H`ob(&< zRiKA%-lZRyQ;^OM*h+QQgt~}iJt2`~CbnZBpK*3T_f)Lj0+S5M_Y~JtKP7HYy%N$+ z!51A?_7-K7P#SiGCO?Bn?i(AxL_fzaGa*^-sp72ST((@Ky|B6Y@a#ew?D}Qa`2aLVuIf_FJ@fCt^?*4i;X%UleN6ITG`$goMmm z*-51?zI?u-N3YfV{G?J)HX@})Xhs_LHY-I5i>0Bkg7XHAVa7Lk)S)j#q5dH@Svx9r z&)bOp82MJhUV-8!6IZlFSL%UfXu5=N5o6w1aWw>wE%L-e@!zU(1f* zG6fQ0Bw0jy?lm92X9h0u9iiBZIx$=tkXIkX=r-mF8&&Yn2%(!$m$jO)QhHY142>@# z`3HM1dIklGRqZ|<@5)jZXHy5SK2!y$(H$GT_dmsL>vUD-_3jdXf`l3}vy z680~GDYp^m6<;5BHJ>a5!A9v{HYd7O0&#St6J7uG=zZ=1j?WcyVq3MT#FVW6nTTJHXq*;roz!SZz}F- zZTS-F;-(&Ig?zE$2zRjyD+ zjr%)&?!-AjVsB1{|MRh)1+aBq#*(dhhOtbmtVPke@ z-4{?Ef)LKLuS`|?Iu^mQv*APt=YM6#s+AKe7os`NT7KO2UyS*nV~s;4WQ;2ns%jGcVJ_1D zG%Sz|R93zNMsjzjEX=kS{dhm-G1)S)BX7Td_5*hLuqA9bc}~i^k9_6fJEZE^yUEPH z{4cBa16jRqME`qn|HDbq^H0A6c=&=}5o&O=s3xH*tXOaO=ZCKw)U+IbzBi;rn<-gt z3rXp(L4MP#`ASA!LC(_Z$QF+?lBcv1fz-OmEo3fIT85P}m=(Q!enGPZQFgr#Yzf=U z*Gq(KV2#j~Hq(%htVmuBhj*%c`BT${6^{0tyg>Njri)L2USKJQg@$FGEE$sU^V}^9 zwu|~Il~P_&fe2G;Jbaf2dRHuc7%Ys8?wc$F((Nt=28LBW*dh^}1~Zos7c8so(spMW zr-B>in#QJT)8ll-hYc;ezOHa>@y0401mq@Hd{_UT(d}tOADF*AWHjHj4OPdOz2XxK zOtaKd397sm&w z$9x?jsP=W#na-7>Ntoc{yW8rY%DqrWn*|7IOD}ojx=we zN1;#f$9Y@4r4Pb+W!17M_PZ!f0deqKxAm8y-vRl?FFC`VqcTUS0W3%{X%%L)`k=J{ z@ENBM@tng9{d$nJqT|GJSl&ubcObFFSK3Aj5kk#s%+WJ6U-}>DKr~v^y?HJC!>UpZ;Je5hDH#j zO)xz{!6I4^@y-4-S_}~(>N?o_S4{ekZlC&5EHT>*4M#amHFZrFaupM3OO⁣60$S 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 P8t@8--%N4g+w1=U6TPXa literal 0 HcmV?d00001 diff --git a/yoRadio/audiohandlers.ino b/yoRadio/audiohandlers.ino new file mode 100644 index 0000000..e0248c2 --- /dev/null +++ b/yoRadio/audiohandlers.ino @@ -0,0 +1,39 @@ +void audio_info(const char *info) { + if(config.store.audioinfo) telnet.printf("##AUDIO.INFO#: %s\n", info); + if (strstr(info, "failed!") != NULL) { + display.title("[Request failed!]"); + player.mode = STOPPED; + player.stopInfo(); + } +} + +void audio_bitrate(const char *info) +{ + telnet.printf("%s %s\n", "##AUDIO.BITRATE#:", info); + config.station.bitrate = atoi(info) / 1000; + netserver.requestOnChange(BITRATE, 0); +} + +void audio_showstation(const char *info) { + if (strlen(info) > 0) { + display.title(info); + if (player.requesToStart) { + telnet.info(); + player.requesToStart = false; + } else { + telnet.printf("##CLI.ICY0#: %s\n", info); + } + } +} + +void audio_showstreamtitle(const char *info) { + if (strlen(info) > 0) { + display.title(info); + if (player.requesToStart) { + telnet.info(); + player.requesToStart = false; + } else { + telnet.printf("##CLI.META#: %s\n", info); + } + } +} diff --git a/yoRadio/config.cpp b/yoRadio/config.cpp new file mode 100644 index 0000000..a51a465 --- /dev/null +++ b/yoRadio/config.cpp @@ -0,0 +1,328 @@ +#include "config.h" +#include +#include + +Config config; + +void Config::init() { + eepromRead(EEPROM_START, store); + if (store.config_set != 4256) setDefaults(); + //if (!SPIFFS.begin(false, "/spiffs", 30)) { + if (!SPIFFS.begin(false)) { + return; + } + ssidsCount = 0; + initPlaylist(); + if (store.lastStation == 0 && store.countStation > 0) { + store.lastStation = 1; + save(); + } + loadStation(store.lastStation); +} + +template int Config::eepromWrite(int ee, const T& value) { + const byte* p = (const byte*)(const void*)&value; + int i; + EEPROM.begin(EEPROM_SIZE); + for (i = 0; i < sizeof(value); i++) + EEPROM.write(ee++, *p++); + EEPROM.commit(); + delay(20); + EEPROM.end(); + return i; +} + +template int Config::eepromRead(int ee, T& value) { + byte* p = (byte*)(void*)&value; + int i; + EEPROM.begin(EEPROM_SIZE); + for (i = 0; i < sizeof(value); i++) + *p++ = EEPROM.read(ee++); + EEPROM.end(); + return i; +} + +void Config::setDefaults() { + store.config_set = 4256; + store.volume = 12; + store.balance = 0; + store.trebble = 0; + store.middle = 0; + store.bass = 0; + store.lastStation = 0; + store.countStation = 0; + store.lastSSID = 0; + store.audioinfo = false; + store.smartstart = 2; +} + +void Config::save() { + eepromWrite(EEPROM_START, store); +} + +byte Config::setVolume(byte val, bool dosave) { + store.volume = val; + if (dosave) save(); + return store.volume; +} + +void Config::setTone(int8_t bass, int8_t middle, int8_t trebble) { + store.bass = bass; + store.middle = middle; + store.trebble = trebble; + save(); +} + +void Config::setSmartStart(byte ss) { + if (store.smartstart < 2) { + store.smartstart = ss; + save(); + } +} + +void Config::setBalance(int8_t balance) { + store.balance = balance; + save(); +} + +byte Config::setLastStation(byte val) { + store.lastStation = val; + save(); + return store.lastStation; +} + +byte Config::setCountStation(byte val) { + store.countStation = val; + save(); + return store.countStation; +} + +byte Config::setLastSSID(byte val) { + store.lastSSID = val; + save(); + return store.lastSSID; +} + +void Config::indexPlaylist() { + File playlist = SPIFFS.open(PLAYLIST_PATH, "r"); + if (!playlist) { + return; + } + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + File index = SPIFFS.open(INDEX_PATH, "w"); + while (playlist.available()) { + uint32_t pos = playlist.position(); + if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + index.write((byte *) &pos, 4); + } + } + index.close(); + playlist.close(); +} + +void Config::initPlaylist() { + store.countStation = 0; + if (!SPIFFS.exists(INDEX_PATH)) indexPlaylist(); + + if (SPIFFS.exists(INDEX_PATH)) { + File index = SPIFFS.open(INDEX_PATH, "r"); + store.countStation = index.size() / 4; + index.close(); + save(); + } +} + +void Config::loadStation(uint16_t ls) { + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + if (store.countStation == 0) { + memset(station.url, 0, BUFLEN); + memset(station.name, 0, BUFLEN); + strncpy(station.name, "ёRadio", BUFLEN); + station.ovol = 0; + return; + } + if (ls > store.countStation) { + ls = 1; + } + File playlist = SPIFFS.open(PLAYLIST_PATH, "r"); + + File index = SPIFFS.open(INDEX_PATH, "r"); + index.seek((ls - 1) * 4, SeekSet); + uint32_t pos; + + index.readBytes((char *) &pos, 4); + + index.close(); + playlist.seek(pos, SeekSet); + if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + memset(station.url, 0, BUFLEN); + memset(station.name, 0, BUFLEN); + strncpy(station.name, sName, BUFLEN); + strncpy(station.url, sUrl, BUFLEN); + station.ovol = sOvol; + setLastStation(ls); + } + playlist.close(); +} + +void Config::fillPlMenu(char plmenu[][40], int from, byte count) { + int ls = from; + byte c = 0; + bool finded = false; + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + if (store.countStation == 0) { + return; + } + File playlist = SPIFFS.open(PLAYLIST_PATH, "r"); + File index = SPIFFS.open(INDEX_PATH, "r"); + while (true) { + if (ls < 1) { + ls++; + c++; + continue; + } + if (!finded) { + index.seek((ls - 1) * 4, SeekSet); + uint32_t pos; + index.readBytes((char *) &pos, 4); + finded = true; + index.close(); + playlist.seek(pos, SeekSet); + } + while (playlist.available()) { + if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + strlcpy(plmenu[c], sName, 39); + c++; + } + if (c >= count) break; + } + break; + } + playlist.close(); +} + +bool Config::parseCSV(const char* line, char* name, char* url, int &ovol) { + char *tmpe; + const char* cursor = line; + char buf[4]; + tmpe=strstr(cursor, "\t"); + if(tmpe==NULL) return false; + strlcpy(name, cursor, tmpe-cursor+1); + if(strlen(name)==0) return false; + cursor=tmpe+1; + tmpe=strstr(cursor, "\t"); + if(tmpe==NULL) return false; + strlcpy(url, cursor, tmpe-cursor+1); + if(strlen(url)==0) return false; + cursor=tmpe+1; + if (strlen(cursor) == 0) return false; + strlcpy(buf, cursor, 3); + ovol = atoi(buf); + return true; +} + +bool Config::parseJSON(const char* line, char* name, char* url, int &ovol) { + char* tmps, *tmpe; + const char* cursor = line; + char port[8], host[254], file[254]; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(name, tmps+3, tmpe-tmps-3+1); + if(strlen(name)==0) return false; + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(host, tmps+3, tmpe-tmps-3+1); + if(strlen(host)==0) return false; + if(strstr(host,"http://")==NULL && strstr(host,"https://")==NULL) { + sprintf(file, "http://%s", host); + strlcpy(host, file, strlen(file)+1); + } + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(file, tmps+3, tmpe-tmps-3+1); + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\",\""); + if(tmpe==NULL) return false; + strlcpy(port, tmps+3, tmpe-tmps-3+1); + int p = atoi(port); + if(p>0){ + sprintf(url, "%s:%d%s", host, p, file); + }else{ + sprintf(url, "%s%s", host, file); + } + cursor=tmpe+3; + tmps=strstr(cursor, "\":\""); + if(tmps==NULL) return false; + tmpe=strstr(tmps, "\"}"); + if(tmpe==NULL) return false; + strlcpy(port, tmps+3, tmpe-tmps-3+1); + ovol = atoi(port); + return true; +} + +bool Config::parseWsCommand(const char* line, char* cmd, char* val, byte cSize) { + char *tmpe; + tmpe=strstr(line, "="); + if(tmpe==NULL) return false; + memset(cmd, 0, cSize); + strlcpy(cmd, line, tmpe-line+1); + if (strlen(tmpe+1) == 0) return false; + memset(val, 0, cSize); + strlcpy(val, tmpe+1, tmpe+1-line+1); + return true; +} + +bool Config::parseSsid(const char* line, char* ssid, char* pass) { + char *tmpe; + tmpe=strstr(line, "\t"); + if(tmpe==NULL) return false; + uint16_t pos= tmpe-line; + if (pos > 19 || strlen(line) > 61) return false; + memset(ssid, 0, 20); + strlcpy(ssid, line, pos + 1); + memset(pass, 0, 40); + strlcpy(pass, line + pos + 1, strlen(line) - pos); + return true; +} + +bool Config::saveWifi(const char* post) { + File file = SPIFFS.open(SSIDS_PATH, "w"); + if (!file) { + return false; + } else { + file.print(post); + file.close(); + ESP.restart(); + } +} + +bool Config::initNetwork() { + File file = SPIFFS.open(SSIDS_PATH, "r"); + if (!file || file.isDirectory()) { + return false; + } + char ssidval[20], passval[40]; + byte c = 0; + while (file.available()) { + if (parseSsid(file.readStringUntil('\n').c_str(), ssidval, passval)) { + strlcpy(ssids[c].ssid, ssidval, 20); + strlcpy(ssids[c].password, passval, 40); + ssidsCount++; + c++; + } + } + file.close(); +} diff --git a/yoRadio/config.h b/yoRadio/config.h new file mode 100644 index 0000000..0dd5331 --- /dev/null +++ b/yoRadio/config.h @@ -0,0 +1,78 @@ +#ifndef config_h +#define config_h +#include "Arduino.h" + +#define EEPROM_SIZE 1024 +#define EEPROM_START 0 +#define BUFLEN 140 +#define PLAYLIST_PATH "/data/playlist.csv" +#define SSIDS_PATH "/data/wifi.csv" +#define TMP_PATH "/data/tmpfile.txt" +#define INDEX_PATH "/data/index.dat" + +struct config_t +{ + unsigned int config_set; //must be 4256 + byte volume; + int8_t balance; + int8_t trebble; + int8_t middle; + int8_t bass; + uint16_t lastStation; + uint16_t countStation; + byte lastSSID; + bool audioinfo; + byte smartstart; +}; + +struct station_t +{ + char name[BUFLEN]; + char url[BUFLEN]; + char title[BUFLEN]; + uint16_t bitrate; + int ovol; +}; + +struct neworkItem +{ + char ssid[20]; + char password[40]; +}; + +class Config { + public: + config_t store; + station_t station; + neworkItem ssids[5]; + byte ssidsCount; + public: + Config() {}; + void save(); + void init(); + byte setVolume(byte val, bool dosave); + void setTone(int8_t bass, int8_t middle, int8_t trebble); + void setBalance(int8_t balance); + byte setLastStation(byte val); + byte setCountStation(byte val); + byte setLastSSID(byte val); + bool parseCSV(const char* line, char* name, char* url, int &ovol); + bool parseJSON(const char* line, char* name, char* url, int &ovol); + bool parseWsCommand(const char* line, char* cmd, char* val, byte cSize); + bool parseSsid(const char* line, char* ssid, char* pass); + void loadStation(uint16_t station); + bool initNetwork(); + bool saveWifi(const char* post); + void setSmartStart(byte ss); + void initPlaylist(); + void indexPlaylist(); + void fillPlMenu(char plmenu[][40], int from, byte count); + private: + template int eepromWrite(int ee, const T& value); + template int eepromRead(int ee, T& value); + void setDefaults(); +}; + +extern Config config; + +#endif diff --git a/yoRadio/controls.cpp b/yoRadio/controls.cpp new file mode 100644 index 0000000..3c8b73a --- /dev/null +++ b/yoRadio/controls.cpp @@ -0,0 +1,101 @@ +#include "controls.h" +#include "options.h" +#include "config.h" +#include "player.h" +#include "display.h" + +#include +#include "OneButton.h" + +long encOldPosition = 0; + +ESP32Encoder encoder; +OneButton encbutton(ENC_BTNB, true); + +OneButton btnleft(BTN_LEFT, true); +OneButton btncenter(BTN_CENTER, true); +OneButton btnright(BTN_RIGHT, true); + +void initControls() { + ESP32Encoder::useInternalWeakPullResistors = UP; + encoder.attachHalfQuad(ENC_BTNL, ENC_BTNR); + encbutton.attachClick(onEncClick); + encbutton.attachDoubleClick(onEncDoubleClick); + encbutton.attachLongPressStart(onEncLPStart); + + btnleft.attachClick(onLeftClick); + btnleft.attachDoubleClick(onLeftDoubleClick); + + btncenter.attachClick(onEncClick); + btncenter.attachDoubleClick(onEncDoubleClick); + btncenter.attachLongPressStart(onEncLPStart); + + btnright.attachClick(onRightClick); + btnright.attachDoubleClick(onRightDoubleClick); +} + +void loopControls() { + encbutton.tick(); + encoderLoop(); + yield(); +} + +void encoderLoop() { + long encNewPosition = encoder.getCount() / 2; + if (encNewPosition != 0 && encNewPosition != encOldPosition) { + encOldPosition = encNewPosition; + encoder.setCount(0); + controlsEvent(encNewPosition > 0); + } +} + +void onEncClick() { + if (display.mode == PLAYER) { + player.toggle(); + } + if (display.mode == STATIONS) { + display.swichMode(PLAYER); + player.play(display.currentPlItem); + } +} + +void onEncDoubleClick() { + display.swichMode(display.mode == PLAYER ? STATIONS : PLAYER); +} + +void onEncLPStart() { + display.swichMode(display.mode == PLAYER ? STATIONS : PLAYER); +} + +void controlsEvent(bool toRight) { + if (display.mode != STATIONS) { + display.swichMode(VOL); + player.stepVol(toRight); + } + if (display.mode == STATIONS) { + int p = toRight ? display.currentPlItem + 1 : display.currentPlItem - 1; + if (p < 1) p = 1; + if (p > config.store.countStation) p = config.store.countStation; + display.currentPlItem = p; + display.clear(); + display.drawPlaylist(); + } +} + +void onLeftClick() { + controlsEvent(false); +} + +void onLeftDoubleClick() { + display.swichMode(PLAYER); + player.prev(); +} + +void onRightClick() { + controlsEvent(true); +} + +void onRightDoubleClick() { + display.swichMode(PLAYER); + player.next(); +} diff --git a/yoRadio/controls.h b/yoRadio/controls.h new file mode 100644 index 0000000..ca3bb29 --- /dev/null +++ b/yoRadio/controls.h @@ -0,0 +1,18 @@ +#ifndef controls_h +#define controls_h + +void initControls(); +void loopControls(); +void onEncClick(); +void onEncDoubleClick(); +void onEncLPStart(); +void encoderLoop(); + +void controlsEvent(bool toRight); + +void onLeftClick(); +void onLeftDoubleClick(); +void onRightClick(); +void onRightDoubleClick(); + +#endif diff --git a/yoRadio/data/data/wifi.csv b/yoRadio/data/data/wifi.csv new file mode 100644 index 0000000..e69de29 diff --git a/yoRadio/data/www/dragpl.js b/yoRadio/data/www/dragpl.js new file mode 100644 index 0000000..90c4b69 --- /dev/null +++ b/yoRadio/data/www/dragpl.js @@ -0,0 +1,40 @@ +let dragged; +let id; +let index; +let indexDrop; +let list; + +document.addEventListener("dragstart", ({target}) => { + dragged = target.parentNode; + id = target.parentNode.id; + list = target.parentNode.parentNode.children; + for(let i = 0; i < list.length; i += 1) { + if(list[i] === dragged){ + index = i; + } + } +}); + +document.addEventListener("dragover", (event) => { + event.preventDefault(); +}); + +document.addEventListener("drop", ({target}) => { + if(target.parentNode.className == "pleitem" && target.parentNode.id !== id) { + dragged.remove( dragged ); + for(let i = 0; i < list.length; i += 1) { + if(list[i] === target.parentNode){ + indexDrop = i; + } + } + if(index > indexDrop) { + target.parentNode.before( dragged ); + } else { + target.parentNode.after( dragged ); + } + let items=document.getElementById('pleditorcontent').getElementsByTagName('li'); + for (let i = 0; i <= items.length-1; i++) { + items[i].getElementsByTagName('span')[0].innerText=("00"+(i+1)).slice(-3); + } + } +}); diff --git a/yoRadio/data/www/elogo.png b/yoRadio/data/www/elogo.png new file mode 100644 index 0000000000000000000000000000000000000000..bdc932f17f3989f2964180adaf2fd49a8670b721 GIT binary patch literal 3691 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 JPDHLkV1jh$>sbH* literal 0 HcmV?d00001 diff --git a/yoRadio/data/www/elogo100.png b/yoRadio/data/www/elogo100.png new file mode 100644 index 0000000000000000000000000000000000000000..8e5cdc570b474a30bbe637fe36b9901760f8678e GIT binary patch literal 17854 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 literal 0 HcmV?d00001 diff --git a/yoRadio/data/www/index.html b/yoRadio/data/www/index.html new file mode 100644 index 0000000..f5740f0 --- /dev/null +++ b/yoRadio/data/www/index.html @@ -0,0 +1,130 @@ + + + + + + + + + + ёRadio + + + +
+ + + + diff --git a/yoRadio/data/www/script.js b/yoRadio/data/www/script.js new file mode 100644 index 0000000..9d91902 --- /dev/null +++ b/yoRadio/data/www/script.js @@ -0,0 +1,347 @@ +var gateway = `ws://${window.location.hostname}/ws`; +var websocket; +var currentItem = 0; + +window.addEventListener('load', onLoad); +function initWebSocket() { + console.log('Trying to open a WebSocket connection...'); + websocket = new WebSocket(gateway); + websocket.onopen = onOpen; + websocket.onclose = onClose; + websocket.onmessage = onMessage; +} +function onOpen(event) { + console.log('Connection opened'); +} +function onClose(event) { + console.log('Connection closed'); + document.getElementById('playbutton').setAttribute("class", "stopped"); + setTimeout(initWebSocket, 2000); +} +function cleanString(input) { + var output = ""; + for (var i=0; i= 160 && input.charCodeAt(i) <= 255) { + output += input.charAt(i); + } + } + return output; +} +function onMessage(event) { + var data = JSON.parse(event.data); + if(data.nameset) document.getElementById('nameset').innerHTML = data.nameset; + if(data.meta) document.getElementById('meta').innerHTML = cleanString(data.meta); + if(data.vol) { + setVolRangeValue(document.getElementById('volrange'),data.vol); + } + if(data.current) setCurrentItem(data.current); + if(data.file) generatePlaylist(data.file); + if(data.bitrate) document.getElementById('bitinfo').innerText = 'bitrate: '+data.bitrate+'kBits'; + if(data.rssi) document.getElementById('rsiinfo').innerText = 'rssi: '+data.rssi+'dBm'; + if(data.mode) { + document.getElementById('playbutton').setAttribute("class",data.mode); + } + if(data.bass) { + setVolRangeValue(document.getElementById('eqbass'),data.bass); + setVolRangeValue(document.getElementById('eqmiddle'),data.middle); + setVolRangeValue(document.getElementById('eqtreble'),data.trebble); + } + if(data.balance) { + setVolRangeValue(document.getElementById('eqbal'),data.balance); + } +} +function scrollToCurrent(){ + var pl = document.getElementById('playlist'); + var lis = pl.getElementsByTagName('li'); + var plh = pl.offsetHeight; + var plt = pl.offsetTop; + var topPos = 0; + var lih = 0; + for (var i = 0; i <= lis.length - 1; i++) { + if(i+1==currentItem) { + topPos = lis[i].offsetTop; + lih = lis[i].offsetHeight; + } + } + pl.scrollTo({ + top: topPos-plt-plh/2+lih/2, + left: 0, + behavior: 'smooth' + }); +} +function setCurrentItem(item){ + currentItem=item; + var pl = document.getElementById('playlist'); + var lis = pl.getElementsByTagName('li'); + for (var i = 0; i <= lis.length - 1; i++) { + lis[i].removeAttribute('class'); + if(i+1==currentItem) { + lis[i].setAttribute("class","active"); + + } + } + scrollToCurrent(); +} +function generatePlaylist(lines){ + var ul = document.getElementById('playlist'); + ul.innerHTML=""; + lines.forEach((line, index) => { + li = document.createElement('li'); + li.setAttribute('onclick','playStation(this);'); + li.setAttribute('attr-id', index+1); + li.setAttribute('attr-name', line.name); + li.setAttribute('attr-url', line.url); + li.setAttribute('attr-ovol', line.ovol); + if(index+1==currentItem){ + li.setAttribute("class","active"); + } + var span = document.createElement('span'); + span.innerHTML = index+1; + li.appendChild(document.createTextNode(line.name)); + li.appendChild(span); + ul.appendChild(li); + initPLEditor(); + }); + scrollToCurrent(); +} +function initPLEditor(){ + ple= document.getElementById('pleditorcontent'); + ple.innerHTML=""; + pllines = document.getElementById('playlist').getElementsByTagName('li'); + for (let i = 0; i <= pllines.length - 1; i++) { + plitem = document.createElement('li'); + plitem.setAttribute('class', 'pleitem'); + plitem.setAttribute('id', 'plitem'+i); + let pName = pllines[i].getAttribute('attr-name'); + let pUrl = pllines[i].getAttribute('attr-url'); + let pOvol = pllines[i].getAttribute('attr-ovol'); + plitem.innerHTML = ''+("00"+(i+1)).slice(-3)+'\ + \ + \ + \ + '; + ple.appendChild(plitem); + } +} +function playStation(el){ + let lis = document.getElementById('playlist').getElementsByTagName('li'); + + for (let i = 0; i <= lis.length - 1; i++) { + lis[i].removeAttribute('class'); + } + el.setAttribute("class","active"); + id=el.getAttribute('attr-id'); + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("playstation="+id); +} +function setVolRangeValue(el, val=null){ + let slave = el.getAttribute('data-slaveid'); + if(val){ + el.value = val; + document.getElementById(slave).innerText=val; + } + document.getElementById(slave).innerText=el.value; + var value = (el.value-el.min)/(el.max-el.min)*100; + el.style.background = 'linear-gradient(to right, #bfa73e 0%, #bfa73e ' + value + '%, #272727 ' + value + '%, #272727 100%)'; +} + +function onRangeVolChange(value) { + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("vol=" + value+"&"); +} +function onRangeEqChange(el){ + let trebble = document.getElementById('eqtreble').value; + let middle = document.getElementById('eqmiddle').value; + let bass = document.getElementById('eqbass').value; + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("trebble=" + trebble + "&middle=" + middle + "&bass=" + bass + "&"); +} +function onRangeBalChange(el){ + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("ballance=" + el.value+"&"); +} +function showSettings(){ + document.getElementById('pleditorwrap').hidden=true; + document.getElementById('settings').hidden=false; +} +function showEditor(){ + document.getElementById('settings').hidden=true; + initPLEditor(); + document.getElementById('pleditorwrap').hidden=false; +} +function doCancel() { + document.getElementById('settings').hidden=true; + document.getElementById('pleditorwrap').hidden=true; +} +function doExport() { + window.open("/data/playlist.csv"); +} +function doUpload(finput) { + var formData = new FormData(); + formData.append("plfile", finput.files[0]); + var xhr = new XMLHttpRequest(); + xhr.open("POST","/upload",true); + xhr.send(formData); +} +function doAdd(){ + let ple=document.getElementById('pleditorcontent'); + let plitem = document.createElement('li'); + let cnt=ple.getElementsByTagName('li'); + plitem.setAttribute('class', 'pleitem'); + plitem.setAttribute('id', 'plitem'+(cnt.length)); + plitem.innerHTML = ''+("00"+(cnt.length+1)).slice(-3)+'\ + \ + \ + \ + '; + ple.appendChild(plitem); + ple.scrollTo({ + top: ple.scrollHeight, + left: 0, + behavior: 'smooth' + }); +} +function doRemove(){ + let items=document.getElementById('pleditorcontent').getElementsByTagName('li'); + let pass=[]; + for (let i = 0; i <= items.length - 1; i++) { + if(items[i].getElementsByTagName('span')[1].getElementsByTagName('input')[0].checked) { + pass.push(items[i]); + } + } + if(pass.length==0) { + alert('Choose something first'); + return; + } + for (var i = 0; i < pass.length; i++) + { + pass[i].remove(); + } + items=document.getElementById('pleditorcontent').getElementsByTagName('li'); + for (let i = 0; i <= items.length-1; i++) { + items[i].getElementsByTagName('span')[0].innerText=("00"+(i+1)).slice(-3); + } +} +function showEqualizer(isshowing){ + document.getElementById('equalizerbg').classList.toggle('hidden'); +} +function submitWiFi(){ + var items=document.getElementById("credentialwrap").getElementsByTagName("li"); + var output=""; + for (var i = 0; i <= items.length - 1; i++) { + inputs=items[i].getElementsByTagName("input"); + if(inputs[0].value == "") continue; + output+=inputs[0].value+"\t"+inputs[1].value+"\n"; + } + if(output!=""){ // Well, let's say, quack. + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("wifisettings="+output); + document.getElementById("settings").innerHTML="

Settings saved. Rebooting...

"; + setTimeout(function(){ window.location.reload(); }, 10000); + } +} +function submitPlaylist(){ + var items=document.getElementById("pleditorcontent").getElementsByTagName("li"); + var output=""; + for (var i = 0; i <= items.length - 1; i++) { + inputs=items[i].getElementsByTagName("input"); + if(inputs[1].value == "" || inputs[2].value == "") continue; + let ovol = inputs[3].value; + if(ovol < -30) ovol = -30; + if(ovol > 30) ovol = 30; + output+=inputs[1].value+"\t"+inputs[2].value+"\t"+inputs[3].value+"\n"; + } + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send("playlist="+output.slice(0, -1)); + document.getElementById('pleditorwrap').hidden=true; +} +function initSliers(){ + var sliders = document.getElementsByClassName('slider'); + for (var i = 0; i <= sliders.length - 1; i++) { + sliders[i].oninput = function() { + setVolRangeValue(this); + }; + setVolRangeValue(sliders[i], 0); + } + return; + var volslider = document.getElementById("volrange"); + var eqvolslider = document.getElementById("eqvol"); + var balslider = document.getElementById("eqbal"); + volslider.oninput = function() { + setVolRangeValue(this); + }; + eqvolslider.oninput = function() { + setVolRangeValue(this); + }; + balslider.oninput = function() { + setVolRangeValue(this); + }; + setVolRangeValue(volslider, 0); + setVolRangeValue(eqvolslider, 0); + setVolRangeValue(balslider, 0); +} +function onLoad(event) { + initWebSocket(); + initButton(); + initSliers(); + document.getElementById("volrange").addEventListener("wheel", function(e){ + if (e.deltaY < 0){ + this.valueAsNumber += 1; + }else{ + this.value -= 1; + } + websocket.send('volume='+this.value); + e.preventDefault(); + e.stopPropagation(); + }, {passive: false}); +} +function initButton() { + document.getElementById('playbutton').addEventListener('click', playbutton); + document.getElementById('prevbutton').addEventListener('click', prevbutton); + document.getElementById('nextbutton').addEventListener('click', nextbutton); + document.getElementById('volmbutton').addEventListener('click', volmbutton); + document.getElementById('volpbutton').addEventListener('click', volpbutton); +} + +function playercommand(cmd){ + xhr = new XMLHttpRequest(); + xhr.open("POST","/",true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.send(cmd+"=1"); +} +function playbutton(){ + var btn=document.getElementById('playbutton'); + if(btn.getAttribute("class")=="stopped") { + btn.setAttribute("class", "playing"); + playercommand("start"); + return; + } + if(btn.getAttribute("class")=="playing") { + btn.setAttribute("class", "stopped"); + playercommand("stop"); + } +} +function prevbutton(){ + playercommand("prev"); +} +function nextbutton(){ + playercommand("next"); +} +function volmbutton(){ + playercommand("volm"); +} +function volpbutton(){ + playercommand("volp"); +} diff --git a/yoRadio/data/www/style.css b/yoRadio/data/www/style.css new file mode 100644 index 0000000..07e40b0 --- /dev/null +++ b/yoRadio/data/www/style.css @@ -0,0 +1,505 @@ +html, body {margin: 0; padding: 0; height: 100%; } +body { + background-color: #000; + color: #fff; + font-family: Times, "Times New Roman", serif; +} +a { color: #e3d25f; text-decoration: none; font-weight: bold } +a:hover { text-decoration: underline } +.logo { + display: block; + width: 155px; + height: 100px; + background-color: transparent; + background-image: url(elogo100.png); + background-position: center center; + background-repeat: no-repeat; + overflow: hidden; + text-indent: -2022px; + z-index: 2; + position: relative; +} +#nameset { + text-transform: uppercase; + font-weight: bold; + font-size: 22px; + color: #e3d25f; + margin: 20px 0 8px 0; +} +#meta { + text-transform: uppercase; + font-size: 16px; + margin-bottom: 14px; + text-align: center; +} +.content { + display: flex; + max-width: 960px; + margin: 0 auto; + flex-direction: column; + align-items: center; + padding: 30px; + height: 100%; + box-sizing: border-box; + position: relative; +} +.playerwrap { + width: 100%; + flex: 1; + display: flex; + position: relative; +} +.player { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + max-width: 480px; + margin: 0 auto; +} +#equalizerwrap { + position: relative; + display: flex; + flex: 1; + flex-direction: column; + align-items: center; +} + +#equalizerbg { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 2px; + background: rgba(0, 0, 0, 0.3); + z-index: 7; +} +#equalizerbg.hidden { + display: none; +} +#equalizer { + padding: 20px; + background: #000; + box-shadow: #000 0 10px 20px; + list-style-type: none; + margin: 0; +} +#equalizer li { + margin-bottom: 10px; + color: #ccc; +} +#equalizer li input { + width: 100%; + height: 25px!important; + border-radius: 13px; +} +.eqinfo { + float: right; +} +#settings, #pleditorwrap { + position: absolute; + background: rgba(0,0,0,1); + top: 0; + right: 0; + left: 0; + bottom: 0; + padding: 30px 10px 20px 0; + overflow-y: auto; + box-sizing: border-box; +} + +#credentialwrap { + max-width: 520px; + margin: 0 auto; + padding: 0; + list-style-type: none; +} + +#settings h2, #pleditor h2{ + text-align: center; + padding-top: 40px 0; + text-transform: uppercase; + font-weight: bold; + font-size: 26px; + color: #e3d25f; + overflow-y: auto; +} +#pleditor h2 { + line-height: 48px; + margin: 5px 0; + padding-left: 48px; +} +#pleditor h2 span { + display: block; + float: right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAA2FBMVEUAAAC+pDnm12HXxVHk1l65mjLJskO9oTjo3GPEqD3cylWyljDfz1rOuknBpjq+oDbt4Gjz6W6zli/17HC1mTLw5Wupjyq8nTOuky3Tv07p22PGq0G1mDDx5Wzw42vj1l7ez1nFqUHCpz64mjKzljDx5WzHq0G1lzHby1a2mTHbyla7nTTXxVHUwU/LtEXIr0HFqj3DpzrApDm9nzW5mzKyli/063Dz6G7v5Wvi013g0FvdzVfZx1POuUi+ojjs4Gbo22PVwk/RvkzQu0qvky2skSyqjyvl119lX1m8AAAAKXRSTlMAAgKgoaCfo6KioaGgn5+fLaSko6OioqGhoEItLfX19fX19fX1pEJCLVUTdPcAAAEKSURBVDjL1ZLZdoJADEDJoKCiqHVt7b7DgOybIO72//+o9FSdSeWhr+YtuXdyMjkRLik6BOdwi/Obuy5BfPTygd7fx1GPcFym2ivfg7xFYTggJ656vnYFAm90wyiukQOv5/TImdGLF8sK+eWO5/ePnBmDxTJpkoJLdk6rjDOj9pWkIgHJcjzGkVGZ6cZ1y7LdEePYaOqrzdRy2jzHhmjOp7bK+Lmw/kcDlx8R84axnkuqSzUFynlmbiSAtq8FYyj9gmG2CgDVINgO4XwJabb64YWhbPc7BcqWdCjCeL/TZMBrTtJMPJVgGPh5nTc6DzNd5A+mT92nCTq5xwY+Ofn5HQ/x+fdoJ8IFxTeZ6Bp9c7k3QwAAAABJRU5ErkJggg=='); + width: 48px; + height: 48px; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + border-radius: 24px; +} +#pleditor h2 span:hover { + background-color: #272727; +} +#pleheader { + display: flex; +} +#pleheader span { + padding: 8px; + font-weight: bold; + box-sizing: border-box; + color: #e3d25f!important; +} +#pleheader .space { + width: 70px; +} +#pleditor { + display: flex; + flex-direction: column; + justify-content: center; + min-width: 900px; + height: 100%; +} +#settings::-webkit-scrollbar, #pleditorcontent::-webkit-scrollbar { + width: 5px; + height: 8px; + background-color: #000; +} +#settings::-webkit-scrollbar-thumb, #pleditorcontent::-webkit-scrollbar-thumb { + background: #e3d25f; +} +#pleditorcontent { + flex: 1 1 auto; + overflow-y: scroll; + height: 0px; + margin: 0; + padding: 0 0px 0 0; + list-style-type: none; + border: #e3d25f 2px solid; + box-sizing: border-box; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAABAAgMAAACrYYWUAAAACVBMVEUnJycAAAAtLS3JOrneAAAAGElEQVQI12NYtYKBZgBoOAiFhtAIAQ0HALK+GReOr3bpAAAAAElFTkSuQmCC'); +} +.grabbable { + cursor: move; + cursor: grab; + cursor: -moz-grab; + cursor: -webkit-grab; +} +.pleitem { + display: flex; + justify-content: space-between; + background: #000; +} +.pleitem span { + display: flex; + flex-direction: column; + justify-content: center; + text-align: right; + color: #d7d7d7; + width: 40px; + padding: 0 8px; + box-sizing: border-box; + font-size: 14px; + border: #2d2d2d 1px solid; +} +.pleitem input { + font-family: Times, "Times New Roman", serif; + background: #000; + color: #e3d25f; + height: 32px; + line-height: 32px; + text-indent: 8px; + font-size: 18px; + border: #2d2d2d 1px solid; + font-weight: normal; + box-sizing: border-box; + margin-top: 4px; + border-radius: 0px; + outline: none; + margin: 0; +} +#pleditorcontent li:nth-child(odd) input, #pleditorcontent li:nth-child(odd) span { + background: #272727; +} +.plecheck { + width: 30px!important; +} +.plecheck input { + line-height: 10px; + height: auto; +} +.plename { + width: 300px; +} +.pleurl { + flex: 1; + color: #d7d7d7!important; +} +.pleovol { + width: 53px; + font-size: 16px!important; +} +#pleheader .pleovol { + width: 62px; +} +.credentialitem { + width: 100%; + display: flex; + justify-content: space-between; + margin-bottom: 14px; +} +.credentialitem span { + display: flex; + flex-direction: column; + justify-content: center; + font-weight: bold; +} +.credentialitem .textinput { + width: 47%; +} +.credentialitem input { + width: 100%; + box-sizing: border-box; + background: #272727; + color: #e3d25f; + padding: 6px 12px; + font-size: 20px; + border: #2d2d2d 1px solid; + font-weight: normal; + box-sizing: border-box; + margin-top: 4px; + border-radius: 0px; +} +.credentialitem label { + text-transform: uppercase; + color: #ccc; +} +.credentialitem input:focus { + outline: none; +} +.formbuttons { + display: flex; + justify-content: space-around; + max-width: 280px; + width: 100%; + margin: 0 auto; + margin-top: 30px; +} +#pleditor .formbuttons { + max-width: 100%; +} +.button { + padding: 8px 22px; + font-size: 18px; + border: #e3d25f 2px solid; + font-weight: normal; + box-sizing: border-box; + border-radius: 20px; + cursor: pointer; + text-align: center; + min-width: 120px; + margin-left: 20px; + text-transform: uppercase; + color: #e3d25f; + background: #272727; +} +.button:hover { + background: #e3d25f; + color: #000; +} +.button:active { + position: relative; + top: 2px; +} +#save_button { + background: #e3d25f; + color: #000; +} +#navbar { + display: flex; + width: 400px; + justify-content: space-between; + position: absolute; + top: 0; + padding: 30px 0 0; + box-sizing: border-box; +} +.playerbyttonwrap { + display: flex; + width: 400px; + justify-content: space-between; + margin-top: 14px; +} +.playerbytton, .stopped, .playing { + background-color: transparent; + background-position: center center; + background-repeat: no-repeat; + width: 48px; + height: 48px; + cursor: pointer; + border: #e3d25f 2px solid; + border-radius: 26px; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); +} +.navbutton { + border-color: transparent; +} +.navbutton:hover { + border-color: #e3d25f; +} +.playerbytton:hover, .stopped:hover, .playing:hover { + background-color: #272727; +} +.playerbytton:active, .stopped:active, .playing:active { + position: relative; + top: 2px; +} +.stopped { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAvVBMVEUAAADg0Vvez1fYx1Lr4V/q3l7l2F3o3F/h01nw52rr4WDx53Dez1nZyVL69o707Xju5Gbt42X38oXx6XP584n17X3t42v584fw6HX27nzs4Wzdz1Tx6HTw523h0lvez1naylXl11/m2GHj1V7f0FrdzVjq3WTn2mLZx1L38IDv5Wjo3GPi01zczFfcy1bz6m/s4Wf7+JP69o317nnt4mju42Pu5W7x6Gzw5Wvr32Xl2F/59If07HP38oTj1lmRTpVfAAAAHnRSTlMAEkkE/taStm377SkpKe3t7e3W1ra2tpKSbW1tSQQSPakHAAAA9klEQVQ4y82R2XaCMBRFtdLaWjvXWRMCQkhQQcIkDv//WSaRpWI0vnpe9+aek0XtgfL/fkdYLnt1vUCiz8FEJwQBiX7HOiHLA7Lu3JwSZTmN45SQfv2GkOdxGLpuyr6GjasCjcOV6/sWTtnf0xVhLTm2LGRb6abbVoUjn4Epom8vl1O2kheSw/kC0Z/XRlUQXGAAOTc9EyXPlSnEx4fPp5J73s64EMp66MxNzoHRGlUrGDrj0DCUkcy2geD8/AInzQ/lmWxWcrMQ69RsyvN20pLvUwXJQZiU5UoCfh669FiuCo6D6alcTVbQb16uEcTv0aXZrj1u9gzXJnV9YpvtAAAAAElFTkSuQmCC'); +} +.playing { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAP1BMVEUAAADv42vy52369Ivu4Wnz6W7x5Wv162/79o/7+JLt32js3Gb48YP584j484Xn12L17Xv17XHq2mXz6XTk0mCFQY+JAAAAAXRSTlMAQObYZgAAALFJREFUOMvlz90OgyAMhmH+CqJQcXr/17p+3cySsjuwRyTvQ5O6Z815nmOM1pxrzD3KWHBd+/7aBPCaUvLegqEdoGufgfaNAdDzYoH2tTsXPfoEmvYb5BAmgPwFWXqoFqCnFAUgB5qAdgXIVCxgdO8VkPQJ/K5fAvoxA/QMgG433NfjuIr/hRYL0D+gHNJDtAA5KDiK9PwfUMUGku67BchUAChIT2zAgqm16itG5uYeNW/M9AgMwr5N0gAAAABJRU5ErkJggg=='); +} +#prevbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABR1BMVEUAAADRu03Zxz7g0Fbn2lvx6Hbp22jXxFC/qTvo2WS2nTXFrkPt4WfbyUHl11rk1Vfs4Wbn11jj1FPYxT/27X/x53LVwj3TwDro2F3Wwz/byUnRvTzp3WbSvz/k1Fvq3GbezVTNuDvOu0DItDnh0FvJszzTv0rErjjezFrWwk7Xw1K/qDjbyVi7pDi3nzbZxVXTvlDBqUHKs0jezETcyUC9pT3t4GPq3FzWwVLj1E7FrUO/pz7u4mbi0F3o2VzcyFjUv1HOt0vIsEa7ojvTvjPNtyv17nzz6XTw5Wvi0l3ey1rax1Xl1VLXw1LSvU/Qu03h0Urgz0jDrEDVwjj38ILz6Xbx5m/q3Wnr3l/fzFvi0ljl1lbZxlLVwkDPu0DXxDvQuy7LtCrl1GHm1mDl1VrYxkzbyUfJs0XIsj/KtT3ItDvBqzvKsin8qlL8AAAAL3RSTlMAFv6t66tWVi0VCwX19e3t7Ozs7Ovr4uLU1MXFsbGvrZ6ejIx2dmRkWVk9PR4eFHLHxygAAAE0SURBVDjL7ZHndoJAFISVJCYxvffeKx0FxIYlKAhI1CT29Pb+v13KXV/C+QFzznyzZ+/d0EhD/feH/m4P2/AYuH4e3MzZHAt+cvwJbB6Amy22Y0M9pgkY6Hm/+yOuXeWtoJ4sUxjo5dzvxQrxrcq06NdfKCqdACCHgKkdrsPzMl1FwNVSslWh4sYDBpzoCdH+U91c0r16JR03SAw4zgbxq6o8TUtSVl9ItkyUk2SqDkC3u8798LJMS9ks05xH10N5OVUqAkBw0WP28wvlDJOJhfeFNxL1S88Y4Dh0yW3xg0F5Bm3ncjGhoFypAcB62ztfbhoZ0xTcMQ/qDUXRChiw/eUdxt7jlD98ZK3YeMWAbQXmelPQgu1MTxRqjwBYIn6s01k8fGQVA6IewrrdBYcOCY0EGgATKDjicNrzUgAAAABJRU5ErkJggg=='); +} +#nextbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABCFBMVEUAAAC+ojXKsDi0mC/awzndxjvXwTTgzUjRuTTUvDjj1VbUvDrHrjHEqjLXxEu6ny/Sv0vWwTXhzkXUvjXq3lzl1lDRujPQujLbxj7RujTcyEfMtTPXwT7byUjRuDnJsTLSukDEqzHHqzm5oC/JsD+1mjDbxDfVwDS0ly+7nDO1mDDYwjW4mjLizULEqTzBpTnAozjUvTG3mTHFqz7fyTzKsDnUvjTSvDDq3Vjl1Evj0UbHrkDdxjvCpzq8njTIsEHgyz7awza/oTbQuTTs4V3k1lPn2VDMt0bQuUXLtUXKskPVvz7VvTvJsTO7oDDXxUzYwkDYwTzDpjrFqjjErDPDqzK/ozLCqTEl328YAAAAJnRSTlMAFVgI7ez7+sWuq6uMY1ctFfXs7Ovr4uLU1LGxr62ennZ2PT0eHggzQnUAAAEdSURBVDjL7ZFncsIwFITtQEJ6771KNrIt4QoOdmwTAoSQXu5/kzwZJE1mcgT215u3366kkTaV1OK6HA+v1RohMd1vr4oxrp1X/gG6TVESD+39SwHkAmj2X7o74xKbfbz5p3o550QCvUGv1jrROVAUaYCqK3xNFDB4vLvtx5szmpalqWmmHTS/BgAWwAP4nuO92se6b5qmZbnf4dxyBSuA+w5lQbuKSr9eNz7xbiiBFvgJZQUHwAfbML7wXqMhAc95p4w++Qt623Jd8J87cESoAMhD/yxcEvF6d4T5JVVDnNCEx2FEEP8Jt8pnRpEAbBb4PM4BaCdngP4FhtmSPh7zET64mqwjeUQG8YnIxoX8LPVMiAsd3WhSmGhTSf0Cx6gshxH98P4AAAAASUVORK5CYII='); +} +#volpbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABp1BMVEUAAADTv03byVXSvkzax1PYxEvbyVLWw07Svkrj0Ufr31vt4WPr317q3V7m2lvj1Vjm2mHdzE3cy1DRu0PbylHXxEzYxlHWw0/u5Gbs4V7r31nu5Gft4mPq3Vnq31/n2lfm2Fjn21zl11fo3WDk1lbj1VXl2FrdzErh0VPg0VPYxEXfz1Lcykzf0FTayEzWwUfcy0/ezlbfz1nUwEvPuUXXxVLGqjvNt0Xv52rs4V3s313k0kjs4V/o2lTm1lDp21fq3Vvu5Gnn2FPr3l3k003s4mfq317q3mHs4mbk1FHbx0Hp3mHo21/cx0Lfz0zdy0jo22Hfz07h0VDj1Vfezk7h01fk2F/YxEfj1Fvh0Vbj1F3g0FjQuD7h1FzZxk7g0lvdzVfez1nTv0nNt0XGrTzaylbYyFTYx1PXxVHt42XayVXv5Wjs4mbu42Ps4WHs4Fzez1np3Fjn2VXn22Dcy1fi0lDgz03w52ru5Wjq3mLk1l3o21rdzlbk1VTayFTo2VDWxFDk1U/dyknv5WXu42Dg0Vvs4Frq3lfk1lfg0Vbp21TUwU3gzUTlFyGcAAAAZXRSTlMAASgEEVk8JhX+9O7k1LaWjo1kTElFGQ78/Pzv5+XX0sm7uLe0sKWjmJCPiYV9cmppT0AtHRQUCPr69vby7+/t6+rq6OXf39zYzMrJwry4tLGxqpqaj4iGhXdsbGZeXllVNDQkGQe+VJQAAAGqSURBVDjLzdJlc9tAEIDh15Jsx2zHThxsqGkDDadJmZmZuZVkmSkOYxl+dCVZmlEyU3/s5Pm2s7d7t3fHvuN656URbygXEBrkj15drmSj/JN4Kx9423lexNbEbs9ylSjRxfd27Lko4TTdtpKdYnrpNZaHhWYcmrqW0xkBKfME0wyJQr9zwIFstXhMxNV2z4yfB5n3d9ineJnTtMxXuXgN6LqJIVwQGCwlqAuspBRFlouXkxA6ALTyeLWZ0XXrEJK2oaZlOV3VBqAvCJEIkz/GGF84gknQ9HJZUdSi1sqhU3DiIC3lIdwLh62Bs3q5nk+l8gki7XCuF6H8gJbaHavDkp5X9fzaF72DH/xGh0e4a1YHabGqGAuUfBj6umF4iEl9//Gdp9R1Voz6rY0rSegJma8Q255gZGeCuheZz1pubUs1JuzuxXB/c4a7vrjjJsP5lHpcxNUxaMbuS675k74552cIrv4ueJG+j2KaJe67jZOn/WdpCk/pDZaRP6/YZezb+jCxXx/s+NONuT0/tr901n36gohNZI9kT7m2HaOB2eubZ7w0FP/I//cX2VZVBFBJqXUAAAAASUVORK5CYII='); +} +#volmbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABd1BMVEUAAAC4njLo11PYxDrm00/hzlDaxEnIrUDHrUG9ojnk0Uvn1lHk0U3SvTPWwTfp2lnj0E3gzUniz0/dyEXj0VHgzU/bxkbaxUjfzVLey1HHsDTaxlHFrTq7ojLTu0i9ozXUvUyxlyyulSzNtEXGrECzmTDTvjLeykLq3Vzp2lnaxT7QujHm01HRuzPm1FPdyELYxEDNuC/WwTvLtS/l1Vfjz1Dm1ljaxkTJszDaxUbKtDLWwULVwELfzFLKszjey1LOuD7AqS/Zw0zKtDy7oyzHsDu+pjK5oC7Wv0zayFTXwk7XxFHTvU3TvU2pjyu9ojq6nze1mzO0mTLKr0LApDy2nDXUvzSxljHp2Vbhz0jgy0PMs0HaxTy+ozu8oDi4nTbQujLTvTGymDDr3Vvo2FnfzE7gzEbcyUDSvDbVwTXi0VXaxEzFrD/EqD/IsD7Cpj7OuD2+pTm6oDm+pDe7ozbMtzW5oTTKtDPIsTO+pTPCqjLFrjGuky/WrPCGAAAAT3RSTlMAJ/7+/bOWGQ4E/vby7u3s5OLX1Mm5tqWQjo5bS0A9NyUlFRQSEf377+/u7uvn5ubf3NjV09HMycK7uLaxl46IiIV7bGxeWVVPTTQoHgUEcf+/bgAAAS1JREFUOMvNzkV3wkAYheGLS4sVKS4F6u7ubgQCQQtJ8Lr7jy90l55Mlj082/fOdwad5yAkmUMznEEGsr4p9p11k/v5ImvYNxp1INni3txYLwRI3df/2fDCV9gjfWDshVfIEKlvEgbLTPxVoQMGVkXiLsc0mx+J52kAEwsig5Gv73Q6EX+a1AJWKwCNsEcZPs0nUqWH+gqwZAHUagjImNbzVIkq3tY06O0GulQQCDR+eyyWzHmh1gOjtj8XFPEU9Ri7SOb14heiBYoqtvp11gHYzYBzDULD9+1+R1u0wFwPgDCEdnK5WjZ/Q7fbuA3iHFk6r9QBchVhEDZXaXkQkcwGCPzKK+Uh/BkPSLblVRdc5WMQ2TMmz5DpDETa2cplxQkJp/PlwRNIOgri//0AbVY1/4adyuMAAAAASUVORK5CYII='); +} +#playlistbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABTVBMVEUAAADr4GLo3GTSvUrn2mLVwlDh0lLq32Xm2l/f0Ffi013o3Gbs4Wrg0VvXxlDf0Frz7Hncx0Dz63jfzUjm2Vjo3F3x6HPq4GTfzkrbyUXk2Fnx6XXu5W7y63fo3WLk11rw6HPVvTvr4Wnr4Gnf0VTv5nLUvUDZyEzs4m3v5XHo3GbMsjri1Fzl2WLUwEnHrDjaylPdzlfQukbXx1DGqjnWxE/r3mjYxlPPukjy6nTx6HHn2mPm2GHj1F3ez1nayFPfzUj07Xrw527u5W3u5Gns4mbg0Frdzljl1lPRvUnJsELGrT/s4mvq3mHk1l/h0lzm2VjczFfh0lLk1VDWxE/i0k7Tv03i0Uzgz0rNt0TKs0PdyULcxj/Eqj3Dpzvz63fr4Gfq3mfo3GPn22Dm2Vbby1XYyFHfy0Xbx0Tv5W3Tv0nZxEPTvT7Ruj7Irjt5kAYfAAAAOXRSTlMA+40mCQPLurZ0V01CPjYW/Pz6+Pf27+7r5uPi3t3Z0cnGw62roqGVhHt3dmtkXVNNREQ6MiwiHhGFwXcaAAABNUlEQVQ4y83SR3OCUBQFYHuNMb333nujWlCBBJQmgiHGrqn/f5mXZCZA5rr3bjjM+ebCm3me4Z2g/yw6oEomjn2RFa8oSUC5v702gzN1gmJTut4GQIZpEBTNdrqapvMcBMgiVaDlFJquBgOSJIimKE6vbnEqAJ4xfCnsOzq/RZkrgyDwl9UKADDMzmUQ4A5gQCBr50oaALgDGBDIvtg53YMA4wCPAGByv0//yYW/B4HcD7gOqUa63/8HAnHfQSyXR+lySuHvH55qlqsfWcSEeqOJQHBBMasIfFqufgwnSwW2g8CoxJvfC97XnWAZL1IlWi54EWjzaEHtwzp19DcZAl0EVm5F0CdCymvVfBs/dP3hhIAW0C1vAr3c7c5Nzu9cuc+whwkkkZ+NeQZOfHMjHE16hnS+AIiQOxEJiBv0AAAAAElFTkSuQmCC'); +} +#eqbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAwFBMVEUAAAD27X/QvErn2WH27oH0633v43Hj01vcyk7w5XDj02HbyVf28IPx53Xu4m7s4Gni0ljgz1TayUvq3Wfo2mPy53Tt4G7s3Wrt4Gn27oDx5nP48of274Li0lT38YT17H306nru4mv59Irz6Hbr32bq3GTl1ljfz07v5G3p2mHo2l7gzl3m2Fvgz1LOuEzXxEDeylrczEvLtEjbykfZx0TVwj7SvznQuzXKtS7j02HcyVHSvVDZx07dzEzNuDTMtzDLJSoKAAAAGHRSTlMA4Uvh0tLS0tJLS0vh4eHh4eHh0tJLS0sGkbICAAAA4klEQVQ4y83Sx9KCMBSG4SN2/XtNCCGEKgjYe7v/uzJRR8YZOePSd5l5FpkvgSet+/PdQUE1Dt5Q4A/7Jty0PCzms8F0EmU8TOMzcCi1CrDXYDKKeBgmARANhAYlyf7KA6x3Il9R0Gk0/u8cG62WAVhf2/UHCqJxHkJJc7WBBikEPumZniOoZdk2uwI10giycZ5AQKQGtABFPN/EgPWZ8SoKjGazC4/1V6+3UVBxnAoKKJE23KTemadJ7BNpmp5QgNjAmOtewU5/lOQy0gkwYK4CJVmyx/BLCvGCgnat9gvP1BGHfhnUEswA0wAAAABJRU5ErkJggg=='); +} +#settingsbutton { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAB8lBMVEUAAACDbR+QdyKgkjzEqirCrDWReCCyp0WJciCJcyKYhzChkzqTgS3Vw0G6oSe6oSjRw0q1nCm8oy3TyFO4nyrIuEKxlyfHu0qiiSKegyGijCyRdyKmlziomjqklTexqEmvpkmKdiXFrCzJry7n3mLf0lLXxUPHrS7cz0/KrzDZy0rPuDfBpyrn32jSwEDKsjTEqi3BqCzIsDTp4264nynPv0Pj3GfWyU/c01yzmybh2mSulSS+pi/Zz1rBrDazmSiqkCXFt0Pa1GOliiOtkynNxFTZ0mOpkCrBtUmqkivIv1OpkyybgSK+s0rJwlqplzKUeyGQdx++tU6ypUHCu1WWfSOnmDjCvFexpkSahCqNdSGHcB+XgimonECAax7OtDDLsC7Hrivt42js4mXq3l3h0Uy8oC/38Xv07XPv5Wvt5GPs4mHn21jez1fm2FLXxVDUwUneyULLtEHbxT7JrzzFqDS/oDTUuTPDpjH59ILz63Hp3GLi1Vzf0Frh0lbby1DUwk/ezUfSvUPQu0PNszvBpTrZwDjVvDfKsDa7nTPVujLRtjHPtDG2mS+xlS7Jry3Eqiz27nfx6W/k12Dq4F7bylXYx07j1E3Qu0rhz0fMtkTJsUHcxz/Vvj/Otj/Eqj7NsTTRtjPHqjO+oDLApDC+pCyMAG9RAAAAX3RSTlMADUYD/NtgQy4hFxQK+fTt49/e1tTT0bizqGVOSEE8Jg8G/vz6+vr49/f29vX09PTz8vHv6efm5uXl4eDf2NXRysbCu7i1s6Keko+Li31zcXFwal9dWU9NS0s+OjcbEJmukO4AAAFySURBVDjLYhiCIJZdlguXnLyXrUpWdkpOXiqvnTcnhnSIVUZVUnXt1PyGGc3Nc9vahUJRpMON06uSJtfU5ubWTysomt9aVhrfzc8El2Z2q0zPrE5OTs5OURUwVSssbl9Q2pvWnygJU+BXOTEpc1KmsEcwN0+YjCKXlLtgfEJa39JENpgKp/SMDAv2CHEBjbzpM+do27gwSZskTEiUQNhhphskLzwlpa6+YNbskpKOhfFinAF81kiOVFBwTc6qyW/IL2ia11La2VkRryTByIjsDfak7Nw6YBDosbLql3f1LOmbwBKNGg6+WSk5orLMXFKRPMxMYgn9iYHoIQWYuLmMnChvYXFbl7o9B5OlJGZQczumNjaVtJaVVcQnOscxYFGglVpU1NKxqCKtN4GPEVtsyTUWFhuJiBjGJ7BwYI9Pf9YYBkYehihBNpzJwVOzvFvHB096ESpfnBbvgEcBo0FPPL8iviTHoczCiT9RskkzDEcAAG+FX5nDaQtoAAAAAElFTkSuQmCC'); +} +.infowrap { + display: flex; + width: 400px; + justify-content: space-around; + font-size: 14px; + margin: 16px 0; +} +#volrange, .slider { + overflow: hidden; + width: 432px; + margin-top: 34px; + background: linear-gradient(to right, #bfa73e 0%, #bfa73e 0%, #272727 0%, #272727 100%); + border: solid 2px #e3d25f; + border-radius: 13px; + height: 25px!important; + outline: none; + transition: background 450ms ease-in; + -webkit-appearance: none; + box-sizing: border-box; +} +#volrange::-webkit-slider-thumb, .slider::-webkit-slider-thumb { + width: 10px; + -webkit-appearance: none;Раменское + height: 10px; + cursor: ew-resize; +} +.slider { + width: 400px; + margin-top: 4px; +} +#playlist { + width: 400px; + scroll-behavior: smooth; + overflow: auto; + margin: 0 0 0 0; + padding: 0; + list-style-type: none; + border: solid 1px #e3d25f; + flex: 1 1 auto; + overflow-y: auto; + height: 0px; + box-sizing: border-box; +} +#playlist::-webkit-scrollbar { + width: 5px; + height: 8px; + background-color: #000; +} +#playlist::-webkit-scrollbar-thumb { + background: #e3d25f; +} +#playlist li { + text-transform: uppercase; + font-size: 20px; + padding: 8px 16px; + color: #bfa73e; + cursor: pointer; + position: relative; +} +#playlist li span { + position: absolute; + display: flex; + flex-direction: column; + justify-content: center; + right: 0; + top: 0; + bottom: 0; + padding: 0 10px; + background: #000; + width: 44px; + box-sizing: border-box; + text-align: right; + font-size: 14px; + vertical-align: middle; +} +#playlist li:nth-child(odd), #playlist li:nth-child(odd) span { + background: #272727; +} + +#playlist li:hover, #playlist li:hover span { + color: #fff; + background: #323232; +} +#playlist li.active, #playlist li.active span { + background: #bfa73e; + color: #000; +} +#copy { + padding-top: 14px; + font-size: 14px; +} + +@-webkit-keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@media screen and (max-width: 480px) { + .content { padding: 20px; } + #playlist { width: 100%; } + #playlist li { font-size: 16px; } + #volrange { width: 100%; margin-top: 24px;} + .playerbyttonwrap { width: 100%; margin-top: 8px; } + #navbar { width: 100%; padding: 20px 20px 0 20px; } + #meta { margin-bottom: 8px; } + .infowrap {width: 100%; padding: 0 10px;} + .logo { width: 100px; height: 65px; background-size: cover; } + #copy { font-size: 10px; padding-top: 10px; } +} +@media screen and (max-width: 480px) { + .playerbytton, #playbutton { + width: 42px; + height: 42px; + background-size: 75%; + } +} + diff --git a/yoRadio/display.cpp b/yoRadio/display.cpp new file mode 100644 index 0000000..ddcbc95 --- /dev/null +++ b/yoRadio/display.cpp @@ -0,0 +1,311 @@ +#include "WiFi.h" +#include "time.h" +#include "display.h" + +#include "player.h" +#include "netserver.h" +#include "options.h" +#include "network.h" + +/* +#include "displayDummy.h" +DisplayDummy dsp; +*/ +#include "displayST7735.h" +DisplayST7735 dsp; + +Display display; + +void ticks() { + display.clockRequest = true; +} + +#define STARTTIME 5000 +#define SCROLLTIME 83 +#define SCROLLDELTA 3 + +void Scroll::init(char *sep, byte tsize, byte top, uint16_t dlay, uint16_t fgcolor, uint16_t bgcolor) { + textsize = tsize; + texttop = top; + fg = fgcolor; + bg = bgcolor; + delayStartScroll=dlay; + memset(separator, 0, 4); + strlcpy(separator, sep, 4); + locked = false; +} + +void Scroll::setText(const char *txt) { + memset(text, 0, BUFLEN / 2); + strlcpy(text, txt, BUFLEN / 2); + getbounds(textwidth, textheight, sepwidth); + if (!locked) { + clearscrolls(); + reset(); + } +} + +void Scroll::lock() { locked = true; } + +void Scroll::unlock() { locked = false; } + +void Scroll::reset() { + locked = false; + clear(); + setTextParams(); + dsp.set_Cursor(TFT_FRAMEWDT, texttop); + dsp.printText(text); + drawFrame(); +} + +void Scroll::setTextParams() { + dsp.set_TextSize(textsize); + dsp.set_TextColor(fg, bg); +} + +void Scroll::clearscrolls() { + x = TFT_FRAMEWDT; + scrolldelay = millis(); + clear(); +} + +void Scroll::loop() { + if (checkdelay(x == TFT_FRAMEWDT ? delayStartScroll : SCROLLTIME, scrolldelay)) { + scroll(); + ticks(); + } + yield(); +} + +boolean Scroll::checkdelay(int m, unsigned long &tstamp) { + if (millis() - tstamp > m) { + tstamp = millis(); + return true; + } else { + return false; + } +} + +void Scroll::drawFrame() { + dsp.drawScrollFrame(texttop, textheight, bg); +} + +void Scroll::ticks() { + if (!doscroll || locked) return; + setTextParams(); + dsp.set_Cursor(x, texttop); + dsp.printText(text); + dsp.printText(separator); + dsp.printText(text); + drawFrame(); +} + +void Scroll::scroll() { + if (!doscroll) return; + if (textwidth > display.screenwidth) { + x -= SCROLLDELTA; + if (-x >= textwidth + sepwidth - TFT_FRAMEWDT) x = TFT_FRAMEWDT; + } +} + +void Scroll::clear() { + dsp.clearScroll(texttop, textheight, bg); +} + +void Scroll::getbounds(uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) { + if (strlen(text) == 0) { + dsp.getScrolBbounds("EMPTY", separator, textsize, tWidth, tHeight, sWidth); + } else { + dsp.getScrolBbounds(text, separator, textsize, tWidth, tHeight, sWidth); + } + doscroll = (tWidth > display.screenwidth); +} + +void Display::init() { + dsp.initD(screenwidth, screenheight); + dsp.drawLogo(); + meta.init(" * ", 2, TFT_FRAMEWDT, STARTTIME, TFT_LOGO, TFT_BG); + title1.init(" * ", 1, TFT_FRAMEWDT + 2 * TFT_LINEHGHT, STARTTIME, TFT_FG, TFT_BG); + title2.init(" * ", 1, TFT_FRAMEWDT + 3 * TFT_LINEHGHT, STARTTIME, TFT_FG, TFT_BG); + plCurrent.init(" * ", 2, 57, 0, TFT_BG, TFT_LOGO); + plCurrent.lock(); +} + +void Display::apScreen() { + meta.setText(dsp.utf8Rus("ёRADIO * ёRADIO * ёRADIO", false)); + dsp.apScreen(); +} + +void Display::start() { + clear(); + if (network.status != CONNECTED) { + apScreen(); + return; + } + mode = PLAYER; + title("[READY]"); + ip(); + volume(); + station(); + rssi(); + time(); + configTime(TIMEZONE, OFFSET, "pool.ntp.org", "ru.pool.ntp.org"); + timer.attach_ms(1000, ticks); + // Экстреминатус секвестирован +} + +void Display::clear() { + dsp.clearDsp(); +} + +void Display::swichMode(displayMode_e newmode) { + if (newmode == VOL) { + volDelay = millis(); + } + if (newmode == mode) return; + clear(); + mode = newmode; + if (newmode != STATIONS) { + ip(); + volume(); + } + if (newmode == PLAYER) { + meta.reset(); + title1.reset(); + title2.reset(); + plCurrent.lock(); + time(); + } else { + meta.lock(); + title1.lock(); + title2.lock(); + } + if (newmode == VOL) { + dsp.frameTitle("VOLUME"); + } + if (newmode == STATIONS) { + currentPlItem = config.store.lastStation; + plCurrent.reset(); + drawPlaylist(); + } +} + +void Display::drawPlayer() { + if (clockRequest) { + if (syncTicks % 21600 == 0) { //6hours + configTime(TIMEZONE, OFFSET, "pool.ntp.org", "ru.pool.ntp.org"); + yield(); + syncTicks = 0; + } + getLocalTime(&timeinfo); + time(); + syncTicks++; + clockRequest = false; + } + meta.loop(); + title1.loop(); + title2.loop(); +} + +void Display::drawVolume() { + if (millis() - volDelay > 3000) { + volDelay = millis(); + swichMode(PLAYER); + } +} + +void Display::drawPlaylist() { + char buf[PLMITEMLENGHT]; + dsp.drawPlaylist(currentPlItem, buf); + plCurrent.setText(dsp.utf8Rus(buf, true)); +} + +void Display::loop() { + switch (mode) { + case PLAYER: { + drawPlayer(); + break; + } + case VOL: { + drawVolume(); + break; + } + case STATIONS: { + plCurrent.loop(); + break; + } + } + yield(); +} + +void Display::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) { + dsp.centerText(text, y, fg, bg); +} + +void Display::bootString(const char* text, byte y) { + dsp.centerText(text, y, TFT_LOGO, TFT_BG); +} + +void Display::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { + dsp.rightText(text, y, fg, bg); +} + +void Display::station() { + meta.setText(dsp.utf8Rus(config.station.name, true)); + netserver.requestOnChange(STATION, 0); +} + +void Display::title(const char *str) { + const char *title = str; + char ttl[BUFLEN / 2] = { 0 }; + char sng[BUFLEN / 2] = { 0 }; + memset(config.station.title, 0, BUFLEN); + strlcpy(config.station.title, title, BUFLEN); + if (strlen(config.station.title) > 0) { + char* ici; + if ((ici = strstr(config.station.title, " - ")) != NULL) { + strlcpy(sng, ici + 3, BUFLEN / 2); + strlcpy(ttl, config.station.title, strlen(config.station.title) - strlen(ici) + 1); + } else { + strlcpy(ttl, config.station.title, BUFLEN / 2); + sng[0] = '\0'; + } + title1.setText(dsp.utf8Rus(ttl, true)); + title2.setText(dsp.utf8Rus(sng, true)); + } + netserver.requestOnChange(TITLE, 0); +} + +void Display::heap() { + if(config.store.audioinfo) dsp.displayHeapForDebug(); +} + +void Display::rssi() { + char buf[20]; + int rssi = WiFi.RSSI(); + sprintf(buf, "%ddBm", rssi); + dsp.rssi(buf); + netserver.setRSSI(rssi); +} + +void Display::ip() { + dsp.ip(WiFi.localIP().toString().c_str()); +} + +void Display::time() { + char timeStringBuff[20] = { 0 }; + if (!dt) { + heap(); + rssi(); + strftime(timeStringBuff, sizeof(timeStringBuff), "%H:%M", &timeinfo); + } else { + strftime(timeStringBuff, sizeof(timeStringBuff), "%H %M", &timeinfo); + } + dsp.printClock(timeStringBuff); + dt = !dt; +} + +void Display::volume() { + dsp.drawVolumeBar(mode == VOL); + netserver.requestOnChange(VOLUME, 0); +} diff --git a/yoRadio/display.h b/yoRadio/display.h new file mode 100644 index 0000000..b2c59b2 --- /dev/null +++ b/yoRadio/display.h @@ -0,0 +1,81 @@ +#ifndef display_h +#define display_h + +#include "Arduino.h" +#include +#include "config.h" + +enum displayMode_e { PLAYER, VOL, STATIONS }; + +#define TIMEZONE 10800 // 3600*3=10800 (UTC+3) +#define OFFSET 0 // Daylight Offset (sec.) + +class Scroll { + public: + Scroll() { }; + void init(char *sep, byte tsize, byte top, uint16_t dlay, uint16_t fgcolor, uint16_t bgcolor); + void setText(const char *txt); + void loop(); + void reset(); + void lock(); + void unlock(); + private: + byte textsize, texttop; + char text[BUFLEN/2]; + char separator[4]; + uint16_t fg, bg; + uint16_t delayStartScroll; + uint16_t textwidth, textheight, sepwidth, startticks, scrollticks; + int x; + bool doscroll, locked; + unsigned long scrolldelay; + void clearscrolls(); + void getbounds(uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); + boolean checkdelay(int m, unsigned long &tstamp); + void scroll(); + void ticks(); + void clear(); + void setTextParams(); + void drawFrame(); +}; + +class Display { + public: + struct tm timeinfo; + uint16_t syncTicks; + bool clockRequest; + uint16_t screenwidth, screenheight; + displayMode_e mode; + uint16_t currentPlItem; + public: + Display() {}; + void init(); + void clear(); + void loop(); + void start(); + void centerText(const char* text, byte y, uint16_t fg, uint16_t bg); + void rightText(const char* text, byte y, uint16_t fg, uint16_t bg); + void bootString(const char* text, byte y); + void station(); + void title(const char *str); + void time(); + void volume(); + void ip(); + void swichMode(displayMode_e newmode); + void drawPlaylist(); + private: + Ticker timer; + Scroll meta, title1, title2, plCurrent; + bool dt; // dots + unsigned long volDelay; + void heap(); + void rssi(); + void apScreen(); + void drawPlayer(); + void drawVolume(); + +}; + +extern Display display; + +#endif diff --git a/yoRadio/displayDummy.cpp b/yoRadio/displayDummy.cpp new file mode 100644 index 0000000..9883d50 --- /dev/null +++ b/yoRadio/displayDummy.cpp @@ -0,0 +1,163 @@ +#include "displayDummy.h" +#include +#include "player.h" +#include "config.h" +#include "network.h" + +DisplayDummy::DisplayDummy() { + +} + +char* DisplayDummy::utf8Rus(const char* str, bool uppercase) { + int index = 0; + static char strn[BUFLEN]; + bool E = false; + strlcpy(strn, str, BUFLEN); + if (uppercase) { + bool next = false; + for (char *iter = strn; *iter != '\0'; ++iter) + { + if (E) { + E = false; + continue; + } + byte rus = (byte) * iter; + if (rus == 208 && (byte) * (iter + 1) == 129) { // ёКостыли + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (rus == 209 && (byte) * (iter + 1) == 145) { + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (next) { + if (rus >= 128 && rus <= 143) *iter = (char)(rus + 32); + if (rus >= 176 && rus <= 191) *iter = (char)(rus - 32); + next = false; + } + if (rus == 208) next = true; + if (rus == 209) { + *iter = (char)208; + next = true; + } + *iter = toupper(*iter); + } + } + while (strn[index]) + { + if (strn[index] >= 0xBF) + { + switch (strn[index]) { + case 0xD0: { + if (strn[index + 1] == 0x81) { + strn[index] = 0xA8; + break; + } + if (strn[index + 1] >= 0x90 && strn[index + 1] <= 0xBF) strn[index] = strn[index + 1] + 0x30; + break; + } + case 0xD1: { + if (strn[index + 1] == 0x91) { + //strn[index] = 0xB7; + strn[index] = 0xB8; + break; + } + if (strn[index + 1] >= 0x80 && strn[index + 1] <= 0x8F) strn[index] = strn[index + 1] + 0x70; + break; + } + } + int sind = index + 2; + while (strn[sind]) { + strn[sind - 1] = strn[sind]; + sind++; + } + strn[sind - 1] = 0; + } + index++; + } + return strn; +} + +void DisplayDummy::apScreen() { + +} + +void DisplayDummy::initD(uint16_t &screenwidth, uint16_t &screenheight) { + +} + +void DisplayDummy::drawLogo() { + +} + +void DisplayDummy::drawPlaylist(uint16_t currentItem, char* currentItemText) { + +} + +void DisplayDummy::clearDsp() { + +} + +void DisplayDummy::drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg) { + +} + +void DisplayDummy::getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) { + +} + +void DisplayDummy::clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg) { + +} + +void DisplayDummy::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) { + +} + +void DisplayDummy::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { + +} + +void DisplayDummy::displayHeapForDebug() { + +} + +void DisplayDummy::printClock(const char* timestr) { + +} + +void DisplayDummy::drawVolumeBar(bool withNumber) { + +} + +void DisplayDummy::frameTitle(const char* str) { + +} + +void DisplayDummy::rssi(const char* str) { +; +} + +void DisplayDummy::ip(const char* str) { + +} + +void DisplayDummy::set_TextSize(uint8_t s) { + +} + +void DisplayDummy::set_TextColor(uint16_t fg, uint16_t bg) { + +} + +void DisplayDummy::set_Cursor(int16_t x, int16_t y) { + +} + +void DisplayDummy::printText(const char* txt) { + +} diff --git a/yoRadio/displayDummy.h b/yoRadio/displayDummy.h new file mode 100644 index 0000000..b0cb378 --- /dev/null +++ b/yoRadio/displayDummy.h @@ -0,0 +1,80 @@ +#ifndef displayDummy_h +#define displayDummy_h + +#include "Arduino.h" +#include "options.h" + +#define TFT_ROTATE 3 +#define TFT_LINEHGHT 10 +#define TFT_FRAMEWDT 4 + +#define PLMITEMS 7 +#define PLMITEMLENGHT 40 +#define PLMITEMHEIGHT 22 + +class DisplayDummy { + public: + DisplayDummy(); + char plMenu[PLMITEMS][PLMITEMLENGHT]; + uint16_t clockY; + void initD(uint16_t &screenwidth, uint16_t &screenheight); + void apScreen(); + void drawLogo(); + void clearDsp(); + void centerText(const char* text, byte y, uint16_t fg, uint16_t bg); + void rightText(const char* text, byte y, uint16_t fg, uint16_t bg); + void set_TextSize(uint8_t s); + void set_TextColor(uint16_t fg, uint16_t bg); + void set_Cursor(int16_t x, int16_t y); + void printText(const char* txt); + void printClock(const char* timestr); + void displayHeapForDebug(); + void drawVolumeBar(bool withNumber); + char* utf8Rus(const char* str, bool uppercase); + void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); + void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); + void clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg); + void frameTitle(const char* str); + void rssi(const char* str); + void ip(const char* str); + void drawPlaylist(uint16_t currentItem, char* currentItemText); + private: + uint16_t swidth, sheight; + +}; + +extern DisplayDummy dsp; + +/* + * TFT COLORS + */ +#define BLACK 0x0000 +#define BLUE 0x001F +#define RED 0xF800 +#define GREEN 0x07E0 +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#define WHITE 0xFFFF +#define GRAY 0x7BEF +#define DARK_GRAY 0x2945 +#define LIGHT_GRAY 0xC618 +#define LIME 0x87E0 +#define AQUA 0x5D1C +#define CYAN 0x07FF +#define DARK_CYAN 0x03EF +#define ORANGE 0xFCA0 +#define PINK 0xF97F +#define BROWN 0x8200 +#define VIOLET 0x9199 +#define SILVER 0xA510 +#define GOLD 0xA508 +#define NAVY 0x000F +#define MAROON 0x7800 +#define PURPLE 0x780F +#define OLIVE 0x7BE0 + +#define TFT_BG BLACK +#define TFT_FG WHITE +#define TFT_LOGO 0xE68B // 224, 209, 92 + +#endif diff --git a/yoRadio/displayST7735.cpp b/yoRadio/displayST7735.cpp new file mode 100644 index 0000000..5d034dd --- /dev/null +++ b/yoRadio/displayST7735.cpp @@ -0,0 +1,321 @@ +#include "displayST7735.h" +#include +#include "fonts/bootlogo.h" +#include "player.h" +#include "config.h" +#include "network.h" + +#define DTYPE INITR_BLACKTAB // 1.8' https://aliexpress.ru/item/1005002822797745.html +/* If there is a noisy line on one side of the screen, then in Adafruit_ST7735.cpp: + + // Black tab, change MADCTL color filter + if ((options == INITR_BLACKTAB) || (options == INITR_MINI160x80)) { + uint8_t data = 0xC0; + sendCommand(ST77XX_MADCTL, &data, 1); + _add this_ -> _colstart = 2; + _add this_ -> _rowstart = 1; + } + +*/ +//#define DTYPE INITR_144GREENTAB // 1.44' https://aliexpress.ru/item/1005002822797745.html + +class GFXClock { + public: + GFXClock() {}; + uint16_t init(Adafruit_ST7735 &tftd, const GFXfont *font, uint16_t fgcolor, uint16_t bgcolor ) { + _dsp = &tftd; + tftd.setFont(font); + tftd.getTextBounds("88:88", 0, 0, &x, &y, &cwidth, &cheight); + tftd.setFont(); + fg = fgcolor; + bg = bgcolor; + swidth = tftd.width(); + _canvas = new GFXcanvas1(swidth, cheight + 3); + _canvas->setFont(font); + _canvas->setTextWrap(false); + _canvas->setTextColor(WHITE); + uint16_t header = TFT_FRAMEWDT + 4 * TFT_LINEHGHT; + uint16_t footer = TFT_FRAMEWDT * 2 + TFT_LINEHGHT + 5; + clockY = header + (tftd.height() - header - footer) / 2 - cheight / 2; + return cheight; + } + void print(const char* timestr) { + _canvas->fillScreen(BLACK); + _canvas->getTextBounds(timestr, 0, 0, &x, &y, &cwidth, &cheight); + _canvas->setCursor((swidth - cwidth) / 2 - 4, cheight); + _canvas->print(timestr); + _dsp->drawBitmap(0, clockY , _canvas->getBuffer(), swidth, cheight + 3, fg, bg); + } + private: + int16_t x, y; + uint16_t cwidth, cheight, fg, bg, clockY, swidth; + GFXcanvas1 *_canvas; + Adafruit_ST7735 *_dsp; +}; + +GFXClock gclock; + +DisplayST7735::DisplayST7735(): Adafruit_ST7735(&SPI, TFT_CS, TFT_DC, TFT_RST) { + +} + +char* DisplayST7735::utf8Rus(const char* str, bool uppercase) { + int index = 0; + static char strn[BUFLEN]; + bool E = false; + strlcpy(strn, str, BUFLEN); + if (uppercase) { + bool next = false; + for (char *iter = strn; *iter != '\0'; ++iter) + { + if (E) { + E = false; + continue; + } + byte rus = (byte) * iter; + if (rus == 208 && (byte) * (iter + 1) == 129) { // ёКостыли + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (rus == 209 && (byte) * (iter + 1) == 145) { + *iter = (char)209; + *(iter + 1) = (char)145; + E = true; + continue; + } + if (next) { + if (rus >= 128 && rus <= 143) *iter = (char)(rus + 32); + if (rus >= 176 && rus <= 191) *iter = (char)(rus - 32); + next = false; + } + if (rus == 208) next = true; + if (rus == 209) { + *iter = (char)208; + next = true; + } + *iter = toupper(*iter); + } + } + while (strn[index]) + { + if (strn[index] >= 0xBF) + { + switch (strn[index]) { + case 0xD0: { + if (strn[index + 1] == 0x81) { + strn[index] = 0xA8; + break; + } + if (strn[index + 1] >= 0x90 && strn[index + 1] <= 0xBF) strn[index] = strn[index + 1] + 0x30; + break; + } + case 0xD1: { + if (strn[index + 1] == 0x91) { + //strn[index] = 0xB7; + strn[index] = 0xB8; + break; + } + if (strn[index + 1] >= 0x80 && strn[index + 1] <= 0x8F) strn[index] = strn[index + 1] + 0x70; + break; + } + } + int sind = index + 2; + while (strn[sind]) { + strn[sind - 1] = strn[sind]; + sind++; + } + strn[sind - 1] = 0; + } + index++; + } + return strn; +} + +void DisplayST7735::apScreen() { + setTextSize(1); + setTextColor(TFT_FG, TFT_BG); + setCursor(TFT_FRAMEWDT, TFT_FRAMEWDT + 2 * TFT_LINEHGHT); + print("AP NAME: "); + print(apSsid); + setCursor(TFT_FRAMEWDT, TFT_FRAMEWDT + 3 * TFT_LINEHGHT); + print("PASSWORD: "); + print(apPassword); + setTextColor(SILVER, TFT_BG); + setCursor(TFT_FRAMEWDT, 107); + print("SETTINGS PAGE ON: "); + setCursor(TFT_FRAMEWDT, 117); + print("http://"); + print(WiFi.softAPIP().toString().c_str()); + print("/"); +} + +void DisplayST7735::initD(uint16_t &screenwidth, uint16_t &screenheight) { + initR(DTYPE); + cp437(true); + fillScreen(TFT_BG); + setRotation(TFT_ROTATE); + setTextWrap(false); + screenwidth = width(); + screenheight = height(); + swidth = screenwidth; + sheight = screenheight; + gclock.init(dsp, &DS_DIGI28pt7b, TFT_LOGO, BLACK); +} + +void DisplayST7735::drawLogo() { + drawRGBBitmap((swidth - 99) / 2, 18, bootlogo2, 99, 64); +} + +// http://greekgeeks.net/#maker-tools_convertColor +#define CLR_ITEM1 0x52AA +#define CLR_ITEM2 0x39C7 +#define CLR_ITEM3 0x18E3 + +void DisplayST7735::drawPlaylist(uint16_t currentItem, char* currentItemText) { + for (byte i = 0; i < PLMITEMS; i++) { + plMenu[i][0] = '\0'; + } + config.fillPlMenu(plMenu, currentItem - 3, PLMITEMS); + setTextSize(2); + int yStart = (sheight / 2 - PLMITEMHEIGHT / 2) - PLMITEMHEIGHT * (PLMITEMS - 1) / 2 + 3; + fillRect(0, (sheight / 2 - PLMITEMHEIGHT / 2) - 1, swidth, PLMITEMHEIGHT + 2, TFT_LOGO); + for (byte i = 0; i < PLMITEMS; i++) { + if (abs(i - 3) == 3) setTextColor(CLR_ITEM3, TFT_BG); + if (abs(i - 3) == 2) setTextColor(CLR_ITEM2, TFT_BG); + if (abs(i - 3) == 1) setTextColor(CLR_ITEM1, TFT_BG); + if (i == 3) { + strlcpy(currentItemText, plMenu[i], PLMITEMLENGHT - 1); + } else { + setCursor(TFT_FRAMEWDT, yStart + i * PLMITEMHEIGHT); + print(utf8Rus(plMenu[i], true)); + } + } +} + +void DisplayST7735::clearDsp() { + fillScreen(TFT_BG); +} + +void DisplayST7735::drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg) { + fillRect(0, texttop, TFT_FRAMEWDT, textheight, bg); + fillRect(swidth - TFT_FRAMEWDT, texttop, TFT_FRAMEWDT, textheight, bg); +} + +void DisplayST7735::getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth) { + int16_t x1, y1; + uint16_t w, h; + setTextSize(textsize); + getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + tWidth = w; + tHeight = h; + getTextBounds(separator, 0, 0, &x1, &y1, &w, &h); + sWidth = w; +} + +void DisplayST7735::clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg) { + fillRect(0, texttop, swidth, textheight, bg); +} + +void DisplayST7735::centerText(const char* text, byte y, uint16_t fg, uint16_t bg) { + int16_t x1, y1; + uint16_t w, h; + const char* txt = text; + getTextBounds(txt, 0, 0, &x1, &y1, &w, &h); + setTextColor(fg); + setCursor((swidth - w) / 2, y); + fillRect(0, y, swidth, h, bg); + print(txt); +} + +void DisplayST7735::rightText(const char* text, byte y, uint16_t fg, uint16_t bg) { + int16_t x1, y1; + uint16_t w, h; + getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + setTextColor(fg); + setCursor(swidth - w - TFT_FRAMEWDT, y); + fillRect(swidth - w - TFT_FRAMEWDT, y, w, h, bg); + print(text); +} + +void DisplayST7735::displayHeapForDebug() { + int16_t vTop = sheight - TFT_FRAMEWDT * 2 - TFT_LINEHGHT * 2 - 2; + setTextSize(1); + setTextColor(DARK_GRAY, TFT_BG); + setCursor(TFT_FRAMEWDT, vTop); + fillRect(TFT_FRAMEWDT, vTop, swidth - TFT_FRAMEWDT / 2, 7, TFT_BG); + print(ESP.getFreeHeap()); + print(" / "); + print(ESP.getMaxAllocHeap()); + + // audio buffer; + fillRect(0, sheight - 2, swidth, 2, TFT_BG); + int astored = player.inBufferFilled(); + int afree = player.inBufferFree(); + int aprcnt = 100 * astored / (astored + afree); + byte sbw = map(aprcnt, 0, 100 , 0, swidth); + fillRect(0, sheight - 2, sbw, 2, SILVER); +} + +void DisplayST7735::printClock(const char* timestr) { + gclock.print(timestr); +} + +void DisplayST7735::drawVolumeBar(bool withNumber) { + int16_t vTop = sheight - TFT_FRAMEWDT * 2; + int16_t vWidth = swidth - TFT_FRAMEWDT - 4; + uint8_t ww = map(config.store.volume, 0, 254, 0, vWidth - 2); + fillRect(TFT_FRAMEWDT, vTop - 2, vWidth, 6, TFT_BG); + drawRect(TFT_FRAMEWDT, vTop - 2, vWidth, 6, TFT_LOGO); + fillRect(TFT_FRAMEWDT + 1, vTop - 1, ww, 5, TFT_LOGO); + if (withNumber) { + setTextSize(1); + setTextColor(TFT_FG); + setFont(&DS_DIGI28pt7b); + char volstr[4]; + uint16_t wv, hv; + int16_t x1, y1; + sprintf(volstr, "%d", config.store.volume); + getTextBounds(volstr, 0, 0, &x1, &y1, &wv, &hv); + fillRect(TFT_FRAMEWDT, 48, swidth - TFT_FRAMEWDT / 2, hv + 3, TFT_BG); + setCursor((swidth - wv) / 2, 48 + hv); + print(volstr); + setFont(); + } +} + +void DisplayST7735::frameTitle(const char* str) { + setTextSize(2); + centerText(str, TFT_FRAMEWDT, TFT_LOGO, TFT_BG); +} + +void DisplayST7735::rssi(const char* str) { + int16_t vTop = sheight - TFT_FRAMEWDT * 2 - TFT_LINEHGHT - 2; + setTextSize(1); + rightText(str, vTop, SILVER, TFT_BG); +} + +void DisplayST7735::ip(const char* str) { + int16_t vTop = sheight - TFT_FRAMEWDT * 2 - TFT_LINEHGHT - 2; + setTextSize(1); + setTextColor(SILVER, TFT_BG); + setCursor(4, vTop); + print(str); +} + +void DisplayST7735::set_TextSize(uint8_t s) { + setTextSize(s); +} + +void DisplayST7735::set_TextColor(uint16_t fg, uint16_t bg) { + setTextColor(fg, bg); +} + +void DisplayST7735::set_Cursor(int16_t x, int16_t y) { + setCursor(x, y); +} + +void DisplayST7735::printText(const char* txt) { + print(txt); +} diff --git a/yoRadio/displayST7735.h b/yoRadio/displayST7735.h new file mode 100644 index 0000000..6b78865 --- /dev/null +++ b/yoRadio/displayST7735.h @@ -0,0 +1,83 @@ +#ifndef displayST7735_h +#define displayST7735_h + +#include "Arduino.h" +#include +#include +#include "options.h" +#include "fonts/DS_DIGI28pt7b.h" + +#define TFT_ROTATE 3 +#define TFT_LINEHGHT 10 +#define TFT_FRAMEWDT 4 + +#define PLMITEMS 7 +#define PLMITEMLENGHT 40 +#define PLMITEMHEIGHT 22 + +class DisplayST7735: public Adafruit_ST7735 { + public: + DisplayST7735(); + char plMenu[PLMITEMS][PLMITEMLENGHT]; + uint16_t clockY; + void initD(uint16_t &screenwidth, uint16_t &screenheight); + void apScreen(); + void drawLogo(); + void clearDsp(); + void centerText(const char* text, byte y, uint16_t fg, uint16_t bg); + void rightText(const char* text, byte y, uint16_t fg, uint16_t bg); + void set_TextSize(uint8_t s); + void set_TextColor(uint16_t fg, uint16_t bg); + void set_Cursor(int16_t x, int16_t y); + void printText(const char* txt); + void printClock(const char* timestr); + void displayHeapForDebug(); + void drawVolumeBar(bool withNumber); + char* utf8Rus(const char* str, bool uppercase); + void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); + void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); + void clearScroll(uint16_t texttop, uint16_t textheight, uint16_t bg); + void frameTitle(const char* str); + void rssi(const char* str); + void ip(const char* str); + void drawPlaylist(uint16_t currentItem, char* currentItemText); + private: + uint16_t swidth, sheight; + +}; + +extern DisplayST7735 dsp; + +/* + * TFT COLORS + */ +#define BLACK 0x0000 +#define BLUE 0x001F +#define RED 0xF800 +#define GREEN 0x07E0 +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#define WHITE 0xFFFF +#define GRAY 0x7BEF +#define DARK_GRAY 0x2945 +#define LIGHT_GRAY 0xC618 +#define LIME 0x87E0 +#define AQUA 0x5D1C +#define CYAN 0x07FF +#define DARK_CYAN 0x03EF +#define ORANGE 0xFCA0 +#define PINK 0xF97F +#define BROWN 0x8200 +#define VIOLET 0x9199 +#define SILVER 0xA510 +#define GOLD 0xA508 +#define NAVY 0x000F +#define MAROON 0x7800 +#define PURPLE 0x780F +#define OLIVE 0x7BE0 + +#define TFT_BG BLACK +#define TFT_FG WHITE +#define TFT_LOGO 0xE68B // 224, 209, 92 + +#endif diff --git a/yoRadio/fonts/DS_DIGI28pt7b.h b/yoRadio/fonts/DS_DIGI28pt7b.h new file mode 100644 index 0000000..f29219d --- /dev/null +++ b/yoRadio/fonts/DS_DIGI28pt7b.h @@ -0,0 +1,109 @@ +const uint8_t DS_DIGI28pt7bBitmaps[] PROGMEM = { + 0x13, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x20, 0x27, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, 0x31, 0x00, 0xFF, 0xFF, 0x7F, 0xFF, 0xF5, 0xFF, + 0xFF, 0x77, 0xFF, 0xF7, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x3F, + 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, + 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFC, + 0x00, 0x07, 0x40, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x05, 0xC0, 0x00, + 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, + 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, + 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFD, 0xFF, 0xF7, 0xDF, 0xFF, 0xDD, 0xFF, + 0xFF, 0x5F, 0xFF, 0xFC, 0x00, 0x01, 0x37, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0x20, 0x27, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x31, 0x00, 0x7F, + 0xFF, 0xF1, 0xFF, 0xFF, 0x47, 0xFF, 0xF6, 0x1F, 0xFF, 0x70, 0x00, 0x07, + 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, + 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, + 0x00, 0x01, 0xE0, 0x00, 0x07, 0x0F, 0xFF, 0x90, 0xFF, 0xFE, 0x17, 0xFF, + 0xF1, 0xDF, 0xFF, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, + 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, + 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1D, 0xFF, 0xF0, 0xDF, + 0xFF, 0xC5, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, + 0xFD, 0x3F, 0xFF, 0xB1, 0xFF, 0xF7, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, + 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x07, + 0x0F, 0xFF, 0x21, 0xFF, 0xF8, 0x1F, 0xFF, 0xA0, 0xFF, 0xF7, 0x00, 0x00, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, + 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF1, 0xFF, 0xF7, 0x3F, 0xFF, 0xB7, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x70, 0x00, 0x07, 0xC0, 0x00, 0x7F, 0x00, + 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, + 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, + 0x3F, 0xC0, 0x01, 0xFC, 0x00, 0x07, 0x4F, 0xFF, 0x90, 0xFF, 0xFE, 0x07, + 0xFF, 0xF4, 0x1F, 0xFF, 0x70, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, + 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, + 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x07, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x7F, 0xFF, 0xF5, 0xFF, 0xFF, 0x37, + 0xFF, 0xF1, 0xDF, 0xFF, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, + 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, + 0x00, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1C, 0x00, 0x00, + 0x4F, 0xFF, 0x80, 0xFF, 0xFE, 0x07, 0xFF, 0xF4, 0x1F, 0xFF, 0x70, 0x00, + 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, 0x00, 0x78, + 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x80, 0x00, + 0x3C, 0x00, 0x01, 0xE1, 0xFF, 0xF7, 0x1F, 0xFF, 0xD9, 0xFF, 0xFF, 0x5F, + 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF5, 0xFF, 0xFF, 0x37, 0xFF, 0xF1, 0xDF, + 0xFF, 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, + 0xF0, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, 0x0F, 0x00, + 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1C, 0x00, 0x00, 0x4F, 0xFF, 0x80, + 0xFF, 0xFE, 0x17, 0xFF, 0xF5, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, 0x00, + 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, 0xFC, + 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, + 0xFD, 0xFF, 0xF7, 0xDF, 0xFF, 0xDD, 0xFF, 0xFF, 0x5F, 0xFF, 0xFC, 0x00, + 0xFF, 0xFF, 0xE7, 0xFF, 0xFD, 0x3F, 0xFF, 0xB1, 0xFF, 0xF7, 0x00, 0x00, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, + 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF0, 0x00, 0x07, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x07, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, + 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x07, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x7F, 0xFF, 0xF5, 0xFF, 0xFF, 0x77, 0xFF, 0xF7, 0xDF, 0xFF, 0x7F, + 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, + 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, + 0x00, 0x3F, 0xC0, 0x01, 0xFC, 0x00, 0x07, 0x4F, 0xFF, 0x90, 0xFF, 0xFE, + 0x17, 0xFF, 0xF5, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, + 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1F, + 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, 0x01, 0xFD, 0xFF, + 0xF7, 0xDF, 0xFF, 0xDD, 0xFF, 0xFF, 0x5F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, + 0xF5, 0xFF, 0xFF, 0x77, 0xFF, 0xF7, 0xDF, 0xFF, 0x7F, 0x00, 0x07, 0xF8, + 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0x80, 0x03, + 0xFC, 0x00, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x3F, 0xC0, + 0x01, 0xFC, 0x00, 0x07, 0x4F, 0xFF, 0x90, 0xFF, 0xFE, 0x07, 0xFF, 0xF4, + 0x1F, 0xFF, 0x70, 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE0, 0x00, + 0x0F, 0x00, 0x00, 0x78, 0x00, 0x03, 0xC0, 0x00, 0x1E, 0x00, 0x00, 0xF0, + 0x00, 0x07, 0x80, 0x00, 0x3C, 0x00, 0x01, 0xE1, 0xFF, 0xF7, 0x1F, 0xFF, + 0xD9, 0xFF, 0xFF, 0x5F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00 +}; + +const GFXglyph DS_DIGI28pt7bGlyphs[] PROGMEM = { + { 0, 0, 0, 12, 0, 1 }, // 0x20 ' ' + { 0, 0, 0, 0, 0, 0 }, // 0x21 '!' + { 0, 0, 0, 0, 0, 0 }, // 0x22 '"' + { 0, 0, 0, 0, 0, 0 }, // 0x23 '#' + { 0, 0, 0, 0, 0, 0 }, // 0x24 '$' + { 0, 0, 0, 0, 0, 0 }, // 0x25 '%' + { 0, 0, 0, 0, 0, 0 }, // 0x26 '&' + { 0, 0, 0, 0, 0, 0 }, // 0x27 ''' + { 0, 0, 0, 0, 0, 0 }, // 0x28 '(' + { 0, 0, 0, 0, 0, 0 }, // 0x29 ')' + { 0, 0, 0, 0, 0, 0 }, // 0x2A '*' + { 0, 0, 0, 0, 0, 0 }, // 0x2B '+' + { 0, 0, 0, 0, 0, 0 }, // 0x2C ',' + { 0, 0, 0, 0, 0, 0 }, // 0x2D '-' + { 0, 0, 0, 0, 0, 0 }, // 0x2E '.' + { 0, 0, 0, 0, 0, 0 }, // 0x2F '/' + { 20, 21, 35, 27, 3, -34 }, // 0x30 '0' + { 113, 4, 35, 14, 5, -34 }, // 0x31 '1' + { 131, 21, 35, 27, 3, -34 }, // 0x32 '2' + { 224, 20, 35, 27, 4, -34 }, // 0x33 '3' + { 312, 21, 34, 27, 3, -34 }, // 0x34 '4' + { 402, 21, 35, 27, 3, -34 }, // 0x35 '5' + { 495, 21, 35, 27, 3, -34 }, // 0x36 '6' + { 588, 20, 34, 27, 4, -34 }, // 0x37 '7' + { 673, 21, 35, 27, 3, -34 }, // 0x38 '8' + { 766, 21, 35, 27, 3, -34 }, // 0x39 '9' + { 859, 4, 29, 12, 4, -28 } // 0x3A ':' +}; + +const GFXfont DS_DIGI28pt7b PROGMEM = { + (uint8_t *)DS_DIGI28pt7bBitmaps, + (GFXglyph *)DS_DIGI28pt7bGlyphs, 0x20, 0x3A, 55 }; diff --git a/yoRadio/fonts/bootlogo.h b/yoRadio/fonts/bootlogo.h new file mode 100644 index 0000000..ddd1cef --- /dev/null +++ b/yoRadio/fonts/bootlogo.h @@ -0,0 +1,157 @@ +#ifndef bootlogo_h +#define bootlogo_h + + +/******************************************************************************* +* generated by lcd-image-converter rev.030b30d from 2019-03-17 01:38:34 +0500 +* image +* filename: unsaved +* name: bootlogo +* +* preset name: Color R5G6B5 +* data block size: 16 bit(s), uint16_t +* RLE compression enabled: no +* conversion type: Color, not_used not_used +* split to rows: yes +* bits per pixel: 16 +* +* preprocess: +* main scan direction: top_to_bottom +* line scan direction: forward +* inverse: no +*******************************************************************************/ + +#include + +static const uint16_t bootlogo2[6336] PROGMEM = { + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙░▒▓▓▓▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙▓▓▓▓▓▒▒▒▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▓▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙▓▓▓▓▓▓▓▓▒▒▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▓▒▒▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙░▓▓▓▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▒▒▒▓▓▓▓▓▓∙∙∙∙∙∙∙∙▓▓▓▓▓▓▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▓▒▒▒▓▓▓░∙∙∙∙∙∙∙∙∙▒▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙▓▓▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▓▓▓▓▓▓▓▓▓▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░░░∙∙░▓▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙░░▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙░∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙░∙∙∙▓▓░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙∙∙░∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙░∙░█▓▓▒░░▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙░∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙░∙∙█▓▓▓▒░░▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙∙░∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙░∙∙▓▓▓▓▓▒░░▒▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒∙∙∙∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙░░∙▓▓▓▓▓▓░░░▒▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░∙∙∙∙∙∙░∙∙∙∙∙░░∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙░∙░█▓▓▓▓▓░░▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒░░∙∙∙∙∙░░∙∙∙∙░░∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙░░∙▓▓▓▓▓▓▒░░▒▓▓▓▓▓▓▓▒▒▒▓▓▓▒▒▒▒▒░▒▒▒▒░░▒∙∙∙∙░░∙∙∙∙░░░∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░∙░█████▓░░▒▒▓▓▓█░∙∙∙∙∙∙∙▒▓▓▒▒░▒▒▒▒░░░░▒∙∙∙░░∙∙∙∙░░∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙░░∙▓█████▒░▒▒▓▓▓▓∙∙∙∙∙∙∙∙∙∙░▓▒░▒▒▒▒▒░░░░▓░∙░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░∙░█████▓░▒▒▓▓▓█∙∙∙∙∙∙░∙∙∙∙∙░░▒▒▒▒▒░░░░▒▒░∙░░∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░∙▒█████▒▒▒▒▓▓█░∙∙∙∙∙░∙∙∙∙∙░░▒▒▒▒▓▒░░░░▒▒░░░∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░░∙▓████▒▒▓▓▓██▓∙░∙∙∙░░∙∙∙∙∙░░░▒▓▓▓░░░░▒▒▒▒░░∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░∙∙███▓▒▒▓▓▓██▓▒∙∙∙∙∙░∙∙∙∙∙░░∙∙▓▓▓▒░░░▒▒▒▒▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░░∙░▒▒▒▓▓▓▓▓██▓▒░∙∙∙∙░░∙∙∙∙░░░∙∙▓▓▓▒░░░▒▒▒▒▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░∙∙▓▓▓▓▓▓▓███▓░▓▒▒▒▒░░░░▒▒░░░░░░▓▓▒▒░▒▒▒▒▒▒▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙░░░∙∙▓▓▓▓▓▓███▓░▒▒▓██▒░▒▒▓▓▓▓░░▒▒▓▓▒▒░░▒▒▒▒▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙░░∙∙∙▓▓▓▓████▓░░▒▓▓█▓░░▒▓▓▓▓▓░░▒▓▓▓▒▒░▒▒▒▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙░░░∙∙∙▓▓▓▓▓▓█▓░░▒▒▓██▒░▒▒▓█▒▓▓░▒▒▓▓▒▓░░▒▒▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙░░∙∙∙∙▓▓▓▓▓▓▓▒░░▒▓▓█▓░░▒▓█▓▓▓▓░▒▓▓▓▓▒░▒▒▒▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙▓▓▓▓▓▓▒░░▒▒▓██▒░░▒▓▓▓▓██▒▒▒▓▓▓▒░▒▒▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙░░∙∙∙∙∙▓▓▓▓▓▓▒░░▒▓▓█▓▒░▒▒▓▓▓▓███▓▓▓▓▒░▒▒▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙░░∙∙∙∙∙▓▓▓▓▓▓▒░▒▒▓▓▓▓▒░▒▒▓▓▓██████▓▓▒░▒▒▓▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙░░∙∙∙∙∙∙▓▓▓▓▓▓▒░▒▒▓▓▓▓▓░▒▓▓▓███████▓█▒▒▓▓██████▓█∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙▓▓▓▓▓▓▒░▒▒▓▒▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙▒▓▒▓▓▓▒░▒▒▒▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░∙∙∙∙∙∙∙∙░▓▒▒▒▓▒▒▒▒▒▓▓▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙▓▒▒▒▒▒▒▒▒▒▓▓▓∙∙∙∙∙∙∙∙∙░∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙▓▒▒▒▒▒▒▒▒▓▓▓▓▓∙∙∙∙∙∙∙░∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓∙∙∙∙∙░∙∙∙∙░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▓▒▒▒▒▒▒▒▒▒▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙█∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▓▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓░░∙∙∙∙░∙∙∙∙░░▒▓███░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓░▓▓▓▓▒▓▓▓█████████▓∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▓▒▒▓▓▓▓▓▓▓▓▓████∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓██∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓█∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙░░░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓█░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▒∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + // ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙░░▒▒▒▒▒▒▒▒▒▒▒▒░░∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3183, 0xace9, 0xde4a, 0xe68a, 0xde6a, 0xd609, 0x9427, 0x2101, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0861, 0x5a84, 0xb4e7, 0xcd46, 0xc546, 0xc526, 0xac66, 0x5202, 0x0840, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9c68, 0xde4a, 0xde4a, 0xde4a, 0xde4a, 0xde4a, 0xde2a, 0xde2a, 0xde4a, 0x7345, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0840, 0xc568, 0xcd67, 0xc547, 0xc527, 0xbd06, 0xbce6, 0xbce6, 0xbce6, 0xac85, 0x0840, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7346, 0xde29, 0xd5e9, 0xd60a, 0xd62a, 0xde2a, 0xde4a, 0xde4a, 0xde2a, 0xd62a, 0xde6a, 0x4a23, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1081, 0xcdc9, 0xcda8, 0xcd68, 0xc567, 0xc547, 0xc527, 0xc527, 0xbd06, 0xbce6, 0xbce6, 0xb4c6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0861, 0xd5c9, 0xd5c9, 0xd5c9, 0xd5e9, 0xd5ea, 0xd60a, 0xd62a, 0xde2a, 0xde4a, 0xde4a, 0xde2a, 0xd609, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7b86, 0xde09, 0xcda8, 0xcda8, 0xcd88, 0xcd68, 0xc567, 0xc547, 0xc527, 0xc527, 0xbd06, 0xc546, 0x7324, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8387, 0xd5e8, 0xcd88, 0xcda9, 0xd5c9, 0xd5c9, 0xd5e9, 0xd60a, 0xd60a, 0xd62a, 0xde4a, 0xde4a, 0xe68a, 0x3982, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xcda9, 0xd5e9, 0xd5e9, 0xcdc9, 0xcda8, 0xcda8, 0xcd88, 0xc568, 0xc567, 0xc547, 0xc527, 0xbd07, 0xaca5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xac87, 0xcd88, 0xcd68, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5c9, 0xd5e9, 0xd60a, 0xd60a, 0xd62a, 0xe68a, 0x8be6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde2a, 0xd609, 0xd5e9, 0xd5e9, 0xd5c9, 0xcdc9, 0xcda8, 0xcda8, 0xcd88, 0xcd68, 0xc547, 0xc547, 0xcd67, 0x0820, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb4c6, 0xc567, 0xc548, 0xcd68, 0xcd68, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5e9, 0xd5e9, 0xd60a, 0xde4a, 0x8c07, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde4a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xd5e9, 0xd5e9, 0xcdc9, 0xcda8, 0xcda8, 0xcd88, 0xc567, 0xcd88, 0x0820, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa468, 0xc547, 0xc527, 0xc547, 0xc548, 0xcd68, 0xcd68, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5e9, 0xde4a, 0x83a6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde4a, 0xd62a, 0xd62a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xd5e9, 0xd5c9, 0xcdc8, 0xcda8, 0xcd88, 0xcda8, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6b06, 0xcd67, 0xbd07, 0xbd07, 0xc527, 0xc547, 0xc548, 0xcd68, 0xcd88, 0xcd88, 0xcda9, 0xcda9, 0xde49, 0x18a0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc5aa, 0xde4a, 0xd62a, 0xd62a, 0xd60a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xd5e9, 0xd5c9, 0xcdc8, 0xace7, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xcd88, 0xbce6, 0xbce6, 0xbd07, 0xbd27, 0xc527, 0xc547, 0xc548, 0xcd68, 0xcd88, 0xcda8, 0xbd48, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7346, 0xe6ab, 0xde4a, 0xde2a, 0xd62a, 0xd62a, 0xd60a, 0xd609, 0xd609, 0xd5e9, 0xd5e9, 0xde49, 0x62e4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4a03, 0xcd86, 0xb4c6, 0xb4c6, 0xbce7, 0xbd07, 0xbd27, 0xc527, 0xc548, 0xcd68, 0xe629, 0x3162, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd60c, 0xe68a, 0xde4a, 0xde4a, 0xde4a, 0xd62a, 0xd62a, 0xd60a, 0xd609, 0xde4a, 0xb508, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5a85, 0xcd67, 0xc526, 0xbce6, 0xb4c6, 0xbce7, 0xc527, 0xcda7, 0xc588, 0x41c3, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xace9, 0xe68b, 0xde8a, 0xde4a, 0xde4a, 0xde4a, 0xde6a, 0xe68a, 0x9c67, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9407, 0xcd87, 0xd5a7, 0xd5c7, 0xcda8, 0x7325, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2943, 0xcdca, 0xe6cb, 0xe6cb, 0xeecb, 0xc5aa, 0x2922, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1000, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x8000, 0x8800, 0x9800, 0x7800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7000, 0xb000, 0x2000, 0x0000, 0x1800, 0xe000, 0x1000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x2800, 0x3800, 0x4800, 0x4800, 0x4800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x2963, 0x5264, 0x8c06, 0xcdc9, 0xcdc8, 0xcda7, 0xcd87, 0xcda7, 0xd5c8, 0xd5e8, 0xde09, 0xcdca, 0x8be6, 0x5aa4, 0x2963, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xa800, 0xd000, 0x1800, 0x0000, 0x0000, 0x1000, 0xe800, 0x1800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x5800, 0xa000, 0xa800, 0x8000, 0x6000, 0x5800, 0x8800, 0xd000, 0xe000, 0xc000, 0x3000, 0x0000, 0x4a25, 0xde2a, 0xde07, 0xc546, 0xb4c5, 0xaca5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xaca5, 0xb4c6, 0xb4c6, 0xbd07, 0xcd87, 0xe648, 0xde4a, 0x4204, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xc000, 0xe800, 0x2000, 0x0000, 0x0000, 0x0000, 0x2800, 0xd000, 0x0800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1000, 0x5800, 0x7800, 0x7000, 0x4000, 0x1000, 0x0000, 0x0000, 0x0000, 0x2000, 0x9800, 0x1000, 0x2800, 0xb000, 0xe800, 0xd365, 0xcda7, 0xbd06, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac65, 0xac85, 0xaca5, 0xb4a6, 0xb4c6, 0xb4e6, 0xc547, 0xd5e8, 0xc5a9, 0x5a85, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0xe800, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x8000, 0x0000, 0x0000, 0x5000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x0000, 0x0000, 0x0800, 0x5800, 0x8800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1000, 0xc000, 0x1800, 0x0000, 0x0020, 0xb4a9, 0xf8a0, 0xf040, 0xbb84, 0xb4a6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac65, 0xac85, 0xaca5, 0xb4a6, 0xb4c6, 0xbce6, 0xbd07, 0xde49, 0x9c88, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0xf800, 0xd800, 0x1000, 0x0000, 0x0000, 0x0000, 0x9800, 0xf800, 0xb800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa000, 0x2000, 0x0000, 0x4800, 0x2000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x8000, 0x8800, 0x7800, 0x7800, 0x8800, 0xa000, 0x7000, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0x4800, 0x0000, 0x3184, 0xde4b, 0xd5c8, 0xd962, 0xf800, 0xd1c2, 0xa486, 0xbce6, 0xbce6, 0xbce6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4c6, 0xbce6, 0xcda8, 0xcdc9, 0x0860, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x8800, 0x5800, 0x0000, 0x0000, 0x0000, 0x1000, 0xe800, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x5800, 0xf800, 0xf000, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x9000, 0x0000, 0x5000, 0x3800, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x4000, 0x9000, 0x6800, 0x1000, 0x0000, 0x0000, 0x3000, 0x8000, 0x5800, 0xa800, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0xb000, 0x0000, 0x39c4, 0xef0c, 0xd609, 0xcda8, 0xd243, 0xf800, 0xe8a1, 0xa405, 0xbce7, 0xbd27, 0xbd06, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xaca5, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4c6, 0xc547, 0xe68b, 0x0860, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf800, 0xe800, 0x1000, 0x0000, 0x0000, 0x0000, 0x7800, 0xf800, 0xb000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf000, 0xf800, 0xa000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0x3000, 0x5800, 0x4800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x9000, 0x3800, 0x0800, 0x0000, 0x0000, 0x0000, 0x7000, 0x4000, 0x0800, 0x0000, 0xa800, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0xe800, 0x2000, 0x3184, 0xef0c, 0xde2a, 0xd609, 0xd5e9, 0xda63, 0xf800, 0xf060, 0x9bc5, 0xbd07, 0xc547, 0xc527, 0xc507, 0xbd06, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xaca5, 0xac85, 0xac85, 0xac65, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xc547, 0xc5a9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc000, 0xf800, 0x8000, 0x0000, 0x0000, 0x0000, 0x1800, 0xf000, 0xf800, 0x3800, 0x0000, 0x0000, 0x1000, 0xa800, 0xf800, 0xf800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0xc800, 0x8000, 0x2800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4800, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x7000, 0x2000, 0x0000, 0x0000, 0x0000, 0x8800, 0xb800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0xe800, 0x7800, 0x0881, 0xdeac, 0xde6b, 0xde4a, 0xd62a, 0xd609, 0xd9e3, 0xf800, 0xf080, 0x9bc6, 0xbd27, 0xc568, 0xc567, 0xc547, 0xc527, 0xbd06, 0xbd06, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xb4a6, 0xcd87, 0x8c07, 0x2000, 0x7800, 0x0800, 0x0000, 0x0000, 0x0000, 0x7000, 0xd000, 0xe800, 0x2000, 0x0000, 0x0000, 0x0000, 0xb800, 0xf800, 0x9800, 0x0000, 0x0000, 0x0000, 0x6000, 0x7000, 0xf800, 0xd800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0800, 0x9000, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x7000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x7000, 0xd800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0xe800, 0x1000, 0xb58b, 0xe68b, 0xde6b, 0xde6b, 0xde4a, 0xd60a, 0xe921, 0xf800, 0xe142, 0x9426, 0xbd68, 0xcda8, 0xcd88, 0xc568, 0xc547, 0xc547, 0xc527, 0xbd07, 0xbce6, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4a6, 0xb4a5, 0xb4a5, 0xac85, 0xac85, 0xac85, 0xac85, 0xac65, 0xac85, 0xac85, 0xeaa3, 0xf800, 0x6000, 0x0000, 0x0000, 0x0000, 0x4000, 0x8000, 0xf800, 0x7000, 0x0000, 0x0000, 0x0000, 0x4800, 0xf800, 0xe000, 0x2000, 0x0000, 0x0000, 0x4800, 0x2800, 0x8800, 0xf800, 0x9000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0x1800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x3800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7800, 0xe000, 0x1000, 0x0000, 0x0000, 0x0000, 0x5000, 0xf800, 0x8000, 0x5ac6, 0xeeec, 0xe68b, 0xe6ab, 0xe68b, 0xde8b, 0xddc9, 0xf040, 0xf800, 0xba84, 0x9467, 0xcda9, 0xd5e9, 0xcdc9, 0xcda8, 0xcda8, 0xd5c8, 0xd5c8, 0xd5c8, 0xcd87, 0xc547, 0xbce6, 0xbcc6, 0xbcc6, 0xb4c6, 0xb4c6, 0xb4a5, 0xb4a5, 0xb465, 0xcaa3, 0xd262, 0xc242, 0xb3a4, 0xb364, 0xf800, 0xf0a1, 0x1000, 0x0000, 0x0000, 0x3000, 0x6800, 0xf000, 0xd000, 0x0800, 0x0000, 0x0000, 0x1000, 0xd800, 0xf800, 0x8000, 0x0000, 0x0000, 0x1000, 0x4800, 0x0000, 0x8800, 0xf800, 0x5000, 0x0000, 0x0000, 0x0000, 0x7000, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0xd800, 0x0800, 0x0000, 0x0000, 0x1800, 0xe800, 0xe000, 0x2040, 0xce4c, 0xe6ac, 0xe6cc, 0xe6cc, 0xe6ab, 0xe6cc, 0xd3a6, 0xf800, 0xf080, 0x8ba6, 0xb508, 0xd609, 0xd609, 0xd609, 0xe6aa, 0xeecb, 0xcdeb, 0x9469, 0x7367, 0xace9, 0xd60b, 0xe669, 0xcd87, 0xbce6, 0xbce6, 0xbcc6, 0xb4c6, 0xbbe4, 0xe8e1, 0xc303, 0x9be4, 0x9b44, 0xbaa3, 0xc961, 0xf800, 0xd282, 0x10a1, 0x0000, 0x0800, 0x5000, 0xa000, 0xf800, 0x6800, 0x0000, 0x0000, 0x0000, 0xa000, 0xf800, 0xe000, 0x1800, 0x0000, 0x0000, 0x7800, 0x0800, 0x0000, 0x6800, 0xf800, 0x4800, 0x0000, 0x0800, 0x8800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x5000, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x3800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb800, 0xc000, 0x0000, 0x0000, 0x0000, 0x9000, 0xf800, 0x7800, 0x5ae6, 0xe6cc, 0xeeed, 0xef0c, 0xeeec, 0xeecc, 0xde6b, 0xf081, 0xf800, 0xc2c4, 0x9c67, 0xcdc9, 0xde4a, 0xe68b, 0xeeec, 0x5264, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x20e2, 0x7b86, 0xde29, 0xc506, 0xbd06, 0xd283, 0xf080, 0xb344, 0x93c5, 0x9c05, 0xaba4, 0xc1e2, 0xf800, 0xf040, 0x8b03, 0xa4a6, 0x0000, 0x6000, 0x6000, 0xf800, 0xc800, 0x1000, 0x0000, 0x0000, 0x4000, 0xe800, 0xf800, 0x7000, 0x0000, 0x0000, 0x5800, 0x1800, 0x0000, 0x0000, 0x1800, 0xe000, 0xa800, 0x7800, 0x7800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x1000, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xe800, 0x8800, 0x0000, 0x0000, 0x2800, 0xf800, 0xf000, 0x2000, 0xdeac, 0xef0d, 0xf72d, 0xef2d, 0xef0d, 0xef0c, 0xe3a6, 0xf800, 0xe1e3, 0x9427, 0xbd69, 0xde4b, 0xe6ab, 0xd62b, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x62e6, 0xe628, 0xda02, 0xf820, 0xb344, 0x8ba4, 0xa425, 0xb4a6, 0xbac3, 0xf020, 0xf800, 0xb9c2, 0x7303, 0xc566, 0x78e1, 0x3800, 0xd000, 0xf800, 0x4800, 0x0000, 0x0000, 0x1800, 0x9000, 0xd800, 0xd800, 0x1800, 0x0000, 0x3800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0x6800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x1000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4800, 0xf800, 0x4000, 0x0000, 0x0000, 0xc000, 0xf800, 0x9000, 0x5aa5, 0xef2d, 0xf72d, 0xf74e, 0xf74d, 0xf74d, 0xe62b, 0xf040, 0xf142, 0xa427, 0xad29, 0xde4b, 0xe6ac, 0xf72d, 0x0860, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x9800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0xc122, 0xf800, 0xc263, 0x8b85, 0xa445, 0xb4c6, 0xb4c6, 0xe0a0, 0xf800, 0xe860, 0x7aa3, 0x8ba4, 0xbce5, 0x7921, 0x3800, 0xf800, 0xc000, 0x0800, 0x0000, 0x0000, 0x7800, 0x2000, 0xe800, 0x9000, 0x0000, 0x3800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc000, 0xd000, 0x0800, 0x0000, 0x4000, 0xf800, 0xe800, 0x2000, 0x9cc9, 0xeeed, 0xf72d, 0xf74d, 0xf76e, 0xf6cc, 0xf102, 0xf162, 0xbca8, 0xad29, 0xd64b, 0xe6cc, 0xef2d, 0x4204, 0x0000, 0x1800, 0x0000, 0x0000, 0x8800, 0xf800, 0x5000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xf800, 0xf242, 0x8bc5, 0x9c46, 0xbce7, 0xbd07, 0xc344, 0xf800, 0xf800, 0xb982, 0x7b24, 0xa445, 0xc2c3, 0x7b24, 0xa000, 0xf800, 0x4800, 0x0000, 0x0000, 0x7000, 0x3000, 0x1000, 0xe000, 0x6000, 0x4800, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3000, 0xf800, 0x7800, 0x0000, 0x0000, 0xc000, 0xf800, 0xa000, 0x0000, 0xe6ad, 0xef0d, 0xf72d, 0xf74d, 0xeeed, 0xf183, 0xf284, 0xbd29, 0xb56a, 0xd66c, 0xef0d, 0xef0d, 0xde8c, 0x5820, 0x9000, 0x7000, 0x6000, 0x2000, 0xf000, 0xc800, 0x0800, 0x0000, 0x0000, 0x0000, 0x7000, 0xf800, 0xe000, 0x8ae5, 0xacc6, 0xbd07, 0xc547, 0xc527, 0xe8e1, 0xf800, 0xf040, 0x8ac3, 0x93e5, 0xbb44, 0xbbe5, 0xaca6, 0xd800, 0xb800, 0x0000, 0x0000, 0x5800, 0x3800, 0x0000, 0x0000, 0x7000, 0xb800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x7000, 0x2800, 0x0000, 0x0000, 0x0000, 0x3000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xe800, 0x2000, 0x0000, 0x5000, 0xf800, 0xf800, 0x3800, 0x28c2, 0xef0d, 0xef0d, 0xeeed, 0xf509, 0xf203, 0xebe7, 0xc5aa, 0xbdaa, 0xdeac, 0xef2d, 0xf74d, 0xee2b, 0xe263, 0x5800, 0x0000, 0x0000, 0x7000, 0x8800, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x4800, 0xf800, 0xf800, 0x8000, 0x1081, 0xd5e8, 0xcd88, 0xcda8, 0xcb85, 0xf040, 0xf800, 0xb9e2, 0x7b64, 0xb3e5, 0xcb04, 0xac65, 0xbcc6, 0xe000, 0x6800, 0x0000, 0x5000, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x1000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x7800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf800, 0x7800, 0x0000, 0x0800, 0xc800, 0xf800, 0xb000, 0x0000, 0xb9e4, 0xf345, 0xf366, 0xf224, 0xec07, 0xddeb, 0xc5aa, 0xce0b, 0xe6cc, 0xef2d, 0xf76e, 0xed6a, 0xf8e1, 0x8a44, 0x0000, 0x0000, 0x0000, 0x9000, 0xf000, 0xb800, 0x0800, 0x0000, 0x0000, 0x0800, 0xd800, 0xf800, 0xe000, 0x1000, 0x0000, 0xde4b, 0xcdc9, 0xcd07, 0xda03, 0xf040, 0xf060, 0x8304, 0x9be5, 0xd263, 0xb4a6, 0xac66, 0xcd66, 0xc000, 0x8800, 0x8000, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x3000, 0x2000, 0x0000, 0x0000, 0x0000, 0x4000, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc800, 0xd000, 0x0800, 0x0000, 0x3000, 0xf800, 0xf000, 0x4000, 0x0000, 0xcdcb, 0xde0b, 0xd58a, 0xd60b, 0xce2b, 0xce0b, 0xde6c, 0xeeec, 0xef0d, 0xf72d, 0xec88, 0xf840, 0xf4e8, 0x83e7, 0x7ba7, 0x83e7, 0x8b66, 0xf000, 0xf820, 0x9a63, 0x6b25, 0x7ba6, 0x7ba6, 0xb9c2, 0xf0a1, 0xf800, 0xb9c2, 0x5ae4, 0x7345, 0xe6ab, 0xd60a, 0xdb45, 0xd223, 0xf800, 0xc1e3, 0x93c5, 0xcae4, 0xbca6, 0xaca6, 0xb4e6, 0xd5a7, 0x48e1, 0x6800, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0xf800, 0x4800, 0x0000, 0x0000, 0xa800, 0xf800, 0xc000, 0x0000, 0x0000, 0xe6ac, 0xe68c, 0xde6b, 0xde6c, 0xe68c, 0xe6cc, 0xeeed, 0xef0d, 0xef2d, 0xed09, 0xf800, 0xea24, 0xad09, 0xce0b, 0xef0d, 0xf76e, 0xe326, 0xf800, 0xe922, 0x9c68, 0xc5ea, 0xe6cc, 0xe4a8, 0xdd89, 0xe860, 0xf840, 0xa365, 0xad08, 0xd62a, 0xde4a, 0xd3e6, 0xd3c6, 0xf020, 0xf080, 0x9b65, 0xc263, 0xcc86, 0xb4e7, 0xbd07, 0xc547, 0xd5a7, 0x7324, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2000, 0xf000, 0x8000, 0x0000, 0x0000, 0x3000, 0xf800, 0xf800, 0x5000, 0x0000, 0x0000, 0xeecc, 0xe68b, 0xe68c, 0xe6ac, 0xeecc, 0xeeec, 0xeeec, 0xef0d, 0xed09, 0xf820, 0xf8e1, 0xb488, 0xbd8a, 0xe6cc, 0xf72d, 0xee4c, 0xf040, 0xf800, 0xc386, 0xad49, 0xdeac, 0xe5ea, 0xec68, 0xcd69, 0xf800, 0xe8e1, 0x9c47, 0xc5ca, 0xde8b, 0xdcc8, 0xe427, 0xd305, 0xf800, 0xc264, 0xc263, 0xcc67, 0xbd48, 0xbd48, 0xc588, 0xcd88, 0xd5c8, 0x8bc6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xd000, 0xb000, 0x0800, 0x0000, 0x0000, 0x9800, 0xf800, 0xc000, 0x0800, 0x0000, 0x10a1, 0xeecb, 0xde4b, 0xe66b, 0xe68b, 0xe6ac, 0xeecc, 0xeecc, 0xe62b, 0xf0c1, 0xf800, 0xcb05, 0xa4c9, 0xd64c, 0xef0d, 0xf72d, 0xeb46, 0xf800, 0xe983, 0x9ca8, 0xce2b, 0xeecc, 0xeb86, 0xde8c, 0xd58a, 0xf000, 0xda03, 0xa4e8, 0xd64b, 0xe549, 0xe386, 0xcdca, 0xe901, 0xf840, 0xd1e3, 0xc3e6, 0xbd48, 0xc589, 0xcdc9, 0xcdc9, 0xcda8, 0xd5e8, 0x9406, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa800, 0xc000, 0x1800, 0x0000, 0x0000, 0x1000, 0xe800, 0xf800, 0x5000, 0x0000, 0x0000, 0x18c2, 0xe68a, 0xde0a, 0xde2a, 0xde4b, 0xe66b, 0xe68b, 0xe66b, 0xe962, 0xf800, 0xf0c1, 0x9c27, 0xc5aa, 0xe6cc, 0xef0d, 0xe60b, 0xf020, 0xf800, 0xbb46, 0xad49, 0xe70d, 0xec88, 0xee4c, 0xde8c, 0xe68c, 0xe8a1, 0xe224, 0xc58a, 0xe427, 0xec07, 0xd60a, 0xcc68, 0xf800, 0xe922, 0xa3a6, 0xad08, 0xc5a9, 0xd60a, 0xd60a, 0xd609, 0xd5e9, 0xde29, 0x9427, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xc800, 0x1800, 0x0000, 0x0000, 0x0000, 0x8000, 0xf800, 0xc000, 0x0800, 0x0000, 0x0000, 0x18e2, 0xe66a, 0xd5ea, 0xd60a, 0xde2a, 0xde2a, 0xde6b, 0xe3a6, 0xf800, 0xf800, 0xcac5, 0xa4c9, 0xde4b, 0xeeec, 0xeecc, 0xe9a3, 0xf800, 0xf0e1, 0xa448, 0xce0b, 0xe4e9, 0xed29, 0xde8c, 0xe6cd, 0xef2d, 0xe3a7, 0xe902, 0xea44, 0xe447, 0xd62b, 0xce0a, 0xe1a3, 0xf820, 0xb3c6, 0x9ca8, 0xcdca, 0xde4a, 0xde4a, 0xde4a, 0xd62a, 0xd60a, 0xde4a, 0x9427, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa000, 0xa800, 0x1000, 0x0000, 0x0000, 0x0000, 0x1800, 0xf000, 0xf800, 0x3800, 0x0000, 0x0000, 0x0000, 0x18c2, 0xe669, 0xcda9, 0xcdc9, 0xd5ea, 0xd60a, 0xd60a, 0xda23, 0xf800, 0xf0a1, 0x9bc6, 0xbd69, 0xe68c, 0xeeed, 0xe488, 0xdaa5, 0xf800, 0xcae5, 0xa509, 0xe5aa, 0xed09, 0xde8c, 0xdeac, 0xef2d, 0xf74d, 0xeeed, 0xcdcb, 0xbd6a, 0xcdeb, 0xde6b, 0xe325, 0xf800, 0xe1a3, 0x9468, 0xc5ca, 0xde6b, 0xe68b, 0xde6b, 0xde6a, 0xde4a, 0xde4a, 0xe68a, 0x8c06, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2000, 0xa800, 0x8000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x7800, 0xf800, 0x9000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0861, 0xde29, 0xc588, 0xcd89, 0xcda9, 0xd5c9, 0xd60a, 0xda23, 0xf800, 0xd1a3, 0x9427, 0xcdca, 0xe68b, 0xe509, 0xe468, 0xcb46, 0xf840, 0xac27, 0xcc68, 0xec07, 0xde6c, 0xde6c, 0xeeed, 0xf72d, 0xf72d, 0xf72d, 0xef0d, 0xe6ed, 0xe68c, 0xec68, 0xe326, 0xf800, 0xbb66, 0xad09, 0xde6b, 0xe6ac, 0xe6ab, 0xe68b, 0xe68b, 0xde6b, 0xde6a, 0xe6ab, 0x7345, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x4000, 0x0000, 0x0000, 0x0800, 0x7000, 0xb000, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0xe800, 0xe800, 0x2000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xde09, 0xc548, 0xc568, 0xc588, 0xcda9, 0xcdc9, 0xd9c3, 0xf800, 0xb284, 0xa4a8, 0xd5ea, 0xddaa, 0xe407, 0xde6b, 0xd529, 0xf0c1, 0xea64, 0xec27, 0xe68c, 0xde8c, 0xeeed, 0xf72d, 0xf74d, 0xf74e, 0xf74e, 0xf74e, 0xf72d, 0xf447, 0xeeed, 0xe962, 0xf1c3, 0xb529, 0xd64b, 0xef0d, 0xef0d, 0xef0c, 0xeeec, 0xeeec, 0xeecc, 0xe6cb, 0xef0c, 0x20e1, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4800, 0x6000, 0x6800, 0x6800, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9800, 0xf800, 0x5800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xd5e9, 0xc527, 0xbd27, 0xc548, 0xc568, 0xc588, 0xda03, 0xf800, 0xa305, 0xace8, 0xd508, 0xdb86, 0xe66b, 0x62e5, 0x4a24, 0x5162, 0x5142, 0x4a04, 0x4a24, 0x4a44, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x6265, 0x8962, 0x5224, 0x7a04, 0xf000, 0x6962, 0x39e3, 0x4a44, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x5265, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2800, 0xf800, 0xa000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xacea, 0xbd26, 0xbce7, 0xbd07, 0xbd27, 0xc548, 0xcb04, 0xf800, 0xbaa4, 0xcbe6, 0xdb05, 0xc589, 0xd60a, 0x7366, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x7800, 0x0800, 0x0000, 0xb000, 0xa800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0xd000, 0x1800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5aa6, 0xc526, 0xb4c6, 0xb4e6, 0xbce7, 0xbd07, 0xbca7, 0xca23, 0xd1a2, 0xcb45, 0xb4c8, 0xbd28, 0xcdc9, 0xb528, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x7000, 0x0000, 0x0000, 0x2800, 0xe800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5800, 0xf000, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc567, 0xac85, 0xaca5, 0xb4c6, 0xb4e6, 0xb4e7, 0xacc7, 0x9446, 0x9c66, 0xacc7, 0xc548, 0xcd89, 0xe68b, 0x2101, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0x9000, 0x0800, 0x0000, 0x0000, 0x9800, 0x7800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1800, 0xe000, 0x5000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc567, 0xa444, 0xa465, 0xac85, 0xaca6, 0xb4c6, 0xb4c6, 0xb4c6, 0xb4e7, 0xbd27, 0xc548, 0xc568, 0xcdc9, 0xe68b, 0x0840, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x9000, 0x1000, 0x0000, 0x0000, 0x3000, 0xd000, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xb000, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8385, 0xac84, 0xa424, 0xa445, 0xa465, 0xac85, 0xaca6, 0xb4c6, 0xb4e6, 0xbd07, 0xbd27, 0xc548, 0xc548, 0xd5e9, 0xc5a9, 0x18c1, 0x0000, 0x0000, 0x0000, 0x0800, 0xa800, 0x3000, 0x0000, 0x0000, 0x0000, 0xb000, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9000, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4a25, 0xc524, 0x9c04, 0xa424, 0xa444, 0xa445, 0xac65, 0xac85, 0xaca6, 0xb4c6, 0xb4e6, 0xbd07, 0xbd27, 0xc548, 0xcda8, 0xe6aa, 0x5a85, 0x0000, 0x0000, 0x7800, 0x4800, 0x0000, 0x0000, 0x0000, 0x3800, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x31a4, 0xe6ce, 0x10a1, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6800, 0x7000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xc569, 0x9c04, 0x9be4, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac65, 0xac85, 0xaca6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc568, 0xde09, 0xcdea, 0x8a23, 0xc8a1, 0x0800, 0x0000, 0x0000, 0x0800, 0xa800, 0x0800, 0x0000, 0x0000, 0x0020, 0x41e4, 0x6b26, 0x8408, 0xbd8b, 0xf74e, 0xf74e, 0xf76e, 0x62e5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x5800, 0xe800, 0xc800, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6000, 0x6800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2102, 0xb4a4, 0x93c3, 0x93e4, 0x9be4, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc507, 0xe8e1, 0xdcc7, 0xcdc9, 0xe66a, 0xe68b, 0xeb25, 0xd509, 0xcdca, 0xe68b, 0xef0d, 0xf72d, 0xf74d, 0xf72d, 0xef0d, 0xeeec, 0xeeec, 0xeeed, 0xf72d, 0xbd8b, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x2000, 0xf000, 0xf800, 0xf800, 0x2800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0800, 0x7800, 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xa466, 0x9c03, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c04, 0xa424, 0xa445, 0xa465, 0xac85, 0xac86, 0xb4a6, 0xb4e6, 0xbd07, 0xbbe5, 0xe901, 0x9c26, 0xaca7, 0xc5a9, 0xd3a5, 0xdbc6, 0xb528, 0xc589, 0xd60a, 0xde4a, 0xde4b, 0xde6b, 0xe68b, 0xe6ac, 0xeecc, 0xeecc, 0xeeec, 0xef0d, 0xef2e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x3800, 0xf800, 0xf800, 0xd800, 0x0800, 0x0000, 0x0000, 0x0000, 0x2800, 0x7800, 0x1800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18e2, 0xbce5, 0x93a3, 0x93a3, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c24, 0xa424, 0xa445, 0xa465, 0xac85, 0xaca5, 0xb4c6, 0xbac3, 0xe162, 0x93e5, 0xb4c7, 0xcbe6, 0xdae4, 0xb4e8, 0xb4e8, 0xcd89, 0xd5e9, 0xd60a, 0xde2a, 0xde2a, 0xde4b, 0xe66b, 0xe68b, 0xe6ac, 0xeecc, 0xeecc, 0xf76e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x2800, 0xf000, 0xf800, 0xe000, 0x3000, 0x1800, 0x5800, 0x8000, 0x3800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x39a3, 0xb463, 0x8b63, 0x8b83, 0x93a3, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c24, 0xa424, 0xa445, 0xa465, 0xaca5, 0xbaa3, 0xe8c1, 0xa364, 0xca83, 0xcb44, 0xaca7, 0xacc7, 0xbd48, 0xcd89, 0xcda9, 0xcdc9, 0xd5ea, 0xd60a, 0xde2a, 0xde2b, 0xde4b, 0xe66b, 0xe68b, 0xe6ac, 0xf74e, 0x2962, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x6800, 0xd000, 0xe000, 0xc800, 0x9800, 0x4800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7305, 0xac23, 0x8b62, 0x8b63, 0x8b83, 0x93a3, 0x93c3, 0x93c3, 0x9be4, 0x9c04, 0x9c24, 0xa444, 0xa445, 0xa3e4, 0xc981, 0xd161, 0xb344, 0xa466, 0xa466, 0xb4e7, 0xbd27, 0xc568, 0xc568, 0xcd89, 0xcda9, 0xd5c9, 0xd5ea, 0xd60a, 0xde2a, 0xde4b, 0xde4b, 0xe66b, 0xef0c, 0x5284, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0800, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5a64, 0xac23, 0x8b42, 0x8b42, 0x8b83, 0x8b83, 0x93a3, 0x93c3, 0x93c4, 0x9be4, 0x9c04, 0xa424, 0xa424, 0x93e4, 0x8ba4, 0x93e5, 0xa445, 0xaca6, 0xb4e6, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xc588, 0xcda9, 0xcda9, 0xd5c9, 0xd5ea, 0xd60a, 0xde2a, 0xde4b, 0xe6ab, 0x7366, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4a24, 0xac44, 0x9382, 0x8342, 0x8b62, 0x8b83, 0x8b83, 0x93a3, 0x93c3, 0x9be4, 0x9be4, 0x9c04, 0x9c04, 0x9c04, 0xa425, 0xa465, 0xac85, 0xaca6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xcd88, 0xcda9, 0xcda9, 0xd5c9, 0xd5ea, 0xd60a, 0xde4b, 0x9447, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0841, 0xa466, 0x9be2, 0x8b42, 0x8342, 0x8b62, 0x8b83, 0x9383, 0x93a3, 0x93c3, 0x9be4, 0x9be4, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xac85, 0xb4a6, 0xb4c6, 0xb4e7, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xcd88, 0xcda9, 0xcdc9, 0xd5e9, 0xd60a, 0xacc8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3982, 0x93c5, 0x9be3, 0x9382, 0x8342, 0x8b63, 0x8b83, 0x9383, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xaca5, 0xb4a6, 0xb4c6, 0xbce7, 0xbd07, 0xbd27, 0xc548, 0xc568, 0xd5c9, 0xde29, 0xde2a, 0x7325, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2942, 0x93c5, 0x9be3, 0x9bc3, 0x9382, 0x8b83, 0x9383, 0x93a3, 0x93a3, 0x93c3, 0x9be4, 0x9c04, 0x9c04, 0xa424, 0xa444, 0xa465, 0xac85, 0xaca5, 0xb4e6, 0xbd06, 0xc547, 0xcd88, 0xc587, 0xacc7, 0x4a03, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1081, 0x2901, 0x4a02, 0x7b44, 0x9be5, 0xa403, 0xac23, 0xac23, 0xac43, 0xac64, 0xb484, 0xb4a4, 0xb4a4, 0xb4a5, 0xac86, 0xa446, 0x6ae4, 0x5223, 0x2921, 0x18c1, 0x0820, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +#endif diff --git a/yoRadio/fonts/glcdfont.c b/yoRadio/fonts/glcdfont.c new file mode 100644 index 0000000..f00553c --- /dev/null +++ b/yoRadio/fonts/glcdfont.c @@ -0,0 +1,273 @@ +#ifndef FONT5X7_H +#define FONT5X7_H + +#ifdef __AVR__ + #include + #include +#elif defined(ESP8266) + #include +#else + #define PROGMEM +#endif + +// Standard ASCII 5x7 font + +static const unsigned char font[] PROGMEM = { +0x00, 0x00, 0x00, 0x00, 0x00, +0x3E, 0x55, 0x51, 0x55, 0x3E, +0x3E, 0x6B, 0x6F, 0x6B, 0x3E, +0x0C, 0x1E, 0x3C, 0x1E, 0x0C, +0x08, 0x1C, 0x3E, 0x1C, 0x08, +0x1C, 0x4A, 0x7F, 0x4A, 0x1C, +0x18, 0x5C, 0x7F, 0x5C, 0x18, +0x00, 0x1C, 0x1C, 0x1C, 0x00, +0x7F, 0x63, 0x63, 0x63, 0x7F, +0x00, 0x1C, 0x14, 0x1C, 0x00, +0x7F, 0x63, 0x6B, 0x63, 0x7F, +0x30, 0x48, 0x4D, 0x33, 0x07, +0x06, 0x29, 0x79, 0x29, 0x06, +0x20, 0x50, 0x3F, 0x02, 0x0C, +0x60, 0x7F, 0x05, 0x35, 0x3F, +0x2A, 0x1C, 0x77, 0x1C, 0x2A, +0x00, 0x7F, 0x3E, 0x1C, 0x08, +0x08, 0x1C, 0x3E, 0x7F, 0x00, +0x14, 0x22, 0x7F, 0x22, 0x14, +0x00, 0x5F, 0x00, 0x5F, 0x00, +0x06, 0x09, 0x7F, 0x01, 0x7F, +0x4A, 0x55, 0x55, 0x55, 0x29, +0x60, 0x60, 0x60, 0x60, 0x60, +0x54, 0x62, 0x7F, 0x62, 0x54, +0x08, 0x04, 0x7E, 0x04, 0x08, +0x08, 0x10, 0x3F, 0x10, 0x08, +0x08, 0x08, 0x2A, 0x1C, 0x08, +0x08, 0x1C, 0x2A, 0x08, 0x08, +0x1C, 0x10, 0x10, 0x10, 0x10, +0x1C, 0x3E, 0x08, 0x3E, 0x1C, +0x30, 0x3C, 0x3F, 0x3C, 0x30, +0x06, 0x1E, 0x7E, 0x1E, 0x06, +0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x5F, 0x00, 0x00, +0x00, 0x07, 0x00, 0x07, 0x00, +0x14, 0x7F, 0x14, 0x7F, 0x14, +0x24, 0x2A, 0x7F, 0x2A, 0x12, +0x23, 0x13, 0x08, 0x64, 0x62, +0x36, 0x49, 0x56, 0x20, 0x50, +0x00, 0x00, 0x07, 0x00, 0x00, +0x00, 0x1C, 0x22, 0x41, 0x00, +0x00, 0x41, 0x22, 0x1C, 0x00, +0x14, 0x08, 0x3E, 0x08, 0x14, +0x08, 0x08, 0x3E, 0x08, 0x08, +0x00, 0xA0, 0x60, 0x00, 0x00, +0x08, 0x08, 0x08, 0x08, 0x08, +0x00, 0x60, 0x60, 0x00, 0x00, +0x20, 0x10, 0x08, 0x04, 0x02, +0x3E, 0x51, 0x49, 0x45, 0x3E, +0x44, 0x42, 0x7F, 0x40, 0x40, +0x42, 0x61, 0x51, 0x49, 0x46, +0x21, 0x41, 0x45, 0x4B, 0x31, +0x18, 0x14, 0x12, 0x7F, 0x10, +0x27, 0x45, 0x45, 0x45, 0x39, +0x3C, 0x4A, 0x49, 0x49, 0x30, +0x01, 0x71, 0x09, 0x05, 0x03, +0x36, 0x49, 0x49, 0x49, 0x36, +0x06, 0x49, 0x49, 0x29, 0x1E, +0x00, 0x6C, 0x6C, 0x00, 0x00, +0x00, 0xAC, 0x6C, 0x00, 0x00, +0x08, 0x14, 0x22, 0x41, 0x00, +0x14, 0x14, 0x14, 0x14, 0x14, +0x00, 0x41, 0x22, 0x14, 0x08, +0x02, 0x01, 0x51, 0x09, 0x06, +0x3E, 0x41, 0x5D, 0x55, 0x5E, +0x7C, 0x12, 0x11, 0x12, 0x7C, +0x7F, 0x49, 0x49, 0x49, 0x36, +0x3E, 0x41, 0x41, 0x41, 0x22, +0x7F, 0x41, 0x41, 0x22, 0x1C, +0x7F, 0x49, 0x49, 0x49, 0x41, +0x7F, 0x09, 0x09, 0x09, 0x01, +0x3E, 0x41, 0x49, 0x49, 0x7A, +0x7F, 0x08, 0x08, 0x08, 0x7F, +0x00, 0x41, 0x7F, 0x41, 0x00, +0x20, 0x40, 0x41, 0x3F, 0x01, +0x7F, 0x08, 0x14, 0x22, 0x41, +0x7F, 0x40, 0x40, 0x40, 0x60, +0x7F, 0x02, 0x0C, 0x02, 0x7F, +0x7F, 0x04, 0x08, 0x10, 0x7F, +0x3E, 0x41, 0x41, 0x41, 0x3E, +0x7F, 0x09, 0x09, 0x09, 0x06, +0x3E, 0x41, 0x51, 0x21, 0x5E, +0x7F, 0x09, 0x19, 0x29, 0x46, +0x46, 0x49, 0x49, 0x49, 0x31, +0x03, 0x01, 0x7F, 0x01, 0x03, +0x3F, 0x40, 0x40, 0x40, 0x3F, +0x1F, 0x20, 0x40, 0x20, 0x1F, +0x3F, 0x40, 0x3C, 0x40, 0x3F, +0x63, 0x14, 0x08, 0x14, 0x63, +0x07, 0x08, 0x70, 0x08, 0x07, +0x61, 0x51, 0x49, 0x45, 0x43, +0x00, 0x7F, 0x41, 0x41, 0x00, +0x02, 0x04, 0x08, 0x10, 0x20, +0x00, 0x41, 0x41, 0x7F, 0x00, +0x04, 0x02, 0x01, 0x02, 0x04, +0x40, 0x40, 0x40, 0x40, 0x40, +0x00, 0x01, 0x02, 0x04, 0x00, +0x20, 0x54, 0x54, 0x54, 0x78, +0x7F, 0x48, 0x44, 0x44, 0x38, +0x38, 0x44, 0x44, 0x44, 0x48, +0x38, 0x44, 0x44, 0x48, 0x7F, +0x38, 0x54, 0x54, 0x54, 0x18, +0x08, 0x7E, 0x09, 0x01, 0x02, +0x08, 0x54, 0x54, 0x58, 0x3C, +0x7F, 0x08, 0x04, 0x04, 0x78, +0x00, 0x44, 0x7D, 0x40, 0x00, +0x20, 0x40, 0x44, 0x3D, 0x00, +0x7F, 0x10, 0x10, 0x28, 0x44, +0x00, 0x41, 0x7F, 0x40, 0x00, +0x7C, 0x04, 0x78, 0x04, 0x78, +0x7C, 0x08, 0x04, 0x04, 0x78, +0x38, 0x44, 0x44, 0x44, 0x38, +0x7C, 0x14, 0x14, 0x14, 0x08, +0x08, 0x14, 0x14, 0x0C, 0x7C, +0x7C, 0x08, 0x04, 0x04, 0x08, +0x48, 0x54, 0x54, 0x54, 0x24, +0x04, 0x3F, 0x44, 0x40, 0x20, +0x3C, 0x40, 0x40, 0x20, 0x7C, +0x1C, 0x20, 0x40, 0x20, 0x1C, +0x3C, 0x40, 0x38, 0x40, 0x3C, +0x44, 0x28, 0x10, 0x28, 0x44, +0x0C, 0x50, 0x50, 0x50, 0x3C, +0x44, 0x64, 0x54, 0x4C, 0x44, +0x00, 0x08, 0x36, 0x41, 0x00, +0x00, 0x00, 0x7F, 0x00, 0x00, +0x00, 0x41, 0x36, 0x08, 0x00, +0x02, 0x01, 0x02, 0x04, 0x02, +0x70, 0x48, 0x44, 0x48, 0x70, +0x00, 0x0E, 0x11, 0x0E, 0x00, +0x00, 0x12, 0x1F, 0x10, 0x00, +0x00, 0x12, 0x19, 0x16, 0x00, +0x00, 0x11, 0x15, 0x0B, 0x00, +0x00, 0x07, 0x04, 0x1F, 0x00, +0x00, 0x17, 0x15, 0x09, 0x00, +0x00, 0x0E, 0x15, 0x09, 0x00, +0x00, 0x01, 0x1D, 0x03, 0x00, +0x00, 0x0A, 0x15, 0x0A, 0x00, +0x00, 0x12, 0x15, 0x0E, 0x00, +0x00, 0x04, 0x04, 0x04, 0x00, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0x3E, 0x00, 0x00, 0x00, 0x00, +0x3E, 0x3E, 0x00, 0x00, 0x00, +0x3E, 0x3E, 0x00, 0x3E, 0x00, +0x3E, 0x3E, 0x00, 0x3E, 0x3E, +0x58, 0x64, 0x04, 0x64, 0x58, +0x7F, 0x3E, 0x1C, 0x08, 0x7F, +0x7F, 0x08, 0x1C, 0x3E, 0x7F, +0x7F, 0x7F, 0x00, 0x7F, 0x7F, +0x08, 0x3E, 0x22, 0x22, 0x22, +0x22, 0x22, 0x22, 0x3E, 0x08, +0x40, 0x00, 0x40, 0x00, 0x40, +0x60, 0x00, 0x40, 0x00, 0x40, +0x60, 0x00, 0x70, 0x00, 0x40, +0x60, 0x00, 0x70, 0x00, 0x78, +0x7C, 0x00, 0x40, 0x00, 0x40, +0x7C, 0x00, 0x7E, 0x00, 0x40, +0x7C, 0x00, 0x7E, 0x00, 0x7F, +0x1C, 0x77, 0x41, 0x41, 0x41, +0x41, 0x41, 0x41, 0x41, 0x41, +0x41, 0x41, 0x41, 0x7F, 0x00, +0x1C, 0x77, 0x41, 0x5D, 0x5D, +0x41, 0x41, 0x41, 0x5D, 0x5D, +0x5D, 0x5D, 0x41, 0x5D, 0x5D, +0x5D, 0x5D, 0x41, 0x7F, 0x00, +0x22, 0x1C, 0x14, 0x1C, 0x22, +0x00, 0x08, 0x1C, 0x08, 0x00, +0x00, 0x00, 0x77, 0x00, 0x00, +0x46, 0x5D, 0x55, 0x5D, 0x31, +0x7C, 0x55, 0x54, 0x55, 0x44, +0x08, 0x08, 0x2A, 0x08, 0x08, +0x00, 0x14, 0x08, 0x14, 0x00, +0x08, 0x14, 0x22, 0x08, 0x14, +0x7F, 0x41, 0x71, 0x31, 0x1F, +0x03, 0x05, 0x7F, 0x05, 0x03, +0x22, 0x14, 0x7F, 0x55, 0x22, +0x02, 0x55, 0x7D, 0x05, 0x02, +0x06, 0x09, 0x09, 0x06, 0x00, +0x44, 0x44, 0x5F, 0x44, 0x44, +0x1C, 0x14, 0x1C, 0x22, 0x7F, +0x20, 0x3E, 0x61, 0x3E, 0x20, +0x20, 0x50, 0x3F, 0x02, 0x0C, +0x80, 0x7C, 0x20, 0x3C, 0x40, +0x44, 0x3C, 0x04, 0x7C, 0x44, +0x00, 0x00, 0x08, 0x00, 0x00, +0x38, 0x55, 0x54, 0x55, 0x18, +0x7E, 0x08, 0x10, 0x7F, 0x01, +0x08, 0x10, 0x08, 0x04, 0x02, +0x14, 0x08, 0x22, 0x14, 0x08, +0x0E, 0x06, 0x0A, 0x10, 0x20, +0x20, 0x10, 0x0A, 0x06, 0x0E, +0x38, 0x30, 0x28, 0x04, 0x02, +0x02, 0x04, 0x28, 0x30, 0x38, +0x7E, 0x11, 0x11, 0x11, 0x7E, +0x7F, 0x49, 0x49, 0x49, 0x31, +0x7F, 0x49, 0x49, 0x49, 0x36, +0x7F, 0x01, 0x01, 0x01, 0x03, +0xC0, 0x7F, 0x41, 0x7F, 0xC0, +0x7F, 0x49, 0x49, 0x49, 0x41, +0x77, 0x08, 0x7F, 0x08, 0x77, +0x41, 0x49, 0x49, 0x49, 0x36, +0x7F, 0x10, 0x08, 0x04, 0x7F, +0x7C, 0x21, 0x12, 0x09, 0x7C, +0x7F, 0x08, 0x14, 0x22, 0x41, +0x40, 0x3E, 0x01, 0x01, 0x7F, +0x7F, 0x02, 0x0C, 0x02, 0x7F, +0x7F, 0x08, 0x08, 0x08, 0x7F, +0x3E, 0x41, 0x41, 0x41, 0x3E, +0x7F, 0x01, 0x01, 0x01, 0x7F, +0x7F, 0x09, 0x09, 0x09, 0x06, +0x3E, 0x41, 0x41, 0x41, 0x22, +0x01, 0x01, 0x7F, 0x01, 0x01, +0x07, 0x48, 0x48, 0x48, 0x3F, +0x0E, 0x11, 0x7F, 0x11, 0x0E, +0x63, 0x14, 0x08, 0x14, 0x63, +0x7F, 0x40, 0x40, 0x7F, 0xC0, +0x07, 0x08, 0x08, 0x08, 0x7F, +0x7F, 0x40, 0x7F, 0x40, 0x7F, +0x7F, 0x40, 0x7F, 0x40, 0xFF, +0x01, 0x7F, 0x48, 0x48, 0x30, +0x7F, 0x48, 0x48, 0x30, 0x7F, +0x7F, 0x48, 0x48, 0x48, 0x30, +0x22, 0x41, 0x49, 0x49, 0x3E, +0x7F, 0x08, 0x3E, 0x41, 0x3E, +0x46, 0x29, 0x19, 0x09, 0x7F, +0x20, 0x54, 0x54, 0x54, 0x78, +0x3C, 0x4A, 0x4A, 0x49, 0x31, +0x7C, 0x54, 0x54, 0x54, 0x28, +0x7C, 0x04, 0x04, 0x04, 0x0C, +0xC0, 0x78, 0x44, 0x7C, 0xC0, +0x38, 0x54, 0x54, 0x54, 0x18, +0x6C, 0x10, 0x7C, 0x10, 0x6C, +0x44, 0x54, 0x54, 0x54, 0x28, +0x7C, 0x20, 0x10, 0x08, 0x7C, +0x7C, 0x40, 0x26, 0x10, 0x7C, +0x7C, 0x10, 0x10, 0x28, 0x44, +0x40, 0x38, 0x04, 0x04, 0x7C, +0x7C, 0x08, 0x10, 0x08, 0x7C, +0x7C, 0x10, 0x10, 0x10, 0x7C, +0x38, 0x44, 0x44, 0x44, 0x38, +0x7C, 0x04, 0x04, 0x04, 0x7C, +0x7C, 0x14, 0x14, 0x14, 0x08, +0x38, 0x44, 0x44, 0x44, 0x48, +0x04, 0x04, 0x7C, 0x04, 0x04, +0x0C, 0x50, 0x50, 0x50, 0x3C, +0x18, 0x24, 0xFC, 0x24, 0x18, +0x44, 0x28, 0x10, 0x28, 0x44, +0x7C, 0x40, 0x40, 0x7C, 0xC0, +0x0C, 0x10, 0x10, 0x10, 0x7C, +0x7C, 0x40, 0x7C, 0x40, 0x7C, +0x7C, 0x40, 0x7C, 0x40, 0xFC, +0x04, 0x7C, 0x50, 0x50, 0x20, +0x7C, 0x50, 0x50, 0x20, 0x7C, +0x7C, 0x50, 0x50, 0x50, 0x20, +0x28, 0x44, 0x54, 0x54, 0x38, +0x7C, 0x10, 0x38, 0x44, 0x38, +0x48, 0x34, 0x14, 0x14, 0x7C +}; +#endif // FONT5X7_H diff --git a/yoRadio/netserver.cpp b/yoRadio/netserver.cpp new file mode 100644 index 0000000..3757ada --- /dev/null +++ b/yoRadio/netserver.cpp @@ -0,0 +1,384 @@ +#include "netserver.h" +#include + +#include "config.h" +#include "player.h" +#include "display.h" +#include "options.h" +#include "network.h" + +NetServer netserver; + +AsyncWebServer webserver(80); +AsyncWebSocket websocket("/ws"); +AsyncUDP udp; + +String processor(const String& var); +void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); +void handleHTTPPost(AsyncWebServerRequest * request); + +byte ssidCount; + +bool NetServer::begin() { + importRequest = false; + webserver.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { + ssidCount = 0; + request->send(SPIFFS, "/www/index.html", String(), false, processor); + }); + webserver.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=31536000"); + webserver.on("/", HTTP_POST, [](AsyncWebServerRequest * request) { + handleHTTPPost(request); + }); + webserver.on(PLAYLIST_PATH, HTTP_GET, [](AsyncWebServerRequest * request) { + request->send(SPIFFS, PLAYLIST_PATH, "application/octet-stream"); + }); + webserver.on(INDEX_PATH, HTTP_GET, [](AsyncWebServerRequest * request) { + request->send(SPIFFS, INDEX_PATH, "application/octet-stream"); + }); + webserver.on("/upload", HTTP_POST, [](AsyncWebServerRequest * request) { + //request->send(200); + }, handleUpload); + webserver.begin(); + websocket.onEvent(onWsEvent); + webserver.addHandler(&websocket); + + //echo -n "helle?" | socat - udp-datagram:255.255.255.255:44490,broadcast + if (udp.listen(44490)) { + udp.onPacket([](AsyncUDPPacket packet) { + if(strcmp((char*)packet.data(),"helle?")==0) + packet.println(WiFi.localIP()); + }); + } +} + +void NetServer::loop() { + websocket.cleanupClients(); + if (playlistrequest > 0) { + requestOnChange(PLAYLIST, playlistrequest); + playlistrequest = 0; + } + if (importRequest) { + if (importPlaylist()) { + requestOnChange(PLAYLIST, 0); + } + importRequest = false; + } + yield(); +} + +void NetServer::onWsMessage(void *arg, uint8_t *data, size_t len) { + AwsFrameInfo *info = (AwsFrameInfo*)arg; + if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { + data[len] = 0; + char cmd[15], val[15]; + if (config.parseWsCommand((const char*)data, cmd, val, 15)) { + if (strcmp(cmd, "volume") == 0) { + byte v = atoi(val); + player.setVol(v, false); + } + } + } +} + +void NetServer::setRSSI(int val) { + rssi = val; + requestOnChange(NRSSI, 0); +} + +void NetServer::getPlaylist(uint8_t clientId) { + String dataString = ""; + File file = SPIFFS.open(PLAYLIST_PATH, "r"); + if (!file || file.isDirectory()) { + return; + } + char sName[BUFLEN], sUrl[BUFLEN], pOvol[30]; + int sOvol; + while (file.available()) { + String line = file.readStringUntil('\n'); + if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) { + sprintf(pOvol, "%d", sOvol); + dataString += "{\"name\":\"" + String(sName) + "\",\"url\":\"" + String(sUrl) + "\",\"ovol\":" + String(pOvol) + "},"; + } + } + if (dataString.length() > 0) { + if (clientId == 0) { + websocket.textAll("{\"file\": [" + dataString.substring(0, dataString.length() - 1) + "]}"); + } else { + websocket.text(clientId, "{\"file\": [" + dataString.substring(0, dataString.length() - 1) + "]}"); + } + } + file.close(); +} + +bool NetServer::savePlaylist(const char* post) { + File file = SPIFFS.open(PLAYLIST_PATH, "w"); + if (!file) { + return false; + } else { + file.print(post); + file.close(); + netserver.requestOnChange(PLAYLISTSAVED, 0); + } +} + +bool NetServer::importPlaylist() { + File tempfile = SPIFFS.open(TMP_PATH, "r"); + if (!tempfile) { + return false; + } + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + String line = tempfile.readStringUntil('\n'); + if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) { + File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w"); + playlistfile.println(line); + while (tempfile.available()) { + line = tempfile.readStringUntil('\n'); + if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) { + playlistfile.println(line); + } + } + playlistfile.close(); + tempfile.close(); + SPIFFS.remove(TMP_PATH); + requestOnChange(PLAYLISTSAVED, 0); + return true; + } + if (config.parseJSON(line.c_str(), sName, sUrl, sOvol)) { + File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w"); + String wline = String(sName) + "\t" + String(sUrl) + "\t" + String(sOvol); + playlistfile.println(wline); + while (tempfile.available()) { + line = tempfile.readStringUntil('\n'); + if (config.parseJSON(line.c_str(), sName, sUrl, sOvol)) { + wline = String(sName) + "\t" + String(sUrl) + "\t" + String(sOvol); + playlistfile.println(wline); + } + } + playlistfile.close(); + tempfile.close(); + SPIFFS.remove(TMP_PATH); + requestOnChange(PLAYLISTSAVED, 0); + return true; + } + tempfile.close(); + SPIFFS.remove(TMP_PATH); + return false; +} + +void NetServer::requestOnChange(requestType_e request, uint8_t clientId) { + char buf[BUFLEN + 50] = { 0 }; + switch (request) { + case PLAYLIST: { + getPlaylist(clientId); + break; + } + case PLAYLISTSAVED: { + config.indexPlaylist(); + config.initPlaylist(); + getPlaylist(clientId); + break; + } + case STATION: { + sprintf (buf, "{\"nameset\": \"%s\"}", config.station.name); + requestOnChange(ITEM, clientId); + break; + } + case ITEM: { + sprintf (buf, "{\"current\": %d}", config.store.lastStation); + break; + } + case TITLE: { + sprintf (buf, "{\"meta\": \"%s\"}", config.station.title); + break; + } + case VOLUME: { + sprintf (buf, "{\"vol\": %d}", config.store.volume); + break; + } + case NRSSI: { + sprintf (buf, "{\"rssi\": %d}", rssi); + break; + } + case BITRATE: { + sprintf (buf, "{\"bitrate\": %d}", config.station.bitrate); + break; + } + case MODE: { + sprintf (buf, "{\"mode\": \"%s\"}", player.mode == PLAYING ? "playing" : "stopped"); + break; + } + case EQUALIZER: { + sprintf (buf, "{\"bass\": %d, \"middle\": %d, \"trebble\": %d}", config.store.bass, config.store.middle, config.store.trebble); + break; + } + case BALANCE: { + sprintf (buf, "{\"balance\": %d}", config.store.balance); + break; + } + } + if (strlen(buf) > 0) { + if (clientId == 0) { + websocket.textAll(buf); + } else { + websocket.text(clientId, buf); + } + } +} + +String processor(const String& var) { // %Templates% + if (var == "VERSION") { + return VERSION; + } + if (var == "SSID") { + ssidCount++; + return String(config.ssids[ssidCount - 1].ssid); + } + if (var == "PASS") { + return String(config.ssids[ssidCount - 1].password); + } + if (var == "APMODE") { + return network.status == CONNECTED ? "" : " style=\"display: none!important\""; + } + if (var == "NOTAPMODE") { + return network.status == CONNECTED ? " hidden" : ""; + } + return String(); +} + +void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if (!index) { + request->_tempFile = SPIFFS.open(TMP_PATH , "w"); + } + if (len) { + request->_tempFile.write(data, len); + //TODO check index+len size + } + if (final) { + request->_tempFile.close(); + netserver.importRequest = true; + request->send(200); + } +} + +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + switch (type) { + case WS_EVT_CONNECT: + Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); + netserver.requestOnChange(STATION, client->id()); + netserver.requestOnChange(TITLE, client->id()); + netserver.requestOnChange(VOLUME, client->id()); + netserver.requestOnChange(EQUALIZER, client->id()); + netserver.requestOnChange(BALANCE, client->id()); + netserver.requestOnChange(BITRATE, client->id()); + netserver.requestOnChange(MODE, client->id()); + netserver.playlistrequest = client->id(); + break; + case WS_EVT_DISCONNECT: + Serial.printf("WebSocket client #%u disconnected\n", client->id()); + break; + case WS_EVT_DATA: + netserver.onWsMessage(arg, data, len); + break; + case WS_EVT_PONG: + case WS_EVT_ERROR: + break; + } +} + +void handleHTTPPost(AsyncWebServerRequest * request) { + if (request->hasParam("wifisettings", true)) { + AsyncWebParameter* p = request->getParam("wifisettings", true); + if (p->value() != "") { + config.saveWifi(p->value().c_str()); + } + request->send(200); + return; + } + if (request->hasParam("playlist", true)) { + AsyncWebParameter* p = request->getParam("playlist", true); + netserver.savePlaylist(p->value().c_str()); + request->send(200); + return; + } + if (network.status != CONNECTED) { + request->send(404); + return; + } + if (request->hasParam("start", true)) { + player.request.station = config.store.lastStation; + request->send(200); + return; + } + if (request->hasParam("stop", true)) { + player.mode = STOPPED; + display.title("[stopped]"); + request->send(200); + return; + } + if (request->hasParam("prev", true)) { + player.prev(); + request->send(200); + return; + } + if (request->hasParam("next", true)) { + player.next(); + request->send(200); + return; + } + if (request->hasParam("volm", true)) { + player.stepVol(false); + request->send(200); + return; + } + if (request->hasParam("volp", true)) { + player.stepVol(true); + request->send(200); + return; + } + if (request->hasParam("vol", true)) { + AsyncWebParameter* p = request->getParam("vol", true); + int v = atoi(p->value().c_str()); + if (v < 0) v = 0; + if (v > 254) v = 254; + player.setVol(v, false); + request->send(200); + return; + } + if (request->hasParam("trebble", true)) { + AsyncWebParameter* pt = request->getParam("trebble", true); + AsyncWebParameter* pm = request->getParam("middle", true); + AsyncWebParameter* pb = request->getParam("bass", true); + int t = atoi(pt->value().c_str()); + int m = atoi(pm->value().c_str()); + int b = atoi(pb->value().c_str()); + //setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass) + player.setTone(b, m, t); + config.setTone(b, m, t); + netserver.requestOnChange(EQUALIZER, 0); + request->send(200); + return; + } + if (request->hasParam("ballance", true)) { + AsyncWebParameter* p = request->getParam("ballance", true); + int b = atoi(p->value().c_str()); + player.setBalance(b); + config.setBalance(b); + netserver.requestOnChange(BALANCE, 0); + request->send(200); + return; + } + if (request->hasParam("playstation", true)) { + AsyncWebParameter* p = request->getParam("playstation", true); + int id = atoi(p->value().c_str()); + if (id < 1) id = 1; + if (id > config.store.countStation) id = config.store.countStation; + player.request.station = id; + player.request.doSave = true; + request->send(200); + return; + } + + request->send(404); +} diff --git a/yoRadio/netserver.h b/yoRadio/netserver.h new file mode 100644 index 0000000..c87c882 --- /dev/null +++ b/yoRadio/netserver.h @@ -0,0 +1,31 @@ +#ifndef netserver_h +#define netserver_h +#include "Arduino.h" + +#include "ESPAsyncWebServer.h" +#include "AsyncUDP.h" + +enum requestType_e { PLAYLIST, STATION, ITEM, TITLE, VOLUME, NRSSI, BITRATE, MODE, EQUALIZER, BALANCE, PLAYLISTSAVED }; + +class NetServer { + public: + uint8_t playlistrequest; // ClientId want the playlist + bool importRequest; + public: + NetServer() {}; + bool begin(); + void loop(); + void requestOnChange(requestType_e request, uint8_t clientId); + void setRSSI(int val); + void onWsMessage(void *arg, uint8_t *data, size_t len); + bool savePlaylist(const char* post); + private: + requestType_e request; + int rssi; + void getPlaylist(uint8_t clientId); + bool importPlaylist(); +}; + +extern NetServer netserver; + +#endif diff --git a/yoRadio/network.cpp b/yoRadio/network.cpp new file mode 100644 index 0000000..dcfab54 --- /dev/null +++ b/yoRadio/network.cpp @@ -0,0 +1,55 @@ +#include "network.h" +#include "WiFi.h" +#include "display.h" +#include "options.h" + +Network network; + +void Network::begin() { + config.initNetwork(); + if (config.ssidsCount == 0) { + raiseSoftAP(); + return; + } + byte ls = (config.store.lastSSID == 0 || config.store.lastSSID > config.ssidsCount) ? 0 : config.store.lastSSID - 1; + byte startedls = ls; + byte errcnt = 0; + WiFi.mode(WIFI_STA); + char buf[40] = { 0 }; + while (true) { + Serial.printf("Attempt to connect to %s\n", config.ssids[ls].ssid); + snprintf(buf, sizeof(buf) - 1, "ATTEMPT TO %s", config.ssids[ls].ssid); + display.bootString(buf, 110); + WiFi.begin(config.ssids[ls].ssid, config.ssids[ls].password); + strcpy(buf, "."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + strcat(buf, "."); + display.bootString(buf, 90); + errcnt++; + if (errcnt > 16) { + errcnt = 0; + ls++; + if (ls > config.ssidsCount - 1) ls = 0; + break; + } + } + if(WiFi.status() != WL_CONNECTED && ls==startedls){ + raiseSoftAP(); + return; + } + if (WiFi.status() == WL_CONNECTED) { + config.setLastSSID(ls + 1); + break; + } + } + digitalWrite(LED_BUILTIN, LOW); + status = CONNECTED; +} + +void Network::raiseSoftAP() { + WiFi.mode(WIFI_AP); + WiFi.softAP(apSsid, apPassword); + status = SOFT_AP; +} diff --git a/yoRadio/network.h b/yoRadio/network.h new file mode 100644 index 0000000..6ec70d1 --- /dev/null +++ b/yoRadio/network.h @@ -0,0 +1,21 @@ +#ifndef network_h +#define network_h + +#define apSsid "yoRadioAP" +#define apPassword "12345987" + +enum n_Status_e { CONNECTED, SOFT_AP, FAILED }; + +class Network { + public: + n_Status_e status; + public: + Network() {}; + void begin(); + private: + void raiseSoftAP(); +}; + +extern Network network; + +#endif diff --git a/yoRadio/options.h b/yoRadio/options.h new file mode 100644 index 0000000..3e396c3 --- /dev/null +++ b/yoRadio/options.h @@ -0,0 +1,47 @@ +#ifndef options_h +#define options_h + +#define VERSION "0.4.170" + +/* + * TFT DISPLAY + */ +/************** + * GND | GND * + * VCC | +5v * + * SCL | D18 * + * SDA | D23 * + * ************ + */ +#define TFT_CS 5 +#define TFT_RST 15 // Or set to -1 and connect to Arduino RESET pin +//#define TFT_RST -1 // we use the seesaw for resetting to save a pin +#define TFT_DC 4 + +/* + * I2S DAC + */ +#define I2S_DOUT 27 // DIN connection +#define I2S_BCLK 26 // BCLK Bit clock +#define I2S_LRC 25 // WSEL Left Right Clock + +/* + * ENCODER + */ +#define ENC_BTNL 13 +#define ENC_BTNB 12 +#define ENC_BTNR 14 + +/* + * BUTTONS + */ +#define BTN_LEFT 32 +#define BTN_CENTER 12 +#define BTN_RIGHT 33 + +/* + * ESP DEVBOARD + */ +#define LED_BUILTIN 2 + +#endif diff --git a/yoRadio/player.cpp b/yoRadio/player.cpp new file mode 100644 index 0000000..cb3c0ee --- /dev/null +++ b/yoRadio/player.cpp @@ -0,0 +1,128 @@ +#include "player.h" +#include "config.h" +#include "telnet.h" +#include "display.h" +#include "options.h" +#include "netserver.h" + +Player player; + +void Player::init() { + setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); + setVolume(0); + mode = STOPPED; + requesToStart = true; + setBalance(config.store.balance); + setTone(config.store.bass, config.store.middle, config.store.trebble); + zeroRequest(); +} + +void Player::stopInfo() { + config.setSmartStart(0); + telnet.info(); + netserver.requestOnChange(MODE, 0); + requesToStart = true; +} + +void Player::loop() { + if (mode == PLAYING) { + Audio::loop(); + } else { + if (isRunning()) { + digitalWrite(LED_BUILTIN, LOW); + stopSong(); + stopInfo(); + } + } + if (request.station > 0) { + if (request.doSave) { + config.setLastStation(request.station); + } + play(request.station); + zeroRequest(); + } + if (request.volume >= 0) { + config.setVolume(request.volume, request.doSave); + display.volume(); + telnet.printf("##CLI.VOL#: %d\n", config.store.volume); + Audio::setVolume(volToI2S(request.volume)); + zeroRequest(); + } + yield(); +} + +void Player::zeroRequest() { + request.station = 0; + request.volume = -1; + request.doSave = false; +} + +void Player::play(byte stationId) { + stopSong(); + digitalWrite(LED_BUILTIN, LOW); + display.title("[connecting]"); + telnet.printf("##CLI.META#: %s\n", config.station.title); + config.loadStation(stationId); + setVol(config.store.volume, true); + display.station(); + telnet.printf("##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name); + if (connecttohost(config.station.url)) { + mode = PLAYING; + config.setSmartStart(1); + netserver.requestOnChange(MODE, 0); + digitalWrite(LED_BUILTIN, HIGH); + requesToStart = true; + }else{ + Serial.println("Some Unknown Bug..."); + } + zeroRequest(); +} + +void Player::prev() { + if (config.store.lastStation == 1) config.store.lastStation = config.store.countStation; else config.store.lastStation--; + request.station = config.store.lastStation; + request.doSave = true; +} + +void Player::next() { + if (config.store.lastStation == config.store.countStation) config.store.lastStation = 1; else config.store.lastStation++; + request.station = config.store.lastStation; + request.doSave = true; +} + +void Player::toggle() { + if (mode == PLAYING) { + mode = STOPPED; + display.title("[stopped]"); + } else { + request.station = config.store.lastStation; + } +} + +void Player::stepVol(bool up) { + if (up) { + if (config.store.volume < 254) { + setVol(config.store.volume + 1, false); + } + } else { + if (config.store.volume > 0) { + setVol(config.store.volume - 1, false); + } + } +} + +byte Player::volToI2S(byte volume) { + int vol = map(volume, 0, 254 - config.station.ovol * 2 , 0, 254); + if (vol > 254) vol = 254; + if (vol < 0) vol = 0; + return vol; +} + +void Player::setVol(byte volume, bool inside) { + if (inside) { + setVolume(volToI2S(volume)); + } else { + request.volume = volume; + request.doSave = true; + } +} diff --git a/yoRadio/player.h b/yoRadio/player.h new file mode 100644 index 0000000..732632f --- /dev/null +++ b/yoRadio/player.h @@ -0,0 +1,36 @@ +#ifndef player_h +#define player_h + +#include "src/audioI2S/AudioEx.h" + +enum audioMode_e { PLAYING, STOPPED }; + +struct audiorequest_t +{ + uint16_t station; + int volume; + bool doSave; +}; + +class Player: public Audio { + public: + audioMode_e mode; + audiorequest_t request; + bool requesToStart; + public: + void init(); + void loop(); + void zeroRequest(); + void play(byte stationId); + void prev(); + void next(); + void toggle(); + void stepVol(bool up); + void setVol(byte volume, bool inside); + byte volToI2S(byte volume); + void stopInfo(); +}; + +extern Player player; + +#endif diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp new file mode 100644 index 0000000..01a8ca4 --- /dev/null +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -0,0 +1,4538 @@ +/* + * Audio.cpp + * + * Created on: Oct 26,2018 + * Updated on: Jan 05,2022 + * Author: Wolle (schreibfaul1) + * + */ +#include "AudioEx.h" +#include "mp3_decoder/mp3_decoder.h" +#include "aac_decoder/aac_decoder.h" +#include "flac_decoder/flac_decoder.h" + +#ifdef SDFATFS_USED +fs::SDFATFS SD_SDFAT; +#endif + +//--------------------------------------------------------------------------------------------------------------------- +AudioBuffer::AudioBuffer(size_t maxBlockSize) { + // if maxBlockSize isn't set use defaultspace (1600 bytes) is enough for aac and mp3 player + if(maxBlockSize) m_resBuffSizeRAM = maxBlockSize; + if(maxBlockSize) m_maxBlockSize = maxBlockSize; +} + +AudioBuffer::~AudioBuffer() { + if(m_buffer) + free(m_buffer); + m_buffer = NULL; +} + +size_t AudioBuffer::init() { + if(m_buffer) free(m_buffer); + m_buffer = NULL; + if(psramInit()) { + // PSRAM found, AudioBuffer will be allocated in PSRAM + m_buffSize = m_buffSizePSRAM; + if(m_buffer == NULL) { + m_buffer = (uint8_t*) ps_calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizePSRAM - m_resBuffSizePSRAM; + if(m_buffer == NULL) { + // not enough space in PSRAM, use ESP32 Flash Memory instead + m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + } + } + } else { // no PSRAM available, use ESP32 Flash Memory" + m_buffSize = m_buffSizeRAM; + m_buffer = (uint8_t*) calloc(m_buffSize, sizeof(uint8_t)); + m_buffSize = m_buffSizeRAM - m_resBuffSizeRAM; + } + if(!m_buffer) + return 0; + resetBuffer(); + return m_buffSize; +} + +void AudioBuffer::changeMaxBlockSize(uint16_t mbs){ + m_maxBlockSize = mbs; + return; +} + +uint16_t AudioBuffer::getMaxBlockSize(){ + return m_maxBlockSize; +} + +size_t AudioBuffer::freeSpace() { + if(m_readPtr >= m_writePtr) { + m_freeSpace = (m_readPtr - m_writePtr); + } else { + m_freeSpace = (m_endPtr - m_writePtr) + (m_readPtr - m_buffer); + } + if(m_f_start) + m_freeSpace = m_buffSize; + return m_freeSpace - 1; +} + +size_t AudioBuffer::writeSpace() { + if(m_readPtr >= m_writePtr) { + m_writeSpace = (m_readPtr - m_writePtr - 1); // readPtr must not be overtaken + } else { + if(getReadPos() == 0) + m_writeSpace = (m_endPtr - m_writePtr - 1); + else + m_writeSpace = (m_endPtr - m_writePtr); + } + if(m_f_start) + m_writeSpace = m_buffSize - 1; + return m_writeSpace; +} + +size_t AudioBuffer::bufferFilled() { + if(m_writePtr >= m_readPtr) { + m_dataLength = (m_writePtr - m_readPtr); + } else { + m_dataLength = (m_endPtr - m_readPtr) + (m_writePtr - m_buffer); + } + return m_dataLength; +} + +void AudioBuffer::bytesWritten(size_t bw) { + m_writePtr += bw; + if(m_writePtr == m_endPtr) { + m_writePtr = m_buffer; + } + if(bw && m_f_start) + m_f_start = false; +} + +void AudioBuffer::bytesWasRead(size_t br) { + m_readPtr += br; + if(m_readPtr >= m_endPtr) { + size_t tmp = m_readPtr - m_endPtr; + m_readPtr = m_buffer + tmp; + } +} + +uint8_t* AudioBuffer::getWritePtr() { + return m_writePtr; +} + +uint8_t* AudioBuffer::getReadPtr() { + size_t len = m_endPtr - m_readPtr; + if(len < m_maxBlockSize) { // be sure the last frame is completed + memcpy(m_endPtr, m_buffer, m_maxBlockSize - len); // cpy from m_buffer to m_endPtr with len + } +return m_readPtr; +} + +void AudioBuffer::resetBuffer() { + m_writePtr = m_buffer; + m_readPtr = m_buffer; + m_endPtr = m_buffer + m_buffSize; + m_f_start = true; + // memset(m_buffer, 0, m_buffSize); //Clear Inputbuffer +} + +uint32_t AudioBuffer::getWritePos() { + return m_writePtr - m_buffer; +} + +uint32_t AudioBuffer::getReadPos() { + return m_readPtr - m_buffer; +} +//--------------------------------------------------------------------------------------------------------------------- +Audio::Audio(bool internalDAC /* = false */, i2s_dac_mode_t channelEnabled /* = I2S_DAC_CHANNEL_LEFT_EN */ ) { + clientsecure.setInsecure(); // if that can't be resolved update to ESP32 Arduino version 1.0.5-rc05 or higher + m_f_channelEnabled = channelEnabled; + m_f_internalDAC = internalDAC; + //i2s configuration + m_i2s_num = I2S_NUM_0; // i2s port number + m_i2s_config.sample_rate = 16000; + m_i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT; + m_i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; + m_i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; // high interrupt priority + m_i2s_config.dma_buf_count = 8; // max buffers + m_i2s_config.dma_buf_len = 1024; // max value + m_i2s_config.use_apll = APLL_DISABLE; // must be disabled in V2.0.1-RC1 + m_i2s_config.tx_desc_auto_clear = true; // new in V1.0.1 + m_i2s_config.fixed_mclk = I2S_PIN_NO_CHANGE; + if (internalDAC) { + log_i("internal DAC"); + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN ); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); + i2s_set_dac_mode(m_f_channelEnabled); + if(m_f_channelEnabled != I2S_DAC_CHANNEL_BOTH_EN) { + m_f_forceMono = true; + } + } + else { + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // Arduino vers. > 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); + m_f_forceMono = false; + } + + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); + + for(int i = 0; i <3; i++) { + m_filter[i].a0 = 1; + m_filter[i].a1 = 0; + m_filter[i].a2 = 0; + m_filter[i].b1 = 0; + m_filter[i].b2 = 0; + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::initInBuff() { + if(!m_f_initInbuffOnce) { + size_t size = InBuff.init(); + if(size == m_buffSizeRAM - m_resBuffSizeRAM) { + sprintf(chbuf, "PSRAM not found, inputBufferSize: %u bytes", size - 1); + if(audio_info) + audio_info(chbuf); + m_f_psram = false; + m_f_initInbuffOnce = true; + } + if(size == m_buffSizePSRAM - m_resBuffSizePSRAM) { + sprintf(chbuf, "PSRAM found, inputBufferSize: %u bytes", size - 1); + if(audio_info) + audio_info(chbuf); + m_f_psram = true; + m_f_initInbuffOnce = true; + } + } + changeMaxBlockSize(1600); // default size mp3 or aac +} +//--------------------------------------------------------------------------------------------------------------------- +esp_err_t Audio::I2Sstart(uint8_t i2s_num) { + // It is not necessary to call this function after i2s_driver_install() (it is started automatically), + // however it is necessary to call it after i2s_stop() + return i2s_start((i2s_port_t) i2s_num); +} + +esp_err_t Audio::I2Sstop(uint8_t i2s_num) { + return i2s_stop((i2s_port_t) i2s_num); +} +//--------------------------------------------------------------------------------------------------------------------- +esp_err_t Audio::i2s_mclk_pin_select(const uint8_t pin) { + if(pin != 0 && pin != 1 && pin != 3) { + ESP_LOGE(TAG, "Only support GPIO0/GPIO1/GPIO3, gpio_num:%d", pin); + return ESP_ERR_INVALID_ARG; + } + switch(pin){ + case 0: + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + WRITE_PERI_REG(PIN_CTRL, 0xFFF0); + break; + case 1: + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3); + WRITE_PERI_REG(PIN_CTRL, 0xF0F0); + break; + case 3: + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2); + WRITE_PERI_REG(PIN_CTRL, 0xFF00); + break; + default: + break; + } + return ESP_OK; +} +//--------------------------------------------------------------------------------------------------------------------- +Audio::~Audio() { + //I2Sstop(m_i2s_num); + //InBuff.~AudioBuffer(); #215 the AudioBuffer is automatically destroyed by the destructor + m_f_initInbuffOnce = false; + setDefaults(); + if(m_playlistBuff) {free(m_playlistBuff); m_playlistBuff = NULL;} + i2s_driver_uninstall((i2s_port_t)m_i2s_num); // #215 free I2S buffer +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setDefaults() { + stopSong(); + initInBuff(); // initialize InputBuffer if not already done + InBuff.resetBuffer(); + MP3Decoder_FreeBuffers(); + FLACDecoder_FreeBuffers(); + if(!m_f_m3u8data) AACDecoder_FreeBuffers(); + if(!m_f_m3u8data) if(m_playlistBuff) {free(m_playlistBuff); m_playlistBuff = NULL;} // free if not m3u8 + client.stop(); + client.flush(); // release memory + clientsecure.stop(); + clientsecure.flush(); + while(!playI2Sremains()){;} + + sprintf(chbuf, "buffers freed, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + + m_f_chunked = false; // Assume not chunked + m_f_ctseen = false; // Contents type not seen yet + m_f_firstmetabyte = false; + m_f_localfile = false; // SPIFFS or SD? (onnecttoFS) + m_f_playing = false; + m_f_ssl = false; + m_f_swm = true; // Assume no metaint (stream without metadata) + m_f_webfile = false; // Assume radiostream (connecttohost) + m_f_webstream = false; + m_f_tts = false; + m_f_firstCall = true; // InitSequence for processWebstream and processLokalFile + m_f_running = false; + m_f_loop = false; // Set if audio file should loop + m_f_unsync = false; // set within ID3 tag but not used + m_f_exthdr = false; // ID3 extended header + m_f_rtsp = false; // RTSP (m3u8)stream + m_f_m3u8data = false; // set again in processM3U8entries() if necessary + m_f_Log = true; // logging always allowed + + m_codec = CODEC_NONE; + m_playlistFormat = FORMAT_NONE; + m_datamode = AUDIO_NONE; + m_audioCurrentTime = 0; // Reset playtimer + m_audioFileDuration = 0; + m_audioDataStart = 0; + m_audioDataSize = 0; + m_avr_bitrate = 0; // the same as m_bitrate if CBR, median if VBR + m_bitRate = 0; // Bitrate still unknown + m_bytesNotDecoded = 0; // counts all not decodable bytes + m_chunkcount = 0; // for chunked streams + m_contentlength = 0; // If Content-Length is known, count it + m_curSample = 0; + m_metaint = 0; // No metaint yet + m_LFcount = 0; // For end of header detection + m_st_remember = 0; // Delete the last streamtitle hash + m_controlCounter = 0; // Status within readID3data() and readWaveHeader() + m_channels = 2; // assume stereo #209 + + //TEST loop + m_file_size = 0; + //TEST loop +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::httpPrint(const char* url) { + + // call to a new subdomain or if no connection is present connect first + + char* host = NULL; + char* extension = NULL; + char resp[256 + 100]; + uint8_t p1 = 0, p2 = 0; + + if(startsWith(url, "http")){if(m_f_ssl) p1 = 8; else p1 = 7;} + + p2 = indexOf(url, "/", p1); + host = strndup(url + p1, p2 - p1); + + extension = strdup(url + p2 + 1); + +// log_i("host %s", host); +// log_i("extension %s", extension); + + resp[0] = '\0'; + strcat(resp, "GET /"); + strcat(resp, extension); + strcat(resp, " HTTP/1.1\r\n"); + strcat(resp, "Host: "); + strcat(resp, host); + strcat(resp, "\r\nUser-Agent: ESP32 audioI2S\r\n"); + strcat(resp, "icy-metadata: 1\r\n"); + strcat(resp, "Accept-Encoding: identity\r\n"); + strcat(resp, "Connection: Keep-Alive\r\n\r\n"); + + int pos_colon = indexOf(host, ":", 0); + int pos_ampersand = indexOf(host, "&", 0); + int port = 80; + if(m_f_ssl) port = 443; + + if((pos_colon >= 0) && ((pos_ampersand == -1) or (pos_ampersand > pos_colon))){ + port = atoi(host + pos_colon + 1);// Get portnumber as integer + host[pos_colon] = '\0';// Host without portnumber + } + + if(!m_f_ssl){ + if(!client.connected()){ + if(m_f_Log) sprintf(chbuf, "new connection, host=%s, extension=%s, port=%i", host, extension, port); + if(m_f_Log) if(audio_info) audio_info(chbuf); + client.connect(host, port); + if(m_f_m3u8data && m_playlistBuff) strcpy(m_playlistBuff, url); // save new m3u8 chunklist + } + client.print(resp); + + } + else{ + if(!clientsecure.connected()){ + if(m_f_Log) sprintf(chbuf, "new connection, host=%s, extension=%s, port=%i", host, extension, port); + if(m_f_Log) if(audio_info) audio_info(chbuf); + clientsecure.connect(host, port); + if(m_f_m3u8data && m_playlistBuff) strcpy(m_playlistBuff, url); // save new m3u8 chunklist + } + clientsecure.print(resp); + } + + + if(host) {free(host); host = NULL;} + if(extension) {free(extension); extension = NULL;} + + strcpy(m_lastHost, url); + + return; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { + // user and pwd for authentification only, can be empty + + char* l_host = NULL; // local copy of host + char* h_host = NULL; // pointer of l_host without http:// or https:// + + + if(strlen(host) == 0) { + if(audio_info) audio_info("Hostaddress is empty"); + return false; + } + + if(strlen(host) >= 512 - 10) { + if(audio_info) audio_info("Hostaddress is too long"); + return false; + } + + l_host = (char*)malloc(strlen(host) + 10); + strcpy(l_host, host); + trim(l_host); + int p = indexOf(l_host, "http", 0); + if(p > 0){ + if(audio_info) audio_info("Hostaddress is wrong"); + if(l_host) { free(l_host); l_host = NULL;} + return false; + } + if(p < 0){ // http not found, shift right +7, then insert http:// + for(int i = strlen(l_host) + 1; i >= 0; i--){ + l_host[i + 7] = l_host[i]; + } + memcpy(l_host, "http://", 7); + } + + setDefaults(); + + sprintf(chbuf, "Connect to new host: \"%s\"", l_host); + if(audio_info) audio_info(chbuf); + + // authentification + uint8_t auth = strlen(user) + strlen(pwd); + char toEncode[auth + 4]; + toEncode[0] = '\0'; + strcat(toEncode, user); + strcat(toEncode, ":"); + strcat(toEncode, pwd); + char authorization[base64_encode_expected_len(strlen(toEncode)) + 1]; + authorization[0] = '\0'; + b64encode((const char*)toEncode, strlen(toEncode), authorization); + + // initializationsequence + int16_t pos_slash; // position of "/" in hostname + int16_t pos_colon; // position of "/" in hostname + int16_t pos_ampersand; // position of "&" in hostname + uint16_t port = 80; // port number + m_f_webstream = true; + setDatamode(AUDIO_HEADER); // Handle header + + h_host = l_host; + + if(startsWith(l_host, "http://")) { + h_host += 7; + m_f_ssl = false; + } + + if(startsWith(l_host, "https://")) { + h_host += 8; + m_f_ssl = true; + port = 443; + } + + // Is it a playlist? + if(endsWith(h_host, ".m3u" )) {m_playlistFormat = FORMAT_M3U; m_datamode = AUDIO_PLAYLISTINIT;} + if(endsWith(h_host, ".pls" )) {m_playlistFormat = FORMAT_PLS; m_datamode = AUDIO_PLAYLISTINIT;} + if(endsWith(h_host, ".asx" )) {m_playlistFormat = FORMAT_ASX; m_datamode = AUDIO_PLAYLISTINIT;} + // if url ...=asx www.fantasyfoxradio.de/infusions/gr_radiostatus_panel/gr_radiostatus_player.php?id=2&p=asx + if(endsWith(h_host, "=asx" )) {m_playlistFormat = FORMAT_ASX; m_datamode = AUDIO_PLAYLISTINIT;} + if(endsWith(h_host, "=pls" )) {m_playlistFormat = FORMAT_PLS; m_datamode = AUDIO_PLAYLISTINIT;} + // if url "http://n3ea-e2.revma.ihrhls.com/zc7729/hls.m3u8?rj-ttl=5&rj-tok=AAABe8unpPAAyu36Dkm0J4mzJg" + if(indexOf(h_host, ".m3u8", 0) >10) {m_playlistFormat = FORMAT_M3U8; m_datamode = AUDIO_PLAYLISTINIT;} + + // In the URL there may be an extension, like noisefm.ru:8000/play.m3u&t=.m3u + pos_slash = indexOf(h_host, "/", 0); + pos_colon = indexOf(h_host, ":", 0); + pos_ampersand = indexOf(h_host, "&", 0); + + char *hostwoext = NULL; // "skonto.ls.lv:8002" in "skonto.ls.lv:8002/mp3" + char *extension = NULL; // "/mp3" in "skonto.ls.lv:8002/mp3" + + if(pos_slash > 1) { + uint8_t hostwoextLen = pos_slash; + hostwoext = (char*)malloc(hostwoextLen + 1); + memcpy(hostwoext, h_host, hostwoextLen); + hostwoext[hostwoextLen] = '\0'; + uint16_t extLen = urlencode_expected_len(h_host + pos_slash); + extension = (char *)malloc(extLen); + memcpy(extension, h_host + pos_slash, extLen); + trim(extension); + urlencode(extension, extLen, true); + } + else{ // url has no extension + hostwoext = strdup(h_host); + extension = strdup("/"); + } + + if((pos_colon >= 0) && ((pos_ampersand == -1) or (pos_ampersand > pos_colon))){ + port = atoi(h_host + pos_colon + 1);// Get portnumber as integer + hostwoext[pos_colon] = '\0';// Host without portnumber + } + + sprintf(chbuf, "Connect to \"%s\" on port %d, extension \"%s\"", hostwoext, port, extension); + if(audio_info) audio_info(chbuf); + + char resp[strlen(h_host) + strlen(authorization) + 100]; + resp[0] = '\0'; + + strcat(resp, "GET "); + strcat(resp, extension); + strcat(resp, " HTTP/1.0\r\n"); + strcat(resp, "Host: "); + strcat(resp, hostwoext); + strcat(resp, "\r\n"); + strcat(resp, "Icy-MetaData:1\r\n"); + strcat(resp, "Authorization: Basic "); + strcat(resp, authorization); + strcat(resp, "\r\n"); + strcat(resp, "User-Agent: ESP32 audioI2S\r\n"); +// strcat(resp, "Accept-Encoding: gzip;q=0\r\n"); // otherwise the server assumes gzip compression +// strcat(resp, "Transfer-Encoding: \r\n"); // otherwise the server assumes gzip compression + strcat(resp, "Connection: keep-alive\r\n\r\n"); + + const uint32_t TIMEOUT_MS{250}; + uint32_t wtf; + if(m_f_ssl == false) { + uint32_t t = millis(); + if(client.connect(hostwoext, port, TIMEOUT_MS)) { + client.setNoDelay(true); + client.print(resp); + uint32_t dt = millis() - t; + sprintf(chbuf, "Connected to server in %u ms", dt); + if(audio_info) audio_info(chbuf); + + strcpy(m_lastHost, l_host); + m_f_running = true; + if(hostwoext) {free(hostwoext); hostwoext = NULL;} + if(extension) {free(extension); extension = NULL;} + if(l_host ) {free(l_host); l_host = NULL;} + wtf = millis(); + while(!client.connected()){ + if(millis()-wtf>TIMEOUT_MS * 2){ + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + } + } // wait until the connection is established + return true; + } + } + + const uint32_t TIMEOUT_MS_SSL{3700}; + + if(m_f_ssl == true) { + uint32_t t = millis(); + if(clientsecure.connect(hostwoext, port, TIMEOUT_MS_SSL)) { +// clientsecure.setNoDelay(true); + // if(audio_info) audio_info("SSL/TLS Connected to server"); + clientsecure.print(resp); + uint32_t dt = millis() - t; + sprintf(chbuf, "SSL has been established in %u ms, free Heap: %u bytes", dt, ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + + strcpy(m_lastHost, l_host); + m_f_running = true; + if(hostwoext) {free(hostwoext); hostwoext = NULL;} + if(extension) {free(extension); extension = NULL;} + if(l_host ) {free(l_host); l_host = NULL;} + //while(!clientsecure.connected()){;} // wait until the connection is established + wtf = millis(); + while(!clientsecure.connected()){ + /*wtf++; + if(wtf>1000){ + wtf=0; + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + }*/ + if(millis()-wtf>TIMEOUT_MS_SSL){ + sprintf(chbuf, "Request %s failed! with WTF", m_lastHost); + if(audio_info) audio_info(chbuf); + return false; + break; + } + } + return true; + } + } + sprintf(chbuf, "Request %s failed!", l_host); + if(audio_info) audio_info(chbuf); + if(audio_showstation) audio_showstation(""); + if(audio_showstreamtitle) audio_showstreamtitle(""); + if(audio_icydescription) audio_icydescription(""); + if(audio_icyurl) audio_icyurl(""); + m_lastHost[0] = 0; + if(hostwoext) {free(hostwoext); hostwoext = NULL;} + if(extension) {free(extension); extension = NULL;} + if(l_host ) {free(l_host); l_host = NULL;} + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setFileLoop(bool input){ + m_f_loop = input; + return input; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::UTF8toASCII(char* str){ + +#ifdef SDFATFS_USED + //UTF8->UTF16 (lowbyte) + const uint8_t ascii[60] = { + //129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148 // UTF8(C3) + // Ä Å Æ Ç É Ñ // CHAR + 000, 000, 000, 0xC4, 143, 0xC6,0xC7, 000,0xC9,000, 000, 000, 000, 000, 000, 000, 0xD1, 000, 000, 000, // ASCII (Latin1) + //149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168 + // Ö Ü ß à ä å æ è + 000, 0xD6,000, 000, 000, 000, 000, 0xDC, 000, 000, 0xDF,0xE0, 000, 000, 000,0xE4,0xE5,0xE6, 000,0xE8, + //169, 170, 171, 172. 173. 174. 175, 176, 177, 179, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188 + // ê ë ì î ï ñ ò ô ö ù û ü + 000, 0xEA, 0xEB,0xEC, 000,0xEE,0xEB, 000,0xF1,0xF2, 000,0xF4, 000,0xF6, 000, 000,0xF9, 000,0xFB,0xFC}; +#else + const uint8_t ascii[60] = { + //129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148 // UTF8(C3) + // Ä Å Æ Ç É Ñ // CHAR + 000, 000, 000, 142, 143, 146, 128, 000, 144, 000, 000, 000, 000, 000, 000, 000, 165, 000, 000, 000, // ASCII + //149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168 + // Ö Ü ß à ä å æ è + 000, 153, 000, 000, 000, 000, 000, 154, 000, 000, 225, 133, 000, 000, 000, 132, 134, 145, 000, 138, + //169, 170, 171, 172. 173. 174. 175, 176, 177, 179, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188 + // ê ë ì î ï ñ ò ô ö ù û ü + 000, 136, 137, 141, 000, 140, 139, 000, 164, 149, 000, 147, 000, 148, 000, 000, 151, 000, 150, 129}; +#endif + + uint16_t i = 0, j=0, s = 0; + bool f_C3_seen = false; + + while(str[i] != 0) { // convert UTF8 to ASCII + if(str[i] == 195){ // C3 + i++; + f_C3_seen = true; + continue; + } + str[j] = str[i]; + if(str[j] > 128 && str[j] < 189 && f_C3_seen == true) { + s = ascii[str[j] - 129]; + if(s != 0) str[j] = s; // found a related ASCII sign + f_C3_seen = false; + } + i++; j++; + } + str[j] = 0; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttoSD(const char* path) { + return connecttoFS(SD, path); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttoFS(fs::FS &fs, const char* path) { + + if(strlen(path)>255) return false; + char audioName[256]; + setDefaults(); // free buffers an set defaults + memcpy(audioName, path, strlen(path)+1); + if(audioName[0] != '/'){ + for(int i = 255; i > 0; i--){ + audioName[i] = audioName[i-1]; + } + audioName[0] = '/'; + } + + sprintf(chbuf, "Reading file: \"%s\"", audioName); + if(audio_info) {vTaskDelay(2); audio_info(chbuf);} + + if(fs.exists(audioName)) { + audiofile = fs.open(audioName); // #86 + } + else { + UTF8toASCII(audioName); + if(fs.exists(audioName)) { + audiofile = fs.open(audioName); + } + } + + if(!audiofile) { + if(audio_info) {vTaskDelay(2); audio_info("Failed to open file for reading");} + return false; + } + + m_f_localfile = true; + m_file_size = audiofile.size();//TEST loop + + char* afn = NULL; // audioFileName + +#ifdef SDFATFS_USED + audiofile.getName(chbuf, sizeof(chbuf)); + afn = strdup(chbuf); +#else + afn = strdup(audiofile.name()); +#endif + + uint8_t dotPos = lastIndexOf(afn, "."); + for(uint8_t i = dotPos + 1; i < strlen(afn); i++){ + afn[i] = toLowerCase(afn[i]); + } + + if(endsWith(afn, ".mp3")){ // MP3 section + free(afn); + m_codec = CODEC_MP3; + if(!MP3Decoder_AllocateBuffers()){audiofile.close(); return false;} + InBuff.changeMaxBlockSize(m_frameSizeMP3); + sprintf(chbuf, "MP3Decoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + m_f_running = true; + return true; + } // end MP3 section + + if(endsWith(afn, ".m4a")){ // M4A section, iTunes + free(afn); + m_codec = CODEC_M4A; + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + }; + m_f_running = true; + return true; + } // end M4A section + + if(endsWith(afn, ".aac")){ // AAC section, without FileHeader + free(afn); + m_codec = CODEC_AAC; + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + m_f_running = true; + return true; + } // end AAC section + + if(endsWith(afn, ".wav")){ // WAVE section + free(afn); + m_codec = CODEC_WAV; + InBuff.changeMaxBlockSize(m_frameSizeWav); + m_f_running = true; + return true; + } // end WAVE section + + if(endsWith(afn, ".flac")) { // FLAC section + free(afn); + m_codec = CODEC_FLAC; + if(!psramFound()){ + if(audio_info) audio_info("FLAC works only with PSRAM!"); + m_f_running = false; + return false; + } + if(!FLACDecoder_AllocateBuffers()){audiofile.close(); return false;} + InBuff.changeMaxBlockSize(m_frameSizeFLAC); + sprintf(chbuf, "FLACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + m_f_running = true; + return true; + } // end FLAC section + + sprintf(chbuf, "The %s format is not supported", afn + dotPos); + if(audio_info) audio_info(chbuf); + audiofile.close(); + if(afn) free(afn); + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::connecttospeech(const char* speech, const char* lang){ + + setDefaults(); + char host[] = "translate.google.com.vn"; + char path[] = "/translate_tts"; + + uint16_t speechLen = strlen(speech); + uint16_t speechBuffLen = speechLen + 300; + memcpy(m_lastHost, speech, 256); + char* speechBuff = (char*)malloc(speechBuffLen); + if(!speechBuff) {log_e("out of memory"); return false;} + memcpy(speechBuff, speech, speechLen); + speechBuff[speechLen] = '\0'; + urlencode(speechBuff, speechBuffLen); + + char resp[strlen(speechBuff) + 200] = ""; + strcat(resp, "GET "); + strcat(resp, path); + strcat(resp, "?ie=UTF-8&tl="); + strcat(resp, lang); + strcat(resp, "&client=tw-ob&q="); + strcat(resp, speechBuff); + strcat(resp, " HTTP/1.1\r\n"); + strcat(resp, "Host: "); + strcat(resp, host); + strcat(resp, "\r\n"); + strcat(resp, "User-Agent: Mozilla/5.0 \r\n"); + strcat(resp, "Accept-Encoding: identity\r\n"); + strcat(resp, "Accept: text/html\r\n"); + strcat(resp, "Connection: close\r\n\r\n"); + + free(speechBuff); + + if(!client.connect(host, 80)) { + log_e("Connection failed"); + return false; + } + client.print(resp); + if(audio_info) audio_info(chbuf); + + m_f_webstream = true; + m_f_running = true; + m_f_ssl = false; + m_f_tts = true; + setDatamode(AUDIO_HEADER); + + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::urlencode(char* buff, uint16_t buffLen, bool spacesOnly) { + + uint16_t len = strlen(buff); + uint8_t* tmpbuff = (uint8_t*)malloc(buffLen); + if(!tmpbuff) {log_e("out of memory"); return;} + char c; + char code0; + char code1; + uint16_t j = 0; + for(int i = 0; i < len; i++) { + c = buff[i]; + if(isalnum(c)) tmpbuff[j++] = c; + else if(spacesOnly){ + if(c == ' '){ + tmpbuff[j++] = '%'; + tmpbuff[j++] = '2'; + tmpbuff[j++] = '0'; + } + else{ + tmpbuff[j++] = c; + } + } + else { + code1 = (c & 0xf) + '0'; + if((c & 0xf) > 9) code1 = (c & 0xf) - 10 + 'A'; + c = (c >> 4) & 0xf; + code0 = c + '0'; + if(c > 9) code0 = c - 10 + 'A'; + tmpbuff[j++] = '%'; + tmpbuff[j++] = code0; + tmpbuff[j++] = code1; + } + if(j == buffLen - 1){ + log_e("out of memory"); + break; + } + } + memcpy(buff, tmpbuff, j); + buff[j] ='\0'; + free(tmpbuff); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showID3Tag(const char* tag, const char* value){ + + chbuf[0] = 0; + // V2.2 + if(!strcmp(tag, "CNT")) sprintf(chbuf, "Play counter: %s", value); + // if(!strcmp(tag, "COM")) sprintf(chbuf, "Comments: %s", value); + if(!strcmp(tag, "CRA")) sprintf(chbuf, "Audio encryption: %s", value); + if(!strcmp(tag, "CRM")) sprintf(chbuf, "Encrypted meta frame: %s", value); + if(!strcmp(tag, "ETC")) sprintf(chbuf, "Event timing codes: %s", value); + if(!strcmp(tag, "EQU")) sprintf(chbuf, "Equalization: %s", value); + if(!strcmp(tag, "IPL")) sprintf(chbuf, "Involved people list: %s", value); + if(!strcmp(tag, "PIC")) sprintf(chbuf, "Attached picture: %s", value); + if(!strcmp(tag, "SLT")) sprintf(chbuf, "Synchronized lyric/text: %s", value); + // if(!strcmp(tag, "TAL")) sprintf(chbuf, "Album/Movie/Show title: %s", value); + if(!strcmp(tag, "TBP")) sprintf(chbuf, "BPM (Beats Per Minute): %s", value); + if(!strcmp(tag, "TCM")) sprintf(chbuf, "Composer: %s", value); + if(!strcmp(tag, "TCO")) sprintf(chbuf, "Content type: %s", value); + if(!strcmp(tag, "TCR")) sprintf(chbuf, "Copyright message: %s", value); + if(!strcmp(tag, "TDA")) sprintf(chbuf, "Date: %s", value); + if(!strcmp(tag, "TDY")) sprintf(chbuf, "Playlist delay: %s", value); + if(!strcmp(tag, "TEN")) sprintf(chbuf, "Encoded by: %s", value); + if(!strcmp(tag, "TFT")) sprintf(chbuf, "File type: %s", value); + if(!strcmp(tag, "TIM")) sprintf(chbuf, "Time: %s", value); + if(!strcmp(tag, "TKE")) sprintf(chbuf, "Initial key: %s", value); + if(!strcmp(tag, "TLA")) sprintf(chbuf, "Language(s): %s", value); + if(!strcmp(tag, "TLE")) sprintf(chbuf, "Length: %s", value); + if(!strcmp(tag, "TMT")) sprintf(chbuf, "Media type: %s", value); + if(!strcmp(tag, "TOA")) sprintf(chbuf, "Original artist(s)/performer(s): %s", value); + if(!strcmp(tag, "TOF")) sprintf(chbuf, "Original filename: %s", value); + if(!strcmp(tag, "TOL")) sprintf(chbuf, "Original Lyricist(s)/text writer(s): %s", value); + if(!strcmp(tag, "TOR")) sprintf(chbuf, "Original release year: %s", value); + if(!strcmp(tag, "TOT")) sprintf(chbuf, "Original album/Movie/Show title: %s", value); + if(!strcmp(tag, "TP1")) sprintf(chbuf, "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group: %s", value); + if(!strcmp(tag, "TP2")) sprintf(chbuf, "Band/Orchestra/Accompaniment: %s", value); + if(!strcmp(tag, "TP3")) sprintf(chbuf, "Conductor/Performer refinement: %s", value); + if(!strcmp(tag, "TP4")) sprintf(chbuf, "Interpreted, remixed, or otherwise modified by: %s", value); + if(!strcmp(tag, "TPA")) sprintf(chbuf, "Part of a set: %s", value); + if(!strcmp(tag, "TPB")) sprintf(chbuf, "Publisher: %s", value); + if(!strcmp(tag, "TRC")) sprintf(chbuf, "ISRC (International Standard Recording Code): %s", value); + if(!strcmp(tag, "TRD")) sprintf(chbuf, "Recording dates: %s", value); + if(!strcmp(tag, "TRK")) sprintf(chbuf, "Track number/Position in set: %s", value); + if(!strcmp(tag, "TSI")) sprintf(chbuf, "Size: %s", value); + if(!strcmp(tag, "TSS")) sprintf(chbuf, "Software/hardware and settings used for encoding: %s", value); + if(!strcmp(tag, "TT1")) sprintf(chbuf, "Content group description: %s", value); + if(!strcmp(tag, "TT2")) sprintf(chbuf, "Title/Songname/Content description: %s", value); + if(!strcmp(tag, "TT3")) sprintf(chbuf, "Subtitle/Description refinement: %s", value); + if(!strcmp(tag, "TXT")) sprintf(chbuf, "Lyricist/text writer: %s", value); + if(!strcmp(tag, "TXX")) sprintf(chbuf, "User defined text information frame: %s", value); + if(!strcmp(tag, "TYE")) sprintf(chbuf, "Year: %s", value); + if(!strcmp(tag, "UFI")) sprintf(chbuf, "Unique file identifier: %s", value); + if(!strcmp(tag, "ULT")) sprintf(chbuf, "Unsychronized lyric/text transcription: %s", value); + if(!strcmp(tag, "WAF")) sprintf(chbuf, "Official audio file webpage: %s", value); + if(!strcmp(tag, "WAR")) sprintf(chbuf, "Official artist/performer webpage: %s", value); + if(!strcmp(tag, "WAS")) sprintf(chbuf, "Official audio source webpage: %s", value); + if(!strcmp(tag, "WCM")) sprintf(chbuf, "Commercial information: %s", value); + if(!strcmp(tag, "WCP")) sprintf(chbuf, "Copyright/Legal information: %s", value); + if(!strcmp(tag, "WPB")) sprintf(chbuf, "Publishers official webpage: %s", value); + if(!strcmp(tag, "WXX")) sprintf(chbuf, "User defined URL link frame: %s", value); + + // V2.3 V2.4 tags + // if(!strcmp(tag, "COMM")) sprintf(chbuf, "Comment: %s", value); + if(!strcmp(tag, "OWNE")) sprintf(chbuf, "Ownership: %s", value); + // if(!strcmp(tag, "PRIV")) sprintf(chbuf, "Private: %s", value); + if(!strcmp(tag, "SYLT")) sprintf(chbuf, "SynLyrics: %s", value); + if(!strcmp(tag, "TALB")) sprintf(chbuf, "Album: %s", value); + if(!strcmp(tag, "TBPM")) sprintf(chbuf, "BeatsPerMinute: %s", value); + if(!strcmp(tag, "TCMP")) sprintf(chbuf, "Compilation: %s", value); + if(!strcmp(tag, "TCOM")) sprintf(chbuf, "Composer: %s", value); + if(!strcmp(tag, "TCON")) sprintf(chbuf, "ContentType: %s", value); + if(!strcmp(tag, "TCOP")) sprintf(chbuf, "Copyright: %s", value); + if(!strcmp(tag, "TDAT")) sprintf(chbuf, "Date: %s", value); + if(!strcmp(tag, "TEXT")) sprintf(chbuf, "Lyricist: %s", value); + if(!strcmp(tag, "TIME")) sprintf(chbuf, "Time: %s", value); + if(!strcmp(tag, "TIT1")) sprintf(chbuf, "Grouping: %s", value); + if(!strcmp(tag, "TIT2")) sprintf(chbuf, "Title: %s", value); + if(!strcmp(tag, "TIT3")) sprintf(chbuf, "Subtitle: %s", value); + if(!strcmp(tag, "TLAN")) sprintf(chbuf, "Language: %s", value); + if(!strcmp(tag, "TLEN")) sprintf(chbuf, "Length (ms): %s", value); + if(!strcmp(tag, "TMED")) sprintf(chbuf, "Media: %s", value); + if(!strcmp(tag, "TOAL")) sprintf(chbuf, "OriginalAlbum: %s", value); + if(!strcmp(tag, "TOPE")) sprintf(chbuf, "OriginalArtist: %s", value); + if(!strcmp(tag, "TORY")) sprintf(chbuf, "OriginalReleaseYear: %s", value); + if(!strcmp(tag, "TPE1")) sprintf(chbuf, "Artist: %s", value); + if(!strcmp(tag, "TPE2")) sprintf(chbuf, "Band: %s", value); + if(!strcmp(tag, "TPE3")) sprintf(chbuf, "Conductor: %s", value); + if(!strcmp(tag, "TPE4")) sprintf(chbuf, "InterpretedBy: %s", value); + if(!strcmp(tag, "TPOS")) sprintf(chbuf, "PartOfSet: %s", value); + if(!strcmp(tag, "TPUB")) sprintf(chbuf, "Publisher: %s", value); + if(!strcmp(tag, "TRCK")) sprintf(chbuf, "Track: %s", value); + if(!strcmp(tag, "TSSE")) sprintf(chbuf, "SettingsForEncoding: %s", value); + if(!strcmp(tag, "TRDA")) sprintf(chbuf, "RecordingDates: %s", value); + if(!strcmp(tag, "TXXX")) sprintf(chbuf, "UserDefinedText: %s", value); + if(!strcmp(tag, "TYER")) sprintf(chbuf, "Year: %s", value); + if(!strcmp(tag, "USER")) sprintf(chbuf, "TermsOfUse: %s", value); + if(!strcmp(tag, "USLT")) sprintf(chbuf, "Lyrics: %s", value); + if(!strcmp(tag, "WOAR")) sprintf(chbuf, "OfficialArtistWebpage: %s", value); + if(!strcmp(tag, "XDOR")) sprintf(chbuf, "OriginalReleaseTime: %s", value); + + latinToUTF8(chbuf, sizeof(chbuf)); + if(chbuf[0] != 0) if(audio_id3data) audio_id3data(chbuf); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::unicode2utf8(char* buff, uint32_t len){ + // converts unicode in UTF-8, buff contains the string to be converted up to len + // range U+1 ... U+FFFF + uint8_t* tmpbuff = (uint8_t*)malloc(len * 2); + if(!tmpbuff) {log_e("out of memory"); return;} + bool bitorder = false; + uint16_t j = 0; + uint16_t k = 0; + uint16_t m = 0; + uint8_t uni_h = 0; + uint8_t uni_l = 0; + + while(m < len - 1) { + if((buff[m] == 0xFE) && (buff[m + 1] == 0xFF)) { + bitorder = true; + j = m + 2; + } // LSB/MSB + if((buff[m] == 0xFF) && (buff[m + 1] == 0xFE)) { + bitorder = false; + j = m + 2; + } // MSB/LSB + m++; + } // seek for last bitorder + m = 0; + if(j > 0) { + for(k = j; k < len; k += 2) { + if(bitorder == true) { + uni_h = (uint8_t)buff[k]; + uni_l = (uint8_t)buff[k + 1]; + } + else { + uni_l = (uint8_t)buff[k]; + uni_h = (uint8_t)buff[k + 1]; + } + + uint16_t uni_hl = ((uni_h << 8) | uni_l); + + if (uni_hl < 0X80){ + tmpbuff[m] = uni_l; + m++; + } + else if (uni_hl < 0X800) { + tmpbuff[m]= ((uni_hl >> 6) | 0XC0); + m++; + tmpbuff[m] =((uni_hl & 0X3F) | 0X80); + m++; + } + else { + tmpbuff[m] = ((uni_hl >> 12) | 0XE0); + m++; + tmpbuff[m] = (((uni_hl >> 6) & 0X3F) | 0X80); + m++; + tmpbuff[m] = ((uni_hl & 0X3F) | 0X80); + m++; + } + } + } + buff[m] = 0; + memcpy(buff, tmpbuff, m); + free(tmpbuff); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::latinToUTF8(char* buff, size_t bufflen){ + // most stations send strings in UTF-8 but a few sends in latin. To standardize this, all latin strings are + // converted to UTF-8. If UTF-8 is already present, nothing is done and true is returned. + // A conversion to UTF-8 extends the string. Therefore it is necessary to know the buffer size. If the converted + // string does not fit into the buffer, false is returned + // utf8 bytelength: >=0xF0 3 bytes, >=0xE0 2 bytes, >=0xC0 1 byte, e.g. e293ab is ⓫ + + uint16_t pos = 0; + uint8_t ext_bytes = 0; + uint16_t len = strlen(buff); + uint8_t c; + + while(pos < len){ + c = buff[pos]; + if(c >= 0xC2) { // is UTF8 char + pos++; + if(c >= 0xC0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + if(c >= 0xE0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + if(c >= 0xF0 && buff[pos] < 0x80) {ext_bytes++; pos++;} + } + else pos++; + } + if(!ext_bytes) return true; // is UTF-8, do nothing + + pos = 0; + + while(buff[pos] != 0){ + len = strlen(buff); + if(buff[pos] >= 0x80 && buff[pos+1] < 0x80){ // is not UTF8, is latin? + for(int i = len+1; i > pos; i--){ + buff[i+1] = buff[i]; + } + uint8_t c = buff[pos]; + buff[pos++] = 0xc0 | ((c >> 6) & 0x1f); // 2+1+5 bits + buff[pos++] = 0x80 | ((char)c & 0x3f); // 1+1+6 bits + } + pos++; + if(pos > bufflen -3){ + buff[bufflen -1] = '\0'; + return false; // do not overwrite + } + } + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_WAV_Header(uint8_t* data, size_t len) { + static size_t headerSize; + static uint32_t cs = 0; + static uint8_t bts = 0; + + if(m_controlCounter == 0){ + m_controlCounter ++; + if((*data != 'R') || (*(data + 1) != 'I') || (*(data + 2) != 'F') || (*(data + 3) != 'F')) { + if(audio_info) audio_info("file has no RIFF tag"); + headerSize = 0; + return -1; //false; + } + else{ + headerSize = 4; + return 4; // ok + } + } + + if(m_controlCounter == 1){ + m_controlCounter ++; + cs = (uint32_t) (*data + (*(data + 1) << 8) + (*(data + 2) << 16) + (*(data + 3) << 24) - 8); + headerSize += 4; + return 4; // ok + } + + if(m_controlCounter == 2){ + m_controlCounter ++; + if((*data != 'W') || (*(data + 1) != 'A') || (*(data + 2) != 'V') || (*(data + 3) != 'E')) { + if(audio_info) audio_info("format tag is not WAVE"); + return -1;//false; + } + else { + headerSize += 4; + return 4; + } + } + + if(m_controlCounter == 3){ + if((*data == 'f') && (*(data + 1) == 'm') && (*(data + 2) == 't')) { + m_controlCounter ++; + headerSize += 4; + return 4; + } + else{ + headerSize += 4; + return 4; + } + } + + if(m_controlCounter == 4){ + m_controlCounter ++; + cs = (uint32_t) (*data + (*(data + 1) << 8)); + if(cs > 40) return -1; //false, something going wrong + bts = cs - 16; // bytes to skip if fmt chunk is >16 + headerSize += 4; + return 4; + } + + if(m_controlCounter == 5){ + m_controlCounter ++; + uint16_t fc = (uint16_t) (*(data + 0) + (*(data + 1) << 8)); // Format code + uint16_t nic = (uint16_t) (*(data + 2) + (*(data + 3) << 8)); // Number of interleaved channels + uint32_t sr = (uint32_t) (*(data + 4) + (*(data + 5) << 8) + + (*(data + 6) << 16) + (*(data + 7) << 24)); // Samplerate + uint32_t dr = (uint32_t) (*(data + 8) + (*(data + 9) << 8) + + (*(data + 10) << 16) + (*(data + 11) << 24)); // Datarate + uint16_t dbs = (uint16_t) (*(data + 12) + (*(data + 13) << 8)); // Data block size + uint16_t bps = (uint16_t) (*(data + 14) + (*(data + 15) << 8)); // Bits per sample + if(audio_info) { + sprintf(chbuf, "FormatCode: %u", fc); audio_info(chbuf); + // sprintf(chbuf, "Channel: %u", nic); audio_info(chbuf); + // sprintf(chbuf, "SampleRate: %u", sr); audio_info(chbuf); + sprintf(chbuf, "DataRate: %u", dr); audio_info(chbuf); + sprintf(chbuf, "DataBlockSize: %u", dbs); audio_info(chbuf); + // sprintf(chbuf, "BitsPerSample: %u", bps); audio_info(chbuf); + } + if((bps != 8) && (bps != 16)){ + sprintf(chbuf, "BitsPerSample is %u, must be 8 or 16" , bps); audio_info(chbuf); + stopSong(); + return -1; + } + if((nic != 1) && (nic != 2)){ + sprintf(chbuf, "num channels is %u, must be 1 or 2" , nic); audio_info(chbuf); + stopSong(); + return -1; + } + if(fc != 1) { + if(audio_info) audio_info("format code is not 1 (PCM)"); + stopSong(); + return -1 ; //false; + } + setBitsPerSample(bps); + setChannels(nic); + setSampleRate(sr); + setBitrate(nic * sr * bps); + // sprintf(chbuf, "BitRate: %u", m_bitRate); + // if(audio_info) audio_info(chbuf); + headerSize += 16; + return 16; // ok + } + + if(m_controlCounter == 6){ + m_controlCounter ++; + headerSize += bts; + return bts; // skip to data + } + + if(m_controlCounter == 7){ + if((*(data + 0) == 'd') && (*(data + 1) == 'a') && (*(data + 2) == 't') && (*(data + 3) == 'a')){ + m_controlCounter ++; + vTaskDelay(30); + headerSize += 4; + return 4; + } + else{ + headerSize ++; + return 1; + } + } + + if(m_controlCounter == 8){ + m_controlCounter ++; + size_t cs = *(data + 0) + (*(data + 1) << 8) + (*(data + 2) << 16) + (*(data + 3) << 24); //read chunkSize + headerSize += 4; + if(m_f_localfile) m_contentlength = getFileSize(); + if(cs){ + m_audioDataSize = cs - 44; + } + else { // sometimes there is nothing here + if(m_f_localfile) m_audioDataSize = getFileSize() - headerSize; + if(m_f_webfile) m_audioDataSize = m_contentlength - headerSize; + } + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + return 4; + } + m_controlCounter = 100; // header succesfully read + m_audioDataStart = headerSize; + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_FLAC_Header(uint8_t *data, size_t len) { + static size_t headerSize; + static size_t retvalue; + static bool f_lastMetaBlock; + + if(retvalue) { + if(retvalue > len) { // if returnvalue > bufferfillsize + if(len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize(); + retvalue -= len; // and wait for more bufferdata + return len; + } + else { + size_t tmp = retvalue; + retvalue = 0; + return tmp; + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_BEGIN) { // init + headerSize = 0; + retvalue = 0; + m_audioDataStart = 0; + f_lastMetaBlock = false; + m_controlCounter = FLAC_MAGIC; + if(m_f_localfile){ + m_contentlength = getFileSize(); + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_MAGIC) { /* check MAGIC STRING */ + if(specialIndexOf(data, "fLaC", 10) != 0) { + log_e("Magic String 'fLaC' not found in header"); + stopSong(); + return -1; + } +// log_i("Magig String found"); + m_controlCounter = FLAC_MBH; + headerSize = 4; + retvalue = 4; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_MBH) { /* METADATA_BLOCK_HEADER */ + uint8_t blockType = *data; + if(!f_lastMetaBlock){ + if(blockType & 128) {f_lastMetaBlock = true;} + blockType &= 127; + if(blockType == 0) m_controlCounter = FLAC_SINFO; + if(blockType == 1) m_controlCounter = FLAC_PADDING; + if(blockType == 2) m_controlCounter = FLAC_APP; + if(blockType == 3) m_controlCounter = FLAC_SEEK; + if(blockType == 4) m_controlCounter = FLAC_VORBIS; + if(blockType == 5) m_controlCounter = FLAC_CUESHEET; + if(blockType == 6) m_controlCounter = FLAC_PICTURE; + headerSize += 1; + retvalue = 1; + return 0; + } + m_controlCounter = FLAC_OKAY; + m_audioDataStart = headerSize; + m_audioDataSize = m_contentlength - m_audioDataStart; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + retvalue = 0; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_SINFO) { /* Stream info block */ + size_t l = bigEndian(data, 3); + vTaskDelay(2); + m_flacMaxBlockSize = bigEndian(data + 5, 2); + sprintf(chbuf, "FLAC maxBlockSize: %u", m_flacMaxBlockSize); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + m_flacMaxFrameSize = bigEndian(data + 10, 3); + if(m_flacMaxFrameSize){ + sprintf(chbuf, "FLAC maxFrameSize: %u", m_flacMaxFrameSize); if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("FLAC maxFrameSize: N/A"); + } + if(m_flacMaxFrameSize > InBuff.getMaxBlockSize()) { + log_e("FLAC maxFrameSize too large!"); + stopSong(); + return -1; + } +// InBuff.changeMaxBlockSize(m_flacMaxFrameSize); + vTaskDelay(2); + uint32_t nextval = bigEndian(data + 13, 3); + m_flacSampleRate = nextval >> 4; + sprintf(chbuf, "FLAC sampleRate: %u", m_flacSampleRate); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + m_flacNumChannels = ((nextval & 0x06) >> 1) + 1; + sprintf(chbuf, "FLAC numChannels: %u", m_flacNumChannels); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + uint8_t bps = (nextval & 0x01) << 4; + bps += (*(data +16) >> 4) + 1; + m_flacBitsPerSample = bps; + if((bps != 8) && (bps != 16)){ + log_e("bits per sample must be 8 or 16, is %i", bps); + stopSong(); + return -1; + } + sprintf(chbuf, "FLAC bitsPerSample: %u", m_flacBitsPerSample); if(audio_info) audio_info(chbuf); + m_flacTotalSamplesInStream = bigEndian(data + 17, 4); + if(m_flacTotalSamplesInStream){ + sprintf(chbuf, "total samples in stream: %u", m_flacTotalSamplesInStream); if(audio_info) audio_info(chbuf); + } + else{ + if(audio_info) audio_info("total samples in stream: N/A"); + } + if(bps != 0 && m_flacTotalSamplesInStream) { + sprintf(chbuf, "audio file duration: %u seconds", m_flacTotalSamplesInStream / m_flacSampleRate); + if(audio_info) audio_info(chbuf); + } + m_controlCounter = FLAC_MBH; // METADATA_BLOCK_HEADER + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_PADDING) { /* PADDING */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_APP) { /* APPLICATION */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_SEEK) { /* SEEKTABLE */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_VORBIS) { /* VORBIS COMMENT */ // field names + const char fn[7][12] = {"TITLE", "VERSION", "ALBUM", "TRACKNUMBER", "ARTIST", "PERFORMER", "GENRE"}; + int offset; + size_t l = bigEndian(data, 3); + + for(int i = 0; i < 7; i++){ + offset = specialIndexOf(data, fn[i], len); + if(offset >= 0){ + sprintf(chbuf, "%s: %s", fn[i], data + offset + strlen(fn[i]) + 1); + chbuf[strlen(chbuf) - 1] = 0; + if(audio_id3data) audio_id3data(chbuf); + } + } + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_CUESHEET) { /* CUESHEET */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == FLAC_PICTURE) { /* PICTURE */ + size_t l = bigEndian(data, 3); + m_controlCounter = FLAC_MBH; + retvalue = l + 3; + headerSize += retvalue; + return 0; + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_MP3_Header(uint8_t *data, size_t len) { + + static size_t id3Size; + static size_t headerSize; + static uint8_t ID3version; + static int ehsz = 0; + static char tag[5]; + static char frameid[5]; + static size_t framesize = 0; + static bool compressed = false; + static bool APIC_seen = false; + static size_t APIC_size = 0; + static uint32_t APIC_pos = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 0){ /* read ID3 tag and ID3 header size */ + if(m_f_localfile){ + ID3version = 0; + m_contentlength = getFileSize(); + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + m_controlCounter ++; + APIC_seen = false; + headerSize = 0; + ehsz = 0; + if(specialIndexOf(data, "ID3", 4) != 0) { // ID3 not found + if(audio_info) audio_info("file has no mp3 tag, skip metadata"); + m_audioDataSize = m_contentlength; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + return -1; // error, no ID3 signature found + } + ID3version = *(data + 3); + switch(ID3version){ + case 2: + m_f_unsync = (*(data + 5) & 0x80); + m_f_exthdr = false; + break; + case 3: + case 4: + m_f_unsync = (*(data + 5) & 0x80); // bit7 + m_f_exthdr = (*(data + 5) & 0x40); // bit6 extended header + break; + }; + id3Size = bigEndian(data + 6, 4, 7); // ID3v2 size 4 * %0xxxxxxx (shift left seven times!!) + id3Size += 10; + + // Every read from now may be unsync'd + sprintf(chbuf, "ID3 framesSize: %i", id3Size); + if(audio_info) audio_info(chbuf); + + sprintf(chbuf, "ID3 version: 2.%i", ID3version); + if(audio_info) audio_info(chbuf); + + if(ID3version == 2){ + m_controlCounter = 10; + } + headerSize = id3Size; + headerSize -= 10; + + return 10; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 1){ // compute extended header size if exists + m_controlCounter ++; + if(m_f_exthdr) { + if(audio_info) audio_info("ID3 extended header"); + ehsz = bigEndian(data, 4); + headerSize -= 4; + ehsz -= 4; + return 4; + } + else{ + if(audio_info) audio_info("ID3 normal frames"); + return 0; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 2){ // skip extended header if exists + if(ehsz > 256) { + ehsz -=256; + headerSize -= 256; + return 256;} // Throw it away + else { + m_controlCounter ++; + headerSize -= ehsz; + return ehsz;} // Throw it away + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 3){ // read a ID3 frame, get the tag + if(headerSize == 0){ + m_controlCounter = 99; + return 0; + } + m_controlCounter ++; + frameid[0] = *(data + 0); + frameid[1] = *(data + 1); + frameid[2] = *(data + 2); + frameid[3] = *(data + 3); + frameid[4] = 0; + for(uint8_t i = 0; i < 4; i++) tag[i] = frameid[i]; // tag = frameid + + headerSize -= 4; + if(frameid[0] == 0 && frameid[1] == 0 && frameid[2] == 0 && frameid[3] == 0) { + // We're in padding + m_controlCounter = 98; // all ID3 metadata processed + } + return 4; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 4){ // get the frame size + m_controlCounter = 6; + + if(ID3version == 4){ + framesize = bigEndian(data, 4, 7); // << 7 + } + else { + framesize = bigEndian(data, 4); // << 8 + } + headerSize -= 4; + uint8_t flag = *(data + 4); // skip 1st flag + (void) flag; + headerSize--; + compressed = (*(data + 5)) & 0x80; // Frame is compressed using [#ZLIB zlib] with 4 bytes for 'decompressed + // size' appended to the frame header. + headerSize--; + uint32_t decompsize = 0; + if(compressed){ + log_i("iscompressed"); + decompsize = bigEndian(data + 6, 4); + headerSize -= 4; + (void) decompsize; + log_i("decompsize=%u", decompsize); + return 6 + 4; + } + return 6; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 5){ // If the frame is larger than 256 bytes, skip the rest + if(framesize > 256){ + framesize -= 256; + headerSize -= 256; + return 256; + } + else { + m_controlCounter = 3; // check next frame + headerSize -= framesize; + return framesize; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 6){ // Read the value + m_controlCounter = 5; // only read 256 bytes + char value[256]; + char ch = *(data + 0); + bool isUnicode = (ch==1) ? true : false; + + if(startsWith(tag, "APIC")) { // a image embedded in file, passing it to external function + //log_i("framesize=%i", framesize); + isUnicode = false; + if(m_f_localfile){ + APIC_seen = true; + APIC_pos = id3Size - headerSize; + APIC_size = framesize; + } + return 0; + } + + size_t fs = framesize; + if(fs >255) fs = 255; + for(int i=0; i 1) { + unicode2utf8(value, fs); // convert unicode to utf-8 U+0020...U+07FF + } + if(!isUnicode){ + uint16_t j = 0, k = 0; + j = 0; + k = 0; + while(j < fs) { + if(value[j] == 0x0A) value[j] = 0x20; // replace LF by space + if(value[j] > 0x1F) { + value[k] = value[j]; + k++; + } + j++; + } //remove non printables + if(k>0) value[k] = 0; else value[0] = 0; // new termination + } + showID3Tag(tag, value); + return fs; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // -- section V2.2 only , greater Vers above ---- + if(m_controlCounter == 10){ // frames in V2.2, 3bytes identifier, 3bytes size descriptor + frameid[0] = *(data + 0); + frameid[1] = *(data + 1); + frameid[2] = *(data + 2); + frameid[3] = 0; + for(uint8_t i = 0; i < 4; i++) tag[i] = frameid[i]; // tag = frameid + headerSize -= 3; + size_t len = bigEndian(data + 3, 3); + headerSize -= 3; + headerSize -= len; + char value[256]; + size_t tmp = len; + if(tmp > 254) tmp = 254; + memcpy(value, (data + 7), tmp); + value[tmp+1] = 0; + chbuf[0] = 0; + + showID3Tag(tag, value); + if(len == 0) m_controlCounter = 98; + + return 3 + 3 + len; + } + // -- end section V2.2 ----------- + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 98){ // skip all ID3 metadata (mostly spaces) + if(headerSize > 256) { + headerSize -=256; + return 256; + } // Throw it away + else { + m_controlCounter = 99; + return headerSize; + } // Throw it away + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == 99){ // exist another ID3tag? + m_audioDataStart += id3Size; + vTaskDelay(30); + if((*(data + 0) == 'I') && (*(data + 1) == 'D') && (*(data + 2) == '3')) { + m_controlCounter = 0; + return 0; + } + else { + m_controlCounter = 100; // ok + m_audioDataSize = m_contentlength - m_audioDataStart; + sprintf(chbuf, "Audio-Length: %u", m_audioDataSize); + if(audio_info) audio_info(chbuf); + if(APIC_seen && audio_id3image){ + size_t pos = audiofile.position(); + audio_id3image(audiofile, APIC_pos, APIC_size); + audiofile.seek(pos); // the filepointer could have been changed by the user, set it back + } + return 0; + } + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_M4A_Header(uint8_t *data, size_t len) { +/* + ftyp + | - moov -> trak -> ... -> mp4a contains raw block parameters + | L... -> ilst contains artist, composer .... + free (optional) + | + mdat contains the audio data */ + + + static size_t headerSize = 0; + static size_t retvalue = 0; + static size_t atomsize = 0; + static size_t audioDataPos = 0; + + if(retvalue) { + if(retvalue > len) { // if returnvalue > bufferfillsize + if(len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize(); + retvalue -= len; // and wait for more bufferdata + return len; + } + else { + size_t tmp = retvalue; + retvalue = 0; + return tmp; + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_BEGIN) { // init + headerSize = 0; + retvalue = 0; + atomsize = 0; + audioDataPos = 0; + m_controlCounter = M4A_FTYP; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_FTYP) { /* check_m4a_file */ + atomsize = bigEndian(data, 4); // length of first atom + if(specialIndexOf(data, "ftyp", 10) != 4) { + log_e("atom 'type' not found in header"); + stopSong(); + return -1; + } + if(specialIndexOf(data, "M4A ", 20) != 8) { + log_e("subtype 'MA4 ' expected, but found '%s '", (data + 8)); + stopSong(); + return -1; + } + m_controlCounter = M4A_CHK; + retvalue = atomsize; + headerSize = atomsize; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_CHK) { /* check Tag */ + atomsize = bigEndian(data, 4); // length of this atom + if(specialIndexOf(data, "moov", 10) == 4) { + m_controlCounter = M4A_MOOV; + return 0; + } + else if(specialIndexOf(data, "free", 10) == 4) { + retvalue = atomsize; + headerSize += atomsize; + return 0; + } + else if(specialIndexOf(data, "mdat", 10) == 4) { + m_controlCounter = M4A_MDAT; + return 0; + } + else { + char atomName[5]; + (void)atomName; + atomName[0] = *data; + atomName[1] = *(data + 1); + atomName[2] = *(data + 2); + atomName[3] = *(data + 3); + atomName[4] = 0; + + log_i("atom %s found", atomName); + + retvalue = atomsize; + headerSize += atomsize; + return 0; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_MOOV) { // moov + // we are looking for track and ilst + if(specialIndexOf(data, "trak", len) > 0){ + int offset = specialIndexOf(data, "trak", len); + retvalue = offset; + atomsize -= offset; + headerSize += offset; + m_controlCounter = M4A_TRAK; + return 0; + } + if(specialIndexOf(data, "ilst", len) > 0){ + int offset = specialIndexOf(data, "ilst", len); + retvalue = offset; + atomsize -= offset; + headerSize += offset; + m_controlCounter = M4A_ILST; + return 0; + + } + if (atomsize > len -10){atomsize -= (len -10); headerSize += (len -10); retvalue = (len -10);} + else {m_controlCounter = M4A_CHK; retvalue = atomsize; headerSize += atomsize;} + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_TRAK) { // trak + if(specialIndexOf(data, "esds", len) > 0){ + int esds = specialIndexOf(data, "esds", len); // Packaging/Encapsulation And Setup Data + uint8_t *pos = data + esds; + uint8_t len_of_OD = *(pos + 12); // length of this OD (which includes the next 2 tags) + (void)len_of_OD; + uint8_t len_of_ESD = *(pos + 20); // length of this Elementary Stream Descriptor + (void)len_of_ESD; + uint8_t audioType = *(pos + 21); + if (audioType == 0x40) sprintf(chbuf, "AudioType: MPEG4 / Audio"); // ObjectTypeIndication + else if(audioType == 0x66) sprintf(chbuf, "AudioType: MPEG2 / Audio"); + else if(audioType == 0x69) sprintf(chbuf, "AudioType: MPEG2 / Audio Part 3"); // Backward Compatible Audio + else if(audioType == 0x6B) sprintf(chbuf, "AudioType: MPEG1 / Audio"); + else sprintf(chbuf, "unknown Audio Type %x", audioType); + if(audio_info) audio_info(chbuf); + + uint8_t streamType = *(pos + 22); + streamType = streamType >> 2; // 6 bits + if(streamType!= 5) { log_e("Streamtype is not audio!"); } + + uint32_t maxBr = bigEndian(pos + 26, 4); // max bitrate + sprintf(chbuf, "max bitrate: %i", maxBr); if(audio_info) audio_info(chbuf); + + uint32_t avrBr = bigEndian(pos + 30, 4); // avg bitrate + sprintf(chbuf, "avr bitrate: %i", avrBr); if(audio_info) audio_info(chbuf); + + uint16_t ASC = bigEndian(pos + 39, 2); + + uint8_t objectType = ASC >> 11; // first 5 bits + if (objectType == 1) sprintf(chbuf, "AudioObjectType: AAC Main"); // Audio Object Types + else if(objectType == 2) sprintf(chbuf, "AudioObjectType: AAC Low Complexity"); + else if(objectType == 3) sprintf(chbuf, "AudioObjectType: AAC Scalable Sample Rate"); + else if(objectType == 4) sprintf(chbuf, "AudioObjectType: AAC Long Term Prediction"); + else if(objectType == 5) sprintf(chbuf, "AudioObjectType: AAC Spectral Band Replication"); + else if(objectType == 6) sprintf(chbuf, "AudioObjectType: AAC Scalable"); + else sprintf(chbuf, "unknown Audio Type %x", audioType); + if(audio_info) audio_info(chbuf); + + const uint32_t samplingFrequencies[13] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 + }; + uint8_t sRate = (ASC & 0x0600) >> 7; // next 4 bits Sampling Frequencies + sprintf(chbuf, "Sampling Frequency: %u",samplingFrequencies[sRate]); + if(audio_info) audio_info(chbuf); + + uint8_t chConfig = (ASC & 0x78) >> 3; // next 4 bits + if(chConfig == 0) if(audio_info) audio_info("Channel Configurations: AOT Specifc Config"); + if(chConfig == 1) if(audio_info) audio_info("Channel Configurations: front-center"); + if(chConfig == 2) if(audio_info) audio_info("Channel Configurations: front-left, front-right"); + if(chConfig > 2) { log_e("Channel Configurations with more than 2 channels is not allowed!"); } + + uint8_t frameLengthFlag = (ASC & 0x04); + uint8_t dependsOnCoreCoder = (ASC & 0x02); + (void)dependsOnCoreCoder; + uint8_t extensionFlag = (ASC & 0x01); + (void)extensionFlag; + + if(frameLengthFlag == 0) if(audio_info) audio_info("AAC FrameLength: 1024 bytes"); + if(frameLengthFlag == 1) if(audio_info) audio_info("AAC FrameLength: 960 bytes"); + } + if(specialIndexOf(data, "mp4a", len) > 0){ + int offset = specialIndexOf(data, "mp4a", len); + int channel = bigEndian(data + offset + 20, 2); // audio parameter must be set before starting + int bps = bigEndian(data + offset + 22, 2); // the aac decoder. There are RAW blocks only in m4a + int srate = bigEndian(data + offset + 26, 4); // + setBitsPerSample(bps); + setChannels(channel); + setSampleRate(srate); + setBitrate(bps * channel * srate); + sprintf(chbuf, "ch; %i, bps: %i, sr: %i", channel, bps, srate); + if(audio_info) audio_info(chbuf); + if(audioDataPos && m_f_localfile) { + m_controlCounter = M4A_AMRDY; + setFilePos(audioDataPos); + return 0; + } + } + m_controlCounter = M4A_MOOV; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_ILST) { // ilst + const char info[12][6] = { "nam\0", "ART\0", "alb\0", "too\0", "cmt\0", "wrt\0", + "tmpo\0", "trkn\0","day\0", "cpil\0", "aART\0", "gen\0"}; + int offset; + for(int i=0; i < 12; i++){ + offset = specialIndexOf(data, info[i], len, true); // seek info[] with '\0' + if(offset>0) { + offset += 19; if(*(data + offset) == 0) offset ++; + char value[256]; + size_t tmp = strlen((const char*)data + offset); + if(tmp > 254) tmp = 254; + memcpy(value, (data + offset), tmp); + value[tmp] = 0; + chbuf[0] = 0; + if(i == 0) sprintf(chbuf, "Title: %s", value); + if(i == 1) sprintf(chbuf, "Artist: %s", value); + if(i == 2) sprintf(chbuf, "Album: %s", value); + if(i == 3) sprintf(chbuf, "Encoder: %s", value); + if(i == 4) sprintf(chbuf, "Comment: %s", value); + if(i == 5) sprintf(chbuf, "Composer: %s", value); + if(i == 6) sprintf(chbuf, "BPM: %s", value); + if(i == 7) sprintf(chbuf, "Track Number: %s", value); + if(i == 8) sprintf(chbuf, "Year: %s", value); + if(i == 9) sprintf(chbuf, "Compile: %s", value); + if(i == 10) sprintf(chbuf, "Album Artist: %s", value); + if(i == 11) sprintf(chbuf, "Types of: %s", value); + if(chbuf[0] != 0) { + if(audio_id3data) audio_id3data(chbuf); + } + } + } + m_controlCounter = M4A_MOOV; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == M4A_MDAT) { // mdat + m_audioDataSize = bigEndian(data, 4); // length of this atom + sprintf(chbuf, "Audio-Length: %u",m_audioDataSize); + if(audio_info) audio_info(chbuf); + retvalue = 8; + headerSize += 8; + m_controlCounter = M4A_AMRDY; // last step before starting the audio + return 0; + } + + if(m_controlCounter == M4A_AMRDY){ // almost ready + m_audioDataStart = headerSize; + m_contentlength = headerSize + m_audioDataSize; // after this mdat atom there may be other atoms + log_i("begin mdat %i", headerSize); + if(m_f_localfile){ + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + m_controlCounter = M4A_OKAY; // that's all + return 0; + } + // this section should never be reached + log_e("error"); + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::read_OGG_Header(uint8_t *data, size_t len){ + static size_t retvalue = 0; + static size_t pageLen = 0; + static bool f_firstPacket = false; + + if(retvalue) { + if(retvalue > len) { // if returnvalue > bufferfillsize + if(len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize(); + retvalue -= len; // and wait for more bufferdata + return len; + } + else { + size_t tmp = retvalue; + retvalue = 0; + return tmp; + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_BEGIN) { // init + retvalue = 0; + m_audioDataStart = 0; + f_firstPacket = true; + m_controlCounter = OGG_MAGIC; + if(m_f_localfile){ + m_contentlength = getFileSize(); + sprintf(chbuf, "Content-Length: %u", m_contentlength); + if(audio_info) audio_info(chbuf); + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_MAGIC) { /* check MAGIC STRING */ + if(specialIndexOf(data, "OggS", 10) != 0) { + log_e("Magic String 'OggS' not found in header"); + stopSong(); + return -1; + } + m_controlCounter = OGG_HEADER; + retvalue = 4; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_HEADER) { /* check OGG PAGE HEADER */ + uint8_t i = 0; + uint8_t ssv = *(data + i); // stream_structure_version + (void)ssv; + i++; + uint8_t htf = *(data + i); // header_type_flag + (void)htf; + i++; + uint32_t tmp = bigEndian(data + i, 4); // absolute granule position + uint64_t agp = (uint64_t) tmp << 32; + i += 4; + agp += bigEndian(data + i, 4); + i += 4; + uint32_t ssnr = bigEndian(data + i, 4); // stream serial number + (void)ssnr; + i += 4; + uint32_t psnr = bigEndian(data + i, 4); // page sequence no + (void)psnr; + i += 4; + uint32_t pchk = bigEndian(data + i, 4); // page checksum + (void)pchk; + i += 4; + uint8_t psegm = *(data + i); + i++; + uint8_t psegmBuff[256]; + pageLen = 0; + for(uint8_t j = 0; j < psegm; j++){ + psegmBuff[j] = *(data + i); + pageLen += psegmBuff[j]; + i++; + } + retvalue = i; + if(agp == 0){ + if(f_firstPacket == true){ + f_firstPacket = false; + m_controlCounter = OGG_FIRST; // ogg first pages + } + else{ + retvalue += pageLen; + m_controlCounter = OGG_MAGIC; + } + } + else{ + if(m_codec == CODEC_OGG_FLAC){ + m_controlCounter = OGG_AMRDY; + } + else { + if(audio_info) audio_info("unknown format"); + stopSong(); + return -1; + } + } + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter == OGG_FIRST) { /* check OGG FIRST PAGES (has no streaming content) */ + uint8_t i = 0; + uint8_t obp = *(data + i); // oneBytePacket shold be 0x7F + (void)obp; + i++; + if(specialIndexOf(data + i, "FLAC", 10) == 0){ + } + else{ + log_i("ogg/flac support only"); // ogg/vorbis or ogg//opus not supported yet + stopSong(); + return -1; + } + i += 4; + uint8_t major_vers = *(data + i); + (void)major_vers; + i++; + uint8_t minor_vers = *(data + i); + (void)minor_vers; + i++; + uint16_t nonah = bigEndian(data + i, 2); // number of non audio headers (0x00 = unknown) + (void)nonah; + i += 2; + if(specialIndexOf(data + i, "fLaC", 10) == 0){ + m_codec = CODEC_OGG_FLAC; + } + i += 4; + // STREAMINFO metadata block begins + uint32_t mblen = bigEndian(data + i, 4); + (void)mblen; + i += 4; // skip metadata block header + length + i += 2; // skip minimun block size + m_flacMaxBlockSize = bigEndian(data + i, 2); + i += 2; + vTaskDelay(2); + sprintf(chbuf, "FLAC maxBlockSize: %u", m_flacMaxBlockSize); if(audio_info) audio_info(chbuf); + i += 3; // skip minimun frame size + vTaskDelay(2); + m_flacMaxFrameSize = bigEndian(data + i, 3); + i += 3; + if(m_flacMaxFrameSize){ + sprintf(chbuf, "FLAC maxFrameSize: %u", m_flacMaxFrameSize); if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("FLAC maxFrameSize: N/A"); + } + if(m_flacMaxFrameSize > InBuff.getMaxBlockSize()) { + log_e("FLAC maxFrameSize too large!"); + stopSong(); + return -1; + } + vTaskDelay(2); + uint32_t nextval = bigEndian(data + i, 3); + i += 3; + m_flacSampleRate = nextval >> 4; + sprintf(chbuf, "FLAC sampleRate: %u", m_flacSampleRate); if(audio_info) audio_info(chbuf); + vTaskDelay(2); + m_flacNumChannels = ((nextval & 0x06) >> 1) + 1; + sprintf(chbuf, "FLAC numChannels: %u", m_flacNumChannels); if(audio_info) audio_info(chbuf); + if(m_flacNumChannels != 1 && m_flacNumChannels != 2){ + vTaskDelay(2); + if(audio_info) audio_info("numChannels must be 1 or 2"); + stopSong(); + return -1; + } + vTaskDelay(2); + uint8_t bps = (nextval & 0x01) << 4; + bps += (*(data +i) >> 4) + 1; + i++; + m_flacBitsPerSample = bps; + if((bps != 8) && (bps != 16)){ + log_e("bits per sample must be 8 or 16, is %i", bps); + stopSong(); + return -1; + } + sprintf(chbuf, "FLAC bitsPerSample: %u", m_flacBitsPerSample); if(audio_info) audio_info(chbuf); + m_flacTotalSamplesInStream = bigEndian(data + i, 4); + i++; + if(m_flacTotalSamplesInStream) { + sprintf(chbuf, "total samples in stream: %u", m_flacTotalSamplesInStream); if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("total samples in stream: N/A"); + } + if(bps != 0 && m_flacTotalSamplesInStream) { + sprintf(chbuf, "audio file duration: %u seconds", m_flacTotalSamplesInStream / m_flacSampleRate); + if(audio_info) audio_info(chbuf); + } + m_controlCounter = OGG_MAGIC; + retvalue = pageLen; + return 0; + } + if(m_controlCounter == OGG_AMRDY){ // ogg almost ready + if(!psramFound()){ + if(audio_info) audio_info("FLAC works only with PSRAM!"); + m_f_running = false; stopSong(); + return -1; + } + if(!FLACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return -1;} + InBuff.changeMaxBlockSize(m_frameSizeFLAC); + sprintf(chbuf, "FLACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + + if(audio_info) audio_info(chbuf); + m_controlCounter = OGG_OKAY; // 100 + retvalue = 0; + return 0; + } + return 0; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::stopSong() { + if(m_f_running) { + m_f_running = false; + audiofile.close(); + } + memset(m_outBuff, 0, sizeof(m_outBuff)); //Clear OutputBuffer + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::playI2Sremains() { // returns true if all dma_buffs flushed + static uint8_t dma_buf_count = 0; + // there is no function to see if dma_buff is empty. So fill the dma completely. + // As soon as all remains played this function returned. Or you can take this to create a short silence. + if(!getSampleRate()) setSampleRate(96000); + if(!getChannels()) setChannels(2); + if(getBitsPerSample() > 8) memset(m_outBuff, 0, sizeof(m_outBuff)); //Clear OutputBuffer (signed) + else memset(m_outBuff, 128, sizeof(m_outBuff)); //Clear OutputBuffer (unsigned, PCM 8u) + //play remains and then flush dmaBuff + m_validSamples = m_i2s_config.dma_buf_len; + while(m_validSamples) { + playChunk(); + } + if(dma_buf_count < m_i2s_config.dma_buf_count){ + dma_buf_count++; + return false; + } + dma_buf_count = 0; + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::pauseResume() { + bool retVal = false; + if(m_f_localfile || m_f_webstream) { + m_f_running = !m_f_running; + retVal = true; + if(!m_f_running) { + memset(m_outBuff, 0, sizeof(m_outBuff)); //Clear OutputBuffer + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); + } + } + return retVal; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::playChunk() { + // If we've got data, try and pump it out.. + int16_t sample[2]; + if(getBitsPerSample() == 8) { + if(getChannels() == 1) { + while(m_validSamples) { + uint8_t x = m_outBuff[m_curSample] & 0x00FF; + uint8_t y = (m_outBuff[m_curSample] & 0xFF00) >> 8; + sample[LEFTCHANNEL] = x; + sample[RIGHTCHANNEL] = x; + while(1) { + if(playSample(sample)) break; + } // Can't send? + sample[LEFTCHANNEL] = y; + sample[RIGHTCHANNEL] = y; + while(1) { + if(playSample(sample)) break; + } // Can't send? + m_validSamples--; + m_curSample++; + } + } + if(getChannels() == 2) { + while(m_validSamples) { + uint8_t x = m_outBuff[m_curSample] & 0x00FF; + uint8_t y = (m_outBuff[m_curSample] & 0xFF00) >> 8; + if(!m_f_forceMono) { // stereo mode + sample[LEFTCHANNEL] = x; + sample[RIGHTCHANNEL] = y; + } + else { // force mono + uint8_t xy = (x + y) / 2; + sample[LEFTCHANNEL] = xy; + sample[RIGHTCHANNEL] = xy; + } + + while(1) { + if(playSample(sample)) break; + } // Can't send? + m_validSamples--; + m_curSample++; + } + } + m_curSample = 0; + return true; + } + if(getBitsPerSample() == 16) { + if(getChannels() == 1) { + while(m_validSamples) { + sample[LEFTCHANNEL] = m_outBuff[m_curSample]; + sample[RIGHTCHANNEL] = m_outBuff[m_curSample]; + if(!playSample(sample)) { + return false; + } // Can't send + m_validSamples--; + m_curSample++; + } + } + if(getChannels() == 2) { + while(m_validSamples) { + if(!m_f_forceMono) { // stereo mode + sample[LEFTCHANNEL] = m_outBuff[m_curSample * 2]; + sample[RIGHTCHANNEL] = m_outBuff[m_curSample * 2 + 1]; + } + else { // mono mode, #100 + int16_t xy = (m_outBuff[m_curSample * 2] + m_outBuff[m_curSample * 2 + 1]) / 2; + sample[LEFTCHANNEL] = xy; + sample[RIGHTCHANNEL] = xy; + } + if(!playSample(sample)) { + return false; + } // Can't send + m_validSamples--; + m_curSample++; + } + } + m_curSample = 0; + return true; + } + log_e("BitsPer Sample must be 8 or 16!"); + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::loop() { + + // - localfile - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_localfile) { // Playing file fron SPIFFS or SD? + processLocalFile(); + } + // - webstream - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webstream) { // Playing file from URL? + if(!m_f_running) return; + +// if(m_f_m3u8wait){ // timer, wait for fresh m3u8 playlist +// if(m_t1 < millis()){ +// log_i("wait zurückgesetzt"); +// m_f_m3u8wait = false; +// processPlayListData(); +// } +// } + if(m_datamode == AUDIO_PLAYLISTINIT || m_datamode == AUDIO_PLAYLISTHEADER || m_datamode == AUDIO_PLAYLISTDATA){ + processPlayListData(); + if(m_f_m3u8data) processWebStream(); + return; + } + if(m_datamode == AUDIO_HEADER){ + processAudioHeaderData(); + if(m_f_m3u8data) processWebStream(); + return; + } + if(m_datamode == AUDIO_DATA){ + processWebStream(); + return; + } + } + return; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processPlayListData() { + + static bool f_entry = false; // entryflag for asx playlist + static bool f_title = false; // titleflag for asx playlist + static bool f_ref = false; // refflag for asx playlist + static bool f_begin = false; + static bool f_end = false; + static bool f_ct = false; + + (void)f_title; // is unused yet + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == AUDIO_PLAYLISTINIT) { // Initialize for receive .m3u file + // We are going to use metadata to read the lines from the .m3u file + // Sometimes this will only contain a single line + f_entry = false; + f_title = false; + f_ref = false; + f_begin = false; + f_end = false; + f_ct = false; + + m_datamode = AUDIO_PLAYLISTHEADER; // Handle playlist data + //if(audio_info) audio_info("Read from playlist"); + } // end AUDIO_PLAYLISTINIT + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + int av = 0; + if(!m_f_ssl) av = client.available(); + else av = clientsecure.available(); + if(av < 1){ + if(f_end) return; + if(f_begin) {f_end = true;} + else return; + } + + char pl[256]; // playlistline + uint8_t b = 0; + int16_t pos = 0; + + while(true){ + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b == 0xff) b = '\n'; // no more to read? send new line + if(b == '\n') {pl[pos] = 0; break;} + if(b < 0x20 || b > 0x7E) continue; + pl[pos] = b; + if(pos < 255) pos++; + if(pos == 254){pl[pos] = '\0'; /*log_e("playlistline overflow");*/} + } + + if(strlen(pl) == 0 && m_datamode == AUDIO_PLAYLISTHEADER) { + if(m_f_Log) if(audio_info) audio_info("Switch to PLAYLISTDATA"); + m_datamode = AUDIO_PLAYLISTDATA; // Expecting data now + return; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == AUDIO_PLAYLISTHEADER) { // Read header + + sprintf(chbuf, "Playlistheader: %s", pl); // Show playlistheader + if(m_f_Log) if(audio_info) audio_info(chbuf); + + if(indexOf(pl, "Content-Type:", 0)){ + f_ct = true; // found ContentType in pl + } + + if(indexOf(pl, "content-type:", 0)){ + f_ct = true; // found ContentType in pl + } + + if((indexOf(pl, "Connection:close", 0) >= 0) && !f_ct){ // #193 is not a playlist if no ct found + m_datamode = AUDIO_HEADER; + } + + int pos = indexOf(pl, "400 Bad Request", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 400 Bad Request"); + stopSong(); + return; + } + + pos = indexOf(pl, "404 Not Found", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 404 Not Found"); + stopSong(); + return; + } + + pos = indexOf(pl, "404 File Not Found", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 404 File Not Found"); + stopSong(); + return; + } + + pos = indexOf(pl, "HTTP/1.0 404", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 404 Not Available"); + stopSong(); + return; + } + + pos = indexOf(pl, "HTTP/1.1 401", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 401 Unauthorized"); + stopSong(); + return; + } + + pos = indexOf(pl, "HTTP/1.1 403", 0); + if(pos >= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Error 403 Forbidden"); + stopSong(); + return; + } + + pos = indexOf(pl, ":", 0); // lowercase all letters up to the colon + if(pos >= 0) { + for(int i=0; i < pos; i++) { + pl[i] = toLowerCase(pl[i]); + } + } + + if(startsWith(pl, "icy-")){ // icy-data in playlist? that can not be + m_datamode = AUDIO_HEADER; + if(audio_info) audio_info("playlist is not valid, switch to AUDIO_HEADER"); + return; + } + + + if(startsWith(pl, "location:") || startsWith(pl, "Location:")) { + char* host; + pos = indexOf(pl, "http", 0); + host = (pl + pos); +// sprintf(chbuf, "redirect to new host %s", host); +// if(m_f_Log) if(audio_info) audio_info(chbuf); + pos = indexOf(pl, "/", 10); + if(strncmp(host, m_lastHost, pos) == 0){ // same host? + if(!m_f_ssl) {client.stop(); client.flush();} + else {clientsecure.stop(); clientsecure.flush();} + httpPrint(host); + } + else connecttohost(host); // different host, + } + return; + } // end AUDIO_PLAYLISTHEADER + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_datamode == AUDIO_PLAYLISTDATA) { // Read next byte of .m3u file data + sprintf(chbuf, "Playlistdata: %s", pl); // Show playlistdata + if(m_f_Log) if(audio_info) audio_info(chbuf); + + pos = indexOf(pl, "= 0) { + m_datamode = AUDIO_NONE; + if(audio_info) audio_info("Not Found"); + stopSong(); + return; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_M3U) { + + if(!f_begin) f_begin = true; // first playlistdata received + if(indexOf(pl, "#EXTINF:", 0) >= 0) { // Info? + pos = indexOf(pl, ",", 0); // Comma in this line? + if(pos > 0) { + // Show artist and title if present in metadata + if(audio_info) audio_info(pl + pos + 1); + } + return; + } + if(startsWith(pl, "#")) { // Commentline? + return; + } + + pos = indexOf(pl, "http://:@", 0); // ":@"?? remove that! + if(pos >= 0) { + sprintf(chbuf, "Entry in playlist found: %s", (pl + pos + 9)); + if(audio_info) audio_info(chbuf); + connecttohost(pl + pos + 9); + return; + } + //sprintf(chbuf, "Entry in playlist found: %s", pl); + //if(audio_info) audio_info(chbuf); + pos = indexOf(pl, "http", 0); // Search for "http" + const char* host; + if(pos >= 0) { // Does URL contain "http://"? + host = (pl + pos); + connecttohost(host); + } // Yes, set new host + return; + } //m3u + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_PLS) { + + if(!f_begin){ + if(strlen(pl) == 0) return; // empty line + if(strcmp(pl, "[playlist]") == 0){ // first entry in valid pls + f_begin = true; // we have first playlistdata received + return; + } + else{ + m_datamode = AUDIO_HEADER; // pls is not valid + if(audio_info) audio_info("pls is not valid, switch to AUDIO_HEADER"); + return; + } + } + + if(startsWith(pl, "File1")) { + pos = indexOf(pl, "http", 0); // File1=http://streamplus30.leonex.de:14840/; + if(pos >= 0) { // yes, URL contains "http"? + memcpy(m_lastHost, pl + pos, strlen(pl) + 1); // http://streamplus30.leonex.de:14840/; + // Now we have an URL for a stream in host. + f_ref = true; + } + } + if(startsWith(pl, "Title1")) { // Title1=Antenne Tirol + const char* plsStationName = (pl + 7); + if(audio_showstation) audio_showstation(plsStationName); + sprintf(chbuf, "StationName: \"%s\"", plsStationName); + if(audio_info) audio_info(chbuf); + f_title = true; + } + if(startsWith(pl, "Length1")) f_title = true; // if no Title is available + if((f_ref == true) && (strlen(pl) == 0)) f_title = true; + + if(indexOf(pl, "Invalid username", 0) >= 0){ // Unable to access account: Invalid username or password + m_f_running = false; + stopSong(); + m_datamode = AUDIO_NONE; + return; + } + + if(f_end) { // we have both StationName and StationURL + log_d("connect to new host %s", m_lastHost); + connecttohost(m_lastHost); // Connect to it + } + return; + } // pls + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_ASX) { // Advanced Stream Redirector + if(!f_begin) f_begin = true; // first playlistdata received + int p1 = indexOf(pl, "<", 0); + int p2 = indexOf(pl, ">", 1); + if(p1 >= 0 && p2 > p1){ // #196 set all between "< ...> to lowercase + for(uint8_t i = p1; i < p2; i++){ + pl[i] = toLowerCase(pl[i]); + } + } + + if(indexOf(pl, "", 0) >= 0) f_entry = true; // found entry tag (returns -1 if not found) + + if(f_entry) { + if(indexOf(pl, "ref href", 0) > 0) { // + pos = indexOf(pl, "http", 0); + if(pos > 0) { + char* plsURL = (pl + pos); // http://87.98.217.63:24112/stream" /> + int pos1 = indexOf(plsURL, "\"", 0); // http://87.98.217.63:24112/stream + if(pos1 > 0) { + plsURL[pos1] = '\0'; + } + memcpy(m_lastHost, plsURL, strlen(plsURL) + 1); // save url in array + log_d("m_lastHost = %s",m_lastHost); + // Now we have an URL for a stream in host. + f_ref = true; + } + } + pos = indexOf(pl, "", 0); + if(pos < 0) pos = indexOf(pl, "<Title>", 0); + if(pos >= 0) { + char* plsStationName = (pl + pos + 7); // remove <Title> + pos = indexOf(plsStationName, "</", 0); + if(pos >= 0){ + *(plsStationName +pos) = 0; // remove + } + if(audio_showstation) audio_showstation(plsStationName); + sprintf(chbuf, "StationName: \"%s\"", plsStationName); + if(audio_info) audio_info(chbuf); + f_title = true; + } + } //entry + if(indexOf(pl, "http", 0) == 0 && !f_entry) { //url only in asx + memcpy(m_lastHost, pl, strlen(pl)); // save url in array + m_lastHost[strlen(pl)] = '\0'; + log_d("m_lastHost = %s",m_lastHost); + connecttohost(pl); + } + if(f_end) { //we have both StationName and StationURL + connecttohost(m_lastHost); // Connect to it + } + return; + } //asx + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_playlistFormat == FORMAT_M3U8) { + + static bool f_StreamInf = false; // set if #EXT-X-STREAM-INF in m3u8 + static bool f_ExtInf = false; // set if #EXTINF in m3u8 + static uint8_t plsEntry = 0; // used in m3u8, counts url entries + static uint8_t seqNrPos = 0; // position at which the SeqNr is found + + if(!f_begin){ + if(strlen(pl) == 0) return; // empty line + if(strcmp(pl, "#EXTM3U") == 0){ // what we expected + f_begin = true; + f_StreamInf = false; + f_ExtInf = false; + plsEntry = 0; + return; + } + else{ + m_datamode = AUDIO_HEADER; // m3u8 is not valid + m_playlistFormat = FORMAT_NONE; + if(audio_info) audio_info("m3u8 is not valid, switch to AUDIO_HEADER"); + return; + } + } + + // example: redirection + // #EXTM3U + // #EXT-X-STREAM-INF:BANDWIDTH=22050,CODECS="mp4a.40.2" + // http://ample.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/playlist.m3u8 + + // example: audio chunks + // #EXTM3U + // #EXT-X-TARGETDURATION:10 + // #EXT-X-MEDIA-SEQUENCE:163374040 + // #EXT-X-DISCONTINUITY + // #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\"" + // http://n3fa-e2.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/main/163374038.aac + // #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\"" + // http://n3fa-e2.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/main/163374039.aac + + if(startsWith(pl,"#EXT-X-STREAM-INF:")){ + int pos = indexOf(pl, "CODECS=\"mp4a", 18); + if(pos < 0){ // not found + m_m3u8codec = CODEC_NONE; + log_e("codec %s in m3u8 playlist not supportet", pl); + stopSong(); + return; + } + f_StreamInf = true; + m_m3u8codec = CODEC_M4A; + return; + } + + if(f_StreamInf){ // it's a redirection, a new m3u8 playlist + if(startsWith(pl, "http")){ + strcpy(m_lastHost, pl); + } + else{ + pos = lastIndexOf(m_lastHost, "/"); + strcpy(m_lastHost + pos + 1, pl); + } + f_StreamInf = false; + connecttohost(m_lastHost); + return; + } + + if(m_m3u8codec == CODEC_NONE){ // second guard + if(!f_end) return; + else {connecttohost(m_lastHost); return;} + } + + static uint32_t seqNr = 0; + if(startsWith(pl, "#EXT-X-MEDIA-SEQUENCE:")){ + // do nothing, because MEDIA-SECUENCE is not set sometimes + } + + static uint16_t targetDuration = 0; + if(startsWith(pl, "#EXT-X-TARGETDURATION:")) {targetDuration = atoi(pl + 22);} + + if(startsWith(pl,"#EXTINF")) { + f_ExtInf = true; + if(STfromEXTINF(pl)) showstreamtitle(pl); + return; + } + + if(f_ExtInf){ + f_ExtInf = false; +// log_i("ExtInf=%s", pl); + int16_t lastSlash = lastIndexOf(m_lastHost, "/"); + + if(!m_playlistBuff){ // will be freed in setDefaults() + m_playlistBuff = (char*)malloc(2 * m_plsBuffEntryLen); + strcpy(m_playlistBuff, m_lastHost); // save the m3u8 url at pos 0 + } + + if(plsEntry == 0){ + seqNrPos = 0; + char* entryPos = m_playlistBuff + m_plsBuffEntryLen; + if(startsWith(pl, "http")){ + strcpy(entryPos , pl); + } + else{ + strcpy(entryPos , m_lastHost); // if not start with http complete with host from m_lasthost + strcpy(entryPos + lastSlash + 1 , pl); + } + // now the url is completed, we have a look at the sequenceNumber + if(m_m3u8codec == CODEC_M4A){ + int p1 = lastIndexOf(entryPos, "/"); + int p2 = indexOf(entryPos, ".aac", 0); + if(p1<0 || p2<0){ + log_e("sequenceNumber not found"); + stopSong(); + return; + } + // seqNr must be between p1 and p2 + for(int i = p1; i < p2; i++){ + if(entryPos[i] >= 48 && entryPos[i] <=57){ // numbers only + if(!seqNrPos) seqNrPos = i; + } + else{ + seqNrPos = 0; // in case ...52397ae8f_1.aac?sid=5193 seqNr=1 + } + } + seqNr = atoi(&entryPos[seqNrPos]); + //log_i("entryPos=%s", entryPos); + //log_i("p1=%i, p2=%i, seqNrPos =%i, seqNr=%d", p1, p2, seqNrPos, seqNr); + } + } + plsEntry++; + return; + } + + if(f_end){ + if(plsEntry > 0){ // we have found some (url) entries + processM3U8entries(plsEntry, seqNr, seqNrPos, targetDuration); + } + else{ + connecttohost(m_lastHost); + } + } + } //end m3u8 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + return; + } // end AUDIO_PLAYLISTDATA +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processM3U8entries(uint8_t _nrOfEntries, uint32_t _seqNr, uint8_t _seqNrpos, uint16_t _targetDuration){ + + // call up the entries in m3u8 sequentially, after the last we need a new playlist + static uint8_t nrOfEntries = 0; + static uint32_t currentSeqNr = 0; + static uint32_t sequenceNr = 0; + static uint32_t maxSeqNr = 0; + static uint8_t sequenceNrPos = 0; + static uint16_t targetDuration = 0; + + char resp[256 + 100]; + char* host = NULL; // e.g http://n31a-e2.revma.ihrhls.com + char* extension = NULL; // e.g hls.m3u8?rj-ttl=5&rj-tok=AAABe9D0_W0AmLAedXo_MfNpLw + char* entryaddr = NULL; + + if(!m_f_m3u8data){ + m_f_m3u8data = true; + nrOfEntries = _nrOfEntries; + currentSeqNr = _seqNr; + sequenceNr = _seqNr; + maxSeqNr = currentSeqNr + _nrOfEntries; + sequenceNrPos = _seqNrpos; + targetDuration = _targetDuration; + connecttohost(m_playlistBuff + m_plsBuffEntryLen); // connect the streamserver first + m_f_m3u8data = true; // connecttohost() will clear m_f_m3u8data, set it again + +// log_i("nrOfEntries=%d, currentSeqNr=%d, sequenceNrPos=%d targetDuration=%d", nrOfEntries, currentSeqNr, sequenceNrPos, targetDuration); +// log_i("m3u8_url=%s", m_playlistBuff); +// log_i("aac_url=%s", m_playlistBuff+ m_plsBuffEntryLen); + + (void)nrOfEntries; // suppress warning -Wunused-but-set-variable + (void)sequenceNr; + (void)targetDuration; + (void)resp; + (void)host; + (void)extension; + + currentSeqNr++; + return; + } + + if(_nrOfEntries == 0){ // processM3U8entries(0,0,0,0) + if(currentSeqNr < maxSeqNr){ // next entry in playlist + char sBuff[12]; itoa(currentSeqNr, sBuff, 10); + entryaddr = m_playlistBuff + m_plsBuffEntryLen; + memcpy(entryaddr + sequenceNrPos, sBuff, strlen(sBuff)); + // log_i("entryaddr=%s", entryaddr); + goto label1; + } + if(currentSeqNr == maxSeqNr){ // we need a new playlist + entryaddr = m_playlistBuff; + // log_i("entryaddr=%s", entryaddr); + + + goto label1; + } + if(currentSeqNr > maxSeqNr){ + return; // guard: in that case never goto label1: + } + } + + if(_nrOfEntries > 0){ // e.g. processM3U8entries(3,123456,55,10) + + while(currentSeqNr > _seqNr) {_seqNr++; _nrOfEntries--;} ; + nrOfEntries = _nrOfEntries; + maxSeqNr = currentSeqNr + _nrOfEntries; + sequenceNrPos = _seqNrpos; + targetDuration = _targetDuration; + char sBuff[12]; itoa(currentSeqNr, sBuff, 10); + entryaddr = m_playlistBuff + m_plsBuffEntryLen; + memcpy(entryaddr + sequenceNrPos, sBuff, strlen(sBuff)); + //log_i("entryaddr=%s", entryaddr); + + goto label1; + } + +label1: + + httpPrint(entryaddr); + + if(currentSeqNr < maxSeqNr){ + m_datamode = AUDIO_HEADER; + m_f_continue = true; + currentSeqNr++; + } + else{ + m_datamode = AUDIO_PLAYLISTINIT; + m_playlistFormat = FORMAT_M3U8; + } + m_f_Log = false; + return; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::STfromEXTINF(char* str){ + // extraxt StreamTitle from m3u #EXTINF line to icy-format + // orig: #EXTINF:10,title="text="TitleName",artist="ArtistName" + // conv: StreamTitle=TitleName - ArtistName + // orig: #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\"" + // conv: StreamTitle=text=\"Spot Block End\" amgTrackId=\"9876543\" - + + if(!startsWith(str,"#EXTINF")) return false; + int t1, t2, t3, n0 = 0, n1 = 0, n2 = 0; + + t1 = indexOf(str, "title", 0); + if(t1 > 0){ + strcpy(chbuf, "StreamTitle="); n0 = 12; + t2 = t1 + 7; // title=" + t3 = indexOf(str, "\"", t2); + while(str[t3 - 1] == '\\'){ + t3 = indexOf(str, "\"", t3 + 1); + } + if(t2 < 0 || t2 > t3) return false; + n1 = t3 - t2; + strncpy(chbuf + n0, str + t2, n1); + } + + t1 = indexOf(str, "artist", 0); + if(t1 > 0){ + strcpy(chbuf + n0 + n1, " - "); n1 += 3; + t2 = indexOf(str, "=\"", t1); t2 += 2; + t3 = indexOf(str, "\"", t2); + if(t2 < 0 || t2 > t3) return false; + n2 = t3 - t2; + strncpy(chbuf + n0 + n1, str + t2, n2); + chbuf[n0 + n1 + n2] = '\0'; + } + strcpy(str, chbuf); + + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processLocalFile() { + + if(!(audiofile && m_f_running && m_f_localfile)) return; + + int bytesDecoded = 0; + uint32_t bytesCanBeWritten = 0; + uint32_t bytesCanBeRead = 0; + int32_t bytesAddedToBuffer = 0; + static bool f_stream; + + if(m_f_firstCall) { // runs only one time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + return; + } + bytesCanBeWritten = InBuff.writeSpace(); + //---------------------------------------------------------------------------------------------------- + // some files contain further data after the audio block (e.g. pictures). + // In that case, the end of the audio block is not the end of the file. An 'eof' has to be forced. + if((m_controlCounter == 100) && (m_contentlength > 0)) { // fileheader was read + if(bytesCanBeWritten + getFilePos() >= m_contentlength){ + if(m_contentlength > getFilePos()) bytesCanBeWritten = m_contentlength - getFilePos(); + else bytesCanBeWritten = 0; + } + } + //---------------------------------------------------------------------------------------------------- + + bytesAddedToBuffer = audiofile.read(InBuff.getWritePtr(), bytesCanBeWritten); + if(bytesAddedToBuffer > 0) { + InBuff.bytesWritten(bytesAddedToBuffer); + } + +// if(psramFound() && bytesAddedToBuffer >4096) +// vTaskDelay(2);// PSRAM has a bottleneck in the queue, so wait a little bit + + if(bytesAddedToBuffer == -1) bytesAddedToBuffer = 0; // read error? eof? + bytesCanBeRead = InBuff.bufferFilled(); + if(bytesCanBeRead > InBuff.getMaxBlockSize()) bytesCanBeRead = InBuff.getMaxBlockSize(); + if(bytesCanBeRead == InBuff.getMaxBlockSize()) { // mp3 or aac frame complete? + if(!f_stream) { + f_stream = true; + if(audio_info) audio_info("stream ready"); + } + if(m_controlCounter != 100){ + if(m_codec == CODEC_WAV){ + int res = read_WAV_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_MP3){ + int res = read_MP3_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_M4A){ + int res = read_M4A_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + m_controlCounter = 100; + } + } + if(m_codec == CODEC_AAC){ + // stream only, no header + m_audioDataSize = getFileSize(); + m_controlCounter = 100; + } + + if(m_codec == CODEC_FLAC){ + int res = read_FLAC_Header(InBuff.getReadPtr(), bytesCanBeRead); + if(res >= 0) bytesDecoded = res; + else{ // error, skip header + stopSong(); + m_controlCounter = 100; + } + } + } + else { + bytesDecoded = sendBytes(InBuff.getReadPtr(), bytesCanBeRead); + } + if(bytesDecoded > 0) {InBuff.bytesWasRead(bytesDecoded); return;} + if(bytesDecoded < 0) { // no syncword found or decode error, try next chunk + InBuff.bytesWasRead(200); // try next chunk + m_bytesNotDecoded += 200; + return; + } + return; + } + if(!bytesAddedToBuffer) { // eof + bytesCanBeRead = InBuff.bufferFilled(); + if(bytesCanBeRead > 200){ + if(bytesCanBeRead > InBuff.getMaxBlockSize()) bytesCanBeRead = InBuff.getMaxBlockSize(); + bytesDecoded = sendBytes(InBuff.getReadPtr(), bytesCanBeRead); // play last chunk(s) + if(bytesDecoded > 0){ + InBuff.bytesWasRead(bytesDecoded); + return; + } + } + InBuff.resetBuffer(); + if(!playI2Sremains()) return; + + if(m_f_loop && f_stream){ //eof + sprintf(chbuf, "loop from: %u to: %u", getFilePos(), m_audioDataStart); //TEST loop + if(audio_info) audio_info(chbuf); + setFilePos(m_audioDataStart); + if(m_codec == CODEC_FLAC) FLACDecoderReset(); + /* + The current time of the loop mode is not reset, + which will cause the total audio duration to be exceeded. + For example: current time ====progress bar====> total audio duration + 3:43 ====================> 3:33 + */ + m_audioCurrentTime = 0; + return; + } //TEST loop + f_stream = false; + m_f_localfile = false; + +#ifdef SDFATFS_USED + audiofile.getName(chbuf, sizeof(chbuf)); + char *afn =strdup(chbuf); +#else + char *afn =strdup(audiofile.name()); // store temporary the name +#endif + + stopSong(); + if(m_codec == CODEC_MP3) MP3Decoder_FreeBuffers(); + if(m_codec == CODEC_AAC) AACDecoder_FreeBuffers(); + if(m_codec == CODEC_M4A) AACDecoder_FreeBuffers(); + if(m_codec == CODEC_FLAC) FLACDecoder_FreeBuffers(); + sprintf(chbuf, "End of file \"%s\"", afn); + if(audio_info) audio_info(chbuf); + if(audio_eof_mp3) audio_eof_mp3(afn); + if(afn) free(afn); + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processWebStream() { + + const uint16_t maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger + int32_t availableBytes; // available bytes in stream + static bool f_tmr_1s; + static bool f_stream; // first audio data received + static int bytesDecoded; + static uint32_t byteCounter; // count received data + static uint32_t chunksize; // chunkcount read from stream + static uint32_t tmr_1s; // timer 1 sec + static uint32_t loopCnt; // count loops if clientbuffer is empty + static uint32_t metacount; // counts down bytes between metadata + + + // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_firstCall) { // runs only ont time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + byteCounter = 0; + chunksize = 0; + bytesDecoded = 0; + loopCnt = 0; + tmr_1s = millis(); + m_t0 = millis(); + metacount = m_metaint; + readMetadata(0, true); // reset all static vars + } + if(m_f_continue){ // next m3u8 chunk is available + byteCounter = 0; + metacount = m_metaint; + m_f_continue = false; + } + + // have we reached the end of the webfile? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webfile && byteCounter == m_contentlength){ + if(InBuff.bufferFilled() < 10000){ + if(m_datamode == AUDIO_DATA && m_f_m3u8data) {processM3U8entries(); return;} // we need the next audioChunk + } + if(InBuff.bufferFilled() > 0){ + if(InBuff.bufferFilled() == 128){ // post tag? comes sometimes after podcasts + if(indexOf((const char*)InBuff.getReadPtr(), "TAG", 0) == 0){ + log_i("%s", InBuff.getReadPtr() + 3); + InBuff.bytesWasRead(128); + return; + } + else{ + log_v("%s", InBuff.getReadPtr()); + InBuff.bytesWasRead(InBuff.bufferFilled()); + return; + } + } + bytesDecoded = sendBytes(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(bytesDecoded > 0) {InBuff.bytesWasRead(bytesDecoded); return;} + if(bytesDecoded == 0) return; // syncword at pos0 found + } + if(m_f_m3u8data) return; + + while(!playI2Sremains()){;} + stopSong(); // Correct close when play known length sound #74 and before callback #112 + + if(m_f_tts){ + sprintf(chbuf, "End of speech: \"%s\"", m_lastHost); + if(audio_info) audio_info(chbuf); + if(audio_eof_speech) audio_eof_speech(m_lastHost); + } + else{ + sprintf(chbuf, "End of webstream: \"%s\"", m_lastHost); + if(audio_info) audio_info(chbuf); + if(audio_eof_stream) audio_eof_stream(m_lastHost); + } + return; + } + + if(m_datamode != AUDIO_DATA) return; + + // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if((tmr_1s + 1000) < millis()) { + f_tmr_1s = true; // flag will be set every second for one loop only + tmr_1s = millis(); + } + + if(m_f_ssl == false) availableBytes = client.available(); // available from stream + if(m_f_ssl == true) availableBytes = clientsecure.available(); // available from stream + + // if we have chunked data transfer: get the chunksize- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_chunked && !m_chunkcount && availableBytes) { // Expecting a new chunkcount? + int b; + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + + if(b == '\r') return; + if(b == '\n'){ + m_chunkcount = chunksize; + chunksize = 0; + if(m_f_tts){ + m_contentlength = m_chunkcount; // tts has one chunk only + m_f_webfile = true; + m_f_chunked = false; + } + return; + } + // We have received a hexadecimal character. Decode it and add to the result. + b = toupper(b) - '0'; // Be sure we have uppercase + if(b > 9) b = b - 7; // Translate A..F to 10..15 + chunksize = (chunksize << 4) + b; + return; + } + + // if we have metadata: get them - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!metacount && !m_f_swm && availableBytes){ + int16_t b = 0; + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b >= 0) { + if(m_f_chunked) m_chunkcount--; + if(readMetadata(b)) metacount = m_metaint; + } + return; + } + + // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(InBuff.bufferFilled() < maxFrameSize && f_stream){ + static uint8_t cnt_slow = 0; + cnt_slow ++; + if(f_tmr_1s) { + if(cnt_slow > 25 && audio_info) audio_info("slow stream, dropouts are possible"); + f_tmr_1s = false; + cnt_slow = 0; + } + } + + // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - + if(f_stream && !availableBytes){ + loopCnt++; + if(loopCnt > 200000) { // wait several seconds + loopCnt = 0; + if(audio_info) audio_info("Stream lost -> try new connection"); + connecttohost(m_lastHost); + } + } + if(availableBytes) loopCnt = 0; + + // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(true) { // statement has no effect + uint32_t bytesCanBeWritten = InBuff.writeSpace(); + if(!m_f_swm) bytesCanBeWritten = min(metacount, bytesCanBeWritten); + if(m_f_chunked) bytesCanBeWritten = min(m_chunkcount, bytesCanBeWritten); + + int16_t bytesAddedToBuffer = 0; + + if(m_f_psram) if(bytesCanBeWritten > 4096) bytesCanBeWritten = 4096; // PSRAM throttle + + if(m_f_webfile){ + // normally there is nothing to do here, if byteCounter == contentLength + // then the file is completely read, but: + // m4a files can have more data (e.g. pictures ..) after the audio Block + // therefore it is bad to read anything else (this can generate noise) + if(byteCounter + bytesCanBeWritten >= m_contentlength) bytesCanBeWritten = m_contentlength - byteCounter; + } + + if(m_f_ssl == false) bytesAddedToBuffer = client.read(InBuff.getWritePtr(), bytesCanBeWritten); + else bytesAddedToBuffer = clientsecure.read(InBuff.getWritePtr(), bytesCanBeWritten); + + if(bytesAddedToBuffer > 0) { + if(m_f_webfile) byteCounter += bytesAddedToBuffer; // Pull request #42 + if(!m_f_swm) metacount -= bytesAddedToBuffer; + if(m_f_chunked) m_chunkcount -= bytesAddedToBuffer; + InBuff.bytesWritten(bytesAddedToBuffer); + } + + if(InBuff.bufferFilled() > maxFrameSize && !f_stream) { // waiting for buffer filled + f_stream = true; // ready to play the audio data + uint16_t filltime = millis() - m_t0; + if(audio_info) audio_info("stream ready"); + sprintf(chbuf, "buffer filled in %d ms", filltime); + if(audio_info) audio_info(chbuf); + } + if(!f_stream) return; + } + + // if we have a webfile, read the file header first - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_webfile && m_controlCounter != 100 && !m_f_m3u8data){ // m3u8call, audiochunk has no header + if(InBuff.bufferFilled() < maxFrameSize) return; + if(m_codec == CODEC_WAV){ + int res = read_WAV_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{stopSong(); return;} + } + if(m_codec == CODEC_MP3){ + int res = read_MP3_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{m_controlCounter = 100;} // error, skip header + } + if(m_codec == CODEC_M4A){ + int res = read_M4A_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{stopSong(); return;} + } + if(m_codec == CODEC_FLAC){ + int res = read_FLAC_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else{stopSong(); return;} // error, skip header + } + if(m_codec == CODEC_AAC){ // aac has no header + m_controlCounter = 100; + return; + } + InBuff.bytesWasRead(bytesDecoded); + return; + } + + // play audio data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if((InBuff.bufferFilled() >= maxFrameSize) && (f_stream == true)) { // fill > framesize? + if(m_f_webfile){ + bytesDecoded = sendBytes(InBuff.getReadPtr(), maxFrameSize); + } + else { // not a webfile + if(m_controlCounter != 100 && (m_codec == CODEC_OGG || m_codec == CODEC_OGG_FLAC)) { //application/ogg + int res = read_OGG_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(res >= 0) bytesDecoded = res; + else { // error, skip header + stopSong(); + m_controlCounter = 100; + } + } + else{ + bytesDecoded = sendBytes(InBuff.getReadPtr(), maxFrameSize); + } + } + if(bytesDecoded < 0) { // no syncword found or decode error, try next chunk + InBuff.bytesWasRead(200); // try next chunk + m_bytesNotDecoded += 200; + return; + } + else { + InBuff.bytesWasRead(bytesDecoded); + } + } + return; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processAudioHeaderData() { + + int av = 0; + if(!m_f_ssl) av=client.available(); + else av= clientsecure.available(); + if(av <= 0) return; + + char hl[512]; // headerline + uint8_t b = 0; + uint16_t pos = 0; + int16_t idx = 0; + static bool f_icyname = false; + static bool f_icydescription = false; + static bool f_icyurl = false; + + while(true){ + if(!m_f_ssl) b = client.read(); + else b = clientsecure.read(); + if(b == '\n') break; + if(b == '\r') hl[pos] = 0; + if(b < 0x20) continue; + hl[pos] = b; + pos++; + if(pos == 510){ + hl[pos] = '\0'; + log_e("headerline overflow"); + break; + } + } + + if(!pos && m_f_ctseen){ // audio header complete? + m_datamode = AUDIO_DATA; // Expecting data now + sprintf(chbuf, "Switch to DATA, metaint is %d", m_metaint); + if(m_f_Log) if(audio_info) audio_info(chbuf); + memcpy(chbuf, m_lastHost, strlen(m_lastHost)+1); +// uint idx = indexOf(chbuf, "?", 0); +// if(idx > 0) chbuf[idx] = 0; + if(audio_lasthost) audio_lasthost(chbuf); + if(!f_icyname){if(audio_showstation) audio_showstation("");} + if(!f_icydescription){if(audio_icydescription) audio_icydescription("");} + if(!f_icyurl){if(audio_icyurl) audio_icyurl("");} + f_icyname = false; + f_icydescription = false; + f_icyurl = false; + delay(50); // #77 + return; + } + if(!pos){ + stopSong(); + if(audio_showstation) audio_showstation(""); + if(audio_icydescription) audio_icydescription(""); + if(audio_icyurl) audio_icyurl(""); + f_icyname = false; + f_icydescription = false; + f_icyurl = false; + log_e("can't see content in audioHeaderData"); + return; + } + + idx = indexOf(hl, ":", 0); // lowercase all letters up to the colon + if(idx >= 0) { + for(int i=0; i< idx; i++) { + hl[i] = toLowerCase(hl[i]); + } + } + + if(indexOf(hl, "HTTP/1.0 404", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("404 Not Found"); + return; + } + + if(indexOf(hl, "HTTP/1.1 404", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("404 Not Found"); + return; + } + + if(indexOf(hl, "ICY 401", 0) >= 0) { + m_f_running = false; + stopSong(); + if(audio_info) audio_info("ICY 401 Service Unavailable"); + return; + } + + if(indexOf(hl, "content-type:", 0) >= 0) { + if(parseContentType(hl)) m_f_ctseen = true; + } + else if(startsWith(hl, "location:")) { + int pos = indexOf(hl, "http", 0); + const char* c_host = (hl + pos); + sprintf(chbuf, "redirect to new host \"%s\"", c_host); + if(audio_info) audio_info(chbuf); + connecttohost(c_host); + } + else if(startsWith(hl, "content-disposition:")) { + int pos1, pos2; // pos3; + // e.g we have this headerline: content-disposition: attachment; filename=stream.asx + // filename is: "stream.asx" + pos1 = indexOf(hl, "filename=", 0); + if(pos1 > 0){ + pos1 += 9; + if(hl[pos1] == '\"') pos1++; // remove '\"' around filename if present + pos2 = strlen(hl); + if(hl[pos2 - 1] == '\"') hl[pos2 - 1] = '\0'; + } + log_d("Filename is %s", hl + pos1); + } + else if(startsWith(hl, "set-cookie:") || + startsWith(hl, "pragma:") || + startsWith(hl, "expires:") || + startsWith(hl, "cache-control:") || + startsWith(hl, "icy-pub:") || + startsWith(hl, "p3p:") || + startsWith(hl, "accept-ranges:") ){ + ; // do nothing + } + else if(startsWith(hl, "connection:")) { + if(indexOf(hl, "close", 0) >= 0) {; /* do nothing */} + } + else if(startsWith(hl, "icy-genre:")) { + ; // do nothing Ambient, Rock, etc + } + else if(startsWith(hl, "icy-br:")) { + const char* c_bitRate = (hl + 7); + int32_t br = atoi(c_bitRate); // Found bitrate tag, read the bitrate in Kbit + br = br * 1000; + setBitrate(br); + sprintf(chbuf, "%d", getBitRate()); + if(audio_bitrate) audio_bitrate(chbuf); + } + else if(startsWith(hl, "icy-metaint:")) { + const char* c_metaint = (hl + 12); + int32_t i_metaint = atoi(c_metaint); + m_metaint = i_metaint; + if(m_metaint) m_f_swm = false ; // Multimediastream + } + else if(startsWith(hl, "icy-name:")) { + char* c_icyname = (hl + 9); // Get station name + idx = 0; + while(c_icyname[idx] == ' '){idx++;} c_icyname += idx; // Remove leading spaces + idx = strlen(c_icyname); + while(c_icyname[idx] == ' '){idx--;} c_icyname[idx + 1] = 0; // Remove trailing spaces + + if(strlen(c_icyname) > 0) { + sprintf(chbuf, "icy-name: %s", c_icyname); + if(audio_info) audio_info(chbuf); + if(audio_showstation) audio_showstation(c_icyname); + f_icyname = true; + } + } + else if(startsWith(hl, "content-length:")) { + const char* c_cl = (hl + 15); + int32_t i_cl = atoi(c_cl); + m_contentlength = i_cl; + m_f_webfile = true; // Stream comes from a fileserver + sprintf(chbuf, "content-length: %i", m_contentlength); + if(m_f_Log) if(audio_info) audio_info(chbuf); + } + else if(startsWith(hl, "icy-description:")) { + const char* c_idesc = (hl + 16); + while(c_idesc[0] == ' ') c_idesc++; + latinToUTF8(hl, sizeof(hl)); // if already UTF-0 do nothing, otherwise convert to UTF-8 + if(audio_icydescription) audio_icydescription(c_idesc); + f_icydescription = true; + } + else if((startsWith(hl, "transfer-encoding:"))){ + if(endsWith(hl, "chunked") || endsWith(hl, "Chunked") ) { // Station provides chunked transfer + m_f_chunked = true; + if(audio_info) audio_info("chunked data transfer"); + m_chunkcount = 0; // Expect chunkcount in DATA + } + } + else if(startsWith(hl, "icy-url:")) { + const char* icyurl = (hl + 8); + idx = 0; + while(icyurl[idx] == ' ') {idx ++;} icyurl += idx; // remove leading blanks + sprintf(chbuf, "icy-url: %s", icyurl); + // if(audio_info) audio_info(chbuf); + if(audio_icyurl) audio_icyurl(icyurl); + f_icyurl = true; + } + else if(startsWith(hl, "www-authenticate:")) { + if(audio_info) audio_info("authentification failed, wrong credentials?"); + m_f_running = false; + stopSong(); + } + else { + if(isascii(hl[0]) && hl[0] >= 0x20) { // all other + sprintf(chbuf, "%s", hl); + if(m_f_Log) if(audio_info) audio_info(chbuf); + } + } + return; +} + +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::readMetadata(uint8_t b, bool first) { + + static uint16_t pos_ml = 0; // determines the current position in metaline + static uint16_t metalen = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(first){ + pos_ml = 0; + metalen = 0; + return true; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!metalen) { // First byte of metadata? + metalen = b * 16 + 1; // New count for metadata including length byte + if(metalen >512){ + if(audio_info) audio_info("Metadata block to long! Skipping all Metadata from now on."); + m_f_swm = true; // expect stream without metadata + } + pos_ml = 0; chbuf[pos_ml] = 0; // Prepare for new line + } + else { + chbuf[pos_ml] = (char) b; // Put new char in +++++ + if(pos_ml < 510) pos_ml ++; + chbuf[pos_ml] = 0; + if(pos_ml == 509) { log_e("metaline overflow in AUDIO_METADATA! metaline=%s", chbuf); } + if(pos_ml == 510) { ; /* last current char in b */} + + } + if(--metalen == 0) { + if(strlen(chbuf)) { // Any info present? + // metaline contains artist and song name. For example: + // "StreamTitle='Don McLean - American Pie';StreamUrl='';" + // Sometimes it is just other info like: + // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';" + // Isolate the StreamTitle, remove leading and trailing quotes if present. + // log_i("ST %s", metaline); + + latinToUTF8(chbuf, sizeof(chbuf)); // convert to UTF-8 if necessary + + int pos = indexOf(chbuf, "song_spot", 0); // remove some irrelevant infos + if(pos > 3) { // e.g. song_spot="T" MediaBaseId="0" itunesTrackId="0" + chbuf[pos] = 0; + } + if(!m_f_localfile) showstreamtitle(chbuf); // Show artist and title if present in metadata + } + return true ; + } + return false;// end_METADATA +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::parseContentType(const char* ct) { + bool ct_seen = false; + if(indexOf(ct, "audio", 0) >= 0) { // Is ct audio? + ct_seen = true; // Yes, remember seeing this + if(indexOf(ct, "mpeg", 13) >= 0) { + m_codec = CODEC_MP3; + sprintf(chbuf, "%s, format is mp3", ct); + if(audio_info) audio_info(chbuf); //ok is likely mp3 + if(!MP3Decoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "MP3Decoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeMP3); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "mp3", 13) >= 0) { + m_codec = CODEC_MP3; + sprintf(chbuf, "%s, format is mp3", ct); + if(audio_info) audio_info(chbuf); + if(!MP3Decoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "MP3Decoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeMP3); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "aac", 13) >= 0) { // audio/x-aac + m_codec = CODEC_AAC; + sprintf(chbuf, "%s, format is aac", ct); + if(m_f_Log) if(audio_info) audio_info(chbuf); + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + } + else if(indexOf(ct, "mp4", 13) >= 0) { // audio/mp4a, audio/mp4a-latm + m_codec = CODEC_M4A; + sprintf(chbuf, "%s, format is aac", ct); + if(m_f_Log) if(audio_info) audio_info(chbuf); + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + } + else if(indexOf(ct, "m4a", 13) >= 0) { // audio/x-m4a + m_codec = CODEC_M4A; + sprintf(chbuf, "%s, format is aac", ct); + if(audio_info) audio_info(chbuf); + if(!AACDecoder_IsInit()){ + if(!AACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + sprintf(chbuf, "AACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + InBuff.changeMaxBlockSize(m_frameSizeAAC); + if(audio_info) audio_info(chbuf); + } + } + else if(indexOf(ct, "wav", 13) >= 0) { // audio/x-wav + m_codec = CODEC_WAV; + sprintf(chbuf, "%s, format is wav", ct); + if(audio_info) audio_info(chbuf); + InBuff.changeMaxBlockSize(m_frameSizeWav); + } + else if(indexOf(ct, "ogg", 13) >= 0) { + m_codec = CODEC_OGG; + sprintf(chbuf, "ContentType %s found", ct); + if(audio_info) audio_info(chbuf); + } + else if(indexOf(ct, "flac", 13) >= 0) { // audio/flac, audio/x-flac + m_codec = CODEC_FLAC; + sprintf(chbuf, "%s, format is flac", ct); + if(audio_info) audio_info(chbuf); + if(!psramFound()){ + if(audio_info) audio_info("FLAC works only with PSRAM!"); + m_f_running = false; + return false; + } + if(!FLACDecoder_AllocateBuffers()) {m_f_running = false; stopSong(); return false;} + InBuff.changeMaxBlockSize(m_frameSizeFLAC); + sprintf(chbuf, "FLACDecoder has been initialized, free Heap: %u bytes", ESP.getFreeHeap()); + if(audio_info) audio_info(chbuf); + } + else { + m_f_running = false; + sprintf(chbuf, "%s, unsupported audio format", ct); + if(audio_info) audio_info(chbuf); + } + } + if(indexOf(ct, "application", 0) >= 0) { // Is ct application? + ct_seen = true; // Yes, remember seeing this + uint8_t pos = indexOf(ct, "application", 0); + if(indexOf(ct, "ogg", 13) >= 0) { + m_codec = CODEC_OGG; + sprintf(chbuf, "ContentType %s found", ct + pos); + if(audio_info) audio_info(chbuf); + } + } + return ct_seen; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showstreamtitle(const char* ml) { + // example for ml: + // StreamTitle='Oliver Frank - Mega Hitmix';StreamUrl='www.radio-welle-woerthersee.at'; + // or adw_ad='true';durationMilliseconds='10135';adId='34254';insertionType='preroll'; + + int16_t idx1, idx2; + uint16_t i = 0, hash = 0; + static uint16_t sTit_remember = 0, sUrl_renember = 0; + + idx1 = indexOf(ml, "StreamTitle=", 0); + if(idx1 >= 0){ // Streamtitle found + idx2 = indexOf(ml, ";", idx1); + char *sTit; + if(idx2 >= 0){sTit = strndup(ml + idx1, idx2 + 1); sTit[idx2] = '\0';} + else sTit = strdup(ml); + + while(i < strlen(sTit)){hash += sTit[i] * i+1; i++;} + + if(sTit_remember != hash){ + sTit_remember = hash; + if(audio_info) audio_info(sTit); + uint8_t pos = 12; // remove "StreamTitle=" + if(sTit[pos] == '\'') pos++; // remove leading \' + if(sTit[strlen(sTit) - 1] == '\'') sTit[strlen(sTit) -1] = '\0'; // remove trailing \' + if(audio_showstreamtitle) audio_showstreamtitle(sTit + pos); + } + free(sTit); + } + + idx1 = indexOf(ml, "StreamUrl=", 0); + idx2 = indexOf(ml, ";", idx1); + if(idx1 >= 0 && idx2 > idx1){ // StreamURL found + uint16_t len = idx2 - idx1; + char *sUrl; + sUrl = strndup(ml + idx1, len + 1); sUrl[len] = '\0'; + + while(i < strlen(sUrl)){hash += sUrl[i] * i+1; i++;} + if(sUrl_renember != hash){ + sUrl_renember = hash; + if(audio_info) audio_info(sUrl); + } + free(sUrl); + } + + idx1 = indexOf(ml, "adw_ad=", 0); + if(idx1 >= 0){ // Advertisement found + idx1 = indexOf(ml, "durationMilliseconds=", 0); + idx2 = indexOf(ml, ";", idx1); + if(idx1 >= 0 && idx2 > idx1){ + uint16_t len = idx2 - idx1; + char *sAdv; + sAdv = strndup(ml + idx1, len + 1); sAdv[len] = '\0'; + if(audio_info) audio_info(sAdv); + uint8_t pos = 21; // remove "StreamTitle=" + if(sAdv[pos] == '\'') pos++; // remove leading \' + if(sAdv[strlen(sAdv) - 1] == '\'') sAdv[strlen(sAdv) -1] = '\0'; // remove trailing \' + if(audio_commercial) audio_commercial(sAdv + pos); + free(sAdv); + } + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::showCodecParams(){ + // print Codec Parameter (mp3, aac) in audio_info() + sprintf(chbuf,"Channels: %i", getChannels()); + if(audio_info) audio_info(chbuf); + sprintf(chbuf,"SampleRate: %i", getSampleRate()); + if(audio_info) audio_info(chbuf); + sprintf(chbuf,"BitsPerSample: %i", getBitsPerSample()); + if(audio_info) audio_info(chbuf); + if(getBitRate()){ + sprintf(chbuf,"BitRate: %i", getBitRate()); + if(audio_info) audio_info(chbuf); + } + else { + if(audio_info) audio_info("BitRate: N/A"); + } + + if(m_codec == CODEC_AAC || m_codec == CODEC_M4A){ + uint8_t answ; + if((answ = AACGetFormat()) < 4){ + const char hf[4][8] = {"unknown", "ADTS", "ADIF", "RAW"}; + sprintf(chbuf, "AAC HeaderFormat: %s", hf[answ]); + if(audio_info) audio_info(chbuf); + } + if(answ == 1){ // ADTS Header + const char co[2][23] = {"MPEG-4", "MPEG-2"}; + sprintf(chbuf, "AAC Codec: %s", co[AACGetID()]); + if(audio_info) audio_info(chbuf); + if(AACGetProfile() <5){ + const char pr[4][23] = {"Main", "LowComplexity", "Scalable Sampling Rate", "reserved"}; + sprintf(chbuf, "AAC Profile: %s", pr[answ]); + if(audio_info) audio_info(chbuf); + } + } + } +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::findNextSync(uint8_t* data, size_t len){ + // Mp3 and aac audio data are divided into frames. At the beginning of each frame there is a sync word. + // The sync word is 0xFFF. This is followed by information about the structure of the frame. + // Wav files have no frames + // Return: 0 the synchronous word was found at position 0 + // > 0 is the offset to the next sync word + // -1 the sync word was not found within the block with the length len + + int nextSync; + static uint32_t swnf = 0; + if(m_codec == CODEC_WAV) { + m_f_playing = true; nextSync = 0; + } + if(m_codec == CODEC_MP3) { + nextSync = MP3FindSyncWord(data, len); + } + if(m_codec == CODEC_AAC) { + nextSync = AACFindSyncWord(data, len); + } + if(m_codec == CODEC_M4A) { + AACSetRawBlockParams(0, 2,44100, 1); m_f_playing = true; nextSync = 0; + } + if(m_codec == CODEC_FLAC) { + FLACSetRawBlockParams(m_flacNumChannels, m_flacSampleRate, + m_flacBitsPerSample, m_flacTotalSamplesInStream, m_audioDataSize); + nextSync = FLACFindSyncWord(data, len); + } + if(m_codec == CODEC_OGG_FLAC) { + FLACSetRawBlockParams(m_flacNumChannels, m_flacSampleRate, + m_flacBitsPerSample, m_flacTotalSamplesInStream, m_audioDataSize); + nextSync = FLACFindSyncWord(data, len); + } + if(nextSync == -1) { + if(audio_info && swnf == 0) audio_info("syncword not found"); + if(m_codec == CODEC_OGG_FLAC){ + nextSync = len; + } + else { + swnf++; // syncword not found counter, can be multimediadata + } + } + if (nextSync == 0){ + if(audio_info && swnf>0){ + sprintf(chbuf, "syncword not found %i times", swnf); + audio_info(chbuf); + swnf = 0; + } + else { + if(audio_info) audio_info("syncword found at pos 0"); + } + } + if(nextSync > 0){ + sprintf(chbuf, "syncword found at pos %i", nextSync); + if(audio_info) audio_info(chbuf); + } + return nextSync; +} +//--------------------------------------------------------------------------------------------------------------------- +int Audio::sendBytes(uint8_t* data, size_t len) { + int bytesLeft; + static bool f_setDecodeParamsOnce = true; + int nextSync = 0; + if(!m_f_playing) { + f_setDecodeParamsOnce = true; + nextSync = findNextSync(data, len); + if(nextSync == 0) { m_f_playing = true;} + return nextSync; + } + // m_f_playing is true at this pos + bytesLeft = len; + int ret = 0; + int bytesDecoded = 0; + if(m_codec == CODEC_WAV){ //copy len data in outbuff and set validsamples and bytesdecoded=len + memmove(m_outBuff, data , len); + if(getBitsPerSample() == 16) m_validSamples = len / (2 * getChannels()); + if(getBitsPerSample() == 8 ) m_validSamples = len / 2; + bytesLeft = 0; + } + if(m_codec == CODEC_MP3) ret = MP3Decode(data, &bytesLeft, m_outBuff, 0); + if(m_codec == CODEC_AAC) ret = AACDecode(data, &bytesLeft, m_outBuff); + if(m_codec == CODEC_M4A) ret = AACDecode(data, &bytesLeft, m_outBuff); + if(m_codec == CODEC_FLAC) ret = FLACDecode(data, &bytesLeft, m_outBuff); + if(m_codec == CODEC_OGG_FLAC) ret = FLACDecode(data, &bytesLeft, m_outBuff); // FLAC webstream wrapped in OGG + + bytesDecoded = len - bytesLeft; + if(bytesDecoded == 0 && ret == 0){ // unlikely framesize + if(audio_info) audio_info("framesize is 0, start decoding again"); + m_f_playing = false; // seek for new syncword + // we're here because there was a wrong sync word + // so skip two sync bytes and seek for next + return 1; + } + if(ret < 0) { // Error, skip the frame... + //if(m_codec == CODEC_M4A){log_i("begin not found"); return 1;} + i2s_zero_dma_buffer((i2s_port_t)m_i2s_num); + if(!getChannels() && (ret == -2)) { + ; // suppress errorcode MAINDATA_UNDERFLOW + } + else { + printDecodeError(ret); + m_f_playing = false; // seek for new syncword + } + if(!bytesDecoded) bytesDecoded = 2; + return bytesDecoded; + } + else{ // ret>=0 + if(f_setDecodeParamsOnce){ + f_setDecodeParamsOnce = false; + m_PlayingStartTime = millis(); + + if(m_codec == CODEC_MP3){ + setChannels(MP3GetChannels()); + setSampleRate(MP3GetSampRate()); + setBitsPerSample(MP3GetBitsPerSample()); + setBitrate(MP3GetBitrate()); + } + if(m_codec == CODEC_AAC || m_codec == CODEC_M4A){ + setChannels(AACGetChannels()); + setSampleRate(AACGetSampRate()); + setBitsPerSample(AACGetBitsPerSample()); + setBitrate(AACGetBitrate()); + } + if(m_codec == CODEC_FLAC || m_codec == CODEC_OGG_FLAC){ + setChannels(FLACGetChannels()); + setSampleRate(FLACGetSampRate()); + setBitsPerSample(FLACGetBitsPerSample()); + setBitrate(FLACGetBitRate()); + } + showCodecParams(); + if(m_f_tts) while(!playI2Sremains()){;} // short silence + } + if(m_codec == CODEC_MP3){ + m_validSamples = MP3GetOutputSamps() / getChannels(); + } + if((m_codec == CODEC_AAC) || (m_codec == CODEC_M4A)){ + m_validSamples = AACGetOutputSamps() / getChannels(); + } + if((m_codec == CODEC_FLAC) || (m_codec == CODEC_OGG_FLAC)){ + m_validSamples = FLACGetOutputSamps() / getChannels(); + } + } + compute_audioCurrentTime(bytesDecoded); + + if(audio_process_extern){ + bool continueI2S = false; + audio_process_extern(m_outBuff, m_validSamples, &continueI2S); + if(!continueI2S){ + return bytesDecoded; + } + } + + while(m_validSamples) { + playChunk(); + } + return bytesDecoded; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::compute_audioCurrentTime(int bd) { + static uint16_t loop_counter = 0; + static int old_bitrate = 0; + static uint64_t sum_bitrate = 0; + static boolean f_CBR = true; // constant bitrate + + if(m_codec == CODEC_MP3) {setBitrate(MP3GetBitrate()) ;} // if not CBR, bitrate can be changed + if(m_codec == CODEC_M4A) {setBitrate(AACGetBitrate()) ;} // if not CBR, bitrate can be changed + if(m_codec == CODEC_AAC) {setBitrate(AACGetBitrate()) ;} // if not CBR, bitrate can be changed + if(m_codec == CODEC_FLAC){setBitrate(FLACGetBitRate());} // if not CBR, bitrate can be changed + if(!getBitRate()) return; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_avr_bitrate == 0) { // first time + loop_counter = 0; + old_bitrate = 0; + sum_bitrate = 0; + f_CBR = true; + m_avr_bitrate = getBitRate(); + old_bitrate = getBitRate(); + } + if(!m_avr_bitrate) return; + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + if(loop_counter < 1000) loop_counter ++; + + if((old_bitrate != getBitRate()) && f_CBR) { + if(audio_info) audio_info("VBR recognized, audioFileDuration is estimated"); + f_CBR = false; // variable bitrate + } + old_bitrate = getBitRate(); + + if(!f_CBR) { + if(loop_counter > 20 && loop_counter < 200) { + // if VBR: m_avr_bitrate is average of the first values of m_bitrate + sum_bitrate += getBitRate(); + m_avr_bitrate = sum_bitrate / (loop_counter - 20); + } + } + else { + if(loop_counter == 2) m_avr_bitrate = getBitRate(); + } + m_audioCurrentTime += (float) bd * 8 / m_avr_bitrate; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::printDecodeError(int r) { + char* e = nullptr; + + if(m_codec == CODEC_MP3){ + switch(r){ + case ERR_MP3_NONE: e = strdup("NONE"); break; + case ERR_MP3_INDATA_UNDERFLOW: e = strdup("INDATA_UNDERFLOW"); break; + case ERR_MP3_MAINDATA_UNDERFLOW: e = strdup("MAINDATA_UNDERFLOW"); break; + case ERR_MP3_FREE_BITRATE_SYNC: e = strdup("FREE_BITRATE_SYNC"); break; + case ERR_MP3_OUT_OF_MEMORY: e = strdup("OUT_OF_MEMORY"); break; + case ERR_MP3_NULL_POINTER: e = strdup("NULL_POINTER"); break; + case ERR_MP3_INVALID_FRAMEHEADER: e = strdup("INVALID_FRAMEHEADER"); break; + case ERR_MP3_INVALID_SIDEINFO: e = strdup("INVALID_SIDEINFO"); break; + case ERR_MP3_INVALID_SCALEFACT: e = strdup("INVALID_SCALEFACT"); break; + case ERR_MP3_INVALID_HUFFCODES: e = strdup("INVALID_HUFFCODES"); break; + case ERR_MP3_INVALID_DEQUANTIZE: e = strdup("INVALID_DEQUANTIZE"); break; + case ERR_MP3_INVALID_IMDCT: e = strdup("INVALID_IMDCT"); break; + case ERR_MP3_INVALID_SUBBAND: e = strdup("INVALID_SUBBAND"); break; + default: e = strdup("ERR_UNKNOWN"); + } + sprintf(chbuf, "MP3 decode error %d : %s", r, e); + if(audio_info) audio_info(chbuf); + } + if(m_codec == CODEC_AAC){ + switch(r){ + case ERR_AAC_NONE: e = strdup("NONE"); break; + case ERR_AAC_INDATA_UNDERFLOW: e = strdup("INDATA_UNDERFLOW"); break; + case ERR_AAC_NULL_POINTER: e = strdup("NULL_POINTER"); break; + case ERR_AAC_INVALID_ADTS_HEADER: e = strdup("INVALID_ADTS_HEADER"); break; + case ERR_AAC_INVALID_ADIF_HEADER: e = strdup("INVALID_ADIF_HEADER"); break; + case ERR_AAC_INVALID_FRAME: e = strdup("INVALID_FRAME"); break; + case ERR_AAC_MPEG4_UNSUPPORTED: e = strdup("MPEG4_UNSUPPORTED"); break; + case ERR_AAC_CHANNEL_MAP: e = strdup("CHANNEL_MAP"); break; + case ERR_AAC_SYNTAX_ELEMENT: e = strdup("SYNTAX_ELEMENT"); break; + case ERR_AAC_DEQUANT: e = strdup("DEQUANT"); break; + case ERR_AAC_STEREO_PROCESS: e = strdup("STEREO_PROCESS"); break; + case ERR_AAC_PNS: e = strdup("PNS"); break; + case ERR_AAC_SHORT_BLOCK_DEINT: e = strdup("SHORT_BLOCK_DEINT"); break; + case ERR_AAC_TNS: e = strdup("TNS"); break; + case ERR_AAC_IMDCT: e = strdup("IMDCT"); break; + case ERR_AAC_SBR_INIT: e = strdup("SBR_INIT"); break; + case ERR_AAC_SBR_BITSTREAM: e = strdup("SBR_BITSTREAM"); break; + case ERR_AAC_SBR_DATA: e = strdup("SBR_DATA"); break; + case ERR_AAC_SBR_PCM_FORMAT: e = strdup("SBR_PCM_FORMAT"); break; + case ERR_AAC_SBR_NCHANS_TOO_HIGH: e = strdup("SBR_NCHANS_TOO_HIGH"); break; + case ERR_AAC_SBR_SINGLERATE_UNSUPPORTED: e = strdup("BR_SINGLERATE_UNSUPPORTED"); break; + case ERR_AAC_NCHANS_TOO_HIGH: e = strdup("NCHANS_TOO_HIGH"); break; + case ERR_AAC_RAWBLOCK_PARAMS: e = strdup("RAWBLOCK_PARAMS"); break; + default: e = strdup("ERR_UNKNOWN"); + } + sprintf(chbuf, "AAC decode error %d : %s", r, e); + if(audio_info) audio_info(chbuf); + } + if(m_codec == CODEC_FLAC){ + switch(r){ + case ERR_FLAC_NONE: e = strdup("NONE"); break; + case ERR_FLAC_BLOCKSIZE_TOO_BIG: e = strdup("BLOCKSIZE TOO BIG"); break; + case ERR_FLAC_RESERVED_BLOCKSIZE_UNSUPPORTED: e = strdup("Reserved Blocksize unsupported"); break; + case ERR_FLAC_SYNC_CODE_NOT_FOUND: e = strdup("SYNC CODE NOT FOUND"); break; + case ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT: e = strdup("UNKNOWN CHANNEL ASSIGNMENT"); break; + case ERR_FLAC_RESERVED_CHANNEL_ASSIGNMENT: e = strdup("RESERVED CHANNEL ASSIGNMENT"); break; + case ERR_FLAC_RESERVED_SUB_TYPE: e = strdup("RESERVED SUB TYPE"); break; + case ERR_FLAC_PREORDER_TOO_BIG: e = strdup("PREORDER TOO BIG"); break; + case ERR_FLAC_RESERVED_RESIDUAL_CODING: e = strdup("RESERVED RESIDUAL CODING"); break; + case ERR_FLAC_WRONG_RICE_PARTITION_NR: e = strdup("WRONG RICE PARTITION NR"); break; + case ERR_FLAC_BITS_PER_SAMPLE_TOO_BIG: e = strdup("BITS PER SAMPLE > 16"); break; + case ERR_FLAG_BITS_PER_SAMPLE_UNKNOWN: e = strdup("BITS PER SAMPLE UNKNOWN"); break; + default: e = strdup("ERR_UNKNOWN"); + } + sprintf(chbuf, "FLAC decode error %d : %s", r, e); + if(audio_info) audio_info(chbuf); + } + if(e) free(e); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t DIN) { + + m_pin_config.bck_io_num = BCLK; + m_pin_config.ws_io_num = LRC; // wclk + m_pin_config.data_out_num = DOUT; + m_pin_config.data_in_num = DIN; + + const esp_err_t result = i2s_set_pin((i2s_port_t) m_i2s_num, &m_pin_config); + return (result == ESP_OK); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getFileSize() { + if(!audiofile) return 0; + return audiofile.size(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getFilePos() { + if(!audiofile) return 0; + return audiofile.position(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioDataStartPos() { + if(!audiofile) return 0; + return m_audioDataStart; +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioFileDuration() { + if(m_f_localfile) {if(!audiofile) return 0;} + if(m_f_webfile) {if(!m_contentlength) return 0;} + + if (m_avr_bitrate && m_codec == CODEC_MP3) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if(m_avr_bitrate && m_codec == CODEC_WAV) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if(m_avr_bitrate && m_codec == CODEC_M4A) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if(m_avr_bitrate && m_codec == CODEC_AAC) m_audioFileDuration = 8 * m_audioDataSize / m_avr_bitrate; + else if( m_codec == CODEC_FLAC) m_audioFileDuration = FLACGetAudioFileDuration(); + else return 0; + return m_audioFileDuration; +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getAudioCurrentTime() { // return current time in seconds + return (uint32_t) m_audioCurrentTime; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setAudioPlayPosition(uint16_t sec){ + // Jump to an absolute position in time within an audio file + // e.g. setAudioPlayPosition(300) sets the pointer at pos 5 min + // works only with format mp3 or wav + if(m_codec == CODEC_M4A) return false; + if(sec > getAudioFileDuration()) sec = getAudioFileDuration(); + uint32_t filepos = m_audioDataStart + (m_avr_bitrate * sec / 8); + + return setFilePos(filepos); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::getTotalPlayingTime() { + // Is set to zero by a connectToXXX() and starts as soon as the first audio data is available, + // the time counting is not interrupted by a 'pause / resume' and is not reset by a fileloop + return millis() - m_PlayingStartTime; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setTimeOffset(int sec){ + // fast forward or rewind the current position in seconds + // audiosource must be a mp3, aac or wav file + + if(!audiofile || !m_avr_bitrate) return false; + + uint32_t oneSec = m_avr_bitrate / 8; // bytes decoded in one sec + int32_t offset = oneSec * sec; // bytes to be wind/rewind + uint32_t startAB = m_audioDataStart; // audioblock begin + uint32_t endAB = m_audioDataStart + m_audioDataSize; // audioblock end + + if(m_codec == CODEC_MP3 || m_codec == CODEC_AAC || m_codec == CODEC_WAV || m_codec == CODEC_FLAC){ + int32_t pos = getFilePos(); + pos += offset; + if(pos < (int32_t)startAB) pos = startAB; + if(pos >= (int32_t)endAB) pos = endAB; + setFilePos(pos); + return true; + } + return false; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setFilePos(uint32_t pos) { + if(!audiofile) return false; +// if(!m_avr_bitrate) return false; + if(m_codec == CODEC_M4A) return false; + m_f_playing = false; + if(m_codec == CODEC_MP3) MP3Decoder_ClearBuffer(); + if(m_codec == CODEC_WAV) {while((pos % 4) != 0) pos++;} // must be divisible by four + if(m_codec == CODEC_FLAC) FLACDecoderReset(); + InBuff.resetBuffer(); + if(pos < m_audioDataStart) pos = m_audioDataStart; // issue #96 + if(m_avr_bitrate) m_audioCurrentTime = (pos-m_audioDataStart) * 8 / m_avr_bitrate; // #96 + return audiofile.seek(pos); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::audioFileSeek(const float speed) { + // 0.5 is half speed + // 1.0 is normal speed + // 1.5 is one and half speed + if((speed > 1.5f) || (speed < 0.25f)) return false; + + uint32_t srate = getSampleRate() * speed; + i2s_set_sample_rates((i2s_port_t)m_i2s_num, srate); + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setSampleRate(uint32_t sampRate) { + if(!sampRate) sampRate = 16000; // fuse, if there is no value -> set default #209 + i2s_set_sample_rates((i2s_port_t)m_i2s_num, sampRate); + m_sampleRate = sampRate; + IIR_calculateCoefficients(m_gain0, m_gain1, m_gain2); // must be recalculated after each samplerate change + return true; +} +uint32_t Audio::getSampleRate(){ + return m_sampleRate; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setBitsPerSample(int bits) { + if((bits != 16) && (bits != 8)) return false; + m_bitsPerSample = bits; + return true; +} +uint8_t Audio::getBitsPerSample(){ + return m_bitsPerSample; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setChannels(int ch) { + if((ch < 1) || (ch > 2)) return false; + m_channels = ch; + return true; +} +uint8_t Audio::getChannels(){ + if (m_channels == 0) { // this should not happen! #209 + m_channels = 2; + } + return m_channels; +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::setBitrate(int br){ + m_bitRate = br; + if(br)return true; + return false; +} +uint32_t Audio::getBitRate(){ + return m_bitRate; +} +//--------------------------------------------------------------------------------------------------------------------- +[[deprecated]]void Audio::setInternalDAC(bool internalDAC /* = true */, i2s_dac_mode_t channelEnabled /* = I2S_DAC_CHANNEL_LEFT_EN */ ) { +// is deprecated, set internal DAC in constructor e.g. Audio audio(true, I2S_DAC_CHANNEL_BOTH_EN); + m_f_channelEnabled = channelEnabled; + m_f_internalDAC = internalDAC; + i2s_driver_uninstall((i2s_port_t)m_i2s_num); + if (internalDAC) { + log_i("internal DAC"); + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN ); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install((i2s_port_t) m_i2s_num, &m_i2s_config, 0, NULL); + // enable the DAC channels + i2s_set_dac_mode(m_f_channelEnabled); + if(m_f_channelEnabled != I2S_DAC_CHANNEL_BOTH_EN) { + m_f_forceMono = true; + } + } + else { // external DAC + m_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + #endif + + i2s_driver_install ((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t) m_i2s_num, &m_pin_config); + } + // clear the DMA buffers + i2s_zero_dma_buffer((i2s_port_t) m_i2s_num); +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setI2SCommFMT_LSB(bool commFMT) { + // false: I2S communication format is by default I2S_COMM_FORMAT_I2S_MSB, right->left (AC101, PCM5102A) + // true: changed to I2S_COMM_FORMAT_I2S_LSB for some DACs (PT8211) + // Japanese or called LSBJ (Least Significant Bit Justified) format + + if (commFMT) { + log_i("commFMT LSB"); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_MSB); // v >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB); + #endif + + } + else { + log_i("commFMT MSB"); + + #if ESP_ARDUINO_VERSION_MAJOR >= 2 + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // vers >= 2.0.0 + #else + m_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + #endif + + } + log_i("commFMT = %i", m_i2s_config.communication_format); + i2s_driver_uninstall((i2s_port_t)m_i2s_num); + i2s_driver_install ((i2s_port_t)m_i2s_num, &m_i2s_config, 0, NULL); +} +//--------------------------------------------------------------------------------------------------------------------- +bool Audio::playSample(int16_t sample[2]) { + + if (getBitsPerSample() == 8) { // Upsample from unsigned 8 bits to signed 16 bits + sample[LEFTCHANNEL] = ((sample[LEFTCHANNEL] & 0xff) -128) << 8; + sample[RIGHTCHANNEL] = ((sample[RIGHTCHANNEL] & 0xff) -128) << 8; + } + + sample[LEFTCHANNEL] = sample[LEFTCHANNEL] >> 1; // half Vin so we can boost up to 6dB in filters + sample[RIGHTCHANNEL] = sample[RIGHTCHANNEL] >> 1; + + // Filterchain, can commented out if not used + sample = IIR_filterChain0(sample); + sample = IIR_filterChain1(sample); + sample = IIR_filterChain2(sample); + //------------------------------------------- + + uint32_t s32 = Gain(sample); // vosample2lume; + + if(m_f_internalDAC) { + s32 += 0x80008000; + } + + esp_err_t err = i2s_write((i2s_port_t) m_i2s_num, (const char*) &s32, sizeof(uint32_t), &m_i2s_bytesWritten, 1000); + if(err != ESP_OK) { + log_e("ESP32 Errorcode %i", err); + return false; + } + if(m_i2s_bytesWritten < 4) { + log_e("Can't stuff any more in I2S..."); // increase waitingtime or outputbuffer + return false; + } + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass){ + // see https://www.earlevel.com/main/2013/10/13/biquad-calculator-v2/ + // values can be between -40 ... +6 (dB) + + m_gain0 = gainLowPass; + m_gain1 = gainBandPass; + m_gain2 = gainHighPass; + + IIR_calculateCoefficients(m_gain0, m_gain1, m_gain2); + + /* + This will cause a clicking sound when adjusting the EQ. + Because when the EQ is adjusted, the IIR filter will be cleared and played, + mixed in the audio data frame, and a click-like sound will be produced. + */ + /* + int16_t tmp[2]; tmp[0] = 0; tmp[1]= 0; + + IIR_filterChain0(tmp, true ); // flush the filter + IIR_filterChain1(tmp, true ); // flush the filter + IIR_filterChain2(tmp, true ); // flush the filter + */ +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::forceMono(bool m) { // #100 mono option + m_f_forceMono = m; // false stereo, true mono +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setBalance(int8_t bal){ // bal -16...16 + if(bal < -16) bal = -16; + if(bal > 16) bal = 16; + m_balance = bal; +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::setVolume(uint8_t vol) { // vol 22 steps, 0...21 + //if(vol > 21) vol = 21; + //volume = map(eeprom_config.volume, 0, 21, 0, 255); + m_vol = map(vol, 0, 254, 0, 64); + //m_vol = volumetable[vol]; +} +//--------------------------------------------------------------------------------------------------------------------- +uint8_t Audio::getVolume() { + return map(m_vol, 0, 64, 0, 254); + for(uint8_t i = 0; i < 22; i++) { + if(volumetable[i] == m_vol) return i; + } + m_vol = 12; // if m_vol not found in table + return m_vol; +} +//--------------------------------------------------------------------------------------------------------------------- +int32_t Audio::Gain(int16_t s[2]) { + int32_t v[2]; + float step = (float)m_vol /64; + uint8_t l = 0, r = 0; + + if(m_balance < 0){ + step = step * (float)(abs(m_balance) * 4); + l = (uint8_t)(step); + } + if(m_balance > 0){ + step = step * m_balance * 4; + r = (uint8_t)(step); + } + + v[LEFTCHANNEL] = (s[LEFTCHANNEL] * (m_vol - l)) >> 6; + v[RIGHTCHANNEL]= (s[RIGHTCHANNEL] * (m_vol - r)) >> 6; + + return (v[RIGHTCHANNEL] << 16) | (v[LEFTCHANNEL] & 0xffff); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::inBufferFilled() { + // current audio input buffer fillsize in bytes + return InBuff.bufferFilled(); +} +//--------------------------------------------------------------------------------------------------------------------- +uint32_t Audio::inBufferFree() { + // current audio input buffer free space in bytes + return InBuff.freeSpace(); +} +//--------------------------------------------------------------------------------------------------------------------- +// *** D i g i t a l b i q u a d r a t i c f i l t e r *** +//--------------------------------------------------------------------------------------------------------------------- +void Audio::IIR_calculateCoefficients(int8_t G0, int8_t G1, int8_t G2){ // Infinite Impulse Response (IIR) filters + + // G1 - gain low shelf set between -40 ... +6 dB + // G2 - gain peakEQ set between -40 ... +6 dB + // G3 - gain high shelf set between -40 ... +6 dB + // https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ + + if(getSampleRate() < 1000) return; // fuse + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /*if(G0 < -40) G0 = -40; // -40dB -> Vin*0.01 + if(G0 > 6) G0 = 6; // +6dB -> Vin*2 + if(G1 < -40) G1 = -40; + if(G1 > 6) G1 = 6; + if(G2 < -40) G2 = -40; + if(G2 > 6) G2 = 6;*/ + if(G0 < -40) G0 = -40; // -40dB -> Vin*0.01 + if(G0 > 16) G0 = 16; // +6dB -> Vin*2 + if(G1 < -40) G1 = -40; + if(G1 > 16) G1 = 16; + if(G2 < -40) G2 = -40; + if(G2 > 16) G2 = 16; + /*const float FcLS = 500; // Frequency LowShelf[Hz] + const float FcPKEQ = 3000; // Frequency PeakEQ[Hz] + const float FcHS = 6000; // Frequency HighShelf[Hz]*/ + const float FcLS = 500; // Frequency LowShelf[Hz] + const float FcPKEQ = 3000; // Frequency PeakEQ[Hz] + const float FcHS = 6000; // Frequency HighShelf[Hz] + + float K, norm, Q, Fc, V ; + + // LOWSHELF + Fc = (float)FcLS / (float)getSampleRate(); // Cutoff frequency + K = tanf((float)PI * Fc); + V = powf(10, fabs(G0) / 20.0); + + if (G0 >= 0) { // boost + norm = 1 / (1 + sqrtf(2) * K + K * K); + m_filter[LOWSHELF].a0 = (1 + sqrtf(2*V) * K + V * K * K) * norm; + m_filter[LOWSHELF].a1 = 2 * (V * K * K - 1) * norm; + m_filter[LOWSHELF].a2 = (1 - sqrtf(2*V) * K + V * K * K) * norm; + m_filter[LOWSHELF].b1 = 2 * (K * K - 1) * norm; + m_filter[LOWSHELF].b2 = (1 - sqrtf(2) * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + sqrtf(2*V) * K + V * K * K); + m_filter[LOWSHELF].a0 = (1 + sqrtf(2) * K + K * K) * norm; + m_filter[LOWSHELF].a1 = 2 * (K * K - 1) * norm; + m_filter[LOWSHELF].a2 = (1 - sqrtf(2) * K + K * K) * norm; + m_filter[LOWSHELF].b1 = 2 * (V * K * K - 1) * norm; + m_filter[LOWSHELF].b2 = (1 - sqrtf(2*V) * K + V * K * K) * norm; + } + + // PEAK EQ + Fc = (float)FcPKEQ / (float)getSampleRate(); // Cutoff frequency + K = tanf((float)PI * Fc); + V = powf(10, fabs(G1) / 20.0); + Q = 2.5; // Quality factor + if (G1 >= 0) { // boost + norm = 1 / (1 + 1/Q * K + K * K); + m_filter[PEAKEQ].a0 = (1 + V/Q * K + K * K) * norm; + m_filter[PEAKEQ].a1 = 2 * (K * K - 1) * norm; + m_filter[PEAKEQ].a2 = (1 - V/Q * K + K * K) * norm; + m_filter[PEAKEQ].b1 = m_filter[PEAKEQ].a1; + m_filter[PEAKEQ].b2 = (1 - 1/Q * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + V/Q * K + K * K); + m_filter[PEAKEQ].a0 = (1 + 1/Q * K + K * K) * norm; + m_filter[PEAKEQ].a1 = 2 * (K * K - 1) * norm; + m_filter[PEAKEQ].a2 = (1 - 1/Q * K + K * K) * norm; + m_filter[PEAKEQ].b1 = m_filter[PEAKEQ].a1; + m_filter[PEAKEQ].b2 = (1 - V/Q * K + K * K) * norm; + } + + // HIGHSHELF + Fc = (float)FcHS / (float)getSampleRate(); // Cutoff frequency + K = tanf((float)PI * Fc); + V = powf(10, fabs(G2) / 20.0); + if (G2 >= 0) { // boost + norm = 1 / (1 + sqrtf(2) * K + K * K); + m_filter[HIFGSHELF].a0 = (V + sqrtf(2*V) * K + K * K) * norm; + m_filter[HIFGSHELF].a1 = 2 * (K * K - V) * norm; + m_filter[HIFGSHELF].a2 = (V - sqrtf(2*V) * K + K * K) * norm; + m_filter[HIFGSHELF].b1 = 2 * (K * K - 1) * norm; + m_filter[HIFGSHELF].b2 = (1 - sqrtf(2) * K + K * K) * norm; + } + else { + norm = 1 / (V + sqrtf(2*V) * K + K * K); + m_filter[HIFGSHELF].a0 = (1 + sqrtf(2) * K + K * K) * norm; + m_filter[HIFGSHELF].a1 = 2 * (K * K - 1) * norm; + m_filter[HIFGSHELF].a2 = (1 - sqrtf(2) * K + K * K) * norm; + m_filter[HIFGSHELF].b1 = 2 * (K * K - V) * norm; + m_filter[HIFGSHELF].b2 = (V - sqrtf(2*V) * K + K * K) * norm; + } + +// log_i("LS a0=%f, a1=%f, a2=%f, b1=%f, b2=%f", m_filter[0].a0, m_filter[0].a1, m_filter[0].a2, +// m_filter[0].b1, m_filter[0].b2); +// log_i("EQ a0=%f, a1=%f, a2=%f, b1=%f, b2=%f", m_filter[1].a0, m_filter[1].a1, m_filter[1].a2, +// m_filter[1].b1, m_filter[1].b2); +// log_i("HS a0=%f, a1=%f, a2=%f, b1=%f, b2=%f", m_filter[2].a0, m_filter[2].a1, m_filter[2].a2, +// m_filter[2].b1, m_filter[2].b2); +} +//--------------------------------------------------------------------------------------------------------------------- +int16_t* Audio::IIR_filterChain0(int16_t iir_in[2], bool clear){ // Infinite Impulse Response (IIR) filters + + uint8_t z1 = 0, z2 = 1; + enum: uint8_t {in = 0, out = 1}; + float inSample[2]; + float outSample[2]; + static int16_t iir_out[2]; + + if(clear){ + memset(m_filterBuff, 0, sizeof(m_filterBuff)); // zero IIR filterbuffer + iir_out[0] = 0; + iir_out[1] = 0; + iir_in[0] = 0; + iir_in[1] = 0; + } + + inSample[LEFTCHANNEL] = (float)(iir_in[LEFTCHANNEL]); + inSample[RIGHTCHANNEL] = (float)(iir_in[RIGHTCHANNEL]); + + outSample[LEFTCHANNEL] = m_filter[0].a0 * inSample[LEFTCHANNEL] + + m_filter[0].a1 * m_filterBuff[0][z1][in] [LEFTCHANNEL] + + m_filter[0].a2 * m_filterBuff[0][z2][in] [LEFTCHANNEL] + - m_filter[0].b1 * m_filterBuff[0][z1][out][LEFTCHANNEL] + - m_filter[0].b2 * m_filterBuff[0][z2][out][LEFTCHANNEL]; + + m_filterBuff[0][z2][in] [LEFTCHANNEL] = m_filterBuff[0][z1][in][LEFTCHANNEL]; + m_filterBuff[0][z1][in] [LEFTCHANNEL] = inSample[LEFTCHANNEL]; + m_filterBuff[0][z2][out][LEFTCHANNEL] = m_filterBuff[0][z1][out][LEFTCHANNEL]; + m_filterBuff[0][z1][out][LEFTCHANNEL] = outSample[LEFTCHANNEL]; + iir_out[LEFTCHANNEL] = (int16_t)outSample[LEFTCHANNEL]; + + + outSample[RIGHTCHANNEL] = m_filter[0].a0 * inSample[RIGHTCHANNEL] + + m_filter[0].a1 * m_filterBuff[0][z1][in] [RIGHTCHANNEL] + + m_filter[0].a2 * m_filterBuff[0][z2][in] [RIGHTCHANNEL] + - m_filter[0].b1 * m_filterBuff[0][z1][out][RIGHTCHANNEL] + - m_filter[0].b2 * m_filterBuff[0][z2][out][RIGHTCHANNEL]; + + m_filterBuff[0][z2][in] [RIGHTCHANNEL] = m_filterBuff[0][z1][in][RIGHTCHANNEL]; + m_filterBuff[0][z1][in] [RIGHTCHANNEL] = inSample[RIGHTCHANNEL]; + m_filterBuff[0][z2][out][RIGHTCHANNEL] = m_filterBuff[0][z1][out][RIGHTCHANNEL]; + m_filterBuff[0][z1][out][RIGHTCHANNEL] = outSample[RIGHTCHANNEL]; + iir_out[RIGHTCHANNEL] = (int16_t) outSample[RIGHTCHANNEL]; + + return iir_out; +} +//--------------------------------------------------------------------------------------------------------------------- +int16_t* Audio::IIR_filterChain1(int16_t iir_in[2], bool clear){ // Infinite Impulse Response (IIR) filters + + uint8_t z1 = 0, z2 = 1; + enum: uint8_t {in = 0, out = 1}; + float inSample[2]; + float outSample[2]; + static int16_t iir_out[2]; + + if(clear){ + memset(m_filterBuff, 0, sizeof(m_filterBuff)); // zero IIR filterbuffer + iir_out[0] = 0; + iir_out[1] = 0; + iir_in[0] = 0; + iir_in[1] = 0; + } + + inSample[LEFTCHANNEL] = (float)(iir_in[LEFTCHANNEL]); + inSample[RIGHTCHANNEL] = (float)(iir_in[RIGHTCHANNEL]); + + outSample[LEFTCHANNEL] = m_filter[1].a0 * inSample[LEFTCHANNEL] + + m_filter[1].a1 * m_filterBuff[1][z1][in] [LEFTCHANNEL] + + m_filter[1].a2 * m_filterBuff[1][z2][in] [LEFTCHANNEL] + - m_filter[1].b1 * m_filterBuff[1][z1][out][LEFTCHANNEL] + - m_filter[1].b2 * m_filterBuff[1][z2][out][LEFTCHANNEL]; + + m_filterBuff[1][z2][in] [LEFTCHANNEL] = m_filterBuff[1][z1][in][LEFTCHANNEL]; + m_filterBuff[1][z1][in] [LEFTCHANNEL] = inSample[LEFTCHANNEL]; + m_filterBuff[1][z2][out][LEFTCHANNEL] = m_filterBuff[1][z1][out][LEFTCHANNEL]; + m_filterBuff[1][z1][out][LEFTCHANNEL] = outSample[LEFTCHANNEL]; + iir_out[LEFTCHANNEL] = (int16_t)outSample[LEFTCHANNEL]; + + + outSample[RIGHTCHANNEL] = m_filter[1].a0 * inSample[RIGHTCHANNEL] + + m_filter[1].a1 * m_filterBuff[1][z1][in] [RIGHTCHANNEL] + + m_filter[1].a2 * m_filterBuff[1][z2][in] [RIGHTCHANNEL] + - m_filter[1].b1 * m_filterBuff[1][z1][out][RIGHTCHANNEL] + - m_filter[1].b2 * m_filterBuff[1][z2][out][RIGHTCHANNEL]; + + m_filterBuff[1][z2][in] [RIGHTCHANNEL] = m_filterBuff[1][z1][in][RIGHTCHANNEL]; + m_filterBuff[1][z1][in] [RIGHTCHANNEL] = inSample[RIGHTCHANNEL]; + m_filterBuff[1][z2][out][RIGHTCHANNEL] = m_filterBuff[1][z1][out][RIGHTCHANNEL]; + m_filterBuff[1][z1][out][RIGHTCHANNEL] = outSample[RIGHTCHANNEL]; + iir_out[RIGHTCHANNEL] = (int16_t) outSample[RIGHTCHANNEL]; + + return iir_out; +} +//--------------------------------------------------------------------------------------------------------------------- +int16_t* Audio::IIR_filterChain2(int16_t iir_in[2], bool clear){ // Infinite Impulse Response (IIR) filters + + uint8_t z1 = 0, z2 = 1; + enum: uint8_t {in = 0, out = 1}; + float inSample[2]; + float outSample[2]; + static int16_t iir_out[2]; + + if(clear){ + memset(m_filterBuff, 0, sizeof(m_filterBuff)); // zero IIR filterbuffer + iir_out[0] = 0; + iir_out[1] = 0; + iir_in[0] = 0; + iir_in[1] = 0; + } + + inSample[LEFTCHANNEL] = (float)(iir_in[LEFTCHANNEL]); + inSample[RIGHTCHANNEL] = (float)(iir_in[RIGHTCHANNEL]); + + outSample[LEFTCHANNEL] = m_filter[2].a0 * inSample[LEFTCHANNEL] + + m_filter[2].a1 * m_filterBuff[2][z1][in] [LEFTCHANNEL] + + m_filter[2].a2 * m_filterBuff[2][z2][in] [LEFTCHANNEL] + - m_filter[2].b1 * m_filterBuff[2][z1][out][LEFTCHANNEL] + - m_filter[2].b2 * m_filterBuff[2][z2][out][LEFTCHANNEL]; + + m_filterBuff[2][z2][in] [LEFTCHANNEL] = m_filterBuff[2][z1][in][LEFTCHANNEL]; + m_filterBuff[2][z1][in] [LEFTCHANNEL] = inSample[LEFTCHANNEL]; + m_filterBuff[2][z2][out][LEFTCHANNEL] = m_filterBuff[2][z1][out][LEFTCHANNEL]; + m_filterBuff[2][z1][out][LEFTCHANNEL] = outSample[LEFTCHANNEL]; + iir_out[LEFTCHANNEL] = (int16_t)outSample[LEFTCHANNEL]; + + + outSample[RIGHTCHANNEL] = m_filter[2].a0 * inSample[RIGHTCHANNEL] + + m_filter[2].a1 * m_filterBuff[2][z1][in] [RIGHTCHANNEL] + + m_filter[2].a2 * m_filterBuff[2][z2][in] [RIGHTCHANNEL] + - m_filter[2].b1 * m_filterBuff[2][z1][out][RIGHTCHANNEL] + - m_filter[2].b2 * m_filterBuff[2][z2][out][RIGHTCHANNEL]; + + m_filterBuff[2][z2][in] [RIGHTCHANNEL] = m_filterBuff[2][z1][in][RIGHTCHANNEL]; + m_filterBuff[2][z1][in] [RIGHTCHANNEL] = inSample[RIGHTCHANNEL]; + m_filterBuff[2][z2][out][RIGHTCHANNEL] = m_filterBuff[2][z1][out][RIGHTCHANNEL]; + m_filterBuff[2][z1][out][RIGHTCHANNEL] = outSample[RIGHTCHANNEL]; + iir_out[RIGHTCHANNEL] = (int16_t) outSample[RIGHTCHANNEL]; + + return iir_out; +} diff --git a/yoRadio/src/audioI2S/AudioEx.h b/yoRadio/src/audioI2S/AudioEx.h new file mode 100644 index 0000000..4ebaa56 --- /dev/null +++ b/yoRadio/src/audioI2S/AudioEx.h @@ -0,0 +1,480 @@ +/* + * Audio.h + * + * Created on: Oct 26,2018 + * Updated on: Jan 05,2022 + * Author: Wolle (schreibfaul1) + */ + +//#define SDFATFS_USED // activate for SdFat + + +#pragma once +#pragma GCC optimize ("Ofast") + +#include +#include +#include +#include +#include + +#include + +#ifdef SDFATFS_USED +#include // https://github.com/greiman/SdFat +#else +#include +#include +#include +#include +#include +#endif // SDFATFS_USED + +#define AUDIOBUFFER_MULTIPLIER 13 + +#ifdef SDFATFS_USED +typedef File32 File; + +namespace fs { + class FS : public SdFat { + public: + bool begin(SdCsPin_t csPin = SS, uint32_t maxSck = SD_SCK_MHZ(25)) { return SdFat::begin(csPin, maxSck); } + }; + + class SDFATFS : public fs::FS { + public: + // sdcard_type_t cardType(); + uint64_t cardSize() { + return totalBytes(); + } + uint64_t usedBytes() { + // set SdFatConfig MAINTAIN_FREE_CLUSTER_COUNT non-zero. Then only the first call will take time. + return (uint64_t)(clusterCount() - freeClusterCount()) * (uint64_t)bytesPerCluster(); + } + uint64_t totalBytes() { + return (uint64_t)clusterCount() * (uint64_t)bytesPerCluster(); + } + }; +} + +extern fs::SDFATFS SD_SDFAT; + +using namespace fs; +#define SD SD_SDFAT +#endif //SDFATFS_USED + + + + +extern __attribute__((weak)) void audio_info(const char*); +extern __attribute__((weak)) void audio_id3data(const char*); //ID3 metadata +extern __attribute__((weak)) void audio_id3image(File& file, const size_t pos, const size_t size); //ID3 metadata image +extern __attribute__((weak)) void audio_eof_mp3(const char*); //end of mp3 file +extern __attribute__((weak)) void audio_showstreamtitle(const char*); +extern __attribute__((weak)) void audio_showstation(const char*); +extern __attribute__((weak)) void audio_bitrate(const char*); +extern __attribute__((weak)) void audio_commercial(const char*); +extern __attribute__((weak)) void audio_icyurl(const char*); +extern __attribute__((weak)) void audio_icydescription(const char*); +extern __attribute__((weak)) void audio_lasthost(const char*); +extern __attribute__((weak)) void audio_eof_speech(const char*); +extern __attribute__((weak)) void audio_eof_stream(const char*); // The webstream comes to an end +extern __attribute__((weak)) void audio_process_extern(int16_t* buff, uint16_t len, bool *continueI2S); // record audiodata or send via BT + +//---------------------------------------------------------------------------------------------------------------------- + +class AudioBuffer { +// AudioBuffer will be allocated in PSRAM, If PSRAM not available or has not enough space AudioBuffer will be +// allocated in FlashRAM with reduced size +// +// m_buffer m_readPtr m_writePtr m_endPtr +// | |<------dataLength------->|<------ writeSpace ----->| +// ▼ ▼ ▼ ▼ +// --------------------------------------------------------------------------------------------------------------- +// | <--m_buffSize--> | <--m_resBuffSize --> | +// --------------------------------------------------------------------------------------------------------------- +// |<-----freeSpace------->| |<------freeSpace-------->| +// +// +// +// if the space between m_readPtr and buffend < m_resBuffSize copy data from the beginning to resBuff +// so that the mp3/aac/flac frame is always completed +// +// m_buffer m_writePtr m_readPtr m_endPtr +// | |<-------writeSpace------>|<--dataLength-->| +// ▼ ▼ ▼ ▼ +// --------------------------------------------------------------------------------------------------------------- +// | <--m_buffSize--> | <--m_resBuffSize --> | +// --------------------------------------------------------------------------------------------------------------- +// |<--- ------dataLength-- ------>|<-------freeSpace------->| +// +// + +public: + AudioBuffer(size_t maxBlockSize = 0); // constructor + ~AudioBuffer(); // frees the buffer + size_t init(); // set default values + void changeMaxBlockSize(uint16_t mbs); // is default 1600 for mp3 and aac, set 16384 for FLAC + uint16_t getMaxBlockSize(); // returns maxBlockSize + size_t freeSpace(); // number of free bytes to overwrite + size_t writeSpace(); // space fom writepointer to bufferend + size_t bufferFilled(); // returns the number of filled bytes + void bytesWritten(size_t bw); // update writepointer + void bytesWasRead(size_t br); // update readpointer + uint8_t* getWritePtr(); // returns the current writepointer + uint8_t* getReadPtr(); // returns the current readpointer + uint32_t getWritePos(); // write position relative to the beginning + uint32_t getReadPos(); // read position relative to the beginning + void resetBuffer(); // restore defaults + +protected: + const size_t m_buffSizePSRAM = 300000; // most webstreams limit the advance to 100...300Kbytes + //const size_t m_buffSizeRAM = 1600 * 5 * AUDIOBUFFER_MULTIPLIER; + const size_t m_buffSizeRAM = 1600 * AUDIOBUFFER_MULTIPLIER; + size_t m_buffSize = 0; + size_t m_freeSpace = 0; + size_t m_writeSpace = 0; + size_t m_dataLength = 0; + //size_t m_resBuffSizeRAM = 1600 * AUDIOBUFFER_MULTIPLIER; // reserved buffspace, >= one mp3 frame + size_t m_resBuffSizeRAM = 1600; // reserved buffspace, >= one mp3 frame + size_t m_resBuffSizePSRAM = 4096 * 4; // reserved buffspace, >= one flac frame + size_t m_maxBlockSize = 1600; + uint8_t* m_buffer = NULL; + uint8_t* m_writePtr = NULL; + uint8_t* m_readPtr = NULL; + uint8_t* m_endPtr = NULL; + bool m_f_start = true; +}; +//---------------------------------------------------------------------------------------------------------------------- + +class Audio : private AudioBuffer{ + + AudioBuffer InBuff; // instance of input buffer + +public: + Audio(bool internalDAC = false, i2s_dac_mode_t channelEnabled = I2S_DAC_CHANNEL_LEFT_EN); // #99 + ~Audio(); + bool connecttohost(const char* host, const char* user = "", const char* pwd = ""); + bool connecttospeech(const char* speech, const char* lang); + bool connecttoFS(fs::FS &fs, const char* path); + bool connecttoSD(const char* path); + bool setFileLoop(bool input);//TEST loop + bool setAudioPlayPosition(uint16_t sec); + bool setFilePos(uint32_t pos); + bool audioFileSeek(const float speed); + bool setTimeOffset(int sec); + bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t DIN=I2S_PIN_NO_CHANGE); + bool pauseResume(); + bool isRunning() {return m_f_running;} + void loop(); + void stopSong(); + void forceMono(bool m); + void setBalance(int8_t bal = 0); + void setVolume(uint8_t vol); + uint8_t getVolume(); + + uint32_t getAudioDataStartPos(); + uint32_t getFileSize(); + uint32_t getFilePos(); + uint32_t getSampleRate(); + uint8_t getBitsPerSample(); + uint8_t getChannels(); + uint32_t getBitRate(); + uint32_t getAudioFileDuration(); + uint32_t getAudioCurrentTime(); + uint32_t getTotalPlayingTime(); + + esp_err_t i2s_mclk_pin_select(const uint8_t pin); + uint32_t inBufferFilled(); // returns the number of stored bytes in the inputbuffer + uint32_t inBufferFree(); // returns the number of free bytes in the inputbuffer + void setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass); + [[deprecated]]void setInternalDAC(bool internalDAC = true, i2s_dac_mode_t channelEnabled = I2S_DAC_CHANNEL_LEFT_EN); + void setI2SCommFMT_LSB(bool commFMT); + +private: + void UTF8toASCII(char* str); + bool latinToUTF8(char* buff, size_t bufflen); + void httpPrint(const char* url); + void setDefaults(); // free buffers and set defaults + void initInBuff(); + void processLocalFile(); + void processWebStream(); + void processPlayListData(); + void processM3U8entries(uint8_t nrOfEntries = 0, uint32_t seqNr = 0, uint8_t pos = 0, uint16_t targetDuration = 0); + bool STfromEXTINF(char* str); + void showCodecParams(); + int findNextSync(uint8_t* data, size_t len); + int sendBytes(uint8_t* data, size_t len); + void compute_audioCurrentTime(int bd); + void printDecodeError(int r); + void showID3Tag(const char* tag, const char* val); + void unicode2utf8(char* buff, uint32_t len); + int read_WAV_Header(uint8_t* data, size_t len); + int read_FLAC_Header(uint8_t *data, size_t len); + int read_MP3_Header(uint8_t* data, size_t len); + int read_M4A_Header(uint8_t* data, size_t len); + int read_OGG_Header(uint8_t *data, size_t len); + bool setSampleRate(uint32_t hz); + bool setBitsPerSample(int bits); + bool setChannels(int channels); + bool setBitrate(int br); + bool playChunk(); + bool playSample(int16_t sample[2]) ; + bool playI2Sremains(); + int32_t Gain(int16_t s[2]); + bool fill_InputBuf(); + void showstreamtitle(const char* ml); + bool parseContentType(const char* ct); + void processAudioHeaderData(); + bool readMetadata(uint8_t b, bool first = false); + esp_err_t I2Sstart(uint8_t i2s_num); + esp_err_t I2Sstop(uint8_t i2s_num); + void urlencode(char* buff, uint16_t buffLen, bool spacesOnly = false); + int16_t* IIR_filterChain0(int16_t iir_in[2], bool clear = false); + int16_t* IIR_filterChain1(int16_t* iir_in, bool clear = false); + int16_t* IIR_filterChain2(int16_t* iir_in, bool clear = false); + inline void setDatamode(uint8_t dm){m_datamode=dm;} + inline uint8_t getDatamode(){return m_datamode;} + inline uint32_t streamavail() {if(m_f_ssl==false) return client.available(); else return clientsecure.available();} + void IIR_calculateCoefficients(int8_t G1, int8_t G2, int8_t G3); + + // implement several function with respect to the index of string + void trim(char *s) { + //fb trim in place + char *pe; + char *p = s; + while ( isspace(*p) ) p++; //left + pe = p; //right + while ( *pe != '\0' ) pe++; + do { + pe--; + } while ( (pe > p) && isspace(*pe) ); + if (p == s) { + *++pe = '\0'; + } else { //move + while ( p <= pe ) *s++ = *p++; + *s = '\0'; + } + } + + bool startsWith (const char* base, const char* str) { + //fb + char c; + while ( (c = *str++) != '\0' ) + if (c != *base++) return false; + return true; + } + + bool endsWith (const char* base, const char* str) { + //fb + int slen = strlen(str) - 1; + const char *p = base + strlen(base) - 1; + while(p > base && isspace(*p)) p--; // rtrim + p -= slen; + if (p < base) return false; + return (strncmp(p, str, slen) == 0); + } + + int indexOf (const char* base, const char* str, int startIndex) { + //fb + const char *p = base; + for (; startIndex > 0; startIndex--) + if (*p++ == '\0') return -1; + char* pos = strstr(p, str); + if (pos == nullptr) return -1; + return pos - base; + } + + int indexOf (const char* base, char ch, int startIndex) { + //fb + const char *p = base; + for (; startIndex > 0; startIndex--) + if (*p++ == '\0') return -1; + char *pos = strchr(p, ch); + if (pos == nullptr) return -1; + return pos - base; + } + + int lastIndexOf(const char* haystack, const char* needle) { + //fb + int nlen = strlen(needle); + if (nlen == 0) return -1; + const char *p = haystack - nlen + strlen(haystack); + while (p >= haystack) { + int i = 0; + while (needle[i] == p[i]) + if (++i == nlen) return p - haystack; + p--; + } + return -1; + } + + int lastIndexOf(const char* haystack, const char needle) { + //fb + const char *p = strrchr(haystack, needle); + return (p ? p - haystack : -1); + } + + int specialIndexOf (uint8_t* base, const char* str, int baselen, bool exact = false){ + int result; // seek for str in buffer or in header up to baselen, not nullterninated + if (strlen(str) > baselen) return -1; // if exact == true seekstr in buffer must have "\0" at the end + for (int i = 0; i < baselen - strlen(str); i++){ + result = i; + for (int j = 0; j < strlen(str) + exact; j++){ + if (*(base + i + j) != *(str + j)){ + result = -1; + break; + } + } + if (result >= 0) break; + } + return result; + } + size_t bigEndian(uint8_t* base, uint8_t numBytes, uint8_t shiftLeft = 8){ + size_t result = 0; + if(numBytes < 1 or numBytes > 4) return 0; + for (int i = 0; i < numBytes; i++) { + result += *(base + i) << (numBytes -i - 1) * shiftLeft; + } + return result; + } + bool b64encode(const char* source, uint16_t sourceLength, char* dest){ + size_t size = base64_encode_expected_len(sourceLength) + 1; + char * buffer = (char *) malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block(&source[0], sourceLength, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + memcpy(dest, buffer, strlen(buffer)); + dest[strlen(buffer)] = '\0'; + free(buffer); + return true; + } + return false; + } + size_t urlencode_expected_len(const char* source){ + size_t expectedLen = strlen(source); + for(int i = 0; i < strlen(source); i++) { + if(isalnum(source[i])){;} + else expectedLen += 2; + } + return expectedLen; + } + + +private: + enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; + enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; + enum : int { CODEC_NONE, CODEC_WAV, CODEC_MP3, CODEC_AAC, CODEC_M4A, CODEC_FLAC, CODEC_OGG, + CODEC_OGG_FLAC, CODEC_OGG_OPUS}; + enum : int { FORMAT_NONE = 0, FORMAT_M3U = 1, FORMAT_PLS = 2, FORMAT_ASX = 3, FORMAT_M3U8 = 4}; + enum : int { AUDIO_NONE, AUDIO_HEADER, AUDIO_DATA, + AUDIO_PLAYLISTINIT, AUDIO_PLAYLISTHEADER, AUDIO_PLAYLISTDATA}; + enum : int { FLAC_BEGIN = 0, FLAC_MAGIC = 1, FLAC_MBH =2, FLAC_SINFO = 3, FLAC_PADDING = 4, FLAC_APP = 5, + FLAC_SEEK = 6, FLAC_VORBIS = 7, FLAC_CUESHEET = 8, FLAC_PICTURE = 9, FLAC_OKAY = 100}; + enum : int { M4A_BEGIN = 0, M4A_FTYP = 1, M4A_CHK = 2, M4A_MOOV = 3, M4A_FREE = 4, M4A_TRAK = 5, M4A_MDAT = 6, + M4A_ILST = 7, M4A_MP4A = 8, M4A_AMRDY = 99, M4A_OKAY = 100}; + enum : int { OGG_BEGIN = 0, OGG_MAGIC = 1, OGG_HEADER = 2, OGG_FIRST = 3, OGG_AMRDY = 99, OGG_OKAY = 100}; + typedef enum { LEFTCHANNEL=0, RIGHTCHANNEL=1 } SampleIndex; + typedef enum { LOWSHELF = 0, PEAKEQ = 1, HIFGSHELF =2 } FilterType; + + const uint8_t volumetable[22]={ 0, 1, 2, 3, 4 , 6 , 8, 10, 12, 14, 17, + 20, 23, 27, 30 ,34, 38, 43 ,48, 52, 58, 64}; //22 elements + + typedef struct _filter{ + float a0; + float a1; + float a2; + float b1; + float b2; + } filter_t; + + File audiofile; // @suppress("Abstract class cannot be instantiated") + WiFiClient client; // @suppress("Abstract class cannot be instantiated") + WiFiClientSecure clientsecure; // @suppress("Abstract class cannot be instantiated") + WiFiUDP udpclient; // @suppress("Abstract class cannot be instantiated") + i2s_config_t m_i2s_config; // stores values for I2S driver + i2s_pin_config_t m_pin_config; + + const size_t m_frameSizeWav = 1600; + const size_t m_frameSizeMP3 = 1600; + const size_t m_frameSizeAAC = 1600; + const size_t m_frameSizeFLAC = 4096 * 4; + + char chbuf[512 + 128]; // must be greater than m_lastHost #254 + char m_lastHost[512]; // Store the last URL to a webstream + char* m_playlistBuff = NULL; // stores playlistdata + const uint16_t m_plsBuffEntryLen = 256; // length of each entry in playlistBuff + filter_t m_filter[3]; // digital filters + int m_LFcount = 0; // Detection of end of header + uint32_t m_sampleRate=16000; + uint32_t m_bitRate=0; // current bitrate given fom decoder + uint32_t m_avr_bitrate = 0; // average bitrate, median computed by VBR + int m_readbytes=0; // bytes read + int m_metalen=0; // Number of bytes in metadata + int m_controlCounter = 0; // Status within readID3data() and readWaveHeader() + int8_t m_balance = 0; // -16 (mute left) ... +16 (mute right) + uint8_t m_vol=64; // volume + uint8_t m_bitsPerSample = 16; // bitsPerSample + uint8_t m_channels=2; + uint8_t m_i2s_num = I2S_NUM_0; // I2S_NUM_0 or I2S_NUM_1 + uint8_t m_playlistFormat = 0; // M3U, PLS, ASX + uint8_t m_m3u8codec = CODEC_NONE; // M4A + uint8_t m_codec = CODEC_NONE; // + uint8_t m_filterType[2]; // lowpass, highpass + int16_t m_outBuff[2048*2]; // Interleaved L/R + int16_t m_validSamples = 0; + int16_t m_curSample = 0; + uint16_t m_st_remember = 0; // Save hash from the last streamtitle + uint16_t m_datamode = 0; // Statemaschine + uint8_t m_flacBitsPerSample = 0; // bps should be 16 + uint8_t m_flacNumChannels = 0; // can be read out in the FLAC file header + uint32_t m_flacSampleRate = 0; // can be read out in the FLAC file header + uint16_t m_flacMaxFrameSize = 0; // can be read out in the FLAC file header + uint16_t m_flacMaxBlockSize = 0; // can be read out in the FLAC file header + uint32_t m_flacTotalSamplesInStream = 0; // can be read out in the FLAC file header + uint32_t m_metaint = 0; // Number of databytes between metadata + uint32_t m_chunkcount = 0 ; // Counter for chunked transfer + uint32_t m_t0 = 0; // store millis(), is needed for a small delay + uint32_t m_contentlength = 0; // Stores the length if the stream comes from fileserver + uint32_t m_bytesNotDecoded = 0; // pictures or something else that comes with the stream + uint32_t m_PlayingStartTime = 0; // Stores the milliseconds after the start of the audio + bool m_f_swm = true; // Stream without metadata + bool m_f_unsync = false; // set within ID3 tag but not used + bool m_f_exthdr = false; // ID3 extended header + bool m_f_localfile = false ; // Play from local mp3-file + bool m_f_webstream = false ; // Play from URL + bool m_f_ssl = false; + bool m_f_running = false; + bool m_f_firstCall = false; // InitSequence for processWebstream and processLokalFile + bool m_f_ctseen = false; // First line of header seen or not + bool m_f_chunked = false ; // Station provides chunked transfer + bool m_f_firstmetabyte = false; // True if first metabyte (counter) + bool m_f_playing = false; // valid mp3 stream recognized + bool m_f_webfile = false; // assume it's a radiostream, not a podcast + bool m_f_tts = false; // text to speech + bool m_f_psram = false; // set if PSRAM is availabe + bool m_f_loop = false; // Set if audio file should loop + bool m_f_forceMono = false; // if true stereo -> mono + bool m_f_internalDAC = false; // false: output vis I2S, true output via internal DAC + bool m_f_rtsp = false; // set if RTSP is used (m3u8 stream) + bool m_f_m3u8data = false; // used in processM3U8entries + bool m_f_Log = true; // if m3u8: log is cancelled + bool m_f_continue = false; // next m3u8 chunk is available + bool m_f_initInbuffOnce = false; // init InBuff only once + i2s_dac_mode_t m_f_channelEnabled = I2S_DAC_CHANNEL_LEFT_EN; // internal DAC on GPIO26 for M5StickC/Plus + uint32_t m_audioFileDuration = 0; + float m_audioCurrentTime = 0; + uint32_t m_audioDataStart = 0; // in bytes + size_t m_audioDataSize = 0; // + float m_filterBuff[3][2][2][2]; // IIR filters memory for Audio DSP + size_t m_i2s_bytesWritten = 0; // set in i2s_write() but not used + size_t m_file_size = 0; // size of the file + uint16_t m_filterFrequency[2]; + int8_t m_gain0 = 0; // cut or boost filters (EQ) + int8_t m_gain1 = 0; + int8_t m_gain2 = 0; +}; + +//---------------------------------------------------------------------------------------------------------------------- diff --git a/yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp new file mode 100644 index 0000000..55fddd6 --- /dev/null +++ b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.cpp @@ -0,0 +1,10224 @@ +/* + * aac_decoder.cpp + * libhelix_HAACDECODER + * + * Created on: 26.10.2018 + * Updated on: 10.09.2021 + ************************************************************************************/ + +#include "aac_decoder.h" + +const uint32_t SQRTHALF = 0x5a82799a; /* sqrt(0.5), format = Q31 */ +const uint32_t Q28_2 = 0x20000000; /* Q28: 2.0 */ +const uint32_t Q28_15 = 0x30000000; /* Q28: 1.5 */ +const uint8_t NUM_ITER_IRN = 5; +const uint8_t NUM_TERMS_RPI = 5; +const uint32_t LOG2_EXP_INV = 0x58b90bfc; /* 1/log2(e), Q31 */ +const uint8_t SF_OFFSET = 100; +const uint8_t AAC_PROFILE_LC = 1; +const uint8_t NUM_TIME_SLOTS = 16; +const uint8_t SAMPLES_PER_SLOT = 2; /* RATE in spec */ +const uint8_t SYNCWORDH = 0xff; /* 12-bit syncword */ +const uint8_t SYNCWORDL = 0xf0; +const uint8_t NUM_SAMPLE_RATES = 12; +const uint8_t NUM_DEF_CHAN_MAPS = 8; +const uint32_t NSAMPS_LONG = 1024; +const uint8_t NSAMPS_SHORT = 128; +const uint8_t NUM_SYN_ID_BITS = 3; +const uint8_t NUM_INST_TAG_BITS = 4; +const uint8_t NWINDOWS_LONG = 1; +const uint8_t NWINDOWS_SHORT = 8; +const uint8_t AAC_MAX_NCHANS = 2; /* set to default max number of channels */ +const uint16_t AAC_MAX_NSAMPS = 1024; +const uint8_t MAX_NCHANS_ELEM = 2; /* max number of channels in any single bitstream element */ +const uint8_t MAX_NUM_PCE_ADIF = 16; +const uint8_t ADIF_COPYID_SIZE = 9; +const uint8_t HUFFTAB_SPEC_OFFSET = 1; +const uint8_t FBITS_OUT_DQ_OFF = 20 - 15; /* (FBITS_OUT_DQ - SF_DQ_OFFSET) */ +const uint8_t GBITS_IN_DCT4 = 4; /* min guard bits in for DCT4 */ +const uint8_t FBITS_LOST_DCT4 = 1; /* number of fraction bits lost (>> out) in DCT-IV */ +const uint8_t FBITS_OUT_IMDCT = 3; +const uint8_t NUM_IMDCT_SIZES = 2; +const uint8_t FBITS_LPC_COEFS = 20; +const uint8_t NUM_ITER_INVSQRT = 4; +const uint32_t X0_COEF_2 = 0xc0000000; /* Q29: -2.0 */ +const uint32_t X0_OFF_2 = 0x60000000; /* Q29: 3.0 */ +const uint32_t Q26_3 = 0x0c000000; /* Q26: 3.0 */ +const uint8_t EXT_SBR_DATA = 0x0d; +const uint8_t EXT_SBR_DATA_CRC = 0x0e; +const uint8_t NUM_SAMPLE_RATES_SBR = 9; /* downsampled (single-rate) mode unsupported */ +const uint8_t MAX_NUM_PATCHES = 5; +const uint8_t MAX_QMF_BANDS = 48; /* max QMF subbands covered by SBR (4.6.18.3.6) */ +const uint8_t MAX_NUM_ENV = 5; +const uint8_t NUM_QMF_DELAY_BUFS = 10; +const uint8_t FBITS_IN_QMFA = 14; +const uint8_t NOISE_FLOOR_OFFSET = 6; +const uint8_t FBITS_OUT_DQ_NOISE = 24; /* range of Q_orig = [2^-24, 2^6] */ +const uint8_t FBITS_LOST_QMFA = (1 + 2 + 3 + 2 + 1); +const uint8_t FBITS_OUT_QMFA = (FBITS_IN_QMFA - FBITS_LOST_QMFA); +const uint8_t FBITS_IN_QMFS = FBITS_OUT_QMFA; +const uint8_t FBITS_LOST_DCT4_64 = (2 + 3 + 2); /* 2 in premul, 3 in FFT, 2 in postmul */ +const uint8_t FBITS_OUT_QMFS = (FBITS_IN_QMFS - FBITS_LOST_DCT4_64 + 6 - 1); +const uint8_t RND_VAL = (1 << (FBITS_OUT_QMFS-1)); +const uint8_t HF_ADJ = 2; +const uint8_t HF_GEN = 8; +const uint8_t FBITS_LPCOEFS = 29; /* Q29 for range of (-4, 4) */ +const uint32_t MAG_16 = (16 * (1 << (32 - (2*(32-FBITS_LPCOEFS))))); /* i.e. 16 in Q26 format */ +const uint32_t RELAX_COEF = 0x7ffff79c; /* 1.0 / (1.0 + 1e-6), Q31 */ +const uint8_t MAX_NUM_SMOOTH_COEFS = 5; +const uint8_t FBITS_OUT_DQ_ENV = 29; /* dequantized env scalefactors are Q(29 - envDataDequantScale) */ +const uint8_t FBITS_GLIM_BOOST = 24; +const uint8_t FBITS_QLIM_BOOST = 14; +const uint8_t MIN_GBITS_IN_QMFS = 2; +const uint16_t nmdctTab[2] = {128, 1024}; +const uint8_t postSkip[2] = {15, 1}; +const uint16_t nfftTab[2] = {64, 512}; +const uint8_t nfftlog2Tab[2] = {6, 9}; +const uint8_t cos4sin4tabOffset[2] = {0, 128}; + +PSInfoBase_t *m_PSInfoBase; +AACDecInfo_t *m_AACDecInfo; +AACFrameInfo_t m_AACFrameInfo; +ADTSHeader_t m_fhADTS; +ADIFHeader_t m_fhADIF; +ProgConfigElement_t *m_pce[16]; +PulseInfo_t m_pulseInfo[2]; // [MAX_NCHANS_ELEM] +aac_BitStreamInfo_t m_aac_BitStreamInfo; +PSInfoSBR_t *m_PSInfoSBR; + +//---------------------------------------------------------------------------------------------------------------------- +inline int MULSHIFT32(int x, int y){ + int z; z = (int64_t)x * (int64_t)y >> 32; + return z; +} +inline int CLZ(int x){ +#ifdef __XTENSA__ + return __builtin_clz(x); +#else + int numZeros; + if(!x) return 32; /* count leading zeros with binary search (function should be 17 ARM instructions total) */ + numZeros = 1; + if (!((unsigned int)x >> 16)) { numZeros += 16; x <<= 16; } + if (!((unsigned int)x >> 24)) { numZeros += 8; x <<= 8; } + if (!((unsigned int)x >> 28)) { numZeros += 4; x <<= 4; } + if (!((unsigned int)x >> 30)) { numZeros += 2; x <<= 2; } + numZeros -= ((unsigned int)x >> 31); + return numZeros; +#endif +} +inline int FASTABS(int x){ +#ifdef __XTENSA__ //fb + return __builtin_abs(x); +#else + int sign; + sign = x >> (sizeof(int) * 8 - 1); + x ^= sign; x -= sign; return x; +#endif +} +inline int64_t MADD64(int64_t sum64, int x, int y){ + sum64 += (int64_t)x * (int64_t)y; + return sum64; +} +inline short CLIPTOSHORT(int x){ +#ifdef __XTENSA__ //fb + asm ("clamps %0, %1, 15" : "=a" (x) : "a" (x) : ); + return x; +#else + int sign; /* clip to [-32768, 32767] */ + sign = x >> 31; + if (sign != (x >> 15)) x = sign ^ ((1 << 15) - 1); + return (short)x; +#endif +} +inline int CLIP_2N(int y, int n){ +#ifdef __XTENSA__ //fb + int x = 1 << n; \ + if (y < -x) y = -x; \ + x--; \ + if (y > x) y = x; \ + return y; +#else + int sign = y >> 31; + if(sign != (y >> n)) + y = sign ^ ((1 << n) - 1); + return y; +#endif +} +inline int CLIP_2N_SHIFT30(int y, int n){ + int sign = y >> 31; + if(sign != (y >> (30 - n))) + y = sign ^ (0x3fffffff); + else + y = (y << n); + return y; +} +//---------------------------------------------------------------------------------------------------------------------- + +const uint32_t cos4sin4tab[128 + 1024] PROGMEM = { +/* 128 - format = Q30 * 2^-7 */ +0xbf9bc731, 0xff9b783c, 0xbed5332c, 0xc002c697, 0xbe112251, 0xfe096c8d, 0xbd4f9c30, 0xc00f1c4a, +0xbc90a83f, 0xfc77ae5e, 0xbbd44dd9, 0xc0254e27, 0xbb1a9443, 0xfae67ba2, 0xba6382a6, 0xc04558c0, +0xb9af200f, 0xf9561237, 0xb8fd7373, 0xc06f3726, 0xb84e83ac, 0xf7c6afdc, 0xb7a25779, 0xc0a2e2e3, +0xb6f8f57c, 0xf6389228, 0xb652643e, 0xc0e05401, 0xb5aeaa2a, 0xf4abf67e, 0xb50dcd90, 0xc1278104, +0xb46fd4a4, 0xf3211a07, 0xb3d4c57c, 0xc1785ef4, 0xb33ca614, 0xf19839a6, 0xb2a77c49, 0xc1d2e158, +0xb2154dda, 0xf01191f3, 0xb186206b, 0xc236fa3b, 0xb0f9f981, 0xee8d5f29, 0xb070de82, 0xc2a49a2e, +0xafead4b9, 0xed0bdd25, 0xaf67e14f, 0xc31bb049, 0xaee80952, 0xeb8d475b, 0xae6b51ae, 0xc39c2a2f, +0xadf1bf34, 0xea11d8c8, 0xad7b5692, 0xc425f410, 0xad081c5a, 0xe899cbf1, 0xac9814fd, 0xc4b8f8ad, +0xac2b44cc, 0xe7255ad1, 0xabc1aff9, 0xc555215a, 0xab5b5a96, 0xe5b4bed8, 0xaaf84896, 0xc5fa5603, +0xaa987dca, 0xe44830dd, 0xaa3bfde3, 0xc6a87d2d, 0xa9e2cc73, 0xe2dfe917, 0xa98cece9, 0xc75f7bfe, +0xa93a6296, 0xe17c1f15, 0xa8eb30a7, 0xc81f363d, 0xa89f5a2b, 0xe01d09b4, 0xa856e20e, 0xc8e78e5b, +0xa811cb1b, 0xdec2df18, 0xa7d017fc, 0xc9b86572, 0xa791cb39, 0xdd6dd4a2, 0xa756e73a, 0xca919b4e, +0xa71f6e43, 0xdc1e1ee9, 0xa6eb6279, 0xcb730e70, 0xa6bac5dc, 0xdad3f1b1, 0xa68d9a4c, 0xcc5c9c14, +0xa663e188, 0xd98f7fe6, 0xa63d9d2b, 0xcd4e2037, 0xa61aceaf, 0xd850fb8e, 0xa5fb776b, 0xce47759a, +0xa5df9894, 0xd71895c9, 0xa5c7333e, 0xcf4875ca, 0xa5b2485a, 0xd5e67ec1, 0xa5a0d8b5, 0xd050f926, +0xa592e4fd, 0xd4bae5ab, 0xa5886dba, 0xd160d6e5, 0xa5817354, 0xd395f8ba, 0xa57df60f, 0xd277e518, +/* 1024 - format = Q30 * 2^-10 */ +0xbff3703e, 0xfff36f02, 0xbfda5824, 0xc0000b1a, 0xbfc149ed, 0xffc12b16, 0xbfa845a0, 0xc0003c74, +0xbf8f4b3e, 0xff8ee750, 0xbf765acc, 0xc0009547, 0xbf5d744e, 0xff5ca3d0, 0xbf4497c8, 0xc0011594, +0xbf2bc53d, 0xff2a60b4, 0xbf12fcb2, 0xc001bd5c, 0xbefa3e2a, 0xfef81e1d, 0xbee189a8, 0xc0028c9c, +0xbec8df32, 0xfec5dc28, 0xbeb03eca, 0xc0038356, 0xbe97a875, 0xfe939af5, 0xbe7f1c36, 0xc004a188, +0xbe669a10, 0xfe615aa3, 0xbe4e2209, 0xc005e731, 0xbe35b423, 0xfe2f1b50, 0xbe1d5062, 0xc0075452, +0xbe04f6cb, 0xfdfcdd1d, 0xbdeca760, 0xc008e8e8, 0xbdd46225, 0xfdcaa027, 0xbdbc2720, 0xc00aa4f3, +0xbda3f652, 0xfd98648d, 0xbd8bcfbf, 0xc00c8872, 0xbd73b36d, 0xfd662a70, 0xbd5ba15d, 0xc00e9364, +0xbd439995, 0xfd33f1ed, 0xbd2b9c17, 0xc010c5c7, 0xbd13a8e7, 0xfd01bb24, 0xbcfbc00a, 0xc0131f9b, +0xbce3e182, 0xfccf8634, 0xbccc0d53, 0xc015a0dd, 0xbcb44382, 0xfc9d533b, 0xbc9c8411, 0xc018498c, +0xbc84cf05, 0xfc6b2259, 0xbc6d2461, 0xc01b19a7, 0xbc558428, 0xfc38f3ac, 0xbc3dee5f, 0xc01e112b, +0xbc266309, 0xfc06c754, 0xbc0ee22a, 0xc0213018, 0xbbf76bc4, 0xfbd49d70, 0xbbdfffdd, 0xc024766a, +0xbbc89e77, 0xfba2761e, 0xbbb14796, 0xc027e421, 0xbb99fb3e, 0xfb70517d, 0xbb82b972, 0xc02b7939, +0xbb6b8235, 0xfb3e2fac, 0xbb54558d, 0xc02f35b1, 0xbb3d337b, 0xfb0c10cb, 0xbb261c04, 0xc0331986, +0xbb0f0f2b, 0xfad9f4f8, 0xbaf80cf4, 0xc03724b6, 0xbae11561, 0xfaa7dc52, 0xbaca2878, 0xc03b573f, +0xbab3463b, 0xfa75c6f8, 0xba9c6eae, 0xc03fb11d, 0xba85a1d4, 0xfa43b508, 0xba6edfb1, 0xc044324f, +0xba582849, 0xfa11a6a3, 0xba417b9e, 0xc048dad1, 0xba2ad9b5, 0xf9df9be6, 0xba144291, 0xc04daaa1, +0xb9fdb635, 0xf9ad94f0, 0xb9e734a4, 0xc052a1bb, 0xb9d0bde4, 0xf97b91e1, 0xb9ba51f6, 0xc057c01d, +0xb9a3f0de, 0xf94992d7, 0xb98d9aa0, 0xc05d05c3, 0xb9774f3f, 0xf91797f0, 0xb9610ebe, 0xc06272aa, +0xb94ad922, 0xf8e5a14d, 0xb934ae6d, 0xc06806ce, 0xb91e8ea3, 0xf8b3af0c, 0xb90879c7, 0xc06dc22e, +0xb8f26fdc, 0xf881c14b, 0xb8dc70e7, 0xc073a4c3, 0xb8c67cea, 0xf84fd829, 0xb8b093ea, 0xc079ae8c, +0xb89ab5e8, 0xf81df3c5, 0xb884e2e9, 0xc07fdf85, 0xb86f1af0, 0xf7ec143e, 0xb8595e00, 0xc08637a9, +0xb843ac1d, 0xf7ba39b3, 0xb82e0549, 0xc08cb6f5, 0xb818698a, 0xf7886442, 0xb802d8e0, 0xc0935d64, +0xb7ed5351, 0xf756940a, 0xb7d7d8df, 0xc09a2af3, 0xb7c2698e, 0xf724c92a, 0xb7ad0561, 0xc0a11f9d, +0xb797ac5b, 0xf6f303c0, 0xb7825e80, 0xc0a83b5e, 0xb76d1bd2, 0xf6c143ec, 0xb757e455, 0xc0af7e33, +0xb742b80d, 0xf68f89cb, 0xb72d96fd, 0xc0b6e815, 0xb7188127, 0xf65dd57d, 0xb7037690, 0xc0be7901, +0xb6ee773a, 0xf62c2721, 0xb6d98328, 0xc0c630f2, 0xb6c49a5e, 0xf5fa7ed4, 0xb6afbce0, 0xc0ce0fe3, +0xb69aeab0, 0xf5c8dcb6, 0xb68623d1, 0xc0d615cf, 0xb6716847, 0xf59740e5, 0xb65cb815, 0xc0de42b2, +0xb648133e, 0xf565ab80, 0xb63379c5, 0xc0e69686, 0xb61eebae, 0xf5341ca5, 0xb60a68fb, 0xc0ef1147, +0xb5f5f1b1, 0xf5029473, 0xb5e185d1, 0xc0f7b2ee, 0xb5cd255f, 0xf4d11308, 0xb5b8d05f, 0xc1007b77, +0xb5a486d2, 0xf49f9884, 0xb59048be, 0xc1096add, 0xb57c1624, 0xf46e2504, 0xb567ef08, 0xc1128119, +0xb553d36c, 0xf43cb8a7, 0xb53fc355, 0xc11bbe26, 0xb52bbec4, 0xf40b538b, 0xb517c5be, 0xc12521ff, +0xb503d845, 0xf3d9f5cf, 0xb4eff65c, 0xc12eac9d, 0xb4dc2007, 0xf3a89f92, 0xb4c85548, 0xc1385dfb, +0xb4b49622, 0xf37750f2, 0xb4a0e299, 0xc1423613, 0xb48d3ab0, 0xf3460a0d, 0xb4799e69, 0xc14c34df, +0xb4660dc8, 0xf314cb02, 0xb45288cf, 0xc1565a58, 0xb43f0f82, 0xf2e393ef, 0xb42ba1e4, 0xc160a678, +0xb4183ff7, 0xf2b264f2, 0xb404e9bf, 0xc16b193a, 0xb3f19f3e, 0xf2813e2a, 0xb3de6078, 0xc175b296, +0xb3cb2d70, 0xf2501fb5, 0xb3b80628, 0xc1807285, 0xb3a4eaa4, 0xf21f09b1, 0xb391dae6, 0xc18b5903, +0xb37ed6f1, 0xf1edfc3d, 0xb36bdec9, 0xc1966606, 0xb358f26f, 0xf1bcf777, 0xb34611e8, 0xc1a1998a, +0xb3333d36, 0xf18bfb7d, 0xb320745c, 0xc1acf386, 0xb30db75d, 0xf15b086d, 0xb2fb063b, 0xc1b873f5, +0xb2e860fa, 0xf12a1e66, 0xb2d5c79d, 0xc1c41ace, 0xb2c33a26, 0xf0f93d86, 0xb2b0b898, 0xc1cfe80a, +0xb29e42f6, 0xf0c865ea, 0xb28bd943, 0xc1dbdba3, 0xb2797b82, 0xf09797b2, 0xb26729b5, 0xc1e7f591, +0xb254e3e0, 0xf066d2fa, 0xb242aa05, 0xc1f435cc, 0xb2307c27, 0xf03617e2, 0xb21e5a49, 0xc2009c4e, +0xb20c446d, 0xf0056687, 0xb1fa3a97, 0xc20d290d, 0xb1e83cc9, 0xefd4bf08, 0xb1d64b06, 0xc219dc03, +0xb1c46551, 0xefa42181, 0xb1b28bad, 0xc226b528, 0xb1a0be1b, 0xef738e12, 0xb18efca0, 0xc233b473, +0xb17d473d, 0xef4304d8, 0xb16b9df6, 0xc240d9de, 0xb15a00cd, 0xef1285f2, 0xb1486fc5, 0xc24e255e, +0xb136eae1, 0xeee2117c, 0xb1257223, 0xc25b96ee, 0xb114058e, 0xeeb1a796, 0xb102a524, 0xc2692e83, +0xb0f150e9, 0xee81485c, 0xb0e008e0, 0xc276ec16, 0xb0cecd09, 0xee50f3ed, 0xb0bd9d6a, 0xc284cf9f, +0xb0ac7a03, 0xee20aa67, 0xb09b62d8, 0xc292d914, 0xb08a57eb, 0xedf06be6, 0xb079593f, 0xc2a1086d, +0xb06866d7, 0xedc0388a, 0xb05780b5, 0xc2af5da2, 0xb046a6db, 0xed901070, 0xb035d94e, 0xc2bdd8a9, +0xb025180e, 0xed5ff3b5, 0xb014631e, 0xc2cc7979, 0xb003ba82, 0xed2fe277, 0xaff31e3b, 0xc2db400a, +0xafe28e4d, 0xecffdcd4, 0xafd20ab9, 0xc2ea2c53, 0xafc19383, 0xeccfe2ea, 0xafb128ad, 0xc2f93e4a, +0xafa0ca39, 0xec9ff4d6, 0xaf90782a, 0xc30875e5, 0xaf803283, 0xec7012b5, 0xaf6ff945, 0xc317d31c, +0xaf5fcc74, 0xec403ca5, 0xaf4fac12, 0xc32755e5, 0xaf3f9822, 0xec1072c4, 0xaf2f90a5, 0xc336fe37, +0xaf1f959f, 0xebe0b52f, 0xaf0fa712, 0xc346cc07, 0xaeffc500, 0xebb10404, 0xaeefef6c, 0xc356bf4d, +0xaee02658, 0xeb815f60, 0xaed069c7, 0xc366d7fd, 0xaec0b9bb, 0xeb51c760, 0xaeb11636, 0xc377160f, +0xaea17f3b, 0xeb223c22, 0xae91f4cd, 0xc3877978, 0xae8276ed, 0xeaf2bdc3, 0xae73059f, 0xc398022f, +0xae63a0e3, 0xeac34c60, 0xae5448be, 0xc3a8b028, 0xae44fd31, 0xea93e817, 0xae35be3f, 0xc3b9835a, +0xae268be9, 0xea649105, 0xae176633, 0xc3ca7bba, 0xae084d1f, 0xea354746, 0xadf940ae, 0xc3db993e, +0xadea40e4, 0xea060af9, 0xaddb4dc2, 0xc3ecdbdc, 0xadcc674b, 0xe9d6dc3b, 0xadbd8d82, 0xc3fe4388, +0xadaec067, 0xe9a7bb28, 0xad9fffff, 0xc40fd037, 0xad914c4b, 0xe978a7dd, 0xad82a54c, 0xc42181e0, +0xad740b07, 0xe949a278, 0xad657d7c, 0xc4335877, 0xad56fcaf, 0xe91aab16, 0xad4888a0, 0xc44553f2, +0xad3a2153, 0xe8ebc1d3, 0xad2bc6ca, 0xc4577444, 0xad1d7907, 0xe8bce6cd, 0xad0f380c, 0xc469b963, +0xad0103db, 0xe88e1a20, 0xacf2dc77, 0xc47c2344, 0xace4c1e2, 0xe85f5be9, 0xacd6b41e, 0xc48eb1db, +0xacc8b32c, 0xe830ac45, 0xacbabf10, 0xc4a1651c, 0xacacd7cb, 0xe8020b52, 0xac9efd60, 0xc4b43cfd, +0xac912fd1, 0xe7d3792b, 0xac836f1f, 0xc4c73972, 0xac75bb4d, 0xe7a4f5ed, 0xac68145d, 0xc4da5a6f, +0xac5a7a52, 0xe77681b6, 0xac4ced2c, 0xc4ed9fe7, 0xac3f6cef, 0xe7481ca1, 0xac31f99d, 0xc50109d0, +0xac249336, 0xe719c6cb, 0xac1739bf, 0xc514981d, 0xac09ed38, 0xe6eb8052, 0xabfcada3, 0xc5284ac3, +0xabef7b04, 0xe6bd4951, 0xabe2555b, 0xc53c21b4, 0xabd53caa, 0xe68f21e5, 0xabc830f5, 0xc5501ce5, +0xabbb323c, 0xe6610a2a, 0xabae4082, 0xc5643c4a, 0xaba15bc9, 0xe633023e, 0xab948413, 0xc5787fd6, +0xab87b962, 0xe6050a3b, 0xab7afbb7, 0xc58ce77c, 0xab6e4b15, 0xe5d72240, 0xab61a77d, 0xc5a17330, +0xab5510f3, 0xe5a94a67, 0xab488776, 0xc5b622e6, 0xab3c0b0b, 0xe57b82cd, 0xab2f9bb1, 0xc5caf690, +0xab23396c, 0xe54dcb8f, 0xab16e43d, 0xc5dfee22, 0xab0a9c27, 0xe52024c9, 0xaafe612a, 0xc5f5098f, +0xaaf23349, 0xe4f28e96, 0xaae61286, 0xc60a48c9, 0xaad9fee3, 0xe4c50914, 0xaacdf861, 0xc61fabc4, +0xaac1ff03, 0xe497945d, 0xaab612ca, 0xc6353273, 0xaaaa33b8, 0xe46a308f, 0xaa9e61cf, 0xc64adcc7, +0xaa929d10, 0xe43cddc4, 0xaa86e57e, 0xc660aab5, 0xaa7b3b1b, 0xe40f9c1a, 0xaa6f9de7, 0xc6769c2e, +0xaa640de6, 0xe3e26bac, 0xaa588b18, 0xc68cb124, 0xaa4d157f, 0xe3b54c95, 0xaa41ad1e, 0xc6a2e98b, +0xaa3651f6, 0xe3883ef2, 0xaa2b0409, 0xc6b94554, 0xaa1fc358, 0xe35b42df, 0xaa148fe6, 0xc6cfc472, +0xaa0969b3, 0xe32e5876, 0xa9fe50c2, 0xc6e666d7, 0xa9f34515, 0xe3017fd5, 0xa9e846ad, 0xc6fd2c75, +0xa9dd558b, 0xe2d4b916, 0xa9d271b2, 0xc714153e, 0xa9c79b23, 0xe2a80456, 0xa9bcd1e0, 0xc72b2123, +0xa9b215ea, 0xe27b61af, 0xa9a76744, 0xc7425016, 0xa99cc5ee, 0xe24ed13d, 0xa99231eb, 0xc759a20a, +0xa987ab3c, 0xe222531c, 0xa97d31e3, 0xc77116f0, 0xa972c5e1, 0xe1f5e768, 0xa9686738, 0xc788aeb9, +0xa95e15e9, 0xe1c98e3b, 0xa953d1f7, 0xc7a06957, 0xa9499b62, 0xe19d47b1, 0xa93f722c, 0xc7b846ba, +0xa9355658, 0xe17113e5, 0xa92b47e5, 0xc7d046d6, 0xa92146d7, 0xe144f2f3, 0xa917532e, 0xc7e8699a, +0xa90d6cec, 0xe118e4f6, 0xa9039413, 0xc800aef7, 0xa8f9c8a4, 0xe0ecea09, 0xa8f00aa0, 0xc81916df, +0xa8e65a0a, 0xe0c10247, 0xa8dcb6e2, 0xc831a143, 0xa8d3212a, 0xe0952dcb, 0xa8c998e3, 0xc84a4e14, +0xa8c01e10, 0xe0696cb0, 0xa8b6b0b1, 0xc8631d42, 0xa8ad50c8, 0xe03dbf11, 0xa8a3fe57, 0xc87c0ebd, +0xa89ab95e, 0xe012250a, 0xa89181df, 0xc8952278, 0xa88857dc, 0xdfe69eb4, 0xa87f3b57, 0xc8ae5862, +0xa8762c4f, 0xdfbb2c2c, 0xa86d2ac8, 0xc8c7b06b, 0xa86436c2, 0xdf8fcd8b, 0xa85b503e, 0xc8e12a84, +0xa852773f, 0xdf6482ed, 0xa849abc4, 0xc8fac69e, 0xa840edd1, 0xdf394c6b, 0xa8383d66, 0xc91484a8, +0xa82f9a84, 0xdf0e2a22, 0xa827052d, 0xc92e6492, 0xa81e7d62, 0xdee31c2b, 0xa8160324, 0xc948664d, +0xa80d9675, 0xdeb822a1, 0xa8053756, 0xc96289c9, 0xa7fce5c9, 0xde8d3d9e, 0xa7f4a1ce, 0xc97ccef5, +0xa7ec6b66, 0xde626d3e, 0xa7e44294, 0xc99735c2, 0xa7dc2759, 0xde37b199, 0xa7d419b4, 0xc9b1be1e, +0xa7cc19a9, 0xde0d0acc, 0xa7c42738, 0xc9cc67fa, 0xa7bc4262, 0xdde278ef, 0xa7b46b29, 0xc9e73346, +0xa7aca18e, 0xddb7fc1e, 0xa7a4e591, 0xca021fef, 0xa79d3735, 0xdd8d9472, 0xa795967a, 0xca1d2de7, +0xa78e0361, 0xdd634206, 0xa7867dec, 0xca385d1d, 0xa77f061c, 0xdd3904f4, 0xa7779bf2, 0xca53ad7e, +0xa7703f70, 0xdd0edd55, 0xa768f095, 0xca6f1efc, 0xa761af64, 0xdce4cb44, 0xa75a7bdd, 0xca8ab184, +0xa7535602, 0xdcbacedb, 0xa74c3dd4, 0xcaa66506, 0xa7453353, 0xdc90e834, 0xa73e3681, 0xcac23971, +0xa7374760, 0xdc671768, 0xa73065ef, 0xcade2eb3, 0xa7299231, 0xdc3d5c91, 0xa722cc25, 0xcafa44bc, +0xa71c13ce, 0xdc13b7c9, 0xa715692c, 0xcb167b79, 0xa70ecc41, 0xdbea292b, 0xa7083d0d, 0xcb32d2da, +0xa701bb91, 0xdbc0b0ce, 0xa6fb47ce, 0xcb4f4acd, 0xa6f4e1c6, 0xdb974ece, 0xa6ee8979, 0xcb6be341, +0xa6e83ee8, 0xdb6e0342, 0xa6e20214, 0xcb889c23, 0xa6dbd2ff, 0xdb44ce46, 0xa6d5b1a9, 0xcba57563, +0xa6cf9e13, 0xdb1baff2, 0xa6c9983e, 0xcbc26eee, 0xa6c3a02b, 0xdaf2a860, 0xa6bdb5da, 0xcbdf88b3, +0xa6b7d94e, 0xdac9b7a9, 0xa6b20a86, 0xcbfcc29f, 0xa6ac4984, 0xdaa0dde7, 0xa6a69649, 0xcc1a1ca0, +0xa6a0f0d5, 0xda781b31, 0xa69b5929, 0xcc3796a5, 0xa695cf46, 0xda4f6fa3, 0xa690532d, 0xcc55309b, +0xa68ae4df, 0xda26db54, 0xa685845c, 0xcc72ea70, 0xa68031a6, 0xd9fe5e5e, 0xa67aecbd, 0xcc90c412, +0xa675b5a3, 0xd9d5f8d9, 0xa6708c57, 0xccaebd6e, 0xa66b70db, 0xd9adaadf, 0xa6666330, 0xccccd671, +0xa6616355, 0xd9857489, 0xa65c714d, 0xcceb0f0a, 0xa6578d18, 0xd95d55ef, 0xa652b6b6, 0xcd096725, +0xa64dee28, 0xd9354f2a, 0xa6493370, 0xcd27deb0, 0xa644868d, 0xd90d6053, 0xa63fe781, 0xcd467599, +0xa63b564c, 0xd8e58982, 0xa636d2ee, 0xcd652bcb, 0xa6325d6a, 0xd8bdcad0, 0xa62df5bf, 0xcd840134, +0xa6299bed, 0xd8962456, 0xa6254ff7, 0xcda2f5c2, 0xa62111db, 0xd86e962b, 0xa61ce19c, 0xcdc20960, +0xa618bf39, 0xd8472069, 0xa614aab3, 0xcde13bfd, 0xa610a40c, 0xd81fc328, 0xa60cab43, 0xce008d84, +0xa608c058, 0xd7f87e7f, 0xa604e34e, 0xce1ffde2, 0xa6011424, 0xd7d15288, 0xa5fd52db, 0xce3f8d05, +0xa5f99f73, 0xd7aa3f5a, 0xa5f5f9ed, 0xce5f3ad8, 0xa5f2624a, 0xd783450d, 0xa5eed88a, 0xce7f0748, +0xa5eb5cae, 0xd75c63ba, 0xa5e7eeb6, 0xce9ef241, 0xa5e48ea3, 0xd7359b78, 0xa5e13c75, 0xcebefbb0, +0xa5ddf82d, 0xd70eec60, 0xa5dac1cb, 0xcedf2380, 0xa5d79950, 0xd6e85689, 0xa5d47ebc, 0xceff699f, +0xa5d17210, 0xd6c1da0b, 0xa5ce734d, 0xcf1fcdf8, 0xa5cb8272, 0xd69b76fe, 0xa5c89f80, 0xcf405077, +0xa5c5ca77, 0xd6752d79, 0xa5c30359, 0xcf60f108, 0xa5c04a25, 0xd64efd94, 0xa5bd9edc, 0xcf81af97, +0xa5bb017f, 0xd628e767, 0xa5b8720d, 0xcfa28c10, 0xa5b5f087, 0xd602eb0a, 0xa5b37cee, 0xcfc3865e, +0xa5b11741, 0xd5dd0892, 0xa5aebf82, 0xcfe49e6d, 0xa5ac75b0, 0xd5b74019, 0xa5aa39cd, 0xd005d42a, +0xa5a80bd7, 0xd59191b5, 0xa5a5ebd0, 0xd027277e, 0xa5a3d9b8, 0xd56bfd7d, 0xa5a1d590, 0xd0489856, +0xa59fdf57, 0xd5468389, 0xa59df70e, 0xd06a269d, 0xa59c1cb5, 0xd52123f0, 0xa59a504c, 0xd08bd23f, +0xa59891d4, 0xd4fbdec9, 0xa596e14e, 0xd0ad9b26, 0xa5953eb8, 0xd4d6b42b, 0xa593aa14, 0xd0cf813e, +0xa5922362, 0xd4b1a42c, 0xa590aaa2, 0xd0f18472, 0xa58f3fd4, 0xd48caee4, 0xa58de2f8, 0xd113a4ad, +0xa58c940f, 0xd467d469, 0xa58b5319, 0xd135e1d9, 0xa58a2016, 0xd44314d3, 0xa588fb06, 0xd1583be2, +0xa587e3ea, 0xd41e7037, 0xa586dac1, 0xd17ab2b3, 0xa585df8c, 0xd3f9e6ad, 0xa584f24b, 0xd19d4636, +0xa58412fe, 0xd3d5784a, 0xa58341a5, 0xd1bff656, 0xa5827e40, 0xd3b12526, 0xa581c8d0, 0xd1e2c2fd, +0xa5812154, 0xd38ced57, 0xa58087cd, 0xd205ac17, 0xa57ffc3b, 0xd368d0f3, 0xa57f7e9d, 0xd228b18d, +0xa57f0ef5, 0xd344d011, 0xa57ead41, 0xd24bd34a, 0xa57e5982, 0xd320eac6, 0xa57e13b8, 0xd26f1138, +0xa57ddbe4, 0xd2fd2129, 0xa57db204, 0xd2926b41, 0xa57d961a, 0xd2d97350, 0xa57d8825, 0xd2b5e151, +}; + +const int cos1sin1tab[514] PROGMEM = { +/* format = Q30 */ +0x40000000, 0x00000000, 0x40323034, 0x003243f1, 0x406438cf, 0x006487c4, 0x409619b2, 0x0096cb58, +0x40c7d2bd, 0x00c90e90, 0x40f963d3, 0x00fb514b, 0x412accd4, 0x012d936c, 0x415c0da3, 0x015fd4d2, +0x418d2621, 0x0192155f, 0x41be162f, 0x01c454f5, 0x41eeddaf, 0x01f69373, 0x421f7c84, 0x0228d0bb, +0x424ff28f, 0x025b0caf, 0x42803fb2, 0x028d472e, 0x42b063d0, 0x02bf801a, 0x42e05ecb, 0x02f1b755, +0x43103085, 0x0323ecbe, 0x433fd8e1, 0x03562038, 0x436f57c1, 0x038851a2, 0x439ead09, 0x03ba80df, +0x43cdd89a, 0x03ecadcf, 0x43fcda59, 0x041ed854, 0x442bb227, 0x0451004d, 0x445a5fe8, 0x0483259d, +0x4488e37f, 0x04b54825, 0x44b73ccf, 0x04e767c5, 0x44e56bbd, 0x0519845e, 0x4513702a, 0x054b9dd3, +0x454149fc, 0x057db403, 0x456ef916, 0x05afc6d0, 0x459c7d5a, 0x05e1d61b, 0x45c9d6af, 0x0613e1c5, +0x45f704f7, 0x0645e9af, 0x46240816, 0x0677edbb, 0x4650dff1, 0x06a9edc9, 0x467d8c6d, 0x06dbe9bb, +0x46aa0d6d, 0x070de172, 0x46d662d6, 0x073fd4cf, 0x47028c8d, 0x0771c3b3, 0x472e8a76, 0x07a3adff, +0x475a5c77, 0x07d59396, 0x47860275, 0x08077457, 0x47b17c54, 0x08395024, 0x47dcc9f9, 0x086b26de, +0x4807eb4b, 0x089cf867, 0x4832e02d, 0x08cec4a0, 0x485da887, 0x09008b6a, 0x4888443d, 0x09324ca7, +0x48b2b335, 0x09640837, 0x48dcf556, 0x0995bdfd, 0x49070a84, 0x09c76dd8, 0x4930f2a6, 0x09f917ac, +0x495aada2, 0x0a2abb59, 0x49843b5f, 0x0a5c58c0, 0x49ad9bc2, 0x0a8defc3, 0x49d6ceb3, 0x0abf8043, +0x49ffd417, 0x0af10a22, 0x4a28abd6, 0x0b228d42, 0x4a5155d6, 0x0b540982, 0x4a79d1ff, 0x0b857ec7, +0x4aa22036, 0x0bb6ecef, 0x4aca4065, 0x0be853de, 0x4af23270, 0x0c19b374, 0x4b19f641, 0x0c4b0b94, +0x4b418bbe, 0x0c7c5c1e, 0x4b68f2cf, 0x0cada4f5, 0x4b902b5c, 0x0cdee5f9, 0x4bb7354d, 0x0d101f0e, +0x4bde1089, 0x0d415013, 0x4c04bcf8, 0x0d7278eb, 0x4c2b3a84, 0x0da39978, 0x4c518913, 0x0dd4b19a, +0x4c77a88e, 0x0e05c135, 0x4c9d98de, 0x0e36c82a, 0x4cc359ec, 0x0e67c65a, 0x4ce8eb9f, 0x0e98bba7, +0x4d0e4de2, 0x0ec9a7f3, 0x4d33809c, 0x0efa8b20, 0x4d5883b7, 0x0f2b650f, 0x4d7d571c, 0x0f5c35a3, +0x4da1fab5, 0x0f8cfcbe, 0x4dc66e6a, 0x0fbdba40, 0x4deab226, 0x0fee6e0d, 0x4e0ec5d1, 0x101f1807, +0x4e32a956, 0x104fb80e, 0x4e565c9f, 0x10804e06, 0x4e79df95, 0x10b0d9d0, 0x4e9d3222, 0x10e15b4e, +0x4ec05432, 0x1111d263, 0x4ee345ad, 0x11423ef0, 0x4f06067f, 0x1172a0d7, 0x4f289692, 0x11a2f7fc, +0x4f4af5d1, 0x11d3443f, 0x4f6d2427, 0x12038584, 0x4f8f217e, 0x1233bbac, 0x4fb0edc1, 0x1263e699, +0x4fd288dc, 0x1294062f, 0x4ff3f2bb, 0x12c41a4f, 0x50152b47, 0x12f422db, 0x5036326e, 0x13241fb6, +0x50570819, 0x135410c3, 0x5077ac37, 0x1383f5e3, 0x50981eb1, 0x13b3cefa, 0x50b85f74, 0x13e39be9, +0x50d86e6d, 0x14135c94, 0x50f84b87, 0x144310dd, 0x5117f6ae, 0x1472b8a5, 0x51376fd0, 0x14a253d1, +0x5156b6d9, 0x14d1e242, 0x5175cbb5, 0x150163dc, 0x5194ae52, 0x1530d881, 0x51b35e9b, 0x15604013, +0x51d1dc80, 0x158f9a76, 0x51f027eb, 0x15bee78c, 0x520e40cc, 0x15ee2738, 0x522c270f, 0x161d595d, +0x5249daa2, 0x164c7ddd, 0x52675b72, 0x167b949d, 0x5284a96e, 0x16aa9d7e, 0x52a1c482, 0x16d99864, +0x52beac9f, 0x17088531, 0x52db61b0, 0x173763c9, 0x52f7e3a6, 0x1766340f, 0x5314326d, 0x1794f5e6, +0x53304df6, 0x17c3a931, 0x534c362d, 0x17f24dd3, 0x5367eb03, 0x1820e3b0, 0x53836c66, 0x184f6aab, +0x539eba45, 0x187de2a7, 0x53b9d48f, 0x18ac4b87, 0x53d4bb34, 0x18daa52f, 0x53ef6e23, 0x1908ef82, +0x5409ed4b, 0x19372a64, 0x5424389d, 0x196555b8, 0x543e5007, 0x19937161, 0x5458337a, 0x19c17d44, +0x5471e2e6, 0x19ef7944, 0x548b5e3b, 0x1a1d6544, 0x54a4a56a, 0x1a4b4128, 0x54bdb862, 0x1a790cd4, +0x54d69714, 0x1aa6c82b, 0x54ef4171, 0x1ad47312, 0x5507b76a, 0x1b020d6c, 0x551ff8ef, 0x1b2f971e, +0x553805f2, 0x1b5d100a, 0x554fde64, 0x1b8a7815, 0x55678236, 0x1bb7cf23, 0x557ef15a, 0x1be51518, +0x55962bc0, 0x1c1249d8, 0x55ad315b, 0x1c3f6d47, 0x55c4021d, 0x1c6c7f4a, 0x55da9df7, 0x1c997fc4, +0x55f104dc, 0x1cc66e99, 0x560736bd, 0x1cf34baf, 0x561d338d, 0x1d2016e9, 0x5632fb3f, 0x1d4cd02c, +0x56488dc5, 0x1d79775c, 0x565deb11, 0x1da60c5d, 0x56731317, 0x1dd28f15, 0x568805c9, 0x1dfeff67, +0x569cc31b, 0x1e2b5d38, 0x56b14b00, 0x1e57a86d, 0x56c59d6a, 0x1e83e0eb, 0x56d9ba4e, 0x1eb00696, +0x56eda1a0, 0x1edc1953, 0x57015352, 0x1f081907, 0x5714cf59, 0x1f340596, 0x572815a8, 0x1f5fdee6, +0x573b2635, 0x1f8ba4dc, 0x574e00f2, 0x1fb7575c, 0x5760a5d5, 0x1fe2f64c, 0x577314d2, 0x200e8190, +0x57854ddd, 0x2039f90f, 0x579750ec, 0x20655cac, 0x57a91df2, 0x2090ac4d, 0x57bab4e6, 0x20bbe7d8, +0x57cc15bc, 0x20e70f32, 0x57dd406a, 0x21122240, 0x57ee34e5, 0x213d20e8, 0x57fef323, 0x21680b0f, +0x580f7b19, 0x2192e09b, 0x581fccbc, 0x21bda171, 0x582fe804, 0x21e84d76, 0x583fcce6, 0x2212e492, +0x584f7b58, 0x223d66a8, 0x585ef351, 0x2267d3a0, 0x586e34c7, 0x22922b5e, 0x587d3fb0, 0x22bc6dca, +0x588c1404, 0x22e69ac8, 0x589ab1b9, 0x2310b23e, 0x58a918c6, 0x233ab414, 0x58b74923, 0x2364a02e, +0x58c542c5, 0x238e7673, 0x58d305a6, 0x23b836ca, 0x58e091bd, 0x23e1e117, 0x58ede700, 0x240b7543, +0x58fb0568, 0x2434f332, 0x5907eced, 0x245e5acc, 0x59149d87, 0x2487abf7, 0x5921172e, 0x24b0e699, +0x592d59da, 0x24da0a9a, 0x59396584, 0x250317df, 0x59453a24, 0x252c0e4f, 0x5950d7b3, 0x2554edd1, +0x595c3e2a, 0x257db64c, 0x59676d82, 0x25a667a7, 0x597265b4, 0x25cf01c8, 0x597d26b8, 0x25f78497, +0x5987b08a, 0x261feffa, 0x59920321, 0x264843d9, 0x599c1e78, 0x2670801a, 0x59a60288, 0x2698a4a6, +0x59afaf4c, 0x26c0b162, 0x59b924bc, 0x26e8a637, 0x59c262d5, 0x2710830c, 0x59cb698f, 0x273847c8, +0x59d438e5, 0x275ff452, 0x59dcd0d3, 0x27878893, 0x59e53151, 0x27af0472, 0x59ed5a5c, 0x27d667d5, +0x59f54bee, 0x27fdb2a7, 0x59fd0603, 0x2824e4cc, 0x5a048895, 0x284bfe2f, 0x5a0bd3a1, 0x2872feb6, +0x5a12e720, 0x2899e64a, 0x5a19c310, 0x28c0b4d2, 0x5a20676c, 0x28e76a37, 0x5a26d42f, 0x290e0661, +0x5a2d0957, 0x29348937, 0x5a3306de, 0x295af2a3, 0x5a38ccc2, 0x2981428c, 0x5a3e5afe, 0x29a778db, +0x5a43b190, 0x29cd9578, 0x5a48d074, 0x29f3984c, 0x5a4db7a6, 0x2a19813f, 0x5a526725, 0x2a3f503a, +0x5a56deec, 0x2a650525, 0x5a5b1efa, 0x2a8a9fea, 0x5a5f274b, 0x2ab02071, 0x5a62f7dd, 0x2ad586a3, +0x5a6690ae, 0x2afad269, 0x5a69f1bb, 0x2b2003ac, 0x5a6d1b03, 0x2b451a55, 0x5a700c84, 0x2b6a164d, +0x5a72c63b, 0x2b8ef77d, 0x5a754827, 0x2bb3bdce, 0x5a779246, 0x2bd8692b, 0x5a79a498, 0x2bfcf97c, +0x5a7b7f1a, 0x2c216eaa, 0x5a7d21cc, 0x2c45c8a0, 0x5a7e8cac, 0x2c6a0746, 0x5a7fbfbb, 0x2c8e2a87, +0x5a80baf6, 0x2cb2324c, 0x5a817e5d, 0x2cd61e7f, 0x5a8209f1, 0x2cf9ef09, 0x5a825db0, 0x2d1da3d5, +0x5a82799a, 0x2d413ccd, +}; + +const uint8_t sinWindowOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 128}; + +const int sinWindow[128 + 1024] PROGMEM = { +/* 128 - format = Q31 * 2^0 */ +0x00c90f88, 0x7fff6216, 0x025b26d7, 0x7ffa72d1, 0x03ed26e6, 0x7ff09478, 0x057f0035, 0x7fe1c76b, +0x0710a345, 0x7fce0c3e, 0x08a2009a, 0x7fb563b3, 0x0a3308bd, 0x7f97cebd, 0x0bc3ac35, 0x7f754e80, +0x0d53db92, 0x7f4de451, 0x0ee38766, 0x7f2191b4, 0x1072a048, 0x7ef05860, 0x120116d5, 0x7eba3a39, +0x138edbb1, 0x7e7f3957, 0x151bdf86, 0x7e3f57ff, 0x16a81305, 0x7dfa98a8, 0x183366e9, 0x7db0fdf8, +0x19bdcbf3, 0x7d628ac6, 0x1b4732ef, 0x7d0f4218, 0x1ccf8cb3, 0x7cb72724, 0x1e56ca1e, 0x7c5a3d50, +0x1fdcdc1b, 0x7bf88830, 0x2161b3a0, 0x7b920b89, 0x22e541af, 0x7b26cb4f, 0x24677758, 0x7ab6cba4, +0x25e845b6, 0x7a4210d8, 0x27679df4, 0x79c89f6e, 0x28e5714b, 0x794a7c12, 0x2a61b101, 0x78c7aba2, +0x2bdc4e6f, 0x78403329, 0x2d553afc, 0x77b417df, 0x2ecc681e, 0x77235f2d, 0x3041c761, 0x768e0ea6, +0x31b54a5e, 0x75f42c0b, 0x3326e2c3, 0x7555bd4c, 0x34968250, 0x74b2c884, 0x36041ad9, 0x740b53fb, +0x376f9e46, 0x735f6626, 0x38d8fe93, 0x72af05a7, 0x3a402dd2, 0x71fa3949, 0x3ba51e29, 0x71410805, +0x3d07c1d6, 0x708378ff, 0x3e680b2c, 0x6fc19385, 0x3fc5ec98, 0x6efb5f12, 0x4121589b, 0x6e30e34a, +0x427a41d0, 0x6d6227fa, 0x43d09aed, 0x6c8f351c, 0x452456bd, 0x6bb812d1, 0x46756828, 0x6adcc964, +0x47c3c22f, 0x69fd614a, 0x490f57ee, 0x6919e320, 0x4a581c9e, 0x683257ab, 0x4b9e0390, 0x6746c7d8, +0x4ce10034, 0x66573cbb, 0x4e210617, 0x6563bf92, 0x4f5e08e3, 0x646c59bf, 0x5097fc5e, 0x637114cc, +0x51ced46e, 0x6271fa69, 0x53028518, 0x616f146c, 0x5433027d, 0x60686ccf, 0x556040e2, 0x5f5e0db3, +0x568a34a9, 0x5e50015d, 0x57b0d256, 0x5d3e5237, 0x58d40e8c, 0x5c290acc, 0x59f3de12, 0x5b1035cf, +/* 1024 - format = Q31 * 2^0 */ +0x001921fb, 0x7ffffd88, 0x004b65ee, 0x7fffe9cb, 0x007da9d4, 0x7fffc251, 0x00afeda8, 0x7fff8719, +0x00e23160, 0x7fff3824, 0x011474f6, 0x7ffed572, 0x0146b860, 0x7ffe5f03, 0x0178fb99, 0x7ffdd4d7, +0x01ab3e97, 0x7ffd36ee, 0x01dd8154, 0x7ffc8549, 0x020fc3c6, 0x7ffbbfe6, 0x024205e8, 0x7ffae6c7, +0x027447b0, 0x7ff9f9ec, 0x02a68917, 0x7ff8f954, 0x02d8ca16, 0x7ff7e500, 0x030b0aa4, 0x7ff6bcf0, +0x033d4abb, 0x7ff58125, 0x036f8a51, 0x7ff4319d, 0x03a1c960, 0x7ff2ce5b, 0x03d407df, 0x7ff1575d, +0x040645c7, 0x7fefcca4, 0x04388310, 0x7fee2e30, 0x046abfb3, 0x7fec7c02, 0x049cfba7, 0x7feab61a, +0x04cf36e5, 0x7fe8dc78, 0x05017165, 0x7fe6ef1c, 0x0533ab20, 0x7fe4ee06, 0x0565e40d, 0x7fe2d938, +0x05981c26, 0x7fe0b0b1, 0x05ca5361, 0x7fde7471, 0x05fc89b8, 0x7fdc247a, 0x062ebf22, 0x7fd9c0ca, +0x0660f398, 0x7fd74964, 0x06932713, 0x7fd4be46, 0x06c5598a, 0x7fd21f72, 0x06f78af6, 0x7fcf6ce8, +0x0729bb4e, 0x7fcca6a7, 0x075bea8c, 0x7fc9ccb2, 0x078e18a7, 0x7fc6df08, 0x07c04598, 0x7fc3dda9, +0x07f27157, 0x7fc0c896, 0x08249bdd, 0x7fbd9fd0, 0x0856c520, 0x7fba6357, 0x0888ed1b, 0x7fb7132b, +0x08bb13c5, 0x7fb3af4e, 0x08ed3916, 0x7fb037bf, 0x091f5d06, 0x7facac7f, 0x09517f8f, 0x7fa90d8e, +0x0983a0a7, 0x7fa55aee, 0x09b5c048, 0x7fa1949e, 0x09e7de6a, 0x7f9dbaa0, 0x0a19fb04, 0x7f99ccf4, +0x0a4c1610, 0x7f95cb9a, 0x0a7e2f85, 0x7f91b694, 0x0ab0475c, 0x7f8d8de1, 0x0ae25d8d, 0x7f895182, +0x0b147211, 0x7f850179, 0x0b4684df, 0x7f809dc5, 0x0b7895f0, 0x7f7c2668, 0x0baaa53b, 0x7f779b62, +0x0bdcb2bb, 0x7f72fcb4, 0x0c0ebe66, 0x7f6e4a5e, 0x0c40c835, 0x7f698461, 0x0c72d020, 0x7f64aabf, +0x0ca4d620, 0x7f5fbd77, 0x0cd6da2d, 0x7f5abc8a, 0x0d08dc3f, 0x7f55a7fa, 0x0d3adc4e, 0x7f507fc7, +0x0d6cda53, 0x7f4b43f2, 0x0d9ed646, 0x7f45f47b, 0x0dd0d01f, 0x7f409164, 0x0e02c7d7, 0x7f3b1aad, +0x0e34bd66, 0x7f359057, 0x0e66b0c3, 0x7f2ff263, 0x0e98a1e9, 0x7f2a40d2, 0x0eca90ce, 0x7f247ba5, +0x0efc7d6b, 0x7f1ea2dc, 0x0f2e67b8, 0x7f18b679, 0x0f604faf, 0x7f12b67c, 0x0f923546, 0x7f0ca2e7, +0x0fc41876, 0x7f067bba, 0x0ff5f938, 0x7f0040f6, 0x1027d784, 0x7ef9f29d, 0x1059b352, 0x7ef390ae, +0x108b8c9b, 0x7eed1b2c, 0x10bd6356, 0x7ee69217, 0x10ef377d, 0x7edff570, 0x11210907, 0x7ed94538, +0x1152d7ed, 0x7ed28171, 0x1184a427, 0x7ecbaa1a, 0x11b66dad, 0x7ec4bf36, 0x11e83478, 0x7ebdc0c6, +0x1219f880, 0x7eb6aeca, 0x124bb9be, 0x7eaf8943, 0x127d7829, 0x7ea85033, 0x12af33ba, 0x7ea1039b, +0x12e0ec6a, 0x7e99a37c, 0x1312a230, 0x7e922fd6, 0x13445505, 0x7e8aa8ac, 0x137604e2, 0x7e830dff, +0x13a7b1bf, 0x7e7b5fce, 0x13d95b93, 0x7e739e1d, 0x140b0258, 0x7e6bc8eb, 0x143ca605, 0x7e63e03b, +0x146e4694, 0x7e5be40c, 0x149fe3fc, 0x7e53d462, 0x14d17e36, 0x7e4bb13c, 0x1503153a, 0x7e437a9c, +0x1534a901, 0x7e3b3083, 0x15663982, 0x7e32d2f4, 0x1597c6b7, 0x7e2a61ed, 0x15c95097, 0x7e21dd73, +0x15fad71b, 0x7e194584, 0x162c5a3b, 0x7e109a24, 0x165dd9f0, 0x7e07db52, 0x168f5632, 0x7dff0911, +0x16c0cef9, 0x7df62362, 0x16f2443e, 0x7ded2a47, 0x1723b5f9, 0x7de41dc0, 0x17552422, 0x7ddafdce, +0x17868eb3, 0x7dd1ca75, 0x17b7f5a3, 0x7dc883b4, 0x17e958ea, 0x7dbf298d, 0x181ab881, 0x7db5bc02, +0x184c1461, 0x7dac3b15, 0x187d6c82, 0x7da2a6c6, 0x18aec0db, 0x7d98ff17, 0x18e01167, 0x7d8f4409, +0x19115e1c, 0x7d85759f, 0x1942a6f3, 0x7d7b93da, 0x1973ebe6, 0x7d719eba, 0x19a52ceb, 0x7d679642, +0x19d669fc, 0x7d5d7a74, 0x1a07a311, 0x7d534b50, 0x1a38d823, 0x7d4908d9, 0x1a6a0929, 0x7d3eb30f, +0x1a9b361d, 0x7d3449f5, 0x1acc5ef6, 0x7d29cd8c, 0x1afd83ad, 0x7d1f3dd6, 0x1b2ea43a, 0x7d149ad5, +0x1b5fc097, 0x7d09e489, 0x1b90d8bb, 0x7cff1af5, 0x1bc1ec9e, 0x7cf43e1a, 0x1bf2fc3a, 0x7ce94dfb, +0x1c240786, 0x7cde4a98, 0x1c550e7c, 0x7cd333f3, 0x1c861113, 0x7cc80a0f, 0x1cb70f43, 0x7cbcccec, +0x1ce80906, 0x7cb17c8d, 0x1d18fe54, 0x7ca618f3, 0x1d49ef26, 0x7c9aa221, 0x1d7adb73, 0x7c8f1817, +0x1dabc334, 0x7c837ad8, 0x1ddca662, 0x7c77ca65, 0x1e0d84f5, 0x7c6c06c0, 0x1e3e5ee5, 0x7c602fec, +0x1e6f342c, 0x7c5445e9, 0x1ea004c1, 0x7c4848ba, 0x1ed0d09d, 0x7c3c3860, 0x1f0197b8, 0x7c3014de, +0x1f325a0b, 0x7c23de35, 0x1f63178f, 0x7c179467, 0x1f93d03c, 0x7c0b3777, 0x1fc4840a, 0x7bfec765, +0x1ff532f2, 0x7bf24434, 0x2025dcec, 0x7be5ade6, 0x205681f1, 0x7bd9047c, 0x208721f9, 0x7bcc47fa, +0x20b7bcfe, 0x7bbf7860, 0x20e852f6, 0x7bb295b0, 0x2118e3dc, 0x7ba59fee, 0x21496fa7, 0x7b989719, +0x2179f64f, 0x7b8b7b36, 0x21aa77cf, 0x7b7e4c45, 0x21daf41d, 0x7b710a49, 0x220b6b32, 0x7b63b543, +0x223bdd08, 0x7b564d36, 0x226c4996, 0x7b48d225, 0x229cb0d5, 0x7b3b4410, 0x22cd12bd, 0x7b2da2fa, +0x22fd6f48, 0x7b1feee5, 0x232dc66d, 0x7b1227d3, 0x235e1826, 0x7b044dc7, 0x238e646a, 0x7af660c2, +0x23beab33, 0x7ae860c7, 0x23eeec78, 0x7ada4dd8, 0x241f2833, 0x7acc27f7, 0x244f5e5c, 0x7abdef25, +0x247f8eec, 0x7aafa367, 0x24afb9da, 0x7aa144bc, 0x24dfdf20, 0x7a92d329, 0x250ffeb7, 0x7a844eae, +0x25401896, 0x7a75b74f, 0x25702cb7, 0x7a670d0d, 0x25a03b11, 0x7a584feb, 0x25d0439f, 0x7a497feb, +0x26004657, 0x7a3a9d0f, 0x26304333, 0x7a2ba75a, 0x26603a2c, 0x7a1c9ece, 0x26902b39, 0x7a0d836d, +0x26c01655, 0x79fe5539, 0x26effb76, 0x79ef1436, 0x271fda96, 0x79dfc064, 0x274fb3ae, 0x79d059c8, +0x277f86b5, 0x79c0e062, 0x27af53a6, 0x79b15435, 0x27df1a77, 0x79a1b545, 0x280edb23, 0x79920392, +0x283e95a1, 0x79823f20, 0x286e49ea, 0x797267f2, 0x289df7f8, 0x79627e08, 0x28cd9fc1, 0x79528167, +0x28fd4140, 0x79427210, 0x292cdc6d, 0x79325006, 0x295c7140, 0x79221b4b, 0x298bffb2, 0x7911d3e2, +0x29bb87bc, 0x790179cd, 0x29eb0957, 0x78f10d0f, 0x2a1a847b, 0x78e08dab, 0x2a49f920, 0x78cffba3, +0x2a796740, 0x78bf56f9, 0x2aa8ced3, 0x78ae9fb0, 0x2ad82fd2, 0x789dd5cb, 0x2b078a36, 0x788cf94c, +0x2b36ddf7, 0x787c0a36, 0x2b662b0e, 0x786b088c, 0x2b957173, 0x7859f44f, 0x2bc4b120, 0x7848cd83, +0x2bf3ea0d, 0x7837942b, 0x2c231c33, 0x78264849, 0x2c52478a, 0x7814e9df, 0x2c816c0c, 0x780378f1, +0x2cb089b1, 0x77f1f581, 0x2cdfa071, 0x77e05f91, 0x2d0eb046, 0x77ceb725, 0x2d3db928, 0x77bcfc3f, +0x2d6cbb10, 0x77ab2ee2, 0x2d9bb5f6, 0x77994f11, 0x2dcaa9d5, 0x77875cce, 0x2df996a3, 0x7775581d, +0x2e287c5a, 0x776340ff, 0x2e575af3, 0x77511778, 0x2e863267, 0x773edb8b, 0x2eb502ae, 0x772c8d3a, +0x2ee3cbc1, 0x771a2c88, 0x2f128d99, 0x7707b979, 0x2f41482e, 0x76f5340e, 0x2f6ffb7a, 0x76e29c4b, +0x2f9ea775, 0x76cff232, 0x2fcd4c19, 0x76bd35c7, 0x2ffbe95d, 0x76aa670d, 0x302a7f3a, 0x76978605, +0x30590dab, 0x768492b4, 0x308794a6, 0x76718d1c, 0x30b61426, 0x765e7540, 0x30e48c22, 0x764b4b23, +0x3112fc95, 0x76380ec8, 0x31416576, 0x7624c031, 0x316fc6be, 0x76115f63, 0x319e2067, 0x75fdec60, +0x31cc7269, 0x75ea672a, 0x31fabcbd, 0x75d6cfc5, 0x3228ff5c, 0x75c32634, 0x32573a3f, 0x75af6a7b, +0x32856d5e, 0x759b9c9b, 0x32b398b3, 0x7587bc98, 0x32e1bc36, 0x7573ca75, 0x330fd7e1, 0x755fc635, +0x333debab, 0x754bafdc, 0x336bf78f, 0x7537876c, 0x3399fb85, 0x75234ce8, 0x33c7f785, 0x750f0054, +0x33f5eb89, 0x74faa1b3, 0x3423d78a, 0x74e63108, 0x3451bb81, 0x74d1ae55, 0x347f9766, 0x74bd199f, +0x34ad6b32, 0x74a872e8, 0x34db36df, 0x7493ba34, 0x3508fa66, 0x747eef85, 0x3536b5be, 0x746a12df, +0x356468e2, 0x74552446, 0x359213c9, 0x744023bc, 0x35bfb66e, 0x742b1144, 0x35ed50c9, 0x7415ece2, +0x361ae2d3, 0x7400b69a, 0x36486c86, 0x73eb6e6e, 0x3675edd9, 0x73d61461, 0x36a366c6, 0x73c0a878, +0x36d0d746, 0x73ab2ab4, 0x36fe3f52, 0x73959b1b, 0x372b9ee3, 0x737ff9ae, 0x3758f5f2, 0x736a4671, +0x37864477, 0x73548168, 0x37b38a6d, 0x733eaa96, 0x37e0c7cc, 0x7328c1ff, 0x380dfc8d, 0x7312c7a5, +0x383b28a9, 0x72fcbb8c, 0x38684c19, 0x72e69db7, 0x389566d6, 0x72d06e2b, 0x38c278d9, 0x72ba2cea, +0x38ef821c, 0x72a3d9f7, 0x391c8297, 0x728d7557, 0x39497a43, 0x7276ff0d, 0x39766919, 0x7260771b, +0x39a34f13, 0x7249dd86, 0x39d02c2a, 0x72333251, 0x39fd0056, 0x721c7580, 0x3a29cb91, 0x7205a716, +0x3a568dd4, 0x71eec716, 0x3a834717, 0x71d7d585, 0x3aaff755, 0x71c0d265, 0x3adc9e86, 0x71a9bdba, +0x3b093ca3, 0x71929789, 0x3b35d1a5, 0x717b5fd3, 0x3b625d86, 0x7164169d, 0x3b8ee03e, 0x714cbbeb, +0x3bbb59c7, 0x71354fc0, 0x3be7ca1a, 0x711dd220, 0x3c143130, 0x7106430e, 0x3c408f03, 0x70eea28e, +0x3c6ce38a, 0x70d6f0a4, 0x3c992ec0, 0x70bf2d53, 0x3cc5709e, 0x70a7589f, 0x3cf1a91c, 0x708f728b, +0x3d1dd835, 0x70777b1c, 0x3d49fde1, 0x705f7255, 0x3d761a19, 0x70475839, 0x3da22cd7, 0x702f2ccd, +0x3dce3614, 0x7016f014, 0x3dfa35c8, 0x6ffea212, 0x3e262bee, 0x6fe642ca, 0x3e52187f, 0x6fcdd241, +0x3e7dfb73, 0x6fb5507a, 0x3ea9d4c3, 0x6f9cbd79, 0x3ed5a46b, 0x6f841942, 0x3f016a61, 0x6f6b63d8, +0x3f2d26a0, 0x6f529d40, 0x3f58d921, 0x6f39c57d, 0x3f8481dd, 0x6f20dc92, 0x3fb020ce, 0x6f07e285, +0x3fdbb5ec, 0x6eeed758, 0x40074132, 0x6ed5bb10, 0x4032c297, 0x6ebc8db0, 0x405e3a16, 0x6ea34f3d, +0x4089a7a8, 0x6e89ffb9, 0x40b50b46, 0x6e709f2a, 0x40e064ea, 0x6e572d93, 0x410bb48c, 0x6e3daaf8, +0x4136fa27, 0x6e24175c, 0x416235b2, 0x6e0a72c5, 0x418d6729, 0x6df0bd35, 0x41b88e84, 0x6dd6f6b1, +0x41e3abbc, 0x6dbd1f3c, 0x420ebecb, 0x6da336dc, 0x4239c7aa, 0x6d893d93, 0x4264c653, 0x6d6f3365, +0x428fbabe, 0x6d551858, 0x42baa4e6, 0x6d3aec6e, 0x42e584c3, 0x6d20afac, 0x43105a50, 0x6d066215, +0x433b2585, 0x6cec03af, 0x4365e65b, 0x6cd1947c, 0x43909ccd, 0x6cb71482, 0x43bb48d4, 0x6c9c83c3, +0x43e5ea68, 0x6c81e245, 0x44108184, 0x6c67300b, 0x443b0e21, 0x6c4c6d1a, 0x44659039, 0x6c319975, +0x449007c4, 0x6c16b521, 0x44ba74bd, 0x6bfbc021, 0x44e4d71c, 0x6be0ba7b, 0x450f2edb, 0x6bc5a431, +0x45397bf4, 0x6baa7d49, 0x4563be60, 0x6b8f45c7, 0x458df619, 0x6b73fdae, 0x45b82318, 0x6b58a503, +0x45e24556, 0x6b3d3bcb, 0x460c5cce, 0x6b21c208, 0x46366978, 0x6b0637c1, 0x46606b4e, 0x6aea9cf8, +0x468a624a, 0x6acef1b2, 0x46b44e65, 0x6ab335f4, 0x46de2f99, 0x6a9769c1, 0x470805df, 0x6a7b8d1e, +0x4731d131, 0x6a5fa010, 0x475b9188, 0x6a43a29a, 0x478546de, 0x6a2794c1, 0x47aef12c, 0x6a0b7689, +0x47d8906d, 0x69ef47f6, 0x48022499, 0x69d3090e, 0x482badab, 0x69b6b9d3, 0x48552b9b, 0x699a5a4c, +0x487e9e64, 0x697dea7b, 0x48a805ff, 0x69616a65, 0x48d16265, 0x6944da10, 0x48fab391, 0x6928397e, +0x4923f97b, 0x690b88b5, 0x494d341e, 0x68eec7b9, 0x49766373, 0x68d1f68f, 0x499f8774, 0x68b5153a, +0x49c8a01b, 0x689823bf, 0x49f1ad61, 0x687b2224, 0x4a1aaf3f, 0x685e106c, 0x4a43a5b0, 0x6840ee9b, +0x4a6c90ad, 0x6823bcb7, 0x4a957030, 0x68067ac3, 0x4abe4433, 0x67e928c5, 0x4ae70caf, 0x67cbc6c0, +0x4b0fc99d, 0x67ae54ba, 0x4b387af9, 0x6790d2b6, 0x4b6120bb, 0x677340ba, 0x4b89badd, 0x67559eca, +0x4bb24958, 0x6737ecea, 0x4bdacc28, 0x671a2b20, 0x4c034345, 0x66fc596f, 0x4c2baea9, 0x66de77dc, +0x4c540e4e, 0x66c0866d, 0x4c7c622d, 0x66a28524, 0x4ca4aa41, 0x66847408, 0x4ccce684, 0x6666531d, +0x4cf516ee, 0x66482267, 0x4d1d3b7a, 0x6629e1ec, 0x4d455422, 0x660b91af, 0x4d6d60df, 0x65ed31b5, +0x4d9561ac, 0x65cec204, 0x4dbd5682, 0x65b0429f, 0x4de53f5a, 0x6591b38c, 0x4e0d1c30, 0x657314cf, +0x4e34ecfc, 0x6554666d, 0x4e5cb1b9, 0x6535a86b, 0x4e846a60, 0x6516dacd, 0x4eac16eb, 0x64f7fd98, +0x4ed3b755, 0x64d910d1, 0x4efb4b96, 0x64ba147d, 0x4f22d3aa, 0x649b08a0, 0x4f4a4f89, 0x647bed3f, +0x4f71bf2e, 0x645cc260, 0x4f992293, 0x643d8806, 0x4fc079b1, 0x641e3e38, 0x4fe7c483, 0x63fee4f8, +0x500f0302, 0x63df7c4d, 0x50363529, 0x63c0043b, 0x505d5af1, 0x63a07cc7, 0x50847454, 0x6380e5f6, +0x50ab814d, 0x63613fcd, 0x50d281d5, 0x63418a50, 0x50f975e6, 0x6321c585, 0x51205d7b, 0x6301f171, +0x5147388c, 0x62e20e17, 0x516e0715, 0x62c21b7e, 0x5194c910, 0x62a219aa, 0x51bb7e75, 0x628208a1, +0x51e22740, 0x6261e866, 0x5208c36a, 0x6241b8ff, 0x522f52ee, 0x62217a72, 0x5255d5c5, 0x62012cc2, +0x527c4bea, 0x61e0cff5, 0x52a2b556, 0x61c06410, 0x52c91204, 0x619fe918, 0x52ef61ee, 0x617f5f12, +0x5315a50e, 0x615ec603, 0x533bdb5d, 0x613e1df0, 0x536204d7, 0x611d66de, 0x53882175, 0x60fca0d2, +0x53ae3131, 0x60dbcbd1, 0x53d43406, 0x60bae7e1, 0x53fa29ed, 0x6099f505, 0x542012e1, 0x6078f344, +0x5445eedb, 0x6057e2a2, 0x546bbdd7, 0x6036c325, 0x54917fce, 0x601594d1, 0x54b734ba, 0x5ff457ad, +0x54dcdc96, 0x5fd30bbc, 0x5502775c, 0x5fb1b104, 0x55280505, 0x5f90478a, 0x554d858d, 0x5f6ecf53, +0x5572f8ed, 0x5f4d4865, 0x55985f20, 0x5f2bb2c5, 0x55bdb81f, 0x5f0a0e77, 0x55e303e6, 0x5ee85b82, +0x5608426e, 0x5ec699e9, 0x562d73b2, 0x5ea4c9b3, 0x565297ab, 0x5e82eae5, 0x5677ae54, 0x5e60fd84, +0x569cb7a8, 0x5e3f0194, 0x56c1b3a1, 0x5e1cf71c, 0x56e6a239, 0x5dfade20, 0x570b8369, 0x5dd8b6a7, +0x5730572e, 0x5db680b4, 0x57551d80, 0x5d943c4e, 0x5779d65b, 0x5d71e979, 0x579e81b8, 0x5d4f883b, +0x57c31f92, 0x5d2d189a, 0x57e7afe4, 0x5d0a9a9a, 0x580c32a7, 0x5ce80e41, 0x5830a7d6, 0x5cc57394, +0x58550f6c, 0x5ca2ca99, 0x58796962, 0x5c801354, 0x589db5b3, 0x5c5d4dcc, 0x58c1f45b, 0x5c3a7a05, +0x58e62552, 0x5c179806, 0x590a4893, 0x5bf4a7d2, 0x592e5e19, 0x5bd1a971, 0x595265df, 0x5bae9ce7, +0x59765fde, 0x5b8b8239, 0x599a4c12, 0x5b68596d, 0x59be2a74, 0x5b452288, 0x59e1faff, 0x5b21dd90, +0x5a05bdae, 0x5afe8a8b, 0x5a29727b, 0x5adb297d, 0x5a4d1960, 0x5ab7ba6c, 0x5a70b258, 0x5a943d5e, +}; + +const int kbdWindowOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 128}; + +const int kbdWindow[128 + 1024] PROGMEM = { +/* 128 - format = Q31 * 2^0 */ +0x00016f63, 0x7ffffffe, 0x0003e382, 0x7ffffff1, 0x00078f64, 0x7fffffc7, 0x000cc323, 0x7fffff5d, +0x0013d9ed, 0x7ffffe76, 0x001d3a9d, 0x7ffffcaa, 0x0029581f, 0x7ffff953, 0x0038b1bd, 0x7ffff372, +0x004bd34d, 0x7fffe98b, 0x00635538, 0x7fffd975, 0x007fdc64, 0x7fffc024, 0x00a219f1, 0x7fff995b, +0x00cacad0, 0x7fff5f5b, 0x00fab72d, 0x7fff0a75, 0x0132b1af, 0x7ffe9091, 0x01739689, 0x7ffde49e, +0x01be4a63, 0x7ffcf5ef, 0x0213b910, 0x7ffbaf84, 0x0274d41e, 0x7ff9f73a, 0x02e2913a, 0x7ff7acf1, +0x035de86c, 0x7ff4a99a, 0x03e7d233, 0x7ff0be3d, 0x0481457c, 0x7febb2f1, 0x052b357c, 0x7fe545d4, +0x05e68f77, 0x7fdd2a02, 0x06b4386f, 0x7fd30695, 0x07950acb, 0x7fc675b4, 0x0889d3ef, 0x7fb703be, +0x099351e0, 0x7fa42e89, 0x0ab230e0, 0x7f8d64d8, 0x0be70923, 0x7f7205f8, 0x0d325c93, 0x7f516195, +0x0e9494ae, 0x7f2ab7d0, 0x100e0085, 0x7efd3997, 0x119ed2ef, 0x7ec8094a, 0x134720d8, 0x7e8a3ba7, +0x1506dfdc, 0x7e42d906, 0x16dde50b, 0x7df0dee4, 0x18cbe3f7, 0x7d9341b4, 0x1ad06e07, 0x7d28ef02, +0x1ceaf215, 0x7cb0cfcc, 0x1f1abc4f, 0x7c29cb20, 0x215ef677, 0x7b92c8eb, 0x23b6a867, 0x7aeab4ec, +0x2620b8ec, 0x7a3081d0, 0x289beef5, 0x79632c5a, 0x2b26f30b, 0x7881be95, 0x2dc0511f, 0x778b5304, +0x30667aa2, 0x767f17c0, 0x3317c8dd, 0x755c5178, 0x35d27f98, 0x74225e50, 0x3894cff3, 0x72d0b887, +0x3b5cdb7b, 0x7166f8e7, 0x3e28b770, 0x6fe4d8e8, 0x40f6702a, 0x6e4a3491, 0x43c40caa, 0x6c970bfc, +0x468f9231, 0x6acb8483, 0x495707f5, 0x68e7e994, 0x4c187ac7, 0x66ecad1c, 0x4ed200c5, 0x64da6797, +0x5181bcea, 0x62b1d7b7, 0x5425e28e, 0x6073e1ae, 0x56bcb8c2, 0x5e218e16, 0x59449d76, 0x5bbc0875, +/* 1024 - format = Q31 * 2^0 */ +0x0009962f, 0x7fffffa4, 0x000e16fb, 0x7fffff39, 0x0011ea65, 0x7ffffebf, 0x0015750e, 0x7ffffe34, +0x0018dc74, 0x7ffffd96, 0x001c332e, 0x7ffffce5, 0x001f83f5, 0x7ffffc1f, 0x0022d59a, 0x7ffffb43, +0x00262cc2, 0x7ffffa4f, 0x00298cc4, 0x7ffff942, 0x002cf81f, 0x7ffff81a, 0x003070c4, 0x7ffff6d6, +0x0033f840, 0x7ffff573, 0x00378fd9, 0x7ffff3f1, 0x003b38a1, 0x7ffff24d, 0x003ef381, 0x7ffff085, +0x0042c147, 0x7fffee98, 0x0046a2a8, 0x7fffec83, 0x004a9847, 0x7fffea44, 0x004ea2b7, 0x7fffe7d8, +0x0052c283, 0x7fffe53f, 0x0056f829, 0x7fffe274, 0x005b4422, 0x7fffdf76, 0x005fa6dd, 0x7fffdc43, +0x006420c8, 0x7fffd8d6, 0x0068b249, 0x7fffd52f, 0x006d5bc4, 0x7fffd149, 0x00721d9a, 0x7fffcd22, +0x0076f828, 0x7fffc8b6, 0x007bebca, 0x7fffc404, 0x0080f8d9, 0x7fffbf06, 0x00861fae, 0x7fffb9bb, +0x008b609e, 0x7fffb41e, 0x0090bbff, 0x7fffae2c, 0x00963224, 0x7fffa7e1, 0x009bc362, 0x7fffa13a, +0x00a17009, 0x7fff9a32, 0x00a7386c, 0x7fff92c5, 0x00ad1cdc, 0x7fff8af0, 0x00b31da8, 0x7fff82ad, +0x00b93b21, 0x7fff79f9, 0x00bf7596, 0x7fff70cf, 0x00c5cd57, 0x7fff672a, 0x00cc42b1, 0x7fff5d05, +0x00d2d5f3, 0x7fff525c, 0x00d9876c, 0x7fff4729, 0x00e05769, 0x7fff3b66, 0x00e74638, 0x7fff2f10, +0x00ee5426, 0x7fff221f, 0x00f58182, 0x7fff148e, 0x00fcce97, 0x7fff0658, 0x01043bb3, 0x7ffef776, +0x010bc923, 0x7ffee7e2, 0x01137733, 0x7ffed795, 0x011b4631, 0x7ffec68a, 0x01233669, 0x7ffeb4ba, +0x012b4827, 0x7ffea21d, 0x01337bb8, 0x7ffe8eac, 0x013bd167, 0x7ffe7a61, 0x01444982, 0x7ffe6533, +0x014ce454, 0x7ffe4f1c, 0x0155a229, 0x7ffe3813, 0x015e834d, 0x7ffe2011, 0x0167880c, 0x7ffe070d, +0x0170b0b2, 0x7ffdecff, 0x0179fd8b, 0x7ffdd1df, 0x01836ee1, 0x7ffdb5a2, 0x018d0500, 0x7ffd9842, +0x0196c035, 0x7ffd79b3, 0x01a0a0ca, 0x7ffd59ee, 0x01aaa70a, 0x7ffd38e8, 0x01b4d341, 0x7ffd1697, +0x01bf25b9, 0x7ffcf2f2, 0x01c99ebd, 0x7ffccdee, 0x01d43e99, 0x7ffca780, 0x01df0597, 0x7ffc7f9e, +0x01e9f401, 0x7ffc563d, 0x01f50a22, 0x7ffc2b51, 0x02004844, 0x7ffbfecf, 0x020baeb1, 0x7ffbd0ab, +0x02173db4, 0x7ffba0da, 0x0222f596, 0x7ffb6f4f, 0x022ed6a1, 0x7ffb3bfd, 0x023ae11f, 0x7ffb06d8, +0x02471558, 0x7ffacfd3, 0x02537397, 0x7ffa96e0, 0x025ffc25, 0x7ffa5bf2, 0x026caf4a, 0x7ffa1efc, +0x02798d4f, 0x7ff9dfee, 0x0286967c, 0x7ff99ebb, 0x0293cb1b, 0x7ff95b55, 0x02a12b72, 0x7ff915ab, +0x02aeb7cb, 0x7ff8cdaf, 0x02bc706d, 0x7ff88351, 0x02ca559f, 0x7ff83682, 0x02d867a9, 0x7ff7e731, +0x02e6a6d2, 0x7ff7954e, 0x02f51361, 0x7ff740c8, 0x0303ad9c, 0x7ff6e98e, 0x031275ca, 0x7ff68f8f, +0x03216c30, 0x7ff632ba, 0x03309116, 0x7ff5d2fb, 0x033fe4bf, 0x7ff57042, 0x034f6773, 0x7ff50a7a, +0x035f1975, 0x7ff4a192, 0x036efb0a, 0x7ff43576, 0x037f0c78, 0x7ff3c612, 0x038f4e02, 0x7ff35353, +0x039fbfeb, 0x7ff2dd24, 0x03b06279, 0x7ff26370, 0x03c135ed, 0x7ff1e623, 0x03d23a8b, 0x7ff16527, +0x03e37095, 0x7ff0e067, 0x03f4d84e, 0x7ff057cc, 0x040671f7, 0x7fefcb40, 0x04183dd3, 0x7fef3aad, +0x042a3c22, 0x7feea5fa, 0x043c6d25, 0x7fee0d11, 0x044ed11d, 0x7fed6fda, 0x04616849, 0x7fecce3d, +0x047432eb, 0x7fec2821, 0x04873140, 0x7feb7d6c, 0x049a6388, 0x7feace07, 0x04adca01, 0x7fea19d6, +0x04c164ea, 0x7fe960c0, 0x04d53481, 0x7fe8a2aa, 0x04e93902, 0x7fe7df79, 0x04fd72aa, 0x7fe71712, +0x0511e1b6, 0x7fe6495a, 0x05268663, 0x7fe57634, 0x053b60eb, 0x7fe49d83, 0x05507189, 0x7fe3bf2b, +0x0565b879, 0x7fe2db0f, 0x057b35f4, 0x7fe1f110, 0x0590ea35, 0x7fe10111, 0x05a6d574, 0x7fe00af3, +0x05bcf7ea, 0x7fdf0e97, 0x05d351cf, 0x7fde0bdd, 0x05e9e35c, 0x7fdd02a6, 0x0600acc8, 0x7fdbf2d2, +0x0617ae48, 0x7fdadc40, 0x062ee814, 0x7fd9becf, 0x06465a62, 0x7fd89a5e, 0x065e0565, 0x7fd76eca, +0x0675e954, 0x7fd63bf1, 0x068e0662, 0x7fd501b0, 0x06a65cc3, 0x7fd3bfe4, 0x06beecaa, 0x7fd2766a, +0x06d7b648, 0x7fd1251e, 0x06f0b9d1, 0x7fcfcbda, 0x0709f775, 0x7fce6a7a, 0x07236f65, 0x7fcd00d8, +0x073d21d2, 0x7fcb8ecf, 0x07570eea, 0x7fca1439, 0x077136dd, 0x7fc890ed, 0x078b99da, 0x7fc704c7, +0x07a6380d, 0x7fc56f9d, 0x07c111a4, 0x7fc3d147, 0x07dc26cc, 0x7fc2299e, 0x07f777b1, 0x7fc07878, +0x0813047d, 0x7fbebdac, 0x082ecd5b, 0x7fbcf90f, 0x084ad276, 0x7fbb2a78, 0x086713f7, 0x7fb951bc, +0x08839206, 0x7fb76eaf, 0x08a04ccb, 0x7fb58126, 0x08bd446e, 0x7fb388f4, 0x08da7915, 0x7fb185ee, +0x08f7eae7, 0x7faf77e5, 0x09159a09, 0x7fad5ead, 0x0933869f, 0x7fab3a17, 0x0951b0cd, 0x7fa909f6, +0x097018b7, 0x7fa6ce1a, 0x098ebe7f, 0x7fa48653, 0x09ada248, 0x7fa23273, 0x09ccc431, 0x7f9fd249, +0x09ec245b, 0x7f9d65a4, 0x0a0bc2e7, 0x7f9aec53, 0x0a2b9ff3, 0x7f986625, 0x0a4bbb9e, 0x7f95d2e7, +0x0a6c1604, 0x7f933267, 0x0a8caf43, 0x7f908472, 0x0aad8776, 0x7f8dc8d5, 0x0ace9eb9, 0x7f8aff5c, +0x0aeff526, 0x7f8827d3, 0x0b118ad8, 0x7f854204, 0x0b335fe6, 0x7f824dbb, 0x0b557469, 0x7f7f4ac3, +0x0b77c879, 0x7f7c38e4, 0x0b9a5c2b, 0x7f7917e9, 0x0bbd2f97, 0x7f75e79b, 0x0be042d0, 0x7f72a7c3, +0x0c0395ec, 0x7f6f5828, 0x0c2728fd, 0x7f6bf892, 0x0c4afc16, 0x7f6888c9, 0x0c6f0f4a, 0x7f650894, +0x0c9362a8, 0x7f6177b9, 0x0cb7f642, 0x7f5dd5ff, 0x0cdcca26, 0x7f5a232a, 0x0d01de63, 0x7f565f00, +0x0d273307, 0x7f528947, 0x0d4cc81f, 0x7f4ea1c2, 0x0d729db7, 0x7f4aa835, 0x0d98b3da, 0x7f469c65, +0x0dbf0a92, 0x7f427e13, 0x0de5a1e9, 0x7f3e4d04, 0x0e0c79e7, 0x7f3a08f9, 0x0e339295, 0x7f35b1b4, +0x0e5aebfa, 0x7f3146f8, 0x0e82861a, 0x7f2cc884, 0x0eaa60fd, 0x7f28361b, 0x0ed27ca5, 0x7f238f7c, +0x0efad917, 0x7f1ed467, 0x0f237656, 0x7f1a049d, 0x0f4c5462, 0x7f151fdc, 0x0f75733d, 0x7f1025e3, +0x0f9ed2e6, 0x7f0b1672, 0x0fc8735e, 0x7f05f146, 0x0ff254a1, 0x7f00b61d, 0x101c76ae, 0x7efb64b4, +0x1046d981, 0x7ef5fcca, 0x10717d15, 0x7ef07e19, 0x109c6165, 0x7eeae860, 0x10c7866a, 0x7ee53b5b, +0x10f2ec1e, 0x7edf76c4, 0x111e9279, 0x7ed99a58, 0x114a7971, 0x7ed3a5d1, 0x1176a0fc, 0x7ecd98eb, +0x11a30910, 0x7ec77360, 0x11cfb1a1, 0x7ec134eb, 0x11fc9aa2, 0x7ebadd44, 0x1229c406, 0x7eb46c27, +0x12572dbf, 0x7eade14c, 0x1284d7bc, 0x7ea73c6c, 0x12b2c1ed, 0x7ea07d41, 0x12e0ec42, 0x7e99a382, +0x130f56a8, 0x7e92aee7, 0x133e010b, 0x7e8b9f2a, 0x136ceb59, 0x7e847402, 0x139c157b, 0x7e7d2d25, +0x13cb7f5d, 0x7e75ca4c, 0x13fb28e6, 0x7e6e4b2d, 0x142b1200, 0x7e66af7f, 0x145b3a92, 0x7e5ef6f8, +0x148ba281, 0x7e572150, 0x14bc49b4, 0x7e4f2e3b, 0x14ed300f, 0x7e471d70, 0x151e5575, 0x7e3eeea5, +0x154fb9c9, 0x7e36a18e, 0x15815ced, 0x7e2e35e2, 0x15b33ec1, 0x7e25ab56, 0x15e55f25, 0x7e1d019e, +0x1617bdf9, 0x7e14386e, 0x164a5b19, 0x7e0b4f7d, 0x167d3662, 0x7e02467e, 0x16b04fb2, 0x7df91d25, +0x16e3a6e2, 0x7defd327, 0x17173bce, 0x7de66837, 0x174b0e4d, 0x7ddcdc0a, 0x177f1e39, 0x7dd32e53, +0x17b36b69, 0x7dc95ec6, 0x17e7f5b3, 0x7dbf6d17, 0x181cbcec, 0x7db558f9, 0x1851c0e9, 0x7dab221f, +0x1887017d, 0x7da0c83c, 0x18bc7e7c, 0x7d964b05, 0x18f237b6, 0x7d8baa2b, 0x19282cfd, 0x7d80e563, +0x195e5e20, 0x7d75fc5e, 0x1994caee, 0x7d6aeed0, 0x19cb7335, 0x7d5fbc6d, 0x1a0256c2, 0x7d5464e6, +0x1a397561, 0x7d48e7ef, 0x1a70cede, 0x7d3d453b, 0x1aa86301, 0x7d317c7c, 0x1ae03195, 0x7d258d65, +0x1b183a63, 0x7d1977aa, 0x1b507d30, 0x7d0d3afc, 0x1b88f9c5, 0x7d00d710, 0x1bc1afe6, 0x7cf44b97, +0x1bfa9f58, 0x7ce79846, 0x1c33c7e0, 0x7cdabcce, 0x1c6d293f, 0x7ccdb8e4, 0x1ca6c337, 0x7cc08c39, +0x1ce0958a, 0x7cb33682, 0x1d1a9ff8, 0x7ca5b772, 0x1d54e240, 0x7c980ebd, 0x1d8f5c21, 0x7c8a3c14, +0x1dca0d56, 0x7c7c3f2e, 0x1e04f59f, 0x7c6e17bc, 0x1e4014b4, 0x7c5fc573, 0x1e7b6a53, 0x7c514807, +0x1eb6f633, 0x7c429f2c, 0x1ef2b80f, 0x7c33ca96, 0x1f2eaf9e, 0x7c24c9fa, 0x1f6adc98, 0x7c159d0d, +0x1fa73eb2, 0x7c064383, 0x1fe3d5a3, 0x7bf6bd11, 0x2020a11e, 0x7be7096c, 0x205da0d8, 0x7bd7284a, +0x209ad483, 0x7bc71960, 0x20d83bd1, 0x7bb6dc65, 0x2115d674, 0x7ba6710d, 0x2153a41b, 0x7b95d710, +0x2191a476, 0x7b850e24, 0x21cfd734, 0x7b7415ff, 0x220e3c02, 0x7b62ee59, 0x224cd28d, 0x7b5196e9, +0x228b9a82, 0x7b400f67, 0x22ca938a, 0x7b2e578a, 0x2309bd52, 0x7b1c6f0b, 0x23491783, 0x7b0a55a1, +0x2388a1c4, 0x7af80b07, 0x23c85bbf, 0x7ae58ef5, 0x2408451a, 0x7ad2e124, 0x24485d7c, 0x7ac0014e, +0x2488a48a, 0x7aacef2e, 0x24c919e9, 0x7a99aa7e, 0x2509bd3d, 0x7a8632f8, 0x254a8e29, 0x7a728858, +0x258b8c50, 0x7a5eaa5a, 0x25ccb753, 0x7a4a98b9, 0x260e0ed3, 0x7a365333, 0x264f9271, 0x7a21d983, +0x269141cb, 0x7a0d2b68, 0x26d31c80, 0x79f8489e, 0x2715222f, 0x79e330e4, 0x27575273, 0x79cde3f8, +0x2799acea, 0x79b8619a, 0x27dc3130, 0x79a2a989, 0x281ededf, 0x798cbb85, 0x2861b591, 0x7976974e, +0x28a4b4e0, 0x79603ca5, 0x28e7dc65, 0x7949ab4c, 0x292b2bb8, 0x7932e304, 0x296ea270, 0x791be390, +0x29b24024, 0x7904acb3, 0x29f6046b, 0x78ed3e30, 0x2a39eed8, 0x78d597cc, 0x2a7dff02, 0x78bdb94a, +0x2ac2347c, 0x78a5a270, 0x2b068eda, 0x788d5304, 0x2b4b0dae, 0x7874cacb, 0x2b8fb08a, 0x785c098d, +0x2bd47700, 0x78430f11, 0x2c1960a1, 0x7829db1f, 0x2c5e6cfd, 0x78106d7f, 0x2ca39ba3, 0x77f6c5fb, +0x2ce8ec23, 0x77dce45c, 0x2d2e5e0b, 0x77c2c86e, 0x2d73f0e8, 0x77a871fa, 0x2db9a449, 0x778de0cd, +0x2dff77b8, 0x777314b2, 0x2e456ac4, 0x77580d78, 0x2e8b7cf6, 0x773ccaeb, 0x2ed1addb, 0x77214cdb, +0x2f17fcfb, 0x77059315, 0x2f5e69e2, 0x76e99d69, 0x2fa4f419, 0x76cd6ba9, 0x2feb9b27, 0x76b0fda4, +0x30325e96, 0x7694532e, 0x30793dee, 0x76776c17, 0x30c038b5, 0x765a4834, 0x31074e72, 0x763ce759, +0x314e7eab, 0x761f4959, 0x3195c8e6, 0x76016e0b, 0x31dd2ca9, 0x75e35545, 0x3224a979, 0x75c4fedc, +0x326c3ed8, 0x75a66aab, 0x32b3ec4d, 0x75879887, 0x32fbb159, 0x7568884b, 0x33438d81, 0x754939d1, +0x338b8045, 0x7529acf4, 0x33d3892a, 0x7509e18e, 0x341ba7b1, 0x74e9d77d, 0x3463db5a, 0x74c98e9e, +0x34ac23a7, 0x74a906cd, 0x34f48019, 0x74883fec, 0x353cf02f, 0x746739d8, 0x3585736a, 0x7445f472, +0x35ce0949, 0x74246f9c, 0x3616b14c, 0x7402ab37, 0x365f6af0, 0x73e0a727, 0x36a835b5, 0x73be6350, +0x36f11118, 0x739bdf95, 0x3739fc98, 0x73791bdd, 0x3782f7b2, 0x7356180e, 0x37cc01e3, 0x7332d410, +0x38151aa8, 0x730f4fc9, 0x385e417e, 0x72eb8b24, 0x38a775e1, 0x72c7860a, 0x38f0b74d, 0x72a34066, +0x393a053e, 0x727eba24, 0x39835f30, 0x7259f331, 0x39ccc49e, 0x7234eb79, 0x3a163503, 0x720fa2eb, +0x3a5fafda, 0x71ea1977, 0x3aa9349e, 0x71c44f0c, 0x3af2c2ca, 0x719e439d, 0x3b3c59d7, 0x7177f71a, +0x3b85f940, 0x71516978, 0x3bcfa07e, 0x712a9aaa, 0x3c194f0d, 0x71038aa4, 0x3c630464, 0x70dc395e, +0x3cacbfff, 0x70b4a6cd, 0x3cf68155, 0x708cd2e9, 0x3d4047e1, 0x7064bdab, 0x3d8a131c, 0x703c670d, +0x3dd3e27e, 0x7013cf0a, 0x3e1db580, 0x6feaf59c, 0x3e678b9b, 0x6fc1dac1, 0x3eb16449, 0x6f987e76, +0x3efb3f01, 0x6f6ee0b9, 0x3f451b3d, 0x6f45018b, 0x3f8ef874, 0x6f1ae0eb, 0x3fd8d620, 0x6ef07edb, +0x4022b3b9, 0x6ec5db5d, 0x406c90b7, 0x6e9af675, 0x40b66c93, 0x6e6fd027, 0x410046c5, 0x6e446879, +0x414a1ec6, 0x6e18bf71, 0x4193f40d, 0x6decd517, 0x41ddc615, 0x6dc0a972, 0x42279455, 0x6d943c8d, +0x42715e45, 0x6d678e71, 0x42bb235f, 0x6d3a9f2a, 0x4304e31a, 0x6d0d6ec5, 0x434e9cf1, 0x6cdffd4f, +0x4398505b, 0x6cb24ad6, 0x43e1fcd1, 0x6c84576b, 0x442ba1cd, 0x6c56231c, 0x44753ec7, 0x6c27adfd, +0x44bed33a, 0x6bf8f81e, 0x45085e9d, 0x6bca0195, 0x4551e06b, 0x6b9aca75, 0x459b581e, 0x6b6b52d5, +0x45e4c52f, 0x6b3b9ac9, 0x462e2717, 0x6b0ba26b, 0x46777d52, 0x6adb69d3, 0x46c0c75a, 0x6aaaf11b, +0x470a04a9, 0x6a7a385c, 0x475334b9, 0x6a493fb3, 0x479c5707, 0x6a18073d, 0x47e56b0c, 0x69e68f17, +0x482e7045, 0x69b4d761, 0x4877662c, 0x6982e039, 0x48c04c3f, 0x6950a9c0, 0x490921f8, 0x691e341a, +0x4951e6d5, 0x68eb7f67, 0x499a9a51, 0x68b88bcd, 0x49e33beb, 0x68855970, 0x4a2bcb1f, 0x6851e875, +0x4a74476b, 0x681e3905, 0x4abcb04c, 0x67ea4b47, 0x4b050541, 0x67b61f63, 0x4b4d45c9, 0x6781b585, +0x4b957162, 0x674d0dd6, 0x4bdd878c, 0x67182883, 0x4c2587c6, 0x66e305b8, 0x4c6d7190, 0x66ada5a5, +0x4cb5446a, 0x66780878, 0x4cfcffd5, 0x66422e60, 0x4d44a353, 0x660c1790, 0x4d8c2e64, 0x65d5c439, +0x4dd3a08c, 0x659f348e, 0x4e1af94b, 0x656868c3, 0x4e623825, 0x6531610d, 0x4ea95c9d, 0x64fa1da3, +0x4ef06637, 0x64c29ebb, 0x4f375477, 0x648ae48d, 0x4f7e26e1, 0x6452ef53, 0x4fc4dcfb, 0x641abf46, +0x500b7649, 0x63e254a2, 0x5051f253, 0x63a9afa2, 0x5098509f, 0x6370d083, 0x50de90b3, 0x6337b784, +0x5124b218, 0x62fe64e3, 0x516ab455, 0x62c4d8e0, 0x51b096f3, 0x628b13bc, 0x51f6597b, 0x625115b8, +0x523bfb78, 0x6216df18, 0x52817c72, 0x61dc701f, 0x52c6dbf5, 0x61a1c912, 0x530c198d, 0x6166ea36, +0x535134c5, 0x612bd3d2, 0x53962d2a, 0x60f0862d, 0x53db024a, 0x60b50190, 0x541fb3b1, 0x60794644, +0x546440ef, 0x603d5494, 0x54a8a992, 0x60012cca, 0x54eced2b, 0x5fc4cf33, 0x55310b48, 0x5f883c1c, +0x5575037c, 0x5f4b73d2, 0x55b8d558, 0x5f0e76a5, 0x55fc806f, 0x5ed144e5, 0x56400452, 0x5e93dee1, +0x56836096, 0x5e5644ec, 0x56c694cf, 0x5e187757, 0x5709a092, 0x5dda7677, 0x574c8374, 0x5d9c429f, +0x578f3d0d, 0x5d5ddc24, 0x57d1ccf2, 0x5d1f435d, 0x581432bd, 0x5ce078a0, 0x58566e04, 0x5ca17c45, +0x58987e63, 0x5c624ea4, 0x58da6372, 0x5c22f016, 0x591c1ccc, 0x5be360f6, 0x595daa0d, 0x5ba3a19f, +0x599f0ad1, 0x5b63b26c, 0x59e03eb6, 0x5b2393ba, 0x5a214558, 0x5ae345e7, 0x5a621e56, 0x5aa2c951, +}; +/* bit reverse tables for FFT */ +const uint8_t bitrevtabOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 17}; +const uint8_t bitrevtab[17 + 129] PROGMEM = { +/* nfft = 64 */ +0x01, 0x08, 0x02, 0x04, 0x03, 0x0c, 0x05, 0x0a, 0x07, 0x0e, 0x0b, 0x0d, 0x00, 0x06, 0x09, 0x0f, +0x00, +/* nfft = 512 */ +0x01, 0x40, 0x02, 0x20, 0x03, 0x60, 0x04, 0x10, 0x05, 0x50, 0x06, 0x30, 0x07, 0x70, 0x09, 0x48, +0x0a, 0x28, 0x0b, 0x68, 0x0c, 0x18, 0x0d, 0x58, 0x0e, 0x38, 0x0f, 0x78, 0x11, 0x44, 0x12, 0x24, +0x13, 0x64, 0x15, 0x54, 0x16, 0x34, 0x17, 0x74, 0x19, 0x4c, 0x1a, 0x2c, 0x1b, 0x6c, 0x1d, 0x5c, +0x1e, 0x3c, 0x1f, 0x7c, 0x21, 0x42, 0x23, 0x62, 0x25, 0x52, 0x26, 0x32, 0x27, 0x72, 0x29, 0x4a, +0x2b, 0x6a, 0x2d, 0x5a, 0x2e, 0x3a, 0x2f, 0x7a, 0x31, 0x46, 0x33, 0x66, 0x35, 0x56, 0x37, 0x76, +0x39, 0x4e, 0x3b, 0x6e, 0x3d, 0x5e, 0x3f, 0x7e, 0x43, 0x61, 0x45, 0x51, 0x47, 0x71, 0x4b, 0x69, +0x4d, 0x59, 0x4f, 0x79, 0x53, 0x65, 0x57, 0x75, 0x5b, 0x6d, 0x5f, 0x7d, 0x67, 0x73, 0x6f, 0x7b, +0x00, 0x08, 0x14, 0x1c, 0x22, 0x2a, 0x36, 0x3e, 0x41, 0x49, 0x55, 0x5d, 0x63, 0x6b, 0x77, 0x7f, +0x00, +}; + +const uint8_t uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f}; + +const uint32_t twidTabOdd[8*6 + 32*6 + 128*6] PROGMEM = { + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x539eba45, 0xe7821d59, + 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, + 0x539eba45, 0xc4df2862, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x22a2f4f8, 0xc4df2862, + 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, + 0xac6145bb, 0x187de2a7, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x45f704f7, 0xf9ba1651, + 0x43103085, 0xfcdc1342, 0x48b2b335, 0xf69bf7c9, 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, + 0x4fd288dc, 0xed6bf9d1, 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, 0x553805f2, 0xe4a2eff6, + 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x569cc31b, 0xe1d4a2c8, + 0x4da1fab5, 0xf0730342, 0x5a6690ae, 0xd5052d97, 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, + 0x5a12e720, 0xce86ff2a, 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, 0x57cc15bc, 0xc91af976, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a12e720, 0xce86ff2a, + 0x553805f2, 0xe4a2eff6, 0x4da1fab5, 0xc1eb0209, 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, + 0x45f704f7, 0xc04ee4b8, 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, 0x3cc85709, 0xc013bc39, + 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x4fd288dc, 0xc2c17d52, + 0x5987b08a, 0xd9e01006, 0x26b2a794, 0xc3bdbdf6, 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, + 0x1a4608ab, 0xc78e9a1d, 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, 0x0d47d096, 0xcc983f70, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x396b3199, 0xc04ee4b8, + 0x5a6690ae, 0xd09441bb, 0xf2b82f6a, 0xd9e01006, 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, + 0xe5b9f755, 0xe1d4a2c8, 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, 0xd94d586c, 0xea70658a, + 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x1a4608ab, 0xc78e9a1d, + 0x57cc15bc, 0xc91af976, 0xc337a8f7, 0xfcdc1342, 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, + 0xba08fb09, 0x0645e9af, 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, 0xb25e054b, 0x0f8cfcbe, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xf720e574, 0xd76619b6, + 0x51d1dc80, 0xc3bdbdf6, 0xa833ea44, 0x20e70f32, 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, + 0xa5ed18e0, 0x2899e64a, 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, 0xa5996f52, 0x2f6bbe45, + 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xd5558381, 0xed6bf9d1, + 0x48b2b335, 0xc0b15502, 0xaac7fa0e, 0x39daf5e8, 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, + 0xb02d7724, 0x3d3e82ae, 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, 0xb74d4ccb, 0x3f4eaafe, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x418d2621, 0xfe6deaa1, + 0x40c7d2bd, 0xff36f170, 0x424ff28f, 0xfda4f351, 0x43103085, 0xfcdc1342, 0x418d2621, 0xfe6deaa1, + 0x4488e37f, 0xfb4ab7db, 0x4488e37f, 0xfb4ab7db, 0x424ff28f, 0xfda4f351, 0x46aa0d6d, 0xf8f21e8e, + 0x45f704f7, 0xf9ba1651, 0x43103085, 0xfcdc1342, 0x48b2b335, 0xf69bf7c9, 0x475a5c77, 0xf82a6c6a, + 0x43cdd89a, 0xfc135231, 0x4aa22036, 0xf4491311, 0x48b2b335, 0xf69bf7c9, 0x4488e37f, 0xfb4ab7db, + 0x4c77a88e, 0xf1fa3ecb, 0x49ffd417, 0xf50ef5de, 0x454149fc, 0xfa824bfd, 0x4e32a956, 0xefb047f2, + 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x4c77a88e, 0xf1fa3ecb, + 0x46aa0d6d, 0xf8f21e8e, 0x5156b6d9, 0xeb2e1dbe, 0x4da1fab5, 0xf0730342, 0x475a5c77, 0xf82a6c6a, + 0x52beac9f, 0xe8f77acf, 0x4ec05432, 0xeeee2d9d, 0x4807eb4b, 0xf7630799, 0x5409ed4b, 0xe6c8d59c, + 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, 0x553805f2, 0xe4a2eff6, 0x50d86e6d, 0xebeca36c, + 0x495aada2, 0xf5d544a7, 0x56488dc5, 0xe28688a4, 0x51d1dc80, 0xea70658a, 0x49ffd417, 0xf50ef5de, + 0x573b2635, 0xe0745b24, 0x52beac9f, 0xe8f77acf, 0x4aa22036, 0xf4491311, 0x580f7b19, 0xde6d1f65, + 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5471e2e6, 0xe61086bc, + 0x4bde1089, 0xf2beafed, 0x595c3e2a, 0xda8249b4, 0x553805f2, 0xe4a2eff6, 0x4c77a88e, 0xf1fa3ecb, + 0x59d438e5, 0xd8a00bae, 0x55f104dc, 0xe3399167, 0x4d0e4de2, 0xf136580d, 0x5a2d0957, 0xd6cb76c9, + 0x569cc31b, 0xe1d4a2c8, 0x4da1fab5, 0xf0730342, 0x5a6690ae, 0xd5052d97, 0x573b2635, 0xe0745b24, + 0x4e32a956, 0xefb047f2, 0x5a80baf6, 0xd34dcdb4, 0x57cc15bc, 0xdf18f0ce, 0x4ec05432, 0xeeee2d9d, + 0x5a7b7f1a, 0xd1a5ef90, 0x584f7b58, 0xddc29958, 0x4f4af5d1, 0xee2cbbc1, 0x5a56deec, 0xd00e2639, + 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, 0x592d59da, 0xdb25f566, + 0x50570819, 0xecabef3d, 0x59afaf4c, 0xcd110216, 0x5987b08a, 0xd9e01006, 0x50d86e6d, 0xebeca36c, + 0x592d59da, 0xcbacb0bf, 0x59d438e5, 0xd8a00bae, 0x5156b6d9, 0xeb2e1dbe, 0x588c1404, 0xca5a86c4, + 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, 0x57cc15bc, 0xc91af976, 0x5a43b190, 0xd6326a88, + 0x5249daa2, 0xe9b38223, 0x56eda1a0, 0xc7ee77b3, 0x5a6690ae, 0xd5052d97, 0x52beac9f, 0xe8f77acf, + 0x55f104dc, 0xc6d569be, 0x5a7b7f1a, 0xd3de9156, 0x53304df6, 0xe83c56cf, 0x54d69714, 0xc5d03118, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a7b7f1a, 0xd1a5ef90, + 0x5409ed4b, 0xe6c8d59c, 0x5249daa2, 0xc402a33c, 0x5a6690ae, 0xd09441bb, 0x5471e2e6, 0xe61086bc, + 0x50d86e6d, 0xc33aee27, 0x5a43b190, 0xcf89e3e8, 0x54d69714, 0xe55937d5, 0x4f4af5d1, 0xc2884e6e, + 0x5a12e720, 0xce86ff2a, 0x553805f2, 0xe4a2eff6, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xcd8bbb6d, + 0x55962bc0, 0xe3edb628, 0x4bde1089, 0xc1633f8a, 0x5987b08a, 0xcc983f70, 0x55f104dc, 0xe3399167, + 0x49ffd417, 0xc0f1360b, 0x592d59da, 0xcbacb0bf, 0x56488dc5, 0xe28688a4, 0x4807eb4b, 0xc0950d1d, + 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x584f7b58, 0xc9edeb50, + 0x56eda1a0, 0xe123e6ad, 0x43cdd89a, 0xc01ed535, 0x57cc15bc, 0xc91af976, 0x573b2635, 0xe0745b24, + 0x418d2621, 0xc004ef3f, 0x573b2635, 0xc8507ea7, 0x57854ddd, 0xdfc606f1, 0x3f35b59d, 0xc0013bd3, + 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, 0x3cc85709, 0xc013bc39, 0x55f104dc, 0xc6d569be, + 0x580f7b19, 0xde6d1f65, 0x3a45e1f7, 0xc03c6a07, 0x553805f2, 0xc6250a18, 0x584f7b58, 0xddc29958, + 0x37af354c, 0xc07b371e, 0x5471e2e6, 0xc57d965d, 0x588c1404, 0xdd196538, 0x350536f1, 0xc0d00db6, + 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x52beac9f, 0xc449d892, + 0x58fb0568, 0xdbcb0cce, 0x2f7afdfc, 0xc1bb5a11, 0x51d1dc80, 0xc3bdbdf6, 0x592d59da, 0xdb25f566, + 0x2c9caf6c, 0xc2517e31, 0x50d86e6d, 0xc33aee27, 0x595c3e2a, 0xda8249b4, 0x29aee694, 0xc2fd08a9, + 0x4fd288dc, 0xc2c17d52, 0x5987b08a, 0xd9e01006, 0x26b2a794, 0xc3bdbdf6, 0x4ec05432, 0xc2517e31, + 0x59afaf4c, 0xd93f4e9e, 0x23a8fb93, 0xc4935b3c, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xd8a00bae, + 0x2092f05f, 0xc57d965d, 0x4c77a88e, 0xc18e18a7, 0x59f54bee, 0xd8024d59, 0x1d719810, 0xc67c1e18, + 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, 0x49ffd417, 0xc0f1360b, + 0x5a2d0957, 0xd6cb76c9, 0x17115bc0, 0xc8b4ab32, 0x48b2b335, 0xc0b15502, 0x5a43b190, 0xd6326a88, + 0x13d4ae08, 0xc9edeb50, 0x475a5c77, 0xc07b371e, 0x5a56deec, 0xd59afadb, 0x10911f04, 0xcb39edca, + 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, 0x0d47d096, 0xcc983f70, 0x4488e37f, 0xc02c64a6, + 0x5a72c63b, 0xd4710883, 0x09f9e6a1, 0xce0866b8, 0x43103085, 0xc013bc39, 0x5a7b7f1a, 0xd3de9156, + 0x06a886a0, 0xcf89e3e8, 0x418d2621, 0xc004ef3f, 0x5a80baf6, 0xd34dcdb4, 0x0354d741, 0xd11c3142, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3e68fb62, 0xc004ef3f, + 0x5a80baf6, 0xd2317756, 0xfcab28bf, 0xd4710883, 0x3cc85709, 0xc013bc39, 0x5a7b7f1a, 0xd1a5ef90, + 0xf9577960, 0xd6326a88, 0x3b1e5335, 0xc02c64a6, 0x5a72c63b, 0xd11c3142, 0xf606195f, 0xd8024d59, + 0x396b3199, 0xc04ee4b8, 0x5a6690ae, 0xd09441bb, 0xf2b82f6a, 0xd9e01006, 0x37af354c, 0xc07b371e, + 0x5a56deec, 0xd00e2639, 0xef6ee0fc, 0xdbcb0cce, 0x35eaa2c7, 0xc0b15502, 0x5a43b190, 0xcf89e3e8, + 0xec2b51f8, 0xddc29958, 0x341dbfd3, 0xc0f1360b, 0x5a2d0957, 0xcf077fe1, 0xe8eea440, 0xdfc606f1, + 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x306c2624, 0xc18e18a7, + 0x59f54bee, 0xce0866b8, 0xe28e67f0, 0xe3edb628, 0x2e88013a, 0xc1eb0209, 0x59d438e5, 0xcd8bbb6d, + 0xdf6d0fa1, 0xe61086bc, 0x2c9caf6c, 0xc2517e31, 0x59afaf4c, 0xcd110216, 0xdc57046d, 0xe83c56cf, + 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, 0xd94d586c, 0xea70658a, 0x28b1b544, 0xc33aee27, + 0x595c3e2a, 0xcc217822, 0xd651196c, 0xecabef3d, 0x26b2a794, 0xc3bdbdf6, 0x592d59da, 0xcbacb0bf, + 0xd3635094, 0xeeee2d9d, 0x24ada23d, 0xc449d892, 0x58fb0568, 0xcb39edca, 0xd0850204, 0xf136580d, + 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x2092f05f, 0xc57d965d, + 0x588c1404, 0xca5a86c4, 0xcafac90f, 0xf5d544a7, 0x1e7de5df, 0xc6250a18, 0x584f7b58, 0xc9edeb50, + 0xc850cab4, 0xf82a6c6a, 0x1c6427a9, 0xc6d569be, 0x580f7b19, 0xc9836582, 0xc5ba1e09, 0xfa824bfd, + 0x1a4608ab, 0xc78e9a1d, 0x57cc15bc, 0xc91af976, 0xc337a8f7, 0xfcdc1342, 0x1823dc7d, 0xc8507ea7, + 0x57854ddd, 0xc8b4ab32, 0xc0ca4a63, 0xff36f170, 0x15fdf758, 0xc91af976, 0x573b2635, 0xc8507ea7, + 0xbe72d9df, 0x0192155f, 0x13d4ae08, 0xc9edeb50, 0x56eda1a0, 0xc7ee77b3, 0xbc322766, 0x03ecadcf, + 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, 0x0f7944a7, 0xcbacb0bf, + 0x56488dc5, 0xc730e997, 0xb7f814b5, 0x089cf867, 0x0d47d096, 0xcc983f70, 0x55f104dc, 0xc6d569be, + 0xb6002be9, 0x0af10a22, 0x0b145041, 0xcd8bbb6d, 0x55962bc0, 0xc67c1e18, 0xb421ef77, 0x0d415013, + 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, 0xb25e054b, 0x0f8cfcbe, 0x06a886a0, 0xcf89e3e8, + 0x54d69714, 0xc5d03118, 0xb0b50a2f, 0x11d3443f, 0x0470ebdc, 0xd09441bb, 0x5471e2e6, 0xc57d965d, + 0xaf279193, 0x14135c94, 0x0238a1c6, 0xd1a5ef90, 0x5409ed4b, 0xc52d3d18, 0xadb6255e, 0x164c7ddd, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xfdc75e3a, 0xd3de9156, + 0x53304df6, 0xc4935b3c, 0xab2968ec, 0x1aa6c82b, 0xfb8f1424, 0xd5052d97, 0x52beac9f, 0xc449d892, + 0xaa0efb24, 0x1cc66e99, 0xf9577960, 0xd6326a88, 0x5249daa2, 0xc402a33c, 0xa9125e60, 0x1edc1953, + 0xf720e574, 0xd76619b6, 0x51d1dc80, 0xc3bdbdf6, 0xa833ea44, 0x20e70f32, 0xf4ebafbf, 0xd8a00bae, + 0x5156b6d9, 0xc37b2b6a, 0xa773ebfc, 0x22e69ac8, 0xf2b82f6a, 0xd9e01006, 0x50d86e6d, 0xc33aee27, + 0xa6d2a626, 0x24da0a9a, 0xf086bb59, 0xdb25f566, 0x50570819, 0xc2fd08a9, 0xa65050b4, 0x26c0b162, + 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xec2b51f8, 0xddc29958, + 0x4f4af5d1, 0xc2884e6e, 0xa5a92114, 0x2a650525, 0xea0208a8, 0xdf18f0ce, 0x4ec05432, 0xc2517e31, + 0xa58480e6, 0x2c216eaa, 0xe7dc2383, 0xe0745b24, 0x4e32a956, 0xc21d0eb8, 0xa57f450a, 0x2dce88aa, + 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, 0xa5996f52, 0x2f6bbe45, 0xe39bd857, 0xe3399167, + 0x4d0e4de2, 0xc1bb5a11, 0xa5d2f6a9, 0x30f8801f, 0xe1821a21, 0xe4a2eff6, 0x4c77a88e, 0xc18e18a7, + 0xa62bc71b, 0x32744493, 0xdf6d0fa1, 0xe61086bc, 0x4bde1089, 0xc1633f8a, 0xa6a3c1d6, 0x33de87de, + 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xdb525dc3, 0xe8f77acf, + 0x4aa22036, 0xc114ccb9, 0xa7f084e7, 0x367c9a7e, 0xd94d586c, 0xea70658a, 0x49ffd417, 0xc0f1360b, + 0xa8c4d9cb, 0x37af8159, 0xd74e4abc, 0xebeca36c, 0x495aada2, 0xc0d00db6, 0xa9b7723b, 0x38cf1669, + 0xd5558381, 0xed6bf9d1, 0x48b2b335, 0xc0b15502, 0xaac7fa0e, 0x39daf5e8, 0xd3635094, 0xeeee2d9d, + 0x4807eb4b, 0xc0950d1d, 0xabf612b5, 0x3ad2c2e8, 0xd177fec6, 0xf0730342, 0x475a5c77, 0xc07b371e, + 0xad415361, 0x3bb6276e, 0xcf93d9dc, 0xf1fa3ecb, 0x46aa0d6d, 0xc063d405, 0xaea94927, 0x3c84d496, + 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, 0xcbe2402d, 0xf50ef5de, + 0x454149fc, 0xc03c6a07, 0xb1cd56aa, 0x3de2f148, 0xca155d39, 0xf69bf7c9, 0x4488e37f, 0xc02c64a6, + 0xb3885772, 0x3e71e759, 0xc850cab4, 0xf82a6c6a, 0x43cdd89a, 0xc01ed535, 0xb55ddfca, 0x3eeb3347, + 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, 0xb74d4ccb, 0x3f4eaafe, 0xc4e1accb, 0xfb4ab7db, + 0x424ff28f, 0xc00b1a20, 0xb955f293, 0x3f9c2bfb, 0xc337a8f7, 0xfcdc1342, 0x418d2621, 0xc004ef3f, + 0xbb771c81, 0x3fd39b5a, 0xc197049e, 0xfe6deaa1, 0x40c7d2bd, 0xc0013bd3, 0xbdb00d71, 0x3ff4e5e0, +}; + +const uint32_t twidTabEven[4*6 + 16*6 + 64*6] PROGMEM = { + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x5a82799a, 0xd2bec333, + 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, + 0x00000000, 0xd2bec333, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x4b418bbe, 0xf383a3e2, + 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, + 0x58c542c5, 0xdc71898d, 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xcac933ae, + 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, + 0x3248d382, 0xc13ad060, 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3248d382, 0xc13ad060, + 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, + 0xcdb72c7e, 0xf383a3e2, 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xee57aa21, 0xdc71898d, + 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, + 0xa73abd3b, 0x3536cc52, 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x43103085, 0xfcdc1342, + 0x418d2621, 0xfe6deaa1, 0x4488e37f, 0xfb4ab7db, 0x45f704f7, 0xf9ba1651, 0x43103085, 0xfcdc1342, + 0x48b2b335, 0xf69bf7c9, 0x48b2b335, 0xf69bf7c9, 0x4488e37f, 0xfb4ab7db, 0x4c77a88e, 0xf1fa3ecb, + 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x4da1fab5, 0xf0730342, + 0x475a5c77, 0xf82a6c6a, 0x52beac9f, 0xe8f77acf, 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, + 0x553805f2, 0xe4a2eff6, 0x51d1dc80, 0xea70658a, 0x49ffd417, 0xf50ef5de, 0x573b2635, 0xe0745b24, + 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x553805f2, 0xe4a2eff6, + 0x4c77a88e, 0xf1fa3ecb, 0x59d438e5, 0xd8a00bae, 0x569cc31b, 0xe1d4a2c8, 0x4da1fab5, 0xf0730342, + 0x5a6690ae, 0xd5052d97, 0x57cc15bc, 0xdf18f0ce, 0x4ec05432, 0xeeee2d9d, 0x5a7b7f1a, 0xd1a5ef90, + 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, 0x5987b08a, 0xd9e01006, + 0x50d86e6d, 0xebeca36c, 0x592d59da, 0xcbacb0bf, 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, + 0x57cc15bc, 0xc91af976, 0x5a6690ae, 0xd5052d97, 0x52beac9f, 0xe8f77acf, 0x55f104dc, 0xc6d569be, + 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a6690ae, 0xd09441bb, + 0x5471e2e6, 0xe61086bc, 0x50d86e6d, 0xc33aee27, 0x5a12e720, 0xce86ff2a, 0x553805f2, 0xe4a2eff6, + 0x4da1fab5, 0xc1eb0209, 0x5987b08a, 0xcc983f70, 0x55f104dc, 0xe3399167, 0x49ffd417, 0xc0f1360b, + 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x57cc15bc, 0xc91af976, + 0x573b2635, 0xe0745b24, 0x418d2621, 0xc004ef3f, 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, + 0x3cc85709, 0xc013bc39, 0x553805f2, 0xc6250a18, 0x584f7b58, 0xddc29958, 0x37af354c, 0xc07b371e, + 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x51d1dc80, 0xc3bdbdf6, + 0x592d59da, 0xdb25f566, 0x2c9caf6c, 0xc2517e31, 0x4fd288dc, 0xc2c17d52, 0x5987b08a, 0xd9e01006, + 0x26b2a794, 0xc3bdbdf6, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xd8a00bae, 0x2092f05f, 0xc57d965d, + 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, 0x48b2b335, 0xc0b15502, + 0x5a43b190, 0xd6326a88, 0x13d4ae08, 0xc9edeb50, 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, + 0x0d47d096, 0xcc983f70, 0x43103085, 0xc013bc39, 0x5a7b7f1a, 0xd3de9156, 0x06a886a0, 0xcf89e3e8, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3cc85709, 0xc013bc39, + 0x5a7b7f1a, 0xd1a5ef90, 0xf9577960, 0xd6326a88, 0x396b3199, 0xc04ee4b8, 0x5a6690ae, 0xd09441bb, + 0xf2b82f6a, 0xd9e01006, 0x35eaa2c7, 0xc0b15502, 0x5a43b190, 0xcf89e3e8, 0xec2b51f8, 0xddc29958, + 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x2e88013a, 0xc1eb0209, + 0x59d438e5, 0xcd8bbb6d, 0xdf6d0fa1, 0xe61086bc, 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, + 0xd94d586c, 0xea70658a, 0x26b2a794, 0xc3bdbdf6, 0x592d59da, 0xcbacb0bf, 0xd3635094, 0xeeee2d9d, + 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x1e7de5df, 0xc6250a18, + 0x584f7b58, 0xc9edeb50, 0xc850cab4, 0xf82a6c6a, 0x1a4608ab, 0xc78e9a1d, 0x57cc15bc, 0xc91af976, + 0xc337a8f7, 0xfcdc1342, 0x15fdf758, 0xc91af976, 0x573b2635, 0xc8507ea7, 0xbe72d9df, 0x0192155f, + 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, 0x0d47d096, 0xcc983f70, + 0x55f104dc, 0xc6d569be, 0xb6002be9, 0x0af10a22, 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, + 0xb25e054b, 0x0f8cfcbe, 0x0470ebdc, 0xd09441bb, 0x5471e2e6, 0xc57d965d, 0xaf279193, 0x14135c94, + 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xfb8f1424, 0xd5052d97, + 0x52beac9f, 0xc449d892, 0xaa0efb24, 0x1cc66e99, 0xf720e574, 0xd76619b6, 0x51d1dc80, 0xc3bdbdf6, + 0xa833ea44, 0x20e70f32, 0xf2b82f6a, 0xd9e01006, 0x50d86e6d, 0xc33aee27, 0xa6d2a626, 0x24da0a9a, + 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xea0208a8, 0xdf18f0ce, + 0x4ec05432, 0xc2517e31, 0xa58480e6, 0x2c216eaa, 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, + 0xa5996f52, 0x2f6bbe45, 0xe1821a21, 0xe4a2eff6, 0x4c77a88e, 0xc18e18a7, 0xa62bc71b, 0x32744493, + 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xd94d586c, 0xea70658a, + 0x49ffd417, 0xc0f1360b, 0xa8c4d9cb, 0x37af8159, 0xd5558381, 0xed6bf9d1, 0x48b2b335, 0xc0b15502, + 0xaac7fa0e, 0x39daf5e8, 0xd177fec6, 0xf0730342, 0x475a5c77, 0xc07b371e, 0xad415361, 0x3bb6276e, + 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, 0xca155d39, 0xf69bf7c9, + 0x4488e37f, 0xc02c64a6, 0xb3885772, 0x3e71e759, 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, + 0xb74d4ccb, 0x3f4eaafe, 0xc337a8f7, 0xfcdc1342, 0x418d2621, 0xc004ef3f, 0xbb771c81, 0x3fd39b5a, +}; + +/* log2Tab[x] = floor(log2(x)), format = Q28 */ +const int log2Tab[65] PROGMEM = { + 0x00000000, 0x00000000, 0x10000000, 0x195c01a3, 0x20000000, 0x25269e12, 0x295c01a3, 0x2ceaecfe, + 0x30000000, 0x32b80347, 0x35269e12, 0x3759d4f8, 0x395c01a3, 0x3b350047, 0x3ceaecfe, 0x3e829fb6, + 0x40000000, 0x41663f6f, 0x42b80347, 0x43f782d7, 0x45269e12, 0x4646eea2, 0x4759d4f8, 0x48608280, + 0x495c01a3, 0x4a4d3c25, 0x4b350047, 0x4c1404ea, 0x4ceaecfe, 0x4dba4a47, 0x4e829fb6, 0x4f446359, + 0x50000000, 0x50b5d69b, 0x51663f6f, 0x52118b11, 0x52b80347, 0x5359ebc5, 0x53f782d7, 0x549101ea, + 0x55269e12, 0x55b88873, 0x5646eea2, 0x56d1fafd, 0x5759d4f8, 0x57dea15a, 0x58608280, 0x58df988f, + 0x595c01a3, 0x59d5d9fd, 0x5a4d3c25, 0x5ac24113, 0x5b350047, 0x5ba58feb, 0x5c1404ea, 0x5c80730b, + 0x5ceaecfe, 0x5d53847a, 0x5dba4a47, 0x5e1f4e51, 0x5e829fb6, 0x5ee44cd5, 0x5f446359, 0x5fa2f045, + 0x60000000 +}; + +const HuffInfo_t huffTabSpecInfo[11] PROGMEM = { + /* table 0 not used */ + {11, { 1, 0, 0, 0, 8, 0, 24, 0, 24, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0}, + { 9, { 0, 0, 1, 1, 7, 24, 15, 19, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 81}, + {16, { 1, 0, 0, 4, 2, 6, 3, 5, 15, 15, 8, 9, 3, 3, 5, 2, 0, 0, 0, 0}, 162}, + {12, { 0, 0, 0, 10, 6, 0, 9, 21, 8, 14, 11, 2, 0, 0, 0, 0, 0, 0, 0, 0}, 243}, + {13, { 1, 0, 0, 4, 4, 0, 4, 12, 12, 12, 18, 10, 4, 0, 0, 0, 0, 0, 0, 0}, 324}, + {11, { 0, 0, 0, 9, 0, 16, 13, 8, 23, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 405}, + {12, { 1, 0, 2, 1, 0, 4, 5, 10, 14, 15, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0}, 486}, + {10, { 0, 0, 1, 5, 7, 10, 14, 15, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 550}, + {15, { 1, 0, 2, 1, 0, 4, 3, 8, 11, 20, 31, 38, 32, 14, 4, 0, 0, 0, 0, 0}, 614}, + {12, { 0, 0, 0, 3, 8, 14, 17, 25, 31, 41, 22, 8, 0, 0, 0, 0, 0, 0, 0, 0}, 783}, + {12, { 0, 0, 0, 2, 6, 7, 16, 59, 55, 95, 43, 6, 0, 0, 0, 0, 0, 0, 0, 0}, 952}, +}; + +const short huffTabSpec[1241] PROGMEM = { + /* spectrum table 1 [81] (signed) */ + 0x0000, 0x0200, 0x0e00, 0x0007, 0x0040, 0x0001, 0x0038, 0x0008, 0x01c0, 0x03c0, 0x0e40, 0x0039, 0x0078, 0x01c8, 0x000f, 0x0240, + 0x003f, 0x0fc0, 0x01f8, 0x0238, 0x0047, 0x0e08, 0x0009, 0x0208, 0x01c1, 0x0048, 0x0041, 0x0e38, 0x0201, 0x0e07, 0x0207, 0x0e01, + 0x01c7, 0x0278, 0x0e78, 0x03c8, 0x004f, 0x0079, 0x01c9, 0x01cf, 0x03f8, 0x0239, 0x007f, 0x0e48, 0x0e0f, 0x0fc8, 0x01f9, 0x03c1, + 0x03c7, 0x0e47, 0x0ff8, 0x01ff, 0x0049, 0x020f, 0x0241, 0x0e41, 0x0248, 0x0fc1, 0x0e3f, 0x0247, 0x023f, 0x0e39, 0x0fc7, 0x0e09, + 0x0209, 0x03cf, 0x0e79, 0x0e4f, 0x03f9, 0x0249, 0x0fc9, 0x027f, 0x0fcf, 0x0fff, 0x0279, 0x03c9, 0x0e49, 0x0e7f, 0x0ff9, 0x03ff, + 0x024f, + /* spectrum table 2 [81] (signed) */ + 0x0000, 0x0200, 0x0e00, 0x0001, 0x0038, 0x0007, 0x01c0, 0x0008, 0x0040, 0x01c8, 0x0e40, 0x0078, 0x000f, 0x0047, 0x0039, 0x0e07, + 0x03c0, 0x0238, 0x0fc0, 0x003f, 0x0208, 0x0201, 0x01c1, 0x0e08, 0x0041, 0x01f8, 0x0e01, 0x01c7, 0x0e38, 0x0240, 0x0048, 0x0009, + 0x0207, 0x0079, 0x0239, 0x0e78, 0x01cf, 0x03c8, 0x0247, 0x0209, 0x0e48, 0x01f9, 0x0248, 0x0e0f, 0x0ff8, 0x0e39, 0x03f8, 0x0278, + 0x03c1, 0x0e47, 0x0fc8, 0x0e09, 0x0fc1, 0x0fc7, 0x01ff, 0x020f, 0x023f, 0x007f, 0x0049, 0x0e41, 0x0e3f, 0x004f, 0x03c7, 0x01c9, + 0x0241, 0x03cf, 0x0e79, 0x03f9, 0x0fff, 0x0e4f, 0x0e49, 0x0249, 0x0fcf, 0x03c9, 0x0e7f, 0x0fc9, 0x027f, 0x03ff, 0x0ff9, 0x0279, + 0x024f, + /* spectrum table 3 [81] (unsigned) */ + 0x0000, 0x1200, 0x1001, 0x1040, 0x1008, 0x2240, 0x2009, 0x2048, 0x2041, 0x2208, 0x3049, 0x2201, 0x3248, 0x4249, 0x3209, 0x3241, + 0x1400, 0x1002, 0x200a, 0x2440, 0x3288, 0x2011, 0x3051, 0x2280, 0x304a, 0x3448, 0x1010, 0x2088, 0x2050, 0x1080, 0x2042, 0x2408, + 0x4289, 0x3089, 0x3250, 0x4251, 0x3281, 0x2210, 0x3211, 0x2081, 0x4449, 0x424a, 0x3441, 0x320a, 0x2012, 0x3052, 0x3488, 0x3290, + 0x2202, 0x2401, 0x3091, 0x2480, 0x4291, 0x3242, 0x3409, 0x4252, 0x4489, 0x2090, 0x308a, 0x3212, 0x3481, 0x3450, 0x3490, 0x3092, + 0x4491, 0x4451, 0x428a, 0x4292, 0x2082, 0x2410, 0x3282, 0x3411, 0x444a, 0x3442, 0x4492, 0x448a, 0x4452, 0x340a, 0x2402, 0x3482, + 0x3412, + /* spectrum table 4 [81] (unsigned) */ + 0x4249, 0x3049, 0x3241, 0x3248, 0x3209, 0x1200, 0x2240, 0x0000, 0x2009, 0x2208, 0x2201, 0x2048, 0x1001, 0x2041, 0x1008, 0x1040, + 0x4449, 0x4251, 0x4289, 0x424a, 0x3448, 0x3441, 0x3288, 0x3409, 0x3051, 0x304a, 0x3250, 0x3089, 0x320a, 0x3281, 0x3242, 0x3211, + 0x2440, 0x2408, 0x2280, 0x2401, 0x2042, 0x2088, 0x200a, 0x2050, 0x2081, 0x2202, 0x2011, 0x2210, 0x1400, 0x1002, 0x1080, 0x1010, + 0x4291, 0x4489, 0x4451, 0x4252, 0x428a, 0x444a, 0x3290, 0x3488, 0x3450, 0x3091, 0x3052, 0x3481, 0x308a, 0x3411, 0x3212, 0x4491, + 0x3282, 0x340a, 0x3442, 0x4292, 0x4452, 0x448a, 0x2090, 0x2480, 0x2012, 0x2410, 0x2082, 0x2402, 0x4492, 0x3092, 0x3490, 0x3482, + 0x3412, + /* spectrum table 5 [81] (signed) */ + 0x0000, 0x03e0, 0x0020, 0x0001, 0x001f, 0x003f, 0x03e1, 0x03ff, 0x0021, 0x03c0, 0x0002, 0x0040, 0x001e, 0x03df, 0x0041, 0x03fe, + 0x0022, 0x03c1, 0x005f, 0x03e2, 0x003e, 0x03a0, 0x0060, 0x001d, 0x0003, 0x03bf, 0x0023, 0x0061, 0x03fd, 0x03a1, 0x007f, 0x003d, + 0x03e3, 0x03c2, 0x0042, 0x03de, 0x005e, 0x03be, 0x007e, 0x03c3, 0x005d, 0x0062, 0x0043, 0x03a2, 0x03dd, 0x001c, 0x0380, 0x0081, + 0x0080, 0x039f, 0x0004, 0x009f, 0x03fc, 0x0024, 0x03e4, 0x0381, 0x003c, 0x007d, 0x03bd, 0x03a3, 0x03c4, 0x039e, 0x0082, 0x005c, + 0x0044, 0x0063, 0x0382, 0x03dc, 0x009e, 0x007c, 0x039d, 0x0383, 0x0064, 0x03a4, 0x0083, 0x009d, 0x03bc, 0x009c, 0x0384, 0x0084, + 0x039c, + /* spectrum table 6 [81] (signed) */ + 0x0000, 0x0020, 0x001f, 0x0001, 0x03e0, 0x0021, 0x03e1, 0x003f, 0x03ff, 0x005f, 0x0041, 0x03c1, 0x03df, 0x03c0, 0x03e2, 0x0040, + 0x003e, 0x0022, 0x001e, 0x03fe, 0x0002, 0x005e, 0x03c2, 0x03de, 0x0042, 0x03a1, 0x0061, 0x007f, 0x03e3, 0x03bf, 0x0023, 0x003d, + 0x03fd, 0x0060, 0x03a0, 0x001d, 0x0003, 0x0062, 0x03be, 0x03c3, 0x0043, 0x007e, 0x005d, 0x03dd, 0x03a2, 0x0063, 0x007d, 0x03bd, + 0x03a3, 0x003c, 0x03fc, 0x0081, 0x0381, 0x039f, 0x0024, 0x009f, 0x03e4, 0x001c, 0x0382, 0x039e, 0x0044, 0x03dc, 0x0380, 0x0082, + 0x009e, 0x03c4, 0x0080, 0x005c, 0x0004, 0x03bc, 0x03a4, 0x007c, 0x009d, 0x0064, 0x0083, 0x0383, 0x039d, 0x0084, 0x0384, 0x039c, + 0x009c, + /* spectrum table 7 [64] (unsigned) */ + 0x0000, 0x0420, 0x0401, 0x0821, 0x0841, 0x0822, 0x0440, 0x0402, 0x0861, 0x0823, 0x0842, 0x0460, 0x0403, 0x0843, 0x0862, 0x0824, + 0x0881, 0x0825, 0x08a1, 0x0863, 0x0844, 0x0404, 0x0480, 0x0882, 0x0845, 0x08a2, 0x0405, 0x08c1, 0x04a0, 0x0826, 0x0883, 0x0865, + 0x0864, 0x08a3, 0x0846, 0x08c2, 0x0827, 0x0866, 0x0406, 0x04c0, 0x0884, 0x08e1, 0x0885, 0x08e2, 0x08a4, 0x08c3, 0x0847, 0x08e3, + 0x08c4, 0x08a5, 0x0886, 0x0867, 0x04e0, 0x0407, 0x08c5, 0x08a6, 0x08e4, 0x0887, 0x08a7, 0x08e5, 0x08e6, 0x08c6, 0x08c7, 0x08e7, + /* spectrum table 8 [64] (unsigned) */ + 0x0821, 0x0841, 0x0420, 0x0822, 0x0401, 0x0842, 0x0000, 0x0440, 0x0402, 0x0861, 0x0823, 0x0862, 0x0843, 0x0863, 0x0881, 0x0824, + 0x0882, 0x0844, 0x0460, 0x0403, 0x0883, 0x0864, 0x08a2, 0x08a1, 0x0845, 0x0825, 0x08a3, 0x0865, 0x0884, 0x08a4, 0x0404, 0x0885, + 0x0480, 0x0846, 0x08c2, 0x08c1, 0x0826, 0x0866, 0x08c3, 0x08a5, 0x04a0, 0x08c4, 0x0405, 0x0886, 0x08e1, 0x08e2, 0x0847, 0x08c5, + 0x08e3, 0x0827, 0x08a6, 0x0867, 0x08c6, 0x08e4, 0x04c0, 0x0887, 0x0406, 0x08e5, 0x08e6, 0x08c7, 0x08a7, 0x04e0, 0x0407, 0x08e7, + /* spectrum table 9 [169] (unsigned) */ + 0x0000, 0x0420, 0x0401, 0x0821, 0x0841, 0x0822, 0x0440, 0x0402, 0x0861, 0x0842, 0x0823, 0x0460, 0x0403, 0x0843, 0x0862, 0x0824, + 0x0881, 0x0844, 0x0825, 0x0882, 0x0863, 0x0404, 0x0480, 0x08a1, 0x0845, 0x0826, 0x0864, 0x08a2, 0x08c1, 0x0883, 0x0405, 0x0846, + 0x04a0, 0x0827, 0x0865, 0x0828, 0x0901, 0x0884, 0x08a3, 0x08c2, 0x08e1, 0x0406, 0x0902, 0x0848, 0x0866, 0x0847, 0x0885, 0x0921, + 0x0829, 0x08e2, 0x04c0, 0x08a4, 0x08c3, 0x0903, 0x0407, 0x0922, 0x0868, 0x0886, 0x0867, 0x0408, 0x0941, 0x08c4, 0x0849, 0x08a5, + 0x0500, 0x04e0, 0x08e3, 0x0942, 0x0923, 0x0904, 0x082a, 0x08e4, 0x08c5, 0x08a6, 0x0888, 0x0887, 0x0869, 0x0961, 0x08a8, 0x0520, + 0x0905, 0x0943, 0x084a, 0x0409, 0x0962, 0x0924, 0x08c6, 0x0981, 0x0889, 0x0906, 0x082b, 0x0925, 0x0944, 0x08a7, 0x08e5, 0x084b, + 0x082c, 0x0982, 0x0963, 0x086a, 0x08a9, 0x08c7, 0x0907, 0x0964, 0x040a, 0x08e6, 0x0983, 0x0540, 0x0945, 0x088a, 0x08c8, 0x084c, + 0x0926, 0x0927, 0x088b, 0x0560, 0x08c9, 0x086b, 0x08aa, 0x0908, 0x08e8, 0x0985, 0x086c, 0x0965, 0x08e7, 0x0984, 0x0966, 0x0946, + 0x088c, 0x08e9, 0x08ab, 0x040b, 0x0986, 0x08ca, 0x0580, 0x0947, 0x08ac, 0x08ea, 0x0928, 0x040c, 0x0967, 0x0909, 0x0929, 0x0948, + 0x08eb, 0x0987, 0x08cb, 0x090b, 0x0968, 0x08ec, 0x08cc, 0x090a, 0x0949, 0x090c, 0x092a, 0x092b, 0x092c, 0x094b, 0x0989, 0x094a, + 0x0969, 0x0988, 0x096a, 0x098a, 0x098b, 0x094c, 0x096b, 0x096c, 0x098c, + /* spectrum table 10 [169] (unsigned) */ + 0x0821, 0x0822, 0x0841, 0x0842, 0x0420, 0x0401, 0x0823, 0x0862, 0x0861, 0x0843, 0x0863, 0x0440, 0x0402, 0x0844, 0x0882, 0x0824, + 0x0881, 0x0000, 0x0883, 0x0864, 0x0460, 0x0403, 0x0884, 0x0845, 0x08a2, 0x0825, 0x08a1, 0x08a3, 0x0865, 0x08a4, 0x0885, 0x08c2, + 0x0846, 0x08c3, 0x0480, 0x08c1, 0x0404, 0x0826, 0x0866, 0x08a5, 0x08c4, 0x0886, 0x08c5, 0x08e2, 0x0867, 0x0847, 0x08a6, 0x0902, + 0x08e3, 0x04a0, 0x08e1, 0x0405, 0x0901, 0x0827, 0x0903, 0x08e4, 0x0887, 0x0848, 0x08c6, 0x08e5, 0x0828, 0x0868, 0x0904, 0x0888, + 0x08a7, 0x0905, 0x08a8, 0x08e6, 0x08c7, 0x0922, 0x04c0, 0x08c8, 0x0923, 0x0869, 0x0921, 0x0849, 0x0406, 0x0906, 0x0924, 0x0889, + 0x0942, 0x0829, 0x08e7, 0x0907, 0x0925, 0x08e8, 0x0943, 0x08a9, 0x0944, 0x084a, 0x0941, 0x086a, 0x0926, 0x08c9, 0x0500, 0x088a, + 0x04e0, 0x0962, 0x08e9, 0x0963, 0x0946, 0x082a, 0x0961, 0x0927, 0x0407, 0x0908, 0x0945, 0x086b, 0x08aa, 0x0909, 0x0965, 0x0408, + 0x0964, 0x084b, 0x08ea, 0x08ca, 0x0947, 0x088b, 0x082b, 0x0982, 0x0928, 0x0983, 0x0966, 0x08ab, 0x0984, 0x0967, 0x0985, 0x086c, + 0x08cb, 0x0520, 0x0948, 0x0540, 0x0981, 0x0409, 0x088c, 0x0929, 0x0986, 0x084c, 0x090a, 0x092a, 0x082c, 0x0968, 0x0987, 0x08eb, + 0x08ac, 0x08cc, 0x0949, 0x090b, 0x0988, 0x040a, 0x08ec, 0x0560, 0x094a, 0x0969, 0x096a, 0x040b, 0x096b, 0x092b, 0x094b, 0x0580, + 0x090c, 0x0989, 0x094c, 0x092c, 0x096c, 0x098b, 0x040c, 0x098a, 0x098c, + /* spectrum table 11 [289] (unsigned) */ + 0x0000, 0x2041, 0x2410, 0x1040, 0x1001, 0x2081, 0x2042, 0x2082, 0x2043, 0x20c1, 0x20c2, 0x1080, 0x2083, 0x1002, 0x20c3, 0x2101, + 0x2044, 0x2102, 0x2084, 0x2103, 0x20c4, 0x10c0, 0x1003, 0x2141, 0x2142, 0x2085, 0x2104, 0x2045, 0x2143, 0x20c5, 0x2144, 0x2105, + 0x2182, 0x2086, 0x2181, 0x2183, 0x20c6, 0x2046, 0x2110, 0x20d0, 0x2405, 0x2403, 0x2404, 0x2184, 0x2406, 0x1100, 0x2106, 0x1004, + 0x2090, 0x2145, 0x2150, 0x2407, 0x2402, 0x2408, 0x2087, 0x21c2, 0x20c7, 0x2185, 0x2146, 0x2190, 0x240a, 0x21c3, 0x21c1, 0x2409, + 0x21d0, 0x2050, 0x2047, 0x2107, 0x240b, 0x21c4, 0x240c, 0x2210, 0x2401, 0x2186, 0x2250, 0x2088, 0x2147, 0x2290, 0x240d, 0x2203, + 0x2202, 0x20c8, 0x1140, 0x240e, 0x22d0, 0x21c5, 0x2108, 0x2187, 0x21c6, 0x1005, 0x2204, 0x240f, 0x2310, 0x2048, 0x2201, 0x2390, + 0x2148, 0x2350, 0x20c9, 0x2205, 0x21c7, 0x2089, 0x2206, 0x2242, 0x2243, 0x23d0, 0x2109, 0x2188, 0x1180, 0x2244, 0x2149, 0x2207, + 0x21c8, 0x2049, 0x2283, 0x1006, 0x2282, 0x2241, 0x2245, 0x210a, 0x208a, 0x2246, 0x20ca, 0x2189, 0x2284, 0x2208, 0x2285, 0x2247, + 0x22c3, 0x204a, 0x11c0, 0x2286, 0x21c9, 0x20cb, 0x214a, 0x2281, 0x210b, 0x22c2, 0x2342, 0x218a, 0x2343, 0x208b, 0x1400, 0x214b, + 0x22c5, 0x22c4, 0x2248, 0x21ca, 0x2209, 0x1010, 0x210d, 0x1007, 0x20cd, 0x22c6, 0x2341, 0x2344, 0x2303, 0x208d, 0x2345, 0x220a, + 0x218b, 0x2288, 0x2287, 0x2382, 0x2304, 0x204b, 0x210c, 0x22c1, 0x20cc, 0x204d, 0x2302, 0x21cb, 0x20ce, 0x214c, 0x214d, 0x2384, + 0x210e, 0x22c7, 0x2383, 0x2305, 0x2346, 0x2306, 0x1200, 0x22c8, 0x208c, 0x2249, 0x2385, 0x218d, 0x228a, 0x23c2, 0x220b, 0x224a, + 0x2386, 0x2289, 0x214e, 0x22c9, 0x2381, 0x208e, 0x218c, 0x204c, 0x2348, 0x1008, 0x2347, 0x21cc, 0x2307, 0x21cd, 0x23c3, 0x2301, + 0x218e, 0x208f, 0x23c5, 0x23c4, 0x204e, 0x224b, 0x210f, 0x2387, 0x220d, 0x2349, 0x220c, 0x214f, 0x20cf, 0x228b, 0x22ca, 0x2308, + 0x23c6, 0x23c7, 0x220e, 0x23c1, 0x21ce, 0x1240, 0x1009, 0x224d, 0x224c, 0x2309, 0x2388, 0x228d, 0x2389, 0x230a, 0x218f, 0x21cf, + 0x224e, 0x23c8, 0x22cb, 0x22ce, 0x204f, 0x228c, 0x228e, 0x234b, 0x234a, 0x22cd, 0x22cc, 0x220f, 0x238b, 0x234c, 0x230d, 0x23c9, + 0x238a, 0x1280, 0x230b, 0x224f, 0x100a, 0x230c, 0x12c0, 0x230e, 0x228f, 0x234d, 0x100d, 0x238c, 0x23ca, 0x23cb, 0x22cf, 0x238d, + 0x1340, 0x100b, 0x234e, 0x23cc, 0x23cd, 0x230f, 0x1380, 0x238e, 0x234f, 0x1300, 0x238f, 0x100e, 0x100c, 0x23ce, 0x13c0, 0x100f, + 0x23cf, +}; + +/* coefficient table 4.A.87, format = Q31 + * reordered as cTab[0], cTab[64], cTab[128], ... cTab[576], cTab[1], cTab[65], cTab[129], ... cTab[639] + * keeping full table (not using symmetry) to allow sequential access in synth filter inner loop + * format = Q31 + */ +const uint32_t cTabS[640] PROGMEM = { + 0x00000000, 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0x6d474e1d, 0xd1c58ace, 0x09015651, 0xfe4d1be3, 0x0055dba1, + 0xffede50e, 0x005b5371, 0x01d78bfc, 0x08d3e41b, 0x2faa221c, 0x6d41d963, 0xd3337b3d, 0x09299ead, 0xfe70b8d1, 0x0050b177, + 0xffed978a, 0x006090c4, 0x01fd3ba0, 0x08a24899, 0x311af3a4, 0x6d32730f, 0xd49fd55f, 0x094d7ec2, 0xfe933dc0, 0x004b6c46, + 0xffefc9b9, 0x0065fde5, 0x02244a24, 0x086b1eeb, 0x328cc6f0, 0x6d18520e, 0xd60a46e5, 0x096d0e21, 0xfeb48d0d, 0x00465348, + 0xfff0065d, 0x006b47fa, 0x024bf7a1, 0x082f552e, 0x33ff670e, 0x6cf4073e, 0xd7722f04, 0x09881dc5, 0xfed4bec3, 0x004103f4, + 0xffeff6ca, 0x0070c8a5, 0x0274ba43, 0x07ee507c, 0x3572ec70, 0x6cc59bab, 0xd8d7f21f, 0x099ec3dc, 0xfef3f6ab, 0x003c1fa4, + 0xffef7b8b, 0x0075fded, 0x029e35b4, 0x07a8127d, 0x36e69691, 0x6c8c4c7a, 0xda3b176a, 0x09b18a1d, 0xff120d70, 0x003745f9, + 0xffeedfa4, 0x007b3875, 0x02c89901, 0x075ca90c, 0x385a49c4, 0x6c492217, 0xdb9b5b12, 0x09c018ce, 0xff2ef725, 0x00329ab6, + 0xffee1650, 0x00807994, 0x02f3e48d, 0x070bbf58, 0x39ce0477, 0x6bfbdd98, 0xdcf898fb, 0x09caeb0f, 0xff4aabc8, 0x002d8e42, + 0xffed651d, 0x0085c217, 0x03201116, 0x06b559c3, 0x3b415115, 0x6ba4629f, 0xde529086, 0x09d1fa23, 0xff6542d1, 0x00293718, + 0xffecc31b, 0x008a7dd7, 0x034d01f0, 0x06593912, 0x3cb41219, 0x6b42a864, 0xdfa93ab5, 0x09d5560b, 0xff7ee3f1, 0x0024dd50, + 0xffebe77b, 0x008f4bfc, 0x037ad438, 0x05f7fb90, 0x3e25b17e, 0x6ad73e8d, 0xe0fc421e, 0x09d52709, 0xff975c01, 0x002064f8, + 0xffeb50b2, 0x009424c6, 0x03a966bb, 0x0590a67d, 0x3f962fb8, 0x6a619c5e, 0xe24b8f66, 0x09d19ca9, 0xffaea5d6, 0x001c3549, + 0xffea9192, 0x0098b855, 0x03d8afe6, 0x05237f9d, 0x41058bc6, 0x69e29784, 0xe396a45d, 0x09cab9f2, 0xffc4e365, 0x0018703f, + 0xffe9ca76, 0x009d10bf, 0x04083fec, 0x04b0adcb, 0x4272a385, 0x6959709d, 0xe4de0cb0, 0x09c0e59f, 0xffda17f2, 0x001471f8, + 0xffe940f4, 0x00a1039c, 0x043889c6, 0x0437fb0a, 0x43de620a, 0x68c7269b, 0xe620c476, 0x09b3d77f, 0xffee183b, 0x0010bc63, + 0xffe88ba8, 0x00a520bb, 0x04694101, 0x03b8f8dc, 0x4547daea, 0x682b39a4, 0xe75f8bb8, 0x09a3e163, 0x0000e790, 0x000d31b5, + 0xffe83a07, 0x00a8739d, 0x049aa82f, 0x03343533, 0x46aea856, 0x6785c24d, 0xe89971b7, 0x099140a7, 0x00131c75, 0x0009aa3f, + 0xffe79e16, 0x00abe79e, 0x04cc2fcf, 0x02a99097, 0x4812f848, 0x66d76725, 0xe9cea84a, 0x097c1ee8, 0x0023b989, 0x0006b1cf, + 0xffe7746e, 0x00af374c, 0x04fe20be, 0x02186a91, 0x4973fef1, 0x661fd6b8, 0xeafee7f1, 0x0963ed46, 0x0033b927, 0x00039609, + 0xffe6d466, 0x00b1978d, 0x05303f87, 0x01816e06, 0x4ad237a2, 0x655f63f2, 0xec2a3f5f, 0x0949eaac, 0x00426f36, 0x00007134, + 0xffe6afee, 0x00b3d15c, 0x05626209, 0x00e42fa2, 0x4c2ca3df, 0x64964063, 0xed50a31d, 0x092d7970, 0x00504f41, 0xfffdfa25, + 0xffe65416, 0x00b5c867, 0x05950122, 0x0040c496, 0x4d83976c, 0x63c45243, 0xee71b2fe, 0x090ec1fc, 0x005d36df, 0xfffb42b0, + 0xffe681c6, 0x00b74c37, 0x05c76fed, 0xff96db90, 0x4ed62be3, 0x62ea6474, 0xef8d4d7b, 0x08edfeaa, 0x006928a0, 0xfff91fca, + 0xffe66dd0, 0x00b8394b, 0x05f9c051, 0xfee723c6, 0x5024d70e, 0x6207f220, 0xf0a3959f, 0x08cb4e23, 0x007400b8, 0xfff681d6, + 0xffe66fac, 0x00b8fe0d, 0x062bf5ec, 0xfe310657, 0x516eefb9, 0x611d58a3, 0xf1b461ab, 0x08a75da4, 0x007e0393, 0xfff48700, + 0xffe69423, 0x00b8c6b0, 0x065dd56a, 0xfd7475d8, 0x52b449de, 0x602b0c7f, 0xf2bf6ea4, 0x0880ffdd, 0x00872c63, 0xfff294c3, + 0xffe6fed4, 0x00b85f70, 0x068f8b44, 0xfcb1d740, 0x53f495aa, 0x5f30ff5f, 0xf3c4e887, 0x08594887, 0x008f87aa, 0xfff0e7ef, + 0xffe75361, 0x00b73ab0, 0x06c0f0c0, 0xfbe8f5bd, 0x552f8ff7, 0x5e2f6367, 0xf4c473c6, 0x08303897, 0x0096dcc2, 0xffef2395, + 0xffe80414, 0x00b58c8c, 0x06f1825d, 0xfb19b7bd, 0x56654bdd, 0x5d26be9b, 0xf5be0fa9, 0x08061671, 0x009da526, 0xffedc418, + 0xffe85b4b, 0x00b36acd, 0x0721bf22, 0xfa44a069, 0x579505f5, 0x5c16d0ae, 0xf6b1f3c3, 0x07da2b7f, 0x00a3508f, 0xffec8409, + 0xffe954d0, 0x00b06b68, 0x075112a2, 0xf96916f5, 0x58befacd, 0x5b001db8, 0xf79fa13a, 0x07ad8c26, 0x00a85e94, 0xffeb3849, + 0xffea353a, 0x00acbd2f, 0x077fedb3, 0xf887507c, 0x59e2f69e, 0x59e2f69e, 0xf887507c, 0x077fedb3, 0x00acbd2f, 0xffea353a, + 0xffeb3849, 0x00a85e94, 0x07ad8c26, 0xf79fa13a, 0x5b001db8, 0x58befacd, 0xf96916f5, 0x075112a2, 0x00b06b68, 0xffe954d0, + 0xffec8409, 0x00a3508f, 0x07da2b7f, 0xf6b1f3c3, 0x5c16d0ae, 0x579505f5, 0xfa44a069, 0x0721bf22, 0x00b36acd, 0xffe85b4b, + 0xffedc418, 0x009da526, 0x08061671, 0xf5be0fa9, 0x5d26be9b, 0x56654bdd, 0xfb19b7bd, 0x06f1825d, 0x00b58c8c, 0xffe80414, + 0xffef2395, 0x0096dcc2, 0x08303897, 0xf4c473c6, 0x5e2f6367, 0x552f8ff7, 0xfbe8f5bd, 0x06c0f0c0, 0x00b73ab0, 0xffe75361, + 0xfff0e7ef, 0x008f87aa, 0x08594887, 0xf3c4e887, 0x5f30ff5f, 0x53f495aa, 0xfcb1d740, 0x068f8b44, 0x00b85f70, 0xffe6fed4, + 0xfff294c3, 0x00872c63, 0x0880ffdd, 0xf2bf6ea4, 0x602b0c7f, 0x52b449de, 0xfd7475d8, 0x065dd56a, 0x00b8c6b0, 0xffe69423, + 0xfff48700, 0x007e0393, 0x08a75da4, 0xf1b461ab, 0x611d58a3, 0x516eefb9, 0xfe310657, 0x062bf5ec, 0x00b8fe0d, 0xffe66fac, + 0xfff681d6, 0x007400b8, 0x08cb4e23, 0xf0a3959f, 0x6207f220, 0x5024d70e, 0xfee723c6, 0x05f9c051, 0x00b8394b, 0xffe66dd0, + 0xfff91fca, 0x006928a0, 0x08edfeaa, 0xef8d4d7b, 0x62ea6474, 0x4ed62be3, 0xff96db90, 0x05c76fed, 0x00b74c37, 0xffe681c6, + 0xfffb42b0, 0x005d36df, 0x090ec1fc, 0xee71b2fe, 0x63c45243, 0x4d83976c, 0x0040c496, 0x05950122, 0x00b5c867, 0xffe65416, + 0xfffdfa25, 0x00504f41, 0x092d7970, 0xed50a31d, 0x64964063, 0x4c2ca3df, 0x00e42fa2, 0x05626209, 0x00b3d15c, 0xffe6afee, + 0x00007134, 0x00426f36, 0x0949eaac, 0xec2a3f5f, 0x655f63f2, 0x4ad237a2, 0x01816e06, 0x05303f87, 0x00b1978d, 0xffe6d466, + 0x00039609, 0x0033b927, 0x0963ed46, 0xeafee7f1, 0x661fd6b8, 0x4973fef1, 0x02186a91, 0x04fe20be, 0x00af374c, 0xffe7746e, + 0x0006b1cf, 0x0023b989, 0x097c1ee8, 0xe9cea84a, 0x66d76725, 0x4812f848, 0x02a99097, 0x04cc2fcf, 0x00abe79e, 0xffe79e16, + 0x0009aa3f, 0x00131c75, 0x099140a7, 0xe89971b7, 0x6785c24d, 0x46aea856, 0x03343533, 0x049aa82f, 0x00a8739d, 0xffe83a07, + 0x000d31b5, 0x0000e790, 0x09a3e163, 0xe75f8bb8, 0x682b39a4, 0x4547daea, 0x03b8f8dc, 0x04694101, 0x00a520bb, 0xffe88ba8, + 0x0010bc63, 0xffee183b, 0x09b3d77f, 0xe620c476, 0x68c7269b, 0x43de620a, 0x0437fb0a, 0x043889c6, 0x00a1039c, 0xffe940f4, + 0x001471f8, 0xffda17f2, 0x09c0e59f, 0xe4de0cb0, 0x6959709d, 0x4272a385, 0x04b0adcb, 0x04083fec, 0x009d10bf, 0xffe9ca76, + 0x0018703f, 0xffc4e365, 0x09cab9f2, 0xe396a45d, 0x69e29784, 0x41058bc6, 0x05237f9d, 0x03d8afe6, 0x0098b855, 0xffea9192, + 0x001c3549, 0xffaea5d6, 0x09d19ca9, 0xe24b8f66, 0x6a619c5e, 0x3f962fb8, 0x0590a67d, 0x03a966bb, 0x009424c6, 0xffeb50b2, + 0x002064f8, 0xff975c01, 0x09d52709, 0xe0fc421e, 0x6ad73e8d, 0x3e25b17e, 0x05f7fb90, 0x037ad438, 0x008f4bfc, 0xffebe77b, + 0x0024dd50, 0xff7ee3f1, 0x09d5560b, 0xdfa93ab5, 0x6b42a864, 0x3cb41219, 0x06593912, 0x034d01f0, 0x008a7dd7, 0xffecc31b, + 0x00293718, 0xff6542d1, 0x09d1fa23, 0xde529086, 0x6ba4629f, 0x3b415115, 0x06b559c3, 0x03201116, 0x0085c217, 0xffed651d, + 0x002d8e42, 0xff4aabc8, 0x09caeb0f, 0xdcf898fb, 0x6bfbdd98, 0x39ce0477, 0x070bbf58, 0x02f3e48d, 0x00807994, 0xffee1650, + 0x00329ab6, 0xff2ef725, 0x09c018ce, 0xdb9b5b12, 0x6c492217, 0x385a49c4, 0x075ca90c, 0x02c89901, 0x007b3875, 0xffeedfa4, + 0x003745f9, 0xff120d70, 0x09b18a1d, 0xda3b176a, 0x6c8c4c7a, 0x36e69691, 0x07a8127d, 0x029e35b4, 0x0075fded, 0xffef7b8b, + 0x003c1fa4, 0xfef3f6ab, 0x099ec3dc, 0xd8d7f21f, 0x6cc59bab, 0x3572ec70, 0x07ee507c, 0x0274ba43, 0x0070c8a5, 0xffeff6ca, + 0x004103f4, 0xfed4bec3, 0x09881dc5, 0xd7722f04, 0x6cf4073e, 0x33ff670e, 0x082f552e, 0x024bf7a1, 0x006b47fa, 0xfff0065d, + 0x00465348, 0xfeb48d0d, 0x096d0e21, 0xd60a46e5, 0x6d18520e, 0x328cc6f0, 0x086b1eeb, 0x02244a24, 0x0065fde5, 0xffefc9b9, + 0x004b6c46, 0xfe933dc0, 0x094d7ec2, 0xd49fd55f, 0x6d32730f, 0x311af3a4, 0x08a24899, 0x01fd3ba0, 0x006090c4, 0xffed978a, + 0x0050b177, 0xfe70b8d1, 0x09299ead, 0xd3337b3d, 0x6d41d963, 0x2faa221c, 0x08d3e41b, 0x01d78bfc, 0x005b5371, 0xffede50f, +}; + +const HuffInfo_t huffTabScaleFactInfo PROGMEM = + {19, { 1, 0, 1, 3, 2, 4, 3, 5, 4, 6, 6, 6, 5, 8, 4, 7, 3, 7, 46, 0}, 0}; + +/* note - includes offset of -60 (4.6.2.3 in spec) */ +const short huffTabScaleFact[121] PROGMEM = { /* scale factor table [121] */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, 6, -6, 7, -7, 8, + -8, 9, -9, 10, -10, -11, 11, 12, -12, 13, -13, 14, -14, 16, 15, 17, + 18, -15, -17, -16, 19, -18, -19, 20, -20, 21, -21, 22, -22, 23, -23, -25, + 25, -27, -24, -26, 24, -28, 27, 29, -30, -29, 26, -31, -34, -33, -32, -36, + 28, -35, -38, -37, 30, -39, -41, -57, -59, -58, -60, 38, 39, 40, 41, 42, + 57, 37, 31, 32, 33, 34, 35, 36, 44, 51, 52, 53, 54, 55, 56, 50, + 45, 46, 47, 48, 49, 58, -54, -52, -51, -50, -55, 43, 60, 59, -56, -53, + -45, -44, -42, -40, -43, -49, -48, -46, -47, +}; + +/* noise table 4.A.88, format = Q31 */ +const uint32_t noiseTab[512*2] PROGMEM = { + 0x8010fd38, 0xb3dc7948, 0x7c4e2301, 0xa9904192, 0x121622a7, 0x86489625, 0xc3d53d25, 0xd0343fa9, + 0x674d6f70, 0x25f4e9fd, 0xce1a8c8b, 0x72a726c5, 0xfea6efc6, 0xaa4adb1a, 0x8b2dd628, 0xf14029e4, + 0x46321c1a, 0x604889a0, 0x33363b63, 0x815ed069, 0x802b4315, 0x8f2bf7f3, 0x85b86073, 0x745cfb46, + 0xc57886b3, 0xb76731f0, 0xa2a66772, 0x828ca631, 0x60cc145e, 0x1ad1010f, 0x090c83d4, 0x9bd7ba87, + 0x5f5aeea2, 0x8b4dbd99, 0x848e7b1e, 0x86bb9fa2, 0x26f18ae5, 0xc0b81194, 0x553407bf, 0x52c17953, + 0x755f468d, 0x166b04f8, 0xa5687981, 0x4343248b, 0xa6558d5e, 0xc5f6fab7, 0x80a4fb8c, 0x8cb53cb7, + 0x7da68a54, 0x9cd8df8a, 0xba05376c, 0xfcb58ee2, 0xfdd657a4, 0x005e35ca, 0x91c75c55, 0x367651e6, + 0x816abf85, 0x8f831c4f, 0x423f9c9c, 0x55aa919e, 0x80779834, 0xb59f4244, 0x800a095c, 0x7de9e0cc, + 0x46bda5cb, 0x4c184464, 0x2c438f71, 0x797216b5, 0x5035cee6, 0xa0c3a26e, 0x9d3f95fa, 0xd4a100c0, + 0x8ac30dac, 0x04b87397, 0x9e5ac516, 0x8b0b442e, 0x66210ad6, 0x88ba7598, 0x45b9bd33, 0xf0be5087, + 0x9261b85e, 0x364f6a31, 0x891c4b50, 0x23ad08ce, 0xf10366a6, 0x80414276, 0x1b562e06, 0x8be21591, + 0x9e798195, 0x7fb4045c, 0x7d9506cf, 0x854e691f, 0x9207f092, 0x7a94c9d5, 0x88911536, 0x3f45cc61, + 0x27059279, 0xa5b57109, 0x6d2bb67b, 0x3bdc5379, 0x74e662d8, 0x80348f8c, 0xf875e638, 0x5a8caea1, + 0x2459ae75, 0x2c54b939, 0x79ee3203, 0xb9bc8683, 0x9b6f630c, 0x9f45b351, 0x8563b2b9, 0xe5dbba41, + 0x697c7d0d, 0x7bb7c90e, 0xac900866, 0x8e6b5177, 0x8822dd37, 0x7fd5a91e, 0x7506da05, 0x82302aca, + 0xa5e4be04, 0x4b4288eb, 0x00b8bc9f, 0x4f1033e4, 0x7200d612, 0x43900c8c, 0xa815b900, 0x676ed1d4, + 0x5c5f23b2, 0xa758ee11, 0xaf73abfa, 0x11714ec0, 0x265239e0, 0xc50de679, 0x8a84e341, 0xa1438354, + 0x7f1a341f, 0x343ec96b, 0x696e71b0, 0xa13bde39, 0x81e75094, 0x80091111, 0x853a73bf, 0x80f9c1ee, + 0xe4980086, 0x886a8e28, 0xa7e89426, 0xdd93edd7, 0x7592100d, 0x0bfa8123, 0x850a26d4, 0x2e34f395, + 0x421b6c00, 0xa4a462e4, 0x4e3f5090, 0x3c189f4c, 0x3c971a56, 0xdd0376d2, 0x747a5367, 0x7bcbc9d7, + 0x3966be6a, 0x7efda616, 0x55445e15, 0x7ba2ab3f, 0x5fe684f2, 0x8cf42af9, 0x808c61c3, 0x4390c27b, + 0x7cac62ff, 0xea6cab22, 0x5d0902ad, 0xc27b7208, 0x7a27389d, 0x5820a357, 0xa29bbe59, 0x9df0f1fd, + 0x92bd67e5, 0x7195b587, 0x97cac65b, 0x8339807e, 0x8f72d832, 0x5fad8685, 0xa462d9d3, 0x81d46214, + 0x6ae93e1d, 0x6b23a5b9, 0xc2732874, 0x81795268, 0x7c568cb6, 0x668513ea, 0x428d024e, 0x66b78b3a, + 0xfee9ef03, 0x9ddcbb82, 0xa605f07e, 0x46dc55e0, 0x85415054, 0xc89ec271, 0x7c42edfb, 0x0befe59b, + 0x89b8f607, 0x6d732a1a, 0xa7081ebd, 0x7e403258, 0x21feeb7b, 0x5dd7a1e7, 0x23e3a31a, 0x129bc896, + 0xa11a6b54, 0x7f1e031c, 0xfdc1a4d1, 0x96402e53, 0xb9700f1a, 0x8168ecd6, 0x7d63d3cc, 0x87a70d65, + 0x81075a7a, 0x55c8caa7, 0xa95d00b5, 0x102b1652, 0x0bb30215, 0xe5b63237, 0xa446ca44, 0x82d4c333, + 0x67b2e094, 0x44c3d661, 0x33fd6036, 0xde1ea2a1, 0xa95e8e47, 0x78f66eb9, 0x6f2aef1e, 0xe8887247, + 0x80a3b70e, 0xfca0d9d3, 0x6bf0fd20, 0x0d5226de, 0xf4341c87, 0x5902df05, 0x7ff1a38d, 0xf02e5a5b, + 0x99f129af, 0x8ac63d01, 0x7b53f599, 0x7bb32532, 0x99ac59b0, 0x5255a80f, 0xf1320a41, 0x2497aa5c, + 0xcce60bd8, 0x787c634b, 0x7ed58c5b, 0x8a28eb3a, 0x24a5e647, 0x8b79a2c1, 0x955f5ce5, 0xa9d12bc4, + 0x7a1e20c6, 0x3eeda7ac, 0xf7be823a, 0x042924ce, 0x808b3f03, 0x364248da, 0xac2895e5, 0x69a8b5fa, + 0x97fe8b63, 0xbdeac9aa, 0x8073e0ad, 0x6c25dba7, 0x005e51d2, 0x52e74389, 0x59d3988c, 0xe5d1f39c, + 0x7b57dc91, 0x341adbe7, 0xa7d42b8d, 0x74e9f335, 0xd35bf7d8, 0x5b7c0a4b, 0x75bc0874, 0x552129bf, + 0x8144b70d, 0x6de93bbb, 0x5825f14b, 0x473ec5ca, 0x80a8f37c, 0xe6552d69, 0x7898360b, 0x806379b0, + 0xa9b59339, 0x3f6bf60c, 0xc367d731, 0x920ade99, 0x125592f7, 0x877e5ed1, 0xda895d95, 0x075f2ece, + 0x380e5f5e, 0x9b006b62, 0xd17a6dd2, 0x530a0e13, 0xf4cc9a14, 0x7d0a0ed4, 0x847c6e3f, 0xbaee4975, + 0x47131163, 0x64fb2cac, 0x5e2100a6, 0x7b756a42, 0xd87609f4, 0x98bfe48c, 0x0493745e, 0x836c5784, + 0x7e5ccb40, 0x3df6b476, 0x97700d28, 0x8bbd93fd, 0x56de9cdb, 0x680b4e65, 0xebc3d90e, 0x6d286793, + 0x6753712e, 0xe05c98a7, 0x3d2b6b85, 0xc4b18ddb, 0x7b59b869, 0x31435688, 0x811888e9, 0xe011ee7a, + 0x6a5844f9, 0x86ae35ea, 0xb4cbc10b, 0x01a6f5d6, 0x7a49ed64, 0x927caa49, 0x847ddaed, 0xae0d9bb6, + 0x836bdb04, 0x0fd810a6, 0x74fe126b, 0x4a346b5f, 0x80184d36, 0x5afd153c, 0x90cc8102, 0xe606d0e6, + 0xde69aa58, 0xa89f1222, 0xe06df715, 0x8fd16144, 0x0317c3e8, 0x22ce92fc, 0x690c3eca, 0x93166f02, + 0x71573414, 0x8d43cffb, 0xe8bd0bb6, 0xde86770f, 0x0bf99a41, 0x4633a661, 0xba064108, 0x7adafae3, + 0x2f6cde5d, 0xb350a52c, 0xa5ebfb0b, 0x74c57b46, 0xd3b603b5, 0x80b70892, 0xa7f7fa53, 0xd94b566c, + 0xdda3fd86, 0x6a635793, 0x3ed005ca, 0xc5f087d8, 0x31e3a746, 0x7a4278f9, 0x82def1f9, 0x06caa2b2, + 0xe9d2c349, 0x8940e7f7, 0x7feef8dd, 0x4a9b01f0, 0xacde69f8, 0x57ddc280, 0xf09e4ba4, 0xb6d9f729, + 0xb48c18f2, 0xd3654aa9, 0xca7a03c8, 0x14d57545, 0x7fda87a5, 0x0e411366, 0xb77d0df0, 0x8c2aa467, + 0x787f2590, 0x2d292db1, 0x9f12682c, 0x44ac364d, 0x1a4b31a6, 0x871f7ded, 0x7ff99167, 0x6630a1d5, + 0x25385eb9, 0x2d4dd549, 0xaf8a7004, 0x319ebe0f, 0x379ab730, 0x81dc56a4, 0x822d8523, 0x1ae8554c, + 0x18fa0786, 0x875f7de4, 0x85ca350f, 0x7de818dc, 0x7786a38f, 0xa5456355, 0x92e60f88, 0xf5526122, + 0x916039bc, 0xc561e2de, 0x31c42042, 0x7c82e290, 0x75d158b2, 0xb015bda1, 0x7220c750, 0x46565441, + 0xd0da1fdd, 0x7b777481, 0x782e73c6, 0x8cd72b7b, 0x7f1006aa, 0xfb30e51e, 0x87994818, 0x34e7c7db, + 0x7faae06b, 0xea74fbc0, 0xd20c7af4, 0xc44f396b, 0x06b4234e, 0xdf2e2a93, 0x2efb07c8, 0xce861911, + 0x7550ea05, 0xd8d90bbb, 0x58522eec, 0x746b3520, 0xce844ce9, 0x7f5cacc3, 0xda8f17e0, 0x2fedf9cb, + 0xb2f77ec4, 0x6f13f4c0, 0x834de085, 0x7b7ace4b, 0x713b16ac, 0x499c5ab0, 0x06a7961d, 0x1b39a48a, + 0xbb853e6e, 0x7c781cc1, 0xc0baebf5, 0x7dace394, 0x815ceebc, 0xcc7b27d4, 0x8274b181, 0xa2be40a2, + 0xdd01d5dc, 0x7fefeb14, 0x0813ec78, 0xba3077cc, 0xe5cf1e1c, 0xedcfacae, 0x54c43a9b, 0x5cd62a42, + 0x93806b55, 0x03095c5b, 0x8e076ae3, 0x71bfcd2a, 0x7ac1989b, 0x623bc71a, 0x5e15d4d2, 0xfb341dd1, + 0xd75dfbca, 0xd0da32be, 0xd4569063, 0x337869da, 0x3d30606a, 0xcd89cca2, 0x7dd2ae36, 0x028c03cd, + 0xd85e052c, 0xe8dc9ec5, 0x7ffd9241, 0xde5bf4c6, 0x88c4b235, 0x8228be2e, 0x7fe6ec64, 0x996abe6a, + 0xdeb0666d, 0x9eb86611, 0xd249b922, 0x18b3e26b, 0x80211168, 0x5f8bb99c, 0x6ecb0dd2, 0x4728ff8d, + 0x2ac325b8, 0x6e5169d2, 0x7ebbd68d, 0x05e41d17, 0xaaa19f28, 0x8ab238a6, 0x51f105be, 0x140809cc, + 0x7f7345d9, 0x3aae5a9d, 0xaecec6e4, 0x1afb3473, 0xf6229ed1, 0x8d55f467, 0x7e32003a, 0x70f30c14, + 0x6686f33f, 0xd0d45ed8, 0x644fab57, 0x3a3fbbd3, 0x0b255fc4, 0x679a1701, 0x90e17b6e, 0x325d537b, + 0xcd7b9b87, 0xaa7be2a2, 0x7d47c966, 0xa33dbce5, 0x8659c3bb, 0x72a41367, 0x15c446e0, 0x45fe8b0a, + 0x9d8ddf26, 0x84d47643, 0x7fabe0da, 0x36a70122, 0x7a28ebfe, 0x7c29b8b8, 0x7f760406, 0xbabe4672, + 0x23ea216e, 0x92bcc50a, 0x6d20dba2, 0xad5a7c7e, 0xbf3897f5, 0xabb793e1, 0x8391fc7e, 0xe270291c, + 0x7a248d58, 0x80f8fd15, 0x83ef19f3, 0x5e6ece7d, 0x278430c1, 0x35239f4d, 0xe09c073b, 0x50e78cb5, + 0xd4b811bd, 0xce834ee0, 0xf88aaa34, 0xf71da5a9, 0xe2b0a1d5, 0x7c3aef31, 0xe84eabca, 0x3ce25964, + 0xf29336d3, 0x8fa78b2c, 0xa3fc3415, 0x63e1313d, 0x7fbc74e0, 0x7340bc93, 0x49ae583b, 0x8b79de4b, + 0x25011ce9, 0x7b462279, 0x36007db0, 0x3da1599c, 0x77780772, 0xc845c9bb, 0x83ba68be, 0x6ee507d1, + 0x2f0159b8, 0x5392c4ed, 0x98336ff6, 0x0b3c7f11, 0xde697aac, 0x893fc8d0, 0x6b83f8f3, 0x47799a0d, + 0x801d9dfc, 0x8516a83e, 0x5f8d22ec, 0x0f8ba384, 0xa049dc4b, 0xdd920b05, 0x7a99bc9f, 0x9ad19344, + 0x7a345dba, 0xf501a13f, 0x3e58bf19, 0x7fffaf9a, 0x3b4e1511, 0x0e08b991, 0x9e157620, 0x7230a326, + 0x4977f9ff, 0x2d2bbae1, 0x607aa7fc, 0x7bc85d5f, 0xb441bbbe, 0x8d8fa5f2, 0x601cce26, 0xda1884f2, + 0x81c82d64, 0x200b709c, 0xcbd36abe, 0x8cbdddd3, 0x55ab61d3, 0x7e3ee993, 0x833f18aa, 0xffc1aaea, + 0x7362e16a, 0x7fb85db2, 0x904ee04c, 0x7f04dca6, 0x8ad7a046, 0xebe7d8f7, 0xfbc4c687, 0xd0609458, + 0x093ed977, 0x8e546085, 0x7f5b8236, 0x7c47e118, 0xa01f2641, 0x7ffb3e48, 0x05de7cda, 0x7fc281b9, + 0x8e0278fc, 0xd74e6d07, 0x94c24450, 0x7cf9e641, 0x2ad27871, 0x919fa815, 0x805fd205, 0x7758397f, + 0xe2c7e02c, 0x1828e194, 0x5613d6fe, 0xfb55359f, 0xf9699516, 0x8978ee26, 0x7feebad9, 0x77d71d82, + 0x55b28b60, 0x7e997600, 0x80821a6b, 0xc6d78af1, 0x691822ab, 0x7f6982a0, 0x7ef56f99, 0x5c307f40, + 0xac6f8b76, 0x42cc8ba4, 0x782c61d9, 0xa0224dd0, 0x7bd234d1, 0x74576e3b, 0xe38cfe9a, 0x491e66ef, + 0xc78291c5, 0x895bb87f, 0x924f7889, 0x71b89394, 0x757b779d, 0xc4a9c604, 0x5cdf7829, 0x8020e9df, + 0x805e8245, 0x4a82c398, 0x6360bd62, 0x78bb60fc, 0x09e0d014, 0x4b0ea180, 0xb841978b, 0x69a0e864, + 0x7df35977, 0x3284b0dd, 0x3cdc2efd, 0x57d31f5e, 0x541069cc, 0x1776e92e, 0x04309ea3, 0xa015eb2d, + 0xce7bfabc, 0x41b638f8, 0x8365932e, 0x846ab44c, 0xbbcc80cb, 0x8afa6cac, 0x7fc422ea, 0x4e403fc0, + 0xbfac9aee, 0x8e4c6709, 0x028e01fb, 0x6d160a9b, 0x7fe93004, 0x790f9cdc, 0x6a1f37a0, 0xf7e7ef30, + 0xb4ea0f04, 0x7bf4c8e6, 0xe981701f, 0xc258a9d3, 0x6acbbfba, 0xef5479c7, 0x079c8bd8, 0x1a410f56, + 0x6853b799, 0x86cd4f01, 0xc66e23b6, 0x34585565, 0x8d1fe00d, 0x7fcdba1a, 0x32c9717b, 0xa02f9f48, + 0xf64940db, 0x5ed7d8f1, 0x61b823b2, 0x356f8918, 0xa0a7151e, 0x793fc969, 0x530beaeb, 0x34e93270, + 0x4fc4ddb5, 0x88d58b6c, 0x36094774, 0xf620ac80, 0x03763a72, 0xf910c9a6, 0x6666fb2d, 0x752c8be8, + 0x9a6dfdd8, 0xd1a7117d, 0x51c1b1d4, 0x0a67773d, 0x43b32a79, 0x4cdcd085, 0x5f067d30, 0x05bfe92a, + 0x7ed7d203, 0xe71a3c85, 0x99127ce2, 0x8eb3cac4, 0xad4bbcea, 0x5c6a0fd0, 0x0eec04af, 0x94e95cd4, + 0x8654f921, 0x83eabb5d, 0xb058d7ca, 0x69f12d3c, 0x03d881b2, 0x80558ef7, 0x82938cb3, 0x2ec0e1d6, + 0x80044422, 0xd1e47051, 0x720fc6ff, 0x82b20316, 0x0d527b02, 0x63049a15, 0x7ad5b9ad, 0xd2a4641d, + 0x41144f86, 0x7b04917a, 0x15c4a2c0, 0x9da07916, 0x211df54a, 0x7fdd09af, 0xfe924f3f, 0x7e132cfe, + 0x9a1d18d6, 0x7c56508b, 0x80f0f0af, 0x8095ced6, 0x8037d0d7, 0x026719d1, 0xa55fec43, 0x2b1c7cb7, + 0xa5cd5ac1, 0x77639fad, 0x7fcd8b62, 0x81a18c27, 0xaee4912e, 0xeae9eebe, 0xeb3081de, 0x8532aada, + 0xc822362e, 0x86a649a9, 0x8031a71d, 0x7b319dc6, 0xea8022e6, 0x814bc5a9, 0x8f62f7a1, 0xa430ea17, + 0x388deafb, 0x883b5185, 0x776fe13c, 0x801c683f, 0x87c11b98, 0xb7cbc644, 0x8e9ad3e8, 0x3cf5a10c, + 0x7ff6a634, 0x949ef096, 0x9f84aa7c, 0x010af13f, 0x782d1de8, 0xf18e492a, 0x6cf63b01, 0x4301cd81, + 0x32d15c9e, 0x68ad8cef, 0xd09bd2d6, 0x908c5c15, 0xd1e36260, 0x2c5bfdd0, 0x88765a99, 0x93deba1e, + 0xac6ae342, 0xe865b84c, 0x0f4f2847, 0x7fdf0499, 0x78b1c9b3, 0x6a73261e, 0x601a96f6, 0xd2847933, + 0x489aa888, 0xe12e8093, 0x3bfa5a5f, 0xd96ba5f7, 0x7c8f4c8d, 0x80940c6f, 0xcef9dd1a, 0x7e1a055f, + 0x3483558b, 0x02b59cc4, 0x0c56333e, 0x05a5b813, 0x92d66287, 0x7516b679, 0x71bfe03f, 0x8056bf68, + 0xc24d0724, 0x8416bcf3, 0x234afbdb, 0x4b0d6f9c, 0xaba97333, 0x4b4f42b6, 0x7e8343ab, 0x7ffe2603, + 0xe590f73c, 0x45e10c76, 0xb07a6a78, 0xb35609d3, 0x1a027dfd, 0x90cb6e20, 0x82d3fe38, 0x7b409257, + 0x0e395afa, 0x1b802093, 0xcb0c6c59, 0x241e17e7, 0x1ee3ea0a, 0x41a82302, 0xab04350a, 0xf570beb7, + 0xbb444b9b, 0x83021459, 0x838d65dc, 0x1c439c84, 0x6fdcc454, 0xef9ef325, 0x18626c1c, 0x020d251f, + 0xc4aae786, 0x8614cb48, 0xf6f53ca6, 0x8710dbab, 0x89abec0d, 0xf29d41c1, 0x94b50336, 0xfdd49178, + 0x604658d1, 0x800e85be, 0xca1bb079, 0x7fa48eeb, 0xa3b7fafe, 0xd330436b, 0x64eb604c, 0x43a658ae, + 0x7caa1337, 0xddd445e6, 0x7efbf955, 0xb706ec71, 0x624a6b53, 0x9e0e231f, 0x97097248, 0xa1e1a17a, + 0x68dd2e44, 0x7f9d2e14, 0xddcc7074, 0x58324197, 0xc88fc426, 0x6d3640ae, 0x7ef83600, 0x759a0270, + 0x98b6d854, 0xd63c9b84, 0x372474a2, 0xe3f18cfd, 0x56ab0bdb, 0x85c9be7e, 0x47dfcfeb, 0xa5830d41, + 0x0ddd6283, 0xf4f480ad, 0x74c60e38, 0xab8943c3, 0xc1508fe7, 0x480cdc39, 0x8e097362, 0xa44793be, + 0x538b7e18, 0x545f5b41, 0x56529175, 0x9771a97e, 0xc2da7421, 0xea8265f2, 0x805d1163, 0x883c5d28, + 0x8ba94c48, 0x4f676e65, 0xf78735b3, 0xe1853671, 0x7f454f53, 0x18147f85, 0x7d09e15d, 0xdb4f3494, + 0x795c8973, 0x83310632, 0x85d8061c, 0x9a1a0ebf, 0xc125583c, 0x2a1b1a95, 0x7fd9103f, 0x71e98c72, + 0x40932ed7, 0x91ed227a, 0x3c5e560e, 0xe816dee9, 0xb0891b80, 0x600038ba, 0xc7d9a80d, 0x7fff5e09, + 0x7e3f4351, 0xbb6b4424, 0xb14448d4, 0x8d6bb7e1, 0xfb153626, 0xa68ad537, 0xd9782006, 0xf62f6991, + 0x359ba8c1, 0x02ccff0b, 0x91bf2256, 0x7ea71c4d, 0x560ce5df, 0xeeba289b, 0xa574c4e7, 0x9e04f6ee, + 0x7860a5ec, 0x0b8db4a2, 0x968ba3d7, 0x0b6c77df, 0xd6f3157d, 0x402eff1a, 0x49b820b3, 0x8152aebb, + 0xd180b0b6, 0x098604d4, 0x7ff92224, 0xede9c996, 0x89c58061, 0x829624c4, 0xc6e71ea7, 0xba94d915, + 0x389c3cf6, 0x5b4c5a06, 0x04b335e6, 0x516a8aab, 0x42c8d7d9, 0x92b12af6, 0x86c8549f, 0xfda98acf, + 0x819673b6, 0x69545dac, 0x6feaa230, 0x726e6d3f, 0x886ebdfe, 0x34f5730a, 0x7af63ba2, 0x77307bbf, + 0x7cd80630, 0x6e45efe0, 0x7f8ad7eb, 0x59d7df99, 0x86c70946, 0xda233629, 0x753f6cbf, 0x825eeb40, +}; + +/* sample rates (table 4.5.1) */ +const int sampRateTab[12] PROGMEM = { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 +}; + +/* max scalefactor band for prediction (main profile only) */ +const uint8_t predSFBMax[12] PROGMEM = { + 33, 33, 38, 40, 40, 40, 41, 41, 37, 37, 37, 34 +}; + +/* channel mapping (table 1.6.3.4) (-1 = unknown, so need to determine mapping based on rules in 8.5.1) */ +const int8_t channelMapTab[8] PROGMEM = { + -1, 1, 2, 3, 4, 5, 6, 8 +}; + +/* number of channels in each element (SCE, CPE, etc.) + * see AACElementID in aaccommon.h + */ +const uint8_t elementNumChans[8] PROGMEM = { + 1, 2, 0, 1, 0, 0, 0, 0 +}; + +/* total number of scale factor bands in one window */ +const uint8_t /*char*/ sfBandTotalShort[12] PROGMEM = { + 12, 12, 12, 14, 14, 14, 15, 15, 15, 15, 15, 15 +}; + +const uint8_t /*char*/ sfBandTotalLong[12] PROGMEM = { + 41, 41, 47, 49, 49, 51, 47, 47, 43, 43, 43, 40 +}; + +/* scale factor band tables */ +const uint8_t sfBandTabShortOffset[12] PROGMEM = {0, 0, 0, 13, 13, 13, 28, 28, 44, 44, 44, 60}; + +const uint16_t sfBandTabShort[76] PROGMEM = { + /* short block 64, 88, 96 kHz [13] (tables 4.5.24, 4.5.26) */ + 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128, + + /* short block 32, 44, 48 kHz [15] (table 4.5.15) */ + 0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 128, + + /* short block 22, 24 kHz [16] (table 4.5.22) */ + 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 64, 76, 92, 108, 128, + + /* short block 11, 12, 16 kHz [16] (table 4.5.20) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 60, 72, 88, 108, 128, + + /* short block 8 kHz [16] (table 4.5.18) */ + 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 60, 72, 88, 108, 128 +}; + +const uint16_t sfBandTabLongOffset[12] PROGMEM = {0, 0, 42, 90, 90, 140, 192, 192, 240, 240, 240, 284}; + +const uint16_t sfBandTabLong[325] PROGMEM = { + /* long block 88, 96 kHz [42] (table 4.5.25) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, + 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 156, 172, 188, 212, + 240, 276, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, + + /* long block 64 kHz [48] (table 4.5.13) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, + 72, 80, 88, 100, 112, 124, 140, 156, 172, 192, 216, 240, 268, 304, 344, 384, + 424, 464, 504, 544, 584, 624, 664, 704, 744, 784, 824, 864, 904, 944, 984, 1024, + + /* long block 44, 48 kHz [50] (table 4.5.14) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, + 96, 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, + 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 1024, + + /* long block 32 kHz [52] (table 4.5.16) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, + 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, + 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024, + + /* long block 22, 24 kHz [48] (table 4.5.21) */ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 76, + 84, 92, 100, 108, 116, 124, 136, 148, 160, 172, 188, 204, 220, 240, 260, 284, + 308, 336, 364, 396, 432, 468, 508, 552, 600, 652, 704, 768, 832, 896, 960, 1024, + + /* long block 11, 12, 16 kHz [44] (table 4.5.19) */ + 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 100, 112, 124, + 136, 148, 160, 172, 184, 196, 212, 228, 244, 260, 280, 300, 320, 344, 368, + 396, 424, 456, 492, 532, 572, 616, 664, 716, 772, 832, 896, 960, 1024, + + /* long block 8 kHz [41] (table 4.5.17) */ + 0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, + 172, 188, 204, 220, 236, 252, 268, 288, 308, 328, 348, 372, 396, 420, + 448, 476, 508, 544, 580, 620, 664, 712, 764, 820, 880, 944, 1024 +}; + +/* TNS max bands (table 4.139) and max order (table 4.138) */ +const uint8_t tnsMaxBandsShortOffset[3] PROGMEM = {0, 0, 12}; + +const uint16_t tnsMaxBandsShort[2*12] PROGMEM = { + 9, 9, 10, 14, 14, 14, 14, 14, 14, 14, 14, 14, /* short block, Main/LC */ + 7, 7, 7, 6, 6, 6, 7, 7, 8, 8, 8, 7 /* short block, SSR */ +}; + +const uint8_t tnsMaxOrderShort[3] PROGMEM = {7, 7, 7}; + +const uint8_t tnsMaxBandsLongOffset[3] PROGMEM = {0, 0, 12}; + +const uint16_t tnsMaxBandsLong[2*12] PROGMEM = { + 31, 31, 34, 40, 42, 51, 46, 46, 42, 42, 42, 39, /* long block, Main/LC */ + 28, 28, 27, 26, 26, 26, 29, 29, 23, 23, 23, 19, /* long block, SSR */ +}; + +const uint8_t tnsMaxOrderLong[3] PROGMEM = {20, 12, 12}; + + +/* k0Tab[sampRateIdx][k] = k0 = startMin + offset(bs_start_freq) for given sample rate (4.6.18.3.2.1) + * downsampled (single-rate) SBR not currently supported + */ +const uint8_t k0Tab[NUM_SAMPLE_RATES_SBR][16] = { + { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 23, 27, 31 }, /* 96 kHz */ + { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 23, 27, 31 }, /* 88 kHz */ + { 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 23, 26, 30 }, /* 64 kHz */ + { 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 27, 31 }, /* 48 kHz */ + { 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 23, 25, 28, 32 }, /* 44 kHz */ + { 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 32 }, /* 32 kHz */ + { 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 32 }, /* 24 kHz */ + { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 30 }, /* 22 kHz */ + { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }, /* 16 kHz */ +}; + + +/* k2Tab[sampRateIdx][k] = stopVector(bs_stop_freq) for given sample rate, bs_stop_freq = [0, 13] (4.6.18.3.2.1) + * generated with Matlab script calc_stopvec.m + * downsampled (single-rate) SBR not currently supported + */ +const uint8_t k2Tab[NUM_SAMPLE_RATES_SBR][14] = { + { 13, 15, 17, 19, 21, 24, 27, 31, 35, 39, 44, 50, 57, 64 }, /* 96 kHz */ + { 15, 17, 19, 21, 23, 26, 29, 33, 37, 41, 46, 51, 57, 64 }, /* 88 kHz */ + { 20, 22, 24, 26, 28, 31, 34, 37, 41, 45, 49, 54, 59, 64 }, /* 64 kHz */ + { 21, 23, 25, 27, 29, 32, 35, 38, 41, 45, 49, 54, 59, 64 }, /* 48 kHz */ + { 23, 25, 27, 29, 31, 34, 37, 40, 43, 47, 51, 55, 59, 64 }, /* 44 kHz */ + { 32, 34, 36, 38, 40, 42, 44, 46, 49, 52, 55, 58, 61, 64 }, /* 32 kHz */ + { 32, 34, 36, 38, 40, 42, 44, 46, 49, 52, 55, 58, 61, 64 }, /* 24 kHz */ + { 35, 36, 38, 40, 42, 44, 46, 48, 50, 52, 55, 58, 61, 64 }, /* 22 kHz */ + { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64 }, /* 16 kHz */ +}; + +const HuffInfo_t huffTabSBRInfo[10] PROGMEM = { + {19, { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 2, 7, 4, 8, 72, 0}, 0}, + {20, { 0, 2, 2, 2, 2, 2, 1, 3, 3, 2, 4, 4, 4, 3, 2, 5, 6, 13, 15, 46}, 121}, + {17, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 1, 25, 10, 0, 0, 0}, 242}, + {19, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 1, 0, 1, 1, 2, 1, 29, 2, 0}, 291}, + {19, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 2, 5, 1, 4, 2, 3, 34, 0}, 340}, + {20, { 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 1, 2, 3, 4, 4, 7, 10, 16}, 403}, + {14, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 13, 2, 0, 0, 0, 0, 0, 0}, 466}, + {14, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 6, 8, 0, 0, 0, 0, 0, 0}, 491}, + {14, { 1, 1, 1, 1, 1, 1, 0, 2, 0, 1, 1, 0, 51, 2, 0, 0, 0, 0, 0, 0}, 516}, + { 8, { 1, 1, 1, 0, 1, 1, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 579}, +}; + +/* Huffman tables from appendix 4.A.6.1, includes offset of -LAV[i] for table i */ +const short huffTabSBR[604] PROGMEM = { + /* SBR table sbr_tenv15 [121] (signed) */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8, + -9, 8, -10, 9, -11, 10, -12, -13, 11, -14, 12, -15, -16, 13, -19, -18, + -17, 14, -24, -20, 16, -26, -21, 15, -23, -25, -22, -60, -59, -58, -57, -56, + -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, + -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, + /* SBR table sbr_fenv15 [121] (signed) */ + 0, -1, 1, -2, -3, 2, -4, 3, -5, 4, -6, 5, -7, 6, -8, 7, + -9, 8, -10, 9, -11, 10, 11, -12, 12, -13, 13, 14, -14, -15, 15, 16, + 17, -16, -17, -18, -19, 18, 19, -20, -21, 20, 21, -24, -23, -22, -26, -28, + 22, 23, 25, -41, -25, 26, 27, -30, -27, 24, 28, 44, -51, -46, -44, -43, + -37, -33, -31, -29, 30, 37, 42, 47, 48, -60, -59, -58, -57, -56, -55, -54, + -53, -52, -50, -49, -48, -47, -45, -42, -40, -39, -38, -36, -35, -34, -32, 29, + 31, 32, 33, 34, 35, 36, 38, 39, 40, 41, 43, 45, 46, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, + /* SBR table sbr_tenv15b [49] (signed) */ + 0, 1, -1, 2, -2, 3, -3, 4, -4, -5, 5, -6, 6, 7, -7, 8, + -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, + -8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, + /* SBR table sbr_fenv15b [49] (signed) */ + 0, -1, 1, -2, 2, 3, -3, -4, 4, -5, 5, -6, 6, -7, 7, 8, + -9, -8, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, + -10, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, + /* SBR table sbr_tenv30 [63] (signed) */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, -7, 6, -8, 7, + -9, -10, 8, 9, 10, -13, -11, -12, -14, 11, 12, -31, -30, -29, -28, -27, + -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + /* SBR table sbr_fenv30 [63] (signed) */ + 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8, + 8, 9, -9, -10, 10, 11, -11, -12, 12, 13, -13, -15, 14, 15, -14, 18, + -18, -24, -19, 16, 17, -22, -21, -16, 20, 21, 22, 25, -23, -20, 24, -31, + -30, -29, -28, -27, -26, -25, -17, 19, 23, 26, 27, 28, 29, 30, 31, + /* SBR table sbr_tenv30b [25] (signed) */ + 0, 1, -1, -2, 2, 3, -3, -4, 4, -5, -12, -11, -10, -9, -8, -7, + -6, 5, 6, 7, 8, 9, 10, 11, 12, + /* SBR table sbr_fenv30b [25] (signed) */ + 0, -1, 1, -2, 2, 3, -3, -4, 4, -5, 5, 6, -12, -11, -10, -9, + -8, -7, -6, 7, 8, 9, 10, 11, 12, + /* SBR table sbr_tnoise30 [63] (signed) */ + 0, 1, -1, -2, 2, -3, 3, -4, 4, -5, 5, 11, -31, -30, -29, -28, + -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, + -11, -10, -9, -8, -7, -6, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + /* SBR table sbr_tnoise30b [25] (signed) */ + 0, -1, 1, -2, 2, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 12, +}; + +/* newBWTab[prev invfMode][curr invfMode], format = Q31 (table 4.158) + * sample file which uses all of these: al_sbr_sr_64_2_fsaac32.aac + */ +static const int newBWTab[4][4] PROGMEM = { + {0x00000000, 0x4ccccccd, 0x73333333, 0x7d70a3d7}, + {0x4ccccccd, 0x60000000, 0x73333333, 0x7d70a3d7}, + {0x00000000, 0x60000000, 0x73333333, 0x7d70a3d7}, + {0x00000000, 0x60000000, 0x73333333, 0x7d70a3d7}, +}; + +/* NINT(2.048E6 / Fs) (figure 4.47) + * downsampled (single-rate) SBR not currently supported + */ +const uint8_t goalSBTab[NUM_SAMPLE_RATES_SBR] = { + 21, 23, 32, 43, 46, 64, 85, 93, 128 +}; + +/* twiddle table for radix 4 pass, format = Q31 */ +static const uint32_t twidTabOdd32[8*6] = { + 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x539eba45, 0xe7821d59, + 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, + 0x539eba45, 0xc4df2862, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, + 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x22a2f4f8, 0xc4df2862, + 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, + 0xac6145bb, 0x187de2a7, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, +}; + +/* PostMultiply64() table + * format = Q30 + * reordered for sequential access + * + * for (i = 0; i <= (32/2); i++) { + * angle = i * M_PI / 64; + * x = (cos(angle) + sin(angle)); + * x = sin(angle); + * } + */ +static const int cos1sin1tab64[34] PROGMEM = { + 0x40000000, 0x00000000, 0x43103085, 0x0323ecbe, 0x45f704f7, 0x0645e9af, 0x48b2b335, 0x09640837, + 0x4b418bbe, 0x0c7c5c1e, 0x4da1fab5, 0x0f8cfcbe, 0x4fd288dc, 0x1294062f, 0x51d1dc80, 0x158f9a76, + 0x539eba45, 0x187de2a7, 0x553805f2, 0x1b5d100a, 0x569cc31b, 0x1e2b5d38, 0x57cc15bc, 0x20e70f32, + 0x58c542c5, 0x238e7673, 0x5987b08a, 0x261feffa, 0x5a12e720, 0x2899e64a, 0x5a6690ae, 0x2afad269, + 0x5a82799a, 0x2d413ccd, +}; + +/* coefficient table 4.A.87, format = Q31 + * reordered as: + * cTab[0], cTab[64], cTab[128], cTab[192], cTab[256], + * cTab[2], cTab[66], cTab[130], cTab[194], cTab[258], + * ... + * cTab[64], cTab[128], cTab[192], cTab[256], cTab[320] + * + * NOTE: cTab[1, 2, ... , 318, 319] = cTab[639, 638, ... 322, 321] + * except cTab[384] = -cTab[256], cTab[512] = -cTab[128] + */ +const uint32_t cTabA[165] PROGMEM = { + 0x00000000, 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0xffed978a, 0x006090c4, 0x01fd3ba0, 0x08a24899, 0x311af3a4, + 0xfff0065d, 0x006b47fa, 0x024bf7a1, 0x082f552e, 0x33ff670e, 0xffef7b8b, 0x0075fded, 0x029e35b4, 0x07a8127d, 0x36e69691, + 0xffee1650, 0x00807994, 0x02f3e48d, 0x070bbf58, 0x39ce0477, 0xffecc31b, 0x008a7dd7, 0x034d01f0, 0x06593912, 0x3cb41219, + 0xffeb50b2, 0x009424c6, 0x03a966bb, 0x0590a67d, 0x3f962fb8, 0xffe9ca76, 0x009d10bf, 0x04083fec, 0x04b0adcb, 0x4272a385, + 0xffe88ba8, 0x00a520bb, 0x04694101, 0x03b8f8dc, 0x4547daea, 0xffe79e16, 0x00abe79e, 0x04cc2fcf, 0x02a99097, 0x4812f848, + 0xffe6d466, 0x00b1978d, 0x05303f87, 0x01816e06, 0x4ad237a2, 0xffe65416, 0x00b5c867, 0x05950122, 0x0040c496, 0x4d83976c, + 0xffe66dd0, 0x00b8394b, 0x05f9c051, 0xfee723c6, 0x5024d70e, 0xffe69423, 0x00b8c6b0, 0x065dd56a, 0xfd7475d8, 0x52b449de, + 0xffe75361, 0x00b73ab0, 0x06c0f0c0, 0xfbe8f5bd, 0x552f8ff7, 0xffe85b4b, 0x00b36acd, 0x0721bf22, 0xfa44a069, 0x579505f5, + 0xffea353a, 0x00acbd2f, 0x077fedb3, 0xf887507c, 0x59e2f69e, 0xffec8409, 0x00a3508f, 0x07da2b7f, 0xf6b1f3c3, 0x5c16d0ae, + 0xffef2395, 0x0096dcc2, 0x08303897, 0xf4c473c6, 0x5e2f6367, 0xfff294c3, 0x00872c63, 0x0880ffdd, 0xf2bf6ea4, 0x602b0c7f, + 0xfff681d6, 0x007400b8, 0x08cb4e23, 0xf0a3959f, 0x6207f220, 0xfffb42b0, 0x005d36df, 0x090ec1fc, 0xee71b2fe, 0x63c45243, + 0x00007134, 0x00426f36, 0x0949eaac, 0xec2a3f5f, 0x655f63f2, 0x0006b1cf, 0x0023b989, 0x097c1ee8, 0xe9cea84a, 0x66d76725, + 0x000d31b5, 0x0000e790, 0x09a3e163, 0xe75f8bb8, 0x682b39a4, 0x001471f8, 0xffda17f2, 0x09c0e59f, 0xe4de0cb0, 0x6959709d, + 0x001c3549, 0xffaea5d6, 0x09d19ca9, 0xe24b8f66, 0x6a619c5e, 0x0024dd50, 0xff7ee3f1, 0x09d5560b, 0xdfa93ab5, 0x6b42a864, + 0x002d8e42, 0xff4aabc8, 0x09caeb0f, 0xdcf898fb, 0x6bfbdd98, 0x003745f9, 0xff120d70, 0x09b18a1d, 0xda3b176a, 0x6c8c4c7a, + 0x004103f4, 0xfed4bec3, 0x09881dc5, 0xd7722f04, 0x6cf4073e, 0x004b6c46, 0xfe933dc0, 0x094d7ec2, 0xd49fd55f, 0x6d32730f, + 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0x6d474e1d, +}; + +/* PreMultiply64() table + * format = Q30 + * reordered for sequential access + * + * for (i = 0; i < 64/4; i++) { + * angle = (i + 0.25) * M_PI / nmdct; + * x = (cos(angle) + sin(angle)); + * x = sin(angle); + * + * angle = (nmdct/2 - 1 - i + 0.25) * M_PI / nmdct; + * x = (cos(angle) + sin(angle)); + * x = sin(angle); + * } + */ +static const int cos4sin4tab64[64] PROGMEM = { + 0x40c7d2bd, 0x00c90e90, 0x424ff28f, 0x3ff4e5e0, 0x43cdd89a, 0x03ecadcf, 0x454149fc, 0x3fc395f9, + 0x46aa0d6d, 0x070de172, 0x4807eb4b, 0x3f6af2e3, 0x495aada2, 0x0a2abb59, 0x4aa22036, 0x3eeb3347, + 0x4bde1089, 0x0d415013, 0x4d0e4de2, 0x3e44a5ef, 0x4e32a956, 0x104fb80e, 0x4f4af5d1, 0x3d77b192, + 0x50570819, 0x135410c3, 0x5156b6d9, 0x3c84d496, 0x5249daa2, 0x164c7ddd, 0x53304df6, 0x3b6ca4c4, + 0x5409ed4b, 0x19372a64, 0x54d69714, 0x3a2fcee8, 0x55962bc0, 0x1c1249d8, 0x56488dc5, 0x38cf1669, + 0x56eda1a0, 0x1edc1953, 0x57854ddd, 0x374b54ce, 0x580f7b19, 0x2192e09b, 0x588c1404, 0x35a5793c, + 0x58fb0568, 0x2434f332, 0x595c3e2a, 0x33de87de, 0x59afaf4c, 0x26c0b162, 0x59f54bee, 0x31f79948, + 0x5a2d0957, 0x29348937, 0x5a56deec, 0x2ff1d9c7, 0x5a72c63b, 0x2b8ef77d, 0x5a80baf6, 0x2dce88aa, +}; + +/* invBandTab[i] = 1.0 / (i + 1), Q31 */ +static const int invBandTab[64] PROGMEM = { + 0x7fffffff, 0x40000000, 0x2aaaaaab, 0x20000000, 0x1999999a, 0x15555555, 0x12492492, 0x10000000, + 0x0e38e38e, 0x0ccccccd, 0x0ba2e8ba, 0x0aaaaaab, 0x09d89d8a, 0x09249249, 0x08888889, 0x08000000, + 0x07878788, 0x071c71c7, 0x06bca1af, 0x06666666, 0x06186186, 0x05d1745d, 0x0590b216, 0x05555555, + 0x051eb852, 0x04ec4ec5, 0x04bda12f, 0x04924925, 0x0469ee58, 0x04444444, 0x04210842, 0x04000000, + 0x03e0f83e, 0x03c3c3c4, 0x03a83a84, 0x038e38e4, 0x03759f23, 0x035e50d8, 0x03483483, 0x03333333, + 0x031f3832, 0x030c30c3, 0x02fa0be8, 0x02e8ba2f, 0x02d82d83, 0x02c8590b, 0x02b93105, 0x02aaaaab, + 0x029cbc15, 0x028f5c29, 0x02828283, 0x02762762, 0x026a439f, 0x025ed098, 0x0253c825, 0x02492492, + 0x023ee090, 0x0234f72c, 0x022b63cc, 0x02222222, 0x02192e2a, 0x02108421, 0x02082082, 0x02000000, +}; + +static const uint32_t poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 }; +static const uint32_t poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 }; + +/* pow2exp[i] = pow(2, i*4/3) exponent */ +static const uint16_t pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 }; + +/* pow2exp[i] = pow(2, i*4/3) fraction */ +static const int pow2frac[8] PROGMEM = { + 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94, + 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6 +}; + +/* pow(2, i/4.0) for i = [0,1,2,3], format = Q30 */ +static const int pow14[4] PROGMEM = { + 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 +}; + +/* pow(2, i/4.0) * pow(j, 4.0/3.0) for i = [0,1,2,3], j = [0,1,2,...,15] + * format = Q28 for j = [0-3], Q25 for j = [4-15] + */ +static const uint32_t pow43_14[4][16] PROGMEM = { + { + 0x00000000, 0x10000000, 0x285145f3, 0x453a5cdb, /* Q28 */ + 0x0cb2ff53, 0x111989d6, 0x15ce31c8, 0x1ac7f203, /* Q25 */ + 0x20000000, 0x257106b9, 0x2b16b4a3, 0x30ed74b4, /* Q25 */ + 0x36f23fa5, 0x3d227bd3, 0x437be656, 0x49fc823c, /* Q25 */ + }, + { + 0x00000000, 0x1306fe0a, 0x2ff221af, 0x52538f52, + 0x0f1a1bf4, 0x1455ccc2, 0x19ee62a8, 0x1fd92396, + 0x260dfc14, 0x2c8694d8, 0x333dcb29, 0x3a2f5c7a, + 0x4157aed5, 0x48b3aaa3, 0x50409f76, 0x57fc3010, + }, + { + 0x00000000, 0x16a09e66, 0x39047c0f, 0x61e734aa, + 0x11f59ac4, 0x182ec633, 0x1ed66a45, 0x25dfc55a, + 0x2d413ccd, 0x34f3462d, 0x3cefc603, 0x4531ab69, + 0x4db4adf8, 0x56752054, 0x5f6fcfcd, 0x68a1eca1, + }, + { + 0x00000000, 0x1ae89f99, 0x43ce3e4b, 0x746d57b2, + 0x155b8109, 0x1cc21cdc, 0x24ac1839, 0x2d0a479e, + 0x35d13f33, 0x3ef80748, 0x48775c93, 0x524938cd, + 0x5c68841d, 0x66d0df0a, 0x717e7bfe, 0x7c6e0305, + }, +}; + +/* pow(j, 4.0 / 3.0) for j = [16,17,18,...,63], format = Q23 */ +static const int pow43[48] PROGMEM = { + 0x1428a2fa, 0x15db1bd6, 0x1796302c, 0x19598d85, + 0x1b24e8bb, 0x1cf7fcfa, 0x1ed28af2, 0x20b4582a, + 0x229d2e6e, 0x248cdb55, 0x26832fda, 0x28800000, + 0x2a832287, 0x2c8c70a8, 0x2e9bc5d8, 0x30b0ff99, + 0x32cbfd4a, 0x34eca001, 0x3712ca62, 0x393e6088, + 0x3b6f47e0, 0x3da56717, 0x3fe0a5fc, 0x4220ed72, + 0x44662758, 0x46b03e7c, 0x48ff1e87, 0x4b52b3f3, + 0x4daaebfd, 0x5007b497, 0x5268fc62, 0x54ceb29c, + 0x5738c721, 0x59a72a59, 0x5c19cd35, 0x5e90a129, + 0x610b9821, 0x638aa47f, 0x660db90f, 0x6894c90b, + 0x6b1fc80c, 0x6daeaa0d, 0x70416360, 0x72d7e8b0, + 0x75722ef9, 0x78102b85, 0x7ab1d3ec, 0x7d571e09, +}; + +/* invTab[x] = 1/(x+1), format = Q30 */ +static const int invTab[5] PROGMEM = {0x40000000, 0x20000000, 0x15555555, 0x10000000, 0x0ccccccd}; + +/* inverse quantization tables for TNS filter coefficients, format = Q31 + * see bottom of file for table generation + * negative (vs. spec) since we use MADD for filter kernel + */ +static const uint32_t invQuant3[16] PROGMEM = { + 0x00000000, 0xc8767f65, 0x9becf22c, 0x83358feb, 0x83358feb, 0x9becf22c, 0xc8767f65, 0x00000000, + 0x2bc750e9, 0x5246dd49, 0x6ed9eba1, 0x7e0e2e32, 0x7e0e2e32, 0x6ed9eba1, 0x5246dd49, 0x2bc750e9, +}; + +static const uint32_t invQuant4[16] PROGMEM = { + 0x00000000, 0xe5632654, 0xcbf00dbe, 0xb4c373ee, 0xa0e0a15f, 0x9126145f, 0x8643c7b3, 0x80b381ac, + 0x7f7437ad, 0x7b1d1a49, 0x7294b5f2, 0x66256db2, 0x563ba8aa, 0x4362210e, 0x2e3d2abb, 0x17851aad, +}; + +static const int8_t sgnMask[3] = {0x02, 0x04, 0x08}; +static const int8_t negMask[3] = {~0x03, ~0x07, ~0x0f}; + +/*********************************************************************************************************************** + * Function: AACDecoder_AllocateBuffers + * + * Description: allocate all the memory needed for the AAC decoder + * try heap first, because it's faster + * + * Inputs: none + * + * Outputs: none + * + * Return: false if not enough memory, otherwise true + * + **********************************************************************************************************************/ +bool AACDecoder_AllocateBuffers(void){ + + if(!m_AACDecInfo) {m_AACDecInfo = (AACDecInfo_t*) malloc(sizeof(AACDecInfo_t));} + if(!m_PSInfoBase) {m_PSInfoBase = (PSInfoBase_t*) malloc(sizeof(PSInfoBase_t));} + if(!m_pce[0]) {m_pce[0] = (ProgConfigElement_t*) malloc(sizeof(ProgConfigElement_t)*16);} + + if(!m_AACDecInfo || !m_PSInfoBase || !m_pce[0]) { + log_i("heap is too small, try PSRAM"); + AACDecoder_FreeBuffers(); + } + else{ + goto nextStep; + } + + if(psramFound()) { + // PSRAM found, Buffer will be allocated in PSRAM + if(!m_AACDecInfo) {m_AACDecInfo = (AACDecInfo_t*) ps_calloc(sizeof(AACDecInfo_t), sizeof(uint8_t));} + if(!m_PSInfoBase) {m_PSInfoBase = (PSInfoBase_t*) ps_calloc(sizeof(PSInfoBase_t), sizeof(uint8_t));} + if(!m_pce[0]) {m_pce[0] = (ProgConfigElement_t*) ps_calloc(sizeof(ProgConfigElement_t)*16, sizeof(uint8_t));} + } + + if(!m_AACDecInfo || !m_PSInfoBase || !m_pce[0]) { + log_e("not enough memory to allocate aacdecoder buffers"); + AACDecoder_FreeBuffers(); + return false; + } + log_i("AAC buffers allocated in PSRAM"); + + nextStep: + + +#ifdef AAC_ENABLE_SBR + // can't allocated in PSRAM, because PSRAM ist too slow + if(!m_PSInfoSBR) {m_PSInfoSBR = (PSInfoSBR_t*)malloc(sizeof(PSInfoSBR_t));} + + if(!m_PSInfoSBR) { + log_e("OOM in SBR, can't allocate %d bytes\n", sizeof(PSInfoSBR_t)); + return false; // ERR_AAC_SBR_INIT; + } +#endif + + + // Clear Buffer + memset( m_AACDecInfo, 0, sizeof(AACDecInfo_t)); //Clear AACDecInfo + memset( m_PSInfoBase, 0, sizeof(PSInfoBase_t)); //Clear PSInfoBase + memset(&m_AACFrameInfo, 0, sizeof(AACFrameInfo_t)); //Clear AACFrameInfo + memset(&m_fhADTS, 0, sizeof(ADTSHeader_t)); //Clear fhADTS + memset(&m_fhADIF, 0, sizeof(ADIFHeader_t)); //Clear fhADIS + memset( m_pce[0], 0, sizeof(ProgConfigElement_t) * 16); //Clear ProgConfigElement + memset(&m_pulseInfo[0], 0, sizeof(PulseInfo_t) *2); //Clear PulseInfo + memset(&m_aac_BitStreamInfo, 0, sizeof(aac_BitStreamInfo_t)); //Clear aac_BitStreamInfo +#ifdef AAC_ENABLE_SBR + memset( m_PSInfoSBR, 0, sizeof(PSInfoSBR_t)); //Clear PSInfoSBR + InitSBRState(); +#endif + + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + for(int ch = 0; ch < MAX_NCHANS_ELEM; ch++) + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + m_AACDecInfo->adtsBlocksLeft = 0; + m_AACDecInfo->tnsUsed = 0; + m_AACDecInfo->pnsUsed = 0; + + return true; +} + +/************************************************************************************** + * Function: AACFlushCodec + * + * Description: flush internal codec state (after seeking, for example) + * + * Inputs: valid AAC decoder instance pointer (HAACDecoder) + * + * Outputs: updated state variables in aacDecInfo + * + * Return: 0 if successful, error code (< 0) if error + **************************************************************************************/ +int AACFlushCodec() +{ + int ch; + + if (!m_AACDecInfo) + return ERR_AAC_NULL_POINTER; + + /* reset common state variables which change per-frame + * don't touch state variables which are (usually) constant for entire clip + * (nChans, sampRate, profile, format, sbrEnabled) + */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + for (ch = 0; ch < MAX_NCHANS_ELEM; ch++) + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + m_AACDecInfo->adtsBlocksLeft = 0; + m_AACDecInfo->tnsUsed = 0; + m_AACDecInfo->pnsUsed = 0; + + /* reset internal codec state (flush overlap buffers, etc.) */ + memset(m_PSInfoBase->overlap, 0, AAC_MAX_NCHANS * AAC_MAX_NSAMPS * sizeof(int)); + memset(m_PSInfoBase->prevWinShape, 0, AAC_MAX_NCHANS * sizeof(int)); + + return ERR_AAC_NONE; +} +/*********************************************************************************************************************** + * Function: AACDecoder_FreeBuffers + * + * Description: allocate all the memory needed for the AAC decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: none + + **********************************************************************************************************************/ +void AACDecoder_FreeBuffers(void) { + +// uint32_t i = ESP.getFreeHeap(); + + if(m_AACDecInfo) {free(m_AACDecInfo); m_AACDecInfo=NULL;} + if(m_PSInfoBase) {free(m_PSInfoBase); m_PSInfoBase=NULL;} + if(m_pce[0]) {for(int i=0; i<16; i++) free(m_pce[i]); m_pce[0]=NULL;} + +#ifdef AAC_ENABLE_SBR + if(m_PSInfoSBR) {free(m_PSInfoSBR); m_PSInfoSBR=NULL;} //Clear AACDecInfo +#endif + +// log_i("AACDecoder: %lu bytes memory was freed", ESP.getFreeHeap() - i); +} + +/*********************************************************************************************************************** + * Function: AACDecoder_IsInit + * + * Description: returns AAC decoder initialization status + * + * Inputs: none + * + * Outputs: none + * + * Return: true if buffers allocated, otherwise false + + **********************************************************************************************************************/ +bool AACDecoder_IsInit(void) { + if(m_AACDecInfo && m_PSInfoBase && m_pce[0]){ + return true; + } + return false; +} + +/*********************************************************************************************************************** + * Function: AACDecoder_FreeBuffers + * + * Description: allocate all the memory needed for the AAC decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: none + + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: AACFindSyncWord + * + * Description: locate the next byte-alinged sync word in the raw AAC stream + * + * Inputs: buffer to search for sync word + * max number of bytes to search in buffer + * + * Outputs: none + * + * Return: offset to first sync word (bytes from start of buf) + * -1 if sync not found after searching nBytes + **********************************************************************************************************************/ +int AACFindSyncWord(uint8_t *buf, int nBytes) +{ + int i; + + /* find byte-aligned syncword (12 bits = 0xFFF) */ + for (i = 0; i < nBytes - 1; i++) { + if ( (buf[i+0] & SYNCWORDH) == SYNCWORDH && (buf[i+1] & SYNCWORDL) == SYNCWORDL ) + return i; + } + + return -1; +} +//************************************************************************************** +int AACGetSampRate(){return m_AACDecInfo->sampRate * (m_AACDecInfo->sbrEnabled ? 2 : 1);} +int AACGetChannels(){return m_AACDecInfo->nChans;} +int AACGetBitsPerSample(){return 16;} +int AACGetID() {return m_AACDecInfo->id;} // 0-MPEG4, 1-MPEG2 +uint8_t AACGetProfile() {return (uint8_t)m_AACDecInfo->profile;} // 0-Main, 1-LC, 2-SSR, 3-reserved +uint8_t AACGetFormat() {return (uint8_t)m_AACDecInfo->format;} // 0-unknown 1-ADTS 2-ADIF, 3-RAW +int AACGetOutputSamps(){return m_AACDecInfo->nChans * AAC_MAX_NSAMPS * (m_AACDecInfo->sbrEnabled ? 2 : 1);} +int AACGetBitrate() { + uint32_t br = AACGetBitsPerSample() * AACGetChannels() * AACGetSampRate(); + return (br / m_AACDecInfo->compressionRatio); +} +/************************************************************************************** + * Function: AACSetRawBlockParams + * + * Description: set internal state variables for decoding a stream of raw data blocks + * + * Inputs: flag indicating source of parameters + * nChans, sampRate, + * and profile 0 = main, 1 = LC, 2 = SSR, 3 = reserved + * optionally filled-in + * + * Outputs: updated codec state + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: if copyLast == 1, then the codec sets up its internal state (for + * decoding raw blocks) based on previously-decoded ADTS header info + * if copyLast == 0, then the codec uses the values passed in + * aacFrameInfo to configure its internal state (useful when the + * source is MP4 format, for example) + **************************************************************************************/ +int AACSetRawBlockParams(int copyLast, int nChans, int sampRateCore, int profile) +{ + if (!m_AACDecInfo) + return ERR_AAC_NULL_POINTER; + + m_AACDecInfo->format = AAC_FF_RAW; + if (copyLast) + return SetRawBlockParams(1, 0, 0, 0); + else + return SetRawBlockParams(0, nChans, sampRateCore, profile); +} + +/*********************************************************************************************************************** + * Function: AACDecode + * + * Description: decode AAC frame + * + * Inputs: double pointer to buffer of AAC data + * pointer to number of valid bytes remaining in inbuf + * pointer to outbuf, big enough to hold one frame of decoded PCM samples + * + * Outputs: PCM data in outbuf, interleaved LRLRLR... if stereo + * number of output samples = 1024 per channel + * updated inbuf pointer + * updated bytesLeft + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: inbuf pointer and bytesLeft are not updated until whole frame is + * successfully decoded, so if ERR_AAC_INDATA_UNDERFLOW is returned + * just call AACDecode again with more data in inbuf + **********************************************************************************************************************/ +int AACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf) +{ + int err, offset, bitOffset, bitsAvail; + int ch, baseChan, elementChans; + uint8_t *inptr; + +#ifdef AAC_ENABLE_SBR + int baseChanSBR, elementChansSBR; +#endif + + /* make local copies (see "Notes" above) */ + inptr = inbuf; + bitOffset = 0; + bitsAvail = (*bytesLeft) << 3; + + /* first time through figure out what the file format is */ + if (m_AACDecInfo->format == AAC_FF_Unknown) { + if (bitsAvail < 32) + return ERR_AAC_INDATA_UNDERFLOW; + + if ((inptr)[0] == 'A' && (inptr)[1] == 'D' && (inptr)[2] == 'I' && (inptr)[3] == 'F') { + /* unpack ADIF header */ + m_AACDecInfo->format = AAC_FF_ADIF; + err = UnpackADIFHeader(&inptr, &bitOffset, &bitsAvail); + if (err) + return err; + } else { + /* assume ADTS by default */ + m_AACDecInfo->format = AAC_FF_ADTS; + } + } + /* if ADTS, search for start of next frame */ + if (m_AACDecInfo->format == AAC_FF_ADTS) { + /* can have 1-4 raw data blocks per ADTS frame (header only present for first one) */ + if (m_AACDecInfo->adtsBlocksLeft == 0) { + offset = AACFindSyncWord(inptr, bitsAvail >> 3); + if (offset < 0) + return ERR_AAC_INDATA_UNDERFLOW; + inptr += offset; + bitsAvail -= (offset << 3); + + err = UnpackADTSHeader(&inptr, &bitOffset, &bitsAvail); + if (err) + return err; + + if (m_AACDecInfo->nChans == -1) { + /* figure out implicit channel mapping if necessary */ + err = GetADTSChannelMapping(inptr, bitOffset, bitsAvail); + if (err) + return err; + } + } + m_AACDecInfo->adtsBlocksLeft--; + } else if (m_AACDecInfo->format == AAC_FF_RAW) { + err = PrepareRawBlock(); + if (err) + return err; + } + + /* check for valid number of channels */ + if (m_AACDecInfo->nChans > AAC_MAX_NCHANS || m_AACDecInfo->nChans <= 0) + return ERR_AAC_NCHANS_TOO_HIGH; + + /* will be set later if active in this frame */ + m_AACDecInfo->tnsUsed = 0; + m_AACDecInfo->pnsUsed = 0; + + bitOffset = 0; + baseChan = 0; + +#ifdef AAC_ENABLE_SBR + baseChanSBR = 0; +#endif + + do { + /* parse next syntactic element */ + err = DecodeNextElement(&inptr, &bitOffset, &bitsAvail); + if (err) + return err; + + elementChans = elementNumChans[m_AACDecInfo->currBlockID]; + if (baseChan + elementChans > AAC_MAX_NCHANS) + return ERR_AAC_NCHANS_TOO_HIGH; + + /* noiseless decoder and dequantizer */ + for (ch = 0; ch < elementChans; ch++) { + err = DecodeNoiselessData(&inptr, &bitOffset, &bitsAvail, ch); + + if (err) + return err; + + if (AACDequantize(ch)) + return ERR_AAC_DEQUANT; + } + + /* mid-side and intensity stereo */ + if (m_AACDecInfo->currBlockID == AAC_ID_CPE) { + if (StereoProcess()) + return ERR_AAC_STEREO_PROCESS; + } + + /* PNS, TNS, inverse transform */ + for (ch = 0; ch < elementChans; ch++) { + + if (PNS(ch)) + return ERR_AAC_PNS; + + if (m_AACDecInfo->sbDeinterleaveReqd[ch]) { + /* deinterleave short blocks, if required */ + if (DeinterleaveShortBlocks(ch)) + return ERR_AAC_SHORT_BLOCK_DEINT; + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + } + + if (TNSFilter(ch)) + return ERR_AAC_TNS; + + if (IMDCT(ch, baseChan + ch, outbuf)) + return ERR_AAC_IMDCT; + } + +#ifdef AAC_ENABLE_SBR + if (m_AACDecInfo->sbrEnabled && (m_AACDecInfo->currBlockID == AAC_ID_FIL || + m_AACDecInfo->currBlockID == AAC_ID_LFE)) { + if (m_AACDecInfo->currBlockID == AAC_ID_LFE) + elementChansSBR = elementNumChans[AAC_ID_LFE]; + else if (m_AACDecInfo->currBlockID == AAC_ID_FIL && (m_AACDecInfo->prevBlockID == AAC_ID_SCE || + m_AACDecInfo->prevBlockID == AAC_ID_CPE)) + elementChansSBR = elementNumChans[m_AACDecInfo->prevBlockID]; + else + elementChansSBR = 0; + + if (baseChanSBR + elementChansSBR > AAC_MAX_NCHANS) + return ERR_AAC_SBR_NCHANS_TOO_HIGH; + + /* parse SBR extension data if present (contained in a fill element) */ + if (DecodeSBRBitstream(baseChanSBR)) + return ERR_AAC_SBR_BITSTREAM; + + /* apply SBR */ + if (DecodeSBRData(baseChanSBR, outbuf)) + return ERR_AAC_SBR_DATA; + + baseChanSBR += elementChansSBR; + } +#endif + + baseChan += elementChans; + } while (m_AACDecInfo->currBlockID != AAC_ID_END); + + /* byte align after each raw_data_block */ + if (bitOffset) { + inptr++; + bitsAvail -= (8-bitOffset); + bitOffset = 0; + if (bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + } + + m_AACDecInfo->compressionRatio = (float)(AACGetOutputSamps()) * 2 / (inptr - inbuf); + + /* update pointers */ + m_AACDecInfo->frameCount++; + *bytesLeft -= (inptr - inbuf); + inbuf = inptr; + + return ERR_AAC_NONE; +} +/*********************************************************************************************************************** + * Function: DecodeLPCCoefs + * + * Description: decode LPC coefficients for TNS + * + * Inputs: order of TNS filter + * resolution of coefficients (3 or 4 bits) + * coefficients unpacked from bitstream + * scratch buffer (b) of size >= order + * + * Outputs: LPC coefficients in Q(FBITS_LPC_COEFS), in 'a' + * + * Return: none + * + * Notes: assumes no guard bits in input transform coefficients + * a[i] = Q(FBITS_LPC_COEFS), don't store a0 = 1.0 + * (so a[0] = first delay tap, etc.) + * max abs(a[i]) < log2(order), so for max order = 20 a[i] < 4.4 + * (up to 3 bits of gain) so a[i] has at least 31 - FBITS_LPC_COEFS - 3 + * guard bits + * to ensure no intermediate overflow in all-pole filter, set + * FBITS_LPC_COEFS such that number of guard bits >= log2(max order) + **********************************************************************************************************************/ +void DecodeLPCCoefs(int order, int res, int8_t *filtCoef, int *a, int *b) +{ + int i, m, t; + const uint32_t *invQuantTab; + + if (res == 3) invQuantTab = invQuant3; + else if (res == 4) invQuantTab = invQuant4; + else return; + + for (m = 0; m < order; m++) { + t = invQuantTab[filtCoef[m] & 0x0f]; /* t = Q31 */ + for (i = 0; i < m; i++) + b[i] = a[i] - (MULSHIFT32(t, a[m-i-1]) << 1); + for (i = 0; i < m; i++) + a[i] = b[i]; + a[m] = t >> (31 - FBITS_LPC_COEFS); + } +} + +/*********************************************************************************************************************** + * Function: FilterRegion + * + * Description: apply LPC filter to one region of coefficients + * + * Inputs: number of transform coefficients in this region + * direction flag (forward = 1, backward = -1) + * order of filter + * 'size' transform coefficients + * 'order' LPC coefficients in Q(FBITS_LPC_COEFS) + * scratch buffer for history (must be >= order samples long) + * + * Outputs: filtered transform coefficients + * + * Return: guard bit mask (OR of abs value of all filtered transform coefs) + * + * Notes: assumes no guard bits in input transform coefficients + * gains 0 int bits + * history buffer does not need to be preserved between regions + **********************************************************************************************************************/ +int FilterRegion(int size, int dir, int order, int *audioCoef, int *a, int *hist) +{ + int i, j, y, hi32, inc, gbMask; + U64 sum64; + + /* init history to 0 every time */ + for (i = 0; i < order; i++) + hist[i] = 0; + + sum64.w64 = 0; /* avoid warning */ + gbMask = 0; + inc = (dir ? -1 : 1); + do { + /* sum64 = a0*y[n] = 1.0*y[n] */ + y = *audioCoef; + sum64.r.hi32 = y >> (32 - FBITS_LPC_COEFS); + sum64.r.lo32 = y << FBITS_LPC_COEFS; + + /* sum64 += (a1*y[n-1] + a2*y[n-2] + ... + a[order-1]*y[n-(order-1)]) */ + for (j = order - 1; j > 0; j--) { + sum64.w64 = MADD64(sum64.w64, hist[j], a[j]); + hist[j] = hist[j-1]; + } + sum64.w64 = MADD64(sum64.w64, hist[0], a[0]); + y = (sum64.r.hi32 << (32 - FBITS_LPC_COEFS)) | (sum64.r.lo32 >> FBITS_LPC_COEFS); + + /* clip output (rare) */ + hi32 = sum64.r.hi32; + if ((hi32 >> 31) != (hi32 >> (FBITS_LPC_COEFS-1))) + y = (hi32 >> 31) ^ 0x7fffffff; + + hist[0] = y; + *audioCoef = y; + audioCoef += inc; + gbMask |= FASTABS(y); + } while (--size); + + return gbMask; +} + +/*********************************************************************************************************************** + * Function: TNSFilter + * + * Description: apply temporal noise shaping, if enabled + * + * Inputs: index of current channel + * + * Outputs: updated transform coefficients + * updated minimum guard bit count for this channel + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int TNSFilter(int ch) +{ + int win, winLen, nWindows, nSFB, filt, bottom, top, order, maxOrder, dir; + int start, end, size, tnsMaxBand, numFilt, gbMask; + int *audioCoef; + uint8_t *filtLength, *filtOrder, *filtRes, *filtDir; + int8_t *filtCoef; + const uint16_t *tnsMaxBandTab; + const uint16_t *sfbTab; + ICSInfo_t *icsInfo; + TNSInfo_t *ti; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + ti = &m_PSInfoBase->tnsInfo[ch]; + + if (!ti->tnsDataPresent) + return 0; + + if (icsInfo->winSequence == 2) { + nWindows = NWINDOWS_SHORT; + winLen = NSAMPS_SHORT; + nSFB = sfBandTotalShort[m_PSInfoBase->sampRateIdx]; + maxOrder = tnsMaxOrderShort[m_AACDecInfo->profile]; + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + tnsMaxBandTab = tnsMaxBandsShort + tnsMaxBandsShortOffset[m_AACDecInfo->profile]; + tnsMaxBand = tnsMaxBandTab[m_PSInfoBase->sampRateIdx]; + } else { + nWindows = NWINDOWS_LONG; + winLen = NSAMPS_LONG; + nSFB = sfBandTotalLong[m_PSInfoBase->sampRateIdx]; + maxOrder = tnsMaxOrderLong[m_AACDecInfo->profile]; + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + tnsMaxBandTab = tnsMaxBandsLong + tnsMaxBandsLongOffset[m_AACDecInfo->profile]; + tnsMaxBand = tnsMaxBandTab[m_PSInfoBase->sampRateIdx]; + } + + if (tnsMaxBand > icsInfo->maxSFB) + tnsMaxBand = icsInfo->maxSFB; + + filtRes = ti->coefRes; + filtLength = ti->length; + filtOrder = ti->order; + filtDir = ti->dir; + filtCoef = ti->coef; + + gbMask = 0; + audioCoef = m_PSInfoBase->coef[ch]; + for (win = 0; win < nWindows; win++) { + bottom = nSFB; + numFilt = ti->numFilt[win]; + for (filt = 0; filt < numFilt; filt++) { + top = bottom; + bottom = top - *filtLength++; + bottom = MAX(bottom, 0); + order = *filtOrder++; + order = MIN(order, maxOrder); + + if (order) { + start = sfbTab[MIN(bottom, tnsMaxBand)]; + end = sfbTab[MIN(top, tnsMaxBand)]; + size = end - start; + if (size > 0) { + dir = *filtDir++; + if (dir) + start = end - 1; + + DecodeLPCCoefs(order, filtRes[win], filtCoef, m_PSInfoBase->tnsLPCBuf, m_PSInfoBase->tnsWorkBuf); + gbMask |= FilterRegion(size, dir, order, audioCoef + start, m_PSInfoBase->tnsLPCBuf, + m_PSInfoBase->tnsWorkBuf); + } + filtCoef += order; + } + } + audioCoef += winLen; + } + + /* update guard bit count if necessary */ + size = CLZ(gbMask) - 1; + if (m_PSInfoBase->gbCurrent[ch] > size) + m_PSInfoBase->gbCurrent[ch] = size; + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeSingleChannelElement + * + * Description: decode one SCE + * + * Inputs: none + * + * Outputs: updated element instance tag + * + * Return: 0 if successful, -1 if error + * + * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData) + **********************************************************************************************************************/ +int DecodeSingleChannelElement() +{ + /* read instance tag */ + m_AACDecInfo->currInstTag = GetBits(NUM_INST_TAG_BITS); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeChannelPairElement + * + * Description: decode one CPE + * + * Inputs: none + * + * Outputs: updated element instance tag + * updated commonWin + * updated ICS info, if commonWin == 1 + * updated mid-side stereo info, if commonWin == 1 + * + * Return: 0 if successful, -1 if error + * + * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData) + **********************************************************************************************************************/ +int DecodeChannelPairElement() +{ + int sfb, gp, maskOffset; + uint8_t currBit, *maskPtr; + ICSInfo_t *icsInfo; + + + icsInfo = m_PSInfoBase->icsInfo; + + /* read instance tag */ + m_AACDecInfo->currInstTag = GetBits(NUM_INST_TAG_BITS); + + /* read common window flag and mid-side info (if present) + * store msMask bits in m_PSInfoBase->msMaskBits[] as follows: + * long blocks - pack bits for each SFB in range [0, maxSFB) starting with lsb of msMaskBits[0] + * short blocks - pack bits for each SFB in range [0, maxSFB), for each group [0, 7] + * msMaskPresent = 0 means no M/S coding + * = 1 means m_PSInfoBase->msMaskBits contains 1 bit per SFB to toggle M/S coding + * = 2 means all SFB's are M/S coded (so m_PSInfoBase->msMaskBits is not needed) + */ + m_PSInfoBase->commonWin = GetBits(1); + if (m_PSInfoBase->commonWin) { + DecodeICSInfo(icsInfo, m_PSInfoBase->sampRateIdx); + m_PSInfoBase->msMaskPresent = GetBits(2); + if (m_PSInfoBase->msMaskPresent == 1) { + maskPtr = m_PSInfoBase->msMaskBits; + *maskPtr = 0; + maskOffset = 0; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + currBit = (uint8_t)GetBits(1); + *maskPtr |= currBit << maskOffset; + if (++maskOffset == 8) { + maskPtr++; + *maskPtr = 0; + maskOffset = 0; + } + } + } + } + } + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeLFEChannelElement + * + * Description: decode one LFE + * + * Inputs: none + * + * Outputs: updated element instance tag + * + * Return: 0 if successful, -1 if error + * + * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData) + **********************************************************************************************************************/ +int DecodeLFEChannelElement() +{ + /* read instance tag */ + m_AACDecInfo->currInstTag = GetBits( NUM_INST_TAG_BITS); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeDataStreamElement + * + * Description: decode one DSE + * + * Inputs: none + * + * Outputs: updated element instance tag + * filled in data stream buffer + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int DecodeDataStreamElement() +{ + uint32_t byteAlign, dataCount; + uint8_t *dataBuf; + + m_AACDecInfo->currInstTag = GetBits( NUM_INST_TAG_BITS); + byteAlign = GetBits(1); + dataCount = GetBits(8); + if (dataCount == 255) + dataCount += GetBits(8); + + if (byteAlign) + ByteAlignBitstream(); + + m_PSInfoBase->dataCount = dataCount; + dataBuf = m_PSInfoBase->dataBuf; + while (dataCount--) + *dataBuf++ = GetBits(8); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeProgramConfigElement + * + * Description: decode one PCE + * + * Inputs: none + * + * Outputs: filled-in ProgConfigElement_t struct + * updated aac_BitStreamInfo_t struct + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: #define KEEP_PCE_COMMENTS to save the comment field of the PCE + * (otherwise we just skip it in the bitstream, to save memory) + **********************************************************************************************************************/ +int DecodeProgramConfigElement(uint8_t idx) +{ + int i; + + m_pce[idx]->elemInstTag = GetBits(4); + m_pce[idx]->profile = GetBits(2); + m_pce[idx]->sampRateIdx = GetBits(4); + m_pce[idx]->numFCE = GetBits(4); + m_pce[idx]->numSCE = GetBits(4); + m_pce[idx]->numBCE = GetBits(4); + m_pce[idx]->numLCE = GetBits(2); + m_pce[idx]->numADE = GetBits(3); + m_pce[idx]->numCCE = GetBits(4); + + m_pce[idx]->monoMixdown = GetBits(1) << 4; /* present flag */ + if (m_pce[idx]->monoMixdown) + m_pce[idx]->monoMixdown |= GetBits(4); /* element number */ + + m_pce[idx]->stereoMixdown = GetBits(1) << 4; /* present flag */ + if (m_pce[idx]->stereoMixdown) + m_pce[idx]->stereoMixdown |= GetBits(4); /* element number */ + + m_pce[idx]->matrixMixdown = GetBits(1) << 4; /* present flag */ + if (m_pce[idx]->matrixMixdown) { + m_pce[idx]->matrixMixdown |= GetBits(2) << 1; /* index */ + m_pce[idx]->matrixMixdown |= GetBits(1); /* pseudo-surround enable */ + } + + for (i = 0; i < m_pce[idx]->numFCE; i++) { + m_pce[idx]->fce[i] = GetBits(1) << 4; /* is_cpe flag */ + m_pce[idx]->fce[i] |= GetBits(4); /* tag select */ + } + + for (i = 0; i < m_pce[idx]->numSCE; i++) { + m_pce[idx]->sce[i] = GetBits(1) << 4; /* is_cpe flag */ + m_pce[idx]->sce[i] |= GetBits(4); /* tag select */ + } + + for (i = 0; i < m_pce[idx]->numBCE; i++) { + m_pce[idx]->bce[i] = GetBits(1) << 4; /* is_cpe flag */ + m_pce[idx]->bce[i] |= GetBits(4); /* tag select */ + } + + for (i = 0; i < m_pce[idx]->numLCE; i++) + m_pce[idx]->lce[i] = GetBits(4); /* tag select */ + + for (i = 0; i < m_pce[idx]->numADE; i++) + m_pce[idx]->ade[i] = GetBits(4); /* tag select */ + + for (i = 0; i < m_pce[idx]->numCCE; i++) { + m_pce[idx]->cce[i] = GetBits(1) << 4; /* independent/dependent flag */ + m_pce[idx]->cce[i] |= GetBits(4); /* tag select */ + } + + ByteAlignBitstream(); + /* eat comment bytes and throw away */ + i = GetBits(8); + while (i--) + GetBits(8); + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeFillElement + * + * Description: decode one fill element + * + * Inputs: none + * (14496-3, table 4.4.11) + * + * Outputs: updated element instance tag + * unpacked extension payload + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int DecodeFillElement() +{ + unsigned int fillCount; + uint8_t *fillBuf; + + fillCount = GetBits(4); + if (fillCount == 15) + fillCount += (GetBits(8) - 1); + + m_PSInfoBase->fillCount = fillCount; + fillBuf = m_PSInfoBase->fillBuf; + while (fillCount--) + *fillBuf++ = GetBits(8); + + m_AACDecInfo->currInstTag = -1; /* fill elements don't have instance tag */ + m_AACDecInfo->fillExtType = 0; + +#ifdef AAC_ENABLE_SBR + /* check for SBR + * aacDecInfo->sbrEnabled is sticky (reset each raw_data_block), so for multichannel + * need to verify that all SCE/CPE/ICCE have valid SBR fill element following, and + * must upsample by 2 for LFE + */ + if (m_PSInfoBase->fillCount > 0) { + m_AACDecInfo->fillExtType = (int)((m_PSInfoBase->fillBuf[0] >> 4) & 0x0f); + if (m_AACDecInfo->fillExtType == EXT_SBR_DATA || m_AACDecInfo->fillExtType == EXT_SBR_DATA_CRC) + m_AACDecInfo->sbrEnabled = 1; + } +#endif + + + m_AACDecInfo->fillBuf = m_PSInfoBase->fillBuf; + m_AACDecInfo->fillCount = m_PSInfoBase->fillCount; + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeNextElement + * + * Description: decode next syntactic element in AAC frame + * + * Inputs: double pointer to buffer containing next element + * pointer to bit offset + * pointer to number of valid bits remaining in buf + * + * Outputs: type of element decoded (aacDecInfo->currBlockID) + * type of element decoded last time (aacDecInfo->prevBlockID) + * updated aacDecInfo state, depending on which element was decoded + * updated buffer pointer + * updated bit offset + * updated number of available bits + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int DecodeNextElement(uint8_t **buf, int *bitOffset, int *bitsAvail) +{ + int err, bitsUsed; + + /* init bitstream reader */ + SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf); + GetBits(*bitOffset); + + m_AACDecInfo->prevBlockID = m_AACDecInfo->currBlockID; + m_AACDecInfo->currBlockID = GetBits(NUM_SYN_ID_BITS); + + /* set defaults (could be overwritten by DecodeXXXElement(), depending on currBlockID) */ + m_PSInfoBase->commonWin = 0; + + err = 0; + switch (m_AACDecInfo->currBlockID) { + case AAC_ID_SCE: + err = DecodeSingleChannelElement(); + break; + case AAC_ID_CPE: + err = DecodeChannelPairElement(); + break; + case AAC_ID_CCE: + break; + case AAC_ID_LFE: + err = DecodeLFEChannelElement(); + break; + case AAC_ID_DSE: + err = DecodeDataStreamElement(); + break; + case AAC_ID_PCE: + err = DecodeProgramConfigElement(0); + break; + case AAC_ID_FIL: + err = DecodeFillElement(); + break; + case AAC_ID_END: + break; + } + if (err) + return ERR_AAC_SYNTAX_ELEMENT; + + /* update bitstream reader */ + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + *bitsAvail -= bitsUsed; + + if (*bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: PreMultiply + * + * Description: pre-twiddle stage of DCT4 + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out, gains 5 (short) or 8 (long) frac bits + * i.e. gains 2-7= -5 int bits (short) or 2-10 = -8 int bits (long) + * normalization by -1/N is rolled into tables here (see trigtabs.c) + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void PreMultiply(int tabidx, int *zbuf1) +{ + int i, nmdct, ar1, ai1, ar2, ai2, z1, z2; + int t, cms2, cps2a, sin2a, cps2b, sin2b; + int *zbuf2; + const uint32_t *csptr; + + nmdct = nmdctTab[tabidx]; + zbuf2 = zbuf1 + nmdct - 1; + csptr = cos4sin4tab + cos4sin4tabOffset[tabidx]; + + /* whole thing should fit in registers - verify that compiler does this */ + for (i = nmdct >> 2; i != 0; i--) { + /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */ + cps2a = *csptr++; + sin2a = *csptr++; + cps2b = *csptr++; + sin2b = *csptr++; + + ar1 = *(zbuf1 + 0); + ai2 = *(zbuf1 + 1); + ai1 = *(zbuf2 + 0); + ar2 = *(zbuf2 - 1); + + /* gain 2 ints bit from MULSHIFT32 by Q30, but drop 7 or 10 int bits from table scaling of 1/M + * max per-sample gain (ignoring implicit scaling) = MAX(sin(angle)+cos(angle)) = 1.414 + * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from + * extra sign bits, and eat one in adding + */ + t = MULSHIFT32(sin2a, ar1 + ai1); + z2 = MULSHIFT32(cps2a, ai1) - t; + cms2 = cps2a - 2*sin2a; + z1 = MULSHIFT32(cms2, ar1) + t; + *zbuf1++ = z1; /* cos*ar1 + sin*ai1 */ + *zbuf1++ = z2; /* cos*ai1 - sin*ar1 */ + + t = MULSHIFT32(sin2b, ar2 + ai2); + z2 = MULSHIFT32(cps2b, ai2) - t; + cms2 = cps2b - 2*sin2b; + z1 = MULSHIFT32(cms2, ar2) + t; + *zbuf2-- = z2; /* cos*ai2 - sin*ar2 */ + *zbuf2-- = z1; /* cos*ar2 + sin*ai2 */ + } +} + +/*********************************************************************************************************************** + * Function: PostMultiply + * + * Description: post-twiddle stage of DCT4 + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out - gains 2 int bits + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void PostMultiply(int tabidx, int *fft1) +{ + int i, nmdct, ar1, ai1, ar2, ai2, skipFactor; + int t, cms2, cps2, sin2; + int *fft2; + const int *csptr; + + nmdct = nmdctTab[tabidx]; + csptr = cos1sin1tab; + skipFactor = postSkip[tabidx]; + fft2 = fft1 + nmdct - 1; + + /* load coeffs for first pass + * cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) + */ + cps2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + cms2 = cps2 - 2*sin2; + + for (i = nmdct >> 2; i != 0; i--) { + ar1 = *(fft1 + 0); + ai1 = *(fft1 + 1); + ar2 = *(fft2 - 1); + ai2 = *(fft2 + 0); + + /* gain 2 ints bit from MULSHIFT32 by Q30 + * max per-sample gain = MAX(sin(angle)+cos(angle)) = 1.414 + * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from + * extra sign bits, and eat one in adding + */ + t = MULSHIFT32(sin2, ar1 + ai1); + *fft2-- = t - MULSHIFT32(cps2, ai1); /* sin*ar1 - cos*ai1 */ + *fft1++ = t + MULSHIFT32(cms2, ar1); /* cos*ar1 + sin*ai1 */ + cps2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + + ai2 = -ai2; + t = MULSHIFT32(sin2, ar2 + ai2); + *fft2-- = t - MULSHIFT32(cps2, ai2); /* sin*ar1 - cos*ai1 */ + cms2 = cps2 - 2*sin2; + *fft1++ = t + MULSHIFT32(cms2, ar2); /* cos*ar1 + sin*ai1 */ + } +} + +/*********************************************************************************************************************** + * Function: PreMultiplyRescale + * + * Description: pre-twiddle stage of DCT4, with rescaling for extra guard bits + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * number of guard bits to add to input before processing + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: see notes on PreMultiply(), above + **********************************************************************************************************************/ +void PreMultiplyRescale(int tabidx, int *zbuf1, int es) +{ + int i, nmdct, ar1, ai1, ar2, ai2, z1, z2; + int t, cms2, cps2a, sin2a, cps2b, sin2b; + int *zbuf2; + const uint32_t *csptr; + + nmdct = nmdctTab[tabidx]; + zbuf2 = zbuf1 + nmdct - 1; + csptr = cos4sin4tab + cos4sin4tabOffset[tabidx]; + + /* whole thing should fit in registers - verify that compiler does this */ + for (i = nmdct >> 2; i != 0; i--) { + /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */ + cps2a = *csptr++; + sin2a = *csptr++; + cps2b = *csptr++; + sin2b = *csptr++; + + ar1 = *(zbuf1 + 0) >> es; + ai1 = *(zbuf2 + 0) >> es; + ai2 = *(zbuf1 + 1) >> es; + + t = MULSHIFT32(sin2a, ar1 + ai1); + z2 = MULSHIFT32(cps2a, ai1) - t; + cms2 = cps2a - 2*sin2a; + z1 = MULSHIFT32(cms2, ar1) + t; + *zbuf1++ = z1; + *zbuf1++ = z2; + + ar2 = *(zbuf2 - 1) >> es; /* do here to free up register used for es */ + + t = MULSHIFT32(sin2b, ar2 + ai2); + z2 = MULSHIFT32(cps2b, ai2) - t; + cms2 = cps2b - 2*sin2b; + z1 = MULSHIFT32(cms2, ar2) + t; + *zbuf2-- = z2; + *zbuf2-- = z1; + + } +} + +/*********************************************************************************************************************** + * Function: PostMultiplyRescale + * + * Description: post-twiddle stage of DCT4, with rescaling for extra guard bits + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * number of guard bits to remove from output + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: clips output to [-2^30, 2^30 - 1], guaranteeing at least 1 guard bit + * see notes on PostMultiply(), above + **********************************************************************************************************************/ +void PostMultiplyRescale(int tabidx, int *fft1, int es) +{ + int i, nmdct, ar1, ai1, ar2, ai2, skipFactor, z; + int t, cs2, sin2; + int *fft2; + const int *csptr; + + nmdct = nmdctTab[tabidx]; + csptr = cos1sin1tab; + skipFactor = postSkip[tabidx]; + fft2 = fft1 + nmdct - 1; + + /* load coeffs for first pass + * cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) + */ + cs2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + + for (i = nmdct >> 2; i != 0; i--) { + ar1 = *(fft1 + 0); + ai1 = *(fft1 + 1); + ai2 = *(fft2 + 0); + + t = MULSHIFT32(sin2, ar1 + ai1); + z = t - MULSHIFT32(cs2, ai1); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft2-- = z; + cs2 -= 2*sin2; + z = t + MULSHIFT32(cs2, ar1); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft1++ = z; + + cs2 = *csptr++; + sin2 = *csptr; + csptr += skipFactor; + + ar2 = *fft2; + ai2 = -ai2; + t = MULSHIFT32(sin2, ar2 + ai2); + z = t - MULSHIFT32(cs2, ai2); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft2-- = z; + cs2 -= 2*sin2; + z = t + MULSHIFT32(cs2, ar2); + {int sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}} + *fft1++ = z; + cs2 += 2*sin2; + } +} + +/*********************************************************************************************************************** + * Function: DCT4 + * + * Description: type-IV DCT + * + * Inputs: table index (for transform size) + * buffer of nmdct samples + * number of guard bits in the input buffer + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: operates in-place + * if number of guard bits in input is < GBITS_IN_DCT4, the input is + * scaled (>>) before the DCT4 and rescaled (<<, with clipping) after + * the DCT4 (rare) + * the output has FBITS_LOST_DCT4 fewer fraction bits than the input + * the output will always have at least 1 guard bit (GBITS_IN_DCT4 >= 4) + * int bits gained per stage (PreMul + FFT + PostMul) + * short blocks = (-5 + 4 + 2) = 1 total + * long blocks = (-8 + 7 + 2) = 1 total + **********************************************************************************************************************/ +void DCT4(int tabidx, int *coef, int gb) +{ + int es; + + /* fast in-place DCT-IV - adds guard bits if necessary */ + if (gb < GBITS_IN_DCT4) { + es = GBITS_IN_DCT4 - gb; + PreMultiplyRescale(tabidx, coef, es); + R4FFT(tabidx, coef); + PostMultiplyRescale(tabidx, coef, es); + } else { + PreMultiply(tabidx, coef); + R4FFT(tabidx, coef); + PostMultiply(tabidx, coef); + } +} + +/*********************************************************************************************************************** + * Function: BitReverse + * + * Description: Ken's fast in-place bit reverse, using super-small table + * + * Inputs: buffer of samples + * table index (for transform size) + * + * Outputs: bit-reversed samples in same buffer + * + * Return: none + **********************************************************************************************************************/ +void BitReverse(int *inout, int tabidx) +{ + int *part0, *part1; + int a,b, t; + const uint8_t* tab = bitrevtab + bitrevtabOffset[tabidx]; + int nbits = nfftlog2Tab[tabidx]; + + part0 = inout; + part1 = inout + (1 << nbits); + + while ((a = pgm_read_byte(tab++)) != 0) { + b = pgm_read_byte(tab++); + + t=part0[4*a+0]; part0[4*a+0]=part0[4*b+0]; part0[4*b+0]=t; /* 0xxx0 <-> 0yyy0 */ + t=part0[4*a+1]; part0[4*a+1]=part0[4*b+1]; part0[4*b+1]=t; + + t=part0[4*a+2]; part0[4*a+2]=part1[4*b+0]; part1[4*b+0]=t; /* 0xxx0 <-> 0yyy0 */ + t=part0[4*a+3]; part0[4*a+3]=part1[4*b+1]; part1[4*b+1]=t; + + t=part1[4*a+0]; part1[4*a+0]=part0[4*b+2]; part0[4*b+2]=t; /* 1xxx0 <-> 0yyy1 */ + t=part1[4*a+1]; part1[4*a+1]=part0[4*b+3]; part0[4*b+3]=t; + + t=part1[4*a+2]; part1[4*a+2]=part1[4*b+2]; part1[4*b+2]=t; /* 1xxx1 <-> 1yyy1 */ + t=part1[4*a+3]; part1[4*a+3]=part1[4*b+3]; part1[4*b+3]=t; + } + + do { + t=part0[4*a+2]; part0[4*a+2]=part1[4*a+0]; part1[4*a+0]=t; /* 0xxx1 <-> 1xxx0 */ + t=part0[4*a+3]; part0[4*a+3]=part1[4*a+1]; part1[4*a+1]=t; + } while ((a = pgm_read_byte(tab++)) != 0); + + +} + +/*********************************************************************************************************************** + * Function: R4FirstPass + * + * Description: radix-4 trivial pass for decimation-in-time FFT + * + * Inputs: buffer of (bit-reversed) samples + * number of R4 butterflies per group (i.e. nfft / 4) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 2 guard bits, gains no integer bits, + * guard bits out = guard bits in - 2 + **********************************************************************************************************************/ +void R4FirstPass(int *x, int bg) +{ + int ar, ai, br, bi, cr, ci, dr, di; + + for (; bg != 0; bg--) { + + ar = x[0] + x[2]; + br = x[0] - x[2]; + ai = x[1] + x[3]; + bi = x[1] - x[3]; + cr = x[4] + x[6]; + dr = x[4] - x[6]; + ci = x[5] + x[7]; + di = x[5] - x[7]; + + /* max per-sample gain = 4.0 (adding 4 inputs together) */ + x[0] = ar + cr; + x[4] = ar - cr; + x[1] = ai + ci; + x[5] = ai - ci; + x[2] = br + di; + x[6] = br - di; + x[3] = bi - dr; + x[7] = bi + dr; + + x += 8; + } +} + +/*********************************************************************************************************************** + * Function: R8FirstPass + * + * Description: radix-8 trivial pass for decimation-in-time FFT + * + * Inputs: buffer of (bit-reversed) samples + * number of R8 butterflies per group (i.e. nfft / 8) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 3 guard bits, gains 1 integer bit + * guard bits out = guard bits in - 3 (if inputs are full scale) + * or guard bits in - 2 (if inputs bounded to +/- sqrt(2)/2) + * see scaling comments in code + **********************************************************************************************************************/ +void R8FirstPass(int *x, int bg) +{ + int ar, ai, br, bi, cr, ci, dr, di; + int sr, si, tr, ti, ur, ui, vr, vi; + int wr, wi, xr, xi, yr, yi, zr, zi; + + for (; bg != 0; bg--) { + + ar = x[0] + x[2]; + br = x[0] - x[2]; + ai = x[1] + x[3]; + bi = x[1] - x[3]; + cr = x[4] + x[6]; + dr = x[4] - x[6]; + ci = x[5] + x[7]; + di = x[5] - x[7]; + + sr = ar + cr; + ur = ar - cr; + si = ai + ci; + ui = ai - ci; + tr = br - di; + vr = br + di; + ti = bi + dr; + vi = bi - dr; + + ar = x[ 8] + x[10]; + br = x[ 8] - x[10]; + ai = x[ 9] + x[11]; + bi = x[ 9] - x[11]; + cr = x[12] + x[14]; + dr = x[12] - x[14]; + ci = x[13] + x[15]; + di = x[13] - x[15]; + + /* max gain of wr/wi/yr/yi vs input = 2 + * (sum of 4 samples >> 1) + */ + wr = (ar + cr) >> 1; + yr = (ar - cr) >> 1; + wi = (ai + ci) >> 1; + yi = (ai - ci) >> 1; + + /* max gain of output vs input = 4 + * (sum of 4 samples >> 1 + sum of 4 samples >> 1) + */ + x[ 0] = (sr >> 1) + wr; + x[ 8] = (sr >> 1) - wr; + x[ 1] = (si >> 1) + wi; + x[ 9] = (si >> 1) - wi; + x[ 4] = (ur >> 1) + yi; + x[12] = (ur >> 1) - yi; + x[ 5] = (ui >> 1) - yr; + x[13] = (ui >> 1) + yr; + + ar = br - di; + cr = br + di; + ai = bi + dr; + ci = bi - dr; + + /* max gain of xr/xi/zr/zi vs input = 4*sqrt(2)/2 = 2*sqrt(2) + * (sum of 8 samples, multiply by sqrt(2)/2, implicit >> 1 from Q31) + */ + xr = MULSHIFT32(SQRTHALF, ar - ai); + xi = MULSHIFT32(SQRTHALF, ar + ai); + zr = MULSHIFT32(SQRTHALF, cr - ci); + zi = MULSHIFT32(SQRTHALF, cr + ci); + + /* max gain of output vs input = (2 + 2*sqrt(2) ~= 4.83) + * (sum of 4 samples >> 1, plus xr/xi/zr/zi with gain of 2*sqrt(2)) + * in absolute terms, we have max gain of appx 9.656 (4 + 0.707*8) + * but we also gain 1 int bit (from MULSHIFT32 or from explicit >> 1) + */ + x[ 6] = (tr >> 1) - xr; + x[14] = (tr >> 1) + xr; + x[ 7] = (ti >> 1) - xi; + x[15] = (ti >> 1) + xi; + x[ 2] = (vr >> 1) + zi; + x[10] = (vr >> 1) - zi; + x[ 3] = (vi >> 1) - zr; + x[11] = (vi >> 1) + zr; + + x += 16; + } +} + +/*********************************************************************************************************************** + * Function: R4Core + * + * Description: radix-4 pass for decimation-in-time FFT + * + * Inputs: buffer of samples + * number of R4 butterflies per group + * number of R4 groups per pass + * pointer to twiddle factors tables + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: gain 2 integer bits per pass (see scaling comments in code) + * min 1 GB in + * gbOut = gbIn - 1 (short block) or gbIn - 2 (long block) + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void R4Core(int *x, int bg, int gp, int *wtab) +{ + int ar, ai, br, bi, cr, ci, dr, di, tr, ti; + int wd, ws, wi; + int i, j, step; + int *xptr, *wptr; + + for (; bg != 0; gp <<= 2, bg >>= 2) { + + step = 2*gp; + xptr = x; + + /* max per-sample gain, per group < 1 + 3*sqrt(2) ~= 5.25 if inputs x are full-scale + * do 3 groups for long block, 2 groups for short block (gain 2 int bits per group) + * + * very conservative scaling: + * group 1: max gain = 5.25, int bits gained = 2, gb used = 1 (2^3 = 8) + * group 2: max gain = 5.25^2 = 27.6, int bits gained = 4, gb used = 1 (2^5 = 32) + * group 3: max gain = 5.25^3 = 144.7, int bits gained = 6, gb used = 2 (2^8 = 256) + */ + for (i = bg; i != 0; i--) { + + wptr = wtab; + + for (j = gp; j != 0; j--) { + + ar = xptr[0]; + ai = xptr[1]; + xptr += step; + + /* gain 2 int bits for br/bi, cr/ci, dr/di (MULSHIFT32 by Q30) + * gain 1 net GB + */ + ws = wptr[0]; + wi = wptr[1]; + br = xptr[0]; + bi = xptr[1]; + wd = ws + 2*wi; + tr = MULSHIFT32(wi, br + bi); + br = MULSHIFT32(wd, br) - tr; /* cos*br + sin*bi */ + bi = MULSHIFT32(ws, bi) + tr; /* cos*bi - sin*br */ + xptr += step; + + ws = wptr[2]; + wi = wptr[3]; + cr = xptr[0]; + ci = xptr[1]; + wd = ws + 2*wi; + tr = MULSHIFT32(wi, cr + ci); + cr = MULSHIFT32(wd, cr) - tr; + ci = MULSHIFT32(ws, ci) + tr; + xptr += step; + + ws = wptr[4]; + wi = wptr[5]; + dr = xptr[0]; + di = xptr[1]; + wd = ws + 2*wi; + tr = MULSHIFT32(wi, dr + di); + dr = MULSHIFT32(wd, dr) - tr; + di = MULSHIFT32(ws, di) + tr; + wptr += 6; + + tr = ar; + ti = ai; + ar = (tr >> 2) - br; + ai = (ti >> 2) - bi; + br = (tr >> 2) + br; + bi = (ti >> 2) + bi; + + tr = cr; + ti = ci; + cr = tr + dr; + ci = di - ti; + dr = tr - dr; + di = di + ti; + + xptr[0] = ar + ci; + xptr[1] = ai + dr; + xptr -= step; + xptr[0] = br - cr; + xptr[1] = bi - di; + xptr -= step; + xptr[0] = ar - ci; + xptr[1] = ai - dr; + xptr -= step; + xptr[0] = br + cr; + xptr[1] = bi + di; + xptr += 2; + } + xptr += 3*step; + } + wtab += 3*step; + } +} + + +/*********************************************************************************************************************** + * Function: R4FFT + * + * Description: Ken's very fast in-place radix-4 decimation-in-time FFT + * + * Inputs: table index (for transform size) + * buffer of samples (non bit-reversed) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 5 guard bits in for nfft <= 512 + * gbOut = gbIn - 4 (assuming input is from PreMultiply) + * gains log2(nfft) - 2 int bits total + * so gain 7 int bits (LONG), 4 int bits (SHORT) + **********************************************************************************************************************/ +void R4FFT(int tabidx, int *x) +{ + int order = nfftlog2Tab[tabidx]; + int nfft = nfftTab[tabidx]; + + /* decimation in time */ + BitReverse(x, tabidx); + + if (order & 0x1) { + /* long block: order = 9, nfft = 512 */ + R8FirstPass(x, nfft >> 3); /* gain 1 int bit, lose 2 GB */ + R4Core(x, nfft >> 5, 8, (int *)twidTabOdd); /* gain 6 int bits, lose 2 GB */ + } else { + /* short block: order = 6, nfft = 64 */ + R4FirstPass(x, nfft >> 2); /* gain 0 int bits, lose 2 GB */ + R4Core(x, nfft >> 4, 4, (int *)twidTabEven); /* gain 4 int bits, lose 1 GB */ + } +} + +/*********************************************************************************************************************** + * Function: UnpackZeros + * + * Description: fill a section of coefficients with zeros + * + * Inputs: number of coefficients + * + * Outputs: nVals zeros, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 4 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackZeros(int nVals, int *coef) +{ + while (nVals > 0) { + *coef++ = 0; + *coef++ = 0; + *coef++ = 0; + *coef++ = 0; + nVals -= 4; + } +} + +/*********************************************************************************************************************** + * Function: UnpackQuads + * + * Description: decode a section of 4-way vector Huffman coded coefficients + * + * Inputs index of Huffman codebook + * number of coefficients + * + * Outputs: nVals coefficients, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 4 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackQuads(int cb, int nVals, int *coef) +{ + int w, x, y, z, maxBits, nCodeBits, nSignBits, val; + uint32_t bitBuf; + + maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 4; + while (nVals > 0) { + /* decode quad */ + bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits); + nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET], bitBuf, &val); + + w = (((int32_t)(val) << 20) >> 29); /* bits 11-9, sign-extend */ + x = (((int32_t)(val) << 23) >> 29); /* bits 8-6, sign-extend */ + y = (((int32_t)(val) << 26) >> 29); /* bits 5-3, sign-extend */ + z = (((int32_t)(val) << 29) >> 29); /* bits 2-0, sign-extend */ + + bitBuf <<= nCodeBits; + nSignBits = (int)(((uint32_t)(val) << 17) >> 29); /* bits 14-12, unsigned */ + + AdvanceBitstream(nCodeBits + nSignBits); + if (nSignBits) { + if (w) {w ^= ((int32_t)bitBuf >> 31); w -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (x) {x ^= ((int32_t)bitBuf >> 31); x -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + } + *coef++ = w; *coef++ = x; *coef++ = y; *coef++ = z; + nVals -= 4; + } +} + +/*********************************************************************************************************************** + * Function: UnpackPairsNoEsc + * + * Description: decode a section of 2-way vector Huffman coded coefficients, + * using non-esc tables (5 through 10) + * + * Inputs index of Huffman codebook (must not be the escape codebook) + * number of coefficients + * + * Outputs: nVals coefficients, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 2 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackPairsNoEsc(int cb, int nVals, int *coef) +{ + int y, z, maxBits, nCodeBits, nSignBits, val; + uint32_t bitBuf; + + maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 2; + while (nVals > 0) { + /* decode pair */ + bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits); + nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb-HUFFTAB_SPEC_OFFSET], bitBuf, &val); + + y = (((int32_t)(val) << 22) >> 27); /* bits 9-5, sign-extend */ + z = (((int32_t)(val) << 27) >> 27); /* bits 4-0, sign-extend */ + + bitBuf <<= nCodeBits; + nSignBits = (((uint32_t)(val) << 20) >> 30); /* bits 11-10, unsigned */ + AdvanceBitstream(nCodeBits + nSignBits); + if (nSignBits) { + if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + } + *coef++ = y; *coef++ = z; + nVals -= 2; + } +} + +/*********************************************************************************************************************** + * Function: UnpackPairsEsc + * + * Description: decode a section of 2-way vector Huffman coded coefficients, + * using esc table (11) + * + * Inputs index of Huffman codebook (must be the escape codebook) + * number of coefficients + * + * Outputs: nVals coefficients, starting at coef + * + * Return: none + * + * Notes: assumes nVals is always a multiple of 2 because all scalefactor bands + * are a multiple of 4 coefficients long + **********************************************************************************************************************/ +void UnpackPairsEsc(int cb, int nVals, int *coef) +{ + int y, z, maxBits, nCodeBits, nSignBits, n, val; + uint32_t bitBuf; + + maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 2; + while (nVals > 0) { + /* decode pair with escape value */ + bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits); + nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb-HUFFTAB_SPEC_OFFSET], bitBuf, &val); + + y = (((int32_t)(val) << 20) >> 26); /* bits 11-6, sign-extend */ + z = (((int32_t)(val) << 26) >> 26); /* bits 5-0, sign-extend */ + + bitBuf <<= nCodeBits; + nSignBits = (((uint32_t)(val) << 18) >> 30); /* bits 13-12, unsigned */ + AdvanceBitstream(nCodeBits + nSignBits); + + if (y == 16) { + n = 4; + while (GetBits(1) == 1) + n++; + y = (1 << n) + GetBits(n); + } + if (z == 16) { + n = 4; + while (GetBits(1) == 1) + n++; + z = (1 << n) + GetBits(n); + } + + if (nSignBits) { + if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;} + } + + *coef++ = y; *coef++ = z; + nVals -= 2; + } +} + +/*********************************************************************************************************************** + * Function: DecodeSpectrumLong + * + * Description: decode transform coefficients for frame with one long block + * + * Inputs: index of current channel + * + * Outputs: decoded, quantized coefficients for this channel + * + * Return: none + * + * Notes: adds in pulse data if present + * fills coefficient buffer with zeros in any region not coded with + * codebook in range [1, 11] (including sfb's above sfbMax) + **********************************************************************************************************************/ +void DecodeSpectrumLong(int ch) +{ + int i, sfb, cb, nVals, offset; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + int *coef; + ICSInfo_t *icsInfo; + + coef = m_PSInfoBase->coef[ch]; + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + /* decode long block */ + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + cb = *sfbCodeBook++; + nVals = sfbTab[sfb+1] - sfbTab[sfb]; + + if (cb == 0) + UnpackZeros(nVals, coef); + else if (cb <= 4) + UnpackQuads(cb, nVals, coef); + else if (cb <= 10) + UnpackPairsNoEsc(cb, nVals, coef); + else if (cb == 11) + UnpackPairsEsc(cb, nVals, coef); + else + UnpackZeros(nVals, coef); + + coef += nVals; + } + + /* fill with zeros above maxSFB */ + nVals = NSAMPS_LONG - sfbTab[sfb]; + UnpackZeros(nVals, coef); + + /* add pulse data, if present */ + if (m_pulseInfo[ch].pulseDataPresent) { + coef = m_PSInfoBase->coef[ch]; + offset = sfbTab[m_pulseInfo[ch].startSFB]; + for (i = 0; i < m_pulseInfo[ch].numPulse; i++) { + offset += m_pulseInfo[ch].offset[i]; + if (coef[offset] > 0) + coef[offset] += m_pulseInfo[ch].amp[i]; + else + coef[offset] -= m_pulseInfo[ch].amp[i]; + } + ASSERT(offset < NSAMPS_LONG); + } +} + +/*********************************************************************************************************************** + * Function: DecodeSpectrumShort + * + * Description: decode transform coefficients for frame with eight short blocks + * + * Inputs: index of current channel + * + * Outputs: decoded, quantized coefficients for this channel + * + * Return: none + * + * Notes: fills coefficient buffer with zeros in any region not coded with + * codebook in range [1, 11] (including sfb's above sfbMax) + * deinterleaves window groups into 8 windows + **********************************************************************************************************************/ +void DecodeSpectrumShort(int ch) +{ + int gp, cb, nVals=0, win, offset, sfb; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + int *coef; + ICSInfo_t *icsInfo; + + coef = m_PSInfoBase->coef[ch]; + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + /* decode short blocks, deinterleaving in-place */ + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + nVals = sfbTab[sfb+1] - sfbTab[sfb]; + cb = *sfbCodeBook++; + + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + offset = win*NSAMPS_SHORT; + if (cb == 0) + UnpackZeros(nVals, coef + offset); + else if (cb <= 4) + UnpackQuads(cb, nVals, coef + offset); + else if (cb <= 10) + UnpackPairsNoEsc(cb, nVals, coef + offset); + else if (cb == 11) + UnpackPairsEsc(cb, nVals, coef + offset); + else + UnpackZeros(nVals, coef + offset); + } + coef += nVals; + } + + /* fill with zeros above maxSFB */ + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + offset = win*NSAMPS_SHORT; + nVals = NSAMPS_SHORT - sfbTab[sfb]; + UnpackZeros(nVals, coef + offset); + } + coef += nVals; + coef += (icsInfo->winGroupLen[gp] - 1)*NSAMPS_SHORT; + } + + ASSERT(coef == m_PSInfoBase->coef[ch] + NSAMPS_LONG); +} + +#ifndef AAC_ENABLE_SBR +/*********************************************************************************************************************** + * Function: DecWindowOverlap + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence LONG-LONG + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + * + **********************************************************************************************************************/ +void DecWindowOverlap(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndCurr, *wndPrev; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + pcm1 = pcm0 + (1024 - 1) * nChans; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + if (winTypeCurr == winTypePrev) { + /* cut window loads in half since current and overlap sections use same symmetric window */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } else { + /* different windows for current and overlap parts - should still fit in registers on ARM w/o stack spill */ + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } +} + +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStart + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence LONG-START + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + **********************************************************************************************************************/ +void DecWindowOverlapLongStart(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int i, in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + pcm1 = pcm0 + (1024 - 1) * nChans; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + in = *buf1--; + + *over1-- = 0; /* Wn = 0 for n = (2047, 2046, ... 1600) */ + *over0++ = in >> 1; /* Wn = 1 for n = (1024, 1025, ... 1471) */ + } while (--i); + + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; /* W[0], W[1], ... --> W[255], W[254], ... */ + w1 = *wndCurr++; /* W[127], W[126], ... --> W[128], W[129], ... */ + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); /* Wn = short window for n = (1599, 1598, ... , 1536) */ + *over0++ = MULSHIFT32(w1, in); /* Wn = short window for n = (1472, 1473, ... , 1535) */ + } while (over0 < over1); +} + +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStop + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence LONG-STOP + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + **********************************************************************************************************************/ +void DecWindowOverlapLongStop(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int i, in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + pcm1 = pcm0 + (1024 - 1) * nChans; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + /* Wn = 0 for n = (0, 1, ... 447) */ + /* Wn = 1 for n = (576, 577, ... 1023) */ + in = *buf0++; + f1 = in >> 1; /* scale since skipping multiply by Q31 */ + + in = *over0; + *pcm0 = CLIPTOSHORT( (in + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (--i); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); +} + +/*********************************************************************************************************************** + * Function: DecWindowOverlapShort + * + * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM, + * for winSequence EIGHT-SHORT (does all 8 short blocks) + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * number of channels + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans + * + * Return: none + * + * Notes: this processes one channel at a time, but skips every other sample in + * the output buffer (pcm) for stereo interleaving + * this should fit in registers on ARM + **********************************************************************************************************************/ +void DecWindowOverlapShort(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev) +{ + int i, in, w0, w1, f0, f1; + int *buf1, *over1; + short *pcm1; + const int *wndPrev, *wndCurr; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* pcm[0-447] = 0 + overlap[0-447] */ + i = 448; + do { + f0 = *over0++; + f1 = *over0++; + *pcm0 = CLIPTOSHORT( (f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); pcm0 += nChans; + *pcm0 = CLIPTOSHORT( (f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); pcm0 += nChans; + i -= 2; + } while (i); + + /* pcm[448-575] = Wp[0-127] * block0[0-127] + overlap[448-575] */ + pcm1 = pcm0 + (128 - 1) * nChans; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *over1; + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + /* save over0/over1 for next short block, in the slots just vacated */ + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + + /* pcm[576-703] = Wc[128-255] * block0[128-255] + Wc[0-127] * block1[0-127] + overlap[576-703] + * pcm[704-831] = Wc[128-255] * block1[128-255] + Wc[0-127] * block2[0-127] + overlap[704-831] + * pcm[832-959] = Wc[128-255] * block2[128-255] + Wc[0-127] * block3[0-127] + overlap[832-959] + */ + for (i = 0; i < 3; i++) { + pcm0 += 64 * nChans; + pcm1 = pcm0 + (128 - 1) * nChans; + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 - 128); /* from last short block */ + in += *(over0 + 0); /* from last full frame */ + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *(over1 - 128); /* from last short block */ + in += *(over1 + 0); /* from last full frame */ + *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm1 -= nChans; + + /* save over0/over1 for next short block, in the slots just vacated */ + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* pcm[960-1023] = Wc[128-191] * block3[128-191] + Wc[0-63] * block4[0-63] + overlap[960-1023] + * over[0-63] = Wc[192-255] * block3[192-255] + Wc[64-127] * block4[64-127] + */ + pcm0 += 64 * nChans; + over0 -= 832; /* points at overlap[64] */ + over1 = over0 + 128 - 1; /* points at overlap[191] */ + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 + 768); /* from last short block */ + in += *(over0 + 896); /* from last full frame */ + *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); + pcm0 += nChans; + + in = *(over1 + 768); /* from last short block */ + *(over1 - 128) = in + f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); /* save in overlap[128-191] */ + *over0++ = MULSHIFT32(w1, in); /* save in overlap[64-127] */ + } while (over0 < over1); + + /* over0 now points at overlap[128] */ + + /* over[64-191] = Wc[128-255] * block4[128-255] + Wc[0-127] * block5[0-127] + * over[192-319] = Wc[128-255] * block5[128-255] + Wc[0-127] * block6[0-127] + * over[320-447] = Wc[128-255] * block6[128-255] + Wc[0-127] * block7[0-127] + * over[448-576] = Wc[128-255] * block7[128-255] + */ + for (i = 0; i < 3; i++) { + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + /* from last short block */ + *(over0 - 128) -= f0; + *(over1 - 128)+= f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* over[576-1024] = 0 */ + i = 448; + over0 += 64; + do { + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + i -= 4; + } while (i); +} + +#endif /* !AAC_ENABLE_SBR */ + +/*********************************************************************************************************************** + * Function: IMDCT + * + * Description: inverse transform and convert to 16-bit PCM + * + * Inputs: index of current channel (0 for SCE/LFE, 0 or 1 for CPE) + * output channel (range = [0, nChans-1]) + * + * Outputs: complete frame of decoded PCM, after inverse transform + * + * Return: 0 if successful, -1 if error + * + * Notes: If AAC_ENABLE_SBR is defined at compile time then window + overlap + * does NOT clip to 16-bit PCM and does NOT interleave channels + * If AAC_ENABLE_SBR is NOT defined at compile time, then window + overlap + * does clip to 16-bit PCM and interleaves channels + * If SBR is enabled at compile time, but we don't know whether it is + * actually used for this frame (e.g. the first frame of a stream), + * we need to produce both clipped 16-bit PCM in outbuf AND + * unclipped 32-bit PCM in the SBR input buffer. In this case we make + * a separate pass over the 32-bit PCM to produce 16-bit PCM output. + * This inflicts a slight performance hit when decoding non-SBR files. + **********************************************************************************************************************/ +int IMDCT(int ch, int chOut, short *outbuf) +{ + int i; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + outbuf += chOut; + + /* optimized type-IV DCT (operates inplace) */ + if (icsInfo->winSequence == 2) { + /* 8 short blocks */ + for (i = 0; i < 8; i++) + DCT4(0, m_PSInfoBase->coef[ch] + i*128, m_PSInfoBase->gbCurrent[ch]); + } else { + /* 1 long block */ + DCT4(1, m_PSInfoBase->coef[ch], m_PSInfoBase->gbCurrent[ch]); + } + +#ifdef AAC_ENABLE_SBR + /* window, overlap-add, don't clip to short (send to SBR decoder) + * store the decoded 32-bit samples in top half (second AAC_MAX_NSAMPS samples) of coef buffer + */ + if (icsInfo->winSequence == 0) + DecWindowOverlapNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 1) + DecWindowOverlapLongStartNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 2) + DecWindowOverlapShortNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 3) + DecWindowOverlapLongStopNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], + m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + + if (!m_AACDecInfo->sbrEnabled) { + for (i = 0; i < AAC_MAX_NSAMPS; i++) { + *outbuf = CLIPTOSHORT((m_PSInfoBase->sbrWorkBuf[ch][i] + RND_VAL) >> FBITS_OUT_IMDCT); + outbuf += m_AACDecInfo->nChans; + } + } + + m_AACDecInfo->rawSampleBuf[ch] = m_PSInfoBase->sbrWorkBuf[ch]; + m_AACDecInfo->rawSampleBytes = sizeof(int); + m_AACDecInfo->rawSampleFBits = FBITS_OUT_IMDCT; +#else + /* window, overlap-add, round to PCM - optimized for each window sequence */ + if (icsInfo->winSequence == 0) + DecWindowOverlap(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 1) + DecWindowOverlapLongStart(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 2) + DecWindowOverlapShort(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + else if (icsInfo->winSequence == 3) + DecWindowOverlapLongStop(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans, + icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]); + + m_AACDecInfo->rawSampleBuf[ch] = 0; + m_AACDecInfo->rawSampleBytes = 0; + m_AACDecInfo->rawSampleFBits = 0; +#endif + + m_PSInfoBase->prevWinShape[chOut] = icsInfo->winShape; + + return 0; +} + +/*********************************************************************************************************************** + * Function: DecodeICSInfo + * + * Description: decode individual channel stream info + * + * Inputs: sample rate index + * + * Outputs: updated icsInfo struct + * + * Return: none + **********************************************************************************************************************/ +void DecodeICSInfo(ICSInfo_t *icsInfo, int sampRateIdx) +{ + int sfb, g, mask; + + icsInfo->icsResBit = GetBits(1); + icsInfo->winSequence = GetBits(2); + icsInfo->winShape = GetBits(1); + if (icsInfo->winSequence == 2) { + /* short block */ + icsInfo->maxSFB = GetBits(4); + icsInfo->sfGroup = GetBits(7); + icsInfo->numWinGroup = 1; + icsInfo->winGroupLen[0] = 1; + mask = 0x40; /* start with bit 6 */ + for (g = 0; g < 7; g++) { + if (icsInfo->sfGroup & mask) { + icsInfo->winGroupLen[icsInfo->numWinGroup - 1]++; + } else { + icsInfo->numWinGroup++; + icsInfo->winGroupLen[icsInfo->numWinGroup - 1] = 1; + } + mask >>= 1; + } + } else { + /* long block */ + icsInfo->maxSFB = GetBits(6); + icsInfo->predictorDataPresent = GetBits(1); + if (icsInfo->predictorDataPresent) { + icsInfo->predictorReset = GetBits(1); + if (icsInfo->predictorReset) + icsInfo->predictorResetGroupNum = GetBits(5); + for (sfb = 0; sfb < MIN(icsInfo->maxSFB, predSFBMax[sampRateIdx]); sfb++) + icsInfo->predictionUsed[sfb] = GetBits(1); + } + icsInfo->numWinGroup = 1; + icsInfo->winGroupLen[0] = 1; + } +} + +/*********************************************************************************************************************** + * Function: DecodeSectionData + * + * Description: decode section data (scale factor band groupings and + * associated Huffman codebooks) + * + * Inputs: window sequence (short or long blocks) + * number of window groups (1 for long blocks, 1-8 for short blocks) + * max coded scalefactor band + * + * Outputs: index of Huffman codebook for each scalefactor band in each section + * + * Return: none + * + * Notes: sectCB, sectEnd, sfbCodeBook, ordered by window groups for short blocks + **********************************************************************************************************************/ +void DecodeSectionData(int winSequence, int numWinGrp, int maxSFB, uint8_t *sfbCodeBook) +{ + int g, cb, sfb; + int sectLen, sectLenBits, sectLenIncr, sectEscapeVal; + + sectLenBits = (winSequence == 2 ? 3 : 5); + sectEscapeVal = (1 << sectLenBits) - 1; + + for (g = 0; g < numWinGrp; g++) { + sfb = 0; + while (sfb < maxSFB) { + cb = GetBits(4); /* next section codebook */ + sectLen = 0; + do { + sectLenIncr = GetBits(sectLenBits); + sectLen += sectLenIncr; + } while (sectLenIncr == sectEscapeVal); + + sfb += sectLen; + while (sectLen--) + *sfbCodeBook++ = (uint8_t)cb; + } + ASSERT(sfb == maxSFB); + } +} + +/*********************************************************************************************************************** + * Function: DecodeOneScaleFactor + * + * Description: decode one scalefactor using scalefactor Huffman codebook + * + * Inputs: none + * + * Outputs: none + * + * Return: one decoded scalefactor, including index_offset of -60 + **********************************************************************************************************************/ +int DecodeOneScaleFactor() +{ + int nBits, val; + uint32_t bitBuf; + + /* decode next scalefactor from bitstream */ + bitBuf = GetBitsNoAdvance(huffTabScaleFactInfo.maxBits) << (32 - huffTabScaleFactInfo.maxBits); + nBits = DecodeHuffmanScalar(huffTabScaleFact, &huffTabScaleFactInfo, bitBuf, &val); + AdvanceBitstream(nBits); + return val; +} + +/*********************************************************************************************************************** + * Function: DecodeScaleFactors + * + * Description: decode scalefactors, PNS energy, and intensity stereo weights + * + * Inputs: number of window groups (1 for long blocks, 1-8 for short blocks) + * max coded scalefactor band + * global gain (starting value for differential scalefactor coding) + * index of Huffman codebook for each scalefactor band in each section + * + * Outputs: decoded scalefactor for each section + * + * Return: none + * + * Notes: sfbCodeBook, scaleFactors ordered by window groups for short blocks + * for section with codebook 13, scaleFactors buffer has decoded PNS + * energy instead of regular scalefactor + * for section with codebook 14 or 15, scaleFactors buffer has intensity + * stereo weight instead of regular scalefactor + **********************************************************************************************************************/ +void DecodeScaleFactors(int numWinGrp, int maxSFB, int globalGain, + uint8_t *sfbCodeBook, short *scaleFactors) +{ + int g, sfbCB, nrg, npf, val, sf, is; + + /* starting values for differential coding */ + sf = globalGain; + is = 0; + nrg = globalGain - 90 - 256; + npf = 1; + + for (g = 0; g < numWinGrp * maxSFB; g++) { + sfbCB = *sfbCodeBook++; + + if (sfbCB == 14 || sfbCB == 15) { + /* intensity stereo - differential coding */ + val = DecodeOneScaleFactor(); + is += val; + *scaleFactors++ = (short)is; + } else if (sfbCB == 13) { + /* PNS - first energy is directly coded, rest are Huffman coded (npf = noise_pcm_flag) */ + if (npf) { + val = GetBits(9); + npf = 0; + } else { + val = DecodeOneScaleFactor(); + } + nrg += val; + *scaleFactors++ = (short)nrg; + } else if (sfbCB >= 1 && sfbCB <= 11) { + /* regular (non-zero) region - differential coding */ + val = DecodeOneScaleFactor(); + sf += val; + *scaleFactors++ = (short)sf; + } else { + /* inactive scalefactor band if codebook 0 */ + *scaleFactors++ = 0; + } + } +} + +/*********************************************************************************************************************** + * Function: DecodePulseInfo + * + * Description: decode pulse information + * + * Inputs: none + * + * Outputs: updated PulseInfo_t struct + * + * Return: none + **********************************************************************************************************************/ +void DecodePulseInfo(uint8_t ch) +{ + int i; + + m_pulseInfo[ch].numPulse = GetBits(2) + 1; /* add 1 here */ + m_pulseInfo[ch].startSFB = GetBits(6); + for (i = 0; i < m_pulseInfo[ch].numPulse; i++) { + m_pulseInfo[ch].offset[i] = GetBits(5); + m_pulseInfo[ch].amp[i] = GetBits(4); + } +} + +/*********************************************************************************************************************** + * Function: DecodeTNSInfo + * + * Description: decode TNS filter information + * + * Inputs: window sequence (short or long blocks) + * + * Outputs: updated TNSInfo_t struct + * buffer of decoded (signed) TNS filter coefficients + * + * Return: none + **********************************************************************************************************************/ +void DecodeTNSInfo(int winSequence, TNSInfo_t *ti, int8_t *tnsCoef) +{ + int i, w, f, coefBits, compress; + int8_t c, s, n; + uint8_t *filtLength, *filtOrder, *filtDir; + + filtLength = ti->length; + filtOrder = ti->order; + filtDir = ti->dir; + + if (winSequence == 2) { + /* short blocks */ + for (w = 0; w < NWINDOWS_SHORT; w++) { + ti->numFilt[w] = GetBits(1); + if (ti->numFilt[w]) { + ti->coefRes[w] = GetBits(1) + 3; + *filtLength = GetBits(4); + *filtOrder = GetBits(3); + if (*filtOrder) { + *filtDir++ = GetBits(1); + compress = GetBits(1); + coefBits = (int)ti->coefRes[w] - compress; /* 2, 3, or 4 */ + s = sgnMask[coefBits - 2]; + n = negMask[coefBits - 2]; + for (i = 0; i < *filtOrder; i++) { + c = GetBits(coefBits); + if (c & s) c |= n; + *tnsCoef++ = c; + } + } + filtLength++; + filtOrder++; + } + } + } else { + /* long blocks */ + ti->numFilt[0] = GetBits(2); + if (ti->numFilt[0]) + ti->coefRes[0] = GetBits(1) + 3; + for (f = 0; f < ti->numFilt[0]; f++) { + *filtLength = GetBits(6); + *filtOrder = GetBits(5); + if (*filtOrder) { + *filtDir++ = GetBits(1); + compress = GetBits(1); + coefBits = (int)ti->coefRes[0] - compress; /* 2, 3, or 4 */ + s = sgnMask[coefBits - 2]; + n = negMask[coefBits - 2]; + for (i = 0; i < *filtOrder; i++) { + c = GetBits(coefBits); + if (c & s) c |= n; + *tnsCoef++ = c; + } + } + filtLength++; + filtOrder++; + } + } +} + +/* bitstream field lengths for gain control data: + * gainBits[winSequence][0] = maxWindow (how many gain windows there are) + * gainBits[winSequence][1] = locBitsZero (bits for alocCode if window == 0) + * gainBits[winSequence][2] = locBits (bits for alocCode if window != 0) + */ +static const uint8_t gainBits[4][3] = { + {1, 5, 5}, /* long */ + {2, 4, 2}, /* start */ + {8, 2, 2}, /* short */ + {2, 4, 5}, /* stop */ +}; + +/*********************************************************************************************************************** + * Function: DecodeGainControlInfo + * + * Description: decode gain control information (SSR profile only) + * + * Inputs: window sequence (short or long blocks) + * + * Outputs: updated GainControlInfo_t struct + * + * Return: none + **********************************************************************************************************************/ +void DecodeGainControlInfo(int winSequence, GainControlInfo_t *gi) +{ + int bd, wd, ad; + int locBits, locBitsZero, maxWin; + + gi->maxBand = GetBits(2); + maxWin = (int)gainBits[winSequence][0]; + locBitsZero = (int)gainBits[winSequence][1]; + locBits = (int)gainBits[winSequence][2]; + + for (bd = 1; bd <= gi->maxBand; bd++) { + for (wd = 0; wd < maxWin; wd++) { + gi->adjNum[bd][wd] = GetBits(3); + for (ad = 0; ad < gi->adjNum[bd][wd]; ad++) { + gi->alevCode[bd][wd][ad] = GetBits(4); + gi->alocCode[bd][wd][ad] = GetBits(wd == 0 ? locBitsZero : locBits); + } + } + } +} + +/*********************************************************************************************************************** + * Function: DecodeICS + * + * Description: decode individual channel stream + * + * Inputs: index of current channel + * + * Outputs: updated section data, scale factor data, pulse data, TNS data, + * and gain control data + * + * Return: none + **********************************************************************************************************************/ +void DecodeICS(int ch) +{ + int globalGain; + ICSInfo_t *icsInfo; + TNSInfo_t *ti; + GainControlInfo_t *gi; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + globalGain = GetBits(8); + if (!m_PSInfoBase->commonWin) + DecodeICSInfo(icsInfo, m_PSInfoBase->sampRateIdx); + + DecodeSectionData(icsInfo->winSequence, icsInfo->numWinGroup, icsInfo->maxSFB, m_PSInfoBase->sfbCodeBook[ch]); + + DecodeScaleFactors(icsInfo->numWinGroup, icsInfo->maxSFB, globalGain, m_PSInfoBase->sfbCodeBook[ch], + m_PSInfoBase->scaleFactors[ch]); + + m_pulseInfo[ch].pulseDataPresent = GetBits(1); + if (m_pulseInfo[ch].pulseDataPresent) + DecodePulseInfo(ch); + + ti = &m_PSInfoBase->tnsInfo[ch]; + ti->tnsDataPresent = GetBits(1); + if (ti->tnsDataPresent) + DecodeTNSInfo(icsInfo->winSequence, ti, ti->coef); + + gi = &m_PSInfoBase->gainControlInfo[ch]; + gi->gainControlDataPresent = GetBits(1); + if (gi->gainControlDataPresent) + DecodeGainControlInfo(icsInfo->winSequence, gi); +} + +/*********************************************************************************************************************** + * Function: DecodeNoiselessData + * + * Description: decode noiseless data (side info and transform coefficients) + * + * Inputs: double pointer to buffer pointing to start of individual channel stream + * (14496-3, table 4.4.24) + * pointer to bit offset + * pointer to number of valid bits remaining in buf + * index of current channel + * + * Outputs: updated global gain, section data, scale factor data, pulse data, + * TNS data, gain control data, and spectral data + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int DecodeNoiselessData(uint8_t **buf, int *bitOffset, int *bitsAvail, int ch) +{ + int bitsUsed; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + SetBitstreamPointer((*bitsAvail+7) >> 3, *buf); + GetBits(*bitOffset); + + DecodeICS(ch); + + if (icsInfo->winSequence == 2) + DecodeSpectrumShort(ch); + else + DecodeSpectrumLong(ch); + + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += ((bitsUsed + *bitOffset) >> 3); + *bitOffset = ((bitsUsed + *bitOffset) & 0x07); + *bitsAvail -= bitsUsed; + + m_AACDecInfo->sbDeinterleaveReqd[ch] = 0; + m_AACDecInfo->tnsUsed |= m_PSInfoBase->tnsInfo[ch].tnsDataPresent; /* set flag if TNS used for any channel */ + + return ERR_AAC_NONE; +} +/*********************************************************************************************************************** + * Function: DecodeHuffmanScalar + * + * Description: decode one Huffman symbol from bitstream + * + * Inputs: pointers to Huffman table and info struct + * left-aligned bit buffer with >= huffTabInfo->maxBits bits + * + * Outputs: decoded symbol in *val + * + * Return: number of bits in symbol + * + * Notes: assumes canonical Huffman codes: + * first CW always 0, we have "count" CW's of length "nBits" bits + * starting CW for codes of length nBits+1 = + * (startCW[nBits] + count[nBits]) << 1 + * if there are no codes at nBits, then we just keep << 1 each time + * (since count[nBits] = 0) + **********************************************************************************************************************/ +int DecodeHuffmanScalar(const signed short *huffTab, const HuffInfo_t *huffTabInfo, uint32_t bitBuf, int32_t *val) +{ + uint32_t count, start, shift, t; + const uint8_t *countPtr; + const signed short *map; + + map = huffTab + huffTabInfo->offset; + countPtr = huffTabInfo->count; + + start = 0; + count = 0; + shift = 32; + do { + start += count; + start <<= 1; + map += count; + count = *countPtr++; + shift--; + t = (bitBuf >> shift) - start; + } while (t >= count); + + *val = (int32_t)map[t]; + return (countPtr - huffTabInfo->count); +} + +/*********************************************************************************************************************** +* Function: UnpackADTSHeader +* +* Description: parse the ADTS frame header and initialize decoder state, Audio Data Transport Stream +* +* Inputs: double pointer to buffer with complete ADTS frame header (byte aligned) +* header size = 7 bytes, plus 2 if CRC +* +* Outputs: filled in ADTS struct +* updated buffer pointer +* updated bit offset +* updated number of available bits +* +* Return: 0 if successful, error code (< 0) if error +* verify that fixed fields don't change between frames +***********************************************************************************************************************/ +int UnpackADTSHeader(uint8_t **buf, int *bitOffset, int *bitsAvail) +{ + int bitsUsed; + + /* init bitstream reader */ + SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf); + GetBits(*bitOffset); + + /* verify that first 12 bits of header are syncword */ + if (GetBits(12) != 0x0fff) { + return ERR_AAC_INVALID_ADTS_HEADER; + } + + /* fixed fields - should not change from frame to frame */ + m_fhADTS.id = GetBits(1); + m_fhADTS.layer = GetBits(2); + m_fhADTS.protectBit = GetBits(1); + m_fhADTS.profile = GetBits(2); + m_fhADTS.sampRateIdx = GetBits(4); + m_fhADTS.privateBit = GetBits(1); + m_fhADTS.channelConfig = GetBits(3); + m_fhADTS.origCopy = GetBits(1); + m_fhADTS.home = GetBits(1); + + /* variable fields - can change from frame to frame */ + m_fhADTS.copyBit = GetBits(1); + m_fhADTS.copyStart = GetBits(1); + m_fhADTS.frameLength = GetBits(13); + m_fhADTS.bufferFull = GetBits(11); + m_fhADTS.numRawDataBlocks = GetBits(2) + 1; + + /* note - MPEG4 spec, correction 1 changes how CRC is handled when protectBit == 0 and numRawDataBlocks > 1 */ + if (m_fhADTS.protectBit == 0) + m_fhADTS.crcCheckWord = GetBits(16); + + /* byte align */ + ByteAlignBitstream(); /* should always be aligned anyway */ + + /* check validity of header */ + if (m_fhADTS.layer != 0 || m_fhADTS.profile != AAC_PROFILE_LC || + m_fhADTS.sampRateIdx >= NUM_SAMPLE_RATES || m_fhADTS.channelConfig >= NUM_DEF_CHAN_MAPS) + return ERR_AAC_INVALID_ADTS_HEADER; + +#ifndef AAC_ENABLE_MPEG4 + if (m_fhADTS.id != 1) + return ERR_AAC_MPEG4_UNSUPPORTED; +#endif + + + /* update codec info */ + m_PSInfoBase->sampRateIdx = m_fhADTS.sampRateIdx; + if (!m_PSInfoBase->useImpChanMap) + m_PSInfoBase->nChans = channelMapTab[m_fhADTS.channelConfig]; + + /* syntactic element fields will be read from bitstream for each element */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + + /* fill in user-accessible data */ + m_AACDecInfo->bitRate = 0; + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx]; + m_AACDecInfo->id = m_fhADTS.id; + m_AACDecInfo->profile = m_fhADTS.profile; + m_AACDecInfo->sbrEnabled = 0; + m_AACDecInfo->adtsBlocksLeft = m_fhADTS.numRawDataBlocks; + + /* update bitstream reader */ + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + *bitsAvail -= bitsUsed ; + if (*bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: GetADTSChannelMapping +* +* Description: determine the number of channels from implicit mapping rules +* +* Inputs: pointer to start of raw_data_block +* bit offset +* bits available +* +* Outputs: updated number of channels +* +* Return: 0 if successful, error code (< 0) if error +* +* Notes: calculates total number of channels using rules in 14496-3, 4.5.1.2.1 +* does not attempt to deduce speaker geometry +***********************************************************************************************************************/ +int GetADTSChannelMapping(uint8_t *buf, int bitOffset, int bitsAvail) +{ + int ch, nChans, elementChans, err; + + nChans = 0; + do { + /* parse next syntactic element */ + err = DecodeNextElement(&buf, &bitOffset, &bitsAvail); + if (err) + return err; + + elementChans = elementNumChans[m_AACDecInfo->currBlockID]; + nChans += elementChans; + + for (ch = 0; ch < elementChans; ch++) { + err = DecodeNoiselessData(&buf, &bitOffset, &bitsAvail, ch); + if (err) + return err; + } + } while (m_AACDecInfo->currBlockID != AAC_ID_END); + + if (nChans <= 0) + return ERR_AAC_CHANNEL_MAP; + + /* update number of channels in codec state and user-accessible info structs */ + m_PSInfoBase->nChans = nChans; + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_PSInfoBase->useImpChanMap = 1; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: GetNumChannelsADIF +* +* Description: get number of channels from program config elements in an ADIF file +* +* Inputs: array of filled-in program config element structures +* number of PCE's +* +* Outputs: none +* +* Return: total number of channels in file +* -1 if error (invalid number of PCE's or unsupported mode) +***********************************************************************************************************************/ +int GetNumChannelsADIF(int nPCE) +{ + int i, j, nChans; + + if (nPCE < 1 || nPCE > MAX_NUM_PCE_ADIF) + return -1; + + nChans = 0; + for (i = 0; i < nPCE; i++) { + /* for now: only support LC, no channel coupling */ + if (m_pce[i]->profile != AAC_PROFILE_LC || m_pce[i]->numCCE > 0) + return -1; + + /* add up number of channels in all channel elements (assume all single-channel) */ + nChans += m_pce[i]->numFCE; + nChans += m_pce[i]->numSCE; + nChans += m_pce[i]->numBCE; + nChans += m_pce[i]->numLCE; + + /* add one more for every element which is a channel pair */ + for (j = 0; j < m_pce[i]->numFCE; j++) { + if ((m_pce[i]->fce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */ + nChans++; + } + for (j = 0; j < m_pce[i]->numSCE; j++) { + if ((m_pce[i]->sce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */ + nChans++; + } + for (j = 0; j < m_pce[i]->numBCE; j++) { + if ((m_pce[i]->bce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */ + nChans++; + } + + } + + return nChans; +} + +/*********************************************************************************************************************** +* Function: GetSampleRateIdxADIF +* +* Description: get sampling rate index from program config elements in an ADIF file +* +* Inputs: array of filled-in program config element structures +* number of PCE's +* +* Outputs: none +* +* Return: sample rate of file +* -1 if error (invalid number of PCE's or sample rate mismatch) +***********************************************************************************************************************/ +int GetSampleRateIdxADIF(int nPCE) +{ + int i, idx; + + if (nPCE < 1 || nPCE > MAX_NUM_PCE_ADIF) + return -1; + + /* make sure all PCE's have the same sample rate */ + idx = m_pce[0]->sampRateIdx; + for (i = 1; i < nPCE; i++) { + if (m_pce[i]->sampRateIdx != idx) + return -1; + } + + return idx; +} + +/*********************************************************************************************************************** +* Function: UnpackADIFHeader +* +* Description: parse the ADIF file header and initialize decoder state +* +* Inputs: double pointer to buffer with complete ADIF header +* (starting at 'A' in 'ADIF' tag) +* pointer to bit offset +* pointer to number of valid bits remaining in inbuf +* +* Outputs: filled-in ADIF struct +* updated buffer pointer +* updated bit offset +* updated number of available bits +* +* Return: 0 if successful, error code (< 0) if error +***********************************************************************************************************************/ +int UnpackADIFHeader(uint8_t **buf, int *bitOffset, int *bitsAvail) +{ + uint8_t i; + int bitsUsed; + + /* init bitstream reader */ + SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf); + GetBits(*bitOffset); + + /* verify that first 32 bits of header are "ADIF" */ + if (GetBits(8) != 'A' || GetBits(8) != 'D' || GetBits(8) != 'I' || GetBits(8) != 'F') + return ERR_AAC_INVALID_ADIF_HEADER; + + /* read ADIF header fields */ + m_fhADIF.copyBit = GetBits(1); + if (m_fhADIF.copyBit) { + for (i = 0; i < ADIF_COPYID_SIZE; i++) + m_fhADIF.copyID[i] = GetBits(8); + } + m_fhADIF.origCopy = GetBits(1); + m_fhADIF.home = GetBits(1); + m_fhADIF.bsType = GetBits(1); + m_fhADIF.bitRate = GetBits(23); + m_fhADIF.numPCE = GetBits(4) + 1; /* add 1 (so range = [1, 16]) */ + if (m_fhADIF.bsType == 0) + m_fhADIF.bufferFull = GetBits(20); + + /* parse all program config elements */ + for (i = 0; i < m_fhADIF.numPCE; i++) + DecodeProgramConfigElement(i); + + /* byte align */ + ByteAlignBitstream(); + + /* update codec info */ + m_PSInfoBase->nChans = GetNumChannelsADIF(m_fhADIF.numPCE); + m_PSInfoBase->sampRateIdx = GetSampleRateIdxADIF(m_fhADIF.numPCE); + + /* check validity of header */ + if (m_PSInfoBase->nChans < 0 || m_PSInfoBase->sampRateIdx < 0 || m_PSInfoBase->sampRateIdx >= NUM_SAMPLE_RATES) + return ERR_AAC_INVALID_ADIF_HEADER; + + /* syntactic element fields will be read from bitstream for each element */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + + /* fill in user-accessible data */ + m_AACDecInfo->bitRate = 0; + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx]; + m_AACDecInfo->profile = m_pce[0]->profile; + m_AACDecInfo->sbrEnabled = 0; + + /* update bitstream reader */ + bitsUsed = CalcBitsUsed(*buf, *bitOffset); + *buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + *bitsAvail -= bitsUsed ; + if (*bitsAvail < 0) + return ERR_AAC_INDATA_UNDERFLOW; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: SetRawBlockParams +* +* Description: set internal state variables for decoding a stream of raw data blocks +* +* Inputs: flag indicating source of parameters (from previous headers or passed +* explicitly by caller) +* number of channels +* sample rate +* profile ID +* +* Outputs: updated state variables in aacDecInfo +* +* Return: 0 if successful, error code (< 0) if error +* +* Notes: if copyLast == 1, then m_PSInfoBase->nChans, m_PSInfoBase->sampRateIdx, and +* aacDecInfo->profile are not changed (it's assumed that we already +* set them, such as by a previous call to UnpackADTSHeader()) +* if copyLast == 0, then the parameters we passed in are used instead +***********************************************************************************************************************/ +int SetRawBlockParams(int copyLast, int nChans, int sampRate, int profile) +{ + int idx; + + if (!copyLast) { + m_AACDecInfo->profile = profile; + m_PSInfoBase->nChans = nChans; + for (idx = 0; idx < NUM_SAMPLE_RATES; idx++) { + if (sampRate == sampRateTab[idx]) { + m_PSInfoBase->sampRateIdx = idx; + break; + } + } + if (idx == NUM_SAMPLE_RATES) + return ERR_AAC_INVALID_FRAME; + } + m_AACDecInfo->nChans = m_PSInfoBase->nChans; + m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx]; + + /* check validity of header */ + if (m_PSInfoBase->sampRateIdx >= NUM_SAMPLE_RATES || m_PSInfoBase->sampRateIdx < 0 || + m_AACDecInfo->profile != AAC_PROFILE_LC) + return ERR_AAC_RAWBLOCK_PARAMS; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** +* Function: PrepareRawBlock +* +* Description: reset per-block state variables for raw blocks (no ADTS/ADIF headers) +* +* Inputs: none +* +* Outputs: updated state variables in aacDecInfo +* +* Return: 0 if successful, error code (< 0) if error +***********************************************************************************************************************/ +int PrepareRawBlock() +{ + /* syntactic element fields will be read from bitstream for each element */ + m_AACDecInfo->prevBlockID = AAC_ID_INVALID; + m_AACDecInfo->currBlockID = AAC_ID_INVALID; + m_AACDecInfo->currInstTag = -1; + + /* fill in user-accessible data */ + m_AACDecInfo->bitRate = 0; + m_AACDecInfo->sbrEnabled = 0; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: DequantBlock + * + * Description: dequantize one block of transform coefficients (in-place) + * + * Inputs: quantized transform coefficients, range = [0, 8191] + * number of samples to dequantize + * scalefactor for this block of data, range = [0, 256] + * + * Outputs: dequantized transform coefficients in Q(FBITS_OUT_DQ_OFF) + * + * Return: guard bit mask (OR of abs value of all dequantized coefs) + * + * Notes: applies dequant formula y = pow(x, 4.0/3.0) * pow(2, (scale - 100)/4.0) + * * pow(2, FBITS_OUT_DQ_OFF) + * clips outputs to Q(FBITS_OUT_DQ_OFF) + * output has no minimum number of guard bits + **********************************************************************************************************************/ +int DequantBlock(int *inbuf, int nSamps, int scale) +{ + int iSamp, scalef, scalei, x, y, gbMask, shift, tab4[4]; + const uint32_t *tab16, *coef; + + if (nSamps <= 0) + return 0; + + scale -= SF_OFFSET; /* new range = [-100, 156] */ + + /* with two's complement numbers, scalei/scalef factorization works for pos and neg values of scale: + * [+4...+7] >> 2 = +1, [ 0...+3] >> 2 = 0, [-4...-1] >> 2 = -1, [-8...-5] >> 2 = -2 ... + * (-1 & 0x3) = 3, (-2 & 0x3) = 2, (-3 & 0x3) = 1, (0 & 0x3) = 0 + * + * Example: 2^(-5/4) = 2^(-1) * 2^(-1/4) = 2^-2 * 2^(3/4) + */ + tab16 = pow43_14[scale & 0x3]; + scalef = pow14[scale & 0x3]; + scalei = (scale >> 2) + FBITS_OUT_DQ_OFF; + + /* cache first 4 values: + * tab16[j] = Q28 for j = [0,3] + * tab4[x] = x^(4.0/3.0) * 2^(0.25*scale), Q(FBITS_OUT_DQ_OFF) + */ + shift = 28 - scalei; + if (shift > 31) { + tab4[0] = tab4[1] = tab4[2] = tab4[3] = 0; + } else if (shift <= 0) { + shift = -shift; + if (shift > 31) + shift = 31; + for (x = 0; x < 4; x++) { + y = tab16[x]; + if (y > (0x7fffffff >> shift)) + y = 0x7fffffff; /* clip (rare) */ + else + y <<= shift; + tab4[x] = y; + } + } else { + tab4[0] = 0; + tab4[1] = tab16[1] >> shift; + tab4[2] = tab16[2] >> shift; + tab4[3] = tab16[3] >> shift; + } + + gbMask = 0; + do { + iSamp = *inbuf; + x = FASTABS(iSamp); + + if (x < 4) { + y = tab4[x]; + } else { + + if (x < 16) { + /* result: y = Q25 (tab16 = Q25) */ + y = tab16[x]; + shift = 25 - scalei; + } else if (x < 64) { + /* result: y = Q21 (pow43tab[j] = Q23, scalef = Q30) */ + y = pow43[x-16]; + shift = 21 - scalei; + y = MULSHIFT32(y, scalef); + } else { + /* normalize to [0x40000000, 0x7fffffff] + * input x = [64, 8191] = [64, 2^13-1] + * ranges: + * shift = 7: 64 - 127 + * shift = 6: 128 - 255 + * shift = 5: 256 - 511 + * shift = 4: 512 - 1023 + * shift = 3: 1024 - 2047 + * shift = 2: 2048 - 4095 + * shift = 1: 4096 - 8191 + */ + x <<= 17; + shift = 0; + if (x < 0x08000000) + x <<= 4, shift += 4; + if (x < 0x20000000) + x <<= 2, shift += 2; + if (x < 0x40000000) + x <<= 1, shift += 1; + + coef = (x < SQRTHALF) ? poly43lo : poly43hi; + + /* polynomial */ + y = coef[0]; + y = MULSHIFT32(y, x) + coef[1]; + y = MULSHIFT32(y, x) + coef[2]; + y = MULSHIFT32(y, x) + coef[3]; + y = MULSHIFT32(y, x) + coef[4]; + y = MULSHIFT32(y, pow2frac[shift]) << 3; + + /* fractional scale + * result: y = Q21 (pow43tab[j] = Q23, scalef = Q30) + */ + y = MULSHIFT32(y, scalef); /* now y is Q24 */ + shift = 24 - scalei - pow2exp[shift]; + } + + /* integer scale */ + if (shift <= 0) { + shift = -shift; + if (shift > 31) + shift = 31; + + if (y > (0x7fffffff >> shift)) + y = 0x7fffffff; /* clip (rare) */ + else + y <<= shift; + } else { + if (shift > 31) + shift = 31; + y >>= shift; + } + } + + /* sign and store (gbMask used to count GB's) */ + gbMask |= y; + + /* apply sign */ + iSamp >>= 31; + y ^= iSamp; + y -= iSamp; + + *inbuf++ = y; + } while (--nSamps); + + return gbMask; +} + +/*********************************************************************************************************************** + * Function: AACDequantize + * + * Description: dequantize all transform coefficients for one channel + * + * Inputs: index of current channel + * + * Outputs: dequantized coefficients, including short-block deinterleaving + * flags indicating if intensity and/or PNS is active + * minimum guard bit count for dequantized coefficients + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int AACDequantize(int ch) +{ + int gp, cb, sfb, win, width, nSamps, gbMask; + int *coef; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + short *scaleFactors; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + if (icsInfo->winSequence == 2) { + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_SHORT; + } else { + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_LONG; + } + coef = m_PSInfoBase->coef[ch]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + scaleFactors = m_PSInfoBase->scaleFactors[ch]; + + m_PSInfoBase->intensityUsed[ch] = 0; + m_PSInfoBase->pnsUsed[ch] = 0; + gbMask = 0; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + /* dequantize one scalefactor band (not necessary if codebook is intensity or PNS) + * for zero codebook, still run dequantizer in case non-zero pulse data was added + */ + cb = (int)(sfbCodeBook[sfb]); + width = sfbTab[sfb+1] - sfbTab[sfb]; + if (cb >= 0 && cb <= 11) + gbMask |= DequantBlock(coef, width, scaleFactors[sfb]); + else if (cb == 13) + m_PSInfoBase->pnsUsed[ch] = 1; + else if (cb == 14 || cb == 15) + m_PSInfoBase->intensityUsed[ch] = 1; /* should only happen if ch == 1 */ + coef += width; + } + coef += (nSamps - sfbTab[icsInfo->maxSFB]); + } + sfbCodeBook += icsInfo->maxSFB; + scaleFactors += icsInfo->maxSFB; + } + m_AACDecInfo->pnsUsed |= m_PSInfoBase->pnsUsed[ch]; /* set flag if PNS used for any channel */ + + /* calculate number of guard bits in dequantized data */ + m_PSInfoBase->gbCurrent[ch] = CLZ(gbMask) - 1; + + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: DeinterleaveShortBlocks + * + * Description: deinterleave transform coefficients in short blocks for one channel + * + * Inputs: index of current channel + * + * Outputs: deinterleaved coefficients (window groups into 8 separate windows) + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: only necessary if deinterleaving not part of Huffman decoding + **********************************************************************************************************************/ +int DeinterleaveShortBlocks(int ch) +{ +// (void)aacDecInfo; +// (void)ch; + /* not used for this implementation - short block deinterleaving performed during Huffman decoding */ + return ERR_AAC_NONE; +} + +/*********************************************************************************************************************** + * Function: Get32BitVal + * + * Description: generate 32-bit unsigned random number + * + * Inputs: last number calculated (seed, first time through) + * + * Outputs: new number, saved in *last + * + * Return: 32-bit number, uniformly distributed between [0, 2^32) + * + * Notes: uses simple linear congruential generator + **********************************************************************************************************************/ +unsigned int Get32BitVal(unsigned int *last) +{ + uint32_t r = *last; + + /* use same coefs as MPEG reference code (classic LCG) + * use unsigned multiply to force reliable wraparound behavior in C (mod 2^32) + */ + r = (1664525U * r) + 1013904223U; + *last = r; + + return r; +} + +/*********************************************************************************************************************** + * Function: InvRootR + * + * Description: use Newton's method to solve for x = 1/sqrt(r) + * + * Inputs: r in Q30 format, range = [0.25, 1] (normalize inputs to this range) + * + * Outputs: none + * + * Return: x = Q29, range = (1, 2) + * + * Notes: guaranteed to converge and not overflow for any r in this range + * + * xn+1 = xn - f(xn)/f'(xn) + * f(x) = 1/sqrt(r) - x = 0 (find root) + * = 1/x^2 - r + * f'(x) = -2/x^3 + * + * so xn+1 = xn/2 * (3 - r*xn^2) + * + * NUM_ITER_INVSQRT = 3, maxDiff = 1.3747e-02 + * NUM_ITER_INVSQRT = 4, maxDiff = 3.9832e-04 + **********************************************************************************************************************/ +int InvRootR(int r) +{ + int i, xn, t; + + /* use linear equation for initial guess + * x0 = -2*r + 3 (so x0 always >= correct answer in range [0.25, 1)) + * xn = Q29 (at every step) + */ + xn = (MULSHIFT32(r, X0_COEF_2) << 2) + X0_OFF_2; + + for (i = 0; i < NUM_ITER_INVSQRT; i++) { + t = MULSHIFT32(xn, xn); /* Q26 = Q29*Q29 */ + t = Q26_3 - (MULSHIFT32(r, t) << 2); /* Q26 = Q26 - (Q31*Q26 << 1) */ + xn = MULSHIFT32(xn, t) << (6 - 1); /* Q29 = (Q29*Q26 << 6), and -1 for division by 2 */ + } + + /* clip to range (1.0, 2.0) + * (because of rounding, this can converge to xn slightly > 2.0 when r is near 0.25) + */ + if (xn >> 30) + xn = (1 << 30) - 1; + + return xn; +} + +/*********************************************************************************************************************** + * Function: ScaleNoiseVector + * + * Description: apply scaling to vector of noise coefficients for one scalefactor band + * + * Inputs: unscaled coefficients + * number of coefficients in vector (one scalefactor band of coefs) + * scalefactor for this band (i.e. noise energy) + * + * Outputs: nVals coefficients in Q(FBITS_OUT_DQ_OFF) + * + * Return: guard bit mask (OR of abs value of all noise coefs) + **********************************************************************************************************************/ +int ScaleNoiseVector(int *coef, int nVals, int sf) +{ + +/* pow(2, i/4.0) for i = [0,1,2,3], format = Q30 */ +static const int pow14[4] PROGMEM = { + 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 +}; + + int i, c, spec, energy, sq, scalef, scalei, invSqrtEnergy, z, gbMask; + + energy = 0; + for (i = 0; i < nVals; i++) { + spec = coef[i]; + + /* max nVals = max SFB width = 96, so energy can gain < 2^7 bits in accumulation */ + sq = (spec * spec) >> 8; /* spec*spec range = (-2^30, 2^30) */ + energy += sq; + } + + /* unless nVals == 1 (or the number generator is broken...), this should not happen */ + if (energy == 0) + return 0; /* coef[i] must = 0 for i = [0, nVals-1], so gbMask = 0 */ + + /* pow(2, sf/4) * pow(2, FBITS_OUT_DQ_OFF) */ + scalef = pow14[sf & 0x3]; + scalei = (sf >> 2) + FBITS_OUT_DQ_OFF; + + /* energy has implied factor of 2^-8 since we shifted the accumulator + * normalize energy to range [0.25, 1.0), calculate 1/sqrt(1), and denormalize + * i.e. divide input by 2^(30-z) and convert to Q30 + * output of 1/sqrt(i) now has extra factor of 2^((30-z)/2) + * for energy > 0, z is an even number between 0 and 28 + * final scaling of invSqrtEnergy: + * 2^(15 - z/2) to compensate for implicit 2^(30-z) factor in input + * +4 to compensate for implicit 2^-8 factor in input + */ + z = CLZ(energy) - 2; /* energy has at least 2 leading zeros (see acc loop) */ + z &= 0xfffffffe; /* force even */ + invSqrtEnergy = InvRootR(energy << z); /* energy << z must be in range [0x10000000, 0x40000000] */ + scalei -= (15 - z/2 + 4); /* nInt = 1/sqrt(energy) in Q29 */ + + /* normalize for final scaling */ + z = CLZ(invSqrtEnergy) - 1; + invSqrtEnergy <<= z; + scalei -= (z - 3 - 2); /* -2 for scalef, z-3 for invSqrtEnergy */ + scalef = MULSHIFT32(scalef, invSqrtEnergy); /* scalef (input) = Q30, invSqrtEnergy = Q29 * 2^z */ + gbMask = 0; + + if (scalei < 0) { + scalei = -scalei; + if (scalei > 31) + scalei = 31; + for (i = 0; i < nVals; i++) { + c = MULSHIFT32(coef[i], scalef) >> scalei; + gbMask |= FASTABS(c); + coef[i] = c; + } + } else { + /* for scalei <= 16, no clipping possible (coef[i] is < 2^15 before scaling) + * for scalei > 16, just saturate exponent (rare) + * scalef is close to full-scale (since we normalized invSqrtEnergy) + * remember, we are just producing noise here + */ + if (scalei > 16) + scalei = 16; + for (i = 0; i < nVals; i++) { + c = MULSHIFT32(coef[i] << scalei, scalef); + coef[i] = c; + gbMask |= FASTABS(c); + } + } + + return gbMask; +} + +/*********************************************************************************************************************** + * Function: GenerateNoiseVector + * + * Description: create vector of noise coefficients for one scalefactor band + * + * Inputs: seed for number generator + * number of coefficients to generate + * + * Outputs: buffer of nVals coefficients, range = [-2^15, 2^15) + * updated seed for number generator + * + * Return: none + **********************************************************************************************************************/ +void GenerateNoiseVector(int *coef, int *last, int nVals) +{ + int i; + + for (i = 0; i < nVals; i++) + coef[i] = ((int32_t)Get32BitVal((uint32_t *)last)) >> 16; +} + +/*********************************************************************************************************************** + * Function: CopyNoiseVector + * + * Description: copy vector of noise coefficients for one scalefactor band from L to R + * + * Inputs: buffer of left coefficients + * number of coefficients to copy + * + * Outputs: buffer of right coefficients + * + * Return: none + **********************************************************************************************************************/ +void CopyNoiseVector(int *coefL, int *coefR, int nVals) +{ + int i; + + for (i = 0; i < nVals; i++) + coefR[i] = coefL[i]; +} + +/*********************************************************************************************************************** + * Function: PNS + * + * Description: apply perceptual noise substitution, if enabled (MPEG-4 only) + * + * Inputs: index of current channel + * + * Outputs: shaped noise in scalefactor bands where PNS is active + * updated minimum guard bit count for this channel + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int PNS(int ch) +{ + int gp, sfb, win, width, nSamps, gb, gbMask; + int *coef; + const uint16_t *sfbTab; + uint8_t *sfbCodeBook; + short *scaleFactors; + int msMaskOffset, checkCorr, genNew; + uint8_t msMask; + uint8_t *msMaskPtr; + ICSInfo_t *icsInfo; + + icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]); + + if (!m_PSInfoBase->pnsUsed[ch]) + return 0; + + if (icsInfo->winSequence == 2) { + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_SHORT; + } else { + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_LONG; + } + coef = m_PSInfoBase->coef[ch]; + sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch]; + scaleFactors = m_PSInfoBase->scaleFactors[ch]; + checkCorr = (m_AACDecInfo->currBlockID == AAC_ID_CPE && m_PSInfoBase->commonWin == 1 ? 1 : 0); + + gbMask = 0; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + msMaskPtr = m_PSInfoBase->msMaskBits + ((gp*icsInfo->maxSFB) >> 3); + msMaskOffset = ((gp*icsInfo->maxSFB) & 0x07); + msMask = (*msMaskPtr++) >> msMaskOffset; + + for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) { + width = sfbTab[sfb+1] - sfbTab[sfb]; + if (sfbCodeBook[sfb] == 13) { + if (ch == 0) { + /* generate new vector, copy into ch 1 if it's possible that the channels will be correlated + * if ch 1 has PNS enabled for this SFB but it's uncorrelated (i.e. ms_used == 0), + * the copied values will be overwritten when we process ch 1 + */ + GenerateNoiseVector(coef, &m_PSInfoBase->pnsLastVal, width); + if (checkCorr && m_PSInfoBase->sfbCodeBook[1][gp*icsInfo->maxSFB + sfb] == 13) + CopyNoiseVector(coef, m_PSInfoBase->coef[1] + (coef - m_PSInfoBase->coef[0]), width); + } else { + /* generate new vector if no correlation between channels */ + genNew = 1; + if (checkCorr && m_PSInfoBase->sfbCodeBook[0][gp*icsInfo->maxSFB + sfb] == 13) { + if((m_PSInfoBase->msMaskPresent==1 && (msMask & 0x01)) || m_PSInfoBase->msMaskPresent == 2 ) + genNew = 0; + } + if (genNew) + GenerateNoiseVector(coef, &m_PSInfoBase->pnsLastVal, width); + } + gbMask |= ScaleNoiseVector(coef, width, m_PSInfoBase->scaleFactors[ch][gp*icsInfo->maxSFB + sfb]); + } + coef += width; + + /* get next mask bit (should be branchless on ARM) */ + msMask >>= 1; + if (++msMaskOffset == 8) { + msMask = *msMaskPtr++; + msMaskOffset = 0; + } + } + coef += (nSamps - sfbTab[icsInfo->maxSFB]); + } + sfbCodeBook += icsInfo->maxSFB; + scaleFactors += icsInfo->maxSFB; + } + + /* update guard bit count if necessary */ + gb = CLZ(gbMask) - 1; + if (m_PSInfoBase->gbCurrent[ch] > gb) + m_PSInfoBase->gbCurrent[ch] = gb; + + return 0; +} + +/*********************************************************************************************************************** + * Function: GetSampRateIdx + * + * Description: get index of given sample rate + * + * Inputs: sample rate (in Hz) + * + * Outputs: none + * + * Return: index of sample rate (table 1.15 in 14496-3:2001(E)) + * -1 if sample rate not found in table + **********************************************************************************************************************/ +int GetSampRateIdx(int sampRate) +{ + int idx; + + for (idx = 0; idx < NUM_SAMPLE_RATES; idx++) { + if (sampRate == sampRateTab[idx]) + return idx; + } + + return -1; +} + +/*********************************************************************************************************************** + * Function: StereoProcessGroup + * + * Description: apply mid-side and intensity stereo to group of transform coefficients + * + * Inputs: dequantized transform coefficients for both channels + * pointer to appropriate scalefactor band table + * mid-side mask enabled flag + * buffer with mid-side mask (one bit for each scalefactor band) + * bit offset into mid-side mask buffer + * max coded scalefactor band + * buffer of codebook indices for right channel + * buffer of scalefactors for right channel, range = [0, 256] + * + * Outputs: updated transform coefficients in Q(FBITS_OUT_DQ_OFF) + * updated minimum guard bit count for both channels + * + * Return: none + * + * Notes: assume no guard bits in input + * gains 0 int bits + **********************************************************************************************************************/ +void StereoProcessGroup(int *coefL, int *coefR, const uint16_t *sfbTab, + int msMaskPres, uint8_t *msMaskPtr, int msMaskOffset, int maxSFB, + uint8_t *cbRight, short *sfRight, int *gbCurrent) +{ +//fb +static const uint32_t pow14[2][4] PROGMEM = { + { 0xc0000000, 0xb3e407d7, 0xa57d8666, 0x945d819b }, + { 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 } +}; + + int sfb, width, cbIdx, sf, cl, cr, scalef, scalei; + int gbMaskL, gbMaskR; + uint8_t msMask; + + msMask = (*msMaskPtr++) >> msMaskOffset; + gbMaskL = 0; + gbMaskR = 0; + + for (sfb = 0; sfb < maxSFB; sfb++) { + width = sfbTab[sfb+1] - sfbTab[sfb]; /* assume >= 0 (see sfBandTabLong/sfBandTabShort) */ + cbIdx = cbRight[sfb]; + + if (cbIdx == 14 || cbIdx == 15) { + /* intensity stereo */ + if (msMaskPres == 1 && (msMask & 0x01)) + cbIdx ^= 0x01; /* invert_intensity(): 14 becomes 15, or 15 becomes 14 */ + sf = -sfRight[sfb]; /* negative since we use identity 0.5^(x) = 2^(-x) (see spec) */ + cbIdx &= 0x01; /* choose - or + scale factor */ + scalef = pow14[cbIdx][sf & 0x03]; + scalei = (sf >> 2) + 2; /* +2 to compensate for scalef = Q30 */ + + if (scalei > 0) { + if (scalei > 30) + scalei = 30; + do { + cr = MULSHIFT32(*coefL++, scalef); + {int sign = (cr) >> 31; if (sign != (cr) >> (31-scalei)) {(cr) = sign ^ ((1 << (31-scalei)) - 1);}} + cr <<= scalei; + gbMaskR |= FASTABS(cr); + *coefR++ = cr; + } while (--width); + } else { + scalei = -scalei; + if (scalei > 31) + scalei = 31; + do { + cr = MULSHIFT32(*coefL++, scalef) >> scalei; + gbMaskR |= FASTABS(cr); + *coefR++ = cr; + } while (--width); + } + } else if ( cbIdx != 13 && ((msMaskPres == 1 && (msMask & 0x01)) || msMaskPres == 2) ) { + /* mid-side stereo (assumes no GB in inputs) */ + do { + cl = *coefL; + cr = *coefR; + + if ( (FASTABS(cl) | FASTABS(cr)) >> 30 ) { + /* avoid overflow (rare) */ + cl >>= 1; + sf = cl + (cr >> 1); + {int sign = (sf) >> 31; if (sign != (sf) >> (30)) {(sf) = sign ^ ((1 << (30)) - 1);}} + sf <<= 1; + cl = cl - (cr >> 1); + {int sign = (cl) >> 31; if (sign != (cl) >> (30)) {(cl) = sign ^ ((1 << (30)) - 1);}} + cl <<= 1; + } else { + /* usual case */ + sf = cl + cr; + cl -= cr; + } + + *coefL++ = sf; + gbMaskL |= FASTABS(sf); + *coefR++ = cl; + gbMaskR |= FASTABS(cl); + } while (--width); + + } else { + /* nothing to do */ + coefL += width; + coefR += width; + } + + /* get next mask bit (should be branchless on ARM) */ + msMask >>= 1; + if (++msMaskOffset == 8) { + msMask = *msMaskPtr++; + msMaskOffset = 0; + } + } + + cl = CLZ(gbMaskL) - 1; + if (gbCurrent[0] > cl) + gbCurrent[0] = cl; + + cr = CLZ(gbMaskR) - 1; + if (gbCurrent[1] > cr) + gbCurrent[1] = cr; + + return; +} + +/*********************************************************************************************************************** + * Function: StereoProcess + * + * Description: apply mid-side and intensity stereo, if enabled + * + * Inputs: none + * + * Outputs: updated transform coefficients in Q(FBITS_OUT_DQ_OFF) + * updated minimum guard bit count for both channels + * + * Return: 0 if successful, -1 if error + **********************************************************************************************************************/ +int StereoProcess() +{ + ICSInfo_t *icsInfo; + int gp, win, nSamps, msMaskOffset; + int *coefL, *coefR; + uint8_t *msMaskPtr; + const uint16_t *sfbTab; + + + /* mid-side and intensity stereo require common_window == 1 (see MPEG4 spec, Correction 2, 2004) */ + if (m_PSInfoBase->commonWin != 1 || m_AACDecInfo->currBlockID != AAC_ID_CPE) + return 0; + + /* nothing to do */ + if (!m_PSInfoBase->msMaskPresent && !m_PSInfoBase->intensityUsed[1]) + return 0; + + icsInfo = &(m_PSInfoBase->icsInfo[0]); + if (icsInfo->winSequence == 2) { + sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_SHORT; + } else { + sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx]; + nSamps = NSAMPS_LONG; + } + coefL = m_PSInfoBase->coef[0]; + coefR = m_PSInfoBase->coef[1]; + + /* do fused mid-side/intensity processing for each block (one long or eight short) */ + msMaskOffset = 0; + msMaskPtr = m_PSInfoBase->msMaskBits; + for (gp = 0; gp < icsInfo->numWinGroup; gp++) { + for (win = 0; win < icsInfo->winGroupLen[gp]; win++) { + StereoProcessGroup(coefL, coefR, sfbTab, m_PSInfoBase->msMaskPresent, + msMaskPtr, msMaskOffset, icsInfo->maxSFB, m_PSInfoBase->sfbCodeBook[1] + gp*icsInfo->maxSFB, + m_PSInfoBase->scaleFactors[1] + gp*icsInfo->maxSFB, m_PSInfoBase->gbCurrent); + coefL += nSamps; + coefR += nSamps; + } + /* we use one bit per sfb, so there are maxSFB bits for each window group */ + msMaskPtr += (msMaskOffset + icsInfo->maxSFB) >> 3; + msMaskOffset = (msMaskOffset + icsInfo->maxSFB) & 0x07; + } + + ASSERT(coefL == m_PSInfoBase->coef[0] + 1024); + ASSERT(coefR == m_PSInfoBase->coef[1] + 1024); + + return 0; +} + +/*********************************************************************************************************************** + * Function: RatioPowInv + * + * Description: use Taylor (MacLaurin) series expansion to calculate (a/b) ^ (1/c) + * + * Inputs: a = [1, 64], b = [1, 64], c = [1, 64], a >= b + * + * Outputs: none + * + * Return: y = Q24, range ~= [0.015625, 64] + **********************************************************************************************************************/ +int RatioPowInv(int a, int b, int c) +{ + int lna, lnb, i, p, t, y; + + if (a < 1 || b < 1 || c < 1 || a > 64 || b > 64 || c > 64 || a < b) + return 0; + + lna = MULSHIFT32(log2Tab[a], LOG2_EXP_INV) << 1; /* ln(a), Q28 */ + lnb = MULSHIFT32(log2Tab[b], LOG2_EXP_INV) << 1; /* ln(b), Q28 */ + p = (lna - lnb) / c; /* Q28 */ + + /* sum in Q24 */ + y = (1 << 24); + t = p >> 4; /* t = p^1 * 1/1! (Q24)*/ + y += t; + + for (i = 2; i <= NUM_TERMS_RPI; i++) { + t = MULSHIFT32(invTab[i-1], t) << 2; + t = MULSHIFT32(p, t) << 4; /* t = p^i * 1/i! (Q24) */ + y += t; + } + + return y; +} + +/*********************************************************************************************************************** + * Function: SqrtFix + * + * Description: use binary search to calculate sqrt(q) + * + * Inputs: q = Q30 + * number of fraction bits in input + * + * Outputs: number of fraction bits in output + * + * Return: lo = Q(fBitsOut) + * + * Notes: absolute precision varies depending on fBitsIn + * normalizes input to range [0x200000000, 0x7fffffff] and takes + * floor(sqrt(input)), and sets fBitsOut appropriately + **********************************************************************************************************************/ +int SqrtFix(int q, int fBitsIn, int *fBitsOut) +{ + int z, lo, hi, mid; + + if (q <= 0) { + *fBitsOut = fBitsIn; + return 0; + } + + /* force even fBitsIn */ + z = fBitsIn & 0x01; + q >>= z; + fBitsIn -= z; + + /* for max precision, normalize to [0x20000000, 0x7fffffff] */ + z = (CLZ(q) - 1); + z >>= 1; + q <<= (2*z); + + /* choose initial bounds */ + lo = 1; + if (q >= 0x10000000) + lo = 16384; /* (int)sqrt(0x10000000) */ + hi = 46340; /* (int)sqrt(0x7fffffff) */ + + /* do binary search with 32x32->32 multiply test */ + do { + mid = (lo + hi) >> 1; + if (mid*mid > q) + hi = mid - 1; + else + lo = mid + 1; + } while (hi >= lo); + lo--; + + *fBitsOut = ((fBitsIn + 2*z) >> 1); + return lo; +} + +/*********************************************************************************************************************** + * Function: InvRNormalized + * + * Description: use Newton's method to solve for x = 1/r + * + * Inputs: r = Q31, range = [0.5, 1) (normalize your inputs to this range) + * + * Outputs: none + * + * Return: x = Q29, range ~= [1.0, 2.0] + * + * Notes: guaranteed to converge and not overflow for any r in [0.5, 1) + * + * xn+1 = xn - f(xn)/f'(xn) + * f(x) = 1/r - x = 0 (find root) + * = 1/x - r + * f'(x) = -1/x^2 + * + * so xn+1 = xn - (1/xn - r) / (-1/xn^2) + * = xn * (2 - r*xn) + * + * NUM_ITER_IRN = 2, maxDiff = 6.2500e-02 (precision of about 4 bits) + * NUM_ITER_IRN = 3, maxDiff = 3.9063e-03 (precision of about 8 bits) + * NUM_ITER_IRN = 4, maxDiff = 1.5288e-05 (precision of about 16 bits) + * NUM_ITER_IRN = 5, maxDiff = 3.0034e-08 (precision of about 24 bits) + **********************************************************************************************************************/ +int InvRNormalized(int r) +{ + int i, xn, t; + + /* r = [0.5, 1.0) + * 1/r = (1.0, 2.0] + * so use 1.5 as initial guess + */ + xn = Q28_15; + + /* xn = xn*(2.0 - r*xn) */ + for (i = NUM_ITER_IRN; i != 0; i--) { + t = MULSHIFT32(r, xn); /* Q31*Q29 = Q28 */ + t = Q28_2 - t; /* Q28 */ + xn = MULSHIFT32(xn, t) << 4; /* Q29*Q28 << 4 = Q29 */ + } + + return xn; +} + + + +/*********************************************************************************************************************** + * Function: BitReverse32 + * + * Description: Ken's fast in-place bit reverse + * + * Inputs: buffer of 32 complex samples + * + * Outputs: bit-reversed samples in same buffer + * + * Return: none +***********************************************************************************************************************/ +void BitReverse32(int *inout) +{ + int t; + t=inout[2] ; inout[2]=inout[32]; inout[32]=t; + t=inout[3] ; inout[3]=inout[33]; inout[33]=t; + + t=inout[4] ; inout[4]=inout[16]; inout[16]=t; + t=inout[5] ; inout[5]=inout[17]; inout[17]=t; + + t=inout[6] ; inout[6]=inout[48]; inout[48]=t; + t=inout[7] ; inout[7]=inout[49]; inout[49]=t; + + t=inout[10]; inout[10]=inout[40]; inout[40]=t; + t=inout[11]; inout[11]=inout[41]; inout[41]=t; + + t=inout[12]; inout[12]=inout[24]; inout[24]=t; + t=inout[13]; inout[13]=inout[25]; inout[25]=t; + + t=inout[14]; inout[14]=inout[56]; inout[56]=t; + t=inout[15]; inout[15]=inout[57]; inout[57]=t; + + t=inout[18]; inout[18]=inout[36]; inout[36]=t; + t=inout[19]; inout[19]=inout[37]; inout[37]=t; + + t=inout[22]; inout[22]=inout[52]; inout[52]=t; + t=inout[23]; inout[23]=inout[53]; inout[53]=t; + + t=inout[26]; inout[26]=inout[44]; inout[44]=t; + t=inout[27]; inout[27]=inout[45]; inout[45]=t; + + t=inout[30]; inout[30]=inout[60]; inout[60]=t; + t=inout[31]; inout[31]=inout[61]; inout[61]=t; + + t=inout[38]; inout[38]=inout[50]; inout[50]=t; + t=inout[39]; inout[39]=inout[51]; inout[51]=t; + + t=inout[46]; inout[46]=inout[58]; inout[58]=t; + t=inout[47]; inout[47]=inout[59]; inout[59]=t; + +} + +/*********************************************************************************************************************** + * Function: R8FirstPass32 + * + * Description: radix-8 trivial pass for decimation-in-time FFT (log2(N) = 5) + * + * Inputs: buffer of (bit-reversed) samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 3 guard bits, gains 1 integer bit + * guard bits out = guard bits in - 3 (if inputs are full scale) + * or guard bits in - 2 (if inputs bounded to +/- sqrt(2)/2) + * see scaling comments in fft.c for base AAC + * should compile with no stack spills on ARM (verify compiled output) + * current instruction count (per pass): 16 LDR, 16 STR, 4 SMULL, 61 ALU + **********************************************************************************************************************/ +void R8FirstPass32(int *r0) +{ + int r1, r2, r3, r4, r5, r6, r7; + int r8, r9, r10, r11, r12, r14; + + /* number of passes = fft size / 8 = 32 / 8 = 4 */ + r1 = (32 >> 3); + do { + + r2 = r0[8]; + r3 = r0[9]; + r4 = r0[10]; + r5 = r0[11]; + r6 = r0[12]; + r7 = r0[13]; + r8 = r0[14]; + r9 = r0[15]; + + r10 = r2 + r4; + r11 = r3 + r5; + r12 = r6 + r8; + r14 = r7 + r9; + + r2 -= r4; + r3 -= r5; + r6 -= r8; + r7 -= r9; + + r4 = r2 - r7; + r5 = r2 + r7; + r8 = r3 - r6; + r9 = r3 + r6; + + r2 = r4 - r9; + r3 = r4 + r9; + r6 = r5 - r8; + r7 = r5 + r8; + + r2 = MULSHIFT32(SQRTHALF, r2); /* can use r4, r5, r8, or r9 for constant and lo32 scratch reg */ + r3 = MULSHIFT32(SQRTHALF, r3); + r6 = MULSHIFT32(SQRTHALF, r6); + r7 = MULSHIFT32(SQRTHALF, r7); + + r4 = r10 + r12; + r5 = r10 - r12; + r8 = r11 + r14; + r9 = r11 - r14; + + r10 = r0[0]; + r11 = r0[2]; + r12 = r0[4]; + r14 = r0[6]; + + r10 += r11; + r12 += r14; + + r4 >>= 1; + r10 += r12; + r4 += (r10 >> 1); + r0[ 0] = r4; + r4 -= (r10 >> 1); + r4 = (r10 >> 1) - r4; + r0[ 8] = r4; + + r9 >>= 1; + r10 -= 2*r12; + r4 = (r10 >> 1) + r9; + r0[ 4] = r4; + r4 = (r10 >> 1) - r9; + r0[12] = r4; + r10 += r12; + + r10 -= 2*r11; + r12 -= 2*r14; + + r4 = r0[1]; + r9 = r0[3]; + r11 = r0[5]; + r14 = r0[7]; + + r4 += r9; + r11 += r14; + + r8 >>= 1; + r4 += r11; + r8 += (r4 >> 1); + r0[ 1] = r8; + r8 -= (r4 >> 1); + r8 = (r4 >> 1) - r8; + r0[ 9] = r8; + + r5 >>= 1; + r4 -= 2*r11; + r8 = (r4 >> 1) - r5; + r0[ 5] = r8; + r8 = (r4 >> 1) + r5; + r0[13] = r8; + r4 += r11; + + r4 -= 2*r9; + r11 -= 2*r14; + + r9 = r10 - r11; + r10 += r11; + r14 = r4 + r12; + r4 -= r12; + + r5 = (r10 >> 1) + r7; + r8 = (r4 >> 1) - r6; + r0[ 2] = r5; + r0[ 3] = r8; + + r5 = (r9 >> 1) - r2; + r8 = (r14 >> 1) - r3; + r0[ 6] = r5; + r0[ 7] = r8; + + r5 = (r10 >> 1) - r7; + r8 = (r4 >> 1) + r6; + r0[10] = r5; + r0[11] = r8; + + r5 = (r9 >> 1) + r2; + r8 = (r14 >> 1) + r3; + r0[14] = r5; + r0[15] = r8; + + r0 += 16; + r1--; + } while (r1 != 0); +} + +/*********************************************************************************************************************** + * Function: R4Core32 + * + * Description: radix-4 pass for 32-point decimation-in-time FFT + * + * Inputs: buffer of samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: gain 2 integer bits + * guard bits out = guard bits in - 1 (if inputs are full scale) + * see scaling comments in fft.c for base AAC + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + * should compile with no stack spills on ARM (verify compiled output) + * current instruction count (per pass): 16 LDR, 16 STR, 4 SMULL, 61 ALU + **********************************************************************************************************************/ +void R4Core32(int *r0) +{ + int r2, r3, r4, r5, r6, r7; + int r8, r9, r10, r12, r14; + int *r1; + + r1 = (int *)twidTabOdd32; + r10 = 8; + do { + /* can use r14 for lo32 scratch register in all MULSHIFT32 */ + r2 = r1[0]; + r3 = r1[1]; + r4 = r0[16]; + r5 = r0[17]; + r12 = r4 + r5; + r12 = MULSHIFT32(r3, r12); + r5 = MULSHIFT32(r2, r5) + r12; + r2 += 2*r3; + r4 = MULSHIFT32(r2, r4) - r12; + + r2 = r1[2]; + r3 = r1[3]; + r6 = r0[32]; + r7 = r0[33]; + r12 = r6 + r7; + r12 = MULSHIFT32(r3, r12); + r7 = MULSHIFT32(r2, r7) + r12; + r2 += 2*r3; + r6 = MULSHIFT32(r2, r6) - r12; + + r2 = r1[4]; + r3 = r1[5]; + r8 = r0[48]; + r9 = r0[49]; + r12 = r8 + r9; + r12 = MULSHIFT32(r3, r12); + r9 = MULSHIFT32(r2, r9) + r12; + r2 += 2*r3; + r8 = MULSHIFT32(r2, r8) - r12; + + r2 = r0[0]; + r3 = r0[1]; + + r12 = r6 + r8; + r8 = r6 - r8; + r14 = r9 - r7; + r9 = r9 + r7; + + r6 = (r2 >> 2) - r4; + r7 = (r3 >> 2) - r5; + r4 += (r2 >> 2); + r5 += (r3 >> 2); + + r2 = r4 + r12; + r3 = r5 + r9; + r0[0] = r2; + r0[1] = r3; + r2 = r6 - r14; + r3 = r7 - r8; + r0[16] = r2; + r0[17] = r3; + r2 = r4 - r12; + r3 = r5 - r9; + r0[32] = r2; + r0[33] = r3; + r2 = r6 + r14; + r3 = r7 + r8; + r0[48] = r2; + r0[49] = r3; + + r0 += 2; + r1 += 6; + r10--; + } while (r10 != 0); +} + +/*********************************************************************************************************************** + * Function: FFT32C + * + * Description: Ken's very fast in-place radix-4 decimation-in-time FFT + * + * Inputs: buffer of 32 complex samples (before bit-reversal) + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: assumes 3 guard bits in, gains 3 integer bits + * guard bits out = guard bits in - 2 + * (guard bit analysis includes assumptions about steps immediately + * before and after, i.e. PreMul and PostMul for DCT) + **********************************************************************************************************************/ +void FFT32C(int *x) +{ + /* decimation in time */ + BitReverse32(x); + + /* 32-point complex FFT */ + R8FirstPass32(x); /* gain 1 int bit, lose 2 GB (making assumptions about input) */ + R4Core32(x); /* gain 2 int bits, lose 0 GB (making assumptions about input) */ +} + +/*********************************************************************************************************************** + * Function: CVKernel1 + * + * Description: kernel of covariance matrix calculation for p01, p11, p12, p22 + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * + * Outputs: 64-bit accumulators for p01re, p01im, p12re, p12im, p11re, p22re + * stored in accBuf + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrcov.s when building for ARM! + **********************************************************************************************************************/ +void CVKernel1(int *XBuf, int *accBuf) +{ + U64 p01re, p01im, p12re, p12im, p11re, p22re; + int n, x0re, x0im, x1re, x1im; + + x0re = XBuf[0]; + x0im = XBuf[1]; + XBuf += (2*64); + x1re = XBuf[0]; + x1im = XBuf[1]; + XBuf += (2*64); + + p01re.w64 = p01im.w64 = 0; + p12re.w64 = p12im.w64 = 0; + p11re.w64 = 0; + p22re.w64 = 0; + + p12re.w64 = MADD64(p12re.w64, x1re, x0re); + p12re.w64 = MADD64(p12re.w64, x1im, x0im); + p12im.w64 = MADD64(p12im.w64, x0re, x1im); + p12im.w64 = MADD64(p12im.w64, -x0im, x1re); + p22re.w64 = MADD64(p22re.w64, x0re, x0re); + p22re.w64 = MADD64(p22re.w64, x0im, x0im); + for (n = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6); n != 0; n--) { + /* 4 input, 3*2 acc, 1 ptr, 1 loop counter = 12 registers (use same for x0im, -x0im) */ + x0re = x1re; + x0im = x1im; + x1re = XBuf[0]; + x1im = XBuf[1]; + + p01re.w64 = MADD64(p01re.w64, x1re, x0re); + p01re.w64 = MADD64(p01re.w64, x1im, x0im); + p01im.w64 = MADD64(p01im.w64, x0re, x1im); + p01im.w64 = MADD64(p01im.w64, -x0im, x1re); + p11re.w64 = MADD64(p11re.w64, x0re, x0re); + p11re.w64 = MADD64(p11re.w64, x0im, x0im); + + XBuf += (2*64); + } + /* these can be derived by slight changes to account for boundary conditions */ + p12re.w64 += p01re.w64; + p12re.w64 = MADD64(p12re.w64, x1re, -x0re); + p12re.w64 = MADD64(p12re.w64, x1im, -x0im); + p12im.w64 += p01im.w64; + p12im.w64 = MADD64(p12im.w64, x0re, -x1im); + p12im.w64 = MADD64(p12im.w64, x0im, x1re); + p22re.w64 += p11re.w64; + p22re.w64 = MADD64(p22re.w64, x0re, -x0re); + p22re.w64 = MADD64(p22re.w64, x0im, -x0im); + + accBuf[0] = p01re.r.lo32; accBuf[1] = p01re.r.hi32; + accBuf[2] = p01im.r.lo32; accBuf[3] = p01im.r.hi32; + accBuf[4] = p11re.r.lo32; accBuf[5] = p11re.r.hi32; + accBuf[6] = p12re.r.lo32; accBuf[7] = p12re.r.hi32; + accBuf[8] = p12im.r.lo32; accBuf[9] = p12im.r.hi32; + accBuf[10] = p22re.r.lo32; accBuf[11] = p22re.r.hi32; +} + +/*********************************************************************************************************************** + * Function: CVKernel2 + * + * Description: kernel of covariance matrix calculation for p02 + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * + * Outputs: 64-bit accumulators for p02re, p02im stored in accBuf + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrcov.s when building for ARM! + **********************************************************************************************************************/ +void CVKernel2(int *XBuf, int *accBuf) +{ + U64 p02re, p02im; + int n, x0re, x0im, x1re, x1im, x2re, x2im; + + p02re.w64 = p02im.w64 = 0; + + x0re = XBuf[0]; + x0im = XBuf[1]; + XBuf += (2*64); + x1re = XBuf[0]; + x1im = XBuf[1]; + XBuf += (2*64); + + for (n = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6); n != 0; n--) { + /* 6 input, 2*2 acc, 1 ptr, 1 loop counter = 12 registers (use same for x0im, -x0im) */ + x2re = XBuf[0]; + x2im = XBuf[1]; + + p02re.w64 = MADD64(p02re.w64, x2re, x0re); + p02re.w64 = MADD64(p02re.w64, x2im, x0im); + p02im.w64 = MADD64(p02im.w64, x0re, x2im); + p02im.w64 = MADD64(p02im.w64, -x0im, x2re); + + x0re = x1re; + x0im = x1im; + x1re = x2re; + x1im = x2im; + XBuf += (2*64); + } + + accBuf[0] = p02re.r.lo32; + accBuf[1] = p02re.r.hi32; + accBuf[2] = p02im.r.lo32; + accBuf[3] = p02im.r.hi32; +} + +/*********************************************************************************************************************** + * Function: SetBitstreamPointer + * + * Description: initialize bitstream reader + * + * Inputs: number of bytes in bitstream + * pointer to byte-aligned buffer of data to read from + * + * Outputs: initialized bitstream info struct + * + * Return: none + **********************************************************************************************************************/ +void SetBitstreamPointer(int nBytes, uint8_t *buf) +{ + /* init bitstream */ + m_aac_BitStreamInfo.bytePtr = buf; + m_aac_BitStreamInfo.iCache = 0; /* 4-byte uint32_t */ + m_aac_BitStreamInfo.cachedBits = 0; /* i.e. zero bits in cache */ + m_aac_BitStreamInfo.nBytes = nBytes; +} + +/*********************************************************************************************************************** + * Function: RefillBitstreamCache + * + * Description: read new data from bitstream buffer into 32-bit cache + * + * Inputs: none + * + * Outputs: updated bitstream info struct + * + * Return: none + * + * Notes: only call when iCache is completely drained (resets bitOffset to 0) + * always loads 4 new bytes except when bsi->nBytes < 4 (end of buffer) + * stores data as big-endian in cache, regardless of machine endian-ness + **********************************************************************************************************************/ +//Optimized for REV16, REV32 (FB) +inline void RefillBitstreamCache() +{ + int nBytes = m_aac_BitStreamInfo.nBytes; + if (nBytes >= 4) { + /* optimize for common case, independent of machine endian-ness */ + m_aac_BitStreamInfo.iCache = (*m_aac_BitStreamInfo.bytePtr++) << 24; + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++) << 16; + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++) << 8; + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++); + + m_aac_BitStreamInfo.cachedBits = 32; + m_aac_BitStreamInfo.nBytes -= 4; + } else { + m_aac_BitStreamInfo.iCache = 0; + while (nBytes--) { + m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++); + m_aac_BitStreamInfo.iCache <<= 8; + } + m_aac_BitStreamInfo.iCache <<= ((3 - m_aac_BitStreamInfo.nBytes)*8); + m_aac_BitStreamInfo.cachedBits = 8*m_aac_BitStreamInfo.nBytes; + m_aac_BitStreamInfo.nBytes = 0; + } +} + +/*********************************************************************************************************************** + * Function: GetBits + * + * Description: get bits from bitstream, advance bitstream pointer + * + * Inputs: pointer to initialized aac_BitStreamInfo_t struct + * number of bits to get from bitstream + * + * Outputs: updated bitstream info struct + * + * Return: the next nBits bits of data from bitstream buffer + * + * Notes: nBits must be in range [0, 31], nBits outside this range masked by 0x1f + * for speed, does not indicate error if you overrun bit buffer + * if nBits == 0, returns 0 + **********************************************************************************************************************/ +unsigned int GetBits(int nBits) +{ + uint32_t data, lowBits; + + nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */ + data = m_aac_BitStreamInfo.iCache >> (31 - nBits); /* unsigned >> so zero-extend */ + data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */ + m_aac_BitStreamInfo.iCache <<= nBits; /* left-justify cache */ + m_aac_BitStreamInfo.cachedBits -= nBits; /* how many bits have we drawn from the cache so far */ + + /* if we cross an int boundary, refill the cache */ + if (m_aac_BitStreamInfo.cachedBits < 0) { + lowBits = -m_aac_BitStreamInfo.cachedBits; + RefillBitstreamCache(); + data |= m_aac_BitStreamInfo.iCache >> (32 - lowBits); /* get the low-order bits */ + + m_aac_BitStreamInfo.cachedBits -= lowBits; /* how many bits have we drawn from the cache so far */ + m_aac_BitStreamInfo.iCache <<= lowBits; /* left-justify cache */ + } + + return data; +} + +/*********************************************************************************************************************** + * Function: GetBitsNoAdvance + * + * Description: get bits from bitstream, do not advance bitstream pointer + * + * Inputs: pointer to initialized aac_BitStreamInfo_t struct + * number of bits to get from bitstream + * + * Outputs: none (state of aac_BitStreamInfo_t struct left unchanged) + * + * Return: the next nBits bits of data from bitstream buffer + * + * Notes: nBits must be in range [0, 31], nBits outside this range masked by 0x1f + * for speed, does not indicate error if you overrun bit buffer + * if nBits == 0, returns 0 + **********************************************************************************************************************/ +unsigned int GetBitsNoAdvance(int nBits) +{ + uint8_t *buf; + uint32_t data, iCache; + int32_t lowBits; + + nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */ + data = m_aac_BitStreamInfo.iCache >> (31 - nBits); /* unsigned >> so zero-extend */ + data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */ + lowBits = nBits - m_aac_BitStreamInfo.cachedBits; /* how many bits do we have left to read */ + + /* if we cross an int boundary, read next bytes in buffer */ + if (lowBits > 0) { + iCache = 0; + buf = m_aac_BitStreamInfo.bytePtr; + while (lowBits > 0) { + iCache <<= 8; + if (buf < m_aac_BitStreamInfo.bytePtr + m_aac_BitStreamInfo.nBytes) + iCache |= (uint32_t)*buf++; + lowBits -= 8; + } + lowBits = -lowBits; + data |= iCache >> lowBits; + } + + return data; +} + +/*********************************************************************************************************************** + * Function: AdvanceBitstream + * + * Description: move bitstream pointer ahead + * + * Inputs: number of bits to advance bitstream + * + * Outputs: updated bitstream info struct + * + * Return: none + * + * Notes: generally used following GetBitsNoAdvance(bsi, maxBits) + **********************************************************************************************************************/ +void AdvanceBitstream(int nBits) +{ + nBits &= 0x1f; + if (nBits > m_aac_BitStreamInfo.cachedBits) { + nBits -= m_aac_BitStreamInfo.cachedBits; + RefillBitstreamCache(); + } + m_aac_BitStreamInfo.iCache <<= nBits; + m_aac_BitStreamInfo.cachedBits -= nBits; +} + +/*********************************************************************************************************************** + * Function: CalcBitsUsed + * + * Description: calculate how many bits have been read from bitstream + * + * Inputs: pointer to start of bitstream buffer + * bit offset into first byte of startBuf (0-7) + * + * Outputs: none + * + * Return: number of bits read from bitstream, as offset from startBuf:startOffset + **********************************************************************************************************************/ +int CalcBitsUsed(uint8_t *startBuf, int startOffset) { + + int bitsUsed; + + bitsUsed = (m_aac_BitStreamInfo.bytePtr - startBuf) * 8; + bitsUsed -= m_aac_BitStreamInfo.cachedBits; + bitsUsed -= startOffset; + + return bitsUsed; +} +/*********************************************************************************************************************** + * Function: ByteAlignBitstream + * + * Description: bump bitstream pointer to start of next byte + * + * Inputs: none + * + * Outputs: byte-aligned bitstream aac_BitStreamInfo_t struct + * + * Return: none + * + * Notes: if bitstream is already byte-aligned, do nothing + **********************************************************************************************************************/ +void ByteAlignBitstream(){ + + int offset; + + offset = m_aac_BitStreamInfo.cachedBits & 0x07; + AdvanceBitstream(offset); +} + +#ifdef AAC_ENABLE_SBR + +/************************************************************************************** + * Function: InitSBRState + * + * Description: initialize PSInfoSBR struct at start of stream or after flush + * + * Inputs: valid AACDecInfo struct + * + * Outputs: PSInfoSBR struct with proper initial state + * + * Return: none + **************************************************************************************/ +void InitSBRState() { + + int i, ch; + uint8_t *c; + + if (!m_PSInfoSBR) + return; + + /* clear SBR state structure */ + c = (uint8_t *)m_PSInfoSBR; + for (i = 0; i < (int)sizeof(m_PSInfoSBR); i++) + *c++ = 0; + + /* initialize non-zero state variables */ + for (ch = 0; ch < AAC_MAX_NCHANS; ch++) { + m_PSInfoSBR->sbrChan[ch].reset = 1; + m_PSInfoSBR->sbrChan[ch].laPrev = -1; + } +} +#endif + +/*********************************************************************************************************************** + * Function: DecodeSBRBitstream + * + * Description: decode sideband information for SBR + * + * Inputs: base output channel (range = [0, nChans-1]) + * + * Outputs: initialized state structs (SBRHdr, SBRGrid, SBRFreq, SBRChan) + * + * Return: 0 if successful, error code (< 0) if error + * + * Notes: SBR payload should be in aacDecInfo->fillBuf + * returns with no error if fill buffer is not an SBR extension block, + * or if current block is not a fill block (e.g. for LFE upsampling) + **********************************************************************************************************************/ +int DecodeSBRBitstream(int chBase) { + + int headerFlag; + + if(m_AACDecInfo->currBlockID != AAC_ID_FIL + || (m_AACDecInfo->fillExtType != EXT_SBR_DATA && m_AACDecInfo->fillExtType != EXT_SBR_DATA_CRC)) + return ERR_AAC_NONE; + + SetBitstreamPointer(m_AACDecInfo->fillCount, m_AACDecInfo->fillBuf); + if(GetBits(4) != (unsigned int) m_AACDecInfo->fillExtType) return ERR_AAC_SBR_BITSTREAM; + + if(m_AACDecInfo->fillExtType == EXT_SBR_DATA_CRC) m_PSInfoSBR->crcCheckWord = GetBits(10); + + headerFlag = GetBits(1); + if(headerFlag) { + /* get sample rate index for output sample rate (2x base rate) */ + m_PSInfoSBR->sampRateIdx = GetSampRateIdx(2 * m_AACDecInfo->sampRate); + if(m_PSInfoSBR->sampRateIdx < 0 || m_PSInfoSBR->sampRateIdx >= NUM_SAMPLE_RATES) + return ERR_AAC_SBR_BITSTREAM; + else if(m_PSInfoSBR->sampRateIdx >= NUM_SAMPLE_RATES_SBR) return ERR_AAC_SBR_SINGLERATE_UNSUPPORTED; + + /* reset flag = 1 if header values changed */ + if(UnpackSBRHeader(&(m_PSInfoSBR->sbrHdr[chBase]))) m_PSInfoSBR->sbrChan[chBase].reset = 1; + + /* first valid SBR header should always trigger CalcFreqTables(), since psi->reset was set in InitSBR() */ + if(m_PSInfoSBR->sbrChan[chBase].reset) + CalcFreqTables(&(m_PSInfoSBR->sbrHdr[chBase + 0]), &(m_PSInfoSBR->sbrFreq[chBase]), + m_PSInfoSBR->sampRateIdx); + + /* copy and reset state to right channel for CPE */ + if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) + m_PSInfoSBR->sbrChan[chBase + 1].reset = m_PSInfoSBR->sbrChan[chBase + 0].reset; + } + + /* if no header has been received, upsample only */ + if(m_PSInfoSBR->sbrHdr[chBase].count == 0) return ERR_AAC_NONE; + + if(m_AACDecInfo->prevBlockID == AAC_ID_SCE) { + UnpackSBRSingleChannel(chBase); + } + else if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) { + UnpackSBRChannelPair(chBase); + } + else { + return ERR_AAC_SBR_BITSTREAM; + } + + ByteAlignBitstream(); + + return ERR_AAC_NONE; +} + +#ifdef AAC_ENABLE_SBR + +/*********************************************************************************************************************** + * Function: DecodeSBRData + * + * Description: apply SBR to one frame of PCM data + * + * Inputs: 1024 samples of decoded 32-bit PCM, before SBR + * size of input PCM samples (must be 4 bytes) + * number of fraction bits in input PCM samples + * base output channel (range = [0, nChans-1]) + * initialized state structs (SBRHdr, SBRGrid, SBRFreq, SBRChan) + * + * Outputs: 2048 samples of decoded 16-bit PCM, after SBR + * + * Return: 0 if successful, error code (< 0) if error + **********************************************************************************************************************/ +int DecodeSBRData(int chBase, short *outbuf) { + + int k, l, ch, chBlock, qmfaBands, qmfsBands; + int upsampleOnly, gbIdx, gbMask; + int *inbuf; + short *outptr; + + SBRHeader *sbrHdr; + SBRGrid *sbrGrid; + SBRFreq *sbrFreq; + SBRChan *sbrChan; + + /* same header and freq tables for both channels in CPE */ + sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]); + sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]); + + /* upsample only if we haven't received an SBR header yet or if we have an LFE block */ + if(m_AACDecInfo->currBlockID == AAC_ID_LFE) { + chBlock = 1; + upsampleOnly = 1; + } + else if(m_AACDecInfo->currBlockID == AAC_ID_FIL) { + if(m_AACDecInfo->prevBlockID == AAC_ID_SCE) + chBlock = 1; + else if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) + chBlock = 2; + else + return ERR_AAC_NONE; + + upsampleOnly = (sbrHdr->count == 0 ? 1 : 0); + if(m_AACDecInfo->fillExtType != EXT_SBR_DATA && m_AACDecInfo->fillExtType != EXT_SBR_DATA_CRC) + return ERR_AAC_NONE; + } + else { + /* ignore non-SBR blocks */ + return ERR_AAC_NONE; + } + + if(upsampleOnly) { + sbrFreq->kStart = 32; + sbrFreq->numQMFBands = 0; + } + + for(ch = 0; ch < chBlock; ch++) { + sbrGrid = &(m_PSInfoSBR->sbrGrid[chBase + ch]); + sbrChan = &(m_PSInfoSBR->sbrChan[chBase + ch]); + + if(m_AACDecInfo->rawSampleBuf[ch] == 0 || m_AACDecInfo->rawSampleBytes != 4) return ERR_AAC_SBR_PCM_FORMAT; + inbuf = (int*) m_AACDecInfo->rawSampleBuf[ch]; + outptr = outbuf + chBase + ch; + + /* restore delay buffers (could use ring buffer or keep in temp buffer for nChans == 1) */ + for(l = 0; l < HF_GEN; l++) { + for(k = 0; k < 64; k++) { + m_PSInfoSBR->XBuf[l][k][0] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0]; + m_PSInfoSBR->XBuf[l][k][1] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1]; + } + } + + /* step 1 - analysis QMF */ + qmfaBands = sbrFreq->kStart; + for(l = 0; l < 32; l++) { + gbMask = QMFAnalysis(inbuf + l * 32, m_PSInfoSBR->delayQMFA[chBase + ch], m_PSInfoSBR->XBuf[l + HF_GEN][0], + m_AACDecInfo->rawSampleFBits, &(m_PSInfoSBR->delayIdxQMFA[chBase + ch]), qmfaBands); + + gbIdx = ((l + HF_GEN) >> 5) & 0x01; + sbrChan->gbMask[gbIdx] |= gbMask; /* gbIdx = (0 if i < 32), (1 if i >= 32) */ + } + + if(upsampleOnly) { + /* no SBR - just run synthesis QMF to upsample by 2x */ + qmfsBands = 32; + for(l = 0; l < 32; l++) { + /* step 4 - synthesis QMF */ + QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch], + &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans); + outptr += 64 * m_AACDecInfo->nChans; + } + } + else { + /* if previous frame had lower SBR starting freq than current, zero out the synthesized QMF + * bands so they aren't used as sources for patching + * after patch generation, restore from delay buffer + * can only happen after header reset + */ + for(k = sbrFreq->kStartPrev; k < sbrFreq->kStart; k++) { + for(l = 0; l < sbrGrid->envTimeBorder[0] + HF_ADJ; l++) { + m_PSInfoSBR->XBuf[l][k][0] = 0; + m_PSInfoSBR->XBuf[l][k][1] = 0; + } + } + + /* step 2 - HF generation */ + GenerateHighFreq(sbrGrid, sbrFreq, sbrChan, ch); + + /* restore SBR bands that were cleared before patch generation (time slots 0, 1 no longer needed) */ + for(k = sbrFreq->kStartPrev; k < sbrFreq->kStart; k++) { + for(l = HF_ADJ; l < sbrGrid->envTimeBorder[0] + HF_ADJ; l++) { + m_PSInfoSBR->XBuf[l][k][0] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0]; + m_PSInfoSBR->XBuf[l][k][1] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1]; + } + } + + /* step 3 - HF adjustment */ + AdjustHighFreq(sbrHdr, sbrGrid, sbrFreq, sbrChan, ch); + + /* step 4 - synthesis QMF */ + qmfsBands = sbrFreq->kStartPrev + sbrFreq->numQMFBandsPrev; + for(l = 0; l < sbrGrid->envTimeBorder[0]; l++) { + /* if new envelope starts mid-frame, use old settings until start of first envelope in this frame */ + QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch], + &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans); + outptr += 64 * m_AACDecInfo->nChans; + } + + qmfsBands = sbrFreq->kStart + sbrFreq->numQMFBands; + for(; l < 32; l++) { + /* use new settings for rest of frame (usually the entire frame, unless the first envelope starts mid-frame) */ + QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch], + &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans); + outptr += 64 * m_AACDecInfo->nChans; + } + } + + /* save delay */ + for(l = 0; l < HF_GEN; l++) { + for(k = 0; k < 64; k++) { + m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0] = m_PSInfoSBR->XBuf[l + 32][k][0]; + m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1] = m_PSInfoSBR->XBuf[l + 32][k][1]; + } + } + sbrChan->gbMask[0] = sbrChan->gbMask[1]; + sbrChan->gbMask[1] = 0; + + if(sbrHdr->count > 0) sbrChan->reset = 0; + } + sbrFreq->kStartPrev = sbrFreq->kStart; + sbrFreq->numQMFBandsPrev = sbrFreq->numQMFBands; + + if(m_AACDecInfo->nChans > 0 && (chBase + ch) == m_AACDecInfo->nChans) m_PSInfoSBR->frameCount++; + + return ERR_AAC_NONE; +} + +#endif + +/*********************************************************************************************************************** + * Function: BubbleSort + * + * Description: in-place sort of uint8_ts + * + * Inputs: buffer of elements to sort + * number of elements to sort + * + * Outputs: sorted buffer + * + * Return: none + **********************************************************************************************************************/ +void BubbleSort(uint8_t *v, int nItems) { + + int i; + uint8_t t; + + while(nItems >= 2) { + for(i = 0; i < nItems - 1; i++) { + if(v[i + 1] < v[i]) { + t = v[i + 1]; + v[i + 1] = v[i]; + v[i] = t; + } + } + nItems--; + } +} +/*********************************************************************************************************************** + * Function: VMin + * + * Description: find smallest element in a buffer of uint8_ts + * + * Inputs: buffer of elements to search + * number of elements to search + * + * Outputs: none + * + * Return: smallest element in buffer + **********************************************************************************************************************/ +uint8_t VMin(uint8_t *v, int nItems) { + + int i; + uint8_t vMin; + + vMin = v[0]; + for(i = 1; i < nItems; i++) { + if(v[i] < vMin) vMin = v[i]; + } + return vMin; +} +/*********************************************************************************************************************** + * Function: VMax + * + * Description: find largest element in a buffer of uint8_ts + * + * Inputs: buffer of elements to search + * number of elements to search + * + * Outputs: none + * + * Return: largest element in buffer + **********************************************************************************************************************/ +uint8_t VMax(uint8_t *v, int nItems) { + + int i; + uint8_t vMax; + + vMax = v[0]; + for(i = 1; i < nItems; i++) { + if(v[i] > vMax) vMax = v[i]; + } + return vMax; +} +/*********************************************************************************************************************** + * Function: CalcFreqMasterScaleZero + * + * Description: calculate master frequency table when freqScale == 0 + * (4.6.18.3.2.1, figure 4.39) + * + * Inputs: alterScale flag + * index of first QMF subband in master freq table (k0) + * index of last QMF subband (k2) + * + * Outputs: master frequency table + * + * Return: number of bands in master frequency table + * + * Notes: assumes k2 - k0 <= 48 and k2 >= k0 (4.6.18.3.6) + **********************************************************************************************************************/ +int CalcFreqMasterScaleZero(uint8_t *freqMaster, int alterScale, int k0, int k2) { + + int nMaster, k, nBands, k2Achieved, dk, vDk[64], k2Diff; + + if(alterScale) { + dk = 2; + nBands = 2 * ((k2 - k0 + 2) >> 2); + } + else { + dk = 1; + nBands = 2 * ((k2 - k0) >> 1); + } + + if(nBands <= 0) return 0; + + k2Achieved = k0 + nBands * dk; + k2Diff = k2 - k2Achieved; + for(k = 0; k < nBands; k++) + vDk[k] = dk; + + if(k2Diff > 0) { + k = nBands - 1; + while(k2Diff) { + vDk[k]++; + k--; + k2Diff--; + } + } + else if(k2Diff < 0) { + k = 0; + while(k2Diff) { + vDk[k]--; + k++; + k2Diff++; + } + } + + nMaster = nBands; + freqMaster[0] = k0; + for(k = 1; k <= nBands; k++) + freqMaster[k] = freqMaster[k - 1] + vDk[k - 1]; + + return nMaster; +} + +/* mBandTab[i] = temp1[i] / 2 */ +static const int mBandTab[3] PROGMEM = {6, 5, 4}; + +/* invWarpTab[i] = 1.0 / temp2[i], Q30 (see 4.6.18.3.2.1) */ +static const int invWarpTab[2] PROGMEM = {0x40000000, 0x313b13b1}; + +/*********************************************************************************************************************** + * Function: CalcFreqMasterScale + * + * Description: calculate master frequency table when freqScale > 0 + * (4.6.18.3.2.1, figure 4.39) + * + * Inputs: alterScale flag + * freqScale flag + * index of first QMF subband in master freq table (k0) + * index of last QMF subband (k2) + * + * Outputs: master frequency table + * + * Return: number of bands in master frequency table + * + * Notes: assumes k2 - k0 <= 48 and k2 >= k0 (4.6.18.3.6) + **********************************************************************************************************************/ +int CalcFreqMaster(uint8_t *freqMaster, int freqScale, int alterScale, int k0, int k2) { + + int bands, twoRegions, k, k1, t, vLast, vCurr, pCurr; + int invWarp, nBands0, nBands1, change; + uint8_t vDk1Min, vDk0Max; + uint8_t *vDelta; + + if(freqScale < 1 || freqScale > 3) return -1; + + bands = mBandTab[freqScale - 1]; + invWarp = invWarpTab[alterScale]; + + /* tested for all k0 = [5, 64], k2 = [k0, 64] */ + if(k2 * 10000 > 22449 * k0) { + twoRegions = 1; + k1 = 2 * k0; + } + else { + twoRegions = 0; + k1 = k2; + } + + /* tested for all k0 = [5, 64], k1 = [k0, 64], freqScale = [1,3] */ + t = (log2Tab[k1] - log2Tab[k0]) >> 3; /* log2(k1/k0), Q28 to Q25 */ + nBands0 = 2 * (((bands * t) + (1 << 24)) >> 25); /* multiply by bands/2, round to nearest int (mBandTab has factor of 1/2 rolled in) */ + + /* tested for all valid combinations of k0, k1, nBands (from sampRate, freqScale, alterScale) + * roundoff error can be a problem with fixpt (e.g. pCurr = 12.499999 instead of 12.50003) + * because successive multiplication always undershoots a little bit, but this + * doesn't occur in any of the ratios we encounter from the valid k0/k1 bands in the spec + */ + t = RatioPowInv(k1, k0, nBands0); + pCurr = k0 << 24; + vLast = k0; + vDelta = freqMaster + 1; /* operate in-place */ + for(k = 0; k < nBands0; k++) { + pCurr = MULSHIFT32(pCurr, t) << 8; /* keep in Q24 */ + vCurr = (pCurr + (1 << 23)) >> 24; + vDelta[k] = (vCurr - vLast); + vLast = vCurr; + } + + /* sort the deltas and find max delta for first region */ + BubbleSort(vDelta, nBands0); + vDk0Max = VMax(vDelta, nBands0); + + /* fill master frequency table with bands from first region */ + freqMaster[0] = k0; + for(k = 1; k <= nBands0; k++) + freqMaster[k] += freqMaster[k - 1]; + + /* if only one region, then the table is complete */ + if(!twoRegions) return nBands0; + + /* tested for all k1 = [10, 64], k2 = [k0, 64], freqScale = [1,3] */ + t = (log2Tab[k2] - log2Tab[k1]) >> 3; /* log2(k1/k0), Q28 to Q25 */ + t = MULSHIFT32(bands * t, invWarp) << 2; /* multiply by bands/2, divide by warp factor, keep Q25 */ + nBands1 = 2 * ((t + (1 << 24)) >> 25); /* round to nearest int */ + + /* see comments above for calculations in first region */ + t = RatioPowInv(k2, k1, nBands1); + pCurr = k1 << 24; + vLast = k1; + vDelta = freqMaster + nBands0 + 1; /* operate in-place */ + for(k = 0; k < nBands1; k++) { + pCurr = MULSHIFT32(pCurr, t) << 8; /* keep in Q24 */ + vCurr = (pCurr + (1 << 23)) >> 24; + vDelta[k] = (vCurr - vLast); + vLast = vCurr; + } + + /* sort the deltas, adjusting first and last if the second region has smaller deltas than the first */ + vDk1Min = VMin(vDelta, nBands1); + if(vDk1Min < vDk0Max) { + BubbleSort(vDelta, nBands1); + change = vDk0Max - vDelta[0]; + if(change > ((vDelta[nBands1 - 1] - vDelta[0]) >> 1)) change = ((vDelta[nBands1 - 1] - vDelta[0]) >> 1); + vDelta[0] += change; + vDelta[nBands1 - 1] -= change; + } + BubbleSort(vDelta, nBands1); + + /* fill master frequency table with bands from second region + * Note: freqMaster[nBands0] = k1 + */ + for(k = 1; k <= nBands1; k++) + freqMaster[k + nBands0] += freqMaster[k + nBands0 - 1]; + + return (nBands0 + nBands1); +} +/*********************************************************************************************************************** + * Function: CalcFreqHigh + * + * Description: calculate high resolution frequency table (4.6.18.3.2.2) + * + * Inputs: master frequency table + * number of bands in master frequency table + * crossover band from header + * + * Outputs: high resolution frequency table + * + * Return: number of bands in high resolution frequency table + **********************************************************************************************************************/ +int CalcFreqHigh(uint8_t *freqHigh, uint8_t *freqMaster, int nMaster, int crossOverBand) { + + int k, nHigh; + + nHigh = nMaster - crossOverBand; + + for(k = 0; k <= nHigh; k++) + freqHigh[k] = freqMaster[k + crossOverBand]; + + return nHigh; +} +/*********************************************************************************************************************** + * Function: CalcFreqLow + * + * Description: calculate low resolution frequency table (4.6.18.3.2.2) + * + * Inputs: high resolution frequency table + * number of bands in high resolution frequency table + * + * Outputs: low resolution frequency table + * + * Return: number of bands in low resolution frequency table + **********************************************************************************************************************/ +int CalcFreqLow(uint8_t *freqLow, uint8_t *freqHigh, int nHigh) { + + int k, nLow, oddFlag; + + nLow = nHigh - (nHigh >> 1); + freqLow[0] = freqHigh[0]; + oddFlag = nHigh & 0x01; + + for(k = 1; k <= nLow; k++) + freqLow[k] = freqHigh[2 * k - oddFlag]; + + return nLow; +} +/*********************************************************************************************************************** + * Function: CalcFreqNoise + * + * Description: calculate noise floor frequency table (4.6.18.3.2.2) + * + * Inputs: low resolution frequency table + * number of bands in low resolution frequency table + * index of starting QMF subband for SBR (kStart) + * index of last QMF subband (k2) + * number of noise bands + * + * Outputs: noise floor frequency table + * + * Return: number of bands in noise floor frequency table + **********************************************************************************************************************/ +int CalcFreqNoise(uint8_t *freqNoise, uint8_t *freqLow, int nLow, int kStart, int k2, int noiseBands) { + + int i, iLast, k, nQ, lTop, lBottom; + + lTop = log2Tab[k2]; + lBottom = log2Tab[kStart]; + nQ = noiseBands * ((lTop - lBottom) >> 2); /* Q28 to Q26, noiseBands = [0,3] */ + nQ = (nQ + (1 << 25)) >> 26; + if(nQ < 1) nQ = 1; + + ASSERT(nQ <= MAX_NUM_NOISE_FLOOR_BANDS); /* required from 4.6.18.3.6 */ + + iLast = 0; + freqNoise[0] = freqLow[0]; + for(k = 1; k <= nQ; k++) { + i = iLast + (nLow - iLast) / (nQ + 1 - k); /* truncating division */ + freqNoise[k] = freqLow[i]; + iLast = i; + } + + return nQ; +} +/*********************************************************************************************************************** + * Function: BuildPatches + * + * Description: build high frequency patches (4.6.18.6.3) + * + * Inputs: master frequency table + * number of bands in low resolution frequency table + * index of first QMF subband in master freq table (k0) + * index of starting QMF subband for SBR (kStart) + * number of QMF bands in high resolution frequency table + * sample rate index + * + * Outputs: starting subband for each patch + * number of subbands in each patch + * + * Return: number of patches + **********************************************************************************************************************/ +int BuildPatches(uint8_t *patchNumSubbands, uint8_t *patchStartSubband, uint8_t *freqMaster, int nMaster, int k0, + int kStart, int numQMFBands, int sampRateIdx) { + + int i, j, k; + int msb, sb, usb, numPatches, goalSB, oddFlag; + + msb = k0; + usb = kStart; + numPatches = 0; + goalSB = goalSBTab[sampRateIdx]; + + if(nMaster == 0) { + patchNumSubbands[0] = 0; + patchStartSubband[0] = 0; + return 0; + } + + if(goalSB < kStart + numQMFBands) { + k = 0; + for(i = 0; freqMaster[i] < goalSB; i++) + k = i + 1; + } + else { + k = nMaster; + } + + do { + j = k + 1; + do { + j--; + sb = freqMaster[j]; + oddFlag = (sb - 2 + k0) & 0x01; + } while(sb > k0 - 1 + msb - oddFlag); + + patchNumSubbands[numPatches] = MAX(sb - usb, 0); + patchStartSubband[numPatches] = k0 - oddFlag - patchNumSubbands[numPatches]; + + /* from MPEG reference code - slightly different from spec */ + if((patchNumSubbands[numPatches] < 3) && (numPatches > 0)) break; + + if(patchNumSubbands[numPatches] > 0) { + usb = sb; + msb = sb; + numPatches++; + } + else { + msb = kStart; + } + + if(freqMaster[k] - sb < 3) k = nMaster; + + } while(sb != (kStart + numQMFBands) && numPatches <= MAX_NUM_PATCHES); + + return numPatches; +} +/*********************************************************************************************************************** + * Function: FindFreq + * + * Description: search buffer of uint8_ts for a specific value + * + * Inputs: buffer of elements to search + * number of elements to search + * value to search for + * + * Outputs: none + * + * Return: non-zero if the value is found anywhere in the buffer, zero otherwise + **********************************************************************************************************************/ +int FindFreq(uint8_t *freq, int nFreq, uint8_t val) { + + int k; + + for(k = 0; k < nFreq; k++) { + if(freq[k] == val) return 1; + } + + return 0; +} +/*********************************************************************************************************************** + * Function: RemoveFreq + * + * Description: remove one element from a buffer of uint8_ts + * + * Inputs: buffer of elements + * number of elements + * index of element to remove + * + * Outputs: new buffer of length nFreq-1 + * + * Return: none + **********************************************************************************************************************/ +void RemoveFreq(uint8_t *freq, int nFreq, int removeIdx) { + + int k; + + if(removeIdx >= nFreq) return; + + for(k = removeIdx; k < nFreq - 1; k++) + freq[k] = freq[k + 1]; +} +/*********************************************************************************************************************** + * Function: CalcFreqLimiter + * + * Description: calculate limiter frequency table (4.6.18.3.2.3) + * + * Inputs: number of subbands in each patch + * low resolution frequency table + * number of bands in low resolution frequency table + * index of starting QMF subband for SBR (kStart) + * number of limiter bands + * number of patches + * + * Outputs: limiter frequency table + * + * Return: number of bands in limiter frequency table + **********************************************************************************************************************/ +int CalcFreqLimiter(uint8_t *freqLimiter, uint8_t *patchNumSubbands, uint8_t *freqLow, int nLow, int kStart, + int limiterBands, int numPatches) { + + int k, bands, nLimiter, nOctaves; + int limBandsPerOctave[3] = { 120, 200, 300 }; /* [1.2, 2.0, 3.0] * 100 */ + uint8_t patchBorders[MAX_NUM_PATCHES + 1]; + + /* simple case */ + if(limiterBands == 0) { + freqLimiter[0] = freqLow[0] - kStart; + freqLimiter[1] = freqLow[nLow] - kStart; + return 1; + } + + bands = limBandsPerOctave[limiterBands - 1]; + patchBorders[0] = kStart; + + /* from MPEG reference code - slightly different from spec (top border) */ + for(k = 1; k < numPatches; k++) + patchBorders[k] = patchBorders[k - 1] + patchNumSubbands[k - 1]; + patchBorders[k] = freqLow[nLow]; + + for(k = 0; k <= nLow; k++) + freqLimiter[k] = freqLow[k]; + + for(k = 1; k < numPatches; k++) + freqLimiter[k + nLow] = patchBorders[k]; + + k = 1; + nLimiter = nLow + numPatches - 1; + BubbleSort(freqLimiter, nLimiter + 1); + + while(k <= nLimiter) { + nOctaves = log2Tab[freqLimiter[k]] - log2Tab[freqLimiter[k - 1]]; /* Q28 */ + nOctaves = (nOctaves >> 9) * bands; /* Q19, max bands = 300 < 2^9 */ + if(nOctaves < (49 << 19)) { /* compare with 0.49*100, in Q19 */ + if(freqLimiter[k] == freqLimiter[k - 1] || FindFreq(patchBorders, numPatches + 1, freqLimiter[k]) == 0) { + RemoveFreq(freqLimiter, nLimiter + 1, k); + nLimiter--; + } + else if(FindFreq(patchBorders, numPatches + 1, freqLimiter[k - 1]) == 0) { + RemoveFreq(freqLimiter, nLimiter + 1, k - 1); + nLimiter--; + } + else { + k++; + } + } + else { + k++; + } + } + + /* store limiter boundaries as offsets from kStart */ + for(k = 0; k <= nLimiter; k++) + freqLimiter[k] -= kStart; + + return nLimiter; +} +/*********************************************************************************************************************** + * Function: CalcFreqTables + * + * Description: calulate master and derived frequency tables, and patches + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRFreq struct for this SCE/CPE block + * sample rate index of output sample rate (after SBR) + * + * Outputs: master and derived frequency tables, and patches + * + * Return: non-zero if error, zero otherwise + **********************************************************************************************************************/ +int CalcFreqTables(SBRHeader *sbrHdr, SBRFreq *sbrFreq, int sampRateIdx) { + int k0, k2; + + k0 = k0Tab[sampRateIdx][sbrHdr->startFreq]; + + if(sbrHdr->stopFreq == 14) + k2 = 2 * k0; + else if(sbrHdr->stopFreq == 15) + k2 = 3 * k0; + else + k2 = k2Tab[sampRateIdx][sbrHdr->stopFreq]; + if(k2 > 64) k2 = 64; + + /* calculate master frequency table */ + if(sbrHdr->freqScale == 0) + sbrFreq->nMaster = CalcFreqMasterScaleZero(sbrFreq->freqMaster, sbrHdr->alterScale, k0, k2); + else + sbrFreq->nMaster = CalcFreqMaster(sbrFreq->freqMaster, sbrHdr->freqScale, sbrHdr->alterScale, k0, k2); + + /* calculate high frequency table and related parameters */ + sbrFreq->nHigh = CalcFreqHigh(sbrFreq->freqHigh, sbrFreq->freqMaster, sbrFreq->nMaster, sbrHdr->crossOverBand); + sbrFreq->numQMFBands = sbrFreq->freqHigh[sbrFreq->nHigh] - sbrFreq->freqHigh[0]; + sbrFreq->kStart = sbrFreq->freqHigh[0]; + + /* calculate low frequency table */ + sbrFreq->nLow = CalcFreqLow(sbrFreq->freqLow, sbrFreq->freqHigh, sbrFreq->nHigh); + + /* calculate noise floor frequency table */ + sbrFreq->numNoiseFloorBands = CalcFreqNoise(sbrFreq->freqNoise, sbrFreq->freqLow, sbrFreq->nLow, sbrFreq->kStart, + k2, sbrHdr->noiseBands); + + /* calculate limiter table */ + sbrFreq->numPatches = BuildPatches(sbrFreq->patchNumSubbands, sbrFreq->patchStartSubband, sbrFreq->freqMaster, + sbrFreq->nMaster, k0, sbrFreq->kStart, sbrFreq->numQMFBands, sampRateIdx); + sbrFreq->nLimiter = CalcFreqLimiter(sbrFreq->freqLimiter, sbrFreq->patchNumSubbands, sbrFreq->freqLow, + sbrFreq->nLow, sbrFreq->kStart, sbrHdr->limiterBands, sbrFreq->numPatches); + + return 0; +} +/*********************************************************************************************************************** + * Function: EstimateEnvelope + * + * Description: estimate power of generated HF QMF bands in one time-domain envelope + * (4.6.18.7.3) + * + * Inputs: initialized PSInfoSBR struct + * initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * index of current envelope + * + * Outputs: power of each QMF subband, stored as integer (Q0) * 2^N, N >= 0 + * + * Return: none + **********************************************************************************************************************/ +void EstimateEnvelope(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int env) { + + int i, m, iStart, iEnd, xre, xim, nScale, expMax; + int p, n, mStart, mEnd, invFact, t; + int *XBuf; + U64 eCurr; + uint8_t *freqBandTab; + + /* estimate current envelope */ + iStart = sbrGrid->envTimeBorder[env] + HF_ADJ; + iEnd = sbrGrid->envTimeBorder[env + 1] + HF_ADJ; + if(sbrGrid->freqRes[env]) { + n = sbrFreq->nHigh; + freqBandTab = sbrFreq->freqHigh; + } + else { + n = sbrFreq->nLow; + freqBandTab = sbrFreq->freqLow; + } + + /* ADS should inline MADD64 (smlal) properly, but check to make sure */ + expMax = 0; + if(sbrHdr->interpFreq) { + for(m = 0; m < sbrFreq->numQMFBands; m++) { + eCurr.w64 = 0; + XBuf = m_PSInfoSBR->XBuf[iStart][sbrFreq->kStart + m]; + for(i = iStart; i < iEnd; i++) { + /* scale to int before calculating power (precision not critical, and avoids overflow) */ + xre = (*XBuf) >> FBITS_OUT_QMFA; + XBuf += 1; + xim = (*XBuf) >> FBITS_OUT_QMFA; + XBuf += (2 * 64 - 1); + eCurr.w64 = MADD64(eCurr.w64, xre, xre); + eCurr.w64 = MADD64(eCurr.w64, xim, xim); + } + + /* eCurr.w64 is now Q(64 - 2*FBITS_OUT_QMFA) (64-bit word) + * if energy is too big to fit in 32-bit word (> 2^31) scale down by power of 2 + */ + nScale = 0; + if(eCurr.r.hi32) { + nScale = (32 - CLZ(eCurr.r.hi32)) + 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + t |= eCurr.r.hi32 << (32 - nScale); + } + else if(eCurr.r.lo32 >> 31) { + nScale = 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + } + else { + t = (int) eCurr.r.lo32; + } + + invFact = invBandTab[(iEnd - iStart) - 1]; + m_PSInfoSBR->eCurr[m] = MULSHIFT32(t, invFact); + m_PSInfoSBR->eCurrExp[m] = nScale + 1; /* +1 for invFact = Q31 */ + if(m_PSInfoSBR->eCurrExp[m] > expMax) expMax = m_PSInfoSBR->eCurrExp[m]; + } + } + else { + for(p = 0; p < n; p++) { + mStart = freqBandTab[p]; + mEnd = freqBandTab[p + 1]; + eCurr.w64 = 0; + for(i = iStart; i < iEnd; i++) { + XBuf = m_PSInfoSBR->XBuf[i][mStart]; + for(m = mStart; m < mEnd; m++) { + xre = (*XBuf++) >> FBITS_OUT_QMFA; + xim = (*XBuf++) >> FBITS_OUT_QMFA; + eCurr.w64 = MADD64(eCurr.w64, xre, xre); + eCurr.w64 = MADD64(eCurr.w64, xim, xim); + } + } + + nScale = 0; + if(eCurr.r.hi32) { + nScale = (32 - CLZ(eCurr.r.hi32)) + 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + t |= eCurr.r.hi32 << (32 - nScale); + } + else if(eCurr.r.lo32 >> 31) { + nScale = 1; + t = (int) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */ + } + else { + t = (int) eCurr.r.lo32; + } + + invFact = invBandTab[(iEnd - iStart) - 1]; + invFact = MULSHIFT32(invBandTab[(mEnd - mStart) - 1], invFact) << 1; + t = MULSHIFT32(t, invFact); + + for(m = mStart; m < mEnd; m++) { + m_PSInfoSBR->eCurr[m - sbrFreq->kStart] = t; + m_PSInfoSBR->eCurrExp[m - sbrFreq->kStart] = nScale + 1; /* +1 for invFact = Q31 */ + } + if(m_PSInfoSBR->eCurrExp[mStart - sbrFreq->kStart] > expMax) + expMax = m_PSInfoSBR->eCurrExp[mStart - sbrFreq->kStart]; + } + } + m_PSInfoSBR->eCurrExpMax = expMax; +} +/*********************************************************************************************************************** + * Function: GetSMapped + * + * Description: calculate SMapped (4.6.18.7.2) + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current envelope + * index of current QMF band + * la flag for this envelope + * + * Outputs: none + * + * Return: 1 if a sinusoid is present in this band, 0 if not + **********************************************************************************************************************/ +int GetSMapped(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int band, int la) { + + int bandStart, bandEnd, oddFlag, r; + + if (sbrGrid->freqRes[env]) { + /* high resolution */ + bandStart = band; + bandEnd = band+1; + } else { + /* low resolution (see CalcFreqLow() for mapping) */ + oddFlag = sbrFreq->nHigh & 0x01; + bandStart = (band > 0 ? 2*band - oddFlag : 0); /* starting index for freqLow[band] */ + bandEnd = 2*(band+1) - oddFlag; /* ending index for freqLow[band+1] */ + } + + /* sMapped = 1 if sIndexMapped == 1 for any frequency in this band */ + for (band = bandStart; band < bandEnd; band++) { + if (sbrChan->addHarmonic[1][band]) { + r = ((sbrFreq->freqHigh[band+1] + sbrFreq->freqHigh[band]) >> 1); + if (env >= la || sbrChan->addHarmonic[0][r] == 1) + return 1; + } + } + return 0; +} + +#define GBOOST_MAX 0x2830afd3 /* Q28, 1.584893192 squared */ +#define ACC_SCALE 6 + +/* squared version of table in 4.6.18.7.5 */ /* Q30 (0x80000000 = sentinel for GMAX) */ +static const uint32_t limGainTab[4] PROGMEM = {0x20138ca7, 0x40000000, 0x7fb27dce, 0x80000000}; + +/*********************************************************************************************************************** + * Function: CalcMaxGain + * + * Description: calculate max gain in one limiter band (4.6.18.7.5) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * index of current channel (0 for SCE, 0 or 1 for CPE) + * index of current envelope + * index of current limiter band + * number of fraction bits in dequantized envelope + * (max = Q(FBITS_OUT_DQ_ENV - 6) = Q23, can go negative) + * + * Outputs: updated gainMax, gainMaxFBits, and sumEOrigMapped in PSInfoSBR struct + * + * Return: none + **********************************************************************************************************************/ +void CalcMaxGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int ch, int env, int lim, int fbitsDQ) { + + int m, mStart, mEnd, q, z, r; + int sumEOrigMapped, sumECurr, gainMax, eOMGainMax, envBand; + uint8_t eCurrExpMax; + uint8_t *freqBandTab; + + mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */ + mEnd = sbrFreq->freqLimiter[lim + 1]; + freqBandTab = (sbrGrid->freqRes[env] ? sbrFreq->freqHigh : sbrFreq->freqLow); + + /* calculate max gain to apply to signal in this limiter band */ + sumECurr = 0; + sumEOrigMapped = 0; + eCurrExpMax = m_PSInfoSBR->eCurrExpMax; + eOMGainMax = m_PSInfoSBR->eOMGainMax; + envBand = m_PSInfoSBR->envBand; + for(m = mStart; m < mEnd; m++) { + /* map current QMF band to appropriate envelope band */ + if(m == freqBandTab[envBand + 1] - sbrFreq->kStart) { + envBand++; + eOMGainMax = m_PSInfoSBR->envDataDequant[ch][env][envBand] >> ACC_SCALE; /* summing max 48 bands */ + } + sumEOrigMapped += eOMGainMax; + + /* easy test for overflow on ARM */ + sumECurr += (m_PSInfoSBR->eCurr[m] >> (eCurrExpMax - m_PSInfoSBR->eCurrExp[m])); + if(sumECurr >> 30) { + sumECurr >>= 1; + eCurrExpMax++; + } + } + m_PSInfoSBR->eOMGainMax = eOMGainMax; + m_PSInfoSBR->envBand = envBand; + + m_PSInfoSBR->gainMaxFBits = 30; /* Q30 tables */ + if(sumECurr == 0) { + /* any non-zero numerator * 1/EPS_0 is > G_MAX */ + gainMax = (sumEOrigMapped == 0 ? (int) limGainTab[sbrHdr->limiterGains] : (int) 0x80000000); + } + else if(sumEOrigMapped == 0) { + /* 1/(any non-zero denominator) * EPS_0 * limGainTab[x] is appx. 0 */ + gainMax = 0; + } + else { + /* sumEOrigMapped = Q(fbitsDQ - ACC_SCALE), sumECurr = Q(-eCurrExpMax) */ + gainMax = limGainTab[sbrHdr->limiterGains]; + if(sbrHdr->limiterGains != 3) { + q = MULSHIFT32(sumEOrigMapped, gainMax); /* Q(fbitsDQ - ACC_SCALE - 2), gainMax = Q30 */ + z = CLZ(sumECurr) - 1; + r = InvRNormalized(sumECurr << z); /* in = Q(z - eCurrExpMax), out = Q(29 + 31 - z + eCurrExpMax) */ + gainMax = MULSHIFT32(q, r); /* Q(29 + 31 - z + eCurrExpMax + fbitsDQ - ACC_SCALE - 2 - 32) */ + m_PSInfoSBR->gainMaxFBits = 26 - z + eCurrExpMax + fbitsDQ - ACC_SCALE; + } + } + m_PSInfoSBR->sumEOrigMapped = sumEOrigMapped; + m_PSInfoSBR->gainMax = gainMax; +} +/*********************************************************************************************************************** + * Function: CalcNoiseDivFactors + * + * Description: calculate 1/(1+Q) and Q/(1+Q) (4.6.18.7.4; 4.6.18.7.5) + * + * Inputs: dequantized noise floor scalefactor + * + * Outputs: 1/(1+Q) and Q/(1+Q), format = Q31 + * + * Return: none + **********************************************************************************************************************/ +void CalcNoiseDivFactors(int q, int *qp1Inv, int *qqp1Inv) { + + int z, qp1, t, s; + + /* 1 + Q_orig */ + qp1 = (q >> 1); + qp1 += (1 << (FBITS_OUT_DQ_NOISE - 1)); /* >> 1 to avoid overflow when adding 1.0 */ + z = CLZ(qp1) - 1; /* z <= 31 - FBITS_OUT_DQ_NOISE */ + qp1 <<= z; /* Q(FBITS_OUT_DQ_NOISE + z) = Q31 * 2^-(31 - (FBITS_OUT_DQ_NOISE + z)) */ + t = InvRNormalized(qp1) << 1; /* Q30 * 2^(31 - (FBITS_OUT_DQ_NOISE + z)), guaranteed not to overflow */ + + /* normalize to Q31 */ + s = (31 - (FBITS_OUT_DQ_NOISE - 1) - z - 1); /* clearly z >= 0, z <= (30 - (FBITS_OUT_DQ_NOISE - 1)) */ + *qp1Inv = (t >> s); /* s = [0, 31 - FBITS_OUT_DQ_NOISE] */ + *qqp1Inv = MULSHIFT32(t, q) << (32 - FBITS_OUT_DQ_NOISE - s); +} +/*********************************************************************************************************************** + * Function: CalcComponentGains + * + * Description: calculate gain of envelope, sinusoids, and noise in one limiter band + * (4.6.18.7.5) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * index of current envelope + * index of current limiter band + * number of fraction bits in dequantized envelope + * + * Outputs: gains for envelope, sinusoids and noise + * number of fraction bits for envelope gain + * sum of the total gain for each component in this band + * other updated state variables + * + * Return: none + **********************************************************************************************************************/ +void CalcComponentGains(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env, int lim, int fbitsDQ) { + + int d, m, mStart, mEnd, q, qm, noiseFloor, sIndexMapped; + int shift, eCurr, maxFlag, gainMax, gainMaxFBits; + int gain, sm, z, r, fbitsGain, gainScale; + uint8_t *freqBandTab; + + mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */ + mEnd = sbrFreq->freqLimiter[lim + 1]; + + gainMax = m_PSInfoSBR->gainMax; + gainMaxFBits = m_PSInfoSBR->gainMaxFBits; + + d = (env == m_PSInfoSBR->la || env == sbrChan->laPrev ? 0 : 1); + freqBandTab = (sbrGrid->freqRes[env] ? sbrFreq->freqHigh : sbrFreq->freqLow); + + /* figure out which noise floor this envelope is in (only 1 or 2 noise floors allowed) */ + noiseFloor = 0; + if(sbrGrid->numNoiseFloors == 2 && sbrGrid->noiseTimeBorder[1] <= sbrGrid->envTimeBorder[env]) noiseFloor++; + + m_PSInfoSBR->sumECurrGLim = 0; + m_PSInfoSBR->sumSM = 0; + m_PSInfoSBR->sumQM = 0; + /* calculate energy of noise to add in this limiter band */ + for(m = mStart; m < mEnd; m++) { + if(m == sbrFreq->freqNoise[m_PSInfoSBR->noiseFloorBand + 1] - sbrFreq->kStart) { + /* map current QMF band to appropriate noise floor band (NOTE: freqLimiter[0] == freqLow[0] = freqHigh[0]) */ + m_PSInfoSBR->noiseFloorBand++; + CalcNoiseDivFactors(m_PSInfoSBR->noiseDataDequant[ch][noiseFloor][m_PSInfoSBR->noiseFloorBand], + &(m_PSInfoSBR->qp1Inv), &(m_PSInfoSBR->qqp1Inv)); + } + if(m == sbrFreq->freqHigh[m_PSInfoSBR->highBand + 1] - sbrFreq->kStart) m_PSInfoSBR->highBand++; + if(m == freqBandTab[m_PSInfoSBR->sBand + 1] - sbrFreq->kStart) { + m_PSInfoSBR->sBand++; + m_PSInfoSBR->sMapped = GetSMapped(sbrGrid, sbrFreq, sbrChan, env, m_PSInfoSBR->sBand, m_PSInfoSBR->la); + } + + /* get sIndexMapped for this QMF subband */ + sIndexMapped = 0; + r = ((sbrFreq->freqHigh[m_PSInfoSBR->highBand + 1] + sbrFreq->freqHigh[m_PSInfoSBR->highBand]) >> 1); + if(m + sbrFreq->kStart == r) { + /* r = center frequency, deltaStep = (env >= la || sIndexMapped'(r, numEnv'-1) == 1) */ + if(env >= m_PSInfoSBR->la || sbrChan->addHarmonic[0][r] == 1) sIndexMapped = + sbrChan->addHarmonic[1][m_PSInfoSBR->highBand]; + } + + /* save sine flags from last envelope in this frame: + * addHarmonic[0][0...63] = saved sine present flag from previous frame, for each QMF subband + * addHarmonic[1][0...nHigh-1] = addHarmonic bit from current frame, for each high-res frequency band + * from MPEG reference code - slightly different from spec + * (sIndexMapped'(m,LE'-1) can still be 0 when numEnv == psi->la) + */ + if(env == sbrGrid->numEnv - 1) { + if(m + sbrFreq->kStart == r) + sbrChan->addHarmonic[0][m + sbrFreq->kStart] = sbrChan->addHarmonic[1][m_PSInfoSBR->highBand]; + else + sbrChan->addHarmonic[0][m + sbrFreq->kStart] = 0; + } + + gain = m_PSInfoSBR->envDataDequant[ch][env][m_PSInfoSBR->sBand]; + qm = MULSHIFT32(gain, m_PSInfoSBR->qqp1Inv) << 1; + sm = (sIndexMapped ? MULSHIFT32(gain, m_PSInfoSBR->qp1Inv) << 1 : 0); + + /* three cases: (sMapped == 0 && delta == 1), (sMapped == 0 && delta == 0), (sMapped == 1) */ + if(d == 1 && m_PSInfoSBR->sMapped == 0) + gain = MULSHIFT32(m_PSInfoSBR->qp1Inv, gain) << 1; + else if(m_PSInfoSBR->sMapped != 0) gain = MULSHIFT32(m_PSInfoSBR->qqp1Inv, gain) << 1; + + /* gain, qm, sm = Q(fbitsDQ), gainMax = Q(fbitsGainMax) */ + eCurr = m_PSInfoSBR->eCurr[m]; + if(eCurr) { + z = CLZ(eCurr) - 1; + r = InvRNormalized(eCurr << z); /* in = Q(z - eCurrExp), out = Q(29 + 31 - z + eCurrExp) */ + gainScale = MULSHIFT32(gain, r); /* out = Q(29 + 31 - z + eCurrExp + fbitsDQ - 32) */ + fbitsGain = 29 + 31 - z + m_PSInfoSBR->eCurrExp[m] + fbitsDQ - 32; + } + else { + /* if eCurr == 0, then gain is unchanged (divide by EPS = 1) */ + gainScale = gain; + fbitsGain = fbitsDQ; + } + + /* see if gain for this band exceeds max gain */ + maxFlag = 0; + if(gainMax != (int) 0x80000000) { + if(fbitsGain >= gainMaxFBits) { + shift = MIN(fbitsGain - gainMaxFBits, 31); + maxFlag = ((gainScale >> shift) > gainMax ? 1 : 0); + } + else { + shift = MIN(gainMaxFBits - fbitsGain, 31); + maxFlag = (gainScale > (gainMax >> shift) ? 1 : 0); + } + } + + if(maxFlag) { + /* gainScale > gainMax, calculate ratio with 32/16 division */ + q = 0; + r = gainScale; /* guaranteed > 0, else maxFlag could not have been set */ + z = CLZ(r); + if(z < 16) { + q = 16 - z; + r >>= q; /* out = Q(fbitsGain - q) */ + } + + z = CLZ(gainMax) - 1; + r = (gainMax << z) / r; /* out = Q((fbitsGainMax + z) - (fbitsGain - q)) */ + q = (gainMaxFBits + z) - (fbitsGain - q); /* r = Q(q) */ + if(q > 30) { + r >>= MIN(q - 30, 31); + } + else { + z = MIN(30 - q, 30); + r = CLIP_2N_SHIFT30(r, z); /* let r = Q30 since range = [0.0, 1.0) (clip to 0x3fffffff = 0.99999) */ + } + + qm = MULSHIFT32(qm, r) << 2; + gain = MULSHIFT32(gain, r) << 2; + m_PSInfoSBR->gLimBuf[m] = gainMax; + m_PSInfoSBR->gLimFbits[m] = gainMaxFBits; + } + else { + m_PSInfoSBR->gLimBuf[m] = gainScale; + m_PSInfoSBR->gLimFbits[m] = fbitsGain; + } + + /* sumSM, sumQM, sumECurrGLim = Q(fbitsDQ - ACC_SCALE) */ + m_PSInfoSBR->smBuf[m] = sm; + m_PSInfoSBR->sumSM += (sm >> ACC_SCALE); + + m_PSInfoSBR->qmLimBuf[m] = qm; + if(env != m_PSInfoSBR->la && env != sbrChan->laPrev && sm == 0) m_PSInfoSBR->sumQM += (qm >> ACC_SCALE); + + /* eCurr * gain^2 same as gain^2, before division by eCurr + * (but note that gain != 0 even if eCurr == 0, since it's divided by eps) + */ + if(eCurr) m_PSInfoSBR->sumECurrGLim += (gain >> ACC_SCALE); + } +} +/*********************************************************************************************************************** + * Function: ApplyBoost + * + * Description: calculate and apply boost factor for envelope, sinusoids, and noise + * in this limiter band (4.6.18.7.5) + * + * Inputs: initialized SBRFreq struct for this SCE/CPE block + * index of current limiter band + * number of fraction bits in dequantized envelope + * + * Outputs: envelope gain, sinusoids and noise after scaling by gBoost + * format = Q(FBITS_GLIM_BOOST) for envelope gain, + * = Q(FBITS_QLIM_BOOST) for noise + * = Q(FBITS_OUT_QMFA) for sinusoids + * + * Return: none + * + * Notes: after scaling, each component has at least 1 GB + **********************************************************************************************************************/ +void ApplyBoost(SBRFreq *sbrFreq, int lim, int fbitsDQ) { + + int m, mStart, mEnd, q, z, r; + int sumEOrigMapped, gBoost; + + mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */ + mEnd = sbrFreq->freqLimiter[lim + 1]; + + sumEOrigMapped = m_PSInfoSBR->sumEOrigMapped >> 1; + r = (m_PSInfoSBR->sumECurrGLim >> 1) + (m_PSInfoSBR->sumSM >> 1) + (m_PSInfoSBR->sumQM >> 1); /* 1 GB fine (sm and qm are mutually exclusive in acc) */ + if(r < (1 << (31 - 28))) { + /* any non-zero numerator * 1/EPS_0 is > GBOOST_MAX + * round very small r to zero to avoid scaling problems + */ + gBoost = (sumEOrigMapped == 0 ? (1 << 28) : GBOOST_MAX); + z = 0; + } + else if(sumEOrigMapped == 0) { + /* 1/(any non-zero denominator) * EPS_0 is appx. 0 */ + gBoost = 0; + z = 0; + } + else { + /* numerator (sumEOrigMapped) and denominator (r) have same Q format (before << z) */ + z = CLZ(r) - 1; /* z = [0, 27] */ + r = InvRNormalized(r << z); + gBoost = MULSHIFT32(sumEOrigMapped, r); + } + + /* gBoost = Q(28 - z) */ + if(gBoost > (GBOOST_MAX >> z)) { + gBoost = GBOOST_MAX; + z = 0; + } + gBoost <<= z; /* gBoost = Q28, minimum 1 GB */ + + /* convert gain, noise, sinusoids to fixed Q format, clipping if necessary + * (rare, usually only happens at very low bitrates, introduces slight + * distortion into final HF mapping, but should be inaudible) + */ + for(m = mStart; m < mEnd; m++) { + /* let gLimBoost = Q24, since in practice the max values are usually 16 to 20 + * unless limiterGains == 3 (limiter off) and eCurr ~= 0 (i.e. huge gain, but only + * because the envelope has 0 power anyway) + */ + q = MULSHIFT32(m_PSInfoSBR->gLimBuf[m], gBoost) << 2; /* Q(gLimFbits) * Q(28) --> Q(gLimFbits[m]-2) */ + r = SqrtFix(q, m_PSInfoSBR->gLimFbits[m] - 2, &z); + z -= FBITS_GLIM_BOOST; + if(z >= 0) { + m_PSInfoSBR->gLimBoost[m] = r >> MIN(z, 31); + } + else { + z = MIN(30, -z); + r = CLIP_2N_SHIFT30(r, z); + m_PSInfoSBR->gLimBoost[m] = r; + } + + q = MULSHIFT32(m_PSInfoSBR->qmLimBuf[m], gBoost) << 2; /* Q(fbitsDQ) * Q(28) --> Q(fbitsDQ-2) */ + r = SqrtFix(q, fbitsDQ - 2, &z); + z -= FBITS_QLIM_BOOST; /* << by 14, since integer sqrt of x < 2^16, and we want to leave 1 GB */ + if(z >= 0) { + m_PSInfoSBR->qmLimBoost[m] = r >> MIN(31, z); + } + else { + z = MIN(30, -z); + r = CLIP_2N_SHIFT30(r, z); + m_PSInfoSBR->qmLimBoost[m] = r; + } + + q = MULSHIFT32(m_PSInfoSBR->smBuf[m], gBoost) << 2; /* Q(fbitsDQ) * Q(28) --> Q(fbitsDQ-2) */ + r = SqrtFix(q, fbitsDQ - 2, &z); + z -= FBITS_OUT_QMFA; /* justify for adding to signal (xBuf) later */ + if(z >= 0) { + m_PSInfoSBR->smBoost[m] = r >> MIN(31, z); + } + else { + z = MIN(30, -z); + r = CLIP_2N_SHIFT30(r, z); + m_PSInfoSBR->smBoost[m] = r; + } + } +} +/*********************************************************************************************************************** + * Function: CalcGain + * + * Description: calculate and apply proper gain to HF components in one envelope + * (4.6.18.7.5) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * index of current envelope + * + * Outputs: envelope gain, sinusoids and noise after scaling + * + * Return: none + **********************************************************************************************************************/ +void CalcGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env) { + + int lim, fbitsDQ; + + /* initialize to -1 so that mapping limiter bands to env/noise bands works right on first pass */ + m_PSInfoSBR->envBand = -1; + m_PSInfoSBR->noiseFloorBand = -1; + m_PSInfoSBR->sBand = -1; + m_PSInfoSBR->highBand = -1; + + fbitsDQ = (FBITS_OUT_DQ_ENV - m_PSInfoSBR->envDataDequantScale[ch][env]); /* Q(29 - optional scalefactor) */ + for(lim = 0; lim < sbrFreq->nLimiter; lim++) { + /* the QMF bands are divided into lim regions (consecutive, non-overlapping) */ + CalcMaxGain(sbrHdr, sbrGrid, sbrFreq, ch, env, lim, fbitsDQ); + CalcComponentGains(sbrGrid, sbrFreq, sbrChan, ch, env, lim, fbitsDQ); + ApplyBoost(sbrFreq, lim, fbitsDQ); + } +} + +/* hSmooth table from 4.7.18.7.6, format = Q31 */ +static const int hSmoothCoef[MAX_NUM_SMOOTH_COEFS] PROGMEM = { 0x2aaaaaab, 0x2697a512, 0x1becfa68, 0x0ebdb043, + 0x04130598, }; + +/*********************************************************************************************************************** + * Function: MapHF + * + * Description: map HF components to proper QMF bands, with optional gain smoothing + * filter (4.6.18.7.6) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current envelope + * reset flag (can be non-zero for first envelope only) + * + * Outputs: complete reconstructed subband QMF samples for this envelope + * + * Return: none + * + * Notes: ensures that output has >= MIN_GBITS_IN_QMFS guard bits, + * so it's not necessary to check anything in the synth QMF + **********************************************************************************************************************/ +void MapHF(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int hfReset) { + + int noiseTabIndex, sinIndex, gainNoiseIndex, hSL; + int i, iStart, iEnd, m, idx, j, s, n, smre, smim; + int gFilt, qFilt, xre, xim, gbMask, gbIdx; + int *XBuf; + + noiseTabIndex = sbrChan->noiseTabIndex; + sinIndex = sbrChan->sinIndex; + gainNoiseIndex = sbrChan->gainNoiseIndex; /* oldest entries in filter delay buffer */ + + if(hfReset) noiseTabIndex = 2; /* starts at 1, double since complex */ + hSL = (sbrHdr->smoothMode ? 0 : 4); + + if(hfReset) { + for(i = 0; i < hSL; i++) { + for(m = 0; m < sbrFreq->numQMFBands; m++) { + sbrChan->gTemp[gainNoiseIndex][m] = m_PSInfoSBR->gLimBoost[m]; + sbrChan->qTemp[gainNoiseIndex][m] = m_PSInfoSBR->qmLimBoost[m]; + } + gainNoiseIndex++; + if(gainNoiseIndex == MAX_NUM_SMOOTH_COEFS) gainNoiseIndex = 0; + } ASSERT(env == 0); /* should only be reset when env == 0 */ + } + + iStart = sbrGrid->envTimeBorder[env]; + iEnd = sbrGrid->envTimeBorder[env + 1]; + for(i = iStart; i < iEnd; i++) { + /* save new values in temp buffers (delay) + * we only store MAX_NUM_SMOOTH_COEFS most recent values, + * so don't keep storing the same value over and over + */ + if(i - iStart < MAX_NUM_SMOOTH_COEFS) { + for(m = 0; m < sbrFreq->numQMFBands; m++) { + sbrChan->gTemp[gainNoiseIndex][m] = m_PSInfoSBR->gLimBoost[m]; + sbrChan->qTemp[gainNoiseIndex][m] = m_PSInfoSBR->qmLimBoost[m]; + } + } + + /* see 4.6.18.7.6 */ + XBuf = m_PSInfoSBR->XBuf[i + HF_ADJ][sbrFreq->kStart]; + gbMask = 0; + for(m = 0; m < sbrFreq->numQMFBands; m++) { + if(env == m_PSInfoSBR->la || env == sbrChan->laPrev) { + /* no smoothing filter for gain, and qFilt = 0 (only need to do once) */ + if(i == iStart) { + m_PSInfoSBR->gFiltLast[m] = sbrChan->gTemp[gainNoiseIndex][m]; + m_PSInfoSBR->qFiltLast[m] = 0; + } + } + else if(hSL == 0) { + /* no smoothing filter for gain, (only need to do once) */ + if(i == iStart) { + m_PSInfoSBR->gFiltLast[m] = sbrChan->gTemp[gainNoiseIndex][m]; + m_PSInfoSBR->qFiltLast[m] = sbrChan->qTemp[gainNoiseIndex][m]; + } + } + else { + /* apply smoothing filter to gain and noise (after MAX_NUM_SMOOTH_COEFS, it's always the same) */ + if(i - iStart < MAX_NUM_SMOOTH_COEFS) { + gFilt = 0; + qFilt = 0; + idx = gainNoiseIndex; + for(j = 0; j < MAX_NUM_SMOOTH_COEFS; j++) { + /* sum(abs(hSmoothCoef[j])) for all j < 1.0 */ + gFilt += MULSHIFT32(sbrChan->gTemp[idx][m], hSmoothCoef[j]); + qFilt += MULSHIFT32(sbrChan->qTemp[idx][m], hSmoothCoef[j]); + idx--; + if(idx < 0) idx += MAX_NUM_SMOOTH_COEFS; + } + m_PSInfoSBR->gFiltLast[m] = gFilt << 1; /* restore to Q(FBITS_GLIM_BOOST) (gain of filter < 1.0, so no overflow) */ + m_PSInfoSBR->qFiltLast[m] = qFilt << 1; /* restore to Q(FBITS_QLIM_BOOST) */ + } + } + + if(m_PSInfoSBR->smBoost[m] != 0) { + /* add scaled signal and sinusoid, don't add noise (qFilt = 0) */ + smre = m_PSInfoSBR->smBoost[m]; + smim = smre; + + /* sinIndex: [0] xre += sm [1] xim += sm*s [2] xre -= sm [3] xim -= sm*s */ + s = (sinIndex >> 1); /* if 2 or 3, flip sign to subtract sm */ + s <<= 31; + smre ^= (s >> 31); + smre -= (s >> 31); + s ^= ((m + sbrFreq->kStart) << 31); + smim ^= (s >> 31); + smim -= (s >> 31); + + /* if sinIndex == 0 or 2, smim = 0; if sinIndex == 1 or 3, smre = 0 */ + s = sinIndex << 31; + smim &= (s >> 31); + s ^= 0x80000000; + smre &= (s >> 31); + + noiseTabIndex += 2; /* noise filtered by 0, but still need to bump index */ + } + else { + /* add scaled signal and scaled noise */ + qFilt = m_PSInfoSBR->qFiltLast[m]; + n = noiseTab[noiseTabIndex++]; + smre = MULSHIFT32(n, qFilt) >> (FBITS_QLIM_BOOST - 1 - FBITS_OUT_QMFA); + + n = noiseTab[noiseTabIndex++]; + smim = MULSHIFT32(n, qFilt) >> (FBITS_QLIM_BOOST - 1 - FBITS_OUT_QMFA); + } + noiseTabIndex &= 1023; /* 512 complex numbers */ + + gFilt = m_PSInfoSBR->gFiltLast[m]; + xre = MULSHIFT32(gFilt, XBuf[0]); + xim = MULSHIFT32(gFilt, XBuf[1]); + xre = CLIP_2N_SHIFT30(xre, 32 - FBITS_GLIM_BOOST); + xim = CLIP_2N_SHIFT30(xim, 32 - FBITS_GLIM_BOOST); + + xre += smre; + *XBuf++ = xre; + xim += smim; + *XBuf++ = xim; + + gbMask |= FASTABS(xre); + gbMask |= FASTABS(xim); + } + /* update circular buffer index */ + gainNoiseIndex++; + if(gainNoiseIndex == MAX_NUM_SMOOTH_COEFS) gainNoiseIndex = 0; + + sinIndex++; + sinIndex &= 3; + + /* ensure MIN_GBITS_IN_QMFS guard bits in output + * almost never occurs in practice, but checking here makes synth QMF logic very simple + */ + if(gbMask >> (31 - MIN_GBITS_IN_QMFS)) { + XBuf = m_PSInfoSBR->XBuf[i + HF_ADJ][sbrFreq->kStart]; + for(m = 0; m < sbrFreq->numQMFBands; m++) { + xre = XBuf[0]; + xim = XBuf[1]; + xre = CLIP_2N(xre, (31 - MIN_GBITS_IN_QMFS)); + xim = CLIP_2N(xim, (31 - MIN_GBITS_IN_QMFS)); + *XBuf++ = xre; + *XBuf++ = xim; + } + gbMask = CLIP_2N(gbMask, (31 - MIN_GBITS_IN_QMFS)); + } + gbIdx = ((i + HF_ADJ) >> 5) & 0x01; + sbrChan->gbMask[gbIdx] |= gbMask; + } + sbrChan->noiseTabIndex = noiseTabIndex; + sbrChan->sinIndex = sinIndex; + sbrChan->gainNoiseIndex = gainNoiseIndex; +} +/*********************************************************************************************************************** + * Function: AdjustHighFreq + * + * Description: adjust high frequencies and add noise and sinusoids (4.6.18.7) + * + * Inputs: initialized SBRHeader struct for this SCE/CPE block + * initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: complete reconstructed subband QMF samples for this channel + * + * Return: none + **********************************************************************************************************************/ +void AdjustHighFreq(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int i, env, hfReset; + uint8_t frameClass, pointer; + + frameClass = sbrGrid->frameClass; + pointer = sbrGrid->pointer; + + /* derive la from table 4.159 */ + if ((frameClass == SBR_GRID_FIXVAR || frameClass == SBR_GRID_VARVAR) && pointer > 0) + m_PSInfoSBR->la = sbrGrid->numEnv + 1 - pointer; + else if (frameClass == SBR_GRID_VARFIX && pointer > 1) + m_PSInfoSBR->la = pointer - 1; + else + m_PSInfoSBR->la = -1; + + /* for each envelope, estimate gain and adjust SBR QMF bands */ + hfReset = sbrChan->reset; + for (env = 0; env < sbrGrid->numEnv; env++) { + EstimateEnvelope(sbrHdr, sbrGrid, sbrFreq, env); + CalcGain(sbrHdr, sbrGrid, sbrFreq, sbrChan, ch, env); + MapHF(sbrHdr, sbrGrid, sbrFreq, sbrChan, env, hfReset); + hfReset = 0; /* only set for first envelope after header reset */ + } + + /* set saved sine flags to 0 for QMF bands outside of current frequency range */ + for (i = 0; i < sbrFreq->freqLimiter[0] + sbrFreq->kStart; i++) + sbrChan->addHarmonic[0][i] = 0; + for (i = sbrFreq->freqLimiter[sbrFreq->nLimiter] + sbrFreq->kStart; i < 64; i++) + sbrChan->addHarmonic[0][i] = 0; + sbrChan->addHarmonicFlag[0] = sbrChan->addHarmonicFlag[1]; + + /* save la for next frame */ + if (m_PSInfoSBR->la == sbrGrid->numEnv) + sbrChan->laPrev = 0; + else + sbrChan->laPrev = -1; +} +/*********************************************************************************************************************** + * Function: CalcCovariance1 + * + * Description: calculate covariance matrix for p01, p12, p11, p22 (4.6.18.6.2) + * + * Inputs: buffer of low-freq samples, starting at time index 0, + * freq index = patch subband + * + * Outputs: complex covariance elements p01re, p01im, p12re, p12im, p11re, p22re + * (p11im = p22im = 0) + * format = integer (Q0) * 2^N, with scalefactor N >= 0 + * + * Return: scalefactor N + * + * Notes: outputs are normalized to have 1 GB (sign in at least top 2 bits) + **********************************************************************************************************************/ +int CalcCovariance1(int *XBuf, int *p01reN, int *p01imN, int *p12reN, int *p12imN, int *p11reN, int *p22reN) { + + int accBuf[2*6]; + int n, z, s, loShift, hiShift, gbMask; + U64 p01re, p01im, p12re, p12im, p11re, p22re; + + CVKernel1(XBuf, accBuf); + p01re.r.lo32 = accBuf[0]; p01re.r.hi32 = accBuf[1]; + p01im.r.lo32 = accBuf[2]; p01im.r.hi32 = accBuf[3]; + p11re.r.lo32 = accBuf[4]; p11re.r.hi32 = accBuf[5]; + p12re.r.lo32 = accBuf[6]; p12re.r.hi32 = accBuf[7]; + p12im.r.lo32 = accBuf[8]; p12im.r.hi32 = accBuf[9]; + p22re.r.lo32 = accBuf[10]; p22re.r.hi32 = accBuf[11]; + + /* 64-bit accumulators now have 2*FBITS_OUT_QMFA fraction bits + * want to scale them down to integers (32-bit signed, Q0) + * with scale factor of 2^n, n >= 0 + * leave 2 GB's for calculating determinant, so take top 30 non-zero bits + */ + gbMask = ((p01re.r.hi32) ^ (p01re.r.hi32 >> 31)) | ((p01im.r.hi32) ^ (p01im.r.hi32 >> 31)); + gbMask |= ((p12re.r.hi32) ^ (p12re.r.hi32 >> 31)) | ((p12im.r.hi32) ^ (p12im.r.hi32 >> 31)); + gbMask |= ((p11re.r.hi32) ^ (p11re.r.hi32 >> 31)) | ((p22re.r.hi32) ^ (p22re.r.hi32 >> 31)); + if (gbMask == 0) { + s = p01re.r.hi32 >> 31; gbMask = (p01re.r.lo32 ^ s) - s; + s = p01im.r.hi32 >> 31; gbMask |= (p01im.r.lo32 ^ s) - s; + s = p12re.r.hi32 >> 31; gbMask |= (p12re.r.lo32 ^ s) - s; + s = p12im.r.hi32 >> 31; gbMask |= (p12im.r.lo32 ^ s) - s; + s = p11re.r.hi32 >> 31; gbMask |= (p11re.r.lo32 ^ s) - s; + s = p22re.r.hi32 >> 31; gbMask |= (p22re.r.lo32 ^ s) - s; + z = 32 + CLZ(gbMask); + } else { + gbMask = FASTABS(p01re.r.hi32) | FASTABS(p01im.r.hi32); + gbMask |= FASTABS(p12re.r.hi32) | FASTABS(p12im.r.hi32); + gbMask |= FASTABS(p11re.r.hi32) | FASTABS(p22re.r.hi32); + z = CLZ(gbMask); + } + + n = 64 - z; /* number of non-zero bits in bottom of 64-bit word */ + if (n <= 30) { + loShift = (30 - n); + *p01reN = p01re.r.lo32 << loShift; *p01imN = p01im.r.lo32 << loShift; + *p12reN = p12re.r.lo32 << loShift; *p12imN = p12im.r.lo32 << loShift; + *p11reN = p11re.r.lo32 << loShift; *p22reN = p22re.r.lo32 << loShift; + return -(loShift + 2*FBITS_OUT_QMFA); + } else if (n < 32 + 30) { + loShift = (n - 30); + hiShift = 32 - loShift; + *p01reN = (p01re.r.hi32 << hiShift) | (p01re.r.lo32 >> loShift); + *p01imN = (p01im.r.hi32 << hiShift) | (p01im.r.lo32 >> loShift); + *p12reN = (p12re.r.hi32 << hiShift) | (p12re.r.lo32 >> loShift); + *p12imN = (p12im.r.hi32 << hiShift) | (p12im.r.lo32 >> loShift); + *p11reN = (p11re.r.hi32 << hiShift) | (p11re.r.lo32 >> loShift); + *p22reN = (p22re.r.hi32 << hiShift) | (p22re.r.lo32 >> loShift); + return (loShift - 2*FBITS_OUT_QMFA); + } else { + hiShift = n - (32 + 30); + *p01reN = p01re.r.hi32 >> hiShift; *p01imN = p01im.r.hi32 >> hiShift; + *p12reN = p12re.r.hi32 >> hiShift; *p12imN = p12im.r.hi32 >> hiShift; + *p11reN = p11re.r.hi32 >> hiShift; *p22reN = p22re.r.hi32 >> hiShift; + return (32 - 2*FBITS_OUT_QMFA - hiShift); + } + + return 0; +} +/*********************************************************************************************************************** + * Function: CalcCovariance2 + * + * Description: calculate covariance matrix for p02 (4.6.18.6.2) + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * + * Outputs: complex covariance element p02re, p02im + * format = integer (Q0) * 2^N, with scalefactor N >= 0 + * + * Return: scalefactor N + * + * Notes: outputs are normalized to have 1 GB (sign in at least top 2 bits) + **********************************************************************************************************************/ +int CalcCovariance2(int *XBuf, int *p02reN, int *p02imN) { + + U64 p02re, p02im; + int n, z, s, loShift, hiShift, gbMask; + int accBuf[2*2]; + + CVKernel2(XBuf, accBuf); + p02re.r.lo32 = accBuf[0]; + p02re.r.hi32 = accBuf[1]; + p02im.r.lo32 = accBuf[2]; + p02im.r.hi32 = accBuf[3]; + + /* 64-bit accumulators now have 2*FBITS_OUT_QMFA fraction bits + * want to scale them down to integers (32-bit signed, Q0) + * with scale factor of 2^n, n >= 0 + * leave 1 GB for calculating determinant, so take top 30 non-zero bits + */ + gbMask = ((p02re.r.hi32) ^ (p02re.r.hi32 >> 31)) | ((p02im.r.hi32) ^ (p02im.r.hi32 >> 31)); + if (gbMask == 0) { + s = p02re.r.hi32 >> 31; gbMask = (p02re.r.lo32 ^ s) - s; + s = p02im.r.hi32 >> 31; gbMask |= (p02im.r.lo32 ^ s) - s; + z = 32 + CLZ(gbMask); + } else { + gbMask = FASTABS(p02re.r.hi32) | FASTABS(p02im.r.hi32); + z = CLZ(gbMask); + } + n = 64 - z; /* number of non-zero bits in bottom of 64-bit word */ + + if (n <= 30) { + loShift = (30 - n); + *p02reN = p02re.r.lo32 << loShift; + *p02imN = p02im.r.lo32 << loShift; + return -(loShift + 2*FBITS_OUT_QMFA); + } else if (n < 32 + 30) { + loShift = (n - 30); + hiShift = 32 - loShift; + *p02reN = (p02re.r.hi32 << hiShift) | (p02re.r.lo32 >> loShift); + *p02imN = (p02im.r.hi32 << hiShift) | (p02im.r.lo32 >> loShift); + return (loShift - 2*FBITS_OUT_QMFA); + } else { + hiShift = n - (32 + 30); + *p02reN = p02re.r.hi32 >> hiShift; + *p02imN = p02im.r.hi32 >> hiShift; + return (32 - 2*FBITS_OUT_QMFA - hiShift); + } + + return 0; +} +/*********************************************************************************************************************** + * Function: CalcLPCoefs + * + * Description: calculate linear prediction coefficients for one subband (4.6.18.6.2) + * + * Inputs: buffer of low-freq samples, starting at time index = 0, + * freq index = patch subband + * number of guard bits in input sample buffer + * + * Outputs: complex LP coefficients a0re, a0im, a1re, a1im, format = Q29 + * + * Return: none + * + * Notes: output coefficients (a0re, a0im, a1re, a1im) clipped to range (-4, 4) + * if the comples coefficients have magnitude >= 4.0, they are all + * set to 0 (see spec) + **********************************************************************************************************************/ +void CalcLPCoefs(int *XBuf, int *a0re, int *a0im, int *a1re, int *a1im, int gb) { + + int zFlag, n1, n2, nd, d, dInv, tre, tim; + int p01re, p01im, p02re, p02im, p12re, p12im, p11re, p22re; + + /* pre-scale to avoid overflow - probably never happens in practice (see QMFA) + * max bit growth per accumulator = 38*2 = 76 mul-adds (X * X) + * using 64-bit MADD, so if X has n guard bits, X*X has 2n+1 guard bits + * gain 1 extra sign bit per multiply, so ensure ceil(log2(76/2) / 2) = 3 guard bits on inputs + */ + if (gb < 3) { + nd = 3 - gb; + for (n1 = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2); n1 != 0; n1--) { + XBuf[0] >>= nd; XBuf[1] >>= nd; + XBuf += (2*64); + } + XBuf -= (2*64*(NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2)); + } + + /* calculate covariance elements */ + n1 = CalcCovariance1(XBuf, &p01re, &p01im, &p12re, &p12im, &p11re, &p22re); + n2 = CalcCovariance2(XBuf, &p02re, &p02im); + + /* normalize everything to larger power of 2 scalefactor, call it n1 */ + if (n1 < n2) { + nd = MIN(n2 - n1, 31); + p01re >>= nd; p01im >>= nd; + p12re >>= nd; p12im >>= nd; + p11re >>= nd; p22re >>= nd; + n1 = n2; + } else if (n1 > n2) { + nd = MIN(n1 - n2, 31); + p02re >>= nd; p02im >>= nd; + } + + /* calculate determinant of covariance matrix (at least 1 GB in pXX) */ + d = MULSHIFT32(p12re, p12re) + MULSHIFT32(p12im, p12im); + d = MULSHIFT32(d, RELAX_COEF) << 1; + d = MULSHIFT32(p11re, p22re) - d; + ASSERT(d >= 0); /* should never be < 0 */ + + zFlag = 0; + *a0re = *a0im = 0; + *a1re = *a1im = 0; + if (d > 0) { + /* input = Q31 d = Q(-2*n1 - 32 + nd) = Q31 * 2^(31 + 2*n1 + 32 - nd) + * inverse = Q29 dInv = Q29 * 2^(-31 - 2*n1 - 32 + nd) = Q(29 + 31 + 2*n1 + 32 - nd) + * + * numerator has same Q format as d, since it's sum of normalized squares + * so num * inverse = Q(-2*n1 - 32) * Q(29 + 31 + 2*n1 + 32 - nd) + * = Q(29 + 31 - nd), drop low 32 in MULSHIFT32 + * = Q(29 + 31 - 32 - nd) = Q(28 - nd) + */ + nd = CLZ(d) - 1; + d <<= nd; + dInv = InvRNormalized(d); + + /* 1 GB in pXX */ + tre = MULSHIFT32(p01re, p12re) - MULSHIFT32(p01im, p12im) - MULSHIFT32(p02re, p11re); + tre = MULSHIFT32(tre, dInv); + tim = MULSHIFT32(p01re, p12im) + MULSHIFT32(p01im, p12re) - MULSHIFT32(p02im, p11re); + tim = MULSHIFT32(tim, dInv); + + /* if d is extremely small, just set coefs to 0 (would have poor precision anyway) */ + if (nd > 28 || (FASTABS(tre) >> (28 - nd)) >= 4 || (FASTABS(tim) >> (28 - nd)) >= 4) { + zFlag = 1; + } else { + *a1re = tre << (FBITS_LPCOEFS - 28 + nd); /* i.e. convert Q(28 - nd) to Q(29) */ + *a1im = tim << (FBITS_LPCOEFS - 28 + nd); + } + } + + if (p11re) { + /* input = Q31 p11re = Q(-n1 + nd) = Q31 * 2^(31 + n1 - nd) + * inverse = Q29 dInv = Q29 * 2^(-31 - n1 + nd) = Q(29 + 31 + n1 - nd) + * + * numerator is Q(-n1 - 3) + * so num * inverse = Q(-n1 - 3) * Q(29 + 31 + n1 - nd) + * = Q(29 + 31 - 3 - nd), drop low 32 in MULSHIFT32 + * = Q(29 + 31 - 3 - 32 - nd) = Q(25 - nd) + */ + nd = CLZ(p11re) - 1; /* assume positive */ + p11re <<= nd; + dInv = InvRNormalized(p11re); + + /* a1re, a1im = Q29, so scaled by (n1 + 3) */ + tre = (p01re >> 3) + MULSHIFT32(p12re, *a1re) + MULSHIFT32(p12im, *a1im); + tre = -MULSHIFT32(tre, dInv); + tim = (p01im >> 3) - MULSHIFT32(p12im, *a1re) + MULSHIFT32(p12re, *a1im); + tim = -MULSHIFT32(tim, dInv); + + if (nd > 25 || (FASTABS(tre) >> (25 - nd)) >= 4 || (FASTABS(tim) >> (25 - nd)) >= 4) { + zFlag = 1; + } else { + *a0re = tre << (FBITS_LPCOEFS - 25 + nd); /* i.e. convert Q(25 - nd) to Q(29) */ + *a0im = tim << (FBITS_LPCOEFS - 25 + nd); + } + } + + /* see 4.6.18.6.2 - if magnitude of a0 or a1 >= 4 then a0 = a1 = 0 + * i.e. a0re < 4, a0im < 4, a1re < 4, a1im < 4 + * Q29*Q29 = Q26 + */ + if (zFlag || MULSHIFT32(*a0re, *a0re) + MULSHIFT32(*a0im, *a0im) >= MAG_16 || MULSHIFT32(*a1re, *a1re) + MULSHIFT32(*a1im, *a1im) >= MAG_16) { + *a0re = *a0im = 0; + *a1re = *a1im = 0; + } + + /* no need to clip - we never changed the XBuf data, just used it to calculate a0 and a1 */ + if (gb < 3) { + nd = 3 - gb; + for (n1 = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2); n1 != 0; n1--) { + XBuf[0] <<= nd; XBuf[1] <<= nd; + XBuf += (2*64); + } + } +} +/*********************************************************************************************************************** + * Function: GenerateHighFreq + * + * Description: generate high frequencies with SBR (4.6.18.6) + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: new high frequency samples starting at frequency kStart + * + * Return: none + **********************************************************************************************************************/ +void GenerateHighFreq(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int band, newBW, c, t, gb, gbMask, gbIdx; + int currPatch, p, x, k, g, i, iStart, iEnd, bw, bwsq; + int a0re, a0im, a1re, a1im; + int x1re, x1im, x2re, x2im; + int ACCre, ACCim; + int *XBufLo, *XBufHi; + (void) ch; + + /* calculate array of chirp factors */ + for (band = 0; band < sbrFreq->numNoiseFloorBands; band++) { + c = sbrChan->chirpFact[band]; /* previous (bwArray') */ + newBW = newBWTab[sbrChan->invfMode[0][band]][sbrChan->invfMode[1][band]]; + + /* weighted average of new and old (can't overflow - total gain = 1.0) */ + if (newBW < c) + t = MULSHIFT32(newBW, 0x60000000) + MULSHIFT32(0x20000000, c); /* new is smaller: 0.75*new + 0.25*old */ + else + t = MULSHIFT32(newBW, 0x74000000) + MULSHIFT32(0x0c000000, c); /* new is larger: 0.90625*new + 0.09375*old */ + t <<= 1; + + if (t < 0x02000000) /* below 0.015625, clip to 0 */ + t = 0; + if (t > 0x7f800000) /* clip to 0.99609375 */ + t = 0x7f800000; + + /* save curr as prev for next time */ + sbrChan->chirpFact[band] = t; + sbrChan->invfMode[0][band] = sbrChan->invfMode[1][band]; + } + + iStart = sbrGrid->envTimeBorder[0] + HF_ADJ; + iEnd = sbrGrid->envTimeBorder[sbrGrid->numEnv] + HF_ADJ; + + /* generate new high freqs from low freqs, patches, and chirp factors */ + k = sbrFreq->kStart; + g = 0; + bw = sbrChan->chirpFact[g]; + bwsq = MULSHIFT32(bw, bw) << 1; + + gbMask = (sbrChan->gbMask[0] | sbrChan->gbMask[1]); /* older 32 | newer 8 */ + gb = CLZ(gbMask) - 1; + + for (currPatch = 0; currPatch < sbrFreq->numPatches; currPatch++) { + for (x = 0; x < sbrFreq->patchNumSubbands[currPatch]; x++) { + /* map k to corresponding noise floor band */ + if (k >= sbrFreq->freqNoise[g+1]) { + g++; + bw = sbrChan->chirpFact[g]; /* Q31 */ + bwsq = MULSHIFT32(bw, bw) << 1; /* Q31 */ + } + + p = sbrFreq->patchStartSubband[currPatch] + x; /* low QMF band */ + XBufHi = m_PSInfoSBR->XBuf[iStart][k]; + if (bw) { + CalcLPCoefs(m_PSInfoSBR->XBuf[0][p], &a0re, &a0im, &a1re, &a1im, gb); + + a0re = MULSHIFT32(bw, a0re); /* Q31 * Q29 = Q28 */ + a0im = MULSHIFT32(bw, a0im); + a1re = MULSHIFT32(bwsq, a1re); + a1im = MULSHIFT32(bwsq, a1im); + + XBufLo = m_PSInfoSBR->XBuf[iStart-2][p]; + + x2re = XBufLo[0]; /* RE{XBuf[n-2]} */ + x2im = XBufLo[1]; /* IM{XBuf[n-2]} */ + XBufLo += (64*2); + + x1re = XBufLo[0]; /* RE{XBuf[n-1]} */ + x1im = XBufLo[1]; /* IM{XBuf[n-1]} */ + XBufLo += (64*2); + + for (i = iStart; i < iEnd; i++) { + /* a0re/im, a1re/im are Q28 with at least 1 GB, + * so the summing for AACre/im is fine (1 GB in, plus 1 from MULSHIFT32) + */ + ACCre = MULSHIFT32(x2re, a1re) - MULSHIFT32(x2im, a1im); + ACCim = MULSHIFT32(x2re, a1im) + MULSHIFT32(x2im, a1re); + x2re = x1re; + x2im = x1im; + + ACCre += MULSHIFT32(x1re, a0re) - MULSHIFT32(x1im, a0im); + ACCim += MULSHIFT32(x1re, a0im) + MULSHIFT32(x1im, a0re); + x1re = XBufLo[0]; /* RE{XBuf[n]} */ + x1im = XBufLo[1]; /* IM{XBuf[n]} */ + XBufLo += (64*2); + + /* lost 4 fbits when scaling by a0re/im, a1re/im (Q28) */ + ACCre = CLIP_2N_SHIFT30(ACCre, 4); + ACCre += x1re; + ACCim = CLIP_2N_SHIFT30(ACCim, 4); + ACCim += x1im; + + XBufHi[0] = ACCre; + XBufHi[1] = ACCim; + XBufHi += (64*2); + + /* update guard bit masks */ + gbMask = FASTABS(ACCre); + gbMask |= FASTABS(ACCim); + gbIdx = (i >> 5) & 0x01; /* 0 if i < 32, 1 if i >= 32 */ + sbrChan->gbMask[gbIdx] |= gbMask; + } + } else { + XBufLo = (int *)m_PSInfoSBR->XBuf[iStart][p]; + for (i = iStart; i < iEnd; i++) { + XBufHi[0] = XBufLo[0]; + XBufHi[1] = XBufLo[1]; + XBufLo += (64*2); + XBufHi += (64*2); + } + } + k++; /* high QMF band */ + } + } +} +/*********************************************************************************************************************** + * Function: DecodeHuffmanScalar + * + * Description: decode one Huffman symbol from bitstream + * + * Inputs: pointers to Huffman table and info struct + * left-aligned bit buffer with >= huffTabInfo->maxBits bits + * + * Outputs: decoded symbol in *val + * + * Return: number of bits in symbol + * + * Notes: assumes canonical Huffman codes: + * first CW always 0, we have "count" CW's of length "nBits" bits + * starting CW for codes of length nBits+1 = + * (startCW[nBits] + count[nBits]) << 1 + * if there are no codes at nBits, then we just keep << 1 each time + * (since count[nBits] = 0) + **********************************************************************************************************************/ +int DecodeHuffmanScalar(const signed int *huffTab, const HuffInfo_t *huffTabInfo, unsigned int bitBuf, + signed int *val) { + + unsigned int count, start, shift, t; + const uint8_t *countPtr; + const signed int /*short*/*map; + + map = huffTab + huffTabInfo->offset; + countPtr = huffTabInfo->count; + + start = 0; + count = 0; + shift = 32; + do { + start += count; + start <<= 1; + map += count; + count = *countPtr++; + shift--; + t = (bitBuf >> shift) - start; + } while(t >= count); + + *val = (signed int) map[t]; + return (countPtr - huffTabInfo->count); +} +/*********************************************************************************************************************** + * Function: DecodeOneSymbol + * + * Description: dequantize one Huffman symbol from bitstream, + * using table huffTabSBR[huffTabIndex] + * + * Inputs: index of Huffman table + * + * Outputs: bitstream advanced by number of bits in codeword + * + * Return: one decoded symbol + **********************************************************************************************************************/ +int DecodeOneSymbol(int huffTabIndex) { + + int nBits, val; + unsigned int bitBuf; + const HuffInfo_t *hi; + + hi = &(huffTabSBRInfo[huffTabIndex]); + + bitBuf = GetBitsNoAdvance(hi->maxBits) << (32 - hi->maxBits); + nBits = DecodeHuffmanScalar(huffTabSBR, hi, bitBuf, &val); + AdvanceBitstream(nBits); + + return val; +} + +/* [1.0, sqrt(2)], format = Q29 (one guard bit for decoupling) */ +static const int envDQTab[2] PROGMEM = {0x20000000, 0x2d413ccc}; + +/*********************************************************************************************************************** + * Function: DequantizeEnvelope + * + * Description: dequantize envelope scalefactors + * + * Inputs: number of scalefactors to process + * amplitude resolution flag for this frame (0 or 1) + * quantized envelope scalefactors + * + * Outputs: dequantized envelope scalefactors + * + * Return: extra int bits in output (6 + expMax) + * in other words, output format = Q(FBITS_OUT_DQ_ENV - (6 + expMax)) + * + * Notes: dequantized scalefactors have at least 2 GB + **********************************************************************************************************************/ +int DequantizeEnvelope(int nBands, int ampRes, int8_t *envQuant, int *envDequant) { + + int exp, expMax, i, scalei; + + if(nBands <= 0) return 0; + + /* scan for largest dequant value (do separately from envelope decoding to keep code cleaner) */ + expMax = 0; + for(i = 0; i < nBands; i++) { + if(envQuant[i] > expMax) expMax = envQuant[i]; + } + + /* dequantized envelope gains + * envDequant = 64*2^(envQuant / alpha) = 2^(6 + envQuant / alpha) + * if ampRes == 0, alpha = 2 and range of envQuant = [0, 127] + * if ampRes == 1, alpha = 1 and range of envQuant = [0, 63] + * also if coupling is on, envDequant is scaled by something in range [0, 2] + * so range of envDequant = [2^6, 2^69] (no coupling), [2^6, 2^70] (with coupling) + * + * typical range (from observation) of envQuant/alpha = [0, 27] --> largest envQuant ~= 2^33 + * output: Q(29 - (6 + expMax)) + * + * reference: 14496-3:2001(E)/4.6.18.3.5 and 14496-4:200X/FPDAM8/5.6.5.1.2.1.5 + */ + if(ampRes) { + do { + exp = *envQuant++; + scalei = MIN(expMax - exp, 31); + *envDequant++ = envDQTab[0] >> scalei; + } while(--nBands); + + return (6 + expMax); + } + else { + expMax >>= 1; + do { + exp = *envQuant++; + scalei = MIN(expMax - (exp >> 1), 31); + *envDequant++ = envDQTab[exp & 0x01] >> scalei; + } while(--nBands); + + return (6 + expMax); + } + +} +/*********************************************************************************************************************** + * Function: DequantizeNoise + * + * Description: dequantize noise scalefactors + * + * Inputs: number of scalefactors to process + * quantized noise scalefactors + * + * Outputs: dequantized noise scalefactors, format = Q(FBITS_OUT_DQ_NOISE) + * + * Return: none + * + * Notes: dequantized scalefactors have at least 2 GB + **********************************************************************************************************************/ +void DequantizeNoise(int nBands, int8_t *noiseQuant, int *noiseDequant) { + + int exp, scalei; + + if(nBands <= 0) return; + + /* dequantize noise floor gains (4.6.18.3.5): + * noiseDequant = 2^(NOISE_FLOOR_OFFSET - noiseQuant) + * + * range of noiseQuant = [0, 30] (see 4.6.18.3.6), NOISE_FLOOR_OFFSET = 6 + * so range of noiseDequant = [2^-24, 2^6] + */ + do { + exp = *noiseQuant++; + scalei = NOISE_FLOOR_OFFSET - exp + FBITS_OUT_DQ_NOISE; /* 6 + 24 - exp, exp = [0,30] */ + + if(scalei < 0) + *noiseDequant++ = 0; + else if(scalei < 30) + *noiseDequant++ = 1 << scalei; + else + *noiseDequant++ = 0x3fffffff; /* leave 2 GB */ + + } while(--nBands); +} +/*********************************************************************************************************************** + * Function: DecodeSBREnvelope + * + * Description: decode delta Huffman coded envelope scalefactors from bitstream + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: dequantized env scalefactors for left channel (before decoupling) + * dequantized env scalefactors for right channel (if coupling off) + * or raw decoded env scalefactors for right channel (if coupling on) + * + * Return: none + **********************************************************************************************************************/ +void DecodeSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int huffIndexTime, huffIndexFreq, env, envStartBits, band, nBands, sf, lastEnv; + int freqRes, freqResPrev, dShift, i; + + if(m_PSInfoSBR->couplingFlag && ch) { + dShift = 1; + if(sbrGrid->ampResFrame) { + huffIndexTime = HuffTabSBR_tEnv30b; + huffIndexFreq = HuffTabSBR_fEnv30b; + envStartBits = 5; + } + else { + huffIndexTime = HuffTabSBR_tEnv15b; + huffIndexFreq = HuffTabSBR_fEnv15b; + envStartBits = 6; + } + } + else { + dShift = 0; + if(sbrGrid->ampResFrame) { + huffIndexTime = HuffTabSBR_tEnv30; + huffIndexFreq = HuffTabSBR_fEnv30; + envStartBits = 6; + } + else { + huffIndexTime = HuffTabSBR_tEnv15; + huffIndexFreq = HuffTabSBR_fEnv15; + envStartBits = 7; + } + } + + /* range of envDataQuant[] = [0, 127] (see comments in DequantizeEnvelope() for reference) */ + for(env = 0; env < sbrGrid->numEnv; env++) { + nBands = (sbrGrid->freqRes[env] ? sbrFreq->nHigh : sbrFreq->nLow); + freqRes = (sbrGrid->freqRes[env]); + freqResPrev = (env == 0 ? sbrGrid->freqResPrev : sbrGrid->freqRes[env - 1]); + lastEnv = (env == 0 ? sbrGrid->numEnvPrev - 1 : env - 1); + if(lastEnv < 0) lastEnv = 0; /* first frame */ + + ASSERT(nBands <= MAX_QMF_BANDS); + + if(sbrChan->deltaFlagEnv[env] == 0) { + /* delta coding in freq */ + sf = GetBits(envStartBits) << dShift; + sbrChan->envDataQuant[env][0] = sf; + for(band = 1; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexFreq) << dShift; + sbrChan->envDataQuant[env][band] = sf + sbrChan->envDataQuant[env][band - 1]; + } + } + else if(freqRes == freqResPrev) { + /* delta coding in time - same freq resolution for both frames */ + for(band = 0; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->envDataQuant[env][band] = sf + sbrChan->envDataQuant[lastEnv][band]; + } + } + else if(freqRes == 0 && freqResPrev == 1) { + /* delta coding in time - low freq resolution for new frame, high freq resolution for old frame */ + for(band = 0; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->envDataQuant[env][band] = sf; + for(i = 0; i < sbrFreq->nHigh; i++) { + if(sbrFreq->freqHigh[i] == sbrFreq->freqLow[band]) { + sbrChan->envDataQuant[env][band] += sbrChan->envDataQuant[lastEnv][i]; + break; + } + } + } + } + else if(freqRes == 1 && freqResPrev == 0) { + /* delta coding in time - high freq resolution for new frame, low freq resolution for old frame */ + for(band = 0; band < nBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->envDataQuant[env][band] = sf; + for(i = 0; i < sbrFreq->nLow; i++) { + if(sbrFreq->freqLow[i] <= sbrFreq->freqHigh[band] + && sbrFreq->freqHigh[band] < sbrFreq->freqLow[i + 1]) { + sbrChan->envDataQuant[env][band] += sbrChan->envDataQuant[lastEnv][i]; + break; + } + } + } + } + + /* skip coupling channel */ + if(ch != 1 || m_PSInfoSBR->couplingFlag != 1) + m_PSInfoSBR->envDataDequantScale[ch][env] = DequantizeEnvelope(nBands, sbrGrid->ampResFrame, + sbrChan->envDataQuant[env], m_PSInfoSBR->envDataDequant[ch][env]); + } + sbrGrid->numEnvPrev = sbrGrid->numEnv; + sbrGrid->freqResPrev = sbrGrid->freqRes[sbrGrid->numEnv - 1]; +} +/*********************************************************************************************************************** + * Function: DecodeSBRNoise + * + * Description: decode delta Huffman coded noise scalefactors from bitstream + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel + * index of current channel (0 for SCE, 0 or 1 for CPE) + * + * Outputs: dequantized noise scalefactors for left channel (before decoupling) + * dequantized noise scalefactors for right channel (if coupling off) + * or raw decoded noise scalefactors for right channel (if coupling on) + * + * Return: none + **********************************************************************************************************************/ +void DecodeSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch) { + + int huffIndexTime, huffIndexFreq, noiseFloor, band, dShift, sf, lastNoiseFloor; + + if(m_PSInfoSBR->couplingFlag && ch) { + dShift = 1; + huffIndexTime = HuffTabSBR_tNoise30b; + huffIndexFreq = HuffTabSBR_fNoise30b; + } + else { + dShift = 0; + huffIndexTime = HuffTabSBR_tNoise30; + huffIndexFreq = HuffTabSBR_fNoise30; + } + + for(noiseFloor = 0; noiseFloor < sbrGrid->numNoiseFloors; noiseFloor++) { + lastNoiseFloor = (noiseFloor == 0 ? sbrGrid->numNoiseFloorsPrev - 1 : noiseFloor - 1); + if(lastNoiseFloor < 0) lastNoiseFloor = 0; /* first frame */ + + ASSERT(sbrFreq->numNoiseFloorBands <= MAX_QMF_BANDS); + + if(sbrChan->deltaFlagNoise[noiseFloor] == 0) { + /* delta coding in freq */ + sbrChan->noiseDataQuant[noiseFloor][0] = GetBits(5) << dShift; + for(band = 1; band < sbrFreq->numNoiseFloorBands; band++) { + sf = DecodeOneSymbol(huffIndexFreq) << dShift; + sbrChan->noiseDataQuant[noiseFloor][band] = sf + sbrChan->noiseDataQuant[noiseFloor][band - 1]; + } + } + else { + /* delta coding in time */ + for(band = 0; band < sbrFreq->numNoiseFloorBands; band++) { + sf = DecodeOneSymbol(huffIndexTime) << dShift; + sbrChan->noiseDataQuant[noiseFloor][band] = sf + sbrChan->noiseDataQuant[lastNoiseFloor][band]; + } + } + + /* skip coupling channel */ + if(ch != 1 || m_PSInfoSBR->couplingFlag != 1) + DequantizeNoise(sbrFreq->numNoiseFloorBands, sbrChan->noiseDataQuant[noiseFloor], + m_PSInfoSBR->noiseDataDequant[ch][noiseFloor]); + } + sbrGrid->numNoiseFloorsPrev = sbrGrid->numNoiseFloors; +} + +/* dqTabCouple[i] = 2 / (1 + 2^(12 - i)), format = Q30 */ +static const int dqTabCouple[25] PROGMEM = { + 0x0007ff80, 0x000ffe00, 0x001ff802, 0x003fe010, 0x007f8080, 0x00fe03f8, 0x01f81f82, 0x03e0f83e, + 0x07878788, 0x0e38e38e, 0x1999999a, 0x2aaaaaab, 0x40000000, 0x55555555, 0x66666666, 0x71c71c72, + 0x78787878, 0x7c1f07c2, 0x7e07e07e, 0x7f01fc08, 0x7f807f80, 0x7fc01ff0, 0x7fe007fe, 0x7ff00200, + 0x7ff80080, +}; + +/*********************************************************************************************************************** + * Function: UncoupleSBREnvelope + * + * Description: scale dequantized envelope scalefactors according to channel + * coupling rules + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for right channel including + * quantized envelope scalefactors + * + * Outputs: dequantized envelope data for left channel (after decoupling) + * dequantized envelope data for right channel (after decoupling) + * + * Return: none + **********************************************************************************************************************/ +void UncoupleSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR) { + + int env, band, nBands, scalei, E_1; + + scalei = (sbrGrid->ampResFrame ? 0 : 1); + for(env = 0; env < sbrGrid->numEnv; env++) { + nBands = (sbrGrid->freqRes[env] ? sbrFreq->nHigh : sbrFreq->nLow); + m_PSInfoSBR->envDataDequantScale[1][env] = m_PSInfoSBR->envDataDequantScale[0][env]; + for(band = 0; band < nBands; band++) { + /* clip E_1 to [0, 24] (scalefactors approach 0 or 2) */ + E_1 = sbrChanR->envDataQuant[env][band] >> scalei; + if(E_1 < 0) E_1 = 0; + if(E_1 > 24) E_1 = 24; + + /* envDataDequant[0] has 1 GB, so << by 2 is okay */ + m_PSInfoSBR->envDataDequant[1][env][band] = MULSHIFT32(m_PSInfoSBR->envDataDequant[0][env][band], + dqTabCouple[24 - E_1]) << 2; + m_PSInfoSBR->envDataDequant[0][env][band] = MULSHIFT32(m_PSInfoSBR->envDataDequant[0][env][band], + dqTabCouple[E_1]) << 2; + } + } +} +/*********************************************************************************************************************** + * Function: UncoupleSBRNoise + * + * Description: scale dequantized noise floor scalefactors according to channel + * coupling rules + * + * Inputs: initialized SBRGrid struct for this channel + * initialized SBRFreq struct for this SCE/CPE block + * initialized SBRChan struct for this channel including + * quantized noise scalefactors + * + * Outputs: dequantized noise data for left channel (after decoupling) + * dequantized noise data for right channel (after decoupling) + * + * Return: none + **********************************************************************************************************************/ +void UncoupleSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR) { + + int noiseFloor, band, Q_1; + + for (noiseFloor = 0; noiseFloor < sbrGrid->numNoiseFloors; noiseFloor++) { + for (band = 0; band < sbrFreq->numNoiseFloorBands; band++) { + /* Q_1 should be in range [0, 24] according to 4.6.18.3.6, but check to make sure */ + Q_1 = sbrChanR->noiseDataQuant[noiseFloor][band]; + if (Q_1 < 0) Q_1 = 0; + if (Q_1 > 24) Q_1 = 24; + + /* noiseDataDequant[0] has 1 GB, so << by 2 is okay */ + m_PSInfoSBR->noiseDataDequant[1][noiseFloor][band] = + MULSHIFT32(m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band], dqTabCouple[24 - Q_1]) << 2; + m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band] = + MULSHIFT32(m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band], dqTabCouple[Q_1]) << 2; + } + } +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapNoClip + * + * Description: apply synthesis window, do overlap-add without clipping, + * for winSequence LONG-LONG + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + out1 = out0 + 1024 - 1; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + if (winTypeCurr == winTypePrev) { + /* cut window loads in half since current and overlap sections use same symmetric window */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } else { + /* different windows for current and overlap parts - should still fit in registers on ARM w/o stack spill */ + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStart + * + * Description: apply synthesis window, do overlap-add, without clipping + * for winSequence LONG-START + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapLongStartNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int i, in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + out1 = out0 + 1024 - 1; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + in = *buf1--; + + *over1-- = 0; /* Wn = 0 for n = (2047, 2046, ... 1600) */ + *over0++ = in >> 1; /* Wn = 1 for n = (1024, 1025, ... 1471) */ + } while (--i); + + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; + w1 = *wndPrev++; + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; /* W[0], W[1], ... --> W[255], W[254], ... */ + w1 = *wndCurr++; /* W[127], W[126], ... --> W[128], W[129], ... */ + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); /* Wn = short window for n = (1599, 1598, ... , 1536) */ + *over0++ = MULSHIFT32(w1, in); /* Wn = short window for n = (1472, 1473, ... , 1535) */ + } while (over0 < over1); +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapLongStop + * + * Description: apply synthesis window, do overlap-add, without clipping + * for winSequence LONG-STOP + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapLongStopNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int i, in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + buf0 += (1024 >> 1); + buf1 = buf0 - 1; + out1 = out0 + 1024 - 1; + over1 = over0 + 1024 - 1; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]); + + i = 448; /* 2 outputs, 2 overlaps per loop */ + do { + /* Wn = 0 for n = (0, 1, ... 447) */ + /* Wn = 1 for n = (576, 577, ... 1023) */ + in = *buf0++; + f1 = in >> 1; /* scale since skipping multiply by Q31 */ + + in = *over0; + *out0++ = in; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (--i); + + /* do 64 more loops - 2 outputs, 2 overlaps per loop */ + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); +} +/*********************************************************************************************************************** + * Function: DecWindowOverlapShort + * + * Description: apply synthesis window, do overlap-add, without clipping + * for winSequence EIGHT-SHORT (does all 8 short blocks) + * + * Inputs: input buffer (output of type-IV DCT) + * overlap buffer (saved from last time) + * window type (sin or KBD) for input buffer + * window type (sin or KBD) for overlap buffer + * + * Outputs: one channel, one frame of 32-bit PCM, non-interleaved + * + * Return: none + * + * Notes: use this function when the decoded PCM is going to the SBR decoder + **********************************************************************************************************************/ +void DecWindowOverlapShortNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev) { + + int i, in, w0, w1, f0, f1; + int *buf1, *over1, *out1; + const int *wndPrev, *wndCurr; + + wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]); + + /* pcm[0-447] = 0 + overlap[0-447] */ + i = 448; + do { + f0 = *over0++; + f1 = *over0++; + *out0++ = f0; + *out0++ = f1; + i -= 2; + } while (i); + + /* pcm[448-575] = Wp[0-127] * block0[0-127] + overlap[448-575] */ + out1 = out0 + (128 - 1); + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + do { + w0 = *wndPrev++; /* W[0], W[1], ...W[63] */ + w1 = *wndPrev++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *over0; + *out0++ = in - f0; + + in = *over1; + *out1-- = in + f1; + + w0 = *wndCurr++; + w1 = *wndCurr++; + in = *buf1--; + + /* save over0/over1 for next short block, in the slots just vacated */ + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + + /* pcm[576-703] = Wc[128-255] * block0[128-255] + Wc[0-127] * block1[0-127] + overlap[576-703] + * pcm[704-831] = Wc[128-255] * block1[128-255] + Wc[0-127] * block2[0-127] + overlap[704-831] + * pcm[832-959] = Wc[128-255] * block2[128-255] + Wc[0-127] * block3[0-127] + overlap[832-959] + */ + for (i = 0; i < 3; i++) { + out0 += 64; + out1 = out0 + 128 - 1; + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 - 128); /* from last short block */ + in += *(over0 + 0); /* from last full frame */ + *out0++ = in - f0; + + in = *(over1 - 128); /* from last short block */ + in += *(over1 + 0); /* from last full frame */ + *out1-- = in + f1; + + /* save over0/over1 for next short block, in the slots just vacated */ + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* pcm[960-1023] = Wc[128-191] * block3[128-191] + Wc[0-63] * block4[0-63] + overlap[960-1023] + * over[0-63] = Wc[192-255] * block3[192-255] + Wc[64-127] * block4[64-127] + */ + out0 += 64; + over0 -= 832; /* points at overlap[64] */ + over1 = over0 + 128 - 1; /* points at overlap[191] */ + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + in = *(over0 + 768); /* from last short block */ + in += *(over0 + 896); /* from last full frame */ + *out0++ = in - f0; + + in = *(over1 + 768); /* from last short block */ + *(over1 - 128) = in + f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); /* save in overlap[128-191] */ + *over0++ = MULSHIFT32(w1, in); /* save in overlap[64-127] */ + } while (over0 < over1); + + /* over0 now points at overlap[128] */ + + /* over[64-191] = Wc[128-255] * block4[128-255] + Wc[0-127] * block5[0-127] + * over[192-319] = Wc[128-255] * block5[128-255] + Wc[0-127] * block6[0-127] + * over[320-447] = Wc[128-255] * block6[128-255] + Wc[0-127] * block7[0-127] + * over[448-576] = Wc[128-255] * block7[128-255] + */ + for (i = 0; i < 3; i++) { + over0 += 64; + over1 = over0 + 128 - 1; + buf0 += 64; + buf1 = buf0 - 1; + wndCurr -= 128; + do { + w0 = *wndCurr++; /* W[0], W[1], ...W[63] */ + w1 = *wndCurr++; /* W[127], W[126], ... W[64] */ + in = *buf0++; + + f0 = MULSHIFT32(w0, in); + f1 = MULSHIFT32(w1, in); + + /* from last short block */ + *(over0 - 128) -= f0; + *(over1 - 128)+= f1; + + in = *buf1--; + *over1-- = MULSHIFT32(w0, in); + *over0++ = MULSHIFT32(w1, in); + } while (over0 < over1); + } + + /* over[576-1024] = 0 */ + i = 448; + over0 += 64; + do { + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + *over0++ = 0; + i -= 4; + } while (i); +} +/*********************************************************************************************************************** + * Function: PreMultiply64 + * + * Description: pre-twiddle stage of 64-point DCT-IV + * + * Inputs: buffer of 64 samples + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out, gains 2 int bits + * gbOut = gbIn + 1 + * output is limited to sqrt(2)/2 plus GB in full GB + * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add + **********************************************************************************************************************/ +void PreMultiply64(int *zbuf1) { + + int i, ar1, ai1, ar2, ai2, z1, z2; + int t, cms2, cps2a, sin2a, cps2b, sin2b; + int *zbuf2; + const int *csptr; + + zbuf2 = zbuf1 + 64 - 1; + csptr = cos4sin4tab64; + + /* whole thing should fit in registers - verify that compiler does this */ + for (i = 64 >> 2; i != 0; i--) { + /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */ + cps2a = *csptr++; + sin2a = *csptr++; + cps2b = *csptr++; + sin2b = *csptr++; + + ar1 = *(zbuf1 + 0); + ai2 = *(zbuf1 + 1); + ai1 = *(zbuf2 + 0); + ar2 = *(zbuf2 - 1); + + /* gain 2 ints bit from MULSHIFT32 by Q30 + * max per-sample gain (ignoring implicit scaling) = MAX(sin(angle)+cos(angle)) = 1.414 + * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from + * extra sign bits, and eat one in adding + */ + t = MULSHIFT32(sin2a, ar1 + ai1); + z2 = MULSHIFT32(cps2a, ai1) - t; + cms2 = cps2a - 2*sin2a; + z1 = MULSHIFT32(cms2, ar1) + t; + *zbuf1++ = z1; /* cos*ar1 + sin*ai1 */ + *zbuf1++ = z2; /* cos*ai1 - sin*ar1 */ + + t = MULSHIFT32(sin2b, ar2 + ai2); + z2 = MULSHIFT32(cps2b, ai2) - t; + cms2 = cps2b - 2*sin2b; + z1 = MULSHIFT32(cms2, ar2) + t; + *zbuf2-- = z2; /* cos*ai2 - sin*ar2 */ + *zbuf2-- = z1; /* cos*ar2 + sin*ai2 */ + } +} +/*********************************************************************************************************************** + * Function: PostMultiply64 + * + * Description: post-twiddle stage of 64-point type-IV DCT + * + * Inputs: buffer of 64 samples + * number of output samples to calculate + * + * Outputs: processed samples in same buffer + * + * Return: none + * + * Notes: minimum 1 GB in, 2 GB out, gains 2 int bits + * gbOut = gbIn + 1 + * output is limited to sqrt(2)/2 plus GB in full GB + * nSampsOut is rounded up to next multiple of 4, since we calculate + * 4 samples per loop + **********************************************************************************************************************/ +void PostMultiply64(int *fft1, int nSampsOut) { + + int i, ar1, ai1, ar2, ai2; + int t, cms2, cps2, sin2; + int *fft2; + const int *csptr; + + csptr = cos1sin1tab64; + fft2 = fft1 + 64 - 1; + + /* load coeffs for first pass + * cps2 = (cos+sin)/2, sin2 = sin/2, cms2 = (cos-sin)/2 + */ + cps2 = *csptr++; + sin2 = *csptr++; + cms2 = cps2 - 2*sin2; + + for (i = (nSampsOut + 3) >> 2; i != 0; i--) { + ar1 = *(fft1 + 0); + ai1 = *(fft1 + 1); + ar2 = *(fft2 - 1); + ai2 = *(fft2 + 0); + + /* gain 2 int bits (multiplying by Q30), max gain = sqrt(2) */ + t = MULSHIFT32(sin2, ar1 + ai1); + *fft2-- = t - MULSHIFT32(cps2, ai1); + *fft1++ = t + MULSHIFT32(cms2, ar1); + + cps2 = *csptr++; + sin2 = *csptr++; + + ai2 = -ai2; + t = MULSHIFT32(sin2, ar2 + ai2); + *fft2-- = t - MULSHIFT32(cps2, ai2); + cms2 = cps2 - 2*sin2; + *fft1++ = t + MULSHIFT32(cms2, ar2); + } +} +/*********************************************************************************************************************** + * Function: QMFAnalysisConv + * + * Description: convolution kernel for analysis QMF + * + * Inputs: pointer to coefficient table, reordered for sequential access + * delay buffer of size 32*10 = 320 real-valued PCM samples + * index for delay ring buffer (range = [0, 9]) + * + * Outputs: 64 consecutive 32-bit samples + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrqmfak.s when building for ARM! + **********************************************************************************************************************/ +void QMFAnalysisConv(int *cTab, int *delay, int dIdx, int *uBuf) { + + int k, dOff; + int *cPtr0, *cPtr1; + U64 u64lo, u64hi; + + dOff = dIdx*32 + 31; + cPtr0 = cTab; + cPtr1 = cTab + 33*5 - 1; + + /* special first pass since we need to flip sign to create cTab[384], cTab[512] */ + u64lo.w64 = 0; + u64hi.w64 = 0; + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, -(*cPtr1--), delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, -(*cPtr1--), delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + + uBuf[0] = u64lo.r.hi32; + uBuf[32] = u64hi.r.hi32; + uBuf++; + dOff--; + + /* max gain for any sample in uBuf, after scaling by cTab, ~= 0.99 + * so we can just sum the uBuf values with no overflow problems + */ + for (k = 1; k <= 31; k++) { + u64lo.w64 = 0; + u64hi.w64 = 0; + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64lo.w64 = MADD64(u64lo.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;} + + uBuf[0] = u64lo.r.hi32; + uBuf[32] = u64hi.r.hi32; + uBuf++; + dOff--; + } +} +/*********************************************************************************************************************** + * Function: QMFAnalysis + * + * Description: 32-subband analysis QMF (4.6.18.4.1) + * + * Inputs: 32 consecutive samples of decoded 32-bit PCM, format = Q(fBitsIn) + * delay buffer of size 32*10 = 320 PCM samples + * number of fraction bits in input PCM + * index for delay ring buffer (range = [0, 9]) + * number of subbands to calculate (range = [0, 32]) + * + * Outputs: qmfaBands complex subband samples, format = Q(FBITS_OUT_QMFA) + * updated delay buffer + * updated delay index + * + * Return: guard bit mask + * + * Notes: output stored as RE{X0}, IM{X0}, RE{X1}, IM{X1}, ... RE{X31}, IM{X31} + * output stored in int buffer of size 64*2 = 128 + * (zero-filled from XBuf[2*qmfaBands] to XBuf[127]) + **********************************************************************************************************************/ +int QMFAnalysis(int *inbuf, int *delay, int *XBuf, int fBitsIn, int *delayIdx, int qmfaBands) { + + int n, y, shift, gbMask; + int *delayPtr, *uBuf, *tBuf; + + /* use XBuf[128] as temp buffer for reordering */ + uBuf = XBuf; /* first 64 samples */ + tBuf = XBuf + 64; /* second 64 samples */ + + /* overwrite oldest PCM with new PCM + * delay[n] has 1 GB after shifting (either << or >>) + */ + delayPtr = delay + (*delayIdx * 32); + if (fBitsIn > FBITS_IN_QMFA) { + shift = MIN(fBitsIn - FBITS_IN_QMFA, 31); + for (n = 32; n != 0; n--) { + y = (*inbuf) >> shift; + inbuf++; + *delayPtr++ = y; + } + } else { + shift = MIN(FBITS_IN_QMFA - fBitsIn, 30); + for (n = 32; n != 0; n--) { + y = *inbuf++; + y = CLIP_2N_SHIFT30(y, shift); + *delayPtr++ = y; + } + } + + QMFAnalysisConv((int *)cTabA, delay, *delayIdx, uBuf); + + /* uBuf has at least 2 GB right now (1 from clipping to Q(FBITS_IN_QMFA), one from + * the scaling by cTab (MULSHIFT32(*delayPtr--, *cPtr++), with net gain of < 1.0) + */ + tBuf[2*0 + 0] = uBuf[0]; + tBuf[2*0 + 1] = uBuf[1]; + for (n = 1; n < 31; n++) { + tBuf[2*n + 0] = -uBuf[64-n]; + tBuf[2*n + 1] = uBuf[n+1]; + } + tBuf[2*31 + 1] = uBuf[32]; + tBuf[2*31 + 0] = -uBuf[33]; + + /* fast in-place DCT-IV - only need 2*qmfaBands output samples */ + PreMultiply64(tBuf); /* 2 GB in, 3 GB out */ + FFT32C(tBuf); /* 3 GB in, 1 GB out */ + PostMultiply64(tBuf, qmfaBands*2); /* 1 GB in, 2 GB out */ + + gbMask = 0; + for (n = 0; n < qmfaBands; n++) { + XBuf[2*n+0] = tBuf[ n + 0]; /* implicit scaling of 2 in our output Q format */ + gbMask |= FASTABS(XBuf[2*n+0]); + XBuf[2*n+1] = -tBuf[63 - n]; + gbMask |= FASTABS(XBuf[2*n+1]); + } + + /* fill top section with zeros for HF generation */ + for ( ; n < 64; n++) { + XBuf[2*n+0] = 0; + XBuf[2*n+1] = 0; + } + + *delayIdx = (*delayIdx == NUM_QMF_DELAY_BUFS - 1 ? 0 : *delayIdx + 1); + + /* minimum of 2 GB in output */ + return gbMask; +} +/*********************************************************************************************************************** + * Function: QMFSynthesisConv + * + * Description: final convolution kernel for synthesis QMF + * + * Inputs: pointer to coefficient table, reordered for sequential access + * delay buffer of size 64*10 = 640 complex samples (1280 ints) + * index for delay ring buffer (range = [0, 9]) + * number of QMF subbands to process (range = [0, 64]) + * number of channels + * + * Outputs: 64 consecutive 16-bit PCM samples, interleaved by factor of nChans + * + * Return: none + * + * Notes: this is carefully written to be efficient on ARM + * use the assembly code version in sbrqmfsk.s when building for ARM! + **********************************************************************************************************************/ +void QMFSynthesisConv(int *cPtr, int *delay, int dIdx, short *outbuf, int nChans) { + + int k, dOff0, dOff1; + U64 sum64; + + dOff0 = (dIdx)*128; + dOff1 = dOff0 - 1; + if (dOff1 < 0) + dOff1 += 1280; + + /* scaling note: total gain of coefs (cPtr[0]-cPtr[9] for any k) is < 2.0, so 1 GB in delay values is adequate */ + for (k = 0; k <= 63; k++) { + sum64.w64 = 0; + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;} + sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;} + + dOff0++; + dOff1--; + *outbuf = CLIPTOSHORT((sum64.r.hi32 + RND_VAL) >> FBITS_OUT_QMFS); + outbuf += nChans; + } +} +/*********************************************************************************************************************** + * Function: QMFSynthesis + * + * Description: 64-subband synthesis QMF (4.6.18.4.2) + * + * Inputs: 64 consecutive complex subband QMF samples, format = Q(FBITS_IN_QMFS) + * delay buffer of size 64*10 = 640 complex samples (1280 ints) + * index for delay ring buffer (range = [0, 9]) + * number of QMF subbands to process (range = [0, 64]) + * number of channels + * + * Outputs: 64 consecutive 16-bit PCM samples, interleaved by factor of nChans + * updated delay buffer + * updated delay index + * + * Return: none + * + * Notes: assumes MIN_GBITS_IN_QMFS guard bits in input, either from + * QMFAnalysis (if upsampling only) or from MapHF (if SBR on) + **********************************************************************************************************************/ +void QMFSynthesis(int *inbuf, int *delay, int *delayIdx, int qmfsBands, short *outbuf, int nChans) { + + int n, a0, a1, b0, b1, dOff0, dOff1, dIdx; + int *tBufLo, *tBufHi; + + dIdx = *delayIdx; + tBufLo = delay + dIdx*128 + 0; + tBufHi = delay + dIdx*128 + 127; + + /* reorder inputs to DCT-IV, only use first qmfsBands (complex) samples + */ + for (n = 0; n < qmfsBands >> 1; n++) { + a0 = *inbuf++; + b0 = *inbuf++; + a1 = *inbuf++; + b1 = *inbuf++; + *tBufLo++ = a0; + *tBufLo++ = a1; + *tBufHi-- = b0; + *tBufHi-- = b1; + } + if (qmfsBands & 0x01) { + a0 = *inbuf++; + b0 = *inbuf++; + *tBufLo++ = a0; + *tBufHi-- = b0; + *tBufLo++ = 0; + *tBufHi-- = 0; + n++; + } + for ( ; n < 32; n++) { + *tBufLo++ = 0; + *tBufHi-- = 0; + *tBufLo++ = 0; + *tBufHi-- = 0; + } + + tBufLo = delay + dIdx*128 + 0; + tBufHi = delay + dIdx*128 + 64; + + /* 2 GB in, 3 GB out */ + PreMultiply64(tBufLo); + PreMultiply64(tBufHi); + + /* 3 GB in, 1 GB out */ + FFT32C(tBufLo); + FFT32C(tBufHi); + + /* 1 GB in, 2 GB out */ + PostMultiply64(tBufLo, 64); + PostMultiply64(tBufHi, 64); + + /* could fuse with PostMultiply64 to avoid separate pass */ + dOff0 = dIdx*128; + dOff1 = dIdx*128 + 64; + for (n = 32; n != 0; n--) { + a0 = (*tBufLo++); + a1 = (*tBufLo++); + b0 = (*tBufHi++); + b1 = -(*tBufHi++); + + delay[dOff0++] = (b0 - a0); + delay[dOff0++] = (b1 - a1); + delay[dOff1++] = (b0 + a0); + delay[dOff1++] = (b1 + a1); + } + + QMFSynthesisConv((int *)cTabS, delay, dIdx, outbuf, nChans); + + *delayIdx = (*delayIdx == NUM_QMF_DELAY_BUFS - 1 ? 0 : *delayIdx + 1); +} +/*********************************************************************************************************************** + * Function: UnpackSBRHeader + * + * Description: unpack SBR header (table 4.56) + * + * Inputs: BitStreamInfo struct pointing to start of SBR header + * + * Outputs: initialized SBRHeader struct for this SCE/CPE block + * + * Return: non-zero if frame reset is triggered, zero otherwise + **********************************************************************************************************************/ +int UnpackSBRHeader(SBRHeader *sbrHdr) { + + SBRHeader sbrHdrPrev; + + /* save previous values so we know whether to reset decoder */ + sbrHdrPrev.startFreq = sbrHdr->startFreq; + sbrHdrPrev.stopFreq = sbrHdr->stopFreq; + sbrHdrPrev.freqScale = sbrHdr->freqScale; + sbrHdrPrev.alterScale = sbrHdr->alterScale; + sbrHdrPrev.crossOverBand = sbrHdr->crossOverBand; + sbrHdrPrev.noiseBands = sbrHdr->noiseBands; + + sbrHdr->ampRes = GetBits(1); + sbrHdr->startFreq = GetBits(4); + sbrHdr->stopFreq = GetBits(4); + sbrHdr->crossOverBand = GetBits(3); + sbrHdr->resBitsHdr = GetBits(2); + sbrHdr->hdrExtra1 = GetBits(1); + sbrHdr->hdrExtra2 = GetBits(1); + + if (sbrHdr->hdrExtra1) { + sbrHdr->freqScale = GetBits(2); + sbrHdr->alterScale = GetBits(1); + sbrHdr->noiseBands = GetBits(2); + } else { + /* defaults */ + sbrHdr->freqScale = 2; + sbrHdr->alterScale = 1; + sbrHdr->noiseBands = 2; + } + + if (sbrHdr->hdrExtra2) { + sbrHdr->limiterBands = GetBits(2); + sbrHdr->limiterGains = GetBits(2); + sbrHdr->interpFreq = GetBits(1); + sbrHdr->smoothMode = GetBits(1); + } else { + /* defaults */ + sbrHdr->limiterBands = 2; + sbrHdr->limiterGains = 2; + sbrHdr->interpFreq = 1; + sbrHdr->smoothMode = 1; + } + sbrHdr->count++; + + /* if any of these have changed from previous frame, reset the SBR module */ + if (sbrHdr->startFreq != sbrHdrPrev.startFreq || sbrHdr->stopFreq != sbrHdrPrev.stopFreq || + sbrHdr->freqScale != sbrHdrPrev.freqScale || sbrHdr->alterScale != sbrHdrPrev.alterScale || + sbrHdr->crossOverBand != sbrHdrPrev.crossOverBand || sbrHdr->noiseBands != sbrHdrPrev.noiseBands + ) + return -1; + else + return 0; +} + +/* cLog2[i] = ceil(log2(i)) (disregard i == 0) */ +static const uint8_t cLog2[9] = {0, 0, 1, 2, 2, 3, 3, 3, 3}; +/*********************************************************************************************************************** + * Function: UnpackSBRGrid + * + * Description: unpack SBR grid (table 4.62) + * + * Inputs: BitStreamInfo struct pointing to start of SBR grid + * initialized SBRHeader struct for this SCE/CPE block + * + * Outputs: initialized SBRGrid struct for this channel + * + * Return: none + **********************************************************************************************************************/ +void UnpackSBRGrid(SBRHeader *sbrHdr, SBRGrid *sbrGrid) { + + int numEnvRaw, env, rel, pBits, border, middleBorder = 0; + uint8_t relBordLead[MAX_NUM_ENV], relBordTrail[MAX_NUM_ENV]; + uint8_t relBorder0[3], relBorder1[3], relBorder[3]; + uint8_t numRelBorder0, numRelBorder1, numRelBorder, numRelLead = 0, numRelTrail; + uint8_t absBordLead = 0, absBordTrail = 0, absBorder; + + sbrGrid->ampResFrame = sbrHdr->ampRes; + sbrGrid->frameClass = GetBits(2); + switch(sbrGrid->frameClass){ + + case SBR_GRID_FIXFIX: + numEnvRaw = GetBits(2); + sbrGrid->numEnv = (1 << numEnvRaw); + if(sbrGrid->numEnv == 1) sbrGrid->ampResFrame = 0; + + ASSERT(sbrGrid->numEnv == 1 || sbrGrid->numEnv == 2 || sbrGrid->numEnv == 4); + + sbrGrid->freqRes[0] = GetBits(1); + for(env = 1; env < sbrGrid->numEnv; env++) + sbrGrid->freqRes[env] = sbrGrid->freqRes[0]; + + absBordLead = 0; + absBordTrail = NUM_TIME_SLOTS; + numRelLead = sbrGrid->numEnv - 1; + numRelTrail = 0; + + /* numEnv = 1, 2, or 4 */ + if(sbrGrid->numEnv == 1) + border = NUM_TIME_SLOTS / 1; + else if(sbrGrid->numEnv == 2) + border = NUM_TIME_SLOTS / 2; + else + border = NUM_TIME_SLOTS / 4; + + for(rel = 0; rel < numRelLead; rel++) + relBordLead[rel] = border; + + middleBorder = (sbrGrid->numEnv >> 1); + + break; + + case SBR_GRID_FIXVAR: + absBorder = GetBits(2) + NUM_TIME_SLOTS; + numRelBorder = GetBits(2); + sbrGrid->numEnv = numRelBorder + 1; + for(rel = 0; rel < numRelBorder; rel++) + relBorder[rel] = 2 * GetBits(2) + 2; + + pBits = cLog2[sbrGrid->numEnv + 1]; + sbrGrid->pointer = GetBits(pBits); + + for(env = sbrGrid->numEnv - 1; env >= 0; env--) + sbrGrid->freqRes[env] = GetBits(1); + + absBordLead = 0; + absBordTrail = absBorder; + numRelLead = 0; + numRelTrail = numRelBorder; + + for(rel = 0; rel < numRelTrail; rel++) + relBordTrail[rel] = relBorder[rel]; + + if(sbrGrid->pointer > 1) + middleBorder = sbrGrid->numEnv + 1 - sbrGrid->pointer; + else + middleBorder = sbrGrid->numEnv - 1; + + break; + + case SBR_GRID_VARFIX: + absBorder = GetBits(2); + numRelBorder = GetBits(2); + sbrGrid->numEnv = numRelBorder + 1; + for(rel = 0; rel < numRelBorder; rel++) + relBorder[rel] = 2 * GetBits(2) + 2; + + pBits = cLog2[sbrGrid->numEnv + 1]; + sbrGrid->pointer = GetBits(pBits); + + for(env = 0; env < sbrGrid->numEnv; env++) + sbrGrid->freqRes[env] = GetBits(1); + + absBordLead = absBorder; + absBordTrail = NUM_TIME_SLOTS; + numRelLead = numRelBorder; + numRelTrail = 0; + + for(rel = 0; rel < numRelLead; rel++) + relBordLead[rel] = relBorder[rel]; + + if(sbrGrid->pointer == 0) + middleBorder = 1; + else if(sbrGrid->pointer == 1) + middleBorder = sbrGrid->numEnv - 1; + else + middleBorder = sbrGrid->pointer - 1; + + break; + + case SBR_GRID_VARVAR: + absBordLead = GetBits(2); /* absBorder0 */ + absBordTrail = GetBits(2) + NUM_TIME_SLOTS; /* absBorder1 */ + numRelBorder0 = GetBits(2); + numRelBorder1 = GetBits(2); + + sbrGrid->numEnv = numRelBorder0 + numRelBorder1 + 1; + ASSERT(sbrGrid->numEnv <= 5); + + for(rel = 0; rel < numRelBorder0; rel++) + relBorder0[rel] = 2 * GetBits(2) + 2; + + for(rel = 0; rel < numRelBorder1; rel++) + relBorder1[rel] = 2 * GetBits(2) + 2; + + pBits = cLog2[numRelBorder0 + numRelBorder1 + 2]; + sbrGrid->pointer = GetBits(pBits); + + for(env = 0; env < sbrGrid->numEnv; env++) + sbrGrid->freqRes[env] = GetBits(1); + + numRelLead = numRelBorder0; + numRelTrail = numRelBorder1; + + for(rel = 0; rel < numRelLead; rel++) + relBordLead[rel] = relBorder0[rel]; + + for(rel = 0; rel < numRelTrail; rel++) + relBordTrail[rel] = relBorder1[rel]; + + if(sbrGrid->pointer > 1) + middleBorder = sbrGrid->numEnv + 1 - sbrGrid->pointer; + else + middleBorder = sbrGrid->numEnv - 1; + + break; + } + + /* build time border vector */ + sbrGrid->envTimeBorder[0] = absBordLead * SAMPLES_PER_SLOT; + + rel = 0; + border = absBordLead; + for(env = 1; env <= numRelLead; env++) { + border += relBordLead[rel++]; + sbrGrid->envTimeBorder[env] = border * SAMPLES_PER_SLOT; + } + + rel = 0; + border = absBordTrail; + for(env = sbrGrid->numEnv - 1; env > numRelLead; env--) { + border -= relBordTrail[rel++]; + sbrGrid->envTimeBorder[env] = border * SAMPLES_PER_SLOT; + } + + sbrGrid->envTimeBorder[sbrGrid->numEnv] = absBordTrail * SAMPLES_PER_SLOT; + + if(sbrGrid->numEnv > 1) { + sbrGrid->numNoiseFloors = 2; + sbrGrid->noiseTimeBorder[0] = sbrGrid->envTimeBorder[0]; + sbrGrid->noiseTimeBorder[1] = sbrGrid->envTimeBorder[middleBorder]; + sbrGrid->noiseTimeBorder[2] = sbrGrid->envTimeBorder[sbrGrid->numEnv]; + } + else { + sbrGrid->numNoiseFloors = 1; + sbrGrid->noiseTimeBorder[0] = sbrGrid->envTimeBorder[0]; + sbrGrid->noiseTimeBorder[1] = sbrGrid->envTimeBorder[1]; + } +} +/*********************************************************************************************************************** + * Function: UnpackDeltaTimeFreq + * + * Description: unpack time/freq flags for delta coding of SBR envelopes (table 4.63) + * + * Inputs: BitStreamInfo struct pointing to start of dt/df flags + * number of envelopes + * number of noise floors + * + * Outputs: delta flags for envelope and noise floors + * + * Return: none + **********************************************************************************************************************/ +void UnpackDeltaTimeFreq(int numEnv, uint8_t *deltaFlagEnv, int numNoiseFloors, uint8_t *deltaFlagNoise) { + + int env, noiseFloor; + + for (env = 0; env < numEnv; env++) + deltaFlagEnv[env] = GetBits(1); + + for (noiseFloor = 0; noiseFloor < numNoiseFloors; noiseFloor++) + deltaFlagNoise[noiseFloor] = GetBits(1); +} +/*********************************************************************************************************************** + * Function: UnpackInverseFilterMode + * + * Description: unpack invf flags for chirp factor calculation (table 4.64) + * + * Inputs: BitStreamInfo struct pointing to start of invf flags + * number of noise floor bands + * + * Outputs: invf flags for noise floor bands + * + * Return: none + **********************************************************************************************************************/ +void UnpackInverseFilterMode(int numNoiseFloorBands, uint8_t *mode) { + + int n; + + for (n = 0; n < numNoiseFloorBands; n++) + mode[n] = GetBits(2); +} +/*********************************************************************************************************************** + * Function: UnpackSinusoids + * + * Description: unpack sinusoid (harmonic) flags for each SBR subband (table 4.67) + * + * Inputs: BitStreamInfo struct pointing to start of sinusoid flags + * number of high resolution SBR subbands (nHigh) + * + * Outputs: sinusoid flags for each SBR subband, zero-filled above nHigh + * + * Return: none + **********************************************************************************************************************/ +void UnpackSinusoids(int nHigh, int addHarmonicFlag, uint8_t *addHarmonic) { + + int n; + + n = 0; + if(addHarmonicFlag) { + for(; n < nHigh; n++) + addHarmonic[n] = GetBits(1); + } + + /* zero out unused bands */ + for(; n < MAX_QMF_BANDS; n++) + addHarmonic[n] = 0; +} +/*********************************************************************************************************************** + * Function: CopyCouplingGrid + * + * Description: copy grid parameters from left to right for channel coupling + * + * Inputs: initialized SBRGrid struct for left channel + * + * Outputs: initialized SBRGrid struct for right channel + * + * Return: none + **********************************************************************************************************************/ +void CopyCouplingGrid(SBRGrid *sbrGridLeft, SBRGrid *sbrGridRight) { + + int env, noiseFloor; + + sbrGridRight->frameClass = sbrGridLeft->frameClass; + sbrGridRight->ampResFrame = sbrGridLeft->ampResFrame; + sbrGridRight->pointer = sbrGridLeft->pointer; + + sbrGridRight->numEnv = sbrGridLeft->numEnv; + for(env = 0; env < sbrGridLeft->numEnv; env++) { + sbrGridRight->envTimeBorder[env] = sbrGridLeft->envTimeBorder[env]; + sbrGridRight->freqRes[env] = sbrGridLeft->freqRes[env]; + } + sbrGridRight->envTimeBorder[env] = sbrGridLeft->envTimeBorder[env]; /* borders are [0, numEnv] inclusive */ + + sbrGridRight->numNoiseFloors = sbrGridLeft->numNoiseFloors; + for(noiseFloor = 0; noiseFloor <= sbrGridLeft->numNoiseFloors; noiseFloor++) + sbrGridRight->noiseTimeBorder[noiseFloor] = sbrGridLeft->noiseTimeBorder[noiseFloor]; + + /* numEnvPrev, numNoiseFloorsPrev, freqResPrev are updated in DecodeSBREnvelope() and DecodeSBRNoise() */ +} +/*********************************************************************************************************************** + * Function: CopyCouplingInverseFilterMode + * + * Description: copy invf flags from left to right for channel coupling + * + * Inputs: invf flags for left channel + * number of noise floor bands + * + * Outputs: invf flags for right channel + * + * Return: none + **********************************************************************************************************************/ +void CopyCouplingInverseFilterMode(int numNoiseFloorBands, uint8_t *modeLeft, uint8_t *modeRight) { + + int band; + + for(band = 0; band < numNoiseFloorBands; band++) + modeRight[band] = modeLeft[band]; +} +/*********************************************************************************************************************** + * Function: UnpackSBRSingleChannel + * + * Description: unpack sideband info (grid, delta flags, invf flags, envelope and + * noise floor configuration, sinusoids) for a single channel + * + * Inputs: BitStreamInfo struct pointing to start of sideband info + * initialized PSInfoSBR struct (after parsing SBR header and building + * frequency tables) + * base output channel (range = [0, nChans-1]) + * + * Outputs: updated PSInfoSBR struct (SBRGrid and SBRChan) + * + * Return: none + **********************************************************************************************************************/ +void UnpackSBRSingleChannel(int chBase) { + + int bitsLeft; + SBRHeader *sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]); + SBRGrid *sbrGridL = &(m_PSInfoSBR->sbrGrid[chBase + 0]); + SBRFreq *sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]); + SBRChan *sbrChanL = &(m_PSInfoSBR->sbrChan[chBase + 0]); + + m_PSInfoSBR->dataExtra = GetBits(1); + if(m_PSInfoSBR->dataExtra) m_PSInfoSBR->resBitsData = GetBits(4); + + UnpackSBRGrid(sbrHdr, sbrGridL); + UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, sbrChanL->deltaFlagNoise); + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]); + + DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0); + + sbrChanL->addHarmonicFlag[1] = GetBits(1); + UnpackSinusoids(sbrFreq->nHigh, sbrChanL->addHarmonicFlag[1], sbrChanL->addHarmonic[1]); + + m_PSInfoSBR->extendedDataPresent = GetBits(1); + if(m_PSInfoSBR->extendedDataPresent) { + m_PSInfoSBR->extendedDataSize = GetBits(4); + if(m_PSInfoSBR->extendedDataSize == 15) m_PSInfoSBR->extendedDataSize += GetBits(8); + + bitsLeft = 8 * m_PSInfoSBR->extendedDataSize; + + /* get ID, unpack extension info, do whatever is necessary with it... */ + while(bitsLeft > 0) { + GetBits(8); + bitsLeft -= 8; + } + } +} +/*********************************************************************************************************************** + * Function: UnpackSBRChannelPair + * + * Description: unpack sideband info (grid, delta flags, invf flags, envelope and + * noise floor configuration, sinusoids) for a channel pair + * + * Inputs: base output channel (range = [0, nChans-1]) + * + * Outputs: updated PSInfoSBR struct (SBRGrid and SBRChan for both channels) + * + * Return: none + **********************************************************************************************************************/ +void UnpackSBRChannelPair(int chBase) { + + int bitsLeft; + SBRHeader *sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]); + SBRGrid *sbrGridL = &(m_PSInfoSBR->sbrGrid[chBase + 0]), *sbrGridR = &(m_PSInfoSBR->sbrGrid[chBase + 1]); + SBRFreq *sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]); + SBRChan *sbrChanL = &(m_PSInfoSBR->sbrChan[chBase + 0]), *sbrChanR = &(m_PSInfoSBR->sbrChan[chBase + 1]); + + m_PSInfoSBR->dataExtra = GetBits(1); + if(m_PSInfoSBR->dataExtra) { + m_PSInfoSBR->resBitsData = GetBits(4); + m_PSInfoSBR->resBitsData = GetBits(4); + } + + m_PSInfoSBR->couplingFlag = GetBits(1); + if(m_PSInfoSBR->couplingFlag) { + UnpackSBRGrid(sbrHdr, sbrGridL); + CopyCouplingGrid(sbrGridL, sbrGridR); + + UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, + sbrChanL->deltaFlagNoise); + UnpackDeltaTimeFreq(sbrGridR->numEnv, sbrChanR->deltaFlagEnv, sbrGridR->numNoiseFloors, + sbrChanR->deltaFlagNoise); + + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]); + CopyCouplingInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1], sbrChanR->invfMode[1]); + + DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBREnvelope(sbrGridR, sbrFreq, sbrChanR, 1); + DecodeSBRNoise(sbrGridR, sbrFreq, sbrChanR, 1); + + /* pass RIGHT sbrChan struct */ + UncoupleSBREnvelope(sbrGridL, sbrFreq, sbrChanR); + UncoupleSBRNoise(sbrGridL, sbrFreq, sbrChanR); + + } + else { + UnpackSBRGrid(sbrHdr, sbrGridL); + UnpackSBRGrid(sbrHdr, sbrGridR); + UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, + sbrChanL->deltaFlagNoise); + UnpackDeltaTimeFreq(sbrGridR->numEnv, sbrChanR->deltaFlagEnv, sbrGridR->numNoiseFloors, + sbrChanR->deltaFlagNoise); + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]); + UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanR->invfMode[1]); + + DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBREnvelope(sbrGridR, sbrFreq, sbrChanR, 1); + DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0); + DecodeSBRNoise(sbrGridR, sbrFreq, sbrChanR, 1); + } + + sbrChanL->addHarmonicFlag[1] = GetBits(1); + UnpackSinusoids(sbrFreq->nHigh, sbrChanL->addHarmonicFlag[1], sbrChanL->addHarmonic[1]); + + sbrChanR->addHarmonicFlag[1] = GetBits(1); + UnpackSinusoids(sbrFreq->nHigh, sbrChanR->addHarmonicFlag[1], sbrChanR->addHarmonic[1]); + + m_PSInfoSBR->extendedDataPresent = GetBits(1); + if(m_PSInfoSBR->extendedDataPresent) { + m_PSInfoSBR->extendedDataSize = GetBits(4); + if(m_PSInfoSBR->extendedDataSize == 15) m_PSInfoSBR->extendedDataSize += GetBits(8); + + bitsLeft = 8 * m_PSInfoSBR->extendedDataSize; + + /* get ID, unpack extension info, do whatever is necessary with it... */ + while(bitsLeft > 0) { + GetBits(8); + bitsLeft -= 8; + } + } +} diff --git a/yoRadio/src/audioI2S/aac_decoder/aac_decoder.h b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.h new file mode 100644 index 0000000..74b570e --- /dev/null +++ b/yoRadio/src/audioI2S/aac_decoder/aac_decoder.h @@ -0,0 +1,583 @@ +// based on helix aac decoder +#pragma once +//#pragma GCC optimize ("O3") +//#pragma GCC diagnostic ignored "-Wnarrowing" + +#include "Arduino.h" + +#define AAC_ENABLE_MPEG4 +//#define AAC_ENABLE_SBR // needs additional 60KB Heap, + +#define ASSERT(x) /* do nothing */ + +#ifndef MAX +#define MAX(a,b) std::max(a,b) +#endif + +#ifndef MIN +#define MIN(a,b) std::min(a,b) +#endif + + +/* AAC file format */ +enum { + AAC_FF_Unknown = 0, /* should be 0 on init */ + AAC_FF_ADTS = 1, + AAC_FF_ADIF = 2, + AAC_FF_RAW = 3 +}; + +/* syntactic element type */ +enum { + AAC_ID_INVALID = -1, + AAC_ID_SCE = 0, + AAC_ID_CPE = 1, + AAC_ID_CCE = 2, + AAC_ID_LFE = 3, + AAC_ID_DSE = 4, + AAC_ID_PCE = 5, + AAC_ID_FIL = 6, + AAC_ID_END = 7 +}; + +enum { + ERR_AAC_NONE = 0, + ERR_AAC_INDATA_UNDERFLOW = -1, + ERR_AAC_NULL_POINTER = -2, + ERR_AAC_INVALID_ADTS_HEADER = -3, + ERR_AAC_INVALID_ADIF_HEADER = -4, + ERR_AAC_INVALID_FRAME = -5, + ERR_AAC_MPEG4_UNSUPPORTED = -6, + ERR_AAC_CHANNEL_MAP = -7, + ERR_AAC_SYNTAX_ELEMENT = -8, + ERR_AAC_DEQUANT = -9, + ERR_AAC_STEREO_PROCESS = -10, + ERR_AAC_PNS = -11, + ERR_AAC_SHORT_BLOCK_DEINT = -12, + ERR_AAC_TNS = -13, + ERR_AAC_IMDCT = -14, + ERR_AAC_NCHANS_TOO_HIGH = -15, + ERR_AAC_SBR_INIT = -16, + ERR_AAC_SBR_BITSTREAM = -17, + ERR_AAC_SBR_DATA = -18, + ERR_AAC_SBR_PCM_FORMAT = -19, + ERR_AAC_SBR_NCHANS_TOO_HIGH = -20, + ERR_AAC_SBR_SINGLERATE_UNSUPPORTED = -21, + ERR_AAC_RAWBLOCK_PARAMS = -22, + ERR_AAC_UNKNOWN = -9999 +}; + +enum { + SBR_GRID_FIXFIX = 0, + SBR_GRID_FIXVAR = 1, + SBR_GRID_VARFIX = 2, + SBR_GRID_VARVAR = 3 +}; + +enum { + HuffTabSBR_tEnv15 = 0, + HuffTabSBR_fEnv15 = 1, + HuffTabSBR_tEnv15b = 2, + HuffTabSBR_fEnv15b = 3, + HuffTabSBR_tEnv30 = 4, + HuffTabSBR_fEnv30 = 5, + HuffTabSBR_tEnv30b = 6, + HuffTabSBR_fEnv30b = 7, + HuffTabSBR_tNoise30 = 8, + HuffTabSBR_fNoise30 = 5, + HuffTabSBR_tNoise30b = 9, + HuffTabSBR_fNoise30b = 7 +}; + +typedef struct _AACDecInfo_t { + /* raw decoded data, before rounding to 16-bit PCM (for postprocessing such as SBR) */ + void *rawSampleBuf[2]; + int rawSampleBytes; + int rawSampleFBits; + /* fill data (can be used for processing SBR or other extensions) */ + uint8_t *fillBuf; + int fillCount; + int fillExtType; + int prevBlockID; /* block information */ + int currBlockID; + int currInstTag; + int sbDeinterleaveReqd[2]; // [MAX_NCHANS_ELEM] + int adtsBlocksLeft; + int bitRate; /* user-accessible info */ + int nChans; + int sampRate; + float compressionRatio; + int id; /* 0: MPEG-4, 1: MPEG2 */ + int profile; /* 0: Main profile, 1: LowComplexity (LC), 2: ScalableSamplingRate (SSR), 3: reserved */ + int format; + int sbrEnabled; + int tnsUsed; + int pnsUsed; + int frameCount; +} AACDecInfo_t; + + +typedef struct _aac_BitStreamInfo_t { + uint8_t *bytePtr; + unsigned int iCache; + int cachedBits; + int nBytes; +} aac_BitStreamInfo_t; + +typedef union _U64 { + int64_t w64; + struct { + unsigned int lo32; + signed int hi32; + } r; +} U64; + +typedef struct _AACFrameInfo_t { + int bitRate; + int nChans; + int sampRateCore; + int sampRateOut; + int bitsPerSample; + int outputSamps; + int profile; + int tnsUsed; + int pnsUsed; +} AACFrameInfo_t; + +typedef struct _HuffInfo_t { + int maxBits; /* number of bits in longest codeword */ + uint8_t count[20]; /* count[MAX_HUFF_BITS] = number of codes with length i+1 bits */ + int offset; /* offset into symbol table */ +} HuffInfo_t; + +typedef struct _PulseInfo_t { + uint8_t pulseDataPresent; + uint8_t numPulse; + uint8_t startSFB; + uint8_t offset[4]; // [MAX_PULSES] + uint8_t amp[4]; // [MAX_PULSES] +} PulseInfo_t; + +typedef struct _TNSInfo_t { + uint8_t tnsDataPresent; + uint8_t numFilt[8]; // [MAX_TNS_FILTERS] max 1 filter each for 8 short windows, or 3 filters for 1 long window + uint8_t coefRes[8]; // [MAX_TNS_FILTERS] + uint8_t length[8]; // [MAX_TNS_FILTERS] + uint8_t order[8]; // [MAX_TNS_FILTERS] + uint8_t dir[8]; // [MAX_TNS_FILTERS] + int8_t coef[60]; // [MAX_TNS_COEFS] max 3 filters * 20 coefs for 1 long window, + // or 1 filter * 7 coefs for each of 8 short windows +} TNSInfo_t; + +typedef struct _GainControlInfo_t { + uint8_t gainControlDataPresent; + uint8_t maxBand; + uint8_t adjNum[3][8]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN] + uint8_t alevCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST] + uint8_t alocCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST] +} GainControlInfo_t; + +typedef struct _ICSInfo_t { + uint8_t icsResBit; + uint8_t winSequence; + uint8_t winShape; + uint8_t maxSFB; + uint8_t sfGroup; + uint8_t predictorDataPresent; + uint8_t predictorReset; + uint8_t predictorResetGroupNum; + uint8_t predictionUsed[41]; // [MAX_PRED_SFB] + uint8_t numWinGroup; + uint8_t winGroupLen[8]; // [MAX_WIN_GROUPS] +} ICSInfo_t; + +typedef struct _ADTSHeader_t { + /* fixed */ + uint8_t id; /* MPEG bit - should be 1 */ + uint8_t layer; /* MPEG layer - should be 0 */ + uint8_t protectBit; /* 0 = CRC word follows, 1 = no CRC word */ + uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */ + uint8_t sampRateIdx; /* sample rate index range = [0, 11] */ + uint8_t privateBit; /* ignore */ + uint8_t channelConfig; /* 0 = implicit, >0 = use default table */ + uint8_t origCopy; /* 0 = copy, 1 = original */ + uint8_t home; /* ignore */ + /* variable */ + uint8_t copyBit; /* 1 bit of the 72-bit copyright ID (transmitted as 1 bit per frame) */ + uint8_t copyStart; /* 1 = this bit starts the 72-bit ID, 0 = it does not */ + int frameLength; /* length of frame */ + int bufferFull; /* number of 32-bit words left in enc buffer, 0x7FF = VBR */ + uint8_t numRawDataBlocks; /* number of raw data blocks in frame */ + /* CRC */ + int crcCheckWord; /* 16-bit CRC check word (present if protectBit == 0) */ +} ADTSHeader_t; + +typedef struct _ADIFHeader_t { + uint8_t copyBit; /* 0 = no copyright ID, 1 = 72-bit copyright ID follows immediately */ + uint8_t origCopy; /* 0 = copy, 1 = original */ + uint8_t home; /* ignore */ + uint8_t bsType; /* bitstream type: 0 = CBR, 1 = VBR */ + int bitRate; /* bitRate: CBR = bits/sec, VBR = peak bits/frame, 0 = unknown */ + uint8_t numPCE; /* number of program config elements (max = 16) */ + int bufferFull; /* bits left in bit reservoir */ + uint8_t copyID[9]; /* [ADIF_COPYID_SIZE] optional 72-bit copyright ID */ +} ADIFHeader_t; + +/* sizeof(ProgConfigElement_t) = 82 bytes (if KEEP_PCE_COMMENTS not defined) */ +typedef struct _ProgConfigElement_t { + uint8_t elemInstTag; /* element instance tag */ + uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */ + uint8_t sampRateIdx; /* sample rate index range = [0, 11] */ + uint8_t numFCE; /* number of front channel elements (max = 15) */ + uint8_t numSCE; /* number of side channel elements (max = 15) */ + uint8_t numBCE; /* number of back channel elements (max = 15) */ + uint8_t numLCE; /* number of LFE channel elements (max = 3) */ + uint8_t numADE; /* number of associated data elements (max = 7) */ + uint8_t numCCE; /* number of valid channel coupling elements (max = 15) */ + uint8_t monoMixdown; /* mono mixdown: bit 4 = present flag, bits 3-0 = element number */ + uint8_t stereoMixdown; /* stereo mixdown: bit 4 = present flag, bits 3-0 = element number */ + uint8_t matrixMixdown; /* bit 4 = present flag, bit 3 = unused,bits 2-1 = index, bit 0 = pseudo-surround enable */ + uint8_t fce[15]; /* [MAX_NUM_FCE] front element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ + uint8_t sce[15]; /* [MAX_NUM_SCE] side element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ + uint8_t bce[15]; /* [MAX_NUM_BCE] back element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */ + uint8_t lce[3]; /* [MAX_NUM_LCE] instance tag for LFE elements */ + uint8_t ade[7]; /* [MAX_NUM_ADE] instance tag for ADE elements */ + uint8_t cce[15]; /* [MAX_NUM_BCE] channel coupling elements: bit 4 = switching flag, bits 3-0 = inst tag */ +} ProgConfigElement_t; + +typedef struct _SBRHeader { + int count; + + uint8_t ampRes; + uint8_t startFreq; + uint8_t stopFreq; + uint8_t crossOverBand; + uint8_t resBitsHdr; + uint8_t hdrExtra1; + uint8_t hdrExtra2; + + uint8_t freqScale; + uint8_t alterScale; + uint8_t noiseBands; + + uint8_t limiterBands; + uint8_t limiterGains; + uint8_t interpFreq; + uint8_t smoothMode; +} SBRHeader; + +/* need one SBRGrid per channel, updated every frame */ +typedef struct _SBRGrid { + uint8_t frameClass; + uint8_t ampResFrame; + uint8_t pointer; + + uint8_t numEnv; /* L_E */ + uint8_t envTimeBorder[5 + 1]; // [MAX_NUM_ENV+1] /* t_E */ + uint8_t freqRes[5]; // [MAX_NUM_ENV]/* r */ + uint8_t numNoiseFloors; /* L_Q */ + uint8_t noiseTimeBorder[2 + 1]; // [MAX_NUM_NOISE_FLOORS+1] /* t_Q */ + + uint8_t numEnvPrev; + uint8_t numNoiseFloorsPrev; + uint8_t freqResPrev; +} SBRGrid; + +/* need one SBRFreq per element (SCE/CPE/LFE), updated only on header reset */ +typedef struct _SBRFreq { + int kStart; /* k_x */ + int nMaster; + int nHigh; + int nLow; + int nLimiter; /* N_l */ + int numQMFBands; /* M */ + int numNoiseFloorBands; /* Nq */ + int kStartPrev; + int numQMFBandsPrev; + uint8_t freqMaster[48 + 1]; // [MAX_QMF_BANDS + 1] /* not necessary to save this after derived tables are generated */ + uint8_t freqHigh[48 + 1]; // [MAX_QMF_BANDS + 1] + uint8_t freqLow[48 / 2 + 1]; // [MAX_QMF_BANDS / 2 + 1] /* nLow = nHigh - (nHigh >> 1) */ + uint8_t freqNoise[5 + 1]; // [MAX_NUM_NOISE_FLOOR_BANDS+1] + uint8_t freqLimiter[48 / 2 + 5];// [MAX_QMF_BANDS / 2 + MAX_NUM_PATCHES] /* max (intermediate) size = nLow + numPatches - 1 */ + + uint8_t numPatches; + uint8_t patchNumSubbands[5 + 1]; // [MAX_NUM_PATCHES + 1] + uint8_t patchStartSubband[5 + 1]; // [MAX_NUM_PATCHES + 1] +} SBRFreq; + +typedef struct _SBRChan { + int reset; + uint8_t deltaFlagEnv[5]; // [MAX_NUM_ENV] + uint8_t deltaFlagNoise[2]; // [MAX_NUM_NOISE_FLOORS] + int8_t envDataQuant[5][48]; // [MAX_NUM_ENV][MAX_QMF_BANDS] /* range = [0, 127] */ + int8_t noiseDataQuant[2][5]; // [MAX_NUM_NOISE_FLOORS][MAX_NUM_NOISE_FLOOR_BANDS] + + uint8_t invfMode[2][5]; // [2][MAX_NUM_NOISE_FLOOR_BANDS] /* invfMode[0/1][band] = prev/curr */ + int chirpFact[5]; // [MAX_NUM_NOISE_FLOOR_BANDS] /* bwArray */ + uint8_t addHarmonicFlag[2]; /* addHarmonicFlag[0/1] = prev/curr */ + uint8_t addHarmonic[2][64]; /* addHarmonic[0/1][band] = prev/curr */ + + int gbMask[2]; /* gbMask[0/1] = XBuf[0-31]/XBuf[32-39] */ + int8_t laPrev; + + int noiseTabIndex; + int sinIndex; + int gainNoiseIndex; + int gTemp[5][48]; // [MAX_NUM_SMOOTH_COEFS][MAX_QMF_BANDS] + int qTemp[5][48]; // [MAX_NUM_SMOOTH_COEFS][MAX_QMF_BANDS] + +} SBRChan; + + +/* state info struct for baseline (MPEG-4 LC) decoding */ +typedef struct _PSInfoBase_t { + int dataCount; + uint8_t dataBuf[510]; // [DATA_BUF_SIZE] + int fillCount; + uint8_t fillBuf[269]; //[FILL_BUF_SIZE] + /* state information which is the same throughout whole frame */ + int nChans; + int useImpChanMap; + int sampRateIdx; + /* state information which can be overwritten by subsequent elements within frame */ + ICSInfo_t icsInfo[2]; // [MAX_NCHANS_ELEM] + int commonWin; + short scaleFactors[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS] + uint8_t sfbCodeBook[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS] + int msMaskPresent; + uint8_t msMaskBits[(15 * 8 + 7) >> 3]; // [MAX_MS_MASK_BYTES] + int pnsUsed[2]; // [MAX_NCHANS_ELEM] + int pnsLastVal; + int intensityUsed[2]; // [MAX_NCHANS_ELEM] +// PulseInfo_t pulseInfo[2]; // [MAX_NCHANS_ELEM] + TNSInfo_t tnsInfo[2]; // [MAX_NCHANS_ELEM] + int tnsLPCBuf[20]; // [MAX_TNS_ORDER] + int tnsWorkBuf[20]; //[MAX_TNS_ORDER] + GainControlInfo_t gainControlInfo[2]; // [MAX_NCHANS_ELEM] + int gbCurrent[2]; // [MAX_NCHANS_ELEM] + int coef[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS] +#ifdef AAC_ENABLE_SBR + int sbrWorkBuf[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS]; +#endif + /* state information which must be saved for each element and used in next frame */ + int overlap[2][1024]; // [AAC_MAX_NCHANS][AAC_MAX_NSAMPS] + int prevWinShape[2]; // [AAC_MAX_NCHANS] +} PSInfoBase_t; + +typedef struct _PSInfoSBR { + /* save for entire file */ + int frameCount; + int sampRateIdx; + + /* state info that must be saved for each channel */ + SBRHeader sbrHdr[2]; + SBRGrid sbrGrid[2]; + SBRFreq sbrFreq[2]; + SBRChan sbrChan[2]; + + /* temp variables, no need to save between blocks */ + uint8_t dataExtra; + uint8_t resBitsData; + uint8_t extendedDataPresent; + int extendedDataSize; + + int8_t envDataDequantScale[2][5]; // [MAX_NCHANS_ELEM][MAX_NUM_ENV + int envDataDequant[2][5][48]; // [MAX_NCHANS_ELEM][MAX_NUM_ENV][MAX_QMF_BANDS + int noiseDataDequant[2][2][5]; // [MAX_NCHANS_ELEM][MAX_NUM_NOISE_FLOORS][MAX_NUM_NOISE_FLOOR_BANDS] + + int eCurr[48]; // [MAX_QMF_BANDS] + uint8_t eCurrExp[48]; // [MAX_QMF_BANDS] + uint8_t eCurrExpMax; + int8_t la; + + int crcCheckWord; + int couplingFlag; + int envBand; + int eOMGainMax; + int gainMax; + int gainMaxFBits; + int noiseFloorBand; + int qp1Inv; + int qqp1Inv; + int sMapped; + int sBand; + int highBand; + + int sumEOrigMapped; + int sumECurrGLim; + int sumSM; + int sumQM; + int gLimBoost[48]; + int qmLimBoost[48]; + int smBoost[48]; + + int smBuf[48]; + int qmLimBuf[48]; + int gLimBuf[48]; + int gLimFbits[48]; + + int gFiltLast[48]; + int qFiltLast[48]; + + /* large buffers */ + int delayIdxQMFA[2]; // [AAC_MAX_NCHANS] + int delayQMFA[2][10 * 32]; // [AAC_MAX_NCHANS][DELAY_SAMPS_QMFA] + int delayIdxQMFS[2]; // [AAC_MAX_NCHANS] + int delayQMFS[2][10 * 128]; // [AAC_MAX_NCHANS][DELAY_SAMPS_QMFS] + int XBufDelay[2][8][64][2]; // [AAC_MAX_NCHANS][HF_GEN][64][2] + int XBuf[32+8][64][2]; +} PSInfoSBR_t; + +bool AACDecoder_AllocateBuffers(void); +int AACFlushCodec(); +void AACDecoder_FreeBuffers(void); +bool AACDecoder_IsInit(void); +int AACFindSyncWord(uint8_t *buf, int nBytes); +int AACSetRawBlockParams(int copyLast, int nChans, int sampRateCore, int profile); +int AACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf); +int AACGetSampRate(); +int AACGetChannels(); +int AACGetID(); // 0-MPEG4, 1-MPEG2 +uint8_t AACGetProfile(); // 0-Main, 1-LC, 2-SSR, 3-reserved +uint8_t AACGetFormat(); // 0-unknown 1-ADTS 2-ADIF, 3-RAW +int AACGetBitsPerSample(); +int AACGetBitrate(); +int AACGetOutputSamps(); +int AACGetBitrate(); +void DecodeLPCCoefs(int order, int res, int8_t *filtCoef, int *a, int *b); +int FilterRegion(int size, int dir, int order, int *audioCoef, int *a, int *hist); +int TNSFilter(int ch); +int DecodeSingleChannelElement(); +int DecodeChannelPairElement(); +int DecodeLFEChannelElement(); +int DecodeDataStreamElement(); +int DecodeProgramConfigElement(uint8_t idx); +int DecodeFillElement(); +int DecodeNextElement(uint8_t **buf, int *bitOffset, int *bitsAvail); +void PreMultiply(int tabidx, int *zbuf1); +void PostMultiply(int tabidx, int *fft1); +void PreMultiplyRescale(int tabidx, int *zbuf1, int es); +void PostMultiplyRescale(int tabidx, int *fft1, int es); +void DCT4(int tabidx, int *coef, int gb); +void BitReverse(int *inout, int tabidx); +void R4FirstPass(int *x, int bg); +void R8FirstPass(int *x, int bg); +void R4Core(int *x, int bg, int gp, int *wtab); +void R4FFT(int tabidx, int *x); +void UnpackZeros(int nVals, int *coef); +void UnpackQuads(int cb, int nVals, int *coef); +void UnpackPairsNoEsc(int cb, int nVals, int *coef); +void UnpackPairsEsc(int cb, int nVals, int *coef); +void DecodeSpectrumLong(int ch); +void DecodeSpectrumShort(int ch); +void DecWindowOverlap(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStart(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStop(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +void DecWindowOverlapShort(int *buf0, int *over0, short *pcm0, int nChans, int winTypeCurr, int winTypePrev); +int IMDCT(int ch, int chOut, short *outbuf); +void DecodeICSInfo(ICSInfo_t *icsInfo, int sampRateIdx); +void DecodeSectionData(int winSequence, int numWinGrp, int maxSFB, uint8_t *sfbCodeBook); +int DecodeOneScaleFactor(); +void DecodeScaleFactors(int numWinGrp, int maxSFB, int globalGain, uint8_t *sfbCodeBook, short *scaleFactors); +void DecodePulseInfo(uint8_t ch); +void DecodeTNSInfo(int winSequence, TNSInfo_t *ti, int8_t *tnsCoef); +void DecodeGainControlInfo(int winSequence, GainControlInfo_t *gi); +void DecodeICS(int ch); +int DecodeNoiselessData(uint8_t **buf, int *bitOffset, int *bitsAvail, int ch); +int DecodeHuffmanScalar(const signed short *huffTab, const HuffInfo_t *huffTabInfo, unsigned int bitBuf, int32_t *val); +int UnpackADTSHeader(uint8_t **buf, int *bitOffset, int *bitsAvail); +int GetADTSChannelMapping(uint8_t *buf, int bitOffset, int bitsAvail); +int GetNumChannelsADIF(int nPCE); +int GetSampleRateIdxADIF(int nPCE); +int UnpackADIFHeader(uint8_t **buf, int *bitOffset, int *bitsAvail); +int SetRawBlockParams(int copyLast, int nChans, int sampRate, int profile); +int PrepareRawBlock(); +int DequantBlock(int *inbuf, int nSamps, int scale); +int AACDequantize(int ch); +int DeinterleaveShortBlocks(int ch); +unsigned int Get32BitVal(unsigned int *last); +int InvRootR(int r); +int ScaleNoiseVector(int *coef, int nVals, int sf); +void GenerateNoiseVector(int *coef, int *last, int nVals); +void CopyNoiseVector(int *coefL, int *coefR, int nVals); +int PNS(int ch); +int GetSampRateIdx(int sampRate); +void StereoProcessGroup(int *coefL, int *coefR, const uint16_t *sfbTab, int msMaskPres, uint8_t *msMaskPtr, + int msMaskOffset, int maxSFB, uint8_t *cbRight, short *sfRight, int *gbCurrent); +int StereoProcess(); +int RatioPowInv(int a, int b, int c); +int SqrtFix(int q, int fBitsIn, int *fBitsOut); +int InvRNormalized(int r); +void BitReverse32(int *inout); +void R8FirstPass32(int *r0); +void R4Core32(int *r0); +void FFT32C(int *x); +void CVKernel1(int *XBuf, int *accBuf); +void CVKernel2(int *XBuf, int *accBuf); +void SetBitstreamPointer(int nBytes, uint8_t *buf); +inline void RefillBitstreamCache(); +unsigned int GetBits(int nBits); +unsigned int GetBitsNoAdvance(int nBits); +void AdvanceBitstream(int nBits); +int CalcBitsUsed(uint8_t *startBuf, int startOffset); +void ByteAlignBitstream(); +// SBR +void InitSBRState(); +int DecodeSBRBitstream(int chBase); +int DecodeSBRData(int chBase, short *outbuf); +int FlushCodecSBR(); +void BubbleSort(uint8_t *v, int nItems); +uint8_t VMin(uint8_t *v, int nItems); +uint8_t VMax(uint8_t *v, int nItems); +int CalcFreqMasterScaleZero(uint8_t *freqMaster, int alterScale, int k0, int k2); +int CalcFreqMaster(uint8_t *freqMaster, int freqScale, int alterScale, int k0, int k2); +int CalcFreqHigh(uint8_t *freqHigh, uint8_t *freqMaster, int nMaster, int crossOverBand); +int CalcFreqLow(uint8_t *freqLow, uint8_t *freqHigh, int nHigh); +int CalcFreqNoise(uint8_t *freqNoise, uint8_t *freqLow, int nLow, int kStart, int k2, int noiseBands); +int BuildPatches(uint8_t *patchNumSubbands, uint8_t *patchStartSubband, uint8_t *freqMaster, int nMaster, int k0, + int kStart, int numQMFBands, int sampRateIdx); +int FindFreq(uint8_t *freq, int nFreq, uint8_t val); +void RemoveFreq(uint8_t *freq, int nFreq, int removeIdx); +int CalcFreqLimiter(uint8_t *freqLimiter, uint8_t *patchNumSubbands, uint8_t *freqLow, int nLow, int kStart, + int limiterBands, int numPatches); +int CalcFreqTables(SBRHeader *sbrHdr, SBRFreq *sbrFreq, int sampRateIdx); +void EstimateEnvelope(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int env); +int GetSMapped(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int band, int la); +void CalcMaxGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int ch, int env, int lim, int fbitsDQ); +void CalcNoiseDivFactors(int q, int *qp1Inv, int *qqp1Inv); +void CalcComponentGains(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env, int lim, int fbitsDQ); +void ApplyBoost(SBRFreq *sbrFreq, int lim, int fbitsDQ); +void CalcGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch, int env); +void MapHF(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int env, int hfReset); +void AdjustHighFreq(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +int CalcCovariance1(int *XBuf, int *p01reN, int *p01imN, int *p12reN, int *p12imN, int *p11reN, int *p22reN); +int CalcCovariance2(int *XBuf, int *p02reN, int *p02imN); +void CalcLPCoefs(int *XBuf, int *a0re, int *a0im, int *a1re, int *a1im, int gb); +void GenerateHighFreq(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +int DecodeHuffmanScalar(const signed int *huffTab, const HuffInfo_t *huffTabInfo, unsigned int bitBuf, signed int *val); +int DecodeOneSymbol(int huffTabIndex); +int DequantizeEnvelope(int nBands, int ampRes, int8_t *envQuant, int *envDequant); +void DequantizeNoise(int nBands, int8_t *noiseQuant, int *noiseDequant); +void DecodeSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +void DecodeSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int ch); +void UncoupleSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR); +void UncoupleSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR); +void DecWindowOverlapNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStartNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void DecWindowOverlapLongStopNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void DecWindowOverlapShortNoClip(int *buf0, int *over0, int *out0, int winTypeCurr, int winTypePrev); +void PreMultiply64(int *zbuf1); +void PostMultiply64(int *fft1, int nSampsOut); +void QMFAnalysisConv(int *cTab, int *delay, int dIdx, int *uBuf); +int QMFAnalysis(int *inbuf, int *delay, int *XBuf, int fBitsIn, int *delayIdx, int qmfaBands); +void QMFSynthesisConv(int *cPtr, int *delay, int dIdx, short *outbuf, int nChans); +void QMFSynthesis(int *inbuf, int *delay, int *delayIdx, int qmfsBands, short *outbuf, int nChans); +int UnpackSBRHeader(SBRHeader *sbrHdr); +void UnpackSBRGrid(SBRHeader *sbrHdr, SBRGrid *sbrGrid); +void UnpackDeltaTimeFreq(int numEnv, uint8_t *deltaFlagEnv, int numNoiseFloors, uint8_t *deltaFlagNoise); +void UnpackInverseFilterMode(int numNoiseFloorBands, uint8_t *mode); +void UnpackSinusoids(int nHigh, int addHarmonicFlag, uint8_t *addHarmonic); +void CopyCouplingGrid(SBRGrid *sbrGridLeft, SBRGrid *sbrGridRight); +void CopyCouplingInverseFilterMode(int numNoiseFloorBands, uint8_t *modeLeft, uint8_t *modeRight); +void UnpackSBRSingleChannel(int chBase); +void UnpackSBRChannelPair(int chBase); diff --git a/yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp new file mode 100644 index 0000000..4c2f9c0 --- /dev/null +++ b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.cpp @@ -0,0 +1,556 @@ +/* + * flac_decoder.cpp + * Java source code from https://www.nayuki.io/page/simple-flac-implementation + * adapted to ESP32 + * + * Created on: Jul 03,2020 + * Updated on: Jul 03,2021 + * + * Author: Wolle + * + * + */ +#include "flac_decoder.h" +#include "vector" +using namespace std; + + +FLACFrameHeader_t *FLACFrameHeader; +FLACMetadataBlock_t *FLACMetadataBlock; +FLACsubFramesBuff_t *FLACsubFramesBuff; + +vectorcoefs; +const uint16_t outBuffSize = 2048; +uint16_t m_blockSize=0; +uint16_t m_blockSizeLeft = 0; +uint16_t m_validSamples = 0; +uint8_t m_status = 0; +uint8_t* m_inptr; +int16_t m_bytesAvail; +int16_t m_bytesDecoded = 0; +float m_compressionRatio = 0; +uint16_t m_rIndex=0; +uint64_t m_bitBuffer = 0; +uint8_t m_bitBufferLen = 0; +bool m_f_OggS_found = false; + +//---------------------------------------------------------------------------------------------------------------------- +// FLAC INI SECTION +//---------------------------------------------------------------------------------------------------------------------- +bool FLACDecoder_AllocateBuffers(void){ + if(psramFound()) { + // PSRAM found, Buffer will be allocated in PSRAM + if(!FLACFrameHeader) {FLACFrameHeader = (FLACFrameHeader_t*) ps_malloc(sizeof(FLACFrameHeader_t));} + if(!FLACMetadataBlock) {FLACMetadataBlock = (FLACMetadataBlock_t*) ps_malloc(sizeof(FLACMetadataBlock_t));} + if(!FLACsubFramesBuff) {FLACsubFramesBuff = (FLACsubFramesBuff_t*) ps_malloc(sizeof(FLACsubFramesBuff_t));} + } + else { + if(!FLACFrameHeader) {FLACFrameHeader = (FLACFrameHeader_t*) malloc(sizeof(FLACFrameHeader_t));} + if(!FLACMetadataBlock) {FLACMetadataBlock = (FLACMetadataBlock_t*) malloc(sizeof(FLACMetadataBlock_t));} + if(!FLACsubFramesBuff) {FLACsubFramesBuff = (FLACsubFramesBuff_t*) malloc(sizeof(FLACsubFramesBuff_t));} + } + if(!FLACFrameHeader || !FLACMetadataBlock || !FLACsubFramesBuff ){ + log_e("not enough memory to allocate flacdecoder buffers"); + return false; + } + FLACDecoder_ClearBuffer(); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +void FLACDecoder_ClearBuffer(){ + memset(FLACFrameHeader, 0, sizeof(FLACFrameHeader_t)); + memset(FLACMetadataBlock, 0, sizeof(FLACMetadataBlock_t)); + memset(FLACsubFramesBuff, 0, sizeof(FLACsubFramesBuff_t)); + m_status = DECODE_FRAME; + return; +} +//---------------------------------------------------------------------------------------------------------------------- +void FLACDecoder_FreeBuffers(){ + if(FLACFrameHeader) {free(FLACFrameHeader); FLACFrameHeader = NULL;} + if(FLACMetadataBlock) {free(FLACMetadataBlock); FLACMetadataBlock = NULL;} + if(FLACsubFramesBuff) {free(FLACsubFramesBuff); FLACsubFramesBuff = NULL;} +} +//---------------------------------------------------------------------------------------------------------------------- +// B I T R E A D E R +//---------------------------------------------------------------------------------------------------------------------- +uint32_t readUint(uint8_t nBits){ + while (m_bitBufferLen < nBits){ + uint8_t temp = *(m_inptr + m_rIndex); + m_rIndex++; + m_bytesAvail--; + if(m_bytesAvail < 0) { log_i("error in bitreader"); } + m_bitBuffer = (m_bitBuffer << 8) | temp; + m_bitBufferLen += 8; + } + m_bitBufferLen -= nBits; + uint32_t result = m_bitBuffer >> m_bitBufferLen; + if (nBits < 32) + result &= (1 << nBits) - 1; + return result; +} + +int32_t readSignedInt(int nBits){ + int32_t temp = readUint(nBits) << (32 - nBits); + temp = temp >> (32 - nBits); // The C++ compiler uses the sign bit to fill vacated bit positions + return temp; +} + +int64_t readRiceSignedInt(uint8_t param){ + long val = 0; + while (readUint(1) == 0) + val++; + val = (val << param) | readUint(param); + return (val >> 1) ^ -(val & 1); +} + +void alignToByte() { + m_bitBufferLen -= m_bitBufferLen % 8; +} +//---------------------------------------------------------------------------------------------------------------------- +// F L A C - D E C O D E R +//---------------------------------------------------------------------------------------------------------------------- +void FLACSetRawBlockParams(uint8_t Chans, uint32_t SampRate, uint8_t BPS, uint32_t tsis, uint32_t AuDaLength){ + FLACMetadataBlock->numChannels = Chans; + FLACMetadataBlock->sampleRate = SampRate; + FLACMetadataBlock->bitsPerSample = BPS; + FLACMetadataBlock->totalSamples = tsis; // total samples in stream + FLACMetadataBlock->audioDataLength = AuDaLength; +} +//---------------------------------------------------------------------------------------------------------------------- +void FLACDecoderReset(){ // set var to default + m_status = DECODE_FRAME; + m_bitBuffer = 0; + m_bitBufferLen = 0; +} +//---------------------------------------------------------------------------------------------------------------------- +int FLACFindSyncWord(unsigned char *buf, int nBytes) { + int i; + + /* find byte-aligned syncword - need 13 matching bits */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] & 0xFF) == 0xFF && (buf[i + 1] & 0xF8) == 0xF8) { + FLACDecoderReset(); + return i; + } + } + return -1; +} +//---------------------------------------------------------------------------------------------------------------------- +int FLACFindOggSyncWord(unsigned char *buf, int nBytes){ + int i; + + /* find byte-aligned syncword - need 13 matching bits */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] & 0xFF) == 0xFF && (buf[i + 1] & 0xF8) == 0xF8) { + FLACDecoderReset(); + log_i("FLAC sync found"); + return i; + } + } + /* find byte-aligned OGG Magic - OggS */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] == 'O') && (buf[i + 1] == 'g') && (buf[i + 2] == 'g') && (buf[i + 3] == 'S')) { + FLACDecoderReset(); + log_i("OggS found"); + m_f_OggS_found = true; + return i; + } + } + return -1; +} +//---------------------------------------------------------------------------------------------------------------------- +int FLACparseOggHeader(unsigned char *buf){ + uint8_t i = 0; + uint8_t ssv = *(buf + i); // stream_structure_version + (void)ssv; + i++; + uint8_t htf = *(buf + i); // header_type_flag + (void)htf; + i++; + uint32_t tmp = 0; // absolute granule position + for (int j = 0; j < 4; j++) { + tmp += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint64_t agp = (uint64_t) tmp << 32; + for (int j = 0; j < 4; j++) { + agp += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint32_t ssnr = 0; // stream serial number + for (int j = 0; j < 4; j++) { + ssnr += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint32_t psnr = 0; // page sequence no + for (int j = 0; j < 4; j++) { + psnr += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint32_t pchk = 0; // page checksum + for (int j = 0; j < 4; j++) { + pchk += *(buf + j + i) << (4 -j - 1) * 8; + } + i += 4; + uint8_t psegm = *(buf + i); + i++; + uint8_t psegmBuff[256]; + uint32_t pageLen = 0; + for(uint8_t j = 0; j < psegm; j++){ + psegmBuff[j] = *(buf + i); + pageLen += psegmBuff[j]; + i++; + } + return i; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t FLACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf){ + + if(m_f_OggS_found == true){ + m_f_OggS_found = false; + *bytesLeft -= FLACparseOggHeader(inbuf); + return ERR_FLAC_NONE; + } + + if(m_status != OUT_SAMPLES){ + m_rIndex = 0; + m_bytesAvail = (*bytesLeft); + m_inptr = inbuf; + } + + if(m_status == DECODE_FRAME){ // Read a ton of header fields, and ignore most of them + + if ((inbuf[0] == 'O') && (inbuf[1] == 'g') && (inbuf[2] == 'g') && (inbuf[3] == 'S')){ + *bytesLeft -= 4; + m_f_OggS_found = true; + return ERR_FLAC_NONE; + } + + uint32_t temp = readUint(8); + uint16_t sync = temp << 6 |readUint(6); + if (sync != 0x3FFE){ + log_i("Sync code expected 0x3FFE but received %X", sync); + return ERR_FLAC_SYNC_CODE_NOT_FOUND; + } + + readUint(1); + FLACFrameHeader->blockingStrategy = readUint(1); + FLACFrameHeader->blockSizeCode = readUint(4); + FLACFrameHeader->sampleRateCode = readUint(4); + FLACFrameHeader->chanAsgn = readUint(4); + FLACFrameHeader->sampleSizeCode = readUint(3); + + if(!FLACMetadataBlock->numChannels){ + if(FLACFrameHeader->chanAsgn == 0) FLACMetadataBlock->numChannels = 1; + if(FLACFrameHeader->chanAsgn == 1) FLACMetadataBlock->numChannels = 2; + if(FLACFrameHeader->chanAsgn > 7) FLACMetadataBlock->numChannels = 2; + } + if(FLACMetadataBlock->numChannels < 1) return ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT; + + if(!FLACMetadataBlock->bitsPerSample){ + if(FLACFrameHeader->sampleSizeCode == 1) FLACMetadataBlock->bitsPerSample = 8; + if(FLACFrameHeader->sampleSizeCode == 2) FLACMetadataBlock->bitsPerSample = 12; + if(FLACFrameHeader->sampleSizeCode == 4) FLACMetadataBlock->bitsPerSample = 16; + if(FLACFrameHeader->sampleSizeCode == 5) FLACMetadataBlock->bitsPerSample = 20; + if(FLACFrameHeader->sampleSizeCode == 6) FLACMetadataBlock->bitsPerSample = 24; + } + if(FLACMetadataBlock->bitsPerSample > 16) return ERR_FLAC_BITS_PER_SAMPLE_TOO_BIG; + if(FLACMetadataBlock->bitsPerSample < 8 ) return ERR_FLAG_BITS_PER_SAMPLE_UNKNOWN; + + if(!FLACMetadataBlock->sampleRate){ + if(FLACFrameHeader->sampleRateCode == 1) FLACMetadataBlock->sampleRate = 88200; + if(FLACFrameHeader->sampleRateCode == 2) FLACMetadataBlock->sampleRate = 176400; + if(FLACFrameHeader->sampleRateCode == 3) FLACMetadataBlock->sampleRate = 192000; + if(FLACFrameHeader->sampleRateCode == 4) FLACMetadataBlock->sampleRate = 8000; + if(FLACFrameHeader->sampleRateCode == 5) FLACMetadataBlock->sampleRate = 16000; + if(FLACFrameHeader->sampleRateCode == 6) FLACMetadataBlock->sampleRate = 22050; + if(FLACFrameHeader->sampleRateCode == 7) FLACMetadataBlock->sampleRate = 24000; + if(FLACFrameHeader->sampleRateCode == 8) FLACMetadataBlock->sampleRate = 32000; + if(FLACFrameHeader->sampleRateCode == 9) FLACMetadataBlock->sampleRate = 44100; + if(FLACFrameHeader->sampleRateCode == 10) FLACMetadataBlock->sampleRate = 48000; + if(FLACFrameHeader->sampleRateCode == 11) FLACMetadataBlock->sampleRate = 96000; + } + + readUint(1); + temp = (readUint(8) << 24); + temp = ~temp; + + uint32_t shift = 0x80000000; // Number of leading zeros + int8_t count = 0; + for(int i=0; i<32; i++){ + if((temp & shift) == 0) {count++; shift >>= 1;} + else break; + } + count--; + for (int i = 0; i < count; i++) readUint(8); + m_blockSize = 0; + + if (FLACFrameHeader->blockSizeCode == 1) + m_blockSize = 192; + else if (2 <= FLACFrameHeader->blockSizeCode && FLACFrameHeader->blockSizeCode <= 5) + m_blockSize = 576 << (FLACFrameHeader->blockSizeCode - 2); + else if (FLACFrameHeader->blockSizeCode == 6) + m_blockSize = readUint(8) + 1; + else if (FLACFrameHeader->blockSizeCode == 7) + m_blockSize = readUint(16) + 1; + else if (8 <= FLACFrameHeader->blockSizeCode && FLACFrameHeader->blockSizeCode <= 15) + m_blockSize = 256 << (FLACFrameHeader->blockSizeCode - 8); + else{ + return ERR_FLAC_RESERVED_BLOCKSIZE_UNSUPPORTED; + } + + if(m_blockSize > 8192){ + log_e("Error: blockSize too big"); + return ERR_FLAC_BLOCKSIZE_TOO_BIG; + } + + if(FLACFrameHeader->sampleRateCode == 12) + readUint(8); + else if (FLACFrameHeader->sampleRateCode == 13 || FLACFrameHeader->sampleRateCode == 14){ + readUint(16); + } + readUint(8); + m_status = DECODE_SUBFRAMES; + *bytesLeft = m_bytesAvail; + m_blockSizeLeft = m_blockSize; + + return ERR_FLAC_NONE; + } + + if(m_status == DECODE_SUBFRAMES){ + + // Decode each channel's subframe, then skip footer + int ret = decodeSubframes(); + if(ret != 0) return ret; + m_status = OUT_SAMPLES; + } + + if(m_status == OUT_SAMPLES){ // Write the decoded samples + // blocksize can be much greater than outbuff, so we can't stuff all in once + // therefore we need often more than one loop (split outputblock into pieces) + uint16_t blockSize; + static uint16_t offset = 0; + if(m_blockSize < outBuffSize + offset) blockSize = m_blockSize - offset; + else blockSize = outBuffSize; + + + for (int i = 0; i < blockSize; i++) { + for (int j = 0; j < FLACMetadataBlock->numChannels; j++) { + int val = FLACsubFramesBuff->samplesBuffer[j][i + offset]; + if (FLACMetadataBlock->bitsPerSample == 8) val += 128; + outbuf[2*i+j] = val; + } + } + + m_validSamples = blockSize * FLACMetadataBlock->numChannels; + offset += blockSize; + + if(offset != m_blockSize) return GIVE_NEXT_LOOP; + offset = 0; + if(offset > m_blockSize) { log_e("offset has a wrong value"); } + } + + alignToByte(); + readUint(16); + m_bytesDecoded = *bytesLeft - m_bytesAvail; +// log_i("m_bytesDecoded %i", m_bytesDecoded); +// m_compressionRatio = (float)m_bytesDecoded / (float)m_blockSize * FLACMetadataBlock->numChannels * (16/8); +// log_i("m_compressionRatio % f", m_compressionRatio); + *bytesLeft = m_bytesAvail; + m_status = DECODE_FRAME; + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +uint16_t FLACGetOutputSamps(){ + int vs = m_validSamples; + m_validSamples=0; + return vs; +} +//---------------------------------------------------------------------------------------------------------------------- +uint64_t FLACGetTotoalSamplesInStream(){ + return FLACMetadataBlock->totalSamples; +} +//---------------------------------------------------------------------------------------------------------------------- +uint8_t FLACGetBitsPerSample(){ + return FLACMetadataBlock->bitsPerSample; +} +//---------------------------------------------------------------------------------------------------------------------- +uint8_t FLACGetChannels(){ + return FLACMetadataBlock->numChannels; +} +//---------------------------------------------------------------------------------------------------------------------- +uint32_t FLACGetSampRate(){ + return FLACMetadataBlock->sampleRate; +} +//---------------------------------------------------------------------------------------------------------------------- +uint32_t FLACGetBitRate(){ + if(FLACMetadataBlock->totalSamples){ + float BitsPerSamp = (float)FLACMetadataBlock->audioDataLength / (float)FLACMetadataBlock->totalSamples * 8; + return ((uint32_t)BitsPerSamp * FLACMetadataBlock->sampleRate); + } + return 0; +} +//---------------------------------------------------------------------------------------------------------------------- +uint32_t FLACGetAudioFileDuration() { + if(FLACGetSampRate()){ + uint32_t afd = FLACGetTotoalSamplesInStream()/ FLACGetSampRate(); // AudioFileDuration + return afd; + } + return 0; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeSubframes(){ + if(FLACFrameHeader->chanAsgn <= 7) { + for (int ch = 0; ch < FLACMetadataBlock->numChannels; ch++) + decodeSubframe(FLACMetadataBlock->bitsPerSample, ch); + } + else if (8 <= FLACFrameHeader->chanAsgn && FLACFrameHeader->chanAsgn <= 10) { + decodeSubframe(FLACMetadataBlock->bitsPerSample + (FLACFrameHeader->chanAsgn == 9 ? 1 : 0), 0); + decodeSubframe(FLACMetadataBlock->bitsPerSample + (FLACFrameHeader->chanAsgn == 9 ? 0 : 1), 1); + if(FLACFrameHeader->chanAsgn == 8) { + for (int i = 0; i < m_blockSize; i++) + FLACsubFramesBuff->samplesBuffer[1][i] = ( + FLACsubFramesBuff->samplesBuffer[0][i] - + FLACsubFramesBuff->samplesBuffer[1][i]); + } + else if (FLACFrameHeader->chanAsgn == 9) { + for (int i = 0; i < m_blockSize; i++) + FLACsubFramesBuff->samplesBuffer[0][i] += FLACsubFramesBuff->samplesBuffer[1][i]; + } + else if (FLACFrameHeader->chanAsgn == 10) { + for (int i = 0; i < m_blockSize; i++) { + long side = FLACsubFramesBuff->samplesBuffer[1][i]; + long right = FLACsubFramesBuff->samplesBuffer[0][i] - (side >> 1); + FLACsubFramesBuff->samplesBuffer[1][i] = right; + FLACsubFramesBuff->samplesBuffer[0][i] = right + side; + } + } + else { + log_e("unknown channel assignment"); + return ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT; + } + } + else{ + log_e("Reserved channel assignment"); + return ERR_FLAC_RESERVED_CHANNEL_ASSIGNMENT; + } + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeSubframe(uint8_t sampleDepth, uint8_t ch) { + int8_t ret = 0; + readUint(1); + uint8_t type = readUint(6); + int shift = readUint(1); + if (shift == 1) { + while (readUint(1) == 0) + shift++; + } + sampleDepth -= shift; + + if(type == 0){ // Constant coding + int16_t s= readSignedInt(sampleDepth); + for(int i=0; i < m_blockSize; i++){ + FLACsubFramesBuff->samplesBuffer[ch][i] = s; + } + } + else if (type == 1) { // Verbatim coding + for (int i = 0; i < m_blockSize; i++) + FLACsubFramesBuff->samplesBuffer[ch][i] = readSignedInt(sampleDepth); + } + else if (8 <= type && type <= 12){ + ret = decodeFixedPredictionSubframe(type - 8, sampleDepth, ch); + if(ret) return ret; + } + else if (32 <= type && type <= 63){ + ret = decodeLinearPredictiveCodingSubframe(type - 31, sampleDepth, ch); + if(ret) return ret; + } + else{ + return ERR_FLAC_RESERVED_SUB_TYPE; + } + if(shift>0){ + for (int i = 0; i < m_blockSize; i++){ + FLACsubFramesBuff->samplesBuffer[ch][i] <<= shift; + } + } + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeFixedPredictionSubframe(uint8_t predOrder, uint8_t sampleDepth, uint8_t ch) { + uint8_t ret = 0; + for(uint8_t i = 0; i < predOrder; i++) + FLACsubFramesBuff->samplesBuffer[ch][i] = readSignedInt(sampleDepth); + ret = decodeResiduals(predOrder, ch); + if(ret) return ret; + coefs.clear(); + if(predOrder == 0) coefs.resize(0); + if(predOrder == 1) coefs.push_back(1); // FIXED_PREDICTION_COEFFICIENTS + if(predOrder == 2){coefs.push_back(2); coefs.push_back(-1);} + if(predOrder == 3){coefs.push_back(3); coefs.push_back(-3); coefs.push_back(1);} + if(predOrder == 4){coefs.push_back(4); coefs.push_back(-6); coefs.push_back(4); coefs.push_back(-1);} + if(predOrder > 4) return ERR_FLAC_PREORDER_TOO_BIG; // Error: preorder > 4" + restoreLinearPrediction(ch, 0); + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeLinearPredictiveCodingSubframe(int lpcOrder, int sampleDepth, uint8_t ch){ + int8_t ret = 0; + for (int i = 0; i < lpcOrder; i++) + FLACsubFramesBuff->samplesBuffer[ch][i] = readSignedInt(sampleDepth); + int precision = readUint(4) + 1; + int shift = readSignedInt(5); + coefs.resize(0); + for (uint8_t i = 0; i < lpcOrder; i++) + coefs.push_back(readSignedInt(precision)); + ret = decodeResiduals(lpcOrder, ch); + if(ret) return ret; + restoreLinearPrediction(ch, shift); + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +int8_t decodeResiduals(uint8_t warmup, uint8_t ch) { + + int method = readUint(2); + if (method >= 2) + return ERR_FLAC_RESERVED_RESIDUAL_CODING; // Reserved residual coding method + uint8_t paramBits = method == 0 ? 4 : 5; + int escapeParam = (method == 0 ? 0xF : 0x1F); + int partitionOrder = readUint(4); + + int numPartitions = 1 << partitionOrder; + if (m_blockSize % numPartitions != 0) + return ERR_FLAC_WRONG_RICE_PARTITION_NR; //Error: Block size not divisible by number of Rice partitions + int partitionSize = m_blockSize/ numPartitions; + + for (int i = 0; i < numPartitions; i++) { + int start = i * partitionSize + (i == 0 ? warmup : 0); + int end = (i + 1) * partitionSize; + + int param = readUint(paramBits); + if (param < escapeParam) { + for (int j = start; j < end; j++){ + FLACsubFramesBuff->samplesBuffer[ch][j] = readRiceSignedInt(param); + } + } else { + int numBits = readUint(5); + for (int j = start; j < end; j++){ + FLACsubFramesBuff->samplesBuffer[ch][j] = readSignedInt(numBits); + } + } + } + return ERR_FLAC_NONE; +} +//---------------------------------------------------------------------------------------------------------------------- +void restoreLinearPrediction(uint8_t ch, uint8_t shift) { + + for (int i = coefs.size(); i < m_blockSize; i++) { + int32_t sum = 0; + for (int j = 0; j < coefs.size(); j++){ + sum += FLACsubFramesBuff->samplesBuffer[ch][i - 1 - j] * coefs[j]; + } + FLACsubFramesBuff->samplesBuffer[ch][i] += (sum >> shift); + } +} +//---------------------------------------------------------------------------------------------------------------------- + diff --git a/yoRadio/src/audioI2S/flac_decoder/flac_decoder.h b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.h new file mode 100644 index 0000000..44bf41f --- /dev/null +++ b/yoRadio/src/audioI2S/flac_decoder/flac_decoder.h @@ -0,0 +1,175 @@ +/* + * flac_decoder.h + * + * Created on: Jul 03,2020 + * Updated on: Apr 27,2021 + * + * Author: wolle + * + * Restrictions: + * blocksize must not exceed 8192 + * bits per sample must be 8 or 16 + * num Channels must be 1 or 2 + * + * + */ +#pragma once +#pragma GCC optimize ("Ofast") + +#include "Arduino.h" + +#define MAX_CHANNELS 2 +#define MAX_BLOCKSIZE 8192 +#define APLL_DISABLE 0 +#define EXTERNAL_I2S 0 + + +typedef struct FLACsubFramesBuff_t{ + int32_t samplesBuffer[MAX_CHANNELS][MAX_BLOCKSIZE]; +}FLACsubframesBuffer_t; + +enum : uint8_t {FLACDECODER_INIT, FLACDECODER_READ_IN, FLACDECODER_WRITE_OUT}; +enum : uint8_t {DECODE_FRAME, DECODE_SUBFRAMES, OUT_SAMPLES}; +enum : int8_t {GIVE_NEXT_LOOP = +1, + ERR_FLAC_NONE = 0, + ERR_FLAC_BLOCKSIZE_TOO_BIG = -1, + ERR_FLAC_RESERVED_BLOCKSIZE_UNSUPPORTED = -2, + ERR_FLAC_SYNC_CODE_NOT_FOUND = -3, + ERR_FLAC_UNKNOWN_CHANNEL_ASSIGNMENT = -4, + ERR_FLAC_RESERVED_CHANNEL_ASSIGNMENT = -5, + ERR_FLAC_RESERVED_SUB_TYPE = -6, + ERR_FLAC_PREORDER_TOO_BIG = -7, + ERR_FLAC_RESERVED_RESIDUAL_CODING = -8, + ERR_FLAC_WRONG_RICE_PARTITION_NR = -9, + ERR_FLAC_BITS_PER_SAMPLE_TOO_BIG = -10, + ERR_FLAG_BITS_PER_SAMPLE_UNKNOWN = 11}; + +typedef struct FLACMetadataBlock_t{ + // METADATA_BLOCK_STREAMINFO + uint16_t minblocksize; // The minimum block size (in samples) used in the stream. + //---------------------------------------------------------------------------------------- + // The maximum block size (in samples) used in the stream. + uint16_t maxblocksize; // (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream. + //---------------------------------------------------------------------------------------- + // The minimum frame size (in bytes) used in the stream. + uint32_t minframesize; // May be 0 to imply the value is not known. + //---------------------------------------------------------------------------------------- + // The maximum frame size (in bytes) used in the stream. + uint32_t maxframesize; // May be 0 to imply the value is not known. + //---------------------------------------------------------------------------------------- + // Sample rate in Hz. Though 20 bits are available, + // the maximum sample rate is limited by the structure of frame headers to 655350Hz. + uint32_t sampleRate; // Also, a value of 0 is invalid. + //---------------------------------------------------------------------------------------- + // Number of channels FLAC supports from 1 to 8 channels + uint8_t numChannels; // 000 : 1 channel .... 111 : 8 channels + //---------------------------------------------------------------------------------------- + // Sample size in bits: + // 000 : get from STREAMINFO metadata block + // 001 : 8 bits per sample + // 010 : 12 bits per sample + // 011 : reserved + // 100 : 16 bits per sample + // 101 : 20 bits per sample + // 110 : 24 bits per sample + uint8_t bitsPerSample; // 111 : reserved + //---------------------------------------------------------------------------------------- + // Total samples in stream. 'Samples' means inter-channel sample, + // i.e. one second of 44.1Khz audio will have 44100 samples regardless of the number + uint64_t totalSamples; // of channels. A value of zero here means the number of total samples is unknown. + //---------------------------------------------------------------------------------------- + uint32_t audioDataLength;// is not the filelength, is only the length of the audio datablock in bytes + + + +}FLACMetadataBlock_t; + + +typedef struct FLACFrameHeader_t { + // 0 : fixed-blocksize stream; frame header encodes the frame number + uint8_t blockingStrategy; // 1 : variable-blocksize stream; frame header encodes the sample number + //---------------------------------------------------------------------------------------- + // Block size in inter-channel samples: + // 0000 : reserved + // 0001 : 192 samples + // 0010-0101 : 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608 + // 0110 : get 8 bit (blocksize-1) from end of header + // 0111 : get 16 bit (blocksize-1) from end of header + uint8_t blockSizeCode; // 1000-1111 : 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/16384/32768 + //---------------------------------------------------------------------------------------- + // 0000 : get from STREAMINFO metadata block + // 0001 : 88.2kHz + // 0010 : 176.4kHz + // 0011 : 192kHz + // 0100 : 8kHz + // 0101 : 16kHz + // 0110 : 22.05kHz + // 0111 : 24kHz + // 1000 : 32kHz + // 1001 : 44.1kHz + // 1010 : 48kHz + // 1011 : 96kHz + // 1100 : get 8 bit sample rate (in kHz) from end of header + // 1101 : get 16 bit sample rate (in Hz) from end of header + // 1110 : get 16 bit sample rate (in tens of Hz) from end of header + uint8_t sampleRateCode; // 1111 : invalid, to prevent sync-fooling string of 1s + //---------------------------------------------------------------------------------------- + // Channel assignment + // 0000 1 channel: mono + // 0001 2 channels: left, right + // 0010 3 channels + // 0011 4 channels + // 0100 5 channels + // 0101 6 channels + // 0110 7 channels + // 0111 8 channels + // 1000 : left/side stereo: channel 0 is the left channel, channel 1 is the side(difference) channel + // 1001 : right/side stereo: channel 0 is the side(difference) channel, channel 1 is the right channel + // 1010 : mid/side stereo: channel 0 is the mid(average) channel, channel 1 is the side(difference) channel + uint8_t chanAsgn; // 1011-1111 : reserved + //---------------------------------------------------------------------------------------- + // Sample size in bits: + // 000 : get from STREAMINFO metadata block + // 001 : 8 bits per sample + // 010 : 12 bits per sample + // 011 : reserved + // 100 : 16 bits per sample + // 101 : 20 bits per sample + // 110 : 24 bits per sample + uint8_t sampleSizeCode; // 111 : reserved + //---------------------------------------------------------------------------------------- + uint32_t totalSamples; // totalSamplesInStream + //---------------------------------------------------------------------------------------- + uint32_t bitrate; // bitrate + + +}FLACFrameHeader_t; + +int FLACFindSyncWord(unsigned char *buf, int nBytes); +int FLACFindOggSyncWord(unsigned char *buf, int nBytes); +int FLACparseOggHeader(unsigned char *buf); +bool FLACDecoder_AllocateBuffers(void); +void FLACDecoder_ClearBuffer(); +void FLACDecoder_FreeBuffers(); +void FLACSetRawBlockParams(uint8_t Chans, uint32_t SampRate, uint8_t BPS, uint32_t tsis, uint32_t AuDaLength); +void FLACDecoderReset(); +int8_t FLACDecode(uint8_t *inbuf, int *bytesLeft, short *outbuf); +uint16_t FLACGetOutputSamps(); +uint64_t FLACGetTotoalSamplesInStream(); +uint8_t FLACGetBitsPerSample(); +uint8_t FLACGetChannels(); +uint32_t FLACGetSampRate(); +uint32_t FLACGetBitRate(); +uint32_t FLACGetAudioFileDuration(); +uint32_t readUint(uint8_t nBits); +int32_t readSignedInt(int nBits); +int64_t readRiceSignedInt(uint8_t param); +void alignToByte(); +int8_t decodeSubframes(); +int8_t decodeSubframe(uint8_t sampleDepth, uint8_t ch); +int8_t decodeFixedPredictionSubframe(uint8_t predOrder, uint8_t sampleDepth, uint8_t ch); +int8_t decodeLinearPredictiveCodingSubframe(int lpcOrder, int sampleDepth, uint8_t ch); +int8_t decodeResiduals(uint8_t warmup, uint8_t ch); +void restoreLinearPrediction(uint8_t ch, uint8_t shift); + + diff --git a/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp new file mode 100644 index 0000000..e2c6ae0 --- /dev/null +++ b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.cpp @@ -0,0 +1,3822 @@ +/* + * mp3_decoder.cpp + * libhelix_HMP3DECODER + * + * Created on: 26.10.2018 + * Updated on: 03.01.2022 + */ +#include "mp3_decoder.h" +/* clip to range [-2^n, 2^n - 1] */ +#if 0 //Fast on ARM: +#define CLIP_2N(y, n) { \ + int sign = (y) >> 31; \ + if (sign != (y) >> (n)) { \ + (y) = sign ^ ((1 << (n)) - 1); \ + } \ +} +#else //on xtensa this is faster, due to asm min/max instructions: +#define CLIP_2N(y, n) { \ + int x = 1 << n; \ + if (y < -x) y = -x; \ + x--; \ + if (y > x) y = x; \ +} +#endif + +const uint8_t m_SYNCWORDH =0xff; +const uint8_t m_SYNCWORDL =0xf0; +const uint8_t m_DQ_FRACBITS_OUT =25; // number of fraction bits in output of dequant +const uint8_t m_CSHIFT =12; // coefficients have 12 leading sign bits for early-terminating mulitplies +const uint8_t m_SIBYTES_MPEG1_MONO =17; +const uint8_t m_SIBYTES_MPEG1_STEREO =32; +const uint8_t m_SIBYTES_MPEG2_MONO =9; +const uint8_t m_SIBYTES_MPEG2_STEREO =17; +const uint8_t m_IMDCT_SCALE =2; // additional scaling (by sqrt(2)) for fast IMDCT36 +const uint8_t m_NGRANS_MPEG1 =2; +const uint8_t m_NGRANS_MPEG2 =1; +const uint32_t m_SQRTHALF =0x5a82799a; // sqrt(0.5) in Q31 format + + +MP3FrameInfo_t *m_MP3FrameInfo; +SFBandTable_t m_SFBandTable; +StereoMode_t m_sMode; /* mono/stereo mode */ +MPEGVersion_t m_MPEGVersion; /* version ID */ +FrameHeader_t *m_FrameHeader; +SideInfoSub_t m_SideInfoSub[m_MAX_NGRAN][m_MAX_NCHAN]; +SideInfo_t *m_SideInfo; +CriticalBandInfo_t m_CriticalBandInfo[m_MAX_NCHAN]; /* filled in dequantizer, used in joint stereo reconstruction */ +DequantInfo_t *m_DequantInfo; +HuffmanInfo_t *m_HuffmanInfo; +IMDCTInfo_t *m_IMDCTInfo; +ScaleFactorInfoSub_t m_ScaleFactorInfoSub[m_MAX_NGRAN][m_MAX_NCHAN]; +ScaleFactorJS_t *m_ScaleFactorJS; +SubbandInfo_t *m_SubbandInfo; +MP3DecInfo_t *m_MP3DecInfo; + +const unsigned short huffTable[4242] PROGMEM = { + /* huffTable01[9] */ + 0xf003, 0x3112, 0x3101, 0x2011, 0x2011, 0x1000, 0x1000, 0x1000, 0x1000, + /* huffTable02[65] */ + 0xf006, 0x6222, 0x6201, 0x5212, 0x5212, 0x5122, 0x5122, 0x5021, 0x5021, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + /* huffTable03[65] */ + 0xf006, 0x6222, 0x6201, 0x5212, 0x5212, 0x5122, 0x5122, 0x5021, 0x5021, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2101, 0x2101, 0x2101, + 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, + 0x2101, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + /* huffTable05[257] */ + 0xf008, 0x8332, 0x8322, 0x7232, 0x7232, 0x6132, 0x6132, 0x6132, 0x6132, 0x7312, 0x7312, 0x7301, + 0x7301, 0x7031, 0x7031, 0x7222, 0x7222, 0x6212, 0x6212, 0x6212, 0x6212, 0x6122, 0x6122, 0x6122, + 0x6122, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + /* huffTable06[129] */ + 0xf007, 0x7332, 0x7301, 0x6322, 0x6322, 0x6232, 0x6232, 0x6031, 0x6031, 0x5312, 0x5312, 0x5312, + 0x5312, 0x5132, 0x5132, 0x5132, 0x5132, 0x5222, 0x5222, 0x5222, 0x5222, 0x5201, 0x5201, 0x5201, + 0x5201, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4122, 0x4122, 0x4122, + 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, + 0x4021, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + /* huffTable07[110] */ + 0xf006, 0x0041, 0x0052, 0x005b, 0x0060, 0x0063, 0x0068, 0x006b, 0x6212, 0x5122, 0x5122, 0x6201, + 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf004, 0x4552, 0x4542, 0x4452, 0x4352, 0x3532, 0x3532, + 0x3442, 0x3442, 0x3522, 0x3522, 0x3252, 0x3252, 0x2512, 0x2512, 0x2512, 0x2512, 0xf003, 0x2152, + 0x2152, 0x3501, 0x3432, 0x2051, 0x2051, 0x3342, 0x3332, 0xf002, 0x2422, 0x2242, 0x1412, 0x1412, + 0xf001, 0x1142, 0x1041, 0xf002, 0x2401, 0x2322, 0x2232, 0x2301, 0xf001, 0x1312, 0x1132, 0xf001, + 0x1031, 0x1222, + /* huffTable08[280] */ + 0xf008, 0x0101, 0x010a, 0x010f, 0x8512, 0x8152, 0x0112, 0x0115, 0x8422, 0x8242, 0x8412, 0x7142, + 0x7142, 0x8401, 0x8041, 0x8322, 0x8232, 0x8312, 0x8132, 0x8301, 0x8031, 0x6222, 0x6222, 0x6222, + 0x6222, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, + 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, + 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0xf003, 0x3552, 0x3452, 0x2542, 0x2542, 0x1352, 0x1352, + 0x1352, 0x1352, 0xf002, 0x2532, 0x2442, 0x1522, 0x1522, 0xf001, 0x1252, 0x1501, 0xf001, 0x1432, + 0x1342, 0xf001, 0x1051, 0x1332, + /* huffTable09[93] */ + 0xf006, 0x0041, 0x004a, 0x004f, 0x0052, 0x0057, 0x005a, 0x6412, 0x6142, 0x6322, 0x6232, 0x5312, + 0x5312, 0x5132, 0x5132, 0x6301, 0x6031, 0x5222, 0x5222, 0x5201, 0x5201, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4122, 0x4122, 0x4122, 0x4122, 0x4021, 0x4021, 0x4021, 0x4021, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0xf003, 0x3552, 0x3542, 0x2532, 0x2532, 0x2352, 0x2352, + 0x3452, 0x3501, 0xf002, 0x2442, 0x2522, 0x2252, 0x2512, 0xf001, 0x1152, 0x1432, 0xf002, 0x1342, + 0x1342, 0x2051, 0x2401, 0xf001, 0x1422, 0x1242, 0xf001, 0x1332, 0x1041, + /* huffTable10[320] */ + 0xf008, 0x0101, 0x010a, 0x010f, 0x0118, 0x011b, 0x0120, 0x0125, 0x8712, 0x8172, 0x012a, 0x012d, + 0x0132, 0x8612, 0x8162, 0x8061, 0x0137, 0x013a, 0x013d, 0x8412, 0x8142, 0x8041, 0x8322, 0x8232, + 0x8301, 0x7312, 0x7312, 0x7132, 0x7132, 0x7031, 0x7031, 0x7222, 0x7222, 0x6212, 0x6212, 0x6212, + 0x6212, 0x6122, 0x6122, 0x6122, 0x6122, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, + 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf003, 0x3772, 0x3762, 0x3672, 0x3752, 0x3572, 0x3662, + 0x2742, 0x2742, 0xf002, 0x2472, 0x2652, 0x2562, 0x2732, 0xf003, 0x2372, 0x2372, 0x2642, 0x2642, + 0x3552, 0x3452, 0x2362, 0x2362, 0xf001, 0x1722, 0x1272, 0xf002, 0x2462, 0x2701, 0x1071, 0x1071, + 0xf002, 0x1262, 0x1262, 0x2542, 0x2532, 0xf002, 0x1601, 0x1601, 0x2352, 0x2442, 0xf001, 0x1632, + 0x1622, 0xf002, 0x2522, 0x2252, 0x1512, 0x1512, 0xf002, 0x1152, 0x1152, 0x2432, 0x2342, 0xf001, + 0x1501, 0x1051, 0xf001, 0x1422, 0x1242, 0xf001, 0x1332, 0x1401, + /* huffTable11[296] */ + 0xf008, 0x0101, 0x0106, 0x010f, 0x0114, 0x0117, 0x8722, 0x8272, 0x011c, 0x7172, 0x7172, 0x8712, + 0x8071, 0x8632, 0x8362, 0x8061, 0x011f, 0x0122, 0x8512, 0x7262, 0x7262, 0x8622, 0x8601, 0x7612, + 0x7612, 0x7162, 0x7162, 0x8152, 0x8432, 0x8051, 0x0125, 0x8422, 0x8242, 0x8412, 0x8142, 0x8401, + 0x8041, 0x7322, 0x7322, 0x7232, 0x7232, 0x6312, 0x6312, 0x6312, 0x6312, 0x6132, 0x6132, 0x6132, + 0x6132, 0x7301, 0x7301, 0x7031, 0x7031, 0x6222, 0x6222, 0x6222, 0x6222, 0x5122, 0x5122, 0x5122, + 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, + 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x5201, 0x5201, 0x5201, + 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, + 0x5021, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, + 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0xf002, 0x2772, 0x2762, 0x2672, 0x2572, 0xf003, 0x2662, + 0x2662, 0x2742, 0x2742, 0x2472, 0x2472, 0x3752, 0x3552, 0xf002, 0x2652, 0x2562, 0x1732, 0x1732, + 0xf001, 0x1372, 0x1642, 0xf002, 0x2542, 0x2452, 0x2532, 0x2352, 0xf001, 0x1462, 0x1701, 0xf001, + 0x1442, 0x1522, 0xf001, 0x1252, 0x1501, 0xf001, 0x1342, 0x1332, + /* huffTable12[185] */ + 0xf007, 0x0081, 0x008a, 0x008f, 0x0092, 0x0097, 0x009a, 0x009d, 0x00a2, 0x00a5, 0x00a8, 0x7622, + 0x7262, 0x7162, 0x00ad, 0x00b0, 0x00b3, 0x7512, 0x7152, 0x7432, 0x7342, 0x00b6, 0x7422, 0x7242, + 0x7412, 0x6332, 0x6332, 0x6142, 0x6142, 0x6322, 0x6322, 0x6232, 0x6232, 0x7041, 0x7301, 0x6031, + 0x6031, 0x5312, 0x5312, 0x5312, 0x5312, 0x5132, 0x5132, 0x5132, 0x5132, 0x5222, 0x5222, 0x5222, + 0x5222, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4122, 0x4122, 0x4122, + 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, + 0x5021, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, + 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0xf003, 0x3772, 0x3762, + 0x2672, 0x2672, 0x2752, 0x2752, 0x2572, 0x2572, 0xf002, 0x2662, 0x2742, 0x2472, 0x2562, 0xf001, + 0x1652, 0x1732, 0xf002, 0x2372, 0x2552, 0x1722, 0x1722, 0xf001, 0x1272, 0x1642, 0xf001, 0x1462, + 0x1712, 0xf002, 0x1172, 0x1172, 0x2701, 0x2071, 0xf001, 0x1632, 0x1362, 0xf001, 0x1542, 0x1452, + 0xf002, 0x1442, 0x1442, 0x2601, 0x2501, 0xf001, 0x1612, 0x1061, 0xf001, 0x1532, 0x1352, 0xf001, + 0x1522, 0x1252, 0xf001, 0x1051, 0x1401, + /* huffTable13[497] */ + 0xf006, 0x0041, 0x0082, 0x00c3, 0x00e4, 0x0105, 0x0116, 0x011f, 0x0130, 0x0139, 0x013e, 0x0143, + 0x0146, 0x6212, 0x6122, 0x6201, 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101, + 0x4101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf006, 0x0108, 0x0111, 0x011a, 0x0123, 0x012c, 0x0131, + 0x0136, 0x013f, 0x0144, 0x0147, 0x014c, 0x0151, 0x0156, 0x015b, 0x6f12, 0x61f2, 0x60f1, 0x0160, + 0x0163, 0x0166, 0x62e2, 0x0169, 0x6e12, 0x61e2, 0x016c, 0x016f, 0x0172, 0x0175, 0x0178, 0x017b, + 0x66c2, 0x6d32, 0x017e, 0x6d22, 0x62d2, 0x6d12, 0x67b2, 0x0181, 0x0184, 0x63c2, 0x0187, 0x6b42, + 0x51d2, 0x51d2, 0x6d01, 0x60d1, 0x6a82, 0x68a2, 0x6c42, 0x64c2, 0x6b62, 0x66b2, 0x5c32, 0x5c32, + 0x5c22, 0x5c22, 0x52c2, 0x52c2, 0x5b52, 0x5b52, 0x65b2, 0x6982, 0x5c12, 0x5c12, 0xf006, 0x51c2, + 0x51c2, 0x6892, 0x6c01, 0x50c1, 0x50c1, 0x64b2, 0x6a62, 0x66a2, 0x6972, 0x5b32, 0x5b32, 0x53b2, + 0x53b2, 0x6882, 0x6a52, 0x5b22, 0x5b22, 0x65a2, 0x6962, 0x54a2, 0x54a2, 0x6872, 0x6782, 0x5492, + 0x5492, 0x6772, 0x6672, 0x42b2, 0x42b2, 0x42b2, 0x42b2, 0x4b12, 0x4b12, 0x4b12, 0x4b12, 0x41b2, + 0x41b2, 0x41b2, 0x41b2, 0x5b01, 0x5b01, 0x50b1, 0x50b1, 0x5692, 0x5692, 0x5a42, 0x5a42, 0x5a32, + 0x5a32, 0x53a2, 0x53a2, 0x5952, 0x5952, 0x5592, 0x5592, 0x4a22, 0x4a22, 0x4a22, 0x4a22, 0x42a2, + 0x42a2, 0x42a2, 0x42a2, 0xf005, 0x4a12, 0x4a12, 0x41a2, 0x41a2, 0x5a01, 0x5862, 0x40a1, 0x40a1, + 0x5682, 0x5942, 0x4392, 0x4392, 0x5932, 0x5852, 0x5582, 0x5762, 0x4922, 0x4922, 0x4292, 0x4292, + 0x5752, 0x5572, 0x4832, 0x4832, 0x4382, 0x4382, 0x5662, 0x5742, 0x5472, 0x5652, 0x5562, 0x5372, + 0xf005, 0x3912, 0x3912, 0x3912, 0x3912, 0x3192, 0x3192, 0x3192, 0x3192, 0x4901, 0x4901, 0x4091, + 0x4091, 0x4842, 0x4842, 0x4482, 0x4482, 0x4272, 0x4272, 0x5642, 0x5462, 0x3822, 0x3822, 0x3822, + 0x3822, 0x3282, 0x3282, 0x3282, 0x3282, 0x3812, 0x3812, 0x3812, 0x3812, 0xf004, 0x4732, 0x4722, + 0x3712, 0x3712, 0x3172, 0x3172, 0x4552, 0x4701, 0x4071, 0x4632, 0x4362, 0x4542, 0x4452, 0x4622, + 0x4262, 0x4532, 0xf003, 0x2182, 0x2182, 0x3801, 0x3081, 0x3612, 0x3162, 0x3601, 0x3061, 0xf004, + 0x4352, 0x4442, 0x3522, 0x3522, 0x3252, 0x3252, 0x3501, 0x3501, 0x2512, 0x2512, 0x2512, 0x2512, + 0x2152, 0x2152, 0x2152, 0x2152, 0xf003, 0x3432, 0x3342, 0x3051, 0x3422, 0x3242, 0x3332, 0x2412, + 0x2412, 0xf002, 0x1142, 0x1142, 0x2401, 0x2041, 0xf002, 0x2322, 0x2232, 0x1312, 0x1312, 0xf001, + 0x1132, 0x1301, 0xf001, 0x1031, 0x1222, 0xf003, 0x0082, 0x008b, 0x008e, 0x0091, 0x0094, 0x0097, + 0x3ce2, 0x3dd2, 0xf003, 0x0093, 0x3eb2, 0x3be2, 0x3f92, 0x39f2, 0x3ae2, 0x3db2, 0x3bd2, 0xf003, + 0x3f82, 0x38f2, 0x3cc2, 0x008d, 0x3e82, 0x0090, 0x27f2, 0x27f2, 0xf003, 0x2ad2, 0x2ad2, 0x3da2, + 0x3cb2, 0x3bc2, 0x36f2, 0x2f62, 0x2f62, 0xf002, 0x28e2, 0x2f52, 0x2d92, 0x29d2, 0xf002, 0x25f2, + 0x27e2, 0x2ca2, 0x2bb2, 0xf003, 0x2f42, 0x2f42, 0x24f2, 0x24f2, 0x3ac2, 0x36e2, 0x23f2, 0x23f2, + 0xf002, 0x1f32, 0x1f32, 0x2d82, 0x28d2, 0xf001, 0x1f22, 0x12f2, 0xf002, 0x2e62, 0x2c92, 0x1f01, + 0x1f01, 0xf002, 0x29c2, 0x2e52, 0x1ba2, 0x1ba2, 0xf002, 0x2d72, 0x27d2, 0x1e42, 0x1e42, 0xf002, + 0x28c2, 0x26d2, 0x1e32, 0x1e32, 0xf002, 0x19b2, 0x19b2, 0x2b92, 0x2aa2, 0xf001, 0x1ab2, 0x15e2, + 0xf001, 0x14e2, 0x1c82, 0xf001, 0x1d62, 0x13e2, 0xf001, 0x1e22, 0x1e01, 0xf001, 0x10e1, 0x1d52, + 0xf001, 0x15d2, 0x1c72, 0xf001, 0x17c2, 0x1d42, 0xf001, 0x1b82, 0x18b2, 0xf001, 0x14d2, 0x1a92, + 0xf001, 0x19a2, 0x1c62, 0xf001, 0x13d2, 0x1b72, 0xf001, 0x1c52, 0x15c2, 0xf001, 0x1992, 0x1a72, + 0xf001, 0x17a2, 0x1792, 0xf003, 0x0023, 0x3df2, 0x2de2, 0x2de2, 0x1ff2, 0x1ff2, 0x1ff2, 0x1ff2, + 0xf001, 0x1fe2, 0x1fd2, 0xf001, 0x1ee2, 0x1fc2, 0xf001, 0x1ed2, 0x1fb2, 0xf001, 0x1bf2, 0x1ec2, + 0xf002, 0x1cd2, 0x1cd2, 0x2fa2, 0x29e2, 0xf001, 0x1af2, 0x1dc2, 0xf001, 0x1ea2, 0x1e92, 0xf001, + 0x1f72, 0x1e72, 0xf001, 0x1ef2, 0x1cf2, + /* huffTable15[580] */ + 0xf008, 0x0101, 0x0122, 0x0143, 0x0154, 0x0165, 0x0176, 0x017f, 0x0188, 0x0199, 0x01a2, 0x01ab, + 0x01b4, 0x01bd, 0x01c2, 0x01cb, 0x01d4, 0x01d9, 0x01de, 0x01e3, 0x01e8, 0x01ed, 0x01f2, 0x01f7, + 0x01fc, 0x0201, 0x0204, 0x0207, 0x020a, 0x020f, 0x0212, 0x0215, 0x021a, 0x021d, 0x0220, 0x8192, + 0x0223, 0x0226, 0x0229, 0x022c, 0x022f, 0x8822, 0x8282, 0x8812, 0x8182, 0x0232, 0x0235, 0x0238, + 0x023b, 0x8722, 0x8272, 0x8462, 0x8712, 0x8552, 0x8172, 0x023e, 0x8632, 0x8362, 0x8542, 0x8452, + 0x8622, 0x8262, 0x8612, 0x0241, 0x8532, 0x7162, 0x7162, 0x8352, 0x8442, 0x7522, 0x7522, 0x7252, + 0x7252, 0x7512, 0x7512, 0x7152, 0x7152, 0x8501, 0x8051, 0x7432, 0x7432, 0x7342, 0x7342, 0x7422, + 0x7422, 0x7242, 0x7242, 0x7332, 0x7332, 0x6142, 0x6142, 0x6142, 0x6142, 0x7412, 0x7412, 0x7401, + 0x7401, 0x6322, 0x6322, 0x6322, 0x6322, 0x6232, 0x6232, 0x6232, 0x6232, 0x7041, 0x7041, 0x7301, + 0x7301, 0x6312, 0x6312, 0x6312, 0x6312, 0x6132, 0x6132, 0x6132, 0x6132, 0x6031, 0x6031, 0x6031, + 0x6031, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5212, 0x5212, 0x5212, + 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, + 0x5122, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, + 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, + 0x3112, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, + 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0xf005, 0x5ff2, 0x5fe2, 0x5ef2, 0x5fd2, 0x4ee2, 0x4ee2, + 0x5df2, 0x5fc2, 0x5cf2, 0x5ed2, 0x5de2, 0x5fb2, 0x4bf2, 0x4bf2, 0x5ec2, 0x5ce2, 0x4dd2, 0x4dd2, + 0x4fa2, 0x4fa2, 0x4af2, 0x4af2, 0x4eb2, 0x4eb2, 0x4be2, 0x4be2, 0x4dc2, 0x4dc2, 0x4cd2, 0x4cd2, + 0x4f92, 0x4f92, 0xf005, 0x49f2, 0x49f2, 0x4ae2, 0x4ae2, 0x4db2, 0x4db2, 0x4bd2, 0x4bd2, 0x4f82, + 0x4f82, 0x48f2, 0x48f2, 0x4cc2, 0x4cc2, 0x4e92, 0x4e92, 0x49e2, 0x49e2, 0x4f72, 0x4f72, 0x47f2, + 0x47f2, 0x4da2, 0x4da2, 0x4ad2, 0x4ad2, 0x4cb2, 0x4cb2, 0x4f62, 0x4f62, 0x5ea2, 0x5f01, 0xf004, + 0x3bc2, 0x3bc2, 0x36f2, 0x36f2, 0x4e82, 0x48e2, 0x4f52, 0x4d92, 0x35f2, 0x35f2, 0x3e72, 0x3e72, + 0x37e2, 0x37e2, 0x3ca2, 0x3ca2, 0xf004, 0x3ac2, 0x3ac2, 0x3bb2, 0x3bb2, 0x49d2, 0x4d82, 0x3f42, + 0x3f42, 0x34f2, 0x34f2, 0x3f32, 0x3f32, 0x33f2, 0x33f2, 0x38d2, 0x38d2, 0xf004, 0x36e2, 0x36e2, + 0x3f22, 0x3f22, 0x32f2, 0x32f2, 0x4e62, 0x40f1, 0x3f12, 0x3f12, 0x31f2, 0x31f2, 0x3c92, 0x3c92, + 0x39c2, 0x39c2, 0xf003, 0x3e52, 0x3ba2, 0x3ab2, 0x35e2, 0x3d72, 0x37d2, 0x3e42, 0x34e2, 0xf003, + 0x3c82, 0x38c2, 0x3e32, 0x3d62, 0x36d2, 0x33e2, 0x3b92, 0x39b2, 0xf004, 0x3e22, 0x3e22, 0x3aa2, + 0x3aa2, 0x32e2, 0x32e2, 0x3e12, 0x3e12, 0x31e2, 0x31e2, 0x4e01, 0x40e1, 0x3d52, 0x3d52, 0x35d2, + 0x35d2, 0xf003, 0x3c72, 0x37c2, 0x3d42, 0x3b82, 0x24d2, 0x24d2, 0x38b2, 0x3a92, 0xf003, 0x39a2, + 0x3c62, 0x36c2, 0x3d32, 0x23d2, 0x23d2, 0x22d2, 0x22d2, 0xf003, 0x3d22, 0x3d01, 0x2d12, 0x2d12, + 0x2b72, 0x2b72, 0x27b2, 0x27b2, 0xf003, 0x21d2, 0x21d2, 0x3c52, 0x30d1, 0x25c2, 0x25c2, 0x2a82, + 0x2a82, 0xf002, 0x28a2, 0x2c42, 0x24c2, 0x2b62, 0xf003, 0x26b2, 0x26b2, 0x3992, 0x3c01, 0x2c32, + 0x2c32, 0x23c2, 0x23c2, 0xf003, 0x2a72, 0x2a72, 0x27a2, 0x27a2, 0x26a2, 0x26a2, 0x30c1, 0x3b01, + 0xf002, 0x12c2, 0x12c2, 0x2c22, 0x2b52, 0xf002, 0x25b2, 0x2c12, 0x2982, 0x2892, 0xf002, 0x21c2, + 0x2b42, 0x24b2, 0x2a62, 0xf002, 0x2b32, 0x2972, 0x13b2, 0x13b2, 0xf002, 0x2792, 0x2882, 0x2b22, + 0x2a52, 0xf002, 0x12b2, 0x12b2, 0x25a2, 0x2b12, 0xf002, 0x11b2, 0x11b2, 0x20b1, 0x2962, 0xf002, + 0x2692, 0x2a42, 0x24a2, 0x2872, 0xf002, 0x2782, 0x2a32, 0x13a2, 0x13a2, 0xf001, 0x1952, 0x1592, + 0xf001, 0x1a22, 0x12a2, 0xf001, 0x1a12, 0x11a2, 0xf002, 0x2a01, 0x20a1, 0x1862, 0x1862, 0xf001, + 0x1682, 0x1942, 0xf001, 0x1492, 0x1932, 0xf002, 0x1392, 0x1392, 0x2772, 0x2901, 0xf001, 0x1852, + 0x1582, 0xf001, 0x1922, 0x1762, 0xf001, 0x1672, 0x1292, 0xf001, 0x1912, 0x1091, 0xf001, 0x1842, + 0x1482, 0xf001, 0x1752, 0x1572, 0xf001, 0x1832, 0x1382, 0xf001, 0x1662, 0x1742, 0xf001, 0x1472, + 0x1801, 0xf001, 0x1081, 0x1652, 0xf001, 0x1562, 0x1732, 0xf001, 0x1372, 0x1642, 0xf001, 0x1701, + 0x1071, 0xf001, 0x1601, 0x1061, + /* huffTable16[651] */ + 0xf008, 0x0101, 0x010a, 0x0113, 0x8ff2, 0x0118, 0x011d, 0x0120, 0x82f2, 0x0131, 0x8f12, 0x81f2, + 0x0134, 0x0145, 0x0156, 0x0167, 0x0178, 0x0189, 0x019a, 0x01a3, 0x01ac, 0x01b5, 0x01be, 0x01c7, + 0x01d0, 0x01d9, 0x01de, 0x01e3, 0x01e6, 0x01eb, 0x01f0, 0x8152, 0x01f3, 0x01f6, 0x01f9, 0x01fc, + 0x8412, 0x8142, 0x01ff, 0x8322, 0x8232, 0x7312, 0x7312, 0x7132, 0x7132, 0x8301, 0x8031, 0x7222, + 0x7222, 0x6212, 0x6212, 0x6212, 0x6212, 0x6122, 0x6122, 0x6122, 0x6122, 0x6201, 0x6201, 0x6201, + 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, + 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf003, 0x3fe2, 0x3ef2, 0x3fd2, 0x3df2, 0x3fc2, 0x3cf2, + 0x3fb2, 0x3bf2, 0xf003, 0x2fa2, 0x2fa2, 0x3af2, 0x3f92, 0x39f2, 0x38f2, 0x2f82, 0x2f82, 0xf002, + 0x2f72, 0x27f2, 0x2f62, 0x26f2, 0xf002, 0x2f52, 0x25f2, 0x1f42, 0x1f42, 0xf001, 0x14f2, 0x13f2, + 0xf004, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x2f32, 0x2f32, 0x2f32, + 0x2f32, 0x00e2, 0x00f3, 0x00fc, 0x0105, 0xf001, 0x1f22, 0x1f01, 0xf004, 0x00fa, 0x00ff, 0x0104, + 0x0109, 0x010c, 0x0111, 0x0116, 0x0119, 0x011e, 0x0123, 0x0128, 0x43e2, 0x012d, 0x0130, 0x0133, + 0x0136, 0xf004, 0x0128, 0x012b, 0x012e, 0x4d01, 0x0131, 0x0134, 0x0137, 0x4c32, 0x013a, 0x4c12, + 0x40c1, 0x013d, 0x32e2, 0x32e2, 0x4e22, 0x4e12, 0xf004, 0x43d2, 0x4d22, 0x42d2, 0x41d2, 0x4b32, + 0x012f, 0x3d12, 0x3d12, 0x44c2, 0x4b62, 0x43c2, 0x47a2, 0x3c22, 0x3c22, 0x42c2, 0x45b2, 0xf004, + 0x41c2, 0x4c01, 0x4b42, 0x44b2, 0x4a62, 0x46a2, 0x33b2, 0x33b2, 0x4a52, 0x45a2, 0x3b22, 0x3b22, + 0x32b2, 0x32b2, 0x3b12, 0x3b12, 0xf004, 0x31b2, 0x31b2, 0x4b01, 0x40b1, 0x4962, 0x4692, 0x4a42, + 0x44a2, 0x4872, 0x4782, 0x33a2, 0x33a2, 0x4a32, 0x4952, 0x3a22, 0x3a22, 0xf004, 0x4592, 0x4862, + 0x31a2, 0x31a2, 0x4682, 0x4772, 0x3492, 0x3492, 0x4942, 0x4752, 0x3762, 0x3762, 0x22a2, 0x22a2, + 0x22a2, 0x22a2, 0xf003, 0x2a12, 0x2a12, 0x3a01, 0x30a1, 0x3932, 0x3392, 0x3852, 0x3582, 0xf003, + 0x2922, 0x2922, 0x2292, 0x2292, 0x3672, 0x3901, 0x2912, 0x2912, 0xf003, 0x2192, 0x2192, 0x3091, + 0x3842, 0x3482, 0x3572, 0x3832, 0x3382, 0xf003, 0x3662, 0x3822, 0x2282, 0x2282, 0x3742, 0x3472, + 0x2812, 0x2812, 0xf003, 0x2182, 0x2182, 0x2081, 0x2081, 0x3801, 0x3652, 0x2732, 0x2732, 0xf003, + 0x2372, 0x2372, 0x3562, 0x3642, 0x2722, 0x2722, 0x2272, 0x2272, 0xf003, 0x3462, 0x3552, 0x2701, + 0x2701, 0x1712, 0x1712, 0x1712, 0x1712, 0xf002, 0x1172, 0x1172, 0x2071, 0x2632, 0xf002, 0x2362, + 0x2542, 0x2452, 0x2622, 0xf001, 0x1262, 0x1612, 0xf002, 0x1162, 0x1162, 0x2601, 0x2061, 0xf002, + 0x1352, 0x1352, 0x2532, 0x2442, 0xf001, 0x1522, 0x1252, 0xf001, 0x1512, 0x1501, 0xf001, 0x1432, + 0x1342, 0xf001, 0x1051, 0x1422, 0xf001, 0x1242, 0x1332, 0xf001, 0x1401, 0x1041, 0xf004, 0x4ec2, + 0x0086, 0x3ed2, 0x3ed2, 0x39e2, 0x39e2, 0x4ae2, 0x49d2, 0x2ee2, 0x2ee2, 0x2ee2, 0x2ee2, 0x3de2, + 0x3de2, 0x3be2, 0x3be2, 0xf003, 0x2eb2, 0x2eb2, 0x2dc2, 0x2dc2, 0x3cd2, 0x3bd2, 0x2ea2, 0x2ea2, + 0xf003, 0x2cc2, 0x2cc2, 0x3da2, 0x3ad2, 0x3e72, 0x3ca2, 0x2ac2, 0x2ac2, 0xf003, 0x39c2, 0x3d72, + 0x2e52, 0x2e52, 0x1db2, 0x1db2, 0x1db2, 0x1db2, 0xf002, 0x1e92, 0x1e92, 0x2cb2, 0x2bc2, 0xf002, + 0x2e82, 0x28e2, 0x2d92, 0x27e2, 0xf002, 0x2bb2, 0x2d82, 0x28d2, 0x2e62, 0xf001, 0x16e2, 0x1c92, + 0xf002, 0x2ba2, 0x2ab2, 0x25e2, 0x27d2, 0xf002, 0x1e42, 0x1e42, 0x24e2, 0x2c82, 0xf001, 0x18c2, + 0x1e32, 0xf002, 0x1d62, 0x1d62, 0x26d2, 0x2b92, 0xf002, 0x29b2, 0x2aa2, 0x11e2, 0x11e2, 0xf002, + 0x14d2, 0x14d2, 0x28b2, 0x29a2, 0xf002, 0x1b72, 0x1b72, 0x27b2, 0x20d1, 0xf001, 0x1e01, 0x10e1, + 0xf001, 0x1d52, 0x15d2, 0xf001, 0x1c72, 0x17c2, 0xf001, 0x1d42, 0x1b82, 0xf001, 0x1a92, 0x1c62, + 0xf001, 0x16c2, 0x1d32, 0xf001, 0x1c52, 0x15c2, 0xf001, 0x1a82, 0x18a2, 0xf001, 0x1992, 0x1c42, + 0xf001, 0x16b2, 0x1a72, 0xf001, 0x1b52, 0x1982, 0xf001, 0x1892, 0x1972, 0xf001, 0x1792, 0x1882, + 0xf001, 0x1ce2, 0x1dd2, + /* huffTable24[705] */ + 0xf009, 0x8fe2, 0x8fe2, 0x8ef2, 0x8ef2, 0x8fd2, 0x8fd2, 0x8df2, 0x8df2, 0x8fc2, 0x8fc2, 0x8cf2, + 0x8cf2, 0x8fb2, 0x8fb2, 0x8bf2, 0x8bf2, 0x7af2, 0x7af2, 0x7af2, 0x7af2, 0x8fa2, 0x8fa2, 0x8f92, + 0x8f92, 0x79f2, 0x79f2, 0x79f2, 0x79f2, 0x78f2, 0x78f2, 0x78f2, 0x78f2, 0x8f82, 0x8f82, 0x8f72, + 0x8f72, 0x77f2, 0x77f2, 0x77f2, 0x77f2, 0x7f62, 0x7f62, 0x7f62, 0x7f62, 0x76f2, 0x76f2, 0x76f2, + 0x76f2, 0x7f52, 0x7f52, 0x7f52, 0x7f52, 0x75f2, 0x75f2, 0x75f2, 0x75f2, 0x7f42, 0x7f42, 0x7f42, + 0x7f42, 0x74f2, 0x74f2, 0x74f2, 0x74f2, 0x7f32, 0x7f32, 0x7f32, 0x7f32, 0x73f2, 0x73f2, 0x73f2, + 0x73f2, 0x7f22, 0x7f22, 0x7f22, 0x7f22, 0x72f2, 0x72f2, 0x72f2, 0x72f2, 0x71f2, 0x71f2, 0x71f2, + 0x71f2, 0x8f12, 0x8f12, 0x80f1, 0x80f1, 0x9f01, 0x0201, 0x0206, 0x020b, 0x0210, 0x0215, 0x021a, + 0x021f, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, + 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, + 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x0224, 0x0229, 0x0232, + 0x0237, 0x023a, 0x023f, 0x0242, 0x0245, 0x024a, 0x024d, 0x0250, 0x0253, 0x0256, 0x0259, 0x025c, + 0x025f, 0x0262, 0x0265, 0x0268, 0x026b, 0x026e, 0x0271, 0x0274, 0x0277, 0x027a, 0x027d, 0x0280, + 0x0283, 0x0288, 0x028b, 0x028e, 0x0291, 0x0294, 0x0297, 0x029a, 0x029f, 0x94b2, 0x02a4, 0x02a7, + 0x02aa, 0x93b2, 0x9882, 0x02af, 0x92b2, 0x02b2, 0x02b5, 0x9692, 0x94a2, 0x02b8, 0x9782, 0x9a32, + 0x93a2, 0x9952, 0x9592, 0x9a22, 0x92a2, 0x91a2, 0x9862, 0x9682, 0x9772, 0x9942, 0x9492, 0x9932, + 0x9392, 0x9852, 0x9582, 0x9922, 0x9762, 0x9672, 0x9292, 0x9912, 0x9192, 0x9842, 0x9482, 0x9752, + 0x9572, 0x9832, 0x9382, 0x9662, 0x9822, 0x9282, 0x9812, 0x9742, 0x9472, 0x9182, 0x02bb, 0x9652, + 0x9562, 0x9712, 0x02be, 0x8372, 0x8372, 0x9732, 0x9722, 0x8272, 0x8272, 0x8642, 0x8642, 0x8462, + 0x8462, 0x8552, 0x8552, 0x8172, 0x8172, 0x8632, 0x8632, 0x8362, 0x8362, 0x8542, 0x8542, 0x8452, + 0x8452, 0x8622, 0x8622, 0x8262, 0x8262, 0x8612, 0x8612, 0x8162, 0x8162, 0x9601, 0x9061, 0x8532, + 0x8532, 0x8352, 0x8352, 0x8442, 0x8442, 0x8522, 0x8522, 0x8252, 0x8252, 0x8512, 0x8512, 0x9501, + 0x9051, 0x7152, 0x7152, 0x7152, 0x7152, 0x8432, 0x8432, 0x8342, 0x8342, 0x7422, 0x7422, 0x7422, + 0x7422, 0x7242, 0x7242, 0x7242, 0x7242, 0x7332, 0x7332, 0x7332, 0x7332, 0x7412, 0x7412, 0x7412, + 0x7412, 0x7142, 0x7142, 0x7142, 0x7142, 0x8401, 0x8401, 0x8041, 0x8041, 0x7322, 0x7322, 0x7322, + 0x7322, 0x7232, 0x7232, 0x7232, 0x7232, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, + 0x6312, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x7301, 0x7301, 0x7301, + 0x7301, 0x7031, 0x7031, 0x7031, 0x7031, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, + 0x6222, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, + 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, + 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x6201, 0x6201, 0x6201, + 0x6201, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, + 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, + 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, + 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, + 0x4011, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0xf002, 0x2ee2, 0x2ed2, + 0x2de2, 0x2ec2, 0xf002, 0x2ce2, 0x2dd2, 0x2eb2, 0x2be2, 0xf002, 0x2dc2, 0x2cd2, 0x2ea2, 0x2ae2, + 0xf002, 0x2db2, 0x2bd2, 0x2cc2, 0x2e92, 0xf002, 0x29e2, 0x2da2, 0x2ad2, 0x2cb2, 0xf002, 0x2bc2, + 0x2e82, 0x28e2, 0x2d92, 0xf002, 0x29d2, 0x2e72, 0x27e2, 0x2ca2, 0xf002, 0x2ac2, 0x2bb2, 0x2d82, + 0x28d2, 0xf003, 0x3e01, 0x30e1, 0x2d01, 0x2d01, 0x16e2, 0x16e2, 0x16e2, 0x16e2, 0xf002, 0x2e62, + 0x2c92, 0x19c2, 0x19c2, 0xf001, 0x1e52, 0x1ab2, 0xf002, 0x15e2, 0x15e2, 0x2ba2, 0x2d72, 0xf001, + 0x17d2, 0x14e2, 0xf001, 0x1c82, 0x18c2, 0xf002, 0x2e42, 0x2e22, 0x1e32, 0x1e32, 0xf001, 0x1d62, + 0x16d2, 0xf001, 0x13e2, 0x1b92, 0xf001, 0x19b2, 0x1aa2, 0xf001, 0x12e2, 0x1e12, 0xf001, 0x11e2, + 0x1d52, 0xf001, 0x15d2, 0x1c72, 0xf001, 0x17c2, 0x1d42, 0xf001, 0x1b82, 0x18b2, 0xf001, 0x14d2, + 0x1a92, 0xf001, 0x19a2, 0x1c62, 0xf001, 0x16c2, 0x1d32, 0xf001, 0x13d2, 0x1d22, 0xf001, 0x12d2, + 0x1d12, 0xf001, 0x1b72, 0x17b2, 0xf001, 0x11d2, 0x1c52, 0xf001, 0x15c2, 0x1a82, 0xf001, 0x18a2, + 0x1992, 0xf001, 0x1c42, 0x14c2, 0xf001, 0x1b62, 0x16b2, 0xf002, 0x20d1, 0x2c01, 0x1c32, 0x1c32, + 0xf001, 0x13c2, 0x1a72, 0xf001, 0x17a2, 0x1c22, 0xf001, 0x12c2, 0x1b52, 0xf001, 0x15b2, 0x1c12, + 0xf001, 0x1982, 0x1892, 0xf001, 0x11c2, 0x1b42, 0xf002, 0x20c1, 0x2b01, 0x1b32, 0x1b32, 0xf002, + 0x20b1, 0x2a01, 0x1a12, 0x1a12, 0xf001, 0x1a62, 0x16a2, 0xf001, 0x1972, 0x1792, 0xf002, 0x20a1, + 0x2901, 0x1091, 0x1091, 0xf001, 0x1b22, 0x1a52, 0xf001, 0x15a2, 0x1b12, 0xf001, 0x11b2, 0x1962, + 0xf001, 0x1a42, 0x1872, 0xf001, 0x1801, 0x1081, 0xf001, 0x1701, 0x1071, +}; +/* pow(2,-i/4) * pow(j,4/3) for i=0..3 j=0..15, Q25 format */ +const int pow43_14[4][16] PROGMEM = { /* Q28 */ +{ 0x00000000, 0x10000000, 0x285145f3, 0x453a5cdb, 0x0cb2ff53, 0x111989d6, + 0x15ce31c8, 0x1ac7f203, 0x20000000, 0x257106b9, 0x2b16b4a3, 0x30ed74b4, + 0x36f23fa5, 0x3d227bd3, 0x437be656, 0x49fc823c, }, + +{ 0x00000000, 0x0d744fcd, 0x21e71f26, 0x3a36abd9, 0x0aadc084, 0x0e610e6e, + 0x12560c1d, 0x168523cf, 0x1ae89f99, 0x1f7c03a4, 0x243bae49, 0x29249c67, + 0x2e34420f, 0x33686f85, 0x38bf3dff, 0x3e370182, }, + +{ 0x00000000, 0x0b504f33, 0x1c823e07, 0x30f39a55, 0x08facd62, 0x0c176319, + 0x0f6b3522, 0x12efe2ad, 0x16a09e66, 0x1a79a317, 0x1e77e301, 0x2298d5b4, + 0x26da56fc, 0x2b3a902a, 0x2fb7e7e7, 0x3450f650, }, + +{ 0x00000000, 0x09837f05, 0x17f910d7, 0x2929c7a9, 0x078d0dfa, 0x0a2ae661, + 0x0cf73154, 0x0fec91cb, 0x1306fe0a, 0x16434a6c, 0x199ee595, 0x1d17ae3d, + 0x20abd76a, 0x2459d551, 0x28204fbb, 0x2bfe1808, }, +}; + +/* pow(j,4/3) for j=16..63, Q23 format */ +const int pow43[48] PROGMEM = { + 0x1428a2fa, 0x15db1bd6, 0x1796302c, 0x19598d85, 0x1b24e8bb, 0x1cf7fcfa, + 0x1ed28af2, 0x20b4582a, 0x229d2e6e, 0x248cdb55, 0x26832fda, 0x28800000, + 0x2a832287, 0x2c8c70a8, 0x2e9bc5d8, 0x30b0ff99, 0x32cbfd4a, 0x34eca001, + 0x3712ca62, 0x393e6088, 0x3b6f47e0, 0x3da56717, 0x3fe0a5fc, 0x4220ed72, + 0x44662758, 0x46b03e7c, 0x48ff1e87, 0x4b52b3f3, 0x4daaebfd, 0x5007b497, + 0x5268fc62, 0x54ceb29c, 0x5738c721, 0x59a72a59, 0x5c19cd35, 0x5e90a129, + 0x610b9821, 0x638aa47f, 0x660db90f, 0x6894c90b, 0x6b1fc80c, 0x6daeaa0d, + 0x70416360, 0x72d7e8b0, 0x75722ef9, 0x78102b85, 0x7ab1d3ec, 0x7d571e09, +}; + +const uint32_t polyCoef[264] PROGMEM = { + /* shuffled vs. original from 0, 1, ... 15 to 0, 15, 2, 13, ... 14, 1 */ + 0x00000000, 0x00000074, 0x00000354, 0x0000072c, 0x00001fd4, 0x00005084, 0x000066b8, 0x000249c4, + 0x00049478, 0xfffdb63c, 0x000066b8, 0xffffaf7c, 0x00001fd4, 0xfffff8d4, 0x00000354, 0xffffff8c, + 0xfffffffc, 0x00000068, 0x00000368, 0x00000644, 0x00001f40, 0x00004ad0, 0x00005d1c, 0x00022ce0, + 0x000493c0, 0xfffd9960, 0x00006f78, 0xffffa9cc, 0x0000203c, 0xfffff7e4, 0x00000340, 0xffffff84, + 0xfffffffc, 0x00000060, 0x00000378, 0x0000056c, 0x00001e80, 0x00004524, 0x000052a0, 0x00020ffc, + 0x000491a0, 0xfffd7ca0, 0x00007760, 0xffffa424, 0x00002080, 0xfffff6ec, 0x00000328, 0xffffff74, + 0xfffffffc, 0x00000054, 0x00000384, 0x00000498, 0x00001d94, 0x00003f7c, 0x00004744, 0x0001f32c, + 0x00048e18, 0xfffd6008, 0x00007e70, 0xffff9e8c, 0x0000209c, 0xfffff5ec, 0x00000310, 0xffffff68, + 0xfffffffc, 0x0000004c, 0x0000038c, 0x000003d0, 0x00001c78, 0x000039e4, 0x00003b00, 0x0001d680, + 0x00048924, 0xfffd43ac, 0x000084b0, 0xffff990c, 0x00002094, 0xfffff4e4, 0x000002f8, 0xffffff5c, + 0xfffffffc, 0x00000044, 0x00000390, 0x00000314, 0x00001b2c, 0x0000345c, 0x00002ddc, 0x0001ba04, + 0x000482d0, 0xfffd279c, 0x00008a20, 0xffff93a4, 0x0000206c, 0xfffff3d4, 0x000002dc, 0xffffff4c, + 0xfffffffc, 0x00000040, 0x00000390, 0x00000264, 0x000019b0, 0x00002ef0, 0x00001fd4, 0x00019dc8, + 0x00047b1c, 0xfffd0be8, 0x00008ecc, 0xffff8e64, 0x00002024, 0xfffff2c0, 0x000002c0, 0xffffff3c, + 0xfffffff8, 0x00000038, 0x0000038c, 0x000001bc, 0x000017fc, 0x0000299c, 0x000010e8, 0x000181d8, + 0x0004720c, 0xfffcf09c, 0x000092b4, 0xffff894c, 0x00001fc0, 0xfffff1a4, 0x000002a4, 0xffffff2c, + 0xfffffff8, 0x00000034, 0x00000380, 0x00000120, 0x00001618, 0x00002468, 0x00000118, 0x00016644, + 0x000467a4, 0xfffcd5cc, 0x000095e0, 0xffff8468, 0x00001f44, 0xfffff084, 0x00000284, 0xffffff18, + 0xfffffff8, 0x0000002c, 0x00000374, 0x00000090, 0x00001400, 0x00001f58, 0xfffff068, 0x00014b14, + 0x00045bf0, 0xfffcbb88, 0x00009858, 0xffff7fbc, 0x00001ea8, 0xffffef60, 0x00000268, 0xffffff04, + 0xfffffff8, 0x00000028, 0x0000035c, 0x00000008, 0x000011ac, 0x00001a70, 0xffffded8, 0x00013058, + 0x00044ef8, 0xfffca1d8, 0x00009a1c, 0xffff7b54, 0x00001dfc, 0xffffee3c, 0x0000024c, 0xfffffef0, + 0xfffffff4, 0x00000024, 0x00000340, 0xffffff8c, 0x00000f28, 0x000015b0, 0xffffcc70, 0x0001161c, + 0x000440bc, 0xfffc88d8, 0x00009b3c, 0xffff7734, 0x00001d38, 0xffffed18, 0x0000022c, 0xfffffedc, + 0xfffffff4, 0x00000020, 0x00000320, 0xffffff1c, 0x00000c68, 0x0000111c, 0xffffb92c, 0x0000fc6c, + 0x00043150, 0xfffc708c, 0x00009bb8, 0xffff7368, 0x00001c64, 0xffffebf4, 0x00000210, 0xfffffec4, + 0xfffffff0, 0x0000001c, 0x000002f4, 0xfffffeb4, 0x00000974, 0x00000cb8, 0xffffa518, 0x0000e350, + 0x000420b4, 0xfffc5908, 0x00009b9c, 0xffff6ff4, 0x00001b7c, 0xffffead0, 0x000001f4, 0xfffffeac, + 0xfffffff0, 0x0000001c, 0x000002c4, 0xfffffe58, 0x00000648, 0x00000884, 0xffff9038, 0x0000cad0, + 0x00040ef8, 0xfffc425c, 0x00009af0, 0xffff6ce0, 0x00001a88, 0xffffe9b0, 0x000001d4, 0xfffffe94, + 0xffffffec, 0x00000018, 0x0000028c, 0xfffffe04, 0x000002e4, 0x00000480, 0xffff7a90, 0x0000b2fc, + 0x0003fc28, 0xfffc2c90, 0x000099b8, 0xffff6a3c, 0x00001988, 0xffffe898, 0x000001bc, 0xfffffe7c, + 0x000001a0, 0x0000187c, 0x000097fc, 0x0003e84c, 0xffff6424, 0xffffff4c, 0x00000248, 0xffffffec, +}; + +/* format = Q30, range = [0.0981, 1.9976] + * + * n = 16; + * k = 0; + * for(i=0; i<5; i++, n=n/2) { + * for(p=0; pbytePtr = buf; + bsi->iCache = 0; /* 4-byte unsigned int */ + bsi->cachedBits = 0; /* i.e. zero bits in cache */ + bsi->nBytes = nBytes; +} +//---------------------------------------------------------------------------------------------------------------------- +void RefillBitstreamCache(BitStreamInfo_t *bsi) { + int nBytes = bsi->nBytes; + /* optimize for common case, independent of machine endian-ness */ + if (nBytes >= 4) { + bsi->iCache = (*bsi->bytePtr++) << 24; + bsi->iCache |= (*bsi->bytePtr++) << 16; + bsi->iCache |= (*bsi->bytePtr++) << 8; + bsi->iCache |= (*bsi->bytePtr++); + bsi->cachedBits = 32; + bsi->nBytes -= 4; + } else { + bsi->iCache = 0; + while (nBytes--) { + bsi->iCache |= (*bsi->bytePtr++); + bsi->iCache <<= 8; + } + bsi->iCache <<= ((3 - bsi->nBytes) * 8); + bsi->cachedBits = 8 * bsi->nBytes; + bsi->nBytes = 0; + } +} +//---------------------------------------------------------------------------------------------------------------------- +unsigned int GetBits(BitStreamInfo_t *bsi, int nBits) { + unsigned int data, lowBits; + + nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */ + data = bsi->iCache >> (31 - nBits); /* unsigned >> so zero-extend */ + data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */ + bsi->iCache <<= nBits; /* left-justify cache */ + bsi->cachedBits -= nBits; /* how many bits have we drawn from the cache so far */ + if (bsi->cachedBits < 0) {/* if we cross an int boundary, refill the cache */ + lowBits = -bsi->cachedBits; + RefillBitstreamCache(bsi); + data |= bsi->iCache >> (32 - lowBits); /* get the low-order bits */ + bsi->cachedBits -= lowBits; /* how many bits have we drawn from the cache so far */ + bsi->iCache <<= lowBits; /* left-justify cache */ + } + return data; +} +//---------------------------------------------------------------------------------------------------------------------- +int CalcBitsUsed(BitStreamInfo_t *bsi, unsigned char *startBuf, int startOffset){ + int bitsUsed; + bitsUsed = (bsi->bytePtr - startBuf) * 8; + bitsUsed -= bsi->cachedBits; + bitsUsed -= startOffset; + return bitsUsed; +} +//---------------------------------------------------------------------------------------------------------------------- +int CheckPadBit(){ + return (m_FrameHeader->paddingBit ? 1 : 0); +} +//---------------------------------------------------------------------------------------------------------------------- +int UnpackFrameHeader(unsigned char *buf){ + int verIdx; + /* validate pointers and sync word */ + if ((buf[0] & m_SYNCWORDH) != m_SYNCWORDH || (buf[1] & m_SYNCWORDL) != m_SYNCWORDL) return -1; + /* read header fields - use bitmasks instead of GetBits() for speed, since format never varies */ + verIdx = (buf[1] >> 3) & 0x03; + m_MPEGVersion = (MPEGVersion_t) (verIdx == 0 ? MPEG25 : ((verIdx & 0x01) ? MPEG1 : MPEG2)); + m_FrameHeader->layer = 4 - ((buf[1] >> 1) & 0x03); /* easy mapping of index to layer number, 4 = error */ + m_FrameHeader->crc = 1 - ((buf[1] >> 0) & 0x01); + m_FrameHeader->brIdx = (buf[2] >> 4) & 0x0f; + m_FrameHeader->srIdx = (buf[2] >> 2) & 0x03; + m_FrameHeader->paddingBit = (buf[2] >> 1) & 0x01; + m_FrameHeader->privateBit = (buf[2] >> 0) & 0x01; + m_sMode = (StereoMode_t) ((buf[3] >> 6) & 0x03); /* maps to correct enum (see definition) */ + m_FrameHeader->modeExt = (buf[3] >> 4) & 0x03; + m_FrameHeader->copyFlag = (buf[3] >> 3) & 0x01; + m_FrameHeader->origFlag = (buf[3] >> 2) & 0x01; + m_FrameHeader->emphasis = (buf[3] >> 0) & 0x03; + /* check parameters to avoid indexing tables with bad values */ + if (m_FrameHeader->srIdx == 3 || m_FrameHeader->layer == 4 || m_FrameHeader->brIdx == 15) return -1; + /* for readability (we reference sfBandTable many times in decoder) */ + m_SFBandTable = sfBandTable[m_MPEGVersion][m_FrameHeader->srIdx]; + if (m_sMode != Joint) /* just to be safe (dequant, stproc check fh->modeExt) */ + m_FrameHeader->modeExt = 0; + /* init user-accessible data */ + m_MP3DecInfo->nChans = (m_sMode == Mono ? 1 : 2); + m_MP3DecInfo->samprate = samplerateTab[m_MPEGVersion][m_FrameHeader->srIdx]; + m_MP3DecInfo->nGrans = (m_MPEGVersion == MPEG1 ? m_NGRANS_MPEG1 : m_NGRANS_MPEG2); + m_MP3DecInfo->nGranSamps = ((int) samplesPerFrameTab[m_MPEGVersion][m_FrameHeader->layer - 1])/m_MP3DecInfo->nGrans; + m_MP3DecInfo->layer = m_FrameHeader->layer; + + /* get bitrate and nSlots from table, unless brIdx == 0 (free mode) in which case caller must figure it out himself + * question - do we want to overwrite mp3DecInfo->bitrate with 0 each time if it's free mode, and + * copy the pre-calculated actual free bitrate into it in mp3dec.c (according to the spec, + * this shouldn't be necessary, since it should be either all frames free or none free) + */ + if (m_FrameHeader->brIdx) { + m_MP3DecInfo->bitrate=((int) bitrateTab[m_MPEGVersion][m_FrameHeader->layer - 1][m_FrameHeader->brIdx]) * 1000; + /* nSlots = total frame bytes (from table) - sideInfo bytes - header - CRC (if present) + pad (if present) */ + m_MP3DecInfo->nSlots= (int) slotTab[m_MPEGVersion][m_FrameHeader->srIdx][m_FrameHeader->brIdx] + - (int) sideBytesTab[m_MPEGVersion][(m_sMode == Mono ? 0 : 1)] - 4 + - (m_FrameHeader->crc ? 2 : 0) + (m_FrameHeader->paddingBit ? 1 : 0); + } + /* load crc word, if enabled, and return length of frame header (in bytes) */ + if (m_FrameHeader->crc) { + m_FrameHeader->CRCWord = ((int) buf[4] << 8 | (int) buf[5] << 0); + return 6; + } else { + m_FrameHeader->CRCWord = 0; + return 4; + } +} +//---------------------------------------------------------------------------------------------------------------------- +int UnpackSideInfo( unsigned char *buf) { + int gr, ch, bd, nBytes; + BitStreamInfo_t bitStreamInfo, *bsi; + + SideInfoSub_t *sis; + /* validate pointers and sync word */ + bsi = &bitStreamInfo; + if (m_MPEGVersion == MPEG1) { + /* MPEG 1 */ + nBytes=(m_sMode == Mono ? m_SIBYTES_MPEG1_MONO : m_SIBYTES_MPEG1_STEREO); + SetBitstreamPointer(bsi, nBytes, buf); + m_SideInfo->mainDataBegin = GetBits(bsi, 9); + m_SideInfo->privateBits= GetBits(bsi, (m_sMode == Mono ? 5 : 3)); + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) + for (bd = 0; bd < m_MAX_SCFBD; bd++) m_SideInfo->scfsi[ch][bd] = GetBits(bsi, 1); + } else { + /* MPEG 2, MPEG 2.5 */ + nBytes=(m_sMode == Mono ? m_SIBYTES_MPEG2_MONO : m_SIBYTES_MPEG2_STEREO); + SetBitstreamPointer(bsi, nBytes, buf); + m_SideInfo->mainDataBegin = GetBits(bsi, 8); + m_SideInfo->privateBits = GetBits(bsi, (m_sMode == Mono ? 1 : 2)); + } + for (gr = 0; gr < m_MP3DecInfo->nGrans; gr++) { + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + sis = &m_SideInfoSub[gr][ch]; /* side info subblock for this granule, channel */ + sis->part23Length = GetBits(bsi, 12); + sis->nBigvals = GetBits(bsi, 9); + sis->globalGain = GetBits(bsi, 8); + sis->sfCompress = GetBits(bsi, (m_MPEGVersion == MPEG1 ? 4 : 9)); + sis->winSwitchFlag = GetBits(bsi, 1); + if (sis->winSwitchFlag) { + /* this is a start, stop, short, or mixed block */ + sis->blockType = GetBits(bsi, 2); /* 0 = normal, 1 = start, 2 = short, 3 = stop */ + sis->mixedBlock = GetBits(bsi, 1); /* 0 = not mixed, 1 = mixed */ + sis->tableSelect[0] = GetBits(bsi, 5); + sis->tableSelect[1] = GetBits(bsi, 5); + sis->tableSelect[2] = 0; /* unused */ + sis->subBlockGain[0] = GetBits(bsi, 3); + sis->subBlockGain[1] = GetBits(bsi, 3); + sis->subBlockGain[2] = GetBits(bsi, 3); + if (sis->blockType == 0) { + /* this should not be allowed, according to spec */ + sis->nBigvals = 0; + sis->part23Length = 0; + sis->sfCompress = 0; + } else if (sis->blockType == 2 && sis->mixedBlock == 0) { + /* short block, not mixed */ + sis->region0Count = 8; + } else { + /* start, stop, or short-mixed */ + sis->region0Count = 7; + } + sis->region1Count = 20 - sis->region0Count; + } else { + /* this is a normal block */ + sis->blockType = 0; + sis->mixedBlock = 0; + sis->tableSelect[0] = GetBits(bsi, 5); + sis->tableSelect[1] = GetBits(bsi, 5); + sis->tableSelect[2] = GetBits(bsi, 5); + sis->region0Count = GetBits(bsi, 4); + sis->region1Count = GetBits(bsi, 3); + } + sis->preFlag = (m_MPEGVersion == MPEG1 ? GetBits(bsi, 1) : 0); + sis->sfactScale = GetBits(bsi, 1); + sis->count1TableSelect = GetBits(bsi, 1); + } + } + m_MP3DecInfo->mainDataBegin = m_SideInfo->mainDataBegin; /* needed by main decode loop */ + assert(nBytes == CalcBitsUsed(bsi, buf, 0) >> 3); + return nBytes; +} +/*********************************************************************************************************************** + * Function: UnpackSFMPEG1 + * + * Description: unpack MPEG 1 scalefactors from bitstream + * + * Inputs: BitStreamInfo, SideInfoSub, ScaleFactorInfoSub structs for this + * granule/channel + * vector of scfsi flags from side info, length = 4 (MAX_SCFBD) + * index of current granule + * ScaleFactorInfoSub from granule 0 (for granule 1, if scfsi[i] is set, + * then we just replicate the scale factors from granule 0 in the + * i'th set of scalefactor bands) + * + * Outputs: updated BitStreamInfo struct + * scalefactors in sfis (short and/or long arrays, as appropriate) + * + * Return: none + * + * Notes: set order of short blocks to s[band][window] instead of s[window][band] + * so that we index through consectutive memory locations when unpacking + * (make sure dequantizer follows same convention) + * Illegal Intensity Position = 7 (always) for MPEG1 scale factors + **********************************************************************************************************************/ +void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis, + ScaleFactorInfoSub_t *sfis, int *scfsi, int gr, ScaleFactorInfoSub_t *sfisGr0){ + int sfb; + int slen0, slen1; + /* these can be 0, so make sure GetBits(bsi, 0) returns 0 (no >> 32 or anything) */ + slen0 = (int)m_SFLenTab[sis->sfCompress][0]; + slen1 = (int)m_SFLenTab[sis->sfCompress][1]; + if (sis->blockType == 2){ + /* short block, type 2 (implies winSwitchFlag == 1) */ + if (sis->mixedBlock){ + /* do long block portion */ + for(sfb = 0; sfb < 8; sfb++) + sfis->l[sfb]=(char)GetBits(bsi, slen0); + sfb=3; + } + else { + /* all short blocks */ + sfb=0; + } + for ( ; sfb < 6; sfb++){ + sfis->s[sfb][0] = (char)GetBits(bsi, slen0); + sfis->s[sfb][1] = (char)GetBits(bsi, slen0); + sfis->s[sfb][2] = (char)GetBits(bsi, slen0); + } + for ( ; sfb < 12; sfb++) { + sfis->s[sfb][0] = (char)GetBits(bsi, slen1); + sfis->s[sfb][1] = (char)GetBits(bsi, slen1); + sfis->s[sfb][2] = (char)GetBits(bsi, slen1); + } + /* last sf band not transmitted */ + sfis->s[12][0] = sfis->s[12][1] = sfis->s[12][2] = 0; + } + else{ + /* long blocks, type 0, 1, or 3 */ + if(gr == 0) { + /* first granule */ + for (sfb = 0; sfb < 11; sfb++) + sfis->l[sfb] = (char)GetBits(bsi, slen0); + for (sfb = 11; sfb < 21; sfb++) + sfis->l[sfb] = (char)GetBits(bsi, slen1); + return; + } + else{ + /* second granule + * scfsi: 0 = different scalefactors for each granule, + * 1 = copy sf's from granule 0 into granule 1 + * for block type == 2, scfsi is always 0 + */ + sfb = 0; + if(scfsi[0]) for( ; sfb < 6 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb < 6 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen0); + if(scfsi[1]) for( ; sfb <11 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb <11 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen0); + if(scfsi[2]) for( ; sfb <16 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb <16 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen1); + if(scfsi[3]) for( ; sfb <21 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb]; + else for( ; sfb <21 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen1); + } + /* last sf band not transmitted */ + sfis->l[21] = 0; + sfis->l[22] = 0; + } +} +/*********************************************************************************************************************** + * Function: UnpackSFMPEG2 + * + * Description: unpack MPEG 2 scalefactors from bitstream + * + * Inputs: BitStreamInfo, SideInfoSub, ScaleFactorInfoSub structs for this + * granule/channel + * index of current granule and channel + * ScaleFactorInfoSub from this granule + * modeExt field from frame header, to tell whether intensity stereo is on + * ScaleFactorJS struct for storing IIP info used in Dequant() + * + * Outputs: updated BitStreamInfo struct + * scalefactors in sfis (short and/or long arrays, as appropriate) + * updated intensityScale and preFlag flags + * + * Return: none + * + * Notes: Illegal Intensity Position = (2^slen) - 1 for MPEG2 scale factors + **********************************************************************************************************************/ +void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis, + ScaleFactorInfoSub_t *sfis, int gr, int ch, int modeExt, ScaleFactorJS_t *sfjs){ + + int i, sfb, sfcIdx, btIdx, nrIdx;// iipTest; + int slen[4], nr[4]; + int sfCompress, preFlag, intensityScale; + (void)gr; + sfCompress = sis->sfCompress; + preFlag = 0; + intensityScale = 0; + + /* stereo mode bits (1 = on): bit 1 = mid-side on/off, bit 0 = intensity on/off */ + if (! ((modeExt & 0x01) && (ch == 1)) ) { + /* in other words: if ((modeExt & 0x01) == 0 || ch == 0) */ + if (sfCompress < 400) { + /* max slen = floor[(399/16) / 5] = 4 */ + slen[0] = (sfCompress >> 4) / 5; + slen[1]= (sfCompress >> 4) % 5; + slen[2]= (sfCompress & 0x0f) >> 2; + slen[3]= (sfCompress & 0x03); + sfcIdx = 0; + } + else if(sfCompress < 500){ + /* max slen = floor[(99/4) / 5] = 4 */ + sfCompress -= 400; + slen[0] = (sfCompress >> 2) / 5; + slen[1]= (sfCompress >> 2) % 5; + slen[2]= (sfCompress & 0x03); + slen[3]= 0; + sfcIdx = 1; + } + else{ + /* max slen = floor[11/3] = 3 (sfCompress = 9 bits in MPEG2) */ + sfCompress -= 500; + slen[0] = sfCompress / 3; + slen[1] = sfCompress % 3; + slen[2] = slen[3] = 0; + if (sis->mixedBlock) { + /* adjust for long/short mix logic (see comment above in NRTab[] definition) */ + slen[2] = slen[1]; + slen[1] = slen[0]; + } + preFlag = 1; + sfcIdx = 2; + } + } + else{ + /* intensity stereo ch = 1 (right) */ + intensityScale = sfCompress & 0x01; + sfCompress >>= 1; + if (sfCompress < 180) { + /* max slen = floor[35/6] = 5 (from mod 36) */ + slen[0] = (sfCompress / 36); + slen[1] = (sfCompress % 36) / 6; + slen[2] = (sfCompress % 36) % 6; + slen[3] = 0; + sfcIdx = 3; + } + else if (sfCompress < 244){ + /* max slen = floor[63/16] = 3 */ + sfCompress -= 180; + slen[0] = (sfCompress & 0x3f) >> 4; + slen[1] = (sfCompress & 0x0f) >> 2; + slen[2] = (sfCompress & 0x03); + slen[3] = 0; + sfcIdx = 4; + } + else{ + /* max slen = floor[11/3] = 3 (max sfCompress >> 1 = 511/2 = 255) */ + sfCompress -= 244; + slen[0] = (sfCompress / 3); + slen[1] = (sfCompress % 3); + slen[2] = slen[3] = 0; + sfcIdx = 5; + } + } + /* set index based on block type: (0,1,3) --> 0, (2 non-mixed) --> 1, (2 mixed) ---> 2 */ + btIdx = 0; + if (sis->blockType == 2) + btIdx = (sis->mixedBlock ? 2 : 1); + for (i = 0; i < 4; i++) + nr[i] = (int)NRTab[sfcIdx][btIdx][i]; + + /* save intensity stereo scale factor info */ + if( (modeExt & 0x01) && (ch == 1) ) { + for (i = 0; i < 4; i++) { + sfjs->slen[i] = slen[i]; + sfjs->nr[i] = nr[i]; + } + sfjs->intensityScale = intensityScale; + } + sis->preFlag = preFlag; + + /* short blocks */ + if(sis->blockType == 2) { + if(sis->mixedBlock) { + /* do long block portion */ + //iipTest = (1 << slen[0]) - 1; + for (sfb=0; sfb < 6; sfb++) { + sfis->l[sfb] = (char)GetBits(bsi, slen[0]); + } + sfb = 3; /* start sfb for short */ + nrIdx = 1; + } + else{ + /* all short blocks, so start nr, sfb at 0 */ + sfb = 0; + nrIdx = 0; + } + + /* remaining short blocks, sfb just keeps incrementing */ + for( ; nrIdx <= 3; nrIdx++) { + //iipTest = (1 << slen[nrIdx]) - 1; + for(i=0; i < nr[nrIdx]; i++, sfb++) { + sfis->s[sfb][0] = (char)GetBits(bsi, slen[nrIdx]); + sfis->s[sfb][1] = (char)GetBits(bsi, slen[nrIdx]); + sfis->s[sfb][2] = (char)GetBits(bsi, slen[nrIdx]); + } + } + /* last sf band not transmitted */ + sfis->s[12][0] = sfis->s[12][1] = sfis->s[12][2] = 0; + } + else{ + /* long blocks */ + sfb = 0; + for (nrIdx = 0; nrIdx <= 3; nrIdx++) { + //iipTest = (1 << slen[nrIdx]) - 1; + for(i=0; i < nr[nrIdx]; i++, sfb++) { + sfis->l[sfb] = (char)GetBits(bsi, slen[nrIdx]); + } + } + /* last sf band not transmitted */ + sfis->l[21] = sfis->l[22] = 0; + + } +} +/*********************************************************************************************************************** + * Function: UnpackScaleFactors + * + * Description: parse the fields of the MP3 scale factor data section + * + * Inputs: MP3DecInfo structure filled by UnpackFrameHeader() and UnpackSideInfo() + * buffer pointing to the MP3 scale factor data + * pointer to bit offset (0-7) indicating starting bit in buf[0] + * number of bits available in data buffer + * index of current granule and channel + * + * Outputs: updated platform-specific ScaleFactorInfo struct + * updated bitOffset + * + * Return: length (in bytes) of scale factor data, -1 if null input pointers + **********************************************************************************************************************/ +int UnpackScaleFactors( unsigned char *buf, int *bitOffset, int bitsAvail, int gr, int ch){ + int bitsUsed; + unsigned char *startBuf; + BitStreamInfo_t bitStreamInfo, *bsi; + + /* init GetBits reader */ + startBuf = buf; + bsi = &bitStreamInfo; + SetBitstreamPointer(bsi, (bitsAvail + *bitOffset + 7) / 8, buf); + if (*bitOffset) + GetBits(bsi, *bitOffset); + + if (m_MPEGVersion == MPEG1) + UnpackSFMPEG1(bsi, &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], + m_SideInfo->scfsi[ch], gr, &m_ScaleFactorInfoSub[0][ch]); + else + UnpackSFMPEG2(bsi, &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], + gr, ch, m_FrameHeader->modeExt, m_ScaleFactorJS); + + m_MP3DecInfo->part23Length[gr][ch] = m_SideInfoSub[gr][ch].part23Length; + + bitsUsed = CalcBitsUsed(bsi, buf, *bitOffset); + buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + + return (buf - startBuf); +} +/*********************************************************************************************************************** + * M P 3 D E C + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: MP3FindSyncWord + * + * Description: locate the next byte-alinged sync word in the raw mp3 stream + * + * Inputs: buffer to search for sync word + * max number of bytes to search in buffer + * + * Outputs: none + * + * Return: offset to first sync word (bytes from start of buf) + * -1 if sync not found after searching nBytes + **********************************************************************************************************************/ +int MP3FindSyncWord(unsigned char *buf, int nBytes) { + int i; + + /* find byte-aligned syncword - need 12 (MPEG 1,2) or 11 (MPEG 2.5) matching bits */ + for (i = 0; i < nBytes - 1; i++) { + if ((buf[i + 0] & m_SYNCWORDH) == m_SYNCWORDH + && (buf[i + 1] & m_SYNCWORDL) == m_SYNCWORDL) + return i; + } + + return -1; +} +/*********************************************************************************************************************** + * Function: MP3FindFreeSync + * + * Description: figure out number of bytes between adjacent sync words in "free" mode + * + * Inputs: buffer to search for next sync word + * the 4-byte frame header starting at the current sync word + * max number of bytes to search in buffer + * + * Outputs: none + * + * Return: offset to next sync word, minus any pad byte (i.e. nSlots) + * -1 if sync not found after searching nBytes + * + * Notes: this checks that the first 22 bits of the next frame header are the + * same as the current frame header, but it's still not foolproof + * (could accidentally find a sequence in the bitstream which + * appears to match but is not actually the next frame header) + * this could be made more error-resilient by checking several frames + * in a row and verifying that nSlots is the same in each case + * since free mode requires CBR (see spec) we generally only call + * this function once (first frame) then store the result (nSlots) + * and just use it from then on + **********************************************************************************************************************/ +int MP3FindFreeSync(unsigned char *buf, unsigned char firstFH[4], int nBytes){ + int offset = 0; + unsigned char *bufPtr = buf; + + /* loop until we either: + * - run out of nBytes (FindMP3SyncWord() returns -1) + * - find the next valid frame header (sync word, version, layer, CRC flag, bitrate, and sample rate + * in next header must match current header) + */ + while (1) { + offset = MP3FindSyncWord(bufPtr, nBytes); + bufPtr += offset; + if (offset < 0) { + return -1; + } else if ((bufPtr[0] == firstFH[0]) && (bufPtr[1] == firstFH[1]) + && ((bufPtr[2] & 0xfc) == (firstFH[2] & 0xfc))) { + /* want to return number of bytes per frame, + * NOT counting the padding byte, so subtract one if padFlag == 1 */ + if ((firstFH[2] >> 1) & 0x01) + bufPtr--; + return bufPtr - buf; + } + bufPtr += 3; + nBytes -= (offset + 3); + }; + + return -1; +} +/*********************************************************************************************************************** + * Function: MP3GetLastFrameInfo + * + * Description: get info about last MP3 frame decoded (number of sampled decoded, + * sample rate, bitrate, etc.) + * + * Inputs: + * + * Outputs: filled-in MP3FrameInfo struct + * + * Return: none + * + * Notes: call this right after calling MP3Decode + **********************************************************************************************************************/ +void MP3GetLastFrameInfo() { + if (m_MP3DecInfo->layer != 3){ + m_MP3FrameInfo->bitrate=0; + m_MP3FrameInfo->nChans=0; + m_MP3FrameInfo->samprate=0; + m_MP3FrameInfo->bitsPerSample=0; + m_MP3FrameInfo->outputSamps=0; + m_MP3FrameInfo->layer=0; + m_MP3FrameInfo->version=0; + } + else{ + m_MP3FrameInfo->bitrate=m_MP3DecInfo->bitrate; + m_MP3FrameInfo->nChans=m_MP3DecInfo->nChans; + m_MP3FrameInfo->samprate=m_MP3DecInfo->samprate; + m_MP3FrameInfo->bitsPerSample=16; + m_MP3FrameInfo->outputSamps=m_MP3DecInfo->nChans + * (int) samplesPerFrameTab[m_MPEGVersion][m_MP3DecInfo->layer-1]; + m_MP3FrameInfo->layer=m_MP3DecInfo->layer; + m_MP3FrameInfo->version=m_MPEGVersion; + } +} +int MP3GetSampRate(){return m_MP3FrameInfo->samprate;} +int MP3GetChannels(){return m_MP3FrameInfo->nChans;} +int MP3GetBitsPerSample(){return m_MP3FrameInfo->bitsPerSample;} +int MP3GetBitrate(){return m_MP3FrameInfo->bitrate;} +int MP3GetOutputSamps(){return m_MP3FrameInfo->outputSamps;} +/*********************************************************************************************************************** + * Function: MP3GetNextFrameInfo + * + * Description: parse MP3 frame header + * + * Inputs: pointer to buffer containing valid MP3 frame header (located using + * MP3FindSyncWord(), above) + * + * Outputs: filled-in MP3FrameInfo struct + * + * Return: error code, defined in mp3dec.h (0 means no error, < 0 means error) + **********************************************************************************************************************/ +int MP3GetNextFrameInfo(unsigned char *buf) { + + if (UnpackFrameHeader( buf) == -1 || m_MP3DecInfo->layer != 3) + return ERR_MP3_INVALID_FRAMEHEADER; + + MP3GetLastFrameInfo(); + + return ERR_MP3_NONE; +} +/*********************************************************************************************************************** + * Function: MP3ClearBadFrame + * + * Description: zero out pcm buffer if error decoding MP3 frame + * + * Inputs: mp3DecInfo struct with correct frame size parameters filled in + * pointer pcm output buffer + * + * Outputs: zeroed out pcm buffer + * + * Return: none + **********************************************************************************************************************/ +void MP3ClearBadFrame( short *outbuf) { + int i; + for (i = 0; i < m_MP3DecInfo->nGrans * m_MP3DecInfo->nGranSamps * m_MP3DecInfo->nChans; i++) + outbuf[i] = 0; +} +/*********************************************************************************************************************** + * Function: MP3Decode + * + * Description: decode one frame of MP3 data + * + * Inputs: number of valid bytes remaining in inbuf + * pointer to outbuf, big enough to hold one frame of decoded PCM samples + * flag indicating whether MP3 data is normal MPEG format (useSize = 0) + * or reformatted as "self-contained" frames (useSize = 1) + * + * Outputs: PCM data in outbuf, interleaved LRLRLR... if stereo + * number of output samples = nGrans * nGranSamps * nChans + * updated inbuf pointer, updated bytesLeft + * + * Return: error code, defined in mp3dec.h (0 means no error, < 0 means error) + * + * Notes: switching useSize on and off between frames in the same stream + * is not supported (bit reservoir is not maintained if useSize on) + **********************************************************************************************************************/ +int MP3Decode( unsigned char *inbuf, int *bytesLeft, short *outbuf, int useSize){ + int offset, bitOffset, mainBits, gr, ch, fhBytes, siBytes, freeFrameBytes; + int prevBitOffset, sfBlockBits, huffBlockBits; + unsigned char *mainPtr; + + /* unpack frame header */ + fhBytes = UnpackFrameHeader(inbuf); + if (fhBytes < 0) + return ERR_MP3_INVALID_FRAMEHEADER; /* don't clear outbuf since we don't know size (failed to parse header) */ + inbuf += fhBytes; + /* unpack side info */ + siBytes = UnpackSideInfo( inbuf); + if (siBytes < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_SIDEINFO; + } + inbuf += siBytes; + *bytesLeft -= (fhBytes + siBytes); + + /* if free mode, need to calculate bitrate and nSlots manually, based on frame size */ + if (m_MP3DecInfo->bitrate == 0 || m_MP3DecInfo->freeBitrateFlag) { + if(!m_MP3DecInfo->freeBitrateFlag){ + /* first time through, need to scan for next sync word and figure out frame size */ + m_MP3DecInfo->freeBitrateFlag=1; + m_MP3DecInfo->freeBitrateSlots=MP3FindFreeSync(inbuf, inbuf - fhBytes - siBytes, *bytesLeft); + if(m_MP3DecInfo->freeBitrateSlots < 0){ + MP3ClearBadFrame(outbuf); + m_MP3DecInfo->freeBitrateFlag = 0; + return ERR_MP3_FREE_BITRATE_SYNC; + } + freeFrameBytes=m_MP3DecInfo->freeBitrateSlots + fhBytes + siBytes; + m_MP3DecInfo->bitrate=(freeFrameBytes * m_MP3DecInfo->samprate * 8) + / (m_MP3DecInfo->nGrans * m_MP3DecInfo->nGranSamps); + } + m_MP3DecInfo->nSlots = m_MP3DecInfo->freeBitrateSlots + CheckPadBit(); /* add pad byte, if required */ + } + + /* useSize != 0 means we're getting reformatted (RTP) packets (see RFC 3119) + * - calling function assembles "self-contained" MP3 frames by shifting any main_data + * from the bit reservoir (in previous frames) to AFTER the sync word and side info + * - calling function should set mainDataBegin to 0, and tell us exactly how large this + * frame is (in bytesLeft) + */ + if (useSize) { + m_MP3DecInfo->nSlots = *bytesLeft; + if (m_MP3DecInfo->mainDataBegin != 0 || m_MP3DecInfo->nSlots <= 0) { + /* error - non self-contained frame, or missing frame (size <= 0), could do loss concealment here */ + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_FRAMEHEADER; + } + + /* can operate in-place on reformatted frames */ + m_MP3DecInfo->mainDataBytes = m_MP3DecInfo->nSlots; + mainPtr = inbuf; + inbuf += m_MP3DecInfo->nSlots; + *bytesLeft -= (m_MP3DecInfo->nSlots); + } else { + /* out of data - assume last or truncated frame */ + if (m_MP3DecInfo->nSlots > *bytesLeft) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INDATA_UNDERFLOW; + } + /* fill main data buffer with enough new data for this frame */ + if (m_MP3DecInfo->mainDataBytes >= m_MP3DecInfo->mainDataBegin) { + /* adequate "old" main data available (i.e. bit reservoir) */ + memmove(m_MP3DecInfo->mainBuf, + m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBytes - m_MP3DecInfo->mainDataBegin, + m_MP3DecInfo->mainDataBegin); + memcpy (m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBegin, inbuf, + m_MP3DecInfo->nSlots); + + m_MP3DecInfo->mainDataBytes = m_MP3DecInfo->mainDataBegin + m_MP3DecInfo->nSlots; + inbuf += m_MP3DecInfo->nSlots; + *bytesLeft -= (m_MP3DecInfo->nSlots); + mainPtr = m_MP3DecInfo->mainBuf; + } else { + /* not enough data in bit reservoir from previous frames (perhaps starting in middle of file) */ + memcpy(m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBytes, inbuf, m_MP3DecInfo->nSlots); + m_MP3DecInfo->mainDataBytes += m_MP3DecInfo->nSlots; + inbuf += m_MP3DecInfo->nSlots; + *bytesLeft -= (m_MP3DecInfo->nSlots); + MP3ClearBadFrame( outbuf); + return ERR_MP3_MAINDATA_UNDERFLOW; + } + } + bitOffset = 0; + mainBits = m_MP3DecInfo->mainDataBytes * 8; + + /* decode one complete frame */ + for (gr = 0; gr < m_MP3DecInfo->nGrans; gr++) { + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + /* unpack scale factors and compute size of scale factor block */ + prevBitOffset = bitOffset; + offset = UnpackScaleFactors( mainPtr, &bitOffset, + mainBits, gr, ch); + sfBlockBits = 8 * offset - prevBitOffset + bitOffset; + huffBlockBits = m_MP3DecInfo->part23Length[gr][ch] - sfBlockBits; + mainPtr += offset; + mainBits -= sfBlockBits; + + if (offset < 0 || mainBits < huffBlockBits) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_SCALEFACT; + } + /* decode Huffman code words */ + prevBitOffset = bitOffset; + offset = DecodeHuffman( mainPtr, &bitOffset, huffBlockBits, gr, ch); + if (offset < 0) { + MP3ClearBadFrame( outbuf); + return ERR_MP3_INVALID_HUFFCODES; + } + mainPtr += offset; + mainBits -= (8 * offset - prevBitOffset + bitOffset); + } + /* dequantize coefficients, decode stereo, reorder short blocks */ + if (MP3Dequantize( gr) < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_DEQUANTIZE; + } + + /* alias reduction, inverse MDCT, overlap-add, frequency inversion */ + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + if (IMDCT( gr, ch) < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_IMDCT; + } + } + /* subband transform - if stereo, interleaves pcm LRLRLR */ + if (Subband( + outbuf + gr * m_MP3DecInfo->nGranSamps * m_MP3DecInfo->nChans) + < 0) { + MP3ClearBadFrame(outbuf); + return ERR_MP3_INVALID_SUBBAND; + } + } + MP3GetLastFrameInfo(); + return ERR_MP3_NONE; +} + +/*********************************************************************************************************************** + * Function: MP3Decoder_ClearBuffer + * + * Description: clear all the memory needed for the MP3 decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: none + * + * Notes: if one or more mallocs fail, function frees any buffers already + * allocated before returning + **********************************************************************************************************************/ +void MP3Decoder_ClearBuffer(void) { + + /* important to do this - DSP primitives assume a bunch of state variables are 0 on first use */ + memset( m_MP3DecInfo, 0, sizeof(MP3DecInfo_t)); //Clear MP3DecInfo + memset(&m_ScaleFactorInfoSub, 0, sizeof(ScaleFactorInfoSub_t)*(m_MAX_NGRAN *m_MAX_NCHAN)); //Clear ScaleFactorInfo + memset( m_SideInfo, 0, sizeof(SideInfo_t)); //Clear SideInfo + memset( m_FrameHeader, 0, sizeof(FrameHeader_t)); //Clear FrameHeader + memset( m_HuffmanInfo, 0, sizeof(HuffmanInfo_t)); //Clear HuffmanInfo + memset( m_DequantInfo, 0, sizeof(DequantInfo_t)); //Clear DequantInfo + memset( m_IMDCTInfo, 0, sizeof(IMDCTInfo_t)); //Clear IMDCTInfo + memset( m_SubbandInfo, 0, sizeof(SubbandInfo_t)); //Clear SubbandInfo + memset(&m_CriticalBandInfo, 0, sizeof(CriticalBandInfo_t)*m_MAX_NCHAN); //Clear CriticalBandInfo + memset( m_ScaleFactorJS, 0, sizeof(ScaleFactorJS_t)); //Clear ScaleFactorJS + memset(&m_SideInfoSub, 0, sizeof(SideInfoSub_t)*(m_MAX_NGRAN *m_MAX_NCHAN)); //Clear SideInfoSub + memset(&m_SFBandTable, 0, sizeof(SFBandTable_t)); //Clear SFBandTable + memset( m_MP3FrameInfo, 0, sizeof(MP3FrameInfo_t)); //Clear MP3FrameInfo + + return; + +} +/*********************************************************************************************************************** + * Function: MP3Decoder_AllocateBuffers + * + * Description: allocate all the memory needed for the MP3 decoder + * + * Inputs: none + * + * Outputs: none + * + * Return: pointer to MP3DecInfo structure (initialized with pointers to all + * the internal buffers needed for decoding) + * + **********************************************************************************************************************/ +bool MP3Decoder_AllocateBuffers(void) { + + // try first SRAM because its faster than PSRAM + + + if(!m_MP3DecInfo) {m_MP3DecInfo = (MP3DecInfo_t*) malloc(sizeof(MP3DecInfo_t) );} + if(!m_FrameHeader) {m_FrameHeader = (FrameHeader_t*) malloc(sizeof(FrameHeader_t) );} + if(!m_SideInfo) {m_SideInfo = (SideInfo_t*) malloc(sizeof(SideInfo_t) );} + if(!m_ScaleFactorJS) {m_ScaleFactorJS = (ScaleFactorJS_t*) malloc(sizeof(ScaleFactorJS_t));} + if(!m_HuffmanInfo) {m_HuffmanInfo = (HuffmanInfo_t*) malloc(sizeof(HuffmanInfo_t) );} + if(!m_DequantInfo) {m_DequantInfo = (DequantInfo_t*) malloc(sizeof(DequantInfo_t) );} + if(!m_IMDCTInfo) {m_IMDCTInfo = (IMDCTInfo_t*) malloc(sizeof(IMDCTInfo_t) );} + if(!m_SubbandInfo) {m_SubbandInfo = (SubbandInfo_t*) malloc(sizeof(SubbandInfo_t) );} + if(!m_MP3FrameInfo) {m_MP3FrameInfo = (MP3FrameInfo_t*) malloc(sizeof(MP3FrameInfo_t) );} + + if(!m_MP3DecInfo || !m_FrameHeader || !m_SideInfo || !m_ScaleFactorJS || !m_HuffmanInfo || + !m_DequantInfo || !m_IMDCTInfo || !m_SubbandInfo || !m_MP3FrameInfo) { + log_i("heap is too small, try PSRAM"); + MP3Decoder_FreeBuffers(); + } + else{ + MP3Decoder_ClearBuffer(); + return true; // success, all buffers allocated in SRAM (Heap) + } + + if(psramFound()) { + // PSRAM found, Buffer will be allocated in PSRAM + if(!m_MP3DecInfo) {m_MP3DecInfo = (MP3DecInfo_t*) ps_calloc(sizeof(MP3DecInfo_t) , sizeof(uint8_t));} + if(!m_FrameHeader) {m_FrameHeader = (FrameHeader_t*) ps_calloc(sizeof(FrameHeader_t) , sizeof(uint8_t));} + if(!m_SideInfo) {m_SideInfo = (SideInfo_t*) ps_calloc(sizeof(SideInfo_t) , sizeof(uint8_t));} + if(!m_ScaleFactorJS) {m_ScaleFactorJS = (ScaleFactorJS_t*) ps_calloc(sizeof(ScaleFactorJS_t), sizeof(uint8_t));} + if(!m_HuffmanInfo) {m_HuffmanInfo = (HuffmanInfo_t*) ps_calloc(sizeof(HuffmanInfo_t) , sizeof(uint8_t));} + if(!m_DequantInfo) {m_DequantInfo = (DequantInfo_t*) ps_calloc(sizeof(DequantInfo_t) , sizeof(uint8_t));} + if(!m_IMDCTInfo) {m_IMDCTInfo = (IMDCTInfo_t*) ps_calloc(sizeof(IMDCTInfo_t) , sizeof(uint8_t));} + if(!m_SubbandInfo) {m_SubbandInfo = (SubbandInfo_t*) ps_calloc(sizeof(SubbandInfo_t) , sizeof(uint8_t));} + if(!m_MP3FrameInfo) {m_MP3FrameInfo = (MP3FrameInfo_t*) ps_calloc(sizeof(MP3FrameInfo_t) , sizeof(uint8_t));} + } + else { + log_e("not enough memory to allocate mp3decoder buffers"); + return false; + } + + if(!m_MP3DecInfo || !m_FrameHeader || !m_SideInfo || !m_ScaleFactorJS || !m_HuffmanInfo || + !m_DequantInfo || !m_IMDCTInfo || !m_SubbandInfo || !m_MP3FrameInfo) { + log_e("not enough memory to allocate mp3decoder buffers in PSRAM"); + return false; + } + MP3Decoder_ClearBuffer(); + return true; +} +/*********************************************************************************************************************** + * Function: MP3Decoder_FreeBuffers + * + * Description: frees all the memory used by the MP3 decoder + * + * Inputs: pointer to initialized MP3DecInfo structure + * + * Outputs: none + * + * Return: none + * + * Notes: safe to call even if some buffers were not allocated + **********************************************************************************************************************/ +void MP3Decoder_FreeBuffers() +{ +// uint32_t i = ESP.getFreeHeap(); + + if(m_MP3DecInfo) {free(m_MP3DecInfo); m_MP3DecInfo=NULL;} + if(m_FrameHeader) {free(m_FrameHeader); m_FrameHeader=NULL;} + if(m_SideInfo) {free(m_SideInfo); m_SideInfo=NULL;} + if(m_ScaleFactorJS ) {free(m_ScaleFactorJS); m_ScaleFactorJS=NULL;} + if(m_HuffmanInfo) {free(m_HuffmanInfo); m_HuffmanInfo=NULL;} + if(m_DequantInfo) {free(m_DequantInfo); m_DequantInfo=0;} + if(m_IMDCTInfo) {free(m_IMDCTInfo); m_IMDCTInfo=0;} + if(m_SubbandInfo) {free(m_SubbandInfo); m_SubbandInfo=0;} + if(m_MP3FrameInfo) {free(m_MP3FrameInfo); m_MP3FrameInfo=0;} + +// log_i("MP3Decoder: %lu bytes memory was freed", ESP.getFreeHeap() - i); +} + +/*********************************************************************************************************************** + * H U F F M A N N + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: DecodeHuffmanPairs + * + * Description: decode 2-way vector Huffman codes in the "bigValues" region of spectrum + * + * Inputs: valid BitStreamInfo struct, pointing to start of pair-wise codes + * pointer to xy buffer to received decoded values + * number of codewords to decode + * index of Huffman table to use + * number of bits remaining in bitstream + * + * Outputs: pairs of decoded coefficients in vwxy + * updated BitStreamInfo struct + * + * Return: number of bits used, or -1 if out of bits + * + * Notes: assumes that nVals is an even number + * si_huff.bit tests every Huffman codeword in every table (though not + * necessarily all linBits outputs for x,y > 15) + **********************************************************************************************************************/ +// no improvement with section=data +int DecodeHuffmanPairs(int *xy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset){ + int i, x, y; + int cachedBits, padBits, len, startBits, linBits, maxBits, minBits; + HuffTabType_t tabType; + unsigned short cw, *tBase, *tCurr; + unsigned int cache; + + if (nVals <= 0) + return 0; + + if (bitsLeft < 0) + return -1; + startBits = bitsLeft; + + tBase = (unsigned short *) (huffTable + huffTabOffset[tabIdx]); + linBits = huffTabLookup[tabIdx].linBits; + tabType = (HuffTabType_t)huffTabLookup[tabIdx].tabType; + +// assert(!(nVals & 0x01)); +// assert(tabIdx < m_HUFF_PAIRTABS); +// assert(tabIdx >= 0); +// assert(tabType != invalidTab); + + if((nVals & 0x01)){log_i("assert(!(nVals & 0x01))"); return -1;} + if(!(tabIdx < m_HUFF_PAIRTABS)){log_i("assert(tabIdx < m_HUFF_PAIRTABS)"); return -1;} + if(!(tabIdx >= 0)){log_i("(tabIdx >= 0)"); return -1;} + if(!(tabType != invalidTab)){log_i("(tabType != invalidTab)"); return -1;} + + + /* initially fill cache with any partial byte */ + cache = 0; + cachedBits = (8 - bitOffset) & 0x07; + if (cachedBits) + cache = (unsigned int) (*buf++) << (32 - cachedBits); + bitsLeft -= cachedBits; + + if (tabType == noBits) { + /* table 0, no data, x = y = 0 */ + for (i = 0; i < nVals; i += 2) { + xy[i + 0] = 0; + xy[i + 1] = 0; + } + return 0; + } else if (tabType == oneShot) { + /* single lookup, no escapes */ + + maxBits = (int)( (((unsigned short)(pgm_read_word(&tBase[0])) >> 0) & 0x000f)); + tBase++; + padBits = 0; + while (nVals > 0) { + /* refill cache - assumes cachedBits <= 16 */ + if (bitsLeft >= 16) { + /* load 2 new bytes into left-justified cache */ + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += 16; + bitsLeft -= 16; + } else { + /* last time through, pad cache with zeros and drain cache */ + if (cachedBits + bitsLeft <= 0) + return -1; + if (bitsLeft > 0) + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + if (bitsLeft > 8) + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += bitsLeft; + bitsLeft = 0; + + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + padBits = 11; + cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */ + } + + /* largest maxBits = 9, plus 2 for sign bits, so make sure cache has at least 11 bits */ + while (nVals > 0 && cachedBits >= 11) { + cw = pgm_read_word(&tBase[cache >> (32 - maxBits)]); + + len=(int)( (((unsigned short)(cw)) >> 12) & 0x000f); + cachedBits -= len; + cache <<= len; + + x=(int)( (((unsigned short)(cw)) >> 4) & 0x000f); + if (x) { + (x) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + + + y=(int)( (((unsigned short)(cw)) >> 8) & 0x000f); + if (y) { + (y) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + /* ran out of bits - should never have consumed padBits */ + if (cachedBits < padBits) + return -1; + + *xy++ = x; + *xy++ = y; + nVals -= 2; + } + } + bitsLeft += (cachedBits - padBits); + return (startBits - bitsLeft); + } else if (tabType == loopLinbits || tabType == loopNoLinbits) { + tCurr = tBase; + padBits = 0; + while (nVals > 0) { + /* refill cache - assumes cachedBits <= 16 */ + if (bitsLeft >= 16) { + /* load 2 new bytes into left-justified cache */ + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += 16; + bitsLeft -= 16; + } else { + /* last time through, pad cache with zeros and drain cache */ + if (cachedBits + bitsLeft <= 0) + return -1; + if (bitsLeft > 0) + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + if (bitsLeft > 8) + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += bitsLeft; + bitsLeft = 0; + + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + padBits = 11; + cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */ + } + + /* largest maxBits = 9, plus 2 for sign bits, so make sure cache has at least 11 bits */ + while (nVals > 0 && cachedBits >= 11) { + maxBits = (int)( (((unsigned short)(pgm_read_word(&tCurr[0]))) >> 0) & 0x000f); + cw = pgm_read_word(&tCurr[(cache >> (32 - maxBits)) + 1]); + len=(int)( (((unsigned short)(cw)) >> 12) & 0x000f); + if (!len) { + cachedBits -= maxBits; + cache <<= maxBits; + tCurr += cw; + continue; + } + cachedBits -= len; + cache <<= len; + + x=(int)( (((unsigned short)(cw)) >> 4) & 0x000f); + y=(int)( (((unsigned short)(cw)) >> 8) & 0x000f); + + if (x == 15 && tabType == loopLinbits) { + minBits = linBits + 1 + (y ? 1 : 0); + if (cachedBits + bitsLeft < minBits) + return -1; + while (cachedBits < minBits) { + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cachedBits += 8; + bitsLeft -= 8; + } + if (bitsLeft < 0) { + cachedBits += bitsLeft; + bitsLeft = 0; + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + } + x += (int) (cache >> (32 - linBits)); + cachedBits -= linBits; + cache <<= linBits; + } + if (x) { + (x) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + if (y == 15 && tabType == loopLinbits) { + minBits = linBits + 1; + if (cachedBits + bitsLeft < minBits) + return -1; + while (cachedBits < minBits) { + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cachedBits += 8; + bitsLeft -= 8; + } + if (bitsLeft < 0) { + cachedBits += bitsLeft; + bitsLeft = 0; + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + } + y += (int) (cache >> (32 - linBits)); + cachedBits -= linBits; + cache <<= linBits; + } + if (y) { + (y) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + /* ran out of bits - should never have consumed padBits */ + if (cachedBits < padBits) + return -1; + + *xy++ = x; + *xy++ = y; + nVals -= 2; + tCurr = tBase; + } + } + bitsLeft += (cachedBits - padBits); + return (startBits - bitsLeft); + } + + /* error in bitstream - trying to access unused Huffman table */ + return -1; +} + +/*********************************************************************************************************************** + * Function: DecodeHuffmanQuads + * + * Description: decode 4-way vector Huffman codes in the "count1" region of spectrum + * + * Inputs: valid BitStreamInfo struct, pointing to start of quadword codes + * pointer to vwxy buffer to received decoded values + * maximum number of codewords to decode + * index of quadword table (0 = table A, 1 = table B) + * number of bits remaining in bitstream + * + * Outputs: quadruples of decoded coefficients in vwxy + * updated BitStreamInfo struct + * + * Return: index of the first "zero_part" value (index of the first sample + * of the quad word after which all samples are 0) + * + * Notes: si_huff.bit tests every vwxy output in both quad tables + **********************************************************************************************************************/ +// no improvement with section=data +int DecodeHuffmanQuads(int *vwxy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset){ + int i, v, w, x, y; + int len, maxBits, cachedBits, padBits; + unsigned int cache; + unsigned char cw, *tBase; + + if(bitsLeft<=0) return 0; + + tBase = (unsigned char *) quadTable + quadTabOffset[tabIdx]; + maxBits = quadTabMaxBits[tabIdx]; + + /* initially fill cache with any partial byte */ + cache = 0; + cachedBits=(8-bitOffset) & 0x07; + if(cachedBits)cache=(unsigned int)(*buf++) << (32 - cachedBits); + bitsLeft -= cachedBits; + + i = padBits = 0; + while (i < (nVals - 3)) { + /* refill cache - assumes cachedBits <= 16 */ + if (bitsLeft >= 16) { + /* load 2 new bytes into left-justified cache */ + cache |= (unsigned int) (*buf++) << (24 - cachedBits); + cache |= (unsigned int) (*buf++) << (16 - cachedBits); + cachedBits += 16; + bitsLeft -= 16; + } else { + /* last time through, pad cache with zeros and drain cache */ + if(cachedBits+bitsLeft <= 0) return i; + if(bitsLeft>0) cache |= (unsigned int)(*buf++)<<(24-cachedBits); + if (bitsLeft > 8) cache |= (unsigned int)(*buf++)<<(16 - cachedBits); + cachedBits += bitsLeft; + bitsLeft = 0; + + cache &= (signed int) 0x80000000 >> (cachedBits - 1); + padBits = 10; + cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */ + } + + /* largest maxBits = 6, plus 4 for sign bits, so make sure cache has at least 10 bits */ + while(i < (nVals - 3) && cachedBits >= 10){ + cw = pgm_read_byte(&tBase[cache >> (32 - maxBits)]); + len=(int)( (((unsigned char)(cw)) >> 4) & 0x0f); + cachedBits -= len; + cache <<= len; + + v=(int)( (((unsigned char)(cw)) >> 3) & 0x01); + if (v) { + (v) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + w=(int)( (((unsigned char)(cw)) >> 2) & 0x01); + if (w) { + (w) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + x=(int)( (((unsigned char)(cw)) >> 1) & 0x01); + if (x) { + (x) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + y=(int)( (((unsigned char)(cw)) >> 0) & 0x01); + if (y) { + (y) |= ((cache) & 0x80000000); + cache <<= 1; + cachedBits--; + } + + /* ran out of bits - okay (means we're done) */ + if (cachedBits < padBits) + return i; + + *vwxy++ = v; + *vwxy++ = w; + *vwxy++ = x; + *vwxy++ = y; + i += 4; + } + } + + /* decoded max number of quad values */ + return i; +} + +/*********************************************************************************************************************** + * Function: DecodeHuffman + * + * Description: decode one granule, one channel worth of Huffman codes + * + * Inputs: MP3DecInfo structure filled by UnpackFrameHeader(), UnpackSideInfo(), + * and UnpackScaleFactors() (for this granule) + * buffer pointing to start of Huffman data in MP3 frame + * pointer to bit offset (0-7) indicating starting bit in buf[0] + * number of bits in the Huffman data section of the frame + * (could include padding bits) + * index of current granule and channel + * + * Outputs: decoded coefficients in hi->huffDecBuf[ch] (hi pointer in mp3DecInfo) + * updated bitOffset + * + * Return: length (in bytes) of Huffman codes + * bitOffset also returned in parameter (0 = MSB, 7 = LSB of + * byte located at buf + offset) + * -1 if null input pointers, huffBlockBits < 0, or decoder runs + * out of bits prematurely (invalid bitstream) + **********************************************************************************************************************/ +// .data about 1ms faster per frame +int DecodeHuffman(unsigned char *buf, int *bitOffset, int huffBlockBits, int gr, int ch){ + + int r1Start, r2Start, rEnd[4]; /* region boundaries */ + int i, w, bitsUsed, bitsLeft; + unsigned char *startBuf = buf; + + SideInfoSub_t *sis; + sis = &m_SideInfoSub[gr][ch]; + //hi = (HuffmanInfo_t*) (m_MP3DecInfo->HuffmanInfoPS); + + if (huffBlockBits < 0) + return -1; + + /* figure out region boundaries (the first 2*bigVals coefficients divided into 3 regions) */ + if (sis->winSwitchFlag && sis->blockType == 2) { + if (sis->mixedBlock == 0) { + r1Start = m_SFBandTable.s[(sis->region0Count + 1) / 3] * 3; + } else { + if (m_MPEGVersion == MPEG1) { + r1Start = m_SFBandTable.l[sis->region0Count + 1]; + } else { + /* see MPEG2 spec for explanation */ + w = m_SFBandTable.s[4] - m_SFBandTable.s[3]; + r1Start = m_SFBandTable.l[6] + 2 * w; + } + } + r2Start = m_MAX_NSAMP; /* short blocks don't have region 2 */ + } else { + r1Start = m_SFBandTable.l[sis->region0Count + 1]; + r2Start = m_SFBandTable.l[sis->region0Count + 1 + sis->region1Count + 1]; + } + + /* offset rEnd index by 1 so first region = rEnd[1] - rEnd[0], etc. */ + rEnd[3] = (m_MAX_NSAMP < (2 * sis->nBigvals) ? m_MAX_NSAMP : (2 * sis->nBigvals)); + rEnd[2] = (r2Start < rEnd[3] ? r2Start : rEnd[3]); + rEnd[1] = (r1Start < rEnd[3] ? r1Start : rEnd[3]); + rEnd[0] = 0; + + /* rounds up to first all-zero pair (we don't check last pair for (x,y) == (non-zero, zero)) */ + m_HuffmanInfo->nonZeroBound[ch] = rEnd[3]; + + /* decode Huffman pairs (rEnd[i] are always even numbers) */ + bitsLeft = huffBlockBits; + for (i = 0; i < 3; i++) { + bitsUsed = DecodeHuffmanPairs(m_HuffmanInfo->huffDecBuf[ch] + rEnd[i], + rEnd[i + 1] - rEnd[i], sis->tableSelect[i], bitsLeft, buf, + *bitOffset); + if (bitsUsed < 0 || bitsUsed > bitsLeft) /* error - overran end of bitstream */ + return -1; + + /* update bitstream position */ + buf += (bitsUsed + *bitOffset) >> 3; + *bitOffset = (bitsUsed + *bitOffset) & 0x07; + bitsLeft -= bitsUsed; + } + + /* decode Huffman quads (if any) */ + m_HuffmanInfo->nonZeroBound[ch] += DecodeHuffmanQuads(m_HuffmanInfo->huffDecBuf[ch] + rEnd[3], + m_MAX_NSAMP - rEnd[3], sis->count1TableSelect, bitsLeft, buf, + *bitOffset); + + assert(m_HuffmanInfo->nonZeroBound[ch] <= m_MAX_NSAMP); + for (i = m_HuffmanInfo->nonZeroBound[ch]; i < m_MAX_NSAMP; i++) + m_HuffmanInfo->huffDecBuf[ch][i] = 0; + + /* If bits used for 576 samples < huffBlockBits, then the extras are considered + * to be stuffing bits (throw away, but need to return correct bitstream position) + */ + buf += (bitsLeft + *bitOffset) >> 3; + *bitOffset = (bitsLeft + *bitOffset) & 0x07; + + return (buf - startBuf); +} + +/*********************************************************************************************************************** + * D E Q U A N T + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: MP3Dequantize + * + * Description: dequantize coefficients, decode stereo, reorder short blocks + * (one granule-worth) + * + * Inputs: index of current granule + * + * Outputs: dequantized and reordered coefficients in hi->huffDecBuf + * (one granule-worth, all channels), format = Q26 + * operates in-place on huffDecBuf but also needs di->workBuf + * updated hi->nonZeroBound index for both channels + * + * Return: 0 on success, -1 if null input pointers + * + * Notes: In calling output Q(DQ_FRACBITS_OUT), we assume an implicit bias + * of 2^15. Some (floating-point) reference implementations factor this + * into the 2^(0.25 * gain) scaling explicitly. But to avoid precision + * loss, we don't do that. Instead take it into account in the final + * round to PCM (>> by 15 less than we otherwise would have). + * Equivalently, we can think of the dequantized coefficients as + * Q(DQ_FRACBITS_OUT - 15) with no implicit bias. + **********************************************************************************************************************/ +int MP3Dequantize(int gr){ + int i, ch, nSamps, mOut[2]; + CriticalBandInfo_t *cbi; + cbi = &m_CriticalBandInfo[0]; + mOut[0] = mOut[1] = 0; + + /* dequantize all the samples in each channel */ + for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) { + m_HuffmanInfo->gb[ch] = DequantChannel(m_HuffmanInfo->huffDecBuf[ch], m_DequantInfo->workBuf, + &m_HuffmanInfo->nonZeroBound[ch], &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], &cbi[ch]); + } + + /* joint stereo processing assumes one guard bit in input samples + * it's extremely rare not to have at least one gb, so if this is the case + * just make a pass over the data and clip to [-2^30+1, 2^30-1] + * in practice this may never happen + */ + if (m_FrameHeader->modeExt && (m_HuffmanInfo->gb[0] < 1 || m_HuffmanInfo->gb[1] < 1)) { + for (i = 0; i < m_HuffmanInfo->nonZeroBound[0]; i++) { + if (m_HuffmanInfo->huffDecBuf[0][i] < -0x3fffffff) m_HuffmanInfo->huffDecBuf[0][i] = -0x3fffffff; + if (m_HuffmanInfo->huffDecBuf[0][i] > 0x3fffffff) m_HuffmanInfo->huffDecBuf[0][i] = 0x3fffffff; + } + for (i = 0; i < m_HuffmanInfo->nonZeroBound[1]; i++) { + if (m_HuffmanInfo->huffDecBuf[1][i] < -0x3fffffff) m_HuffmanInfo->huffDecBuf[1][i] = -0x3fffffff; + if (m_HuffmanInfo->huffDecBuf[1][i] > 0x3fffffff) m_HuffmanInfo->huffDecBuf[1][i] = 0x3fffffff; + } + } + + /* do mid-side stereo processing, if enabled */ + if (m_FrameHeader->modeExt >> 1) { + if (m_FrameHeader->modeExt & 0x01) { + /* intensity stereo enabled - run mid-side up to start of right zero region */ + if (cbi[1].cbType == 0) + nSamps = m_SFBandTable.l[cbi[1].cbEndL + 1]; + else + nSamps = 3 * m_SFBandTable.s[cbi[1].cbEndSMax + 1]; + } else { + /* intensity stereo disabled - run mid-side on whole spectrum */ + nSamps = (m_HuffmanInfo->nonZeroBound[0] > m_HuffmanInfo->nonZeroBound[1] ? + m_HuffmanInfo->nonZeroBound[0] : m_HuffmanInfo->nonZeroBound[1]); + } + MidSideProc(m_HuffmanInfo->huffDecBuf, nSamps, mOut); + } + + /* do intensity stereo processing, if enabled */ + if (m_FrameHeader->modeExt & 0x01) { + nSamps = m_HuffmanInfo->nonZeroBound[0]; + if (m_MPEGVersion == MPEG1) { + IntensityProcMPEG1(m_HuffmanInfo->huffDecBuf, nSamps, &m_ScaleFactorInfoSub[gr][1], &m_CriticalBandInfo[0], + m_FrameHeader->modeExt >> 1, m_SideInfoSub[gr][1].mixedBlock, mOut); + } else { + IntensityProcMPEG2(m_HuffmanInfo->huffDecBuf, nSamps, &m_ScaleFactorInfoSub[gr][1], &m_CriticalBandInfo[0], + m_ScaleFactorJS, m_FrameHeader->modeExt >> 1, m_SideInfoSub[gr][1].mixedBlock, mOut); + } + } + + /* adjust guard bit count and nonZeroBound if we did any stereo processing */ + if (m_FrameHeader->modeExt) { + m_HuffmanInfo->gb[0] = CLZ(mOut[0]) - 1; + m_HuffmanInfo->gb[1] = CLZ(mOut[1]) - 1; + nSamps = (m_HuffmanInfo->nonZeroBound[0] > m_HuffmanInfo->nonZeroBound[1] ? + m_HuffmanInfo->nonZeroBound[0] : m_HuffmanInfo->nonZeroBound[1]); + m_HuffmanInfo->nonZeroBound[0] = nSamps; + m_HuffmanInfo->nonZeroBound[1] = nSamps; + } + + /* output format Q(DQ_FRACBITS_OUT) */ + return 0; +} + +/*********************************************************************************************************************** + * D Q C H A N + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: DequantBlock + * + * Description: Ken's highly-optimized, low memory dequantizer performing the operation + * y = pow(x, 4.0/3.0) * pow(2, 25 - scale/4.0) + * + * Inputs: input buffer of decode Huffman codewords (signed-magnitude) + * output buffer of same length (in-place (outbuf = inbuf) is allowed) + * number of samples + * + * Outputs: dequantized samples in Q25 format + * + * Return: bitwise-OR of the unsigned outputs (for guard bit calculations) + **********************************************************************************************************************/ +int DequantBlock(int *inbuf, int *outbuf, int num, int scale){ + int tab4[4]; + int scalef, scalei, shift; + int sx, x, y; + int mask = 0; + const int *tab16; + const unsigned int *coef; + + tab16 = pow43_14[scale & 0x3]; + scalef = pow14[scale & 0x3]; + scalei =((scale >> 2) < 31 ? (scale >> 2) : 31 ); + //scalei = MIN(scale >> 2, 31); /* smallest input scale = -47, so smallest scalei = -12 */ + + /* cache first 4 values */ + shift = (scalei + 3 < 31 ? scalei + 3 : 31); + shift = (shift > 0 ? shift : 0); + + tab4[0] = 0; + tab4[1] = tab16[1] >> shift; + tab4[2] = tab16[2] >> shift; + tab4[3] = tab16[3] >> shift; + + do { + sx = *inbuf++; + x = sx & 0x7fffffff; /* sx = sign|mag */ + if (x < 4) { + y = tab4[x]; + } else if (x < 16) { + y = tab16[x]; + y = (scalei < 0) ? y << -scalei : y >> scalei; + } else { + if (x < 64) { + y = pow43[x-16]; + /* fractional scale */ + y = MULSHIFT32(y, scalef); + shift = scalei - 3; + } else { + /* normalize to [0x40000000, 0x7fffffff] */ + x <<= 17; + shift = 0; + if (x < 0x08000000) + x <<= 4, shift += 4; + if (x < 0x20000000) + x <<= 2, shift += 2; + if (x < 0x40000000) + x <<= 1, shift += 1; + + coef = (x < m_SQRTHALF) ? poly43lo : poly43hi; + + /* polynomial */ + y = coef[0]; + y = MULSHIFT32(y, x) + coef[1]; + y = MULSHIFT32(y, x) + coef[2]; + y = MULSHIFT32(y, x) + coef[3]; + y = MULSHIFT32(y, x) + coef[4]; + y = MULSHIFT32(y, pow2frac[shift]) << 3; + + /* fractional scale */ + y = MULSHIFT32(y, scalef); + shift = scalei - pow2exp[shift]; + } + + /* integer scale */ + if (shift < 0) { + shift = -shift; + if (y > (0x7fffffff >> shift)) + y = 0x7fffffff; /* clip */ + else + y <<= shift; + } else { + y >>= shift; + } + } + + /* sign and store */ + mask |= y; + *outbuf++ = (sx < 0) ? -y : y; + + } while (--num); + + return mask; +} + +/*********************************************************************************************************************** + * Function: DequantChannel + * + * Description: dequantize one granule, one channel worth of decoded Huffman codewords + * + * Inputs: sample buffer (decoded Huffman codewords), length = m_MAX_NSAMP samples + * work buffer for reordering short-block, length = m_MAX_REORDER_SAMPS + * samples (3 * width of largest short-block critical band) + * non-zero bound for this channel/granule + * valid FrameHeader, SideInfoSub, ScaleFactorInfoSub, and CriticalBandInfo + * structures for this channel/granule + * + * Outputs: MAX_NSAMP dequantized samples in sampleBuf + * updated non-zero bound (indicating which samples are != 0 after DQ) + * filled-in cbi structure indicating start and end critical bands + * + * Return: minimum number of guard bits in dequantized sampleBuf + * + * Notes: dequantized samples in Q(DQ_FRACBITS_OUT) format + **********************************************************************************************************************/ +int DequantChannel(int *sampleBuf, int *workBuf, int *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, + CriticalBandInfo_t *cbi) +{ + int i, j, w, cb; + int /* cbStartL, */ cbEndL, cbStartS, cbEndS; + int nSamps, nonZero, sfactMultiplier, gbMask; + int globalGain, gainI; + int cbMax[3]; + typedef int ARRAY3[3]; /* for short-block reordering */ + ARRAY3 *buf; /* short block reorder */ + + /* set default start/end points for short/long blocks - will update with non-zero cb info */ + if (sis->blockType == 2) { + // cbStartL = 0; + if (sis->mixedBlock) { + cbEndL = (m_MPEGVersion == MPEG1 ? 8 : 6); + cbStartS = 3; + } else { + cbEndL = 0; + cbStartS = 0; + } + cbEndS = 13; + } else { + /* long block */ + //cbStartL = 0; + cbEndL = 22; + cbStartS = 13; + cbEndS = 13; + } + cbMax[2] = cbMax[1] = cbMax[0] = 0; + gbMask = 0; + i = 0; + + /* sfactScale = 0 --> quantizer step size = 2 + * sfactScale = 1 --> quantizer step size = sqrt(2) + * so sfactMultiplier = 2 or 4 (jump through globalGain by powers of 2 or sqrt(2)) + */ + sfactMultiplier = 2 * (sis->sfactScale + 1); + + /* offset globalGain by -2 if midSide enabled, for 1/sqrt(2) used in MidSideProc() + * (DequantBlock() does 0.25 * gainI so knocking it down by two is the same as + * dividing every sample by sqrt(2) = multiplying by 2^-.5) + */ + globalGain = sis->globalGain; + if (m_FrameHeader->modeExt >> 1) + globalGain -= 2; + globalGain += m_IMDCT_SCALE; /* scale everything by sqrt(2), for fast IMDCT36 */ + + /* long blocks */ + for (cb = 0; cb < cbEndL; cb++) { + + nonZero = 0; + nSamps = m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb]; + gainI = 210 - globalGain + sfactMultiplier * (sfis->l[cb] + (sis->preFlag ? (int)preTab[cb] : 0)); + + nonZero |= DequantBlock(sampleBuf + i, sampleBuf + i, nSamps, gainI); + i += nSamps; + + /* update highest non-zero critical band */ + if (nonZero) + cbMax[0] = cb; + gbMask |= nonZero; + + if (i >= *nonZeroBound) + break; + } + + /* set cbi (Type, EndS[], EndSMax will be overwritten if we proceed to do short blocks) */ + cbi->cbType = 0; /* long only */ + cbi->cbEndL = cbMax[0]; + cbi->cbEndS[0] = cbi->cbEndS[1] = cbi->cbEndS[2] = 0; + cbi->cbEndSMax = 0; + + /* early exit if no short blocks */ + if (cbStartS >= 12) + return CLZ(gbMask) - 1; + + /* short blocks */ + cbMax[2] = cbMax[1] = cbMax[0] = cbStartS; + for (cb = cbStartS; cb < cbEndS; cb++) { + + nSamps = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb]; + for (w = 0; w < 3; w++) { + nonZero = 0; + gainI = 210 - globalGain + 8*sis->subBlockGain[w] + sfactMultiplier*(sfis->s[cb][w]); + + nonZero |= DequantBlock(sampleBuf + i + nSamps*w, workBuf + nSamps*w, nSamps, gainI); + + /* update highest non-zero critical band */ + if (nonZero) + cbMax[w] = cb; + gbMask |= nonZero; + } + + /* reorder blocks */ + buf = (ARRAY3 *)(sampleBuf + i); + i += 3*nSamps; + for (j = 0; j < nSamps; j++) { + buf[j][0] = workBuf[0*nSamps + j]; + buf[j][1] = workBuf[1*nSamps + j]; + buf[j][2] = workBuf[2*nSamps + j]; + } + + assert(3*nSamps <= m_MAX_REORDER_SAMPS); + + if (i >= *nonZeroBound) + break; + } + + /* i = last non-zero INPUT sample processed, which corresponds to highest possible non-zero + * OUTPUT sample (after reorder) + * however, the original nzb is no longer necessarily true + * for each cb, buf[][] is updated with 3*nSamps samples (i increases 3*nSamps each time) + * (buf[j + 1][0] = 3 (input) samples ahead of buf[j][0]) + * so update nonZeroBound to i + */ + *nonZeroBound = i; + + assert(*nonZeroBound <= m_MAX_NSAMP); + + cbi->cbType = (sis->mixedBlock ? 2 : 1); /* 2 = mixed short/long, 1 = short only */ + + cbi->cbEndS[0] = cbMax[0]; + cbi->cbEndS[1] = cbMax[1]; + cbi->cbEndS[2] = cbMax[2]; + + cbi->cbEndSMax = cbMax[0]; + cbi->cbEndSMax = (cbi->cbEndSMax > cbMax[1] ? cbi->cbEndSMax : cbMax[1]); + cbi->cbEndSMax = (cbi->cbEndSMax > cbMax[2] ? cbi->cbEndSMax : cbMax[2]); + + return CLZ(gbMask) - 1; +} + +/*********************************************************************************************************************** + * S T P R O C + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: MidSideProc + * + * Description: sum-difference stereo reconstruction + * + * Inputs: vector x with dequantized samples from left and right channels + * number of non-zero samples (MAX of left and right) + * assume 1 guard bit in input + * guard bit mask (left and right channels) + * + * Outputs: updated sample vector x + * updated guard bit mask + * + * Return: none + * + * Notes: assume at least 1 GB in input + **********************************************************************************************************************/ +void MidSideProc(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, int mOut[2]){ + int i, xr, xl, mOutL, mOutR; + + /* L = (M+S)/sqrt(2), R = (M-S)/sqrt(2) + * NOTE: 1/sqrt(2) done in DequantChannel() - see comments there + */ + mOutL = mOutR = 0; + for (i = 0; i < nSamps; i++) { + xl = x[0][i]; + xr = x[1][i]; + x[0][i] = xl + xr; + x[1][i] = xl - xr; + mOutL |= FASTABS(x[0][i]); + mOutR |= FASTABS(x[1][i]); + } + mOut[0] |= mOutL; + mOut[1] |= mOutR; +} + +/*********************************************************************************************************************** + * Function: IntensityProcMPEG1 + * + * Description: intensity stereo processing for MPEG1 + * + * Inputs: vector x with dequantized samples from left and right channels + * number of non-zero samples in left channel + * valid FrameHeader struct + * two each of ScaleFactorInfoSub, CriticalBandInfo structs (both channels) + * flags indicating midSide on/off, mixedBlock on/off + * guard bit mask (left and right channels) + * + * Outputs: updated sample vector x + * updated guard bit mask + * + * Return: none + * + * Notes: assume at least 1 GB in input + * + **********************************************************************************************************************/ +void IntensityProcMPEG1(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, ScaleFactorInfoSub_t *sfis, + CriticalBandInfo_t *cbi, int midSideFlag, int mixFlag, int mOut[2]) +{ + int i = 0, j = 0, n = 0, cb = 0, w = 0; + int sampsLeft, isf, mOutL, mOutR, xl, xr; + int fl, fr, fls[3], frs[3]; + int cbStartL = 0, cbStartS = 0, cbEndL = 0, cbEndS = 0; + int *isfTab; + (void) mixFlag; + + /* NOTE - this works fine for mixed blocks, as long as the switch point starts in the + * short block section (i.e. on or after sample 36 = sfBand->l[8] = 3*sfBand->s[3] + * is this a safe assumption? + */ + if (cbi[1].cbType == 0) { + /* long block */ + cbStartL = cbi[1].cbEndL + 1; + cbEndL = cbi[0].cbEndL + 1; + cbStartS = cbEndS = 0; + i = m_SFBandTable.l[cbStartL]; + } else if (cbi[1].cbType == 1 || cbi[1].cbType == 2) { + /* short or mixed block */ + cbStartS = cbi[1].cbEndSMax + 1; + cbEndS = cbi[0].cbEndSMax + 1; + cbStartL = cbEndL = 0; + i = 3 * m_SFBandTable.s[cbStartS]; + } + sampsLeft = nSamps - i; /* process to length of left */ + isfTab = (int *) ISFMpeg1[midSideFlag]; + mOutL = mOutR = 0; + + /* long blocks */ + for (cb = cbStartL; cb < cbEndL && sampsLeft > 0; cb++) { + isf = sfis->l[cb]; + if (isf == 7) { + fl = ISFIIP[midSideFlag][0]; + fr = ISFIIP[midSideFlag][1]; + } else { + fl = isfTab[isf]; + fr = isfTab[6] - isfTab[isf]; + } + + n = m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb]; + for (j = 0; j < n && sampsLeft > 0; j++, i++) { + xr = MULSHIFT32(fr, x[0][i]) << 2; + x[1][i] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fl, x[0][i]) << 2; + x[0][i] = xl; + mOutL |= FASTABS(xl); + sampsLeft--; + } + } + /* short blocks */ + for (cb = cbStartS; cb < cbEndS && sampsLeft >= 3; cb++) { + for (w = 0; w < 3; w++) { + isf = sfis->s[cb][w]; + if (isf == 7) { + fls[w] = ISFIIP[midSideFlag][0]; + frs[w] = ISFIIP[midSideFlag][1]; + } else { + fls[w] = isfTab[isf]; + frs[w] = isfTab[6] - isfTab[isf]; + } + } + n = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb]; + for (j = 0; j < n && sampsLeft >= 3; j++, i += 3) { + xr = MULSHIFT32(frs[0], x[0][i + 0]) << 2; + x[1][i + 0] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fls[0], x[0][i + 0]) << 2; + x[0][i + 0] = xl; + mOutL |= FASTABS(xl); + xr = MULSHIFT32(frs[1], x[0][i + 1]) << 2; + x[1][i + 1] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fls[1], x[0][i + 1]) << 2; + x[0][i + 1] = xl; + mOutL |= FASTABS(xl); + xr = MULSHIFT32(frs[2], x[0][i + 2]) << 2; + x[1][i + 2] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fls[2], x[0][i + 2]) << 2; + x[0][i + 2] = xl; + mOutL |= FASTABS(xl); + sampsLeft -= 3; + } + } + mOut[0] = mOutL; + mOut[1] = mOutR; + return; +} + +/*********************************************************************************************************************** + * Function: IntensityProcMPEG2 + * + * Description: intensity stereo processing for MPEG2 + * + * Inputs: vector x with dequantized samples from left and right channels + * number of non-zero samples in left channel + * valid FrameHeader struct + * two each of ScaleFactorInfoSub, CriticalBandInfo structs (both channels) + * ScaleFactorJS struct with joint stereo info from UnpackSFMPEG2() + * flags indicating midSide on/off, mixedBlock on/off + * guard bit mask (left and right channels) + * + * Outputs: updated sample vector x + * updated guard bit mask + * + * Return: none + * + * Notes: assume at least 1 GB in input + * + **********************************************************************************************************************/ +void IntensityProcMPEG2(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, + ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, + ScaleFactorJS_t *sfjs, int midSideFlag, int mixFlag, int mOut[2]) { + int i, j, k, n, r, cb, w; + int fl, fr, mOutL, mOutR, xl, xr; + int sampsLeft; + int isf, sfIdx, tmp, il[23]; + int *isfTab; + int cbStartL, cbStartS, cbEndL, cbEndS; + + (void) mixFlag; + + isfTab = (int *) ISFMpeg2[sfjs->intensityScale][midSideFlag]; + mOutL = mOutR = 0; + + /* fill buffer with illegal intensity positions (depending on slen) */ + for (k = r = 0; r < 4; r++) { + tmp = (1 << sfjs->slen[r]) - 1; + for (j = 0; j < sfjs->nr[r]; j++, k++) + il[k] = tmp; + } + + if (cbi[1].cbType == 0) { + /* long blocks */ + il[21] = il[22] = 1; + cbStartL = cbi[1].cbEndL + 1; /* start at end of right */ + cbEndL = cbi[0].cbEndL + 1; /* process to end of left */ + i = m_SFBandTable.l[cbStartL]; + sampsLeft = nSamps - i; + + for (cb = cbStartL; cb < cbEndL; cb++) { + sfIdx = sfis->l[cb]; + if (sfIdx == il[cb]) { + fl = ISFIIP[midSideFlag][0]; + fr = ISFIIP[midSideFlag][1]; + } else { + isf = (sfis->l[cb] + 1) >> 1; + fl = isfTab[(sfIdx & 0x01 ? isf : 0)]; + fr = isfTab[(sfIdx & 0x01 ? 0 : isf)]; + } + int r=m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb]; + n=(r < sampsLeft ? r : sampsLeft); + //n = MIN(fh->sfBand->l[cb + 1] - fh->sfBand->l[cb], sampsLeft); + for (j = 0; j < n; j++, i++) { + xr = MULSHIFT32(fr, x[0][i]) << 2; + x[1][i] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fl, x[0][i]) << 2; + x[0][i] = xl; + mOutL |= FASTABS(xl); + } + /* early exit once we've used all the non-zero samples */ + sampsLeft -= n; + if (sampsLeft == 0) + break; + } + } else { + /* short or mixed blocks */ + il[12] = 1; + + for (w = 0; w < 3; w++) { + cbStartS = cbi[1].cbEndS[w] + 1; /* start at end of right */ + cbEndS = cbi[0].cbEndS[w] + 1; /* process to end of left */ + i = 3 * m_SFBandTable.s[cbStartS] + w; + + /* skip through sample array by 3, so early-exit logic would be more tricky */ + for (cb = cbStartS; cb < cbEndS; cb++) { + sfIdx = sfis->s[cb][w]; + if (sfIdx == il[cb]) { + fl = ISFIIP[midSideFlag][0]; + fr = ISFIIP[midSideFlag][1]; + } else { + isf = (sfis->s[cb][w] + 1) >> 1; + fl = isfTab[(sfIdx & 0x01 ? isf : 0)]; + fr = isfTab[(sfIdx & 0x01 ? 0 : isf)]; + } + n = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb]; + + for (j = 0; j < n; j++, i += 3) { + xr = MULSHIFT32(fr, x[0][i]) << 2; + x[1][i] = xr; + mOutR |= FASTABS(xr); + xl = MULSHIFT32(fl, x[0][i]) << 2; + x[0][i] = xl; + mOutL |= FASTABS(xl); + } + } + } + } + mOut[0] = mOutL; + mOut[1] = mOutR; + return; +} + +/*********************************************************************************************************************** + * I M D C T + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: AntiAlias + * + * Description: smooth transition across DCT block boundaries (every 18 coefficients) + * + * Inputs: vector of dequantized coefficients, length = (nBfly+1) * 18 + * number of "butterflies" to perform (one butterfly means one + * inter-block smoothing operation) + * + * Outputs: updated coefficient vector x + * + * Return: none + * + * Notes: weighted average of opposite bands (pairwise) from the 8 samples + * before and after each block boundary + * nBlocks = (nonZeroBound + 7) / 18, since nZB is the first ZERO sample + * above which all other samples are also zero + * max gain per sample = 1.372 + * MAX(i) (abs(csa[i][0]) + abs(csa[i][1])) + * bits gained = 0 + * assume at least 1 guard bit in x[] to avoid overflow + * (should be guaranteed from dequant, and max gain from stproc * max + * gain from AntiAlias < 2.0) + **********************************************************************************************************************/ +// a little bit faster in RAM (< 1 ms per block) +/* __attribute__ ((section (".data"))) */ +void AntiAlias(int *x, int nBfly){ + int k, a0, b0, c0, c1; + const uint32_t *c; + + /* csa = Q31 */ + for (k = nBfly; k > 0; k--) { + c = csa[0]; + x += 18; + a0 = x[-1]; + c0 = *c; + c++; + b0 = x[0]; + c1 = *c; + c++; + x[-1] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[0] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-2]; + c0 = *c; + c++; + b0 = x[1]; + c1 = *c; + c++; + x[-2] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[1] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-3]; + c0 = *c; + c++; + b0 = x[2]; + c1 = *c; + c++; + x[-3] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[2] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-4]; + c0 = *c; + c++; + b0 = x[3]; + c1 = *c; + c++; + x[-4] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[3] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-5]; + c0 = *c; + c++; + b0 = x[4]; + c1 = *c; + c++; + x[-5] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[4] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-6]; + c0 = *c; + c++; + b0 = x[5]; + c1 = *c; + c++; + x[-6] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[5] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-7]; + c0 = *c; + c++; + b0 = x[6]; + c1 = *c; + c++; + x[-7] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[6] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + + a0 = x[-8]; + c0 = *c; + c++; + b0 = x[7]; + c1 = *c; + c++; + x[-8] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1; + x[7] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1; + } +} + +/*********************************************************************************************************************** + * Function: WinPrevious + * + * Description: apply specified window to second half of previous IMDCT (overlap part) + * + * Inputs: vector of 9 coefficients (xPrev) + * + * Outputs: 18 windowed output coefficients (gain 1 integer bit) + * window type (0, 1, 2, 3) + * + * Return: none + * + * Notes: produces 9 output samples from 18 input samples via symmetry + * all blocks gain at least 1 guard bit via window (long blocks get extra + * sign bit, short blocks can have one addition but max gain < 1.0) + **********************************************************************************************************************/ + +void WinPrevious(int *xPrev, int *xPrevWin, int btPrev){ + int i, x, *xp, *xpwLo, *xpwHi, wLo, wHi; + const uint32_t *wpLo, *wpHi; + + xp = xPrev; + /* mapping (see IMDCT12x3): xPrev[0-2] = sum[6-8], xPrev[3-8] = sum[12-17] */ + if (btPrev == 2) { + /* this could be reordered for minimum loads/stores */ + wpLo = imdctWin[btPrev]; + xPrevWin[0] = MULSHIFT32(wpLo[6], xPrev[2]) + + MULSHIFT32(wpLo[0], xPrev[6]); + xPrevWin[1] = MULSHIFT32(wpLo[7], xPrev[1]) + + MULSHIFT32(wpLo[1], xPrev[7]); + xPrevWin[2] = MULSHIFT32(wpLo[8], xPrev[0]) + + MULSHIFT32(wpLo[2], xPrev[8]); + xPrevWin[3] = MULSHIFT32(wpLo[9], xPrev[0]) + + MULSHIFT32(wpLo[3], xPrev[8]); + xPrevWin[4] = MULSHIFT32(wpLo[10], xPrev[1]) + + MULSHIFT32(wpLo[4], xPrev[7]); + xPrevWin[5] = MULSHIFT32(wpLo[11], xPrev[2]) + + MULSHIFT32(wpLo[5], xPrev[6]); + xPrevWin[6] = MULSHIFT32(wpLo[6], xPrev[5]); + xPrevWin[7] = MULSHIFT32(wpLo[7], xPrev[4]); + xPrevWin[8] = MULSHIFT32(wpLo[8], xPrev[3]); + xPrevWin[9] = MULSHIFT32(wpLo[9], xPrev[3]); + xPrevWin[10] = MULSHIFT32(wpLo[10], xPrev[4]); + xPrevWin[11] = MULSHIFT32(wpLo[11], xPrev[5]); + xPrevWin[12] = xPrevWin[13] = xPrevWin[14] = xPrevWin[15] = + xPrevWin[16] = xPrevWin[17] = 0; + } else { + /* use ARM-style pointers (*ptr++) so that ADS compiles well */ + wpLo = imdctWin[btPrev] + 18; + wpHi = wpLo + 17; + xpwLo = xPrevWin; + xpwHi = xPrevWin + 17; + for (i = 9; i > 0; i--) { + x = *xp++; + wLo = *wpLo++; + wHi = *wpHi--; + *xpwLo++ = MULSHIFT32(wLo, x); + *xpwHi-- = MULSHIFT32(wHi, x); + } + } +} + +/*********************************************************************************************************************** + * Function: FreqInvertRescale + * + * Description: do frequency inversion (odd samples of odd blocks) and rescale + * if necessary (extra guard bits added before IMDCT) + * + * Inputs: output vector y (18 new samples, spaced NBANDS apart) + * previous sample vector xPrev (9 samples) + * index of current block + * number of extra shifts added before IMDCT (usually 0) + * + * Outputs: inverted and rescaled (as necessary) outputs + * rescaled (as necessary) previous samples + * + * Return: updated mOut (from new outputs y) + **********************************************************************************************************************/ + +int FreqInvertRescale(int *y, int *xPrev, int blockIdx, int es) { + + if (es == 0) { + /* fast case - frequency invert only (no rescaling) */ + if (blockIdx & 0x01) { + y += m_NBANDS; + for (int i = 0; i < 9; i++) { + *y = - *y; y += 2 * m_NBANDS; + } + } + return 0; + } + + int d, mOut; + /* undo pre-IMDCT scaling, clipping if necessary */ + mOut = 0; + if (blockIdx & 0x01) { + /* frequency invert */ + for (int i = 0; i < 9; i++) { + d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = -*y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = *xPrev; CLIP_2N(d, (31 - es)); *xPrev++ = d << es; + } + } else { + for (int i = 0; i < 9; i++) { + d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS; + d = *xPrev; CLIP_2N(d, (31 - es)); *xPrev++ = d << es; + } + } + return mOut; + +} + + +/* require at least 3 guard bits in x[] to ensure no overflow */ +void idct9(int *x) { + int a1, a2, a3, a4, a5, a6, a7, a8, a9; + int a10, a11, a12, a13, a14, a15, a16, a17, a18; + int a19, a20, a21, a22, a23, a24, a25, a26, a27; + int m1, m3, m5, m6, m7, m8, m9, m10, m11, m12; + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + + x0 = x[0]; + x1 = x[1]; + x2 = x[2]; + x3 = x[3]; + x4 = x[4]; + x5 = x[5]; + x6 = x[6]; + x7 = x[7]; + x8 = x[8]; + + a1 = x0 - x6; + a2 = x1 - x5; + a3 = x1 + x5; + a4 = x2 - x4; + a5 = x2 + x4; + a6 = x2 + x8; + a7 = x1 + x7; + + a8 = a6 - a5; /* ie x[8] - x[4] */ + a9 = a3 - a7; /* ie x[5] - x[7] */ + a10 = a2 - x7; /* ie x[1] - x[5] - x[7] */ + a11 = a4 - x8; /* ie x[2] - x[4] - x[8] */ + + /* do the << 1 as constant shifts where mX is actually used (free, no stall or extra inst.) */ + m1 = MULSHIFT32(c9_0, x3); + m3 = MULSHIFT32(c9_0, a10); + m5 = MULSHIFT32(c9_1, a5); + m6 = MULSHIFT32(c9_2, a6); + m7 = MULSHIFT32(c9_1, a8); + m8 = MULSHIFT32(c9_2, a5); + m9 = MULSHIFT32(c9_3, a9); + m10 = MULSHIFT32(c9_4, a7); + m11 = MULSHIFT32(c9_3, a3); + m12 = MULSHIFT32(c9_4, a9); + + a12 = x[0] + (x[6] >> 1); + a13 = a12 + (m1 << 1); + a14 = a12 - (m1 << 1); + a15 = a1 + (a11 >> 1); + a16 = (m5 << 1) + (m6 << 1); + a17 = (m7 << 1) - (m8 << 1); + a18 = a16 + a17; + a19 = (m9 << 1) + (m10 << 1); + a20 = (m11 << 1) - (m12 << 1); + + a21 = a20 - a19; + a22 = a13 + a16; + a23 = a14 + a16; + a24 = a14 + a17; + a25 = a13 + a17; + a26 = a14 - a18; + a27 = a13 - a18; + + x0 = a22 + a19; + x[0] = x0; + x1 = a15 + (m3 << 1); + x[1] = x1; + x2 = a24 + a20; + x[2] = x2; + x3 = a26 - a21; + x[3] = x3; + x4 = a1 - a11; + x[4] = x4; + x5 = a27 + a21; + x[5] = x5; + x6 = a25 - a20; + x[6] = x6; + x7 = a15 - (m3 << 1); + x[7] = x7; + x8 = a23 - a19; + x[8] = x8; +} + + +/*********************************************************************************************************************** + * Function: IMDCT36 + * + * Description: 36-point modified DCT, with windowing and overlap-add (50% overlap) + * + * Inputs: vector of 18 coefficients (N/2 inputs produces N outputs, by symmetry) + * overlap part of last IMDCT (9 samples - see output comments) + * window type (0,1,2,3) of current and previous block + * current block index (for deciding whether to do frequency inversion) + * number of guard bits in input vector + * + * Outputs: 18 output samples, after windowing and overlap-add with last frame + * second half of (unwindowed) 36-point IMDCT - save for next time + * only save 9 xPrev samples, using symmetry (see WinPrevious()) + * + * Notes: this is Ken's hyper-fast algorithm, including symmetric sin window + * optimization, if applicable + * total number of multiplies, general case: + * 2*10 (idct9) + 9 (last stage imdct) + 36 (for windowing) = 65 + * total number of multiplies, btCurr == 0 && btPrev == 0: + * 2*10 (idct9) + 9 (last stage imdct) + 18 (for windowing) = 47 + * + * blockType == 0 is by far the most common case, so it should be + * possible to use the fast path most of the time + * this is the fastest known algorithm for performing + * long IMDCT + windowing + overlap-add in MP3 + * + * Return: mOut (OR of abs(y) for all y calculated here) + **********************************************************************************************************************/ +// barely faster in RAM + +int IMDCT36(int *xCurr, int *xPrev, int *y, int btCurr, int btPrev, int blockIdx, int gb){ + int i, es, xBuf[18], xPrevWin[18]; + int acc1, acc2, s, d, t, mOut; + int xo, xe, c, *xp, yLo, yHi; + const uint32_t *cp, *wp; + acc1 = acc2 = 0; + xCurr += 17; + /* 7 gb is always adequate for antialias + accumulator loop + idct9 */ + if (gb < 7) { + /* rarely triggered - 5% to 10% of the time on normal clips (with Q25 input) */ + es = 7 - gb; + for (i = 8; i >= 0; i--) { + acc1 = ((*xCurr--) >> es) - acc1; + acc2 = acc1 - acc2; + acc1 = ((*xCurr--) >> es) - acc1; + xBuf[i + 9] = acc2; /* odd */ + xBuf[i + 0] = acc1; /* even */ + xPrev[i] >>= es; + } + } else { + es = 0; + /* max gain = 18, assume adequate guard bits */ + for (i = 8; i >= 0; i--) { + acc1 = (*xCurr--) - acc1; + acc2 = acc1 - acc2; + acc1 = (*xCurr--) - acc1; + xBuf[i + 9] = acc2; /* odd */ + xBuf[i + 0] = acc1; /* even */ + } + } + /* xEven[0] and xOdd[0] scaled by 0.5 */ + xBuf[9] >>= 1; + xBuf[0] >>= 1; + + /* do 9-point IDCT on even and odd */ + idct9(xBuf + 0); /* even */ + idct9(xBuf + 9); /* odd */ + + xp = xBuf + 8; + cp = c18 + 8; + mOut = 0; + if (btPrev == 0 && btCurr == 0) { + /* fast path - use symmetry of sin window to reduce windowing multiplies to 18 (N/2) */ + wp = fastWin36; + for (i = 0; i < 9; i++) { + /* do ARM-style pointer arithmetic (i still needed for y[] indexing - compiler spills if 2 y pointers) */ + c = *cp--; + xo = *(xp + 9); + xe = *xp--; + /* gain 2 int bits here */ + xo = MULSHIFT32(c, xo); /* 2*c18*xOdd (mul by 2 implicit in scaling) */ + xe >>= 2; + + s = -(*xPrev); /* sum from last block (always at least 2 guard bits) */ + d = -(xe - xo); /* gain 2 int bits, don't shift xo (effective << 1 to eat sign bit, << 1 for mul by 2) */ + (*xPrev++) = xe + xo; /* symmetry - xPrev[i] = xPrev[17-i] for long blocks */ + t = s - d; + + yLo = (d + (MULSHIFT32(t, *wp++) << 2)); + yHi = (s + (MULSHIFT32(t, *wp++) << 2)); + y[(i) * m_NBANDS] = yLo; + y[(17 - i) * m_NBANDS] = yHi; + mOut |= FASTABS(yLo); + mOut |= FASTABS(yHi); + } + } else { + /* slower method - either prev or curr is using window type != 0 so do full 36-point window + * output xPrevWin has at least 3 guard bits (xPrev has 2, gain 1 in WinPrevious) + */ + WinPrevious(xPrev, xPrevWin, btPrev); + + wp = imdctWin[btCurr]; + for (i = 0; i < 9; i++) { + c = *cp--; + xo = *(xp + 9); + xe = *xp--; + /* gain 2 int bits here */ + xo = MULSHIFT32(c, xo); /* 2*c18*xOdd (mul by 2 implicit in scaling) */ + xe >>= 2; + + d = xe - xo; + (*xPrev++) = xe + xo; /* symmetry - xPrev[i] = xPrev[17-i] for long blocks */ + + yLo = (xPrevWin[i] + MULSHIFT32(d, wp[i])) << 2; + yHi = (xPrevWin[17 - i] + MULSHIFT32(d, wp[17 - i])) << 2; + y[(i) * m_NBANDS] = yLo; + y[(17 - i) * m_NBANDS] = yHi; + mOut |= FASTABS(yLo); + mOut |= FASTABS(yHi); + } + } + + xPrev -= 9; + mOut |= FreqInvertRescale(y, xPrev, blockIdx, es); + + return mOut; +} + + + +/* 12-point inverse DCT, used in IMDCT12x3() + * 4 input guard bits will ensure no overflow + */ +void imdct12(int *x, int *out) { + int a0, a1, a2; + int x0, x1, x2, x3, x4, x5; + + x0 = *x; + x += 3; + x1 = *x; + x += 3; + x2 = *x; + x += 3; + x3 = *x; + x += 3; + x4 = *x; + x += 3; + x5 = *x; + x += 3; + + x4 -= x5; + x3 -= x4; + x2 -= x3; + x3 -= x5; + x1 -= x2; + x0 -= x1; + x1 -= x3; + + x0 >>= 1; + x1 >>= 1; + + a0 = MULSHIFT32(c3_0, x2) << 1; + a1 = x0 + (x4 >> 1); + a2 = x0 - x4; + x0 = a1 + a0; + x2 = a2; + x4 = a1 - a0; + + a0 = MULSHIFT32(c3_0, x3) << 1; + a1 = x1 + (x5 >> 1); + a2 = x1 - x5; + + /* cos window odd samples, mul by 2, eat sign bit */ + x1 = MULSHIFT32(c6[0], a1 + a0) << 2; + x3 = MULSHIFT32(c6[1], a2) << 2; + x5 = MULSHIFT32(c6[2], a1 - a0) << 2; + + *out = x0 + x1; + out++; + *out = x2 + x3; + out++; + *out = x4 + x5; + out++; + *out = x4 - x5; + out++; + *out = x2 - x3; + out++; + *out = x0 - x1; +} + +/*********************************************************************************************************************** + * Function: IMDCT12x3 + * + * Description: three 12-point modified DCT's for short blocks, with windowing, + * short block concatenation, and overlap-add + * + * Inputs: 3 interleaved vectors of 6 samples each + * (block0[0], block1[0], block2[0], block0[1], block1[1]....) + * overlap part of last IMDCT (9 samples - see output comments) + * window type (0,1,2,3) of previous block + * current block index (for deciding whether to do frequency inversion) + * number of guard bits in input vector + * + * Outputs: updated sample vector x, net gain of 1 integer bit + * second half of (unwindowed) IMDCT's - save for next time + * only save 9 xPrev samples, using symmetry (see WinPrevious()) + * + * Return: mOut (OR of abs(y) for all y calculated here) + **********************************************************************************************************************/ +// barely faster in RAM +int IMDCT12x3(int *xCurr, int *xPrev, int *y, int btPrev, int blockIdx, int gb){ + int i, es, mOut, yLo, xBuf[18], xPrevWin[18]; /* need temp buffer for reordering short blocks */ + const uint32_t *wp; + es = 0; + /* 7 gb is always adequate for accumulator loop + idct12 + window + overlap */ + if (gb < 7) { + es = 7 - gb; + for (i = 0; i < 18; i += 2) { + xCurr[i + 0] >>= es; + xCurr[i + 1] >>= es; + *xPrev++ >>= es; + } + xPrev -= 9; + } + + /* requires 4 input guard bits for each imdct12 */ + imdct12(xCurr + 0, xBuf + 0); + imdct12(xCurr + 1, xBuf + 6); + imdct12(xCurr + 2, xBuf + 12); + + /* window previous from last time */ + WinPrevious(xPrev, xPrevWin, btPrev); + + /* could unroll this for speed, minimum loads (short blocks usually rare, so doesn't make much overall difference) + * xPrevWin[i] << 2 still has 1 gb always, max gain of windowed xBuf stuff also < 1.0 and gain the sign bit + * so y calculations won't overflow + */ + wp = imdctWin[2]; + mOut = 0; + for (i = 0; i < 3; i++) { + yLo = (xPrevWin[0 + i] << 2); + mOut |= FASTABS(yLo); + y[(0 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[3 + i] << 2); + mOut |= FASTABS(yLo); + y[(3 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[6 + i] << 2) + (MULSHIFT32(wp[0 + i], xBuf[3 + i])); + mOut |= FASTABS(yLo); + y[(6 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[9 + i] << 2) + (MULSHIFT32(wp[3 + i], xBuf[5 - i])); + mOut |= FASTABS(yLo); + y[(9 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[12 + i] << 2) + + (MULSHIFT32(wp[6 + i], xBuf[2 - i]) + + MULSHIFT32(wp[0 + i], xBuf[(6 + 3) + i])); + mOut |= FASTABS(yLo); + y[(12 + i) * m_NBANDS] = yLo; + yLo = (xPrevWin[15 + i] << 2) + + (MULSHIFT32(wp[9 + i], xBuf[0 + i]) + + MULSHIFT32(wp[3 + i], xBuf[(6 + 5) - i])); + mOut |= FASTABS(yLo); + y[(15 + i) * m_NBANDS] = yLo; + } + + /* save previous (unwindowed) for overlap - only need samples 6-8, 12-17 */ + for (i = 6; i < 9; i++) + *xPrev++ = xBuf[i] >> 2; + for (i = 12; i < 18; i++) + *xPrev++ = xBuf[i] >> 2; + + xPrev -= 9; + mOut |= FreqInvertRescale(y, xPrev, blockIdx, es); + + return mOut; +} + +/*********************************************************************************************************************** + * Function: HybridTransform + * + * Description: IMDCT's, windowing, and overlap-add on long/short/mixed blocks + * + * Inputs: vector of input coefficients, length = nBlocksTotal * 18) + * vector of overlap samples from last time, length = nBlocksPrev * 9) + * buffer for output samples, length = MAXNSAMP + * SideInfoSub struct for this granule/channel + * BlockCount struct with necessary info + * number of non-zero input and overlap blocks + * number of long blocks in input vector (rest assumed to be short blocks) + * number of blocks which use long window (type) 0 in case of mixed block + * (bc->currWinSwitch, 0 for non-mixed blocks) + * + * Outputs: transformed, windowed, and overlapped sample buffer + * does frequency inversion on odd blocks + * updated buffer of samples for overlap + * + * Return: number of non-zero IMDCT blocks calculated in this call + * (including overlap-add) + **********************************************************************************************************************/ +int HybridTransform(int *xCurr, int *xPrev, int y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc){ + int xPrevWin[18], currWinIdx, prevWinIdx; + int i, j, nBlocksOut, nonZero, mOut; + int fiBit, xp; + + assert(bc->nBlocksLong <= m_NBANDS); + assert(bc->nBlocksTotal <= m_NBANDS); + assert(bc->nBlocksPrev <= m_NBANDS); + + mOut = 0; + + /* do long blocks, if any */ + for (i = 0; i < bc->nBlocksLong; i++) { + /* currWinIdx picks the right window for long blocks (if mixed, long blocks use window type 0) */ + currWinIdx = sis->blockType; + if (sis->mixedBlock && i < bc->currWinSwitch) + currWinIdx = 0; + + prevWinIdx = bc->prevType; + if (i < bc->prevWinSwitch) + prevWinIdx = 0; + + /* do 36-point IMDCT, including windowing and overlap-add */ + mOut |= IMDCT36(xCurr, xPrev, &(y[0][i]), currWinIdx, prevWinIdx, i, + bc->gbIn); + xCurr += 18; + xPrev += 9; + } + + /* do short blocks (if any) */ + for (; i < bc->nBlocksTotal; i++) { + assert(sis->blockType == 2); + + prevWinIdx = bc->prevType; + if (i < bc->prevWinSwitch) + prevWinIdx = 0; + + mOut |= IMDCT12x3(xCurr, xPrev, &(y[0][i]), prevWinIdx, i, bc->gbIn); + xCurr += 18; + xPrev += 9; + } + nBlocksOut = i; + + /* window and overlap prev if prev longer that current */ + for (; i < bc->nBlocksPrev; i++) { + prevWinIdx = bc->prevType; + if (i < bc->prevWinSwitch) + prevWinIdx = 0; + WinPrevious(xPrev, xPrevWin, prevWinIdx); + + nonZero = 0; + fiBit = i << 31; + for (j = 0; j < 9; j++) { + xp = xPrevWin[2 * j + 0] << 2; /* << 2 temp for scaling */ + nonZero |= xp; + y[2 * j + 0][i] = xp; + mOut |= FASTABS(xp); + + /* frequency inversion on odd blocks/odd samples (flip sign if i odd, j odd) */ + xp = xPrevWin[2 * j + 1] << 2; + xp = (xp ^ (fiBit >> 31)) + (i & 0x01); + nonZero |= xp; + y[2 * j + 1][i] = xp; + mOut |= FASTABS(xp); + + xPrev[j] = 0; + } + xPrev += 9; + if (nonZero) + nBlocksOut = i; + } + + /* clear rest of blocks */ + for (; i < 32; i++) { + for (j = 0; j < 18; j++) + y[j][i] = 0; + } + + bc->gbOut = CLZ(mOut) - 1; + + return nBlocksOut; +} + +/*********************************************************************************************************************** + * Function: IMDCT + * + * Description: do alias reduction, inverse MDCT, overlap-add, and frequency inversion + * + * Inputs: MP3DecInfo structure filled by UnpackFrameHeader(), UnpackSideInfo(), + * UnpackScaleFactors(), and DecodeHuffman() (for this granule, channel) + * includes PCM samples in overBuf (from last call to IMDCT) for OLA + * index of current granule and channel + * + * Outputs: PCM samples in outBuf, for input to subband transform + * PCM samples in overBuf, for OLA next time + * updated hi->nonZeroBound index for this channel + * + * Return: 0 on success, -1 if null input pointers + **********************************************************************************************************************/ +// a bit faster in RAM +/*__attribute__ ((section (".data")))*/ +int IMDCT( int gr, int ch) { + int nBfly, blockCutoff; + BlockCount_t bc; + + /* m_SideInfo is an array of up to 4 structs, stored as gr0ch0, gr0ch1, gr1ch0, gr1ch1 */ + /* anti-aliasing done on whole long blocks only + * for mixed blocks, nBfly always 1, except 3 for 8 kHz MPEG 2.5 (see sfBandTab) + * nLongBlocks = number of blocks with (possibly) non-zero power + * nBfly = number of butterflies to do (nLongBlocks - 1, unless no long blocks) + */ + blockCutoff = m_SFBandTable.l[(m_MPEGVersion == MPEG1 ? 8 : 6)] / 18; /* same as 3* num short sfb's in spec */ + if (m_SideInfoSub[gr][ch].blockType != 2) { + /* all long transforms */ + int x=(m_HuffmanInfo->nonZeroBound[ch] + 7) / 18 + 1; + bc.nBlocksLong=(x<32 ? x : 32); + //bc.nBlocksLong = min((hi->nonZeroBound[ch] + 7) / 18 + 1, 32); + nBfly = bc.nBlocksLong - 1; + } else if (m_SideInfoSub[gr][ch].blockType == 2 && m_SideInfoSub[gr][ch].mixedBlock) { + /* mixed block - long transforms until cutoff, then short transforms */ + bc.nBlocksLong = blockCutoff; + nBfly = bc.nBlocksLong - 1; + } else { + /* all short transforms */ + bc.nBlocksLong = 0; + nBfly = 0; + } + + AntiAlias(m_HuffmanInfo->huffDecBuf[ch], nBfly); + int x=m_HuffmanInfo->nonZeroBound[ch]; + int y=nBfly * 18 + 8; + m_HuffmanInfo->nonZeroBound[ch]=(x>y ? x: y); + + assert(m_HuffmanInfo->nonZeroBound[ch] <= m_MAX_NSAMP); + + /* for readability, use a struct instead of passing a million parameters to HybridTransform() */ + bc.nBlocksTotal = (m_HuffmanInfo->nonZeroBound[ch] + 17) / 18; + bc.nBlocksPrev = m_IMDCTInfo->numPrevIMDCT[ch]; + bc.prevType = m_IMDCTInfo->prevType[ch]; + bc.prevWinSwitch = m_IMDCTInfo->prevWinSwitch[ch]; + /* where WINDOW switches (not nec. transform) */ + bc.currWinSwitch = (m_SideInfoSub[gr][ch].mixedBlock ? blockCutoff : 0); + bc.gbIn = m_HuffmanInfo->gb[ch]; + + m_IMDCTInfo->numPrevIMDCT[ch] = HybridTransform(m_HuffmanInfo->huffDecBuf[ch], m_IMDCTInfo->overBuf[ch], + m_IMDCTInfo->outBuf[ch], &m_SideInfoSub[gr][ch], &bc); + m_IMDCTInfo->prevType[ch] = m_SideInfoSub[gr][ch].blockType; + m_IMDCTInfo->prevWinSwitch[ch] = bc.currWinSwitch; /* 0 means not a mixed block (either all short or all long) */ + m_IMDCTInfo->gb[ch] = bc.gbOut; + + assert(m_IMDCTInfo->numPrevIMDCT[ch] <= m_NBANDS); + + /* output has gained 2 int bits */ + return 0; +} + +/*********************************************************************************************************************** + * S U B B A N D + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: Subband + * + * Description: do subband transform on all the blocks in one granule, all channels + * + * Inputs: filled MP3DecInfo structure, after calling IMDCT for all channels + * vbuf[ch] and vindex[ch] must be preserved between calls + * + * Outputs: decoded PCM data, interleaved LRLRLR... if stereo + * + * Return: 0 on success, -1 if null input pointers + **********************************************************************************************************************/ +int Subband( short *pcmBuf) { + int b; + if (m_MP3DecInfo->nChans == 2) { + /* stereo */ + for (b = 0; b < m_BLOCK_SIZE; b++) { + FDCT32(m_IMDCTInfo->outBuf[0][b], m_SubbandInfo->vbuf + 0 * 32, m_SubbandInfo->vindex, + (b & 0x01), m_IMDCTInfo->gb[0]); + FDCT32(m_IMDCTInfo->outBuf[1][b], m_SubbandInfo->vbuf + 1 * 32, m_SubbandInfo->vindex, + (b & 0x01), m_IMDCTInfo->gb[1]); + PolyphaseStereo(pcmBuf, + m_SubbandInfo->vbuf + m_SubbandInfo->vindex + m_VBUF_LENGTH * (b & 0x01), + polyCoef); + m_SubbandInfo->vindex = (m_SubbandInfo->vindex - (b & 0x01)) & 7; + pcmBuf += (2 * m_NBANDS); + } + } else { + /* mono */ + for (b = 0; b < m_BLOCK_SIZE; b++) { + FDCT32(m_IMDCTInfo->outBuf[0][b], m_SubbandInfo->vbuf + 0 * 32, m_SubbandInfo->vindex, + (b & 0x01), m_IMDCTInfo->gb[0]); + PolyphaseMono(pcmBuf, + m_SubbandInfo->vbuf + m_SubbandInfo->vindex + m_VBUF_LENGTH * (b & 0x01), + polyCoef); + m_SubbandInfo->vindex = (m_SubbandInfo->vindex - (b & 0x01)) & 7; + pcmBuf += m_NBANDS; + } + } + + return 0; +} + +/*********************************************************************************************************************** + * D C T 3 2 + **********************************************************************************************************************/ + +/*********************************************************************************************************************** + * Function: FDCT32 + * + * Description: Ken's highly-optimized 32-point DCT (radix-4 + radix-8) + * + * Inputs: input buffer, length = 32 samples + * require at least 6 guard bits in input vector x to avoid possibility + * of overflow in internal calculations (see bbtest_imdct test app) + * buffer offset and oddblock flag for polyphase filter input buffer + * number of guard bits in input + * + * Outputs: output buffer, data copied and interleaved for polyphase filter + * no guarantees about number of guard bits in output + * + * Return: none + * + * Notes: number of muls = 4*8 + 12*4 = 80 + * final stage of DCT is hardcoded to shuffle data into the proper order + * for the polyphase filterbank + * fully unrolled stage 1, for max precision (scale the 1/cos() factors + * differently, depending on magnitude) + * guard bit analysis verified by exhaustive testing of all 2^32 + * combinations of max pos/max neg values in x[] + **********************************************************************************************************************/ +#define D32FP(i, s1, s2) { \ + a0 = buf[i]; a3 = buf[31-i]; \ + a1 = buf[15-i]; a2 = buf[16+i]; \ + b0 = a0 + a3; b3 = MULSHIFT32(*cptr++, a0 - a3) << 1; \ + b1 = a1 + a2; b2 = MULSHIFT32(*cptr++, a1 - a2) << (s1); \ + buf[i] = b0 + b1; buf[15-i] = MULSHIFT32(*cptr, b0 - b1) << (s2); \ + buf[16+i] = b2 + b3; buf[31-i] = MULSHIFT32(*cptr++, b3 - b2) << (s2); \ +} + +static const uint8_t FDCT32s1s2[16] = {5,3,3,2,2,1,1,1, 1,1,1,1,1,2,2,4}; + +void FDCT32(int *buf, int *dest, int offset, int oddBlock, int gb) { + int i, s, tmp, es; + const int *cptr = (const int*)m_dcttab; + int a0, a1, a2, a3, a4, a5, a6, a7; + int b0, b1, b2, b3, b4, b5, b6, b7; + int *d; + + /* scaling - ensure at least 6 guard bits for DCT + * (in practice this is already true 99% of time, so this code is + * almost never triggered) + */ + es = 0; + if (gb < 6) { + es = 6 - gb; + for (i = 0; i < 32; i++) + buf[i] >>= es; + } + + /* first pass */ + for (unsigned i=0; i < 8; i++) { + D32FP(i, FDCT32s1s2[0 + i], FDCT32s1s2[8 + i]); + } + + /* second pass */ + for (i = 4; i > 0; i--) { + a0 = buf[0]; a7 = buf[7]; a3 = buf[3]; a4 = buf[4]; + b0 = a0 + a7; b7 = MULSHIFT32(*cptr++, a0 - a7) << 1; + b3 = a3 + a4; b4 = MULSHIFT32(*cptr++, a3 - a4) << 3; + a0 = b0 + b3; a3 = MULSHIFT32(*cptr, b0 - b3) << 1; + a4 = b4 + b7; a7 = MULSHIFT32(*cptr++, b7 - b4) << 1; + + a1 = buf[1]; a6 = buf[6]; a2 = buf[2]; a5 = buf[5]; + b1 = a1 + a6; b6 = MULSHIFT32(*cptr++, a1 - a6) << 1; + b2 = a2 + a5; b5 = MULSHIFT32(*cptr++, a2 - a5) << 1; + a1 = b1 + b2; a2 = MULSHIFT32(*cptr, b1 - b2) << 2; + a5 = b5 + b6; a6 = MULSHIFT32(*cptr++, b6 - b5) << 2; + + b0 = a0 + a1; b1 = MULSHIFT32(m_COS4_0, a0 - a1) << 1; + b2 = a2 + a3; b3 = MULSHIFT32(m_COS4_0, a3 - a2) << 1; + buf[0] = b0; buf[1] = b1; + buf[2] = b2 + b3; buf[3] = b3; + + b4 = a4 + a5; b5 = MULSHIFT32(m_COS4_0, a4 - a5) << 1; + b6 = a6 + a7; b7 = MULSHIFT32(m_COS4_0, a7 - a6) << 1; + b6 += b7; + buf[4] = b4 + b6; buf[5] = b5 + b7; + buf[6] = b5 + b6; buf[7] = b7; + + buf += 8; + } + buf -= 32; /* reset */ + + /* sample 0 - always delayed one block */ + d = dest + 64*16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + s = buf[ 0]; d[0] = d[8] = s; + + /* samples 16 to 31 */ + d = dest + offset + (oddBlock ? m_VBUF_LENGTH : 0); + + s = buf[ 1]; d[0] = d[8] = s; d += 64; + + tmp = buf[25] + buf[29]; + s = buf[17] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 9] + buf[13]; d[0] = d[8] = s; d += 64; + s = buf[21] + tmp; d[0] = d[8] = s; d += 64; + + tmp = buf[29] + buf[27]; + s = buf[ 5]; d[0] = d[8] = s; d += 64; + s = buf[21] + tmp; d[0] = d[8] = s; d += 64; + s = buf[13] + buf[11]; d[0] = d[8] = s; d += 64; + s = buf[19] + tmp; d[0] = d[8] = s; d += 64; + + tmp = buf[27] + buf[31]; + s = buf[ 3]; d[0] = d[8] = s; d += 64; + s = buf[19] + tmp; d[0] = d[8] = s; d += 64; + s = buf[11] + buf[15]; d[0] = d[8] = s; d += 64; + s = buf[23] + tmp; d[0] = d[8] = s; d += 64; + + tmp = buf[31]; + s = buf[ 7]; d[0] = d[8] = s; d += 64; + s = buf[23] + tmp; d[0] = d[8] = s; d += 64; + s = buf[15]; d[0] = d[8] = s; d += 64; + s = tmp; d[0] = d[8] = s; + + /* samples 16 to 1 (sample 16 used again) */ + d = dest + 16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + + s = buf[ 1]; d[0] = d[8] = s; d += 64; + + tmp = buf[30] + buf[25]; + s = buf[17] + tmp; d[0] = d[8] = s; d += 64; + s = buf[14] + buf[ 9]; d[0] = d[8] = s; d += 64; + s = buf[22] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 6]; d[0] = d[8] = s; d += 64; + + tmp = buf[26] + buf[30]; + s = buf[22] + tmp; d[0] = d[8] = s; d += 64; + s = buf[10] + buf[14]; d[0] = d[8] = s; d += 64; + s = buf[18] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 2]; d[0] = d[8] = s; d += 64; + + tmp = buf[28] + buf[26]; + s = buf[18] + tmp; d[0] = d[8] = s; d += 64; + s = buf[12] + buf[10]; d[0] = d[8] = s; d += 64; + s = buf[20] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 4]; d[0] = d[8] = s; d += 64; + + tmp = buf[24] + buf[28]; + s = buf[20] + tmp; d[0] = d[8] = s; d += 64; + s = buf[ 8] + buf[12]; d[0] = d[8] = s; d += 64; + s = buf[16] + tmp; d[0] = d[8] = s; + + /* this is so rarely invoked that it's not worth making two versions of the output + * shuffle code (one for no shift, one for clip + variable shift) like in IMDCT + * here we just load, clip, shift, and store on the rare instances that es != 0 + */ + if (es) { + d = dest + 64*16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); + + d = dest + offset + (oddBlock ? m_VBUF_LENGTH : 0); + for (i = 16; i <= 31; i++) { + s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); d += 64; + } + + d = dest + 16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH); + for (i = 15; i >= 0; i--) { + s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); d += 64; + } + } +} + +/*********************************************************************************************************************** + * P O L Y P H A S E + **********************************************************************************************************************/ +inline +short ClipToShort(int x, int fracBits){ + + /* assumes you've already rounded (x += (1 << (fracBits-1))) */ + x >>= fracBits; + +#ifndef __XTENSA__ + /* Ken's trick: clips to [-32768, 32767] */ + //ok vor generic case (fb) + int sign = x >> 31; + if (sign != (x >> 15)) + x = sign ^ ((1 << 15) - 1); + + return (short)x; +#else + //this is better on xtensa (fb) + asm ("clamps %0, %1, 15" : "=a" (x) : "a" (x) : ); + return x; +#endif +} +/*********************************************************************************************************************** + * Function: PolyphaseMono + * + * Description: filter one subband and produce 32 output PCM samples for one channel + * + * Inputs: pointer to PCM output buffer + * number of "extra shifts" (vbuf format = Q(DQ_FRACBITS_OUT-2)) + * pointer to start of vbuf (preserved from last call) + * start of filter coefficient table (in proper, shuffled order) + * no minimum number of guard bits is required for input vbuf + * (see additional scaling comments below) + * + * Outputs: 32 samples of one channel of decoded PCM data, (i.e. Q16.0) + * + * Return: none + **********************************************************************************************************************/ +void PolyphaseMono(short *pcm, int *vbuf, const uint32_t *coefBase){ + int i; + const uint32_t *coef; + int *vb1; + int vLo, vHi, c1, c2; + uint64_t sum1L, sum2L, rndVal; + + rndVal = (uint64_t)( 1ULL << ((m_DQ_FRACBITS_OUT - 2 - 2 - 15) - 1 + (32 - m_CSHIFT)) ); + + /* special case, output sample 0 */ + coef = coefBase; + vb1 = vbuf; + sum1L = rndVal; + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi=*(vb1+(23-(j))); // 0...7 + sum1L=MADD64(sum1L, vLo, c1); sum1L=MADD64(sum1L, vHi, -c2); + } + *(pcm + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* special case, output sample 16 */ + coef = coefBase + 256; + vb1 = vbuf + 64*16; + sum1L = rndVal; + for(int j=0; j<8; j++){ + c1=*coef; coef++; vLo=*(vb1+(j)); sum1L = MADD64(sum1L, vLo, c1); // 0...7 + } + *(pcm + 16) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* main convolution loop: sum1L = samples 1, 2, 3, ... 15 sum2L = samples 31, 30, ... 17 */ + coef = coefBase + 16; + vb1 = vbuf + 64; + pcm++; + + /* right now, the compiler creates bad asm from this... */ + for (i = 15; i > 0; i--) { + sum1L = sum2L = rndVal; + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j))); + sum1L=MADD64(sum1L, vLo, c1); sum2L = MADD64(sum2L, vLo, c2); + sum1L=MADD64(sum1L, vHi, -c2); sum2L = MADD64(sum2L, vHi, c1); + } + vb1 += 64; + *(pcm) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*i) = ClipToShort((int)SAR64(sum2L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + pcm++; + } +} +/*********************************************************************************************************************** + * Function: PolyphaseStereo + * + * Description: filter one subband and produce 32 output PCM samples for each channel + * + * Inputs: pointer to PCM output buffer + * number of "extra shifts" (vbuf format = Q(DQ_FRACBITS_OUT-2)) + * pointer to start of vbuf (preserved from last call) + * start of filter coefficient table (in proper, shuffled order) + * no minimum number of guard bits is required for input vbuf + * (see additional scaling comments below) + * + * Outputs: 32 samples of two channels of decoded PCM data, (i.e. Q16.0) + * + * Return: none + * + * Notes: interleaves PCM samples LRLRLR... + **********************************************************************************************************************/ +void PolyphaseStereo(short *pcm, int *vbuf, const uint32_t *coefBase){ + int i; + const uint32_t *coef; + int *vb1; + int vLo, vHi, c1, c2; + uint64_t sum1L, sum2L, sum1R, sum2R, rndVal; + + rndVal = (uint64_t)( 1 << ((m_DQ_FRACBITS_OUT - 2 - 2 - 15) - 1 + (32 - m_CSHIFT)) ); + + /* special case, output sample 0 */ + coef = coefBase; + vb1 = vbuf; + sum1L = sum1R = rndVal; + + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j))); + sum1L=MADD64(sum1L, vLo, c1); sum1L=MADD64(sum1L, vHi, -c2); + vLo=*(vb1+32+(j)); vHi=*(vb1+32+(23-(j))); + sum1R=MADD64(sum1R, vLo, c1); sum1R=MADD64(sum1R, vHi, -c2); \ + } + *(pcm + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 1) = ClipToShort((int)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* special case, output sample 16 */ + coef = coefBase + 256; + vb1 = vbuf + 64*16; + sum1L = sum1R = rndVal; + + for(int j=0; j<8; j++){ + c1=*coef; coef++; vLo = *(vb1+(j)); sum1L = MADD64(sum1L, vLo, c1); + vLo = *(vb1+32+(j)); sum1R = MADD64(sum1R, vLo, c1); + } + *(pcm + 2*16 + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*16 + 1) = ClipToShort((int)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + + /* main convolution loop: sum1L = samples 1, 2, 3, ... 15 sum2L = samples 31, 30, ... 17 */ + coef = coefBase + 16; + vb1 = vbuf + 64; + pcm += 2; + + /* right now, the compiler creates bad asm from this... */ + for (i = 15; i > 0; i--) { + sum1L = sum2L = rndVal; + sum1R = sum2R = rndVal; + + for(int j=0; j<8; j++){ + c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j))); + sum1L=MADD64(sum1L, vLo, c1); sum2L=MADD64(sum2L, vLo, c2); + sum1L=MADD64(sum1L, vHi, -c2); sum2L=MADD64(sum2L, vHi, c1); + vLo=*(vb1+32+(j)); vHi=*(vb1+32+(23-(j))); + sum1R=MADD64(sum1R, vLo, c1); sum2R=MADD64(sum2R, vLo, c2); + sum1R=MADD64(sum1R, vHi, -c2); sum2R=MADD64(sum2R, vHi, c1); + } + vb1 += 64; + *(pcm + 0) = ClipToShort((int)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 1) = ClipToShort((int)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*2*i + 0) = ClipToShort((int)SAR64(sum2L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + *(pcm + 2*2*i + 1) = ClipToShort((int)SAR64(sum2R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15); + pcm += 2; + } +} diff --git a/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h new file mode 100644 index 0000000..b202147 --- /dev/null +++ b/yoRadio/src/audioI2S/mp3_decoder/mp3_decoder.h @@ -0,0 +1,513 @@ +// based om helix mp3 decoder +#pragma once + +#include "Arduino.h" +#include "assert.h" + +static const uint8_t m_HUFF_PAIRTABS =32; +static const uint8_t m_BLOCK_SIZE =18; +static const uint8_t m_NBANDS =32; +static const uint8_t m_MAX_REORDER_SAMPS =(192-126)*3; // largest critical band for short blocks (see sfBandTable) +static const uint16_t m_VBUF_LENGTH =17*2* m_NBANDS; // for double-sized vbuf FIFO +static const uint8_t m_MAX_SCFBD =4; // max scalefactor bands per channel +static const uint16_t m_MAINBUF_SIZE =1940; +static const uint8_t m_MAX_NGRAN =2; // max granules +static const uint8_t m_MAX_NCHAN =2; // max channels +static const uint16_t m_MAX_NSAMP =576; // max samples per channel, per granule + +enum { + ERR_MP3_NONE = 0, + ERR_MP3_INDATA_UNDERFLOW = -1, + ERR_MP3_MAINDATA_UNDERFLOW = -2, + ERR_MP3_FREE_BITRATE_SYNC = -3, + ERR_MP3_OUT_OF_MEMORY = -4, + ERR_MP3_NULL_POINTER = -5, + ERR_MP3_INVALID_FRAMEHEADER = -6, + ERR_MP3_INVALID_SIDEINFO = -7, + ERR_MP3_INVALID_SCALEFACT = -8, + ERR_MP3_INVALID_HUFFCODES = -9, + ERR_MP3_INVALID_DEQUANTIZE = -10, + ERR_MP3_INVALID_IMDCT = -11, + ERR_MP3_INVALID_SUBBAND = -12, + + ERR_UNKNOWN = -9999 +}; + +typedef struct MP3FrameInfo { + int bitrate; + int nChans; + int samprate; + int bitsPerSample; + int outputSamps; + int layer; + int version; +} MP3FrameInfo_t; + +typedef struct SFBandTable { + int/*short*/ l[23]; + int/*short*/ s[14]; +} SFBandTable_t; + +typedef struct BitStreamInfo { + unsigned char *bytePtr; + unsigned int iCache; + int cachedBits; + int nBytes; +} BitStreamInfo_t; + +typedef enum { /* map these to the corresponding 2-bit values in the frame header */ + Stereo = 0x00, /* two independent channels, but L and R frames might have different # of bits */ + Joint = 0x01, /* coupled channels - layer III: mix of M-S and intensity, Layers I/II: intensity and direct coding only */ + Dual = 0x02, /* two independent channels, L and R always have exactly 1/2 the total bitrate */ + Mono = 0x03 /* one channel */ +} StereoMode_t; + +typedef enum { /* map to 0,1,2 to make table indexing easier */ + MPEG1 = 0, + MPEG2 = 1, + MPEG25 = 2 +} MPEGVersion_t; + +typedef struct FrameHeader { + int layer; /* layer index (1, 2, or 3) */ + int crc; /* CRC flag: 0 = disabled, 1 = enabled */ + int brIdx; /* bitrate index (0 - 15) */ + int srIdx; /* sample rate index (0 - 2) */ + int paddingBit; /* padding flag: 0 = no padding, 1 = single pad byte */ + int privateBit; /* unused */ + int modeExt; /* used to decipher joint stereo mode */ + int copyFlag; /* copyright flag: 0 = no, 1 = yes */ + int origFlag; /* original flag: 0 = copy, 1 = original */ + int emphasis; /* deemphasis mode */ + int CRCWord; /* CRC word (16 bits, 0 if crc not enabled) */ +} FrameHeader_t; + +typedef struct SideInfoSub { + int part23Length; /* number of bits in main data */ + int nBigvals; /* 2x this = first set of Huffman cw's (maximum amplitude can be > 1) */ + int globalGain; /* overall gain for dequantizer */ + int sfCompress; /* unpacked to figure out number of bits in scale factors */ + int winSwitchFlag; /* window switching flag */ + int blockType; /* block type */ + int mixedBlock; /* 0 = regular block (all short or long), 1 = mixed block */ + int tableSelect[3]; /* index of Huffman tables for the big values regions */ + int subBlockGain[3]; /* subblock gain offset, relative to global gain */ + int region0Count; /* 1+region0Count = num scale factor bands in first region of bigvals */ + int region1Count; /* 1+region1Count = num scale factor bands in second region of bigvals */ + int preFlag; /* for optional high frequency boost */ + int sfactScale; /* scaling of the scalefactors */ + int count1TableSelect; /* index of Huffman table for quad codewords */ +} SideInfoSub_t; + +typedef struct SideInfo { + int mainDataBegin; + int privateBits; + int scfsi[m_MAX_NCHAN][m_MAX_SCFBD]; /* 4 scalefactor bands per channel */ +} SideInfo_t; + +typedef struct { + int cbType; /* pure long = 0, pure short = 1, mixed = 2 */ + int cbEndS[3]; /* number nonzero short cb's, per subbblock */ + int cbEndSMax; /* max of cbEndS[] */ + int cbEndL; /* number nonzero long cb's */ +} CriticalBandInfo_t; + +typedef struct DequantInfo { + int workBuf[m_MAX_REORDER_SAMPS]; /* workbuf for reordering short blocks */ +} DequantInfo_t; + +typedef struct HuffmanInfo { + int huffDecBuf[m_MAX_NCHAN][m_MAX_NSAMP]; /* used both for decoded Huffman values and dequantized coefficients */ + int nonZeroBound[m_MAX_NCHAN]; /* number of coeffs in huffDecBuf[ch] which can be > 0 */ + int gb[m_MAX_NCHAN]; /* minimum number of guard bits in huffDecBuf[ch] */ +} HuffmanInfo_t; + +typedef enum HuffTabType { + noBits, + oneShot, + loopNoLinbits, + loopLinbits, + quadA, + quadB, + invalidTab +} HuffTabType_t; + +typedef struct HuffTabLookup { + int linBits; + int tabType; /*HuffTabType*/ +} HuffTabLookup_t; + +typedef struct IMDCTInfo { + int outBuf[m_MAX_NCHAN][m_BLOCK_SIZE][m_NBANDS]; /* output of IMDCT */ + int overBuf[m_MAX_NCHAN][m_MAX_NSAMP / 2]; /* overlap-add buffer (by symmetry, only need 1/2 size) */ + int numPrevIMDCT[m_MAX_NCHAN]; /* how many IMDCT's calculated in this channel on prev. granule */ + int prevType[m_MAX_NCHAN]; + int prevWinSwitch[m_MAX_NCHAN]; + int gb[m_MAX_NCHAN]; +} IMDCTInfo_t; + +typedef struct BlockCount { + int nBlocksLong; + int nBlocksTotal; + int nBlocksPrev; + int prevType; + int prevWinSwitch; + int currWinSwitch; + int gbIn; + int gbOut; +} BlockCount_t; + +typedef struct ScaleFactorInfoSub { /* max bits in scalefactors = 5, so use char's to save space */ + char l[23]; /* [band] */ + char s[13][3]; /* [band][window] */ +} ScaleFactorInfoSub_t; + +typedef struct ScaleFactorJS { /* used in MPEG 2, 2.5 intensity (joint) stereo only */ + int intensityScale; + int slen[4]; + int nr[4]; +} ScaleFactorJS_t; + +/* NOTE - could get by with smaller vbuf if memory is more important than speed + * (in Subband, instead of replicating each block in FDCT32 you would do a memmove on the + * last 15 blocks to shift them down one, a hardware style FIFO) + */ +typedef struct SubbandInfo { + int vbuf[m_MAX_NCHAN * m_VBUF_LENGTH]; /* vbuf for fast DCT-based synthesis PQMF - double size for speed (no modulo indexing) */ + int vindex; /* internal index for tracking position in vbuf */ +} SubbandInfo_t; + +typedef struct MP3DecInfo { + /* buffer which must be large enough to hold largest possible main_data section */ + unsigned char mainBuf[m_MAINBUF_SIZE]; + /* special info for "free" bitrate files */ + int freeBitrateFlag; + int freeBitrateSlots; + /* user-accessible info */ + int bitrate; + int nChans; + int samprate; + int nGrans; /* granules per frame */ + int nGranSamps; /* samples per granule */ + int nSlots; + int layer; + + int mainDataBegin; + int mainDataBytes; + int part23Length[m_MAX_NGRAN][m_MAX_NCHAN]; +} MP3DecInfo_t; + + + + +/* format = Q31 + * #define M_PI 3.14159265358979323846 + * double u = 2.0 * M_PI / 9.0; + * float c0 = sqrt(3.0) / 2.0; + * float c1 = cos(u); + * float c2 = cos(2*u); + * float c3 = sin(u); + * float c4 = sin(2*u); + */ + +const int c9_0 = 0x6ed9eba1; +const int c9_1 = 0x620dbe8b; +const int c9_2 = 0x163a1a7e; +const int c9_3 = 0x5246dd49; +const int c9_4 = 0x7e0e2e32; + + + +const int c3_0 = 0x6ed9eba1; /* format = Q31, cos(pi/6) */ +const int c6[3] = { 0x7ba3751d, 0x5a82799a, 0x2120fb83 }; /* format = Q31, cos(((0:2) + 0.5) * (pi/6)) */ + +/* format = Q31 + * cos(((0:8) + 0.5) * (pi/18)) + */ +const uint32_t c18[9] = { 0x7f834ed0, 0x7ba3751d, 0x7401e4c1, 0x68d9f964, 0x5a82799a, 0x496af3e2, 0x36185aee, 0x2120fb83, 0x0b27eb5c}; + +/* scale factor lengths (num bits) */ +const char m_SFLenTab[16][2] = { {0, 0}, {0, 1}, {0, 2}, {0, 3}, {3, 0}, {1, 1}, {1, 2}, {1, 3}, + {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}, {4, 2}, {4, 3}}; + +/* NRTab[size + 3*is_right][block type][partition] + * block type index: 0 = (bt0,bt1,bt3), 1 = bt2 non-mixed, 2 = bt2 mixed + * partition: scale factor groups (sfb1 through sfb4) + * for block type = 2 (mixed or non-mixed) / by 3 is rolled into this table + * (for 3 short blocks per long block) + * see 2.4.3.2 in MPEG 2 (low sample rate) spec + * stuff rolled into this table: + * NRTab[x][1][y] --> (NRTab[x][1][y]) / 3 + * NRTab[x][2][>=1] --> (NRTab[x][2][>=1]) / 3 (first partition is long block) + */ +const char NRTab[6][3][4] = { + {{ 6, 5, 5, 5}, {3, 3, 3, 3}, {6, 3, 3, 3}}, + {{ 6, 5, 7, 3}, {3, 3, 4, 2}, {6, 3, 4, 2}}, + {{11, 10, 0, 0}, {6, 6, 0, 0}, {6, 3, 6, 0}}, + {{ 7, 7, 7, 0}, {4, 4, 4, 0}, {6, 5, 4, 0}}, + {{ 6, 6, 6, 3}, {4, 3, 3, 2}, {6, 4, 3, 2}}, + {{ 8, 8, 5, 0}, {5, 4, 3, 0}, {6, 6, 3, 0}} +}; + + + +/* optional pre-emphasis for high-frequency scale factor bands */ +const char preTab[22] = { 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0 }; + +/* pow(2,-i/4) for i=0..3, Q31 format */ +const int pow14[4] PROGMEM = { + 0x7fffffff, 0x6ba27e65, 0x5a82799a, 0x4c1bf829 +}; + + +/* + * Minimax polynomial approximation to pow(x, 4/3), over the range + * poly43lo: x = [0.5, 0.7071] + * poly43hi: x = [0.7071, 1.0] + * + * Relative error < 1E-7 + * Coefs are scaled by 4, 2, 1, 0.5, 0.25 + */ +const unsigned int poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 }; +const unsigned int poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 }; + +/* pow(2, i*4/3) as exp and frac */ +const int pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 }; + +const int pow2frac[8] PROGMEM = { + 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94, + 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6 +}; + +const uint16_t m_HUFF_OFFSET_01= 0; +const uint16_t m_HUFF_OFFSET_02= 9 + m_HUFF_OFFSET_01; +const uint16_t m_HUFF_OFFSET_03= 65 + m_HUFF_OFFSET_02; +const uint16_t m_HUFF_OFFSET_05= 65 + m_HUFF_OFFSET_03; +const uint16_t m_HUFF_OFFSET_06=257 + m_HUFF_OFFSET_05; +const uint16_t m_HUFF_OFFSET_07=129 + m_HUFF_OFFSET_06; +const uint16_t m_HUFF_OFFSET_08=110 + m_HUFF_OFFSET_07; +const uint16_t m_HUFF_OFFSET_09=280 + m_HUFF_OFFSET_08; +const uint16_t m_HUFF_OFFSET_10= 93 + m_HUFF_OFFSET_09; +const uint16_t m_HUFF_OFFSET_11=320 + m_HUFF_OFFSET_10; +const uint16_t m_HUFF_OFFSET_12=296 + m_HUFF_OFFSET_11; +const uint16_t m_HUFF_OFFSET_13=185 + m_HUFF_OFFSET_12; +const uint16_t m_HUFF_OFFSET_15=497 + m_HUFF_OFFSET_13; +const uint16_t m_HUFF_OFFSET_16=580 + m_HUFF_OFFSET_15; +const uint16_t m_HUFF_OFFSET_24=651 + m_HUFF_OFFSET_16; + +const int huffTabOffset[m_HUFF_PAIRTABS] PROGMEM = { + 0, m_HUFF_OFFSET_01, m_HUFF_OFFSET_02, m_HUFF_OFFSET_03, + 0, m_HUFF_OFFSET_05, m_HUFF_OFFSET_06, m_HUFF_OFFSET_07, + m_HUFF_OFFSET_08, m_HUFF_OFFSET_09, m_HUFF_OFFSET_10, m_HUFF_OFFSET_11, + m_HUFF_OFFSET_12, m_HUFF_OFFSET_13, 0, m_HUFF_OFFSET_15, + m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, + m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, + m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, + m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24,}; + +const HuffTabLookup_t huffTabLookup[m_HUFF_PAIRTABS] PROGMEM = { + { 0, noBits }, + { 0, oneShot }, + { 0, oneShot }, + { 0, oneShot }, + { 0, invalidTab }, + { 0, oneShot }, + { 0, oneShot }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, loopNoLinbits }, + { 0, invalidTab }, + { 0, loopNoLinbits }, + { 1, loopLinbits }, + { 2, loopLinbits }, + { 3, loopLinbits }, + { 4, loopLinbits }, + { 6, loopLinbits }, + { 8, loopLinbits }, + { 10, loopLinbits }, + { 13, loopLinbits }, + { 4, loopLinbits }, + { 5, loopLinbits }, + { 6, loopLinbits }, + { 7, loopLinbits }, + { 8, loopLinbits }, + { 9, loopLinbits }, + { 11, loopLinbits }, + { 13, loopLinbits }, +}; + + +const int quadTabOffset[2] PROGMEM = {0, 64}; +const int quadTabMaxBits[2] PROGMEM = {6, 4}; + +/* indexing = [version][samplerate index] + * sample rate of frame (Hz) + */ +const int samplerateTab[3][3] PROGMEM = { + { 44100, 48000, 32000 }, /* MPEG-1 */ + { 22050, 24000, 16000 }, /* MPEG-2 */ + { 11025, 12000, 8000 }, /* MPEG-2.5 */ +}; + + + +/* indexing = [version][layer] + * number of samples in one frame (per channel) + */ +const int/*short*/samplesPerFrameTab[3][3] PROGMEM = { { 384, 1152, 1152 }, /* MPEG1 */ +{ 384, 1152, 576 }, /* MPEG2 */ +{ 384, 1152, 576 }, /* MPEG2.5 */ +}; + +/* layers 1, 2, 3 */ +const short bitsPerSlotTab[3] = { 32, 8, 8 }; + +/* indexing = [version][mono/stereo] + * number of bytes in side info section of bitstream + */ +const int/*short*/sideBytesTab[3][2] PROGMEM = { { 17, 32 }, /* MPEG-1: mono, stereo */ +{ 9, 17 }, /* MPEG-2: mono, stereo */ +{ 9, 17 }, /* MPEG-2.5: mono, stereo */ +}; + +/* indexing = [version][sampleRate][long (.l) or short (.s) block] + * sfBandTable[v][s].l[cb] = index of first bin in critical band cb (long blocks) + * sfBandTable[v][s].s[cb] = index of first bin in critical band cb (short blocks) + */ +const SFBandTable_t sfBandTable[3][3] PROGMEM = { + { /* MPEG-1 (44, 48, 32 kHz) */ + { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 }, + {0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192} }, + { {0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 }, + {0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192} }, + { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576 }, + {0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192} } }, + { /* MPEG-2 (22, 24, 16 kHz) */ + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192} }, + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 332, 394, 464, 540, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192} }, + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192} }, }, + { /* MPEG-2.5 (11, 12, 8 kHz) */ + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } }, + { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 }, + {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } }, + { {0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576 }, + {0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192 } }, }, +}; + + +/* indexing = [intensity scale on/off][left/right] + * format = Q30, range = [0.0, 1.414] + * + * illegal intensity position scalefactors (see comments on ISFMpeg1) + */ +const int ISFIIP[2][2] PROGMEM = { + {0x40000000, 0x00000000}, /* mid-side off */ + {0x40000000, 0x40000000}, /* mid-side on */ +}; + +const unsigned char uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f}; + +/* anti-alias coefficients - see spec Annex B, table 3-B.9 + * csa[0][i] = CSi, csa[1][i] = CAi + * format = Q31 + */ +const uint32_t csa[8][2] PROGMEM = { + {0x6dc253f0, 0xbe2500aa}, + {0x70dcebe4, 0xc39e4949}, + {0x798d6e73, 0xd7e33f4a}, + {0x7ddd40a7, 0xe8b71176}, + {0x7f6d20b7, 0xf3e4fe2f}, + {0x7fe47e40, 0xfac1a3c7}, + {0x7ffcb263, 0xfe2ebdc6}, + {0x7fffc694, 0xff86c25d}, +}; + +/* format = Q30, right shifted by 12 (sign bits only in top 12 - undo this when rounding to short) + * this is to enable early-terminating multiplies on ARM + * range = [-1.144287109, 1.144989014] + * max gain of filter (per output sample) ~= 2.731 + * + * new (properly sign-flipped) values + * - these actually are correct to 32 bits, (floating-pt coefficients in spec + * chosen such that only ~20 bits are required) + * + * Reordering - see table 3-B.3 in spec (appendix B) + * + * polyCoef[i] = + * D[ 0, 32, 64, ... 480], i = [ 0, 15] + * D[ 1, 33, 65, ... 481], i = [ 16, 31] + * D[ 2, 34, 66, ... 482], i = [ 32, 47] + * ... + * D[15, 47, 79, ... 495], i = [240,255] + * + * also exploits symmetry: D[i] = -D[512 - i], for i = [1, 255] + * + * polyCoef[256, 257, ... 263] are for special case of sample 16 (out of 0) + * see PolyphaseStereo() and PolyphaseMono() + */ + +// prototypes +bool MP3Decoder_AllocateBuffers(void); +void MP3Decoder_FreeBuffers(); +int MP3Decode( unsigned char *inbuf, int *bytesLeft, short *outbuf, int useSize); +void MP3GetLastFrameInfo(); +int MP3GetNextFrameInfo(unsigned char *buf); +int MP3FindSyncWord(unsigned char *buf, int nBytes); +int MP3GetSampRate(); +int MP3GetChannels(); +int MP3GetBitsPerSample(); +int MP3GetBitrate(); +int MP3GetOutputSamps(); + +//internally used +void MP3Decoder_ClearBuffer(void); +void PolyphaseMono(short *pcm, int *vbuf, const uint32_t *coefBase); +void PolyphaseStereo(short *pcm, int *vbuf, const uint32_t *coefBase); +void SetBitstreamPointer(BitStreamInfo_t *bsi, int nBytes, unsigned char *buf); +unsigned int GetBits(BitStreamInfo_t *bsi, int nBits); +int CalcBitsUsed(BitStreamInfo_t *bsi, unsigned char *startBuf, int startOffset); +int DequantChannel(int *sampleBuf, int *workBuf, int *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi); +void MidSideProc(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, int mOut[2]); +void IntensityProcMPEG1(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, int midSideFlag, int mixFlag, int mOut[2]); +void IntensityProcMPEG2(int x[m_MAX_NCHAN][m_MAX_NSAMP], int nSamps, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, ScaleFactorJS_t *sfjs, int midSideFlag, int mixFlag, int mOut[2]); +void FDCT32(int *x, int *d, int offset, int oddBlock, int gb);// __attribute__ ((section (".data"))); +void FreeBuffers(); +int CheckPadBit(); +int UnpackFrameHeader(unsigned char *buf); +int UnpackSideInfo(unsigned char *buf); +int DecodeHuffman( unsigned char *buf, int *bitOffset, int huffBlockBits, int gr, int ch); +int MP3Dequantize( int gr); +int IMDCT( int gr, int ch); +int UnpackScaleFactors( unsigned char *buf, int *bitOffset, int bitsAvail, int gr, int ch); +int Subband(short *pcmBuf); +short ClipToShort(int x, int fracBits); +void RefillBitstreamCache(BitStreamInfo_t *bsi); +void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int *scfsi, int gr, ScaleFactorInfoSub_t *sfisGr0); +void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int gr, int ch, int modeExt, ScaleFactorJS_t *sfjs); +int MP3FindFreeSync(unsigned char *buf, unsigned char firstFH[4], int nBytes); +void MP3ClearBadFrame( short *outbuf); +int DecodeHuffmanPairs(int *xy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset); +int DecodeHuffmanQuads(int *vwxy, int nVals, int tabIdx, int bitsLeft, unsigned char *buf, int bitOffset); +int DequantBlock(int *inbuf, int *outbuf, int num, int scale); +void AntiAlias(int *x, int nBfly); +void WinPrevious(int *xPrev, int *xPrevWin, int btPrev); +int FreqInvertRescale(int *y, int *xPrev, int blockIdx, int es); +void idct9(int *x); +int IMDCT36(int *xCurr, int *xPrev, int *y, int btCurr, int btPrev, int blockIdx, int gb); +void imdct12(int *x, int *out); +int IMDCT12x3(int *xCurr, int *xPrev, int *y, int btPrev, int blockIdx, int gb); +int HybridTransform(int *xCurr, int *xPrev, int y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc); +inline uint64_t SAR64(uint64_t x, int n) {return x >> n;} +inline int MULSHIFT32(int x, int y) { int z; z = (uint64_t) x * (uint64_t) y >> 32; return z;} +inline uint64_t MADD64(uint64_t sum64, int x, int y) {sum64 += (uint64_t) x * (uint64_t) y; return sum64;}/* returns 64-bit value in [edx:eax] */ +inline uint64_t xSAR64(uint64_t x, int n){return x >> n;} +inline int FASTABS(int x){ return __builtin_abs(x);} //xtensa has a fast abs instruction //fb +#define CLZ(x) __builtin_clz(x) //fb diff --git a/yoRadio/telnet.cpp b/yoRadio/telnet.cpp new file mode 100644 index 0000000..9c05de5 --- /dev/null +++ b/yoRadio/telnet.cpp @@ -0,0 +1,269 @@ +#include +#include "WiFi.h" + +#include "config.h" +#include "telnet.h" +#include "player.h" +#include "display.h" + +Telnet telnet; + +bool Telnet::_isIPSet(IPAddress ip) { + return ip.toString() == "0.0.0.0"; +} + +bool Telnet::begin() { + if (WiFi.status() == WL_CONNECTED || _isIPSet(WiFi.softAPIP())) { + server.begin(); + server.setNoDelay(true); + Serial.printf("Ready! Use 'telnet %s 23' to connect\n", WiFi.localIP().toString().c_str()); + return true; + } else { + return false; + } +} + +void Telnet::stop() { + server.stop(); +} + +void Telnet::emptyClientStream(WiFiClient client) { + client.flush(); + delay(50); + while (client.available()) { + client.read(); + } +} + +void Telnet::cleanupClients() { + for (int i = 0; i < MAX_TLN_CLIENTS; i++) { + if (!clients[i].connected()) { + if (clients[i]) { + Serial.printf("Client [%d] is %s\n", i, clients[i].connected() ? "connected" : "disconnected"); + clients[i].stop(); + } + } + } +} + +void Telnet::loop() { + uint8_t i; + if (WiFi.status() == WL_CONNECTED) { + if (server.hasClient()) { + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (!clients[i] || !clients[i].connected()) { + if (clients[i]) { + clients[i].stop(); + } + clients[i] = server.available(); + if (!clients[i]) Serial.println("available broken"); + on_connect(clients[i].remoteIP().toString().c_str(), i); + clients[i].setNoDelay(true); + emptyClientStream(clients[i]); + break; + } + } + if (i >= MAX_TLN_CLIENTS) { + server.available().stop(); + } + } + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (clients[i] && clients[i].connected() && clients[i].available()) { + String inputstr = clients[i].readStringUntil('\n'); + inputstr.trim(); + on_input(inputstr.c_str(), i); + } + } + } else { + for (i = 0; i < MAX_TLN_CLIENTS; i++) { + if (clients[i]) { + clients[i].stop(); + } + } + delay(1000); + } + yield(); +} + +void Telnet::print(const char *buf) { + for (int id = 0; id < MAX_TLN_CLIENTS; id++) { + if (clients[id] && clients[id].connected()) { + print(id, buf); + } + } + Serial.print(buf); +} + +void Telnet::print(byte id, const char *buf) { + if (clients[id] && clients[id].connected()) { + clients[id].print(buf); + } +} + +void Telnet::printf(const char *format, ...) { + char buf[MAX_PRINTF_LEN]; + va_list args; + va_start (args, format ); + vsnprintf(buf, MAX_PRINTF_LEN, format, args); + va_end (args); + for (int id = 0; id < MAX_TLN_CLIENTS; id++) { + if (clients[id] && clients[id].connected()) { + clients[id].print(buf); + } + } + Serial.print(buf); +} + +void Telnet::printf(byte id, const char *format, ...) { + if (clients[id] && clients[id].connected()) { + va_list argptr; + va_start(argptr, format); + char *szBuffer = 0; + const size_t nBufferLength = vsnprintf(szBuffer, 0, format, argptr) + 1; + if (nBufferLength == 1) return; + szBuffer = (char *) malloc(nBufferLength); + if (! szBuffer) return; + vsnprintf(szBuffer, nBufferLength, format, argptr); + va_end(argptr); + clients[id].print(szBuffer); + free(szBuffer); + } +} + +void Telnet::on_connect(const char* str, byte clientId) { + Serial.printf("Telnet: [%d] %s connected\n", clientId, str); + print(clientId, "\nWelcome to ёRadio!\n(Use ^] + q to disconnect.)\n> "); +} + +void Telnet::info() { + byte volume; + telnet.printf("##CLI.INFO#\n"); + char timeStringBuff[50]; + strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &display.timeinfo); + telnet.printf("##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + telnet.printf("##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name); + if (player.mode == PLAYING) { + telnet.printf("##CLI.META#: %s\n", config.station.title); + } + telnet.printf("##CLI.VOL#: %d\n", config.store.volume); + if (player.mode == PLAYING) { + telnet.printf("##CLI.PLAYING#\n"); + } else { + telnet.printf("##CLI.STOPPED#\n"); + } + telnet.printf("> "); +} + +void Telnet::on_input(const char* str, byte clientId) { + if (strlen(str) == 0) return; + if (strcmp(str, "cli.prev") == 0 || strcmp(str, "prev") == 0) { + player.prev(); + return; + } + if (strcmp(str, "cli.next") == 0 || strcmp(str, "next") == 0) { + player.next(); + return; + } + if (strcmp(str, "cli.toggle") == 0 || strcmp(str, "toggle") == 0) { + player.toggle(); + return; + } + if (strcmp(str, "cli.stop") == 0 || strcmp(str, "stop") == 0) { + player.mode = STOPPED; + display.title("[stopped]"); + info(); + return; + } + if (strcmp(str, "cli.start") == 0 || strcmp(str, "start") == 0 || strcmp(str, "cli.play") == 0 || strcmp(str, "play") == 0) { + player.play(config.store.lastStation); + return; + } + if (strcmp(str, "sys.boot") == 0 || strcmp(str, "boot") == 0 || strcmp(str, "reboot") == 0) { + ESP.restart(); + return; + } + if (strcmp(str, "cli.vol") == 0 || strcmp(str, "vol") == 0) { + printf(clientId, "##CLI.VOL#: %d\n", config.store.volume); + return; + } + if (strcmp(str, "sys.date") == 0) { + char timeStringBuff[50]; + strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &display.timeinfo); + telnet.printf("##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + return; + } + int volume; + if (sscanf(str, "vol(%d)", &volume) == 1 || sscanf(str, "cli.vol(\"%d\")", &volume) == 1 || sscanf(str, "vol %d", &volume) == 1) { + if (volume < 0) volume = 0; + if (volume > 254) volume = 254; + player.setVol(volume, false); + return; + } + + if (strcmp(str, "cli.audioinfo") == 0 || strcmp(str, "audioinfo") == 0) { + printf(clientId, "##CLI.AUDIOINFO#: %d\n", config.store.audioinfo>0); + return; + } + byte ainfo; + if (sscanf(str, "audioinfo(%d)", &ainfo) == 1 || sscanf(str, "cli.audioinfo(\"%d\")", &ainfo) == 1 || sscanf(str, "audioinfo %d", &ainfo) == 1) { + config.store.audioinfo = ainfo>0; + config.save(); + return; + } + if (strcmp(str, "cli.smartstart") == 0 || strcmp(str, "smartstart") == 0) { + printf(clientId, "##CLI.SMARTSTART#: %d\n", config.store.smartstart); + return; + } + byte sstart; + if (sscanf(str, "smartstart(%d)", &sstart) == 1 || sscanf(str, "cli.smartstart(\"%d\")", &sstart) == 1 || sscanf(str, "smartstart %d", &sstart) == 1) { + config.store.smartstart = sstart; + config.save(); + return; + } + if (strcmp(str, "cli.list") == 0 || strcmp(str, "list") == 0) { + printf(clientId, "#CLI.LIST#\n"); + File file = SPIFFS.open(PLAYLIST_PATH, "r"); + if (!file || file.isDirectory()) { + return; + } + char sName[BUFLEN], sUrl[BUFLEN]; + int sOvol; + byte c = 1; + while (file.available()) { + if (config.parseCSV(file.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) { + printf(clientId, "#CLI.LISTNUM#: %*d: %s, %s\n", 3, c, sName, sUrl); + c++; + } + } + printf(clientId, "##CLI.LIST#\n"); + printf(clientId, "> "); + return; + } + if (strcmp(str, "cli.info") == 0 || strcmp(str, "info") == 0) { + printf(clientId, "##CLI.INFO#\n"); + char timeStringBuff[50]; + strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &display.timeinfo); + printf(clientId, "##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset + printf(clientId, "##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name); + if (player.mode == PLAYING) { + printf(clientId, "##CLI.META#: %s\n", config.station.title); + } + printf(clientId, "##CLI.VOL#: %d\n", config.store.volume); + if (player.mode == PLAYING) { + printf(clientId, "##CLI.PLAYING#\n"); + } else { + printf(clientId, "##CLI.STOPPED#\n"); + } + printf(clientId, "> "); + return; + } + + uint8_t sb; + if (sscanf(str, "play(%d)", &sb) == 1 || sscanf(str, "cli.play(\"%d\")", &sb) == 1 || sscanf(str, "play %d", &sb) == 1 ) { + if (sb < 1) sb = 1; + if (sb >= config.store.countStation) sb = config.store.countStation; + player.play(sb); + return; + } + telnet.printf(clientId, "unknown command: %s\n> ", str); +} diff --git a/yoRadio/telnet.h b/yoRadio/telnet.h new file mode 100644 index 0000000..5e3cb4e --- /dev/null +++ b/yoRadio/telnet.h @@ -0,0 +1,33 @@ +#ifndef telnet_h +#define telnet_h + +#include + +#define MAX_TLN_CLIENTS 5 +#define MAX_PRINTF_LEN BUFLEN+50 + +class Telnet { + public: + Telnet() {}; + bool begin(); + void loop(); + void stop(); + void print(byte id, const char *buf); + void print(const char *buf); + void printf(byte id, const char *format, ...); + void printf(const char *format, ...); + void cleanupClients(); + void info(); + protected: + WiFiServer server = WiFiServer(23); + WiFiClient clients[MAX_TLN_CLIENTS]; + void emptyClientStream(WiFiClient client); + void on_connect(const char* str, byte clientId); + void on_input(const char* str, byte clientId); + private: + bool _isIPSet(IPAddress ip); +}; + +extern Telnet telnet; + +#endif diff --git a/yoRadio/yoRadio.ino b/yoRadio/yoRadio.ino new file mode 100644 index 0000000..9897095 --- /dev/null +++ b/yoRadio/yoRadio.ino @@ -0,0 +1,42 @@ +#include "Arduino.h" + +#include "options.h" +#include "config.h" +#include "telnet.h" +#include "player.h" +#include "display.h" +#include "player.h" +#include "network.h" +#include "netserver.h" +#include "controls.h" + +void setup() { + Serial.begin(115200); + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + config.init(); + display.init(); + player.init(); + network.begin(); + if (network.status != CONNECTED) { + netserver.begin(); + display.start(); + return; + } + initControls(); + netserver.begin(); + telnet.begin(); + player.setVol(config.store.volume, true); + display.start(); + if(config.store.smartstart==1) player.play(config.store.lastStation); +} + +void loop() { + if (network.status == CONNECTED) { + telnet.loop(); + player.loop(); + loopControls(); + } + display.loop(); + netserver.loop(); +}