Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion docs/development/application/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Which row carries the configuration follows the branch. In the configuration bra
## The shaping rules

* **Prune.** A heading the browser wrote that gathers nothing leaves, deepest first, so a whole chain of them goes at once and a reconstructions directory with nothing to show stays silent. A folder the disk holds stays, since the configuration branch mirrors the disk.
* **Collapse.** A heading above a single row folds into that row, which takes the joined name and rises into its place. The surviving row keeps its node type, path, configuration and children, so its click behavior, theme, context menu and favorite star carry over. A fold that would repeat a name already beside it stays open, and the branch roots stay in place. With a single configuration present, the configuration branch reads as one row per reconstruction, and it grows back into groups as soon as a second configuration arrives.
* **Collapse.** A heading above a single row folds into that row, which takes the joined name and rises into its place. The surviving row keeps its node type, path, configuration and children, so its click behavior, theme, context menu and favorite star carry over. A fold that would repeat a name already beside it stays open, and the branch roots stay in place. With a single configuration present, the configuration branch reads as one row per reconstruction, and it grows back into groups as soon as a second configuration arrives. A heading naming something somebody chose — an audio file, a source folder — hands the row a name that is no longer the configuration's own text, and the row records that it gathered a plain name. A reader of configuration text meets it as the plain name it has become, which is why such a row reads in the default font while a chain of configuration headings stays in the fixed-width one.
* **Order.** Containers come ahead of leaves, then rows follow a natural sort over the label, so a row sits where its displayed name puts it and `8 kHz` precedes `44.1 kHz`. The pass runs once every label is final. The branches directly under the container root keep the order the builder gives them.
* **Unique sibling labels.** Where siblings would read alike, every member of that label takes its short configuration hash. One rule serves the channel directories under a transformation group, the nested configuration directories, and the variants under a sample.

Expand All @@ -42,6 +42,8 @@ Which row carries the configuration follows the branch. In the configuration bra

**What a row answers follows its kind.** A reconstruction plays on a click, opens on a double click, and offers its path items, the tab's own actions and the favorite mark. A directory offers its path items and the favorite mark. A group or a sample represents no path, so its menu reads the subtree: how many reconstructions it gathers, expanding and collapsing everything below it, the label the tree shows it by, and, on a sample, the audio its reconstructions were made from, answered through any one of them.

**A row naming a reconstruction says what made it and what it is made of.** The configuration comes from a directory name. A row carries it where the configuration is what distinguishes that row, and reads it from the directory above otherwise, so both branches say the same thing about one file. The recordings come from the document, which is the only place that knows them. A row asks for them as the pointer reaches it, and the answer is kept against the moment the file was last written. One reading answers for both tabs. A pointer crossing many rows leaves the reading of the row it comes to rest on, and an answer landing after the details were built rebuilds them where they stand. Each recording is listed under the color its place on the record gives it, the color [the stems card](stems.md) paints it in. A document naming one recording says what the row already says, so the list is left to the documents holding several.

**Favorites are paths.** A row is a favorite when its path is in the session's set, and it reads as part of a favorite folder when any of its parents is. A reconstruction therefore reads as part of a favorite folder wherever a view puts it, including the sample branch, whose headings carry no path. One path reaches the panel as several rows, so `application.py` resolves the toggled path into every row representing it and hands them to both tabs. Each row repaints with the ancestry its own path carries.

## Filtering
Expand Down
2 changes: 2 additions & 0 deletions docs/development/application/playback.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Each verb is an action, so the combination it answers to is the shipped scheme's

The left button carries three gestures: a click seeks, a drag pans the view, and a double-click fits it to the audio. `PlotClickGesture` (`ui/elements/graphs/gesture.py`) settles which one a press was, so the playhead and the view each answer only the gesture meant for them.

The wheel carries the view too. Scrolling zooms x, and Shift with the wheel zooms y. Alt with the wheel pans x, and Alt and Shift pan y, by `pan_factor` of the span in view per notch, stopping at the bounds the graph constrains its axes to. `PlotWheelPan` (`ui/elements/graphs/pan.py`) makes those moves. `GUIGraph` locks both axes on every hover frame Alt is held, so the plot's own zoom leaves the wheel to the pan, and a lane linked to the waveform holds the same lock. The spectrum, whose view is fixed to its band, does not register the pan.

Because the target prefers the active tab's own source, Play/Pause acts on what the user is looking at whenever that screen can play something. On a screen that plays nothing of its own, it reaches the source already sounding. Those screens are the Main tab, an empty Reconstruction or Instructions tab, and the Sequencer before a project is open. So it resumes a paused reconstruction from the Main tab, and starts the song on the Sequencer with a project open.

## What the surfaces show
Expand Down
5 changes: 3 additions & 2 deletions docs/development/application/stems.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Opening the document loads the recorded stems through `load_stems`, the same cal

The document's name follows the naming rules in `sampletones_core.reconstructions.naming`, applied to the recorded paths in order. A single source names the document after the file's stem. Several stems that share one directory name it after that directory. Paths that share no directory fall back to the `.stn` filename.

The Stems card names every recorded path, one row per stem. Each row has its own full-path tooltip and reveals its recording on a click. The Audio source panel keeps the reconstruction's own file and the choice between the two waveforms. Locating reveals every recorded path at once, in one window with every stem selected where the file manager supports that, and in one window per directory otherwise.
The Stems card names every recorded path, one row per stem. Each row has its own full-path tooltip and reveals its recording on a double-click. The Audio source panel keeps the reconstruction's own file, the choice between the two waveforms and the engine rate, which a document living on disk retimes through `set_nes_frequency` and a project sample leaves to the project. Locating reveals every recorded path at once, in one window with every stem selected where the file manager supports that, and in one window per directory otherwise.

## The stems card

Expand All @@ -31,7 +31,8 @@ The reconstruction tab's Stems card turns the recorded assignment into a listene
5. **The selection follows the open document.** The card lives with the reconstruction it describes. Opening a document seeds the rows and the ticked boxes, a regenerated reconstruction keeps what the reader chose and ticks the channels a stem newly reaches, and closing the document empties the card.
6. **Listening choices stay out of the document.** The ticked set is session state, like every choice that shapes what is heard (see [Playback](playback.md)). Saving the reconstruction records the assignment and not the selection. The banding works the same way: collapsing the levels changes how the card draws and never what it describes.
7. **Removing a recording edits the document.** Where a box steers listening, the remove button rewrites what is described. The change is asked about first and recorded in the project history, and [Editing a stems reconstruction](#editing-a-stems-reconstruction) says what it releases. A reconstruction holds at least one recording, so the last row standing keeps its button disabled.
8. **The ribbon shows what the record holds.** Under the waveform, a lane per channel in play divides into the stretches one recording holds throughout, each painted in that recording's color. A recording takes its color from the place it holds on the record, so one recording reads alike wherever it is drawn. The lanes appear only where more than one owner is in play, either a recording the record names or the row the frames a reader wrote gather under, since a document with a single owner has nothing to tell apart. A stretch reads solid where the reader hears the recording holding it, faded where the reader left it out, and in the surface's own ground where the frames rest. It names its owner whether or not it is listened to. The instruments panel paints the same stretches in a band beneath each dimension's bars.
8. **The ribbon shows what the record holds.** Under the waveform, a lane per channel in play divides into the stretches one recording holds throughout, each painted in that recording's color. A recording takes its color from the place it holds on the record, so one recording reads alike wherever it is drawn — on this card, under the waveform, beneath an instrument's bars, and in the list [the browser](browser.md) shows on hovering a reconstruction. A lane appears for every channel in play, one unbroken stretch where a single owner holds it throughout, so a channel's lane answers whether it sounds and by whom independently of every other channel's. A stretch reads solid where the reader hears the recording holding it, faded where the reader left it out, and in the surface's own ground where the frames rest. It names its owner whether or not it is listened to. The instruments panel paints the same stretches in a band beneath each dimension's bars, where a dimension answering to a single owner has nothing to tell apart and draws no band.
9. **The menu makes the same choices as the boxes.** A right-click on a row offers Mute or Unmute, Solo or Unsolo, and the file items every listing offers. Mute and Unmute set the recording's choice across every channel it offers. Solo hears one recording on every channel it offers and silences the others, and it remembers the choice it replaced, so Unsolo returns to it whichever recordings were soloed in between. A choice made by hand, or a regenerated document, ends that memory, and Unsolo then hears every recording whole. The frames a reader wrote have no file, so their row offers no file items. Both choices stay session state, like the boxes.

### What the code guarantees

Expand Down
7 changes: 4 additions & 3 deletions docs/development/application/vocabularies.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ and exempt.
### Text resolves where it is displayed

A class that reads text holds the manager as `self._language_manager`, assigned in its own `__init__`, and
looks each string up at the point of use, so a language change takes effect on the next read. Where the
same text is read at more than one site in a class, one named binding serves them all and the reads stay in
step.
looks each string up at the point of use, so a language change takes effect on the next read. A class that
inherits the manager declares it in its body instead, `_language_manager: LanguageManager`, which states
the same thing to a reader and to the hook. Where the same text is read at more than one site in a class,
one named binding serves them all and the reads stay in step.

### The forms a lookup takes

Expand Down
4 changes: 4 additions & 0 deletions docs/formats/bitphase.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ is the same cell you would see in the tracker.
| Speed | 1–255 | the groove's tick counts, bounded to that range |
| DPCM channel | present | rests, apart from the groove trigger each pattern's first row carries |

A row that names a voice on a channel the voice has no instrument for plays nothing in the song, so the
exporter writes a note cut on it and reports the row by its frame, channel and row. The project export
dialog lists those rows.

Tables and instruments are numbered together, and each slice takes one of each. The table column is
therefore what a wide document reaches first, and the exporter raises an error instead of writing a
document whose later voices cannot be named. A song whose rows vary spends one of those ids on its groove,
Expand Down
4 changes: 4 additions & 0 deletions docs/formats/famitracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ that ends its note.
| Tempo / speed | engine-dependent (split at row `speed_split_point`) | tempo 32–255, speed 1–31 | written verbatim from settings |
| DPCM samples | 64 | not modeled | always empty |

A row that names a voice on a channel the voice has no instrument for plays nothing in the song, so the
exporter writes a note cut on it and reports the row by its frame, channel and row. The project export
dialog lists those rows.

The exporter also reserves an empty pattern index per channel (`max used index + 1`) for order slots the
song leaves unset. A channel that already fills indices up to 127 leaves no room for it, and the exporter
reports this instead of writing a corrupt order.
Expand Down
13 changes: 9 additions & 4 deletions docs/guide/reconstruction.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,23 @@ If the reconstruction you have open has unsaved changes, opening another one ask
first.

The **Source** card switches playback between **Reconstruction** and **Original audio**, so you can
compare the two. The **Waveform** card has a checkbox for each channel, and keys `1` to `4` switch the
same checkboxes.
compare the two. Its **NES frequency** field retimes the reconstruction: type a new rate and press
`Enter`. The field is locked for a reconstruction that belongs to a project, which follows the project's
rate.

The **Waveform** card has a checkbox for each channel, and keys `1` to `4` switch the same checkboxes.

Click the waveform to play from that point. While playback is paused, a click moves the playback
position. Drag the waveform to move the view, and double-click to fit the view.
position. Drag the waveform to move the view, and double-click to fit the view. Scroll to zoom, hold **Alt** and
scroll to move the view sideways, or hold **Alt** and **Shift** and scroll to move it up and down.

## Hearing what each recording contributed

The **Stems** card lists the recordings a reconstruction was built from, grouped by the
[level](converting.md#one-reconstruction-each-or-one-mix-from-all) each one was given. A row has a
checkbox for each channel the recording used, and the checkbox at the front switches all of them.
Double-click a row to show the recording in your file browser.
Double-click a row to show the recording in your file browser. Right-click a row to **Mute** the
recording, hear it alone with **Solo**, or copy its name or path. **Unsolo** brings back the mix you had.

Uncheck a box to hear the reconstruction without that recording on that channel. The waveform, the
playback, the original audio and a WAV export all follow the checkboxes, so you can hear what each
Expand Down
4 changes: 4 additions & 0 deletions docs/guide/sequencer.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ rows.
- **Bitphase project...** saves a `.btp` file.
- **NSF program...** saves an `.nsf` file, which the NES or an NSF player plays directly.

A voice plays on the channels its instruments cover. Where a row names a voice on another channel, the
FamiTracker and Bitphase files hold a note cut on that row, which is how the song plays it. The dialog
that announces the export lists those rows by frame, channel and row.

An NSF program has room for 32 KB, so the app tells you when a song is too long. See [NSF
export](../formats/nsf.md) and [song compression](../concepts/compression.md).

Expand Down
15 changes: 13 additions & 2 deletions src/sampletones_application/application.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Any, Dict, Final, Optional
from typing import Any, Dict, Final, Optional, Tuple

import dearpygui.dearpygui as dpg
from pydantic import ValidationError
Expand Down Expand Up @@ -638,6 +638,8 @@ def __init__(
sequencer_tab=self._sequencer_tab,
instructions_tab=self._instructions_tab,
)
self.browser_manager.on_recordings_read = self._show_reconstruction_recordings

self._setup_gui()
self._restore_current_items(
library_path=library_path,
Expand Down Expand Up @@ -1105,6 +1107,15 @@ def _refresh_browsers(self) -> None:
self._reconstructions_tab.refresh_browser()
self._sequencer_tab.refresh_browser()

def _show_reconstruction_recordings(self, path: Path, names: Tuple[str, ...]) -> None:
"""Hands a document's recordings to both browsers, whichever of them asked for the reading.

The two browsers render one tree and read one set of documents, so a reading answers for
each of them and the row it belongs to is the one that shows it.
"""
self._reconstructions_tab.show_browser_recordings(path, names)
self._sequencer_tab.show_browser_recordings(path, names)

def _repaint_reconstruction_favorites(self, node: FileSystemNode) -> None:
"""Repaints the toggled path in both browsers, whichever tab the star was clicked in.

Expand Down Expand Up @@ -1289,7 +1300,7 @@ def _apply_retuned_sample(self, retuned: RetunedSample) -> None:
self.reconstruction_manager.apply_edited(
retuned.reconstruction,
)
self._reconstructions_tab.update_reconstruction()
self._reconstructions_tab.update_reconstruction(refit_waveform=True)

def _open_project_properties(self) -> None:
"""Opens the properties dialog seeded with the current project's info.
Expand Down
Loading
Loading