HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_audio_buffer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // max_element(), copy(), fill()
4#include <vector>
5
7
8namespace hart {
9
10template <typename SampleType>
12{
13public:
14 AudioBuffer (size_t numChannels = 0, size_t numFrames = 0) :
15 m_numChannels (numChannels),
16 m_numFrames (numFrames),
17 m_frames (m_numChannels * m_numFrames),
18 m_channelPointers (m_numChannels)
19 {
20 updateChannelPointers();
21 }
22
23 AudioBuffer(const AudioBuffer& other) :
24 m_numChannels (other.m_numChannels),
25 m_numFrames (other.m_numFrames),
26 m_frames (other.m_frames),
27 m_channelPointers (m_numChannels)
28 {
29 updateChannelPointers();
30 }
31
33 m_numChannels (other.m_numChannels),
34 m_numFrames (other.m_numFrames),
35 m_frames (std::move (other.m_frames)),
36 m_channelPointers (std::move (other.m_channelPointers))
37 {
38 other.erase();
39 }
40
41 ~AudioBuffer() = default;
42
44 {
45 if (this == &other)
46 return *this;
47
48 if (m_numChannels != other.m_numChannels)
49 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Can't copy from a buffer with different number of channels", *this);
50
51 m_numFrames = other.m_numFrames;
52 m_frames = other.m_frames;
53 m_channelPointers.resize (m_numChannels);
54 updateChannelPointers();
55
56 return *this;
57 }
58
60 {
61 if (this == &other)
62 return *this;
63
64 if (m_numChannels != other.m_numChannels)
65 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Can't move from a buffer with different number of channels", *this);
66
67 m_numFrames = other.m_numFrames;
68 m_frames = std::move (other.m_frames);
69 m_channelPointers = std::move (other.m_channelPointers);
70 other.erase();
71
72 return *this;
73 }
74
75 const SampleType* const* getArrayOfReadPointers() const
76 {
77 return static_cast<const SampleType* const*> (m_channelPointers.data());
78 }
79
80 SampleType* const* getArrayOfWritePointers()
81 {
82 return m_channelPointers.data();
83 }
84
85 static AudioBuffer emptyLike (const AudioBuffer& other)
86 {
87 return AudioBuffer (other.getNumChannels(), other.getNumFrames());
88 }
89
90 size_t getNumChannels() const { return m_numChannels; }
91 size_t getNumFrames() const { return m_numFrames; }
92
93 SampleType* operator[] (size_t channel)
94 {
95 return m_channelPointers[channel];
96 }
97
98 const SampleType* operator[] (size_t channel) const
99 {
100 return m_channelPointers[channel];
101 }
102
103 void appendFrom (const AudioBuffer<SampleType>& otherBuffer)
104 {
105 if (otherBuffer.getNumChannels() != m_numChannels)
106 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, "Channel count mismatch");
107
108 const size_t thisNumFrames = m_numFrames;
109 const size_t otherNumFrames = otherBuffer.getNumFrames();
110
111 std::vector<SampleType> combinedFrames (m_numChannels * (thisNumFrames + otherNumFrames));
112
113 for (size_t channel = 0; channel < m_numChannels; ++channel)
114 {
115 SampleType* newChannelStart = &combinedFrames[channel * (thisNumFrames + otherNumFrames)];
116 std::copy (m_channelPointers[channel], m_channelPointers[channel] + thisNumFrames, newChannelStart);
117 std::copy (otherBuffer[channel], otherBuffer[channel] + otherNumFrames, newChannelStart + thisNumFrames);
118 }
119
120 m_frames = std::move (combinedFrames);
121 m_numFrames += otherNumFrames;
122
123 updateChannelPointers();
124 }
125
126 void erase()
127 {
128 m_numFrames = 0;
129 m_frames.clear();
130
131 // If m_channelPointers was std::move'd, its size will be zero
132 if (m_channelPointers.size() != m_numChannels)
133 m_channelPointers.resize (m_numChannels);
134
135 updateChannelPointers();
136 }
137
138 SampleType getMagnitude (size_t channel, size_t startFrame, size_t numFrames) const
139 {
140 if (channel >= m_numChannels)
141 HART_THROW_OR_RETURN (hart::IndexError, "Invalid channel", (SampleType) 0);
142
143 if (startFrame + numFrames > m_numFrames || numFrames == 0)
144 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
145
146 const SampleType* start = m_channelPointers[channel] + startFrame;
147 const SampleType* peakSample = std::max_element (
148 start,
149 start + numFrames,
150 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
151 );
152
153 return std::abs (*peakSample);
154 }
155
156 SampleType getMagnitude (size_t startFrame, size_t numFrames) const
157 {
158 if (startFrame + numFrames > m_numFrames || numFrames == 0)
159 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
160
161 SampleType peakSampleAcrossAllChannels = (SampleType) 0;
162
163 for (size_t channel = 0; channel < m_numChannels; ++channel)
164 {
165 const SampleType* start = m_channelPointers[channel] + startFrame;
166 const SampleType* peakSample = std::max_element (
167 start,
168 start + numFrames,
169 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
170 );
171 peakSampleAcrossAllChannels = std::max (peakSampleAcrossAllChannels, std::abs (*peakSample));
172 }
173
174 return peakSampleAcrossAllChannels;
175 }
176
177 // TODO: Implement resize() to avoid repeated memory re-allocations caused by spamming appendFrom()
178
179 /// @brief Copies audio from another buffer
180 /// @param destChannel Channel within this buffer to copy the frames to
181 /// @param destStartFrame Start frame within this buffer's channel
182 /// @param source Source buffer to read from
183 /// @param sourceChannel Channel within the source buffer to read from
184 /// @param sourceStartFrame Offset within the source buffer's channel to start reading frames from
185 /// @param numFrames Number of frames to copy
186 void copyFrom (size_t destChannel, size_t destStartFrame, const AudioBuffer& source, size_t sourceChannel, size_t sourceStartFrame, size_t numFrames)
187 {
188 if (destChannel >= m_numChannels || sourceChannel >= source.m_numChannels)
189 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
190
191 if (destStartFrame + numFrames > m_numFrames || sourceStartFrame + numFrames > source.m_numFrames)
192 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
193
194 std::copy (
195 source.m_channelPointers[sourceChannel] + sourceStartFrame,
196 source.m_channelPointers[sourceChannel] + sourceStartFrame + numFrames,
197 m_channelPointers[destChannel] + destStartFrame
198 );
199 }
200
201 /// @brief Clears the entire buffer
202 /// @details Sets all frames in all channels to zeros
203 void clear()
204 {
205 std::fill (m_frames.begin(), m_frames.end(), (SampleType) 0);
206 }
207
208 /// @brief Clears a specific section of a given channel
209 /// @details Overwrites a selected section of the channel with zeros
210 /// @param channel Cnannel in which to clear a frame range
211 /// @param startFrame Start of the frame range to clear (inclusive)
212 /// @param numFrames Amount of frames to clear
213 void clear (size_t channel, size_t startFrame, size_t numFrames)
214 {
215 if (channel >= m_numChannels)
216 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
217
218 if (startFrame + numFrames > m_numFrames)
219 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
220
221 std::fill (m_channelPointers[channel], m_channelPointers[channel] + numFrames, (SampleType) 0);
222 }
223
224private:
225 const size_t m_numChannels = 0;
226 size_t m_numFrames = 0;
227 std::vector<SampleType> m_frames;
228 std::vector<SampleType*> m_channelPointers;
229
230 void updateChannelPointers()
231 {
232 for (size_t channel = 0; channel < m_numChannels; ++channel)
233 m_channelPointers[channel] = m_numFrames > 0 ? &m_frames[channel * m_numFrames] : nullptr;
234 }
235};
236
237} // namespace hart
void clear(size_t channel, size_t startFrame, size_t numFrames)
Clears a specific section of a given channel.
SampleType * operator[](size_t channel)
static AudioBuffer emptyLike(const AudioBuffer &other)
AudioBuffer & operator=(AudioBuffer &&other)
size_t getNumFrames() const
AudioBuffer & operator=(const AudioBuffer &other)
void appendFrom(const AudioBuffer< SampleType > &otherBuffer)
void copyFrom(size_t destChannel, size_t destStartFrame, const AudioBuffer &source, size_t sourceChannel, size_t sourceStartFrame, size_t numFrames)
Copies audio from another buffer.
const SampleType *const * getArrayOfReadPointers() const
SampleType getMagnitude(size_t startFrame, size_t numFrames) const
SampleType *const * getArrayOfWritePointers()
AudioBuffer(const AudioBuffer &other)
~AudioBuffer()=default
const SampleType * operator[](size_t channel) const
AudioBuffer(AudioBuffer &&other)
SampleType getMagnitude(size_t channel, size_t startFrame, size_t numFrames) const
void clear()
Clears the entire buffer.
size_t getNumChannels() const
AudioBuffer(size_t numChannels=0, size_t numFrames=0)
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
#define HART_THROW_OR_RETURN(ExceptionType, message, returnValue)