JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_AudioProcessorPlayer.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29template <typename Value>
31{
32 ChannelInfo() = default;
34 : data (dataIn), numChannels (numChannelsIn) {}
35
36 Value* const* data = nullptr;
37 int numChannels = 0;
38};
39
63static void initialiseIoBuffers (ChannelInfo<const float> ins,
65 const int numSamples,
66 int processorIns,
67 int processorOuts,
68 AudioBuffer<float>& tempBuffer,
69 std::vector<float*>& channels)
70{
71 jassert ((int) channels.size() >= jmax (processorIns, processorOuts));
72
73 size_t totalNumChans = 0;
74 const auto numBytes = (size_t) numSamples * sizeof (float);
75
76 const auto prepareInputChannel = [&] (int index)
77 {
78 if (ins.numChannels == 0)
79 zeromem (channels[totalNumChans], numBytes);
80 else
81 memcpy (channels[totalNumChans], ins.data[index % ins.numChannels], numBytes);
82 };
83
85 {
86 // If there aren't enough output channels for the number of
87 // inputs, we need to use some temporary extra ones (can't
88 // use the input data in case it gets written to).
90 jassert (tempBuffer.getNumSamples() >= numSamples);
91
92 for (int i = 0; i < processorOuts; ++i)
93 {
94 channels[totalNumChans] = outs.data[i];
97 }
98
99 for (auto i = processorOuts; i < processorIns; ++i)
100 {
101 channels[totalNumChans] = tempBuffer.getWritePointer (i - processorOuts);
104 }
105 }
106 else
107 {
108 for (int i = 0; i < processorIns; ++i)
109 {
110 channels[totalNumChans] = outs.data[i];
113 }
114
115 for (auto i = processorIns; i < processorOuts; ++i)
116 {
117 channels[totalNumChans] = outs.data[i];
118 zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float));
120 }
121 }
122}
123
124//==============================================================================
125AudioProcessorPlayer::AudioProcessorPlayer (bool doDoublePrecisionProcessing)
126 : isDoublePrecision (doDoublePrecisionProcessing)
127{
128}
129
134
135//==============================================================================
136AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const
137{
138 if (proc.isMidiEffect())
139 return {};
140
141 std::vector<NumChannels> layouts { deviceChannels };
142
143 if (deviceChannels.ins == 0 || deviceChannels.ins == 1)
144 {
145 layouts.emplace_back (defaultProcessorChannels.ins, deviceChannels.outs);
146 layouts.emplace_back (deviceChannels.outs, deviceChannels.outs);
147 }
148
149 const auto it = std::find_if (layouts.begin(), layouts.end(), [&] (const NumChannels& chans)
150 {
151 return proc.checkBusesLayoutSupported (chans.toLayout());
152 });
153
154 return it != std::end (layouts) ? *it : layouts[0];
155}
156
157void AudioProcessorPlayer::resizeChannels()
158{
159 const auto maxChannels = jmax (deviceChannels.ins,
160 deviceChannels.outs,
161 actualProcessorChannels.ins,
162 actualProcessorChannels.outs);
163 channels.resize ((size_t) maxChannels);
164 tempBuffer.setSize (maxChannels, blockSize);
165}
166
168{
169 const ScopedLock sl (lock);
170
171 if (processor == processorToPlay)
172 return;
173
174 sampleCount = 0;
175 currentWorkgroup.reset();
176
177 if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
178 {
179 defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
180 actualProcessorChannels = findMostSuitableLayout (*processorToPlay);
181
182 if (processorToPlay->isMidiEffect())
183 processorToPlay->setRateAndBufferSizeDetails (sampleRate, blockSize);
184 else
185 processorToPlay->setPlayConfigDetails (actualProcessorChannels.ins,
186 actualProcessorChannels.outs,
187 sampleRate,
188 blockSize);
189
190 auto supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision;
191
192 processorToPlay->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
193 : AudioProcessor::singlePrecision);
194
195 processorToPlay->prepareToPlay (sampleRate, blockSize);
196 }
197
198 AudioProcessor* oldOne = nullptr;
199
200 oldOne = isPrepared ? processor : nullptr;
201 processor = processorToPlay;
202 isPrepared = true;
203 resizeChannels();
204
205 if (oldOne != nullptr)
206 oldOne->releaseResources();
207}
208
210{
211 if (doublePrecision != isDoublePrecision)
212 {
213 const ScopedLock sl (lock);
214
215 currentWorkgroup.reset();
216
217 if (processor != nullptr)
218 {
219 processor->releaseResources();
220
221 auto supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision;
222
223 processor->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
224 : AudioProcessor::singlePrecision);
225
226 processor->prepareToPlay (sampleRate, blockSize);
227 }
228
229 isDoublePrecision = doublePrecision;
230 }
231}
232
234{
235 if (midiOutput != midiOutputToUse)
236 {
237 const ScopedLock sl (lock);
238 midiOutput = midiOutputToUse;
239 }
240}
241
242//==============================================================================
244 const int numInputChannels,
245 float* const* const outputChannelData,
246 const int numOutputChannels,
247 const int numSamples,
248 const AudioIODeviceCallbackContext& context)
249{
250 const ScopedLock sl (lock);
251
252 jassert (currentDevice != nullptr);
253
254 // These should have been prepared by audioDeviceAboutToStart()...
255 jassert (sampleRate > 0 && blockSize > 0);
256
257 incomingMidi.clear();
258 messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
259
260 initialiseIoBuffers ({ inputChannelData, numInputChannels },
261 { outputChannelData, numOutputChannels },
262 numSamples,
263 actualProcessorChannels.ins,
264 actualProcessorChannels.outs,
265 tempBuffer,
266 channels);
267
268 const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs);
269 AudioBuffer<float> buffer (channels.data(), (int) totalNumChannels, numSamples);
270
271 if (processor != nullptr)
272 {
273 // The processor should be prepared to deal with the same number of output channels
274 // as our output device.
275 jassert (processor->isMidiEffect() || numOutputChannels == actualProcessorChannels.outs);
276
277 const ScopedLock sl2 (processor->getCallbackLock());
278
279 if (std::exchange (currentWorkgroup, currentDevice->getWorkgroup()) != currentDevice->getWorkgroup())
280 processor->audioWorkgroupContextChanged (currentWorkgroup);
281
282 class PlayHead final : private AudioPlayHead
283 {
284 public:
285 PlayHead (AudioProcessor& proc,
286 Optional<uint64_t> hostTimeIn,
287 uint64_t sampleCountIn,
288 double sampleRateIn)
289 : processor (proc),
290 hostTimeNs (hostTimeIn),
291 sampleCount (sampleCountIn),
292 seconds ((double) sampleCountIn / sampleRateIn)
293 {
294 if (useThisPlayhead)
295 processor.setPlayHead (this);
296 }
297
298 ~PlayHead() override
299 {
300 if (useThisPlayhead)
301 processor.setPlayHead (nullptr);
302 }
303
304 private:
305 Optional<PositionInfo> getPosition() const override
306 {
307 PositionInfo info;
308 info.setHostTimeNs (hostTimeNs);
309 info.setTimeInSamples ((int64_t) sampleCount);
310 info.setTimeInSeconds (seconds);
311 return info;
312 }
313
314 AudioProcessor& processor;
315 Optional<uint64_t> hostTimeNs;
316 uint64_t sampleCount;
317 double seconds;
318 bool useThisPlayhead = processor.getPlayHead() == nullptr;
319 };
320
321 PlayHead playHead { *processor,
322 context.hostTimeNs != nullptr ? makeOptional (*context.hostTimeNs) : nullopt,
323 sampleCount,
324 sampleRate };
325
326 sampleCount += (uint64_t) numSamples;
327
328 if (! processor->isSuspended())
329 {
330 if (processor->isUsingDoublePrecision())
331 {
332 conversionBuffer.makeCopyOf (buffer, true);
333 processor->processBlock (conversionBuffer, incomingMidi);
334 buffer.makeCopyOf (conversionBuffer, true);
335 }
336 else
337 {
338 processor->processBlock (buffer, incomingMidi);
339 }
340
341 if (midiOutput != nullptr)
342 {
343 if (midiOutput->isBackgroundThreadRunning())
344 {
345 midiOutput->sendBlockOfMessages (incomingMidi,
347 sampleRate);
348 }
349 else
350 {
351 midiOutput->sendBlockOfMessagesNow (incomingMidi);
352 }
353 }
354
355 return;
356 }
357 }
358
359 for (int i = 0; i < numOutputChannels; ++i)
360 FloatVectorOperations::clear (outputChannelData[i], numSamples);
361}
362
364{
365 currentDevice = device;
366 auto newSampleRate = device->getCurrentSampleRate();
370
371 const ScopedLock sl (lock);
372
373 sampleRate = newSampleRate;
374 blockSize = newBlockSize;
375 deviceChannels = { numChansIn, numChansOut };
376
377 resizeChannels();
378
379 messageCollector.reset (sampleRate);
380
381 currentWorkgroup.reset();
382
383 if (processor != nullptr)
384 {
385 if (isPrepared)
386 processor->releaseResources();
387
388 auto* oldProcessor = processor;
389 setProcessor (nullptr);
391 }
392}
393
395{
396 const ScopedLock sl (lock);
397
398 if (processor != nullptr && isPrepared)
399 processor->releaseResources();
400
401 sampleRate = 0.0;
402 blockSize = 0;
403 isPrepared = false;
404 tempBuffer.setSize (1, 1);
405
406 currentDevice = nullptr;
407 currentWorkgroup.reset();
408}
409
411{
412 messageCollector.addMessageToQueue (message);
413}
414
415//==============================================================================
416//==============================================================================
417#if JUCE_UNIT_TESTS
418
420{
422 : UnitTest ("AudioProcessorPlayer", UnitTestCategories::audio) {}
423
424 void runTest() override
425 {
426 struct Layout
427 {
428 int numIns, numOuts;
429 };
430
431 const Layout processorLayouts[] { Layout { 0, 0 },
432 Layout { 1, 1 },
433 Layout { 4, 4 },
434 Layout { 4, 8 },
435 Layout { 8, 4 } };
436
437 beginTest ("Buffers are prepared correctly for a variety of channel layouts");
438 {
439 for (const auto& layout : processorLayouts)
440 {
441 for (const auto numSystemInputs : { 0, 1, layout.numIns })
442 {
443 const int numSamples = 256;
444 const auto systemIns = getTestBuffer (numSystemInputs, numSamples);
445 auto systemOuts = getTestBuffer (layout.numOuts, numSamples);
446 AudioBuffer<float> tempBuffer (jmax (layout.numIns, layout.numOuts), numSamples);
447 std::vector<float*> channels ((size_t) jmax (layout.numIns, layout.numOuts), nullptr);
448
449 initialiseIoBuffers ({ systemIns.getArrayOfReadPointers(), systemIns.getNumChannels() },
450 { systemOuts.getArrayOfWritePointers(), systemOuts.getNumChannels() },
451 numSamples,
452 layout.numIns,
453 layout.numOuts,
454 tempBuffer,
455 channels);
456
457 int channelIndex = 0;
458
459 for (const auto& channel : channels)
460 {
461 const auto value = [&]
462 {
463 // Any channels past the number of inputs should be silent.
464 if (layout.numIns <= channelIndex)
465 return 0.0f;
466
467 // If there's no input, all input channels should be silent.
468 if (numSystemInputs == 0) return 0.0f;
469
470 // If there's one input, all input channels should copy from that input.
471 if (numSystemInputs == 1) return 1.0f;
472
473 // Otherwise, each processor input should match the corresponding system input.
474 return (float) (channelIndex + 1);
475 }();
476
477 expect (FloatVectorOperations::findMinAndMax (channel, numSamples) == Range<float> (value, value));
478
479 channelIndex += 1;
480 }
481 }
482 }
483 }
484 }
485
486 static AudioBuffer<float> getTestBuffer (int numChannels, int numSamples)
487 {
488 AudioBuffer<float> result (numChannels, numSamples);
489
490 for (int i = 0; i < result.getNumChannels(); ++i)
491 FloatVectorOperations::fill (result.getWritePointer (i), (float) i + 1, result.getNumSamples());
492
493 return result;
494 }
495};
496
498
499#endif
500
501} // namespace juce
A multi-channel buffer containing floating point audio samples.
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Changes the buffer's size or number of channels.
void makeCopyOf(const AudioBuffer< OtherType > &other, bool avoidReallocating=false)
Resizes this buffer to match the given one, and copies all of its content across.
Type * getWritePointer(int channelNumber) noexcept
Returns a writeable pointer to one of the buffer's channels.
int getNumChannels() const noexcept
Returns the number of channels of audio data that this buffer contains.
int getNumSamples() const noexcept
Returns the number of samples allocated in each of the buffer's channels.
Base class for an audio device with synchronised input and output channels.
virtual double getCurrentSampleRate()=0
Returns the sample rate that the device is currently using.
virtual AudioWorkgroup getWorkgroup() const
Returns the workgroup for this device.
virtual BigInteger getActiveInputChannels() const =0
Returns a mask showing which of the available input channels are currently enabled.
virtual BigInteger getActiveOutputChannels() const =0
Returns a mask showing which of the available output channels are currently enabled.
virtual int getCurrentBufferSizeSamples()=0
Returns the buffer size that the device is currently using.
A subclass of AudioPlayHead can supply information about the position and status of a moving play hea...
void handleIncomingMidiMessage(MidiInput *, const MidiMessage &) override
Receives an incoming message.
void audioDeviceAboutToStart(AudioIODevice *) override
Called to indicate that the device is about to start calling back.
void setProcessor(AudioProcessor *processorToPlay)
Sets the processor that should be played.
void audioDeviceStopped() override
Called to indicate that the device has stopped.
void setMidiOutput(MidiOutput *midiOutputToUse)
Sets the MIDI output that should be used, if required.
void setDoublePrecisionProcessing(bool doublePrecision)
Switch between double and single floating point precisions processing.
void audioDeviceIOCallbackWithContext(const float *const *, int, float *const *, int, int, const AudioIODeviceCallbackContext &) override
Processes a block of incoming and outgoing audio data.
Base class for audio processing classes or plugins.
void setProcessingPrecision(ProcessingPrecision newPrecision) noexcept
Changes the processing precision of the receiver.
bool isUsingDoublePrecision() const noexcept
Returns true if the current precision is set to doublePrecision.
virtual bool supportsDoublePrecisionProcessing() const
Returns true if the Audio processor supports double precision floating point processing.
virtual void prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock)=0
Called before playback starts, to let the processor prepare itself.
virtual void processBlock(AudioBuffer< float > &buffer, MidiBuffer &midiMessages)=0
Renders the next block.
const CriticalSection & getCallbackLock() const noexcept
This returns a critical section that will automatically be locked while the host is calling the proce...
bool isSuspended() const noexcept
Returns true if processing is currently suspended.
virtual void audioWorkgroupContextChanged(const AudioWorkgroup &workgroup)
This is called by the host when the thread workgroup context has changed.
AudioPlayHead * getPlayHead() const noexcept
Returns the current AudioPlayHead object that should be used to find out the state and position of th...
virtual bool isMidiEffect() const
Returns true if this is a MIDI effect plug-in and does no audio processing.
virtual void releaseResources()=0
Called after playback has stopped, to let the object free up any resources it no longer needs.
void reset()
Disengages this instance so that it no longer represents a workgroup.
int countNumberOfSetBits() const noexcept
Returns the total number of set bits in the value.
Automatically locks and unlocks a mutex object.
void clear() noexcept
Removes all events from the buffer.
Represents a midi input device.
void removeNextBlockOfMessages(MidiBuffer &destBuffer, int numSamples)
Removes all the pending messages from the queue as a buffer.
void reset(double sampleRate)
Clears any messages from the queue.
void addMessageToQueue(const MidiMessage &message)
Takes an incoming real-time message and adds it to the queue.
Encapsulates a MIDI message.
Represents a midi output device.
void sendBlockOfMessagesNow(const MidiBuffer &buffer)
Sends out a sequence of MIDI messages immediately.
bool isBackgroundThreadRunning() const noexcept
Returns true if the background thread used to send blocks of data is running.
void sendBlockOfMessages(const MidiBuffer &buffer, double millisecondCounterToStartAt, double samplesPerSecondForBuffer)
This lets you supply a block of messages that will be sent out at some point in the future.
A simple optional type.
static double getMillisecondCounterHiRes() noexcept
Returns the number of millisecs since a fixed event (usually system startup).
This is a base class for classes that perform a unit test.
Represents a shared variant value.
Definition juce_Value.h:51
T data(T... args)
T end(T... args)
T exchange(T... args)
T find_if(T... args)
#define jassert(expression)
Platform-independent assertion macro.
memcpy
JUCE Namespace.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
const uint64_t * hostTimeNs
If the host provides this information, this field will be set to point to an integer holding the curr...
void zeromem(void *memory, size_t numBytes) noexcept
Fills a block of memory with zeros.
Definition juce_Memory.h:28
Additional information that may be passed to the AudioIODeviceCallback.
T resize(T... args)
T size(T... args)
typedef uint64_t
typedef size_t