1 #include "engine/cachingreader/cachingreaderchunk.h"
2
3 #include <QtDebug>
4
5 #include "sources/audiosourcestereoproxy.h"
6 #include "engine/engine.h"
7 #include "util/math.h"
8 #include "util/sample.h"
9 #include "util/logger.h"
10
11
12 namespace {
13
14 mixxx::Logger kLogger("CachingReaderChunk");
15
16 const SINT kInvalidChunkIndex = -1;
17
18 } // anonymous namespace
19
20 // One chunk should contain 1/2 - 1/4th of a second of audio.
21 // 8192 frames contain about 170 ms of audio at 48 kHz, which
22 // is well above (hopefully) the latencies people are seeing.
23 // At 10 ms latency one chunk is enough for 17 callbacks.
24 // Additionally the chunk size should be a power of 2 for
25 // easier memory alignment.
26 // TODO(XXX): The optimum value of the "constant" kFrames depends
27 // on the properties of the AudioSource as the remarks above suggest!
28 const mixxx::audio::ChannelCount CachingReaderChunk::kChannels = mixxx::kEngineChannelCount;
29 const SINT CachingReaderChunk::kFrames = 8192; // ~ 170 ms at 48 kHz
30 const SINT CachingReaderChunk::kSamples =
31 CachingReaderChunk::frames2samples(CachingReaderChunk::kFrames);
32
CachingReaderChunk(mixxx::SampleBuffer::WritableSlice sampleBuffer)33 CachingReaderChunk::CachingReaderChunk(
34 mixxx::SampleBuffer::WritableSlice sampleBuffer)
35 : m_index(kInvalidChunkIndex),
36 m_sampleBuffer(std::move(sampleBuffer)) {
37 DEBUG_ASSERT(m_sampleBuffer.length() == kSamples);
38 }
39
init(SINT index)40 void CachingReaderChunk::init(SINT index) {
41 DEBUG_ASSERT(m_index == kInvalidChunkIndex || index == kInvalidChunkIndex);
42 m_index = index;
43 m_bufferedSampleFrames.frameIndexRange() = mixxx::IndexRange();
44 }
45
46 // Frame index range of this chunk for the given audio source.
frameIndexRange(const mixxx::AudioSourcePointer & pAudioSource) const47 mixxx::IndexRange CachingReaderChunk::frameIndexRange(
48 const mixxx::AudioSourcePointer& pAudioSource) const {
49 DEBUG_ASSERT(m_index != kInvalidChunkIndex);
50 if (!pAudioSource) {
51 return mixxx::IndexRange();
52 }
53 const SINT minFrameIndex =
54 pAudioSource->frameIndexMin() +
55 frameIndexOffset();
56 return intersect(
57 mixxx::IndexRange::forward(minFrameIndex, kFrames),
58 pAudioSource->frameIndexRange());
59 }
60
bufferSampleFrames(const mixxx::AudioSourcePointer & pAudioSource,mixxx::SampleBuffer::WritableSlice tempOutputBuffer)61 mixxx::IndexRange CachingReaderChunk::bufferSampleFrames(
62 const mixxx::AudioSourcePointer& pAudioSource,
63 mixxx::SampleBuffer::WritableSlice tempOutputBuffer) {
64 DEBUG_ASSERT(m_index != kInvalidChunkIndex);
65 const auto sourceFrameIndexRange = frameIndexRange(pAudioSource);
66 mixxx::AudioSourceStereoProxy audioSourceProxy(
67 pAudioSource,
68 tempOutputBuffer);
69 DEBUG_ASSERT(
70 audioSourceProxy.getSignalInfo().getChannelCount() ==
71 kChannels);
72 m_bufferedSampleFrames =
73 audioSourceProxy.readSampleFrames(
74 mixxx::WritableSampleFrames(
75 sourceFrameIndexRange,
76 mixxx::SampleBuffer::WritableSlice(m_sampleBuffer)));
77 DEBUG_ASSERT(m_bufferedSampleFrames.frameIndexRange().empty() ||
78 m_bufferedSampleFrames.frameIndexRange().isSubrangeOf(sourceFrameIndexRange));
79 return m_bufferedSampleFrames.frameIndexRange();
80 }
81
readBufferedSampleFrames(CSAMPLE * sampleBuffer,const mixxx::IndexRange & frameIndexRange) const82 mixxx::IndexRange CachingReaderChunk::readBufferedSampleFrames(
83 CSAMPLE* sampleBuffer,
84 const mixxx::IndexRange& frameIndexRange) const {
85 DEBUG_ASSERT(m_index != kInvalidChunkIndex);
86 const auto copyableFrameIndexRange =
87 intersect(frameIndexRange, m_bufferedSampleFrames.frameIndexRange());
88 if (!copyableFrameIndexRange.empty()) {
89 const SINT dstSampleOffset =
90 frames2samples(copyableFrameIndexRange.start() - frameIndexRange.start());
91 const SINT srcSampleOffset =
92 frames2samples(copyableFrameIndexRange.start() - m_bufferedSampleFrames.frameIndexRange().start());
93 const SINT sampleCount = frames2samples(copyableFrameIndexRange.length());
94 SampleUtil::copy(
95 sampleBuffer + dstSampleOffset,
96 m_bufferedSampleFrames.readableData(srcSampleOffset),
97 sampleCount);
98 }
99 return copyableFrameIndexRange;
100 }
101
readBufferedSampleFramesReverse(CSAMPLE * reverseSampleBuffer,const mixxx::IndexRange & frameIndexRange) const102 mixxx::IndexRange CachingReaderChunk::readBufferedSampleFramesReverse(
103 CSAMPLE* reverseSampleBuffer,
104 const mixxx::IndexRange& frameIndexRange) const {
105 DEBUG_ASSERT(m_index != kInvalidChunkIndex);
106 const auto copyableFrameIndexRange =
107 intersect(frameIndexRange, m_bufferedSampleFrames.frameIndexRange());
108 if (!copyableFrameIndexRange.empty()) {
109 const SINT dstSampleOffset =
110 frames2samples(copyableFrameIndexRange.start() - frameIndexRange.start());
111 const SINT srcSampleOffset =
112 frames2samples(copyableFrameIndexRange.start() - m_bufferedSampleFrames.frameIndexRange().start());
113 const SINT sampleCount = frames2samples(copyableFrameIndexRange.length());
114 SampleUtil::copyReverse(
115 reverseSampleBuffer - dstSampleOffset - sampleCount,
116 m_bufferedSampleFrames.readableData(srcSampleOffset),
117 sampleCount);
118 }
119 return copyableFrameIndexRange;
120 }
121
CachingReaderChunkForOwner(mixxx::SampleBuffer::WritableSlice sampleBuffer)122 CachingReaderChunkForOwner::CachingReaderChunkForOwner(
123 mixxx::SampleBuffer::WritableSlice sampleBuffer)
124 : CachingReaderChunk(std::move(sampleBuffer)),
125 m_state(FREE),
126 m_pPrev(nullptr),
127 m_pNext(nullptr) {
128 }
129
init(SINT index)130 void CachingReaderChunkForOwner::init(SINT index) {
131 // Must not be accessed by a worker!
132 DEBUG_ASSERT(m_state != READ_PENDING);
133 // Must not be referenced in MRU/LRU list!
134 DEBUG_ASSERT(!m_pNext);
135 DEBUG_ASSERT(!m_pPrev);
136
137 CachingReaderChunk::init(index);
138 m_state = READY;
139 }
140
free()141 void CachingReaderChunkForOwner::free() {
142 // Must not be accessed by a worker!
143 DEBUG_ASSERT(m_state != READ_PENDING);
144 // Must not be referenced in MRU/LRU list!
145 DEBUG_ASSERT(!m_pNext);
146 DEBUG_ASSERT(!m_pPrev);
147
148 CachingReaderChunk::init(kInvalidChunkIndex);
149 m_state = FREE;
150 }
151
insertIntoListBefore(CachingReaderChunkForOwner ** ppHead,CachingReaderChunkForOwner ** ppTail,CachingReaderChunkForOwner * pBefore)152 void CachingReaderChunkForOwner::insertIntoListBefore(
153 CachingReaderChunkForOwner** ppHead,
154 CachingReaderChunkForOwner** ppTail,
155 CachingReaderChunkForOwner* pBefore) {
156 DEBUG_ASSERT(m_state == READY);
157 // Both head and tail need to be adjusted
158 DEBUG_ASSERT(ppHead);
159 DEBUG_ASSERT(ppTail);
160 // Cannot insert before itself
161 DEBUG_ASSERT(this != pBefore);
162 // Must not yet be referenced in MRU/LRU list
163 DEBUG_ASSERT(this != *ppHead);
164 DEBUG_ASSERT(this != *ppTail);
165 DEBUG_ASSERT(!m_pNext);
166 DEBUG_ASSERT(!m_pPrev);
167 if (kLogger.traceEnabled()) {
168 kLogger.trace()
169 << "insertIntoListBefore()"
170 << this
171 << ppHead << *ppHead
172 << ppTail << *ppTail
173 << pBefore;
174 }
175
176 if (pBefore) {
177 // List must already contain one or more item, i.e. has both
178 // a head and a tail
179 DEBUG_ASSERT(*ppHead);
180 DEBUG_ASSERT(*ppTail);
181 m_pPrev = pBefore->m_pPrev;
182 pBefore->m_pPrev = this;
183 m_pNext = pBefore;
184 if (*ppHead == pBefore) {
185 // Replace head
186 *ppHead = this;
187 }
188 } else {
189 // Append as new tail
190 m_pPrev = *ppTail;
191 *ppTail = this;
192 if (m_pPrev) {
193 m_pPrev->m_pNext = this;
194 }
195 if (!*ppHead) {
196 // Initialize new head if the list was empty before
197 *ppHead = this;
198 }
199 }
200 }
201
removeFromList(CachingReaderChunkForOwner ** ppHead,CachingReaderChunkForOwner ** ppTail)202 void CachingReaderChunkForOwner::removeFromList(
203 CachingReaderChunkForOwner** ppHead,
204 CachingReaderChunkForOwner** ppTail) {
205 DEBUG_ASSERT(m_state == READY);
206 // Both head and tail need to be adjusted
207 DEBUG_ASSERT(ppHead);
208 DEBUG_ASSERT(ppTail);
209 if (kLogger.traceEnabled()) {
210 kLogger.trace()
211 << "removeFromList()"
212 << this
213 << ppHead << *ppHead
214 << ppTail << *ppTail;
215 }
216
217 // Disconnect this chunk from the double-linked list
218 auto* const pPrev = m_pPrev;
219 auto* const pNext = m_pNext;
220 m_pPrev = nullptr;
221 m_pNext = nullptr;
222
223 // Reconnect the adjacent list items and adjust head/tail if needed
224 if (pPrev) {
225 DEBUG_ASSERT(this == pPrev->m_pNext);
226 pPrev->m_pNext = pNext;
227 } else {
228 // Only the current head item doesn't have a predecessor
229 if (this == *ppHead) {
230 // pNext becomes the new head
231 *ppHead = pNext;
232 } else {
233 // Item was not part the list and must not have any successor
234 DEBUG_ASSERT(!pPrev);
235 }
236 }
237 if (pNext) {
238 DEBUG_ASSERT(this == pNext->m_pPrev);
239 pNext->m_pPrev = pPrev;
240 } else {
241 // Only the current tail item doesn't have a successor
242 if (this == *ppTail) {
243 // pPrev becomes the new tail
244 *ppTail = pPrev;
245 } else {
246 // Item was not part the list and must not have any predecessor
247 DEBUG_ASSERT(!pPrev);
248 }
249 }
250 }
251