DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
ProcessorChain.h
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
19#include "ProcessorTraits.h"
20#include "AudioSpec.h"
21#include "AudioBuffer.h"
22
23#include <cstddef>
24#include <concepts>
25#include <tuple>
26#include <utility>
27#include <array>
28#include <atomic>
29
30namespace dspark {
31
51template <typename T, typename... Processors>
53{
54 static_assert(sizeof...(Processors) > 0, "ProcessorChain requires at least one processor.");
55 static_assert((AudioProcessor<Processors, T> && ...),
56 "Every ProcessorChain slot must satisfy the dspark::AudioProcessor concept "
57 "(prepare(AudioSpec), processBlock(AudioBufferView<T>) noexcept, reset() noexcept).");
58
59public:
64 void prepare(const AudioSpec& spec)
65 {
66 std::apply([&spec](auto&... procs) {
67 (procs.prepare(spec), ...);
68 }, processors_);
69 }
70
75 void processBlock(AudioBufferView<T> buffer) noexcept
76 {
77 processBlockImpl(buffer, std::index_sequence_for<Processors...>{});
78 }
79
83 void reset() noexcept
84 {
85 std::apply([](auto&... procs) {
86 (procs.reset(), ...);
87 }, processors_);
88 }
89
96 template <std::size_t Index>
97 [[nodiscard]] auto& get() noexcept
98 {
99 static_assert(Index < sizeof...(Processors), "Processor index out of bounds.");
100 return std::get<Index>(processors_);
101 }
102
109 template <std::size_t Index>
110 [[nodiscard]] const auto& get() const noexcept
111 {
112 static_assert(Index < sizeof...(Processors), "Processor index out of bounds.");
113 return std::get<Index>(processors_);
114 }
115
119 [[nodiscard]] static constexpr std::size_t size() noexcept
120 {
121 return sizeof...(Processors);
122 }
123
137 [[nodiscard]] int getLatency() const noexcept
138 {
139 return getLatencyImpl(std::index_sequence_for<Processors...>{});
140 }
141
142 // -- Bypass control ------------------------------------------------------
143
156 template <std::size_t Index>
157 void setBypassed(bool bypassed) noexcept
158 {
159 static_assert(Index < sizeof...(Processors), "Index out of range");
160 bypassed_[Index].store(bypassed, std::memory_order_relaxed);
161 }
162
166 template <std::size_t Index>
167 [[nodiscard]] bool isBypassed() const noexcept
168 {
169 static_assert(Index < sizeof...(Processors), "Index out of range");
170 return bypassed_[Index].load(std::memory_order_relaxed);
171 }
172
173private:
174 template <std::size_t... Is>
175 [[nodiscard]] int getLatencyImpl(std::index_sequence<Is...>) const noexcept
176 {
177 return (getProcessorLatency<Is>() + ...);
178 }
179
180 template <std::size_t I>
181 [[nodiscard]] int getProcessorLatency() const noexcept
182 {
183 using P = std::tuple_element_t<I, std::tuple<Processors...>>;
184 if constexpr (requires(const P& p) { { p.getLatency() } -> std::convertible_to<int>; })
185 {
186 return static_cast<int>(std::get<I>(processors_).getLatency());
187 }
188 else
189 {
190 // A latency reporter that is not callable on a const processor
191 // (or does not return an int-convertible value) would otherwise
192 // be skipped silently, desynchronising host delay compensation.
193 static_assert(!requires(P& p) { p.getLatency(); },
194 "Processor getLatency() must be const and return a value convertible to int "
195 "to be summed by ProcessorChain::getLatency().");
196 return 0;
197 }
198 }
199
200 template <std::size_t... Is>
201 void processBlockImpl(AudioBufferView<T> buffer, std::index_sequence<Is...>) noexcept
202 {
203 // Uses memory_order_relaxed as we only care about the state, no memory synchronization needed
204 ((!bypassed_[Is].load(std::memory_order_relaxed) ? std::get<Is>(processors_).processBlock(buffer) : (void)0), ...);
205 }
206
207 std::tuple<Processors...> processors_;
208 std::array<std::atomic<bool>, sizeof...(Processors)> bypassed_ {};
209};
210
211} // namespace dspark
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
Compile-time chain of audio processors.
void prepare(const AudioSpec &spec)
Prepares all processors in order.
auto & get() noexcept
Accesses the processor at the given index.
bool isBypassed() const noexcept
Reads the bypass state (lock-free readout, any thread).
void reset() noexcept
Resets the internal state of all processors (e.g., clearing delay lines).
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a buffer through all non-bypassed processors in order.
void setBypassed(bool bypassed) noexcept
Toggles bypass for a processor (control thread; the relaxed atomic word is adopted by the next proces...
int getLatency() const noexcept
Returns the total latency in samples across all processors.
static constexpr std::size_t size() noexcept
Returns the number of processors in the chain.
const auto & get() const noexcept
Const access to the processor at the given index.
A type that can prepare, process audio blocks, and reset state.
Main namespace for the DSPark framework.
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:37