A music editor and player for the AY-3-8910 / YM2149F sound chips,
originally used in the ZX Spectrum home computer.
π Live web demo β
Vortex Tracker II was created by Sergey V. Bulba (c) 2000β2009.
Author: Sergey Bulba
*E-mail: svbulba@gmail.com *
Support page: https://ay.strangled.net/main_e.htm
This Rust port exists only because of his extraordinary work. Sergey built
an entire chip emulator, 13 file-format parsers, a complete tracker editor
and the first cross-format ZX Spectrum music toolchain β all in 23,000 lines
of hand-written Delphi/Pascal. The original readme.txt and readme.rus.txt
are preserved in the legacy/ directory of this repository.
Sergey has a much more feature full player available on his site, namely Ay_Emul. You should check it out.
Vortex Tracker II is a music tracker β a step-sequencer style editor β for the AY-3-8910, AY-3-8912 and YM2149F Programmable Sound Generators (PSGs). These chips produced the characteristic "chip-tune" sound on the ZX Spectrum, Amstrad CPC, MSX, and many other platforms of the 1980s and 90s.
A tracker works by arranging notes, instruments (samples) and effects into a pattern grid. Patterns are ordered into a position list to form a complete song. The AY/YM chip has three independent square-wave tone channels (A, B, C) plus a shared noise generator and hardware envelope.
Vortex Tracker II saves and exports to the Pro Tracker 3 (.pt3) format,
playable on real ZX Spectrum hardware and by many emulators.
As an experiment in using agentic AI, I chose Sergey's pascal code to see how much I could do with these amazing tools. I wanted to know what I could achieve, what the tools limits were, and what my limits were. In an effort to work more efficiently, I created several mcp tools; first backlog-mcp to help organise stories, memoryweb to help remember between agent sessions and other agents, and finally recordari - the multi tenanted version of memoryweb. All of those tools (except recordari) were used in the creation of VTIR.
These formats can be opened and loaded into the editor. They are all one-way imports β the file is converted into the internal VTM representation on load, but the original format cannot be written back out.
| # | Format | Extension | Implemented |
|---|---|---|---|
| 1 | Pro Tracker 2.xx | .pt2 |
β |
| 2 | Pro Tracker 1.xx | .pt1 |
β |
| 3 | Flash Tracker | .fls |
β |
| 4 | Fast Tracker | .ftc |
β |
| 5 | Global Tracker 1.x | .gtr |
β |
| 6 | Pro Sound Creator 1.xx | .psc |
β |
| 7 | Pro Sound Maker (compiled) | .psm |
β |
| 8 | ASC Sound Master (compiled) | .asc, .as0 |
β |
| 9 | Sound Tracker / Super Sonic (compiled) | .stc |
β |
| 10 | Sound Tracker Pro (compiled) | .stp |
β |
| 11 | SQ-Tracker (compiled) | .sqt |
β |
| 12 | Amadeus / Fuxoft AY Language | .fxm, .ay |
β .ay / β .fxm |
These are the only formats that can be saved back to disk. This matches the design of the original Delphi/Pascal application, which only ever wrote VTM and PT3 β all other formats were strictly read-only imports.
| Format | Extension | Implemented | Notes |
|---|---|---|---|
| Pro Tracker 3.xx | .pt3 |
β | ZX Spectrum binary β playable on real hardware and emulators |
| Vortex Tracker Module (text) | .vtm |
β | Native format β full round-trip, no data loss |
Why only PT3 and VTM?
The original Pascal source (legacy/trfuncs.pas) defines conversion functions only in the directionX β VTM(PT22VTM,STC2VTM, etc.) andVTM β PT3/VTM β TextFile. There are noVTM2PT2,VTM2STC, or similar writers in the original code, and adding them is out of scope for this faithful port.
| Page | Description |
|---|---|
| AY State Machine | How the AY-3-8910 / YM2149F chip emulator works β registers, tone generators, noise LFSR, envelope shapes, synthesizer, audio pipeline |
| File Formats | Binary layout and parsing notes for every supported tracker format |
| Tracker Songs & Fixtures | Catalogue of real-world tunes and test fixtures included in the repo |
| Using the TAP export | Step-by-step guide: how to load and play an exported .tap file in an emulator or on real ZX Spectrum hardware |
This is an active work-in-progress conversion from the original Delphi/Pascal
source. See requirements/requirements-index.md for the full backlog.
- β All core data structures (Module, Pattern, Sample, Ornament, β¦)
- β All five PT3 tone-frequency tables
- β Full AY/YM chip emulator (all 8 envelope shapes, noise LFSR, mixer)
- β Stereo-16 PCM synthesizer with FIR low-pass filter
- β Tracker playback engine (note entry, all 11 effect commands, ornaments)
- β
Song timing helpers:
get_module_time,get_position_time,get_position_time_ex,get_time_params(Pascal-baseline verified) - β
Cross-platform audio output via
cpal - β egui-based GUI skeleton (pattern view, sample view, ornament view, toolbar) with status bar showing current position + elapsed / total time
- β
TurboSound GUI slot management β
Turbo Soundmenu can load/replace the 2nd chip module, disable chip 2, and switch the active editor between chip 1 / chip 2 on native and WASM builds - β
Terminal CLI tracker diagnostics tool (
vti-cli) β keyboard navigation + headless tick harness; header shows elapsed / total time - β Playback cursor follow β pattern editor highlights and scrolls to the playing row in real time
- β File open (import): PT3, PT2, PT1, STC, STP, VTM text, AY (ZXAY ST11; EMUL partial β the original Pascal application had full EMUL playback via a built-in Z80 emulator; this Rust port has no Z80 emulator yet, so only EMUL files whose payload contains an embedded PT3/STP module with a recognisable header can be loaded β all other EMUL files will fail to import)
- β File save (export): PT3 binary, VTM text β these are the only writable formats
- β PT3 round-trip writer (parse β write β parse verified)
- β
ZX Spectrum export (
.tap,.scl,.ay, Hobeta.$header) - β 181 tests across vti-core and vti-ay, 0 failing
- Remaining 8 format parsers: ASC, SQT, GTR, FTC, FLS, PSC, PSM, FXM
- Full keyboard note-entry in the pattern editor
- Editable sample / ornament fields
- Position list editor
- Options dialog (sample rate, chip type, panning, buffer settings)
- Channel panning selector (Mono / ABC / ACB / β¦)
- Seek-to-time UI (scrub bar) β timing helpers are now ready to drive this
- GitHub Actions release pipeline (Mac
.dmg, Windows.exe, Linux binary)
| Platform | Required |
|---|---|
| All | Rust toolchain β₯ 1.75 |
| Linux | libasound2-dev (ALSA headers), pkg-config |
| macOS | Xcode Command Line Tools |
| Windows | No extra dependencies |
# Linux
sudo apt install libasound2-dev pkg-config
# All platforms
cargo build --release
cargo run --release
# Build CLI binary
cargo build --bin vti-cli --features="cli"
# Run CLI directly from build output (debug)
target/debug/vti-cli crates/vti-core/tests/fixtures/tunes/madness_descent.pt3
# Run CLI via helper script (uses release/debug binary if present, else cargo run)
./scripts/vti-cli crates/vti-core/tests/fixtures/tunes/madness_descent.pt3
# Start interactive mode with playback enabled immediately
./scripts/vti-cli crates/vti-core/tests/fixtures/tunes/madness_descent.pt3 --play=true
# Headless diagnostics (no real audio device needed)
./scripts/vti-cli crates/vti-core/tests/fixtures/tunes/madness_descent.pt3 --ticks 512vti-cli starts with playback off by default. Use --play, --play=true, or --play=false to control startup playback. Use --active-chip 2 to start focused on the TurboSound second chip (requires --ts2). Keyboard controls: 1/2 select chip, arrows move row/channel, PageUp/PageDown move positions, Space play/pause, s step one tick, f toggle follow-playhead, Home/End jump top/bottom, q quit.
TurboSound playback is currently exposed via the CLI by loading a primary module
(chip 1) plus a second module via --ts2 (chip 2).
# Interactive 2-chip playback
cargo run --features cli --bin vti-cli -- \
crates/vti-core/tests/fixtures/tunes/madness_descent.pt3 \
--ts2 crates/vti-core/tests/fixtures/tunes/Space\ Crusade\ Loader.pt3 \
--play=true
# Headless diagnostics (prints chips=2 when TurboSound is active)
cargo run --features cli --bin vti-cli -- \
crates/vti-core/tests/fixtures/tunes/madness_descent.pt3 \
--ts2 crates/vti-core/tests/fixtures/tunes/Space\ Crusade\ Loader.pt3 \
--ticks 512In interactive mode, confirm TurboSound is active by checking for turbosound=on
and a regs2: line in the header.
# Library and integration tests (no audio device required)
cargo test -p vti-core -p vti-ay -p vti-audio
# Device-dependent audio tests (requires a real output device)
cargo test -p vti-audio -- --ignored
# Focused device diagnostics around cpal start/callback/fill path
cargo test -p vti-audio audio_player_diagnostics_show_callback_activity -- --ignored
# Pascal parity / approval baseline tests
cargo test -p vti-core -p vti-ay --test pascal_baseline_testsThe correctness of the playback engine is verified against the original Pascal
source by committed JSON fixtures in crates/*/tests/fixtures/pascal-baselines/.
The fixtures were generated by compiling and running
pascal-tests/vt_harness.pas (FPC) against the exact Pascal algorithms in
trfuncs.pas and AY.pas. They represent the ground truth for:
| Fixture | What it captures |
|---|---|
noise_lfsr.json |
200-step LFSR sequence (taps: bit13β16, noise_val = bit16 of seed) |
envelope_shapes.json |
All 8 AY envelope shapes, 64 amplitude steps each |
pt3_vol.json |
Complete 16Γ16 PT3 volume table |
note_tables.json |
All 5 note tables (PT, ST, ASM, REAL, NATURAL) |
pattern_play_basic.json |
AY register values across 20 ticks of a 4-row tone pattern |
pattern_play_envelope.json |
Same with AY envelope type 8 active |
To regenerate the baselines (run infrequently β after a deliberate Pascal change):
# Requires fpc: sudo apt-get install fp-compiler (Linux)
# or: brew install fpc (macOS)
bash pascal-tests/run_harness.shOr trigger the Regenerate Pascal Baselines workflow manually in GitHub Actions.
A diff in a fixture that was not caused by an intentional Pascal source change
is a regression β investigate before merging.
A live build is automatically deployed to https://corbym.github.io/vtir/ on every push to main.
To build and serve the web version locally, install trunk and the WASM target:
rustup target add wasm32-unknown-unknown
cargo install trunk
# Serve with hot-reload at http://localhost:8080
trunk serve
# Or produce a release build in dist/
trunk build --releaseNote: Audio uses the browser's Web Audio API via
cpal's webaudio backend. A short test tone is pre-loaded on row 0 of pattern 0 β click βΆ Play to verify audio is working. You can then add your own notes in the pattern editor and play them back.
Cargo.toml β workspace root + binary crate
src/
main.rs β eframe entry-point
bin/vti-cli.rs β terminal CLI tracker + diagnostics harness
app.rs β top-level application state
scripts/
vti-cli β helper launcher for CLI from build output or cargo run
ui/ β egui panels
pattern_editor.rs
sample_editor.rs
ornament_editor.rs
toolbar.rs
crates/
vti-core/ β data types, playback engine, format parsers
vti-ay/ β AY-3-8910 / YM2149F emulator
vti-audio/ β cross-platform audio (cpal)
- Sergey V. Bulba β original Vortex Tracker II author; without his work none of this would exist.
- Roman Scherbakov β co-founder of the original Vortex Tracker project.
- Hacker KAY β AY/YM amplitude measurement tables used in the emulator.
- Alone Coder (Dima Bystrov) β author of Pro Tracker 3.6x/3.7x, the reference format this tool targets.
- The cpal and egui communities for excellent cross-platform audio and GUI crates.