Draw audio waveformsチュートリアルにもどってきました。
子コンポーネントについては理解したから進めるね~
JUCEプログラミング、「Draw audio waveforms」チュートリアルに戻ってきました。現状で実装した波形描画のアプリだと、描画部分を「MainComponent」のタイマーを利用して処理しているので、本格的なアプリになったときに、描画処理がユーザーの操作に反応する処理を妨げたりして、アプリの応答性が低下する可能性もあるとのことです。そのため、子コンポーネントとして描画処理を分離して描画処理をアプリの「MainComponent」クラスと機能分離していくような内容となります。
前回までの「Draw audio waveforms」チュートリアルはこちらの記事です。
今回やっていく内容は、チュートリアルの最後の「Exercise」になります。ZIPでダウンロードできるプログラムとしては、「AudioThumbnailTutorial_03.h」の内容となります。
Exerciseの内容ですので、答えの要素が含まれていることご了承ください。じぶんでチャレンジしたい方は、まだ見ないほうが良いと思います。
このチュートリアルの参考となる「親と子のコンポーネント」に関する記事もご参照ください。
こんな人の役に立つかも
・JUCEチュートリアル「Draw audio waveforms」のExerciseの実装例を知りたい人
プログラムの方向性と概要
ベースとするプログラム
プログラムのベースとして、以前作成した波形表示の音声再生アプリをベースとしました。次のGitHubリポジトリにもアップロードしていますので、ご利用ください。
子コンポーネントとして、波形を描画していた処理と、現在再生位置の垂直線を描画していた処理をそれぞれ「DrawComponent」というファイルにまとめました。
Projucerで上のスクショのように「.h」「.cpp」を追加します。
※もちろん、mainComponentのファイルのみに実装することも可能ですが、何となく今回は子コンポーネントとして描画関連のクラスを追加していますので、このようにしてみました。
DrawComponent.h
DrawComponent.hファイルに、2つのクラスを追加していきます。
SimpleThumbnailComponentクラス
#include <JuceHeader.h>
#pragma once
class SimpleThumbnailComponent : public juce::Component,
private juce::ChangeListener
//[1]ChangeListenerを継承します。
{
public:
//[2]それぞれの関数を定義します。
SimpleThumbnailComponent(int sourceSamplesPerThumbnailSample,
juce::AudioFormatManager& formatManager,
juce::AudioThumbnailCache& cache);
void setFile(const juce::File& file);
void paint(juce::Graphics& g) override;
void paintIfNoFileLoaded(juce::Graphics& g);
void paintIfFileLoaded(juce::Graphics& g);
void changeListenerCallback(juce::ChangeBroadcaster* source) override;
private:
void thumbnailChanged();
juce::AudioThumbnail thumbnail;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SimpleThumbnailComponent)
};
SimpleThumbnailComponentクラスは、音声波形を描画する処理を担います。
[1]では、「ChangeListener」クラスを継承します。ChangeListenerクラスは、AudioThumbnailクラスの変化を受け取ることができます。そのため、サムネイルが変化したときに描画処理を実行するようなコールバック関数を実行することができるようにしていきます。
[2]では、この後実装していく関数を定義しています。以前MainComponentクラスに実装されていたAudioThumbnail関連の処理をこのクラスに移動させたイメージになります。
「setFile関数」はこのクラスに分離してから追加されました。以前はMainComponentクラスでAudioThumbnailクラスのsetSource関数を直接実行して音声ファイルのthumbnailを読み込みましたが、MainComponentから呼び出してsetFileでこのクラス内のAudioThumbnailオブジェクトのthumbnailへとへファイルをsetSourceするようになりました。
SimplePositionOverlayクラス
class SimplePositionOverlay : public juce::Component,
private juce::Timer
//[1]
{
public:
SimplePositionOverlay(const juce::AudioTransportSource& transportSourceToUse);
void paint(juce::Graphics& g) override;
private:
void timerCallback() override;
const juce::AudioTransportSource& transportSource;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SimplePositionOverlay)
};
SimplePositionOverlayクラスは、再生中位置の垂直線を描く機能を持ったクラスです。[1]のように、以前mainComponentクラスで継承していたTimerクラスをこのクラスが代わりに継承して、40msec毎に現在位置の描画を更新していきます。
コンストラクタで、transportSourceを受け取り、その音声ファイルから情報を取得(再生位置など)して40msec毎にpaintで描画する、というシンプルなクラスになっています。
DrawComponent.cpp
先ほどヘッダファイルに定義した関数を「DrawComponent.cpp」へと実装していきます。
SimpleThumbnailComponentクラス
#include "DrawComponent.h"
//SimpleThumbnailComponent
//[1]コンストラクタ
SimpleThumbnailComponent::SimpleThumbnailComponent(int sourceSamplesPerThumbnailSample,
juce::AudioFormatManager& formatManager,
juce::AudioThumbnailCache& cache)
: thumbnail(sourceSamplesPerThumbnailSample, formatManager, cache)
{
thumbnail.addChangeListener(this);
}
まずは、コンストラクタのイニシャライザでprivateなメンバであるAudioThumbnailクラスのオブジェクト「thumbnail」を初期化(読み込み)します。そして、コンストラクタの中でthumbnailの変更を検知するためのコールバック関数を紐づけます。これで音声を読み込むとコールバック関数が実行されるようになりました。
void SimpleThumbnailComponent::setFile(const juce::File& file)
{
thumbnail.setSource(new juce::FileInputSource(file));
}
setFile関数は、thumbnailに音声ファイルをセットする関数で、これはMainComponentのタイミング(ファイルを指定するタイミング)でセットされます。以前MainComponentクラスで行っていた処理をそのまま引き継いでいます。
void SimpleThumbnailComponent::paint(juce::Graphics& g)
{
if (thumbnail.getNumChannels() == 0)
paintIfNoFileLoaded(g);
else
paintIfFileLoaded(g);
}
void SimpleThumbnailComponent::paintIfNoFileLoaded(juce::Graphics& g)
{
g.fillAll(juce::Colours::white);
g.setColour(juce::Colours::darkgrey);
g.drawFittedText("No File Loaded", getLocalBounds(), juce::Justification::centred, 1);
}
void SimpleThumbnailComponent::paintIfFileLoaded(juce::Graphics& g)
{
g.fillAll(juce::Colours::white);
g.setColour(juce::Colours::red);
thumbnail.drawChannels(g, getLocalBounds(), 0.0, thumbnail.getTotalLength(), 1.0f);
}
paint関数でも、以前MainComponentで行っていた処理をそのまま移植しているだけになります。
void SimpleThumbnailComponent::changeListenerCallback(juce::ChangeBroadcaster* source)
{
if (source == &thumbnail)
thumbnailChanged();
}
void SimpleThumbnailComponent::thumbnailChanged()
{
repaint();
}
thumbnailの音声データが変更されたタイミングでは、thumbnailChanged関数が呼び出されます。その中身は、repaint関数を呼び出して再描画処理を行うというものです。
SimpolePositionOverlayクラス
//SimpolePositionOverlay
SimplePositionOverlay::SimplePositionOverlay(const juce::AudioTransportSource& transportSourceToUse)
: transportSource(transportSourceToUse)
{
startTimer(40);
}
コンストラクタでは、タイマーを起動しています。また、イニシャライザで、AudioTransportSourceを受け取り、クラス内のメンバ変数「transportSouece」へ保管しています。
void SimplePositionOverlay::paint(juce::Graphics& g)
{
auto duration = (float)transportSource.getLengthInSeconds();
if (duration > 0.0)
{
auto audioPosition = (float)transportSource.getCurrentPosition();
auto drawPosition = (audioPosition / duration) * (float)getWidth();
g.setColour(juce::Colours::green);
g.drawLine(drawPosition, 0.0f, drawPosition, (float)getHeight(), 2.0f);
}
}
void SimplePositionOverlay::timerCallback()
{
repaint();
}
paint関数と、timerCallback関数でも、以前MainComponentクラスで行っていた内容をそのまま移植しています。一部変更点としては、子コンポーネントとなったので、MainComponentが指定した位置(の左上)が原点となるので、ウィンドウに対する余白を考慮しない配置計算となりました。drawPositionや、drawLineの位置の数値はよりシンプルに設定できるようになりました。
SimpleThumbnaiComponentとSimplePositionOverlayクラスは、MainComponentのresize関数のところで配置されますが、配置された時点で、上の絵のオレンジのところが原点となります。そのため、MainCoomponent全体に対する位置を考慮する必要がなくなりました。
たしかに、子コンポーネントはGUI的にはとても便利です。
ちょっと長くなってしまいますので、MainComponentのプログラムについては、次回記載していきたいです。