®om's blog { un blog libre }

Le mixage audio

Que se passe-t-il lorsque nous percevons le son provenant de plusieurs sources audio simultanément, par exemple lorsque plusieurs personnes parlent en même temps ?

Dans la réalité, ce que nous entendons est la somme de chacun des signaux.

Mais si nous voulons mélanger plusieurs pistes audio numériques, nous rencontrons un problème : chaque échantillon d’un signal audio est compris entre une valeur min et une valeur max, disons entre -1 et 1. Pour les mixer, nous ne pouvons donc pas sommer plusieurs signaux comme dans la réalité : le signal résultant doit aussi être compris entre -1 et 1. Comment faire alors ?

En théorie

Les graphes présentés dans les sections suivantes ont été créés avec gnuplot, et les définitions de fonctions sont écrites dans la syntaxe correspondante. Les sources (.gnu) sont disponibles pour chacun des graphes, vous permettant de les manipuler en 3D.

Somme tronquée

La première idée est de sommer les signaux en tronquant le résultat dans l’intervalle [-1; 1]. Pour le mixage de deux sources audio x et y :

mix_sum(x, y) = min(1, max(-1, x + y))

Le résultat sera parfait lorsque |x + y| <= 1. Par contre, dans le reste des cas, nous obtenons du clipping, désagréable à l’oreille.

Visualisons cette fonction :

mix_sum

Les axes horizontaux correspondent à un échantillon de chacune des deux sources audio ; l’axe vertical représente la valeur résultant de la combinaison des deux en utilisant la somme tronquée.

Le clipping correspond aux deux paliers horizontaux du haut et du bas.

Moyenne

Pour éviter tout clipping, il suffirait de moyenner les deux sources audio :

mix_mean(x, y) = (x + y) / 2;

mix_mean

Effectivement, ça fonctionne bien. Mais ce n’est pas forcément le meilleur choix.

Le son résultant va toujours être plus faible que le plus fort des deux sources, et souvent de manière significative. En particulier, si nous mélangeons une source audio quelconque avec un silence, l’amplitude va être divisée par deux.

De plus, la définition va également être divisée par deux : si l’amplitude est codée sur 8 bits, elle peut prendre 256 valeurs. En divisant les signaux par deux, chaque signal aura une définition de 7 bits (128 valeurs).

Ces inconvénients s’agravent lorsqu’il y a plus de deux sources à mélanger.

k × somme

Nous pouvons alors chercher un compromis entre conserver l’amplitude et éviter le clipping. En fait, les fonctions de somme tronquée et de moyenne ne sont que deux cas particuliers de cette fonction :

mix_ksum(k, x, y) = min(1, max(-1, k * (x + y)))

En effet :

mix_sum(x, y) = mix_ksum(1, x, y)
mix_mean(x, y) = mix_ksum(0.5, x, y)

Nous pouvons choisir n’importe quel k entre 0.5 et 1 : plus k est faible, moins le clipping sera probable ; plus k est élevé, plus l’amplitude sera conservée.

Voici le graphe pour k = 0.7 :

mix_ksum

Cette méthode est très utile si nous connaissons à l’avance les sources audio. Par exemple, pour mélanger deux fichiers son, nous pouvons effectuer une première passe pour analyser le max m de la somme des deux signaux, et choisir k < 1/m : cela garantit qu’il n’y aura pas de clipping, et nous pouvons conserver l’amplitude dans la mesure du possible, sans distorsion.

Si ces sources audio nous parviennent en direct (streaming, conversation audio…), nous pouvons choisir un nombre arbitrairement (plus ou moins basé sur l’expérience). Choisir k > 0.5 se justifie car si deux sources audio sont indépendantes, les ajouter ne provoque pas des pics deux fois plus importants (les pics d’un signal vont souvent être compensés par les creux de l’autre).

Fonction non-linéaire

Mais nous pouvons trouver un meilleur compromis grâce à des fonctions non-linéaires.

Dans la première partie de son billet Mixing digital audio, Viktor T. Toth présente une stratégie très intéressante. Il part du principe que le mixage de deux sources audio doit respecter les règles suivantes :

  • si l’une des sources est silencieuse, alors nous voulons entendre l’autre inaltérée ;
  • si les signaux sont de même signe, l’amplitude du résultat (en valeur absolue) doit être supérieure à celle des sources.

Et si les signaux prennent valeur dans [0, 1], la fonction suivante respecte ces contraintes :

vtt(x, y) = x + y - x * y

vtt

Cependant, en réalité, les signaux prennent valeur dans [-1, 1], et cette fonction ne convient pas. L’auteur s’en est rendu compte, mais malheureusement la solution qu’il propose n’est pas appropriée (par exemple, le mixage ne se comporte pas symétriquement si nous inversons le signal).

Nous pouvons extrapoler son idée originale pour la faire fonctionner sur [-1, 1] :

mix_vtt(x, y) = \
    x >= 0 && y >= 0 ? x + y - x * y \
  : x <= 0 && y <= 0 ? x + y + x * y \
  : x + y

Le principe est d’utiliser le symétrique de sa fonction pour la partie négative, et ajouter deux bouts de plans pour les raccords :

mix_vttx

Mais quelque chose saute aux yeux : sa représentation n’est pas lisse (la fonction n’est pas continûment dérivable (C1)). Cela signifie que les variations du résultat en fonction des variations des sources changent brutalement en certains endroits.

Ce n’est pas satisfaisant mathématiquement.

Surface lisse

Et en effet, en y réfléchissant, la fonction souffre de quelques défauts.

Par exemple, si l’une des deux sources audio est à 1, alors si l’autre est positive, elle n’a aucun impact, si elle est négative, elle a un impact linéaire important. Ce n’est rien d’autre qu’un clipping de l’une des deux sources.

Par ailleurs, dans la réalité, le mixage de deux signaux est simplement leur addition. Le résultat devrait donc être invariant si nous ajoutons une constante à une source et la soustrayons à l’autre :

mix(x, y) = mix(x + k, y - k) = x + y

Cette propriété me semble importante : peu importe que le son provienne d’une source ou d’une autre, cela n’intervient pas dans le mixage.

Or, dans la fonction précédente, elle n’est pas respectée. Par exemple :

mix_vttx(0.5, 0.5) = 0.75
min_vttx(0, 1) = 1

Afin de dépasser ce problème, posons cette propriété comme principe : puisque l’identification de l’apport individuel de chaque signal ne compte pas, considérons uniquement leur somme (ou leur moyenne). Ainsi, au lieu d’une fonction à deux variables x et y, nous pouvons utiliser une fonction à une seule variable z = (x + y) / 2 (la moyenne).

Remarquons que nous pouvions déjà exprimer les fonctions linéaires vues précédemment en fonction d’une seule variable. En effet, en posant z = (x + y) / 2, nous obtenons :

sum(z) = max(-1, min(1, 2 * z))
mean(z) = z
ksum(z) = max(-1, min(1, 2 * k * z))

Dans le fond, nous cherchons une fonction qui s’approche de sum pour les amplitudes faibles (pour conserver l’amplitude au mieux) et de mean pour les amplitudes élevées (pour éviter le clipping).

Avec un peu d’imagination, nous pouvons trouver une fonction qui convient parfaitement (pour 2 pistes audio) :

g(z) = z * (2 - abs(z))

Elle se généralise pour n pistes audio :

g(z) = sgn(z) * (1 - (1 - abs(z)) ** n)

abs(x) désigne la valeur absolue de x (|x|), et ** est la fonction puissance (a ** n signifie an)

g

Cette fonction a plein de propriétés intéressantes :

∀x, |g(x)| <= 1
dans le bon intervalle
g(-1) = -1, g(0) = 0 et g(1) = 1
résultats cohérents
∀x (|x| < 1), g’(x) > 0
pas de clipping
∀x, |g(x)| ≤ |sum(x)|
l’amplitude ne dépasse jamais la somme de celle des sources
∀x, |g(x)| ≥ |mean(x)|
l’amplitude est toujours supérieure à la moyenne des sources
∀x, g’(0) = sum’(x) = n
g se comporte comme sum lorsque l’amplitude est faible (elle varie de la même manière)
∀x≠0, x.g’‘(x) < 0
la croissance de g ralentit lorsque l’amplitude augmente (en valeur absolue), donc les fortes amplitudes sont plus compressées que les faibles
∀x, g(-x) = -g(x) (impaire)
comportement symétrique sur un signal inversé
g ∈ C1 (continûment dérivable)
parfaitement lisse

Passons alors en 3 dimensions, et posons :

mix_f(x, y) = g((x + y) / 2)

mix_f

Nous nous apercevons que c’est une version lissée de la fonction précédente :

mix_f_vttx

Cette fonction me semble donc pertinente pour mixer plusieurs flux audio.

En pratique

Bon, jusqu’ici nous avons fait de beaux dessins, c’était rigolo. Maintenant, passons à la pratique, et implémentons les fonctions de mixage en C.

Nous manipulerons uniquement des flux audio brut (des wav sans en-tête), contenant uniquement des échantillons encodés par des entiers signés sur 16 bits, en little endian. Ça peut paraître compliqué comme ça, mais c’est juste le format utilisé pour les cd audio.

Le programme est indifférent au nombre de canaux ou à la fréquence (il mixe les échantillons les uns à la suite des autres), mais bien évidemment les différentes pistes mixées doivent avoir ces paramètres identiques.

Implémentation

Bien que nous n’ayons vu jusqu’ici que le mixage de deux pistes audio (au-delà c’était compliqué de les visualiser sur des graphes), l’implémentation permet de mixer n pistes audio.

Le mixage s’effectue sur un échantillon de chaque piste audio à la fois (il est indépendant des échantillons précédents et suivants). Les fonctions de mixage ont toutes la même signature :

int mix(int n, int samples[]);
n
le nombre de pistes audio
samples
le tableau des n échantillons à mixer

La valeur du retour ainsi que celles des samples[i] tient sur 16 bits (compris entre -32768 et 32767).

À titre d’exemple, voici l’implémentation de la fonction f (celle qui est lisse) :

int mix_f(int n, int samples[]) {
    double z = _dsum(n, samples) / n;
    int sgn = z >= 0 ? 1 : -1;
    double g = sgn * (1 - pow(1 - sgn * z, n));
    return to_int16(g);
}

avec _dsum une fonction qui somme les n samples et to_int16 une fonction qui convertit un flottant compris entre -1 et 1 vers un entier compris entre -32768 et 32767.

Une fonction main s’occupe d’ouvrir les fichiers dont les noms sont passés en paramètres et d’appliquer pour chaque échantillon la fonction de mixage désirée.

Sources

Les sources complètes sont gittées :

git clone http://git.rom1v.com/mixpoc.git

(ou sur github).

Le projet contient :

  • le code du PoC (mixpoc.c) ;
  • un makefile minimaliste (Makefile) ;
  • les sources des graphes gnuplot (*.gnu) ;
  • des scripts utilitaires (voir ci-dessous l’utilisation).

Utilisation

Fichiers raw

Le PoC ne manipule que des fichiers raw. Il est peu probable que vous ayez de tels fichiers sur votre ordinateurs, vous devez donc pouvoir en créer à partir de vos fichiers audio habituels.

Vous aurez besoin de sox et éventuellement avconv ou ffmpeg.

./toraw file.wav file.raw
./toraw file.ogg file.raw
./toraw file.flac file.raw

Pour l’opération inverse :

./rawtowav file.raw file.wav

Si le format n’est pas supporté par sox (comme le mp3), convertissez-le en wav d’abord :

avconv -i file.mp3 file.wav
ffmpeg -i file.mp3 file.wav

Lecture et enregistrement

Il est possible de lire des fichiers raw directement et d’en enregistrer de nouveaux à partir du microphone (pratique pour essayer de mixer une musique avec une conversation).

Le paquet alsa-utils doit être installé (vérifiez que le microphone est bien activé dans alsamixer).

Pour enregistrer :

./record file.raw
./record > file.raw

Pour lire :

./play file.raw
./play < file.raw

Pour lire en direct le son provenant du microphone (à tester avec un casque pour éviter l’effet Larsen) :

./record | ./play

Mixpoc

Pour compiler mixpoc (nécessite make et un compilateur C comme gcc) :

make

Pour l’utiliser, la syntaxe est la suivante :

./mixpoc (sum|mean|ksum|vttx|f) file1 [file2 [...]]

Le résultat sort sur la sortie standard. Ainsi :

./mixpoc f file1.raw file2.raw filen.raw > result.raw

écrit le fichier result.raw.

Pour lire en direct le résultat :

./mixpoc f file1.raw file2.raw filen.raw | ./play

ou plus simplement (grâce au script mix) :

./mix f file1.raw file2.raw filen.raw

Pour ajouter une source silencieuse, vous pouvez utiliser /dev/zero :

./mix f file.raw /dev/zero

Vous avez maintenant tout ce dont vous avez besoin pour tester.

Gnuplot

Pour visualiser les graphes gnuplot, je vous conseille le paquet gnuplot-qt.

Pour les ouvrir :

gnuplot -p file.gnu

La souris ou les flèches du clavier permettent de tourner le graphe en 3 dimensions.

Les commandes nécessaires pour générer une image .png sont écrites en commentaire à l’intérieur du fichier. Je les ai fait commencer par ## pour pouvoir les décommenter automatiquement (sans décommenter le reste) avec un script.

Ainsi, pour générer les fichiers .png :

./gg file.gnu
./gg *.gnu

Conclusion

Pour mixer plusieurs pistes son, la fonction f me semble très bonne, à la fois en théorie et en pratique. Sur les exemples que j’ai testés, le résultat était celui attendu.

Cependant, je n’ai ni du matériel audio ni des oreilles de haute qualité, et mes connaissances en acoustique sont très limitées.

Les critiques sont donc les bienvenues.

Commentaires

dwan

J’ai bien l’impression que tu as construit un compresseur :)

http://fr.wikipedia.org/wiki/Compresseur_%28audio%29

Florian

Tu viens de créer un compresseur/limiteur :)

Bon, en un peu mieux, le tiens a un arrondi du seuil de compression !!

L'éclectique

Bravo cher ®om (permettez la familiarité) pour ce remarquable travail d’analyse. Mais hélas la fonction de mixage que vous proposez aurait été une parfaite solution si elle pouvait être linéaire, ce qui n’est malheureusement pas le cas. Je précise bien linéaire (bilinéaire ou autre ne ferait pas l’affaire), car tout manquement à la sacro-sainte linéarité en audio laisse apparaître (ou plutôt entendre) des distortions, qui dénaturent le signal et peuvent provoquer une rage des tympans chez les audiophiles …

Des distortions ?

Le son étant un phénomène vibratoire, son étude repose principalement sur sa décomposition en vibrations élémentaires. Généralement, la brique de base est l’onde pure sinusoïdale, et la décomposition la plus usitée est la transformée de Fourier. Il en existe d’autres comme la décomposition en ondelettes, où les briques de bases sont des ondelettes.

Le résultat de la décomposition est appelé le spectre: c’est la recette de reconstitution du son par un mélange d’ondes pures.

La fidélité d’un système de reproduction du son (dont le mixeur de sons fait partie) est directment liée à la fidélité de reproduction des ondes pures. Les défauts de reproductions peuvent être multiples :

  • Accentuation, atténuation ou déphasage de certaines fréquences du spectre, mais il y a pire ;
  • Ajout de nouvelles fréquences inexistantes dans le signal à reproduire

Les fonctions non-linéaires excellent dans ce deuxième cas. C’est parfois appréciable comme sur les guitares électriques, mais en général ce n’est pas beau à entendre.

Cette non-linéarité est quantifiable, par exemple sur les amplificateurs audio par ce qu’on nomme Taux de Distortion Harmonique. La course à la linéarité fait que ce taux se mesure sur les équipements de bonne qualité en millième de pourcent (comme ce circuit amplificateur audio, page 3) …

Que des théories, on veut de la pratique !

On peut réaliser un test rapide de la fonction mix_f. Préparons deux sons à mixer :

  • Une onde sinusoïdale à 19kHz et d’amplitude 0,5, sauvé sous le nom de fichier 19khz.raw
  • Une onde sinusoïdale à 20kHz et d’amplitude 0,5, sauvé sous le nom de fichier 20khz.raw

J’ai fait cela sous Audacity, projet à 44100Hz, menu Générer puis Son…. J’ai exporté chaque son en données RAW, puis j’ai lancé mixpoc en faisant :

mixpoc f 19khz.raw 20khz.raw > fmix.raw

J’ai ensuite importé le fichier résultant du mixage. Que remarquer ? Les sons à 19kHz et 20kHz sont situés à la limite de la sensibilité en fréquence de l’oreille humaine. Ils sont donc généralement inaudibles, sauf parfois pour les jeunes gens dont les tympans n’ont pas encore été traumatisés par des MP3 128kbps à 120dB mais passons … Avec un mixage de bonne qualité, et avec une chaîne de rendu correct (une carte son ordinaire avec une paire d’écouteurs ou un casque bon marché suffisent pour l’expérience) on ne devrait rien entendre, mais le fichier fmix.raw présente une tonalité basse, aux alentours de 1kHz, très facilement détectable car l’oreille humaine présente un pic de sensibilité vers cette fréquence. Le mixage sum de mixpoc donne un très bon résultat puisque je n’entends rien quand je reproduis le son chez moi, et le mixage vttx est le plus affreux de tous puisque la distortion est très audible.

On peut visualiser les dégâts de la non-linéarité sur Audacity, en sélectionnant le menu déroulant d’une piste et en choisissant le mode d’affichage Spectre au lieu de Forme d’onde.

A un signal pur correspond une raie unique sur le spectre, le mixage de sons à 19kHz et 20kHz ne devrait donner qu’un signal ayant un spectre formé de deux raies, mais la distorsion introduit une foultitude de raies espacée de 1kHz pour le fichier fmix.raw.

Alors c’est inutile ?

Cette distortion porte un nom précis : l’intermodulation. La fréquence de 1kHz que l’on entend n’est pas quelconque, elle est la différence entre 19kHz et 20kHz. Si par exemple on avait utilisé 18kHz et 18.2kHz, on aurait entendu une intermodulation à 200Hz. Cette distorsion est certes indésirable en audio, mais en revenche c’est la clé de voûte des systèmes de transmission et de communication sans fil. La plupart des récepteurs modernes font intermoduler, dans un circuit électronique appelé mélangeur, le signal reçu par l’antenne avec une onde générée au niveau du recepteur pour obtenir une intermodulation de plus faible fréquence. L’exemple le plus usuel est la radio FM : quand on veut capter une station FM sur 100MHz par exemple, le récepteur radio génère une onde à 110.7Mhz, le résulat du mixage non linéaire est une onde à 10.7Mhz (c’est une valeur standard pour les récepteurs FM) plus facile à traiter car de fréquence plus faible, et qui est spécifiquement attendue à la sortie du mélangeur. Pour les télés analogiques, c’est 38,9MHz, et pour les récepteurs ondes courtes de nos grand-pères, c’est généralement 455kHz.

La fonction non-linéaire la plus usitée en traitement de signal radio-fréquence est la forme bilinéaire f(x,y) = k.x.y (k étant une constante), car le résultat d’intermodulation est simple à étudier et à prévoir (remarquer que pour deux ondes le produit cos(x).cos(y) = 1/2 . (cos(x-y) + cos(x+y)), la différence d’intermodulation étant le terme cos(x-y)).

Certains amateurs d’équipements audio anciens (comme les amplificateurs à lampes) recherchent à reproduire les défauts sur les installations modernes. La fonction mix_f étant quadratique (tracé), elle s’approche de la réponse d’une lampe et pourrait servir à recréer les distorsions de ces équipements.

Faut-il se ruiner en équipement audio ?

Non, les ordinateurs actuels sont capable de prouesses impossibles pour les ingénieurs de l’époque analogique, comme l’explique Chris Montgomery (créateur du codec audio Vorbis) dans son introduction au multimédia (video). La plupart des interfaces audio d’ordinateurs approchent la perfection en rendu, mais l’enregistrement laisse un peu à désirer.

Le monde du logiciel Libre offre Octave (clone du soft propriétaire MatLab) (cette page wiki est malheureusement dépouillée, je la complèterai si le temps s’offre à moi).

Le monde de l’audio est très vaste, mais il reste heureusement très accessible à notre ère. J’espère avoir été utile et ne pas avoir abusé de cet espace de commentaire.

Ah oui, et bravo pour votre blog !

®om

Waoh, ça c’est du commentaire : c’est un article à lui tout seul ;-) Merci infiniment pour les explications.

Ton exemple avec 19kHz et 20kHz illustre très bien le problème. Merci d’avoir pris le temps de tester l’exemple avec le PoC.

Quelques tests

J’ai reproduit tes tests. Au passage, j’ai ajouté un script genfreq qui génère un fichier de 5 secondes d’une fréquence donnée (la même chose que ce que propose Audacity manuellement).

./genfreq 19000 .5 19khz.raw
./genfreq 20000 .5 20khz.raw
./mix f 19khz.raw 20khz.raw

Effectivement, les fréquences apparues sont bien audibles. D’ailleurs, même :

./mix f 19khz.raw 19khz.raw

produit un son audible (le principe de la fonction mix_f est de “déformer” la moyenne des signaux, donc s’il y a deux fois le même signal, le résultat est déformé).

Ceci a le même effet :

./mix f 19khz.raw

Cela signifie que ma fonction g (donc f aussi) n’est “bonne” (mathématiquement parlant, sans parler des distorsions créées) que pour le mixage de 2 pistes audio (avec 1 ou 3, elle ne respecte plus toutes les propriétés énoncées). La distorsion qu’elle produit devrait être dépendante du nombre de sources.

EDIT : c’est corrigé.

En parcourant une plage de fréquences, on entend bien aussi la distorsion de mix_f.

L’original :

./genfreq 5000-10000 .5 - | ./play

Avec la fonction mix_sum :

./genfreq 5000-10000 .5 tmp.raw && ./mix sum tmp.raw tmp.raw

Avec la fonction mix_f :

./genfreq 5000-10000 .5 tmp.raw && ./mix f tmp.raw tmp.raw
mix_sum aussi

Cependant, mix_sum ne s’en sort bien que parce que l’amplitude totale ne dépasse pas 1. Sinon, on obtient ça :

./genfreq 19000 .6 19khz.raw
./genfreq 20000 .6 20khz.raw
./mix sum 19khz.raw 20khz.raw
./mix sum 19khz.raw 19khz.raw
./genfreq 5000-10000 .6 tmp.raw && ./mix sum tmp.raw tmp.raw

C’est encore pire… Seul mix_mean s’en sort dans tous les cas, mais au prix d’une amplitude diminuée. Ce qui me ramène au point de départ.

Pick any two

J’ai l’impression (corrige-moi si je me trompe) que parmi les caractéristiques suivantes, nous devons en choisir au plus deux :

  1. amplitude relativement conservée
  2. pas de hard-clipping
  3. pas de distorsion (en dehors du clipping)

Ou alors il faut faire des compromis (j’accepte de perdre un peu d’amplitude, d’avoir un peu de hard-clipping et un peu de distorsion).

Je n’ai pas d’expérience sur des exemples réels (voix humaine, musique…) : vaut-il mieux perdre la linéarité pour éviter le clipping (l’objectif que poursuit mon billet), ou considérer que la probabilité de clipping est suffisamment faible pour garder la linéarité ?

Concrètement, est-ce qu’il vaut mieux quelque chose comme mix_ksum (on préfère la linéarité sur l’absence totale de clipping) ou comme mix_f (on préfère éviter le clipping même si ça rajoute un peu des fréquences sur tout le spectre) ?

Avec des pistes audio “réelles”, je ne parviens pas à entendre qu’il y a des fréquences ajoutées avec mix_f.

Ou peut-être y’a-t-il de meilleures stratégies ?

dwan

@®om

vaut-il mieux perdre la linéarité pour éviter le clipping (l’objectif que poursuit mon billet), ou considérer que la probabilité de clipping est suffisamment faible pour garder la linéarité ?

Le clipping est à éviter absolument, ça rajoute beaucoup trop d’harmoniques et c’est peu musical. Et, selon une certaine loi, s’il y a une possibilité pour que ça clippe, ça va clipper :)

Un compresseur audio est linéaire jusqu’à un certain volume (threshold), puis au-dessus devient progressivement (ou pas, voir knee) de moins en moins linéaire, ou du moins change de linéarité, ce qui conserve relativement le spectre du signal original (signal seulement distordu) tout en réduisant la dynamique du signal.

Un compresseur réglé agressivement devient un limiteur : linéaire jusqu’au moment où pour un volume d’entrée supérieur à x, le volume de sortie sera égal à x (saturation du signal).

Un point intéressant : un compresseur (hard ou software) dispose souvent d’une sortie spéciale à laquelle il est possible de récupérer en temps réel la réduction de volume appliquée à son signal d’entrée : il est alors possible d’utiliser cette information pour baisser le volume d’un autre signal concurrent (side-chain). En musique rock, on compresse souvent beaucoup la grosse caisse, et on met la guitare basse en side-chain : de cette manière, lorsqu’il y a un coup de grosse caisse, cela réduit le volume de la basse (ducking), ce qui laisse plus de puissance disponible à l’ampli pour reproduire la grosse caisse, et donne l’impression que les deux instruments sont “liés”.

Si tu disposes de deux signaux que tu veux mixer ensemble tout en réduisant leur dynamique de façon à maintenir un signal sommé à peu près constant, il suffit d’appliquer une compression à chacun de tes deux signaux, et pourquoi pas une troisième un peu plus légère sur le mix final. C’est un procédé classique en matière de mixage.

La grosse différence entre ta manière de procéder et une compression, c’est que tu appliques ton effet de manière immédiate, ce qui correspond à une compression à attaque et relâchement instantané. Si cela peut être utilisé en tant qu’effet, le rendu est assez désagréable et écrase beaucoup trop la dynamique, tes deux sources sonores luttant pour dominer à une rythme bien trop élevé (44100Hz). En sonorisation et enregistrement, on préfère souvent laisser passer les transitoires (quelques millisecondes après le début de l’attaque d’un son) avant de compresser le signal progressivement.

®om

@dwan Merci pour ta réponse éclairante.

Je voudrais implémenter un (vrai) compresseur pour tester. J’ai écrit des fonctions mathématiques qui-vont-bien (enfin, j’espère) pour le hard et soft knees (même si ma fonction de smooth est trop compliquée) :

gnuplot -p knee.gnu

knee

(commit dc04b19)

Mais je me rend compte qu’il ne faudrait pas réaliser le mixage échantillon par échantillon, mais “globalement”, sur un intervalle de temps suffisant pour capturer au moins une période du signal (sinon le signal se fait vraiment distordre). Avec une fonction linéaire par morceaux, c’est flagrant :

./mix hknee file1.raw file2.raw
./mix sknee file1.raw file2.raw

(commit 154866b)

Êtes-vous d’accord avec cela ?

Avoir une durée d’attaque et de relâchement non-nuls suffirait peut-être à résoudre le problème. Qu’en pensez-vous ?

dwan

Doit-on voir les graphiques dans votre commentaire ou faut-il compiler votre exemple ? Je ne suis pas très doué pour ça ;)

®om

Cela signifie que ma fonction g (donc f aussi) n’est “bonne” (mathématiquement parlant, sans parler des distorsions créées) que pour le mixage de 2 pistes audio (avec 1 ou 3, elle ne respecte plus toutes les propriétés énoncées). La distorsion qu’elle produit devrait être dépendante du nombre de sources.

J’ai trouvé comment généraliser ma fonction g pour que ça fonctionne pour n pistes audio :

g(x) = sgn(x) * (1 - (1 - abs(x)) ** n)

Pour n = 2, on tombe bien sur la première que j’avais :

g(x) = x * (2 - abs(x))

(commit 54be29e)

®om

@dwan

Doit-on voir les graphiques dans votre commentaire ou faut-il compiler votre exemple ? Je ne suis pas très doué pour ça ;-)

Je l’ai ajouté ;-)

dwan

@®om

Mais je me rend compte qu’il ne faudrait pas réaliser le mixage échantillon par échantillon, mais “globalement”, sur un intervalle de temps suffisant pour capturer au moins une période du signal (sinon le signal se fait vraiment distordre).

Oui, et vu que les sources ne seront pas toujours de belles sinus, il faut raisonner en terme de temps d’attaque.

@®om

Avec une fonction linéaire par morceaux, c’est flagrant

L’attaque et le relâchement d’un compresseur se basent sur une mesure du volume (et donc de la puissance) moyen sur une certaine durée du signal, histoire d’obtenir une correction de l’amplitude moins frénétique. Il faut donc observer les samples contenus dans une fenêtre mouvante dont l’avant se trouve à l’endroit de la tête de lecture. Il est également possible d’observer un peu dans le futur (fonction lookahead), ce qui est légèrement problématique dans le cas d’un traitement en direct (introduction d’un retard) mais tout à fait indolore dans le cas d’un traitement différé (offline)

@®om

Avoir une durée d’attaque et de relâchement non-nuls suffirait peut-être à résoudre le problème. Qu’en pensez-vous ?

Il est très souhaitable que ces durées soient paramétrables, tout comme le threshold (et le knee dans une moindre mesure).

@®om

g(x) = x * (2 - abs(x))

J’ai implémenté cette fonction à l’aide du logiciel Pure Data, du coup j’ai une question : les deux sources doivent-elles passer chacune par cette transformation puis être sommées ou doivent-elles y passer une fois sommées ? Dans le premier cas, j’observe une forte distortion du signal même à faible volume, et dans le second j’observe une distortion de retournement (traduction hasardeuse de foldback distortion, mes lectures dans ce domaine sont en anglais…), c’est à dire que les crêtes dépassant un certain seuil sont retournées dans le sens opposé.

®om

OK, merci pour les réponses.

@dawn

J’ai implémenté cette fonction à l’aide du logiciel Pure Data, du coup j’ai une question : les deux sources doivent-elles passer chacune par cette transformation puis être sommées ou doivent-elles y passer une fois sommées ?

Une fois sommées (g s’applique sur la moyenne des sources).

@dawn

Dans le premier cas, j’observe une forte distortion du signal même à faible volume, et dans le second j’observe une distortion de retournement (traduction hasardeuse de foldback distortion, mes lectures dans ce domaine sont en anglais…), c’est à dire que les crêtes dépassant un certain seuil sont retournées dans le sens opposé.

Tu aurais une capture d’écran et/ou un bout de piste audio pour reproduire ce comportement ?

dwan

Le comportement bizarre était de ma faute :) C’est réparé, ça marche ! Ce que j’observe maintenant est bien conforme à ton graphe. Par contre, il s’agit du comportement d’un compresseur-expanseur : ton rapport d’amplification est d’abord positif, avant de devenir négatif vers 50% de l’amplitude. Pour l’instant tu as donc un waveshaper, vu que tu appliques une fonction de transfert directement à ta forme d’onde. Si tu veux un vrai compresseur, tu dois appliquer cette fonction de transfert (ou une autre, pour rigoler) au volume de ton son, ce qui suppose de calculer ce volume. Un petit tour ici et devrait te renseigner.

®om

@dwan

Par contre, il s’agit du comportement d’un compresseur-expanseur : ton rapport d’amplification est d’abord positif, avant de devenir négatif vers 50% de l’amplitude.

Je ne pense pas. La conversion de l’amplitude x en dB s’effectue grâce à la fonction 20 × log~10~(x), qui est strictement croissante. Par ailleurs, comme je le dis dans le billet, la fonction g est toujours comprise entre la moyenne et la somme de la valeur de chaque échantillon :

g

Nous pouvons en conclure que le volume résultant est, lui aussi, toujours compris entre la moyenne et la somme de celui des sources. Il s’agit donc d’une amplification de la moyenne et d’une compression de la somme (ce qui compte).

Ou alors j’ai loupé quelque chose.

dwan

Je me suis mélangé les pinceaux (décidément). Pour une somme de deux signaux d’amplitude maximale -2 2, c’est bien un compresseur. Pour un signal seul d’amplitude maximale -1 1, c’est un compresseur-expanseur, dont le rapport d’amplification ne devient pas négatif au-delà de 50% comme je l’ai dit, mais seulement inférieur à 1. Ouf :)

Bonjour à tous.

C’est un article et des commentaires fortement passionnants que vous proposez là. Le mixage de sons numériques me tracasse depuis longtemps, et je pense que vous m’avez fourni quelques bonnes pistes.

Je vous propose donc de venir expliquer ces résultats devant un public conquis d’avance lors du prochain THSF qui aura lieu à Toulouse, dernière semaine de mai.

Poster un commentaire

Nom : (requis)
E-mail : (optionnel, non publié)
Site web : (optionnel)
Quelle est la 3e lettre du mot blog ? (antispam)

Les commentaires sont formatés en markdown.