Chris@1777: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1777: Chris@1777: /* Chris@1777: Sonic Visualiser Chris@1777: An audio file viewer and annotation editor. Chris@1777: Centre for Digital Music, Queen Mary, University of London. Chris@1777: This file copyright 2006 Chris Cannam and QMUL. Chris@1777: Chris@1777: This program is free software; you can redistribute it and/or Chris@1777: modify it under the terms of the GNU General Public License as Chris@1777: published by the Free Software Foundation; either version 2 of the Chris@1777: License, or (at your option) any later version. See the file Chris@1777: COPYING included with this distribution for more information. Chris@1777: */ Chris@1777: Chris@1777: #include "BasicCompressedDenseThreeDimensionalModel.h" Chris@1777: Chris@1777: #include "base/LogRange.h" Chris@1777: Chris@1777: #include Chris@1777: #include Chris@1777: #include Chris@1777: #include Chris@1777: Chris@1777: #include Chris@1777: Chris@1777: #include Chris@1777: #include Chris@1777: Chris@1777: using std::vector; Chris@1777: Chris@1777: #include "system/System.h" Chris@1777: Chris@1777: BasicCompressedDenseThreeDimensionalModel::BasicCompressedDenseThreeDimensionalModel(sv_samplerate_t sampleRate, Chris@1777: int resolution, Chris@1777: int yBinCount, Chris@1777: bool notifyOnAdd) : Chris@1777: m_startFrame(0), Chris@1777: m_sampleRate(sampleRate), Chris@1777: m_resolution(resolution), Chris@1777: m_yBinCount(yBinCount), Chris@1777: m_minimum(0.0), Chris@1777: m_maximum(0.0), Chris@1777: m_haveExtents(false), Chris@1777: m_notifyOnAdd(notifyOnAdd), Chris@1777: m_sinceLastNotifyMin(-1), Chris@1777: m_sinceLastNotifyMax(-1), Chris@1777: m_completion(100) Chris@1777: { Chris@1777: } Chris@1777: Chris@1777: bool Chris@1777: BasicCompressedDenseThreeDimensionalModel::isOK() const Chris@1777: { Chris@1777: return true; Chris@1777: } Chris@1777: Chris@1777: bool Chris@1777: BasicCompressedDenseThreeDimensionalModel::isReady(int *completion) const Chris@1777: { Chris@1777: if (completion) *completion = getCompletion(); Chris@1777: return true; Chris@1777: } Chris@1777: Chris@1777: sv_samplerate_t Chris@1777: BasicCompressedDenseThreeDimensionalModel::getSampleRate() const Chris@1777: { Chris@1777: return m_sampleRate; Chris@1777: } Chris@1777: Chris@1777: sv_frame_t Chris@1777: BasicCompressedDenseThreeDimensionalModel::getStartFrame() const Chris@1777: { Chris@1777: return m_startFrame; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setStartFrame(sv_frame_t f) Chris@1777: { Chris@1777: m_startFrame = f; Chris@1777: } Chris@1777: Chris@1777: sv_frame_t Chris@1777: BasicCompressedDenseThreeDimensionalModel::getTrueEndFrame() const Chris@1777: { Chris@1777: return m_resolution * m_data.size() + (m_resolution - 1); Chris@1777: } Chris@1777: Chris@1777: int Chris@1777: BasicCompressedDenseThreeDimensionalModel::getResolution() const Chris@1777: { Chris@1777: return m_resolution; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setResolution(int sz) Chris@1777: { Chris@1777: m_resolution = sz; Chris@1777: } Chris@1777: Chris@1777: int Chris@1777: BasicCompressedDenseThreeDimensionalModel::getWidth() const Chris@1777: { Chris@1777: return int(m_data.size()); Chris@1777: } Chris@1777: Chris@1777: int Chris@1777: BasicCompressedDenseThreeDimensionalModel::getHeight() const Chris@1777: { Chris@1777: return m_yBinCount; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setHeight(int sz) Chris@1777: { Chris@1777: m_yBinCount = sz; Chris@1777: } Chris@1777: Chris@1777: float Chris@1777: BasicCompressedDenseThreeDimensionalModel::getMinimumLevel() const Chris@1777: { Chris@1777: return m_minimum; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setMinimumLevel(float level) Chris@1777: { Chris@1777: m_minimum = level; Chris@1777: } Chris@1777: Chris@1777: float Chris@1777: BasicCompressedDenseThreeDimensionalModel::getMaximumLevel() const Chris@1777: { Chris@1777: return m_maximum; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setMaximumLevel(float level) Chris@1777: { Chris@1777: m_maximum = level; Chris@1777: } Chris@1777: Chris@1777: BasicCompressedDenseThreeDimensionalModel::Column Chris@1777: BasicCompressedDenseThreeDimensionalModel::getColumn(int index) const Chris@1777: { Chris@1777: QReadLocker locker(&m_lock); Chris@1777: if (in_range_for(m_data, index)) return expandAndRetrieve(index); Chris@1777: else return Column(); Chris@1777: } Chris@1777: Chris@1777: float Chris@1777: BasicCompressedDenseThreeDimensionalModel::getValueAt(int index, int n) const Chris@1777: { Chris@1777: Column c = getColumn(index); Chris@1777: if (in_range_for(c, n)) return c.at(n); Chris@1777: return m_minimum; Chris@1777: } Chris@1777: Chris@1777: //static int given = 0, stored = 0; Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::truncateAndStore(int index, Chris@1777: const Column &values) Chris@1777: { Chris@1777: assert(in_range_for(m_data, index)); Chris@1777: Chris@1777: //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl; Chris@1777: Chris@1777: // The default case is to store the entire column at m_data[index] Chris@1777: // and place 0 at m_trunc[index] to indicate that it has not been Chris@1777: // truncated. We only do clever stuff if one of the clever-stuff Chris@1777: // tests works out. Chris@1777: Chris@1777: m_trunc[index] = 0; Chris@1777: if (index == 0 || Chris@1777: int(values.size()) != m_yBinCount) { Chris@1777: // given += values.size(); Chris@1777: // stored += values.size(); Chris@1777: m_data[index] = values; Chris@1777: return; Chris@1777: } Chris@1777: Chris@1777: // Maximum distance between a column and the one we refer to as Chris@1777: // the source of its truncated values. Limited by having to fit Chris@1777: // in a signed char, but in any case small values are usually Chris@1777: // better Chris@1777: static int maxdist = 6; Chris@1777: Chris@1777: bool known = false; // do we know whether to truncate at top or bottom? Chris@1777: bool top = false; // if we do know, will we truncate at top? Chris@1777: Chris@1777: // If the previous column is not truncated, then it is the only Chris@1777: // candidate for comparison. If it is truncated, then the column Chris@1777: // that it refers to is the only candidate. Either way, we only Chris@1777: // have one possible column to compare against here, and we are Chris@1777: // being careful to ensure it is not a truncated one (to avoid Chris@1777: // doing more work recursively when uncompressing). Chris@1777: int tdist = 1; Chris@1777: int ptrunc = m_trunc[index-1]; Chris@1777: if (ptrunc < 0) { Chris@1777: top = false; Chris@1777: known = true; Chris@1777: tdist = -ptrunc + 1; Chris@1777: } else if (ptrunc > 0) { Chris@1777: top = true; Chris@1777: known = true; Chris@1777: tdist = ptrunc + 1; Chris@1777: } Chris@1777: Chris@1777: Column p = expandAndRetrieve(index - tdist); Chris@1777: int h = m_yBinCount; Chris@1777: Chris@1777: if (int(p.size()) == h && tdist <= maxdist) { Chris@1777: Chris@1777: int bcount = 0, tcount = 0; Chris@1777: if (!known || !top) { Chris@1777: // count how many identical values there are at the bottom Chris@1777: for (int i = 0; i < h; ++i) { Chris@1777: if (values.at(i) == p.at(i)) ++bcount; Chris@1777: else break; Chris@1777: } Chris@1777: } Chris@1777: if (!known || top) { Chris@1777: // count how many identical values there are at the top Chris@1777: for (int i = h; i > 0; --i) { Chris@1777: if (values.at(i-1) == p.at(i-1)) ++tcount; Chris@1777: else break; Chris@1777: } Chris@1777: } Chris@1777: if (!known) top = (tcount > bcount); Chris@1777: Chris@1777: int limit = h / 4; // don't bother unless we have at least this many Chris@1777: if ((top ? tcount : bcount) > limit) { Chris@1777: Chris@1777: if (!top) { Chris@1777: // create a new column with h - bcount values from bcount up Chris@1777: Column tcol(h - bcount); Chris@1777: // given += values.size(); Chris@1777: // stored += h - bcount; Chris@1777: for (int i = bcount; i < h; ++i) { Chris@1777: tcol[i - bcount] = values.at(i); Chris@1777: } Chris@1777: m_data[index] = tcol; Chris@1777: m_trunc[index] = (signed char)(-tdist); Chris@1777: return; Chris@1777: } else { Chris@1777: // create a new column with h - tcount values from 0 up Chris@1777: Column tcol(h - tcount); Chris@1777: // given += values.size(); Chris@1777: // stored += h - tcount; Chris@1777: for (int i = 0; i < h - tcount; ++i) { Chris@1777: tcol[i] = values.at(i); Chris@1777: } Chris@1777: m_data[index] = tcol; Chris@1777: m_trunc[index] = (signed char)(tdist); Chris@1777: return; Chris@1777: } Chris@1777: } Chris@1777: } Chris@1777: Chris@1777: // given += values.size(); Chris@1777: // stored += values.size(); Chris@1777: // cout << "given: " << given << ", stored: " << stored << " (" Chris@1777: // << ((float(stored) / float(given)) * 100.f) << "%)" << endl; Chris@1777: Chris@1777: // default case if nothing wacky worked out Chris@1777: m_data[index] = values; Chris@1777: return; Chris@1777: } Chris@1777: Chris@1777: BasicCompressedDenseThreeDimensionalModel::Column Chris@1777: BasicCompressedDenseThreeDimensionalModel::rightHeight(const Column &c) const Chris@1777: { Chris@1777: if (int(c.size()) == m_yBinCount) return c; Chris@1777: else { Chris@1777: Column cc(c); Chris@1777: cc.resize(m_yBinCount, 0.0); Chris@1777: return cc; Chris@1777: } Chris@1777: } Chris@1777: Chris@1777: BasicCompressedDenseThreeDimensionalModel::Column Chris@1777: BasicCompressedDenseThreeDimensionalModel::expandAndRetrieve(int index) const Chris@1777: { Chris@1777: // See comment above m_trunc declaration in header Chris@1777: Chris@1777: assert(index >= 0 && index < int(m_data.size())); Chris@1777: Column c = m_data.at(index); Chris@1777: if (index == 0) { Chris@1777: return rightHeight(c); Chris@1777: } Chris@1777: int trunc = (int)m_trunc[index]; Chris@1777: if (trunc == 0) { Chris@1777: return rightHeight(c); Chris@1777: } Chris@1777: bool top = true; Chris@1777: int tdist = trunc; Chris@1777: if (trunc < 0) { top = false; tdist = -trunc; } Chris@1777: Column p = expandAndRetrieve(index - tdist); Chris@1777: int psize = int(p.size()), csize = int(c.size()); Chris@1777: if (psize != m_yBinCount) { Chris@1777: cerr << "WARNING: BasicCompressedDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl; Chris@1777: } Chris@1777: if (top) { Chris@1777: for (int i = csize; i < psize; ++i) { Chris@1777: c.push_back(p.at(i)); Chris@1777: } Chris@1777: } else { Chris@1777: Column cc(psize); Chris@1777: for (int i = 0; i < psize - csize; ++i) { Chris@1777: cc[i] = p.at(i); Chris@1777: } Chris@1777: for (int i = 0; i < csize; ++i) { Chris@1777: cc[i + (psize - csize)] = c.at(i); Chris@1777: } Chris@1777: return cc; Chris@1777: } Chris@1777: return c; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setColumn(int index, Chris@1777: const Column &values) Chris@1777: { Chris@1777: QWriteLocker locker(&m_lock); Chris@1777: Chris@1777: while (index >= int(m_data.size())) { Chris@1777: m_data.push_back(Column()); Chris@1777: m_trunc.push_back(0); Chris@1777: } Chris@1777: Chris@1777: bool allChange = false; Chris@1777: Chris@1777: for (int i = 0; in_range_for(values, i); ++i) { Chris@1777: float value = values[i]; Chris@1777: if (ISNAN(value) || ISINF(value)) { Chris@1777: continue; Chris@1777: } Chris@1777: if (!m_haveExtents || value < m_minimum) { Chris@1777: m_minimum = value; Chris@1777: allChange = true; Chris@1777: } Chris@1777: if (!m_haveExtents || value > m_maximum) { Chris@1777: m_maximum = value; Chris@1777: allChange = true; Chris@1777: } Chris@1777: m_haveExtents = true; Chris@1777: } Chris@1777: Chris@1777: truncateAndStore(index, values); Chris@1777: Chris@1777: // assert(values == expandAndRetrieve(index)); Chris@1777: Chris@1777: sv_frame_t windowStart = index; Chris@1777: windowStart *= m_resolution; Chris@1777: Chris@1777: if (m_notifyOnAdd) { Chris@1777: if (allChange) { Chris@1777: emit modelChanged(getId()); Chris@1777: } else { Chris@1777: emit modelChangedWithin(getId(), Chris@1777: windowStart, windowStart + m_resolution); Chris@1777: } Chris@1777: } else { Chris@1777: if (allChange) { Chris@1777: m_sinceLastNotifyMin = -1; Chris@1777: m_sinceLastNotifyMax = -1; Chris@1777: emit modelChanged(getId()); Chris@1777: } else { Chris@1777: if (m_sinceLastNotifyMin == -1 || Chris@1777: windowStart < m_sinceLastNotifyMin) { Chris@1777: m_sinceLastNotifyMin = windowStart; Chris@1777: } Chris@1777: if (m_sinceLastNotifyMax == -1 || Chris@1777: windowStart > m_sinceLastNotifyMax) { Chris@1777: m_sinceLastNotifyMax = windowStart; Chris@1777: } Chris@1777: } Chris@1777: } Chris@1777: } Chris@1777: Chris@1777: QString Chris@1777: BasicCompressedDenseThreeDimensionalModel::getBinName(int n) const Chris@1777: { Chris@1777: if (n >= 0 && (int)m_binNames.size() > n) return m_binNames[n]; Chris@1777: else return ""; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setBinName(int n, QString name) Chris@1777: { Chris@1777: while ((int)m_binNames.size() <= n) m_binNames.push_back(""); Chris@1777: m_binNames[n] = name; Chris@1777: emit modelChanged(getId()); Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setBinNames(std::vector names) Chris@1777: { Chris@1777: m_binNames = names; Chris@1777: emit modelChanged(getId()); Chris@1777: } Chris@1777: Chris@1777: bool Chris@1777: BasicCompressedDenseThreeDimensionalModel::hasBinValues() const Chris@1777: { Chris@1777: return !m_binValues.empty(); Chris@1777: } Chris@1777: Chris@1777: float Chris@1777: BasicCompressedDenseThreeDimensionalModel::getBinValue(int n) const Chris@1777: { Chris@1777: if (n < (int)m_binValues.size()) return m_binValues[n]; Chris@1777: else return 0.f; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setBinValues(std::vector values) Chris@1777: { Chris@1777: m_binValues = values; Chris@1777: } Chris@1777: Chris@1777: QString Chris@1777: BasicCompressedDenseThreeDimensionalModel::getBinValueUnit() const Chris@1777: { Chris@1777: return m_binValueUnit; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setBinValueUnit(QString unit) Chris@1777: { Chris@1777: m_binValueUnit = unit; Chris@1777: } Chris@1777: Chris@1777: bool Chris@1777: BasicCompressedDenseThreeDimensionalModel::shouldUseLogValueScale() const Chris@1777: { Chris@1777: QReadLocker locker(&m_lock); Chris@1777: Chris@1777: vector sample; Chris@1777: vector n; Chris@1777: Chris@1777: for (int i = 0; i < 10; ++i) { Chris@1777: int index = i * 10; Chris@1777: if (in_range_for(m_data, index)) { Chris@1777: const Column &c = m_data.at(index); Chris@1777: while (c.size() > sample.size()) { Chris@1777: sample.push_back(0.0); Chris@1777: n.push_back(0); Chris@1777: } Chris@1777: for (int j = 0; in_range_for(c, j); ++j) { Chris@1777: sample[j] += c.at(j); Chris@1777: ++n[j]; Chris@1777: } Chris@1777: } Chris@1777: } Chris@1777: Chris@1777: if (sample.empty()) return false; Chris@1777: for (decltype(sample)::size_type j = 0; j < sample.size(); ++j) { Chris@1777: if (n[j]) sample[j] /= n[j]; Chris@1777: } Chris@1777: Chris@1777: return LogRange::shouldUseLogScale(sample); Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::setCompletion(int completion, bool update) Chris@1777: { Chris@1777: if (m_completion != completion) { Chris@1777: m_completion = completion; Chris@1777: Chris@1777: if (completion == 100) { Chris@1777: Chris@1777: m_notifyOnAdd = true; // henceforth Chris@1777: emit modelChanged(getId()); Chris@1777: Chris@1777: } else if (!m_notifyOnAdd) { Chris@1777: Chris@1777: if (update && Chris@1777: m_sinceLastNotifyMin >= 0 && Chris@1777: m_sinceLastNotifyMax >= 0) { Chris@1777: emit modelChangedWithin(getId(), Chris@1777: m_sinceLastNotifyMin, Chris@1777: m_sinceLastNotifyMax + m_resolution); Chris@1777: m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; Chris@1777: } else { Chris@1777: emit completionChanged(getId()); Chris@1777: } Chris@1777: } else { Chris@1777: emit completionChanged(getId()); Chris@1777: } Chris@1777: } Chris@1777: } Chris@1777: Chris@1777: int Chris@1777: BasicCompressedDenseThreeDimensionalModel::getCompletion() const Chris@1777: { Chris@1777: return m_completion; Chris@1777: } Chris@1777: Chris@1777: QString Chris@1815: BasicCompressedDenseThreeDimensionalModel::getDelimitedDataHeaderLine(QString delimiter, Chris@1815: DataExportOptions) const Chris@1815: { Chris@1815: QStringList list; Chris@1815: for (int i = 0; i < m_yBinCount; ++i) { Chris@1815: list << QString("Bin%1").arg(i+1); Chris@1815: } Chris@1815: return list.join(delimiter); Chris@1815: } Chris@1815: Chris@1815: QString Chris@1777: BasicCompressedDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter, Chris@1777: DataExportOptions, Chris@1777: sv_frame_t startFrame, Chris@1777: sv_frame_t duration) const Chris@1777: { Chris@1777: QReadLocker locker(&m_lock); Chris@1777: QString s; Chris@1777: for (int i = 0; in_range_for(m_data, i); ++i) { Chris@1777: Column c = getColumn(i); Chris@1777: sv_frame_t fr = m_startFrame + i * m_resolution; Chris@1777: if (fr >= startFrame && fr < startFrame + duration) { Chris@1777: QStringList list; Chris@1777: for (int j = 0; in_range_for(c, j); ++j) { Chris@1777: list << QString("%1").arg(c.at(j)); Chris@1777: } Chris@1777: s += list.join(delimiter) + "\n"; Chris@1777: } Chris@1777: } Chris@1777: return s; Chris@1777: } Chris@1777: Chris@1777: void Chris@1777: BasicCompressedDenseThreeDimensionalModel::toXml(QTextStream &out, Chris@1777: QString indent, Chris@1777: QString extraAttributes) const Chris@1777: { Chris@1777: QReadLocker locker(&m_lock); Chris@1777: Chris@1777: // For historical reasons we read and write "resolution" as "windowSize". Chris@1777: Chris@1777: // Our dataset doesn't have its own export ID, we just use Chris@1777: // ours. Actually any model could do that, since datasets aren't Chris@1777: // in the same id-space as models when re-read Chris@1777: Chris@1777: SVDEBUG << "BasicCompressedDenseThreeDimensionalModel::toXml" << endl; Chris@1777: Chris@1777: Model::toXml Chris@1777: (out, indent, Chris@1777: QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" startFrame=\"%6\" %7") Chris@1777: .arg(m_resolution) Chris@1777: .arg(m_yBinCount) Chris@1777: .arg(m_minimum) Chris@1777: .arg(m_maximum) Chris@1777: .arg(getExportId()) Chris@1777: .arg(m_startFrame) Chris@1777: .arg(extraAttributes)); Chris@1777: Chris@1777: out << indent; Chris@1777: out << QString("\n") Chris@1777: .arg(getExportId()); Chris@1777: Chris@1777: for (int i = 0; in_range_for(m_binNames, i); ++i) { Chris@1777: if (m_binNames[i] != "") { Chris@1777: out << indent + " "; Chris@1777: out << QString("\n") Chris@1777: .arg(i).arg(m_binNames[i]); Chris@1777: } Chris@1777: } Chris@1777: Chris@1777: for (int i = 0; in_range_for(m_data, i); ++i) { Chris@1777: Column c = getColumn(i); Chris@1777: out << indent + " "; Chris@1777: out << QString("").arg(i); Chris@1777: for (int j = 0; in_range_for(c, j); ++j) { Chris@1777: if (j > 0) out << " "; Chris@1777: out << c.at(j); Chris@1777: } Chris@1777: out << QString("\n"); Chris@1777: out.flush(); Chris@1777: } Chris@1777: Chris@1777: out << indent + "\n"; Chris@1777: } Chris@1777: Chris@1777: