Restructuration de pmoaudio avec ajout des messages de synchro
This commit is contained in:
291
old_code/pmoaudio/nodes/timer_node.rs
Normal file
291
old_code/pmoaudio/nodes/timer_node.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use crate::{
|
||||
nodes::{AudioError, MultiSubscriberNode},
|
||||
AudioChunk,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
|
||||
/// TimerNode - Node passthrough qui calcule la position temporelle
|
||||
///
|
||||
/// Ce node ne modifie pas les données audio, il les passe directement
|
||||
/// aux abonnés tout en maintenant un compteur de samples pour calculer
|
||||
/// la position en secondes.
|
||||
///
|
||||
/// # Fonctionnement
|
||||
///
|
||||
/// Pour chaque chunk reçu:
|
||||
/// 1. Incrémente `elapsed_samples += chunk.len()`
|
||||
/// 2. Calcule `position_sec = elapsed_samples / sample_rate`
|
||||
/// 3. Push le chunk (sans modification) vers les abonnés
|
||||
///
|
||||
/// # Utilisation
|
||||
///
|
||||
/// Le TimerNode fournit un [`TimerHandle`] qui permet de lire la position
|
||||
/// depuis d'autres threads/tasks sans bloquer le pipeline.
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::TimerNode;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (mut timer, timer_tx) = TimerNode::new(10);
|
||||
/// let handle = timer.get_position_handle();
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// timer.run().await.unwrap();
|
||||
/// });
|
||||
///
|
||||
/// // Lire la position depuis un autre thread
|
||||
/// let position = handle.position_sec().await;
|
||||
/// println!("Position: {:.2} sec", position);
|
||||
/// }
|
||||
/// ```
|
||||
pub struct TimerNode {
|
||||
rx: mpsc::Receiver<Arc<AudioChunk>>,
|
||||
subscribers: MultiSubscriberNode,
|
||||
elapsed_samples: Arc<RwLock<u64>>,
|
||||
current_sample_rate: Arc<RwLock<u32>>,
|
||||
}
|
||||
|
||||
impl TimerNode {
|
||||
/// Crée un nouveau TimerNode
|
||||
pub fn new(channel_size: usize) -> (Self, mpsc::Sender<Arc<AudioChunk>>) {
|
||||
let (tx, rx) = mpsc::channel(channel_size);
|
||||
|
||||
let node = Self {
|
||||
rx,
|
||||
subscribers: MultiSubscriberNode::new(),
|
||||
elapsed_samples: Arc::new(RwLock::new(0)),
|
||||
current_sample_rate: Arc::new(RwLock::new(48000)), // Default
|
||||
};
|
||||
|
||||
(node, tx)
|
||||
}
|
||||
|
||||
/// Ajoute un abonné
|
||||
pub fn add_subscriber(&mut self, tx: mpsc::Sender<Arc<AudioChunk>>) {
|
||||
self.subscribers.add_subscriber(tx);
|
||||
}
|
||||
|
||||
/// Retourne la position actuelle en secondes
|
||||
pub async fn position_sec(&self) -> f64 {
|
||||
let elapsed = *self.elapsed_samples.read().await;
|
||||
let sample_rate = *self.current_sample_rate.read().await;
|
||||
elapsed as f64 / sample_rate as f64
|
||||
}
|
||||
|
||||
/// Retourne le nombre total d'échantillons écoulés
|
||||
pub async fn elapsed_samples(&self) -> u64 {
|
||||
*self.elapsed_samples.read().await
|
||||
}
|
||||
|
||||
/// Reset le compteur
|
||||
pub async fn reset(&self) {
|
||||
let mut elapsed = self.elapsed_samples.write().await;
|
||||
*elapsed = 0;
|
||||
}
|
||||
|
||||
/// Démarre la boucle de traitement du TimerNode
|
||||
pub async fn run(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
// Mettre à jour le sample rate si nécessaire
|
||||
{
|
||||
let mut sr = self.current_sample_rate.write().await;
|
||||
if *sr != chunk.sample_rate() {
|
||||
*sr = chunk.sample_rate();
|
||||
}
|
||||
}
|
||||
|
||||
// Incrémenter le compteur d'échantillons
|
||||
{
|
||||
let mut elapsed = self.elapsed_samples.write().await;
|
||||
*elapsed += chunk.len() as u64;
|
||||
}
|
||||
|
||||
// Push immédiatement le même chunk vers les abonnés (passthrough)
|
||||
self.subscribers.push(chunk).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Version non-bloquante avec try_push
|
||||
pub async fn run_nonblocking(mut self) -> Result<(), AudioError> {
|
||||
while let Some(chunk) = self.rx.recv().await {
|
||||
{
|
||||
let mut sr = self.current_sample_rate.write().await;
|
||||
if *sr != chunk.sample_rate() {
|
||||
*sr = chunk.sample_rate();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut elapsed = self.elapsed_samples.write().await;
|
||||
*elapsed += chunk.len() as u64;
|
||||
}
|
||||
|
||||
self.subscribers.try_push(chunk).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retourne un handle pour lire la position depuis d'autres threads
|
||||
pub fn get_position_handle(&self) -> TimerHandle {
|
||||
TimerHandle {
|
||||
elapsed_samples: self.elapsed_samples.clone(),
|
||||
current_sample_rate: self.current_sample_rate.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle pour lire la position du TimerNode depuis d'autres threads
|
||||
///
|
||||
/// Ce handle peut être cloné et utilisé depuis plusieurs threads/tasks
|
||||
/// pour monitorer la position de lecture sans bloquer le pipeline.
|
||||
///
|
||||
/// # Exemples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoaudio::TimerNode;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (mut timer, _tx) = TimerNode::new(10);
|
||||
/// let handle = timer.get_position_handle();
|
||||
/// let handle_clone = handle.clone();
|
||||
///
|
||||
/// // Utiliser depuis plusieurs tasks
|
||||
/// tokio::spawn(async move {
|
||||
/// loop {
|
||||
/// let pos = handle_clone.position_sec().await;
|
||||
/// println!("Position: {:.2}s", pos);
|
||||
/// tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct TimerHandle {
|
||||
elapsed_samples: Arc<RwLock<u64>>,
|
||||
current_sample_rate: Arc<RwLock<u32>>,
|
||||
}
|
||||
|
||||
impl TimerHandle {
|
||||
/// Retourne la position actuelle en secondes
|
||||
pub async fn position_sec(&self) -> f64 {
|
||||
let elapsed = *self.elapsed_samples.read().await;
|
||||
let sample_rate = *self.current_sample_rate.read().await;
|
||||
elapsed as f64 / sample_rate as f64
|
||||
}
|
||||
|
||||
/// Retourne le nombre total d'échantillons écoulés
|
||||
pub async fn elapsed_samples(&self) -> u64 {
|
||||
*self.elapsed_samples.read().await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::BitDepth;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_timer_node_position_calculation() {
|
||||
let (mut node, tx) = TimerNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
let handle = node.get_position_handle();
|
||||
|
||||
// Spawn le node
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer 3 chunks de 1000 samples à 48000 Hz
|
||||
for i in 0..3 {
|
||||
let stereo = vec![[0i32; 2]; 1000];
|
||||
let chunk = AudioChunk::new(i, stereo, 48000, BitDepth::B24);
|
||||
tx.send(chunk).await.unwrap();
|
||||
}
|
||||
|
||||
// Attendre que les chunks soient traités
|
||||
for _ in 0..3 {
|
||||
out_rx.recv().await.unwrap();
|
||||
}
|
||||
|
||||
// Vérifier la position
|
||||
let position = handle.position_sec().await;
|
||||
let expected = 3000.0 / 48000.0; // 3 chunks * 1000 samples / 48000 Hz
|
||||
assert!((position - expected).abs() < 0.0001);
|
||||
|
||||
let elapsed = handle.elapsed_samples().await;
|
||||
assert_eq!(elapsed, 3000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_timer_node_passthrough() {
|
||||
let (mut node, tx) = TimerNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Envoyer un chunk
|
||||
let chunk = AudioChunk::from_channels_i32(
|
||||
42,
|
||||
vec![100, 200, 300],
|
||||
vec![400, 500, 600],
|
||||
48000,
|
||||
BitDepth::B24,
|
||||
);
|
||||
tx.send(chunk.clone()).await.unwrap();
|
||||
|
||||
// Recevoir le chunk
|
||||
let received = out_rx.recv().await.unwrap();
|
||||
|
||||
// Vérifier que c'est le même Arc (pas de clone des données)
|
||||
assert!(Arc::ptr_eq(&chunk, &received));
|
||||
assert_eq!(received.order(), 42);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_timer_node_sample_rate_change() {
|
||||
let (mut node, tx) = TimerNode::new(10);
|
||||
let (out_tx, mut out_rx) = mpsc::channel(10);
|
||||
|
||||
node.add_subscriber(out_tx);
|
||||
|
||||
let handle = node.get_position_handle();
|
||||
|
||||
tokio::spawn(async move {
|
||||
node.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Chunk à 48000 Hz
|
||||
let chunk1 = AudioChunk::new(0, vec![[0i32; 2]; 48000], 48000, BitDepth::B24);
|
||||
tx.send(chunk1).await.unwrap();
|
||||
out_rx.recv().await.unwrap();
|
||||
|
||||
// Après 48000 samples à 48000 Hz = 1 seconde
|
||||
let pos1 = handle.position_sec().await;
|
||||
assert!((pos1 - 1.0).abs() < 0.0001);
|
||||
|
||||
// Chunk à 96000 Hz
|
||||
let chunk2 = AudioChunk::new(1, vec![[0i32; 2]; 96000], 96000, BitDepth::B24);
|
||||
tx.send(chunk2).await.unwrap();
|
||||
out_rx.recv().await.unwrap();
|
||||
|
||||
// Position calculée avec le nouveau sample rate
|
||||
let pos2 = handle.position_sec().await;
|
||||
let expected = (48000.0 + 96000.0) / 96000.0;
|
||||
assert!((pos2 - expected).abs() < 0.0001);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user