; play VGM (and MDX) (and MOD) on SCSP ; ; 68K gets bogged down by some songs, need to optimize ; ; SN noise is present! (but not accurate) ; (emulators don't do SCSP noise) ; ; to do: ; ; OPM noise and DT2 bits (0=normal, 1=1.41x, 2-1.57x, 3=1.73x) ; ; OPM LFO (and OPN2 AM) ; ; SSG envelope (both kinds) ; ; use SSG/PSG clock from VGM header ; ; try to emulate SSG/PSG noise with SCSP noise ; ; (SSG code could be improved) ; ; YM2608 rhythm ; ; better fix for MODs that use the 9xx command with loops? ; MDX: ; when syncsend happens, do channels restart THIS frame or NEXT frame? ; (and does it matter whether the channel sending is before or after the one restarting?) ; ; $F8 $FF means the note is 1 tick shorter! ; ; $EA, $EB, $F0, and slot mask don't do anything (do any songs use these?) ; OPN2 differences from OPN: ; ; $22 - LFO enable, LFO freq ; $28 - key on/off handles channels 0-2 and 4-6 ; $2A - DAC data ; $2B - bit 7 = DAC enable ; ; $60-6E - bit 7 has AM enable bit ; ; $B4-B7 - bit 7 left / bit 6 right / bits 5-4 AMS / bits 2-0 FMS ; ; second register bank duplicates $30 to $B7 (but no channel 3 special) ; MDX files: ; ; name followed by $0D,$0A,$1A ; name of PDX or just $00 ; voice word: offset of voice data (relative to voice word position) ; MML words (9x or 16x): offsets of MML data (relative to voice word position) ; ; voice data begins with an index byte (voice number) ; then has FB/CON, slotmask (low 4 bits) ; and finally 4 of each: DET/MUL, TL, AR, D1, D2, SL/RR ; (27 bytes total) const ssgvol,12 ; SSG volume offset (-.375dB per step) const psgvol,14 ; PSG volume offset (-.375dB per step) const keyscalef,4 ; shift for keyscale bits (4,5, or 6, which is correct?) const maxchan,15 ; number of Mod channels minus 1 const tempoconv,6890 ; used to set SCSP timer sectiondata incbin "sinepatt.bin" sinepatt: incbin "sinepatt.bin" incbin "sinepatt.bin" db 0,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 0,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 squarepatt: db 0,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 0,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db 0,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 db 0,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 db -99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99,-99 alignw finetune.w: dw 32768,32532,32298,32065,31835,31606,31378,31152 dw 34715,34466,34218,33972,33728,33485,33244,33005 dw 16384,16266,16149,16032,15917,15803,15689,15576 dw 17357,17233,17109,16981,16864,16742,16622,16502 arpeggtab.w: dw 32768,30928,29193,27554,26008,24548,23170,21870 dw 20642,19484,18390,17358,16384,15464,14596,13777 vibtable.sb: db $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0A,$0B,$0C,$0D,$0E,$0F db $10,$0F,$0E,$0D,$0C,$0B,$0A,$09,$08,$07,$06,$05,$04,$03,$02,$01 db $00,$FF,$FE,$FD,$FC,$FB,$FA,$F9,$F8,$F7,$F6,$F5,$F4,$F3,$F2,$F1 db $F0,$F1,$F2,$F3,$F4,$F5,$F6,$F7,$F8,$F9,$FA,$FB,$FC,$FD,$FE,$FF dumbtable.b: db $40,$C0,$80,$C0 voltable.b: incbin "scspvol.bin" alignw pantable2.w: dw 0,$B800,$A800,$A000 lfospeed.w: dw $8000,$8000,$8000,$8000,$8000,$8000,$8000,$8000 dw $4800,$4C00,$5000,$5000,$5400,$5800,$7000,$7800 lfopitch.b: db 0,$20,$20,$40,$40,$60,$80,$80 ; db 0,0,$20,$20,$40,$40,$60,$80 ; SCSP pitch resolution ; octave -1 .167Hz ; octave 0 - 342/1024 = .334Hz ; octave 1 .668Hz detunetable.sb: db 0,0,-1,-1,0,0,1,1 db 0,-1,-1,-2,0,1,1,2 ; db 0,0,1,1,0,0,-1,-1 ; db 0,1,1,2,0,-1,-1,-2 ; dw 0,1,2,3,0,-1,-2,-3 fbtable.w: dw 0,0,$5000,$6000,$6000,$7000,$7000,$8000 ; this table uses tweaked operator order for OPN 1-3-2-4 mixtable.w: dw 0,0,0,$A000 dw 0,0,0,$A000 dw 0,0,0,$A000 dw 0,0,0,$A000 dw 0,0,$A000,$A000 dw 0,$A000,$A000,$A000 dw 0,$A000,$A000,$A000 dw $A000,$A000,$A000,$A000 ; this table uses tweaked operator order for OPN 1-3-2-4 algotable.w: dw $800,$9861,$979E,$979E dw $800,$985F,0,$979E dw $800,$9861,0,$979D dw $800,0,$979E,$979F dw $800,0,$979E,$979E dw $800,$97DF,$979E,$975D dw $800,0,$979E,0 dw $800,0,0,0 ; 22 880 ; 21 840 ; 1F 7C0 ; 1E 780 ; 1D 740 pantable.b: db $60,$18,$08,0 notetable.b: db 0,1,2,2,3,4,5,5,6,7,8,8,9,10,11,11 mdxvtable.sb: db $A8,$A5,$A2,$A0,$9D,$9A,$98,$95,$92,$90,$8D,$8A,$88,$85,$82,$80 mdxnotes.b: db $02,$04,$05,$06,$08,$09,$0A,$0C,$0D,$0E,$10,$11 db $12,$14,$15,$16,$18,$19,$1A,$1C,$1D,$1E,$20,$21 db $22,$24,$25,$26,$28,$29,$2A,$2C,$2D,$2E,$30,$31 db $32,$34,$35,$36,$38,$39,$3A,$3C,$3D,$3E,$40,$41 db $42,$44,$45,$46,$48,$49,$4A,$4C,$4D,$4E,$50,$51 db $52,$54,$55,$56,$58,$59,$5A,$5C,$5D,$5E,$60,$61 db $62,$64,$65,$66,$68,$69,$6A,$6C,$6D,$6E,$70,$71 db $72,$74,$75,$76,$78,$79,$7A,$7C,$7D,$7E,$7E,$7E mdxlfotable.b: ; db $1E,$1C,$1A,$19,$18,$17,$16,$16 ; db $15,$14,$14,$13,$13,$13,$12,$12 ; db $12,$11,$11,$11,$10,$10,$10,$0F db $1D,$1B,$19,$17,$16,$15,$14,$14 db $13,$13,$12,$12,$11,$11,$10,$10 db $10,$0F,$0F,$0F,$0F,$0E,$0E,$0D ; db $1C,$18,$16,$14,$13,$12,$11,$10 ; db $0F,$0F,$0E,$0E,$0D,$0D,$0C,$0C mdxlfotable2.b: db $00,$20,$40,$40,$60,$60,$60,$60 db $60,$60,$60,$60,$60,$60,$60,$80 ; db $80,$80,$80,$80,$80,$80,$80,$A0 ; db $00,$40,$60,$60,$60,$60,$60,$60 ; db $60,$60,$60,$60,$80,$80,$80,$80 ; db $00,$20,$40,$40,$40,$60,$60,$60 ; db $60,$60,$60,$60,$80,$80,$80,$80 alignw opmfreq.w: incbin "opmfreq.bin" autostart.b: db 0 alignd testvgm: ; incbin "rainbow.mod" ; incbin "ara.mod" ; incbin "ftma.mod" ; incbin "ambpower.mod" ; incbin "monday.mod" ; incbin "03-menu.mod" ; slightly problematic (9xx) ; incbin "spacespa.mod" ; incbin "corruption.mod" ; extra problematic (9xx) ; incbin "crystalh.mod" ; incbin "knulla-kuk.mod" ; incbin "bloodm.mod" ; incbin "mm_tune.mod" ; incbin "sirius7.mod" ; incbin "ps3-005.vgm" ; shop ; incbin "ps3-006.vgm" ; wren ; incbin "ps3-007.vgm" ; world ; incbin "ps3-008.vgm" ; opening ; incbin "ps3-009.vgm" ; megido ; incbin "ps3-010.vgm" ; dead party ; incbin "codework.vgm" ; incbin "d:\junk2\vgm\lotus.vgm" ; incbin "destruct.vgm" ; incbin "03g.vgm" ; incbin "canal.vgm" ; incbin "ilberns.vgm" ; incbin "1943\10.vgm" ; incbin "1943\02.vgm" ; incbin "zepik.vgm" ; incbin "mystery.vgm" ; incbin "dance.vgm" ; incbin "machine.vgm" ; incbin "cyber01.vgm" ; incbin "cyber06.vgm" ; this one uses alternate multiplier bits ; incbin "cyber09.vgm" ; incbin "tf3.mdx" ; incbin "ale0d.mdx" ; incbin "mcpmu.mdx" ; this one is humbug now ; incbin "majo03.mdx" ; incbin "control.mdx" ; incbin "mariko.mdx" ; incbin "omoide.mdx" ; incbin "o4.mdx" ; incbin "ninr_knp.mdx" ; incbin "ys_iv.mdx" ; incbin "lana.mdx" ; incbin "omens.mdx" ; doesn't loop right sectionbss intsamples.d: resd vbsig2.d: resd loopoff.d: resd fmclk.d: resd nfreqhigh.d: resd nfreqlow.d: resd nfraction.d: resd mdxp.d: resd 8 mdxloop.d: resd 8 mdxvoice.d: resd 8 mdxportv.sd: resd 8 mdxportr.sw: resw mdxfrac.sw: resw resd 7 mdxlfo.w: resw resw resd 7 vgmp.d: resd stime.d: resd vtime.d: resd ym1reg.b: resb 256 ym2reg.b: resb 256 chipmode.b: resb divarmed.b: resb latchbyte.b: resb mdxtime.b: resb 8 mdxsync.b: resb 8 mdxmask.b: resb 8 mdxcount.b: resb 32 ; if I change this, change it in startmdx too mdxnest.b: resb 8 mdxvol.b: resb 8 mdxnote.b: resb 8 mdxlen.b: resb 8 mdxtrim.sb: resb 8 mdxstop.b: resb 8 mdxnolift.b: resb 8 mdxplfo.b: resb 8 mdxlfodel.b: resb 8 mdxlfotrig.b: resb 8 mdxfinish.b: resb alignd ; mod player stuff msonglen.d: resd msongbase.d: resd msongaddr.d: resd pattbase.d: resd pattoffs.d: resd pattsize.d: resd looppatt.d: resd numpatt.d: resd samlength.d: resd 32 samaddr.d: resd 32 loopstart.d: resd 32 ; looplength.d: resd 32 ; loopexist.b: resb 32 ; samftune.b: resb 32 samvol.b: resb 32 chandata: chanaddr.d: resd > resd maxchan ;chanbound.d: resd > resd maxchan ; unused? ;chaninc.d: resd > resd maxchan ; chanptr.d: resd > resd maxchan chanper.w: resw chantarg.w: resw resd maxchan chanvol.b: resb chanvol2.b: resb ; unused? chanvol3.b: resb ; chansam.b: resb resd maxchan chanloop.b: resb ; 0=not playing, 1-16=just started (needs init), 17=playing, 18=looping volslide.sb: resb chaneffect.b: resb chanparam.sb: resb resd maxchan chanpar2.b: resb oldbend.sb: resb oldtrem.b: resb oldvib.b: resb resd maxchan chankey.b: resb chankey2.b: resb ; Saturn-specific chanfade.w: resw resd maxchan chanticks.b: resb chanvole.b: resb chanpan.b: resb chanenv.b: resb resd maxchan chandataend: alterper.w: resw ; Saturn-specific oldperiod.w: resw resd maxchan mspeed.b: resb intcount.b: resb numchan.b: resb numsamp.b: resb loopcount.b: resb count64.b: resb ;musicfade.b: resb ;mastervol.b: resb zombiemod.b: resb alignd resb $400 stackend: sectioncode ; 68k vector table dd stackend dd main dd nullint,nullint,nullint,nullint,nullint,nullint dd nullint,nullint,nullint,nullint,nullint,nullint,nullint,nullint dd nullint,nullint,nullint,nullint,nullint,nullint,nullint,nullint dd nullint,timerint,nullint,nullint,nullint,nullint,nullint,nullint dd nullint,nullint,nullint,nullint,nullint,nullint,nullint,nullint dd nullint,nullint,nullint,nullint,nullint,nullint,nullint,nullint dd nullint,nullint,nullint,nullint,nullint,nullint,nullint,nullint dd nullint,nullint,nullint,nullint,nullint,nullint,nullint,nullint driverend.d: dd stackend aparam.d: dd stackend ; default song address command.w: dw 0 dw 0 pstatus.w: dw 1 istatus.w: dw 0 ; debug stuff icounter.d: dd 0 stuff2.d: dd ym1reg.a opnkeys.b: resb 4 opnmask.b: db $FF,$FF,$FF,$FF timerint: asm move d0.d,[a7-] move $500,d0.d sub [intsamples].d,d0 move d0,[$100418].w move $40,[$100422].w add 1,[vbsig2].d move [a7+].d,d0 endasm nullint: asm rte ; RTE better than RTS ??? ;) endasm main: asm move $2700,SR ; $2000 = superviser mode ; $700 = disable interrupts, 0 = enable all? move stackend,a7.d endasm beginfunc localvar w.d,x.d,y.d,z.d intsamples=46 ; 60Hz default interrupt rate gosub initscsp [$100424].w=$40 ; [$100426].w=$00 ; set int level to 1 [$100428].w=$00 ; [$10041E].w=$40 ; disable 68K interrupts except timer A [$100418].w=$0500-intsamples ; set timer rate (5=every 32 samples) [$100422].w=$40 ; reset int asm move $2000,SR ; $2000 = superviser mode ; $700 = disable interrupts, 0 = enable all? endasm command=0 pstatus=0 ; public status = idle istatus=0 ; internal status = idle ifequal autostart,0,programloop aparam=testvgm.a command=autostart programloop: icounter=_+1 y=vbsig2 gosub vgminterrupt ifequal command,0,nocommand pstatus=1 ifunequal command,1,pl01 ; start a VGM callex ,startvgm,aparam istatus=_ or 1 pl01: ifunequal command,2,pl02 ; stop gosub initscsp > istatus=_ and $FFF8 pl02: ifunequal command,3,pl03 ; start an MDX callex ,startmdx,aparam > istatus=_ or 2 pl03: ifunequal command,4,pl04 ; start a Mod callex ,startmod,aparam > istatus=_ or 4 pl04: command=0 pstatus=0 nocommand: whileequal y,vbsig2 > wend ; wait for next timer int goto programloop programend: icounter=_-1 goto programend initscsp: beginfunc localvar x.d,y.d,h1.d,h2.d h1=sinepatt shr 16+$130 h2=sinepatt and $FFFF x=0 whileless x,32 y=x shl 5+$100000 [y].w=h1 > y=_+2 ; $00 main [y].w=h2 > y=_+2 ; $02 samp addr low word [y].w=0 > y=_+2 ; $04 loop begin [y].w=$80 > y=_+2 ; $06 loop end [y].w=$1F > y=_+2 ; $08 decay1/decay2/attack [y].w=$1F > y=_+2 ; $0A sustain/release [y].w=255 > y=_+2 ; $0C volume/etc [y].w=0 > y=_+2 ; $0E modulation [y].w=0 > y=_+2 ; $10 octave/freq [y].w=0 > y=_+2 ; $12 LFO [y].w=0 > y=_+2 ; $14 DSP mixing [y].w=$A000 ; volume and panning x=_+1 wend endfunc return startvgm: beginfunc modaddr.d localvar x.d,y.d,z.d,h1.d,h2.d ; chipmode 0 = single YM2203 ; 1 = dual YM2203 ; 2 = YM2612 + SN76489 ; 3 = YM2151 ; 4 = YM2608 chipmode=2 > fmclk=3579545 divarmed=0 x=0 whileless x,256 ym1reg(x)=0 ym2reg(x)=0 x=_+1 wend ; set OPN default panning to both speakers ; (since single OPN is mono only, and doesn't actually have this register) ym1reg($B4)=$C0 ym1reg($B5)=$C0 ym1reg($B6)=$C0 ifunequal [modaddr].d,"Vgm ".d,programend modaddr=_+28 loadlittle loopoff,[modaddr].d > loopoff=_+modaddr ; loop offset modaddr=_+16 > loadlittle x,[modaddr].d ; is there a YM2612 clock? ifequal x,0,s103 fmclk=x and $FFFFFF shr 1 ; chipmode is already 2 s103: modaddr=_+4 > loadlittle x,[modaddr].d ; is there a YM2151 clock? ifequal x,0,s102 fmclk=x and $FFFFFF > chipmode=3 s102: modaddr=_+4 > loadlittle x,[modaddr].d ; remaining VGM header size ifunequal x,0,s105 > x=$0C s105: vgmp=modaddr+x ; beginning of data ifless x,20,s101 modaddr=_+16 > loadlittle x,[modaddr].d ; is there a YM2203 clock? ifequal x,0,s101 fmclk=x and $FFFFFF > chipmode=0 ifequal x and $40000000,0,s101 ; if we have two OPN, then set first OPN to Left, second to Right ym1reg($B4)=$80 > ym2reg($B4)=$40 ym1reg($B5)=$80 > ym2reg($B5)=$40 ym1reg($B6)=$80 > ym2reg($B6)=$40 chipmode=1 s101: modaddr=_+4 > loadlittle x,[modaddr].d ; is there a YM2608 clock? ifequal x,0,s106 fmclk=x and $FFFFFF shr 1 > chipmode=4 s106: ; how many PSG/SSG channels? ifequal chipmode,3,s104 gosub setpsgwave ifunequal chipmode,1,s104 gosub setpsgwave2 s104: x=fmclk shr 8 nfreqlow=203210937/x shl 1 nfreqhigh=nfreqlow shl 1-1 nfraction=x*169 shr 12 ; nfreqhigh=52021 ; nfreqlow=26011 ; nfraction=645 intsamples=46 ; 60Hz interrupt stime=0 vtime=0 ifgreater loopoff,vgmp,s100 > loopoff=vgmp goto s100 setpsgwave: z=$100300 setpsgwave2: h1=squarepatt shr 16+$130 h2=squarepatt and $FFFF x=z+$60 whileless z,x [z].w=h1 > z=_+2 [z].w=h2 > z=_+$1E wend [z].w=$A0 > z=_+$20 ; make a noise channel too return s100: endfunc returnex 4 vgminterrupt: ; this is a virtual vblank int where we process YM register writes beginfunc localvar w.d,x.d,y.d,z.d,xx.d,chan.d,op.d,sreg.d,octave.d,ssgupdate.d,changecount.d localvar bank.d,index.d,tptr.d,nextpatt.d,pattaddr.d,nextsong.d,newsam.d ifunequal istatus and 1,0,dovgm ifunequal istatus and 2,0,domdx ifequal istatus and 4,0,intdone ; ~~~~~~~~ Mod playback ~~~~~~~~ count64=_+1 and 63 ; is it time to process a new pattern row? ifunequal intcount,0,doeffects pattaddr=pattoffs+pattbase nextpatt=numchan shl 2+pattoffs nextsong=msongaddr ifunequal [msongaddr].b+1*pattsize,nextpatt,mi97 ; end of pattern? gosub advancesong mi97: chan=0 whileless chan,numchan w=chan shl 2 volslide(w)=0 ; reset volume slides chanpar2(w)=0 ; decode entry for a channel x=[pattaddr].b > pattaddr=_+1 y=[pattaddr].b > pattaddr=_+1 z=x and 15 shl 8+y ; period y=[pattaddr].b > pattaddr=_+1 newsam=x and $F0 shl 4+y shr 4 ; sample number y=_ and 15 ; effect type x=[pattaddr].b > pattaddr=_+1 ; effect parameter ifequal newsam and $C0,0,ko21 chanpan(w)=newsam and $C0 ; unused bits 6-7 of sample can control newsam=_ and $1F ; speaker left/right enable ko21: ifequal newsam,0,mi81 ; sample number is set? ifequal chansam(w),newsam,mi92 chansam(w)=newsam > chanaddr(w)=samaddr(newsam shl 2) chanptr(w)=0 chanloop(w)=16 chankey(w)=1 ; chankey2(w)=1 mi92: chanvol(w)=samvol(newsam) mi81: ; effects that need to be processed BEFORE playing a new note ifunequal y,5,mi69 ; 5xy - tone portamento AND volume (x=volume up, y=volume down) y=x shr 3 and $1E > ifunequal y,0,mi67 y=0-x shl 1 mi67: volslide(w)=y > goto mi99 mi69: ifunequal y,3,mi62 ; 3xx - tone portamento - x=speed ($300 = continue previous rate) ifless x,128,mi94 > x=127 mi94: ifunequal x,0,mi66 mi99: x=oldbend(w) mi66: chanparam(w)=x > oldbend(w)=x ifless z,$1D,mi63 chantarg(w)=z*finetune(samftune(chansam(w))) shr 11 mi63: ; make sure slide is going in the right direction ifequal chantarg(w)-chanper(w) xor chanparam(w) and $80000000,0,mi64 chanparam(w)=0-_ mi64: chaneffect(w)=5 > chanpar2(w)=2 goto mx01 mi62: ifgreater z,$1C,mx07 ; is there a valid period? ifequal z,2,ko18 ifunequal z,1,ko16 > chankey(w)=0 ; key off ko16: ifequal chaneffect(w),6,ko19 ; if vibrato was active before then reset period goto mi09 ko18: chaneffect(w)=6 chanparam(w)=oldvib(w) > goto ko17 ; vibrato continuation mx07: ; calculate finetuned period (with 4-bit fraction) chanper(w)=z*finetune(samftune(chansam(w))) shr 11 chanptr(w)=0 ifequal chansam(w),0,ko19 ; don't start playing if there isn't a sample chanloop(w)=16 ko19: ; chaninc(w)=periodconv/chanper(w) alterper(w)=chanper(w) mi09: chaneffect(w)=14 ; cancel old effects from prior row ko17: ; start other effects? ifequal y,0,mi01 ; 0xy - play + optional arpeggio - x=first halfnote add, y=second ifequal y,2,mi54 ; 2xx - slide down - x=speed ifunequal y,1,mi53 ; 1xx - slide up - x=speed x=0-_ mi54: chanparam(w)=x ; chantarg(w)=0 chaneffect(w)=5 goto mx01 mi53: ifunequal y,4,mi30 ; 4xy - vibrato - x=speed, y=depth ($400=continue previous rate) ifunequal x and 15,0,mx03 > x=oldvib(w) and 15+_ mx03: ifunequal x and $F0,0,mx04 > x=oldvib(w) and $F0+_ mx04: y=6 > oldvib(w)=x > goto mi01 mi30: ifunequal y,6,mi91 ; 6xy - vibrato AND volume slide (x=volume up, y=volume down) chanparam(w)=oldvib(w) goto mi72 mi91: ifunequal y,7,mi95 ; 7xy - tremolo - x=speed, y=depth ($700=continue previous rate) ifunequal x and 15,0,mx05 > x=oldtrem(w) and 15+_ mx05: ifunequal x and $F0,0,mx06 > x=oldtrem(w) and $F0+_ mx06: oldtrem(w)=x > goto mi01 mi95: ifunequal y,10,mi42 ; Axy - volume slide - x=up speed, y=down speed mi72: z=x shr 3 and $1E > ifunequal z,0,mi45 z=0-x shl 1 mi45: volslide(w)=z goto mx01 mi42: ifunequal y,9,mi05 ; 9xx - set sample offset chanptr(w)=x shl 8 > chanloop(w)=16 ; Saturn has different shift x=samlength(chansam(w) shl 2) ifless chanptr(w),x,mx01 ; imperfect workaround for dumb offsets... chanptr(w)=_-x ; chanbound(w)=samlength(chansam(w) shl 2) goto mx01 mi05: ifunequal y,11,mi03 ; Bxx - jump to song position nextsong=msongbase+x nextpatt=[nextsong].b*pattsize goto mx01 mi03: ifunequal y,12,mi04 ; Cxx - set volume - x=volume x=_ and 127 ifless x,65,mi90 > x=64 mi90: chanvol(w)=x > goto mx01 mi04: ifunequal y,13,mi06 ; Dxx - pattern break - jump to row in next pattern z=x and 15+(x shr 4*10) gosub advancesong nextpatt=z*numchan shl 2+_ goto mx01 mi06: ; chaneffect(w)=14 ifunequal y,14,mi16 ; Eyx - misc y=x shr 4 > x=_ and 15 ifequal y,2,mi49 ; pitch bump down ifunequal y,1,mi51 ; pitch bump up x=0-_ mi49: chanparam(w)=x > chanpar2(w)=1 chaneffect(w)=5 goto mx01 mi51: ifunequal y,6,mi13 ; loop pattern ifunequal x,0,mi14 looppatt=pattoffs > goto mx01 mi14: ifunequal loopcount,0,mi15 > loopcount=x+1 > goto mx01 mi15: ifequal loopcount,1,mx01 loopcount=_-1 > nextpatt=looppatt goto mx01 mi13: ifunequal y,9,mi17 ; set retrigger interval y=16 > goto mi01 mi17: ifunequal y,10,mi43 ; volume bump up volslide(w)=x shl 1+1 goto mx01 mi43: ifunequal y,11,mi44 ; volume bump down volslide(w)=0-x shl 1+1 goto mx01 mi44: ifunequal y,12,mi57 ; note cut ifunequal x,0,mi88 > chanloop(w)=0 ; cut immediately mi88: y=17 > goto mi01 mi57: ifunequal y,13,ko15 ; delay note ifequal chanloop(w),0,mx01 ifgreater chanloop(w),16,mx01 chanloop(w)=_-x goto mx01 ko15: ; ifunequal y,14,mi48 ; delay pattern ; nexttotal=intsamples*mspeed*x+_ ; goto mx01 mi48: ; end of $E00 effects goto mx01 mi16: ifunequal y,15,mi10 ; Fxx - set speed - 0-$20 is speed, $21-$FF is tempo ifunequal x,0,mi19 > x=1 mi19: ifless x,$21,mi11 intsamples=tempoconv/x goto mx01 mi11: mspeed=x goto mx01 mi10: mi01: chaneffect(w)=y chanparam(w)=x mx01: chan=_+1 wend pattoffs=nextpatt msongaddr=nextsong ; end of pattern processing goto skipeffects doeffects: chan=0 whileless chan,numchan w=chan shl 2 ; always check for volume slides ; check chaneffect for other effects ; 0=arpegg, 5=pitch bend, 6=vibrato, 7=tremolo (vibrato is handled down below now) ; 16=retrigger, 17=note cut ; other values are ignored (do nothing) x=volslide(w) ifequal x,0,mi41 ; volume slide/bump y=x shr 1.sb+chanvol(w) ifless y.sd,65,mi46 > y=64 mi46: ifgreater y.sd,-1,mi47 > y=0 mi47: chanvol(w)=y ifequal x and 1,0,mi41 volslide(w)=0 mi41: y=chaneffect(w) x=chanparam.b(w) ifunequal y,0,mi82 ; arpeggio ifequal x,0,mi82 chanpar2(w)=_+4 ifless chanpar2(w),12,mi98 > chanpar2(w)=0 mi98: y=x shl chanpar2(w) and $F00 shr 7 ; chaninc(w)=periodconv/(chanper(w)*arpeggtab(y) shr 15) alterper(w)=chanper(w)*arpeggtab(y) shr 15 goto mx02 mi82: ifunequal y,5,mi50 ; pitch slide/bump x=chanparam(w) ; need signed y=x shl 4+chanper(w) ifless y,$F001,mi58 > y=$F000 ; do some clipping mi58: ifgreater y,$38F,mi59 > y=$390 mi59: ifequal chantarg(w),0,mi60 ifunequal chanpar2(w),2,mi60 ifless x.sd,0,mi61 ifless chantarg(w),y,mi65 goto mi60 mi61: ifless chantarg(w),y,mi60 mi65: y=chantarg(w) > chaneffect(w)=10 mi60: ; chaninc(w)=periodconv/y alterper(w)=y chanper(w)=y ifunequal chanpar2(w),1,mx02 chaneffect(w)=10 goto mx02 mi50: ifunequal y,7,mi78 ; tremolo z=x and 15 ; workaround for signedness issues... y=vibtable(x shr 4*count64 and 63)*z.sd shr 4.sb+chanvol(w) ifless y.sd,65,mi80 > y=64 mi80: ifgreater y.sd,-1,mi79 > y=0 mi79: chanvol(w)=y goto mx02 mi78: ; ifunequal y,16,mi18 ; retrigger ; chanpar2(w)=_+1 ; ifless chanpar2(w),x,mi18 ; chanpar2(w)=0 ; chanptr(w)=0 > chanloop(w)=16 ;; chanbound(w)=samlength(chansam(w) shl 2) mi18: ifunequal y,17,mi55 ; note cut ifgreater x,1,mi56 chanloop(w)=0 > chaneffect(w)=10 > x=1 mi56: chanparam(w)=x-1 mi55: mx02: chan=_+1 wend skipeffects: changecount=0 ; more stuff, that happens every tick chan=0 whileless chan,numchan w=chan shl 2 ; initialization for channels that key-on ifequal chanloop(w),0,ko01 ifgreater chanloop(w),16,ko01 chanloop(w)=_+1 ifless chanloop(w),17,ko01 chankey(w)=1 > chanfade(w)=65535 chankey2(w)=1 chanticks(w)=0 > chanenv(w)=-2 > chanvole(w)=0 ; chanbound(w)=samlength(chansam(w) shl 2) ko01: newsam=0 ifunequal chaneffect(w),6,mi73 ; vibrato newsam=1 x=chanparam.b(w) z=x and 15 ; workaround for signedness issues... y=vibtable(x shr 4*count64 and 63)*z.sd+$800*chanper(w) shr 11 ; chaninc(w)=periodconv/y alterper(w)=y mi73: ifequal zombiemod,0,ko02 ; zombie features x=chansam(w) shl 5+pattbase ifunequal newsam,0,ko20 ; the other vibrato overrides this vibrato y=x+27 > ifequal [y].b,0,ko20 ; innate vibrato z=y+1 y=vibtable([z].b*count64 shr 2 and 63)*[y].sb+$4000*chanper(w) shr 14 ; chaninc(w)=periodconv/y alterper(w)=y ko20: ; envelopes of dooom ifequal chankey(w),1,ko03 y=x+24 > loadbig y,[y].w z=chanfade(w)-y ; fadeout ifgreater z.sd,-1,ko13 > z=0 ko13: chanfade(w)=z ko03: ifunequal [x].w,0,ko12 ; no envelope? chanvole(w)=128 ; full volume ifequal chankey(w),1,ko02 chanloop(w)=0 > goto ko02 ; stop sample (key-off with no envelope) ko12: ifequal chanticks(w),0,ko04 y=chanenv(w)+x+1 > z=chanvole(w) z=_+[y].sb ifless z.sd,129,ko05 > z=128 ko05: ifgreater z.sd,-1,ko06 > z=0 ko06: chanticks(w)=_-1 > chanvole(w)=z ; decrement timer and modify volume goto ko07 ko04: chanenv(w)=_+2 ifequal chanenv(w),24,ko14 ; end of envelope? y=chanenv(w)+x ifunequal [y].w,0,ko08 ; sustain point? ifunequal chankey(w),1,ko08 ; key off yet? ko14: chanenv(w)=_-2 > goto ko07 ; no, stay put ko08: ifunequal [y].b,128,ko09 ; loop point? y=_+1 > chanvole(w)=[y].b y=x+26 > chanenv(w)=[y].b+1 shl 1 y=chanenv(w)+x ko09: chanticks(w)=[y].b goto ko12 ko07: ko02: ; Saturn stuff ifequal chankey2(w),0,mx10 changecount=_+1 sreg=w shl 3+$100000 > [sreg].b=0 ; key off mx10: chan=_+1 wend ; more Saturn stuff ifequal changecount,0,noexec1 [$1003E0].b=$10 ; key on execute (key off in this case) noexec1: ; write important stuff to SCSP chan=0 whileless chan,numchan w=chan shl 2 y=chanptr(w)+chanaddr(w)+$8300000 ; y=chanaddr(w)+$8300000 ifunequal loopexist(chansam(w)),0,mx12 > y=_-$200000 mx12: z=chanvol(w) ifequal zombiemod,0,mx09 z=chanfade(w)*z shr 7*chanvole(w) shr 16 mx09: z=voltable(z) ; ifunequal chanloop(w),0,mx08 > z=255 > y=$1100000 mx08: sreg=w shl 3+$10000C > [sreg].w=z ; set volume ; choose panning sreg=_+10 [sreg].w=pantable2(chanpan(w) shr 5) sreg=_-6 ; z=chanper(w) z=alterper(w) ifequal oldperiod(w),z,nofreq oldperiod(w)=z octave=-2048 whileless z,1300 > octave=_+2048 > z=_ shl 1 > wend z=57272720/z whileless z,22050 > octave=_-2048 > z=_ shl 1 > wend z=_-22050*190 shr 12 [sreg].w=octave+z and $7FFF ; set frequency nofreq: ifequal chankey2(w),0,noupdate sreg=_-$10 [sreg].w=y shr 16 > sreg=_+2 ; key/etc [sreg].w=y > sreg=_+2 ; address [sreg].w=loopstart(chansam(w) shl 2)-chanptr(w) > sreg=_+2 [sreg].w=looplength(chansam(w) shl 2)-chanptr(w) ; [sreg].w=loopstart(chansam(w) shl 2) > sreg=_+2 ; [sreg].w=looplength(chansam(w) shl 2) noupdate: chankey2(w)=0 chan=_+1 wend ifequal changecount,0,mi12 [$1003E0].b=$10 ; key on execute goto mi12 advancesong: nextsong=msongaddr+1 ifunequal msongbase+msonglen,nextsong,mi08 ; last pattern? ; turn all channels off and restart song x=0 > whileless x,numchan chanloop(x shl 2)=0 > x=_+1 wend nextsong=msongbase mi08: looppatt=[nextsong].b*pattsize > loopcount=0 nextpatt=looppatt return mi12: intcount=_+1 ifless intcount,mspeed,mi00 > intcount=0 mi00: goto intdone domdx: ; +++ MDX processing +++ chan=0 whileless chan,8 xx=chan shl 2 ifequal mdxsync(chan),1,nexttrack ; channel waiting? x=mdxportr(xx) ; portamento ifequal x,0,m111 mdxportv(xx)=x+_ gosub updatepitch m111: mdxtime(chan)=_-1 x=mdxtime(chan) ifunequal mdxlfotrig(chan),x,m120 ; LFO turning on? y=mdxlfo(xx) sreg=chan shl 7+$100012 [sreg].w=y > sreg=_+$20 [sreg].w=y > sreg=_+$20 [sreg].w=y > sreg=_+$20 [sreg].w=y m120: ifunequal mdxstop(chan),x,m113 ; early key-off? ; ifunequal mdxnolift(chan),0,m113 ; is key-off disabled? gosub mdxkeyoff m113: ifunequal mdxtime(chan),0,nexttrack ; previous note done? ; ifunequal mdxnolift(chan),0,m110 ; is key-off disabled? ; gosub mdxkeyoff ;m110: mdxportr(xx)=0 ; reset portamento mdxnolift(chan)=0 tptr=mdxp(xx) m99: y=[tptr].b > tptr=_+1 ; process new data ifgreater y,$7F,m100 ; rest ; gosub mdxkeyoff2 ; mdxnolift(chan)=0 mdxtime(chan)=y+1 > goto trackdone m100: y=_-$80 ifgreater y,$5F,m101 ; play note ; gosub mdxkeyoff ; mdxnolift(chan)=0 mdxportv(xx)=0 mdxnote(chan)=y gosub updatepitch sreg=chan shl 7+$100012 [sreg].w=$8000 > sreg=_+$20 ; LFO off [sreg].w=$8000 > sreg=_+$20 [sreg].w=$8000 > sreg=_+$20 [sreg].w=$8000 sreg=chan shl 7+$100000 [sreg].b=$08 > sreg=_+$20 [sreg].b=$08 > sreg=_+$20 [sreg].b=$08 > sreg=_+$20 [sreg].b=$18 ; key on mdxstop(chan)=mdxlen(chan)*[tptr].b shr 3-mdxtrim(chan) mdxtime(chan)=[tptr].b+1 > tptr=_+1 mdxlfotrig(chan)=$FF ifequal mdxplfo(chan),0,trackdone mdxlfotrig(chan)=mdxtime(chan)-mdxlfodel(chan) goto trackdone m101: ifequal y,$7A,mvolumeup ifequal y,$79,mvolumedown ifequal y,$77,mdiskeyoff ifequal y,$6E,msyncwait ifequal y,$68,m99 ; PCM8 mode (unimplemented) z=[tptr].sb > tptr=_+1 ; following commands have (at least) 1 parameter byte ifequal y,$7F,mtempo ifequal y,$7D,mloadvoice ifequal y,$7C,msetpan ifequal y,$7B,msetvol ifequal y,$78,mnotelen ifequal y,$70,mdelaykey ifequal y,$6F,msyncsend ifequal y,$6D,m99 ; noise (unimplemented) ifequal y,$6C,mplfo ifequal y,$6B,mlfostuff ifequal y,$6A,mlfostuff ifequal y,$69,mdelaylfo x=[tptr].b > tptr=_+1 ; following commands have 2 parameter bytes ifequal y,$7E,msetreg ifequal y,$76,mbeginloop ifequal y,$75,mloopback ifequal y,$74,mloopcut ifequal y,$73,mdetune ifequal y,$72,mporta ifequal y,$71,mendperf ifequal y,$67,mfadeout driverend=tptr ; driver crash goto programend mdxkeyoff: ifunequal mdxnolift(chan),0,m123 ; is key-off disabled? mdxkeyoff2: sreg=chan shl 7+$100000 [sreg].b=$00 > sreg=_+$20 [sreg].b=$00 > sreg=_+$20 [sreg].b=$00 > sreg=_+$20 [sreg].b=$10 m123: return mvolumeup: z=mdxvol(chan)-130 ifgreater z.sd,-129,m106 mdxvol(chan)=0 goto updatevolume mvolumedown: z=mdxvol(chan)-126 ifless z.sd,0,m106 mdxvol(chan)=$7F goto updatevolume mdiskeyoff: mdxnolift(chan)=1 goto m99 msyncwait: mdxsync(chan)=1 goto trackdone mtempo: intsamples=256-(z and $FF)*45 shr 6 goto m99 mloadvoice: x=vgmp y=$FF countdown y ifequal [x].sb,z,m104 x=_+27 nextcount goto m99 ; voice data not found? m104: mdxvoice(xx)=x y=$20+chan > x=_+1 callex ,opmupdate,y,ym1reg(y) and $C0 or [x].b x=_+1 > mdxmask(chan)=[x].b y=_+$20 ; some of the data I will write to ym1reg (quicker), some I will send to opmupdate (changes SCSP) whileless y,$58 x=_+1 > ym1reg(y)=[x].b > y=_+8 wend whileless y,$80 x=_+1 > callex ,opmupdate,y,[x].b > y=_+8 wend whileless y,$C0 x=_+1 > ym1reg(y)=[x].b > y=_+8 wend whileless y,$100 x=_+1 > callex ,opmupdate,y,[x].b > y=_+8 wend goto updatevolume msetpan: y=$20+chan > z=_ shl 6 callex ,opmupdate,y,ym1reg(y) and $3F or z goto m99 msetvol: ifless z.sd,0,m106 > z=mdxvtable(z and 15) m106: mdxvol(chan)=128+z updatevolume: w=ym1reg($20+chan) and 7 shl 3 x=mdxvoice(xx)+7 op=$60+chan whileless op,$80 ifequal mixtable(w),0,m107 y=[x].b+mdxvol(chan) ; ifless y,128,m108 > y=127 ;m108: callex ,opmupdate,op,y m107: op=_+8 > w=_+2 > x=_+1 wend goto m99 mnotelen: ifgreater z.sd,0,m114 mdxtrim(chan)=z mdxlen(chan)=0 > goto m99 m114: mdxtrim(chan)=0 mdxlen(chan)=8-z goto m99 mbeginloop: ifequal mdxcount(mdxnest(chan)),0,m116 mdxnest(chan)=_+8 m116: mdxcount(mdxnest(chan))=(z and $FF) goto m99 mdelaykey: goto m99 msyncsend: mdxsync(z and 7)=_ or 2 > goto m99 mplfo: ifgreater z.sd,-1,m118 mdxplfo(chan)=z and 1 > goto m99 m118: ; mdxplfo(chan)=1 ; enable it by default??? tptr=_+1 > x=[tptr].b*intsamples shr 5 > tptr=_+1 ; LFO freq ifless x,24,m119 > x=23 m119: x=mdxlfotable(x) shl 10 y=[tptr].sb > tptr=_+2 ; LFO amplitude ifgreater y.sd,-1,m124 > y=0-_ m124: ifless y,16,m121 > y=15 m121: mdxlfo(xx)=z and 3 shl 8+x+mdxlfotable2(y) goto m99 ;malfo: ;msetlfo: mlfostuff: ifequal z,-128,m99 ifequal z,-127,m99 tptr=_+4 goto m99 mdelaylfo: ifunequal z,0,m122 > z=1 m122: mdxlfodel(chan)=z goto m99 msetreg: callex ,opmupdate,z and $FF,x goto m99 mloopback: mdxcount(mdxnest(chan))=_-1 ifunequal mdxcount(mdxnest(chan)),0,m117 mdxnest(chan)=_-8 > goto m99 m117: tptr=z shl 8+x+_ goto m99 mloopcut: ifunequal mdxcount(mdxnest(chan)),1,m99 tptr=z shl 8+x+_-1 goto m99 mdetune: mdxfrac(xx)=z shl 8+x > gosub updatepitch goto m99 mporta: mdxportr(xx)=z shl 8+x goto m99 mfadeout: goto m99 mendperf: mdxfinish=1 shl chan or _ ifunequal mdxfinish,$FF,m109 ; all tracks ended, restart the song chan=0 whileless chan,8 xx=chan shl 2 mdxp(xx)=mdxloop(xx) mdxfrac(xx)=0 mdxtime(chan)=1 mdxnest(chan)=chan mdxnolift(chan)=0 chan=_+1 wend mdxfinish=0 goto intdone m109: ifequal z,0,m112 tptr=z shl 8+x+_ ; loop back goto m99 m112: tptr=_-3 ; halt right here trackdone: mdxp(xx)=tptr nexttrack: chan=_+1 wend ; do another loop through channels to check sync chan=0 whileless chan,8 ifunequal mdxsync(chan),3,m115 mdxsync(chan)=0 m115: chan=_+1 wend goto intdone updatepitch: x=mdxportv(xx) shr 8.sb+mdxfrac(xx) ym1reg($30+chan)=x shl 2 callex ,opmupdate,$28+chan,mdxnotes(x shr 6.sb+mdxnote(chan)) return dovgm: ssgupdate=0 ; +++ VGM processing +++ stime=_+735 ; stime=_+7 goto v110 v133: vtime=w and 15+_ v110: ifless stime,vtime,vgmdone ; read bytes from VGM data v100: w=[vgmp].b > vgmp=_+1 ifequal w shr 4,8,v133 ; DAC writes (there can be a lot of these) ifless w,$30,v100 ; ifequal w,$4F,skip01 ; game gear stereo (?) ifequal w,$50,sn76 ifless w,$51,skip01 ifequal w,$54,write2151 ifequal w,$55,ymwrite ifequal w,$A5,ymwrite2 ifequal w,$52,ymwrite ; YM-2612 ifequal w,$53,ymwrite2 ; ifequal w,$56,ymwrite ; YM-2608 ifequal w,$57,ymwrite2 ; ifless w,$60,skip02 ifunequal w,$61,v103 x=[vgmp].b > vgmp=_+1 > x=[vgmp].b shl 8+_ > vgmp=_+1 vtime=_+x > goto v110 v103: ifunequal w,$62,v104 > vtime=_+735 > goto v110 v104: ifunequal w,$63,v105 > vtime=_+882 > goto v110 v105: ifunequal w,$66,v106 ; end of data, time to loop vgmp=loopoff > goto v100 v106: ifunequal w,$67,v126 ; data block (skip) vgmp=_+2 x=[vgmp].b > vgmp=_+1 x=[vgmp].b shl 8+_ > vgmp=_+1 x=[vgmp].b shl 16+_ vgmp=_+x+2 > goto v100 v126: ifunequal w,$68,v129 ; PCM RAM write (skip) vgmp=_+11 > goto v100 v129: x=w shr 4 ifunequal x,$07,v107 > vtime=w and 15+_+1 > goto v110 v107: ; unimplemented PCM stuff ifequal w,$90,skip04 ifequal w,$91,skip04 ifequal w,$92,skip05 ifequal w,$93,skip10 ifequal w,$94,skip01 ifequal w,$95,skip04 ; other unknown data ifequal x,$0A,skip02 ifequal x,$0B,skip02 ifequal x,$0C,skip03 ifequal x,$0D,skip03 ifequal x,$0E,skip04 ifequal x,$0F,skip04 driverend=vgmp ; driver crash goto programend skip10: vgmp=_+5 skip05: vgmp=_+1 skip04: vgmp=_+1 skip03: vgmp=_+1 skip02: vgmp=_+1 skip01: vgmp=_+1 goto v100 sn76: x=[vgmp].b > vgmp=_+1 ifequal x and $80,0,databyte latchbyte=x and $70 ym2reg(x shr 4)=x and 15 goto v156 databyte: ifunequal latchbyte and $10,0,v158 ; volume bytes overwrite previous ifunequal latchbyte and $60,$60,v157 ; noise bytes overwrite previous v158: latchbyte=_+$80 v157: ym2reg(latchbyte shr 4)=x and 63 v156: chan=latchbyte and $60 sreg=chan+$10030D chan=_ shr 4 z=ym2reg(chan+9) ifequal z,15,psgchanoff [sreg].b=ym2reg(chan+9)*5+psgvol ; set PSG channel volume ifunequal chan,6,v160 ; noise channel sreg=_+3 [sreg].w=0 ; set SCSP frequency sreg=_-$10 > [sreg].w=$18A0 ; ensure the 'key' is down goto v100 v160: ; square wave channels z=ym2reg(chan) shl 4+ym2reg(chan+8) ifless z,5,psgchanoff sreg=_+3 z=223721/z octave=0 whilegreater z,1377 > octave=_+1 > z=_ shr 1 > wend whileless z,689 > octave=_-1 > z=_ shl 1 > wend z=_-689*12175 shr 13 [sreg].w=octave shl 11+z ; set SCSP frequency sreg=_-$10 > [sreg].b=$18 ; ensure the 'key' is down goto v100 psgchanoff: [sreg].b=$FF goto v100 armdivider: divarmed=1 > goto v100 setdivider3: x=fmclk shr 7 > goto v159 ; * 2/256 setdivider2: x=fmclk shr 1+fmclk shr 7 ; * 3/256 v159: ifequal divarmed,0,v100 nfreqlow=406421875/x nfreqhigh=nfreqlow shl 1-1 nfraction=x*169 shr 12 goto v100 ymwrite2: sreg=$100180 > bank=$100 goto v124 ymwrite: sreg=$100000 > bank=0 v124: x=[vgmp].b > vgmp=_+1 y=[vgmp].b > vgmp=_+1 ifequal chipmode,2,v132 ifequal x,$2D,armdivider ifequal x,$2E,setdivider3 ifequal x,$2F,setdivider2 v132: ifunequal x,$28,v111 ; key on/off ; ifgreater y and 7,2,v100 ; non-existant channels on OPN ; this section uses tweaked operator order for OPN 1-3-2-4 chan=y and 7 ifless chan,3,v130 > chan=_-1 > goto v131 ; second bank in OPN2 mode v130: opnkeys(chan)=y ; > y=opnmask(chan) and _ v131: sreg=chan shl 7+_ [sreg].b=y and $10 shr 1 > sreg=_+$20 ; set SCSP key on/off bits [sreg].b=y and $40 shr 3 > sreg=_+$20 [sreg].b=y and $20 shr 2 > sreg=_+$20 [sreg].b=y and $80 shr 4+$10 ; let 'er rip goto v100 v111: ; ifequal ym1reg(x+bank),y,v100 ym1reg(x+bank)=y w=x shr 4 ifequal w,0,ssgregs ; SSG registers chan=x and 3 ifequal chan,3,v100 ; non-existant channels ifequal w,3,newfreqmul ifequal w,$0B,newalgo ; ifequal w,$0A,newfreq ifequal x and $F4,$A0,newfreq op=x shr 2 and 3 sreg=chan shl 2+op shl 5+_ ifunequal w,4,v112 ; volume change sreg=_+$0D > [sreg].b=y shl 1 goto v100 v112: x=_ and 15+bank sreg=_+$0A ifequal w,5,newenv ; attack change ifequal w,6,newenv2 ; decay1 change ifequal w,7,newenv2 ; decay2 change ifequal w,8,newenv ; sustain/release change goto v100 newenv: z=ym1reg(x+$80) shl 1 ; sustain ranges from 0 to -42dB or -93dB on OPN ifunequal z and $1E0,$1E0,v123 > z=_+$200 ; on SCSP it can go 0 to -94.5dB v123: [sreg].w=ym1reg(x+$50) and $C0 shl keyscalef+z newenv2: sreg=_-2 [sreg].w=ym1reg(x+$50) and 31+(ym1reg(x+$60) and 31 shl 6)+(ym1reg(x+$70) and 31 shl 11) goto v100 ssgregs: sreg=bank shr 1+$100300 ; ifequal bank,0,v125 > sreg=_+$80 ;v125: ifgreater x,5,v119 ; SSG period chan=x and 6 z=ym1reg(chan+bank+1) shl 8+ym1reg(chan+bank) ifless z,5,updatessg z=250000/z octave=0 whilegreater z,1377 > octave=_+1 > z=_ shr 1 > wend whileless z,689 > octave=_-1 > z=_ shl 1 > wend z=_-689*12175 shr 13 sreg=chan shl 4+$10+_ [sreg].w=octave shl 11+z ; set SCSP frequency goto updatessg v119: ifequal x,6,v100 ifequal x,7,updatessg ; control register ifgreater x,11,v100 chan=x-8 ifequal y and 16,0,v120 > y=0 ; not handling envelope generator yet v120: sreg=chan shl 5+$0C+_ [sreg].w=15-y shl 3+ssgvol ; set SCSP volume updatessg: ifequal bank,0,v118 ssgupdate=_ or 2 > goto v100 v118: ssgupdate=_ or 1 > goto v100 ;chan3freq: ; ifequal ym1reg($27+bank) and $40,0,v100 ; chan=2 newfreq: ; OPN frequency = Hz * 2.3593 (at block/octave 4) (at 4MHz) ; SCSP frequency at 0,0 is 344.53125Hz ifequal x and 8,0,newfreqmul ifequal ym1reg($27+bank) and $40,0,v100 ; channel 3 special regs, no effect in normal mode chan=2 newfreqmul: sreg=chan shl 7+$10+_ ifunequal chan,2,normalmode ifequal ym1reg($27+bank) and $40,0,normalmode ; channel 3 special mode index=bank+$A9 > op=0 > gosub calcfreq index=_-1 > gosub calcfreq index=_+2 > gosub calcfreq index=_-8 > gosub calcfreq goto v100 normalmode: index=$A0+chan+bank op=0 whileless op,16 gosub calcfreq wend goto v100 calcfreq: w=ym1reg(index+4) and 7 shl 8+ym1reg(index) ; OPN freq y=ym1reg(index+4) shr 3 ; > y=1 shl y ; OPN block xx=ym1reg($30+chan+op+bank) > x=xx and 15 shl 1 ; OPN multiplier (and detune) ifunequal x,0,v117 > x=1 v117: ; z=x*y*w ; 440Hz -> 4,1038 -> 1038*32 z=w*x shl y ifless z,$100,badfreq octave=0 whilegreater z,nfreqhigh > octave=_+2048 > z=_ shr 1 > wend whileless z,nfreqlow > octave=_-2048 > z=_ shl 1 > wend z=_-nfreqlow*nfraction shr 14 ifless z,2,nodetune ifgreater z,1021,nodetune ; overflowing would be bad z=detunetable(z and $200 shr 2 or xx shr 4)+_ nodetune: [sreg].w=octave+z ; set SCSP frequency badfreq: sreg=_+$20 op=_+4 return newalgo: ; load SCSP modulator settings from a table sreg=chan shl 7+$0E+_ w=ym1reg($B0+chan+bank) x=w and 7 shl 3+mixtable.a ; y=w and 7 shl 3+algotable.a y=x+$40 ; set first slot self-feedback ; z=w and $38+$10 shl 9 z=fbtable(w and $38 shr 2) w=ym1reg($B4+chan+bank) ; choose panning xx=pantable(w shr 6) shl 8 ; LFO setting w=lfopitch(w and 7)+lfospeed(ym1reg($22) shl 1)+$200 op=0 whileless op,16 [sreg].w=[y].w+z > sreg=_+4 [sreg].w=w > sreg=_+4 [sreg].w=[x].w+xx sreg=_+$18 > x=_+2 > y=_+2 z=0 op=_+4 wend goto v100 write2151: ; *** YM-2151 stuff *** x=[vgmp].b > vgmp=_+1 y=[vgmp].b > vgmp=_+1 callex ,opmupdate,x,y goto v100 vgmdone: ; update SSG? ifequal ssgupdate and 1,0,v122 sreg=$100300 y=ym1reg(7) chan=0 whileless chan,3 x=y and 1 xor 1 shl 3 > y=_ shr 1 ifunequal ym1reg(chan+8),0,v121 > x=0 v121: [sreg].b=x > sreg=_+$20 chan=_+1 wend [sreg].b=$10 v122: ifequal ssgupdate and 2,0,v128 sreg=$100380 y=ym2reg(7) chan=0 whileless chan,3 x=y and 1 xor 1 shl 3 > y=_ shr 1 ifunequal ym2reg(chan+8),0,v127 > x=0 v127: [sreg].b=x > sreg=_+$20 chan=_+1 wend [sreg].b=$10 v128: intdone: endfunc return startmdx: beginfunc modaddr.d localvar x.d,y.d,h1.d,h2.d nfreqhigh=58131 nfreqlow=29066 nfraction=577 ; nfreqhigh=52021 ; nfreqlow=26011 ; nfraction=645 x=0 whileless x,$20 ym1reg(x)=0 > x=_+1 wend whileless x,$28 ym1reg(x)=$C0 > x=_+1 ; set default panning to both wend whileless x,$100 ym1reg(x)=0 > x=_+1 wend whileunequal [modaddr].b,0 > modaddr=_+1 > wend modaddr=_+1 > h1=modaddr ; voice data pointer vgmp=[modaddr].b shl 8 > modaddr=_+1 vgmp=[modaddr].b+h1+_ > modaddr=_+1 ; track data pointers and stuff x=0 whileless x,8 y=x shl 2 mdxp(y)=[modaddr].b shl 8 > modaddr=_+1 mdxp(y)=[modaddr].b+h1+_ > modaddr=_+1 mdxloop(y)=mdxp(y) mdxvoice(y)=vgmp mdxportv(y)=0 mdxfrac(y)=0 mdxlfo(y)=0 mdxtime(x)=1 mdxsync(x)=0 mdxmask(x)=15 mdxvol(x)=0 mdxnolift(x)=0 mdxlen(x)=0 mdxtrim(x)=0 mdxplfo(x)=1 mdxlfodel(x)=1 mdxlfotrig(x)=$FF mdxnest(x)=x mdxcount(x)=0 mdxcount(x+8)=0 mdxcount(x+16)=0 mdxcount(x+24)=0 x=_+1 wend mdxfinish=0 intsamples=46 ; 60Hz interrupt endfunc returnex 4 ; changed this to a subroutine so MDX stuff can use it opmupdate: beginfunc y.d,x.d localvar w.d,z.d,xx.d,chan.d,sreg.d,op.d,octave.d ifunequal x,$08,v150 ; key on/off sreg=y and 7 shl 7+$100000 [sreg].b=y and $40 shr 3 > sreg=_+$20 ; set SCSP key on/off bits [sreg].b=y and $10 shr 1 > sreg=_+$20 [sreg].b=y and $20 shr 2 > sreg=_+$20 [sreg].b=y and $08+$10 ; let 'er rip goto v155 v150: ; ifequal ym1reg(x),y,v155 ym1reg(x)=y w=x shr 5 ifequal w,0,v155 ; unimplemented chan=x and 7 sreg=chan shl 7+$100000 ifequal w,1,chanregs ; per-channel stuff ifequal w,2,newfreq2 op=x shr 3 and 3 sreg=op shl 5+_ ifunequal w,3,v151 ; volume change sreg=_+$0D > [sreg].b=y shl 1 goto v155 v151: x=_ and 31 sreg=_+$0A ifequal w,4,newenv3 ; attack change ifequal w,5,newenv4 ; decay1 change ifequal w,6,newenv4 ; decay2 change ; ifequal w,7,newenv3 ; sustain/release change ; goto v155 newenv3: z=ym1reg(x+$E0) shl 1 ; sustain ranges from 0 to -42dB or -93dB on OPN ifunequal z and $1E0,$1E0,v152 > z=_+$200 ; on SCSP it can go 0 to -94.5dB v152: [sreg].w=ym1reg(x+$80) and $C0 shl keyscalef+z newenv4: sreg=_-2 [sreg].w=ym1reg(x+$80) and 31+(ym1reg(x+$A0) and 31 shl 6)+(ym1reg(x+$C0) and 31 shl 11) goto v155 chanregs: ifless x,$28,newalgo2 ifless x,$38,newfreq2 ; insert LFO sensitivity stuff here (regs $38-$3F) goto v155 newalgo2: ; load SCSP modulator settings from a table sreg=_+$0E w=ym1reg($20+chan) x=w and 7 shl 3+mixtable.a ; y=w and 7 shl 3+algotable.a y=x+$40 ; set first slot self-feedback ; z=w and $38+$10 shl 9 z=fbtable(w and $38 shr 2) ; OPM panning xx=pantable(w shr 6) shl 8 op=0 whileless op,16 [sreg].w=[y].w+z > sreg=_+8 [sreg].w=[x].w+xx sreg=_+$18 > x=_+2 > y=_+2 z=0 op=_+4 wend goto v155 newfreq2: newfreq3: ; OPM frequency is specified as note number and fraction w=notetable(ym1reg($28+chan) and 15) shl 8+ym1reg($30+chan) ; OPM freq w=opmfreq(w shr 1 and $7FE) ; convert to linear using table y=ym1reg($28+chan) shr 4 ;> y=1 shl y ; OPM octave sreg=_+$10 op=$40+chan whileless op,$60 xx=ym1reg(op) > x=xx and 15 shl 1 ; OPM multiplier (and detune) ifunequal x,0,v154 > x=1 v154: ; z=x*y*w ; 440Hz -> 4,1038 -> 1038*32 z=w*x shl y ifequal z,0,v155 octave=0 whilegreater z,nfreqhigh > octave=_+2048 > z=_ shr 1 > wend whileless z,nfreqlow > octave=_-2048 > z=_ shl 1 > wend z=_-nfreqlow*nfraction shr 14 ifless z,2,nodetune2 ifgreater z,1021,nodetune2 ; overflowing would be bad z=detunetable(z and $200 shr 2 or xx shr 4)+_ nodetune2: [sreg].w=octave+z ; set SCSP frequency sreg=_+$20 op=_+8 wend v155: endfunc returnex 8 ; end of opmupdate routine startmod: beginfunc modaddr.d localvar x.d,y.d,z1.d,z2.d,z3.d,ftshift.d ftshift=0 intsamples=tempoconv/125 mspeed=6 > intcount=0 loopcount=0 numchan=4 > numsamp=16 > zombiemod=0 x=modaddr+$438 ifunequal [x].d,"6CHN".d,sm02 > numchan=6 > numsamp=32 sm02: ifunequal [x].d,"8CHN".d,sm03 > numchan=8 > numsamp=32 sm03: ifunequal [x].d,"M.K.".d,sm04 > numchan=4 > numsamp=32 sm04: ifunequal [x].d,"10CH".d,sm12 > numchan=10 > numsamp=32 sm12: ifunequal [x].d,"12CH".d,sm13 > numchan=12 > numsamp=32 sm13: ifunequal [x].d,"14CH".d,sm14 > numchan=14 > numsamp=32 sm14: ifunequal [x].d,"16CH".d,sm15 > numchan=16 > numsamp=32 sm15: x=_+4 ifunequal [x].d,"zomb".d,sm16 > zombiemod=1 x=_+10 > mspeed=[x].b x=_+1 > intsamples=tempoconv/[x].b ftshift=32 sm16: ; nexttotal=wavetotal+intsamples pattsize=numchan shl 8 ; callex ,printnt,"channels: ".a ; callex ,printhexr,numchan ; callex ,printnt,"samples: ".a ; callex ,printhexr,numsamp-1 modaddr=_+20 ; load sample metadata (Saturn version) x=1 ; start at 1, 0 is invalid whileless x,numsamp y=x shl 2 modaddr=_+22 ; skip name z3=[modaddr].b shl 9 > modaddr=_+1 ; don't forget about z3=[modaddr].b shl 1+_ > modaddr=_+1 ; byte order! ifless z3,$10000,sm65 > z3=$FFFF sm65: samlength(y)=z3 ; used only to calculate addresses! samftune(x)=[modaddr].b and 15 shl 1+ftshift > modaddr=_+1 z1=[modaddr].b and 127 > modaddr=_+1 ifless z1,65,sm10 > z1=64 ; volume max=64 sm10: samvol(x)=z1 z1=[modaddr].b shl 9 > modaddr=_+1 ; loop start z1=[modaddr].b shl 1+_ > modaddr=_+1 z2=[modaddr].b shl 9 > modaddr=_+1 ; loop length z2=[modaddr].b shl 1+_ > modaddr=_+1 ifgreater z2,2,sm07 > z2=0 > z1=0 sm07: ifgreater z3,z2,sm06 > z2=z3 ; loop can't be bigger than entire sample sm06: ifgreater z3+1-z2,z1,sm05 > z1=z3-z2 ; loop can't go outside sample sm05: loopstart(y)=z1 looplength(y)=z3 ; used for actual playback! loopexist(x)=0 ifequal z2,0,sm11 looplength(y)=z2+z1 ; don't play past end of loop (if any) loopexist(x)=1 sm11: x=_+1 wend samvol=0 msonglen=[modaddr].b > modaddr=_+2 ifequal msonglen,0,programend msongbase=modaddr > modaddr=_+128 msongaddr=msongbase ifequal numsamp,16,sm08 > modaddr=_+4 ; the spot where "M.K." is sm08: pattbase=modaddr pattoffs=[msongaddr].b*pattsize looppatt=pattoffs ; count patterns x=0 > y=msongaddr > numpatt=1 whileless x,128 ifgreater numpatt,[y].b,sm01 numpatt=[y].b+1 sm01: x=_+1 > y=_+1 wend ; store sample addresses y=numpatt*pattsize+pattbase x=1 whileless x,numsamp z1=x shl 2 samaddr(z1)=y > y=_+samlength(z1) ; also shift things left so we don't have to do it later ; samlength(z1)=_ shl 12 ; samrpoint(z1)=_ shl 12 ; samrend(z1)=_ shl 12 x=_+1 wend ; clear channel data x=chandata > y=chandataend whileless x,y [x].d=0 > x=_+4 wend x=0 > y=maxchan+1 whileless x,y chanpan(x shl 2)=dumbtable(x+1 and 2+zombiemod) ; default panning alterper(x shl 2)=$F000 chanper(x shl 2)=$F000 > x=_+1 wend endfunc returnex 4