| 1 |
|
- |
# audiofiles — Completed Work
|
| 2 |
|
- |
|
| 3 |
|
- |
Archived completed sections from todo.md. All items below are done.
|
| 4 |
|
- |
|
| 5 |
|
- |
## Phase 0: Project Setup
|
| 6 |
|
- |
|
| 7 |
|
- |
### Done
|
| 8 |
|
- |
- [x] Init Cargo workspace (audiofiles-core, audiofiles-plugin, audiofiles-app, xtask)
|
| 9 |
|
- |
- [x] audiofiles-core: pure library crate, no async, no UI (rusqlite, sha2, thiserror)
|
| 10 |
|
- |
- [x] audiofiles-plugin: nih-plug CLAP/VST3 plugin crate with egui editor placeholder
|
| 11 |
|
- |
- [x] audiofiles-app: standalone binary (placeholder only)
|
| 12 |
|
- |
- [x] Set up SQLite with rusqlite + migrations (8 tables, 7 indexes, PRAGMA user_version tracking)
|
| 13 |
|
- |
- [x] xtask bundler for CLAP/VST3 packaging (`cargo xtask bundle audiofiles-plugin`)
|
| 14 |
|
- |
- [x] 4 tests: schema creation, migration versioning, idempotency, FK enforcement
|
| 15 |
|
- |
- [x] CI — `.build.yml` for builds.sr.ht (check, test, clippy, audit)
|
| 16 |
|
- |
|
| 17 |
|
- |
## Phase 1: Core Library — Sample Store & VFS
|
| 18 |
|
- |
|
| 19 |
|
- |
### Done
|
| 20 |
|
- |
- [x] CoreError type + Result alias + helpers (error.rs)
|
| 21 |
|
- |
- [x] Content-addressed store: import file → SHA-256 → copy to `{hash}.{ext}` (store.rs)
|
| 22 |
|
- |
- [x] Deduplication: skip copy when hash already exists
|
| 23 |
|
- |
- [x] Sample remove: delete file + row, CASCADE handles refs
|
| 24 |
|
- |
- [x] VFS CRUD: create/delete/rename VFS roots (vfs.rs)
|
| 25 |
|
- |
- [x] VFS node CRUD: create/delete/rename/move directories and sample links
|
| 26 |
|
- |
- [x] VFS node query: list children (dirs first), breadcrumb trail
|
| 27 |
|
- |
- [x] Root-level name conflict detection (SQLite NULL UNIQUE workaround)
|
| 28 |
|
- |
- [x] Tag CRUD: add/remove/query tags on samples (tags.rs)
|
| 29 |
|
- |
- [x] Tag query: find_by_tag, list_tag_names, list_tag_values
|
| 30 |
|
- |
- [x] 23 unit tests (4 db + 4 store + 10 vfs + 5 tags)
|
| 31 |
|
- |
- [x] Ratatui TUI app for interactive testing (app.rs, ui.rs, main.rs)
|
| 32 |
|
- |
- [x] TUI: vim-style nav, import files/dirs, mkdir, tag, rename, delete, cycle VFS
|
| 33 |
|
- |
- [x] Collections CRUD: create/delete collections, add/remove members (schema + 11 files implemented)
|
| 34 |
|
- |
|
| 35 |
|
- |
## Phase 2: CLAP Plugin Shell
|
| 36 |
|
- |
|
| 37 |
|
- |
### Done
|
| 38 |
|
- |
- [x] Plugin struct with SharedState bridge between audio + GUI threads
|
| 39 |
|
- |
- [x] BrowserState: DB, Store, VFS navigation ported from TUI app (state.rs)
|
| 40 |
|
- |
- [x] Audio preview: symphonia decode to interleaved stereo f32 (preview.rs)
|
| 41 |
|
- |
- [x] Preview playback: fill_output() with try_lock for audio thread safety
|
| 42 |
|
- |
- [x] egui editor: single-panel file browser (breadcrumb, file list, footer) (editor.rs)
|
| 43 |
|
- |
- [x] VFS selector dropdown (ComboBox to switch VFS roots)
|
| 44 |
|
- |
- [x] Breadcrumb navigation (clickable path segments)
|
| 45 |
|
- |
- [x] Keyboard navigation: Up/Down/J/K, Enter/Right enter dir, Backspace/Left go up, Space toggle preview
|
| 46 |
|
- |
- [x] Play buttons per sample row + double-click to preview
|
| 47 |
|
- |
- [x] Copy-path-to-clipboard button (ctx.copy_text)
|
| 48 |
|
- |
- [x] Plugin state persistence: db-path persisted via nih-plug #[persist]
|
| 49 |
|
- |
- [x] Default data dir via `dirs` crate (platform data_dir/audiofiles)
|
| 50 |
|
- |
- [x] CLAP + VST3 bundle builds successfully
|
| 51 |
|
- |
|
| 52 |
|
- |
### Done (UX Overhaul)
|
| 53 |
|
- |
- [x] Three-panel layout (sidebar + columnar file list + detail panel)
|
| 54 |
|
- |
- [x] Sortable columns: BPM, key, duration, classification, name
|
| 55 |
|
- |
- [x] Detail panel with waveform + metadata + tag management
|
| 56 |
|
- |
- [x] Tag editor in detail panel (add/remove tags on selected sample)
|
| 57 |
|
- |
- [x] Search bar with folder/global scope toggle
|
| 58 |
|
- |
- [x] Dark theme applied via egui Visuals
|
| 59 |
|
- |
|
| 60 |
|
- |
## Phase 3: Import Pipeline
|
| 61 |
|
- |
|
| 62 |
|
- |
Store is always flat (content-addressed blobs by SHA-256 hash). VFS controls how samples appear to the user.
|
| 63 |
|
- |
|
| 64 |
|
- |
### Done
|
| 65 |
|
- |
- [x] Folder import: background worker walks directory, hashes + copies + creates VFS nodes (import.rs)
|
| 66 |
|
- |
- [x] Progress reporting: pre-walk file count → progress bar, per-file updates, cancel support
|
| 67 |
|
- |
- [x] Import dialog in plugin UI: "Import Folder" button in breadcrumb bar, rfd folder picker, progress screen
|
| 68 |
|
- |
- [x] Drag-and-drop import: directories use background worker, single files remain synchronous
|
| 69 |
|
- |
- [x] Analysis flow wired into import: auto-transitions to ConfigureAnalysis after import completes
|
| 70 |
|
- |
|
| 71 |
|
- |
### Done (Phase 3 completion)
|
| 72 |
|
- |
- [x] Import options UI: flat / new VFS / merge into existing VFS — ConfigureImport screen with radio buttons, VFS name editor, merge VFS picker (editor.rs, state.rs)
|
| 73 |
|
- |
- [x] Post-import tag prompt: TagFolders screen with per-folder tag input, real-time validation, Apply/Skip buttons (editor.rs, state.rs)
|
| 74 |
|
- |
- [x] Duplicate detection feedback: ImportFileResult enum, duplicates counter through worker, status shows "X duplicates skipped" (import.rs, state.rs)
|
| 75 |
|
- |
- [x] ImportStrategy enum (Flat/NewVfs/MergeIntoVfs), ImportedFolder struct, import_directory_flat(), import_structured() (import.rs)
|
| 76 |
|
- |
- [x] Drag-and-drop directories use MergeIntoVfs strategy (main.rs), "Import Folder" button shows config screen
|
| 77 |
|
- |
- [x] 84 tests pass, full workspace compiles cleanly
|
| 78 |
|
- |
|
| 79 |
|
- |
## Phase 4: Audio Analysis
|
| 80 |
|
- |
|
| 81 |
|
- |
### Done
|
| 82 |
|
- |
- [x] Analysis module structure (analysis/ with 10 submodules)
|
| 83 |
|
- |
- [x] Symphonia → mono f32 decode for analysis (analysis/decode.rs)
|
| 84 |
|
- |
- [x] Basic loudness: peak_db, rms_db (analysis/basic.rs, 4 tests)
|
| 85 |
|
- |
- [x] LUFS loudness via bs1770 ITU-R BS.1770-4 (analysis/loudness.rs, 2 tests)
|
| 86 |
|
- |
- [x] Spectral features via realfft STFT: centroid, flatness, rolloff, ZCR, onset strength (analysis/spectral.rs, 4 tests)
|
| 87 |
|
- |
- [x] BPM + key detection via stratum-dsp analyze_audio (analysis/bpm.rs, 2 tests)
|
| 88 |
|
- |
- [x] Rule-based classification: 12 classes (Kick, Snare, HiHat, Cymbal, Percussion, Bass, Vocal, Synth, Pad, Fx, Noise, Music) (analysis/classify.rs, 6 tests)
|
| 89 |
|
- |
- [x] Loop detection: cross-correlation + beat alignment heuristic (analysis/loop_detect.rs, 4 tests)
|
| 90 |
|
- |
- [x] Tag suggestion engine: AnalysisResult → Vec<TagSuggestion> with confidence + reason (analysis/suggest.rs, 7 tests)
|
| 91 |
|
- |
- [x] AnalysisConfig: 7 toggleable analyses, all default true (analysis/config.rs)
|
| 92 |
|
- |
- [x] Background worker thread: mpsc channels, progress reporting, cancellation (analysis/worker.rs)
|
| 93 |
|
- |
- [x] analyze_sample() orchestrator + save_analysis() DB writer (analysis/mod.rs)
|
| 94 |
|
- |
- [x] DB migration 003: added lufs, spectral_flatness, spectral_rolloff, zero_crossing_rate, classification columns
|
| 95 |
|
- |
- [x] Plugin state: ImportMode enum (None/ConfigureAnalysis/Analyzing/ReviewSuggestions), ReviewItem, SuggestionState
|
| 96 |
|
- |
- [x] Plugin state: start_analysis_flow, run_analysis, poll_analysis, cancel_analysis, apply_accepted_suggestions
|
| 97 |
|
- |
- [x] Plugin UI: configure analysis screen (checkboxes per analysis type)
|
| 98 |
|
- |
- [x] Plugin UI: analysis progress screen (progress bar, current file, cancel)
|
| 99 |
|
- |
- [x] Plugin UI: review/audit screen (two-panel: sample list + tag suggestions with checkboxes, accept/reject all, apply)
|
| 100 |
|
- |
- [x] CLAP + VST3 bundles build
|
| 101 |
|
- |
|
| 102 |
|
- |
### Done (post-Phase 4)
|
| 103 |
|
- |
- [x] Wire analysis flow into import pipeline (auto-transition in poll_import Complete handler)
|
| 104 |
|
- |
|
| 105 |
|
- |
### Done (UX Overhaul)
|
| 106 |
|
- |
- [x] Waveform data generation: downsampled peak pairs stored as f32 blob in waveform_data table (DB migration 004)
|
| 107 |
|
- |
- [x] Waveform rendering in egui detail panel (custom Painter-based widget with click-to-seek)
|
| 108 |
|
- |
|
| 109 |
|
- |
## Phase 5: Search & Filtering
|
| 110 |
|
- |
|
| 111 |
|
- |
### Done (UX Overhaul)
|
| 112 |
|
- |
- [x] Multi-dimensional filter query builder: BPM range, key, duration, classification, tags (search.rs)
|
| 113 |
|
- |
- [x] Full-text search across sample name (search bar in toolbar, folder/global scope)
|
| 114 |
|
- |
- [x] Filter panel UI: BPM range, duration range, 12 classification checkboxes, key selector, tag pills (ui/filter_panel.rs)
|
| 115 |
|
- |
- [x] Sort by analysis column: Name, BPM, Key, Duration, Classification (click headers, ascending/descending toggle)
|
| 116 |
|
- |
- [x] Multi-select: Cmd+Click toggle, Shift+Click range, Cmd+A select all (Selection struct in state.rs)
|
| 117 |
|
- |
- [x] Context menus: right-click sample (Preview, Copy Path, Delete), right-click folder (Open, Delete)
|
| 118 |
|
- |
- [x] Enriched file list query: LEFT JOIN vfs_nodes + audio_analysis (list_children_enriched in vfs.rs)
|
| 119 |
|
- |
|
| 120 |
|
- |
### Done (Phase 5 completion)
|
| 121 |
|
- |
- [x] Key compatibility filter (compatible_keys() in search.rs, KeyFilterMode::Compatible, filter_panel toggle)
|
| 122 |
|
- |
- [x] Smart folders: saved filter queries, stored as JSON in DB (smart_folders.rs, smart_folders table)
|
| 123 |
|
- |
- [x] Smart folder sidebar section in plugin UI (sidebar.rs, collapsible section)
|
| 124 |
|
- |
- [x] Similarity search: feature vector distance (weighted Euclidean on analysis columns, similarity.rs)
|
| 125 |
|
- |
- [x] "Find similar" context menu action on any sample (file_list.rs, state.find_similar())
|
| 126 |
|
- |
- [x] Near-duplicate detection: peak envelope fingerprint comparison (fingerprint.rs, DB migration 006, "Find Duplicates" context menu)
|
| 127 |
|
- |
|
| 128 |
|
- |
## Phase 6: Bulk Operations & Polish
|
| 129 |
|
- |
|
| 130 |
|
- |
### Done
|
| 131 |
|
- |
- [x] Keyboard shortcuts (j/k navigate, space preview, enter open, / search, Cmd+A select all, Shift+Arrow range select)
|
| 132 |
|
- |
- [x] Right-click context menus throughout
|
| 133 |
|
- |
- [x] DB migration 005: user_config table, transaction() helper on Database
|
| 134 |
|
- |
- [x] Rename pattern engine: {name}, {ext}, {bpm}, {key}, {class}, {duration}, {n}/{nn}/{nnn} tokens, separator collapsing, dedup (rename.rs)
|
| 135 |
|
- |
- [x] Bulk core functions: collect_subtree, list_all_directories (recursive CTEs), restore_node, bulk_add_tag, bulk_remove_tag
|
| 136 |
|
- |
- [x] Undo system: UndoOp enum (BulkDelete/BulkMove/BulkRename/BulkTagAdd/BulkTagRemove), 50-deep stack, Cmd+Z shortcut, toolbar undo button
|
| 137 |
|
- |
- [x] Bulk tag modal: add/remove tag to multi-selection, validation, sample name list (Cmd+T shortcut)
|
| 138 |
|
- |
- [x] Bulk move modal: scrollable directory picker with full paths, move to root or subfolder
|
| 139 |
|
- |
- [x] Bulk rename modal: pattern input with token chips, live preview table (old→new), error display (F2 shortcut)
|
| 140 |
|
- |
- [x] Multi-select context menu: Tag.../Move to.../Rename.../Copy Paths/Delete (right-click when >1 selected)
|
| 141 |
|
- |
- [x] Bulk delete with confirmation: DeleteMultiple variant, snapshot subtrees+tags for undo
|
| 142 |
|
- |
- [x] Column customization: ColumnConfig with show/hide for Classification, BPM, Key, Duration, Peak dB, Tags; persisted to user_config table
|
| 143 |
|
- |
- [x] Merged icon into Name column, eliminated empty vertical gutters in file list
|
| 144 |
|
- |
- [x] 21 new tests (10 rename, 5 vfs bulk, 4 tags bulk, 2 db transaction) → 109 total
|
| 145 |
|
- |
|
| 146 |
|
- |
## Phase 7: Export
|
| 147 |
|
- |
|
| 148 |
|
- |
Raw filesystem export + Rhai plugin-based device export (community/device-maker extensible profiles).
|
| 149 |
|
- |
|
| 150 |
|
- |
Requires: Phase 4 format detection (sample_rate, channels in audio_analysis table)
|
| 151 |
|
- |
|
| 152 |
|
- |
### Done — Raw Export
|
| 153 |
|
- |
- [x] Export VFS subtree to real filesystem (preserving directory structure)
|
| 154 |
|
- |
- [x] Export flattened with naming pattern
|
| 155 |
|
- |
- [x] Export with metadata sidecar (.audiofiles.json per sample)
|
| 156 |
|
- |
- [x] Hardlink optimization for same-filesystem export (transparent fallback to copy)
|
| 157 |
|
- |
- [x] Export dialog in plugin UI (destination, format, channels, structure, sidecar, progress)
|
| 158 |
|
- |
- [x] Single-item Export... in context menus (sample + directory)
|
| 159 |
|
- |
|
| 160 |
|
- |
### Done — Core Export Engine (`audiofiles-core/src/export/`)
|
| 161 |
|
- |
- [x] DeviceProfile, AudioConstraints, NamingRules, ExportConstraints types (export/profile.rs)
|
| 162 |
|
- |
- [x] ChannelConstraint, NamingCase enums (export/profile.rs)
|
| 163 |
|
- |
- [x] DeviceProfileSummary for UI listing (export/profile.rs)
|
| 164 |
|
- |
- [x] ExportFormat::Aiff variant + AIFF dispatch in export_single_item (export/mod.rs)
|
| 165 |
|
- |
- [x] AIFF encoder: big-endian PCM, 80-bit extended sample rate, 16/24-bit (export/encode_aiff.rs)
|
| 166 |
|
- |
- [x] Filename sanitizer: case, separator, stripping, truncation (export/sanitize.rs)
|
| 167 |
|
- |
- [x] device_profile field on ExportConfig (export/mod.rs)
|
| 168 |
|
- |
- [x] AIFF radio button in export UI (ui/export_screens.rs)
|
| 169 |
|
- |
|
| 170 |
|
- |
### Done — Rhai Plugin Runtime (`crates/audiofiles-rhai/`)
|
| 171 |
|
- |
- [x] audiofiles-rhai crate (rhai sync feature, toml, serde, thiserror, audiofiles-core)
|
| 172 |
|
- |
- [x] PluginError enum: ManifestParse, ManifestMissing, ManifestInvalid, ScriptCompile, ScriptRuntime, Io (error.rs)
|
| 173 |
|
- |
- [x] Sandboxed Rhai engine: 100K ops, 32-level calls, 10K string, no print/debug (engine.rs)
|
| 174 |
|
- |
- [x] TOML manifest parsing → DeviceProfile conversion (manifest.rs)
|
| 175 |
|
- |
- [x] RhaiSampleInfo + RhaiExportContext wrapper types with exported getter modules (types.rs)
|
| 176 |
|
- |
- [x] Host API: pad, truncate, case, replace, strip, format_index, file_stem/ext (host_api.rs)
|
| 177 |
|
- |
- [x] CompiledHooks + hook runners: validate_sample, transform_filename, pre/post_export (hooks.rs)
|
| 178 |
|
- |
- [x] Filesystem plugin discovery: scan dirs for manifest.toml (loader.rs)
|
| 179 |
|
- |
- [x] PluginRegistry: case-insensitive lookup, user-overrides-bundled, sorted listing (registry.rs)
|
| 180 |
|
- |
- [x] Bundled plugin embedding via include_str! (bundled.rs)
|
| 181 |
|
- |
- [x] create_registry() public API: bundled + user plugins from ~/.config/audiofiles/plugins/user/ (lib.rs)
|
| 182 |
|
- |
- [x] Backend trait: list_device_profiles() method
|
| 183 |
|
- |
- [x] DirectBackend: PluginRegistry behind device-profiles feature flag
|
| 184 |
|
- |
- [x] IPC: device_profiles.list method constant
|
| 185 |
|
- |
|
| 186 |
|
- |
### Done — Bundled Device Plugins (`crates/audiofiles-rhai/plugins/bundled/`)
|
| 187 |
|
- |
Each plugin is a `manifest.toml`. All declarative, no Rhai scripts needed.
|
| 188 |
|
- |
- [x] Dirtywave M8 (m8/) — 44.1k, 16/24-bit, mono, 127-char lower
|
| 189 |
|
- |
- [x] Elektron Digitakt (digitakt/) — 48k, 16-bit, mono, 64-char lower
|
| 190 |
|
- |
- [x] Elektron Digitakt II (digitakt_ii/) — 48k, 16-bit, both, 64-char lower
|
| 191 |
|
- |
- [x] Roland SP-404 MKII (sp404mkii/) — 44.1/48k, 16/24-bit, both, 12-char upper, 128MB limit
|
| 192 |
|
- |
- [x] Akai MPC One/Live (mpc/) — 44.1/48k, 16/24-bit, both, 64-char lower
|
| 193 |
|
- |
- [x] Polyend Tracker (polyend_tracker/) — 44.1k, 16-bit, both, 8-char upper
|
| 194 |
|
- |
- [x] Synthstrom Deluge (deluge/) — 44.1k, 16/24-bit, both, WAV/AIFF, 64-char original
|
| 195 |
|
- |
- [x] 1010music Blackbox (blackbox/) — 48k, 16/24-bit, both, 64-char lower
|
| 196 |
|
- |
- [x] Korg Volca Sample 2 (volca_sample_2/) — 31.25k, 16-bit, mono, 100-slot limit
|
| 197 |
|
- |
- [x] TE OP-1 / OP-1 Field (op1/) — 44.1k, 16-bit, mono, AIFF, 11-char lower
|
| 198 |
|
- |
- [x] Elektron Octatrack (octatrack/) — 44.1k, 16/24-bit, both, WAV/AIFF, 64-char upper
|
| 199 |
|
- |
- [x] Elektron Model:Samples (model_samples/) — 48k, 16-bit, mono, 64-char lower
|
| 200 |
|
- |
- [x] Novation Circuit Rhythm (circuit_rhythm/) — 44.1/48k, 16-bit, both, 32-char lower
|
| 201 |
|
- |
- [x] NI Maschine+ (maschine_plus/) — 44.1/48k, 16/24-bit, both, 128-char original
|
| 202 |
|
- |
|
| 203 |
|
- |
### Done — Pipeline Integration
|
| 204 |
|
- |
- [x] run_export() applies device profile constraints (format, rate, depth, channels from profile)
|
| 205 |
|
- |
- [x] run_export() applies NamingRules via sanitize_filename() when device profile set
|
| 206 |
|
- |
- [x] run_export() runs validate_sample hook (reject incompatible samples)
|
| 207 |
|
- |
- [x] run_export() runs transform_filename hook (custom naming beyond NamingRules)
|
| 208 |
|
- |
- [x] File size limit enforcement (skip/warn if encoded file exceeds max_file_size_bytes)
|
| 209 |
|
- |
- [x] Dedup with device-safe `_2`, `_3` suffixes when sanitization creates collisions
|
| 210 |
|
- |
- [x] name_overrides field on ExportConfig for hook-transformed names
|
| 211 |
|
- |
- [x] resolve_output_names made pub for backend pre-computation
|
| 212 |
|
- |
|
| 213 |
|
- |
### Done — UI
|
| 214 |
|
- |
- [x] Device export dialog in plugin UI (profile picker, destination, preview, progress)
|
| 215 |
|
- |
- [x] Device profile picker: ComboBox listing bundled + user profiles with manufacturer metadata
|
| 216 |
|
- |
- [x] Custom profile option ("None (manual)" hides profile overrides, shows all manual controls)
|
| 217 |
|
- |
|
| 218 |
|
- |
## Phase 8: Standalone App (Done sections)
|
| 219 |
|
- |
|
| 220 |
|
- |
Wraps audiofiles-core in a native window, for users who want to manage outside DAW.
|
| 221 |
|
- |
|
| 222 |
|
- |
### Done
|
| 223 |
|
- |
- [x] Created audiofiles-browser shared crate (editor, state, preview extracted from plugin)
|
| 224 |
|
- |
- [x] Rewired audiofiles-plugin to use audiofiles-browser (editor.rs, state.rs deleted from plugin)
|
| 225 |
|
- |
- [x] audiofiles-app binary: eframe + cpal standalone reusing plugin UI panels
|
| 226 |
|
- |
- [x] cpal audio output stream for preview playback (audio.rs)
|
| 227 |
|
- |
- [x] Native file drag-and-drop import (egui dropped_files)
|
| 228 |
|
- |
- [x] CLI file argument import (open files from command line / OS file associations)
|
| 229 |
|
- |
- [x] macOS Info.plist with audio file type associations (.wav, .flac, .mp3, .ogg, .aiff)
|
| 230 |
|
- |
- [x] Import logic ported from TUI to BrowserState (import_path, import_single_file, import_directory_recursive)
|
| 231 |
|
- |
- [x] Old TUI app replaced (ratatui/crossterm removed)
|
| 232 |
|
- |
- [x] 84 tests pass, full workspace compiles cleanly
|
| 233 |
|
- |
|
| 234 |
|
- |
### Done (UX Overhaul)
|
| 235 |
|
- |
- [x] Editor.rs refactored from 1006→191 lines (thin dispatcher to 10 UI submodules)
|
| 236 |
|
- |
- [x] 14 new UI files: ui/{mod,theme,widgets,toolbar,sidebar,file_list,detail,footer,filter_panel,import_screens,overlays}.rs + waveform.rs
|
| 237 |
|
- |
- [x] 109 tests pass (9 UX overhaul + 21 Phase 6 bulk ops)
|
| 238 |
|
- |
|
| 239 |
|
- |
## Pre-Launch Fixes (from audit 2026-02-27)
|
| 240 |
|
- |
|
| 241 |
|
- |
### Done
|
| 242 |
|
- |
- [x] `CoreError::RenameInvalid` variant, `RenamePattern::parse()` returns typed error (rename.rs, error.rs)
|
| 243 |
|
- |
- [x] `import_single_file()` returns `Result<_, CoreError>` instead of String (browser/import.rs)
|
| 244 |
|
- |
- [x] `bulk_add_tag`/`bulk_remove_tag` wrapped in explicit transactions (tags.rs)
|
| 245 |
|
- |
|
| 246 |
|
- |
### Done
|
| 247 |
|
- |
- [x] Add tests for `state.rs` orchestration logic (95 tests added: selection, bulk ops, import/analysis, navigation, rename, column config, misc)
|
| 248 |
|
- |
- [x] `audio.rs` `start_output_stream()` returns typed `AudioError` enum
|
| 249 |
|
- |
|
| 250 |
|
- |
## Audit Follow-Up (2026-02-28)
|
| 251 |
|
- |
|
| 252 |
|
- |
### Done
|
| 253 |
|
- |
- [x] Fix 4 clippy warnings in audiofiles-browser: `drop_non_drop`, `clone_on_copy`, two `collapsible_if`
|
| 254 |
|
- |
- [x] Escape SQL LIKE wildcards in search.rs and tags.rs via `escape_like()`
|
| 255 |
|
- |
- [x] Wrap each migration step in transactions
|
| 256 |
|
- |
- [x] Remove unused import in error.rs test module
|
| 257 |
|
- |
- [x] Validate hash parameter in `store.rs::sample_path()` (validate_hash checks 64-char lowercase hex)
|
| 258 |
|
- |
- [x] Add tests for `audiofiles-plugin` (8 tests) and `audiofiles-app` (5 tests)
|
| 259 |
|
- |
- [x] `contents.clone()` in file_list.rs confirmed as Arc clone (cheap reference count bump)
|
| 260 |
|
- |
|
| 261 |
|
- |
## Audit Follow-Up (2026-02-27)
|
| 262 |
|
- |
|
| 263 |
|
- |
### Done
|
| 264 |
|
- |
- [x] Add tests for `browser/preview.rs` (7 tests: mono/stereo/multichannel decode, sample rate, error cases)
|
| 265 |
|
- |
- [x] Split `state.rs` into directory module (state/mod.rs, navigation.rs, import_workflow.rs, bulk_ops.rs, tests.rs)
|
| 266 |
|
- |
|
| 267 |
|
- |
## Audit Follow-Up (2026-02-28, third audit)
|
| 268 |
|
- |
|
| 269 |
|
- |
### Done — Clippy
|
| 270 |
|
- |
- [x] Fix 3 `items_after_test_module`: moved `save_analysis`, `decode_to_mono`, `worker_loop` above their test modules
|
| 271 |
|
- |
- [x] Fix 2 `io_other_error` in test code: use `std::io::Error::other()`
|
| 272 |
|
- |
- [x] Fix `identity_op` in test: changed `0 + 1` to `1`
|
| 273 |
|
- |
- [x] `too_many_arguments`: added `#[allow(clippy::too_many_arguments)]` on test helper
|
| 274 |
|
- |
|
| 275 |
|
- |
## Audit Follow-Up (2026-03-01, test coverage + error handling)
|
| 276 |
|
- |
|
| 277 |
|
- |
### Done — Test Coverage
|
| 278 |
|
- |
- [x] Centralized `split_name_ext` from 3 duplicate locations into `util.rs` + 3 tests (normal, no ext, multiple dots)
|
| 279 |
|
- |
- [x] Smart folder error tests: 2 tests (rename nonexistent, corrupt JSON graceful fallback)
|
| 280 |
|
- |
|
| 281 |
|
- |
### Done — Error Handling
|
| 282 |
|
- |
- [x] Added `CoreError::Serialization(String)` variant — distinct from `Export` for JSON serialization failures
|
| 283 |
|
- |
- [x] Fixed smart_folders.rs: `CoreError::Export` → `CoreError::Serialization` for serde_json errors
|
| 284 |
|
- |
- [x] Added `eprintln!` warning for corrupt smart folder JSON (was silent `unwrap_or_default`)
|
| 285 |
|
- |
- [x] Error handling audit: no other actionable issues (eprintln is correct for CLAP plugin crate, bundled.rs already returns Result)
|
| 286 |
|
- |
|
| 287 |
|
- |
## Phase 7B: MIDI Instrument Mode (2026-03-01)
|
| 288 |
|
- |
|
| 289 |
|
- |
### Done — Core Types (audiofiles-core/src/instrument.rs)
|
| 290 |
|
- |
- [x] InstrumentMode enum (Chromatic, MultiSample)
|
| 291 |
|
- |
- [x] AdsrEnvelope struct (5ms attack, 100ms decay, sustain 1.0, 50ms release defaults)
|
| 292 |
|
- |
- [x] VelocityTarget enum (Volume)
|
| 293 |
|
- |
- [x] KeyZone struct (sample_hash, name, root/low/high note, velocity range)
|
| 294 |
|
- |
- [x] InstrumentConfig struct (mode, envelope, velocity_target, max_voices=8)
|
| 295 |
|
- |
- [x] key_to_root_note(): parse "A minor" → MIDI note 57, all 12 pitch classes + sharps/flats
|
| 296 |
|
- |
- [x] note_name(): MIDI note → "C3", "A#4" (C-1=0 convention, C3=48, C4=60)
|
| 297 |
|
- |
- [x] 9 tests
|
| 298 |
|
- |
|
| 299 |
|
- |
### Done — Instrument Playback State (audiofiles-browser/src/instrument.rs)
|
| 300 |
|
- |
- [x] EnvelopePhase enum (Idle, Attack, Decay, Sustain, Release)
|
| 301 |
|
- |
- [x] Voice struct (active, note, velocity, age, fractional position, zone_index, envelope state)
|
| 302 |
|
- |
- [x] LoadedZone struct (PreviewBuffer, root/low/high note, velocity range)
|
| 303 |
|
- |
- [x] InstrumentPlayback struct (config, zone_buffers, fixed voice pool, note_counter, active, sample_rate)
|
| 304 |
|
- |
- [x] Extended SharedState with instrument: Mutex<InstrumentPlayback>
|
| 305 |
|
- |
- [x] Extended BrowserState with instrument_visible, instrument_root_note
|
| 306 |
|
- |
- [x] 2 tests
|
| 307 |
|
- |
|
| 308 |
|
- |
### Done — MIDI Voice Engine (audiofiles-plugin/src/instrument.rs)
|
| 309 |
|
- |
- [x] fill_instrument(): try_lock, drain MIDI events, render voices, return bool
|
| 310 |
|
- |
- [x] NoteOn: zone matching, free voice allocation or oldest-note stealing
|
| 311 |
|
- |
- [x] NoteOff: move matching voices to Release phase
|
| 312 |
|
- |
- [x] Choke: immediately deactivate matching voices
|
| 313 |
|
- |
- [x] Pitch shifting: linear interpolation, rate = 2^(semitone/12) * (source_rate/host_rate)
|
| 314 |
|
- |
- [x] ADSR envelope: per-sample advance, linear ramps for A/D/R, constant S
|
| 315 |
|
- |
- [x] Velocity: gain = envelope_level * velocity (Volume target)
|
| 316 |
|
- |
- [x] Plugin integration: MIDI_INPUT = MidiConfig::Basic, instrument priority over preview
|
| 317 |
|
- |
- [x] 8 tests
|
| 318 |
|
- |
|
| 319 |
|
- |
### Done — Sample Loading
|
| 320 |
|
- |
- [x] load_chromatic_sample(): decode, derive root from analysis, single zone 0-127
|
| 321 |
|
- |
- [x] toggle_instrument(): toggle active/visible
|
| 322 |
|
- |
- [x] add_instrument_zone(): decode, append zone, set MultiSample mode
|
| 323 |
|
- |
- [x] remove_instrument_zone(): remove zone, kill affected voices, fix indices
|
| 324 |
|
- |
- [x] Context menu "Play as Instrument" for samples
|
| 325 |
|
- |
|
| 326 |
|
- |
### Done — Keyboard + Panel UI
|
| 327 |
|
- |
- [x] I key toggles instrument panel visibility
|
| 328 |
|
- |
- [x] Instrument panel: bottom panel (120px), 3 sections
|
| 329 |
|
- |
- [x] Mode selector: Chromatic / Multi-sample radio buttons
|
| 330 |
|
- |
- [x] Piano keyboard: 3-octave custom painter, active note highlighting, root note dot
|
| 331 |
|
- |
- [x] Click-to-set root note on keyboard, octave +/- navigation
|
| 332 |
|
- |
- [x] ADSR sliders: logarithmic for time params, A/D 1ms-5s, S 0-1, R 1ms-10s
|
| 333 |
|
- |
- [x] Zone bars: colored horizontal bars below keyboard per zone (multi-sample mode)
|
| 334 |
|
- |
- [x] Right-click zone to remove
|
| 335 |
|
- |
- [x] Drag-and-drop: file list samples → keyboard creates zone (root +/- 6 semitones)
|
| 336 |
|
- |
|
| 337 |
|
- |
### Files Created
|
| 338 |
|
- |
- crates/audiofiles-core/src/instrument.rs
|
| 339 |
|
- |
- crates/audiofiles-browser/src/instrument.rs
|
| 340 |
|
- |
- crates/audiofiles-plugin/src/instrument.rs
|
| 341 |
|
- |
- crates/audiofiles-browser/src/ui/instrument_panel.rs
|
| 342 |
|
- |
|
| 343 |
|
- |
### Files Modified
|
| 344 |
|
- |
- crates/audiofiles-core/src/lib.rs
|
| 345 |
|
- |
- crates/audiofiles-browser/src/lib.rs
|
| 346 |
|
- |
- crates/audiofiles-browser/src/state/mod.rs
|
| 347 |
|
- |
- crates/audiofiles-browser/src/editor.rs
|
| 348 |
|
- |
- crates/audiofiles-browser/src/ui/mod.rs
|
| 349 |
|
- |
- crates/audiofiles-browser/src/ui/file_list.rs
|
| 350 |
|
- |
- crates/audiofiles-plugin/src/lib.rs
|
| 351 |
|
- |
|
| 352 |
|
- |
### Test Count
|
| 353 |
|
- |
- 410 → 429 (19 new: 9 core, 2 browser, 8 plugin)
|
| 354 |
|
- |
|
| 355 |
|
- |
## Phase 9: Cloud Sync (MNW SyncKit)
|
| 356 |
|
- |
|
| 357 |
|
- |
### Done
|
| 358 |
|
- |
- [x] Create `crates/audiofiles-sync/` crate (lib.rs, error.rs, service.rs, auth.rs, scheduler.rs)
|
| 359 |
|
- |
- [x] Workspace deps: tokio, uuid, base64, chrono, rand; synckit-client path dep
|
| 360 |
|
- |
- [x] Migration 007: `sync_state` table, `sync_changelog` table, `vfs.sync_files` column
|
| 361 |
|
- |
- [x] 27 triggers across 9 tables (samples, audio_analysis, vfs, vfs_nodes, tags, collections, collection_members, smart_folders, user_config), all guarded by `applying_remote != '1'`
|
| 362 |
|
- |
- [x] `service.rs`: table_columns whitelist, FK-safe UPSERT/DELETE ordering, push/pull with `spawn_blocking` (Connection is !Send), composite PK handling, initial snapshot, cleanup
|
| 363 |
|
- |
- [x] `auth.rs`: PKCE helpers (verifier/challenge/state), localhost callback server
|
| 364 |
|
- |
- [x] `scheduler.rs`: 60s check interval, exponential backoff (2^n min, max 15), auto-sync guards
|
| 365 |
|
- |
- [x] `lib.rs`: `SyncManager` public API, `SyncStatus`/`SyncState` types, interior-mutable command channel
|
| 366 |
|
- |
- [x] `audiofiles-app/main.rs`: tokio runtime (2 threads), `SyncManager` from env vars (`AF_SYNC_SERVER_URL`, `AF_SYNC_API_KEY`), session restore, scheduler start, `needs_refresh` polling
|
| 367 |
|
- |
- [x] `Vfs` struct: `sync_files: bool` field, `set_vfs_sync_files`/`get_vfs_sync_files` in core + Backend trait + DirectBackend
|
| 368 |
|
- |
- [x] `sync_panel.rs`: egui Window overlay with 4 states (Disconnected, Authenticating, NeedsEncryption, Ready)
|
| 369 |
|
- |
- [x] Ready state: Sync Now, auto-sync toggle, interval selector (5/15/30/60 min), per-VFS "Sync audio files" checkboxes, Disconnect
|
| 370 |
|
- |
- [x] Toolbar "Sync" button, Escape dismisses panel
|
| 371 |
|
- |
- [x] CLAP plugin updated: passes `None` for sync manager
|
| 372 |
|
- |
- [x] 483 tests passing, 0 clippy warnings, DB v7
|
| 373 |
|
- |
|
| 374 |
|
- |
### Alpha Polish (2026-03-08)
|
| 375 |
|
- |
- [x] VFS management UI: context menus (rename/delete) on sidebar libraries, "+" button for new library
|
| 376 |
|
- |
- [x] Directory management: "New Folder" and "Rename" in directory context menu
|
| 377 |
|
- |
- [x] 4 modal dialogs: VFS create, VFS rename, directory create, directory rename (overlays.rs, editor.rs)
|
| 378 |
|
- |
|
| 379 |
|
- |
### Done — Per-VFS sync_files toggle
|
| 380 |
|
- |
- [x] Per-VFS sync_files toggle: wired into blob upload/download queries (sync_files=1 WHERE clause)
|
| 381 |
|
- |
|
| 382 |
|
- |
### Testing — Export Pipeline (Done)
|
| 383 |
|
- |
- [x] `resolve_output_names()` tests — 9 tests (no pattern, format changes, patterns, name overrides, dedup after sanitization)
|
| 384 |
|
- |
- [x] `run_export()` tests — 5 tests (AIFF format, cancel callback, missing source error, sidecar fields, directory structure)
|
| 385 |
|
- |
|
| 386 |
|
- |
### 9F: Sync Without Blobs (Done)
|
| 387 |
|
- |
- [x] Post-pull cloud_only marking: `mark_cloud_only_samples()` scans for samples with no local blob, sets cloud_only=1 with trigger suppression
|
| 388 |
|
- |
- [x] VFS nodes referencing cloud-only samples show cloud icon, muted text, no preview/playback/export/instrument
|
| 389 |
|
- |
- [x] Analysis results sync independently (audio_analysis in sync whitelist, usable for search/filter without local blob)
|
| 390 |
|
- |
- [x] Tags and collections work on cloud-only samples (metadata-only operations)
|
| 391 |
|
- |
- [x] `cloud_only` field added to `VfsNodeWithAnalysis`, enriched queries JOIN samples table
|
| 392 |
|
- |
- [x] 7 new tests (4 mark_cloud_only, 3 enriched query cloud_only)
|
| 393 |
|
- |
|
| 394 |
|
- |
## Audit Action Items (2026-03-11, sixth audit) — All resolved
|
| 395 |
|
- |
|
| 396 |
|
- |
- [x] CRITICAL: Clear applying_remote flag on startup (crash recovery -- silent data loss vector) — cleared at top of perform_sync()
|
| 397 |
|
- |
- [x] Migrate browser crate from eprintln! to tracing (25 sites across 8 files, appropriate error/warn/debug levels)
|
| 398 |
|
- |
- [x] Split import_screens.rs (668 LOC) into directory module (configure.rs, progress.rs, tagging.rs)
|
| 399 |
|
- |
- [x] Fix theme include_str! paths (moved 9 theme TOMLs into audiofiles-browser/themes/, paths now 2 levels)
|
| 400 |
|
- |
- [x] Add end-to-end integration test (3 tests: full pipeline import->analyze->search->tag->export, analysis roundtrip verification, multi-sample search with combined filters)
|
| 401 |
|
- |
|
| 402 |
|
- |
## Audit Action Items (2026-03-13, seventh audit — pre-launch skeptical lens)
|
| 403 |
|
- |
|
| 404 |
|
- |
- [x] Use SmallVec or pre-allocated buffer for MIDI events in audio path (SmallVec<[NoteEvent; 8]> replaces Vec, zero heap allocation for ≤8 events per buffer)
|
| 405 |
|
- |
- [x] Add allowlist validation to `query_sample_field` SQL column interpolation (ALLOWED_FIELDS const, returns CoreError::Internal on disallowed field)
|
| 406 |
|
- |
- [x] Add hash re-verification method on content store (`SampleStore::verify_sample` — re-hashes file, compares to expected hash, 3 tests)
|
| 407 |
|
- |
- [x] Fix test count: 532 tests (was 528, +4 new: allowlist validation, verify match/corruption/missing)
|
| 408 |
|
- |
|
| 409 |
|
- |
## Brand Rename (2026-03-19)
|
| 410 |
|
- |
|
| 411 |
|
- |
### Done
|
| 412 |
|
- |
- [x] Rename "AudioFiles" → "audiofiles" in all user-visible strings (window title, tray tooltip, headings, sync panel, auth callback)
|
| 413 |
|
- |
- [x] Rename data directory path `.join("AudioFiles")` → `.join("audiofiles")`
|
| 414 |
|
- |
- [x] Rename export default directory "AudioFiles Export" → "audiofiles Export"
|
| 415 |
|
- |
- [x] Rename CFBundleName in Info.plist
|
| 416 |
|
- |
- [x] Rename .app/.zip in release.sh (`AudioFiles.app` → `audiofiles.app`)
|
| 417 |
|
- |
- [x] Rename in all doc comments, tracing messages across 6 crates
|
| 418 |
|
- |
- [x] Rename in all project docs (CLAUDE.md, launch.md, audit.md, todo.md, content_seed.md, architecture.md, pitches, strategy docs)
|
| 419 |
|
- |
- [x] Rust struct `AudioFilesApp` unchanged (PascalCase convention)
|
| 420 |
|
- |
- [x] Crate names `audiofiles-*` unchanged (already lowercase)
|
| 421 |
|
- |
- [x] Bundle identifier `com.audiofiles.app` unchanged (already lowercase)
|
| 422 |
|
- |
- [x] 560 tests pass, cargo check + clippy clean, zero stale references
|
| 423 |
|
- |
|
| 424 |
|
- |
## Deferred (Done)
|
| 425 |
|
- |
|
| 426 |
|
- |
- [x] Resolve PreviewBuffer name collision (ipc/protocol.rs renamed to IpcPreviewBuffer)
|
| 427 |
|
- |
|
| 428 |
|
- |
---
|
| 429 |
|
- |
|
| 430 |
|
- |
## Rust Patterns Audit (2026-03-21)
|
| 431 |
|
- |
|
| 432 |
|
- |
- [x] Remove unnecessary `codec_params.clone()` in 3 decode paths
|
| 433 |
|
- |
- [x] Stop cloning full UpdateStatus struct every UI frame — extract 3 fields under lock
|
| 434 |
|
- |
- [x] Deferred breadcrumb mutation — iterate by ref, apply navigation after loop
|
| 435 |
|
- |
- [x] Improve HashMap deduplicate in rename.rs — use get_mut to avoid extra clones
|
| 436 |
|
- |
- [x] Eliminate double hashes clone in bulk_ops.rs — move into undo op instead of clone
|
| 437 |
|
- |
- [x] Reduce import_workflow hash clones — build SampleHash once, reuse for name fallback
|
| 438 |
|
- |
- [x] Tag tree sidebar — dot-separated tags rendered as collapsible folder tree (sidebar.rs)
|
| 439 |
|
- |
- [x] Remove Linux Wayland drag-out backend (linux.rs deleted, wayland-client/raw-window-handle deps removed)
|
| 440 |
|
- |
- [x] Remove unused smallvec dependency
|
| 441 |
|
- |
- [x] Fix `!is_ok()` → `is_err()` in vfs_mirror test
|
| 442 |
|
- |
|
| 443 |
|
- |
---
|
| 444 |
|
- |
|
| 445 |
|
- |
## TagTree Integration (2026-03-21)
|
| 446 |
|
- |
|
| 447 |
|
- |
- [x] Add `tagtree` workspace dependency
|
| 448 |
|
- |
- [x] Replace local `validate_tag()` with `tagtree::validate_with()` (TagConfig: max_depth 5, max_length 100)
|
| 449 |
|
- |
- [x] Replace local `escape_like()` with `tagtree::escape_like()` in tags.rs and search.rs
|
| 450 |
|
- |
- [x] Replace `find_by_prefix` LIKE pattern with `tagtree::like_descendant_pattern()`
|
| 451 |
|
- |
- [x] Replace `remove_tags_by_prefix` LIKE pattern with `tagtree::like_descendant_pattern()`
|
| 452 |
|
- |
- [x] Replace `list_children_tags` with `tagtree::children_at_prefix()`
|
| 453 |
|
- |
- [x] Remove local `escape_like()` from util.rs (and its 5 tests)
|
| 454 |
|
- |
- [x] All 568 tests pass
|