Syntaxhighlight
Jump to navigation
Jump to search
Python[edit]
def setupListeners(self):
# make sure we have a valid ableton parameter
aParInfo = self.LOMParInfo('Parameter')
if not aParInfo:
self.disconnect()
return
TDAbletonCompBaseExt.setupListeners(self)
# set up listener parameters
# lomExpression: a Python expression to the live object in tda
lomExpression = aParInfo['lomExpression']
# returnAddress: osc address that updates will be sent to from tda.
# note that this must be a valid CHOP channel name
returnAddress = aParInfo['returnAddress'] + '/value'
# id: unique id of the object requesting a listener
ownerId = self.ownerComp.id
# property: the name of the live object's property to listen to
property = 'value'
# this tuple is what a listener key looks like
listener = (lomExpression, property, returnAddress, ownerId)
# addListener(listener, outgoing parameter, parMin, parMax)
self.addListener(listener, self.ownerComp.par.Valuesend,
aParInfo['lomInfo']['min'], aParInfo['lomInfo']['max'])
GLSL[edit]
uniform vec4 uDiffuseColor;
uniform vec4 uAmbientColor;
uniform vec3 uSpecularColor;
uniform float uShininess;
uniform float uShadowStrength;
uniform vec3 uShadowColor;
out Vertex {
vec4 color;
vec3 worldSpacePos;
vec3 worldSpaceNorm;
flat int cameraIndex;
}vVert;
void main()
{
// First deform the vertex and normal
// TDDeform always returns values in world space
vec4 worldSpacePos =TDDeform(P);
gl_Position = TDWorldToProj(worldSpacePos);
// This is here to ensure we only execute lighting etc. code
// when we need it. If picking is active we don't need this, so
// this entire block of code will be ommited from the compile.
// The TD_PICKING_ACTIVE define will be set automatically when
// picking is active.
#ifndef TD_PICKING_ACTIVE
int cameraIndex = TDCameraIndex();
vVert.cameraIndex = cameraIndex;
vVert.worldSpacePos.xyz = worldSpacePos.xyz;
vVert.color = TDInstanceColor(Cd);
vec3 worldSpaceNorm = TDDeformNorm(N);
vVert.worldSpaceNorm = normalize(worldSpaceNorm);
#else // TD_PICKING_ACTIVE
// This will automatically write out the nessessary values
// for this shader to work with picking.
// See the documentation if you want to write custom values for picking.
TDWritePickingValues();
#endif // TD_PICKING_ACTIVE
}
C++[edit]
/* Shared Use License: This file is owned by Derivative Inc. (Derivative) and
* can only be used, and/or modified for use, in conjunction with
* Derivative's TouchDesigner software, and only if you are a licensee who has
* accepted Derivative's TouchDesigner license or assignment agreement (which
* also govern the use of this file). You may share a modified version of this
* file with another authorized licensee of Derivative's TouchDesigner software.
* Otherwise, no redistribution or sharing of this file, with or without
* modification, is permitted.
*/
#include "CPlusPlusCHOPExample.h"
#include <stdio.h>
#include <string.h>
#include <cmath>
#include <assert.h>
// These functions are basic C function, which the DLL loader can find
// much easier than finding a C++ Class.
// The DLLEXPORT prefix is needed so the compile exports these functions from the .dll
// you are creating
extern "C"
{
DLLEXPORT
int32_t
GetCHOPAPIVersion(void)
{
// Always return CHOP_CPLUSPLUS_API_VERSION in this function.
return CHOP_CPLUSPLUS_API_VERSION;
}
DLLEXPORT
CHOP_CPlusPlusBase*
CreateCHOPInstance(const OP_NodeInfo* info)
{
// Return a new instance of your class every time this is called.
// It will be called once per CHOP that is using the .dll
return new CPlusPlusCHOPExample(info);
}
DLLEXPORT
void
DestroyCHOPInstance(CHOP_CPlusPlusBase* instance)
{
// Delete the instance here, this will be called when
// Touch is shutting down, when the CHOP using that instance is deleted, or
// if the CHOP loads a different DLL
delete (CPlusPlusCHOPExample*)instance;
}
};
CPlusPlusCHOPExample::CPlusPlusCHOPExample(const OP_NodeInfo* info) : myNodeInfo(info)
{
myExecuteCount = 0;
myOffset = 0.0;
}
CPlusPlusCHOPExample::~CPlusPlusCHOPExample()
{
}
void
CPlusPlusCHOPExample::getGeneralInfo(CHOP_GeneralInfo* ginfo)
{
// This will cause the node to cook every frame
ginfo->cookEveryFrameIfAsked = true;
ginfo->timeslice = true;
ginfo->inputMatchIndex = 0;
}
bool
CPlusPlusCHOPExample::getOutputInfo(CHOP_OutputInfo* info)
{
// If there is an input connected, we are going to match it's channel names etc
// otherwise we'll specify our own.
if (info->opInputs->getNumInputs() > 0)
{
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;
}
}
const char*
CPlusPlusCHOPExample::getChannelName(int32_t index, void* reserved)
{
return "chan1";
}
void
CPlusPlusCHOPExample::execute(const CHOP_Output* output,
OP_Inputs* inputs,
void* reserved)
{
myExecuteCount++;
double scale = inputs->getParDouble("Scale");
// In this case we'll just take the first input and re-output it scaled.
if (inputs->getNumInputs() > 0)
{
// We know the first CHOP has the same number of channels
// because we returned false from getOutputInfo.
inputs->enablePar("Speed", 0); // not used
inputs->enablePar("Reset", 0); // not used
inputs->enablePar("Shape", 0); // not used
int ind = 0;
for (int i = 0 ; i < output->numChannels; i++)
{
for (int j = 0; j < output->numSamples; j++)
{
const OP_CHOPInput *cinput = inputs->getInputCHOP(0);
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;
}
}
}
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
int shape = inputs->getParInt("Shape");
// const char *shape_str = inputs->getParString("Shape");
// keep each channel at a different phase
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;
}
}
int32_t
CPlusPlusCHOPExample::getNumInfoCHOPChans()
{
// 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 2;
}
void
CPlusPlusCHOPExample::getInfoCHOPChan(int32_t index,
OP_InfoCHOPChan* chan)
{
// 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 = "executeCount";
chan->value = (float)myExecuteCount;
}
if (index == 1)
{
chan->name = "offset";
chan->value = (float)myOffset;
}
}
bool
CPlusPlusCHOPExample::getInfoDATSize(OP_InfoDATSize* infoSize)
{
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;
}
void
CPlusPlusCHOPExample::getInfoDATEntries(int32_t index,
int32_t nEntries,
OP_InfoDATEntries* entries)
{
// It's safe to use static buffers here because Touch will make it's own
// copies of the strings immediately after this call returns
// (so the buffers can be reuse for each column/row)
static char tempBuffer1[4096];
static char tempBuffer2[4096];
if (index == 0)
{
// Set the value for the first column
#ifdef WIN32
strcpy_s(tempBuffer1, "executeCount");
#else // macOS
strlcpy(tempBuffer1, "executeCount", sizeof(tempBuffer1));
#endif
entries->values[0] = tempBuffer1;
// Set the value for the second column
#ifdef WIN32
sprintf_s(tempBuffer2, "%d", myExecuteCount);
#else // macOS
snprintf(tempBuffer2, sizeof(tempBuffer2), "%d", myExecuteCount);
#endif
entries->values[1] = tempBuffer2;
}
if (index == 1)
{
// Set the value for the first column
#ifdef WIN32
strcpy_s(tempBuffer1, "offset");
#else // macOS
strlcpy(tempBuffer1, "offset", sizeof(tempBuffer1));
#endif
entries->values[0] = tempBuffer1;
// Set the value for the second column
#ifdef WIN32
sprintf_s(tempBuffer2, "%g", myOffset);
#else // macOS
snprintf(tempBuffer2, sizeof(tempBuffer2), "%g", myOffset);
#endif
entries->values[1] = tempBuffer2;
}
}
void
CPlusPlusCHOPExample::setupParameters(OP_ParameterManager* manager)
{
// speed
{
OP_NumericParameter np;
np.name = "Speed";
np.label = "Speed";
np.defaultValues[0] = 1.0;
np.minSliders[0] = -10.0;
np.maxSliders[0] = 10.0;
OP_ParAppendResult res = manager->appendFloat(np);
assert(res == OP_ParAppendResult::Success);
}
// scale
{
OP_NumericParameter np;
np.name = "Scale";
np.label = "Scale";
np.defaultValues[0] = 1.0;
np.minSliders[0] = -10.0;
np.maxSliders[0] = 10.0;
OP_ParAppendResult res = manager->appendFloat(np);
assert(res == OP_ParAppendResult::Success);
}
// shape
{
OP_StringParameter sp;
sp.name = "Shape";
sp.label = "Shape";
sp.defaultValue = "Sine";
const char *names[] = { "Sine", "Square", "Ramp" };
const char *labels[] = { "Sine", "Square", "Ramp" };
OP_ParAppendResult res = manager->appendMenu(sp, 3, 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);
}
}
void
CPlusPlusCHOPExample::pulsePressed(const char* name)
{
if (!strcmp(name, "Reset"))
{
myOffset = 0.0;
}
}