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// Find a block size acceptable to all stages; side-effects on instances
48size_t FindBufferSize(const Mixer::Inputs &inputs, size_t bufferSize)
49{
50 size_t blockSize = bufferSize;
51 for (const auto &input : inputs) {
52 const auto sequence = input.pSequence.get();
53 const auto nInChannels = sequence->NChannels();
54 if (!sequence) {
55 assert(false);
56 break;
57 }
58 for (const auto &stage : input.stages) {
59 // Need an instance to query acceptable block size
60 const auto pInstance = stage.factory();
61 if (pInstance)
62 blockSize = std::min(blockSize, pInstance->SetBlockSize(blockSize));
63 // Cache the first factory call
64 stage.mpFirstInstance = move(pInstance);
65 }
66 }
67 return blockSize;
68}
69}
70
72 const bool mayThrow,
73 const WarpOptions &warpOptions,
74 const double startTime, const double stopTime,
75 const unsigned numOutChannels,
76 const size_t outBufferSize, const bool outInterleaved,
77 double outRate, sampleFormat outFormat,
78 const bool highQuality, MixerSpec *const mixerSpec,
79 ApplyGain applyGain
80) : mNumChannels{ numOutChannels }
81 , mInputs{ move(inputs) }
82 , mBufferSize{ FindBufferSize(mInputs, outBufferSize) }
83 , mApplyGain{ applyGain }
84 , mHighQuality{ highQuality }
85 , mFormat{ outFormat }
86 , mInterleaved{ outInterleaved }
87
88 , mTimesAndSpeed{ std::make_shared<TimesAndSpeed>( TimesAndSpeed{
89 startTime, stopTime, warpOptions.initialSpeed, startTime
90 } ) }
91
92 // PRL: Bug2536: see other comments below for the last, padding argument
93 // TODO: more-than-two-channels
94 // Issue 3565 workaround: allocate one extra buffer when applying a
95 // GVerb effect stage. It is simply discarded
96 // See also issue 3854, when the number of out channels expected by the
97 // plug-in is yet larger
98 , mFloatBuffers{ 3, mBufferSize, 1, 1 }
99
100 // non-interleaved
101 , mTemp{ initVector<float>(mNumChannels, mBufferSize) }
102 , mBuffer{ initVector<SampleBuffer>(mInterleaved ? 1 : mNumChannels,
103 [format = mFormat,
104 size = mBufferSize * (mInterleaved ? mNumChannels : 1)
105 ](auto &buffer){ buffer.Allocate(size, format); }
106 )}
107 , mEffectiveFormat{ floatSample }
108{
109 assert(BufferSize() <= outBufferSize);
110 const auto nChannelsIn =
111 std::accumulate(mInputs.begin(), mInputs.end(), size_t{},
112 [](auto sum, const auto &input){
113 return sum + input.pSequence->NChannels(); });
114
115 // Examine the temporary instances that were made in FindBufferSize
116 // This finds a sufficient, but not necessary, condition to do dithering
117 bool needsDither = std::any_of(mInputs.begin(), mInputs.end(),
118 [](const Input &input){
119 return std::any_of(input.stages.begin(), input.stages.end(),
120 [](const MixerOptions::StageSpecification &spec){
121 return spec.mpFirstInstance &&
122 spec.mpFirstInstance->NeedsDither(); } ); } );
123
124 auto pMixerSpec = ( mixerSpec &&
125 mixerSpec->GetNumChannels() == mNumChannels &&
126 mixerSpec->GetNumTracks() == nChannelsIn
127 ) ? mixerSpec : nullptr;
128 mHasMixerSpec = pMixerSpec != nullptr;
129
130 // Reserve vectors first so we can take safe references to pushed elements
131 mSources.reserve(nChannelsIn);
132 const auto nStages = std::accumulate(mInputs.begin(), mInputs.end(), 0,
133 [](auto sum, const auto &input){
134 return sum + input.stages.size() * input.pSequence->NChannels(); });
135 mSettings.reserve(nStages);
136 mStageBuffers.reserve(nStages);
137
138 size_t i = 0;
139 for (auto &input : mInputs) {
140 const auto &sequence = input.pSequence;
141 if (!sequence) {
142 assert(false);
143 break;
144 }
145 auto increment = finally([&]{ i += sequence->NChannels(); });
146
147 auto &source = mSources.emplace_back(sequence, BufferSize(), outRate,
148 warpOptions, highQuality, mayThrow, mTimesAndSpeed,
149 (pMixerSpec ? &pMixerSpec->mMap[i] : nullptr));
150 AudioGraph::Source *pDownstream = &source;
151 for (const auto &stage : input.stages) {
152 // Make a mutable copy of stage.settings
153 auto &settings = mSettings.emplace_back(stage.settings);
154 // TODO: more-than-two-channels
155 // Like mFloatBuffers but padding not needed for soxr
156 // Allocate one extra buffer to hold dummy zero inputs
157 // (Issue 3854)
158 auto &stageInput = mStageBuffers.emplace_back(3, mBufferSize, 1);
159 const auto &factory = [&stage]{
160 // Avoid unnecessary repeated calls to the factory
161 return stage.mpFirstInstance
162 ? move(stage.mpFirstInstance)
163 : stage.factory();
164 };
165 auto &pNewDownstream =
166 mStages.emplace_back(EffectStage::Create(-1,
167 *pDownstream, stageInput,
168 factory, settings, outRate, std::nullopt, *sequence
169 ));
170 if (pNewDownstream)
171 pDownstream = pNewDownstream.get();
172 else {
173 // Just omit the failed stage from rendering
174 // TODO propagate the error?
175 mStageBuffers.pop_back();
176 mSettings.pop_back();
177 }
178 }
179 mDecoratedSources.emplace_back(Source{ source, *pDownstream });
180 }
181
182 // Decide once at construction time
183 std::tie(mNeedsDither, mEffectiveFormat) = NeedsDither(needsDither, outRate);
184}
185
186Mixer::~Mixer() = default;
187
188std::pair<bool, sampleFormat>
189Mixer::NeedsDither(bool needsDither, double rate) const
190{
191 // This will accumulate the widest effective format of any input
192 // clip
193 auto widestEffectiveFormat = narrowestSampleFormat;
194
195 // needsDither may already be given as true.
196 // There are many other possible disqualifiers for the avoidance of dither.
197 if (std::any_of(mSources.begin(), mSources.end(),
198 std::mem_fn(&MixerSource::VariableRates))
199 )
200 // We will call MixVariableRates(), so we need nontrivial resampling
201 needsDither = true;
202
203 for (const auto &input : mSources) {
204 auto &sequence = input.GetSequence();
205
206 if (sequence.GetRate() != rate)
207 // Also leads to MixVariableRates(), needs nontrivial resampling
208 needsDither = true;
209 else if (mApplyGain == ApplyGain::Mixdown &&
210 !mHasMixerSpec &&
211 sequence.NChannels() > 1 && mNumChannels == 1)
212 {
213 needsDither = true;
214 }
215 else if (mApplyGain != ApplyGain::Discard) {
217 for (auto c : {0, 1}) {
218 const auto gain = sequence.GetChannelGain(c);
219 if (!(gain == 0.0 || gain == 1.0))
220 // Fractional gain may be applied even in MixSameRate
221 needsDither = true;
222 }
223 }
224 // Examine all tracks. (This ignores the time bounds for the mixer.
225 // If it did not, we might avoid dither in more cases. But if we fix
226 // that, remember that some mixers change their time bounds after
227 // construction, as when scrubbing.)
228 if (!sequence.HasTrivialEnvelope())
229 // Varying or non-unit gain may be applied even in MixSameRate
230 needsDither = true;
231 auto effectiveFormat = sequence.WidestEffectiveFormat();
232 if (effectiveFormat > mFormat)
233 // Real, not just nominal, precision loss would happen in at
234 // least one clip
235 needsDither = true;
236 widestEffectiveFormat =
237 std::max(widestEffectiveFormat, effectiveFormat);
238 }
239
240 if (needsDither)
241 // Results will be dithered to width mFormat
242 return { true, mFormat };
243 else {
244 // Results will not be dithered
245 assert(widestEffectiveFormat <= mFormat);
246 return { false, widestEffectiveFormat };
247 }
248}
249
251{
252 for (auto &buffer: mTemp)
253 std::fill(buffer.begin(), buffer.end(), 0);
254}
255
256static void MixBuffers(unsigned numChannels,
257 const unsigned char *channelFlags, const float *gains,
258 const float &src, std::vector<std::vector<float>> &dests, int len)
259{
260 const auto pSrc = &src;
261 for (unsigned int c = 0; c < numChannels; c++) {
262 if (!channelFlags[c])
263 continue;
264 for (int j = 0; j < len; ++j)
265 dests[c][j] += pSrc[j] * gains[c]; // the actual mixing process
266 }
267}
268
269#define stackAllocate(T, count) static_cast<T*>(alloca(count * sizeof(T)))
270
271size_t Mixer::Process(const size_t maxToProcess)
272{
273 assert(maxToProcess <= BufferSize());
274
275 // MB: this is wrong! mT represented warped time, and mTime is too inaccurate to use
276 // it here. It's also unnecessary I think.
277 //if (mT >= mT1)
278 // return 0;
279
280 size_t maxOut = 0;
281 const auto channelFlags = stackAllocate(unsigned char, mNumChannels);
282 const auto gains = stackAllocate(float, mNumChannels);
284 std::fill(gains, gains + mNumChannels, 1.0f);
285
286 // Decides which output buffers an input channel accumulates into
287 auto findChannelFlags = [&channelFlags, numChannels = mNumChannels]
288 (const bool *map, const WideSampleSequence &sequence, size_t iChannel){
289 const auto end = channelFlags + numChannels;
290 std::fill(channelFlags, end, 0);
291 if (map)
292 // ignore left and right when downmixing is customized
293 std::copy(map, map + numChannels, channelFlags);
294 else if (IsMono(sequence))
295 std::fill(channelFlags, end, 1);
296 else if (iChannel == 0)
297 channelFlags[0] = 1;
298 else if (iChannel == 1) {
299 if (numChannels >= 2)
300 channelFlags[1] = 1;
301 else
302 channelFlags[0] = 1;
303 }
304 return channelFlags;
305 };
306
307 auto &[mT0, mT1, _, mTime] = *mTimesAndSpeed;
308 auto oldTime = mTime;
309 // backwards (as possibly in scrubbing)
310 const auto backwards = (mT0 > mT1);
311
312 Clear();
313 // TODO: more-than-two-channels
314 auto maxChannels = std::max(2u, mFloatBuffers.Channels());
315
316 for (auto &[ upstream, downstream ] : mDecoratedSources) {
317 auto oResult = downstream.Acquire(mFloatBuffers, maxToProcess);
318 // One of MixVariableRates or MixSameRate assigns into mTemp[*][*] which
319 // are the sources for the CopySamples calls, and they copy into
320 // mBuffer[*][*]
321 if (!oResult)
322 return 0;
323 auto result = *oResult;
324 maxOut = std::max(maxOut, result);
325
326 // Insert effect stages here! Passing them all channels of the track
327
328 const auto limit = std::min<size_t>(upstream.Channels(), maxChannels);
329 for (size_t j = 0; j < limit; ++j) {
330 const auto pFloat = (const float *)mFloatBuffers.GetReadPosition(j);
331 auto &sequence = upstream.GetSequence();
333 for (size_t c = 0; c < mNumChannels; ++c) {
334 if (mNumChannels > 1)
335 gains[c] = sequence.GetChannelGain(c);
336 else
337 gains[c] = sequence.GetChannelGain(j);
338 }
340 gains[0] /= static_cast<float>(limit);
341 }
342
343 const auto flags =
344 findChannelFlags(upstream.MixerSpec(j), sequence, j);
345 MixBuffers(mNumChannels, flags, gains, *pFloat, mTemp, result);
346 }
347
348 downstream.Release();
349 mFloatBuffers.Advance(result);
351 }
352
353 if (backwards)
354 mTime = std::clamp(mTime, mT1, oldTime);
355 else
356 mTime = std::clamp(mTime, oldTime, mT1);
357
358 const auto dstStride = (mInterleaved ? mNumChannels : 1);
359 auto ditherType = mNeedsDither
362 for (size_t c = 0; c < mNumChannels; ++c)
365 ? mBuffer[0].ptr() + (c * SAMPLE_SIZE(mFormat))
366 : mBuffer[c].ptr()
367 ),
368 mFormat, maxOut, ditherType,
369 1, dstStride);
370
371 // MB: this doesn't take warping into account, replaced with code based on mSamplePos
372 //mT += (maxOut / mRate);
373
374 assert(maxOut <= maxToProcess);
375 return maxOut;
376}
377
379{
380 return mBuffer[0].ptr();
381}
382
384{
385 return mBuffer[channel].ptr();
386}
387
389{
390 return mEffectiveFormat;
391}
392
394{
395 return mTimesAndSpeed->mTime;
396}
397
398void Mixer::Reposition(double t, bool bSkipping)
399{
400 const auto &[mT0, mT1, _, __] = *mTimesAndSpeed;
401 auto &mTime = mTimesAndSpeed->mTime;
402 mTime = t;
403 const bool backwards = (mT1 < mT0);
404 if (backwards)
405 mTime = std::clamp(mTime, mT1, mT0);
406 else
407 mTime = std::clamp(mTime, mT0, mT1);
408
409 for (auto &source : mSources)
410 source.Reposition(mTime, bSkipping);
411}
412
413void Mixer::SetTimesAndSpeed(double t0, double t1, double speed, bool bSkipping)
414{
415 wxASSERT(std::isfinite(speed));
416 auto &[mT0, mT1, mSpeed, _] = *mTimesAndSpeed;
417 mT0 = t0;
418 mT1 = t1;
419 mSpeed = fabs(speed);
420 Reposition(t0, bSkipping);
421}
422
423void Mixer::SetSpeedForKeyboardScrubbing(double speed, double startTime)
424{
425 wxASSERT(std::isfinite(speed));
426 auto &[mT0, mT1, mSpeed, _] = *mTimesAndSpeed;
427
428 // Check if the direction has changed
429 if ((speed > 0.0 && mT1 < mT0) || (speed < 0.0 && mT1 > mT0)) {
430 // It's safe to use 0 and std::numeric_limits<double>::max(),
431 // because Mixer::MixVariableRates() doesn't sample past the start
432 // or end of the audio in a track.
433 if (speed > 0.0 && mT1 < mT0) {
434 mT0 = 0;
435 mT1 = std::numeric_limits<double>::max();
436 }
437 else {
438 mT0 = std::numeric_limits<double>::max();
439 mT1 = 0;
440 }
441
442 Reposition(startTime, true);
443 }
444
445 mSpeed = fabs(speed);
446}
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, std::vector< std::vector< float > > &dests, int len)
Definition: Mix.cpp:256
#define stackAllocate(T, count)
Definition: Mix.cpp:269
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:69
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
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, Source &upstream, Buffers &inBuffers, const Factory &factory, EffectSettings &settings, double sampleRate, std::optional< sampleCount > genLength, const WideSampleSequence &sequence)
Satisfies postcondition of constructor or returns null.
Definition: EffectStage.cpp:79
AudioGraph::Buffers mFloatBuffers
Definition: Mix.h:155
ApplyGain
Definition: Mix.h:48
const ApplyGain mApplyGain
Definition: Mix.h:140
const std::vector< SampleBuffer > mBuffer
Definition: Mix.h:163
std::vector< Input > Inputs
Definition: Mix.h:45
void SetSpeedForKeyboardScrubbing(double speed, double startTime)
Definition: Mix.cpp:423
std::vector< Source > mDecoratedSources
Definition: Mix.h:171
sampleFormat EffectiveFormat() const
Deduce the effective width of the output, which may be narrower than the stored format.
Definition: Mix.cpp:388
const sampleFormat mFormat
Definition: Mix.h:142
virtual ~Mixer()
std::vector< std::vector< float > > mTemp
Definition: Mix.h:160
std::pair< bool, sampleFormat > NeedsDither(bool needsDither, double rate) const
Definition: Mix.cpp:189
const unsigned mNumChannels
Definition: Mix.h:128
sampleFormat mEffectiveFormat
Definition: Mix.h:146
constSamplePtr GetBuffer()
Retrieve the main buffer or the interleaved buffer.
Definition: Mix.cpp:378
const bool mInterleaved
Definition: Mix.h:143
void Clear()
Definition: Mix.cpp:250
bool mNeedsDither
Definition: Mix.h:147
Mixer(Inputs inputs, 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:71
double MixGetCurrentTime()
Current time in seconds (unwarped, i.e. always between startTime and stopTime)
Definition: Mix.cpp:393
const bool mHighQuality
Definition: Mix.h:141
std::vector< MixerSource > mSources
Definition: Mix.h:165
size_t Process()
Definition: Mix.h:98
size_t BufferSize() const
Definition: Mix.h:79
void Reposition(double t, bool bSkipping=false)
Reposition processing to absolute time next time Process() is called.
Definition: Mix.cpp:398
void SetTimesAndSpeed(double t0, double t1, double speed, bool bSkipping=false)
Used in scrubbing and other nonuniform playback policies.
Definition: Mix.cpp:413
const std::shared_ptr< TimesAndSpeed > mTimesAndSpeed
Definition: Mix.h:150
bool mHasMixerSpec
Definition: Mix.h:148
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
bool VariableRates() const
Definition: MixerSource.h:65
bool IsMono(const Channel &channel)
Whether the channel is mono.
size_t FindBufferSize(const Mixer::Inputs &inputs, size_t bufferSize)
Definition: Mix.cpp:48
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.
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:56