TouchDesigner C++を使う
TouchDesignerで、C++周りのPluginを作るための試行錯誤を日記的に、
つまづくことをどんどんメモしておく。
随時更新
最終編集 : 2023/07/02
環境
環境1 home
macOS Catalina 10.15.7
cmake version 3.19.1
TouchDesigner Non Commercial 2022.29530
XCode 12.4
環境2 work
macOS Catalina
cmake version
環境3 home win
win10
visual Studio 2022
TouchDesigner 2022.29530
satoruhiga/TouchDesigner-Plugin-Templateを動かす
C++でオリジナルのオペレーターを作る
より、satoruhigaさんのTD用のC++を作るためのテンプレートのGitHubがあるのでそれを参考に動かそうと試みる。以下がsatoruhigaさんの諸々のリンク
Youtubeに上げてくれているこのTemplateのHow to動画
How to use TouchDesigner-Plugin-Template on Windows
How to use TouchDesigner-Plugin-Template on OSX
CMakeがbuildできない[未解決]
とりえあえず、satoruhigaさんのtutorialにそのまま従って、やるとcmake ..
をするときに失敗する。
環境1では以下の文言
$ cmake -build .
CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
Compatibility with CMake < 2.8.12 will be removed from a future version of
CMake.
Update the VERSION argument <min> value or use a ...<max> suffix to tell
CMake that the project does not need compatibility with older versions.
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/xxxxxxx/xxxxxxx/TouchDesigner/cppOP_storuhiga_test/TouchDesigner-Plugin-Template/build
CMake suite maintained and supported by Kitware (kitware.com/cmake).
cmakeのバージョン周りのエラーっぽい。
githubから撮ってきたまんまのCMakeLists.txt
はcmake_minimum_required(VERSION 2.8)
を使っている。
なんかわからないけどここを参考にしてcmake_minimum_required(VERSION 3.13)
にしてみた。
cmake ..
からのcmake --build .
Scanning dependencies of target CustomSOP
[ 5%] Building CXX object CMakeFiles/CustomSOP.dir/src/SOP_main.cpp.o
[ 10%] Building CXX object CMakeFiles/CustomSOP.dir/derivative/FrameQueue.cpp.o
[ 15%] Building C object CMakeFiles/CustomSOP.dir/derivative/GL/glew.c.o
[ 20%] Building C object CMakeFiles/CustomSOP.dir/derivative/GL/glewinfo.c.o
~~~~~~~~/cppOP_storuhiga_test/TouchDesigner-Plugin-Template/derivative/GL/glewinfo.c:13444:11: warning:
implicit declaration of function 'sscanf_s' is invalid in C99
[-Wimplicit-function-declaration]
if (sscanf_s(argv[p++], "%d.%d", ¶ms->major, ¶ms->minor) !...
^
.
.
.
'CGLReleasePixelFormat' has been explicitly marked deprecated here
extern void CGLReleasePixelFormat(CGLPixelFormatObj pix) OPENGL_AVAILABL...
4 warnings generated.
[ 25%] Linking CXX CFBundle shared module ../out/CustomSOP.bundle/Contents/MacOS/CustomSOP
make[2]: ../bin/MacOSX/oscer: Permission denied
make[2]: *** [../out/CustomSOP.bundle/Contents/MacOS/CustomSOP] Error 1
make[1]: *** [CMakeFiles/CustomSOP.dir/all] Error 2
make: *** [all] Error 2
sscanf_s周りでなんかエラー出していて、binの中に入っているoscerという謎のもののPermission deniedになっている。oscerを見にいってみる
drwxr-xr-x 3 xxxx staff 96 11 5 03:03 .
drwxr-xr-x 4 xxxx staff 128 11 5 03:03 ..
-rw-r--r-- 1 xxxx staff 4096288 11 5 03:03 oscer
確かに実行権限がなさそうなんでchmodで与えてみる。ほんとは良くないんだろうけどめんどくさいから、chmod 777
で全権限を与えた。
もう一回 build
4 warnings generated.
[ 25%] Linking CXX CFBundle shared module ../out/CustomSOP.bundle/Contents/MacOS/CustomSOP
ld: warning: directory not found for option '-L/Users/xxxxxx/programming/TouchDesigner/cppOP_storuhiga_test/TouchDesigner-Plugin-Template/libs/lib'
Undefined symbols for architecture x86_64:
"_sscanf_s", referenced from:
_glewParseArgs in glewinfo.c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [../out/CustomSOP.bundle/Contents/MacOS/CustomSOP] Error 1
make[1]: *** [CMakeFiles/CustomSOP.dir/all] Error 2
make: *** [all] Error 2
oscer周りのエラーは消えた。けどdirectory not found ~~~~~~~~~/libs/lib
ってでている。これがlinkできない的なことが書いてありそう。
たしかに現在の作業ディレクトリに/libs/libなんて存在しない。
だけど、CMakeLists.txtではなんかそこからライブラリ周りでなんかしようとしている。
include_directories(
"src"
"derivative"
"derivative/GL"
${CMAKE_CURRENT_SOURCE_DIR}/libs/include
)
link_directories(
${CMAKE_CURRENT_SOURCE_DIR}/libs/lib
)
libsまわりだけコメントアウトしてみる。
だめ。
^
4 warnings generated.
[ 25%] Linking CXX CFBundle shared module ../out/CustomSOP.bundle/Contents/MacOS/CustomSOP
Undefined symbols for architecture x86_64:
"_sscanf_s", referenced from:
_glewParseArgs in glewinfo.c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [../out/CustomSOP.bundle/Contents/MacOS/CustomSOP] Error 1
make[1]: *** [CMakeFiles/CustomSOP.dir/all] Error 2
make: *** [all] Error 2
sscanf_s
がglewinfo.c.o
になんかしようとしている時にエラーだしてる?glewまわりのやつは./derivative/GL/
にいる。
というか、CMakeLists.txtでSOPもCHOPもTOPもDATも作ろうとしているけど、やめればいいのでは?CHOPだけにしてみる。
BuiltCustomOp(CustomCHOP "src/CHOP_main.cpp" "")
# BuiltCustomOp(CustomSOP "src/SOP_main.cpp" "")
# BuiltCustomOp(CustomTOP "src/TOP_main.cpp" "")
# BuiltCustomOp(CustomDAT "src/DAT_main.cpp" "")
やりなおす。
[ 20%] Linking CXX CFBundle shared module ../out/CustomCHOP.bundle/Contents/MacOS/CustomCHOP
Undefined symbols for architecture x86_64:
"_sscanf_s", referenced from:
_glewParseArgs in glewinfo.c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [../out/CustomCHOP.bundle/Contents/MacOS/CustomCHOP] Error 1
make[1]: *** [CMakeFiles/CustomCHOP.dir/all] Error 2
make: *** [all] Error 2
結局CustomCHOPをやるときにGLのなんかを使おうとして、エラーを出している。
[ 80%] Building C object CMakeFiles/CustomCHOP.dir/derivative/GL/glewinfo.c.o
でエラーを出している。glewinfo.cのエラーが出ているからglewinfo.c.oがリンクできていないっぽい。
clang: error: linker command failed with exit code 1 (use -v to see invocation)
はリンクするファイルがたりてないということを 言っているらしい。
OpenGLまわりのGLEWまわりのことがさっっぱりわからないので詰んだ。
TD公式 Write a C plusplus Plugin
便利なものを使おうとしたのがわるいということで、一からTD公式のページからスタートする。
TouchDesigner内のSamples/CPlusPlus/
にsampleが入っている。
それぞれのsampleの中にvisual studioとxcodeのプロジェクトが入っている。
In general when the CPlusPlus OP cooks it will call some functions in your class which are asking some questions (for example, if the node should cook every frame). It will then call the execute() function where your class should do the actual work.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
C PlusPlus OPはcookするときにexcute()という関数を呼ぶようになっているらしい。
とりあえず、一回XCodeでやってみることにする。
Plugins on macOS are built as a bundle named with a .plugin extension. The sample will open and build with the latest version of Xcode on macOS.
Unlike Visual Studio, Xcode doesn't keep build products alongside project source code.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
xcodeは.plugin
というバンドルでビルドされる。
bundle, .pluginとは
Bundleについて
.app的なことね。bundleの構造
.pluginというのもbundleの一部ということらしい。プリケーションでアドオン利用可能なモジュール。拡張子は「.plugin」。
例) QuickTimeプラグイン
/Library/Internet Plug-Ins/QuickTime Plugin.plugin
[引用] https://l-w-i.net/t/macosx/bundle_001.txt
とりあえず、CHOP/CPlusPlusCHOPExample.xcodeproj
を開いてみる。
基本構造として、CPlusPlus_Commonというのが共通で必要とされていて、OPの種類によって例えば、CHOPの場合は、
CHOP_CPlusPlusBase.hが必要とされるらしい。
とりあえず、訳もわからずBuildすると、succesして、TouchDesignerを開なったと言われる。おそらく、TouchDesignerのアプリの名前をバージョン名に変えているから。
デフォルトでは勝手開くようになっているみたい。
To use a debug build during development and testing, use Product > Build in Xcode, then switch to TouchDesigner and place a CPlusPlus CHOP or TOP and hit the + button beside the Plugin Path parameter, switch back to Xcode, and drag the plugin from the Products group in the left navigation pane to the choose file dialog on-screen from TouchDesigner. You can save the .toe to use for debugging so you don't have to repeat this (see the next section).
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
TDのCPlusPlus OPのplugin pathに.dllや.pluginを選ばないといけないんだが、xcodeの左側のフォルダメニューからProductsにある、.pluginを直接ドラッグアンドロップして、TDに投げると動いた。
.pluginのfinder上での場所は、右クリックからshow in finderで見れる。
/Users/You/Library/Developer/Xcode/DerivedData/CPlusPlusCHOPExample-cekhbqwwgqgzblarivhcbzaejwai/Build/Products/Debug
コード読んでないからまだわからないけどLFOみたいなものが動く。うれしい。
XCode10.1
XCode 10.1だとどうにもバージョンが古すぎるせいか、Buildが通らなかった。
project nameの変更
Console OUTPUT
SEEING CONSOLE OUTPUT
Messages sent to std::cout will appear in the console in Xcode's Debug area. Note: printing lines of text to the console has an impact on performance. Be sure to disable all printing when performance testing or shipping a finalized plugin.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
XCodeのdebug areaのコンソールにstd::cout
とかするとちゃんと出力されるらしい。
cplusplus class達の使い方
CPlusPlus_Common.h
というファイルにTOP, CHOP, SOP, DAT等々全てのCPlusPlusのあらゆるclass全体でincludeされるファイル。ここの中に色んなClassが入っていて、普通のクラスや抽象クラスなど色々。
その上で、OP毎の、CHOP_CPlusPlusBase.h
のようなファイルが用意されている。中身はnamespace TD{}
の中にclassがたくさん定義されている。このclassは普通のclassとして機能するものと、仮想関数を提供しているclassも存在する。
基本的には、そのCHOPならCHOPの CHOP_CPlusPlusBase
をスーパークラスとした継承を行なって、使う。
TouchDesigner defines a base C++ class that you will inherit from to you create your own class. There are some pure-virtual functions you are required to override, while there are others that will do some default behavior of you don't provide an overridden method. Data is passed in and out of the functions using other simple classes which hold the information.
To initialize the plugin and create an instance of the class there are 3 C functions you are required to specify. The first one will tell TouchDesigner which version of the API you are running (by returning a constant which you can see in the header file). The other two will create (using new) and destroy (using delete) an instance of the class when TouchDesigner asks them to. A single instance of the class will get created for each OP that is using the plugin.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
Debugの設定 XCode
DEBUGGING
You can attach the Xcode debugger to TouchDesigner when it's running. Breakpoints in your plugin code will behave as normal. This is also a good way to explore all the data structures TouchDesigner passes in and out of the functions.To launch your project with the debugger already attached, edit the project's scheme (Product > Scheme > Edit Scheme...) so its Run target has TouchDesigner as its executable in the 'Info' section. If you have a .toe file already setup, put the path to that in the 'Arguments' section. Debugging will operate as normal.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
TouchDesignerと実行中のXCodeを連携してブレークポイントを設定したりしてデバックできる。
XCodeの(Product > Scheme > Edit Scheme...)より
InfoセクションのExecutableに今使っているTouchDesignerの.appを選択する。デフォルトではTouchDesigner.app
で設定されているため、アプリケーションを複数管理している場合は改めて選択すること。
そうすることでRunしたときにTouchDesignerが勝手に開いて紐づく。
さらに、
Argumentsの項目の、Arguments Passed On Launch
に.toe
のファイルパスを指定すると、Runしたときに実行時引数としてTouchDesignerに渡してくれて、そのファイルを開いてくれて早い。
諸々をすると、XCodeのコンソールに出力されているのが見れる。
XCodeのSchemeとTargetについて
Debugの設定 VisualStudio
You can attach the Visual Studio debugger to TouchDesigner when it's running. Breakpoints in your plugin code will behave as normal. This is also a good way to explore all the data structures TouchDesigner passes in and out of the functions.
To launch your project with the debugger already attached in Visual Studio, go to the project properties and under the debugging section put in the full path to TouchDesigner.exe in the 'Command' section. If you have a .toe file already setup, put that in the 'Command Arguments' section. Now you can hit F5 and your project will load up inside TouchDesigner with the debugger already attached. You'll get an error saying there is no debug information for TouchDesigher.exe, but that's expected. You'll have debug information for your .dll when the process is executing your code.
https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
Project > Propertiesを開く
ConfigurationをDebug, Platform X64を選んで、
Debuggingのところを開く。
XCode同様にCommand, Command Argumentsのところを埋める。
Commandのところに、TouchDesigner.exeのpathを入れる。
C:\Program Files\Derivative\TouchDesigner.2022.29530\bin\TouchDesigner.exe
Command Argumentsのところに、実行時に開きたいtoeファイルを指定する。
Visual Studioによる基本的な最初のセットアップ & DLLの適用の仕方
visual studioのsolutionファイル(.sin
)を開こうとしてみるとバージョンが一致していないため、アップデートを求めてくるのでソリューションファイルのアップデートを行う。
VisualStudio2022での環境でテストしたけっか、開いたばっかりだとSolution Exploerに.cpp
, .hpp
のファイルが出てこなかった。
Add > Exsiting Fileから追加しようと思ってもエラーが出て弾かれた。
ただ、Visual Studioを一度閉じて、もう一度Solution FIleを開きなおすと出てくるようになった。謎。。
テストとして、Local WIndows Debugger(F5)を押して、Debug, buildを開始してみる。
とりあえずこの状態でTouchDesignerがちゃんと開くなら、Visual StudioでのBuildは成功しているのでおk。
.sin
と同じ階層のDebugフォルダの中に、dllが生成されるようになる。
CPlusplus DAT等々にそのdllを読み込ませると使える。
Visual Studioのproject名などの変え方
projectファイルの名前を変更する。
ソリューションファイルの名前を変更する。
.sinファイルが変更される。
https://learn.microsoft.com/ja-jp/visualstudio/ide/use-solution-explorer?view=vs-2022
XCodeによる.Pluginの吐き出しかた
下記のやり方をそのままやると書き出せる。
Building on macOS using XCode
Open one of the included .xcodeproj projects from the Samples/CPlusPlus directory.
Under the 'Product' menu select 'Archive'. If the build is successful a new window will come up showing a list of archives that have been created, including the one that was just created.
Select the one that was just created and click the 'Distribute Content' button. In the 'Select a method of distribution' dialog that comes up, select 'Build Products', and click 'Next'. Pick a location to save the archive and complete the operation.
In the folder where you saved the Build Products there will be a 'Products' folder and inside that <projectname>.plugin folder. This .plugin folder is what should be placed in your 'Plugins' folder to load this as a Custom OP.
[引用] https://docs.derivative.ca/Custom_Operators
XCode上でプロジェクトファイルを変更すると、Target周りの設定も変えてくれる。
そうではなく、ただFinder上でProjectファイルをリネームすると、Product > Scheme > Edit Scheme > Build > でTargetがmissingになっている。この状態でbuildすると、/Users/You/Library/Developer/Xcode/DerivedData/CPlusPlusCHOPExample-cekhbqwwgqgzblarivhcbzaejwai/Build/Product/
が存在しない。buildされない状態となってしまう。
CHOP CPlusPlus 流れ
CPlusPlus OPがクックするときに、class内のいくつかの関数を呼び出す。
毎フレームクックすべきかどうか?()
などなど
その後にexecute()関数を呼び出して実際の処理を行う。という流れになる。
/*
When the CHOP cooks the functions will be called in this order
getGeneralInfo()
getOutputInfo()
if getOutputInfo() returns true
{
getChannelName() once for each channel needed
}
execute()
getNumInfoCHOPChans()
for the number of chans returned getNumInfoCHOPChans()
{
getInfoCHOPChan()
}
getInfoDATSize()
for the number of rows/cols returned by getInfoDATSize()
{
getInfoDATEntries()
}
getInfoPopupString()
getWarningString()
getErrorString()
*/
In general when the CPlusPlus OP cooks it will call some functions in your class which are asking some questions (for example, if the node should cook every frame). It will then call the execute() function where your class should do the actual work.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin
setupParameters(OP_ParameterManager* manager, void *reserved1)
setupParameters(OP_ParameterManager* manager, void *reserved1)で、パラメーターウィンドウの設定ができる。
void
myExampleCHOP::setupParameters(OP_ParameterManager* manager, void *reserved1)
{
// Speed
{
//OP NumericParameter classのインスタンスを作る。数字用のパラメーター?
//CPlusPlus_Common.hにそのClassの定義がある。
OP_NumericParameter np;
//NumericParameterに対して.nameや.labelとすることで設定できる。
np.name = "Speed";
np.label = "Speed";
//defaultValues[0]はparameterの1つ目の値に設定するため。vecotrとかで複数値を持つなら[1],[2]としていけば良い。
np.defaultValues[0] = 1.0;
np.minSliders[0] = -10.0;
np.maxSliders[0] = 10.0;
//CPlusPlus_Common.hの中に純粋仮想関数として定義されている。
//virtual OP_ParAppendResult appendPulse(const OP_NumericParameter &np) = 0;
//とりあえず、ここにmanager経由で渡して引数として渡してあげると以上のnpのことをfloatとして丸ごと投げてくれる。
//OP_ParAppendResultでmanager->append***()のreturnが返される。成功しているか失敗しているか。
OP_ParAppendResult res = manager->appendFloat(np);
//assert()で主張している。上の関数のreturnがsuccessだったら問題なくおk、それ以外のなんか失敗系だったら、プログラムの終了。
assert(res == OP_ParAppendResult::Success);
}
// pulse
{
OP_NumericParameter np;
np.name = "Reset";
np.label = "Reset";
//pulseのnpにdefaultValue[0]は無理みたい。下のassert()で弾かれた。
//np.defaultValues[0] = 0.0;
//CPlusPlus_Common.hの中に純粋仮想関数として定義されている。
//virtual OP_ParAppendResult appendPulse(const OP_NumericParameter &np) = 0;
//とりあえず、ここにmanager経由で渡して引数として渡してあげると以上のnpのことをpulseとして丸ごと投げてくれる。
OP_ParAppendResult res = manager->appendPulse(np);
assert(res == OP_ParAppendResult::Success);
}
// Combine
{
OP_NumericParameter np;
np.name = "Combine";
np.label = "Combine";
np.defaultValues[0] = 0.0;
OP_ParAppendResult res = manager->appendToggle(np);
assert(res == OP_ParAppendResult::Success);
}
}
manager->append****
シリーズは純粋仮想関数となっているため、どこか僕らの見えないところで中身の定義をしてくれている。取りあえずそれにOP_NumericParameter
に情報を色々付けたものを渡すと旨いようにパラメーターを作ってくれる。
CPlusPlus_Common.h
を見にいけば、どんなものが他にあるか見に行ける。
class OP_ParameterManager
{
public:
// Returns PARAMETER_APPEND_SUCCESS on succesful
virtual OP_ParAppendResult appendFloat(const OP_NumericParameter &np, int32_t size = 1) = 0;
virtual OP_ParAppendResult appendInt(const OP_NumericParameter &np, int32_t size = 1) = 0;
virtual OP_ParAppendResult appendXY(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendXYZ(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendUV(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendUVW(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendRGB(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendRGBA(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendToggle(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendPulse(const OP_NumericParameter &np) = 0;
virtual OP_ParAppendResult appendString(const OP_StringParameter &sp) = 0;
virtual OP_ParAppendResult appendFile(const OP_StringParameter &sp) = 0;
virtual OP_ParAppendResult appendFolder(const OP_StringParameter &sp) = 0;
virtual OP_ParAppendResult appendDAT(const OP_StringParameter &sp) = 0;
virtual OP_ParAppendResult appendCHOP(const OP_StringParameter &sp) = 0;
virtual OP_ParAppendResult appendTOP(const OP_StringParameter &sp) = 0;
virtual OP_ParAppendResult appendObject(const OP_StringParameter &sp) = 0;
// appendSOP() located further down in the class
getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, void* reserved1)
getOutputInfoはノードのinputが存在するときにsampleRate..etcなどの情報をどうするかという項目。
中を見ると単純なif文になっていて、inputがある場合と、ない場合になっている。
inputがある場合はデフォルトではreturn false
になっていて、false
を返すとinputとマッチするようにsampleやsampleRateがマッチする僕らの見えないところのファイルでなっている。
inputがない場合はreturn true
になっていて、どうするかは自分で決めれる。
inputs->getNumInputs() > 1
とかで分岐すると、inputが1つだけのときどうするかとかも行える。
myExampleCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, void* reserved1)
{
// If there is an input connected, we are going to match it's channel names etc
// otherwise we'll specify our own.
if (inputs->getNumInputs() > 0)
{
return false;
}
else
{
info->numChannels = 1;
//timesliceのCHOPを作りたい場合は、numSample =1, startIndex =0にすればよいらしい。
// Since we are outputting a timeslice, the system will dictate
// the numSamples and startIndex of the CHOP data
//info->numSamples = 1;
//info->startIndex = 0
// For illustration we are going to output 120hz data
//何も繋がれていない時は、sampleRateが120Hzになる。
info->sampleRate = 120;
return true;
}
}
OP_Inputs*->
上記の例でinput->
の形で書かれている場所
OP_InputsではこのCPlusPlus CHOPにinputされている情報諸々にアクセスできる。inputされているnodeについてや、parameterについてなど。
下記で紹介するgetNumInputs()
やgetInputCHOP()
など
parameter周りでいくと、
getPar****()
で始まる。
virtual int32_t getParInt(const char* name, int32_t index = 0) const = 0;
などでは
nameに"myParameterName"
などparmeterの名前を入れて、vectorとかの場合は、index番号を入れてあげて値を受け取る。
他にも
virtual bool getParInt2(const char* name, int32_t &v0, int32_t &v1) const = 0;
なども定義されていて、引数にアドレス(int32_t &v1)を渡せばそこにvectorの値一個一個を入れて返してくれる。
OPをparmaterとして選択させているparameter
例えば、virtual OP_ParAppendResult appendDAT(const OP_StringParameter &sp) = 0;
などでDATを入れれるようにしているところの値を受け取るには、
virtual const OP_DATInput* getParDAT(const char *name) const = 0;
で受け取れる。
// these are defined by parameters.
// may return nullptr when invalid input
// this value is valid until the parameters are rebuilt or it is called with the same parameter name.
virtual const OP_DATInput* getParDAT(const char *name) const = 0;
private:
// Deprecated, only declared here so legacy code can work.
virtual const OP_TOPInputOpenGL* getParTOPOpenGL(const char *name) const = 0;
public:
virtual const OP_CHOPInput* getParCHOP(const char *name) const = 0;
virtual const OP_ObjectInput* getParObject(const char *name) const = 0;
// getParSOP() declared later on in the class
// these work on any type of parameter and can be interchanged
// for menu types, int returns the menu selection index, string returns the item
// returns the requested value, index may be 0 to 4.
virtual double getParDouble(const char* name, int32_t index = 0) const = 0;
// for multiple values: returns True on success/false otherwise
virtual bool getParDouble2(const char* name, double &v0, double &v1) const = 0;
virtual bool getParDouble3(const char* name, double &v0, double &v1, double &v2) const = 0;
virtual bool getParDouble4(const char* name, double &v0, double &v1, double &v2, double &v3) const = 0;
// returns the requested value
virtual int32_t getParInt(const char* name, int32_t index = 0) const = 0;
// for multiple values: returns True on success/false otherwise
virtual bool getParInt2(const char* name, int32_t &v0, int32_t &v1) const = 0;
virtual bool getParInt3(const char* name, int32_t &v0, int32_t &v1, int32_t &v2) const = 0;
virtual bool getParInt4(const char* name, int32_t &v0, int32_t &v1, int32_t &v2, int32_t &v3) const = 0;
// returns the requested value
// this value is valid until the parameters are rebuilt or it is called with the same parameter name.
// return value usable for life of parameter
// The returned string will be in UTF-8 encoding.
virtual const char* getParString(const char* name) const = 0;
// this is similar to getParString, but will return an absolute path if it exists, with
// slash direction consistent with O/S requirements.
// to get the original parameter value, use getParString
// return value usable for life of parameter
// The returned string will be in UTF-8 encoding.
virtual const char* getParFilePath(const char* name) const = 0;
CHOP_OutputInfo*->
info->
でoutputする(このCHOPをどうするか)情報を決めれる
numChannnels
, numSamples
, startIndex
, sampleRate
が決めれる。
startIndex
if you arn't outputting a timeslice, specify the start index of the channels here. This is the 'Start' you see when you middle click on a CHOP
[引用] CHOP_CPlusPlusBase.h
タイムスライスを出力しない場合は、ここでチャンネルの開始インデックスを指定します。これは、CHOPをミドルクリックしたときに表示される'Start'である。CHOPをミドルクリックしたときに表示される「スタート」です。
class CHOP_OutputInfo
{
public:
// The number of channels you want to output
int32_t numChannels;
// If you arn't outputting a timeslice, specify the number of samples here
int32_t numSamples;
// if you arn't outputting a timeslice, specify the start index
// of the channels here. This is the 'Start' you see when you
// middle click on a CHOP
uint32_t startIndex;
// Specify the sample rate of the channel data
// DEFAULT : whatever the timeline FPS is ($FPS)
float sampleRate;
void* reserved1;
int32_t reserved[20];
};
OP_Inputs->getNumInputs
OP_Inputs inputs
はCPlusPlus_Common.h
にclassがあって、
getNumInputs()
では、inputされているノード個数を表す。bypassで値が入ってなくたっとしても、接続されているため1カウントとして扱われる。
if(inputs->getNumInputs() == 1){
std::cout << "1 input" << std::endl;
}
if(inputs->getNumInputs() == 2){
std::cout << "2 input" << std::endl;
}
OP_Inputs->getInputCHOP(int32_t index)
virtual const OP_CHOPInput* getInputCHOP(int32_t index) const = 0;
は
入力されているnodeに対してのindexを指定すると(2番目に繋がれているinput nodeなら1)、OP_CHOPInput
のclassでCHOPそのもののデータが返ってくる。
以下のようなCHOP情報にアクセスできる。
class OP_CHOPInput
{
public:
const char* opPath;
uint32_t opId;
int32_t numChannels;
int32_t numSamples;
double sampleRate;
double startIndex;
// Retrieve a float array for a specific channel.
// 'i' ranges from 0 to numChannels-1
// The returned arrray contains 'numSamples' samples.
// e.g: getChannelData(1)[10] will refer to the 11th sample in the 2nd channel
const float*
getChannelData(int32_t i) const
{
return channelData[i];
}
// Retrieve the name of a specific channel.
// 'i' ranges from 0 to numChannels-1
// For example getChannelName(1) is the name of the 2nd channel
const char*
getChannelName(int32_t i) const
{
return nameData[i];
}
const float** channelData;
const char** nameData;
// The number of times this node has cooked
int64_t totalCooks;
int32_t reserved[18];
};
getOutputInfo()の他の例。
以下の例では、
inputs->getNumInputs() > 0
でinputされているノードがあった時、
info->getParInt("Combine")
でCombineという名前のparameterの値が0以外(true)なら、
info->numChannnels = 1
にしてoutputされるchannnelを一つにする。
Combine = 0
Combine = 1
myExampleCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, void* reserved1)
{
// If there is an input connected, we are going to match it's channel names etc
// otherwise we'll specify our own.
if (inputs->getNumInputs() > 0)
{
bool isCombined = inputs->getParInt("Combine");
if(isCombined)
{
info->numChannels = 1;
return true;
}
else{
return false;
}
}
else
{
info->numChannels = 1;
// Since we are outputting a timeslice, the system will dictate
// the numSamples and startIndex of the CHOP data
//info->numSamples = 1;
//info->startIndex = 0
// For illustration we are going to output 120hz data
info->sampleRate = 120;
return true;
}
}
Time sliced Dataを作りたいとき
getGeneralInfo()
より、ginfo->timeslice = true
にしておく。
こうすることで、基本的にtimeslice状態になる。
常に、timeslice = false
にしておく場合は、getOutputInfo()
でinfo->sampleRate
をしっかりどの状態でも決めておくこと。
でないと、inputのノードでtimeslice状態のものが来たときにgetOoutputInfo()がreutrn true
とかになっていても入力のinputに引っ張られてtimesliceになったりsampleが引っ張られたり、するかかもしれない。
// Note: To disable timeslicing you'll need to turn this off, as well as ensure that
// getOutputInfo() returns true, and likely also set the info->numSamples to how many
// samples you want to generate for this CHOP. Otherwise it'll take on length of the
// input CHOP, which may be timesliced.
[引用] exampleCHOP.cpp
TIME SLICED DATA
If you are unfamiliar with what a time slice is, see the Time Slicing article.If the plugin is outputting time sliced data then TouchDesigner will tell you how many samples it wants you to output. The number of samples depends on how long it's been since TouchDesigner last cooked (due to skipped frames), and the sample rate of the Time Slice vs the sample rate of the CHOP. For example let's say the CHOP is outputting 120hz sample rate data and the Timeline is running at 60hz. If TouchDesigner skipped cooking the last frame the Time Slice size will be 2 frames long. Since the CHOPs sample rate is 120hz vs. the 60hz of the timeline, you will be asked to provide 4 samples of data per channel to fill the Time Slice.
[引用] https://derivative.ca/UserGuide/Write_a_CPlusPlus_CHOP
execute(){}
ここで実際のCHOPの値の挙動を決めていく。
CHOP_OUTPUT* output
は先ほど出てきた、CHOP_OutputInfo class
とは少し別物で、
具体的なCHOPの値のoutputに関する部分。
CHOP_OutputInfo class
はOPを中ボタンクリックした時のようなInfoにまつわる情報。
おそらく、CHOP_Output()
はCHOP_OutputInfo
の変数(field)をそのまま移行する形でコンストラクタで代入している。
class CHOP_Output
{
public:
CHOP_Output(int32_t nc, int32_t l, float s, uint32_t st,
float **cs, const char** ns):
numChannels(nc),
numSamples(l),
sampleRate(s),
startIndex(st),
channels(cs),
names(ns)
{
}
....
const OP_Inputs* inputs
は先ほどの時と一緒で、input nodeやinput parameter周りが扱える。
// For example channels[1][10] will point to the 11th sample in the 2nd
output->channels[i][j]
にfloatのデータを入れると値を入れれる。
この場合のiはchannel index, jはsamples index
// e.g: getChannelData(1)[10] will refer to the 11th sample in the 2nd channel
inp
下記の例でいう、cinput->getChannelData(i)[ind]
でinput nodeのsampleDataをゲットすることができる。
void
myExampleCHOP::execute(CHOP_Output* output, const OP_Inputs* inputs, void* reserved)
{
myExecuteCount++;
//parameterのScaleを受け取っている。
double scale = inputs->getParDouble("Scale");
// In this case we'll just take the first input and re-output it scaled.
//nodeのInputがあるとき
if (inputs->getNumInputs() > 0)
{
// We know the first CHOP has the same number of channels
// because we returned false from getOutputInfo.
//parameterを有効・無効にできる。1=enable, 0= disable
// disable or enable updating of the parameter
//virtual void enablePar(const char* name, bool onoff) const = 0;
inputs->enablePar("Speed", 0); // not used
inputs->enablePar("Reset", 0); // not used
inputs->enablePar("Shape", 0); // not used
bool isCombined = inputs->getParInt("Combine");
if(isCombined){
int ind = 0;
float total = 0.0;
//index0番目のinput nodeをgetInputCHOPする
//OP_CHOP Input *でCPlusPlus_Common.hにclass定義されている。
const OP_CHOPInput *cinput = inputs->getInputCHOP(0);
//output->numChannelsが使えるのはconstractorで勝手にnumChannelsが入っているから。
for (int i = 0 ; i < output->numChannels; i++)
{
for (int j = 0; j < output->numSamples; j++)
{
//inputのchannel数分のその
int numChans = cinput->numChannels;
for(int k = 0; k < numChans; k++){
total += float(cinput->getChannelData(i)[ind] * scale);
}
output->channels[0][j] = total;
//output->channels[i][j] = float(cinput->getChannelData(i)[ind] * scale);
ind++;
// Make sure we don't read past the end of the CHOP input
//indが持っているsample数を超えてしまったときに0にcountをもどす。
ind = ind % cinput->numSamples;
}
}
}//isCombined
else{
int ind = 0;
const OP_CHOPInput *cinput = inputs->getInputCHOP(0);
for (int i = 0 ; i < output->numChannels; i++)
{
for (int j = 0; j < output->numSamples; j++)
{
output->channels[i][j] = float(cinput->getChannelData(i)[ind] * scale);
ind++;
// Make sure we don't read past the end of the CHOP input
ind = ind % cinput->numSamples;
}
}
}
}
//input nodeがないとき
else // If not input is connected, lets output a sine wave instead
{
inputs->enablePar("Speed", 1);
inputs->enablePar("Reset", 1);
double speed = inputs->getParDouble("Speed");
double step = speed * 0.01f;
// menu items can be evaluated as either an integer menu position, or a string
//下でswitch文で分岐させている。
int shape = inputs->getParInt("Shape");
// const char *shape_str = inputs->getParString("Shape");
// keep each channel at a different phase
//outputのchannnel数に合わせて、位相を少し移動させている。
double phase = 2.0f * 3.14159f / (float)(output->numChannels);
// Notice that startIndex and the output->numSamples is used to output a smooth
// wave by ensuring that we are outputting a value for each sample
// Since we are outputting at 120, for each frame that has passed we'll be
// outputing 2 samples (assuming the timeline is running at 60hz).
for (int i = 0; i < output->numChannels; i++)
{
double offset = myOffset + phase*i;
double v = 0.0f;
switch(shape)
{
case 0: // sine
v = sin(offset);
break;
case 1: // square
v = fabs(fmod(offset, 1.0)) > 0.5;
break;
case 2: // ramp
v = fabs(fmod(offset, 1.0));
break;
}
v *= scale;
for (int j = 0; j < output->numSamples; j++)
{
output->channels[i][j] = float(v);
offset += step;
}
}
myOffset += step * output->numSamples;
}
}
getChannelName(int32_t index, OP_String name, const OP_Inputs inputs, void* reserved1)
getChannelName
はgetOutputInfo()
のときにreturn true
だったら、呼ばれる関数。
/*if getOutputInfo() returns true
{
getChannelName() once for each channel needed
}*/
以下の例ではgetOutputInfo
やexecute
の内容も変更しないとうまくいかないが、以下のようにすると、outputで定義されたchanNumの数だけgetChannelName
が呼ばれて、indexがインクリメントしていく、
それに対して、name->setString(char*)
で入れてあげることによって、それぞれにchannel Nameがつく。
void
WorkshopCHOP::getChannelName(int32_t index, OP_String *name, const OP_Inputs* inputs, void* reserved1)
{
/*<string>をincludeしている。<string>はC++, <string.h>はCのlibraryのため}*/
//std::stringでc++をよべて、setString(cの文字列==char*)になっているため、c_str()でc++tocにしないといけない。
std::string chanName = "chan" + std::to_string(index);
name->setString(chanName.c_str());
// name->setString("chan1");
}
class OP_String{
public:
// val is expected to be UTF-8 encoded
virtual void setString(const char* val) = 0;
}
info CHOPへ出力
info CHOPにこのc++CHOPを持って行った際に何を出力するかをプログラムできる。
getNumInfoChopChans
でまず、いくつのchannelをinfoCHOPに渡すかを決める。
exampleの初期状態だと、2になっていて、executeCount
, offset
という二つの値をカスタムで出力することになっている。他のどのCHOPにも共通しているStart
, cook
...etcは勝手にoutputされる。
getNumInfoChopChans(void * reserved1)
int32_t
myExampleCHOP::getNumInfoCHOPChans(void * reserved1)
{
// We return the number of channel we want to output to any Info CHOP
// connected to the CHOP. In this example we are just going to send one channel.
//2つのカスタムしたchannelをinfoCHOPに送れる。
return 2;
}
getInfoCHOPChan(int32_t index, OP_InfoCHOPChan* chan, void* reserved1)
上記で2と宣言した場合は
2回この関数が呼ばれて、indexがインクリメントする。
chan->name->setString(char*)
でchannelの名前を決めて
chan->value =
で(float)の値を代入してあげる。
void
myExampleCHOP::getInfoCHOPChan(int32_t index, OP_InfoCHOPChan* chan,
void* reserved1)
{
// This function will be called once for each channel we said we'd want to return
// In this example it'll only be called once.
if (index == 0)
{
chan->name->setString("executeCount");
chan->value = (float)myExecuteCount;
}
if (index == 1)
{
chan->name->setString("offset");
chan->value = (float)myOffset;
}
}
info DATへ出力
info chop同様にinfo DATへの定義もここで決めれる。
getInfoDATSize
でDATの行列のサイズを決めれる。
getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved1)
infoSize->byColumn = false
のようにfalseにすると、次の関数で行優先で一度にまず行から定義していく方式になって、
infoSize->byColumn = true
にすると、列優先で、列からになる。
bool
myExampleCHOP::getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved1)
{
infoSize->rows = 2;
infoSize->cols = 2;
// Setting this to false means we'll be assigning values to the table
// one row at a time. True means we'll do it one column at a time.
infoSize->byColumn = false;
return true;
}
getInfoDATEntries(int32_t index,int32_t nEntries,OP_InfoDATEntries* entries, void* reserved1)
以下の例では行優先になっているため、一度のindexに1行とりあえず入力していく。
entries->values[0]->setString("executeCount");
のように
void
myExampleCHOP::getInfoDATEntries(int32_t index,int32_t nEntries,OP_InfoDATEntries* entries, void* reserved1)
{
char tempBuffer[4096];
if (index == 0)
{
// Set the value for the first column
entries->values[0]->setString("executeCount");
// Set the value for the second column
#ifdef _WIN32
sprintf_s(tempBuffer, "%d", myExecuteCount);
#else // macOS
//この例ではexcute()が呼び出されるときにclassの変数になっているmExecuteCountがincrementされるようになっている。
snprintf(tempBuffer, sizeof(tempBuffer), "%d", myExecuteCount);
#endif
entries->values[1]->setString(tempBuffer);
}
if (index == 1)
{
// Set the value for the first column
entries->values[0]->setString("offset");
// Set the value for the second column
#ifdef _WIN32
sprintf_s(tempBuffer, "%g", myOffset);
#else // macOS
snprintf(tempBuffer, sizeof(tempBuffer), "%g", myOffset);
#endif
entries->values[1]->setString( tempBuffer);
}
}
DAT CPlusPlus 流れ
コンストラクタ・デストラクタ
自由に自分で用意した変数など初期化など好きに使ってくれておk
CPlusPlusDATExample::CPlusPlusDATExample(const OP_NodeInfo* info) : myNodeInfo(info)
// : myNodeInfo(info)は初期化子リストというクラスの変数を初期化できる方法。
// https://hakase0274.hatenablog.com/entry/2019/09/28/200000
{
myExecuteCount = 0;
myOffset = 0.0;
myChop = "";
myChopChanName = "";
myChopChanVal = 0;
}
CPlusPlusDATExample::~CPlusPlusDATExample()
{
}
getGeneralInfo()
cook周りの設定ができる。
void
CPlusPlusDATExample::getGeneralInfo(DAT_GeneralInfo* ginfo, const OP_Inputs* inputs, void* reserved1)
{
// This will cause the node to cook every frame
ginfo->cookEveryFrameIfAsked = false;
// ginfo->cookEveryFrameIfAsked = true;
}
bool cookEveryFrame
// Set this to true if you want the DAT to cook every frame, even
// if none of it's inputs/parameters are changing.
// This is generally useful for cases where the node is outputting to
// something external to TouchDesigner, such as a network socket or device.
// It ensures the node cooks every if nothing inside the network is using/viewing
// the output of this node.
// Important:
// If the node may not be viewed/used by other nodes in the file,
// such as a TCP network output node that isn't viewed in perform mode,
// you should set cookOnStart = true in OP_CustomOPInfo.
// That will ensure cooking is kick-started for this node.
// Note that this fix only works for Custom Operators, not
// cases where the .dll is loaded into CPlusPlus DAT.
[引用] DAT_CPlusPlusBase.h
cookEveryFrame = true
にすると、DATの入力やパラメーターに変更がなくても、毎フレームCookを行う。
これは一般的に、ノードがネットワークソケットやデバイスなど、TouchDesignerの外部に出力している場合に便利。ネットワークソケットやデバイスなど、TouchDesignerの外部に出力する場合に便利。
このCPP DATの中で何もしていないくてもcookしてくれるようになる。
TCPの出力などを perform modeで使用していると、最適化が行われることでなんだかcookが機能していないなという場面があるらしい。
このために、別途OP_CustomOPInfoでcookOnStart = true
に設定する必要がある。
cookOnStartについて
OP_CustomOPInfo cookOnStart = false;
// False by default. If this is on the node will cook at least once
// when the project it is contained within starts up, or when the node
// is created.
// For pure output nodes that are using 'cookEveryFrame=true' in their
// GeneralInfo, setting this to 'true' is required to kick-start the
// every-frame cooking.
[引用] CPlusPlus_Common.h
デフォルトではfalse
になっていて、true
の場合プロジェクトの起動時、ノードの作成時に少なくとも一回はクックされる。おそらくこの仕組みのおかげで、上の問題の初めの最初のkick startをしてくれるからうまくいくってことなのかも。
cookOnStart
はFillDATPluginInfo(DAT_PluginInfo *info)
から変更しやすいためここでするのが良い。
info->customOPInfo.cookOnStart = true
にすればよい。
void
FillDATPluginInfo(DAT_PluginInfo *info)
{
// Always return DAT_CPLUSPLUS_API_VERSION in this function.
info->apiVersion = DATCPlusPlusAPIVersion;
// The opType is the unique name for this TOP. It must start with a
// capital A-Z character, and all the following characters must lower case
// or numbers (a-z, 0-9)
info->customOPInfo.opType->setString("Customdat");
// The opLabel is the text that will show up in the OP Create Dialog
info->customOPInfo.opLabel->setString("Custom DAT");
// Will be turned into a 3 letter icon on the nodes
info->customOPInfo.opIcon->setString("CDT");
// Information about the author of this OP
info->customOPInfo.authorName->setString("Author Name");
info->customOPInfo.authorEmail->setString("email@email.com");
// This DAT works with 0 or 1 inputs
info->customOPInfo.minInputs = 0;
info->customOPInfo.maxInputs = 1;
}
bool cookEveryFrameIfAsked
// Set this to true if you want the DAT to cook every frame, but only
// if someone asks for it to cook. So if nobody is using the output from
// the DAT, it won't cook. This is difereent from 'cookEveryFrame'
// since that will cause it to cook every frame no matter what.
// DEFAULT: false
bool cookEveryFrameIfAsked;
[引用] DAT_CPlusPlusBase.h
cookEveryFrameIfAsked = false
にすると、このCpp DATのoutputにcookを求められているような状況(計算しないといけない状況)でないとcookしなくなる。trueにすると、全てのフレームでcookする。
そういう意味でcookEveryFrame
とは違う。
execute(DAT_Output* output,const OP_Inputs* inputs, void* reserved)
chop同様にexecute
で描いたものが基本的に処理内容になる。
CHOPと大きく違う部分はOP_CHOPInputではなくて、OP_DATInputから、実際にinputに繋いだnodeの中のDATのセルの要素などがいじれる。
CHOPなどでも共通のinputしているノード一般に対するclassの、OP_Inputs* inputs
からconst OP_DATInput *cinput = inputs->getInputDAT(0);
などとすると、OP_DATInput
classで受け取ってそれに対してアクセスできる。
OP_DATINputの定義内容
class OP_DATInput
{
public:
const char* opPath;
uint32_t opId;
int32_t numRows;
int32_t numCols;
bool isTable;
// data, referenced by (row,col), which will be a const char* for the
// contents of the cell
// E.g getCell(1,2) will be the contents of the cell located at (1,2)
// The string will be in UTF-8 encoding.
const char*
getCell(int32_t row, int32_t col) const
{
return cellData[row * numCols + col];
}
const char** cellData;
// The number of times this node has cooked
int64_t totalCooks;
int32_t reserved[18];
};
chop同様、DAT_Output
に対して、処理してあげることで、このcpp datとしてのoutputの内容が決まる。
void
CPlusPlusDATExample::execute(DAT_Output* output,
const OP_Inputs* inputs,
void* reserved)
{
myExecuteCount++;
if (!output)
return;
//OP_Inputs*よりinputしているノードの個数をチェック
if (inputs->getNumInputs() > 0)
{
// `OP_Inputs void enablePar(const char* name, bool onoff) const = 0;`は他のOP同様に、そのCpp OPのパラメーターを受け取れるようにするかどうかのon/offする関数。
inputs->enablePar("Rows", 0); // not used
inputs->enablePar("Cols", 0); // not used
inputs->enablePar("Outputtype", 0); // not used
// OP_DATInputはOP_CHOPInputとかと同じで、inputに繋いでいるノードなどのDATの要素(セルの中身)とかにアクセスするclass
const OP_DATInput *cinput = inputs->getInputDAT(0);
// OP_DATInputで具体的にな値にアクセス
int numRows = cinput->numRows;
int numCols = cinput->numCols;
bool isTable = cinput->isTable;
if (!isTable) // is Text
{
// textの場合は、cinput->getCell(0,0)で取れるみたい。
const char* str = cinput->getCell(0, 0);
output->setText(str);
}
else
{
// output->に関しては、DAT_CPlusPlusBase.hにvirtual定義がある
output->setOutputDataType(DAT_OutDataType::Table);
output->setTableSize(numRows, numCols);
for (int i = 0; i < cinput->numRows; i++)
{
for (int j = 0; j < cinput->numCols; j++)
{
const char* str = cinput->getCell(i, j);
output->setCellString(i, j, str);
}
}
}
}
else // If no input is connected, lets output a custom table/text DAT
{
inputs->enablePar("Rows", 1);
inputs->enablePar("Cols", 1);
inputs->enablePar("Outputtype", 1);
//getPar**()でparameterの取得
int outputDataType = inputs->getParInt("Outputtype");
int numRows = inputs->getParInt("Rows");
int numCols = inputs->getParInt("Cols");
switch (outputDataType)
{
case 0: // Table
//下記で定義
makeTable(output, numRows, numCols);
break;
case 1: // Text
makeText(output);
break;
default: // table
makeTable(output, numRows, numCols);
break;
}
// if there is an input chop parameter:
const OP_CHOPInput *cinput = inputs->getParCHOP("Chop");
if (cinput)
{
int numSamples = cinput->numSamples;
int ind = 0;
for (int i = 0; i < cinput->numChannels; i++)
{
myChopChanName = std::string(cinput->getChannelName(i));
myChop = inputs->getParString("Chop");
static char tempBuffer[50];
myChopChanVal = float(cinput->getChannelData(i)[ind]);
#ifdef _WIN32
sprintf_s(tempBuffer, "%g", myChopChanVal);
#else // macOS
snprintf(tempBuffer, sizeof(tempBuffer), "%g", myChopChanVal);
#endif
if (numCols == 0)
numCols = 2;
output->setTableSize(numRows + i + 1, numCols);
output->setCellString(numRows + i, 0, myChopChanName.c_str());
output->setCellString(numRows + i, 1, &tempBuffer[0]);
}
}
}
}
void
CPlusPlusDATExample::makeTable(DAT_Output* output, int numRows, int numCols)
{
output->setOutputDataType(DAT_OutDataType::Table);
output->setTableSize(numRows, numCols);
std::array<const char*, 5> data = { "this", "is", "some", "test", "data"};
for (int i = 0; i < numRows; i++)
{
for (int j = 0; j < numCols; j++)
{
int j2 = j;
// If we are asked to make more columns than we have data for
if (j2 >= data.size())
j2 = j2 % data.size();
output->setCellString(i, j, data[j2]);
}
}
}
void
CPlusPlusDATExample::makeText(DAT_Output* output)
{
output->setOutputDataType(DAT_OutDataType::Text);
output->setText("This is some test data.");
}
info CHOP周り
CHOP同様に、info CHOPへ出力できる。
getNumInfoCHOPChans()
とgetInfoCHOPChan()
が同様にあって、使い方は同じ。
int32_t
CPlusPlusDATExample::getNumInfoCHOPChans(void* reserved1)
{
// We return the number of channel we want to output to any Info CHOP
// connected to the CHOP. In this example we are just going to send one channel.
return 4;
}
void
CPlusPlusDATExample::getInfoCHOPChan(int32_t index,
OP_InfoCHOPChan* chan, void* reserved1)
{
// This function will be called once for each channel we said we'd want to return
// In this example it'll only be called once.
if (index == 0)
{
chan->name->setString("executeCount");
chan->value = (float)myExecuteCount;
}
if (index == 1)
{
chan->name->setString("offset");
chan->value = (float)myOffset;
}
if (index == 2)
{
chan->name->setString(myChop.c_str());
chan->value = (float)myOffset;
}
if (index == 3)
{
chan->name->setString(myChopChanName.c_str());
chan->value = myChopChanVal;
}
}
info DAT周り
CHOP同様
bool
CPlusPlusDATExample::getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved1)
{
infoSize->rows = 3;
infoSize->cols = 3;
// Setting this to false means we'll be assigning values to the table
// one row at a time. True means we'll do it one column at a time.
infoSize->byColumn = false;
return true;
}
void
CPlusPlusDATExample::getInfoDATEntries(int32_t index,
int32_t nEntries,
OP_InfoDATEntries* entries,
void* reserved1)
{
char tempBuffer[4096];
if (index == 0)
{
// Set the value for the first column
#ifdef _WIN32
strcpy_s(tempBuffer, "executeCount");
#else // macOS
strlcpy(tempBuffer, "executeCount", sizeof(tempBuffer));
#endif
entries->values[0]->setString(tempBuffer);
// Set the value for the second column
#ifdef _WIN32
sprintf_s(tempBuffer, "%d", myExecuteCount);
#else // macOS
snprintf(tempBuffer, sizeof(tempBuffer), "%d", myExecuteCount);
#endif
entries->values[1]->setString(tempBuffer);
}
if (index == 1)
{
// Set the value for the first column
#ifdef _WIN32
strcpy_s(tempBuffer, "offset");
#else // macOS
strlcpy(tempBuffer, "offset", sizeof(tempBuffer));
#endif
entries->values[0]->setString(tempBuffer);
// Set the value for the second column
#ifdef _WIN32
sprintf_s(tempBuffer, "%g", myOffset);
#else // macOS
snprintf(tempBuffer, sizeof(tempBuffer), "%g", myOffset);
#endif
entries->values[1]->setString(tempBuffer);
}
if (index == 2)
{
// Set the value for the first column
#ifdef _WIN32
strcpy_s(tempBuffer, "DAT input name");
#else // macOS
strlcpy(tempBuffer, "offset", sizeof(tempBuffer));
#endif
entries->values[0]->setString(tempBuffer);
// Set the value for the second column
#ifdef _WIN32
strcpy_s(tempBuffer, myDat.c_str());
#else // macOS
snprintf(tempBuffer, sizeof(tempBuffer), "%g", myOffset);
#endif
entries->values[1]->setString(tempBuffer);
}
}
Parameter周り
これも基本的に同じ。
CHOPをparameter内に追加したい場合はこうする。
void
CPlusPlusDATExample::setupParameters(OP_ParameterManager* manager, void* reserved1)
{
// CHOP
{
OP_StringParameter np;
np.name = "Chop";
np.label = "CHOP";
OP_ParAppendResult res = manager->appendCHOP(np);
assert(res == OP_ParAppendResult::Success);
}
// Number of Rows
{
OP_NumericParameter np;
np.name = "Rows";
np.label = "Rows";
np.defaultValues[0] = 4;
np.minSliders[0] = 0;
np.maxSliders[0] = 10;
OP_ParAppendResult res = manager->appendInt(np);
assert(res == OP_ParAppendResult::Success);
}
// Number of Columns
{
OP_NumericParameter np;
np.name = "Cols";
np.label = "Cols";
np.defaultValues[0] = 5;
np.minSliders[0] = 0;
np.maxSliders[0] = 10;
OP_ParAppendResult res = manager->appendInt(np);
assert(res == OP_ParAppendResult::Success);
}
// DAT output type
{
OP_StringParameter sp;
sp.name = "Outputtype";
sp.label = "Output Type";
sp.defaultValue = "Table";
const char *names[] = {"Table", "Text"};
const char *labels[] = {"Table", "Text"};
OP_ParAppendResult res = manager->appendMenu(sp, 2, names, labels);
assert(res == OP_ParAppendResult::Success);
}
// pulse
{
OP_NumericParameter np;
np.name = "Reset";
np.label = "Reset";
OP_ParAppendResult res = manager->appendPulse(np);
assert(res == OP_ParAppendResult::Success);
}
}
pulseのparameterを押したときに、const char* name
でその名前が引数に入ってきたpulsePressed()
の関数が発火する。
void
CPlusPlusDATExample::pulsePressed(const char* name, void* reserved1)
{
if (!strcmp(name, "Reset"))
{
myOffset = 0.0;
}
}
Reference
Intro to Custom CHOP C++ - Hugo Laliberte
lachose1/wiimoteCHOP-cleanbase: WiimoteCHOP base code for the #tdsummit2019 workshop : Introduction to Custom CHOP C++Custom Operators - Derivative
TouchDesigner/CustomOperatorSamples: A collection of custom operators to start exploring development in c++ for TouchDesigner.
Discussion