SampleToNES (sampletones) is a desktop tool for people writing music for the NES 2A03 sound chip, mainly in FamiTracker.
The core idea is to approximate an audio sample using only the chip's basic oscillators — two pulse channels, a triangle, and noise — without any DPCM samples.
A built-in sequencer lets you arrange the reconstructed samples into patterns and play them back inside the application, so you can experiment with the results before exporting the instruments into FamiTracker.
It supports:
- loading common audio formats: WAV, MP3, FLAC, OGG, AIFF, and AU
- a wide range of NES frequencies, from 15 Hz to 600 Hz, including the two most common standards:
- NTSC (60 Hz)
- PAL (50 Hz)
- various sample rates, from 8000 Hz to 192,000 Hz
- restricting the reconstruction to a chosen subset of oscillators:
pulse1pulse2trianglenoise
- exporting reconstructed audio as FamiTracker
.ftiinstruments or as.wav
- Windows, macOS, or Linux
- Python 3.12 (https://www.python.org/downloads/)
- uv, recommended for development and running from source
The quickest way to get a ready-to-run build. Python 3.12 is the only prerequisite.
- Make sure Python 3.12 is installed and available as
pythonin your PATH. - Double-click
install.batin this folder. It will build a standalonesampletones.exein the same directory. - Run
sampletones.exeto start the application.
- Make sure Python 3.12 is installed and available as
python3in your PATH. - Open a terminal in this folder and run:
This will build a standalone
./install.sh
sampletonesexecutable in the same directory. - Run
./sampletonesto start the application.
For now, the installation script is adjusted to Debian-based distributions only.
Requires uv. make is recommended (included with most Linux and macOS systems); without it, run the equivalent uv commands directly.
- Set up everything — creates the virtual environment, installs all dependencies (including dev tools), and installs
sampletonesas a global command:make setup
- Run the application:
On Windows without
make run # from the project directory sampletones # from any folder, thanks to the global install
make, userun.batin place ofmake run.
make setup is the recommended entry point. To do the same steps manually with uv:
uv sync --group dev # create the environment with dev tools
uv tool install . # install the global `sampletones` command
uv run sampletones # run from the project without the global installTo update the global command after pulling new changes, re-run make setup (or uv tool install --force .).
SampleToNES can use CUDA for acceleration via CuPy. GPU mode is enabled by the gpu extra.
On Linux, the gpu extra installs CuPy alongside the CUDA 12 libraries it needs (libcudart and libnvrtc for the elementwise kernels, libcublas for the constant-Q transform's matmul) as Python wheels. A compatible NVIDIA driver on the host is sufficient — no system CUDA toolkit required, regardless of which CUDA version (if any) is installed system-wide.
make setup GPU=1 # environment and global command, both with GPU supportOr with uv directly:
uv sync --group dev --extra gpu # environment with GPU support and dev tooling
uv tool install ".[gpu]" # global `sampletones` command with GPU supportOn Windows, the gpu extra installs CuPy. GPU mode additionally requires the NVIDIA CUDA Toolkit (version 12.x), which provides cudart64_12.dll, nvrtc64_120_0.dll, and cublas64_12.dll (the last backs the constant-Q transform's matmul).
To optimize sample reconstruction, all single-oscillator instructions are prerendered as samples with spectral information.
The instruction data depends on the following configuration properties:
-
nes_frequency(NES frequency, usually NTSC or PAL) -
sample_rate(in Hz) -
transformation_gamma, which determines the transformation of the spectral information:-
0- raw absolute values of Fourier Transform -
100- absolute values transformed via$\log\left(1 + x\right)$ operation Intermediate values interpolate between these two. Warning. For now, only the extremal values,0and100, are working. This is going to be fixed in some next release.
-
Each set of parameters corresponds to a different instructions data, encoded by a configuration key.
Libraries are generated using the generator included in the application. They can be generated from the Instructions tab of the application and explored using the application.
For each key, the data consists of single instructions. Each instruction contains:
- metadata
- instruction data
- a single waveform frame
- spectrum
Prerendered instructions contain the following data:
- generator class (
pulse/triangle/noise) - instruction data (
on/pitch/period/volume/duty_cycle/short)
Instructions contain basic information for all 2A03 oscillators:
- on (0-1): whether a generator is on (1) or off (0)
- pitch (33-119) for pulse and triangle generators, and period (0-15) for the noise generator
- volume (0-15) for pulse and noise generators
- duty_cycle (0-3) for pulse generators, and the short (0-1) flag for the noise generator
Each instruction is prerendered as a sample containing the entire period of a wave (excluding the longest noise samples, which are trimmed to 1 second).
Within each waveform, each instruction data contains spectral information on the frequency distribution in the waveform, precalculated using Fast Fourier Transform.
Instructions libraries are stored as .ins files in the user's documents folder, e.g.:
sr_44100_nf_30_ws_1615_tg_0_ch_283a31a50176c14faf36949913117e49.ins
The configuration is embedded in the file name:
sr_44100corresponds to the sample rate 44100 Hznf_30describes NES frequency of 30 Hzws_1615is the size of the FFT transformation (1615 samples)tg_0encodestransformation_gamma = 0ch_283a31a50176c14faf36949913117e49is the config hash.
Generators are responsible for producing waveforms and keeping the internal state of the generators (phase and clock).
As in 2A03, there are four generators of three types:
pulse1pulse2trianglenoise
For the most part, generators are not used during the reconstruction: each single instruction is precalculated with spectral information.
Reconstructor is the main object responsible for sample conversion. It uses generators defined in the generation configuration. You can use any combination of generators to reconstruct your samples.
By default, pulse1, triangle, and noise are turned on.
Reconstruction is an object containing all conversion information. The most important ones are:
approximation: The sum of all generator waveforms approximating the input wave.approximations: Partial approximations from all generatorsinstructions: A dictionary of all FamiTracker instructions per each generator.config: A snapshot of the configuration used to reconstruct the audioaudio_filepath: The path to the original audio file.
SampleToNES offers additional generation settings:
mixer: For amplifying the NES waveforms. Too low values may result in clamped dynamics; too high values may cause quiet samples to be lost.find_best_phase: Tries to find the best phase for a sample to fit the frame.Trueby default. Allows ignoring phase shifts while searching for the best approximation.fast_difference: Instead of calculating the FFT of the audio remainder after finding partial approximations in a frame, it calculates the difference between spectral features only. Disabled by default, as it may lead to inaccurate approximations.reset_phase: Resets phases within each instruction. Not recommended.
For now, only mixer is present in the main application. Other values are experimental and may be edited in the JSON configuration file.
All local files, that is:
- configuration (
config.json) - instruction libraries (
.ins) - reconstructions (
.stn)
are stored in the default documents directory. The path depends on the operating system.
The default path of the instruction data is:
C:\Users\<user>\Documents\SampleToNES\instructions
Local files can be found in the following directory:
/home/<user>/Documents/SampleToNES/instructions
Similarly, the default directory for macOS is:
/Users/<user>/Documents/SampleToNES/instructions
You can run the application with a custom config via:
sampletones --config <config-path>or, shortly:
sampletones -c <config-path>SampleToNES supports CLI arguments also for instructions data generation. To generate a library for a given config, run:
sampletones --generate --config <config-path>If the config path is not provided, the default one is loaded.
Similarly, you can reconstruct a single WAV file or a directory using:
sampletones <path> --config <config-path>You can also specify the output file via --output (-o):
sampletones <path> --config <config-path> --output <output-path>However, this approach is discouraged, since the reconstruction files won't appear in the program application.
For more information, run:
sampletones --helpSingle elements of the sampletones Python package can be used as well.
SampleToNES exposes a variety of classes:
from sampletones import (
Config, # generation configuration
Window, # FFT window
InstructionLibrary, # library
Reconstruction, # reconstruction data
Reconstructor, # object reconstructing an audio
# Generators
PulseGenerator,
TriangleGenerator,
NoiseGenerator,
# Instructions
PulseInstruction,
TriangleInstruction,
NoiseInstruction,
)Currently, the API is not well documented. I hope that this will change in time.
from sampletones import Config, PulseGenerator, PulseInstruction
from sampletones_core.audio.io import write_wave
# Load configuration
config = Config.load("config.json")
# Prepare generator and instruction
generator = PulseGenerator(config)
instruction = PulseInstruction(on=True, pitch=55, volume=7, duty_cycle=2)
# Generate waveform
audio = generator(instruction)
# Save audio file
sample_rate = config.sample_rate
write_wave("pulse.wav", sample_rate, audio)The output will be a single G2 square wave with a length of one frame.
By default, the generator stores the internal state after generation to continue the process. To disable that behavior, pass save=False when calling the generator:
audio = generator(instruction, save=False) # doesn't change the generator statefrom sampletones import Config, Reconstructor
from sampletones_core.audio.io import write_wave
# Load configuration
config = Config.load("config.json")
# Load data and prepare the reconstructor
reconstructor = Reconstructor(config)
# Reconstruct an audio file and save the reconstruction to a file
reconstruction = reconstructor("sample.wav")
reconstruction.save("reconstruction.stn")
# Save the reconstruction waveform
sample_rate = config.sample_rate
write_wave("reconstruction.wav", sample_rate, reconstruction.approximation)The graphical user interface is implemented with DearPyGui, a Python wrapper for ImGui (https://www.dearimgui.com/).
The core depends on common Python packages:
numpyscipylibrosacupy(optional; required for GPU mode)
See the GPU support (CUDA) section for enabling GPU acceleration.
Instruction libraries and reconstructions are serialized with MessagePack (the msgpack package). No external compiler or system dependency is required — it is installed automatically with the package.
To build a standalone executable on Linux, additional tkinter development packages are required:
python3-tktk-devtcl-dev
The install.sh script will prompt to install these packages during installation.