IzP_6&~aEd%uxMH+!)LM@b(I88tn1Ly`XaOCvZSRmS%ht6mDE+
zTJDpj;;J0InHsCLb@KH+D5{6_(BP+g$w(GsS<3XO0HKA3(wX~`feYl=XHxXZJ(1$)
z=NNGBwoQ1%qqp!lH~KevW(CMp(vxbXqUGYi4WC@|@h1!8Fe&aCWEmp9i2a7JchZe5
zKZj5-x?TN_?F3ZfPK3MP^w-?r<^JvJZtN~7(OA64v~0iQ_ItLQi{o@ICHwm^KTS-G5f_MQ~6oj{CU@WroK
z?6g>B`XUZFLknbrqTU{P`)T0S`-b-?hAIeDzfcbc~mC8^Xxv6V#h@ajT#_nfW%$va%^Dt-
zH32P)-Mm|T-Z+(%_w~{3jBh9LsBH5ur7xH-UYiq?tu2`r}2*}
zExTWyt23#II6rKYtZLuaC8Iey;(PVg7Xy0Axq;rZJ{x>P69$n*^(y_&n6KU>+$rA0
zH{y&X3+JesM1R^e;;SpDZO=lpQb?%<7m%qA6{HqxIpFcSwC<J#5jw0;P*QT^@~odjD)B|Fv_YAL;v&-xSBPrmVR4E9j~@9APl5I?8;Q793}c5GRiB?|f=c~T
zi92SG>s#UIX(nX0qV5)NbF#X^le5^okyjmnyGGw~2L;k(gFLI(1$8L_)_V^Ye|l&h
zP_A5294INuzXr^48W*sgb~~r71cJx$UIz3=hDBAx-?!mN;v&PG#!SBQ7@@5nu8=ew41pvl5YJ
zBJfCL9#pNZv<68RId^=s7Qui`(=$7TbH;K`3Bp6M;+dp7N~>*9nTj|x!o-s>yRxN4
z_6N>~fyLN%A*B7MkGIBnkUC)^J<$mRyA}9C61-Y+ptBb+f|oCZuoaoy{Q_;2Xszr>
z`yr`Oig>tk-+fTAlt3|?Y0ESdsv7h#keVFO}O$D^fO)gxZ$LP!W@L6QeS_er%gsm<*IyV_TSWGWtkFY|&ga1loE#TF8
z+`B7$`(Om6q52%!u6N0fT8_xm@I#?x440RMw@t&8{-tI&J$2#tiUp+
z)CN;B8umIWbz*}sxw2KKvW|6;sc1D)L-&ObXu8|&$jN27)k$~a##4sdjGea?td~}M
zG|C8yDHRAuZ9-e8D%57m(hzQv_6+dzL+u@9&$4xsZ%qX7
z)gLL)PF1Hp(5n$VSzclqWGsSc33lOPSe$=Vy*nbz;okio6x6s^jBBn4)Q*N5mkUx^
z4!vG-5K8U=HiLKOfBN%p6B!#0Equ=AgO>S0vMj|V{<=dw1oKLDt+SnUHtj3am(B3#
zqpBW~!f(RH0FG(ZQk!DucmY#Cao{*SK9`x`F2E^Wp*=QHW>x=WT)w{;R`SKT!fAi-L9H&&L
z#gCItYfHBL@zPivdB22`+!w$!_ls!Q>3#%`h}v}A=WcSq=$sP?1a-6d=*gHz*mf%B
zFDLR;j60=iPbBaA;icMBikO3b&4O9R?mVHT5LO^$X4wpPRYD!Li6nz)^74vGEfX9*+l!#Fx1NryA8E0>Q@Ram3c4-x2jMJV(&m
zOI;j`VYtZ0sy-WKM&XGK1#AudGwy&KK?hVSf3yMY`jnfMyVdcn{`T1aMV&S~
z!~r0clCeLtHqj4-Lxra6wOZyq
zajOANB%UcTpup|HO<$QRu-jq2N3UNl&{58fZq%C@^TkNU&@B*#`?0SlvpAR5FFz&p
zL|;DEq<`|-py!9Yl+gmeSD|%WvF@El5>b
z&KhEjKDm5CN7*#bnxpHD^0H*~AeD&Xne;3$r
zbE{aZg^UWAF(3QyG=hI*oj~`Puamw$ULDHdx_$Ch{`p6FQ{wuv6=thrQIx%B;WH#Rf`49&%1Yx@pXQD^F!**#AFF5ELQ)f_s13ynEF55TIE0K&D5mm9JAQe#Wbr}h75FbHo&$)s
zzZIJszuc%te7XN?kPHBL@&Cjg0M_xGO*pycQ)Laa`=#OYL_>%X9uib2>-BDpv!qFH
z6bL|{jln2q^D3E&mj-KBh__ZO_0Ko+*A8cB8)=rg@MIZ_O>3tCCx4o!)A(+VmbFD9
z*obzcDS@Re|3V;=_^66GzuL)El_iuucfh~M@V|HFHmHz!%Q<6G8=Rc*%?~bMq_gq8
z+Miee27$wVtTbAoD971RyV`HlgtzYp1^S`k$pyF5tm8j8C65+VxYZ?j)^sqI;lNdL
zW(01%0`C=!v90KCtPe_={C-fcbIUT1)KL8OmzY=6c3J)9U+~EmykE@MHT#x+#{&
z1=|`7sjDZ`Z)K%pWe)9lAC-)@Gc~Fz!d()=qx`^CnWS13_(rJ1`YT0Sslj5{2PaIS
z3xGHUl#5$Wt{Z7j)#STDASfV9c?5X9^R$W(jg;}lAC_g%LYbs9MCO=dvmf}LU~>*N
zdMeHYXY4iOB*ks$rqECo|AT+;-^Bdfc5bU|!Zw?s=q(#X4`Hjwm9D$_A(8JkM~@9#
zUnUoOzHPM8U)zz!GUm(0kSbf0lwkopKo&WcN=kC7F#lVBa-6Y{FeIr2C)eB9U
zi9VO1RaKu~g5Xl$u(oVH3{%8o;;b{+K&S#)Hrp}iq)=d*EbPxWF@_`U{6nTYb!`cq
z#f}dIUD{q`dlD%sl%%}<4EVl19J2oozPEj_p!DW;>N=36#a!ay`!TO4c}8NHLetxn
zPJ0PS2NeeGpQpsav*-*RL7nz{_I@w=qurLzWpE!YIh!Qm!^GYbVsYgL^v=NhoK`jG
z57zPXC0#mqqvmd5dw)4#WW;ZEEh1avI8Xv2!v%J^`I8YHf}f9@mnTV%c=bZ*E%Hcr
z>OPl2*5=^+x(|u%PqiKad(G8BJMUl1qBor`+Ui2Otkg!uzHc;vqfdmjzW2EK4P}U=
z3ULfS!UG8jR&IpNJZ9vbwG1WB=Cd0bxE<<)@wuL0oDj;0nHF4Pu;@$tRD1~sHJ-Lt
z`U)*B&Px&pRnK@61Gxgg`ihiKl$ZD34MF+y96;<`Ev4lU+kk_7-l^xEr35@-3m*BRzTNL=x)va5+Fq4#%@?rA=8)a>n5?(Av`0w>
zbL6>$26JM(h>*+x;4#nyO5qb*ry+K-j-GRTM8`u#ux^OhXcx4e%0aC*K@07U0fz)G
z2{Aum=%0l^nc3ya7h_ghU+Az0UahCP?@Jk@OAVT#BSsN8mVQdbV_#Qb7luI1yVW@p
z?}+gu1mJ6B^qo@y8x(b!g#^cx6H(oU=j(5-cI5>Jg3;IN+z7xGj#eDrsbHtsRVXd{
zElc1iEDXo7|8r%Fb&pSZwXI@!+GNZ`?z{Q0O+*J><>;+5N{{Mk?`8?wwvJ3%TM=QdiA8oPu|w5(nK&5k8_dMyn>x6%ptIJ0HwwtQ}G*uMs%&
zIN|k4)05adJmA&Pg4{kOKR^WCR?2xy`Qqg6k@qAtXzc_O?Mzp_)I{8|I?6<9F#&!M
z<9!*7ZK2+)Jf8{d19TE6;NUod4weY}a}}zj<26gaPx7)Ug0IT~}Ry4Epxi{y=~LF@Sex3tj<-Xb0&VuVN4AL6_N)_MiISETVjI`J!TQ
zM#?q&0;6cq6|B
zKruIppeufv+o318R-5!HWSPAA&cEbh7vb_u!c&hb(%S18r_j
zd@ce{?hHWSN2~3G?Mr7(itEvaSYqPaP3@Tz*^tcY5(H9nwc(arLzO#d{7insI&9{I
z04~IC53G$PQnUqtKf7t|7Hdk$WliR^aps3k$VzN~zJ7hi1&CaFo}3N4_zU)-cdLJ7
zm$sH6Zn+oWJs>t9PXbjcW&Os(AcyksZu(E8jc$PJAg#HPH
zS*y1dI$0};_n_{kY7^
z=i;G!QPFM~xX)xTMi8-^X;+b+tlRoE)hT)Xi>oB-pgS{>YXWU=i*826_Zcn+!ojj=
zd#~CH(H`+7akzKGq#`MUTd%o>OM}h#l~aYD@U5NZDm)Q?;$-h&J9KUp-|yvNS5x2P
z$M*?^!$owe5_6A{^uhB17-h2Uwp#~CS+_ZNq199~zHpj-z3KpFJAOH#ZXj-<>WL^y
z;HL5F_w=jVeskln#}w#p8-K3mT{W}1`^V0;4DLPJvg`gb*d$rnQeUCQIck{WhKoo6
z0Ju2n9cInyS({RgP0^IU+#}7*f2J)Pi3#(dvNskSXo<3G$;&Hq4ul^1XqQqQ5p#og
zUyKSatG_pNe>i-1BEZt@Jr=8Lm^IfSn^M}>vH$~(%f%%vinpQm+jE9|JIQ6qRXM?!
z_tVZrE@1@!*aVWl^eOKKR5oMB0}D{i=@~ISgY*bHm1PubycjB5eeakE7~8A<{k$5i
z9yPZvC32zJ&=5-C<-902aqL=EsIX<2%rRydBXlsdFa4w3qeITSJy62^Wn?@?(35)t
zIvn#P-A*)b;?YAvQ(iG57bLLL>(-B}ZXjpr)HDFwXAKGvq28h1#i*fy5JrZOTy|4Q
z-d_^FLR(hwEDer~F!B1+Zl86m#nM$UkbCjsKH#F4LEI91$A)VeeQcoxDdXbBu3w?S
z_dCsb*T~U?URO(x_3h`W5sNbMZ{tl`fn|Hv>-_vz_H0}ggdGb|W7Dk5@;=gc*=2fV
z2rSzg&~~ozm&O3OsnqWh(LZ6?OOwN-%h`uV^{&5*7nrdr(bi9^pSxyS1)0F{i!XDG
zM>VN5cq;BS7|qxu`I&|&)tlX12JxM{sTui}R0whVQo-jMRfUzGeyX8=%_?Q28`1pSo~ZwM42l1xRA?
zcmKV+EDq?-_g}h4A9h#!yE~)6;}!uVPiRJghKgg1Hu!*VvzTyrkDjwnK$?2^YOYgH
zBsw0!IzX=++#{;{l%$-lNV6W|n|K6Cf09HS%TwBU-U9R}hxzILN3$#%;T_GBUVwf}*n9hiqX|I;<;A6+++
zeb`R8L=3919sR&C-G0b#){tl0?=Otq8`o1v`jRk(LvcFx7gegE0>Kh6T?^0$rGGSR
zh2%dcSun&397><44nvZ*=T}zrtvdE*%PYw(sWB(B_w?K>olKRn)NoR;f8gSWYs$WV
znybMoy9PYviLy?T3*Xt3`=Fl1J7U7Gw=C}eX`l)ln^@4!j2J6lW6N^Vg-6@oU2l^t`8qT(s9I2TC5z=*
z9Iih)Eh;amJFGDPQ}+5S@$3F&?X1iAZ^x%}40(2&Ln}2>_(b!!NDaxTgWj{5r2V&=
z%XEufr@_p##{(pe?b=FzcwW8NCN4dx={L9mjlL#=*zBjK4ryk$wp+;|={G_Y5}%)z
zOjdhj1UO=9c>Lj5w2u)8=~~^L6a0>VP&-Hbdin~$x)orpYslmEim?oF
zlG!307?60_x8na0~0w-}pN*>2%OyP50|L+3GI9zLNx?bpW6^UT>mNX%8I75+#Qr*w;kQO
zQ;F0a7aHK&`afGQ$h}n@-J%vaf%J_~00OCyTbcC`z4lL?=)?i1(|;gB+&@as(yQJo
z;#Lm)_eLkN)R9;H=iz&qg(VQ#zfR~dgvpIV1w=7drD-?;VA|j6o4U)DxV0&3z6hXs
zK***0FZ&MBJV32|3&8SV&;YzL<=G)r>tWo;w}|S!%hEJiF9Fz`1jjWW(rp(SjZ*+7
z%trdr;$p}U&4-JJh&Pvy1B3KMwBjPbsA7kebMu+4=q!v6v2rEP06%Ox{-yK~NM}cv
zS)lle@u}k>%I@3ohi0%8R0e9i7{8fv0meOi?+gcUK_0-H#R--rvJUR;jEqvD<8WMX*6MunMJ{D4I^a78K3nAJWJ+&Xp%v?N%@8Z7-Mje6AO9
z{;Nd5J(M>IxJ*8}upVr}j#iQ$-HR0{q6NMq;E$uMLC*|DuLh$rq@^!eJec>M&}+K;
z{BMO?M_>8xY;cx;J3J7l`;*7MpI9JX5UZcaLbFDD9IqLtzS-O;Mf+J`Mmabv>%a9(
zHbK&cE*Ds+ox%QgfuMlZOgZ#Dt~gu?Z{l<6+gK{pWnXjn7J&?XO_|?g2ll2skEuWR
z*8015(1xV@;W*DVc3FmmW`w7
zf-u9REU@e7BF^L$$vAm3{^(lDb6GoSwlLY2zeVGeAvvKix_kU61{(`CMOh)F^#w`<&%ib%+f3QA`^xgid=nlGM
zW}zDhP^RP-wYo>(??L_w8n_(3m!JCa`GUJEu7qCC52!`{sN`3~Q@sm#k@Vu%0G<5Z
z{49=+?L45A-?F6=+ibGog19N*@_h5(W<8WhrowNVjH{`C_pAVz*Z*klSKy_s_;`Ek
z%54*AH=q7(GVN`-*I2vjKZb>AK!;lUCAt@&wrysIbxy6PivJ+}pR-vA)wt*CA+Ywh
zi_zjj&Yv-)pYYD|22%LBKlTPQ#~=NNG^Z--h1}d5ex8kD
z!QU%l2+959G+&?|w7|C>90lCg0>pL+FwbegJcZn+83D`uAML#fG!*XJH*N`83Q;OW
zrIIb7l4VBrB4po{4xf*H%~wp=fUDQ*l@|bt?T=Jn*lVIml6DHj#7AT#=(e0=;PqA
zkTfWqff?ov4x6h7G#!qosQ=!#H*){l-}?|!v4=^_@an0%P$!E!hw^;lK6W=eyBA
zb#me=p-#C2f5$Iiy7b{NWN5>&*5>8}t$M9}Zk;o6WbC9Y!n?!v%2huc0E0WRaSOb)
z@q8Da-;QYONLOwTmn`*=6U`4}{Z#@h|4IUhWnOQ#`RF|7WZqUcHWxO=N?JIdx
zm2vZjmB)t4+BX_r{OGm7q$q?j>%|hJ%*X`N{MU6TwIien3DB~`B
za`k6n!rkl#i)a9P-<`PhA;fKi*oL!K4>p-MOQH`OvPKI^MtA>Y@VLybwm#faHD%Dpp}lqJ+;eIGw8i#lyG>9dn*h1
zk%$pJb<9;%XpSjZ3fBc5N&b?hwwecD`_^|)uM))}|GPiXwXH3M3ENpGsC#0rRwC~<
zR=5YZSIE%dq7Gllnrl44LoFnwty(B(Y)|`wL-oRTmPt;JnD-VH)+BfJz}l1h-q${uC)qFP6p7YcJ;jXVaZhoq`h{;@t;xc-
zUJ|MpD7rS4%#|GX;Rsr*cVgt&ZM90Hkcs~BLPPOOJta7&`#W(u2WvJ}VoA(~2Mb<3
z(M!)hrr%}yma`a<27zi-La`|05;B_4)3TZCMqE_o%5U_d{oBLploRm@ud+;$g-6Gw
z#P6*QMY$xo11OOK*I!eT;z(GTE2YPR`VSM&8sGesDz-*Yy#`?*aHA1tt(zO)eONFH
zwRdj*EFST}F`=tl`@HX}PmFDYof_fh%@&vphEvVi)CRKV#2#*jYFKrRE8jxrC9O(;
zdOa1Me~LOQqJI;qPgp2K&9*1az+K!{
z&$~zYYZ%Eu!|;unt#u#VDz(G8*ybaHsl-czfd@jQDm)Ar?&84%H{`Q*4m%vvC)^zB
z^Z#;_&|E){T>Z>2S0#*|V8apg+R%5OCZWbR)$_)Kg~41$ebhVGtQPCB$5!V37HuQb`uZacXm)!bm)(q6b^U(`Ma+IWx6-4=_!W=<%^;WxtpL
zTM4CiX7;v$e5%fsv|0Hg@V;{}Rx
z04YIik~)f@(r0h|%#N}W(2M`5>;#iWLMx_bM@&mECbwJDo@~M#CJ$BvDQtQ_OCfOO
zov`Mk>xs*r#7D+^2};N5uOt^3=ISR1zIEUnTnp&7=L|`cZw^+Y|VC0@v~raC!u%
z3;MCGSquApVy@=Vs+}@sC%jNjUZ@14zgNJ+LfKJ_>cz4>Tp&Ky3~{&sZ}fAwMb}^s
z!skHB?NOw+H59(+V0}h9?oG4ijvi8SWd-_Yys?I87YkPUr{J0er*(&|hnr7kO|dyZ
z!P61cG(BGXRP9#RA89(%mw-mWnwY^5Uf=Vni0kXpXhqi~3#~qyMf?4}g4K#4qpkP6
zv5QR*i*-ldzs_GQoo(@`+}A5R)0e;Vi29Yh{H;G^mDpz&wq!-RzKnwotM1));L1@n
zXp{R}c~)bwoxeA8r(yG3I+pxfgSpU0jyk;Z-?b8(noTI3)s|G$t}t&80elUs%YLA-
zs2#4zzeAv&q+d#;{0t~^zXUzfq)ult2Q)|Dfc+Av^Yo%6Byb66;n%Yixbk^EJp#J-
zd%YB9pmWLso%42fCmql^d4SG&L4Sq@=$x!T=X@>b4y^0v4F8*}eXo_}Pazt%^3BJ)
z{`!`+pAfPFx;tZw5!Ir_B^*SSp7;|U2S$ktx@d`+qQq+k*51t
zMAiCKrt;|kR`2avm~A#Yl;E(`YXS2i&tKsVwZzPRg14p=;rWw~G-M_ROLB3@P1!eK
zYv0lXTpXB37E4H6pVUGY$->X|tLQiH$0R#ioawEa69*R5^z=XTc3e4SYoDZq_
zo2vB%vHl#ba0NP&LREUS&(B}YFri!K-Q1fw_FDd=_{$NQF#n%ro#o1Qu&&3`;)T8M
z;x!lSPu8Jig(lJMa8z@le58@H0+^>z+{PL}%3thQH>z}K0O2KKyoVc-{nrPlrRBdg
z?>lxHx-r?KwNrEf8hxw?mWg^uowCU}OPHNZje6==F1I!?Mz=SFpQVTk?JrHTe=_y;
zcEGKIy(XTJeCJ~f`)q1g_`nNpahfP;{z@?dJMSlllss+ZETu#UOWS<=;aZ!pEh;su
z8-7OAk0>6q3jLv79;mbSx*Kj)4kDpY_ioaHKrCRKQsE@j!=eyA3=7JDNtri{DLJAL
zF|0=@#zc9y7OZE;)(X_8y^~(d5+0X;`Ye=-a88DjGH3}RAWmYWrh$pPnSHp+g*
zlLnx9MnsO;)wP^sKkUUfUwLkB8yHyqfY_P59^ARTtSqdeF
z^&?%f>63?ZB7ayURHzHv(cVJ?eoC?1#@#A2&+)_-dmuKe*S#3G!ZR#D
znDrqN2BoUQN$zwsdt1j7hrX{}?}=-IZkYPuIO^zp$`8KG<(HuQsoMq@S2jBuw<1M}
z=%XwrFc*XBJ9?2Wb2rO(qlF^#jlEQ_fe@GO-j`CcwZ^+$J!dbJTDCQuW1SO}6LN{n
zDPk{k)`Vz7iry^XOH?R3ubjI&%ReC`rz7wh*Atc;KyKJMbmY=R=10;bhkICA;u&;E
zt3|#AL%s#mx~Z0Mdewk=`=N^#0=b5fm%0}6>;!VTSj|-k0{18^MiIV=yN#yX<-V%+
zJK6)No6cU`V8-ROw!TajT@??
zfHs!*XELgfrXT59akkE>$=bTy)n^}InEA2#XLq|L#8g4#2-oe2Og$u5Y!5ap4$cIo
z9^l&)jhr3x@$QzGdO(=%wQ6G3t1{WQ9TXV-+xcXG_0mCG;G
zx?@nArIK$xUn*urg;ZeQWEIvEQhYB?ywT8hR7h%uEu`xs?K#r1o<1Bn+Z#{6C_g#j
zH@OYK?zI(a)f<=?ocexnu;A1`pU72(5rw0FO%k?tiM=hTb#22=
zIbN5Xt!@93pZJ92u4(+S;<^WA^w}#NiT%G@rpB3`HFCZK&+Et5Cl%oRoz3U;yPVAP
z5x$`pD|V&D3cU@e1q84M@w(U!I=Q{
z^Q(=gE>*6zpK5Qy%5dmpZMMxfVNmM_gS-DY9ZKZUqvyiP=GqSp
b_?#ccW7zNX_EHmB^4g@5t
zf``H&54I13It`(CdY-+*5`*1VpPhDq5kJAL>YdoB2?vx
z9O@+q-dy*4TD=~VNYca=L3q=0jMmov>FMO=-bYzXIQ0Xd&_~?L3Vb=U>fO^7_@xWr
zJP<+I2|&pahD9~@#BY7VWb|w`2qx_DI!t&7-o3#nz7!+2Ly=ip#hImxgU%hEbh}OA
zJO48Q20>tH=aC{8)u#O+M>#A{ai9fO-kY-oa^Q
zc@xwFK;*j{HvcTRF(_>B7~sD&I<$N61%MX8QyAl}d@1J*#S=V^Bw2Ui?HUP*Oix{c#MIXY^7oeDHGhqR_Akq5gnl5t_6jG#;hJh!DKT@
z)c$|DX8%#mYn)qZYjEX;G(mrwrOpBFSHyRNnu0k$Y^US^5lh${9XiU6Hwbx|yPDrE
zIoI&wAPQDq-dE0X{h#YFHGG!7O3sg#Ys9S_>kH#vORMHsJvTew^&3*I|CU|9NB^8*
zyiu{m+m9>iqM~2zX&IjVfzsDL2b|QV$v#GZ;PHZOp%yrGHx+EkAHe!0zQ|mzkX8;q
z&qG?+v9h#y~fu!B4ykHU7Fr{QR4Zf2!Ap(6B*L
zvV$tznx0XL?|HSB-k5n-DpytJ$=TnM?KRSZzzflT@sH7eVXdEq+z31$U5}2wlo4is7g+1^;ldBya^%>OG<}q4
zF26d1c=K9J1CV=yp6lER3+Y>UaVM-@2<51l8r5IA=;Z8PD?}I#-l(iB4Yw8o{QK!m
zcjJwaJev{4)i}ko*Es|xjHSXI7`yrs8cfc3im3I9X^5TVmglRNxJ>aXDaS?y=a*1B
z5S6V`R~`d>`ALh$fYFmq!-S>9?KZCaXQxYwKLJB$xz
zy7epL7L(LEs``}U1KqZfsF<=}>6v+DT4V}Kt&ESFnro}IB%Df^K@
zwiw19@?vba9LJw>0)s(fwjDQ7mZ=TpI9LJTa-3KT7ghM#2pG5^g@%FuL
zd^CQLnOotH3=S$9e#ofrd~?kye0E^}yWhQzdp$={C%@Q=Cy;>0Rb=Ab1vFh{=cEG
zNwE!Z#WW|5`9PEFN=NdJjYtkwlWL^|lG4P>ST2Si5PAahPg^Mj)R@4r_SNJ5QrwSG
zqJ}=76>T2(!fe=&4>=kAZ1?1}D*d_9c%0)AaaYda#b~B@zqGU|a78UR&b`y|OMdih
ze!mz^4BxJ&v=Xome89rvIFwTbXKMh_`pdhwSPh3F9^I008dttnfNQ-Xkaq2SfZCk-MObSJ*{o!KMyKZTYD
zd-Gx<21b0_KD!uvGV{Jh@(m+RPID&+W<-~WAWvaL%igvcU;GM7DuZdYszpr{H_rAI
zKG?sQFuZl{>VYwlCdbZw1+Tal?oZ}#@wzL;cQJNoh|`lNO0*Pe+i7HYXc82w97h
z={g^LaLfuwBVBi6?a4kJE1_E9&{isVY^tYh2(*-A=
zbtphshrD{4>%S7E(>rrea-)$^&HF}gj;hL4+1p8bQ_)qS;^d@g;AiBm(3zKnFUgZ@
zb*RA(&hn7Ks6JdMOw5@&@8FS}rSFzKX}%$qs5^G%vbVFXLsH`*@A=nek{$)@)u(R=
zRFd=@Elk!eS~@Gdr-)}8sSGD1rPl0Hx-M$GmoBFHEkTbL103Fm2_`q3$PxRA{wY_B
zXyvC)Jec8NjCg*g*y8AA!c+Mf6?4aW|A(@*vvQ{iW7m4&(!#hEC}OMnwlP4!w!Jb00P7iTuk&T)tE@OO+Jqq
z@au8s-oJx&fR{;FRHdq3Bk@ZWScvB1PxUsaU`RhbbQcKvFFPB*t#$1&=1a>vPO1=&
z7f`4TwV<iWJ1!Pk{)&!p`q98fTK?ByN*S#X%pEN4Yxqc!6yUV%%u
ztF~}cxC}#IjNFCs`IEx$?AM@rcR|nR9rszk0WDWgINUG!#;Nf4VHE@WaT2x*$jnUQ
z)!`p?vlO<%DzFOX6B0FgUL7A>Vr!(XKMO9ENtKP`-bPo{+dBwXCMoUHXz$ZtImtcz
zzUI(ke^4aYR$bL9nh>9c8X2!^p9X~vg`T-Q$U>>GOeyMNWARx99doSHT}Zj0BrN?!$4L-*v=6l1CWf#Y1g(lS3d{eO7oEXA&{O~rNYHDg(7Yu^0>ic7fxvAxWe~5(
z1m5!H147RL#gGofKE9VzV*e0WM$tq&c~dDtiy){_Xogjr5*P_$9}oWwxj^}lQgU69
zlky>@Ja|3%f7FQo&|ZOy(vea;#A)g6}!3@4}E&I(HA^8_kV29q-aywnGG
z2kS~ppuV=->lTnI>+65dFWN-nXm3ytGJ>g?qQ}v?G-*asbh{
zyg(wo{%%xIUksV2%$%#uH3CEg@hs)4C8X)VKfVo8c{!Yx*_Fy&2TZ22wyZa)6`0Na
z)7#rieBOTAoG|b69FDy`T+qS;My#$ud$j?e7$RbxX|^?N@|Kt2=ZV4l+u72b*>%%W
z4}*0@&hLbKoG^U5=sQtj1es?k>ly@J6r)UPGy1(zsBG{hf&_Xn|8c)^!{pE<1eaDi
zVb#ncjRd^WLlW_w;XQvdTz;;9)%iW@AroTPHw9#-A9QKxKDO&LZnN@LDQvk
z8^N?{gs{O$BJSt;+Tpn$AgqVN2Vrf8HRM>-^kicPsO+!Rvt?f4*OgI8%Z)ykbrwHRrtKk@LR7L3rTj6qxtt
zb2R=wHoS4i3~`Ek>*Q7We*i_f
zmdg;Qu=jb_7(A{E2VXbwyBgs@Br{m0N*Am<9^s}c+yB>sVYqbq4nM_)W0UJnWTO4Pwf>KyJ3iWk7i(gviI%U
zVPe&$=28u2Gq8h4_;-s;FrR5iR{PG@RDZ2cVENvZfdnSO01v1L4lR(eq!wTSPt(=+
zffOZH6q}P~;vPtBLNP4CgcO5JCcxB`0LOp{$bSMLhsVHyAp#PfP%Kh#0>pw0CjbTc
z44mEnf7J>~R`&+M7*Sl5xOxhGq+)ejL8OBF3jm`Z2rk}5(|3~HC`mb=H6sB|`&kn<
z8GuoW8&*#A%QTjmTm`x5LN@_4@}#+`EcPJ=gxuQ(k-j|se_#72fuO-@l6nCVGOq4D
zSM%wp-1I*l-Uc3%PDy?@Qtf--znA00?ezMw+N)K$g;|kpDJR=)1E!A}wq_I|m~%48
zAi_h*e;Z7Y;IMB%rUojL{+&qvAa}gH%)9pO4zavgjUs%G7LVK^b<8EV=J%wn>gD<7
z96RK6HtN#2@=`x9*S&&%#9`@x@<5vm-3^GhzD4!&t;|9D`oWV(2B5%5XFH-ZW&ycm
z9dH3eOf2Gy^5JNsn!?xs$x?9@$my(23Rvs(H}_Ym15Njhu17h@^Ucx5uK~?nh>MtpitEr0RdjTQ@AGY8By`Io`jHNDpB#~T@X;aCqnGo_4K`(N|;
zRgQcfpi2=x7Pd#1TuC$4?1y%?=T;jCe@#j&o?1<1GrwzRcp#W=Q}GF%Z^ke;?Bl?s
zJ!Wv8c;ef=!PhtmUC0|~vif2le}kL%sIjOf8v&EgI3)HuY)AD3Z>Z{(ec*He;>(YYQKHrDV&cJ$4~tWjSM63b*p=j
zcx9Ct`$sh>S7>c*lDe-;Am~x#zcc
z>l%lAs2)WJo_8w^^bEfzVtEtFv!r|fjx#2HPgqKXr`1Wk`JD6nGkH>c7Y4tnE`NFH
z0^+s(e=RTV6wBOm_&Do+1VROW6pW_91&!P=ROOyE92qg_;JB$kcGL08azvl^*|K5y
zu@5rAFQm^r({Rkc9?M@w=a^cxrfhUQ?8wIxORary^x#-l3)8!gX6>oL5xQ+A;l}D=
zaT&6rxzkDDF@d@iafn|Zc&jUeBhZKdW7$u~Gvp_ywF;%XpTEPymn@S`{)#a)PDx!e
z4Kwr_!7DKbGmg(4H133+HToo{fm*yE!e%E1NU)?9iF&ql8uQ@YGrRW1ew;|mYQQQmIi@yO3&l$nw!gR
zUc~zz30EGPrs9wnc}0zP78T8ls{(ZEbg!HeO{cp>yN(eSwoGOR|IcHRx@iixI80D^
zTOGCmjTeQkHR8Q7inp8`jt=jl*Gdw67`E#uE{gdhW7TUg}RbEaBR{}CE
z=N}zh@QU4#@ihJ{WoHcmv77vtJ$&nGKF>;)^szdxMk-!jp@7~%62-!@{K}X{m=I1k
ze%8knW(8xX!_=$8ybiQXGkdKLU?6=j`?BQY??LL~f}S5Mi4<14%p^;N6LZgXW=CeO
zAkD*hB92%c>SwLQ-++t>R-5~7N!r^{3o&jiJ{mxDOe}cWZEzpG3QP!eKB864^yZ1|
z4;(4&{oje44I836#GTB1)Uf|PMXc9wdoBo$e#jP}Pn+F%M|mO-yfDAc_wstl@^O~z
z%vLx!0w=!@aXCS^Y}+|MITn2Rsu#}fa`*_G0=ipYC2D^UKH7gebZ*Vl3ZL+b&YC)y
zPf#u70h+wAI4m(V7A^mciT>*c(xGJ`9&&Ey39FMAq8c!0xZ+AJBq5V8kfm<&_P_vdl1zs_LfC~ct2)ft
z^lnG8>fqyj+4X_-8Fz1OtG#uYfp?m;dFwseG|7Kj#(Ato_|WaEX#qwOHCOai4iJce
ziy!?IU;CA8{RD5FCw%zT{GcUo1^evylDiDE>itk-|4}c%9maNxFrpr*RTZUJW3~
zdF3&|Rg!b3(
zK~_rEyRZ^=5I)gBu-|}Zl|911e;KU)=&kN!A9j8u1CY59Bv+e*xSXPZo6K|E*yFfB
zYaOXNAJO7=qkZAw6A#%SwE_+|hPL7=VV2Qb}
zGGSeV%1j}M9;qSo-#Q3;tKfC}+jZA&C-F?H1IN=C;m@^_y}xpzCCfW2ci%eUB2&_O~q8y&ZxIF)O)42QQQ&
zI(|OuIQhL6ZunB=iC
zJT4AWBvjzhjzh}0dSb?p^)9`Q*hxehYpFqRjJ=$=VDU@D^?EW{eHi^{bV(lT$o7X2}7CejaG
z2y5BjyzPw>Otk2@uCQ$=RI|ff(13?=NgIn69d(HB_9yzEdN5#;W5+-w
z7TagHL=MS>DI%5WxR)
zNv&Cb-PgXF(c%zxD73WjK%wd4_>m2-ra_WQK-S>A1N%BNi#3E${cJRRrODtfgOQ!|
zaQQ-yzr#j(*C2eo()-yg)?X6W+y5#O7jbv)2P#uxic|i6M)>tJyvNxHN!o3U9bfeG
z5q>g7BYAvEj5R;}lhb$ZsvL{bt`LJC!`&==pz3sal)V_ilCxb8PZ_hXJiOYm;hSAs
zxbNKzx~6CgxI4aGpR79_9eOVC+=t4Lhs}d!-M)E#QbfYdZ-b=>kB5R%u&GnoZ%hB=
z+l+k)Y!O+cmj_5KeQJ4}jnS@AXUR74Mb4>mZb+1ud$2@)+{!|GRc@v!u~L9B6&@_X
zwyG)kUbZ%jlo#3xvXvxLc6CLP9
zrw2M@uKCAy5_5rSCWsEUq8`oWMIQBYj>2jBS
zz@ESxCDSu&V9wVIALLSTUAkokL6T#(2lKFP!(HUtb2m3ifV^g$C~wu#li;yB+Pl;s
z(C=ATju@g{oTUyKXOA|M9L-&w;@V)i+#?s_cH{e~2{rlHg}kd9_DwM$T$}9~pl@H;
zrc;s)S*a-d1P=n}$aS{|OR|k5us3N5FKAz}3Y3*RLVSaR9zCWQMIoGKyH?VlsZwOh
z6G2RuSq%9*?M}dHy8a_rK*9e&PBI^VjcqOJ$^D3FK*3*<%uTVgd~bZ-HN3z+I{@;2
zC_z7b9h&e46#NU*^dR}i8D1Tie^vAU#w?^|xw-321VjQ^~ey&vwcbKpa1k@$h{z|
zVG>pl`HiV3No(XtPDm29q_k;10wi0c3iSO1S3i?PN}LfNU##wc_es@V!a%8-7{PsJ
z;nl?gn9eESfp_@-D58s=s)-)Sz<(jPG>=7{xL|cIAToDaBAY{US=En!F%8W?b?h%M;v8K9;r1Xv8lXZd*CG^m6cR5D`=>6oFu?r#aX_8A0H{agPR)hX_Lp2V*M@MXhw
z7xC}pjKs3O`&T@SM)*0lj%%(7*|_jhP150lI8K;~;!*tKE9}-HkrwJ^-UQKa$Q(fX}+Vz)HeWzKY_{FI|nVo`B-0D85i?^<4IyD)4copVEpg?W5GXr
zaNZyPn+Hb>Ln^xKB@&x+?K4c&K>;r}Q^!dUd*CZVmKN{&aB4!o(Jp=b{w4U4Pud?BjuP~f
zhXPPFNBe|d@l~c>qc^u%j|DW;1)b`?-{bb}U1qJ|ESHU}=
z6don%AFLY&d_i$JHJHD9ZCFvq?jW4B
zc2%z%zzKz;5>_=;-ZzUJ_|pwzV{9Zec6vP9JPYlc^UHIIG)B4WE&^TZZ47DNg0_bH
zz4$=1GuL+rNYQOWSs(yA+=O~tT=9NXreH+c%=HjMU2p6rPLKU4j+M&r3*j`w^^3P`
zG0f|5?#j@~ZOv^x`r$Kvkg%E_KTPlsKUS(!40d-QvqKFVbb>)5Yq3(!(i@aC-zUnB
z5W-eEV0H0qps6TYE+g<%g~>$9qBh%Z`+O703(p9sylMRa`h@TNVHhOqWD
z;Y`&oMt2}~kvyog=cS<|&>7YEKV>KR8~8B}IKOOEk1
z6a&>IQ(!)q^9<^dvdBMin9T9zSk1OCfK%LSHLv>7bggnt+n??O6e=&lA0t2Gl$@5(
zT@C|@*h}0?@pph0AG}DWkOEM(j?)0FbCs8pThX-3!CE@<;39A5q*Njm)yfnNptXLE
zlmw1bFuy>}-klv_IqgxczL>YC@nCkl}yg-z6Ih@2MRI
X= 202311L)
+#if !defined(__sgi) && (__STDC_VERSION__ >= 202311L)
// static_assert is a keyword in C23, do not define it
-#elif (__STDC_VERSION__ >= 201112L)
+#elif !defined(__sgi) && (__STDC_VERSION__ >= 201112L)
# define static_assert(cond, msg) _Static_assert(cond, msg)
#else
# ifndef GLUE
diff --git a/include/sfx.h b/include/sfx.h
index d37e333ac0..8d1fa7993a 100644
--- a/include/sfx.h
+++ b/include/sfx.h
@@ -2,6 +2,7 @@
#define SFX_H
#include "PR/ultratypes.h"
+#include "libc/assert.h"
#include "z64math.h"
/**
@@ -23,25 +24,50 @@
typedef enum SfxId {
NA_SE_NONE, // Requesting a sfx with this id will play no sound
+
NA_SE_PL_BASE = 0x7FF,
#include "tables/sfx/playerbank_table.h"
+ NA_SE_PL_END,
+
NA_SE_IT_BASE = 0x17FF,
#include "tables/sfx/itembank_table.h"
+ NA_SE_IT_END,
+
NA_SE_EV_BASE = 0x27FF,
#include "tables/sfx/environmentbank_table.h"
+ NA_SE_EV_END,
+
NA_SE_EN_BASE = 0x37FF,
#include "tables/sfx/enemybank_table.h"
+ NA_SE_EN_END,
+
NA_SE_SY_BASE = 0x47FF,
#include "tables/sfx/systembank_table.h"
+ NA_SE_SY_END,
+
NA_SE_OC_BASE = 0x57FF,
#include "tables/sfx/ocarinabank_table.h"
+ NA_SE_OC_END,
+
NA_SE_VO_BASE = 0x67FF,
#include "tables/sfx/voicebank_table.h"
+ NA_SE_VO_END,
+
NA_SE_MAX
} SfxId;
#undef DEFINE_SFX
+// These limits are due to the way Sequence 0 is programmed. There is also a global limit of 1024 entries for every bank
+// enforced in Audio_PlayActiveSfx in sfx.c
+static_assert(NA_SE_PL_END - (NA_SE_PL_BASE + 1) <= 512, "Player Bank SFX Table is limited to 512 entries due to Sequence 0");
+static_assert(NA_SE_IT_END - (NA_SE_IT_BASE + 1) <= 128, "Item Bank SFX Table is limited to 128 entries due to Sequence 0");
+static_assert(NA_SE_EV_END - (NA_SE_EV_BASE + 1) <= 512, "Environment Bank SFX Table is limited to 512 entries due to Sequence 0");
+static_assert(NA_SE_EN_END - (NA_SE_EN_BASE + 1) <= 768, "Enemy Bank SFX Table is limited to 768 entries due to Sequence 0");
+static_assert(NA_SE_SY_END - (NA_SE_SY_BASE + 1) <= 128, "System Bank SFX Table is limited to 128 entries due to Sequence 0");
+static_assert(NA_SE_OC_END - (NA_SE_OC_BASE + 1) <= 128, "Ocarina Bank SFX Table is limited to 128 entries due to Sequence 0");
+static_assert(NA_SE_VO_END - (NA_SE_VO_BASE + 1) <= 512, "Voice Bank SFX Table is limited to 512 entries due to Sequence 0");
+
typedef enum SfxPauseMenu {
/* 0 */ SFX_PAUSE_MENU_CLOSE,
/* 1 */ SFX_PAUSE_MENU_OPEN
diff --git a/linker_scripts/soundfont.ld b/linker_scripts/soundfont.ld
index d914a7de31..c480e36867 100644
--- a/linker_scripts/soundfont.ld
+++ b/linker_scripts/soundfont.ld
@@ -4,14 +4,15 @@ OUTPUT_ARCH (mips)
SECTIONS {
- .rodata :
+ .rodata ALIGN(16) :
{
*(.data*)
*(.rodata*)
. = ALIGN(16);
- __LEN__ = . - ADDR(.rodata);
}
+ __LEN__ = ABSOLUTE(SIZEOF(.rodata));
+
/DISCARD/ :
{
*(*);
diff --git a/src/audio/session_config.c b/src/audio/session_config.c
index e0309468fe..37125864fe 100644
--- a/src/audio/session_config.c
+++ b/src/audio/session_config.c
@@ -1,5 +1,10 @@
#include "global.h"
#include "buffers.h"
+#include "assets/audio/sequence_sizes.h"
+#include "assets/audio/soundfont_sizes.h"
+#define SFX_SEQ_SIZE Sequence_0_SIZE
+#define AMBIENCE_SEQ_SIZE Sequence_1_SIZE
+#define SFX_SOUNDFONTS_SIZE (Soundfont_0_SIZE + Soundfont_1_SIZE + Soundfont_2_SIZE)
static s32 sBssPad[36];
AudioContext gAudioCtx;
@@ -14,21 +19,12 @@ const s16 gAudioTatumInit[] = {
TATUMS_PER_BEAT, // gTatumsPerBeat
};
-// TODO: Extract from table?
-#define NUM_SOUNDFONTS 41
-#define SFX_SEQ_SIZE 0xC6A0
-#define AMBIENCE_SEQ_SIZE 0xFC0
-#define SOUNDFONT_0_SIZE 0x81C0
-#define SOUNDFONT_1_SIZE 0x36D0
-#define SOUNDFONT_2_SIZE 0xCE0
-
// Sizes of everything on the init pool
#define AI_BUFFERS_SIZE (AIBUF_SIZE * ARRAY_COUNT(gAudioCtx.aiBuffers))
#define SOUNDFONT_LIST_SIZE (NUM_SOUNDFONTS * sizeof(SoundFont))
// 0x19BD0
-#define PERMANENT_POOL_SIZE \
- (SFX_SEQ_SIZE + AMBIENCE_SEQ_SIZE + SOUNDFONT_0_SIZE + SOUNDFONT_1_SIZE + SOUNDFONT_2_SIZE + 0x430)
+#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + AMBIENCE_SEQ_SIZE + SFX_SOUNDFONTS_SIZE + 0x430)
const AudioHeapInitSizes gAudioHeapInitSizes = {
ALIGN16(sizeof(gAudioHeap) - 0x100), // audio heap size
diff --git a/src/overlays/actors/ovl_Boss_02/z_boss_02.c b/src/overlays/actors/ovl_Boss_02/z_boss_02.c
index 0a6f660353..1baced1646 100644
--- a/src/overlays/actors/ovl_Boss_02/z_boss_02.c
+++ b/src/overlays/actors/ovl_Boss_02/z_boss_02.c
@@ -4,6 +4,7 @@
* Description: Twinmold
*/
+#include "prevent_bss_reordering.h"
#include "z_boss_02.h"
#include "z64rumble.h"
#include "z64shrink_window.h"
diff --git a/tools/audio/.gitignore b/tools/audio/.gitignore
index f0d3c612fd..5864deaf3c 100644
--- a/tools/audio/.gitignore
+++ b/tools/audio/.gitignore
@@ -1,5 +1,6 @@
__pycache__/
+afile_sizes
atblgen
sfpatch
sbc
diff --git a/tools/audio/Makefile b/tools/audio/Makefile
index df860ef72e..aeec49070d 100644
--- a/tools/audio/Makefile
+++ b/tools/audio/Makefile
@@ -1,4 +1,4 @@
-PROGRAMS := atblgen sfpatch sbc sfc
+PROGRAMS := afile_sizes atblgen sbc sfc sfpatch
ifeq ($(shell which xml2-config),)
$(error xml2-config not found. Did you install libxml2-dev?)
@@ -9,7 +9,7 @@ FORMAT_ARGS := -i -style=file
CC := gcc
CFLAGS := -Wall -Wextra -pedantic
-OPTFLAGS := -Og -g3
+OPTFLAGS := -O2
XML_CFLAGS := $(shell xml2-config --cflags)
XML_LDFLAGS := $(shell xml2-config --libs)
@@ -30,10 +30,11 @@ format:
$(CLANG_FORMAT) $(FORMAT_ARGS) $(shell find . -maxdepth 1 -type f -name "*.[ch]")
$(MAKE) -C sampleconv format
-atblgen_SOURCES := audio_tablegen.c samplebank.c soundfont.c xml.c util.c
-sfpatch_SOURCES := sfpatch.c util.c
-sbc_SOURCES := samplebank_compiler.c samplebank.c aifc.c xml.c util.c
-sfc_SOURCES := soundfont_compiler.c samplebank.c soundfont.c aifc.c xml.c util.c
+afile_sizes_SOURCES := afile_sizes.c util.c
+atblgen_SOURCES := audio_tablegen.c samplebank.c soundfont.c xml.c util.c
+sbc_SOURCES := samplebank_compiler.c samplebank.c aifc.c xml.c util.c
+sfc_SOURCES := soundfont_compiler.c samplebank.c soundfont.c aifc.c xml.c util.c
+sfpatch_SOURCES := sfpatch.c util.c
atblgen_CFLAGS := $(XML_CFLAGS)
sbc_CFLAGS := $(XML_CFLAGS)
diff --git a/tools/audio/README.md b/tools/audio/README.md
new file mode 100644
index 0000000000..95998dd944
--- /dev/null
+++ b/tools/audio/README.md
@@ -0,0 +1,59 @@
+# Z64 Audio Tools
+
+The Z64 Audio Tools work together to implement the full audio asset pipeline
+
+
+
+**Licensing Information**
+* The programs `atblgen`, `sampleconv`, `sbc` and `sfc` are (mostly) distributed under MPL-2.0. The VADPCM encoding and decoding portions of `sampleconv` are under CC0-1.0.
+* The programs `sfpatch` and `afile_sizes` are distributed under CC0-1.0.
+* The extraction tool is distributed under CC0-1.0.
+
+## sampleconv
+
+Converts aifc <-> aiff / wav
+
+Used in extraction and build to convert audio sample data between uncompressed mono 16-bit PCM and the compressed formats used by the audio driver.
+
+## SampleBank Compiler (sbc)
+
+Converts samplebank xml + aifc -> asm
+
+Samplebanks are converted to assembly files for building as it is easier to define the necessary absolute symbols, and they are pure unstructured data.
+
+## SoundFont Compiler (sfc)
+
+Converts soundfont & samplebank xml + aifc -> C
+
+Soundfonts are converted to C rather than assembly as it shares data structures with the audio driver code. Modifying the structures used by the driver without updating `sfc` to write them should error at compile-time rather than crash at runtime.
+
+## sfpatch
+
+`Usage: sfpatch in.elf out.elf`
+
+This tool patches the symbol table of an ELF file (`in.elf`) to make every defined symbol in the file an absolute symbol. This is a required step for building soundfonts from C source as all pointers internal to a soundfont are offset from the start of the soundfont file and not the audiobank segment as a whole. Making all defined symbols ABS symbols prevents the linker from updating their values later, ensuring they remain file-relative.
+
+## atblgen
+
+Generates various audio code tables.
+
+- Samplebank table: Specifies where in the `Audiotable` file each samplebank begins and how large it is.
+- Soundfont table: Specifies where in the `Audiobank` files each soundfont begins, how large it is, which samplebanks it uses, and how many instruments/drums/sfx it contains.
+- Sequence font table: Contains information on what soundfonts each sequence uses. Generated from the sequence object files that embed a `.note.fonts` section that holds this information.
+
+The sequence table is not generated as some things in that table are better left manually specified, such as sequence enum names and flags. This also lets us have the sequence table before assembling any sequence files which is nice for some sequence commands like `runseq`.
+
+## afile_sizes
+
+Produces header files containing binary file sizes for a given set of object files. Used to produce headers containing soundfont and sequence files and the number of each for use in code files.
+
+## extraction
+
+This collection of python files implements the extraction of audio data from a base ROM.
+
+Files that are designed to be used externally include:
+- `audio_extract.py` is the main file for audio extraction, it expects an external script to call `extract_audio_for_version` with the necessary inputs.
+- `disassemble_sequence.py` is runnable but is not used in this way in either extraction or building. It may be used to manually disassemble a sequence binary.
+- `tuning.py` is runnable but is not used that way in either extraction or building. It may be used to manually determine alternative matches for the samplerate and basenote of a sample as the extraction procedure cannot always determine these uniquely.
+
+See individual python source files for further details on their purposes.
diff --git a/tools/audio/afile_sizes.c b/tools/audio/afile_sizes.c
new file mode 100644
index 0000000000..1bd87ef483
--- /dev/null
+++ b/tools/audio/afile_sizes.c
@@ -0,0 +1,120 @@
+/* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET */
+/* SPDX-License-Identifier: CC0-1.0 */
+#include
+#include
+#include
+#include
+
+#include "elf32.h"
+#include "util.h"
+
+static int
+usage(const char *progname)
+{
+ fprintf(stderr,
+ // clang-format off
+ "Generates a header containing definitions for the sizes of all the input object files and a" "\n"
+ "definition for the number of input files." "\n"
+ "Usage: %s