#はじめに
今回はMin-DevKitというものを用いて、MaxのエクスターナルオブジェクトをC++で自作する方法について紹介します。
ついに、夢のカスタムオブジェクト制作です!
基本的には、こちらのgithubやライブラリ、映像で紹介されている通りに進めていき、後半は簡単なオブジェクトを実際に作ってみます。
毎度のことながら、とりあえずこうすればできました!の列挙になっているので、質問やツッコミ、訂正等いただけたら、大変うれしいです。
ぜひよろしくお願いします。
それでは、早速やってみましょう!
-今後、スクリーンショットなど増やしていきます!
また、公式の発表したやり方でうまくいかなかった部分があり、無理やりやってます。ここの解決方法やアドバイスをいただけたら、大変嬉しいです。-
環境 : macbook pro(OS Mojave), Max(ver8.1.5)
#準備物
- MaxのPackage Managerを開いて"min-devkit"と検索すると、min-devkitが見つかるので、こちらをインストールします。
- CMakeのサイトからCMakeをダウンロードします。
- Xcode(10 or 11)をダウンロードします。(windowsの方はVisual Studio2017 or 2019, 以降macを用いたものとして話を進めます。)
#テンプレートを見てみる
上でダウンロードしたMaxのMin-DevKitを実行すると、"C++ Object Development Kit"というパッチが開きます。
あとは、こちらに書いてある流れの通りに進めるだけで、ファイルが作られるのですが、その前にexampleのファイルを見てみましょう。
"C++ Object Development Kit"の上部のタブの一番左にある"Overview"を開くとMin-DevKitを用いて作られたオブジェクトの例がのっています(min.* みたいなやつです)。
ここにのっているオブジェクトのソースコードは全てみることができるので、オブジェクトのhelpファイルをみてオブジェクトの挙動を確認しながら、C++で書かれたソースコードを確認し、コードの記述の仕方を掴む、といった感じです。
"C++ Object Development Kit"の上部のタブの2番目にある"Compile Code"は、ソースコードをXcodeで開いてくれるパッチです。
上半分(1)がパッケージの選択(min.-オブジェクトだと、min-devkitがパッケージ名)、下半分(2)がパッケージ内のオブジェクトの選択です。
ここでオブジェクトを選択すると、勝手にXcodeが開いてくれるそうです。(僕は一度も成功したことがありません。)
このソースコードを参考にC++でオブジェクトをコンパイルします!
###ここでXcodeが開いてくれないとき
諦めたくなりますが、力技で解決します。
MacのFinder内のDocuments(書類)に"Max 8"というフォルダがあると思います。
この中の"Max 8/Packages/min-devkit"というフォルダがあります。これは、Packege ManagerでMin-DevKitをダウンロードしたときにダウンロードしたものです。
この中に"CMakeLists.txt"というのがあるのを確認して、同じ階層に"build"という空のフォルダを作ります。
次に上でダウンロードしたCMakeを開いて"Where is the source code"に"CMakeLists.txt"があるフォルダを指定、"Where to build the binaries"に先ほど作った同じ階層の"build"階層を指定します。
あとはgenerateのボタンを押すと、プロジェクトの種類を指定するようにポップアップが出ます。今回はmacなので"Specify the generator for this project"で"Xcode"を選択します。(他はそのままで大丈夫です。)
すると、build内にxcodeprojectを作ってくれます。
これを開けば、ソースコードがみれます。
ヘルプパッチと合わせてコードを見てみましょう!!
#自分のオブジェクトを作る
さて、では実際にオブジェクトを作ってみましょう。
今回は、bangを送ったら自分の簡単なプロフィールをコンソールに出力する、という誰得オブジェクトを作ってみたいと思います。
##ファイルの作成
まずはプロジェクトファイルを作ります。
###パッケージの作成
"C++ Object Development Kit"の上部タブの3番目にある"Create a Package"にてパッケージを作ります。
開くと、おっきなボタンがありますので、そこを押すとパッケージの名前を聞かれます。
好きな名前で大丈夫ですが、ここで指定した名前がオブジェクト名の"xx.-"のxxの部分になります。
僕は"ld"という名前にしました。
###オブジェクトファイルの作成
続いてオブジェクトの名前を指定します。
4番目のタブの"Create an Object"にて行います。
パッチ上部のメニューから、先ほど作成したパッケージを指定し(僕は"ld")、下にある大きなボタンをクリックすると、オブジェクトの名前を聞かれます。
好きな名前にしてください。(僕は"bio"としました。)
さて、順調にいけばこれで自動的にxcodeのプロジェクトが作成され、開かれます。(らしいのですが、僕はここでも自動的には開いてくれませんでした。)
####ここでやっぱり自動的に開いてくれないとき
諦めたくなりますが、力技で解決します(2)。
この時点で、"Documents/Max 8/Packages/"のフォルダ内には先ほど作成したパッケージ(僕は"ld"でした)が存在し、その中に入ると、上で確認したような"min-devkit"パッケージと同じようなファイルが存在しているのがわかるかと思います。この中の"source/projects"フォルダ内に名前を指定したオブジェクト(僕は"ld.bio")と"xx.hello-world"のフォルダが作成されていると思います。
そこまでできている場合は、先ほどと全く同じで、"CMakeLists.txt"のある階層にbuildフォルダを作り、CMakeでbuildフォルダ内にプロジェクトファイルを作成します。
この自作パッケージ内に"CMakeLists.txt"がない場合は、先ほどの"min-devkit"パッケージ内の"CMakeLists.txt"をコピペしてもってきます。
その後の操作は、"min-devkit"パッケージのときと同様に、CMakeを使って自分のパッケージ内にプロジェクトファイルを作ります。
作成したら、その中にあるプロジェクトファイル(僕の場合は"ld.xcodeproj"というファイル)を開きます。
##コードの記述
さて、それではいよいよC++にてコードを書いていきます。
###プロジェクトファイル内のコード
パッケージを作成した際に、デフォルトで"XX.hello-world"というオブジェクトのファイルが生成されます。
特に何も指定せずにxcodeのbuildを押すと、"XX.hello-world"というオブジェクトと、先ほど指定した名前のオブジェクトが、特に問題なく生成されると思います。
"Documents/Max 8/Packages/XX(自分のパッケージ)"の中の"externals"というフォルダの中をみると、自分が作ったエクスターナルオブジェクトが"XX.hello-world.mxo"(Macの場合)という感じで存在しているのが確認できると思います。
また、"help"フォルダの中には、同じくhelpファイルが"XX.hello-world.maxhelp"のような感じで存在しているのがわかると思います。
これらは、xcodeにてbuildしたあとにMaxを立ち上げ直す(Maxを起動していない場合は普通にMaxを立ち上げる)と、使えるようになります。
試しに、この段階でMaxに"xx.hello-world"(僕の場合は"ld.hello-world")というオブジェクトを置いてみましょう。エラーなくオブジェクトを置くことができると思います。
ヘルプファイルも開けることを確認しましょう。
###デフォルトのソースコード
さて、先ほど自分で指定した名前のオブジェクトも同様にエラーなくパッチ内に置くことができますが、どのような機能があるのか確認しましょう。
ヘルプファイルを見ればわかると思いますが、これは"xx.hello-world"オブジェクトと同じ機能です。
つまり、オブジェクトが生成された際、デフォルトで"xx.hello-world"オブジェクトと全く同じコードが記述されています。(ソースコードを直接確認すれば、より明確です。)
この状態から書き換えて、自分の実装したいものをコードで記述します。
###コード内の_testファイル
ソースコードにはデフォルトで"xx.-"というファイルと"xx.-_test"というファイルが存在します。このうち"xx.-"のほうは、実際のオブジェクトの挙動を書くファイルで"_test"のファイルはユニットテストを行うためのファイルです。
例えば、hello-worldの場合、テストファイルでは「もしもbangを入力したとき」のようなシナリオを考え、そのときの挙動を確認する、といったような使い方です。
これを使わない場合、C++によるテストをせずに、ビルドする度にMaxを立ち上げてオブジェクトを実際に置いてテストする、という方法が考えられます。
このテストファイルは新規オブジェクトファイルを作成した際に必ず生成されてしまいます。僕はほとんど使用しないので、CMakeでプロジェクトファイルを生成する前に"/Documents/Max 8/Packages/(パッケージの名前)/source/projects/(オブジェクト)/(オブジェクト)_test.cpp"から削除しています。
###自己紹介オブジェクト
さて、前置きがかなり長くなりましたが、実際のオブジェクトのコードの書き換えを行いたいと思います。
ちなみに、今回のオブジェクトのソースコードはgithubの方で公開していますので、ご参照ください。
今回はBangが入力されたら、Max consoleに自分の超略歴が出力されるオブジェクトをつくります。
ソースコードを上からみていきます。
色々宣言をした後に
MIN_DESCRIPTION {"Post to the Max Console."};
MIN_TAGS {"utilities"};
MIN_AUTHOR {"Cycling '74"};
MIN_RELATED {"print, jit.print, dict.print"};
となっています。
ここは、ブジェクトの特性などを記述する部分です。
ここに記述したものはオブジェクトのreferenceなどに記載されます。
(そう、この自作オブジェクトたちにはreferenceのページもちゃんと作られるのです。)
inlet<> input { this, "(bang) post greeting to the max console" };
outlet<> output { this, "(anything) output the message which is posted to the max console" };
の部分では、オブジェクトのインレットとアウトレットを定義します。
入力タイプや出力タイプ、マウスオーバーした際に出る説明などを記載します。
この場合はそれぞれ一つずつになっていますが、今回作るパッチではアウトレットが不要なのでoutletの行を削除します。
// define an optional argument for setting the message
argument<symbol> greeting_arg { this, "greeting", "Initial value for the greeting attribute.",
MIN_ARGUMENT_FUNCTION {
greeting = arg;
}
};
// the actual attribute for the message
attribute<symbol> greeting { this, "greeting", "hello world",
description {
"Greeting to be posted. "
"The greeting will be posted to the Max console when a bang is received."
}
};
ここは、argumentとattributeを指定します。
どんな型のアーギュメントやアトリビュートを指定するのか、をここで指定することで、それをコード内で使用することができます。
この前半部分では、アーギュメントとして指定された文字列を"greeting"という変数に格納しています。(後ほど使います。)
このように入力したメッセージは"arg(もしくはargs)"という変数で受け取ることができます。
後半部分では、アーギュメントが指定される前の初期値として"hello world"が定義されています。
今回は、このargumentsとattributeの指定も不要なので、全部消します。
// respond to the bang message to do something
message<> bang { this, "bang", "Post the greeting.",
MIN_FUNCTION {
symbol the_greeting = greeting; // fetch the symbol itself from the attribute named greeting
cout << the_greeting << endl; // post to the max console
output.send(the_greeting); // send out our outlet
return {};
}
};
さあ、この部分は入力によっての処理を記述する部分になります。
ここでは、bangを入力した際の挙動がすべてこの部分に記載されています。
ちなみに、もちろん処理を別関数として記述して、入力に応じて関数を呼び出す、といった処理を記述しても構いません。
ここで定義されている処理は、
先ほど文字列を格納した"greeting"を"the_greeting"という変数に格納し直して、Max consoleに出力しています。
この"cout << XX << endl"で挟むと、Max consoleに出力してくれます(普通のc++と同様ですね)。
次行の"output.send(XX)"の部分は、XXをアウトレットから出力するものです。
上でアウトレットの名前を"output"と定義しているので"output.send"となります。
今回はアウトレットもなく、入力するメッセージもないので削除して、このように書き換えました。
message<> bang { this, "bang", "Post the bio.",
MIN_FUNCTION {
cout << "LUDO TAKEBE / 武部 瑠人(たけべ るど) - ..." << endl; // post to the max console
return {};
}
};
最後の
// post to max window == but only when the class is loaded the first time
message<> maxclass_setup { this, "maxclass_setup",
MIN_FUNCTION {
cout << "hello world" << endl;
return {};
}
};
という部分は、オブジェクトを生成した際に行う処理です。(コンストラクタとは別です。)
この場合、オブジェクトをパッチに置くと、"hello world"とMax consoleに出力してくれます。
これも今回は不要なので、この部分は消します。
書き換えは以上です!!
あとはbuildして、Maxパッチに実際に置いてbangを送ってみましょう。
このときのbuild(run)は、"ALL_BUILD"ではなく、コードを記述したファイルのみを指定して行った方が、エラーもなく、時間も短く済みます。
短いので、ソースコードの全体を載せます。
/// @file
/// @ingroup minexamples
/// @copyright Copyright 2018 The Min-DevKit Authors. All rights reserved.
/// @license Use of this source code is governed by the MIT License found in the License.md file.
#include "c74_min.h"
using namespace c74::min;
class ld_bio : public object<ld_bio> {
public:
MIN_DESCRIPTION {"post LUDO's bio"};
MIN_TAGS {"utilities"};
MIN_AUTHOR {"LUDO TAKEBE"};
MIN_RELATED {"print"};
inlet<> input { this, "(bang) post bio to the max console" };
// respond to the bang message to do something
message<> bang { this, "bang", "Post the bio.",
MIN_FUNCTION {
cout << "LUDO TAKEBE / 武部 瑠人(たけべ るど) - ..." << endl; // post to the max console
return {};
}
};
};
MIN_EXTERNAL(ld_bio);
##やってみた結果
実際にできたオブジェクトはこんな感じです。
I created a custom externals object in Max, using Min-DevKit and compiling c++.
— LUDO TAKEBE (@LUDO_vis_) August 23, 2020
it just output my bio! :) pic.twitter.com/Uj0x1HNdI0
たったこれだけのオブジェクトですが、自分で作ると感動ですね。
#むすび
以上です。
当然ながら、今回紹介していない機能はたくさん存在します。githubなどをみるといろんなサンプルがありますので、ぜひご覧ください。
また、僕のgithubにもいくつか他のオブジェクトがあるので、拙作ですがよければ参考程度にみてみてください。(特にinletとoutletの数をargumentで指定して動的に変更する方法などはかなり時間がかかったので、同じようなことで詰まってる方はご質問ください!)
最初にも書きましたが、ツッコミや質問、ご指摘などありましたら、ご遠慮なくお送りください。
お願いします!
では、さらにMax使っていきましょ〜〜。