;;; ;; Repeat a scale of notes on the triangle channel. .segment "HEADER" .byte 'N', 'E', 'S', $1A .byte $02, $01 .res $0A, $00 .segment "VECTORS" .addr nmi, reset, irq .segment "CHARS" .incbin "../assets/basic.chr" .segment "CODE" .include "../shared/asm.s" .include "../shared/apu.s" .include "../shared/oam.s" .include "../shared/ppu.s" .include "../shared/clear.s" .scope Vars zp_flags = $20 ;; Boolean value stating whether the currently indexed note should be ;; delivered to the triangle channel or not. zp_next_note = $21 ;; Index to the 'notes_{low,high}' tables, pointing at the current note. zp_index = $22 ;; Simple counter increased/reset on NMI code. zp_counter = $23 .endscope ;; Where the scale of notes should start and end. INITIAL_NOTE = 40 HIGHEST_NOTE = 50 .proc reset sei cld ldx #$40 stx APU::m_frame_counter ldx #$FF txs inx stx PPU::m_control stx PPU::m_mask stx APU::m_dmc bit PPU::m_status @vblankwait1: bit PPU::m_status bpl @vblankwait1 ldx #0 lda #0 @ram_reset_loop: sta $000, x sta $100, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x inx bne @ram_reset_loop lda #$EF @sprite_reset_loop: sta $200, x inx bne @sprite_reset_loop OAM_WRITE_SPRITES @vblankwait2: bit PPU::m_status bpl @vblankwait2 lda #$3F sta PPU::m_address lda #$00 sta PPU::m_address lda #$0F ldx #$20 @palettes_reset_loop: sta PPU::m_data dex bne @palettes_reset_loop __fallthrough__ main .endproc .proc main CLEAR_SCREEN ;; Initialize the note index, reset the counter and ensure that the first ;; note is delivered. lda #INITIAL_NOTE sta Vars::zp_index ldx #0 stx Vars::zp_counter inx stx Vars::zp_next_note ;; Same as beep.s lda #$0F sta APU::m_status ;; Enable the triangle channel. lda #%10000001 sta APU::m_triangle_control cli lda #%10001000 sta PPU::m_control lda #%00011110 sta PPU::m_mask @main_game_loop: ;; Should we change the note? If not, then skip this altogether, or ;; otherwise sending the same note over and over will result in a ;; blurry/ugly sound. lda Vars::zp_next_note beq @skip_note ;; Fetch the new note and send it to the triangle channel. Note that notes ;; on both tables are defined with the square channels in mind. Sicne the ;; triangle channel is an octave below, we need to divide the given value by ;; two. ldx Vars::zp_index lda notes_low, x lsr sta APU::m_triangle_low lda notes_high, x lsr sta APU::m_triangle_high @skip_note: lda #%10000000 ora Vars::zp_flags sta Vars::zp_flags @wait_for_render: bit Vars::zp_flags bmi @wait_for_render jmp @main_game_loop ;; NOTE: notes taken from: https://www.nesdev.org/wiki/APU_basics. ;; NOTE: only valid for NTSC. For PAL we would need another set of values ;; which can be found in other places. notes_low: .byte $f1,$7f,$13,$ad,$4d,$f3,$9d,$4c,$00,$b8,$74,$34 .byte $f8,$bf,$89,$56,$26,$f9,$ce,$a6,$80,$5c,$3a,$1a .byte $fb,$df,$c4,$ab,$93,$7c,$67,$52,$3f,$2d,$1c,$0c .byte $fd,$ef,$e1,$d5,$c9,$bd,$b3,$a9,$9f,$96,$8e,$86 .byte $7e,$77,$70,$6a,$64,$5e,$59,$54,$4f,$4b,$46,$42 .byte $3f,$3b,$38,$34,$31,$2f,$2c,$29,$27,$25,$23,$21 .byte $1f,$1d,$1b,$1a,$18,$17,$15,$14 notes_high: .byte $07,$07,$07,$06,$06,$05,$05,$05,$05,$04,$04,$04 .byte $03,$03,$03,$03,$03,$02,$02,$02,$02,$02,$02,$02 .byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00 .endproc .proc nmi bit Vars::zp_flags bpl @next pha txa pha tya pha ;; Increase the counter and do nothing if we are not at the desired value yet. inc Vars::zp_counter lda Vars::zp_counter cmp #30 bne @skip_reset ;; Reset counter, move the note index and state that we need to update the ;; triangle channel with this new note. lda #0 sta Vars::zp_counter inc Vars::zp_index inc Vars::zp_next_note ;; Wrap around the note index if needed. lda Vars::zp_index cmp #HIGHEST_NOTE bne @done lda #INITIAL_NOTE sta Vars::zp_index bne @done @skip_reset: ;; In the general case, avoid the current note to be delivered. lda #0 sta Vars::zp_next_note @done: lda #%01111111 and Vars::zp_flags sta Vars::zp_flags pla tay pla tax pla @next: rti .endproc .proc irq rti .endproc