Commit Graph

48 Commits

Author SHA1 Message Date
77d0d73ed7 🗑️ Remove unused imports and format code
- Comment out `use std::simd::*` in pmoaudio as it's unused and modules import their own SIMD
- Remove `Instant` from imports in track_metadata.rs (unused)
- Reorder anyhow import to match Rust convention (`anyhow, Result` → `Result`) and remove unused imports
- Improve XML response logging readability with line breaks in iterator chain
+ Refactor `get()` method to multi-line for clarity and consistency
2026-04-04 00:57:37 +02:00
6fe2f6be95 Refactor audio pipeline for multi-client streaming and improved error handling
Refactor the audio pipeline to support multi-client streaming with new OggFlacStreamHandle and StreamingOggFlacSink.

- Replace DirectOggFlacSink with StreamingOggFlacSink in pipeline
- Update documentation and comments to reflect multi-client support
- Add detailed warning logs for buffer underruns and client disconnections
- Remove TimerBufferNode from pipeline as it's no longer needed
- Update connect() calls to subscribe() for new streaming behavior

This change enables multiple clients to subscribe to the same audio stream, with each subscription getting a live feed from the current point in time.
2026-02-26 20:42:07 +01:00
6f8a80a58a Refactorisation des nœuds audio pour utiliser boxed()
Cette mise à jour refactorise les nœuds audio pour utiliser la méthode `boxed()` lors de l'enregistrement des enfants, améliorant ainsi la cohérence et la lisibilité du code. Les méthodes `make()` sont ajoutées pour faciliter la création d'instances boxées des nœuds, et les exemples sont mis à jour en conséquence.
2026-02-26 20:18:30 +01:00
306e691c61 Refactor WebRenderer to use server-side streaming with OGG-FLAC sink
This commit refactors the WebRenderer to use a server-side streaming architecture with OGG-FLAC sink instead of the previous WebSocket-based approach. The changes include:

- Replaced WebSocket communication with HTTP streaming using DirectOggFlacSink
- Implemented a new pipeline architecture with dedicated handlers for UPnP commands
- Added new modules for registration, registry, and streaming
- Updated the renderer to work with a pipeline control system
- Removed old WebSocket session management
- Added support for HTTP streaming with gapless playback
- Updated dependencies and features for the new architecture

The WebRenderer now acts as a MediaRenderer UPnP device that serves audio streams via HTTP endpoints, with commands relayed to the audio pipeline through a new control system.
2026-02-26 20:05:42 +01:00
a4301140d8 Migration vers une forme unifiée des cargos 2025-12-30 16:49:29 +01:00
9f62191830 webui control point step 2 2025-12-06 12:48:21 +01:00
cd47266fc8 Gestion des morts prématurées. 2025-11-18 21:25:37 +01:00
9be6835ddc Ou encore un peu de factorisation dans les nœuds. 2025-11-17 22:21:56 +01:00
d9ad056933 Fabriquans un object Channel dans RadioRaradise 2025-11-15 14:59:41 +01:00
1c2d30cbe9 debuggage des stream 2025-11-15 12:21:30 +01:00
Claude
215b097f4b Add detailed tracing for backpressure investigation
Investigation revealed the root cause of premature streaming termination:

1. MPSC Channel Size Issue:
   - DEFAULT_CHANNEL_SIZE = 16 chunks × 50ms = 800ms capacity
   - TimerNode max_lead_time = 3.0 seconds
   - The channel fills up in 0.8s while TimerNode wants 3s buffer
   - This creates stop-and-go pattern instead of smooth backpressure

2. Channel Closure Issue:
   - When RadioParadiseStreamSource::process() returns, the Node
     automatically closes output channels
   - TimerNode receives EOF and terminates immediately
   - Remaining chunks in MPSC buffer (up to 16) are never sent to sink

Added comprehensive tracing:
- RadioParadiseStreamSource: Track backpressure blocking, chunk counts,
  decode timing
- TimerNode: Log all pacing decisions, sleep durations, lead time
- Both use trace! for high-frequency events, debug! for blocking

Next steps:
- Option A: Increase channel size to match max_lead_time
  (60 chunks for 3s @ 50ms)
- Option B: Wait for channels to drain before closing
  (use tx.closed().await)
- Option C: Both A and B for optimal behavior

The previous "wait for playback duration" fix is a valid workaround
but doesn't address the architectural issue.
2025-11-12 10:27:35 +00:00
Claude
8ba6c1365a Reduce verbose logging from INFO to DEBUG/TRACE
Cleaned up excessive INFO logging that was added during debugging.
Logs are now properly categorized by verbosity:

- Frequent/repeated logs (every chunk) → TRACE
  * TimerNode SLEEPING messages
  * Broadcaster "Read X bytes" messages

- Occasional/setup logs → DEBUG
  * Node::run() starting/spawning
  * TopZeroSync received
  * Cancelled during sleep

- Important events remain INFO
  * Encoder initialization
  * Header captured
  * Stream ended
  * Broadcaster task started

This makes INFO logs clean and useful for production monitoring,
while keeping detailed information available via DEBUG/TRACE levels.

Tested with RUST_LOG=info - output is now clean with only
meaningful events logged.

Affected files:
- pmoaudio/src/nodes/timer_node.rs: SLEEPING → trace, TopZeroSync → debug
- pmoaudio/src/pipeline.rs: Node::run/Spawning → debug
- pmoaudio-ext/src/sinks/streaming_flac_sink.rs: Read bytes → trace
2025-11-12 10:02:38 +00:00
Claude
fd421cfc80 Add diagnostic logging to TimerNode for real-time pacing verification
Enhanced logging to INFO level for key TimerNode operations:
- TopZeroSync reception and timer reset
- Sleep operations when lead_time exceeds max_lead_time
- Warning when no timer is set (missing TopZeroSync)

This diagnostic logging confirmed that:
1. TopZeroSync is properly received from RadioParadiseStreamSource
2. TimerNode correctly calculates lead_time and sleeps ~48ms per 50ms chunk
3. Real-time pacing is working as expected (3.0s max lead time)

The backpressure mechanism is functioning correctly - chunks flow at
real-time speed (~50ms per chunk) rather than downloading at maximum speed.
2025-11-12 07:02:39 +00:00
Claude
7fb953c5a3 Add INFO-level logging to pipeline and TimerNode for debugging 2025-11-12 06:59:24 +00:00
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
Claude
7e81a8e777 Add TimerNode for rate limiting and improve progressive cache handling
Changes:
- Add TimerNode (pmoaudio/src/nodes/timer_node.rs): Rate-limits audio chunk flow based on timestamps with configurable max_lead_time
- Integrate TimerNode into play_and_cache.rs pipeline: PlaylistSource → TimerNode (3s pacing) → AudioSink
- Improve EOF retry in playlist_source.rs: Wait for prebuffer (512KB) before decoding, retry on temporary EOF with 200ms delay
- Export TimerNode in pmoaudio lib.rs and nodes/mod.rs

Known issue: Cache files may still be truncated when TrackBoundary arrives before pump completes flushing.
This requires allowing parallel write tasks as suggested.
2025-11-07 13:44:25 +00:00
Claude
78004b0327 Fix FLAC pk collision by ensuring full 1024 bytes are read
Problem Analysis:
- All FLAC files had the same pk (071c5713d5cf485ca688832207bef0f9)
- Root cause: read() can return < 1024 bytes on first call
- If read returned only 400 bytes:
  * header.len() = 400
  * 400 > 512 = false
  * Used header[..] (first 400 bytes = FLAC header)
  * All FLAC files have identical headers → same pk!

Solution:
- Added read_exact_or_eof() that loops until 1024 bytes read (or EOF)
- Guarantees we skip FLAC header and use actual audio content
- Works for small files (< 512 bytes) and large files (>= 1024 bytes)

Additional Feature:
- Added AudioSink::with_null_output() for testing without audio device
- Added --null-audio flag to play_and_cache example
- Allows testing in containerized environments

Changes:
1. pmocache/src/download.rs: Added read_exact_or_eof()
2. pmocache/src/cache.rs: Use read_exact_or_eof() for pk calculation
3. pmoaudio/src/nodes/audio_sink.rs: Added null output mode
4. pmoparadise/examples/play_and_cache.rs: Added --null-audio flag

Test Results:
- New pk: 83702c1cbca72074ebf7c123336786ea (was 071c...)
- Null audio output works correctly
- Ready for full testing
2025-11-07 07:24:19 +00:00
coissac
9871cfcb70 Merge branch 'claude/add-pmoplaylist-source-011CUpmZ9YbyUAUshEePVTJi' into claude/add-pmoplaylist-source-011CUq8bHCyjrEqGxCCXuvfh 2025-11-05 20:22:59 +01:00
Claude
06a2797479 Fix AudioSink Send trait issue with cpal Stream
Problem:
- cpal::Stream is not Send
- Cannot use Stream across await points in async functions
- Caused compilation error in AudioSinkLogic::process

Solution:
- Spawn dedicated thread for cpal Stream (similar to rodio approach)
- Communicate with thread via std::mpsc channel
- Thread waits for shutdown command before dropping stream
- Main async loop can now safely await without Send issues

Changes:
- Add std::mpsc and std::thread imports
- Create stream_cmd channel (std::mpsc::channel)
- Spawn thread::spawn for stream creation and management
- Replace drop(stream) with stream_cmd_tx.send + thread.join
- Handle errors in thread with tracing::error (no ? operator)

Testing:
- Compiled successfully with libsoxr and libasound2 (local install)
- Dependencies installed in ~/.local without sudo
- PKG_CONFIG_PATH configured correctly
- LD_LIBRARY_PATH configured correctly

Note: pmoparadise example has unrelated netstat2 compilation issue
2025-11-05 18:39:14 +00:00
Claude
30d30739bd Refactor AudioSink: remove volume, use dsp optimized conversions
Changes:
- Remove all volume management (use VolumeNode in pipeline instead)
- Detect hardware format (I16/U16/F32) at startup
- Accept all AudioChunk formats (I16/I24/I32/F32/F64) as input
- Use optimized SIMD functions from dsp::int_float module
- SharedBuffer stores raw AudioChunk + intermediate F32 buffer
- Callbacks adapted to hardware format with proper conversion

Architecture:
1. AudioChunk pushed to SharedBuffer
2. Lazy conversion to F32 interleaved using dsp functions
3. Callback converts F32 → hardware format (I16/U16) if needed

Benefits:
- SIMD optimized conversions (dsp module)
- Clean separation of concerns (volume in VolumeNode)
- Hardware format detection (use native format when possible)
- Flexible input (accepts any AudioChunk type)

Note: Requires ALSA (libasound2-dev) on Linux for compilation
2025-11-05 18:25:08 +00:00
Claude
8076ed5a48 Replace rodio with cpal for AudioSink
- Replace rodio dependency with cpal in pmoaudio/Cargo.toml
- Add AudioSink node using cpal for direct hardware access
- Add SharedBuffer for async/callback communication
- Convert all audio formats to F32 for cpal
- Improve latency and control over audio stream
- Add WHY_CPAL.md explaining the technical choice
- Update INSTALL_NOTES.md with ALSA requirements
- Export AudioSink in lib.rs and mod.rs

Benefits:
- Minimal latency (no extra layers)
- Direct hardware control
- Lighter binary (~3.8 MB less)
- Same ALSA dependency as rodio on Linux
- Cross-platform (ALSA/JACK on Linux, CoreAudio on macOS, WASAPI on Windows)
2025-11-05 18:04:08 +00:00
Claude
ff4ebcdfa5 feat: Add AudioSink node for audio playback via rodio
Implémente AudioSink qui permet la lecture audio en temps réel sur la sortie audio
standard via rodio avec architecture thread-safe.

- Nouveau nœud AudioSink avec thread dédié pour gérer rodio (OutputStream non-Send)
- Accepte tous formats audio (I16/I24/I32/F32/F64) et convertit vers I16
- Support volume, arrêt gracieux, transitions gapless
- Exemples: play_audio.rs et play_with_resampling.rs
- Documentation: INSTALL_LIBSOXR.md mise à jour avec instructions ALSA
- Tests unitaires inclus, tous passent
2025-11-05 14:30:15 +00:00
Claude
971f0ba9d6 test: Add comprehensive test coverage for PlaylistSource and ResamplingNode
This commit adds extensive unit and integration tests for the audio pipeline
components that were previously untested.

## ResamplingNode Tests (pmoaudio/src/nodes/resampling_node.rs)
- Added 7 test functions covering:
  - Helper function tests: extract_channels_i16/i24
  - Reconstruction tests: reconstruct_chunk_i16/i24
  - Logic tests: passthrough when sample rate matches
  - Async tests: verify sync markers pass through unchanged
  - Integration test: actual 44.1kHz → 48kHz resampling

## PlaylistSource Tests (pmoaudio-ext/src/sources/playlist_source.rs)
- Added 10 test functions covering:
  - Stream validation: valid/invalid channel counts and bit depths
  - PCM conversion: bytes_to_segment for I16/I24/I32 formats
  - Mono/stereo handling: verify channel duplication
  - Error handling: unsupported bit depth rejection
  - Type safety: compilation verification

## Bug Fixes
- Fixed imports: Node and NodeLogic moved from nodes to pipeline module
- Fixed AudioCache import: use pmoaudiocache::Cache with alias
- Fixed API calls in ResamplingNode:
  - BitDepth::from_audio_chunk() → match pattern
  - .stereo() → .get_frames()
  - .to_i32() → .as_i32() for I24
  - .sample_rate() → .get_sample_rate()

## Documentation
- Added INSTALL_LIBSOXR.md with detailed installation instructions
- Documents local libsoxr installation without sudo privileges
- Provides troubleshooting guide for build and test environments

All tests pass successfully (17 tests total: 7 ResamplingNode + 10 PlaylistSource).
2025-11-05 14:16:12 +00:00
Claude
83e9cca756 fix: Correct ResamplingNode API usage for AudioChunkData
- Replace BitDepth::from_audio_chunk() with match pattern
- Use get_frames() instead of stereo() method (API change)
- Use as_i32() instead of to_i32() for I24 conversion

Also successfully installed libsoxr locally without sudo:
- Downloaded libsoxr-dev and libsoxr0 via apt-get
- Extracted to ~/.local using dpkg -x
- Set PKG_CONFIG_PATH and LD_LIBRARY_PATH
- Compilation now succeeds with libsoxr

The implementation is now complete and compiles successfully.
2025-11-05 13:56:27 +00:00
Claude
6a7ba01102 feat: Add PlaylistSource and ResamplingNode for playlist playback
This commit implements a new audio source that reads from pmoplaylist
and streams tracks continuously, along with a resampling node to
normalize sample rates.

## New Components

### PlaylistSource (pmoaudio-ext)
- New source in pmoaudio-ext/src/sources/playlist_source.rs
- Reads from pmoplaylist ReadHandle
- Decodes tracks from audio cache (pmoaudiocache)
- Emits PCM with heterogeneous sample_rate and bit_depth
- Polls playlist when empty (configurable interval, default 100ms)
- Emits TrackBoundary markers between tracks
- Graceful shutdown with EndOfStream on stop
- Gated behind 'playlist' feature flag

**Design Philosophy:**
- Keeps each node simple (single responsibility)
- Emits raw PCM without format normalization
- Pipeline designer chooses how to handle heterogeneity
- Ideal for Radio Paradise (homogeneous streams)
- Requires ResamplingNode + ToI24Node for mixed playlists

### ResamplingNode (pmoaudio)
- Generic resampling node in pmoaudio/src/nodes/resampling_node.rs
- Normalizes variable sample rates to a target rate
- Uses libsoxr for high-quality resampling
- Automatically detects sample rate changes
- Recreates resampler as needed
- Preserves chunk type (I16/I24/I32/F32/F64)
- Quality adapts to bit depth (Medium/High/Very High)

## Architecture

PlaylistSource is placed in pmoaudio-ext to avoid circular dependencies:
- pmoaudio-ext depends on: pmoaudio, pmoplaylist, pmoaudiocache
- No reverse dependencies = clean dependency graph

## Configuration

### pmoaudio-ext/Cargo.toml
- Updated 'playlist' feature to include pmoaudiocache, pmocache, pmoflac
- Added sources module export

### pmoaudio
- Added resampling_node module
- Public export: ResamplingNode

## System Requirements

⚠️ **IMPORTANT**: libsoxr-dev must be installed for compilation

See INSTALL_NOTES.md for installation instructions per platform.

## Usage Example

```rust
// Radio Paradise (homogeneous 44.1kHz/16bit)
let mut source = PlaylistSource::new(playlist, cache);
let to_i24 = ToI24Node::new();
source.register(Box::new(to_i24));

// Mixed playlist (needs normalization)
let mut source = PlaylistSource::new(playlist, cache);
let mut resampler = ResamplingNode::new(48000);  // Force 48kHz
let to_i24 = ToI24Node::new();
source.register(Box::new(resampler));
resampler.register(Box::new(to_i24));
```

## Files Changed
- pmoaudio-ext/Cargo.toml: Update playlist feature
- pmoaudio-ext/src/lib.rs: Add sources module
- pmoaudio-ext/src/sources/mod.rs: New sources module
- pmoaudio-ext/src/sources/playlist_source.rs: New PlaylistSource (580 lines)
- pmoaudio/src/nodes/resampling_node.rs: New ResamplingNode (350 lines)
- pmoaudio/src/nodes/mod.rs: Register resampling_node
- pmoaudio/src/lib.rs: Export ResamplingNode
- INSTALL_NOTES.md: System requirements documentation

## Future Work
- GapInsertionNode (inserts silence between tracks)
- CrossfadeNode (fade-in/fade-out mixing)
- Examples (deferred until implementation validated)
2025-11-05 13:44:24 +00:00
Claude
92c53ed3a5 feat: Add logic_mut() to Node and fix playlist registration in FlacCacheSink
- Add Node::logic_mut() method to allow post-construction configuration
  of node logic before run() is called
- Fix FlacCacheSink to properly transfer playlist_handle_pending to
  the inner logic using logic_mut()
- Resolves FIXME at flac_cache_sink.rs:656 about missing logic_mut()

This enables the playlist registration mechanism to work correctly:
1. User calls register_playlist() on FlacCacheSink
2. Handle is stored in playlist_handle_pending
3. During run(), handle is transferred to FlacCacheSinkLogic
4. Tracks are automatically added to playlist after caching
2025-11-04 22:29:32 +00:00
88349e7797 Récupération de l'erreur git cleaning 2025-11-04 21:13:06 +01:00
8389d1a78e Récupération de l'erreur git cleaning 2025-11-04 21:13:06 +01:00
83d7520840 Ajout de la gestion des couvertures d'albums par le FlacCacheSink 2025-11-03 20:48:58 +01:00
0603da2998 Ajout de la gestion des couvertures d'albums par le FlacCacheSink 2025-11-03 20:48:58 +01:00
40950164e9 Ajout d'un noeud puis vers le cache audio 2025-11-03 14:45:37 +01:00
157baadcfd Ajout d'un noeud puis vers le cache audio 2025-11-03 14:45:37 +01:00
1eff9a57a5 Implémentation des pmometadata dans pmocacheaudio 2025-11-03 14:45:37 +01:00
7d907e7429 Implémentation des pmometadata dans pmocacheaudio 2025-11-03 14:45:37 +01:00
6bce26c4fc Retour sur pmoaudio 2025-11-03 14:45:37 +01:00
5f29496d79 Retour sur pmoaudio 2025-11-03 14:45:37 +01:00
8fe110eeb7 Restructuration de pmoaudio avec ajout des messages de synchro 2025-11-03 14:45:37 +01:00
a14210345c Restructuration de pmoaudio avec ajout des messages de synchro 2025-11-03 14:45:37 +01:00
27a5279378 Passons en stéreo
reprise du module DSP pmoaudio
2025-11-03 14:45:37 +01:00
0351d17fee Passons en stéreo
reprise du module DSP pmoaudio
2025-11-03 14:45:37 +01:00
208fe8be76 amélioration de la webapp 2025-10-20 16:18:21 +02:00
776c535862 amélioration de la webapp 2025-10-20 16:18:21 +02:00
3ca6fa9884 Complete la crate pmoaudio 2025-10-11 16:02:45 +02:00
fb94886c53 Complete la crate pmoaudio 2025-10-11 16:02:45 +02:00
6a98fcc2b9 Reprise générale de la structure de l'appliweb 2025-10-11 09:55:24 +02:00
3fa53c1ba8 Reprise générale de la structure de l'appliweb 2025-10-11 09:55:24 +02:00
e70537ed1b Création du module pmoaudio 2025-10-11 09:46:26 +02:00
d29b6cc1fb Création du module pmoaudio 2025-10-11 09:46:26 +02:00