tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_WaveAudioNode.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15 legacy::EditTimeRange editTime,
16 double off,
18 LiveClipLevel level,
19 double speed,
20 const juce::AudioChannelSet& channelSetToUse)
21 : editPosition (editTime),
22 loopSection (loop.getStart() * speed, loop.getEnd() * speed),
23 offset (off),
24 originalSpeedRatio (speed),
25 audioFile (af),
26 clipLevel (level),
27 channelsToUse (channelSetToUse)
28{
29}
30
31WaveAudioNode::~WaveAudioNode()
32{
33}
34
35//==============================================================================
36void WaveAudioNode::getAudioNodeProperties (AudioNodeProperties& info)
37{
38 info.hasAudio = true;
39 info.hasMidi = false;
40 info.numberOfChannels = juce::jlimit (1, std::max (channelsToUse.size(), 1),
41 audioFile.getNumChannels());
42}
43
44bool WaveAudioNode::purgeSubNodes (bool keepAudio, bool)
45{
46 return keepAudio;
47}
48
49void WaveAudioNode::visitNodes (const VisitorFn& v)
50{
51 v (*this);
52}
53
54SampleCount WaveAudioNode::editTimeToFileSample (double editTime) const noexcept
55{
56 return static_cast<SampleCount> ((editTime - (editPosition.getStart() - offset))
57 * originalSpeedRatio * audioFileSampleRate + 0.5);
58}
59
61{
62 PerChannelState() { resampler.reset(); }
63
65 float lastSample = 0;
66};
67
69{
70 reader = audioFile.engine->getAudioFileManager().cache.createReader (audioFile);
71 outputSampleRate = info.sampleRate;
72 updateFileSampleRate();
73
74 channelState.clear();
75
76 if (reader != nullptr)
77 for (int i = std::max (channelsToUse.size(), reader->getNumChannels()); --i >= 0;)
78 channelState.add (new PerChannelState());
79}
80
81bool WaveAudioNode::isReadyToRender()
82{
83 // if the hash is 0 it means an empty file path which means a missing file so
84 // this will never return a valid reader and we should just bail
85 if (audioFile.isNull())
86 return true;
87
88 if (reader == nullptr)
89 {
90 reader = audioFile.engine->getAudioFileManager().cache.createReader (audioFile);
91
92 if (reader == nullptr)
93 return false;
94 }
95
96 if (audioFileSampleRate == 0.0 && ! updateFileSampleRate())
97 return false;
98
99 return true;
100}
101
102bool WaveAudioNode::updateFileSampleRate()
103{
104 if (reader != nullptr)
105 {
106 audioFileSampleRate = reader->getSampleRate();
107
108 if (audioFileSampleRate > 0)
109 {
110 if (! loopSection.isEmpty())
111 reader->setLoopRange (SampleRange ((SampleCount) (loopSection.getStart() * audioFileSampleRate),
112 (SampleCount) (loopSection.getEnd() * audioFileSampleRate)));
113
114 return true;
115 }
116 }
117
118 return false;
119}
120
122{
123 reader = nullptr;
124}
125
126void WaveAudioNode::renderOver (const AudioRenderContext& rc)
127{
128 callRenderAdding (rc);
129}
130
131void WaveAudioNode::renderAdding (const AudioRenderContext& rc)
132{
133 invokeSplitRender (rc, *this);
134}
135
136void WaveAudioNode::renderSection (const AudioRenderContext& rc, legacy::EditTimeRange editTime)
137{
138 // keep a local copy, because releaseAudioNodeResources may remove the reader halfway through..
139 const auto localReader = reader;
140
141 rc.sanityCheck();
142
143 if (rc.destBuffer == nullptr
144 || rc.bufferNumSamples == 0
145 || localReader == nullptr
146 || editTime.getStart() >= editPosition.getEnd())
147 return;
148
149 SCOPED_REALTIME_CHECK
150
151 if (audioFileSampleRate == 0.0 && ! updateFileSampleRate())
152 return;
153
154 const auto fileStart = editTimeToFileSample (editTime.getStart());
155 const auto fileEnd = editTimeToFileSample (editTime.getEnd());
156 const auto numFileSamples = (int) (fileEnd - fileStart);
157
158 localReader->setReadPosition (fileStart);
159
160 AudioScratchBuffer fileData (rc.destBufferChannels.size(), numFileSamples + 2);
161
162 int lastSampleFadeLength = 0;
163
164 {
165 SCOPED_REALTIME_CHECK
166
167 if (localReader->readSamples (numFileSamples + 2, fileData.buffer, rc.destBufferChannels, 0,
168 channelsToUse,
169 rc.isRendering ? 5000 : 3))
170 {
171 if (! rc.isContiguousWithPreviousBlock() && ! rc.isFirstBlockOfLoop())
172 lastSampleFadeLength = std::min (rc.bufferNumSamples, rc.playhead.isUserDragging() ? 40 : 10);
173 }
174 else
175 {
176 lastSampleFadeLength = std::min (rc.bufferNumSamples, 40);
177 fileData.buffer.clear();
178 }
179 }
180
181 float gains[2];
182
183 // For stereo, use the pan, otherwise ignore it
184 if (rc.destBuffer->getNumChannels() == 2)
185 clipLevel.getLeftAndRightGains (gains[0], gains[1]);
186 else
187 gains[0] = gains[1] = clipLevel.getGainIncludingMute();
188
189 if (rc.playhead.isUserDragging())
190 {
191 gains[0] *= 0.4f;
192 gains[1] *= 0.4f;
193 }
194
195 auto ratio = numFileSamples / (double) rc.bufferNumSamples;
196
197 if (ratio > 0.0)
198 {
199 auto numDestChannels = std::min (rc.destBuffer->getNumChannels(), fileData.buffer.getNumChannels());
200 jassert (numDestChannels <= channelState.size()); // this should always have been made big enough
201
202 for (int channel = 0; channel < numDestChannels; ++channel)
203 {
204 if (channel < channelState.size())
205 {
206 const auto src = fileData.buffer.getReadPointer (channel);
207 const auto dest = rc.destBuffer->getWritePointer (channel, rc.bufferStartSample);
208
209 auto& state = *channelState.getUnchecked (channel);
210 state.resampler.processAdding (ratio, src, dest, rc.bufferNumSamples, gains[channel & 1]);
211
212 if (lastSampleFadeLength > 0)
213 {
214 for (int i = 0; i < lastSampleFadeLength; ++i)
215 {
216 auto alpha = i / (float) lastSampleFadeLength;
217 dest[i] = alpha * dest[i] + state.lastSample * (1.0f - alpha);
218 }
219 }
220
221 state.lastSample = dest[rc.bufferNumSamples - 1];
222 }
223 else
224 {
225 rc.destBuffer->clear (channel, rc.bufferStartSample, rc.bufferNumSamples);
226 }
227 }
228 }
229}
230
231void WaveAudioNode::prepareForNextBlock (const AudioRenderContext& rc)
232{
233 SCOPED_REALTIME_CHECK
234
235 // keep a local copy, because releaseAudioNodeResources may remove the reader halfway through..
236 if (auto localReader = reader)
237 localReader->setReadPosition (editTimeToFileSample (rc.getEditTime().editRange1.getStart()));
238}
239
240}} // namespace tracktion { inline namespace engine
int size() const noexcept
Reader::Ptr createReader(const AudioFile &)
Creates a Reader to read an AudioFile.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
bool purgeSubNodes(bool keepAudio, bool keepMidi) override
Tells the node to delete any sub-nodes that don't produce the required type of output.
WaveAudioNode(const AudioFile &file, legacy::EditTimeRange editTime, double offset, legacy::EditTimeRange loopSection, LiveClipLevel level, double speedRatio, const juce::AudioChannelSet &channelsToUse)
offset is a time added to the start of the file, e.g.
void releaseAudioNodeResources() override
tells the node that play has stopped, and it can free up anything it no longer needs.
void prepareAudioNodeToPlay(const PlaybackInitialisationInfo &) override
tells the node to initialise itself ready for playing from the given time.
#define jassert(expression)
typedef int
typedef double
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Interpolators::Lagrange LagrangeInterpolator
Holds some really basic properties of a node.
Passed into AudioNodes when they are being initialised, to give them useful contextual information th...
Provides a thread-safe way to share a clip's levels with an audio engine without worrying about the C...
float getGainIncludingMute() const noexcept
Returns the clip's gain if the clip is not muted.
void getLeftAndRightGains(float &left, float &right) const noexcept
Reutrns the left and right gains taking in to account mute and pan values.