aboutsummaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/bullets.s3
-rw-r--r--src/driver.s9
-rw-r--r--src/enemies.s3
-rw-r--r--src/interrupts.s5
-rw-r--r--src/items.s9
-rw-r--r--src/jetpac.s12
-rw-r--r--src/sound.s171
7 files changed, 212 insertions, 0 deletions
diff --git a/src/bullets.s b/src/bullets.s
index 1072a8f..9e40546 100644
--- a/src/bullets.s
+++ b/src/bullets.s
@@ -133,6 +133,9 @@
beq @find_free_bullet_bucket
@initialize_bucket:
+ ;; Play the bullet sound if appropiate.
+ jsr Sound::play_bullet_maybe
+
;; We found a free bucket. Initialize the first byte to 0 since it has
;; not moved yet. The heading is taken from the player's state.
lda Player::zp_state
diff --git a/src/driver.s b/src/driver.s
index 2bc78ca..4e54e8c 100644
--- a/src/driver.s
+++ b/src/driver.s
@@ -240,6 +240,9 @@
sta zp_next_bullet_cycle
sta zp_next_enemy_cycle
+ ;; And make the sound for entering into a level.
+ SOUND_ENTER_LEVEL
+
@game:
;; Has the player died?
lda Globals::zp_flags
@@ -818,6 +821,9 @@
ora #%01100000
sta Globals::zp_flags
+ ;; Make some noise!
+ START_TAKE_OFF_SOUND
+
rts
.endproc
@@ -928,6 +934,9 @@
ora #%01100000
sta Globals::zp_flags
+ ;; Stop the sound effect for take off.
+ STOP_TAKE_OFF_SOUND
+
rts
@set:
diff --git a/src/enemies.s b/src/enemies.s
index 2208758..76f3ddf 100644
--- a/src/enemies.s
+++ b/src/enemies.s
@@ -695,6 +695,9 @@
ldx Enemies::zp_pool_index
sta Enemies::zp_pool_base + 3, x
+ ;; Play an explosion effect.
+ SOUND_EXPLOSION
+
;; Restore back the value for the 'y' register.
pla
tay
diff --git a/src/interrupts.s b/src/interrupts.s
index 7026223..658f6a3 100644
--- a/src/interrupts.s
+++ b/src/interrupts.s
@@ -161,6 +161,11 @@
sta PPU::m_scroll
sta PPU::m_scroll
+ ;; The sound effect from bullets follow a frame rule. Tick the frame count
+ ;; from sound as the very last thing to be done before unblocking the main
+ ;; code.
+ jsr Sound::tick
+
;; Unblock the main code.
lda #%01111111
and Globals::zp_flags
diff --git a/src/items.s b/src/items.s
index d8d1391..1e8a111 100644
--- a/src/items.s
+++ b/src/items.s
@@ -591,6 +591,9 @@
;; Increase the number of collected items.
inc Items::zp_collected
+ ;; Make the "item drop" sound
+ SOUND_ITEM_DROP
+
;; Now we unset the 'S' bit, which is unconditionally true regardless of
;; the collection state. That being said, if we still need to collect
;; more fuel tanks (the rocket has all its parts and we have not filled
@@ -679,6 +682,9 @@
@set_modes:
sta Items::zp_pool_base, x
+ ;; Make the sound for grabbing an item.
+ SOUND_ITEM_PICKUP
+
;; Mark the player's to be already grabbing an item.
lda Items::zp_state
ora #$80
@@ -988,6 +994,9 @@
ADD_ITEM_SCORE
ldx Globals::zp_tmp2
+ ;; Make the sound for item pickup.
+ SOUND_ITEM_PICKUP
+
rts
.endproc
diff --git a/src/jetpac.s b/src/jetpac.s
index f2e28b2..a011a45 100644
--- a/src/jetpac.s
+++ b/src/jetpac.s
@@ -42,6 +42,7 @@
.include "assets.s"
.include "background.s"
.include "prng.s"
+.include "sound.s"
.include "explosions.s"
.include "score.s"
.include "items.s"
@@ -53,6 +54,14 @@
.include "driver.s"
.include "interrupts.s"
+;; Sanity check for some constant values. 'cl65' fails at this, so I'm only
+;; doing the check on 'nasm'.
+.ifdef __NASM__
+ .if Bullets::BULLET_TIMER_VALUE > Sound::BULLET_SFX_FRAME_COUNT
+ .error "Sound::BULLET_SFX_FRAME_COUNT must be smaller than Bullets::BULLET_TIMER_VALUE"
+ .endif
+.endif
+
;; Pretty standard reset function, nothing crazy.
.proc reset
;; Disable interrupts and decimal mode.
@@ -73,6 +82,9 @@
stx PPU::m_mask
stx APU::m_dmc
+ ;; Initialize the APU.
+ jsr Sound::init
+
;; First PPU wait.
bit PPU::m_status
@vblankwait1:
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