mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Play the launcher's sounds at Low by default.
A new launcher.json starts with sounds at low instead of off, and a value the file cannot hold falls back to low too (launcher/ui/sound.py DEFAULT_LEVEL). The player can still pick Off, Medium or High in Customize Launcher. The sounds are still only the game's select and back sounds, read from the player's own game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,7 +47,7 @@ Customize Launcher (on the home screen) makes the look the player's own:
|
||||
- Animations (on by default): the cursor glides from row to row, a new screen slides into place, sparks drift up
|
||||
over the backdrop and Navi bobs in the title band with a pulsing halo (`App.animate`). They move by the time that
|
||||
passed, stand still while the window is hidden or minimised, and Off stops them all.
|
||||
- Sounds (off by default; Low, Medium or High): two of the game's own interface sounds, the decide and cancel sounds
|
||||
- Sounds (Low by default; Off, Medium or High): two of the game's own interface sounds, the decide and cancel sounds
|
||||
of its menus, when a row is chosen or a choice changes and when the player goes back. The launcher decodes them
|
||||
from the player's built game or dump into `data/cache/sounds` (`gamefiles.extract_sounds`:
|
||||
`Sound/Resource/Stream/SE_Sys_Do00.bwav` and `SE_Sys_Cancel00.bwav`, the files `runtime/menu_sound.c` plays in the
|
||||
@@ -377,7 +377,7 @@ pruning, unsafe archives, the background update (a release staged beside the run
|
||||
`staged.json` marker, a staged release installed after a restart, no install while the game runs, the install once
|
||||
it exited, nothing by itself with Install Updates Automatically off, a failed staging tried again after the next
|
||||
check), the font decoding on a system font wrapped like the game's, the look (every colour scheme complete, the
|
||||
background picture cut to the window's shape and kept, the sound effects as WAVs and off by default), `make_game.py`
|
||||
background picture cut to the window's shape and kept, the sound effects as WAVs and low by default), `make_game.py`
|
||||
on a synthetic
|
||||
`.nup` (events, skipped steps, `--rebuild` without the dump, step keys, the shaders key following the translator the
|
||||
build uses), the RomFS step beside the lift and compile
|
||||
|
||||
@@ -9,7 +9,7 @@ Draft notes for the release after 0.6.1. The version number is not decided yet.
|
||||
it takes effect at the next start.
|
||||
- Customize Launcher (on the launcher's home screen): six colour schemes, a background picture of your own with
|
||||
adjustable dimming, animations (a gliding cursor, sliding screens, drifting sparks) and sound effects: the game's
|
||||
own select and back sounds, read from your game. The sounds are off until you turn them on.
|
||||
own select and back sounds, read from your game, quiet (Low) unless you pick another loudness or turn them off.
|
||||
- Three more settings take effect without a restart: HDR between off and the tone mapper (only the game's native
|
||||
HDR still waits for one), the camera's stick sensitivity, and the speakers (stereo or 5.1).
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ DEFAULTS = {
|
||||
'background': '', # the picture the player chose as the background; its fitted copy is data/background.npy
|
||||
'background_dim': 40, # how much that picture is darkened, in percent
|
||||
'animations': True, # the cursor glides, screens slide in, sparks drift, Navi bobs
|
||||
'sounds': 'off', # the chimes: off (until the player turns them on), low, medium or high
|
||||
'sounds': 'low', # the game's select and back sounds: off, low (sound.py DEFAULT_LEVEL), medium, high
|
||||
'feed_repo': '', # owner/name overriding feed.json, for testing a fork's releases
|
||||
'github_token': '', # only for a private release repository; stored as plain text
|
||||
'last_check': 0.0,
|
||||
|
||||
@@ -918,7 +918,7 @@ class App:
|
||||
def sounds(d):
|
||||
keys = [k for k, _ in SOUND_LEVELS]
|
||||
now = self.settings.get('sounds')
|
||||
at = keys.index(now) if now in keys else 0
|
||||
at = keys.index(now if now in keys else sound.DEFAULT_LEVEL)
|
||||
self.settings['sounds'] = keys[max(0, min(len(keys) - 1, at + d))]
|
||||
self.settings.save()
|
||||
self.sounds.set_level(self.settings['sounds'])
|
||||
@@ -946,9 +946,10 @@ class App:
|
||||
Row('divider'),
|
||||
Row('toggle', 'Animations', on=animations, get=lambda: bool(self.settings.get('animations')),
|
||||
desc=('A gliding cursor, sliding screens and drifting sparks.',)),
|
||||
Row('enum', 'Sounds', get=lambda: dict(SOUND_LEVELS).get(self.settings.get('sounds'), 'Off'),
|
||||
Row('enum', 'Sounds', get=lambda: dict(SOUND_LEVELS).get(self.settings.get('sounds'),
|
||||
dict(SOUND_LEVELS)[sound.DEFAULT_LEVEL]),
|
||||
left=lambda: sounds(-1), right=lambda: sounds(1),
|
||||
desc=('The game\'s select and back sounds. Off by default.',)),
|
||||
desc=('The game\'s select and back sounds. Low by default.',)),
|
||||
Row('divider'),
|
||||
Row('action', 'Back', on=lambda: self.show('home', self.home_row('Customize Launcher')), height=40,
|
||||
desc=('Back to the main menu.',)),
|
||||
|
||||
@@ -3,11 +3,11 @@ going back, read from the player's game (launcher/gamefiles.py extract_sounds de
|
||||
runtime/menu_sound.c plays them in the in-game menu). No sound ships with the launcher and none is made here: until
|
||||
the player's game has been read, and for a sound it does not hold, the launcher is silent.
|
||||
|
||||
They are off until the player picks a loudness in Customize Launcher (the Sounds row). An effect is the game's sound
|
||||
as a 16-bit WAV in memory, scaled to the loudness. Windows plays it with PlaySound from that memory; elsewhere the WAV
|
||||
goes to a file in the cache folder and the first of pw-play, paplay and aplay found plays it. One effect sounds at a
|
||||
time: a new one cuts the last. Moving the cursor makes no sound, as it would cut a choice's sound short every time
|
||||
the pointer lands on a row of the next screen.
|
||||
They play at Low until the player picks another loudness, or Off, in Customize Launcher (the Sounds row). An effect
|
||||
is the game's sound as a 16-bit WAV in memory, scaled to the loudness. Windows plays it with PlaySound from that
|
||||
memory; elsewhere the WAV goes to a file in the cache folder and the first of pw-play, paplay and aplay found plays
|
||||
it. One effect sounds at a time: a new one cuts the last. Moving the cursor makes no sound, as it would cut a
|
||||
choice's sound short every time the pointer lands on a row of the next screen.
|
||||
|
||||
select a row was chosen, a switch or a choice changed, Play, the end of a build the game's decide sound
|
||||
back back to the screen before, something could not be done the game's cancel sound
|
||||
@@ -21,6 +21,7 @@ import subprocess
|
||||
import numpy as np
|
||||
|
||||
LEVELS = {'off': 0.0, 'low': 0.18, 'medium': 0.4, 'high': 0.75} # the Sounds choice: the loudest sample's share of full scale
|
||||
DEFAULT_LEVEL = 'low' # a new launcher.json's (launcher/settings.py DEFAULTS), also for a value it cannot hold
|
||||
EFFECTS = ('select', 'back') # the names play takes: the game's sounds of gamefiles.SOUNDS
|
||||
|
||||
|
||||
@@ -50,10 +51,10 @@ class Sounds:
|
||||
"""Plays the effects at the level of the Sounds choice. Every failure is silent: a PC without a sound device, or
|
||||
without a player program, just has a quiet launcher."""
|
||||
|
||||
def __init__(self, cache_dir, level='off', log=None):
|
||||
def __init__(self, cache_dir, level=DEFAULT_LEVEL, log=None):
|
||||
self.cache = cache_dir
|
||||
self.log = log or (lambda line: None)
|
||||
self.level = None # set_level below: 'off' unless the player chose a loudness
|
||||
self.level = None # set_level below: DEFAULT_LEVEL unless the player chose another
|
||||
self.game = {} # the game's sounds decoded from the player's files: name -> (rate, channels, samples)
|
||||
self.wavs = {} # name -> WAV bytes at the current level (Windows plays from this memory: it must stay)
|
||||
self.files = {} # name -> the WAV file elsewhere
|
||||
@@ -88,7 +89,7 @@ class Sounds:
|
||||
return bool(game)
|
||||
|
||||
def set_level(self, level):
|
||||
level = level if level in LEVELS else 'off'
|
||||
level = level if level in LEVELS else DEFAULT_LEVEL
|
||||
if level == self.level:
|
||||
return
|
||||
self.stop()
|
||||
|
||||
@@ -18,7 +18,7 @@ Run: python tests/launcher_test.py (Python 3.14 for the zstd tests; the window
|
||||
a source checkout's build/xlate/), the prompts step with a stand-in prompt tool, the object cache of the compile
|
||||
step with the system's C compiler, and its windres call under folders with spaces (a script in windres's place)
|
||||
- jobs: an install driven by a fake make_game.py, with and without the prompts row
|
||||
- the look: the colour schemes, the background picture's fitting and kept copy, the sound effects (off by default,
|
||||
- the look: the colour schemes, the background picture's fitting and kept copy, the sound effects (low by default,
|
||||
only the game's own select and back sounds, decoded from a made-up .bwav)
|
||||
- the window: --selftest draws every screen (skipped without a display)
|
||||
"""
|
||||
@@ -2599,9 +2599,10 @@ class LookTest(Tmp):
|
||||
self.assertAlmostEqual(peak / 32767, sound.LEVELS['high'], delta=0.01)
|
||||
self.assertFalse(sound.Sounds(self.tmp / 'empty', 'high').game)
|
||||
|
||||
def test_only_the_games_sounds_play_and_only_once_turned_on(self):
|
||||
def test_only_the_games_sounds_play_at_low_unless_set(self):
|
||||
sound = self.sound
|
||||
self.assertEqual(settings.DEFAULTS['sounds'], 'off')
|
||||
self.assertEqual(settings.DEFAULTS['sounds'], 'low')
|
||||
self.assertEqual(sound.DEFAULT_LEVEL, 'low')
|
||||
self.assertEqual(set(gamefiles.SOUNDS), set(sound.EFFECTS))
|
||||
empty = self.tmp / 'empty'
|
||||
self.game_sounds()
|
||||
@@ -2616,27 +2617,29 @@ class LookTest(Tmp):
|
||||
self.assertTrue(quiet.load_game()) # the game's sounds, once decoded
|
||||
quiet.play('select')
|
||||
self.assertEqual(popen.call_count, 1)
|
||||
s = sound.Sounds(self.layout.cache) # no level given, as a new launcher.json has it
|
||||
s = sound.Sounds(self.layout.cache) # no level given, as a new launcher.json has it: low
|
||||
self.assertEqual(s.level, 'low')
|
||||
s.play('select')
|
||||
s.set_level('nonsense') # a level launcher.json cannot hold: off
|
||||
self.assertEqual(popen.call_count, 2)
|
||||
s.set_level('off') # the player turned them off
|
||||
s.play('select')
|
||||
self.assertEqual(popen.call_count, 1)
|
||||
s.set_level('low')
|
||||
self.assertEqual(popen.call_count, 2)
|
||||
s.set_level('nonsense') # a level launcher.json cannot hold: the default
|
||||
self.assertEqual(s.level, 'low')
|
||||
s.play('back')
|
||||
s.play('move') # the cursor has no sound
|
||||
s.play('no such effect')
|
||||
self.assertEqual(popen.call_count, 2)
|
||||
self.assertEqual(popen.call_count, 3)
|
||||
with open(popen.call_args[0][0][1], 'rb') as f:
|
||||
self.assertEqual(f.read(4), b'RIFF')
|
||||
s.set_level('off')
|
||||
s.play('select')
|
||||
self.assertEqual(popen.call_count, 2)
|
||||
peak = max(abs(v) for v in struct.unpack_from('<8h', s.wav('back'), 44))
|
||||
self.assertAlmostEqual(peak / 32767, sound.LEVELS['low'], delta=0.01)
|
||||
|
||||
def test_the_look_settings_survive_and_fall_back(self):
|
||||
path = self.tmp / 'launcher.json'
|
||||
s = settings.Settings(path)
|
||||
self.assertEqual((s['theme'], s['background'], s['background_dim'], s['animations'], s['sounds']),
|
||||
('kokiri', '', 40, True, 'off'))
|
||||
('kokiri', '', 40, True, 'low'))
|
||||
s['theme'], s['sounds'], s['background_dim'] = 'shadow', 'high', 60
|
||||
s.save()
|
||||
t = settings.Settings(path)
|
||||
|
||||
Reference in New Issue
Block a user