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

チュートリアルオーディオ波形を描く

📚 Source Page

を使ったオーディオ波形の表示について紹介します。AudioThumbnailクラスを使用します。これによって、オーディオ・アプリケーション内で任意の数の波形を簡単に描画できるようになります。

レベル:中級

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

クラス: AudioThumbnail,AudioThumbnailCache,AudioFormatReader,ChangeListener

スタート

注記

このチュートリアルはTutorial: Build an audio playerを読んで理解する必要がある。また、本書は、あなたが以下の項目について熟知していることを前提としている。Graphicsクラスと、コンポーネント内で描画を行うための Component::paint() 関数 (Tutorial: The Graphics class).

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

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

デモ・プロジェクト

デモ・プロジェクトでは、次のように3つのボタンが表示される。Tutorial: Build an audio player(サウンドファイルを開く、再生する、停止する)。

また、サウンドファイルの波形を描画する矩形領域もあります。デフォルトの状態(サウンドファイルがロードされていない状態)では、アプリケーションは以下のようになります:

デモプロジェクトの初期状態
デモプロジェクトの初期状態

サウンドファイルがロードされると、アプリケーションは次のようになる:

AudioThumbnailクラスを使ってファイルを開き、表示したデモ・プロジェクト。
AudioThumbnailクラスを使ってファイルを開き、表示したデモ・プロジェクト。

オーディオ波形の描画は、特に長いファイルの場合、一般的に、波形の描画が効率的で、ユーザーにもわかりやすいフォーマットで、オーディオデータの低解像度バージョンを保存する必要があります。そのためAudioThumbnailクラスがこの低解像度バージョンを処理し、必要に応じて作成・更新されます。

AudioThumbnailの設定

まず重要な点はAudioThumbnailクラスではないのサブクラスです。Componentクラスである。そのAudioThumbnailクラスは、オーディオ波形をpaint()別の関数Componentオブジェクトを追加します。以下のコードは、以下のデモ・プロジェクトに基づいてこの機能を追加する方法を示しています。Tutorial: Build an audio player.

追加オブジェクト

我々のMainContentComponentクラスに2つのメンバを追加する必要があります。AudioThumbnailCacheオブジェクトとAudioThumbnailオブジェクトになる。そのAudioThumbnailCacheクラスは、1つまたは複数のオーディオファイルの必要な低解像度バージョンをキャッシュするために使用されます。これは、例えば、あるファイルを閉じて新しいファイルを開き、また最初のファイルを開こうとするとAudioThumbnailCacheには最初のファイルの低解像度バージョンが残っているので、データを再スキャンして再計算する必要はない。もうひとつの便利な機能はAudioThumbnailCacheの異なるインスタンス間で共有できる。AudioThumbnailクラス

    juce::TextButton openButton;
juce::TextButton playButton;
juce::TextButton stopButton;

std::unique_ptr chooser;

juce::AudioFormatManager formatManager; // [3]
std::unique_ptr readerSource;
juce::AudioTransportSource transportSource;
TransportState state;
juce::AudioThumbnailCache thumbnailCache; // [1]
juce::AudioThumbnail thumbnail; // [2]

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

このような静的に割り当てられたオブジェクトを使用する場合、次のことが重要である。AudioThumbnailCacheオブジェクト[1]の前に表示される。AudioThumbnailオブジェクト[2]に引数として渡されるからである。AudioThumbnail constructor.ということも重要である。AudioFormatManagerオブジェクト[3]の前に表示される。AudioThumbnailオブジェクトも同じ理由である。

オブジェクトの初期化

のイニシャライザーリストではMainContentComponentコンストラクターでこれらのオブジェクトをセットアップする:

    MainContentComponent()
: state (Stopped),
thumbnailCache (5), // [4]
thumbnail (512, formatManager, thumbnailCache) // [5]
{
  • [4](その)AudioThumbnailCacheオブジェクトは、保存するサムネイルの数で構築されなければならない。
  • [5](その)AudioThumbnailオブジェクトは、1 つのサムネイルサンプルを作成するために使用されるソースサンプルの数を指定することで、それ自体を構築する必要があります。これが低解像度バージョンの解像度を決定します。他の 2 つの引数はAudioFormatManagerそしてAudioThumbnailCacheオブジェクトがある。

についてAudioThumbnailクラスもChangeBroadcasterクラスに登録することができます。変更のリスナーとして登録できる[6](私たちのMainContentComponentコンストラクタ)。これらの変更はAudioThumbnailオブジェクトが変更されたので、波形の描画を更新する必要がある。

        thumbnail.addChangeListener (this);            // [6]

変化への対応

我々のchangeListenerCallback()関数からブロードキャストされているかどうかを判断する必要がある。AudioTransportSourceオブジェクトまたはAudioThumbnailオブジェクトがある:

    void changeListenerCallback (juce::ChangeBroadcaster* source) override
{
if (source == &transportSource) transportSourceChanged();
if (source == &thumbnail) thumbnailChanged();
}

についてtransportSourceChanged()の変更に対応するためのオリジナルのコードを含むだけである。AudioTransportSourceオブジェクトがある:

    void transportSourceChanged()
{
changeState (transportSource.isPlaying() ? Playing : Stopped);
}

もしそれがAudioThumbnailオブジェクトが変更されたら、Component::repaint()関数を呼び出します。これによりpaint()関数を呼び出す:

    void thumbnailChanged()
{
repaint();
}

ファイルを開く

サウンドファイルをうまく開いたら、そのファイルをAudioThumbnailオブジェクト[7]の中にある。FileInputSourceオブジェクトがある。

    void openButtonClicked()
{
chooser = std::make_unique ("Select a Wave file to play...",
juce::File{},
"*.wav");
auto chooserFlags = juce::FileBrowserComponent::openMode
| juce::FileBrowserComponent::canSelectFiles;

chooser->launchAsync (chooserFlags, [this] (const juce::FileChooser& fc)
{
auto file = fc.getResult();

if (file != juce::File{})
{
auto* reader = formatManager.createReaderFor (file);

if (reader != nullptr)
{
auto newSource = std::make_unique (reader, true);
transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
playButton.setEnabled (true);
thumbnail.setSource (new juce::FileInputSource (file)); // [7]
readerSource.reset (newSource.release());
}
}
});
}

描画の実行

我々のpaint()関数では、まず描画する矩形を計算する。次にAudioThumbnailこれは、ファイルがロードされているかどうかを示す:

    void paint (juce::Graphics& g) override
{
juce::Rectangle thumbnailBounds (10, 100, getWidth() - 20, getHeight() - 120);

if (thumbnail.getNumChannels() == 0)
paintIfNoFileLoaded (g, thumbnailBounds);
else
paintIfFileLoaded (g, thumbnailBounds);
}

もしファイルが読み込まれていなければ、次のようなメッセージが表示される。ファイルが読み込まれない私たちのpaintIfNoFileLoaded()関数を使用する。Graphicsオブジェクトと境界の矩形:

    void paintIfNoFileLoaded (juce::Graphics& g, const juce::Rectangle& thumbnailBounds)
{
g.setColour (juce::Colours::darkgrey);
g.fillRect (thumbnailBounds);
g.setColour (juce::Colours::white);
g.drawFittedText ("No File Loaded", thumbnailBounds, juce::Justification::centred, 1);
}

重要なのは次だ。もし私たちがするファイルをロードすれば、波形を描くことができる:

    void paintIfFileLoaded (juce::Graphics& g, const juce::Rectangle& thumbnailBounds)
{
g.setColour (juce::Colours::white);
g.fillRect (thumbnailBounds);

g.setColour (juce::Colours::red); // [8]

thumbnail.drawChannels (g, // [9]
thumbnailBounds,
0.0, // start time
thumbnail.getTotalLength(), // end time
1.0f); // vertical zoom
}

を使用する際の基本的なポイントをすべて網羅している。AudioThumbnailオブジェクトがある。

エクササイズ

実際には、サウンドファイルの特定の領域だけを表示したい場合が多い。それはAudioThumbnail::drawChannels()関数は、JUCEを使用して、これがいかに簡単に実装できるかを示しています。ファイルの特定の領域だけを表示するようにコードを修正してみてください。

タイムポジションマーカーの追加

このセクションでは、ファイル再生の現在時刻を表示する縦線の追加について説明します。

タイマーの追加

まず最初にTimerクラスを基本クラスのリストに加える。[10]:

class MainContentComponent   : public juce::AudioAppComponent,
public juce::ChangeListener,
private juce::Timer // [10]
{
public:

次に、タイマー・コールバックがコンポーネントを再描画するようにします。このコードがprivateのセクションを私的に継承していることにお気づきだろう。Timerクラスである:

    void timerCallback() override
{
repaint();
}

の中でMainContentComponentコンストラクタでタイマーをスタートさせる必要がある。[11]- 40ミリ秒ごとで十分だろう:

        startTimer (40);                                  // [11]
}
エクササイズ

実際、ファイルが正常にオープンされた時点でタイマーをスタートさせれば、タイマーのスタートを遅らせることができる。

ポジションラインを引く

最後に、線を引くために、線の位置を計算し、線を引く必要がある。その後サムネイルを描く

    void paintIfFileLoaded (juce::Graphics& g, const juce::Rectangle& thumbnailBounds)
{
g.setColour (juce::Colours::white);
g.fillRect (thumbnailBounds);

g.setColour (juce::Colours::red);

auto audioLength = (float) thumbnail.getTotalLength(); // [12]
thumbnail.drawChannels (g, thumbnailBounds, 0.0, audioLength, 1.0f);

g.setColour (juce::Colours::green);

auto audioPosition = (float) transportSource.getCurrentPosition();
auto drawPosition = (audioPosition / audioLength) * (float) thumbnailBounds.getWidth()
+ (float) thumbnailBounds.getX(); // [13]
g.drawLine (drawPosition, (float) thumbnailBounds.getY(), drawPosition,
(float) thumbnailBounds.getBottom(), 2.0f); // [14]
}
  • [12]この値を2回使う必要があるので、ファイルの長さを変数に格納する。
  • [13]位置はオーディオファイルの全長に対する割合で計算されます。線を引く位置は、サムネイルが描画される矩形の幅と同じ割合にする必要があります。矩形のx座標に基づいて描画位置をオフセットする必要があります。
  • [14]ここでは、矩形の上端(y)と下端(b)の間に幅2ピクセルの線を引く。

これでアプリケーションをビルドして実行できるはずだ。

注記

この修正版アプリケーションのソースコードはAudioThumbnailTutorial_02.hファイルにある。

警告

この例の問題点は、40ミリ秒ごとにコンポーネントを強制的に再描画していることです。これは単純な例では許容できるかもしれませんが、より複雑なケースではパフォーマンスの問題にぶつかるでしょう。これについては、下の演習を見てください。

エクササイズ

図面を別の子コンポーネントに分離する (Tutorial: Parent and child components).つのコンポーネントが必要です:

  • オーディオ波形を描画するコンポーネント
  • 再生位置を垂直線として描画するコンポーネント
  • この2つの子コンポーネントを含むメインの親コンポーネント

こうすることで、コードが簡単になるだけでなく、正しく行えば、フレームごとに波形を再描画する必要がなくなるので、より効率的になります。また、ユーザーが波形をクリックした場合に再生位置を変更する機能を追加することもできる。

注記

この練習を実装するためのソースコードはAudioThumbnailTutorial_03.hそしてAudioThumbnailTutorial_04.hのファイルにある。

概要

このチュートリアルではAudioThumbnailクラスと、それをオーディオ・アプリケーションに統合する方法について説明しました。特に

  • 初期化AudioThumbnailそしてAudioThumbnailCacheオブジェクトがある。
  • を使用している。AudioThumbnailクラスをコンポーネント内で使用します。
  • コンポーネントを構造化することで、描画が複雑なコンテンツが不必要に再描画されないようにする。

こちらも参照