Faust: A Simple Envelope

Temporal envelopes are essential for many sound synthesis applications. Often, triggered envelopes are used, which can be started by a single trigger value. Faust offers a selection of useful envelopes in the envelopes library. The following example uses an attack-release envelope with exponential trajectories which can be handy for plucked sounds. The output of the sinusoid with fixed frequency is simply multiplied with the en.arfe() function.

Check the envelopes in the library list for more information and other envelopes:

https://faust.grame.fr/doc/libraries/#en.asr

// envelope.dsp
//
// A fixed frequency sine with
// a trigger and controllable release time.
//
// - mono (left channel only)
//
// Henrik von Coler
// 2020-05-07

import("stdfaust.lib");

// a simple trigger button
trigger  = button("Trigger");

// a slider for the release time
release  = hslider("Decay",0.5,0.01,5,0.01);

// generate a single sine and apply the arfe envelope
// the attack time is set to 0.01
process = os.osc(666) * 0.77 * en.arfe(0.01, release, 0,trigger) : _ ;

Additive & Spectral: Parabolic Interpolation

Quadratic Interpolation

The detection of local maxima in a spectrum is limited to the DFT support points without further processing. The following example shows this for a 25 Hz sinusoid at a sampling rate of 100 Hz.

Quadratic or parabolic interpolation can be used to estimate the true peak of the sinusoid. using the detected maximum $a$ and its upper and lower frequency bin.

$p = 0.5 (\alpha-\gamma)/(\alpha-2\beta+\gamma)$

$a^* = \beta-1/4(\alpha-\gamma)$

More details on JOS Website

The Simple Mesh

The Simple Mesh is a group exercise to create a fully connected mesh audio network, using JackTrip:

/images/mis/mesh_1.png

Four Access Points in a fully connected mesh.

In this configuration, audio signals will be sent to every node automatically.


Step 1: Launch a Hub Server on each AP

The JackTrip connections for the ring are all based on the Hub-Mode - make sure to use capital Letter arguments to create Hub instances (-S and -C). After starting a Jack server, we can launch a JackTrip Hub Server. This process will keep running and will wait for clients to connect:

jacktrip -S

Step 2: Launch Hub Clients on each AP

If N is the total number of Access Points in the system,each PI needs a connection to N-1 peers. We do that by lauchching an individual number of JackTrip clients on each PI - this avoids duplicate connections. First steps:

  • Find the IP addresses and hostnames of all peers.

  • Use a single channel connection (-n 1).

  • Use arguments -J and -K to set the Jack client names on your own, respectively the peers machine.

If we start connecting the PIs starting with the first one in the list (e.g. student1, student2, student3, ...), each PI needs to establish one connection less than the previous one: student1 needs to start JackTrip clients other PIs, resulting in N-1 clients. student2 does not need to connect to student1 actively and has to launch N-2 clients. The last PI in the list does not need to launch any JackTrip connection.

Port Offset

Each JackTrip client we start needs an individual UDP port number (servers do not have this issue). The Bind Port Number (the one used on the local machine) can be set with the -B flag. The default port is 4464. It can be chosen "randomly" or incremented by one for each new JackTrip client we launch.

Connection Example

Assuming we have 12 PIs (stutent1 ... student12), this would be the list of commands to launch all JackTrip clients from student8. It is a total of four clients - students 1-7 have already connected to this one one themselves:

jacktrip -C 10.10.10.101 -n 1 -B 4465 -J student9  -K student8 &
jacktrip -C 10.10.10.103 -n 1 -B 4466 -J student10 -K student8 &
jacktrip -C 10.10.10.104 -n 1 -B 4467 -J student11 -K student8 &
jacktrip -C 10.10.10.105 -n 1 -B 4468 -J student12 -K student8 &
...

This first version will make direct connections between hardware inputs/outputs and JackTrip clients. A signal from a single PI is automatically sent to all other Access Points.

Host Discovery with Nmap

Host Discovery with Nmap

Nmap can be used to find all active clients in a network. It can be used in many different ways - this set of arguments works best for a local area network with several PIs.

nmap -sP 10.10.10.1/24

For a network with several PIs connected, the output will look like this:

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-10 10:24 EDT
Nmap scan report for 10.10.10.1
Host is up (0.11s latency).
Nmap scan report for 10.10.10.83
Host is up (0.00011s latency).
Nmap scan report for 10.10.10.101
Host is up (0.055s latency).
Nmap scan report for 10.10.10.102
Host is up (0.055s latency).
Nmap scan report for 10.10.10.103
Host is up (0.055s latency).
Nmap scan report for 10.10.10.104
Host is up (0.0033s latency).
Nmap scan report for 10.10.10.105
Host is up (0.0033s latency).
Nmap scan report for 10.10.10.106
Host is up (0.0032s latency).
Nmap scan report for 10.10.10.107
Host is up (0.0046s latency).
Nmap scan report for 10.10.10.108
Host is up (0.0046s latency).
Nmap scan report for 10.10.10.109
Host is up (0.0046s latency).
Nmap scan report for 10.10.10.112
Host is up (0.049s latency).
Nmap done: 256 IP addresses (12 hosts up) scanned in 30.59 seconds

UX in Spatial Sound Synthesis

The UEQ

The User Experience Questionnaire (UEQ) is a well established tool for measuring the user experience of interactive systems and products (Laugwitz, 2008). Below are two results - one from a communication tool and one from an expressive musical instrument.

/images/spatial/spatial_synthesis/whatsapp_ueq.png

UEQ results for WhatsApp (Hinderks, 2019).


The UEQ with a novel DMI

/images/spatial/spatial_synthesis/glooo_ueq.png

UEQ results for the GLOOO instrument (von Coler, 2021).


References

2021

  • Henrik von Coler. A System for Expressive Spectro-spatial Sound Synthesis. PhD thesis, TU Berlin, 2021.
    [details] [BibTeX▼]

2019

  • Andreas Hinderks, Anna-Lena Meiners, Francisco José Dom\'ınguez Mayo, and Jörg Thomaschewski. Interpreting the results from the user experience questionnaire (ueq) using importance-performance analysis (ipa). In WEBIST 2019: 15th International Conference on Web Information Systems and Technologies (2019), pp. 388-395. ScitePress Digital Library, 2019.
    [details] [BibTeX▼]

2015

2008

  • Bettina Laugwitz, Theo Held, and Martin Schrepp. Construction and evaluation of a user experience questionnaire. In Proceedings of the 4th Symposium of the Workgroup Human-Computer Interaction and Usability Engineering of the Austrian Computer Society, volume 5298, 63–76. 11 2008. doi:10.1007/978-3-540-89350-9_6.
    [details] [BibTeX▼]

Waveguide with Excitation Input

This example is a first step towards excitation-continuous instruments, such as wind instruments. Instead of initializing the waveguides with a single excitation function, they are fed with an input signal.

// waveguide_input.dsp
//
// waveguide with excitation by input signal
//
// - one-pole lowpass termination
//
// Henrik von Coler
// 2020-11-19

import("all.lib");

// use '(pm.)l2s' to calculate number of samples
// from length in meters:

segment(maxLength,length) = waveguide(nMax,n)
with{
    nMax = maxLength : l2s;
    n = length : l2s/2;
};



// one lowpass terminator
fc = hslider("lowpass",1000,10,10000,1);
rt = rTermination(basicBlock,*(-1) : si.smooth(1.0-2*(fc/ma.SR)));

// one gain terminator with control
gain = hslider("gain",0.5,0,1,0.01);
lt = lTermination(*(-1)* gain,basicBlock);

// a simple allpass (Smith Paper)
s = hslider("s",0.9,0,0.9,0.01);
c = hslider("c",0.9,0,0.9,0.01);
allpass = _ <: *(s),(*(c):(+:_)~(*(-s))):_, mem*c:+;


// another allpass
g = hslider("g",0.9, 0,0.9,0.01);
allp = allpass_comb(2,1,g);


scatter = pm.basicBlock(allpass);



idString(length,pos,excite) = endChain(wg)
with{

    nUp   = length*pos;
    nDown = length*(1-pos);

    wg = chain(lt : segment(6,nUp) : out : in(excite) : scatter : segment(6,nDown) :  rt); // waveguide chain
};

exc = select2(gain>0.9,1,0);

length = hslider("length",1,0.1,10,0.01):si.smoo;

process(in) = idString(length,0.15, in) <: _,_;

Using Arrays in SuperCollider

Simple Arrays

In SC, arrays are collections of objects of any kind. They can be defined and accessed using brackets:

// define simple arrays:
a = [0,1,2,3];
b = [0,1,2,"last_value"];

// access indices:
a[3];

Dynamic Creation

The array class offers numerous methods for creating arrays, including fill():

c = Array.fill(4,{arg i; 10/(i+1) });

Arrays of Buses

Especially in multichannel projects and larger mixing setups, arrays of buses can be helpful. Make sure to boot the server to actually use (scope) the buses:

// an array of 16 buses, each with 4 channels:
~busArray = Array.fill(16,{Bus.control(s, 4)})

// scope the second bus in the array:
~busArray[1].scope

// set the third bus of the second bus in the array:
~busArray[1].setAt(2,0.5);

Array of Nodes/UGens

The same array approach can be used to generate multiple nodes, for example sine waves at different frequencies and amplitudes:

// an array of 16 sine oscillators:
~sineArray = Array.fill(16,{arg i;{SinOsc.ar(200*i)}.play})

Array of Synths

The previous example can also be used with SynthDefs, which is a good starting point for additive synthesis:

// a simple synthdef
(
SynthDef(\sine,
{|f = 100, a = 1|

   Out.ar(0, a * SinOsc.ar(f));

}).send(s);
)

~busArray = Array.fill(16,{arg i;Synth.new(\sine,[f:200*(i+1),a:0.2])})

Envelopes: Exponential

For percussive, plucked or struck instrument sounds, the envelope needs to model an exponential decay. This is very useful for string-like sounds but most importantly for most electronic musicians, it is the very core of kick drum sounds.

In contrast to the ADSR envelope, the exponential one does not contain a sustain portion for holding a sound. The only parameter is the decay rate, allowing quick adjustment. Alternative to an actual exponential, a modified reciprocal function can be used for easier implementation. The factor $d$ controls the rate of the decay, respectively the decay time:

$$ e = \frac{1}{(1+(d t))} $$


The following example adds a short linear attack before the exponential decay. This minimizes clicks which otherwise occur through the rapid step from $0$ to $1$:

Your browser does not support the HTML5 canvas tag

Attack Time:

Decay Time:

Pure Data: Send-Receive & Throw-Catch

Send & Receive

Control Rate

Send and receive objects allow a wireless connection of both control and audio signals. The objects are created with send and receive or short s and r for control rate signals and get one argument - a string labeling the connection.

Local Sends

Prepending a $0- to a send label turns it into a local connection. These are only valid inside a patch and its subpatches but not across different abstractions. The example send-receive-help.pd shows the difference between local and global sends when used in both cases. It relies on the additional abstraction send-receive.pd which needs to be in the same directory:

/images/basics/pd-send-receive.png

Send and receive of control signals with subpatch and abstraction.


The inside of both the subpatch and the abstraction are identical:

/images/basics/pd-send-receive-sub.png

Inside of send-receive and the subpatch.


Audio Rate

Audio send and receives follow the same rules as control ones. They are created with an additional ~, as usual for audio objects. The example send-receive-audio.pd shows the use of these buses:

/images/basics/pd-send-receive-audio.png

Send and receive of audio signals with subpatch and abstraction.


Throw & Catch

Throw and catch are bus extensions of the above introduced send-receive method, only for audio signals. Unlike with s~ and r~, it is possible to send multiple signals to one catch~. This allows a flexible audio routing and grouping without a lot of lines. The example throw-catch.pd throws four sine waves to a common bus for a minimal additive synthesis:

/images/basics/pd-throw-catch.png

Using throw and catch to merge four signals.