diff --git a/Makefile b/Makefile index 9d76114..68b4f8d 100644 --- a/Makefile +++ b/Makefile @@ -23,19 +23,25 @@ $(BUILDDIR)/bios.bin: bios.asm tape.asm $(BUILDDIR)/memory.inc basic: $(MAKE) -C apps/basic $(or $(MAKECMDGOALS),all) --no-print-directory +sn76489: + $(MAKE) -C apps/sn76489 $(or $(MAKECMDGOALS),all) --no-print-directory + $(BUILDDIR)/memory.inc: memory.mk @mkdir -p $(@D) @awk 'BEGIN { print "; Generated from rom_layout.mk; do not edit." } \ /^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print $$1 " .equ " $$3 }' $< > $@ -$(BUILDDIR)/rom.bin: $(BUILDDIR)/bios.bin basic +$(BUILDDIR)/rom.bin: $(BUILDDIR)/bios.bin basic sn76489 echo "bios: $(shell wc -c < $(BUILDDIR)/bios.bin) / $$(($(BIOS_ROM_SIZE)+0))" echo "basic: $(shell wc -c < $(BUILDDIR)/basic.bin) / $$(($(BASIC_ROM_SIZE)+0))" + echo "sn76489: $(shell wc -c < $(BUILDDIR)/sn76489.bin) / $$(($(SN76489_ROM_SIZE)+0))" objcopy --pad-to=$$(($(BIOS_ROM_START)+$(BIOS_ROM_SIZE))) \ -I binary $(BUILDDIR)/bios.bin -O binary $(BUILDDIR)/bios_padded.bin objcopy --pad-to=$(BASIC_ROM_SIZE) \ -I binary $(BUILDDIR)/basic.bin -O binary $(BUILDDIR)/basic_padded.bin - cat $(BUILDDIR)/bios_padded.bin $(BUILDDIR)/basic_padded.bin > $(BUILDDIR)/rom.bin + objcopy --pad-to=$(SN76489_ROM_SIZE) \ + -I binary $(BUILDDIR)/sn76489.bin -O binary $(BUILDDIR)/sn76489_padded.bin + cat $(BUILDDIR)/bios_padded.bin $(BUILDDIR)/basic_padded.bin $(BUILDDIR)/sn76489_padded.bin > $(BUILDDIR)/rom.bin -.PHONY: $(TOPTARGETS) $(SUBDIRS) basic +.PHONY: $(TOPTARGETS) $(SUBDIRS) basic sn76489 diff --git a/apps/sn76489/Makefile b/apps/sn76489/Makefile new file mode 100644 index 0000000..b431d75 --- /dev/null +++ b/apps/sn76489/Makefile @@ -0,0 +1,22 @@ +.SILENT: +ASM := zasm +ASMFLAGS := -w -b +BUILDDIR := ../../build + +TOPTARGETS := all clean +$(TOPTARGETS): $(SUBDIRS) + +all: $(BUILDDIR) $(BUILDDIR)/sn76489.bin + +clean: + rm -rf $(BUILDDIR) + +$(BUILDDIR): + mkdir -p $(BUILDDIR) + +$(BUILDDIR)/sn76489.bin: main.asm psglib_zasm.asm $(BUILDDIR)/memory.inc + echo '>$<' + $(ASM) $(ASMFLAGS) $< $@ + +.PHONY: $(TOPTARGETS) $(SUBDIRS) + diff --git a/apps/sn76489/main.asm b/apps/sn76489/main.asm new file mode 100644 index 0000000..3e2e49d --- /dev/null +++ b/apps/sn76489/main.asm @@ -0,0 +1,89 @@ +#include "../../build/memory.inc" +#include "../../bios.inc" +#include "../../hardware.inc" + +CTC_BASE .equ 020h +CTC_CHANNEL_0 .equ CTC_BASE+000h +CTC_CHANNEL_1 .equ CTC_BASE+001h +CTC_CHANNEL_2 .equ CTC_BASE+002h +CTC_CHANNEL_3 .equ CTC_BASE+003h + +RAM_TOP .equ 0xbfff +; workspace variables here vv +; ------------------------ ^^ +SAVED_SP .equ RAM_TOP-0x02 ; word +STACK_TOP .equ RAM_TOP-0x100 + +; ------------------------------------------------------- +; Entry +; ------------------------------------------------------- +.org SN76489_ROM_START + push bc + push de + push hl + + ld hl, 0 + add hl, sp ; get sp + ld (SAVED_SP), hl ; save sp for later + ld hl, STACK_TOP + ld sp, hl ; use own stack + + ld hl,str_header + call BIOS_PUTS + + ld hl,SHARED_MEM_START+042h + ld bc, ctc_interrupt ; install own ctc isr + ld (hl), bc + + di + + call PSGInit ; Initialize psglib + ld a,10100111b ; interrupts enabled + ; timer mode + ; prescaler 256 + ; automatic trigger + ; time constant follows + ; reset + ; control word + out (CTC_CHANNEL_1), a + ld a,78 ; 50Hz + out (CTC_CHANNEL_1), a + + ; music must be loaded from 8000 + ld hl, 0x8000 + call PSGPlayNoRepeat + + ei + +exit: + ld hl, (SAVED_SP) + ld sp, hl ; restore original stack + + pop hl + pop de + pop bc + ret + +; ------------------------------------------------------- +; CTC CHAN 1 interrupt handler +; ------------------------------------------------------- +ctc_interrupt: + push af + push bc + push de + push hl + + call PSGFrame + + pop hl + pop de + pop bc + pop af + reti + +; ------------------------------------------------------- +; Strings +; ------------------------------------------------------- +str_header: .db "SN76489 test",00dh,00ah,000h + +#include "psglib_zasm.asm" \ No newline at end of file diff --git a/apps/sn76489/psglib.asm b/apps/sn76489/psglib.asm new file mode 100644 index 0000000..1284093 --- /dev/null +++ b/apps/sn76489/psglib.asm @@ -0,0 +1,1146 @@ +;================================================================ +; PSGlib - Programmable Sound Generator audio library - by sverx +; https://github.com/sverx/PSGlib +;================================================================ + +; NOTE: this uses a WLA-DX 'ramsection' at slot 3 +; If you want to change or remove that, +; see the note at the end of this file + +.define PSG_STOPPED 0 +.define PSG_PLAYING 1 + +.define PSGDataPort $19 + +.define PSGLatch $80 +.define PSGData $40 + +.define PSGChannel0 %00000000 +.define PSGChannel1 %00100000 +.define PSGChannel2 %01000000 +.define PSGChannel3 %01100000 +.define PSGVolumeData %00010000 + +.define PSGWait $38 +.define PSGSubString $08 +.define PSGLoop $01 +.define PSGEnd $00 + +.define SFX_CHANNEL2 $01 +.define SFX_CHANNEL3 $02 +.define SFX_CHANNELS2AND3 SFX_CHANNEL2|SFX_CHANNEL3 +.define SFX_CHANNEL0 $04 +.define SFX_CHANNEL1 $08 + +.section "PSGInit" free +; ************************************************************************************ +; initializes the PSG 'engine' +; destroys AF +PSGInit: + xor a ; ld a,PSG_STOPPED + ld (PSGMusicStatus),a ; set music status to PSG_STOPPED + ld (PSGSFXStatus),a ; set SFX status to PSG_STOPPED + ld (PSGChannel0SFX),a ; set channel 0 SFX to PSG_STOPPED + ld (PSGChannel1SFX),a ; set channel 1 SFX to PSG_STOPPED + ld (PSGChannel2SFX),a ; set channel 2 SFX to PSG_STOPPED + ld (PSGChannel3SFX),a ; set channel 3 SFX to PSG_STOPPED + ld (PSGMusicVolumeAttenuation),a ; volume attenuation = none + ret +.ends + +.section "PSGPlay and PSGPlayNoRepeat and PSGPlayLoops" free +; ************************************************************************************ +; all receive in HL the address of the PSG to start playing +; PSGPlayLoops also receives in A the number of times to loop the PSG +; destroys AF +PSGPlayNoRepeat: ; we don't want the song ever to loop + xor a +PSGPlayLoops: ; we want the song to loop a certain amount of times + ld (PSGLoopCounter),a + xor a ; we don't want the song to loop forever + jp + +PSGPlay: + ld a,$1 ; we want the song to loop forever ++:ld (PSGLoopFlag),a + call PSGStop ; if there's a tune already playing, we should stop it! + ld (PSGMusicStart),hl ; store the begin point of music + ld (PSGMusicPointer),hl ; set music pointer to begin of music + ld (PSGMusicLoopPoint),hl ; looppointer points to begin too + xor a + ld (PSGMusicSkipFrames),a ; reset the skip frames + ld (PSGMusicSubstringLen),a ; reset the substring len (for compression) + ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent) + ld (PSGMusicLastLatch),a ; reset last latch to chn 0 volume 0 + ld a,$0F ; ensure unused channels remain silent + ld (PSGChan0Volume),a + ld (PSGChan1Volume),a + ld (PSGChan2Volume),a + ld (PSGChan3Volume),a + ld a,PSG_PLAYING + ld (PSGMusicStatus),a ; set status to PSG_PLAYING + ret +.ends + +.section "PSGStop" free +; ************************************************************************************ +; stops the music (leaving the SFX on, if it's playing) +; destroys AF +PSGStop: + ld a,(PSGMusicStatus) ; if it's already stopped, leave + or a + ret z + ld a,(PSGChannel0SFX) + or a + jr nz,+ + ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent) + out (PSGDataPort),a ++:ld a,(PSGChannel1SFX) + or a + jr nz,+ + ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent) + out (PSGDataPort),a ++:ld a,(PSGChannel2SFX) + or a + jr nz,+ + ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent) + out (PSGDataPort),a ++:ld a,(PSGChannel3SFX) + or a + jr nz,+ + ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xF (silent) + out (PSGDataPort),a ++:xor a ; ld a,PSG_STOPPED + ld (PSGMusicStatus),a ; set status to PSG_STOPPED + ret +.ends + +.section "PSGResume" free +; ************************************************************************************ +; resume a previously stopped music (with volume attenuation if needed) +; destroys AF,HL +PSGResume: + ld a,(PSGMusicStatus) ; if it's already playing, leave + or a + ret nz + + ld hl,PSGMusicVolumeAttenuation + ld l,(hl) ; load volume attenuation in L + + ld a,(PSGChannel0SFX) + or a + jr nz,_ch1 + ld a,(PSGChan0LowTone) ; restore channel 0 frequency + or PSGLatch|PSGChannel0 + out (PSGDataPort),a + ld a,(PSGChan0HighTone) + out (PSGDataPort),a + ld a,(PSGChan0Volume) ; restore channel 0 volume + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_ch1: + ld a,(PSGChannel1SFX) + or a + jr nz,_ch2 + ld a,(PSGChan1LowTone) ; restore channel 1 frequency + or PSGLatch|PSGChannel1 + out (PSGDataPort),a + ld a,(PSGChan1HighTone) + out (PSGDataPort),a + ld a,(PSGChan1Volume) ; restore channel 1 volume + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a + +_ch2: + ld a,(PSGChannel2SFX) + or a + jr nz,_ch3 + ld a,(PSGChan2LowTone) ; restore channel 2 frequency + or PSGLatch|PSGChannel2 + out (PSGDataPort),a + ld a,(PSGChan2HighTone) + out (PSGDataPort),a + ld a,(PSGChan2Volume) ; restore channel 2 volume + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a + +_ch3: + ld a,(PSGChannel3SFX) + or a + jr nz,_leave + ld a,(PSGChan3LowTone) ; restore channel 3 frequency + or PSGLatch|PSGChannel3 + out (PSGDataPort),a + ld a,(PSGChan3Volume) ; restore channel 3 volume + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a + +_leave: + ld a,PSG_PLAYING + ld (PSGMusicStatus),a ; set status to PSG_PLAYING + ret +.ends + +.section "PSGCancelLoop" free +; ************************************************************************************ +; sets the currently looping music to no more loops after the current +; destroys AF +PSGCancelLoop: + xor a + ld (PSGLoopFlag),a + ld (PSGLoopCounter),a + ret +.ends + +.section "PSGGetStatus" free +; ************************************************************************************ +; gets the current status of music into register A +PSGGetStatus: + ld a,(PSGMusicStatus) + ret +.ends + +.section "PSGSetMusicVolumeAttenuation" free +; ************************************************************************************ +; receives in L the volume attenuation for the music (0-15) +; destroys AF +PSGSetMusicVolumeAttenuation: + ld a,l + ld (PSGMusicVolumeAttenuation),a + ld a,(PSGMusicStatus) ; if tune is not playing, leave + or a + ret z + + ld a,(PSGChannel0SFX) ; channel 0 busy with SFX? + or a + jr nz,_restore_channel1 ; if so, skip channel 0 + + ld a,(PSGChan0Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_restore_channel1: + ld a,(PSGChannel1SFX) ; channel 1 busy with SFX? + or a + jr nz,_restore_channel2 ; if so, skip channel 1 + + ld a,(PSGChan1Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_restore_channel2: + ld a,(PSGChannel2SFX) ; channel 2 busy with SFX? + or a + jr nz,_restore_channel3 ; if so, skip channel 2 + + ld a,(PSGChan2Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_restore_channel3: + ld a,(PSGChannel3SFX) ; channel 3 busy with SFX? + or a + ret nz ; if so, we're done + + ld a,(PSGChan3Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + ret +.ends + +.section "PSGSetSFXVolumeAttenuation" free +; ************************************************************************************ +; receives in L the volume attenuation for the SFXs (0-15) +; destroys AF +PSGSetSFXVolumeAttenuation: + ld a,l + ld (PSGSFXVolumeAttenuation),a + ld a,(PSGSFXStatus) ; if an SFX is not playing, leave + or a + ret z + + ld a,(PSGChannel0SFX) ; channel 0 used by SFX? + or a + jr z,_restore_channel1 ; if not, skip channel 0 + + ld a,(PSGSFXChan0Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_restore_channel1: + ld a,(PSGChannel1SFX) ; channel 1 used by SFX? + or a + jr z,_restore_channel2 ; if not, skip channel 1 + + ld a,(PSGSFXChan1Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_restore_channel2: + ld a,(PSGChannel2SFX) ; channel 2 used by SFX? + or a + jr z,_restore_channel3 ; if not, skip channel 2 + + ld a,(PSGSFXChan2Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_restore_channel3: + ld a,(PSGChannel3SFX) ; channel 3 used by SFX? + or a + ret z ; if not, we're done + + ld a,(PSGSFXChan3Volume) + and $0F + add a,l + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++:or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + ret +.ends + +.section "PSGSilenceChannels" free +; ************************************************************************************ +; sets all PSG channels to volume ZERO +; destroys AF +PSGSilenceChannels: + ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent) + out (PSGDataPort),a + ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent) + out (PSGDataPort),a + ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent) + out (PSGDataPort),a + ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xF (silent) + out (PSGDataPort),a + ret +.ends + +.section "PSGRestoreVolumes" free +; ************************************************************************************ +; resets all PSG channels to previous volume +; destroys AF,HL +PSGRestoreVolumes: + ld a,(PSGChannel0SFX) ; channel 0 busy with SFX? + jr nz,_restoreSFX0 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + jr z,_chkchn1 ; if not, skip channel 0 + + ld a,(PSGChan0Volume) + and $0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write0 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + jp _write0 + +_restoreSFX0: + ld a,(PSGSFXChan0Volume) + and $0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write0 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + +_write0: + or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_chkchn1: + ld a,(PSGChannel1SFX) ; channel 1 busy with SFX? + jr nz,_restoreSFX1 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + jr z,_chkchn2 ; if not, skip channel 1 + + ld a,(PSGChan1Volume) + and $0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write1 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + jp _write1 + +_restoreSFX1: + ld a,(PSGSFXChan1Volume) + and $0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write1 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + +_write1: + or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_chkchn2: + ld a,(PSGChannel2SFX) ; channel 2 busy with SFX? + jr nz,_restoreSFX2 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + jr z,_chkchn3 ; if not, skip chn2 + + ld a,(PSGChan2Volume) + and $0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write2 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + jp _write2 + +_restoreSFX2: + ld a,(PSGSFXChan2Volume) + and $0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write2 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + +_write2: + or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_chkchn3: + ld a,(PSGChannel3SFX) ; channel 3 busy with SFX? + jr nz,_restoreSFX3 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + ret z ; if not, we've done + + ld a,(PSGChan3Volume) + and $0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write3 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + jp _write3 + +_restoreSFX3: + ld a,(PSGSFXChan3Volume) + and $0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp $0F ; check overflow + jr c,_write3 ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 + +_write3: + or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + ret +.ends + +.section "PSGSFXPlay and PSGSFXPlayLoop" free +; ************************************************************************************ +; receives in HL the address of the SFX PSG to start +; receives in C the mask that indicates which channel(s) the SFX will use +; destroys AF +PSGSFXPlayLoop: + ld a,1 ; SFX _IS_ a looping one + jp + +PSGSFXPlay: + xor a ; SFX is _NOT_ a looping one ++:ld (PSGSFXLoopFlag),a + call PSGSFXStop ; if there's a SFX already playing, we should stop it! + ld (PSGSFXStart),hl ; store begin of SFX + ld (PSGSFXPointer),hl ; set the pointer to begin of SFX + ld (PSGSFXLoopPoint),hl ; looppointer points to begin too + xor a + ld (PSGSFXSkipFrames),a ; reset the skip frames + ld (PSGSFXSubstringLen),a ; reset the substring len + bit 2,c ; channel 0 needed? + jr z,+ + ld a,PSG_PLAYING + ld (PSGChannel0SFX),a ++:bit 3,c ; channel 1 needed? + jr z,+ + ld a,PSG_PLAYING + ld (PSGChannel1SFX),a ++:bit 0,c ; channel 2 needed? + jr z,+ + ld a,PSG_PLAYING + ld (PSGChannel2SFX),a ++:bit 1,c ; channel 3 needed? + jr z,+ + ld a,PSG_PLAYING + ld (PSGChannel3SFX),a ++:ld (PSGSFXStatus),a ; set SFXstatus to PSG_PLAYING if SFX uses at least one channel (0=PSG_STOPPED otherwise) + ld a,(PSGMusicStatus) ; check if a tune is currently playing + or a + ret z ; if not, we've done + + ld a,(PSGChan3LowTone) ; test if music playing on channel 3 uses the frequency of channel 2 + and 3 + cp 3 + ret nz ; if channel 3 doesn't use the frequency of channel 2 we're done + + ld a,PSG_PLAYING + ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied by the SFX + ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; and silence channel 3 + out (PSGDataPort),a + ret +.ends + +.section "PSGSFXStop" free +; ************************************************************************************ +; stops the SFX (leaving the music on, if it's playing) +; destroys AF +PSGSFXStop: + ld a,(PSGSFXStatus) ; check status + or a + ret z ; no SFX playing, leave + + ld a,(PSGChannel0SFX) ; channel 0 playing? + or a + jr z,+ + ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent) + out (PSGDataPort),a ++:ld a,(PSGChannel1SFX) ; channel 1 playing? + or a + jr z,+ + ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent) + out (PSGDataPort),a ++:ld a,(PSGChannel2SFX) ; channel 2 playing? + or a + jr z,+ + ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent) + out (PSGDataPort),a ++:ld a,(PSGChannel3SFX) ; channel 3 playing? + or a + jr z,+ + ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xf (silent) + out (PSGDataPort),a ++:ld a,(PSGMusicStatus) ; check if a tune is playing + or a + jp z,_skipRestore ; if it's not playing, skip restoring PSG values + + ld a,(PSGChannel0SFX) ; channel 0 playing? + or a + jr z,_skip_chn0 + ld a,(PSGChan0LowTone) + and $0F ; use only low 4 bits of byte + or PSGLatch|PSGChannel0 ; latch channel 0, low part of tone + out (PSGDataPort),a + ld a,(PSGChan0HighTone) ; high part of tone (latched channel 0, tone) + and $3F ; use only low 6 bits of byte + out (PSGDataPort),a + ld a,(PSGChan0Volume) ; restore music' channel 0 volume + and $0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++: or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc + +_skip_chn0: + ld a,(PSGChannel1SFX) ; channel 1 playing? + or a + jr z,_skip_chn1 + ld a,(PSGChan1LowTone) + and $0F ; use only low 4 bits of byte + or PSGLatch|PSGChannel1 ; latch channel 1, low part of tone + out (PSGDataPort),a + ld a,(PSGChan1HighTone) ; high part of tone (latched channel 1, tone) + and $3F ; use only low 6 bits of byte + out (PSGDataPort),a + ld a,(PSGChan1Volume) ; restore music' channel 1 volume + and $0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++: or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc + +_skip_chn1: + ld a,(PSGChannel2SFX) ; channel 2 playing? + or a + jr z,_skip_chn2 + ld a,(PSGChan2LowTone) + and $0F ; use only low 4 bits of byte + or PSGLatch|PSGChannel2 ; latch channel 2, low part of tone + out (PSGDataPort),a + ld a,(PSGChan2HighTone) ; high part of tone (latched channel 2, tone) + and $3F ; use only low 6 bits of byte + out (PSGDataPort),a + ld a,(PSGChan2Volume) ; restore music' channel 2 volume + and $0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++: or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc + +_skip_chn2: + ld a,(PSGChannel3SFX) ; channel 3 playing? + or a + jr z,_skip_chn3 + ld a,(PSGChan3LowTone) + and $07 ; use only low 3 bits of byte + or PSGLatch|PSGChannel3 ; latch channel 3, low part of tone (no high part) + out (PSGDataPort),a + ld a,(PSGChan3Volume) ; restore music' channel 3 volume + and $0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp $0F ; check overflow + jr c,+ ; if it's <=15 then ok + ld a,$0F ; else, reset to 15 ++: or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc +_skip_chn3: + xor a ; ld a,PSG_STOPPED +_skipRestore: + ld (PSGChannel0SFX),a + ld (PSGChannel1SFX),a + ld (PSGChannel2SFX),a + ld (PSGChannel3SFX),a + ld (PSGSFXStatus),a ; set status to PSG_STOPPED + ret +.ends + +.section "PSGSFXCancelLoop" free +; ************************************************************************************ +; sets the currently looping SFX to no more loops after the current +; destroys AF +PSGSFXCancelLoop: + xor a + ld (PSGSFXLoopFlag),a + ret +.ends + +.section "PSGSFXGetStatus" free +; ************************************************************************************ +; gets the current SFX status into register A +PSGSFXGetStatus: + ld a,(PSGSFXStatus) + ret +.ends + +.section "PSGFrame" free +; ************************************************************************************ +; processes a music frame +; destroys AF,HL,BC +PSGFrame: + ld a,(PSGMusicStatus) ; check if we have got to play a tune + or a + ret z + + ld a,(PSGMusicSkipFrames) ; check if we havve got to skip frames + or a + jp nz,_skipFrame + + ld hl,(PSGMusicPointer) ; read current address + +_intLoop: + ld b,(hl) ; load PSG byte (in B) + inc hl ; point to next byte + ld a,(PSGMusicSubstringLen) ; read substring len + or a + jr z,_continue ; check if it is 0 (we are not in a substring) + dec a ; decrease len + ld (PSGMusicSubstringLen),a ; save len + jr nz,_continue + ld hl,(PSGMusicSubstringRetAddr) ; substring is over, retrieve return address + +_continue: + ld a,b ; copy PSG byte into A + cp PSGLatch ; is it a latch? + jr c,_noLatch ; if < $80 then it is NOT a latch + ld (PSGMusicLastLatch),a ; it is a latch - save it in "LastLatch" + + ; we have got the latch PSG byte both in A and in B + ; and we have to check if the value should pass to PSG or not + bit 4,a ; test if it is a volume + jr nz,_latch_Volume ; jump if volume data + bit 6,a ; test if the latch it is for channels 0-1 or for 2-3 + jp nz,_latch_chn23 ; jump if it is for channels 2-3 + + ; we have got the latch (tone, chn 0 or 1) PSG byte both in A and in B + ; and we have to check if the value should be passed to PSG or not + bit 5,a ; test if tone it is for channel 0 or 1 + jr z,_ifchn0 ; jump if channel 0 + ld (PSGChan1LowTone),a ; save tone LOW data + ld a,(PSGChannel1SFX) ; channel 1 available for music? + or a + jr z,_send2PSG_B ; then send data + jp _intLoop +_ifchn0: + ld (PSGChan0LowTone),a ; save tone LOW data + ld a,(PSGChannel0SFX) ; channel 0 available for music? + or a + jr z,_send2PSG_B ; then send data + jp _intLoop + +_latch_chn23: + ; we have got the latch (tone, chn 2 or 3) PSG byte both in A and in B + ; and we have to check if the value should be passed to PSG or not + bit 5,a ; test if tone it is for channel 2 or 3 + jr z,_ifchn2 ; jump if channel 2 + ld (PSGChan3LowTone),a ; save tone LOW data + ld a,(PSGChannel3SFX) ; channel 3 available for music? + or a + jp nz,_intLoop + ld a,(PSGChan3LowTone) + and 3 ; test if channel 3 is set to use the frequency of channel 2 + cp 3 + jr nz,_send2PSG_B ; if channel 3 does not use frequency of channel 2 jump + ld a,(PSGChannel2SFX) ; test if an SFX is playing on channel 2 + or a + jr z,_send2PSG_B ; if no SFX is playing on channel 2 jump + ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied + ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; and silence channel 3 + out (PSGDataPort),a + jp _intLoop +_ifchn2: + ld (PSGChan2LowTone),a ; save tone LOW data + ld a,(PSGChannel2SFX) ; channel 2 available for music? + or a + jr z,_send2PSG_B + jp _intLoop + +_noLatch: + cp PSGData + jr c,_command ; if < $40 then it is a command + ; it's a data + ld a,(PSGMusicLastLatch) ; retrieve last latch + jp _output_NoLatch + +_latch_Volume: + bit 6,a ; test if the latch it is for channels 0-1 or for 2-3 + jr nz,_latch_Volume_23 ; volume is for channel 2 or 3 + bit 5,a ; test if volume it is for channel 0 or 1 + jr z,_ifchn0v ; jump for channel 0 + ld (PSGChan1Volume),a ; save volume data + ld a,(PSGChannel1SFX) ; channel 1 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop +_ifchn0v: + ld (PSGChan0Volume),a ; save volume data + ld a,(PSGChannel0SFX) ; channel 0 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop + +_send2PSG_B: + ld a,b +_send2PSG_A: + out (PSGDataPort),a ; output the byte + jp _intLoop + +_latch_Volume_23: + bit 5,a ; test if volume it is for channel 2 or 3 + jr z,_chn2 ; jump for channel 2 + ld (PSGChan3Volume),a ; save volume data + ld a,(PSGChannel3SFX) ; channel 3 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop +_chn2: + ld (PSGChan2Volume),a ; save volume data + ld a,(PSGChannel2SFX) ; channel 2 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop + +_skipFrame: + dec a + ld (PSGMusicSkipFrames),a + ret + +_command: + cp PSGWait + jr z,_done ; no additional frames + jr c,_otherCommands ; other commands? + and $07 ; take only the last 3 bits for skip frames + ld (PSGMusicSkipFrames),a ; we got additional frames +_done: + ld (PSGMusicPointer),hl ; save current address + ret ; frame done + +_otherCommands: + cp PSGSubString + jr nc,_substring + cp PSGEnd + jr z,_musicLoop + cp PSGLoop + jr z,_setLoopPoint + + ; *************************************************************************** + ; we should never get here! + ; if we do, it means the PSG file is probably corrupted, so we just RET + ; *************************************************************************** + + ret + +_sendVolume2PSG_B: + ld a,b +_sendVolume2PSG_A: + ld c,a ; save the PSG command byte + and $0F ; keep lower nibble + ld b,a ; save value + ld a,(PSGMusicVolumeAttenuation) ; load volume attenuation + add a,b ; add value + cp $0F ; check overflow + jr c,_no_overflow ; if it is <=15 then ok + ld a,$0F ; else, reset to 15 +_no_overflow: + ld b,a ; save new attenuated volume value + ld a,c ; retrieve PSG command + and $F0 ; keep upper nibble + or b ; set attenuated volume + out (PSGDataPort),a ; output the byte + jp _intLoop + +_output_NoLatch: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ; note that non-latch commands can be only contain frequencies (no volumes) + ; for channels 0,1,2 only (no noise) + bit 6,a ; test if the latch it is for channels 0-1 or for chn 2 + jr nz,_high_part_Tone_ch2 ; it is tone data for channel 2 + + bit 5,a ; test if latch is for channel 0 or 1 + jr nz,_high_part_Tone_ch1 ; it is tone data for channel 1 + + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ld a,b ; move PSG data in A + ld (PSGChan0HighTone),a ; save channel 0 tone HIGH data + ld a,(PSGChannel0SFX) ; channel 0 available for music? + or a + jr z,_send2PSG_B + jp _intLoop + +_high_part_Tone_ch1: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ld a,b ; move PSG data in A + ld (PSGChan1HighTone),a ; save channel 1 tone HIGH data + ld a,(PSGChannel1SFX) ; channel 1 available for music? + or a + jr z,_send2PSG_B + jp _intLoop + +_high_part_Tone_ch2: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ; PSG data can only be for channel 2, here + ld a,b ; move PSG data in A + ld (PSGChan2HighTone),a ; save channel 2 tone HIGH data + ld a,(PSGChannel2SFX) ; channel 2 available for music + or a + jp z,_send2PSG_B ; TOO FAR for JR here :| + jp _intLoop + +_setLoopPoint: + ld (PSGMusicLoopPoint),hl + jp _intLoop + +_musicLoop: + ld hl,(PSGMusicLoopPoint) + ld a,(PSGLoopFlag) ; infinite loop requested? + or a + jp nz,_intLoop ; Yes: do loop + ld a,(PSGLoopCounter) ; one more loop requested? + or a + jp z,PSGStop ; No: stop the music! (tail call optimization) + dec a + ld (PSGLoopCounter),a ; decrement loop counter + jp _intLoop ; do loop + +_substring: + sub PSGSubString-4 ; len is value - $08 + 4 + ld (PSGMusicSubstringLen),a ; save len + ld c,(hl) ; load substring address (offset) + inc hl + ld b,(hl) + inc hl + ld (PSGMusicSubstringRetAddr),hl ; save return address + ld hl,(PSGMusicStart) + add hl,bc ; make substring current + jp _intLoop +.ends + +.section "PSGSFXFrame" free +; ************************************************************************************ +; processes a SFX frame +; destroys AF,HL,BC +PSGSFXFrame: + ld a,(PSGSFXStatus) ; check if we have got to play SFX + or a + ret z + + ld a,(PSGSFXSkipFrames) ; check if we have got to skip frames + or a + jp nz,_skipSFXFrame + + ld hl,(PSGSFXPointer) ; read current SFX address + +_intSFXLoop: + ld b,(hl) ; load a byte in B, temporary + inc hl ; point to next byte + ld a,(PSGSFXSubstringLen) ; read substring len + or a ; check if it is 0 (we are not in a substring) + jr z,_SFXcontinue + dec a ; decrease len + ld (PSGSFXSubstringLen),a ; save len + jr nz,_SFXcontinue + ld hl,(PSGSFXSubstringRetAddr) ; substring over, retrieve return address + +_SFXcontinue: + ld a,b ; restore byte + cp PSGData + jp c,_SFXcommand ; if less than $40 then it is a command + bit 7,a ; check if it is a latch + jr z,_SFXoutbyte ; if not, output it directly + bit 4,a ; check if it is a volume byte + jr z,_SFXoutbyte ; if not, output it directly + + bit 6,a ; check if it is volume for channels 0-1 or for 2-3 + jr nz,_SFXchn23 + + bit 5,a ; check if it is volume for channel 0 or channel 1 + jr nz,_SFXvolumechn1 + ld (PSGSFXChan0Volume),a + jr _SFXoutvolume + +_SFXvolumechn1: + ld (PSGSFXChan1Volume),a + jr _SFXoutvolume + +_SFXchn23: + bit 5,a ; check if it is volume for channel 2 or channel 3 + jr nz,_SFXvolumechn3 + ld (PSGSFXChan2Volume),a + jr _SFXoutvolume + +_SFXvolumechn3: + ld (PSGSFXChan3Volume),a + +_SFXoutvolume: + and $0F ; keep lower nibble + ld c,a ; save value + ld a,(PSGSFXVolumeAttenuation) ; load volume attenuation + add a,c ; add value + cp $0F ; check overflow + jr c,_no_overflow_SFX ; if it is <=15 then ok + ld a,$0F ; else, reset to 15 +_no_overflow_SFX: + ld c,a ; save new attenuated volume value + ld a,b ; retrieve original PSG command + and $F0 ; keep upper nibble (channel+volume bits) + or c ; set attenuated SFX volume +_SFXoutbyte: + out (PSGDataPort),a ; output the byte + jp _intSFXLoop + +_skipSFXFrame: + dec a + ld (PSGSFXSkipFrames),a + ret + +_SFXcommand: + cp PSGWait + jr z,_SFXdone ; no additional frames + jr c,_SFXotherCommands ; other commands? + and $07 ; take only the last 3 bits for skip frames + ld (PSGSFXSkipFrames),a ; we got additional frames to skip +_SFXdone: + ld (PSGSFXPointer),hl ; save current address + ret ; frame done + +_SFXotherCommands: + cp PSGSubString + jr nc,_SFXsubstring + cp PSGEnd + jr z,_sfxLoop + cp PSGLoop + jr z,_SFXsetLoopPoint + + ; *************************************************************************** + ; we should never get here! + ; if we do, it means the PSG SFX file is probably corrupted, so we just RET + ; *************************************************************************** + + ret + +_SFXsetLoopPoint: + ld (PSGSFXLoopPoint),hl + jp _intSFXLoop + +_sfxLoop: + ld a,(PSGSFXLoopFlag) ; is it a looping SFX? + or a + jp z,PSGSFXStop ; No:stop it! (tail call optimization) + ld hl,(PSGSFXLoopPoint) + ld (PSGSFXPointer),hl + jp _intSFXLoop + +_SFXsubstring: + sub PSGSubString-4 ; len is value - $08 + 4 + ld (PSGSFXSubstringLen),a ; save len + ld c,(hl) ; load substring address (offset) + inc hl + ld b,(hl) + inc hl + ld (PSGSFXSubstringRetAddr),hl ; save return address + ld hl,(PSGSFXStart) + add hl,bc ; make substring current + jp _intSFXLoop +.ends + +; NOTE: if you don't want to use a ramsection, +; comment the ".ramsection" line and +; uncomment the next ".enum" one, +; setting .enum start RAM address +; according to your needs. +; Also change ".ends" into ".ende" at end of this file + +;.ramsection "PSGlib variables" slot 3 +.enum $c000 export ; PSGlib variables location in RAM + ; fundamental vars + PSGMusicStatus db ; are we playing a background music? + PSGMusicStart dw ; the pointer to the beginning of music + PSGMusicPointer dw ; the pointer to the current + PSGMusicLoopPoint dw ; the pointer to the loop begin + PSGMusicSkipFrames db ; the frames we need to skip + PSGLoopFlag db ; the tune should loop forever or not (flag) + PSGLoopCounter db ; how many times the tune should loop + PSGMusicLastLatch db ; the last PSG music latch + + ; decompression vars + PSGMusicSubstringLen db ; lenght of the substring we are playing + PSGMusicSubstringRetAddr dw ; return to this address when substring is over + + ; command buffers + PSGChan0Volume db ; the volume for channel 0 + PSGChan1Volume db ; the volume for channel 1 + PSGChan2Volume db ; the volume for channel 2 + PSGChan3Volume db ; the volume for channel 3 + + PSGChan0LowTone db ; the low tone bits for channels 0 + PSGChan1LowTone db ; the low tone bits for channels 1 + PSGChan2LowTone db ; the low tone bits for channels 2 + PSGChan3LowTone db ; the low tone bits for channels 3 + PSGChan0HighTone db ; the high tone bits for channel 0 + PSGChan1HighTone db ; the high tone bits for channel 1 + PSGChan2HighTone db ; the high tone bits for channel 2 + + PSGMusicVolumeAttenuation db ; the volume attenuation applied to the tune (0-15) + + ; ******* SFX ************* + + ; flags for SFX channels usage + PSGChannel0SFX db ; !0 means channel 0 is allocated to SFX + PSGChannel1SFX db ; !0 means channel 1 is allocated to SFX + PSGChannel2SFX db ; !0 means channel 2 is allocated to SFX + PSGChannel3SFX db ; !0 means channel 3 is allocated to SFX + + ; command buffers for SFX + PSGSFXChan0Volume db ; the volume for channel 0 + PSGSFXChan1Volume db ; the volume for channel 1 + PSGSFXChan2Volume db ; the volume for channel 2 + PSGSFXChan3Volume db ; the volume for channel 3 + + PSGSFXVolumeAttenuation db ; the volume attenuation applied to the SFX (0-15) + + ; fundamental vars for SFX + PSGSFXStatus db ; are we playing a SFX? + PSGSFXStart dw ; the pointer to the beginning of SFX + PSGSFXPointer dw ; the pointer to the current address + PSGSFXLoopPoint dw ; the pointer to the loop begin + PSGSFXSkipFrames db ; the frames we need to skip + PSGSFXLoopFlag db ; the SFX should loop or not (flag) + + ; decompression vars for SFX + PSGSFXSubstringLen db ; lenght of the substring we are playing + PSGSFXSubstringRetAddr dw ; return to this address when substring is over +.ends +;.ende ; in case you want to use .enum instead of .ramsection \ No newline at end of file diff --git a/apps/sn76489/psglib_zasm.asm b/apps/sn76489/psglib_zasm.asm new file mode 100644 index 0000000..2e55640 --- /dev/null +++ b/apps/sn76489/psglib_zasm.asm @@ -0,0 +1,1106 @@ +;================================================================ +; PSGlib - Programmable Sound Generator audio library - by sverx +; https://github.com/sverx/PSGlib +;================================================================ + +; zasm port for z80_monitor. The code is included in the application ROM and +; its workspace occupies RAM immediately above the application's private stack. + +PSG_STOPPED .equ 0 +PSG_PLAYING .equ 1 + +PSGDataPort .equ 0x19 + +PSGLatch .equ 0x80 +PSGData .equ 0x40 + +PSGChannel0 .equ 00000000b +PSGChannel1 .equ 00100000b +PSGChannel2 .equ 01000000b +PSGChannel3 .equ 01100000b +PSGVolumeData .equ 00010000b + +PSGWait .equ 0x38 +PSGSubString .equ 0x08 +PSGLoop .equ 0x01 +PSGEnd .equ 0x00 + +SFX_CHANNEL2 .equ 0x01 +SFX_CHANNEL3 .equ 0x02 +SFX_CHANNELS2AND3 .equ SFX_CHANNEL2|SFX_CHANNEL3 +SFX_CHANNEL0 .equ 0x04 +SFX_CHANNEL1 .equ 0x08 + +; ************************************************************************************ +; initializes the PSG 'engine' +; destroys AF +PSGInit: + xor a ; ld a,PSG_STOPPED + ld (PSGMusicStatus),a ; set music status to PSG_STOPPED + ld (PSGSFXStatus),a ; set SFX status to PSG_STOPPED + ld (PSGChannel0SFX),a ; set channel 0 SFX to PSG_STOPPED + ld (PSGChannel1SFX),a ; set channel 1 SFX to PSG_STOPPED + ld (PSGChannel2SFX),a ; set channel 2 SFX to PSG_STOPPED + ld (PSGChannel3SFX),a ; set channel 3 SFX to PSG_STOPPED + ld (PSGMusicVolumeAttenuation),a ; volume attenuation = none + ret + +; ************************************************************************************ +; all receive in HL the address of the PSG to start playing +; PSGPlayLoops also receives in A the number of times to loop the PSG +; destroys AF +PSGPlayNoRepeat: ; we don't want the song ever to loop + xor a +PSGPlayLoops: ; we want the song to loop a certain amount of times + ld (PSGLoopCounter),a + xor a ; we don't want the song to loop forever + jp _psglib_anon_01 +PSGPlay: + ld a,0x1 ; we want the song to loop forever +_psglib_anon_01:ld (PSGLoopFlag),a + call PSGStop ; if there's a tune already playing, we should stop it! + ld (PSGMusicStart),hl ; store the begin point of music + ld (PSGMusicPointer),hl ; set music pointer to begin of music + ld (PSGMusicLoopPoint),hl ; looppointer points to begin too + xor a + ld (PSGMusicSkipFrames),a ; reset the skip frames + ld (PSGMusicSubstringLen),a ; reset the substring len (for compression) + ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent) + ld (PSGMusicLastLatch),a ; reset last latch to chn 0 volume 0 + ld a,0x0F ; ensure unused channels remain silent + ld (PSGChan0Volume),a + ld (PSGChan1Volume),a + ld (PSGChan2Volume),a + ld (PSGChan3Volume),a + ld a,PSG_PLAYING + ld (PSGMusicStatus),a ; set status to PSG_PLAYING + ret + +; ************************************************************************************ +; stops the music (leaving the SFX on, if it's playing) +; destroys AF +PSGStop: + ld a,(PSGMusicStatus) ; if it's already stopped, leave + or a + ret z + ld a,(PSGChannel0SFX) + or a + jr nz,_psglib_anon_02 + ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_02:ld a,(PSGChannel1SFX) + or a + jr nz,_psglib_anon_03 + ld a,PSGLatch|PSGChannel1|PSGVolumeData|0x0F ; latch channel 1, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_03:ld a,(PSGChannel2SFX) + or a + jr nz,_psglib_anon_04 + ld a,PSGLatch|PSGChannel2|PSGVolumeData|0x0F ; latch channel 2, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_04:ld a,(PSGChannel3SFX) + or a + jr nz,_psglib_anon_05 + ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; latch channel 3, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_05:xor a ; ld a,PSG_STOPPED + ld (PSGMusicStatus),a ; set status to PSG_STOPPED + ret + +; ************************************************************************************ +; resume a previously stopped music (with volume attenuation if needed) +; destroys AF,HL +PSGResume: + ld a,(PSGMusicStatus) ; if it's already playing, leave + or a + ret nz + + ld hl,PSGMusicVolumeAttenuation + ld l,(hl) ; load volume attenuation in L + + ld a,(PSGChannel0SFX) + or a + jr nz,_ch1 + ld a,(PSGChan0LowTone) ; restore channel 0 frequency + or PSGLatch|PSGChannel0 + out (PSGDataPort),a + ld a,(PSGChan0HighTone) + out (PSGDataPort),a + ld a,(PSGChan0Volume) ; restore channel 0 volume + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_06 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_06:or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_ch1: + ld a,(PSGChannel1SFX) + or a + jr nz,_ch2 + ld a,(PSGChan1LowTone) ; restore channel 1 frequency + or PSGLatch|PSGChannel1 + out (PSGDataPort),a + ld a,(PSGChan1HighTone) + out (PSGDataPort),a + ld a,(PSGChan1Volume) ; restore channel 1 volume + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_07 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_07:or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a + +_ch2: + ld a,(PSGChannel2SFX) + or a + jr nz,_ch3 + ld a,(PSGChan2LowTone) ; restore channel 2 frequency + or PSGLatch|PSGChannel2 + out (PSGDataPort),a + ld a,(PSGChan2HighTone) + out (PSGDataPort),a + ld a,(PSGChan2Volume) ; restore channel 2 volume + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_08 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_08:or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a + +_ch3: + ld a,(PSGChannel3SFX) + or a + jr nz,_leave + ld a,(PSGChan3LowTone) ; restore channel 3 frequency + or PSGLatch|PSGChannel3 + out (PSGDataPort),a + ld a,(PSGChan3Volume) ; restore channel 3 volume + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_09 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_09:or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a + +_leave: + ld a,PSG_PLAYING + ld (PSGMusicStatus),a ; set status to PSG_PLAYING + ret + +; ************************************************************************************ +; sets the currently looping music to no more loops after the current +; destroys AF +PSGCancelLoop: + xor a + ld (PSGLoopFlag),a + ld (PSGLoopCounter),a + ret + +; ************************************************************************************ +; gets the current status of music into register A +PSGGetStatus: + ld a,(PSGMusicStatus) + ret + +; ************************************************************************************ +; receives in L the volume attenuation for the music (0-15) +; destroys AF +PSGSetMusicVolumeAttenuation: + ld a,l + ld (PSGMusicVolumeAttenuation),a + ld a,(PSGMusicStatus) ; if tune is not playing, leave + or a + ret z + + ld a,(PSGChannel0SFX) ; channel 0 busy with SFX? + or a + jr nz,_music_restore_channel1 ; if so, skip channel 0 + + ld a,(PSGChan0Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_10 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_10:or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_music_restore_channel1: + ld a,(PSGChannel1SFX) ; channel 1 busy with SFX? + or a + jr nz,_music_restore_channel2 ; if so, skip channel 1 + + ld a,(PSGChan1Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_11 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_11:or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_music_restore_channel2: + ld a,(PSGChannel2SFX) ; channel 2 busy with SFX? + or a + jr nz,_music_restore_channel3 ; if so, skip channel 2 + + ld a,(PSGChan2Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_12 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_12:or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_music_restore_channel3: + ld a,(PSGChannel3SFX) ; channel 3 busy with SFX? + or a + ret nz ; if so, we're done + + ld a,(PSGChan3Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_13 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_13:or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + ret + +; ************************************************************************************ +; receives in L the volume attenuation for the SFXs (0-15) +; destroys AF +PSGSetSFXVolumeAttenuation: + ld a,l + ld (PSGSFXVolumeAttenuation),a + ld a,(PSGSFXStatus) ; if an SFX is not playing, leave + or a + ret z + + ld a,(PSGChannel0SFX) ; channel 0 used by SFX? + or a + jr z,_sfx_restore_channel1 ; if not, skip channel 0 + + ld a,(PSGSFXChan0Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_14 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_14:or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_sfx_restore_channel1: + ld a,(PSGChannel1SFX) ; channel 1 used by SFX? + or a + jr z,_sfx_restore_channel2 ; if not, skip channel 1 + + ld a,(PSGSFXChan1Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_15 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_15:or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_sfx_restore_channel2: + ld a,(PSGChannel2SFX) ; channel 2 used by SFX? + or a + jr z,_sfx_restore_channel3 ; if not, skip channel 2 + + ld a,(PSGSFXChan2Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_16 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_16:or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_sfx_restore_channel3: + ld a,(PSGChannel3SFX) ; channel 3 used by SFX? + or a + ret z ; if not, we're done + + ld a,(PSGSFXChan3Volume) + and 0x0F + add a,l + cp 0x0F ; check overflow + jr c,_psglib_anon_17 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_17:or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + ret + +; ************************************************************************************ +; sets all PSG channels to volume ZERO +; destroys AF +PSGSilenceChannels: + ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent) + out (PSGDataPort),a + ld a,PSGLatch|PSGChannel1|PSGVolumeData|0x0F ; latch channel 1, volume=0xF (silent) + out (PSGDataPort),a + ld a,PSGLatch|PSGChannel2|PSGVolumeData|0x0F ; latch channel 2, volume=0xF (silent) + out (PSGDataPort),a + ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; latch channel 3, volume=0xF (silent) + out (PSGDataPort),a + ret + +; ************************************************************************************ +; resets all PSG channels to previous volume +; destroys AF,HL +PSGRestoreVolumes: + ld a,(PSGChannel0SFX) ; channel 0 busy with SFX? + jr nz,_restoreSFX0 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + jr z,_chkchn1 ; if not, skip channel 0 + + ld a,(PSGChan0Volume) + and 0x0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write0 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + jp _write0 + +_restoreSFX0: + ld a,(PSGSFXChan0Volume) + and 0x0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write0 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + +_write0: + or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_chkchn1: + ld a,(PSGChannel1SFX) ; channel 1 busy with SFX? + jr nz,_restoreSFX1 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + jr z,_chkchn2 ; if not, skip channel 1 + + ld a,(PSGChan1Volume) + and 0x0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write1 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + jp _write1 + +_restoreSFX1: + ld a,(PSGSFXChan1Volume) + and 0x0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write1 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + +_write1: + or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_chkchn2: + ld a,(PSGChannel2SFX) ; channel 2 busy with SFX? + jr nz,_restoreSFX2 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + jr z,_chkchn3 ; if not, skip chn2 + + ld a,(PSGChan2Volume) + and 0x0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write2 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + jp _write2 + +_restoreSFX2: + ld a,(PSGSFXChan2Volume) + and 0x0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write2 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + +_write2: + or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + +_chkchn3: + ld a,(PSGChannel3SFX) ; channel 3 busy with SFX? + jr nz,_restoreSFX3 + + ld a,(PSGMusicStatus) ; check if tune is playing + or a + ret z ; if not, we've done + + ld a,(PSGChan3Volume) + and 0x0F + ld hl,PSGMusicVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write3 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + jp _write3 + +_restoreSFX3: + ld a,(PSGSFXChan3Volume) + and 0x0F + ld hl,PSGSFXVolumeAttenuation + add a,(hl) + cp 0x0F ; check overflow + jr c,_write3 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 + +_write3: + or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + ret + +; ************************************************************************************ +; receives in HL the address of the SFX PSG to start +; receives in C the mask that indicates which channel(s) the SFX will use +; destroys AF +PSGSFXPlayLoop: + ld a,1 ; SFX _IS_ a looping one + jp _psglib_anon_18 +PSGSFXPlay: + xor a ; SFX is _NOT_ a looping one +_psglib_anon_18:ld (PSGSFXLoopFlag),a + call PSGSFXStop ; if there's a SFX already playing, we should stop it! + ld (PSGSFXStart),hl ; store begin of SFX + ld (PSGSFXPointer),hl ; set the pointer to begin of SFX + ld (PSGSFXLoopPoint),hl ; looppointer points to begin too + xor a + ld (PSGSFXSkipFrames),a ; reset the skip frames + ld (PSGSFXSubstringLen),a ; reset the substring len + bit 2,c ; channel 0 needed? + jr z,_psglib_anon_19 + ld a,PSG_PLAYING + ld (PSGChannel0SFX),a +_psglib_anon_19:bit 3,c ; channel 1 needed? + jr z,_psglib_anon_20 + ld a,PSG_PLAYING + ld (PSGChannel1SFX),a +_psglib_anon_20:bit 0,c ; channel 2 needed? + jr z,_psglib_anon_21 + ld a,PSG_PLAYING + ld (PSGChannel2SFX),a +_psglib_anon_21:bit 1,c ; channel 3 needed? + jr z,_psglib_anon_22 + ld a,PSG_PLAYING + ld (PSGChannel3SFX),a +_psglib_anon_22:ld (PSGSFXStatus),a ; set SFXstatus to PSG_PLAYING if SFX uses at least one channel (0=PSG_STOPPED otherwise) + ld a,(PSGMusicStatus) ; check if a tune is currently playing + or a + ret z ; if not, we've done + + ld a,(PSGChan3LowTone) ; test if music playing on channel 3 uses the frequency of channel 2 + and 3 + cp 3 + ret nz ; if channel 3 doesn't use the frequency of channel 2 we're done + + ld a,PSG_PLAYING + ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied by the SFX + ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; and silence channel 3 + out (PSGDataPort),a + ret + +; ************************************************************************************ +; stops the SFX (leaving the music on, if it's playing) +; destroys AF +PSGSFXStop: + ld a,(PSGSFXStatus) ; check status + or a + ret z ; no SFX playing, leave + + ld a,(PSGChannel0SFX) ; channel 0 playing? + or a + jr z,_psglib_anon_23 + ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_23:ld a,(PSGChannel1SFX) ; channel 1 playing? + or a + jr z,_psglib_anon_24 + ld a,PSGLatch|PSGChannel1|PSGVolumeData|0x0F ; latch channel 1, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_24:ld a,(PSGChannel2SFX) ; channel 2 playing? + or a + jr z,_psglib_anon_25 + ld a,PSGLatch|PSGChannel2|PSGVolumeData|0x0F ; latch channel 2, volume=0xF (silent) + out (PSGDataPort),a +_psglib_anon_25:ld a,(PSGChannel3SFX) ; channel 3 playing? + or a + jr z,_psglib_anon_26 + ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; latch channel 3, volume=0xf (silent) + out (PSGDataPort),a +_psglib_anon_26:ld a,(PSGMusicStatus) ; check if a tune is playing + or a + jp z,_skipRestore ; if it's not playing, skip restoring PSG values + + ld a,(PSGChannel0SFX) ; channel 0 playing? + or a + jr z,_skip_chn0 + ld a,(PSGChan0LowTone) + and 0x0F ; use only low 4 bits of byte + or PSGLatch|PSGChannel0 ; latch channel 0, low part of tone + out (PSGDataPort),a + ld a,(PSGChan0HighTone) ; high part of tone (latched channel 0, tone) + and 0x3F ; use only low 6 bits of byte + out (PSGDataPort),a + ld a,(PSGChan0Volume) ; restore music' channel 0 volume + and 0x0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp 0x0F ; check overflow + jr c,_psglib_anon_27 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_27: or PSGLatch|PSGChannel0|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc + +_skip_chn0: + ld a,(PSGChannel1SFX) ; channel 1 playing? + or a + jr z,_skip_chn1 + ld a,(PSGChan1LowTone) + and 0x0F ; use only low 4 bits of byte + or PSGLatch|PSGChannel1 ; latch channel 1, low part of tone + out (PSGDataPort),a + ld a,(PSGChan1HighTone) ; high part of tone (latched channel 1, tone) + and 0x3F ; use only low 6 bits of byte + out (PSGDataPort),a + ld a,(PSGChan1Volume) ; restore music' channel 1 volume + and 0x0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp 0x0F ; check overflow + jr c,_psglib_anon_28 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_28: or PSGLatch|PSGChannel1|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc + +_skip_chn1: + ld a,(PSGChannel2SFX) ; channel 2 playing? + or a + jr z,_skip_chn2 + ld a,(PSGChan2LowTone) + and 0x0F ; use only low 4 bits of byte + or PSGLatch|PSGChannel2 ; latch channel 2, low part of tone + out (PSGDataPort),a + ld a,(PSGChan2HighTone) ; high part of tone (latched channel 2, tone) + and 0x3F ; use only low 6 bits of byte + out (PSGDataPort),a + ld a,(PSGChan2Volume) ; restore music' channel 2 volume + and 0x0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp 0x0F ; check overflow + jr c,_psglib_anon_29 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_29: or PSGLatch|PSGChannel2|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc + +_skip_chn2: + ld a,(PSGChannel3SFX) ; channel 3 playing? + or a + jr z,_skip_chn3 + ld a,(PSGChan3LowTone) + and 0x07 ; use only low 3 bits of byte + or PSGLatch|PSGChannel3 ; latch channel 3, low part of tone (no high part) + out (PSGDataPort),a + ld a,(PSGChan3Volume) ; restore music' channel 3 volume + and 0x0F ; use only low 4 bits of byte + push bc + ld b,a ; save it temporary + ld a,(PSGMusicVolumeAttenuation) ; + add a,b + cp 0x0F ; check overflow + jr c,_psglib_anon_30 ; if it's <=15 then ok + ld a,0x0F ; else, reset to 15 +_psglib_anon_30: or PSGLatch|PSGChannel3|PSGVolumeData + out (PSGDataPort),a ; output the byte + pop bc +_skip_chn3: + xor a ; ld a,PSG_STOPPED +_skipRestore: + ld (PSGChannel0SFX),a + ld (PSGChannel1SFX),a + ld (PSGChannel2SFX),a + ld (PSGChannel3SFX),a + ld (PSGSFXStatus),a ; set status to PSG_STOPPED + ret + +; ************************************************************************************ +; sets the currently looping SFX to no more loops after the current +; destroys AF +PSGSFXCancelLoop: + xor a + ld (PSGSFXLoopFlag),a + ret + +; ************************************************************************************ +; gets the current SFX status into register A +PSGSFXGetStatus: + ld a,(PSGSFXStatus) + ret + +; ************************************************************************************ +; processes a music frame +; destroys AF,HL,BC +PSGFrame: + ld a,(PSGMusicStatus) ; check if we have got to play a tune + or a + ret z + + ld a,(PSGMusicSkipFrames) ; check if we havve got to skip frames + or a + jp nz,_skipFrame + + ld hl,(PSGMusicPointer) ; read current address + +_intLoop: + ld b,(hl) ; load PSG byte (in B) + inc hl ; point to next byte + ld a,(PSGMusicSubstringLen) ; read substring len + or a + jr z,_continue ; check if it is 0 (we are not in a substring) + dec a ; decrease len + ld (PSGMusicSubstringLen),a ; save len + jr nz,_continue + ld hl,(PSGMusicSubstringRetAddr) ; substring is over, retrieve return address + +_continue: + ld a,b ; copy PSG byte into A + cp PSGLatch ; is it a latch? + jr c,_noLatch ; if < 0x80 then it is NOT a latch + ld (PSGMusicLastLatch),a ; it is a latch - save it in "LastLatch" + + ; we have got the latch PSG byte both in A and in B + ; and we have to check if the value should pass to PSG or not + bit 4,a ; test if it is a volume + jr nz,_latch_Volume ; jump if volume data + bit 6,a ; test if the latch it is for channels 0-1 or for 2-3 + jp nz,_latch_chn23 ; jump if it is for channels 2-3 + + ; we have got the latch (tone, chn 0 or 1) PSG byte both in A and in B + ; and we have to check if the value should be passed to PSG or not + bit 5,a ; test if tone it is for channel 0 or 1 + jr z,_ifchn0 ; jump if channel 0 + ld (PSGChan1LowTone),a ; save tone LOW data + ld a,(PSGChannel1SFX) ; channel 1 available for music? + or a + jr z,_send2PSG_B ; then send data + jp _intLoop +_ifchn0: + ld (PSGChan0LowTone),a ; save tone LOW data + ld a,(PSGChannel0SFX) ; channel 0 available for music? + or a + jr z,_send2PSG_B ; then send data + jp _intLoop + +_latch_chn23: + ; we have got the latch (tone, chn 2 or 3) PSG byte both in A and in B + ; and we have to check if the value should be passed to PSG or not + bit 5,a ; test if tone it is for channel 2 or 3 + jr z,_ifchn2 ; jump if channel 2 + ld (PSGChan3LowTone),a ; save tone LOW data + ld a,(PSGChannel3SFX) ; channel 3 available for music? + or a + jp nz,_intLoop + ld a,(PSGChan3LowTone) + and 3 ; test if channel 3 is set to use the frequency of channel 2 + cp 3 + jr nz,_send2PSG_B ; if channel 3 does not use frequency of channel 2 jump + ld a,(PSGChannel2SFX) ; test if an SFX is playing on channel 2 + or a + jr z,_send2PSG_B ; if no SFX is playing on channel 2 jump + ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied + ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; and silence channel 3 + out (PSGDataPort),a + jp _intLoop +_ifchn2: + ld (PSGChan2LowTone),a ; save tone LOW data + ld a,(PSGChannel2SFX) ; channel 2 available for music? + or a + jr z,_send2PSG_B + jp _intLoop + +_noLatch: + cp PSGData + jr c,_command ; if < 0x40 then it is a command + ; it's a data + ld a,(PSGMusicLastLatch) ; retrieve last latch + jp _output_NoLatch + +_latch_Volume: + bit 6,a ; test if the latch it is for channels 0-1 or for 2-3 + jr nz,_latch_Volume_23 ; volume is for channel 2 or 3 + bit 5,a ; test if volume it is for channel 0 or 1 + jr z,_ifchn0v ; jump for channel 0 + ld (PSGChan1Volume),a ; save volume data + ld a,(PSGChannel1SFX) ; channel 1 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop +_ifchn0v: + ld (PSGChan0Volume),a ; save volume data + ld a,(PSGChannel0SFX) ; channel 0 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop + +_send2PSG_B: + ld a,b +_send2PSG_A: + out (PSGDataPort),a ; output the byte + jp _intLoop + +_latch_Volume_23: + bit 5,a ; test if volume it is for channel 2 or 3 + jr z,_chn2 ; jump for channel 2 + ld (PSGChan3Volume),a ; save volume data + ld a,(PSGChannel3SFX) ; channel 3 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop +_chn2: + ld (PSGChan2Volume),a ; save volume data + ld a,(PSGChannel2SFX) ; channel 2 available for music? + or a + jr z,_sendVolume2PSG_B + jp _intLoop + +_skipFrame: + dec a + ld (PSGMusicSkipFrames),a + ret + +_command: + cp PSGWait + jr z,_done ; no additional frames + jr c,_otherCommands ; other commands? + and 0x07 ; take only the last 3 bits for skip frames + ld (PSGMusicSkipFrames),a ; we got additional frames +_done: + ld (PSGMusicPointer),hl ; save current address + ret ; frame done + +_otherCommands: + cp PSGSubString + jr nc,_substring + cp PSGEnd + jr z,_musicLoop + cp PSGLoop + jr z,_setLoopPoint + + ; *************************************************************************** + ; we should never get here! + ; if we do, it means the PSG file is probably corrupted, so we just RET + ; *************************************************************************** + + ret + +_sendVolume2PSG_B: + ld a,b +_sendVolume2PSG_A: + ld c,a ; save the PSG command byte + and 0x0F ; keep lower nibble + ld b,a ; save value + ld a,(PSGMusicVolumeAttenuation) ; load volume attenuation + add a,b ; add value + cp 0x0F ; check overflow + jr c,_no_overflow ; if it is <=15 then ok + ld a,0x0F ; else, reset to 15 +_no_overflow: + ld b,a ; save new attenuated volume value + ld a,c ; retrieve PSG command + and 0xF0 ; keep upper nibble + or b ; set attenuated volume + out (PSGDataPort),a ; output the byte + jp _intLoop + +_output_NoLatch: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ; note that non-latch commands can be only contain frequencies (no volumes) + ; for channels 0,1,2 only (no noise) + bit 6,a ; test if the latch it is for channels 0-1 or for chn 2 + jr nz,_high_part_Tone_ch2 ; it is tone data for channel 2 + + bit 5,a ; test if latch is for channel 0 or 1 + jr nz,_high_part_Tone_ch1 ; it is tone data for channel 1 + + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ld a,b ; move PSG data in A + ld (PSGChan0HighTone),a ; save channel 0 tone HIGH data + ld a,(PSGChannel0SFX) ; channel 0 available for music? + or a + jr z,_send2PSG_B + jp _intLoop + +_high_part_Tone_ch1: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ld a,b ; move PSG data in A + ld (PSGChan1HighTone),a ; save channel 1 tone HIGH data + ld a,(PSGChannel1SFX) ; channel 1 available for music? + or a + jr z,_send2PSG_B + jp _intLoop + +_high_part_Tone_ch2: + ; we got the last latch in A and the PSG data in B + ; and we have to check if the value should pass to PSG or not + ; PSG data can only be for channel 2, here + ld a,b ; move PSG data in A + ld (PSGChan2HighTone),a ; save channel 2 tone HIGH data + ld a,(PSGChannel2SFX) ; channel 2 available for music + or a + jp z,_send2PSG_B ; TOO FAR for JR here :| + jp _intLoop + +_setLoopPoint: + ld (PSGMusicLoopPoint),hl + jp _intLoop + +_musicLoop: + ld hl,(PSGMusicLoopPoint) + ld a,(PSGLoopFlag) ; infinite loop requested? + or a + jp nz,_intLoop ; Yes: do loop + ld a,(PSGLoopCounter) ; one more loop requested? + or a + jp z,PSGStop ; No: stop the music! (tail call optimization) + dec a + ld (PSGLoopCounter),a ; decrement loop counter + jp _intLoop ; do loop + +_substring: + sub PSGSubString-4 ; len is value - 0x08 + 4 + ld (PSGMusicSubstringLen),a ; save len + ld c,(hl) ; load substring address (offset) + inc hl + ld b,(hl) + inc hl + ld (PSGMusicSubstringRetAddr),hl ; save return address + ld hl,(PSGMusicStart) + add hl,bc ; make substring current + jp _intLoop + +; ************************************************************************************ +; processes a SFX frame +; destroys AF,HL,BC +PSGSFXFrame: + ld a,(PSGSFXStatus) ; check if we have got to play SFX + or a + ret z + + ld a,(PSGSFXSkipFrames) ; check if we have got to skip frames + or a + jp nz,_skipSFXFrame + + ld hl,(PSGSFXPointer) ; read current SFX address + +_intSFXLoop: + ld b,(hl) ; load a byte in B, temporary + inc hl ; point to next byte + ld a,(PSGSFXSubstringLen) ; read substring len + or a ; check if it is 0 (we are not in a substring) + jr z,_SFXcontinue + dec a ; decrease len + ld (PSGSFXSubstringLen),a ; save len + jr nz,_SFXcontinue + ld hl,(PSGSFXSubstringRetAddr) ; substring over, retrieve return address + +_SFXcontinue: + ld a,b ; restore byte + cp PSGData + jp c,_SFXcommand ; if less than 0x40 then it is a command + bit 7,a ; check if it is a latch + jr z,_SFXoutbyte ; if not, output it directly + bit 4,a ; check if it is a volume byte + jr z,_SFXoutbyte ; if not, output it directly + + bit 6,a ; check if it is volume for channels 0-1 or for 2-3 + jr nz,_SFXchn23 + + bit 5,a ; check if it is volume for channel 0 or channel 1 + jr nz,_SFXvolumechn1 + ld (PSGSFXChan0Volume),a + jr _SFXoutvolume + +_SFXvolumechn1: + ld (PSGSFXChan1Volume),a + jr _SFXoutvolume + +_SFXchn23: + bit 5,a ; check if it is volume for channel 2 or channel 3 + jr nz,_SFXvolumechn3 + ld (PSGSFXChan2Volume),a + jr _SFXoutvolume + +_SFXvolumechn3: + ld (PSGSFXChan3Volume),a + +_SFXoutvolume: + and 0x0F ; keep lower nibble + ld c,a ; save value + ld a,(PSGSFXVolumeAttenuation) ; load volume attenuation + add a,c ; add value + cp 0x0F ; check overflow + jr c,_no_overflow_SFX ; if it is <=15 then ok + ld a,0x0F ; else, reset to 15 +_no_overflow_SFX: + ld c,a ; save new attenuated volume value + ld a,b ; retrieve original PSG command + and 0xF0 ; keep upper nibble (channel+volume bits) + or c ; set attenuated SFX volume +_SFXoutbyte: + out (PSGDataPort),a ; output the byte + jp _intSFXLoop + +_skipSFXFrame: + dec a + ld (PSGSFXSkipFrames),a + ret + +_SFXcommand: + cp PSGWait + jr z,_SFXdone ; no additional frames + jr c,_SFXotherCommands ; other commands? + and 0x07 ; take only the last 3 bits for skip frames + ld (PSGSFXSkipFrames),a ; we got additional frames to skip +_SFXdone: + ld (PSGSFXPointer),hl ; save current address + ret ; frame done + +_SFXotherCommands: + cp PSGSubString + jr nc,_SFXsubstring + cp PSGEnd + jr z,_sfxLoop + cp PSGLoop + jr z,_SFXsetLoopPoint + + ; *************************************************************************** + ; we should never get here! + ; if we do, it means the PSG SFX file is probably corrupted, so we just RET + ; *************************************************************************** + + ret + +_SFXsetLoopPoint: + ld (PSGSFXLoopPoint),hl + jp _intSFXLoop + +_sfxLoop: + ld a,(PSGSFXLoopFlag) ; is it a looping SFX? + or a + jp z,PSGSFXStop ; No:stop it! (tail call optimization) + ld hl,(PSGSFXLoopPoint) + ld (PSGSFXPointer),hl + jp _intSFXLoop + +_SFXsubstring: + sub PSGSubString-4 ; len is value - 0x08 + 4 + ld (PSGSFXSubstringLen),a ; save len + ld c,(hl) ; load substring address (offset) + inc hl + ld b,(hl) + inc hl + ld (PSGSFXSubstringRetAddr),hl ; save return address + ld hl,(PSGSFXStart) + add hl,bc ; make substring current + jp _intSFXLoop + +; PSGlib workspace in the RAM reserved by the SN76489 application. +PSGLIB_RAM_START .equ STACK_TOP+1 + ; fundamental vars +PSGMusicStatus .equ PSGLIB_RAM_START+0x00 ; are we playing a background music? +PSGMusicStart .equ PSGLIB_RAM_START+0x01 ; the pointer to the beginning of music +PSGMusicPointer .equ PSGLIB_RAM_START+0x03 ; the pointer to the current +PSGMusicLoopPoint .equ PSGLIB_RAM_START+0x05 ; the pointer to the loop begin +PSGMusicSkipFrames .equ PSGLIB_RAM_START+0x07 ; the frames we need to skip +PSGLoopFlag .equ PSGLIB_RAM_START+0x08 ; the tune should loop forever or not (flag) +PSGLoopCounter .equ PSGLIB_RAM_START+0x09 ; how many times the tune should loop +PSGMusicLastLatch .equ PSGLIB_RAM_START+0x0a ; the last PSG music latch + + ; decompression vars +PSGMusicSubstringLen .equ PSGLIB_RAM_START+0x0b ; lenght of the substring we are playing +PSGMusicSubstringRetAddr .equ PSGLIB_RAM_START+0x0c ; return to this address when substring is over + + ; command buffers +PSGChan0Volume .equ PSGLIB_RAM_START+0x0e ; the volume for channel 0 +PSGChan1Volume .equ PSGLIB_RAM_START+0x0f ; the volume for channel 1 +PSGChan2Volume .equ PSGLIB_RAM_START+0x10 ; the volume for channel 2 +PSGChan3Volume .equ PSGLIB_RAM_START+0x11 ; the volume for channel 3 + +PSGChan0LowTone .equ PSGLIB_RAM_START+0x12 ; the low tone bits for channels 0 +PSGChan1LowTone .equ PSGLIB_RAM_START+0x13 ; the low tone bits for channels 1 +PSGChan2LowTone .equ PSGLIB_RAM_START+0x14 ; the low tone bits for channels 2 +PSGChan3LowTone .equ PSGLIB_RAM_START+0x15 ; the low tone bits for channels 3 +PSGChan0HighTone .equ PSGLIB_RAM_START+0x16 ; the high tone bits for channel 0 +PSGChan1HighTone .equ PSGLIB_RAM_START+0x17 ; the high tone bits for channel 1 +PSGChan2HighTone .equ PSGLIB_RAM_START+0x18 ; the high tone bits for channel 2 + +PSGMusicVolumeAttenuation .equ PSGLIB_RAM_START+0x19 ; the volume attenuation applied to the tune (0-15) + + ; ******* SFX ************* + + ; flags for SFX channels usage +PSGChannel0SFX .equ PSGLIB_RAM_START+0x1a ; !0 means channel 0 is allocated to SFX +PSGChannel1SFX .equ PSGLIB_RAM_START+0x1b ; !0 means channel 1 is allocated to SFX +PSGChannel2SFX .equ PSGLIB_RAM_START+0x1c ; !0 means channel 2 is allocated to SFX +PSGChannel3SFX .equ PSGLIB_RAM_START+0x1d ; !0 means channel 3 is allocated to SFX + + ; command buffers for SFX +PSGSFXChan0Volume .equ PSGLIB_RAM_START+0x1e ; the volume for channel 0 +PSGSFXChan1Volume .equ PSGLIB_RAM_START+0x1f ; the volume for channel 1 +PSGSFXChan2Volume .equ PSGLIB_RAM_START+0x20 ; the volume for channel 2 +PSGSFXChan3Volume .equ PSGLIB_RAM_START+0x21 ; the volume for channel 3 + +PSGSFXVolumeAttenuation .equ PSGLIB_RAM_START+0x22 ; the volume attenuation applied to the SFX (0-15) + + ; fundamental vars for SFX +PSGSFXStatus .equ PSGLIB_RAM_START+0x23 ; are we playing a SFX? +PSGSFXStart .equ PSGLIB_RAM_START+0x24 ; the pointer to the beginning of SFX +PSGSFXPointer .equ PSGLIB_RAM_START+0x26 ; the pointer to the current address +PSGSFXLoopPoint .equ PSGLIB_RAM_START+0x28 ; the pointer to the loop begin +PSGSFXSkipFrames .equ PSGLIB_RAM_START+0x2a ; the frames we need to skip +PSGSFXLoopFlag .equ PSGLIB_RAM_START+0x2b ; the SFX should loop or not (flag) + + ; decompression vars for SFX +PSGSFXSubstringLen .equ PSGLIB_RAM_START+0x2c ; lenght of the substring we are playing +PSGSFXSubstringRetAddr .equ PSGLIB_RAM_START+0x2d ; return to this address when substring is over +PSGLIB_RAM_SIZE .equ 0x2f +#assert PSGLIB_RAM_START+PSGLIB_RAM_SIZE <= SAVED_SP diff --git a/bios.asm b/bios.asm index 434c328..bf272f1 100644 --- a/bios.asm +++ b/bios.asm @@ -145,6 +145,7 @@ init: ei call tape_init + call psg_silence ; Print header message ld hl,msg_header @@ -272,6 +273,13 @@ release_daisy_chain: ctc_uart_init: ; Reset CTC channels ld a,003h + ; A channel may still be waiting for a time constant after a warm boot. + ; In that state the first byte is consumed as the constant, so send the + ; reset control word again to guarantee the unused channel is disabled. + out (CTC_CHANNEL_0),a + out (CTC_CHANNEL_1),a + out (CTC_CHANNEL_2),a + out (CTC_CHANNEL_3),a out (CTC_CHANNEL_0),a out (CTC_CHANNEL_1),a out (CTC_CHANNEL_2),a @@ -557,6 +565,20 @@ delay_ms: jp nz,1$ ; 10T ret +; ------------------------------------------------------- +; Silence all audio generators +; ------------------------------------------------------- +psg_silence: + ld a,0x9f ; mute tone 0 + out (SN_PORT),a + ld a,0xbf ; mute tone 1 + out (SN_PORT),a + ld a,0xdf ; mute tone 2 + out (SN_PORT),a + ld a,0xff ; mute noise + out (SN_PORT),a + ret + ; ------------------------------------------------------- ; Strings ; ------------------------------------------------------- diff --git a/hardware.inc b/hardware.inc index 11b9da7..b00b2a6 100644 --- a/hardware.inc +++ b/hardware.inc @@ -6,3 +6,4 @@ BANK_ENABLE_BIT .equ 004h BANK_MODE_CLEAR_MASK .equ 0f9h TAPE_PORT .equ 018h +SN_PORT .equ 019h \ No newline at end of file diff --git a/hardware/CUPL.PLD b/hardware/CUPL.PLD deleted file mode 100644 index 1668645..0000000 --- a/hardware/CUPL.PLD +++ /dev/null @@ -1,199 +0,0 @@ -Name casette; -PartNo 00; -Date 2026-10-03; -Revision 01; -Designer joppe; -Company unknown; -Assembly None; -Location unknown; -Device g22v10; - - -/* ------------------------------------------------------------ - Constants - ------------------------------------------------------------ */ - -ar = 'b'0; -sp = 'b'0; - - -/* ------------------------------------------------------------ - Pin assignments - - Physical signals IOSEL, RD and WR are active-low. - Their polarity is expressed in the equations with !. - ------------------------------------------------------------ */ - -/* inputs */ - -PIN 1 = CLK; - -PIN 2 = A1; -PIN 3 = A2; - -PIN 4 = IOSEL; -PIN 5 = RD; -PIN 6 = A0; - -PIN 7 = COMP; -PIN 8 = WR; - - -/* data bus */ - -PIN 14 = D0; -PIN 15 = D1; -PIN 16 = D2; -PIN 17 = D3; -PIN 18 = D4; -PIN 19 = D5; -PIN 20 = D6; -PIN 21 = D7; - - -/* registered output */ - -PIN 22 = LED; - - -/* ------------------------------------------------------------ - Read register at offset 0 - - Conditions: - - /IOSEL = 0 - /RD = 0 - /WR = 1 - A2:A0 = 000 - - Result: - - D0 = COMP - D7..D1 = 0 - ------------------------------------------------------------ */ - -D0 = COMP; - -D1 = 'b'0; -D2 = 'b'0; -D3 = 'b'0; -D4 = 'b'0; -D5 = 'b'0; -D6 = 'b'0; -D7 = 'b'0; - - -D0.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D1.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D2.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D3.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D4.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D5.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D6.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - -D7.OE = - !IOSEL - & !RD - & WR - & !A2 - & !A1 - & !A0; - - -/* ------------------------------------------------------------ - Writable LED register - - Write conditions: - - /IOSEL = 0 - /RD = 1 - /WR = 0 - A2:A0 = 000 - - During such a write: - - LED.D = D0 - - Otherwise: - - LED.D = LED - - Pin 1 (CLK) clocks the registered macrocell. - ------------------------------------------------------------ */ - -LED.D = - ( - !IOSEL - & RD - & !WR - & !A2 - & !A1 - & !A0 - & D0 - ) - - # - - ( - ( - IOSEL - # !RD - # WR - # A2 - # A1 - # A0 - ) - & LED - ); - - -LED.ar = ar; -LED.sp = sp; \ No newline at end of file diff --git a/hardware/acc.jed b/hardware/acc.jed new file mode 100644 index 0000000..97c29af --- /dev/null +++ b/hardware/acc.jed @@ -0,0 +1,49 @@ + +CUPL(WM) 5.0a Serial# MW-10400000 +Device g22v10 Library DLIB-h-40-1 +Created Sat Oct 17 12:42:37 2026 +Name acc +Partno 00 +Revision 01 +Date 2026-10-11 +Designer Joppe Blondel +Company unknown +Assembly unknown +Location unknown +*QP24 +*QF5892 +*G0 +*F0 +*L00416 00000000000000000000000011111111 +*L00448 11111111111111111111111111111111 +*L00480 11111111101110111011011110111111 +*L00512 10111111110111111111111011110111 +*L00544 11111111111111111111111111111111 +*L00576 11101111111110111111111111111111 +*L00608 11111111111111101111111111111111 +*L00640 11110111111111111111111111101111 +*L00672 11111111011111111111111111111111 +*L00704 11110110111111111111111111111111 +*L00736 11111111111111111110011111111111 +*L00768 11111111111111111111111100000000 +*L03648 00001111111111111111111111111111 +*L03680 11111111111111111111011110111011 +*L03712 10111011111101111111111111111111 +*L03744 01111011101101110111111110111111 +*L03776 11111111000000000000000000000000 +*L04288 00000000000000000000000011111111 +*L04320 11111111111111111111111111111111 +*L04352 11111111011110111011011111111111 +*L04384 10111111111111110000000000000000 +*L04864 00000000000000000000111111111111 +*L04896 11111111111111111111111111111111 +*L04928 11111011101110110111011111111011 +*L04960 11111111111100000000000000000000 +*L05344 00000000000000000000000011111011 +*L05376 10111011101110111111011111111111 +*L05408 11111111111111111111111111110111 +*L05440 11111111111111110000000000000000 +*L05792 00000000000000000010000000001111 +*L05824 01110011000000110000000000000000 +*C5C1A +*46C6 \ No newline at end of file diff --git a/hardware/audio_casette_card.abs b/hardware/audio_casette_card.abs new file mode 100644 index 0000000..252c450 Binary files /dev/null and b/hardware/audio_casette_card.abs differ diff --git a/hardware/audio_casette_card.doc b/hardware/audio_casette_card.doc new file mode 100644 index 0000000..d567d1c --- /dev/null +++ b/hardware/audio_casette_card.doc @@ -0,0 +1,297 @@ + +******************************************************************************* + acc +******************************************************************************* + +CUPL(WM) 5.0a Serial# MW-10400000 +Device g22v10 Library DLIB-h-40-1 +Created Sat Oct 17 12:42:37 2026 +Name acc +Partno 00 +Revision 01 +Date 2026-10-11 +Designer Joppe Blondel +Company unknown +Assembly unknown +Location unknown + +=============================================================================== + Expanded Product Terms +=============================================================================== + +AYADDRWRITE => + A0 & A1 & !A2 & !IOSELN & RDN & !WRN + +AYBC1 => + !A0 & A1 & !A2 & !IOSELN & !RDN & WRN + # A0 & A1 & !A2 & !IOSELN & RDN & !WRN + +AYBDIR => + A1 & !A2 & !IOSELN & RDN & !WRN + +AYDATAREAD => + !A0 & A1 & !A2 & !IOSELN & !RDN & WRN + +AYDATAWRITE => + !A0 & A1 & !A2 & !IOSELN & RDN & !WRN + +COUT.d => + !A0 & !A1 & !A2 & D0 & !IOSELN & RDN & !WRN + # COUT & IOSELN + # COUT & !RDN + # COUT & WRN + # A0 & COUT + # A1 & COUT + # A2 & COUT + +COUT.ar => + 0 + +COUT.sp => + 0 + +D0 => + COMP + +D0.oe => + !A0 & !A1 & !A2 & !IOSELN & !RDN & WRN + +SNWEN => + A0 & !A1 & !A2 & !IOSELN & RDN & !WRN + +ar => + 0 + +sp => + 0 + +AYBC1.oe => + 1 + +AYBDIR.oe => + 1 + +COUT.oe => + 1 + +SNWEN.oe => + 1 + + +=============================================================================== + Symbol Table +=============================================================================== + +Pin Variable Pterms Max Min +Pol Name Ext Pin Type Used Pterms Level +--- -------- --- --- ---- ------ ------ ----- + + A0 6 V - - - + A1 2 V - - - + A2 3 V - - - + AYADDRWRITE 0 I 1 - - + AYBC1 17 V 2 14 1 + AYBDIR 16 V 1 12 1 + AYDATAREAD 0 I 1 - - + AYDATAWRITE 0 I 1 - - + CLK 1 V - - - + COMP 7 V - - - + COUT 22 V - - - + COUT d 22 X 7 10 1 + COUT ar 22 X 1 1 1 + COUT sp 22 X 1 1 1 + D0 14 V 1 8 1 + D0 oe 14 X 1 1 1 + IOSELN 4 V - - - + RDN 5 V - - - + SNWEN 15 V 1 10 1 + WRN 8 V - - - + ar 0 I 1 - - + sp 0 I 1 - - + AYBC1 oe 17 D 1 1 0 + AYBDIR oe 16 D 1 1 0 + COUT oe 22 D 1 1 0 + SNWEN oe 15 D 1 1 0 + + +LEGEND D : default variable F : field G : group + I : intermediate variable N : node M : extended node + U : undefined V : variable X : extended variable + T : function + + +=============================================================================== + Fuse Plot +=============================================================================== + +SP + 05764 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +AR + 00000 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +Pin #23 05808 Mode xx + 00044 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00088 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00132 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00176 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00220 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00264 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00308 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00352 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00396 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #22 05810 Mode -x + 00440 -------------------------------------------- + 00484 -----x---x---x--x----x-------x--------x----- + 00528 -------x----x------------------------------- + 00572 -------x---------x-------------------------- + 00616 -------x--------------------x--------------- + 00660 -------x------------x----------------------- + 00704 ----x--x------------------------------------ + 00748 -------xx----------------------------------- + 00792 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00836 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00880 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #21 05812 Mode xx + 00924 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 00968 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01012 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01056 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01100 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01144 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01188 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01232 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01276 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01320 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01364 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01408 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01452 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #20 05814 Mode xx + 01496 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01540 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01584 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01628 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01672 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01716 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01760 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01804 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01848 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01892 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01936 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 01980 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02024 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02068 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02112 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #19 05816 Mode xx + 02156 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02200 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02244 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02288 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02332 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02376 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02420 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02464 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02508 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02552 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02596 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02640 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02684 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02728 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02772 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02816 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02860 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #18 05818 Mode xx + 02904 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02948 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 02992 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03036 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03080 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03124 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03168 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03212 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03256 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03300 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03344 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03388 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03432 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03476 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03520 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03564 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03608 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #17 05820 Mode -- + 03652 -------------------------------------------- + 03696 ----x----x---x---x---x------x--------------- + 03740 ----x----x---x--x---x--------x-------------- + 03784 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03828 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03872 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03916 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 03960 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04004 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04048 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04092 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04136 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04180 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04224 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04268 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #16 05822 Mode -- + 04312 -------------------------------------------- + 04356 ----x----x---x--x------------x-------------- + 04400 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04444 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04488 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04532 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04576 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04620 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04664 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04708 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04752 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04796 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 04840 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #15 05824 Mode x- + 04884 -------------------------------------------- + 04928 -----x---x---x--x---x--------x-------------- + 04972 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05016 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05060 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05104 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05148 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05192 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05236 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05280 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05324 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Pin #14 05826 Mode -- + 05368 -----x---x---x---x---x------x--------------- + 05412 ------------------------x------------------- + 05456 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05500 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05544 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05588 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05632 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05676 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 05720 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + +LEGEND X : fuse not blown + - : fuse blown + +=============================================================================== + Chip Diagram +=============================================================================== + + ______________ + | acc | + CLK x---|1 24|---x Vcc + A1 x---|2 23|---x + A2 x---|3 22|---x COUT + IOSELN x---|4 21|---x + RDN x---|5 20|---x + A0 x---|6 19|---x + COMP x---|7 18|---x + WRN x---|8 17|---x AYBC1 + x---|9 16|---x AYBDIR + x---|10 15|---x SNWEN + x---|11 14|---x D0 + GND x---|12 13|---x + |______________| + diff --git a/hardware/audio_casette_card.lst b/hardware/audio_casette_card.lst new file mode 100644 index 0000000..065a793 --- /dev/null +++ b/hardware/audio_casette_card.lst @@ -0,0 +1,74 @@ +LISTING FOR LOGIC DESCRIPTION FILE: audio_casette_card.pld Page 1 + +CUPL(WM): Universal Compiler for Programmable Logic +Version 5.0a Serial# MW-10400000 +Copyright (c) 1983, 1998 Logical Devices, Inc. +Created Sat Oct 17 12:42:37 2026 + + 1:Name acc; + 2:PartNo 00; + 3:Date 2026-10-11; + 4:Revision 01; + 5:Designer Joppe Blondel; + 6:Company unknown; + 7:Assembly unknown; + 8:Location unknown; + 9:Device g22v10; + 10: + 11:/* Constants */ + 12:ar = 'b'0; + 13:sp = 'b'0; + 14: + 15:/* Pin assignments */ + 16:/* inputs */ + 17:PIN 1 = CLK; + 18:PIN 2 = A1; + 19:PIN 3 = A2; + 20:PIN 4 = IOSELN; + 21:PIN 5 = RDN; + 22:PIN 6 = A0; + 23:PIN 7 = COMP; + 24:PIN 8 = WRN; + 25:/* outputs */ + 26:PIN 14 = D0; + 27:PIN 15 = SNWEN; + 28:PIN 16 = AYBDIR; + 29:PIN 17 = AYBC1; + 30:PIN 22 = COUT; + 31: + 32:/* Read reg0 */ + 33:/* places comparator output on D0 when reading addr0 */ + 34:D0 = COMP; + 35:D0.OE = !IOSELN & !RDN & WRN & !A0 & !A1 & !A2; + 36: + 37:/* Write reg0 */ + 38:/* writes D0 to casette output port */ + 39:/* during a write COUT.D=D0 otherwise COUT.D = COUT */ + 40:COUT.D = (!IOSELN & RDN & !WRN & !A0 & !A1 & !A2 & D0) # + 41: ((IOSELN # !RDN # WRN # A0 # A1 # A2) & COUT); + 42:COUT.ar = ar; + 43:COUT.sp = sp; + 44: + 45:/* Write to SN76489 at addr 1 */ + 46:SNWEN = !(!IOSELN & RDN & !WRN & A0 & !A1 & !A2); + 47: + 48:/* AY-3-8910 access at addr 2 (data) addr 3 (adress) */ + 49:AYDATAREAD = !IOSELN & !RDN & WRN & !A0 & A1 & !A2; + 50:AYDATAWRITE = !IOSELN & RDN & !WRN & !A0 & A1 & !A2; + 51:AYADDRWRITE = !IOSELN & RDN & !WRN & A0 & A1 & !A2; + 52:AYBDIR = AYDATAWRITE # AYADDRWRITE; + 53:AYBC1 = AYDATAREAD # AYADDRWRITE; + +LISTING FOR LOGIC DESCRIPTION FILE: audio_casette_card.pld Page 2 + +CUPL(WM): Universal Compiler for Programmable Logic +Version 5.0a Serial# MW-10400000 +Copyright (c) 1983, 1998 Logical Devices, Inc. +Created Sat Oct 17 12:42:37 2026 + + 54: + + + +Jedec Fuse Checksum (5c1a) +Jedec Transmit Checksum (46c6) diff --git a/hardware/audio_casette_card.pld b/hardware/audio_casette_card.pld new file mode 100644 index 0000000..9d05e00 --- /dev/null +++ b/hardware/audio_casette_card.pld @@ -0,0 +1,53 @@ +Name acc; +PartNo 00; +Date 2026-10-11; +Revision 01; +Designer Joppe Blondel; +Company unknown; +Assembly unknown; +Location unknown; +Device g22v10; + +/* Constants */ +ar = 'b'0; +sp = 'b'0; + +/* Pin assignments */ +/* inputs */ +PIN 1 = CLK; +PIN 2 = A1; +PIN 3 = A2; +PIN 4 = IOSELN; +PIN 5 = RDN; +PIN 6 = A0; +PIN 7 = COMP; +PIN 8 = WRN; +/* outputs */ +PIN 14 = D0; +PIN 15 = SNWEN; +PIN 16 = AYBDIR; +PIN 17 = AYBC1; +PIN 22 = COUT; + +/* Read reg0 */ +/* places comparator output on D0 when reading addr0 */ +D0 = COMP; +D0.OE = !IOSELN & !RDN & WRN & !A0 & !A1 & !A2; + +/* Write reg0 */ +/* writes D0 to casette output port */ +/* during a write COUT.D=D0 otherwise COUT.D = COUT */ +COUT.D = (!IOSELN & RDN & !WRN & !A0 & !A1 & !A2 & D0) # + ((IOSELN # !RDN # WRN # A0 # A1 # A2) & COUT); +COUT.ar = ar; +COUT.sp = sp; + +/* Write to SN76489 at addr 1 */ +SNWEN = !(!IOSELN & RDN & !WRN & A0 & !A1 & !A2); + +/* AY-3-8910 access at addr 2 (data) addr 3 (adress) */ +AYDATAREAD = !IOSELN & !RDN & WRN & !A0 & A1 & !A2; +AYDATAWRITE = !IOSELN & RDN & !WRN & !A0 & A1 & !A2; +AYADDRWRITE = !IOSELN & RDN & !WRN & A0 & A1 & !A2; +AYBDIR = AYDATAWRITE # AYADDRWRITE; +AYBC1 = AYDATAREAD # AYADDRWRITE; \ No newline at end of file diff --git a/hardware/audio_casette_card.sim b/hardware/audio_casette_card.sim new file mode 100644 index 0000000..e212fce --- /dev/null +++ b/hardware/audio_casette_card.sim @@ -0,0 +1,79 @@ +%SIGNAL +PIN 6 = A0 +PIN 2 = A1 +PIN 3 = A2 +PIN 17 = AYBC1 +PIN 16 = AYBDIR +PIN 1 = CLK +PIN 7 = COMP +PIN 22 = COUT +PIN 14 = D0 +PIN 4 = IOSELN +PIN 5 = RDN +PIN 15 = SNWEN +PIN 8 = WRN +%END + +%FIELD +%END + +%EQUATION +AYADDRWRITE => + A0 & A1 & !A2 & !IOSELN & RDN & !WRN + +AYBC1 => + !A0 & A1 & !A2 & !IOSELN & !RDN & WRN + # A0 & A1 & !A2 & !IOSELN & RDN & !WRN + +AYBDIR => + A1 & !A2 & !IOSELN & RDN & !WRN + +AYDATAREAD => + !A0 & A1 & !A2 & !IOSELN & !RDN & WRN + +AYDATAWRITE => + !A0 & A1 & !A2 & !IOSELN & RDN & !WRN + +COUT.d => + !A0 & !A1 & !A2 & D0 & !IOSELN & RDN & !WRN + # !COUT & IOSELN + # !COUT & !RDN + # !COUT & WRN + # A0 & !COUT + # A1 & !COUT + # A2 & !COUT + +COUT.ar => + 0 + +COUT.sp => + 0 + +D0 => + COMP + +D0.oe => + !A0 & !A1 & !A2 & !IOSELN & !RDN & WRN + +SNWEN => + A0 & !A1 & !A2 & !IOSELN & RDN & !WRN + +ar => + 0 + +sp => + 0 + +AYBC1.oe => + 1 + +AYBDIR.oe => + 1 + +COUT.oe => + 1 + +SNWEN.oe => + 1 + +%END diff --git a/hardware/casette.jed b/hardware/casette.jed deleted file mode 100644 index 4ae757a..0000000 --- a/hardware/casette.jed +++ /dev/null @@ -1,54 +0,0 @@ - -CUPL(WM) 5.0a Serial# MW-10400000 -Device g22v10 Library DLIB-h-40-1 -Created Sat Oct 03 16:22:02 2026 -Name casette -Partno 00 -Revision 01 -Date 2026-10-03 -Designer joppe -Company unknown -Assembly None -Location unknown -*QP24 -*QF5892 -*G1 -*F0 -*L00416 00000000000000000000000011111111 -*L00448 11111111111111111111111111111111 -*L00480 11111111101110111011011110111111 -*L00512 10111111110111111111111011110111 -*L00544 11111111111111111111111111111111 -*L00576 11101111111110111111111111111111 -*L00608 11111111111111101111111111111111 -*L00640 11110111111111111111111111100111 -*L00672 11111111111111111111111111111111 -*L00704 11110110111111111111111111111111 -*L00736 11111111111111111110111111111111 -*L00768 01111111111111111111111100000000 -*L00896 00000000000000000000000000001111 -*L00928 10111011101110111011111101111111 -*L00960 11111111000000000000000000000000 -*L01472 00000000000000000000000011111011 -*L01504 10111011101110111111011111111111 -*L01536 11110000000000000000000000000000 -*L02144 00000000000011111011101110111011 -*L02176 10111111011111111111111100000000 -*L02880 00000000000000000000000011111011 -*L02912 10111011101110111111011111111111 -*L02944 11110000000000000000000000000000 -*L03648 00001111101110111011101110111111 -*L03680 01111111111111110000000000000000 -*L04288 00000000000000000000000011111011 -*L04320 10111011101110111111011111111111 -*L04352 11110000000000000000000000000000 -*L04864 00000000000000000000111110111011 -*L04896 10111011101111110111111111111111 -*L05344 00000000000000000000000011111011 -*L05376 10111011101110111111011111111111 -*L05408 11111111111111111111111111110111 -*L05440 11111111111111110000000000000000 -*L05792 00000000000000000010111111111111 -*L05824 11110011000000110000000000000000 -*C5BE4 -*6A87 \ No newline at end of file diff --git a/hardware/compile.sh b/hardware/compile.sh new file mode 100755 index 0000000..625bea5 --- /dev/null +++ b/hardware/compile.sh @@ -0,0 +1,2 @@ +#!/bin/bash +/opt/packages/wincupl/cupl -jaxfl g22v10 audio_casette_card.pld \ No newline at end of file diff --git a/memory.mk b/memory.mk index 9a4dadd..50595cc 100644 --- a/memory.mk +++ b/memory.mk @@ -16,6 +16,9 @@ BIOS_ROM_SIZE := 0x0780 BASIC_ROM_START := 0x0800 BASIC_ROM_SIZE := 0x2200 +SN76489_ROM_START := 0x2a00 +SN76489_ROM_SIZE := 0x1000 + BIOS_RAM_START := 0xc000 BIOS_WP_RAM_START := 0xc800 BASIC_RAM_START := 0xce00 diff --git a/scripts/encode.py b/scripts/encode.py index 2bb4140..50230b7 100644 --- a/scripts/encode.py +++ b/scripts/encode.py @@ -106,11 +106,10 @@ class PulseWriter: self.bytes(struct.pack(" None: +def header(pulses: PulseWriter, path:Path, file_size: int) -> None: fname = path.name - fsize = os.path.getsize(path) data = ( - struct.pack(" None: ) parser.add_argument("input", type=Path, help="input file to encode") parser.add_argument("output", type=Path, help="output WAV file") + parser.add_argument( + "--max-size", + type=int, + metavar="BYTES", + help="encode at most this many bytes from the input file", + ) parser.add_argument("--sample-rate", type=int, default=48_000) parser.add_argument( "--t-short", @@ -152,6 +157,13 @@ def main() -> None: parser.error("--t-short must be less than --t-long") if not 0.0 <= args.amplitude <= 1.0: parser.error("--amplitude must be between 0 and 1") + if args.max_size is not None and args.max_size < 0: + parser.error("--max-size must not be negative") + + input_size = os.path.getsize(args.input) + encoded_size = ( + input_size if args.max_size is None else min(input_size, args.max_size) + ) try: pulses = PulseWriter( @@ -165,14 +177,14 @@ def main() -> None: # create header block pulses.space(5.0) - header(pulses, args.input) + header(pulses, args.input, encoded_size) pulses.space(1.0) with open(args.input, "rb") as f: - while True: - data = f.read(BLOCK_SIZE) - if not len(data): - break + remaining = encoded_size + while remaining: + data = f.read(min(BLOCK_SIZE, remaining)) raw(pulses, data) + remaining -= len(data) pulses.space(1.0) with wave.open(str(args.output), "wb") as wav: diff --git a/tapes/silver_surfer.wav b/tapes/silver_surfer.wav new file mode 100644 index 0000000..c407dd5 Binary files /dev/null and b/tapes/silver_surfer.wav differ diff --git a/tapes/sn76489.wav b/tapes/sn76489.wav new file mode 100644 index 0000000..9767b23 Binary files /dev/null and b/tapes/sn76489.wav differ