A plucked or struck string, as a percussion voice. More...
#include <string.hpp>
Public Member Functions | |
| StringVoice () | |
| void | set_tune_hz (double hz) |
| Pitch of the string, in Hz. | |
| void | set_decay_seconds (double seconds) |
| Time for the note to fall by 60 dB, in seconds. | |
| void | set_damping (double amount) |
| How much faster the upper partials die than the fundamental. | |
| void | set_stiffness (double amount) |
| Inharmonicity from string stiffness, 0 (a true harmonic series) to 1. | |
| void | set_pluck_position (double position) |
| Where along the string it is excited, 0 (at the end) to 0.5 (the middle). | |
| void | set_exciter_ms (double ms) |
| Length of the excitation burst in milliseconds. | |
| void | set_brightness_hz (double hz) |
| Bandwidth of the excitation in Hz at zero velocity, before the velocity response opens it. | |
| void | set_pick_direction (double amount) |
| Darkens the attack without retuning the string. | |
| void | set_restart_on_hit (bool restart) |
| Whether a new hit restarts the string or adds to what is still ringing. | |
| void | set_modulation (StringModulation mode) |
| Optional post-body modulation. | |
| void | set_modulation_mix (double mix) |
| void | set_modulation_ratio (double ratio) |
| void | set_fm_depth_octaves (double octaves) |
| void | set_lpg_amount (double amount) |
| Amount of the lowpass-gate stage after modulation. | |
| LowpassGate & | gate () |
| 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. | |
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 plucked or struck string, as a percussion voice.
The body is a delay line fed back through its own losses, so the harmonics are not written down anywhere – they are the frequencies whose period divides the loop, and everything else cancels going round. That is what separates this from an oscillator bank: the excitation is part of the sound, so the same string struck with a stick and plucked with a fingertip are genuinely different, and a second hit during the tail adds to what is still travelling rather than replacing it.
It sits in the percussion set because a struck string is percussion – a hammered dulcimer, a kalimba, a piano. What makes it a voice rather than a raw KarplusStrongT is the lifecycle every other drum here has: an excitation whose length and colour are controls, velocity that reaches timbre rather than only level, and the shared output stage.
Velocity drives the string's dynamic-level filter, which is the physical claim: playing harder puts more high-frequency energy into the string, so a hard note is brighter as well as louder. That is the same coupling the tom makes with its bend and the FM voices make with their index.
RT contract: prepare() allocates the delay lines. Everything else, including note_on and process, allocates nothing and takes no locks.
|
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 |
Bandwidth of the excitation in Hz at zero velocity, before the velocity response opens it.
Deliberately well below the audio band. This is the floor the velocity coupling works up from, so setting it wide leaves velocity nothing to do – the excitation is already passing everything and a hard hit sounds like a soft one turned up. A dark floor is what makes the coupling audible.
|
inline |
How much faster the upper partials die than the fundamental.
|
inline |
Time for the note to fall by 60 dB, in seconds.
Derived per round trip, so a high note decays faster at the same setting – as a real string does.
|
inline |
Length of the excitation burst in milliseconds.
Short is a hard mallet; longer is a fingertip.
|
inline |
|
inline |
Amount of the lowpass-gate stage after modulation.
Zero is a transparent bypass; one applies the full coupled level/brightness decay.
|
inline |
Optional post-body modulation.
mix crossfades from the unmodified string to the selected mode. FM bends the delay length, Ring multiplies by a sine, and Sync substitutes a slave saw reset by the string pitch. The independent FM path is armed on the next hit after FM is selected; this keeps the default and non-FM realtime paths at one string model.
|
inline |
|
inline |
|
inline |
|
inlineoverridevirtual |
Reimplemented from pulp::signal::drum::Voice.
|
inline |
Darkens the attack without retuning the string.
|
inline |
Where along the string it is excited, 0 (at the end) to 0.5 (the middle).
A comb, not a level: it removes the partials with a node at that point.
|
inline |
Whether a new hit restarts the string or adds to what is still ringing.
Adding is the physical behaviour and is why a fast repeated figure does not sound like one sample fired twice.
|
inline |
Inharmonicity from string stiffness, 0 (a true harmonic series) to 1.
This is what separates a struck bar or a piano from a guitar.
|
inline |
Pitch of the string, in Hz.
|
inlineinherited |
|
inlineinherited |
|
inlineinherited |
|
inlineinherited |