HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_time_shift.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <vector>
4
5#include "hart_dsp.hpp"
6#include "hart_precision.hpp" // secPrecision
7#include "hart_utils.hpp" // roundToSizeT()
8
9namespace hart
10{
11
12/// @brief Shifts an audio signal forward in time by a fixed amount
13/// @details Delays the input signal by a specified duration, expressed in seconds.
14/// Internally, the delay is converted to an integer number of frames based on the
15/// current sample rate, so it soesn't do sub-frame (fractional) elays.
16/// This DSP is a low-level utility intended for testing and signal alignment purposes,
17/// such as compensating for latency in other processors. It performs a pure time shift
18/// with no feedback or dry/wet mixing. The output is zero-filled until the internal
19/// buffer is primed. Delay time is fixed and cannot be changed after construction or
20/// copy/move assignment. The delay is identical across all channels, and processing
21/// is only supported when the number of input and output channels matches.
22/// It is encouraged to use it in conjunction with `hart::Impulse` signal,
23/// although other signal types may also be appropriate.
24/// @throws hart::ValueError for negative or zero delay (in seconds or frames)
25/// @tparam SampleType Floating point sample type, typecally `float` or `double`.
26/// @ingroup DSP
27template <typename SampleType>
28class TimeShift :
29 public DSP<SampleType, TimeShift<SampleType>>
30{
31public:
32 /// @brief Creates a TimeShift processor with a fixed delay.
33 /// @param delaySeconds Delay duration in seconds. Must be greater than zero.
34 /// The delay value is converted to an integer number of frames during prepare(),
35 /// based on the provided sample rate. Values that resolve to zero frames are rejected.
36 /// @throws hart::ValueError for negative or zero delay (in seconds or frames)
37 TimeShift (double delaySeconds):
38 m_delaySeconds (delaySeconds)
39 {
40 if (delaySeconds < 0.0 || floatsEqual (delaySeconds, 0.0))
41 HART_THROW_OR_RETURN_VOID (hart::ValueError, "TimeShift: delaySeconds must be > 0");
42 }
43
44 /// @brief Creates a TimeShift processor by copying other one
45 /// @details The entire state, included internal delay byffers' content is copied as well
46 /// @param other TimeShift to copy from
47 TimeShift (const TimeShift& other):
48 DSP<SampleType, TimeShift<SampleType>> (other),
49 m_delaySeconds (other.m_delaySeconds),
50 m_delayFrames (other.m_delayFrames),
51 m_bufferSizeFrames (other.m_bufferSizeFrames),
52 m_writeIndex (other.m_writeIndex),
53 m_buffers (other.m_buffers)
54 {
55 }
56
57 /// @brief Creates a TimeShift processor by moving other one
58 /// @details The entire state, included internal delay byffers' content is moved as well
59 /// @param other TimeShift to move from
60 TimeShift (TimeShift&& other) noexcept:
61 DSP<SampleType, TimeShift<SampleType>> (std::move (other)),
62 m_delaySeconds (other.m_delaySeconds),
63 m_delayFrames (other.m_delayFrames),
64 m_bufferSizeFrames (other.m_bufferSizeFrames),
65 m_writeIndex (other.m_writeIndex),
66 m_buffers (std::move (other.m_buffers))
67 {
68 other.m_delayFrames = 0;
69 other.m_bufferSizeFrames = 0;
70 other.m_writeIndex = 0;
71 }
72
73 /// @brief Creates a TimeShift processor by copying other one
74 /// @details The entire state, included internal delay byffers' content is copied as well
75 /// @param other TimeShift to copy from
76 TimeShift& operator= (const TimeShift& other)
77 {
78 if (this == &other)
79 return *this;
80
81 DSP<SampleType, TimeShift<SampleType>>::operator= (other);
82
83 m_delaySeconds = other.m_delaySeconds;
84 m_delayFrames = other.m_delayFrames;
85 m_bufferSizeFrames = other.m_bufferSizeFrames;
86 m_writeIndex = other.m_writeIndex;
87 m_buffers = other.m_buffers;
88
89 return *this;
90 }
91
92 /// @brief Creates a TimeShift processor by moving other one
93 /// @details The entire state, included internal delay byffers' content is moved as well
94 /// @param other TimeShift to move from
95 TimeShift& operator= (TimeShift&& other) noexcept
96 {
97 if (this == &other)
98 return *this;
99
100 DSP<SampleType, TimeShift<SampleType>>::operator= (std::move (other));
101
102 m_delaySeconds = other.m_delaySeconds;
103 m_delayFrames = other.m_delayFrames;
104 m_bufferSizeFrames = other.m_bufferSizeFrames;
105 m_writeIndex = other.m_writeIndex;
106 m_buffers = std::move (other.m_buffers);
107
108 other.m_delayFrames = 0;
109 other.m_bufferSizeFrames = 0;
110 other.m_writeIndex = 0;
111
112 return *this;
113 }
114
115 ~TimeShift() override = default;
116
117 void prepare (
118 double sampleRateHz,
119 size_t numInputChannels,
120 size_t numOutputChannels,
121 size_t /* maxBlockSizeFrames */
122 ) override
123 {
124 hassert (numInputChannels == numOutputChannels)
125 const size_t numChannels = numInputChannels;
126
127 hassert (sampleRateHz > 0.0);
128 m_delayFrames = roundToSizeT (m_delaySeconds * sampleRateHz);
129
130 if (m_delayFrames == 0)
131 HART_THROW_OR_RETURN_VOID (hart::ValueError, "TimeShift: Delay time resolved to 0 frames!");
132
133 m_bufferSizeFrames = m_delayFrames + 1;
134 m_writeIndex = 0;
135
136 m_buffers.clear();
137 m_buffers.resize (numChannels);
138
139 for (size_t channel = 0; channel < numChannels; ++channel)
140 m_buffers[channel].assign (m_bufferSizeFrames, static_cast<SampleType> (0));
141 }
142
143 void process (
144 const AudioBuffer<SampleType>& input,
145 AudioBuffer<SampleType>& output,
146 const EnvelopeBuffers& /*envelopeBuffers*/,
147 ChannelFlags /*channelsToProcess*/
148 ) override
149 {
150 const size_t numFrames = input.getNumFrames();
151 const size_t numChannels = input.getNumChannels();
152
153 for (size_t frame = 0; frame < numFrames; ++frame)
154 {
155 const size_t readIndex = (m_writeIndex + m_bufferSizeFrames - m_delayFrames) % m_bufferSizeFrames;
156
157 for (size_t channel = 0; channel < numChannels; ++channel)
158 {
159 const SampleType inputSample = input[channel][frame];
160 output[channel][frame] = m_buffers[channel][readIndex];
161 m_buffers[channel][m_writeIndex] = inputSample;
162 }
163
164 m_writeIndex = (m_writeIndex + 1) % m_bufferSizeFrames;
165 }
166 }
167
168 bool supportsEnvelopeFor (int /*id*/) const override
169 {
170 return false;
171 }
172
173 bool supportsChannelLayout (size_t numInputChannels, size_t numOutputChannels) const override
174 {
175 return numInputChannels == numOutputChannels;
176 }
177
178 bool supportsSampleRate (double sampleRateHz) const override
179 {
180 return sampleRateHz > 0.0;
181 }
182
183 void represent (std::ostream& stream) const override
184 {
185 stream << "TimeShift (" << secPrecision << m_delaySeconds << ")";
186 }
187
188 void setValue (int /*id*/, double /*value*/) override {}
189
192
193private:
194 double m_delaySeconds = 0.0;
195 size_t m_delayFrames = 0;
196 size_t m_bufferSizeFrames = 0;
197 size_t m_writeIndex = 0;
198
199 std::vector<std::vector<SampleType>> m_buffers;
200};
201
203
204} // namespace hart
Container for audio data.
A set of boolean flags mapped to each audio channel.
Base for DSP effects.
Definition hart_dsp.hpp:345
Shifts an audio signal forward in time by a fixed amount.
TimeShift & operator=(const TimeShift &other)
Creates a TimeShift processor by copying other one.
~TimeShift() override=default
TimeShift & operator=(TimeShift &&other) noexcept
Creates a TimeShift processor by moving other one.
TimeShift(double delaySeconds)
Creates a TimeShift processor with a fixed delay.
bool supportsSampleRate(double sampleRateHz) const override
Tells whether this effect supports given sample rate.
TimeShift(const TimeShift &other)
Creates a TimeShift processor by copying other one.
void setValue(int, double) override
Sets DSP value.
void process(const AudioBuffer< SampleType > &input, AudioBuffer< SampleType > &output, const EnvelopeBuffers &, ChannelFlags) override
Processes the audio.
void represent(std::ostream &stream) const override
Makes a text representation of this DSP effect for test failure outputs.
TimeShift(TimeShift &&other) noexcept
Creates a TimeShift processor by moving other one.
void prepare(double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t) override
Prepare for processing.
bool supportsEnvelopeFor(int) const override
Tells whether this effect accepts automation envelopes for a particular parameter.
bool supportsChannelLayout(size_t numInputChannels, size_t numOutputChannels) const override
Tells the runner (host) whether this effect supports a specific i/o configuration.
Thrown when an inappropriate value is encountered.
#define HART_DSP_COPYABLE(ClassName)
Implements a generic hart::DSP::copy() method.
Definition hart_dsp.hpp:602
#define HART_DSP_MOVABLE(ClassName)
Implements a generic hart::DSP::move() method.
Definition hart_dsp.hpp:610
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns otherwise.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
std::ostream & secPrecision(std::ostream &stream)
Sets number of decimal places for values in seconds.
static size_t roundToSizeT(SampleType x)
Rounds a floating point value to a size_t value.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
#define HART_DSP_DECLARE_ALIASES_FOR(ClassName)
Definition hart_dsp.hpp:619