やればやるほど色々な機能をつけたくなってきます。
市販のプラグインってとても高機能なんだね〜
ダイヤルスライダーをカスタマイズするために、こちらの記事もご参照ください。
PluginAllianceで販売しているこのプラグインのダイヤル、なんだかいいな~と思い、以前カスタマイズしたスライダーをLookAndFeelクラスで描画する方法でできそうだな、と感じたので、挑戦してみました。
この記事は、以前のシンプルなハイパスフィルターのカスタマイズ記事になります。
こんな人の役にたつかも
・スライダーの見た目をカスタマイズしたい人
・JUCEでEQプラグインのようなものを作りたい人
・スペクトルアナライザのスケーリング調整を行いたい人
スペクトルアナライザの周波数スケーリング調整
スペアナの中央値を1kHzとなるように係数を調整しました。
PluginEditor.h内の「drawNextFrameOfSpectrum」関数の係数を変更して調整しました。
auto fftDataIndex = juce::jlimit(0, audioProcessor.fftSize / 2, (int)(skewedProportionX * (float)audioProcessor.fftSize * 0.51f));
1kHzのサイン波を入力したとき、0.51fとすることで、中央の線に1kHzが表示されるようになりました。
サイン波を入力したのは、以前の記事で作成したサイン波を出力するプラグインを利用してみました。
GUIを調整
実装の追加
PluginEditor.h
PluginEditor.hに以下のダイヤルスライダーの外観をオリジナルで描画するためのクラスを作成しました。このカスタマイズしたLookAndFeelクラスは、以前JUCEチュートリアルで作成したものを転用しました。
class dialLookAndFeelStyle01 : public juce::LookAndFeel_V4
{
public:
dialLookAndFeelStyle01()
{
setColour(juce::Slider::thumbColourId, juce::Colours::red);
}
void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos,
const float rotaryStartAngle, const float rotaryEndAngle, juce::Slider&) override
{
int offset = 10;//[1]今回追加したパラメータです。
auto radius = (float)juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = (float)x + (float)width * 0.5f;
auto centreY = (float)y + (float)height * 0.5f;
auto rx = centreX - radius + offset/2;//[1-1]offsetの追加です。
auto ry = centreY - radius + offset/2;//[1-2]offsetの追加です。
auto rw = radius * 2.0f - offset;//[1-3]offsetの追加です。
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
//[2]追加した描画です。
juce::Path p2;
p2.clear();
p2.startNewSubPath(centreX, centreY);
p2.addArc(rx, ry, rw, rw, rotaryStartAngle, angle);
g.setColour(juce::Colour::fromRGBA(152, 251, 152, 254));
g.strokePath(p2, juce::PathStrokeType(10.0f));
//ダイヤルのベース部分を塗りつぶします。
g.setColour(juce::Colours::grey);
g.fillEllipse(rx, ry, rw, rw);
//ダイヤルの枠線を描画します。
g.setColour(juce::Colours::black);
g.drawEllipse(rx, ry, rw, rw, 1.0f);
//ポインタ部分(ダイヤルの現在値)の描画です。
juce::Path p;
auto pointerLength = radius * 0.7f;
auto pointerThickness = 2.0f;
p.addRectangle(-pointerThickness * 0.5f, -radius, pointerThickness, pointerLength);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(juce::Colours::black);
g.fillPath(p);
}
};
[1]と[2]の内容を描画処理に追加しました。
上記作成したLookAndFeelクラス「dialLookAndFeelStyle01」をプラグイン本体クラスのprivateなメンバとして追加しました。
class _01_panda_filterAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer
{
//...略...
private:
//...略...
dialLookAndFeelStyle01 dialLookAndFeel01;//追加しました。
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (_01_panda_filterAudioProcessorEditor)
};
PluginEditor.cpp
コンストラクタにスライダーの初期化を次のように変更しました。
_01_panda_filterAudioProcessorEditor::_01_panda_filterAudioProcessorEditor (_01_panda_filterAudioProcessor& p, juce::AudioProcessorValueTreeState& vts)
//...略...
{
//カットオフ周波数スライダーの設定
addAndMakeVisible(cutoffSlider);
cutoffAttachment.reset(new SliderAttachment(valueTreeState, "cutoff", cutoffSlider));
cutoffSlider.setRange(20.0, 22000.0);
cutoffSlider.setSkewFactorFromMidPoint(1000.0);
cutoffSlider.setSliderStyle(juce::Slider::Rotary);
cutoffSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, true, 100, 20);
cutoffSlider.setTextValueSuffix("Hz");
cutoffSlider.setLookAndFeel(&dialLookAndFeel01);
//Qパラメータスライダーの設定
addAndMakeVisible(QParamSlider);
QParamAttachment.reset(new SliderAttachment(valueTreeState, "qParam", QParamSlider));
QParamSlider.setSliderStyle(juce::Slider::Rotary);
QParamSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, true, 100, 20);
QParamSlider.setLookAndFeel(&dialLookAndFeel01);
//Slopeスライダーの設定
addAndMakeVisible(SlopeSlider);
SlopeSliderAttachment.reset(new SliderAttachment(valueTreeState, "slope", SlopeSlider));
SlopeSlider.setSliderStyle(juce::Slider::Rotary);
SlopeSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, true, 100, 20);
SlopeSlider.setLookAndFeel(&dialLookAndFeel01);
setOpaque(true);
startTimerHz(30);
setSize (500, 300);
}
全てのスライダーはsetSliderStyle関数で「Rotary」として、setLookAndFeel関数を使って先ほど作成した「dialLookAndFeel01」のLookAndFeelを紐付けています。
LookAndFeelはデコンストラクタで次のように処理する必要があります。
_01_panda_filterAudioProcessorEditor::~_01_panda_filterAudioProcessorEditor()
{
cutoffSlider.setLookAndFeel(nullptr);
QParamSlider.setLookAndFeel(nullptr);
SlopeSlider.setLookAndFeel(nullptr);
}
そして、GUIにそれぞれのスライダーを配置しました。
void _01_panda_filterAudioProcessorEditor::resized()
{
cutoffSlider.setBounds(10, 10, 100, 100);//(X,Y,幅,高さ)
QParamSlider.setBounds(150, 10, 100, 100);
SlopeSlider.setBounds(300, 10, 100, 100);
}
このようなダイヤルスライダーの配置になりました。
ダイヤルスライダーって言ってるけれど、Rotaryスライダーが正式名称っぽいですね
間違えてました、すみません
工夫した点
現在の値を緑の円弧が追従して、パラメータ現在地の視認性を高めたいと思いました。PluginAllianceのプラグインの見た目を真似しています。
この描画を行なった時の工夫をまとめます。
今回のスライダーの見た目の描画は、dialLookAndFeelStyle01クラスのdrawRotarySlider関数で描画を行なっています。(先ほどPluginEditor.hに記載したクラスです。もう一度抜き出して貼り付けます。)
void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos,
const float rotaryStartAngle, const float rotaryEndAngle, juce::Slider&) override
{
int offset = 10;//[1]今回追加したパラメータです。
auto radius = (float)juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = (float)x + (float)width * 0.5f;
auto centreY = (float)y + (float)height * 0.5f;
auto rx = centreX - radius + offset/2;//[1-1]offsetの追加です。
auto ry = centreY - radius + offset/2;//[1-2]offsetの追加です。
auto rw = radius * 2.0f - offset;//[1-3]offsetの追加です。
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
//[2]追加した描画です。
juce::Path p2;
p2.clear();
p2.startNewSubPath(centreX, centreY);
p2.addArc(rx, ry, rw, rw, rotaryStartAngle, angle);
g.setColour(juce::Colour::fromRGBA(152, 251, 152, 254));
g.strokePath(p2, juce::PathStrokeType(10.0f));
//ダイヤルのベース部分を塗りつぶします。
g.setColour(juce::Colours::grey);
g.fillEllipse(rx, ry, rw, rw);
//ダイヤルの枠線を描画します。
g.setColour(juce::Colours::black);
g.drawEllipse(rx, ry, rw, rw, 1.0f);
//ポインタ部分(ダイヤルの現在値)の描画です。
juce::Path p;
auto pointerLength = radius * 0.7f;
auto pointerThickness = 2.0f;
p.addRectangle(-pointerThickness * 0.5f, -radius, pointerThickness, pointerLength);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(juce::Colours::black);
g.fillPath(p);
}
[2]で周囲の緑色のパスを描画する際、パスの始点は左上端点になっているので、startNewSubPath関数で始点を設定します。視点は、本当は、スライダーの初期位置の左下の点を指定するのが一番いいのですが、計算が複雑だと感じましたので、中央にしました。
スライダーの中央からパスをはじめ、描画の順番を最初にすることで、必要な部分のみを表示させるような方式を取りました。とりあえず、ダイヤルスライダー中央から描画すると以下のようになります。
このパスの線の太さを太くして、ダイヤルスライダー本体を上から描画しました。
後からダイヤルの本体部分を上書きすることで、パスの開始点を隠しています。こうすることで、無駄にダイヤルの始点の座標を計算することなく(計算機的には問題ないと思いますが、自分の頭で考えることをやめました^^;)実現できます。
ただ、パスの線を太くすると、描画領域からはみ出して、一部パスの描画が欠ける場合がありますので、ダイヤルスライダー自体の大きさを中央を中心に小さくしたいです。
単純にrwがスライダーの幅と高さの円を表しているので、これから小さくしたい数値を引けば良いのですが、小さくした後に左上基準で描画されるので、中央がずれてしまいます。
そのため、「offset」というパラメータを導入して、中央を中心に小さくなるような仕組みに変更しました。
これが、プログラムの[1]で追加したoffsetのパラメータの意味になります。
もともとあるrx、ry、rwがダイヤルの中央、ダイヤルの目盛りの描画に利用されていますので、サイズを変更しても、中央がずれることなくサイズを大小できるようにしたいです。
上の左の図のダイヤル(外の矩形が描画領域で、下にテキストを含んでいます。中の矩形がダイヤルスライダー自体の描画領域です)を右の図のようにサイズ変更した場合、rx、ry、rwのパラメータにoffsetという変数を追加することで、中央をずらさずにダイヤルのサイズを変更することができるようになりました。