DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
ProcessorChain.h
Go to the documentation of this file.
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
42template <typename T, typename... Processors>
44{
45 static_assert(sizeof...(Processors) > 0, "ProcessorChain requires at least one processor.");
46 static_assert((AudioProcessor<Processors, T> && ...),
47 "Every ProcessorChain slot must satisfy the dspark::AudioProcessor concept "
48 "(prepare(AudioSpec), processBlock(AudioBufferView<T>) noexcept, reset() noexcept).");
49
50public:
55 void prepare(const AudioSpec& spec)
56 {
57 std::apply([&spec](auto&... procs) {
58 (procs.prepare(spec), ...);
59 }, processors_);
60 }
61
66 void processBlock(AudioBufferView<T> buffer) noexcept
67 {
68 processBlockImpl(buffer, std::index_sequence_for<Processors...>{});
69 }
70
74 void reset() noexcept
75 {
76 std::apply([](auto&... procs) {
77 (procs.reset(), ...);
78 }, processors_);
79 }
80
87 template <std::size_t Index>
88 [[nodiscard]] auto& get() noexcept
89 {
90 static_assert(Index < sizeof...(Processors), "Processor index out of bounds.");
91 return std::get<Index>(processors_);
92 }
93
100 template <std::size_t Index>
101 [[nodiscard]] const auto& get() const noexcept
102 {
103 static_assert(Index < sizeof...(Processors), "Processor index out of bounds.");
104 return std::get<Index>(processors_);
105 }
106
110 [[nodiscard]] static constexpr std::size_t size() noexcept
111 {
112 return sizeof...(Processors);
113 }
114
128 [[nodiscard]] int getLatency() const noexcept
129 {
130 return getLatencyImpl(std::index_sequence_for<Processors...>{});
131 }
132
133 // -- Bypass control ------------------------------------------------------
134
146 template <std::size_t Index>
147 void setBypassed(bool bypassed) noexcept
148 {
149 static_assert(Index < sizeof...(Processors), "Index out of range");
150 bypassed_[Index].store(bypassed, std::memory_order_relaxed);
151 }
152
156 template <std::size_t Index>
157 [[nodiscard]] bool isBypassed() const noexcept
158 {
159 static_assert(Index < sizeof...(Processors), "Index out of range");
160 return bypassed_[Index].load(std::memory_order_relaxed);
161 }
162
163private:
164 template <std::size_t... Is>
165 [[nodiscard]] int getLatencyImpl(std::index_sequence<Is...>) const noexcept
166 {
167 return (getProcessorLatency<Is>() + ...);
168 }
169
170 template <std::size_t I>
171 [[nodiscard]] int getProcessorLatency() const noexcept
172 {
173 using P = std::tuple_element_t<I, std::tuple<Processors...>>;
174 if constexpr (requires(const P& p) { { p.getLatency() } -> std::convertible_to<int>; })
175 {
176 return static_cast<int>(std::get<I>(processors_).getLatency());
177 }
178 else
179 {
180 // A latency reporter that is not callable on a const processor
181 // (or does not return an int-convertible value) would otherwise
182 // be skipped silently, desynchronising host delay compensation.
183 static_assert(!requires(P& p) { p.getLatency(); },
184 "Processor getLatency() must be const and return a value convertible to int "
185 "to be summed by ProcessorChain::getLatency().");
186 return 0;
187 }
188 }
189
190 template <std::size_t... Is>
191 void processBlockImpl(AudioBufferView<T> buffer, std::index_sequence<Is...>) noexcept
192 {
193 // Uses memory_order_relaxed as we only care about the state, no memory synchronization needed
194 ((!bypassed_[Is].load(std::memory_order_relaxed) ? std::get<Is>(processors_).processBlock(buffer) : (void)0), ...);
195 }
196
197 std::tuple<Processors...> processors_;
198 std::array<std::atomic<bool>, sizeof...(Processors)> bypassed_ {};
199};
200
201} // namespace dspark
Owning audio buffer and non-owning view for real-time DSP processing.
Describes the audio processing environment (sample rate, block size, channels).
C++20 concepts defining the strictly real-time DSP processor contract.
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
Thread-safe read of the bypass state of a processor.
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
Thread-safe toggle to bypass or enable a processor.
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:35