Algorithmic trip-hop music generator inspired by Wax Tailor
WaxEngine is a Python program that composes and renders original downtempo/cinematic hip-hop instrumentals. It combines music theory rules, genre-specific production techniques, and layered audio processing to produce full tracks -- no samples, no AI models, just code that understands the groove.
WaxEngine generates tracks with these characteristics:
- Dusty boom-bap drums with MPC-style swing and ghost notes
- Jazz-informed harmony -- minor 7th chords, chromatic bass walks, modal interchange
- Cinematic orchestration -- strings, horns, and harp layered under the beat
- Rhodes and vibraphone melodies with pentatonic phrasing
- Vinyl warmth -- crackle, tape wobble, and lo-fi filtering baked in
- Dynamic arrangements that build, breathe, and resolve like a film score
Tempo range: 75-95 BPM. Duration: 3-5 minutes per track. Every generation is unique.
- Python 3.11+
- FluidSynth (
apt install fluidsynth libfluidsynth-devorbrew install fluid-synth) - ffmpeg (for MP3 export)
- libxcb-cursor0 (Linux only, for GUI:
apt install libxcb-cursor0) - A General MIDI SoundFont (.sf2)
# Clone the repo
git clone https://github.com/youruser/waxengine.git
cd waxengine
# Create virtual environment and install dependencies
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Download a SoundFont
mkdir -p soundfonts
# Place a GM SoundFont (e.g., FluidR3_GM.sf2) in soundfonts/python src/gui.pyA dark-themed window opens where you can select presets, tweak parameters, generate tracks, and play them back -- all without touching the command line.
# Default random parameters
python -m src
# Use a mood preset
python -m src --preset noir_detective
# Custom key and tempo
python -m src --tempo 84 --key Dm --swing 0.60 --output my_track.wav
# MIDI only (fast, no audio rendering)
python -m src --midi-only --output my_track.mid
# Reproducible generation
python -m src --seed 42 --verboseYour track appears in ./output/ (or wherever --output points).
python src/gui.pyThe GUI provides:
- Parameter controls -- Dropdowns for preset, root, mode, template, and progression. Sliders for tempo and swing with randomize checkboxes. Seed input and format selection.
- Preset auto-fill -- Selecting a preset populates all fields with its values. "(Random)" resets everything.
- Background generation -- The Generate button runs in a separate thread so the UI stays responsive.
- Arrangement view -- After generation, a table shows each section's type, bar count, intensity, and active instruments.
- Audio playback -- Play/Stop button for the generated WAV file.
- Output access -- Displays the file path with an Open Folder button.
| Flag | Description | Default |
|---|---|---|
--preset, -p |
Named style preset | None (random) |
--tempo, -t |
BPM (75-95) | Random |
--key, -k |
Root key (e.g., Am, Dm, Gm) |
Random minor key |
--swing |
Swing ratio (0.50-0.75) | Random 0.55-0.65 |
--seed, -s |
Random seed for reproducibility | Random |
--format, -f |
Output format: wav or mp3 |
wav |
--output, -o |
Output file path | ./output/waxengine_<seed>_<timestamp>.<ext> |
--midi-only |
Export MIDI without audio rendering | False |
--template |
Song structure: classic, cinematic, minimal, noir |
Random |
--progression |
Chord progression name | Random |
--verbose, -v |
Show arrangement plan and progress | False |
--list-presets |
List all available presets and exit | -- |
| Preset | Vibe | Key | BPM | Template |
|---|---|---|---|---|
noir_detective |
Dark, smoky, suspenseful | D minor | 78 | noir |
dusty_grooves |
Classic vinyl hip-hop | A dorian | 88 | classic |
orchestral_hiphop |
Grand, cinematic | G minor | 82 | cinematic |
midnight_jazz |
Late-night lounge | E dorian | 76 | minimal |
melancholic_drift |
Dreamy, beautiful sadness | C aeolian | 75 | cinematic |
tension_rising |
Phrygian suspense, uneasy | A phrygian | 80 | noir |
from src.generation.pipeline import generate
# Simple generation with defaults
result = generate()
# Use a preset with overrides
result = generate(
preset_name="noir_detective",
seed=42,
verbose=True,
)
# Full control
result = generate(
root="D",
mode="minor",
bpm=82,
swing=0.60,
template_name="cinematic",
progression_name="noir_minor",
seed=100,
output_format="wav",
)
# Access results
print(f"Duration: {result.duration_seconds:.0f}s")
print(f"MIDI: {result.midi_path}")
print(f"Audio: {result.audio_path}")
print(f"Generation time: {result.generation_time:.1f}s")You can also work with parameters directly:
from src.generation.parameters import randomize_parameters, parameters_summary
params = randomize_parameters(root="A", mode="dorian", bpm=88, seed=42)
print(parameters_summary(params))Create a JSON file in presets/:
{
"description": "Rainy cafe atmosphere",
"root": "D",
"mode": "dorian",
"bpm": 78,
"swing": 0.58,
"template": "minimal",
"progression": "dorian_groove"
}Use it with --preset rainy_cafe (filename without .json).
Parameters --> Arranger --> Composition Engines --> MIDI --> Renderer --> Mixer --> Output
| | |
| +----+-----+ +----+------+
| | Drums | | Per-track |
| | Bass | | effects |
v | Keys | | Vinyl FX |
Song Structure | Strings | | Master |
Chord Map | Melody | +-----------+
Section Plan | Texture |
+----------+
-
Parameter Selection -- Tempo, key, swing, structure template, and instrument configs are chosen (from preset or randomized within genre-appropriate ranges).
-
Arrangement Planning -- A structure template is selected (classic, cinematic, minimal, or noir), sections are assigned chord progressions, and instrument entry/exit is planned per section.
-
Composition -- Each instrument engine independently generates its part:
- Drums: Boom-bap patterns with swing, ghost notes, fills at transitions
- Bass: Root-fifth patterns with chromatic approach notes and octave jumps
- Keys: Rhodes comping with voice-led extended chord voicings
- Strings: Sustained pads split into high (violins) and low (cello) with voice leading
- Melody: Pentatonic/modal phrases with call-and-response structure
- Textures: Ambient pads, reverse swells, risers for transitions
-
MIDI Assembly -- All parts are quantized with swing, humanized, and assembled into a multi-track MIDI file.
-
Audio Rendering -- FluidSynth renders each track to audio using SoundFont instruments.
-
Effects Processing -- Per-track chains (EQ, compression, reverb, saturation) plus tremolo on keys. Vinyl processor adds crackle, tape wobble, and lo-fi warmth.
-
Mixing -- Tracks are leveled, panned with constant-power law, and summed through a master chain (glue compression, smile EQ, tape saturation, limiter at -1dB).
-
Export -- Final mix is written to WAV (24-bit) or MP3.
WaxEngine includes 17 curated trip-hop progressions:
noir_minor: i - iv - v - i (minor blues foundation)aeolian_cadence: i - bVI - bVII - imodal_wandering: i - iv - bVII - bIIIminor_jazz_turnaround: ii dim7 - V7(b9) - iphrygian_tension: i - bII - i (Phrygian color)cinematic_build,dorian_groove,melancholic_drift,suspense, and more
Chords are voiced with extensions (7ths, 9ths) and inversions, then voice-led to minimize movement between voicings.
All rhythmic content passes through a swing quantizer that shifts alternate subdivisions forward by a configurable ratio (default 60%). Humanization adds timing jitter and velocity variation to avoid mechanical feel.
Melodic phrases are built from weighted random walks on pentatonic/modal subsets. Constraints include maximum interval size, phrase length (2-4 bars), mandatory rest periods, and tendency toward chord tones on strong beats.
WaxEngine/
├── src/
│ ├── __main__.py # Module entry point (python -m src)
│ ├── main.py # CLI entry point
│ ├── gui.py # PyQt6 GUI (python src/gui.py)
│ ├── theory/ # Scales, chords, progressions, rhythm
│ │ ├── scales.py # 17 scale/mode definitions
│ │ ├── chords.py # Chord voicing engine (22 qualities)
│ │ ├── progressions.py # 17 curated progressions
│ │ └── rhythm.py # Swing, humanization, syncopation
│ ├── composition/ # Instrument generators
│ │ ├── arranger.py # Song structure templates & section planning
│ │ ├── drums.py # Boom-bap patterns with swing/ghost notes
│ │ ├── bassline.py # Walking bass, root-fifth, syncopated
│ │ ├── melody.py # Pentatonic/modal phrase generation
│ │ ├── keys.py # Rhodes/Wurlitzer comping patterns
│ │ ├── strings.py # High/low string pads with voice leading
│ │ └── textures.py # Ambient pads, swells, risers
│ ├── synthesis/ # Audio rendering & effects
│ │ ├── renderer.py # FluidSynth MIDI-to-audio
│ │ ├── effects.py # Per-track & master effect chains
│ │ ├── vinyl.py # Crackle, hiss, wow/flutter, bit reduction
│ │ └── mixer.py # Multi-track mixing & export
│ ├── generation/ # Pipeline orchestration
│ │ ├── pipeline.py # End-to-end generation
│ │ ├── parameters.py # Layered randomization system
│ │ └── presets.py # Named style presets
│ └── utils/
│ └── midi_utils.py # NoteEvent, GM constants, MIDI helpers
├── soundfonts/ # FluidR3_GM.sf2 SoundFont
├── output/ # Generated tracks (.mid, .wav)
├── presets/ # JSON preset configurations
├── tests/ # 5 test suites
├── requirements.txt
└── pyproject.toml
| Track | EQ | Compression | Reverb | Special |
|---|---|---|---|---|
| Drums | HP 60Hz, cut 300Hz | Fast attack, 4:1 | Short room | Tape saturation |
| Bass | LP 3kHz, boost 80Hz | Slow attack, 3:1 | None | Light overdrive |
| Keys | Scoop 400Hz, air 10kHz | Medium, 2:1 | Medium plate | Tremolo |
| Strings | HP 150Hz | Gentle 2:1 | Large hall | -- |
| Textures | BP 200Hz-4kHz | None | Long hall | -- |
- Smile EQ (subtle low/high boost)
- Glue compression (slow attack, 1.5:1)
- Tape saturation (subtle warmth)
- Vinyl noise layer (crackle at -24dB)
- Limiter at -1dB
# Quick dark cinematic track
python -m src --preset noir_detective --seed 1337 --verbose
# Upbeat jazzy groove
python -m src --tempo 92 --key Am --preset dusty_grooves
# Long atmospheric piece with specific progression
python -m src --preset orchestral_hiphop --tempo 78 --progression cinematic_build
# MIDI only for loading into a DAW
python -m src --midi-only --output ./daw_import.mid
# Batch generate 10 unique tracks
for i in $(seq 1 10); do
python -m src --seed $i --output "./output/track_$i.wav"
done
# List all available presets
python -m src --list-presets208 tests across 5 test suites:
python -m pytest tests/ -v| Suite | Tests | Coverage |
|---|---|---|
test_theory.py |
51 | Scales, chords, progressions, rhythm |
test_composition.py |
42 | All instrument generators |
test_arrangement.py |
44 | Arranger, textures, parameters |
test_rendering.py |
39 | Renderer, effects, vinyl, mixer |
test_pipeline.py |
32 | Full pipeline, presets, CLI |
- No vocals or spoken word -- Instrumentals only. Wax Tailor's signature dialogue samples and vocal features are not replicated.
- SoundFont-dependent quality -- Output quality is bounded by the SoundFont instruments used. Higher-quality SoundFonts produce better results.
- Not machine learning -- Algorithmic/rule-based composition. Deterministic and reproducible given the same seed.
- No real sample chopping -- The "sample collage" aesthetic is approximated through melodic fragmentation and lo-fi processing, not actual sample manipulation.
mido>=1.3.0
pydub>=0.25.1
numpy>=1.24.0
scipy>=1.10.0
soundfile>=0.12.0
pyfluidsynth>=1.3.0
pedalboard>=0.7.0
PyQt6>=6.5.0
Optional:
music21>=9.1.0
pretty_midi>=0.2.10
librosa>=0.10.0
matplotlib>=3.7.0
System dependencies: fluidsynth, libfluidsynth-dev, ffmpeg, libxcb-cursor0 (Linux)
MIT License. All generated music is original and royalty-free.
Inspired by the production artistry of Wax Tailor (Jean-Christophe Le Saoût). This project is an homage to his cinematic approach to hip-hop production. WaxEngine is not affiliated with or endorsed by Wax Tailor.