/* * Carla Native Plugins * Copyright (C) 2013-2018 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 "CarlaThread.hpp" #include "CarlaMathUtils.hpp" extern "C" { #include "audio_decoder/ad.h" } typedef struct adinfo ADInfo; struct AudioFilePool { float* buffer[2]; uint64_t startFrame; uint32_t sampleRate; uint32_t size; #ifdef CARLA_PROPER_CPP11_SUPPORT AudioFilePool() : buffer{nullptr}, startFrame(0), sampleRate(0), size(0) {} #else AudioFilePool() : startFrame(0), sampleRate(0), size(0) { buffer[0] = buffer[1] = nullptr; } #endif ~AudioFilePool() { CARLA_ASSERT(buffer[0] == nullptr); CARLA_ASSERT(buffer[1] == nullptr); CARLA_ASSERT(startFrame == 0); CARLA_ASSERT(size == 0); } void create(const uint32_t srate) { CARLA_ASSERT(buffer[0] == nullptr); CARLA_ASSERT(buffer[1] == nullptr); CARLA_ASSERT(startFrame == 0); CARLA_ASSERT(size == 0); size = srate * 60; // buffer of 60 secs sampleRate = srate; buffer[0] = new float[size]; buffer[1] = new float[size]; reset(); } void destroy() { CARLA_ASSERT(buffer[0] != nullptr); CARLA_ASSERT(buffer[1] != nullptr); CARLA_ASSERT(size != 0); if (buffer[0] != nullptr) { delete[] buffer[0]; buffer[0] = nullptr; } if (buffer[1] != nullptr) { delete[] buffer[1]; buffer[1] = nullptr; } startFrame = 0; size = 0; } void reset() { startFrame = 0; CARLA_SAFE_ASSERT_RETURN(size != 0,); carla_zeroFloats(buffer[0], size); carla_zeroFloats(buffer[1], size); } CARLA_DECLARE_NON_COPY_STRUCT(AudioFilePool) }; class AbstractAudioPlayer { public: virtual ~AbstractAudioPlayer() {} virtual uint64_t getLastFrame() const = 0; }; class AudioFileThread : public CarlaThread { public: AudioFileThread(AbstractAudioPlayer* const player, const uint32_t sampleRate) : CarlaThread("AudioFileThread"), kPlayer(player), fLoopingMode(true), fNeedsRead(false), fQuitNow(true), fFilePtr(nullptr), fFileNfo(), fMaxPlayerFrame(0), fPollTempData(nullptr), fPollTempSize(0), fPool(), fMutex() { CARLA_ASSERT(kPlayer != nullptr); static bool adInitiated = false; if (! adInitiated) { ad_init(); adInitiated = true; } ad_clear_nfo(&fFileNfo); fPool.create(sampleRate); } ~AudioFileThread() override { CARLA_ASSERT(fQuitNow); CARLA_ASSERT(! isThreadRunning()); if (fFilePtr != nullptr) { ad_close(fFilePtr); fFilePtr = nullptr; } if (fPollTempData != nullptr) { delete[] fPollTempData; fPollTempData = nullptr; fPollTempSize = 0; } fPool.destroy(); } void startNow() { fNeedsRead = true; fQuitNow = false; startThread(); } void stopNow() { fNeedsRead = false; fQuitNow = true; stopThread(1000); const CarlaMutexLocker cml(fMutex); fPool.reset(); } uint32_t getMaxFrame() const noexcept { return fMaxPlayerFrame; } void setLoopingMode(const bool on) noexcept { fLoopingMode = on; } void setNeedsRead() noexcept { fNeedsRead = true; } bool loadFilename(const char* const filename) { CARLA_SAFE_ASSERT_RETURN(! isThreadRunning(), false); CARLA_SAFE_ASSERT_RETURN(filename != nullptr && *filename != '\0', false); fPool.startFrame = 0; // clear old data if (fFilePtr != nullptr) { ad_close(fFilePtr); fFilePtr = nullptr; } if (fPollTempData != nullptr) { delete[] fPollTempData; fPollTempData = nullptr; fPollTempSize = 0; fMaxPlayerFrame = 0; } 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 size_t pollTempSize = std::min(static_cast(fFileNfo.frames), fPool.size * fFileNfo.channels); try { fPollTempData = new float[pollTempSize]; } catch (...) { ad_close(fFilePtr); fFilePtr = nullptr; return false; } fMaxPlayerFrame = static_cast(fFileNfo.frames/fFileNfo.channels); fPollTempSize = pollTempSize; readPoll(); return true; } else { // invalid ad_clear_nfo(&fFileNfo); ad_close(fFilePtr); fFilePtr = nullptr; return false; } } bool tryPutData(AudioFilePool& pool, const uint32_t framePos, const uint32_t frames) { CARLA_SAFE_ASSERT_RETURN(pool.size == fPool.size, false); if (! fMutex.tryLock()) return false; //if (pool.startFrame != fPool.startFrame || pool.buffer[0] != fPool.buffer[0] || pool.buffer[1] != fPool.buffer[1]) { pool.startFrame = fPool.startFrame; if (frames == 0) { carla_copyFloats(pool.buffer[0], fPool.buffer[0], fPool.size); carla_copyFloats(pool.buffer[1], fPool.buffer[1], fPool.size); } else { CARLA_SAFE_ASSERT_UINT2_RETURN(framePos + frames < fPool.size, framePos, fPool.size, false); carla_copyFloats(pool.buffer[0] + framePos, fPool.buffer[0] + framePos, frames); carla_copyFloats(pool.buffer[1] + framePos, fPool.buffer[1] + framePos, frames); } } fMutex.unlock(); return true; } void readPoll() { if (fFileNfo.frames <= 0 || fFilePtr == nullptr) { carla_stderr("R: no song loaded"); fNeedsRead = false; return; } const uint64_t lastFrame = kPlayer->getLastFrame(); int32_t readFrameCheck; if (lastFrame >= static_cast(fFileNfo.frames)) { if (fLoopingMode) { const uint64_t readFrameCheckLoop = lastFrame % fMaxPlayerFrame; CARLA_SAFE_ASSERT_RETURN(readFrameCheckLoop < INT32_MAX,); carla_debug("R: transport out of bounds for loop"); readFrameCheck = static_cast(readFrameCheckLoop); } else { carla_stderr("R: transport out of bounds"); fNeedsRead = false; return; } } else { CARLA_SAFE_ASSERT_RETURN(lastFrame < INT32_MAX,); readFrameCheck = static_cast(lastFrame); } const int32_t readFrame = readFrameCheck; // temp data buffer carla_zeroFloats(fPollTempData, fPollTempSize); { carla_debug("R: poll data - reading at %li:%02li", readFrame/fPool.sampleRate/60, (readFrame/fPool.sampleRate) % 60); ad_seek(fFilePtr, readFrame); size_t i = 0; ssize_t j = 0; ssize_t rv = ad_read(fFilePtr, fPollTempData, fPollTempSize); if (rv < 0) { carla_stderr("R: ad_read failed"); fNeedsRead = false; return; } const size_t urv = static_cast(rv); // see if we can read more if (readFrame + rv >= static_cast(fFileNfo.frames) && urv < fPollTempSize) { carla_debug("R: from start"); ad_seek(fFilePtr, 0); rv += ad_read(fFilePtr, fPollTempData+urv, fPollTempSize-urv); } // lock, and put data asap const CarlaMutexLocker cml(fMutex); do { for (; i < fPool.size && j < rv; ++j) { if (fFileNfo.channels == 1) { fPool.buffer[0][i] = fPollTempData[j]; fPool.buffer[1][i] = fPollTempData[j]; i++; } else { if (j % 2 == 0) { fPool.buffer[0][i] = fPollTempData[j]; } else { fPool.buffer[1][i] = fPollTempData[j]; i++; } } } if (i >= fPool.size) break; if (rv == fFileNfo.frames) { // full file read j = 0; carla_debug("R: full file was read, filling buffers again"); } else { carla_debug("read break, not enough space"); carla_zeroFloats(fPool.buffer[0] + i, fPool.size - i); carla_zeroFloats(fPool.buffer[1] + i, fPool.size - i); break; } } while (i < fPool.size); fPool.startFrame = lastFrame; } fNeedsRead = false; } protected: void run() override { while (! fQuitNow) { const uint64_t lastFrame = kPlayer->getLastFrame(); const uint64_t loopedFrame = fLoopingMode ? lastFrame % fMaxPlayerFrame : lastFrame; if (fNeedsRead || lastFrame < fPool.startFrame || (lastFrame - fPool.startFrame >= fPool.size*3/4 && loopedFrame < fMaxPlayerFrame)) readPoll(); carla_msleep(50); } } private: AbstractAudioPlayer* const kPlayer; bool fLoopingMode; bool fNeedsRead; bool fQuitNow; void* fFilePtr; ADInfo fFileNfo; uint32_t fMaxPlayerFrame; float* fPollTempData; size_t fPollTempSize; AudioFilePool fPool; CarlaMutex fMutex; CARLA_DECLARE_NON_COPY_STRUCT(AudioFileThread) }; #endif // AUDIO_BASE_HPP_INCLUDED