デシベルを使ってオーディオレベルをコントロールする
このチュートリアルでは、デシベルスケールを使ってオーディオの出力レベルを変更する方法を示します。これは、オーディオアプリケーションでオーディオレベルの値をユーザーに表示する、より一般的な方法です。
レベル:中級
プラットフォーム:Windows, macOS, Linux
スタート
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
このチュートリアルはTutorial: Control audio levels.そのチュートリアルに従うべきだった。
デモ・プロジェクト
と似たようなものだ。Tutorial: Control audio levelsこのチュートリアルのデモ・プロジェクトでは、1つのスライダーを含むウィ ンドウを表示します。今回、スライダーの値はdecibels.この値はdecibelsオーディオ処理アルゴリズムで使用する前に、線形ゲイン値に変換する必要がある。ほとんどのオーディオアプリケーションは、ゲインをdecibelsこのようにフィーリング値が変化(または比較)するにつれて、より自然になります。デモ・プロジェクトのユーザー・インタフェースを以下のスクリーンショットに示す。

カスタマイズされたスライダーの作成
今回は、スライダーに隣接して表示されるテキストが、スライダー内の値を示すだけでなく、スライダー内の値を示していることに注目してください。decibelsと表示されますが、接尾辞 "dB "も表示されます。これはカスタ ム・スライダー・クラスを作ることで実現できます、DecibelSlider
を継承している。Sliderクラスを作成しました。このカスタム・スライダー・クラスでは、テキストボックスのインタフェースは、値をdecibels.の中に表示されるテキストの接尾辞はSliderオブジェクトのテキストボックスはSlider::setTextValueSuffix()関数を使うには、もうひとつカスタマイズが必要だ。これは、値が変換される方法をカスタマイズすることである。-INF dBレベルが非常に低くなったとき
INFとはインフィニティここで-INF dBは、このアプリケーションの目的上、沈黙として扱われる。
デシベルズ級
についてDecibelsクラスの値の変換に必要な静的関数がいくつか含まれています。decibelsとリニアゲインがあります。の値を変換する簡単な手段も提供する。decibelsへのStringオブジェクトをオーバーライドする。例えば、仮想関数Slider::getTextFromValue()を使用する。Decibels::toString()関数の中でDecibelSlider
クラス)のようなものだ:
juce::String getTextFromValue (double value) override
{
return juce::Decibels::toString (value);
}
これによってDecibelSlider
クラスを使用して、与えられたスライダー値に対して適切なテキストをテキストボックスに表示できます。
についてDecibelsを変換する関数はありません。Stringオブジェクトをdecibelsそのため、独自のものを書く必要がある。ここではSlider::getValueFromText()仮想関数(これもDecibelSlider
クラス):
double getValueFromText (const juce::String& text) override
{
auto minusInfinitydB = -100.0;
auto decibelText = text.upToFirstOccurrenceOf ("dB", false, false).trim(); // [1]
return decibelText.equalsIgnoreCase ("-INF") ? minusInfinitydB
: decibelText.getDoubleValue(); // [2]
}
これは、ユーザーがテキストボックスに値を入力し、それがチェックされ、スライダーの有効な値に変換されることを可能にします。これを行うには
- [1]もし "dB "という接尾辞があれば、それを取り除き、(もし "dB "という接尾辞がなければString::trim()関数)。
- [2]残りのテキストが"-INF "であるかどうかをチェックし、その場合は-100を返す。decibelsを -INF dBに置き換えた。Decibelsクラスが使用するNotesこれについてはこのチュートリアルを参照)。そうでなければ、この残りのテキストを
double
値を返します。
スライダーの値を使う
の中でTutorial: Control audio levelsでスライダーの値に直接アクセスしている。getNextAudioBlock()
関数に変換されます。からの変換はdecibelsリニアゲインへの変換には、CPUに負荷のかかる演算が含まれる可能性があるため、特にオーディオスレッドでは、あまり頻繁に変換を実行しない方が賢明でしょう。このチュートリアルのデモプロジェクトではfloat
メンバーlevel
でのMainContentComponent
クラスを作成し、リスナーを使用してスライダーが変更されたときにこの値を更新します。
を初期化する。level
コンストラクタでメンバーをゼロに変換し、これをdecibelsを使用している。Decibels::gainToDecibels()関数を使ってスライダーに初期位置を与えます。Slider::setValue()関数)のようなものだ:
MainContentComponent()
{
decibelSlider.setValue (juce::Decibels::gainToDecibels (level));
decibelLabel.setText ("Noise Level in dB", juce::dontSendNotification);
デシベルをリニアゲインに変換
のラムダ関数ではSlider::onValueChangeヘルパー・オブジェクトからdecibelsスライダーで使用されるスケールを、オーディオ処理に必要なリニアゲイン値に変更します:
decibelSlider.onValueChange = [this] { level = juce::Decibels::decibelsToGain ((float) decibelSlider.getValue()); };
この関数は、最初にSlider::setValue()関数をコンストラクタで呼び出す。これはSlider::onValueChange値が変更されたときにヘルパーオブジェクトがlevel
メンバーが正しく設定される。
- 追加Labelオブジェクトを
MainContentComponent
リニアゲインの値を表示するクラス。 - 追加Sliderオブジェクトを
MainContentComponent
クラスはリニアゲイン値を表示し、ユーザーはどちらかのスライダーを使ってノイズレベルを表示、指定できます。どちらかのスライダを動かすと、両方のスライダが正しく更新されるはずです。(参照Tutorial: The Slider class異なる単位間の変換の簡単な例を参照)。
オーディオの処理
我々のMainContentComponent::getNextAudioBlock()
オーディオを処理する:
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
auto currentLevel = level;
auto levelScale = currentLevel * 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 - currentLevel;
}
}
とほぼ同じであることに注意。getNextAudioBlock()
関数Tutorial: Control audio levels ただしの関数ローカルコピーを取るだけである。level
を計算する。levelScale
以前と同様に
このアプローチの問題点
この方法の問題点のひとつはlevel
変数は、オーディオスレッドの実行中(この場合、2回のgetNextAudioBlock
).このような変更は、通常、耳に聞こえるパチパチ音などのオーディオ・アーティファクトを引き起こす。これを避けるために、実際のシンセサイザーではlevel
値を滑らかに変化させます。これを達成するテクニックは他のチュートリアルで説明されています(Tutorial: Build a sine wave synthesiser).
もう1つの問題は、スレッドセーフに関するものだ。のようなメンバー変数への書き込みはlevel
あるスレッド(GUIスレッド)で同じ値を別のスレッド(オーディオスレッド)から読み取ることは、スレッド同期が(クリティカルセクションを経由するかアトミックを使用して)行われない場合、C++では技術的に未定義の動作です。これらの問題はこのチュートリアルの範囲外であり、将来のチュートリアルで説明します。一般的なアーキテクチャー(x86、x86_64、ARM)では、1つのスレッドの読み書きは1つのスレッドで行われるからです。float
読み取りと書き込みが混在することはなく、一般的に安全である。
さらに考えてみると、コードをさらに最適化したくなるかもしれない。levelScale
を呼び出すたびに計算する必要はない)。getNextAudioBlock()
関数)。しかしそうすると、2つのメンバー変数は1つのアトミック操作として更新されなくなってしまいます。もちろん、これを回避する方法もありますが、このチュートリアルの範囲外です。
備考
このチュートリアルのデモ・プロジェクトで示されるコードでは、-100 dB以下の値を -INF dB(つまりリニア・ゲイン値がゼロ)として扱うことを想定しています。この-100 dBという値はDecibelsクラスの計算をオーバーライドできる。これはDecibelsクラスで、どの値を-INF dBとして扱うかを指定します。例えば、-96 dB(およびそれ以下)を-INF dBとして使用する場合、スライダーの値をMainContentComponent
コンストラクターでこれを行うことができる:
decibelSlider.setValue (juce::Decibels::gainToDecibels (level, -96.0f));
しかしもちろん、アプリケーションのすべての部分で-INF dBに同じ値を使うようにする必要がある。デモ・プロジェクトのコードには1つ問題があります。-100.0
私たちのDecibelSlider::getValueFromText()
関数を使用する。もしDecibelsクラスのデフォルト値が(何らかの理由で)変更されると、コードは壊れてしまう。残念ながら、このデフォルト値は