カテゴリー
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);
    カテゴリー
    Linux Mac

    Davinci ResolveでPythonを使う

    Davinci ResolveでScriptを追加したい。

    この情報では、Davinci ResolveはPATH `/usr/local/bin/python3`でPython3を探そうとするので、シンボリックリンクを貼るといい、とありました。これで解決しました。

    ln -s `which python3` /usr/local/bin/python3

    カテゴリー
    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
    {
    
    };