/* * Carla Native Plugins * Copyright (C) 2013-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the GPL.txt file */ #ifndef AUDIO_BASE_HPP_INCLUDED #define AUDIO_BASE_HPP_INCLUDED #include "CarlaMathUtils.hpp" extern "C" { #include "audio_decoder/ad.h" } #include "water/threads/ScopedLock.h" #include "water/threads/SpinLock.h" #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Weffc++" #endif #include "zita-resampler/resampler.h" #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # pragma GCC diagnostic pop #endif #if defined(CARLA_OS_WIN) # include # define CARLA_MLOCK(ptr, size) VirtualLock((ptr), (size)) #elif !defined(CARLA_OS_WASM) # include # define CARLA_MLOCK(ptr, size) mlock((ptr), (size)) #else # define CARLA_MLOCK(ptr, size) #endif // #define DEBUG_FILE_OPS typedef struct adinfo ADInfo; struct AudioFilePool { float* buffer[2]; float* tmpbuf[2]; uint32_t numFrames; uint32_t maxFrame; volatile uint64_t startFrame; water::SpinLock mutex; #ifdef CARLA_PROPER_CPP11_SUPPORT AudioFilePool() noexcept : buffer{nullptr}, tmpbuf{nullptr}, numFrames(0), maxFrame(0), startFrame(0), mutex() {} #else AudioFilePool() noexcept : numFrames(0), startFrame(0), mutex() { buffer[0] = buffer[1] = nullptr; tmpbuf[0] = tmpbuf[1] = nullptr; } #endif ~AudioFilePool() { destroy(); } void create(const uint32_t desiredNumFrames, const uint32_t fileNumFrames, const bool withTempBuffers) { CARLA_ASSERT(buffer[0] == nullptr); CARLA_ASSERT(buffer[1] == nullptr); CARLA_ASSERT(tmpbuf[0] == nullptr); CARLA_ASSERT(tmpbuf[1] == nullptr); CARLA_ASSERT(startFrame == 0); CARLA_ASSERT(numFrames == 0); CARLA_ASSERT(maxFrame == 0); buffer[0] = new float[desiredNumFrames]; buffer[1] = new float[desiredNumFrames]; carla_zeroFloats(buffer[0], desiredNumFrames); carla_zeroFloats(buffer[1], desiredNumFrames); CARLA_MLOCK(buffer[0], sizeof(float)*desiredNumFrames); CARLA_MLOCK(buffer[1], sizeof(float)*desiredNumFrames); if (withTempBuffers) { tmpbuf[0] = new float[desiredNumFrames]; tmpbuf[1] = new float[desiredNumFrames]; carla_zeroFloats(tmpbuf[0], desiredNumFrames); carla_zeroFloats(tmpbuf[1], desiredNumFrames); CARLA_MLOCK(tmpbuf[0], sizeof(float)*desiredNumFrames); CARLA_MLOCK(tmpbuf[1], sizeof(float)*desiredNumFrames); } const water::GenericScopedLock gsl(mutex); startFrame = 0; numFrames = desiredNumFrames; maxFrame = fileNumFrames; } void destroy() noexcept { { const water::GenericScopedLock gsl(mutex); startFrame = 0; numFrames = 0; maxFrame = 0; } if (buffer[0] != nullptr) { delete[] buffer[0]; buffer[0] = nullptr; } if (buffer[1] != nullptr) { delete[] buffer[1]; buffer[1] = nullptr; } if (tmpbuf[0] != nullptr) { delete[] tmpbuf[0]; tmpbuf[0] = nullptr; } if (tmpbuf[1] != nullptr) { delete[] tmpbuf[1]; tmpbuf[1] = nullptr; } } // NOTE it is assumed that mutex is locked bool tryPutData(float* const out1, float* const out2, uint64_t framePos, const uint32_t frames, const bool loopingMode, const bool isOffline, bool& needsRead, uint64_t& needsReadFrame) { CARLA_SAFE_ASSERT_RETURN(numFrames != 0, false); CARLA_SAFE_ASSERT_RETURN(maxFrame != 0, false); if (framePos >= maxFrame) { if (loopingMode) framePos %= maxFrame; else return false; } uint64_t frameDiff; const uint32_t numFramesNearEnd = numFrames*3/4; if (framePos < startFrame) { if (startFrame + numFrames <= maxFrame) { needsRead = true; needsReadFrame = framePos; return false; } frameDiff = framePos + (maxFrame - startFrame); if (frameDiff + frames >= numFrames) { needsRead = true; needsReadFrame = framePos; return false; } carla_copyFloats(out1, buffer[0] + frameDiff, frames); carla_copyFloats(out2, buffer[1] + frameDiff, frames); } else { frameDiff = framePos - startFrame; if (frameDiff + frames >= numFrames) { needsRead = true; needsReadFrame = framePos; return false; } carla_copyFloats(out1, buffer[0] + frameDiff, frames); carla_copyFloats(out2, buffer[1] + frameDiff, frames); } if (frameDiff > numFramesNearEnd) { needsRead = true; needsReadFrame = framePos + (isOffline ? 0 : frames); } return true; } CARLA_DECLARE_NON_COPYABLE(AudioFilePool) }; class AudioFileReader { public: AudioFileReader() : fEntireFileLoaded(false), fLoopingMode(true), fCurrentBitRate(0), fNeedsFrame(0), fNeedsRead(false), fFilePtr(nullptr), fFileNfo(), fPollTempData(nullptr), fPollTempSize(0), fResampleRatio(0.0), fResampleTempData(nullptr), fResampleTempSize(0), fPool(), fPoolMutex(), fPoolReadyToSwap(false), fResampler(), fReaderMutex() { ad_clear_nfo(&fFileNfo); } ~AudioFileReader() { cleanup(); } void cleanup() { fPool.destroy(); fCurrentBitRate = 0; fEntireFileLoaded = false; if (fFilePtr != nullptr) { ad_close(fFilePtr); fFilePtr = nullptr; } if (fPollTempData != nullptr) { delete[] fPollTempData; fPollTempData = nullptr; fPollTempSize = 0; } if (fResampleTempData != nullptr) { delete[] fResampleTempData; fResampleTempData = nullptr; fResampleTempSize = 0; } } void destroy() { const CarlaMutexLocker cml(fReaderMutex); fPool.destroy(); fNeedsFrame = 0; fNeedsRead = false; } bool isEntireFileLoaded() const noexcept { return fEntireFileLoaded; } int getCurrentBitRate() const noexcept { return fCurrentBitRate; } uint32_t getMaxFrame() const noexcept { return fPool.maxFrame; } ADInfo getFileInfo() const noexcept { return fFileNfo; } void setLoopingMode(const bool on) noexcept { fLoopingMode = on; } void setNeedsRead(const uint64_t frame) noexcept { if (fEntireFileLoaded) return; fNeedsFrame = frame; fNeedsRead = true; } bool loadFilename(const char* const filename, const uint32_t sampleRate, const uint32_t previewDataSize, float* previewData) { CARLA_SAFE_ASSERT_RETURN(filename != nullptr && *filename != '\0', false); const CarlaMutexLocker cml(fReaderMutex); cleanup(); ad_clear_nfo(&fFileNfo); // open new fFilePtr = ad_open(filename, &fFileNfo); if (fFilePtr == nullptr) return false; ad_dump_nfo(99, &fFileNfo); // Fix for misinformation using libsndfile if (fFileNfo.frames % fFileNfo.channels) --fFileNfo.frames; if (fFileNfo.frames <= 0) carla_stderr("L: filename \"%s\" has 0 frames", filename); if ((fFileNfo.channels == 1 || fFileNfo.channels == 2) && fFileNfo.frames > 0) { // valid const uint32_t fileNumFrames = static_cast(fFileNfo.frames); const uint32_t maxPoolNumFrames = sampleRate * 30; const bool needsResample = fFileNfo.sample_rate != sampleRate; uint32_t maxFrame; if (needsResample) { if (! fResampler.setup(fFileNfo.sample_rate, sampleRate, fFileNfo.channels, 32)) { ad_clear_nfo(&fFileNfo); ad_close(fFilePtr); fFilePtr = nullptr; carla_stderr2("loadFilename error, resampler setup failed"); return false; } fResampleRatio = static_cast(sampleRate) / static_cast(fFileNfo.sample_rate); maxFrame = static_cast(static_cast(fileNumFrames) * fResampleRatio + 0.5); } else { fResampler.clear(); fResampleRatio = 0.0; maxFrame = fileNumFrames; } if (fileNumFrames <= maxPoolNumFrames || fFileNfo.can_seek == 0) { // entire file fits in a small pool, lets read it now const uint32_t poolNumFrames = needsResample ? static_cast(static_cast(fileNumFrames) * fResampleRatio + 0.5) : fileNumFrames; fPool.create(poolNumFrames, maxFrame, false); readEntireFileIntoPool(needsResample); ad_close(fFilePtr); fFilePtr = nullptr; const float fileNumFramesF = static_cast(fileNumFrames); const float previewDataSizeF = static_cast(previewDataSize); for (uint i=0; i(i)/previewDataSizeF * fileNumFramesF; const uint step = carla_fixedValue(0U, fileNumFrames-1U, static_cast(stepF + 0.5f)); previewData[i] = std::max(std::fabs(fPool.buffer[0][step]), std::fabs(fPool.buffer[1][step])); } } else { // file is too big for our audio pool, we need an extra buffer const uint32_t poolNumFrames = sampleRate * 5; const uint pollTempSize = poolNumFrames * fFileNfo.channels; uint resampleTempSize = 0; readFilePreview(previewDataSize, previewData); fPool.create(poolNumFrames, maxFrame, true); try { fPollTempData = new float[pollTempSize]; } catch (...) { ad_clear_nfo(&fFileNfo); ad_close(fFilePtr); fFilePtr = nullptr; carla_stderr2("loadFilename error, out of memory"); return false; } CARLA_MLOCK(fPollTempData, sizeof(float)*pollTempSize); if (needsResample) { resampleTempSize = static_cast(static_cast(poolNumFrames) * fResampleRatio + 0.5); resampleTempSize *= fFileNfo.channels; try { fResampleTempData = new float[resampleTempSize]; } catch (...) { delete[] fPollTempData; fPollTempData = nullptr; ad_clear_nfo(&fFileNfo); ad_close(fFilePtr); fFilePtr = nullptr; carla_stderr2("loadFilename error, out of memory"); return false; } CARLA_MLOCK(fResampleTempData, sizeof(float)*resampleTempSize); } fPollTempSize = pollTempSize; fResampleTempSize = resampleTempSize; } fNeedsRead = true; return true; } else { // invalid ad_clear_nfo(&fFileNfo); ad_close(fFilePtr); fFilePtr = nullptr; return false; } } void createSwapablePool(AudioFilePool& pool) { pool.create(fPool.numFrames, fPool.maxFrame, false); } void putAndSwapAllData(AudioFilePool& pool) { const water::GenericScopedLock gsl1(fPool.mutex); const water::GenericScopedLock gsl2(pool.mutex); CARLA_SAFE_ASSERT_RETURN(fPool.numFrames != 0,); CARLA_SAFE_ASSERT_RETURN(fPool.buffer[0] != nullptr,); CARLA_SAFE_ASSERT_RETURN(fPool.tmpbuf[0] == nullptr,); CARLA_SAFE_ASSERT_RETURN(pool.numFrames == 0,); CARLA_SAFE_ASSERT_RETURN(pool.buffer[0] == nullptr,); CARLA_SAFE_ASSERT_RETURN(pool.tmpbuf[0] == nullptr,); pool.startFrame = fPool.startFrame; pool.numFrames = fPool.numFrames; pool.buffer[0] = fPool.buffer[0]; pool.buffer[1] = fPool.buffer[1]; fPool.startFrame = 0; fPool.numFrames = 0; fPool.buffer[0] = nullptr; fPool.buffer[1] = nullptr; } bool tryPutData(AudioFilePool& pool, float* const out1, float* const out2, uint64_t framePos, const uint32_t frames, const bool loopMode, const bool isOffline, bool& needsIdleRequest) { _tryPoolSwap(pool); bool needsRead = false; uint64_t needsReadFrame; const bool ret = pool.tryPutData(out1, out2, framePos, frames, loopMode, isOffline, needsRead, needsReadFrame); if (needsRead) { needsIdleRequest = true; setNeedsRead(needsReadFrame); } #ifdef DEBUG_FILE_OPS if (! ret) { carla_stdout("tryPutData fail"); } #endif return ret; } void readFilePreview(uint32_t previewDataSize, float* previewData) { carla_zeroFloats(previewData, previewDataSize); const uint fileNumFrames = static_cast(fFileNfo.frames); const float fileNumFramesF = static_cast(fileNumFrames); const float previewDataSizeF = static_cast(previewDataSize); const uint samplesPerRun = fFileNfo.channels; const uint maxSampleToRead = fileNumFrames - samplesPerRun; CARLA_SAFE_ASSERT_INT_RETURN(samplesPerRun == 1 || samplesPerRun == 2, samplesPerRun,); float tmp[2] = { 0.0f, 0.0f }; if (samplesPerRun == 2) previewDataSize -= 1; for (uint i=0; i(i)/previewDataSizeF * fileNumFramesF; const uint pos = carla_fixedValue(0U, maxSampleToRead, static_cast(posF)); ad_seek(fFilePtr, pos); ad_read(fFilePtr, tmp, samplesPerRun); previewData[i] = std::max(std::fabs(tmp[0]), std::fabs(tmp[1])); } } void readEntireFileIntoPool(const bool needsResample) { CARLA_SAFE_ASSERT_RETURN(fPool.numFrames > 0,); const uint numChannels = fFileNfo.channels; const uint fileNumFrames = static_cast(fFileNfo.frames); const uint bufferSize = fileNumFrames * numChannels; float* const buffer = (float*)std::calloc(bufferSize, sizeof(float)); CARLA_SAFE_ASSERT_RETURN(buffer != nullptr,); ad_seek(fFilePtr, 0); ssize_t rv = ad_read(fFilePtr, buffer, bufferSize); CARLA_SAFE_ASSERT_INT2_RETURN(rv == static_cast(bufferSize), static_cast(rv), static_cast(bufferSize), std::free(buffer)); fCurrentBitRate = ad_get_bitrate(fFilePtr); float* rbuffer; if (needsResample) { const uint rbufferSize = fPool.numFrames * numChannels; rbuffer = (float*)std::calloc(rbufferSize, sizeof(float)); CARLA_SAFE_ASSERT_RETURN(rbuffer != nullptr, std::free(buffer);); rv = static_cast(rbufferSize); fResampler.inp_count = fileNumFrames; fResampler.out_count = fPool.numFrames; fResampler.inp_data = buffer; fResampler.out_data = rbuffer; fResampler.process(); CARLA_SAFE_ASSERT_INT(fResampler.inp_count <= 2, fResampler.inp_count); } else { rbuffer = buffer; } { // lock, and put data asap const water::GenericScopedLock gsl(fPool.mutex); if (numChannels == 1) { for (ssize_t i=0, j=0; j < rv; ++i, ++j) fPool.buffer[0][i] = fPool.buffer[1][i] = rbuffer[j]; } else { for (ssize_t i=0, j=0; j < rv; ++j) { if (j % 2 == 0) { fPool.buffer[0][i] = rbuffer[j]; } else { fPool.buffer[1][i] = rbuffer[j]; ++i; } } } } if (rbuffer != buffer) std::free(rbuffer); std::free(buffer); fEntireFileLoaded = true; } void readPoll() { const CarlaMutexLocker cml(fReaderMutex); if (fFileNfo.channels == 0 || fFilePtr == nullptr) { carla_debug("R: no song loaded"); fNeedsFrame = 0; fNeedsRead = false; return; } if (fPollTempData == nullptr) { carla_debug("R: nothing to poll"); fNeedsFrame = 0; fNeedsRead = false; return; } const uint32_t maxFrame = fPool.maxFrame; uint64_t lastFrame = fNeedsFrame; int64_t readFrameCheck; if (lastFrame >= maxFrame) { if (fLoopingMode) { const uint64_t readFrameCheckLoop = lastFrame % maxFrame; CARLA_SAFE_ASSERT_RETURN(readFrameCheckLoop < INT32_MAX,); carla_debug("R: transport out of bounds for loop"); readFrameCheck = static_cast(readFrameCheckLoop); } else { carla_debug("R: transport out of bounds"); fNeedsFrame = 0; fNeedsRead = false; return; } } else { CARLA_SAFE_ASSERT_RETURN(lastFrame < INT32_MAX,); readFrameCheck = static_cast(lastFrame); } const int64_t readFrame = readFrameCheck; // temp data buffer carla_zeroFloats(fPollTempData, fPollTempSize); { #if 0 const int32_t sampleRate = 44100; carla_debug("R: poll data - reading at frame %li, time %li:%02li, lastFrame %li", readFrame, readFrame/sampleRate/60, (readFrame/sampleRate) % 60, lastFrame); #endif const int64_t readFrameReal = carla_isNotZero(fResampleRatio) ? static_cast(static_cast(readFrame) / fResampleRatio + 0.5) : readFrame; ad_seek(fFilePtr, readFrameReal); size_t i = 0; ssize_t j = 0; ssize_t rv = ad_read(fFilePtr, fPollTempData, fPollTempSize); if (rv < 0) { carla_stderr("R: ad_read1 failed"); fNeedsFrame = 0; fNeedsRead = false; return; } const size_t urv = static_cast(rv); // see if we can read more if (readFrameReal + rv >= static_cast(fFileNfo.frames) && urv < fPollTempSize) { #ifdef DEBUG_FILE_OPS carla_stdout("R: from start"); #endif ad_seek(fFilePtr, 0); j = ad_read(fFilePtr, fPollTempData+urv, fPollTempSize-urv); if (j < 0) { carla_stderr("R: ad_read2 failed"); fNeedsFrame = 0; fNeedsRead = false; return; } rv += j; } #ifdef DEBUG_FILE_OPS carla_stdout("R: reading %li frames at frame %lu", rv, readFrameCheck); #endif fCurrentBitRate = ad_get_bitrate(fFilePtr); // local copy const uint32_t poolNumFrames = fPool.numFrames; float* const pbuffer0 = fPool.tmpbuf[0]; float* const pbuffer1 = fPool.tmpbuf[1]; const float* tmpbuf = fPollTempData; // resample as needed if (fResampleTempSize != 0) { tmpbuf = fResampleTempData; fResampler.inp_count = static_cast(rv / fFileNfo.channels); fResampler.out_count = fResampleTempSize / fFileNfo.channels; fResampler.inp_data = fPollTempData; fResampler.out_data = fResampleTempData; fResampler.process(); CARLA_ASSERT_INT(fResampler.inp_count <= 1, fResampler.inp_count); } j = 0; do { if (fFileNfo.channels == 1) { for (; i < poolNumFrames && j < rv; ++i, ++j) pbuffer0[i] = pbuffer1[i] = tmpbuf[j]; } else { for (; i < poolNumFrames && j < rv; ++j) { if (j % 2 == 0) { pbuffer0[i] = tmpbuf[j]; } else { pbuffer1[i] = tmpbuf[j]; ++i; } } } if (i >= poolNumFrames) break; if (rv == fFileNfo.frames) { // full file read j = 0; #ifdef DEBUG_FILE_OPS carla_stdout("R: full file was read, filling buffers again"); #endif } else { #ifdef DEBUG_FILE_OPS carla_stdout("read break, not enough space"); #endif carla_zeroFloats(pbuffer0, poolNumFrames - i); carla_zeroFloats(pbuffer1, poolNumFrames - i); break; } } while (i < poolNumFrames); // lock, and put data asap const CarlaMutexLocker cmlp(fPoolMutex); const water::GenericScopedLock gsl(fPool.mutex); std::memcpy(fPool.buffer[0], pbuffer0, sizeof(float)*poolNumFrames); std::memcpy(fPool.buffer[1], pbuffer1, sizeof(float)*poolNumFrames); fPool.startFrame = static_cast(readFrame); fPoolReadyToSwap = true; #ifdef DEBUG_FILE_OPS carla_stdout("Reading done and internal pool is now full"); #endif } fNeedsRead = false; } private: bool fEntireFileLoaded; bool fLoopingMode; int fCurrentBitRate; volatile uint64_t fNeedsFrame; volatile bool fNeedsRead; void* fFilePtr; ADInfo fFileNfo; float* fPollTempData; uint fPollTempSize; double fResampleRatio; float* fResampleTempData; uint fResampleTempSize; AudioFilePool fPool; CarlaMutex fPoolMutex; bool fPoolReadyToSwap; Resampler fResampler; CarlaMutex fReaderMutex; // try a pool data swap if possible and relevant // NOTE it is assumed that `pool` mutex is locked void _tryPoolSwap(AudioFilePool& pool) { uint32_t tmp_u32; uint64_t tmp_u64; float* tmp_fp; const CarlaMutexTryLocker cmtl(fPoolMutex); if (! cmtl.wasLocked()) return; const water::GenericScopedLock gsl(fPool.mutex); if (! fPoolReadyToSwap) return; tmp_u64 = pool.startFrame; pool.startFrame = fPool.startFrame; fPool.startFrame = tmp_u64; tmp_u32 = pool.numFrames; pool.numFrames = fPool.numFrames; fPool.numFrames = tmp_u32; tmp_fp = pool.buffer[0]; pool.buffer[0] = fPool.buffer[0]; fPool.buffer[0] = tmp_fp; tmp_fp = pool.buffer[1]; pool.buffer[1] = fPool.buffer[1]; fPool.buffer[1] = tmp_fp; fPoolReadyToSwap = false; #ifdef DEBUG_FILE_OPS carla_stdout("Pools have been swapped, internal one is now invalidated"); #endif } CARLA_DECLARE_NON_COPYABLE(AudioFileReader) }; #endif // AUDIO_BASE_HPP_INCLUDED