Skip to main content

max / audiofiles

Clear audiofiles-bench clippy backlog and soften a dead match arm bulk_ops.rs: DeleteMultiple is intercepted by the early return at the top of execute_confirmed_action, so the arm is structurally unreachable. Make it a no-op rather than unreachable!(), so reordering that guard degrades to doing nothing instead of panicking on the UI thread. audiofiles-bench: 29 clippy warnings to 0. 25 via --fix (uninlined format args, redundant closures, map_or), plus two let...else conversions, a digit separator, and a scoped allow for struct_field_names on StageTiming, where the _ms suffix is the unit rather than redundant naming. The reported "duplicated classification loop" is not duplication: one loop collects results, the other prints the per-class breakdown. They share only the expected_class_from_dir guard, so the structure is left alone.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-27 21:01 UTC
Signed with PGP, not checked
Commit: 049c7743bb73b4056d4c225f0be1699e1ed96c0c
Parent: 114de48
2 files changed, +38 insertions, -54 deletions
@@ -18,6 +18,9 @@
18 18
19 19 // Timing Helpers
20 20
21 + // The `_ms` suffix is the unit, not redundant naming. Dropping it would leave
22 + // bare `decode`/`loudness` fields whose scale a reader has to guess.
23 + #[allow(clippy::struct_field_names)]
21 24 struct StageTiming {
22 25 decode_ms: f64,
23 26 loudness_ms: f64,
@@ -211,7 +214,7 @@
211 214 if values.is_empty() {
212 215 return 0.0;
213 216 }
214 - values.sort_by(|a, b| a.total_cmp(b));
217 + values.sort_by(f64::total_cmp);
215 218 let idx = (p / 100.0 * (values.len() - 1) as f64).round() as usize;
216 219 values[idx.min(values.len() - 1)]
217 220 }
@@ -301,7 +304,7 @@
301 304 }
302 305
303 306 let n = all_timings.len();
304 - println!("Benchmarked {} files", n);
307 + println!("Benchmarked {n} files");
305 308 println!();
306 309
307 310 // Aggregate per-stage
@@ -335,10 +338,7 @@
335 338 let p95 = percentile(vals, 95.0);
336 339 let p99 = percentile(vals, 99.0);
337 340 let max = percentile(vals, 100.0);
338 - println!(
339 - " {:<16} {:>8.2} {:>8.2} {:>8.2} {:>8.2} {:>8.2}",
340 - name, mean, p50, p95, p99, max
341 - );
341 + println!(" {name:<16} {mean:>8.2} {p50:>8.2} {p95:>8.2} {p99:>8.2} {max:>8.2}");
342 342 }
343 343
344 344 println!(
@@ -362,11 +362,10 @@
362 362 let avg_dur = durations.iter().sum::<f64>() / durations.len() as f64;
363 363 let avg_total = total.iter().sum::<f64>() / total.len() as f64;
364 364 let realtime_ratio = avg_dur * 1000.0 / avg_total;
365 - println!(" Avg sample duration: {:.2}s", avg_dur);
366 - println!(" Avg analysis time: {:.1}ms", avg_total);
365 + println!(" Avg sample duration: {avg_dur:.2}s");
366 + println!(" Avg analysis time: {avg_total:.1}ms");
367 367 println!(
368 - " Real-time ratio: {:.0}× (analysis is {:.0}× faster than real-time)",
369 - realtime_ratio, realtime_ratio
368 + " Real-time ratio: {realtime_ratio:.0}× (analysis is {realtime_ratio:.0}× faster than real-time)"
370 369 );
371 370 println!();
372 371
@@ -405,10 +404,7 @@
405 404 let mean = decode_times.iter().sum::<f64>() / count as f64;
406 405 let p95 = percentile(&mut decode_times, 95.0);
407 406 let max = percentile(&mut decode_times, 100.0);
408 - println!(
409 - " {:<8} {:>6} {:>10.2} {:>10.2} {:>10.2}",
410 - fmt, count, mean, p95, max
411 - );
407 + println!(" {fmt:<8} {count:>6} {mean:>10.2} {p95:>10.2} {max:>10.2}");
412 408 }
413 409 println!();
414 410
@@ -444,9 +440,9 @@
444 440 let tp_rate = tp_ok as f64 / tp_elapsed;
445 441
446 442 println!(" Full pipeline (all stages, parallel):");
447 - println!(" Files: {} ({} succeeded)", tp_count, tp_ok);
448 - println!(" Wall time: {:.1}s", tp_elapsed);
449 - println!(" Throughput: {:.1} files/sec", tp_rate);
443 + println!(" Files: {tp_count} ({tp_ok} succeeded)");
444 + println!(" Wall time: {tp_elapsed:.1}s");
445 + println!(" Throughput: {tp_rate:.1} files/sec");
450 446 println!(
451 447 " Avg/file: {:.1}ms",
452 448 tp_elapsed * 1000.0 / tp_ok as f64
@@ -464,7 +460,7 @@
464 460 let st_rate = st_ok as f64 / st_elapsed;
465 461
466 462 println!(" Single-threaded comparison (100 files):");
467 - println!(" Throughput: {:.1} files/sec", st_rate);
463 + println!(" Throughput: {st_rate:.1} files/sec");
468 464 println!(" Speedup from parallelism: {:.1}×", tp_rate / st_rate);
469 465 println!();
470 466
@@ -512,7 +508,7 @@
512 508 let thirty_sec_frames = (30.0 * 44100.0 / hop as f64) as usize;
513 509 let frame_mem = thirty_sec_frames * (frame_samples / 2 + 1) * 8; // f64 magnitude bins
514 510 println!(" STFT magnitude frames (30s @ 44.1kHz, 1024-sample window):");
515 - println!(" Frames: {}", thirty_sec_frames);
511 + println!(" Frames: {thirty_sec_frames}");
516 512 println!(" Memory: {:.1} MB", frame_mem as f64 / 1_048_576.0);
517 513 println!();
518 514
@@ -550,9 +546,8 @@
550 546 continue;
551 547 }
552 548
553 - let expected = match expected_class_from_dir(dir_name) {
554 - Some(c) => c,
555 - None => continue,
549 + let Some(expected) = expected_class_from_dir(dir_name) else {
550 + continue;
556 551 };
557 552
558 553 let results: Vec<ClassifyResult> = files
@@ -571,10 +566,7 @@
571 566 }
572 567
573 568 let total_classified = all_results.len();
574 - println!(
575 - " Evaluated {} samples (up to {} per class)",
576 - total_classified, max_per_class
577 - );
569 + println!(" Evaluated {total_classified} samples (up to {max_per_class} per class)");
578 570 println!();
579 571
580 572 // Strict accuracy: predicted class == expected class exactly
@@ -593,12 +585,10 @@
593 585
594 586 println!(" Overall:");
595 587 println!(
596 - " Strict accuracy (exact class match): {:.1}% ({}/{})",
597 - strict_acc, strict_correct, total_classified
588 + " Strict accuracy (exact class match): {strict_acc:.1}% ({strict_correct}/{total_classified})"
598 589 );
599 590 println!(
600 - " Layer 1 accuracy (drum detection): {:.1}% ({}/{})",
601 - drum_acc, drum_correct, total_classified
591 + " Layer 1 accuracy (drum detection): {drum_acc:.1}% ({drum_correct}/{total_classified})"
602 592 );
603 593 println!();
604 594
@@ -611,9 +601,8 @@
611 601 println!(" {}", "─".repeat(52));
612 602
613 603 for dir_name in &class_dirs {
614 - let expected = match expected_class_from_dir(dir_name) {
615 - Some(c) => c,
616 - None => continue,
604 + let Some(expected) = expected_class_from_dir(dir_name) else {
605 + continue;
617 606 };
618 607 let class_results: Vec<&ClassifyResult> = all_results
619 608 .iter()
@@ -629,10 +618,7 @@
629 618 .count();
630 619 let acc = correct as f64 / n as f64 * 100.0;
631 620 let avg_conf = class_results.iter().map(|r| r.confidence).sum::<f64>() / n as f64;
632 - println!(
633 - " {:<12} {:>6} {:>8} {:>9.1}% {:>9.2}",
634 - dir_name, n, correct, acc, avg_conf
635 - );
621 + println!(" {dir_name:<12} {n:>6} {correct:>8} {acc:>9.1}% {avg_conf:>9.2}");
636 622 }
637 623 println!();
638 624
@@ -651,7 +637,7 @@
651 637 println!(" Confusion matrix (rows=expected, cols=predicted):");
652 638 print!(" {:>12}", "");
653 639 for name in &class_names {
654 - print!(" {:>7}", name);
640 + print!(" {name:>7}");
655 641 }
656 642 println!(" {:>7}", "other");
657 643 println!(" {}", "─".repeat(60));
@@ -670,13 +656,13 @@
670 656 .iter()
671 657 .filter(|r| r.predicted == *pred)
672 658 .count();
673 - print!(" {:>7}", count);
659 + print!(" {count:>7}");
674 660 }
675 661 let other = class_results
676 662 .iter()
677 663 .filter(|r| !is_drum_class(r.predicted))
678 664 .count();
679 - println!(" {:>7}", other);
665 + println!(" {other:>7}");
680 666 }
681 667 println!();
682 668
@@ -698,7 +684,7 @@
698 684 let mut sorted: Vec<_> = non_drum_classes.into_iter().collect();
699 685 sorted.sort_by_key(|a| std::cmp::Reverse(a.1));
700 686 for (class, count) in &sorted {
701 - println!(" {} → {}", count, class);
687 + println!(" {count} → {class}");
702 688 }
703 689 }
704 690 println!();
@@ -743,12 +729,12 @@
743 729 " {} → OK (dur={:.2}s, class={}, conf={:.2})",
744 730 name,
745 731 r.duration,
746 - r.classification.map(|c| c.as_str()).unwrap_or("none"),
732 + r.classification.map_or("none", |c| c.as_str()),
747 733 r.classification_confidence.unwrap_or(0.0)
748 734 );
749 735 }
750 736 Err(e) => {
751 - println!(" {} → ERROR: {}", name, e);
737 + println!(" {name} → ERROR: {e}");
752 738 }
753 739 }
754 740 }
@@ -792,7 +778,7 @@
792 778 /// bench needs no RNG dependency and is reproducible across runs.
793 779 fn synth_features(count: usize) -> Vec<(String, audiofiles_core::similarity::FeatureVector)> {
794 780 use audiofiles_core::similarity::FeatureVector;
795 - let mut state: u64 = 0x2545F4914F6CDD1D;
781 + let mut state: u64 = 0x2545_F491_4F6C_DD1D;
796 782 let mut next = || {
797 783 // xorshift64 → unit f64
798 784 state ^= state << 13;
@@ -856,15 +842,12 @@
856 842 audiofiles_core::similarity::feature_distance(probe_fv, fv, &weights)
857 843 })
858 844 .collect();
859 - best.sort_by(|a, b| a.total_cmp(b));
845 + best.sort_by(f64::total_cmp);
860 846 let _ = &best[..best.len().min(20)];
861 847 }
862 848 let linear_us = t.elapsed().as_secs_f64() * 1e6 / runs as f64;
863 849
864 - println!(
865 - " {:>9} {:>12.1} {:>14.1} {:>14.1}",
866 - size, build_ms, indexed_us, linear_us
867 - );
850 + println!(" {size:>9} {build_ms:>12.1} {indexed_us:>14.1} {linear_us:>14.1}");
868 851 }
869 852 }
870 853
@@ -950,10 +933,7 @@
950 933 let _index = SimilarityIndex::build_from_data(data);
951 934 let load_build_ms = t.elapsed().as_secs_f64() * 1000.0;
952 935
953 - println!(
954 - " {:>9} {:>14.1} {:>16.1} {:>14.1}",
955 - size, batched_ms, per_row_ms, load_build_ms
956 - );
936 + println!(" {size:>9} {batched_ms:>14.1} {per_row_ms:>16.1} {load_build_ms:>14.1}");
957 937 }
958 938
959 939 let _ = std::fs::remove_file(&db_path);
@@ -129,7 +129,11 @@
129 129 // edit panel; only the gated case routes through here.
130 130 self.batch_reverse();
131 131 }
132 - Some(ConfirmAction::DeleteMultiple { .. }) => unreachable!(),
132 + // Intercepted by the early return at the top of this function, which
133 + // routes to execute_bulk_delete. A no-op rather than unreachable!()
134 + // so reordering that guard degrades to doing nothing, not a panic in
135 + // the UI thread.
136 + Some(ConfirmAction::DeleteMultiple { .. }) => {}
133 137 None => {}
134 138 }
135 139 }