SampleToNES (sampletones) is a WAV converter that transforms audio signals into basic oscillator instructions for the 2A03 NES audio chip:
- 2x pulse
- triangle
- noise
without using any DPCM samples.
SampleToNES allows exporting reconstructed audio as FamiTracker .fti instruments
It also supports:
- 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
- reconstruction limited to a selected oscillator:
pulse1pulse2trianglenoise
- Python 3.12 (https://www.python.org/downloads/)
- Windows, macOS, or Linux
- 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 distrubtions only.
If you already have a Python 3.12 environment set up (for example, using venv or another virtual environment tool), you can install and run SampleToNES directly as a package:
- Open a terminal in this folder.
- Activate your Python 3.12 environment.
- Install the package:
pip install . - Run the application:
sampletones
SampleToNES supports CUDA. To install with the GPU mode, run:
pip install ".[gpu]"To optimize sample reconstruction, all single-oscillator instructions are prerendered as samples with spectral information.
The instruction data depends on the following configuration properties:
-
change_rate(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.
-
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 .dat files in the user's documents folder, e.g.:
sr_44100_cr_30_ws_1615_tg_0_ch_283a31a50176c14faf36949913117e49.dat
The configuration is embedded in the file name:
sr_44100corresponds to the sample rate 44100 Hzcr_30describes change rate 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) - libraries (
.dat) - reconstructions (
.json)
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
InstructionsLibrary, # 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.audio 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.audio 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.json")
# 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:
numpycupy(optional; required for GPU mode)
If you have CUDA and want GPU acceleration, install the package with the GPU extras:
pip install ".[gpu]"Serialization uses FlatBuffers. You do not need the flatc compiler to run the application; the compiler is required only for development. On Debian/Ubuntu, install it with:
sudo apt-get install flatbuffers-compilerAlternatively, build the compiler from source: https://github.com/google/flatbuffers
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.