Some time ago, Clay Cowgill programmed (or started) a vectrex game called moon lander. Within moon lander he made use of digitized sound routines.
Since Clay lost interest in Vectrex, I offered to take over the programming, he agreed and I was able to finish and “publish” moon lander.
The original digitized sound routines from Clay made it neccessary to have all vector drawing switched off and only a blank screen was “seen” while playing samples.
Here is a collection of routines and instructions to remedy that restriction – and a demo.
(working package of all neccessary files: Digitized.zip)
The gist of the package lies in the macro definition in a file called DIGI_MAK.I:
; ; I used the 6809 assembler: ; as09 [1.11]. ; Copyright 1990-1994, Frank A. Vorstenbosch, Kingswood Software. ; Available at: ; http://www.falstaff.demon.co.uk/cross.html ; INCLUDE "VECTREX.I" ; vectrex function includes ; PUBLIC macros: ; INTENSITY_A_DIGIT -> set intensity to A uses ## cycles ; NEXT_DIGIT_BYTE ; MOVE_TO_D_DIGIT ; WAIT_RECAL_DIGIT -> uses 0 cycles ; INIT_SOUND_DIGIT -> ; expects startposition in D, expects length in X ; plays digitized sound, while displaying vectors ; first try was using interrupts, but handling them used to much ; time. ; therefor I switched to using timer 2 (without interrupt handling) only, ; that means in order to recalibrate I somehow have to keep track of ; system time ; the sound digitized routine pretty much allways uses ## cycles ; and I nearly have to call it every ### cycles ; which means I have to built ; uniform vector drawing routines, which also ; means some sort of fixed (or at least KNOWN) scalefactors to use ; (for positioning AND drawining AND recalibration!) ; ; I must use vector functions which allways use exactly the ; same cycles, so that I can calculate for the recalibration-needed 30000 ; cycles... ; actually it is enough to insure that those function do not take longer ; than the 'pause' between our sample-byte-outputs ; samples must be: ; 8 bit mono signed ; different sample-frequency can be used, provided ; the constant "T2_TIMER_PEROID_REAL" is set to a appropriate value ; for now this routine playes all samples 'backwards' ; -> so you also have to turn your samples arround :-) ; This is the single most important variable... ; for different sample frequency, THIS must be adjusted... ; 140 for 8KHz samples ; 330 for 4KHz samples ; 700 for 2KHz samples ; 1400 for 1KHz samples ; ... T2_TIMER_PEROID_REAL EQU 700 ; 2K samples about that many cycles between update of samples T2_TIMER_PEROID_LO EQU lo(T2_TIMER_PEROID_REAL-(256*(T2_TIMER_PEROID_REAL/256))) T2_TIMER_PEROID_HI EQU lo(T2_TIMER_PEROID_REAL/256) T2_TIMER_PEROID EQU T2_TIMER_PEROID_HI+256*T2_TIMER_PEROID_LO ; it is enough if we don't miss a sample ; that means in between our about 700 cycle we can ; do whatever we want! ; we must use a fixed scale value, since somehow we must ; calculate the wait_recal ; (actually we MUST asure, that we stay not for more time in the ; move_to_d or draw_vlc functions, this is sort of a delimiter) ; it should be ok, to use smaller values, ; this (50) value was ment for use with 8kHz samples, ; for 4kHz samples it could probably be doubled... ; (without changing anything else) SCALE_FACTOR_DIGIT EQU 50 ; 30000 cycles per wait_recal ; divided by T2_TIMER_PEROID_REAL + length of one digital sample play + offset for use of jsr's... ; about 35 for 2 kHz ; about 70 for 4 kHz ; about 135 for 8 kHz ; this means only this many times our samples can be called ; this also means the way it is implemented now... ; only about that many vectors can be drawn ; ; in order to be able to draw more vectors, the DRAW_VLC ; function must be changed, so that more than just one vector is ; drawn between two samples (can easily be done) ; must be corrected by the additions 13.03.2000 RECAL_COUNTER_RESET EQU (30000/(T2_TIMER_PEROID_REAL + 71 + 50) ) ; *************************************************************************** ; this sets the timer to our restart value RESTART_TIMER macro ; name of macro LDD #T2_TIMER_PEROID ; load the timer 2 value we calculated STD VIA_t2_lo ; and set the timer endm ; end of macro ; *************************************************************************** ; this sets VIA B to our known sample state... SET_VIAB_WITH_VARIABLE macro ; name of macro LDA via_b_start ; load the calculated VIA B STA VIA_port_b ; write back to reg B in 6522 endm ; end of macro ; *************************************************************************** ; this calculates our sample state for VIA B SET_VIAB_INTERN macro ; name of macro LDA VIA_port_b ; data reg B from 6522 ANDA #$f8 ; save top 5 bits, mask off bottom 3 ORA #$06 ; set S/H, SEL 0, SEL 1 STA via_b_start ; and remember it endm ; end of macro ; *************************************************************************** ; this is a waiter, for our current sample-byte to finnish WAIT_FOR_NEXT_DIGIT macro ; name of macro wait_for_next_digit\?: LDB #$0020 ; B-reg = T2 interrupt bit BITB VIA_int_flags ; Wait for T2 to time out BEQ wait_for_next_digit\?; repeat endm ; end of macro ; *************************************************************************** ; well, not really a 'digit' function... but it does what it's called INTENSITY_A_DIGIT macro STA Vec_Brightness ; Save intensity in $C827 STA VIA_port_a ; Store intensity in D/A LDD #$0504 ; mux disabled channel 2 STA VIA_port_b ; STB VIA_port_b ; mux enabled channel 2 STB VIA_port_b ; do it again just because ? LDB #$01 ; STB VIA_port_b ; turn off mux endm ; *************************************************************************** ; Kills D ; must ALLWAYS have Y the position ; must ALLWAYS have Timer 2 ; ; Kills and VIA port B and A ; cycles left = 130 (with clays digitized sound = 8Khz) ; cycles left = 300 (4Khz) ; ; 13.03.2000 counting not correct anymore !!! ; uses 27+30 cycles when completely done, without restart ; uses 51+30 cycles when completely done, with restart ; uses 32+30 cycles when one digitized sound byte was played. ; + 9 ; ; => Interrupts are not worth it... ; NEXT_DIGIT_BYTE macro ; name of macro ; load current digit byte and increment counter DEC digit_recal_counter; decrement our counter, used for wait_recal TST digit_is_playing ; is there a digital sample to be played? BEQ timer_restart_only\?; no, than jump out of here WAIT_FOR_NEXT_DIGIT ; otherwise we wait till the last played ; sample-byte is finnished LDA ,-Y ; load the next sample_byte to A CMPY digit_start_pos BNE sound_not_done\? ; with this sample, otherwise we continue further below ; if we are done, should we restart? sound_done\?: LDB digit_looping ; is this sample a looping one? STB digit_is_playing ; store it to is_playing BEQ timer_restart_only\?; if none looping... we are done ; but we still must use the timer ; ok, for restart, we only change current position LDY digit_end_pos ; load the start position ; this is the end_position of the sample, ; since we go backwards BRA timer_restart_only\?; and restart the timer, next byte ; is played next round... ; here our normal 'digit_byte_playing_section' sound_not_done\?: ; load the next sample_byte to A ; and store it to the 6522 -> PSG STA VIA_port_a ; store in reg A in 6522 (DAC) ; following must come after the above, or we ; put noise to the psg, ; likewise, before storing anything else to ; port A, we will disable the connection to PSG SET_VIAB_WITH_VARIABLE ; this sets the MUX of 6522 to PSG sound_restart_timer\?: CLR VIA_shift_reg ; Clear shift regigster, why ??? ; without it, the display 'wobbles' a bit??? INC VIA_port_b ; and disable the mux, so no junk will ; enter our PSG-DAC... timer_restart_only\?: RESTART_TIMER ; restart timer... makro_rts\?: endm ; end of macro ; *************************************************************************** ; uses for a scalefactor of 50 ; about 100+... cycles (could still be optimized further) ; MOVE_TO_D_DIGIT macro PSHS D ; save the position NEXT_DIGIT_BYTE ; play one sample_byte PULS D ; restore position STA VIA_port_a ; Store Y in D/A register LDA #$CE ; Blank low, zero high? STA VIA_cntl ; CLRA STA VIA_port_b ; Enable mux STA VIA_shift_reg ; Clear shift regigster INC VIA_port_b ; Disable mux STB VIA_port_a ; Store X in D/A register STA VIA_t1_cnt_hi ; enable timer LDB #$40 ; t1 flag wait_for_t1\?: BITB VIA_int_flags ; BEQ wait_for_t1\? endm ;*************************************************************************** ; exactly 60 with 1 loop (scale minimum 10) ; scale 50 uses 60 + 45 -> 105 cycles ; ; loop 1: 4 + 5 = 9 (branched) ; loop END 1: 4 + 3 = 7 (not branched) ; ; loop 1: 3 + (4 + 5) * 0 + 4 + 3 = 10 (scale) ; loop 2: 3 + (4 + 5) * 1 + 4 + 3 = 19 ; loop 3: 3 + (4 + 5) * 2 + 4 + 3 = 28 ; loop 4: 3 + (4 + 5) * 3 + 4 + 3 = 37 ; loop 5: 3 + (4 + 5) * 4 + 4 + 3 = 46 ; loop 6: 3 + (4 + 5) * 5 + 4 + 3 = 55 ; ; minimum 1 loop = 3 + 4 + 3 = 10 -> minimum scale possible = 10 ; -> per additional loop + 7 DRAW_LINE_D_DIGIT macro STA VIA_port_a ; Send Y to A/D CLR VIA_port_b ; Enable mux switched LEAX 2,X ; Point to next coordinate pair X=X+2 NOP ; Wait a moment INC VIA_port_b ; Disable mux STB VIA_port_a ; Send X to A/D LDD #$FF00 ; Shift reg=$FF (solid line), T1H=0 STA VIA_shift_reg ; Put pattern in shift register STB VIA_t1_cnt_hi ; Set T1H (scale factor), enabling t1 LDD #$0040 ; B-reg = T1 interrupt bit wait_for_t1\?: BITB VIA_int_flags ; BEQ wait_for_t1\? NOP STA VIA_shift_reg ; Clear shift register (blank output) endm ;*************************************************************************** ; ; uses 8 cycles ; (in relation to the last done digital output) ; only one vector drawn for now... ; could probably be doubled (2*51 < 130) ; DRAW_VLC_DIGIT macro NEXT_DIGIT_BYTE ; play one sample-byte LDA ,X+ ; load # of lines in this list DRAW_VLA_DIGIT\?: STA $C823 ; helper RAM, here we store the # of lines LDD ,X ; load y, x DRAW_LINE_D_DIGIT ; draw the line NEXT_DIGIT_BYTE ; and play one sample-byte LDA $C823 ; load line count DECA ; decrement it BPL DRAW_VLA_DIGIT\? ; go back for more points if not below 0 endm ;*************************************************************************** ; uses 0 cycles ; (in relation to the last done digital output) ; a wait_recal routine for the sample... output WAIT_RECAL_DIGIT macro wait_for_next_digit\?: NEXT_DIGIT_BYTE ; play one sample-byte LDA digit_recal_counter; load # of time_outs CMPA #RECAL_COUNTER_RESET; # should we recalibrate now? BLO wait_for_next_digit\?; if not yet... loop till the time is right LDA #SCALE_FACTOR_DIGIT; set the fixed scale factor we will use... STA VIA_t1_cnt_lo ; move to time 1 lo, this means scaling ; now we move out of bounds ; five times the move should about be 255 (ff) scalefactor :-? LDA #5 ; loop 5 times STA $C823 ; store that recal_loop1\?: LDD #$7F7F ; load the next pos, super long saturation JSR move_to_d_digitj ; move to d -> must be achieved DEC $C823 ; done yet with out 5? BNE recal_loop1\? ; not yet? than loop LDB #$CC STB VIA_cntl ; blank low and zero low ; five times the move should about be 255 (ff) scalefactor :-? LDA #5 ; loop 5 times STA $C823 ; store that recal_loop2\?: LDD #$8080 ; load the next pos, super long saturation JSR move_to_d_digitj ; move to d -> must be achieved DEC $C823 ; done yet with out 5? BNE recal_loop2\? ; not yet? than loop LDB #$CC STB VIA_cntl ; /BLANK low and /ZERO low LDD #$0302 STA VIA_port_b ; mux=1, disable mux CLR VIA_port_a ; clear D/A register STB VIA_port_b ; mux=1, enable mux STB VIA_port_b ; do it again LDB #$01 STB VIA_port_b ; disable mux LDA #RECAL_COUNTER_RESET; load our calculated reset value STA digit_recal_counter; and store it to our timer counter... SET_VIAB_INTERN ; rethink our VIAB value NEXT_DIGIT_BYTE ; and do one sample-byte endm ;*************************************************************************** ; expects startposition in D ; expects length in X ; ; sets up Y register, this should under no circumstances be destroyed INIT_SOUND_DIGIT macro STD digit_start_pos ; store new start position STX digit_length ; store the length STX digit_counter ; same in counter... TFR X,D ; move X to D ADDD digit_start_pos ; calculate end position STD digit_end_pos ; and store it LDA #0 ; looping per default is OFF STA digit_looping ; store it LDA #1 ; sound is playing is ON STA digit_is_playing ; sound is playing SET_VIAB_INTERN ; calculate out first VIA B poke LDY digit_end_pos ; initialize Y to position in sample data ; LDA #SCALE_FACTOR_DIGIT; set the fixed scale factor we will use... ; STA VIA_t1_cnt_lo ; move to time 1 lo, this means scaling RESTART_TIMER ; set our timer 2 for the first time... endm