incdir Include: include misc/AYPlayer.i include misc/mine.i incdir '' *** Amadeus AY Player for my DT2 Play AY Emulator Interface, (C) 1994 Raxoft *** This is an example how your own AY Player could look like... * 1 Initial version * 2 Added pushtranspose (143) a poptranspose (144) commands * 3 Protected volume envelope streams from causing enforcer hits ;This is how single Amadeus song looks like. ;Note that each module can contain several songs like this one... ;See aym_ structure for more info STRUCTURE AMAD_Song,0 UWORD ams_getabs ;where the original channelA located in ZX ram UBYTE ams_andsix ;strange F.F.s thing. Should be 31 normally. UBYTE ams_loops ;how many loops before songend UWORD ams_looplen ;len of one song loop in VBIs WORD ams_fadeoffset ;precise fade specification UWORD ams_fadelen ;how long to fade (not supported by DT so far) ;set to zero for neverending song UBYTE ams_assignA ;0-3 specifies, what amiga channel will be UBYTE ams_assignB ;assigned to each ay channel (A-C, noise) UBYTE ams_assignC UBYTE ams_assignN ;original ZX data follow. First three ptrs to init channels UWORD ams_channelA ;these are the original ZX data. UWORD ams_channelB ;ayp_getabs says, where the ams_channelA was UWORD ams_channelC ;located when it was in ZX RAM. ;and so on... ; The Player itself. See AYPlayer.i for more info about players in general. dd ;Section AmadeusCode,code commented because of QUICK_HACK AYPLAYERHEADER AMAD dc.b 12-1 ;custom ayfreq transpose: up octave - 1 tone AYbase ds.l 1 ;where ay registers should be "outed" AYass ds.l 1 ;where ay channel assignment should be copied AYfreq ds.l 1 ;from where we can take AY frequencies dc.w 0 ;initplayer dc.w 0 ;endplayer dc.w initsound-* dc.w 0 ;endsound dc.w interrupt-* dc.w 0 ;No pattern support (patterns are pretty dc.w 0 ;limitating and bad idea IMHO) dc.b 'Amadeus 1.1',0 dc.b '(C) 1987 Frantisek Fuka - Fuxoft',0 dc.b '(C) 1992-1997 Patrik Rak - Raxoft',0 even ;Here is the song preparation entry... PlayAY passes us ptr to our custom ;AMAD_SongStructure in a0, so it is our job to initialize our player pointers ;Also we calculate the songlen we will use later. ;Also, this is where we CAN assign AY channels to amiga channels ;(otherwise standard 0,1,2,3 assignment is made.) initsound lea ams_channelA(a0),a1 moveq #0,d0 move.w (a0)+,d0 ;careful with the words >32767 :-) sub.l d0,a1 move.l a1,getabsset ;for ZX -> Amiga RAM relocation move.b (a0)+,andsix ;FFs internal moveq #0,d0 move.b (a0)+,d0 ;loops mulu (a0)+,d0 ;looplen add.w (a0)+,d0 ;add/sub offset lea songlen,a1 move.w d0,(a1)+ ;store songlen move.w (a0)+,(a1) ;store fadelen move.l AYass(pc),a1 move.l (a0)+,(a1) ;copy channel assignment move.l a0,a1 ;point to channelA bra.b cold ;skip to orig. amad init routine ;This routine is called every 1/50th of second (usually) ;Here you should process your music data and load the AY regs ;also, you can do the song len counting here - return null if song should ;continue. To signal end, return non zero value telling how many VBIs should ;DT fade. interrupt bsr.w warm ;call orig. amadeus int. routine ;This is how signal songend routine should ALWAYS look like ;Note that you MAY NOT return nonzero value more then once per song!!! moveq #0,d0 ;signal continue lea songlen,a0 tst.w (a0) beq.b .exit ;already signalled subq.w #1,(a0)+ ;decrease songlen and point to fadelen bne.b .exit ;not end yet move.w (a0),d0 ;if zero, then will never fade. Otherwise fade for x VBIs .exit rts *************************************************************************** ;Amadeus (Amiga Version for AY-3-8912 Emulator) 1.00 by Raxoft 1992 ;Based on Original Amadeus V8 (C) Fuxoft 1987 ;I am not going to comment this source too detailed, because no one is going ;to read it and understand Amadeus DATA format anyway... So only roughly... ;This code is rewrite from Z80 code, as you can see from the code design. ;I did only few optimalizations, because I didn't want to create some ;incompatibility by my mistake... So have it in mind and don't blame me... ;Code has been sligthly rearanged by me and Delirium in 1994. ;=========================================================================== ;Support routines for easier Z-80 emulation in 68000 code ;GetAbs replaces this code, which is used very often in amadeus (and other ;routines as well): ; LD (HL),E ;imagine that HL is a1 and DE is D7 ; INC HL ; LD (HL),D ; INC HL ;and DE is not just some value, it is another Address Pointer to ZX data... ;This allows that there is no need to change the original data at all... getabs moveq #0,d7 ;Simulates reading word address from ZX move.b (a1)+,d6 ;memory and recalculates it to the move.b (a1)+,d7 ;amiga memory. Note that ZX has LSB lsl.w #8,d7 ;followed MSB. And word can be on move.b d6,d7 ;odd address as well. add.l getabsset,d7 ;Returns Address in d7, also increases rts ;that pointer... ;Because data in ZX memory often use direct addressing, it is necessary to ;recalculate it to your Amiga memory. Let's say you have ZX data, which were ;located on 53000, and you have them on $534a3 in your amiga memory. So you ;have to set this to $534a3-53000, otherwise the getabs won't work properly... getabsset dc.l 0 ;Base for your ZX memory ;=========================================================================== ;This code initiates all important values needed later. ;On ZX it also installed IM2 routine. ;a1 contains pointer to first data stream cold lea worksp,a6 ;Workspace bsr.b getabs ;Read three channel pointers move.l d7,(a6) ;and install them to workspace bsr.b getabs move.l d7,wrk2(a6) bsr.b getabs move.l d7,wrk3(a6) move.w #$0108,d0 ;Set note len to 1 and strobe to 8 move.w d0,wrk1+len(a6) move.w d0,wrk2+len(a6) move.w d0,wrk3+len(a6) lea stacks+stacksize-worksp(a6),a1 ;Init Channel stacks move.l a1,stk1-worksp(a6) lea stacksize(a1),a1 move.l a1,stk2-worksp(a6) lea stacksize(a1),a1 move.l a1,stk3-worksp(a6) clr.w wrk1+flags(a6) ;reset flags and transpositions clr.w wrk2+flags(a6) clr.w wrk3+flags(a6) clr.l wrk1+freqbeg(a6);reset frequency envelopes starts clr.l wrk2+freqbeg(a6);don't think it is necessary clr.l wrk3+freqbeg(a6);but recoded it as well :-) clr.l wrk1+envbeg(a6);reset volume envelopes starts clr.l wrk2+envbeg(a6);this is checked against zero to prevent clr.l wrk3+envbeg(a6);enforcer hit in some amadeus tunes sf barva-worksp(a6);Clear noise color/frequency rts ;=========================================================================== ;This was the original routine which was called by Z80 every interrupt, pushed ;registers, called warm, poped registers, EI, RET. Now, it is the same as WARM ;indeed ;inter equ warm ;=========================================================================== ;This routine originally disabled IM2, turned on IM1 and stopped AY sound. ;Now it does only the latter, although you can also call AYoff and it will do ;the same as calling StopAMAD & AYemulate. It just puts 255 to the strobe ;register ;No need for this routine, since the playAY will turn of the AY anyway ;(something not possible on ZX :-) ;stopamad lea worksp(pc),a6 ; moveq #-1,d0 ; bra.b stopout ;=========================================================================== ;Main routine, should be called every 50th of second, followed by the call ;to AYemulate. ;Of course that you first have to install Data for amadeus, THEN call Cold, ;and THEN you can call this. ;It does everything: advances in all channels, generates new notes, new ;envelopes, all effects, everything... warm lea worksp,a6 ;Workspace pointer to a6 as base lea (a6),a5 ;Proccess Channel 1 move.l stk1-worksp(a6),a4 ;get stack pointer moveq #1,d0 ;channel number bsr.b main ;Oh man, DO IT !!! move.l a4,stk1-worksp(a6) ;store stack pointer lea wrk2(a6),a5 ;Channel 2 move.l stk2-worksp(a6),a4 moveq #2,d0 bsr.b main move.l a4,stk2-worksp(a6) lea wrk3(a6),a5 ;Channel 3 move.l stk3-worksp(a6),a4 moveq #3,d0 bsr.b main move.l a4,stk3-worksp(a6) move.b wrk3+master(a6),d0 ;Get enable bits of all add.b d0,d0 ;three channels and generate or.b wrk2+master(a6),d0 ;strobe add.b d0,d0 or.b wrk1+master(a6),d0 stopout moveq #7,d5 ;"Out" it to the AY backup reg. bsr.b out ;and fall down to outy ;=========================================================================== ;routine which originally OUTed all register stored in memory to the AY ;at once. Now, it just copies them from AY Backup registers (note that ;everything was prepared in memory at first) to the "AY register" (in our ;case, just another part of memory:-) ;Originally it was send there from reg 13 to reg 0, but here is no reason ;to do that so... Although in Z80 it was better solution :-) outy moveq #14/2-1,d5 ;14 register to copy lea output-worksp(a6),a1 ;pointer to my ay output buffer move.l AYbase,a3 ;pointer to AY registers outy1 move.w (a1)+,(a3)+ ;And feed them all :-) dbra d5,outy1 rts ;=========================================================================== ;Originally it was a little longer routine, which stored one byte to the ;output table. Motorola makes it in one instruction :-) out move.b d0,output-worksp(a6,d5.w) rts ;=========================================================================== ;Main processing routine for single channel ;a4 stack pointer for this buffer main move.b d0,hlas-worksp(a6) ;store channel number subq.b #1,len(a5) ;decrease note length, skip beq.w nova ;if we need to read new one. hl11 subq.b #1,envper(a5) ;dec volume envelope delay counter bne.B hlst ;skip if still same volume move.l envpos(a5),d0 ;Pointer to Vol Envelope stream beq.b nohit1 ;!!!Just to be sure, FF is sclerotic !!! move.l d0,a1 ;On ZX it loads 243 from 0, but here... ? hl1 move.b (a1)+,d0 ;Read next volume/command cmp.b #128,d0 ;is it Jump? bne.b neskhl ;Skip if not. bsr.w getabs ;Get the Jump destination move.l d7,a1 ; bra.b hl1 ;And continue there... neskhl cmp.b #30,d0 ;0-30 used for volume 0-15 bcs.b neskh0 ;followed by volume delay sub.b #50,d0 ;values above 50 used for nohit1 move.b d0,vol(a5) ;volume 0-15, which has automatically move.b #1,envper(a5) ;env volume delay = 1 (often used -> bra.b contx1 ;safes data) neskh0 move.b d0,vol(a5) ;Ranges 0-30 and 50-255 are strange, move.b (a1)+,envper(a5);but better than try to always add ;128 or like by yourself... And FF ;wrote his musics in asm, of course... contx1 move.l a1,envpos(a5) ;Store the volume envelope position hlst move.w freq(a5),d0 ;Skip if freq is 0 -> Generates beq.b playit ;no tone btst #2,flags(a5) ;Is it first Edge? If it is, so go and bne.b playit ;playit move.l freqpos(a5),d0 ;Pointer to Freq envelope stream beq.b playit ;!!!Just to be sure, FF is sclerotic !!! move.l d0,a1 ;On ZX it loads 243 from 0, but here... ? frqc1 moveq #0,d0 ;Get next freq/command move.b (a1)+,d0 move.l a1,freqpos(a5) ;Store pointer to stream for next time cmp.b #128,d0 ;Is it jump? bne.b nojpfr ;skip if not bsr.w getabs ;Get jump destination address move.l d7,a1 ;as new stream bra.b frqc1 ;and continue there nojpfr cmp.b #130,d0 ;Is it Tone Slide On command? bne.b nicfr1 ;Nope bset #3,flags(a5) ;Set that we will do Tone Slides bra.b frqc1 ;next freq/command nicfr1 cmp.b #131,d0 ;Is it Freq Slide On command? bne.b nicfr2 ;Nope bclr #3,flags(a5) ;Clear it, so we now do Freq Slides bra.b frqc1 ;next freq/command nicfr2 cmp.b #132,d0 ;Is it Tone/Noise Toggler? bne.b nicfr3 ;Nope eor.b #9,master(a5) ;Toggle Tone / Noise on flags bra.b frqc1 ;Next command ;d0 was no command, so it is either Tone or Freq change value (depends) nicfr3 btst #3,flags(a5) ;Are we in Tone Slide mode? beq.b nojpf0 ;Skip if in Freq Slide mode. add.b tone(a5),d0 ;Add/Sub value from currently played move.b d0,tone(a5) ;note and store it back ;Note that following part can perhaps read values out of table, if not ;used carefully in music data! Also note, that value 0 is not here checked for ;silence - so it sometimes happened, that when one channel was playing only ;noise (like drum), and the transposition unforunatelly set the note to zero, ;value from (freq-2) was loaded to freq. It was OK till it was not zero (it ;was never so on ZX Amadeus), but when it happened (and of course it happened), ;value of 0 was loaded to freq here, thus disabling tone (which was disabled ;by strobe anyway), but also noise (by seting volume to zero), although it ;should play... Took me quite a while, why in several songs the drums ;sometimes sounded quite strange... That's the reason why I put ;dc.w 65535 before freqs table in FXS, just to be NOT ZERO... Now, the ;freq table goes up to 13.75 Hz, so there is a non zero value... add.b d0,d0 ;Get AY frequency from the table move.l AYfreq(pc),a3 ;Note that first note is 1, move.w (a3,d0.w),freq(a5) ;not 0 - 0 is for silence bra.b playit ;and go to play sound nojpf0 ext.w d0 ;So it is Freq change value add.w d0,d0 ;Correction - change AY freq change to add.w d0,d0 ;Amiga freq change... add.w d0,d0 add.w d0,freq(a5) ;And store new frequency ;Well, so volume and Frequency is already generated, so it is showtime playit move.b barva-worksp(a6),d0 ;"Out" Noise freq and.b andsix-worksp(a6),d0 ;FF sometimes use only range 0-15 ?!?!? moveq #6,d5 ;(Why is it here in single channel processing?) bsr.w out ;(Well, don't ask me :-) bclr #2,flags(a5) ;Set flag First Edge of Note played moveq #7,d5 add.b hlas-worksp(a6),d5 ;Get Channel number + 7 (8-10) move.w freq(a5),d0 ;Check frequency - if zero, then beq.b mlc ;no tone AND no noise !!! move.b vol(a5),d0 ;Else "Out" channel volume mlc bsr.w out ;Channel volume OR zero subq.b #8,d5 ;Get channel number - 1 (0-2) add.b d5,d5 ;Get Freq reg position (0,2,4) move.b freq(a5),d0 ;"Out" frequency bsr.w out addq.b #1,d5 move.b freq+1(a5),d0 bsr.w out navrat rts ;return ;=========================================================================== ;Just store channel position as we proccess it... Originally much longer ;routine, of course :-), because of z80... ;putpos move.l a1,(a5) ; rts ;changed to inline instruction ;and changed spec subroutines so it is not almost used... :-) putpos macro move.l a1,(a5) endm ;=========================================================================== ;This is called whenever new note is going to be played nova move.l (a5),a1 ;Get channel position nova2 moveq #0,d0 ;Get next note/command move.b (a1)+,d0 putpos ;Remember position btst #7,d0 ;Is it special command? bne.B spec ;Go there if it is moveq #0,d5 ;Fake freq = 0 -> will cause silence ;later tst.b d0 ;Is the note 0 (silence) ? beq.B notran ;Go further if it is... add.b trans(a5),d0 ;Add current Transposition to note move.b d0,tone(a5) ;and store it bclr #3,flags(a5) ;Turn Freq Slides on (default) add.b d0,d0 ;Get note frequency move.l AYfreq,a3 move.w (a3,d0.w),d5 notran move.b (a1)+,len(a5) ;Get number of VBIs for which it will putpos ;sound (0=256) and store pointer move.w d5,freq(a5) ;store frequency or 0 for silence move.l freqbeg(a5),freqpos(a5) ;Re-Set Freq Env stream bset #2,flags(a5) ;Say This is First Edge of Note ;to prevent early Freq Stream proccessing ;in case we will now jump to hl11 to ;keep everything synchronized ;Next part is little tricky. It handles continuous Volume Envelope streams, ;so eg. Volume can still fade even if we play a lot different notes. ;Bit 0 of flags says, if we want to turn this feature on, bit 1 says if ;it is already on. To turn it off, it is necessary turn both bits off. ;It is quite clever, allows control even in music nesting etc. btst #1,flags(a5) ;Already continuous Vol Envs? bne.w hl11 ;Proccess it if so.. btst #0,flags(a5) ;Do we want turn it on? beq.b nic0 ;Skip if not... bset #1,flags(a5) ;From now, Vol Envs are continuous... nic0 move.l envbeg(a5),d0 ;Get begin of Vol Env stream move.l d0,a3 ;!!!Just to be sure, FF is sclerotic !!! beq.b nohit2 ;On ZX it loads 243 from 0, but here... ? move.b (a3)+,vol(a5) ;and load first volume and delay move.b (a3)+,envper(a5);note that cannot use that +50 mode nohit2 move.l a3,envpos(a5) ;here. Re-Set Vol Env position. bra.w playit ;And finally, play the note... ;=========================================================================== ;This part proccesses all speciall commands found in the main channel stream, ;like gosubs, for-nexts, etc. ;Note that we trust the data - so there is no Command Code check, and no ;Stack overflow check because of too much Gosubs or For-Nexts spec lea nova2(pc),a0 ;to save some bytes add.b d0,d0 ;Execute proper routine... ;Perhaps should be slightly lea.l jptabl(pc,d0.w),a3 ;more CRASH PROOF :-) add.w (a3),a3 jmp (a3) jptabl dc.w jp-*,call-*,for_-*,next-* dc.w zm6-*,zmch-*,frqsta-*,def-* dc.w transp-*,ret_-* dc.w join-*,unjoin-*,usr-* dc.w add6-*,addtra-* dc.w pushtra-*,poptra-* ;=========================================================================== jp bsr.w getabs ;Stream Jump to some address move.l d7,a1 ;load new position. jmp (a0) ;=========================================================================== call bsr.w getabs ;Stream call. Remember position move.l a1,-(a4) move.l d7,a1 ;on the stack and load new one. jmp (a0) ;=========================================================================== for_ move.b (a1)+,-(a4) subq.l #1,a4 move.l a1,-(a4) ;For. Remember count and position jmp (a0) ;on the steck ;=========================================================================== next subq.b #1,5(a4) beq.b next1 move.l (a4),a1 jmp (a0) next1 addq.l #6,a4 jmp (a0) ;=========================================================================== zm6 move.b (a1)+,barva-worksp(a6) ;change Frequency of Noise Channel jmp (a0) ;=========================================================================== zmch move.b (a1)+,master(a5);Select Tone AND/OR Noise On/Off jmp (a0) ;Note also Toggler in Freq Env Stream, used ;to generate pretty interesting sounds... ;=========================================================================== def bsr.w getabs ;Define Volume Envelope Stream begin move.l d7,envbeg(a5) ;for following notes jmp (a0) ;=========================================================================== transp move.b (a1)+,trans(a5) ;Change transposition directly jmp (a0) ;=========================================================================== ret_ move.l (a4)+,a1 ;Return from subchannel. Get position from stack. jmp (a0) ;=========================================================================== frqsta bsr.w getabs ;Define Frequency Envelope Stream begin move.l d7,freqbeg(a5) ;for following notes jmp (a0) ;=========================================================================== join bset #0,flags(a5) ;Turn on Continuous Volume Envelopes bra.b unjin ; bclr #1,flags(a5) ; jmp (a0) ;=========================================================================== unjoin bclr #0,flags(a5) ;Turn off Continuous Volume Envelopes unjin bclr #1,flags(a5) jmp (a0) ;=========================================================================== usr addq.l #2,a1 ;This function was used for USER calls to any ;Z80 code (eg. for synchronizing picture with jmp (a0) ;sound as in Indiana Jones 3 by F.F.) ;Not implemented, OF COURSE :-) ;=========================================================================== add6 move.b (a1)+,d0 ;Increase/Decrease Noise Channel Freq add.b d0,barva-worksp(a6) ;Used for some effects... jmp (a0) ;=========================================================================== addtra move.b (a1)+,d0 ;Increase/Decrease Transposition level add.b d0,trans(a5) jmp (a0) ;=========================================================================== pushtra move.b trans(a5),d0 ;Store Transposition level move.w d0,-(a4) jmp (a0) ;=========================================================================== poptra move.w (a4)+,d0 ;Retore Transposition level move.b d0,trans(a5) jmp (a0) ;=========================================================================== section AmadeusData,bss output ds.b 14 ;Output space for AY registers barva ds.b 1 ;Noise Frequency hlas ds.b 1 ;Temporary, for channel number stk1 ds.l 1 ;Stack pointers for all three channels stk2 ds.l 1 stk3 ds.l 1 stacksize equ 128 ;even bigger then on ZX I think... worksp ds.b 3*30 ;WorkSpace for three channels, see below stacks ds.b 3*stacksize ;Space for Channel Stacks, used to store ;Gosub & For-Next informations. wrk1 equ 0 ;Offset definitions wrk2 equ 30 wrk3 equ 60 data equ 0 ;l ;Current Main Channel position len equ 4 ;b ;Delay remaining to the end of note master equ 5 ;b ;Partial Strobe (0,3), see AY strobe envpos equ 6 ;l ;Current Volume Envelope Stream Pos. envper equ 10 ;b ;Delay till pickup of next volume vol equ 11 ;b ;current volume freq equ 12 ;w ;current frequency envbeg equ 14 ;l ;Start of Volume Envelope Stream freqpos equ 18 ;l ;Current Freq Env Stream position. flags equ 22 ;b ;Flags - see code for more trans equ 23 ;b ;Current Transposition freqbeg equ 24 ;l ;Start of Freq Env Stream tone equ 28 ;b ;Current note songlen ds.w 1 fadelen ds.w 1 andsix ds.b 1