🦷

TouchDesigner C++を使う

2022/11/05に公開

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++でオリジナルのオペレーターを作る
https://note.com/toyoshimorioka/n/nce76daf98907
より、satoruhigaさんのTD用のC++を作るためのテンプレートのGitHubがあるのでそれを参考に動かそうと試みる。

以下がsatoruhigaさんの諸々のリンク
https://github.com/satoruhiga/TouchDesigner-Plugin-Template

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.txtcmake_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", &params->major, &params->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ではなんかそこからライブラリ周りでなんかしようとしている。

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_sglewinfo.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公式のページからスタートする。

https://docs.derivative.ca/Write_a_CPlusPlus_Plugin
https://derivative.ca/UserGuide/Write_a_CPlusPlus_Plugin

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について
https://zenn.dev/yukichi_tech/scraps/495f9828b44c80
.app的なことね。

bundleの構造
https://l-w-i.net/t/macosx/bundle_001.txt
.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について

https://qiita.com/kazy_dev/items/feb68f162ec3d91005d3
https://softmoco.com/devenv/how-to-create-and-setup-build-scheme.php
http://transxcode.com/API-FolOPQR/REF_S/SPI_XYZ/XcoHelp_Fo/XCoHelp176.html

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
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)で、パラメーターウィンドウの設定ができる。

myExampleCHOP.cpp
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を見にいけば、どんなものが他にあるか見に行ける。

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.cpp

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;で受け取れる。

CPlusPlus_Common.h
	// 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をミドルクリックしたときに表示される「スタート」です。

CHOP_CPlusPlusBase.h
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 inputsCPlusPlus_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情報にアクセスできる。

CPlusPlus_Common.h
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.cpp

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)をそのまま移行する形でコンストラクタで代入している。

CHOP_CPlusPlusBase.h
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をゲットすることができる。

myExampleCHOP.cpp
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)

getChannelNamegetOutputInfo()のときにreturn trueだったら、呼ばれる関数。

	/*if getOutputInfo() returns true
	{
		getChannelName() once for each channel needed 
	}*/

以下の例ではgetOutputInfoexecuteの内容も変更しないとうまくいかないが、以下のようにすると、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");
}
CPlusPlus_Common.h
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)

myExampleCHOP
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)の値を代入してあげる。

myExampleCHOP
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にすると、列優先で、列からになる。

myExampleCHOP
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");のように

myExampleCHOP
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.cpp
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周りの設定ができる。

CPlusPlusDATExample.cpp
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をしてくれるからうまくいくってことなのかも。

cookOnStartFillDATPluginInfo(DAT_PluginInfo *info)から変更しやすいためここでするのが良い。
info->customOPInfo.cookOnStart = trueにすればよい。

CPlusPlusDATExample.cpp
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_DATInputclassで受け取ってそれに対してアクセスできる。

OP_DATINputの定義内容

CPlusPlus_Common.h
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の内容が決まる。

CPlusPlusDATExample.cpp
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()が同様にあって、使い方は同じ。

CPlusPlusDATExample.cpp
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同様

CPlusPlusDATExample.cpp

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内に追加したい場合はこうする。

CPlusPlusDATExample.cpp
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()の関数が発火する。

CPlusPlusDATExample.cpp
void
CPlusPlusDATExample::pulsePressed(const char* name, void* reserved1)
{
	if (!strcmp(name, "Reset"))
	{
		myOffset = 0.0;
	}
}

Reference

Intro to Custom CHOP C++ - Hugo Laliberte
https://www.youtube.com/watch?v=9wBwRDMS2I4
lachose1/wiimoteCHOP-cleanbase: WiimoteCHOP base code for the #tdsummit2019 workshop : Introduction to Custom CHOP C++
https://github.com/lachose1/wiimoteCHOP-cleanbase

Custom Operators - Derivative
https://docs.derivative.ca/Custom_Operators
TouchDesigner/CustomOperatorSamples: A collection of custom operators to start exploring development in c++ for TouchDesigner.
https://github.com/TouchDesigner/CustomOperatorSamples

Discussion