Chris@133: Chris@133: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@133: Chris@133: /* Chris@133: Sonic Visualiser Chris@133: An audio file viewer and annotation editor. Chris@133: Centre for Digital Music, Queen Mary, University of London. Chris@133: This file copyright 2006 Chris Cannam. Chris@133: Chris@133: This program is free software; you can redistribute it and/or Chris@133: modify it under the terms of the GNU General Public License as Chris@133: published by the Free Software Foundation; either version 2 of the Chris@133: License, or (at your option) any later version. See the file Chris@133: COPYING included with this distribution for more information. Chris@133: */ Chris@133: Chris@133: #include "SpectrumLayer.h" Chris@133: Chris@133: #include "data/model/FFTModel.h" Chris@133: #include "view/View.h" Chris@153: #include "base/AudioLevel.h" Chris@153: #include "base/Preferences.h" Chris@133: Chris@133: #include Chris@133: #include Chris@133: Chris@133: SpectrumLayer::SpectrumLayer() : Chris@133: m_model(0), Chris@153: m_channelMode(MixChannels), Chris@153: m_channel(-1), Chris@153: m_channelSet(false), Chris@153: m_colour(Qt::darkBlue), Chris@153: m_energyScale(dBScale), Chris@153: m_normalize(false), Chris@153: m_gain(1.0), Chris@153: m_windowSize(1024), Chris@153: m_windowType(HanningWindow), Chris@153: m_windowHopLevel(2) Chris@133: { Chris@153: Preferences *prefs = Preferences::getInstance(); Chris@153: connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), Chris@153: this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); Chris@153: setWindowType(prefs->getWindowType()); Chris@133: } Chris@133: Chris@133: SpectrumLayer::~SpectrumLayer() Chris@133: { Chris@153: for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i]; Chris@133: } Chris@133: Chris@133: void Chris@133: SpectrumLayer::setModel(DenseTimeValueModel *model) Chris@133: { Chris@133: m_model = model; Chris@153: setupFFTs(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setupFFTs() Chris@153: { Chris@153: for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i]; Chris@153: m_fft.clear(); Chris@153: Chris@153: int minChannel = m_channel, maxChannel = m_channel; Chris@153: Chris@153: if (m_channel == -1 && Chris@153: m_channelMode != MixChannels) { Chris@153: minChannel = 0; Chris@153: maxChannel = 0; Chris@153: if (m_model->getChannelCount() > 1) { Chris@153: maxChannel = m_model->getChannelCount() - 1; Chris@153: } Chris@153: } Chris@153: Chris@153: for (int c = minChannel; c <= maxChannel; ++c) { Chris@153: Chris@153: m_fft.push_back(new FFTModel(m_model, Chris@153: c, Chris@153: HanningWindow, Chris@153: m_windowSize, Chris@153: getWindowIncrement(), Chris@153: m_windowSize, Chris@153: true)); Chris@153: Chris@153: if (m_channelSet) m_fft[m_fft.size()-1]->resume(); Chris@153: } Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setChannel(int channel) Chris@153: { Chris@153: m_channelSet = true; Chris@153: Chris@153: if (m_channel == channel) { Chris@153: for (size_t i = 0; i < m_fft.size(); ++i) { Chris@153: m_fft[i]->resume(); Chris@153: } Chris@153: return; Chris@153: } Chris@153: Chris@153: m_channel = channel; Chris@153: Chris@153: if (!m_fft.empty()) setupFFTs(); Chris@153: Chris@153: emit layerParametersChanged(); Chris@133: } Chris@133: Chris@133: void Chris@133: SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const Chris@133: { Chris@153: if (m_fft.empty()) return; Chris@153: if (!m_channelSet) { Chris@153: for (size_t i = 0; i < m_fft.size(); ++i) { Chris@153: m_fft[i]->resume(); Chris@153: } Chris@153: } Chris@133: Chris@153: FFTModel *fft = m_fft[0]; //!!! for now Chris@153: Chris@153: int windowIncrement = getWindowIncrement(); Chris@133: Chris@133: size_t f = v->getCentreFrame(); Chris@133: Chris@133: int w = (v->width() * 2) / 3; Chris@133: int xorigin = (v->width() / 2) - (w / 2); Chris@133: Chris@133: int h = (v->height() * 2) / 3; Chris@133: int yorigin = (v->height() / 2) + (h / 2); Chris@133: Chris@133: size_t column = f / windowIncrement; Chris@133: Chris@133: paint.save(); Chris@133: paint.setPen(m_colour); Chris@133: paint.setRenderHint(QPainter::Antialiasing, false); Chris@133: Chris@133: QPainterPath path; Chris@133: float thresh = -80.f; Chris@133: Chris@153: for (size_t bin = 0; bin < fft->getHeight(); ++bin) { Chris@133: Chris@153: float x = xorigin + (float(w) * bin) / fft->getHeight(); Chris@153: float mag; Chris@153: if (m_normalize) { Chris@153: mag = fft->getNormalizedMagnitudeAt(column, bin); Chris@153: } else { Chris@153: mag = fft->getMagnitudeAt(column, bin); Chris@153: } Chris@153: mag *= m_gain; Chris@153: float y = 0.f; Chris@153: Chris@153: switch (m_energyScale) { Chris@153: Chris@153: case dBScale: Chris@153: { Chris@153: float db = thresh; Chris@153: if (mag > 0.f) db = 10.f * log10f(mag); Chris@153: if (db < thresh) db = thresh; Chris@153: float val = (db - thresh) / -thresh; Chris@153: y = yorigin - (float(h) * val); Chris@153: break; Chris@153: } Chris@153: Chris@153: case MeterScale: Chris@153: y = yorigin - AudioLevel::multiplier_to_preview(mag, h); Chris@153: break; Chris@153: Chris@153: default: Chris@153: y = yorigin - (float(h) * mag); Chris@153: break; Chris@153: } Chris@133: Chris@133: if (bin == 0) { Chris@133: path.moveTo(x, y); Chris@133: } else { Chris@133: path.lineTo(x, y); Chris@133: } Chris@133: } Chris@133: Chris@133: paint.drawPath(path); Chris@133: paint.restore(); Chris@133: Chris@133: } Chris@133: Chris@153: Layer::PropertyList Chris@153: SpectrumLayer::getProperties() const Chris@153: { Chris@153: PropertyList list; Chris@153: list.push_back("Colour"); Chris@153: list.push_back("Scale"); Chris@153: list.push_back("Normalize"); Chris@153: list.push_back("Gain"); Chris@153: list.push_back("Window Size"); Chris@153: list.push_back("Window Increment"); Chris@153: Chris@153: if (m_model && m_model->getChannelCount() > 1 && m_channel == -1) { Chris@153: list.push_back("Channels"); Chris@153: } Chris@153: Chris@153: return list; Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::getPropertyLabel(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Colour") return tr("Colour"); Chris@153: if (name == "Energy Scale") return tr("Scale"); Chris@153: if (name == "Channels") return tr("Channels"); Chris@153: if (name == "Window Size") return tr("Window Size"); Chris@153: if (name == "Window Increment") return tr("Window Overlap"); Chris@153: if (name == "Normalize") return tr("Normalize"); Chris@153: if (name == "Gain") return tr("Gain"); Chris@153: return ""; Chris@153: } Chris@153: Chris@153: Layer::PropertyType Chris@153: SpectrumLayer::getPropertyType(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Gain") return RangeProperty; Chris@153: if (name == "Normalize") return ToggleProperty; Chris@153: return ValueProperty; Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::getPropertyGroupName(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Window Size" || Chris@153: name == "Window Increment") return tr("Window"); Chris@153: if (name == "Scale" || Chris@153: name == "Normalize" || Chris@153: name == "Gain") return tr("Energy Scale"); Chris@153: return QString(); Chris@153: } Chris@153: Chris@153: int Chris@153: SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@153: int *min, int *max) const Chris@153: { Chris@153: int deft = 0; Chris@153: Chris@153: int garbage0, garbage1; Chris@153: if (!min) min = &garbage0; Chris@153: if (!max) max = &garbage1; Chris@153: Chris@153: if (name == "Gain") { Chris@153: Chris@153: *min = -50; Chris@153: *max = 50; Chris@153: Chris@153: deft = lrint(log10(m_gain) * 20.0); Chris@153: if (deft < *min) deft = *min; Chris@153: if (deft > *max) deft = *max; Chris@153: Chris@153: } else if (name == "Normalize") { Chris@153: Chris@153: deft = (m_normalize ? 1 : 0); Chris@153: Chris@153: } else if (name == "Colour") { Chris@153: Chris@153: *min = 0; Chris@153: *max = 5; Chris@153: Chris@153: if (m_colour == Qt::black) deft = 0; Chris@153: else if (m_colour == Qt::darkRed) deft = 1; Chris@153: else if (m_colour == Qt::darkBlue) deft = 2; Chris@153: else if (m_colour == Qt::darkGreen) deft = 3; Chris@153: else if (m_colour == QColor(200, 50, 255)) deft = 4; Chris@153: else if (m_colour == QColor(255, 150, 50)) deft = 5; Chris@153: Chris@153: } else if (name == "Channels") { Chris@153: Chris@153: *min = 0; Chris@153: *max = 2; Chris@153: if (m_channelMode == MixChannels) deft = 1; Chris@153: else if (m_channelMode == OverlayChannels) deft = 2; Chris@153: else deft = 0; Chris@153: Chris@153: } else if (name == "Scale") { Chris@153: Chris@153: *min = 0; Chris@153: *max = 2; Chris@153: Chris@153: deft = (int)m_energyScale; Chris@153: Chris@153: } else if (name == "Window Size") { Chris@153: Chris@153: *min = 0; Chris@153: *max = 10; Chris@153: Chris@153: deft = 0; Chris@153: int ws = m_windowSize; Chris@153: while (ws > 32) { ws >>= 1; deft ++; } Chris@153: Chris@153: } else if (name == "Window Increment") { Chris@153: Chris@153: *min = 0; Chris@153: *max = 5; Chris@153: Chris@153: deft = m_windowHopLevel; Chris@153: Chris@153: } else { Chris@153: deft = Layer::getPropertyRangeAndValue(name, min, max); Chris@153: } Chris@153: Chris@153: return deft; Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::getPropertyValueLabel(const PropertyName &name, Chris@153: int value) const Chris@153: { Chris@153: if (name == "Colour") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: return tr("Black"); Chris@153: case 1: return tr("Red"); Chris@153: case 2: return tr("Blue"); Chris@153: case 3: return tr("Green"); Chris@153: case 4: return tr("Purple"); Chris@153: case 5: return tr("Orange"); Chris@153: } Chris@153: } Chris@153: if (name == "Scale") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: return tr("Linear"); Chris@153: case 1: return tr("Meter"); Chris@153: case 2: return tr("dB"); Chris@153: } Chris@153: } Chris@153: if (name == "Channels") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: return tr("Separate"); Chris@153: case 1: return tr("Mean"); Chris@153: case 2: return tr("Overlay"); Chris@153: } Chris@153: } Chris@153: if (name == "Window Size") { Chris@153: return QString("%1").arg(32 << value); Chris@153: } Chris@153: if (name == "Window Increment") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: return tr("None"); Chris@153: case 1: return tr("25 %"); Chris@153: case 2: return tr("50 %"); Chris@153: case 3: return tr("75 %"); Chris@153: case 4: return tr("87.5 %"); Chris@153: case 5: return tr("93.75 %"); Chris@153: } Chris@153: } Chris@153: return tr(""); Chris@153: } Chris@153: Chris@133: void Chris@153: SpectrumLayer::setProperty(const PropertyName &name, int value) Chris@133: { Chris@153: if (name == "Gain") { Chris@153: setGain(pow(10, float(value)/20.0)); Chris@153: } else if (name == "Colour") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: setBaseColour(Qt::black); break; Chris@153: case 1: setBaseColour(Qt::darkRed); break; Chris@153: case 2: setBaseColour(Qt::darkBlue); break; Chris@153: case 3: setBaseColour(Qt::darkGreen); break; Chris@153: case 4: setBaseColour(QColor(200, 50, 255)); break; Chris@153: case 5: setBaseColour(QColor(255, 150, 50)); break; Chris@153: } Chris@153: } else if (name == "Channels") { Chris@153: if (value == 1) setChannelMode(MixChannels); Chris@153: else if (value == 2) setChannelMode(OverlayChannels); Chris@153: else setChannelMode(SeparateChannels); Chris@153: } else if (name == "Scale") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: setEnergyScale(LinearScale); break; Chris@153: case 1: setEnergyScale(MeterScale); break; Chris@153: case 2: setEnergyScale(dBScale); break; Chris@153: } Chris@153: } else if (name == "Window Size") { Chris@153: setWindowSize(32 << value); Chris@153: } else if (name == "Window Increment") { Chris@153: setWindowHopLevel(value); Chris@153: } else if (name == "Normalize") { Chris@153: setNormalize(value ? true : false); Chris@153: } Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setBaseColour(QColor colour) Chris@153: { Chris@153: if (m_colour == colour) return; Chris@153: m_colour = colour; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setChannelMode(ChannelMode channelMode) Chris@153: { Chris@153: if (m_channelMode == channelMode) return; Chris@153: m_channelMode = channelMode; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setEnergyScale(EnergyScale scale) Chris@153: { Chris@153: if (m_energyScale == scale) return; Chris@153: m_energyScale = scale; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setWindowSize(size_t ws) Chris@153: { Chris@153: if (m_windowSize == ws) return; Chris@153: m_windowSize = ws; Chris@153: setupFFTs(); Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setWindowHopLevel(size_t v) Chris@153: { Chris@153: if (m_windowHopLevel == v) return; Chris@153: m_windowHopLevel = v; Chris@153: setupFFTs(); Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setWindowType(WindowType w) Chris@153: { Chris@153: if (m_windowType == w) return; Chris@153: m_windowType = w; Chris@153: setupFFTs(); Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setNormalize(bool n) Chris@153: { Chris@153: if (m_normalize == n) return; Chris@153: m_normalize = n; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setGain(float gain) Chris@153: { Chris@153: if (m_gain == gain) return; Chris@153: m_gain = gain; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name) Chris@153: { Chris@153: if (name == "Window Type") { Chris@153: setWindowType(Preferences::getInstance()->getWindowType()); Chris@153: return; Chris@153: } Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::toXmlString(QString indent, QString extraAttributes) const Chris@153: { Chris@153: QString s; Chris@153: Chris@153: s += QString("colour=\"%1\" " Chris@153: "channelMode=\"%2\" " Chris@153: "channel=\"%3\" " Chris@153: "energyScale=\"%4\" " Chris@153: "windowSize=\"%5\" " Chris@153: "windowHopLevel=\"%6\" " Chris@153: "gain=\"%7\" " Chris@153: "normalize=\"%8\"") Chris@153: .arg(encodeColour(m_colour)) Chris@153: .arg(m_channelMode) Chris@153: .arg(m_channel) Chris@153: .arg(m_energyScale) Chris@153: .arg(m_windowSize) Chris@153: .arg(m_windowHopLevel) Chris@153: .arg(m_gain) Chris@153: .arg(m_normalize ? "true" : "false"); Chris@153: Chris@153: return Layer::toXmlString(indent, extraAttributes + " " + s); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setProperties(const QXmlAttributes &attributes) Chris@153: { Chris@153: bool ok = false; Chris@153: Chris@153: QString colourSpec = attributes.value("colour"); Chris@153: if (colourSpec != "") { Chris@153: QColor colour(colourSpec); Chris@153: if (colour.isValid()) { Chris@153: setBaseColour(QColor(colourSpec)); Chris@153: } Chris@153: } Chris@153: Chris@153: ChannelMode channelMode = (ChannelMode) Chris@153: attributes.value("channelMode").toInt(&ok); Chris@153: if (ok) setChannelMode(channelMode); Chris@153: Chris@153: int channel = attributes.value("channel").toInt(&ok); Chris@153: if (ok) setChannel(channel); Chris@153: Chris@153: EnergyScale scale = (EnergyScale) Chris@153: attributes.value("energyScale").toInt(&ok); Chris@153: if (ok) setEnergyScale(scale); Chris@153: Chris@153: size_t windowSize = attributes.value("windowSize").toUInt(&ok); Chris@153: if (ok) setWindowSize(windowSize); Chris@153: Chris@153: size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); Chris@153: if (ok) setWindowHopLevel(windowHopLevel); Chris@153: Chris@153: float gain = attributes.value("gain").toFloat(&ok); Chris@153: if (ok) setGain(gain); Chris@153: Chris@153: bool normalize = (attributes.value("normalize").trimmed() == "true"); Chris@153: setNormalize(normalize); Chris@133: } Chris@133: Chris@133: bool Chris@133: SpectrumLayer::getValueExtents(float &min, float &max, bool &logarithmic, Chris@133: QString &units) const Chris@133: { Chris@133: return false; Chris@133: } Chris@133: