aboutsummaryrefslogtreecommitdiff
path: root/src/sound.s
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-04-13 16:34:11 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-04-13 16:34:11 +0200
commitaef156ac6216ed157b06a2872ba051af43317e93 (patch)
treea6daca2bc0353e6b59823bc6bf019362ecb02507 /src/sound.s
parentc3c6f3b4469bf9eadff5763fa16dbbbf15637e57 (diff)
downloadjetpac.nes-aef156ac6216ed157b06a2872ba051af43317e93.tar.gz
jetpac.nes-aef156ac6216ed157b06a2872ba051af43317e93.zip
Add sound effects
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'src/sound.s')
-rw-r--r--src/sound.s171
1 files changed, 171 insertions, 0 deletions
diff --git a/src/sound.s b/src/sound.s
new file mode 100644
index 0000000..a824898
--- /dev/null
+++ b/src/sound.s
@@ -0,0 +1,171 @@
+.segment "CODE"
+
+;; The sound for this game is extremely simple, coming from a system that only
+;; allowed for 1-bit beeps. Here the sound is a bit different due to a vastly
+;; different audio hardware, and we make use of three channels:
+;;
+;; 1. Square 1: used on level entry and bullets.
+;; 2. Square 2: used for item collection or dropping.
+;; 3. Noise: enemy explosion and rocket launch.
+.scope Sound
+ ;; Bullets can go super fast, and if we delivered the sound effect for each
+ ;; bullet it could potentially be annoying. Moreover, because of the
+ ;; limitations from the ZX Spectrum, the original game didn't spit a sound
+ ;; effect for each bullet either. Hence, wait for some frames before
+ ;; producing a sound. This is why we call Sound::tick() on NMI code, and why
+ ;; the sound effect for bullet is called via Sound::play_bullet_maybe().
+ ;;
+ ;; NOTE: the maximum count value is supposed to be bigger than the timer for
+ ;; bullets creation. This is guaranteed in 'jetpac.s'.
+ zp_frame_count = $DA
+ BULLET_SFX_FRAME_COUNT = HZ / 10
+
+ ;; Period values for square channels.
+ .ifdef PAL
+ BULLET_SFX_LOW = $29
+ BULLET_SFX_HIGH = $00
+ ENTER_SFX_LOW = $4D
+ ENTER_SFX_HIGH = $01
+ PICKUP_SFX_LOW = $61
+ PICKUP_SFX_HIGH = $01
+ DROP_SFX_LOW = $3A
+ DROP_SFX_HIGH = $01
+ .else
+ BULLET_SFX_LOW = $2C
+ BULLET_SFX_HIGH = $00
+ ENTER_SFX_LOW = $67
+ ENTER_SFX_HIGH = $01
+ PICKUP_SFX_LOW = $7C
+ PICKUP_SFX_HIGH = $01
+ DROP_SFX_LOW = $52
+ DROP_SFX_HIGH = $01
+ .endif
+
+ ;; Initialize all the sound channels which are needed and reset some
+ ;; register values.
+ .proc init
+ ;; Enable square 1, 2; and noise.
+ lda #%00001011
+ sta APU::m_status
+
+ ;; Reset sweep registers and frame count.
+ lda #0
+ sta APU::m_square_1_sweep
+ sta APU::m_square_2_sweep
+ sta Sound::zp_frame_count
+
+ ;; Silence channels.
+ lda #0
+ sta APU::m_noise_envelope
+ lda #$30
+ sta APU::m_square_1_envelope
+ sta APU::m_square_2_envelope
+
+ rts
+ .endproc
+
+ ;; Tick the internal frame count for sound effects.
+ ;;
+ ;; NOTE: expected to only be called at the end of NMI code.
+ .proc tick
+ ;; If there is no bullet sound effect to be delivered, don't even sweat
+ ;; it.
+ lda Sound::zp_frame_count
+ beq @end
+
+ ;; Increase the frame counter and check the limit. If we reached that
+ ;; limit, reset it so we don't tick until the next bullet sfx request
+ ;; comes in.
+ clc
+ adc #1
+ cmp #Sound::BULLET_SFX_FRAME_COUNT
+ beq @reset
+ sta Sound::zp_frame_count
+ rts
+
+ @reset:
+ lda #0
+ sta Sound::zp_frame_count
+
+ @end:
+ rts
+ .endproc
+
+ ;; Play the bullet sound effect if we can (i.e. the frame count allows us to
+ ;; do it).
+ .proc play_bullet_maybe
+ ;; If we cannot play the sound yet, skip this altogether.
+ lda Sound::zp_frame_count
+ bne @end
+ inc Sound::zp_frame_count
+
+ lda #$01
+ sta APU::m_square_1_envelope
+ lda #%10000001
+ sta APU::m_square_1_sweep
+ lda #BULLET_SFX_LOW
+ sta APU::m_square_1_low
+ lda #BULLET_SFX_HIGH
+ sta APU::m_square_1_high
+
+ @end:
+ rts
+ .endproc
+.endscope
+
+;; Make an explosion sound via the noise channel.
+.macro SOUND_EXPLOSION
+ lda #$03
+ sta APU::m_noise_envelope
+ lda #$8F
+ sta APU::m_noise_mode
+ lda #$F8
+ sta APU::m_noise_counter
+.endmacro
+
+;; Make a small beep, suitable for level entry.
+.macro SOUND_ENTER_LEVEL
+ lda #%10000100
+ sta APU::m_square_1_envelope
+ lda #Sound::ENTER_SFX_LOW
+ sta APU::m_square_1_low
+ lda #Sound::ENTER_SFX_HIGH
+ sta APU::m_square_1_high
+.endmacro
+
+;; Make a small beep for item pickup.
+.macro SOUND_ITEM_PICKUP
+ lda #%10000100
+ sta APU::m_square_2_envelope
+ lda #Sound::PICKUP_SFX_LOW
+ sta APU::m_square_2_low
+ lda #Sound::PICKUP_SFX_HIGH
+ sta APU::m_square_2_high
+.endmacro
+
+;; Make a small beep for item collection in the droppping zone (i.e. fuel tanks
+;; and shuttle parts making into the shuttle).
+.macro SOUND_ITEM_DROP
+ lda #%10000100
+ sta APU::m_square_2_envelope
+ lda #Sound::DROP_SFX_LOW
+ sta APU::m_square_2_low
+ lda #Sound::DROP_SFX_HIGH
+ sta APU::m_square_2_high
+.endmacro
+
+;; Start the sound effect for the rocket take off animation.
+.macro START_TAKE_OFF_SOUND
+ lda #$38
+ sta APU::m_noise_envelope
+ lda #$0F
+ sta APU::m_noise_mode
+ lda #0
+ sta APU::m_noise_counter
+.endmacro
+
+;; Stop the sound effect for the rocket take off animation.
+.macro STOP_TAKE_OFF_SOUND
+ lda #0
+ sta APU::m_noise_envelope
+.endmacro