sfx.pyΒΆ
Part of PirateMaker.
1"""Synthesized sound effects for the PirateMaker port.
2
3The coin, hit and jump cues are generated with ``simvx.core.AudioSynth``
4rather than loaded from sample files. The upstream project ships those three
5effects without a stated licence, so reusing the files would put bytes of
6unknown provenance into a redistributable tree; a few oscillators and
7envelopes give the same arcade read with terms this port can actually state.
8
9The music beds are unaffected: both ``.ogg`` files carry explicit licences
10(see ``ATTRIBUTION.md``) and are still loaded from ``assets/audio/``.
11
12Each builder returns a baked :class:`~simvx.core.AudioClip`, so the caller
13holds plain sample data and can hand it to as many ``AudioPlayer`` nodes as it
14likes. Results are cached: the level rebuilds on every play/edit switch and
15re-synthesizing per switch would be wasted work.
16"""
17
18from __future__ import annotations
19
20from functools import cache
21
22from simvx.core import ADSR, AudioClip, AudioSynth, Exponential, LowPass, Oscillator
23
24# Equal-tempered reference, matching the convention in examples/demos/afterglow.
25_A4 = 440.0
26_SEMI = 2.0 ** (1.0 / 12.0)
27
28
29def _note(semitones_from_a4: float) -> float:
30 """Frequency of a note `semitones_from_a4` semitones above (or below) A4."""
31 return _A4 * (_SEMI**semitones_from_a4)
32
33
34@cache
35def coin() -> AudioClip:
36 """Bright two-note pickup chime, the classic ascending coin blip."""
37 s = AudioSynth()
38 s.add(Oscillator.square(_note(16)), gain=0.18, envelope=ADSR(decay=0.06, sustain=0.0, release=0.03))
39 s.add(
40 Oscillator.square(_note(23)),
41 gain=0.16,
42 envelope=ADSR(attack=0.05, decay=0.12, sustain=0.0, release=0.05),
43 )
44 return s.bake(duration=0.24)
45
46
47@cache
48def hit() -> AudioClip:
49 """Damage cue: a low filtered noise burst under a falling saw."""
50 s = AudioSynth()
51 s.add(
52 Oscillator.noise.white(seed=17),
53 gain=0.24,
54 envelope=ADSR(attack=0.001, decay=0.09, sustain=0.0, release=0.05),
55 filter=LowPass(900.0),
56 )
57 s.add(Oscillator.saw(_note(-9)), gain=0.20, envelope=Exponential(start=1.0, end=0.03))
58 return s.bake(duration=0.28)
59
60
61@cache
62def jump() -> AudioClip:
63 """Short upward blip. Triangle rather than square so a held run of jumps
64 does not turn buzzy."""
65 s = AudioSynth()
66 s.add(Oscillator.triangle(_note(4)), gain=0.26, envelope=ADSR(decay=0.08, sustain=0.0, release=0.04))
67 s.add(Oscillator.sine(_note(16)), gain=0.10, envelope=ADSR(decay=0.05, sustain=0.0, release=0.03))
68 return s.bake(duration=0.15)