diff options
Diffstat (limited to 'sound')
| -rw-r--r-- | sound/beep.s | 84 |
1 files changed, 62 insertions, 22 deletions
diff --git a/sound/beep.s b/sound/beep.s index cae6ec6..f9ab7a8 100644 --- a/sound/beep.s +++ b/sound/beep.s @@ -101,36 +101,70 @@ 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. + ;; This was the configuration for the APU. After that, all enabled channels + ;; can produce output and it's going to be combined using a non-linear + ;; mixing scheme (https://www.nesdev.org/wiki/APU_Mixer). Long story short: + ;; all channels work independently and they are going to be properly + ;; combined in the end, so a programmer can focus on each channel + ;; separately. + + ;; This example uses the Square 1 channel to produce sound. Let's see how it + ;; can be configured, and 'sound/select.s' will deal with the other + ;; channels. + ;; + ;; The $4000 register configures how the square 1 channel will produce its + ;; output via the envelope. The **envelope** is a hardware feature that + ;; automatically controls the volume of a sound over time. The APU has two + ;; modes of operation: constant volume or decay envelope mode. This is + ;; regulated via bit 4 of the $4000 register: 1 for constant volume; 0 for + ;; decay envelope mode. With this in mind, the low nibble of this register + ;; works like this: ;; - ;; 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. + ;; - Bit 4 = 1: the low nibble contains the **volume** of the sound, which + ;; will be frozen in time (i.e. constant). Set the volume to + ;; 0 for a silent channel; 1 for very low volume, F for + ;; maximum volume. + ;; - Bit 4 = 0: the low nibble contains the **period** of decay of the + ;; volume. That is, it makes the volume to decay every V + ;; frames, where V is the value on the low nibble. This is in + ;; the end possible because of the internal frame counter + ;; from the APU. In this context, the volume will always + ;; start at F, and it will decay until reaching zero at the + ;; given period. With all of that in mind, then, note that + ;; higher number means decaying the volume slower. ;; - ;; 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). + ;; Another important bit is 5, which is the "looping" bit. That is, whether + ;; the configured envelope should be repeated after a shot has been done. If + ;; this bit is set to 1, then this envelope will be repeated over and + ;; over. If, otherwise, the bit is set to 0, then it's a one-shot note. For + ;; how long this one-shot should happen? This is regulated in $4003 as you + ;; will see below. ;; - ;; 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. + ;; With this in mind note that in this example we set these two bits to + ;; 1. Hence, it's an continuous beep, and the low nibble contains the volume + ;; that will be constant over time. The value for the volume is F, hence + ;; maximum volume. + ;; + ;; Finally we have the two most significant bits, which configure the "duty" + ;; cycle. That is, on a square wave, what percentage of time should the wave + ;; spend in the "up" position. We have two bits, so we can select four + ;; different configurations of the wave's shape (see: + ;; https://www.nesdev.org/wiki/APU_Pulse). Here we pick "10", which + ;; corresponds to the 50% duty cycle, meaning that we want a square wave + ;; that spends the same amount of time up and down. 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. + ;; issues on some tones. Configuring the sweep register so we tend towards + ;; lower frequencies (longer periods) can also be a way of muting the + ;; channel. 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 + ;; it, which for square 1 are $4002 and $4003. $4002 contains 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: @@ -139,10 +173,16 @@ ;; ;; 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. + ;; will (magically) track things for you. Funnily enough, this "counter" is + ;; not really a counter, but an index to a table with the actual values. See + ;; the table here: https://www.nesdev.org/wiki/APU_Length_Counter. 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. + ;; + ;; NOTE: one gotcha from writing into $4003 is that it implicitely resets + ;; the envelope. Hence, if you write to this every frame for whatever + ;; reason, then you will never notice the fading. lda #$C9 sta APU::m_square_1_low lda #$00 |
