Skip to content

Commit b146d73

Browse files
committed
File Reader: precompute per-callback sample counts using fractional carry
1 parent 43c94b0 commit b146d73

2 files changed

Lines changed: 98 additions & 15 deletions

File tree

Source/Processors/FileReader/FileReader.cpp

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "../../Audio/AudioComponent.h"
3030
#include "../PluginManager/PluginManager.h"
3131
#include "BinaryFileSource/BinaryFileSource.h"
32+
#include <cmath>
3233
#include <stdio.h>
3334

3435
#include "../Settings/DataStream.h"
@@ -417,6 +418,43 @@ float FileReader::getDefaultSampleRate() const
417418
return 44100.0;
418419
}
419420

421+
void FileReader::resetBufferSchedule()
422+
{
423+
if (m_sysSampleRate > 0.0f && currentSampleRate > 0.0f && m_bufferSize > 0)
424+
m_samplesPerCallbackExact = double (m_bufferSize) * (double (getDefaultSampleRate()) / double (m_sysSampleRate));
425+
else
426+
m_samplesPerCallbackExact = 0.0;
427+
428+
m_samplesPerCallbackRemainder = 0.0;
429+
bufferAInfo = BufferWindowInfo {};
430+
bufferBInfo = BufferWindowInfo {};
431+
}
432+
433+
void FileReader::updateBufferWindowInfo (BufferWindowInfo& info)
434+
{
435+
info = BufferWindowInfo {};
436+
437+
if (m_samplesPerCallbackExact <= 0.0 || stopSample <= startSample)
438+
return;
439+
440+
constexpr double roundingEpsilon = 1.0e-9;
441+
double remainder = m_samplesPerCallbackRemainder;
442+
443+
for (int windowIndex = 0; windowIndex < BUFFER_WINDOW_CACHE_SIZE; ++windowIndex)
444+
{
445+
info.sampleOffsets[(size_t) windowIndex] = info.totalSamples;
446+
447+
const double exactSamples = m_samplesPerCallbackExact + remainder;
448+
const int scheduledSamples = jmax (0, int (std::floor (exactSamples + roundingEpsilon)));
449+
450+
info.sampleCounts[(size_t) windowIndex] = scheduledSamples;
451+
info.totalSamples += scheduledSamples;
452+
remainder = exactSamples - double (scheduledSamples);
453+
}
454+
455+
m_samplesPerCallbackRemainder = remainder;
456+
}
457+
420458
bool FileReader::startAcquisition()
421459
{
422460
if (! isEnabled)
@@ -426,8 +464,9 @@ bool FileReader::startAcquisition()
426464

427465
{
428466
const ScopedLock sl(bufferLock);
429-
readAndFillBufferCache (bufferA);
467+
readAndFillBufferCache (bufferA, bufferAInfo);
430468
readBuffer = &bufferA;
469+
readBufferInfo = &bufferAInfo;
431470
bufferCacheWindow = 0;
432471
needsBufferReset.set (false);
433472
m_shouldFillBackBuffer.set (true);
@@ -470,6 +509,7 @@ void FileReader::setCurrentSample(int64 sampleNumber)
470509

471510
currentSample = sampleNumber;
472511
playbackSamplePos.set(sampleNumber);
512+
resetBufferSchedule();
473513

474514
// Reset file position
475515
input->seekTo(sampleNumber);
@@ -478,7 +518,8 @@ void FileReader::setCurrentSample(int64 sampleNumber)
478518
{
479519
// Get the current back buffer without switching
480520
HeapBlock<float>* backBuffer = getBackBuffer();
481-
readAndFillBufferCache(*backBuffer);
521+
BufferWindowInfo* backBufferInfo = getBackBufferInfo();
522+
readAndFillBufferCache(*backBuffer, *backBufferInfo);
482523
}
483524

484525
if (wasThreadRunning)
@@ -496,6 +537,7 @@ void FileReader::setCurrentSample(int64 sampleNumber)
496537
bufferCacheWindow.set(0);
497538
needsBufferReset.set(false);
498539
readBuffer = &bufferA;
540+
readBufferInfo = &bufferAInfo;
499541
m_shouldFillBackBuffer.set(false);
500542
}
501543

@@ -643,7 +685,7 @@ void FileReader::updateSettings()
643685
if (m_bufferSize == 0)
644686
m_bufferSize = 1024;
645687

646-
m_samplesPerBuffer.set (m_bufferSize * (getDefaultSampleRate() / m_sysSampleRate));
688+
resetBufferSchedule();
647689

648690
bufferA.malloc (currentNumChannels * m_bufferSize * BUFFER_WINDOW_CACHE_SIZE);
649691
bufferB.malloc (currentNumChannels * m_bufferSize * BUFFER_WINDOW_CACHE_SIZE);
@@ -653,6 +695,7 @@ void FileReader::updateSettings()
653695
currentSample = startSample;
654696

655697
readBuffer = &bufferA;
698+
readBufferInfo = &bufferAInfo;
656699
bufferCacheWindow = 0;
657700
m_shouldFillBackBuffer.set (false);
658701

@@ -672,7 +715,7 @@ void FileReader::checkAudioDevice()
672715
m_bufferSize = ads.bufferSize;
673716
if (m_bufferSize == 0)
674717
m_bufferSize = 1024;
675-
m_samplesPerBuffer.set (m_bufferSize * (getDefaultSampleRate() / m_sysSampleRate));
718+
resetBufferSchedule();
676719

677720
bufferA.malloc (currentNumChannels * m_bufferSize * BUFFER_WINDOW_CACHE_SIZE);
678721
bufferB.malloc (currentNumChannels * m_bufferSize * BUFFER_WINDOW_CACHE_SIZE);
@@ -682,6 +725,7 @@ void FileReader::checkAudioDevice()
682725
currentSample = startSample;
683726

684727
readBuffer = &bufferA;
728+
readBufferInfo = &bufferAInfo;
685729
bufferCacheWindow = 0;
686730
needsBufferReset.set (false);
687731
m_shouldFillBackBuffer.set (false);
@@ -741,18 +785,22 @@ void FileReader::process (AudioBuffer<float>& buffer)
741785
bufferCacheWindow.set(0);
742786
}
743787

744-
int samplesNeededPerBuffer = int(float(buffer.getNumSamples()) * (getDefaultSampleRate() / m_sysSampleRate));
745-
m_samplesPerBuffer.set(samplesNeededPerBuffer);
746-
747788
// Handle buffer switching
748789
if (firstProcess && bufferCacheWindow.get() == 0)
749790
{
750791
switchBuffer();
751792
}
752793

794+
BufferWindowInfo* frontBufferInfo = readBufferInfo;
795+
if (frontBufferInfo == nullptr)
796+
return;
797+
798+
const int windowIndex = bufferCacheWindow.get();
799+
const int samplesNeededPerBuffer = frontBufferInfo->sampleCounts[(size_t) windowIndex];
800+
const int sampleOffset = frontBufferInfo->sampleOffsets[(size_t) windowIndex];
801+
753802
// Get current buffer position
754-
const float* tempReadBuffer = readBuffer->getData() +
755-
(samplesNeededPerBuffer * currentNumChannels * bufferCacheWindow.get());
803+
const float* tempReadBuffer = readBuffer->getData() + (sampleOffset * currentNumChannels);
756804

757805
// Copy data to output buffer
758806
for (int ch = 0; ch < currentNumChannels; ++ch)
@@ -827,9 +875,15 @@ void FileReader::switchBuffer()
827875
const ScopedLock sl(bufferLock);
828876

829877
if (readBuffer == &bufferA)
878+
{
830879
readBuffer = &bufferB;
880+
readBufferInfo = &bufferBInfo;
881+
}
831882
else
883+
{
832884
readBuffer = &bufferA;
885+
readBufferInfo = &bufferAInfo;
886+
}
833887

834888
m_shouldFillBackBuffer.set(true);
835889
notify();
@@ -848,23 +902,32 @@ HeapBlock<float>* FileReader::getBackBuffer()
848902
return &bufferA;
849903
}
850904

905+
FileReader::BufferWindowInfo* FileReader::getBackBufferInfo()
906+
{
907+
if (readBuffer == &bufferA)
908+
return &bufferBInfo;
909+
910+
return &bufferAInfo;
911+
}
912+
851913
void FileReader::run()
852914
{
853915
while (! threadShouldExit())
854916
{
855917
if (m_shouldFillBackBuffer.compareAndSetBool (false, true))
856918
{
857-
readAndFillBufferCache (*getBackBuffer());
919+
readAndFillBufferCache (*getBackBuffer(), *getBackBufferInfo());
858920
}
859921

860922
wait (30);
861923
}
862924
}
863925

864-
void FileReader::readAndFillBufferCache (HeapBlock<float>& cacheBuffer)
926+
void FileReader::readAndFillBufferCache (HeapBlock<float>& cacheBuffer, BufferWindowInfo& bufferInfo)
865927
{
866-
const int samplesNeededPerBuffer = m_samplesPerBuffer.get();
867-
const int samplesNeeded = samplesNeededPerBuffer * BUFFER_WINDOW_CACHE_SIZE;
928+
updateBufferWindowInfo (bufferInfo);
929+
930+
const int samplesNeeded = bufferInfo.totalSamples;
868931
if (samplesNeeded <= 0 || stopSample <= startSample)
869932
return;
870933

Source/Processors/FileReader/FileReader.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
#include "../../Utils/Utils.h"
3333

34+
#include <array>
35+
3436
#define BUFFER_WINDOW_CACHE_SIZE 10
3537

3638
class ScrubberInterface;
@@ -186,9 +188,22 @@ class FileReader : public GenericProcessor,
186188
void loadCustomParametersFromXml (XmlElement*) override;
187189

188190
private:
191+
struct BufferWindowInfo
192+
{
193+
std::array<int, BUFFER_WINDOW_CACHE_SIZE> sampleCounts {};
194+
std::array<int, BUFFER_WINDOW_CACHE_SIZE> sampleOffsets {};
195+
int totalSamples = 0;
196+
};
197+
189198
/** Checks for changes in the audio device settings */
190199
void checkAudioDevice();
191200

201+
/** Resets the fractional block conversion state after a seek or format change. */
202+
void resetBufferSchedule();
203+
204+
/** Precomputes how many file samples each cached callback window should consume. */
205+
void updateBufferWindowInfo (BufferWindowInfo& info);
206+
192207
/** Generates any events found within the current continuous buffer interval */
193208
void addEventsInRange (int64 start, int64 stop);
194209

@@ -215,23 +230,28 @@ class FileReader : public GenericProcessor,
215230
HeapBlock<float>* readBuffer;
216231
HeapBlock<float> bufferA;
217232
HeapBlock<float> bufferB;
233+
BufferWindowInfo bufferAInfo;
234+
BufferWindowInfo bufferBInfo;
235+
BufferWindowInfo* readBufferInfo = nullptr;
218236

219237
HashMap<String, int> supportedExtensions;
220238

221239
Atomic<bool> m_shouldFillBackBuffer;
222-
Atomic<int> m_samplesPerBuffer;
223240

224241
unsigned int m_bufferSize;
225242
float m_sysSampleRate;
243+
double m_samplesPerCallbackExact = 0.0;
244+
double m_samplesPerCallbackRemainder = 0.0;
226245

227246
HeapBlock<float>* getFrontBuffer();
228247
HeapBlock<float>* getBackBuffer();
248+
BufferWindowInfo* getBackBufferInfo();
229249

230250
/** Executes the background thread task */
231251
void run() override;
232252

233253
/** Reads a chunk of the file that fills an entire buffer cache. */
234-
void readAndFillBufferCache (HeapBlock<float>& cacheBuffer);
254+
void readAndFillBufferCache (HeapBlock<float>& cacheBuffer, BufferWindowInfo& bufferInfo);
235255

236256
/** Returns the number of included file sources */
237257
int getNumBuiltInFileSources() const { return 1; }

0 commit comments

Comments
 (0)