;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;"2nd_music_driver_2025-06-20\main.p8": %zeropage basicsafe %import music song_data { %option ignore_unused const ubyte PULSE = %00000000 const ubyte SAWTOOTH = %01000000 const ubyte TRIANGLE = %10000000 const ubyte NOISE = %11000000 const ubyte SILENT = %00000000 const ubyte LEFT = %01000000 const ubyte RIGHT = %10000000 const ubyte STEREO = %11000000 ; 6 bytes per instrument const ubyte INSTRUMENTS_LEN = sizeof(instruments)/6 ; +1 for tick speed const ubyte ORDERS_LEN = sizeof(orders)/(CHANNELS+1) ; 2 bytes per pattern pointer const ubyte PATTERNS_LEN = sizeof(patterns)/2 ; This is what you pass to music.play() ; Note: mkword's first arg is the high byte, not the low byte! uword[] @nosplit info = [mkword(INSTRUMENTS_LEN, CHANNELS), mkword(PATTERNS_LEN, ORDERS_LEN), mkword(0, 0), ; (Reserved) &instruments, &orders, &patterns] const uword INS_NULL = 0 const uword INS_SAWLONG = 1 const uword INS_PLSSHRT = 2 const uword INS_PLSLONG = 3 const uword INS_NOISHRT_L = 4 const uword INS_NOISHRT_R = 5 const uword INS_NOILONG_L = 6 const uword INS_NOILONG_R = 7 const uword INS_NOISHRT = 8 const uword INS_NOILONG = 9 const uword INS_CHIRP = 10 ; WAVEFORM|WIDTH, EAR_CHANLS|VOL, MAXVOL_6b, ATTACK_8b, SUSTAIN_8b, RELEASE_8b ; ; From psg.envelope()'s remarks: ; maxvolume = 0-63 ; attack, sustain, release = 0-255 that determine the speed of the A/D/R: ; attack time: MAXVOL/15/attack sec. higher value = faster attack. ; sustain time: sustain/60 sec. higher value = longer sustain. ; release time: MAXVOL/15/release sec. higher vaule = faster release. ; ; (A max volume of 0 is interpreted as 'no note', which can be used for ; envelopes longer than the row it resides in, as that current envelope ; will continue until the next row with an instrument without a maxvol of 0.) ubyte[] instruments = [ 0, 0, 0, 255, 0, 255, ; 0 NULL SAWTOOTH, STEREO|0, 30, 208, 0, 5, ; 1 SAWLONG PULSE|48, STEREO|0, 33, 170, 5, 44, ; 2 PLSSHRT PULSE|38, STEREO|0, 33, 170, 13, 44, ; 3 PLSLONG NOISE , LEFT|0, 19, 58, 0, 16, ; 4 NOISHRT_L NOISE , RIGHT|0, 19, 58, 0, 16, ; 5 NOISHRT_R NOISE , LEFT|0, 19, 58, 0, 8, ; 6 NOILONG_L NOISE , RIGHT|0, 19, 58, 0, 8, ; 7 NOILONG_R NOISE , STEREO|0, 14, 58, 0, 16, ; 8 NOISHRT NOISE , STEREO|0, 14, 58, 0, 8, ; 9 NOILONG PULSE| 9, STEREO|0, 21, 208, 0, 6] ; 10 CHIRP ; Accepts values 1-6 (ORDERS_LEN = sizeof(orders)/CHANNELS) const ubyte CHANNELS = 5 ; Contains indices into the patterns array, where the actual ; index equals i-1, and a value of 0 means no pattern at all ; ; The first byte of an order entry however, is the tick speed (frames per row) ubyte[] orders = [ 8, 1, 2, 3, 4, 5, ] ; This is an array of pointers ; ; In each pattern, the first byte correlates to its length mask. ; For example, a pattern with a length of 64 would have a mask of 63 ; (Pattern length must be a power of 2!) ; ; If the 2nd byte is nonzero, that pattern's array lacks the @nosplit tag. ; ; The words after are the pattern's rows: ; Bits 0-6 are: The note index; piano key starting from C0 (0-119) ; (A note index of 0 indicates a silent note) ; Bits 7-F are: The instrument index (0-511) uword[] @nosplit patterns = [pattern_hatLR, pattern_hatST, pattern_saw, pattern_pulse, pattern_chirp] ; 1 uword[] @nosplit pattern_hatLR = [mkword(0,7), ; Mask of 7, for 8 notes total INS_NOISHRT_L<<7 | piano.KEY_C9, INS_NOISHRT_R<<7 | piano.KEY_C9, INS_NOILONG_L<<7 | piano.KEY_D9S, INS_NULL <<7 | 0, INS_NOISHRT_R<<7 | piano.KEY_C9, INS_NOISHRT_L<<7 | piano.KEY_C9, INS_NOILONG_R<<7 | piano.KEY_D9S, INS_NULL <<7 | 0] ; 2 uword[] @nosplit pattern_hatST = [mkword(0,7), ; Mask of 7, for 8 notes total INS_NOISHRT<<7 | piano.KEY_C9, INS_NOISHRT<<7 | piano.KEY_C9, INS_NOILONG<<7 | piano.KEY_D9S, INS_NULL <<7 | 0, INS_NOISHRT<<7 | piano.KEY_C9, INS_NOISHRT<<7 | piano.KEY_C9, INS_NOILONG<<7 | piano.KEY_D9S, INS_NULL <<7 | 0] ; 3 uword[] @nosplit pattern_saw = [mkword(0,63), ; 64 notes total INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2S, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2S, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2S, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_C3, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_A2S, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_F2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_F2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_F2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_D2S, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_SAWLONG<<7 | piano.KEY_G2S, INS_NULL <<7 | 0] uword[] @nosplit pattern_pulse = [mkword(0,63), ; 64 notes total ; 0 -> 7: INS_PLSSHRT<<7 | piano.KEY_F3, INS_PLSSHRT<<7 | piano.KEY_G3S, INS_PLSSHRT<<7 | piano.KEY_C4, INS_PLSLONG<<7 | piano.KEY_G4, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_C4, INS_PLSLONG<<7 | piano.KEY_D4S, INS_NULL <<7 | 0, ; 8 -> 15: INS_PLSSHRT<<7 | piano.KEY_F3, INS_PLSSHRT<<7 | piano.KEY_G3S, INS_PLSSHRT<<7 | piano.KEY_C4, INS_PLSLONG<<7 | piano.KEY_G4, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_C4, INS_PLSLONG<<7 | piano.KEY_D4S, INS_NULL <<7 | 0, ; 16 -> 23: INS_PLSSHRT<<7 | piano.KEY_D3S, INS_PLSSHRT<<7 | piano.KEY_G3, INS_PLSSHRT<<7 | piano.KEY_A3S, INS_PLSLONG<<7 | piano.KEY_G4, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_C4, INS_PLSLONG<<7 | piano.KEY_D4S, INS_NULL <<7 | 0, ; 24 -> 31: INS_PLSSHRT<<7 | piano.KEY_D3S, INS_PLSSHRT<<7 | piano.KEY_G3, INS_PLSSHRT<<7 | piano.KEY_A3S, INS_PLSLONG<<7 | piano.KEY_G4, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_C4, INS_PLSSHRT<<7 | piano.KEY_D4S, INS_PLSSHRT<<7 | piano.KEY_C4S, ; 32 -> 39: INS_PLSSHRT<<7 | piano.KEY_C3S, INS_PLSSHRT<<7 | piano.KEY_F3, INS_PLSSHRT<<7 | piano.KEY_G3S, INS_PLSLONG<<7 | piano.KEY_D4S, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_G3S, INS_PLSLONG<<7 | piano.KEY_C4, INS_NULL <<7 | 0, ; 40 -> 47: INS_PLSSHRT<<7 | piano.KEY_C3S, INS_PLSSHRT<<7 | piano.KEY_F3, INS_PLSSHRT<<7 | piano.KEY_G3S, INS_PLSLONG<<7 | piano.KEY_D4S, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_G3S, INS_PLSLONG<<7 | piano.KEY_C4, INS_NULL <<7 | 0, ; 48 -> 55: INS_PLSSHRT<<7 | piano.KEY_D3S, INS_PLSSHRT<<7 | piano.KEY_G3, INS_PLSSHRT<<7 | piano.KEY_A3S, INS_PLSLONG<<7 | piano.KEY_F4, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_A3S, INS_PLSLONG<<7 | piano.KEY_C4S, INS_NULL <<7 | 0, ; 56 -> 63: INS_PLSSHRT<<7 | piano.KEY_D3S, INS_PLSSHRT<<7 | piano.KEY_G3, INS_PLSSHRT<<7 | piano.KEY_A3S, INS_PLSLONG<<7 | piano.KEY_F4, INS_NULL <<7 | 0, INS_PLSSHRT<<7 | piano.KEY_A3S, INS_PLSSHRT<<7 | piano.KEY_C4S, INS_PLSSHRT<<7 | piano.KEY_C4] const byte OCTAVE = 12 const byte CHIRPMOD = OCTAVE * 0 uword[] @nosplit pattern_chirp = [mkword(0,15), ; 16 notes total INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_NULL <<7 | 0, INS_CHIRP<<7 | (piano.KEY_G4 + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_G4S + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_A4S + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_C5 + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_G4 + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_G4S + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_A4S + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_C5 + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_G4 + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_G4S + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_A4S + CHIRPMOD), INS_CHIRP<<7 | (piano.KEY_C5 + CHIRPMOD)] } main { sub start() { cx16.screen_set_charset(3+2, 0) ; +2 for upper/lower charset's thin variant txt.clear_screen() music.init() music.play(song_data.info) music_debug.print_info() uword frame = 0 repeat { if (frame%music.STATE_ticksPerRow) == 0 { ;txt.print("whichOrder = ") ;printn.uw_dec(music.STATE_whichOrder) ;txt.print(", whichRow = ") txt.print("whichRow = ") printn.ub_dec(music.STATE_whichRow) txt.nl() } sys.waitvsync() frame++ } } } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;"2nd_music_driver_2025-06-20\music.p8": ; - PUBLIC ROUTINES - ; music.init() (only needs to be called once!) ; music.play(ptr_info) ; music.stop() ; music.stop_forced() %import piano ; - EXAMPLE SONG - ; CAN BE PLAYED WITH THESE 2 LINES: ;music.init() ;music.play(song_example.info) song_example { %option ignore_unused const ubyte PULSE = %00000000 const ubyte SAWTOOTH = %01000000 const ubyte TRIANGLE = %10000000 const ubyte NOISE = %11000000 const ubyte SILENT = %00000000 const ubyte LEFT = %01000000 const ubyte RIGHT = %10000000 const ubyte STEREO = %11000000 ; 6 bytes per instrument const ubyte INSTRUMENTS_LEN = sizeof(instruments)/6 ; +1 for tick speed const ubyte ORDERS_LEN = sizeof(orders)/(CHANNELS+1) const ubyte PATTERNS_LEN = sizeof(patterns)/2 ; This is what you pass to music.play() ; Note: mkword's first arg is the high byte, not the low byte! uword[] @nosplit info = [mkword(INSTRUMENTS_LEN, CHANNELS), mkword(PATTERNS_LEN, ORDERS_LEN), mkword(0, 0), ; (Reserved) &instruments, &orders, &patterns] ; WAVEFORM|WIDTH, EAR_CHANLS|VOL, MAXVOL_6b, ATTACK_8b, SUSTAIN_8b, RELEASE_8b ; ; From psg.envelope()'s remarks: ; maxvolume = 0-63 ; attack, sustain, release = 0-255 that determine the speed of the A/D/R: ; attack time: MAXVOL/15/attack sec. higher value = faster attack. ; sustain time: sustain/60 sec. higher value = longer sustain. ; release time: MAXVOL/15/release sec. higher vaule = faster release. ; ; (A max volume of 0 is interpreted as 'no note', which can be used for ; envelopes longer than the row it resides in, as that current envelope ; will continue until the next row with an instrument without a maxvol of 0.) ubyte[] instruments = [PULSE|63, STEREO|63, 63, 255, 10, 128, TRIANGLE, STEREO|48, 63, 4, 60, 4] ; Accepts values 1-6 (ORDERS_LEN = sizeof(orders)/CHANNELS) const ubyte CHANNELS = 6 ; Contains indices into the patterns array, where the actual ; index equals i-1, and a value of 0 means no pattern at all ; ; The first byte of an order entry however, is the tick speed (frames per row) ubyte[] orders = [12, 1, 2, 0, 0, 0, 0] ; This is an array of pointers ; ; In each pattern, the first byte correlates to its length mask. ; For example, a pattern with a length of 64 would have a mask of 63 ; (Pattern length must be a power of 2!) ; ; If the 2nd byte is nonzero, that pattern's array lacks the @nosplit tag. ; ; The words after are the pattern's rows: ; Bits 0-6 are: The note index; piano key starting from C0# (1-119) ; (A note index of 0 indicates a silent note) ; Bits 7-F are: The instrument index (0-511) uword[] @nosplit patterns = [pattern_1, pattern_2] uword[] @nosplit pattern_1 = [mkword(0,3), ; Mask of 3, for a total of 4 notes 0<<7 | piano.KEY_C4, 0<<7 | piano.KEY_G4, 0<<7 | piano.KEY_C5, 0<<7 | piano.KEY_G4] uword[] @nosplit pattern_2 = [mkword(0,0), ; Mask of 0, for a total of 1 note 1<<7 | piano.KEY_C4] } ; Uses (at most) the upper 12 PSG channels music { %option ignore_unused const uword NULL = 0 ; To make null pointer comparisons a bit more readable ; Byte indexes within song header const ubyte INDEX_CHANNELS = 0 ; LSB of uword 0 const ubyte INDEX_INSTRUMENTS_LEN = 1 ; MSB of uword 0 const ubyte INDEX_ORDERS_LEN = 2 ; LSB of uword 1 const ubyte INDEX_PATTERNS_LEN = 3 ; MSB of uword 1 ;const ubyte INDEX_? = 4 ; LSB of uword 2 ;const ubyte INDEX_? = 5 ; MSB of uword 2 const ubyte INDEX_INSTRUMENTS = 6 const ubyte INDEX_ORDERS = 8 const ubyte INDEX_PATTERNS = 10 ; - SONG DATA (AKA CONTENTS OF SONG INFO) - ubyte SONG_channels ubyte SONG_instruments_len ubyte SONG_orders_len ubyte SONG_patterns_len ubyte SONG_order_size ; = channels+1 (used for alignment, mostly) uword SONG_ptr_info uword SONG_ptr_instruments uword SONG_ptr_orders uword SONG_ptr_patterns ; - STATE VARIABLES - bool STATE_isInit bool STATE_isPlaying uword STATE_whichOrder ; Remember, this is a uword, not a ubyte! ubyte STATE_whichRow ubyte STATE_whichTick ; Actually decrements, loading a new row at 0 ubyte STATE_ticksPerRow ubyte STATE_highestMask ; For every note, switch to an alternate channel ; so that envelopes aren't cut off prematurely ; (This is never reinitialized on purpose, as it's redundant!) ; ; (Also, this is of type ubyte[] instead of bool[], ; so I'm able to apply an XOR to easily flip the boolean) ubyte[6] STATE_altChannel ; Determines the length of each pattern ubyte[6] STATE_rowMasks ; Pointers to the currently loaded patterns uword[6] STATE_ptrs_curOrder sub init() { if STATE_isInit return psg.init() cx16.enable_irq_handlers(false) cx16.set_vsync_irq_handler(&music.irq_routine) STATE_isInit = true } asmsub get_voice_num(ubyte channel @Y) clobbers(A) -> ubyte @Y { ; Calculates the raw voice index, based on a channel index of 0 -> 5 ; returns: (4 + Y<<1 + alt_channel[Y]) ; (I might've been able to save some bytes by storing the boolean in X, ; but that would clobber another register, which is probably worse) %asm {{ lda p8v_STATE_altChannel, y beq _dont_increment ; If boolean is false, don't increment result ;_increment: tya asl ; Y<<1 clc adc #4 ; +4 tay iny ; +alt_channel[Y] rts _dont_increment: tya asl ; Y<<1 clc adc #4 ; +4 tay rts }} } ; Like peekw, but for split arrays ; (I don't have time to make this an asmsub right now unfortunately) ; TODO: This doesn't work in the context in which it's used; why? sub get_split_uword(uword arr, ubyte arr_len, ubyte index) -> uword { uword result setlsb(result, arr[index]) arr += (arr_len as uword) setmsb(result, arr[index]) return result } sub apply_key(ubyte whichVoice, ubyte whichKey) { void piano.key(whichVoice, whichKey&%01111111) } ; Applies both voice and envelope sub apply_instrument(ubyte whichVoice, uword whichInstrument) { whichInstrument *= 6 ; Instrument index to byte offset (each are 6 bytes) whichInstrument += SONG_ptr_instruments ; Byte offset to pointer ubyte wf_pw = whichInstrument[0] ; waveform | pulse_width ubyte ec_sv = whichInstrument[1] ; ear_channels | start_vol ubyte maxvol = whichInstrument[2] ubyte attack = whichInstrument[3] ubyte sustain = whichInstrument[4] ubyte release = whichInstrument[5] if maxvol == 0 { return } psg.voice(whichVoice, ec_sv&%11000000, ec_sv&%00111111, wf_pw&%11000000, wf_pw&%00111111) psg.envelope(whichVoice, maxvol, attack, sustain, release) } sub load_order(uword whichOrder) { ;STATE_whichOrder = whichOrder ; (This line should be redundant) whichOrder *= SONG_order_size ; Order index to byte offset whichOrder += SONG_ptr_orders ; Byte offset to pointer STATE_whichRow = 0 STATE_ticksPerRow = whichOrder[0] STATE_highestMask = %00000001 ; A mask of 1 bit by default whichOrder++ ; whichOrder = &whichOrder[1] (skips the order's tick rate) &ubyte loop_i = &cx16.r4L ; Effectively an alias for cx16.r4L ubyte loop_max = SONG_channels-1 ; For each pattern ptr in current order for loop_i in 0 to loop_max { ubyte patternID = whichOrder[loop_i] &uword ptr_pattern = &cx16.r5 ; Alias for r5 if patternID != 0 { ptr_pattern = peekw( SONG_ptr_patterns + (((patternID-1) as uword)<<1) ) } else { ; A pattern ID of 0 indicates 'no pattern used' ptr_pattern = NULL } STATE_ptrs_curOrder[loop_i] = ptr_pattern if patternID != 0 { ; First byte of pattern is the mask ubyte rowMask = ptr_pattern[0] STATE_rowMasks[loop_i] = rowMask ; For finding the largest mask in all of the currently loaded patterns, ; which is used to determine when an order is supposed to end if STATE_highestMask < rowMask { STATE_highestMask = rowMask } } } } ; (Assumes order is already loaded) sub load_row(ubyte whichRow) { STATE_whichTick = STATE_ticksPerRow ; Decrements instead of incrementing &ubyte loop_i = &cx16.r4L ; Effectively an alias for cx16.r4L ubyte loop_max = SONG_channels-1 ; For each pattern ptr in current order for loop_i in 0 to loop_max { uword ptr_curPattern = STATE_ptrs_curOrder[loop_i] if ptr_curPattern == NULL { continue } ubyte patternIsSplit = ptr_curPattern[1] ptr_curPattern += 2 ; Skip the pattern's mask and split value ubyte patternMask = STATE_rowMasks[loop_i] ubyte whichRowMasked = whichRow & patternMask uword rowValue if patternIsSplit != 0 { ; TODO: Figure out why this doesn't work ;rowValue = get_split_uword(ptr_curPattern, patternMask, whichRowMasked) } else { rowValue = peekw( ptr_curPattern + ((whichRowMasked as uword)<<1) ) } ubyte whichKey = lsb(rowValue) ;&%01111111 (this AND is redundant) ubyte whichInstrument = (rowValue>>7) as ubyte ubyte whichVoice = get_voice_num(loop_i) if whichKey == 0 { continue } apply_key(whichVoice, whichKey) ; (Automatically unsets MSb of whichKey) apply_instrument(whichVoice, whichInstrument) STATE_altChannel[loop_i] ^= 1 ; Flip the relevant bit } } ; Just 1 instruction (a 65C02-specific instruction :D) ; Assuming that STATE_isPlaying is zeropage, 1 byte per call is actually saved ; by inlining this lol (otherwise, the byte count is the same; 3 per call) inline asmsub stop() clobbers() { %asm {{ stz p8v_STATE_isPlaying }} } sub stop_forced() { stop() } ; TODO: Set all of the channels to mute here sub play(uword ptr_info) { if ptr_info == NULL { return } SONG_channels = ptr_info[INDEX_CHANNELS] SONG_instruments_len = ptr_info[INDEX_INSTRUMENTS_LEN] SONG_orders_len = ptr_info[INDEX_ORDERS_LEN] SONG_patterns_len = ptr_info[INDEX_PATTERNS_LEN] SONG_order_size = SONG_channels+1 SONG_ptr_info = ptr_info SONG_ptr_instruments = peekw(ptr_info + INDEX_INSTRUMENTS) SONG_ptr_orders = peekw(ptr_info + INDEX_ORDERS) SONG_ptr_patterns = peekw(ptr_info + INDEX_PATTERNS) STATE_whichOrder = 0 STATE_whichRow = 0 ; So that order 0 is immediately loaded next irq STATE_whichTick = 0 STATE_highestMask = 255 ; Full mask by default, until order 0 is loaded STATE_isPlaying = true } sub irq_routine() -> bool { if not STATE_isPlaying { goto lbl_songStopped } ubyte whichRowMasked = STATE_whichRow & STATE_highestMask if whichRowMasked == 0 { ; TODO: Looping is broken. Fix it. load_order(STATE_whichOrder) STATE_whichOrder++ ; Should be faster than a normal modulo if STATE_whichOrder >= SONG_orders_len { STATE_whichOrder -= SONG_orders_len } } if STATE_whichTick == 0 { load_row(whichRowMasked) STATE_whichRow++ } STATE_whichTick-- lbl_songStopped: return psg.envelopes_irq() } } ;------------------------------------------------------------------------------; %import textio %import conv ; txt.clear_screen() ; txt.nl() printn { %option ignore_unused sub ub_dec(ubyte v) { txt.print(conv.str_ub(v)) } sub b_dec(byte v) { txt.print(conv.str_b(v)) } sub uw_dec(uword v) { txt.print(conv.str_uw(v)) } sub w_dec(word v) { txt.print(conv.str_w(v)) } sub ub_bin(ubyte v) { txt.print(conv.str_ubbin(v)) } sub ub_hex(ubyte v) { txt.print(conv.str_ubhex(v)) } sub uw_hex(uword v) { txt.print(conv.str_uwhex(v)) } } music_debug { %option ignore_unused sub print_info() { txt.print("channels = ") printn.ub_dec(music.SONG_channels) txt.print("\ninstruments_len = ") printn.ub_dec(music.SONG_instruments_len) txt.print("\norders_len = ") printn.ub_dec(music.SONG_orders_len) txt.print("\npatterns_len = ") printn.ub_dec(music.SONG_patterns_len) txt.print("\norder_size = ") printn.ub_dec(music.SONG_order_size) txt.print("\nptr_instruments = $") printn.uw_hex(music.SONG_ptr_instruments) txt.print("\nptr_orders = $") printn.uw_hex(music.SONG_ptr_orders) txt.print("\nptr_patterns = $") printn.uw_hex(music.SONG_ptr_patterns) txt.nl() } sub print_state() { txt.print("isInit = ") printn.ub_dec(music.STATE_isInit as ubyte) txt.print("\nisPlaying = ") printn.ub_dec(music.STATE_isPlaying as ubyte) txt.print("\nwhichOrder = ") printn.uw_dec(music.STATE_whichOrder) ; uword, not ubyte txt.print("\nwhichRow = ") printn.ub_dec(music.STATE_whichRow) txt.print("\nwhichTick = ") printn.ub_dec(music.STATE_whichTick) txt.print("\nticksPerRow = ") printn.ub_dec(music.STATE_ticksPerRow) txt.print("\nhighestMask = ") printn.ub_dec(music.STATE_highestMask) &ubyte loop_i = &cx16.r6L txt.print("\naltChannel = [") for loop_i in 0 to 5 { printn.ub_dec(music.STATE_altChannel[cx16.r4L]) txt.print(", ") } txt.print("]\n") txt.print("rowMasks = [") for loop_i in 0 to 5 { printn.ub_dec(music.STATE_rowMasks[cx16.r4L]) txt.print(", ") } txt.print("]\n") txt.print("curOrder = [") for loop_i in 0 to 5 { txt.chrout('$') printn.uw_hex(music.STATE_ptrs_curOrder[cx16.r4L]) txt.print(", ") } txt.print("]\n") } sub print_arr_ub(str prefix, uword ptr, ubyte index) { txt.print(prefix) txt.print(" = $") printn.ub_hex(ptr[index]) txt.nl() } sub print_arr_uw(str prefix, uword ptr, ubyte index) { txt.print(prefix) txt.print(" = $") printn.uw_hex(peekw( ptr + ((index as uword)<<1) )) txt.nl() } } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;"2nd_music_driver_2025-06-20\piano.p8": %import psg piano { ;%option no_symbol_prefixing, ignore_unused %option ignore_unused ; Referring to the index in the lookup table, ; not the actual frequency value, of course. const ubyte KEY_C0 = 0 ; 16.35Hz const ubyte KEY_C0S = 1 ; 17.32Hz const ubyte KEY_D0 = 2 ; 18.35Hz const ubyte KEY_D0S = 3 ; 19.44Hz const ubyte KEY_E0 = 4 ; 20.60Hz const ubyte KEY_F0 = 5 ; 21.82Hz const ubyte KEY_F0S = 6 ; 23.12Hz const ubyte KEY_G0 = 7 ; 24.49Hz const ubyte KEY_G0S = 8 ; 25.95Hz const ubyte KEY_A0 = 9 ; 27.50Hz const ubyte KEY_A0S = 10 ; 29.13Hz const ubyte KEY_B0 = 11 ; 30.86Hz const ubyte KEY_C1 = 12 ; 32.70Hz const ubyte KEY_C1S = 13 ; 34.64Hz const ubyte KEY_D1 = 14 ; 36.70Hz const ubyte KEY_D1S = 15 ; 38.89Hz const ubyte KEY_E1 = 16 ; 41.20Hz const ubyte KEY_F1 = 17 ; 43.65Hz const ubyte KEY_F1S = 18 ; 46.24Hz const ubyte KEY_G1 = 19 ; 48.99Hz const ubyte KEY_G1S = 20 ; 51.91Hz const ubyte KEY_A1 = 21 ; 55.00Hz const ubyte KEY_A1S = 22 ; 58.27Hz const ubyte KEY_B1 = 23 ; 61.73Hz const ubyte KEY_C2 = 24 ; 65.40Hz const ubyte KEY_C2S = 25 ; 69.29Hz const ubyte KEY_D2 = 26 ; 73.41Hz const ubyte KEY_D2S = 27 ; 77.78Hz const ubyte KEY_E2 = 28 ; 82.40Hz const ubyte KEY_F2 = 29 ; 87.30Hz const ubyte KEY_F2S = 30 ; 92.49Hz const ubyte KEY_G2 = 31 ; 97.99Hz const ubyte KEY_G2S = 32 ; 103.82Hz const ubyte KEY_A2 = 33 ; 110.00Hz const ubyte KEY_A2S = 34 ; 116.54Hz const ubyte KEY_B2 = 35 ; 123.47Hz const ubyte KEY_C3 = 36 ; 130.81Hz const ubyte KEY_C3S = 37 ; 138.59Hz const ubyte KEY_D3 = 38 ; 146.83Hz const ubyte KEY_D3S = 39 ; 155.56Hz const ubyte KEY_E3 = 40 ; 164.81Hz const ubyte KEY_F3 = 41 ; 174.61Hz const ubyte KEY_F3S = 42 ; 184.99Hz const ubyte KEY_G3 = 43 ; 195.99Hz const ubyte KEY_G3S = 44 ; 207.65Hz const ubyte KEY_A3 = 45 ; 220.00Hz const ubyte KEY_A3S = 46 ; 233.08Hz const ubyte KEY_B3 = 47 ; 246.94Hz const ubyte KEY_C4 = 48 ; 261.62Hz const ubyte KEY_C4S = 49 ; 277.18Hz const ubyte KEY_D4 = 50 ; 293.66Hz const ubyte KEY_D4S = 51 ; 311.12Hz const ubyte KEY_E4 = 52 ; 329.62Hz const ubyte KEY_F4 = 53 ; 349.22Hz const ubyte KEY_F4S = 54 ; 369.99Hz const ubyte KEY_G4 = 55 ; 391.99Hz const ubyte KEY_G4S = 56 ; 415.30Hz const ubyte KEY_A4 = 57 ; 440.00Hz const ubyte KEY_A4S = 58 ; 466.16Hz const ubyte KEY_B4 = 59 ; 493.88Hz const ubyte KEY_C5 = 60 ; 523.25Hz const ubyte KEY_C5S = 61 ; 554.36Hz const ubyte KEY_D5 = 62 ; 587.32Hz const ubyte KEY_D5S = 63 ; 622.25Hz const ubyte KEY_E5 = 64 ; 659.25Hz const ubyte KEY_F5 = 65 ; 698.45Hz const ubyte KEY_F5S = 66 ; 739.98Hz const ubyte KEY_G5 = 67 ; 783.99Hz const ubyte KEY_G5S = 68 ; 830.60Hz const ubyte KEY_A5 = 69 ; 880.00Hz const ubyte KEY_A5S = 70 ; 932.32Hz const ubyte KEY_B5 = 71 ; 987.76Hz const ubyte KEY_C6 = 72 ; 1046.50Hz const ubyte KEY_C6S = 73 ; 1108.73Hz const ubyte KEY_D6 = 74 ; 1174.65Hz const ubyte KEY_D6S = 75 ; 1244.50Hz const ubyte KEY_E6 = 76 ; 1318.51Hz const ubyte KEY_F6 = 77 ; 1396.91Hz const ubyte KEY_F6S = 78 ; 1479.97Hz const ubyte KEY_G6 = 79 ; 1567.98Hz const ubyte KEY_G6S = 80 ; 1661.21Hz const ubyte KEY_A6 = 81 ; 1760.00Hz const ubyte KEY_A6S = 82 ; 1864.65Hz const ubyte KEY_B6 = 83 ; 1975.53Hz const ubyte KEY_C7 = 84 ; 2093.00Hz const ubyte KEY_C7S = 85 ; 2217.46Hz const ubyte KEY_D7 = 86 ; 2349.31Hz const ubyte KEY_D7S = 87 ; 2489.01Hz const ubyte KEY_E7 = 88 ; 2637.02Hz const ubyte KEY_F7 = 89 ; 2793.82Hz const ubyte KEY_F7S = 90 ; 2959.95Hz const ubyte KEY_G7 = 91 ; 3135.96Hz const ubyte KEY_G7S = 92 ; 3322.43Hz const ubyte KEY_A7 = 93 ; 3520.00Hz const ubyte KEY_A7S = 94 ; 3729.31Hz const ubyte KEY_B7 = 95 ; 3951.06Hz const ubyte KEY_C8 = 96 ; 4186.00Hz const ubyte KEY_C8S = 97 ; 4434.92Hz const ubyte KEY_D8 = 98 ; 4698.63Hz const ubyte KEY_D8S = 99 ; 4978.03Hz const ubyte KEY_E8 = 100 ; 5274.04Hz const ubyte KEY_F8 = 101 ; 5587.65Hz const ubyte KEY_F8S = 102 ; 5919.91Hz const ubyte KEY_G8 = 103 ; 6271.92Hz const ubyte KEY_G8S = 104 ; 6644.87Hz const ubyte KEY_A8 = 105 ; 7040.00Hz const ubyte KEY_A8S = 106 ; 7458.62Hz const ubyte KEY_B8 = 107 ; 7902.13Hz const ubyte KEY_C9 = 108 ; 8372.01Hz const ubyte KEY_C9S = 109 ; 8869.84Hz const ubyte KEY_D9 = 110 ; 9397.27Hz const ubyte KEY_D9S = 111 ; 9956.06Hz const ubyte KEY_E9 = 112 ;10548.08Hz const ubyte KEY_F9 = 113 ;11175.30Hz const ubyte KEY_F9S = 114 ;11839.82Hz const ubyte KEY_G9 = 115 ;12543.85Hz const ubyte KEY_G9S = 116 ;13289.75Hz const ubyte KEY_A9 = 117 ;14080.00Hz const ubyte KEY_A9S = 118 ;14917.24Hz const ubyte KEY_B9 = 119 ;15804.26Hz ; PSG frequency lookup table (used by key(); private) ubyte[120] priv_freqs_lo = [44, 47, 49, 52, 55, 59, 62, 66, 70, 74, 78, 83, 88, 93, 99,104,111,117,124,132,139,148,156,166,176,186,197,209,221,234,248, 7, 23, 39, 57, 75, 95,116,138,162,186,213,241, 14, 45, 79,114,151,190,232, 20, 67,117,169,225, 28, 91,157,227, 46,125,208, 41,134,234, 83,194, 57,182, 58,199, 92,249,160, 81, 13,211,166,133,113,107,116,141,183,242, 64,162, 25,167, 76, 10,226,215,233, 27,110,229,129, 69, 51, 77,151, 19,196,173,210, 54,220,201, 2,138,102,155, 46, 38,136, 90,164,107,184] ubyte[120] priv_freqs_hi = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 26, 27, 29, 31, 32, 34, 36, 39, 41, 43, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 93, 98,104,110,117,124,131,139,147,156,165] ; voice_num: 0-15, which: 0-119 ; returns: the resulting raw frequency value sub key(ubyte voice_num, ubyte which) -> uword { uword freq_full = mkword(priv_freqs_hi[which], priv_freqs_lo[which]) psg.freq(voice_num, freq_full) return freq_full } }