256-fall-2009: HW2

Roy Fejgin

Getting the project

This compressed tar file contains all the files needed to build and run this assignment.

Build instructions

To build just type 'make'.

To clean all temporary files and executables type 'make clean'.

To execute, type './poly'.

The project builds for JACK/Linux.



A main concept in the way I implemented this assignment is the 'AudioChainElement'. This is an abstract class representing any entity that can be connected into an audio chain. That means it accepts audio inputs on one end, and generates audio outputs (samples) on the other. The number of inputs connected to the element and the number of outputs (other elements to which this one is connected) can be arbitrarily large.

A good example of an audio chain element is a filter. Another is a plucked string.

The to use an audio chain is:

  1. Patch up any number of audio chain elements using their connectTo() API. A graph of any shape may be created.

Then, for each sample:

  1. Call the tick() function on the element at the start of the chain.
    This function causes the whole chain to generate its output by each element recursively calling tick() on the elements it is connected to.

  2. Read the audio output from the element at the end of the chain.

  3. Call endOfTick() on the start of the chain to let all elements prepare for the next sample.

Many of the objects in this assignment are descendant classes of AudioChainElement. The plucked string is one example. But the delays and filters used inside the plucked string object are themselves AudioChainElements. That means that they can be independently be connected into audio chains, inside or outside the plucked string. In this exercise filters and delays are used only inside the plucked string (which contains its own little audio chain), but their code can be reused so that they are placed in any audio chain.

Another example of code reuse in this hierarchy is the graph traversal code: all classes share it since it implemented in the base class (AudioChainElement).

Note: maybe “Unit Generator” would have been a better name for the Audio Chain Element, but I implemented it before I'd knew that term.

(the README continues below)

AudioChainElement Class Hierarchy

Cycles in the chain/graph

The implementation attempts to take care of loops in the chain (i.e., cycles in the directed graph represents the chain). When we hit a node that has already been processed in the current tick (a tick is a traversal of the graph for the purpose generating a single output), the recursive call stops. The output generated by the last element in the loop is accumulated in the next element and used as part of that element's input for the next tick.

There may be a problem with this implementation, which wasn't sure how to solve. Consider a graph containing the following paths: X->Y->Z->DAC, and also X->T->Z->DAC. That is, there are two paths from A to C (but not an actual loop). My implementation will send a sample to the DAC along the first path, and when the second path is traversed and C is encountered for a second time, the traversal will stop (even though it's not an actual loop), and the output of T will be accumulated in Z until the next tick. I'm not sure if this one-sample delay is valid.

Last year, after finishing this homework I took a look at the ChucK implementation and realized that it traverses the UANA graph in reverse order (starting from the DAC). Would implementing it this way this have solved my problem?

Anyway – this problem doesn't affect the audio in this exercise since this it doesn't contain any graphs of the above form (proper loops, such as that one in the plucked string object, are handled correctly).

Description of the main files/classes

Polyphony is arbitrary. The PluckedString objects are created dynamically.

Data Structures

When the MIDI callback receives a 'note off' MIDI message, release() is called on the associated PluckedString object.

In response, the PluckedString changes the gain of its feedback loop so that the audio level starts to decay rapidly.

Once the PluckedString has determined its release stage is finished, it calls a callback function that was given to it when it was constructed. This callback lets Poly.cpp know that the string is ready to be destroyed.

This implementation has the following advantages:

Musical Statement

A very meaningful statement by the name of plickpluck.

(it's not performed very well since it was recorded on a Windows setup with very high latency...).