Back to Outbreak: Soycoon City
SOURCE / PINNED RELEASE

Made of little things.

Outbreak: Soycoon City

Release
db7750b1d009…
Author-recorded commit
3ee3f5fd27a6…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b87c04571a9

Archive hash verified: dbebdc772b142a1c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

sounds/shotgun-revision/prepare-preview.py
"""Render the four runtime blast/pump pairs for a convenient listening pass."""
import array
import json
from pathlib import Path
import subprocess
import tempfile
import wave

ROOT = Path(__file__).resolve().parents[2]
manifest = json.loads((ROOT / 'sounds/runtime.json').read_text())
cues = {cue['id']: cue for cue in manifest['sounds']}
rate = 44100
mix = [0.] * (rate * 8)
for number in range(4):
    for cue_id, delay in [('shotgun_fire', 0), ('shotgun_pump', .25)]:
        cue = cues[cue_id]
        candidate = cue['files'][number % len(cue['files'])]
        pcm = subprocess.check_output(['ffmpeg', '-v', 'error', '-i', str(ROOT / candidate['path']),
                                       '-f', 's16le', '-ac', '1', '-ar', str(rate), '-'])
        samples = array.array('h', pcm)
        fade = max(1, round(rate * cue['mix']['fadeMs'] / 1000))
        gain = .85 * 10 ** (cue['mix']['gainDb'] / 20)
        start = round((number * 2 + delay) * rate)
        for frame, sample in enumerate(samples):
            envelope = min(1, frame / fade, (len(samples) - 1 - frame) / fade)
            mix[start + frame] += sample * gain * envelope
assert max(abs(sample) for sample in mix) < 32767
output = Path(__file__).resolve().parent / 'audition'
output.mkdir(exist_ok=True)
with tempfile.TemporaryDirectory(prefix='soyindent-shot-preview-') as directory:
    master = Path(directory) / 'preview.wav'
    with wave.open(str(master), 'wb') as wav:
        wav.setnchannels(1); wav.setsampwidth(2); wav.setframerate(rate)
        wav.writeframes(array.array('h', (round(sample) for sample in mix)).tobytes())
    subprocess.run(['ffmpeg', '-v', 'error', '-y', '-i', str(master), '-map_metadata', '-1',
                    '-codec:a', 'libmp3lame', '-b:a', '96k', str(output / 'shot-and-pump.mp3')], check=True)
print('Prepared four shot/pump pairs, 250 ms pump delay, two seconds between shots')