cppにプログラミングをしていきます。
GUIへの描画も初めてだね
JUCEプログラミング、Draw audio waveformsのチュートリアルを引き続きやっていきます。
今回は、cppを実装して、プログラムの中身も見ていきたいと思います。
前回の記事の続きとなります。
こんな人の役に立つかも
・JUCEフレームワークに入門したい人
・JUCEで音声プログラミングを学びたい人
・JUCEのチュートリアルを進めている人
MainComponent.cpp
コンストラクタ
まずは、コンストラクタで初期化です。
MainComponent::MainComponent()
: state(Stopped),
thumbnailCache(5), //[1]初期化します。
thumbnail(512, formatManager, thumbnailCache) //[1]
{
//...略...
formatManager.registerBasicFormats();
transportSource.addChangeListener(this);
thumbnail.addChangeListener(this);//[2]リスナー関数の設定です。
setAudioChannels(0, 2);
setSize (800, 600);
}
[1]では、ヘッダーで定義した「AudioThumbnailCache」クラスのthumbnailCacheと、「AudioThumbnail」クラスのthumbnailをイニシャライザで初期化しています。
AudioThumbnailCacheクラスは、初期化時にintで何枚のサムネイルを格納するかを指定できるようです。今回は、5を指定しています。
また、AudioThumbnailは(サンプル数、AudioFormatManager、AudioThumbnailCache)を与えて初期化します。
[2]では、AudioThumbnailオブジェクトが更新されて、波形描画が更新される必要がある時に発生するリスナー関数を設定しています。
コールバック関数
以前のコールバック関数との変化がわかりやすいようにコメントアウトで残しています。
/*↓以前のプログラム
void MainComponent::changeListenerCallback(juce::ChangeBroadcaster* source)
{
if (source == &transportSource)
{
if (transportSource.isPlaying())
changeState(Playing);
else
changeState(Stopped);
}
}
*/
//以下のプログラムに変更しています。
void MainComponent::changeListenerCallback(juce::ChangeBroadcaster* source)
{
if (source == &transportSource) transportSourceChanged();
if (source == &thumbnail) thumbnailChanged();
}
void MainComponent::transportSourceChanged()
{
changeState(transportSource.isPlaying() ? Playing : Stopped);
}
void MainComponent::thumbnailChanged()
{
repaint();
}
2つのオブジェクトがリスナー関数に紐づけられています。一つは、「transportSource」で、もう一つが「thumbnail」です。この二つの変更が起きたときにリスナー関数「changeListenerCallback」は動作しますので、どちらのオブジェクトに変更があったかを条件判定して実行する処理を決定する必要があります。if文で「source == &transportSource」のように判定することでどの変化によるコールバックなのか、判定ができます。
transportSourceの変更時の処理を「transportSourceChanged」として外部の関数にまとめ、thumbnailの変更時の処理を「thumbnailChanged」という外部の関数にまとめています。transportSourceChanged関数の中身は、三項演算子を利用しているだけで、以前の処理内容と同様のものになります。
thumbnailChanged関数では、repaint関数を呼び出して、GUIの描画を再度行います。(paint関数の内容を実行します。)
openButtonClicked関数
音声ファイルを開く処理で、thumbnailを設定します。
void MainComponent::openButtonClicked()
{
juce::FileChooser chooser("Select a Wave file to play...",
{},
"*.wav");
if (chooser.browseForFileToOpen())
{
auto file = chooser.getResult();
auto* reader = formatManager.createReaderFor(file);
if (reader != nullptr)
{
std::unique_ptr<juce::AudioFormatReaderSource> newSource(new juce::AudioFormatReaderSource(reader, true));
transportSource.setSource(newSource.get(), 0, nullptr, reader->sampleRate);
playButton.setEnabled(true);
thumbnail.setSource(new juce::FileInputSource(file));//ここを追加しました。
readerSource.reset(newSource.release());
}
}
}
AudioThumbnailクラスの「setSource」関数に、「juce::FileInputSource」クラスのファイルを与えます。
与える変数はこういうものを与えるんですね~、という感じで進めています。
この辺りは用法が決まってきそう。
波形の描画に関する機能
paint関数
paint関数は、以下のように書き換えます。
void MainComponent::paint (juce::Graphics& g)
{
juce::Rectangle<int> thumbnailBounds(10, 100, getWidth() - 20, getHeight() - 120);//[1]描画領域を設定します。
//[2]サムネイルのチャンネル数が0のとき、とそれ以外の条件です。
if (thumbnail.getNumChannels() == 0)
paintIfNoFileLoaded(g, thumbnailBounds);
else
paintIfFileLoaded(g, thumbnailBounds);
}
[1]では「juce::Rectangle」クラスを使い、波形のサムネイル表示の描画領域を設定しています(thumbnailBounds)。(x,y,幅,高さ)で指定します。getWidthでは、ウィンドウの描画領域全体の幅から、20を引くことで、左右に10づつのマージンを設定するようにしています。高さも同様です。これで、ウィンドウサイズに合わせて描画領域が変化します。
[2]では、音声ファイルが存在していないとき(サムネイルの音声ファイルのチャンネル数が0のときで判定)、波形の描画領域に「No File Loaded」と表示します。描画自体はpaintIfNoFileLoaded関数を外部に定義しています。音声ファイルが存在するときはelse条件のpaintIfFileLoaded関数で音声波形のサムネイルを描画する処理を行います。
描画で呼び出す関数
paintIfNoFileLoaded
実際に描画する関数になります。まずは、No File Loadedの文字を描画する関数です。
//[1]thumbnailに音声ファイルが読み込まれなかった場合の処理です。
void MainComponent::paintIfNoFileLoaded(juce::Graphics& g, const juce::Rectangle<int>& thumbnailBounds)
{
g.setColour(juce::Colours::darkgrey);//darkgrayの色設定
g.fillRect(thumbnailBounds);//領域を塗りつぶします。
g.setColour(juce::Colours::white);//white色の設定
g.drawFittedText("No File Loaded", thumbnailBounds, juce::Justification::centred, 1);//テキストの描画を行います。
}
gというjuceのgraphicクラスを利用して描画を行います。色の設定(setColour)→描画(fillRectまたは、drawFittedText)という流れで行ってるのがわかります。
fillRectはjuce::Rectangleを与えることで四角が描画できます。
drawFittedTextでは文字が描画できます。
paintIfFileLoaded
最後に、波形のサムネイルを描画する関数です。
void MainComponent::paintIfFileLoaded(juce::Graphics& g, const juce::Rectangle<int>& thumbnailBounds)
{
//[1]波形の背景を白色で描画します。
g.setColour(juce::Colours::white);
g.fillRect(thumbnailBounds);
//[2]音声波形を赤色で描画します。
g.setColour(juce::Colours::red);
thumbnail.drawChannels(g,
thumbnailBounds,
0.0,
thumbnail.getTotalLength(),
1.0f);
}
[1]で、最初に音声波形を描画する部分の背景を白色に塗りつぶしておきます。[2]では、AudioThumbnailクラスの「drawChannels」関数を使い、音声の波形を描画していきます。描画領域はthumbnailBoundsを与えることでその領域にフィットした音声波形が描画されるようです。
プログラム例
今回のプログラムは、GitHubリポジトリにアップロードしましたので、ご活用ください。