Tracker playback check
The tracker playback check tells you whether a song exported to a tracker plays there the way SampleToNES plays it. It exports your projects, or a set of small projects that comes with SampleToNES, and plays each exported file with the tracker's own playback code. It then compares, tick by tick and channel by channel, what the sound chip plays with what SampleToNES plays. The report lists each difference.
Each tracker the check plays through is a target:
bitphaseexports a.btpdocument and plays it with the engine of a copy of the Bitphase source code.famitrackerexports an.ftmmodule, has FamiTracker export it to an.nsffile, and plays that file the way a NES does.
Use it to:
- Check that your song plays in the tracker the way you wrote it.
- Check an export after you change the exporter or update the tracker.
- Find the tick, the row and the channel where the tracker plays a song differently.
The check needs the tracker installed, so it is not part of the automatic tests.
▍What it needs
For the bitphase target:
- Node.js, so that the
nodeprogram runs in a terminal. - A copy of the Bitphase source code with its packages installed. Clone or download
https://github.com/paator/bitphaseand runpnpm installin that folder.
For the famitracker target:
FamiTracker.exe, version 0.4.6, from famitracker.com.- On Linux and macOS, Wine, so that the
wineprogram runs in a terminal. On Debian and Ubuntu, that issudo apt install wine. On macOS,brew install --cask wine-stable.
▍Run it
Each run needs one target. In an installed copy, name the target and what it needs, and run the command twice to check both:
sampletones tracker-playback bitphase --directory path/to/bitphase
sampletones tracker-playback famitracker --executable path/to/FamiTracker.exe
To check your own song, add --project with its project file. Repeat it to check several songs in one
run:
sampletones tracker-playback famitracker --executable path/to/FamiTracker.exe --project my-song.stp
From a copy of the source code, make tracker-playback runs the targets you give it. PROJECT takes
one or more project files, separated by spaces:
make tracker-playback BITPHASE=path/to/bitphase FAMITRACKER=path/to/FamiTracker.exe PROJECT=my-song.stp
Without --project, the run plays the corpus that comes with SampleToNES:
- Small projects that each exercise one thing a song can do:
- Notes on every channel.
- Volume rows and transpose rows.
- Note-offs.
- Instruments with looping envelopes.
- Samples with arpeggios and bends.
- Noise at several periods, in both modes.
- A tempo whose rows last unequal ticks.
- An order that revisits patterns.
- Notes below A-0, and bends past the lowest and highest note the chip plays.
- A longer arrangement, rebuilt from rendered sounds, at its own tempo and at a faster one.
The results go into a new folder inside the tracker-playback folder of your
SampleToNES folder. The folder is named by the date and time the run started, for
example run-20260930-124501. When the run ends, the command prints each project's verdict and a link
to the report.
▍What a run writes
| Path | Contents |
|---|---|
report.md |
the verdict for each project, and a table of every difference |
documents/ |
each project as the export wrote it, and what the tracker's playback recorded of it |
For the bitphase target, documents/ has <project>.btp, ready to open in Bitphase, and
<project>.json, what the sound chip held on every tick, for your own analysis.
For the famitracker target, documents/ has:
<project>.ftm, ready to open in FamiTracker.<project>.nsf, the file FamiTracker exported. It sounds slightly different from a normal export: the markers the check adds shift the triangle and noise a little and add a faint click on each row. The.ftmhas no markers.<project>.json, what the sound chip held on every tick, for your own analysis.
Files are named after the project file. A second project with the same name gets -2 appended.
▍Reading the report
The first table has one row per project: how many ticks each side plays, and the verdict. The verdict
is matches, a number of differences, or the rows or the length differ. A section per project
follows. It says which file the project came from, or what a corpus project
exercises. It also says what the export reported leaving out, such as an instrument it shortened. A
table then lists each difference. A line names:
- The channel.
- What differs.
audiblemeans the channel sounds on one side only. Otherwise the difference is in theperiod, thevolume, or thetimbre, which is the duty cycle on a pulse channel and the short mode on the noise channel. - The first tick it shows on, with its frame and row.
- What each side sounds there.
- How many ticks show it.
Ticks that differ in the same way on the same channel are counted together. Under each difference,
further lines show later rows where both sides play new values. This separates two causes that change
the same field. The examples_per_difference setting in settings.yaml, in the tool's
tracker_playback/config folder, sets how many lines a difference shows.
counted down after a sound means the chip's envelope or length counter keeps lowering the volume
after the registers are set. Other sounds hold the volume their registers set.
A channel that is silent on both sides shows no difference. The report also says when the two sides place a tick on different rows, or play a different number of ticks.
With an uneven tempo, FamiTracker spreads the extra ticks differently, so its rows start on other ticks than in SampleToNES. Expect row mismatches and the differences that follow from them. The export is not at fault.
▍Options
bitphase --directory <folder>: the folder with the Bitphase source code. Thebitphasetarget needs it.famitracker --executable <file>:FamiTracker.exe. Thefamitrackertarget needs it.--project <file>: a project file to check. Repeat it to check several. Without it, the run plays the corpus.--output <folder>or-o <folder>: the folder the run writes into, in place of the timestamped one.
▍How it works
Each project is exported by the same code the app's export runs. The target then plays the file with the tracker's own playback code and records the sound chip's registers on each tick.
- For
bitphase, a script that comes with SampleToNES plays the document through the Bitphase copy's own loader and renderer, and records each write its engine makes to the chip. - For
famitracker, FamiTracker exports the module to an NSF from its command line. The NSF holds FamiTracker's own sound driver, the program a NES runs to play the song. The check runs it on an emulated NES processor, the way an NSF player does, and records each write the driver makes to the chip.
To tell where each row starts, the module FamiTracker exports has a marker on every row of its DPCM channel, which SampleToNES leaves empty. The marker sets the DPCM channel's output level to one of a few low values, and the check reads it. The other channels' registers stay as the export wrote them.
The reference side is SampleToNES's own playback of the project, turned into chip register values. Each side is reduced to what the chip plays on every channel on every tick: sounding or silent, timer or noise period, volume, and duty cycle or noise mode. The two are then compared.