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

A cymbal, built from a bank of comb filters and then deliberately de-tuned out of any musical relationship. More...

#include <cymbal.hpp>

Public Member Functions

 CymbalVoice ()
void set_tune_hz (double hz)
 Frequency the lowest comb rings at, in Hz.
void set_decay_ms (double ms)
 How long the bank rings, as a T60 in milliseconds.
void set_decay_tilt (double tilt)
 How much faster each successive comb dies than the one below it.
void set_high_mode_emphasis_db (double db)
 Extra high-mode level at the top comb, interpolated from zero dB at the bottom comb.
void set_velocity_feedback (double amount)
 Velocity-dependent feedback and high-mode emphasis.
void set_velocity_high_mode_db (double db)
void set_upper_highpass_hz (double hz)
 Base cutoff of the extra high-pass stages in the upper half of the comb bank.
void set_spread (double amount)
 How far the combs are spread from unison (0) to their full plate ratios (1).
void set_inharmonicity (double amount)
 How far the ratios are pushed further apart still, progressively.
void set_shift_hz (double hz)
 How many hertz every partial is shifted by.
void set_noise_level (double level)
 Level of the noise part of the strike, and of the swept tone under it.
void set_strike_level (double level)
void set_strike_ms (double ms)
void set_tone_hz (double hz)
 Output tone controls: a lowpass on top and a highpass to clear the mud the shifter leaves at the bottom.
void set_low_cut_hz (double hz)
void set_variation (double amount)
 How much successive excitations differ, 0 (bit-identical) to 1.
void set_hit_life (HitLifeMode mode)
HitLifeMode hit_life () const
void set_noise_color (NoiseColor color)
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 comb_count = 8
 Number of parallel combs.
static constexpr double comb_ratios [comb_count]
 Plate-like ratios for the comb delays.

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 cymbal, built from a bank of comb filters and then deliberately de-tuned out of any musical relationship.

A comb filter is a delay fed back on itself, so it rings at every multiple of one over its delay — a harmonic series. Eight of them at plate-like ratios gives a dense metallic wash, which is most of the way to a cymbal. The problem is that it is still chordal: each comb is individually harmonic, so the bank rings on a set of pitches and reads as tuned metal rather than as a crash.

The fix is the interesting part. A frequency shifter adds a constant number of hertz to everything rather than multiplying by a ratio, so a comb's series at 400, 800, 1200 Hz becomes 437, 837, 1237 Hz — no longer multiples of anything, and with no fundamental left to hear as a pitch. Shifting by a few tens of hertz is what turns the bank from a chord into a cymbal, and no amount of detuning or filtering does the same job, because detuning moves the series and shifting destroys it.

Per-hit variation comes from advancing the noise seed, so two crashes in a row differ the way two real strikes do; setting variation to zero makes the voice bit-reproducible again.

The parallel-comb cymbal technique is credited to Zion Jaymes' cymbal synthesis tutorial. The implementation here is original, and the frequency shifter it depends on is Pulp's own (pulp::signal::FrequencyShifterT).

RT contract: prepare() allocates the delay lines. Everything else, including note_on and process, allocates nothing.

Constructor & Destructor Documentation

◆ CymbalVoice()

pulp::signal::drum::CymbalVoice::CymbalVoice ( )
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.

◆ hit_life()

HitLifeMode pulp::signal::drum::CymbalVoice::hit_life ( ) const
inline

◆ 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::CymbalVoice::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.

◆ 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::CymbalVoice::on_is_active ( ) const
inlineoverrideprotectedvirtual

◆ on_note_on()

void pulp::signal::drum::CymbalVoice::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::CymbalVoice::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::CymbalVoice::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::CymbalVoice::output ( )
inline

◆ output() [2/2]

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

◆ output_oversampling()

OutputOversampling pulp::signal::drum::CymbalVoice::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::CymbalVoice::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::CymbalVoice::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_decay_ms()

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

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

◆ set_decay_tilt()

void pulp::signal::drum::CymbalVoice::set_decay_tilt ( double tilt)
inline

How much faster each successive comb dies than the one below it.

Higher modes losing energy first is what makes a crash settle into a wash.

◆ set_high_mode_emphasis_db()

void pulp::signal::drum::CymbalVoice::set_high_mode_emphasis_db ( double db)
inline

Extra high-mode level at the top comb, interpolated from zero dB at the bottom comb.

This changes the plate balance without changing the shared output tone filter.

◆ set_hit_life()

void pulp::signal::drum::CymbalVoice::set_hit_life ( HitLifeMode mode)
inline

◆ set_inharmonicity()

void pulp::signal::drum::CymbalVoice::set_inharmonicity ( double amount)
inline

How far the ratios are pushed further apart still, progressively.

◆ set_low_cut_hz()

void pulp::signal::drum::CymbalVoice::set_low_cut_hz ( double hz)
inline

◆ set_noise_color()

void pulp::signal::drum::CymbalVoice::set_noise_color ( NoiseColor color)
inline

◆ set_noise_level()

void pulp::signal::drum::CymbalVoice::set_noise_level ( double level)
inline

Level of the noise part of the strike, and of the swept tone under it.

◆ set_output_oversampling()

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

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

◆ set_shift_hz()

void pulp::signal::drum::CymbalVoice::set_shift_hz ( double hz)
inline

How many hertz every partial is shifted by.

This is the control that turns tuned metal into a cymbal; at 0 the bank is audibly chordal.

◆ set_spread()

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

How far the combs are spread from unison (0) to their full plate ratios (1).

◆ set_strike_level()

void pulp::signal::drum::CymbalVoice::set_strike_level ( double level)
inline

◆ set_strike_ms()

void pulp::signal::drum::CymbalVoice::set_strike_ms ( double ms)
inline

◆ set_tone_hz()

void pulp::signal::drum::CymbalVoice::set_tone_hz ( double hz)
inline

Output tone controls: a lowpass on top and a highpass to clear the mud the shifter leaves at the bottom.

◆ set_tune_hz()

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

Frequency the lowest comb rings at, in Hz.

◆ set_upper_highpass_hz()

void pulp::signal::drum::CymbalVoice::set_upper_highpass_hz ( double hz)
inline

Base cutoff of the extra high-pass stages in the upper half of the comb bank.

Successive upper modes rise from this corner; zero bypasses them.

◆ set_variation()

void pulp::signal::drum::CymbalVoice::set_variation ( double amount)
inline

How much successive excitations differ, 0 (bit-identical) to 1.

This is a convenience over set_hit_life(), not a separate policy: it selects one of the two body-preserving modes, because a cymbal that reset its body on every hit would cut its own ring off.

◆ set_velocity_feedback()

void pulp::signal::drum::CymbalVoice::set_velocity_feedback ( double amount)
inline

Velocity-dependent feedback and high-mode emphasis.

Both are explicit because feedback also changes ring time: zero keeps decay independent of velocity; one doubles the top comb's T60 at full velocity. A positive value opts into the harder-hit-rings-harder cymbal behavior.

◆ set_velocity_high_mode_db()

void pulp::signal::drum::CymbalVoice::set_velocity_high_mode_db ( double db)
inline

◆ 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

◆ comb_count

int pulp::signal::drum::CymbalVoice::comb_count = 8
staticconstexpr

Number of parallel combs.

◆ comb_ratios

double pulp::signal::drum::CymbalVoice::comb_ratios[comb_count]
staticconstexpr
Initial value:
= {1.00, 1.47, 1.83, 2.21,
2.59, 3.16, 3.71, 4.31}

Plate-like ratios for the comb delays.

Deliberately not a harmonic series — a bank at integer ratios would reinforce one pitch, which is the opposite of what a cymbal does.


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