A code-first audio studio: write -> render -> listen -> adjust.
Project description
shipwright-audio
A code-first audio studio for game and app sound. Define sounds in small Python functions, run one command, and get rendered audio files.
Requires Python 3.10 through 3.12.
Install
As a tool:
uv tool install shipwright-audio
# or
pipx install shipwright-audio
From a checkout:
uv sync
uv run shipwright --version
Quick Start
Create a project with one runnable starter sound:
shipwright init my_game_audio
cd my_game_audio
shipwright starter_blip
Generated layout:
my_game_audio/
shipwright.toml
.gitignore
sounds/
starter_blip.py
output/
.gitkeep
Use shipwright init . to initialize the current directory. Existing generated
files are not overwritten unless you pass --force.
CLI
shipwright.toml marks the project root. shipwright walks up from the current
directory to find it, loads the Python files in sounds/, and writes renders to
output/ (both relative to that root, so you can run it from a subdirectory).
Point it at a project elsewhere with -C/--project:
shipwright # list available sounds
shipwright starter_blip # render one sound
shipwright all # render every sound
shipwright all --flac --jobs 4 # parallel render with extra FLAC files
shipwright starter_blip --play # render and audition
shipwright --watch starter_blip # re-render on save
shipwright -C path/to/project all # render a project without cd-ing into it
Useful render flags:
shipwright ui_blip --out build/blip.wav --duration 0.4 --gain -3
shipwright sea_bed --stems --lufs -18
shipwright sea_bed --sr 48000 --ogg --flac --mp3
shipwright all --seed 1234 --jobs 0
shipwright --watch all
--jobs 0 uses the available CPU count. MP3 support depends on the local
libsndfile build used by soundfile.
Write Sounds
Each sound is a function decorated with @sound("name"). It returns either:
Buffer: raw NumPy samples for direct SFX synthesis.RenderSpec: MIDI tracks, audio tracks, sends/returns, and master FX.
Synthesized SFX
from shipwright import Buffer, dsp, sound
@sound("zap")
def zap():
sig = dsp.saw(220, 0.25)
sig = dsp.ad_env(sig, attack=0.001, release=0.2)
sig = dsp.lowpass(sig, 1200)
sig = dsp.normalize(sig, 0.9)
return Buffer(dsp.to_stereo(sig))
Sample-Based Audio
Put WAV/AIFF/FLAC files in your project and place them with AudioClip.
Audio tracks use the same mixer controls as MIDI tracks: gain_db, pan,
Faust fx, sends/returns, sidechain, and stems.
from shipwright import AudioClip, AudioTrack, RenderSpec, ReturnBus, Send, sound
from shipwright import instruments
@sound("hit_with_space")
def hit_with_space():
hit = AudioTrack(
clips=[
AudioClip("assets/hit.wav", start=0.0),
AudioClip("assets/hit.wav", start=0.35, gain_db=-8),
],
gain_db=-3,
pan=-0.2,
sends=[Send("verb", -12)],
name="hit",
)
return RenderSpec(
tracks=[hit],
returns=[ReturnBus("verb", fx=[instruments.reverb(0.35)])],
)
AudioClip.start and AudioClip.dur use the spec's time_unit; offset is
always seconds into the source file. Relative paths resolve from the project
root.
MIDI / Instrument Tracks
from shipwright import RenderSpec, Track, compose, instruments, sound
@sound("loop")
def loop():
bpm = 96
chords = compose.progression(
["Dm9", "Bbadd9", "F/C", "C7sus4"],
bpm=bpm,
beats_per_chord=4,
timing="beats",
)
pad = Track(instruments.soft_pad(), chords, gain_db=-8, pan=-0.2)
return RenderSpec(
tracks=[pad],
tempo=bpm,
time_unit="beats",
master_fx=[instruments.reverb(0.3)],
)
See examples/music_sea_bed.py and
examples/sfx_ui_blip.py for complete examples.
Composition
shipwright.compose includes lightweight theory helpers:
- Chords:
dim,aug,6,9/11/13,add9, suspended chords, slash chords. - Scales and keys, including quantize-to-scale.
- Swing and humanization.
- Time signatures and beat/second conversion.
- Microtonal tuning (any n-EDO or just-intonation ratio set).
- Optional MIDI import/export through the
midiextra.
meter = (3, 4)
start = compose.bar_start(2, meter)
notes = compose.melody([60, 62, 63, 67], bpm=120, start_beat=start, timing="beats")
notes = compose.apply_groove(notes, swing=0.25, time_signature=meter, timing="beats")
notes = compose.read_midi("riff.mid")
compose.write_midi("out.mid", notes, bpm=120)
Microtonal
Note.pitch is a float (fractional MIDI = microtonal), and the numpy
@instrument path renders any frequency, so microtonality works end to end
without MIDI's integer limit. (Faust / SoundFont / VST instruments still go
through integer MIDI note-on, so use a numpy instrument for microtonal timbres.)
A tuning is a list of cents-above-the-root; edo() and just() build common
ones, tuned_scale() turns one into pitches, and quantize_tuning() snaps to it.
notes = compose.melody([60, 60.5, 61, 61.5], bpm=96, timing="beats") # quarter-tones
quarter = compose.edo(24) # 24 equal divisions of the octave
pitches = compose.tuned_scale(quarter, root=60, octaves=2)
snapped = compose.quantize_tuning_notes(notes, quarter, root=60)
ji = compose.just([1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8]) # just-intonation major
notes = compose.quantize_tuning_notes(notes, ji, root=60)
Instruments
Built-in Faust instruments need no external files:
instruments.pluck()
instruments.saw_lead()
instruments.soft_pad()
instruments.sub_bass()
Custom Instruments
The simplest way to write your own instrument is the @instrument decorator: a
per-note (freq, dur, vel) -> samples function built from the same dsp
vocabulary you use for SFX. It is called once per note and the voices are summed,
so polyphony, chords, and overlapping notes just work. The result drops onto a
Track like any built-in.
from shipwright import Track, RenderSpec, compose, dsp, instrument, sound
@instrument("organ")
def organ(freq, dur, vel):
sig = dsp.saw(freq, dur, amp=vel / 127)
sig = dsp.ad_env(sig, attack=0.01, release=0.25)
return dsp.lowpass(sig, 3000)
@sound("riff")
def riff():
notes = compose.melody([62, 65, 69, 65], bpm=96, timing="beats")
return RenderSpec([Track(organ, notes)], tempo=96, time_unit="beats")
Define instruments inline next to a sound, or in a shared module (e.g.
my_instruments.py) you import — the project root is on the import path.
For instruments backed by external files or DSP languages:
- Faust: build one with
Instrument.faust("name", dsp_string, voices)(see the built-ins for thefreq/gain/gateconvention). - SoundFont: install the
soundfontextra, create asoundfonts/folder in your project, drop.sf2files in it, and useinstruments.soundfont("Piano")orinstruments.soundfont("piano", "/path/to/file.sf2", preset=0). - VST/AU: use
instruments.plugin("name", "/path/to/plugin.vst3").
Mixing And Export
Tracks support gain_db, pan, per-track Faust fx, sends, sidechain ducking,
and stem export.
from shipwright import ReturnBus, Send, Track
pad = Track(
instruments.soft_pad(),
notes,
gain_db=-8,
pan=-0.2,
sends=[Send("verb", -14)],
)
return RenderSpec(
tracks=[pad],
returns=[ReturnBus("verb", fx=[instruments.reverb(0.4)], gain_db=-3)],
)
Exports are peak-limited, can normalize to a LUFS target with --lufs, and
dither 16-bit output by default.
Effects
Track fx, return-bus fx, and master_fx take Faust strings (streaming,
time-domain) — instruments.reverb() is one. For frequency-space / FFT work the
streaming graph can't do, write a numpy effect with @effect: an
(audio, sr) -> audio function that runs offline on the rendered stereo audio.
Drop it into a track's fx or master_fx next to Faust strings.
import numpy as np
from shipwright import Track, effect
@effect
def lowpass_blur(audio, sr):
spec = np.fft.rfft(audio, axis=0)
freqs = np.fft.rfftfreq(len(audio), 1 / sr)
spec[freqs > 2000] *= 0.2
return np.fft.irfft(spec, n=len(audio), axis=0)
pad = Track(organ, notes, fx=[lowpass_blur])
The dsp module ships ready-made frequency-space effects you can call from a
numpy @effect or directly in a Buffer SFX: dsp.spectral_gate,
dsp.spectral_filter, and dsp.convolve_reverb.
Because a numpy effect needs the whole signal, it runs after a track's Faust
chain (not interleaved per-sample), and it isn't supported on return buses — use
Faust there, or apply the effect on a track or in master_fx.
Project Config
A project is configured through its shipwright.toml:
[shipwright]
sr = 48000
block = 512
master_ceiling = 0.97
target_lufs = -18
export_subtype = "PCM_16"
dither = true
sounds_dir = "sounds"
soundfont_dir = "soundfonts"
output_dir = "output"
Two environment variables override discovery as escape hatches:
SHIPWRIGHT_ROOT— use this directory as the project root instead of walking up for ashipwright.toml.SHIPWRIGHT_SOUNDS— load sounds from here (used to run the bundledexamples/).
Architecture
| Layer | File | Job |
|---|---|---|
| Composition | shipwright/compose.py |
chord symbols / pitch lists to Notes |
| Sound sources | shipwright/instruments.py |
Faust, SoundFont, and plugin instruments |
| SFX synthesis | shipwright/dsp.py |
NumPy oscillators, noise, envelopes, filters |
| Engine | shipwright/engine.py |
DawDreamer graph, mix, FX, offline render |
DawDreamer hosts plugins and renders deterministically offline. It does not compose and ships no instruments, so Shipwright keeps composition and sound source helpers above the engine.
Development
uv run --extra dev pytest
env SHIPWRIGHT_SOUNDS=examples uv run shipwright ui_blip
env SHIPWRIGHT_SOUNDS=examples uv run shipwright sea_bed
License
shipwright-audio is MIT licensed. Its DawDreamer dependency is GPLv3; review
that license before redistributing bundled applications or generated tooling.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shipwright_audio-0.2.0.tar.gz.
File metadata
- Download URL: shipwright_audio-0.2.0.tar.gz
- Upload date:
- Size: 31.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5029d6dfc0698a76c8642944c50720bd942dfa254a0385bc5ec722a850f4c4c
|
|
| MD5 |
df16f5a339540ba4cf5fae4e44bab46d
|
|
| BLAKE2b-256 |
44a257379392d004e6be4ddaa838fa009f78a2c6dc82a39eaa4be32a025b748a
|
Provenance
The following attestation bundles were made for shipwright_audio-0.2.0.tar.gz:
Publisher:
publish.yml on dinger086/shipwright-audio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shipwright_audio-0.2.0.tar.gz -
Subject digest:
e5029d6dfc0698a76c8642944c50720bd942dfa254a0385bc5ec722a850f4c4c - Sigstore transparency entry: 1999521460
- Sigstore integration time:
-
Permalink:
dinger086/shipwright-audio@7d38dbf939a1357c4223c5f2dbeaba27d36db877 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/dinger086
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d38dbf939a1357c4223c5f2dbeaba27d36db877 -
Trigger Event:
release
-
Statement type:
File details
Details for the file shipwright_audio-0.2.0-py3-none-any.whl.
File metadata
- Download URL: shipwright_audio-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27ac290bb0274615ec9f1476ca7e1d66cd6caaa5e94376c05be0e6b6591c498e
|
|
| MD5 |
ec3709a152522798cb2c37be05da3837
|
|
| BLAKE2b-256 |
34a67ac4d3223c612b19d28f97453ade8e1cfa98b3197d75af76abfad4e1fd21
|
Provenance
The following attestation bundles were made for shipwright_audio-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on dinger086/shipwright-audio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shipwright_audio-0.2.0-py3-none-any.whl -
Subject digest:
27ac290bb0274615ec9f1476ca7e1d66cd6caaa5e94376c05be0e6b6591c498e - Sigstore transparency entry: 1999521551
- Sigstore integration time:
-
Permalink:
dinger086/shipwright-audio@7d38dbf939a1357c4223c5f2dbeaba27d36db877 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/dinger086
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d38dbf939a1357c4223c5f2dbeaba27d36db877 -
Trigger Event:
release
-
Statement type: