TR123e - EMCT Computing Final Project Version 1.0
Research Project Translation from gen~ to embedded Moog synth
Loading...
Searching...
No Matches
render.cpp File Reference

Real-time monophonic synthesizer implementation for Bela platform. More...

#include <Bela.h>
#include <libraries/Midi/Midi.h>
#include <cmath>
#include "ADSR.h"
#include "KeyFollow.h"
#include "MidiHandler.h"
#include "MoogFilterEnvelope.h"
#include "PortamentoFilter.h"
#include "PortamentoPlayer.h"
#include "ResonanceRamp.h"
#include "VelocityParser.h"
#include "zdf_moogladder_v2.h"

Functions

bool setup (BelaContext *context, void *userData)
 System initialization and configuration.
void render (BelaContext *context, void *userData)
 Real-time audio processing callback.
void cleanup (BelaContext *context, void *userData)
 System cleanup and resource deallocation.

Variables

float oscillatorPhase = 0.0f
 Primary oscillator phase accumulator [0, 2π].
float twoPi = 2.0f * M_PI
 Precomputed 2π constant for computational efficiency.
Midi midi
 MIDI interface controller.
MidiHandler midiHandler (44100.0f, 1.0f)
 High-level MIDI message processor and timing controller.
VelocityParser velocityParser (64)
 Velocity-sensitive note triggering processor.
PortamentoFilter portamentoFilter
 Portamento behavior analysis module.
PortamentoPlayer portamentoPlayer (44100.0f, 100.0f)
 Pitch interpolation and portamento synthesis engine.
ADSR envelope
 Primary amplitude envelope generator.
MoogFilterEnvelope filterEnv (44100.0f)
 Filter cutoff frequency envelope generator.
KeyFollow keyFollow (0.01f)
 Keyboard tracking / key follow processor.
ResonanceRamp resonanceRamp (44100.0f, 50.0f)
 Resonance parameter smoothing filter.
ZDFMoogLadderFilter zdfFilter (44100.0f)
 Zero-delay feedback Moog ladder filter implementation.
float * inputBuffer = nullptr
 Input audio buffer for oscillator output.
float * outputBuffer = nullptr
 Output audio buffer for filtered audio.
int bufferSize = 0
 Current audio buffer size in samples.
float baseCutoffFrequency = 5000.0f
 Base filter cutoff frequency in Hz.
int gAudioFramesPerAnalogFrame = 0
 Audio-to-analog frame ratio for control rate processing.

Detailed Description

Real-time monophonic synthesizer implementation for Bela platform.

This file implements a complete monophonic synthesizer using the Bela real-time audio platform. The synthesizer features a sine wave oscillator with portamento, dual ADSR envelopes (amplitude and filter), a zero-delay feedback Moog ladder filter, and comprehensive MIDI control with analog input integration.

@architecture The system employs a modular signal flow architecture: MIDI Input → Note Processing → Oscillator → Filter → Amplification → Audio Output ↓ ↓ ↓ Portamento Amp Envelope Filter Envelope

@performance_characteristics

  • Real-time processing at variable sample rates (typically 44.1kHz)
  • Ultra-low latency audio processing (< 5ms typical)
  • Zero-delay feedback filter implementation for analog-accurate response
  • Hardware analog control integration with smooth parameter interpolation

@dependencies

  • Bela.h: Real-time audio framework
  • libraries/Midi/Midi.h: MIDI protocol implementation
  • Custom DSP classes: ADSR, KeyFollow, MidiHandler, etc.
Author
[Timothy Paul Read]
Date
[2025/5/25]
Version
1.0

Function Documentation

◆ cleanup()

void cleanup ( BelaContext * context,
void * userData )

System cleanup and resource deallocation.

@function cleanup

Releases dynamically allocated memory and performs orderly shutdown of audio processing system. Called once when audio processing terminates.

Parameters
contextBela audio context (unused in cleanup)
userDataUser-defined data pointer (unused)

@complexity O(1) - Constant time cleanup @realtime_safety Non-real-time safe (performs memory deallocation)

Note
This function is called outside the real-time audio context and may perform blocking operations safely.

Deallocate input buffer memory Prevents memory leaks on system shutdown

Deallocate output buffer memory
Completes memory management for audio buffers

◆ render()

void render ( BelaContext * context,
void * userData )

Real-time audio processing callback.

@function render

Core real-time audio processing function called at regular intervals (typically every 64-512 samples). Implements complete signal chain from MIDI input through oscillator, envelope, and filter processing to audio output.

Parameters
contextBela audio context with I/O buffers and timing information
userDataUser-defined data pointer (unused)

@complexity O(n) where n = context->audioFrames @realtime_safety Real-time safe (no dynamic allocation or blocking operations) @latency Ultra-low latency (< 5ms typical processing delay)

Note
This function executes in real-time audio context and must complete processing within the buffer period to prevent audio dropouts. All operations must be deterministic and bounded in execution time.

Calculate current time in milliseconds for MIDI timing correlation Provides high-precision timestamp for event scheduling and delay compensation Formula: (samples_elapsed / sample_rate) * 1000 = time_in_ms

Process all available MIDI messages in current buffer period Non-blocking iteration ensures real-time safety while handling multiple simultaneous MIDI events with sample-accurate timing

Process Note On/Off messages for synthesis control Both message types handled by unified processor for consistent timing

Process Control Change messages for real-time parameter modification Implements standard MIDI CC protocol for synthesizer control

CC 14: Filter Cutoff Frequency Control Maps MIDI CC value to exponential frequency range [20Hz - 30kHz] Formula: f = 20 * (1500^(cc/127)) provides musical frequency scaling

CC 15: Filter Resonance Control Linear mapping from MIDI CC to resonance parameter [0.0-1.0] Higher values increase filter Q and self-oscillation tendency

Update MIDI handler timing state for accurate event scheduling Maintains internal timing reference for sample-accurate MIDI processing

Process all sample-accurate delayed MIDI messages Handles messages that require precise timing alignment with audio samples for glitch-free note transitions and envelope triggering

Parse velocity to determine note-on vs note-off state Implements MIDI standard where velocity 0 = note-off

Analyze portamento requirements based on playing technique Determines whether smooth pitch transition is appropriate based on note overlap timing and musical context

Convert MIDI velocity to normalized amplitude scaling [0.0-1.0] Provides velocity-sensitive filter envelope response

Process note-on events: trigger all synthesis modules

Process note-off events: release all synthesis modules

Set resonance emphasis during note events Creates characteristic filter "pop" during note articulation typical of classic analog synthesizers

Static variable for output gain to maintain state between function calls Note: Double semicolon appears to be a typo in original code

Process each audio sample in the current buffer Implements complete synthesis chain: oscillator → envelope → filter → output

Calculate analog input index based on audio/analog frame ratio Analog inputs sampled at lower rate than audio (typically 8:1) This indexing ensures proper correlation between control and audio timing

Generate amplitude envelope value [0.0-1.0] Provides time-varying amplitude control for musical articulation

Generate current oscillator frequency from portamento processor Returns frequency in Hz with smooth transitions between notes

Calculate keyboard tracking contribution to filter frequency Higher notes slightly open filter for consistent timbre across range

Generate filter cutoff frequency from envelope and key tracking Combines base frequency, envelope modulation, and keyboard tracking

Read analog control potentiometers [0.0-1.0] Hardware control integration for real-time parameter adjustment

Read filter mode selection from analog input Maps continuous [0.0-1.0] to discrete mode values [0-2] Modes: 0=LP24, 1=LP12, 2=HP12, 3=BP12 (depending on filter implementation)

Read additional control parameters from analog inputs

Update filter resonance with smooth ramping Prevents audio artifacts from abrupt parameter changes

Update filter parameters for current sample Combines envelope-generated cutoff with manual control

Update envelope parameters in real-time Allows dynamic response characteristics during performance

Generate oscillator output using Direct Digital Synthesis (DDS) Implements conditional processing for CPU efficiency

Only generate oscillator output when envelope is active Saves CPU cycles during silent periods (envelope in idle state)

Generate sine wave using phase accumulator method sin(φ) where φ = accumulated phase [0, 2π]

Update phase accumulator for next sample Phase increment = 2π * frequency / sample_rate This implements the fundamental DDS frequency equation

Phase wrapping to prevent floating-point precision loss Maintains phase within [0, 2π] range for optimal accuracy

Apply amplitude envelope to oscillator output Creates time-varying amplitude response for musical articulation

Reset oscillator phase during silent periods Ensures clean restart for next note-on event

Apply output scaling to prevent clipping 50% scaling provides headroom for filter resonance peaks

Store oscillator output in input buffer for filter processing Separation allows for potential future multi-stage processing

Apply filter processing to entire buffer Separate loop allows for potential SIMD optimization and maintains clean separation between synthesis and filtering stages

Process each sample through ZDF Moog ladder filter Apply output gain control for final level adjustment

Write processed audio to both output channels (stereo) Monophonic synthesizer output duplicated to both channels for standard stereo compatibility

◆ setup()

bool setup ( BelaContext * context,
void * userData )

System initialization and configuration.

@function setup

Initializes all audio processing modules, allocates dynamic memory, configures MIDI interface, and establishes initial parameter states. Called once at system startup before real-time processing begins.

Parameters
contextBela audio context containing system configuration
userDataUser-defined data pointer (unused in this implementation)
Returns
true if initialization successful, false triggers system abort

@complexity O(1) - Constant time initialization @realtime_safety Non-real-time safe (performs memory allocation)

Note
This function performs dynamic memory allocation and must complete before real-time audio processing begins. Failure to complete initialization will prevent audio system startup.

Configure MIDI interface for hardware device "hw:0,0" This corresponds to the first MIDI interface on the system

Enable built-in MIDI message parsing for automatic protocol handling Parser provides structured access to MIDI channel messages, system exclusive data, and real-time messages with proper timing information

Cache sample rate for computational efficiency Eliminates repeated context dereferencing in audio callback

Calculate and cache audio/analog frame ratio Bela processes analog inputs at lower rates than audio samples Typical ratio: 8 audio frames per analog frame (44.1kHz/5.5kHz)

Initialize ZDF Moog ladder filter with current sample rate Reconfiguration ensures proper coefficient calculation for actual system sample rate (may differ from default 44.1kHz)

Reset filter internal state to prevent initialization artifacts Clears delay lines and feedback paths for clean startup

Configure initial filter parameters

  • Cutoff: 1kHz (musically neutral, not too bright or dull)
  • Resonance: 0.5 (moderate Q, stable self-oscillation threshold)
  • Drive: 1.0 (unity gain, no input saturation)
  • Mode: 0 (24dB/octave low-pass, classic Moog response)

Cache buffer size for memory allocation Size determined by Bela configuration, typically 64-512 samples

Allocate input buffer for oscillator output staging Enables separation of synthesis and filtering for modular processing

Allocate output buffer for final processed audio Provides clean separation between processing stages

Configure filter envelope parameters

  • Attack: 1ms (near-instantaneous response for percussive sounds)
  • Decay: 100ms (moderate decay for musical filter sweeps)
  • Sustain: 0.75 (maintains 75% of peak level during note hold)
  • Release: 200ms (smooth fade-out preventing clicks)

Set filter envelope depth to 48 semitones (4 octaves) Provides musically significant frequency modulation range while preventing excessive filter opening that could cause aliasing

Initialize resonance ramping to moderate value 0.5 provides good filter character without excessive self-oscillation

Reset amplitude envelope to idle state Ensures clean initialization without residual envelope activity

Configure amplitude envelope timing (values in samples)

  • Attack: 441 samples ≈ 10ms (quick response, prevents clicks)
  • Decay: 529 samples ≈ 12ms (fast initial decay for punch)
  • Release: 11,025 samples ≈ 250ms (musical release tail)

Set sustain level to 65% of peak amplitude Balances musical sustain with dynamic range preservation

Configure envelope curvature parameters

  • Attack ratio: 0.3 (exponential attack curve)
  • Decay/Release ratio: 0.0001 (near-linear decay for natural sound)

These parameters control the mathematical curvature of envelope segments, affecting the perceptual character of amplitude changes

Variable Documentation

◆ baseCutoffFrequency

float baseCutoffFrequency = 5000.0f

Base filter cutoff frequency in Hz.

Fundamental filter frequency before envelope and key tracking modulation. Set to 5kHz as musically useful default providing bright, open character while allowing sufficient modulation range.

◆ bufferSize

int bufferSize = 0

Current audio buffer size in samples.

Dynamically determined from Bela context, typically 64-512 samples depending on system configuration and latency requirements.

◆ envelope

ADSR envelope

Primary amplitude envelope generator.

Four-stage ADSR (Attack, Decay, Sustain, Release) envelope with exponential segment shaping and configurable curvature parameters. Implements state machine for proper envelope stage transitions.

◆ filterEnv

MoogFilterEnvelope filterEnv(44100.0f) ( 44100. 0f)

Filter cutoff frequency envelope generator.

Parameters
sampleRate44100.0f Hz - Audio processing rate

Specialized envelope generator for filter frequency modulation with velocity sensitivity and exponential frequency mapping. Provides musical filter sweeps with proper logarithmic frequency scaling.

◆ gAudioFramesPerAnalogFrame

int gAudioFramesPerAnalogFrame = 0

Audio-to-analog frame ratio for control rate processing.

Bela systems process analog inputs at lower rates than audio (typically 8:1 ratio). This variable caches the ratio for efficient analog input indexing without repeated division operations in the audio callback.

◆ inputBuffer

float* inputBuffer = nullptr

Input audio buffer for oscillator output.

Dynamically allocated buffer for storing raw oscillator output before filter processing. Separation of generation and filtering stages allows for potential future multi-stage processing implementations.

◆ keyFollow

KeyFollow keyFollow(0.01f) ( 0. 01f)

Keyboard tracking / key follow processor.

Parameters
intensity0.01f - Key tracking coefficient [0.0-1.0]

Implements keyboard tracking for filter cutoff frequency, allowing higher notes to open the filter proportionally. Uses MIDI note number to frequency mapping with configurable tracking intensity.

◆ midi

Midi midi

MIDI interface controller.

Handles low-level MIDI protocol parsing and message buffering. Configured for hardware MIDI interface "hw:0,0" (first MIDI device).

◆ midiHandler

MidiHandler midiHandler(44100.0f, 1.0f) ( 44100. 0f,
1. 0f )

High-level MIDI message processor and timing controller.

Parameters
sampleRate44100.0f Hz - Standard audio sample rate
jitterReduction1.0f - Temporal quantization factor for timing stability

Implements sophisticated MIDI timing algorithms to handle note scheduling, velocity-sensitive triggering, and temporal message buffering for frame-accurate event processing.

◆ oscillatorPhase

float oscillatorPhase = 0.0f

Primary oscillator phase accumulator [0, 2π].

Maintains phase continuity for sine wave generation using direct digital synthesis (DDS) method. Phase wrapping prevents floating-point precision degradation over extended operation periods.

◆ outputBuffer

float* outputBuffer = nullptr

Output audio buffer for filtered audio.

Stores final processed audio after filter stage, ready for DAC output. Double-buffering architecture prevents audio artifacts during processing.

◆ portamentoFilter

PortamentoFilter portamentoFilter

Portamento behavior analysis module.

Analyzes incoming MIDI note patterns to determine appropriate portamento behavior based on legato playing technique detection and note overlap timing.

◆ portamentoPlayer

PortamentoPlayer portamentoPlayer(44100.0f, 100.0f) ( 44100. 0f,
100. 0f )

Pitch interpolation and portamento synthesis engine.

Parameters
sampleRate44100.0f Hz - Audio processing rate
glideTime100.0f ms - Default portamento transition duration

Implements exponential pitch interpolation algorithms for smooth frequency transitions between notes. Uses logarithmic frequency scaling to maintain perceptually linear pitch movement across the entire musical range.

◆ resonanceRamp

ResonanceRamp resonanceRamp(44100.0f, 50.0f) ( 44100. 0f,
50. 0f )

Resonance parameter smoothing filter.

Parameters
sampleRate44100.0f Hz - Audio processing rate
rampTime50.0f ms - Parameter transition smoothing time

Implements first-order low-pass filtering for resonance parameter changes to prevent audio artifacts from abrupt control changes, particularly important for high-Q filter settings where parameter discontinuities can cause instability.

◆ twoPi

float twoPi = 2.0f * M_PI

Precomputed 2π constant for computational efficiency.

Cached value eliminates repeated trigonometric calculations in the audio processing loop, reducing CPU overhead in real-time context.

◆ velocityParser

VelocityParser velocityParser(64) ( 64 )

Velocity-sensitive note triggering processor.

Parameters
threshold64 - MIDI velocity threshold for note-on discrimination

Implements hysteresis-based velocity parsing to distinguish between note-on and note-off events, preventing false triggering from low velocity note-on messages (common in electronic controllers).

◆ zdfFilter

ZDFMoogLadderFilter zdfFilter(44100.0f) ( 44100. 0f)

Zero-delay feedback Moog ladder filter implementation.

Parameters
sampleRate44100.0f Hz - Audio processing rate

Advanced digital implementation of the classic Moog transistor ladder filter using zero-delay feedback (ZDF) topology. Provides analog-accurate frequency response and self-oscillation behavior with numerical stability across all parameter ranges.

Note
ZDF implementation eliminates the one-sample delay inherent in traditional digital filter structures, providing superior transient response and improved analog modeling accuracy.