Files
pmomusic/pmoaudio
Claude 9e1ab7198a Make FlacFileSink cache progressive compliant
Apply same architecture as FlacCacheSink to prevent file truncation
when external readers access files during encoding.

Changes:
1. Add pump_track_segments_from_channel() for parallel pump tasks
2. Refactor process() to use dispatcher + tokio::select! pattern
3. Create .complete marker after flush/wait to signal file is ready
4. Allow multiple tracks to encode in parallel (pump continues in background)

This ensures FlacFileSink is cache progressive compliant, meaning external
code can safely read output files while they're being written without
risk of truncation.
2025-11-07 15:35:40 +00:00
..

PMOAudio

Pipeline audio stéréo async optimisé pour Rust, utilisant Tokio.

Caractéristiques

  • Pipeline push-based async : Tous les nodes utilisent Tokio pour un traitement non-bloquant
  • Zero-copy optimisé : Les données audio sont partagées via Arc<Vec<f32>> pour éviter les clonages inutiles
  • Support multiroom : BufferNode avec buffer circulaire et offsets indépendants par abonné
  • TimerNode : Calcul de position temporelle en temps réel
  • Backpressure : Channels bounded avec try_send pour éviter les blocages

Architecture

AudioChunk

Structure de données pour un chunk audio stéréo :

pub struct AudioChunk {
    pub order: u64,                  // Numéro d'ordre
    pub left: Arc<Vec<f32>>,         // Canal gauche (partagé)
    pub right: Arc<Vec<f32>>,        // Canal droit (partagé)
    pub sample_rate: u32,            // Taux d'échantillonnage
}

Les données sont wrappées dans Arc pour permettre le partage sans copie entre plusieurs abonnés.

Nodes

SingleSubscriberNode

  • Un seul abonné
  • Pas de clone inutile du Arc

MultiSubscriberNode

  • Plusieurs abonnés
  • Partage le même Arc<AudioChunk> avec tous

SourceNode

  • Génère ou lit des chunks audio
  • Version mock avec génération de sinusoïdes pour tests

DecoderNode

  • Décode les chunks audio
  • Supporte le passthrough et le resampling (mock)

DspNode

  • Applique des transformations DSP
  • Clone les données uniquement si modification nécessaire
  • Exemple : gain, filtrage

BufferNode

  • Buffer circulaire (VecDeque<Arc<AudioChunk>>)
  • Support multiroom avec offsets indépendants
  • try_send non-bloquant pour éviter de bloquer la source

TimerNode

  • Node passthrough qui ne modifie pas les données
  • Incrémente un compteur de samples
  • Calcule la position : position_sec = elapsed_samples / sample_rate
  • Fournit un TimerHandle pour monitoring

SinkNode

  • Node terminal qui consomme les chunks
  • Versions : silent, logging, stats, mock file writer

Pipeline type

SourceNode → DecoderNode → DSPNode → BufferNode → TimerNode → SinkNode(s)
                                           ↓
                                    Multiroom Sinks
                                    (avec offsets)

Exemples

Pipeline simple

use pmoaudio::{SinkNode, SourceNode, TimerNode};

#[tokio::main]
async fn main() {
    let (mut timer, timer_tx) = TimerNode::new(10);
    let (sink, sink_tx) = SinkNode::new("Output".to_string(), 10);

    timer.add_subscriber(sink_tx);
    let timer_handle = timer.get_position_handle();

    tokio::spawn(async move { timer.run().await.unwrap() });

    let sink_handle = tokio::spawn(async move {
        sink.run_with_stats().await.unwrap()
    });

    tokio::spawn(async move {
        let mut source = SourceNode::new();
        source.add_subscriber(timer_tx);
        source.generate_chunks(30, 4800, 48000, 440.0).await.unwrap();
    });

    sink_handle.await.unwrap();
}

Multiroom

let (buffer, buffer_tx) = BufferNode::new(50, 10);

let (sink1, sink1_tx) = SinkNode::new("Room 1".to_string(), 10);
let (sink2, sink2_tx) = SinkNode::new("Room 2".to_string(), 10);

buffer.add_subscriber_with_offset(sink1_tx, 0).await;  // Pas de délai
buffer.add_subscriber_with_offset(sink2_tx, 5).await;  // 5 chunks de retard

Lancer les exemples

# Pipeline simple
cargo run --example simple_pipeline

# Pipeline complet avec tous les nodes
cargo run --example pipeline_demo

# Configuration multiroom
cargo run --example multiroom_demo

# Streaming avec timing réel
cargo run --example streaming_demo

Tests

cargo test

20 tests unitaires couvrant :

  • Propagation des chunks
  • Calcul de position par TimerNode
  • BufferNode multi-abonné avec offsets
  • Arc sharing et zero-copy
  • DSP avec gain et filtrage
  • Resampling

Optimisations

  1. Arc sharing : Les AudioChunk sont clonés via Arc::clone() qui ne clone que le pointeur
  2. Copy-on-Write : Les DSP nodes clonent les données uniquement si modification nécessaire
  3. Bounded channels : Backpressure automatique
  4. try_send : Non-bloquant pour BufferNode, permet de sauter des chunks si un abonné est saturé
  5. RwLock : Pour partage concurrent du compteur TimerNode

Dépendances

  • tokio : Runtime async et channels
  • async-trait : Traits async

License

CeCill-2.0