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@195:     This file copyright 2006-2007 QMUL.
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@167: #include "base/RangeMapper.h"
Chris@277: #include "base/Pitch.h"
Chris@285: #include "base/ColourMapper.h"
Chris@254: 
Chris@254: #include <QPainter>
Chris@316: #include <QTextStream>
Chris@316: 
Chris@133: 
Chris@133: SpectrumLayer::SpectrumLayer() :
Chris@193:     m_originModel(0),
Chris@153:     m_channel(-1),
Chris@153:     m_channelSet(false),
Chris@290:     m_windowSize(4096),
Chris@153:     m_windowType(HanningWindow),
Chris@290:     m_windowHopLevel(3),
Chris@284:     m_showPeaks(false),
Chris@275:     m_newFFTNeeded(true)
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@195: 
Chris@195:     setBinScale(LogBins);
Chris@133: }
Chris@133: 
Chris@133: SpectrumLayer::~SpectrumLayer()
Chris@133: {
Chris@193:     //!!! delete parent's model
Chris@193: //    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@193:     if (m_originModel == model) return;
Chris@193:     m_originModel = model;
Chris@277: 
Chris@277:     if (m_sliceableModel) {
Chris@277:         const Model *oldModel = m_sliceableModel;
Chris@277:         setSliceableModel(0);
Chris@277:         // surprised I'm allowed to delete a const pointer -- may be a
Chris@277:         // source of future compiler rejection?
Chris@277:         delete oldModel;
Chris@277:     }
Chris@275: //!!!    setupFFT();
Chris@153: }
Chris@153: 
Chris@153: void
Chris@193: SpectrumLayer::setupFFT()
Chris@153: {
Chris@193:     FFTModel *oldFFT = dynamic_cast<FFTModel *>
Chris@193:         (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@153:     
Chris@193:     if (oldFFT) {
Chris@193:         setSliceableModel(0);
Chris@193:         delete oldFFT;
Chris@153:     }
Chris@153: 
Chris@193:     FFTModel *newFFT = new FFTModel(m_originModel,
Chris@193:                                     m_channel,
Chris@193:                                     m_windowType,
Chris@193:                                     m_windowSize,
Chris@193:                                     getWindowIncrement(),
Chris@193:                                     m_windowSize,
Chris@327:                                     false,
Chris@327:                                     StorageAdviser::Criteria
Chris@327:                                     (StorageAdviser::SpeedCritical |
Chris@327:                                      StorageAdviser::FrequentLookupLikely));
Chris@153: 
Chris@193:     setSliceableModel(newFFT);
Chris@193: 
Chris@254:     m_biasCurve.clear();
Chris@254:     for (size_t i = 0; i < m_windowSize; ++i) {
Chris@254:         m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
Chris@254:     }
Chris@254: 
Chris@193:     newFFT->resume();
Chris@153: }
Chris@153: 
Chris@153: void
Chris@153: SpectrumLayer::setChannel(int channel)
Chris@153: {
Chris@153:     m_channelSet = true;
Chris@153: 
Chris@193:     FFTModel *fft = dynamic_cast<FFTModel *>
Chris@193:         (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@193: 
Chris@153:     if (m_channel == channel) {
Chris@193:         if (fft) fft->resume();
Chris@153:         return;
Chris@153:     }
Chris@153: 
Chris@153:     m_channel = channel;
Chris@153: 
Chris@153:     emit layerParametersChanged();
Chris@133: }
Chris@133: 
Chris@153: Layer::PropertyList
Chris@153: SpectrumLayer::getProperties() const
Chris@153: {
Chris@193:     PropertyList list = SliceLayer::getProperties();
Chris@153:     list.push_back("Window Size");
Chris@153:     list.push_back("Window Increment");
Chris@284:     list.push_back("Show Peak Frequencies");
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 == "Window Size") return tr("Window Size");
Chris@153:     if (name == "Window Increment") return tr("Window Overlap");
Chris@284:     if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
Chris@193:     return SliceLayer::getPropertyLabel(name);
Chris@153: }
Chris@153: 
Chris@153: Layer::PropertyType
Chris@153: SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153: {
Chris@193:     if (name == "Window Size") return ValueProperty;
Chris@193:     if (name == "Window Increment") return ValueProperty;
Chris@284:     if (name == "Show Peak Frequencies") return ToggleProperty;
Chris@193:     return SliceLayer::getPropertyType(name);
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@284:     if (name == "Show Peak Frequencies") return tr("Plot Type");
Chris@193:     return SliceLayer::getPropertyGroupName(name);
Chris@153: }
Chris@153: 
Chris@153: int
Chris@153: SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216:                                         int *min, int *max, int *deflt) const
Chris@153: {
Chris@216:     int val = 0;
Chris@153: 
Chris@216:     int garbage0, garbage1, garbage2;
Chris@153:     if (!min) min = &garbage0;
Chris@153:     if (!max) max = &garbage1;
Chris@216:     if (!deflt) deflt = &garbage2;
Chris@153: 
Chris@193:     if (name == "Window Size") {
Chris@153: 
Chris@153: 	*min = 0;
Chris@254: 	*max = 15;
Chris@216:         *deflt = 5;
Chris@153: 	
Chris@216: 	val = 0;
Chris@153: 	int ws = m_windowSize;
Chris@216: 	while (ws > 32) { ws >>= 1; val ++; }
Chris@153: 
Chris@153:     } else if (name == "Window Increment") {
Chris@153: 	
Chris@153: 	*min = 0;
Chris@153: 	*max = 5;
Chris@216:         *deflt = 2;
Chris@153: 	
Chris@216:         val = m_windowHopLevel;
Chris@153:     
Chris@284:     } else if (name == "Show Peak Frequencies") {
Chris@284: 
Chris@284:         return m_showPeaks ? 1 : 0;
Chris@284: 
Chris@153:     } else {
Chris@193: 
Chris@216:         val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153:     }
Chris@153: 
Chris@216:     return val;
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 == "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@193:     return SliceLayer::getPropertyValueLabel(name, value);
Chris@153: }
Chris@153: 
Chris@167: RangeMapper *
Chris@167: SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167: {
Chris@193:     return SliceLayer::getNewPropertyRangeMapper(name);
Chris@167: }
Chris@167: 
Chris@133: void
Chris@153: SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133: {
Chris@193:     if (name == "Window Size") {
Chris@153: 	setWindowSize(32 << value);
Chris@153:     } else if (name == "Window Increment") {
Chris@153:         setWindowHopLevel(value);
Chris@284:     } else if (name == "Show Peak Frequencies") {
Chris@284:         setShowPeaks(value ? true : false);
Chris@193:     } else {
Chris@193:         SliceLayer::setProperty(name, value);
Chris@153:     }
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@275:     m_newFFTNeeded = true;
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@275:     m_newFFTNeeded = true;
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@275:     m_newFFTNeeded = true;
Chris@153:     emit layerParametersChanged();
Chris@153: }
Chris@153: 
Chris@153: void
Chris@284: SpectrumLayer::setShowPeaks(bool show)
Chris@284: {
Chris@284:     if (m_showPeaks == show) return;
Chris@284:     m_showPeaks = show;
Chris@284:     emit layerParametersChanged();
Chris@284: }
Chris@284: 
Chris@284: 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@133: bool
Chris@248: SpectrumLayer::getValueExtents(float &, float &, bool &, QString &) const
Chris@133: {
Chris@133:     return false;
Chris@133: }
Chris@133: 
Chris@254: float
Chris@265: SpectrumLayer::getXForBin(int bin, int totalBins, float w) const
Chris@265: {
Chris@265:     if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
Chris@265: 
Chris@265:     float sampleRate = m_sliceableModel->getSampleRate();
Chris@265:     float binfreq = (sampleRate * bin) / (totalBins * 2);
Chris@265:     
Chris@265:     return getXForFrequency(binfreq, w);
Chris@265: }
Chris@265: 
Chris@265: int
Chris@265: SpectrumLayer::getBinForX(float x, int totalBins, float w) const
Chris@265: {
Chris@265:     if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
Chris@265: 
Chris@265:     float sampleRate = m_sliceableModel->getSampleRate();
Chris@265:     float binfreq = getFrequencyForX(x, w);
Chris@265: 
Chris@265:     return int((binfreq * totalBins * 2) / sampleRate);
Chris@265: }
Chris@265: 
Chris@265: float
Chris@254: SpectrumLayer::getFrequencyForX(float x, float w) const
Chris@254: {
Chris@254:     float freq = 0;
Chris@280:     if (!m_sliceableModel) return 0;
Chris@254: 
Chris@254:     int sampleRate = m_sliceableModel->getSampleRate();
Chris@254: 
Chris@254:     float maxfreq = float(sampleRate) / 2;
Chris@254: 
Chris@254:     switch (m_binScale) {
Chris@254: 
Chris@254:     case LinearBins:
Chris@254:         freq = ((x * maxfreq) / w);
Chris@254:         break;
Chris@254:         
Chris@254:     case LogBins:
Chris@254:         freq = powf(10.f, (x * log10f(maxfreq)) / w);
Chris@254:         break;
Chris@254: 
Chris@254:     case InvertedLogBins:
Chris@254:         freq = maxfreq - powf(10.f, ((w - x) * log10f(maxfreq)) / w);
Chris@254:         break;
Chris@254:     }
Chris@254: 
Chris@254:     return freq;
Chris@254: }
Chris@254: 
Chris@254: float
Chris@254: SpectrumLayer::getXForFrequency(float freq, float w) const
Chris@254: {
Chris@254:     float x = 0;
Chris@280:     if (!m_sliceableModel) return x;
Chris@254: 
Chris@254:     int sampleRate = m_sliceableModel->getSampleRate();
Chris@254: 
Chris@254:     float maxfreq = float(sampleRate) / 2;
Chris@254: 
Chris@254:     switch (m_binScale) {
Chris@254: 
Chris@254:     case LinearBins:
Chris@254:         x = (freq * w) / maxfreq;
Chris@254:         break;
Chris@254:         
Chris@254:     case LogBins:
Chris@254:         x = (log10f(freq) * w) / log10f(maxfreq);
Chris@254:         break;
Chris@254: 
Chris@254:     case InvertedLogBins:
Chris@274:         if (maxfreq == freq) x = w;
Chris@274:         else x = w - (log10f(maxfreq - freq) * w) / log10f(maxfreq);
Chris@254:         break;
Chris@254:     }
Chris@254: 
Chris@254:     return x;
Chris@254: }
Chris@254: 
Chris@260: bool
Chris@267: SpectrumLayer::getXScaleValue(const View *v, int x, 
Chris@260:                               float &value, QString &unit) const
Chris@260: {
Chris@267:     if (m_xorigins.find(v) == m_xorigins.end()) return false;
Chris@267:     int xorigin = m_xorigins.find(v)->second;
Chris@267:     value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1);
Chris@260:     unit = "Hz";
Chris@260:     return true;
Chris@260: }
Chris@260: 
Chris@264: bool
Chris@274: SpectrumLayer::getYScaleValue(const View *v, int y,
Chris@274:                               float &value, QString &unit) const
Chris@274: {
Chris@274:     value = getValueForY(y, v);
Chris@274: 
Chris@274:     if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274: 
Chris@274:         if (value > 0.f) {
Chris@274:             value = 10.f * log10f(value);
Chris@284:             if (value < m_threshold) value = m_threshold;
Chris@284:         } else value = m_threshold;
Chris@274: 
Chris@274:         unit = "dBV";
Chris@274: 
Chris@274:     } else {
Chris@274:         unit = "V";
Chris@274:     }
Chris@274: 
Chris@274:     return true;
Chris@274: }
Chris@274: 
Chris@274: bool
Chris@274: SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1,
Chris@274:                                    float &diff, QString &unit) const
Chris@274: {
Chris@274:     bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274:     if (rv && (unit == "dBV")) unit = "dB";
Chris@274:     return rv;
Chris@274: }
Chris@274: 
Chris@274: 
Chris@274: bool
Chris@264: SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint,
Chris@264:                                    QPoint cursorPos,
Chris@264:                                    std::vector<QRect> &extents) const
Chris@264: {
Chris@264:     QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y());
Chris@264:     extents.push_back(vertical);
Chris@264: 
Chris@264:     QRect horizontal(0, cursorPos.y(), v->width(), 12);
Chris@264:     extents.push_back(horizontal);
Chris@264: 
Chris@280:     int hoffset = 2;
Chris@280:     if (m_binScale == LogBins) hoffset = 13;
Chris@278: 
Chris@280:     int sw = getVerticalScaleWidth(v, paint);
Chris@280: 
Chris@280:     QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280:                 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264:                 paint.fontMetrics().height());
Chris@280:     extents.push_back(value);
Chris@280: 
Chris@280:     QRect log(sw, cursorPos.y() + 2,
Chris@280:               paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280:               paint.fontMetrics().height());
Chris@280:     extents.push_back(log);
Chris@280: 
Chris@280:     QRect freq(cursorPos.x(),
Chris@280:                v->height() - paint.fontMetrics().height() - hoffset,
Chris@280:                paint.fontMetrics().width("123456 Hz") + 2,
Chris@280:                paint.fontMetrics().height());
Chris@280:     extents.push_back(freq);
Chris@264: 
Chris@278:     int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278:     QRect pitch(cursorPos.x() - w,
Chris@278:                 v->height() - paint.fontMetrics().height() - hoffset,
Chris@278:                 w,
Chris@278:                 paint.fontMetrics().height());
Chris@278:     extents.push_back(pitch);
Chris@278: 
Chris@264:     return true;
Chris@264: }
Chris@264: 
Chris@254: void
Chris@254: SpectrumLayer::paintCrosshairs(View *v, QPainter &paint,
Chris@254:                                QPoint cursorPos) const
Chris@254: {
Chris@280:     if (!m_sliceableModel) return;
Chris@280: 
Chris@254:     paint.save();
Chris@282:     QFont fn = paint.font();
Chris@282:     if (fn.pointSize() > 8) {
Chris@282:         fn.setPointSize(fn.pointSize() - 1);
Chris@282:         paint.setFont(fn);
Chris@282:     }
Chris@254: 
Chris@254:     ColourMapper mapper(m_colourMap, 0, 1);
Chris@254:     paint.setPen(mapper.getContrastingColour());
Chris@254: 
Chris@254:     int xorigin = m_xorigins[v];
Chris@254:     int w = v->width() - xorigin - 1;
Chris@254:     
Chris@254:     paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y());
Chris@254:     paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height());
Chris@254:     
Chris@254:     float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
Chris@254: 
Chris@280:     int hoffset = 2;
Chris@280:     if (m_binScale == LogBins) hoffset = 13;
Chris@278: 
Chris@278:     v->drawVisibleText(paint,
Chris@278:                        cursorPos.x() + 2,
Chris@278:                        v->height() - 2 - hoffset,
Chris@278:                        QString("%1 Hz").arg(fundamental),
Chris@278:                        View::OutlinedText);
Chris@278: 
Chris@278:     if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278:         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@278:         v->drawVisibleText(paint,
Chris@278:                            cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
Chris@278:                            v->height() - 2 - hoffset,
Chris@278:                            pitchLabel,
Chris@278:                            View::OutlinedText);
Chris@278:     }
Chris@264: 
Chris@280:     float value = getValueForY(cursorPos.y(), v);
Chris@284:     float thresh = m_threshold;
Chris@280:     float db = thresh;
Chris@280:     if (value > 0.f) db = 10.f * log10f(value);
Chris@280:     if (db < thresh) db = thresh;
Chris@280: 
Chris@280:     v->drawVisibleText(paint,
Chris@280:                        xorigin + 2,
Chris@280:                        cursorPos.y() - 2,
Chris@280:                        QString("%1 V").arg(value),
Chris@280:                        View::OutlinedText);
Chris@280: 
Chris@280:     v->drawVisibleText(paint,
Chris@280:                        xorigin + 2,
Chris@280:                        cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280:                        QString("%1 dBV").arg(db),
Chris@280:                        View::OutlinedText);
Chris@280:     
Chris@254:     int harmonic = 2;
Chris@254: 
Chris@254:     while (harmonic < 100) {
Chris@254: 
Chris@254:         float hx = lrintf(getXForFrequency(fundamental * harmonic, w));
Chris@254:         hx += xorigin;
Chris@254: 
Chris@254:         if (hx < xorigin || hx > v->width()) break;
Chris@254:         
Chris@254:         int len = 7;
Chris@254: 
Chris@254:         if (harmonic % 2 == 0) {
Chris@254:             if (harmonic % 4 == 0) {
Chris@254:                 len = 12;
Chris@254:             } else {
Chris@254:                 len = 10;
Chris@254:             }
Chris@254:         }
Chris@254: 
Chris@254:         paint.drawLine(int(hx),
Chris@254:                        cursorPos.y(),
Chris@254:                        int(hx),
Chris@254:                        cursorPos.y() + len);
Chris@254: 
Chris@254:         ++harmonic;
Chris@254:     }
Chris@254: 
Chris@254:     paint.restore();
Chris@254: }
Chris@254: 
Chris@199: QString
Chris@199: SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const
Chris@199: {
Chris@199:     if (!m_sliceableModel) return "";
Chris@199: 
Chris@199:     int minbin = 0, maxbin = 0, range = 0;
Chris@199:     QString genericDesc = SliceLayer::getFeatureDescription
Chris@199:         (v, p, false, minbin, maxbin, range);
Chris@199: 
Chris@199:     if (genericDesc == "") return "";
Chris@199: 
Chris@199:     float minvalue = 0.f;
Chris@248:     if (minbin < int(m_values.size())) minvalue = m_values[minbin];
Chris@199: 
Chris@199:     float maxvalue = minvalue;
Chris@248:     if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
Chris@199:         
Chris@199:     if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199:     
Chris@199:     QString binstr;
Chris@199:     QString hzstr;
Chris@199:     int minfreq = lrintf((minbin * m_sliceableModel->getSampleRate()) /
Chris@199:                          m_windowSize);
Chris@199:     int maxfreq = lrintf((std::max(maxbin, minbin+1)
Chris@199:                            * m_sliceableModel->getSampleRate()) /
Chris@199:                           m_windowSize);
Chris@199: 
Chris@199:     if (maxbin != minbin) {
Chris@199:         binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199:     } else {
Chris@199:         binstr = QString("%1").arg(minbin+1);
Chris@199:     }
Chris@199:     if (minfreq != maxfreq) {
Chris@199:         hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199:     } else {
Chris@199:         hzstr = tr("%1 Hz").arg(minfreq);
Chris@199:     }
Chris@199:     
Chris@199:     QString valuestr;
Chris@199:     if (maxvalue != minvalue) {
Chris@199:         valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199:     } else {
Chris@199:         valuestr = QString("%1").arg(minvalue);
Chris@199:     }
Chris@199:     
Chris@199:     QString dbstr;
Chris@199:     float mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@199:     float maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199:     QString mindbstr;
Chris@199:     QString maxdbstr;
Chris@199:     if (mindb == AudioLevel::DB_FLOOR) {
Chris@199:         mindbstr = tr("-Inf");
Chris@199:     } else {
Chris@199:         mindbstr = QString("%1").arg(lrintf(mindb));
Chris@199:     }
Chris@199:     if (maxdb == AudioLevel::DB_FLOOR) {
Chris@199:         maxdbstr = tr("-Inf");
Chris@199:     } else {
Chris@199:         maxdbstr = QString("%1").arg(lrintf(maxdb));
Chris@199:     }
Chris@199:     if (lrintf(mindb) != lrintf(maxdb)) {
Chris@199:         dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199:     } else {
Chris@199:         dbstr = tr("%1").arg(mindbstr);
Chris@199:     }
Chris@199: 
Chris@199:     QString description;
Chris@199: 
Chris@248:     if (range > int(m_sliceableModel->getResolution())) {
Chris@199:         description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199:             .arg(genericDesc)
Chris@199:             .arg(binstr)
Chris@199:             .arg(hzstr)
Chris@199:             .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199:                  m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199:             .arg(valuestr)
Chris@199:             .arg(dbstr);
Chris@199:     } else {
Chris@199:         description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199:             .arg(genericDesc)
Chris@199:             .arg(binstr)
Chris@199:             .arg(hzstr)
Chris@199:             .arg(valuestr)
Chris@199:             .arg(dbstr);
Chris@199:     }
Chris@199:     
Chris@199:     return description;
Chris@199: }
Chris@199: 
Chris@254: void
Chris@275: SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@275: {
Chris@275:     if (!m_originModel || !m_originModel->isOK() ||
Chris@275:         !m_originModel->isReady()) return;
Chris@275: 
Chris@275:     if (m_newFFTNeeded) {
Chris@275:         const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275:         m_newFFTNeeded = false;
Chris@275:     }
Chris@277: 
Chris@277:     FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277:         (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277: 
Chris@280:     float thresh = (powf(10, -6) / m_gain) * (m_windowSize / 2.f); // -60dB adj
Chris@277: 
Chris@277:     int xorigin = getVerticalScaleWidth(v, paint) + 1;
Chris@277:     int w = v->width() - xorigin - 1;
Chris@277: 
Chris@278:     int pkh = 0;
Chris@287: //!!!    if (m_binScale == LogBins) {
Chris@287:         pkh = 10;
Chris@287: //!!!    }
Chris@278: 
Chris@284:     if (fft && m_showPeaks) {
Chris@277: 
Chris@277:         // draw peak lines
Chris@277: 
Chris@277:         size_t col = v->getCentreFrame() / fft->getResolution();
Chris@277: 
Chris@277:         paint.save();
Chris@277:         paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277:         paint.setPen(QColor(160, 160, 160)); //!!!
Chris@277: 
Chris@290:         int peakminbin = 0;
Chris@290:         int peakmaxbin = fft->getHeight() - 1;
Chris@290:         float peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@290:         peakmaxbin = ((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate());
Chris@290:         
Chris@280:         FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290:             (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280: 
Chris@280:         ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
Chris@277: 
Chris@277:         BiasCurve curve;
Chris@277:         getBiasCurve(curve);
Chris@277:         size_t cs = curve.size();
Chris@280: 
Chris@280:         std::vector<float> values;
Chris@277:         
Chris@277:         for (size_t bin = 0; bin < fft->getHeight(); ++bin) {
Chris@280:             float value = m_sliceableModel->getValueAt(col, bin);
Chris@280:             if (bin < cs) value *= curve[bin];
Chris@280:             values.push_back(value);
Chris@280:         }
Chris@280: 
Chris@280:         for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280:              i != peaks.end(); ++i) {
Chris@280: 
Chris@280:             size_t bin = i->first;
Chris@277:             
Chris@280: //            std::cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << std::endl;
Chris@280: 
Chris@277:             if (!fft->isOverThreshold(col, bin, thresh)) continue;
Chris@277:             
Chris@280:             float freq = i->second;
Chris@280:           
Chris@277:             int x = lrintf(getXForFrequency(freq, w));
Chris@277: 
Chris@277:             float norm = 0.f;
Chris@280:             float y = getYForValue(values[bin], v, norm); // don't need y, need norm
Chris@277: 
Chris@277:             paint.setPen(mapper.map(norm));
Chris@278:             paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1);
Chris@277:         }
Chris@277: 
Chris@277:         paint.restore();
Chris@277:     }
Chris@275:     
Chris@275:     SliceLayer::paint(v, paint, rect);
Chris@277: 
Chris@278:     //!!! All of this stuff relating to depicting frequencies
Chris@278:     //(keyboard, crosshairs etc) should be applicable to any slice
Chris@278:     //layer whose model has a vertical scale unit of Hz.  However, the
Chris@278:     //dense 3d model at the moment doesn't record its vertical scale
Chris@278:     //unit -- we need to fix that and hoist this code as appropriate.
Chris@278:     //Same really goes for any code in SpectrogramLayer that could be
Chris@278:     //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger
Chris@278:     //proposition.
Chris@278: 
Chris@287: //    if (m_binScale == LogBins) {
Chris@277: 
Chris@287: //        int pkh = 10;
Chris@277:         int h = v->height();
Chris@277: 
Chris@277:         // piano keyboard
Chris@277:         //!!! should be in a new paintHorizontalScale()?
Chris@278:         // nice to have a piano keyboard class, of course
Chris@277: 
Chris@278: 	paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1);
Chris@277: 
Chris@277: 	int px = xorigin, ppx = xorigin;
Chris@278: 	paint.setBrush(paint.pen().color());
Chris@277: 
Chris@277: 	for (int i = 0; i < 128; ++i) {
Chris@277: 
Chris@277: 	    float f = Pitch::getFrequencyForPitch(i);
Chris@277: 	    int x = lrintf(getXForFrequency(f, w));
Chris@278:                            
Chris@278:             x += xorigin;
Chris@277: 
Chris@278:             if (i == 0) {
Chris@278:                 px = ppx = x;
Chris@278:             }
Chris@278:             if (i == 1) {
Chris@278:                 ppx = px - (x - px);
Chris@278:             }
Chris@278: 
Chris@278:             if (x < xorigin) {
Chris@278:                 ppx = px;
Chris@278:                 px = x;
Chris@277:                 continue;
Chris@277:             }
Chris@278:                 
Chris@278:             if (x > w) {
Chris@278:                 break;
Chris@278:             }
Chris@277: 
Chris@277: 	    int n = (i % 12);
Chris@277: 
Chris@277:             if (n == 1) {
Chris@277:                 // C# -- fill the C from here
Chris@284:                 QColor col = Qt::gray;
Chris@284:                 if (i == 61) { // filling middle C
Chris@284:                     col = Qt::blue;
Chris@284:                     col = col.light(150);
Chris@284:                 }
Chris@277:                 if (x - ppx > 2) {
Chris@278:                     paint.fillRect((px + ppx) / 2 + 1,
Chris@277:                                    h - pkh,
Chris@278:                                    x - (px + ppx) / 2 - 1,
Chris@277:                                    pkh,
Chris@284:                                    col);
Chris@277:                 }
Chris@277:             }
Chris@277: 
Chris@277: 	    if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
Chris@277: 		// black notes
Chris@277: 		paint.drawLine(x, h - pkh, x, h);
Chris@278: 		int rw = lrintf(float(x - px) / 4) * 2;
Chris@277: 		if (rw < 2) rw = 2;
Chris@278: 		paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
Chris@277: 	    } else if (n == 0 || n == 5) {
Chris@277: 		// C, F
Chris@277: 		if (px < w) {
Chris@277: 		    paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h);
Chris@277: 		}
Chris@277: 	    }
Chris@277: 
Chris@277:             ppx = px;
Chris@277: 	    px = x;
Chris@277: 	}
Chris@287: //    }
Chris@275: }
Chris@275: 
Chris@275: void
Chris@254: SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254: {
Chris@254:     curve = m_biasCurve;
Chris@254: }
Chris@199: 
Chris@316: void
Chris@316: SpectrumLayer::toXml(QTextStream &stream,
Chris@316:                      QString indent, QString extraAttributes) const
Chris@220: {
Chris@316:     QString s = QString("windowSize=\"%1\" "
Chris@316:                         "windowHopLevel=\"%2\"")
Chris@220:         .arg(m_windowSize)
Chris@220:         .arg(m_windowHopLevel);
Chris@220: 
Chris@316:     SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220: }
Chris@220: 
Chris@220: void
Chris@220: SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220: {
Chris@220:     SliceLayer::setProperties(attributes);
Chris@220: 
Chris@220:     bool ok = false;
Chris@220: 
Chris@220:     size_t windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220:     if (ok) setWindowSize(windowSize);
Chris@220: 
Chris@220:     size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220:     if (ok) setWindowHopLevel(windowHopLevel);
Chris@220: }
Chris@220: 
Chris@220: