# HG changeset patch # User Chris Cannam # Date 1146572861 0 # Node ID 7de62a884810da2d23f4049cee90bf9573bfd44a # Parent e076e676439b26a55bee515a5a755e348a9809e3 * Start factoring out the spectrogram's FFT cache into a separate set of classes that will permit a choice of disk or memory cache strategies diff -r e076e676439b -r 7de62a884810 base/FFTCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/FFTCache.cpp Tue May 02 12:27:41 2006 +0000 @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + 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 (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "FFTCache.h" +#include "System.h" + +#include + +FFTMemoryCache::FFTMemoryCache() : + m_width(0), + m_height(0), + m_magnitude(0), + m_phase(0), + m_factor(0) +{ +} + +FFTMemoryCache::~FFTMemoryCache() +{ + std::cerr << "FFTMemoryCache[" << this << "]::~Cache" << std::endl; + + for (size_t i = 0; i < m_width; ++i) { + if (m_magnitude && m_magnitude[i]) free(m_magnitude[i]); + if (m_phase && m_phase[i]) free(m_phase[i]); + } + + if (m_magnitude) free(m_magnitude); + if (m_phase) free(m_phase); + if (m_factor) free(m_factor); +} + +void +FFTMemoryCache::resize(size_t width, size_t height) +{ + std::cerr << "FFTMemoryCache[" << this << "]::resize(" << width << "x" << height << " = " << width*height << ")" << std::endl; + + if (m_width == width && m_height == height) return; + + resize(m_magnitude, width, height); + resize(m_phase, width, height); + + m_factor = (float *)realloc(m_factor, width * sizeof(float)); + + m_width = width; + m_height = height; + + std::cerr << "done, width = " << m_width << " height = " << m_height << std::endl; +} + +void +FFTMemoryCache::resize(uint16_t **&array, size_t width, size_t height) +{ + for (size_t i = width; i < m_width; ++i) { + free(array[i]); + } + + if (width != m_width) { + array = (uint16_t **)realloc(array, width * sizeof(uint16_t *)); + if (!array) throw std::bad_alloc(); + MUNLOCK(array, width * sizeof(uint16_t *)); + } + + for (size_t i = m_width; i < width; ++i) { + array[i] = 0; + } + + for (size_t i = 0; i < width; ++i) { + array[i] = (uint16_t *)realloc(array[i], height * sizeof(uint16_t)); + if (!array[i]) throw std::bad_alloc(); + MUNLOCK(array[i], height * sizeof(uint16_t)); + } +} + +void +FFTMemoryCache::reset() +{ + for (size_t x = 0; x < m_width; ++x) { + for (size_t y = 0; y < m_height; ++y) { + m_magnitude[x][y] = 0; + m_phase[x][y] = 0; + } + m_factor[x] = 1.0; + } +} + diff -r e076e676439b -r 7de62a884810 base/FFTCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/FFTCache.h Tue May 02 12:27:41 2006 +0000 @@ -0,0 +1,149 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + 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 (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _FFT_CACHE_H_ +#define _FFT_CACHE_H_ + +#include +#include + +class FFTCacheBase +{ +public: + virtual ~FFTCacheBase() { } + + virtual size_t getWidth() const = 0; + virtual size_t getHeight() const = 0; + + virtual void resize(size_t width, size_t height) = 0; + virtual void reset() = 0; // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const = 0; + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const = 0; + virtual float getPhaseAt(size_t x, size_t y) const = 0; + + virtual bool isLocalPeak(size_t x, size_t y) const = 0; + virtual bool isOverThreshold(size_t x, size_t y, float threshold) const = 0; + + virtual void setNormalizationFactor(size_t x, float factor) = 0; + virtual void setMagnitudeAt(size_t x, size_t y, float mag) = 0; + virtual void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) = 0; + virtual void setPhaseAt(size_t x, size_t y, float phase) = 0; + + virtual QColor getColour(unsigned char index) const = 0; + virtual void setColour(unsigned char index, QColor colour) = 0; + +protected: + FFTCacheBase() { } +}; + + +/** + * For the in-memory FFT cache, we would like to cache magnitude with + * enough resolution to have gain applied afterwards and determine + * whether something is a peak or not, and also cache phase rather + * than only phase-adjusted frequency so that we don't have to + * recalculate if switching between phase and magnitude displays. At + * the same time, we don't want to take up too much memory. It's not + * expected to be accurate enough to be used as input for DSP or + * resynthesis code. + * + * This implies probably 16 bits for a normalized magnitude and at + * most 16 bits for phase. + * + * Each column's magnitudes are expected to be stored normalized + * to [0,1] with respect to the column, so the normalization + * factor should be calculated before all values in a column, and + * set appropriately. + */ + +class FFTMemoryCache : public FFTCacheBase +{ +public: + FFTMemoryCache(); // of size zero, call resize() before using + virtual ~FFTMemoryCache(); + + virtual size_t getWidth() const { return m_width; } + virtual size_t getHeight() const { return m_height; } + + virtual void resize(size_t width, size_t height); + virtual void reset(); // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const { + return getNormalizedMagnitudeAt(x, y) * m_factor[x]; + } + + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const { + return float(m_magnitude[x][y]) / 65535.0; + } + + virtual float getPhaseAt(size_t x, size_t y) const { + int16_t i = (int16_t)m_phase[x][y]; + return (float(i) / 32767.0) * M_PI; + } + + virtual bool isLocalPeak(size_t x, size_t y) const { + if (y > 0 && m_magnitude[x][y] < m_magnitude[x][y-1]) return false; + if (y < m_height-1 && m_magnitude[x][y] < m_magnitude[x][y+1]) return false; + return true; + } + + virtual bool isOverThreshold(size_t x, size_t y, float threshold) const { + if (threshold == 0.0) return true; + return getMagnitudeAt(x, y) > threshold; + } + + virtual void setNormalizationFactor(size_t x, float factor) { + if (x < m_width) m_factor[x] = factor; + } + + virtual void setMagnitudeAt(size_t x, size_t y, float mag) { + // norm factor must already be set + setNormalizedMagnitudeAt(x, y, mag / m_factor[x]); + } + + virtual void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) { + if (x < m_width && y < m_height) { + m_magnitude[x][y] = uint16_t(norm * 65535.0); + } + } + + virtual void setPhaseAt(size_t x, size_t y, float phase) { + // phase in range -pi -> pi + if (x < m_width && y < m_height) { + m_phase[x][y] = uint16_t(int16_t((phase * 32767) / M_PI)); + } + } + + virtual QColor getColour(unsigned char index) const { + return m_colours[index]; + } + + virtual void setColour(unsigned char index, QColor colour) { + m_colours[index] = colour; + } + +private: + size_t m_width; + size_t m_height; + uint16_t **m_magnitude; + uint16_t **m_phase; + float *m_factor; + QColor m_colours[256]; + + void resize(uint16_t **&, size_t, size_t); +}; + +#endif diff -r e076e676439b -r 7de62a884810 base/MatrixFileCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/MatrixFileCache.cpp Tue May 02 12:27:41 2006 +0000 @@ -0,0 +1,266 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + 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 (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "MatrixFileCache.h" +#include "base/TempDirectory.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) : + m_fd(-1), + m_off(-1), + m_mode(mode), + m_width(0), + m_height(0), + m_rx(0), + m_rw(0), + m_range(0), + m_headerSize(2 * sizeof(size_t)) +{ + QDir tempDir(TempDirectory::instance()->getPath()); + QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); + bool newFile = !QFileInfo(fileName).exists(); + + if (newFile && mode == ReadOnly) { + std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: Read-only mode " + << "specified, but cache file does not exist" << std::endl; + return; + } + + int flags = 0; + mode_t fmode = S_IRUSR | S_IWUSR; + + if (mode == ReadWrite) { + flags = O_RDWR | O_CREAT; + } else { + flags = O_RDONLY; + } + + if ((m_fd = ::open(fileName.toLocal8Bit(), flags, mode)) < 0) { + ::perror("Open failed"); + std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: " + << "Failed to open cache file \"" + << fileName.toStdString() << "\""; + if (mode == ReadWrite) std::cerr << " for writing"; + std::cerr << std::endl; + } + + if (newFile) { + resize(0, 0); // write header + } else { + size_t header[2]; + if (::read(m_fd, header, 2 * sizeof(size_t))) { + perror("Read failed"); + std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: " + << "Failed to read header" << std::endl; + return; + } + m_width = header[0]; + m_height = header[1]; + seekTo(0, 0); + } + + std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << m_width << "x" << m_height << std::endl; + +} + +MatrixFileCache::~MatrixFileCache() +{ + if (m_fd >= 0) { + if (::close(m_fd) < 0) { + ::perror("MatrixFileCache::~MatrixFileCache: close failed"); + } + } +} + +size_t +MatrixFileCache::getWidth() const +{ + return m_width; +} + +size_t +MatrixFileCache::getHeight() const +{ + return m_height; +} + +void +MatrixFileCache::resize(size_t w, size_t h) +{ + if (m_mode != ReadWrite) { + std::cerr << "ERROR: MatrixFileCache::resize called on read-only cache" + << std::endl; + return; + } + + off_t off = m_headerSize + (w * h * sizeof(float)); + + if (w * h > m_width * m_height) { + + if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) { + ::perror("Seek failed"); + std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", " + << h << "): seek failed, cannot resize" << std::endl; + return; + } + + // guess this requires efficient support for sparse files + + float f(0); + if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) { + ::perror("WARNING: MatrixFileCache::resize: write failed"); + } + + } else { + + if (::ftruncate(m_fd, off) < 0) { + ::perror("MatrixFileCache::resize: ftruncate failed"); + } + } + + m_width = 0; + m_height = 0; + m_off = 0; + + if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) { + ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed"); + return; + } + + size_t header[2]; + header[0] = w; + header[1] = h; + if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) { + ::perror("ERROR: MatrixFileCache::resize: Failed to write header"); + return; + } + + m_width = w; + m_height = h; + + seekTo(0, 0); +} + +void +MatrixFileCache::reset() +{ + if (m_mode != ReadWrite) { + std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache" + << std::endl; + return; + } + + //... +} + +void +MatrixFileCache::setRangeOfInterest(size_t x, size_t width) +{ +} + +float +MatrixFileCache::getValueAt(size_t x, size_t y) const +{ + if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) { + return m_range[x - m_rx][y]; + } + + if (!seekTo(x, y)) return 0.f; + float value; + if (::read(m_fd, &value, sizeof(float)) != sizeof(float)) { + ::perror("MatrixFileCache::getValueAt: read failed"); + return 0.f; + } + return value; +} + +void +MatrixFileCache::getColumnAt(size_t x, float *values) const +{ + if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) { + for (size_t y = 0; y < m_height; ++y) { + values[y] = m_range[x - m_rx][y]; + } + } + + if (!seekTo(x, 0)) return; + if (::read(m_fd, values, m_height * sizeof(float)) != m_height * sizeof(float)) { + ::perror("MatrixFileCache::getColumnAt: read failed"); + } + return; +} + +void +MatrixFileCache::setValueAt(size_t x, size_t y, float value) +{ + if (m_mode != ReadWrite) { + std::cerr << "ERROR: MatrixFileCache::setValueAt called on read-only cache" + << std::endl; + return; + } + + if (!seekTo(x, y)) return; + if (::write(m_fd, &value, sizeof(float)) != sizeof(float)) { + ::perror("WARNING: MatrixFileCache::setValueAt: write failed"); + } + + //... update range as appropriate +} + +void +MatrixFileCache::setColumnAt(size_t x, float *values) +{ + if (m_mode != ReadWrite) { + std::cerr << "ERROR: MatrixFileCache::setColumnAt called on read-only cache" + << std::endl; + return; + } + + if (!seekTo(x, 0)) return; + if (::write(m_fd, values, m_height * sizeof(float)) != m_height * sizeof(float)) { + ::perror("WARNING: MatrixFileCache::setColumnAt: write failed"); + } + + //... update range as appropriate +} + +bool +MatrixFileCache::seekTo(size_t x, size_t y) const +{ + off_t off = m_headerSize + (x * m_height + y) * sizeof(float); + if (off == m_off) return true; + + if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { + ::perror("Seek failed"); + std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y + << ") failed" << std::endl; + return false; + } + + m_off = off; + return true; +} + diff -r e076e676439b -r 7de62a884810 base/MatrixFileCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/MatrixFileCache.h Tue May 02 12:27:41 2006 +0000 @@ -0,0 +1,66 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + 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 (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _MATRIX_FILE_CACHE_H_ +#define _MATRIX_FILE_CACHE_H_ + +#include +#include + +// This class is _not_ thread safe. Each instance must only be used +// within a single thread. You may however have as many instances as +// you like referring to the same file in separate threads. + +class MatrixFileCache +{ +public: + enum Mode { ReadOnly, ReadWrite }; + + MatrixFileCache(QString fileBase, Mode mode); + virtual ~MatrixFileCache(); + + size_t getWidth() const; + size_t getHeight() const; + + void resize(size_t width, size_t height); + void reset(); + + void setRangeOfInterest(size_t x, size_t width); + + float getValueAt(size_t x, size_t y) const; + void getColumnAt(size_t x, float *values) const; +// float getColumnMaximum(size_t x) const; +// float getColumnMinimum(size_t x) const; + + void setValueAt(size_t x, size_t y, float value); + void setColumnAt(size_t x, float *values); + +protected: + int m_fd; + Mode m_mode; + size_t m_width; + size_t m_height; + size_t m_rx; + size_t m_rw; + float **m_range; + size_t m_headerSize; + + mutable off_t m_off; + + bool seekTo(size_t x, size_t y) const; +}; + +#endif +