From a7a268c9abc34c2fdf1cbf802e625f825bf86743 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Thu, 9 Apr 2026 23:03:04 +0200 Subject: Add the sound/scale.s example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This example just repeats a scale of notes over and over. Signed-off-by: Miquel Sabaté Solà --- Makefile | 3 + sound/scale.s | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 sound/scale.s diff --git a/Makefile b/Makefile index 2940102..8acdb7f 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,9 @@ sound: $(E) " CC sound/select" $(Q) $(CC65) $(CCOPTS) sound/select.s -C config/nrom.cfg -o out/sound/select.nes + $(E) " CC sound/scale" + $(Q) $(CC65) $(CCOPTS) sound/scale.s -C config/nrom.cfg -o out/sound/scale.nes + .PHONY: fx fx: $(E) " CC fx/blink" diff --git a/sound/scale.s b/sound/scale.s new file mode 100644 index 0000000..ffb679d --- /dev/null +++ b/sound/scale.s @@ -0,0 +1,229 @@ +;;; +;; 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 -- cgit v1.2.3