Audacity 3.2.0
Mix.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Mix.cpp
6
7 Dominic Mazzoni
8 Markus Meyer
9 Vaughan Johnson
10
11*******************************************************************//*******************************************************************/
17#include "Mix.h"
18#include "MixerSource.h"
19
20#include <cmath>
21#include "EffectStage.h"
22#include "Dither.h"
23#include "Resample.h"
24#include "WideSampleSequence.h"
25#include "float_cast.h"
26#include <numeric>
27
28namespace {
29template<typename T, typename F> std::vector<T>
30initVector(size_t dim1, const F &f)
31{
32 std::vector<T> result( dim1 );
33 for (auto &row : result)
34 f(row);
35 return result;
36}
37
38template<typename T> std::vector<std::vector<T>>
39initVector(size_t dim1, size_t dim2)
40{
41 return initVector<std::vector<T>>(dim1,
42 [dim2](auto &row){ row.resize(dim2); });
43}
44}
45
46namespace
47{
48void ConsiderStages(const Mixer::Stages& stages, size_t& blockSize)
49{
50 for (const auto& stage : stages)
51 {
52 // Need an instance to query acceptable block size
53 const auto pInstance = stage.factory();
54 if (pInstance)
55 blockSize = std::min(blockSize, pInstance->SetBlockSize(blockSize));
56 // Cache the first factory call
57 stage.mpFirstInstance = move(pInstance);
58 }
59}
60
61// Find a block size acceptable to all stages; side-effects on instances
63 const Mixer::Inputs& inputs,
64 const std::optional<Mixer::Stages>& masterEffects, size_t bufferSize)
65{
66 size_t blockSize = bufferSize;
67 for (const auto &input : inputs) {
68 const auto sequence = input.pSequence.get();
69 const auto nInChannels = sequence->NChannels();
70 if (!sequence) {
71 assert(false);
72 break;
73 }
74 ConsiderStages(input.stages, blockSize);
75 }
76 if (masterEffects)
77 ConsiderStages(*masterEffects, blockSize);
78
79 return blockSize;
80}
81} // namespace
82
84 Inputs inputs, std::optional<Stages> masterEffects, const bool mayThrow,
85 const WarpOptions& warpOptions, const double startTime,
86 const double stopTime, const unsigned numOutChannels,
87 const size_t outBufferSize, const bool outInterleaved, double outRate,
88 sampleFormat outFormat, const bool highQuality, MixerSpec* const mixerSpec,
89 ApplyGain applyGain)
90 : mNumChannels { numOutChannels }
91 , mInputs { move(inputs) }
92 , mMasterEffects { move(masterEffects) }
93 , mBufferSize { FindBufferSize(mInputs, mMasterEffects, outBufferSize) }
94 , mApplyGain { applyGain }
95 , mHighQuality { highQuality }
96 , mFormat { outFormat }
97 , mInterleaved { outInterleaved }
98 , mTimesAndSpeed { std::make_shared<TimesAndSpeed>(TimesAndSpeed {
99 startTime, stopTime, warpOptions.initialSpeed, startTime }) }
100
101 // PRL: Bug2536: see other comments below for the last, padding argument
102 // TODO: more-than-two-channels
103 // Issue 3565 workaround: allocate one extra buffer when applying a
104 // GVerb effect stage. It is simply discarded
105 // See also issue 3854, when the number of out channels expected by the
106 // plug-in is yet larger
107 , mFloatBuffers { 3, mBufferSize, 1, 1 }
108
109 // non-interleaved
110 , mTemp { mNumChannels, mBufferSize, 1, 1 }
111 , mBuffer { initVector<SampleBuffer>(
112 mInterleaved ? 1 : mNumChannels,
113 [format = mFormat,
114 size = mBufferSize * (mInterleaved ? mNumChannels : 1)](
115 auto& buffer) { buffer.Allocate(size, format); }) }
116 , mEffectiveFormat { floatSample }
117{
118 assert(BufferSize() <= outBufferSize);
119 const auto nChannelsIn =
120 std::accumulate(mInputs.begin(), mInputs.end(), size_t{},
121 [](auto sum, const auto &input){
122 return sum + input.pSequence->NChannels(); });
123
124 // Examine the temporary instances that were made in FindBufferSize
125 // This finds a sufficient, but not necessary, condition to do dithering
126 bool needsDither = std::any_of(mInputs.begin(), mInputs.end(),
127 [](const Input &input){
128 return std::any_of(input.stages.begin(), input.stages.end(),
129 [](const MixerOptions::StageSpecification &spec){
130 return spec.mpFirstInstance &&
131 spec.mpFirstInstance->NeedsDither(); } ); } );
132 if (mMasterEffects)
133 needsDither |= std::any_of(
134 mMasterEffects->begin(), mMasterEffects->end(),
135 [](const MixerOptions::StageSpecification& spec) {
136 return spec.mpFirstInstance && spec.mpFirstInstance->NeedsDither();
137 });
138
139 auto pMixerSpec = ( mixerSpec &&
140 mixerSpec->GetNumChannels() == mNumChannels &&
141 mixerSpec->GetNumTracks() == nChannelsIn
142 ) ? mixerSpec : nullptr;
143 mHasMixerSpec = pMixerSpec != nullptr;
144
145 // Reserve vectors first so we can take safe references to pushed elements
146 mSources.reserve(nChannelsIn);
147 // One stereo-capable stage per effect.
148 const auto nMasterStages = mMasterEffects ? mMasterEffects->size() : 0;
149 const auto nStages =
150 std::accumulate(
151 mInputs.begin(), mInputs.end(), 0,
152 [](auto sum, const auto& input) {
153 return sum + input.stages.size() * input.pSequence->NChannels();
154 }) +
155 nMasterStages;
156 mSettings.reserve(nStages);
157 mStageBuffers.reserve(nStages);
158
159 size_t i = 0;
160 for (auto &input : mInputs) {
161 const auto &sequence = input.pSequence;
162 if (!sequence) {
163 assert(false);
164 break;
165 }
166 auto increment = finally([&]{ i += sequence->NChannels(); });
167
168 auto &source = mSources.emplace_back(sequence, BufferSize(), outRate,
169 warpOptions, highQuality, mayThrow, mTimesAndSpeed,
170 (pMixerSpec ? &pMixerSpec->mMap[i] : nullptr));
171 AudioGraph::Source *pDownstream = &source;
172 for (const auto &stage : input.stages)
173 if (
174 auto& pNewDownstream =
175 RegisterEffectStage(*pDownstream, stage, outRate))
176 {
177 pDownstream = pNewDownstream.get();
178 }
179 mDecoratedSources.emplace_back(Source{ source, *pDownstream });
180 }
181
182 if (mMasterEffects)
183 {
184 AudioGraph::Source* pDownstream = this;
185 for (const auto& stage : *mMasterEffects)
186 if (
187 auto& pNewDownstream =
188 RegisterEffectStage(*pDownstream, stage, outRate))
189 {
190 mMasterStages.emplace_back(pDownstream = pNewDownstream.get());
191 }
192 }
193
194 // Decide once at construction time
195 std::tie(mNeedsDither, mEffectiveFormat) = NeedsDither(needsDither, outRate);
196}
197
198Mixer::~Mixer() = default;
199
200std::pair<bool, sampleFormat>
201Mixer::NeedsDither(bool needsDither, double rate) const
202{
203 // This will accumulate the widest effective format of any input
204 // clip
205 auto widestEffectiveFormat = narrowestSampleFormat;
206
207 // needsDither may already be given as true.
208 // There are many other possible disqualifiers for the avoidance of dither.
209 if (std::any_of(mSources.begin(), mSources.end(),
210 std::mem_fn(&MixerSource::VariableRates))
211 )
212 // We will call MixVariableRates(), so we need nontrivial resampling
213 needsDither = true;
214
215 for (const auto &input : mSources) {
216 auto &sequence = input.GetSequence();
217
218 if (sequence.GetRate() != rate)
219 // Also leads to MixVariableRates(), needs nontrivial resampling
220 needsDither = true;
221 else if (mApplyGain == ApplyGain::Mixdown &&
222 !mHasMixerSpec &&
223 sequence.NChannels() > 1 && mNumChannels == 1)
224 {
225 needsDither = true;
226 }
227 else if (mApplyGain != ApplyGain::Discard) {
229 for (auto c : {0, 1}) {
230 const auto gain = sequence.GetChannelGain(c);
231 if (!(gain == 0.0 || gain == 1.0))
232 // Fractional gain may be applied even in MixSameRate
233 needsDither = true;
234 }
235 }
236 // Examine all tracks. (This ignores the time bounds for the mixer.
237 // If it did not, we might avoid dither in more cases. But if we fix
238 // that, remember that some mixers change their time bounds after
239 // construction, as when scrubbing.)
240 if (!sequence.HasTrivialEnvelope())
241 // Varying or non-unit gain may be applied even in MixSameRate
242 needsDither = true;
243 auto effectiveFormat = sequence.WidestEffectiveFormat();
244 if (effectiveFormat > mFormat)
245 // Real, not just nominal, precision loss would happen in at
246 // least one clip
247 needsDither = true;
248 widestEffectiveFormat =
249 std::max(widestEffectiveFormat, effectiveFormat);
250 }
251
252 if (needsDither)
253 // Results will be dithered to width mFormat
254 return { true, mFormat };
255 else {
256 // Results will not be dithered
257 assert(widestEffectiveFormat <= mFormat);
258 return { false, widestEffectiveFormat };
259 }
260}
261
263{
264 for (auto c = 0; c < mTemp.Channels(); ++c)
266}
267
268static void MixBuffers(unsigned numChannels,
269 const unsigned char *channelFlags, const float *gains,
270 const float &src, AudioGraph::Buffers &dests, int len)
271{
272 const auto pSrc = &src;
273 for (unsigned int c = 0; c < numChannels; c++) {
274 if (!channelFlags[c])
275 continue;
276 auto dest = &dests.GetWritePosition(c);
277 for (int j = 0; j < len; ++j)
278 dest[j] += pSrc[j] * gains[c]; // the actual mixing process
279 }
280}
281
282#define stackAllocate(T, count) static_cast<T*>(alloca(count * sizeof(T)))
283
284size_t Mixer::Process(const size_t maxToProcess)
285{
286 assert(maxToProcess <= BufferSize());
287
288 // MB: this is wrong! mT represented warped time, and mTime is too inaccurate to use
289 // it here. It's also unnecessary I think.
290 //if (mT >= mT1)
291 // return 0;
292
293 auto &[mT0, mT1, _, mTime] = *mTimesAndSpeed;
294 auto oldTime = mTime;
295 // backwards (as possibly in scrubbing)
296 const auto backwards = (mT0 > mT1);
297
298 Clear();
299
300 std::optional<size_t> maxOut;
301 if (mMasterStages.empty())
302 maxOut = Acquire(mTemp, maxToProcess);
303 else
304 {
305 const auto stage = mMasterStages.back();
306 maxOut = stage->Acquire(mTemp, maxToProcess);
307 stage->Release();
308 }
309 if (!maxOut)
310 return 0;
311
312 if (backwards)
313 mTime = std::clamp(mTime, mT1, oldTime);
314 else
315 mTime = std::clamp(mTime, oldTime, mT1);
316
317 const auto dstStride = (mInterleaved ? mNumChannels : 1);
318 auto ditherType = mNeedsDither
321 for (size_t c = 0; c < mNumChannels; ++c)
324 ? mBuffer[0].ptr() + (c * SAMPLE_SIZE(mFormat))
325 : mBuffer[c].ptr()
326 ),
327 mFormat, *maxOut, ditherType,
328 1, dstStride);
329
330 // MB: this doesn't take warping into account, replaced with code based on mSamplePos
331 //mT += (maxOut / mRate);
332
333 assert(*maxOut <= maxToProcess);
334 return *maxOut;
335}
336
338{
339 return mBuffer[0].ptr();
340}
341
343{
344 return mBuffer[channel].ptr();
345}
346
348{
349 return mEffectiveFormat;
350}
351
353{
354 return mTimesAndSpeed->mTime;
355}
356
357void Mixer::Reposition(double t, bool bSkipping)
358{
359 const auto &[mT0, mT1, _, __] = *mTimesAndSpeed;
360 auto &mTime = mTimesAndSpeed->mTime;
361 mTime = t;
362 const bool backwards = (mT1 < mT0);
363 if (backwards)
364 mTime = std::clamp(mTime, mT1, mT0);
365 else
366 mTime = std::clamp(mTime, mT0, mT1);
367
368 for (auto &source : mSources)
369 source.Reposition(mTime, bSkipping);
370}
371
372void Mixer::SetTimesAndSpeed(double t0, double t1, double speed, bool bSkipping)
373{
374 wxASSERT(std::isfinite(speed));
375 auto &[mT0, mT1, mSpeed, _] = *mTimesAndSpeed;
376 mT0 = t0;
377 mT1 = t1;
378 mSpeed = fabs(speed);
379 Reposition(t0, bSkipping);
380}
381
382void Mixer::SetSpeedForKeyboardScrubbing(double speed, double startTime)
383{
384 wxASSERT(std::isfinite(speed));
385 auto &[mT0, mT1, mSpeed, _] = *mTimesAndSpeed;
386
387 // Check if the direction has changed
388 if ((speed > 0.0 && mT1 < mT0) || (speed < 0.0 && mT1 > mT0)) {
389 // It's safe to use 0 and std::numeric_limits<double>::max(),
390 // because Mixer::MixVariableRates() doesn't sample past the start
391 // or end of the audio in a track.
392 if (speed > 0.0 && mT1 < mT0) {
393 mT0 = 0;
394 mT1 = std::numeric_limits<double>::max();
395 }
396 else {
397 mT0 = std::numeric_limits<double>::max();
398 mT1 = 0;
399 }
400
401 Reposition(startTime, true);
402 }
403
404 mSpeed = fabs(speed);
405}
406
407bool Mixer::AcceptsBuffers(const Buffers& buffers) const
408{
409 return buffers.Channels() == mNumChannels &&
410 AcceptsBlockSize(buffers.BlockSize());
411}
412
413bool Mixer::AcceptsBlockSize(size_t blockSize) const
414{
415 return blockSize <= BufferSize();
416}
417
419{
420 return std::accumulate(
421 mDecoratedSources.begin(), mDecoratedSources.end(), sampleCount { 0 },
422 [](sampleCount sum, const Source& source) {
423 return std::max(sum, source.downstream.Remaining());
424 });
425}
426
428{
429 return true;
430}
431
432std::optional<size_t> Mixer::Acquire(Buffers& data, size_t maxToProcess)
433{
434 // TODO: more-than-two-channels
435 auto maxChannels = std::max(2u, mFloatBuffers.Channels());
436 const auto channelFlags = stackAllocate(unsigned char, mNumChannels);
437 const auto gains = stackAllocate(float, mNumChannels);
439 std::fill(gains, gains + mNumChannels, 1.0f);
440
441 // Decides which output buffers an input channel accumulates into
442 auto findChannelFlags = [&channelFlags, numChannels = mNumChannels]
443 (const bool *map, const WideSampleSequence &sequence, size_t iChannel){
444 const auto end = channelFlags + numChannels;
445 std::fill(channelFlags, end, 0);
446 if (map)
447 // ignore left and right when downmixing is customized
448 std::copy(map, map + numChannels, channelFlags);
449 else if (IsMono(sequence))
450 std::fill(channelFlags, end, 1);
451 else if (iChannel == 0)
452 channelFlags[0] = 1;
453 else if (iChannel == 1) {
454 if (numChannels >= 2)
455 channelFlags[1] = 1;
456 else
457 channelFlags[0] = 1;
458 }
459 return channelFlags;
460 };
461
462 size_t maxOut = 0;
463 for (auto c = 0;c < data.Channels(); ++c)
464 data.ClearBuffer(c, maxToProcess);
465
466 for (auto& [upstream, downstream] : mDecoratedSources)
467 {
468 auto oResult = downstream.Acquire(mFloatBuffers, maxToProcess);
469 // One of MixVariableRates or MixSameRate assigns into mTemp[*][*]
470 // which are the sources for the CopySamples calls, and they copy into
471 // mBuffer[*][*]
472 if (!oResult)
473 return 0;
474 const auto result = *oResult;
475 maxOut = std::max(maxOut, result);
476
477 // Insert effect stages here! Passing them all channels of the track
478
479 const auto limit = std::min<size_t>(upstream.Channels(), maxChannels);
480 for (size_t j = 0; j < limit; ++j)
481 {
482 const auto pFloat = (const float*)mFloatBuffers.GetReadPosition(j);
483 auto& sequence = upstream.GetSequence();
485 {
486 for (size_t c = 0; c < mNumChannels; ++c)
487 {
488 if (mNumChannels > 1)
489 gains[c] = sequence.GetChannelGain(c);
490 else
491 gains[c] = sequence.GetChannelGain(j);
492 }
493 if (
495 mNumChannels == 1)
496 gains[0] /= static_cast<float>(limit);
497 }
498
499 const auto flags =
500 findChannelFlags(upstream.MixerSpec(j), sequence, j);
501 MixBuffers(mNumChannels, flags, gains, *pFloat, data, result);
502 }
503
504 downstream.Release();
505 mFloatBuffers.Advance(result);
507 }
508
509 // MB: this doesn't take warping into account, replaced with code based on mSamplePos
510 //mT += (maxOut / mRate);
511
512 assert(maxOut <= maxToProcess);
513 return maxOut;
514}
515
516std::unique_ptr<EffectStage>& Mixer::RegisterEffectStage(
518 double outRate)
519{
520 // Make a mutable copy of stage.settings
521 auto& settings = mSettings.emplace_back(stage.settings);
522 // TODO: more-than-two-channels
523 // Like mFloatBuffers but padding not needed for soxr
524 // Allocate one extra buffer to hold dummy zero inputs
525 // (Issue 3854)
526 auto& stageInput = mStageBuffers.emplace_back(3, mBufferSize, 1);
527 const auto& factory = [&stage] {
528 // Avoid unnecessary repeated calls to the factory
529 return stage.mpFirstInstance ? move(stage.mpFirstInstance) :
530 stage.factory();
531 };
532 auto& pNewDownstream = mStages.emplace_back(EffectStage::Create(
533 -1, mNumChannels, upstream, stageInput, factory, settings, outRate,
534 std::nullopt));
535 if (!pNewDownstream)
536 {
537 // Just omit the failed stage from rendering
538 // TODO propagate the error?
539 mStageBuffers.pop_back();
540 mSettings.pop_back();
541 }
542 return pNewDownstream;
543}
constexpr int BufferSize
static RegisteredToolbarFactory factory
int min(int a, int b)
@ none
Definition: Dither.h:20
#define _(s)
Definition: Internat.h:73
static void MixBuffers(unsigned numChannels, const unsigned char *channelFlags, const float *gains, const float &src, AudioGraph::Buffers &dests, int len)
Definition: Mix.cpp:268
#define stackAllocate(T, count)
Definition: Mix.cpp:282
static const AttachedProjectObjects::RegisteredFactory masterEffects
DitherType gLowQualityDither
These global variables are assigned at application startup or after change of preferences.
DitherType gHighQualityDither
void CopySamples(constSamplePtr src, sampleFormat srcFormat, samplePtr dst, sampleFormat dstFormat, size_t len, DitherType ditherType, unsigned int srcStride, unsigned int dstStride)
Copy samples from any format to any other format; apply dithering only if narrowing the format.
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
@ narrowestSampleFormat
Two synonyms for previous values that might change if more values were added.
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const char * constSamplePtr
Definition: SampleFormat.h:58
static Settings & settings()
Definition: TrackInfo.cpp:51
Accumulates (non-interleaved) data during effect processing.
size_t BlockSize() const
float & GetWritePosition(unsigned iChannel)
Get writable position for one channel.
void Advance(size_t count)
Move the positions.
size_t Rotate()
Shift all data at and after the old position to position 0.
unsigned Channels() const
void ClearBuffer(unsigned iChannel, size_t n)
constSamplePtr GetReadPosition(unsigned iChannel) const
Get accumulated data for one channel.
Upstream producer of sample streams, taking Buffers as external context.
static std::unique_ptr< EffectStage > Create(int channel, int nInputChannels, Source &upstream, Buffers &inBuffers, const Factory &factory, EffectSettings &settings, double sampleRate, std::optional< sampleCount > genLength)
Satisfies postcondition of constructor or returns null.
Definition: EffectStage.cpp:79
AudioGraph::Buffers mFloatBuffers
Definition: Mix.h:168
bool AcceptsBuffers(const Buffers &buffers) const override
Definition: Mix.cpp:407
bool Release() override
Caller is done examining last Acquire()d positions.
Definition: Mix.cpp:427
ApplyGain
Definition: Mix.h:50
const ApplyGain mApplyGain
Definition: Mix.h:153
bool AcceptsBlockSize(size_t blockSize) const override
Definition: Mix.cpp:413
std::vector< EffectSettings > mSettings
Definition: Mix.h:179
const std::vector< SampleBuffer > mBuffer
Definition: Mix.h:176
std::vector< AudioGraph::Source * > mMasterStages
Definition: Mix.h:182
std::vector< Input > Inputs
Definition: Mix.h:47
std::vector< std::unique_ptr< EffectStage > > mStages
Definition: Mix.h:181
void SetSpeedForKeyboardScrubbing(double speed, double startTime)
Definition: Mix.cpp:382
std::vector< Source > mDecoratedSources
Definition: Mix.h:185
sampleFormat EffectiveFormat() const
Deduce the effective width of the output, which may be narrower than the stored format.
Definition: Mix.cpp:347
const sampleFormat mFormat
Definition: Mix.h:155
std::optional< size_t > Acquire(Buffers &data, size_t bound) override
Occupy vacant space in Buffers with some data.
Definition: Mix.cpp:432
virtual ~Mixer()
AudioGraph::Buffers mTemp
Definition: Mix.h:173
std::pair< bool, sampleFormat > NeedsDither(bool needsDither, double rate) const
Definition: Mix.cpp:201
const unsigned mNumChannels
Definition: Mix.h:140
sampleFormat mEffectiveFormat
Definition: Mix.h:159
constSamplePtr GetBuffer()
Retrieve the main buffer or the interleaved buffer.
Definition: Mix.cpp:337
std::vector< MixerOptions::StageSpecification > Stages
Definition: Mix.h:35
const bool mInterleaved
Definition: Mix.h:156
const size_t mBufferSize
Definition: Mix.h:145
void Clear()
Definition: Mix.cpp:262
bool mNeedsDither
Definition: Mix.h:160
std::vector< AudioGraph::Buffers > mStageBuffers
Definition: Mix.h:180
double MixGetCurrentTime()
Current time in seconds (unwarped, i.e. always between startTime and stopTime)
Definition: Mix.cpp:352
const bool mHighQuality
Definition: Mix.h:154
sampleCount Remaining() const override
Result includes any amount Acquired and not yet Released.
Definition: Mix.cpp:418
std::vector< MixerSource > mSources
Definition: Mix.h:178
size_t Process()
Definition: Mix.h:99
Mixer(Inputs inputs, std::optional< Stages > masterEffects, bool mayThrow, const WarpOptions &warpOptions, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, bool highQuality=true, MixerSpec *mixerSpec=nullptr, ApplyGain applyGain=ApplyGain::MapChannels)
Definition: Mix.cpp:83
size_t BufferSize() const
Definition: Mix.h:80
void Reposition(double t, bool bSkipping=false)
Reposition processing to absolute time next time Process() is called.
Definition: Mix.cpp:357
std::unique_ptr< EffectStage > & RegisterEffectStage(AudioGraph::Source &upstream, const MixerOptions::StageSpecification &stage, double outRate)
Definition: Mix.cpp:516
void SetTimesAndSpeed(double t0, double t1, double speed, bool bSkipping=false)
Used in scrubbing and other nonuniform playback policies.
Definition: Mix.cpp:372
const std::shared_ptr< TimesAndSpeed > mTimesAndSpeed
Definition: Mix.h:163
bool mHasMixerSpec
Definition: Mix.h:161
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
bool VariableRates() const
Definition: MixerSource.h:65
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
bool IsMono(const Channel &channel)
Whether the channel is mono.
void ConsiderStages(const Mixer::Stages &stages, size_t &blockSize)
Definition: Mix.cpp:48
size_t FindBufferSize(const Mixer::Inputs &inputs, const std::optional< Mixer::Stages > &masterEffects, size_t bufferSize)
Definition: Mix.cpp:62
std::vector< std::vector< T > > initVector(size_t dim1, size_t dim2)
Definition: Mix.cpp:39
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
STL namespace.
std::shared_ptr< EffectInstance > mpFirstInstance
Definition: MixerOptions.h:110
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:56