JUCEで簡単なソフトシンセを作る（UI実装編）

(2020/4/30追記) JUCEのバージョンが上がってScopedPointerが使えなくなったので対応しときました。

##パラメータの定義
準備編で作った状態だと音も出なけりゃパラメータの一つもないので、まずはパラメータを出してみる。JUCEではプラグイン自体がAudioProcessorというクラスでパラメータはそれとは別にAudioProcessorParameterというクラスを使う。こいつのサブクラスを作ってプラグインにそのインスタンスを追加していくという仕組み。Xcodeは一旦閉じて、ProjucerのFile explorerでSourceグループを右クリックしてAdd New CPP & Header File...でSimpleSynthParameterという名前でファイルを追加する。それぞれのファイルの内容は下記。

```SimpleSynthParameter.h
/*
  ==============================================================================

    SimpleSynthParameter.h
    Created: 29 Dec 2018 11:59:25pm
    Author:  ring2

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class SimpleSynthParameter : public AudioProcessorParameter
{
public:
    SimpleSynthParameter();
    float getValue () const override;
    void setValue (float newValue) override;
    float getDefaultValue () const override;
    String getName (int maximumStringLength) const override;
    String getLabel () const override;
    float getValueForText (const String &text) const override;

    bool isChanged(bool clearFlag = true) { bool temp = changed; if (clearFlag) changed = false; return temp; }

private:
    bool changed;
    float value;
};
```

```SimpleSynthParameter.cpp
/*
  ==============================================================================

    SimpleSynthParameter.cpp
    Created: 29 Dec 2018 11:59:25pm
    Author:  ring2

  ==============================================================================
*/

#include "SimpleSynthParameter.h"

SimpleSynthParameter::SimpleSynthParameter()
{
    value = 1.0f;
}

float SimpleSynthParameter::getValue () const
{
    return value;
}

void SimpleSynthParameter::setValue (float newValue)
{
    value = newValue;
}

float SimpleSynthParameter::getDefaultValue () const
{
    return 1.0f;
}

String SimpleSynthParameter::getName (int maximumStringLength) const
{
    return String("Volume");
}

String SimpleSynthParameter::getLabel () const
{
    return String();
}

float SimpleSynthParameter::getValueForText (const String &text) const
{
    float theValue = text.getFloatValue();
    if (theValue < 0.0f) theValue = 0.0f;
    else if (theValue > 1.0f) theValue = 1.0f;
    return theValue;
}
```
さらに、プラグイン本体の実装に下記を加える。

```SimpleSynthAudioProcessor.cpp
/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic framework code for a JUCE plugin processor.

  ==============================================================================
*/

#include "SimpleSynthAudioProcessor.h"
#include "SimpleSynthAudioProcessorEditor.h"
#include "SimpleSynthParameter.h"  // <- これと

//==============================================================================
SimpleSynthAudioProcessor::SimpleSynthAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
{
    addParameter(new SimpleSynthParameter);  // <- これ
}
// 以下省略
```

これでビルドしてLogicかMainStageでプラグインを起動すると、追加したパラメータが見える（プラグインのウィンドウをエディタからコントロールに変更する必要あり）。
<img width="571" alt="スクリーンショット 2018-12-30 0.28.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/e3e851ce-b051-d8fd-a7b6-91f9bbe1c018.png">
ここでは必要最小限のメソッドしかoverrideしてないし、パラメータも1つしか追加してないけど、addParameterすればどんどんパラメータは追加できる。この例のようにGenericな0~1パラメータを追加することもあれば、もっと複雑なパラメータを定義して追加することも可能（というか実際は殆ど後者かと）。現在のJUCEの実装では、パラメータの数値は0~1に制限されている（と思う）ので、実際にUIで使用する際はそれを人間がわかりやすい表記に変換して表示、操作する。

##UIの実装
JUCEには（というかProjucerには）UIエディタがついてるので、UIの実装にはそれを使わない手はない（いやあるが）。ProjucerのFile explorerでSourceを右クリックしてAdd New GUI Component...を選択、名前をSimpleSynthMainComponentにする。プラグインでウィンドウサイズを可変にするのは色々とハードルが高いので、General class settingsでFixed SizeをKeep component size fixedにする。次にSubcomponentタブに移り、キャンバス上で右クリックしてNew Sliderを選択。member nameをsliderVolumeに、maximumを1に変更する。追加でNew Labelでラベルを置いて、Label textをVolumeにし、それぞれ適当に配置する。<img width="1290" alt="スクリーンショット 2018-12-30 0.44.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/6f9aafac-0bde-da5a-d68b-d9a09b908806.png">
作ったUIは、プラグインのエディタ（＝ウィンドウ）にそのまま貼り付ける。ここはコードを書く必要がある。

```SimpleSynthAudioProcessorEditor.h
/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic framework code for a JUCE plugin editor.

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"
#include "SimpleSynthAudioProcessor.h"
#include "SimpleSynthMainComponent.h"  // <- 追加

//==============================================================================
/**
*/
class SimpleSynthAudioProcessorEditor  : public AudioProcessorEditor
{
public:
    SimpleSynthAudioProcessorEditor (SimpleSynthAudioProcessor&);
    ~SimpleSynthAudioProcessorEditor();

    //==============================================================================
    void paint (Graphics&) override;
    void resized() override;

private:
    // This reference is provided as a quick way for your editor to
    // access the processor object that created it.
    SimpleSynthAudioProcessor& processor;

    std::unique_ptr<SimpleSynthMainComponent> mainComponent;  // <- 追加

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleSynthAudioProcessorEditor)
};
```

```SimpleSynthAudioProcessorEditor.cpp
/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic framework code for a JUCE plugin editor.

  ==============================================================================
*/

#include "SimpleSynthAudioProcessor.h"
#include "SimpleSynthAudioProcessorEditor.h"


//==============================================================================
SimpleSynthAudioProcessorEditor::SimpleSynthAudioProcessorEditor (SimpleSynthAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
    // Make sure that before the constructor has finished, you've set the
    // editor's size to whatever you need it to be.
    mainComponent = std::make_unique<SimpleSynthMainComponent>(&processor);  // <- 追加
    addAndMakeVisible(mainComponent.get());  // <- 追加
    setSize (mainComponent->getWidth(), mainComponent->getHeight());  // <- 変更
}
```
これでビルドすればHello Worldではなく、上で作ったUIが表示される。いちいちプラグインをLogicやMainStageで起動するのは面倒なので、ここから暫くはStandalone版をXcodeから直接実行するのが楽（でもたまにプラグイン版を起動した方がいい。ちょいちょいプラグイン版でしか確認できないことや、起きないバグとかが出てくるから）。
<img width="753" alt="スクリーンショット 2018-12-30 1.02.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/0e76f7c4-e889-170e-b4d4-408f2e1c7524.png">

##UIとパラメータを繋ぐ
では作成したUIとパラメータを実際に繋ぐ。そのために、UIのクラスであるSimpleSynthMainComponentからプラグイン本体であるSimpleSynthAudioProcessorの情報を参照、操作したいので、メンバに追加する。ただし、ProjucerでUIを作ったので、コンストラクタの引数と初期化部分はProjucerのClassタブで設定する必要がある。具体的にはConstructor paramsにSimpleSynthAudioProcessor* theProcessor、Member initializerにprocessor(theProcessor)と書いておき、ヘッダファイルにメンバ変数を追加する（これはコードで書く）。~~あと、諸事情あってAU/VSTホストでパラメータ変更された場合の処理はタイマーを使ってポーリング処理で行うので（これホントになんとかならないのかな）、Parent classをpublic Component, public Timerとしておく。めんどくさいけど、UIが複雑になればなるほどこれをやる価値は後ででてくる（逆にシンプルならコードで書いた方が断然早い）。~~AudioProcessorParameter::Listenerを使えばパラメータの変更時にコールバックで処理できる（このサンプルではやってないが）。手でコードを書く部分は、//[Headers]とか、//[UserMethod]のようにコメントでくくられた内側に書かないと後でProjucerに全部消されるので注意。
<img width="1142" alt="スクリーンショット 2018-12-30 2.54.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/3aeedb77-dcf6-0c32-5e31-181183334871.png">
変更後のコードは下記。

```SimpleSynthMainComponent.h
/*
  ==============================================================================

  This is an automatically generated GUI class created by the Projucer!

  Be careful when adding custom code to these files, as only the code within
  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
  and re-saved.

  Created with Projucer version: 5.4.1

  ------------------------------------------------------------------------------

  The Projucer is part of the JUCE library.
  Copyright (c) 2017 - ROLI Ltd.

  ==============================================================================
*/

#pragma once

//[Headers]     -- You can add your own extra header files here --
#include "../JuceLibraryCode/JuceHeader.h"
class SimpleSynthAudioProcessor;  // プラグイン本体のクラスをここで宣言しとく
//[/Headers]



//==============================================================================
/**
                                                                    //[Comments]
    An auto-generated component, created by the Projucer.

    Describe your class and how it works here!
                                                                    //[/Comments]
*/
class SimpleSynthMainComponent  : public Component,
                                  public Timer,
                                  public Slider::Listener
{
public:
    //==============================================================================
    SimpleSynthMainComponent (SimpleSynthAudioProcessor* theProcessor);
    ~SimpleSynthMainComponent();

    //==============================================================================
    //[UserMethods]     -- You can add your own custom methods in this section.
    void timerCallback () override;  // タイマーのコールバック
    void sliderDragStarted (Slider* sliderThatWasMoved) override;  // Projucerが勝手に書いてくれないコード
    void sliderDragEnded (Slider* sliderThatWasMoved) override;  // これも
    //[/UserMethods]

    void paint (Graphics& g) override;
    void resized() override;
    void sliderValueChanged (Slider* sliderThatWasMoved) override;



private:
    //[UserVariables]   -- You can add your own custom variables in this section.
    SimpleSynthAudioProcessor* processor;  // プラグイン本体のポインタ
    const OwnedArray<AudioProcessorParameter>& params;  // プラグインのパラメータの配列
    //[/UserVariables]

    //==============================================================================
    std::unique_ptr<Slider> sliderVolume;
    std::unique_ptr<Label> label;


    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleSynthMainComponent)
};

//[EndFile] You can add extra defines here...
//[/EndFile]
```

```SimpleSynthMainComponent.cpp
/*
  ==============================================================================

  This is an automatically generated GUI class created by the Projucer!

  Be careful when adding custom code to these files, as only the code within
  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
  and re-saved.

  Created with Projucer version: 5.4.7

  ------------------------------------------------------------------------------

  The Projucer is part of the JUCE library.
  Copyright (c) 2017 - ROLI Ltd.

  ==============================================================================
*/

//[Headers] You can add your own extra header files here...
//[/Headers]

#include "SimpleSynthMainComponent.h"


//[MiscUserDefs] You can add your own user definitions and misc code here...
#include "SimpleSynthAudioProcessor.h"
#include "SimpleSynthParameter.h"
//[/MiscUserDefs]

//==============================================================================
SimpleSynthMainComponent::SimpleSynthMainComponent (SimpleSynthAudioProcessor* theProcessor)
    : processor(theProcessor)
{
    //[Constructor_pre] You can add your own custom stuff here..
    //[/Constructor_pre]

    sliderVolume.reset (new Slider ("new slider"));
    addAndMakeVisible (sliderVolume.get());
    sliderVolume->setRange (0, 1, 0);
    sliderVolume->setSliderStyle (Slider::LinearHorizontal);
    sliderVolume->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20);
    sliderVolume->addListener (this);

    sliderVolume->setBounds (16, 80, 432, 24);

    label.reset (new Label ("new label",
                            TRANS("Volume")));
    addAndMakeVisible (label.get());
    label->setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
    label->setJustificationType (Justification::centredLeft);
    label->setEditable (false, false, false);
    label->setColour (TextEditor::textColourId, Colours::black);
    label->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    label->setBounds (16, 48, 150, 24);

    softKeyboard.reset (new MidiKeyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard));
    addAndMakeVisible (softKeyboard.get());

    labelNote.reset (new Label ("new label",
                                TRANS("---")));
    addAndMakeVisible (labelNote.get());
    labelNote->setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
    labelNote->setJustificationType (Justification::centredLeft);
    labelNote->setEditable (false, false, false);
    labelNote->setColour (TextEditor::textColourId, Colours::black);
    labelNote->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    labelNote->setBounds (80, 16, 32, 24);

    label2.reset (new Label ("new label",
                             TRANS("Note#:")));
    addAndMakeVisible (label2.get());
    label2->setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
    label2->setJustificationType (Justification::centredLeft);
    label2->setEditable (false, false, false);
    label2->setColour (TextEditor::textColourId, Colours::black);
    label2->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    label2->setBounds (16, 16, 56, 24);

    labelWidth.reset (new Label ("new label",
                                 TRANS("label text")));
    addAndMakeVisible (labelWidth.get());
    labelWidth->setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
    labelWidth->setJustificationType (Justification::centredLeft);
    labelWidth->setEditable (false, false, false);
    labelWidth->setColour (TextEditor::textColourId, Colours::black);
    labelWidth->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    labelWidth->setBounds (296, 16, 72, 24);

    labelHeight.reset (new Label ("new label",
                                  TRANS("label text")));
    addAndMakeVisible (labelHeight.get());
    labelHeight->setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular"));
    labelHeight->setJustificationType (Justification::centredLeft);
    labelHeight->setEditable (false, false, false);
    labelHeight->setColour (TextEditor::textColourId, Colours::black);
    labelHeight->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    labelHeight->setBounds (400, 16, 72, 24);


    //[UserPreSize]
    AudioProcessorParameter* param = processor->getParameters().getReference(0);
    param->addListener(this);
    sliderVolume->setValue((double)param->getValue(), dontSendNotification);

    Rectangle<int> r = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
    labelWidth->setText(String::formatted("%d", r.getWidth()), dontSendNotification);
    labelHeight->setText(String::formatted("%d", r.getHeight()), dontSendNotification);
    //[/UserPreSize]

    setSize (568, 320);


    //[Constructor] You can add your own custom stuff here..
    //startTimer(1.0 / 15.0); //  15 fps
    keyboardState.addListener (this);
    //[/Constructor]
}

SimpleSynthMainComponent::~SimpleSynthMainComponent()
{
    //[Destructor_pre]. You can add your own custom destruction code here..
    processor->getParameters().getReference(0)->removeListener(this);
    stopTimer();
    //[/Destructor_pre]

    sliderVolume = nullptr;
    label = nullptr;
    softKeyboard = nullptr;
    labelNote = nullptr;
    label2 = nullptr;
    labelWidth = nullptr;
    labelHeight = nullptr;


    //[Destructor]. You can add your own custom destruction code here..
    //[/Destructor]
}

//==============================================================================
void SimpleSynthMainComponent::paint (Graphics& g)
{
    //[UserPrePaint] Add your own custom painting code here..
    //[/UserPrePaint]

    g.fillAll (Colour (0xff323e44));

    {
        int x = 0, y = 175, width = proportionOfWidth (1.0000f), height = 145;
        Colour fillColour = Colour (0xff2e2e2e);
        //[UserPaintCustomArguments] Customize the painting arguments here..
        //[/UserPaintCustomArguments]
        g.setColour (fillColour);
        g.fillRect (x, y, width, height);
    }

    //[UserPaint] Add your own custom painting code here..
    //[/UserPaint]
}

void SimpleSynthMainComponent::resized()
{
    //[UserPreResize] Add your own custom resize code here..
    //[/UserPreResize]

    softKeyboard->setBounds (0, getHeight() - 145, 568, 145);
    //[UserResized] Add your own custom resize handling here..
    //[/UserResized]
}

void SimpleSynthMainComponent::sliderValueChanged (Slider* sliderThatWasMoved)
{
    //[UsersliderValueChanged_Pre]
    //[/UsersliderValueChanged_Pre]

    if (sliderThatWasMoved == sliderVolume.get())
    {
        //[UserSliderCode_sliderVolume] -- add your slider handling code here..
        processor->getParameters().getReference(0)->setValueNotifyingHost((float)sliderVolume->getValue());
        //processor->setParameterNotifyingHost(0, (float)sliderVolume->getValue());
        //[/UserSliderCode_sliderVolume]
    }

    //[UsersliderValueChanged_Post]
    //[/UsersliderValueChanged_Post]
}



//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
void SimpleSynthMainComponent::timerCallback ()
{
    SimpleSynthParameter* param = (SimpleSynthParameter*)processor->getParameters().getReference(0);
    if (param->isChanged())
    {
        sliderVolume->setValue((double)param->getValue(), dontSendNotification);
    }
}

void SimpleSynthMainComponent::sliderDragStarted (Slider* sliderThatWasMoved)
{
    if (sliderThatWasMoved == sliderVolume.get())
    {
        processor->getParameters().getReference(0)->beginChangeGesture();
    }
}

void SimpleSynthMainComponent::sliderDragEnded (Slider* sliderThatWasMoved)
{
    if (sliderThatWasMoved == sliderVolume.get())
    {
        processor->getParameters().getReference(0)->endChangeGesture();
    }
}

void SimpleSynthMainComponent::parameterValueChanged (int parameterIndex, float newValue)
{
    SimpleSynthParameter* param = (SimpleSynthParameter*)(processor->getParameters().getReference(0));
    sliderVolume->setValue((double)param->getValue(), dontSendNotification);
}

void SimpleSynthMainComponent::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    processor->midi((unsigned char)(0x90 | (midiChannel & 0x0f)), (unsigned char)midiNoteNumber, (unsigned char)(velocity * 127.0f));
    labelNote->setText(String::formatted("%d", midiNoteNumber), dontSendNotification);
}

void SimpleSynthMainComponent::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    processor->midi((unsigned char)(0x80 | (midiChannel & 0x0f)), (unsigned char)midiNoteNumber, (unsigned char)(velocity * 127.0f));
    labelNote->setText(String("---"), dontSendNotification);
}
//[/MiscUserCode]


//==============================================================================
（以下省略）
```

```SimpleSynthParameter.h
/*
  ==============================================================================

    SimpleSynthParameter.h
    Created: 29 Dec 2018 11:59:25pm
    Author:  ring2

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class SimpleSynthParameter : public AudioProcessorParameter
{
public:
    SimpleSynthParameter();
    float getValue () const override;
    void setValue (float newValue) override;
    float getDefaultValue () const override;
    String getName (int maximumStringLength) const override;
    String getLabel () const override;
    float getValueForText (const String &text) const override;
    
    bool isChanged(bool clearFlag = true) { bool temp = changed; if (clearFlag) changed = false; return temp; }

private:
    bool changed;
    float value;
};
```

```SimpleSynthParameter.cpp
/*
  ==============================================================================

    SimpleSynthParameter.cpp
    Created: 29 Dec 2018 11:59:25pm
    Author:  ring2

  ==============================================================================
*/

#include "SimpleSynthParameter.h"

SimpleSynthParameter::SimpleSynthParameter()
{
    value = 1.0f;
    changed = true;
}

float SimpleSynthParameter::getValue () const
{
    return value;
}

void SimpleSynthParameter::setValue (float newValue)
{
    value = newValue;
    // 値が変更されたらフラグを立てておく。
    // このフラグはisChanged()メソッドで参照されると同時にクリアされる。
    changed = true;
}

float SimpleSynthParameter::getDefaultValue () const
{
    return 1.0f;
}

String SimpleSynthParameter::getName (int maximumStringLength) const
{
    return String("Volume");
}

String SimpleSynthParameter::getLabel () const
{
    return String();
}

float SimpleSynthParameter::getValueForText (const String &text) const
{
    float theValue = text.getFloatValue();
    if (theValue < 0.0f) theValue = 0.0f;
    else if (theValue > 1.0f) theValue = 1.0f;
    return theValue;
}
```

パラメータの変更がきちんとホストに伝わっているかどうかはStandalone版では確認できないので、DAWホストでプラグイン版を読み込んで確認する。上記コードはVST3とAU(v2)では動くが、現時点ではAUv3ではどうもオートメーションの書き込みができない様子（Logic Pro X）。ホスト側で書いたオートメーションをReadして動かすことはできる。LogicかJUCEどちらかがおかしいと思うが調べてない。

##鍵盤UIの追加
信号処理部分を記述する前に鍵盤を足してしまおう。これがあると後々色々と楽。JUCEには鍵盤のUIが用意されてるのでそれをそのまんま使う。まずはProjucerでSimpleSynthMainComponentにGeneric Componentを追加して、member nameをsoftKeyboardに、classをMidiKeyboardComponentに、constructor paramsをkeyboardState, MidiKeyboardComponent::horizontalKeyboardとする。あと、どのNoteが押されたかの確認用にLabelを追加してmember nameをlabelNoteに、label textを"---"にしておく。
<img width="1293" alt="スクリーンショット 2018-12-30 3.39.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/d28b2f84-dfff-c8f4-597c-089bf88159d4.png">
ClassタブでParent classにprivate MidiKeyboardStateListenerを追加。これで鍵盤がクリックされた時にコールバックで受けられるようになる。その後コードを追加。

```SimpleSynthMainComponent.h
/*
  ==============================================================================

  This is an automatically generated GUI class created by the Projucer!

  Be careful when adding custom code to these files, as only the code within
  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
  and re-saved.

  Created with Projucer version: 5.4.7

  ------------------------------------------------------------------------------

  The Projucer is part of the JUCE library.
  Copyright (c) 2017 - ROLI Ltd.

  ==============================================================================
*/

#pragma once

//[Headers]     -- You can add your own extra header files here --
#include "../JuceLibraryCode/JuceHeader.h"
class SimpleSynthAudioProcessor;
//[/Headers]



//==============================================================================
/**
                                                                    //[Comments]
    An auto-generated component, created by the Projucer.

    Describe your class and how it works here!
                                                                    //[/Comments]
*/
class SimpleSynthMainComponent  : public Component,
                                  public Timer,
                                  private MidiKeyboardStateListener,
                                  public AudioProcessorParameter::Listener,
                                  public Slider::Listener
{
public:
    //==============================================================================
    SimpleSynthMainComponent (SimpleSynthAudioProcessor* theProcessor);
    ~SimpleSynthMainComponent() override;

    //==============================================================================
    //[UserMethods]     -- You can add your own custom methods in this section.
    void timerCallback () override;
    void sliderDragStarted (Slider* sliderThatWasMoved) override;
    void sliderDragEnded (Slider* sliderThatWasMoved) override;
    void parameterValueChanged (int parameterIndex, float newValue) override;
    void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override {}
    void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
    void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
    //[/UserMethods]

    void paint (Graphics& g) override;
    void resized() override;
    void sliderValueChanged (Slider* sliderThatWasMoved) override;



private:
    //[UserVariables]   -- You can add your own custom variables in this section.
    SimpleSynthAudioProcessor* processor;
    MidiKeyboardState keyboardState;
    //[/UserVariables]

    //==============================================================================
    std::unique_ptr<Slider> sliderVolume;
    std::unique_ptr<Label> label;
    std::unique_ptr<MidiKeyboardComponent> softKeyboard;
    std::unique_ptr<Label> labelNote;
    std::unique_ptr<Label> label2;
    std::unique_ptr<Label> labelWidth;
    std::unique_ptr<Label> labelHeight;


    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleSynthMainComponent)
};

//[EndFile] You can add extra defines here...
//[/EndFile]
```

```SimpleSynthMainComponent.cpp
/*
  ==============================================================================

  This is an automatically generated GUI class created by the Projucer!

  Be careful when adding custom code to these files, as only the code within
  the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
  and re-saved.

  Created with Projucer version: 5.4.1

  ------------------------------------------------------------------------------

  The Projucer is part of the JUCE library.
  Copyright (c) 2017 - ROLI Ltd.

  ==============================================================================
*/

//[Headers] You can add your own extra header files here...
//[/Headers]

#include "SimpleSynthMainComponent.h"


//[MiscUserDefs] You can add your own user definitions and misc code here...
#include "SimpleSynthAudioProcessor.h"
#include "SimpleSynthParameter.h"
//[/MiscUserDefs]

//==============================================================================
SimpleSynthMainComponent::SimpleSynthMainComponent (SimpleSynthAudioProcessor* theProcessor)
    : processor(theProcessor),
      params(theProcessor->getParameters())
{
    //[Constructor_pre] You can add your own custom stuff here..
    //[/Constructor_pre]

    sliderVolume.reset (new Slider ("new slider"));
    addAndMakeVisible (sliderVolume.get());
    sliderVolume->setRange (0, 1, 0);
    sliderVolume->setSliderStyle (Slider::LinearHorizontal);
    sliderVolume->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20);
    sliderVolume->addListener (this);

    sliderVolume->setBounds (88, 176, 432, 24);

    label.reset (new Label ("new label",
                            TRANS("Volume")));
    addAndMakeVisible (label.get());
    label->setFont (Font (15.0f, Font::plain).withTypefaceStyle ("Regular"));
    label->setJustificationType (Justification::centredLeft);
    label->setEditable (false, false, false);
    label->setColour (TextEditor::textColourId, Colours::black);
    label->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    label->setBounds (88, 144, 150, 24);

    softKeyboard.reset (new MidiKeyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard));
    addAndMakeVisible (softKeyboard.get());

    softKeyboard->setBounds (0, 352, 600, 48);

    labelNote.reset (new Label ("new label",
                                TRANS("---")));
    addAndMakeVisible (labelNote.get());
    labelNote->setFont (Font (15.0f, Font::plain).withTypefaceStyle ("Regular"));
    labelNote->setJustificationType (Justification::centredLeft);
    labelNote->setEditable (false, false, false);
    labelNote->setColour (TextEditor::textColourId, Colours::black);
    labelNote->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    labelNote->setBounds (152, 112, 32, 24);

    label2.reset (new Label ("new label",
                             TRANS("Note#:")));
    addAndMakeVisible (label2.get());
    label2->setFont (Font (15.0f, Font::plain).withTypefaceStyle ("Regular"));
    label2->setJustificationType (Justification::centredLeft);
    label2->setEditable (false, false, false);
    label2->setColour (TextEditor::textColourId, Colours::black);
    label2->setColour (TextEditor::backgroundColourId, Colour (0x00000000));

    label2->setBounds (88, 112, 56, 24);


    //[UserPreSize]
    sliderVolume->setValue((double)params[0]->getValue(), dontSendNotification);
    //[/UserPreSize]

    setSize (600, 400);


    //[Constructor] You can add your own custom stuff here..
    startTimer(1.0 / 15.0); //  15 fps
    keyboardState.addListener (this);  // これで鍵盤のコールバックが受けられるようになる
    //[/Constructor]
}

（中略）

//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
void SimpleSynthMainComponent::timerCallback ()
{
    SimpleSynthParameter* param = (SimpleSynthParameter*)(params[0]);
    if (param->isChanged())
    {
        sliderVolume->setValue((double)param->getValue(), dontSendNotification);
    }
}

void SimpleSynthMainComponent::sliderDragStarted (Slider* sliderThatWasMoved)
{
    if (sliderThatWasMoved == sliderVolume.get())
    {
        params[0]->beginChangeGesture();
    }
}

void SimpleSynthMainComponent::sliderDragEnded (Slider* sliderThatWasMoved)
{
    if (sliderThatWasMoved == sliderVolume.get())
    {
        params[0]->endChangeGesture();
    }
}

// 鍵盤のコールバック。今はノートナンバーをラベルに表示するだけ。
void SimpleSynthMainComponent::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    labelNote->setText(String::formatted("%d", midiNoteNumber), dontSendNotification);
}

void SimpleSynthMainComponent::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    labelNote->setText(String("---"), dontSendNotification);
}
//[/MiscUserCode]
（以下略）
```

これでビルドして実行すると、鍵盤をクリックするたびにノートナンバーが表示される。