1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-18 09:00:07 +02:00
audacity/src/Mix.cpp
richardash1981 819d13dc76 Add a variety of comments to this otherwise uncommented code.
Update the implementation to make sense in a multi-clip environment.
Previously, a mix-and-rendered track always contained silence from time zero
until the time at which the first audio happened, as well as silence between
sections of rendered audio. Any audio before time zero was silently lost.
These changes mean that the single rendered clip now starts at the first sound in any of the tracks being mix-and-rendered and stops at the end of the last sound. This avoids a lot of unneccessary silence, and ensures that mix-and-rendering a single clip in a track leaves the clip the same length in the new, rendered, track. This is very useful.
These changes should also fix at least one bug in the case where a user-defined selection is being mix-and-rendered, although it is not currently possible to do this from the UI.
2011-08-28 19:54:03 +00:00

733 lines
20 KiB
C++

/**********************************************************************
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.
*//*******************************************************************/
#include "Audacity.h"
#include "Mix.h"
#include <math.h>
#include <wx/textctrl.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/timer.h>
#include <wx/intl.h>
#include "WaveTrack.h"
#include "DirManager.h"
#include "Envelope.h"
#include "Internat.h"
#include "Prefs.h"
#include "Project.h"
#include "Resample.h"
#include "float_cast.h"
bool MixAndRender(TrackList *tracks, TrackFactory *trackFactory,
double rate, sampleFormat format,
double startTime, double endTime,
WaveTrack **newLeft, WaveTrack **newRight)
{
// This function was formerly known as "Quick Mix".
WaveTrack **waveArray;
Track *t;
int numWaves = 0; /* number of wave tracks in the selection */
int numMono = 0; /* number of mono, centre-panned wave tracks in selection*/
bool mono = false; /* flag if output can be mono without loosing anything*/
bool oneinput = false; /* flag set to true if there is only one input track
(mono or stereo) */
int w;
TrackListIterator iter(tracks);
t = iter.First();
while (t) {
if (t->GetSelected() && t->GetKind() == Track::Wave) {
numWaves++;
float pan = ((WaveTrack*)t)->GetPan();
if (t->GetChannel() == Track::MonoChannel && pan == 0)
numMono++;
}
t = iter.Next();
}
if (numMono == numWaves)
mono = true;
/* 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.
*/
double mixStartTime; /* start time of first track to start */
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.
waveArray = new WaveTrack *[numWaves];
w = 0;
t = iter.First();
while (t) {
if (t->GetSelected() && t->GetKind() == Track::Wave) {
waveArray[w++] = (WaveTrack *) t;
tstart = t->GetStartTime();
tend = t->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;
} else if (tstart < mixStartTime)
mixStartTime = tstart; // have a start, only make it smaller
} // end if start and end are different
} // end if track is a selected WaveTrack.
/** @TODO: could we not use a SelectedTrackListOfKindIterator here? */
t = iter.Next();
}
/* create the destination track (new track) */
if ((numWaves == 1) || ((numWaves == 2) && (iter.First()->GetLink() != NULL)))
oneinput = true;
// only one input track (either 1 mono or one linked stereo pair)
WaveTrack *mixLeft = trackFactory->NewWaveTrack(format, rate);
if (oneinput)
mixLeft->SetName(iter.First()->GetName()); /* set name of output track to be the same as the sole input track */
else
mixLeft->SetName(_("Mix"));
mixLeft->SetOffset(mixStartTime);
WaveTrack *mixRight = 0;
if (mono) {
mixLeft->SetChannel(Track::MonoChannel);
}
else {
mixRight = trackFactory->NewWaveTrack(format, rate);
if (oneinput)
mixLeft->SetName(iter.First()->GetLink()->GetName()); /* set name to match input track's right channel!*/
else
mixRight->SetName(_("Mix"));
mixLeft->SetChannel(Track::LeftChannel);
mixRight->SetChannel(Track::RightChannel);
mixRight->SetOffset(mixStartTime);
mixLeft->SetLinked(true);
}
int maxBlockLen = mixLeft->GetIdealBlockSize();
// If the caller didn't specify a time range, use the whole range in which
// any input track had clips in it.
if (startTime == endTime) {
startTime = mixStartTime;
endTime = mixEndTime;
}
Mixer *mixer = new Mixer(numWaves, waveArray, tracks->GetTimeTrack(),
startTime, endTime, mono ? 1 : 2, maxBlockLen, false,
rate, format);
::wxSafeYield();
ProgressDialog *progress = new ProgressDialog(_("Mix and Render"),
_("Mixing and rendering tracks"));
int updateResult = eProgressSuccess;
while(updateResult == eProgressSuccess) {
sampleCount blockLen = mixer->Process(maxBlockLen);
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);
}
delete progress;
mixLeft->Flush();
if (!mono)
mixRight->Flush();
if (updateResult == eProgressCancelled || updateResult == eProgressFailed)
{
delete mixLeft;
if (!mono)
delete mixRight;
} else {
*newLeft = mixLeft;
if (!mono)
*newRight = mixRight;
#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.
printf(" Tracks: %d\n", numWaves);
printf(" Mix length: %f sec\n", totalTime);
printf("Elapsed time: %f sec\n", elapsedTime);
printf("Max number of tracks to mix in real time: %f\n", maxTracks);
#endif
}
delete[] waveArray;
delete mixer;
return (updateResult == eProgressSuccess || updateResult == eProgressStopped);
}
Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks,
TimeTrack *timeTrack,
double startTime, double stopTime,
int numOutChannels, int outBufferSize, bool outInterleaved,
double outRate, sampleFormat outFormat,
bool highQuality, MixerSpec *mixerSpec)
{
int i;
mNumInputTracks = numInputTracks;
mInputTrack = new WaveTrack*[mNumInputTracks];
mSamplePos = new sampleCount[mNumInputTracks];
for(i=0; i<mNumInputTracks; i++) {
mInputTrack[i] = inputTracks[i];
mSamplePos[i] = inputTracks[i]->TimeToLongSamples(startTime);
}
mTimeTrack = timeTrack;
mT0 = startTime;
mT1 = stopTime;
mT = startTime;
mNumChannels = numOutChannels;
mBufferSize = outBufferSize;
mInterleaved = outInterleaved;
mRate = outRate;
mFormat = outFormat;
mApplyTrackGains = true;
mGains = new float[mNumChannels];
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;
}
mBuffer = new samplePtr[mNumBuffers];
mTemp = new samplePtr[mNumBuffers];
for (int c = 0; c < mNumBuffers; c++) {
mBuffer[c] = NewSamples(mInterleavedBufferSize, mFormat);
mTemp[c] = NewSamples(mInterleavedBufferSize, floatSample);
}
mFloatBuffer = new float[mInterleavedBufferSize];
mQueueMaxLen = 65536;
mProcessLen = 1024;
mQueueStart = new int[mNumInputTracks];
mQueueLen = new int[mNumInputTracks];
mSampleQueue = new float *[mNumInputTracks];
mSRC = new Resample*[mNumInputTracks];
for(i=0; i<mNumInputTracks; i++) {
double factor = (mRate / mInputTrack[i]->GetRate());
double lowFactor = factor, highFactor = factor;
if (timeTrack) {
highFactor /= timeTrack->GetRangeLower() / 100.0;
lowFactor /= timeTrack->GetRangeUpper() / 100.0;
}
mSRC[i] = new Resample(highQuality, lowFactor, highFactor);
mSampleQueue[i] = new float[mQueueMaxLen];
mQueueStart[i] = 0;
mQueueLen[i] = 0;
}
int envLen = mInterleavedBufferSize;
if (mQueueMaxLen > envLen)
envLen = mQueueMaxLen;
mEnvValues = new double[envLen];
}
Mixer::~Mixer()
{
int i;
for (i = 0; i < mNumBuffers; i++) {
DeleteSamples(mBuffer[i]);
DeleteSamples(mTemp[i]);
}
delete[] mBuffer;
delete[] mTemp;
delete[] mInputTrack;
delete[] mEnvValues;
delete[] mFloatBuffer;
delete[] mGains;
delete[] mSamplePos;
for(i=0; i<mNumInputTracks; i++) {
delete mSRC[i];
delete[] mSampleQueue[i];
}
delete[] mSRC;
delete[] mSampleQueue;
delete[] mQueueStart;
delete[] mQueueLen;
}
void Mixer::ApplyTrackGains(bool apply)
{
mApplyTrackGains = apply;
}
void Mixer::Clear()
{
for (int c = 0; c < mNumBuffers; c++) {
memset(mTemp[c], 0, mInterleavedBufferSize * SAMPLE_SIZE(floatSample));
}
}
void MixBuffers(int numChannels, int *channelFlags, float *gains,
samplePtr src, samplePtr *dests,
int len, bool interleaved)
{
for (int c = 0; c < numChannels; c++) {
if (!channelFlags[c])
continue;
samplePtr destPtr;
int skip;
if (interleaved) {
destPtr = dests[0] + c*SAMPLE_SIZE(floatSample);
skip = numChannels;
} else {
destPtr = dests[c];
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;
}
}
}
sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track,
sampleCount *pos, float *queue,
int *queueStart, int *queueLen,
Resample *SRC)
{
double trackRate = track->GetRate();
double initialWarp = mRate / trackRate;
double tstep = 1.0 / trackRate;
double t = *pos / trackRate;
int sampleSize = SAMPLE_SIZE(floatSample);
sampleCount out = 0;
/* 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
sampleCount endPos;
double endTime = track->GetEndTime();
if (endTime > mT1) {
endPos = track->TimeToLongSamples(mT1);
}
else {
endPos = track->TimeToLongSamples(endTime);
}
while (out < mMaxOut) {
if (*queueLen < mProcessLen) {
memmove(queue, &queue[*queueStart], (*queueLen) * sampleSize);
*queueStart = 0;
int getLen = mQueueMaxLen - *queueLen;
// Constrain
if (*pos + getLen > endPos) {
getLen = endPos - *pos;
}
// Nothing to do if past end of track
if (getLen > 0) {
track->Get((samplePtr)&queue[*queueLen],
floatSample,
*pos,
getLen);
track->GetEnvelopeValues(mEnvValues,
getLen,
(*pos) / trackRate,
tstep);
for (int i = 0; i < getLen; i++) {
queue[(*queueLen) + i] *= mEnvValues[i];
}
*queueLen += getLen;
*pos += getLen;
}
}
double factor = initialWarp;
if (mTimeTrack) {
double warpFactor = mTimeTrack->GetEnvelope()->GetValue(t);
warpFactor = (mTimeTrack->GetRangeLower() * (1.0 - warpFactor) +
warpFactor * mTimeTrack->GetRangeUpper()) / 100.0;
factor /= warpFactor;
}
sampleCount thisProcessLen = mProcessLen;
bool last = (*queueLen < mProcessLen);
if (last) {
thisProcessLen = *queueLen;
}
int input_used;
int outgen = SRC->Process(factor,
&queue[*queueStart],
thisProcessLen,
last,
&input_used,
&mFloatBuffer[out],
mMaxOut - out);
if (outgen < 0) {
return 0;
}
*queueStart += input_used;
*queueLen -= input_used;
out += outgen;
t += (input_used / trackRate);
if (last) {
break;
}
}
for (int c = 0; c < mNumChannels; c++) {
if (mApplyTrackGains) {
mGains[c] = track->GetChannelGain(c);
}
else {
mGains[c] = 1.0;
}
}
MixBuffers(mNumChannels,
channelFlags,
mGains,
(samplePtr)mFloatBuffer,
mTemp,
out,
mInterleaved);
return out;
}
sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrack *track,
sampleCount *pos)
{
int slen = mMaxOut;
int c;
double t = *pos / track->GetRate();
double trackEndTime = track->GetEndTime();
double tEnd = trackEndTime > mT1 ? mT1 : trackEndTime;
//don't process if we're at the end of the selection or track.
if (t>=tEnd)
return 0;
//if we're about to approach the end of the track or selection, figure out how much we need to grab
if (t + slen/track->GetRate() > tEnd)
slen = (int)((tEnd - t) * track->GetRate() + 0.5);
if (slen > mMaxOut)
slen = mMaxOut;
track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen);
track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate);
for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
for(c=0; c<mNumChannels; c++)
if (mApplyTrackGains)
mGains[c] = track->GetChannelGain(c);
else
mGains[c] = 1.0;
MixBuffers(mNumChannels, channelFlags, mGains,
(samplePtr)mFloatBuffer, mTemp, slen, mInterleaved);
*pos += slen;
return slen;
}
sampleCount Mixer::Process(sampleCount maxToProcess)
{
if (mT >= mT1)
return 0;
int i, j;
sampleCount out;
sampleCount maxOut = 0;
int *channelFlags = new int[mNumChannels];
mMaxOut = maxToProcess;
Clear();
for(i=0; i<mNumInputTracks; i++) {
WaveTrack *track = mInputTrack[i];
for(j=0; j<mNumChannels; j++)
channelFlags[j] = 0;
if( mMixerSpec ) {
//ignore left and right when downmixing is not required
for( j = 0; j < mNumChannels; j++ )
channelFlags[ j ] = mMixerSpec->mMap[ i ][ j ] ? 1 : 0;
}
else {
switch(track->GetChannel()) {
case Track::MonoChannel:
default:
for(j=0; j<mNumChannels; j++)
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;
}
}
if (mTimeTrack ||
track->GetRate() != mRate)
out = MixVariableRates(channelFlags, track,
&mSamplePos[i], mSampleQueue[i],
&mQueueStart[i], &mQueueLen[i], mSRC[i]);
else
out = MixSameRate(channelFlags, track, &mSamplePos[i]);
if (out > maxOut)
maxOut = out;
}
out = mInterleaved ? maxOut * mNumChannels : maxOut;
for(int c=0; c<mNumBuffers; c++)
CopySamples(mTemp[c], floatSample, mBuffer[c], mFormat, out);
mT += (maxOut / mRate);
delete [] channelFlags;
return maxOut;
}
samplePtr Mixer::GetBuffer()
{
return mBuffer[0];
}
samplePtr Mixer::GetBuffer(int channel)
{
return mBuffer[channel];
}
double Mixer::MixGetCurrentTime()
{
return mT;
}
void Mixer::Restart()
{
int i;
mT = mT0;
for(i=0; i<mNumInputTracks; i++)
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mT0);
for(i=0; i<mNumInputTracks; i++) {
mQueueStart[i] = 0;
mQueueLen[i] = 0;
}
}
void Mixer::Reposition(double t)
{
int i;
mT = t;
if( mT < mT0 )
mT = mT0;
if( mT > mT1 )
mT = mT1;
for(i=0; i<mNumInputTracks; i++) {
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mT);
mQueueStart[i] = 0;
mQueueLen[i] = 0;
}
}
MixerSpec::MixerSpec( int numTracks, int maxNumChannels )
{
mNumTracks = mNumChannels = numTracks;
mMaxNumChannels = maxNumChannels;
if( mNumChannels > mMaxNumChannels )
mNumChannels = mMaxNumChannels;
Alloc();
for( int i = 0; i < mNumTracks; i++ )
for( int j = 0; j < mNumChannels; j++ )
mMap[ i ][ j ] = ( i == j );
}
MixerSpec::MixerSpec( const MixerSpec &mixerSpec )
{
mNumTracks = mixerSpec.mNumTracks;
mMaxNumChannels = mixerSpec.mMaxNumChannels;
mNumChannels = mixerSpec.mNumChannels;
Alloc();
for( int i = 0; i < mNumTracks; i++ )
for( int j = 0; j < mNumChannels; j++ )
mMap[ i ][ j ] = mixerSpec.mMap[ i ][ j ];
}
void MixerSpec::Alloc()
{
mMap = new bool*[ mNumTracks ];
for( int i = 0; i < mNumTracks; i++ )
mMap[ i ] = new bool[ mMaxNumChannels ];
}
MixerSpec::~MixerSpec()
{
Free();
}
void MixerSpec::Free()
{
for( int i = 0; i < mNumTracks; i++ )
delete[] mMap[ i ];
delete[] mMap;
}
bool MixerSpec::SetNumChannels( int newNumChannels )
{
if( mNumChannels == newNumChannels )
return true;
if( newNumChannels > mMaxNumChannels )
return false;
for( int i = 0; i < mNumTracks; i++ )
{
for( int j = newNumChannels; j < mNumChannels; j++ )
mMap[ i ][ j ] = false;
for( int j = mNumChannels; j < newNumChannels; j++ )
mMap[ i ][ j ] = false;
}
mNumChannels = newNumChannels;
return true;
}
MixerSpec& MixerSpec::operator=( const MixerSpec &mixerSpec )
{
Free();
mNumTracks = mixerSpec.mNumTracks;
mNumChannels = mixerSpec.mNumChannels;
mMaxNumChannels = mixerSpec.mMaxNumChannels;
Alloc();
for( int i = 0; i < mNumTracks; i++ )
for( int j = 0; j < mNumChannels; j++ )
mMap[ i ][ j ] = mixerSpec.mMap[ i ][ j ];
return *this;
}
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
// version control system. Please do not modify past this point.
//
// Local Variables:
// c-basic-offset: 3
// indent-tabs-mode: nil
// End:
//
// vim: et sts=3 sw=3
// arch-tag: d4e10e74-cdf9-46ac-b309-91b115d2a78f