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