pikachu volume

This commit is contained in:
bryanthaboi
2026-07-30 11:20:21 -04:00
parent 4b9a326f6b
commit 0af69e2df6
5 changed files with 83 additions and 12 deletions
+5
View File
@@ -193,6 +193,11 @@ flushes the live options. Old saves that still embed an `options` table are
migrated once into `options.lua` on load.
- Music / SFX volume
- PIKACHU VOL (0-7, Yellow only): trims Pikachu's PCM voice clips under the
SFX level, so the follower's constant chatter, the title-screen cry and
every in-battle "Pika!" can be pulled down (or muted at 0) without
quieting the rest of the sound effects. The row is hidden on Red/Blue,
which have no voice clips.
- Music Filter
- OG GLITCHES on / off (Gen 1 quirks vs. modern-clean battle rules)
- BATTLE LAYOUT (OG / WIDE); see "Widescreen battle layout" below
+3
View File
@@ -223,6 +223,9 @@ function SaveData.defaultOptions()
-- 0-7 like the GB's NR50 master volume
musicVol = 7,
sfxVol = 7,
-- Yellow only: 0-7 trim for Pikachu's PCM voice clips on top of sfxVol
-- (the PIKACHU VOL row appears only on Yellow; see Sound.lua)
pikaVol = 7,
musicFilter = 0,
-- logic fast-forward multiplier; audio is unaffected (GameSpeed.lua)
speed = 1,
+47 -10
View File
@@ -16,6 +16,30 @@ local cache = {}
-- scaling the 0.8 base every source gets
local BASE_VOLUME = 0.8
local volumeScale = 1
-- port addition (Yellow only): 0-7 trim from save.options.pikaVol on top of
-- the SFX level, for Pikachu's voice clips alone. Yellow voices every
-- Pikachu cry with PCM samples that are far louder and far more frequent
-- than the chip cries around them (the follower alone talks on every
-- interaction), so this is the one source players want to pull down
-- without muting the rest of the SFX bus. 7 = untouched, 0 = silent.
local pikaScale = 1
-- cache keys whose volume the Pikachu trim applies to: the PCM clips, plus
-- the chip PIKACHU cry that a Yellow cache without extracted clips falls
-- back to (playCry). Red/Blue never reach the second branch, so a shared
-- options.lua carrying a low pikaVol cannot quiet their Pikachu.
local function isPikaKey(key)
if type(key) ~= "string" then return false end
if key:sub(1, 8) == "pikacry:" then return true end
return key == "cry:PIKACHU"
and require("src.core.GameVersion").isYellow()
end
local function volumeFor(key)
local scale = volumeScale
if isPikaKey(key) then scale = scale * pikaScale end
return BASE_VOLUME * scale
end
-- Fanfares occupy the music's tone channels on the Game Boy: their sfx
-- headers claim channels 5-7 (= hardware channels 1-3), silencing the
@@ -91,7 +115,7 @@ local function playPath(data, key, def, pitch, tempo)
reportBadDef("sfx", key, owner(data, "sfx", key), err)
return nil
end
s:setVolume(BASE_VOLUME * volumeScale)
s:setVolume(volumeFor(key))
cache[key] = s
src = s
end
@@ -215,7 +239,7 @@ function Sound.playPikaCry(data, n)
cache[key] = false
return nil
end
s:setVolume(BASE_VOLUME * volumeScale)
s:setVolume(volumeFor(key))
cache[key] = s
src = s
end
@@ -249,7 +273,7 @@ function Sound.playCry(data, species)
owner(data, "cries", species), err)
return nil
end
s:setVolume(BASE_VOLUME * volumeScale)
s:setVolume(volumeFor(key))
cache[key] = s
src = s
end
@@ -325,7 +349,7 @@ function Sound.startLoop(data, name)
return
end
s:setLooping(true)
s:setVolume(BASE_VOLUME * volumeScale)
s:setVolume(volumeFor(name))
loopCache[name] = s
src = s
end
@@ -349,14 +373,26 @@ end
-- 0-7 SFX volume level (0 mutes); cached sources (menu beeps, cries,
-- the low-health alarm loop) update immediately so the change is heard
-- on the next play
local function reapplyVolumes()
for key, src in pairs(cache) do
if src then pcall(src.setVolume, src, volumeFor(key)) end
end
for key, src in pairs(loopCache) do
if src then pcall(src.setVolume, src, volumeFor(key)) end
end
end
function Sound.setVolumeLevel(level)
volumeScale = math.max(0, math.min(7, level or 7)) / 7
for _, src in pairs(cache) do
if src then pcall(src.setVolume, src, BASE_VOLUME * volumeScale) end
end
for _, src in pairs(loopCache) do
if src then pcall(src.setVolume, src, BASE_VOLUME * volumeScale) end
end
reapplyVolumes()
end
-- 0-7 Pikachu-voice trim on top of the SFX level (7 = no trim, 0 mutes the
-- clips while the rest of the SFX bus keeps its level). Yellow only: on
-- Red/Blue no cached key answers isPikaKey, so this is inert there.
function Sound.setPikaVolumeLevel(level)
pikaScale = math.max(0, math.min(7, level or 7)) / 7
reapplyVolumes()
end
-- hot reload / jukebox A-B: drop one key's sources (its pitch-tempo
@@ -390,6 +426,7 @@ Assets.register(Sound.invalidate)
-- loading a save)
function Sound.applyOptions(opts)
Sound.setVolumeLevel(opts and opts.sfxVol or 7)
Sound.setPikaVolumeLevel(opts and opts.pikaVol or 7)
end
return Sound
+2 -1
View File
@@ -153,7 +153,8 @@ local function defaultsSave()
options = {
textSpeed = 3, animations = true, battleStyle = "shift",
battleLayout = "og",
ruleset = "gen1_faithful", musicVol = 7, sfxVol = 7, musicFilter = 0,
ruleset = "gen1_faithful", musicVol = 7, sfxVol = 7, pikaVol = 7,
musicFilter = 0,
speed = 1, colors = "gbc", tilt = 0, gbcfx = 0,
videoMode = "windowed", mods = {},
},
+26 -1
View File
@@ -2,7 +2,8 @@
-- (engine/menus/main_menu.asm DisplayOptionMenu), the battle ruleset
-- (cycles the merged rulesets registry; gen1_faithful keeps the original
-- quirks), plus the port's audio rows and display rows: music/SFX
-- volume (0-7), music low-pass filter (OFF/1X/2X/3X), COLORS / TILT /
-- volume (0-7), PIKACHU VOL (0-7, Yellow only: trims the PCM voice clips
-- under SFX VOL), music low-pass filter (OFF/1X/2X/3X), COLORS / TILT /
-- GBC FX / ZOOM / VOID FILL / VIDEO MODE, and the MODS row that opens
-- the mod manager.
-- Rows are descriptors fed through the ui.options.rows hook, so mods can
@@ -16,6 +17,7 @@ local GBCFX = require("src.render.GBCFX")
local Zoom = require("src.render.Zoom")
local TileRenderer = require("src.render.TileRenderer")
local GameSpeed = require("src.core.GameSpeed")
local GameVersion = require("src.core.GameVersion")
local VideoMode = require("src.core.VideoMode")
local FrameCap = require("src.core.FrameCap")
local Logger = require("src.core.Logger")
@@ -178,6 +180,18 @@ local function buildRows(game)
require("src.core.Sound").setVolumeLevel(o.sfxVol)
return true
end },
-- Yellow only (filtered out below on Red/Blue): trims Pikachu's voice
-- clips under the SFX level, since Yellow plays them constantly (every
-- follower interaction, the title screen, the dex) and they are mixed
-- much hotter than the chip cries.
{ id = "pikaVol", label = Strings("PIKACHU VOL"),
value = function(g) return volLabel(g.save.options.pikaVol) end,
step = function(g, dir)
local o = g.save.options
o.pikaVol = stepVolume(o.pikaVol, dir)
require("src.core.Sound").setPikaVolumeLevel(o.pikaVol)
return true
end },
{ id = "musicFilter", label = Strings("MUSIC FILTER"),
value = function(g)
return FILTERS[(g.save.options.musicFilter or 0) + 1]
@@ -317,6 +331,17 @@ local function buildRows(game)
end
rows = filtered
end
-- PIKACHU VOL only means something where the voice clips exist: Yellow
-- (data.audio.pikaCries is the clip count the importer wrote). Red/Blue
-- keep the row list they always had.
local audio = game and game.data and game.data.audio
if not (GameVersion.isYellow() and audio and audio.pikaCries) then
local filtered = {}
for _, row in ipairs(rows) do
if row.id ~= "pikaVol" then filtered[#filtered + 1] = row end
end
rows = filtered
end
-- A mod's render pipelines are display modes like TILT, so their rows sit
-- with it rather than at the end of the list where a mod's own
-- ui.options.rows additions land. Nothing registered means nothing