カテゴリー
C++ JUCE

JUCEでSimplePianoVisualizerを作る

MIDI Pianoの入力や演奏の様子を鍵盤上で「見る」だけの手頃でシンプルなVisualizer Pluginがなかったので、久しぶりにC++の復習も兼ねて作ってみた。

【C++の復習】juce::MidiKeyboardStateはPluginProcessor.hでインスタンス化し、その他のComponentではその参照を利用する。

// PluginProcessor.h

class SimplePianoVisualizerAudioProcessor  : public juce::AudioProcessor

{
public:

// 省略
  
  juce::MidiKeyboardState& getKeyboardState() {
    return keyboardState;
  } //インスタンスへの参照をゲットするpublic関数

private:
    //==============================================================================
  juce::MidiKeyboardState keyboardState; // ここでインスタンス化
  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimplePianoVisualizerAudioProcessor)
};
// PluginEditor.cpp

SimplePianoVisualizerAudioProcessorEditor::SimplePianoVisualizerAudioProcessorEditor (SimplePianoVisualizerAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p), keyboardState(p.getKeyboardState())
{
    setSize (400, 300);
}

【C++の復習】keyboardComponentはPluginEditor.hでインスタンス化し、initialization listでイニシャライズする。

//PluginEditor.h

class SimplePianoVisualizerAudioProcessorEditor  :  public juce::AudioProcessorEditor

{
public:
    SimplePianoVisualizerAudioProcessorEditor (SimplePianoVisualizerAudioProcessor&);
    ~SimplePianoVisualizerAudioProcessorEditor() override;

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

private:
  SimplePianoVisualizerAudioProcessor& audioProcessor;
  juce::MidiKeyboardState& keyboardState;
  juce::MidiKeyboardComponent keyboardComponent;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimplePianoVisualizerAudioProcessorEditor)
};
//PluginEditor.cpp

SimplePianoVisualizerAudioProcessorEditor::SimplePianoVisualizerAudioProcessorEditor (SimplePianoVisualizerAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p), keyboardState(p.getKeyboardState()), keyboardComponent(p.getKeyboardState(), juce::KeyboardComponentBase::horizontalKeyboard)
{

  setSize (400, 300);
}

【JUCEの復習】PluginEditor.cppのコンストラクタとresized()に記述し、画面上に表示する。

// Constructor
{
  addAndMakeVisible(keyboardComponent);

  setSize (400, 100);
  setResizable(true, false);
}

// resized()
void SimplePianoVisualizerAudioProcessorEditor::resized()
{

  auto area = getLocalBounds();
  keyboardComponent.setBounds(area);
}

これを実行すると、以下のように表示されます。縦・横を自由に表示されるようにするには、Sliderなどでサイズを可変にすると良いです。

void SimplePianoVisualizerAudioProcessorEditor::sliderValueChanged(juce::Slider *slider)
{
  horizontalSliderValue = horizontalSlider.getValue();
  verticalSliderValue = verticalSlider.getValue();
  // making sure the resized() (UI changes) are made on the UI thread
  (new SliderValueChangedCallback(this))->post();
}

void SimplePianoVisualizerAudioProcessorEditor::resized()
{
  auto area = getLocalBounds();

  auto verticalMargin = area.getHeight()/2.8;
  auto keyboardArea = area.removeFromBottom(verticalMargin).reduced(30, 0);
  keyWidth = 32 * (horizontalSliderValue/10);
  keyboardComponent.setKeyWidth(keyWidth);
  keyboardComponent.setBounds(keyboardArea.getX(), keyboardArea.getY(), keyboardArea.getWidth(), keyboardArea.getHeight()*(verticalSliderValue/10));

}

PluginProcessorのprocessBlockで下のように処理をすると、キーボードを弾くとスクリーン上のキーボードが反応するようになります。

void SimplePianoVisualizerAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
 
  for(const auto m: midiMessages) {
    keyboardState.processNextMidiEvent(m.getMessage());
  }
}
カテゴリー
Linux

Cron JobでCertbotを自動で実行しLogする

以下は、毎月28日の9時17分にcertbot renew とnginxのリスタートを実行し、その結果をcertbot_renew_logファイルにlogするcron jobです。taskを( )で囲まないとlogできませんでした。

17  9  28  *  *  (certbot renew  && systemctl restart nginx) >> /var/log/certbot_renew_log 2>&1
カテゴリー
Android Java

Custom Button in Android

// activity

public class MainActivity extends AppCompatActivity {

    Button button1;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getSupportActionBar().hide();

        RelativeLayout rootLinearLayout = new RelativeLayout(this);
        rootLinearLayout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);


        OvalShape ovalShape = new OvalShape();
        ShapeDrawable shapeDrawable = new ShapeDrawable(ovalShape);
        shapeDrawable.getPaint().setColor(Color.GRAY); // Change this to your desired color.

        button1 = new Button(this);
        button1.setText("Task1");
        int button1Id = View.generateViewId();
        button1.setId(button1Id);

        int size = 200; // Change this to your desired size.
        RelativeLayout.LayoutParams buttonLP = new RelativeLayout.LayoutParams(size, size);
        buttonLP.addRule(RelativeLayout.CENTER_IN_PARENT);

        RelativeLayout.LayoutParams buttonLP1 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        buttonLP1.addRule(RelativeLayout.CENTER_IN_PARENT);
        button1.setLayoutParams(buttonLP);
        button1.setOnClickListener(new OnButtonClicked());

        button1.setOnTouchListener(new OnButtonTouched(Color.GRAY, Color.BLUE));

        button1.setBackground(shapeDrawable);
        rootLinearLayout.addView(button1);
    }
}

// custom onTouchListener

public class OnButtonTouched implements View.OnTouchListener {
    private final int normalColor;
    private final int pressedColor;

    private static final String TAG = "ButtonClicked";

    public OnButtonTouched(int normalColor, int pressedColor) {
        this.normalColor = normalColor;
        this.pressedColor = pressedColor;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d(TAG, "in touched");
        if (!(v.getBackground() instanceof ShapeDrawable)) {
            return false; // Return false if the background is not of the expected type
        }

        ShapeDrawable shapeDrawable = (ShapeDrawable) v.getBackground();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // Button pressed: change color
                shapeDrawable.getPaint().setColor(pressedColor);
                v.invalidate(); // Force the button to be redrawn
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // Button released or touch cancelled: revert to original color
                shapeDrawable.getPaint().setColor(normalColor);
                v.invalidate(); // Force the button to be redrawn
                break;
        }
        return false; // Return false to indicate not to consume touch events, let them propagate so the onClick events will fire.
    }
}
カテゴリー
C++ JUCE

JUCE ComboBoxの挙動

これは僕が知らなかったのでメモしておきます。ComboBoxはcomboBox.addItem(“string”, counter);でアイテムを追加出来ますが、このcounterはアイテムが追加される順番だと思っていましたが、intであればなんでもいいようです。

つまり、何かのデータのindexなどをこれにあてることで、comboBox.getSelectedId()で値をゲットできるので、とても便利です。

カテゴリー
C++ JUCE

Custom ComponentにTooltipをセットする

カスタムComponentにTooltipをセットしたい場合は、public juce::SettableTooltipClientを継承する必要があります。

class CustomComponent : public juce::Component, public juce::SettableTooltipClient
{

};

カテゴリー
C++ JUCE

JUCE Forwarding MouseEvent

Child ComponentがParent Componentのエリアを全て覆ってしまっている場合、Parent ComponentのMouseEventが実行されませんが、以下のようにoverrideすると実行されるようになります。

class ForwardingLabel  : public juce::Label
{
public:
  ForwardingLabel()
  {

  }
  
  void mouseDown(const juce::MouseEvent &event) override {
    if(auto parent = getParentComponent()) {
      parent->mouseDown(event);
    }
  }
  
  void mouseExit(const juce::MouseEvent &event) override {
    if(auto parent = getParentComponent()) {
      parent->mouseExit(event);
    }
  }

private:
  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ForwardingLabel)
};
カテゴリー
C++ JUCE

JUCE APVTS Parameter

JUCEのAudioProcessorValueTreeStateのパラメターを使う場合、以下のようなシナリオがあると思います。

  1. パラメターをゲットして、値をget/setする場合
auto param = valueTreeState.getParameterAsValue(ParamId);
// getting value
int paramValue = param.getValue();
// setting value
param.setValue(juce::var(newValue));

2. パラメターのリアルタイム値をゲットしたい場合

int value = (int)*valueTreeState.getRawParameterValue(ParamId);

Host(DAW)に保存されているパラメターをPlug-In再起動時に読み込む場合などは、getRawParameterValueを使わないといけないようです。

カテゴリー
C++ JUCE

JUCE ThreadID

JUCEでThreadIDを確認するには以下の方法があります。

// get threadID
void* currentThreadId = juce::Thread::getCurrentThreadId();
DBG("setSelectorId thread ID: " + juce::String(reinterpret_cast<uintptr_t>(currentThreadId)));

// check if the thread is the Message Thread
bool isMessageThread = juce::MessageManager::getInstance()->isThisTheMessageThread();
DBG("Is this message thread? " + juce::String(isMessageThread ? "YES" : "NO"));
カテゴリー
C++ JUCE

JUCE ComboBoxの操作を区別する

ComboBoxの操作において、プログラム的に操作をする場合とユーザーが手動で操作をする場合において、異なるcallback処理をしたい場合は、以下のようにCustomComboBoxクラスのmouseDown()をoverrideするといいと思います。

class CustomComboBox  : public juce::ComboBox
{
public:
  CustomComboBox() : juce::ComboBox()
  {

  }

  ~CustomComboBox() override
  {
  }

  void mouseDown(const juce::MouseEvent &event) override {
    DBG("Mouse down");
    userInteracted = true;
    juce::ComboBox::mouseDown(event);
  }
  
  bool getUserInteracted() const {
    return userInteracted;
  }
  
  void setUserInteractedToFalse() {
    userInteracted = false;
  }
  
private:
  bool userInteracted = false;
  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComboBox)
};
void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override {
  if (comboBoxThatHasChanged == &selector) {
    if(selector.getUserInteracted()) {
      DBG("user selected ...");
      // do something
      selector.setUserInteractedToFalse();
    } else {
      DBG("program selected ...");
      // do something
    }
  }
}
カテゴリー
C++ JUCE

JUCE ComboBoxのSelectorID

ComboBoxをAudioProcessorValueTreeState::ComboBoxAttachmentを使わずにAPVTSのパラメターと連動させたい場合は以下のようにします。

こうすることで、SelectorIDのmax値を2000(でも2000000でも可)などにし、後々の拡張にも対応出来ます。setValue()の時、型をjuce::var()にキャストすることでホスト(DAW)のパラメータとして保存されるようです。

void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override 
{
  if (comboBoxThatHasChanged == &voicingSelector) 
  {
    controls.clear();
    int comboBoxSelectedIndex = comboBoxThatHasChanged->getSelectedId();
    if(comboBoxSelectedIndex != 0) 
    {
      DBG("comboBoxSelectedIndex " << comboBoxSelectedIndex);
      auto selectorIdParam = valueTreeState.getParameterAsValue(selectorIdString);
// need to cast to juce::var type in order to persist the value in Host...

selectorIdParam.setValue(juce::var(comboBoxSelectedIndex));
    }
  }
}

復習ですが、ComboBoxの基本的な使い方は以下。。。

// add the parent Component as a listener
// you should remove the listener in the deconstructor
comboBox.addListener(this);

// sets selector id
comboBox.setSelectedId(id, juce::dontSendNotification);