カテゴリー
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);
カテゴリー
C++

C++ でstd::vectorをsortする

C++でstd::vectorを特定の順番でソートするsample code

例1 strings

void sortGuitarVoicingTypes() {
  std::vector<juce::String> order = {"Drop 2", "Drop 3", "Drop 2 & 3", "Drop 2 & 4", "Drop x2 & 3", "Closed Voicing"};
  std::vector<juce::String> result;
  for (const auto& str : order) {
    auto it = std::find(guitarVoicingTypes.begin(), guitarVoicingTypes.end(), str);
    if (it != guitarVoicingTypes.end()) {
      result.push_back(str);
      guitarVoicingTypes.erase(it);
    }
  }
  std::sort(guitarVoicingTypes.begin(), guitarVoicingTypes.end());
  result.insert(result.end(), guitarVoicingTypes.begin(), guitarVoicingTypes.end());
  guitarVoicingTypes = result;
}

例2 Object

struct Voicing {
  Voicing() {}
  Voicing(int i, juce::StringRef name, juce::StringRef quality, std::vector<int> voices)
  : index(i), name(name), quality(quality), voices(voices) {}
  int index;
  juce::String name;
  juce::String quality;
  std::vector<int> voices;
 
};
  
struct compare {
  inline bool operator() (const Voicing& v1, const Voicing& v2)
  {
    // Define the ordering of the quality values
      const std::array<juce::String, 4> qualityOrder = {MINOR_NAME, MAJOR_NAME, MINOR_MAJOR_NAME, DOMINANT_NAME};

      // Compare the quality values
      auto lhsQualityIndex = std::distance(qualityOrder.begin(), std::find(qualityOrder.begin(), qualityOrder.end(), v1.quality));
      auto rhsQualityIndex = std::distance(qualityOrder.begin(), std::find(qualityOrder.begin(), qualityOrder.end(), v2.quality));
      
    if (lhsQualityIndex != qualityOrder.size() && rhsQualityIndex != qualityOrder.size()) {
      // If both qualities are in the qualityOrder array, compare their indices
      return lhsQualityIndex < rhsQualityIndex;
    } else if (lhsQualityIndex == qualityOrder.size() && rhsQualityIndex == qualityOrder.size()) {
      // If both qualities are not in the qualityOrder array, compare them alphabetically
      return v1.quality < v2.quality;
    } else if (lhsQualityIndex != rhsQualityIndex) {
      // If only one quality is in the qualityOrder array, it is considered greater than the other
      return lhsQualityIndex < rhsQualityIndex;
    } else {
      // If the quality values are the same, compare the indices
      return v1.index < v2.index;
    }
  }
};

このように使います。

// std::vector<Voicing> voicings {};
std::sort(voicings.begin(), voicings.end(), compare());
カテゴリー
C++ JUCE

JUCEでJSONを読み込む

Compile timeにJSONファイルを読みたい場合は以下のようにします。

Resourcesというグループを作り、その中にjsonファイルをおきます。
// BinaryDataというオブジェクトが自動的(?)に作成されるようです。
const char* jsonData = BinaryData::jazz_chords_json;
juce::String jsonString = juce::String::createStringFromData(jsonData, BinaryData::jazz_chords_jsonSize);
juce::var parsedJson = juce::JSON::parse(jsonString);
juce::var jazzChords = parsedJson["jazz_chords"];
for(int i = 0; i < jazzChords.size(); ++i) {
  DBG(juce::String(jazzChords[i]["notes"]));
}
カテゴリー
Mac

MacでLogicの音声と画面、マイクの音声入力を収録

M1以前はSoundFlowerが使えたようですが、M1以降はBlackHoleというAudio Driverを使うと良いようです。設定方法を記録しておきます。

Blackholeはここで入手
2chもしくは16chをインストールします。

Multi-Output Deviceを作成 To OBSと名前をつけます。
BlackHole 2chとAudio Interface(ここではScarlett)を選択し、Master DeviceをBlackHole 2chに指定します。
Logic ProのAudio Outputを先ほど作成したTo OBSに指定します。
OBSのDesktop Audio DeviceとしてBlackHole 2chを指定します。

ScarlettをMaster DeviceとするとOBSの音声収録が不安定でしたが、MasterをBlackHoleにすることで安定しました。

カテゴリー
C++ JUCE

JUCE PropertiesFile

それほどサイズの大きくないデータ(ユーザー設定などに関する情報)はPropertiesFileに保存するのが良いようです。

JUCEフォーラム参照

カテゴリー
JUCE

Pluginを配布するためのNotarizationについて

SignされていないPkgファイルを配布すると、「身元不明の開発者」というアラートが出ます。これはPkgファイルがnotarizeされていないためです。

これにどう対処するかのメモです。

参考にした情報
Notarizing Installers For macOS Catalina
Apple Audio Plugin Notarization
Macでプラグインのインストーラーを作成する

まずは、ビルドの対象マシンをAny Mac(Apple Silicon, Intel)とします。

CertificateはDeveloper ID InstallerとDeveloper ID Applicationのふたつが必要です。

Keychain AccessからSigning requestを発行します。*このプロセスは有効期限内のCertificationがある場合はスキップ出来ます。むしろ、同じ名前のCertificationが二つ以上あると認証が上手く出来ません。(Updated 28 Feb 2024)

Keychain Access -> Certificate Assistant -> Request a Certificate From a Certificate Authority…
Emailと、入手したいCertificateに適当な名前をつけます。Saved to diskを選択すると、選択したフォルダにrequestが作成されます。
Show in Finderをクリックするとどこに保存されたのかを示してくれます。
Developer ID Installerを選択 -> Continue
G2 Sub-CAを選択し、Choose Fileで先ほど作成したRequestを選択、-> Continue

Certificateをダウンロードし、ダブルクリックするとKeychainに保存されます。

上記のプロセスをDeveloper ID Applicationにおいても繰り返します。

以下はビルド手順です。

Allを選択
Build ConfigurationはReleaseを選択
ビルドをすると/Users/<your_user_name>/Library/Audio/Plug-InsのVST3とComponentsにファイルが作成されます。

以下のコマンドでDeveloper ID Application 証書で codesignをします。

codesign --force -s "Developer ID Application: Your Team (XXXXXXXXXX)" ./ChordGenius.vst3 --timestamp --options runtime

codesign --force -s "Developer ID Application: Your Team (XXXXXXXXXX)" ./ChordGenius.component --timestamp --options runtime

sign済のファイルに対して 以下のpkgbuildコマンドでDeveloper ID Installer 証書を使って pkgファイルを作成します。

pkgbuild --root /Users/<your_user_name>/<path>/ChordGenius.vst3 --identifier app.kobito.ChordGenius --install-location /Library/Audio/Plug-Ins/VST3/ChordGenius.vst3 --version 1.0.0 --sign "Developer ID Installer: Your Team (XXXXXXXXXX)" --timestamp /Users/<your_user_name>/<path>/ChordGeniusVST3.pkg


pkgbuild --root /Users/<your_user_name>/<path>/ChordGenius.component --identifier app.kobito.ChordGenius.ChordGeniusAUv3 --install-location /Library/Audio/Plug-Ins/Components/ChordGenius.component --version 1.0.0 --sign "Developer ID Installer: Your Team (XXXXXXXXXX)" --timestamp /Users/<your_user_name>/<path>/ChordGeniusAUv3.pkg

ここでApp-Specific Passwordを作成します。

パスワードが登録されていると、以下のコマンドでTeam ID情報を確認出来ます。

xcrun altool --list-providers -u "<your_email_address"
// paste the app-specific password
// you'll get the Team ID information

パスワードをKeychainに登録します。

xcrun notarytool store-credentials --apple-id "<your_email_address>" --password "xxxx-xxxx-xxxx-xxxx" --team-id "XXXXXXXXXX"

// prompts to enter a profile name. can be any name.

最後に、notarytoolでpkgファイルをAppleに提出します。自動的にスキャンされ、問題がなければApproveされます。

xcrun notarytool submit /Users/<your_user_name>/<path>/ChordGeniusVST3.pkg --keychain-profile "<your_profile_name>" --wait