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
145 changes: 47 additions & 98 deletions docs/development/bugs-and-todos.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ behind. Each entry says what is owed and why, and the code and the change that c
### Navigation

* Interface scale
* Tree navigation using keys
* Keyboard navigation of the converter's list of gathered recordings: a cursor the arrow keys move, `Home`
and `End`, and folders opened and closed from the keyboard.
* Keyboard navigation of the trees, and of the converter's list of gathered recordings: a cursor the arrow
keys move, `Home` and `End`, and folders opened and closed from the keyboard.
* Waveform LOD for zooming
* Drag and drop
* Multiple Reconstruction views
* In-project sample selection in Reconstruction view
* Drag and drop of browser nodes onto views, such as a reconstruction onto Samples.
* Tabs for several reconstructions in the Reconstruction view.
* Application installation progress bar

### Tracker
Expand All @@ -24,91 +22,51 @@ The first two entries are the settings an imported `.fti` reports as left behind
[FamiTracker export](../formats/famitracker.md#c-reading-an-instrument-file)). Each one closed is a
dimension the import starts carrying.

* Release points. A note-off cuts the channel today. A release segment needs the playback walk, the NSF
driver and `NoteOff` to gain one.
* Arpeggio modes. A sequence's `setting` byte says absolute. Fixed, relative and scheme need an enum of
their own, and scheme needs the item bit-packing FamiTracker gives it.
* The sample column reads the sample still playing from the top of each frame, so a frame's first rows
take no transpose or volume while playback still carries the previous frame's sample.
* A tracker export moves a whole contour by one written note, so where a transpose carries part of it
outside pitches 24–119, the ticks in-app playback clamps sound unclamped in the tracker. A clamped table
(Bitphase) or arpeggio (FamiTracker) per such transposition would make them exact.
* A FamiTracker transpose row moving a note more than fifteen semitones, or a shared pattern's cell that
frames reach needing different slides, is written without its slide and reported. A second effect
column, or a pattern cloned per frame, would carry it.
* A FamiTracker module's pulse level can sound a step away from in-app playback. FamiTracker rounds the
product of the two levels down and keeps the quietest level where that comes out silent, while the app
and Bitphase round it to the nearest step.
* A FamiTracker module places the longer rows of an uneven tempo by FamiTracker's own running count.
A compatibility setting writing a speed effect per row at tempo 150 would make it play the app's groove.
* Release points. A note-off cuts the channel today, so a release segment has nowhere to play.
* Arpeggio modes. Fixed, relative and scheme arpeggios import as absolute offsets today.
* Compatibility options for the FamiTracker export, so a module plays as the app does where FamiTracker's
own rules differ, such as how it rounds a pulse level and where it places the longer rows of an uneven
tempo.

### Workflow

* Waveform construction preview for single-file conversion
* Picking several rows of the converter's list at once, so a group leaves or settles in one gesture. The
widget family already draws a multi-pick reading, built for the mix chooser. A converter pick needs one
with no ceiling, and every gesture the list offers one row must reach each picked row.
* Selection operations on a reconstruction
* Reconstruction trimming
* Marking broken files where the browsers list them, so fewer notices reach the reader. A reconstruction
that fails to load, one whose recordings are missing and a recording that cannot be read could stand in a
warning or an error color. Today every attempt to open or play one raises its notice, once per attempt,
and a mark would tell the reader before they try.
* Waveform construction preview: a waveform drawn from the partial reconstruction while the converter
reconstructs a single file.
* Picking several rows of the converter's list at once, so a group leaves or settles in one gesture.
* Selecting parts of a reconstruction's waveform to crop, trim, cut and paste them along with their
instructions.
* Marking broken files where the browsers list them: a reconstruction that fails to load, one whose
recordings are missing and a recording that cannot be read could stand in a warning or an error color,
so the reader learns before trying to open or play one.

### Features

* In-application guide/tutorial
* Language selector
* Reading only the bins the refinement asks for. The pitch reading transforms every bin the spectrum
covers and uses a handful of them per frame. On a CPU build one transform is a tenth or more of a short
conversion. Restricting the kernel to the rows the chosen notes name is a slice. The care is that the
union of harmonic bins over a whole stream is wider than any one frame's.
* Keeping what a stopped folder scan found. Stopping a long walk drops the recordings met so far, while the
gathering already answers with them. Handing that list on would let a reader stop a long walk and keep
the count they watched climb. It needs the empty branch of the read reworked beside it, since a stop
before the first recording is a different answer from a folder with none.
* Calibrating the pitch refinement. The refinement's confidence threshold, change weight and window are
chosen by hand, and [the calibration](../tools/calibration.md) could measure them beside the criterion
blend. The change weight is the one with an audible trade-off: it decides how large a one-frame
excursion the walk follows and how large it absorbs, which is vibrato against jitter.
* Reading only the bins the refinement asks for. The pitch refinement bends notes by reading the audio's
spectrum, and it transforms every frequency bin the spectrum covers while using a handful per frame,
which is a tenth or more of a short conversion on a CPU build.
* Keeping the recordings a stopped folder scan has found, so stopping a long walk keeps the count the reader
watched climb.
* Calibrating the pitch refinement. The settings that decide how a pitch reading bends a note (a
confidence threshold, a change weight and a window) are chosen by hand, and
[the calibration](../tools/calibration.md) could measure them. The change weight trades vibrato against
jitter.

### Technical

* Leading the calibration report with `mr-loudness-dB`. The report lists `mr-auditory-dB` first, which
reads silence as closer to a tone than any render. The order changes once by-ear ratings of a sweep hold
the loudness-weighted referee at ρ ≥ 0.6 in every category. A listening round scored `polyphony-chord`
6–7 dB better on a render that dropped the noise channel entirely, so the bar is unmet.
* An axiom that a recording built with noise reconstructs with the noise channel sounding. The corpus
knows which items were synthesized from noise, and the render records hold the per-channel timelines.
Such a test would fence the criterion against noise deafness without a listening round, as
`referee/test_axioms.py` does for the referees.
* API documentation
* Code documentation
* Screen scenarios on Windows and macOS. They run on Linux alone: a scenario's pointer and keys reach the
application through X11, and whether DearPyGui opens its window on GitHub's Windows and macOS runners is
unverified. A run there starts from a spike that opens the application on each runner and presses one
control through the callback a click runs.
* What a build makes of the configuration and the session state an older one left behind. Neither has a
version, so neither travels an upgrade chain or has an archived corpus. A `state.yaml` naming a panel
that has since gone, or a configuration missing a setting added since, is read by whatever each loader
happens to do with it.
* The element enums that outlived their keys. An element enum is named only where a `_label(element)`
helper takes one, and the language-keys check expands such a helper over the whole enum, so a member no
call names is reached all the same. Spelling those keys literally at the call site makes each entry
exactly checkable and retires the enums that remain.
* Respecting FamiTracker limits at the writers. A target format's ceilings belong to the code that writes
it, and today only the sequence-length ceiling follows that rule. The instrument, sequence and pattern
counts should follow it too, so a project past one of them is reported to the reader and not refused by
the writer.
* Screen scenarios on Windows and macOS. They run on Linux alone, and whether DearPyGui opens its window on
GitHub's Windows and macOS runners is unverified.
* An upgrade path for the configuration and the session state. A file an older build left behind is read
as the loader happens to, and no archived files of older builds test it.
* The element enums that outlived their keys. The language-keys check treats a key named through an
element enum as the whole enum, so a key no call uses goes unnoticed. Spelling each key at its call site
makes the check exact.
* Per-tab undo routing
* A history of its own for a standalone reconstruction document, one loaded from disk and not opened as a
project sample. The engine is session-scoped to a project, so an edit to such a document is undoable
nowhere. Giving it a stack reuses the same engine ([undo](application/undo.md)). Until then, an edit that
silences a channel or lets a recording go is reversible only by reloading the file.
* In-application console
project sample. An edit to such a document is undoable nowhere ([undo](application/undo.md)), so an edit
that silences a channel or lets a recording go is reversible only by reloading the file.
* Improve performance of the browser's favorite scan of the entire tree per click
* The Command key on macOS. The macOS scheme and the text-field rule read Command as Super, and whether
DearPyGui reports it as Super there or swaps it with Ctrl is unverified on a Mac.

## Architecture

Expand All @@ -124,31 +82,22 @@ currently out of line. An entry leaves when the code meets the contract again.
* `application.py` and `ui/elements/tree/tree.py` each hold several concerns in one module, past the size
at which the sequencer panels were divided into subpackages. Each divides the same way: a module per
concern, with the class that stays holding the collaborators and the public surface.
* The Main tab's public surface renames several calls on its way to a panel (`refresh_converter_view`,
`is_converter_panel_visible`, `refresh_browser`). Those are the tab's own face, and the inbound callbacks
the Coordinators contract governs are forwarded as they stand. The open question is whether the face
needs those names at all, or whether the application should ask for the thing and not for the refresh of
it.
* `dpg_get_item_parent` catches `Exception`, where the Error Handling Policy leaves the broad catch to a
service's top-level task wrapper. Its docstring says why, and the catch narrows the day DearPyGui raises
a type of its own.
* Every fixture in the Main tab coordinator's tests builds the coordinator through `__new__` and fills its
privates by hand, so those cases describe a method and not the wired object. A case reading the
coordinator's own behavior cannot see a hook left unset. Building the object in that file closes the
gap.
* The tab coordinators' and the application's unit tests build their object through `__new__` and fill its
private fields by hand, so a case describes a method and not the wired object, and a hook left unset goes
unseen.
* Which recordings a mix is built from is decided in `ui/` until **Add** is pressed. The chooser holds the
pick as its own set and asks the view model how a gesture moves it, so a projection computes state
transitions. The logic layer hears only the answer. Principles 3 and 4 put that machine in `logic/`,
with the chooser drawing what a view model says and reporting the gesture. Moving it is a phase and not
a patch, because the dialog drives the pick today.
* `ConverterMessages` reads the strings it shows a reader once, at construction, where principle 8 has text
resolve at the point of use. The stage names and status lines are cached as fields, and the run's
templates are read live. The fix is to read each key where it is used and let the manager answer.
* Every gesture in the converter re-derives the whole setup. A gesture hands `ConverterLogic._rewrite` a
state whose recordings are new objects, so the rows and the batch entries are read from it cold, and the
garbage collector's own share falls inside them. On a very large folder that is long enough to feel as a
pause before a widget is touched, and all of it repeats work, since one recording changed. The answer is
to hold the rows against the gathering that produced them and derive entries for the recordings a gesture
moved.
* Every gesture in the converter re-derives the whole setup, so a gesture costs fifteen times as much on
10,000 recordings as on 1,000, much of it in garbage collection. A gesture hands
`ConverterLogic._rewrite` a state whose recordings are new objects, and the rows and the batch entries
are read from it cold. Holding the rows against the gathering that produced them, and deriving entries
for the recordings a gesture moved, closes it.

## Bugs

* The Sample column shows no sample on a frame's first rows, since its reading starts over at each frame. It
offers no transpose or volume there, while playback applies them to the sample the previous frame left
sounding.
3 changes: 1 addition & 2 deletions docs/tools/calibration.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,4 @@ closer to a sine than silence is". Rankings it is known to get wrong are recorde
### Which referee leads

The report lists `mr-auditory-dB` first. `mr-loudness-dB` takes the lead once by-ear ratings of a sweep
of renders agree with its scores. [Bugs and to-dos](../development/bugs-and-todos.md) tracks the bar
that agreement must reach.
of renders agree with its scores.
6 changes: 3 additions & 3 deletions src/sampletones_application/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,7 @@ def _refresh_busy_state(self) -> None:
panel reads the live ``_is_operation_active`` state for itself; this only nudges them to
re-apply, so the busy truth lives in one place. The menu follows the same edge, since what
grays an entry offering another such operation is one already running."""
self._instructions_tab.refresh_generate_button()
self._instructions_tab.follow_busy_state()
self._update_menu()

def _on_dialog_activity_changed(self) -> None:
Expand All @@ -1127,15 +1127,15 @@ def _on_dialog_activity_changed(self) -> None:
converter's own view, and the menu entries that would start another exclusive operation.
"""
self._refresh_busy_state()
self._main_tab.refresh_converter_view()
self._main_tab.follow_busy_state()

def _on_library_operation_changed(self) -> None:
"""Responds to a library generation starting or finishing: refreshes the cross-tab action
buttons and, additionally, the converter view so the Convert button reflects the library
operation. The converter's own view changes refresh only the action buttons, so this extra
converter refresh fires solely on library edges and stays clear of a refresh loop."""
self._refresh_busy_state()
self._main_tab.refresh_converter_view()
self._main_tab.follow_busy_state()

def _export_reconstruction_wav_dialog(self) -> None:
if self._reconstruction_coordinator.check_loaded():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ def _ask_before_exit(self, proceed: VoidCallback, decline: VoidCallback) -> None
on_cancel=decline,
)

def refresh_generate_button(self) -> None:
def follow_busy_state(self) -> None:
self._library_panel.refresh_action_buttons()

@property
Expand Down
7 changes: 6 additions & 1 deletion src/sampletones_application/coordinators/tabs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,12 @@ def _ask_before_exit(self, proceed: VoidCallback, decline: VoidCallback) -> None
def is_converter_panel_visible(self) -> bool:
return self._converter_panel.is_visible()

def refresh_converter_view(self) -> None:
def follow_busy_state(self) -> None:
"""Re-applies the converter's view to the busy state after another operation starts or ends.

The converter's own view changes already reach the busy state through
``on_busy_state_changed``, so this answers the edges of other operations alone.
"""
self._converter_logic.refresh_view()

def _take_up_path(self, path: Path) -> None:
Expand Down
Loading
Loading