Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
a9e99fb
Split: the architecture document's principles from their mechanism
JakimPL Sep 4, 2026
dbd8b8b
Moved: the choice to bend from the configuration onto the stem
JakimPL Aug 25, 2026
c4855d8
Gave: the reconstructor and the output paths the channels a run hands…
JakimPL Sep 4, 2026
0487cd8
Added: the batch conversion writing one reconstruction per gathered r…
JakimPL Sep 4, 2026
42a69d6
Gave: a stem entry the settings its recording is converted with
JakimPL Sep 4, 2026
f9bf5b1
Added: converter source structures
JakimPL Sep 5, 2026
489fd7b
Refactored: converter settings management
JakimPL Sep 5, 2026
c763cfc
Split: the converter logic into the concerns it held
JakimPL Sep 5, 2026
18ab751
Decomposed: the stems list into the jobs it was doing
JakimPL Sep 5, 2026
75859dd
Named: the converter's output as a run over the sources gathered
JakimPL Sep 5, 2026
d59415f
Drew: a gathered folder as one row answering for what it holds
JakimPL Sep 5, 2026
d8e7a83
Refreshed: every browser after a run, so a written reconstruction sta…
JakimPL Sep 5, 2026
4888a8b
Generated: the settings card from the choices the model declares
JakimPL Sep 5, 2026
0a1a4f5
Made: the channels a row holds the whole of what its reconstruction r…
JakimPL Sep 5, 2026
f6faeb0
Crossed: a gesture that rebuilds widgets to the render thread
JakimPL Sep 5, 2026
32ed495
Recorded: why an absent DearPyGui item is caught as broadly as it is
JakimPL Sep 5, 2026
14cee1b
Divided: the Main tab's wiring by the collaborator each hook reaches
JakimPL Sep 5, 2026
e87425b
Retired: the explorer logic object that forwarded every member
JakimPL Sep 5, 2026
c1cef07
Drove: the settings card's whole wiring chain from a click to the row…
JakimPL Sep 5, 2026
42f7a8f
Recorded: what the Main tab's own surface still renames
JakimPL Sep 5, 2026
e4778a0
Opened: a gathered folder onto the recordings it holds
JakimPL Sep 5, 2026
21eb76e
Held: a scrolling region to what it works out rather than what it mea…
JakimPL Sep 5, 2026
5c13baf
Offered: every gathered recording to the question of what a mix holds
JakimPL Sep 5, 2026
69b32c0
Named: the channels once above the rows that stand under them
JakimPL Sep 5, 2026
c4fa0ab
Divided: the converter card into the sections it reads as
JakimPL Sep 5, 2026
9b15311
Held: the gathered list to a ceiling it scrolls inside
JakimPL Sep 5, 2026
d31227b
Drew: the settings card as one row of the list's own grid
JakimPL Sep 5, 2026
2f1991f
Asked: which recordings to mix in the shape the card lists them
JakimPL Sep 5, 2026
5833bf0
Carried: the run's shape between launches and the whole tree below a …
JakimPL Sep 5, 2026
d0d5318
Held: the converter card to a surface test and the bands to a mix of two
JakimPL Sep 5, 2026
b282197
Held: the gathering of a folder to the gesture that asks for it
JakimPL Sep 5, 2026
8e7796c
Moved: the reconstruction card below the list whose row it reads
JakimPL Sep 5, 2026
eeaa582
Held: a rebuilt region at the position the reader scrolled it to
JakimPL Sep 5, 2026
e0701f9
Capped: the pick a mix is built from at the room it has
JakimPL Sep 5, 2026
8155683
Gathered: the recordings a reader picks out of an overflowing folder
JakimPL Sep 5, 2026
7a1b834
Read: a folder beside the interface, saying how far it has got
JakimPL Sep 5, 2026
85ec6db
Toned: a channel's name the colour its boxes are drawn in
JakimPL Sep 5, 2026
1486b75
Answered: a folder holding no recordings when the reading comes back
JakimPL Sep 5, 2026
b4ac51b
Stated: the crossing the render-thread helper actually answers
JakimPL Sep 5, 2026
584bc7f
Drew: the bend a recording was converted with beside its channel
JakimPL Sep 5, 2026
d65b2f8
Recorded: the reconstructions an earlier build left unreadable
JakimPL Sep 5, 2026
dbcaa2c
Settled: what a record stamped with the pending data version means
JakimPL Sep 5, 2026
6f5afe6
Cleared: the dead declarations and the readings that hid what they an…
JakimPL Sep 5, 2026
d347c9d
Fixed: a folder's region writing back the scroll it had just read
JakimPL Sep 5, 2026
9aa3c83
Drew: a level caption in the weight a section header takes
JakimPL Sep 5, 2026
627fd97
Fixed: a stems list repainting the widgets a second opening took down
JakimPL Sep 5, 2026
5d17513
Fixed: an open folder cutting off the rows at its end
JakimPL Sep 6, 2026
d9425df
Measured: what a converter holding ten thousand recordings costs
JakimPL Sep 6, 2026
032a831
Recorded: the deallocation on a stateless thread the crash lands in
JakimPL Sep 6, 2026
ed17429
Recorded: the render loop racing the thread DearPyGui calls back on
JakimPL Sep 6, 2026
f753511
Held: a widget's callback for the frame that drew it
JakimPL Sep 6, 2026
90a517d
Held: a stems list to the room its owner gives it
JakimPL Sep 6, 2026
584620c
Widened: the column a row's own box stands in
JakimPL Sep 6, 2026
5737a62
Filled: a half-held box in its own tone
JakimPL Sep 6, 2026
8580aa5
Offered: a folder beside the recordings a mix already holds
JakimPL Sep 6, 2026
4290927
Divided: the settings row among the cards standing in it
JakimPL Sep 6, 2026
164643b
Matched: the general settings card to the advanced one
JakimPL Sep 6, 2026
e6acb70
Moved: gathering a recording onto the double-click
JakimPL Sep 6, 2026
50cd1ce
Brightened: a half-held box against the box beside it
JakimPL Sep 6, 2026
a21c46f
Named: every reconstruction an overwrite would replace
JakimPL Sep 7, 2026
0decf28
Carried: a folder walk's answer with the walk that earns it
JakimPL Sep 7, 2026
a0b9f57
Held: a region to the body it still has
JakimPL Sep 7, 2026
b010ef3
Reworded: the converter's messages in plain English
JakimPL Sep 7, 2026
d113363
Removed: the converter methods nothing calls
JakimPL Sep 7, 2026
aef4c97
Asked: the model for the ceiling a mix holds to
JakimPL Sep 7, 2026
547608e
Stated: four docstrings by what the code does
JakimPL Sep 7, 2026
160a9da
Tightened: the tests that read their own mocks back
JakimPL Sep 7, 2026
b1a3f44
Moved: the render-thread crossings into a document of their own
JakimPL Sep 7, 2026
3c47f56
Stated: each fact in the document that owns it
JakimPL Sep 7, 2026
7d23db0
Collapsed: three test rules into the one they are facets of
JakimPL Sep 7, 2026
2e17775
Recorded: four places the branch stands apart from the contract
JakimPL Sep 7, 2026
281fab6
Held: a folder walk's claim until its answer has gone out
JakimPL Sep 7, 2026
98012bd
Closed: the scan window on the gesture that stops the walk
JakimPL Sep 7, 2026
9d143fb
Named: what a folder holding no recordings answers with
JakimPL Sep 7, 2026
36838cd
Read: the benchmark's growth without the collector's share in it
JakimPL Sep 7, 2026
2e7f4a2
Corrected: the guide and the documents where the code moved past them
JakimPL Sep 7, 2026
6c505b1
Held: a case's path expectations to the platform running them
JakimPL Sep 7, 2026
7daab2a
Checked: that no case holds rendered text against a spelled-out literal
JakimPL Sep 7, 2026
0e45ae9
Named: the settings card after the source it edits
JakimPL Sep 7, 2026
7f17b45
Rewrote: the interface guide
JakimPL Sep 7, 2026
183c0e8
Stated: the channel choice plainly in the configuration guide
JakimPL Sep 7, 2026
53bbe21
Corrected: the first steps and the tab they open
JakimPL Sep 7, 2026
c512eda
Added: the rules a guide page is held to
JakimPL Sep 7, 2026
0960a1f
Named: the Reconstruction tab as the application labels it
JakimPL Sep 7, 2026
5fc1bdd
Removed: the opening paragraph that said twice what its sections say
JakimPL Sep 7, 2026
c2681e7
Improved: documentation
JakimPL Sep 7, 2026
b6d1abb
Toned: the folder scan's Stop as the cancel it is
JakimPL Sep 7, 2026
eacbdc1
Lined: the list's names and columns up across its tables
JakimPL Sep 7, 2026
e154653
Redrew: the folder a recording left, and nothing around it
JakimPL Sep 7, 2026
c635a36
Answered: the keyboard on the row the converter list holds
JakimPL Sep 7, 2026
df5a69c
Stated: the converter's keys and what the list still owes
JakimPL Sep 7, 2026
7766da7
Measured: the load bounds on a clock every platform can read
JakimPL Sep 8, 2026
ddc9d90
Rested: the row gestures a stems list has no owner for
JakimPL Sep 8, 2026
6f5a92e
Sounded: a recording from the question of which to mix
JakimPL Sep 8, 2026
9420e4c
Commit: Stood: a folder's row in the rhythm of the rows around it
JakimPL Sep 8, 2026
1b1820b
Spelled: the prose words that had drifted British
JakimPL Sep 8, 2026
eeb382f
Spelled: cancelling as canceling, keys and enums included
JakimPL Sep 8, 2026
76f902b
Spelled: travelling and materialise the American way
JakimPL Sep 8, 2026
4c2c43a
Read: each -wards form for the sentence it sits in
JakimPL Sep 8, 2026
fbdefaf
Stated: which British spellings a third-party name keeps
JakimPL Sep 8, 2026
205cc23
Added: a separator between converter context menu items
JakimPL Sep 8, 2026
adf8e87
Banded: a group's row apart from the recordings around it
JakimPL Sep 8, 2026
69571ba
Improved: converter visuals
JakimPL Sep 9, 2026
a149d0c
Ruled: the settings card's grid and held removal to the list's own rule
JakimPL Sep 9, 2026
8d0bc24
Picked: a row on every click and left Esc to playback alone
JakimPL Sep 9, 2026
c2ff98d
Printed: the removal key on its menu item and stated the callback note
JakimPL Sep 9, 2026
20f01f3
Tested: the gutter, the shape, the indents and the gestures nobody an…
JakimPL Sep 9, 2026
9447637
Tidied: the marker's centering, the body's width and where sources is…
JakimPL Sep 9, 2026
0015597
Picked: the row a menu stands over
JakimPL Sep 9, 2026
72752cc
Extracted: hashing into sampletones_shared and gave every runtime-nam…
JakimPL Sep 9, 2026
02eaf51
Cleared: what a timed gesture built, and kept the batch that qualified
JakimPL Sep 9, 2026
b36968d
Stated: the folder's reserve once, and every docstring as what it does
JakimPL Sep 9, 2026
5f14ccf
Tested: folder removal, the menu a right-click raises, the indents an…
JakimPL Sep 9, 2026
f605ac8
Named: each case for what it checks, and read the keys from the schem…
JakimPL Sep 9, 2026
1cbef63
Settled: a region once more after it takes a new height
JakimPL Sep 9, 2026
589cf46
Tested: the reports a menu item makes and the frames a region settles…
JakimPL Sep 9, 2026
5d63e89
Took: every height through the one place that notes the move
JakimPL Sep 9, 2026
4e58a8a
Answered: a gesture for the widgets still standing
JakimPL Sep 9, 2026
0072d62
Timed: a batch through the standard library's own clock
JakimPL Sep 9, 2026
f7e4724
Stated: the identity rule where tags are governed, and joined the str…
JakimPL Sep 9, 2026
d5aae92
Corrected: the page that promised a scan the walk gives up
JakimPL Sep 9, 2026
899933a
Answered: one question per rule across the stems list
JakimPL Sep 9, 2026
cf76da3
Tested: what the range changed in passing
JakimPL Sep 9, 2026
c22c250
Pinned: the settling frame and the fields a reshape reads
JakimPL Sep 9, 2026
c76b2c4
Recorded: the scopes a key reaches and the deviations left standing
JakimPL Sep 9, 2026
a9481a6
Held: the picked row to the reading rather than to the widgets
JakimPL Sep 10, 2026
fc9611a
Said: the drag a row takes, and the click it takes instead
JakimPL Sep 10, 2026
467074e
Opened: the row geometry at the height its layout gives a row
JakimPL Sep 10, 2026
f91c988
Tested: what a stems list says, and toned the heading with its boxes
JakimPL Sep 10, 2026
4288cc9
Named: the panel key scope once, and the rule a row leaves by
JakimPL Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ repos:
pass_filenames: false
verbose: true

- id: rendered-literals
name: rendered literals
entry: uv run scripts/checks/rendered_literals.py
language: system
files: ^tests/.*\.py$
pass_filenames: false
verbose: true

- id: tag-names
name: tag names
entry: uv run scripts/checks/tag_names.py --all
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: help setup install build release system-deps run clean pre-commit test benchmarks \
ftm-samples nsf-samples nsf-render compression-report icons player check-import-boundary check-tag-names check-unused-tags \
ftm-samples nsf-samples nsf-render compression-report icons player check-import-boundary check-tag-names check-unused-tags check-rendered-literals \
check-language-keys check-palette-colors check-shortcut-actions calibration lint pylint mypy format

ifeq ($(OS),Windows_NT)
Expand Down Expand Up @@ -113,7 +113,7 @@ test:
$(call script,dev/tests)

benchmarks:
uv run python -m pytest tests/benchmarks --no-cov
uv run python -m pytest tests/benchmarks --no-cov -s

ftm-samples: export SAMPLETONES_FTM_OUTPUT_DIR := build/ftm
ftm-samples:
Expand Down Expand Up @@ -145,6 +145,9 @@ check-tag-names:
check-unused-tags:
uv run scripts/checks/unused_tags.py

check-rendered-literals:
uv run scripts/checks/rendered_literals.py

check-language-keys:
uv run scripts/checks/language_keys.py

Expand Down
7 changes: 4 additions & 3 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ from sampletones import (
| `Config` | generation configuration; build it with `Config.load(path)` or `Config.default()` |
| `Window` | analysis window derived from a config (`Window.from_config(config)`) |
| `InstructionLibrary` | the library of candidate instructions a reconstruction searches |
| `Reconstructor` | runs a reconstruction: `Reconstructor(config)("sample.wav")` |
| `Reconstructor` | runs a reconstruction: `Reconstructor(config, channels)("sample.wav")` |
| `Reconstruction` | the result of a reconstruction — its approximation audio, per-channel instructions, and the config used |
| `ChannelName` | enum naming the four channels: `pulse1`, `pulse2`, `triangle`, `noise` |
| `Generator` | shared base class of the oscillator generators |
Expand Down Expand Up @@ -92,12 +92,13 @@ With a library in place for the configuration:
```python
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
reconstructor = Reconstructor(config)
# Prepare the reconstructor for the channels the run may use
reconstructor = Reconstructor(config, frozenset(DEFAULT_CHANNELS))

# Reconstruct an audio file and save the reconstruction
reconstruction = reconstructor("sample.wav")
Expand Down
25 changes: 16 additions & 9 deletions docs/concepts/reconstruction.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ below that is room the matching leaves unused, and material that was never in A=
temperament — most recordings of most instruments — sits somewhere inside it.

`sampletones_core.reconstructions.reconstructor.refinement` spends that room, after the decoder has
settled which note each frame plays and before the frames are rendered.
settled which note each frame plays and before the frames are rendered. It spends it where the run
asks: a stem entry names the channels it carries toward its own recording, so one recording's bass
line can land on its exact tuning while another's lead keeps the grid.

### 6.1 Reading rather than searching

Expand Down Expand Up @@ -304,14 +306,14 @@ frames with no pitch to read.

### 6.2 Landing the note, and holding it

A reading becomes a bend through the generator, which owns the divider geometry: `bend_towards`
A reading becomes a bend through the generator, which owns the divider geometry: `bend_toward`
answers with the divider steps that land the note nearest the frequency read, bounded by
`bend_range` — **half the gap to each neighboring note**. That bound is what leaves the refined
pitches gapless: note *n* covers `[(tₙ + tₙ₊₁) / 2, (tₙ + tₙ₋₁) / 2]`, and those windows tile the
divider range exactly, so every divider the notes span is reachable and none is claimed twice.

A bend that followed every reading exactly would jitter, and jitter is more audible than the tuning
it chases. So the per-frame proposals are settled by a change-penalised walk, the same shape the
it chases. So the per-frame proposals are settled by a change-penalized walk, the same shape the
Viterbi decoder settles a note contour with: the cost of a bend is how far it stands from that
frame's reading, plus a toll on changing at all. The states a frame may take are the bends its
neighborhood proposed together with no bend, which keeps the walk to a handful of states even where
Expand All @@ -329,14 +331,19 @@ a tenth or more, since the reading needs a handful of bins per frame and the tra
every bin the spectrum covers. Restricting it to the bins the chosen notes actually name is the
work `docs/development/bugs-and-todos.md` records under **Features**.

A frame makes no proposal where it rests, where its channel is not pitched — the noise channel's
sixteen periods have no finer grid — or where its reading falls below the confidence threshold. A
conversion that bent no note records both bend dimensions as ones the channel governs, so it writes
the same instrument it wrote before the feature existed.
A frame makes no proposal where it rests, where the stem holding it leaves that channel out, where
its channel is not pitched — the noise channel's sixteen periods have no finer grid — or where its
reading falls below the confidence threshold. A conversion that bent no note records both bend
dimensions as ones the channel governs, so it writes the instrument an unrefined run writes.

Which recordings are carried, and on which channels, each stem entry states for itself in
`bends` — a subset of the channels it occupies, and of the three that load a divider. A channel a
stem leaves out keeps the note the matching chose, and a stem carrying nothing at all is never
read, so the transform is spent only where a bend comes of it. The settings below shape a bend
once it is asked for, and hold for a whole run.

| parameter | default | notes |
|---|---|---|
| `generation.refinement.enabled` | on | acts only where the run renders the chosen instructions |
| `generation.refinement.confidence` | 0.15 | the share of a frame's energy its harmonics must hold |
| `generation.refinement.change_weight` | 2.0 | divider steps of reading error worth avoiding one change |
| `generation.refinement.window` | 4 | the frames on either side whose readings a frame may settle on |
Expand Down Expand Up @@ -383,7 +390,7 @@ noise):
| spectral / temporal weight | 0.8 / 0.2 | criterion blend |
| spectral distance | β-divergence | also `squared`, `absolute` |
| selector | Viterbi | `greedy` / `viterbi` |
| pitch refinement | on | bends each note onto the divider the source sounds |
| pitch refinement | per stem | bends each note onto the divider the source sounds |
| normalize / quantize | on / off | input preprocessing |

Package map:
Expand Down
17 changes: 12 additions & 5 deletions docs/concepts/stems.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,20 @@ single-sample pipeline always did.
A request becomes jobs through `reconstructions.converter`: a `ConversionPlan`
answers with the `ConversionJob`s it divides into, resolved against the
configuration the run uses. `GroupConversion` mixes the recordings it is given
into one job, and `DirectoryConversion` scans a folder into one single-source job
per audio file. `ReconstructionConverter` runs those jobs across its worker pool
into one job; `BatchConversion` gives each gathered recording a job of its own,
carrying the setup that recording's own row holds and the folder whose tree its
reconstruction mirrors; and `DirectoryConversion` scans a folder into one
single-source job per audio file, which is what the command line converts a
directory as. `ReconstructionConverter` runs those jobs across its worker pool
and reports the reconstructions written.

`StemsConfig` (`reconstructor/stems/configs/`) is the setup: the entries with
their ids and channels, the precedence hierarchy and its mode, and the channel
cap. It validates its own consistency — unique ids, a hierarchy naming every
`StemsConfig` (`reconstructor/stems/configs/`) is the setup: the entries, the
precedence hierarchy and its mode, and the channel cap. An entry is an id and the
`StemSettings` its recording is converted with — the channels it may occupy, and
which of those it carries toward the divider it really sounds. A further
per-recording choice is a field on those settings, which is what lets the list a
reader sets a run up in, the entry the run records, and a later reader of that
record all state the same thing. It validates its own consistency — unique ids, a hierarchy naming every
entry exactly once, a cap of at least one — so an inconsistent setup can be
neither built nor stored, and it derives the views the run reads (`entries_by_id`,
`covered_channels`, `frame_budget`).
Expand Down
Loading