Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
path_switcher.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "crossfade.hpp"
4#include "detail/audio_range.hpp"
5
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <cstddef>
10#include <span>
11#include <type_traits>
12
13namespace pulp::signal {
14
20template <typename SampleType = float, std::size_t MaxPaths = 16> class ClickFreePathSwitcherT {
21 public:
22 static_assert(MaxPaths > 0);
23 static_assert(std::is_floating_point_v<SampleType>);
24 static constexpr std::size_t max_paths = MaxPaths;
25
26 bool configure(std::size_t paths, std::size_t initial_path, std::size_t fade_samples) noexcept {
27 if (paths == 0 || paths > MaxPaths || initial_path >= paths)
28 return false;
29 paths_ = paths;
30 fade_samples_ = fade_samples < 2 ? 0 : fade_samples;
31 selected_path_ = initial_path;
32 position_ = fade_samples_;
33 from_.fill(SampleType{0});
34 weights_.fill(SampleType{0});
35 target_.fill(SampleType{0});
36 weights_[initial_path] = SampleType{1};
37 from_[initial_path] = SampleType{1};
38 target_[initial_path] = SampleType{1};
39 return true;
40 }
41
42 bool request_path(std::size_t path) noexcept {
43 if (path >= paths_)
44 return false;
45 selected_path_ = path;
46 from_ = weights_;
47 target_.fill(SampleType{0});
48 target_[path] = SampleType{1};
49 position_ = 0;
50 if (fade_samples_ == 0) {
51 weights_ = target_;
52 position_ = 0;
53 }
54 return true;
55 }
56
57 void reset() noexcept {
58 weights_.fill(SampleType{0});
59 from_.fill(SampleType{0});
60 target_.fill(SampleType{0});
61 if (paths_ > 0) {
62 weights_[selected_path_] = SampleType{1};
63 from_[selected_path_] = SampleType{1};
64 target_[selected_path_] = SampleType{1};
65 }
66 position_ = fade_samples_;
67 }
68
69 std::size_t path_count() const noexcept {
70 return paths_;
71 }
72 std::size_t selected_path() const noexcept {
73 return selected_path_;
74 }
75 bool switching() const noexcept {
76 return position_ < fade_samples_;
77 }
78 std::span<const SampleType> weights() const noexcept {
79 return {weights_.data(), paths_};
80 }
81
82 bool next_gains(std::span<SampleType> destination) noexcept {
83 if (paths_ == 0 || destination.size() < paths_)
84 return false;
85 if (position_ < fade_samples_) {
86 if (position_ == 0) {
87 weights_ = from_;
88 } else if (position_ + 1 == fade_samples_) {
89 weights_ = target_;
90 } else {
91 using ProgressType =
92 std::conditional_t<(sizeof(SampleType) < sizeof(double)), double, long double>;
93 const auto t = static_cast<ProgressType>(position_) /
94 static_cast<ProgressType>(fade_samples_ - 1);
95 const auto u = crossfade_smoothstep(t);
96 std::array<ProgressType, MaxPaths> mixed{};
97 ProgressType norm_squared{};
98 for (std::size_t path = 0; path < paths_; ++path) {
99 mixed[path] = static_cast<ProgressType>(from_[path]) * (ProgressType{1} - u) +
100 static_cast<ProgressType>(target_[path]) * u;
101 norm_squared += mixed[path] * mixed[path];
102 }
103 const auto scale = norm_squared > ProgressType{0}
104 ? ProgressType{1} / std::sqrt(norm_squared)
105 : ProgressType{1};
106 for (std::size_t path = 0; path < paths_; ++path)
107 weights_[path] = static_cast<SampleType>(mixed[path] * scale);
108 }
109 ++position_;
110 }
111 std::copy_n(weights_.begin(), paths_, destination.begin());
112 return true;
113 }
114
115 bool process(const SampleType* const* sources, std::size_t source_count,
116 SampleType* destination, std::size_t frames) noexcept {
117 if (sources == nullptr || destination == nullptr || source_count != paths_ || paths_ == 0)
118 return false;
119 for (std::size_t path = 0; path < paths_; ++path)
120 if (sources[path] == nullptr ||
121 detail::audio_ranges_overlap(sources[path], destination, frames))
122 return false;
123 std::array<SampleType, MaxPaths> gains{};
124 for (std::size_t frame = 0; frame < frames; ++frame) {
125 next_gains(std::span<SampleType>(gains.data(), paths_));
126 SampleType sum{};
127 for (std::size_t path = 0; path < paths_; ++path)
128 sum += sources[path][frame] * gains[path];
129 destination[frame] = sum;
130 }
131 return true;
132 }
133
134 private:
135 std::size_t paths_ = 0;
136 std::size_t selected_path_ = 0;
137 std::size_t fade_samples_ = 0;
138 std::size_t position_ = 0;
139 std::array<SampleType, MaxPaths> from_{};
140 std::array<SampleType, MaxPaths> target_{};
141 std::array<SampleType, MaxPaths> weights_{};
142};
143
146
147} // namespace pulp::signal
Fixed-capacity, click-free path selector.
Definition path_switcher.hpp:20
bool switching() const noexcept
Definition path_switcher.hpp:75
bool configure(std::size_t paths, std::size_t initial_path, std::size_t fade_samples) noexcept
Definition path_switcher.hpp:26
static constexpr std::size_t max_paths
Definition path_switcher.hpp:24
std::span< const SampleType > weights() const noexcept
Definition path_switcher.hpp:78
bool process(const SampleType *const *sources, std::size_t source_count, SampleType *destination, std::size_t frames) noexcept
Definition path_switcher.hpp:115
std::size_t path_count() const noexcept
Definition path_switcher.hpp:69
bool request_path(std::size_t path) noexcept
Definition path_switcher.hpp:42
std::size_t selected_path() const noexcept
Definition path_switcher.hpp:72
void reset() noexcept
Definition path_switcher.hpp:57
bool next_gains(std::span< SampleType > destination) noexcept
Definition path_switcher.hpp:82
The one shared old→new crossfade gain law for the SIGNAL side.
Definition additive_bank.hpp:218
ClickFreePathSwitcherT< double > ClickFreePathSwitcher64
Definition path_switcher.hpp:145
constexpr T crossfade_smoothstep(T t) noexcept
Smoothstep progress shaping: cubic 3u²−2u³ with zero slope at both ends, clamped to [0,...
Definition crossfade.hpp:50
@ u
Definition formant_filter_bank.hpp:20
ClickFreePathSwitcherT< float > ClickFreePathSwitcher
Definition path_switcher.hpp:144