# Annexe : Noyau - audio.py

## Rôle et utilité
Le script `core/audio.py` agit comme un traducteur entre l'interface Python de GrimOS et le serveur de son natif de Linux (généralement ALSA via les commandes `amixer`). Il permet à la barre des tâches de lire le volume actuel et de le modifier.

## Implémentation technique

*   **Utilisation de `subprocess`** : Le fichier ne s'appuie sur aucune bibliothèque externe complexe (comme *PyAudio* ou *PulseAudio-libs*). Il exécute directement des commandes shell avec `subprocess.run()`.
*   **Lecture du volume** : Pour connaître le volume actuel, il lance `amixer sget Master`. Il découpe ensuite la réponse texte (parsing) pour trouver une valeur en pourcentage (ex: `[50%]`) et l'état de la sourdine (`[on]` ou `[off]`).
*   **Modification du volume** : Lorsqu'on ajuste la jauge, il convertit la valeur en commande `amixer sset Master X%`.
*   **Mode muet (Mute)** : Il propose une fonction de bascule (`toggle_mute`) qui utilise la commande `amixer sset Master toggle`.

## Pistes de modification

*   **Support de PulseAudio/PipeWire** : Si le système sous-jacent évoluait vers un serveur de son plus moderne, c'est ce fichier qu'il faudrait modifier pour utiliser `pactl` ou `wpctl` au lieu d'`amixer`.
*   **Sélecteur de sortie** : Un développeur pourrait enrichir ce module pour lister les différentes cartes sons branchées (Haut-parleurs, Casque Bluetooth) via la commande `aplay -l` et permettre à l'utilisateur de choisir sa sortie audio depuis la barre des tâches.


## Code Source

```python
import subprocess
import re
import threading

class AudioManager:
    def __init__(self, desktop):
        self.desktop = desktop
        self.control_name = self._find_master_control()

    def _find_master_control(self):
        """Tente de trouver le contrôle principal ALSA (Master, PCM, ou autre)."""
        try:
            # Chercher le contrôle 'Master' en premier
            res = subprocess.run(['amixer', 'sget', 'Master'], capture_output=True, text=True)
            if res.returncode == 0:
                return 'Master'
            
            # Sinon, lister tous les contrôles scontrols
            res = subprocess.run(['amixer', 'scontrols'], capture_output=True, text=True)
            if "PCM" in res.stdout:
                return "PCM"
            
            # Prendre le premier dispo
            match = re.search(r"Simple mixer control '([^']+)'", res.stdout)
            if match:
                return match.group(1)
        except Exception:
            pass
        return 'Master' # Valeur par défaut de secours

    def get_volume(self):
        try:
            res = subprocess.run(['amixer', 'sget', self.control_name], capture_output=True, text=True)
            if res.returncode == 0:
                # Chercher par ex: [50%]
                match = re.search(r"\[(\d+)%\]", res.stdout)
                if match:
                    return int(match.group(1))
        except Exception:
            pass
        return 0

    def set_volume(self, level):
        try:
            subprocess.run(['amixer', 'sset', self.control_name, f'{level}%', 'unmute'], capture_output=True)
            # Démuter spécifiquement les haut-parleurs au cas où
            subprocess.run(['amixer', 'sset', 'Speaker', 'unmute'], capture_output=True)
        except Exception:
            pass

    def test_audio(self):
        # speaker-test est bloquant, on le lance dans un thread
        def run_test():
            try:
                # Test avec un son wav natif (court et reconnaissable : "Front Center")
                # Si aplay ne trouve pas le fichier, speaker-test fera un bruit rose ou un bip.
                res = subprocess.run(['aplay', '/usr/share/sounds/alsa/Front_Center.wav'], capture_output=True)
                if res.returncode != 0:
                    # Fallback sur speaker-test
                    subprocess.run(['speaker-test', '-t', 'sine', '-f', '440', '-c', '1', '-l', '1'], timeout=2, capture_output=True)
            except Exception:
                pass
                
        threading.Thread(target=run_test, daemon=True).start()
```
