summaryrefslogtreecommitdiffstats
path: root/src/engine/cachingreader/cachingreaderchunk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/cachingreader/cachingreaderchunk.cpp')
-rw-r--r--src/engine/cachingreader/cachingreaderchunk.cpp247
1 files changed, 247 insertions, 0 deletions
diff --git a/src/engine/cachingreader/cachingreaderchunk.cpp b/src/engine/cachingreader/cachingreaderchunk.cpp
new file mode 100644
index 0000000000..23456282f3
--- /dev/null
+++ b/src/engine/cachingreader/cachingreaderchunk.cpp
@@ -0,0 +1,247 @@
+#include "engine/cachingreader/cachingreaderchunk.h"
+
+#include <QtDebug>
+
+#include "sources/audiosourcestereoproxy.h"
+#include "engine/engine.h"
+#include "util/math.h"
+#include "util/sample.h"
+#include "util/logger.h"
+
+
+namespace {
+
+mixxx::Logger kLogger("CachingReaderChunk");
+
+const SINT kInvalidChunkIndex = -1;
+
+} // anonymous namespace
+
+// One chunk should contain 1/2 - 1/4th of a second of audio.
+// 8192 frames contain about 170 ms of audio at 48 kHz, which
+// is well above (hopefully) the latencies people are seeing.
+// At 10 ms latency one chunk is enough for 17 callbacks.
+// Additionally the chunk size should be a power of 2 for
+// easier memory alignment.
+// TODO(XXX): The optimum value of the "constant" kFrames depends
+// on the properties of the AudioSource as the remarks above suggest!
+const mixxx::AudioSignal::ChannelCount CachingReaderChunk::kChannels = mixxx::kEngineChannelCount;
+const SINT CachingReaderChunk::kFrames = 8192; // ~ 170 ms at 48 kHz
+const SINT CachingReaderChunk::kSamples =
+ CachingReaderChunk::frames2samples(CachingReaderChunk::kFrames);
+
+CachingReaderChunk::CachingReaderChunk(
+ mixxx::SampleBuffer::WritableSlice sampleBuffer)
+ : m_index(kInvalidChunkIndex),
+ m_sampleBuffer(std::move(sampleBuffer)) {
+ DEBUG_ASSERT(m_sampleBuffer.length() == kSamples);
+}
+
+void CachingReaderChunk::init(SINT index) {
+ DEBUG_ASSERT(m_index == kInvalidChunkIndex || index == kInvalidChunkIndex);
+ m_index = index;
+ m_bufferedSampleFrames.frameIndexRange() = mixxx::IndexRange();
+}
+
+// Frame index range of this chunk for the given audio source.
+mixxx::IndexRange CachingReaderChunk::frameIndexRange(
+ const mixxx::AudioSourcePointer& pAudioSource) const {
+ DEBUG_ASSERT(m_index != kInvalidChunkIndex);
+ if (!pAudioSource) {
+ return mixxx::IndexRange();
+ }
+ const SINT minFrameIndex =
+ pAudioSource->frameIndexMin() +
+ frameIndexOffset();
+ return intersect(
+ mixxx::IndexRange::forward(minFrameIndex, kFrames),
+ pAudioSource->frameIndexRange());
+}
+
+mixxx::IndexRange CachingReaderChunk::bufferSampleFrames(
+ const mixxx::AudioSourcePointer& pAudioSource,
+ mixxx::SampleBuffer::WritableSlice tempOutputBuffer) {
+ DEBUG_ASSERT(m_index != kInvalidChunkIndex);
+ const auto sourceFrameIndexRange = frameIndexRange(pAudioSource);
+ mixxx::AudioSourceStereoProxy audioSourceProxy(
+ pAudioSource,
+ tempOutputBuffer);
+ DEBUG_ASSERT(audioSourceProxy.channelCount() == kChannels);
+ m_bufferedSampleFrames =
+ audioSourceProxy.readSampleFrames(
+ mixxx::WritableSampleFrames(
+ sourceFrameIndexRange,
+ mixxx::SampleBuffer::WritableSlice(m_sampleBuffer)));
+ DEBUG_ASSERT(m_bufferedSampleFrames.frameIndexRange() <= sourceFrameIndexRange);
+ return m_bufferedSampleFrames.frameIndexRange();
+}
+
+mixxx::IndexRange CachingReaderChunk::readBufferedSampleFrames(
+ CSAMPLE* sampleBuffer,
+ const mixxx::IndexRange& frameIndexRange) const {
+ DEBUG_ASSERT(m_index != kInvalidChunkIndex);
+ const auto copyableFrameIndexRange =
+ intersect(frameIndexRange, m_bufferedSampleFrames.frameIndexRange());
+ if (!copyableFrameIndexRange.empty()) {
+ const SINT dstSampleOffset =
+ frames2samples(copyableFrameIndexRange.start() - frameIndexRange.start());
+ const SINT srcSampleOffset =
+ frames2samples(copyableFrameIndexRange.start() - m_bufferedSampleFrames.frameIndexRange().start());
+ const SINT sampleCount = frames2samples(copyableFrameIndexRange.length());
+ SampleUtil::copy(
+ sampleBuffer + dstSampleOffset,
+ m_bufferedSampleFrames.readableData(srcSampleOffset),
+ sampleCount);
+ }
+ return copyableFrameIndexRange;
+}
+
+mixxx::IndexRange CachingReaderChunk::readBufferedSampleFramesReverse(
+ CSAMPLE* reverseSampleBuffer,
+ const mixxx::IndexRange& frameIndexRange) const {
+ DEBUG_ASSERT(m_index != kInvalidChunkIndex);
+ const auto copyableFrameIndexRange =
+ intersect(frameIndexRange, m_bufferedSampleFrames.frameIndexRange());
+ if (!copyableFrameIndexRange.empty()) {
+ const SINT dstSampleOffset =
+ frames2samples(copyableFrameIndexRange.start() - frameIndexRange.start());
+ const SINT srcSampleOffset =
+ frames2samples(copyableFrameIndexRange.start() - m_bufferedSampleFrames.frameIndexRange().start());
+ const SINT sampleCount = frames2samples(copyableFrameIndexRange.length());
+ SampleUtil::copyReverse(
+ reverseSampleBuffer - dstSampleOffset - sampleCount,
+ m_bufferedSampleFrames.readableData(srcSampleOffset),
+ sampleCount);
+ }
+ return copyableFrameIndexRange;
+}
+
+CachingReaderChunkForOwner::CachingReaderChunkForOwner(
+ mixxx::SampleBuffer::WritableSlice sampleBuffer)
+ : CachingReaderChunk(std::move(sampleBuffer)),
+ m_state(FREE),
+ m_pPrev(nullptr),
+ m_pNext(nullptr) {
+}
+
+void CachingReaderChunkForOwner::init(SINT index) {
+ // Must not be accessed by a worker!
+ DEBUG_ASSERT(m_state != READ_PENDING);
+ // Must not be referenced in MRU/LRU list!
+ DEBUG_ASSERT(!m_pNext);
+ DEBUG_ASSERT(!m_pPrev);
+
+ CachingReaderChunk::init(index);
+ m_state = READY;
+}
+
+void CachingReaderChunkForOwner::free() {
+ // Must not be accessed by a worker!
+ DEBUG_ASSERT(m_state != READ_PENDING);
+ // Must not be referenced in MRU/LRU list!
+ DEBUG_ASSERT(!m_pNext);
+ DEBUG_ASSERT(!m_pPrev);
+
+ CachingReaderChunk::init(kInvalidChunkIndex);
+ m_state = FREE;
+}
+
+void CachingReaderChunkForOwner::insertIntoListBefore(
+ CachingReaderChunkForOwner** ppHead,
+ CachingReaderChunkForOwner** ppTail,
+ CachingReaderChunkForOwner* pBefore) {
+ DEBUG_ASSERT(m_state == READY);
+ // Both head and tail need to be adjusted
+ DEBUG_ASSERT(ppHead);
+ DEBUG_ASSERT(ppTail);
+ // Cannot insert before itself
+ DEBUG_ASSERT(this != pBefore);
+ // Must not yet be referenced in MRU/LRU list
+ DEBUG_ASSERT(this != *ppHead);
+ DEBUG_ASSERT(this != *ppTail);
+ DEBUG_ASSERT(!m_pNext);
+ DEBUG_ASSERT(!m_pPrev);
+ if (kLogger.traceEnabled()) {
+ kLogger.trace()
+ << "insertIntoListBefore()"
+ << this
+ << ppHead << *ppHead
+ << ppTail << *ppTail
+ << pBefore;
+ }
+
+ if (pBefore) {
+ // List must already contain one or more item, i.e. has both
+ // a head and a tail
+ DEBUG_ASSERT(*ppHead);
+ DEBUG_ASSERT(*ppTail);
+ m_pPrev = pBefore->m_pPrev;
+ pBefore->m_pPrev = this;
+ m_pNext = pBefore;
+ if (*ppHead == pBefore) {
+ // Replace head
+ *ppHead = this;
+ }
+ } else {
+ // Append as new tail
+ m_pPrev = *ppTail;
+ *ppTail = this;
+ if (m_pPrev) {
+ m_pPrev->m_pNext = this;
+ }
+ if (!*ppHead) {
+ // Initialize new head if the list was empty before
+ *ppHead = this;
+ }
+ }
+}
+
+void CachingReaderChunkForOwner::removeFromList(
+ CachingReaderChunkForOwner** ppHead,
+ CachingReaderChunkForOwner** ppTail) {
+ DEBUG_ASSERT(m_state == READY);
+ // Both head and tail need to be adjusted
+ DEBUG_ASSERT(ppHead);
+ DEBUG_ASSERT(ppTail);
+ if (kLogger.traceEnabled()) {
+ kLogger.trace()
+ << "removeFromList()"
+ << this
+ << ppHead << *ppHead
+ << ppTail << *ppTail;
+ }
+
+ // Disconnect this chunk from the double-linked list
+ const auto pPrev = m_pPrev;
+ const auto pNext = m_pNext;
+ m_pPrev = nullptr;
+ m_pNext = nullptr;
+
+ // Reconnect the adjacent list items and adjust head/tail if needed
+ if (pPrev) {
+ DEBUG_ASSERT(this == pPrev->m_pNext);
+ pPrev->m_pNext = pNext;
+ } else {
+ // Only the current head item doesn't have a predecessor
+ if (this == *ppHead) {
+ // pNext becomes the new head
+ *ppHead = pNext;
+ } else {
+ // Item was not part the list and must not have any successor
+ DEBUG_ASSERT(!pPrev);
+ }
+ }
+ if (pNext) {
+ DEBUG_ASSERT(this == pNext->m_pPrev);
+ pNext->m_pPrev = pPrev;
+ } else {
+ // Only the current tail item doesn't have a successor
+ if (this == *ppTail) {
+ // pPrev becomes the new tail
+ *ppTail = pPrev;
+ } else {
+ // Item was not part the list and must not have any predecessor
+ DEBUG_ASSERT(!pPrev);
+ }
+ }
+}