/* * Carla Native Plugins * Copyright (C) 2013 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 doc/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 size; AudioFilePool() : startFrame(0), size(0) { buffer[0] = buffer[1] = nullptr; } ~AudioFilePool() { CARLA_ASSERT(buffer[0] == nullptr); CARLA_ASSERT(buffer[1] == nullptr); CARLA_ASSERT(startFrame == 0); CARLA_ASSERT(size == 0); } void create(const uint32_t sampleRate) { CARLA_ASSERT(buffer[0] == nullptr); CARLA_ASSERT(buffer[1] == nullptr); CARLA_ASSERT(startFrame == 0); CARLA_ASSERT(size == 0); size = sampleRate * 2; 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() { CARLA_ASSERT(size != 0); startFrame = 0; FLOAT_CLEAR(buffer[0], size); FLOAT_CLEAR(buffer[1], size); } }; class AbstractAudioPlayer { public: virtual ~AbstractAudioPlayer() {} virtual uint64_t getLastFrame() const = 0; }; class AudioFileThread : public CarlaThread { public: AudioFileThread(AbstractAudioPlayer* const player, const double sampleRate) : CarlaThread("AudioFileThread"), kPlayer(player), fNeedsRead(false), fFilePtr(nullptr) { CARLA_ASSERT(kPlayer != nullptr); static bool adInitiated = false; if (! adInitiated) { ad_init(); adInitiated = true; } ad_clear_nfo(&fFileNfo); fPool.create((uint32_t)sampleRate); } ~AudioFileThread() override { CARLA_ASSERT(! isRunning()); if (fFilePtr != nullptr) ad_close(fFilePtr); fPool.destroy(); } void startNow() { fNeedsRead = true; start(); } void stopNow() { fNeedsRead = false; stop(1000); const CarlaMutex::ScopedLocker sl(fMutex); fPool.reset(); } uint32_t getMaxFrame() const { return fFileNfo.frames > 0 ? (uint32_t)fFileNfo.frames : 0; } void setNeedsRead() { fNeedsRead = true; } bool loadFilename(const char* const filename) { CARLA_ASSERT(! isRunning()); CARLA_ASSERT(filename != nullptr); fPool.startFrame = 0; // clear old data if (fFilePtr != nullptr) { ad_close(fFilePtr); fFilePtr = nullptr; } ad_clear_nfo(&fFileNfo); // open new fFilePtr = ad_open(filename, &fFileNfo); if (fFilePtr == nullptr) return false; ad_dump_nfo(99, &fFileNfo); if (fFileNfo.frames == 0) carla_stderr("L: filename \"%s\" has 0 frames", filename); if ((fFileNfo.channels == 1 || fFileNfo.channels == 2) && fFileNfo.frames > 0) { // valid readPoll(); return true; } else { // invalid ad_clear_nfo(&fFileNfo); ad_close(fFilePtr); fFilePtr = nullptr; return false; } } void tryPutData(AudioFilePool& pool) { CARLA_ASSERT(pool.size == fPool.size); if (pool.size != fPool.size) return; if (! fMutex.tryLock()) return; //if (pool.startFrame != fPool.startFrame || pool.buffer[0] != fPool.buffer[0] || pool.buffer[1] != fPool.buffer[1]) { pool.startFrame = fPool.startFrame; FLOAT_COPY(pool.buffer[0], fPool.buffer[0], fPool.size); FLOAT_COPY(pool.buffer[1], fPool.buffer[1], fPool.size); } fMutex.unlock(); } void readPoll() { if (fFileNfo.frames <= 0 || fFilePtr == nullptr) { carla_stderr("R: no song loaded"); fNeedsRead = false; return; } int64_t lastFrame = kPlayer->getLastFrame(); int64_t readFrame = lastFrame; int64_t maxFrame = fFileNfo.frames; if (lastFrame >= maxFrame) { #if 0 if (false) //if (handlePtr->loopMode) { carla_stderr("R: DEBUG read loop, lastFrame:%i, maxFrame:%i", lastFrame, maxFrame); if (maxFrame >= static_cast(fPool.size)) { readFrame %= maxFrame; } else { readFrame = 0; lastFrame -= lastFrame % maxFrame; } } else #endif { carla_stderr("R: transport out of bounds"); fNeedsRead = false; return; } } // temp data buffer const size_t tmpSize = fPool.size * fFileNfo.channels; float tmpData[tmpSize]; FLOAT_CLEAR(tmpData, int(tmpSize)); { carla_stderr("R: poll data - reading at %li:%02li", readFrame/44100/60, (readFrame/44100) % 60); ad_seek(fFilePtr, readFrame); ssize_t i, j, rv = ad_read(fFilePtr, tmpData, tmpSize); i = j = 0; // lock, and put data asap const CarlaMutex::ScopedLocker sl(fMutex); for (ssize_t size = (ssize_t)fPool.size; i < size && j < rv; ++j) { if (fFileNfo.channels == 1) { fPool.buffer[0][i] = tmpData[j]; fPool.buffer[1][i] = tmpData[j]; i++; } else { if (j % 2 == 0) { fPool.buffer[0][i] = tmpData[j]; } else { fPool.buffer[1][i] = tmpData[j]; i++; } } } #if 0 if (false) //if (handlePtr->loopMode && i < fPool.size) { while (i < fPool.size) { for (j=0; i < fPool.size && j < rv; ++j) { if (fFileNfo.channels == 1) { fPool.buffer[0][i] = tmpData[j]; fPool.buffer[1][i] = tmpData[j]; i++; } else { if (j % 2 == 0) { fPool.buffer[0][i] = tmpData[j]; } else { fPool.buffer[1][i] = tmpData[j]; i++; } } } } } else #endif { for (ssize_t size = (ssize_t)fPool.size; i < size; ++i) { fPool.buffer[0][i] = 0.0f; fPool.buffer[1][i] = 0.0f; } } fPool.startFrame = lastFrame; } fNeedsRead = false; } protected: void run() override { while (! shouldExit()) { const uint64_t lastFrame(kPlayer->getLastFrame()); if (fNeedsRead || lastFrame < fPool.startFrame || (lastFrame - fPool.startFrame >= fPool.size*3/4 && lastFrame < (uint64_t)fFileNfo.frames)) readPoll(); else carla_msleep(50); } } private: AbstractAudioPlayer* const kPlayer; volatile bool fNeedsRead; void* fFilePtr; ADInfo fFileNfo; AudioFilePool fPool; CarlaMutex fMutex; }; #endif // AUDIO_BASE_HPP_INCLUDED