;;@
;;@ 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