343 lines
9.0 KiB
Markdown
343 lines
9.0 KiB
Markdown
|
|
# PMOAudio - Refactoring Summary
|
||
|
|
|
||
|
|
## Vue d'ensemble
|
||
|
|
|
||
|
|
Refactoring complet du système audio pour supporter plusieurs types de samples (entiers et flottants) avec une architecture générique optimisée pour le temps réel.
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
### Option choisie: Générique + Enum plat
|
||
|
|
|
||
|
|
- **`AudioChunkData<T: Sample>`**: Structure générique pour factoriser le code
|
||
|
|
- **`AudioChunk`**: Enum plat avec 6 variants (I8, I16, I24, I32, F32, F64)
|
||
|
|
- **`Sample` trait**: Interface unifiée pour tous les types de samples
|
||
|
|
|
||
|
|
## Nouveaux fichiers créés
|
||
|
|
|
||
|
|
### 1. `src/sample_types.rs`
|
||
|
|
Définition du trait `Sample` et du type `I24` (24-bit audio).
|
||
|
|
|
||
|
|
**Features principales:**
|
||
|
|
- Type `I24` wrapper sur `i32` avec validation de plage (±2^23)
|
||
|
|
- Trait `Sample` implémenté pour: i8, i16, I24, i32, f32, f64
|
||
|
|
- Conversions normalisées vers/depuis f64 et f32
|
||
|
|
- Tests unitaires complets
|
||
|
|
|
||
|
|
### 2. `src/conversions.rs`
|
||
|
|
Module complet de conversions entre tous les types audio.
|
||
|
|
|
||
|
|
**Features principales:**
|
||
|
|
- **Conversions Int → Int**: Utilise `bitdepth_change_stereo` avec SIMD
|
||
|
|
- **Conversions Int → Float**: Utilise `i32_stereo_to_pairs_f32` avec SIMD
|
||
|
|
- **Conversions Float → Int**: Utilise `pairs_f32_to_i32_stereo` avec SIMD
|
||
|
|
- **Conversions Float → Float**: Direct avec cast
|
||
|
|
- **34 implémentations From/Into** pour conversions ergonomiques
|
||
|
|
- Tests de round-trip et validation
|
||
|
|
|
||
|
|
**Point clé**: Les conversions I32 ↔ F32/F64 n'ont **pas besoin** de paramètre BitDepth car le type définit lui-même sa résolution (I32 = ±2^31).
|
||
|
|
|
||
|
|
### 3. `src/macros.rs`
|
||
|
|
Macros pour simplifier la manipulation des AudioChunk et AudioSegment.
|
||
|
|
|
||
|
|
**Macros disponibles:**
|
||
|
|
- `extract_chunk_data!(chunk, TYPE)` - Extrait les données typées
|
||
|
|
- `match_chunk!(chunk, data => expr)` - Pattern matching unifié
|
||
|
|
- `map_chunk!(chunk, data => transform)` - Transformation préservant le type
|
||
|
|
- `is_chunk_type!(chunk, TYPE)` - Prédicat de type
|
||
|
|
- `extract_audio_chunk!(segment)` - Extrait AudioChunk d'un segment
|
||
|
|
- `extract_sync_marker!(segment)` - Extrait SyncMarker d'un segment
|
||
|
|
- `match_segment!(segment, chunk => ..., marker => ...)` - Match sur segment
|
||
|
|
|
||
|
|
**Tests**: 7 tests unitaires
|
||
|
|
|
||
|
|
## Fichiers modifiés
|
||
|
|
|
||
|
|
### 1. `src/audio_chunk.rs` - Refactoring complet
|
||
|
|
|
||
|
|
**Avant:**
|
||
|
|
```rust
|
||
|
|
pub struct AudioChunk {
|
||
|
|
stereo: Arc<[[i32; 2]]>,
|
||
|
|
sample_rate: u32,
|
||
|
|
bit_depth: BitDepth,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Après:**
|
||
|
|
```rust
|
||
|
|
pub struct AudioChunkData<T: Sample> {
|
||
|
|
stereo: Arc<[[T; 2]]>,
|
||
|
|
sample_rate: u32,
|
||
|
|
gain_db: f64, // Toujours en dB
|
||
|
|
}
|
||
|
|
|
||
|
|
pub enum AudioChunk {
|
||
|
|
I8(Arc<AudioChunkData<i8>>),
|
||
|
|
I16(Arc<AudioChunkData<i16>>),
|
||
|
|
I24(Arc<AudioChunkData<I24>>),
|
||
|
|
I32(Arc<AudioChunkData<i32>>),
|
||
|
|
F32(Arc<AudioChunkData<f32>>),
|
||
|
|
F64(Arc<AudioChunkData<f64>>),
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Nouvelles méthodes:**
|
||
|
|
- `AudioChunk::to_f32()`, `to_f64()`, `to_i32()` - Conversions de type
|
||
|
|
- `AudioChunk::set_gain_db()` - Modification du gain
|
||
|
|
- `AudioChunk::type_name()` - Nom du type runtime
|
||
|
|
- Implémentations spécialisées pour i32, f32, f64
|
||
|
|
|
||
|
|
**Tests**: 4 tests unitaires
|
||
|
|
|
||
|
|
### 2. `src/audio_segment.rs` - Helpers ergonomiques
|
||
|
|
|
||
|
|
**Nouvelles méthodes d'accès:**
|
||
|
|
- `as_chunk()` - Récupère le AudioChunk
|
||
|
|
- `as_sync_marker()` - Récupère le SyncMarker
|
||
|
|
- `as_track_metadata()` - Extrait les métadonnées de track
|
||
|
|
- `as_error()` - Récupère le message d'erreur
|
||
|
|
|
||
|
|
**Helpers de conversion:**
|
||
|
|
- `to_f32_chunk()` - Convertit vers F32
|
||
|
|
- `to_i32_chunk()` - Convertit vers I32
|
||
|
|
|
||
|
|
**Helpers de propriétés:**
|
||
|
|
- `sample_rate()` - Sample rate du chunk
|
||
|
|
- `frame_count()` - Nombre de frames
|
||
|
|
- `gain_db()` - Gain en dB
|
||
|
|
- `chunk_type_name()` - Type du chunk
|
||
|
|
|
||
|
|
**Manipulation du gain:**
|
||
|
|
- `with_gain_db(gain_db)` - Nouveau segment avec gain absolu
|
||
|
|
- `adjust_gain_db(delta_db)` - Nouveau segment avec gain relatif
|
||
|
|
|
||
|
|
**Tests**: 4 tests unitaires
|
||
|
|
|
||
|
|
### 3. `src/dsp/int_float.rs` - Simplification
|
||
|
|
|
||
|
|
**Changements:**
|
||
|
|
- ❌ Suppression du trait `BitDepthType` obsolète
|
||
|
|
- ❌ Suppression des types `Bit8`, `Bit16`, `Bit24`, `Bit32`
|
||
|
|
- ✅ Utilisation de l'enum `BitDepth` du module principal
|
||
|
|
- ✅ Fonctions SIMD préservées et optimisées
|
||
|
|
- ✅ Paramètres runtime au lieu de génériques
|
||
|
|
|
||
|
|
### 4. `src/dsp/resampling.rs` - Mise à jour BitDepth
|
||
|
|
|
||
|
|
**Changements:**
|
||
|
|
- Type `ResamplingError` créé (remplace `AudioError` manquant)
|
||
|
|
- `Resampler.bit_depth: u32` → `BitDepth`
|
||
|
|
- Match sur les variants d'enum au lieu de valeurs numériques
|
||
|
|
- Qualité de resampling adaptée au bit depth (VeryHigh pour 24/32-bit)
|
||
|
|
|
||
|
|
### 5. `src/lib.rs` - Exports et organisation
|
||
|
|
|
||
|
|
**Ajouts:**
|
||
|
|
- `mod macros` avec `#[macro_use]`
|
||
|
|
- `pub use sample_types::{I24, Sample}`
|
||
|
|
- `pub use audio_segment::_AudioSegment` (pour les macros)
|
||
|
|
- `pub mod conversions`
|
||
|
|
|
||
|
|
**Temporairement désactivé:**
|
||
|
|
- `mod nodes` (commenté)
|
||
|
|
|
||
|
|
## Statistiques de tests
|
||
|
|
|
||
|
|
### Tests réussis: **35/35** ✅
|
||
|
|
|
||
|
|
**Répartition:**
|
||
|
|
- `audio_chunk`: 4 tests
|
||
|
|
- `audio_segment`: 4 tests
|
||
|
|
- `conversions`: 12 tests
|
||
|
|
- `macros`: 7 tests
|
||
|
|
- `sample_types`: 5 tests
|
||
|
|
- `events`: 3 tests
|
||
|
|
|
||
|
|
### Couverture des conversions
|
||
|
|
|
||
|
|
**From/Into implémentations: 34 au total**
|
||
|
|
|
||
|
|
- Wrapper conversions (6): AudioChunkData → AudioChunk
|
||
|
|
- I16 ↔ I32 (2)
|
||
|
|
- I24 ↔ I32 (2)
|
||
|
|
- I32 ↔ F32 (2)
|
||
|
|
- I32 ↔ F64 (2)
|
||
|
|
- F32 ↔ F64 (2)
|
||
|
|
- Et toutes les autres combinaisons...
|
||
|
|
|
||
|
|
## Optimisations
|
||
|
|
|
||
|
|
### Performance temps réel
|
||
|
|
- **Objectif**: Audio 192kHz/24-bit stéréo en temps réel
|
||
|
|
- **SIMD**: Toutes les conversions critiques utilisent les fonctions SIMD du module DSP
|
||
|
|
- **Zero-copy**: Partage via `Arc<[[T; 2]]>`
|
||
|
|
- **Lazy evaluation**: Le gain n'est appliqué que lors de la lecture des frames
|
||
|
|
|
||
|
|
### Harmonisation du gain
|
||
|
|
- ✅ **Tous les gains en dB** (décibels)
|
||
|
|
- ✅ Helpers de conversion: `db_to_linear()`, `linear_to_db()`
|
||
|
|
- ❌ Plus d'interfaces linéaires (sauf helpers de conversion)
|
||
|
|
|
||
|
|
## Exemple d'utilisation
|
||
|
|
|
||
|
|
Voir [`examples/audio_chunk_api.rs`](examples/audio_chunk_api.rs) pour une démonstration complète.
|
||
|
|
|
||
|
|
### Création rapide
|
||
|
|
```rust
|
||
|
|
// Chunk I32
|
||
|
|
let chunk = AudioChunkData::new(
|
||
|
|
vec![[1000i32, 2000i32]],
|
||
|
|
48000,
|
||
|
|
0.0
|
||
|
|
);
|
||
|
|
|
||
|
|
// Segment avec gain
|
||
|
|
let segment = AudioSegment::new_chunk_with_gain_db(
|
||
|
|
0, 0.0,
|
||
|
|
vec![[1000i32, 2000i32]],
|
||
|
|
48000,
|
||
|
|
BitDepth::B32,
|
||
|
|
6.0 // +6 dB
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Conversions
|
||
|
|
```rust
|
||
|
|
// Via méthodes
|
||
|
|
let chunk_f32 = audio_chunk.to_f32();
|
||
|
|
|
||
|
|
// Via From/Into
|
||
|
|
let chunk_i32: Arc<AudioChunkData<i32>> = (&*chunk_i16).into();
|
||
|
|
```
|
||
|
|
|
||
|
|
### Macros
|
||
|
|
```rust
|
||
|
|
// Type checking
|
||
|
|
if is_chunk_type!(&chunk, I32) {
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pattern matching universel
|
||
|
|
match_chunk!(&chunk, data => {
|
||
|
|
println!("{} frames", data.len());
|
||
|
|
});
|
||
|
|
|
||
|
|
// Transformation
|
||
|
|
let with_gain = map_chunk!(&chunk, data => {
|
||
|
|
data.set_gain_db(6.0)
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### Helpers AudioSegment
|
||
|
|
```rust
|
||
|
|
// Accès ergonomique
|
||
|
|
if let Some(sr) = segment.sample_rate() {
|
||
|
|
println!("Sample rate: {}", sr);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Manipulation du gain
|
||
|
|
let louder = segment.adjust_gain_db(3.0)?;
|
||
|
|
|
||
|
|
// Conversion
|
||
|
|
let f32_chunk = segment.to_f32_chunk()?;
|
||
|
|
```
|
||
|
|
|
||
|
|
## Points clés de design
|
||
|
|
|
||
|
|
### 1. Type = Résolution
|
||
|
|
Chaque type définit sa propre résolution:
|
||
|
|
- I8 = ±2^7 (128)
|
||
|
|
- I16 = ±2^15 (32,768)
|
||
|
|
- I24 = ±2^23 (8,388,608)
|
||
|
|
- I32 = ±2^31 (2,147,483,648)
|
||
|
|
- F32 / F64 = normalisé [-1.0, 1.0]
|
||
|
|
|
||
|
|
**Conséquence**: Pas besoin de paramètre `BitDepth` pour les conversions I32 ↔ Float.
|
||
|
|
|
||
|
|
### 2. Gain toujours en dB
|
||
|
|
- Plus de gains linéaires dans l'API principale
|
||
|
|
- Conversions disponibles via helpers si nécessaire
|
||
|
|
- Évaluation paresseuse du gain
|
||
|
|
|
||
|
|
### 3. Immutabilité
|
||
|
|
- Toutes les modifications créent de nouvelles instances
|
||
|
|
- Partage efficace via `Arc`
|
||
|
|
- Pas de copy-on-write nécessaire pour les données audio
|
||
|
|
|
||
|
|
### 4. Stéréo strict
|
||
|
|
- Format fixe: `[[T; 2]]` (gauche, droite)
|
||
|
|
- Pas de support multicanal pour l'instant
|
||
|
|
- Optimisé pour le cas d'usage principal
|
||
|
|
|
||
|
|
## Compilation et tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Build
|
||
|
|
cargo build --package pmoaudio
|
||
|
|
|
||
|
|
# Tests
|
||
|
|
cargo test --package pmoaudio --lib
|
||
|
|
|
||
|
|
# Exemple
|
||
|
|
cargo run --package pmoaudio --example audio_chunk_api
|
||
|
|
```
|
||
|
|
|
||
|
|
**Statut**: ✅ Compilation sans erreur, tous les tests passent
|
||
|
|
|
||
|
|
## Travail futur (optionnel)
|
||
|
|
|
||
|
|
Les tâches suivantes ont été identifiées mais ne sont pas critiques:
|
||
|
|
|
||
|
|
1. **Benchmark temps réel 192kHz/24-bit**
|
||
|
|
- Valider les performances en conditions réelles
|
||
|
|
- Mesurer l'overhead des conversions
|
||
|
|
|
||
|
|
2. **Macros avancées**
|
||
|
|
- Macros procédurales pour génération de code
|
||
|
|
- DSL pour pipelines audio
|
||
|
|
|
||
|
|
3. **Support multicanal**
|
||
|
|
- Format `[[T; N]]` générique
|
||
|
|
- Gestion des configurations surround
|
||
|
|
|
||
|
|
4. **Réactivation des Nodes**
|
||
|
|
- Mise à jour avec la nouvelle API
|
||
|
|
- Tests d'intégration complets
|
||
|
|
|
||
|
|
## Notes de migration
|
||
|
|
|
||
|
|
Pour le code existant utilisant l'ancienne API:
|
||
|
|
|
||
|
|
### AudioChunk
|
||
|
|
**Avant:**
|
||
|
|
```rust
|
||
|
|
let chunk = AudioChunk::new(stereo, 48000, BitDepth::B32);
|
||
|
|
let gain = chunk.gain_linear();
|
||
|
|
```
|
||
|
|
|
||
|
|
**Après:**
|
||
|
|
```rust
|
||
|
|
let chunk_data = AudioChunkData::new(stereo, 48000, 0.0);
|
||
|
|
let chunk = AudioChunk::I32(chunk_data);
|
||
|
|
let gain = chunk.gain_linear(); // Toujours disponible
|
||
|
|
```
|
||
|
|
|
||
|
|
### AudioSegment
|
||
|
|
**Avant:**
|
||
|
|
```rust
|
||
|
|
segment.chunk.sample_rate
|
||
|
|
```
|
||
|
|
|
||
|
|
**Après:**
|
||
|
|
```rust
|
||
|
|
segment.sample_rate().unwrap() // Avec helper
|
||
|
|
// ou
|
||
|
|
segment.as_chunk().unwrap().sample_rate() // Direct
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Date**: 2025-11-01
|
||
|
|
**Version**: PMOAudio 0.1.0
|
||
|
|
**Status**: ✅ Refactoring complet, tous les tests passent
|