Skip to content

Commit 5109c09

Browse files
committed
Add async file chunk reading to FileReader
1 parent 5c939c3 commit 5109c09

5 files changed

Lines changed: 127 additions & 45 deletions

File tree

Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ void KWIKFileSource::updateActiveRecord()
172172
samplePos=0;
173173
try
174174
{
175-
String path = "/recordings/" + String(availableDataSets[activeRecord]) + "/data";
175+
String path = "/recordings/" + String(availableDataSets[activeRecord.get()]) + "/data";
176176
dataSet = new DataSet(sourceFile->openDataSet(path.toUTF8()));
177177
}
178178
catch (FileIException error)
@@ -275,4 +275,4 @@ bool KWIKFileSource::isReady()
275275
}
276276
else
277277
return true;
278-
}
278+
}

Source/Processors/FileReader/FileReader.cpp

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
FileReader::FileReader()
3232
: GenericProcessor ("File Reader")
33+
, Thread ("filereader_Async_Reader")
3334
, timestamp (0)
3435
, currentSampleRate (0)
3536
, currentNumChannels (0)
@@ -39,6 +40,7 @@ FileReader::FileReader()
3940
, stopSample (0)
4041
, counter (0)
4142
, bufferCacheWindow (0)
43+
, m_shouldFillBackBuffer(false)
4244
{
4345
setProcessorType (PROCESSOR_TYPE_SOURCE);
4446

@@ -63,6 +65,8 @@ FileReader::FileReader()
6365

6466
FileReader::~FileReader()
6567
{
68+
signalThreadShouldExit();
69+
notify();
6670
}
6771

6872

@@ -180,13 +184,19 @@ bool FileReader::setFile (String fullpath)
180184

181185
static_cast<FileReaderEditor*> (getEditor())->populateRecordings (input);
182186
setActiveRecording (0);
187+
188+
m_samplesPerBuffer.set(float(BUFFER_SIZE) * (getDefaultSampleRate() / 44100.0f));
189+
190+
readAndFillBufferCache(bufferA); // pre-fill the front buffer with a blocking read
191+
192+
startThread(); // start async file reader thread
183193

184194
return true;
185195
}
186196

187197

188198
void FileReader::setActiveRecording (int index)
189-
{
199+
{
190200
input->setActiveRecord (index);
191201

192202
currentNumChannels = input->getActiveNumChannels();
@@ -205,7 +215,13 @@ void FileReader::setActiveRecording (int index)
205215

206216
static_cast<FileReaderEditor*> (getEditor())->setTotalTime (samplesToMilliseconds (currentNumSamples));
207217

208-
readBuffer.malloc (currentNumChannels * BUFFER_SIZE * BUFFER_WINDOW_CACHE_SIZE);
218+
bufferA.malloc (currentNumChannels * BUFFER_SIZE * BUFFER_WINDOW_CACHE_SIZE);
219+
bufferB.malloc (currentNumChannels * BUFFER_SIZE * BUFFER_WINDOW_CACHE_SIZE);
220+
221+
// set the backbuffer so that on the next call to process() we start with bufferA and buffer
222+
// cache window id = 0
223+
readBuffer = &bufferB;
224+
bufferCacheWindow = 0;
209225
}
210226

211227

@@ -233,47 +249,20 @@ void FileReader::updateSettings()
233249
void FileReader::process (AudioSampleBuffer& buffer)
234250
{
235251
const int samplesNeededPerBuffer = int (float (buffer.getNumSamples()) * (getDefaultSampleRate() / 44100.0f));
236-
const int samplesNeeded = samplesNeededPerBuffer * BUFFER_WINDOW_CACHE_SIZE;
252+
m_samplesPerBuffer.set(samplesNeededPerBuffer);
237253
// FIXME: needs to account for the fact that the ratio might not be an exact
238254
// integer value
239255

240-
int samplesRead = 0;
241-
242-
// if window id == 0, we need to read and cache BUFFER_WINDOW_CACHE_SIZE more buffer windows
256+
// if cache window id == 0, we need to read and cache BUFFER_WINDOW_CACHE_SIZE more buffer windows
243257
if (bufferCacheWindow == 0)
244258
{
245-
246-
// should only loop if reached end of file and resuming from start
247-
while (samplesRead < samplesNeeded)
248-
{
249-
int samplesToRead = samplesNeeded - samplesRead;
250-
251-
// if reached end of file stream
252-
if ( (currentSample + samplesToRead) > stopSample)
253-
{
254-
samplesToRead = stopSample - currentSample;
255-
if (samplesToRead > 0)
256-
input->readData (readBuffer + samplesRead * currentNumChannels, samplesToRead);
257-
258-
// reset stream to beginning
259-
input->seekTo (startSample);
260-
currentSample = startSample;
261-
}
262-
else // else read the block needed
263-
{
264-
input->readData (readBuffer + samplesRead * currentNumChannels, samplesToRead);
265-
266-
currentSample += samplesToRead;
267-
}
268-
269-
samplesRead += samplesToRead;
270-
}
259+
switchBuffer();
271260
}
272261

273262
for (int i = 0; i < currentNumChannels; ++i)
274263
{
275264
// offset readBuffer index by current cache window count * buffer window size * num channels
276-
input->processChannelData (readBuffer + (samplesNeededPerBuffer * currentNumChannels * bufferCacheWindow),
265+
input->processChannelData (*readBuffer + (samplesNeededPerBuffer * currentNumChannels * bufferCacheWindow),
277266
buffer.getWritePointer (i, 0),
278267
i,
279268
samplesNeededPerBuffer);
@@ -325,3 +314,73 @@ int64 FileReader::millisecondsToSamples (unsigned int ms) const
325314
{
326315
return (int64) (currentSampleRate * float (ms) / 1000.f);
327316
}
317+
318+
void FileReader::switchBuffer()
319+
{
320+
if (readBuffer == &bufferA)
321+
readBuffer = &bufferB;
322+
else
323+
readBuffer = &bufferA;
324+
325+
m_shouldFillBackBuffer.set(true);
326+
notify();
327+
}
328+
329+
HeapBlock<int16>* FileReader::getFrontBuffer()
330+
{
331+
return readBuffer;
332+
}
333+
334+
HeapBlock<int16>* FileReader::getBackBuffer()
335+
{
336+
if (readBuffer == &bufferA) return &bufferB;
337+
338+
return &bufferA;
339+
}
340+
341+
void FileReader::run()
342+
{
343+
while (!threadShouldExit())
344+
{
345+
if (m_shouldFillBackBuffer.compareAndSetBool(false, true))
346+
{
347+
readAndFillBufferCache(*getBackBuffer());
348+
}
349+
350+
wait(30);
351+
}
352+
}
353+
354+
void FileReader::readAndFillBufferCache(HeapBlock<int16> &cacheBuffer)
355+
{
356+
const int samplesNeededPerBuffer = m_samplesPerBuffer.get();
357+
const int samplesNeeded = samplesNeededPerBuffer * BUFFER_WINDOW_CACHE_SIZE;
358+
359+
int samplesRead = 0;
360+
361+
// should only loop if reached end of file and resuming from start
362+
while (samplesRead < samplesNeeded)
363+
{
364+
int samplesToRead = samplesNeeded - samplesRead;
365+
366+
// if reached end of file stream
367+
if ( (currentSample + samplesToRead) > stopSample)
368+
{
369+
samplesToRead = stopSample - currentSample;
370+
if (samplesToRead > 0)
371+
input->readData (cacheBuffer + samplesRead * currentNumChannels, samplesToRead);
372+
373+
// reset stream to beginning
374+
input->seekTo (startSample);
375+
currentSample = startSample;
376+
}
377+
else // else read the block needed
378+
{
379+
input->readData (cacheBuffer + samplesRead * currentNumChannels, samplesToRead);
380+
381+
currentSample += samplesToRead;
382+
}
383+
384+
samplesRead += samplesToRead;
385+
}
386+
}

Source/Processors/FileReader/FileReader.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@
3232
#include "FileSource.h"
3333

3434
#define BUFFER_SIZE 1024
35-
#define BUFFER_WINDOW_CACHE_SIZE 3
35+
#define BUFFER_WINDOW_CACHE_SIZE 10
3636

3737

3838
/**
3939
Reads data from a file.
4040
4141
@see GenericProcessor
4242
*/
43-
class FileReader : public GenericProcessor
43+
class FileReader : public GenericProcessor,
44+
private Thread
4445
{
4546
public:
4647
FileReader();
@@ -92,9 +93,30 @@ class FileReader : public GenericProcessor
9293

9394
ScopedPointer<FileSource> input;
9495

95-
HeapBlock<int16> readBuffer;
96+
HeapBlock<int16> * readBuffer; // Ptr to the current "front" buffer
97+
HeapBlock<int16> bufferA;
98+
HeapBlock<int16> bufferB;
9699

97100
HashMap<String, int> supportedExtensions;
101+
102+
Atomic<int> m_shouldFillBackBuffer;
103+
Atomic<int> m_samplesPerBuffer;
104+
105+
/** Swaps the backbuffer to the front and flags the background reader
106+
thread to update the new backbuffer */
107+
void switchBuffer();
108+
109+
HeapBlock<int16>* getFrontBuffer();
110+
HeapBlock<int16>* getBackBuffer();
111+
112+
/** Executes the background thread task */
113+
void run() override;
114+
115+
/** Reads a chunk of the file that fills an entire buffer cache.
116+
117+
This method will read into the buffer that passed in by the param
118+
*/
119+
void readAndFillBufferCache(HeapBlock<int16> &cacheBuffer);
98120

99121

100122
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FileReader);

Source/Processors/FileReader/FileSource.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ int FileSource::getRecordNumChannels (int index) const
5858

5959
int FileSource::getActiveNumChannels() const
6060
{
61-
return getRecordNumChannels (activeRecord);
61+
return getRecordNumChannels (activeRecord.get());
6262
}
6363

6464

@@ -70,7 +70,7 @@ float FileSource::getRecordSampleRate (int index) const
7070

7171
float FileSource::getActiveSampleRate() const
7272
{
73-
return getRecordSampleRate (activeRecord);
73+
return getRecordSampleRate (activeRecord.get());
7474
}
7575

7676

@@ -82,13 +82,13 @@ int64 FileSource::getRecordNumSamples (int index) const
8282

8383
int64 FileSource::getActiveNumSamples() const
8484
{
85-
return getRecordNumSamples (activeRecord);
85+
return getRecordNumSamples (activeRecord.get());
8686
}
8787

8888

8989
int FileSource::getActiveRecord() const
9090
{
91-
return activeRecord;
91+
return activeRecord.get();
9292
}
9393

9494

@@ -100,13 +100,14 @@ RecordedChannelInfo FileSource::getChannelInfo (int recordIndex, int channel) co
100100

101101
RecordedChannelInfo FileSource::getChannelInfo (int channel) const
102102
{
103-
return getChannelInfo (activeRecord, channel);
103+
return getChannelInfo (activeRecord.get(), channel);
104104
}
105105

106106

107107
void FileSource::setActiveRecord (int index)
108108
{
109-
activeRecord = index;
109+
// activeRecord = index;
110+
activeRecord.set(index);
110111
updateActiveRecord();
111112
}
112113

Source/Processors/FileReader/FileSource.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class PLUGIN_API FileSource
8181

8282
bool fileOpened;
8383
int numRecords;
84-
int activeRecord;
84+
Atomic<int> activeRecord; // atomic to protect against threaded data race in FileReader
8585
String filename;
8686

8787

0 commit comments

Comments
 (0)