Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
zap.hpp
Go to the documentation of this file.
1#pragma once
2
8
9#include <algorithm>
10#include <cmath>
11
12namespace pulp::signal::drum {
13
39class ZapVoice : public Voice {
40public:
43 r.level_db = 15.0f;
44 r.bend_octaves = 1.0f;
45 // Velocity also opens the distortion, which reads as a brighter,
46 // sharper zap rather than a louder one.
47 r.brightness_octaves = 0.8f;
49 }
50
51 void set_tune_hz(double hz) { tune_hz_ = std::clamp(hz, 20.0, 4000.0); }
52
54 void set_pitch_sweep_octaves(double octaves) {
55 pitch_sweep_oct_ = std::clamp(octaves, 0.0, 6.0);
56 }
57 void set_pitch_sweep_ms(double ms) { pitch_sweep_ms_ = std::clamp(ms, 0.5, 500.0); }
58
60 void set_decay_ms(double ms) { decay_ms_ = std::clamp(ms, 10.0, 4000.0); }
61
62 void set_shape(PhaseDistortionShape shape) { shape_ = shape; }
63
66 void set_distortion(double amount) { distortion_ = std::clamp(amount, 0.0, 1.0); }
67 void set_distortion_ms(double ms) { distortion_ms_ = std::clamp(ms, 0.5, 500.0); }
68
70 void set_resonant_depth(double multiples) {
71 resonant_depth_ = std::clamp(multiples, 1.0, 32.0);
72 }
73
75 void set_detune_cents(double cents) { detune_cents_ = std::clamp(cents, 0.0, 100.0); }
76
80 void set_ring(double amount) { ring_ = std::clamp(amount, 0.0, 1.0); }
81 void set_ring_ratio(double ratio) { ring_ratio_ = std::clamp(ratio, 0.25, 16.0); }
82
83 LowpassGate& gate() { return gate_; }
84 OutputStage& output() { return output_; }
85 const OutputStage& output() const { return output_; }
86 OutputStage* output_stage() noexcept override { return &output_; }
87 int latency_samples() const noexcept override {
88 return output_.latency_samples();
89 }
90 OutputOversampling output_oversampling() const noexcept override {
91 return output_.oversampling();
92 }
94 output_.set_oversampling(factor);
95 }
96
97protected:
98 void on_prepare(double sample_rate) override {
99 osc_a_.set_sample_rate(sample_rate);
100 osc_b_.set_sample_rate(sample_rate);
101 amp_env_.set_sample_rate(sample_rate);
102 pitch_env_.set_sample_rate(sample_rate);
103 dcw_env_.set_sample_rate(sample_rate);
104 gate_.set_sample_rate(sample_rate);
105 dc_.reset();
106 output_.prepare(sample_rate);
107 }
108
109 void on_reset() override {
110 osc_a_.reset();
111 osc_b_.reset();
112 amp_env_.reset();
113 pitch_env_.reset();
114 dcw_env_.reset();
115 gate_.reset();
116 dc_.reset();
117 output_.reset();
118 ring_phase_ = 0.0;
119 }
120
121 void on_note_on(float velocity) override {
122 output_.reset_nonlinear_state();
123 output_.trigger();
124 const auto& response = velocity_response();
125 velocity_gain_ = response.gain(velocity);
126 applied_bend_ = pitch_sweep_oct_ + response.bend(velocity);
127 // Velocity opens the distortion as well as the pitch, so a harder hit
128 // is a sharper zap rather than the same zap turned up.
129 applied_distortion_ =
130 std::clamp(distortion_ * static_cast<double>(response.brightness_scale(velocity)),
131 0.0, 1.0);
132
133 osc_a_.reset();
134 osc_b_.reset();
135 osc_a_.set_shape(shape_);
136 osc_b_.set_shape(shape_);
137 osc_a_.set_resonant_depth(resonant_depth_);
138 osc_b_.set_resonant_depth(resonant_depth_);
139 ring_phase_ = 0.0;
140 dc_.reset();
141
142 pitch_env_.set_attack_ms(0.0);
143 pitch_env_.set_decay_time_constant_ms(pitch_sweep_ms_);
144 pitch_env_.trigger();
145
146 // The distortion envelope is its own decay, not a copy of the pitch
147 // one. Sharing them would make this an ordinary filtered drop.
148 dcw_env_.set_attack_ms(0.0);
149 dcw_env_.set_decay_time_constant_ms(distortion_ms_);
150 dcw_env_.trigger();
151
152 amp_env_.set_attack_ms(0.3);
153 amp_env_.set_decay_t60_ms(decay_ms_);
154 amp_env_.trigger();
155 }
156
157 bool on_is_active() const override {
158 return amp_env_.is_active() || output_.has_tail();
159 }
160
161 void render_add(float* out, int num_samples) override {
162 const double detune = std::pow(2.0, detune_cents_ / 1200.0);
163
164 for (int i = 0; i < num_samples; ++i) {
165 const double pitch = pitch_env_.process();
166 const double dcw = dcw_env_.process();
167 const double amplitude = amp_env_.process();
168
169 const double f = tune_hz_ * std::exp2(applied_bend_ * pitch);
170 osc_a_.set_frequency(f);
171 osc_b_.set_frequency(f * detune);
172
173 const double warp = applied_distortion_ * dcw;
174 osc_a_.set_amount(warp);
175 osc_b_.set_amount(warp);
176
177 double mixed = 0.5 * (static_cast<double>(osc_a_.process()) +
178 static_cast<double>(osc_b_.process()));
179
180 if (ring_ > 0.0) {
181 ring_phase_ += f * ring_ratio_ / sample_rate();
182 if (ring_phase_ >= 1.0) ring_phase_ -= std::floor(ring_phase_);
183 mixed += ring_ * mixed *
184 std::cos(2.0 * 3.14159265358979323846 * ring_phase_);
185 }
186
187 // Warping a cosine's phase moves its mean off zero, so the voice
188 // needs a DC blocker that an ordinary oscillator would not.
189 const double centred = dc_.process(static_cast<float>(mixed));
190 const double gated = gate_.process(static_cast<float>(centred * amplitude),
191 amplitude);
192 out[i] += static_cast<float>(
193 output_.process(static_cast<float>(gated)) * velocity_gain_);
194 }
195 }
196
197private:
198 double tune_hz_ = 220.0;
199 double pitch_sweep_oct_ = 2.5;
200 double pitch_sweep_ms_ = 35.0;
201 double decay_ms_ = 350.0;
203 double distortion_ = 0.8;
204 double distortion_ms_ = 60.0;
205 double resonant_depth_ = 12.0;
206 double detune_cents_ = 9.0;
207 double ring_ = 0.0;
208 double ring_ratio_ = 1.5;
209
210 PhaseDistortionOsc osc_a_;
211 PhaseDistortionOsc osc_b_;
212 DecayEnvelope64 amp_env_;
213 DecayEnvelope64 pitch_env_;
214 DecayEnvelope64 dcw_env_;
215 LowpassGate gate_;
217 OutputStage output_;
218
219 double velocity_gain_ = 1.0;
220 double applied_bend_ = 2.5;
221 double applied_distortion_ = 0.8;
222 double ring_phase_ = 0.0;
223};
224
225} // namespace pulp::signal::drum
One-pole DC blocker (first-order high-pass).
Definition dc_blocker.hpp:28
double sample_rate() const
Definition voice.hpp:105
void set_velocity_response(const VelocityResponse &r)
Definition voice.hpp:129
float velocity() const
Definition voice.hpp:104
const VelocityResponse & velocity_response() const
Definition voice.hpp:130
LowpassGate & gate()
Definition zap.hpp:83
void set_pitch_sweep_octaves(double octaves)
Depth and time constant of the pitch drop, in octaves.
Definition zap.hpp:54
void set_detune_cents(double cents)
Detune of the second oscillator, in cents.
Definition zap.hpp:75
bool on_is_active() const override
Definition zap.hpp:157
void set_ring_ratio(double ratio)
Definition zap.hpp:81
void on_prepare(double sample_rate) override
Sizes buffers and fixes coefficients.
Definition zap.hpp:98
void set_distortion(double amount)
Distortion depth at the start of the hit, and how quickly it collapses.
Definition zap.hpp:66
void render_add(float *out, int num_samples) override
Adds the voice's output into out. Must add, never assign.
Definition zap.hpp:161
void set_ring(double amount)
Depth of a ring modulation applied to the pair, and its ratio to the tuning.
Definition zap.hpp:80
int latency_samples() const noexcept override
Fixed host-rate delay introduced by this voice's selected output quality.
Definition zap.hpp:87
void set_pitch_sweep_ms(double ms)
Definition zap.hpp:57
void set_output_oversampling(OutputOversampling factor) override
Definition zap.hpp:93
OutputStage * output_stage() noexcept override
The terminal saturation and degradation stage, at the generic voice boundary.
Definition zap.hpp:86
OutputStage & output()
Definition zap.hpp:84
void set_tune_hz(double hz)
Definition zap.hpp:51
void set_shape(PhaseDistortionShape shape)
Definition zap.hpp:62
const OutputStage & output() const
Definition zap.hpp:85
void set_decay_ms(double ms)
How long the note lasts, as a T60.
Definition zap.hpp:60
void on_note_on(float velocity) override
Starts a hit at the given velocity, already clamped to [0, 1].
Definition zap.hpp:121
void set_resonant_depth(double multiples)
Highest carrier multiple the resonant shapes reach.
Definition zap.hpp:70
void on_reset() override
Clears every piece of state the voice carries, so is_active() becomes false and the next render start...
Definition zap.hpp:109
void set_distortion_ms(double ms)
Definition zap.hpp:67
OutputOversampling output_oversampling() const noexcept override
Output quality at the generic voice boundary.
Definition zap.hpp:90
ZapVoice()
Definition zap.hpp:41
Definition clap.hpp:13
OutputOversampling
Realtime quality choices for the shared drum output nonlinearity.
Definition output_stage.hpp:20
OutputStageT< float > OutputStage
Definition output_stage.hpp:384
PhaseDistortionOscT< float > PhaseDistortionOsc
Definition phase_distortion.hpp:116
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ i
Definition formant_filter_bank.hpp:20
PhaseDistortionShape
Which phase warp a PhaseDistortionOscT applies.
Definition phase_distortion.hpp:9
@ resonant_saw
a swept carrier windowed by a falling ramp
Definition phase_distortion.hpp:12
@ gated
Flat body, hard cut.
Definition nonlin_ambience_design.hpp:245
DecayEnvelopeT< double > DecayEnvelope64
Definition decay_envelope.hpp:201
@ detune
Sub-semitone offset scaled by the pedal. Dry on.
Definition pitch_shifter.hpp:171
LowpassGateT< float > LowpassGate
Definition lowpass_gate.hpp:138
How hard a hit changes the sound, beyond changing how loud it is.
Definition velocity.hpp:33