# Using YAML for Configuration

At some point, a program might require more arguments on startup than a command line call can manage without complications. In that case it can help to work with configuration files, which are passed to the binary when starting it. This can be done in many ways, including plain text files or markup languages like XML, Markdown, JSON or others.

The examples in this module use YAML for passing configuration data. YAML is human-readable and easy to edit, which allows quick changes to configurations.

## The YamlMan Class

The YAML manager class YamlMan is used in all following examples which make use of YAML for parsing configuration parameters in adapted versions.

### The Constructor

The constructor of the class is passed a file path to a YAML file as argument:

YamlMan::YamlMan(std::string filepath)


The following behavior is specific for each example, storing the values from the YAML file to member variables:

YAML::Node config = YAML::LoadFile(filepath);

// access YAML nodes:
param1 = config["param1"].as<std::string>();
param2 = config["param2"].as<int>();
param3 = config["param3"].as<double>();


# Simple Waveguides in C++

Waveguides can be used to model acoustical oscillators, such as strings, pipes or drumheads. The theory of digital waveguides is covered in the sound synthesis introduction.

## The WaveGuide Class

The WaveGuide class implements all necessary components for a waveguide string emulation. Inside the class, the actual waveguide-buffers are realized as pointers to double arrays.

From WaveGuide.h:

/// length of the delay lines
int     l_delayLine;

/// leftward delay line
double  *dl_l;

/// rightward delay line
double  *dl_r;


The pointer arrays need to be initialized in the constructor of the WaveGuide class.

From WaveGuide.cpp:

dl_l = new double[l_delayLine];
dl_r = new double[l_delayLine];

for (int i=0; i<l_delayLine-1; i++)
{
dl_l[i] = 0.0;
dl_r[i] = 0.0;
}


## Plucking the String

The method void WaveGuide::excite(double pos, double shift) is called for plucking the string, respectively exciting it. It receives a plucking position and a shift parameter. In two loops, the method fills the two waveguides with the excitation function. In this example, a simple triangular function is chosen.

From WaveGuide.cpp:

// set positive slope until plucking index
for (int i=0; i<idx; i++)
{
dl_l[i] = 0.5* ((double) i / (double)(idx));
dl_r[i] = 0.5* ((double) i / (double)(idx));
}
// set negative slope from plucking index to end
for (int i=idx; i<l_delayLine; i++)
{
dl_l[i] = 0.5*(1.0 - ((double) i / (double) (l_delayLine-idx)));
dl_r[i] = 0.5*(1.0 - ((double) i / (double) (l_delayLine-idx)));
}


# Karplus-Strong in C++

The Karplus-Strong algorithm is a proto-physical model. The underlying theory is covered in the Karplus-Strong Section of the Sound Synthesis Introduction. Although the resulting sounds are very interesting, the Karplus-Strong algorithm is easy to implement, especially in C/C++. It is based on a single buffer, filled with noise, and a moving average smoothing.

## The Noise Buffer

Besides the general framework of all examples in this teaching unit, the karplus_strong_example needs just a few additional elements, defined in the classs header:

// the buffer length
int l_buff = 600;

// the 'playback position' in the buffer
int buffer_pos=0;

/// noise buffer
double  *noise_buffer;

/// length of moving average filter
int l_smooth = 10;

// feedback gain
double gain =1.0;


Note that the pitch of the resulting sound is hard-coded in this example, since it is based only on the sampling rate of the system and the buffer length. In contrast to the original Karplus-Strong algorithm, this version uses an arbitrary length for the moving average filter, instead of only two samples. This results in a faster decay of high frequency components.

## Initializing the Buffer

Since the noise buffer is implemented as a pointer to an array of doubles, it first needs to be allocated and initialized. This happens in the constructor of the karplus_strong_example class:

// allocate noise buffer
noise_buffer = new double [l_buff];
for (int i=0; i<l_buff; i++)
noise_buffer[i]=0.0;


## Plucking the Algorithm

Each time the Karplus-Strong algorithm is excited, or plucked, the buffer needs to be filled with a sequence of random noise. At each call of the JACK callback function (process), it is checked, whether a new event has been triggered via MIDI or OSC. If that is true, the playback position of the buffer is set to 0 and each sample of the noise_buffer is filled with a random double between -1 and 1:

cout << "Filling buffer!";
buffer_pos = 0;
for(int i=0; i<=l_buff; i++)
noise_buffer[i]=  rand() % 2 - 1;


## Running Through the Buffer

The sound is generated by directly writing the samples of the noise_buffer to the JACK output buffer. This is managed in a circular fashion with the buffer_pos counter. Wrapping the counter to the buffer size makes the process circular. This example uses a stereo output with the mono signal.

for(int sampCNT=0; sampCNT<nframes; sampCNT++)
{

// write all input samples to output
for(int chanCNT=0; chanCNT<nChannels; chanCNT++)
{
out[chanCNT][sampCNT]=noise_buffer[buffer_pos];
}

// increment buffer position
buffer_pos++;
if (buffer_pos>=l_buff)
buffer_pos=0;
}


## Smoothing the Buffer

The above version results in a never-ending oscillation, a white tone. The timbre of this tone changes with every triggering, since a unique random sequence is used each time. With the additional smoothing, the tone will decay and lose the high spectral components, gradually. This is done as follows:

// smoothing the buffer
double sum = 0;
for(int smoothCNT=0; smoothCNT<l_smooth; smoothCNT++)
{
if(buffer_pos+smoothCNT<l_buff)
sum+=noise_buffer[buffer_pos+smoothCNT];
else
sum+=noise_buffer[smoothCNT];
}
noise_buffer[buffer_pos] = gain*(sum/l_smooth);


## Compiling

To compile the KarplusStrongExample, run the following command line:

g++ -Wall -L/usr/lib src/yamlman.cpp src/main.cpp src/karplus_strong_example.cpp src/oscman.cpp src/midiman.cpp -ljack -llo -lyaml-cpp -lsndfile -lrtmidi -o karplus_strong


This call of the g++ compiler includes all necessary libraries and creates the binary karplus_strong.

## Running the Example

The binary can be started with the following command line:

./karplus_strong -c config.yml -m "OSC"


This will use the configurations from the YAML file and wait for OSC input. The easiest way of triggering the synth via OSC is to use the Puredata patch from the example's directory.

## Exercises

Exercise I

Make the buffer length and filter length command line or realtime-controllable parameters.

Exercise II

Implement a fractional noise buffer for arbitrary pitches.

# Using MIDI with RtMidi

Although the MIDI protocol is quite old and has several drawbacks, it is still widely used and is appropriate for many applications. Read the MIDI section in the Computer Music Basics for a deeper introduction.

The development system used in this class relies on the RtMidi framework. This allows the inclusion of any ALSA MIDI device on Linux systems and hence any USB MIDI device. The RtMidi Tutorial gives a thorough introduction to the use of the library.

## ALSA MIDI

The Advanced Linux Sound Architecture (ALSA) makes audio- and MIDI interfaces accessible for software. As an API it is part of the Linux kernel. Other frameworks, like JACK or Pulseaudio work on a higher level and rely on ALSA.

### Finding your ALSA MIDI Devices

After connecting a MIDI device to an USB port, it should be available via ALSA. All ALSA MIDI devices can be listed with the following shell command:



## Including the Library

The liblo comes with additional C++11 wrappers to offer an object-oriented workflow. This feature is also used in the examples of this class. The following lines include both headers:

#include <lo/lo.h>
#include <lo/lo_cpp.h>


## The GainExample

The GainExample is based on the ThroughExample, adding the capability to control the gain of the passed through signal with OSC messages.

### Passing Command Line Arguments

The main function of this example accepts the OSC port to listen to as a command line argument. This is realized with a string comparison. The compiled binary is then started with an extra argument for the port:



# Working with the g++ Compiler

## Compiling a Program

Examples and projects in this modules can be compiled with g++ from the GNU Compiler Collection. With the proper libraries installed, g++ can be called directly for small to medium sized projects. The first example, just passing through the audio, is compiled with the following command:

g++ -Wall src/gain_example.cpp src/oscman.cpp -ljack -o gain_example


The compiler gets the extra argument Wall to print all warnings. All source (cpp) files are passed to the compiler, followed by all libraries which need to be linked (linker arguments). The name of the binary or executable is specified after the -o` flag.

# Past Projects - Sound Synthesis in C++

The following projects have been realized within the Sound Synthesis seminar in the past.