web-dev-qa-db-ja.com

音をミックスするアルゴリズム

一緒に追加する必要がある2つの生のサウンドストリームがあります。この質問の目的のために、それらは同じビットレートとビット深度であると仮定できます(たとえば、16ビットサンプル、44.1khzサンプルレート)。

明らかに、それらを一緒に追加するだけで、16ビットのスペースがオーバーフローおよびアンダーフローします。それらを足し合わせて2で割ると、それぞれの音量が半分になりますが、これは音響的には正しくありません。2人が部屋で話している場合、声が半分になっても静かにならず、マイクで拾うことができますリミッターを押すことなく両方アップ。

  • それでは、これらのサウンドをソフトウェアミキサーに追加する正しい方法は何ですか?
  • 私は間違っていますか、正しい方法はそれぞれの音量を半分に下げることですか?
  • 私がしようとしている音量とミキシング効果を得るために、コンプレッサー/リミッターまたは他の処理段階を追加する必要がありますか?

-アダム

56
Adam Davis

これらを一緒に追加する必要がありますが、結果を許容範囲にクリップして、オーバーフロー/アンダーフローを防ぎます。

クリッピングが発生した場合、willオーディオに歪みを導入しますが、それは避けられません。クリッピングコードを使用してこの状態を「検出」し、ユーザー/オペレーターに報告することができます(ミキサーの赤い「クリップ」ライトに相当...)

より適切なコンプレッサー/リミッターを実装することもできますが、正確なアプリケーションを知らなくても、それが価値があるかどうかを判断するのは困難です。

多くのオーディオ処理を行っている場合は、オーディオレベルを浮動小数点値として表現し、プロセスの最後で16ビット空間のみに戻すことができます。ハイエンドのデジタルオーディオシステムは、多くの場合この方法で機能します。

30
Roddy

2つの高いランクの返信の1つにコメントしたいと思いますが、私の評判が低いため(私は推測します)、できません。

「チェック」の答え:足し算とクリップは正しいですが、クリッピングを避けたい場合はそうではありません。

リンクに関する答えは、[0,1]の2つの正の信号に対する実行可能なブードゥーアルゴリズムから始まりますが、いくつかの非常に誤りのある代数を適用して、符号付きの値と8ビット値の完全に不正なアルゴリズムを導き出します。また、アルゴリズムは3つ以上の入力にスケーリングしません(合計が増加する間、信号の積は減少します)。

したがって-入力信号を浮動小数点数に変換し、[0,1]にスケーリングします(たとえば、符号付き16ビット値は
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
そしてそれらを合計します。

入力信号をスケーリングするには、ブードゥー値を乗算または減算するのではなく、おそらく実際の作業を行う必要があります。移動平均ボリュームを維持することをお勧めします。その後、高(0.25を超える)または低(0.01を下回る)にドリフトし始めたら、ボリュームに基づいてスケーリング値を適用し始めます。これは本質的に自動レベル実装になり、任意の数の入力に合わせて拡張できます。何よりも、ほとんどの場合、信号をまったく混乱させません。

28
podperson

混合についての記事があります こちら 。他の人がこれについてどう思うか知りたいです。

27
Ben Dyer

ほとんどのオーディオミキシングアプリケーションは、浮動小数点数でミキシングを行います(少数のストリームをミキシングするには32ビットで十分です)。 16ビットのサンプルを、-1.0〜1.0の範囲の浮動小数点数に変換して、16ビットの世界のフルスケールを表します。次に、サンプルを合計します-これで十分な余裕があります。最後に、値がフルスケールを超えるサンプルが見つかった場合は、信号全体を減衰させるか、ハードリミッティング(値を1.0にクリッピング)を使用できます。

これにより、16ビットのサンプルを加算してオーバーフローさせるよりもはるかに優れたサウンド結果が得られます。以下に、2つの16ビットサンプルを合計する方法を示す非常に簡単なコード例を示します。

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

「Quieter by half」はまったく正しくありません。耳の対数応答のため、サンプルを半分に分割すると、6db静かになります-確かに目立ちますが、悲惨ではありません。

0.75を掛けて妥協することもできます。これにより、3 dB静かになりますが、オーバーフローの可能性が減り、発生したときの歪みも少なくなります。

9
Mark Ransom

誰も正しい答えを知らないとは信じられません。誰もが十分に近いが、それでも、純粋な哲学です。最も近い、つまり最高のものは、(s1 + s2)-(s1 * s2)でした。特にMCU向けの優れたアプローチです。

そのため、アルゴリズムは次のようになります。

  1. 出力するサウンドの音量を確認します。いずれかの信号の平均値または最大値にすることができます。
    factor = average(s1)両方の信号がすでに正常であると仮定します32767.0をオーバーフローしません
  2. この係数で両方の信号を正規化します。
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. それらを一緒に追加し、同じ係数で結果を正規化します
    output = ((s1+s2)/max(s1+s2))*factor

手順1.の後、実際に整数に戻す必要はないことに注意してください。-1.0から1.0の間隔でフロートを操作し、最後に選択した力率で整数に戻り値を適用できます。私は急いでいるので、私は今間違えなかったことを望みます。

8
Dalen

また、曲線のy = 1.1x-0.2x ^ 3のようなアルゴリズムを使用して、上部と下部にキャップを付けて、ヘッドルームを購入することもできます。これを Hexaphone で使用しました。プレーヤーが複数のノートを一緒に演奏している場合(最大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;
  }
}

これは防弾ではありませんが、最大1.25レベルまで取得でき、クリップをニース曲線に滑らかにします。ハーモニックディストーションを生成します。これはクリッピングよりも聞こえが良く、状況によっては望ましい場合があります。

6
Glenn Barnett

サンプルを-1.0〜+1.0の範囲の浮動小数点値に変換してから、次のようにします。

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

これを正しく行う必要がある場合は、少なくとも理論上は、オープンソースソフトウェアミキサーの実装を検討することをお勧めします。

いくつかのリンク:

Audacity

GStreamer

実際には、おそらくライブラリを使用する必要があります。

4
krusty.ar

それらを一緒に追加するのは正しいことです。常に2つのファイルの合計のピークポイントをスキャンし、何らかのしきい値に達した場合(またはファイルとその周辺スポットの平均がしきい値に達した場合)、ファイル全体を縮小できます。

3
Jon Smock

ストリームが無相関である限り、あまり心配する必要はないはずです。クリッピングでうまくいくはずです。クリップポイントでの歪みが本当に心配な場合は、ソフトリミッターを使用しても大丈夫でしょう。

2
Tony Arkles

サンプルを-1.0〜+1.0の範囲の浮動小数点値に変換してから、次のようにします。

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

| s1 + s2 |の場合、大きな歪みが発生しますアプローチ1.0(少なくとも単純な正弦波を混合するときに試したとき)。私はいくつかの場所でこの勧告を読みましたが、謙虚な意見では、それは役に立たないアプローチです。

波が「ミックス」するときに物理的に起こることは、ここですでに示唆されている多くのポスターのように、アンプリチュードが追加されることです。どちらか

  • クリップ(結果も歪める)または
  • 16ビットの値を32ビットの数値に要約し、ソースの数で割る
2
Michael Beer

私は次のことをしました:

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

Srcの左ヘッドルームに正規化された宛先値MAX_VALを乗算して追加します。決してクリップせず、音量を下げず、自然に聞こえます。

例:

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

そして、これはいいですね:)

1
Julian Wingert

特定の範囲を超えないようにサンプルを追加する新しい方法を見つけました。基本的な考え方は、-1〜1の範囲の値を約-Infinity〜+ Infinityの範囲に変換し、すべてを加算して、初期変換を逆にすることです。このために、次の公式を思いつきました。

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

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

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

私はそれを試してみましたが、動作しますが、複数の大きな音の場合、結果のオーディオは単にサンプルを加算して大きすぎる値をすべてクリップするよりも悪く聞こえます。次のコードを使用してこれをテストしました。

#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

一度この方法で行いました。フロート(-1〜1のサンプル)を使用し、値が1の「autoGain」変数を初期化しました。その後、すべてのサンプルを加算します(2を超えることもあります)。次に、発信信号にautoGainを掛けます。乗算前の信号の合計の絶対値が1よりも大きい場合、1 /合計値を割り当てます。これにより、オートゲインが1よりも小さくなり(0.7など)、全体の音が大きくなりすぎているとわかるとすぐにメインボリュームを小さくするオペレーターと同等になります。その後、調整可能な時間をかけてオートゲインを追加して、最終的に「1」に戻るまで(オペレーターがショックから回復し、音量を徐々に上げていきます:-))。

1
Andi

あなたのプロファイルでは組み込みシステムで作業していると言われているので、浮動小数点演算は必ずしもオプションではないと思います。

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

ご想像のとおり、ソースのボリュームを失いたくない場合は、追加とクリッピングが正しい方法です。 int16_t、合計がint32_t、その後制限してint16_t

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

はい。ボリュームの半分はある程度主観的ですが、ここで見られるように、ボリュームの半分(音量)は約10 dBの減少です(パワーを10で除算するか、サンプル値を3.16で除算します)。ただし、サンプル値を半分に下げることは明らかです。これは6 dBの減少であり、顕著な減少ですが、音量を半分にするほどではありません(ラウドネステーブル () は非常に便利です)。

この6 dBの削減により、すべてのクリッピングが回避されます。しかし、より多くの入力チャネルが必要な場合はどうなりますか? 4つのチャネルの場合、入力値を4で除算する必要があります。これは12 dB低下するため、各チャネルのラウドネスの半分以下になります。

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

クリップするのではなく、ミックスし、入力信号のラウドネスを失わないようにします。これは不可能であり、何らかの歪みがないわけではありません。

Mark Ransomが示唆するように、チャンネルごとに6 dBを失うことなくクリッピングを回避する解決策は、「追加とクリッピング」と「平均化」の間のどこかにヒットすることです。

それは、2つのソースの場合です。追加、1〜2のどこかでの除算([-65536、65534]からより小さな範囲への縮小)、および制限。

このソリューションで頻繁にクリップし、耳障りな音がする場合は、コンプレッサーで限界膝を柔らかくしたい場合があります。分割係数を入力電力に依存させる必要があるため、これはもう少し複雑です。最初にリミッターのみを試してみて、結果に満足できない場合にのみコンプレッサーを検討してください。

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

この質問は古いですが、ここに有効なメソッドIMOがあります。

  1. 両方のサンプルを累乗で変換します。
  2. 両方のサンプルをパワーで追加します。
  3. それを正規化します。最大値などは制限を超えません。
  4. 振幅に変換します。

最初の2つのステップを一緒に作成できますが、ステップ3および4の2回目のパスで正規化するには最大値と最小値が必要です。

私はそれが誰かを助けることを願っています。

皆さんのアイデアを共有してくれてありがとう。最近、サウンドミキシングに関連する仕事もしています。私もこの問題について実験を行ってきました。皆さんに役立つかもしれません:)。

IOS RemoteIO AudioUnitで8Khzサンプルレートと16ビットサンプル(SInt16)サウンドを使用していることに注意してください。

私の実験に沿って私が見つけた最良の結果は、このすべての答えとは異なるものでしたが、基本は同じです( (Roddy が示唆するように)

それらを一緒に追加する必要がありますが、結果を許容範囲にクリップして、オーバーフロー/アンダーフローを防止します」。

しかし、オーバーフロー/アンダーフローなしで追加する最良の方法は何でしょうか?

Key Idea:: AとBという2つの音波があり、結果の波Cは2つの波の 重ね合わせ になりますA&B。制限されたビット範囲でのサンプルは、オーバーフローを引き起こす可能性があります。そのため、今度は、上側でmaximum limit crossと、重ね合わせ波形の下側でminimum limit crossを計算できます。次に、重ね合わせ波形の上部にmaximum upside limit crossを差し引き、下限にminimum downside limit crossを追加します重ね合わせ波形の一部。出来上がり...完了です。

ステップ:

  1. 最初に、上限クロスの最大値と下限クロスの最小値について、データループを1回走査します。
  2. オーディオデータをもう一度トラバースし、正のオーディオデータ部分から最大値を減算し、負の部分に最小値を追加しますオーディオデータの。

次のコードは実装を示します。

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;
}

それは私のためにうまくいきます、私は後でよりスムーズな出力を得るためにupSideDownValuedownSideUpValueの値を徐々に変更するつもりです.

0
Ratul Sharker