Contrôle du volume par application sous Windows (comme SteelSeries Sonar), avec à terme un boîtier matériel physique pour piloter les canaux.
POC en C++ natif : binaire léger, aucun runtime, empreinte minimale.
Une interface unique (panneau Win32, aucun terminal) qui réunit tout :
- bouton Démarrer / Arrêter le moteur ;
- quatre pages (boutons Canaux / Applications / Égaliseur / Matériel) ;
- page Canaux : canaux dynamiques (bouton + Ajouter, Suppr par canal), renommables, chacun avec une sortie réelle optionnelle, un volume avec boost, un bouton Muet et un vumètre ;
- page Applications : seules les applis qui jouent réellement du son (sessions actives) sont listées ; chacune a son volume avec boost, son bouton Muet, son vumètre, et un menu pour l'envoyer vers un canal OU directement vers un périphérique ;
- page Égaliseur (façon SteelSeries) : un égaliseur paramétrique 10 bandes (chaque bande : activée, fréquence, gain, Q) avec sa courbe de réponse dessinée en temps réel (dB affichés). Points déplaçables à la souris sur la courbe (glisser = fréquence + gain), Q à la molette, plus des sliders de gain et un éditeur clavier. Il s'applique à la cible choisie : un canal OU une appli ;
- règle anti-doublon : si une appli est envoyée vers un canal, seul l'EQ du canal s'applique (l'EQ de l'appli est ignoré). Une appli envoyée vers un périphérique utilise son propre EQ. Une appli sans cible reste audible sur ton ancienne sortie par défaut ;
- les basses/aigus simples ont été retirés (doublon avec l'égaliseur) ; il reste un volume rapide par app/canal ;
Les sons système de Windows (notifications, « ding » du volume) n'appartiennent à aucun processus — leur session rapporte le PID 0. Ils ne peuvent donc pas être capturés individuellement, et Windows refuse de leur assigner une sortie dédiée, contrairement aux applications ordinaires.
Le mode de captation, page Settings, décide de leur sort :
| Mode | Sons système | Applications |
|---|---|---|
| Bascule du périphérique par défaut (coché, historique) | ❌ muets : tout tombe dans le câble et rien ne le rejoue | routées automatiquement |
| Assignation manuelle (décoché) | ✅ audibles sur la vraie sortie | seules celles que vous envoyez vers le câble sont routées |
En mode manuel, la vraie sortie reste le périphérique par défaut. Vous envoyez chaque application à traiter vers CABLE Input depuis Réglages → Système → Son → Mélangeur de volume (bouton d'accès direct dans Settings). Son son brut tombe alors dans le câble — silencieux — pendant que SoundMapping la capture et rend la version traitée sur son canal.
SoundMapping n'assigne que les applications qui jouent réellement dans le câble : une application restée sur la sortie réelle est ignorée plutôt que routée, ce qui l'aurait fait entendre deux fois. L'énumération porte donc sur tous les périphériques actifs, et non sur le seul périphérique par défaut.
L'assignation est automatique sous Windows 11. Windows n'expose aucune API publique, mais une interface WinRT interne existe —
Windows.Media.Internal.AudioPolicyConfig, celle qu'utilise le panneau Son lui-même, et en production depuis des années dans EarTrumpet et SoundSwitch. Elle prend un PID, sans chemin ni hachage à reconstruire. Elle n'est pas documentée : son vtable est donc reproduit au slot près (19 méthodes réservées précèdent celles qui nous intéressent) et l'appel est verrouillé aux builds ≥ 21390. En deçà, l'assignation reste manuelle.Pourquoi pas un routage des sons système vers un canal : il faudrait retirer le son brut des applications du mélange du câble. La seule prise disponible est le mute de session, et Windows l'applique avant la capture par processus : couper une application coupe aussi sa capture, donc tout son. Essayé, mesuré, abandonné. SteelSeries Sonar l'évite en installant ses propres périphériques virtuels signés, un par canal.
Un profil est une configuration complète — canaux, routages, volumes, EQ, cibles des sliders, port série. Page Settings : choisir dans la liste puis Charger, ou saisir un nom et Enregistrer. De quoi basculer entre un réglage « Jeu » et un réglage « Film » sans tout refaire.
Les profils sont des fichiers %APPDATA%\SoundMapping\profils\<nom>.cfg, au
même format que settings.cfg. Le profil actif est mémorisé, et charger un
profil réécrit aussitôt la configuration courante pour qu'un arrêt brutal ne
fasse pas revenir l'ancienne. Les noms sont assainis avant de devenir des noms
de fichiers : un nom contenant ..\ ou : ne peut pas écrire hors du dossier.
Page Égaliseur, liste Préréglage : Plat, Basses, Voix, Loudness, Aigus. Un préréglage remet aussi fréquence et Q à leurs valeurs par défaut — l'appliquer après avoir déplacé des points à la souris donnerait sinon un résultat imprévisible.
Les curseurs de volume vont de 0 à 400 % (repère de graduation sur 100 % = gain unité), par application et par canal. Les deux étages se multipliant, le boost total peut atteindre +24 dB. La valeur est affichée en pourcentage et en dB, parce que c'est le dB qui correspond à ce que l'oreille perçoit : 200 % ne fait que +6 dB.
Au-delà de 0 dBFS, un échantillon serait écrêté brutalement par le mixeur
Windows — une saturation sale et fatigante. Un limiteur doux
(src/dsp/SoftClip.h) s'interpose : identité exacte sous le seuil (un signal
qui ne sature pas n'est pas coloré), puis compression progressive qui tend vers
0 dBFS sans jamais le dépasser. La fonction et sa dérivée sont continues au
seuil, donc pas de cassure audible à l'entrée en limitation.
Le vumètre sous chaque curseur affiche la crête sur une échelle en dB (−60 dB à gauche, 0 dB à droite) : vert en usage normal, orange au-dessus de −6 dB, et une pastille rouge à droite quand le limiteur travaille. C'est le retour sans lequel on pousserait le gain à l'aveugle.
Le boost amplifie aussi le bruit de fond du signal source. Si une application est trop faible, baisser son volume dans l'application puis compenser ici donne un moins bon résultat que l'inverse.
- page Matériel : associe un boîtier série (ex. COM3) qui envoie
sld<n>:<0-100>, avecnde 1 à 4 (numérotation du boîtier ; en interne les sliders sont indexés à partir de 0, la conversion se fait dansSerialReader). Port + baud réglables, bouton Connecter, et 4 lignes pour mapper chaque slider physique vers le volume d'un canal ou d'une appli (la valeur reçue déplace le volume en direct) ; - liste qui se rafraîchit automatiquement quand une appli apparaît/disparaît ;
- bascule du périphérique par défaut : au Démarrer, CABLE devient la sortie
Windows par défaut (tout le son entre dans l'app) ; à l'Arrêter/fermeture, la
sortie réelle sélectionnée redevient le défaut (via l'API
IPolicyConfig) ; - réglages sauvegardés par application dans
%APPDATA%\SoundMapping\settings.cfg: ils reviennent au prochain lancement ; - tâche de fond : fermer ou minimiser la fenêtre la réduit dans la zone de notification (le moteur continue de tourner). Clic-droit sur l'icône → Ouvrir / Démarrer-Arrêter / Quitter ; double-clic → rouvrir.
Pourquoi pas un service Windows : un service tourne en session 0, isolée — il ne voit ni les sessions audio de l'utilisateur ni le périphérique par défaut. Le bon modèle (SteelSeries GG, EarTrumpet…) est une app utilisateur en arrière-plan avec icône tray.
Sous le capot : chaque appli est capturée séparément (process loopback,
MixEngine → AppAudioRouter), passe dans son EQ (ChannelEq) + volume, et est
rendue sur la sortie choisie. Windows mixe les flux automatiquement.
Les outils console (RouterTest, VirtualRoute, AppEq) restent disponibles
comme cibles séparées pour le debug.
- C++17 + Win32 (GUI native, zéro dépendance externe).
- Windows Core Audio (WASAPI / COM) pour le volume par application.
- Windows 10/11.
Pour utiliser SoundMapping : rien, l'exécutable est natif et sans dépendance.
Pour compiler en local uniquement (inutile si l'on passe par GitHub Actions, voir Compiler — Option A) :
- Visual Studio 2022 avec la charge de travail « Développement Desktop en C++ » (ou Build Tools for Visual Studio + CMake) ;
- Inno Setup 6 pour fabriquer l'installeur.
Aucun outil de développement n'est nécessaire sur la machine : chaque push
déclenche .github/workflows/build.yml, qui compile avec MSVC et fabrique
l'installeur sur un runner windows-latest. Deux artefacts sont téléchargeables
depuis l'onglet Actions du dépôt :
| Artefact | Contenu |
|---|---|
SoundMapping-Setup |
SoundMapping-Setup-<version>.exe (installeur) |
SoundMapping-portable |
les .exe seuls, sans installation |
Un tag v* attache en plus l'installeur à la release GitHub.
build.batCompile en Release x64 puis, si Inno Setup 6 est installé, fabrique
l'installeur dans installer\output\.
cmake -S . -B build -A x64
cmake --build build --config ReleaseBinaire produit : build\bin\SoundMapping.exe
installer/SoundMapping.iss (Inno Setup 6) produit un installeur qui :
- installe pour l'utilisateur courant, dans
%LOCALAPPDATA%\Programs\SoundMapping, sans UAC — le démarrage automatique et la configuration vivent tous les deux dans le profil de l'utilisateur, et une élévation les écrirait dans celui du compte administrateur. Installation machine si besoin :SoundMapping-Setup.exe /ALLUSERS; - pose le raccourci directement dans le menu Démarrer (pas dans un sous-dossier, pour qu'il sorte à la recherche et s'épingle d'un clic droit), plus un raccourci bureau en option ;
- propose le lancement au démarrage de Windows : clé
RundeHKCU, avec l'option--traypour démarrer replié dans la zone de notification ; - conserve la configuration (
%APPDATA%\SoundMapping\settings.cfg) lors d'une mise à jour comme d'une désinstallation.
Le numéro de version vit à trois endroits, à garder synchronisés :
| Fichier | Ce qu'il porte |
|---|---|
CMakeLists.txt |
project(... VERSION x.y.z) |
src/resources/app.rc |
SM_VERSION / SM_VERSION_STR (l'exe) |
installer/SoundMapping.iss |
AppVersion par défaut |
Une fois les trois à jour et poussés :
git tag -a v1.2.0 -m "SoundMapping 1.2.0" && git push origin v1.2.0Le tag déclenche la CI, qui injecte 1.2.0 dans l'installeur
(/DAppVersion) et attache l'installeur + l'exécutable portable à une release
GitHub — lien de téléchargement permanent, contrairement aux artefacts
d'Actions qui expirent au bout de 90 jours.
Lancer le nouvel installeur par-dessus suffit : l'AppId étant stable, Windows
y voit le même produit et met à jour au lieu de créer une seconde entrée. Le
dossier et les options choisis la fois précédente sont réutilisés sans reposer
les questions.
L'application en cours d'exécution est gérée par le Restart Manager de
Windows (CloseApplications=force) : il la ferme proprement — elle enregistre
sa configuration en traitant WM_QUERYENDSESSION —, remplace l'exécutable, puis
la relance. Volontairement pas d'AppMutex, qui se serait contenté d'afficher
« fermez l'application puis cliquez OK ».
Décocher « Lancer au démarrage de Windows » lors d'une mise à jour ne supprime
pas la clé Run : le même réglage est exposé dans le menu de la zone de
notification, et l'installeur ne doit pas revenir sur ce choix. La clé est
nettoyée à la désinstallation.
Compilation manuelle de l'installeur :
"%ProgramFiles(x86)%\Inno Setup 6\ISCC.exe" /DBinDir=build\bin installer\SoundMapping.issLes réglages (canaux, routages, volumes, EQ, port série, cibles des sliders) sont écrits dans :
%APPDATA%\SoundMapping\settings.cfg
Ils sont enregistrés automatiquement à chaque modification, ainsi qu'à la fermeture de la fenêtre et à l'arrêt / redémarrage de Windows. L'écriture est atomique : une coupure en pleine sauvegarde ne peut pas corrompre le fichier. Pour repartir de zéro, fermer l'application puis supprimer ce fichier.
Recharger les réglages ne suffit pas : l'état de marche en fait partie.
- Si le moteur tournait à la fermeture, il redémarre tout seul au lancement suivant ; sinon il reste à l'arrêt.
- Si le port série était connecté, il est rouvert automatiquement. Un adaptateur USB n'étant pas toujours prêt à l'ouverture de session, la connexion est retentée pendant ~30 s.
- Les périphériques audio qui apparaissent après l'application (service audio, DAC USB, câble virtuel) sont détectés à chaud : la liste des sorties se met à jour sans avoir à relancer quoi que ce soit.
Le menu de la zone de notification expose « Lancer au démarrage de Windows »
(même clé Run que celle écrite par l'installeur).
SoundMapping/
├── CMakeLists.txt
├── build.bat # compile + fabrique l'installeur
├── assets/
│ ├── soundmapping.ico # icone de l'appli (toutes tailles)
│ └── make_icon.py # regenere le .ico depuis le dessin
├── installer/SoundMapping.iss # script Inno Setup 6
└── src/
├── main.cpp # WinMain + CoInitialize + instance unique
├── resources/
│ ├── app.rc # icone + infos de version de l'exe
│ └── resource.h
├── audio/
│ ├── AudioSessionManager.{h,cpp} # volume par appli (Core Audio)
│ ├── EndpointEnumerator.{h,cpp} # liste les sorties (id + nom)
│ └── ProcessCapture.{h,cpp} # capture le son d'1 appli (Win11)
├── dsp/
│ ├── Biquad.h # filtre biquad (RBJ), pur C++
│ └── ChannelEq.{h,cpp} # EQ basses/aigus par canal
├── routing/
│ ├── AudioRouter.{h,cpp} # routage loopback -> sortie
│ └── AppAudioRouter.{h,cpp} # capture appli -> EQ+volume -> sortie
├── gui/
│ ├── MixerWindow.{h,cpp} # table de mixage (Win32)
│ └── ControlPanel.{h,cpp} # panneau unifie (canaux/applis/EQ/materiel)
├── hardware/SerialController.{h,cpp} # futur boîtier (stub)
└── tools/
├── router_test.cpp # test du routage (source -> sortie)
├── virtual_route.cpp # route le périph VIRTUEL -> sortie réelle
└── app_eq.cpp # EQ par appli (capture par processus)
Idée directrice : toute la logique audio est dans audio/. La GUI et le
futur hardware ne sont que des « clients » de cette couche. Brancher le boîtier
ne touchera donc pas au coeur.
Objectif final : des périphériques virtuels vers lesquels certaines applis envoient leur son, chacun avec ses réglages, tous mixés vers une sortie réelle sélectionnable.
Créer un périphérique virtuel nécessite un driver noyau signé (voir la
roadmap). On commence donc par la partie réutilisable et sans driver : le
moteur de routage (routing/AudioRouter). Il capture le son d'une sortie en
loopback, applique un volume, et le rejoue sur une autre sortie — avec
rééchantillonnage automatique. Quand le périphérique virtuel existera, seule
l'« id source » changera, le moteur restera identique.
cmake --build build --config Release --target RouterTest
.\build\bin\RouterTest.exeL'outil liste tes sorties, demande une source (capturée en loopback) et une
destination (sortie réelle), puis route le son. Tape un volume 0-100 à la
volée, q pour quitter.
Sans périphérique virtuel pour l'instant, teste avec de vrais périphériques : envoie une appli vers la sortie A (Paramètres Windows → Son), route A → B avec l'outil, et tu entendras le son aussi sur B. Le routage exclusif (son inaudible sur la source) viendra avec le driver virtuel.
Créer un périphérique audio se fait au niveau driver noyau. Plutôt que de le
réécrire, on utilise le driver open-source Virtual-Audio-Driver (dérivé du
sample officiel SYSVAD de Microsoft, licence MIT), puis notre AudioRouter
renvoie son son vers une sortie réelle.
- Désactiver Secure Boot (BIOS/UEFI) — sinon le test-signing ne s'active pas et le driver ne se chargera pas.
- Activer le test-signing (PowerShell admin), puis redémarrer :
Un filigrane « Mode test » apparaît : c'est normal.
bcdedit /set testsigning on - Télécharger la dernière release depuis VirtualDrivers/Virtual-Audio-Driver et la décompresser.
- Device Manager → menu Action → Ajouter un matériel d'ancienne
génération → Installer manuellement → Contrôleurs audio, vidéo et jeu →
Disque fourni… → sélectionner
VirtualAudioDriver.inf. - Vérifier qu'une sortie « Virtual Audio Driver » apparaît dans les Paramètres → Son.
Souci connu sur Windows 11 24H2 (périphérique absent, erreur de signature 52) : voir l'issue #1.
cmake --build build --config Release --target VirtualRoute
.\build\bin\VirtualRoute.exeL'outil détecte automatiquement le périphérique virtuel, te demande la sortie réelle de destination, et lance le routage. Mets ensuite le périphérique virtuel par défaut dans Windows : tout ton son y arrive, puis ressort sur la sortie réelle choisie. Volume réglable en direct.
Avec toutes les apps sur un seul CABLE, leurs sons sont déjà mélangés : on
ne peut pas leur appliquer des effets différents. Pour un EQ par appli, on
capture chaque application séparément grâce à l'API process loopback de
Windows 11 (ProcessCapture), on lui applique son propre EQ (ChannelEq :
basses + aigus), puis on rend le résultat sur la sortie choisie
(AppAudioRouter).
Prérequis : Windows 11 (ou Windows 10 build 20348+) pour la capture par processus.
cmake --build build --config Release --target AppEq
.\build\bin\AppEq.exeChoisis une application (ex. Spotify) et une sortie, puis règle en direct :
b6 # +6 dB de basses
t-3 # -3 dB d'aigus
v80 # volume 80 %
q # quitter
Astuce : mets la sortie de l'appli sur CABLE pour ne pas l'entendre en direct — tu n'entends alors que la version traitée. Le moteur EQ est validé par test (identité à 0 dB, boost basses/aigus ciblés, stabilité).
- POC — mixer logiciel fonctionnel. ✅
- Moteur de routage user-mode (
AudioRouter) — loopback → sortie réelle, avec volume. ✅ (testable viaRouterTest) - EQ par application — capture par processus (
ProcessCapture) + EQ (ChannelEq) + routeur per-app (AppAudioRouter), testable viaAppEq. ✅ - Application unifiée (
SoundMapping.exe) — GUI Win32 :MixEnginegère un canal par appli (volume + basses + aigus) vers une sortie sélectionnable. ✅ - Périphérique virtuel — driver open-source Virtual-Audio-Driver (SYSVAD,
test-signing local) + auto-routage virtuel → sortie réelle (
VirtualRoute). ✅ Plus tard : compiler notre propre driver depuis les sources si besoin. - Notifications de sessions (
IAudioSessionNotification) → mise à jour auto de la liste, sans bouton Rafraîchir. - Icône systray + lancement au démarrage.
⚠️ Une app utilisateur en arrière-plan, pas un service Windows (session 0) : un service ne voit pas les sessions audio de ta session interactive. - Hardware — Arduino + potentiomètres → lecture série →
SerialControllermappe les curseurs sur les volumes. - Persistance des réglages par application.