All chapters
Cookbook · Chapter 7

Sound and audio

Umfeld's native audio engine — oscillators, samples, envelopes, FFT, drum machines and effects.

Umfeld has its own audio system built in — no external audio library to add. You request audio in settings(), then fill the output buffer inside audioEvent(), which runs on the audio thread once per buffer. Generators and effects each expose a process() that returns one sample at a time.

The audio skeleton

#include "Umfeld.h"
#include "audio/AudioUtilities.h"
#include "audio/Wavetable.h"

using namespace umfeld;

Wavetable* osc;

void settings() {
    size(640, 480);
    audio();                                   // request default in / out
}

void setup() {
    osc = new Wavetable(1024, get_audio_sample_rate());
    osc->set_waveform(WAVEFORM_SINE);          // SINE / TRIANGLE / SAWTOOTH / SQUARE / NOISE
    osc->set_frequency(220.0f);
    osc->set_amplitude(0.7f);
}

void draw() {
    background(216);
    osc->set_frequency(map(mouseX, 0, width, 20.0f, 880.0f));
}

void audioEvent(const PAudio& audio) {
    float buffer[audio.buffer_size];
    for (int i = 0; i < audio.buffer_size; i++) {
        buffer[i] = osc->process();
    }
    if (audio.output_channels == 2) {
        merge_interleaved_stereo(buffer, buffer, audio.output_buffer, audio.buffer_size);
    }
}

void shutdown() {
    delete osc;                                // free what you `new`-ed
}

Playing audio files

loadSample() loads a clip into a Sampler you can play(), loop, and scrub:

#include "audio/Sampler.h"

Sampler* sample;

void setup() {
    sample = loadSample("loop.wav");
    sample->set_looping();
    sample->play();
}

void audioEvent(const PAudio& audio) {
    float buffer[audio.buffer_size];
    for (int i = 0; i < audio.buffer_size; i++) {
        buffer[i] = sample->process();
    }
    merge_interleaved_stereo(buffer, buffer, audio.output_buffer, audio.buffer_size);
}

sample->get_position_normalized() (0..1) is handy for drawing a playhead.

Using live audio

Request input channels with audio(inputs, outputs) and read audio.input_buffer in the callback. This passes the microphone straight to the speakers and measures its energy:

float energy = 0.0f;

void settings() { size(640, 480); audio(1, 2); }   // 1 in, 2 out

void audioEvent(const PAudio& audio) {
    float buffer[audio.buffer_size];
    energy = 0.0f;
    for (uint32_t i = 0; i < audio.buffer_size; ++i) {
        buffer[i] = audio.input_buffer[i];
        energy   += abs(buffer[i]);
    }
    energy /= audio.buffer_size;
    merge_interleaved_stereo(buffer, buffer, audio.output_buffer, audio.buffer_size);
}

Drawing a waveform and FFT spectrum

Run the buffer through the FFT to get frequency bins you can draw as bars:

#include "audio/FFT.h"

std::vector<std::pair<float, float>> spectrum;   // (frequency, dB)

void setup() {
    fft_start(get_audio_buffer_size(), get_audio_sample_rate());
}

void draw() {
    background(216);
    noStroke();
    fill(0, 127, 255);
    if (!spectrum.empty()) {
        const float bw = width / spectrum.size();
        for (const auto& [freq, db] : spectrum) {
            float x = map(freq, 20.0f, 800.0f, 0.0f, width);
            float h = map(db, 0.0f, 50.0f, height, 0.0f);
            rect(x, h, bw, height - h);
        }
    }
}

void audioEvent(const PAudio& audio) {
    float buffer[audio.buffer_size];
    for (int i = 0; i < audio.buffer_size; i++) buffer[i] = /* your signal */ 0.0f;
    spectrum = fft_process(buffer, 20.0f, 800.0f);
    merge_interleaved_stereo(buffer, buffer, audio.output_buffer, audio.buffer_size);
}

void shutdown() { fft_stop(); }

Audio-reactive graphics

Anything you store from the audio thread (like energy above) can drive the drawing. Keep the audio callback cheap and just read the value in draw():

void draw() {
    background(216);
    noFill();
    stroke(255, 63, 89);
    circle(width / 2, height / 2, energy * 0.5f * height + 0.25f * height);
}

Building a synthesizer (envelopes and notes)

An ADSR envelope shapes a note; Note and Scale turn musical degrees into frequencies:

#include "audio/ADSR.h"
#include "audio/Note.h"
#include "audio/Scale.h"

Wavetable* osc;
ADSR*      adsr;

void setup() {
    osc  = new Wavetable(512, get_audio_sample_rate());
    adsr = new ADSR(get_audio_sample_rate());
    osc->set_waveform(WAVEFORM_SINE);
    adsr->set_attack(0.01f);
    adsr->set_decay(0.2f);
    adsr->set_sustain(0.0f);
    adsr->set_release(0.0f);
}

void play_degree(int degree) {
    int   midi = Scale::note(Scale::MAJOR, Note::C_4, degree);
    float freq = AudioUtilities::midi_note_to_frequency(static_cast<uint8_t>(midi));
    osc->set_frequency(freq);
    adsr->start();
}

void audioEvent(const PAudio& audio) {
    float buffer[audio.buffer_size];
    for (int i = 0; i < audio.buffer_size; i++) {
        buffer[i] = adsr->process(osc->process());   // envelope the oscillator
    }
    merge_interleaved_stereo(buffer, buffer, audio.output_buffer, audio.buffer_size);
}

Building a drum machine

BeatDSP fires a callback on a clock; trigger samples on the beats you want:

#include "audio/BeatDSP.h"

Sampler *kick, *snare, *hihat;
BeatDSP* beat;

void on_beat(uint32_t count) {
    if (count % 8 == 0) kick->play();
    if (count % 8 == 4) snare->play();
    hihat->play();
}

void setup() {
    kick  = loadSample("kick.wav");
    snare = loadSample("snare.wav");
    hihat = loadSample("hat.wav");
    beat  = new BeatDSP(get_audio_sample_rate());
    beat->set_bpm(130 * 4);
    beat->set_callback(on_beat);
}

Using effects

Effects (here a Delay) also expose process() — chain them after your source in the loop:

#include "audio/Delay.h"

Delay* delay;   // new Delay(sample_rate, echo_length, decay, wet)

void setup() {
    delay = new Delay(get_audio_sample_rate(), 0.3f, 0.6f, 0.5f);
}

void audioEvent(const PAudio& audio) {
    float buffer[audio.buffer_size];
    for (int i = 0; i < audio.buffer_size; i++) {
        buffer[i] = delay->process(osc->process());
    }
    merge_interleaved_stereo(buffer, buffer, audio.output_buffer, audio.buffer_size);
}

The repository ships more building blocks the same way — LowPassFilter, OscillatorFunction, distortion, waveshaper, resonator, and a vocoder.