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. | |
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.
|
inline |
|
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.
|
inline |
|
inlineinherited |
Whether the voice still has something to render.
A kit uses this to skip voices entirely rather than summing silence.
|
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.
|
inlineinherited |
Starts a hit. velocity is in [0, 1]; values outside are clamped.
|
inlineoverrideprotectedvirtual |
Implements pulp::signal::drum::Voice.
|
inlineoverrideprotectedvirtual |
Starts a hit at the given velocity, already clamped to [0, 1].
Implements pulp::signal::drum::Voice.
|
inlineoverrideprotectedvirtual |
Sizes buffers and fixes coefficients.
The only hook allowed to allocate.
Implements pulp::signal::drum::Voice.
|
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.
|
inline |
|
inline |
|
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.
|
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.
|
inlinenoexceptinherited |
|
inlineinherited |
Fixes the sample rate and clears all state.
The only method a derived voice may allocate in.
|
inlineinherited |
Adds num_samples of this voice's output into out.
|
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.
|
inlineoverrideprotectedvirtual |
Adds the voice's output into out. Must add, never assign.
Implements pulp::signal::drum::Voice.
|
inlineprotectedvirtualinherited |
Optional stereo realization.
The default is centre-panned and preserves exact mono-sum equivalence.
Reimplemented in pulp::signal::drum::ClapVoice.
|
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.
|
inlineinherited |
|
inline |
How long the bank rings, as a T60 in milliseconds.
|
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.
|
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.
|
inline |
|
inline |
How far the ratios are pushed further apart still, progressively.
|
inline |
|
inline |
|
inline |
Level of the noise part of the strike, and of the swept tone under it.
|
inlineoverridevirtual |
Reimplemented from pulp::signal::drum::Voice.
|
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.
|
inline |
How far the combs are spread from unison (0) to their full plate ratios (1).
|
inline |
|
inline |
|
inline |
Output tone controls: a lowpass on top and a highpass to clear the mud the shifter leaves at the bottom.
|
inline |
Frequency the lowest comb rings at, in 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.
|
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.
|
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.
|
inline |
|
inlineinherited |
|
inlineinherited |
|
inlineinherited |
|
inlineinherited |
|
staticconstexpr |
Number of parallel combs.
|
staticconstexpr |
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.