From 82625e6716def7d9706ee96b14ee582f3c1763ad Mon Sep 17 00:00:00 2001 From: Paul Giblock Date: Mon, 8 Sep 2008 04:03:23 +0000 Subject: [PATCH] Add Csaba and Attila's Gameboy instrument git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@1584 0778d3d1-df1d-0410-868b-ea421aaaa00d --- ChangeLog | 40 ++ plugins/CMakeLists.txt | 1 + plugins/papu/Basic_Gb_Apu.cpp | 83 +++ plugins/papu/Basic_Gb_Apu.h | 53 ++ plugins/papu/CMakeLists.txt | 3 + plugins/papu/artwork.png | Bin 0 -> 74916 bytes plugins/papu/btn_15.png | Bin 0 -> 1355 bytes plugins/papu/btn_7.png | Bin 0 -> 1352 bytes plugins/papu/btn_down.png | Bin 0 -> 1327 bytes plugins/papu/btn_off.png | Bin 0 -> 1271 bytes plugins/papu/btn_on.png | Bin 0 -> 1263 bytes plugins/papu/btn_up.png | Bin 0 -> 1330 bytes plugins/papu/gb_apu/Blip_Buffer.cpp | 429 ++++++++++++ plugins/papu/gb_apu/Blip_Buffer.h | 259 +++++++ plugins/papu/gb_apu/Blip_Synth.h | 208 ++++++ plugins/papu/gb_apu/Gb_Apu.cpp | 261 +++++++ plugins/papu/gb_apu/Gb_Apu.h | 84 +++ plugins/papu/gb_apu/Gb_Oscs.cpp | 451 ++++++++++++ plugins/papu/gb_apu/Gb_Oscs.h | 100 +++ plugins/papu/gb_apu/LGPL.txt | 504 ++++++++++++++ plugins/papu/gb_apu/Multi_Buffer.cpp | 215 ++++++ plugins/papu/gb_apu/Multi_Buffer.h | 174 +++++ plugins/papu/gb_apu/blargg_common.h | 178 +++++ plugins/papu/gb_apu/blargg_source.h | 66 ++ plugins/papu/gb_apu/boost/config.hpp | 13 + plugins/papu/gb_apu/boost/cstdint.hpp | 42 ++ plugins/papu/gb_apu/boost/static_assert.hpp | 22 + plugins/papu/logo.png | Bin 0 -> 3888 bytes plugins/papu/papu_instrument.cpp | 724 ++++++++++++++++++++ plugins/papu/papu_instrument.h | 163 +++++ plugins/sid/logo.png | Bin 1247 -> 1966 bytes 31 files changed, 4073 insertions(+) create mode 100644 plugins/papu/Basic_Gb_Apu.cpp create mode 100644 plugins/papu/Basic_Gb_Apu.h create mode 100644 plugins/papu/CMakeLists.txt create mode 100644 plugins/papu/artwork.png create mode 100644 plugins/papu/btn_15.png create mode 100644 plugins/papu/btn_7.png create mode 100644 plugins/papu/btn_down.png create mode 100644 plugins/papu/btn_off.png create mode 100644 plugins/papu/btn_on.png create mode 100644 plugins/papu/btn_up.png create mode 100644 plugins/papu/gb_apu/Blip_Buffer.cpp create mode 100644 plugins/papu/gb_apu/Blip_Buffer.h create mode 100644 plugins/papu/gb_apu/Blip_Synth.h create mode 100644 plugins/papu/gb_apu/Gb_Apu.cpp create mode 100644 plugins/papu/gb_apu/Gb_Apu.h create mode 100644 plugins/papu/gb_apu/Gb_Oscs.cpp create mode 100644 plugins/papu/gb_apu/Gb_Oscs.h create mode 100644 plugins/papu/gb_apu/LGPL.txt create mode 100644 plugins/papu/gb_apu/Multi_Buffer.cpp create mode 100644 plugins/papu/gb_apu/Multi_Buffer.h create mode 100644 plugins/papu/gb_apu/blargg_common.h create mode 100644 plugins/papu/gb_apu/blargg_source.h create mode 100644 plugins/papu/gb_apu/boost/config.hpp create mode 100644 plugins/papu/gb_apu/boost/cstdint.hpp create mode 100644 plugins/papu/gb_apu/boost/static_assert.hpp create mode 100644 plugins/papu/logo.png create mode 100644 plugins/papu/papu_instrument.cpp create mode 100644 plugins/papu/papu_instrument.h diff --git a/ChangeLog b/ChangeLog index 24f8ef028..4c763a98f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,43 @@ +2008-09-08 Paul Giblock + + * plugins/sid/logo.png: + Un-box the sid logo + + * plugins/papu: + * plugins/papu/Basic_Gb_Apu.cpp: + * plugins/papu/btn_off.png: + * plugins/papu/btn_on.png: + * plugins/papu/papu_instrument.h: + * plugins/papu/logo.png: + * plugins/papu/Basic_Gb_Apu.h: + * plugins/papu/btn_up.png: + * plugins/papu/btn_15.png: + * plugins/papu/btn_7.png: + * plugins/papu/artwork.png: + * plugins/papu/gb_apu: + * plugins/papu/gb_apu/Blip_Buffer.cpp: + * plugins/papu/gb_apu/Gb_Oscs.cpp: + * plugins/papu/gb_apu/Blip_Synth.h: + * plugins/papu/gb_apu/Gb_Apu.h: + * plugins/papu/gb_apu/blargg_source.h: + * plugins/papu/gb_apu/Multi_Buffer.cpp: + * plugins/papu/gb_apu/Blip_Buffer.h: + * plugins/papu/gb_apu/Gb_Oscs.h: + * plugins/papu/gb_apu/boost: + * plugins/papu/gb_apu/boost/config.hpp: + * plugins/papu/gb_apu/boost/cstdint.hpp: + * plugins/papu/gb_apu/boost/static_assert.hpp: + * plugins/papu/gb_apu/Multi_Buffer.h: + * plugins/papu/gb_apu/LGPL.txt: + * plugins/papu/gb_apu/blargg_common.h: + * plugins/papu/gb_apu/Gb_Apu.cpp: + * plugins/papu/btn_down.png: + * plugins/papu/CMakeLists.txt: + * plugins/papu/papu_instrument.cpp: + * plugins/CMakeLists.txt: + - Add Csaba and Attila's GameBoy emulator so it can be tested in RC1 + - Update artwork to something not trademarked. "Freeboy" is just for now. + 2008-09-07 Tobias Doerffel * plugins/patman/patman.cpp: diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 0e9d622ed..5b637bd06 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_SUBDIRECTORY(lb302) ADD_SUBDIRECTORY(live_tool) ADD_SUBDIRECTORY(midi_import) ADD_SUBDIRECTORY(organic) +ADD_SUBDIRECTORY(papu) ADD_SUBDIRECTORY(patman) ADD_SUBDIRECTORY(peak_controller_effect) ADD_SUBDIRECTORY(sf2_player) diff --git a/plugins/papu/Basic_Gb_Apu.cpp b/plugins/papu/Basic_Gb_Apu.cpp new file mode 100644 index 000000000..7307eb8a2 --- /dev/null +++ b/plugins/papu/Basic_Gb_Apu.cpp @@ -0,0 +1,83 @@ + +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/libs/ + +#include "Basic_Gb_Apu.h" + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +gb_time_t const frame_length = 70224; + +Basic_Gb_Apu::Basic_Gb_Apu() +{ + time = 0; + + // Adjust frequency equalization to make it sound like a tiny speaker + apu.treble_eq( -20.0 ); // lower values muffle it more + buf.bass_freq( 461 ); // higher values simulate smaller speaker +} + +Basic_Gb_Apu::~Basic_Gb_Apu() +{ +} + +blargg_err_t Basic_Gb_Apu::set_sample_rate( long rate ) +{ + apu.output( buf.center(), buf.left(), buf.right() ); + buf.clock_rate( 4194304 ); + return buf.set_sample_rate( rate ); +} + +void Basic_Gb_Apu::write_register( gb_addr_t addr, int data ) +{ + apu.write_register( clock(), addr, data ); +} + +int Basic_Gb_Apu::read_register( gb_addr_t addr ) +{ + return apu.read_register( clock(), addr ); +} + +void Basic_Gb_Apu::end_frame() +{ + time = 0; + bool stereo = apu.end_frame( frame_length ); + buf.end_frame( frame_length, stereo ); +} + +long Basic_Gb_Apu::samples_avail() const +{ + return buf.samples_avail(); +} + +long Basic_Gb_Apu::read_samples( sample_t* out, long count ) +{ + return buf.read_samples( out, count ); +} + +//added by 589 ---> + +void Basic_Gb_Apu::reset() +{ + apu.reset(); +} + +void Basic_Gb_Apu::treble_eq( const blip_eq_t& eq ) +{ + apu.treble_eq( eq ); +} + +void Basic_Gb_Apu::bass_freq( int bf ) +{ + buf.bass_freq( bf ); +} + +// <--- diff --git a/plugins/papu/Basic_Gb_Apu.h b/plugins/papu/Basic_Gb_Apu.h new file mode 100644 index 000000000..ee01ed578 --- /dev/null +++ b/plugins/papu/Basic_Gb_Apu.h @@ -0,0 +1,53 @@ + +// Simplified Nintendo Game Boy PAPU sound chip emulator + +// Gb_Snd_Emu 0.1.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BASIC_GB_APU_H +#define BASIC_GB_APU_H + +#include "gb_apu/Gb_Apu.h" +#include "gb_apu/Multi_Buffer.h" + +class Basic_Gb_Apu { +public: + Basic_Gb_Apu(); + ~Basic_Gb_Apu(); + + // Set output sample rate + blargg_err_t set_sample_rate( long rate ); + + // Pass reads and writes in the range 0xff10-0xff3f + void write_register( gb_addr_t, int data ); + int read_register( gb_addr_t ); + + // End a 1/60 sound frame and add samples to buffer + void end_frame(); + + // Samples are generated in stereo, left first. Sample counts are always + // a multiple of 2. + + // Number of samples in buffer + long samples_avail() const; + + // Read at most 'count' samples out of buffer and return number actually read + typedef blip_sample_t sample_t; + long read_samples( sample_t* out, long count ); + + //added by 589 ---> + void reset(); + void treble_eq( const blip_eq_t& eq ); + void bass_freq( int bf ); + //<--- + +private: + Gb_Apu apu; + Stereo_Buffer buf; + blip_time_t time; + + // faked CPU timing + blip_time_t clock() { return time += 4; } +}; + +#endif + diff --git a/plugins/papu/CMakeLists.txt b/plugins/papu/CMakeLists.txt new file mode 100644 index 000000000..3f458f86c --- /dev/null +++ b/plugins/papu/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(papu papu_instrument.cpp papu_instrument.h Basic_Gb_Apu.cpp Basic_Gb_Apu.h gb_apu/Gb_Oscs.cpp gb_apu/Gb_Apu.h gb_apu/Blip_Buffer.cpp gb_apu/Gb_Apu.cpp gb_apu/Gb_Oscs.h gb_apu/blargg_common.h gb_apu/Blip_Buffer.h gb_apu/Multi_Buffer.cpp gb_apu/blargg_source.h gb_apu/Blip_Synth.h gb_apu/Multi_Buffer.h MOCFILES papu_instrument.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png) diff --git a/plugins/papu/artwork.png b/plugins/papu/artwork.png new file mode 100644 index 0000000000000000000000000000000000000000..e8e9c225a80dd2cadefb9e5cc31f4fc748f19969 GIT binary patch literal 74916 zcmV*jKuo`hP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOS* z7a=nLU!S}H000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}001BWNkl)5EuW&-Oq6bB_Aoe)gNvvzPcc|NbBO_WyVrd%DYTIAmvMhohq->Mv_8 zMNwjm!CH$_3g;Z&dz4aWtr6#0^d%yQ2*wzcQYfVm5xn<^$n1B7bJd@z{|=y(!Wh$> ztNyBwS4!c$Z;tWahkJ@YFZyizODTmCwe;TG$F(%R9zxR57Xsw&)Y5%Q# zkL~qp-&6ZuMFeZ@L)UQ8bJf>QlH|;J+u!SZS@cfY`_Frib8c?!l~Pnyg)!#LyQz<< zs%q{t?PssoN5wU)-=#4I=iF?M#dliYi!p{IN$}n?9iLK(Mdok+;Yrk0U1*K-6HO9=n>{_XG zl|gf_t#h^or~A9^A~qXUnT4C6yZFwzGn>tO4~mcVRc{s%K>(vQUIYbr5wr-x?Eb6P z@$t^jwzTL`<#%QOizqT^(zO(MWsE=!3_fhV(d%x}dQD38d zKUG!D?Vt8B-Tl;k&bFW~Qlg6#PW#$jL(vxWw!AD;U|nTXU!#^;1t_gie*Sv(bu0*i zZ>+YeKiJY#T>)FPz>5}ek!G&7Q+E-A_R=gxzOYwu#yb zy8E~K_ie@7-rQ~Bdf2;cTl9x1ob%Rd(RJIFuYI1{Qr7#TD?E#SZm;dk5c!3l=_fcMepN=98g5Gky=m>gO@Vc%_edE?z@N%vN zs~@1faoU^SRSwi*ZQ8YfixymK4b3yE*2@#T7mQKo3hzPzh}yD!eWeuI7@9*w;l$&` z&y4ItHfLKa&v`#8TvHHG8fgn0vQ)t;X#TZb(B|)2BdvG8Xu~fP{xb5 zTe7x*bPY-S{<`0ng=~xNx2=e3%U9q3vOQFXqKgE*EvW5KV^KKORk+>t?uLBb{n&k; zt{2?(e45WoJjR1_zPXMU!3qxWxGGp%UzUv~ssGgvzQ{#sYiRF1wxvD0ilNp>_3!KR zbzStf%Tap)wKD0(_H7Lp6h-@Y3Z-R$MA>EUg-c*?D*7 zew)89i2>`4Qa^t;?yKKJ{cQF7su#E&^LDL*QVJ6n+t<(4I$!%-?ak++JG(6Yt>eMA zqHC{Ly^f2lSljce|KHtHUFEmPvUe3iJ65d)&P6Pv?c#QoLffJ*@>&)tob$Yxu7}zU zBNlmu?LFUq=Gt&tYpJS=BuScQs=rrNbw&=AQq4Yf&QTN<{cMPMk{@o@cfRaPjy*zz^^?LDqz^+W``N3@wccfD~J0dQ?>pkBGIbz0<-cLlq~ zX%>ZeUC*K$BXHp{+0$ zDU?O`yv(C$dk@Rv=C((;Ec9Cj(mrh6&kN7m_fZRG7a(qXDQzLHg}nXy#c~tzDN$%` z@m`^|VLC1FK87tK(xhdy)<`R6I2-S%b1n5E5NMezwg~N%t}EQy#9fVgW~0`im||&+ z=fy7s_mom#^g_$G%o_gx@s(N&9WCFE;j~h7mX{WgSEIs5y-R*6zs;Rk7Nwa`F&f#EK)w_d6*9aCLiUi3p94GZLMwAp}iKh;;YkSwNTgY zdaWSpbF^LhBuQ}Iqm>SO$9YT(PisDBi~{ACKKQa+>atv8H#ffQrJeV}&wG*ers`fJ zZ2l0FF9Vj^8?Xy*7-PWsbF&5yTX^06d;QGqP1g08&c5(qsg>w+jlu3E8O{mAem|VwIV=RCN~7^Ri0bU0#(THW zT6VR57kpT>2^R@|*hF#$+-VbS7Cr1t*aH&2C@;DUJZuwd^b%#%MT3_v^f_+tBBgQ0 zH7!e2o%f6g@thE}(kRV_^3s5X%XkpW6yIE`azRF85mBhgkhI+BMJWHGn7M5Tx{9Wo zM_;zaU1H55A?seVn;u)H_!j;BQ5EMRrPJl6v;on!(P^j8YD>M$b+1V|?PJq4Yf`cG zy3|Ug_8x3|0UpJoE{a*3MeYc4TaX^6CF>ZmhKClt;PXJD^9U7-m||U2HT+*M+$0~% z>ySlaKf@r}6qh=2ClSc=sJR$DSoruE35u-#q$q_p3ejW4 z7b*Hi2%T&8Ko@9jAK%^&=Ly#$i-<7CKolh66}kal7u~-^K1J>EEyESgOW!`Kptps* zz2|D>UGqcQY3;TW?Fx4r*jwmHH}az}I%zzn$z&3*<*`N^6dG4nF-0`5g=SjHMT@x@ zuu$=(pZ5~Z1A0V+vaC?Leo3ZT%C`0B#$j!c2N7IZ#aPCGQdE@-K%CLgqF|y_1i_^F zKT;>&+8CaImpZ*o<@{U@-8)a^9T-JlnXvKZ&evj!#SJH3qICvc3$jue9pkJfwI>0E zM$lu&SlyugUL>a1;wseFr0rRC^YZgl<3s|u=MgF;cs!yNMrpJXiqi?!+ORRL z#cPcbL$4HcAsBB!3q_(RjHl2QP6--~QGw2zpivkgS~et+m=b?Y27mox+#| zr41%Y5kd;kS`$-sko7XMG$YZL)2hHH=3|}jk&ZDokY@tCim?`L5}X%IlAw)68%ti~ zC>w;Z5}^`7(2OTJ9?j9on4)wz50wacS#o@O$|Rp+wLwiPvdUteCO^$75%x}wICWDd z`3c*@0cvuJFAKCWOq`>fc-BsO?Cxbeb1>qu{XYGDk1Zh`WcUPf<&Z?91f&!a5+oOV zp|L=kj+s`s=x_G9H$Fh8Lcgcc8oaMiT4PX1DOi=DCDdC~z5=TdUtttT<*4!sOv+R= zm9-Q`lPjS}5@fi+G+8B2dX(9KF`CLGm|j991*Iz}%aW?9&YrNC&#AX`H!6P08XED^z1?kugeqtUuC_p-d> zvNYJkLW5Wu~cm^pMWf-#mPNf@n+7!5}a`frmoED}&w75n@993D=&e*HRL zJjU4QF;wJv&cWdxiOR_Hob}=ABcJcNXP$oOF{#pIS)bu(M8DU2==GB%r7TN^gB1Wa zH@6uK23)?JaPQtd#^Yl~qY+!%8}xcTPEW^-_YN5(38&*JMWWb#>@vNLRh0JZ-@D6X zZ=dma%&r956N zX|IO^4o>c~y7*wAtgacJW>M~6jsnMbVkF_?5L=WFs*=5}IMR~}!Yhtur zSOwH2JDgV)bsiA5jD^(1dml50?iG|pj!7)P_tmd*>Cz>}<1wqNt2h_<}PA7Qek>%Z|EJoD_c554|ikZ^i>%Ju8ldFiE>_{1kZNl_H6udnm^>#y_LYp*b!PWjYJ zFYs%>_G{d_^&x-xzyDiad*wB9U-8`MKEqf4=#SXi-sbf1fWQ96|H->YIj7Sx{oasd z;#nPgp3l~Kx?1DPamFqqC_F_4yC*5Dw!)TE8ha|kFl04JDG%?GAKWD?Cvfi_lL>6#<<`ZfX;5+A>)GTvZiW+C!}%bQvY^ zJRx0#nl9p1FGE3pl^nVkPoC#I`|PuP;R|12YikQ*4Abcpr3}O2kgBS9=bd*LkH>ud zPyU3@fBy4KCX=R`Pisw{=Ul&jow6+XKfdt|u3WkD(DNM~9r1^M_y>IY)1N-~m^{y$ z#e4BnpW?+AUp)8v-}uJAhzdh0FTeD58wiWffjX+HDC&mooY>Q5eU^x&9s;^`+N z@bD*Gdh8M}e)chbba66d%Q7*`6oxF049F)eu+BUt>2G&>TEF+oRq1?Y6y+S!R9r>u~wQjA5 z_r7t%yFgq0|2A85k+8Qtfcj^xHCdJgR^bmop@n z+O>Gp^vS~V&kiABS&s2n$)Q)`QP!|^A=KX&MTpZfn^6=6r>Cdfy?YmJgAg7Z9B_Ji z8qyZxSzll086nF+~V-?m;stZ9&W841$pms{=3z4 zOGD5Ox4#|B*9xJQ#acN`CKFaht4yaw2yj_6zuKo@t1%3m-up~;L5UOFc`ABy2@ZM z;P&m?eCIpg;qLu=eEG{?=9y=nVQp=Vn;+Z&c%<`9r&IDGf9NsZdx{u~J<9d3UcJiN z+8R4MI~*JwaOch)zW@Cn@WUVeklo!~zWBv2@|CarHoLpKj8Bi7&3F0IE}#3%XW6-U zffuho!|}lhAHMz;H-7mJz7SS=t0ZI3B{$$1y}_kCMV*vPGsDeM#m`SZqUu#_4M&_* zu%G7~_7uadizJ)77}bNkM0x11^pHwnO^BCdG1rnjvu7L_k9 zVsL#Nt@-KEbue zp5XoW-{-IX>aTeDxBEu_J5gFo79X)F?3QqF^rWJ|wSRY=+aELmofFmTCLgMLQ5HGlgKb837o{x59FP}p^89m~{1-3%6ZE$o-}rFCz5DmMFcc<~ zOsf-;l_AB7Fv+I$Oo=%>p*qi@?N&I8{&}~!qefSl9~R#%@u>?m7OGfAyyfdVRz>KK$@Qe(-}IaP9GH42MI? zvSfF6m#=^Q>s+{Sfx%$F#fumD;DZl%?X}m~Sl@W0^R2C|v9Y; ze!u@n*Z1CY?bwtl;iUp`k&oA6D`K19T zl~DMKyqB?(uF>DTK;?Usc||Io2Z#5VGv4lbyU?MgY=Nke|E^1e!a)A{}D&~jtBQszWIN>!JDtW%;vDquYKVnzx$OwMRl9h zyvApqe46jwn{aCS9OU=0n_H|t{Q|2y>m07EgUT6B?qTnLfb8F8WEIBw2E1B?O)V;? z?uMD&?lEoTy zP10UQ=_;Oi?pZEexWM-279V}|5h^N#t<5d+vf$|Gh$o+X67M}*TU%^yZZe%tIX*t- zy&LcI;9&2e=kvhE)+Xzl8;r-}rN`{=?>Bkb!C>&v>%a2KYh1m0m5Ud5@m_f4l~CGPua8ss;Nej4f@;Hv1%1@2^P4&|2~R+#_9oi>bW!VsEq-s z*JL_HIiXjjj1&~oLyk()B4wqYa(Hx`&wb`|WZ6BW%D8{)Y5tr4_M5zOyNBZnE{7lf z;s$xP!PoxaF^1CzT-&%rd3uv#^?8nyl-~N|47MNVI3H6bIcw8X2JgO)d+Sw_<69(2 zh4%?cB}=<1Jq%xJyW;Jlq_&%0|E~VI{=Nvq>W0nj^VGuLC>Ax(QxOp+r#VGYVXQ`n z5>1CzmaWmJsC+fobYfAJS@&7mDLZf=rg8U22rot>SBUjO;$pJ#V> z7v~)BzyCf(5z;Mp@7`?`8@mT2D5y$%nC|9sL$mrPw)M94b+5nI2DU5wZ8`Lv9ui#;5T#-XwrX{? zM4icjck6w-X@F&^vPBIRy3GSWF6gm{Z(UbkElcegt7i;zHD5iIB?+H+_BoWm{)0Wf z`OR-KolaRDt&pZEZ~XF?DC!6bk_OqX*sys(WNA!9<27>`b zQLwwa+jvde+uMv{?0;}@z~0^-7ccIT^!g0e*ST@~4jKqErT1g6|sygR4r!Ih-pYQDm9LivnesdJr*`&T{u)%BT7R z{8(W}3VIa}jvmlYQU-lkU%!M=cga#%SrIligmHC;qk{tq?@;7Cartrb{VCRKdOBs0 z3`oR4QK6KjHx*XWlw2~(Tv48sl%-G&dV$_u_=a%1D2)77fVhVh%`C#f+SPuG_H?(s z)LG)1LaF&SZ8mtbVe>yY`8k%$OM78;6aHPR>Z-F{VcKcAZjAG*Ae)Qa_!)w(nl7^8 z{?t6S5IqiqX*6}3BqT|~-rgSXzyChp`ObGZIy}Tz6~M9lIXO9De}A8U z{D*(w!}s3@U~hk)C!c(h3l}c2wRwSluXpBrhlht0)0{L-Nz;_UV8D&{Zt(20&z^fs zRaNx+eYUr^+1=f3uK)7OFZ0%0Z&6ewzx7+c)m;Dn-F@!dxkHv^JoVId9)J9C(lli< znQ-skJ)U~%DV}@oIj&r}g7==iy*)&P#~yo(?F&1cPNv+ue?JsMR2Ai9g4YVCg>mk2 z;!$3al!}2^28CsmSd>*HC~(1}vfh#U3Z*NQb|@QYy@w}L?9c>?FMX8HFiDRrOR4+= z?%X}4zn-G9oUJD`SEiPol`VEQMs$Qae|wYZF>GH-dGYfb zeEHWt!T9(CF7H0ZM{hfh4=cSbJ)6a%GxykVLmQc#Fksc@$DA=FDqtw zSUvlr5E7OZiE3@2UD;H}EJnvRS#>8I)3D==F{EjawY3eBp zC~ZPA@JuQwjK+#(qwMkc>4;NtoY)*O71pGPhh$PPI-QdD6k-!FDd}Lu3Ol3;OhicS z7OQ5HgdVQg=hm&eTpwo)cDE?hJ}-V{$P52;jZwBv&nfOr?y_;>87Rl~>m`5sC%asI ze3d&7axU%;dH%T#F6@jc%Zwwp#rOaI7DvYkWC=r~k?9GP`{aq^crZX;*ujogNOo56 zH%g`-ozPbrlr;7KAGb{PJVMv9jzx*_?Lcfvl7vCOk5m;p zk`wCB?Ck8Ywzfu|=S;?9v=AwrFgZQtgBv&4*}2H(_9i|u{LlX!fIQE6?6Dy5N-1vN zzRh2M_j_z@ZSmrZFS53_!5{zeAM^Xa|NGp!TmibL001BWNklf;--G&=+)n$Z&OsosBh=cI;0kWW6=kvlYrb=icE*+&FrT&mLbT zpH4ZR-XL3pY>*-O0|sW3$$rWH;ee}8T;X)`0hf0*fB4U?vG*WnXJ<&((`YM<#{<6o z-JASBKiVhx^fuG-Gz@Mik9qL+kJ-AkO@HtVr>4(?(K;ieS*=bmu0;0lA+DU2eR6g{ z#iLZO&HsEhi?oP!T6@8@%(V$I-TrgSzAs99*S!>NVpEiaa=;`>@J27~rL`#FU)HE# zfrK{gh96o9^axzyGZaCs7FH6?-*@m>>I!TTu*NXx^@DO#iXzX?xI(L|tBgh?PEJlZ zK02Z-%TR=3E%`J@DZ|mx5os^w(xpqy`G)-g8*A(FT%MDY6SOs?y^P1NJx-RS=N_ZA zW^HY4?)qC>TU^||H0RR2{`%|u=tn>1TYvQ}27>`V{NWGj_xt?8AN(_h!y%*5i0z$C zR#sMc`>l8R!4H1G_rCWx+`M^MB2f>mBm(DK@oasm7{^3|AVPrQDk2 zZ0Leb;;##9(X>Urxx! z_fT>dtM8MzRn{|u&jy%8QIK#_m6Tb@U}cNDH>Vt|ZR6!8qv4d5{sXd9u`x>6+c*65 z)r$Z4^9kSj+dV#*T%?~^oH<5gu;o3t{VKg`hlEwC^_2aUK2?96-sUydD@VS!M=Hgv zVP@MyePr7--(grJU(S|qWJc= zzr}~|-{9HjpXJK#MRa0$@11w}*~>rY?Kj`%;P9A@)fKGOy!!K(*}H#_d$&L0)}5Oa z;;5<;<4Tew!#SAvihFv>``LswHD=<36|wYPESM0Fc9v2JMhjAnnVueTsuhehr3Ixt zNtR)iP*um=xqq9Zy$?~@11zwAYeMeSD)yi3eU;@hxD~4 zzx5&c$4gol|$FvVTkqqu@$)Jt|9aBsQSCaw6$+Q->_lptk0~M zfbR_8FxsGWoK)1xvwP>zQK2{c*_ft}KwY6J9=!4xU80o((J0X<9jB!?V2kp=TS!Cq(UbO=PN{kAq{UNHdBxo zH|Qv@PAa5U9)L1B>Ixm^#gs0*ByD1DFwBHd;?B%(tEvjT%+K}`kF{p5m5)*iS1p?f zr?`6cDiWJMOeUus9Ue64lp3QLuC9<~8Ad5|V%fiUAB75v#n|vSqd7V{33SrSS1|4N zR7z3#D#p>r6lKZU>KdcLsA>AR=om!M*3e5cvR)6PRH!%Cy1D*zIwhaxR7KIS5;Y2K z4gIXgU^F00Q@nV_<1r^ECzM4Q%3NayT%$EfmNKcPAW#{hk_zPotu5jNlPlI$kKt6a zF;;9!#&$AbBOTCl3g=5)YH(IlIM3a3%neRC>?x-Glrq)W)ZoNXdWTdlpxmxRsT{8l z*}Mu*K7Wy|?G5hTyT`lle#E_d_sJ&(DLu$7i8wYlHCL~O(%XZBL+;$Z%i&>3+P_Hd zGIHrtBz;)jq_@6__b@%Yj~X9_&MT5pW+Srg9oE*@N%B*scWz=2_Q|SZ-qlusxH&@+ z24V;vjff*m;}WzA!Y(>;HJGm=YksF_8mo98dM=HT$@)C+B|D* z5P>w&l+y#oCpl{ykFj@nhokas{`Akk!1nVW&0=)$I08aCEptSL+_U$B79E=u)@+-I z$fFQZNNmjLD+z-NmDIC^LcbEFK!e5vOC7CueOUb>wKY*u#KIV#Vz$+`oIbIiHG) zUc1`Pdz1*rhld;;9-`4pj{)#y#l(5?=`?oxRE6=kWZyrNVV7kdKYJPN^R08u78mj|4@ICN_Ri-Ifcj zLao_g6J<1I=_#F1`4Y6BkI{eY%P;cR|MqW4dV(vbK!vf2$`i>a!PlR;E3+X$N(_cT zKh@%zTzq(G5ykYXX=fA|YO_sOKeVz2nQI^zUu2y|qci2hs53cqv#_wHRt~yR1a-Ar zXm}Yq&V;yLHA3vgqg$PGh|M`d(~8!_jSgqu`|8a3%BpOdJGL7#h8A7t9OFIWTo|C} zJ?^}ejhdfV=bQsKKYhj+gKni9o9e>Kp+(Rxw7#woL91_F4cGgq6LS?SKtt|#O_53tO`m+Y0JrUOyNQ=azv5n&~Zp70YjQj z%UGLWgUc#DSjO1F*H{}pK#hr(80S%5W8&~ZuPTxl&Q-;PDnCWUV+iM)YdWsN{nq=_ z=rAhFd4;H1!xOE8s~omg1csX!RA_HQsl_PtQbCCExen8OW9PD_yP#^pOIj%qAHBuU ztkr0R4Nak_s+1(nsGQ)a5L2R)PK^H6J2$};nAA}@hZw=y9%V5NR$GEAE>Z={>Q!*N zeeB#SLV^o@%M^3G`Oq&d3a-R{a4j$*zD1lT^eJ1Ar`JkBbfu+5+;9JX5y_!js@hOt zQL!}FwBoB)@k?xUs9QPFdXi1Mq2vrjo$EAr?)m1)Mh`z`Sxe{kJ=D`|+bpbZ7v6Sy zrkm&0s35R1+r;9*i6Ehs9bSV+Q6yn=xJCBO}0Ca5a z5F3Kc23f=|K*ngW8hj8LSJls>V{;VWR4US7I|^fC_~$*uHL^+v!Ij#r4lT32^Vr0a z)ZQXsP)i16s(DyPLM$5KB^?cxYH+N!HLrzRsi;Ie(LjVoq!MLCX+a^l=)xMMP|9JA ziZOV&hSmz_{A{p?pDAna6h)P?GTh{JI;K)n`a{ED)nXHa)hZ=TH~8mYeTjebKm44D zJH}zitCA#J3D#Rw{4R)8XeBuD7!=OUCXR>}d>yB>1)Z532!d$e#K?0UQeD-!rA=>A z34C;4zmx^QnnVREzSAAF9#+U~tnz#YK}AKOqIGC_A`<6>KA1uY$#n{r+m1Ye-r;u)*p^prMiHEVec_&J(ZSOIZdWPAQxVjWCT?O~u!u zFV{lJn@kdd@77h}7an1%dwv|l`=0piO4lkb4=M1tG=jz@Bx|v;9yS+1&9@bu> zTU&Q>HDUs_LtIq#I_wG2b1AM_K3kghVBuZ2@R!ELjmkZa&Q6RC#?X0u6|Q5I!o?6$ z+OI{HjUkI(W<5%)o}#OKFc@*4H$kqJX5Tsj{!(Y4ns?xZB)Z^A$0jbS#%_px9jmzS z!~WGq$9-H#zB?i*YkJ&E*Co26UI%~ai~`&R$5t%Ng6jTxU;d*XDuKi z%o=lt2`+74*?ZTJz(RIG!gSH7U=#ssP+mpl8!I$pAG;b5rskE`V4BjcRJnw<)gs~9 z>*tCfbBw+*S~YFCgYwWQKWoFSM91(jDtjDC+3+5fiLz#*$^dJF`;LMnvuN!p$_b?y zME4n&`$)QvO@*)iiz~qc*DB6O$77A=iKln?@!!9}&EwZ`KBM#{t67RGJwg%M5j!71 zuo{r7V-^ADbpzVeKJ`rg{QRH@322P2L447Ob7xTR0LF#$w3|b=H*VP2(KR}Us(T(4 zwmt1~Sv~!tE;wzgUzSzFA*k=Ks$8@*YOb6y;5hYp4NA|?v|hv+Xj|nr_qkoNyJ(hu zm#KJ0&=piZWJg5R@1`?8Swe;Zr6ZuHqB|a*-=?v2SY~Cy;ykXbLi}qaZeG>&mT#~C z9o-rqjfRtePKyq`?px2O>-F|NG*qzjBc1F2*R>Z;O02QiC~(eI)KdsTW+o^GPw2DX z2yvUgQR5o31~kpI-C(JMPrT0!MP>ve+-MnReA zC4`19Ns@$koGAcln&MRe6+J?w$2dPh_YT;+qS(5aa_@E-h+#F0r*akl{12bz-S^() zwV&PQ=z(Cp)+P`lq3OI zD$BB&5TcbqS%q`u9H1!C^%F01ilSSazi8fAeNAIbP{PcQ-fg4p-AN*Iyl5pskw*yE zi!`pQ@gTT}Rtr)EnrIS7^48WpM8wemXh@VMu?e^eR~Eq=iVDQX09|wtc&!Xt8GIEi zn2rRM(EeHlH&xF<6fa>+u+*ZYZOz`8y}O3hCQWmaW-Yyhe(?4C_7Xh27XCJ;B%@&p zBg^XjQ#DVlBeT(HfygKf;NUC-Q7BStDP4s%301v^L~uT~`*sy&6|yYK3)-Z!Ni|;K zM4`bWWkGILzL{%Drm!h{ja~ofBo+N#AKqu zLE(?MzxO)5^cqPvWSU3qr95%?CskG9#Dhf8snr$=36o5e4@hU7V~_}BNOYyN@*!^+ z<2$r!jV-E2{zR8nFBz~Wp1H;vAA<@DXJ>VFiwG7?u$g^EHSuOU{0#!;n>p3uWeGPr z5QWTZ9#ZUxh#S!0W)eAJ3*S@lyLhPfDMsS&|AR|CAlHKTswd#TAyqMEd<(gv@g{eSlzGWO^nu5$lQfw+BE1}hAb9!Zjs=Nez-DC00nuu4eOVN>Cho;LTeJGV?DzRa}1u04Oom+Ga*)i$P~b*0JwSYQKDN)E(*2^ z6KC2P%o+1+2J*G%=hY*yhfylEfoi3)oCD6*d%Q@ z6}6zM*!iW_5Fr!|UZ$!&Kx1%HhId5B-k>6ZMhDWBRT`&CDsoCHT$NL}97!t%YbpH| z!!@Ft4d)oRN`65GOC#8bfQpR!kjD@zIzqWM9~T@ zD!6wd4Pa5b+qEmMLMWmVuPw8xg{8F)y8A3`56pxadHO5^vWkKh+KoAsmp~;JaK43QF!N}9 zy`!R#tLT*oGs_zGL>0pbOXW0zMwtNeX%pw-`EWL4gI8ya2JH#mAb>O&v?MjyKu!tg z^^QuFDD6?ELMUR5e~HpLJyS6lq-^hOu(8wQh2OYCV#>L+b<@G7=402)DrFSLYku>$ zuJP)|A(K-_Vhy`b_W0=L1NQe$F~)M|R)H}CL=QMQnNSs>I71nW){111##BbI^llo! zFwo$_iwIS7f-4+QA~J;vqbj2K)RBfD7rIS=!>`P+_~Ba~7q!y4`Kg zbP#FfZB>iSRL_F*m5=y!`?GRpXux?>PiO2qf2NeUE{N#P?rQ(PJJhdHRE@yV07(r; zf#7ZnA$>}Ve%ll?LR3lUr$y!}%7W09Rj|ZXR8$HYZ4618lILZ>3Yek$0vH>fX;MrgTT^2r;3Sc{rT~=; z(Abb36NeLr;P5sQi3}+1sHDX7gh@FeSusqiG1h91@>8@X3`NvHT456*%>>)4aQ=k; zs$yfS$LD`XNKy^HM5%kVPZPuP5Nzl=WCE-)Dj<85co){p>!6We_|$5h{KnG<+2Qsr z&%KWv2YWgBL{pW9!@U!V!f|qPgmVRD=@75*CM5GK>!?(LS7`*;3`S>YW3kGhokly$ z(5}WDXuSbLT4?GYi+FV3h6OrG)bDxXa&r01Z@HtWY8M~OB+46FvT|RRpoK&D5j4c zrIg?cl!YW!*i>_PypKr*YlZ$OgL1-PbB!{eG8m>jb7380gfvS?5<}KAr2T~cK-1s6 zL$;C!z)_XUC(6u?9hb-%aM+*+d|)KGkg{-*rWY6ik>U`2n%yhH?iB@+&1ipY#vJ2C z-nvf)etYVt+G1*S4fV zz-Do9J+@ivn$aY!e0jI?NV`qf?Dt;WnLK!HwcB8zFkcfa8HD!Gj+Ve1g%&$c1&*Fd)fAh|yp%@S5rE>dEJC$f z<9;iAENoF}0ofR(Cuk)k7Lvr1_AFUXV^Yu4t9>v9FML6h4XQBAm;JbiQ&fo$KIS-UesiO+W#;!sA(>UP(Ns`@=BX<^ zp1#rprDqn^&yM}+TT{m4oXNPN$c3V)D9SOefV~ejZW>Zj&gC%=>nVzgDlZtUz~O^P z4~%IGV+_5thczi>>0+8MB7n|E=95P0B+d>Go`ADmtEs_t?|VYtTt$96MmKcTwuex2yHy>l68I0|eIw|~n81bC zwb$gryAI6ZAeb>V>KigjM4B6Gq$5JFQC4BGppvHJQ6*KFcTNZ~17AE6xO5mN9r_=* zhEyXe8Z6}*Psc&HVjQe>DCgC&4x_4y!1;^${xFu* zckq!PAkpAhqfIk^+&R}2+0Rf-X@pejxVY6Gr@4f*i0Ct3C(=erHGKA&J}_wVogsSL z!21#%AbUqj=E8BgXD_XPagt9C{(SySE1f>)w!_&+9sTt<@^6Vss56niKg@Df4c#I@LZdPYvE3unBeEK7R59#vV< zAA|uD{WPNz!R4OBrU4VEaRB9GF_gsEQ#XKFC~}H%y0REDP#Rx3Dkaz?R02A2U<}@Q zL<^P6nM_03Y5n|#IK0+OU4gZl>GYIdKV>=@v*9j<_1NAI`DUv~QiZXB16> zN->#7Ysj*cmDN6JrqM>Rb49cL*hxdykyu{r>*fzKpT+~hj2^^LM9(_U#57r(7}mvl zW@MXLp-649MRoeNaJGm>w+e++jQMR|R1ABD!C-_bgwjpHsfK`G7s{(vQHm0oX%?BK zONc2jL@nS3%dGsT_KB3ANvZvYH~s#kAGTwUzku?GIi6 zmr#{LQ53;b$US9QQI+5-xO;amlzO^?vT!&Th%RMO;d4iC)I&LsccGHVRpGn$;7h?I zNg#8mq!H3q9SuG)I2|^xGJ*dqSqLqhw%8P?9L9&*d#eSsi56Co$Ju0c!w#i%R)%c!q0)u$@`R%PCr$Iht%6xLfHD8}^{j49YlCqj8jY z=NN4a5UHr7KwKFbi%Jy`^k_v*n&Js#geq4w-cL~!Xq#fw1f6QE)u_}`mN`Xvia~?% zcqzbm%pg>bRT99*exgZ-eJbTi5<^~;j7AB@_86_?2sx{(JM;&J(Mm?IpOB=A;rbrw z${mEY=<&5NjjBQ3@yta8W3!8zL}SN6Kk$Wwwp)JhECvf@NtIg+{w2KcYu+awZXYQjhp<|ASrYuuz8npb44|;rb zKc%XC%t1m~IPyFIj7Nt@P0wttLVR8n0WeDGQH~Q_q41T9p@C3F8mJc)!-6m;93Aj? z=R$Sf=MkjM*A#q&*$2VaZ;>qQd zL|c?i8irjcRtlTGwyj^iJy^kDiQTA_yl%Q3?ebf@Mf`W7~B()lf zg1*&^MkB0En3g$(*+VB)qtvo2iv`)9tY_KT*~0sTG_#CGLz2|cUpb_jZnAOVg#OB% zS?(JJ>L@Cylel9JXZ;}>s%zL2zHN}0W4@lPmYf%g)>e zL$#EK#4%&i!ZKw(Qxx6zfM&{D001BWNklpQ>~2 z*89rtX0y5PyDZ7FCEJo^TasnZl8F;wkRVScK%N(QksyCSf*{EI-~kdKuaZF!WRh7k zK_g>nLERg~-ZP1d)X4kEAYWXeS z#XORdIe>&+;6j)MD>U6fOcLUxDQ3#;Hq0g|w9N?Z_f^U=d%}PT37BH z9(3lR(RgTGBGw+hh`D+5C@M6$xXTj{!`Ma+v6atudc09&3QVy=w9BBp+)diu3bV-J zE@J>&-HvyNqKGI`xs9$JrkLBu&np{56iPmR0enAsjviWPg~%`r@w_5I5Tu5WV`HPX ziVcw}5rhpqZ;pY%GR0!iz zH=NsR!vO$orU#=$W@x>HIAvS4NhMm4_4$=fG~V)MiBu@!H>o@eM_DG#wKHJ}v;Ig^ zT5WH5bAjbzf&6B7l$6qq(1#)~gv-ash&Kv}XXV(0W#sq#b70^w8NUyj%0_%wSCI+99M41 ztz;{maemYEDVG-sCnyoAB4<{Ea6lju3N{qpyZ3VT!|eAPoV3WsOJWjjNF*W=9n``Z zPub;=;Tw|ff{-GA`lpq2-NzlI*>gL&Dq6FKVK{4-8`YQdDO@f60a@;A4wW4M&t zRPaLgUh`U6vXAY+Bs_dm5x0eDN&gMrJ>T9K!p>Xdpx(*z5LSFL!`UZvUxhCm9C5bj zvV1)2RpcL7R&IAsmLw>Vy!6p+KDnEiBnP#b-sP$4~$5*CtMD~AI? zIX_e6;t%26qW1%$xL2ug0v(a&*%~FSQf$J#N~KqDJ{wkO(gnt3p?J6DuFLAn-x}Is zUWzPqO)0a^igK7@0>Q=GdpWgdDF;8+4mBu$^K^&8-SL9%>KJJ!l(@emkBepR_h*Sh zP}9GzV5PRcmaUcLyTU3qxngqS%5SQ5qv%dgfc zB{8odAkCa}(oKdfhv-7G*^QVkU?-H+s?YDGRgXSlWzw&={nK2{AzgfK^s95ZVK!!e zh;Du%s0`+ui-Z%vlpES+g%_2u3A^CJNwG9v^%bdWZPF=lKYNy1VLKTRM;BWdbM4@QWYPlY=?jk6;z2+nxa&q*%ylZ;c(&yk#IvnXH*G}8Z z(n)j0H5a9bnU7Wi6=-xM?0gGEA?V1|y%I*8?@M+S?%@>_LPp3!gkRFwZYn|uF5fxW zvlox=xn@7Vm2S~SWmhG!&6q`DN}%P4{9xRDocxF zk7V-TL_wg3yWKa5z&XgYQ}56YgQ~ZhD=cwBcNNV9B6kR7KUVipX7e|q?&-Rw z!H{;v5!{()2e3ruP7^6JBq6CBw43+4e{O$nEC^bXW>phZi?pg`TGbNmT8U1hgpMkx zpg_B&2!oI~iY%Z-;}^jzXk-DDY~YvMj7|v#Cluw8HUr}k17ixW9D}bNX=?`nshye* zJ;^<<{*x&ve)WY~+Fr{xYCjhsqfaQL=y6lg%53JKE$P%XVZB69Ez_x$Y1c{wjS_?v zR9K+hicQz1&wPbD-$}vXWK4Oa!@zjR{;``B2FyW_yzdWhXPf`T+zk_Hq$Vuj$8nTW z?vN&QfBDKP0N*$|VQ)|8Mix#XBxQ^xN;CPYWm@V{*;13K!iaMtAiBsFNwGv_a7cV& z!W_^*_iuXi&`^^Xl7@yD9JUcQ5j~-$N^t__-?al+aLba(;gTc3h^}I`qllYIC%M~K zndeT=cEbtZ^YcsDdCtS`wzclg-`qZ{)_K;pTW?W9CtdfndzyKNlPZzMag6XvX~^Fb zkqBtj71b3SXtD$H&V>%sWR9937t%J0KV08X%$g2(D#%RYC@Z=!( zFk+9z0ZnN*V0^a2*q$cCb4^BOBZ`A@svp<2r~9h+WD!v>>+iLfM{+5h_s{m zrnDk#YHIf+@#ZeVwQ*Y89-YSTKi`$^`Ob70TWBym*JN}qqBNw;jjlDG7QBDgZ2G58 z8rZbwOQRX5Rg6**T6_H9vathx?&ze=phZ_}OuLy_3foI>OnU<-x?94s@&&@7ZCWyd z?5fHmQC1kxwD>X-igMZvBDX>5;v?t3$1U`_H3CzQNYDTNg%2A(qRFBTr=Fh^vB=3! zkw!nc8(BLQX&Wu56o;D;LEH_jQVlY4DT$`HFq4PW>Yki7*|1WI+{RB)7>Pn_@|?Qr z6K=i2tyze!d^>D=+`X*ntnGs43}rP>VzW8hMW!4Z$KeS+b=b>@zR42{Jp);-@Ekhy2xTL;MI z_w)-jlve!Y<*}|qLRcbCOw{&RU6e%YlgQRA6*W)o)1L1KWd=ul#`eL;z8y2)QDYE# zVs42KmiO_qOOnqYG?$!j@^NKz2{Vyz9G*@||3ZrFRuNfMK6RH#3>kE$1(xfiq4K|@OIcJNNPY(}~z&86d6#ImzX%9Ss<|aY`Nug0Vt+g)MX{6Cei?r%# z1>)fMb34$%8F#D9=sqnH?X;P6Qk>CwG*k)z5}D#juDItEx{~)LQ;&FQ5pZq<;&2uE z9+pl^(&;9qx1P#WOFYk*C1~=Yd$S6QZ$Pwih`I5jEG!)6*3DI_TUXhs?+^q*s;`%X z#Znld1>Y|KB7@J8kfsvj1X}=0j61Z!00x0Xw-lY4B z(Gd5p&vET`l~%`CFFpHwEuH&oHiRE27x~LgZ03wM60LxQ zt1o~!imZ5JpZ_KB)WiivgRnvsXpIt*Sb2oa0z+~SwY$5y@T(&C?q*`7SKpuJ{i|y{ zc6NlXJ-?IN0cz)#;c|x1RHA`?MeaI=n_T=bku5L=mvC3LU zi65Di(wutqK`)U94kUhOZsgHV<2`A;rJ-w@>}tofKxa3ah46MGs8VRJR(%ek-WyX3 za$=XM9+{;JriTtLL|EqmE+TI?m3hN=>$b}@6mTov`IejL1MTdtAg6@@645VVwbML8XIeCwA$^I1(-+bX-5|@Nfg~R=Kc~^vB)V0i==3&Q91+#&03v` z9->ej;?CkGtH(Ob?=`Bsw^p`|pv&aV9M2;C8d|ax-BH>>lqn155kXlcAE>V>ywAvd zZ!d1Ldc4E#J;k0N)OT-0qk(PMF3D3cR7365E3@!v}x8ObZkf%g`&^k zsL#ZV$FZ|hj7^l%9NGYLk{n$*_;`Dx2qRkIGI&%5PR>=pch*bX`Jl+=J!sZ7#GW1B zV_-xwyenYu2`G=&++@}YX=u|46^_@H3+=`5kQ33rtTvIkVm3C4-1*RFV_DFwtKRXa z0~StdilfyGgrqXkLegt>esXq)q>ouS<+&^;5;^JNMqX*UCwE^qVBFV^wJ3?*+D z?HxhXiczs9)G>h+w6`>!ZJ$eTEm9e2aQ=ziEF73%V0gf+Bk8(yU?h2jQw|mxfJLd; zq!K{blB{0y`RMhSf|#P@P0`sA#C^sOsy>TvU#BvnIq_JDiG>cuigUAzuEvv7ITzA+ zkIA%tU|vAj7OX5vK6)djASQnM`0re&JOalbD>1&OLvg_6{bW#`L_Vu~J>^4rNgP3K zMe@puViBjrqL$>UBnEJDmK2&){T$^o3I+$XBI>yN-p! zUFeRa(i)BK=@ZN!Ez#W6RBv9PSeRjSe2~q}D!!70pRUUavu=jwMMo33atut7e;f;5yv^qloeC>srqctJA z3bUG5Umnfv&3~~~_^&)?E|1wG1zMYm>aB-6Up-po{9}TJeKL2OE9+e^{^s2;YN3Xh z7~4;*%KY7X@9_1*6Di$Phy=-2;KR4?a_QHb$Z!%JP14#aQ1-`ZH6m)Y8l6rPDI-c{ zNvTq#*={p5R-(Fdi+zVHC~uQfpPS>@`8kNa^p@8|MwDJewgcdyw2$u#BDGDUa}OaT z*WPP#bi z%uDUCtMeojY1+{mbfd!RRgc=OVd`rG4|cwl2D68noOue4o)(M@`zWpON+sUAHqVm> zi>zS8n@ zt!9Tvg%nGJEZ+w2UvAM@ zjXCjjn=tNJoi@i;USv|%X@o_AE3YfAzgb{(a3AvHj$a+&()uO`9%(awx@B9LNUk-dsT zZErgpS&C#^W`ZZNjxE8-C>9H=oD!pny%0GRuy!io{zL<@rI*sLk^6G<_Z@__baOAYl@QBrWjLn|$ zl@Ua#)&qoVvgp23DMU)C^E{tF|98J&P#xsJ{Bi2l4mWSyqt%S@bl`PaNN+5rG^IkBzx$t87~#YxHvXEZ(Q3AN;@bnX z!d8X=O!IHy2&{Vi>IZd#TgO` zS4A*Uh}4wFIxLKUJ{}P^wyCWvwpXFK>C>uJh}so84WCXE&_?Mf2s(6vHY!fU6JnGw z@=gWc$1ju-1xZ{8DUEj+oozF@J7CwWVsa8nB^R0LUeDd|s_v$z%xXnhFEcfR%uc^7 zly;>DwNw;_+svL4a6Be%Y*SxX)Ydeu%>wPZnQ!nh=NlCQ%Hsi}a~&q;+RV(xOiV$k zocN~a+kXjQ$N+CtX0oW2$~^>A6)x}>fAJbcd6;ASzQonV>s-2Yow2bAG%bRtgAfJ; zAbf*M)*521&=E+l$WF6{L@+#DMs*}tFRk*qCl|PVZIvH={}NyM#&II+M48FvXf!@T zd;DL2@g`;eIL8(~+41jQU**?7iaGPNPfX-gx^XOdiu~hWU#H@q`&7n%`xVK_uXw~* z_t6xpl3X4hZ@<1lWBE9z4}7xYUwwO((tyvxnJBlFwWT}e9!0kkYu1FZ>qfeOxP0AC zXhg9?sTeRdp_x1+P)ZQC8U#&`PQxc?76=fI){FBbXQqo2k^b=N4_bg=FQ+dnuSQ5jSCl^EfvlwYEqj)-tt zCdq!vnJi{dM@i7uTzSLe@Z^`+ywjpqt0E=T>vePcD3#uTi9(AInKTq>$fF%uoCvKY z#o{Y_?`C?yCwn#I>Y{qf&=ZJVK8WhQpDv)fb%qGo}6 zZ|&yr{I_4*W^mU4V^ht%JXGj$`>jd#j~x9J$KS`>uWs_h9~LMNhTVvI3z!j( z{Iku`MF!7J@^+nd<LqDy^j`29r}Of;!Y!1Zs1JV0Dbv zw*Tp1-Df+D?lD;Ru{lL)NTn2AqctIOsc?_UVy1O1xO!=ut$T3x@nhV$zLuhV8jXfi z#E=BVAMttpXLX+c zW8+Dkz>x1P`fRTV&OLzf-~4%vulzk}^?j%!02+ezMKDhBOyuHD8wUhD8wdTXOS`Tdew=Zl`-3oKXz{_tcJO1dW zKK?I%TBF^Gdz6E-`%q;ROpB@&DSTF^;2$9bt(wP&Ka*@;Jjh7r^PF0EmZBJ^x>aYV zzC)|kCJZCOATmEoTc0xNQ!JJY{#!_gCm|7dfJdx+LVN;G5=m`snmvNRr?Kg?dArE! z_tYtH+n%`=cRNE+6I>uTJyIOC!AUQl6Ri%1a}>^71IZdTp9;p8cNEHq4^rjA=?{+II~7C@ks$8cqet@VJ5 zA8e+#Qmx*8pyO|^hg`lKr}=HG?sMm6?IDbB>Dz(Nm5cQUHvX+^0jn!26D4$^p#lfa zEOUB#MmIpDwnSRHSoepG{hs5(FMaOa%}|Gn?@#jnHG_43_PM&r$(`I*6wpF1^_{++cOg3W8A%#1(W`Bv}Dap6}5?k(pFiivrM%$Y3OuiG~_Y3>O2 z>^?+wyTM17FVkwZY%51O0tk-FWlGo(sSR&-(mU;!L|Phw3IzkjF~$aV^VYlDmH{WY zy|nef$A9l)oC4KXZ}Y&%zjR5baOc{(c;MqNUg@q@I~C_HfWNlxE3wv&60ljE)s;BK zy5C+}ysY&03wH+M&=H z;?7l{)s-l%Q6^Y-e`3jM0OY^q&`or~h^@4PC{x+lKEP`~uMx$HCm)^0o4A&qf-ctm zVbAyWqR;BRD5X^=>6L-CJ0^<>K6Ych&G5i9?VXr6UjH?>Zrx^WZ7u!qIEyU7@+Ki? zlIu%nvAOzBi5iPB@YCoJc8$(+=T>0zQ_a>!@W97kzHNvaQb28!2R{D2+v$#^w&6YS z@o!ued8(@0QnR2EEgWW{N?dIdg;S$DI6mbO#{IG1olYn9s{44@@7UneEnr0`!)Vl6 z6JuOTRoo%isZ(EDqg3orsbq!8g&D&sk2d$AlBdDo0*vz!Z6I`W2vv0&Hr+` z)Lnf@SHmsQZ;Qf^#&(^d=`ptM1Sx^zVT`|hCrsh)OE*>@&iE7&2yR^6V0PE%AL#g3 zmO>DvToH#HFtl6wE;|skBRoETv`BMPeP*!k3kR%KvlbjawZTAT7jM3sVcoy#wA6a! zvVoufdM*9$pS`&bn7sbW&(#qUW{(s0-ik7#2^nTka?rssm3sYeF$qqi?9ev4I$09|P^eNr;MKW1a8Msqo! z^nkGL`_IDB(+1n`Nyls6QO)WxS!}henci@j_2q2_=8E_QAB8548>#tTX49;ZN;wb1 z#YrbJNfP1M;Fv_gn(yfdNkqF58ZNrklY3a>C+Mz+HNLb9Z+xQ6y}J)~eBYD(AmLrP zrOqM1M9$_$=7c%$H099_d(R5=@tDA3-K(q6*euYhl|Me#y(o$OkkWX-=v|-$Bs}s)ppN$+odv5I*+dFLmBQ)VU$$(!!2e`O2o04sIkq?MofJT zT3ZF$wci8lUX;Xskj{5ZX#72fWR@8R385~bMyv0Kqe_%YbFg|j8{-KPY zq-RG?%<$$vJ=pPgAMtyChVJuoMV=UAYt2Y_X7MvhtV;`$N}?xaeIfb=@Hub zhAGxv5r%P!b(cQ)cD}OIpfp%zU{q5mYbt}9f*&IVU0R0sgCF0=WHFA-$*_0-G+KPX z*7ho%fGCbB6pBUwLWsUnxjiyXu6LG@Nb6Ril|)Ge(ip6%NKvcRm^n6plm`B~@DRqI zK2l6`^!dG`XwiP4<4+wa*mes{&6OVb_$Ln*=%;$<n?kA&aJNdSVU!1<5v{Y*Lc?NES336Gznc3Hk}DzDy4Y! zpG`IXndhoKero!+M()D8>o`^J2!fWRQx`8;>w%Ad@?L_AdX`^PxN&g zN#iDw;darQSSw-yu@H1RP?QC#VUxHL^5w6LrWUrIUwGi-KmKGE21T;Ui8Fio@zRY4 zIKBd5e5Yd3=G3Ee{OE6&9?1B`>4^PDRHgyn*ZiSJ@z`GCdHlQ4b(h?|jMa62NLY7A z*IjE@j^Ei3bV*0KPO3IffBN|$~4}3}kq9@B2U7{1hRSd?pBg(#~u`O79QxLBl z{0!*2YZeZ8_`W@xq9K;A&E|C7O>513{rKff*Zt8mBlwfo4GmbSoL}4QN249(XcB zdQB<=A$}obV4z5$An-he=f!xQLUroSXs{6O^X+&QeF3Fe~YgB8}DqV zMY^B7JjTEL(=f07tjgF>h5bhxDFA6>NwW1Bz`Ccp?)OAmc{i7~Eo|mY7G>snB&6c> zV{?qnXu^3V+YMRXrqNsr&CJIBEtu{fsL)Zxc58-PX;TFCh z6So`G*Y2@zW|FOqdnmudm%h4}℘*so`3oL8&L36eU_5 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 N002ovPDHLkV1negTD$-N literal 0 HcmV?d00001 diff --git a/plugins/papu/btn_7.png b/plugins/papu/btn_7.png new file mode 100644 index 0000000000000000000000000000000000000000..bee0d2976e12c9526ff03f1e9156a2660fc1d205 GIT binary patch literal 1352 zcmV-O1-JT%P)Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT2?7BqEc@p+W&i*H8FWQhbVF}# zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1hq*-K~y-)g_X;1)m0G3zcc6eyYKeW zN?#zafFVLGF(yO_5#xgBf&~VnQFpRn;UjExN%{x4FzghKA#p)iYY5Sp29$)bks>98 zhIfIs_tD$?d!BPt2rR!gPPK||%b7L0mZ7-Ezv>Vra-xzJi`#9Iag%&I0J&jB15xs^BprON|@L8DNi zTPpcwA~#bTG;_g7cz3vYzB@Gq`G2S*;C+qy2D0^Ct1DaIFs{1O=JI@=*;<0UYd`L*odP;~If&b4>u+WW>@z1_QC+x)V?DW*MqmRCP zVaL+Eelj|+dH=fcEyL?3Hq-4}3%D{nheo4`JkLQyu+~DMVCJ5HzzBj60Kto3!=?co zJ@zIpU0lK!pH4%eP*tb`Le%dp0jgk9gg!?Q2x=oWU6xk5WFMUM#pG*0)cQ4W@COa* z@2nZyKBP9F`P)mlas4(LjV4rk5F)*?8#Z9W00;!YI|mR!iE!w%Nf<-;;hRhU0D*d{ zRss|t6{z-fF2wLcuyH{3!L{X#g4Y{%A-9dN8HjVQ6^pfc8YwmJFQT!yh$KznoP#k2 z%-pZI7u3H1pb!uUyZ5Zc zANka`r}hrtyc_SDzY)EqX?>6HmNur5Ehn+#Jn@ug3U3MCDb#DfYkj@ZLx6|l0lav4 z@c?-M0uqI)s#n!qZJzl2e&n$-nBBxtt=I;VMBmIw~fAz<^WxkWGZ#U!Ziwn({ zqORTHGPA{Hr4ezZs!dHXPhbE;1V)}`l;tVJ3%q!UcnCy=M2!iAxoka56j|3!;cg^I zXEKUrJd%sd>0IlVR_F1rho1TgDd@<_ZB-bv%652lnwCavSI3<-W8Tqoxz&1>+De+G z1#2y-z^ZC0Bps)|ndMkes@!*6T3G6)4OPx#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT2?7BtA~$kKq5uE@8FWQhbVF}# zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1e{4kK~y-)jg`-DR8)iO0uhEC6}rgAXd6IPqM0 zAxVdvGdrTNXO9Ys``L$2+AJ9KHtbU(%uu+D;r zpp@!LP*6&PhyWrmGgufR3_zfWK^_p+@Cd*}@M2LaGNQ7Ta*xZxYGF3!Bs;CAQ3<|DgX#tl_`q) z3Xas)*tyNt6n_oax#x?;O3lPVjeE87AOr}gW1AQP0g-^k0t_Urb(E!oVps`n+<105G24S`Rp0(&x$#VH*CIs> zW&x1`v%t9wN-1cqH@bE|&YhV7AUJk>@89Z^6VsTSmnTSOX&sVx9wLA+gPGyIN1!!`076IS)Xz%*fT>@WaqhEO0P6lycYD`86~rK< z5OxqRU{;9pTpGT;wu-gyec7FKYUTX;PwmFWojUc^OZ_+RCWjVo#4n4f9ro?g4inpQ znmCoGp7KoKEx|j1_>N+t?z~Y)6>wh<;Mv2o2gn0p5DP>^yolst^VH|}VxL{kLtIc1 zUua%hxC?X+>Ax=eCqKSgR(G=PtBqvu@={}8+_nc?rgyolG{COZV~sB6DKwyoppfSo zWw`-+hG!3E4+aYpi`D@vRkj{Fj;&iYa5qwzs~N>tJ<=(q%v|%EX6v6{4?T3R1vGYk zPY<--WLs*BF{J_9mSJc0kax5>w^GZDjZ9`jYb^=Giilz2mJ{E|ax4i&_nay%t+Y+u lmV)}tPx#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT2?7Bp3a)amQ~&?~8FWQhbVF}# zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1Y}7>K~y-)g;h(BR7Dj2&Z*nCyYKXL zI|K6qK@1oaqAW~^vJh9s4KbLwFfp!Jniy8D%w&Fm3&WCMVAvZa8YLJL*$D}-aO5#C zGLP<_N8e}FImbo!pbQFCImx}XIbVH`Q-tR)Iz129$&NPbX))GlOt&31J6fStRA>h8 zV>3jN0~}atQRJfr3TjvyihOHZ`kS-+s&u+&r3(u;Jpg-d!~Z%wfBvoJa+c48P#j9) zv11(7-%yEP_GL6JKAvDS5|bz{%TV*)I~C7>g4xVPBpe}tQF+aBP_a4Qj=FI|_f4yt zMBzrO?G~e{+QZ4-wZXyy^#4K4gU@t3FOlz@+M67G*M+f@zS0+~(zl9SYDFd}OCb@4 zse>88%%Ez32@D310;Vt%FcV-5$1wmh7dg}#Cbg!|Tb-~{ZdKYaGU?2*X>bBAf^{wi+YT12O+jwO_3Jm0zTue=QUJAEN*{=g|3Z-^}p|^ARmCt>f;UC3L$z zm<>Uw4rO=jz>WbB2!MnD5W$FGySEt(G$hmrFo0|b9sraWq6D8s)M@W;mK43y^{bWd z)_ReK@OHi4>J+I_@6j5%Yir1h0wDxA=fKQQFhHXX00#Z%k8BjIog$Fg2+s%{DGUZ8 zgPI2v$hNl8v=-`dEB0yc&9$sJ7`5AO>94ikYn9(jm=vl8B8I9Vgc8gQ=iK8PJ{6Ol zsO-jMBLJWgQ=_8cWfTH61&N{>7`DPdODdL`+@|~B(^s=}%O5JTd@kZTI~P+~mQX0v z03rr~5rV)u2j}>)*_r5{sblAGr&}#st^%G&VdLZ>@qfCm_qQ=6p{j@AmlI& zFfphy(yFqa3^q5g`Li^`g~02VwtpM+9?z+Z-@P|^?_qXo`EL5Y6`j*EXdEh1-^{bX zRW4MO2Jr+529u%0L?ge^(A>a|Jb5hc5F{Pm~<_lM^ayY;dfl*Z@l~n;1 zg{VMPpiom%b1qWmrWd$0^5*lh4_Mgr}rymiXLCq-Gr-g`2IH?u-Xw}MHptgvd7JPO=cT^|%(--x>R hvfsCH3G%GAe*<`e@!q1d`Yiwe002ovPDHLkV1gH?IA#C< literal 0 HcmV?d00001 diff --git a/plugins/papu/btn_on.png b/plugins/papu/btn_on.png new file mode 100644 index 0000000000000000000000000000000000000000..b5f890dfbc7096d38913f1fd90eea7df1bda8c57 GIT binary patch literal 1263 zcmVPx#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT2?7BoGhv}T$s2ptR;qM3}Q4PY=8ua zVZvizWFGyPp1!a1sB+OgK$M4dQk%1>uj>1qI)oPo9iNA3W&3N@v=~d0>87DtODr0N zMa?@`QAHFPz<@&>MJ`HEP@<_X^8RM%Zp`ko>2%RZ7Z$EN0Q$m$PrE#G=B?Utmd|)! z?5S7A4scX`Q$pocS4PvpRVEloT|-nUOCLMuj0DGkfLYZ<6gYwh!#c@wP_Z%IjM|kt z-BYpGiTt%j(=0|)@q@|ErNP1i{j`tC^|iVWcU5W-pQFAGp8E$$s={%Io@8eyt>jxo@EFjz*1PF|CzkT>jE>0Xf z3sr?GAVj0d5}*nuMI0=mNYI#S=!SHQb+__+sODdWiS93vM>uWJ`e4`0!3lK{EibL( z&g~_%+a0J5MTiV-H*CO$0npQ7d?Hj83K|Yn2~dDkpgQDSh!KR~vWQyE-L;aUciL{% zx^~=&#QV3a)kdpGl{ydC&|X_ZUKH@&!x#f*9(6oS8l4xuAX1R(NY6+d2^0z?tqvF!e;kB%IIche|(%);H*R9*AGYLc#L<|vy_a&Ga#+XMP{_oI{0MKYs zqa;8f5Cn({CsaR$*8$35R^X;}ty4*`|S&B1lO^m@rgeYbZGeA6K0SqDr z5j}?exuO^(1_+}Rh9H6refe-axV8P*UpLo2 zc6sgnykDKwVw|(SQoonNJAnp)bq*qeq*?_tfe4Rn1)wyvb!$~@B~-C>pcX+0q6S_G zsm(Okbi?ZApA`A;J=#BRTtV*<9N+wOV)E{T?CA2H^gS(F$HO4mQ>3nzXP#{y$d-!W z2*E2f=xAyq-e^=5TlN4!0)hm90ssMtLRB@Ws#Rz6U>~NTyksjl&viN9y}0}U7%tNP z5c|a+AJzDN`Esw59b8-OypayvAzzw(zD%aXCu6QKRhz>Ah6oI6ODb&v34$O2A^`$X zAyH!@VXnCVlcvu13ivxYifbjM*8=jZ%*A5&=WhSsU(fTOk%H#W9vFi$+gzV_6h$)S z20H7VnF*e@Tc@qEaP^`rD$Y4lfm78&Ncvtwr?gmAsz3Cctga7=wo9V+-R#OH<Xo Z>_5*`&k?bFnp6M)002ovPDHLkV1l^&G&KMK literal 0 HcmV?d00001 diff --git a/plugins/papu/btn_up.png b/plugins/papu/btn_up.png new file mode 100644 index 0000000000000000000000000000000000000000..42673f93f5f6504a169b5bf3a08860a8be95ba68 GIT binary patch literal 1330 zcmV-21Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT2?7Bt2<*v$bN~PV8FWQhbVF}# zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1fNMnK~y-)g_X~5R85kU1w;b@3W7*%kdWZY=zzt!%a+JMuMG-`i1~gPCM3IYP7>XtFChIJPh4%1Mc`|6K$>ii!2SA=U@c){pP8}@I zq}h=7W^1L`H=?4_UJk`)os9;AE3Q+BD-xojwZ7n-(;S=v7)pugh=PjXp^!UHGmvSo zsYT6Vh3*RHSBd;owWe=I1BL1S*2V7RB>2%#6HrH+^=HV{4{fOIct!iZQJ3?k+`6jC zLcyc~)&>eeL0ChCAR=HEKmr5;5Cg&x2~dgvQP8RgfJ8?cg;+wN?6PXz&y{NS3Ki1} zYt|KhqDuGr=O5j$|AwNI6QfW6b#w9P(1xKmtCjxIirToQIhU*XxhAqSMF;`TJ2>w^ zL{LigL{LyltQD>$oPHN# z`o?YSzjl7|yw~r3vVGs|-R$GR+H)Un9@w>e`{w;|(g`$uZx-#vHmtQ^7C7f20uU)^ ztwE$wC=_vQd<0`Bc4F@f0{{q9m+r5;6)Xw?3SKENp{EmpSs|oCd8#5()!|N>zw~#j zv1_q0eZO|l+|wh6*M91jYHx4bGTKi*rlezV>(*V&%{3sh?8#-B7+UEb5P?8==lz`+ zJF#ok0eklkKmc66G!GGhfFM0U2tXo$tabnpv?^1jvaaAsZA!df?@Z~>h`oQgRI1iZ zBGj5~pxJ03GX~y!Xsw}?S{~3V>gCw@$g*q?#qkeD05HBh^E*fcLg>K(AcjCdBw%p> z18HXoWvQZ6tQK9;I?zbX7FDb1dp2*`+_j;I!7LzBU=~Hz`JdhQD^nA8* zDpm?2SV>5ZzyuZzFN7pdC6lC`%hE3t`L8{i-?lGfarI7}{{GeeJJac*ncK;0V(Ldi zH{NOzSI$ze@+?qp1;G)57f4u^n5gGB>J0_0cp8BN0tbKs00yx@L?nnvE-uePKAQx4 zF)!krs_?mm3p3N`E$DwP`e#4CRaSr5EsL#mcVoWweA0FMyw%&hjR)Area`4oos(e7=Z(r0~jn!ELumbRM`dSByoPxz~9bbrYt2>0ofI$%*}-#7dj7rJ@i;( z2x#K$NFTIb>pE(KG4X)w%CL8OD0o_*JE>daD#n_kbB=`JM8q(0$4hA09P>hzS+C;x o`(4v?an!t%US7gkgvX8j2S)zw*fCnhoB#j-07*qoM6N<$g3rWM<^TWy literal 0 HcmV?d00001 diff --git a/plugins/papu/gb_apu/Blip_Buffer.cpp b/plugins/papu/gb_apu/Blip_Buffer.cpp new file mode 100644 index 000000000..cde60d6f6 --- /dev/null +++ b/plugins/papu/gb_apu/Blip_Buffer.cpp @@ -0,0 +1,429 @@ + +// Blip_Buffer 0.3.4. http://www.slack.net/~ant/libs/ + +#include "Blip_Buffer.h" + +#include +#include +#include + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Blip_Buffer::Blip_Buffer() +{ + samples_per_sec = 44100; + buffer_ = NULL; + + // try to cause assertion failure if buffer is used before these are set + clocks_per_sec = 0; + factor_ = ~0ul; + offset_ = 0; + buffer_size_ = 0; + length_ = 0; + + bass_freq_ = 16; +} + +void Blip_Buffer::clear( bool entire_buffer ) +{ + long count = (entire_buffer ? buffer_size_ : samples_avail()); + offset_ = 0; + reader_accum = 0; + if ( buffer_ ) + memset( buffer_, sample_offset_ & 0xFF, (count + widest_impulse_) * sizeof (buf_t_) ); +} + +blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + unsigned new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) + 1 - widest_impulse_ - 64; + if ( msec != blip_default_length ) + { + size_t s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + require( false ); // requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + delete [] buffer_; + buffer_ = NULL; // allow for exception in allocation below + buffer_size_ = 0; + offset_ = 0; + + int const count_clocks_extra = 2; + buffer_ = BLARGG_NEW buf_t_ [new_size + widest_impulse_ + count_clocks_extra]; + BLARGG_CHECK_ALLOC( buffer_ ); + } + + buffer_size_ = new_size; + length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + + samples_per_sec = new_rate; + if ( clocks_per_sec ) + clock_rate( clocks_per_sec ); // recalculate factor + + bass_freq( bass_freq_ ); // recalculate shift + + clear(); + + return blargg_success; +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const +{ + blip_resampled_time_t factor = (unsigned long) floor( + (double) samples_per_sec / clock_rate * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + require( factor > 0 ); // clock_rate/sample_rate ratio is too large + return factor; +} + +Blip_Buffer::~Blip_Buffer() +{ + delete [] buffer_; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + if ( freq == 0 ) + { + bass_shift = 31; // 32 or greater invokes undefined behavior elsewhere + return; + } + bass_shift = 1 + (int) floor( 1.442695041 * log( 0.124 * samples_per_sec / freq ) ); + if ( bass_shift < 0 ) + bass_shift = 0; + if ( bass_shift > 24 ) + bass_shift = 24; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + return (resampled_time( t ) >> BLIP_BUFFER_ACCURACY) - (offset_ >> BLIP_BUFFER_ACCURACY); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( count > buffer_size_ ) + count = buffer_size_; + + return ((count << BLIP_BUFFER_ACCURACY) - offset_ + (factor_ - 1)) / factor_; +} + +void Blip_Impulse_::init( blip_pair_t_* imps, int w, int r, int fb ) +{ + fine_bits = fb; + width = w; + impulses = (imp_t*) imps; + generate = true; + volume_unit_ = -1.0; + res = r; + buf = NULL; + + impulse = &impulses [width * res * 2 * (fine_bits ? 2 : 1)]; + offset = 0; +} + +const int impulse_bits = 15; +const long impulse_amp = 1L << impulse_bits; +const long impulse_offset = impulse_amp / 2; + +void Blip_Impulse_::scale_impulse( int unit, imp_t* imp_in ) const +{ + long offset = ((long) unit << impulse_bits) - impulse_offset * unit + + (1 << (impulse_bits - 1)); + imp_t* imp = imp_in; + imp_t* fimp = impulse; + for ( int n = res / 2 + 1; n--; ) + { + int error = unit; + for ( int nn = width; nn--; ) + { + long a = ((long) *fimp++ * unit + offset) >> impulse_bits; + error -= a - unit; + *imp++ = (imp_t) a; + } + + // add error to middle + imp [-width / 2 - 1] += (imp_t) error; + } + + if ( res > 2 ) + { + // second half is mirror-image + const imp_t* rev = imp - width - 1; + for ( int nn = (res / 2 - 1) * width - 1; nn--; ) + *imp++ = *--rev; + *imp++ = (imp_t) unit; + } + + // copy to odd offset + *imp++ = (imp_t) unit; + memcpy( imp, imp_in, (res * width - 1) * sizeof *imp ); + + /* + for ( int i = 0; i < res; i++ ) + { + for ( int j = 0; j < width; j++ ) + printf( "%6d,", imp_in [i * width + j] - 0x8000 ); + printf( "\n" ); + }*/ +} + +const int max_res = 1 << blip_res_bits_; + +void Blip_Impulse_::fine_volume_unit() +{ + // to do: find way of merging in-place without temporary buffer + + imp_t temp [max_res * 2 * Blip_Buffer::widest_impulse_]; + scale_impulse( (offset & 0xffff) << fine_bits, temp ); + imp_t* imp2 = impulses + res * 2 * width; + scale_impulse( offset & 0xffff, imp2 ); + + // merge impulses + imp_t* imp = impulses; + imp_t* src2 = temp; + for ( int n = res / 2 * 2 * width; n--; ) + { + *imp++ = *imp2++; + *imp++ = *imp2++; + *imp++ = *src2++; + *imp++ = *src2++; + } +} + +void Blip_Impulse_::volume_unit( double new_unit ) +{ + if ( new_unit == volume_unit_ ) + return; + + if ( generate ) + treble_eq( blip_eq_t( -8.87, 8800, 44100 ) ); + + volume_unit_ = new_unit; + + offset = 0x10001 * (unsigned long) floor( volume_unit_ * 0x10000 + 0.5 ); + + if ( fine_bits ) + fine_volume_unit(); + else + scale_impulse( offset & 0xffff, impulses ); +} + +static const double pi = 3.1415926535897932384626433832795029L; + +void Blip_Impulse_::treble_eq( const blip_eq_t& new_eq ) +{ + if ( !generate && new_eq.treble == eq.treble && new_eq.cutoff == eq.cutoff && + new_eq.sample_rate == eq.sample_rate ) + return; // already calculated with same parameters + + generate = false; + eq = new_eq; + + double treble = pow( 10.0, 1.0 / 20 * eq.treble ); // dB (-6dB = 0.50) + if ( treble < 0.000005 ) + treble = 0.000005; + + const double treble_freq = 22050.0; // treble level at 22 kHz harmonic + const double sample_rate = eq.sample_rate; + const double pt = treble_freq * 2 / sample_rate; + double cutoff = eq.cutoff * 2 / sample_rate; + if ( cutoff >= pt * 0.95 || cutoff >= 0.95 ) + { + cutoff = 0.5; + treble = 1.0; + } + + // DSF Synthesis (See T. Stilson & J. Smith (1996), + // Alias-free digital synthesis of classic analog waveforms) + + // reduce adjacent impulse interference by using small part of wide impulse + const double n_harm = 4096; + const double rolloff = pow( treble, 1.0 / (n_harm * pt - n_harm * cutoff) ); + const double rescale = 1.0 / pow( rolloff, n_harm * cutoff ); + + const double pow_a_n = rescale * pow( rolloff, n_harm ); + const double pow_a_nc = rescale * pow( rolloff, n_harm * cutoff ); + + double total = 0.0; + const double to_angle = pi / 2 / n_harm / max_res; + + float buf [max_res * (Blip_Buffer::widest_impulse_ - 2) / 2]; + const int size = max_res * (width - 2) / 2; + for ( int i = size; i--; ) + { + double angle = (i * 2 + 1) * to_angle; + + // equivalent + //double y = dsf( angle, n_harm * cutoff, 1.0 ); + //y -= rescale * dsf( angle, n_harm * cutoff, rolloff ); + //y += rescale * dsf( angle, n_harm, rolloff ); + + const double cos_angle = cos( angle ); + const double cos_nc_angle = cos( n_harm * cutoff * angle ); + const double cos_nc1_angle = cos( (n_harm * cutoff - 1.0) * angle ); + + double b = 2.0 - 2.0 * cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + double d = 1.0 + rolloff * (rolloff - 2.0 * cos_angle); + double c = pow_a_n * rolloff * cos( (n_harm - 1.0) * angle ) - + pow_a_n * cos( n_harm * angle ) - + pow_a_nc * rolloff * cos_nc1_angle + + pow_a_nc * cos_nc_angle; + + // optimization of a / b + c / d + double y = (a * d + c * b) / (b * d); + + // fixed window which affects wider impulses more + if ( width > 12 ) + { + double window = cos( n_harm / 1.25 / Blip_Buffer::widest_impulse_ * angle ); + y *= window * window; + } + + total += (float) y; + buf [i] = (float) y; + } + + // integrate runs of length 'max_res' + double factor = impulse_amp * 0.5 / total; // 0.5 accounts for other mirrored half + imp_t* imp = impulse; + const int step = max_res / res; + int offset = res > 1 ? max_res : max_res / 2; + for ( int n = res / 2 + 1; n--; offset -= step ) + { + for ( int w = -width / 2; w < width / 2; w++ ) + { + double sum = 0; + for ( int i = max_res; i--; ) + { + int index = w * max_res + offset + i; + if ( index < 0 ) + index = -index - 1; + if ( index < size ) + sum += buf [index]; + } + *imp++ = (imp_t) floor( sum * factor + (impulse_offset + 0.5) ); + } + } + + // rescale + double unit = volume_unit_; + if ( unit >= 0 ) + { + volume_unit_ = -1; + volume_unit( unit ); + } +} + +void Blip_Buffer::remove_samples( long count ) +{ + require( buffer_ ); // sample rate must have been set + + if ( !count ) // optimization + return; + + remove_silence( count ); + + // Allows synthesis slightly past time passed to end_frame(), as long as it's + // not more than an output sample. + // to do: kind of hacky, could add run_until() which keeps track of extra synthesis + int const copy_extra = 1; + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + widest_impulse_ + copy_extra; + if ( count >= remain ) + memmove( buffer_, buffer_ + count, remain * sizeof (buf_t_) ); + else + memcpy( buffer_, buffer_ + count, remain * sizeof (buf_t_) ); + memset( buffer_ + remain, sample_offset_ & 0xFF, count * sizeof (buf_t_) ); +} + +#include BLARGG_ENABLE_OPTIMIZER + +long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, bool stereo ) +{ + require( buffer_ ); // sample rate must have been set + + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( !count ) + return 0; // optimization + + int sample_offset_ = this->sample_offset_; + int bass_shift = this->bass_shift; + buf_t_* buf = buffer_; + long accum = reader_accum; + + if ( !stereo ) + { + for ( long n = count; n--; ) + { + long s = accum >> accum_fract; + accum -= accum >> bass_shift; + accum += (long (*buf++) - sample_offset_) << accum_fract; + *out++ = (blip_sample_t) s; + + // clamp sample + if ( (BOOST::int16_t) s != s ) + out [-1] = blip_sample_t (0x7FFF - (s >> 24)); + } + } + else + { + for ( long n = count; n--; ) + { + long s = accum >> accum_fract; + accum -= accum >> bass_shift; + accum += (long (*buf++) - sample_offset_) << accum_fract; + *out = (blip_sample_t) s; + out += 2; + + // clamp sample + if ( (BOOST::int16_t) s != s ) + out [-2] = blip_sample_t (0x7FFF - (s >> 24)); + } + } + + reader_accum = accum; + + remove_samples( count ); + + return count; +} + +void Blip_Buffer::mix_samples( const blip_sample_t* in, long count ) +{ + buf_t_* buf = &buffer_ [(offset_ >> BLIP_BUFFER_ACCURACY) + (widest_impulse_ / 2 - 1)]; + + int prev = 0; + while ( count-- ) + { + int s = *in++; + *buf += s - prev; + prev = s; + ++buf; + } + *buf -= *--in; +} + diff --git a/plugins/papu/gb_apu/Blip_Buffer.h b/plugins/papu/gb_apu/Blip_Buffer.h new file mode 100644 index 000000000..c0d8aeeb4 --- /dev/null +++ b/plugins/papu/gb_apu/Blip_Buffer.h @@ -0,0 +1,259 @@ + +// Buffer of sound samples into which band-limited waveforms can be synthesized +// using Blip_Wave or Blip_Synth. + +// Blip_Buffer 0.3.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +#include "blargg_common.h" + +class Blip_Reader; + +// Source time unit. +typedef long blip_time_t; + +// Type of sample produced. Signed 16-bit format. +typedef BOOST::int16_t blip_sample_t; + +// Make buffer as large as possible (currently about 65000 samples) +const int blip_default_length = 0; + +typedef unsigned long blip_resampled_time_t; // not documented + +class Blip_Buffer { +public: + // Construct an empty buffer. + Blip_Buffer(); + ~Blip_Buffer(); + + // Set output sample rate and buffer length in milliseconds (1/1000 sec), + // then clear buffer. If length is not specified, make as large as possible. + // If there is insufficient memory for the buffer, sets the buffer length + // to 0 and returns error string (or propagates exception if compiler supports it). + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = blip_default_length ); + + // Length of buffer, in milliseconds + int length() const; + + // Current output sample rate + long sample_rate() const; + + // Number of source time units per second + void clock_rate( long ); + long clock_rate() const; + + // Set frequency at which high-pass filter attenuation passes -3dB + void bass_freq( int frequency ); + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clear out any samples waiting rather than the entire buffer. + void clear( bool entire_buffer = true ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begin + // a new time frame at the end of the current frame. All transitions must have + // been added before 'time'. + void end_frame( blip_time_t time ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Return number of samples actually read and removed. If stereo is + // true, increment 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, bool stereo = false ); + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + +// Beta features + + // Number of raw samples that can be mixed within frame of specified duration + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( const blip_sample_t* buf, long count ); + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer is full. + blip_time_t count_clocks( long count ) const; + + + // not documented yet + + void remove_silence( long count ); + + blip_resampled_time_t resampled_time( blip_time_t t ) const + { + return t * blip_resampled_time_t (factor_) + offset_; + } + + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; + + blip_resampled_time_t resampled_duration( int t ) const + { + return t * blip_resampled_time_t (factor_); + } + +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); + + // Don't use the following members. They are public only for technical reasons. + public: + enum { sample_offset_ = 0x7F7F }; // repeated byte allows memset to clear buffer + enum { widest_impulse_ = 24 }; + typedef BOOST::uint16_t buf_t_; + + unsigned long factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + unsigned buffer_size_; + private: + long reader_accum; + int bass_shift; + long samples_per_sec; + long clocks_per_sec; + int bass_freq_; + int length_; + + enum { accum_fract = 15 }; // less than 16 to give extra sample range + + friend class Blip_Reader; +}; + +// Low-pass equalization parameters (see notes.txt) +class blip_eq_t { +public: + blip_eq_t( double treble = 0 ); + blip_eq_t( double treble, long cutoff, long sample_rate ); +private: + double treble; + long cutoff; + long sample_rate; + friend class Blip_Impulse_; +}; + +// not documented yet (see Multi_Buffer.cpp for an example of use) +class Blip_Reader { + const Blip_Buffer::buf_t_* buf; + long accum; + #ifdef __MWERKS__ + void operator = ( struct foobar ); // helps optimizer + #endif +public: + // avoid anything which might cause optimizer to put object in memory + + int begin( Blip_Buffer& blip_buf ) { + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum; + return blip_buf.bass_shift; + } + + int read() const { + return accum >> Blip_Buffer::accum_fract; + } + + void next( int bass_shift = 9 ) { + accum -= accum >> bass_shift; + accum += ((long) *buf++ - Blip_Buffer::sample_offset_) << Blip_Buffer::accum_fract; + } + + void end( Blip_Buffer& blip_buf ) { + blip_buf.reader_accum = accum; + } +}; + + + +// End of public interface + +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +const int blip_res_bits_ = 5; + +typedef BOOST::uint32_t blip_pair_t_; + +class Blip_Impulse_ { + typedef BOOST::uint16_t imp_t; + + blip_eq_t eq; + double volume_unit_; + imp_t* impulses; + imp_t* impulse; + int width; + int fine_bits; + int res; + bool generate; + + void fine_volume_unit(); + void scale_impulse( int unit, imp_t* ) const; +public: + Blip_Buffer* buf; + BOOST::uint32_t offset; + + void init( blip_pair_t_* impulses, int width, int res, int fine_bits = 0 ); + void volume_unit( double ); + void treble_eq( const blip_eq_t& ); +}; + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), cutoff( 0 ), sample_rate( 44100 ) { +} + +inline blip_eq_t::blip_eq_t( double t, long c, long sr ) : + treble( t ), cutoff( c ), sample_rate( sr ) { +} + +inline int Blip_Buffer::length() const { + return length_; +} + +inline long Blip_Buffer::samples_avail() const { + return long (offset_ >> BLIP_BUFFER_ACCURACY); +} + +inline long Blip_Buffer::sample_rate() const { + return samples_per_sec; +} + +inline void Blip_Buffer::end_frame( blip_time_t t ) { + offset_ += t * factor_; + assert(( "Blip_Buffer::end_frame(): Frame went past end of buffer", + samples_avail() <= (long) buffer_size_ )); +} + +inline void Blip_Buffer::remove_silence( long count ) { + assert(( "Blip_Buffer::remove_silence(): Tried to remove more samples than available", + count <= samples_avail() )); + offset_ -= blip_resampled_time_t (count) << BLIP_BUFFER_ACCURACY; +} + +inline int Blip_Buffer::output_latency() const { + return widest_impulse_ / 2; +} + +inline long Blip_Buffer::clock_rate() const { + return clocks_per_sec; +} + +inline void Blip_Buffer::clock_rate( long cps ) +{ + clocks_per_sec = cps; + factor_ = clock_rate_factor( cps ); +} + +#include "Blip_Synth.h" + +#endif + diff --git a/plugins/papu/gb_apu/Blip_Synth.h b/plugins/papu/gb_apu/Blip_Synth.h new file mode 100644 index 000000000..709fbf359 --- /dev/null +++ b/plugins/papu/gb_apu/Blip_Synth.h @@ -0,0 +1,208 @@ + +// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding +// waveforms to a Blip_Buffer. + +// Blip_Buffer 0.3.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BLIP_SYNTH_H +#define BLIP_SYNTH_H + +#ifndef BLIP_BUFFER_H + #include "Blip_Buffer.h" +#endif + +// Quality level. Higher levels are slower, and worse in a few cases. +// Use blip_good_quality as a starting point. +const int blip_low_quality = 1; +const int blip_med_quality = 2; +const int blip_good_quality = 3; +const int blip_high_quality = 4; + +// Blip_Synth is a transition waveform synthesizer which adds band-limited +// offsets (transitions) into a Blip_Buffer. For a simpler interface, use +// Blip_Wave (below). +// +// Range specifies the greatest expected offset that will occur. For a +// waveform that goes between +amp and -amp, range should be amp * 2 (half +// that if it only goes between +amp and 0). When range is large, a higher +// accuracy scheme is used; to force this even when range is small, pass +// the negative of range (i.e. -range). +template +class Blip_Synth { + BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 ); + BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 ); + enum { + abs_range = (range < 0) ? -range : range, + fine_mode = (range > 512 || range < 0), + width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_), + res = 1 << blip_res_bits_, + impulse_size = width / 2 * (fine_mode + 1), + base_impulses_size = width / 2 * (res / 2 + 1), + fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 : + abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 : + abs_range <= 2048 ? 7 : 8) : 0) + }; + blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size]; + Blip_Impulse_ impulse; + void init() { impulse.init( impulses, width, res, fine_bits ); } +public: + Blip_Synth() { init(); } + Blip_Synth( double volume ) { init(); this->volume( volume ); } + + // Configure low-pass filter (see notes.txt). Not optimized for real-time control + void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); } + + // Set volume of a transition at amplitude 'range' by setting volume_unit + // to v / range + void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); } + + // Set base volume unit of transitions, where 1.0 is a full swing between the + // positive and negative extremes. Not optimized for real-time control. + void volume_unit( double unit ) { impulse.volume_unit( unit ); } + + // Default Blip_Buffer used for output when none is specified for a given call + Blip_Buffer* output() const { return impulse.buf; } + void output( Blip_Buffer* b ) { impulse.buf = b; } + + // Add an amplitude offset (transition) with a magnitude of delta * volume_unit + // into the specified buffer (default buffer if none specified) at the + // specified source time. Delta can be positive or negative. To increase + // performance by inlining code at the call site, use offset_inline(). + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + void offset_resampled( blip_resampled_time_t t, int o ) const { + offset_resampled( t, o, impulse.buf ); + } + void offset( blip_time_t t, int delta ) const { + offset( t, delta, impulse.buf ); + } + void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t time, int delta ) const { + offset_inline( time, delta, impulse.buf ); + } +}; + +// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer. +// A wave is built from a series of delays and new amplitudes. This provides a +// simpler interface than Blip_Synth, nothing more. +template +class Blip_Wave { + Blip_Synth synth; + blip_time_t time_; + int last_amp; + void init() { time_ = 0; last_amp = 0; } +public: + // Start wave at time 0 and amplitude 0 + Blip_Wave() { init(); } + Blip_Wave( double volume ) { init(); this->volume( volume ); } + + // See Blip_Synth for description + void volume( double v ) { synth.volume( v ); } + void volume_unit( double v ) { synth.volume_unit( v ); } + void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); } + Blip_Buffer* output() const { return synth.output(); } + void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; } + + // Current time in frame + blip_time_t time() const { return time_; } + void time( blip_time_t t ) { time_ = t; } + + // Current amplitude of wave + int amplitude() const { return last_amp; } + void amplitude( int ); + + // Move forward by 't' time units + void delay( blip_time_t t ) { time_ += t; } + + // End time frame of specified duration. Localize time to new frame. + // If wave hadn't been run to end of frame, start it at beginning of new frame. + void end_frame( blip_time_t duration ) + { + time_ -= duration; + if ( time_ < 0 ) + time_ = 0; + } +}; + +// End of public interface + +template +void Blip_Wave::amplitude( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + synth.offset_inline( time_, delta ); +} + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + typedef blip_pair_t_ pair_t; + + unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1; + assert(( "Blip_Synth/Blip_wave: Went past end of buffer", + sample_index < blip_buf->buffer_size_ )); + enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 }; + pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index]; + + enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ }; + enum { mask = res * 2 - 1 }; + const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size]; + + pair_t offset = impulse.offset * delta; + + if ( !fine_bits ) + { + // normal mode + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta; + t1 += imp [1] * delta; + imp += 2; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } + else + { + // fine mode + enum { sub_range = 1 << fine_bits }; + delta += sub_range / 2; + int delta2 = (delta & (sub_range - 1)) - sub_range / 2; + delta >>= fine_bits; + + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta2; + t0 += imp [1] * delta; + + t1 += imp [2] * delta2; + t1 += imp [3] * delta; + + imp += 4; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } +} + +template +void Blip_Synth::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); +} + +#endif + diff --git a/plugins/papu/gb_apu/Gb_Apu.cpp b/plugins/papu/gb_apu/Gb_Apu.cpp new file mode 100644 index 000000000..49a909102 --- /dev/null +++ b/plugins/papu/gb_apu/Gb_Apu.cpp @@ -0,0 +1,261 @@ + +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/libs/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Gb_Apu::Gb_Apu() +{ + square1.synth = &square_synth; + square2.synth = &square_synth; + square1.has_sweep = true; + wave.synth = &other_synth; + noise.synth = &other_synth; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = &wave; + oscs [3] = &noise; + + volume( 1.0 ); + reset(); +} + +Gb_Apu::~Gb_Apu() +{ +} + +void Gb_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + other_synth.treble_eq( eq ); +} + +void Gb_Apu::volume( double vol ) +{ + vol *= 0.60 / osc_count; + square_synth.volume( vol ); + other_synth.volume( vol ); +} + +void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Gb_Apu::reset() +{ + next_frame_time = 0; + last_time = 0; + frame_count = 0; + stereo_found = false; + + square1.reset(); + square2.reset(); + wave.reset(); + noise.reset(); + + memset( regs, 0, sizeof regs ); +} + +void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + + Gb_Osc& osc = *oscs [index]; + if ( center && !left && !right ) + { + // mono + left = center; + right = center; + } + else + { + // must be silenced or stereo + require( (!left && !right) || (left && right) ); + } + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Gb_Apu::run_until( gb_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + if ( end_time == last_time ) + return; + + while ( true ) + { + gb_time_t time = next_frame_time; + if ( time > end_time ) + time = end_time; + + // run oscillators + for ( int i = 0; i < osc_count; ++i ) { + Gb_Osc& osc = *oscs [i]; + if ( osc.output ) { + if ( osc.output != osc.outputs [3] ) + stereo_found = true; + osc.run( last_time, time ); + } + } + last_time = time; + + if ( time == end_time ) + break; + + next_frame_time += 4194304 / 256; // 256 Hz + + // 256 Hz actions + square1.clock_length(); + square2.clock_length(); + wave.clock_length(); + noise.clock_length(); + + frame_count = (frame_count + 1) & 3; + if ( frame_count == 0 ) { + // 64 Hz actions + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } + + if ( frame_count & 1 ) + square1.clock_sweep(); // 128 Hz action + } +} + +bool Gb_Apu::end_frame( gb_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + assert( next_frame_time >= end_time ); + next_frame_time -= end_time; + + assert( last_time >= end_time ); + last_time -= end_time; + + bool result = stereo_found; + stereo_found = false; + return result; +} + +void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data ) +{ + require( (unsigned) data < 0x100 ); + + int reg = addr - start_addr; + if ( (unsigned) reg >= register_count ) + return; + + run_until( time ); + + regs [reg] = data; + + if ( addr < 0xff24 ) + { + // oscillator + int index = reg / 5; + oscs [index]->write_register( reg - index * 5, data ); + } + // added + else if ( addr == 0xff24 ) + { + int global_volume = data & 7; + int old_volume = square1.global_volume; + if ( old_volume != global_volume ) + { + int any_enabled = false; + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + if ( osc.enabled ) + { + if ( osc.last_amp ) + { + int new_amp = osc.last_amp * global_volume / osc.global_volume; + if ( osc.output ) + square_synth.offset( time, new_amp - osc.last_amp, osc.output ); + osc.last_amp = new_amp; + } + any_enabled |= osc.volume; + } + osc.global_volume = global_volume; + } + + if ( !any_enabled && square1.outputs [3] ) + square_synth.offset( time, (global_volume - old_volume) * 15 * 2, square1.outputs [3] ); + } + } + + else if ( addr == 0xff25 || addr == 0xff26 ) + { + int mask = (regs [0xff26 - start_addr] & 0x80) ? ~0 : 0; + int flags = regs [0xff25 - start_addr] & mask; + + // left/right assignments + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + osc.enabled &= mask; + int bits = flags >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = (bits >> 3 & 2) | (bits & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output && osc.last_amp ) + { + if ( old_output ) + square_synth.offset( time, -osc.last_amp, old_output ); + osc.last_amp = 0; + } + } + } + else if ( addr >= 0xff30 ) + { + int index = (addr & 0x0f) * 2; + wave.wave [index] = data >> 4; + wave.wave [index + 1] = data & 0x0f; + } +} + +int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr ) +{ + // function now takes actual address, i.e. 0xFFXX + require( start_addr <= addr && addr <= end_addr ); + + run_until( time ); + + int data = regs [addr - start_addr]; + + if ( addr == 0xff26 ) + { + data &= 0xf0; + for ( int i = 0; i < osc_count; i++ ) + { + const Gb_Osc& osc = *oscs [i]; + if ( osc.enabled && (osc.length || !osc.length_enabled) ) + data |= 1 << i; + } + } + + return data; +} + diff --git a/plugins/papu/gb_apu/Gb_Apu.h b/plugins/papu/gb_apu/Gb_Apu.h new file mode 100644 index 000000000..e2f940b94 --- /dev/null +++ b/plugins/papu/gb_apu/Gb_Apu.h @@ -0,0 +1,84 @@ + +// Nintendo Game Boy PAPU sound chip emulator + +// Gb_Snd_Emu 0.1.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef GB_APU_H +#define GB_APU_H + +typedef long gb_time_t; // clock cycle count +typedef unsigned gb_addr_t; // 16-bit address + +#include "Gb_Oscs.h" + +class Gb_Apu { +public: + Gb_Apu(); + ~Gb_Apu(); + + // Set overall volume of all oscillators, where 1.0 is full volume + void volume( double ); + + // Set treble equalization + void treble_eq( const blip_eq_t& ); + + // Reset oscillators and internal state + void reset(); + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silence all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, + // which refer to Square 1, Square 2, Wave, and Noise. + // If buffer is NULL, silence oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reads and writes at addr must satisfy start_addr <= addr <= end_addr + enum { start_addr = 0xff10 }; + enum { end_addr = 0xff3f }; + enum { register_count = end_addr - start_addr + 1 }; + + // Write 'data' to address at specified time + void write_register( gb_time_t, gb_addr_t, int data ); + + // Read from address at specified time + int read_register( gb_time_t, gb_addr_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new frame at time 0. Return true if any oscillators added + // sound to one of the left/right buffers, false if they only added + // to the center buffer. + bool end_frame( gb_time_t ); + +private: + // noncopyable + Gb_Apu( const Gb_Apu& ); + Gb_Apu& operator = ( const Gb_Apu& ); + + Gb_Osc* oscs [osc_count]; + gb_time_t next_frame_time; + gb_time_t last_time; + int frame_count; + bool stereo_found; + + Gb_Square square1; + Gb_Square square2; + Gb_Wave wave; + Gb_Noise noise; + BOOST::uint8_t regs [register_count]; + Gb_Square::Synth square_synth; // shared between squares + Gb_Wave::Synth other_synth; // shared between wave and noise + + void run_until( gb_time_t ); +}; + +inline void Gb_Apu::output( Blip_Buffer* b ) { output( b, NULL, NULL ); } + +inline void Gb_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, NULL, NULL ); } + +#endif + diff --git a/plugins/papu/gb_apu/Gb_Oscs.cpp b/plugins/papu/gb_apu/Gb_Oscs.cpp new file mode 100644 index 000000000..bf8d72fcc --- /dev/null +++ b/plugins/papu/gb_apu/Gb_Oscs.cpp @@ -0,0 +1,451 @@ + +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/libs/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +const int trigger = 0x80; + +// Gb_Osc + +Gb_Osc::Gb_Osc() +{ + output = NULL; + outputs [0] = NULL; + outputs [1] = NULL; + outputs [2] = NULL; + outputs [3] = NULL; +} + +void Gb_Osc::reset() +{ + delay = 0; + last_amp = 0; + period = 2048; + volume = 0; + global_volume = 7; // added + frequency = 0; + length = 0; + enabled = false; + length_enabled = false; + output_select = 3; + output = outputs [output_select]; +} + +void Gb_Osc::clock_length() +{ + if ( length_enabled && length ) + --length; +} + +void Gb_Osc::write_register( int reg, int value ) +{ + if ( reg == 4 ) + length_enabled = value & 0x40; +} + +// Gb_Env + +void Gb_Env::reset() +{ + env_period = 0; + env_dir = 0; + env_delay = 0; + new_volume = 0; + Gb_Osc::reset(); +} + +Gb_Env::Gb_Env() +{ +} + +void Gb_Env::clock_envelope() +{ + if ( env_delay && !--env_delay ) + { + env_delay = env_period; + if ( env_dir ) + { + if ( volume < 15 ) + ++volume; + } + else if ( volume > 0 ) + { + --volume; + } + } +} + +void Gb_Env::write_register( int reg, int value ) +{ + if ( reg == 2 ) { + env_period = value & 7; + env_dir = value & 8; + volume = new_volume = value >> 4; + } + else if ( reg == 4 && (value & trigger) ) { + env_delay = env_period; + volume = new_volume; + enabled = true; + } + Gb_Osc::write_register( reg, value ); +} + +// Gb_Square + +void Gb_Square::reset() +{ + phase = 1; + duty = 1; + + sweep_period = 0; + sweep_delay = 0; + sweep_shift = 0; + sweep_dir = 0; + sweep_freq = 0; + + new_length = 0; + + Gb_Env::reset(); +} + +Gb_Square::Gb_Square() +{ + has_sweep = false; +} + +void Gb_Square::clock_sweep() +{ + if ( sweep_period && sweep_delay && !--sweep_delay ) + { + sweep_delay = sweep_period; + frequency = sweep_freq; + + period = (2048 - frequency) * 4; + + int offset = sweep_freq >> sweep_shift; + if ( sweep_dir ) + offset = -offset; + sweep_freq += offset; + + if ( sweep_freq < 0 ) + { + sweep_freq = 0; + } + else if ( sweep_freq >= 2048 ) + { + sweep_delay = 0; + sweep_freq = 2048; // stop sound output + } + } +} + +void Gb_Square::write_register( int reg, int value ) +{ + static unsigned char const duty_table [4] = { 1, 2, 4, 6 }; + + switch ( reg ) + { + case 0: + sweep_period = (value >> 4) & 7; // changed + sweep_shift = value & 7; + sweep_dir = value & 0x08; + break; + + case 1: + new_length = length = 64 - (value & 0x3f); + duty = duty_table [value >> 6]; + break; + + case 3: + frequency = (frequency & ~0xFF) + value; + length = new_length; + break; + + case 4: + frequency = (value & 7) * 0x100 + (frequency & 0xFF); + length = new_length; + if ( value & trigger ) + { + sweep_freq = frequency; + if ( has_sweep && sweep_period && sweep_shift ) + { + sweep_delay = 1; + clock_sweep(); + } + } + break; + } + + period = (2048 - frequency) * 4; + + Gb_Env::write_register( reg, value ); +} + +void Gb_Square::run( gb_time_t time, gb_time_t end_time ) +{ + // to do: when frequency goes above 20000 Hz output should actually be 1/2 volume + // rather than 0 + + if ( !enabled || (!length && length_enabled) || !volume || sweep_freq == 2048 || + !frequency || period < 27 ) + { + if ( last_amp ) + { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + int amp = (phase < duty) ? volume : -volume; + amp *= global_volume; + if ( amp != last_amp ) + { + synth->offset( time, amp - last_amp, output ); + last_amp = amp; + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const int duty = this->duty; + int phase = this->phase; + amp *= 2; + do + { + phase = (phase + 1) & 7; + if ( phase == 0 || phase == duty ) + { + amp = -amp; + synth->offset_inline( time, amp, output ); + } + time += period; + } + while ( time < end_time ); + + this->phase = phase; + last_amp = amp >> 1; + } + delay = time - end_time; + } +} + + +// Gb_Wave + +void Gb_Wave::reset() +{ + volume_shift = 0; + wave_pos = 0; + new_length = 0; + memset( wave, 0, sizeof wave ); + Gb_Osc::reset(); +} + +Gb_Wave::Gb_Wave() { +} + +void Gb_Wave::write_register( int reg, int value ) +{ + switch ( reg ) + { + case 0: + new_enabled = value & 0x80; + enabled &= new_enabled; + break; + + case 1: + new_length = length = 256 - value; + break; + + case 2: + volume = ((value >> 5) & 3); + volume_shift = (volume - 1) & 7; // silence = 7 + break; + + case 3: + frequency = (frequency & ~0xFF) + value; + break; + + case 4: + frequency = (value & 7) * 0x100 + (frequency & 0xFF); + if ( new_enabled && (value & trigger) ) + { + wave_pos = 0; + length = new_length; + enabled = true; + } + break; + } + + period = (2048 - frequency) * 2; + + Gb_Osc::write_register( reg, value ); +} + +void Gb_Wave::run( gb_time_t time, gb_time_t end_time ) +{ + // to do: when frequency goes above 20000 Hz output should actually be 1/2 volume + // rather than 0 + if ( !enabled || (!length && length_enabled) || !volume || !frequency || period < 7 ) + { + if ( last_amp ) { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + int const vol_factor = global_volume * 2; + + // wave data or shift may have changed + int diff = (wave [wave_pos] >> volume_shift) * vol_factor - last_amp; + if ( diff ) + { + last_amp += diff; + synth->offset( time, diff, output ); + } + + time += delay; + if ( time < end_time ) + { + int const volume_shift = this->volume_shift; + int wave_pos = this->wave_pos; + + do + { + wave_pos = unsigned (wave_pos + 1) % wave_size; + int amp = (wave [wave_pos] >> volume_shift) * vol_factor; + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->wave_pos = wave_pos; + } + delay = time - end_time; + } +} + + +// Gb_Noise + +void Gb_Noise::reset() +{ + bits = 1; + tap = 14; + Gb_Env::reset(); +} + +Gb_Noise::Gb_Noise() { +} + +void Gb_Noise::write_register( int reg, int value ) +{ + if ( reg == 1 ) { + new_length = length = 64 - (value & 0x3f); + } + else if ( reg == 2 ) { + // based on VBA code, noise is the only exception to the envelope code + // while the volume level here is applied when the channel is enabled, + // current volume is only affected by writes to this register if volume + // is zero and direction is up... (definitely needs verification) + int temp = volume; + Gb_Env::write_register( reg, value ); + if ( ( value & 0xF8 ) != 0 ) volume = temp; + return; + } + else if ( reg == 3 ) { + tap = 14 - (value & 8); + // noise formula and frequency tested against Metroid 2 and Zelda LA + int divisor = (value & 7) * 16; + if ( !divisor ) + divisor = 8; + period = divisor << (value >> 4); + } + else if ( reg == 4 && value & trigger ) { + bits = ~0u; + length = new_length; + } + + Gb_Env::write_register( reg, value ); +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Gb_Noise::run( gb_time_t time, gb_time_t end_time ) +{ + if ( !enabled || (!length && length_enabled) || !volume ) { + if ( last_amp ) { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + int amp = bits & 1 ? -volume : volume; + amp *= global_volume; + if ( amp != last_amp ) { + synth->offset( time, amp - last_amp, output ); + last_amp = amp; + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + // keep parallel resampled time to eliminate multiplication in the loop + const blip_resampled_time_t resampled_period = + output->resampled_duration( period ); + blip_resampled_time_t resampled_time = output->resampled_time( time ); + const unsigned mask = ~(1u << tap); + unsigned bits = this->bits; + amp *= 2; + + do { + unsigned feedback = bits; + bits >>= 1; + feedback = 1 & (feedback ^ bits); + time += period; + bits = (feedback << tap) | (bits & mask); + // feedback just happens to be true only when the level needs to change + // (the previous and current bits are different) + if ( feedback ) { + amp = -amp; + synth->offset_resampled( resampled_time, amp, output ); + } + resampled_time += resampled_period; + } + while ( time < end_time ); + + this->bits = bits; + last_amp = amp >> 1; + } + delay = time - end_time; + } +} + diff --git a/plugins/papu/gb_apu/Gb_Oscs.h b/plugins/papu/gb_apu/Gb_Oscs.h new file mode 100644 index 000000000..0c0092a8d --- /dev/null +++ b/plugins/papu/gb_apu/Gb_Oscs.h @@ -0,0 +1,100 @@ + +// Private oscillators used by Gb_Apu + +// Gb_Snd_Emu 0.1.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef GB_OSCS_H +#define GB_OSCS_H + +#include "Blip_Buffer.h" + +enum { gb_apu_max_vol = 7 }; + +struct Gb_Osc { + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + + int delay; + int last_amp; + int period; + int volume; + int global_volume; + int frequency; + int length; + int new_length; + bool enabled; + bool length_enabled; + + Gb_Osc(); + + void clock_length(); + void reset(); + virtual void run( gb_time_t begin, gb_time_t end ) = 0; + virtual void write_register( int reg, int value ); +}; + +struct Gb_Env : Gb_Osc { + int env_period; + int env_dir; + int env_delay; + int new_volume; + + Gb_Env(); + void reset(); + void clock_envelope(); + void write_register( int, int ); +}; + +struct Gb_Square : Gb_Env { + int phase; + int duty; + + int sweep_period; + int sweep_delay; + int sweep_shift; + int sweep_dir; + int sweep_freq; + bool has_sweep; + + typedef Blip_Synth Synth; + const Synth* synth; + + Gb_Square(); + void reset(); + void run( gb_time_t, gb_time_t ); + void write_register( int, int ); + void clock_sweep(); +}; + +struct Gb_Wave : Gb_Osc { + int volume_shift; + unsigned wave_pos; + enum { wave_size = 32 }; + bool new_enabled; + BOOST::uint8_t wave [wave_size]; + + typedef Blip_Synth Synth; + const Synth* synth; + + Gb_Wave(); + void reset(); + void run( gb_time_t, gb_time_t ); + void write_register( int, int ); +}; + +struct Gb_Noise : Gb_Env { + unsigned bits; + int tap; + + typedef Blip_Synth Synth; + const Synth* synth; + + Gb_Noise(); + void reset(); + void run( gb_time_t, gb_time_t ); + void write_register( int, int ); +}; + +#endif + diff --git a/plugins/papu/gb_apu/LGPL.txt b/plugins/papu/gb_apu/LGPL.txt new file mode 100644 index 000000000..b1e3f5a26 --- /dev/null +++ b/plugins/papu/gb_apu/LGPL.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/plugins/papu/gb_apu/Multi_Buffer.cpp b/plugins/papu/gb_apu/Multi_Buffer.cpp new file mode 100644 index 000000000..e4383cde8 --- /dev/null +++ b/plugins/papu/gb_apu/Multi_Buffer.cpp @@ -0,0 +1,215 @@ + +// Blip_Buffer 0.3.4. http://www.slack.net/~ant/libs/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; +} + +blargg_err_t Multi_Buffer::set_channel_count( int ) +{ + return blargg_success; +} + +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +{ +} + +Mono_Buffer::~Mono_Buffer() +{ +} + +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) +{ + BLARGG_RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +// Silent_Buffer + +Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +{ + chan.left = NULL; + chan.center = NULL; + chan.right = NULL; +} + +// Mono_Buffer + +Mono_Buffer::channel_t Mono_Buffer::channel( int index ) +{ + channel_t ch; + ch.center = &buf; + ch.left = &buf; + ch.right = &buf; + return ch; +} + +void Mono_Buffer::end_frame( blip_time_t t, bool ) +{ + buf.end_frame( t ); +} + +// Stereo_Buffer + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +{ + chan.center = &bufs [0]; + chan.left = &bufs [1]; + chan.right = &bufs [2]; +} + +Stereo_Buffer::~Stereo_Buffer() +{ +} + +blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + BLARGG_RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + stereo_added = false; + was_stereo = false; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + stereo_added |= stereo; +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +{ + require( !(count & 1) ); // count must be even + count = (unsigned) count / 2; + + long avail = bufs [0].samples_avail(); + if ( count > avail ) + count = avail; + if ( count ) + { + if ( stereo_added || was_stereo ) + { + mix_stereo( out, count ); + + bufs [0].remove_samples( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + else + { + mix_mono( out, count ); + + bufs [0].remove_samples( count ); + + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + + // to do: this might miss opportunities for optimization + if ( !bufs [0].samples_avail() ) { + was_stereo = stereo_added; + stereo_added = false; + } + } + + return count * 2; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader left; + Blip_Reader right; + Blip_Reader center; + + left.begin( bufs [1] ); + right.begin( bufs [2] ); + int bass = center.begin( bufs [0] ); + + while ( count-- ) + { + int c = center.read(); + long l = c + left.read(); + long r = c + right.read(); + center.next( bass ); + out [0] = l; + out [1] = r; + out += 2; + + if ( (BOOST::int16_t) l != l ) + out [-2] = 0x7FFF - (l >> 24); + + left.next( bass ); + right.next( bass ); + + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + center.end( bufs [0] ); + right.end( bufs [2] ); + left.end( bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader in; + int bass = in.begin( bufs [0] ); + + while ( count-- ) + { + long s = in.read(); + in.next( bass ); + out [0] = s; + out [1] = s; + out += 2; + + if ( (BOOST::int16_t) s != s ) { + s = 0x7FFF - (s >> 24); + out [-2] = s; + out [-1] = s; + } + } + + in.end( bufs [0] ); +} + diff --git a/plugins/papu/gb_apu/Multi_Buffer.h b/plugins/papu/gb_apu/Multi_Buffer.h new file mode 100644 index 000000000..5e3c478e0 --- /dev/null +++ b/plugins/papu/gb_apu/Multi_Buffer.h @@ -0,0 +1,174 @@ + +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.3.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "Blip_Buffer.h" + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer( int samples_per_frame ); + virtual ~Multi_Buffer() { } + + // Set the number of channels available + virtual blargg_err_t set_channel_count( int ); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + virtual channel_t channel( int index ) = 0; + + // See Blip_Buffer.h + virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0; + virtual void clock_rate( long ) = 0; + virtual void bass_freq( int ) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' + // if nothing was added to the left and right buffers of any channel for + // this time frame. + virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) = 0; + virtual long samples_avail() const = 0; + +protected: + void channels_changed() { channels_changed_count_++; } +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int const samples_per_frame_; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; +public: + Mono_Buffer(); + ~Mono_Buffer(); + + // Buffer used for all channels + Blip_Buffer* center() { return &buf; } + + // See Multi_Buffer + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); +}; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + + // Buffers used for all channels + Blip_Buffer* center() { return &bufs [0]; } + Blip_Buffer* left() { return &bufs [1]; } + Blip_Buffer* right() { return &bufs [2]; } + + // See Multi_Buffer + blargg_err_t set_sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int index ); + void end_frame( blip_time_t, bool added_stereo = true ); + + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + enum { buf_count = 3 }; + Blip_Buffer bufs [buf_count]; + channel_t chan; + bool stereo_added; + bool was_stereo; + + void mix_stereo( blip_sample_t*, long ); + void mix_mono( blip_sample_t*, long ); +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer { + channel_t chan; +public: + Silent_Buffer(); + + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ) { } + void bass_freq( int ) { } + void clear() { } + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t, bool unused = true ) { } + long samples_avail() const { return 0; } + long read_samples( blip_sample_t*, long ) { return 0; } +}; + + +// End of public interface + +inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec ) +{ + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return blargg_success; +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; } + +inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int index ) { return chan; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); } + +inline void Mono_Buffer::clear() { buf.clear(); } + +inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); } + +inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + +inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } + +#endif + diff --git a/plugins/papu/gb_apu/blargg_common.h b/plugins/papu/gb_apu/blargg_common.h new file mode 100644 index 000000000..519bba7c4 --- /dev/null +++ b/plugins/papu/gb_apu/blargg_common.h @@ -0,0 +1,178 @@ + +// Sets up common environment for Shay Green's libraries. +// +// Don't modify this file directly; #define HAVE_CONFIG_H and put your +// configuration into "config.h". + +// Copyright (C) 2004-2005 Shay Green. + +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// Allow prefix configuration file *which can re-include blargg_common.h* +// (probably indirectly). +#ifdef HAVE_CONFIG_H + #undef BLARGG_COMMON_H + #include "config.h" + #define BLARGG_COMMON_H +#endif + +// Source files use #include BLARGG_ENABLE_OPTIMIZER before performance-critical code +#ifndef BLARGG_ENABLE_OPTIMIZER + #define BLARGG_ENABLE_OPTIMIZER "blargg_common.h" +#endif + +// Source files have #include BLARGG_SOURCE_BEGIN at the beginning +#ifndef BLARGG_SOURCE_BEGIN + #define BLARGG_SOURCE_BEGIN "blargg_source.h" +#endif + +// Determine compiler's language support + +#if defined (__MWERKS__) + // Metrowerks CodeWarrior + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + +#elif defined (_MSC_VER) + // Microsoft Visual C++ + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + +#elif defined (__GNUC__) + // GNU C++ + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #define BLARGG_COMPILER_HAS_BOOL 1 + +#elif defined (__MINGW32__) + // Mingw? + #define BLARGG_COMPILER_HAS_BOOL 1 + +#elif __cplusplus < 199711 + // Pre-ISO C++ compiler + #define BLARGG_COMPILER_HAS_BOOL 0 + #define STATIC_CAST( type ) (type) + +#endif + +// STATIC_CAST(T) (expr) -> static_cast< T > (expr) +#ifndef STATIC_CAST + #define STATIC_CAST( type ) static_cast< type > +#endif + +// Set up boost +#include "boost/config.hpp" +#ifndef BOOST_MINIMAL + #define BOOST boost + #ifndef BLARGG_COMPILER_HAS_NAMESPACE + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #endif + #ifndef BLARGG_COMPILER_HAS_BOOL + #define BLARGG_COMPILER_HAS_BOOL 1 + #endif +#endif + +// Bool support +#ifndef BLARGG_COMPILER_HAS_BOOL + #define BLARGG_COMPILER_HAS_BOOL 1 +#elif !BLARGG_COMPILER_HAS_BOOL + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// Set up namespace support + +#ifndef BLARGG_COMPILER_HAS_NAMESPACE + #define BLARGG_COMPILER_HAS_NAMESPACE 0 +#endif + +#ifndef BLARGG_USE_NAMESPACE + #define BLARGG_USE_NAMESPACE BLARGG_COMPILER_HAS_NAMESPACE +#endif + +#ifndef BOOST + #if BLARGG_USE_NAMESPACE + #define BOOST boost + #else + #define BOOST + #endif +#endif + +#undef BLARGG_BEGIN_NAMESPACE +#undef BLARGG_END_NAMESPACE +#if BLARGG_USE_NAMESPACE + #define BLARGG_BEGIN_NAMESPACE( name ) namespace name { + #define BLARGG_END_NAMESPACE } +#else + #define BLARGG_BEGIN_NAMESPACE( name ) + #define BLARGG_END_NAMESPACE +#endif + +#if BLARGG_USE_NAMESPACE + #define STD std +#else + #define STD +#endif + +// BOOST::uint8_t, BOOST::int16_t, etc. +#include "boost/cstdint.hpp" + +// BOOST_STATIC_ASSERT( expr ) +#include "boost/static_assert.hpp" + +// Common standard headers +#if BLARGG_COMPILER_HAS_NAMESPACE + #include + #include +#else + #include + #include +#endif + +// blargg_err_t (NULL on success, otherwise error string) +typedef const char* blargg_err_t; +const blargg_err_t blargg_success = 0; + +// BLARGG_NEW is used in place of 'new' to create objects. By default, +// plain new is used. +#ifndef BLARGG_NEW + #define BLARGG_NEW new +#endif + +// BLARGG_BIG_ENDIAN and BLARGG_LITTLE_ENDIAN +// Only needed if modules are used which must know byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) + #if defined (__powerc) || defined (macintosh) + #define BLARGG_BIG_ENDIAN 1 + + #elif defined (_MSC_VER) && defined (_M_IX86) + #define BLARGG_LITTLE_ENDIAN 1 + + #endif +#endif + +// BLARGG_NONPORTABLE (allow use of nonportable optimizations/features) +#ifndef BLARGG_NONPORTABLE + #define BLARGG_NONPORTABLE 0 +#endif +#ifdef BLARGG_MOST_PORTABLE + #error "BLARGG_MOST_PORTABLE has been removed; use BLARGG_NONPORTABLE." +#endif + +// BLARGG_CPU_* +#if !defined (BLARGG_CPU_POWERPC) && !defined (BLARGG_CPU_X86) + #if defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + + #elif defined (_MSC_VER) && defined (_M_IX86) + #define BLARGG_CPU_X86 1 + + #endif +#endif + +#endif + diff --git a/plugins/papu/gb_apu/blargg_source.h b/plugins/papu/gb_apu/blargg_source.h new file mode 100644 index 000000000..4b86e9ecc --- /dev/null +++ b/plugins/papu/gb_apu/blargg_source.h @@ -0,0 +1,66 @@ + +// By default, #included at beginning of library source files + +// Copyright (C) 2005 Shay Green. + +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert(( "unmet requirement", expr )) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +#undef dprintf +#define dprintf (1) ? ((void) 0) : (void) + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr returns non-NULL error string, return it from current function, otherwise continue. +#define BLARGG_RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is NULL, return out of memory error string. +#define BLARGG_CHECK_ALLOC( ptr ) do { if ( !(ptr) ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} + +#endif + diff --git a/plugins/papu/gb_apu/boost/config.hpp b/plugins/papu/gb_apu/boost/config.hpp new file mode 100644 index 000000000..f271715c5 --- /dev/null +++ b/plugins/papu/gb_apu/boost/config.hpp @@ -0,0 +1,13 @@ + +// Boost substitute. For full boost library see http://boost.org + +#ifndef BOOST_CONFIG_HPP +#define BOOST_CONFIG_HPP + +#define BOOST_MINIMAL 1 + +#define BLARGG_BEGIN_NAMESPACE( name ) +#define BLARGG_END_NAMESPACE + +#endif + diff --git a/plugins/papu/gb_apu/boost/cstdint.hpp b/plugins/papu/gb_apu/boost/cstdint.hpp new file mode 100644 index 000000000..e446dfdd9 --- /dev/null +++ b/plugins/papu/gb_apu/boost/cstdint.hpp @@ -0,0 +1,42 @@ + +// Boost substitute. For full boost library see http://boost.org + +#ifndef BOOST_CSTDINT_HPP +#define BOOST_CSTDINT_HPP + +#if BLARGG_USE_NAMESPACE + #include +#else + #include +#endif + +BLARGG_BEGIN_NAMESPACE( boost ) + +#if UCHAR_MAX != 0xFF || SCHAR_MAX != 0x7F +# error "No suitable 8-bit type available" +#endif + +typedef unsigned char uint8_t; +typedef signed char int8_t; + +#if USHRT_MAX != 0xFFFF +# error "No suitable 16-bit type available" +#endif + +typedef short int16_t; +typedef unsigned short uint16_t; + +#if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; +#elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; +#else +# error "No suitable 32-bit type available" +#endif + +BLARGG_END_NAMESPACE + +#endif + diff --git a/plugins/papu/gb_apu/boost/static_assert.hpp b/plugins/papu/gb_apu/boost/static_assert.hpp new file mode 100644 index 000000000..66927ccbb --- /dev/null +++ b/plugins/papu/gb_apu/boost/static_assert.hpp @@ -0,0 +1,22 @@ + +// Boost substitute. For full boost library see http://boost.org + +#ifndef BOOST_STATIC_ASSERT_HPP +#define BOOST_STATIC_ASSERT_HPP + +#if defined (_MSC_VER) && _MSC_VER <= 1200 + // MSVC6 can't handle the ##line concatenation + #define BOOST_STATIC_ASSERT( expr ) struct { int n [1 / ((expr) ? 1 : 0)]; } + +#else + #define BOOST_STATIC_ASSERT3( expr, line ) \ + typedef int boost_static_assert_##line [1 / ((expr) ? 1 : 0)] + + #define BOOST_STATIC_ASSERT2( expr, line ) BOOST_STATIC_ASSERT3( expr, line ) + + #define BOOST_STATIC_ASSERT( expr ) BOOST_STATIC_ASSERT2( expr, __LINE__ ) + +#endif + +#endif + diff --git a/plugins/papu/logo.png b/plugins/papu/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..88f37d4fa735bef3875d74230ae2821e21dd400c GIT binary patch literal 3888 zcmV-056|$4P)Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT2?7BUDXt_OkN^MU0=v3qR5J%d_ul;jh-3f_h&!`Y`>VQj z|G4+}`<$N5KBSJ%?Ewzg1PTU)5RyLwl~H)l2$ig@I@4SPxFRdY-Ow!&N!tJn;N~Hh| zPqv3tDn-?f9h8?>(Hn^{x2%l0Wpl|51Q10Lv)TMbPEO9G|8)ROO-qLxa<@S^WkwiP&E~us^Rzh>FMdEqOywe$|?jwr0mKonLTF?S=m|W z^?FPu)28g~?3;dc0Ks5T`_ua$P*uK-r=R{0UV8I=IuhS5ttcr(QI8ypFeJ-CQJxRC z!-h@+oel!IxwN#jP+n0<<@O!8+*y>(o5zfqv#>kt7z_r?X7e()+x^022hi2kwX~(V zdG+l}m$CZ!r)h5Mpz`2xL{S1D-q(vo>q5mqToEx?oJa-}saTjikBg!_Kh6vb8XBS? zGBl9O;UjgFmsj#p^)7}D&!?2Ny z5ekLq>I(7Lswa7F%?o(FUc6qfPP$a#L!r=BCr_T_*=L^SsV5(2e@!jTy$M9gfX-kf z8INKhOe$?f)igv=L{(K(RYg=qR7J&NF=KOxNRmi*sE0^YAuj7U($-5|djugBqbM(z zNhKq(STayG4U5G>YfB4uyBz@GQUSzbv5IH^^J#8aavj;(Syb$;L6i*r&%Bgk&HzV1QCojYJYsnf z08XAfsqNjfhvW6fxaF3Az3DWSLY# zMo!2y2P0T)j{dcbYJ!PlN7LESiP>zXsksTe-Hsrf68)l0sJpv+ct?9XPp*1`haP%> z!$<2m))__88v#hkNx~hqm@RrVSw&IxD2g6Y)FVj}lB7pfMO0NnQ4}OeLJ$O0Ri(ST z7tNT}e>9b}%>1ci5Ji!W&Q4legA^8*U^1IAnM{|afoL>(Y|ZKym_L6W!}Evnr;oqv z7apLk;Y$QbCY6dK%Q2G47)d#fEXPU836jZpzcx~-)PQ>^qG2#u`q#>_NQ@~dA{ac0 z)0xR@8(!zSB|pVzG?I~#G5?}9(B9tu`RAW~#{MtAVEwBv6OYF+NCJJSB$D2UswgN* znzWq8XcR~*Dyo)7QPU`*9#Ju%ND_*YMv~H}wW3E=Rl?yImaM}5y;8|ISI?UPKu>oM z`}TiH$>>sCE*F`ZnYdi8%8LdN4u>Z`{^+CJ|GO0!3Y5Cmkwfm!@koI;L=mdeUYre*6)H7>h zDFLUIU`q=a?701fSnNYdr9~34D2kE*R8&<#(=;?yMb$JSku>>cIK3?m6ch|29Pc7!v>@c<;~7#+UvG#==mgP75Th}LUNS&3iP=47Kun!HGJwNj zr@sC;PN$R2o8RT%9(Vwo&4%4>pMTN0(DCEPwcCHSl)Ha(CsU_R8BC1ez`;X&^iegk zoMh&#nS9gGLR6DT2}X3HNVqddUr!5~suCDArC$^w73J=Kx&cWNIdbF(TensqD;n2a zyNJBJJcbS(IzVP=Fs8F^?aP!D7Yz_To=EV;=lhs8Z7Py9&=M^c3)81fW%{(KghC;9 zR_~%a*o56~V?xPj!m>tf+=@MGBD%92VLBP(hS?J_81&TD)nT*QD6gpE-&a0}-EPNb zvlU!aHrmnA@y6lW!#Ccyd^xYJUyIFVzVk^|t9^qI^8@Pv8cv zfp{!-O>-m!Juc&niu)$lIt-T47gkurpv;G_-)gPCf?li2EV=c9&~3} zTFs#vIyyR;GG!9)zqgrte|ry$u332505milCz+I~J$!_z)2ES{nMrPLuI{2JWm8j= zcF%9_=K3YqvheB!=RA)@BFJ(Ix7)?#9DRL#bajPz;)y4jG<7-?Cr@J7uwnT9{tE(- z&PP(--MpC$vx%!0EI98Zo6R<8q0&m4&vt)`Ac#zwF%3}=IN2KH)AAi;=43H<;e2#D z-RTe4OgtXvWLrDqCXC1FbbhDs=K$#H>RQ^^*vPBv*74#WSEIWOlz+=>oA~0LZD?q8 zoowTp8?Wc(N1h^bB8UbU%qC`(&8EA%8@t`cs#Q<&cQ@aRUa!aLbbjyp0|3P1@zqZ} zzKUP|;#NGKoD1{d)*wy6HWXFGVa;Gnap48uwOCIvDGe}NPjQBvp}F)nwxUTQ9>4F5 z)1a#>L`qR8EGoj~at*3-C!I}Fwtn~_aw5i(C5yjvQq9o@Y8sjlbUNJD3`Q4@>?at1 zJ{H_?9sYvhq&2u=d?^6G_?J8RboK#qeID|1eYCfC;BYv2@WDs;*)2cEXf%?Unb|!U z4NGU44{M%Z&2!H^bwOdyjMCyFMvWMODC)47OjLiikLm-5Q4|G>q+|K7em=lU_=e>X zPs#&wsBxvmh^O~yYipyi@dWlvCqswj;c~ecY^hN?6LDWzw~hr1<})M^_})o@?18qK zmz&Gvgp8_!C;QxevmNIi`V5otbgI&d!{Okiwd=U!uDdXs&17a~ZW`qx#((z@9yODK0L?ZnvEiP}1o%z2Pw7aD?1kKXpgz(KL-A zxqkM1{slQX+1RWZ6y)ch4qA=a9S%19*G87#_pewi7BVw4mtD*U5CK-KSTWjQG>+b| zemz+}AL&$@csfm8Q#1SaA0QEnlkIk6FzDH`^^??=<%SbaUacYlHDEsP;@$4(FvH63oELyyXmtI_hrfEF(*s6hcZOtGS zjbXD|PnS=Um`nx|3Hg*8HJOPe5&+a4J(VuM`|f)zTD%ym)r#G2Uv{x3*l$8+vzcYL z|1%q3-^kYYKEUnuGNN=eMw5x2&Q1hD%--C zl1L`d&@dZK#F9xshsA6n){;O~RYIX|cJ2C@nX_hLwOMhwT%51^JZJzupHJ7)(xTn` z4}Z_1#n4&lB&rc4-z-|s_FH4YuB!D6vcRkec$R<6Wux8ra)3NA~Brn9s2 zq2Au!<>7FcbUMwEZ@!_Tyqtpv4l-=iD2m3GVl)^?#9~O29*-x7v@DZpwNQ|kM{!Xh zqIlt2!J(S3&@_$bpMQZ7ql);Os~4P?D*oUOO(+z)DiVoQgu`JHi3E{wnChK7sj8^N zU@=oVemr^k`SgW*(dl$#IviMSHj?ouL%d!_77QmlI~xE^(`alwK~qZ$4yTg?`@Uqu zhSz!Ukw?hN%3|!;v6q~64QgUA7}UbyFnxV}DCsm`)qF)&WhJ$T4^uK`4CAgCk6w~U zCX+~#gwN+eNu^NI3PytgP1A6@-DsLhXJ;o>YY2;UR8zFZfD|@$&4y0qPM3TQKv&MNl1DLNe~eP0jo8GV|7Q_wyl!i z{o(gyWo1!PQu2dx-bSm^^g~KA#s!(j!SC zX*os3wsH>aKghi+mXqW05C{Y=xxISXT1;naC6P!F?dxOruHEdat|r{ugUjtECCfx2 zeaxIai$&Ki#_q7=^?JXPD*nY9-H}LyXe@@RD8%D&LZMJUInn8K*laf3Zue#B(*G#G y8l3T9@p!x + * Csaba Hruska + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#include +#include +#include "Basic_Gb_Apu.h" + +#include "papu_instrument.h" +#include "instrument_track.h" +#include "knob.h" +#include "note_play_handle.h" +#include "pixmap_button.h" +#include "tooltip.h" +#include "graph.h" + +#undef SINGLE_SOURCE_COMPILE +#include "embed.cpp" + +extern "C" +{ +plugin::descriptor PLUGIN_EXPORT papu_plugin_descriptor = +{ + STRINGIFY_PLUGIN_NAME( PLUGIN_NAME ), + "PAPU", + QT_TRANSLATE_NOOP( "pluginBrowser", "Emulation of GameBoy APU" ), + + "Attila Herman " + "Csaba Hruska ", + 0x0100, + plugin::Instrument, + new pluginPixmapLoader( "logo" ), + NULL +} ; + +} + + +papuInstrument::papuInstrument( instrumentTrack * _instrument_track ) : + instrument( _instrument_track, &papu_plugin_descriptor ), + + m_ch1SweepTimeModel( 4.0f, 0.0f, 7.0f, 1.0f, this, tr( "Sweep time" ) ), + m_ch1SweepDirModel( false, this, tr( "Sweep direction" ) ), + m_ch1SweepRtShiftModel( 4.0f, 0.0f, 7.0f, 1.0f, this, + tr( "Sweep RtShift amount" ) ), + m_ch1WavePatternDutyModel( 2.0f, 0.0f, 3.0f, 1.0f, this, + tr( "Wave Pattern Duty" ) ), + m_ch1VolumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this, + tr( "Channel 1 volume" ) ), + m_ch1VolSweepDirModel( false, this, + tr( "Volume sweep direction" ) ), + m_ch1SweepStepLengthModel( 0.0f, 0.0f, 7.0f, 1.0f, this, + tr( "Length of each step in sweep" ) ), + + m_ch2WavePatternDutyModel( 2.0f, 0.0f, 3.0f, 1.0f, this, + tr( "Wave Pattern Duty" ) ), + m_ch2VolumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this, + tr( "Channel 2 volume" ) ), + m_ch2VolSweepDirModel( false, this, + tr( "Volume sweep direction" ) ), + m_ch2SweepStepLengthModel( 0.0f, 0.0f, 7.0f, 1.0f, this, + tr( "Length of each step in sweep" ) ), + + //m_ch3OnModel( true, this, tr( "Channel 3 Master on/off" ) ), + m_ch3VolumeModel( 3.0f, 0.0f, 3.0f, 1.0f, this, + tr( "Channel 3 volume" ) ), + + m_ch4VolumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this, + tr( "Channel 4 volume" ) ), + m_ch4VolSweepDirModel( false, this, + tr( "Volume sweep direction" ) ), + m_ch4SweepStepLengthModel( 0.0f, 0.0f, 7.0f, 1.0f, this, + tr( "Length of each step in sweep" ) ), + m_ch4ShiftRegWidthModel( false, this, + tr( "Shift Register width (0: 15 bits; 1: 7 bits)" ) ), + + m_so1VolumeModel( 7.0f, 0.0f, 7.0f, 1.0f, this, tr( "Right Output level") ), + m_so2VolumeModel( 7.0f, 0.0f, 7.0f, 1.0f, this, tr( "Left Output level" ) ), + m_ch1So1Model( true, this, tr( "Channel 1 to SO2 (Left)" ) ), + m_ch2So1Model( true, this, tr( "Channel 2 to SO2 (Left)" ) ), + m_ch3So1Model( true, this, tr( "Channel 3 to SO2 (Left)" ) ), + m_ch4So1Model( true, this, tr( "Channel 4 to SO2 (Left)" ) ), + m_ch1So2Model( true, this, tr( "Channel 1 to SO1 (Right)" ) ), + m_ch2So2Model( true, this, tr( "Channel 2 to SO1 (Right)" ) ), + m_ch3So2Model( true, this, tr( "Channel 3 to SO1 (Right)" ) ), + m_ch4So2Model( true, this, tr( "Channel 4 to SO1 (Right)" ) ), + m_trebleModel( -20.0f, -100.0f, 200.0f, 1.0f, this, tr( "Treble" ) ), + m_bassModel( 461.0f, -1.0f, 600.0f, 1.0f, this, tr( "Bass" ) ), + + m_graphModel( 0, 15, 32, this, FALSE, 1 ) +{ +} + + +papuInstrument::~papuInstrument() +{ +} + + +void papuInstrument::saveSettings( QDomDocument & _doc, + QDomElement & _this ) +{ + m_ch1SweepTimeModel.saveSettings( _doc, _this, "st" ); + m_ch1SweepDirModel.saveSettings( _doc, _this, "sd" ); + m_ch1SweepRtShiftModel.saveSettings( _doc, _this, "srs" ); + m_ch1WavePatternDutyModel.saveSettings( _doc, _this, "ch1wpd" ); + m_ch1VolumeModel.saveSettings( _doc, _this, "ch1vol" ); + m_ch1VolSweepDirModel.saveSettings( _doc, _this, "ch1vsd" ); + m_ch1SweepStepLengthModel.saveSettings( _doc, _this, "ch1ssl" ); + + m_ch2WavePatternDutyModel.saveSettings( _doc, _this, "ch2wpd" ); + m_ch2VolumeModel.saveSettings( _doc, _this, "ch2vol" ); + m_ch2VolSweepDirModel.saveSettings( _doc, _this, "ch2vsd" ); + m_ch2SweepStepLengthModel.saveSettings( _doc, _this, "ch2ssl" ); + + //m_ch3OnModel.saveSettings( _doc, _this, "ch3on" ); + m_ch3VolumeModel.saveSettings( _doc, _this, "ch3vol" ); + + m_ch4VolumeModel.saveSettings( _doc, _this, "ch4vol" ); + m_ch4VolSweepDirModel.saveSettings( _doc, _this, "ch4vsd" ); + m_ch4SweepStepLengthModel.saveSettings( _doc, _this, "ch4ssl" ); + m_ch4ShiftRegWidthModel.saveSettings( _doc, _this, "srw" ); + + m_so1VolumeModel.saveSettings( _doc, _this, "so1vol" ); + m_so2VolumeModel.saveSettings( _doc, _this, "so2vol" ); + m_ch1So1Model.saveSettings( _doc, _this, "ch1so2" ); + m_ch2So1Model.saveSettings( _doc, _this, "ch2so2" ); + m_ch3So1Model.saveSettings( _doc, _this, "ch3so2" ); + m_ch4So1Model.saveSettings( _doc, _this, "ch4so2" ); + m_ch1So2Model.saveSettings( _doc, _this, "ch1so1" ); + m_ch2So2Model.saveSettings( _doc, _this, "ch2so1" ); + m_ch3So2Model.saveSettings( _doc, _this, "ch3so1" ); + m_ch4So2Model.saveSettings( _doc, _this, "ch4so1" ); + m_trebleModel.saveSettings( _doc, _this, "Treble" ); + m_bassModel.saveSettings( _doc, _this, "Bass" ); + + QString sampleString; + base64::encode( (const char *)m_graphModel.samples(), + m_graphModel.length() * sizeof(float), sampleString ); + _this.setAttribute( "sampleShape", sampleString ); +} + +void papuInstrument::loadSettings( const QDomElement & _this ) +{ + m_ch1SweepTimeModel.loadSettings( _this, "st" ); + m_ch1SweepDirModel.loadSettings( _this, "sd" ); + m_ch1SweepRtShiftModel.loadSettings( _this, "srs" ); + m_ch1WavePatternDutyModel.loadSettings( _this, "ch1wpd" ); + m_ch1VolumeModel.loadSettings( _this, "ch1vol" ); + m_ch1VolSweepDirModel.loadSettings( _this, "ch1vsd" ); + m_ch1SweepStepLengthModel.loadSettings( _this, "ch1ssl" ); + + m_ch2WavePatternDutyModel.loadSettings( _this, "ch2wpd" ); + m_ch2VolumeModel.loadSettings( _this, "ch2vol" ); + m_ch2VolSweepDirModel.loadSettings( _this, "ch2vsd" ); + m_ch2SweepStepLengthModel.loadSettings( _this, "ch2ssl" ); + + //m_ch3OnModel.loadSettings( _this, "ch3on" ); + m_ch3VolumeModel.loadSettings( _this, "ch3vol" ); + + m_ch4VolumeModel.loadSettings( _this, "ch4vol" ); + m_ch4VolSweepDirModel.loadSettings( _this, "ch4vsd" ); + m_ch4SweepStepLengthModel.loadSettings( _this, "ch4ssl" ); + m_ch4ShiftRegWidthModel.loadSettings( _this, "srw" ); + + m_so1VolumeModel.loadSettings( _this, "so1vol" ); + m_so2VolumeModel.loadSettings( _this, "so2vol" ); + m_ch1So1Model.loadSettings( _this, "ch1so2" ); + m_ch2So1Model.loadSettings( _this, "ch2so2" ); + m_ch3So1Model.loadSettings( _this, "ch3so2" ); + m_ch4So1Model.loadSettings( _this, "ch4so2" ); + m_ch1So2Model.loadSettings( _this, "ch1so1" ); + m_ch2So2Model.loadSettings( _this, "ch2so1" ); + m_ch3So2Model.loadSettings( _this, "ch3so1" ); + m_ch4So2Model.loadSettings( _this, "ch4so1" ); + m_trebleModel.loadSettings( _this, "Treble" ); + m_bassModel.loadSettings( _this, "Bass" ); + + int size = 0; + char * dst = 0; + base64::decode( _this.attribute( "sampleShape"), &dst, &size ); + m_graphModel.setSamples( (float*) dst ); +} + +QString papuInstrument::nodeName( void ) const +{ + return( papu_plugin_descriptor.name ); +} + + + + +/*f_cnt_t papuInstrument::desiredReleaseFrames( void ) const +{ + const float samplerate = engine::getMixer()->processingSampleRate(); + int maxrel = 0; + for( int i = 0 ; i < 3 ; ++i ) + { + if( maxrel < m_voice[i]->m_releaseModel.value() ) + maxrel = m_voice[i]->m_releaseModel.value(); + } + + return f_cnt_t( float(relTime[maxrel])*samplerate/1000.0 ); +}*/ + +f_cnt_t papuInstrument::desiredReleaseFrames( void ) const +{ + return f_cnt_t( 1000 ); +} + +void papuInstrument::playNote( notePlayHandle * _n, bool, + sampleFrame * _working_buffer ) +{ + const f_cnt_t tfp = _n->totalFramesPlayed(); + const int samplerate = engine::getMixer()->processingSampleRate(); + const fpp_t frames = _n->framesLeftForCurrentPeriod(); + + int data = 0; + int freq = _n->frequency(); + + if ( tfp == 0 ) + { + Basic_Gb_Apu *papu = new Basic_Gb_Apu(); + papu->set_sample_rate( samplerate ); + + // Master sound circuitry power control + papu->write_register( 0xff26, 0x80 ); + + data = m_ch1VolumeModel.value(); + data = data<<1; + data += m_ch1VolSweepDirModel.value(); + data = data<<3; + data += m_ch1SweepStepLengthModel.value(); + papu->write_register( 0xff12, data ); + + data = m_ch2VolumeModel.value(); + data = data<<1; + data += m_ch2VolSweepDirModel.value(); + data = data<<3; + data += m_ch2SweepStepLengthModel.value(); + papu->write_register( 0xff17, data ); + + //channel 4 - noise + data = m_ch4VolumeModel.value(); + data = data<<1; + data += m_ch4VolSweepDirModel.value(); + data = data<<3; + data += m_ch4SweepStepLengthModel.value(); + papu->write_register( 0xff21, data ); + + //channel 4 init + papu->write_register( 0xff23, 128 ); + + _n->m_pluginData = papu; + } + + Basic_Gb_Apu *papu = static_cast( _n->m_pluginData ); + + papu->treble_eq( m_trebleModel.value() ); + papu->bass_freq( m_bassModel.value() ); + + //channel 1 - square + data = m_ch1SweepTimeModel.value(); + data = data<<1; + data += m_ch1SweepDirModel.value(); + data = data << 3; + data += m_ch1SweepRtShiftModel.value(); + papu->write_register( 0xff10, data ); + + data = m_ch1WavePatternDutyModel.value(); + data = data<<6; + papu->write_register( 0xff11, data ); + + + //channel 2 - square + data = m_ch2WavePatternDutyModel.value(); + data = data<<6; + papu->write_register( 0xff16, data ); + + + //channel 3 - wave + //data = m_ch3OnModel.value()?128:0; + data = 128; + papu->write_register( 0xff1a, data ); + + int ch3voldata[4] = { 0, 3, 2, 1 }; + data = ch3voldata[(int)m_ch3VolumeModel.value()]; + data = data<<5; + papu->write_register( 0xff1c, data ); + + + //controls + data = m_so1VolumeModel.value(); + data = data<<4; + data += m_so2VolumeModel.value(); + papu->write_register( 0xff24, data ); + + data = m_ch4So2Model.value()?128:0; + data += m_ch3So2Model.value()?64:0; + data += m_ch2So2Model.value()?32:0; + data += m_ch1So2Model.value()?16:0; + data += m_ch4So1Model.value()?8:0; + data += m_ch3So1Model.value()?4:0; + data += m_ch2So1Model.value()?2:0; + data += m_ch1So1Model.value()?1:0; + papu->write_register( 0xff25, data ); + + const float * wpm = m_graphModel.samples(); + + for( char i=0; i<16; i++ ) + { + data = (int)floor(wpm[i*2]) << 4; + data += (int)floor(wpm[i*2+1]); + papu->write_register( 0xff30 + i, data ); + } + + if( ( freq >= 65 ) && ( freq <=4000 ) ) + { + int initflag = (tfp==0)?128:0; + // Hz = 4194304 / ( ( 2048 - ( 11-bit-freq ) ) << 5 ) + data = 2048 - ( ( 4194304 / freq )>>5 ); + if( tfp==0 ) + { + papu->write_register( 0xff13, data & 0xff ); + papu->write_register( 0xff14, (data>>8) | initflag ); + } + papu->write_register( 0xff18, data & 0xff ); + papu->write_register( 0xff19, (data>>8) | initflag ); + papu->write_register( 0xff1d, data & 0xff ); + papu->write_register( 0xff1e, (data>>8) | initflag ); + } + + if( tfp == 0 ) + { + //PRNG Frequency = (1048576 Hz / (ratio + 1)) / 2 ^ (shiftclockfreq + 1) + char sopt=0; + char ropt=1; + float fopt = 524288.0 / ( ropt * pow( 2, sopt+1 ) ); + float f; + for ( char s=0; s<16; s++ ) + for ( char r=0; r<8; r++ ) { + f = 524288.0 / ( r * pow( 2, s+1 ) ); + if( fabs( freq-fopt ) > fabs( freq-f ) ) { + fopt = f; + ropt = r; + sopt = s; + } + } + data = sopt; + data = data << 1; + data += m_ch4ShiftRegWidthModel.value(); + data = data << 3; + data += ropt; + papu->write_register( 0xff22, data ); + } + + int const buf_size = 2048; + int framesleft = frames; + int datalen = 0; + static blip_sample_t buf [buf_size*2]; + while( framesleft > 0 ) + { + int avail = papu->samples_avail(); + if( avail <= 0 ) + { + papu->end_frame(); + avail = papu->samples_avail(); + } + datalen = framesleft>avail?avail:framesleft; + datalen = datalen>buf_size?buf_size:datalen; + + long count = papu->read_samples( buf, datalen*2)/2; + + for( fpp_t frame = 0; frame < count; ++frame ) + { + for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + { + sample_t s = float(buf[frame*2+ch])/32768.0; + _working_buffer[frames-framesleft+frame][ch] = s; + } + } + framesleft -= count; + } + getInstrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); +} + + + +void papuInstrument::deleteNotePluginData( notePlayHandle * _n ) +{ + delete static_cast( _n->m_pluginData ); +} + + + + +pluginView * papuInstrument::instantiateView( QWidget * _parent ) +{ + return( new papuInstrumentView( this, _parent ) ); +} + + +class papuKnob : public knob +{ +public: + papuKnob( QWidget * _parent ) : + knob( knobStyled, _parent ) + { + setFixedSize( 30, 30 ); + setCenterPointX( 15.0 ); + setCenterPointY( 15.0 ); + setInnerRadius( 8 ); + setOuterRadius( 13 ); + setTotalAngle( 270.0 ); + setLineWidth( 1 ); + setOuterColor( QColor( 0xF1, 0xFF, 0x93 ) ); + } +}; + + + +papuInstrumentView::papuInstrumentView( instrument * _instrument, + QWidget * _parent ) : + instrumentView( _instrument, _parent ) +{ + + setAutoFillBackground( TRUE ); + QPalette pal; + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); + setPalette( pal ); + + m_ch1SweepTimeKnob = new papuKnob( this ); + m_ch1SweepTimeKnob->setHintText( tr( "Sweep Time:" ) + " ", "" ); + m_ch1SweepTimeKnob->move( 5 + 4*32, 106 ); + toolTip::add( m_ch1SweepTimeKnob, tr( "Sweep Time" ) ); + + m_ch1SweepRtShiftKnob = new papuKnob( this ); + m_ch1SweepRtShiftKnob->setHintText( tr( "Sweep RtShift amount:" ) + + " ", "" ); + m_ch1SweepRtShiftKnob->move( 5 + 3*32, 106 ); + toolTip::add( m_ch1SweepRtShiftKnob, tr( "Sweep RtShift amount" ) ); + + m_ch1WavePatternDutyKnob = new papuKnob( this ); + m_ch1WavePatternDutyKnob->setHintText( tr( "Wave pattern duty:" ) + + " ", "" ); + m_ch1WavePatternDutyKnob->move( 5 + 2*32, 106 ); + toolTip::add( m_ch1WavePatternDutyKnob, tr( "Wave Pattern Duty" ) ); + + m_ch1VolumeKnob = new papuKnob( this ); + m_ch1VolumeKnob->setHintText( tr( "Square Channel 1 Volume:" ) + + " ", "" ); + m_ch1VolumeKnob->move( 5, 106 ); + toolTip::add( m_ch1VolumeKnob, tr( "Square Channel 1 Volume:" ) ); + + m_ch1SweepStepLengthKnob = new papuKnob( this ); + m_ch1SweepStepLengthKnob->setHintText( tr( "Length of each step in sweep:" ) + + " ", "" ); + m_ch1SweepStepLengthKnob->move( 5 + 32, 106 ); + toolTip::add( m_ch1SweepStepLengthKnob, tr( "Length of each step in sweep" ) ); + + + + m_ch2WavePatternDutyKnob = new papuKnob( this ); + m_ch2WavePatternDutyKnob->setHintText( tr( "Wave pattern duty:" ) + + " ", "" ); + m_ch2WavePatternDutyKnob->move( 5 + 2*32, 155 ); + toolTip::add( m_ch2WavePatternDutyKnob, tr( "Wave pattern duty" ) ); + + m_ch2VolumeKnob = new papuKnob( this ); + m_ch2VolumeKnob->setHintText( tr( "Square Channel 2 Volume:" ) + + " ", "" ); + m_ch2VolumeKnob->move( 5, 155 ); + toolTip::add( m_ch2VolumeKnob, tr( "Square Channel 2 Volume" ) ); + + m_ch2SweepStepLengthKnob = new papuKnob( this ); + m_ch2SweepStepLengthKnob->setHintText( tr( "Length of each step in sweep:" ) + + " ", "" ); + m_ch2SweepStepLengthKnob->move( 5 + 32, 155 ); + toolTip::add( m_ch2SweepStepLengthKnob, tr( "Length of each step in sweep" ) ); + + + + m_ch3VolumeKnob = new papuKnob( this ); + m_ch3VolumeKnob->setHintText( tr( "Wave Channel Volume:" ) + " ", "" ); + m_ch3VolumeKnob->move( 5, 204 ); + toolTip::add( m_ch3VolumeKnob, tr( "Wave Channel Volume" ) ); + + + + m_ch4VolumeKnob = new papuKnob( this ); + m_ch4VolumeKnob->setHintText( tr( "Noise Channel Volume:" ) + " ", "" ); + m_ch4VolumeKnob->move( 144, 155 ); + toolTip::add( m_ch4VolumeKnob, tr( "Noise Channel Volume" ) ); + + m_ch4SweepStepLengthKnob = new papuKnob( this ); + m_ch4SweepStepLengthKnob->setHintText( tr( "Length of each step in sweep:" ) + + " ", "" ); + m_ch4SweepStepLengthKnob->move( 144 + 32, 155 ); + toolTip::add( m_ch4SweepStepLengthKnob, tr( "Length of each step in sweep" ) ); + + + + m_so1VolumeKnob = new papuKnob( this ); + m_so1VolumeKnob->setHintText( tr( "SO1 Volume (Right):" ) + " ", "" ); + m_so1VolumeKnob->move( 5, 58 ); + toolTip::add( m_so1VolumeKnob, tr( "SO1 Volume (Right)" ) ); + + m_so2VolumeKnob = new papuKnob( this ); + m_so2VolumeKnob->setHintText( tr( "SO2 Volume (Left):" ) + " ", "" ); + m_so2VolumeKnob->move( 5 + 32, 58 ); + toolTip::add( m_so2VolumeKnob, tr( "SO2 Volume (Left)" ) ); + + m_trebleKnob = new papuKnob( this ); + m_trebleKnob->setHintText( tr( "Treble:" ) + " ", "" ); + m_trebleKnob->move( 5 + 2*32, 58 ); + toolTip::add( m_trebleKnob, tr( "Treble" ) ); + + m_bassKnob = new papuKnob( this ); + m_bassKnob->setHintText( tr( "Bass:" ) + " ", "" ); + m_bassKnob->move( 5 + 3*32, 58 ); + toolTip::add( m_bassKnob, tr( "Bass" ) ); + + m_ch1SweepDirButton = new pixmapButton( this, NULL ); + m_ch1SweepDirButton->setCheckable( TRUE ); + m_ch1SweepDirButton->move( 167, 108 ); + m_ch1SweepDirButton->setActiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_down" ) ); + m_ch1SweepDirButton->setInactiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_up" ) ); + toolTip::add( m_ch1SweepDirButton, tr( "Sweep Direction" ) ); + + m_ch1VolSweepDirButton = new pixmapButton( this, NULL ); + m_ch1VolSweepDirButton->setCheckable( TRUE ); + m_ch1VolSweepDirButton->move( 207, 108 ); + m_ch1VolSweepDirButton->setActiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_up" ) ); + m_ch1VolSweepDirButton->setInactiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_down" ) ); + toolTip::add( m_ch1VolSweepDirButton, tr( "Volume Sweep Direction" ) ); + + + + m_ch2VolSweepDirButton = new pixmapButton( this, + tr( "Volume Sweep Direction" ) ); + m_ch2VolSweepDirButton->setCheckable( TRUE ); + m_ch2VolSweepDirButton->move( 102, 156 ); + m_ch2VolSweepDirButton->setActiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_up" ) ); + m_ch2VolSweepDirButton->setInactiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_down" ) ); + toolTip::add( m_ch2VolSweepDirButton, tr( "Volume Sweep Direction" ) ); + + //m_ch3OnButton = new pixmapButton( this, NULL ); + //m_ch3OnButton->move( 176, 53 ); + + m_ch4VolSweepDirButton = new pixmapButton( this, + tr( "Volume Sweep Direction" ) ); + m_ch4VolSweepDirButton->setCheckable( TRUE ); + m_ch4VolSweepDirButton->move( 207, 157 ); + m_ch4VolSweepDirButton->setActiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_up" ) ); + m_ch4VolSweepDirButton->setInactiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_down" ) ); + toolTip::add( m_ch4VolSweepDirButton, tr( "Volume Sweep Direction" ) ); + + m_ch4ShiftRegWidthButton = new pixmapButton( this, NULL ); + m_ch4ShiftRegWidthButton->setCheckable( TRUE ); + m_ch4ShiftRegWidthButton->move( 207, 171 ); + m_ch4ShiftRegWidthButton->setActiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_7" ) ); + m_ch4ShiftRegWidthButton->setInactiveGraphic( + PLUGIN_NAME::getIconPixmap( "btn_15" ) ); + toolTip::add( m_ch4ShiftRegWidthButton, tr( "Shift Register Width" ) ); + + + + + m_ch1So1Button = new pixmapButton( this, NULL ); + m_ch1So1Button->setCheckable( TRUE ); + m_ch1So1Button->move( 208, 51 ); + m_ch1So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch1So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch1So1Button, tr( "Channel1 to SO1 (Right)" ) ); + + m_ch2So1Button = new pixmapButton( this, NULL ); + m_ch2So1Button->setCheckable( TRUE ); + m_ch2So1Button->move( 208, 51 + 12 ); + m_ch2So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch2So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch2So1Button, tr( "Channel2 to SO1 (Right)" ) ); + + m_ch3So1Button = new pixmapButton( this, NULL ); + m_ch3So1Button->setCheckable( TRUE ); + m_ch3So1Button->move( 208, 51 + 2*12 ); + m_ch3So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch3So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch3So1Button, tr( "Channel3 to SO1 (Right)" ) ); + + m_ch4So1Button = new pixmapButton( this, NULL ); + m_ch4So1Button->setCheckable( TRUE ); + m_ch4So1Button->move( 208, 51 + 3*12 ); + m_ch4So1Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch4So1Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch4So1Button, tr( "Channel4 to SO1 (Right)" ) ); + + m_ch1So2Button = new pixmapButton( this, NULL ); + m_ch1So2Button->setCheckable( TRUE ); + m_ch1So2Button->move( 148, 51 ); + m_ch1So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch1So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch1So2Button, tr( "Channel1 to SO2 (Left)" ) ); + + m_ch2So2Button = new pixmapButton( this, NULL ); + m_ch2So2Button->setCheckable( TRUE ); + m_ch2So2Button->move( 148, 51 + 12 ); + m_ch2So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch2So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch2So2Button, tr( "Channel2 to SO2 (Left)" ) ); + + m_ch3So2Button = new pixmapButton( this, NULL ); + m_ch3So2Button->setCheckable( TRUE ); + m_ch3So2Button->move( 148, 51 + 2*12 ); + m_ch3So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch3So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch3So2Button, tr( "Channel3 to SO2 (Left)" ) ); + + m_ch4So2Button = new pixmapButton( this, NULL ); + m_ch4So2Button->setCheckable( TRUE ); + m_ch4So2Button->move( 148, 51 + 3*12 ); + m_ch4So2Button->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "btn_on" ) ); + m_ch4So2Button->setInactiveGraphic( PLUGIN_NAME::getIconPixmap("btn_off") ); + toolTip::add( m_ch4So2Button, tr( "Channel4 to SO2 (Left)" ) ); + + + m_graph = new graph( this ); + m_graph->setGraphStyle( graph::NearestStyle ); + m_graph->setGraphColor( QColor(0x4E, 0x83, 0x2B) ); + m_graph->move( 37, 199 ); + m_graph->resize(208, 47); + toolTip::add( m_graph, tr( "Wave Pattern" ) ); +} + + +papuInstrumentView::~papuInstrumentView() +{ +} + + +void papuInstrumentView::modelChanged( void ) +{ + papuInstrument * p = castModel(); + + m_ch1SweepTimeKnob->setModel( &p->m_ch1SweepTimeModel ); + m_ch1SweepDirButton->setModel( &p->m_ch1SweepDirModel ); + m_ch1SweepRtShiftKnob->setModel( &p->m_ch1SweepRtShiftModel ); + m_ch1WavePatternDutyKnob->setModel( &p->m_ch1WavePatternDutyModel ); + m_ch1VolumeKnob->setModel( &p->m_ch1VolumeModel ); + m_ch1VolSweepDirButton->setModel( &p->m_ch1VolSweepDirModel ); + m_ch1SweepStepLengthKnob->setModel( &p->m_ch1SweepStepLengthModel ); + + m_ch2WavePatternDutyKnob->setModel( &p->m_ch2WavePatternDutyModel ); + m_ch2VolumeKnob->setModel( &p->m_ch2VolumeModel ); + m_ch2VolSweepDirButton->setModel( &p->m_ch2VolSweepDirModel ); + m_ch2SweepStepLengthKnob->setModel( &p->m_ch2SweepStepLengthModel ); + + //m_ch3OnButton->setModel( &p->m_ch3OnModel ); + m_ch3VolumeKnob->setModel( &p->m_ch3VolumeModel ); + + m_ch4VolumeKnob->setModel( &p->m_ch4VolumeModel ); + m_ch4VolSweepDirButton->setModel( &p->m_ch4VolSweepDirModel ); + m_ch4SweepStepLengthKnob->setModel( &p->m_ch4SweepStepLengthModel ); + m_ch4ShiftRegWidthButton->setModel( &p->m_ch4ShiftRegWidthModel ); + + m_so1VolumeKnob->setModel( &p->m_so1VolumeModel ); + m_so2VolumeKnob->setModel( &p->m_so2VolumeModel ); + m_ch1So1Button->setModel( &p->m_ch1So1Model ); + m_ch2So1Button->setModel( &p->m_ch2So1Model ); + m_ch3So1Button->setModel( &p->m_ch3So1Model ); + m_ch4So1Button->setModel( &p->m_ch4So1Model ); + m_ch1So2Button->setModel( &p->m_ch1So2Model ); + m_ch2So2Button->setModel( &p->m_ch2So2Model ); + m_ch3So2Button->setModel( &p->m_ch3So2Model ); + m_ch4So2Button->setModel( &p->m_ch4So2Model ); + m_trebleKnob->setModel( &p->m_trebleModel ); + m_bassKnob->setModel( &p->m_bassModel ); + m_graph->setModel( &p->m_graphModel ); +} + +extern "C" +{ + +// neccessary for getting instance out of shared lib +plugin * PLUGIN_EXPORT lmms_plugin_main( model *, void * _data ) +{ + return( new papuInstrument( + static_cast( _data ) ) ); +} + + +} + +#include "moc_papu_instrument.cxx" diff --git a/plugins/papu/papu_instrument.h b/plugins/papu/papu_instrument.h new file mode 100644 index 000000000..9df25b76f --- /dev/null +++ b/plugins/papu/papu_instrument.h @@ -0,0 +1,163 @@ +/* + * papu_instrument.h - GameBoy papu based instrument + * + * Copyright (c) 2008 + * Csaba Hruska + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef _PAPU_H +#define _PAPU_H + +#include +#include "instrument.h" +#include "instrument_view.h" +#include "knob.h" +#include "graph.h" + +class papuInstrumentView; +class notePlayHandle; +class pixmapButton; + +class papuInstrument : public instrument +{ + Q_OBJECT +public: + + papuInstrument( instrumentTrack * _instrument_track ); + virtual ~papuInstrument(); + + virtual void playNote( notePlayHandle * _n, bool _try_parallelizing, + sampleFrame * _working_buffer ); + virtual void deleteNotePluginData( notePlayHandle * _n ); + + + virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent ); + virtual void loadSettings( const QDomElement & _this ); + + virtual QString nodeName( void ) const; + + virtual f_cnt_t desiredReleaseFrames( void ) const; + + virtual pluginView * instantiateView( QWidget * _parent ); + + +/*public slots: + void updateKnobHint( void ); + void updateKnobToolTip( void );*/ + +private: + knobModel m_ch1SweepTimeModel; + boolModel m_ch1SweepDirModel; + knobModel m_ch1SweepRtShiftModel; + knobModel m_ch1WavePatternDutyModel; + knobModel m_ch1VolumeModel; + boolModel m_ch1VolSweepDirModel; + knobModel m_ch1SweepStepLengthModel; + + knobModel m_ch2WavePatternDutyModel; + knobModel m_ch2VolumeModel; + boolModel m_ch2VolSweepDirModel; + knobModel m_ch2SweepStepLengthModel; + + boolModel m_ch3OnModel; + knobModel m_ch3VolumeModel; + + knobModel m_ch4VolumeModel; + boolModel m_ch4VolSweepDirModel; + knobModel m_ch4SweepStepLengthModel; + knobModel m_ch4ShiftClockFreqModel; + boolModel m_ch4ShiftRegWidthModel; + knobModel m_ch4FreqDivRatioModel; + + knobModel m_so1VolumeModel; + knobModel m_so2VolumeModel; + boolModel m_ch1So1Model; + boolModel m_ch2So1Model; + boolModel m_ch3So1Model; + boolModel m_ch4So1Model; + boolModel m_ch1So2Model; + boolModel m_ch2So2Model; + boolModel m_ch3So2Model; + boolModel m_ch4So2Model; + knobModel m_trebleModel; + knobModel m_bassModel; + + graphModel m_graphModel; + + friend class papuInstrumentView; +} ; + + +class papuInstrumentView : public instrumentView +{ + Q_OBJECT +public: + papuInstrumentView( instrument * _instrument, QWidget * _parent ); + virtual ~papuInstrumentView(); + +private: + virtual void modelChanged( void ); + + knob * m_ch1SweepTimeKnob; + pixmapButton * m_ch1SweepDirButton; + knob * m_ch1SweepRtShiftKnob; + knob * m_ch1WavePatternDutyKnob; + knob * m_ch1VolumeKnob; + pixmapButton * m_ch1VolSweepDirButton; + knob * m_ch1SweepStepLengthKnob; + + knob * m_ch2WavePatternDutyKnob; + knob * m_ch2VolumeKnob; + pixmapButton * m_ch2VolSweepDirButton; + knob * m_ch2SweepStepLengthKnob; + + pixmapButton * m_ch3OnButton; + knob * m_ch3VolumeKnob; + + knob * m_ch4VolumeKnob; + pixmapButton * m_ch4VolSweepDirButton; + knob * m_ch4SweepStepLengthKnob; + knob * m_ch4ShiftClockFreqKnob; + pixmapButton * m_ch4ShiftRegWidthButton; + knob * m_ch4FreqDivRatioKnob; + + knob * m_so1VolumeKnob; + knob * m_so2VolumeKnob; + pixmapButton * m_ch1So1Button; + pixmapButton * m_ch2So1Button; + pixmapButton * m_ch3So1Button; + pixmapButton * m_ch4So1Button; + pixmapButton * m_ch1So2Button; + pixmapButton * m_ch2So2Button; + pixmapButton * m_ch3So2Button; + pixmapButton * m_ch4So2Button; + knob * m_trebleKnob; + knob * m_bassKnob; + + graph * m_graph; + +/*protected slots: + void updateKnobHint( void ); + void updateKnobToolTip( void );*/ +} ; + + +#endif \ No newline at end of file diff --git a/plugins/sid/logo.png b/plugins/sid/logo.png index 549a79ca904fac2eaa44879d2f6c0e43c3a07a79..e7687f9de05a317545cf0280838dcaa9d1ca6f62 100644 GIT binary patch delta 1905 zcmV-%2afpP39b*2Hwgd$0002_L%V;GKp%ez0TnqGXQ*g~000LrNklP#NU7CI0RctYZlT-KvfXX(?i_#InY(lM)k=4PA53$zclOTCopa82&iQ}N zDR6R}94GXk6a59Gt^k0V*n;KPSb=D}5dH-cJi_+q_rw2eeRpCkei*i-m=FU}8 zVhdK>6sT?e7)%f%r(rUWLb(Hj0Aly+oJFcI$_fyUv{{k)*ZoXZ-~s}H!3rAWJ-oJp zIw^qwLfQ3I7a$yIv!e5_?`X?3ysa~>Eo_P7R0Dxpdc3WXzQ|HvLy zk^vRxRQgjt85{hgd-$KNpMm8807<2sXt3_o^BYdT<#sa+RWp%^<|S*B1cZQ`viH6Sq;EH7y~@BXLyyIxtSvhs8X14!pvU?#vJ zA^~Owvl7Ax_)77xQ;EdEo(&HH{5gO`35t*fFaef_0qi)m>-`-qOCF8`%7SVyHo~%* zLzv7w;|Q*t_{&06%{#jZZ~eHj%K2 z1mzNmdsJs<17$=`;PwP*efZ(sza{oCHHqMQs{eneR_2Np2VI{?*y<5fOMl!Y{AM^BuJEueC$}Ezw6a|0sK-4KEp*O ztQF1qYZ-QG@X+AyP0N1)bZf}!1B>8{)|c5ZnoeH&o&gN*`D7zlelbPZl#Lt3_V*@5 zr89M9?NoB$&v$MF%TCkZ1OS8^7KWMF8?N)Y4H)C2`#%6MR1v)w?4N@W8IUIcoXuq7 zzrTJ1Ab(YX!JEiZiEba5i*g$sLbI8%B+H3yl`2T`_jeN`D<6N*Tndc-u9w@v*`JER zRzs*ZKP9;QM{%3vz^^)7RwVW0@9$`wjBM$|(cbMZ-oVoS7JywU6H`rW@w{R$GEF=h z|K+Pvn@n}s>BR6r&$}DI9GYBC%Gk-i_W=I7+|I@kM4Ce?N@9Oc%7wD_NXo1MxqxL{?6LkM0>V>ykSY&dr)EN^{)`)^=zvqeQ;rXj zk<>i*nv)9=Ufk+gQ~Wsmyb1s%zFb`9+;_Tvn{Lrn>lmd(q}gXNFCVr z+%=MAS*`*G{GsTI=JOuxR88HZ_5pBS%Y|9d3NC-So&dD^e5`RyqPowCbY)GD*=(k-^Z6)%Bh_qdrN>A1t{)$Z z|K+v?jQrpjfZR{0D$bb-2<3&2yhPsh$F8*)Kh(4FX3mbM-G;5v;s85yPt%P(?XRNi zkEH+%mOJ`?fA!K#dNA%Ox=ZGMFL#=JeRY2Y007A{(>c)n_SHlCKL3ze7765~xl_T3 z>DUXPJ-sKD=w8#iecf6Y8dT^T1>DI^OsHCG{r~Ev7GzyWByMS0ym~d~HNQ;uXii+N z&w!Mbk3w`57K?zai^18eVr_Ta6`0Z5M69DwF7fH+FdI!xW|`^hSQk;H;&Bnc6r^1i zz&sLxMJy-I0>mldXxo{=y3^c#r=8QusDcTEL3&00000NkvXXu0mjfy$Ya# delta 1181 zcmV;O1Y-NH58nxpHwpg${{a7>y{D6rKp%ex91|4?y;yVF000DENklq;mR-gr1d(6iD_o70qgJt>fbh@=T|H97V*tn z{W$o>IZV$iFfR+4I)B2eM=ruk`k0qSzvwdKl_M8Gl78kDvb(**sDH<_qRodLf$7-= z9C)n*u0`*X#3bSrbz#A&G#Z9A1&`06J}ngRp4}E z4c4Rzg45`=?|NwkUZ{QsYLzS@<{y7_ElxG=L%Kv5TnDD6=V9r$QwpSsl2Ki;Eg|Mr z*;1Tqt_#|V{|C@FI0m%! zq6Vln6rt8owAz&j8i5CSHC&LSA9GF@GG%E|y>RCEam=)~Mz+rp2q15624AP62Uwdy zZ|u*3QOfxkN%$QOkamB2#Lp8(BeF~;@H86gDWuAl(u#fGGZ6PF;u{|av1boFRx35Y z6OYNk=TV<9uYCO*<`>*?had?8gwY5uL4*fLmk6QLW>N1Xor`Ec-<^=fO4@873GoV&p{Dk4F=QK02!!|EJrI?Z@J&sH1}H1dM-rDqDdK;ecjLh89hjL*OpC>1 zv4GgS7rxoqV1P%m(xETW(TZ652~@t=hRJDXLLl5`Ge&D_;CDLXf215~I|J6$FJl6d zQd}ImxqIm7C7k~J_Ocx6^L0wR{Cw&19qJ#nRdznt7;$G}DQ%Tlcc;U)*YEZYV|QK4 zl6ymR5o>_3o4J1{bCvk$&2n(LD|^33>F!y5K0Y|Sn>MwsSO5ShFWZdvhH6N}Da-@_ zfTs(!=sbB41siji2><{&$_$u3-H(soDu+@bVIBYga5!vKlx@a0rh2@8co*_EWG~zG vY1C;n(qQB@Yq&_nMK^2p&mOA)(Z#=Zs?H-D