Reading Inputs and Writing Outputs

The exact mechanism for references to input and output ports depends somewhat on the domain. This is because primitives in the domain XXX use objects of class InXXXPort and OutXXXPort (derived from PortHole) for input and output, respectively. The examples we use here are for the SDF domain. See the appropriate domain chapter for variations that apply to other domains.

Portholes and Particles

In the SDF domain, normal inputs and outputs become members of type InSDFPort and OutSDFPort after the preprocessor is finished. These are derived from base class PortHole. For example, given the following item in the defprimitive of an SDF primitive:

input
{
  name {in}
  type {float}
}

A member named in, of type InSDFPort, will become part of the primitive. We are not usually interested in directly accessing these porthole classes, but rather wish to read or write data through the portholes. All data passing through a porthole is derived from the base class Particle. Each particle contains data of the type specified in the type sub item of the input or output item.

The operator % operating on a porthole returns a reference to a particle. Consider the following example:

go
{
  Particle& currentSample = in%0;
  Particle& pastSample = in%1;
  ...
}

The right-hand argument to the % operator specifies the delay of the access. A zero always means the most recent particle. A one means the particle arriving just before the most recent particle. The same rules apply to outputs. Given an output named out, the same particles that are read from in can be written to out in the same order as follows:

go
{
  ...
  out%1 = pastSample;
  out%0 = currentSample;
}

This works because out%n returns a reference to a particle, and hence can accept an assignment. The assignment operator for the class Particle is overloaded to make a copy of the data field of the particle.

Operating directly on class Particle, as in the above examples, is useful for writing primitives that accept anytype of input. The operations need not concern themselves with the type of data contained by the particle. But it is far more common to operate numerically on the data carried by a particle. This can be done using a cast to a compatible type. In the example above, in is of type float, therefore its data can be accessed by

go
{
  Particle& currentSample = in%0;
  double value = (double)currentSample;
  ...
}

or more concisely,

go
{
  double value = (double)(in%0);
  ...
}

The expression (double)(in%0) can be used anywhere a double can be used. In many contexts, where there is no ambiguity, the conversion operator can be omitted:

double value = in%0;

However, since conversion operators are defined to convert particles to several types, it is often necessary to indicate precisely which type of conversion is desired.

To write data to an output porthole, note that the right-hand side of the assignment operator should be of type Particle, as shown in the above example. An operator << is defined for particle classes to make this more convenient. Consider the following example:

go
{
  float t;
  t = some value to be sent to the output
  out%0 << t;
}

Note the distinction between the << operator and the assignment operator. The latter operator copies particles, the former operator loads data into particles. The type of the right-side operand of << may be int, float, double, Fix, Complex or Envelope. The appropriate type of conversion will be performed. For more information on the Envelope and Message types, please see Using Data Types.

SDF PortHole Parameters

In the example above, where in%1 was referenced, some special action is required to tell MLDesigner that past input particles are to be saved. A special action is also required to tell the SDF scheduler how many particles will be consumed at each input and produced at each output when a primitive fires. This information can be provided through a call to setSDFParams in the setup method. This has the syntax

setup
{
  portName.setSDFParams(multiplicity, past)
}

where portName is the name of the input or output porthole, multiplicity is the number of particles consumed or produced, and past is the maximum value that offset can take in any expression of the form name%offset. For example, if the go method references name%0 and name%1, then past would have to be at least one. It is zero by default.

Multiple Portholes

Sometimes a primitive should be defined with n input ports and/or n output ports, where n is a variable. This is supported by the class MultiPortHole and its derived classes (MultiIn<domain>Port and MultiOut<domain>Port). An object of this class has a sequential list of PortHoles. For SDF, we have the specialized derived class MultiInSDFPort (which contains InSDFPorts) and MultiOutSDFPort (which contains OutSDFPorts). Defining a multiple porthole is easy, as illustrated next:

defprimitive
{
  ...
  inmulti
  {
    name {input_name}
    type {input_type}
  }
  outmulti
  {
    name {output_name}
    type {output_type}
  }
  ...
}

To successfully access individual portholes in a MultiPortHole, In<domain>MPHIter or Out<domain>MPHIter iterator class should be used. Consider the following code segment from the definition of the SDFFork primitive:

input
{
  name {input}
  type {anytype}
}
outmulti
{
  name {output}
  type {=input}
}
...
go
{
  OutSDFMPHIter nextp(output);
  OutSDFPort* p;
  while ((p = nextp++) != 0)
    (*p)%0 = input%0;
}

A single input porthole supplies a particle that gets copied to any number of output portholes. The type of the output MultiPortHole is inherited from the type of the input. The first line of the go method creates an OutSDFMPHIter iterator called nextp, initialized to point to portholes in output. The ++ operator on the iterator returns a pointer to the next porthole in the list, until there are no more portholes, at which time it returns NULL. So the while construct steps through all output portholes, copying the input particle data to the appropriate output. Consider another example, taken from the SDFAdd primitive:

inmulti
{
  name {input}
  type {float}
}
output
{
  name {output}
  type {float}
}
go
{
  InSDFMPHIter nexti(input);
  InSDFPort* p;
  double sum = 0.0;
  while ((p = nexti++) != 0)
    sum += double((*p)%0);
  output%0 << sum;
}

An InSDFMPHIter iterator named nexti is created and used to access the inputs individually. Occasionally the numberPorts method of class MultiPortHole, which returns the number of ports, is useful. This is called simply as portname.numberPorts(), and returns an integer.

Type Conversion

The type conversion operators and << operators are defined as virtual methods in the base class Particle. There are never really objects of class Particle in the system. Instead, there are objects of class IntParticle, FloatParticle, ComplexParticle and FixParticle, which hold data of type int, double (not float!), Complex and Fix, respectively (there are also MessageParticle, DataStructParticle and a variety of matrix particles, described later). The conversion and loading operators are designed to ”do the right thing” when an attempt is made to convert between mismatched types.

Clearly we can convert an int to a double or Complex, or a double to a Complex, with no loss of information. Attempts to convert in the opposite direction work as follows: conversion of a Complex to a double produces the magnitude of the complex number. Conversion of a double to an int produces the greatest integer that is less than or equal to the double value. There are also operators to convert to or from float and Fix. Each particle also has a virtual print method, so a primitive that writes particles to a file can accept anytype.