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(), Loop
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 /// @brief Creates a Signal that produces audio from a wav file
29 /// @param filePath Path to a wav file
30 /// Can be absolute or relative. If a relative path is used, it will resolve
31 /// as relative to a data root path provided via respective CLI argument.
32 /// @see HART_REQUIRES_DATA_PATH_ARG
33 /// @param loop Indicates whether the signal should loop the audio or produce
34 /// silence after wav file runs out of frames.
35 WavFile (const std::string& filePath, Loop loop = Loop::no):
36 m_filePath (filePath),
37 m_loop (loop)
38 {
39 // TODO: Check if the file exists first
40
41 drwav_uint64 numFrames;
42 unsigned int numChannels;
43 unsigned int wavSampleRateHz;
44
45 float* pcmFrames = drwav_open_file_and_read_pcm_frames_f32 (
46 toAbsolutePath (filePath).c_str(),
47 &numChannels,
48 &wavSampleRateHz,
49 &numFrames,
50 nullptr
51 );
52
53 if (pcmFrames == nullptr)
54 HART_THROW_OR_RETURN_VOID (hart::IOError, std::string ("Could not read frames from the wav file"));
55
56 m_wavFrames = std::make_shared<AudioBuffer<float>> (numChannels, numFrames);
57
58 for (size_t frame = 0; frame < numFrames; ++frame)
59 for (size_t channel = 0; channel < numChannels; ++channel)
60 (*m_wavFrames)[channel][frame] = pcmFrames[frame * numChannels + channel];
61
62 drwav_free (pcmFrames, nullptr);
63
64 m_wavSampleRateHz = static_cast<double> (wavSampleRateHz);
65 m_wavNumChannels = static_cast<size_t> (numChannels);
66 }
67
68 /// @copydoc Signal::supportsNumChannels()
69 /// @note WavFile can only fill as much channels as there are in the wav file, or less.
70 /// For instance, if the wav file is stereo, it can generate two channels (as they are),
71 /// one channel (left, discarding right), but not three channels.
72 bool supportsNumChannels (size_t numChannels) const override
73 {
74 return numChannels <= m_wavNumChannels;
75 };
76
77 void prepare (double sampleRateHz, size_t numOutputChannels, size_t /*maxBlockSizeFrames*/) override
78 {
79 // There are a few ovbvious cases where channel number mismatch can be gracefully resolved - perhaps in the future
80 if (numOutputChannels != m_wavNumChannels)
81 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, std::string ("Unexpected channel number"));
82
83 if (floatsNotEqual (sampleRateHz, m_wavSampleRateHz))
84 HART_THROW_OR_RETURN_VOID (hart::UnsupportedError, std::string ("Wav file is in different sampling rate, resampling not supported"));
85 }
86
87 void renderNextBlock (AudioBuffer<SampleType>& output) override
88 {
89 // TODO: Add support for number of channels different from the wav file
90 // TODO: Add resampling
91 const size_t numFrames = output.getNumFrames();
92 size_t frameInOutputBuffer = 0;
93 size_t frameInWavBuffer = m_wavOffsetFrames;
94
95 while (m_wavOffsetFrames < m_wavFrames->getNumFrames() && frameInOutputBuffer < numFrames)
96 {
97 for (size_t channel = 0; channel < m_wavNumChannels; ++channel)
98 output[channel][frameInOutputBuffer] = (*m_wavFrames)[channel][frameInWavBuffer];
99
100 ++frameInOutputBuffer;
101 ++frameInWavBuffer;
102 ++m_wavOffsetFrames;
103
104 if (m_loop == Loop::yes)
105 m_wavOffsetFrames %= m_wavFrames->getNumFrames();
106 }
107
108 while (frameInOutputBuffer < numFrames)
109 {
110 hassert (m_loop == Loop::no);
111
112 for (size_t channel = 0; channel < m_wavNumChannels; ++channel)
113 output[channel][frameInOutputBuffer] = (SampleType) 0;
114
115 ++frameInOutputBuffer;
116 }
117 }
118
119 void reset() override
120 {
121 m_wavOffsetFrames = 0;
122 }
123
124 void represent (std::ostream& stream) const override
125 {
126 stream << "WavFile (\"" << m_filePath << (m_loop == Loop::yes ? "\", Loop::yes)" : "\", Loop::no)");
127 }
128
129private:
130 const std::string m_filePath;
131 const Loop m_loop;
132 size_t m_wavNumChannels;
133 double m_wavSampleRateHz;
134 size_t m_wavOffsetFrames = 0;
135 std::shared_ptr<AudioBuffer<float>> m_wavFrames;
136};
137
139
140} // namespace hart
Container for audio data.
SampleType * operator[](size_t channel)
Get a raw pointer to a specific channel's mutable audio data.
size_t getNumFrames() const
Get number of frames (samples)
Thrown when a numbers of channels is mismatched.
Thrown when some I/O operation fails.
Base class for signals.
Thrown when some parameter has an unsupported value.
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.
#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
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.
Loop
Helper values for something that could loop, like a Signal.
#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)