aboutsummaryrefslogtreecommitdiff
path: root/sound/beep.s
blob: cae6ec62dda4ce3011d9a288cb6a145f83881934 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
;;;
;; Make an annoying "beep" sound.

;;; Nothing remarkable here, go down below until you see some comments :)

.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
.endscope

.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

    ;; In order to get sound, you need to enable/disable the channels you
    ;; want. One typical setup is to enable all of them except for DMC. You do
    ;; this by writing into $4015 (APU Status). Note that this register can also
    ;; be read. Reading from this register will give you the state of
    ;; interrupts, and the length of the counter for each channel. Yes, each
    ;; channel has an internal counter, but we can discuss this later.
    ;;
    ;; Anyways, if we want to enable all channels except DMC, we are in luck
    ;; because then we just need to set all bits for the low nibble.
    lda #$0F
    sta APU::m_status

    ;; This was the configuration for the APU. Now let's produce sound on the
    ;; Square 1 channel (registers: $4000-$4003).

    ;; Let's configure the Square 1 channel before producing any sound.
    ;;
    ;; The low nibble controls the volume: 0 for silent, 1 very low, F
    ;; maximum. Just to be annoying we will be setting the volume at a maximum
    ;; level.
    ;;
    ;; Then we have two bits which seem quite odd at first. Bit 4 sets/unsets
    ;; whether the volume is to be kept constant. If unset, then an internal
    ;; counter will tune it down when running out. Similarly, bit 5 sets/unsets
    ;; whether the length counter is to be accounted or not. See this counter
    ;; down below. All in all, here we make the sound constant, so the beep
    ;; never stops until we mute it (which we don't in this example).
    ;;
    ;; Finally we have the "duty" bits, which has four possibilities
    ;; available. These regulate the tone for the note, and it's basically the
    ;; percentage of time the square wave is in the "up" position.
    lda #%10111111
    sta APU::m_square_1_envelope

    ;; The $4001 address contains the sweep register, which is a way that the
    ;; APU has in order to produce different pitch effects, or workaround known
    ;; issues on some tones. I'm not touching it here other than resetting to 0.
    lda #0
    sta APU::m_square_1_sweep

    ;; The note to play is 11 bits long. This means that we need two bytes for
    ;; it, which for square 1 are $4002 and $4003. $4002 is the least
    ;; significant bits, and the three least significant bits from $4003 are the
    ;; most significant bits from this 11-bit note definition. How to know which
    ;; note corresponds to what value is easy via:
    ;; https://www.nesdev.org/wiki/APU_period_table. Thus, in NTSC, setting $0C9
    ;; to this 11-bit value gives us a C#.
    ;;
    ;; The 5 other bits from $4003 correspond to the length counter. That is,
    ;; you can regulate for how long this note has to be reproduced, and the APU
    ;; will (magically) track things for you. This is not available on this
    ;; configuration because we disabled the option when we configured
    ;; 'APU::m_square_1_envelope' (see above). Hence, we will set these bits to
    ;; 0.
    lda #$C9
    sta APU::m_square_1_low
    lda #$00
    sta APU::m_square_1_high

    ;; NOTE: and that's it :)

    cli
    lda #%10001000
    sta PPU::m_control
    lda #%00011110
    sta PPU::m_mask

@main_game_loop:

    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
.endproc

.proc nmi
    bit Vars::zp_flags
    bpl @next

    pha
    txa
    pha
    tya
    pha

    lda #%01111111
    and Vars::zp_flags
    sta Vars::zp_flags

    pla
    tay
    pla
    tax
    pla
@next:
    rti
.endproc

.proc irq
    rti
.endproc