HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Writing a CHOP

Creating a Custom CHOP

CHOP stands for Channel Operation. Each CHOP is responsible for creating or modifying channel data. All CHOPs are derived from the CHOP_Node base class. To write a custom CHOP, you need to do the following things:

The cook method basically comes in two flavors, cooks that have inputs and those that don't. All cooking involves evaluating the parameters, processing the sample data and returning the status. CHOPs without inputs generally begin the cook by calling the destroyClip method to make sure all the sample data is created fresh. Cooks that have inputs call the copyInput method which gets information from the parent clip.

Below are the two common examples of a CHOP's cookMyChop() method.

Generator skeleton example:

CHOP_Example::cookMyChop(OP_Context &context)
{
CL_Track *track;
fpreal samplerate;
int nchan;
char *cnames[MAX_CHANS];
// clear out the old tracks & data
destroyClip();
samplerate = evalFloat("samplerate", 0, context.getTime());
if(UTequalZero(samplerate))
{
return error();
}
myClip->setSampleRate(samplerate);
// Assign start & end time to myClip
myClip->setStart(1);
myClip->setTrackLength(240);
// evaluate parameters
evalString(name, "channame", 0, context.getTime());
nchan = name.expandArrays(cnames, MAX_CHANS);
my_NC = nchan;
// create the tracks
for (my_C=0; my_C < nchan; my_C++)
{
track = myClip->addTrack(cnames[my_C]);
data = track->getData();
// assign values to data
}
return error();
}

Filter skeleton example:

CHOP_Example::cookMyChop(OP_Context &context)
{
CL_Track *track;
const CL_Clip *parent_clip;
const CL_Track *parent_track;
const fpreal *src;
fpreal *dest;
parent_clip = copyInput(context, 0, 1, 1); // copy data and slerps too
// evaluate parameters
my_NC = myClip->getNumTracks();
// modify the tracks
for (my_C=0; my_C < my_NC; my_C++)
{
track = myClip->getTrack(my_C);
parent_track = parent_clip->getTrack(my_C);
src = parent_track->getData();
dest = track->getData();
// assign values to dest using values in src
}
return error();
}

CHOP_Node provides several useful methods for CHOP cooking:

  • cookMyChop(OP_Context &context)
    This function must be overridden by all subclasses. It is supplied with a context that describes the sample data and the time that the CHOP is to be cooked. Each CHOP has a clip pointer (myClip) associated with it. All access to the clip is done through the CL_Clip class. The context passes in the time at which to cook and can be accessed through context.getTime().
  • getClip(OP_Context *context = 0)
    This function returns a pointer to the sample data (myClip).
  • inputClip(int index, OP_Context &context)
    This function returns a pointer to a clip with input at the specified index.
  • usesScope() const
    This function returns a 1 since scope is used by default for CHOPs. This may be overridden at the derived level.
  • isScoped(const UT_String &name)
    Determines if the specified name is found within the scope string on the CHOP's common parameters page.
  • destroyClip()
    Clears and deletes the sample data (myClip).
  • copyInput(OP_Context &context, int index, int data, int slerps)
    This function returns a pointer to a clip that is created with the specified context and index, and contains a copy of the specified data and slerps.

Cooking and Changing Handles

Handles allow for interactive modification of parameters in the graph
window.  They appear as boxes or squares and may have bars in horizontal
or vertical directions depending on their function.  CHOPs are not required
to have handles.

Handles are based on @c CHOP_Handle.h and required methods 
for cooking and recognizing changes.

@c cookMyHandles example:
#define OFFSET_HANDLE 1
void
CHOP_Example::cookMyHandles(OP_Context &context)
{
destroyHandles();
xoffset = (end - start) * 0.75 + start;
yoffset = OFFSET(context.getTime());
hlook = myChannels->getChannel("offset") ? HANDLE_GUIDE : HANDLE_BOX;
handle = new CHOP_Handle(this, "offset", OFFSET_HANDLE, xoffset, yoffset,
myHandles.append(handle);
}
This creates a handle which modifies the offset parameter and allows for
motion in the vertical direction only.  The handle is positioned three
quarters of the way between the start and end of the clip.

The general form of the CHOP_Handle allows for many variations:
CHOP_Handle( CHOP_Node *dad, // the parent
const char *name, // the handle label
int id, // used to determine changes
fpreal xoffset, // the handle's x position
fpreal yoffset, // the handle's y position
CHOP_HandleLook look, // what the handle looks like
CHOP_HandleMotion motion, // how the handle moves
int flags) // other handle descriptions
@c CHOP_HandleLook can be one of three types:

- @c HANDLE_GUIDE - a line that cannot be moved
- @c HANDLE_BOX - a filled in square 
- @c HANDLE_SQUARE - a square outline.


CHOP_HandleMotion can be one of three types as well: 

- @c HANDLE_HORIZONTAL - a vertical line that slides horizontally 
- @c HANDLE_VERTICAL - a horizontal line that slides vertically
- @c HANDLE_PLANE - a square that allows both types of motion


Other flags further describe the appearance of the handle:

- @c HANDLE_LABEL_LEFT - place the label to the left of the handle
- @c HANDLE_LABEL_RIGHT - place the label to the right of the handle
- @c HANDLE_LABEL_TOP - place the label above the handle
- @c HANDLE_LABEL_BOTTOM - place the label below the handle
- @c HANDLE_BAR - allows the handle to be used in bar mode
- @c HANDLE_GRAPH - allows the handle to be used in graph mode
- @c HANDLE_WIDTH_END - the handle is at the end of the clip

The DEFAULT_FLAGS are set to position the label below and to the
left of the handle in graph mode.  

Along with a cook method, a method to handle changes in the CHOP 
(including changes to the handles) is also necessary.  

@c handleChanged example:
CHOP_Example::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 example checks for changes in three handles and modifies the parameter associated with the affected handle. Handles are selected based on their Id numbers. Any changes in the CHOP from something other than a handle should also be included in this method.

When dealing with handles, it is important to make sure the units are correct. The xoffset is always in samples and should be rounded off to the nearest integer value just to be safe. In order to return the proper value, the toUnit method must be used. This converts the xoffset value to the type of unit currently displayed by the graph. If the handle is at the end of an interval (has a HANDLE_WIDTH_END flag), a 1 must be provided as the third parameter. Only values in the x-plane need to be converted to the proper units.

CHOP Examples

  • CHOP Stair Example
    An example of a CHOP generator with handles.
  • CHOP Spring Example
    An annotated version of Houdini's Spring CHOP, which applies spring-like motion to motion. This is also a good example of a realtime Time Slice CHOP.
  • CHOP Blend Example
    An annotated version of Houdini's Blend CHOP, which is a multi-input operation that blends channels together.