1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-11-18 07:04:07 +01:00

Make AudioIOBufferHelper.h cache friendly.

Improves performance of project loading substantively.

Signed-off-by: Emily Mabrey <emabrey@tenacityaudio.org>
Helped-by: Alex Disibio <alexdisibio@gmail.com>
This commit is contained in:
Emily Mabrey
2021-07-31 16:51:24 -04:00
parent 8c4278cc64
commit 15c4f546f3
2 changed files with 145 additions and 145 deletions

View File

@@ -3843,149 +3843,144 @@ bool AudioIoCallback::FillOutputBuffers(
return true; return true;
} }
// ------ MEMORY ALLOCATION ---------------------- //Real time process section
std::unique_ptr<AudioIOBufferHelper> bufHelper = std::make_unique<AudioIOBufferHelper>(numPlaybackChannels, framesPerBuffer);
// ------ End of MEMORY ALLOCATION ---------------
auto & em = RealtimeEffectManager::Get();
em.RealtimeProcessStart();
bool selected = false;
int group = 0;
int chanCnt = 0;
// Choose a common size to take from all ring buffers
const auto toGet =
std::min<size_t>(framesPerBuffer, GetCommonlyReadyPlayback());
// The drop and dropQuickly booleans are so named for historical reasons.
// JKC: The original code attempted to be faster by doing nothing on silenced audio.
// This, IMHO, is 'premature optimisation'. Instead clearer and cleaner code would
// simply use a gain of 0.0 for silent audio and go on through to the stage of
// applying that 0.0 gain to the data mixed into the buffer.
// Then (and only then) we would have if needed fast paths for:
// - Applying a uniform gain of 0.0.
// - Applying a uniform gain of 1.0.
// - Applying some other uniform gain.
// - Applying a linearly interpolated gain.
// I would expect us not to need the fast paths, since linearly interpolated gain
// is very cheap to process.
bool drop = false; // Track should become silent.
bool dropQuickly = false; // Track has already been faded to silence.
for (unsigned t = 0; t < numPlaybackTracks; t++)
{ {
WaveTrack *vt = mPlaybackTracks[t].get(); std::unique_ptr<AudioIOBufferHelper> bufHelper = std::make_unique<AudioIOBufferHelper>(numPlaybackChannels, framesPerBuffer);
bufHelper.get()->chans[chanCnt] = vt; auto& em = RealtimeEffectManager::Get();
em.RealtimeProcessStart();
// TODO: more-than-two-channels bool selected = false;
auto nextTrack = int group = 0;
t + 1 < numPlaybackTracks int chanCnt = 0;
? mPlaybackTracks[t + 1].get()
: nullptr;
// First and last channel in this group (for example left and right // Choose a common size to take from all ring buffers
// channels of stereo). const auto toGet = std::min<size_t>(framesPerBuffer, GetCommonlyReadyPlayback());
bool firstChannel = vt->IsLeader();
bool lastChannel = !nextTrack || nextTrack->IsLeader();
if ( firstChannel ) // The drop and dropQuickly booleans are so named for historical reasons.
{ // JKC: The original code attempted to be faster by doing nothing on silenced audio.
selected = vt->GetSelected(); // This, IMHO, is 'premature optimisation'. Instead clearer and cleaner code would
// IF mono THEN clear 'the other' channel. // simply use a gain of 0.0 for silent audio and go on through to the stage of
if ( lastChannel && (numPlaybackChannels>1)) { // applying that 0.0 gain to the data mixed into the buffer.
// TODO: more-than-two-channels // Then (and only then) we would have if needed fast paths for:
memset(bufHelper.get()->tempBufs[1], 0, framesPerBuffer * sizeof(float)); // - Applying a uniform gain of 0.0.
} // - Applying a uniform gain of 1.0.
drop = TrackShouldBeSilent( *vt ); // - Applying some other uniform gain.
dropQuickly = drop; // - Applying a linearly interpolated gain.
} // I would expect us not to need the fast paths, since linearly interpolated gain
// is very cheap to process.
if( mbMicroFades ) bool drop = false; // Track should become silent.
dropQuickly = dropQuickly && TrackHasBeenFadedOut( *vt ); bool dropQuickly = false; // Track has already been faded to silence.
for (unsigned t = 0; t < numPlaybackTracks; t++) {
decltype(framesPerBuffer) len = 0; WaveTrack* vt = mPlaybackTracks[t].get();
bufHelper.get()->chans[chanCnt] = vt;
if (dropQuickly) // TODO: more-than-two-channels
{ auto nextTrack =
len = mPlaybackBuffers[t]->Discard(toGet); t + 1 < numPlaybackTracks
// keep going here. ? mPlaybackTracks[t + 1].get()
// we may still need to issue a paComplete. : nullptr;
}
else
{
len = mPlaybackBuffers[t]->Get((samplePtr)bufHelper.get()->tempBufs[chanCnt],
floatSample,
toGet);
// wxASSERT( len == toGet );
if (len < framesPerBuffer)
// This used to happen normally at the end of non-looping
// plays, but it can also be an anomalous case where the
// supply from FillBuffers fails to keep up with the
// real-time demand in this thread (see bug 1932). We
// must supply something to the sound card, so pad it with
// zeroes and not random garbage.
memset((void*)&bufHelper.get()->tempBufs[chanCnt][len], 0,
(framesPerBuffer - len) * sizeof(float));
chanCnt++;
}
// PRL: Bug1104: // First and last channel in this group (for example left and right
// There can be a difference of len in different loop passes if one channel // channels of stereo).
// of a stereo track ends before the other! Take a max! bool firstChannel = vt->IsLeader();
bool lastChannel = !nextTrack || nextTrack->IsLeader();
// PRL: More recent rewrites of FillBuffers should guarantee a if (firstChannel) {
// padding out of the ring buffers so that equal lengths are selected = vt->GetSelected();
// available, so maxLen ought to increase from 0 only once // IF mono THEN clear 'the other' channel.
mMaxFramesOutput = std::max(mMaxFramesOutput, len); if (lastChannel && (numPlaybackChannels > 1)) {
// TODO: more-than-two-channels
memset(bufHelper.get()->tempBufs[1], 0, framesPerBuffer * sizeof(float));
}
drop = TrackShouldBeSilent(*vt);
dropQuickly = drop;
}
if ( !lastChannel ) if (mbMicroFades)
continue; dropQuickly = dropQuickly && TrackHasBeenFadedOut(*vt);
// Last channel of a track seen now decltype(framesPerBuffer) len = 0;
len = mMaxFramesOutput;
if( !dropQuickly && selected ) if (dropQuickly) {
len = em.RealtimeProcess(group, chanCnt, bufHelper.get()->tempBufs, len); len = mPlaybackBuffers[t]->Discard(toGet);
group++; // keep going here.
// we may still need to issue a paComplete.
} else {
len = mPlaybackBuffers[t]->Get((samplePtr)bufHelper.get()->tempBufs[chanCnt],
floatSample,
toGet);
// wxASSERT( len == toGet );
if (len < framesPerBuffer)
// This used to happen normally at the end of non-looping
// plays, but it can also be an anomalous case where the
// supply from FillBuffers fails to keep up with the
// real-time demand in this thread (see bug 1932). We
// must supply something to the sound card, so pad it with
// zeroes and not random garbage.
memset((void*)&bufHelper.get()->tempBufs[chanCnt][len], 0,
(framesPerBuffer - len) * sizeof(float));
chanCnt++;
}
CallbackCheckCompletion(mCallbackReturn, len); // PRL: Bug1104:
if (dropQuickly) // no samples to process, they've been discarded // There can be a difference of len in different loop passes if one channel
continue; // of a stereo track ends before the other! Take a max!
// Our channels aren't silent. We need to pass their data on. // PRL: More recent rewrites of FillBuffers should guarantee a
// // padding out of the ring buffers so that equal lengths are
// Note that there are two kinds of channel count. // available, so maxLen ought to increase from 0 only once
// c and chanCnt are counting channels in the Tracks. mMaxFramesOutput = std::max(mMaxFramesOutput, len);
// chan (and numPlayBackChannels) is counting output channels on the device.
// chan = 0 is left channel
// chan = 1 is right channel.
//
// Each channel in the tracks can output to more than one channel on the device.
// For example mono channels output to both left and right output channels.
if (len > 0) for (int c = 0; c < chanCnt; c++)
{
vt = bufHelper.get()->chans[c];
if (vt->GetChannelIgnoringPan() == Track::LeftChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel ) if (!lastChannel)
AddToOutputChannel( 0, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt); continue;
if (vt->GetChannelIgnoringPan() == Track::RightChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel ) // Last channel of a track seen now
AddToOutputChannel( 1, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt); len = mMaxFramesOutput;
}
chanCnt = 0; if (!dropQuickly && selected)
len = em.RealtimeProcess(group, chanCnt, bufHelper.get()->tempBufs, len);
group++;
CallbackCheckCompletion(mCallbackReturn, len);
if (dropQuickly) // no samples to process, they've been discarded
continue;
// Our channels aren't silent. We need to pass their data on.
//
// Note that there are two kinds of channel count.
// c and chanCnt are counting channels in the Tracks.
// chan (and numPlayBackChannels) is counting output channels on the device.
// chan = 0 is left channel
// chan = 1 is right channel.
//
// Each channel in the tracks can output to more than one channel on the device.
// For example mono channels output to both left and right output channels.
if (len > 0) for (int c = 0; c < chanCnt; c++) {
vt = bufHelper.get()->chans[c];
if (vt->GetChannelIgnoringPan() == Track::LeftChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel)
AddToOutputChannel(0, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt);
if (vt->GetChannelIgnoringPan() == Track::RightChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel)
AddToOutputChannel(1, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt);
}
chanCnt = 0;
}
// Poke: If there are no playback tracks, then the earlier check
// about the time indicator being past the end won't happen;
// do it here instead (but not if looping or scrubbing)
if (numPlaybackTracks == 0)
CallbackCheckCompletion(mCallbackReturn, 0);
// wxASSERT( maxLen == toGet );
em.RealtimeProcessEnd();
delete bufHelper.release();
} }
// Poke: If there are no playback tracks, then the earlier check
// about the time indicator being past the end won't happen;
// do it here instead (but not if looping or scrubbing)
if (numPlaybackTracks == 0)
CallbackCheckCompletion(mCallbackReturn, 0);
// wxASSERT( maxLen == toGet );
em.RealtimeProcessEnd();
mLastPlaybackTimeMillis = ::wxGetUTCTimeMillis(); mLastPlaybackTimeMillis = ::wxGetUTCTimeMillis();
ClampBuffer( outputFloats, framesPerBuffer*numPlaybackChannels ); ClampBuffer( outputFloats, framesPerBuffer*numPlaybackChannels );

View File

@@ -9,34 +9,39 @@ class AudioIOBufferHelper
private: private:
unsigned int numPlaybackChannels; unsigned int numPlaybackChannels;
unsigned long framesPerBuffer; unsigned long framesPerBuffer;
public: public:
WaveTrack** chans; WaveTrack** chans;
float** tempBufs; float** tempBufs;
AudioIOBufferHelper(const unsigned int numPlaybackChannels, const unsigned long framesPerBuffer) { AudioIOBufferHelper(const unsigned int numPlaybackChannels, const unsigned long framesPerBuffer) {
this->numPlaybackChannels = numPlaybackChannels; this->numPlaybackChannels = numPlaybackChannels;
this->framesPerBuffer = framesPerBuffer; this->framesPerBuffer = framesPerBuffer;
this->chans = safenew WaveTrack * [numPlaybackChannels]; this->chans = safenew WaveTrack * [numPlaybackChannels];
this->tempBufs = safenew float* [numPlaybackChannels]; this->tempBufs = safenew float* [numPlaybackChannels];
for (unsigned int c = 0; c < numPlaybackChannels; c++) { tempBufs[0] = safenew float[(size_t)numPlaybackChannels * framesPerBuffer];
tempBufs[c] = safenew float[framesPerBuffer];
} for (unsigned int c = 1; c < numPlaybackChannels; c++) {
tempBufs[c] = tempBufs[c - 1] + framesPerBuffer;
} }
}
~AudioIOBufferHelper() { ~AudioIOBufferHelper() {
for (unsigned int c = 0; c < numPlaybackChannels; c++) {
delete[] tempBufs[c];
}
delete[] tempBufs; delete[] tempBufs[0];
delete[] chans; delete[] tempBufs;
}
tempBufs = nullptr;
delete[] chans;
chans = nullptr;
}
}; };
#endif #endif