mirror of
https://github.com/cookiengineer/audacity
synced 2025-11-18 15:13:56 +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:
245
src/AudioIO.cpp
245
src/AudioIO.cpp
@@ -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++) {
|
||||||
|
WaveTrack* vt = mPlaybackTracks[t].get();
|
||||||
|
bufHelper.get()->chans[chanCnt] = vt;
|
||||||
|
|
||||||
decltype(framesPerBuffer) len = 0;
|
// TODO: more-than-two-channels
|
||||||
|
auto nextTrack =
|
||||||
|
t + 1 < numPlaybackTracks
|
||||||
|
? mPlaybackTracks[t + 1].get()
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
if (dropQuickly)
|
// First and last channel in this group (for example left and right
|
||||||
{
|
// channels of stereo).
|
||||||
len = mPlaybackBuffers[t]->Discard(toGet);
|
bool firstChannel = vt->IsLeader();
|
||||||
// keep going here.
|
bool lastChannel = !nextTrack || nextTrack->IsLeader();
|
||||||
// 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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PRL: Bug1104:
|
if (firstChannel) {
|
||||||
// There can be a difference of len in different loop passes if one channel
|
selected = vt->GetSelected();
|
||||||
// of a stereo track ends before the other! Take a max!
|
// IF mono THEN clear 'the other' channel.
|
||||||
|
if (lastChannel && (numPlaybackChannels > 1)) {
|
||||||
|
// TODO: more-than-two-channels
|
||||||
|
memset(bufHelper.get()->tempBufs[1], 0, framesPerBuffer * sizeof(float));
|
||||||
|
}
|
||||||
|
drop = TrackShouldBeSilent(*vt);
|
||||||
|
dropQuickly = drop;
|
||||||
|
}
|
||||||
|
|
||||||
// PRL: More recent rewrites of FillBuffers should guarantee a
|
if (mbMicroFades)
|
||||||
// padding out of the ring buffers so that equal lengths are
|
dropQuickly = dropQuickly && TrackHasBeenFadedOut(*vt);
|
||||||
// available, so maxLen ought to increase from 0 only once
|
|
||||||
mMaxFramesOutput = std::max(mMaxFramesOutput, len);
|
|
||||||
|
|
||||||
if ( !lastChannel )
|
decltype(framesPerBuffer) len = 0;
|
||||||
continue;
|
|
||||||
|
|
||||||
// Last channel of a track seen now
|
if (dropQuickly) {
|
||||||
len = mMaxFramesOutput;
|
len = mPlaybackBuffers[t]->Discard(toGet);
|
||||||
|
// 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++;
|
||||||
|
}
|
||||||
|
|
||||||
if( !dropQuickly && selected )
|
// PRL: Bug1104:
|
||||||
len = em.RealtimeProcess(group, chanCnt, bufHelper.get()->tempBufs, len);
|
// There can be a difference of len in different loop passes if one channel
|
||||||
group++;
|
// of a stereo track ends before the other! Take a max!
|
||||||
|
|
||||||
CallbackCheckCompletion(mCallbackReturn, len);
|
// PRL: More recent rewrites of FillBuffers should guarantee a
|
||||||
if (dropQuickly) // no samples to process, they've been discarded
|
// padding out of the ring buffers so that equal lengths are
|
||||||
continue;
|
// available, so maxLen ought to increase from 0 only once
|
||||||
|
mMaxFramesOutput = std::max(mMaxFramesOutput, len);
|
||||||
|
|
||||||
// Our channels aren't silent. We need to pass their data on.
|
if (!lastChannel)
|
||||||
//
|
continue;
|
||||||
// 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 )
|
// Last channel of a track seen now
|
||||||
AddToOutputChannel( 0, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt);
|
len = mMaxFramesOutput;
|
||||||
|
|
||||||
if (vt->GetChannelIgnoringPan() == Track::RightChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel )
|
if (!dropQuickly && selected)
|
||||||
AddToOutputChannel( 1, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt);
|
len = em.RealtimeProcess(group, chanCnt, bufHelper.get()->tempBufs, len);
|
||||||
}
|
group++;
|
||||||
|
|
||||||
chanCnt = 0;
|
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 );
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user