max / audiofiles
- Co-Authored-By
- Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 files changed,
+79 insertions,
-41 deletions
| @@ -510,7 +510,8 @@ | |||
| 510 | 510 | self.detail.selected_tags = | |
| 511 | 511 | std::sync::Arc::new(self.backend.get_sample_tags(&hash).unwrap_or_default()); | |
| 512 | 512 | if let Ok(prov) = self.backend.sample_tag_provenance(&hash) { | |
| 513 | - | self.detail.selected_tag_sources = prov.into_iter().map(|(t, s, r)| (t, (s, r))).collect(); | |
| 513 | + | self.detail.selected_tag_sources = | |
| 514 | + | prov.into_iter().map(|(t, s, r)| (t, (s, r))).collect(); | |
| 514 | 515 | } | |
| 515 | 516 | } | |
| 516 | 517 | } |
| @@ -473,7 +473,8 @@ | |||
| 473 | 473 | /// stops appearing on every future sample of that class. | |
| 474 | 474 | pub fn dismiss_suggestion(&mut self, classification: &str, tag: &str) { | |
| 475 | 475 | let entry = self | |
| 476 | - | .detail.dismissed_suggestions | |
| 476 | + | .detail | |
| 477 | + | .dismissed_suggestions | |
| 477 | 478 | .entry(classification.to_string()) | |
| 478 | 479 | .or_default(); | |
| 479 | 480 | if !entry.iter().any(|t| t == tag) { | |
| @@ -514,7 +515,8 @@ | |||
| 514 | 515 | /// during exploration can start fresh. | |
| 515 | 516 | pub fn reset_dismissed_suggestions(&mut self) { | |
| 516 | 517 | let n: usize = self | |
| 517 | - | .detail.dismissed_suggestions | |
| 518 | + | .detail | |
| 519 | + | .dismissed_suggestions | |
| 518 | 520 | .values() | |
| 519 | 521 | .map(std::vec::Vec::len) | |
| 520 | 522 | .sum(); |
| @@ -3137,7 +3137,10 @@ | |||
| 3137 | 3137 | ||
| 3138 | 3138 | state.trigger_preview(&hash); | |
| 3139 | 3139 | // Synchronous state set immediately on dispatch. | |
| 3140 | - | assert_eq!(state.preview.previewing_hash.as_deref(), Some(hash.as_str())); | |
| 3140 | + | assert_eq!( | |
| 3141 | + | state.preview.previewing_hash.as_deref(), | |
| 3142 | + | Some(hash.as_str()) | |
| 3143 | + | ); | |
| 3141 | 3144 | assert!( | |
| 3142 | 3145 | state.status.starts_with("Playing:"), | |
| 3143 | 3146 | "status was: {}", | |
| @@ -3187,7 +3190,10 @@ | |||
| 3187 | 3190 | ||
| 3188 | 3191 | // First toggle: not playing -> starts preview. | |
| 3189 | 3192 | state.toggle_preview(); | |
| 3190 | - | assert_eq!(state.preview.previewing_hash.as_deref(), Some(hash.as_str())); | |
| 3193 | + | assert_eq!( | |
| 3194 | + | state.preview.previewing_hash.as_deref(), | |
| 3195 | + | Some(hash.as_str()) | |
| 3196 | + | ); | |
| 3191 | 3197 | await_playing(&state); | |
| 3192 | 3198 | ||
| 3193 | 3199 | // Second toggle: playing -> stops. | |
| @@ -3204,7 +3210,10 @@ | |||
| 3204 | 3210 | state.refresh_contents(); | |
| 3205 | 3211 | ||
| 3206 | 3212 | state.trigger_preview("deadbeef"); | |
| 3207 | - | assert!(state.preview.previewing_hash.is_none(), "no preview should start"); | |
| 3213 | + | assert!( | |
| 3214 | + | state.preview.previewing_hash.is_none(), | |
| 3215 | + | "no preview should start" | |
| 3216 | + | ); | |
| 3208 | 3217 | assert!( | |
| 3209 | 3218 | !state.status.is_empty(), | |
| 3210 | 3219 | "an error status should be surfaced" |
| @@ -23,21 +23,25 @@ | |||
| 23 | 23 | if let Some(ref waveform_data) = state.detail.selected_waveform { | |
| 24 | 24 | // Compute playback position as a 0.0–1.0 fraction for the waveform cursor. | |
| 25 | 25 | // Only valid when the currently-playing hash matches this node's hash. | |
| 26 | - | let playback_pos = if state.preview.previewing_hash.as_deref() == node.node.sample_hash.as_deref() { | |
| 27 | - | let playback = state.shared.preview.lock(); | |
| 28 | - | if playback.playing { | |
| 29 | - | if let Some(ref buf) = playback.buffer { | |
| 30 | - | // During streaming, the buffer grows so use the metadata estimate | |
| 31 | - | // for a stable cursor. Fall back to current buffer size otherwise. | |
| 32 | - | let total_frames = if playback.streaming { | |
| 33 | - | playback | |
| 34 | - | .total_frames_estimate | |
| 35 | - | .unwrap_or(playback.decoded_frames) | |
| 36 | - | } else { | |
| 37 | - | buf.data.len() / 2 | |
| 38 | - | }; | |
| 39 | - | if total_frames > 0 { | |
| 40 | - | Some((playback.position_frac / total_frames as f64) as f32) | |
| 26 | + | let playback_pos = | |
| 27 | + | if state.preview.previewing_hash.as_deref() == node.node.sample_hash.as_deref() { | |
| 28 | + | let playback = state.shared.preview.lock(); | |
| 29 | + | if playback.playing { | |
| 30 | + | if let Some(ref buf) = playback.buffer { | |
| 31 | + | // During streaming, the buffer grows so use the metadata estimate | |
| 32 | + | // for a stable cursor. Fall back to current buffer size otherwise. | |
| 33 | + | let total_frames = if playback.streaming { | |
| 34 | + | playback | |
| 35 | + | .total_frames_estimate | |
| 36 | + | .unwrap_or(playback.decoded_frames) | |
| 37 | + | } else { | |
| 38 | + | buf.data.len() / 2 | |
| 39 | + | }; | |
| 40 | + | if total_frames > 0 { | |
| 41 | + | Some((playback.position_frac / total_frames as f64) as f32) | |
| 42 | + | } else { | |
| 43 | + | None | |
| 44 | + | } | |
| 41 | 45 | } else { | |
| 42 | 46 | None | |
| 43 | 47 | } | |
| @@ -46,10 +50,7 @@ | |||
| 46 | 50 | } | |
| 47 | 51 | } else { | |
| 48 | 52 | None | |
| 49 | - | } | |
| 50 | - | } else { | |
| 51 | - | None | |
| 52 | - | }; | |
| 53 | + | }; | |
| 53 | 54 | ||
| 54 | 55 | let resp = waveform::draw_waveform(ui, waveform_data, playback_pos, 120.0); | |
| 55 | 56 | // Hover indicator: paint a vertical action line at the cursor X | |
| @@ -320,7 +321,8 @@ | |||
| 320 | 321 | { | |
| 321 | 322 | let class_str = class.to_string(); | |
| 322 | 323 | let dismissed_for_class = state | |
| 323 | - | .detail.dismissed_suggestions | |
| 324 | + | .detail | |
| 325 | + | .dismissed_suggestions | |
| 324 | 326 | .get(&class_str) | |
| 325 | 327 | .cloned() | |
| 326 | 328 | .unwrap_or_default(); | |
| @@ -389,7 +391,8 @@ | |||
| 389 | 391 | const UNDO_WINDOW: f32 = 5.0; | |
| 390 | 392 | // Bind the tag directly so there's no re-fetch + expect() in render. | |
| 391 | 393 | let undo_tag = state | |
| 392 | - | .detail.last_dismissed_suggestion | |
| 394 | + | .detail | |
| 395 | + | .last_dismissed_suggestion | |
| 393 | 396 | .as_ref() | |
| 394 | 397 | .filter(|(c, _, at)| { | |
| 395 | 398 | c == &class_str && at.elapsed().as_secs_f32() < UNDO_WINDOW | |
| @@ -460,11 +463,13 @@ | |||
| 460 | 463 | // these the button "works" but always returns zero results, | |
| 461 | 464 | // which reads as a broken feature instead of a missing prereq. | |
| 462 | 465 | let has_spectral = state | |
| 463 | - | .detail.selected_analysis | |
| 466 | + | .detail | |
| 467 | + | .selected_analysis | |
| 464 | 468 | .as_ref() | |
| 465 | 469 | .is_some_and(|a| a.spectral_centroid.is_some() || a.spectral_bandwidth.is_some()); | |
| 466 | 470 | let has_fingerprint = state | |
| 467 | - | .detail.selected_analysis | |
| 471 | + | .detail | |
| 472 | + | .selected_analysis | |
| 468 | 473 | .as_ref() | |
| 469 | 474 | .is_some_and(|a| a.fingerprint.is_some()); | |
| 470 | 475 | egui::CollapsingHeader::new("Discovery") |
| @@ -260,7 +260,8 @@ | |||
| 260 | 260 | ui.label(egui::RichText::new("Trim").strong()); | |
| 261 | 261 | ||
| 262 | 262 | let sample_rate = state | |
| 263 | - | .detail.selected_analysis | |
| 263 | + | .detail | |
| 264 | + | .selected_analysis | |
| 264 | 265 | .as_ref() | |
| 265 | 266 | .map_or(44100, |a| a.sample_rate); | |
| 266 | 267 | let total = state.edit.total_frames; | |
| @@ -307,7 +308,11 @@ | |||
| 307 | 308 | ui.label(egui::RichText::new("Levels").strong()); | |
| 308 | 309 | ||
| 309 | 310 | // Current peak display + gain clipping warning | |
| 310 | - | let current_peak = state.detail.selected_analysis.as_ref().and_then(|a| a.peak_db); | |
| 311 | + | let current_peak = state | |
| 312 | + | .detail | |
| 313 | + | .selected_analysis | |
| 314 | + | .as_ref() | |
| 315 | + | .and_then(|a| a.peak_db); | |
| 311 | 316 | if let Some(peak) = current_peak { | |
| 312 | 317 | let predicted = peak + state.edit.gain_db; | |
| 313 | 318 | if predicted > 0.0 { | |
| @@ -464,7 +469,8 @@ | |||
| 464 | 469 | // range only if duration is missing (un-analyzed sample). Prevents the | |
| 465 | 470 | // silent-failure / undefined-behaviour case where positions exceed length. | |
| 466 | 471 | let duration_ms_cap = state | |
| 467 | - | .detail.selected_analysis | |
| 472 | + | .detail | |
| 473 | + | .selected_analysis | |
| 468 | 474 | .as_ref() | |
| 469 | 475 | .map_or(f64::MAX, |a| a.duration * 1000.0); | |
| 470 | 476 |
| @@ -190,8 +190,11 @@ | |||
| 190 | 190 | ui.separator(); | |
| 191 | 191 | ||
| 192 | 192 | ui.label( | |
| 193 | - | egui::RichText::new(format!("Root: {}", note_name(state.preview.instrument_root_note))) | |
| 194 | - | .color(theme::content_secondary()), | |
| 193 | + | egui::RichText::new(format!( | |
| 194 | + | "Root: {}", | |
| 195 | + | note_name(state.preview.instrument_root_note) | |
| 196 | + | )) | |
| 197 | + | .color(theme::content_secondary()), | |
| 195 | 198 | ); | |
| 196 | 199 | ||
| 197 | 200 | ui.separator(); | |
| @@ -407,7 +410,8 @@ | |||
| 407 | 410 | } | |
| 408 | 411 | // Release notes that are no longer under the pointer (drag across keys) | |
| 409 | 412 | let to_release: Vec<u8> = state | |
| 410 | - | .preview.piano_held_notes | |
| 413 | + | .preview | |
| 414 | + | .piano_held_notes | |
| 411 | 415 | .iter() | |
| 412 | 416 | .filter(|&&n| n != note) | |
| 413 | 417 | .copied() | |
| @@ -449,7 +453,8 @@ | |||
| 449 | 453 | egui::vec2(key_width - 1.0, white_height), | |
| 450 | 454 | ); | |
| 451 | 455 | ||
| 452 | - | let is_active = active_notes.contains(¬e) || state.preview.piano_held_notes.contains(¬e); | |
| 456 | + | let is_active = | |
| 457 | + | active_notes.contains(¬e) || state.preview.piano_held_notes.contains(¬e); | |
| 453 | 458 | let is_root = note == state.preview.instrument_root_note; | |
| 454 | 459 | ||
| 455 | 460 | let fill = if is_active { | |
| @@ -476,8 +481,8 @@ | |||
| 476 | 481 | ||
| 477 | 482 | // Draw black keys on top | |
| 478 | 483 | for &(key_rect, black_note) in &black_key_rects { | |
| 479 | - | let is_active = | |
| 480 | - | active_notes.contains(&black_note) || state.preview.piano_held_notes.contains(&black_note); | |
| 484 | + | let is_active = active_notes.contains(&black_note) | |
| 485 | + | || state.preview.piano_held_notes.contains(&black_note); | |
| 481 | 486 | let is_root = black_note == state.preview.instrument_root_note; | |
| 482 | 487 | ||
| 483 | 488 | let fill = if is_active { |
| @@ -700,7 +700,8 @@ | |||
| 700 | 700 | .color(theme::content_secondary()), | |
| 701 | 701 | ); | |
| 702 | 702 | let dismissed_total: usize = state | |
| 703 | - | .detail.dismissed_suggestions | |
| 703 | + | .detail | |
| 704 | + | .dismissed_suggestions | |
| 704 | 705 | .values() | |
| 705 | 706 | .map(std::vec::Vec::len) | |
| 706 | 707 | .sum(); |
| @@ -229,7 +229,13 @@ | |||
| 229 | 229 | state.preview.show_midi_window = !state.preview.show_midi_window; | |
| 230 | 230 | } | |
| 231 | 231 | ||
| 232 | - | if widgets::toolbar_toggle(ui, "Loop", state.preview.loop_enabled, "Toggle loop (L)", None) { | |
| 232 | + | if widgets::toolbar_toggle( | |
| 233 | + | ui, | |
| 234 | + | "Loop", | |
| 235 | + | state.preview.loop_enabled, | |
| 236 | + | "Toggle loop (L)", | |
| 237 | + | None, | |
| 238 | + | ) { | |
| 233 | 239 | state.toggle_loop(); | |
| 234 | 240 | } | |
| 235 | 241 | ||
| @@ -305,7 +311,10 @@ | |||
| 305 | 311 | ui.close(); | |
| 306 | 312 | } | |
| 307 | 313 | if ui | |
| 308 | - | .button(format!("{}Loop (L)", active_dot(state.preview.loop_enabled))) | |
| 314 | + | .button(format!( | |
| 315 | + | "{}Loop (L)", | |
| 316 | + | active_dot(state.preview.loop_enabled) | |
| 317 | + | )) | |
| 309 | 318 | .clicked() | |
| 310 | 319 | { | |
| 311 | 320 | state.toggle_loop(); |