summaryrefslogtreecommitdiff
path: root/src/cachemanager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/cachemanager.cc')
-rw-r--r--src/cachemanager.cc326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/cachemanager.cc b/src/cachemanager.cc
new file mode 100644
index 0000000..61fdb3e
--- /dev/null
+++ b/src/cachemanager.cc
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * cachemanager.cc
+ *
+ * Fri Apr 10 10:39:24 CEST 2015
+ * Copyright 2015 Jonas Suhr Christensen
+ * jsc@umbraculum.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo 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
+ * (at your option) any later version.
+ *
+ * DrumGizmo 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "cachemanager.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sndfile.h>
+
+#include <hugin.hpp>
+
+static sample_t nodata[FRAMESIZE];
+
+#define BUFFER_SIZE 4092
+
+static size_t readChunk(std::string filename, int filechannel, size_t pos,
+ size_t num_samples, sample_t* buf)
+{
+ SF_INFO sf_info;
+ SNDFILE *fh = sf_open(filename.c_str(), SFM_READ, &sf_info);
+ if(!fh) {
+ ERR(audiofile,"SNDFILE Error (%s): %s\n",
+ filename.c_str(), sf_strerror(fh));
+ return 0;
+ }
+
+ if(pos > sf_info.frames) {
+ return 0;
+ }
+
+ sf_seek(fh, pos, SEEK_SET);
+
+ size_t size = sf_info.frames - pos;
+ if(size > num_samples) size = num_samples;
+
+ sample_t* data = buf;
+ if(sf_info.channels == 1) {
+ size = sf_readf_float(fh, data, size);
+ } else {
+ // check filechannel exists
+ if(filechannel >= sf_info.channels) {
+ filechannel = sf_info.channels - 1;
+ }
+ sample_t buffer[BUFFER_SIZE];
+ int readsize = BUFFER_SIZE / sf_info.channels;
+ int totalread = 0;
+ int read;
+ do {
+ read = sf_readf_float(fh, buffer, readsize);
+ for (int i = 0; i < read && totalread < (int)size; i++) {
+ data[totalread++] = buffer[i * sf_info.channels + filechannel];
+ }
+ } while(read > 0 && totalread < (int)size && totalread < sf_info.frames);
+ // set data size to total bytes read
+ size = totalread;
+ }
+
+ sf_close(fh);
+
+ return size;
+}
+
+CacheManager::CacheManager() {}
+
+CacheManager::~CacheManager()
+{
+ deinit();
+}
+
+void CacheManager::init(size_t poolsize, bool threaded)
+{
+ this->threaded = threaded;
+
+ for(size_t i = 0; i < FRAMESIZE; i++) {
+ nodata[i] = 0;
+ }
+
+ id2cache.resize(poolsize);
+ for(size_t i = 0; i < poolsize; i++) {
+ availableids.push_back(i);
+ }
+
+ running = true;
+ if(threaded) {
+ run();
+ sem_run.wait();
+ }
+}
+
+void CacheManager::deinit()
+{
+ if(!running) return;
+ running = false;
+ if(threaded) {
+ sem.post();
+ wait_stop();
+ }
+}
+
+// Invariant: initial_samples_needed < preloaded audio data
+// Proposal: preloaded > 2 x CHUNKSIZE? So that we can fill c.front immediatly on open
+sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed,
+ int channel, cacheid_t &id)
+{
+ {
+ MutexAutolock l(m_ids);
+ if(availableids.empty()) {
+ id = CACHE_DUMMYID;
+ } else {
+ id = availableids.front();
+ availableids.pop_front();
+ }
+ }
+
+ if(id == CACHE_DUMMYID) {
+ return nodata;
+ }
+
+ cache_t c;
+ c.file = file;
+ c.channel = channel;
+ c.pos = initial_samples_needed;
+ c.localpos = 0;
+ c.front = new sample_t[CHUNKSIZE];
+ c.back = new sample_t[CHUNKSIZE];
+
+ size_t size = CHUNKSIZE;
+ if(size > (file->preloadedsize - c.pos)) size = (file->preloadedsize - c.pos);
+ memcpy(c.front, c.file->data + c.pos, size * sizeof(sample_t));
+ c.ready = false;
+ c.pos += size;
+
+ // Increase audio ref count
+
+ {
+ MutexAutolock l(m_ids);
+ id2cache[id] = c;
+ }
+
+ // Only load next buffer if there are more data in the file to be loaded...
+ if(c.pos < file->size) {
+ cevent_t e =
+ createLoadNextEvent(c.file, c.channel, c.pos, c.back);
+ e.ready = &id2cache[id].ready;
+ pushEvent(e);
+ }
+
+ return file->data; // preloaded data
+}
+
+sample_t *CacheManager::next(cacheid_t id, size_t &size)
+{
+ size = FRAMESIZE;
+
+ if(id == CACHE_DUMMYID) {
+ return nodata;
+ }
+
+ cache_t& c = id2cache[id];
+ if(c.localpos < CHUNKSIZE) {
+ sample_t *s = c.front + c.localpos;
+ c.localpos += size;
+ return s;
+ }
+
+ if(!c.ready) {
+ printf("#%d: NOT READY!\n", id); // TODO: Count and show in UI?
+ }
+
+ // Swap buffers
+ sample_t *tmp = c.front;
+ c.front = c.back;
+ c.back = tmp;
+
+ c.localpos = size; // Next time we go here we have already read the first frame.
+
+ c.pos += CHUNKSIZE;
+
+ if(c.pos < c.file->size) {
+ cevent_t e = createLoadNextEvent(c.file, c.channel, c.pos, c.back);
+ c.ready = false;
+ e.ready = &c.ready;
+ pushEvent(e);
+ }
+
+ return c.front;
+}
+
+void CacheManager::close(cacheid_t id)
+{
+ if(id == CACHE_DUMMYID) {
+ return;
+ }
+
+ cevent_t e = createCloseEvent(id);
+ pushEvent(e);
+}
+
+void CacheManager::handleLoadNextEvent(cevent_t &e)
+{
+#if 0 // memcpy
+ size_t size = CHUNKSIZE;
+ if(size > (e.file->size - e.pos)) {
+ size = (e.file->size - e.pos);
+ }
+ memcpy(e.buffer, e.file->data + e.pos, size * sizeof(sample_t));
+#elif 1 // diskread
+ //memset(e.buffer, 0, CHUNKSIZE * sizeof(sample_t));
+ readChunk(e.file->filename, e.channel, e.pos, CHUNKSIZE, e.buffer);
+#endif
+ *e.ready = true;
+}
+
+void CacheManager::handleCloseEvent(cevent_t &e)
+{
+ cache_t& c = id2cache[e.id];
+ delete[] c.front;
+ delete[] c.back;
+
+ {
+ MutexAutolock l(m_ids);
+ availableids.push_back(e.id);
+ }
+
+ // TODO: Count down ref coutner on c.file and close it if 0.
+}
+
+
+void CacheManager::handleEvent(cevent_t &e)
+{
+ switch(e.cmd) {
+ case LOADNEXT:
+ handleLoadNextEvent(e);
+ break;
+ case CLOSE:
+ handleCloseEvent(e);
+ break;
+ }
+}
+
+void CacheManager::thread_main()
+{
+ sem_run.post(); // Signal that the thread has been started
+
+ while(running) {
+ sem.wait();
+
+ m_events.lock();
+ if(eventqueue.empty()) {
+ m_events.unlock();
+ continue;
+ }
+
+ cevent_t e = eventqueue.front();
+ eventqueue.pop_front();
+ m_events.unlock();
+
+ // TODO: Skip event if e.pos < cache.pos
+ // if(!e.active) continue;
+
+ handleEvent(e);
+ }
+}
+
+void CacheManager::pushEvent(cevent_t e)
+{
+ if(!threaded) {
+ handleEvent(e);
+ return;
+ }
+
+ // Check that if event should be merged (Maybe by event queue (ie. push
+ // in front).
+ {
+ MutexAutolock l(m_events);
+ eventqueue.push_back(e);
+ }
+
+ sem.post();
+}
+
+CacheManager::cevent_t
+CacheManager::createLoadNextEvent(AudioFile *file, size_t channel, size_t pos,
+ sample_t* buffer)
+{
+ cevent_t e;
+ e.cmd = LOADNEXT;
+ e.pos = pos;
+ e.buffer = buffer;
+ e.file = file;
+ e.channel = channel;
+ return e;
+}
+
+CacheManager::cevent_t
+CacheManager::createCloseEvent(cacheid_t id)
+{
+ cevent_t e;
+ e.cmd = CLOSE;
+ e.id = id;
+ return e;
+}