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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ClickNode.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
11
12namespace tracktion { inline namespace engine
13{
14
15namespace
16{
17 juce::AudioBuffer<float> loadWavDataIntoMemory (const void* data, size_t size, double targetSampleRate)
18 {
19 auto in = new juce::MemoryInputStream (data, size, false);
20
21 juce::WavAudioFormat wavFormat;
23
24 if (r == nullptr)
25 return {};
26
27 auto ratio = r->sampleRate / targetSampleRate;
28 auto targetLength = (int) (r->lengthInSamples / ratio);
29
30 juce::AudioBuffer<float> buf ((int) r->numChannels, targetLength);
31
32 {
33 juce::AudioFormatReaderSource readerSource (r.get(), false);
34
35 juce::ResamplingAudioSource resamplerSource (&readerSource, false, (int) r->numChannels);
36 resamplerSource.setResamplingRatio (ratio);
37 resamplerSource.prepareToPlay (targetLength, targetSampleRate);
38
40 info.buffer = &buf;
41 info.startSample = 0;
42 info.numSamples = targetLength;
43 resamplerSource.getNextAudioBlock (info);
44 }
45
46 return buf;
47 }
48
49 juce::AudioBuffer<float> loadWavDataIntoMemory (const juce::File& file, double targetSampleRate)
50 {
52 file.loadFileAsData (mb);
53
54 return loadWavDataIntoMemory (mb.getData(), mb.getSize(), targetSampleRate);
55 }
56}
57
58//==============================================================================
59//==============================================================================
61 : edit (e), midi (isMidi)
62{
64}
65
66void ClickGenerator::prepareToPlay (double newSampleRate, TimePosition startTime)
67{
68 if (sampleRate != newSampleRate)
69 {
70 sampleRate = newSampleRate;
71 bigClick = {};
72 littleClick = {};
73 }
74
75 if (midi)
76 {
77 bigClickMidiNote = Click::getMidiClickNote (edit.engine, true);
78 littleClickMidiNote = Click::getMidiClickNote (edit.engine, false);
79 }
80 else
81 {
82 if (bigClick.getNumSamples() == 0)
83 {
84 juce::File file (Click::getClickWaveFile (edit.engine, true));
85
86 if (file.existsAsFile())
87 bigClick = loadWavDataIntoMemory (file, sampleRate);
88
89 if (bigClick.getNumSamples() == 0)
90 bigClick = loadWavDataIntoMemory (TracktionBinaryData::bigclick_wav, TracktionBinaryData::bigclick_wavSize, sampleRate);
91 }
92
93 if (littleClick.getNumSamples() == 0)
94 {
95 juce::File file (Click::getClickWaveFile (edit.engine, false));
96
97 if (file.existsAsFile())
98 littleClick = loadWavDataIntoMemory (file, sampleRate);
99
100 if (littleClick.getNumSamples() == 0)
101 littleClick = loadWavDataIntoMemory (TracktionBinaryData::littleclick_wav, TracktionBinaryData::littleclick_wavSize, sampleRate);
102 }
103 }
104
105 tempoPosition.set (startTime);
106}
107
108namespace
109{
110 struct BeatInfo
111 {
113 bool isFirstBeatOfBar = false;
114 };
115
116 inline BeatInfo getBeatInfo (const tempo::Sequence& sequence, tempo::Sequence::Position& tempoPosition)
117 {
118 const auto beats = tempoPosition.getBeats().inBeats();
119 int beat = static_cast<int> (std::floor (beats));
120 const auto beatTime = sequence.toTime (BeatPosition::fromBeats (beat));
121 const bool isFirstBeatOfBar = tempoPosition.getBarsBeats().getWholeBeats() == 0;
122
123 return { beatTime, isFirstBeatOfBar };
124 }
125}
126
127void ClickGenerator::processBlock (choc::buffer::ChannelArrayView<float>* destBuffer,
128 MidiMessageArray* bufferForMidiMessages, TimeRange editTime)
129{
130 // Use the end time here to avoid the end of blocks playing the start of a beat
131 if (isMutedAtTime (editTime.getEnd()))
132 return;
133
134 const bool emphasis = edit.clickTrackEmphasiseBars;
135
136 tempoPosition.set (editTime.getStart());
137 auto beatInfo = getBeatInfo (sequence, tempoPosition);
138
139 if (midi)
140 {
141 if (bufferForMidiMessages == nullptr)
142 return;
143
144 auto gain = edit.getClickTrackVolume();
145 auto t = beatInfo.time;
146
147 while (t < editTime.getEnd())
148 {
149 auto note = (emphasis && beatInfo.isFirstBeatOfBar) ? bigClickMidiNote
150 : littleClickMidiNote;
151
152 if (t >= editTime.getStart())
153 bufferForMidiMessages->addMidiMessage (juce::MidiMessage::noteOn (10, note, gain),
154 (t - editTime.getStart()).inSeconds(),
155 MidiMessageArray::notMPE);
156
157 tempoPosition.add (1_bd);
158 beatInfo = getBeatInfo (sequence, tempoPosition);
159 t = beatInfo.time;
160 }
161 }
162 else
163 {
164 if (destBuffer == nullptr)
165 return;
166
167 if (isPlaying())
168 {
169 auto num = std::min (samplesRemaining(), int (destBuffer->getNumFrames()));
170 auto dstView = destBuffer->getFrameRange ({ 0, (choc::buffer::FrameCount) num });
171
172 render (dstView);
173 }
174
175 auto t = beatInfo.time;
176
177 while (t < editTime.getEnd())
178 {
179 auto b = (emphasis && beatInfo.isFirstBeatOfBar) ? &bigClick : &littleClick;
180
181 if (b->getNumSamples() > 0 && t >= editTime.getStart() && ! isMutedAtTime (t))
182 {
183 const auto clickStartTime = t - editTime.getStart();
184 const auto clickStartOffset = static_cast<int> (toSamples (clickStartTime, sampleRate));
185
186 trigger (b);
187 isMutedAtTime (t);
188
189 auto num = std::min (samplesRemaining(), int (destBuffer->getNumFrames()) - int (clickStartOffset));
190 auto dstView = destBuffer->getFrameRange ({ choc::buffer::FrameCount (clickStartOffset), choc::buffer::FrameCount (clickStartOffset + num) });
191
192 render (dstView);
193 }
194
195 tempoPosition.add (1_bd);
196 beatInfo = getBeatInfo (sequence, tempoPosition);
197 t = beatInfo.time;
198 }
199 }
200}
201
202bool ClickGenerator::isMutedAtTime (TimePosition time) const
203{
204 const bool clickEnabled = edit.clickTrackEnabled.get();
205
206 auto range = edit.getClickTrackRange();
207 if (! range.isEmpty() && time < range.getStart())
208 return true;
209
210 if (clickEnabled && edit.clickTrackRecordingOnly)
211 return ! (edit.getTransport().isRecording() || context.getNumActivelyRecordingDevices() >= 1);
212
213 if (! clickEnabled)
214 return ! range.contains (time);
215
216 return ! clickEnabled;
217}
218
219bool ClickGenerator::isPlaying()
220{
221 return currentSample != nullptr && samplePos >= 0;
222}
223
224void ClickGenerator::trigger (juce::AudioBuffer<float>* s)
225{
226 currentSample = s;
227 samplePos = 0;
228}
229
230int ClickGenerator::samplesRemaining()
231{
232 return currentSample->getNumSamples() - samplePos;
233}
234
235void ClickGenerator::render (choc::buffer::ChannelArrayView<float>& view)
236{
237 auto todo = view.getNumFrames();
238 auto gain = edit.getClickTrackVolume();
239
240 copyRemappingChannels (view, toBufferView (*currentSample).getFrameRange ({ choc::buffer::FrameCount (samplePos), choc::buffer::FrameCount (samplePos) + todo }));
241 applyGain (view, gain);
242
243 samplePos += static_cast<int> (todo);
244 if (samplePos >= currentSample->getNumSamples())
245 reset();
246}
247
248void ClickGenerator::reset()
249{
250 currentSample = nullptr;
251 samplePos = -1;
252}
253
254//==============================================================================
255//==============================================================================
256ClickNode::ClickNode (Edit& e, int numAudioChannels, bool isMidi, tracktion::graph::PlayHead& playHeadToUse)
257 : edit (e), playHead (playHeadToUse),
258 clickGenerator (edit, isMidi),
259 numChannels (numAudioChannels), generateMidi (isMidi)
260{
261}
262
267
269{
271 props.hasAudio = ! generateMidi;
272 props.hasMidi = generateMidi;
273 props.numberOfChannels = numChannels;
274 props.nodeID = (size_t) edit.getProjectItemID().getRawID();
275
276 return props;
277}
278
280{
281 sampleRate = info.sampleRate;
282 clickGenerator.prepareToPlay (sampleRate,
283 TimePosition::fromSamples (playHead.getPosition(), sampleRate));
284}
285
287{
288 return true;
289}
290
292{
293 SCOPED_REALTIME_CHECK
294
295 if (playHead.isUserDragging() || ! playHead.isPlaying())
296 return;
297
298 const auto splitTimelinePosition = referenceSampleRangeToSplitTimelineRange (playHead, pc.referenceSampleRange);
299 const auto editTime = tracktion::timeRangeFromSamples (splitTimelinePosition.timelineRange1, sampleRate);
300 clickGenerator.processBlock (&pc.buffers.audio, &pc.buffers.midi, editTime);
301}
302
303
304//==============================================================================
305namespace Click
306{
307 int getMidiClickNote (Engine& e, bool big)
308 {
309 auto& storage = e.getPropertyStorage();
310 int n;
311
312 if (big)
313 {
314 n = storage.getProperty (SettingID::clickTrackMidiNoteBig, 37);
315
316 if (n < 0 || n > 127)
317 n = 37;
318 }
319 else
320 {
321 n = storage.getProperty (SettingID::clickTrackMidiNoteLittle, 76);
322
323 if (n < 0 || n > 127)
324 n = 76;
325 }
326
327 return n;
328 }
329
330 juce::String getClickWaveFile (Engine& e, bool big)
331 {
332 return e.getPropertyStorage().getProperty (big ? SettingID::clickTrackSampleBig
333 : SettingID::clickTrackSampleSmall);
334 }
335
336 void setMidiClickNote (Engine& e, bool big, int noteNum)
337 {
338 auto& storage = e.getPropertyStorage();
339
340 if (big)
341 storage.setProperty (SettingID::clickTrackMidiNoteBig, juce::String (noteNum));
342 else
343 storage.setProperty (SettingID::clickTrackMidiNoteLittle, juce::String (noteNum));
344
346 }
347
348 void setClickWaveFile (Engine& e, bool big, const juce::String& filename)
349 {
350 auto& storage = e.getPropertyStorage();
351
352 if (big)
353 storage.setProperty (SettingID::clickTrackSampleBig, filename);
354 else
355 storage.setProperty (SettingID::clickTrackSampleSmall, filename);
356
358 }
359}
360
361}} // namespace tracktion { inline namespace engine
assert
int getNumSamples() const noexcept
Type get() const noexcept
void * getData() noexcept
size_t getSize() const noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
var getProperty(const Identifier &propertyName, const var &defaultReturnValue) const
void processBlock(choc::buffer::ChannelArrayView< float > *, MidiMessageArray *, TimeRange)
Adds clicks to a block of audio and MIDI for a given time range.
void prepareToPlay(double sampleRate, TimePosition startTime)
Prepares a ClickGenerator to be played.
ClickGenerator(Edit &, bool isMidi)
Creates a click generator for an Edit.
std::vector< Node * > getDirectInputNodes() override
Should return all the inputs directly feeding in to this node.
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
void process(ProcessContext &) override
Called when the node is to be processed.
void prepareToPlay(const tracktion::graph::PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
The Tracktion Edit class!
TimeRange getClickTrackRange() const noexcept
Returns the range the click track will be audible within.
float getClickTrackVolume() const noexcept
Returns the click track volume.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
juce::CachedValue< bool > clickTrackRecordingOnly
Whether the click track should be audible only when recording.
juce::CachedValue< bool > clickTrackEmphasiseBars
Whether the click track should emphasise bars.
juce::CachedValue< bool > clickTrackEnabled
Whether the click track is enabled.
ProjectItemID getProjectItemID() const noexcept
Returns the ProjectItemID of the Edit.
Engine & engine
A reference to the Engine.
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
int64_t getRawID() const noexcept
Returns a combined ID as an integer, useful for creating hashes.
EditPlaybackContext * getCurrentPlaybackContext() const
Returns the active EditPlaybackContext if this Edit is attached to the DeviceManager for playback.
bool isRecording() const
Returns true if recording is in progress.
static std::vector< std::unique_ptr< ScopedContextAllocator > > restartAllTransports(Engine &, bool clearDevices)
Restarts all TransportControl[s] in the Edit.
Struct to describe a single iteration of a process call.
Converts a monotonically increasing reference range in to a timeline range.
int64_t getPosition() const
Returns the current timeline position.
bool isUserDragging() const
Returns true if the user is dragging.
bool isPlaying() const noexcept
Returns true is the play head is currently playing.
T floor(T... args)
typedef int
T min(T... args)
choc::buffer::BufferView< SampleType, choc::buffer::SeparateChannelLayout > toBufferView(juce::AudioBuffer< SampleType > &buffer)
Converts a juce::AudioBuffer<SampleType> to a choc::buffer::BufferView.
SettingID
A list of settings the engine will get and set.
SplitTimelineRange referenceSampleRangeToSplitTimelineRange(const PlayHead &playHead, juce::Range< int64_t > referenceSampleRange)
Converts a reference sample range to a TimelinePositionWindow which could have two time ranges if the...
AudioBuffer< float > * buffer
constexpr double inBeats() const
Returns the position as a number of beats.
Represents a position in real-life time.
int getWholeBeats() const
Returns the number of whole beats.
A Sequence::Position is an iterator through a Sequence.
TimePosition add(TimeDuration)
Increments the position by a time duration.
BeatPosition getBeats() const
Returns the current beats of the Position.
void set(TimePosition)
Sets the Position to a new time.
BarsAndBeats getBarsBeats() const
Returns the current bars and beats of the Position.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...
typedef size_t
time