diff --git a/README.md b/README.md index ca81c2b..920b479 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ A chain of 24 FFmpeg effects covers a mastering chain, compression, highpass and

Two-deck DJ console with jog wheels, the central mixer, and the FX rack

-Two decks run from a pro layout with jog wheels, a central mixer, scrolling waveform overviews, and a track browser, loading from the library, a saved set, or an online import. The engine handles octave-aware beatmatch sync with a continuous lock, key-lock, a 3-band EQ, a single-knob filter, and channel trim with auto-gain. Performance controls cover four hotcues, beat loops, momentary loop rolls, slip mode, beat jumps, and quantize. The FX rack adds a flanger, an impulse-response reverb, and a resonant wah per deck, with a master limiter on the DJ bus. Live stems ride on per-stem faders, and cue output pre-listens a deck through a headphone device chosen with `setSinkId`. Automix sequences and crossfades the set on its own, a ten-pad sampler bank fires one-shots, and a Next staging lane queues upcoming tracks. Design Mode turns the console into a hand-arranged layout that persists and exports. +Two decks run from a pro layout with jog wheels, a central mixer, scrolling waveform overviews, and a track browser, loading from the library, a saved set, or an online import. The engine handles octave-aware beatmatch sync with a continuous lock, key-lock, a 3-band EQ, a single-knob filter, and channel trim with auto-gain. Performance controls cover four hotcues, beat loops, momentary loop rolls, slip mode, beat jumps, and quantize. The FX rack adds a flanger, an impulse-response reverb, and a resonant wah per deck, with a master limiter on the DJ bus. Live stems ride on per-stem faders, and cue output pre-listens a deck through a headphone device chosen with `setSinkId`. Automix sequences and crossfades the set on its own and can be seeded and started from the Library's SUGGEST playlist, a ten-pad sampler bank fires one-shots, and a Next staging lane queues upcoming tracks. Design Mode turns the console into a hand-arranged layout that persists and exports. ### VJ visual engine @@ -172,7 +172,7 @@ Every track and the relationships between them render as an interactive force-di Cross-provider Catalogue gallery with provider badges and inspector

-The library lives on the backend, with audio on disk, metadata in `data/library.db`, and access over `/api/library/*`. Every render saves automatically with its prompt, model, duration, steps, CFG, seed, MIME type, and timestamp. List and grid views, full-text search, a favorites filter, and sorting by newest, duration, or title organize the collection, and each row plays inline with download, delete, favorite, and send-to-editor actions plus a details panel. The Catalogue view adds a cross-provider gallery with provider badges, an inspector with on-demand spectrograms, and a lineage panel, and it runs Suno cover and mashup from any entry. +The library lives on the backend, with audio on disk, metadata in `data/library.db`, and access over `/api/library/*`. Every render saves automatically with its prompt, model, duration, steps, CFG, seed, MIME type, and timestamp. List and grid views, full-text search, a favorites filter, and sorting by newest, duration, title, or play count organize the collection, and each row plays inline with download, delete, favorite, and send-to-editor actions plus a details panel. A per-entry play count persists to the library database and surfaces as a badge and a sort. SUGGEST builds a continuous playlist from the analyzed library, ordered by Camelot-wheel harmony and a chosen BPM flow across a time budget, then plays it through the footer queue or sends it to the DJ tab as an automix set. The Catalogue view adds a cross-provider gallery with provider badges, an inspector with on-demand spectrograms, and a lineage panel, and it runs Suno cover and mashup from any entry. ### Bottom panel tools diff --git a/backend/modules/library/router.py b/backend/modules/library/router.py index f0b46aa..0d46c01 100644 --- a/backend/modules/library/router.py +++ b/backend/modules/library/router.py @@ -109,7 +109,7 @@ async def stream_audio(entry_id: str) -> Response: resp.raise_for_status() audio_bytes = resp.content # Cache to disk so future requests skip CDN. - local_name = meta.get("audio_filename") or f"{entry_id}.mp3" + local_name = (meta or {}).get("audio_filename") or f"{entry_id}.mp3" local_path = entry_dir / local_name try: local_path.write_bytes(audio_bytes) @@ -176,7 +176,8 @@ def register_play(entry_id: str) -> dict[str, Any]: if record is None: raise HTTPException(404, f"Entry {entry_id!r} not found") entry_dir = store._dir_for(entry_id) # noqa: SLF001 - store._sync_record_to_db(record, _read_metadata(entry_dir) or {}) # noqa: SLF001 + meta = _read_metadata(entry_dir) if entry_dir is not None else None + store._sync_record_to_db(record, meta or {}) # noqa: SLF001 new_count = store.db.increment_play_count(entry_id) or 1 return {"id": entry_id, "play_count": new_count} diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 94ca4b3..bf5c0e8 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -2,15 +2,21 @@ _by GANTASMO_ -theDAW is a complete digital audio workstation built on Stable Audio 3, the state-of-the-art open audio model from Stability AI. The model turns a text prompt into high-fidelity 44.1 kHz stereo audio, and theDAW surrounds that core with a full production environment. A React front end and a FastAPI backend run together from one launcher, so the whole studio starts with a single double-click. +theDAW packs most of a music career into one program. It writes original audio from a text prompt, arranges it on a multitrack timeline, masters it through a deep effects chain, splits it into stems, performs it across two beatmatched DJ decks, runs a reactive visual show, transcribes it to sheet music and tablature, and files everything in a searchable library that records how each piece descended from the last. It trains custom models on a collection and answers questions about any of it from a built-in assistant. A separate DAW, audio generator, stem separator, DJ rig, VJ engine, notation editor, sample manager, and model trainer collapse into a single window. -Audio generation happens in the MAKE workspace. A prompt becomes finished audio through Stable Audio 3, and the same model menu also reaches Magenta RealTime 2 for streaming text-to-music and Suno for cloud generation. Chimera fusion can blend several clips into one downbeat-aligned piece, while init signals, inpainting, and LoRA adapters give finer control over the result. When the model selection moves between Stable Audio and the Magenta sidecar, the GPU swap runs on its own. +![The 3D lineage galaxy in the LEARN workspace](screenshots/learn-galaxy.png "full") -Editing and mastering come next. The EDIT workspace opens a piece in a waveform editor that supports region inpainting. Effects and loudness work happens in MIX, which hosts the Edit Tool Stack alongside Quick Master macros. For live use, the DJ workspace provides a two-deck engine with beatmatch sync, key-lock, live stems, an FX rack, hot cues, and hands-free automix. WebGL visuals in the VJ workspace respond to the audio and accept MIDI, microphone, and mobile input. +Generation runs on Stable Audio 3, the open generative audio model from Stability AI. A prompt becomes finished stereo, and the same model menu reaches Magenta RealTime 2 for streaming text-to-music and Suno for cloud renders. Chimera fusion folds several clips into one tempo-aligned track, while init signals, inpainting, and LoRA adapters steer a render toward a target. -Everything generated is kept. The Library stores each piece on disk together with its analysis, stems, and MIDI, and LEARN renders the relationships between pieces as a navigable 3D genealogy. A symbolic-music pipeline produces sheet music, tablature, and multi-instrument arrangements, and it can read a score back into a usable text prompt. LoRA training and autoencoder round-trips have a home in the TRAIN workspace. An in-app assistant answers questions from this guide, and a paste-a-URL importer pulls audio from YouTube, SoundCloud, and Bandcamp. +![The two-deck DJ performance console](screenshots/dj.png "full") -The in-app **Docs** button renders this guide as an interactive modal with a filterable table of contents, raw Markdown download, and print-to-PDF. Each workspace and the backend are documented in full below. +A track then moves through EDIT for waveform surgery and region inpainting, and MIX for the Edit Tool Stack and Quick Master macros. The DJ workspace runs two decks with beatmatch sync, key-lock, live stems, an FX rack, hot cues, and hands-free automix, and the VJ workspace drives WebGL visuals from the audio, a microphone, MIDI, or a phone on the same network. + +![Mastering and effects in the MIX workspace](screenshots/mix.png "full") + +Nothing is discarded. The Library keeps every piece on disk with its analysis, stems, and MIDI, and LEARN draws the lineage between pieces as a navigable 3D genealogy. A symbolic-music pipeline turns a track into sheet music, tablature, and multi-instrument arrangements, and reads a finished score back into a prompt. TRAIN fits LoRA adapters and runs autoencoder round-trips, an assistant answers from this guide, and a paste-a-URL importer pulls audio from YouTube, SoundCloud, and Bandcamp. + +The **Docs** button in the top bar opens this guide as a modal with a filterable table of contents, a Markdown download, and a print-to-PDF in three styles. Every workspace and the full backend API follow below. --- @@ -310,6 +316,8 @@ A sticky bar fixed at the bottom of the MAKE workspace submits the generation jo --- +![MAKE generation controls and parameters](screenshots/make-controls.png) + ## 7. EDIT Tab ### Purpose @@ -395,6 +403,8 @@ During the render, the COMMIT EDIT button shows an animated spinner and is disab --- +![The EDIT multitrack waveform editor](screenshots/edit.png) + ## 8. MIX Tab ### Purpose @@ -465,6 +475,8 @@ The last processing invocations are retained in the store. Any history item can --- +![The MIX effects and mastering workspace](screenshots/mix-overview.png) + ## 9. DJ Tab ### Purpose @@ -501,7 +513,7 @@ DJ MIDI-learn binds a hardware controller to deck, mixer, and hotcue actions. It ### 9.7 Automix, Sampler, and Side List -- **Automix** sequences a setlist hands-free, beatmatching each transition. +- **Automix** sequences a setlist hands-free, beatmatching each transition. The Library's **Suggest a Playlist** can populate this set and start it in one step through its **Send to DJ** action (see §13.9). - **Sampler bank**: drag a clip onto a pad to load a one-shot, then trigger pads during a set. - **Side List**: a play-next staging lane above the browser. Stage upcoming tracks, reorder them, and pull them onto a deck when ready. @@ -515,6 +527,8 @@ A floating **Edit Layout** control turns on Design Mode. In Design Mode, panels --- +![The DJ center mixer, EQ, and crossfader](screenshots/dj-center-mixer.png) + ## 10. VJ Tab ### Purpose @@ -597,6 +611,8 @@ Long-running training jobs are tracked through `GET /api/jobs/{id}`, polled at 1 --- +![The TRAIN workspace for LoRA adapters](screenshots/train.png) + ## 12. LEARN Tab ### Purpose @@ -631,6 +647,8 @@ theDAW carries several other rich visualizations, each documented in its own sec --- +![The 2D lineage family tree in LEARN](screenshots/learn-2d.png) + ## 13. Library ### Purpose @@ -675,13 +693,14 @@ Toggle between a dense **List** view (one row per entry) and a **Grid** view (ti - **Search** filters across title, prompt, model, tags, and notes at the same time. - **FAVS** toggle restricts the view to favorited entries. -- **Sort** by Newest (timestamp descending), Duration (longest first), or Title (alphabetical). +- **Sort** by Newest (timestamp descending), Duration (longest first), Title (alphabetical), or Plays (most-played first). ### 13.4 Per-entry Controls | Control | Description | |---|---| | **Play / Pause** | Loads and plays the entry through `playerStore`. Pausing one entry while another is active stops playback globally. | +| **Play count** | A badge on each played entry shows how many times it has played. The first play after a load increments a per-entry tally persisted in the library database (`POST /api/library/entries/{id}/play`), so the count survives restarts and metadata edits. | | **Favorite star** | Toggles the favorite flag; persisted to the backend immediately. | | **Download** | Triggers a browser file download of the audio. | | **Delete** | Removes the entry from the backend store and the in-memory store. | @@ -733,7 +752,18 @@ Important endpoints: A stats footer shows the total entry count, the favorites count, cumulative storage size, and cumulative playback duration. -### 13.9 Empty State +### 13.9 Suggest a Playlist + +The **SUGGEST** button opens a playlist builder that sequences analyzed tracks into a continuous set. The criteria are a target length, an optional BPM range, a flow shape (Steady, Build up, Wind down, or Wave), a harmonic toggle, and an optional genre or text filter. The backend engine (`POST /api/library/suggest-playlist`) reads each track's analysis and orders the set by harmonic key on the Camelot wheel, the chosen BPM flow, and small nudges toward popular and stylistically varied picks, filling the time budget. Each result row shows its BPM, Camelot code, and the reason it was chosen. + +Two actions run the result: + +- **Play All** loads the sequence into the footer player and auto-advances track to track, restoring the loop preference when it finishes. +- **Send to DJ** loads the order as the active automix set, switches to the DJ tab, and starts the beatmatch-crossfade automix (see §9.7). + +Suggestions are strongest when the library is analyzed. Unanalyzed tracks still fill the budget but cannot be harmonically sequenced; the analyzer and the analyze-on-add toggle cover this over time. + +### 13.10 Empty State Shown until the first generation. It contains a **Go generate something** button that switches the active workspace to MAKE. @@ -795,6 +825,8 @@ These exports use the same PPQ timing constants as live playback (`PPQ = 480`, o --- +![The 16-step sequencer with five synthesized voices](screenshots/sequencer.png) + ## 15. Piano Roll ### Purpose @@ -839,6 +871,8 @@ Clips in the waveform editor whose `sourceKind` is `'piano-roll'` display an **E --- +![The piano roll with MIDI import and export](screenshots/piano.png) + ## 16. Bottom Panel Tabs The bottom panel is collapsible and vertically resizable (drag the grip handle above it), and a maximize toggle expands any tab to fill the window. Six tabs are available. @@ -867,6 +901,8 @@ Text in the overlay uses `textShadow` for legibility against any visualization b **Canvas scaling:** the canvas is sized to its container in physical pixels (device pixel ratio capped at 2×) through a `ResizeObserver`. The `style` dimensions are set in CSS pixels, so the canvas stays crisp on high-DPI displays. +![The real-time spectral analyzer](screenshots/visualizer.png) + ### 16.2 Piano Full piano roll interface embedded in the bottom panel. See [§15](#15-piano-roll). @@ -913,6 +949,8 @@ A session-scoped file holding area for arbitrary audio files. Contents are clear The SLIDE tab is the glass-capsule control surface that mirrors the VJ engine's control manifest as faders. Moving a SLIDE fader updates the matching control in the VJ, and moving a control inside the VJ updates SLIDE. A content toggle switches which control set is shown, a detach button pops SLIDE out into its own window for a second monitor, and the shared maximize toggle expands it to fill the panel. +![The SLIDE glass control surface](screenshots/slide.png) + ### 16.7 Score The Score tab renders a track's symbolic music — sheet music, guitar and bass tabs, and arrangements — from its MIDI artifacts, and exports to MusicXML, ABC, PDF, and SVG. It is documented in full in §33. @@ -1729,6 +1767,8 @@ The result runs on Windows through WSL2 with NVIDIA, on native Linux with NVIDIA --- +![The Magenta RealTime 2 conditioning panel](screenshots/magenta.png) + ## 28. Edit Tool Stack Beyond the 24-effect MIX chain (§8), theDAW mounts the **Edit Tool Stack**, six backend module families under `/api/edit/*`. Each family provides a focused set of audio processors built on FFmpeg, NumPy, and librosa DSP. The browser GUIs come from `frontend/public/edit-modules/` and iframe into the MIX effect stage. @@ -1761,6 +1801,8 @@ The **Catalogue** view (`CatalogueView`, lazy-loaded in the shell) is a cross-pr --- +![The cross-provider Catalogue browser](screenshots/catalogue.png) + ## 30. YouTube Import The `ytimport` module (`/api/ytimport`) imports audio from a URL into the Library. @@ -1820,6 +1862,8 @@ theDAW turns audio into symbolic music and back: audio → MIDI → sheet music, A track needs a MIDI first. Convert one from the Library (right-click → Convert to MIDI, §13.7); once a MIDI artifact exists, the Score buttons activate. +![Guitar tablature rendered from a track's MIDI in the Score panel](screenshots/score.png) + ### 33.1 Notation artifacts Every symbolic file a track produces is a notation artifact with a kind: `midi`, `musicxml`, `abc`, `alphatex` (tabs), `pdf`, or `svg`. The Score panel's left rail lists them; selecting one previews it (MusicXML as sheet music through OpenSheetMusicDisplay, alphaTex as tablature through alphaTab), and DOWNLOAD saves it. Artifacts are stored under `data/generations//notation/` and tracked in the library database with lineage back to their source MIDI or score. diff --git a/docs/reports/feature-doc-coverage-report.md b/docs/reports/feature-doc-coverage-report.md index f57e678..c2658e0 100644 --- a/docs/reports/feature-doc-coverage-report.md +++ b/docs/reports/feature-doc-coverage-report.md @@ -1,7 +1,7 @@ # Feature Documentation Coverage Report > [!NOTE] -> Generated: 2026-06-12T05:28:45.204Z · Git revision: `9dec05fd4f52` · Repomix tracked: **no** +> Generated: 2026-06-12T07:37:20.958Z · Git revision: `c1ad0dc8e117` · Repomix tracked: **no** ## Audit Dashboard @@ -24,16 +24,16 @@ |---|---|---|---|---|---|---| | `shell-center-tabs-right-library` | Center-tab workspace shell with collapsible right library rail | daw | implemented | **documented** | #5-ui-shell
#zustand-store-architecture | Matched 3/4 guide terms. | | `docs-modal-download-print-rag` | In-app docs modal with raw Markdown download, print/PDF, anchors, and RAG source copy | assistant | implemented | **documented** | #5-ui-shell
#19-11-assistant
#25-4-documentation-maintenance-rule | Matched 2/4 guide terms. | -| `assistant-orb-providers-keys-attachments` | AI Assistant orb with provider/model selection, key pools, attachments, voice input, and streaming chat | assistant | implemented | **documented** | #5-ui-shell
#19-11-assistant | Matched 4/5 guide terms. | +| `assistant-orb-providers-keys-attachments` | AI Assistant orb with provider/model selection, key pools, attachments, voice input, and streaming chat | assistant | implemented | **documented** | #5-ui-shell
#19-11-assistant
#29-catalogue | Matched 4/5 guide terms. | | `create-advanced-generation-templates-prompts-spectrograms` | Advanced generation controls with templates, saved prompts, prompt enhancer, output settings, and spectrogram viewer | create | implemented | **documented** | #6-1-primary-synthesis-prompt
#6-3-advanced-generation-panel
#12-3-how-the-visualizations-are-rendered | Matched 4/4 guide terms. | | `create-chimera-fusion-stack` | Chimera multi-clip fusion stack with BPM alignment, base clip, noise weights, and weave scheduling | chimera | implemented | **documented** | #1-repository-anatomy
#purpose
#6-3-1-chimera-fusion-stack
#6-4-init-signal-conditioning
#12-2-3d-graph-controls
#12-3-how-the-visualizations-are-rendered
#13-1-automatic-entry-creation
#13-5-bundle-downloads-and-lineage
#19-14-chimera
#25-3-current-feature-to-screenshot-map
#27-1-the-sidecar-and-conditioning
#30-youtube-import | Matched 5/5 guide terms. | | `create-mic-recorder-send-targets` | Browser microphone recorder that can send recordings to editor, init, inpaint, or library | create | implemented | **documented** | #6-3-1-chimera-fusion-stack
#6-4-1-microphone-recorder
#10-1-inputs
#10-4-export
#13-1-automatic-entry-creation
#25-5-promo-video-capture | Matched 5/5 guide terms. | | `edit-advanced-effects-chain-analyzer` | Advanced effects chain with categorized FFmpeg processors, column resizing, waveform previews, and source/output stats | edit | implemented | **documented** | #purpose
#8-1-layout
#8-2-quick-master
#8-3-effect-catalog-and-chain
#8-4-source-output-and-routing
#19-7-studio-processing
#adding-a-new-ffmpeg-effect | Matched 5/5 guide terms. | -| `library-backend-local-storage` | Disk-backed backend library provider with range-streamed audio and mutable metadata | library | implemented | **documented** | #6-4-1-microphone-recorder
#purpose
#13-1-automatic-entry-creation
#19-13-disk-backed-library
#19-15-stems
#library-storage-fills-the-disk
#zustand-store-architecture
#33-1-notation-artifacts | Matched 4/5 guide terms. | +| `library-backend-local-storage` | Disk-backed backend library provider with range-streamed audio and mutable metadata | library | implemented | **documented** | #6-4-1-microphone-recorder
#purpose
#13-1-automatic-entry-creation
#13-4-per-entry-controls
#19-13-disk-backed-library
#19-15-stems
#library-storage-fills-the-disk
#zustand-store-architecture
#33-1-notation-artifacts | Matched 4/5 guide terms. | | `library-bundle-download-lineage-export` | Library bundle downloads and lineage graph exports including metadata, stems, MIDI, and relations | library | implemented | **documented** | #12-2-3d-graph-controls
#13-4-per-entry-controls
#13-5-bundle-downloads-and-lineage
#19-13-disk-backed-library | Matched 2/4 guide terms. | | `library-stems-sidecar` | Stem separation sidecar with install/start/stop/status/progress/abort and persisted stem rows | library | implemented | **documented** | #13-4-per-entry-controls
#13-6-stem-separation
#19-15-stems
#credits | Matched 3/4 guide terms. | | `library-midi-conversion` | Audio-to-MIDI conversion with installable engines, persisted MIDI rows, and editor send targets | library | implemented | **documented** | #6-4-1-microphone-recorder
#13-4-per-entry-controls
#13-7-midi-conversion
#19-16-midi
#33-notation-score-tabs-and-arrangements | Matched 4/4 guide terms. | -| `settings-feature-toggles-modules-admin` | Settings modal for feature toggles, module enablement, restart, and shutdown controls | settings | implemented | **documented** | #one-shot-launcher-windows
#19-8-jobs-list
#19-11-assistant
#19-12-module-loader
#api-unreachable-banner-in-the-header
#backend-job-persistence
#25-3-current-feature-to-screenshot-map
#32-admin-module-and-assistant-key-apis | Matched 4/5 guide terms. | +| `settings-feature-toggles-modules-admin` | Settings modal for feature toggles, module enablement, restart, and shutdown controls | settings | implemented | **documented** | #one-shot-launcher-windows
#13-4-per-entry-controls
#19-8-jobs-list
#19-11-assistant
#19-12-module-loader
#api-unreachable-banner-in-the-header
#backend-job-persistence
#25-3-current-feature-to-screenshot-map
#32-admin-module-and-assistant-key-apis | Matched 4/5 guide terms. | | `waveform-editor-inpaint-review` | Waveform editor paintbrush inpainting workflow with crop-aware mask submission and accept/discard review | daw | implemented | **documented** | #frontend-dependencies
#6-5-inpainting-regen-region
#6-8-run-generation
#7-4-inpainting-from-the-editor
#10-2-pop-out-and-mobile
#14-2-voice-synthesis
#16-5-media
#controls
#19-4-generation-async-thedaw-ui
#19-12-module-loader
#19-13-disk-backed-library
#19-14-chimera
#26-1-modes | Matched 5/5 guide terms. | | `sequencer-midi-export-render` | Step sequencer Standard MIDI export plus single-track/multi-track render-to-editor flows | daw | implemented | **documented** | #13-7-midi-conversion
#14-5-midi-export
#15-5-midi-import-and-export | Matched 2/4 guide terms. | | `piano-roll-linked-clip-editing` | Piano roll MIDI import/export, render-to-editor, and linked clip re-editing | daw | implemented | **documented** | #15-5-midi-import-and-export
#15-7-edit-in-piano-roll
#16-6-slide | Matched 4/4 guide terms. | diff --git a/docs/reports/feature-doc-coverage.json b/docs/reports/feature-doc-coverage.json index 631bcca..bf230f2 100644 --- a/docs/reports/feature-doc-coverage.json +++ b/docs/reports/feature-doc-coverage.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-06-12T05:28:45.204Z", - "repoRevision": "9dec05fd4f52", + "generatedAt": "2026-06-12T07:37:20.958Z", + "repoRevision": "c1ad0dc8e117", "repomixContext": { "path": "repomix-output.md", "present": false, @@ -615,7 +615,8 @@ "featureId": "assistant-orb-providers-keys-attachments", "docAnchors": [ "#5-ui-shell", - "#19-11-assistant" + "#19-11-assistant", + "#29-catalogue" ], "coverage": "documented", "notes": "Matched 4/5 guide terms." @@ -682,6 +683,7 @@ "#6-4-1-microphone-recorder", "#purpose", "#13-1-automatic-entry-creation", + "#13-4-per-entry-controls", "#19-13-disk-backed-library", "#19-15-stems", "#library-storage-fills-the-disk", @@ -729,6 +731,7 @@ "featureId": "settings-feature-toggles-modules-admin", "docAnchors": [ "#one-shot-launcher-windows", + "#13-4-per-entry-controls", "#19-8-jobs-list", "#19-11-assistant", "#19-12-module-loader", diff --git a/docs/screenshots/manifest.json b/docs/screenshots/manifest.json index 1ec175d..f92a7b9 100644 --- a/docs/screenshots/manifest.json +++ b/docs/screenshots/manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-06-12T05:30:40.357Z", + "generatedAt": "2026-06-12T07:39:22.863Z", "entries": [ { "file": "01-shell-make.png", diff --git a/frontend/public/USER_GUIDE.md b/frontend/public/USER_GUIDE.md index 94ca4b3..bf5c0e8 100644 --- a/frontend/public/USER_GUIDE.md +++ b/frontend/public/USER_GUIDE.md @@ -2,15 +2,21 @@ _by GANTASMO_ -theDAW is a complete digital audio workstation built on Stable Audio 3, the state-of-the-art open audio model from Stability AI. The model turns a text prompt into high-fidelity 44.1 kHz stereo audio, and theDAW surrounds that core with a full production environment. A React front end and a FastAPI backend run together from one launcher, so the whole studio starts with a single double-click. +theDAW packs most of a music career into one program. It writes original audio from a text prompt, arranges it on a multitrack timeline, masters it through a deep effects chain, splits it into stems, performs it across two beatmatched DJ decks, runs a reactive visual show, transcribes it to sheet music and tablature, and files everything in a searchable library that records how each piece descended from the last. It trains custom models on a collection and answers questions about any of it from a built-in assistant. A separate DAW, audio generator, stem separator, DJ rig, VJ engine, notation editor, sample manager, and model trainer collapse into a single window. -Audio generation happens in the MAKE workspace. A prompt becomes finished audio through Stable Audio 3, and the same model menu also reaches Magenta RealTime 2 for streaming text-to-music and Suno for cloud generation. Chimera fusion can blend several clips into one downbeat-aligned piece, while init signals, inpainting, and LoRA adapters give finer control over the result. When the model selection moves between Stable Audio and the Magenta sidecar, the GPU swap runs on its own. +![The 3D lineage galaxy in the LEARN workspace](screenshots/learn-galaxy.png "full") -Editing and mastering come next. The EDIT workspace opens a piece in a waveform editor that supports region inpainting. Effects and loudness work happens in MIX, which hosts the Edit Tool Stack alongside Quick Master macros. For live use, the DJ workspace provides a two-deck engine with beatmatch sync, key-lock, live stems, an FX rack, hot cues, and hands-free automix. WebGL visuals in the VJ workspace respond to the audio and accept MIDI, microphone, and mobile input. +Generation runs on Stable Audio 3, the open generative audio model from Stability AI. A prompt becomes finished stereo, and the same model menu reaches Magenta RealTime 2 for streaming text-to-music and Suno for cloud renders. Chimera fusion folds several clips into one tempo-aligned track, while init signals, inpainting, and LoRA adapters steer a render toward a target. -Everything generated is kept. The Library stores each piece on disk together with its analysis, stems, and MIDI, and LEARN renders the relationships between pieces as a navigable 3D genealogy. A symbolic-music pipeline produces sheet music, tablature, and multi-instrument arrangements, and it can read a score back into a usable text prompt. LoRA training and autoencoder round-trips have a home in the TRAIN workspace. An in-app assistant answers questions from this guide, and a paste-a-URL importer pulls audio from YouTube, SoundCloud, and Bandcamp. +![The two-deck DJ performance console](screenshots/dj.png "full") -The in-app **Docs** button renders this guide as an interactive modal with a filterable table of contents, raw Markdown download, and print-to-PDF. Each workspace and the backend are documented in full below. +A track then moves through EDIT for waveform surgery and region inpainting, and MIX for the Edit Tool Stack and Quick Master macros. The DJ workspace runs two decks with beatmatch sync, key-lock, live stems, an FX rack, hot cues, and hands-free automix, and the VJ workspace drives WebGL visuals from the audio, a microphone, MIDI, or a phone on the same network. + +![Mastering and effects in the MIX workspace](screenshots/mix.png "full") + +Nothing is discarded. The Library keeps every piece on disk with its analysis, stems, and MIDI, and LEARN draws the lineage between pieces as a navigable 3D genealogy. A symbolic-music pipeline turns a track into sheet music, tablature, and multi-instrument arrangements, and reads a finished score back into a prompt. TRAIN fits LoRA adapters and runs autoencoder round-trips, an assistant answers from this guide, and a paste-a-URL importer pulls audio from YouTube, SoundCloud, and Bandcamp. + +The **Docs** button in the top bar opens this guide as a modal with a filterable table of contents, a Markdown download, and a print-to-PDF in three styles. Every workspace and the full backend API follow below. --- @@ -310,6 +316,8 @@ A sticky bar fixed at the bottom of the MAKE workspace submits the generation jo --- +![MAKE generation controls and parameters](screenshots/make-controls.png) + ## 7. EDIT Tab ### Purpose @@ -395,6 +403,8 @@ During the render, the COMMIT EDIT button shows an animated spinner and is disab --- +![The EDIT multitrack waveform editor](screenshots/edit.png) + ## 8. MIX Tab ### Purpose @@ -465,6 +475,8 @@ The last processing invocations are retained in the store. Any history item can --- +![The MIX effects and mastering workspace](screenshots/mix-overview.png) + ## 9. DJ Tab ### Purpose @@ -501,7 +513,7 @@ DJ MIDI-learn binds a hardware controller to deck, mixer, and hotcue actions. It ### 9.7 Automix, Sampler, and Side List -- **Automix** sequences a setlist hands-free, beatmatching each transition. +- **Automix** sequences a setlist hands-free, beatmatching each transition. The Library's **Suggest a Playlist** can populate this set and start it in one step through its **Send to DJ** action (see §13.9). - **Sampler bank**: drag a clip onto a pad to load a one-shot, then trigger pads during a set. - **Side List**: a play-next staging lane above the browser. Stage upcoming tracks, reorder them, and pull them onto a deck when ready. @@ -515,6 +527,8 @@ A floating **Edit Layout** control turns on Design Mode. In Design Mode, panels --- +![The DJ center mixer, EQ, and crossfader](screenshots/dj-center-mixer.png) + ## 10. VJ Tab ### Purpose @@ -597,6 +611,8 @@ Long-running training jobs are tracked through `GET /api/jobs/{id}`, polled at 1 --- +![The TRAIN workspace for LoRA adapters](screenshots/train.png) + ## 12. LEARN Tab ### Purpose @@ -631,6 +647,8 @@ theDAW carries several other rich visualizations, each documented in its own sec --- +![The 2D lineage family tree in LEARN](screenshots/learn-2d.png) + ## 13. Library ### Purpose @@ -675,13 +693,14 @@ Toggle between a dense **List** view (one row per entry) and a **Grid** view (ti - **Search** filters across title, prompt, model, tags, and notes at the same time. - **FAVS** toggle restricts the view to favorited entries. -- **Sort** by Newest (timestamp descending), Duration (longest first), or Title (alphabetical). +- **Sort** by Newest (timestamp descending), Duration (longest first), Title (alphabetical), or Plays (most-played first). ### 13.4 Per-entry Controls | Control | Description | |---|---| | **Play / Pause** | Loads and plays the entry through `playerStore`. Pausing one entry while another is active stops playback globally. | +| **Play count** | A badge on each played entry shows how many times it has played. The first play after a load increments a per-entry tally persisted in the library database (`POST /api/library/entries/{id}/play`), so the count survives restarts and metadata edits. | | **Favorite star** | Toggles the favorite flag; persisted to the backend immediately. | | **Download** | Triggers a browser file download of the audio. | | **Delete** | Removes the entry from the backend store and the in-memory store. | @@ -733,7 +752,18 @@ Important endpoints: A stats footer shows the total entry count, the favorites count, cumulative storage size, and cumulative playback duration. -### 13.9 Empty State +### 13.9 Suggest a Playlist + +The **SUGGEST** button opens a playlist builder that sequences analyzed tracks into a continuous set. The criteria are a target length, an optional BPM range, a flow shape (Steady, Build up, Wind down, or Wave), a harmonic toggle, and an optional genre or text filter. The backend engine (`POST /api/library/suggest-playlist`) reads each track's analysis and orders the set by harmonic key on the Camelot wheel, the chosen BPM flow, and small nudges toward popular and stylistically varied picks, filling the time budget. Each result row shows its BPM, Camelot code, and the reason it was chosen. + +Two actions run the result: + +- **Play All** loads the sequence into the footer player and auto-advances track to track, restoring the loop preference when it finishes. +- **Send to DJ** loads the order as the active automix set, switches to the DJ tab, and starts the beatmatch-crossfade automix (see §9.7). + +Suggestions are strongest when the library is analyzed. Unanalyzed tracks still fill the budget but cannot be harmonically sequenced; the analyzer and the analyze-on-add toggle cover this over time. + +### 13.10 Empty State Shown until the first generation. It contains a **Go generate something** button that switches the active workspace to MAKE. @@ -795,6 +825,8 @@ These exports use the same PPQ timing constants as live playback (`PPQ = 480`, o --- +![The 16-step sequencer with five synthesized voices](screenshots/sequencer.png) + ## 15. Piano Roll ### Purpose @@ -839,6 +871,8 @@ Clips in the waveform editor whose `sourceKind` is `'piano-roll'` display an **E --- +![The piano roll with MIDI import and export](screenshots/piano.png) + ## 16. Bottom Panel Tabs The bottom panel is collapsible and vertically resizable (drag the grip handle above it), and a maximize toggle expands any tab to fill the window. Six tabs are available. @@ -867,6 +901,8 @@ Text in the overlay uses `textShadow` for legibility against any visualization b **Canvas scaling:** the canvas is sized to its container in physical pixels (device pixel ratio capped at 2×) through a `ResizeObserver`. The `style` dimensions are set in CSS pixels, so the canvas stays crisp on high-DPI displays. +![The real-time spectral analyzer](screenshots/visualizer.png) + ### 16.2 Piano Full piano roll interface embedded in the bottom panel. See [§15](#15-piano-roll). @@ -913,6 +949,8 @@ A session-scoped file holding area for arbitrary audio files. Contents are clear The SLIDE tab is the glass-capsule control surface that mirrors the VJ engine's control manifest as faders. Moving a SLIDE fader updates the matching control in the VJ, and moving a control inside the VJ updates SLIDE. A content toggle switches which control set is shown, a detach button pops SLIDE out into its own window for a second monitor, and the shared maximize toggle expands it to fill the panel. +![The SLIDE glass control surface](screenshots/slide.png) + ### 16.7 Score The Score tab renders a track's symbolic music — sheet music, guitar and bass tabs, and arrangements — from its MIDI artifacts, and exports to MusicXML, ABC, PDF, and SVG. It is documented in full in §33. @@ -1729,6 +1767,8 @@ The result runs on Windows through WSL2 with NVIDIA, on native Linux with NVIDIA --- +![The Magenta RealTime 2 conditioning panel](screenshots/magenta.png) + ## 28. Edit Tool Stack Beyond the 24-effect MIX chain (§8), theDAW mounts the **Edit Tool Stack**, six backend module families under `/api/edit/*`. Each family provides a focused set of audio processors built on FFmpeg, NumPy, and librosa DSP. The browser GUIs come from `frontend/public/edit-modules/` and iframe into the MIX effect stage. @@ -1761,6 +1801,8 @@ The **Catalogue** view (`CatalogueView`, lazy-loaded in the shell) is a cross-pr --- +![The cross-provider Catalogue browser](screenshots/catalogue.png) + ## 30. YouTube Import The `ytimport` module (`/api/ytimport`) imports audio from a URL into the Library. @@ -1820,6 +1862,8 @@ theDAW turns audio into symbolic music and back: audio → MIDI → sheet music, A track needs a MIDI first. Convert one from the Library (right-click → Convert to MIDI, §13.7); once a MIDI artifact exists, the Score buttons activate. +![Guitar tablature rendered from a track's MIDI in the Score panel](screenshots/score.png) + ### 33.1 Notation artifacts Every symbolic file a track produces is a notation artifact with a kind: `midi`, `musicxml`, `abc`, `alphatex` (tabs), `pdf`, or `svg`. The Score panel's left rail lists them; selecting one previews it (MusicXML as sheet music through OpenSheetMusicDisplay, alphaTex as tablature through alphaTab), and DOWNLOAD saves it. Artifacts are stored under `data/generations//notation/` and tracked in the library database with lineage back to their source MIDI or score. diff --git a/frontend/src/components/layout/DocsModal.tsx b/frontend/src/components/layout/DocsModal.tsx index e5315e6..ab713df 100644 --- a/frontend/src/components/layout/DocsModal.tsx +++ b/frontend/src/components/layout/DocsModal.tsx @@ -16,6 +16,123 @@ interface Heading { const slugify = (s: string): string => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); +// Three print styles for Save-as-PDF. Each is rendered into a clean iframe and +// printed from there, so the entire guide exports (every section, code block, +// table, image). Paper is a classic serif document, Studio is the modern +// hairline-and-accent house style, Carbon is a dark blue-accented theme. +// Carbon needs the print dialog's "Background graphics" option enabled. +type PrintTheme = { label: string; hint: string; swatch: string; css: string }; + +const PRINT_THEMES: Record<'paper' | 'studio' | 'carbon', PrintTheme> = { + paper: { + label: 'Paper', hint: 'classic', swatch: '#ffffff', + css: ` + * { box-sizing: border-box; } + html, body { margin: 0; padding: 0; background: #fff; } + .page-frame { display: none; } + .guide, .guide * { -webkit-print-color-adjust: exact; print-color-adjust: exact; } + .guide { font-family: Georgia, 'Times New Roman', serif; color: #1a1a1a; font-size: 11pt; line-height: 1.55; max-width: 6.9in; margin: 0 auto; } + .guide h1, .guide h2, .guide h3, .guide h4 { font-family: Georgia, 'Times New Roman', serif; color: #000; line-height: 1.25; page-break-after: avoid; page-break-inside: avoid; } + .guide h1 { font-size: 22pt; font-weight: 700; margin: 0 0 12pt; padding-bottom: 6pt; border-bottom: 1.5pt solid #333; } + .guide h2 { font-size: 15pt; font-weight: 700; margin: 16pt 0 5pt; } + .guide h3 { font-size: 12pt; font-weight: 700; margin: 11pt 0 3pt; color: #333; } + .guide p { margin: 6pt 0; } + .guide ul, .guide ol { margin: 6pt 0 6pt 22pt; padding: 0; } + .guide li { margin: 3pt 0; } + .guide a { color: #11457e; text-decoration: underline; } + .guide strong { color: #000; font-weight: 700; } + .guide em { font-style: italic; } + .guide hr { border: none; border-top: 1pt solid #ccc; margin: 12pt 0; } + .guide blockquote { margin: 8pt 0; padding: 4pt 12pt; border-left: 3pt solid #999; background: #f6f6f6; color: #333; page-break-inside: avoid; } + .guide blockquote p { margin: 2pt 0; } + .guide pre { background: #f4f4f4; border: 1pt solid #ddd; border-radius: 2pt; padding: 7pt 9pt; margin: 6pt 0; font-size: 8.5pt; line-height: 1.45; font-family: Consolas, 'Liberation Mono', Menlo, monospace; color: #222; white-space: pre-wrap; word-break: break-word; page-break-inside: avoid; } + .guide code { font-family: Consolas, 'Liberation Mono', Menlo, monospace; font-size: 9pt; background: #f0f0f0; color: #a02060; padding: 1pt 3pt; border-radius: 2pt; word-break: break-word; } + .guide pre code { background: none; padding: 0; color: inherit; font-size: inherit; } + .guide table { border-collapse: collapse; width: 100%; margin: 8pt 0; font-size: 9pt; page-break-inside: avoid; } + .guide th, .guide td { border: 1pt solid #bbb; padding: 4pt 7pt; text-align: left; vertical-align: top; } + .guide th { background: #eee; color: #000; font-weight: 700; } + .guide img { display: block; max-width: 100%; height: auto; margin: 9pt auto; page-break-inside: avoid; } + @page { margin: 0.75in; size: letter; } + `, + }, + studio: { + label: 'Studio', hint: 'modern', swatch: '#7c3aed', + css: ` + * { box-sizing: border-box; } + html, body { margin: 0; padding: 0; background: #fff; } + .page-frame { display: none; } + .guide, .guide * { -webkit-print-color-adjust: exact; print-color-adjust: exact; } + .guide { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; color: #20222b; font-size: 10pt; line-height: 1.55; max-width: 7.1in; margin: 0 auto; } + .guide h1, .guide h2, .guide h3, .guide h4 { color: #0e0f15; font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; page-break-after: avoid; page-break-inside: avoid; } + .guide h1 { font-size: 20pt; margin: 0 0 12pt; padding-bottom: 6pt; border-bottom: 1pt solid #6d28d9; letter-spacing: -0.02em; } + .guide h2 { font-size: 13.5pt; margin: 18pt 0 4pt; padding-bottom: 3pt; border-bottom: 0.5pt solid #e7e4ef; } + .guide h3 { font-size: 9.5pt; margin: 12pt 0 3pt; color: #6d28d9; text-transform: uppercase; letter-spacing: 0.08em; } + .guide p { margin: 5pt 0; } + .guide ul, .guide ol { margin: 5pt 0 5pt 18pt; padding: 0; } + .guide li { margin: 2.5pt 0; } + .guide li::marker { color: #9b8bc4; } + .guide a { color: #6d28d9; text-decoration: none; border-bottom: 0.5pt solid #cbb8f0; } + .guide strong { color: #0e0f15; font-weight: 650; } + .guide em { color: #3c3a45; } + .guide hr { border: none; border-top: 0.5pt solid #e7e4ef; margin: 14pt 0; } + .guide blockquote { margin: 7pt 0; padding: 1pt 0 1pt 12pt; border-left: 2pt solid #6d28d9; color: #44414f; font-style: italic; page-break-inside: avoid; } + .guide blockquote p { margin: 2pt 0; } + .guide pre { background: #f7f6fb; border: 0.5pt solid #e7e4ef; border-radius: 3pt; padding: 7pt 9pt; margin: 6pt 0; font-size: 8pt; line-height: 1.45; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; color: #2c2a38; white-space: pre-wrap; word-break: break-word; page-break-inside: avoid; } + .guide code { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 8.5pt; background: #f1eefa; color: #5b21b6; padding: 0.5pt 3pt; border-radius: 2pt; word-break: break-word; } + .guide pre code { background: none; padding: 0; color: inherit; font-size: inherit; } + .guide table { border-collapse: collapse; width: 100%; margin: 8pt 0; font-size: 8.5pt; page-break-inside: avoid; } + .guide th { text-align: left; font-weight: 700; color: #0e0f15; font-size: 7.5pt; text-transform: uppercase; letter-spacing: 0.05em; padding: 4pt 8pt 4pt 0; border-bottom: 1pt solid #2c2a38; } + .guide td { padding: 4pt 8pt 4pt 0; border-bottom: 0.5pt solid #ece9f3; vertical-align: top; color: #2c2a38; } + .guide img { display: block; max-width: 100%; height: auto; margin: 9pt auto; border-radius: 3pt; page-break-inside: avoid; } + @page { margin: 0.7in; size: letter; } + `, + }, + carbon: { + label: 'Carbon', hint: 'dark', swatch: '#a855f7', + css: ` + * { box-sizing: border-box; -webkit-print-color-adjust: exact; print-color-adjust: exact; } + html { + background-color: #0c0617; + background-image: + repeating-linear-gradient(135deg, rgba(217,70,239,0.05) 0, rgba(217,70,239,0.05) 1px, transparent 1px, transparent 18px), + radial-gradient(130% 95% at 50% -12%, #2c1450 0%, #170a2e 46%, #0a0513 100%); + } + body { margin: 0; padding: 0; background: transparent; } + /* Soft edge vignette + faint neon glow, fixed so it repeats on every printed + page. No hard border box, so flowing content can never look like it spills + past an edge. */ + .page-frame { position: fixed; inset: 0; box-shadow: inset 0 0 130pt rgba(8,4,16,0.9), inset 0 0 26pt rgba(192,38,211,0.14); pointer-events: none; } + /* Carbon supplies its own margins (page padding) so a full-bleed dark page + needs the print dialog's Margins set to None; otherwise default margins add + white edges around the dark sheet. */ + .guide { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; color: #cabfe2; font-size: 10pt; line-height: 1.55; max-width: 6.7in; margin: 0 auto; padding: 0.55in 0 0.65in; } + .guide h1, .guide h2, .guide h3, .guide h4 { color: #f4ecff; font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; page-break-after: avoid; page-break-inside: avoid; } + .guide h1 { font-size: 20pt; margin: 0 0 12pt; padding-bottom: 6pt; border-bottom: 1pt solid #c026d3; color: #fff; text-shadow: 0 0 16px rgba(217,70,239,0.5); letter-spacing: -0.02em; } + .guide h2 { font-size: 13.5pt; margin: 18pt 0 4pt; padding-bottom: 3pt; border-bottom: 0.5pt solid #2a1740; color: #d8b4fe; } + .guide h3 { font-size: 9.5pt; margin: 12pt 0 3pt; color: #c084fc; text-transform: uppercase; letter-spacing: 0.08em; } + .guide p { margin: 5pt 0; } + .guide ul, .guide ol { margin: 5pt 0 5pt 18pt; padding: 0; } + .guide li { margin: 2.5pt 0; } + .guide li::marker { color: #c026d3; } + .guide a { color: #d8b4fe; text-decoration: none; border-bottom: 0.5pt solid #6b3aa0; } + .guide strong { color: #fff; font-weight: 650; } + .guide em { color: #bba7dc; } + .guide hr { border: none; border-top: 0.5pt solid #2a1740; margin: 14pt 0; } + .guide blockquote { margin: 7pt 0; padding: 3pt 0 3pt 12pt; border-left: 2pt solid #c026d3; background: #170c28; color: #c7b6e4; font-style: italic; page-break-inside: avoid; } + .guide blockquote p { margin: 2pt 0; } + .guide pre { background: #150b27; border: 0.5pt solid #2a1740; border-radius: 3pt; padding: 7pt 9pt; margin: 6pt 0; font-size: 8pt; line-height: 1.45; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; color: #ddccf5; white-space: pre-wrap; word-break: break-word; page-break-inside: avoid; } + .guide code { font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 8.5pt; background: #231140; color: #e9d5ff; padding: 0.5pt 3pt; border-radius: 2pt; word-break: break-word; } + .guide pre code { background: none; padding: 0; color: inherit; font-size: inherit; } + .guide table { border-collapse: collapse; width: 100%; margin: 8pt 0; font-size: 8.5pt; page-break-inside: avoid; } + .guide th { text-align: left; font-weight: 700; color: #d8b4fe; font-size: 7.5pt; text-transform: uppercase; letter-spacing: 0.05em; padding: 4pt 8pt 4pt 0; border-bottom: 1pt solid #c026d3; } + .guide td { padding: 4pt 8pt 4pt 0; border-bottom: 0.5pt solid #21142f; vertical-align: top; color: #cabfe2; } + .guide img { display: block; max-width: 100%; height: auto; margin: 9pt auto; border-radius: 3pt; page-break-inside: avoid; } + @page { margin: 0; size: letter; } + `, + }, +}; +type ThemeKey = keyof typeof PRINT_THEMES; + export const DocsModal: React.FC = ({ open, onClose }) => { const [markdown, setMarkdown] = useState(''); const [html, setHtml] = useState(''); @@ -23,6 +140,7 @@ export const DocsModal: React.FC = ({ open, onClose }) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [search, setSearch] = useState(''); + const [pdfMenuOpen, setPdfMenuOpen] = useState(false); const contentRef = useRef(null); useEffect(() => { @@ -81,7 +199,16 @@ export const DocsModal: React.FC = ({ open, onClose }) => { // loads from /screenshots/… regardless of the current route. let src = href || ''; if (src && !/^(https?:|data:|\/)/.test(src)) src = '/' + src.replace(/^\.?\//, ''); - return `${text || ''}`; + // The title doubles as an optional size hint: "full" spans the column, a + // bare number is that many px of max-width, anything else stays a normal + // title. Supporting screenshots default to a moderate width so they do not + // each eat a whole page. + let maxW = '500px'; + let titleAttr = ''; + if (title === 'full') maxW = '100%'; + else if (title && /^\d+$/.test(title)) maxW = `${title}px`; + else if (title) titleAttr = ` title="${title}"`; + return `${text || ''}`; }; const parsed = marked.parse(markdown, { renderer, gfm: true, breaks: false }) as string; setHtml(parsed); @@ -117,9 +244,56 @@ export const DocsModal: React.FC = ({ open, onClose }) => { if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; - const handlePrint = () => { - // Print CSS in the component scopes the print output to just the docs content. - window.print(); + const handlePrint = (themeKey: ThemeKey) => { + if (!html) return; + // Render the whole guide into an offscreen same-origin iframe with the chosen + // theme CSS and print THAT. Printing the live modal dropped code blocks and + // left ghost pages from the hidden app shell. The iframe is given a real size + // and kept onscreen-but-offset (not display:none or 0x0) and the guide's lazy + // image loading is stripped, so every screenshot actually loads before print. + const iframe = document.createElement('iframe'); + iframe.setAttribute('aria-hidden', 'true'); + iframe.style.cssText = 'position:fixed;left:-10000px;top:0;width:820px;height:1160px;border:0;opacity:0;pointer-events:none;'; + document.body.appendChild(iframe); + const cleanup = () => { try { document.body.removeChild(iframe); } catch { /* already gone */ } }; + const doc = iframe.contentDocument; + const win = iframe.contentWindow; + if (!doc || !win) { cleanup(); return; } + // Eager-load every image in the print copy (lazy images never enter an + // offscreen iframe's viewport, so they would otherwise print blank). + const printHtml = html.replace(/\sloading="lazy"/g, ''); + doc.open(); + doc.write( + '' + + `` + + 'theDAW User Guide' + + '
' + printHtml + '
', + ); + doc.close(); + let fired = false; + const fire = () => { + if (fired) return; + fired = true; + win.focus(); + win.print(); + window.setTimeout(cleanup, 1000); + }; + // Print once images have loaded so none come out blank; fall back after 5s. + const imgs = Array.from(doc.images); + let pending = imgs.length; + if (pending === 0) { + window.setTimeout(fire, 60); + } else { + const tick = () => { pending -= 1; if (pending <= 0) fire(); }; + imgs.forEach((img) => { + if (img.complete) tick(); + else { + img.addEventListener('load', tick, { once: true }); + img.addEventListener('error', tick, { once: true }); + } + }); + window.setTimeout(fire, 5000); + } }; const handleDownloadMd = () => { @@ -170,14 +344,36 @@ export const DocsModal: React.FC = ({ open, onClose }) => { > MD - +
+ + {pdfMenuOpen && ( + <> +
setPdfMenuOpen(false)} /> +
+
PDF style
+ {(Object.keys(PRINT_THEMES) as ThemeKey[]).map((k) => ( + + ))} +
+ + )} +
-