Interactive Swarm Space

Basic Audio Input and Synthesis Units

Table of Contents

InputFile Unit
OutputFile Unit
Sample Unit
WaveTableOscil Unit
Noise Unit
DelayUnit (and MultiDelayUnit)


InputFile Unit

Using the InputFile Unit you can read sound files and use them for playback in your ISO Synth Patch. Compatible sound file formats are: AIFF, WAV and RAW. The number of audio channels is equal to the number of audio channels of the used sound file and can be theoretically arbitrarily high. All sample rates and bits per sample (bit rates) are acceptable, as long as the audio hardware in use supports them.

Alternatives:

InputFile will directly stream a file from your hard disk. This is appropriate for realtime situations, but if you want to build a sample player with prerecorded sounds, we advice you to use either SampleUnit or TriggerSampleUnit. While SampleUnit is great for storing a bunch of samples and switch amongst them at runtime (even with crossfades), TriggerSampleUnit is a multi-channel fixed sample unit, that receives a trigger signal on its control port, making it possible to produce audio-timing accurate. This grooves can be generated using the RhythmUnit. For details please check out these classes in our Doxygen documentation.

Control Ports

port name default value
amplitude 1.0
transpose 1.0

transpose: This is a factor which influences the playback speed. So a transpose value of 0.5 leads to half the playback speed, which translates into the audio file being played one octave below its original frequency.

Switch Ports

active 1.0 (= active)
play 1.0 (= play)
looping 0.0 (= off)
playStart 0.0 (= start time)
playEnd file duration

play: trigger a playback routine (1.0) - or stop playback by sending this SwitchPort a value of 0.0.

looping: if this is set to 1.0, the sound file will loop endlessly.

playStart: Marks the absolute time in your sound file at which it will start playback. Neat, if you would like to "scroll around" in your sample material - or remove the attack. However clicking is likely to occur!

playStop: Simultaneous to the behavior of playStart, this switch port marks the time at which the sound file will stop playing. Works as well when "looping" is active.

Example Syntax

InputFile* inputFile = new InputFile("/<directory>/mySound.aiff");

inputFile->set("transpose", 0.5);
inputFile->set("amplitude", 0.8);

inputFile->connect(outputUnit);


OutputFile Unit

ISOSynth makes it very easy to record the audio output of your patch to a file - or the output of any of your units for that matter. Just keep in mind that the operation of writing to your disk might decrease the patch's performance considerably, sometimes even introducing drop-outs (we keep working on that). However these dropouts will not be present in the audio file! OutputFile will start writing to disk as soon as the synth is started - remember the Synth::get().start(); method from the Introduction chapter?

Example Syntax

Noise* exampleUnit = new Noise();
OutputFile* fileOutUnit = new OutputFile("/<directory>/mySound.aiff"); // where to save
                                                                       // the recording

exampleUnit->connect(outputUnit); //play the exampleUnit unit
exampleUnit->connect(fileOutUnit); //write the exampleUnits audio stream to disk


Sample Unit

This unit is similar to the InputUnit for the fact that its main purpose is the playback of audio files (samples). However SampleUnit is much more powerful than the InputUnit, because it can be assigned an arbitrary number of audio files to choose from. A SwitchPort called "sample" will let you choose at runtime which of these samples should be playing. To avoid clicks, a further SwitchPort "sampleFadeDuration" will let you specify a crossfade time span. Please keep in mind, that you shouldn't use this crossfade feature if your sample material contains strong attacks because in the crossfade process the beginning of a sample is being faded in to avoid amplitude clipping. For the same reason the SwitchPort "loopFadeDuration" should be used with caution as well. It serves the purpose of creating smoother loops. There are two situations where this comes in handy: 1) Your original sound file is not prepared to be loopable (e.g. doesn't start and end with a zero-crossing) and 2) you would like to shift start and end points during playback using the "playStart" and "playEnd" SwitchPort. Shifting this edge points will not snap them to the position of the nearest zero-crossing!

Control Ports

port name default value
amplitude 1.0
transpose 1.0

transpose: This is a factor which influences the playback speed. So a transpose value of 0.5 leads to half the playback speed, which translates into the audio file being played one octave below its original frequency.

Switch Ports

sample 0.0 (= first sample file)
active 1.0 (= active)
play 1.0 (= play)
looping 0.0 (= off)
loopFadeDuration 0.0 (= crossfade time in ms)
sampleFadeDuration 0.0 (= crossfade time in ms)
playStart 0.0 (= start time)
playEnd file duration

play: set active/inactive

looping: if this is set to 1.0, the sound file will loop endlessly.

loopFadeDuration: crossfade time in ms used when looping

sampleFadeDuration: crossfade time in ms used when changing from one sample file to another.

playStart: Marks the absolute time at which the sound file will start playing

playStop: Simultaneous to the behavior of playStart, this switch port marks the time at which the sound file will stop playing.

Example Syntax

SampleUnit* sample = new SampleUnit("/<directory>/mySound.aif");
sample->addSample("/<directory>/anotherSample.aif");

sample->set("transpose", 0.5);
sample->set("amplitude", 0.8);
sample->set("sampleFadeDuration", 20.0); //20 ms crossfade
sample->connect(outputUnit);


WaveTableOscil Unit

This wavetable oscillator can be used together with a set of default wavetables ("sinewave", "squarewave", "sawtoothwave") or with a user defined wavetable which can be read from a local text file. Per default, there is no interpolation applied. However if you need linear interpolation for your wavetable, add "LINEARINTERPOL" to your constructor arguments of the WaveTableOscil constructor.

There are two child units of the WaveTableOscil, BLPulseGen ("band-limited pulse generator") and DCSPulseGen ("dynamically controlled spectrum pulse generator").

Control Ports

frequency any positive value (Hertz) is possible
amplitude any value is possible! Look out for clipping
phase 0.0 (between 0.0 and 1.0)
offset any offset it possible

frequency: cycles per second, please note that frequencies beyond half of your sample rate will create aliasing (nyquist theorem).

phase: the default setting of 0.0 translates into no phase shift at all. 1.0 translates into a full cycle of phase shift (which of course is equivalent to no shift at all). Only positive values for phase are allowed!

offset: dc offset value, 0.0 (default) means no offset.

Example Syntax

WaveTableOscil* osc1 = new WaveTableOscil("sinewave"); //built-in
WaveTableOscil* osc2 = new WaveTableOscil("mywavetable",
                                          "/<directory>/mytable.txt",
                                          LINEARINTERPOL); //user defined wavetable with
                                                           // linear interpolation

osc1->set("frequency", 440.0);
osc1->set("amplitude", 0.8);
osc2->set("frequency", 1200.0);
osc2->set("amplitude", 0.6);

osc1->connect(outputUnit);
osc2->connect(outputUnit);


Noise Unit

This unit creates white noise and thus a uniform distribution in the frequency spectrum.

There are three alternatives to the Noise unit, namely NoiseH ("sample and hold noise") , NoiseI ("interpolated sample and hold noise") and StochasticNoise ("stochastically distributed noise"). These will be described later.

Control Ports

frequency (only for NoiseH and NoiseI) 440.0 (Hz)
amplitude 1.0
offset 0.0

frequency: Only applicable to NoiseH and NoiseI! cycles per second, please note that frequencies beyond half of your sample rate will create aliasing (nyquist theorem).

phase: the default setting of 0.0 translates into no phase shift at all. 1.0 translates into a full cycle of phase shift (which of course is equivalent to no shift at all). Only positive values for phase are allowed.

offset: dc offset value, 0.0 (default) means no offset. -1.0 means maximal negative offset, 1.0 means maximal positive offset. Please note that offsetting a wavetable can lead to audible clicks when switch the table on and off

Example Syntax

Noise* whiteNoise = new Noise();


whiteNoise->connect(outputUnit);


Alternative Noise Units (PinkNoise, NoiseH, NoiseI and StochasticNoise)

PinkNoise

PinkNoise is also called 1/f Noise, meaning higher frequencies will have lower amplitudes. Pink Noise has no additional Control- or SwitchPorts.

NoiseH

NoiseH creates "sample and hold" noise, it creates new random samples at regular intervals. During the intervals, the sample doesn't change.

sample and hold noise
sample and hold noise

Control Ports

frequency (only for NoiseH and NoiseI) 440.0 (Hz)
amplitude 1.0 (max)
offset 0.0 (dc offset, max = +/- 1.0)

NoiseI

NoiseI also creates "sample and hold" noise but it interpolates between the random samples. Likewise a frequency control value defines the rate at which new samples are set as interpolation targets.

Interpolated Noise
interpolated sample and hold noise

Control Ports

frequency (only for NoiseH and NoiseI) 440.0 (Hz)
amplitude 1.0 (max)
offset 0.0 (dc offset, max = +/- 1.0)

StochasticNoise

Unit that distributes its noise according to a density function - also known as probability distribution. This density function is passed to the StochasticNoise unit as an array of sample values representing the edge points of a piece-wise linear distribution. Creating such an array and passing it to the StochasticNoise unit can be done like this:

sample* distribution = new sample[4]; 

distribution[0] = 0.33333;
distribution[1] = 1.0;
distribution[2] = 0.1;
distribution[3] = 0.0;

StochasticNoise* noise = new StochasticNoise(new math::Vector<sample>(4, distribution));
noise->connect(outputUnit);

Here is what the resulting sound wave might look like.

Stochastic Noise
stochastic noise fitting a piece-wise linear distribution


DelayUnit (and MultiDelayUnit)

The delay unit will, all but surprisingly, play back a received audio signal with a specified delay.

Control Ports

delay 0.0 (delay time in ms)
scale 1.0 (amplitude scale factor)

delay: default delay time is 0.0 ms (no delay).

phase: the default scale factor is 1.0 - the received audio signal by the delay unit will be scaled with this factor.

  • 0.0 < scale < 1.0: the delayed signal will have a lower amplitude than the original
  • scale = 1.0: the delayed signal will have the same amplitude as the original
  • scale > 1.0 the delayed signal will have a higher amplitude (look out for clipping!)
  • negative scale factor: the delayed signal will be phase-inverted.

When you construct a DelayUnit, you have to call its constructor with the ringbuffer size argument. It specifies the size of the buffer (in samples) and therefore the maximal delay length:

DelayUnit* myDelayUnit = new DelayUnit(88200);
      // buffer size = 88200 samples == 2 second

Here, a ringbuffer size of 2*44100 corresponds to exactly two second, because the default sampling rate is 44.1 kHz. As with most other units, you can also provide an argument that depicts the number of channels in this unit:

DelayUnit* myStereoDelayUnit = new DelayUnit(2, 44100); 
     // 2-channel delay unit, 1 second buffer size

From what you know so far, we believe it is not necessary to write a complete sample code here. Instead we will go one step further and provide you with the syntax of an other unit that makes itself use of several DelayUnits, the MultiDelayUnit. The following patch attempts to simulate the sound of an object bouncing off a surface several times. Each time the object hits the surface it looses momentum due to friction, thus decreasing both amplitudes and time intervals between each hit. This patch shows, that a unit can in fact contain other units by acting as a manager to them. In this MultiDelayUnit example, the manager unit makes sure that all contained DelayUnits use the same buffer (a ring buffer) and mixes all delayed signals together. The control ports of the contained DelayUnits are accessible using the following port names:

delay0, delay1, delay2, ..., delayN

scale0, scale1, scale2, ..., scaleN

where N corresponds with the number of taps (delays) used in your MultiDelayUnit.

Example Syntax

InputFile* inputFile = new InputFile("/<directory...>/tom.aif"); // short percussive sound
MultiDelayUnit* mulDelayUnit = new MultiDelayUnit(1, 100000, numDelays);
  // max delay roughly 2.5 secs

int numDelays = 13; //augment this for longer trails

inputFile->setLooping(1.0f);
mulDelayUnit->setControlType(FRAMECONTROL);

// calculate and set individual delay times and amplitudes
float initialDelay = 300.0f;
float tempDelay = 0.0f; // initial delay time
float tempScale = 0.8f; // initial amplitude
for (float d = 0.0f; d < numDelays; d+=1.0f) {
	tempDelay = tempDelay + (initialDelay / (d+1.0f));
	      // increase delay by an interval that's becoming progressively smaller
	mulDelayUnit->set(String("delay%1").arg(d), tempDelay);
	      // set "delay0" to delay13" to this delay time (tempDelay)
	mulDelayUnit->set(String("scale%1").arg(d), tempScale*=0.8f);
	      // set "scale0" to "scale13", each time tempScale gets multiplied by 0.8
}

inputFile->connect(mulDelayUnit);
mulDelayUnit->connect(m_output); 

Instead of setting all delay time and scale control values individually, we used a for-loop to set them. Each iteration, tempDelay adds an interval to its previous value that gets progressively smaller. Amplitude gets smaller each time too, but it is in fact so simple to decrease this value, that we can do it right at the spot where we set the scale control value. tempScale*=0.8f is just a short notation for: tempScale = tempScale * 0.8f which translates into: Decrease tempScale by the factor 0.8 each time you read this instruction, and because we are inside a loop, tempScale will get smaller and smaller. You might have been confused by the following argument: String("scale%1").arg(d). This argument will compose a string that will replace the placeholder %1 with the value of the argument variable d, which happens to be the loop increment index (which iterates from 0 to 13, the number of internal delay units used). So if d = 5 (6th iteration), String("scale%1").arg(d) will produce the String "scale5". Using this dynamic String composition along with setting the delay times and amplitudes accordingly, we can with no effort create a multidelay that contains 200 or much more delay units.


Last updated: May 15, 2024