pocketstation 1.0.0

Source-aware desktop audio Session SDK
use assert_no_alloc::{assert_no_alloc, AllocDisabler};
use pocketstation::internal::capture::{
    capture_delivery_start_gate, captured_frame_stream, captured_frame_stream_with_start_gate,
    CapturedFrameDelivery,
};
use pocketstation::internal::frame::{
    AudioBufferPool, AudioFrame, SampleFormat, SampleSpec, SourceId, StreamId,
};

#[global_allocator]
static ALLOCATOR: AllocDisabler = AllocDisabler;

const SPEC: SampleSpec = SampleSpec {
    sample_rate_hz: 48_000,
    channels: 1,
    format: SampleFormat::F32Interleaved,
};

fn frame(pool: &std::sync::Arc<AudioBufferPool>, sequence_number: u64) -> AudioFrame {
    AudioFrame::try_new(
        StreamId::new(1),
        SourceId::new(2),
        sequence_number,
        sequence_number * 20_000_000,
        SPEC,
        pool.acquire().expect("prepared pool capacity"),
    )
    .expect("prepared frame shape")
}

#[test]
fn given_preallocated_capture_handoff_when_frames_flow_then_no_heap_allocation_occurs() {
    let pool = AudioBufferPool::new(2, 960);
    let (mut sender, mut stream) = captured_frame_stream(1).expect("prepared capture handoff");

    assert_no_alloc(|| {
        for sequence_number in 0..100 {
            assert_eq!(
                sender.try_send(frame(&pool, sequence_number)),
                CapturedFrameDelivery::Delivered
            );
            drop(stream.try_next().expect("enqueued capture frame"));
        }
    });

    assert_eq!(pool.available_slots(), 2);
}

#[test]
fn given_full_capture_handoff_when_newest_is_dropped_then_no_heap_allocation_occurs() {
    let pool = AudioBufferPool::new(2, 960);
    let (mut sender, mut stream) = captured_frame_stream(1).expect("prepared capture handoff");
    assert_eq!(
        sender.try_send(frame(&pool, 1)),
        CapturedFrameDelivery::Delivered
    );

    assert_no_alloc(|| {
        assert_eq!(
            sender.try_send(frame(&pool, 2)),
            CapturedFrameDelivery::DroppedNewest
        );
    });

    drop(stream.try_next().expect("oldest frame remains queued"));
    assert_eq!(pool.available_slots(), 2);
}

#[test]
fn given_closed_capture_start_gate_when_frame_arrives_then_discard_is_allocation_free() {
    let pool = AudioBufferPool::new(1, 960);
    let (_controller, gate) = capture_delivery_start_gate();
    let (mut sender, mut stream) =
        captured_frame_stream_with_start_gate(1, gate).expect("prepared gated handoff");

    assert_no_alloc(|| {
        assert_eq!(
            sender.try_send(frame(&pool, 1)),
            CapturedFrameDelivery::DiscardedBeforeStart
        );
    });

    assert!(stream.try_next().is_none());
    assert_eq!(pool.available_slots(), 1);
}

#[test]
fn given_exhausted_audio_pool_when_acquire_is_rejected_then_no_heap_allocation_occurs() {
    let pool = AudioBufferPool::new(1, 960);
    let held = pool.acquire().expect("first prepared slot");

    assert_no_alloc(|| {
        assert!(pool.acquire().is_none());
    });

    drop(held);
    assert_eq!(pool.available_slots(), 1);
    assert_eq!(pool.acquire_failures(), 1);
}