カテゴリー
C++ JUCE

JUCE ARA Binding

 🔗 ARA Binding Process – Step by Step

  1. Plugin Declaration Phase

  When JUCE builds your plugin, it needs to declare ARA capability:

  // In your plugin’s JucePluginCharacteristics.h or .jucer settings

  #define JucePlugin_Enable_ARA 1

  // This tells JUCE to build ARA support into the plugin binary

  2. ARA Factory Export

  Your plugin must export an ARA factory function:

  // In PluginARADocumentController.cpp (which you have)

  const ARA::ARAFactory* JUCE_CALLTYPE createARAFactory()

  {

      return juce::ARADocumentControllerSpecialisation::createARAFactory<GrooveEngineTracksDocumentController>();

  }

  This is the “entry point” that DAWs call to discover ARA capability.

  3. Plugin Inheritance Chain

  Your plugin processor must inherit from AudioProcessorARAExtension:

  // In PluginProcessor.h

  class GrooveEngineTracksAudioProcessor : public juce::AudioProcessor

                                         , public juce::AudioProcessorARAExtension  // ← ARA binding!

  {

      // …

  };

  4. ARA Binding Sequence (What Happens at Runtime)

  When you insert the plugin on a track in Studio One:

  1. Studio One loads plugin → Calls createARAFactory()

                             ↓

  2. Factory returns ARA capabilities → Studio One sees “this plugin supports ARA”

                             ↓

  3. Studio One creates ARA document → Calls your DocumentController constructor

                             ↓

  4. Plugin instance created → AudioProcessorARAExtension gets bound to the document

                             ↓

  5. isBoundToARA() returns true → Plugin can now access ARA features

  5. The Binding Check

  You can verify binding status:

  // In your plugin code

  if (auto* araExtension = dynamic_cast<juce::AudioProcessorARAExtension*>(&audioProcessor))

  {

      if (araExtension->isBoundToARA())

      {

          // 🎉 Successfully bound! Can access ARA features

          auto* docController = araExtension->getDocumentController();

          auto* playbackRenderer = araExtension->getPlaybackRenderer();

      }

      else

      {

          // ❌ Not bound – plugin loaded as regular audio effect

      }

  }

  6. What “Binding” Actually Means

  Bound = Connected to ARA Document:

  – Plugin has access to timeline audio data

  – Can read from ARAAudioSource objects

  – Receives PlaybackRegion assignments

  – Gets timeline sync and transport info

  Not Bound = Regular Audio Plugin:

  – Only processes live audio buffer

  – No access to timeline data

  – No waveform display capability

  7. DAW-Specific Binding Triggers

  Studio One:

  – Binding happens when you insert plugin as “Event FX”

  – Regular insert = not bound, Event FX = bound

  Logic Pro:

  – Binding happens automatically for ARA-capable plugins

  – Plugin appears in both regular and ARA contexts

  Reaper:

  – User explicitly chooses “ARA” vs “Normal” mode when inserting

  8. Debugging Binding Issues

  Common reasons binding fails:

  // ❌ Missing ARA factory export

  // ❌ Plugin not built with JucePlugin_Enable_ARA = 1  

  // ❌ Wrong insertion method in DAW

  // ❌ Plugin binary not in system directories

  // ❌ DocumentController constructor issues

  9. Your Plugin’s Binding Status

  From your CLAUDE.md, your plugin IS successfully binding:

  – ✅ isBoundToARA() returns true

  – ✅ DocumentController accessible

  – ✅ Audio sources detected (3 sources found)

  – ✅ Factory creation working

  The binding is working perfectly! The issue you discovered was about track isolation, not binding itself.

  10. Visual Confirmation

  You can add this debug output to see binding in real-time:

  // In your plugin constructor or editor

  DBG(“ARA Binding Status: ” + juce::String(araExtension && araExtension->isBoundToARA() ? “BOUND” : “NOT BOUND”));

  Key Takeaway: Binding is the DAW’s way of saying “this plugin instance should have access to timeline audio data” rather than just

  processing live audio streams. Your plugin is successfully bound – that’s why you can see waveforms at all!

カテゴリー
C++ JUCE

juce::AudioSource関連クラス

  1. juce::File – ディスク上のファイル
    ・パスを表現している
    ・オーディオに関しては何も知らない
    ・AudioFormatReader作成時に使用される
    juce::File audioFile("/Users/username/audio/kick.wav");

    2. juce::AudioFormatReader – decoder/parser
     ・ファイルからオーディオを読む
     ・オーディオをバッファしない – 都度ディスクから読む
     ・thread-safeではない

    auto* reader = formatManager.createReaderFor(audioFile);

    3. juce::AudioFormatReaderSource – readerをstreamable sourceに変換する
     ・AudioFormatReaderをwrapしAudioSourceを作成する
     ・オーディオをバッファし、ポジションをトラッキングする
     ・real-timeプレイバックに使う
     ・コンストラクト時にreaderのownershipを保持する

    auto readerSource = std::make_unique<juce::AudioFormatReaderSource>(reader, true);
    
    カテゴリー
    C++

    command failed with a nonzero exit code

    XCodeのProduct -> Clean Build Folderでは上手くいかない場合

    xcodebuild clean -project YourApp.xcodeproj

    とすると、
    ** CLEAN FAILED **

    とログを吐き出すと思います。

    $ rm -rf "/Users/YourName/YourPath/YourApp/Builds/MacOSX/build"

    一旦builフォルダごと削除して、再度Build Cleanをすると上手く行きました。


    ** CLEAN SUCCEEDED **

    カテゴリー
    C++ JUCE

    JUCEでフォントを使う

    JUCEでフォントを使うには

    // フォントファイルをプロジェクトに読み込み
    // juce::FontOptions オブジェクトを定義
    juce::FontOptions mainFont = juce::FontOptions(juce::Typeface::createSystemTypefaceFor(BinaryData::RobotoCondensedVariableFont_wght_ttf, BinaryData::RobotoCondensedVariableFont_wght_ttfSize));
    
    // 使用する場合はjuce::FontオブジェクトにmainFontをアサイン
    juce::Font font = mainFont;
    font.setHeight(height);
    label.setFont(font);
    カテゴリー
    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());
      }
    }
    カテゴリー
    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"));