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

The Hordijk shift-register / DAC chaos sequencer. More...

#include <rungler.hpp>

Public Member Functions

 RunglerT ()
void prepare (double sample_rate)
void set_reg_bits (int n)
 Changing the register length reloads the seed pattern.
int reg_bits () const
void set_dac_bits (int d)
int dac_bits () const
void set_feedback_tap (int tap)
int feedback_tap () const
void set_range_v (double volts)
double range_v () const
void set_external_data (bool on)
bool external_data () const
void set_seed_pattern (std::uint32_t pattern)
std::uint32_t seed_pattern () const
void apply_reset_edge ()
 Verb 2 — and the one documented exception to "continuous outputs hold": the register goes back to the seed pattern and the output steps to DAC(seed) immediately, so a wandering line can be re-pinned live.
void reset ()
std::uint32_t register_bits () const
 Current register contents, bit i at bit position i.
int dac_code () const
 Current DAC code in 0 .. 2^D − 1.
SampleType value () const
SampleType process (bool run_high, bool reset_edge, bool clock_edge, bool data_in=false)
 Advances one sample.

Static Public Member Functions

static constexpr int latency_samples ()

Static Public Attributes

static constexpr int kMinBits = 4
 Shift-register length.
static constexpr int kMaxBits = 16
static constexpr int kDefaultBits = 8
static constexpr int kMinDacBits = 1
 DAC width — 2^D output levels.
static constexpr int kMaxDacBits = 4
static constexpr int kDefaultDacBits = 3
static constexpr std::uint32_t kSeedPattern = 0b10110100u
 Initial register pattern, restored by reset() and by a reset edge.
static constexpr int kFeedbackTap = 0
 Register index XOR-ed with the last stage to form the serial input.
static constexpr double kRangeV = 2.0
 Output span in volts: the DAC covers [−range_v, +range_v].
static constexpr double kMaxRangeV = 1000.0
 Ceiling on the output span.

Detailed Description

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

The Hordijk shift-register / DAC chaos sequencer.

(lineage — informative) The rungler is Rob Hordijk's signature circuit from the Blippoo Box: an N-stage shift register whose serial input is fed back from the register itself, clocked by an oscillator, with a few register bits driving a small DAC whose stepped voltage modulates the oscillators — a loop that produces smooth, quasi-periodic chaos, roughly predictable yet never exactly repeating [Rob Hordijk, "The Blippoo Box: A Chaotic Electronic Music Instrument, Bent by Design," Leonardo Music Journal 19 (2009), 35–43, MIT Press]. The topology is cited; the specific tap positions are a design parameter, because the paper describes the concept rather than one canonical wiring and no citable exact tap map exists. Those are our engineering choices, not cited constants.

Why it sounds "smoothly chaotic." With pure self-feedback the register is a finite-state machine over 2^N states, so the output is strictly periodic — but the period can be very long and the orbit visits its DAC levels in an order with no small repeating unit, which the ear reads as evolving, roughly predictable, never quite looping. XOR-ing an external data bit perturbs the state each clock, lengthening and reshaping the orbit, so a slowly changing input voltage steers the chaos without ever making it random. The compositional value is deterministic long-form variation: controllable, but not memorizable.

The bound is provable, not measured (series law 1 + law 8). The only feedback is a 1-bit XOR into the register; there is no gain-carrying analog nonlinearity, so no small-signal-gain compensation applies. The output is a D-bit DAC code mapped affinely onto [−range_v, +range_v], so |y| ≤ range_v by construction for any clock or data sequence whatsoever. That is the invariant a registry worst_case_gain cites, and the suite asserts it over adversarial data rather than typical data.

No oversampling, no smoothing. The stepped hold is the sound. There is no continuous nonlinearity here to alias, so band-limiting would only remove the intended edge; a caller who wants the steps softened puts a SlewLimiterT downstream (the "drunken glide" patch).

The all-zero register is an absorbing state. With XOR feedback, 0 ⊕ 0 is 0 forever, so a seed pattern of 0 produces a constant −range_v. That is left as-is rather than remapped the way Xorshift32 remaps a zero seed: the seed pattern is a musical parameter whose neighbours must stay meaningful, and silently substituting a different pattern for one value would make it non-monotone. Enabling external_data kicks the register out of the state on the first data_in bit that is 1.

USE. Generative pitch source — rungler CV → QuantizeScaleT → oscillator: the archetypal self-playing patch, always in key, never quite repeating. Timbral chaos — into a filter cutoff or a wavefolder depth for burbling, semi-predictable motion. Cross-modulation — clock the rungler from an oscillator whose pitch the rungler modulates (caller-wired) for the Blippoo's audio-rate loop. Tamed — into a SlewLimiterT for a wandering portamento line.

RT contract: as the header. Integer bit operations only.

Constructor & Destructor Documentation

◆ RunglerT()

template<typename SampleType = float>
pulp::signal::RunglerT< SampleType >::RunglerT ( )
inline

Member Function Documentation

◆ apply_reset_edge()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::apply_reset_edge ( )
inline

Verb 2 — and the one documented exception to "continuous outputs hold": the register goes back to the seed pattern and the output steps to DAC(seed) immediately, so a wandering line can be re-pinned live.

◆ dac_bits()

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::dac_bits ( ) const
inline

◆ dac_code()

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::dac_code ( ) const
inline

Current DAC code in 0 .. 2^D − 1.

◆ external_data()

template<typename SampleType = float>
bool pulp::signal::RunglerT< SampleType >::external_data ( ) const
inline

◆ feedback_tap()

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::feedback_tap ( ) const
inline

◆ latency_samples()

template<typename SampleType = float>
constexpr int pulp::signal::RunglerT< SampleType >::latency_samples ( )
inlinestaticconstexpr

◆ prepare()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::prepare ( double sample_rate)
inline

◆ process()

template<typename SampleType = float>
SampleType pulp::signal::RunglerT< SampleType >::process ( bool run_high,
bool reset_edge,
bool clock_edge,
bool data_in = false )
inline

Advances one sample.

data_in is XOR-ed into the serial input when external_data is enabled — the paper's "bent by design" input, most naturally driven from a ComparatorT (kit) on any signal in the patch.

◆ range_v()

template<typename SampleType = float>
double pulp::signal::RunglerT< SampleType >::range_v ( ) const
inline

◆ reg_bits()

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::reg_bits ( ) const
inline

◆ register_bits()

template<typename SampleType = float>
std::uint32_t pulp::signal::RunglerT< SampleType >::register_bits ( ) const
inline

Current register contents, bit i at bit position i.

◆ reset()

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

◆ seed_pattern()

template<typename SampleType = float>
std::uint32_t pulp::signal::RunglerT< SampleType >::seed_pattern ( ) const
inline

◆ set_dac_bits()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::set_dac_bits ( int d)
inline

◆ set_external_data()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::set_external_data ( bool on)
inline

◆ set_feedback_tap()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::set_feedback_tap ( int tap)
inline

◆ set_range_v()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::set_range_v ( double volts)
inline

◆ set_reg_bits()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::set_reg_bits ( int n)
inline

Changing the register length reloads the seed pattern.

A shift register whose length changes mid-orbit has no meaningful "same state" to keep — truncating drops bits and extending invents them — so the deterministic answer is to start the new length from the seed rather than from a silently reinterpreted bit pattern.

◆ set_seed_pattern()

template<typename SampleType = float>
void pulp::signal::RunglerT< SampleType >::set_seed_pattern ( std::uint32_t pattern)
inline

◆ value()

template<typename SampleType = float>
SampleType pulp::signal::RunglerT< SampleType >::value ( ) const
inline

Member Data Documentation

◆ kDefaultBits

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kDefaultBits = 8
staticconstexpr

◆ kDefaultDacBits

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kDefaultDacBits = 3
staticconstexpr

◆ kFeedbackTap

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kFeedbackTap = 0
staticconstexpr

Register index XOR-ed with the last stage to form the serial input.

[design parameter] default 0, range 0 .. N − 2.

◆ kMaxBits

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kMaxBits = 16
staticconstexpr

◆ kMaxDacBits

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kMaxDacBits = 4
staticconstexpr

◆ kMaxRangeV

template<typename SampleType = float>
double pulp::signal::RunglerT< SampleType >::kMaxRangeV = 1000.0
staticconstexpr

Ceiling on the output span.

Two orders past the documented range — no modular standard exceeds ±15 V — so it only catches a nonsense value. The bound is what makes |y| <= range_v a FINITE guarantee. [design parameter] default 1000 V ceiling, range 15 .. 100000 V.

◆ kMinBits

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kMinBits = 4
staticconstexpr

Shift-register length.

[design parameter] default 8, range 4 .. 16.

◆ kMinDacBits

template<typename SampleType = float>
int pulp::signal::RunglerT< SampleType >::kMinDacBits = 1
staticconstexpr

DAC width — 2^D output levels.

D = 3 gives the classic eight steps. [design parameter] default 3, range 1 .. 4.

◆ kRangeV

template<typename SampleType = float>
double pulp::signal::RunglerT< SampleType >::kRangeV = 2.0
staticconstexpr

Output span in volts: the DAC covers [−range_v, +range_v].

[design parameter] default 2 V, range 0.5 .. 5 V.

◆ kSeedPattern

template<typename SampleType = float>
std::uint32_t pulp::signal::RunglerT< SampleType >::kSeedPattern = 0b10110100u
staticconstexpr

Initial register pattern, restored by reset() and by a reset edge.

[design parameter] default 0b10110100 (180), range 0 .. 65535.


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