Make the five music tracks clearly distinct (0.5.2)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 23:06:11 +02:00
parent 0089ac0c27
commit 6625342163
13 changed files with 119 additions and 27 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -328,3 +328,5 @@ Ajouts à l'arborescence proposée :
**Tests** : catalogue (fichiers présents, aucun chemin libre), `AudioManager` (lecture, musique mémorisée), bus idempotents, bornes des volumes. **Non vérifié** : tout ce qui s'entend (timbre, niveau relatif musique/effets/voix, boucle sans claquement, agrément) ; vérifié seulement : durées et niveaux de crête des fichiers (aucun écrêtage).
**Addendum (0.5.1) — choix de la musique de fond** (demande utilisateur) : 5 morceaux au choix, `forest`, `marsh`, `meadow` (prairie), `river` (rivière), `night` (nuit), plus un choix « Automatique » (par défaut : forêt en CE1, marais en CE2, comme avant). Le choix est un réglage de l'appareil (`music_track` dans `user://settings.json`, valeur inconnue ramenée à « auto ») : le format des sauvegardes de profil est inchangé. Sélectionner un morceau dans les réglages le fait entendre aussitôt. Sons toujours provisoires et jamais écoutés ici.
**Correctif (0.5.2)** : les cinq morceaux de 0.5.1 étaient trop proches (même timbre de nappe + cloches, même niveau) : l'utilisateur n'a pas perçu de différence. Le changement de morceau fonctionnait (vérifié : identifiant, flux et bus corrects à chaque choix) ; seul le matériau sonore était indiscernable. Les morceaux sont désormais construits sur des styles distincts : forêt (nappe + cloches), marais (bourdon grave qui respire, rares cloches creuses), prairie (pizzicati allègres, basse sur le temps), rivière (arpège continu et lié), nuit (boîte à musique aiguë en 3 temps). Mesure objective : centroïde spectral de 220 à 1277 Hz, durée de boucle de 12 à 46 s, densité d'attaques de 3 à 36/s. Toujours jamais écouté ici.
+1 -1
View File
@@ -11,7 +11,7 @@ config_version=5
[application]
config/name="Les Aventuriers des mots"
config/version="0.5.1"
config/version="0.5.2"
run/main_scene="res://scenes/boot/boot.tscn"
config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg"
+116 -26
View File
@@ -138,7 +138,107 @@ def make_music(
return normalise(folded, 0.5)
def pluck(freq: float, duration: float, decay: float = 9.0) -> Signal:
"""Note pincee courte et sautillante (harmoniques qui tombent vite)."""
t = _time(duration)
tone = np.sin(2 * np.pi * freq * t) + 0.5 * np.sin(2 * np.pi * freq * 2 * t) * np.exp(-decay * t)
tone += 0.25 * np.sin(2 * np.pi * freq * 3 * t) * np.exp(-decay * 2 * t)
return tone * np.exp(-decay * t) * np.minimum(t / 0.003, 1.0)
def flow(freq: float, duration: float) -> Signal:
"""Note ronde et liee, a attaque douce : pour les arpeges continus."""
t = _time(duration)
tone = np.sin(2 * np.pi * freq * t) + 0.15 * np.sin(2 * np.pi * freq * 2 * t)
env = np.minimum(t / 0.03, 1.0) * np.exp(-2.2 * t)
return tone * env
def drone(freq: float, duration: float, tremolo_hz: float) -> Signal:
"""Bourdon grave et lent, avec un battement d'amplitude."""
t = _time(duration)
tone = np.sin(2 * np.pi * freq * t) + 0.4 * np.sin(2 * np.pi * freq * 1.5 * t)
wobble = 0.65 + 0.35 * np.sin(2 * np.pi * tremolo_hz * t)
env = np.minimum(t / 3.0, 1.0) * np.minimum((duration - t) / 3.0, 1.0)
return tone * wobble * np.clip(env, 0.0, 1.0)
def fold_loop(track: Signal, body_seconds: float) -> Signal:
"""Replie la queue sur le debut : la boucle ne claque pas."""
length = int(body_seconds * SAMPLE_RATE)
folded = track[:length].copy()
overflow = track[length:]
folded[: len(overflow)] += overflow
return folded
def make_arpeggio(
chords: list[list[str]],
pattern: list[int],
step: float,
voice: str,
bass: bool,
pad_gain: float,
) -> Signal:
"""Motif d'arpege repete sur chaque accord ; `pattern` indexe l'accord."""
steps_per_chord = len(pattern)
chord_seconds = steps_per_chord * step
body = chord_seconds * len(chords)
track = np.zeros(int((body + 3.0) * SAMPLE_RATE))
for index, chord in enumerate(chords):
start = index * chord_seconds
if pad_gain > 0:
for note in chord:
place(track, pad(hz(note), chord_seconds + 1.0), start, gain=pad_gain)
if bass:
place(track, pluck(hz(chord[0]) / 2, 1.2, decay=4.0), start, gain=0.9)
for position, chord_index in enumerate(pattern):
note = chord[chord_index % len(chord)]
octave_up = 2.0 if chord_index >= len(chord) else 1.0
freq = hz(note) * 2.0 * octave_up
sound = pluck(freq, 0.6) if voice == "pluck" else flow(freq, 1.2)
place(track, sound, start + position * step, gain=0.5)
return normalise(fold_loop(track, body), 0.5)
def make_drone_music(seed: int) -> Signal:
"""Marais : bourdon grave qui respire et rares notes de cloche creuses."""
rng = np.random.default_rng(seed)
body = 40.0
track = np.zeros(int((body + 4.0) * SAMPLE_RATE))
place(track, drone(hz("A1"), body + 4.0, 0.11), 0.0, gain=0.8)
place(track, drone(hz("E2"), body + 4.0, 0.17), 0.0, gain=0.5)
for start in np.arange(2.0, body - 3.0, 5.0):
note = ["A3", "C4", "E4", "G3"][int(rng.integers(4))]
place(track, bell(hz(note), 3.5, decay=1.6, brightness=0.15), float(start), gain=0.5)
return normalise(fold_loop(track, body), 0.5)
def make_music_box(seed: int) -> Signal:
"""Nuit : boite a musique aigue en 3 temps, tres lente, sur un lit grave."""
rng = np.random.default_rng(seed)
scale = ["C5", "D5", "E5", "G5", "A5", "C6", "E6"]
beat = 0.95
bars = 16
body = bars * 3 * beat
track = np.zeros(int((body + 3.0) * SAMPLE_RATE))
for bar in range(bars):
root = ["C3", "A2", "F2", "G2"][bar % 4]
place(track, pad(hz(root), 3 * beat + 1.0, attack=0.8), bar * 3 * beat, gain=0.3)
for count in range(3):
if count == 0 or rng.random() < 0.6:
note = scale[int(rng.integers(len(scale)))]
place(
track,
bell(hz(note), 1.8, decay=3.2, brightness=0.7),
(bar * 3 + count) * beat,
gain=0.55,
)
return normalise(fold_loop(track, body), 0.5)
def make_all_music() -> dict[str, Signal]:
"""Cinq ambiances nettement differentes (timbre, rythme, registre)."""
forest = make_music(
chords=[["C3", "G3", "E4"], ["A2", "E3", "C4"], ["F2", "C3", "A3"], ["G2", "D3", "B3"]] * 2,
melody_scale=["C5", "D5", "E5", "G5", "A5", "C6"],
@@ -146,34 +246,24 @@ def make_all_music() -> dict[str, Signal]:
seed=11,
melody_density=0.45,
)
marsh = make_music(
chords=[["A2", "E3", "C4"], ["F2", "C3", "A3"], ["D3", "A3", "F4"], ["E3", "B3", "G4"]] * 2,
melody_scale=["A4", "C5", "D5", "E5", "G5"],
seconds_per_chord=5.0,
seed=23,
melody_density=0.35,
marsh = make_drone_music(seed=23)
meadow = make_arpeggio( # prairie : pizzicati allegres, basse sur le temps
chords=[["G3", "B3", "D4"], ["E3", "G3", "B3"], ["C3", "E3", "G3"], ["D3", "F#3", "A3"]] * 2,
pattern=[0, 1, 2, 1, 0, 1, 2, 3],
step=0.25,
voice="pluck",
bass=True,
pad_gain=0.0,
)
meadow = make_music( # prairie : majeur lumineux, un peu plus vif
chords=[["G2", "D3", "B3"], ["E2", "B2", "G3"], ["C3", "G3", "E4"], ["D3", "A3", "F#4"]] * 2,
melody_scale=["G4", "A4", "B4", "D5", "E5", "G5"],
seconds_per_chord=3.5,
seed=37,
melody_density=0.55,
)
river = make_music( # riviere : mode dorien, melodie qui coule
chords=[["D3", "A3", "F4"], ["G2", "D3", "B3"], ["C3", "G3", "E4"], ["A2", "E3", "C4"]] * 2,
melody_scale=["D5", "E5", "F5", "A5", "C6", "A4"],
seconds_per_chord=3.0,
seed=41,
melody_density=0.65,
)
night = make_music( # nuit : berceuse lente et rare
chords=[["F2", "C3", "A3"], ["D3", "A3", "E4"], ["Bb2", "F3", "D4"], ["C3", "G3", "E4"]] * 2,
melody_scale=["F5", "G5", "A5", "C6", "E5"],
seconds_per_chord=6.0,
seed=53,
melody_density=0.25,
river = make_arpeggio( # riviere : arpege continu, lie, qui monte et descend
chords=[["D3", "A3", "F4"], ["G3", "D4", "B4"], ["C3", "G3", "E4"], ["A2", "E3", "C4"]] * 2,
pattern=[0, 1, 2, 3, 4, 3, 2, 1],
step=0.18,
voice="flow",
bass=False,
pad_gain=0.12,
)
night = make_music_box(seed=53)
return {"forest": forest, "marsh": marsh, "meadow": meadow, "river": river, "night": night}