Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
bfed33f
Fixed: the source sweep inside hidden folders and the deleted-files c…
JakimPL Oct 3, 2026
a9c723d
Fixed: note keys taking combinations meant for the shortcuts
JakimPL Oct 3, 2026
0eb01d4
Fixed: a project at an unreachable version refused by its version
JakimPL Oct 3, 2026
92113f6
Fixed: leaving where no output device is offered
JakimPL Oct 3, 2026
053b1ae
Fixed: envelope bars and stepper buttons answering a press that began…
JakimPL Oct 3, 2026
45935c2
Fixed: saving a project opened at start to its own file
JakimPL Oct 3, 2026
d730f66
Fixed: leaving with a Display size never confirmed
JakimPL Oct 3, 2026
92521d1
Fixed: a Keyboard settings row reading as listening after the reassig…
JakimPL Oct 3, 2026
1b25ccf
Fixed: a box clicked in the Converter list picks its row
JakimPL Oct 3, 2026
10175c2
Fixed: General settings filling its row while Advanced settings is pu…
JakimPL Oct 3, 2026
3b302be
Fixed: the Playback menu missing the song's transitions
JakimPL Oct 3, 2026
60ff7c7
Fixed: a run writing every reconstruction into the folder Destination…
JakimPL Oct 3, 2026
d481092
Fixed: two closes before the first is answered asking twice
JakimPL Oct 3, 2026
014a8f1
Fixed: a library folder pointed away from and back keeping its librar…
JakimPL Oct 3, 2026
85db526
Fixed: new voices reaching a pool with no project open
JakimPL Oct 3, 2026
d5c193e
Fixed: history lines naming a renamed or moved voice one way
JakimPL Oct 3, 2026
00764bb
Fixed: a reconstruction whose file was removed explaining its locked …
JakimPL Oct 3, 2026
42061e4
Merged: tests into bugfixes
JakimPL Oct 3, 2026
aef178a
Fixed: a missing recording leaving the reconstruction alone on the wa…
JakimPL Oct 3, 2026
933c488
Merged: bugfixes into bugfixes-main
JakimPL Oct 3, 2026
bfd8b22
Fixed: held callbacks reading rows a rebuild took away
JakimPL Oct 3, 2026
90a6e95
Fixed: a folder read outliving the exit and a folder asked for during…
JakimPL Oct 3, 2026
c922a3e
Merged: bugfixes into bugfixes-lifecycle
JakimPL Oct 3, 2026
7748933
Rewrote: the import-with-no-project scenario for the greyed pool entries
JakimPL Oct 3, 2026
82b83d6
Reworded: the lifecycle fixes' docstrings in positive terms
JakimPL Oct 3, 2026
185bd9f
Merged: bugfixes into bugfixes-sequencer
JakimPL Oct 3, 2026
b7a99c0
Merged: bugfixes-lifecycle into bugfixes
JakimPL Oct 3, 2026
4a29b0f
Merged: bugfixes-main into bugfixes
JakimPL Oct 3, 2026
e3938b9
Steadied: the Play-from-row scenario against the grid a playing song …
JakimPL Oct 3, 2026
ca64df1
Fixed: Export instrument in a two-tunings project failing with no mes…
JakimPL Oct 3, 2026
eb03911
Merged: bugfixes-sequencer into bugfixes
JakimPL Oct 3, 2026
8ca0fe0
Fixed: the song reporting its end while still holding the output
JakimPL Oct 3, 2026
3ac7eda
Fixed: Edit voice and the Converter's Load asking twice
JakimPL Oct 3, 2026
1dcbf36
Fixed: the exit running work left in its frame and losing the failure…
JakimPL Oct 3, 2026
eab23e0
Fixed: a file opened from the command line remembered by a relative path
JakimPL Oct 3, 2026
39da8b8
Fixed: a missed release holding the bar graph's press and a returning…
JakimPL Oct 3, 2026
908e0f0
Corrected: the docs and docstrings the review found out of step with …
JakimPL Oct 3, 2026
8b78474
Tightened: the leaving and kept-size cases to the buffer and the wind…
JakimPL Oct 3, 2026
4017e9b
Recorded: a rerun skipping a recording whose channels changed inside …
JakimPL Oct 3, 2026
75cb3ec
Fixed: the screen tier reading a double-click before its witness coun…
JakimPL Oct 3, 2026
f45bdfd
Merged: dev into bugfixes
JakimPL Oct 3, 2026
20f9b7c
Fixed: the screen tier's silent device playing through the sound serv…
JakimPL Oct 3, 2026
526f16b
Fixed: a folder read failing on a folder it may list but not enter, a…
JakimPL Oct 4, 2026
5da00bd
Fixed: a rerun skipping a recording whose channels changed, by writin…
JakimPL Oct 4, 2026
21d6cd5
Fixed: playing and applying Audio settings on a machine with no audio…
JakimPL Oct 4, 2026
e2eaf65
Fixed: Reconstruct file and Reconstruct directory converting at once …
JakimPL Oct 4, 2026
8a58519
Added: a tooltip on the greyed-out New instrument saying a project br…
JakimPL Oct 4, 2026
f023309
Fixed: Cancel in Display settings leaving a hand-sized window at the …
JakimPL Oct 4, 2026
33c2a50
Merged: bugfixes-device into bugfixes
JakimPL Oct 4, 2026
66f004e
Added: Alt+1 to Alt+4 as channel keys that answer while the digits ty…
JakimPL Oct 4, 2026
fce61bf
Fixed: the Display countdown leaving a hand-sized window at the neare…
JakimPL Oct 4, 2026
ed2b5bf
Removed: test helpers nothing calls, among them the screen tier's for…
JakimPL Oct 4, 2026
ddcfc62
Recorded: marking broken files in the browsers so fewer notices reach…
JakimPL Oct 4, 2026
f9ef08d
Fixed: a refused stream leaving the Playback menu on Pause and raisin…
JakimPL Oct 4, 2026
07b0dbf
Moved: screen scenario homes into a temporary folder, keeping a faile…
JakimPL Oct 4, 2026
09c79d6
Changed: a gesture asked for again while its question waits answering…
JakimPL Oct 4, 2026
19c40c4
Removed: the gates' ignore, with the reconstruction cases recording t…
JakimPL Oct 4, 2026
9349829
Fixed: library folders left behind keeping their libraries in memory
JakimPL Oct 4, 2026
e409fd6
Changed: the channel aliases to Ctrl+1 to Ctrl+4, with a text field k…
JakimPL Oct 4, 2026
edb9fdb
Fixed: the converter hashing its settings once per recording on every…
JakimPL Oct 4, 2026
e0348c0
Fixed: a run left with one job naming the folder in its status line
JakimPL Oct 4, 2026
06c495e
Fixed: a raise after an answer or a wait stranding a gesture's conver…
JakimPL Oct 4, 2026
b154964
Changed: every way of listing recordings refusing only during a conve…
JakimPL Oct 4, 2026
f211f85
Fixed: a folder listing only its names reading as one with no recordings
JakimPL Oct 4, 2026
ff63db0
Corrected: guide passages and docstrings that drifted from what the c…
JakimPL Oct 4, 2026
6c11ebf
Changed: the keybindings editor keeping every key of an action, a pre…
JakimPL Oct 4, 2026
2117219
Fixed: a stepper hold outliving a release made outside the window, sh…
JakimPL Oct 4, 2026
95785ba
Changed: the no-project notice naming voices for every gesture that a…
JakimPL Oct 4, 2026
e554a91
Merged: followups-converter into bugfixes
JakimPL Oct 4, 2026
8e3dd0f
Merged: followups-keys into bugfixes
JakimPL Oct 4, 2026
8e023b0
Fixed: a question that waited for the screen asking about what the an…
JakimPL Oct 4, 2026
bd67884
Fixed: a song on a stream the device refuses ending silently, with th…
JakimPL Oct 4, 2026
4a916e3
Fixed: a playback worker outliving its stop wiping or doubling the pl…
JakimPL Oct 4, 2026
ed57141
Removed: an exit case that exercised the callback queue alone, which …
JakimPL Oct 4, 2026
db48291
Fixed: the playback ownership table naming the guarded player as the …
JakimPL Oct 4, 2026
1e21993
Fixed: screen scenario homes stranded by a locked folder or a crashed…
JakimPL Oct 4, 2026
3498fd4
Merged: followups-questions into bugfixes
JakimPL Oct 4, 2026
52b9813
Fixed: a library folder loading on the way back a library the reader …
JakimPL Oct 4, 2026
ec51208
Fixed: a library folder reached again showing a version error where L…
JakimPL Oct 4, 2026
41f25a3
Fixed: library notices and confirmations naming a linked folder's tar…
JakimPL Oct 4, 2026
402014d
Fixed: a question that fails as it reaches the free screen leaving it…
JakimPL Oct 4, 2026
8047fc1
Fixed: a modal asked for in the frame after a failing turn jumping th…
JakimPL Oct 4, 2026
3c68a7c
Fixed: number fields keeping Alt chords they can't type, which blocke…
JakimPL Oct 4, 2026
5481bbc
Fixed: a stale keybinding override dropping the reassignments stored …
JakimPL Oct 4, 2026
a9a19da
Fixed: the keybindings editor keeping a key named twice, refusing a t…
JakimPL Oct 4, 2026
e917ecd
Changed: the guards waiting for the screen through the dialogs render…
JakimPL Oct 4, 2026
8e863b5
Moved: listing what a Reconstruct names and remembering its folder in…
JakimPL Oct 4, 2026
1d64f1a
Fixed: the exit leaving with a project a standing dialog left unsaved…
JakimPL Oct 4, 2026
7059b24
Corrected: docstrings and the stems document naming a missing accesso…
JakimPL Oct 4, 2026
9b3389b
Fixed: a playback worker a stop let go of reporting a late refusal wh…
JakimPL Oct 4, 2026
65577d3
Fixed: a device failing a write or a stop mid-playback escaping the p…
JakimPL Oct 4, 2026
7910571
Fixed: the audio backend torn down under a playback worker a stop let…
JakimPL Oct 4, 2026
514d000
Merged: sideeffects-keys into bugfixes
JakimPL Oct 4, 2026
f9f7c30
Fixed: a playback refused at its start opening with other words than …
JakimPL Oct 4, 2026
9bd09bb
Fixed: the modal line's description saying a window asked for again k…
JakimPL Oct 4, 2026
737fd48
Fixed: the screen tier's stale homes sweep reading every worker gone …
JakimPL Oct 4, 2026
39c0ee5
Merged: sideeffects-playback into bugfixes
JakimPL Oct 4, 2026
bc14d69
Fixed: the screen tier's scroll into view measuring a row before a fr…
JakimPL Oct 4, 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ __pycache__/
.venv/
.venv-build/
.vscode/
.worktrees/

dist/
wheels/
Expand Down
2 changes: 1 addition & 1 deletion docs/development/application/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Which row carries the configuration follows the branch. In the configuration bra

**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 the id it was converted as 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.
**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. A starred path the disk no longer holds stays starred, so a file on a drive unplugged for one run carries its star once the drive is back.

## Filtering

Expand Down
59 changes: 49 additions & 10 deletions docs/development/application/dialogs.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,34 @@ another one left in meets the same fate, since that frame still draws the one th

The screen therefore belongs to one conversation at a time. A conversation is a dialog, the modals it
hands the screen to while it steps aside, and the ones its answers raise. A modal asked for from anywhere
else, such as the report of a job that finished or a prompt raised by a gesture that waited for edits,
waits in line. It opens once the conversation holding the screen has ended, a frame after its last window
left, and the line opens in the order it was asked. A dialog asked for again while it waits keeps its place
with the newer request, and one hidden while it waits leaves the line.

`ModalQueue` (`utils/gui/modal_queue.py`) keeps the line, and `GUIWindow.show` is the only way into it, so
a caller raises a dialog whenever it has one to raise and never waits a frame of its own for the screen. A
window that reports work under way and leaves the rest of the interface live beside it is no modal, so it
opens at once.
else, such as the report of a job that finished, waits in line. It opens once the conversation holding the
screen has ended, a frame after its last window left, and the line opens in the order it was asked. A dialog
asked for again while it waits takes one place, at the back of the line, with the newer request. One hidden
while it waits leaves the line.

**A question reads what it asks about once the screen is free for it.** A guard with something to ask
takes a turn in the line, and reads its state again when the line reaches that turn. The conversation that
held the screen has settled what it changes by then. An exit asked for while Close project asks therefore
finds the project Discard closed, and asks nothing about it. A chain of several guards, such as the exit's,
waits for the screen as a whole while any of them has something to ask. Every guard then reads its state on
the free screen, so a project that Project properties left unsaved is asked about, though it was clean when
the window was closed. A request with nothing to ask goes on at once, whatever holds the screen, so closing
the window over a dialog leaves at once when nothing is unsaved. A turn that opens no question lets the line
go on to the next window in the same frame.

`ModalQueue` (`utils/gui/modal_queue.py`) keeps the line. `GUIWindow.show` is the only way a window enters
it. A guard takes its turn through the `when_free` of the `DialogsRenderer` it was given, which `asking`
(`utils/callbacks/gates.py`) waits on. A caller raises a dialog whenever it has one to raise and never waits
a frame of its own for the screen. A window that reports work under way and leaves the rest of the interface
live beside it is no modal, so it opens at once.

## How a dialog answers

A dialog that closes on its answer leaves the screen first, and the answer runs a frame later as a hand-off
of its conversation. Whatever the answer raises, such as a question of its own or an error, opens ahead of
the line. Leaving also releases the dialog's keyboard claim, so a prompt the answer raises holds the
keyboard alone. `GUIWindow._leave_then` is that step.
keyboard alone. `GUIWindow._leave_then` is that step. An answer that fails with an error leaves the hand-offs
after it, and the line, to go on a frame later.

What the answer needs, such as a ticked box or the fields of a form, is read before the dialog leaves. Only
the first answer runs: a second click reaches a dialog that has already gone.
Expand All @@ -82,6 +94,33 @@ A dialog that comes back once the modal it raised is answered steps aside. `yiel
and keeps its tree, and `resume` brings it back, a frame each way. The dialog keeps the screen while it
stands aside, so nothing waiting in line opens between it and the prompt it raised.

## A gesture asks once

A gesture that asks before it replaces a document, closes one or leaves the application holds one
conversation at a time. The conversation is the chain of questions the gesture passes, and every way out of
a question reaches whoever asked it. An answer that goes on lets the request through. Cancel, Escape, the
title bar's close and a save that failed turn it away. So does an error raised anywhere along the chain:
before a question, as the question reaches the screen, or after its answer. A request therefore always ends
in one of the two.

While a conversation stands, the same gesture asked for again asks nothing, so two closes before the first is
answered ask once. Once the conversation has ended, the gesture asks again. The span covers the wait for the
edits of the open reconstruction and the turn in the line too. A gesture repeated while an edit is on its way
asks once the edit lands, and one repeated while another conversation holds the screen asks once it ends.
`SingleFlight` (`utils/callbacks/gates.py`) holds the conversation, and the composition root wraps every such
gesture in one, whichever door it is asked for through.

**The latest request wins.** A gesture whose question is the same whatever it carries goes on with the
request made last. Starting or opening a document, editing a voice, closing and leaving ask this way. A
reader who double-clicks one file and then another before the question shows opens the second. The menu's
Open, asked for after a browser's file, shows the file dialog. Cancel drops every request the conversation
gathered. `LatestRequestFlight` holds such a conversation.

**A question built from the request keeps its request.** Loading what a run wrote asks whether the open
document is backed by that very file, so the answer holds for that file alone. A press made while the question
stands is absorbed, and the answer loads the file the question named. `FirstRequestFlight` holds such a
conversation.

## Where it is written

`GUIWindow.dialog_window` (`ui/elements/window.py`) is the only place a dialog's `dpg.window` is opened.
Expand Down
14 changes: 9 additions & 5 deletions docs/development/application/keyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The application orders its scopes by priority, from highest to lowest:
| `PANEL` | a sub-panel the keys are meant for | its tab is in front, its card is open, and the sub-panel holds what the keys act on: a cursor, a row picked out, or an open audition | handles the keys its own category names and yields every combination it does not own, so a higher-reaching shortcut still wins |
| `SHORTCUT` | application shortcuts (`ShortcutManager`) | always | fires the matching shortcut while no field is being edited, or whenever the shortcut is `field_transparent` |

The router offers a panel the key ahead of the shortcut scope. A panel therefore returns `False` on any combination it does not own. The grid, for example, yields every `Ctrl`-modified press. That lets field-transparent shortcuts, such as the tab switch, reach the shortcut scope while a grid cursor is set.
The router offers a panel the key ahead of the shortcut scope. A panel therefore returns `False` on any combination it does not own. A panel that reads keys as entry, such as notes or hex digits, takes **plain presses** alone: a key pressed on its own or with Shift, the way a character is typed. Ctrl, Alt and Super make a press a command, and a command goes on to the shortcuts. The grid therefore lets `Ctrl+D` through, and an open instrument's note keys let `Ctrl+Z` through. That lets field-transparent shortcuts, such as the tab switch, reach the shortcut scope while a grid cursor is set.

### A panel scope answers on its own tab, from an open card

Expand All @@ -38,7 +38,9 @@ Whether a text or value field keeps a plain key for itself is one router query,

The query resolves the focused item to the field behind it. A `dpg.group` reports the state of the widget inside it, and DearPyGui names the outermost such group as the focused item. The instruments panel's sequence input, laid out beside its copy button inside a card body group, reaches the keyboard as that group. An active group therefore answers with the field being edited below it. The query follows the one branch that reports focus, so a panel-spanning group costs a key press only the path down to its field.

**Focus is claimed per key.** A focused input keeps the keys it genuinely consumes and yields the rest. A text or number field consumes `Space` and `Shift+Space`, because space is a character it types, and `Escape`, which cancels the field. Those keys serve the field while it holds focus. A modified combination stays global and fires from anywhere, which is why playing from the shown frame works while typing. Playing from the cursor row belongs to the grid: the sequencer grid claims it while the grid itself holds the keyboard.
**Focus is claimed per key.** A focused input keeps the keys it genuinely consumes and yields the rest. A text or number field consumes `Space` and `Shift+Space`, because space is a character it types, and `Escape`, which cancels the field. Those keys serve the field while it holds focus.

A command, a combination held with Ctrl or Super, fires from anywhere, which is why playing from the shown frame works while typing. A text or number field keeps only the commands that edit what it holds: select all, copy, cut, paste, undo and redo, spelled with Ctrl and with Super. Alt is a typing key as well as a modifier: AltGr and Option type characters through it, and Windows reports AltGr as Ctrl+Alt. A text field therefore keeps an Alt combination on a key that types a character, while one on a function, caret or editing key fires, so `Alt+F4` reaches its action from a field. A number field types no such character, so every Alt combination fires from it. An action meant to fire while the reader types takes Ctrl or Super. Playing from the cursor row belongs to the grid: the sequencer grid claims it while the grid itself holds the keyboard.

**Interactive widgets release the keyboard.** A selectable cell or a transport button hands focus back after its click, so the next playback key reaches the router. `Space` and `Escape` therefore stay live in the moment after any click.

Expand All @@ -62,15 +64,17 @@ One key table (`utils/gui/keyboard/keys.py`) reads a key both ways: the name a f

### A preference layers over the shipped scheme

`ShortcutsConfig` holds the scheme name and the per-action overrides, both written the way a keybinding file writes them, so a preference outlives the build that stored it. `ShortcutCatalog.select` answers with the default for a scheme a build stopped shipping. An override is reported and left out when it names an action this build does not have, a key the table does not have, or a combination its category already gives away. One stale entry therefore costs only itself.
`ShortcutsConfig` holds the scheme name and the per-action overrides, both written the way a keybinding file writes them, so a preference outlives the build that stored it. `ShortcutCatalog.select` answers with the default for a scheme a build stopped shipping. An override lists every key of its action, main key first and joined by commas, and a key spells the comma as `Comma`. A stored single key is therefore an action with one key. The overrides are read together, so entries that move keys between their actions stand in whatever order they were written. An override is reported and left out whole when it names an action this build does not have, a key the table does not have, or a combination another action of its category keeps. A stale entry therefore costs itself, and its action keeps the scheme's keys.

A change reaches the running application through `ShortcutSource.on_bindings_changed`, the keyboard's analog of the palette switch ([`palette.md`](palette.md)). The dispatcher re-reads the keys, and the menus re-print their accelerators. Each registration names the action it fires, so a rebind has little to catch up.

### A scheme is edited through a draft

`ShortcutDraft` (`utils/gui/shortcuts/draft.py`) holds the scheme being edited together with the actions the reader has touched: the combination each was given, or nothing where it was left unbound. Only those actions reach the preference, and every other key follows the scheme beneath.
`ShortcutDraft` (`utils/gui/shortcuts/draft.py`) holds the scheme being edited together with the actions the reader has touched: every key each now answers, main key first, and none where it was left unbound. Only those actions reach the preference, and every other key follows the scheme beneath.

An edit changes the keys the reader names, and the action keeps the rest. A pressed key becomes the action's main key, the one the menus print, and the keys it had follow it. A list written in the entry box becomes exactly the action's keys, each named once, and a comma written where a key goes is the comma key. Clear unbinds every key. An action left on the keys the scheme ships holds no edit, so a change that leaves the keys as they were gives the reader nothing to store or discard.

An assignment displaces. Giving an action a combination its category already answers takes the key from the holder in the same step, so every scheme a draft produces is valid. The dialog names the holder and asks before that step is taken. The dialog edits the draft, and a commit activates it, so a reader rebinding Escape, Tab or Enter keeps the keys the dialog is operated by until they are done.
An assignment displaces only what it takes. Giving an action keys its category already answers takes those keys from their holders in the same step, so every scheme a draft produces is valid. A holder keeps its other keys, the next one becoming its main key. The dialog names every holder in one question and asks before that step is taken. Cancel on that question brings the reader back to where they gave the keys: a written list to the entry box, and a pressed key to its row, listening for the next press. A row reads as listening exactly while it takes the keys. The dialog edits the draft, and a commit activates it, so a reader rebinding Escape, Tab or Enter keeps the keys the dialog is operated by until they are done.

### A scheme belongs to a platform; an action does not

Expand Down
Loading
Loading