HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_audio_buffer_signal.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "signals/hart_signal.hpp"
5#include "hart_slice.hpp"
6
7#include <memory>
8#include <sstream>
9
10// TODO: Better documentation
11
12namespace hart {
13
14/// @brief Plays audio from a pre-rendered AudioBuffer
15/// @ingroup Signals
16template<typename SampleType>
18 public Signal<SampleType, AudioBufferSignal<SampleType>>
19{
20public:
21 enum class Loop
22 {
23 yes,
24 no
25 };
26
27 /// @brief Construct from a shared_ptr (shared ownership)
28 explicit AudioBufferSignal (std::shared_ptr<AudioBuffer<SampleType>> buffer, Loop loop = Loop::no) :
29 m_buffer (std::move (buffer)),
30 m_loop (loop)
31 {
32 if (m_buffer == nullptr)
33 HART_THROW_OR_RETURN_VOID (hart::NullPointerError, "Null pointer buffer shouldn't be used to make an AudioBufferSignal");
34
35 this->setNumChannels (m_buffer->getNumChannels());
36 }
37
38 /// @brief Construct by copying a buffer (takes ownership of a copy)
39 explicit AudioBufferSignal (const AudioBuffer<SampleType>& buffer, Loop loop = Loop::no) :
40 m_buffer (std::make_shared<AudioBuffer<SampleType>> (buffer)),
41 m_loop (loop)
42 {
43 this->setNumChannels (m_buffer->getNumChannels());
44 }
45
46 /// @brief Construct by moving a buffer (takes ownership)
47 explicit AudioBufferSignal (AudioBuffer<SampleType>&& buffer, Loop loop = Loop::no) :
48 m_buffer (std::make_shared<AudioBuffer<SampleType>> (std::move (buffer))),
49 m_loop (loop)
50 {
51 this->setNumChannels (m_buffer->getNumChannels());
52 }
53
54 bool supportsNumChannels (size_t numChannels) const override
55 {
56 return m_buffer != nullptr && numChannels == m_buffer->getNumChannels();
57 }
58
59 bool supportsSampleRate(double sampleRateHz) const override
60 {
61 if (m_buffer == nullptr)
62 return false;
63
64 // If buffer has undefined sample rate, we'll accept any rate
65 return ! m_buffer->hasSampleRate() || m_buffer->getSampleRateHz() == sampleRateHz;
66 }
67
68 void prepare (double sampleRateHz, size_t numOutputChannels, size_t /*maxBlockSizeFrames*/) override
69 {
70 if (m_buffer == nullptr)
71 HART_THROW_OR_RETURN_VOID (hart::NullPointerError, "AudioBufferSignal: buffer is null");
72
73 if (numOutputChannels != m_buffer->getNumChannels())
74 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, "AudioBufferSignal: output channel count does not match buffer");
75
76 if (m_buffer->hasSampleRate() && m_buffer->getSampleRateHz() != sampleRateHz)
77 HART_THROW_OR_RETURN_VOID(hart::SampleRateError, "AudioBufferSignal: sample rate does not match buffer");
78 }
79
80 void renderNextBlock (AudioBuffer<SampleType>& output) override
81 {
82 if (m_buffer == nullptr)
83 {
84 hassertfalse; // No buffer to play audio from!
85 output.clear();
86 return;
87 }
88
89 const size_t numFrames = output.getNumFrames();
90 const size_t bufferTotalFrames = m_buffer->getNumFrames();
91 const size_t bufferNumChannels = m_buffer->getNumChannels();
92 size_t framesWritten = 0;
93
94 while (framesWritten < numFrames)
95 {
96 if (m_readPosition >= bufferTotalFrames)
97 {
98 if (m_loop == Loop::no)
99 break; // fill remaining with zeros below
100
101 m_readPosition = 0; // loop
102 }
103
104 const size_t framesAvailable = bufferTotalFrames - m_readPosition;
105 const size_t framesToCopy = std::min (framesAvailable, numFrames - framesWritten);
106
107 for (size_t channel = 0; channel < bufferNumChannels; ++channel)
108 {
109 const SampleType* src = (*m_buffer)[channel] + m_readPosition;
110 SampleType* dst = output[channel] + framesWritten;
111 std::copy(src, src + framesToCopy, dst);
112 }
113
114 framesWritten += framesToCopy;
115 m_readPosition += framesToCopy;
116 }
117
118 // Zero out any remaining frames (only when non‑looping and we ran out)
119 for (; framesWritten < numFrames; ++framesWritten)
120 for (size_t channel = 0; channel < bufferNumChannels; ++channel)
121 output[channel][framesWritten] = SampleType(0);
122 }
123
124 void reset() override
125 {
126 m_readPosition = 0;
127 }
128
129 void represent (std::ostream& stream) const override
130 {
131 stream << "AudioBufferSignal (";
132
133 if (m_buffer != nullptr)
134 m_buffer->represent (stream);
135 else
136 stream << "<nullptr>";
137
138 stream << ", Loop::" << (m_loop == Loop::yes ? "yes" : "no") << ')';
139 }
140
141 std::unique_ptr<SignalBase<SampleType>> copy() const override
142 {
143 return hart::make_unique<AudioBufferSignal> (m_buffer, m_loop);
144 }
145
146 std::unique_ptr<SignalBase<SampleType>> move() override
147 {
148 return hart::make_unique<AudioBufferSignal> (std::move (m_buffer), m_loop);
149 }
150
151private:
152 std::shared_ptr<AudioBuffer<SampleType>> m_buffer;
153 Loop m_loop = Loop::no;
154 size_t m_readPosition = 0;
155};
156
158
159} // namespace hart
Plays audio from a pre-rendered AudioBuffer.
void renderNextBlock(AudioBuffer< SampleType > &output) override
Renders next block audio for the signal.
bool supportsSampleRate(double sampleRateHz) const override
Tells whether this Signal supports given sample rate.
AudioBufferSignal(std::shared_ptr< AudioBuffer< SampleType > > buffer, Loop loop=Loop::no)
Construct from a shared_ptr (shared ownership)
AudioBufferSignal(const AudioBuffer< SampleType > &buffer, Loop loop=Loop::no)
Construct by copying a buffer (takes ownership of a copy)
void prepare(double sampleRateHz, size_t numOutputChannels, size_t) override
Prepare the signal for rendering.
void represent(std::ostream &stream) const override
Makes a text representation of this Signal for test failure outputs.
std::unique_ptr< SignalBase< SampleType > > move() override
Returns a smart pointer with a moved instance of this object.
bool supportsNumChannels(size_t numChannels) const override
Tells the host whether this Signal is capable of generating audio for a certain amount of channels.
std::unique_ptr< SignalBase< SampleType > > copy() const override
Returns a smart pointer with a copy of this object.
AudioBufferSignal(AudioBuffer< SampleType > &&buffer, Loop loop=Loop::no)
Construct by moving a buffer (takes ownership)
void reset() override
Resets the Signal to initial state.
Container for audio data.
Thrown when a numbers of channels is mismatched.
Thrown when a nullptr could be handled gracefully.
Thrown when sample rate is mismatched.
Base class for signals.
#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 hassertfalse
Triggers a HartAssertException
#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)