imH-amgl*Kl>)7n9nyG zl2-3u!aVUqv-b%&oGg2jT=|qwB`p9G@6Faan!O4bGbDbaU2c4_a6q?A%NiszxUd%H zs(FT*zIDMqZx3bSK6Wso8AV3p})zxmC(eHr#?s z;cK+($OVyl`54}m*IRTqh4u`0K|~{HJ{W8Nh)8)1{`mHafqB`{`Qcv0fJ48vl9j>b zsyShy`SkhryDY3Zxw^!;;jn>Ro9aDRWAsY>N(FVa0x|?o^{71Xd{#|Sdg>p_AJ4*J zkK7K!1$RKwsA`s&sM78dq;s<4_rm*mjEdYl*CnTaoYmsv{t*oBi z|9C-ZHzT8iH^uka+znPEZEdL(UPPJ$z;6F&9sjePYI{&7RH{>2_#?ZH`IhA8tP9Hw z^E*Fx&lca6{|pW^aW_m@^T9%2+xw 2+md(zxnwaSKXUYt_j|Eo0{S4d-dHv*uRlYc^|Mx|(01~op3+T?{%Zp> z0_6~E1tH4=()nq-z#b7$wO4vEpyd(DMA&aFm*GyK6U56&gn&7-PL@=syBrkdSiTX@ zT+-`F;w+|Q#&p7Gp=*c~_*g-RZdvJffZi`h_ o zW9sz<$b*^Bm81-@;h~6QB3C|qpVn63O~ tbt2#{X&$K0lQc^$pAI#La`{kWQ z$}r_sJ#tOw$E a2U1H#%k$S)%zgR-89V|eo z%jMw2=Zu1#6-g$H7N?APPV0`|R!|9j!Ir3$u|*Q*akzM*;FN0BL!!PG+N5EV0a%CY z51R!<2FZv5ODVG7)UyxPS0l^1bb4*Y1O^HQ9m dW?6V74Jn(H{!#&$2OOxR&56U tk~)w>UlW76(JTy6yh|31XPr$AoAMFPUIZ))C+YPTL&yuX78P) zoPJWKc4gJzn!S!K`I4;5f&NW%L{jiKPF~At*Z3q8=VhlN<0ymtGLgCRs!~Lc17?}J zL$28{>A@iLXqVckzQg(%ZWv1*?eY+Qrus#xWisa!dyA!PQLhVVd3Hfkh7{gU9dq1B zqaJO{#BHeWC@i#3jPeu+OYOW$B?WQsnlPzfV*du+b2)jBM`x$7k7s6#BAq?cxpThJ z;M G;4*%TY$m3 zO|;=E Nwq?7cZNdy>6ka0?@ zp%_QsWL?)U>v^iyaDqG*aU7^@kbM~f{$z|}bSmn81=qb{%|{(7pCH^_|FGjt6KqnL z_wqhHTkzaA-0H}FulB)rRV7M{Xc 403pdm`P3`k z4Bp;eZ=K9|x4z$3DS3{}*c(krPw2U#)&*#GbatLwXek2GbxyXTtCzMHI+B5B(pnl8 zS|e)>&k4~dReeSZ(z?iBnyh9yROaStIQ3l-6nQ6mKF}zA39(%F&9Yv$)Wm(CKl`?5 z* gN! XYL4KxFGrjxAVp5$#lA%8)}jK|9#|n?S>;e8X 821^m4!Hi zt#TD*9$GfiCdzm(2;m|SAJRysgHRVMdk_~6&Rs4~Y*!2$7iJy20cIAp0cP$F=wAv4 zwpZ$tW>f{0DXHm7rlF*pHy(DjLcP9^N%I(^p*E6w4$aUXU4J37(KFgDdn>D3f-7Fa zh{dy7Ncz;n*efNh?muuJ$|qEPtd}%_eAiLk!j&j|WWO_7M@?~!B6ZI;aCt?(;By09 zWT`Ey@(VdA_@p|&ktX>9mri>)u*Y+I97>Gw*c*CHnocVur<$4#bQ2d!*PSkQ6K|^m zbA2{yj-nvnnvKvMZH6=2xP#ic5aa=W=6uS;Y$@JJ=7;d#^glnOKofg13`?z47=anK zW%Y3BISvgWX&A{9ho9~3yeqPk?niLdh>5WmyFZcERCRhRwM6E$ru!AkHW>8 @U^Q3#dZqZxLleiTRqUJpA7y<(`bimwBw7y!{ zwQPx@>3(8GM (??UM)v-Z(J^k^sqaAi7DAMO1n=S%2o`$^%!R-L z1d#6k2c4~N;F5ujd)XVEt_a|N5rZ>NT4C^06<~zwMG&3>ROY*3nvX( F;u`H}S_DHc+;A|yik@YYqshB*VH7WvmnTi1&Ein7( zBA=fkhY+#_2|ypzU>955y|`B2gs!j>IBPB*Ts2VuNwS;n}Uha_sSCFTXA8Gm!sh3Cugc~$`_ zAn(gtb7i#p4eSl9y8hj`emg3nVr3FC4g({7s%sfbnve8yuP aMxYmXbVnKrsxb^s@F1@4Ys$`zV;*X2en)Q @&)($&i*?W5D+JIiz~SvWp!?^!5>7nDMyp+@X{ddMQIMGn-zd7-xk5&6(vokS zlRXy(Qi2A+{Q^r!be}vem3Dpn>U|BrH9+m$h8af* z(% z;w#LL6ZXe{+s+~{awN Hwm?O-yF78h4R4H2VI JF}!okeZG z(d%xttbC)bNLkuY+BJIIHld >ci-SKhIUU=~L_LxTKG7w}IfHaPpeH7ZY@8jHr zQx4q$EiOpEx7JhUO^ `uzpI`hy;w{oVksu6;qw zdby2fB8kR24J#0=e=xGkecOGUFkZJT@KO-dGHhdw^gFAI+8#Lp!^Av~)`R9{y(R_S z<@6xt)(`lulub^hl58S}9_4_wV-Ot?aNl_rE6f& jpg zK#_9y^j1-%2amD+$~*1)ovNroTVT6u1YJ^>o`z@XZ?{jpsYKmn_2mAYGoM=)TLmzx zI)VaO3hZ`YrhXo6@09b-Q?4=BECY+W8*X=|IDuBTUq*1|vj&)&NONaJS9`h61i3cl z8zFhO-n47E8Wtg(e)el`wq?HRM%laT+03}e69ck)qCG~ksluB)5oyEMl~zvx$D4=4 z)G*l0ct3*C^{FcM#gTbISfD%Cc&YqoExN)UWbR@QBAJm&KYF+h`+tyI>@JJj0K89N zAGx#XKAb7o3w5eBCK|1x+M&MpI#umJ3nx3~EkNvjDv0XOPe~&{*8eJQ)XO73DKJ}Z zh3YWtP*uG(#nml$g-~T$M4+DSepSaD!s=S$Uov7+8=3o;#~~BWZ9iHE>uSoVuP0s_ zaq^aC=P;8eF%h$gT{$tws+52S;v1aBy)sZGsCtVTWly)KiWDfxof>-+Z6=*zf!Q(* zFnQ2)+34{MpO~A%%(p?|juusJBkf#B*OS1hsXY?ucm={`Y%NYO-91novYj!xWW0q8 zZoBN`O^b?;!@PT2c9Qe8;-~^#c#K3ZcfS~s8r8q@VY_olLc`V`#Qvy*XnG!#lv#=} zpTBPH@|NqzCbMqbN%Q?N)=y}BBGK(sZslja`mOT%_|B23N ?&3S{vZSdwM8H(VO8U`+Qpcm3Q0`Wcf zSzdpt8q&V&^pHWa{ega*74~ub_BviB%MQ+0f}Nr^O_PXnx <;b~;cFE=$h`@D{4kZvW zp;~$d@ 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?tu 2`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~~r71 cJx$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%!u6N 0fT8_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%57 m(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!~r0clC eLtHqj4-Lxra6wOZyq zajOANB%UcTpup|HO<$QRu-jq2N3UNl&{58fZq%C@^TkNU&@B*#`?0SlvpAR5FFz&p zL|;DEq<`|-py!9Yl+gmeS D|%WvF@El5>b z&KhEjKDm5CN7*# bnxpHD^0H*~AeD&Xne;3$r zbE{aZg^UWAF(3QyG=hI*oj~`Puamw$ULDHdx_$Ch{`p6FQ{wuv6=thrQ Ix%B;WH#Rf`49&%1Yx@pXQD^F!**#AFF 5ELQ)f_s13ynEF55TIE0 K&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?ww vJ3%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 zKru Ippeufv+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_{k Y 7^ 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}R0whV Qo-jMRfUzGeyX8=%_?Q 28`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^V g-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 z Ojdhj1UO=9c>Lj5w2u)8=~~^L6a0>VP&-Hbdin~$x)orpYslmEim?oF zlG!307?60_x8n a0~0w-}pN*>2%OyP50|L+3GI9zLNx?bpW6^U T>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{^(lDb6GoSwlLY2zeVGe AvvKix_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-)p YYD|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*l VIml6DHj#7AT#=(e 0=;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{OGm 7q$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#ioaw Ea69*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(=%wQ6 G3t1{WQ9TXV-+xc XG_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@e 1q84M @w (U!I=Q{ z^Q(=gE>*6zpK5Qy%5dmpZMMx fVNmM_ 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?+v9 h#y~fu!B4ykHU7Fr{QR4Zf2!Ap(6B*L zvV$tznx0XL?|HSB-k5n-DpytJ$=TnM?KRSZ zzflT@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=C y;>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^I008dttnf NQ-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?a4kJE 1_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}OMnwlP 4!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}Zj0Br N?!$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$Ag qVN2Vrf8HRM>-^kicPsO+!Rvt?f4*OgI8%Z)ykbrwHRrtKk@LR7L3rTj6qxtt zb2R=wHoS4i 3~`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~?n h>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>S 7>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;|mYQQ QmIi@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&&Ey39FMA q8c!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~ zdF 3&|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}+j ZA&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 z7Tag HL=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#3xvXvx Lc6CLP9 zrw2M@uKCAy5_5rSCWs EUq8`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-32 1VjQ^~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-0D 85i?^<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$JHJHD9ZC F vq?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`+O 703(p9sylMRa`h@TNVHhOqWD z;Y`&oMt2}~kvyo g=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_Iqg xczL>YC@nCkl}yg-z6Ih@2MRI X = 201112L) +#if !defined(__sgi) && (defined(__GNUC__) || (__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 47a449e9c4..16d0b122d3 100644 --- a/include/sfx.h +++ b/include/sfx.h @@ -4,6 +4,7 @@ #include "ultra64.h" #include "versions.h" #include "z64math.h" +#include "libc/assert.h" typedef enum SfxBankType { /* 0 */ BANK_PLAYER, @@ -63,23 +64,48 @@ typedef struct SfxBankEntry { 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; +// These limits are due to the way Sequence 0 is programmed. There is also a global limit of 512 entries for every bank +// enforced in Audio_PlayActiveSfx in sfx.c +static_assert(NA_SE_PL_END - (NA_SE_PL_BASE + 1) <= 256, "Player Bank SFX Table is limited to 256 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) <= 256, "Environment Bank SFX Table is limited to 256 entries due to Sequence 0"); +static_assert(NA_SE_EN_END - (NA_SE_EN_BASE + 1) <= 512, "Enemy Bank SFX Table is limited to 512 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) <= 256, "Voice Bank SFX Table is limited to 256 entries due to Sequence 0"); + #undef DEFINE_SFX #define SFX_BANK_SHIFT(sfxId) (((sfxId) >> 12) & 0xFF) 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 8c2824eee4..27771515a2 100644 --- a/src/audio/session_config.c +++ b/src/audio/session_config.c @@ -1,4 +1,8 @@ #include "global.h" +#include "assets/audio/sequence_sizes.h" +#include "assets/audio/soundfont_sizes.h" +#define SFX_SEQ_SIZE Sequence_0_SIZE +#define SFX_SOUNDFONTS_SIZE (Soundfont_0_SIZE + Soundfont_1_SIZE) AudioContext gAudioCtx; AudioCustomUpdateFunction gAudioCustomUpdateFunction; @@ -9,19 +13,13 @@ const TempoData gTempoData = { SEQTICKS_PER_BEAT, // seqTicksPerBeat }; -// TODO: Extract from table? -#define NUM_SOUNDFONTS 38 -#define SFX_SEQ_SIZE 0x6A90 -#define SFX_SOUNDFONT_1_SIZE 0x3AA0 -#define SFX_SOUNDFONT_2_SIZE 0x17B0 - // 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)) #if OOT_VERSION < PAL_1_0 || PLATFORM_GC -#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONT_1_SIZE + SFX_SOUNDFONT_2_SIZE) +#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONTS_SIZE) #else -#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONT_1_SIZE + SFX_SOUNDFONT_2_SIZE + 0x10) +#define PERMANENT_POOL_SIZE (SFX_SEQ_SIZE + SFX_SOUNDFONTS_SIZE + 0x10) #endif const AudioHeapInitSizes gAudioHeapInitSizes = { diff --git a/src/boot/z_std_dma.c b/src/boot/z_std_dma.c index 72d04dce1b..350c98e02b 100644 --- a/src/boot/z_std_dma.c +++ b/src/boot/z_std_dma.c @@ -27,7 +27,7 @@ #endif #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.2:88 pal-1.0:86 pal-1.1:86" + "ntsc-1.2:82 pal-1.0:80 pal-1.1:80" StackEntry sDmaMgrStackInfo; OSMesgQueue sDmaMgrMsgQueue; diff --git a/src/code/fault_gc.c b/src/code/fault_gc.c index 13dfb670ad..af69321590 100644 --- a/src/code/fault_gc.c +++ b/src/code/fault_gc.c @@ -42,8 +42,8 @@ */ #if PLATFORM_GC -#pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-eu-mq-dbg:176 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192" \ - "gc-us-mq:192" +#pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-eu-mq-dbg:160 gc-jp:176 gc-jp-ce:176 gc-jp-mq:176 gc-us:176" \ + "gc-us-mq:176" #include "global.h" #include "alloca.h" diff --git a/src/code/graph.c b/src/code/graph.c index 56765ae5a1..2564d83dd9 100644 --- a/src/code/graph.c +++ b/src/code/graph.c @@ -7,8 +7,8 @@ #define GFXPOOL_HEAD_MAGIC 0x1234 #define GFXPOOL_TAIL_MAGIC 0x5678 -#pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:160 ntsc-1.1:160 ntsc-1.2:160 pal-1.0:160 pal-1.1:160" +#pragma increment_block_number "gc-eu:0 gc-eu-mq:0 gc-jp:0 gc-jp-ce:0 gc-jp-mq:0 gc-us:0 gc-us-mq:0 ntsc-1.0:160" \ + "ntsc-1.1:160 ntsc-1.2:160 pal-1.0:160 pal-1.1:160" /** * The time at which the previous `Graph_Update` ended. diff --git a/src/code/main.c b/src/code/main.c index d11630a0a1..8ad00b52bd 100644 --- a/src/code/main.c +++ b/src/code/main.c @@ -24,7 +24,7 @@ extern struct IrqMgr gIrqMgr; #endif #pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160 gc-us-mq:160" \ - "ntsc-1.0:148 ntsc-1.1:148 ntsc-1.2:148 pal-1.0:146 pal-1.1:146" + "ntsc-1.0:141 ntsc-1.1:141 ntsc-1.2:141 pal-1.0:139 pal-1.1:139" extern u8 _buffersSegmentEnd[]; diff --git a/src/code/z_bgcheck.c b/src/code/z_bgcheck.c index 129900bdbb..bb1b3a71f3 100644 --- a/src/code/z_bgcheck.c +++ b/src/code/z_bgcheck.c @@ -1,7 +1,7 @@ #include "global.h" #include "terminal.h" -#pragma increment_block_number "ntsc-1.0:136 ntsc-1.1:136 ntsc-1.2:136" +#pragma increment_block_number "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128" u16 DynaSSNodeList_GetNextNodeIdx(DynaSSNodeList* nodeList); void BgCheck_GetStaticLookupIndicesFromPos(CollisionContext* colCtx, Vec3f* pos, Vec3i* sector); diff --git a/src/code/z_camera.c b/src/code/z_camera.c index 1ddc538dc5..d5c97c109a 100644 --- a/src/code/z_camera.c +++ b/src/code/z_camera.c @@ -3639,7 +3639,7 @@ s32 Camera_KeepOn3(Camera* camera) { } #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:90 ntsc-1.1:90 ntsc-1.2:90 pal-1.0:88 pal-1.1:88" + "ntsc-1.0:83 ntsc-1.1:83 ntsc-1.2:83 pal-1.0:81 pal-1.1:81" s32 Camera_KeepOn4(Camera* camera) { static Vec3f D_8015BD50; diff --git a/src/code/z_collision_check.c b/src/code/z_collision_check.c index 7904809bf9..0567fd15a1 100644 --- a/src/code/z_collision_check.c +++ b/src/code/z_collision_check.c @@ -16,7 +16,7 @@ #include "z_lib.h" #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:104 ntsc-1.1:104 ntsc-1.2:104 pal-1.0:104 pal-1.1:104" + "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:96 pal-1.0:96 pal-1.1:96" typedef s32 (*ColChkResetFunc)(PlayState*, Collider*); typedef void (*ColChkApplyFunc)(PlayState*, CollisionCheckContext*, Collider*); diff --git a/src/code/z_debug.c b/src/code/z_debug.c index d83dc23375..d94e79e83b 100644 --- a/src/code/z_debug.c +++ b/src/code/z_debug.c @@ -12,8 +12,8 @@ typedef struct InputCombo { /* 0x2 */ u16 press; } InputCombo; // size = 0x4 -#pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192 gc-us-mq:192" \ - "ntsc-1.0:192 ntsc-1.1:192 ntsc-1.2:192 pal-1.0:192 pal-1.1:192" +#pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160 gc-us-mq:160" \ + "ntsc-1.0:160 ntsc-1.1:160 ntsc-1.2:160 pal-1.0:160 pal-1.1:160" RegEditor* gRegEditor; diff --git a/src/code/z_demo.c b/src/code/z_demo.c index 89726c6a29..1e27258306 100644 --- a/src/code/z_demo.c +++ b/src/code/z_demo.c @@ -1,3 +1,5 @@ +#pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ + "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" #include "global.h" #include "quake.h" #include "z64camera.h" @@ -124,8 +126,8 @@ u16 gCamAtSplinePointsAppliedFrame; u16 gCamEyePointAppliedFrame; u16 gCamAtPointAppliedFrame; -#pragma increment_block_number "gc-eu:186 gc-eu-mq:176 gc-jp:188 gc-jp-ce:188 gc-jp-mq:176 gc-us:188 gc-us-mq:176" \ - "ntsc-1.0:80 ntsc-1.1:80 ntsc-1.2:80 pal-1.0:80 pal-1.1:80" +#pragma increment_block_number "gc-eu:0 gc-eu-mq:0 gc-jp:188 gc-jp-ce:188 gc-jp-mq:0 gc-us:188 gc-us-mq:0" \ + "ntsc-1.0:128 ntsc-1.1:80 ntsc-1.2:80 pal-1.0:80 pal-1.1:80" // Cam ID to return to when a scripted cutscene is finished s16 sReturnToCamId; diff --git a/src/code/z_kankyo.c b/src/code/z_kankyo.c index 4d92cc82b1..2980933cdd 100644 --- a/src/code/z_kankyo.c +++ b/src/code/z_kankyo.c @@ -1,5 +1,5 @@ -#pragma increment_block_number "gc-eu:244 gc-eu-mq:244 gc-jp:224 gc-jp-ce:224 gc-jp-mq:224 gc-us:224 gc-us-mq:224" \ - "ntsc-1.0:224 ntsc-1.1:224 ntsc-1.2:224 pal-1.0:248 pal-1.1:248" +#pragma increment_block_number "gc-eu:232 gc-eu-mq:232 gc-jp:212 gc-jp-ce:212 gc-jp-mq:212 gc-us:212 gc-us-mq:212" \ + "ntsc-1.0:224 ntsc-1.1:224 ntsc-1.2:224 pal-1.0:240 pal-1.1:240" #include "global.h" #include "ultra64.h" diff --git a/src/code/z_message.c b/src/code/z_message.c index 368a53723e..35e30abda5 100644 --- a/src/code/z_message.c +++ b/src/code/z_message.c @@ -8,7 +8,7 @@ #endif #pragma increment_block_number "gc-eu:0 gc-eu-mq:0 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:112 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:96 pal-1.0:128 pal-1.1:128" #if !PLATFORM_GC #define OCARINA_BUTTON_A_PRIM_1_R 80 diff --git a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c index 8d1f8e6ca6..a1253dbae6 100644 --- a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c +++ b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c @@ -7,7 +7,7 @@ #include "assets/objects/object_toki_objects/object_toki_objects.h" #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.2:128" + "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) diff --git a/src/overlays/actors/ovl_Fishing/z_fishing.c b/src/overlays/actors/ovl_Fishing/z_fishing.c index 6a7dc3ad59..ebb4a45787 100644 --- a/src/overlays/actors/ovl_Fishing/z_fishing.c +++ b/src/overlays/actors/ovl_Fishing/z_fishing.c @@ -35,8 +35,8 @@ #include "cic6105.h" #endif -#pragma increment_block_number "gc-eu:177 gc-eu-mq:177 gc-jp:177 gc-jp-ce:177 gc-jp-mq:177 gc-us:177 gc-us-mq:177" \ - "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" +#pragma increment_block_number "gc-eu:170 gc-eu-mq:170 gc-jp:170 gc-jp-ce:170 gc-jp-mq:170 gc-us:170 gc-us-mq:170" \ + "ntsc-1.0:121 ntsc-1.1:121 ntsc-1.2:121 pal-1.0:121 pal-1.1:121" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED diff --git a/src/overlays/actors/ovl_player_actor/z_player.c b/src/overlays/actors/ovl_player_actor/z_player.c index 8ce71a8480..8b6b326387 100644 --- a/src/overlays/actors/ovl_player_actor/z_player.c +++ b/src/overlays/actors/ovl_player_actor/z_player.c @@ -332,21 +332,21 @@ void Player_Action_CsAction(Player* this, PlayState* play); // .bss part 1 #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:64 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:64 ntsc-1.1:64 ntsc-1.2:64 pal-1.0:128 pal-1.1:128" static s32 D_80858AA0; // TODO: There's probably a way to match BSS ordering with less padding by spreading the variables out and moving // data around. It would be easier if we had more options for controlling BSS ordering in debug. #pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192 gc-us-mq:192" \ - "ntsc-1.0:192 ntsc-1.1:128 ntsc-1.2:192 pal-1.0:192 pal-1.1:192" + "ntsc-1.0:192 ntsc-1.1:192 ntsc-1.2:192 pal-1.0:192 pal-1.1:192" static s32 sSavedCurrentMask; static Vec3f sInteractWallCheckResult; static Input* sControlInput; #pragma increment_block_number "gc-eu:192 gc-eu-mq:192 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160 gc-us-mq:160" \ - "ntsc-1.0:128 ntsc-1.1:192 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:128 ntsc-1.1:128 ntsc-1.2:128 pal-1.0:128 pal-1.1:128" // .data 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 e93bb158c1..fa74b8c8a9 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