web-dev-qa-db-fra.com

Algorithme pour mélanger le son

J'ai deux flux sonores bruts que je dois additionner. Pour les besoins de cette question, nous pouvons supposer qu'ils ont le même débit et la même profondeur de bits (par exemple, échantillon 16 bits, taux d'échantillonnage de 44,1 kHz).

Évidemment, si je les additionne simplement, je déborderai et débordera mon espace 16 bits. Si je les additionne et que je divise par deux, le volume de chaque son est divisé par deux, ce qui n'est pas correct du point de vue sonore. Si deux personnes parlent dans une pièce, leur voix ne devient pas plus calme de moitié et un microphone peut les écouter. les deux en place sans frapper le limiteur.

  • Alors, quelle est la bonne méthode pour ajouter ces sons ensemble dans mon mélangeur de logiciels? 
  • Est-ce que je me trompe et la bonne méthode consiste à réduire de moitié le volume de chacun? 
  • Dois-je ajouter un compresseur/limiteur ou une autre étape de traitement pour obtenir le volume et l'effet de mixage recherchés?

-Adam

56
Adam Davis

Vous devez les ajouter ensemble, mais fixez le résultat dans la plage autorisée pour éviter les débordements.

En cas d'écrêtage, vous introduirez une distorsion dans le son, mais c'est inévitable. Vous pouvez utiliser votre code de coupure pour "détecter" cette condition et la signaler à l'utilisateur/opérateur (équivalent du voyant rouge "Clip" sur un mélangeur ...).

Vous pouvez implémenter un compresseur/limiteur plus "approprié", mais sans connaître votre application exacte, il est difficile de dire si cela en vaudrait la peine. 

Si vous effectuez beaucoup de traitements audio, vous pouvez vouloir représenter vos niveaux audio sous forme de valeurs à virgule flottante et revenir uniquement à l'espace 16 bits à la fin du processus. Les systèmes audio numériques haut de gamme fonctionnent souvent de cette façon.

30
Roddy

Il y a un article sur le mélange ici . Je serais intéressé de savoir ce que les autres en pensent.

27
Ben Dyer

Je préférerais commenter l'une des deux réponses très bien classées mais, en raison de ma maigre réputation (je suppose), je ne peux pas.

La réponse "cochée": additionnez et le clip est correct, mais pas si vous voulez éviter l'écrêtage.

La réponse avec le lien commence par un algorithme vaudou exploitable pour deux signaux positifs dans [0,1], puis applique une algèbre très erronée pour dériver un algorithme complètement incorrect pour les valeurs signées et les valeurs à 8 bits. L’algorithme n’a pas non plus une échelle d’au moins trois entrées (le produit des signaux diminuera tant que la somme augmentera).

Donc - convertissez les signaux d’entrée en données flottantes, mettez-les à l’échelle à [0,1] (par exemple, une valeur signée de 16 bits deviendrait
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
et ensuite les résumer.

Pour mettre à l'échelle les signaux d'entrée, vous devez probablement effectuer un travail réel plutôt que de multiplier ou de soustraire une valeur vaudou. Je suggérerais de garder un volume moyen en cours, puis, si cela commence à dériver haut (au-dessus de 0,25 par exemple) ou faible (en dessous de 0,01 par exemple), commencez à appliquer une valeur de mise à l'échelle basée sur le volume. Cela devient essentiellement une implémentation de niveau automatique, qui évolue avec un nombre quelconque d'entrées. Mieux encore, dans la plupart des cas, votre signal ne sera pas dérangé.

27
podperson

La plupart des applications de mixage audio font leur mélange avec des nombres à virgule flottante (32 bits est assez bon pour mixer un petit nombre de flux). Traduisez les échantillons 16 bits en nombres à virgule flottante dans la plage -1,0 à 1,0 représentant la pleine échelle dans le monde 16 bits. Ensuite, faites la somme des échantillons - vous disposez maintenant de beaucoup d’espace libre. Enfin, si vous vous retrouvez avec des échantillons dont la valeur dépasse la valeur maximale, vous pouvez soit atténuer l’intégralité du signal, soit utiliser une limitation stricte (valeurs de saturation à 1,0). 

Cela donnera de bien meilleurs résultats que d'ajouter des échantillons 16 bits et de les laisser déborder. Voici un exemple de code très simple montrant comment vous pouvez additionner deux échantillons 16 bits:

short sample1 = ...;
short sample2 = ...;
float samplef1 = sample1 / 32768.0f;
float samplef2 = sample2 / 32768.0f;
float mixed = samplef1 + sample2f;
// reduce the volume a bit:
mixed *= 0.8;
// hard clipping
if (mixed > 1.0f) mixed = 1.0f;
if (mixed < -1.0f) mixed = -1.0f;
short outputSample = (short)(mixed * 32768.0f)
17
Mark Heath

"Plus calme de moitié" n'est pas tout à fait correct. En raison de la réponse logarithmique de l'oreille, le fait de diviser les échantillons en deux rendra le son plus silencieux de 6 dB - certainement perceptible, mais pas désastreux.

Vous voudrez peut-être faire un compromis en multipliant par 0,75. Cela le rendra plus silencieux à 3 dB, mais réduira les risques de débordement et réduira également la distorsion lorsque cela se produit.

9
Mark Ransom

Je ne peux pas croire que personne ne connaisse la bonne réponse. Tout le monde est assez proche mais reste une pure philosophie. Le plus proche, c’est-à-dire que le meilleur était: (s1 + s2) s1 * s2). C'est une excellente approche, en particulier pour les MCU.

Donc, l'algorithme va:

  1. Découvrez le volume dans lequel vous voulez que le son soit émis. Ce peut être la moyenne ou les maxima de l'un des signaux.
    factor = average(s1) Vous supposez que les deux signaux sont déjà corrects, --- (ne débordent pas le 32767.
  2. Normaliser les deux signaux avec ce facteur:
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. Additionnez-les ensemble et normalisez le résultat avec le même facteur
    output = ((s1+s2)/max(s1+s2))*factor

Notez qu'après l'étape 1. vous n'avez pas vraiment besoin de revenir aux entiers, vous pouvez travailler avec des flottants dans l'intervalle -1.0 à 1.0 et appliquer le retour aux entiers à la fin avec le facteur de puissance précédemment choisi. J'espère que je ne me suis pas trompé maintenant, car je suis pressé.

8
Dalen

Vous pouvez également vous acheter une marge de sécurité avec un algorithme tel que y = 1,1x - 0,2x ^ 3 pour la courbe et avec un plafond en haut et en bas. Je l'ai utilisé dans Hexaphone lorsque le joueur joue plusieurs notes ensemble (jusqu'à 6).

float waveshape_distort( float in ) {
  if(in <= -1.25f) {
    return -0.984375;
  } else if(in >= 1.25f) {
    return 0.984375;
  } else {    
    return 1.1f * in - 0.2f * in * in * in;
  }
}

Ce n'est pas pare-balles - mais vous permettra d'atteindre un niveau de 1,25 et adoucira le clip en une courbe agréable. Produit une distorsion harmonique, qui sonne mieux que l'écrêtage et peut être souhaitable dans certaines circonstances.

6
Glenn Barnett

convertir les échantillons en valeurs à virgule flottante allant de -1,0 à +1,0, puis:

out = (s1 + s2) - (s1 * s2);
4
user226799

Si vous avez besoin de le faire correctement, je suggérerais de regarder les implémentations de mélangeur de logiciel open source, au moins pour la théorie.

Quelques liens:

Audace

GStreamer

En fait, vous devriez probablement utiliser une bibliothèque.

4
krusty.ar

Vous avez raison de les ajouter ensemble. Vous pouvez toujours analyser la somme des deux fichiers pour les points de pointe et réduire l'ensemble du fichier s'ils atteignent un certain seuil (ou si la moyenne de celui-ci et de ses zones environnantes atteint un seuil)

3
Jon Smock

Je pense que, tant que les flux ne sont pas corrélés, vous ne devriez pas trop vous inquiéter, vous devriez pouvoir vous débrouiller avec des coupures. Si vous êtes vraiment préoccupé par la distorsion au niveau des points de clip, un limiteur progressif fonctionnerait probablement bien.

2
Tony Arkles

convertir les échantillons en valeurs à virgule flottante allant de -1,0 à +1,0, puis:

out = (s1 + s2) - (s1 * s2);

Introduira une forte distorsion lorsque | s1 + s2 | approche 1.0 (du moins quand je l’ai essayé en mélangeant des ondes sinusoïdales simples) . J'ai lu cette recommandation à plusieurs endroits, mais à mon humble avis, c’est une approche inutile.

Ce qui se passe physiquement lorsque les ondes se mélangent, c’est leur amplitude qui s’ajoute, tout comme bon nombre des affiches suggérées ici déjà. 

  • clip (déforme également le résultat) ou 
  • résumez vos valeurs de 16 bits en un nombre de 32 bits, puis divisez-les par le nombre de vos sources (c'est ce que je suggérerais, car c'est le seul moyen que je connaisse pour éviter les distorsions).
2
Michael Beer

J'ai fait la chose suivante:

MAX_VAL = Full 8 or 16 or whatever value
dst_val = your base audio sample
src_val = sample to add to base

Res = (((MAX_VAL - dst_val) * src_val) / MAX_VAL) + dst_val

Multipliez la marge gauche de src par la valeur de destination normalisée MAX_VAL et ajoutez-la. Il ne sera jamais coupé, ne sera jamais moins bruyant et sonnera absolument naturel. 

Exemple:

250.5882 = (((255 - 180) * 240) / 255) + 180

Et ça sonne bien :)

1
Julian Wingert

J'ai trouvé une nouvelle façon d'ajouter des échantillons de manière à ce qu'ils ne puissent jamais dépasser une plage donnée. L'idée de base est de convertir les valeurs dans une plage comprise entre -1 et 1 en une plage comprise entre approximativement -Infinity et + Infinity, de tout additionner et d'inverser la transformation initiale. Je suis venu avec les formules suivantes pour cela:

f(x)=-\frac{x}{|x|-1}

f'(x)=\frac{x}{|x|+1}

o=f'(\sum f(s))

Je l'ai essayé et cela fonctionne, mais pour plusieurs sons forts, l'audio résultant est pire que de simplement additionner les échantillons et de découper chaque valeur trop grande. J'ai utilisé le code suivant pour tester ceci:

#include <math.h>
#include <stdio.h>
#include <float.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <sndfile.h>

// fabs wasn't accurate enough
long double ldabs(long double x){
  return x < 0 ? -x : x;
}

// -Inf<input<+Inf, -1<=output<=+1
long double infiniteToFinite( long double sample ){
  // if the input value was too big, we'll just map it to -1 or 1
  if( isinf(sample) )
    return sample < 0 ? -1. : 1.;
  long double ret = sample / ( ldabs(sample) + 1 );
  // Just in case of calculation errors
  if( isnan(ret) )
    ret = sample < 0 ? -1. : 1.;
  if( ret < -1. )
    ret = -1.;
  if( ret > 1. )
    ret = 1.;
  return ret;
}

// -1<=input<=+1, -Inf<output<+Inf
long double finiteToInfinite( long double sample ){
  // if out of range, clamp to 1 or -1
  if( sample > 1. )
    sample = 1.;
  if( sample < -1. )
    sample = -1.;
  long double res = -( sample / ( ldabs(sample) - 1. ) );
  // sample was too close to 1 or -1, return largest long double
  if( isinf(res) )
    return sample < 0 ? -LDBL_MAX : LDBL_MAX;
  return res;
}

// -1<input<1, -1<=output<=1 | Try to avoid input values too close to 1 or -1
long double addSamples( size_t count, long double sample[] ){
  long double sum = 0;
  while( count-- ){
    sum += finiteToInfinite( sample[count] );
    if( isinf(sum) )
      sum = sum < 0 ? -LDBL_MAX : LDBL_MAX;
  }
  return infiniteToFinite( sum );
}

#define BUFFER_LEN 256

int main( int argc, char* argv[] ){

  if( argc < 3 ){
    fprintf(stderr,"Usage: %s output.wav input1.wav [input2.wav...]\n",*argv);
    return 1;
  }

  {
    SNDFILE *outfile, *infiles[argc-2];
    SF_INFO sfinfo;
    SF_INFO sfinfo_tmp;

    memset( &sfinfo, 0, sizeof(sfinfo) );

    for( int i=0; i<argc-2; i++ ){
      memset( &sfinfo_tmp, 0, sizeof(sfinfo_tmp) );
      if(!( infiles[i] = sf_open( argv[i+2], SFM_READ, &sfinfo_tmp ) )){
        fprintf(stderr,"Could not open file: %s\n",argv[i+2]);
        puts(sf_strerror(0));
        goto cleanup;
      }
      printf("Sample rate %d, channel count %d\n",sfinfo_tmp.samplerate,sfinfo_tmp.channels);
      if( i ){
        if( sfinfo_tmp.samplerate != sfinfo.samplerate
         || sfinfo_tmp.channels != sfinfo.channels
        ){
          fprintf(stderr,"Mismatching sample rate or channel count\n");
          goto cleanup;
        }
      }else{
        sfinfo = sfinfo_tmp;
      }
      continue;
      cleanup: {
        while(i--)
          sf_close(infiles[i]);
        return 2;
      }
    }

    if(!( outfile = sf_open(argv[1], SFM_WRITE, &sfinfo) )){
      fprintf(stderr,"Could not open file: %s\n",argv[1]);
      puts(sf_strerror(0));
      for( int i=0; i<argc-2; i++ )
        sf_close(infiles[i]);
      return 3;
    }

    double inbuffer[argc-2][BUFFER_LEN];
    double outbuffer[BUFFER_LEN];

    size_t max_read;
    do {
      max_read = 0;
      memset(outbuffer,0,BUFFER_LEN*sizeof(double));
      for( int i=0; i<argc-2; i++ ){
        memset( inbuffer[i], 0, BUFFER_LEN*sizeof(double) );
        size_t read_count = sf_read_double( infiles[i], inbuffer[i], BUFFER_LEN );
        if( read_count > max_read )
          max_read = read_count;
      }
      long double insamples[argc-2];
      for( size_t j=0; j<max_read; j++ ){
        for( int i=0; i<argc-2; i++ )
          insamples[i] = inbuffer[i][j];
        outbuffer[j] = addSamples( argc-2, insamples );
      }
      sf_write_double( outfile, outbuffer, max_read );
    } while( max_read );

    sf_close(outfile);
    for( int i=0; i<argc-2; i++ )
      sf_close(infiles[i]);
  }

  return 0;
}
1
Daniel Abrecht

Je l'ai fait ainsi une fois: j'ai utilisé des floats (échantillons compris entre -1 et 1), et j'ai initialisé une variable "autoGain" avec une valeur de 1. Ensuite, je rajouterais tous les échantillons (pouvant également être supérieurs à 2). Ensuite, je multiplierais le signal sortant avec autoGain. Si la valeur absolue de la somme des signaux avant la multiplication était supérieure à 1, je ferais assigner 1/cette valeur de somme. Cela réduirait effectivement l'autogain à 1, disons 0,7, et équivaudrait à un opérateur qui diminue rapidement le volume principal dès qu'il constate que le son global devient trop fort. Ensuite, je voudrais, sur une période de temps ajustable, ajouter à l'autogain jusqu'à ce qu'il soit enfin rentré à "1" (notre opérateur a récupéré de l'état de choc et monte lentement le volume :-)).

1
Andi

Étant donné que votre profil indique que vous travaillez dans des systèmes intégrés, je supposerai que les opérations en virgule flottante ne sont pas toujours une option.

> So what's the correct method to add these sounds together in my software mixer?

Comme vous l'avez deviné, l'ajout et l'écrêtage sont la meilleure solution si vous ne voulez pas perdre de volume sur les sources. Avec les exemples qui sont int16_t, la somme doit être int32_t, puis limiter et reconvertir en int16_t.

> Am I wrong and the correct method is to lower the volume of each by half?

Oui. Réduire de moitié le volume est quelque peu subjectif, mais ce que vous pouvez voir ici et là est que réduire de moitié le volume (volume) correspond à une diminution d'environ 10 dB (en divisant la puissance par 10 ou les valeurs d'échantillon par 3,16). Mais vous voulez évidemment réduire de moitié les valeurs de échantillon. C'est une diminution de 6 dB, une réduction notable, mais pas autant que la réduction de moitié du volume (la table loudness there est très utile). 

Avec cette réduction de 6 dB, vous éviterez tout écrêtage. Mais que se passe-t-il lorsque vous voulez plus de canaux d’entrée? Pour quatre canaux, vous devez diviser les valeurs d’entrée par 4, c’est-à-dire baisser de 12 dB, pour atteindre moins de la moitié du volume de chaque canal.

> Do I need to add a compressor/limiter or some other processing stage to 
get the volume and mixing effect I'm trying for?

Vous voulez mixer, pas couper, et ne pas perdre le volume sur les signaux d'entrée. Ce n'est pas possible, pas sans une sorte de distorsion.

Comme suggéré par Mark Ransom, une solution pour éviter l'écrêtage tout en ne perdant pas jusqu'à 6 dB par canal consiste à choisir entre "ajouter et écrêter" et "faire la moyenne".

Cela concerne deux sources: additionner, diviser par un nombre compris entre 1 et 2 (réduire la plage de [-65536, 65534] à quelque chose de plus petit), puis limiter. 

Si vous utilisez souvent cette solution et que le son semble trop dur, vous voudrez peut-être adoucir le genou avec un compresseur. Ceci est un peu plus complexe, car vous devez rendre le facteur de division dépendant de la puissance d'entrée. Essayez d’abord le limiteur seul et n’envisagez le compresseur que si vous n'êtes pas satisfait du résultat.

1
Gauthier
// #include <algorithm>
// short ileft, nleft; ...
// short iright, nright; ...

// Mix
float hiL = ileft + nleft;
float hiR = iright + nright;

// Clipping
short left = std::max(-32768.0f, std::min(hiL, 32767.0f));
short right = std::max(-32768.0f, std::min(hiR, 32767.0f));
1
Luka

Cette question est ancienne mais voici la méthode valide OMI.

  1. Convertir les deux échantillons au pouvoir.
  2. Ajouter les deux échantillons au pouvoir.
  3. Normalisez-le. Telle que la valeur maximale ne dépasse pas votre limite.
  4. Reconvertir en amplitude.

Vous pouvez faire les 2 premières étapes ensemble, mais vous aurez besoin du maximum et du minimum pour normaliser lors d'une seconde passe pour les étapes 3 et 4.

J'espère que ça aide quelqu'un.

0

Merci à tous d’avoir partagé vos idées. Récemment, je travaille également sur le mixage sonore. J'ai aussi fait des expériences sur ce sujet, que cela vous aide :).

Notez que j'utilise un son de taux d'échantillonnage de 8 Khz et d'échantillon de 16 bits (SInt16) dans ios RemoteIO AudioUnit.

Au cours de mes expériences, le meilleur résultat que j'ai trouvé était différent de toute cette réponse, mais la base est la même (As Roddy suggest)

"Vous devriez les additionner ensemble, mais coupez le résultat dans la plage autorisée pour éviter les débordements/débordements".

Mais quel devrait être le meilleur moyen d’ajouter sans débordement/débordement? 

Idée clé :: Vous avez deux ondes sonores dites A & B, et l’onde résultante C sera la superposition de deux ondes A & B. Un échantillon sous une plage de bits limitée peut provoquer un débordement. Nous pouvons donc maintenant calculer le croix limite maximale à la hausse & croix limite minimale à la baisse de la forme d'onde de superposition. Nous allons maintenant soustraire croix de limite maximale à la hausse à la partie supérieure de la forme d'onde de superposition et ajouter croix de limite minimale à la baisse à la partie inférieure de la forme d'onde de superposition. VOILA ... tu as fini.

Pas:

  1. Commencez par parcourir votre boucle de données une fois pour la valeur maximale de la limite supérieure croisée & valeur minimale de la limite inférieure croisée.
  2. Effectuez un autre parcours vers les données audio, soustrayez la valeur maximale de la partie de données audio positives et ajoutez valeur minimale à la partie négative des données audio.

le code suivant montrerait l'implémentation.

static unsigned long upSideDownValue = 0;
static unsigned long downSideUpValue = 0;
#define SINT16_MIN -32768
#define SINT16_MAX 32767
SInt16* mixTwoVoice (SInt16* RecordedVoiceData, SInt16* RealTimeData, SInt16 *OutputData, unsigned int dataLength){

unsigned long tempDownUpSideValue = 0;
unsigned long tempUpSideDownValue = 0;
//calibrate maker loop
for(unsigned int i=0;i<dataLength ; i++)
{
    SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];

    if(SINT16_MIN < summedValue && summedValue < SINT16_MAX)
    {
        //the value is within range -- good boy
    }
    else
    {
       //nasty calibration needed
        unsigned long tempCalibrateValue;
        tempCalibrateValue = ABS(summedValue) - SINT16_MIN; // here an optimization comes ;)

        if(summedValue < 0)
        {
            //check the downside -- to calibrate
            if(tempDownUpSideValue < tempCalibrateValue)
                tempDownUpSideValue = tempCalibrateValue;
        }
        else
        {
            //check the upside ---- to calibrate
            if(tempUpSideDownValue < tempCalibrateValue)
                tempUpSideDownValue = tempCalibrateValue;
        }
    }
}

//here we need some function which will gradually set the value
downSideUpValue = tempUpSideDownValue;
upSideDownValue = tempUpSideDownValue;

//real mixer loop
for(unsigned int i=0;i<dataLength;i++)
{
    SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];

    if(summedValue < 0)
    {
        OutputData[i] = summedValue + downSideUpValue;
    }
    else if(summedValue > 0)
    {
        OutputData[i] = summedValue - upSideDownValue;
    }
    else
    {
        OutputData[i] = summedValue;
    }
}

return OutputData;
}

cela fonctionne bien pour moi, j'ai ensuite l'intention de changer progressivement la valeur de upSideDownValue & downSideUpValue _ pour obtenir une sortie plus douce.

0
Ratul Sharker