Standalone pi policy gate extension package
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

80 lines
2.3 KiB

#!/usr/bin/env python3
from __future__ import annotations
import math
import wave
from pathlib import Path
SAMPLE_RATE = 44100
PROFILES = {
"confirm": {
"amplitude": 0.28,
"segments": [
(80, [660, 990]),
(35, []),
(95, [880, 1320]),
(35, []),
(130, [1174, 1568]),
],
},
"block": {
"amplitude": 0.42,
"segments": [
(120, [392, 587]),
(36, []),
(130, [294, 440]),
(36, []),
(190, [196, 294]),
],
},
"refine": {
"amplitude": 0.30,
"segments": [
(95, [587, 784]),
(32, []),
(95, [659, 880]),
(32, []),
(95, [587, 784]),
],
},
}
def synth_segment(duration_ms: int, freqs: list[int], amplitude: float) -> list[int]:
samples = round((duration_ms / 1000) * SAMPLE_RATE)
data: list[int] = []
for i in range(samples):
sample = 0.0
if freqs:
t = i / SAMPLE_RATE
attack = min(1.0, i / max(1, int(samples * 0.1)))
release = min(1.0, (samples - i) / max(1, int(samples * 0.12)))
envelope = min(attack, release)
sample = sum(math.sin(2 * math.pi * freq * t) for freq in freqs) / len(freqs)
sample *= amplitude * envelope
data.append(int(max(-1.0, min(1.0, sample)) * 32767))
return data
def write_wave(path: Path, profile: dict[str, object]) -> None:
amplitude = float(profile["amplitude"])
segments = profile["segments"]
pcm: list[int] = []
for duration_ms, freqs in segments: # type: ignore[misc]
pcm.extend(synth_segment(duration_ms, list(freqs), amplitude))
path.parent.mkdir(parents=True, exist_ok=True)
with wave.open(str(path), "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(SAMPLE_RATE)
wav.writeframes(b"".join(sample.to_bytes(2, "little", signed=True) for sample in pcm))
if __name__ == "__main__":
root = Path(__file__).resolve().parent.parent
assets = root / "assets"
for name, profile in PROFILES.items():
write_wave(assets / f"{name}.wav", profile)
print(f"wrote {assets / f'{name}.wav'}")