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

チュートリアル: デシベルを使った音声レベルの制御

📚 Source Page

このチュートリアルでは、デシベルスケールを使用して出力レベルを変更するために音声を処理する方法を示します。これは、オーディオアプリケーション内でユーザーに音声レベル値を提示するより一般的な方法です。

レベル: 中級
プラットフォーム: Windows, macOS, Linux
クラス: Decibels, Slider, String

はじめに

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

この手順でヘルプが必要な場合は、Tutorial: Projucer Part 1: Getting started with the Projucerを参照してください。

ヒント

このチュートリアルは、Tutorial: Control audio levelsから続きます。続ける前に、そのチュートリアルに従っておく必要があります。

デモプロジェクト

Tutorial: Control audio levelsのデモプロジェクトと同様に、このチュートリアルのデモプロジェクトは、単一のスライダーを含むウィンドウを表示します。今回、スライダーの値はデシベルで表されます。このデシベルの値は、音声処理アルゴリズムで使用される前に、リニアゲイン値に変換する必要があります。ほとんどのオーディオアプリケーションは、値が変化(または比較)されるときにより自然に感じることが多いため、デシベルでユーザーにゲインを表現します。デモプロジェクトのユーザーインターフェイスを次のスクリーンショットに示します。

The demo project main window showing the control slider in decibels.
The demo project main window showing the control slider in decibels.

カスタマイズされたスライダーの作成

今回は、スライダーの横に表示されるテキストがデシベルの値を表示するだけでなく、サフィックス「dB」も表示することに注意してください。これは、Sliderクラスを継承するカスタムスライダークラスDecibelSliderを作成することで実現されます。このカスタムスライダークラスでは、テキストボックスインターフェイスがカスタマイズされ、デシベルで値を表示します。Sliderオブジェクトのテキストボックス内に表示されるテキストへのサフィックスは、Slider::setTextValueSuffix()関数を使用して追加できますが、もう1つカスタマイズが必要です。これは、レベルが非常に低くなったときに**-INF dB**を表示できるように、値が変換される方法をカスタマイズすることです。

ヒント

INFinfinityを指し、-INF dBはアプリケーションの目的では無音として扱われます。

Decibelsクラス

Decibelsクラスには、デシベルとリニアゲインの間の変換に必要な多数の静的関数が含まれています。また、デシベルの値をStringオブジェクトに変換する簡単な手段も提供します。たとえば、Decibels::toString()関数を使用して、仮想関数Slider::getTextFromValue()をオーバーライドします(DecibelSliderクラス内で):

juce::String getTextFromValue (double value) override
{
return juce::Decibels::toString (value);
}

これにより、DecibelSliderクラスは、指定されたスライダー値に対してテキストボックスに適切なテキストを表示できます。

Decibelsクラスには、Stringオブジェクトをデシベルの値に戻す関数がないため、独自に記述する必要があります。ここでは、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」サフィックスを削除し(存在する場合)、テキストをトリミングして先頭と末尾の空白を削除します(String::trim()関数を使用)。
  • [2]: 残りのテキストが「-INF」であるかどうかをチェックし、この場合は-100を返します(これは、Decibelsクラスが使用する-INF dBのデシベルのデフォルト値です。この議論については、このチュートリアルのNotesを参照してください)。それ以外の場合は、この残りのテキストをdouble値に変換して返します。

スライダー値の使用

Tutorial: Control audio levelsでは、getNextAudioBlock()関数でスライダーの値に直接アクセスしました。デシベルからリニアゲインへの変換には、潜在的にCPU集約的な算術演算が含まれるため、特にオーディオスレッドで変換を頻繁に実行することは避けるのが賢明です。このチュートリアルのデモプロジェクトでは、MainContentComponentクラスにfloatメンバーlevelを格納し、リスナーを使用してスライダーが変更されたときにこの値を更新します。

コンストラクタでlevelメンバーをゼロに初期化し、Decibels::gainToDecibels()関数を使用してこれをデシベルに変換し、スライダーの初期位置を指定します(Slider::setValue()関数を使用)。次のようになります:

MainContentComponent()
{
decibelSlider.setValue (juce::Decibels::gainToDecibels (level));
decibelLabel.setText ("Noise Level in dB", juce::dontSendNotification);

デシベルからリニアゲインへの変換

Slider::onValueChangeヘルパーオブジェクトのラムダ関数で、スライダーが使用するデシベルスケールから音声処理に必要なリニアゲイン値への変換を実行します:

decibelSlider.onValueChange = [this] { level = juce::Decibels::decibelsToGain ((float) decibelSlider.getValue()); };

この関数は、コンストラクタでSlider::setValue()関数を使用してスライダーの値を最初に設定したときに呼び出されます。これにより、値が変更されたときにSlider::onValueChangeヘルパーオブジェクトに割り当てられたラムダ関数が呼び出され、levelメンバーが正しく設定されます。

注記

演習: MainContentComponentクラスに別のLabelオブジェクトを追加して、リニアゲイン値を表示します。- MainContentComponentクラスに別のSliderオブジェクトを追加して、リニアゲイン値を表示し、ユーザーがどちらのスライダーを使用してもノイズレベルを表示および指定できるようにします。どちらのスライダーが移動されても、両方のスライダーが正しく更新される必要があります。(異なる単位間の変換の簡単な例については、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;
}
}

これは、Tutorial: Control audio levelsgetNextAudioBlock()関数とほぼ同じであることに注意してください。ただしlevel値の関数ローカルコピーを取得してから、以前と同様にlevelScale値を計算します。

このアプローチの問題

このアプローチの1つの問題は、オーディオスレッドの実行中にlevel変数がその値を急激に変更する可能性があることです(この場合、getNextAudioBlockの2回の呼び出しの間)。このような変更は、通常、可聴クラックリングなどのオーディオアーティファクトを導入します。これを回避するために、実際のシンセサイザーでは、滑らかに変化するlevel値が必要です。これを実現する技術は、他のチュートリアルで説明されています(Tutorial: Build a sine wave synthesiserを参照)。

もう1つの問題は、スレッドセーフに関連しています。levelのようなメンバー変数を1つのスレッド(GUIスレッド)で書き込み、別のスレッド(オーディオスレッド)から同じ値を読み取ることは、スレッド同期が行われない場合(クリティカルセクションまたはアトミックを使用して)、C++では技術的に未定義動作です。これらの問題はこのチュートリアルの範囲を超えており、将来のチュートリアルで説明される予定です。この特定のケースでは、これについてあまり心配する必要はありません。一般的なアーキテクチャ(x86、x86_64、ARM)では、単一のfloatの読み取りと書き込みはアトミック操作であり、読み取りと書き込みが混在することはなく、一般的に安全だからです。

さらに考えると、levelScaleもメンバー変数にすることでコードをさらに最適化したくなるかもしれません(したがって、getNextAudioBlock()関数の呼び出しごとに計算する必要がありません)。しかし、そうすると2つのメンバー変数が単一のアトミック操作として更新されなくなります。もちろん、これを回避する方法もありますが、これはこのチュートリアルの範囲を超えています。

Notes

このチュートリアルのデモプロジェクトで提示されているコードは、-100 dB以下の値を-INF dB(つまり、リニアゲイン値がゼロ)として扱いたいことを前提としています。この-100 dBの値は、Decibelsクラスで使用されるデフォルト値ですが、計算でこれをオーバーライドできます。これは、Decibelsクラスの各関数に、-INF dBとして扱う値を指定する追加の引数を提供することで実現されます。たとえば、MainContentComponentコンストラクタでスライダー値を更新するときに-96 dB(以下)を-INF dBとして使用するには、次のようにします:

decibelSlider.setValue (juce::Decibels::gainToDecibels (level, -96.0f));

ただし、もちろん、アプリケーションのすべての部分が-INF dBに同じ値を使用することを確認する必要があります。DecibelSlider::getValueFromText()関数で-100.0をハードコーディングしているため、デモプロジェクトのコードには1つの潜在的な問題があります。Decibelsクラスがデフォルト値を(何らかの理由で)変更すると、コードが壊れます。残念ながら、このデフォルト値はDecibelsクラスのprivateメンバーであるため、Decibelsクラスにデフォルト値を尋ねることはできません。代わりに、独自のデフォルト値を指定し、これを全体で使用する必要があります。

注記

演習: デモプロジェクトを変更して、-INFの独自のデフォルト値を-96 dBとして指定します。DecibelSliderクラスとMainContentComponentクラスの両方を更新する必要があります。

まとめ

このチュートリアルでは、以下を紹介しました:

  • Decibelsクラス。
  • デシベルスケールとリニアゲインの間の変換。
  • デシベルスケールで値を表示するためのカスタムスライダーの作成。

関連項目