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

チュートリアルオーディオプレーヤーの構築

📚 Source Page

このチュートリアルでは、サウンドファイルを開いて再生する方法について説明します。これには、JUCEでサウンドファイルを扱うためのいくつかの重要なクラスが含まれます。

レベル:中級

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

クラス: AudioFormatManager,AudioFormatReader,AudioFormatReaderSource,AudioTransportSource,FileChooser,ChangeListener,File,FileChooser

スタート

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

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

デモ・プロジェクト

このデモ・プロジェクトでは、サウンド・ファイルの再生をコントロールするための3つのボタン・インターフェースを紹介します。つのボタンは

  • ユーザーがサウンドファイルを選択するためのファイル選択ボタンを表示します。
  • サウンドを再生するボタン
  • 音を止めるボタン

インターフェイスは以下のスクリーンショットの通り:

3つのボタンでサウンドファイルの再生をコントロールするインターフェース。
3つのボタンでサウンドファイルの再生をコントロールするインターフェース。

役に立つクラス

AudioSourceクラス

でサンプルごとにオーディオを生成することができます。getNextAudioBlock()Audio Application テンプレートには、オーディオを生成・処理するためのツールが組み込まれています。これらにより、アプリケーションコード内でオーディオの各サンプルを処理することなく、高レベルのビルディングブロックをリンクして、強力なオーディオアプリケーションを形成することができます(JUCEがこれを代行します)。これらのビルディングブロックはAudioSourceクラスである。実際、もしあなたがAudioAppComponentクラスである、Tutorial: Build a white noise generator- を利用している。AudioSourceクラスがすでにある。そのAudioAppComponentクラス自体はAudioSourceクラスが含まれていることが重要です。AudioSourcePlayerオブジェクトの間でオーディオをストリーミングする。AudioAppComponentとオーディオハードウェアデバイスの間で、オーディオサンプルを直接生成することができます。オーディオ・サンプルを直接getNextAudioBlock()関数の代わりにAudioSourceオブジェクトを組み合わせて一連のプロセスを形成する。このチュートリアルではこの機能を利用する。

オーディオフォーマット

JUCE は、多くの形式のサウンドファイルを読み書きするためのツールを提供しています。このチュートリアルでは、特に以下のクラスを使用します:

  • AudioFormatManager:このクラスにはオーディオ・フォーマット(WAV、AIFF、Ogg Vorbisなど)のリストが含まれており、これらのフォーマットからオーディオ・データを読み取るのに適したオブジェクトを作成することができます。
  • AudioFormatReader:このクラスは、オーディオファイルに対する低レベルのファイル読み込み操作を処理し、オーディオを一貫したフォーマットで読み込むことを可能にします。float値)。を指定する。AudioFormatManagerオブジェクトが特定のファイルを開くように要求されると、このクラスのインスタンスを生成する。
  • AudioFormatReaderSource:のサブクラスである。AudioSourceクラスです。このクラスはAudioFormatReaderオブジェクトを使ってオーディオをレンダリングする。getNextAudioBlock()関数である。
  • AudioTransportSource:このクラスはAudioSourceクラスです。このクラスはAudioFormatReaderSourceオブジェクトを制御する。このコントロールにはAudioFormatReaderSourceオブジェクトに変換できます。また、サンプル・レートの変換も可能で、希望すればオーディオを前もってバッファリングすることもできる。

まとめる

これから、これらのクラスと適切なユーザー・インターフェース・クラスを組み合わせて、サウンドファイル再生アプリケーションを作ります。この時点で、様々なフェーズについて考えておくと便利です。運輸省- オーディオファイルの再生オーディオファイルがロードされると、以下の4つの状態が考えられる:

  • 停止:オーディオ再生が停止し、開始できる状態。
  • スタート:オーディオ再生はまだ開始されていないが、開始するように指示されている。
  • プレー:オーディオ再生中
  • 停止:音声は再生中ですが、再生停止を指示されます。停止の状態だ。

これらの状態を表すためにenum我々のMainContentComponentクラスである:

    enum TransportState
{
Stopped,
Starting,
Playing,
Stopping
};

インターフェイスの初期化

のコンストラクタでMainContentComponentクラスで、3つのボタンを設定する:

    MainContentComponent()
: state (Stopped)
{
addAndMakeVisible (&openButton);
openButton.setButtonText ("Open...");
openButton.onClick = [this] { openButtonClicked(); };

addAndMakeVisible (&playButton);
playButton.setButtonText ("Play");
playButton.onClick = [this] { playButtonClicked(); };
playButton.setColour (juce::TextButton::buttonColourId, juce::Colours::green);
playButton.setEnabled (false);

addAndMakeVisible (&stopButton);
stopButton.setButtonText ("Stop");
stopButton.onClick = [this] { stopButtonClicked(); };
stopButton.setColour (juce::TextButton::buttonColourId, juce::Colours::red);
stopButton.setEnabled (false);

特にプレーそしてストップボタンは初期状態ではそのプレーボタンは、有効なファイルがロードされると有効になる。ここでは、ラムダ関数をButton::onClickこれら3つのボタンそれぞれに対応するヘルパー・オブジェクト (Tutorial: Listeners and Broadcasters).また、コンストラクタのイニシャライザーリストでトランスポートの状態を初期化する。

その他の初期化

の3つである。TextButtonには他に4人のメンバーがいる。MainContentComponentクラスである:

    juce::AudioFormatManager formatManager;
std::unique_ptr readerSource;
juce::AudioTransportSource transportSource;
TransportState state;

ここではAudioFormatManager,AudioFormatReaderSourceそしてAudioTransportSource先に述べたクラスである。

の中でMainContentComponentコンストラクタでAudioFormatManagerオブジェクトに標準フォーマットのリストを登録する。[1]:

        formatManager.registerBasicFormats();       // [1]

これにより、最低限AudioFormatManagerオブジェクトを使ってWAVとAIFFフォーマットのリーダーを作成することができます。その他のフォーマットは、プラットフォームとjuce_audio_formatsモジュールをProjucerプロジェクトに追加した:

juce_audio_formatsモジュールのオプションは、オーディオフォーマットのオプションを表示します。
juce_audio_formatsモジュールのオプションは、オーディオフォーマットのオプションを表示します。

の中でMainContentComponentコンストラクタにMainContentComponentオブジェクトをリスナーとして[2]に対するAudioTransportSourceオブジェクトの状態の変化(たとえば、停止したときなど)に対応できるようにする:

        transportSource.addChangeListener (this);   // [2]
注記

関数名はaddChangeListener()この場合、単にaddListener()JUCEの他の多くのリスナー・クラスと同様です。

AudioTransportSourceの変更への対応

輸送の変更が報告されるとchangeListenerCallback()関数が呼び出される。この関数はメッセージスレッド上で非同期に呼び出される:

    void changeListenerCallback (juce::ChangeBroadcaster* source) override
{
if (source == &transportSource)
{
if (transportSource.isPlaying())
changeState (Playing);
else
changeState (Stopped);
}
}

これは単にメンバ関数を呼び出しているだけであることがわかる。changeState().

州の変更

輸送状態の変化は、この一つの機能に集約される。changeState().これにより、この機能のすべてのロジックを一箇所にまとめることができます。この関数はstateメンバは、この新しい状態にあるときに起こる必要がある他のオブジェクトへの変更をトリガする。

注記

経験豊富な読者の方はstate design patternこのコードを構成する別の方法として。

    void changeState (TransportState newState)
{
if (state != newState)
{
state = newState;

switch (state)
{
case Stopped: // [3]
stopButton.setEnabled (false);
playButton.setEnabled (true);
transportSource.setPosition (0.0);
break;

case Starting: // [4]
playButton.setEnabled (false);
transportSource.start();
break;

case Playing: // [5]
stopButton.setEnabled (true);
break;

case Stopping: // [6]
transportSource.stop();
break;
}
}
}
  • [3]トランスポートが停止を無効にする。ストップボタンを押すとプレーボタンをクリックし、トランスポートポジションをファイルの最初に戻す。
  • [4](その)スタートの状態は、ユーザーがプレーボタンを押すとAudioTransportSourceオブジェクトの再生を開始する。この時点でプレーボタンもある。
  • [5](その)プレー状態がトリガーされる。AudioTransportSourceオブジェクトがchangeListenerCallback()関数を使用します。ここではストップボタンをクリックする。
  • [6](その)停止の状態は、ユーザーがストップボタンをクリックします。AudioTransportSourceオブジェクトを停止させる。

オーディオの処理

このデモ・プロジェクトでのオーディオ処理は非常に単純である。AudioTransportSourceオブジェクトに渡す。AudioSourceChannelInfoを介して渡されたAudioAppComponentクラスである:

    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
if (readerSource.get() == nullptr)
{
bufferToFill.clearActiveBufferRegion();
return;
}

transportSource.getNextAudioBlock (bufferToFill);
}

有効なAudioFormatReaderSourceオブジェクトを最初に生成し、そうでなければ出力をゼロにするだけである(便利なAudioSourceChannelInfo::clearActiveBufferRegion()関数)。そのAudioFormatReaderSourceメンバはstd::unique_ptrオブジェクトに格納される。これは、ユーザーのアクションに基づいて動的にオブジェクトを生成する必要があるからである。またnullptr無効なオブジェクトに対して

を渡すことも忘れてはならない。prepareToPlay()コールバックを他のAudioSource私たちが使っているオブジェクト:

    void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
transportSource.prepareToPlay (samplesPerBlockExpected, sampleRate);
}

そしてreleaseResources()コールバックも:

    void releaseResources() override
{
transportSource.releaseResources();
}

ファイルを開く

ファイルを開くにはFileChooserオブジェクトに応答する。**オープン...**ボタンがクリックされる:

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

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

if (file != juce::File{}) // [9]
{
auto* reader = formatManager.createReaderFor (file); // [10]

if (reader != nullptr)
{
auto newSource = std::make_unique (reader, true); // [11]
transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate); // [12]
playButton.setEnabled (true); // [13]
readerSource.reset (newSource.release()); // [14]
}
}
});
}
  • [7]を作成する。FileChooserオブジェクトに短いメッセージを添えて、ユーザーが以下のものだけを選択できるようにする。.wavファイルである。
  • [8]をポップアップします。FileChooserオブジェクトがある。
  • [9]もしif()ユーザが(キャンセルではなく)実際にファイルを選択した場合に成功します。
  • [10](その)AudioFormatManager::createReaderFor()関数は、この特定のファイル用のリーダーを作成するために使用される。これはnullptr失敗した場合(たとえば、ファイルがオーディオフォーマットでない場合AudioFormatManagerオブジェクトが処理できる)。
  • [11]新しいAudioFormatReaderSourceオブジェクトを作成した。番目の引数trueが欲しいことを示している。AudioFormatReaderSourceオブジェクトを管理する。AudioFormatReaderオブジェクトを保存し、不要になったら削除する。私たちはAudioFormatReaderSourceオブジェクトを一時的なstd::unique_ptrオブジェクトに変換することで、以前に割り当てられたAudioFormatReaderSourceそれ以降のファイルを開くコマンドの実行を早める。
  • [12](その)AudioFormatReaderSourceオブジェクトがAudioTransportSourceオブジェクトで使用されています。getNextAudioBlock()関数を使用します。ファイルのサンプル・レートがハードウェアのサンプル・レートと一致しない場合は、第4引数としてこれを渡します。AudioFormatReaderオブジェクトを参照してください。参照Notes第2引数と第3引数の詳細はこちら。そのAudioTransportSourceソースは、必要なサンプルレート変換を処理します。
  • [13](その)プレーボタンをクリックできるようにする。
  • [14]としている。AudioTransportSourceは、新しく割り当てられたAudioFormatReaderSourceオブジェクトに安全に格納できる。AudioFormatReaderSourceオブジェクトをreaderSourceメンバーです。(で述べたようにProcessing the audio上記参照)。そのためには、所有権をローカルのnewSource変数は、std::unique_ptr::release() を使う。
注記

新しく割り当てられたAudioFormatReaderSourceオブジェクトを一時的なstd::unique_ptrオブジェクトに変換することで、例外セーフになるという利点もある。関数呼び出し中に例外がスローされる可能性があるAudioTransportSource::setSource()この場合、std::unique_ptr オブジェクトはAudioFormatReaderSourceオブジェクトが不要になった。もし、この時点で生のポインタを使ってAudioFormatReaderSource例外がスローされた場合、ポインタがぶら下がったままになってしまうからだ。

ファイルの再生と停止

実際にファイルを再生するコードはすでにセットアップしてあるので、あとはchangeState()関数に適切な引数を与えてファイルを再生する。その際プレーボタンがクリックされたら、次のようにする:

    void playButtonClicked()
{
changeState (Starting);
}

ファイルの停止も同様に簡単だ。ストップボタンがクリックされる:

    void stopButtonClicked()
{
changeState (Stopping);
}
エクササイズ

3番目の(filePatternsAllowedを作成する際の引数)。FileChooserオブジェクトを使うことで、アプリケーションがAIFFファイルもロードできるようになる。ファイル・パターンはセミコロンで区切ることができるので、次のようにします。"*.wav;*.aif;*.aiff"の2つの一般的なファイル拡張子を使用できるようにします。

一時停止機能の追加

を追加する手順を説明します。ポーズ機能をアプリケーションに追加します。ここではプレーボタンがポーズボタンを無効にする代わりに)ファイル再生中にまたストップボタンがゼロに戻すボタンをクリックします。

まず、2つの状態を追加する必要がある。一時停止そして休止私たちのTransportStateを列挙する:

    enum TransportState
{
Stopped,
Starting,
Playing,
Pausing,
Paused,
Stopping
};

私たちのchangeState()関数は2つの新しいステートを処理する必要があり、他のステートのコードも更新する必要がある:

    void changeState (TransportState newState)
{
if (state != newState)
{
state = newState;

switch (state)
{
case Stopped:
playButton.setButtonText ("Play");
stopButton.setButtonText ("Stop");
stopButton.setEnabled (false);
transportSource.setPosition (0.0);
break;

case Starting:
transportSource.start();
break;

case Playing:
playButton.setButtonText ("Pause");
stopButton.setButtonText ("Stop");
stopButton.setEnabled (true);
break;

case Pausing:
transportSource.stop();
break;

case Paused:
playButton.setButtonText ("Resume");
stopButton.setButtonText ("Return to Zero");
break;

case Stopping:
transportSource.stop();
break;
}
}
}

ボタンを適切に有効・無効にし、それぞれの状態でボタンのテキストを正しく更新します。

で一時停止を求められたときに、実際にトランスポートを停止していることに注目してほしい。一時停止の状態である。その中でchangeListenerCallback()関数では、一時停止リクエストか停止リクエストかによって、正しい状態に移行するようにロジックを変更する必要がある:

    void changeListenerCallback (juce::ChangeBroadcaster* source) override
{
if (source == &transportSource)
{
if (transportSource.isPlaying())
changeState (Playing);
else if ((state == Stopping) || (state == Playing))
changeState (Stopped);
else if (Pausing == state)
changeState (Paused);
}
}

を変更する必要がある。プレーボタンがクリックされる:

    void playButtonClicked()
{
if ((state == Stopped) || (state == Paused))
changeState (Starting);
else if (state == Playing)
changeState (Pausing);
}

そしてストップボタンがクリックされる:

    void stopButtonClicked()
{
if (state == Paused)
changeState (Stopped);
else
changeState (Stopping);
}

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

注記

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

エクササイズ

追加Labelオブジェクトをインターフェイスに追加する。AudioTransportSourceオブジェクトを使用することができます。オブジェクトはAudioTransportSource::getCurrentPosition()関数を使用してこのポジションを取得する。またMainContentComponentクラスはTimerクラスで定期的な更新を行います。timerCallback()関数を使用してラベルを更新することもできる。さらにRelativeTimeクラスを使用して、生の秒単位の時間をより便利な分、秒、ミリ秒の形式に変換することができます。

注記

この演習のソースコードはPlayingSoundFilesTutorial_03.hファイルにある。

概要

このチュートリアルでは、サウンドファイルの読み込みと再生について紹介しました。特に、以下の事柄を取り上げました:

備考

の2番目と3番目の引数である。AudioTransportSource::setSource()関数を使用すると、バックグラウンド・スレッドで先読みバッファリングを制御できる。第2引数は使用するバッファサイズ、第3引数はTimeSliceThreadこのオブジェクトはバックグラウンド処理に使用される。この例では、バッファサイズをゼロにしてnullptrこれはデフォルトである。

こちらも参照