Hello.
This is a small player to play songs through the 1-bit port $AA. The song I have chosen is the main title from Bubble Bobble though some notes don't correspond to the original one, I think ...
(I did it yesterday with a very big hang-over)
The main routine (playit) has been taken/copied/ported from the "Assembly Listing of the Sea Change ROM". You'll see the t-states are wrong (you have to add 1). I just added some lines to maintain compatibility with the MSX (even reading the $aa port just to keep the first 6 bits and change only the 7th bit; otherwise you'll have strange effects, like turning on the CAPs light).
The code has been compiled with TNIasm and the binary is just 384 bytes long. You can get it right
HERE.
The "replayer" is written in a very easy way. Don't complain
This is not very useful, I know. More to come ...
ORG $9000
db $fe
dw Start_Of_File, End_Of_File, Start_Of_File
Start_Of_File:
in a,($aa) ;If we don't do this we'll have
and $ef ;problems when we read this port
out ($aa),a ;later.
nop
nop
ld hl,Pseudo_Tracker
The_Loop:
xor a
cp (hl)
ret z
push hl
ld a,(hl)
add a,a
add a,a
ld h,0
ld l,a
ld de,Notes_And_Length-4
add hl,de
push hl
pop ix
ld e,(ix+0)
ld d,(ix+1)
ld l,(ix+2)
ld h,(ix+3)
call playit
pop hl
inc hl
jr The_Loop
Notes_And_Length: ;First 2 bytes = Length // Last 2 bytes = Note
dw $68,$66b ;01 - c4 3822ms
dw $75,$5b4 ;02 - d4 3405ms
dw $83,$511 ;03 - e4 3033ms
dw $8b,$4c7 ;04 - f4 2863ms
dw $b0,$5b4 ;05 - d4 (longer 50%)
dw $34,$66b ;06 - c4 (shorter 50%)
dw $9c,$66b ;07 - c4 (longer 50%)
dw $2e,$737 ;08 - a#3 (shorter 50%)
dw $5c,$737 ;09 - a#3 4290ms
dw $58,$7a6 ;10 - a3 4545ms
dw $2c,$7a6 ;11 - a3 (shorter 50%)
dw $27,$89a ;12 - g3 (shorter 50%)
dw $45,$9ab ;13 - f3 5726ms
dw $4e,$89a ;14 - g3 5102ms
dw $84,$7a6 ;15 - a3 (longer 50%)
dw $8a,$737 ;16 - a#3 (longer 50%)
dw $138,$66b ;17 - c4 (longer 300%)
dw $7c,$560 ;18 - d#4 3214ms
dw $d1,$326 ;19 - c5 1911ms
dw $eb,$2cb ;20 - d5 1702ms
dw $f8,$2a1 ;21 - d#5 1607ms
dw $107,$279 ;22 - e5 1516ms
dw $117,$254 ;23 - f5 1431ms
dw $139,$210 ;24 - g5 1275ms
dw $b0,$3c4 ;25 - a4 2272ms
dw $9c,$43e ;26 - g4 2551ms
dw $22f,$38c ;27 - a#4 2145ms (longer 300%)
dw $174,$38c ;28 - a#4 2145ms (longer 200%)
dw $210,$3c4 ;29 - a4 (longer 300%)
dw $1a3,$4c7 ;30 - f4 (longer 300%)
dw $160,$3c4 ;31 - a4 (longer 200%)
dw $1d6,$43e ;32 - g4 (longer 300%)
dw $160,$5b4 ;33 - d4 (longer 300%)
dw $2c1,$43e ;34 - g4 (longer 450%)
dw $160,$1d3 ;35 - a5 1136ms
dw $139,$66b ;36 - c4 (longer 300%)
dw $274,$4c7 ;37 - f4 (longer 450%)
Pseudo_Tracker:
;First part
db 1,2,3,4,3,5,6,3,2,7,8,2,1,8,10,17
db 11,12,13,14,15,11,14,10,16,6,1,2,3,2,1
;First part (repeat)
db 1,2,3,4,3,5,6,3,2,7,8,2,1,8,10,17
db 11,12,13,14,15,11,14,10,16,6,1,2,3,2,1
;Second part
db 1,2,18,3,19,20,21,22,1,2,3,4,19,20,22,23
db 1,2,3,26,19,20,22,24,1,2,3,25,19,20,22,35
db 4,26,25,27,28,25,28,29,30,31,32,33,31,34
db 4,26,25,27,28,25,28,29,30,31,32,36,31,37
;End and exit
db 0
playit:
di
ld a,l ;
srl l ;
srl l ; L = medium part of tone period
cpl ;
and $03 ; A = 3 - fine part of tone period
ld c,a ;
ld b,$00 ;
ld ix,BE_IX_p_3 ; Address: BE_IX+3
add ix,bc ; IX holds address of entry into the loop
; the loop will contain 0-3 NOPs, implementing
; the fine part of the tone period.
in a,($aa) ;Let's be sure we just modify the 7th bit
BE_IX_p_3:
nop ;(4) optionally executed NOPs for small
; adjustments to tone period
BE_IX_p_2:
nop ;(4)
BE_IX_p_1:
nop ;(4)
BE_IX_p_0:
inc b ;(4)
inc c ;(4)
BE_H_L_LP:
dec c ;(4) timing loop for duration of
jr nz,BE_H_L_LP ;(12/7) high or low pulse of waveform
ld c,$3f ;(7)
dec b ;(4)
jp nz,BE_H_L_LP ;(10) JUMP to BE_H&L_LP
xor $80 ;(7) toggle output beep bit
out ($aa),a ;(11) output pulse
ld b,h ;(4) B = coarse part of tone period
ld c,a ;(4) save port #AA output byte
bit 4,a ;(8) if new output bit is high, go
jr nz,BE_AGAIN ;(2/7) to BE_AGAIN
ld a,d ;(4) one cycle of waveform has completed
or e ;(4) (low->low). if cycle countdown = 0
jr z,BE_END ;(12/7) go to BE_END
ld a,c ;(4) restore output byte for port #AA
ld c,l ;(4) C = medium part of tone period
dec de ;(6) decrement cycle count
jp (ix) ;(8) do another cycle
BE_AGAIN:
ld c,l ;(4) C = medium part of tone period
inc c ;(4) adds 16 cycles to make duration of high = duration of low
jp (ix) ;(8) do high pulse of tone
BE_END:
ei ; Enable Interrupts
ret ;
End_Of_File:
; From the "Assembly Listing of the Sea Change ROM"
; Documented by Alvin Albrecht.
;
; -----------------------
; THE 'BEEPER' SUBROUTINE
; -----------------------
; Outputs a square wave of given duration and frequency
; to the loudspeaker.
; Enter with: DE = #cycles - 1
; HL = tone period as described next
;
; The tone period is measured in T states and consists of
; three parts: a coarse part (H register), a medium part
; (bits 7..2 of L) and a fine part (bits 1..0 of L) which
; contribute to the waveform timing as follows:
;
; coarse medium fine
; duration of low = 118 + 1024*H + 16*(L>>2) + 4*(L&0x3)
; duration of hi = 118 + 1024*H + 16*(L>>2) + 4*(L&0x3)
; Tp = tone period = 236 + 2048*H + 32*(L>>2) + 8*(L&0x3)
; = 236 + 2048*H + 8*L = 236 + 8*HL
;
; As an example, to output five seconds of middle C (261.624 Hz):
; (a) Tone period = 1/261.624 = 3.822ms
; (b) Tone period in T-States = 3.822ms*fCPU = 13378
; where fCPU = clock frequency of the CPU = 3.5MHz
; © Find H and L for desired tone period:
; HL = (Tp - 236) / 8 = (13378 - 236) / 8 = 1643 = 0x066B
; (d) Tone duration in cycles = 5s/3.822ms = 1308 cycles
; DE = 1308 - 1 = 0x051B
;
; The resulting waveform has a duty ratio of exactly 50%.
;
; ---------------------
; THE 'SEMI-TONE' TABLE
; ---------------------
;
; Holds frequencies corresponding to semitones in middle octave.
; To move n octaves higher or lower, frequencies are multiplied by 2^n.
;
;semi_tone:
; 261.625565290 C
; 277.182631135 C#
; 293.664768100 D
; 311.126983881 D#
; 329.627557039 E
; 349.228231549 F
; 369.994422674 F#
; 391.995436072 G
; 415.304697513 G#
; 440.000000000 A
; 466.163761616 A#
; 493.883301378 B