Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
distortion.hpp
Go to the documentation of this file.
1#pragma once
2
98
102#include <pulp/signal/units.hpp>
103
104#include <algorithm>
105#include <cmath>
106#include <cstdint>
107#include <utility>
108
109namespace pulp::signal {
110
112enum class DiodeModel : std::uint8_t {
116};
117
119enum class ClipperTopology : std::uint8_t {
122};
123
124namespace detail {
125
133 switch (model) {
135 network.saturation_current = 1e-6;
136 network.ideality = 1.1;
137 break;
138 case DiodeModel::led:
139 network.saturation_current = 1e-18;
140 network.ideality = 2.0;
141 break;
143 default:
144 network.saturation_current = 1e-14;
145 network.ideality = 1.0;
146 break;
147 }
148}
149
155inline void apply_symmetry(junction::JunctionPair& network, double symmetry) {
156 if (!std::isfinite(symmetry)) return;
157 const double s = std::clamp(symmetry, -1.0, 1.0);
158 network.leg_a = 1.0 / (1.0 + std::max(0.0, s));
159 network.leg_b = 1.0 + std::min(0.0, s);
160}
161
162} // namespace detail
163
167struct ClipperSolverConfig;
168
169namespace detail {
170
194template <typename Residual, typename Derivative>
195inline double solve_safeguarded_newton(Residual&& f, Derivative&& df, double lo, double hi,
196 double initial, double alternate, double tolerance,
197 int max_iterations, int& iterations_out) {
198 // Order the bracket and pad it, so a root sitting exactly on an endpoint is
199 // still strictly interior.
200 if (lo > hi) std::swap(lo, hi);
201 const double pad = 1e-9 + 0.01 * (hi - lo);
202 lo -= pad;
203 hi += pad;
204
205 double f_lo = f(lo);
206 double f_hi = f(hi);
207 // If the physical bracket does not actually straddle the root, widen a
208 // bounded number of times rather than failing. Bounded because an unbounded
209 // widening loop is exactly the RT hazard the iteration cap exists to
210 // prevent.
211 for (int widen = 0; widen < 8 && f_lo * f_hi > 0.0; ++widen) {
212 const double span = std::max(hi - lo, 1e-3);
213 lo -= span;
214 hi += span;
215 f_lo = f(lo);
216 f_hi = f(hi);
217 }
218
219 // Two candidate starts: where the solve landed last sample (excellent when
220 // the signal is moving slowly, which is the common case) and the analytic
221 // conduction estimate (excellent on a cold start or a fast transient).
222 // Taking the better of the two by residual costs two evaluations and saves
223 // the dozens of iterations a bad start would need.
224 double v = std::clamp(initial, lo, hi);
225 const double candidate = std::clamp(alternate, lo, hi);
226 if (std::abs(f(candidate)) < std::abs(f(v))) v = candidate;
227 int iterations = 0;
228 for (; iterations < max_iterations; ++iterations) {
229 const double fv = f(v);
230 if (std::abs(fv) < tolerance) break;
231
232 // Maintain the bracket from every evaluation, so the fallback keeps
233 // getting tighter even while Newton is doing the work.
234 if (f_lo * fv < 0.0) {
235 hi = v;
236 f_hi = fv;
237 } else {
238 lo = v;
239 f_lo = fv;
240 }
241
242 const double dfv = df(v);
243 double next = dfv > 0.0 ? v - fv / dfv : v;
244 // Outside the bracket, or non-finite: bisect instead. This is what
245 // makes the iteration globally convergent rather than merely usually
246 // convergent.
247 if (!std::isfinite(next) || next <= lo || next >= hi) next = 0.5 * (lo + hi);
248 v = next;
249 }
250 iterations_out = iterations;
251 return std::isfinite(v) ? v : 0.0;
252}
253
254} // namespace detail
255
262 static constexpr double kNewtonTolerance = 1e-9;
263
268 static constexpr int kMaxNewtonIterations = 8;
269
273
277 static constexpr double kAdaaEpsilon = 1e-5;
278};
279
280namespace detail {
281
291 double history_voltage, double history_flow,
292 double source_current, double flow_weight,
293 double c_over_step, double resistance,
294 double initial, int& iterations_out) {
295 const auto residual = [&](double v) {
296 const double flow = source_current - v / resistance - network.current(v);
297 return c_over_step * (v - history_voltage) -
298 flow_weight * (history_flow + flow);
299 };
300 const auto derivative = [&](double v) {
301 return c_over_step +
302 flow_weight * (1.0 / resistance + network.conductance(v));
303 };
304
305 const double open_circuit = source_current * resistance;
307 residual, derivative,
308 /*lo=*/std::min({0.0, history_voltage, open_circuit}),
309 /*hi=*/std::max({0.0, history_voltage, open_circuit}),
310 initial, network.conduction_estimate(open_circuit, resistance),
313}
314
323inline double solve_tr_bdf2(const junction::JunctionPair& network, double previous_voltage,
324 double previous_source_current, double source_current,
325 double capacitance, double timestep, double resistance,
326 int& iterations_out) {
327 constexpr double gamma = 2.0 - 1.4142135623730950488016887242097;
328 constexpr double bdf2_step = (1.0 - gamma) / (2.0 - gamma);
329 constexpr double stage_weight = 1.0 / (gamma * (2.0 - gamma));
330
331 const double source_at_stage =
332 previous_source_current + gamma * (source_current - previous_source_current);
333 const double previous_flow = previous_source_current - previous_voltage / resistance -
334 network.current(previous_voltage);
335 const double c_over_stage_t = capacitance / (gamma * timestep);
336 int stage_iterations = 0;
337 const double stage_voltage = solve_implicit_flow_stage(
338 network, previous_voltage, previous_flow, source_at_stage,
339 /*flow_weight=*/0.5, c_over_stage_t, resistance,
340 /*initial=*/previous_voltage, stage_iterations);
341
342 const double bdf2_history =
343 stage_weight * stage_voltage + (1.0 - stage_weight) * previous_voltage;
344 const double c_over_bdf2_t = capacitance / (bdf2_step * timestep);
345 int final_iterations = 0;
346 const double result = solve_implicit_flow_stage(
347 network, bdf2_history, /*history_flow=*/0.0, source_current,
348 /*flow_weight=*/1.0, c_over_bdf2_t, resistance,
349 /*initial=*/stage_voltage, final_iterations);
350 iterations_out = stage_iterations + final_iterations;
351 return result;
352}
353
354} // namespace detail
355
361template <typename SampleType = float>
363public:
365
369 static constexpr double kResistanceDefault = 10000.0;
370
374 static constexpr double kCapacitanceDefault = 51e-12;
375
377 detail::apply_diode_model(network_, Model::silicon);
378 detail::apply_symmetry(network_, 0.0);
379 }
380
381 void prepare(double sample_rate) {
382 sample_rate_ = std::isfinite(sample_rate) && sample_rate > 0.0 ? sample_rate
383 : sample_rate_;
384 timestep_ = 1.0 / sample_rate_;
385 reset();
386 }
387
388 void set_diode_model(Model model) { detail::apply_diode_model(network_, model); }
389
391 void set_symmetry(double symmetry) { detail::apply_symmetry(network_, symmetry); }
392
393 void set_resistance(double ohms) {
394 if (std::isfinite(ohms)) resistance_ = std::max(ohms, 1.0);
395 }
396
399 void set_capacitance(double farads) {
400 if (std::isfinite(farads)) capacitance_ = std::max(farads, 0.0);
401 }
402
429
430 void reset() {
431 voltage_ = 0.0;
432 previous_source_current_ = 0.0;
433 previous_voltage_ = 0.0;
434 last_iterations_ = 0;
435 }
436
439 int last_iteration_count() const { return last_iterations_; }
440
441 SampleType process(SampleType input) {
442 if (!std::isfinite(static_cast<double>(input))) {
443 reset();
444 return SampleType{0};
445 }
446 const double vin = static_cast<double>(input);
447 const double v = solve(vin);
448 if (!std::isfinite(v)) {
449 reset();
450 return SampleType{0};
451 }
452 previous_source_current_ = vin / resistance_;
453 previous_voltage_ = v;
454 voltage_ = v;
455 return static_cast<SampleType>(snap_to_zero(v));
456 }
457
460 double voltage() const { return voltage_; }
461
464 double resistive_residual(double vin, double v) const {
465 return (vin - v) / resistance_ - network_.current(v);
466 }
467
468private:
469 double solve(double vin) {
470 // With no capacitor the ODE is not an ODE: `C·dv/dt` vanishes and the
471 // circuit equation is purely algebraic, `(vin − v)/R = i_D(v)`. Pushing
472 // that through trapezoidal rule would NOT solve it — TR at `C = 0`
473 // enforces `g(vₙ) = −g(vₙ₋₁)`, so the residual alternates sign every
474 // sample instead of vanishing. That is the textbook non-L-stable
475 // behaviour of TR at a stiff limit, not a bug in the solve, and it is
476 // why the degenerate case gets the algebraic equation directly.
477 const bool memoryless = capacitance_ <= 0.0;
478 if (!memoryless) {
479 return detail::solve_tr_bdf2(network_, previous_voltage_, previous_source_current_,
480 vin / resistance_, capacitance_, timestep_, resistance_,
481 last_iterations_);
482 }
483
484 const auto residual = [&](double v) {
485 return network_.current(v) - (vin - v) / resistance_;
486 };
487 // Every term is strictly positive, so the residual is strictly
488 // increasing in v — which is what makes the bracketed fallback safe.
489 const auto derivative = [&](double v) {
490 return network_.conductance(v) + 1.0 / resistance_;
491 };
492
494 residual, derivative,
495 /*lo=*/std::min({0.0, previous_voltage_, vin}),
496 /*hi=*/std::max({0.0, previous_voltage_, vin}),
497 /*initial=*/previous_voltage_,
498 /*alternate=*/network_.conduction_estimate(vin, resistance_),
501 }
502
503 junction::JunctionPair network_{};
504 double sample_rate_ = 44100.0;
505 double timestep_ = 1.0 / 44100.0;
506 double resistance_ = kResistanceDefault;
507 double capacitance_ = kCapacitanceDefault;
508
509 double voltage_ = 0.0;
510 double previous_source_current_ = 0.0;
511 double previous_voltage_ = 0.0;
512 int last_iterations_ = 0;
513};
514
517
534template <typename SampleType = float>
536public:
539
543 static constexpr double kFeedbackResistanceDefault = 51000.0;
544
547 static constexpr double kInputResistanceDefault = 4700.0;
548
555 static constexpr double kKneeCornerHzDefault = 720.0;
556
558 detail::apply_diode_model(network_, Model::silicon);
559 detail::apply_symmetry(network_, 0.0);
560 update_capacitance();
561 }
562
563 void prepare(double sample_rate) {
564 sample_rate_ = std::isfinite(sample_rate) && sample_rate > 0.0 ? sample_rate
565 : sample_rate_;
566 timestep_ = 1.0 / sample_rate_;
567 ground_stage_.prepare(sample_rate_);
568 reset();
569 }
570
571 void set_topology(Topology topology) { topology_ = topology; }
572 Topology topology() const { return topology_; }
573
574 void set_diode_model(Model model) {
575 detail::apply_diode_model(network_, model);
576 ground_stage_.set_diode_model(model);
577 }
578
579 void set_symmetry(double symmetry) {
580 detail::apply_symmetry(network_, symmetry);
581 ground_stage_.set_symmetry(symmetry);
582 }
583
584 void set_feedback_resistance(double ohms) {
585 if (!std::isfinite(ohms)) return;
586 feedback_resistance_ = std::max(ohms, 1.0);
587 update_capacitance();
588 }
589
590 void set_input_resistance(double ohms) {
591 if (std::isfinite(ohms)) input_resistance_ = std::max(ohms, 1.0);
592 }
593
594 void set_knee_corner_hz(double hz) {
595 if (!std::isfinite(hz)) return;
596 knee_corner_hz_ = std::clamp(hz, 1.0, 20000.0);
597 update_capacitance();
598 }
599
600
605 double linear_gain() const { return feedback_resistance_ / input_resistance_; }
606
607 void reset() {
608 previous_source_current_ = 0.0;
609 previous_voltage_ = 0.0;
610 last_iterations_ = 0;
611 ground_stage_.reset();
612 }
613
615 return topology_ == Topology::to_ground ? ground_stage_.last_iteration_count()
616 : last_iterations_;
617 }
618
619 SampleType process(SampleType input) {
620 if (!std::isfinite(static_cast<double>(input))) {
621 reset();
622 return SampleType{0};
623 }
624 const double vin = static_cast<double>(input);
625 if (topology_ == Topology::to_ground) {
626 // A plain linear gain stage with the network shunting its output —
627 // which IS the diode clipper fed by a pre-gain, so it composes one
628 // rather than re-deriving the same circuit with the wrong
629 // capacitor. That distinction is not cosmetic: the feedback cap
630 // that sets the in-loop knee sits at 720 Hz, and reusing it here
631 // would put a shunt clipper behind an audio-band filter, which
632 // measurably tilts the supposedly level-independent plateau this
633 // topology exists to provide.
634 return ground_stage_.process(static_cast<SampleType>(vin * linear_gain()));
635 }
636
637 // In-loop: the same ODE with v ≡ −vout, R ≡ Rf, C ≡ Cf, and the input
638 // current vin/Rin as an extra forcing term.
639 const double forcing = vin / input_resistance_;
640 const double v = solve(forcing);
641 if (!std::isfinite(v)) {
642 reset();
643 return SampleType{0};
644 }
645 previous_source_current_ = forcing;
646 previous_voltage_ = v;
647 // v is −vout by the substitution, so the stage's output is its negation
648 // — an inverting stage, which is what the topology is.
649 return static_cast<SampleType>(snap_to_zero(-v));
650 }
651
652private:
653 void update_capacitance() {
654 constexpr double two_pi = 6.283185307179586476925286766559;
655 capacitance_ = 1.0 / (two_pi * feedback_resistance_ * knee_corner_hz_);
656 }
657
658 double solve(double source_current) {
659 return detail::solve_tr_bdf2(network_, previous_voltage_, previous_source_current_,
660 source_current, capacitance_, timestep_,
661 feedback_resistance_, last_iterations_);
662 }
663
664 junction::JunctionPair network_{};
665 Topology topology_ = Topology::in_loop;
666 double sample_rate_ = 44100.0;
667 double timestep_ = 1.0 / 44100.0;
668 double feedback_resistance_ = kFeedbackResistanceDefault;
669 double input_resistance_ = kInputResistanceDefault;
670 double knee_corner_hz_ = kKneeCornerHzDefault;
671 double capacitance_ = 0.0;
672
673 double previous_source_current_ = 0.0;
674 double previous_voltage_ = 0.0;
675 int last_iterations_ = 0;
676
678 DiodeClipperT<SampleType> ground_stage_{};
679};
680
683
702template <typename SampleType = float>
704public:
708 static constexpr double kPreGainDbMax = 12.0;
709
710 void prepare(double sample_rate) {
711 pre_.prepare(static_cast<SampleType>(sample_rate));
712 post_.prepare(static_cast<SampleType>(sample_rate));
713 reset();
714 }
715
716 void set_pre_tone_hz(double hz) {
717 if (!std::isfinite(hz)) return;
718 pre_hz_ = std::clamp(hz, 20.0, 20000.0);
719 pre_.set_cutoff(static_cast<SampleType>(pre_hz_));
720 }
721
722 void set_post_tone_hz(double hz) {
723 if (!std::isfinite(hz)) return;
724 post_hz_ = std::clamp(hz, 20.0, 20000.0);
725 post_.set_cutoff(static_cast<SampleType>(post_hz_));
726 }
727
730 void set_pre_gain_db(double db) {
731 if (!std::isfinite(db)) return;
732 pre_gain_ = units::db_to_linear(std::clamp(db, -kPreGainDbMax, kPreGainDbMax));
733 }
734
736 void set_tone_mix(double mix01) {
737 if (std::isfinite(mix01)) mix_ = std::clamp(mix01, 0.0, 1.0);
738 }
739
740 void reset() {
741 pre_.reset();
742 post_.reset();
743 }
744
748 SampleType process_pre(SampleType input) {
749 if (!std::isfinite(static_cast<double>(input))) {
750 reset();
751 return SampleType{0};
752 }
753 const auto out = pre_.process(input);
754 const double shaped =
755 static_cast<double>(out.lowpass) + pre_gain_ * static_cast<double>(out.highpass);
756 const double result = static_cast<double>(input) +
757 mix_ * (shaped - static_cast<double>(input));
758 if (!std::isfinite(result)) {
759 reset();
760 return SampleType{0};
761 }
762 return static_cast<SampleType>(result);
763 }
764
766 SampleType process_post(SampleType input) {
767 if (!std::isfinite(static_cast<double>(input))) {
768 reset();
769 return SampleType{0};
770 }
771 const double filtered = static_cast<double>(post_.process_lowpass(input));
772 const double result = static_cast<double>(input) +
773 mix_ * (filtered - static_cast<double>(input));
774 if (!std::isfinite(result)) {
775 reset();
776 return SampleType{0};
777 }
778 return static_cast<SampleType>(result);
779 }
780
781private:
783 TptFilterT<SampleType> post_{};
784 double pre_hz_ = 720.0;
785 double post_hz_ = 4000.0;
786 double pre_gain_ = 1.0;
787 double mix_ = 1.0;
788};
789
792
793} // namespace pulp::signal
The diode-to-ground clipper: series resistor, shunt diode network, optional shunt capacitor.
Definition distortion.hpp:362
void reset()
Antiderivative antialiasing is NOT offered on this class, deliberately.
Definition distortion.hpp:430
void prepare(double sample_rate)
Definition distortion.hpp:381
DiodeModel Model
Definition distortion.hpp:364
double voltage() const
The node voltage without advancing — for tests that check the ODE's residual directly.
Definition distortion.hpp:460
DiodeClipperT()
Definition distortion.hpp:376
int last_iteration_count() const
Iterations the last solve took.
Definition distortion.hpp:439
SampleType process(SampleType input)
Definition distortion.hpp:441
static constexpr double kResistanceDefault
Definition distortion.hpp:369
void set_resistance(double ohms)
Definition distortion.hpp:393
static constexpr double kCapacitanceDefault
Definition distortion.hpp:374
void set_symmetry(double symmetry)
−1 = single diode (half-wave), 0 = matched pair, +1 = mismatched pair.
Definition distortion.hpp:391
void set_diode_model(Model model)
Definition distortion.hpp:388
double resistive_residual(double vin, double v) const
(vin − v)/R − i_D(v), the residual the memoryless (C = 0) solution must drive to zero.
Definition distortion.hpp:464
void set_capacitance(double farads)
Zero is the degenerate memoryless configuration: a solver limit case for tests, not a shipped preset.
Definition distortion.hpp:399
The op-amp gain stage with the diode network either at its output or across its feedback resistor.
Definition distortion.hpp:535
int last_iteration_count() const
Definition distortion.hpp:614
void set_feedback_resistance(double ohms)
Definition distortion.hpp:584
static constexpr double kFeedbackResistanceDefault
Definition distortion.hpp:543
static constexpr double kInputResistanceDefault
Definition distortion.hpp:547
static constexpr double kKneeCornerHzDefault
Definition distortion.hpp:555
void set_topology(Topology topology)
Definition distortion.hpp:571
ClipperTopology Topology
Definition distortion.hpp:538
void set_input_resistance(double ohms)
Definition distortion.hpp:590
Topology topology() const
Definition distortion.hpp:572
double linear_gain() const
The stage's linear (small-signal) gain, Rf/Rin — the bound the loop can never exceed at frequencies b...
Definition distortion.hpp:605
void set_knee_corner_hz(double hz)
Definition distortion.hpp:594
FeedbackClipperT()
Definition distortion.hpp:557
void reset()
Definition distortion.hpp:607
DiodeModel Model
Definition distortion.hpp:537
void prepare(double sample_rate)
Definition distortion.hpp:563
SampleType process(SampleType input)
Definition distortion.hpp:619
void set_diode_model(Model model)
Definition distortion.hpp:574
void set_symmetry(double symmetry)
Definition distortion.hpp:579
Pre/post tone shaping around the clipper: a two-pole tilt pair.
Definition distortion.hpp:703
void set_pre_tone_hz(double hz)
Definition distortion.hpp:716
void prepare(double sample_rate)
Definition distortion.hpp:710
SampleType process_post(SampleType input)
The post stage: a plain one-pole low-pass, the de-fizz.
Definition distortion.hpp:766
void set_post_tone_hz(double hz)
Definition distortion.hpp:722
void set_pre_gain_db(double db)
Pre-emphasis shelf gain in dB.
Definition distortion.hpp:730
void reset()
Definition distortion.hpp:740
SampleType process_pre(SampleType input)
The pre-emphasis stage: a high shelf built from the one-pole's complementary outputs,...
Definition distortion.hpp:748
static constexpr double kPreGainDbMax
Definition distortion.hpp:708
void set_tone_mix(double mix01)
Linear crossfade of the shaped stage against its bypass.
Definition distortion.hpp:736
First-order TPT (trapezoidal integration) filter.
Definition tpt_filter.hpp:30
The semiconductor junction law, once.
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
void apply_diode_model(junction::JunctionPair &network, DiodeModel model)
The (Is, n) pair for a device family.
Definition distortion.hpp:132
void apply_symmetry(junction::JunctionPair &network, double symmetry)
symmetry ∈ [−1, +1] → the two legs' reciprocal series counts.
Definition distortion.hpp:155
double solve_implicit_flow_stage(const junction::JunctionPair &network, double history_voltage, double history_flow, double source_current, double flow_weight, double c_over_step, double resistance, double initial, int &iterations_out)
Solves one implicit stage of C·dv/dt = source_current − v/R − i_D(v).
Definition distortion.hpp:290
double solve_tr_bdf2(const junction::JunctionPair &network, double previous_voltage, double previous_source_current, double source_current, double capacitance, double timestep, double resistance, int &iterations_out)
One L-stable TR-BDF2 step of C·dv/dt = source_current(t) − v/R − i_D(v).
Definition distortion.hpp:323
double solve_safeguarded_newton(Residual &&f, Derivative &&df, double lo, double hi, double initial, double alternate, double tolerance, int max_iterations, int &iterations_out)
One monotone implicit stage, solved by a SAFEGUARDED Newton iteration.
Definition distortion.hpp:195
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
FeedbackClipperT< float > FeedbackClipper
Definition distortion.hpp:681
ToneStackT< double > ToneStack64
Definition distortion.hpp:791
DiodeClipperT< double > DiodeClipper64
Definition distortion.hpp:516
ToneStackT< float > ToneStack
Definition distortion.hpp:790
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
DiodeClipperT< float > DiodeClipper
Definition distortion.hpp:515
@ e
Definition formant_filter_bank.hpp:20
DiodeModel
Diode device families. The members differ only in (Is, n).
Definition distortion.hpp:112
@ led
Smallest Is, highest n: latest onset, steepest slope.
Definition distortion.hpp:115
@ silicon
Small-signal switching class. Clips ~0.6 V, hard.
Definition distortion.hpp:113
@ germanium
Much larger Is: turns on earlier and softer.
Definition distortion.hpp:114
FeedbackClipperT< double > FeedbackClipper64
Definition distortion.hpp:682
ClipperTopology
Where the diode pair sits relative to the gain stage.
Definition distortion.hpp:119
@ in_loop
Across the op-amp's feedback resistor. Knee tracks drive.
Definition distortion.hpp:121
@ to_ground
Shunt across the stage output. Level-independent knee.
Definition distortion.hpp:120
Definition distortion.hpp:256
static constexpr double kAdaaEpsilon
ADAA fallback guard on |v[n] − v[n−1]|.
Definition distortion.hpp:277
static constexpr double kNewtonTolerance
Convergence threshold normalized to the current produced by one volt through the modeled resistance.
Definition distortion.hpp:262
static constexpr int kMaxNewtonIterations
Hard cap on inner-loop iterations per sample.
Definition distortion.hpp:268
static constexpr int kMaxNewtonIterationsPerSample
TR-BDF2 has two implicit stages, each with the cap above.
Definition distortion.hpp:272
One junction network: the exponential law over one or two antiparallel legs.
Definition junction.hpp:94
double conductance(double v) const
di/dv — the incremental conductance a Newton solve needs.
Definition junction.hpp:123
double current(double v) const
i(v).
Definition junction.hpp:116
First-order topology-preserving transform (TPT) filter.
The real-unit vocabulary shared by every DSP block in the catalog.