Commit Graph

434 Commits

Author SHA1 Message Date
coissac
9d1aa3ea3d Merge pull request #13 from coissac/claude/add-pmoplaylist-source-011CUq8bHCyjrEqGxCCXuvfh
Add register_audio_cache/register_cover_cache functions to pmoupnp
2025-11-05 20:55:49 +01:00
Claude
123bac1fdf Add register_audio_cache/register_cover_cache functions to pmoupnp
Fix "PlaylistManager not initialized" error in play_and_cache example.
The error occurred because pmoplaylist's WriteHandle calls
pmoupnp::get_audio_cache() to validate cache pks, but the global
cache registry wasn't initialized.

Changes:
- Add register_audio_cache() and register_cover_cache() functions
- Export them from pmoupnp lib.rs
- Call them in play_and_cache example after creating caches

This mirrors how UpnpServer initializes the cache registry.
2025-11-05 19:49:59 +00:00
coissac
4e775e2119 Merge pull request #12 from coissac/claude/add-pmoplaylist-source-011CUq8bHCyjrEqGxCCXuvfh
Fix play_and_cache example: enable pmoconfig feature for PlaylistManager
2025-11-05 20:45:47 +01:00
Claude
a0b6463273 Fix play_and_cache example: enable pmoconfig feature for PlaylistManager
- Add pmoconfig to the 'full' feature so PlaylistManager can auto-initialize
- Update play_and_cache example to use AudioSink::new() without volume
- Remove unused PlaylistManager import

Without pmoconfig feature enabled, PlaylistManager::get() fails with
"PlaylistManager not initialized" error.
2025-11-05 19:44:38 +00:00
coissac
95b7a9015c Merge pull request #11 from coissac/claude/add-pmoplaylist-source-011CUq8bHCyjrEqGxCCXuvfh
Fix corrupted Cargo.lock (duplicate ndk-context entries)
2025-11-05 20:40:29 +01:00
Claude
df06bc74fd Fix play_and_cache example to use new AudioSink API
Remove obsolete with_volume() call and unused PlaylistManager import.
AudioSink no longer manages volume - use VolumeNode if needed.
2025-11-05 19:39:48 +00:00
Claude
50fed84a1b Fix corrupted Cargo.lock (duplicate ndk-context entries)
Regenerate Cargo.lock to resolve parsing error where ndk-context
was specified twice. This fixes the "failed to parse lock file" error.
2025-11-05 19:34:31 +00:00
coissac
5337615c6a Merge pull request #10 from coissac/claude/add-pmoplaylist-source-011CUpmZ9YbyUAUshEePVTJi
Claude/add pmoplaylist source 011 c upm z9 yby ua ush ee pvt ji
2025-11-05 20:27:17 +01:00
coissac
c047e625d8 Merge pull request #9 from coissac/claude/add-pmoplaylist-source-011CUq8bHCyjrEqGxCCXuvfh
Claude/add pmoplaylist source 011 c uq8b h cyjr eq gx cc xuvfh
2025-11-05 20:23:13 +01: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
e60535ed3c Update netstat2 to 0.11 for libc compatibility
Fix compilation error with recent libc versions where tcp_info struct
has changed. The netstat2 0.11 update includes compatibility fixes.
2025-11-05 19:06:08 +00:00
Claude
282b4cfad6 Amélioration documentation pour environnement Claude Code
- Ajout d'un guide complet pour les sessions Claude Code
- Correction: cpal remplace rodio pour AudioSink
- Ajout de libasound2t64 dans les dépendances (bibliothèque partagée)
- Ajout de RUSTFLAGS dans la configuration des variables d'environnement
- Documentation du workflow pour chaque nouvelle session
- Ajout de setup-env.sh au .gitignore

Le guide inclut maintenant:
- Installation complète sans sudo
- Configuration des variables d'environnement
- Vérification de l'installation
- Compilation et test des exemples
- Résolution des erreurs communes
2025-11-05 18:57:52 +00: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
bf1de53952 fix: Correct play_and_cache example API usage
Corrections pour que l'exemple utilise les bonnes API:
- Utilisation directe de Cache::new() au lieu de méthodes de config
- Suppression des appels à root() qui n'existent pas
- Utilisation du singleton PlaylistManager() au lieu de new()
- Ajout de cache-sink comme dépendance de playlist feature dans pmoaudio-ext

L'exemple devrait maintenant compiler correctement avec:
cargo run --example play_and_cache --features full -- <channel_id>
2025-11-05 15:55:57 +00:00
Claude
6b3851de19 feat: Add play_and_cache example for Radio Paradise streaming and playback
Crée un nouvel exemple complet qui démontre l'utilisation de tout le pipeline:
- Téléchargement d'un bloc Radio Paradise
- Cache FLAC via FlacCacheSink
- Playlist alimentée automatiquement
- Lecture en temps réel via PlaylistSource et AudioSink

Architecture à deux pipelines :
Pipeline 1 (Download & Cache):
  RadioParadiseStreamSource → FlacCacheSink (avec playlist abonnée)

Pipeline 2 (Playback):
  PlaylistSource (lit la playlist) → AudioSink (joue l'audio)

Les deux pipelines s'exécutent en parallèle, permettant la lecture pendant le
téléchargement.

Modifications:
- Ajout pmoaudio-ext avec feature playlist dans pmoparadise
- Nouvelle feature "full" combinant pmoaudio + pmoaudio-ext
- Logs détaillés à tous les niveaux (DEBUG)

Usage: cargo run --example play_and_cache --features full -- <channel_id>
2025-11-05 15:49:23 +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
coissac
aa916fa1ac Merge pull request #8 from coissac/claude/download-block-example-011CUpYvvxQzW5Hv2E4aL1nk
Ok maintenant tout devrait rouler
2025-11-05 13:39:54 +01:00
Claude
33fefa7d33 Merge refactoring branch and remove obsolete code
This merges the refactoring branch (claude/finish-pmomusic-refactoring-011CUpSZ7FyDCZL78pzoHXLs)
into main and explicitly removes obsolete files that were part of the old architecture.

Changes from refactoring branch:
- Updated client.rs with improved ClientBuilder
- Updated config_ext.rs with user-friendly channel configuration

Removed obsolete files:
- .pmomusic.yml (contained passwords - security issue)
- pmoparadise/src/paradise/ directory (6 files - old orchestration layer)
- pmoparadise/src/stream.rs (deprecated streaming API)
- pmoparadise/src/streaming.rs (deprecated streaming API)
- pmoparadise/src/track.rs (deprecated track API)
- pmoparadise/src/ffmpeg_streaming.rs (deprecated FFmpeg code)
- 5 obsolete example files (extract_track, show_source_image,
  stream_block, test_streaming, with_cache)

This makes the refactoring the new main branch state with a clean codebase.
2025-11-05 12:32:24 +00:00
coissac
4110b3875a Merge pull request #6 from coissac/claude/fix-download-block-bug-clean-011CUpYvvxQzW5Hv2E4aL1nk
fix download block bug clean
2025-11-05 13:10:11 +01:00
Claude
98bf45cd27 feat: Add user-friendly default_channel configuration
Ajoute la possibilité de configurer le channel par défaut de Radio Paradise
de manière persistante et user-friendly.

Fonctionnalités :
- get_paradise_default_channel() : récupère le channel configuré (défaut: 0/main)
- set_paradise_default_channel(u8) : définit le channel par défaut
- Accepte DEUX formats dans le fichier YAML :
  * Noms conviviaux : "main", "mellow", "rock", "eclectic"
  * IDs numériques : 0, 1, 2, 3
- Stocke les valeurs comme chaînes conviviales pour la lisibilité
- Validation automatique avec fallback sur "main" si invalide
- Persistence automatique de la valeur par défaut lors du premier accès

Exemple de configuration YAML :
```yaml
sources:
  radio_paradise:
    enabled: true
    default_channel: mellow  # ou 1
```

Cette amélioration rend la configuration plus accessible aux utilisateurs
qui préfèrent un channel autre que Main Mix par défaut.
2025-11-05 12:00:59 +00:00
Claude
bac4a94cad refactor: Eliminate remaining duplications in client.rs
Corrections :
1. Supprimé le commentaire obsolète sur block_base (ligne 277)
2. Créé la constante DEFAULT_CHANNEL pour éviter de coder "0" en dur
3. Utilisé DEFAULT_CHANNEL dans with_client(), ClientBuilder::default() et tests
4. Amélioré la documentation de with_client() pour guider vers le builder

Bien que with_client() et ClientBuilder::default() aient encore une structure
similaire, ils utilisent maintenant les mêmes constantes, réduisant ainsi
le risque d'incohérence lors de modifications futures.
2025-11-05 12:00:55 +00:00
Claude
0a54db5963 refactor: Replace block_base field with dynamic calculation
Supprime complètement la duplication d'information en transformant
block_base d'un champ stocké en une méthode calculée dynamiquement.

Changements:
- Supprimé le champ block_base de RadioParadiseClient
- Ajouté la constante BLOCK_BASE_URL pour éviter la duplication de l'URL
- Transformé block_base en méthode publique qui calcule à partir de channel
- Simplifié with_client() et clone_with_channel()
- Simplifié le builder qui n'a plus besoin d'initialiser block_base

Cette approche garantit que block_base est toujours cohérent avec channel,
éliminant définitivement toute possibilité de bug de synchronisation.
2025-11-05 12:00:50 +00:00
Claude
cc3e31dbd0 fix: Eliminate channel/block_base duplication in ClientBuilder
Le bug identifié était que le block_base n'était pas synchronisé avec
le channel dans le ClientBuilder, causant le téléchargement du même
bloc pour différents channels.

Changements:
- Supprimé le champ block_base du ClientBuilder (duplication)
- Supprimé la constante DEFAULT_BLOCK_BASE (plus nécessaire)
- Supprimé la méthode .block_base() du builder (complexité inutile)
- Le block_base est maintenant calculé dynamiquement dans build()
  à partir du channel, éliminant toute possibilité de désynchronisation

Cette approche suit le principe DRY et élimine une source de bugs.
2025-11-05 12:00:45 +00:00
coissac
27f7b0a304 Merge pull request #5 from coissac/claude/download-block-example-011CUpYvvxQzW5Hv2E4aL1nk
feat: Add download_block example for Radio Paradise
2025-11-05 12:43:22 +01:00
Claude
23af037f36 feat: Add download_block example for Radio Paradise
Add a new example that demonstrates downloading a complete Radio Paradise
block and saving each track as a separate FLAC file.

The example:
- Takes a channel ID as argument (0-3)
- Fetches current block metadata
- Creates an output directory ./rp_channel_{id}block{blockid}
- Uses RadioParadiseStreamSource to stream and decode the block
- Uses FlacFileSink to automatically detect TrackBoundary markers
- Saves each track as a separate FLAC file with metadata

Example usage:
  cargo run --example download_block --features=pmoaudio -- 0

This demonstrates the full pipeline integration between pmoparadise
and pmoaudio, showing how RadioParadiseStreamSource and FlacFileSink
work together to handle multi-track FLAC blocks seamlessly.
2025-11-05 11:42:06 +00:00
coissac
6dd3ef6c61 Merge pull request #4 pmoparadise refactoring phase 1
Claude/download block example 011 c up yvvx qz w5 hv2 e4a l1nk
2025-11-05 11:51:53 +01:00
coissac
7cefc51020 Merge branch 'main' into claude/download-block-example-011CUpYvvxQzW5Hv2E4aL1nk 2025-11-05 11:51:11 +01:00
Claude
8ff74da191 feat: Add download_block example for Radio Paradise
Add a new example that demonstrates downloading a complete Radio Paradise
block and saving each track as a separate FLAC file.

The example:
- Takes a channel ID as argument (0-3)
- Fetches current block metadata
- Creates an output directory ./rp_channel_{id}block{blockid}
- Uses RadioParadiseStreamSource to stream and decode the block
- Uses FlacFileSink to automatically detect TrackBoundary markers
- Saves each track as a separate FLAC file with metadata

Example usage:
  cargo run --example download_block --features=pmoaudio -- 0

This demonstrates the full pipeline integration between pmoparadise
and pmoaudio, showing how RadioParadiseStreamSource and FlacFileSink
work together to handle multi-track FLAC blocks seamlessly.
2025-11-05 10:21:41 +00:00
Claude
78cbded701 refactor: Clean up obsolete per-track references and broken example
Remove obsolete code referencing the deleted per-track feature:
- Remove FlacDecode and WavEncode error variants from error.rs
- Remove claxon::Error conversion impl
- Delete broken radio_paradise_stream.rs example (incorrect imports)

Result:
- error.rs: 77 → 60 lines (-17 lines, -22%)
- examples/radio_paradise_stream.rs: deleted (-100 lines)
- Warnings reduced from 7 to 4

All remaining code compiles successfully.
2025-11-05 09:27:32 +00:00
Claude
010d233920 fix: Add #[async_trait] to RadioParadiseExt to eliminate warning
Add async_trait annotation to RadioParadiseExt trait and its implementation
to suppress the "async fn in public traits" warning.

This is the recommended approach for traits with async methods as it ensures
proper Future bounds (Send) are generated.
2025-11-05 09:21:27 +00:00
Claude
69831a17df refactor: Remove obsolete streaming API (stream.rs, track.rs, per-track feature)
The old streaming API has been completely replaced by RadioParadiseStreamSource
which integrates directly with the pmoaudio pipeline.

Removed:
- src/stream.rs (179 lines) - BlockStream, stream_block(), download_block()
- src/track.rs - Per-track extraction functionality
- examples/stream_block.rs - Obsolete streaming example
- examples/extract_track.rs - Per-track extraction example
- Feature "per-track" and dependencies (hound, tempfile)

Updated:
- Cargo.toml: Removed per-track feature and obsolete examples
- lib.rs: Removed module declarations and re-exports

The new RadioParadiseStreamSource provides:
- Direct integration with pmoaudio pipeline
- FLAC decoding via pmoflac
- Automatic TrackBoundary insertion
- Better performance and lower latency
2025-11-05 09:15:11 +00:00
Claude
9b45be87d6 refactor: Simplify RadioParadiseConfigExt - remove history methods
Remove all history-related configuration methods from config_ext.rs:
- get_paradise_history_database() / set_paradise_history_database()
- get_paradise_history_size() / set_paradise_history_size()
- DEFAULT_HISTORY_DATABASE_DIR constant
- HISTORY_DEFAULT_MAX_TRACKS import

Keep only essential methods:
- get_paradise_enabled() / set_paradise_enabled()

Result: 264 lines → 115 lines (-149 lines, -56%)
2025-11-05 09:07:12 +00:00
Claude
74d8788463 refactor: Remove obsolete streaming.rs and deprecated examples
Further cleanup of unused code after paradise/ removal.

## Removed Files

### Module (217 lines)
- **streaming.rs**: FLAC streaming decoder using claxon
  - `ChannelReader`: Async Stream → sync Read adapter
  - `StreamingPCMDecoder`: Claxon-based FLAC decoder
  - `PCMChunk`: PCM data container
  - **Reason**: Was only used by paradise/worker.rs (deleted)
  - **Replacement**: RadioParadiseStreamSource uses pmoflac directly

### Examples (3 files)
- **show_source_image.rs**: Used deprecated RadioParadiseSource
- **with_cache.rs**: Used deprecated RadioParadiseSource
- **test_streaming.rs**: Used deleted streaming.rs module
  - **Replacement**: radio_paradise_stream.rs example shows modern approach

## Updated
- **lib.rs**: Removed `pub mod streaming;`

## Remaining Examples
Valid examples using current API:
-  now_playing.rs - API metadata access
-  stream_block.rs - HTTP block streaming
-  extract_track.rs - Per-track extraction (feature: per-track)
-  radio_paradise_stream.rs - Modern pmoaudio integration

## Statistics
- Before: 3566 lines (after paradise/ removal)
- After: 3348 lines
- This cleanup: -218 lines (-6%)
- **Total removed since start: 3048 lines (-48%)**

## Testing
-  All 23 tests pass
-  Compilation successful with all features
-  Examples compile (except per-track which requires feature)
2025-11-05 07:49:19 +00:00
Claude
f80ebd9f3d refactor: Remove obsolete orchestration layer, create RadioParadiseSource stub
Major cleanup removing 2831 lines (~45%) of outdated server orchestration code.
RadioParadiseStreamSource (pmoaudio integration) is now the primary implementation.

## Changes

### Removed (2393 lines)
- **paradise/ module** - Complete server orchestration system:
  - worker.rs (1146 lines) - Background polling, caching, state machine
  - channel.rs (429 lines) - Channel lifecycle management
  - playlist.rs (294 lines) - Shared playlist management
  - history.rs (218 lines) - SQLite persistence
  - constants.rs (209 lines) - Server configuration constants
  - mod.rs (29 lines) - Module exports

### Replaced
- **source.rs** (612 → 174 lines, -72%):
  - Old: Full MusicSource implementation with UPnP/DIDL integration
  - New: Minimal stub for backward compatibility with pmomediaserver
  - Returns empty results and deprecation warnings
  - Documents migration path to RadioParadiseStreamSource

### Updated
- **config_ext.rs**: Now imports HISTORY_DEFAULT_MAX_TRACKS from channels module
- **lib.rs**:
  - Removed paradise module
  - Updated documentation to focus on RadioParadiseStreamSource
  - Updated cargo features documentation

## Architecture

**Before**: Complex orchestration with workers, channels, caching, history
**After**: Simple API access + pmoaudio streaming (RadioParadiseStreamSource)

## Compatibility

RadioParadiseSource stub maintains API compatibility for pmomediaserver while
clearly indicating deprecation. All operations return empty results or errors
with migration guidance.

## Testing

-  All 23 tests pass
-  Compilation successful with all features
-  pmomediaserver compatibility maintained (stub implementation)

## Migration Path

Old (deprecated):
```rust
let source = RadioParadiseSource::from_registry(client)?;
```

New (recommended):
```rust
let stream_source = RadioParadiseStreamSource::new(client, None).await?;
let node = Node::from_logic(stream_source);
```
2025-11-05 07:40:36 +00:00
Claude
23e07fb6cf refactor: Complete REST API simplification with channels module
Changes:
1. Updated pmoserver_ext.rs to use channels module:
   - Changed imports from paradise:: to channels::
   - Now uses ChannelDescriptor from channels module
   - No longer depends on paradise orchestration code

2. Updated lib.rs:
   - Added pub mod channels
   - Maintains existing modules for now (will evaluate removal later)

3. Verification:
   - All tests pass (25/25)
   - No dead code warnings
   - Compilation successful with all features

The REST API is now simplified to provide only direct Radio Paradise
API access without the heavy orchestration layer.
2025-11-05 07:17:47 +00:00
Claude
f8e09939ba refactor: Clean up dead code and simplify pmoserver REST API
Changes:
1. Removed dead code from paradise/worker.rs:
   - Unused process_song() method
   - Unused DecodedBlock struct
   - Unused helper functions: song_duration_ms, ms_to_frames, decode_block_audio

2. Simplified pmoserver_ext.rs (840 → 383 lines):
   - Removed complex orchestration endpoints (status, playlist, history, streaming)
   - Kept only simple API access endpoints:
     * /now-playing
     * /block/current
     * /block/{event_id}
     * /channels
   - Removed dependencies on RadioParadiseSource and ParadiseChannel

3. Created channels.rs:
   - Extracted channel definitions from paradise/channel.rs
   - Pure data module with no orchestration logic
   - Contains: ParadiseChannelKind, ChannelDescriptor, ALL_CHANNELS

Note: This is work in progress. Still need to update lib.rs and remove
unused modules once dependencies are fully resolved.
2025-11-05 07:15:16 +00:00
Claude
1817d1becc feat: Add I32 support to RadioParadiseStreamSource
Add support for 32-bit integer audio samples to match FileSource and
HttpSource capabilities, ensuring complete bit depth coverage.

Changes:
- Add I32 case to pcm_to_audio_segment() for 32-bit stereo samples
- Update output_type() comment to document 16/24/32-bit support
- Note that bit depth is auto-detected from FLAC header via pmoflac

The implementation now supports the full range of FLAC bit depths:
- 16-bit: AudioChunk::I16 (most common)
- 24-bit: AudioChunk::I24 (high quality)
- 32-bit: AudioChunk::I32 (maximum precision)

pmoflac reads bits_per_sample from the FLAC STREAMINFO header
(decoder.rs:97), so the actual bit depth is determined by the source
stream, not hardcoded.

Verified: cargo check passes successfully.
2025-11-05 06:40:51 +00:00
Claude
77469d5ee8 fix: Correct I24 conversion in RadioParadiseStreamSource
Fix three critical bugs in pcm_to_audio_segment():

1. **Incorrect sign extension for I24**
   - Before: i32::from_le_bytes([b0, b1, b2, 0]) >> 8
     Always produces positive values for negative samples
   - After: Proper sign extension using bit 7 of MSB
     buf[3] = 0xFF if (b2 & 0x80) != 0

2. **Silent clamping instead of error handling**
   - Before: I24::new_clamped() - silently clamps invalid values
   - After: I24::new().ok_or_else() - returns error for invalid values
     Consistent with FileSource/HttpSource behavior

3. **Missing validation before chunks_exact()**
   - Before: chunks_exact() panics if size not multiple of frame_bytes
   - After: Explicit validation with descriptive error message

Implementation now matches the reference pattern from pmoaudio's
FileSource and HttpSource (file_source.rs:326-357, http_source.rs:440-470).

Verified: cargo check passes successfully.
2025-11-05 06:34:05 +00:00
Claude
ce63cbffb3 refactor: Remove dead FFmpeg code from pmoparadise
Remove unused FFmpeg-based progressive streaming implementation that was
never completed and is not used anywhere in the codebase.

Changes:
- Delete src/ffmpeg_streaming.rs (173 lines of unfinished code with TODOs)
- Remove ffmpeg module import from lib.rs
- Remove ffmpeg feature from Cargo.toml
- Remove ffmpeg-next dependency from Cargo.toml

The current implementation uses claxon (StreamingPCMDecoder) and symphonia
(decode_block_audio) for FLAC decoding, which are fully functional.

Verified: cargo check passes successfully after removal.
2025-11-05 06:24:57 +00:00
Claude
28e33dc26f fix: Properly await async metadata setters in song_to_metadata
Problem:
- Used `let _ = metadata.set_title(...)` which creates unawaited Future
- Futures were never executed → metadata fields never set!
- Ignored Result<(), MetadataError> which could contain errors

Solution:
- Spawn tokio task to configure metadata asynchronously
- Properly await all set_*() calls
- Handle errors with eprintln! warnings instead of silent ignore
- Clone all data upfront for the async task

Type info:
- metadata: MemoryTrackMetadata (concrete type)
- Returns: Arc<RwLock<dyn TrackMetadata>> (trait object)
- Methods: async fn set_*(&mut self) -> MetadataResult<()>

All 7 tests still pass 
2025-11-05 06:01:51 +00:00
Claude
094c4af082 fix: Complete RadioParadiseStreamSource refactoring for current pmoaudio API
Refactored RadioParadiseStreamSource to use current pmoaudio API:

Audio Segment Creation:
- Replaced AudioSegment::new_audio() with manual construction using _AudioSegment
- Convert PCM to Vec<[i16; 2]> or Vec<[I24; 2]> stereo pairs
- Use AudioChunkData::new(stereo, sample_rate, gain_db) → Arc
- Wrap in AudioChunk::I16() or AudioChunk::I24()
- Create AudioSegment with order, timestamp_sec, and _AudioSegment::Chunk()

Sync Markers:
- Replaced AudioSegment::new_sync() with AudioSegment::new_track_boundary()
- Created TopZeroSync manually with _AudioSegment::Sync()
- Use AudioSegment::new_end_of_stream() for EOF

Stream Handling:
- Changed from decoder.next() (doesn't exist) to decoder.read()
- Added AsyncReadExt import
- Use buffered read approach like http_source
- Changed decoder.stream_info() to decoder.info()

I24 Construction:
- Changed I24::from_i32() to I24::new_clamped()
- Properly handles 24-bit PCM conversion with sign extension

Metadata:
- Fixed RwLock usage - write().await returns guard directly, no Result
- Added `let _` for Future return values

Testing:
 All 7 unit tests pass (cache FIFO behavior)
 RadioParadiseStreamSource compiles successfully with pmoaudio feature
2025-11-05 05:57:52 +00:00
Claude
dbfa392429 fix: Partial test corrections for RadioParadiseStreamSource
Fixed test issues:
- Changed EventId(i) to plain i (EventId is type alias for u64)
- Added create_test_client() helper using RadioParadiseClient::with_client()
- Cast DEFAULT_CHUNK_DURATION_MS to u32 as expected by constructor

Outstanding API incompatibility issues:
- AudioSegment API has evolved (new_audio/new_sync no longer exist)
- AudioChunkData::from_interleaved() doesn't exist
- I24::from_i32() should be I24::new() or I24::new_clamped()
- Need to understand current pmoaudio API for creating audio segments

Tests compile but RadioParadiseStreamSource implementation needs
significant refactoring to match current pmoaudio API.
2025-11-05 05:50:29 +00:00
Claude
fc5b0288b7 test: Add comprehensive unit tests for RadioParadiseStreamSource cache
Added 8 unit tests covering cache FIFO behavior:

1. test_cache_fifo_basic - Verify basic cache operation with 5 elements
2. test_cache_fifo_exactly_10_elements - Verify behavior at capacity limit
3. test_cache_fifo_eviction_oldest - Verify oldest element evicted on overflow
4. test_cache_fifo_multiple_evictions - Verify multiple sequential evictions
5. test_cache_never_exceeds_capacity - Critical test: 100 insertions, never exceeds 10
6. test_cache_fifo_order_preserved - Verify FIFO order (front=oldest, back=newest)
7. test_block_queue_push - Verify block queue management

Tests validate:
- VecDeque capacity never exceeded (while loop correctness)
- Oldest elements evicted first (FIFO ordering)
- Cache maintains exactly ≤10 elements at all times
- Pre-allocated capacity of 10 is respected

Note: Tests require 'pmoaudio' feature which depends on libsoxr system library
2025-11-05 05:44:53 +00:00
Claude
17dec3351e fix: Use while loop instead of if for robust cache size guarantee
Problem:
- With `if >= CACHE_SIZE`, only ONE element removed per call
- If cache ever had >10 elements (abnormal state), would stay oversized
- Example: 12 elements → if removes 1 → 11 elements → add 1 → 12 elements 

Solution:
- Use `while >= CACHE_SIZE` to remove ALL excess elements
- Example: 12 elements → while removes 2 → 10 elements → add 1 → 10 elements 
- Guarantees exactly ≤10 elements regardless of initial state

Changes:
- mark_block_downloaded(): changed `if` to `while`
- Updated comment to reflect "tous les éléments excédentaires"
- Documentation updated with robustness guarantee
2025-11-05 05:35:09 +00:00
Claude
ec61af7b71 fix: Prevent cache from exceeding pre-allocated capacity
Problem:
- Previous logic: push_back() first, then pop_front() if len > 10
- This temporarily creates 11 elements, exceeding VecDeque capacity of 10
- Wastes the benefit of with_capacity() pre-allocation

Solution:
- Check capacity BEFORE adding: if len >= 10, pop_front() first
- Then push_back() new element
- Guarantees never exceeding 10 elements at any time

Changes:
- mark_block_downloaded(): inverted order (pop before push)
- Changed condition from `> CACHE_SIZE` to `>= CACHE_SIZE`
- Documentation updated with correct logic and benefits
2025-11-05 05:33:36 +00:00