一緒に追加する必要がある2つの生のサウンドストリームがあります。この質問の目的のために、それらは同じビットレートとビット深度であると仮定できます(たとえば、16ビットサンプル、44.1khzサンプルレート)。
明らかに、それらを一緒に追加するだけで、16ビットのスペースがオーバーフローおよびアンダーフローします。それらを足し合わせて2で割ると、それぞれの音量が半分になりますが、これは音響的には正しくありません。2人が部屋で話している場合、声が半分になっても静かにならず、マイクで拾うことができますリミッターを押すことなく両方アップ。
-アダム
これらを一緒に追加する必要がありますが、結果を許容範囲にクリップして、オーバーフロー/アンダーフローを防ぎます。
クリッピングが発生した場合、willオーディオに歪みを導入しますが、それは避けられません。クリッピングコードを使用してこの状態を「検出」し、ユーザー/オペレーターに報告することができます(ミキサーの赤い「クリップ」ライトに相当...)
より適切なコンプレッサー/リミッターを実装することもできますが、正確なアプリケーションを知らなくても、それが価値があるかどうかを判断するのは困難です。
多くのオーディオ処理を行っている場合は、オーディオレベルを浮動小数点値として表現し、プロセスの最後で16ビット空間のみに戻すことができます。ハイエンドのデジタルオーディオシステムは、多くの場合この方法で機能します。
2つの高いランクの返信の1つにコメントしたいと思いますが、私の評判が低いため(私は推測します)、できません。
「チェック」の答え:足し算とクリップは正しいですが、クリッピングを避けたい場合はそうではありません。
リンクに関する答えは、[0,1]の2つの正の信号に対する実行可能なブードゥーアルゴリズムから始まりますが、いくつかの非常に誤りのある代数を適用して、符号付きの値と8ビット値の完全に不正なアルゴリズムを導き出します。また、アルゴリズムは3つ以上の入力にスケーリングしません(合計が増加する間、信号の積は減少します)。
したがって-入力信号を浮動小数点数に変換し、[0,1]にスケーリングします(たとえば、符号付き16ビット値はfloat v = ( s + 32767.0 ) / 65536.0 (close enough...))
そしてそれらを合計します。
入力信号をスケーリングするには、ブードゥー値を乗算または減算するのではなく、おそらく実際の作業を行う必要があります。移動平均ボリュームを維持することをお勧めします。その後、高(0.25を超える)または低(0.01を下回る)にドリフトし始めたら、ボリュームに基づいてスケーリング値を適用し始めます。これは本質的に自動レベル実装になり、任意の数の入力に合わせて拡張できます。何よりも、ほとんどの場合、信号をまったく混乱させません。
混合についての記事があります こちら 。他の人がこれについてどう思うか知りたいです。
ほとんどのオーディオミキシングアプリケーションは、浮動小数点数でミキシングを行います(少数のストリームをミキシングするには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)
「Quieter by half」はまったく正しくありません。耳の対数応答のため、サンプルを半分に分割すると、6db静かになります-確かに目立ちますが、悲惨ではありません。
0.75を掛けて妥協することもできます。これにより、3 dB静かになりますが、オーバーフローの可能性が減り、発生したときの歪みも少なくなります。
誰も正しい答えを知らないとは信じられません。誰もが十分に近いが、それでも、純粋な哲学です。最も近い、つまり最高のものは、(s1 + s2)-(s1 * s2)でした。特にMCU向けの優れたアプローチです。
そのため、アルゴリズムは次のようになります。
factor = average(s1)
両方の信号がすでに正常であると仮定します32767.0をオーバーフローしませんs1 = (s1/max(s1))*factor
s2 = (s2/max(s2))*factor
output = ((s1+s2)/max(s1+s2))*factor
手順1.の後、実際に整数に戻す必要はないことに注意してください。-1.0から1.0の間隔でフロートを操作し、最後に選択した力率で整数に戻り値を適用できます。私は急いでいるので、私は今間違えなかったことを望みます。
また、曲線の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レベルまで取得でき、クリップをニース曲線に滑らかにします。ハーモニックディストーションを生成します。これはクリッピングよりも聞こえが良く、状況によっては望ましい場合があります。
サンプルを-1.0〜+1.0の範囲の浮動小数点値に変換してから、次のようにします。
out = (s1 + s2) - (s1 * s2);
それらを一緒に追加するのは正しいことです。常に2つのファイルの合計のピークポイントをスキャンし、何らかのしきい値に達した場合(またはファイルとその周辺スポットの平均がしきい値に達した場合)、ファイル全体を縮小できます。
ストリームが無相関である限り、あまり心配する必要はないはずです。クリッピングでうまくいくはずです。クリップポイントでの歪みが本当に心配な場合は、ソフトリミッターを使用しても大丈夫でしょう。
サンプルを-1.0〜+1.0の範囲の浮動小数点値に変換してから、次のようにします。
out =(s1 + s2)-(s1 * s2);
| s1 + s2 |の場合、大きな歪みが発生しますアプローチ1.0(少なくとも単純な正弦波を混合するときに試したとき)。私はいくつかの場所でこの勧告を読みましたが、謙虚な意見では、それは役に立たないアプローチです。
波が「ミックス」するときに物理的に起こることは、ここですでに示唆されている多くのポスターのように、アンプリチュードが追加されることです。どちらか
私は次のことをしました:
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〜1の範囲の値を約-Infinity〜+ Infinityの範囲に変換し、すべてを加算して、初期変換を逆にすることです。このために、次の公式を思いつきました。
私はそれを試してみましたが、動作しますが、複数の大きな音の場合、結果のオーディオは単にサンプルを加算して大きすぎる値をすべてクリップするよりも悪く聞こえます。次のコードを使用してこれをテストしました。
#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〜1のサンプル)を使用し、値が1の「autoGain」変数を初期化しました。その後、すべてのサンプルを加算します(2を超えることもあります)。次に、発信信号にautoGainを掛けます。乗算前の信号の合計の絶対値が1よりも大きい場合、1 /合計値を割り当てます。これにより、オートゲインが1よりも小さくなり(0.7など)、全体の音が大きくなりすぎているとわかるとすぐにメインボリュームを小さくするオペレーターと同等になります。その後、調整可能な時間をかけてオートゲインを追加して、最終的に「1」に戻るまで(オペレーターがショックから回復し、音量を徐々に上げていきます:-))。
あなたのプロファイルでは組み込みシステムで作業していると言われているので、浮動小数点演算は必ずしもオプションではないと思います。
> 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]からより小さな範囲への縮小)、および制限。
このソリューションで頻繁にクリップし、耳障りな音がする場合は、コンプレッサーで限界膝を柔らかくしたい場合があります。分割係数を入力電力に依存させる必要があるため、これはもう少し複雑です。最初にリミッターのみを試してみて、結果に満足できない場合にのみコンプレッサーを検討してください。
// #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));
この質問は古いですが、ここに有効なメソッドIMOがあります。
最初の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を追加します重ね合わせ波形の一部。出来上がり...完了です。
ステップ:
次のコードは実装を示します。
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;
}
それは私のためにうまくいきます、私は後でよりスムーズな出力を得るためにupSideDownValue&downSideUpValueの値を徐々に変更するつもりです.