pl-nk v0.4.5
Plonk|Plink|Plank are a set of cross-platform C/C++ frameworks for audio software development
Core Plonk concepts.
Fundamentals

An outline of the core concepts used in Plonk. More...

An outline of the core concepts used in Plonk.

Introduction

Fundamental types

Plonk generally makes use of the standard C++ types, char, int, float, double etc. To keep consistent types on all platforms these are typedef 'd in the plonk namespace as follows:

There is also an Int24 class to assist with manipulating 24-bit audio data.

Core classes

Plonk is designed to fully support a range of audio sample types in the future. Currently, only 32-bit float support is extensively tested but 64-bit support should work (and the infrastructure is in place to make integer and maybe fixed-point processing a possibility in the future). To help support this many Plonk classes are C++ template classes. For example, The Variable class is a template. Essentially, this allows you to store another value or object in a rerefence counted container (Variable has other useful features too which are outlined below). To store an int in a Variable you would use a Variable<Int>, for convenience (and to avoid many nested angle brackets that can result from templated code) commonly used types are typedef 'd. For example, IntVariable is equivalent to Variable<Int> (and this naming scheme is used throughout Plonk for similar typedef s

Plonk classes are divided into "user" classes and "internal" (i.e., implementation) classes. Most internal classes are denoted by the Internal suffix added to the class name (should you come across them in the documentation). Generally, Plonk classes come in pairs (e.g., SomeClass and SomeClassInternal). As a user you will generally deal with the user classes and almost never the internal classes directly (unless of course you write your own extensions using the same pattern). Almost all Plonk classes like this are built on a custom reference counting base class (which incidentally is thread safe and lock free) to help manage object lifetimes. You should never need to deal with the reference counting system, simply create user classes on the stack, store them by value in your own classes, and pass around by value (or by reference if you're careful).

For example, to use the Variable class to store and manipulate integers you could do something like:

 IntVariable a (1); // 'a' initialised to 1
 IntVariable b (2); // 'b' initialised to 2
 IntVariable c (b); // 'c' shares same state as 'b'
 
 a = 3; // 'a' now set to 3 - "a.setValue(3)" is equivalent
 a = b; // 'a' now shares 'b' (the original internal from 'a' is destroyed as it's no longer referenced) 
 b = 4; // 'a', 'b' and 'c' all store 4 since they all share the same state

This technique should mean that you can avoid the use of explicit memory allocation in your own user code. You should rarely need to use new, delete, malloc or free (etc). One use of the Variable class is to store values you wish to use in an audio process that you wish to manipulate from elsewhere (e.g., from A GUI).

As mentioned previously, the Variable class has other useful features. Instances of Variable can be combined into expressions that are evaluated at a later time. For example:

 IntVariable a (1);
 IntVariable b (2);
 IntVariable c = a + b; // creates a variable that will evaluate "a + b" at a later time;
 
 int t1 = a; // is 1 - implies "t1 = a.getValue()"
 int t2 = b; // is 2
 int t3 = c; // is 3 - evaluates "a.getValue() + b.getValue()"
 
 a = 100;
 
 int t4 = c; // is 102

Variable supports many unary and binary arithmetic operators in this way including +, -, *, /, pow, and so on. The Variable documentation page provides a full list.

Arrays

Plonk provides its own dynamic array class ObjectArray (which is also a template class and employs the same reference counted lifetime management as the Variable class). ObjectArray is very generalised. Please note that while assignment of a Plonk array is thread safe, manipulations of the array are not. To store and manipulate numerical data there is the specialised class NumericalArray (which inherits from ObjectArray).

As with the Variable class there are typedef s for many of the common types. One convention in Plonk is to use the plural form to imply an array e.g., Ints is a NumericalArray<Int>. It is appreciated that this style may not suit all users and there are alternative forms using the word 'Array', thus IntArray is equivalent to Ints (both are NumericalArray<Int>). There is nothing to prevent you from using an ObjectArray<Int> but the NumericalArray has added arithmetic functionality.

 Ints a;              // empty array
 Ints b (1);          // an array containing a single element '1'
 Ints c (1, 2, 3, 4); // initialise with array {1, 2, 3, 4}, up to 32 items can be used to initialise this way
 
 int cn = c.length(); // is 4, the number of items stored in 'c'
 int cs = c.size();   // is also 4, 'size' and 'length' are only different for null terminated arrays (see Text below)
 
 b.add (10);          // 'b' is now {1, 10}
 b.add (100);         // 'b' is now {1, 10, 100}
 
 c[1] = 20;           // 'c' is now {1, 20, 3, 4}
 b[2] = c[1];         // 'b' is now {1, 10, 20}
 a = b;               // 'a' now shares 'b'
 
 a.remove (0);        // remove at index 0, 'a' and 'b' are now both {10, 20}
 c.removeItem (20);   // removes item '20', 'c' is now { 1, 3, 4}
 c.removeItem (100);  // has no effect as 'c' doesn't contain '100'
 cn = c.length();     // is now 3

By default Plonk checks inidices to ensure they are not out of bounds. If you wish to avoid these checks and are sure that array indices are within the bounds you can call faster versions:

 Ints a (1, 2, 3);
 int b = a[0]; // is 1
 int c = a[1]; // is 2
 int d = a[2]; // is 3
 int e = a[3]; // index out-of-bounds, returns zero (or a 'null' object if this is not a NumericalArray)
 int f = a.atUnchecked (0); // is 1
 int g = a.atUnchecked (1); // is 2
 int h = a.atUnchecked (2); // is 3
 int i = a.atUnchecked (3); // returns garbage or may crash, raises an assertion though in a Debug build

As dicsussed above, arithmetic can be performed on NumericalArray types. For example:

 Ints a (1, 2, 3);          
 Ints b (4, 5, 6);
 Ints c (a + b);            // 'c' is {5, 7, 9}
 Ints d (1, 10, 100, 1000); 
 Ints e (c * d);            // 'e' is {5, 70, 900, 5000} i.e., the shorter array 'loops' to complete the pattern
 Ints f (e);                // 'f' shares 'e'
 e *= 10;                   // 'e' is now {50, 700, 9000, 50000} but 'f' is still {5, 70, 900, 5000}

These arithmetic operations are evaluated on assignment unlike the Variable class (although you could wrap a NumericalArray in a Variable if you wish, to leverage the deferred evaluation capabiltiies of Variable).

Numerical arrays can be filled with commonly required data e.g., sine wave tables, random values. For example:

 Floats a (Floats::newClear (10));        // array with 10 items all zero
 Floats b (Floats::sineTable (8));        // sine wave shape using 8 items (bipolar -1...+1) starting at 0
 Floats c (Floats::cosineTable (8));      // cosine wave shape using 8 items (bipolar -1...+1) starting at 1
 Floats d (Floats::cosineWindow (8));     // cosine wave shape using 8 items (unipolar 0...+1) starting at 0
 Floats e (Floats::rand (4, 10, 100));    // array of 4 random values 10...100 uniformly distributed
 Floats f (Floats::exprand (4, 10, 100)); // array of 4 random values 10...100 exponentially distributed

Text is a specially adapted Chars (CharArray or NumericalArray<Char>) for dealing with text strings.

 Text a ("Hello");
 Text b ("World!");
 Text c (a + " " + b); // is "Hello World!" as '+' is overidden to do concatenation
 int an = a.length();  // is 5
 int bn = b.length();  // is 6
 int cn = c.length();  // is 12
 int as = a.size();    // is 6  (size is one larger to hold the null terminating character
 int bs = b.size();    // is 7
 int cs = c.size();    // is 13

If you need to access the raw C++ array stored in the Plonk array you can use getArray() or cast the Plonk array to a pointer to the array's value type:

 Ints a (1, 2, 3);
 
 // all of these are equivalent
 int* ptr1 = a.getArray();           // explicit getArray()
 int* ptr2 = static_cast<int*> (a);  // explicit cast, calls getArray() internally 
 int* ptr3 (a);                      // implicit cast, calls getArray() internally
 int* ptr4 = a;                      // implicit cast, calls getArray() internally
 int* ptr5 = (int*)a;                // should work, but C casts are not recommended in C++

It is best not to store any of the pointers obtained this way as the array could be reallocated if it is modified (e.g., by adding items). If you are iterating through an array this may be faster than repeated calls to atUnchecked() or the [] operator.

Channels, units and graphs

Channels, units and graphs are concepts used to represent audio processing configurations in Plonk.

In fact in Plonk there is no separate "graph" object, rather graphs are built by passing units to other units during construction. There are channel objects (the ChannelBase<SampleType> class) although you rarely deal with these directly in user code. Most user code will create and manipulate unit objects (via the UnitBase<SampleType> class). By default Plonk is setup to deal with 32-bit floating point data throughout its graphs. This is achieved by the macro PLONK_TYPE_DEFAULT which is set to 'float'. This macro is used to set the default type for the UnitBase class and the unit factory classes. A NumericalArray<PLONK_TYPE_DEFAULT> is typedef 'd to Buffer (thus a Buffer is by default the same as a Floats or FloatArray object). Another convention is for the base types (ChannelBase, UnitBase) to become simply 'Channel' and 'Unit' for the default sample type. For factory classes, these are generally names like SomethingUnit, which becomes simply 'Something' for the default sample type.

For example, the SineUnit factory class is used to create wavetable-based sine wave oscillators. To create one using the default sample type you would do this:

 Unit u = Sine::ar (1000, 0.1);

This creates a single channel 1kHz sine oscillator unit at an amplitude of 0.1 and stores it in the variable 'u' (of type Unit). The 'ar' factory function name is common throughout the factory classes and denotes 'audio rate' in a similar way to other Music-N languages (Csound, SuperCollider etc). This means it will output one sample for each sample required by the audio hardware. It is possible to create 'control rate' units too although this will be discussed later (in fact there isn't really a global notion of 'control rate' as there is in other Music-N langauges as Plonk channels can run at arbitrary sample rates and block sizes).

To create a non-bandlimited sawtooth with amplitude 0.1 and frequency 1kHz you would use:

 Unit u = Saw::ar (1000, 0.1);

The arguments, or 'inputs', for the factory functions are documented in the factory class documentation. For example, SineUnit documentation states:

Factory functions:
  • ar (frequency=440, mul=1, add=0, preferredBlockSize=default, preferredSampleRate=default)
  • kr (frequency=440, mul=1, add=0)
Inputs:
  • frequency: (unit, multi) the frequency of the oscillator in Hz
  • mul: (unit, multi) the multiplier applied to the output
  • add: (unit, multi) the offset aded to the output
  • preferredBlockSize: the preferred output block size (for advanced usage, leave on default if unsure)
  • preferredSampleRate: the preferred output sample rate (for advanced usage, leave on default if unsure)

The 'mul' and 'add' inputs are common to many factory classes these are applied after the output of the unit at full ampltitude. The default for float and double sample types is ±1. Thus Sine::ar(1000) would output a full scale sine wave at 1kHz. The 'add' input can be used to mix a unit with the output of another unit. Depending on which optimisations are turned on this may be the most efficient way of mixing signals (if not the most convenient). The 'add' input is also useful for creating modulators. E.g.,

 Unit u = Sine::ar (Sine::ar (5, 100, 1000), 0.1);

Here the 5Hz sine is used to modulate another sine. This could be rewritten a follows:

 Unit m = Sine::ar (5, 100, 1000);
 Unit u = Sine::ar (m, 0.1);

(Both of these versions produce identical graphs, the processing required to render either graph will be exactly the same. The latter version may be a fraction slower to construct but most compilers should be able to optimise.)

The 'mul' input for 'm' is 100, this is effectively the modulation depth. The 'add' input is 1000, this is effectively the centre value for the modulation. Thus the modulator will have a centre value of 1000 and deviate by ±100 (i.e., 900...1100). The unit 'm' is then used as the frequency input for the unit 'u'. Any input marked as 'unit' can be any other unit. Units can be created explicitly using the factory classes, or in some cases implicitly from constants, Variable types, NumericalArray types and so on. Inputs marked as 'multi' can be used to create multichannel units by passing in a multichannel unit to this input. For example, the following code creates a two-channel unit, both with amplitude 0.1 but one with frequency 995Hz and the other 1005Hz:

 Unit u = Sine::ar (Floats (995, 1005), 0.1);

Or alternatively:

 Unit u = Sine::ar (Unit (995, 1005), 0.1);

This is equivalent to:

 Unit u = Unit (Sine::ar (995, 0.1), Sine::ar (1005, 0.1));

Similarly, multichannel units can be passed into all inputs labelled 'multi'. The following code creates two sines with frequencies as above but with different amplitudes too:

 Unit u = Sine::ar (Floats (995, 1005), Floats (0.09, 0.11));

This is equivalent to:

 Unit u = Unit (Sine::ar (995, 0.09), Sine::ar (1005, 0.11));

Running processes and 'audio hosts'

Plonk is not tied to any particular platform or audio IO system. Plonk does provide some built-in "audio hosts" which interface with commonly available IO APIs. For example, there are audio hosts for PortAudio (PortAudioAudioHost), iOS (IOSAudioHost) and Juce (JuceAudioHost). It's reasonably trivial to add hosts, each derives from the AudioHostBase class.

In each case the audio host class communicates with the host API. As a user you simply need to inherit from the desired audio host, implement the constructGraph() function (see below) and call startHost().

The constructGraph() function needs to return the graph (i.e., a unit) that contains the audio processing algorithm you want to play. For example, here is a PortAudio-based example:

 class AudioHost : public PortAudioAudioHost
 {
 public:
    AudioHost()
    {
        // ..DO NOT call startHost() here.. 
    }
    
    ~AudioHost();
    {
    }
    
    Unit constructGraph()
    {
        Unit u = Sine::ar (1000, 0.1);
        return u;
    }
 };

Then you need to create an instance of this class and call its startHost() function. For a naive implementation could be:

 int main(int argc, char *argv[])
 {
    AudioHost host;
    host.startHost();
 
    while(true) // pause forever..
        Threading::sleep (0.001);
 
    return 0;
 }

In reality, you would probably use your AudioHost class within the context of an event driven system (e.g., on Mac OS X and iOS you could store your AudioHost in your AppDelegate class as per the macplnk and iosplnk examples).

In each of the examples below you would be able to return the 'u' variable from a constructGraph() function as above.

Implicit arithmetic units

Arithmetic can be performed on units in an intuitive fashion. For example, the following lines are all equivalent to each other:

 Unit u = Sine::ar (Floats (995, 1005), 0.1);         // use the mul input
 Unit u = Sine::ar (Floats (995, 1005)) * 0.1;        // explicit multiply
 Unit u = Sine::ar (Floats (995, 1005)) * Unit (0.1); // explicit multiply and explicit unit creation

Similarly, the following lines are also equivalent to each other:

 Unit u = Sine::ar (Floats (995, 1005), Floats (0.09, 0.11));
 Unit u = Sine::ar (Floats (995, 1005)) * Floats (0.09, 0.11);
 Unit u = Sine::ar (Floats (995, 1005)) * Unit (0.1, 0.11);

Mixing can be performed using the addition operator:

 Unit u = Sine::ar (200, 0.1) +
          Sine::ar (400, 0.1 / 2) +
          Sine::ar (600, 0.1 / 3) +
          Sine::ar (800, 0.1 / 4);

(This creates the first four harmonics of a sawtooth wave.)

Ring modulation can be achieved using the '*' operator too:

 Unit u = Sine::ar (1200) * Sine::ar (1000) * 0.1; // 2200 and 200 Hz will be heard at 0.1 amplitude (1200+1000 & 1200-1000)

As can amplitude modulation:

 Unit u = Sine::ar (1000) * Sine::ar (0.5, 0.1, 0.1);

Here the amplitude modulation is at a rate of 0.5Hz and the 'mul' and 'add' of 0.1 scales and shifts the sine wave to fit betweem 0...0.2. Alternatives to this are:

 Unit u = Sine::ar (1000, Sine::ar (0.5, 0.1, 0.1));
 Unit u = Sine::ar (1000, Sine::ar (0.5).linlin (-1, 1, 0.0, 0.2));
 Unit u = Sine::ar (1000, Sine::ar (0.5).linlin (0.0, 0.2));

Here the latter two versions use the 'linlin' function which maps one linear range onto another. With all four arguments, the first two are the input range of the source unit (-1, 1) and the second two are the desired output range (0.0, 0.2). As most units' use a default output range (i.e., ±1 for float and double sample types) only the output range needs specifying (i.e., the last of the three forms).

Plonk units also support unary operators such as standard trig functions (sin, cos, etc) and some useful coverters (for dBs, MIDI note numbers, etc). For example, an inefficient way to synthesise a sine wave would be:

 const float pi = FloatMath::getPi();  // Math<Type> contains some useful constants
 Unit p = Saw::ar (1000, pi);          // phasor ±pi (i.e., ±180° in radians)
 Unit u = sin (p) * 0.1;               // call sin() on the phasor and scale

The sin() function can be called like this or in "message passing" format when called on Plonk units or arrays but not built-in types. This can often make reading a chain of arithmetic functions easier):

 const float pi = FloatMath::getPi();  // Math<Type> contains some useful constants
 Unit p = Saw::ar (1000, pi);          // phasor ±pi (i.e., ±180° in radians)
 Unit u = p.sin() * 0.1;               // call sin() on the phasor and scale

Coversions from MIDI note to frequency in Hertz is often useful:

 Unit u = Sine::ar (m2f (69), 0.1); // MIDI note 69 = 440Hz

This also works on units, of course, where message passing format can be used:

 Unit f = Sine::ar (0.5).linlin (60, 72).m2f(); // sweep between notes 60 and 72 i.e., middle-C to the next octave
 Unit u = Sine::ar (f, 0.1);

For a similar sweep with discrete steps round() can be used to quantise the MIDI not numbers to integers before conversion to Hz:

 Unit f = Sine::ar (0.5).linlin (60, 72).round (1).m2f();
 Unit u = Sine::ar (f, 0.1);

Similar coversions are available from frequency back to MIDI notes (f2m) and between dB and linear amplitude (dB2a and a2dB) e.g,:

 Unit u = Sine::ar (1000, dB2a (-18)); // -18dB is amplitude 0.125

A simple non-bandlimited square wave can be created using the comparison operators e.g.,:

 Unit u = (Saw::ar (200) > 0).linlin (0, 1, -0.1, 0.1);

The comparison operator units output either 0 or 1 (corresponding to false and true). Here, when the sawtooth wave is greater than zero the comparison results in '1' and when is is zero or below it outputs '0'. Thus a square wave is created, this is then mapped to ±0.1.

A pulse wave can be created by comparing against a value other than zero. This can even be modulated as in the example below, to achieve pulse width modulation:

 Unit m = Sine::ar (0.5, 0.25);
 Unit u = (Saw::ar (200) > m).linlin (0, 1, -0.1, 0.1);

See the UnitBase for full list of supported operators and arithmetic functions.

Mixing

Returning to techniques for mixing units, in most cases it is more straightforward and efficient to use the MixerUnit rather than chains of '+' operators:

 Unit u = Mixer::ar (Sine::ar (Floats (200, 400, 600, 800), 
                               Floats (0.1, 0.1/2, 0.1/3, 0.1/4)));

Or perhaps more clearly:

 Unit s = Sine::ar (Floats (200, 400, 600, 800), 
                    Floats (0.1, 0.1/2, 0.1/3, 0.1/4));
 Unit u = Mixer::ar (s);

Or:

 Unit u = Sine::ar (Floats (200, 400, 600, 800), 
                    Floats (0.1, 0.1/2, 0.1/3, 0.1/4)).mix();

Thus Mixer and Unit::mix() mix down multiple channels to a single channel.

Mixing multiple channels is as straightforward but requires the use of arrays of units i.e. a Units or UnitArray. Take the following example:

 Unit a = Sine::ar (Floats (200, 600), Floats (0.1, 0.1/3));
 Unit b = Sine::ar (Floats (400, 800), Floats (0.1/2, 0.1/4));
 Unit u = a + b;

Here 200Hz and 400Hz are mixed in one channel and 600Hz and 800Hz are mixed in another channel. Using Mixer this could be:

 Unit a = Sine::ar (Floats (200, 600), Floats (0.1, 0.1/3));
 Unit b = Sine::ar (Floats (400, 800), Floats (0.1/2, 0.1/4));
 Units us = Units (a, b);
 Unit u = Mixer::ar (us);

Clearly in this case this results in more code but with larger numbers of units to mix this can be more convenient.

An equivalent would be:

 Units us;
 us.add (Sine::ar (Floats (200, 600), Floats (0.1, 0.1/3)));
 us.add (Sine::ar (Floats (400, 800), Floats (0.1/2, 0.1/4)));
 Unit u = Mixer::ar (us);

This technique allows you to build arrays of units using conventional C++ loops:

 Units us;
 
 for (int i = 0; i < 4; ++i)
 {
    us.add (Sine::ar (Floats::exprand (2, 100, 1000), 
                      Floats::rand (2, 0.05, 0.1)));
 }
 
 Unit u = Mixer::ar (us);

Oscillators and generators

See the oscillator and noise unit documentation for a list of available unit factories. These include sine, saw, square oscillators and white noise.

Audio rate and control rate

As mentioned previously, most Music-N style langauges have a notion of "audio rate" (where signals are calculated every hardware sample "tick"); and, "control rate" (where some signals are calulcated only every few samples). This can often lead to more efficient processing although care needs to be taken to achieve similar audio quality since it is easy to introduce discontinuities into the signal path when not performing the calculations on every sample. Take for example a hardware sampling rate of 44100Hz, a typical "control rate" might be every 64 samples; this gives a control rate of 44100/64 = 689.0625Hz. All channels in Plonk have their own sample rate, thus an audio rate channel might run at 44100Hz and a control rate channel might run at a lower frequency. It wouldn't make sense to run many, many different rates but in theory it is possible in Plonk.

Closely linked to sample rate is the processing "block size". Rather than processing one sample from each channel in a Plonk graph before moving onto the next sample, it is common to process a block of samples using the same process then apply another process to that (and so on). This makes all sorts of optimisations possible (either manually or by clever compiler tricks that we never see). In Plonk all channels can run at differnt block sizes.

The default sample rate in Plonk is intially 44100Hz, although this is changed very soon after starting an audio host if the hardware sample rate is different. Similarly, the default block size is 512 samples, although this may also be changed by an audio host to match the hardware block size. Also by default the "control rate" is governed by the default block size so in fact the default control rate is 44100/512 or around 86Hz.

These defaults are stored in two specially adapted Variable classes: SampleRate (which is as special kind of Variable<double>); and, BlockSize (which is a special kind of Variable<int>).

If you need to, these values can be obtained:

 double sr = SampleRate::getDefault().getValue();
 int bs = BlockSize::getDefault().getValue();

Here is an example where control rate can be appropriately used (based on an example above where all units/channels were audio rate):

 Unit f = Sine::kr (0.5).linlin (60, 72).m2f(); // use a control rate 'kr' modulator
 Unit u = Sine::ar (f.ar(), 0.1);               // resample modulator to audio rate

Here the 'linlin' code and the 'm2f' code (which in fact contains some expensive mathematical functions) are only called once every control rate 'tick'. In fact, the default here is once every block. This is then resampled before being used as the modulator in the second Sine in order to remove discontinuities. A quick test of this example showed it was 40% faster to use the control rate in this way, rather than using an audio rate modulator that is calling 'm2f' every sample.

[todo]

Filters

The underlying code for the filter units can look daunting but the classes designed to be used in user code are straightforward. These are as follows:

You need to view each filter's documentation for the inputs each accepts but in general the first input is the signal to filter. For example, to apply a low-pass filter to a sawtooth wave you could do the following:

 Unit s = Saw::ar (200, 0.1);
 Unit u = LPF::ar (s, 400); // cut-off at 400Hz

Of course, the cutoff frequency can be modulated, here with a resonant low-pass:

 Unit s = Saw::ar (200, 0.1);
 Unit m = Sine::ar (1).linexp (400, 1600); // exponential map ±1 to 400...1600
 Unit u = RLPF::ar (s, m, 5);              // Q-factor of 5

Here linexp() is the equivalent of linlin() but the output range is exponential. This means in the example above 0 maps to 800, this makes the musical interval the same for both the negative and positive phases of the input sine wave. Here -1..0 maps to 400..800 which is an octave and 0..+1 maps to 800..1600 which is also an octave. (If linlin() had been used then 0 would have mapped to the linear half way point between 400 and 1600 i.e., 1000.) However, linexp() is more CPU intensive so linlin() may be a useful compromise in some circumstances even when exponential mapping is more appropriate.

In addition to the audio filters, LagUnit is useful for smoothing out discontinuities in control signals (e.g., from a slider). For example:

 Unit f = Sine::ar (0.1).linlin (60, 72).round (1).m2f();
 Unit u = Sine::ar (Lag::ar (f, 0.075), 0.1);

Here the quantised control signal (from an example earlier) is rounded off slightly to give a gentle portamento effect (it takes 0.075s to glide to the desired note).

Sound files

Plonk inlcudes range of sound file reading routines. It currently supports WAV and AIFF (and AIFC) by default and optionally Ogg Vorbis and Opus formats (by adding the appropriate files from the /ext directory and enabling the relevant preprocessor macro).

Sound files may be read into a memory and played back from there or streamed from disk. In either case the AudioFileReader class does the work of reading and decoding the sample data.

 AudioFileReader reader ("/path/to/file.wav");
 if (reader.isReady())
 {
    // do something with the file 
 }

An entire sound file can be read into a Signal object. This can then be played back using the SignalPlay unit.

 AudioFileReader reader ("/path/to/file.wav");
 Unit u;
 if (reader.isReady())
 {
    Signal signal = reader.getSignal();
    u = SignalPlay::ar (signal);
 }

Delay

[todo]

FFT processing

[todo]

 All Classes Functions Typedefs Enumerations Enumerator Properties