Add your description here
Project description
songtools
A Python DSL for composing music from code. Notes carry expression, structure carries time. No magic strings, no duplicate logic or state.
Install
uv add songtools
Requires Python 3.14+ and sounddevice (PortAudio backend).
Core idea
Sound knows timbre, never time. Tune owns relative time. play / compile own absolute time (bpm). Combine them with operators and you get a tiny, composable music language.
Abstractions
| Name | What it is |
|---|---|
Sound |
A voice (sample or synth). Knows timbre, never time. |
Key |
Harmonic context: root + scale + quality. |
Tune |
Anything arranged in time. Pure data — no methods that mutate. |
Layer |
Parallel voices. Shorter sides loop to LCM. |
compile() / play() |
The transport. Owns bpm and output. |
Operators
| Op | Meaning |
|---|---|
Tune(a, b, c) |
Slots of 1 beat; nesting subdivides |
Layer(a, b, c) |
Parallel voices; shorter sides loop to LCM |
a + b |
a, then b (sequence) |
a * n |
repeat |
x @ y |
realize: left plays right, in left's context |
None is not used — use REST for a rest inside a Tune.
Vocabulary
- Notes:
KeyRoot.C,KeyRoot.D, ... (chromatic, 0–11) - Scales:
Scale.MAJOR,Scale.MINOR,Scale.DORIAN - Qualities:
Quality.TRIAD,Quality.SEVENTH,Quality.NINTH,Quality.SUS2,Quality.SUS4,Quality.POWER - Degrees:
Degree.I,Degree.II, ...Degree.VII(also used as chord symbols)
Effects
Chain on any Sound with @:
| Effect | What it does |
|---|---|
Gain(amount) |
Multiply amplitude |
Decay(duration) |
Exponential fade (timedelta) |
LowPass(hertz) |
One-pole lowpass filter |
HighPass(hertz) |
One-pole highpass filter |
Echo(seconds) |
Single-tap delay at −6 dB |
Delay(seconds, fb, mix) |
Feedback delay with dry/wet blend |
Gate(seconds) |
Hard cut to length |
FadeIn(seconds) |
Linear ramp up from silence |
FadeOut(seconds) |
Linear ramp down to silence |
Reverse |
Reverse the buffer |
Drive(amount) |
Tanh saturation |
Clip(threshold) |
Hard-limit peaks |
Normalize(peak) |
Scale so the loudest sample = peak |
Tremolo(hertz, depth) |
Amplitude modulation |
BitCrush(bits) |
Reduce bit depth (lo-fi) |
Humanize(velocity) |
Subtle gain variation |
Quick start
1. Generate some sounds
python scripts/sounds.py # writes pluck.wav, kick.wav, snare.wav, hat.wav
2. Compose
from datetime import timedelta
from songtools.files import WavFile
from songtools.keys import Key
from songtools.layers import Layer
from songtools.sounds import REST, Sound
from songtools.tunes import Tune
from songtools.types import (
Decay,
Degree,
Echo,
Gain,
KeyRoot,
LowPass,
Pitch,
Quality,
Scale,
)
# --- Load or synthesize sounds ---
pluck = WavFile(Path("pluck.wav")).parse(Pitch(60))
kick = WavFile(Path("kick.wav")).parse(Pitch(60))
hat = WavFile(Path("hat.wav")).parse(Pitch(60))
# --- Shape them with effects ---
warm_pluck = pluck @ LowPass(1800) @ Decay(timedelta(milliseconds=900)) @ Gain(0.5)
hard_hat = hat @ Gain(0.3)
# --- Tune to a key ---
key = Key(KeyRoot.C, Scale.MAJOR, Quality.TRIAD)
melody = Tune(
warm_pluck @ key @ Degree.I,
warm_pluck @ key @ Degree.IV,
warm_pluck @ key @ Degree.V,
warm_pluck @ key @ Degree.I,
)
# --- Arrange ---
beat = Tune(hard_hat, REST, hard_hat, REST) * 4
bass = Tune(kick, REST, kick, REST) * 4
song = Layer(melody, beat, bass)
# --- Render ---
song.compile(beats_per_minute=120).play() # play live
WavFile(Path("out.wav")).save(song.compile(beats_per_minute=120)) # or save
See scripts/sounds.py and scripts/test.py for full working examples.
Rules of thumb
- Slot = 1 beat. Want it faster? Nest a
Tune. Slower? Spread withREST. Global speed? bpm. - Variations are data. Write them:
groove * 3 + fill. - Quality falls out of Key × degree. Never name a chord's quality — it emerges.
- Transpose a layer: change one
Keyconstant. Drums don't move. @chains left-to-right:sound @ key @ Degree.I, then add effects.
Project layout
src/songtools/
types.py # Buffer, Pitch, KeyRoot, Scale, Quality, Degree, effects
sounds.py # Sound, KeyedSound, REST
keys.py # Key (harmonic context)
tunes.py # Tune (sequential time)
layers.py # Layer (parallel time)
transports.py # mixdown — the bpm-aware renderer
files.py # WavFile — load/save 16-bit mono WAV
scripts/
sounds.py # Synthesize pluck, kick, snare, hat
test.py # Full song example
License
MIT
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 songtools-0.3.0.tar.gz.
File metadata
- Download URL: songtools-0.3.0.tar.gz
- Upload date:
- Size: 8.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3af0d8e020af3b128bf27d00382587187b42a560e818d70053db3b8d93de651
|
|
| MD5 |
bb160a06cbcde7496fd10dfae2a484a4
|
|
| BLAKE2b-256 |
cd018e3661720231d76f4174dc058fe73bea3415e565a4d7acf4f2e025b10734
|
File details
Details for the file songtools-0.3.0-py3-none-any.whl.
File metadata
- Download URL: songtools-0.3.0-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e12b37cbf77a4ab17a687fd589ee01f6ca3101b89e2a3f642c74ea1a47175ad
|
|
| MD5 |
b763cbce1a4447765d14b17f4cfe065f
|
|
| BLAKE2b-256 |
1cb5f7ea4c624b8f559203525f0f087db430901e31ed774161e4ab91b757417c
|