2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
Mix.cpp
|
|
|
|
|
|
|
|
Dominic Mazzoni
|
|
|
|
Markus Meyer
|
|
|
|
Vaughan Johnson
|
|
|
|
|
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class Mixer
|
|
|
|
\brief Functions for doing the mixdown of the tracks.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class MixerSpec
|
|
|
|
\brief Class used with Mixer.
|
|
|
|
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "Mix.h"
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include <wx/textctrl.h>
|
|
|
|
#include <wx/timer.h>
|
|
|
|
#include <wx/intl.h>
|
|
|
|
|
2019-06-06 13:21:01 +00:00
|
|
|
#include "Envelope.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "WaveTrack.h"
|
|
|
|
#include "Prefs.h"
|
|
|
|
#include "Resample.h"
|
2015-07-05 05:55:40 +00:00
|
|
|
#include "TimeTrack.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "float_cast.h"
|
|
|
|
|
2019-03-31 20:12:07 +00:00
|
|
|
#include "widgets/ProgressDialog.h"
|
|
|
|
|
2016-02-14 23:50:45 +00:00
|
|
|
//TODO-MB: wouldn't it make more sense to DELETE the time track after 'mix and render'?
|
2020-08-22 23:44:49 +00:00
|
|
|
void MixAndRender(TrackList *tracks, WaveTrackFactory *trackFactory,
|
2010-01-23 19:44:49 +00:00
|
|
|
double rate, sampleFormat format,
|
2016-03-31 03:24:39 +00:00
|
|
|
double startTime, double endTime,
|
|
|
|
WaveTrack::Holder &uLeft, WaveTrack::Holder &uRight)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-03-31 03:24:39 +00:00
|
|
|
uLeft.reset(), uRight.reset();
|
|
|
|
|
2011-08-28 19:54:03 +00:00
|
|
|
// This function was formerly known as "Quick Mix".
|
2020-12-25 00:09:05 +00:00
|
|
|
bool mono = false; /* flag if output can be mono without losing anything*/
|
2011-08-28 19:30:17 +00:00
|
|
|
bool oneinput = false; /* flag set to true if there is only one input track
|
|
|
|
(mono or stereo) */
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-16 20:36:46 +00:00
|
|
|
const auto trackRange = tracks->Selected< const WaveTrack >();
|
|
|
|
auto first = *trackRange.begin();
|
2011-08-28 20:14:47 +00:00
|
|
|
// this only iterates tracks which are relevant to this function, i.e.
|
2014-06-03 20:30:19 +00:00
|
|
|
// selected WaveTracks. The tracklist is (confusingly) the list of all
|
2011-08-28 20:14:47 +00:00
|
|
|
// tracks in the project
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-26 23:10:45 +00:00
|
|
|
int numWaves = 0; /* number of wave tracks in the selection */
|
|
|
|
int numMono = 0; /* number of mono, centre-panned wave tracks in selection*/
|
2017-04-16 20:36:46 +00:00
|
|
|
for(auto wt : trackRange) {
|
|
|
|
numWaves++;
|
|
|
|
float pan = wt->GetPan();
|
|
|
|
if (wt->GetChannel() == Track::MonoChannel && pan == 0)
|
|
|
|
numMono++;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (numMono == numWaves)
|
|
|
|
mono = true;
|
|
|
|
|
2011-08-28 19:54:03 +00:00
|
|
|
/* the next loop will do two things at once:
|
|
|
|
* 1. build an array of all the wave tracks were are trying to process
|
|
|
|
* 2. determine when the set of WaveTracks starts and ends, in case we
|
|
|
|
* need to work out for ourselves when to start and stop rendering.
|
|
|
|
*/
|
|
|
|
|
2011-08-29 23:01:28 +00:00
|
|
|
double mixStartTime = 0.0; /* start time of first track to start */
|
2011-08-28 19:54:03 +00:00
|
|
|
bool gotstart = false; // flag indicates we have found a start time
|
|
|
|
double mixEndTime = 0.0; /* end time of last track to end */
|
|
|
|
double tstart, tend; // start and end times for one track.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-26 23:10:45 +00:00
|
|
|
WaveTrackConstArray waveArray;
|
2017-04-16 20:36:46 +00:00
|
|
|
|
|
|
|
for(auto wt : trackRange) {
|
2018-11-19 01:50:24 +00:00
|
|
|
waveArray.push_back( wt->SharedPointer< const WaveTrack >() );
|
2017-04-16 20:36:46 +00:00
|
|
|
tstart = wt->GetStartTime();
|
|
|
|
tend = wt->GetEndTime();
|
|
|
|
if (tend > mixEndTime)
|
|
|
|
mixEndTime = tend;
|
|
|
|
// try and get the start time. If the track is empty we will get 0,
|
|
|
|
// which is ambiguous because it could just mean the track starts at
|
|
|
|
// the beginning of the project, as well as empty track. The give-away
|
|
|
|
// is that an empty track also ends at zero.
|
|
|
|
|
|
|
|
if (tstart != tend) {
|
|
|
|
// we don't get empty tracks here
|
|
|
|
if (!gotstart) {
|
|
|
|
// no previous start, use this one unconditionally
|
|
|
|
mixStartTime = tstart;
|
|
|
|
gotstart = true;
|
|
|
|
} else if (tstart < mixStartTime)
|
|
|
|
mixStartTime = tstart; // have a start, only make it smaller
|
|
|
|
} // end if start and end are different
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
/* create the destination track (NEW track) */
|
2018-10-10 18:09:10 +00:00
|
|
|
if (numWaves == (int)TrackList::Channels(first).size())
|
2011-08-28 19:30:17 +00:00
|
|
|
oneinput = true;
|
|
|
|
// only one input track (either 1 mono or one linked stereo pair)
|
|
|
|
|
2016-03-13 07:14:46 +00:00
|
|
|
auto mixLeft = trackFactory->NewWaveTrack(format, rate);
|
2011-08-28 19:30:17 +00:00
|
|
|
if (oneinput)
|
2017-04-16 20:36:46 +00:00
|
|
|
mixLeft->SetName(first->GetName()); /* set name of output track to be the same as the sole input track */
|
2011-08-28 19:30:17 +00:00
|
|
|
else
|
2020-12-25 00:09:05 +00:00
|
|
|
/* i18n-hint: noun, means a track, made by mixing other tracks */
|
2011-08-28 19:30:17 +00:00
|
|
|
mixLeft->SetName(_("Mix"));
|
2011-08-28 19:54:03 +00:00
|
|
|
mixLeft->SetOffset(mixStartTime);
|
2017-04-16 20:36:46 +00:00
|
|
|
|
|
|
|
// TODO: more-than-two-channels
|
2016-03-13 07:14:46 +00:00
|
|
|
decltype(mixLeft) mixRight{};
|
2018-09-20 14:44:51 +00:00
|
|
|
if ( !mono ) {
|
2016-03-13 07:14:46 +00:00
|
|
|
mixRight = trackFactory->NewWaveTrack(format, rate);
|
2011-08-29 10:16:11 +00:00
|
|
|
if (oneinput) {
|
2017-04-16 20:36:46 +00:00
|
|
|
auto channels = TrackList::Channels(first);
|
|
|
|
if (channels.size() > 1)
|
|
|
|
mixRight->SetName((*channels.begin().advance(1))->GetName()); /* set name to match input track's right channel!*/
|
2011-08-29 10:16:11 +00:00
|
|
|
else
|
2017-04-16 20:36:46 +00:00
|
|
|
mixRight->SetName(first->GetName()); /* set name to that of sole input channel */
|
2011-08-29 10:16:11 +00:00
|
|
|
}
|
2011-08-28 19:30:17 +00:00
|
|
|
else
|
|
|
|
mixRight->SetName(_("Mix"));
|
2011-08-28 19:54:03 +00:00
|
|
|
mixRight->SetOffset(mixStartTime);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2011-08-28 19:54:03 +00:00
|
|
|
|
2016-08-24 11:56:33 +00:00
|
|
|
auto maxBlockLen = mixLeft->GetIdealBlockSize();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2011-08-28 19:54:03 +00:00
|
|
|
// If the caller didn't specify a time range, use the whole range in which
|
|
|
|
// any input track had clips in it.
|
2010-01-23 19:44:49 +00:00
|
|
|
if (startTime == endTime) {
|
2011-08-28 19:54:03 +00:00
|
|
|
startTime = mixStartTime;
|
|
|
|
endTime = mixEndTime;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 23:10:45 +00:00
|
|
|
Mixer mixer(waveArray,
|
2016-12-22 22:08:29 +00:00
|
|
|
// Throw to abort mix-and-render if read fails:
|
|
|
|
true,
|
2020-11-30 18:41:10 +00:00
|
|
|
Mixer::WarpOptions{*tracks},
|
2016-02-01 01:39:24 +00:00
|
|
|
startTime, endTime, mono ? 1 : 2, maxBlockLen, false,
|
|
|
|
rate, format);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-07-23 23:29:50 +00:00
|
|
|
::wxSafeYield();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-12-24 15:43:25 +00:00
|
|
|
auto updateResult = ProgressResult::Success;
|
2016-02-01 01:39:24 +00:00
|
|
|
{
|
2019-12-08 03:37:02 +00:00
|
|
|
ProgressDialog progress(XO("Mix and Render"),
|
|
|
|
XO("Mixing and rendering tracks"));
|
2016-02-01 01:39:24 +00:00
|
|
|
|
2016-12-24 15:43:25 +00:00
|
|
|
while (updateResult == ProgressResult::Success) {
|
2016-08-24 15:24:26 +00:00
|
|
|
auto blockLen = mixer.Process(maxBlockLen);
|
2016-02-01 01:39:24 +00:00
|
|
|
|
|
|
|
if (blockLen == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (mono) {
|
|
|
|
samplePtr buffer = mixer.GetBuffer();
|
|
|
|
mixLeft->Append(buffer, format, blockLen);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
samplePtr buffer;
|
|
|
|
buffer = mixer.GetBuffer(0);
|
|
|
|
mixLeft->Append(buffer, format, blockLen);
|
|
|
|
buffer = mixer.GetBuffer(1);
|
|
|
|
mixRight->Append(buffer, format, blockLen);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateResult = progress.Update(mixer.MixGetCurrentTime() - startTime, endTime - startTime);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mixLeft->Flush();
|
2014-06-03 20:30:19 +00:00
|
|
|
if (!mono)
|
2010-01-23 19:44:49 +00:00
|
|
|
mixRight->Flush();
|
2016-12-24 15:43:25 +00:00
|
|
|
if (updateResult == ProgressResult::Cancelled || updateResult == ProgressResult::Failed)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-03-31 03:24:39 +00:00
|
|
|
return;
|
2016-03-13 07:14:46 +00:00
|
|
|
}
|
|
|
|
else {
|
2018-11-19 18:17:28 +00:00
|
|
|
uLeft = mixLeft, uRight = mixRight;
|
2010-01-23 19:44:49 +00:00
|
|
|
#if 0
|
|
|
|
int elapsedMS = wxGetElapsedTime();
|
|
|
|
double elapsedTime = elapsedMS * 0.001;
|
|
|
|
double maxTracks = totalTime / (elapsedTime / numWaves);
|
|
|
|
|
|
|
|
// Note: these shouldn't be translated - they're for debugging
|
|
|
|
// and profiling only.
|
2017-10-09 04:37:10 +00:00
|
|
|
wxPrintf(" Tracks: %d\n", numWaves);
|
|
|
|
wxPrintf(" Mix length: %f sec\n", totalTime);
|
|
|
|
wxPrintf("Elapsed time: %f sec\n", elapsedTime);
|
|
|
|
wxPrintf("Max number of tracks to mix in real time: %f\n", maxTracks);
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-30 18:41:10 +00:00
|
|
|
Mixer::WarpOptions::WarpOptions(const TrackList &list)
|
|
|
|
: minSpeed(0.0), maxSpeed(0.0)
|
|
|
|
{
|
|
|
|
auto timeTrack = *(list.Any<const TimeTrack>().begin());
|
|
|
|
envelope = timeTrack ? timeTrack->GetEnvelope() : nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Mixer::WarpOptions::WarpOptions(const BoundedEnvelope *e)
|
|
|
|
: envelope(e), minSpeed(0.0), maxSpeed(0.0)
|
|
|
|
{}
|
|
|
|
|
2015-04-14 18:52:22 +00:00
|
|
|
Mixer::WarpOptions::WarpOptions(double min, double max)
|
2019-06-06 13:21:01 +00:00
|
|
|
: minSpeed(min), maxSpeed(max)
|
2015-04-14 18:52:22 +00:00
|
|
|
{
|
|
|
|
if (minSpeed < 0)
|
|
|
|
{
|
|
|
|
wxASSERT(false);
|
|
|
|
minSpeed = 0;
|
|
|
|
}
|
|
|
|
if (maxSpeed < 0)
|
|
|
|
{
|
|
|
|
wxASSERT(false);
|
|
|
|
maxSpeed = 0;
|
|
|
|
}
|
|
|
|
if (minSpeed > maxSpeed)
|
|
|
|
{
|
|
|
|
wxASSERT(false);
|
|
|
|
std::swap(minSpeed, maxSpeed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-26 23:10:45 +00:00
|
|
|
Mixer::Mixer(const WaveTrackConstArray &inputTracks,
|
2016-12-22 22:08:29 +00:00
|
|
|
bool mayThrow,
|
2015-04-14 18:52:22 +00:00
|
|
|
const WarpOptions &warpOptions,
|
2010-01-23 19:44:49 +00:00
|
|
|
double startTime, double stopTime,
|
2016-09-06 13:19:27 +00:00
|
|
|
unsigned numOutChannels, size_t outBufferSize, bool outInterleaved,
|
2010-01-23 19:44:49 +00:00
|
|
|
double outRate, sampleFormat outFormat,
|
2020-11-30 23:25:01 +00:00
|
|
|
bool highQuality, MixerSpec *mixerSpec, bool applyTrackGains)
|
2016-04-14 15:54:59 +00:00
|
|
|
: mNumInputTracks { inputTracks.size() }
|
|
|
|
|
2020-11-30 23:25:01 +00:00
|
|
|
, mApplyTrackGains{ applyTrackGains }
|
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
// This is the number of samples grabbed in one go from a track
|
|
|
|
// and placed in a queue, when mixing with resampling.
|
|
|
|
// (Should we use WaveTrack::GetBestBlockSize instead?)
|
|
|
|
, mQueueMaxLen{ 65536 }
|
|
|
|
, mSampleQueue{ mNumInputTracks, mQueueMaxLen }
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-03-30 14:46:44 +00:00
|
|
|
, mNumChannels{ numOutChannels }
|
2016-04-14 15:54:59 +00:00
|
|
|
, mGains{ mNumChannels }
|
2016-12-22 22:08:29 +00:00
|
|
|
|
2020-11-30 23:25:01 +00:00
|
|
|
, mFormat{ outFormat }
|
|
|
|
, mRate{ outRate }
|
|
|
|
|
2016-12-22 22:08:29 +00:00
|
|
|
, mMayThrow{ mayThrow }
|
2016-04-14 15:54:59 +00:00
|
|
|
{
|
2013-09-16 02:32:43 +00:00
|
|
|
mHighQuality = highQuality;
|
2016-04-14 15:54:59 +00:00
|
|
|
mInputTrack.reinit(mNumInputTracks);
|
2015-04-14 18:52:22 +00:00
|
|
|
|
|
|
|
// mSamplePos holds for each track the next sample position not
|
|
|
|
// yet processed.
|
2016-04-14 15:54:59 +00:00
|
|
|
mSamplePos.reinit(mNumInputTracks);
|
|
|
|
for(size_t i=0; i<mNumInputTracks; i++) {
|
2016-05-15 15:06:51 +00:00
|
|
|
mInputTrack[i].SetTrack(inputTracks[i]);
|
2010-01-23 19:44:49 +00:00
|
|
|
mSamplePos[i] = inputTracks[i]->TimeToLongSamples(startTime);
|
|
|
|
}
|
2019-06-06 13:21:01 +00:00
|
|
|
mEnvelope = warpOptions.envelope;
|
2010-01-23 19:44:49 +00:00
|
|
|
mT0 = startTime;
|
|
|
|
mT1 = stopTime;
|
2012-12-19 21:49:25 +00:00
|
|
|
mTime = startTime;
|
2010-01-23 19:44:49 +00:00
|
|
|
mBufferSize = outBufferSize;
|
|
|
|
mInterleaved = outInterleaved;
|
2015-04-17 01:31:10 +00:00
|
|
|
mSpeed = 1.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
if( mixerSpec && mixerSpec->GetNumChannels() == mNumChannels &&
|
|
|
|
mixerSpec->GetNumTracks() == mNumInputTracks )
|
|
|
|
mMixerSpec = mixerSpec;
|
|
|
|
else
|
|
|
|
mMixerSpec = NULL;
|
|
|
|
|
|
|
|
if (mInterleaved) {
|
|
|
|
mNumBuffers = 1;
|
|
|
|
mInterleavedBufferSize = mBufferSize * mNumChannels;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mNumBuffers = mNumChannels;
|
|
|
|
mInterleavedBufferSize = mBufferSize;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
mBuffer.reinit(mNumBuffers);
|
|
|
|
mTemp.reinit(mNumBuffers);
|
2017-12-08 11:26:09 +00:00
|
|
|
for (unsigned int c = 0; c < mNumBuffers; c++) {
|
2016-02-01 15:16:00 +00:00
|
|
|
mBuffer[c].Allocate(mInterleavedBufferSize, mFormat);
|
|
|
|
mTemp[c].Allocate(mInterleavedBufferSize, floatSample);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2020-11-19 23:19:08 +00:00
|
|
|
// PRL: Bug2536: see other comments below
|
|
|
|
mFloatBuffer = Floats{ mInterleavedBufferSize + 1 };
|
2015-04-14 18:52:22 +00:00
|
|
|
|
|
|
|
// But cut the queue into blocks of this finer size
|
|
|
|
// for variable rate resampling. Each block is resampled at some
|
|
|
|
// constant rate.
|
2010-01-23 19:44:49 +00:00
|
|
|
mProcessLen = 1024;
|
|
|
|
|
2015-04-14 18:52:22 +00:00
|
|
|
// Position in each queue of the start of the next block to resample.
|
2016-04-14 15:54:59 +00:00
|
|
|
mQueueStart.reinit(mNumInputTracks);
|
2015-04-14 18:52:22 +00:00
|
|
|
|
|
|
|
// For each queue, the number of available samples after the queue start.
|
2016-04-14 15:54:59 +00:00
|
|
|
mQueueLen.reinit(mNumInputTracks);
|
|
|
|
mResample.reinit(mNumInputTracks);
|
2018-06-11 18:05:00 +00:00
|
|
|
mMinFactor.resize(mNumInputTracks);
|
|
|
|
mMaxFactor.resize(mNumInputTracks);
|
|
|
|
for (size_t i = 0; i<mNumInputTracks; i++) {
|
2016-05-15 15:06:51 +00:00
|
|
|
double factor = (mRate / mInputTrack[i].GetTrack()->GetRate());
|
2019-06-06 13:21:01 +00:00
|
|
|
if (mEnvelope) {
|
2013-08-04 01:58:54 +00:00
|
|
|
// variable rate resampling
|
2015-04-14 18:52:22 +00:00
|
|
|
mbVariableRates = true;
|
2019-06-06 13:21:01 +00:00
|
|
|
mMinFactor[i] = factor / mEnvelope->GetRangeUpper();
|
|
|
|
mMaxFactor[i] = factor / mEnvelope->GetRangeLower();
|
2015-04-14 18:52:22 +00:00
|
|
|
}
|
|
|
|
else if (warpOptions.minSpeed > 0.0 && warpOptions.maxSpeed > 0.0) {
|
|
|
|
// variable rate resampling
|
|
|
|
mbVariableRates = true;
|
2018-06-11 18:05:00 +00:00
|
|
|
mMinFactor[i] = factor / warpOptions.maxSpeed;
|
|
|
|
mMaxFactor[i] = factor / warpOptions.minSpeed;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2015-04-14 18:52:22 +00:00
|
|
|
else {
|
|
|
|
// constant rate resampling
|
|
|
|
mbVariableRates = false;
|
2018-06-11 18:05:00 +00:00
|
|
|
mMinFactor[i] = mMaxFactor[i] = factor;
|
2015-04-14 18:52:22 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mQueueStart[i] = 0;
|
|
|
|
mQueueLen[i] = 0;
|
|
|
|
}
|
|
|
|
|
2018-06-11 18:05:00 +00:00
|
|
|
MakeResamplers();
|
|
|
|
|
2016-08-24 11:56:33 +00:00
|
|
|
const auto envLen = std::max(mQueueMaxLen, mInterleavedBufferSize);
|
2016-04-14 15:54:59 +00:00
|
|
|
mEnvValues.reinit(envLen);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Mixer::~Mixer()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-06-11 18:05:00 +00:00
|
|
|
void Mixer::MakeResamplers()
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < mNumInputTracks; i++)
|
|
|
|
mResample[i] = std::make_unique<Resample>(mHighQuality, mMinFactor[i], mMaxFactor[i]);
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void Mixer::Clear()
|
|
|
|
{
|
2017-12-08 11:26:09 +00:00
|
|
|
for (unsigned int c = 0; c < mNumBuffers; c++) {
|
2016-02-01 15:16:00 +00:00
|
|
|
memset(mTemp[c].ptr(), 0, mInterleavedBufferSize * SAMPLE_SIZE(floatSample));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-02 19:53:09 +00:00
|
|
|
void MixBuffers(unsigned numChannels, int *channelFlags, float *gains,
|
2016-02-01 15:16:00 +00:00
|
|
|
samplePtr src, SampleBuffer *dests,
|
2010-01-23 19:44:49 +00:00
|
|
|
int len, bool interleaved)
|
|
|
|
{
|
2017-12-08 11:26:09 +00:00
|
|
|
for (unsigned int c = 0; c < numChannels; c++) {
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!channelFlags[c])
|
|
|
|
continue;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
samplePtr destPtr;
|
2016-09-02 19:53:09 +00:00
|
|
|
unsigned skip;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (interleaved) {
|
2016-02-01 15:16:00 +00:00
|
|
|
destPtr = dests[0].ptr() + c*SAMPLE_SIZE(floatSample);
|
2010-01-23 19:44:49 +00:00
|
|
|
skip = numChannels;
|
|
|
|
} else {
|
2016-02-01 15:16:00 +00:00
|
|
|
destPtr = dests[c].ptr();
|
2010-01-23 19:44:49 +00:00
|
|
|
skip = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
float gain = gains[c];
|
|
|
|
float *dest = (float *)destPtr;
|
|
|
|
float *temp = (float *)src;
|
|
|
|
for (int j = 0; j < len; j++) {
|
|
|
|
*dest += temp[j] * gain; // the actual mixing process
|
|
|
|
dest += skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2019-06-06 13:21:01 +00:00
|
|
|
namespace {
|
|
|
|
//Note: The meaning of this function has changed (December 2012)
|
|
|
|
//Previously this function did something that was close to the opposite (but not entirely accurate).
|
|
|
|
/** @brief Compute the integral warp factor between two non-warped time points
|
|
|
|
*
|
|
|
|
* Calculate the relative length increase of the chosen segment from the original sound.
|
|
|
|
* So if this time track has a low value (i.e. makes the sound slower), the NEW warped
|
|
|
|
* sound will be *longer* than the original sound, so the return value of this function
|
|
|
|
* is larger.
|
|
|
|
* @param t0 The starting time to calculate from
|
|
|
|
* @param t1 The ending time to calculate to
|
|
|
|
* @return The relative length increase of the chosen segment from the original sound.
|
|
|
|
*/
|
|
|
|
double ComputeWarpFactor(const Envelope &env, double t0, double t1)
|
|
|
|
{
|
|
|
|
return env.AverageOfInverse(t0, t1);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t Mixer::MixVariableRates(int *channelFlags, WaveTrackCache &cache,
|
2010-01-23 19:44:49 +00:00
|
|
|
sampleCount *pos, float *queue,
|
|
|
|
int *queueStart, int *queueLen,
|
2012-11-04 04:44:10 +00:00
|
|
|
Resample * pResample)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-01-30 19:13:57 +00:00
|
|
|
const WaveTrack *const track = cache.GetTrack().get();
|
2015-04-16 21:35:58 +00:00
|
|
|
const double trackRate = track->GetRate();
|
2015-04-17 01:31:10 +00:00
|
|
|
const double initialWarp = mRate / mSpeed / trackRate;
|
2015-04-16 21:35:58 +00:00
|
|
|
const double tstep = 1.0 / trackRate;
|
2016-09-06 11:40:49 +00:00
|
|
|
auto sampleSize = SAMPLE_SIZE(floatSample);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
decltype(mMaxOut) out = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
/* time is floating point. Sample rate is integer. The number of samples
|
|
|
|
* has to be integer, but the multiplication gives a float result, which we
|
|
|
|
* round to get an integer result. TODO: is this always right or can it be
|
|
|
|
* off by one sometimes? Can we not get this information directly from the
|
|
|
|
* clip (which must know) rather than convert the time?
|
|
|
|
*
|
|
|
|
* LLL: Not at this time. While WaveClips provide methods to retrieve the
|
|
|
|
* start and end sample, they do the same float->sampleCount conversion
|
|
|
|
* to calculate the position.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Find the last sample
|
|
|
|
double endTime = track->GetEndTime();
|
2015-04-16 21:35:58 +00:00
|
|
|
double startTime = track->GetStartTime();
|
2016-01-28 03:51:38 +00:00
|
|
|
const bool backwards = (mT1 < mT0);
|
|
|
|
const double tEnd = backwards
|
|
|
|
? std::max(startTime, mT1)
|
|
|
|
: std::min(endTime, mT1);
|
2016-08-24 15:24:26 +00:00
|
|
|
const auto endPos = track->TimeToLongSamples(tEnd);
|
2015-04-16 21:35:58 +00:00
|
|
|
// Find the time corresponding to the start of the queue, for use with time track
|
2016-08-25 12:53:59 +00:00
|
|
|
double t = ((*pos).as_long_long() +
|
|
|
|
(backwards ? *queueLen : - *queueLen)) / trackRate;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
while (out < mMaxOut) {
|
2017-12-08 11:26:09 +00:00
|
|
|
if (*queueLen < (int)mProcessLen) {
|
2015-04-14 18:52:22 +00:00
|
|
|
// Shift pending portion to start of the buffer
|
2010-01-23 19:44:49 +00:00
|
|
|
memmove(queue, &queue[*queueStart], (*queueLen) * sampleSize);
|
|
|
|
*queueStart = 0;
|
|
|
|
|
2016-08-24 16:13:53 +00:00
|
|
|
auto getLen = limitSampleBufferSize(
|
2016-08-21 22:05:43 +00:00
|
|
|
mQueueMaxLen - *queueLen,
|
|
|
|
backwards ? *pos - endPos : endPos - *pos
|
|
|
|
);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-16 21:35:58 +00:00
|
|
|
// Nothing to do if past end of play interval
|
2010-01-23 19:44:49 +00:00
|
|
|
if (getLen > 0) {
|
2015-04-16 21:35:58 +00:00
|
|
|
if (backwards) {
|
2021-05-23 21:25:55 +00:00
|
|
|
auto results =
|
|
|
|
cache.GetFloats(*pos - (getLen - 1), getLen, mMayThrow);
|
2017-03-16 23:24:36 +00:00
|
|
|
if (results)
|
|
|
|
memcpy(&queue[*queueLen], results, sizeof(float) * getLen);
|
|
|
|
else
|
|
|
|
memset(&queue[*queueLen], 0, sizeof(float) * getLen);
|
2015-04-16 21:35:58 +00:00
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
track->GetEnvelopeValues(mEnvValues.get(),
|
2015-04-16 21:35:58 +00:00
|
|
|
getLen,
|
2016-08-25 12:53:59 +00:00
|
|
|
(*pos - (getLen- 1)).as_double() / trackRate);
|
2015-04-16 21:35:58 +00:00
|
|
|
*pos -= getLen;
|
|
|
|
}
|
|
|
|
else {
|
2021-05-23 21:25:55 +00:00
|
|
|
auto results = cache.GetFloats(*pos, getLen, mMayThrow);
|
2017-03-16 23:24:36 +00:00
|
|
|
if (results)
|
|
|
|
memcpy(&queue[*queueLen], results, sizeof(float) * getLen);
|
|
|
|
else
|
|
|
|
memset(&queue[*queueLen], 0, sizeof(float) * getLen);
|
2015-04-16 21:35:58 +00:00
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
track->GetEnvelopeValues(mEnvValues.get(),
|
2015-04-16 21:35:58 +00:00
|
|
|
getLen,
|
2016-08-25 12:53:59 +00:00
|
|
|
(*pos).as_double() / trackRate);
|
2015-04-16 21:35:58 +00:00
|
|
|
|
|
|
|
*pos += getLen;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-24 16:13:53 +00:00
|
|
|
for (decltype(getLen) i = 0; i < getLen; i++) {
|
2010-01-23 19:44:49 +00:00
|
|
|
queue[(*queueLen) + i] *= mEnvValues[i];
|
|
|
|
}
|
|
|
|
|
2015-04-16 21:35:58 +00:00
|
|
|
if (backwards)
|
|
|
|
ReverseSamples((samplePtr)&queue[0], floatSample,
|
2016-01-28 03:51:38 +00:00
|
|
|
*queueLen, getLen);
|
2015-04-16 21:35:58 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
*queueLen += getLen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto thisProcessLen = mProcessLen;
|
2017-12-08 11:26:09 +00:00
|
|
|
bool last = (*queueLen < (int)mProcessLen);
|
2010-01-23 19:44:49 +00:00
|
|
|
if (last) {
|
|
|
|
thisProcessLen = *queueLen;
|
|
|
|
}
|
|
|
|
|
2012-12-19 21:49:25 +00:00
|
|
|
double factor = initialWarp;
|
2019-06-06 13:21:01 +00:00
|
|
|
if (mEnvelope)
|
2012-12-19 21:49:25 +00:00
|
|
|
{
|
|
|
|
//TODO-MB: The end time is wrong when the resampler doesn't use all input samples,
|
|
|
|
// as a result of this the warp factor may be slightly wrong, so AudioIO will stop too soon
|
|
|
|
// or too late (resulting in missing sound or inserted silence). This can't be fixed
|
|
|
|
// without changing the way the resampler works, because the number of input samples that will be used
|
2015-04-16 21:35:58 +00:00
|
|
|
// is unpredictable. Maybe it can be compensated later though.
|
|
|
|
if (backwards)
|
2019-06-06 13:21:01 +00:00
|
|
|
factor *= ComputeWarpFactor( *mEnvelope,
|
|
|
|
t - (double)thisProcessLen / trackRate + tstep, t + tstep);
|
2015-04-16 21:35:58 +00:00
|
|
|
else
|
2019-06-06 13:21:01 +00:00
|
|
|
factor *= ComputeWarpFactor( *mEnvelope,
|
|
|
|
t, t + (double)thisProcessLen / trackRate);
|
2012-12-19 21:49:25 +00:00
|
|
|
}
|
|
|
|
|
2016-09-05 17:44:23 +00:00
|
|
|
auto results = pResample->Process(factor,
|
2020-11-19 23:19:08 +00:00
|
|
|
&queue[*queueStart],
|
|
|
|
thisProcessLen,
|
|
|
|
last,
|
|
|
|
// PRL: Bug2536: crash in soxr happened on Mac, sometimes, when
|
|
|
|
// mMaxOut - out == 1 and &mFloatBuffer[out + 1] was an unmapped
|
|
|
|
// address, because soxr, strangely, fetched an 8-byte (misaligned!)
|
|
|
|
// value from &mFloatBuffer[out], but did nothing with it anyway,
|
|
|
|
// in soxr_output_no_callback.
|
|
|
|
// Now we make the bug go away by allocating a little more space in
|
|
|
|
// the buffer than we need.
|
|
|
|
&mFloatBuffer[out],
|
|
|
|
mMaxOut - out);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-09-05 17:44:23 +00:00
|
|
|
const auto input_used = results.first;
|
2010-01-23 19:44:49 +00:00
|
|
|
*queueStart += input_used;
|
|
|
|
*queueLen -= input_used;
|
2016-09-05 17:44:23 +00:00
|
|
|
out += results.second;
|
|
|
|
t += (input_used / trackRate) * (backwards ? -1 : 1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (last) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
for (size_t c = 0; c < mNumChannels; c++) {
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mApplyTrackGains) {
|
|
|
|
mGains[c] = track->GetChannelGain(c);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mGains[c] = 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MixBuffers(mNumChannels,
|
|
|
|
channelFlags,
|
2016-04-14 15:54:59 +00:00
|
|
|
mGains.get(),
|
|
|
|
(samplePtr)mFloatBuffer.get(),
|
|
|
|
mTemp.get(),
|
2010-01-23 19:44:49 +00:00
|
|
|
out,
|
|
|
|
mInterleaved);
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t Mixer::MixSameRate(int *channelFlags, WaveTrackCache &cache,
|
2010-01-23 19:44:49 +00:00
|
|
|
sampleCount *pos)
|
|
|
|
{
|
2018-01-30 19:13:57 +00:00
|
|
|
const WaveTrack *const track = cache.GetTrack().get();
|
2016-08-25 12:53:59 +00:00
|
|
|
const double t = ( *pos ).as_double() / track->GetRate();
|
2015-04-16 21:35:58 +00:00
|
|
|
const double trackEndTime = track->GetEndTime();
|
|
|
|
const double trackStartTime = track->GetStartTime();
|
2016-01-28 03:51:38 +00:00
|
|
|
const bool backwards = (mT1 < mT0);
|
|
|
|
const double tEnd = backwards
|
|
|
|
? std::max(trackStartTime, mT1)
|
|
|
|
: std::min(trackEndTime, mT1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//don't process if we're at the end of the selection or track.
|
2015-04-16 21:35:58 +00:00
|
|
|
if ((backwards ? t <= tEnd : t >= tEnd))
|
2010-01-23 19:44:49 +00:00
|
|
|
return 0;
|
2014-06-03 20:30:19 +00:00
|
|
|
//if we're about to approach the end of the track or selection, figure out how much we need to grab
|
2017-07-18 15:50:20 +00:00
|
|
|
auto slen = limitSampleBufferSize(
|
|
|
|
mMaxOut,
|
|
|
|
// PRL: maybe t and tEnd should be given as sampleCount instead to
|
|
|
|
// avoid trouble subtracting one large value from another for a small
|
|
|
|
// difference
|
|
|
|
sampleCount{ (backwards ? t - tEnd : tEnd - t) * track->GetRate() + 0.5 }
|
2016-09-02 16:50:40 +00:00
|
|
|
);
|
2016-08-20 21:56:41 +00:00
|
|
|
|
2015-04-16 21:35:58 +00:00
|
|
|
if (backwards) {
|
2021-05-23 21:25:55 +00:00
|
|
|
auto results = cache.GetFloats(*pos - (slen - 1), slen, mMayThrow);
|
2017-03-16 23:24:36 +00:00
|
|
|
if (results)
|
2016-04-14 15:54:59 +00:00
|
|
|
memcpy(mFloatBuffer.get(), results, sizeof(float) * slen);
|
2017-03-16 23:24:36 +00:00
|
|
|
else
|
2016-04-14 15:54:59 +00:00
|
|
|
memset(mFloatBuffer.get(), 0, sizeof(float) * slen);
|
|
|
|
track->GetEnvelopeValues(mEnvValues.get(), slen, t - (slen - 1) / mRate);
|
2016-09-06 23:46:56 +00:00
|
|
|
for(decltype(slen) i = 0; i < slen; i++)
|
2015-04-16 21:35:58 +00:00
|
|
|
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
|
2016-04-14 15:54:59 +00:00
|
|
|
ReverseSamples((samplePtr)mFloatBuffer.get(), floatSample, 0, slen);
|
2015-04-16 21:35:58 +00:00
|
|
|
|
|
|
|
*pos -= slen;
|
|
|
|
}
|
|
|
|
else {
|
2021-05-23 21:25:55 +00:00
|
|
|
auto results = cache.GetFloats(*pos, slen, mMayThrow);
|
2017-03-16 23:24:36 +00:00
|
|
|
if (results)
|
2016-04-14 15:54:59 +00:00
|
|
|
memcpy(mFloatBuffer.get(), results, sizeof(float) * slen);
|
2017-03-16 23:24:36 +00:00
|
|
|
else
|
2016-04-14 15:54:59 +00:00
|
|
|
memset(mFloatBuffer.get(), 0, sizeof(float) * slen);
|
|
|
|
track->GetEnvelopeValues(mEnvValues.get(), slen, t);
|
2016-09-06 23:46:56 +00:00
|
|
|
for(decltype(slen) i = 0; i < slen; i++)
|
2015-04-16 21:35:58 +00:00
|
|
|
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
|
|
|
|
|
|
|
|
*pos += slen;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t c=0; c<mNumChannels; c++)
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mApplyTrackGains)
|
|
|
|
mGains[c] = track->GetChannelGain(c);
|
|
|
|
else
|
|
|
|
mGains[c] = 1.0;
|
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
MixBuffers(mNumChannels, channelFlags, mGains.get(),
|
|
|
|
(samplePtr)mFloatBuffer.get(), mTemp.get(), slen, mInterleaved);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return slen;
|
|
|
|
}
|
|
|
|
|
2016-09-06 13:19:27 +00:00
|
|
|
size_t Mixer::Process(size_t maxToProcess)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2012-12-19 21:49:25 +00:00
|
|
|
// MB: this is wrong! mT represented warped time, and mTime is too inaccurate to use
|
|
|
|
// it here. It's also unnecessary I think.
|
|
|
|
//if (mT >= mT1)
|
|
|
|
// return 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
decltype(Process(0)) maxOut = 0;
|
2016-04-14 15:54:59 +00:00
|
|
|
ArrayOf<int> channelFlags{ mNumChannels };
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mMaxOut = maxToProcess;
|
|
|
|
|
|
|
|
Clear();
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t i=0; i<mNumInputTracks; i++) {
|
2018-01-30 19:13:57 +00:00
|
|
|
const WaveTrack *const track = mInputTrack[i].GetTrack().get();
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t j=0; j<mNumChannels; j++)
|
2010-01-23 19:44:49 +00:00
|
|
|
channelFlags[j] = 0;
|
|
|
|
|
|
|
|
if( mMixerSpec ) {
|
|
|
|
//ignore left and right when downmixing is not required
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t j = 0; j < mNumChannels; j++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
channelFlags[ j ] = mMixerSpec->mMap[ i ][ j ] ? 1 : 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
switch(track->GetChannel()) {
|
|
|
|
case Track::MonoChannel:
|
|
|
|
default:
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t j=0; j<mNumChannels; j++)
|
2010-01-23 19:44:49 +00:00
|
|
|
channelFlags[j] = 1;
|
|
|
|
break;
|
|
|
|
case Track::LeftChannel:
|
|
|
|
channelFlags[0] = 1;
|
|
|
|
break;
|
|
|
|
case Track::RightChannel:
|
|
|
|
if (mNumChannels >= 2)
|
|
|
|
channelFlags[1] = 1;
|
|
|
|
else
|
|
|
|
channelFlags[0] = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-04-14 18:52:22 +00:00
|
|
|
if (mbVariableRates || track->GetRate() != mRate)
|
2015-04-17 01:31:10 +00:00
|
|
|
maxOut = std::max(maxOut,
|
2016-04-14 15:54:59 +00:00
|
|
|
MixVariableRates(channelFlags.get(), mInputTrack[i],
|
|
|
|
&mSamplePos[i], mSampleQueue[i].get(),
|
|
|
|
&mQueueStart[i], &mQueueLen[i], mResample[i].get()));
|
2010-01-23 19:44:49 +00:00
|
|
|
else
|
2015-04-17 01:31:10 +00:00
|
|
|
maxOut = std::max(maxOut,
|
2016-04-14 15:54:59 +00:00
|
|
|
MixSameRate(channelFlags.get(), mInputTrack[i], &mSamplePos[i]));
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-25 12:53:59 +00:00
|
|
|
double t = mSamplePos[i].as_double() / (double)track->GetRate();
|
2015-04-16 21:35:58 +00:00
|
|
|
if (mT0 > mT1)
|
2016-01-26 23:59:33 +00:00
|
|
|
// backwards (as possibly in scrubbing)
|
|
|
|
mTime = std::max(std::min(t, mTime), mT1);
|
2015-04-16 21:35:58 +00:00
|
|
|
else
|
2016-01-26 23:59:33 +00:00
|
|
|
// forwards (the usual)
|
|
|
|
mTime = std::min(std::max(t, mTime), mT1);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2013-09-16 02:32:43 +00:00
|
|
|
if(mInterleaved) {
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t c=0; c<mNumChannels; c++) {
|
2016-02-01 15:16:00 +00:00
|
|
|
CopySamples(mTemp[0].ptr() + (c * SAMPLE_SIZE(floatSample)),
|
2015-04-14 18:52:22 +00:00
|
|
|
floatSample,
|
2016-02-01 15:16:00 +00:00
|
|
|
mBuffer[0].ptr() + (c * SAMPLE_SIZE(mFormat)),
|
2015-04-14 18:52:22 +00:00
|
|
|
mFormat,
|
|
|
|
maxOut,
|
2020-11-28 17:53:53 +00:00
|
|
|
mHighQuality ? gHighQualityDither : gLowQualityDither,
|
2015-04-14 18:52:22 +00:00
|
|
|
mNumChannels,
|
|
|
|
mNumChannels);
|
2013-09-16 02:32:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t c=0; c<mNumBuffers; c++) {
|
2016-02-01 15:16:00 +00:00
|
|
|
CopySamples(mTemp[c].ptr(),
|
2015-04-14 18:52:22 +00:00
|
|
|
floatSample,
|
2016-02-01 15:16:00 +00:00
|
|
|
mBuffer[c].ptr(),
|
2015-04-14 18:52:22 +00:00
|
|
|
mFormat,
|
|
|
|
maxOut,
|
2020-11-28 17:53:53 +00:00
|
|
|
mHighQuality ? gHighQualityDither : gLowQualityDither);
|
2013-09-16 02:32:43 +00:00
|
|
|
}
|
|
|
|
}
|
2012-12-19 21:49:25 +00:00
|
|
|
// MB: this doesn't take warping into account, replaced with code based on mSamplePos
|
|
|
|
//mT += (maxOut / mRate);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return maxOut;
|
|
|
|
}
|
|
|
|
|
|
|
|
samplePtr Mixer::GetBuffer()
|
|
|
|
{
|
2016-02-01 15:16:00 +00:00
|
|
|
return mBuffer[0].ptr();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
samplePtr Mixer::GetBuffer(int channel)
|
|
|
|
{
|
2016-02-01 15:16:00 +00:00
|
|
|
return mBuffer[channel].ptr();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
double Mixer::MixGetCurrentTime()
|
|
|
|
{
|
2012-12-19 21:49:25 +00:00
|
|
|
return mTime;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Mixer::Restart()
|
|
|
|
{
|
2018-08-09 23:31:52 +00:00
|
|
|
mTime = mT0;
|
|
|
|
|
|
|
|
for(size_t i=0; i<mNumInputTracks; i++)
|
|
|
|
mSamplePos[i] = mInputTrack[i].GetTrack()->TimeToLongSamples(mT0);
|
|
|
|
|
|
|
|
for(size_t i=0; i<mNumInputTracks; i++) {
|
|
|
|
mQueueStart[i] = 0;
|
|
|
|
mQueueLen[i] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bug 1887: libsoxr 0.1.3, first used in Audacity 2.3.0, crashes with
|
|
|
|
// constant rate resampling if you try to reuse the resampler after it has
|
|
|
|
// flushed. Should that be considered a bug in sox? This works around it:
|
|
|
|
MakeResamplers();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2018-12-03 21:04:50 +00:00
|
|
|
void Mixer::Reposition(double t, bool bSkipping)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2012-12-19 21:49:25 +00:00
|
|
|
mTime = t;
|
2015-04-16 21:35:58 +00:00
|
|
|
const bool backwards = (mT1 < mT0);
|
|
|
|
if (backwards)
|
|
|
|
mTime = std::max(mT1, (std::min(mT0, mTime)));
|
|
|
|
else
|
|
|
|
mTime = std::max(mT0, (std::min(mT1, mTime)));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-14 15:54:59 +00:00
|
|
|
for(size_t i=0; i<mNumInputTracks; i++) {
|
2016-05-15 15:06:51 +00:00
|
|
|
mSamplePos[i] = mInputTrack[i].GetTrack()->TimeToLongSamples(mTime);
|
2010-01-23 19:44:49 +00:00
|
|
|
mQueueStart[i] = 0;
|
|
|
|
mQueueLen[i] = 0;
|
|
|
|
}
|
2018-11-07 14:58:12 +00:00
|
|
|
|
|
|
|
// Bug 2025: libsoxr 0.1.3, first used in Audacity 2.3.0, crashes with
|
|
|
|
// constant rate resampling if you try to reuse the resampler after it has
|
|
|
|
// flushed. Should that be considered a bug in sox? This works around it.
|
|
|
|
// (See also bug 1887, and the same work around in Mixer::Restart().)
|
2018-12-03 21:04:50 +00:00
|
|
|
if( bSkipping )
|
|
|
|
MakeResamplers();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 01:31:10 +00:00
|
|
|
void Mixer::SetTimesAndSpeed(double t0, double t1, double speed)
|
|
|
|
{
|
2016-02-15 15:54:11 +00:00
|
|
|
wxASSERT(std::isfinite(speed));
|
2015-04-17 01:31:10 +00:00
|
|
|
mT0 = t0;
|
|
|
|
mT1 = t1;
|
2015-05-04 22:50:44 +00:00
|
|
|
mSpeed = fabs(speed);
|
2015-04-17 01:31:10 +00:00
|
|
|
Reposition(t0);
|
|
|
|
}
|
|
|
|
|
2020-01-13 13:40:59 +00:00
|
|
|
void Mixer::SetSpeedForPlayAtSpeed(double speed)
|
Bug 1954: Clicks may occur starting/pausing play-at-speed or Scrub
Problem:
On Windows, after 50ms, there is a short period of roughly zero introduced into the output. On Linux, there is also a spike which sounds like a crackle.
In AudioIO::FillBuffers(), Mixer::SetTimesAndSpeed() is called, which sets mT0 and mT1 to a small interval.
In Mixer::MixVariableRates(), all the samples in the interval are used, which means the Resample::Process() is called with last equal to true.
So when Mixer::MixVariableRates() is called again, the resampler is being reused after a call to Process() in which last is true.
It is not stated in the soxr documentation if the resampler will produce valid results in this case, and it's only the scrubbing code which does this.
I think this is the problem, and so the partial fix below avoids this happening.
Partial fix for play-at-speed and keyboard scrubbing:
For these, there is no need to reset the values of mT0 and mT1. (There is no need to allow for the sample position being used to potentially jump around.)
So for these cases, Mixer::SetSpeed() is called, rather than Mixer::SetTimesAndSpeed().
2020-01-15 11:12:40 +00:00
|
|
|
{
|
|
|
|
wxASSERT(std::isfinite(speed));
|
|
|
|
mSpeed = fabs(speed);
|
|
|
|
}
|
|
|
|
|
2020-01-13 13:40:59 +00:00
|
|
|
void Mixer::SetSpeedForKeyboardScrubbing(double speed, double startTime)
|
|
|
|
{
|
|
|
|
wxASSERT(std::isfinite(speed));
|
|
|
|
|
|
|
|
// Check if the direction has changed
|
|
|
|
if ((speed > 0.0 && mT1 < mT0) || (speed < 0.0 && mT1 > mT0)) {
|
2020-01-17 10:06:27 +00:00
|
|
|
// It's safe to use 0 and std::numeric_limits<double>::max(),
|
|
|
|
// because Mixer::MixVariableRates() doesn't sample past the start
|
|
|
|
// or end of the audio in a track.
|
2020-01-13 13:40:59 +00:00
|
|
|
if (speed > 0.0 && mT1 < mT0) {
|
|
|
|
mT0 = 0;
|
2020-01-17 10:06:27 +00:00
|
|
|
mT1 = std::numeric_limits<double>::max();
|
2020-01-13 13:40:59 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-01-17 10:06:27 +00:00
|
|
|
mT0 = std::numeric_limits<double>::max();
|
2020-01-13 13:40:59 +00:00
|
|
|
mT1 = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Reposition(startTime, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
mSpeed = fabs(speed);
|
|
|
|
}
|
|
|
|
|
2016-09-02 19:53:09 +00:00
|
|
|
MixerSpec::MixerSpec( unsigned numTracks, unsigned maxNumChannels )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mNumTracks = mNumChannels = numTracks;
|
|
|
|
mMaxNumChannels = maxNumChannels;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if( mNumChannels > mMaxNumChannels )
|
|
|
|
mNumChannels = mMaxNumChannels;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
Alloc();
|
|
|
|
|
2017-12-08 11:26:09 +00:00
|
|
|
for( unsigned int i = 0; i < mNumTracks; i++ )
|
|
|
|
for( unsigned int j = 0; j < mNumChannels; j++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
mMap[ i ][ j ] = ( i == j );
|
|
|
|
}
|
|
|
|
|
|
|
|
MixerSpec::MixerSpec( const MixerSpec &mixerSpec )
|
|
|
|
{
|
|
|
|
mNumTracks = mixerSpec.mNumTracks;
|
|
|
|
mMaxNumChannels = mixerSpec.mMaxNumChannels;
|
|
|
|
mNumChannels = mixerSpec.mNumChannels;
|
|
|
|
|
|
|
|
Alloc();
|
|
|
|
|
2017-12-08 11:26:09 +00:00
|
|
|
for( unsigned int i = 0; i < mNumTracks; i++ )
|
|
|
|
for( unsigned int j = 0; j < mNumChannels; j++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
mMap[ i ][ j ] = mixerSpec.mMap[ i ][ j ];
|
|
|
|
}
|
|
|
|
|
|
|
|
void MixerSpec::Alloc()
|
|
|
|
{
|
2016-04-14 15:54:59 +00:00
|
|
|
mMap.reinit(mNumTracks, mMaxNumChannels);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MixerSpec::~MixerSpec()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-09-02 19:53:09 +00:00
|
|
|
bool MixerSpec::SetNumChannels( unsigned newNumChannels )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if( mNumChannels == newNumChannels )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if( newNumChannels > mMaxNumChannels )
|
|
|
|
return false;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2017-12-08 11:26:09 +00:00
|
|
|
for( unsigned int i = 0; i < mNumTracks; i++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-12-08 11:26:09 +00:00
|
|
|
for( unsigned int j = newNumChannels; j < mNumChannels; j++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
mMap[ i ][ j ] = false;
|
|
|
|
|
2017-12-08 11:26:09 +00:00
|
|
|
for( unsigned int j = mNumChannels; j < newNumChannels; j++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
mMap[ i ][ j ] = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
mNumChannels = newNumChannels;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
MixerSpec& MixerSpec::operator=( const MixerSpec &mixerSpec )
|
|
|
|
{
|
|
|
|
mNumTracks = mixerSpec.mNumTracks;
|
|
|
|
mNumChannels = mixerSpec.mNumChannels;
|
|
|
|
mMaxNumChannels = mixerSpec.mMaxNumChannels;
|
|
|
|
|
|
|
|
Alloc();
|
|
|
|
|
2017-12-08 11:26:09 +00:00
|
|
|
for( unsigned int i = 0; i < mNumTracks; i++ )
|
|
|
|
for( unsigned int j = 0; j < mNumChannels; j++ )
|
2010-01-23 19:44:49 +00:00
|
|
|
mMap[ i ][ j ] = mixerSpec.mMap[ i ][ j ];
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|