HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CHOP Generator Example - Stair

Overview

This example shows how to create a CHOP generator, create tracks and add handles to the graph. The channels generated are "stairs" - flat with periodic jumps.

Example Walkthrough

These code fragments are taken from CHOP_Stair.C.

The first two headers are required for all HDK OPs, while the last three headers are generally required for most CHOP nodes.

#include <OP/OP_Channels.h>
#include "CHOP_Stair.h"

The first two includes are required when using CHOP viewport handle.

CHOP_SWITCHER2(4, "Stair", 7, "Channel");
// Names of gadgets
static PRM_Name names[] =
{
PRM_Name("number", "Number of Stairs"),
PRM_Name("height", "Stair Height"),
PRM_Name("offset", "Offset"),
PRM_Name("direction", "Direction"),
PRM_Name("channelname", "Channel Name"),
PRM_Name("start", "Start"),
PRM_Name("end", "End"),
// SampleRateName
// ExtendLeftName
// ExtendRightName
// DefaultValueName
};
// Menus
static PRM_Name CHOPstairMenu[] = {
PRM_Name("up", "Up"),
PRM_Name("down", "Down"),
};
| PRM_CHOICELIST_REPLACE),CHOPstairMenu);
// Defaults
static PRM_Default nameDefault(0,"chan1");
static PRM_Range stairRange(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 10);
CHOP_Stair::myTemplateList[] =
{
// page 1
PRM_Template(PRM_INT_J, 1, &names[0], PRMoneDefaults, 0, &stairRange),
PRM_Template(PRM_ORD, 1, &names[3], PRMzeroDefaults, &stairMenu),
// page 2
PRM_Template(PRM_STRING, 1, &names[4], &nameDefault,0),
};
OP_TemplatePair CHOP_Stair::myTemplatePair(
CHOP_Stair::myTemplateList, &CHOP_Node::myTemplatePair);

This chunk of code creates the parameter dialog for the CHOP. Wherever possible, use the standard names, defaults and ranges found in PRM_ChopShared.h.

enum
{
VAR_C = 200,
VAR_NC = 201
};
CHOP_Stair::myVariableList[] = {
{ "C", VAR_C, 0 },
{ "NC", VAR_NC, 0 },
{ 0, 0, 0 }
};
OP_VariablePair CHOP_Stair::myVariablePair(
CHOP_Stair::myVariableList, &CHOP_Node::myVariablePair);
bool
CHOP_Stair::evalVariableValue(fpreal &v, int index, int thread)
{
switch( index )
{
case VAR_C:
v = (fpreal) my_C;
return true;
case VAR_NC:
v = (fpreal) my_NC;
return true;
}
return CHOP_Node::evalVariableValue(v, index, thread);
}

This CHOP, like many CHOPs, defines the C and NC local variables. C is the local variable for the currently cooking track, while NC is the total number of tracks in the clip.

CHOP_Stair::myConstructor(OP_Network *net,
const char *name,
{
return new CHOP_Stair(net, name, op);
}
CHOP_Stair::CHOP_Stair( OP_Network *net,
const char *name,
: CHOP_Node(net, name, op),
myExpandArray()
{
myParmBase = getParmList()->getParmIndex( names[0].getToken() );
my_C = 0;
my_NC = 0;
}
{
}

The stair CHOP is derived directly from CHOP_Node. We initialize our local variables to nice initial values.

bool
CHOP_Stair::updateParmsFlags()
{
bool changes = CHOP_Node::updateParmsFlags();
if(LEXTEND() == CL_TRACK_DEFAULT || REXTEND() == CL_TRACK_DEFAULT)
changes |= enableParm(CHOP_DefaultValueName.getToken(),1);
else
changes |= enableParm(CHOP_DefaultValueName.getToken(),0);
return changes;
}

The updateParmsFlags() method only enables the default value parameter when one of the extend conditions uses the "default value" setting.

void
CHOP_Stair::getInterval(fpreal t, fpreal *start, fpreal *end)
{
start = START(t);
end = END(t);
if (*start >= *end)
{
fpreal temp = *start;
start = *end;
end = temp;
}
start = SYSrint(*start);
end = SYSrint(*end);
}

This method fetches the frame range of the CHOP, ensuring it is valid.

#define START_HANDLE 1
#define END_HANDLE 2
#define OFFSET_HANDLE 3
void
CHOP_Stair::cookMyHandles(OP_Context &context)
{
CHOP_Handle *handle;
destroyHandles();
// Grab the values for our handles
getInterval(context.getTime(), &start, &end);
xoffset = (end - start) * 0.75 + start;
yoffset = OFFSET(context.getTime());
// If the parameter has a channel, use a guide rather than a handle.
hlook = myChannels->getChannel("start") ? HANDLE_GUIDE : HANDLE_BOX;
// This creates a new handle for the start of the clip, and appends it to
// the list of handles. It will have the label 'start' to the bottom-right
// of the handle, and appear in bar view mode as well. It moves horizontally
// along the frame axis.
handle = new CHOP_Handle(this, "start", START_HANDLE, start, 0.0,
myHandles.append(handle);
// The end of the clip also has a handle for adjusting the clip
hlook = myChannels->getChannel("end") ? HANDLE_GUIDE : HANDLE_BOX;
handle = new CHOP_Handle(this, "end", END_HANDLE, end, 0.0,
myHandles.append(handle);
// This is a horizontal handle which moves along the value axis to adjust
// the height of a stair.
hlook = myChannels->getChannel("offset") ? HANDLE_GUIDE : HANDLE_BOX;
handle = new CHOP_Handle(this, "offset", OFFSET_HANDLE, xoffset, yoffset,
myHandles.append(handle);
}

The Stair CHOP has interactive handles that are drawn in the CHOP viewer. All of these handles are interactive unless they have been overridden by a channel, in which case they merely become guides. This CHOP has two handles which adjust the start and end time of the clip, and a third handle which moves vertically to adjust the height of a stair.

This method creates the handles for the viewer to draw. Each handle is passed a unique ID so that the corresponding handleChanged() can determine which handle was moved.

CHOP_Stair::handleChanged(CHOP_Handle *handle, CHOP_HandleData *hdata)
{
fpreal result = 0.0;
OP_Network *net = 0;
if (hdata->shift)
{
setCurrent(1);
if (net = getParent())
net->pickRequest(this, 0);
}
switch(handle->getId())
{
offset = CL_RINT(hdata->xoffset);
if (!hdata->shift)
SET_START(myHandleCookTime, offset);
result = toUnit(offset, hdata->unit);
break;
case END_HANDLE:
offset = CL_RINT(hdata->xoffset);
if (!hdata->shift)
SET_END(myHandleCookTime, offset);
result = toUnit(offset, hdata->unit, 1);
break;
if (!hdata->shift)
SET_OFFSET(myHandleCookTime, hdata->yoffset);
result = hdata->yoffset;
break;
}
return result;
}

This method processes user interaction and sets parameters accordingly. The handle changed by the user is indicated by the handle parameter, while the specifics of the change are pass in hdata (including the X and Y position).

If the shift flag is set, the node should show its parm dialog and the handle displays its current value in the viewport. Do not modify any values in this case.

Otherwise, the handle directly affects the parameter it affects using one of the OP_Parameter::setFloat() or setInt() methods.

CHOP_Stair::shiftStart(fpreal new_offset, fpreal t)
{
SET_START(t, new_offset);
return new_offset;
}

This overridden virtual method allows generator CHOPs to be shifted by certain downstream CHOPs (such as the interpolate CHOP). The only thing that needs to be done is to modify the CHOP so that the start time is set to the new_offset parameter.

CHOP_Stair::cookMyChop(OP_Context &context)
{
CL_Track *track;
fpreal samplerate;
int left, right;
fpreal defvalue;
int nchan;
fpreal stepheight;
fpreal index, idxstep;
int i;
destroyClip();
samplerate = RATE(context.getTime());
if(UTequalZero(samplerate))
{
return error();
}
myClip->setSampleRate(samplerate);
// Read non-expression parms
CHAN_NAME(name, context.getTime());
nchan = myExpandArray.setPattern(name);
my_NC = nchan;
getInterval(context.getTime(), &start, &end);
left = LEXTEND();
right = REXTEND();
myClip->setTrackLength((int)(end-start+1));
myClip->setStart(start);

This portion of the cook method sets up all the attributes of the clip - the frame range, sample rate, and tracks to create. Since this is a generator CHOP, all these values are derived from parameters, rather than inputs.

// Create the tracks
for (my_C=0; my_C < nchan; my_C++)
{
defvalue = DEFAULT(context.getTime());
value = OFFSET(context.getTime()); // reread for local vars
idxstep = (end-start+1) / (NUMBER(context.getTime()) + 1);
stepheight = HEIGHT(context.getTime());
if (DIRECTION() == 1)
stepheight *= -1;
track = myClip->addTrack(myExpandArray(my_C));
track->setLeft((CL_TrackOutside)left);
track->setRight((CL_TrackOutside)right);
if(left == CL_TRACK_DEFAULT || right == CL_TRACK_DEFAULT)
track->setDefault(defvalue);
data = track->getData();
track->constant(0);
index = idxstep;
for(i=0; i < (end-start+1); i++)
{
if (i && i > index)
{
value += stepheight;
index += idxstep;
}
data[i] = value;
}
}
my_C = 0;
return error();
}

The next part of the cook method creates each track, assigns the extend conditions to it, and then fills in the data. Parameters are re-evaluated for each track, in case the local variable 'C' is used in an expression for one of the parameters (which corresponds to our my_C data member).

void
{
table->addOperator(
new OP_Operator("hdk_stair", // Internal name
"HDK Stair", // UI name
CHOP_Stair::myConstructor, // CHOP constructor
&CHOP_Stair::myTemplatePair, // Parameters
0, // Min # of inputs
0, // Max # of inputs
&CHOP_Stair::myVariablePair, // Local variables
OP_FLAG_GENERATOR) // generator/source
);
}

Finally, the CHOP is registered. Since it is a generator, it has no inputs and it sets the OP_FLAG_GENERATOR flag.