Chris@152: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@152: Chris@152: /* Chris@152: Sonic Visualiser Chris@152: An audio file viewer and annotation editor. Chris@152: Centre for Digital Music, Queen Mary, University of London. Chris@202: This file copyright 2006 Chris Cannam and QMUL. Chris@152: Chris@152: This program is free software; you can redistribute it and/or Chris@152: modify it under the terms of the GNU General Public License as Chris@152: published by the Free Software Foundation; either version 2 of the Chris@152: License, or (at your option) any later version. See the file Chris@152: COPYING included with this distribution for more information. Chris@152: */ Chris@152: Chris@152: #include "EditableDenseThreeDimensionalModel.h" Chris@152: Chris@478: #include "base/LogRange.h" Chris@478: Chris@152: #include Chris@387: #include Chris@536: #include Chris@536: #include Chris@387: Chris@181: #include Chris@181: Chris@256: #include Chris@534: #include Chris@256: Chris@152: EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(size_t sampleRate, Chris@152: size_t resolution, Chris@152: size_t yBinCount, Chris@535: CompressionType compression, Chris@152: bool notifyOnAdd) : Chris@152: m_sampleRate(sampleRate), Chris@152: m_resolution(resolution), Chris@152: m_yBinCount(yBinCount), Chris@535: m_compression(compression), Chris@152: m_minimum(0.0), Chris@152: m_maximum(0.0), Chris@256: m_haveExtents(false), Chris@152: m_notifyOnAdd(notifyOnAdd), Chris@152: m_sinceLastNotifyMin(-1), Chris@152: m_sinceLastNotifyMax(-1), Chris@152: m_completion(100) Chris@152: { Chris@152: } Chris@152: Chris@152: bool Chris@152: EditableDenseThreeDimensionalModel::isOK() const Chris@152: { Chris@152: return true; Chris@152: } Chris@152: Chris@152: size_t Chris@152: EditableDenseThreeDimensionalModel::getSampleRate() const Chris@152: { Chris@152: return m_sampleRate; Chris@152: } Chris@152: Chris@152: size_t Chris@152: EditableDenseThreeDimensionalModel::getStartFrame() const Chris@152: { Chris@152: return 0; Chris@152: } Chris@152: Chris@152: size_t Chris@152: EditableDenseThreeDimensionalModel::getEndFrame() const Chris@152: { Chris@152: return m_resolution * m_data.size() + (m_resolution - 1); Chris@152: } Chris@152: Chris@152: Model * Chris@152: EditableDenseThreeDimensionalModel::clone() const Chris@152: { Chris@536: QReadLocker locker(&m_lock); Chris@534: Chris@152: EditableDenseThreeDimensionalModel *model = Chris@152: new EditableDenseThreeDimensionalModel Chris@535: (m_sampleRate, m_resolution, m_yBinCount, m_compression); Chris@152: Chris@152: model->m_minimum = m_minimum; Chris@152: model->m_maximum = m_maximum; Chris@256: model->m_haveExtents = m_haveExtents; Chris@152: Chris@152: for (size_t i = 0; i < m_data.size(); ++i) { Chris@533: model->setColumn(i, m_data.at(i)); Chris@152: } Chris@152: Chris@152: return model; Chris@152: } Chris@152: Chris@152: size_t Chris@152: EditableDenseThreeDimensionalModel::getResolution() const Chris@152: { Chris@152: return m_resolution; Chris@152: } Chris@152: Chris@152: void Chris@152: EditableDenseThreeDimensionalModel::setResolution(size_t sz) Chris@152: { Chris@152: m_resolution = sz; Chris@152: } Chris@152: Chris@152: size_t Chris@182: EditableDenseThreeDimensionalModel::getWidth() const Chris@182: { Chris@182: return m_data.size(); Chris@182: } Chris@182: Chris@182: size_t Chris@182: EditableDenseThreeDimensionalModel::getHeight() const Chris@152: { Chris@152: return m_yBinCount; Chris@152: } Chris@152: Chris@152: void Chris@182: EditableDenseThreeDimensionalModel::setHeight(size_t sz) Chris@152: { Chris@152: m_yBinCount = sz; Chris@152: } Chris@152: Chris@152: float Chris@152: EditableDenseThreeDimensionalModel::getMinimumLevel() const Chris@152: { Chris@152: return m_minimum; Chris@152: } Chris@152: Chris@152: void Chris@152: EditableDenseThreeDimensionalModel::setMinimumLevel(float level) Chris@152: { Chris@152: m_minimum = level; Chris@152: } Chris@152: Chris@152: float Chris@152: EditableDenseThreeDimensionalModel::getMaximumLevel() const Chris@152: { Chris@152: return m_maximum; Chris@152: } Chris@152: Chris@152: void Chris@152: EditableDenseThreeDimensionalModel::setMaximumLevel(float level) Chris@152: { Chris@152: m_maximum = level; Chris@152: } Chris@152: Chris@533: EditableDenseThreeDimensionalModel::Column Chris@533: EditableDenseThreeDimensionalModel::getColumn(size_t index) const Chris@152: { Chris@536: QReadLocker locker(&m_lock); Chris@534: if (index >= m_data.size()) return Column(); Chris@534: return expandAndRetrieve(index); Chris@152: } Chris@152: Chris@152: float Chris@182: EditableDenseThreeDimensionalModel::getValueAt(size_t index, size_t n) const Chris@152: { Chris@534: Column c = getColumn(index); Chris@535: if (n < c.size()) return c.at(n); Chris@534: return m_minimum; Chris@534: } Chris@152: Chris@535: //static int given = 0, stored = 0; Chris@534: Chris@534: void Chris@534: EditableDenseThreeDimensionalModel::truncateAndStore(size_t index, Chris@534: const Column &values) Chris@534: { Chris@534: assert(index < m_data.size()); Chris@534: Chris@534: //std::cout << "truncateAndStore(" << index << ", " << values.size() << ")" << std::endl; Chris@534: Chris@535: // The default case is to store the entire column at m_data[index] Chris@535: // and place 0 at m_trunc[index] to indicate that it has not been Chris@535: // truncated. We only do clever stuff if one of the clever-stuff Chris@535: // tests works out. Chris@535: Chris@534: m_trunc[index] = 0; Chris@535: if (index == 0 || Chris@535: m_compression == NoCompression || Chris@535: values.size() != m_yBinCount) { Chris@535: // given += values.size(); Chris@535: // stored += values.size(); Chris@534: m_data[index] = values; Chris@534: return; Chris@152: } Chris@152: Chris@535: // Maximum distance between a column and the one we refer to as Chris@535: // the source of its truncated values. Limited by having to fit Chris@535: // in a signed char, but in any case small values are usually Chris@535: // better Chris@535: static int maxdist = 6; Chris@534: Chris@535: bool known = false; // do we know whether to truncate at top or bottom? Chris@535: bool top = false; // if we do know, will we truncate at top? Chris@534: Chris@535: // If the previous column is not truncated, then it is the only Chris@535: // candidate for comparison. If it is truncated, then the column Chris@535: // that it refers to is the only candidate. Either way, we only Chris@535: // have one possible column to compare against here, and we are Chris@535: // being careful to ensure it is not a truncated one (to avoid Chris@535: // doing more work recursively when uncompressing). Chris@534: int tdist = 1; Chris@534: int ptrunc = m_trunc[index-1]; Chris@534: if (ptrunc < 0) { Chris@534: top = false; Chris@534: known = true; Chris@534: tdist = -ptrunc + 1; Chris@534: } else if (ptrunc > 0) { Chris@534: top = true; Chris@534: known = true; Chris@534: tdist = ptrunc + 1; Chris@534: } Chris@534: Chris@534: Column p = expandAndRetrieve(index - tdist); Chris@534: int h = m_yBinCount; Chris@534: Chris@534: if (p.size() == h && tdist <= maxdist) { Chris@534: Chris@534: int bcount = 0, tcount = 0; Chris@534: if (!known || !top) { Chris@535: // count how many identical values there are at the bottom Chris@534: for (int i = 0; i < h; ++i) { Chris@534: if (values.at(i) == p.at(i)) ++bcount; Chris@534: else break; Chris@534: } Chris@534: } Chris@534: if (!known || top) { Chris@535: // count how many identical values there are at the top Chris@534: for (int i = h; i > 0; --i) { Chris@534: if (values.at(i-1) == p.at(i-1)) ++tcount; Chris@534: else break; Chris@534: } Chris@534: } Chris@534: if (!known) top = (tcount > bcount); Chris@534: Chris@535: int limit = h / 4; // don't bother unless we have at least this many Chris@534: if ((top ? tcount : bcount) > limit) { Chris@534: Chris@534: if (!top) { Chris@535: // create a new column with h - bcount values from bcount up Chris@534: Column tcol(h - bcount); Chris@535: // given += values.size(); Chris@535: // stored += h - bcount; Chris@534: for (int i = bcount; i < h; ++i) { Chris@534: tcol[i - bcount] = values.at(i); Chris@534: } Chris@534: m_data[index] = tcol; Chris@534: m_trunc[index] = -tdist; Chris@534: return; Chris@534: } else { Chris@535: // create a new column with h - tcount values from 0 up Chris@534: Column tcol(h - tcount); Chris@535: // given += values.size(); Chris@535: // stored += h - tcount; Chris@534: for (int i = 0; i < h - tcount; ++i) { Chris@534: tcol[i] = values.at(i); Chris@534: } Chris@534: m_data[index] = tcol; Chris@534: m_trunc[index] = tdist; Chris@534: return; Chris@534: } Chris@534: } Chris@534: } Chris@534: Chris@535: // given += values.size(); Chris@535: // stored += values.size(); Chris@534: // std::cout << "given: " << given << ", stored: " << stored << " (" Chris@534: // << ((float(stored) / float(given)) * 100.f) << "%)" << std::endl; Chris@534: Chris@535: // default case if nothing wacky worked out Chris@534: m_data[index] = values; Chris@534: return; Chris@534: } Chris@534: Chris@534: EditableDenseThreeDimensionalModel::Column Chris@534: EditableDenseThreeDimensionalModel::expandAndRetrieve(size_t index) const Chris@534: { Chris@535: // See comment above m_trunc declaration in header Chris@535: Chris@534: assert(index < m_data.size()); Chris@534: Column c = m_data.at(index); Chris@534: if (index == 0) { Chris@534: return c; Chris@534: } Chris@534: int trunc = (int)m_trunc[index]; Chris@534: if (trunc == 0) { Chris@534: return c; Chris@534: } Chris@534: bool top = true; Chris@534: int tdist = trunc; Chris@534: if (trunc < 0) { top = false; tdist = -trunc; } Chris@534: Column p = expandAndRetrieve(index - tdist); Chris@537: int psize = p.size(), csize = c.size(); Chris@537: if (psize != m_yBinCount) { Chris@534: std::cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << std::endl; Chris@534: } Chris@534: if (top) { Chris@537: for (int i = csize; i < psize; ++i) { Chris@534: c.push_back(p.at(i)); Chris@534: } Chris@534: } else { Chris@593: // push_front is very slow on QVector -- but not enough to Chris@593: // make it desirable to choose a different container, since Chris@593: // QVector has all the other advantages for us. easier to Chris@593: // write the whole array out to a new vector Chris@593: Column cc(psize); Chris@593: for (int i = 0; i < psize - csize; ++i) { Chris@593: cc[i] = p.at(i); Chris@534: } Chris@593: for (int i = 0; i < csize; ++i) { Chris@593: cc[i + (psize - csize)] = c.at(i); Chris@593: } Chris@593: return cc; Chris@534: } Chris@534: return c; Chris@152: } Chris@152: Chris@152: void Chris@182: EditableDenseThreeDimensionalModel::setColumn(size_t index, Chris@182: const Column &values) Chris@152: { Chris@536: QWriteLocker locker(&m_lock); Chris@152: Chris@182: while (index >= m_data.size()) { Chris@182: m_data.push_back(Column()); Chris@534: m_trunc.push_back(0); Chris@152: } Chris@152: Chris@152: bool allChange = false; Chris@152: Chris@534: // if (values.size() > m_yBinCount) m_yBinCount = values.size(); Chris@439: Chris@152: for (size_t i = 0; i < values.size(); ++i) { Chris@256: float value = values[i]; Chris@257: if (std::isnan(value) || std::isinf(value)) { Chris@256: continue; Chris@256: } Chris@256: if (!m_haveExtents || value < m_minimum) { Chris@256: m_minimum = value; Chris@152: allChange = true; Chris@152: } Chris@256: if (!m_haveExtents || value > m_maximum) { Chris@256: m_maximum = value; Chris@152: allChange = true; Chris@152: } Chris@256: m_haveExtents = true; Chris@152: } Chris@152: Chris@534: truncateAndStore(index, values); Chris@534: Chris@593: // assert(values == expandAndRetrieve(index)); Chris@152: Chris@182: long windowStart = index; Chris@182: windowStart *= m_resolution; Chris@182: Chris@152: if (m_notifyOnAdd) { Chris@152: if (allChange) { Chris@152: emit modelChanged(); Chris@152: } else { Chris@152: emit modelChanged(windowStart, windowStart + m_resolution); Chris@152: } Chris@152: } else { Chris@152: if (allChange) { Chris@152: m_sinceLastNotifyMin = -1; Chris@152: m_sinceLastNotifyMax = -1; Chris@152: emit modelChanged(); Chris@152: } else { Chris@152: if (m_sinceLastNotifyMin == -1 || Chris@152: windowStart < m_sinceLastNotifyMin) { Chris@152: m_sinceLastNotifyMin = windowStart; Chris@152: } Chris@152: if (m_sinceLastNotifyMax == -1 || Chris@152: windowStart > m_sinceLastNotifyMax) { Chris@152: m_sinceLastNotifyMax = windowStart; Chris@152: } Chris@152: } Chris@152: } Chris@152: } Chris@152: Chris@152: QString Chris@152: EditableDenseThreeDimensionalModel::getBinName(size_t n) const Chris@152: { Chris@152: if (m_binNames.size() > n) return m_binNames[n]; Chris@152: else return ""; Chris@152: } Chris@152: Chris@152: void Chris@152: EditableDenseThreeDimensionalModel::setBinName(size_t n, QString name) Chris@152: { Chris@152: while (m_binNames.size() <= n) m_binNames.push_back(""); Chris@152: m_binNames[n] = name; Chris@152: emit modelChanged(); Chris@152: } Chris@152: Chris@152: void Chris@152: EditableDenseThreeDimensionalModel::setBinNames(std::vector names) Chris@152: { Chris@152: m_binNames = names; Chris@152: emit modelChanged(); Chris@152: } Chris@152: Chris@478: bool Chris@478: EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const Chris@478: { Chris@536: QReadLocker locker(&m_lock); Chris@534: Chris@533: QVector sample; Chris@533: QVector n; Chris@478: Chris@478: for (int i = 0; i < 10; ++i) { Chris@478: size_t index = i * 10; Chris@478: if (index < m_data.size()) { Chris@533: const Column &c = m_data.at(index); Chris@478: while (c.size() > sample.size()) { Chris@478: sample.push_back(0.f); Chris@478: n.push_back(0); Chris@478: } Chris@478: for (int j = 0; j < c.size(); ++j) { Chris@533: sample[j] += c.at(j); Chris@478: ++n[j]; Chris@478: } Chris@478: } Chris@478: } Chris@478: Chris@478: if (sample.empty()) return false; Chris@478: for (int j = 0; j < sample.size(); ++j) { Chris@478: if (n[j]) sample[j] /= n[j]; Chris@478: } Chris@478: Chris@533: return LogRange::useLogScale(sample.toStdVector()); Chris@478: } Chris@478: Chris@152: void Chris@333: EditableDenseThreeDimensionalModel::setCompletion(int completion, bool update) Chris@152: { Chris@152: if (m_completion != completion) { Chris@152: m_completion = completion; Chris@152: Chris@152: if (completion == 100) { Chris@152: Chris@152: m_notifyOnAdd = true; // henceforth Chris@152: emit modelChanged(); Chris@152: Chris@152: } else if (!m_notifyOnAdd) { Chris@152: Chris@333: if (update && Chris@333: m_sinceLastNotifyMin >= 0 && Chris@152: m_sinceLastNotifyMax >= 0) { Chris@152: emit modelChanged(m_sinceLastNotifyMin, Chris@152: m_sinceLastNotifyMax + m_resolution); Chris@152: m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; Chris@152: } else { Chris@152: emit completionChanged(); Chris@152: } Chris@152: } else { Chris@152: emit completionChanged(); Chris@152: } Chris@152: } Chris@152: } Chris@152: Chris@318: QString Chris@318: EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter) const Chris@318: { Chris@536: QReadLocker locker(&m_lock); Chris@318: QString s; Chris@318: for (size_t i = 0; i < m_data.size(); ++i) { Chris@318: QStringList list; Chris@533: for (size_t j = 0; j < m_data.at(i).size(); ++j) { Chris@533: list << QString("%1").arg(m_data.at(i).at(j)); Chris@318: } Chris@318: s += list.join(delimiter) + "\n"; Chris@318: } Chris@318: return s; Chris@318: } Chris@318: Chris@152: void Chris@152: EditableDenseThreeDimensionalModel::toXml(QTextStream &out, Chris@314: QString indent, Chris@314: QString extraAttributes) const Chris@152: { Chris@536: QReadLocker locker(&m_lock); Chris@534: Chris@152: // For historical reasons we read and write "resolution" as "windowSize" Chris@152: Chris@318: std::cerr << "EditableDenseThreeDimensionalModel::toXml" << std::endl; Chris@318: Chris@314: Model::toXml Chris@314: (out, indent, Chris@314: QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" %6") Chris@152: .arg(m_resolution) Chris@152: .arg(m_yBinCount) Chris@152: .arg(m_minimum) Chris@152: .arg(m_maximum) Chris@152: .arg(getObjectExportId(&m_data)) Chris@152: .arg(extraAttributes)); Chris@152: Chris@152: out << indent; Chris@152: out << QString("\n") Chris@152: .arg(getObjectExportId(&m_data)); Chris@152: Chris@152: for (size_t i = 0; i < m_binNames.size(); ++i) { Chris@152: if (m_binNames[i] != "") { Chris@152: out << indent + " "; Chris@152: out << QString("\n") Chris@152: .arg(i).arg(m_binNames[i]); Chris@152: } Chris@152: } Chris@152: Chris@152: for (size_t i = 0; i < m_data.size(); ++i) { Chris@152: out << indent + " "; Chris@152: out << QString("").arg(i); Chris@533: for (size_t j = 0; j < m_data.at(i).size(); ++j) { Chris@152: if (j > 0) out << " "; Chris@533: out << m_data.at(i).at(j); Chris@152: } Chris@152: out << QString("\n"); Chris@318: out.flush(); Chris@152: } Chris@152: Chris@152: out << indent + "\n"; Chris@152: } Chris@152: Chris@152: