#[cfg(all(
feature = "plotting",
feature = "onset-detection",
feature = "beat-tracking",
feature = "statistics"
))]
fn create_percussive_audio() -> audio_samples::AudioSamples<'static, f32> {
use std::num::NonZero;
let sample_rate = audio_samples::sample_rate!(44100);
let duration = 4.0; let n_samples = (sample_rate.get() as f64 * duration) as usize;
let n_samples = NonZero::new(n_samples).unwrap();
let beat_interval = 0.5;
let mut samples = audio_samples::AudioSamples::zeros_mono(n_samples, sample_rate);
let sample_rate_hz = samples.sample_rate_hz();
for beat in [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] {
let start_sample = (beat * beat_interval * sample_rate_hz) as usize;
for i in 0..2000 {
if start_sample + i >= n_samples.get() {
break;
}
let t = i as f64 / sample_rate_hz;
let decay = (-t * 50.0).exp();
let kick = (2.0 * std::f64::consts::PI * 60.0 * t).sin(); samples[start_sample + i] += (kick * decay * 0.8) as f32;
}
}
for beat in [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5] {
let start_sample = (beat * beat_interval * sample_rate_hz) as usize;
for i in 0..1500 {
if start_sample + i >= n_samples.get() {
break;
}
let t = i as f64 / sample_rate_hz;
let decay = (-t * 80.0).exp();
let snare = (2.0 * std::f64::consts::PI * 200.0 * t).sin()
+ (2.0 * std::f64::consts::PI * 350.0 * t).sin() * 0.5;
samples[start_sample + i] += (snare * decay * 0.6) as f32;
}
}
samples
}
#[cfg(not(all(
feature = "plotting",
feature = "onset-detection",
feature = "beat-tracking",
feature = "statistics"
)))]
fn main() {
eprintln!(
"error: This example requires the `plotting`, `onset-detection`, `beat-tracking`, and `statistics` features."
);
std::process::exit(1);
}
#[cfg(all(
feature = "plotting",
feature = "onset-detection",
feature = "beat-tracking",
feature = "statistics"
))]
fn assert_plot_html<P: audio_samples::operations::PlotUtils>(
plot: &P,
name: &str,
) -> audio_samples::AudioSampleResult<()> {
let html = plot.html()?;
assert!(
!html.is_empty() && html.contains("plotly"),
"{name} marker plot HTML should be a non-empty Plotly document"
);
println!("Rendered {name} marker plot ({} bytes)", html.len());
Ok(())
}
#[cfg(all(
feature = "plotting",
feature = "onset-detection",
feature = "beat-tracking",
feature = "statistics"
))]
fn main() -> audio_samples::AudioSampleResult<()> {
use audio_samples::operations::beat::BeatTrackingConfig;
use audio_samples::operations::onset::OnsetDetectionConfig;
use audio_samples::operations::plotting::waveform::WaveformPlotParams;
use audio_samples::operations::{AudioBeatTracking, AudioPlotting};
use audio_samples::{AudioOnsetDetection, AudioSamples};
println!("=== Event Markers Example ===\n");
let audio: &'static AudioSamples<'static, f32> = Box::leak(Box::new(create_percussive_audio()));
println!("Generated percussive audio: 4 seconds at 120 BPM");
println!("\n--- Onset Detection ---");
let onset_config = OnsetDetectionConfig::percussive();
let onsets = audio.detect_onsets(&onset_config)?;
println!("Detected {} onsets:", onsets.len());
for (idx, onset_time) in onsets.iter().enumerate() {
println!(" Onset {}: {:.3}s", idx + 1, onset_time);
}
assert!(
onsets.len() >= 4,
"expected to detect several onsets in the percussive pattern, got {}",
onsets.len()
);
let plot_onsets = audio
.plot_waveform(&WaveformPlotParams::default())?
.add_onset_markers(onsets.clone(), Some("red"), false);
assert_plot_html(&plot_onsets, "onsets")?;
let plot_onsets_labeled = audio
.plot_waveform(&WaveformPlotParams::default())?
.add_onset_markers(onsets, Some("red"), true);
assert_plot_html(&plot_onsets_labeled, "onsets_labeled")?;
println!("\n--- Beat Tracking ---");
let beat_config = BeatTrackingConfig::new(
120.0, Some(0.1), onset_config.clone(),
);
let beat_data = audio.detect_beats(&beat_config)?;
println!("Detected tempo: {:.1} BPM", beat_data.tempo_bpm);
println!("Detected {} beats:", beat_data.beat_times.len());
assert!(
beat_data.tempo_bpm > 0.0 && !beat_data.beat_times.is_empty(),
"beat tracking should report a positive tempo and at least one beat"
);
for (idx, beat_time) in beat_data.beat_times.iter().take(8).enumerate() {
println!(" Beat {}: {:.3}s", idx + 1, beat_time);
}
let plot_beats = audio
.plot_waveform(&WaveformPlotParams::default())?
.add_beat_markers(beat_data.beat_times.clone(), Some("blue"), false);
assert_plot_html(&plot_beats, "beats")?;
let plot_beats_numbered = audio
.plot_waveform(&WaveformPlotParams::default())?
.add_beat_markers(beat_data.beat_times.clone(), Some("blue"), true);
assert_plot_html(&plot_beats_numbered, "beats_numbered")?;
println!("\n--- Combined Visualization ---");
let onsets_fresh = audio.detect_onsets(&onset_config)?;
let plot_combined = audio
.plot_waveform(&WaveformPlotParams::default())?
.add_onset_markers(onsets_fresh, Some("rgba(255,0,0,0.5)"), false)
.add_beat_markers(beat_data.beat_times, Some("rgba(0,0,255,0.7)"), true);
assert_plot_html(&plot_combined, "combined")?;
println!(" Red = Onsets (note attacks)");
println!(" Blue = Beats (tempo grid)");
Ok(())
}