Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::drum::MembraneVoice Class Reference

A struck membrane, modelled as its modes rather than as an oscillator. More...

#include <membrane.hpp>

Public Member Functions

 MembraneVoice ()
void set_tune_hz (double hz)
 Fundamental of the body, in Hz.
void set_structure (double amount)
 Blend from a harmonic series (0) to ideal-membrane ratios (1).
void set_stretch (double amount)
 Extra progressive sharpening of the upper modes, as a stiff plate has.
void set_decay_ms (double ms)
 How long the fundamental rings, as a T60 in milliseconds.
void set_damping (double amount)
 How much faster the upper modes die than the fundamental.
void set_brightness (double amount)
 Relative level of the upper modes in the strike.
void set_position (double position)
 Where the head is struck, 0 (edge) to 0.5 (midpoint).
void set_spread (double amount)
 Random detuning of the modes, drawn once per hit.
void set_exciter_ms (double ms)
 Length of the exciter burst and the corner it is filtered at.
void set_exciter_cutoff_hz (double hz)
void set_exciter (MembraneExciter exciter)
void set_sub_level (double level)
 Optional layers around the modal body.
void set_air_level (double level)
void set_air_decay_ms (double ms)
void set_click_level (double level)
void set_click_decay_ms (double ms)
double mode_gain_energy () const
 Sum of squared mode gains after the constant-energy normalization.
LowpassGate & gate ()
 The lowpass gate that couples the tail's level to its brightness.
OutputStage & output ()
const OutputStage & output () const
OutputStage * output_stage () noexcept override
 The terminal saturation and degradation stage, at the generic voice boundary.
int latency_samples () const noexcept override
 Fixed host-rate delay introduced by this voice's selected output quality.
OutputOversampling output_oversampling () const noexcept override
 Output quality at the generic voice boundary.
void set_output_oversampling (OutputOversampling factor) override
void prepare (double sample_rate)
 Fixes the sample rate and clears all state.
void reset ()
 Silences the voice immediately, without a fade.
void note_on (float velocity)
 Starts a hit. velocity is in [0, 1]; values outside are clamped.
void choke (float fade_ms=4.0f)
 Fades the voice out over fade_ms and then silences it.
bool is_active () const
 Whether the voice still has something to render.
VoiceState state () const
float velocity () const
double sample_rate () const
const OutputStage * output_stage () const noexcept
void set_velocity_response (const VelocityResponse &r)
const VelocityResponse & velocity_response () const
void process (float *out, int num_samples)
 Adds num_samples of this voice's output into out.
void process_stereo (float *left, float *right, int num_samples)
 Adds a stereo rendering to left and right.

Static Public Attributes

static constexpr int mode_count = 6
 Number of modes the body carries.
static constexpr double membrane_ratios [mode_count]
 Frequency ratios of an ideal circular membrane's first six modes, relative to its fundamental.

Protected Member Functions

void on_prepare (double sample_rate) override
 Sizes buffers and fixes coefficients.
void on_reset () override
 Clears every piece of state the voice carries, so is_active() becomes false and the next render starts from silence.
void on_note_on (float velocity) override
 Starts a hit at the given velocity, already clamped to [0, 1].
bool on_is_active () const override
void render_add (float *out, int num_samples) override
 Adds the voice's output into out. Must add, never assign.
virtual void render_add_stereo (float *left, float *right, int num_samples)
 Optional stereo realization.

Detailed Description

A struck membrane, modelled as its modes rather than as an oscillator.

Everything the voice does follows from one idea: a drumhead has a set of resonances, striking it excites all of them at once, and they die away at different rates. Nothing here has a waveform. The character comes from which frequencies the modes sit at and how their decays differ, so the controls are physical (how big, how damped, where it was struck) rather than synthesis controls (which oscillator, which filter).

structure is the important one. At 0 the modes are a harmonic series, which is what a string does and what almost every synthesised drum implicitly assumes. At 1 they are the ratios of an ideal circular membrane — 1, 1.593, 2.135, 2.295, 2.653, 2.917 — which are not integer multiples of anything. That is why a real drum has no clear pitch while a harmonic bank plays a note, and moving between them is the difference between a tom and a tuned percussion instrument. The membrane ratios are published physics (they are the zeros of a Bessel function; see Fletcher & Rossing) rather than anything fitted.

position is a comb, not a level. A mode with a node at the strike point cannot be excited by a strike there, so the position silently removes whole partials rather than changing the level: at the midpoint every even-numbered mode disappears entirely. That is why moving the strike point changes which drum it sounds like and not how loud it is.

The weighting is the one-dimensional |sin(pi * n * position)| — the same comb a struck string has. A circular head's true weighting is a Bessel function of the strike radius, which nulls a different set of modes; the sine comb is the standard approximation and is what the mode ratios above are paired with in practice.

The lowpass gate at the end is what stops the result sounding like a bank of filters: it couples level to brightness, so the tail darkens as it dies the way a physical body does.

RT contract: prepare() sizes the mode bank and allocates. Everything else, including note_on and process, allocates nothing.

Constructor & Destructor Documentation

◆ MembraneVoice()

pulp::signal::drum::MembraneVoice::MembraneVoice ( )
inline

Member Function Documentation

◆ choke()

void pulp::signal::drum::Voice::choke ( float fade_ms = 4.0f)
inlineinherited

Fades the voice out over fade_ms and then silences it.

Used by choke groups; a shorter fade is more abrupt but never clicks the way an instant cut does.

◆ gate()

LowpassGate & pulp::signal::drum::MembraneVoice::gate ( )
inline

The lowpass gate that couples the tail's level to its brightness.

◆ is_active()

bool pulp::signal::drum::Voice::is_active ( ) const
inlineinherited

Whether the voice still has something to render.

A kit uses this to skip voices entirely rather than summing silence.

◆ latency_samples()

int pulp::signal::drum::MembraneVoice::latency_samples ( ) const
inlineoverridevirtualnoexcept

Fixed host-rate delay introduced by this voice's selected output quality.

Generic registry consumers can report it without recovering the concrete voice type.

Reimplemented from pulp::signal::drum::Voice.

◆ mode_gain_energy()

double pulp::signal::drum::MembraneVoice::mode_gain_energy ( ) const
inline

Sum of squared mode gains after the constant-energy normalization.

Exposed for renderers and matchers that need to distinguish a changed strike position from an unintended level change.

◆ note_on()

void pulp::signal::drum::Voice::note_on ( float velocity)
inlineinherited

Starts a hit. velocity is in [0, 1]; values outside are clamped.

◆ on_is_active()

bool pulp::signal::drum::MembraneVoice::on_is_active ( ) const
inlineoverrideprotectedvirtual

◆ on_note_on()

void pulp::signal::drum::MembraneVoice::on_note_on ( float velocity)
inlineoverrideprotectedvirtual

Starts a hit at the given velocity, already clamped to [0, 1].

Implements pulp::signal::drum::Voice.

◆ on_prepare()

void pulp::signal::drum::MembraneVoice::on_prepare ( double sample_rate)
inlineoverrideprotectedvirtual

Sizes buffers and fixes coefficients.

The only hook allowed to allocate.

Implements pulp::signal::drum::Voice.

◆ on_reset()

void pulp::signal::drum::MembraneVoice::on_reset ( )
inlineoverrideprotectedvirtual

Clears every piece of state the voice carries, so is_active() becomes false and the next render starts from silence.

Implements pulp::signal::drum::Voice.

◆ output() [1/2]

OutputStage & pulp::signal::drum::MembraneVoice::output ( )
inline

◆ output() [2/2]

const OutputStage & pulp::signal::drum::MembraneVoice::output ( ) const
inline

◆ output_oversampling()

OutputOversampling pulp::signal::drum::MembraneVoice::output_oversampling ( ) const
inlineoverridevirtualnoexcept

Output quality at the generic voice boundary.

Kits use this surface to keep every registered path on one latency contract.

Reimplemented from pulp::signal::drum::Voice.

◆ output_stage() [1/2]

OutputStage * pulp::signal::drum::MembraneVoice::output_stage ( )
inlineoverridevirtualnoexcept

The terminal saturation and degradation stage, at the generic voice boundary.

A registry consumer reaches it without recovering the concrete voice type, so adding an engine cannot desynchronise a downcast from the engine it was selected for. A voice that ends without one returns nullptr.

Reimplemented from pulp::signal::drum::Voice.

◆ output_stage() [2/2]

const OutputStage * pulp::signal::drum::Voice::output_stage ( ) const
inlinenoexceptinherited

◆ prepare()

void pulp::signal::drum::Voice::prepare ( double sample_rate)
inlineinherited

Fixes the sample rate and clears all state.

The only method a derived voice may allocate in.

◆ process()

void pulp::signal::drum::Voice::process ( float * out,
int num_samples )
inlineinherited

Adds num_samples of this voice's output into out.

◆ process_stereo()

void pulp::signal::drum::Voice::process_stereo ( float * left,
float * right,
int num_samples )
inlineinherited

Adds a stereo rendering to left and right.

Voices that do not provide a stereo realization are placed in the centre with half their mono signal in each channel, so summing the two channels reproduces process() exactly. A stereo-capable voice may override render_add_stereo, but the shared choke fade remains here.

◆ render_add()

void pulp::signal::drum::MembraneVoice::render_add ( float * out,
int num_samples )
inlineoverrideprotectedvirtual

Adds the voice's output into out. Must add, never assign.

Implements pulp::signal::drum::Voice.

◆ render_add_stereo()

virtual void pulp::signal::drum::Voice::render_add_stereo ( float * left,
float * right,
int num_samples )
inlineprotectedvirtualinherited

Optional stereo realization.

The default is centre-panned and preserves exact mono-sum equivalence.

Reimplemented in pulp::signal::drum::ClapVoice.

◆ reset()

void pulp::signal::drum::Voice::reset ( )
inlineinherited

Silences the voice immediately, without a fade.

For transport stops and buffer-size changes, where a click cannot be heard because nothing is playing either side of it.

◆ sample_rate()

double pulp::signal::drum::Voice::sample_rate ( ) const
inlineinherited

◆ set_air_decay_ms()

void pulp::signal::drum::MembraneVoice::set_air_decay_ms ( double ms)
inline

◆ set_air_level()

void pulp::signal::drum::MembraneVoice::set_air_level ( double level)
inline

◆ set_brightness()

void pulp::signal::drum::MembraneVoice::set_brightness ( double amount)
inline

Relative level of the upper modes in the strike.

◆ set_click_decay_ms()

void pulp::signal::drum::MembraneVoice::set_click_decay_ms ( double ms)
inline

◆ set_click_level()

void pulp::signal::drum::MembraneVoice::set_click_level ( double level)
inline

◆ set_damping()

void pulp::signal::drum::MembraneVoice::set_damping ( double amount)
inline

How much faster the upper modes die than the fundamental.

At 0 every mode rings equally long, which sounds like a bell rather than a drum.

◆ set_decay_ms()

void pulp::signal::drum::MembraneVoice::set_decay_ms ( double ms)
inline

How long the fundamental rings, as a T60 in milliseconds.

◆ set_exciter()

void pulp::signal::drum::MembraneVoice::set_exciter ( MembraneExciter exciter)
inline

◆ set_exciter_cutoff_hz()

void pulp::signal::drum::MembraneVoice::set_exciter_cutoff_hz ( double hz)
inline

◆ set_exciter_ms()

void pulp::signal::drum::MembraneVoice::set_exciter_ms ( double ms)
inline

Length of the exciter burst and the corner it is filtered at.

A short bright burst is a stick; a longer darker one is a hand.

◆ set_output_oversampling()

void pulp::signal::drum::MembraneVoice::set_output_oversampling ( OutputOversampling factor)
inlineoverridevirtual

Reimplemented from pulp::signal::drum::Voice.

◆ set_position()

void pulp::signal::drum::MembraneVoice::set_position ( double position)
inline

Where the head is struck, 0 (edge) to 0.5 (midpoint).

At the midpoint every even-numbered mode is nulled and the fundamental is at its strongest, so the strike point selects which partials exist rather than how loud the hit is.

◆ set_spread()

void pulp::signal::drum::MembraneVoice::set_spread ( double amount)
inline

Random detuning of the modes, drawn once per hit.

A real head is never perfectly uniform, and a bank at exact ratios sounds synthetic.

◆ set_stretch()

void pulp::signal::drum::MembraneVoice::set_stretch ( double amount)
inline

Extra progressive sharpening of the upper modes, as a stiff plate has.

◆ set_structure()

void pulp::signal::drum::MembraneVoice::set_structure ( double amount)
inline

Blend from a harmonic series (0) to ideal-membrane ratios (1).

◆ set_sub_level()

void pulp::signal::drum::MembraneVoice::set_sub_level ( double level)
inline

Optional layers around the modal body.

The sub follows the body's decay at half the fundamental; air is a short high-passed noise wash; click is a still-shorter filtered-noise strike.

◆ set_tune_hz()

void pulp::signal::drum::MembraneVoice::set_tune_hz ( double hz)
inline

Fundamental of the body, in Hz.

◆ set_velocity_response()

void pulp::signal::drum::Voice::set_velocity_response ( const VelocityResponse & r)
inlineinherited

◆ state()

VoiceState pulp::signal::drum::Voice::state ( ) const
inlineinherited

◆ velocity()

float pulp::signal::drum::Voice::velocity ( ) const
inlineinherited

◆ velocity_response()

const VelocityResponse & pulp::signal::drum::Voice::velocity_response ( ) const
inlineinherited

Member Data Documentation

◆ membrane_ratios

double pulp::signal::drum::MembraneVoice::membrane_ratios[mode_count]
staticconstexpr
Initial value:
= {1.0, 1.5933, 2.1355,
2.2954, 2.6531, 2.9173}

Frequency ratios of an ideal circular membrane's first six modes, relative to its fundamental.

Published physics, not fitted values.

◆ mode_count

int pulp::signal::drum::MembraneVoice::mode_count = 6
staticconstexpr

Number of modes the body carries.


The documentation for this class was generated from the following file:
  • /home/runner/work/pulp/pulp/core/signal/include/pulp/signal/drum/membrane.hpp