メインコンテンツまでスキップ

チュートリアルオーディオレベルのコントロール

📚 Source Page

このチュートリアルでは、オーディオを処理して出力レベルを変える方法を説明します。これは、低レベルのオーディオサンプルデータを処理することで実現します。

レベル中級

プラットフォームWindows , macOS , Linux

クラス: AudioAppComponent,Random,AudioSourceChannelInfo,AudioBuffer,Slider

スタート

注記

このチュートリアルはTutorial: Build a white noise generatorを読んで理解しておく必要がある。また、以下の操作に慣れていることを前提としている。Sliderオブジェクト(参照Tutorial: The Slider class).

このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

デモ・プロジェクトでは、ホワイト・ノイズ・ジェネレーターの出力レベルを制御するために使用できるスライダーを1つ含むウィンドウが表示されます。これは次のスクリーンショットに示されています。

レベル・コントロール・スライダーを表示したデモ・プロジェクトのメイン・ウィンドウ。
レベル・コントロール・スライダーを表示したデモ・プロジェクトのメイン・ウィンドウ。

IDEからプロジェクトを実行し、発生するホワイトノイズのレベルを実際にコントロールできることを確認してください。

乗算としてのレベルコントロール

コードを見て、次のことに気づくかもしれない。MainContentComponentクラスはないを継承する。Slider::Listenerクラスの値を取得します。実際、スライダーの値はgetNextAudioBlock()関数が呼び出される:

    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
auto level = (float) levelSlider.getValue();

このような単純なアプリケーションでは、このテクニックは許容範囲ですが、より複雑なアプリケーションでは、ほぼ間違いなく別のテクニックを使いたいでしょう。オーディオ処理システムを制御する値は、UIコントロールにのみ保存するのではなく、アプリケーションの "データモデル "に保存する方が良いでしょう。

警告

と呼んでも構わないがSlider::getValue()でのgetNextAudioBlock()ファンクションユーべからずを呼び出す。Slider::setValue()関数は、このgetNextAudioBlock()関数で実行されます。これは、このコードがオーディオスレッド上で実行されているためです。オーディオ・スレッドからUIオブジェクトの状態を変更するようなことはしてはいけませんが、UIオブジェクトの状態を問い合わせることは、副作用がないことを確認している限り、許容されます。

スライダーで指定されたレベルのホワイトノイズを生成するには、基本的な演算を行う必要がある。そのRandom::nextFloat()関数は常に0.0から1.0の間の値を生成する。これをオーディオ波形としてプロットすると、次のようになる:

0.0から1.0の間で生成された乱数
0.0から1.0の間で生成された乱数

これにアプローチする簡単な方法は、まずノイズをスケーリングし、常に-1.0から1.0の間でスケーリングされるようにすることである。これを行うには、2.0を掛け、1.0を引く。そうすると、下図のような信号になります:

1.0~1.0の間で生成された乱数
1.0~1.0の間で生成された乱数

これはgetNextAudioBlock()関数は次のようになる:

    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
auto level = (float) levelSlider.getValue();

for (auto channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
{
auto* buffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);

for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
auto noise = random.nextFloat() * 2.0f - 1.0f;
buffer[sample] = noise * level;
}
}
}

ちょっとした最適化

算術演算の回数を減らすことは、しばしばDSPの目標である。これは、1サンプルにつき1回の乗算演算を行わずに済むケースの1つである。これを実現するには、まず、0.0と1.0の間の値に、次の値を掛け合わせればよい。ダブル必要な出力レベル。例えば、レベルを0.25にしたいとしよう。ランダムな値に0.5を掛けると次のようになる:

0.0から0.5の間でスケーリングされた乱数
0.0から0.5の間でスケーリングされた乱数

最後に、乱数値がゼロを中心とするようにオフセットする必要がある。これを行うには、乗算した値の半分に相当する値を引く。これはもちろん元のレベル値である。最終結果はこのようになる:

乱数を-0.25から0.25の間でスケーリングしオフセットする。
乱数を-0.25から0.25の間でスケーリングしオフセットする。

これはgetNextAudioBlock()関数で、1サンプルあたりの乗算演算が1回減ったことがわかるはずだ:

    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
auto level = (float) levelSlider.getValue();
auto levelScale = level * 2.0f;

for (auto channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
{
auto* buffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);

for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
buffer[sample] = random.nextFloat() * levelScale - level;
}
}
注記

この改訂版のコードは、以下のサイトにある。SynthLevelControlTutorial_02.hファイルにある。

この実装に残る1つの問題は、レベルが、生成されたオーディオブロックごとに新しい定数値に更新されるだけだということです。この場合、(ソース音がホワイトノイズであるため)聴こえません。もしこれがほとんどのオーディオコンテンツに適用された場合、このテクニックはオーディオアーティファクトを引き起こすでしょう。これは、レベル値が、オーディオブロックの中で滑らかに変化するのではなく、各オーディオブロックの間で、かなり急激にジャンプする可能性があるためです。このようなアーチファクト(レベルが変化したときに聞こえるパチパチという音など)を避けるテクニックは、他のチュートリアルで説明されています(参照Tutorial: Build a sine wave synthesiser).

エクササイズ

ユーザーインターフェースに2つ目のスライダーを追加します。最初のスライダーで左チャンネルのレベルをコントロールし、この2番目のスライダーで右チャンネルのレベルをコントロールします。

概要

このチュートリアルでは、オーディオ信号のレベルをコントロールする方法を説明しました。以下のトピックを取り上げました:

  • 信号値に必要な出力レベルを乗じることによって、信号のレベルを変更すること。
  • スライダーを使ってオーディオレベルをコントロールする。

こちらも参照