From 4f15396e81f5ea8cf20b781c70fd39904e0536a2 Mon Sep 17 00:00:00 2001 From: George Reales Date: Wed, 29 Apr 2020 20:04:37 +0200 Subject: [PATCH] Custom jucer project and app icon --- Media/icon.png | Bin 0 -> 97552 bytes JuceOPLVSTi.jucer => OPL.jucer | 292 +++-- cmd/release_win.cmd | 23 - dro2midi/README | 182 --- dro2midi/build.bat | 1 - dro2midi/dro2midi.cpp | 1328 -------------------- dro2midi/gen_test_midi.cpp | 74 -- dro2midi/make | 21 - dro2midi/midiio.cpp | 2161 -------------------------------- dro2midi/midiio.hpp | 402 ------ img/Two-OP AM.png | Bin img/Two-OP FM.png | Bin img/abs_sine.png | Bin img/adlib.png | Bin img/algo_switch_off.png | Bin img/algo_switch_on.png | Bin img/algo_switch_on2.png | Bin img/algo_switch_on3.png | Bin img/alternating_sine.png | Bin img/bassdrum.png | Bin img/camel_sine.png | Bin img/channeloff.png | Bin img/channelon.png | Bin img/cymbal.png | Bin img/disabled.png | Bin img/full_sine.png | Bin img/half_sine.png | Bin img/hihat.png | Bin img/line_border_horiz.png | Bin img/line_border_vert.png | Bin img/logarithmic_saw.png | Bin img/quarter_sine.png | Bin img/snare.png | Bin img/square.png | Bin img/toggle_off_sq.png | Bin img/toggle_on_sq.png | Bin img/tom.png | Bin img/yamaha.png | Bin py/atten_levels.py | 13 - py/oplparse.py | 340 ----- py/sbi_filter.py | 45 - 41 files changed, 145 insertions(+), 4737 deletions(-) create mode 100644 Media/icon.png rename JuceOPLVSTi.jucer => OPL.jucer (50%) delete mode 100644 cmd/release_win.cmd delete mode 100644 dro2midi/README delete mode 100644 dro2midi/build.bat delete mode 100644 dro2midi/dro2midi.cpp delete mode 100644 dro2midi/gen_test_midi.cpp delete mode 100644 dro2midi/make delete mode 100644 dro2midi/midiio.cpp delete mode 100644 dro2midi/midiio.hpp mode change 100644 => 100755 img/Two-OP AM.png mode change 100644 => 100755 img/Two-OP FM.png mode change 100644 => 100755 img/abs_sine.png mode change 100644 => 100755 img/adlib.png mode change 100644 => 100755 img/algo_switch_off.png mode change 100644 => 100755 img/algo_switch_on.png mode change 100644 => 100755 img/algo_switch_on2.png mode change 100644 => 100755 img/algo_switch_on3.png mode change 100644 => 100755 img/alternating_sine.png mode change 100644 => 100755 img/bassdrum.png mode change 100644 => 100755 img/camel_sine.png mode change 100644 => 100755 img/channeloff.png mode change 100644 => 100755 img/channelon.png mode change 100644 => 100755 img/cymbal.png mode change 100644 => 100755 img/disabled.png mode change 100644 => 100755 img/full_sine.png mode change 100644 => 100755 img/half_sine.png mode change 100644 => 100755 img/hihat.png mode change 100644 => 100755 img/line_border_horiz.png mode change 100644 => 100755 img/line_border_vert.png mode change 100644 => 100755 img/logarithmic_saw.png mode change 100644 => 100755 img/quarter_sine.png mode change 100644 => 100755 img/snare.png mode change 100644 => 100755 img/square.png mode change 100644 => 100755 img/toggle_off_sq.png mode change 100644 => 100755 img/toggle_on_sq.png mode change 100644 => 100755 img/tom.png mode change 100644 => 100755 img/yamaha.png delete mode 100644 py/atten_levels.py delete mode 100644 py/oplparse.py delete mode 100644 py/sbi_filter.py diff --git a/Media/icon.png b/Media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a1c3d4373c521ed139b860090e1a965163163b91 GIT binary patch literal 97552 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 z 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�aeLt)wTYtjV8^d(N=}RJERe^ z*nb0Qbl+C#4Sdd>uk~fdupK7RRo!LlTX&SAf=qh8eR_T;GPf?L7Yz+^*6Ox~V8Okmxa#KjVx;`@LOA0<* zALZia67DW0!tv3FxrWyo<9m1oA`P001GXYga_Z`&Aq)~BPPMZ{=wMCfzp$fQEG11c zSdNaS07LfPHF~=EAR*vJr40wE(~;>s)0eM zLd0zj&i6J6nDA@-mu+Wf`B6DZl=|@S@ZJ0Ow%gIG%DKA;coMA9%1O@CZLgCZf`jy4 zPBuGl^=-8q^zD)PQOEpu!UJdyAG+rdQ}#{t9VG}?NT35L z2E`j--!NE}FX(yYMknDxH$6Q)BfX8oR2p9?SpzX{(r<@=ljaP!rm7~Mm1mj9klkD& z0YVpY93MW@-*kXY=y3f7{nUJ{AU!Aujrm{Ymc9n30A=Ur%c`qKROeuXYZHXsAlg9b zWrq=PfvS#9JP!m3&>;xl(SX;RLGQOFY6GhbXg~!E&tzxkq^?d0{wFt@q210o&JMCktHoSr++~dy6FNqw*~N0Gt!ejM5Y=;k%X40@X~ynd^9NC1qK`%W|QG_RRymEF1o_(E`r@rd^78 zycu9MzCEJ=$oRs`^-_NAP^~ zF4xV&AO0!hA;-hu2b0m@E+S?=1~O#Pm4@INP|E%XIDDp){6=iq%Faq!P*9MGB*3Wy zVLI_+!V!V#Mgpc^IZOma3#%U()0fx)oE zF#x%gy@`AuY>J!rxoc}Vp~%Wj;&A|&sQ_n z-J67Ak`qGPA=5RE#xu3ffKK6D>kRs>r+w4S8h@&KHPqF;z>Ci>gi8E&7^6gvHV^a* z4|OHT7s#pyOia9*a#{#p%c1%F|6FFb5G-%Ilcu2ce(XzU+Ni2_t!nby=gdSbwvyX&ns^rggOscwXJCkHK1|5l2LHq03CXN zX{r}S(%aixxtbaOTt_E2CzFYd1p9w;^8J(Lay3g!9;@or+dw{$MMu6!c+%Z&sRJ_1 zcYN~k%GU2e$#w$wWu+y{c;5Ri0UCQp2$x7pz)d*hy<0 zlABHTym}DLtqtU*Qx!PoMMBo@-E>eX_#Io(Mqlj%6z`l?g!fJlXvy_9z!- zLj~P*qeL~r(avt}UnihU&M(J6-@k{#j&iPhVW~IX4-g)p7i5?3-o5FC#k7x;*#!jz zqXwSt1to~}ZatNvlLye)x2p+CYC&OP7Am~I(K=SD8iW0ITYsglFVeXCup*d(`z73v z&mU;Lzj;*4Klw~z#}YZn7>0xpOGzQ%7dt{3Mm?yI1VZ!bVuxuLgWmMB)tcy+R zwXwW;ah%nuOHWV(_U*puvw|%I>O_hMiH2SymL=Mb76@67qnw}?Af@CTz_@)tb4@p_#bYUf6d$J*=)>It>^O7C|uvwJe?>2L8Q3vYtp# zt&%inM%AP>uHV9V`0tR$_v5&|``D~Cu;wTbD{`{qnGK}LzB4y(S^bk$=O<1kRa=48w3(1=06Zzf z46h*Ya*S=wmi$~4BauhHmJ0YhWO#V|yoBhlG`S#o#5QoXS`hb#ZJ?TfS%0}{Z9G+J zE8@77kXDQlv+)dt;2;+78lhy9@Kp{*ppJDMZeB@PO^G1+2*lMkVoU4|^fD_YJuNpf z(=?My&Y(SVwHRn_Hd|DTdZE4EbYVBVnW=3==ArM!bdAB_g{l4ZA{77SFm^nR_=JND zdKgg02(;=QkpQ8z)DTRXHJong4OUd>#)-RCSGZFEBcmdIiniRBe0*!-uJ_pYw9MDJ z{=h{q9*ovM{ZUm#jSS7bu}R6I1O(XU8|%}pg{iKuPYW;RovA)$9(kqIe%%O^ee0S%Cc7HtAL%B92s+}^Sa+)!G*=$PDjkXCEB?wgV z!Apkh6o4XZp~gox_^2yeTkSx0$@YA+qJ{4u(T&BH2{z89t}|4EJxcM>(snd@$3kJ$ zBtz(>yrqN7N6~n@|CVkW^QV74>1&)H0Q5CD?b7-QK$&i>Q_(CFq~im&Ye&7Xiz>#E zY`2EosAERow*rlwon_CfWh8(4>eZ`AikJvAm*WFwGMpa;tTjz0s)uyz@r$MKbFw{A zxitk}O;GX0b!ZOtON4dV*SIC$?=E68XtB99St3ygVnk5r)^g=R3Yr-%^qSs9bEnJ6eGVtWiv?McoWRxD^{$xXY)q8lg7tHOALr{Q|m-Vl(i9TfUd@KX6fa(>o z>Zc8T+Go16oe01t`~>Q+7E_hPI>;h5?~#*3>G|k348S|jc$`~Kee4sf z+$^8mIa@E};<4fjkU=RcE4#Mhn&8Q^WJES^@bfWhLeE|5o1z?lvk#~C@mXmNj)rmV zaQrnky&J<_N%IV=Yfh14nqAbO+Kxjim8jfmdQI#N<8XQ29dcywkrX7uj39(-$GQWV z@#ascm^K~sj)wb7y|Hw#Y zX=M{IrGQ%o?zYTnH zcL^Z|j}s;r7nh$v#m!!Gv8kB*f9NCl+m>ZKGGi_#CUydpYNu-MTmI4_i`4i)P7PZ6 zDhvm9#u}keyZ`&q%i=cyBMTbC%b(H zP^Q|E77EFj{7U}gHF&TAqY^JX_P*(BkZv*K>{4e=-L{Cmv1(N-Gtsf)?hj3VCj?Z? zdQ1ZNB$(>fmU93Zx`VU})=wa{35qZ_U14M8`G?=Auk8^tFGt2vcRVjx1t4+*tZt9P z$*{#EE?HTmP)QM7`a$mck^eGXX)`l3F)F4VXn{2H zR=y^RlQLG3it78fssjMd!m;UqBK2o^*3Ys*6o3)2*2TXGnlEIIg?d0T4J6%bBM@&j zHvV~1lQSSEA;wOXs{sgq#V-%kH~nDH8T-4N*1;tv?mAA{Vopp<^v}#}iP@=P?S2W2 z>-zB**(*+tpr_H1Mi*4mGi3}p{Bb|UyJ;X}*+KH!(I3M}@QH1&;OM`H=MpKsF9UMe zASa&7GV2$0$FrUcMKAF{pQ~%Ad89{9IDWql71GvBw4;wAzRlWp(EGQ&1`ZsQK9gO( zimuT}$iYIo3TVuGs5%6KzvT8s-UQi2cgWPVbZ`CLt^8j&^WpB|<=|EcS!lGS`)}b9AMQ#&dJ8hSm0Q*8KY>V*g&27`4%5L_ z{c{;jQYamNWny>6b(@JI$fzxloO6u>%N2J0qT$aj`sK7irt5VP75IYK@z&Gt4Q2ou zYf^v_)koQ((9MN`iC24hG+7D$^Sd0pvu|7cv=4IC;9vMNJL~U5qoWH)lhz%;u@D8z z)%ndntsu^o(*$vbN>dpP1l3-htpmV)GBH}8nVo*&Y?5~a-_@E4Eh8Rn*OE3F5*0RF zRl8Qm__z`c(LDUNB?hVO0DC)rI@DNkEfB~ zY`ny@&M!1`(pNKM4Fz7>F;4@~V!V)LGw91AAc-i;w%A8lVnKp@Y1PF&>G(IRgBeu! zP4I-QrdDPWADkm9!Eaqt!9D4D{GKa%~86>-D#YeuJNq` z%6Ym~BtVk#;O?a<mGqJSTA5eUJvJJg5mPt z3ZO;4IctR8bcXg;2wh`E{?UwGp+!OnDE|U zCT2p3(#OaLf)Y|-NyH}SVU~?%YQspBE(;Y_AF}~l3>c4>l{QQ?G&E$I(GsA>6uElS zKI}+Oeh29!kbG|i!BPp2`iWvS{>!qQhmgEZ(2xv~ySwy^%R0}?XdiN}7umU-DjxE4 z*8$-##6|zn$=PT_2&kBtz1(u#nvN~k*G#^|qyiJ8h@z3=xIQWAmIY9D0SjMRPcIQP zblHzJ!g2We$q?vc1kM~QaO}viqzRK@4xkD- zJ}M0@-K2}KUpF^EoTTOx<6+nyPdhFEDJdB(t(fB-Y^GLH&b2{&RCxE*K#lQ2doY!- z6&*4ZPD}cH!W4d!4M*%s>lk-_Wkc3M7a6tqTKHj>9_EmPi;@nukejgpi&^wxQIX0w zH7)Zqpws{JnX2f!(zGpnczLhNaDZn5G0pz1=wE{)xi9dbK_qE*k>QB&#i!-7oDk6U zygbCo;r#MeJ4}thUHaP0rZLHLl<;I^B?5vXwWx@Ha@_NJKAMt6X0TyEmzGaGH_c_i zY@-E{=F2j(sI;#*3x2x~t|wKxL_;YLa)JP=!RUBvdYr(zr@Z79KV(BJwE^29QR|ig ziklrxpeU8!dPWpkfTL&Y{QAG?MZ?jMaG4AoUqQo_i^bSaCY?tsY)q~C#kny|hKT|&P!=y?kd5(SI{GzMv5 zg~_w-w9{J8jp$g*N=tiH|J`fVzhN*1bVC+S{%#;Zb`dG2SbMq@4~XwAX_7`4rq&=Z zODIs`8zaqvcn~CHJ@Z9|`P2D!gMq4oPXPM@pBX-tU;)p@xKshpw+SK{Gu(9HKPA)! z!Pk?IGsNKc)vPDt65MaB*7;+3i|Bc>>~nkALlP-b`T&_LUg%@E)!Na)f#IJ_^e(j#Gfb-rPZoO(UkV=pS%iwrN zoT>Fbn27YK@Tn_|#=>y3yHwE6_kV7?ZJHajpc0D0L15td;+3wr3&Bx}hll%VQh&T4 z-d8cIw<}W5LzYu;o+~$L@=GT_-YR6RQ~(Eh#iw1GQ97GrQs1~($!hKqa85=2seHm9=vW;Zk_ zCe9|zzUw1zfLHJ^h(?j=hNbMsG=`EbV!reVt2+hsq zR*50dZ6*&Yc&>**OiWIC6I6StVQP5+Dgno&>sCEJ%L&cye_5RTvDs}-vYWe|X zIl!lYu@eUPbhZvrkit4^2=QStwN3!)!T_2|7^e(s`{{A%;Zwve;J5f<7bk7YAXQDR_ppNalHfVOLpu(B2ysYJ4~WTO^UCP)Ta z22!o%uZ@Bx`vi?o>7n&yAIM_~&$`{$cU~k1p~5k6boNZG`+Ek;bSemtel<_KWQaV57yDp?;ivfRYipqi4&4*0Upbov;pyrA zvhj4a{ZH<0_g~OX9MRuZI!882@ItOMNe0*9;=$^l`c?F>$}sq_CH{YAGRNsfg-@$b zYrpVNO|fU1}=Pag0Anj-i=#-pSzqQ;eyo%k`y18f45nCp6hJ4dO|;+VeRGx zR$uscLljHY&~c+6nIXu-Ag#u3zck@|hs2dFqU@>Qn-0qZw;?YZ5#1|x28&aMzM}d? zXPinErat5JB-Zl<{AK44bhJC7r{WGthg81d`(yG~o>MVFdo;eFtms^?@zSrl58lE1 zPY{XfG*emw&^E^4?WGQQEy$J*jE}GCed4))grF9TIweJgqhK+4kOs)y!mwc&>_wX_ z-yKxqomaW~r($z?IBEKu1IlqRUSC+}6L+)|$ z@#iiuWMbf4A8h{%YD3c3V1{2hdnRI@9-7$5PTXNk=Ww+V9Nb~+F8W%FVU3yV_BmIr z-j^B8Ru+F&?(FinL!y6I=5d6+0r$Bn6~-->6(%J23;6JHtK#x0!Bb!zQLvekLS>j| zAsbJV(%WY2v;Tb0uhs}wMpUoF9qtStxjZ44)b(epCc95Tjug&Ech^IZa z(e!4#Yr3MIeoM{k=qc@%G2yacWg+4BBs1KCH((zswYr|1K9WF3CV3@JJ5`Dk-?%Ga z?bd>k{sL><$wS{^m~+8Bne!q9(Nf58u9h+ItMx1BM4=rZ=(Oi6bsP9y6gf21)rdaB=pIj_uLbKm9utle`H~L$b#i z$BRP!&3HL`(OX*oQDRY_48+e(e_vIUl_8TN4h&tTbxuc~-!29ehr^KAlDBaX)ik@Bo={7z5ECF(cgPOa`^Wz%A-3?D-K8Om& z)T;Dr5Z7>XqU@W%dlC>--L-EczeBO#@igTIN>fR=k2HWXJ^uHDtfJ=|B~`z#8q%{% z0~xI*60L!#It25U6J*>G0t#r1xVt}FPX5Q(ni1i0n8fswC(bEHgt2c zVgZPLZ%K=u8?fSmYMw;NOEI+o4FjwlKp`~=D2ra~v~-zqMJ9iJe2v;@;*!M|B)}i} zlt~*-d#!7M+w<%zL9lc{DIy+dl9i4Lj~odK>yBtZVCD-*ZZ3|T=W2?xJ$Q=OP4Eip zSg4OZu?A$s%W@0qkwSw|U^;6E6(CheLPK5qtr7GbP0C*h@|c@OOqdtWBTe+b@F#{; za?!49?6FTj+_}z@g!ci8?ajR()n95s73a3caSh;o>41L^+q^A7By|v3Q=r%irO0~| zg`$ov%)|gt2J&i1%h>W69hXe=g@}Yb=?xD3ba4>tG2iS{c{E|Z3%=x{#T5pLQ%GIN zsJNtW6#alMG*cMX>|;QA`Ml4m8xq_(Vb$ z;svFzIjEe(x7s#zPwc6kyGw(UT(=%LBg zO?sYdyqLcbaK7#URl)r~rpJI{`rB=Abq|!6`GK%)dmi^!aTiT69V(oJNUGK56tldL zvJptb+5ivcc3d-i*jo&TH&kEuAh9Nkgu6)}_xb4Xun&llst7v|j!>DrJ#XLJA;y5% zn7vOp;Dl4>UprZooR_FU5rz?nSOPh@5-ca_a3qmkn9k8%d<{SM$^o_p6%i zKl|l=r(|O`bXemBJxUI4_M7Op^w>U9A7MSV+Y%lZBEX!%Cd+{c_+!~&aydn84z|xt zDQjxiu>1}WMbPQm1_A&;KU(Wr;!ugJO2>@`*c>`jtEalgD@{W}_@GYw)x;=nN?jd2 z2pik#j%R|vUR5s6TpK_kYLx_DlLwmjI&8z9-qwr>uK*t|0M4Whbo0Q#z&sdWuKeB) z!ko;Yo0haGe)a|N)?-bUyO(h@LV!Z~AHjKXJnjZ2=TB~2-rAkd$3!ugT%;J#;;tto zvN>Z5ir8x}_PUXbF&`+Sk$p@4OmQ`r|3B{F&aF^s#RmuYf&&1>=vdd0BjNL1z3P8r zQ*7QRg`8_SLU>S@BoQh(_0@a(vjn2Q*wExPnE9h35zOnps`Rqj?nhdeZ->hI zmz%yIX>g}NGSW!a(lp7ry=EgB?WZ2ru99qqCOv4AprJw#vbF`(s+=5EGz0^zn4SIp zWqKrFgSIh;i=d$jZrAiWp_V9%uc<}J#*=UEUc{2qx^Eo@HqSe%0`$!$*okUeh8 zk7iXh#&-bFv;O?#dbP5>){ZVT0f)zJgAcKMq)tHSzKDPKUiTF~6{%!K3zTw!!Hw$# zHkH)wG~a4hvD7iQiF&~{e6Pqs$L`Q;3hY!_faXwh6ahXEJzM#X{2myXMe#^yV%;MZ z7zTus9#c8yt5HD-!52hM7I{4bxC(1YW>4CMhscHTJmP&Kku&s{%Gb{H?HBWBAkX*- z`30m`rR%U|xCdD*6nmQSLC=?ykvk+IdV2bo4IW}hpX9cxWT2)ovI9!ww|RxGM>aTY zL8N~D%oC8Ma|Jb9&1Z+}yW^(G&V)+o6H@6ADkO`Hj!&r78X)(_jo?9_!HyKsqcLN^ z&~>fR5;X8UxlT)`KWcDqC&2J>+9>MiA0H2}UmvL=d^;f~p;X}`{|TUpoY=S8v{I@4 zW)SK9bGnYlpe}l=YWWr*bEN$UIP!k-#quk7;NHDneRd#havMEF)Sqv)d}UMG0pZsI zK-e=app-gSV@Q@`1C}yFIA{Q%9mw0wIX0ZrZ#5u1+~#o&o`dFrz+tvd;ktRN?V(Cn z$W4HNvXwxRN1!m+q^6Uwcr$&^2uk@z^j9-Ix`A&F2Zpqqy47~rp<+i=PyZOq-fpm0 zzc$ZF7|=fds}@a!Q%b0F!N#8j#hWE6gJZcB!WJg0|90o zqzhS*0lNN~!X@KD`f5!#3z1F3<&Z^!~@zCAt?(j$eQxIr-vBDW%9^K7omZ};QCik5|mO?L)#~Q&TUnHYQp{Ur=+TOOGoMr zC33pEV7d%a#eabrXrCxEADEur6n_KRgqbab-KK)x$za)HX#1jAOjncp5zsdu*B^@w z4G;g|u=)lDe<~35@xkoV(Q7lMu?T3!ZI`~0;o*7L+4EaS>kU$HUdaLI!V8<43LOai*+uM8iv_Foza7wEdZ zb!N_zN-{<}q!RDyyU;Kn=9IP(WOVCXw%s4sdS?@*lHw!kl`DmsFZDUsLnYbNX0a~$ zTo6yVvdbaf%`3M_ikq2vP%c_~zf2euTTOxNg%em-wZVv|v`blLoZp*%YNq@E6oVsl z_3FAM7M#;c8(QszSr6iI0S>}4|S*eV7R_^7lagm!==31=)1xLaKtQ-_7g6HKD-n+c&WQRdS>`X`tCCOJ9$*bc*0_@%=q0m|4k_zb2@jegV z+=6Nx;CTIkKLymuG=n|i4a<;*Gjyq1!K>o$4uBb8Em1v=N|K%Z?#e@!QvOfKqd+Vy zVZi`AT_W^i;X?(DZlw*Z+;Ra}yQU)J4x7Dy3K@bFT?!&$(K1eM;DCy2FZuS5Yb9mC z{VUpI&;4HE1)bS5iX_h~XE#8FZAr%pbus8f+do&fkK{dqc0Sn&$Cb%nzCM)op%t=d zOqKS#@;urS70ubZLLfexeE3R-iiP#E=*}xj8Lq(Wl(b?j8Tm9x+lqJOWq0j=lvC`U zc}UD&t*OOS4~5pG44cW4p^tM{G6q>ZZca_IBo&y~nJu)h)=ye;;Hp*637k*Ubrmm$T2hzu^a^UEgSSxhe zBTi3Gfur+VPI4_+ddVVwh;T69RI?uJtREDDbF@<3pRk{1hfk7*0)!O*Eb*c3>0(TY z)9!0zw5-~ENs2E!e$i>Nx3_n+g?{z0zTmWu(5*fv=X2lk`H#luqY~!Mm%lwnxSyRY zWVyL|xLZoJ#Qa?Ry|p)V6jxVim0Z7+)sQvud-x1d;g?*|kz7_-s3f|WyYO#@EbJ)b zSNdlp^cafRG_Yc)QLNK0d_rf77GvP?0F=Q|8@TPtTE>W4eQ{>7?K5Nre+5&0@946*8Cd(|)^QUI zzp9*={5*I?R^XtOFrhwgW*w+;{;E>(!gX?TbYt@rwN76~h9*JV5~~2qh2A*%t*Plg zctF(Dl&;(ts!#k9>v}|}nKZ^Ou4F1l5mw}CVe8eKpJXK?S{FL#KH>rHp8@y~0F7Mf`JO?3iivi+nbRwF18}hLHqP_T7 z-~mMQK7-M?$FX3223J&}xnn7ioQvv_12^&<-nO<6+|bRvK2#pvVeXR+<$2Dc70rEnSDng-{o1Ml zMdo+w_N#5mYCfy7aOt)+^Oy6Cm4`}z>Z-*^^_9^=29O=G(p$gU9-fmB{IL|*tUF4N zkH>7pA<0N&c(OB3_u&`Q#j1+u>+_?bb%C}0(|3dEok_f8$l3osy{oIs%%MjG8$gqV zc|V5C($dHVP`w5A2A=#l`DR?oza(srKzrY#W<4vSU`M^fhV7Fe&)QtlCmyt3jYlh# zx8?gj_$4>R_uAU2^_wcG^NiYBQ8rA3cycHxZ2kns5E!Sb)+Py!;0F|Hf*9ds8O|dd zJ+El(>6V`u@?pz@!pC2LqPTPCjv>hAfyC+{=r@il-ya^=DafyioLpQ*BVb-M9BfU; z!%#7H_PTjX>{jnSI&Xtde&J`kB@^M2h9VhQGIH0JE=$nH<&cS?5dcJ$$#Tn8&_kfK zAT2Mi2YFYL03s~3opV7|P(%c#oP;?~R8$l&C%gclECRgz8YE7ESGc{a;l|k_5zHUz zM5MeHF8AVnBh^RT9S-ag2qenEhSBuAye~RdRcvpQ(`k+U<{$I$vyC{bm@gN z2MZTh5cu2&pgBIXq3pPBO1DP#_HR03f$WF!_d!-}L1glilKeX?Dgr<4GBa{uOy0bG ztE%=juEajCOe7@Y>m?rY4OuTUTnKZO?t%@CdNq5kq$)oFm~2(wEo##nna~?SlV4MX zF)F__k9ipK2X95L`6(R4?BR)t%8wtzKt)4FY3Wc+7$zefK}2HWrad0=9mDm|yqY2{ z&M^CnjGut4U^}P}J_W}mkf^j_B0{N+lXh+=4hfS(gZ^a>#S0>dpB6|%J;7lI2`+0t z65Nl*k-H*5$xkTBt#6<>WI;r~<2nu+=ZL~YI6FJzF^DG&4yq-AZ^kKEyEncx?kT&~ z7J&ieO;?xi&8@ehj=VxL2XTVt?T4cV$;IlcEaCE91-ZzOGW;N->oUhpoT0@R|2x=! zEV|!wU(oTcKg=MN2Qe680;N-%TaMW#sO6MnO#J|30ESKVwdexcv>Do_A|^q~t>Av~>#k7;J+0GsnE%4T9=*dFhdfwvYun0=wI( z42?OOx$nk{*#Hq}3hIM6CJ7ly0_Kj-vC$5a3z2tZH4veDHoke4Vm6HcrNup2$sS!` zZ;;l$9iGzw)qV=*K>DZmXi=GYWeg%AA0OISfO#mXpn#o~@BwET?9>Kijr=&r;mE7v z;^Lk2vcT&(_?3M)CIeL3d<7c@NJ!v({* zj~pm0G-N4;x(lZ}=YAJ`wE4GNce(u~-NafIHu_o;cF z+bgL3KoAmnX3eP+f=4CRs;i>uaIEtE!Irf=IyIZLq{P%3q*WDTM2S%7W00Bxkl z##TYz-I#43eQ?Q&!`t{a2?lp4g)|)fb2_*MtT5#P=hp%7X%E==XE`~M4dz$2zd!C|Ju)(f% z59l*7bh9;t{;X%z=I?T1`icHc{moqnCf$>>xe%_|G`v{9V(-|>XhjFzJ?n*6f4dkI zHF#t{bysls4xGBAP9;t@GwOw?QsSYJwo%W!<1FAHrvJK76 zbx)t(h!E4+>`*i=eReO}KDO+Q{v!kwG?(Q+-1}Coc+pzZVpI?n{b@yrEH3b|2@7gA zL7pYim#g`)H{VmTR|`P*jbh4CacB=nF#-=0jEHFNKvlmy__gn# zio6Rs+o3aSxi}2pIMhPEQZewltuoKG-ve2v9p6sx;={?mr^VkVQAGHy+$er&3vNC6 zn&-}m6DTS-H}{)cv;EY3(tNCrioEOJXN4`l=2z5KW&gTG>(V7@f|ESb-#mPIu5}@_ z?}~4kqY0}B<^KhzO3GWyyAXc*6R)G_Pzm_IV43j>L{v;iDs?IdAto)l;ZVT(CkO!z zX71~)W1Kkj)aK>e(6b>OLHQG%L>|iT-%9zYTi;X`(wWJr5~|Nm-W6vIu4n7kty3ro z_m-a&#$&%;Fw!I(MJU&~prqWr=7+X!-W->hcoU3tSZ|HH%`ag#EkUbGs2Su9@W`r@ zcBLvIF4AA#%-mgJ389=2DkR=DYlgwkj6%!faqpEXfLuJ-*x1CNG3RA~`RjHhO=U7|7?Jz33XFC{E~TU|D|M1!(Nuq4(uv*e0QCLLkGT*Y z9v(keN!iLu4&K>&w{PFhU$Ox8=8421WHB*>6*U$C}jD@qRJUadMJFwIU5TR|4%io}Y?B84Z3(f2@%H z5Sf^rTd5uu-Z6+@j%agIuMQwT_aTcVZH1slX21M~OET4-|0J6jYta1!2_AWX@B_D>UP5V{z zyCf(s5npp5b5s7g)CWJ6DnpC6h*hxvmu8y|SW%y7a5|!HGmKYnpWND=}Uc61BhGL3IqI94cu_(-YTN20y!8X0E1+_D^J<`W2 zi>*rd`^ej1L2O=i3&(*=BtZ+Ul(8t|a=Ux^Uvh1hXT(n|e51tdYWTOn7A9v9_z(pB zT}i=QV&ebxt!M|k-Muh7D9LkcXz2Y4c+-a-Dy_rJLsk_BTgAeSp0d(@?X(*>Q|ffz zFl%pnBj?~=<2uUBFwbX%nOMZj#6|m z|6C*2Ze~^Cu=l2f&HhCcWs#81g3_wjoMS90=7smwMbwWTU4_Kq{Etunnro(<06uNE zy|JXIq)iT8%ETxdtVHI2w z>sm^{u%RIjOpEa-V8bC44sE`(+xr{RCU@8t-uiInuP5=?+`_>miR|ld&db1&bfX?V z0UuZBD(Co0?tH!^i@1FvMT8S|-DM$b6~`@(`W?9)9cNT7|5V2s8-rnn=NXYWyEpK| z8I%uy7lEFF9G=AWAg>x!BZNQzi}x0Uli6rhH=8f}(jd7uPagg1+e;}1GAUV4$)VW) zZW|4-PJ!pB66%)T=GQXf;^IWCUz0Mf`Pbif9eVwiN!mRYMk0z(GMQ}f8T)#Fw?N5Y zQrZrNcOP;sMf9T=zN(jY6-p3rT#}f~dC#Kiyx(Ow$aXjBKtiQPg z-|=UC>N?1YEzuVWxX3d+-S>YM`;t8JztaJ7XG#_a$?bvM1IdvaTLPoSa3^W&*G%!OI(<({EpHid{%M(kXXWzcjX9P0x_d+v88v;Lh*2U!xt zHYhUmO-&Eq*|3*tRhtP0>GBQ(ePSK$?2I9!r zm>8iAkp3ZE#iB6do&?D?c&^mjL0d|xwoJ(yR1R@rbv%QD$jH7q9c?pSlfREdNSj3} zyPW@gLvqV@q2;R=v;VCY8ujX%GNZN#HpEj`1OC~EHWp7HmBz0xnRWC|g)v_T$(`^F z)8FBbN5=`3dIlsLQzY+x;dg{Iilm}%d}gbY3?&IEnN?q&IKEYAdFSZ#V)ij9A}LFk zLH`VcKh3IEs=-x2)my1(>VpjFY&0(Pa>euaj$C0xwt3+B|7b~8$B|-uC*Mylp$Lyl zP*HBPcoQ=>=dI%Q>mz(3nJ~w0PZ_?dg>1K!3PaH_IDmxd52lcKLRndPZo10vna}J5 zp$4Iqd5rC~+~aubNP^^<>dj>7D^2XymkMxRX7SZT@h5w_FIw<2@rZKp>wjtkhYN#Ic72{ z5=>u8q4U-^9V&HFWzSw478 z)wU9wX~06E>F)_Q)cqdIoRb8gMIQbxR$pPNt&gf*rUYZJK4}-+skGXEl$LF@mO`n2 zVO9=R!RBq-;sHWbL@Uaq-ln`d8RV!G)Tz`Yp^w#xH$ef%&-JX1OFvO?uI+Nok<0z{ z!I1d)<4qgf8ig!h%00`>4in|xw}?XBYG-deewg+~eI3tvR&R{j#{szphYIN6c*ddo zBY(|hnkp}NB;S64LUKlwe&nzL2_)m^vdpsiH_I|oo)5%pA2`aQPOmh{fA17CT&$_? zf=2>sqB|-bMivkMiDb*;UdAIFCHvKzVJ8BgO>|`W$5gZmN2k3_eW!nP_4JHFNi63% z(p6G3%xTfa>Jubqw`a)~fg_73!`HLiqC1tsXgail5*LWdC08!iKY0@T$!S0dt=BKA z|6PC`$5bwju2pnwSLawR^bxFuCtk}gKc4reebDr-vacGHHIx#75=TNh53{{sP1lZG zLVB{6tNau}-~>mTWgkHwNy$P${omJo^7ygnK=yQCKC0AQC2NV33YivaiQgQ(F9YVp6bo{Y+;J@iEI(lr>>_F=E?R$gHMv1nHml}x92p_ zFT$IWrL(@&h9gFE|0>k#a+sK5l6{eifcM=o5_#9BniOvIuwVqr$I8n3byKS{aRbUtiYizK~|U{9H9N zV=?h`z>Sgdy&Ig&fL3Mj>aI9PrIhuoFjcYbC+)V#$mo^7Evo9V`gnKaT(@G;=}+NX z3NW3)^~kK>H-480&Q+SW$W-LF)@jnZ)f@T_>=q7?G0D97>(|Al%a%ErMQ8?AXZRiR zwvV~b2EVDDoSc#p50Bc<&;71v)Cpyp0d@fZoF{mhe!)4l(?Pj4*KUfs8nCG20_TDF zyxSDC<>H-za(yHv8^k{~>B%YGr2MBRSLZgy;-o^I_0*!sw*Y!0fS2dpuQ7M;xw$$0 z6wc@qBQtiTNNkas9aj!43C{1IfBwn5)N&tU|EK3xiL?sIN=fPXkUTg3YoOK>;??aZ zdH__QtE0CuL(<{Rv;-O!o_kW~9*zrpjmQN53&(rm*jg4h8?MEqfe+hF4n1yPNZlG7m37ZjHuB!2m422yIXyq1>gujLVL^J86lACurxFyH)feChgH85Z z6=&E!oQ2nEEb!b3Ks3{#?IFobe-+%{RlAmiVZrB@%?DQyzt`$1nef0(P^NH<2R_ru zKEh^5W($0V%~*0EC6Kn!s4(|;3^yV7plhq5v9KwD>m^BEwOf-he5r|!n_Ko@5Q&0Q zWs~13EJ6TWHAro?yfw+Mb&9#;ufq~>U9=cZh)9#* zyEaNb^bjTOYbO1tn{KSO;AvM1nfX)DS^v*AchB+^yP7S`^{Bbb4VR9N)#%{_#K4PO zIm$M+i*a~NEdHY^w%p)GmfOGZBK~jHrI+kwK^|KQP=G+#4^&*wpe4x0Ecmzusc44} zc^CSrl$V!VAv-4vh3{|`?7hUpkQEDxY=uE`JNGWxk}iy_H~dVhS-(=HN4sd;5`gW& z>lD+mXEPH6*ojdo`+IXFbl8^*nV3G`}QpApEYHFIKO>!zBEy+bhQu_7s zJ&}T-<&tV2k^0njsOTl!G%*Fbb4u1a#IR*t6cdSu&6zf<3Gl2Js~Y3k9R)Z{GF2`9 z@AlEf;ra)5l;^^yDHAK?6n0)KAsVyB!l~;-v zsYjQ%oXWoV2wn^LmUF$awVeBJQzT!7OYXD(H8bultLtakGHvT_lS~9LXaprihVze4 zSYjKdxVi;s8uPQf%KA7EpVI3uxd`>JweN}Bz9g9C^`(|iI8(~Zxlv_cQc;-h0dHc? zuy@47r`W24C{^uNty<76J@9|E0IQOO0dk}Y$Cs>k`|tEOw0d-Pbq%4M89^zBqP5Mc zauG>e8)gn@7|^tq(yLRlnx6+!%>3QN#@Go_4Vh&EM&QKwElhhO>4%D-B_pNGGFANl zL7QS91gt>vnwMU11gG9fgfl&(!^LbXUS4X+Ym$y;;gV;h9C6+g>IL@`zsJr#s#B>G zh3rw3#;|7u-#hrhub4)Se0m>B#t5f%rhol$1hJn+nNaQPBO}hDp*I_j3Vx$XJ}ANX zD>(-8!xgm!dO^pSE`mertSWAFN%AIHVA)Jw)^j|!W{p`YbYTCgI0OmfjPp*_R7p8h zrO5e^JY|uOKzN7CrxF|b6{f%Wy4;PwIq0|Co`>ZGZUQds37!9cd9HRywhFt>w)0q3 z+{r$cc7F~{zO^+61C=}8|3B3OkU^dA3)6bO`1V!`Kn+)&}=W+$+ z1OTBheCQ)JeJJ&J;-$;u*PysU4gyVt#8E?fZweGn=2$(NS8<)V zh?tgw-GIlR8vICml~w^VVA7yA_Ny@rF!mifohRE~hbeA8EY4!P5?K>H3h@Qhf=vmRR)f1@Qr6J4xk`Q3ZYWxr?jjkc(gv zleAgB+f#?OHzYUXUbn+4V6DPpfWBpWqN;z&yg-lHH&poS*Y<7j9@+=Fl;J3y-_leDBFr5`c67wCp;|+|x{01UEU|2CL2^1_ zu?8jUU^QB=>My0WN(>pbN0zvNKS9?B9sC`V(tr(Ko0suz`xtpBSCAdqLwv+xyD?5g zKgE{ItFgG}=B9>&1Ql*;i_tx(-h4mBEbq!as||}2-GEegM@UvS$~u`+Cl!ih=u06A znTA2~XYi+v2tqp@S_^`Vr4l&&VHmv|YXs+6x_#pf82eqyk>&d02@#g?gN zusN~zJ?(e<-mB8!SP4cyTq>-GeI&VS(Lz&3iuuhd4oapWS&>Z!#>ThgBsw3?%zCRL zv*+J+kzeX~N_*(R4Nzd&--;+$5d#c^}gq5a^li1W}&#K4S6ehbGR3NC9 zZ9vg=0oV{$Ws9_QW#Wsxp&DHlXu^ux23>_Ag3^yRmE?XclkRJVIw+1;ParPqXX6?C7QO*%GWBDTU`0E_LBrG z-g+1^!W5oKi~_aCgLUcXLAHUJ19PylrJ zgkjm2E?uew0Dx@K$K?e$E|b^W%NB39vN84&Ls*v#7jNy&M{i!3ufHdTuBF2>y5Z`VbWsO~+YSISJ*W!Jyy z1*K-&yX+-;DM$-Zh)UmP6i#JjWk~nLHqEk@xG#NK-=VZzHP)d@20V9}BW%u6R(mt= zr;oFoi6(=7AJ&{8SE9TEN)C|k=q~#vqvoe_?NqA@eLMT^)FR$SJ=ChdHrWkmlsdg( zt>n0W_sd#xd@v7n&Q}d}hLGRfFyT=2H}8!8!Z`Elf#KS$b8$GNEr`tI@ciemrjGo6 zs#BQx5$M}{B$-uc)v?PDxXsc}JI#nZ2xl&q)%z;uqp!2sB!Bz4xZMibOWA*Z%iT$Y z3_#*aH(UuqRbC$$8GNIv=e?`hI46j4Dgv||9Aa(?4k~1YG z>!{s8b4aH=XP$u-pSrQJ0GZqtAndDzSSN2+AB2%0=Pnz@p4_IjnO9UW*p_(tv+;0y zSg{=oNq)zV^Famv10u+CxW{zQ!c-@8!&xuNV`Uk0l_t@-ibqAq)fZ7hy)6GA@@LxR zF^zWH**Lh`p>>JR$;s*7kS!qj<`gASjeNun&{=qyTj2Z;4x55Uo2rRj zC=sxZdHeJ06Hj#GzO0up=P8Pq;mCQ*Kbj-CPqRA6i*mDeQ-fI4mC%QoH$5elW`Ug-JhV1f5QA zk`82vrJUfms+UlFq&-uOJ@LYxoK9W;L#gsraarxpWiOpNwJ@jS0D3Q=X%bnv7zj>! zP%LX?j=d#t!fbBdRiT-fl(B^4n$hv&=jW{V5~&st29jeEgboWGiJI$0_qpLK35r2- zn)OZVK`_u6IG#3?`X)K-3|nQg=L@9a=<5m#3p;=yfD-XBY9vTE_Ej^)TZJi^+YliQ zqUE$&=2r`O{d`1JU>&Hx&sWD&Ny{3XcwNCZ)hWLUeitj^W7Obd1plgj0u!4y#&Kgy zkTC*X2XuM$cT0XvnuQwr;VX!^Bk0=&a2Z%cFl_4T4$#)qRq$_^vDrWOeA-(z&wa3x zcmZAABgco|wzDeCyC>6Ss? zQldS8ZqgIg5ipoZQQIe7le1;z^~B+S}9VCoT64DDuZPT-mh5uULvkbk3Kd6-q zFq?tbmaAyb0|<#f`Zr%`_+=S89HO@1LFDp;utsCY2TY=|JGFdOk)+pGeit7s+u04D zQ`b;=>;^bPI=vTN<}lHJvXYD{z(_xJ%-kgB5=#X1>X04hbIm75@ku~j(eOnAG&!1e zEkzs%GI9H^k$H@$jHF0M4yI$jhp%QN|pX?WR3&aSD80cq87f>=RWK3SgWd@56YFw zU82dJ$*x|qb4fnCJZH!w2AzgMC9>!q|DV%`+>0A0x?1qPV8?C_TJ~}rlN(2kjgwp# zTt9yPd=9aPOJK)!lS{RLGl1r`f zkP!#%V3>w=-4r#rcAj!mp0&PdQmE9JC5A=*iPoz2Xooka;oL6wR{sM%1I~$s-+b03 zZQIkS;=D`0{~;XsFn4QKns+JYfsB_FE%z;oMX7ly_Xi&kGtx@=r&KHNP~2iiiw>4! z%H(d*jNe7x=4Q@|=9pv-|M|po`Ni-)(uHFN#Beco380Gg)bK(3h*N99q~mn`kNXSd zMZN6yTWM3)?ykEBNN0X8X49cy`hnL^N!@s6Zo_$2$1He*AWgv~Av718hVDvbbt`$?#Kt+tSC*Bx8Lf6JOx4- zFn~~~`4+YHYvVvKK4}MM*!Qc5^T>Vq@8}HeB#{GMHG4)rZt_M=l4aJ%rpo$Tjxo0> zX`|-Zvo7(4VRx=C1 z|NMkL12IWlQ85DOVM4Z+->`#su^l7wx%)g-6#5y82%Qz$*`<3ZLMG&F<)@x{-Hm%X z9)VQ{t{H)MzJ=6LZ1?$3N3kfO6^|7buB5>nO#zM*E5K~OA)wFtCMG7M=~L}~Y>sJi z=g%4(xcyPpkVRcgGv~z>ep#NM2i9k>5a&o}-)(%I;8EAY?bTeQ6~+y)X=+qQXOA{t zfydae*NY@JEM$ZUDbMq=Cr4Sr{M!Vr>LnGEZvI+b@8K%UKsFF@`=DA{r?q$;O+NxSElcD~ZFAaa-=pV)~S`i%XrXzJ}NbsA7WT zPTCcQ(03rGBA94|sq+#BKdB?TD<7O{emb165X-73Xw^!!)nBD-iIglJo?m!TUrIp_ z>Q406R~KG!Cr}1y+=67{LCN(bnU#wjzwF~oS}7u0{SkRgkQ_oz+LQQjlEM;M2Pyo} z&lE%iJ(jXL`4M}Ft1+I|i1)Qwryk0CKJ@{zCDAwqNP31U-nvrEPoqCPdnGSq(H*CzZGxF^oP+}2UgLBDHoqF8DjYo04F26wX*hjqHIrYK zd+Lc?_Y#?r+uHLaHWi-3}&$XZiJ7Bs&8K+)QRE6WafRT}k-PQ#Pc zRU*sSV0e%@?@kKwi~q?sW!sly6cm|91=)&%jE>M;n>7;u>T9>P3v0-uLuJ>+2{;~p zx5Z-IlVtsH1ii>Ek-f^>H-*CpZ60)~rq^Ru8B`2V5__&cs3G=*6lCV+FS>naH^S@* z4uP>xAy9UZhDz`{VfQClZuupx_IC-ETTPEP(h$52M;8yr;J0EAMgHl%wo%AQQN8fX zT2i1n=!TT-Ec&ylRZ-Hl*!cLEYOAXk;qwKYEAjp&nHS#XICYZo1$|zWm27U5 zNkOZJ)Jum}*<2vozNNv{#b1#Y$g`aJ%l!@D9&&z!TxB8G(a%g&Mm_Iv66MwK?zjGs^E%R#AP(Cnz_#rp4Lr zDwaw5;odqpiHmfE!L~spjO%80plUwyd=CW&46pWi>B?1&D~1#1>rH(4)>DA@qV=+vYg!=Ev5rH48H}FU)rP&NjC>11=@ghDbdOaK$cPzV+vzf>lLb z*^w=x=K63E*AT>L$&D1rI=9xI(XXke6n)Vj{w2kD=d;66?;!b0J;ySxo`JO9?Pe93orz^s8v{P}0(&vvu6T#i)({da>);A(#!ZVmNEuX96omW3&o z2NX|+JXW{0H^rf>zJYQ_u{gC@xh}xXQ|$sGIK1Q*_@VRY$s*Vke4?0wwMtD2WmTD& zS5uoTVb0O#dn#nlh&EbrtMJhdz>l#Q3lXa8J*xS4s(2y_z9P%<9t|g(9eaMiP#!K> zor^Ymg0>Ou32srgg3qKLiWHESQrD>d_u+(M5MUf9_)8}o(-ELR#BCDHac|)-5P)W7 zA0{h0e%w;Xa+#xz{$qByu?YU)sN{zej`B(yRXnfHZY!T?|_36 z3=vYUqae-UKad4shE=e(n89fElnT7S29FQ?J0%)DHRGLtyJ@&#NzSx86c5)9FMP?g zF(f=MWUOR5o(HC*^xMjcagt&~X*nJ{tt(?~E0qbOd{)<#a41$B4i* zprpznOY8HZa-HPhQe+fF7sQKJNA`voXt;gP`|a4yJmz<*e^vv0KAA)S2%HUF6jogy zLrLJImPgtcajgtxYzBphTxy>mn;oXOArQ7qN6o_XHDn!gIHTZ;b}6QgfyOH8s=Pqn zP?KDQs-cyR=f)qIwaOrhkN|)a_Y{r>7kvGTD^3C-xRz9~dp0~ff7aR>B?w8X%Jh*_ zfkEe&9R;4_=3>Kc8xNuCVCjf>VmIn{xd#%|!j)&QExIXMj_N|u=*bDQ~!Z+1Zf zQ57+w^!tdGp*{vy&BZ3mSTJ-|2;_h`>7&BWc0xXF4f=xmIjWaz!u->HMT*4NWBBu1 zLcXElfY`(|_5lZAIO9a~%&m{A#EK@2$Fu!Uu&k|f4Q?AGWD(bfJ(1|EQJwv@v(>X_ zJ;oMB7R0d{tzkn92F5=ES&qCxy$PJL{Le{sqV?S4AqyaFIV88x+mE6Le848@!20PB+bIj2KaCLNC&Q7@qtEF8D0&nXm=wnd5_k`zrQbi6Tylj2n{21Z z63WFF_`yxJx8c68Lne9_FySB);5m#J_dT#;Ug0z7gzD=l}y?Nhw*buAvcP zgocC)J|0;fQVrq^$&Nz}VYY#JnEPl}CG%DMjWwZ}z)5e`E<;60nPl^`R2Lq2R$ja^aHcBfMbRgDMY zy=`JjiWqcM!CDGG9W)>a%Wc29Ea&Irz>lDbv72C%?&0pHJaZmq#A}Mj7yeNR+O$S_L##rSReZb{Y^V6h-dH>-sqks^CYh{Em!35UNl$7KkD`8Bf)iR(BHYX zw6QYRDmlA1toQ5+ z!GD5Jde1iRyTcB$OJv5(LKjarA_e4CLk$2z+8mIRE#O*RwVK z`kiMLqI?6xk*gs!^qu<7Nj?TwrMQkpvv%xc@7T+(akj0bVW@wJpY71>JfW}2sUwnP zgfUs<1K?6$OB7RAKGqq~}N^X5U*hWSlHezbxXGk4?xvKPLHpyc5vv;qMP zKr3ceD*Q9${pP_UmQp7?+58}njr`-UKPwYvL$x&q?YER5*n?<5xP1o#pbU+Kd_q<0Zo^ zk!7J3R8l#sHeo8A#eVnZUWqr`W1o#CsFperxgxr%+NK4@fYpP(|CYzGFjUjdOeDUF4tt1&l-7asl!tczydMtaEwwYroh~Upros|dSyoj zI~@0)V>~oH7AhWm;t%%%Zug-)hcyu=}g+n{>{KAv3@_L7`;52_-F(^V%?Z6wF7t8AlnO1xh9ZV zN};SdNY&V9ltUL&jUBWu=Y)-BazFCGNCV5%`9^tP?ui;bAhDoK60EBp;(*FRvjDF^ z=(2tB0)kzH%8mW#>PyBI(Jc! zd#)KN%9hib8WW+v?_!x<%-mNV`-isg&u5Ve!o1Ln=)Lamvi}6-co}>X8rN=J`nL1t z`DGv9R2$A$bdOFe^~Q$*(QGHyhIuXCV_y%FYyfC-?Ra`g)noTW!KtFK@M3@-RM;t1 zAd8nZF64~5o7*lDi9iuP0VInwSDE{FIjdcP<>oL#)4V3XW_re1*X^cKWZ{J7~OeLZ%$L}>Ca0CH4H-f^Va(> z6tk(*S}GRfgkApqEehjx?vnZt&4^?Y8gMZ!pw~QxI$IN;tH(W9VsI_%IGcB)6gpc7 zpG|%oN0xv^ve;_mrbUXH9g7`hWLx<&;=kA2zF8|w{K+{)vc-d^3b#IyKCC}%h?ai|QBuFE#dv~RwLzXy(m zqxgSOq!|A~ryr|6RiJ%$%Js~XcO}eo=^loKCCobSGih%NYa_Dln@OzIaY2lMtY0QFFr=F|Jq~l=5IWNlL z%*fOx09XDq?Aiu($7OG><>tAvx}->V+XnKFJ+-e({5?7bwdVRu_IY7H&&kFr#jU0C z0pY~NtwR_Ns61Y4D94b+&`t#M^!zA`Td@M2i6(7KEd$oTn_`gl0)qzCV(@mV2@^+w$e((t0gGSj}FGNLwmu=cXWFZvhgv2M#R zJakO1pL~li=v=AX;|POwf#4+_qH*$_0Us-%WQh;GP?ua+nJ^DT=`1)R^2Ecjst^8m zmTZfU``Z3`{p>AU;7yOi;w)!( z#1Zl6)Faupl07BSx8E{;`1{LUW8cN$eY%*ROJ0koE8w`|{*N&{j3M7+u37pzM-&&| zsv6xZSX{=t^q(;x2~e1yx?(dBv8%PYL3}W|nk$O{-8;OX;7UdHiJa7VV9QSd;XKn2{T@7kL5=zAn}x&1(wvtv{VE4mu@!&S z!nJ}_M&u5vpQI$$RH$2{R6C|SH2~(ij0-pmedkSHJG9jUH#(3;q5PLHR=*G-W6=jS zOdh%b%uQzfBzxD=7s|2+at|n7q$rInhWzVV_;b#jLRPmJskm3KzUE(rNIC(x9RJ$) ztlroCB2)U*Y(rLVR&3)OiwItUa`8RCi%E5rE6to`baWJu-$R(rlhDvr?{H=8$h5cZ zg$w$a4vovCj7&_tzSJ-HW!lev-YBF(@eaqwSw>7WOT}em7&fF?oB*wU-rCxA^0%3* zyL&&X@%;5gODUx{&KaXYmyUrWwT+EQptCe|bacFi+Wj#u`N9AvO-K~?x$HgEzs6C@ zb27V-6$$R;AD`B^UcQ`ISZD^PYiMZL*V}v4-Tk$Mq~sDdiqI)cCCtlflA4i{lFCX? ze+<&{cwu3o*VNFXj^Qb{d~KgQh1*SLSu8E^727EjO;YM~xaivI>c=Sal2cQUAQSeQ z8B5B`&#$Ygd4fBgx@KOowCiISp)M|##jP3DI9Qo^dC3b4zT*=UwvLYa&!0cvJ@D>` z)(P=>t;`9wV*iyCiaOuL8Y%Ur?!sLw7vrM4BN%&SYhx4IHrVjre~}G4X;MolQ~#kt zNPAk8oqhb4#(p+7HnPSuv$Nx%ss(}lHQLk7;dZ2iy{rdG0#&F?V=+L3a^SW|ny!|cP_HElP zPL9zhtVOuyWQa>ty#*yl{E_2}*iZh=07vzH-G#jMA=rlw@vU;d+~$L{p$ zee?74WCMzei!U)+Sc@eP*KO_X$8RrK$|mA|In*Nl{w&;FTo-UZeL_pT`!Lgi$`t9f zYtD}C85&}tG>hO^Yde!a$IPP>cZ4V} zPq0%cj_d+fFFs-gO$@A?y6xNYx0hE#-zsT_;G$XFlBj*%$?MlwgHO2*?pjB(;<_ci{;}G@P zT)3cJUcTGhUZ!TYG(En#Y4Z#w4P&@_E(sH*0KXi%Oig6B^=f^3Hg+#R2y27eQt*vk z@e0`Lt!io(@Gw?ZR<|`oXHf9)25V)#e0ldt2Ib``H2M)wj*B>PKjdZ1Z$!xoE&*2# z_ChGqP+UxtkdT;Y{^Vy-ofB{IU-tZS(-<~NyW=VfrC9E4kWF~a?)E!BOwDE01~%@# z!HU+}JdfeWxJ$vv!Xj;E#tV!9mHtr8@5T4;r@?{X7v4`UfO7W$Ty=UnzUdL$ix+Xf zU>KTQC8=Pa4CDpte*R2+{56prN{>BnYNU4-a+Y+Nz8Gxl${abE;j@84TS2^4h_Y3X zUL`y3n_n-Yyyzq5ARe=@82pFzk-j!vUQKOq5i5+wYozN|eSLkpw?uIncul(tzmc46vF(yq1a`ODeG(C&ND zP(ac&jT13t6xZ{=QIBE{i%mP>${B?D8p8mxc z@n@v3AEzPuXn5Gk;bSpY2BB177j1P}`Zip(i=Rq*_39}0YiYoH97hu~vpURbFTBcx zb4$0sQq=ZozIpz5b!5!(g|`2)3-3QQWTAMU#miAPSRD?GX=RBm7m#)w5SfJ>-%v?t zqI=~9*n^YPD=`~SPvh0ASKp6}{4zC_!ze9PP*yHm!|9m9%g2|TmuK?b@g#m>su(_b z2P5}FX-CK7hIC;7IC65YqK9X9drI{B$M1GFndV%PcYsnntd0-$kg+-(7O!@9XO!j1 z>hbW;R~EQlZPP(|ehfC7{QP<0)0C9M^vpMJUL+-Lky+{Z-ajfzz-w8%w#%t;!@xQ8 zw?IT=d~Y;ihoWM_ty@d>`pg&sza?K1eM^(GO}akSY|5QeEoHM0*H2x?`QSmcv1|yE zv;J|NVo@d_J!I@0_Vn|jvWKb`z+My=v0@2T{1ysEMjr&O#R_d%>3C)Nms1pB45}D1 z8Mm{w&CcvAJtMc*Z{eHNNXu0}Cnu-0wypCHpXO&?#9W%U(DAZto+xK~$OC&3!7KiX zmXnAJR&T!%qaG)g+qnJY#JDjiT#~uTtV~Q!hRuJz*UFl7U@?xC{NBAs99Yi&Q`KX* znYXF-v4x>wM$$1R9NPVun6cY)Wo6VtG&~fYcT9I!j{_VOhM7De(=Bw5)t&gTI~z&PjSW%B%ah2ITsM2qCa zM14)oe=yY{;~dq#&TryZ)QPTqO{S{JxLK4$b9g+J>O)^Rq3gaNJTc3SjPD>`>?1%2 zWo7^36VBbMazej_w;nDUtctH~Wj%K67ASVRt2hOkJ61*Efg05$h=ir z`Z!KA^0We{j^5)ZPK-|soZ2oeeQ607zDy=WYCqnb+;(rEW-4xEbQIU*Dy|tsL4WGE zOOZ7G>b`yZ7(R{J1M_=YVr{%anYaqabiDdwW{rTd=EBVwr`dUDlkovlA$%21P5fi( zC%={SrzMM;ez3D%a`1o9y+-Ma zFHBCxFZ>VD(4pTt7K0PnvsqodJqRbQ^qFzp*M<$gMSkU0_=e|#8YlL&>{rc7bazN- z?U5e#*wb8;H(55O;(ThHlm43X0mpas3B7OO~_5hLngJ z7m@G+y(U$5rEp7;Q`^dcc48UgITq1(Ol-SZ+KC`N`=7+_Z@(x1&9gtO`+D9`Ln%`^Z93>X=Gkr_k2(TOImwoo($8E-9r=Njq{EGz_9* zZY?=L-?75W{nuD-wAb|eP7$AHb1i$MTW-f3pEahs(9Lzc#BH|b`flvK6~pScAi}wf zQtaezy6_fnsV&Rkwb{g_ZML=RI>$zPZtkT!e{EIZ(k?1&iCGl2;W^jqtk2nFgGpS? zJH~xI+D1(WGqWON?-_p7lYDJ&LXOc(VG8Bt3M7~mZ%albkMcg+y2UZ~Rxjm^Pi+yu zo4!2$VwGO2TSS5V#3toM)4Qq9ZTdY%vhzy}veU#O+_&^k4)g@?x;<^&oZWW5N0I~2 g`TzYN$xlqSLdw5cf*W6@lFz1U>T0~$Z+Z3q02ifW$p8QV literal 0 HcmV?d00001 diff --git a/JuceOPLVSTi.jucer b/OPL.jucer similarity index 50% rename from JuceOPLVSTi.jucer rename to OPL.jucer index 6de47df..d09deb5 100644 --- a/JuceOPLVSTi.jucer +++ b/OPL.jucer @@ -1,147 +1,145 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmd/release_win.cmd b/cmd/release_win.cmd deleted file mode 100644 index 18a2d92..0000000 --- a/cmd/release_win.cmd +++ /dev/null @@ -1,23 +0,0 @@ -@ECHO OFF -IF [%1]==[] GOTO usage - -ECHO Building Windows Release -SET RELEASE_VERSION=%1 - -MSBUILD Builds\VisualStudio2015\JuceOPLVSTi.vcxproj /p:Configuration="Release - 32-bit" /p:Platform="x86" -MSBUILD Builds\VisualStudio2015\JuceOPLVSTi.vcxproj /p:Configuration="Release - 64-bit" /p:Platform="x64" - -MKDIR %RELEASE_TEMP% -COPY "Builds\VisualStudio2015\Release - 32-bit\JuceOPLVSTi.dll" . -COPY "Builds\VisualStudio2015\x64\Release - 64-bit\JuceOPLVSTi x64.dll" . - -REM We can zip using jar from the JDK! -REM http://stackoverflow.com/a/29879102/1480560 -DEL JuceOPLVSTi_%RELEASE_VERSION%.zip -jar -cMf JuceOPLVSTi_%RELEASE_VERSION%.zip "JuceOPLVSTi x64.dll" JuceOPLVSTi.dll Instruments -DEL "JuceOPLVSTi x64.dll" JuceOPLVSTi.dll -GOTO :eof - -:usage -ECHO Specify version on command line (eg 0-14-1) - diff --git a/dro2midi/README b/dro2midi/README deleted file mode 100644 index b388fc1..0000000 --- a/dro2midi/README +++ /dev/null @@ -1,182 +0,0 @@ ---------------------------------------------------------------- - DRO2MIDI - version 1.6 (2013-11-29) - Written by malvineous@shikadi.net - Heavily based upon IMF2MIDI written by Guenter Nagler in 1996 - http://www.shikadi.net/utils/ ---------------------------------------------------------------- - -// What is it? -//////////////// - -DRO2MIDI converts Adlib music (in .dro, .imf or .raw format) into standard -MIDI files, so that it can be played or edited in most audio applications, or -so the notes can be extracted to be played on a real instrument. - -// Features -///////////// - - * Converts DOSBox .dro captures, id Software .imf songs and Rdos .raw captures - - * Converts OPL frequency changes into MIDI pitchbends - - * Tries to map Adlib instruments to MIDI instruments as best it can, but - these mappings can be easily added to and changed - - * Instruments can also be mapped to MIDI percussion - - * OPL rhythm-mode percussion is converted (v1.4 adds support for user-defined - mapping via new syntax in the mapping file) - - * The OPL conversion constant can be changed (see -c option) to more - accurately convert notes without excessive pitchbend events - -// Usage -////////// - - Linux: $ dro2midi file.dro file.mid - - Windows: C:\>dro2midi file.dro file.mid - -For a list of the command line options run dro2midi with no parameters or -see below for more details. For best results, all the .txt data files should -be in the current directory. - -Instrument mappings between Adlib registers and MIDI instruments are stored in -inst.txt. This file contains a number of existing mappings, but additional -mappings can easily be added. During conversion, if an exact match cannot -be found the mapping with the closest Adlib parameters will be used instead. -A message will be printed when this happens, along with a line that can be -copied into inst.txt to provide an exact match. This approximation can have -the unfortunate side effect of providing some odd conversions, such as -converting a bass-line into a monotonic drum. - -To get a perfect conversion you may wish to delete all but the first -"all-zero" instrument in inst.txt, which will cause all instruments to be -converted as a piano. You can then copy the definitions printed during -conversion one by one into inst.txt, to assign the best-sounding instrument -without worring about any default mappings taking over. Alternatively the -i -option can be used which will disable the closest-match algorithm, and only -exact matches will be used (again, anything that can't be exactly matched will -be mapped as a piano.) - -// Command-line options -///////////////////////// - --p disables generation of MIDI pitchbends. This results in a single note-on -when the instrument sounds, but no further pitch change results until the -note is switched off again. This can also be used to prevent the large number -of small pitchbends generated when the conversion constant is slightly off (but -see the -c option, which now provides a better way around this.) - --a will, if pitchbends are disabled with -p, approximate any pitchbend by -playing the nearest note to the new pitch at the time. This results in a -"hammering" of notes during a pitchbend, which while humourous, is probably -of limited use. - --r disables the conversion of OPL rhythm mode instruments. As OPL rhythm mode -conversion is now quite flexible, this option should rarely be needed. - --i will disable the approximation algorithm which selects similar instruments -when an exact match cannot be found in the mapping file (insts.txt). This is -useful when trying to create a perfect map for a single song, as it makes it -easier to pick out which instruments are being mapped. This option should not -be used for the final conversion however, as any instruments that haven't been -precisely mapped will come out (by default) as a grand piano. (This default -mapping is simply the first entry in insts.txt.) - --c changes the conversion constant used when converting OPL notes into their -MIDI equivalents. This is the heart of what DRO2MIDI does. Unfortunately -depending on which set of documents are available, those writing OPL *players* -are told the conversion constant is either 49716, or 50000. Thus half the -games out there use one, and half use the other. The result of using the wrong -constant in the DRO2MIDI conversion is a tiny difference in pitch when the song -is played (about 1/17th of a cent.) Most people will be unable to hear the -difference between these two values, however if the wrong constant is used by -DRO2MIDI during the conversion into MIDI, the resulting file will contain -thousands of small pitchbend events, as it tries to approximate the exact OPL -note played. Using -c to try a different constant should solve this problem. -The default constant is 49716, and "-c alt" will change the constant to 50000. -It is possible to specify an arbitrary constant like "-c 49999" however this -should be unnecessary unless the same nonstandard constant was used wherever -the song was originally played. Note that no error checking is performed here, -so out of range values could easily crash the program (not that that's a major -problem though...) After conversion the number of pitchbend events is -displayed at the end of the output, so it will be obvious when the correct -constant is in use as this number will be significantly smaller than with -any other constant. - --v disables the volume detection. Normally DRO2MIDI will take the OPL -carrier's "Level" amount and translate it to a MIDI note velocity. This will -result in the output MIDI file more accurately matching the loud and quiet -parts of the OPL song. Some songs (e.g. Stunts) somehow manage to work with -these volume levels set to zero, which results in very quiet MIDI files (there -is an internal limit as to how quiet a note can sound to prevent it being lost -entirely.) If your output MIDI file is much too quiet, this option will cause -all notes to be played at maximum velocity. - --s instructs dro2midi to write all detected instruments to a .sbi file. This -is a 52 byte binary instrument format for OPL chips created by Creative Labs. -It is supported by applications written to work with the OPL, such as Ad Lib -Tracker 2. - -// inst.txt -///////////// - -The instrument mappings are stored in inst.txt, in a format like this: - - NO 07-12/4F-00/F2-F2/60-72/08/00-00: patch=15 # Tubular bells - -These lines are printed automatically when an unknown instrument is encounted. -All you will need to do is copy and paste the line into insts.txt and choose -a MIDI instrument for it. The file itself is read in from the current -directory during conversion, so if you run DRO2MIDI in another folder remember -to copy the file across too or your mappings won't be used. - -The first two characters indicate what type of instrument it is. The -hexadecimal numbers that follow are the Adlib register values for that -instrument. "patch=15" assigns MIDI instrument #15 for this Adlib instrument. -For percussion, "drum=35" could be used instead. Anything after a # symbol is -treated as a comment. See the comments at the top of the file for more -detailed information. - -The instrument names (and values to supply to the patch= parameter) are stored -in patch.txt, and the drum names (and numbers) in drum.txt. You may find -these files helpful to reference when selecting instruments for conversion. -(These two files are read in from the current directory during conversion to -allow the display of instrument names in status messages instead of just -numbers.) - -Note that the parser for insts.txt file is quick and dirty, so it's easy to -get a syntax error - for example, an otherwise blank line with a single space -on it will cause an error (so if you get an error about a blank line, make -sure it really is blank!) - -// License -//////////// - -DRO2MIDI was based on IMF2MIDI by Guenter Nagler. DRO2MIDI is released under -the GPL license, except where it is incompatible with IMF2MIDI's original -license, in which case IMF2MIDI's license takes precedence. - ----- Begin IMF2MIDI license ---- - -IMF2MIDI (c) 1996 was created by Guenter Nagler. - -IMF2MIDI is free and may be used as you wish with this one exception: - - You may NOT charge any fee or derive any profit for distribution - of IMF2MIDI. Thus, you may NOT sell or bundle IMF2MIDI with any - product in a retail environment (shareware disk distribution, CD-ROM, - etc.) without permission of the author. - -You may give IMF2MIDI to your friends, upload it to a BBS, or ftp it to -another internet site, as long as you don't charge anything for it. - ----- End IMF2MIDI license ---- - -// Contact -//////////// - -Source code is available at http://www.shikadi.net/utils/ - -You can e-mail me at malvineous@shikadi.net diff --git a/dro2midi/build.bat b/dro2midi/build.bat deleted file mode 100644 index 2864811..0000000 --- a/dro2midi/build.bat +++ /dev/null @@ -1 +0,0 @@ -cl midiio.cpp dro2midi.cpp /link /OUT:dro2midi.exe diff --git a/dro2midi/dro2midi.cpp b/dro2midi/dro2midi.cpp deleted file mode 100644 index aa44e3d..0000000 --- a/dro2midi/dro2midi.cpp +++ /dev/null @@ -1,1328 +0,0 @@ -// -// DRO2MIDI - Convert DOSBox raw OPL captures (.dro), Rdos (.raw) and id -// Software (.imf, .wlf) into MIDI files (.mid) -// -// Created by malvineous@shikadi.net in June 2007. See README for license. -// Based on imf2midi v1.0 written by Guenter Nagler in 1996 (gnagler@ihm.tu-graz.ac.at) -// -// v1.0 / 2007-06-16 / malvineous@shikadi.net: Original release -// - imf2midi with .imf reader hacked to read .dro files instead -// -// v1.1 / 2007-07-28 / malvineous@shikadi.net: More file formats -// - Added .imf and .raw support. -// - Replaced Guenter's OPL -> MIDI frequency conversion algorithm (from a -// lookup table into a much more accurate formula), consequently was able -// to simplify pitchbend code (now conversions with pitchbends enabled -// sound quite good!) -// -// v1.2 / 2007-07-28 / malvineous@shikadi.net: Bugfix release -// - Fixed some file length calculations causing some files to be converted -// without any notes. -// - Added portamento-to-note for large (>2 semitone) pitchbends, but it -// doesn't seem to work when using Timidity. -// -// v1.3 / 2007-09-02 / malvineous@shikadi.net: New features -// - Fixed "tom tom" incorrectly called "bass drum" in output messages. -// - Fixed multi-note pitchbends by removing portamento-to-note and -// adjusting standard pitchbend range instead, thanks to a suggestion -// by Xky (xkyrauh2001@hotmail.com) -// - Implemented a better method for reading Adlib register -> MIDI patch -// mapping information (all stored in inst.txt now instead of having a -// seperate file for each instrument.) Also improved method for mapping -// instruments to percussion on MIDI channel 10. -// - Fixed OPL rhythm instrument conversion issue (a MIDI noteon was being -// generated too often - if the OPL instrument is on and we receive -// another keyon, it *shouldn't* generate a fresh MIDI keyon.) -// - Fixed IMF type-1 conversion issue where unsigned numbers were being -// read as signed, and the conversion was cutting off half way through. -// -// v1.4 / 2009-03-28 / malvineous@shikadi.net -// - Some code cleanup, fixed all the warnings in midiio.cpp. -// - Fixed a bunch of char/unsigned char issues, hopefully Win32 -// conversions will now be as reliable as under Linux. -// - Added line numbers to instrument names and mapping file error messages. -// - Added new instrument mapping entries for rhythm mode instruments -// (which previously were hard coded.) -// - Added transpose and mute options to instrument mapping file. -// - Added -c option to change OPL constant during conversion, thanks to a -// suggestion from Wraithverge (liam82067@yahoo.com) -// - Added -v option to disable note volume (helps with Stunts which -// otherwise comes out with no notes because they're all silent.) -// - Corrected OPL volume -> MIDI note velocity algorithm, and added hard -// limit to prevent notes from having a zero velocity (which stops them -// from being converted, like with Stunts.) -// - New instrument mappings (to be copied into insts.txt) are printed to -// stderr, so "dro2midi 2>> insts.txt" will conveniently append them all -// to the mapping file. Thanks to Wraithverge for the idea. -// - Replaced getop() and getchannel() lookup functions with GET_OP() and -// GET_CHANNEL() macro algorithms to calculate the values as required. -// - Included 128 standard GM instrument mappings extracted from Creative -// Labs' MIDI player (see gen_test_midi.cpp.) -// -// v1.5 / 2010-03-28 / Wraithverge (liam82067 at yahoo dot com): Changes -// - Added code for several MIDI Controller Events for output MID-files, -// and they are the following: -// Reset Controllers [121] -- At the head of the Event list. -// Balance (or Pan) [10] -- At the head of the Event list. -// Expression [11] -- At the head of the Event list. -// All Notes Off [123] -- At the foot of the Event list. -// Hopefully, other musicians will find these to be needed, as well. -// - Added detectors for the (at this time) two DRO formats, and a hint -// to let us know that the DRO v2.0 format is not supported. -// -// v1.6 / 2013-11-14 / bsa (http://bsutherland.github.io/JuceOPLVSTi) -// - Added -s switch to save detected instruments in Creative Sound -// Blaster Instrument format (.sbi) -// - -#define VERSION "1.6" -#define MAPPING_FILE "inst.txt" - -#define PATCH_NAME_FILE "patch.txt" -#define PERC_NAME_FILE "drum.txt" -#define NUM_MIDI_PATCHES 128 // 128 MIDI instruments -#define NUM_MIDI_PERC 128 // 46 MIDI percussive notes (channel 10), but 128 possible notes -#define INSTR_NAMELEN 32 // Maximum length of an instrument name - -//#define PITCHBEND_RANGE 12.0 // 12 == pitchbends can go up to a full octave -#define PITCHBEND_RANGE 24.0 // 24 == pitchbends can go up two full octaves -#define PITCHBEND_ONESEMITONE (8192.0 / PITCHBEND_RANGE) -const double pitchbend_center = 8192.0; - -#include "midiio.hpp" -#include -#include -#include -#include -#include -#include - -#define WRITE_BINARY "wb" -#define READ_TEXT "r" - -#ifdef _MSC_VER -// Keep MS VC++ happy -#define strncasecmp _strnicmp -#define strcasecmp _stricmp -#define snprintf _snprintf -#define log2(x) (log(x) / log(2.)) -inline double round( double d ) -{ -return floor( d + 0.5 ); -} -#endif - -bool bRhythm = true; // convert rhythm mode instruments (-r) -bool bUsePitchBends = true; // use pitch bends to better match MIDI note frequency with the OPL frequency (-p) -bool bApproximatePitchbends = false; // if pitchbends are disabled, should we approximate them by playing the nearest note when the pitch changes? -bool bPerfectMatchesOnly = false; // if true, only match perfect instruments -bool bEnableVolume = true; // enable note velocity based on OPL instrument volume -bool bWriteSbiInstruments = false; // write detected instruments to .SBI files - -// Rhythm instruments -enum RHYTHM_INSTRUMENT { - NormalInstrument, - BassDrum, - SnareDrum, - TomTom, - TopCymbal, - HiHat -}; - -// MIDI channels to use for these instruments when they are mapped as normal -// notes. Probably best not to use 1-10 as these are used by the rest of the -// OPL mapping code. Note these are zero-based, so the GM drum channel is -// channel 9 in this context. -#define CHAN_BASSDRUM 10 // Bass drum sits on OPL channel 7 -#define CHAN_SNAREDRUM 11 // OPL channel 8 carrier -#define CHAN_TOMTOM 12 // OPL channel 9 modulator -#define CHAN_TOPCYMBAL 13 // OPL channel 9 carrier -#define CHAN_HIHAT 14 // OPL channel 8 modulator - -char cPatchName[NUM_MIDI_PATCHES][INSTR_NAMELEN]; -char cPercName[NUM_MIDI_PERC][INSTR_NAMELEN]; - -char* input = 0; -char* output = 0; - -FILE* f = 0; // input file -MidiWrite* write = 0; - -//int resolution = 384; // 560Hz IMF -int resolution = 500; // 1000Hz DRO -int program = 0; -float tempo = 120.0; - -// Arrays of [9] refer to OPL channels, arrays of [16] also refer to OPL -// channels but use fake channels (11-15) for rhythm mode instruments (but -// only for elements like keyon and instrument mapping that can happen -// independently of the OPL channel in use. Things like OPL channel pitch -// which affect both rhythm mode instruments using that channel are not -// stored separately, i.e. they're in the [9] array.) -int mapchannel[16]; -int curfreq[9]; -bool keyAlreadyOn[16]; -int lastkey[16]; // last MIDI key pressed on this channel -int pitchbent[16]; -int transpose[16]; // used for instruments with mapped transpose values -int drumnote[16]; // note to play on MIDI channel 10 if Adlib channel has a - // percussive instrument assigned to it -int lastprog[16]; // last program/patch set on the MIDI channel -bool mute[16]; // true if the instrument on this channel is currently muted - -// Statistics -int iNotesActive = 0; -int iPitchbendCount = 0; -int iTotalNotes = 0; - -typedef struct -{ - /*unsigned char reg20[2]; - unsigned char reg40[2]; - unsigned char reg60[2]; - unsigned char reg80[2]; - unsigned char regC0; - unsigned char regE0[2];*/ - - unsigned int reg20[2]; - unsigned int reg40[2]; - unsigned int reg60[2]; - unsigned int reg80[2]; - unsigned int regC0; - unsigned int regE0[2]; - - // Is this a normal instrument or a mapping for a rhythm instrument? - RHYTHM_INSTRUMENT eRhythmInstrument; - - int prog; // MIDI patch (or -1 if a drum) - int isdrum; - int note; // note to play if drum - bool muted; // true if this instrument is muted - char name[128]; - - signed int iTranspose; // number of semitones to transpose this instrument - - // The instrument can be redirected to another. This is used when a new - // instrument is encountered - its info is printed to the screen then it's - // recorded (pointing to its best match) to prevent it appearing as a new - // instrument hundreds of times and cluttering the output. - int redirect; // if >= 0, use ::instr[redirect] instead of this one - - // When using this struct to cache current channel parms we need to - // remember the octave, so that when registers 0xA0-0xA8 are changed - // (to change note frequency) we can still pass the octave to the note - // conversion function (as the octave is otherwise only available when - // setting registers 0xB0-0xB8.) - int iOctave; -} INSTRUMENT; - -INSTRUMENT reg[9]; // current registers of channel - -#define MAXINSTR 2048 -int instrcnt = 0; -INSTRUMENT instr[MAXINSTR]; - -int iFormat = 0; // input format -#define FORMAT_IMF 1 -#define FORMAT_DRO 2 -#define FORMAT_RAW 3 -int iSpeed = 0; // clock speed (in Hz) -int iInitialSpeed = 0; // first iSpeed value written to MIDI header - -double dbConversionVal; -// Convert the given OPL F-num and octave values into a fractional MIDI note -// number (for the use of pitchbends.) -double freq2key(int freq, int octave) -{ - int iFNum = freq; - int iBlock = octave; - double dbOriginalFreq = dbConversionVal * (double)iFNum * pow(2, (double)(iBlock - 20)); - return 69.0 + 12.0 * log2(dbOriginalFreq / 440.0); -} - -void version() -{ - printf("DRO2MIDI v" VERSION " - Convert raw Adlib captures to General MIDI\n" - "Written by malvineous@shikadi.net in 2007\n" - "Heavily based upon IMF2MIDI written by Guenter Nagler in 1996\n" - "With contributions by Wraithverge (C) 2010, bsa in 2013\n" - "http://www.shikadi.net/utils/\n" - "\n" - ); - return; -} - -void usage() -{ - version(); - fprintf(stderr, - "Usage: dro2midi [-p [-a]] [-r] [-i] [-c alt|] [-v] input.dro output.mid\n" - "\n" - "Where:\n" - " -p Disable use of MIDI pitch bends\n" - " -a If pitchbends are disabled, approximate by playing the nearest note\n" - " -r Don't convert OPL rhythm-mode instruments\n" - " -i Only use instruments that match perfectly (default is 'close enough is\n" - " good enough.') Useful when guessing new patches. Instruments that can't\n" - " be matched use the first entry in " MAPPING_FILE " (piano by default)\n" - " -c Change the value used to convert OPL notes into MIDI notes from the\n" - " default 49716. Any non-zero value can be specified. The special word\n" - " \"alt\" means 50000, the other commonly used value. It is unlikely any\n" - " other values will need to be used, unless you get an excessive amount\n" - " of artificial pitchbends in the output MIDI.\n" - " -v Disable note velocity and play all notes as loudly as possible,\n" - " instead of trying to match the volume of the OPL note.\n" - " -s Write detected instruments to .sbi files\n" - " (Creative Sound Blaster Instrument).\n" - "\n" - "Supported input formats:\n" - " .raw Rdos RAW OPL capture\n" - " .dro DOSBox RAW OPL capture\n" - " .imf id Software Music Format (type-0 and type-1 at 560Hz)\n" - " .wlf id Software Music Format (type-0 and type-1 at 700Hz)\n" - "\n" - "Instrument definitions are read in from " MAPPING_FILE ". Instrument names\n" - "are read in from " PATCH_NAME_FILE " and " PERC_NAME_FILE ". See the README for more details.\n" - ); - exit(1); -} - -/* Channel 1 2 3 4 5 6 7 8 9 - * Operator 1 00 01 02 08 09 0A 10 11 12 } cells - * Operator 2 03 04 05 0B 0C 0D 13 14 15 } - */ -// Converts a cell into a channel -#define GET_CHANNEL(i) ((((i) / 8) * 3) + (((i) % 8) % 3)) - -// Converts a cell into 0 (for operator 1) or 1 (for operator 2) -#define GET_OP(i) (((i) % 8) / 3) - -// Helper functions to read data from files in a non-optimised but platform -// independent (little/big endian) way. -inline unsigned char readByte(FILE *f) -{ - unsigned char c; - fread(&c, 1, 1, f); - return c; -} -inline unsigned short readUINT16LE(FILE *f) -{ - unsigned char c[2]; - fread(&c, 1, 2, f); - return c[0] | (c[1] << 8L); -} -inline unsigned long readUINT32LE(FILE *f) -{ - unsigned char c[4]; - fread(&c, 1, 4, f); - return c[0] | (c[1] << 8L) | (c[2] << 16L) | (c[3] << 24L); -} - -bool loadInstruments(void) -{ - for (int i = 0; i < NUM_MIDI_PATCHES; i++) sprintf(cPatchName[i], "Patch #%d", i+1); - for (int i = 0; i < NUM_MIDI_PERC; i++) sprintf(cPercName[i], "Note #%d", i); - - char line[256]; - - FILE* p = fopen(PATCH_NAME_FILE, "r"); - if (!p) { - fprintf(stderr, "Warning: Unable to open file listing patch names (" - PATCH_NAME_FILE ")\nInstrument names will not be available.\n"); - } else { - while (fgets(line, sizeof(line)-1, p)) { - int iValue, iLen; - char *p = strpbrk(line, "\n\r"); - if (p) *p = '\0'; // terminate the string at the newline - if (sscanf(line, "%d=%n", &iValue, &iLen) == 1) { - assert(iValue <= NUM_MIDI_PATCHES); - snprintf(cPatchName[iValue-1], INSTR_NAMELEN, "%s [%d]", &line[iLen], iValue); - } else if ((line[0] != '#') && (line[0] != '\n')) { - fprintf(stderr, "Invalid line in " PATCH_NAME_FILE ": %s\n", line); - } - } - fclose(p); - } - - p = fopen(PERC_NAME_FILE, "r"); - if (!p) { - fprintf(stderr, "Warning: Unable to open file listing percussion note " - "names (" PERC_NAME_FILE ")\nPercussion names will not be available.\n"); - } else { - while (fgets(line, sizeof(line)-1, p)) { - int iValue, iLen; - char *p = strpbrk(line, "\n\r"); - if (p) *p = '\0'; // terminate the string at the newline - if (sscanf(line, "%d=%n", &iValue, &iLen) == 1) { - assert(iValue <= NUM_MIDI_PERC); - snprintf(cPercName[iValue], INSTR_NAMELEN, "%s [%d]", &line[iLen], iValue); - } else if ((line[0] != '#') && (line[0] != '\n')) { - fprintf(stderr, "Invalid line in " PERC_NAME_FILE ": %s\n", line); - } - } - fclose(p); - } - - FILE* f = fopen(MAPPING_FILE, "r"); - if (!f) { - fprintf(stderr, "Warning: Unable to open instrument mapping file " - MAPPING_FILE ", defaulting to a Grand Piano\nfor all instruments.\n"); - return true; - } - INSTRUMENT in; - memset(&in, 0, sizeof(in)); - in.redirect = -1; // none of these should redirect (but later automatic - // instruments will redirect to these ones) - - // Loop until we run out of lines in the data file or we hit the maximum - // number of instruments - char value[256]; - int iLineNum = 0; - for (::instrcnt = 0; fgets(line, sizeof(line)-1, f) && (instrcnt < MAXINSTR);) { - iLineNum++; - - // Ignore blank lines and comments - if ((line[0] == '#') || (line[0] == '\r') || (line[0] == '\n')) continue; - - // Figure out what type of rhythm mode instrument (if any) the first two - // chars are referring to. - char cInstType[3]; - int iNumFields = sscanf(line, "%2s ", cInstType); - if ((cInstType[0] == 'N') && (cInstType[1] == 'O')) in.eRhythmInstrument = NormalInstrument; - else if ((cInstType[0] == 'B') && (cInstType[1] == 'D')) in.eRhythmInstrument = BassDrum; - else if ((cInstType[0] == 'S') && (cInstType[1] == 'D')) in.eRhythmInstrument = SnareDrum; - else if ((cInstType[0] == 'T') && (cInstType[1] == 'T')) in.eRhythmInstrument = TomTom; - else if ((cInstType[0] == 'T') && (cInstType[1] == 'C')) in.eRhythmInstrument = TopCymbal; - else if ((cInstType[0] == 'H') && (cInstType[1] == 'H')) in.eRhythmInstrument = HiHat; - else { - fprintf(stderr, "Invalid instrument type \"%s\" on line %d:\n\n %s\n", - cInstType, iLineNum, line); - return false; - } - - switch (in.eRhythmInstrument) { - case NormalInstrument: - case BassDrum: - // Normal instrument or rhythm Bass Drum, read both - // operators + connection byte - iNumFields = sscanf(&line[3], "%02X-%02X/%02X-%02X/%02X-%02X/%02X-%02X" - "/%02X/%02X-%02X: %255c\n", - &in.reg20[0], &in.reg20[1], - &in.reg40[0], &in.reg40[1], - &in.reg60[0], &in.reg60[1], - &in.reg80[0], &in.reg80[1], - &in.regC0, - &in.regE0[0], &in.regE0[1], value); - if (iNumFields != 12) { - fprintf(stderr, "Unable to parse line %d: (expected 12 instrument " - "fields, got %d)\n\n%s\n", iLineNum, iNumFields, line); - return false; - } - break; - - case TomTom: - case HiHat: - // This instrument is one operator only, but it does use the connection - // byte (probably) - iNumFields = sscanf(&line[3], "%02X/%02X/%02X/%02X/%02X/%02X: %255c\n", - &in.reg20[0], - &in.reg40[0], - &in.reg60[0], - &in.reg80[0], - &in.regC0, - &in.regE0[0], value); - if (iNumFields != 7) { - fprintf(stderr, "Unable to parse line %d: (expected 7 instrument " - "fields, got %d)\n\n%s\n", iLineNum, iNumFields, line); - return false; - } - break; - - case SnareDrum: - case TopCymbal: - // This instrument does not uses the connection byte, so read in one byte - // less. Also read the values into the other operator. - iNumFields = sscanf(&line[3], "%02X/%02X/%02X/%02X/%02X: %255c\n", - &in.reg20[1], - &in.reg40[1], - &in.reg60[1], - &in.reg80[1], - &in.regE0[1], value); - if (iNumFields != 6) { - fprintf(stderr, "Unable to parse line %d: (expected 6 instrument " - "fields, got %d)\n\n%s\n", iLineNum, iNumFields, line); - return false; - } - break; - } - - // Default options - in.isdrum = 0; - in.prog = 1; - in.iTranspose = 0; - in.muted = false; - - // If we got this far it's a valid instrument - int iValue; - char nextopt[256]; - // We need to manually terminate the %255c in the sscanf() above, so do - // this by terminating it at the end-of-line char. Hopefully reading past - // the end of the input string (which causes this) won't crash things... - for (int i = 0; i < 256; i++) { - if (value[i] == '\n') value[i] = 0; - else if (value[i] == '\0') break; - } - value[255] = 0; - - const char *cList = value; - int iLen; - while (sscanf(cList, "%s%n", nextopt, &iLen) >= 1) { - cList += iLen; - if (nextopt[0] == '#') break; // reached an end of line comment - if (nextopt[0] == ' ') continue; // skip double spaces - if (sscanf(nextopt, "patch=%d", &iValue) == 1) { - // MIDI patch - in.isdrum = 0; - in.prog = iValue - 1; - if ((in.prog < 0) || (in.prog > 127)) { - fprintf(stderr, "ERROR: Instrument #%d (line %d) was set to " - "patch=%d, but this value must be between 1 and 128 inclusive.\n", - instrcnt, iLineNum, in.prog + 1); - return false; - } - } else if (sscanf(nextopt, "drum=%d", &iValue) == 1) { - // MIDI drum - in.isdrum = 1; - in.prog = -1; - in.note = iValue; - if ((in.note < 0) || (in.note > 127)) { - fprintf(stderr, "ERROR: Drum instrument #%d (line %d) was set to " - "drum=%d, but this value must be between 1 and 128 inclusive.\n", - instrcnt, iLineNum, in.note); - return false; - } - } else if (sscanf(nextopt, "transpose=%d", &iValue) == 1) { - // MIDI drum - in.iTranspose = iValue; - } else if (strcmp(nextopt, "mute") == 0) { - // Mute this instrument - in.muted = true; - } else { - fprintf(stderr, "Unknown instrument option on line %d: %s\n", - iLineNum, nextopt); - return false; - } - } - - char cInstTypeText[256]; - switch (in.eRhythmInstrument) { - case NormalInstrument: cInstTypeText[0] = '\0'; break; - case BassDrum: strcpy(cInstTypeText, "(OPL BD) "); break; - case TomTom: strcpy(cInstTypeText, "(OPL TT) "); break; - case HiHat: strcpy(cInstTypeText, "(OPL HH) "); break; - case SnareDrum: strcpy(cInstTypeText, "(OPL SD) "); break; - case TopCymbal: strcpy(cInstTypeText, "(OPL TC) "); break; - } - sprintf(in.name, "Inst#%03d %s@ line %3d%s: %s", instrcnt, - cInstTypeText, - iLineNum, - (in.isdrum) ? " (perc)" : "", - (in.isdrum) ? cPercName[in.note] : cPatchName[in.prog] - ); - if (in.iTranspose) { - char cTranspose[256]; - sprintf(cTranspose, " @ %+d semitones", in.iTranspose); - strcat(in.name, cTranspose); - } - if (in.muted) strcat(in.name, " {muted}"); - //printf("%s\n", in.name); - - // Add instrument - ::instr[instrcnt++] = in; - } - fclose(f); - return true; -} - -long difference(int a, int b, int importance = 1) -{ - long diff = a - b; - if (diff < 0) diff = -diff; - return diff * importance; -} - -long compareinstr(INSTRUMENT& a, INSTRUMENT& b, RHYTHM_INSTRUMENT ri) -{ - // Note that we're not using b.eRhythmInstrument below as "b" refers to the - // OPL channel, and this never has an instrument type set. The instrument - // type we want is passed in as "ri", so we compare against that instead. - //fprintf(stderr, "%d ", ri); - switch (ri) { - case NormalInstrument: - case BassDrum: - // Compare the full register set (both operators plus the connection - // byte) for normal instruments (which occupy a whole channel) and the - // one rhythm mode instrument which also occupies a whole channel. - return - difference(a.eRhythmInstrument, ri, 4) + - difference(a.reg20[0], b.reg20[0], 2) + - difference(a.reg20[1], b.reg20[1], 2) + - difference(a.reg40[0], b.reg40[0], 1) + - difference(a.reg40[1], b.reg40[1], 1) + - difference(a.reg60[0], b.reg60[0], 2) + - difference(a.reg60[1], b.reg60[1], 2) + - difference(a.reg80[0], b.reg80[0], 2) + - difference(a.reg80[1], b.reg80[1], 2) + - difference(a.regC0, b.regC0, 3) + - difference(a.regE0[0], b.regE0[0], 1) + - difference(a.regE0[1], b.regE0[1], 1); - case TomTom: - case HiHat: - // These two rhythm instruments only use one operator's settings, the - // settings of the other operator (should) be ignored. There is also - // only one Connection byte, but there is no documentation to say whether - // this applies to these modulator-only rhythm instruments or to the - // other carrier-only ones (below.) I'm guessing and putting it here. - return - difference(a.eRhythmInstrument, ri, 4) + - difference(a.reg20[0], b.reg20[0], 2) + - difference(a.reg40[0], b.reg40[0], 1) + - difference(a.reg60[0], b.reg60[0], 2) + - difference(a.reg80[0], b.reg80[0], 2) + - difference(a.regC0, b.regC0, 3) + - difference(a.regE0[0], b.regE0[0], 1); - case SnareDrum: - case TopCymbal: - // These instruments only use one operator - but the other one compared - // to the previous set above. They also don't use the single Connection - // byte (I think.) - return - difference(a.eRhythmInstrument, ri, 4) + - difference(a.reg20[1], b.reg20[1], 2) + - difference(a.reg40[1], b.reg40[1], 1) + - difference(a.reg60[1], b.reg60[1], 2) + - difference(a.reg80[1], b.reg80[1], 2) + - difference(a.regE0[1], b.regE0[1], 1); - } -} - -void writesbi(const char* filename, int instrno, int chanOPL) { - char fname[100]; - char title[32]; - snprintf(fname, 100, "%s_%03d.sbi", filename, instrno); - FILE* f_sbi = fopen(fname, WRITE_BINARY); - if (!f_sbi) { - fprintf(stderr, "Could not open instrument file %s for writing.\n", filename); - } else { - fwrite("SBI\x1a", sizeof(char), 4, f_sbi); - memset(title, 0, 32); - snprintf(title, 32, "dro2midi_%03d", instrno); - fwrite(title, sizeof(char), 32, f_sbi); - unsigned char instr[16]; - memset(instr, 0, 16); - instr[0] = (unsigned char)reg[chanOPL].reg20[0]; - instr[1] = (unsigned char)reg[chanOPL].reg20[1]; - instr[2] = (unsigned char)reg[chanOPL].reg40[0]; - instr[3] = (unsigned char)reg[chanOPL].reg40[1]; - instr[4] = (unsigned char)reg[chanOPL].reg60[0]; - instr[5] = (unsigned char)reg[chanOPL].reg60[1]; - instr[6] = (unsigned char)reg[chanOPL].reg80[0]; - instr[7] = (unsigned char)reg[chanOPL].reg80[1]; - instr[8] = (unsigned char)reg[chanOPL].regE0[0]; - instr[9] = (unsigned char)reg[chanOPL].regE0[1]; - instr[10] = (unsigned char)reg[chanOPL].regC0; - fwrite(instr, sizeof(char), 16, f_sbi); - fclose(f_sbi); - } -} - -int findinstr(int chanMIDI) -{ - assert((chanMIDI < 9) || ((chanMIDI >= CHAN_BASSDRUM) && (chanMIDI <= CHAN_HIHAT))); - - RHYTHM_INSTRUMENT ri; - int chanOPL; - switch (chanMIDI) { - case CHAN_BASSDRUM: ri = BassDrum; chanOPL = 6; break; - case CHAN_SNAREDRUM: ri = SnareDrum; chanOPL = 7; break; - case CHAN_TOMTOM: ri = TomTom; chanOPL = 8; break; - case CHAN_TOPCYMBAL: ri = TopCymbal; chanOPL = 8; break; - case CHAN_HIHAT: ri = HiHat; chanOPL = 7; break; - default: ri = NormalInstrument; chanOPL = chanMIDI; break; - } - - int besti = -1; - long bestdiff = -1; - for (int i = 0; i < instrcnt; i++) { - long diff = compareinstr(instr[i], reg[chanOPL], ri); - if (besti < 0 || diff < bestdiff) { - bestdiff = diff; - besti = i; - if (bestdiff == 0) break; - } - } - - if (besti >= 0) { // could be -1 if no instruments are loaded - while (instr[besti].redirect >= 0) { // Could have multiple redirects - // This instrument was an automatically generated one to avoid printing - // the instrument definition multiple times, so instead of using the auto - // one, use the one it originally matched against. - besti = instr[besti].redirect; - } - } - - if (bestdiff != 0) { - if (::bPerfectMatchesOnly) { - // User doesn't want "close enough is good enough" instrument guessing - besti = 0; // use first instrument - } - // Couldn't find an exact match, print the details - switch (ri) { - case NormalInstrument: - case BassDrum: - // Normal instrument or rhythm Bass Drum use both - // operators + connection byte - printf("** New instrument in use on channel %d\n** Copy this into " - MAPPING_FILE " to assign it a MIDI patch:\n", chanOPL); - fprintf(stderr, "%s %02X-%02X/%02X-%02X/%02X-%02X/%02X-%02X/%02X/" - "%02X-%02X: patch=?\n", - ((ri == BassDrum) ? "BD" : "NO"), - reg[chanOPL].reg20[0], reg[chanOPL].reg20[1], - reg[chanOPL].reg40[0], reg[chanOPL].reg40[1], - reg[chanOPL].reg60[0], reg[chanOPL].reg60[1], - reg[chanOPL].reg80[0], reg[chanOPL].reg80[1], - reg[chanOPL].regC0, - reg[chanOPL].regE0[0], reg[chanOPL].regE0[1] - ); - if (::bWriteSbiInstruments) { - writesbi(output, instrcnt, chanOPL); - } - break; - case TomTom: - case HiHat: - // This instrument is one operator only, but it does use the connection - // byte (probably) - printf("** New rhythm instrument in use on OPL channel %d modulator\n" - "** Copy this into " MAPPING_FILE " to assign it a MIDI patch:\n", - chanOPL); - fprintf(stderr, "%s %02X/%02X/%02X/%02X/%02X/%02X: " - "patch=?\n", - ((ri == TomTom) ? "TT" : "HH"), - reg[chanOPL].reg20[0], - reg[chanOPL].reg40[0], - reg[chanOPL].reg60[0], - reg[chanOPL].reg80[0], - reg[chanOPL].regC0, - reg[chanOPL].regE0[0] - ); - break; - case SnareDrum: - case TopCymbal: - // This instrument does not uses the connection byte, so read in one - // byte less. Also read the values into the other operator. - // This instrument is one operator only, but it does use the connection - // byte (probably) - printf("** New rhythm instrument in use on OPL channel %d carrier\n" - "** Copy this into " MAPPING_FILE " to assign it a MIDI patch:\n", - chanOPL); - fprintf(stderr, "%s %02X/%02X/%02X/%02X/%02X: " - "patch=?\n", - ((ri == SnareDrum) ? "SD" : "TC"), - reg[chanOPL].reg20[1], - reg[chanOPL].reg40[1], - reg[chanOPL].reg60[1], - reg[chanOPL].reg80[1], - reg[chanOPL].regE0[1] - ); - break; - } - - printf(">> Using similar match: %s\n", instr[besti].name); - // Save this unknown instrument as a known one, so the same registers don't get printed again -// reg[channel].prog = instr[besti].prog; // but keep the same patch that we've already assigned to the instrument, so it doesn't drop back to a piano for the rest of the song - // Maybe ^ isn't necessary if we're redirecting? - instr[instrcnt] = reg[chanOPL]; - instr[instrcnt].eRhythmInstrument = ri; - if (besti >= 0) { - instr[instrcnt].redirect = besti; // Next time this instrument is matched, use the original one instead - } else { - instr[instrcnt].redirect = -1; // Will only happen when no instruments are loaded - } - instrcnt++; - } - return besti; -} - -// Function for processing OPL note on and off events, and generating MIDI -// events in response. This function is also called when the pitch changes -// while a note is currently being played, causing it to generate MIDI -// pitchbends (if enabled) instead. -// Normally chanOPL and chanMIDI will be the same, except for rhythm mode -// instruments, which uses chanOPL for pitch and instrument patches, but -// chanMIDI for instrument mapping and note on/off events (since there will be -// two instrument maps and notes for a single OPL channel.) -void doNoteOnOff(bool bKeyOn, int chanOPL, int chanMIDI) -{ - double keyFrac = freq2key(curfreq[chanOPL], reg[chanOPL].iOctave); - int key = (int)round(keyFrac); - if ((key > 0) && (bKeyOn)) { - // This is set to true to forcibly stop a MIDI keyon being generated for - // this note. This is done when a pitchbend is deemed as having done the - // job properly. - bool bKeyonAgain = true; - - if (keyAlreadyOn[chanMIDI]) { - // There's already a note playing on this channel, just worry about the pitch of that - - if (mapchannel[chanMIDI] != gm_drumchannel) { - // We're using a normal instrument here - - if (::bUsePitchBends) { - // It's the same note, but the pitch is off just slightly, use a pitchbend - //double dbDiff = fabs(keyFrac - key); // should be between -0.9999 and 0.9999 - double dbDiff = keyFrac - (double)(lastkey[chanMIDI] - transpose[chanMIDI]); // hopefully between -PITCHBEND_RANGE and PITCHBEND_RANGE - - if (dbDiff > PITCHBEND_RANGE) { - fprintf(stderr, "Warning: This song wanted to pitchbend by %.2f notes, but the maximum is %.1f\n", dbDiff, PITCHBEND_RANGE); - - // Turn this note off - write->noteoff(mapchannel[chanMIDI], lastkey[chanMIDI]); - ::iNotesActive--; - lastkey[chanMIDI] = -1; - keyAlreadyOn[chanMIDI] = false; - // leave bKeyonAgain as true, so that a noteon will be played instead - } else { - int iNewBend = (int)(pitchbend_center + (PITCHBEND_ONESEMITONE * dbDiff)); - if (iNewBend != pitchbent[chanMIDI]) { - //printf("pitchbend to %d/%.2lf (center + %d) (%.2lf " - // "semitones)\n", iNewBend, (double)pitchbend_center*2, - // (int)(iNewBend - pitchbend_center), (double)dbDiff); - write->pitchbend(mapchannel[chanMIDI], iNewBend); -// ::iPitchbendCount++; - pitchbent[chanMIDI] = iNewBend; - } - // This pitchbend has done the job, don't play a noteon - bKeyonAgain = false; - } - } else { - // We're not using pitchbends, so just switch off the note if it's different (the next one will play below) - if ((::bApproximatePitchbends) && (key != (lastkey[chanMIDI] - transpose[chanMIDI]))) { - write->noteoff(mapchannel[chanMIDI], lastkey[chanMIDI]); - ::iNotesActive--; - lastkey[chanMIDI] = -1; - keyAlreadyOn[chanMIDI] = false; - //bKeyonAgain = true; - } else { - // Same note, different pitch, just pretend like it's not there - bKeyonAgain = false; - } - } - } else { - // This has mapped to a percussive MIDI note, so no pitchbends (or - // we'll bend all the percussive notes on the MIDI percussion channel.) - // But we don't want to play the note again, 'cos it's already on, so - // just ignore the keyon event. - // We'll also end up here if the instrument parameters are changed - // while the instrument is sounding, e.g. to change the characteristics - // of a hihat without sounding a new note. This won't be converted. - bKeyonAgain = false; - } - } // else this is a percussive instrument - - //} else { - //if ((!bDontKeyonAgain) && ((!keyAlreadyOn[channel]) || (::bUsePitchBends))) { // If *now* there's no note playing... (or we're using pitchbends, i.e. a portamento has been set up) - if (bKeyonAgain) { // If *now* there's no note playing... (or we're using pitchbends, i.e. a portamento has been set up) - // See if we need to update anything - - // See if the instrument needs to change - int i = findinstr(chanMIDI); - if ( - (i >= 0) && ( - (instr[i].prog != lastprog[chanMIDI]) || - ( - (instr[i].isdrum) && - (drumnote[chanMIDI] != instr[i].note) - ) || ( - // Same instrument mapping, but different mute setting? - (instr[i].muted != mute[chanMIDI]) - ) - ) - ) { - printf("// Ch%02d <- %s\n", chanMIDI, instr[i].name); - if (!instr[i].isdrum) { - // Normal instrument (not MIDI percussion) - assert(instr[i].prog >= 0); - - if (mapchannel[chanMIDI] == gm_drumchannel) { - // This was playing drums, now we're back to normal notes - - // make sure this sets things back to what they were in the init - // section in main() - mapchannel[chanMIDI] = chanMIDI; - drumnote[chanMIDI] = -1; // NOTE: This drumnote won't be reset if the drum instrument was muted! (As it then wouldn't have been assigned to gm_drumchannel) - } - - transpose[chanMIDI] = instr[i].iTranspose; - write->program(mapchannel[chanMIDI], lastprog[chanMIDI] = instr[i].prog); - } else { - // This new instrument is a drum - assert(instr[i].prog == -1); - - /*if (instr[i].muted) { - // This instrument is muted, which means whichever channel we - // assign it to will become muted. We can't therefore assign it to - // the drum channel as we normally would, otherwise all the MIDI - // percussion will become muted. So we assign it to its normal - // channel instead, like we would with a non-percussion instrument. - mapchannel[chanMIDI] = chanMIDI; - } else { - mapchannel[chanMIDI] = gm_drumchannel; - }*/ - mapchannel[chanMIDI] = gm_drumchannel; - drumnote[chanMIDI] = instr[i].note; - lastprog[chanMIDI] = instr[i].prog; - // drums don't use transpose values - } - mute[chanMIDI] = instr[i].muted; - } - - // Play the note - //if ((::bUsePitchBends) && (!keyAlreadyOn[channel])) { - if ((::bUsePitchBends) && (mapchannel[chanMIDI] != gm_drumchannel)) { // If pitchbends are enabled and this isn't a percussion instrument - double dbDiff = keyFrac - key; // should be between -0.9999 and 0.9999 - assert(dbDiff < PITCHBEND_RANGE); // not really necessary... - - int iNewBend = (int)(pitchbend_center + (PITCHBEND_ONESEMITONE * dbDiff)); - if (iNewBend != pitchbent[chanMIDI]) { - //printf("new note at pitchbend %d\n", iNewBend); - write->pitchbend(mapchannel[chanMIDI], iNewBend); // pitchbends are between 0x0000L and 0x2000L -// ::iPitchbendCount++; - pitchbent[chanMIDI] = iNewBend; - } - } - - int level; - if (!mute[chanMIDI]) { - if (::bEnableVolume) { - level = reg[chanOPL].reg40[1] & 0x3f; - if (level > 0x30) level = 0x30; // don't allow fully silent notes - } else level = 0; // 0 == loudest - } else { - level = 0x3f; // silent - } - - if (mapchannel[chanMIDI] != gm_drumchannel) { - // Normal note - lastkey[chanMIDI] = key + transpose[chanMIDI]; - } else { - // Percussion - //write->noteon(gm_drumchannel, drumnote[chanMIDI], (0x3f - level) << 1); - lastkey[chanMIDI] = drumnote[chanMIDI]; - } - - write->noteon(mapchannel[chanMIDI], lastkey[chanMIDI], (0x3f - level) << 1); - //printf("note on chan %d, mute is %s\n", chanMIDI, mute[chanMIDI] ? "true" : "false"); - ::iNotesActive++; - ::iTotalNotes++; - - // If this note went on with a pitchbend active on the channel, count it - if (pitchbent[chanMIDI] != pitchbend_center) ::iPitchbendCount++; - - keyAlreadyOn[chanMIDI] = true; - - } // if (not muted) - - } else { - // There's no note currently playing on this channel, so if we've still got - // one switch it off. - if (lastkey[chanMIDI] != -1) { - write->noteoff(mapchannel[chanMIDI], lastkey[chanMIDI]); - ::iNotesActive--; - lastkey[chanMIDI] = -1; - keyAlreadyOn[chanMIDI] = false; - } - } - - return; -} - -int main(int argc, char**argv) -{ - int c; - - // Defaults - ::dbConversionVal = 49716.0; - - argc--; argv++; - while (argc > 0 && **argv == '-') - { - if (strncasecmp(*argv, "-r", 2) == 0) { - ::bRhythm = false; - printf("Rhythm-mode instruments disabled.\n"); - } else if (strncasecmp(*argv, "-p", 2) == 0) { - ::bUsePitchBends = false; - printf("Pitchbends disabled.\n"); - } else if (strncasecmp(*argv, "-a", 2) == 0) { - ::bApproximatePitchbends = true; - } else if (strncasecmp(*argv, "-i", 2) == 0) { - ::bPerfectMatchesOnly = true; - printf("Only using exact instrument matches - approximations disabled!\n"); - } else if (strncasecmp(*argv, "-v", 2) == 0) { - ::bEnableVolume = false; - printf("Note velocity disabled, all notes will be played as loud as possible.\n"); - } else if (strncasecmp(*argv, "-s", 2) == 0) { - ::bWriteSbiInstruments = true; - } else if (strncasecmp(*argv, "-c", 2) == 0) { - argc--; argv++; - if (argc == 0) { - fprintf(stderr, "-c requires a parameter\n"); - usage(); - } - if (strncasecmp(*argv, "alt", 3) == 0) { - ::dbConversionVal = 50000.0; - } else { - // Use the given value - ::dbConversionVal = strtod(*argv, NULL); - if (::dbConversionVal == 0) { - fprintf(stderr, "-c requires a non-zero parameter\n"); - usage(); - } - } - } else if (strncasecmp(*argv, "--version", 9) == 0) { - version(); - return 0; - } else { - fprintf(stderr, "invalid option %s\n", *argv); - usage(); - } - argc--; argv++; - } - if (argc < 2) usage(); - - if ((::bUsePitchBends) && (::bApproximatePitchbends)) { - fprintf(stderr, "ERROR: Pitchbends can only be approximated (-a) if " - "proper MIDI pitchbends are disabled (-p)\n"); - return 1; - } - - input = argv[0]; - output = argv[1]; - if (strcmp(input, output) == 0) - { - fprintf(stderr, "cannot convert to same file\n"); - return 1; - } - - if (!loadInstruments()) return 1; - - - f = fopen(input, READ_BINARY); - if (!f) - { - perror(input); - return 1; - } - unsigned long imflen = 0; - - unsigned char cSig[9]; - fseek(f, 0, SEEK_SET); - //fgets(cSig, 9, f); - fread(cSig, 1, 9, f); - iSpeed = 0; - if (strncmp((char *)cSig, "DBRAWOPL", 8) == 0) { - ::iFormat = FORMAT_DRO; - fseek(f, 8, SEEK_SET); // Seek to "version" fields. - unsigned long version = readUINT32LE(f); - if (version == 0x10000) { - printf("Input file is in DOSBox DRO v1.0 format.\n"); - } else if (version == 0x2) { - printf("Input file is in DOSBox DRO v2.0 format, which is not supported.\n"); - return 2; - } else { - printf("Input file is in DOSBox DRO format, but an unknown version!\n"); - return 3; - } - ::iInitialSpeed = 1000; - - fseek(f, 16, SEEK_SET); // seek to "length in bytes" field - imflen = readUINT32LE(f); - } else if (strncmp((char *)cSig, "RAWADATA", 8) == 0) { - ::iFormat = FORMAT_RAW; - printf("Input file is in Rdos RAW format.\n"); - - // Read until EOF (0xFFFF is really the end but we'll check that during conversion) - fseek(f, 0, SEEK_END); - imflen = ftell(f); - - fseek(f, 8, SEEK_SET); // seek to "initial clock speed" field - ::iInitialSpeed = 1000; - int iClockSpeed = readUINT16LE(f); - if ((iClockSpeed == 0) || (iClockSpeed == 0xFFFF)) { - ::iSpeed = (int)18.2; // default to 18.2Hz...well, 18Hz thanks to rounding - } else { - ::iSpeed = (int)(1193180.0 / iClockSpeed); - } - } else { - ::iFormat = FORMAT_IMF; - if ((cSig[0] == 0) && (cSig[1] == 0)) { - printf("Input file appears to be in IMF type-0 format.\n"); - fseek(f, 0, SEEK_END); - imflen = ftell(f); - fseek(f, 0, SEEK_SET); - } else { - printf("Input file appears to be in IMF type-1 format.\n"); - imflen = cSig[0] + (cSig[1] << 8); - fseek(f, 2, SEEK_SET); // seek to start of actual OPL data - } - if (strcasecmp(&input[strlen(input)-3], "imf") == 0) { - printf("File extension is .imf - using 560Hz speed (rename to .wlf if " - "this is too slow)\n"); - ::iInitialSpeed = 560; - } else if (strcasecmp(&input[strlen(input)-3], "wlf") == 0) { - printf("File extension is .wlf - using 700Hz speed (rename to .imf if " - "this is too fast)\n"); - ::iInitialSpeed = 700; - } else { - printf("Unknown file extension - must be .imf or .wlf\n"); - return 3; - } - } - printf("Using conversion constant of %.1lf\n", ::dbConversionVal); - - write = new MidiWrite(output); - if (!write) { - fprintf(stderr, "out of memory\n"); - return 1; - } - if (!write->getf()) { - perror(output); - return 1; - } - if (iSpeed == 0) { - iSpeed = iInitialSpeed; - } - resolution = iInitialSpeed / 2; - write->head(/* version */ 0, /* track count updated later */0, resolution); - - write->track(); - write->tempo((long)(60000000.0 / tempo)); - write->tact(4,4,24,8); - - for (c = 0; c < 10; c++) { - mapchannel[c] = c; - write->resetctrlrs(mapchannel[c], 0); // Reset All Controllers (Ensures default settings upon every playback). - write->volume(mapchannel[c], 127); - write->balance(mapchannel[c], 64); // Usually called 'Pan'. - write->expression(mapchannel[c], 127); // Similar to 'Volume', but this is primarily used for volume damping. - } - - for (c = 0; c <= 8; c++) { - lastprog[c] = -1; - reg[c].iOctave = 0; - } - - int delay = 0; - int channel; - int code, param; - - for (c = 0; c < 9; c++) { - curfreq[c] = 0; - mapchannel[c] = c; // This can get reset when playing a drum and then a normal instrument on a channel - see instrument-change code below - keyAlreadyOn[c] = false; - lastkey[c] = -1; // last MIDI key pressed on this channel - pitchbent[c] = (int)pitchbend_center; - transpose[c] = 0; - drumnote[c] = 0; // probably not necessary... - mute[c] = false; - - if (::bUsePitchBends) { - write->control(mapchannel[c], 100, 0); // RPN LSB for "Pitch Bend Sensitivity" - write->control(mapchannel[c], 101, 0); // RPN MSB for "Pitch Bend Sensitivity" - write->control(mapchannel[c], 6, (int)PITCHBEND_RANGE); // Data for Pitch Bend Sensitivity (in semitones) - controller 38 can be used for cents in addition - write->control(mapchannel[c], 100, 0x7F); // RPN LSB for "Finished" - write->control(mapchannel[c], 101, 0x7F); // RPN MSB for "Finished" - } -// write->pitchbend(mapchannel[c], pitchbend_center); - } - // Rhythm-mode only channels - for (c = 10; c < 15; c++) { - keyAlreadyOn[c] = false; - mapchannel[c] = c; // as above, this will be changed later but must be - // eventually set back to this value here (or the instrument will jump - // channels unexpectedly.) - pitchbent[c] = (int)pitchbend_center; - lastkey[c] = -1; // last MIDI key pressed on this channel - transpose[c] = 0; - drumnote[c] = 0; // probably not necessary... - mute[c] = false; - } - - int iMinLen; // Minimum length for valid notes to still be present - switch (::iFormat) { - case FORMAT_IMF: iMinLen = 4; break; - case FORMAT_DRO: iMinLen = 2; break; - case FORMAT_RAW: iMinLen = 2; break; - } - - unsigned long iSize = imflen; // sometimes the counter wraps around, need this to stop it from happening - while ((imflen >= (unsigned long)iMinLen) && (imflen <= iSize)) { - - // Get the next OPL register and value from the input file - - switch (::iFormat) { - case FORMAT_IMF: - // Write the last iteration's delay (since the delay needs to come *after* the note) - write->time(delay); - - code = readByte(f); - param = readByte(f); - delay = readUINT16LE(f); - imflen -= 4; - break; - case FORMAT_DRO: - code = readByte(f); - imflen--; - switch (code) { - case 0x00: // delay (byte) - delay += 1 + readByte(f); - imflen--; - continue; - case 0x01: // delay (int) - delay += 1 + readUINT16LE(f); - imflen -= 2; - continue; - case 0x02: // use first OPL chip - case 0x03: // use second OPL chip - fprintf(stderr, "Warning: This song uses multiple OPL chips - this isn't yet supported!\n"); - continue; - case 0x04: // escape - code = readByte(f); - imflen--; - break; - } - param = readByte(f); - imflen--; - - // Write any delay (as this needs to come *before* the next note) - write->time(delay); - delay = 0; - break; - case FORMAT_RAW: - param = readByte(f); - code = readByte(f); - imflen -= 2; - switch (code) { - case 0x00: // delay - delay += param; - continue; - case 0x02: // control data - switch (param) { - case 0x00: { - if (delay != 0) { - // See below - we need to write out any delay at the old clock speed before we change it - write->time((delay * iInitialSpeed / ::iSpeed)); - delay = 0; - } - int iClockSpeed = readUINT16LE(f); - if ((iClockSpeed == 0) || (iClockSpeed == 0xFFFF)) { - printf("Speed set to invalid value, ignoring speed change.\n"); - } else { - ::iSpeed = (int)round(1193180.0 / iClockSpeed); - printf("Speed changed to %dHz\n", iSpeed); - } - imflen -= 2; - break; - } - case 0x01: - case 0x02: - printf("Switching OPL ports is not yet implemented!\n"); - break; - } - continue; - case 0xFF: - if (param == 0xFF) { - // End of song - imflen = 0; - continue; - } - break; - } - - // Write any delay (as this needs to come *before* the next note) - // Since our global clock speed is 1000Hz, we have to multiply this - // delay accordingly as the delay units are in the current clock speed. - // This calculation converts them into 1000Hz delay units regardless of - // the current clock speed. - if (delay != 0) write->time((delay * iInitialSpeed / ::iSpeed)); - //printf("delay is %d (ticks %d)\n", (delay * iInitialSpeed / ::iSpeed), delay); - delay = 0; - break; - - default: // should never happen - break; - - } // switch (::iFormat) - - // Convert the OPL register and value into a MIDI event - - if (code >= 0xa0 && code <= 0xa8) { // set freq bits 0-7 - channel = code-0xa0; - curfreq[channel] = (curfreq[channel] & 0xF00) + (param & 0xff); - if (keyAlreadyOn[channel]) { - param = 0x20; // bare noteon for code below - doNoteOnOff(true, channel, channel); - } - continue; - } else if (code >= 0xB0 && code <= 0xB8) { // set freq bits 8-9 and octave and on/off - channel = code - 0xb0; - curfreq[channel] = (curfreq[channel] & 0x0FF) + ((param & 0x03)<<8); - // save octave so we know what it is if we run 0xA0-0xA8 regs change code - // next (which doesn't have the octave) - reg[channel].iOctave = (param >> 2) & 7; - int keyon = (param >> 5) & 1; - doNoteOnOff(keyon, channel, channel); - } else if ((code == 0xBD) && (::bRhythm)) { - if ((param >> 5) & 1) { - // Bass Drum - doNoteOnOff((param >> 4) & 1, channel, CHAN_BASSDRUM); - doNoteOnOff((param >> 3) & 1, channel, CHAN_SNAREDRUM); - doNoteOnOff((param >> 2) & 1, channel, CHAN_TOMTOM); - doNoteOnOff((param >> 1) & 1, channel, CHAN_TOPCYMBAL); - doNoteOnOff( param & 1, channel, CHAN_HIHAT); - } - } else if (code >= 0x20 && code <= 0x35) { - channel = GET_CHANNEL(code-0x20); - reg[channel].reg20[GET_OP(code-0x20)] = param; - } else if (code >= 0x40 && code <= 0x55) { - channel = GET_CHANNEL(code-0x40); - reg[channel].reg40[GET_OP(code-0x40)] = param; - } else if (code >= 0x60 && code <= 0x75) { - channel = GET_CHANNEL(code-0x60); - reg[channel].reg60[GET_OP(code-0x60)] = param; - } else if (code >= 0x80 && code <= 0x95) { - channel = GET_CHANNEL(code-0x80); - reg[channel].reg80[GET_OP(code-0x80)] = param; - } else if (code >= 0xc0 && code <= 0xc8) { - channel = code-0xc0; - reg[channel].regC0 = param; - } else if (code >= 0xe0 && code <= 0xF5) { - channel = GET_CHANNEL(code-0xe0); - reg[channel].regE0[GET_OP(code-0xe0)] = param; - } - } - - for (c = 0; c < 10; c++) { - mapchannel[c] = c; - write->allnotesoff(mapchannel[c], 0); // All Notes Off (Ensures that even incomplete Notes will be switched-off per each MIDI channel at the end-of-playback). - } - - delete write; - fclose(f); - - // Display completion message and some stats - printf("\nConversion complete. Wrote %s\n\n Total pitchbent notes: %d\n" - " Total notes: %d\n Notes still active at end of song: %d\n\n", - output, ::iPitchbendCount, ::iTotalNotes, ::iNotesActive); - - return 0; -} diff --git a/dro2midi/gen_test_midi.cpp b/dro2midi/gen_test_midi.cpp deleted file mode 100644 index 99eb97d..0000000 --- a/dro2midi/gen_test_midi.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// -// gen_test_midi.cpp - by malvineous@shikadi.net -// -// Generate a MIDI file that plays one note from all 128 GM instruments (in -// order), followed by all the percussion notes. -// -// When this file is played through an OPL synthesiser capable of MIDI -// playback, then captured through DOSBox, it will provide a complete set of -// OPL patches that can easily be associated back to MIDI instruments. -// - -#include "midiio.hpp" -#include -#include -#include -#include -#include -#include - -#define WRITE_BINARY "wb" -#define READ_TEXT "r" - -int main(int argc, char**argv) -{ - if (argc != 2) { - fprintf(stderr, "no output filename given\n"); - return 1; - } - - MidiWrite *write = new MidiWrite(argv[1]); - if (!write) { - fprintf(stderr, "out of memory\n"); - return 1; - } - if (!write->getf()) { - perror(argv[1]); - return 1; - } - - float tempo = 120.0; - int resolution = 280; - write->head(/* version */ 0, /* track count updated later */0, resolution); - - write->track(); - write->tempo((long)(60000000.0 / tempo)); - write->tact(4,4,24,8); - - write->volume(1, 127); - write->volume(9, 127); - for (int i = 0; i < 128; i++) { - write->program(1, i); - write->noteon(1, 60, 127); - write->time(50); - write->noteoff(1, 60); - write->time(50); - } -/* - // Need to play a normal note for percussion to get DOSBox to start capturing - // OPL data! (Why hasn't my patch to fix that been incorporated yet?!) - write->noteon(1, 60, 127); - write->time(50); - write->noteoff(1, 60); - write->time(200); - for (int i = 35; i < 82; i++) { - write->noteon(9, i, 127); - write->time(50); - write->noteoff(9, i); - write->time(50); - }*/ - - delete write; - - return 0; -} diff --git a/dro2midi/make b/dro2midi/make deleted file mode 100644 index a452b9f..0000000 --- a/dro2midi/make +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -# Usage: -# -# ./make -# -# or if you have a cross compiler for i586-pc-mingw32 installed -# -# ./make i586-pc-mingw32 .exe -# - -if [ "$1" != "" ]; then - PLATFORM="$1"- - TARGET="dro2midi$2" -else - PLATFORM="" - TARGET="dro2midi" -fi - -${PLATFORM}g++ -o ${TARGET} dro2midi.cpp midiio.cpp && - ${PLATFORM}strip ${TARGET} diff --git a/dro2midi/midiio.cpp b/dro2midi/midiio.cpp deleted file mode 100644 index 618e8ce..0000000 --- a/dro2midi/midiio.cpp +++ /dev/null @@ -1,2161 +0,0 @@ -// midiio.cpp written by Günter Nagler 1995 (gnagler@ihm.tu-graz.ac.at) -#include "midiio.hpp" -#include -#ifdef __MSDOS__ -#include -#endif -#include -#include - -static const char* copyright = "midiio v1.4 (c) 1995 by Günter Nagler (" __DATE__ ")"; - -int compress = 1; - -#define NOTREALISTIC_PAUSE 0x1000000UL - -// common sysex events -unsigned char sysex_gmreset[] = { 0xF0, 0x05, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; -unsigned char sysex_gsreset[] = { 0xF0, 0x0A, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 }; -unsigned char sysex_gsexit[] = { 0xF0, 0x0A, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x7F, 0x42, 0xF7 }; -unsigned char sysex_xgreset[] = { 0xF0, 0x08, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 }; - -static int issysex(const unsigned char* sysex, const unsigned char* sysdata, int syslen) -{ - if (*sysex == 0xF0) - sysex++; - while (syslen > 0) - { - if (*sysex != *sysdata) - return 0; - syslen--; - if (*sysex == 0xF7) - break; - sysex++; - sysdata++; - } - return (syslen != 0) ? 0 : 1; -} - -static int sysexlen(unsigned const char* sysex) -{ -int len = 0; - - len = 0; - while (*sysex != 0xF7) - { - sysex++; - len++; - } - return len+1; // incl. F7 -} - -// class MidiRead - -const char* MidiRead::copyright() -{ - return (const char*)::copyright; -} - -MidiRead::MidiRead(const char* filename, FILE* f) -{ - midiname_ = filename; - if (f) - { - f_ = f; - shouldclose_ = 0; - } - else - { - shouldclose_ = 1; - if (!filename) - f_ = 0; - else - f_ = fopen(filename, READ_BINARY); - } - - buflen_ = 0; - bufpos_ = 0; - curpos_ = 0; - pos_ = 0; - curchannel_ = NOCHANNEL; - curtime_ = 0; - options_ = 0; - if (f_) - { - fseek(f_, 0L, SEEK_END); - filesize_ = ftell(f_); - fseek(f_, 0L, SEEK_SET); - } - else - filesize_ = 0; - - version_ = tracks_ = clicks_ = trackno_ = 0; - tracklen_ = 0; -} - - -MidiRead::~MidiRead() -{ - if (f_ && shouldclose_) - fclose(f_); -} - -int MidiRead::runhead() -{ - if (!f_) - { - error("file not open"); - return 0; - } - seek(0); - if (getlong() != MThd) - { - error("missing midi header MThd"); - return 0; - } - - if (getlong() == 6) - { - version_ = getword(); - tracks_ = getword(); - clicks_ = getword(); - head(version_, tracks_, clicks_); - } - else - { - error("illegal midi header"); - return 0; - } - return 1; -} - -int MidiRead::run() -{ - pos_ = curpos_; - if (!runhead()) - return 0; - pos_ = curpos_; - for (trackno_ = 1; trackno_ <= tracks_; trackno_++) - if (!runtrack(trackno_)) - return 0; - if (curpos_ >= filesize_) - percent(percent_ = 100); - endmidi(); - return 1; -} - -int MidiRead::runevent(long trackend) -{ -int midicode; - - pos_ = curpos_; - - unsigned char *c = need(1); - if (!c || c[0] >= 0x80 || lastcode_ < 0) - midicode = getbyte(); - else - midicode = lastcode_; - - if (midicode < 0) - return 0; - - switch(midicode) - { - case 0xf0: // sysex - { - int syslen = scansysevent(trackend - curpos_); - if (!syslen) - { - error("end of sysex not found or sysex too large"); - return 0; - } - unsigned char* sysdata = get(syslen); - if ((options_ & OPTION_NOSYSEVENTS) == 0) - { - if (issysex(sysex_gmreset, sysdata, syslen)) - gmreset(); - else if (issysex(sysex_gsreset, sysdata, syslen)) - gsreset(); - else if (issysex(sysex_gsexit, sysdata, syslen)) - gsexit(); - else if (issysex(sysex_xgreset, sysdata, syslen)) - xgreset(); - else - sysex(syslen, sysdata); - } - else if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - sysex(syslen, sysdata); - else - event(0xf0, syslen, sysdata); - } - break; - case 0xf2: - { - c = get(2); if (!c) return 0; - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - songpos((unsigned(c[1]) << 7) + unsigned(c[0])); - else - event(0xf2, 2, c); - break; - } - case 0xf3: - c = get(1); if (!c) return 0; - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - songselect(*c); - else - event(0xf3, 1, c); - break; - case 0xf6: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - tunerequest(); - else - event(0xf6); - break; - case 0xf8: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - timingclock(); - else - event(0xf8); - break; - case 0xfa: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - start(); - else - event(0xfa); - break; - case 0xfb: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - cont(); - else - event(0xfb); - break; - case 0xfc: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - stop(); - else - event(0xfc); - break; - case 0xfe: - if ((options_ & OPTION_NOREALTIMEEVENTS) == 0) - activesense(); - else - event(0xfe); - break; - case 0xff: - { - int c; - unsigned long endpos; - int len; - - c = getbyte(); - len = (int)getdelta(); - endpos = curpos_ + len; - if (options_ & OPTION_NOREALTIMEEVENTS) - { - len += curdeltalen_ + 1; - seek(curpos_ - curdeltalen_ - 1); - event(0xff, len, get(len)); - } - else if (options_ & OPTION_NOMETAEVENTS) - meta(c, len, get(len)); - else - switch(c) - { - case 0: - if (len == 2) - seqnumber(getword()); - else - meta(c, len, get(len)); - break; - case meta_text: - text(c, len, "text", get(len)); break; - case meta_copyright: - text(c, len, "copyright", get(len)); break; - case meta_trackname: - text(c, len, "trackname", get(len)); break; - case meta_instrument: - text(c, len, "instrument", get(len)); break; - case meta_lyric: - text(c, len, "lyric", get(len)); break; - case meta_marker: - text(c, len, "marker", get(len)); break; - case meta_cuepoint: - text(c, len, "cuepoint", get(len)); break; - case 8: - case 9: - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - text(c, len, 0, get(len)); break; - case 0x20: - if (len == 1) - prefixchannel(getbyte()); - else - meta(c, len, get(len)); - break; - case 0x21: - if (len == 1) - prefixport(getbyte()); - else - meta(c, len, get(len)); - break; - case 0x2F: - end(); - break; - case 0x51: - tempo(gettri()); - break; - case 0x54: - if (len == 5) - { - unsigned char *s = get(len); - - smpteofs(s[0], s[1], s[2], s[3], s[4]); - } - else - meta(c, len, get(len)); - break; - case 0x58: - if (len == 4) - { - int nom, clicksperbeat, notes32perbeat, log2denom; - - nom = getbyte(); - log2denom = getbyte(); - - clicksperbeat = getbyte(); - notes32perbeat = getbyte(); - tact(nom, 1 << log2denom, clicksperbeat, notes32perbeat); - } - else - meta(c, len, get(len)); - break; - case 0x59: - if (len == 2) - { - signed char s[2]; - - s[0] = (signed char)getbyte(); // sf - s[1] = (signed char)getbyte(); // mi - if (s[0] >= -7 && s[0] <= +7 && s[1] >= 0 && s[1] <= 1) - key(s[0], s[1]); - else - meta(c, len, (unsigned char*)s); - } - else - meta(c, len, get(len)); - break; - default: - meta(c, len, get(len)); - break; - } - seek(endpos); - } - break; - default: - { - if (midicode < 0) - return 0; - if (midicode < 0x80) - { - char msg[30]; - - sprintf(msg, "illegal midi command %02X", midicode); - error(msg); - return 0; - } - int channel = midicode & 0x0F; - int cmd = midicode & 0xF0; - - switch(cmd) - { - case 0x80: - case 0x90: - { - unsigned char* c; - - lastcode_ = midicode; - c = get(2); if (!c) return 0; - if (options_ & OPTION_NONOTEEVENTS) - event(midicode, 2, c); - else - { - if (cmd == 0x80 || c[1] == 0) - noteoff(channel, c[0], c[1]); - else - noteon(channel, c[0], c[1]); - } - } - break; - case 0xA0: - { - unsigned char* p = get(2); if (!p) return 0; - - lastcode_ = midicode; - - if (options_ & OPTION_NOPOLYEVENTS) - event(midicode, 2, p); - else - { - polyaftertouch(channel, p[0], p[1]); - } - } - break; - case 0xB0: - { - unsigned char* p = get(2); if (!p) return 0; - - lastcode_ = midicode; - if (options_ & OPTION_NOCONTROLEVENTS) - event(midicode, 2, p); - else if (options_ & OPTION_NOCONTROLS) - control(channel, p[0], p[1]); - else - switch(p[0]) - { - case ctrl_highbank: highbank(channel, p[1]); break; - case ctrl_wheel: wheel(channel, p[1]); break; - case ctrl_breath: breath(channel, p[1]); break; - case ctrl_foot: foot(channel, p[1]); break; - case ctrl_portamentotime: portamentotime(channel, p[1]); break; - case ctrl_data: data(channel, p[1]); break; - case ctrl_volume: volume(channel, p[1]); break; - case ctrl_balance: balance(channel, p[1]); break; - case ctrl_expression: expression(channel, p[1]); break; - case ctrl_lowbank: lowbank(channel, p[1]); break; - case ctrl_hold: hold(channel, p[1]); break; - case ctrl_reverb: reverb(channel, p[1]); break; - case ctrl_chorus: chorus(channel, p[1]); break; - case ctrl_datainc: datainc(channel, p[1]); break; - case ctrl_datadec: datadec(channel, p[1]); break; - case ctrl_lowrpn: lowrpn(channel, p[1]); break; - case ctrl_highrpn: - case ctrl_resetctrlrs: resetctrlrs(channel, p[1]); break; - case ctrl_allnotesoff: allnotesoff(channel, p[1]); break; - { - unsigned char *c = need(8); - - if (c && - c[0] == 0 && c[1] == midicode && c[2] == ctrl_lowrpn && c[3] == 0 && - c[4] == 0 && c[5] == midicode && c[6] == ctrl_data) - { - c = get(8); - pitchbendrange(channel, c[7]); - } - else - highrpn(channel, p[1]); - break; - } - default: - control(channel, p[0], p[1]); - break; - } - break; - } - - case 0xC0: - { - unsigned char* p = get(1); if (!p) return 0; - - lastcode_ = midicode; - if (options_ & OPTION_NOPROGRAMEVENTS) - event(midicode, 1, p); - else - program(channel, p[0]); - } - break; - case 0xD0: - { - unsigned char* p = get(1); if (!p) return 0; - - lastcode_ = midicode; - if (options_ & OPTION_NOAFTERTOUCHEVENTS) - event(midicode, 1, p); - else - aftertouch(channel, p[0]); - } - break; - case 0xE0: - { - unsigned char* p = get(2); if (!p) return 0; - unsigned val = unsigned(p[0]) + (unsigned(p[1]) << 7); - - lastcode_ = midicode; - if (options_ & OPTION_NOPITCHBENDEVENTS) - event(midicode, 2, p); - else - pitchbend(channel, val); - } - break; - default: - { - char msg[30]; - - sprintf(msg, "unexpected command byte %02X", midicode); - error(msg); - return 0; - } - } - } - break; - } - return (int)(curpos_ - pos_); -} - -int MidiRead::runtrack(int trackno) -{ -unsigned long trackpos = curpos_, trackend; - - curtime_ = 0; - lastcode_ = -1; - pos_ = curpos_; - if (!f_) - { - error("file not open"); - return 0; - } - - if (getlong() != MTrk) - { - error("missing midi track MTrk"); - return 0; - } - tracklen_ = getlong(); - track(trackno, tracklen_, curchannel_ = scanchannel(tracklen_)); - trackpos = curpos_; - trackend = trackpos + tracklen_; - lastcode_ = -1; - if ((options_ & OPTION_NOEVENTS) == 0) - while (curpos_ < trackpos + tracklen_) - { - int newpercent = (int)((curpos_ * 100) / filesize_); - if (newpercent != percent_) - percent(percent_ = newpercent); - - unsigned long delta = getdelta(); - if ( delta >= NOTREALISTIC_PAUSE ) - warning("Unrealistic large pause found"); - time(delta); - curtime_ += delta; - if (runevent(trackend) <= 0) - return 0; - } - seek(trackend); - pos_ = curpos_; - endtrack(trackno); - return 1; -} - -void MidiRead::setchannel(int channel) -{ - assert(channel >= -1 && channel <= 15); - curchannel_ = channel; -} - -int MidiRead::scansysevent(unsigned long maxlen) -{ -int n = sizeof(buf_); -unsigned char* c, *p; -long savepos = curpos_; -int len; - - if (maxlen < n) - n = (int)maxlen; - c = need(n); - if (!c) - { - n = buflen_; - c = need(n); - if (!c) - return 0; - } - - if (c[0] < 0x80) - { - // short sysex 0..127 bytes - len = c[0]; - if (n >= len+1) - { - if (c[len] == 0xF7) - return len+1; - } - } - else if (n >= 2 && c[1] < 0x80) - { - // 128..16383 bytes - int len = (int(c[0] & 0x7f) << 7) + c[1]; - if (n >= len + 2) - { - if (c[len+1] == 0xF7) - return len+2; - } - } - // sysex events without length information? - p = (unsigned char*)memchr(c, 0xF7, n); - if (p) - return (int)(p - c + 1); - seek(savepos); - return 0; -} - -int MidiRead::scanchannel(unsigned long maxlen) -{ -int n = 512; -unsigned char* c; -int firstchannel = NOCHANNEL; -int channel, code; -long savepos = curpos_, endpos; -int lastcode = -1; - - if (maxlen < n) - n = (int)maxlen; - c = need(n); - if (!c) - return -1; - - endpos = curpos_ + n; - while (curpos_ < endpos) - { - channel = NOCHANNEL; - getdelta(); - c = need(1); - if (!c || *c >= 0x80 || lastcode < 0) - code = getbyte(); - else - code = lastcode; - switch(code & 0xF0) - { - case 0xF0: - switch(code) - { - case 0xFF: - getbyte(); - get((int)getdelta()); - break; - case 0xf0: // sysex - { - int len = scansysevent(endpos-curpos_); - if (!get(len)) - goto endscan; - } - break; - case 0xf2: get(2); break; - case 0xf3: getbyte(); break; - case 0xf6: - case 0xf8: - case 0xfa: - case 0xfb: - case 0xfc: - case 0xfe: - break; - default: - goto endscan; - } - break; - case 0x80: - case 0x90: - case 0xA0: - case 0xB0: - case 0xE0: - channel = code & 15; - get(2); - break; - case 0xC0: - case 0xD0: - channel = code & 15; - getbyte(); - break; - default: - goto endscan; - } - if (code < 0xf0) - lastcode = code; - if (channel >= 0) - { - if (firstchannel < 0) - firstchannel = channel; - else if (channel != firstchannel) - { - firstchannel = MULTICHANNEL; - break; - } - } - } -endscan: - seek(savepos); - return firstchannel; -} - -int MidiRead::getbyte() -{ -unsigned char* c = get(1); - - if (c) - return *c; - return -1; -} - -unsigned MidiRead::getword() -{ -unsigned char* c = get(2); -unsigned n = 0; - - if (c) - { - n = *c++; - n = (n << 8) + *c++; - } - return n; -} - -unsigned long MidiRead::gettri() -{ -unsigned char* c = get(3); -unsigned long n = 0; - - if (c) - { - n = *c++; - n = (n << 8) + *c++; - n = (n << 8) + *c++; - } - return n; -} - -unsigned long MidiRead::getlong() -{ -unsigned char* c = get(4); -unsigned long n = 0; - - if (c) - { - n = *c++; - n = (n << 8) + *c++; - n = (n << 8) + *c++; - n = (n << 8) + *c++; - } - return n; -} - -unsigned long MidiRead::getdelta() -{ -unsigned long n = 0; -int i = 0, c; - - curdeltalen_ = 0; - for (i = 0; i < 4; i++) - { - c = getbyte(); - if (c < 0) - { - error("unexpected end of file"); - return 0; - } - curdeltalen_++; - n = (n << 7) + (c & 0x7f); - if ((c & 0x80) == 0) - break; - } - return n; -} - -unsigned char* MidiRead::need(int n) -{ - assert(n >= 0); - if (n == 0) - return 0; - if (n > buflen_) - { - if (!f_) - return 0; - if (n > sizeof(buf_)) - return 0; - if (n > sizeof(buf_) - bufpos_) - { - // move to beginning of buf - memmove(buf_, buf_ + bufpos_, buflen_); - bufpos_ = 0; - } - // add new data at end - if (sizeof(buf_) - bufpos_ - buflen_ > 0) - { - fseek(f_, curpos_+buflen_, SEEK_SET); - int l = fread(buf_+bufpos_+buflen_, 1, sizeof(buf_) - bufpos_ - buflen_, f_); - if (l > 0) - buflen_ += l; - else if (l == 0) - ; - } - } - if (n <= buflen_) - return buf_ + bufpos_; - return 0; -} - -unsigned char* MidiRead::get(int n) -{ -unsigned char* s; - - s = need(n); - if (s) - { - buflen_ -= n; - bufpos_ += n; - curpos_ += n; - } - else if (n > sizeof(buf_)) - warning("midi event larger than internal bufsize ignored"); - else if (n > 0) - { - error("unexpected end of file"); - exit(1); - } - return s; -} - -void MidiRead::seek(long pos) -{ - if (pos == curpos_ || pos < 0) - return; - if (pos >= curpos_ - bufpos_ && pos < curpos_ - bufpos_ + buflen_) - { - int n = (int)(pos - curpos_ + bufpos_); - - if (n < bufpos_) - { - buflen_ += bufpos_ - n; - bufpos_ -= bufpos_ - n; - } - else - { - buflen_ -= n - bufpos_; - bufpos_ += n - bufpos_; - } - curpos_ = pos; - } - else - { - fseek(f_, curpos_ = pos, SEEK_SET); - bufpos_ = buflen_ = 0; - } -} - -unsigned long MidiRead::microsec(unsigned long units, unsigned long msperbeat) -{ - assert(clicks_ != 0); // call runhead() or run() first! - - if (units > msperbeat) - return (units / clicks_) * msperbeat; - else - return units * (msperbeat / clicks_); -} - - -long MidiRead::units(unsigned long microsec, unsigned long msperbeat) -{ - assert(clicks_ != 0); // call runhead() or run() first! - assert(msperbeat > 0); // invalid tempo! - int clicks = clicks_; - while ((msperbeat & 1) == 0) - { - if ((clicks & 1) == 0) - clicks >>= 1; - else if ((microsec & 1) == 0) - microsec >>= 1; - else - break; - msperbeat >>= 1; - } - if (microsec >= 0x10000L) - return (microsec / msperbeat) * clicks; - else - return (microsec * clicks) / msperbeat; -} - - -static const char* GMProg[128] = -{ - "Piano", "BritePiano", "HammerPiano", "HonkeyTonk", "NewTines", "DigiPiano", "Harpsicord", "Clav", - "Celesta", "Glocken", "MusicBox", "Vibes", "Marimba", "Xylophon", "Tubular", "Santur", - "FullOrgan", "PercOrgan", "BX-3Organ", "ChurchPipe", "Positive", "Musette", "Harmonica", "Tango", - "ClassicGtr", "A.Guitar", "JazzGuitar", "CleanGtr", "MuteGuitar", "OverDrive", "DistGuitar", "RockMonics", - "JazzBass", "DeepBass", "PickBass", "FretLess", "SlapBass1", "SlapBass2", "SynthBass1", "SynthBass2", - "Violin", "Viola", "Cello", "ContraBass", "TremoloStr", "Pizzicato", "Harp", "Timpani", - "Marcato", "SlowString", "AnalogPad", "StringPad", "Choir", "DooVoice", "Voices", "OrchHit", - "Trumpet", "Trombone", "Tuba", "MutedTrumpet", "FrenchHorn", "Brass", "SynBrass1", "SynBrass2", - "SopranoSax", "AltoSax", "TenorSax", "BariSax", "SweetOboe", "EnglishHorn", "BasoonOboe", "Clarinet", - "Piccolo", "Flute", "Recorder", "PanFlute", "Bottle", "Shakuhachi","Whistle", "Ocarina", - "SquareWave", "SawWave", "SynCalinope", "SynChiff", "Charang", "AirChorus", "Rezzo4ths", "Bass&Lead", - "Fantasia", "WarmPad", "PolyPad", "GhostPad", "BowedGlas", "MetalPad", "HaloPad", "Sweep", - "IceRain", "SoundTrack", "Crystal", "Atmosphere", "Brightness", "Goblin", "EchoDrop", "StarTheme", - "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba","Scotland","Fiddle", "Shanai", - "MetalBell", "Agogo", "SteelDrums", "Woodblock", "Taiko", "Tom", "SynthTom", "RevCymbal", - "FretNoise", "NoiseChiff", "Seashore", "Birds", "Telephone", "Helicopter", "Stadium!!", "GunShot" -}; - -const char* MidiRead::notename(unsigned char note) -{ -static char name[5]; -char* s = name; - - switch(note % 12) - { - case 0: *s++ = 'c'; break; - case 1: *s++ = 'c'; *s++ = '#'; break; - case 2: *s++ = 'd'; break; - case 3: *s++ = 'd'; *s++ = '#'; break; - case 4: *s++ = 'e'; break; - case 5: *s++ = 'f'; break; - case 6: *s++ = 'f'; *s++ = '#'; break; - case 7: *s++ = 'g'; break; - case 8: *s++ = 'g'; *s++ = '#'; break; - case 9: *s++ = 'a'; break; - case 10: *s++ = 'a'; *s++ = '#'; break; - case 11: *s++ = 'b'; break; // former 'h': German language only - } - - sprintf(s, "%d", (note / 12)-1); // octave (assuming Piano C4 is 60) - return (const char*) name; -} - - -const char* MidiRead::progname(int n, int channel) -{ -static char defname[10] = ""; - - if (channel == 9) // drum programs - { - switch(n) - { - case 0: return "Dr1"; - case 0x10: return "Dr2"; - case 0x19: return "Dr3"; - case 0x20: return "Dr4"; - case 0x28: return "Dr5"; - case 0x40: return "Dr6"; - case 0x18: return "Dr7"; - case 0x30: return "Dr8"; - } - } - else if (n >= 0 && n <= 127) - return GMProg[n]; -def: - sprintf(defname, "%d", n); - return (const char*)defname; -} - -void MidiRead::head(unsigned version, unsigned tracks, unsigned clicksperquarter) -{ -} - -void MidiRead::track(int trackno, long length, int channel) -{ -} - -void MidiRead::endtrack(int trackno) -{ -} - -void MidiRead::event(int what, int len, unsigned char* data) -{ -} - -void MidiRead::seqnumber(unsigned int seqno) -{ -} - -void MidiRead::smpteofs(int hour, int min, int sec, int frame, int fracframe) -{ -} - -void MidiRead::key(int signature, int isminor) -{ -} - -void MidiRead::prefixchannel(unsigned char channel) -{ -} - -void MidiRead::prefixport(unsigned char port) -{ -} - -void MidiRead::text(int what, int len, const char* whattext, const unsigned char* txt) -{ -} - -void MidiRead::meta(int what, int len, const unsigned char* data) -{ -} - -void MidiRead::end() -{ -} - -void MidiRead::tact(int nom, int denom, int v1, int v2) -{ -} - -void MidiRead::tempo(unsigned long ticks) -{ -} - -void MidiRead::program(int channel, int program) -{ -} - -void MidiRead::control(int channel, int what, int value) -{ -} - -void MidiRead::highbank(int channel, int val) -{ - control(channel, ctrl_highbank, val); -} - -void MidiRead::wheel(int channel, int val) -{ - control(channel, ctrl_wheel, val); -} - -void MidiRead::breath(int channel, int val) -{ - control(channel, ctrl_breath, val); -} - -void MidiRead::foot(int channel, int val) -{ - control(channel, ctrl_foot, val); -} - -void MidiRead::portamentotime(int channel, int val) -{ - control(channel, ctrl_portamentotime, val); -} - -void MidiRead::data(int channel, int val) -{ - control(channel, ctrl_data, val); -} - -void MidiRead::volume(int channel, int val) -{ - control(channel, ctrl_volume, val); -} - -void MidiRead::balance(int channel, int val) -{ - control(channel, ctrl_balance, val); -} - -void MidiRead::expression(int channel, int val) -{ - control(channel, ctrl_expression, val); -} - -void MidiRead::lowbank(int channel, int val) -{ - control(channel, ctrl_lowbank, val); -} - -void MidiRead::hold(int channel, int val) -{ - control(channel, ctrl_hold, val); -} - -void MidiRead::reverb(int channel, int val) -{ - control(channel, ctrl_reverb, val); -} - -void MidiRead::chorus(int channel, int val) -{ - control(channel, ctrl_chorus, val); -} - -void MidiRead::datainc(int channel, int val) -{ - control(channel, ctrl_datainc, val); -} - -void MidiRead::datadec(int channel, int val) -{ - control(channel, ctrl_datadec, val); -} - -void MidiRead::lowrpn(int channel, int val) -{ - control(channel, ctrl_lowrpn, val); -} - -void MidiRead::highrpn(int channel, int val) -{ - control(channel, ctrl_highrpn, val); -} - -void MidiRead::resetctrlrs(int channel, int val) -{ - control(channel, ctrl_resetctrlrs, val); -} - -void MidiRead::allnotesoff(int channel, int val) -{ - control(channel, ctrl_allnotesoff, val); -} - -void MidiRead::pitchbendrange(int channel, int halfnotes) -{ - highrpn(channel, 0); - time(0); - lowrpn(channel, 0); - time(0); - data(channel, halfnotes); -} - -void MidiRead::noteon(int channel, int note, int vel) -{ -} - -void MidiRead::noteoff(int channel, int note, int vel) -{ -} - -void MidiRead::time(unsigned long ticks) -{ -} - -void MidiRead::pitchbend(int channel, int val) -{ -} - -void MidiRead::polyaftertouch(int channel, int note, int val) -{ -} - -void MidiRead::aftertouch(int channel, int val) -{ -} - -void MidiRead::songpos(unsigned pos) -{ -} - -void MidiRead::songselect(unsigned char song) -{ -} - -void MidiRead::tunerequest() -{ -} - -void MidiRead::timingclock() -{ -} - -void MidiRead::start() -{ -} - -void MidiRead::cont() -{ -} - -void MidiRead::stop() -{ -} - -void MidiRead::activesense() -{ -} - -void MidiRead::sysex(int syslen, const unsigned char* sysdata) -{ -} - -void MidiRead::gmreset() -{ -} - -void MidiRead::gsreset() -{ -} - -void MidiRead::gsexit() -{ -} - -void MidiRead::xgreset() -{ -} - -void MidiRead::endmidi() -{ -} - -void MidiRead::percent(int perc) -{ -} - -void MidiRead::error(const char* msg) -{ - fprintf(stderr, "error: %s\n", msg); -} - -void MidiRead::warning(const char* msg) -{ - fprintf(stderr, "warning: %s\n", msg); -} - -FILE* MidiRead::getf() -{ - return f_; -} - -// class MidiWrite - -const char* MidiWrite::copyright() -{ - return (const char*)::copyright; -} - -MidiWrite::MidiWrite(const char* filename) -{ - midiname_ = filename; - if (midiname_) - f_ = fopen(midiname_, WRITE_BINARY); - else - f_ = 0; - trackpos_ = -1; - curpos_ = 0; - trackchannel_ = -1; - - bufpos_ = 0; - buflen_ = 0; - filesize_ = 0; - trackcount_ = 0; - curtime_ = curdelta_ = 0; - lastcode_ = -1; - clicks_ = 0; -} - -MidiWrite::~MidiWrite() -{ - if (trackcount_ > 0) - { - seek(10); - putword(trackcount_); - } - if (trackpos_ > 0) - endtrack(); - flush(); - if (f_) - fclose(f_); -} - -void MidiWrite::head(int version, int tracks, unsigned clicksperquarter) -{ - seek(0); - putlong(MThd); - putlong(6); - putword(version); - putword(tracks); // unknown - putword(clicks_ = clicksperquarter); -} - -void MidiWrite::track() -{ - if (trackpos_ > 0) - endtrack(); - endtrack_ = 0; - lastcode_ = -1; - curtime_ = curdelta_ = 0; - seek(trackpos_ = filesize_); - putlong(MTrk); - putlong(0); // unknown yet - trackcount_++; -} - -void MidiWrite::endtrack() -{ - seek(filesize_); - if (!endtrack_) - end(); - if (trackpos_ <= 0) - return; - seek(trackpos_+4); - putlong(filesize_ - trackpos_ - 8); - trackpos_ = 0; -} - -void MidiWrite::event(int what, int len, const unsigned char* data) -{ - puttime(); - putcode(what); - put(len, data); -} - -void MidiWrite::prefixchannel(unsigned char channel) -{ - meta(0x20, 1, &channel); -} - -void MidiWrite::prefixport(unsigned char port) -{ - meta(0x21, 1, &port); -} - -void MidiWrite::text(int what, int len, const unsigned char* txt) -{ - if (len < 0) - len = strlen((char *)txt); - meta(what, len, txt); -} - -void MidiWrite::meta(int what, int len, const unsigned char* data) -{ - puttime(); - putcode(0xff); - putbyte(what); - putdelta(len); - put(len, data); -} - -void MidiWrite::end() -{ - if (endtrack_) // don't need end of track event twice - return; - endtrack_ = 1; - meta(0x2f, 0, 0); -} - -void MidiWrite::tact(int nom, int denom, int v1, int v2) -{ -int log2denom; - - switch(denom) - { - case 1: log2denom = 0; break; - case 2: log2denom = 1; break; - case 4: log2denom = 2; break; - case 8: log2denom = 3; break; - case 16: log2denom = 4; break; - case 32: log2denom = 5; break; - case 64: log2denom = 6; break; - case 128: log2denom = 7; break; - case 256: log2denom = 8; break; - default: - log2denom = 2; - assert((1 << log2denom) == denom); - } - puttime(); - putcode(0xff); - putbyte(0x58); - putbyte(4); - putbyte(nom); - putbyte(log2denom); - putbyte(v1); - putbyte(v2); -} - -void MidiWrite::seqnumber(unsigned int seqno) -{ - puttime(); - putcode(0xff); - putbyte(0x00); - putbyte(2); - putword(seqno); -} - -void MidiWrite::smpteofs(int hour, int min, int sec, int frame, int fracframe) -{ - puttime(); - putcode(0xff); - putbyte(0x54); - putbyte(5); - putbyte(hour); - putbyte(min); - putbyte(sec); - putbyte(frame); - putbyte(fracframe); -} - -void MidiWrite::key(int signature, int isminor) -{ - puttime(); - putcode(0xff); - putbyte(0x59); - putbyte(2); - putbyte((signed char)signature); - putbyte((signed char)isminor); -} - -void MidiWrite::tempo(unsigned long ticks) -{ - puttime(); - putcode(0xff); - putbyte(0x51); - putbyte(3); - puttri(ticks); -} - -void MidiWrite::program(int channel, int prg) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xC0 + channel); - putbyte(prg); -} - -void MidiWrite::control(int channel, int what, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xB0 + channel); - putbyte(what); - putbyte(val); -} - -void MidiWrite::highbank(int channel, int val) -{ - control(channel, ctrl_highbank, val); -} - -void MidiWrite::wheel(int channel, int val) -{ - control(channel, ctrl_wheel, val); -} - -void MidiWrite::breath(int channel, int val) -{ - control(channel, ctrl_breath, val); -} - -void MidiWrite::foot(int channel, int val) -{ - control(channel, ctrl_foot, val); -} - -void MidiWrite::portamentotime(int channel, int val) -{ - control(channel, ctrl_portamentotime, val); -} - -void MidiWrite::data(int channel, int val) -{ - control(channel, ctrl_data, val); -} - -void MidiWrite::volume(int channel, int val) -{ - control(channel, ctrl_volume, val); -} - -void MidiWrite::balance(int channel, int val) -{ - control(channel, ctrl_balance, val); -} - -void MidiWrite::expression(int channel, int val) -{ - control(channel, ctrl_expression, val); -} - -void MidiWrite::lowbank(int channel, int val) -{ - control(channel, ctrl_lowbank, val); -} - -void MidiWrite::hold(int channel, int val) -{ - control(channel, ctrl_hold, val); -} - -void MidiWrite::reverb(int channel, int val) -{ - control(channel, ctrl_reverb, val); -} - -void MidiWrite::chorus(int channel, int val) -{ - control(channel, ctrl_chorus, val); -} - -void MidiWrite::datainc(int channel, int val) -{ - control(channel, ctrl_datainc, val); -} - -void MidiWrite::datadec(int channel, int val) -{ - control(channel, ctrl_datadec, val); -} - -void MidiWrite::lowrpn(int channel, int val) -{ - control(channel, ctrl_lowrpn, val); -} - -void MidiWrite::highrpn(int channel, int val) -{ - control(channel, ctrl_highrpn, val); -} - -void MidiWrite::resetctrlrs(int channel, int val) -{ - control(channel, ctrl_resetctrlrs, val); -} - -void MidiWrite::allnotesoff(int channel, int val) -{ - control(channel, ctrl_allnotesoff, val); -} - -void MidiWrite::noteon(int channel, int note, int vel) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0x90+channel); - putbyte(note); - putbyte(vel); -} - -void MidiWrite::noteoff(int channel, int note, int vel) -{ - assert(channel >= 0 && channel < 16); - puttime(); - if (vel != 0 || lastcode_ < 0 || (lastcode_ & 0xF0) != 0x90) - putcode(0x80+channel); - else - putcode(0x90+channel); // vel == 0! - putbyte(note); - putbyte(vel); -} - -void MidiWrite::time(unsigned long ticks) -{ - if ( ticks >= NOTREALISTIC_PAUSE ) - warning("generating unrealistic large pause"); - curdelta_ += ticks; - curtime_ += ticks; -} - -void MidiWrite::cleardelta() -{ - curtime_ -= curdelta_; - curdelta_ = 0; -} - -void MidiWrite::pitchbend(int channel, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xE0 + channel); - putbyte(val & 0x7F); - putbyte((val >> 7) & 0x7F); -} - -void MidiWrite::polyaftertouch(int channel, int note, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xA0 + channel); - putbyte(note); - putbyte(val); -} - -void MidiWrite::aftertouch(int channel, int val) -{ - assert(channel >= 0 && channel < 16); - puttime(); - putcode(0xD0 + channel); - putbyte(val); -} - -void MidiWrite::songpos(unsigned pos) -{ - puttime(); - putcode(0xF2); - putbyte(pos & 0x7F); - putbyte((pos >> 7) & 0x7F); -} - -void MidiWrite::songselect(unsigned char song) -{ - puttime(); - putcode(0xF3); - putbyte(song); -} - -void MidiWrite::tunerequest() -{ - puttime(); - putcode(0xf6); -} - -void MidiWrite::timingclock() -{ - puttime(); - putcode(0xf8); -} - -void MidiWrite::start() -{ - puttime(); - putcode(0xfa); -} - -void MidiWrite::cont() -{ - puttime(); - putcode(0xfb); -} - -void MidiWrite::stop() -{ - puttime(); - putcode(0xfc); -} - -void MidiWrite::activesense() -{ - puttime(); - putcode(0xfe); -} - -void MidiWrite::sysex(int syslen, const unsigned char* sysdata) -{ - puttime(); - putcode(0xf0); - if (*sysdata == 0xF0) - { - syslen--; - sysdata++; - } - put(syslen, sysdata); -} - -void MidiWrite::gmreset() -{ - sysex(sysexlen(sysex_gmreset), sysex_gmreset); -} - -void MidiWrite::xgreset() -{ - sysex(sysexlen(sysex_xgreset), sysex_xgreset); -} - -void MidiWrite::gsreset() -{ - sysex(sysexlen(sysex_gsreset), sysex_gsreset); -} - -void MidiWrite::gsexit() -{ - sysex(sysexlen(sysex_gsexit), sysex_gsexit); -} - -void MidiWrite::pitchbendrange(int channel, int halfnotes) -{ - highrpn(channel, 0); - lowrpn(channel, 0); - data(channel, halfnotes); -} - -void MidiWrite::putbyte(unsigned char val) -{ - put(1, &val); -} - -void MidiWrite::putcode(unsigned char code) -{ -int put; - - assert(code >= 0x80); - - if (compress) - put = !(code == lastcode_ && code <= 0x9f); - else - put = 1; - if (put) - putbyte(code); - lastcode_ = code; -} - -static unsigned char c[4]; - -void MidiWrite::putword(unsigned val) -{ - c[1] = (unsigned char)(val & 0xff); val >>= 8; - c[0] = (unsigned char)(val & 0xff); - put(2, c); -} - -void MidiWrite::puttri(unsigned long val) -{ - c[2] = (unsigned char)(val & 0xff); val >>= 8; - c[1] = (unsigned char)(val & 0xff); val >>= 8; - c[0] = (unsigned char)(val & 0xff); - put(3, c); -} - -void MidiWrite::putlong(unsigned long val) -{ - c[3] = (unsigned char)(val & 0xff); val >>= 8; - c[2] = (unsigned char)(val & 0xff); val >>= 8; - c[1] = (unsigned char)(val & 0xff); val >>= 8; - c[0] = (unsigned char)(val & 0xff); - put(4, c); -} - -void MidiWrite::putdelta(unsigned long val) -{ - int i = 0, j = 3; - while (i < 4) - { - c[j] = val & 0x7F; - if (j < 3) - c[j] |= 0x80; - val >>= 7; - i++; - if (!val) - break; - j--; - } - put(i, c+j); -} - -void MidiWrite::puttime() -{ - putdelta(curdelta_); - curdelta_ = 0; -} - -void MidiWrite::flush() -{ - if (buflen_ > 0) - { - fseek(f_, curpos_ - bufpos_, SEEK_SET); - if (fwrite(buf_, buflen_, 1, f_) != 1) - error("write error (maybe disk full)"); - assert(ftell(f_) == curpos_ - bufpos_ + buflen_); - bufpos_ = buflen_ = 0; - } -} - -void MidiWrite::put(int len, const unsigned char* c) -{ - if (len <= 0) - return; - if (c == 0 || len > sizeof(buf_)) - return; - if (sizeof(buf_) - bufpos_ < len) - flush(); - memcpy(buf_+bufpos_, c, len); - bufpos_+=len; - if (bufpos_ > buflen_) - buflen_ = bufpos_; - curpos_+= len; - if (curpos_ > filesize_) - filesize_ = curpos_; -} - -void MidiWrite::seek(long pos) -{ - assert(pos >= 0 && pos <= filesize_); - if (curpos_ == pos) - return; - if (pos >= curpos_-bufpos_ && pos <= curpos_-bufpos_+buflen_) - { - bufpos_ = (int)(pos - (curpos_-bufpos_)); - curpos_ = pos; - return; - } - flush(); - curpos_ = pos; -} - -FILE* MidiWrite::getf() -{ - return f_; -} - -void MidiWrite::error(const char* msg) -{ - fprintf(stderr, "midi write error: %s\n", msg); -} - -void MidiWrite::warning(const char* msg) -{ - fprintf(stderr, "midi write warning: %s\n", msg); -} - -int MidiWrite::unitsperquarter() -{ - return clicks_; -} - -MidiCopy::MidiCopy(const char* filename, FILE* f) : MidiRead(filename, f) -{ - dest_ = NULL; - for (int c = 0; c < 16; c++) - mapchannel_[c] = c; // no change -} - -void MidiCopy::head(unsigned version, unsigned tracks, unsigned clicksperquarter) -{ - if (dest_) - dest_->head(version, 0, clicksperquarter); -} - -void MidiCopy::track(int trackno, long length, int channel) -{ - if (dest_) - dest_->track(); -} - -void MidiCopy::endtrack(int trackno) -{ - if (dest_) - dest_->endtrack(); -} - -void MidiCopy::event(int what, int len, unsigned char* data) -{ - if (dest_) - dest_->event(what, len, data); -} - - -void MidiCopy::seqnumber(unsigned int seqno) -{ - if (dest_) - dest_->seqnumber(seqno); -} - -void MidiCopy::smpteofs(int hour, int min, int sec, int frame, int fracframe) -{ - if (dest_) - dest_->smpteofs(hour, min, sec, frame, fracframe); -} - -void MidiCopy::key(int signature, int isminor) -{ - if (dest_) - dest_->key(signature, isminor); -} - -void MidiCopy::prefixchannel(unsigned char channel) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->prefixchannel(mapchannel_[channel]); -} - -void MidiCopy::prefixport(unsigned char port) -{ - if (dest_) - dest_->prefixport(port); -} - -void MidiCopy::text(int what, int len, char* whattext, unsigned char* txt) -{ - if (dest_) - dest_->text(what, len, txt); -} - -void MidiCopy::meta(int what, int len, unsigned char* data) -{ - if (dest_) - dest_->meta(what, len, data); -} - -void MidiCopy::end() -{ - if (dest_) - dest_->end(); -} - -void MidiCopy::tact(int nom, int denom, int v1, int v2) -{ - if (dest_) - dest_->tact(nom, denom, v1,v2); -} - -void MidiCopy::tempo(unsigned long ticks) -{ - if (dest_) - dest_->tempo(ticks); -} - -void MidiCopy::program(int channel, int program) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->program(mapchannel_[channel], program); -} - -void MidiCopy::control(int channel, int what, int value) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->control(mapchannel_[channel], what, value); -} - -void MidiCopy::highbank(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->highbank(mapchannel_[channel], val); -} - -void MidiCopy::wheel(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->wheel(mapchannel_[channel], val); -} - -void MidiCopy::breath(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->wheel(mapchannel_[channel], val); -} - -void MidiCopy::foot(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->foot(mapchannel_[channel], val); -} - -void MidiCopy::portamentotime(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->portamentotime(mapchannel_[channel], val); -} - -void MidiCopy::data(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->data(mapchannel_[channel], val); -} - -void MidiCopy::volume(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->volume(mapchannel_[channel], val); -} - -void MidiCopy::balance(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->balance(mapchannel_[channel], val); -} - -void MidiCopy::expression(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->expression(mapchannel_[channel], val); -} - -void MidiCopy::lowbank(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->lowbank(mapchannel_[channel], val); -} - -void MidiCopy::hold(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->hold(mapchannel_[channel], val); -} - -void MidiCopy::reverb(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->reverb(mapchannel_[channel], val); -} - -void MidiCopy::chorus(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->chorus(mapchannel_[channel], val); -} - -void MidiCopy::datainc(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->datainc(mapchannel_[channel], val); -} - -void MidiCopy::datadec(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->datadec(mapchannel_[channel], val); -} - -void MidiCopy::lowrpn(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->lowrpn(mapchannel_[channel], val); -} - -void MidiCopy::highrpn(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->highrpn(mapchannel_[channel], val); -} - -void MidiCopy::resetctrlrs(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->resetctrlrs(mapchannel_[channel], val); -} - -void MidiCopy::allnotesoff(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->allnotesoff(mapchannel_[channel], val); -} - - -void MidiCopy::noteon(int channel, int note, int vel) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->noteon(mapchannel_[channel], note, vel); -} - -void MidiCopy::noteoff(int channel, int note, int vel) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->noteoff(mapchannel_[channel], note, vel); -} - -void MidiCopy::time(unsigned long ticks) -{ - if (dest_) - dest_->time(ticks); -} - -void MidiCopy::pitchbend(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->pitchbend(mapchannel_[channel],val); -} - -void MidiCopy::polyaftertouch(int channel, int note, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->polyaftertouch(mapchannel_[channel], note, val); -} - -void MidiCopy::aftertouch(int channel, int val) -{ - if (dest_ && mapchannel_[channel] >= 0) - dest_->aftertouch(mapchannel_[channel], val); -} - -void MidiCopy::songpos(unsigned pos) -{ - if (dest_) - dest_->songpos(pos); -} - -void MidiCopy::songselect(unsigned char song) -{ - if (dest_) - dest_->songselect(song); -} - -void MidiCopy::tunerequest() -{ - if (dest_) - dest_->tunerequest(); - -} - -void MidiCopy::timingclock() -{ - if (dest_) - dest_->timingclock(); -} - -void MidiCopy::start() -{ - if (dest_) - dest_->start(); -} - -void MidiCopy::cont() -{ - if (dest_) - dest_->cont(); -} - -void MidiCopy::stop() -{ - if (dest_) - dest_->stop(); -} - -void MidiCopy::activesense() -{ - if (dest_) - dest_->activesense(); -} - -void MidiCopy::sysex(int syslen, unsigned char* sysdata) -{ - if (dest_) - dest_->sysex(syslen, sysdata); -} - -void MidiCopy::xgreset() -{ - if (dest_) - dest_->xgreset(); -} - -void MidiCopy::gmreset() -{ - if (dest_) - dest_->gmreset(); -} - -void MidiCopy::gsreset() -{ - if (dest_) - dest_->gsreset(); -} - -void MidiCopy::gsexit() -{ - if (dest_) - dest_->gsexit(); -} - -void MidiCopy::setoutput(MidiWrite* dest) -{ - assert(dest != NULL && dest->getf() != NULL); // need a valid MidiWrite instance - dest_ = dest; -} - -void MidiCopy::stopoutput() -{ - dest_ = NULL; // owner is responsible for deleting the MidiWrite object -} - -MidiWrite* MidiCopy::getoutput() -{ - return dest_; -} - -void MidiCopy::mapchannel(int channel, int newchannel) -{ - assert(channel >= 0 && channel < 16); - assert(newchannel >= 0 && newchannel < 16); - mapchannel_[channel] = newchannel; -} - -void MidiCopy::ignorechannel(int channel) -{ - assert(channel >= 0 && channel < 16); - mapchannel_[channel] = -1; -} diff --git a/dro2midi/midiio.hpp b/dro2midi/midiio.hpp deleted file mode 100644 index bc88e10..0000000 --- a/dro2midi/midiio.hpp +++ /dev/null @@ -1,402 +0,0 @@ -#ifndef __MIDIIO__ -#define __MIDIIO__ - -#include - -#ifndef MIDI_BUFSIZE -#define MIDI_BUFSIZE 1024 -#endif - -// opening modes of a midi file -#define WRITE_BINARY "wb" -#define READ_BINARY "rb" - -const unsigned long MThd = 0x4D546864ul; -const unsigned long MTrk = 0x4D54726Bul; - -// different standard midi formats -#define VERSION_MULTICHANNEL 0 -#define VERSION_SINGLECHANNEL 1 -#define VERSION_MULTISONG 2 - -#define OPTION_NOCONTROLS 1 // no control details but general information -#define OPTION_NOEVENTS 2 // no track events at all -#define OPTION_NOMETAEVENTS 4 // no meta details but general information -#define OPTION_NOSYSEVENTS 8 // no sysex details but general information -#define OPTION_NONOTEEVENTS 16 // no note events (8x or 9x) -#define OPTION_NOPOLYEVENTS 32 // no poly aftertouch events (Ax) -#define OPTION_NOCONTROLEVENTS 64 // no control events at all (Bx) -#define OPTION_NOPROGRAMEVENTS 128 // no program change events (Cx) -#define OPTION_NOAFTERTOUCHEVENTS 256 // no aftertouch events (Dx) -#define OPTION_NOPITCHBENDEVENTS 512 // no pitchbend events (Ex) -#define OPTION_NOREALTIMEEVENTS 1024 // no realtime events (Fx) - -// getchannel delivers a valid channel or: -#define NOCHANNEL (-1) -#define MULTICHANNEL (-2) -#define VALIDCHANNEL(ch) ((ch) >= 0 && (ch) <= 15) -#define gm_drumchannel 9 -#define SAYCHANNEL(ch) ((ch) + 1) // 0..15 in midi format but spoken 1..16! - -// for use of param what in function text() -#define meta_text 1 -#define meta_copyright 2 -#define meta_trackname 3 -#define meta_instrument 4 -#define meta_lyric 5 -#define meta_marker 6 -#define meta_cuepoint 7 -#define meta_endtrack 0x2f - -#define ctrl_highbank 0 -#define ctrl_wheel 1 -#define ctrl_breath 2 -#define ctrl_foot 4 -#define ctrl_portamentotime 5 -#define ctrl_data 6 -#define ctrl_volume 7 -#define ctrl_balance 10 -#define ctrl_expression 11 -#define ctrl_lowbank 32 -#define ctrl_hold 64 -#define ctrl_reverb 91 -#define ctrl_chorus 93 -#define ctrl_datainc 96 -#define ctrl_datadec 97 -#define ctrl_lowrpn 100 -#define ctrl_highrpn 101 -#define ctrl_resetctrlrs 121 -#define ctrl_allnotesoff 123 - -#define balance_left 0 -#define balance_center 64 -#define balance_right 127 - -#define volume_mute 0 -#define volume_full 127 - -#define bpm(ticks) (60000000.0 / (ticks)) -#define ticks(beats) ((unsigned long)(60000000.0 / (beats))) - -#define tempo_60bpm (1000000L) -#define tempo_120bpm (500000L) -#define tempo_240bpm (250000L) - - -class MidiRead -{ -public: - static const char* copyright(); - - MidiRead(const char* filename, FILE* f = 0); - virtual ~MidiRead(); - - FILE* getf(); - - int run(); - int runhead(); - int runtrack(int trackno); - int runevent(long trackend); - - long getpos() { return pos_; } - long geteventpos() { return pos_; } - long getcurpos() { return curpos_; } - unsigned long getcurtime() { return curtime_; } // in midi units - - virtual void head(unsigned version, unsigned tracks, unsigned clicksperquarter); - virtual void track(int trackno, long length, int channel); - virtual void endtrack(int trackno); // closing track - - virtual void time(unsigned long ticks); // delay between events - - virtual void event(int what, int len = 0, unsigned char* data = 0); - - // these are event categories: - virtual void meta(int what, int len, const unsigned char* data); - // these are special meta events: - virtual void text(int what, int len, const char* whattext, const unsigned char* txt); - virtual void end(); // end of track command - virtual void prefixchannel(unsigned char channel); - virtual void prefixport(unsigned char port); - virtual void seqnumber(unsigned int seqno); - virtual void smpteofs(int hour, int min, int sec, int frame, int fracframe); - virtual void tact(int nom, int denom, int clicksperbeat, int notes32perbeat); - virtual void tempo(unsigned long microsecperbeat); - virtual void key(int signature, int isminor); // C=0, -7=7flats +7=7sharps - virtual void program(int channel, int prg); - virtual void control(int channel, int what, int val); // general controls - // special controls: - virtual void highbank(int channel, int val); - virtual void wheel(int channel, int val); - virtual void breath(int channel, int val); - virtual void foot(int channel, int val); - virtual void portamentotime(int channel, int val); - virtual void data(int channel, int val); - virtual void volume(int channel, int val); - virtual void balance(int channel, int val); - virtual void expression(int channel, int val); - virtual void lowbank(int channel, int val); - virtual void hold(int channel, int val); - virtual void reverb(int channel, int val); - virtual void chorus(int channel, int val); - virtual void datainc(int channel, int val); - virtual void datadec(int channel, int val); - virtual void lowrpn(int channel, int val); - virtual void highrpn(int channel, int val); - virtual void resetctrlrs(int channel, int val); - virtual void allnotesoff(int channel, int val); - virtual void pitchbendrange(int channel, int val); - virtual void noteon(int channel, int note, int vel); - virtual void noteoff(int channel, int note, int vel); - virtual void pitchbend(int channel, int val); - virtual void polyaftertouch(int channel, int note, int val); - virtual void aftertouch(int channel, int val); - virtual void songpos(unsigned pos); - virtual void songselect(unsigned char song); - virtual void tunerequest(); - virtual void timingclock(); - virtual void start(); - virtual void cont(); - virtual void stop(); - virtual void activesense(); - virtual void sysex(int syslen, const unsigned char* sysdata); - // these are special sysex events: - virtual void xgreset(); - virtual void gmreset(); - virtual void gsreset(); - virtual void gsexit(); - - virtual void endmidi(); // after last track - virtual void error(const char* msg); - virtual void warning(const char* msg); - - virtual void percent(int perc); - - int getchannel() { return curchannel_; } - void setchannel(int channel); - - static const char* progname(int n, int channel); - static const char* notename(unsigned char note); - - int options_; - - int version_, tracks_, clicks_, trackno_; - - void seek(long pos); - int getbyte(); - unsigned getword(); - unsigned long gettri(); - unsigned long getlong(); - unsigned long getdelta(); - unsigned char* get(int len); - unsigned char* need(int nbytes); - - unsigned long microsec(unsigned long units, unsigned long msperbeats); - long units(unsigned long microsec, unsigned long msperbeats); - - // use scanchannel only at start of track! - int scanchannel(unsigned long maxlen); // channel 0-15 or -1=no channel or -2=multichannels - - // use sysevent only directly after reading F0 or F7 - int scansysevent(unsigned long maxlen); - -protected: - const char *midiname_; - FILE* f_; - unsigned char shouldclose_; // 0=no, otherwise=yes - long filesize_; - unsigned char buf_[MIDI_BUFSIZE]; - int buflen_, bufpos_; - int curchannel_; - unsigned long curtime_; - int percent_; - int lastcode_; - unsigned long tracklen_; - - long pos_, curpos_; - unsigned char curdeltalen_; // number of bytes read by recent getdelta() call -}; - -class MidiWrite -{ -public: - static const char* copyright(); - - MidiWrite(const char* filename); - virtual ~MidiWrite(); - - FILE* getf(); - - long getcurpos() { return curpos_; } - long getcurtime() { return curtime_; } - void cleardelta(); - - void head(int version = 1, int tracks = 0, unsigned clicksperquarter = 192); - void track(); - void endtrack(); - - void event(int what, int len, const unsigned char* data); - - void text(int what, int len, const unsigned char* txt); - void meta(int what, int len, const unsigned char* data); // 0xff .... - virtual void prefixchannel(unsigned char channel); - virtual void prefixport(unsigned char port); - virtual void seqnumber(unsigned int seqno); - virtual void smpteofs(int hour, int min, int sec, int frame, int fracframe); - virtual void key(int signature, int isminor); - void tact(int nom, int denom, int v1, int v2); - void tempo(unsigned long microsecperbeat); - void end(); - void program(int channel, int prg); - void control(int channel, int what, int val); - // these are special controls - void highbank(int channel, int val); - void wheel(int channel, int val); - void breath(int channel, int val); - void foot(int channel, int val); - void portamentotime(int channel, int val); - void data(int channel, int val); - void volume(int channel, int val); - void balance(int channel, int val); - void expression(int channel, int val); - void lowbank(int channel, int val); - void hold(int channel, int val); - void reverb(int channel, int val); - void chorus(int channel, int val); - void datainc(int channel, int val); - void datadec(int channel, int val); - void lowrpn(int channel, int val); - void highrpn(int channel, int val); - void resetctrlrs(int channel, int val); - void allnotesoff(int channel, int val); - void pitchbendrange(int channel, int halfnotes); - - void noteon(int channel, int note, int vel); - void noteoff(int channel, int note, int vel = 0); - void time(unsigned long ticks); - void pitchbend(int channel, int val); - void polyaftertouch(int channel, int note, int val); - void aftertouch(int channel, int val); - void songpos(unsigned pos); - void songselect(unsigned char song); - void tunerequest(); - void timingclock(); - void start(); - void cont(); - void stop(); - void activesense(); - void sysex(int syslen, const unsigned char* sysdata); - void xgreset(); - void gmreset(); - void gsreset(); - void gsexit(); - - void putbyte(unsigned char val); - void putcode(unsigned char code); - void putword(unsigned val); - void puttri(unsigned long val); - void putlong(unsigned long val); - void putdelta(unsigned long val); - void puttime(); - void put(int len, const unsigned char* c); - void seek(long pos); - - virtual void error(const char* msg); - virtual void warning(const char* msg); - - int unitsperquarter(); - -protected: - const char *midiname_; - FILE* f_; - long trackpos_, curpos_, filesize_; - int trackchannel_, trackcount_, lastcode_, endtrack_; - - unsigned char buf_[MIDI_BUFSIZE]; - int bufpos_, buflen_; - - unsigned long curdelta_; - unsigned long curtime_; - - int clicks_; - - void flush(); -}; - - -class MidiCopy : public MidiRead -{ -public: - MidiCopy(const char* filename, FILE* f = 0); - - void setoutput(MidiWrite* dest); - void stopoutput(); - MidiWrite* getoutput(); - - void mapchannel(int channel, int newchannel); - void ignorechannel(int channel); - - virtual void head(unsigned version, unsigned tracks, unsigned clicksperquarter); - virtual void track(int trackno, long length, int channel); - virtual void endtrack(int trackno); - - virtual void time(unsigned long ticks); - - virtual void event(int what, int len = 0, unsigned char* data = 0); - - virtual void meta(int what, int len, unsigned char* data); - virtual void text(int what, int len, char* whattext, unsigned char* txt); - virtual void end(); - virtual void prefixchannel(unsigned char channel); - virtual void prefixport(unsigned char port); - virtual void seqnumber(unsigned int seqno); - virtual void smpteofs(int hour, int min, int sec, int frame, int fracframe); - virtual void tact(int nom, int denom, int clicksperbeat, int notes32perbeat); - virtual void tempo(unsigned long microsecperbeat); - virtual void key(int signature, int isminor); // C=0, -7=7flats +7=7sharps - virtual void program(int channel, int prg); - virtual void control(int channel, int what, int val); - virtual void highbank(int channel, int val); - virtual void wheel(int channel, int val); - virtual void breath(int channel, int val); - virtual void foot(int channel, int val); - virtual void portamentotime(int channel, int val); - virtual void data(int channel, int val); - virtual void volume(int channel, int val); - virtual void balance(int channel, int val); - virtual void expression(int channel, int val); - virtual void lowbank(int channel, int val); - virtual void hold(int channel, int val); - virtual void reverb(int channel, int val); - virtual void chorus(int channel, int val); - virtual void datainc(int channel, int val); - virtual void datadec(int channel, int val); - virtual void lowrpn(int channel, int val); - virtual void highrpn(int channel, int val); - virtual void resetctrlrs(int channel, int val); - virtual void allnotesoff(int channel, int val); - virtual void noteon(int channel, int note, int vel); - virtual void noteoff(int channel, int note, int vel); - virtual void pitchbend(int channel, int val); - virtual void polyaftertouch(int channel, int note, int val); - virtual void aftertouch(int channel, int val); - virtual void songpos(unsigned pos); - virtual void songselect(unsigned char song); - virtual void tunerequest(); - virtual void timingclock(); - virtual void start(); - virtual void cont(); - virtual void stop(); - virtual void activesense(); - virtual void sysex(int syslen, unsigned char* sysdata); - virtual void gmreset(); - virtual void gsreset(); - virtual void gsexit(); - virtual void xgreset(); - -protected: - MidiWrite* dest_; - int mapchannel_[16]; // channel 0-15 or events are ignored for invalid channel -}; - -#endif diff --git a/img/Two-OP AM.png b/img/Two-OP AM.png old mode 100644 new mode 100755 diff --git a/img/Two-OP FM.png b/img/Two-OP FM.png old mode 100644 new mode 100755 diff --git a/img/abs_sine.png b/img/abs_sine.png old mode 100644 new mode 100755 diff --git a/img/adlib.png b/img/adlib.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_off.png b/img/algo_switch_off.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_on.png b/img/algo_switch_on.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_on2.png b/img/algo_switch_on2.png old mode 100644 new mode 100755 diff --git a/img/algo_switch_on3.png b/img/algo_switch_on3.png old mode 100644 new mode 100755 diff --git a/img/alternating_sine.png b/img/alternating_sine.png old mode 100644 new mode 100755 diff --git a/img/bassdrum.png b/img/bassdrum.png old mode 100644 new mode 100755 diff --git a/img/camel_sine.png b/img/camel_sine.png old mode 100644 new mode 100755 diff --git a/img/channeloff.png b/img/channeloff.png old mode 100644 new mode 100755 diff --git a/img/channelon.png b/img/channelon.png old mode 100644 new mode 100755 diff --git a/img/cymbal.png b/img/cymbal.png old mode 100644 new mode 100755 diff --git a/img/disabled.png b/img/disabled.png old mode 100644 new mode 100755 diff --git a/img/full_sine.png b/img/full_sine.png old mode 100644 new mode 100755 diff --git a/img/half_sine.png b/img/half_sine.png old mode 100644 new mode 100755 diff --git a/img/hihat.png b/img/hihat.png old mode 100644 new mode 100755 diff --git a/img/line_border_horiz.png b/img/line_border_horiz.png old mode 100644 new mode 100755 diff --git a/img/line_border_vert.png b/img/line_border_vert.png old mode 100644 new mode 100755 diff --git a/img/logarithmic_saw.png b/img/logarithmic_saw.png old mode 100644 new mode 100755 diff --git a/img/quarter_sine.png b/img/quarter_sine.png old mode 100644 new mode 100755 diff --git a/img/snare.png b/img/snare.png old mode 100644 new mode 100755 diff --git a/img/square.png b/img/square.png old mode 100644 new mode 100755 diff --git a/img/toggle_off_sq.png b/img/toggle_off_sq.png old mode 100644 new mode 100755 diff --git a/img/toggle_on_sq.png b/img/toggle_on_sq.png old mode 100644 new mode 100755 diff --git a/img/tom.png b/img/tom.png old mode 100644 new mode 100755 diff --git a/img/yamaha.png b/img/yamaha.png old mode 100644 new mode 100755 diff --git a/py/atten_levels.py b/py/atten_levels.py deleted file mode 100644 index e0ba7a9..0000000 --- a/py/atten_levels.py +++ /dev/null @@ -1,13 +0,0 @@ -lvls = [.75, 1.5, 3, 6, 12, 24] - -def get_db(i): - db = 0.0 - for d in lvls: - if i & 0x1: - db += d - i >>= 1 - return db - -for i in range(2**6): - db = get_db(i) - print '"%.2f dB",' % db, diff --git a/py/oplparse.py b/py/oplparse.py deleted file mode 100644 index 33b9d56..0000000 --- a/py/oplparse.py +++ /dev/null @@ -1,340 +0,0 @@ -import sys -import json - -REG_SCOPE = { - 0x00: 'Chip', - 0x20: '0x20', - 0x30: '0x20', - 0x40: '0x40', - 0x50: '0x40', - 0x60: 'Attack/decay', - 0x70: 'Attack/decay', - 0x80: 'Sustain/release', - 0x90: 'Sustain/release', - 0xA0: 'Frequency (L)', - 0xB0: 'Key on/off/Frequency(H)', - 0xE0: 'Wave select', - 0xF0: 'Wave select', -} - -# Map register ranges to channel + operator -OPERATOR_MAP = { - 0x00: (1, 1), - 0x01: (2, 1), - 0x02: (3, 1), - 0x03: (1, 2), - 0x04: (2, 2), - 0x05: (3, 2), - 0x08: (4, 1), - 0x09: (5, 1), - 0x0a: (6, 1), - 0x0b: (4, 2), - 0x0c: (4, 2), - 0x0d: (6, 2), - 0x10: (7, 1), - 0x11: (8, 1), - 0x12: (9, 1), - 0x13: (7, 2), - 0x14: (8, 2), - 0x15: (9, 2), -} -OSC_MAP = {1: 'mod', 2: 'car'} - -def reg_to_chan_and_op(reg): - # from REGOP in opl driver - #return ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ) - chan, op = OPERATOR_MAP.get(reg % 32, (-1, 1)) - print 'ch%02d.%s: ' % (chan, OSC_MAP[op]), - return chan, op - -def interpret_00(reg, val, ev): - regs = {1:'TEST', 2:'Timer 1', 3:'Timer 2', 4:'Timer Ctrl', 8:'CSM/keysplit'} - print '%02X (%s)' % (reg, regs.get(reg, '??')), - -def interpret_20(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - bitfields = {7: 'tre', 6: 'vib', 5: 'sus', 4:'ksr'} - bf = val & 0xf0 - opts = [] - for b, opt in bitfields.iteritems(): - optset = 'X' if (bf & (0x1 << b)) else '-' - opts.append('%s:%s' % (opt, optset)) - ev[op-1][opt]=optset - fm_mult = val & 0x0f - mults = {0:0.5, 11:10, 13:12, 14:15} - fm_mult = mults.get(fm_mult, fm_mult) - opts.append('fm_mult:x%d' % (fm_mult)) - ev[op-1]['fm_mult'] = fm_mult - print '; '.join(opts) , - -def _db_atten_to_factor(atten_db): - return (10 ** -(atten_db / 10)) - -def interpret_40(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - bit_db = {5:24, 4:12, 3:6, 2:3, 1:1.5, 0:0.75} - oct_db = {0:0, 0x40:1.5, 0x80:3, 0xC0:6} - lvl = 0.0 - for b, db in bit_db.iteritems(): - if (val & (0x1 << b)): - lvl += db - db_oct = oct_db[val & 0xC0] - oct_scale = '-%d dB/8ve' % db_oct - factor = _db_atten_to_factor(lvl) - ev[op-1]['db'] = lvl - ev[op-1]['db_oct'] = db_oct - ev[op-1]['scale'] = factor - print '%.2f dB; (x%.4f) %s' % (lvl, factor, oct_scale) , - -def interpret_60(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - a = val >> 4 - d = val & 0x0f - ev[op-1]['a'] = a - ev[op-1]['d'] = d - print 'Att: 0x%1x; Dec: 0x%1x' % (a, d) , - -def interpret_80(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - s = val >> 4 - r = val & 0x0f - ev[op-1]['s'] = s - ev[op-1]['r'] = r - print 'Sus: 0x%1x; Rel: 0x%1x' % (s, r) , - -def interpret_A0(reg, val, ev): - global freq_lsb - freq_lsb = val - chan = 1 + reg % 0xA0 - print 'ch%02d: Freq-LSB=%02X' % (chan, val) , - -def interpret_B0(reg, val, ev): - global freq_lsb - try: - freq_lsb - except NameError: - freq_lsb = 0 - oct = (val >> 2) & 0x7 - fnum = ((val & 0x3) << 8) + freq_lsb - chan = 1 + reg % 0xB0 - key = 'ON' if (1 << 5) & val else 'off' - frq = _fnum_to_hz(fnum, oct) - ev[1]['frq'] = frq - ev[2]['key'] = key - ev[2]['ch'] = chan - #ev[0]['gate'] = key - print 'ch%02d: Key-%s; f0x%03X oct:%d (%.4f Hz)' % (chan, key, fnum, oct, frq) , - -''' -A0-A8: Frequency Number: - - Determines the pitch of the note. Highest bits of F-Number are stored - in the register below. - - -B0-B8: Key On / Block Number / F-Number(hi bits): - - bit 5: KEY-ON. When 1, channel output is enabled. - bits 2-4: Block Number. Roughly determines the octave. - bits 0-1: Frequency Number. 2 highest bits of the above register. - - The following formula is used to determine F-Number and Block: - - F-Number = Music Frequency * 2^(20-Block) / 49716 Hz -''' - -def _fnum_to_hz(fnum, octave): - return (49716.0 * fnum) / (2 ** (21 - octave)) - -def interpret_BD(reg, val, ev): - print '* BD' , - -def interpret_C0(reg, val, ev): - alg = 'ADD' if val & 0x1 else 'MODULATE' - feedback = (val >> 1) & 0xc - ev[2]['alg'] = alg - ev[2]['feedback'] = feedback - print 'feedback: %d algo: %s' % (feedback, alg) , - -def interpret_E0(reg, val, ev): - ch, op = reg_to_chan_and_op(reg) - ev[2]['ch'] = ch - waves = {0: 'SIN', 1: 'HALFSIN', 2: 'ABSSIN', 3: 'QUARTSIN'} - wav = waves[val & 0x3] - ev[op-1]['wav'] = wav - print wav , - -def interpret_write(ts, reg, val, ev): - print '%10d : ' % ts, - if reg >= 0x01 and reg <= 0x08: - interpret_00(reg, val, ev) - elif reg >= 0x20 and reg <= 0x35: - interpret_20(reg, val, ev) - elif reg >= 0x40 and reg <= 0x55: - interpret_40(reg, val, ev) - elif reg >= 0x60 and reg <= 0x75: - interpret_60(reg, val, ev) - elif reg >= 0x80 and reg <= 0x95: - interpret_80(reg, val, ev) - elif reg >= 0xA0 and reg <= 0xA8: - interpret_A0(reg, val, ev) - elif reg >= 0xB0 and reg <= 0xB8: - interpret_B0(reg, val, ev) - elif reg == 0xBD: - interpret_BD(reg, val, ev) - elif reg >= 0xC0 and reg <= 0xC8: - interpret_C0(reg, val, ev) - elif reg >= 0xE0 and reg <= 0xF5: - interpret_E0(reg, val, ev) - else: - print '?????', - print - - #scope = REG_SCOPE.get(reg & 0xF0, '??') - #chan, operator = reg_to_chan_and_op(reg) - #aprint '%10f : %02d.%1d : %02x : %20s' % (float(ts)/1000, chan, operator, val, scope) - -def parse_opldump(stream): - events = dict() - t0 = tp = False - line = stream.readline() - while len(line) >= 1: - ts, opl2, op = line.split(':') - if 'OPL2' == opl2: - ts = int(ts) - if not t0: - t0 = ts - ts -= t0 - reg, val = op.split('=') - reg = int(reg, 16) - val = int(val, 16) - try: - ev = events[ts] - except KeyError: - ev = [dict(), dict(), dict()] - events[ts] = ev - interpret_write(ts, reg, val, ev) - line = stream.readline() - return events - -def get_javascript_for(event): - mod = event[0] - car = event[1] - anc = event[2] - indent = 8*' ' - lines = [] - if 'ch' in anc: - lines.append('var ch = opl2.channels[%d];\n' % (anc['ch'] - 1)) - if 'fm_mult' in car: - lines.append('ch.setModulatorMultiplier(%d); ch.setCarrierMultiplier(%d);\n' % (mod['fm_mult'], car['fm_mult'])) - if 'scale' in car: - lines.append('ch.setModulatorAttenuation(%f); ch.setCarrierAttenuation(%f);\n' % (mod['scale'], car['scale'])) - if 'a' in mod: - lines.append('ch.setEnvParams(ch.modEnv, 1/%d, 1/%d, 1/%d, 1/%d);\n' % (mod['a'], mod['d'], mod['s']+1, mod['r'])) - if 'a' in car: - lines.append('ch.setEnvParams(ch.carEnv, 1/%d, 1/%d, 1/%d, 1/%d);\n' % (car['a'], car['d'], car['s']+1, car['r'])) - if 'wav' in car: - lines.append('ch.setCarrierWaveform(ch.%s); ch.setModulatorWaveform(ch.%s);\n' % (car['wav'], mod['wav'])) - if 'key' in anc: - if 'ON' == anc['key'].upper(): - lines.append('ch.noteOn(%f);\n' % car['frq']) - elif 'OFF' == anc['key'].upper(): - lines.append('ch.noteOff();\n') - return indent + indent.join(lines) - - -def find_unique_instruments(events): - instruments = dict() - ilist = list() - timestamps = events.keys() - timestamps.sort() - for t in timestamps: - ev = events[t] - try: # get rid of fields which don't define uniqueness - del ev[2]['key'] - del ev[2]['ch'] - del ev[1]['frq'] - except KeyError: - pass - instr_json = json.dumps(ev, indent=2) - if instr_json not in instruments: - instruments[instr_json] = t - ilist.append(instr_json) - return ilist, instruments - -def print_instrument(json_i, ts): - print - print '@ %10d:' % ts - try: - d = json.loads(json_i) - print 'Waveforms: %10s%10s' % (d[0]['wav'], d[1]['wav']) - print 'Freq mult: %9dx%9dx' % (d[0]['fm_mult'], d[1]['fm_mult']) - print 'Levels: %7d dB%7d dB' % (-d[0]['db'], -d[1]['db']) - envs = (d[0]['a'], d[0]['d'], d[0]['s'], d[0]['r'], d[1]['a'], d[1]['d'], d[1]['s'], d[1]['r']) - print 'Envelopes: %1x %1x %1x %1x %1x %1x %1x %1x' % envs - except KeyError: - print 'incomplete instrument?' - -# Conversions mapping to VST floating point values -W2F = {'SIN':0, 'HALFSIN':.33, 'ABSSIN':.66, 'QUARTSIN':1} -def m2f(mult): - if mult < 1: # half frq - return 0 - else: - return float(mult)/15.0 - -def a2f(att_db): - a = float(att_db)/0.75 - return a/63.0 - -def b2f(bool_param): - if 'X' == bool_param: - return 1.0 - else: - return 0.0 -D2F = {0:0.0,1.5:0.33,3.0:0.66,6.0:1.0} - -def e2f(env_val): - return float(env_val)/15.0 - -def output_instrument_vst_program(json_i, ts): - try: - d = json.loads(json_i) - m=d[0]; c=d[1] - lines = [ - ' const float i_params_%d[] = {' % ts, - ' %.6ff, %.6ff, // waveforms' % (W2F[c['wav']], W2F[m['wav']]), - ' %.6ff, %.6ff, // frq multipliers' % (m2f(c['fm_mult']), m2f(m['fm_mult'])), - ' %.6ff, %.6ff, // attenuation' % (a2f(c['db']), a2f(m['db'])), - ' %.1ff, %.1ff, %.1ff, %.1ff, // tre / vib / sus / ks' % tuple([b2f(c[x]) for x in ['tre', 'vib', 'sus', 'ksr']]), - ' %.1ff, %.1ff, %.1ff, %.1ff, // tre / vib / sus / ks' % tuple([b2f(m[x]) for x in ['tre', 'vib', 'sus', 'ksr']]), - ' %.6ff, %.6ff, // KSR/8ve' % (D2F[c['db_oct']], D2F[m['db_oct']]), - ' %.6ff, // algorithm' % (1.0 if 'ADD'==d[2]['alg'] else 0.0), - ' %.6ff, // feedback' % (float(d[2]['feedback'])/7.0), - ' %.1ff, %.1ff, %.1ff, %.1ff, // adsr' % tuple([e2f(c[x]) for x in ['a', 'd', 's', 'r']]), - ' %.1ff, %.1ff, %.1ff, %.1ff, // adsr' % tuple([e2f(m[x]) for x in ['a', 'd', 's', 'r']]), - ' };', - ' std::vector v_i_params_%d (i_params_%d, i_params_%d + sizeof(i_params_%d) / sizeof(float));' % (ts,ts,ts,ts), - ' programs["Instr %d"] = std::vector(v_i_params_%d);' % (ts, ts), - ] - print - print '\n'.join(lines) - except KeyError: - pass - #print - #print '// incomplete instrument..' - -def main(argv): - events = parse_opldump(sys.stdin) - ilist, instruments = find_unique_instruments(events) - for i in ilist: - output_instrument_vst_program(i, instruments[i]) - #print i, instruments[i] - #print_instrument(i, instruments[i]) - -if '__main__' == __name__: - main(sys.argv) diff --git a/py/sbi_filter.py b/py/sbi_filter.py deleted file mode 100644 index 2a1f28d..0000000 --- a/py/sbi_filter.py +++ /dev/null @@ -1,45 +0,0 @@ -import glob -import os - -class SBI: - def __init__(self, sbi_data): - if sbi_data[0:3] != b'SBI': - raise ValueError('Invalid file header.') - if len(sbi_data) != 52: - raise ValueError('Invalid file size.') - self.register_values = sbi_data[36:] - - def __eq__(self, other): - return isinstance(other, SBI) and self.register_values == other.register_values - - def __hash__(self): - return hash(self.register_values) - - -# Requires Python 3.5+ -def find_distinct_sbis(root_dir): - sbis = {} - bad = [] - for filename in glob.iglob(os.path.join(root_dir, '**', '*.sbi'), recursive=True): - with open(filename, 'rb') as f: - try: - sbi = SBI(f.read()) - try: - dup_filename = sbis[sbi] - print('Duplicate SBI file: %s (duplicate of %s)' % (filename, dup_filename)) - bad.append(filename) - except KeyError: - sbis[sbi] = filename - except ValueError as e: - print('Invalid SBI file: %s (%s)' % (filename, e)) - bad.append(filename) - return sbis, bad - - -if __name__ == '__main__': - distinct_sbis, to_delete = find_distinct_sbis('.') - print('Found %d distinct SBI files' % len(distinct_sbis)) - print('Writing batch file..') - with open('delete_bad_sbis.cmd', 'w') as batch_file: - for delete in to_delete: - print('DEL %s' % delete, file=batch_file)