| ;;@
;;@ C P C - A Y M O N O & S T E R E O - S A M P L E - P L A Y E R ;;@ ;;@ (c) 2019-2020 ;;@ Prodatron / SymbiosiS (J�rn Mika) ;;@ Devilmarkus (Markus Hohmann) ;;@ Arnoldemu (Kevin Thacker) ;;@ AST (David Ast) ;;@ OLenz (Oliver Lenz) ;;@ org #8D00 begin: jp start ds &100-3 write "player.bin" ;;write direct "a:code.bin" lookuptable equ begin TXT_OUTPUT equ #BB5A start: ;; init conversion table for sample playback using AY-3-8912 call init_table ld a,8 call set_speed2 LD HL,BufferRsx LD BC,PtrRsx JP #BCD1 BufferRsx: DS 4 PtrRsx: DW RSX_TABLE JP SetBank JP playsample JP playclip JP setspeed JP 14khz JP 12khz JP 11khz JP 8khz JP 6khz JP 5_5khz JP 4khz JP info_message jp playmono jp PlaySampled jp setdriver jp setmonospeed jp setmonospeed jp setleft jp setright jp setcenter jp setstripes RSX_TABLE: DB "PLAYBAN","K"+#80 DB "PLAYSAMPL","E"+#80 DB "PLAYCLI","P"+#80 DB "PLAYSPEE","D"+#80 DB "FREQ.1","4"+#80 DB "FREQ.1","2"+#80 DB "FREQ.1","1"+#80 DB "FREQ.","8"+#80 DB "FREQ.","6"+#80 DB "FREQ.5.","5"+#80 DB "FREQ.","4"+#80 DB "PLAYERHEL","P"+#80 defb "PLAYMCLI","P"+&80 ;; |PLAY,<start>,<length> ;; Play sample using chosen driver ;; Sample data is 8-bit signed mono defb "PLAYMON","O"+&80 defb "DRIVE","R"+&80 ;; |DRIVER,<index> ;; 0 = AY ;; 1 = digiblaster ;; 2 = amdrum defb "MONOSPEE","D"+&80 defb "SPEE","D"+&80 ;; Set playback rate. ;; ;; 0 = 1KHz (1000hz) ;; 1 = 1KHz (1000hz) ;; 2 = 2Khz (2000hz) ;; 3 = 3Khz (3000hz) ;; 4 = 4Khz (4000hz) ;; 5 = 5Khz (5000hz) ;; 6 = 6Khz (6000hz) ;; 7 = 7Khz (7000hz) ;; 8 = 8Khz (8000hz) ;; 9 = 9Khz (9000hz) ;; 10 = 10Khz (10000hz) ;; 11 = 11Khz (11025hz) ;; 12 = 12Khz (12000hz) ;; 13 = 13Khz (13000hz) ;; 14 = 14Khz (14000hz) ;; 15 = 15Khz (15000hz) ;; 16 = 16Khz (16000hz) ;; 17+ -> 1Khz defb "LEF","T"+&80 ;; When AY driver, playback to channel A defb "RIGH","T"+&80 ;; When AY driver, playback to channel B defb "CENTE","R"+&80 ;; When AY driver, playback to channel C defb "STRIPE","S"+&80 ;; When AY driver, playback to channel C DB 0 setstripes: cp 1 jp nz,PlayError ; Quit if no parameter supplied (could add error handling) ld c,(ix+0) ld b,(ix+1) ld hl,17 ; Get the Maximum Speed Count sbc hl,bc jp c,SyntaxError ld a,&7f ld (borderstereo+2),a ld (borderstereo2+2),a ld (borderay+2),a ld (borderdigi+2),a ld (borderamdrum+2),a ld a,(ix+0) cp 17 jr z,nostripes setstripespen ld (borderay+1),a ld (borderdigi+1),a ld (borderamdrum+1),a ld (borderstereo+1),a ld (borderstereo2+1),a ret nostripes: ld a,&1f ld (borderay+2),a ld (borderdigi+2),a ld (borderamdrum+2),a ld (borderstereo+2),a ld (borderstereo2+2),a ld a,0 jr setstripespen info_message ld a,1 call &bc0e LD HL,MESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET MESSPRI ;HL=ZEIGER ;GIBT TEXTE AUS LD A,(HL) OR A RET Z INC HL CALL TXT_OUTPUT JR MESSPRI ret ds &ac OldStart jp start ;; old code had it's entry point here, maybe users still init with CALL &9000 MESSAGE DB 15,3,"****************************************" DB 15,3,"* ",15,2,"Stereo & Mono Sampleplayer ",164," 2020",15,3," *" DB 15,3,"* ",15,1,"Prodatron of SymbiosiS & Devilmarkus",15,3," *" DB 15,3,"* ",15,2,"With help from Arnoldemu, AST, OLenz",15,3," *" DB 15,3,"****************************************" DB 15,3," Commands:",13,10 DB 15,1,"|PLAYERHELP - This info",13,10 DB 15,1,"|PLAYBANK,<bank> - set RAM bank 0-256",13,10 DB 15,1,"|STRIPES,<pen> - 0-16, 17 disables",13,10, DB 15,2,"Stereo section",13,10 DB 15,1,"|PLAYSPEED,<speed> - set speed 1-255",13,10 DB 15,1,"|FREQ.<frequency> - set accurate speed",13,10 db 15,2,"Possible values are: 14,12,11,8,6,5.5,4",13,10 DB 15,3,"Example: |FREQ.12 - Set player to 12khz",13,10 DB 15,1,"|PLAYSAMPLE,<startbank>,<size>",13,10 DB 15,2,"<size> is the number of banks to play",13,10 DB 15,1,"|PLAYCLIP,<startaddress>,<length>",13,10 DB 15,2,"Mono section",13,10 DB 15,1,"|DRIVER,<d> - 0=PSG,1=DIGIBL.,2=AMDRUM",13,10 DB 15,1,"|MONOSPEED,<speed> - Speed = khz(4-16)",13,10 DB 15,1,"|LEFT,|CENTER,|RIGHT - PSG play channel",13,10 DB 15,1,"|PLAYMONO,<startbank>,<size>",13,10 DB 15,1,"|PLAYMCLIP,<startaddress>,<length>",13,10,0 SYNTAXMESSAGE DB "ERROR! Bad parameters",7,13,10,0 SHORTMESSAGE DB "ERROR! Samplesize too small",7,13,10,0 LARGEMESSAGE DB "ERROR! Samplesize too large",7,13,10,0 ADDRESSLOW DB "ERROR! Start address error, min. &4000",7,13,10,0 ADDRESSHIGH DB "ERROR! Start address error, max. &7CFF",7,13,10,0 ERRORMESSAGE DB "ERROR! Bank out of range",7,13,10,0 RANGEMESSAGE DB "ERROR! Speed out of range",7,13,10,0 PLERRORMESSAGE DB "ERROR! Command error - |PLAYERHELP for help",7,13,10,0 setspeed: cp 1 jp nz,PlayError ; Quit if no parameter supplied (could add error handling) ld c,(ix+0) ld b,(ix+1) ld hl,255 ; Get the Maximum Speed Count sbc hl,bc jp c,SyntaxError ld hl,0 ; Get the minimum sampleaddress sbc hl,bc jp nc,SyntaxError ld a,(ix+0) ld (speedbuffer),a ret 14khz: ld a,1 ld (speedbuffer),a ret 12khz: ld a,6 ld (speedbuffer),a ret 11khz: ld a,10 ld (speedbuffer),a ret 8khz: ld a,27 ld (speedbuffer),a ret 6khz: ld a,47 ld (speedbuffer),a ret 5_5khz: ld a,54 ld (speedbuffer),a ret 4khz: ld a,89 ld (speedbuffer),a ret SyntaxError: LD HL,SYNTAXMESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET TooShort: LD HL,SHORTMESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET TooLarge: LD HL,LARGEMESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET AddressTooLow: LD HL,ADDRESSLOW ; Zeiger auf Fehler-Text CALL MESSPRI RET AddressTooHigh: LD HL,ADDRESSHIGH ; Zeiger auf Fehler-Text CALL MESSPRI RET BankOutOfRange: LD HL,ERRORMESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET OutOfRange: LD HL,RANGEMESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET PlayError: LD HL,PLERRORMESSAGE ; Zeiger auf Fehler-Text CALL MESSPRI RET PlaySampled cp 2 jp nz,PlayError ld c,(ix+2) ld b,(ix+3) ;; get lowest bank ld hl,256 ; Get the Maximum Bank Count sbc hl,bc jr c,SyntaxError ; Quit if we're requesting an invalid bank ld e,(ix+0) ld d,(ix+1) ;; get amount of banks to play ld hl,256 ; Get the maximum banksize sbc hl,de jp c,SyntaxError ld hl,&0 ; Get the minimum sampleaddress sbc hl,de jp nc,SyntaxError ld h,b ld l,c ;; We load &4000 as start address and also as size SampleLoop: ld (ix+0),l ld (ix+1),h push hl push de call ForceBank ;; first call of first bank ld BC,&4000 ld (ix+0),c ld (ix+1),b ld (ix+2),c ld (ix+3),b call driver ;; Spielt den aktuellen Sample ab ld a,(hl) ;; [2] ; *** RETMOD *** cp &FF jr z,exitSample ; *** RETMOD *** pop de pop hl inc hl call &bb09 CP 252 jp z,exitSample dec DE ld a,d or e jp nz,SampleLoop exitSample ret SetBank: cp 1 jp nz,PlayError ; Quit if no parameter supplied (could add error handling) ForceBank: ld c,(ix+0) ld b,(ix+1) ; Get Bank Number Passed in Parameter BankOffsetCalc: ld hl,256 ; Get the Maximum Bank Count sbc hl,bc jp c,BankOutOfRange ; Quit if we're requesting an invalid bank LD HL,4mb_banks ; Point to Table add hl,bc add hl,bc ; Add 2 * BC to HL to point to correct word ld C,(HL) inc HL ld B,(HL) ; Get Port Number ld a,b or c ret z ; Safety Check Quit if Zero Word Selected out (C),C ; Switch the bank in ret samplesize: ds 2 playclip: cp 2 jp nz,PlayError ld c,(ix+2) ld b,(ix+3) ld hl,&3fff ; Get the minimum sampleaddress sbc hl,bc jp nc,AddressTooLow ld hl,&7D00 ; Get the maximum sampleaddress sbc hl,bc jp c,AddressTooHigh ld c,(ix+0) ld b,(ix+1) ld hl,767 ; Get the minimum samplesize sbc hl,bc jp nc,TooShort ld hl,&4001 ; Get the maximum samplesize sbc hl,bc jp c,TooLarge psg2clip ld hl,&4000 ld (psg2clip1+1),hl di ;lock interrupts exx ex af,af' push af push bc ;save 2nd register set push de push hl ld bc,#f6c0 ;prepare 2nd register set ld de,#0f80 exx ld (psg2clip5+1),sp ;save stackpointer psg2clip1 ld d,(ix+0) ld e,(ix+1) scf ccf rr D rr E ld l,(ix+2) ld h,(ix+3) ld sp,hl ld bc,#f401 ld hl,10*256+8 psg2clip2 ;*** SAMPLE LOOP out (c),l ;4 L=channel A exx ;1 pop hl ;3 HL'=next 2 bytes ld a,l ;1 1st byte lower nibble (left channel) and d ;1 D'=15 out (c),c ;4 out (c),0 ;4 out (c),0 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 33 out (c),h ;4 H=channel B exx ;1 ld a,(speedbuffer) clippause dec a jr nz,clippause xor a ld a,l ;1 1st byte higher nibble (right channel) ; *** RETMOD *** cp &FF jp z,psg2clip5 ; *** RETMOD *** rrca:rrca:rrca:rrca ;4 and d ;1 out (c),c ;4 out (c),0 ;4 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 34 out (c),l ;4 exx ;1 ld a,h ;1 2nd byte lower nibble (left channel) and d ;1 out (c),c ;4 out (c),0 ;4 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 30 out (c),h ;4 exx ;1 out (c),c ;4 out (c),0 ;4 ld a,h ;1 2nd byte higher nibble (right channel) rrca rrca rrca rrca ;4 and d ;1 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 34 dec e ;1 next samples jp nz,psg2clip2 ;3 -> 33+34+30+34+4 = 135 -> 67,5nops/sample -> 14,8kHz sample rate ld iy,psgcol ld (ixbuffer),ix ld ix,psgcolb exx ;1 ex af,af' ;1 ld a,h ;1 and d ;1 exx ;1 a'=left,a=right borderstereo ld bc,#7f01 ;2 out (c),c ;4 ld (psgclipcol1+2),a ;4 psgclipcol1 ld a,(iy+0) ;5 out (c),a ;4 inc c ;1 out (c),c ;4 ex af,af' ;1 ld (psgclipcol2+2),a ;4 psgclipcol2 ld a,(ix+0) ;5 out (c),a ;4 dec c ;1 ; ld b,#f4 ;2 46 / 256 = 0,18nops (insignificant) ld a,#48 ;2 ld bc,#f782 ;3 out (c),c ;4 ld bc,#f40e ;3 out (c),c ;4 ld bc,#f6c0 ;3 out (c),c ;4 out (c),0 ;4 ld bc,#f792 ;3 out (c),c ;4 dec b ;1 out (c),a ;4 ld b,#f4 ;2 in a,(c) ;4 ld bc,#f782 ;3 out (c),c ;4 ld bc,#f401 ;3 bit 2,a ;2 jp z,psg2clip5 ;2 59 / 16384 = insignificant ld ix,(ixbuffer) dec d jp nz,psg2clip2 psg2clip5 ld sp,0 ;finished -> restore stack and 2nd register set exx pop hl pop de pop bc pop af ex af,af' exx ei ret playsample: cp 2 jp nz,PlayError ld c,(ix+2) ld b,(ix+3) ld hl,256 ; Get the Maximum Bank Count sbc hl,bc jp c,BankOutOfRange ld hl,4mb_banks add hl,bc add hl,bc ld e,(hl) inc hl ld d,(hl) ex de,hl ld e,(ix+0) ld d,(ix+1) push hl ld hl,256 ; Get the Maximum Bank Count or a sbc hl,bc sbc hl,de jp c,BankOutOfRange pop hl push de pop ix call psgini call psg2ch ret ;### PSG2CH -> PSG Stereo sample player ;### Input HL=start banking config, IX=number of 16K blocks (0-256) ;### Destroyed AF,BC,DE,HL,IXL psg2ch ld (psg2ch1+1),hl di ;lock interrupts exx ex af,af' push af push bc ;save 2nd register set push de push hl ld bc,#f6c0 ;prepare 2nd register set ld de,#0f80 exx ld (psg2ch5+1),sp ;save stackpointer psg2ch1 ld bc,0 ;*** BANKING LOOP out (c),c ld sp,#4000 ld de,32*256 ;32*256=8192 16bit loops (=16384 8bit samples) ld bc,#f401 ld hl,10*256+8 psg2ch2 ;*** SAMPLE LOOP out (c),l ;4 L=channel A exx ;1 pop hl ;3 HL'=next 2 bytes ld a,l ;1 1st byte lower nibble (left channel) and d ;1 D'=15 out (c),c ;4 out (c),0 ;4 out (c),0 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 33 out (c),h ;4 H=channel B exx ;1 ld a,(speedbuffer) looppause dec a jr nz,looppause xor a ld a,l ;1 1st byte higher nibble (right channel) ; *** RETMOD *** cp &FF jp z,psg2ch5 ; *** RETMOD *** rrca:rrca:rrca:rrca ;4 and d ;1 out (c),c ;4 out (c),0 ;4 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 34 out (c),l ;4 exx ;1 ld a,h ;1 2nd byte lower nibble (left channel) and d ;1 out (c),c ;4 out (c),0 ;4 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 30 out (c),h ;4 exx ;1 out (c),c ;4 out (c),0 ;4 ld a,h ;1 2nd byte higher nibble (right channel) rrca rrca rrca rrca ;4 and d ;1 exx ;1 out (c),a ;4 exx ;1 out (c),e ;4 out (c),0 ;4 exx ;1 34 dec e ;1 next samples jp nz,psg2ch2 ;3 -> 33+34+30+34+4 = 135 -> 67,5nops/sample -> 14,8kHz sample rate ld iy,psgcol ld (ixbuffer),ix ld ix,psgcolb exx ;1 ex af,af' ;1 ld a,h ;1 and d ;1 exx ;1 a'=left,a=right borderstereo2 ld bc,#7f01 ;2 out (c),c ;4 ld (psgcol1+2),a ;4 psgcol1 ld a,(iy+0) ;5 out (c),a ;4 inc c ;1 out (c),c ;4 ex af,af' ;1 ld (psgcol2+2),a ;4 psgcol2 ld a,(ix+0) ;5 out (c),a ;4 dec c ;1 ; ld b,#f4 ;2 46 / 256 = 0,18nops (insignificant) ld a,#48 ;2 ld bc,#f782 ;3 out (c),c ;4 ld bc,#f40e ;3 out (c),c ;4 ld bc,#f6c0 ;3 out (c),c ;4 out (c),0 ;4 ld bc,#f792 ;3 out (c),c ;4 dec b ;1 out (c),a ;4 ld b,#f4 ;2 in a,(c) ;4 ld bc,#f782 ;3 out (c),c ;4 ld bc,#f401 ;3 bit 2,a ;2 jp z,psg2ch5 ;2 59 / 16384 = insignificant ld ix,(ixbuffer) dec d jp nz,psg2ch2 dec lx ;next 16K memory block jr z,psg2ch5 ld hl,psg2ch1+1 ld a,(hl) cp #c0 jp nz,notc0 ld a,#c3 notc0 inc a jp z,psg2ch4 bit 2,a jp nz,psg2ch3 add 4 ;next 64K memory bank psg2ch3 ld (hl),a jp psg2ch1 psg2ch4 ld (hl),#c4 ;next 512K memory segment inc hl dec (hl) jp psg2ch1 psg2ch5 ld sp,0 ;finished -> restore stack and 2nd register set exx pop hl pop de pop bc pop af ex af,af' exx ei ret ;### PSGREG -> sets PSG register ;### Input A=register, E=value ;### Destroyed BC psgreg ld b,#f4 out (c),a ld bc,#f6c0 out (c),c out (c),0 ld b,#f4 out (c),e ld bc,#f680 out (c),c out (c),0 ret ;### PSGINI -> Resets PSG ;### Destroyed AF,BC,E psgini xor a ld e,a psgini1 call psgreg inc a cp 13 jr c,psgini1 ld a,7 ld e,#3f jp psgreg ;### PSGCOL -> Color table for left/right channel VU meter psgcol ; 0 1 2 3 4 5 6 7 8 9 A B C D E F ; 100 0 33 66 100 db #4b,#4b,#5b,#5b,#53,#53,#57,#5f,#55,#44,#54,#55,#5b,#4b,#4b,#4b ;blue psgcolb ; 0 1 2 3 4 5 6 7 8 9 A B C D E F ; 100 0 33 66 100 db #4b,#4b,#43,#4a,#4e,#4e,#4d,#45,#4c,#5c,#54,#4c,#4a,#4b,#4b,#4b ;blue 4mb_banks: dw &7FC0, &7FC4, &7FC5, &7FC6, &7FC7, &7FCC, &7FCD, &7FCE, &7FCF, &7FD4 dw &7FD5, &7FD6, &7FD7, &7FDC, &7FDD, &7FDE, &7FDF, &7FE4, &7FE5, &7FE6 dw &7FE7, &7FEC, &7FED, &7FEE, &7FEF, &7FF4, &7FF5, &7FF6, &7FF7, &7FFC dw &7FFD, &7FFE, &7FFF, &7EC4, &7EC5, &7EC6, &7EC7, &7ECC, &7ECD, &7ECE dw &7ECF, &7ED4, &7ED5, &7ED6, &7ED7, &7EDC, &7EDD, &7EDE, &7EDF, &7EE4 dw &7EE5, &7EE6, &7EE7, &7EEC, &7EED, &7EEE, &7EEF, &7EF4, &7EF5, &7EF6 dw &7EF7, &7EFC, &7EFD, &7EFE, &7EFF, &7DC4, &7DC5, &7DC6, &7DC7, &7DCC dw &7DCD, &7DCE, &7DCF, &7DD4, &7DD5, &7DD6, &7DD7, &7DDC, &7DDD, &7DDE dw &7DDF, &7DE4, &7DE5, &7DE6, &7DE7, &7DEC, &7DED, &7DEE, &7DEF, &7DF4 dw &7DF5, &7DF6, &7DF7, &7DFC, &7DFD, &7DFE, &7DFF, &7CC4, &7CC5, &7CC6 dw &7CC7, &7CCC, &7CCD, &7CCE, &7CCF, &7CD4, &7CD5, &7CD6, &7CD7, &7CDC dw &7CDD, &7CDE, &7CDF, &7CE4, &7CE5, &7CE6, &7CE7, &7CEC, &7CED, &7CEE dw &7CEF, &7CF4, &7CF5, &7CF6, &7CF7, &7CFC, &7CFD, &7CFE, &7CFF, &7BC4 dw &7BC5, &7BC6, &7BC7, &7BCC, &7BCD, &7BCE, &7BCF, &7BD4, &7BD5, &7BD6 dw &7BD7, &7BDC, &7BDD, &7BDE, &7BDF, &7BE4, &7BE5, &7BE6, &7BE7, &7BEC dw &7BED, &7BEE, &7BEF, &7BF4, &7BF5, &7BF6, &7BF7, &7BFC, &7BFD, &7BFE dw &7BFF, &7AC4, &7AC5, &7AC6, &7AC7, &7ACC, &7ACD, &7ACE, &7ACF, &7AD4 dw &7AD5, &7AD6, &7AD7, &7ADC, &7ADD, &7ADE, &7ADF, &7AE4, &7AE5, &7AE6 dw &7AE7, &7AEC, &7AED, &7AEE, &7AEF, &7AF4, &7AF5, &7AF6, &7AF7, &7AFC dw &7AFD, &7AFE, &7AFF, &79C4, &79C5, &79C6, &79C7, &79CC, &79CD, &79CE dw &79CF, &79D4, &79D5, &79D6, &79D7, &79DC, &79DD, &79DE, &79DF, &79E4 dw &79E5, &79E6, &79E7, &79EC, &79ED, &79EE, &79EF, &79F4, &79F5, &79F6 dw &79F7, &79FC, &79FD, &79FE, &79FF, &78C4, &78C5, &78C6, &78C7, &78CC dw &78CD, &78CE, &78CF, &78D4, &78D5, &78D6, &78D7, &78DC, &78DD, &78DE dw &78DF, &78E4, &78E5, &78E6, &78E7, &78EC, &78ED, &78EE, &78EF, &78F4 dw &78F5, &78F6, &78F7, &78FC, &78FD, &78FE, &78FF DW 0 ; Signify End of list DO NOT REMOVE ;; channel definition for ay-playback channel: db 9 setleft: ld a,8 ld (channel),a ret setcenter: ld a,9 ld (channel),a ret setright: ld a,10 ld (channel),a ret ;; set playback driver setdriver: ld a,(ix+0) or a ld hl,play_sample_ay jr z,setdriver2 dec a ld hl,play_sample_digi jr z,setdriver2 ld hl,play_sample_amdrum setdriver2: ld (driver+1),hl ret ;;------------------------------------------------------------------------------- ;; this is the time for CALL: RET combination time_call equ 5+3 ;; these are the timings for the bare code to write to the hardware ;; we use these to modify the timings sample_digi_time equ 2+4+25+5+2+1+1+1+3+time_call sample_amdrum_time equ 2+2+25+2+2+4+2+1+1+3+2+time_call sample_ay_time equ 61+time_call setmonospeed: ld a,(ix+0) set_speed2: cp 17 jr c,set_speed3 xor a set_speed3: ld l,a ld h,0 add hl,hl ld de,speed_table add hl,de ld e,(hl) inc hl ld d,(hl) ld bc,sample_digi_time call calc_speed ld (speed_digi+1),hl ld bc,sample_amdrum_time call calc_speed ld (speed_amdrum+1),hl ld bc,sample_ay_time call calc_speed ld (speed_ay+1),hl ret ;;------------------------------------------------------------------------------- ;; de = timing value from speed_table ;; bc = timing value for instructions to update hardware for playback calc_speed: ld l,e ld h,d or a sbc hl,bc ;; HL = delay we require (including CALL/RET) ;; calculate address from end of delay so we have the correct number of NOPs ;; until RET for this delay ld c,l ld b,h ld hl,end_speed_delay or a sbc hl,bc ret ;;------------------------------------------------------------------------------- ;; Calculating the timings: ;; ;; Hz = number of samples played per second ;; 1,000,000 "NOP cycles" processed by Z80 per second. ;; A "NOP cycle" is a timing unit within the Amstrad enforced by the video hardware. ;; So to playback at the rate we want ;; we need to update the sound hardware at a rate defined in the table below. ;; These values don't take into account the actual time needed to read the sample, ;; convert it it required, write to hardware, and then update playback values ;; ;; Cycles = 1,000,000/Hz speed_table: defw 1000 ;; 1KHz (1000hz) defw 1000 ;; 1KHz (1000hz) defw 500 ;; 2Khz (2000hz) defw 333 ;; 3Khz (3000hz) defw 250 ;; 4Khz (4000hz) defw 200 ;; 5Khz (5000hz) defw 167 ;; 6Khz (6000hz) defw 143 ;; 7Khz (7000hz) defw 125 ;; 8Khz (8000hz) defw 111 ;; 9Khz (9000hz) defw 100 ;; 10Khz (10000hz) defw 91 ;; 11Khz (11025hz) defw 83 ;; 12Khz (12000hz) defw 77 ;; 13Khz (13000hz) defw 71 ;; 14Khz (14000hz) defw 67 ;; 15Khz (15000hz) defw 63 ;; 16Khz (16000hz) ;;------------------------------------------------------------------------------- ;; The speed is controlled by a software delay. ;; ;; This part of the code has 1000 NOP instructions, enough for a playback rate of 1khz. ;; But we have the accuracy of finer control, so we could in theory have other rates than we have defined. ;; The fastest rate that the CPC can playback is around 18Khz, any faster and the CPC can't write to the hardware ;; fast enough. ;; ;; Using the speed_table and the number of cycles that is needed to update the ;; hardware we calculate an address within this code here. The code CALLs to the calculated address ;; then the nops are executed until RET is reached, and then this returns back to the ;; playback code. speed_delay: defs 1000 end_speed_delay: ret ;;------------------------------------------------------------------------------- ;; this is the main RSX entry point for driver playback playmono: cp 2 ret nz driver: jp play_sample_ay ;;------------------------------------------------------------------------------- play_sample_digi: ;; This is the RSX function for the Digiblaster "driver" ld e,(ix+0) ld d,(ix+1) ld l,(ix+2) ld h,(ix+3) ;; DE = length in samples ;; HL = start address of sample data ;; Play a sample using the Digiblaster/SoundPlayer ;; ;; This device plays 8-bit mono signed samples. ;; ;; This device is connected to the printer port on the CPC. ;; The recommended port number for this is &efxx. ;; xx = any value ;; ;; disable interrupts. This stops interrupts from breaking our timing ;; and breaking the sound. di ;; OUTI decrements B before sending data to I/O port. ;; So B = &ef+1 ld b,&f0 digiloop: ;; This performs the same as: ;; DEC B ;; LD A,(HL) ;; OUT (C),A ;; INC HL ;; ;; but without effecting A register and being much quicker outi ;; [5] ld a,(hl) ;; [2] ; *** RETMOD *** cp &FF jr z,gooutdigi ; *** RETMOD *** push BC borderdigi: LD BC,#7F10 OUT (C),C rlca rlca and #2 add 21 OR #40 LD B,#7F OUT (C),a pop BC ;; restore B back for next iteration of loop inc b ;; [1] ;; this CALL is modified based on the playback rate chosen speed_digi: call 0 ;; update number of samples remaining to play dec de ;; [2] ld a,d ;; [1] or e ;; [1] jp nz,digiloop ;; [3] gooutdigi: ;; re-enable interrupts again ei ;; return back to basic ret ;;------------------------------------------------------------------------------- play_sample_amdrum: ;; This is the RSX function for the Amdrum "driver" ld e,(ix+0) ld d,(ix+1) ld l,(ix+2) ld h,(ix+3) ;; DE = length in samples ;; HL = start address of sample data ;; Play a sample using the Amdrum ;; ;; This device plays 8-bit mono unsigned samples. ;; ;; The recommended port number for this is &ffxx. ;; xx = any value ;; disable interrupts. This stops interrupts from breaking our timing ;; and breaking the sound. di ld b,&ff amdrumloop: ;; read sample byte ld a,(hl) ;; [2] ; *** RETMOD *** cp &FF jr z,gooutamdrum ; *** RETMOD *** ;; convert from signed to unsigned xor &80 ;; [2] ;; write to amdrum out (c),a ;; [4] push BC borderamdrum: LD BC,#7F10 OUT (C),C rlca rlca and #2 add 8 OR #40 LD B,#7F OUT (C),a pop BC ;; update sample pointer inc hl ;; [2] ;; this CALL is modified based on the playback rate chosen speed_amdrum: call 0 ;; update number of bytes remaining to play dec de ;; [2] ld a,d ;; [1] or e ;; [1] jp nz,amdrumloop ;; [3] gooutamdrum ei ;; return back to BASIC ret ;;------------------------------------------------------------------------------- play_sample_ay: ld l,(ix+2) ld h,(ix+3) di ;; store de' for basic exx push de ld e,(ix+0) ld d,(ix+1) exx ;; turn off noise ld e,7 ld d,&3f call write_ay_reg ld e,6 ld d,0 call write_ay_reg ld e,0 ld d,0 call write_ay_reg ; ld e,1 ld d,0 call write_ay_reg ld e,2 ld d,0 call write_ay_reg ; ld e,3 ld d,0 call write_ay_reg ; ld e,4 ld d,0 call write_ay_reg ; ld e,5 ld d,0 call write_ay_reg ; ld e,12 ld d,0 call write_ay_reg ; ld e,11 ld d,0 call write_ay_reg ; ; ld e,6 ld d,0 call write_ay_reg ld e,8 ld d,0 call write_ay_reg ld e,9 ld d,0 call write_ay_reg ld e,10 ld d,0 call write_ay_reg ;; select ay register b ; ld bc,&f400 ; ld a,(channel) ; out (c),a ; ld bc,&f6c0 ; out (c),c ; ld bc,&f600 ; out (c),c ;; get ready to write it ; ld bc,&f680 ; out (c),c ld a,(channel) ld bc,#f4c0 out (c),a ld b,#F6 out (c),c dw #71ed ; ld bc,&f400 ld d, lookuptable /256 ;; 19968 nops per frame ;; 50 frames per second ;; 998400 nops per second ;; 12000 samples per second ;; 83.2 nops per sample loop2: ;; convert 8-bit sample to 4-bit sample ld e,(hl) ;; [2] ld a,e ; *** RETMOD *** cp &FF jr z,exitay ; *** RETMOD *** ld a,(de) ;; [2] ; ld b,#f4 ; 2 ; out (c),a ;; [4] ; ld bc,&f680 ; 3 out (c),c ; 4 dw #71ed ; 4 ; push bc ; 4 borderay LD BC,#7F10 ; 3 OUT (C),C ; 4 rlca ;1 rlca ;1 and #07 ;2 add 2 ;2 OR #40 ;2 LD B,#7F ;2 OUT (C),a ;4 pop BC ; 3 inc hl ;; [2] speed_ay: call 0 exx ;; [1] dec de ;; [2] ld a,d ;; [1] or e ;; [1] exx ;; [1] jp nz,loop2 ;; [3] exitay ;; restore de' for basic exx pop de exx ei RET storedExit: db 0 write_ay_reg: ld bc,&f400 out (c),e ld bc,&f6c0 out (c),c ld c,&00 out (c),c ld bc,&f400 out (c),d ld bc,&f680 out (c),c ld c,0 out (c),c ret init_table: ld hl,lookuptable ld d,0 ld b,0 init_tab2: ld a,d cp 2 ld c,0 jr c,init_tab3 cp 5 ld c,1 jr c,init_tab3 cp 7 ld c,2 jr c,init_tab3 cp 10 ld c,3 jr c,init_tab3 cp 14 ld c,4 jr c,init_tab3 cp 19 ld c,5 jr c,init_tab3 cp 29 ld c,6 jr c,init_tab3 cp 40 ld c,7 jr c,init_tab3 cp 56 ld c,8 jr c,init_tab3 cp 80 ld c,9 jr c,init_tab3 cp 103 ld c,10 jr c,init_tab3 cp 131 ld c,11 jr c,init_tab3 cp 161 ld c,12 jr c,init_tab3 cp 197 ld c,13 jr c,init_tab3 cp 236 ld c,14 jr c,init_tab3 ld c,15 init_tab3: ;; 8-bit signed to 8-bit unsigned xor &80 ld l,a ;; L = low byte of address in table ;; but also effectively the sample value ;; write into table ;; C = value for AY ld (hl),c inc d djnz init_tab2 ret ixbuffer: ds 2 speedbuffer: db 1 |