Generate ZX Spectrum AY (PT3) music from MIDI and other sources.
Project description
spectrumizer
Generate ZX Spectrum AY music (.pt3) from public sources (MIDI now;
MusicXML/scores planned). The output is a standard Vortex Tracker / Sergey
Bulba PT3 module, so anything it produces drops straight into a Spectrum game
that ships a PT3 replayer.
Instead of typing every arrangement note-by-note in Python, you feed a source file and spectrumizer arranges it down to the AY's 3 channels (+ noise).
▶ Hear it in your browser: demo page · or the Demos section below.
⚠️ Licence: spectrumizer does not launder licences. The licence of the SOURCE governs the OUTPUT — a
.pt3from a copyrighted MIDI is still copyrighted. Only bundle public-domain or your own music into a release. ReadLICENSING.md.
Install
pip install -e . # from a clone — installs the `spectrumizer` command + deps
Or, without installing the package:
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
All deps are pure-Python (mido), so the same wheels work on Intel & Apple
Silicon — no native build step.
Use
# faithful 3-voice reduction
spectrumizer song.mid -o song.pt3 # or: python -m spectrumizer song.mid -o song.pt3
# chiptune flavour: octave-doubled leads + synth drums when the source has none
spectrumizer song.mid -o song.pt3 --style chiptune
# tune the AY octave by ear, change grid/tempo
spectrumizer song.mid --transpose -12 --rows-per-beat 4 --speed 6 \
--name "MY THEME" --author "ME"
# dynamics: MIDI velocity drives per-note volume (on by default)
spectrumizer song.mid -o song.pt3 --no-dynamics # ...or flat per-channel volume
# generate and immediately hear it (renders through a software AY, then plays)
spectrumizer song.mid -o song.pt3 --play
Run spectrumizer --help for all flags.
How it works
MIDI ─(inputs/midi.py, mido)→ IR ─(arrange/)→ 3 AY channels ─(pt3/)→ .pt3
spectrumizer/pt3/— the proven PT3 emitter (note encoding, channel packer, samples, ornaments, file writer). The byte format is verified against the real player; don't change it blindly.spectrumizer/arrange/— the hard part:quantize— map time to PT3's row grid (derivespeedfrom tempo).reduce— peel the source polyphony into ≤3 monophonic lines (lead / bass / harmony) via a greedy high/low "skyline".embellish— chiptune passes (octave leads, synth drums; chord arps planned).- dynamics — MIDI velocity → per-note AY volume, normalised so the piece's
loudest note hits each channel's ceiling (on by default;
--no-dynamics). - Channel allocation: A = lead, B = bass, C = real drums if present, else synth drums (chiptune), else harmony (faithful).
spectrumizer/ir.py— the source-agnostic note model both inputs target.
PT3 invariants baked in (from the player source)
The player ends a pattern when channel A hits its 0x00 terminator and then
resets all three channels — so every channel of a pattern encodes exactly
ROWS_PER_PATTERN (64) rows, and row 0 is never an empty rest (the packer drops
leading rests). arrange/model.py enforces both.
Listen to it (no Spectrum needed)
spectrumizer ships its own playback path: a small software AY-3-8910 that
renders a .pt3 to a stereo .wav (classic ABC panning — A left, B centre,
C right) and plays it through your system audio player (afplay on macOS;
ffplay / aplay / paplay / sox elsewhere).
spectrumizer-play song.pt3 # render song.wav and play it
spectrumizer-play song.pt3 --no-play # just write the .wav
spectrumizer-play song.pt3 --seconds 30 # cap length, looping the song's tail
spectrumizer-play song.pt3 --rate 22050 # faster render (lower fidelity)
spectrumizer-play song.pt3 --tuning equal # equal-tempered instead of the PT3 table
spectrumizer-play song.pt3 --stereo mono # mono (default abc = A-left/B-centre/C-right)
spectrumizer-play song.pt3 --noise-period 5 # force a noise period (default: the module's real one)
The synth (spectrumizer/audio.py) plus the PT3 interpreter
(spectrumizer/pt3/player.py, the inverse of the encoder) only implement the
subset of PT3 this tool emits — notes, OFF, sample/ornament/volume, NtSkip. Pitch
uses the exact PT3 tone table (the table-1 periods from the real Bulba player,
so notes land where the chip puts them; pass --tuning equal for the old
equal-tempered approximation). Treat it as a faithful audition, not a
cycle-exact emulation. For the real thing, drop the .pt3 into the PT3 slot of a
128K Spectrum build running a Bulba/Vortex replayer, rebuild, and run it in an
emulator (e.g. ZEsarUX).
Demos
Hear every mode in your browser on the demo page
(GitHub Pages, nothing to install) — or click a clip to play it in GitHub's file
viewer. All are examples/ode-to-joy.mid rendered through the built-in software
AY; regenerate with pip install -e ".[demos]" && python examples/make_demos.py.
| Demo | What it shows |
|---|---|
| ▶ Faithful | 3-voice reduction |
| ▶ Chiptune | octave lead + synth drums |
| ▶ No dynamics | flat volume — vs the velocity dynamics |
| ▶ Equal-tempered | vs the exact PT3 tone table |
| ▶ Mono | vs the default ABC stereo |
Tests
pip install -e ".[dev]" # installs pytest
pytest -q
Status
- Generate: MIDI → PT3, faithful + chiptune, with velocity-driven dynamics.
- Audition: built-in software-AY playback to a stereo WAV — exact PT3 tone
table, real per-frame noise period, ABC panning (
spectrumizer-play/--play). - Planned: MusicXML (music21) input, chord arpeggios, AY hardware envelopes
(buzzer bass), general PT3 playback (envelope/slides), raw-AY/
.vtxexport.
Origin
spectrumizer grew out of hand-written, per-track PT3 composer scripts for a ZX Spectrum game, generalising them into a single reusable arranger. It is now a standalone, game-agnostic tool.
Credits
- Sergey Bulba — the PT3 module format and the Vortex Tracker / PT3 replayer this tool targets, including the NoteTableCreator tone-table data the audition synth uses for exact Spectrum pitches.
- Ivan Roshin — NoteTableCreator, the source of those packed AY tone tables.
These credits acknowledge the format and reference data; spectrumizer's encoder,
decoder and synth are independent implementations (see LICENSE).
Licence
MIT © Miguel Ángel Esteve Marco. Note: the MIT licence covers
spectrumizer's own code, not the music you run through it — see
LICENSING.md.
Project details
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 spectrumizer-0.1.0.tar.gz.
File metadata
- Download URL: spectrumizer-0.1.0.tar.gz
- Upload date:
- Size: 34.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f8846d524b03c659235e4eb10d2f9428e3559a3478f1676c91d7d07ddcb7c31
|
|
| MD5 |
3d6c790094e6c42347a0007e8b35f416
|
|
| BLAKE2b-256 |
48878d6ebb1574aec8bcba797e14c87fc395272a73b51f928a7c9d18d4c5b2a5
|
Provenance
The following attestation bundles were made for spectrumizer-0.1.0.tar.gz:
Publisher:
publish.yml on revengator/spectrumizer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spectrumizer-0.1.0.tar.gz -
Subject digest:
5f8846d524b03c659235e4eb10d2f9428e3559a3478f1676c91d7d07ddcb7c31 - Sigstore transparency entry: 1764698973
- Sigstore integration time:
-
Permalink:
revengator/spectrumizer@ac677fb880cab2ebd8c0149de63206bced8af3f4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/revengator
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ac677fb880cab2ebd8c0149de63206bced8af3f4 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file spectrumizer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: spectrumizer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.6 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 |
2163717c6ad35a6c8ea0f73925183d433cc8839b476acca2407b22ab7df2124c
|
|
| MD5 |
474b538b6c5577f7036285f4489a91ef
|
|
| BLAKE2b-256 |
88278c0c779e05d96a9bd62d7ed7ec72faf3ede65d80526c96b1bc03c8aa8c13
|
Provenance
The following attestation bundles were made for spectrumizer-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on revengator/spectrumizer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spectrumizer-0.1.0-py3-none-any.whl -
Subject digest:
2163717c6ad35a6c8ea0f73925183d433cc8839b476acca2407b22ab7df2124c - Sigstore transparency entry: 1764699073
- Sigstore integration time:
-
Permalink:
revengator/spectrumizer@ac677fb880cab2ebd8c0149de63206bced8af3f4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/revengator
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ac677fb880cab2ebd8c0149de63206bced8af3f4 -
Trigger Event:
workflow_dispatch
-
Statement type: