| 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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);
|