tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_BufferedAudioReader.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14//==============================================================================
15BufferedAudioReader::BufferedAudioReader (std::unique_ptr<juce::AudioFormatReader> sourceReader, juce::TimeSliceThread& t)
16 : juce::AudioFormatReader (nullptr, "BufferedAudioReader"),
17 source (std::move (sourceReader)), thread (t)
18{
19 assert (source);
20
21 sampleRate = source->sampleRate;
22 bitsPerSample = source->bitsPerSample;
23 lengthInSamples = source->lengthInSamples;
24 numChannels = source->numChannels;
25 usesFloatingPointData = true;
26 metadataValues = source->metadataValues;
27
28 data.resize (choc::buffer::Size::create (numChannels, lengthInSamples));
29
30 // Read the first chunk on the calling thread in case it needs to be played back straight away
31 readNextChunk();
32}
33
34BufferedAudioReader::~BufferedAudioReader()
35{
36 thread.removeTimeSliceClient (this);
37}
38
39float BufferedAudioReader::getProportionComplete() const
40{
41 return source->lengthInSamples == 0 ? 0.0f
42 : validEnd.load (std::memory_order_relaxed) / static_cast<float> (sourceLength);
43}
44
45bool BufferedAudioReader::readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
46 juce::int64 startSampleInFile, int numSamples)
47{
48 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
49 startSampleInFile, numSamples, lengthInSamples);
50
51 if (numSamples <= 0)
52 return true;
53
54 using namespace choc::buffer;
55 const auto srcRange = FrameRange { static_cast<FrameCount> (startSampleInFile),
56 static_cast<FrameCount> (startSampleInFile + numSamples) };
57 const auto srcView = data.getFrameRange (srcRange);
58
59 const auto numChannelsToRead = std::min (srcView.getNumChannels(), static_cast<FrameCount> (numDestChannels));
60 const auto destSize = choc::buffer::Size::create (numChannelsToRead, numSamples);
61 const auto destView = createChannelArrayView (reinterpret_cast<float* const*> (destSamples),
62 destSize.getChannelRange().size(),
63 destSize.getFrameRange().size());
64
65 if (validEnd.load (std::memory_order_acquire) < srcRange.end)
66 {
67 destView.clear();
68 return false;
69 }
70
71 copyIntersectionAndClearOutside (destView, srcView);
72
73 return true;
74}
75
76int BufferedAudioReader::useTimeSlice()
77{
78 return readNextChunk() ? 0 : -1;
79}
80
81bool BufferedAudioReader::readNextChunk()
82{
83 if (! source)
84 return false;
85
86 using namespace choc::buffer;
87 const auto start = validEnd.load (std::memory_order_acquire);
88 const auto end = std::min (start + chunkSize, sourceLength);
89
90 if (readIntoBuffer ({ start, end }))
91 {
92 validEnd.store (end, std::memory_order_release);
93
94 if (const bool hasFinished = end == sourceLength; hasFinished)
95 {
96 source.reset();
97 return false;
98 }
99 }
100
101 return true;
102}
103
104bool BufferedAudioReader::readIntoBuffer (choc::buffer::FrameRange range)
105{
106 // This is just a quick way of converting the offset used by choc::buffer to a real pointer
107 // The alternative would be to create some storage for the pointers and add the offsets manually
108 auto destView = toAudioBuffer (data.getFrameRange (range));
109 return source->read (destView.getArrayOfWritePointers(), static_cast<int> (destView.getNumChannels()),
110 static_cast<juce::int64> (range.start), static_cast<int> (range.size()));
111}
112
113
114//==============================================================================
115BufferedAudioFileManager::BufferedAudioFileManager (Engine& e)
116 : engine (e)
117{
118}
119
120std::shared_ptr<BufferedAudioReader> BufferedAudioFileManager::get (juce::File f)
121{
122 TRACKTION_ASSERT_MESSAGE_THREAD
123 auto& item = cache[f];
124
125 if (! item)
126 {
127 if (auto reader = createReader (f))
128 {
129 item = std::make_shared<BufferedAudioReader> (std::move (reader), readThread);
130 readThread.addTimeSliceClient (item.get());
131 readThread.startThread (juce::Thread::Priority::normal);
132
133 if (! timer.isTimerRunning())
134 timer.startTimer (5'000);
135 }
136 }
137
138 return item;
139}
140
141void BufferedAudioFileManager::cleanUp()
142{
143 TRACKTION_ASSERT_MESSAGE_THREAD
144 std::erase_if (cache, [](auto& item)
145 {
146 return item.second.use_count() == 1;
147 });
148
149 if (cache.empty())
150 timer.stopTimer();
151}
152
153std::unique_ptr<juce::AudioFormatReader> BufferedAudioFileManager::createReader (juce::File f)
154{
155 if (auto mappedFileAndReader = AudioFileUtils::createMappedFileAndReaderFor (engine, f))
156 return std::make_unique<MemoryMappedFileReader> (std::move (mappedFileAndReader));
157
158 return {};
159}
160
161}} // namespace tracktion { inline namespace engine
assert
bool startThread()
void addTimeSliceClient(TimeSliceClient *clientToAdd, int millisecondsBeforeStarting=0)
void stopTimer() noexcept
bool isTimerRunning() const noexcept
T data(T... args)
T end(T... args)
T is_pointer_v
T min(T... args)
T move(T... args)
long long int64
juce::AudioBuffer< float > toAudioBuffer(choc::buffer::ChannelArrayView< float > view)
Creates a juce::AudioBuffer from a choc::buffer::BufferView.