Skip to content

Commit 033224e

Browse files
ophiocusclaude
andcommitted
v0.4.86 — TBSS-FR-0018: sample library, multisample zones, piano editor, PD demos
E1: src/samplelib.rs — curated pack manifest (Philharmonia S3 zips + VCSL github archive; URLs + sizes verified live), licenses surfaced before download, background download with progress + zip-slip-guarded extract to the config-dir sample-library. E2: pitch-from-filename parser covering the three real conventions (Philharmonia _As2_, Iowa .Db5., VCSL C4/F#1 tokens), tested on synthetic names AND on the real packs via an env-gated probe: percussion = 0 pitched / 148 honest unparseables; brass = 2,940 pitched, 0 failures (french horn/trombone/trumpet/tuba). E3: multisample zones in the engine — SampleZone{root, sample, trim, loop} on TrackerInstrument, nearest-root selection at trigger, zone- scoped bounds/loops in the mixer, legacy no-zone instruments behave identically (equivalence test). 3 new engine tests. E4: five-octave piano (C-2..B-6) in the instrument editor — zone coverage tinting, black/white hit-testing, click auditions through render_one_note (real engine path) under app-wide exclusivity. E5: per-key zone wave lane — peaks from decoded data, trim band drawn, drag-selection writes zone.start/end back to the song. Also (user addition): src/tracker_demos.rs — bundled public-domain demos (Ode to Joy 1824, Frère Jacques trad., Korobeiniki 1861) as patterns playing instrument 00; loading preserves the user's instruments; melodies range- and shape-tested. Sample Library window wired app-wide (modal accounting + Esc), one- file-per-note dynamic ranking capped at 61 zones, instruments stream through the tracker's existing background decode queue. 207 passed + 5 ignored, clippy 1.95.0 clean, smoke-tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 982f2d7 commit 033224e

12 files changed

Lines changed: 3115 additions & 1546 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,34 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); thi
88

99
(Nothing yet — known issues all resolved as of v0.4.23.)
1010

11+
## [0.4.86] — 2026-08-24
12+
13+
### Added — Tracker sample library, multisample instruments, piano editor (TBSS-FR-0018)
14+
15+
- **🌐 Sample Library.** Download free instrument packs from inside the
16+
Tracker — Philharmonia Orchestra (strings / brass / woodwind /
17+
percussion) and the CC0 Versilian Community Sample Library. Licenses
18+
are shown before download; packs come from their sources on demand
19+
and nothing is bundled with TinyBooth. Verified against the real
20+
packs: the Brass zip alone yields **2,940 per-note recordings, every
21+
one auto-mapped to its pitch** from the filename; unpitched
22+
percussion is honestly reported rather than misfiled.
23+
- **Multisample instruments.** One pack click builds an instrument with
24+
a zone per note — playing C-3 uses the C-3 recording, not a
25+
five-octave stretch of one sample. Existing single-sample instruments
26+
and songs load and render exactly as before (pinned by test).
27+
- **Five-octave piano editor.** Each instrument shows a C-2…B-6 piano:
28+
green keys hold a real recording, others pitch-stretch the nearest
29+
zone; clicking a key auditions it through the real engine (filter,
30+
gain, loop and all).
31+
- **Per-key wave editor.** The selected key opens its zone's waveform
32+
with the familiar wave-lane language — and dragging a selection
33+
**sets that zone's trim** directly.
34+
- **Demo songs.** A Demos menu loads bundled public-domain classics —
35+
*Ode to Joy* (Beethoven, 1824), *Frère Jacques* (trad.), and
36+
*Korobeiniki* (trad., 1861 — yes, that one) — as ready-made patterns
37+
that play through whatever instrument you've built.
38+
1139
## [0.4.85] — 2026-08-24
1240

1341
### Fixed — audit tranche 2: data-safety and integrity

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tinybooth-sound-studio"
3-
version = "0.4.85"
3+
version = "0.4.86"
44
edition = "2021"
55
authors = ["ophiocus <csantanad@gmail.com>"]
66
description = "Channel recorder, visualizer, and TinyBooth project exporter"

‎docs/feature-requests/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Design proposals for upcoming TinyBooth Sound Studio features. Each RFC follows
2121
| **TBSS-FR-0015** | [Record tab: in-listing playback + live monitor](TBSS-FR-0015-record-tab-live-monitor-and-listing-playback.md) | ✅ Implemented | v0.4.81 | Takes audition in place: ▶/■ per row, scrubbable playhead on the thumbnail, app-wide audio exclusivity, ⇪ integrate-into-project (folder + `.tib`), ▶-to-mixer detour retired. 🎙 live input monitor: capture stream + DSP chain + viz with no WAV writer, for base levels before recording. |
2222
| **TBSS-FR-0016** | [Keyboard focus & keymap interface](TBSS-FR-0016-keyboard-focus-keymap.md) | ✅ Implemented (core) | v0.4.82 | Standardized keyboard ownership: fixed precedence (text entry > modal > editor widget > global) + a declarative binding registry with pure, unit-tested resolution. Ships Space (play/pause), Ctrl+S (save), F1 (manual, migrated), Esc (close topmost modal). Defines the Editor-scope contract the FR-0014 tracker grid will implement; help overlay + rebinding deferred. |
2323
| **TBSS-FR-0017** | [Mix: scrub, highlight, clip-through-the-chain](TBSS-FR-0017-mix-scrub-select-clip.md) | ✅ Implemented | v0.4.83 | Mix lanes gain a draggable playhead (wires the dormant `seek_frames`), click-drag region highlight per-track (lane drag) or all-tracks (ruler drag), and ✂ Clip: render the highlighted range through the full playback chain (enabled corrections, faders, automation, polarity, master) via a range/track-filtered `export::mixdown` → WAV + optional ⇪ integrate as a new track. |
24-
| **TBSS-FR-0018** | [Tracker sample library + multisample piano](TBSS-FR-0018-sample-library-multisample-piano.md) | 📝 Proposed | — | In-app acquisition of free instrument samples at volume (curated manifest: Philharmonia download-on-demand, VCSL CC0, Iowa MIS — licenses vetted, nothing bundled), pitch parsed from filenames → auto-built **multisample** instruments (nearest-zone trigger), edited via a five-octave piano (coverage display, per-key audition) whose selected key spawns a wave-editor lane with the Record/Mix scrub+selection language — selection = zone trim/loop. |
24+
| **TBSS-FR-0018** | [Tracker sample library + multisample piano](TBSS-FR-0018-sample-library-multisample-piano.md) | ✅ Implemented (v1) | v0.4.86 | In-app acquisition of free instrument samples at volume (curated manifest: Philharmonia download-on-demand, VCSL CC0, Iowa MIS — licenses vetted, nothing bundled), pitch parsed from filenames → auto-built **multisample** instruments (nearest-zone trigger), edited via a five-octave piano (coverage display, per-key audition) whose selected key spawns a wave-editor lane with the Record/Mix scrub+selection language — selection = zone trim/loop. |
2525

2626
## Convention
2727

‎src/app.rs‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,8 @@ pub struct TinyBoothApp {
492492
/// Tracker tab state. TBSS-FR-0014 (state lives in ui/tracker.rs
493493
/// per the TrimState convention the audit endorsed).
494494
pub tracker_state: crate::ui::tracker::TrackerUiState,
495+
/// Sample Library window (TBSS-FR-0018).
496+
pub samplelib_state: crate::ui::samplelib::SampleLibUiState,
495497

496498
/// Mixer/automation recorder. Captures fader gestures while a strip's
497499
/// arm toggle is on and the player is in Playing state. Flushed into
@@ -735,6 +737,7 @@ impl TinyBoothApp {
735737
album_state: AlbumUiState::default(),
736738
chordvideo_state: ChordVideoUiState::default(),
737739
tracker_state: crate::ui::tracker::TrackerUiState::default(),
740+
samplelib_state: crate::ui::samplelib::SampleLibUiState::default(),
738741
recorder: crate::automation::Recorder::default(),
739742
mix_console_fraction: 0.42,
740743
recordings_page: 0,
@@ -1084,7 +1087,8 @@ impl TinyBoothApp {
10841087
/// reference window (reading it while working is the point), and
10851088
/// counting it would kill its own F1 toggle.
10861089
fn any_modal_open(&self) -> bool {
1087-
self.show_admin
1090+
self.samplelib_state.open
1091+
|| self.show_admin
10881092
|| self.show_trim
10891093
|| self.show_health
10901094
|| self.show_telemetry_settings
@@ -1100,6 +1104,10 @@ impl TinyBoothApp {
11001104
/// migration prompt is deliberately NOT Esc-closable: its choices
11011105
/// differ materially, so it requires an explicit click.
11021106
fn close_top_modal(&mut self) {
1107+
if self.samplelib_state.open {
1108+
self.samplelib_state.open = false;
1109+
return;
1110+
}
11031111
if self.import_dialog.take().is_some() {
11041112
return;
11051113
}
@@ -3388,6 +3396,10 @@ impl eframe::App for TinyBoothApp {
33883396
) {
33893397
crate::ui::update_dialog::show(ctx);
33903398
}
3399+
// Sample Library window (TBSS-FR-0018) — floats over any tab.
3400+
{
3401+
crate::ui::samplelib::show(self, ctx);
3402+
}
33913403
if should_close_for_update {
33923404
// Stop any in-flight recording first so the WAV writer
33933405
// finalises its header before Drop. Save is implicit via

‎src/main.rs‎

Lines changed: 119 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,119 @@
1-
#![windows_subsystem = "windows"]
2-
3-
mod album;
4-
mod analysis;
5-
mod app;
6-
mod audio;
7-
mod audiodecode;
8-
mod automation;
9-
mod chorddb;
10-
mod chordgrid;
11-
mod chordvideo;
12-
mod chordvoice;
13-
mod cleanup;
14-
mod coherence;
15-
mod config;
16-
mod crossfade;
17-
mod crossfade_player;
18-
mod dsp;
19-
mod export;
20-
mod fretboard;
21-
mod generator;
22-
mod git_update;
23-
mod keymap;
24-
mod lufs;
25-
mod manual;
26-
mod player;
27-
mod project;
28-
mod suno_import;
29-
mod suno_meta;
30-
mod tba;
31-
mod tba_album;
32-
mod telemetry;
33-
mod tib;
34-
mod tib_project;
35-
mod tracker;
36-
mod trim;
37-
mod ui;
38-
mod wav_meta;
39-
40-
use eframe::egui;
41-
use std::sync::Arc;
42-
43-
pub const APP_NAME: &str = "TinyBooth Sound Studio";
44-
pub const APP_WINDOW_TITLE: &str = "TinyBooth Sound Studio";
45-
pub const APP_GH_REPO: &str = "ophiocus/TinyBoothSoundStudio";
46-
47-
/// PNG bitmap embedded at compile time for the window's top-left icon.
48-
/// 256×256 is plenty — Windows' DWM scales it down for titlebar/taskbar.
49-
const VIEWPORT_ICON_PNG: &[u8] = include_bytes!("../assets/icon_viewport.png");
50-
51-
/// Decode the embedded icon into raw RGBA for egui. Falls back silently
52-
/// if decoding ever fails — we'd rather ship without a custom icon than
53-
/// refuse to start.
54-
fn load_icon() -> egui::IconData {
55-
match image::load_from_memory(VIEWPORT_ICON_PNG) {
56-
Ok(img) => {
57-
let rgba = img.to_rgba8();
58-
let (width, height) = rgba.dimensions();
59-
egui::IconData {
60-
rgba: rgba.into_raw(),
61-
width,
62-
height,
63-
}
64-
}
65-
Err(_) => egui::IconData::default(),
66-
}
67-
}
68-
69-
/// Install a panic hook that appends the panic message + backtrace to
70-
/// `%APPDATA%\TinyBooth Sound Studio\logs\panic.log`. Without this, a
71-
/// panic in a GUI-subsystem build has nowhere to go — there's no
72-
/// console, so the window just vanishes with no trace (see the v0.4.39
73-
/// "session vanished with no log" investigation). Keeps the default
74-
/// hook chained so behaviour under a debugger/console is unchanged.
75-
fn install_panic_logger() {
76-
let default_hook = std::panic::take_hook();
77-
std::panic::set_hook(Box::new(move |info| {
78-
if let Some(dir) = dirs::data_dir() {
79-
let logs = dir.join(APP_NAME).join("logs");
80-
let _ = std::fs::create_dir_all(&logs);
81-
if let Ok(mut f) = std::fs::OpenOptions::new()
82-
.create(true)
83-
.append(true)
84-
.open(logs.join("panic.log"))
85-
{
86-
use std::io::Write;
87-
let bt = std::backtrace::Backtrace::force_capture();
88-
let _ = writeln!(
89-
f,
90-
"=== panic @ {} (v{}) ===\n{info}\n--- backtrace ---\n{bt}\n",
91-
chrono::Local::now().to_rfc3339(),
92-
env!("APP_VERSION"),
93-
);
94-
}
95-
}
96-
default_hook(info);
97-
}));
98-
}
99-
100-
fn main() -> eframe::Result<()> {
101-
install_panic_logger();
102-
103-
let native_options = eframe::NativeOptions {
104-
viewport: egui::ViewportBuilder::default()
105-
.with_inner_size([1200.0, 800.0])
106-
.with_min_inner_size([900.0, 560.0])
107-
.with_title(APP_WINDOW_TITLE)
108-
.with_icon(Arc::new(load_icon())),
109-
..Default::default()
110-
};
111-
112-
eframe::run_native(
113-
APP_NAME,
114-
native_options,
115-
Box::new(|cc| Ok(Box::new(app::TinyBoothApp::new(cc)))),
116-
)
117-
}
1+
#![windows_subsystem = "windows"]
2+
3+
mod album;
4+
mod analysis;
5+
mod app;
6+
mod audio;
7+
mod audiodecode;
8+
mod automation;
9+
mod chorddb;
10+
mod chordgrid;
11+
mod chordvideo;
12+
mod chordvoice;
13+
mod cleanup;
14+
mod coherence;
15+
mod config;
16+
mod crossfade;
17+
mod crossfade_player;
18+
mod dsp;
19+
mod export;
20+
mod fretboard;
21+
mod generator;
22+
mod git_update;
23+
mod keymap;
24+
mod lufs;
25+
mod manual;
26+
mod player;
27+
mod project;
28+
mod samplelib;
29+
mod suno_import;
30+
mod suno_meta;
31+
mod tba;
32+
mod tba_album;
33+
mod telemetry;
34+
mod tib;
35+
mod tib_project;
36+
mod tracker;
37+
mod tracker_demos;
38+
mod trim;
39+
mod ui;
40+
mod wav_meta;
41+
42+
use eframe::egui;
43+
use std::sync::Arc;
44+
45+
pub const APP_NAME: &str = "TinyBooth Sound Studio";
46+
pub const APP_WINDOW_TITLE: &str = "TinyBooth Sound Studio";
47+
pub const APP_GH_REPO: &str = "ophiocus/TinyBoothSoundStudio";
48+
49+
/// PNG bitmap embedded at compile time for the window's top-left icon.
50+
/// 256×256 is plenty — Windows' DWM scales it down for titlebar/taskbar.
51+
const VIEWPORT_ICON_PNG: &[u8] = include_bytes!("../assets/icon_viewport.png");
52+
53+
/// Decode the embedded icon into raw RGBA for egui. Falls back silently
54+
/// if decoding ever fails — we'd rather ship without a custom icon than
55+
/// refuse to start.
56+
fn load_icon() -> egui::IconData {
57+
match image::load_from_memory(VIEWPORT_ICON_PNG) {
58+
Ok(img) => {
59+
let rgba = img.to_rgba8();
60+
let (width, height) = rgba.dimensions();
61+
egui::IconData {
62+
rgba: rgba.into_raw(),
63+
width,
64+
height,
65+
}
66+
}
67+
Err(_) => egui::IconData::default(),
68+
}
69+
}
70+
71+
/// Install a panic hook that appends the panic message + backtrace to
72+
/// `%APPDATA%\TinyBooth Sound Studio\logs\panic.log`. Without this, a
73+
/// panic in a GUI-subsystem build has nowhere to go — there's no
74+
/// console, so the window just vanishes with no trace (see the v0.4.39
75+
/// "session vanished with no log" investigation). Keeps the default
76+
/// hook chained so behaviour under a debugger/console is unchanged.
77+
fn install_panic_logger() {
78+
let default_hook = std::panic::take_hook();
79+
std::panic::set_hook(Box::new(move |info| {
80+
if let Some(dir) = dirs::data_dir() {
81+
let logs = dir.join(APP_NAME).join("logs");
82+
let _ = std::fs::create_dir_all(&logs);
83+
if let Ok(mut f) = std::fs::OpenOptions::new()
84+
.create(true)
85+
.append(true)
86+
.open(logs.join("panic.log"))
87+
{
88+
use std::io::Write;
89+
let bt = std::backtrace::Backtrace::force_capture();
90+
let _ = writeln!(
91+
f,
92+
"=== panic @ {} (v{}) ===\n{info}\n--- backtrace ---\n{bt}\n",
93+
chrono::Local::now().to_rfc3339(),
94+
env!("APP_VERSION"),
95+
);
96+
}
97+
}
98+
default_hook(info);
99+
}));
100+
}
101+
102+
fn main() -> eframe::Result<()> {
103+
install_panic_logger();
104+
105+
let native_options = eframe::NativeOptions {
106+
viewport: egui::ViewportBuilder::default()
107+
.with_inner_size([1200.0, 800.0])
108+
.with_min_inner_size([900.0, 560.0])
109+
.with_title(APP_WINDOW_TITLE)
110+
.with_icon(Arc::new(load_icon())),
111+
..Default::default()
112+
};
113+
114+
eframe::run_native(
115+
APP_NAME,
116+
native_options,
117+
Box::new(|cc| Ok(Box::new(app::TinyBoothApp::new(cc)))),
118+
)
119+
}

0 commit comments

Comments
 (0)