SUPERB
Examples

transport_tempo_map

examples/primitives/transport_tempo_map.rs, the whole program

Run it with cargo run --example transport_tempo_map (see the examples for the features some need).

//! 120 bpm in 4/4, 90 bpm from beat 8, 3/4 from bar 4. A 256-frame block
//! that starts 100 frames before the loop end splits into 100 frames and
//! 156 frames wrapped back to beat 4.
//!
//! ```text
//! cargo run --example transport_tempo_map
//! ```

use superb::primitives::transport::{LoopRange, TempoMap, Transport};
use superb::primitives::{BeatPosition, Result, SampleRate, TimePosition, TimeSignature};

fn main() -> Result<()> {
    let rate = SampleRate::HZ_48K;
    assert_eq!(rate.seconds_to_samples(1.0), 48_000);
    // 120 bpm until beat 8, then 90. 4/4 until bar 4 (beat 12), then 3/4.
    let mut map = TempoMap::new(rate, 120.0);
    map.add_tempo(BeatPosition::new(8.0), 90.0)?;
    map.add_meter(4, TimeSignature::new(3, 4)?)?;

    // Beat 7.5 is bar 2, beat 4, tick 480. Beat 12 is the first 3/4 bar.
    // Beat 15 is the next bar. 4 beats at 90 bpm from second 4.000 is 6.667 s.
    println!("beat   bar.beat.tick   seconds   samples   bpm");
    let rows = [
        (0.0, "1.1.000", "0.000", 0, "120"),
        (4.0, "2.1.000", "2.000", 96_000, "120"),
        (7.5, "2.4.480", "3.750", 180_000, "120"),
        (8.0, "3.1.000", "4.000", 192_000, "90"),
        (12.0, "4.1.000", "6.667", 320_000, "90"),
        (15.0, "5.1.000", "8.667", 416_000, "90"),
    ];
    for (beat, bbt, seconds, samples, bpm) in rows {
        let b = BeatPosition::new(beat);
        assert_eq!(map.beat_to_bbt(b).to_string(), bbt);
        assert_eq!(format!("{:.3}", map.beat_to_seconds(b)), seconds);
        assert_eq!(map.beat_to_samples(b).samples(), samples);
        assert_eq!(format!("{:.0}", map.tempo_at(b)), bpm);
        println!(
            "{:>4}   {:<14}  {:>7.3}   {:>7}   {:>3}",
            beat,
            map.beat_to_bbt(b).to_string(),
            map.beat_to_seconds(b),
            map.beat_to_samples(b).samples(),
            map.tempo_at(b)
        );
    }
    // Two seconds is still inside the 120 bpm region: beat 4.000.
    let two_seconds = TimePosition::from_seconds(2.0, rate);
    let beat = map.samples_to_beat(two_seconds).beats();
    assert_eq!(format!("{beat:.3}"), "4.000");
    println!("2.000 s = beat {beat:.3}");

    // Loop bar 2, samples 96000..192000. The playhead sits 100 frames
    // before the end, at 191900.
    let mut transport = Transport::with_tempo_map(map);
    let range = LoopRange::new(
        transport
            .tempo_map()
            .beat_to_samples(BeatPosition::new(4.0)),
        transport
            .tempo_map()
            .beat_to_samples(BeatPosition::new(8.0)),
    )?;
    assert_eq!(range.start().samples(), 96_000);
    assert_eq!(range.end().samples(), 192_000);
    transport.set_loop(Some(range));
    transport.set_loop_enabled(true);
    transport.seek(range.end().offset(-100));
    assert_eq!(transport.position().samples(), 191_900);
    transport.play();

    // 100 frames finish the loop, then 156 frames start at sample 96000.
    // The playhead is sample 96156, beat 4.006.
    println!("\nblock of 256 frames across the loop point:");
    let segs: Vec<_> = transport.advance(256).collect();
    assert_eq!(segs.len(), 2);
    assert_eq!(segs[0].block_offset, 0);
    assert_eq!(segs[0].frames, 100);
    assert_eq!(segs[0].start.samples(), 191_900);
    assert!(!segs[0].wrapped);
    assert_eq!(segs[1].block_offset, 100);
    assert_eq!(segs[1].frames, 156);
    assert_eq!(segs[1].start.samples(), 96_000);
    assert!(segs[1].wrapped);
    assert_eq!(segs[0].frames + segs[1].frames, 256);
    assert_eq!(transport.position().samples(), 96_156);
    assert_eq!(format!("{:.3}", transport.beat_position().beats()), "4.006");
    for seg in &segs {
        println!(
            "  offset {:>3}, {:>3} frames, from sample {}{}",
            seg.block_offset,
            seg.frames,
            seg.start.samples(),
            if seg.wrapped {
                " (wrapped to loop start)"
            } else {
                ""
            }
        );
    }
    println!(
        "playhead now at beat {:.3}",
        transport.beat_position().beats()
    );

    // Stopped, the same 256-frame call is one segment and the playhead
    // stays at sample 96156.
    let at = transport.position().samples();
    transport.stop();
    let segs: Vec<_> = transport.advance(256).collect();
    assert_eq!(segs.len(), 1);
    assert_eq!(segs[0].frames, 256);
    assert_eq!(transport.position().samples(), at);
    println!(
        "stopped: {} segment(s) of {} frames, position unchanged",
        segs.len(),
        segs[0].frames
    );
    Ok(())
}