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