HART  0.1.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_wavfile.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <memory>
4#include <string>
5
6#include "dr_wav.h"
7
9#include "signals/hart_signal.hpp"
10#include "hart_utils.hpp" // toAbsolutePath(), floatsNotEqual()
11
12namespace hart
13{
14
15// TODO: skipTo()
16// TODO: Add "normalize" option?
17// TODO: Add an entity that reuses wav data if a WavFile for a previously opened file gets instantiated
18
19/// @brief Produces audio from a wav file
20/// @details Original levels from the wav file are preserved
21/// @ingroup Signals
22template<typename SampleType>
23class WavFile:
24 public Signal<SampleType>
25{
26public:
27 enum class Loop
28 {
29 yes,
30 no
31 };
32
33 /// @brief Creates a Signal that produces audio from a wav file
34 /// @param filePath Path to a wav file
35 /// Can be absolute or relative. If a relative path is used, it will resolve
36 /// as relative to a data root path provided via respective CLI argument.
37 /// @see HART_REQUIRES_DATA_PATH_ARG
38 /// @param loop Indicates whether the signal should loop the audio or produce
39 /// silence after wav file runs out of frames.
40 WavFile (const std::string& filePath, Loop loop = Loop::no):
41 m_filePath (filePath),
42 m_loop (loop)
43 {
44 // TODO: Check if the file exists first
45
46 drwav_uint64 numFrames;
47 unsigned int numChannels;
48 unsigned int wavSampleRateHz;
49
50 float* pcmFrames = drwav_open_file_and_read_pcm_frames_f32 (
51 toAbsolutePath (filePath).c_str(),
52 &numChannels,
53 &wavSampleRateHz,
54 &numFrames,
55 nullptr
56 );
57
58 if (pcmFrames == nullptr)
59 HART_THROW_OR_RETURN_VOID (hart::IOError, std::string ("Could not read frames from the wav file"));
60
61 m_wavFrames = std::make_shared<AudioBuffer<float>> (numChannels, numFrames);
62
63 for (size_t frame = 0; frame < numFrames; ++frame)
64 for (size_t channel = 0; channel < numChannels; ++channel)
65 (*m_wavFrames)[channel][frame] = pcmFrames[frame * numChannels + channel];
66
67 drwav_free (pcmFrames, nullptr);
68
69 m_wavSampleRateHz = static_cast<double> (wavSampleRateHz);
70 m_wavNumChannels = static_cast<size_t> (numChannels);
71 }
72
73 /// @copydoc Signal::supportsNumChannels()
74 /// @note WavFile can only fill as much channels as there are in the wav file, or less.
75 /// For instance, if the wav file is stereo, it can generate two channels (as they are),
76 /// one channel (left, discarding right), but not three channels.
77 bool supportsNumChannels (size_t numChannels) const override
78 {
79 return numChannels <= m_wavNumChannels;
80 };
81
82 void prepare (double sampleRateHz, size_t numOutputChannels, size_t /*maxBlockSizeFrames*/) override
83 {
84 // There are a few ovbvious cases where channel number mismatch can be gracefully resolved - perhaps in the future
85 if (numOutputChannels != m_wavNumChannels)
86 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, std::string ("Unexpected channel number"));
87
88 if (floatsNotEqual (sampleRateHz, m_wavSampleRateHz))
89 HART_THROW_OR_RETURN_VOID (hart::UnsupportedError, std::string ("Wav file is in different sampling rate, resampling not supported"));
90 }
91
92 void renderNextBlock (AudioBuffer<SampleType>& output) override
93 {
94 // TODO: Add support for number of channels different from the wav file
95 // TODO: Add resampling
96 const size_t numFrames = output.getNumFrames();
97 size_t frameInOutputBuffer = 0;
98 size_t frameInWavBuffer = m_wavOffsetFrames;
99
100 while (m_wavOffsetFrames < m_wavFrames->getNumFrames() && frameInOutputBuffer < numFrames)
101 {
102 for (size_t channel = 0; channel < m_wavNumChannels; ++channel)
103 output[channel][frameInOutputBuffer] = (*m_wavFrames)[channel][frameInWavBuffer];
104
105 ++frameInOutputBuffer;
106 ++frameInWavBuffer;
107 ++m_wavOffsetFrames;
108
109 if (m_loop == Loop::yes)
110 m_wavOffsetFrames %= m_wavFrames->getNumFrames();
111 }
112
113 while (frameInOutputBuffer < numFrames)
114 {
115 hassert (m_loop == Loop::no);
116
117 for (size_t channel = 0; channel < m_wavNumChannels; ++channel)
118 output[channel][frameInOutputBuffer] = (SampleType) 0;
119
120 ++frameInOutputBuffer;
121 }
122 }
123
124 void reset() override
125 {
126 m_wavOffsetFrames = 0;
127 }
128
129 void represent (std::ostream& stream) const override
130 {
131 stream << "WavFile (\"" << m_filePath << (m_loop == Loop::yes ? "\", Loop::yes)" : "\", Loop::no)");
132 }
133
135
136private:
137 const std::string m_filePath;
138 const Loop m_loop;
139 size_t m_wavNumChannels;
140 double m_wavSampleRateHz;
141 size_t m_wavOffsetFrames = 0;
142 std::shared_ptr<AudioBuffer<float>> m_wavFrames;
143};
144
146
147} // namespace hart
SampleType * operator[](size_t channel)
size_t getNumFrames() const
Base class for signals.
Produces audio from a wav file.
void renderNextBlock(AudioBuffer< SampleType > &output) override
Renders next block audio for the signal.
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.
bool supportsNumChannels(size_t numChannels) const override
Tells the host whether this Signal is capable of generating audio for a certain amount of cchannels.
WavFile(const std::string &filePath, Loop loop=Loop::no)
Creates a Signal that produces audio from a wav file.
void reset() override
Resets the Signal to initial state.
#define HART_SIGNAL_DEFINE_COPY_AND_MOVE(ClassName)
Defines hart::Signal::copy() and hart::Signal::move() methods.
static SampleType floatsNotEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
static std::string toAbsolutePath(const std::string &path)
Converts path to absolute, if it's relative @deials Relative paths are resolved based on a provided -...
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
#define hassert(condition)
#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)