Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::KarplusStrongT< SampleType > Class Template Reference

A plucked string: a delay line fed back through a loss filter. More...

#include <karplus_strong.hpp>

Public Member Functions

void prepare (double sample_rate, double lowest_hz=20.0)
 Sizes the delay line for the lowest note the string will play.
void set_frequency (double hz)
void set_modulated_frequency (double hz)
 Uses the delay line's continuously interpolated reader for audio-rate pitch modulation.
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 high partials die than low ones, 0 (bright) to 1 (dark).
void set_stiffness (double amount)
 Inharmonicity, 0 (a true harmonic series) to 1 (bar-like stretch).
void set_pluck_position (double position)
 Where along the string it is excited, 0 (at the end) to 0.5 (the middle).
void set_dynamic_bandwidth_hz (double hz)
 Bandwidth of the excitation, in Hz — how hard the string is played.
void set_pick_direction (double amount)
 Direction of the pick, 0 to 1.
void reset ()
void pluck (bool restart=false)
 Excites the string.
bool is_active () const
double level () const
SampleType process (SampleType excitation)
 Advances one sample.

Detailed Description

template<typename SampleType = float>
class pulp::signal::KarplusStrongT< SampleType >

A plucked string: a delay line fed back through a loss filter.

The idea is that a string is a delay. A disturbance travels to the end, reflects, and comes back, and the round trip time is the period — so a buffer of noise circulating through a delay of that length is a plucked string, with no oscillator and no harmonic series written down anywhere. The harmonics appear because only frequencies whose period divides the loop survive going round it.

Three filters in the loop turn that from a curiosity into an instrument:

  • Loss. A gain below one, set from the decay time, is what makes the note stop. It is derived from the frequency rather than fixed, because the loop runs f0 times a second: the same per-trip loss gives a much shorter note at a high pitch, which is exactly what a real string does.
  • Damping. A lowpass in the loop makes high partials die faster than low ones, which every real string does and an ideal delay does not.
  • Stiffness. An allpass makes the loop's delay frequency-dependent, so the partials stretch slightly sharp of a true harmonic series. That inharmonicity is what separates a piano or a struck bar from a guitar, and it is audible long before it is measurable as a tuning error.

The excitation matters as much as the loop. pluck_position subtracts a delayed copy of the burst from itself, which cancels every partial with a node at that point — the reason a string plucked near the bridge is thin and bright and one plucked at the middle is round.

The fractional part of the loop length is taken by a first-order allpass rather than by interpolating between samples. Both approaches are standard, but they differ in a way that matters here: linear interpolation is itself a lowpass, and it sits inside the loop next to the damping filter, so its attenuation compounds on every round trip and shortens the note by an amount that depends on pitch. An allpass has unity magnitude at every frequency, so the decay stays the decay the caller asked for. The allpass delay is kept away from zero, where its pole and zero cancel on the unit circle and the delay becomes discontinuous.

Karplus & Strong, CMJ 7(2), 1983; the loss and stiffness extensions follow Jaffe & Smith, CMJ 7(2), 1983.

RT contract: prepare() allocates the delay line. Everything else, including pluck() and process(), allocates nothing and takes no locks.

Member Function Documentation

◆ is_active()

template<typename SampleType = float>
bool pulp::signal::KarplusStrongT< SampleType >::is_active ( ) const
inline

◆ level()

template<typename SampleType = float>
double pulp::signal::KarplusStrongT< SampleType >::level ( ) const
inline

◆ pluck()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::pluck ( bool restart = false)
inline

Excites the string.

The caller supplies the burst sample by sample through process; this only opens the window and clears the loop's contribution from any previous note if restart is set.

Leaving restart false is the physical behaviour — a string plucked again while ringing keeps what is already travelling on it — and is what stops a fast repeated figure sounding identical each time.

◆ prepare()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::prepare ( double sample_rate,
double lowest_hz = 20.0 )
inline

Sizes the delay line for the lowest note the string will play.

◆ process()

template<typename SampleType = float>
SampleType pulp::signal::KarplusStrongT< SampleType >::process ( SampleType excitation)
inline

Advances one sample.

excitation is the burst being injected; feed it noise for a pluck, silence once the burst is over.

◆ reset()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::reset ( )
inline

◆ set_damping()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_damping ( double amount)
inline

How much faster high partials die than low ones, 0 (bright) to 1 (dark).

◆ set_decay_seconds()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_decay_seconds ( double seconds)
inline

Time for the note to fall by 60 dB, in seconds.

◆ set_dynamic_bandwidth_hz()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_dynamic_bandwidth_hz ( double hz)
inline

Bandwidth of the excitation, in Hz — how hard the string is played.

A harder attack is brighter, not merely louder, so this is where playing dynamics belong. Setting it from velocity is what stops a soft note being the same note turned down.

◆ set_frequency()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_frequency ( double hz)
inline

◆ set_modulated_frequency()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_modulated_frequency ( double hz)
inline

Uses the delay line's continuously interpolated reader for audio-rate pitch modulation.

Decay and damping remain tied to the static frequency; returning to set_frequency() restores the lossless allpass path.

◆ set_pick_direction()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_pick_direction ( double amount)
inline

Direction of the pick, 0 to 1.

A one-pole on the excitation only; at 0 it is bypassed, and raising it darkens the attack the way striking with the flat of a pick does compared with its point.

◆ set_pluck_position()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_pluck_position ( double position)
inline

Where along the string it is excited, 0 (at the end) to 0.5 (the middle).

◆ set_stiffness()

template<typename SampleType = float>
void pulp::signal::KarplusStrongT< SampleType >::set_stiffness ( double amount)
inline

Inharmonicity, 0 (a true harmonic series) to 1 (bar-like stretch).


The documentation for this class was generated from the following file: