Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
0120d1b
Added: the rules for writing the documentation
JakimPL Sep 20, 2026
9e6e0ba
Removed: the reconstruction manager's unread hash and coefficient
JakimPL Sep 20, 2026
c350790
Stated: the level an instrument preview sounds at
JakimPL Sep 20, 2026
df02360
Moved: the ETA mark to the layer that shows it
JakimPL Sep 20, 2026
762636e
Fixed: a redrawn bar writing to the field beneath it
JakimPL Sep 20, 2026
0984a95
Re-weighed: the stages a conversion reports
JakimPL Sep 20, 2026
7b7e10c
Held: a channel's frames to one reading of what is in play
JakimPL Sep 20, 2026
869ed63
Named: the recording behind each frame under an instrument's bars
JakimPL Sep 20, 2026
f5c483a
Removed: the drive a frame was rendered at
JakimPL Sep 20, 2026
4c95d93
Inverted: the drive a candidate is measured at
JakimPL Sep 20, 2026
1199c50
Stated: the drive a conversion reaches at
JakimPL Sep 20, 2026
823206f
Restated: the shares a conversion's stages are weighed at
JakimPL Sep 20, 2026
147afa4
Stated: the two shapes a category's message module takes
JakimPL Sep 20, 2026
49578b0
Settled: when a channel reads as standing by
JakimPL Sep 20, 2026
007c6e9
Painted: each ownership stretch by the reading it stands for
JakimPL Sep 20, 2026
d27dc68
Carried: the envelopes and their owners in one model
JakimPL Sep 20, 2026
4802d18
Folded: every dialog onto one geometry it opens from
JakimPL Sep 20, 2026
7e47c20
Wrote: the contract a dialog's geometry holds to
JakimPL Sep 20, 2026
0864cef
Refined: the repository-wide development documents
JakimPL Sep 20, 2026
a661d7a
Changed: measurements stated as comparisons rather than bare figures
JakimPL Sep 20, 2026
65589c3
Refined: the application development documents
JakimPL Sep 20, 2026
0a6c527
Compressed: the payload a binary document is written as
JakimPL Sep 20, 2026
609d139
Merge pull request #69 from JakimPL/deflation
JakimPL Sep 20, 2026
36a5f0d
Refined: the release documents
JakimPL Sep 20, 2026
e995840
Added: the packed-plane layout the compression study prices
JakimPL Sep 20, 2026
2968eb6
Added: the repeated-value grammars the study compares
JakimPL Sep 20, 2026
43c731d
Stated: what a named risk and a documented claim owe
JakimPL Sep 20, 2026
39ed407
Held: which stem wins a channel clear of the drive it plays at
JakimPL Sep 21, 2026
b6a5ac3
Refined: the user guide and the glossary
JakimPL Sep 21, 2026
019347a
Guarded: the band beneath the bars against a press meant for them
JakimPL Sep 21, 2026
bf0098a
Held: a dimension's band to the frames that dimension draws
JakimPL Sep 21, 2026
5421b0d
Moved: the application half of the stems document
JakimPL Sep 21, 2026
1b427f6
Refined: the concept documents
JakimPL Sep 21, 2026
98de827
Refined: the format references
JakimPL Sep 21, 2026
dfc24a0
Measured: the frames a dialog takes to settle
JakimPL Sep 21, 2026
ef25396
Folded: the fifth dialog onto the base its siblings use
JakimPL Sep 21, 2026
97e7978
Stated: a song's planes once, in the table every reader derives from
JakimPL Sep 21, 2026
875204f
Refined: README
JakimPL Sep 21, 2026
69fc703
Held: a dialog to the width it states
JakimPL Sep 21, 2026
ea30ec5
Added: a repeat count in the bits a plane's register ignores
JakimPL Sep 21, 2026
910aea2
Added: a test holding the documentation's links and index
JakimPL Sep 21, 2026
ce73850
Restated: what the dialogs, the lanes and the drive actually do
JakimPL Sep 21, 2026
d1aaf71
Folded: the triangle's silence into the pitch it sounds
JakimPL Sep 21, 2026
5a01ded
Cleared: the bug ledger
JakimPL Sep 21, 2026
3aca4d6
Read: a phrase's own count from the token that leaves it unstated
JakimPL Sep 21, 2026
4c1bbfe
Removed: the trailing rest entry the reading's own case answers
JakimPL Sep 21, 2026
5d9bae5
Wrote: what a plane's byte holds and what the driver reads from it
JakimPL Sep 21, 2026
138d0f4
Centered: the error dialog again on the height its traceback reaches
JakimPL Sep 21, 2026
7cd6854
Left: a resting stretch to the ground of whatever draws it
JakimPL Sep 21, 2026
2a05d3d
Merge branch 'revision' into documentation
JakimPL Sep 21, 2026
53a8a96
Closed: the two bugs this branch's fixes answer
JakimPL Sep 21, 2026
7502775
Refined: the glossary, the index and the documentation rules
JakimPL Sep 21, 2026
60e5b15
Refined: the user guide's prose and glossary links
JakimPL Sep 21, 2026
7635efd
Refined: the reference pages and the API page
JakimPL Sep 21, 2026
d8e40ee
Refined: calibration and the small concept pages, and corrected the h…
JakimPL Sep 21, 2026
7041e86
Refined: the concept documents and their glossary terms
JakimPL Sep 21, 2026
d9a7df0
Refined: the development documents at the top level
JakimPL Sep 21, 2026
5f52454
Refined: the application development documents
JakimPL Sep 21, 2026
c486b8c
Refined: the release documents
JakimPL Sep 21, 2026
ae7181b
Refined: the documentation's remaining habit phrases and cross-refere…
JakimPL Sep 21, 2026
2c6d2fb
Merge branch 'revision' into compression
JakimPL Sep 21, 2026
5db72c7
Read: a plane's absence against the value it is seeded to
JakimPL Sep 21, 2026
491513a
Stated: the conditions the duty-cycle split was measured under
JakimPL Sep 21, 2026
1e4055b
Reformat
JakimPL Sep 21, 2026
0742295
Read: every document in the encoding it is stored in
JakimPL Sep 21, 2026
886e037
Merge pull request #70 from JakimPL/compression
JakimPL Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Added NSF player and export.
* Added stems conversion: to mix several recordings into one reconstruction.
* Changed drive to reach for louder instructions while a recording converts; reconvert anything converted at a drive other than `1.00`.
* Improved clarity of reconstructions.
* Optimized the size of reconstructions.
* Bumped the reconstruction data-version to `2.2` with backward compatibility for `2.1`.
Expand Down
57 changes: 24 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<div align="center">
<img src="https://raw.githubusercontent.com/JakimPL/SampleToNES/main/src/sampletones_assets/icons/sampletones.svg" alt="SampleToNES" width="64">
<p><i>SampleToNES</i> v0.3.2</p>
<p><i>SampleToNES</i></p>
</div>

## Overview
Expand All @@ -21,26 +21,31 @@ The core idea is to approximate an audio sample using only the chip's basic osci

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:
With it you can:

* loading common audio formats: WAV, MP3, FLAC, OGG, AIFF, and AU
* a wide range of NES frequencies, from 15 Hz to 300 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:
* `pulse1`
* `pulse2`
* `triangle`
* `noise`
* exporting reconstructed audio as FamiTracker `.fti` instruments, Bitphase `.json` instrument presets, `.nsf` programs the NES itself plays, or `.wav`
* convert your own recordings — WAV, MP3, FLAC, OGG, AIFF or AU — into NES instruments
* choose which of the four channels each recording may use, and how loud it plays on them
* arrange the results into a song and play it back in the app
* export instruments and songs for:
* [_FamiTracker_](http://famitracker.com/)
* [_Bitphase_](https://bitphase.app/)
* `.nsf` program
* audio `.wav` file

## Installation

You can install _SampleToNES_ in three ways:

- **Download a release** for Windows or Linux from the [releases page](https://github.com/JakimPL/SampleToNES/releases), extract it, and start `sampletones`.
- **Install from PyPI** on Windows, macOS or Linux. You need Python 3.12 or newer:
- **Install from PyPI** on Windows, macOS or Linux. You need Python 3.12 or newer. On Linux and
macOS, install the audio and file dialog libraries first:

```sh
sudo apt-get install libportaudio2 libasound2 python3-tk # Debian and Ubuntu
brew install portaudio # macOS
```

Then install the app and start it:

```sh
uv tool install sampletones # or: pipx install sampletones
Expand All @@ -53,26 +58,12 @@ An NVIDIA graphics card can speed up conversion. The [installation guide](https:

## Usage

### Where your files are stored

Your configuration, instruction libraries (`.ins`), and reconstructions (`.stn`) live under your documents folder, in `SampleToNES/`:

- Windows: `C:\Users\<user>\Documents\SampleToNES`
- Linux: `/home/<user>/Documents/SampleToNES`
- macOS: `/Users/<user>/Documents/SampleToNES`

### Command line

Every operation is a named command, and `sampletones` alone starts the interface:

```sh
sampletones run --config <config-path> # start with a custom config
sampletones open <project-path> # start with a project, reconstruction or library loaded
sampletones convert <audio-path> --config <config-path> -o <out> # reconstruct a recording without the GUI
sampletones library --config <config-path> # generate an instruction library
```
`sampletones` starts the app. Your work is saved in a `SampleToNES` folder inside your documents
folder.

Run `sampletones --help` for the commands and `sampletones <command> --help` for a command's options. The [command-line guide](https://github.com/JakimPL/SampleToNES/blob/main/docs/guide/command-line.md) explains them.
Every operation is also a named command — `sampletones convert <audio-path>` reconstructs a
recording without the interface, for example. Run `sampletones --help` for the list, and see the
[command-line guide](https://github.com/JakimPL/SampleToNES/blob/main/docs/guide/command-line.md).

## Documentation

Expand Down
44 changes: 7 additions & 37 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,10 @@ This page is for using _SampleToNES_ as a library in your own Python code. Use i
Names come from two packages:

- The names in the table below come from `sampletones`: `from sampletones import ...`.
- The examples also use a few helpers from `sampletones_core`: `write_wave`, `ensure_library`, `DEFAULT_CHANNELS`, and the FamiTracker instrument writers. Import them with the full path each example shows.
- The examples also use a few helpers from `sampletones_core` that are not in the table: `write_wave`, `ensure_library`, `DEFAULT_CHANNELS`, and the FamiTracker instrument writers. Import them with the full path each example shows.

## Public surface

```python
from sampletones import (
Config,
Window,
InstructionLibrary,
Reconstruction,
Reconstructor,
ChannelName,
Generator,
PulseGenerator,
TriangleGenerator,
NoiseGenerator,
Instruction,
PulseInstruction,
TriangleInstruction,
NoiseInstruction,
)
```

| Name | Purpose |
| --- | --- |
| `Config` | generation configuration; build it with `Config.load(path)` or `Config.default()` |
Expand All @@ -51,24 +32,18 @@ The package version is available as `sampletones.__version__`.
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)
write_wave("pulse.wav", config.sample_rate, audio)
```

The output is a single `G2` square wave one frame long.

Each generator keeps an oscillator phase and clock. By default a call renders a standalone waveform and leaves that state where it was; pass `save=True` to advance it into the next call, so a sequence of instructions renders as one continuous signal:
Each generator keeps an oscillator phase and clock. A call renders a standalone waveform and leaves that state where it was. Pass `save=True` to advance the state into the next call, so a sequence of instructions renders as one continuous signal:

```python
audio = generator(instruction, save=True) # advances the generator state
Expand All @@ -86,7 +61,7 @@ config = Config.load("config.json")
ensure_library(config) # builds the .ins library when it is missing or another version built it
```

The same step is reached from the application's _Instructions_ tab, or on the command line with `sampletones library --config config.json`.
The application's _Instructions_ tab and `sampletones library --config config.json` do the same.

### Reconstruct a sample

Expand All @@ -97,19 +72,14 @@ from sampletones import Config, Reconstructor
from sampletones_core.audio.io import write_wave
from sampletones_core.constants.enums import DEFAULT_CHANNELS

# Load configuration
config = Config.load("config.json")

# Prepare the reconstructor for the channels the run may use
# The channels the run may use
reconstructor = Reconstructor(config, frozenset(DEFAULT_CHANNELS))

# Reconstruct an audio file and save the reconstruction
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)
write_wave("reconstruction.wav", config.sample_rate, reconstruction.approximation)
```

### Load a reconstruction
Expand Down Expand Up @@ -139,4 +109,4 @@ for channel, features in reconstruction.export().items():
write_fti(f"{channel.value}.fti", instrument)
```

This writes one `.fti` per channel, named after the channel. A complete FamiTracker `.ftm` module is assembled from a project in the application, not from a single reconstruction — see [FamiTracker formats](../formats/famitracker.md).
This writes one `.fti` per channel, named after the channel. A `.ftm` module comes from a project in the application. See [FamiTracker formats](../formats/famitracker.md).
Loading
Loading