svgui  1.9
SpectrumLayer.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2006-2007 QMUL.
00008     
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "SpectrumLayer.h"
00017 
00018 #include "data/model/FFTModel.h"
00019 #include "view/View.h"
00020 #include "base/AudioLevel.h"
00021 #include "base/Preferences.h"
00022 #include "base/RangeMapper.h"
00023 #include "base/Pitch.h"
00024 #include "ColourMapper.h"
00025 
00026 #include <QPainter>
00027 #include <QTextStream>
00028 
00029 
00030 SpectrumLayer::SpectrumLayer() :
00031     m_originModel(0),
00032     m_channel(-1),
00033     m_channelSet(false),
00034     m_windowSize(4096),
00035     m_windowType(HanningWindow),
00036     m_windowHopLevel(3),
00037     m_showPeaks(false),
00038     m_newFFTNeeded(true)
00039 {
00040     Preferences *prefs = Preferences::getInstance();
00041     connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
00042             this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
00043     setWindowType(prefs->getWindowType());
00044 
00045     setBinScale(LogBins);
00046 }
00047 
00048 SpectrumLayer::~SpectrumLayer()
00049 {
00050     Model *m = const_cast<Model *>
00051         (static_cast<const Model *>(m_sliceableModel));
00052     if (m) m->aboutToDelete();
00053     m_sliceableModel = 0;
00054     delete m;
00055 }
00056 
00057 void
00058 SpectrumLayer::setModel(DenseTimeValueModel *model)
00059 {
00060     SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl;
00061     
00062     if (m_originModel == model) return;
00063 
00064     m_originModel = model;
00065 
00066     if (m_sliceableModel) {
00067         Model *m = const_cast<Model *>
00068             (static_cast<const Model *>(m_sliceableModel));
00069         m->aboutToDelete();
00070         setSliceableModel(0);
00071         delete m;
00072     }
00073 
00074     m_newFFTNeeded = true;
00075 
00076     emit layerParametersChanged();
00077 }
00078 
00079 void
00080 SpectrumLayer::setChannel(int channel)
00081 {
00082     SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
00083     
00084     m_channelSet = true;
00085     
00086     if (m_channel == channel) return;
00087 
00088     m_channel = channel;
00089 
00090     m_newFFTNeeded = true;
00091 
00092     emit layerParametersChanged();
00093 }
00094 
00095 void
00096 SpectrumLayer::setupFFT()
00097 {
00098     if (m_sliceableModel) {
00099         Model *m = const_cast<Model *>
00100             (static_cast<const Model *>(m_sliceableModel));
00101         m->aboutToDelete();
00102         setSliceableModel(0);
00103         delete m;
00104     }
00105 
00106     if (!m_originModel) {
00107         return;
00108     }
00109 
00110     FFTModel *newFFT = new FFTModel(m_originModel,
00111                                     m_channel,
00112                                     m_windowType,
00113                                     m_windowSize,
00114                                     getWindowIncrement(),
00115                                     m_windowSize,
00116                                     false,
00117                                     StorageAdviser::Criteria
00118                                     (StorageAdviser::SpeedCritical |
00119                                      StorageAdviser::FrequentLookupLikely));
00120 
00121     setSliceableModel(newFFT);
00122 
00123     m_biasCurve.clear();
00124     for (int i = 0; i < m_windowSize; ++i) {
00125         m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
00126     }
00127 
00128     newFFT->resume();
00129 
00130     m_newFFTNeeded = false;
00131 }
00132 
00133 Layer::PropertyList
00134 SpectrumLayer::getProperties() const
00135 {
00136     PropertyList list = SliceLayer::getProperties();
00137     list.push_back("Window Size");
00138     list.push_back("Window Increment");
00139     list.push_back("Show Peak Frequencies");
00140     return list;
00141 }
00142 
00143 QString
00144 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
00145 {
00146     if (name == "Window Size") return tr("Window Size");
00147     if (name == "Window Increment") return tr("Window Overlap");
00148     if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
00149     return SliceLayer::getPropertyLabel(name);
00150 }
00151 
00152 QString
00153 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
00154 {
00155     if (name == "Show Peak Frequencies") return "show-peaks";
00156     return SliceLayer::getPropertyIconName(name);
00157 }
00158 
00159 Layer::PropertyType
00160 SpectrumLayer::getPropertyType(const PropertyName &name) const
00161 {
00162     if (name == "Window Size") return ValueProperty;
00163     if (name == "Window Increment") return ValueProperty;
00164     if (name == "Show Peak Frequencies") return ToggleProperty;
00165     return SliceLayer::getPropertyType(name);
00166 }
00167 
00168 QString
00169 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
00170 {
00171     if (name == "Window Size" ||
00172         name == "Window Increment") return tr("Window");
00173     if (name == "Show Peak Frequencies") return tr("Bins");
00174     return SliceLayer::getPropertyGroupName(name);
00175 }
00176 
00177 int
00178 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
00179                                         int *min, int *max, int *deflt) const
00180 {
00181     int val = 0;
00182 
00183     int garbage0, garbage1, garbage2;
00184     if (!min) min = &garbage0;
00185     if (!max) max = &garbage1;
00186     if (!deflt) deflt = &garbage2;
00187 
00188     if (name == "Window Size") {
00189 
00190         *min = 0;
00191         *max = 15;
00192         *deflt = 5;
00193         
00194         val = 0;
00195         int ws = m_windowSize;
00196         while (ws > 32) { ws >>= 1; val ++; }
00197 
00198     } else if (name == "Window Increment") {
00199         
00200         *min = 0;
00201         *max = 5;
00202         *deflt = 2;
00203         
00204         val = m_windowHopLevel;
00205     
00206     } else if (name == "Show Peak Frequencies") {
00207 
00208         return m_showPeaks ? 1 : 0;
00209 
00210     } else {
00211 
00212         val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
00213     }
00214 
00215     return val;
00216 }
00217 
00218 QString
00219 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
00220                                     int value) const
00221 {
00222     if (name == "Window Size") {
00223         return QString("%1").arg(32 << value);
00224     }
00225     if (name == "Window Increment") {
00226         switch (value) {
00227         default:
00228         case 0: return tr("None");
00229         case 1: return tr("25 %");
00230         case 2: return tr("50 %");
00231         case 3: return tr("75 %");
00232         case 4: return tr("87.5 %");
00233         case 5: return tr("93.75 %");
00234         }
00235     }
00236     return SliceLayer::getPropertyValueLabel(name, value);
00237 }
00238 
00239 RangeMapper *
00240 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
00241 {
00242     return SliceLayer::getNewPropertyRangeMapper(name);
00243 }
00244 
00245 void
00246 SpectrumLayer::setProperty(const PropertyName &name, int value)
00247 {
00248     if (name == "Window Size") {
00249         setWindowSize(32 << value);
00250     } else if (name == "Window Increment") {
00251         setWindowHopLevel(value);
00252     } else if (name == "Show Peak Frequencies") {
00253         setShowPeaks(value ? true : false);
00254     } else {
00255         SliceLayer::setProperty(name, value);
00256     }
00257 }
00258 
00259 void
00260 SpectrumLayer::setWindowSize(int ws)
00261 {
00262     if (m_windowSize == ws) return;
00263     m_windowSize = ws;
00264     m_newFFTNeeded = true;
00265     emit layerParametersChanged();
00266 }
00267 
00268 void
00269 SpectrumLayer::setWindowHopLevel(int v)
00270 {
00271     if (m_windowHopLevel == v) return;
00272     m_windowHopLevel = v;
00273     m_newFFTNeeded = true;
00274     emit layerParametersChanged();
00275 }
00276 
00277 void
00278 SpectrumLayer::setWindowType(WindowType w)
00279 {
00280     if (m_windowType == w) return;
00281     m_windowType = w;
00282     m_newFFTNeeded = true;
00283     emit layerParametersChanged();
00284 }
00285 
00286 void
00287 SpectrumLayer::setShowPeaks(bool show)
00288 {
00289     if (m_showPeaks == show) return;
00290     m_showPeaks = show;
00291     emit layerParametersChanged();
00292 }
00293 
00294 void
00295 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
00296 {
00297     if (name == "Window Type") {
00298         setWindowType(Preferences::getInstance()->getWindowType());
00299         return;
00300     }
00301 }
00302 
00303 bool
00304 SpectrumLayer::getValueExtents(float &, float &, bool &, QString &) const
00305 {
00306     return false;
00307 }
00308 
00309 float
00310 SpectrumLayer::getXForBin(int bin, int totalBins, float w) const
00311 {
00312     if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
00313 
00314     float sampleRate = m_sliceableModel->getSampleRate();
00315     float binfreq = (sampleRate * bin) / (totalBins * 2);
00316     
00317     return getXForFrequency(binfreq, w);
00318 }
00319 
00320 int
00321 SpectrumLayer::getBinForX(float x, int totalBins, float w) const
00322 {
00323     if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
00324 
00325     float sampleRate = m_sliceableModel->getSampleRate();
00326     float binfreq = getFrequencyForX(x, w);
00327 
00328     return int((binfreq * totalBins * 2) / sampleRate);
00329 }
00330 
00331 float
00332 SpectrumLayer::getFrequencyForX(float x, float w) const
00333 {
00334     float freq = 0;
00335     if (!m_sliceableModel) return 0;
00336 
00337     int sampleRate = m_sliceableModel->getSampleRate();
00338 
00339     float maxfreq = float(sampleRate) / 2;
00340 
00341     switch (m_binScale) {
00342 
00343     case LinearBins:
00344         freq = ((x * maxfreq) / w);
00345         break;
00346         
00347     case LogBins:
00348         freq = powf(10.f, (x * log10f(maxfreq)) / w);
00349         break;
00350 
00351     case InvertedLogBins:
00352         freq = maxfreq - powf(10.f, ((w - x) * log10f(maxfreq)) / w);
00353         break;
00354     }
00355 
00356     return freq;
00357 }
00358 
00359 float
00360 SpectrumLayer::getXForFrequency(float freq, float w) const
00361 {
00362     float x = 0;
00363     if (!m_sliceableModel) return x;
00364 
00365     int sampleRate = m_sliceableModel->getSampleRate();
00366 
00367     float maxfreq = float(sampleRate) / 2;
00368 
00369     switch (m_binScale) {
00370 
00371     case LinearBins:
00372         x = (freq * w) / maxfreq;
00373         break;
00374         
00375     case LogBins:
00376         x = (log10f(freq) * w) / log10f(maxfreq);
00377         break;
00378 
00379     case InvertedLogBins:
00380         if (maxfreq == freq) x = w;
00381         else x = w - (log10f(maxfreq - freq) * w) / log10f(maxfreq);
00382         break;
00383     }
00384 
00385     return x;
00386 }
00387 
00388 bool
00389 SpectrumLayer::getXScaleValue(const View *v, int x, 
00390                               float &value, QString &unit) const
00391 {
00392     if (m_xorigins.find(v) == m_xorigins.end()) return false;
00393     int xorigin = m_xorigins.find(v)->second;
00394     value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1);
00395     unit = "Hz";
00396     return true;
00397 }
00398 
00399 bool
00400 SpectrumLayer::getYScaleValue(const View *v, int y,
00401                               float &value, QString &unit) const
00402 {
00403     value = getValueForY(y, v);
00404 
00405     if (m_energyScale == dBScale || m_energyScale == MeterScale) {
00406 
00407         if (value > 0.f) {
00408             value = 10.f * log10f(value);
00409             if (value < m_threshold) value = m_threshold;
00410         } else value = m_threshold;
00411 
00412         unit = "dBV";
00413 
00414     } else {
00415         unit = "V";
00416     }
00417 
00418     return true;
00419 }
00420 
00421 bool
00422 SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1,
00423                                    float &diff, QString &unit) const
00424 {
00425     bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
00426     if (rv && (unit == "dBV")) unit = "dB";
00427     return rv;
00428 }
00429 
00430 
00431 bool
00432 SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint,
00433                                    QPoint cursorPos,
00434                                    std::vector<QRect> &extents) const
00435 {
00436     QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y());
00437     extents.push_back(vertical);
00438 
00439     QRect horizontal(0, cursorPos.y(), v->width(), 12);
00440     extents.push_back(horizontal);
00441 
00442     int hoffset = 2;
00443     if (m_binScale == LogBins) hoffset = 13;
00444 
00445     int sw = getVerticalScaleWidth(v, false, paint);
00446 
00447     QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
00448                 paint.fontMetrics().width("0.0000001 V") + 2,
00449                 paint.fontMetrics().height());
00450     extents.push_back(value);
00451 
00452     QRect log(sw, cursorPos.y() + 2,
00453               paint.fontMetrics().width("-80.000 dBV") + 2,
00454               paint.fontMetrics().height());
00455     extents.push_back(log);
00456 
00457     QRect freq(cursorPos.x(),
00458                v->height() - paint.fontMetrics().height() - hoffset,
00459                paint.fontMetrics().width("123456 Hz") + 2,
00460                paint.fontMetrics().height());
00461     extents.push_back(freq);
00462 
00463     int w(paint.fontMetrics().width("C#10+50c") + 2);
00464     QRect pitch(cursorPos.x() - w,
00465                 v->height() - paint.fontMetrics().height() - hoffset,
00466                 w,
00467                 paint.fontMetrics().height());
00468     extents.push_back(pitch);
00469 
00470     return true;
00471 }
00472 
00473 void
00474 SpectrumLayer::paintCrosshairs(View *v, QPainter &paint,
00475                                QPoint cursorPos) const
00476 {
00477     if (!m_sliceableModel) return;
00478 
00479     paint.save();
00480     QFont fn = paint.font();
00481     if (fn.pointSize() > 8) {
00482         fn.setPointSize(fn.pointSize() - 1);
00483         paint.setFont(fn);
00484     }
00485 
00486     ColourMapper mapper(m_colourMap, 0, 1);
00487     paint.setPen(mapper.getContrastingColour());
00488 
00489     int xorigin = m_xorigins[v];
00490     int w = v->width() - xorigin - 1;
00491     
00492     paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y());
00493     paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height());
00494     
00495     float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
00496 
00497     int hoffset = 2;
00498     if (m_binScale == LogBins) hoffset = 13;
00499 
00500     v->drawVisibleText(paint,
00501                        cursorPos.x() + 2,
00502                        v->height() - 2 - hoffset,
00503                        QString("%1 Hz").arg(fundamental),
00504                        View::OutlinedText);
00505 
00506     if (Pitch::isFrequencyInMidiRange(fundamental)) {
00507         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
00508         v->drawVisibleText(paint,
00509                            cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
00510                            v->height() - 2 - hoffset,
00511                            pitchLabel,
00512                            View::OutlinedText);
00513     }
00514 
00515     float value = getValueForY(cursorPos.y(), v);
00516     float thresh = m_threshold;
00517     float db = thresh;
00518     if (value > 0.f) db = 10.f * log10f(value);
00519     if (db < thresh) db = thresh;
00520 
00521     v->drawVisibleText(paint,
00522                        xorigin + 2,
00523                        cursorPos.y() - 2,
00524                        QString("%1 V").arg(value),
00525                        View::OutlinedText);
00526 
00527     v->drawVisibleText(paint,
00528                        xorigin + 2,
00529                        cursorPos.y() + 2 + paint.fontMetrics().ascent(),
00530                        QString("%1 dBV").arg(db),
00531                        View::OutlinedText);
00532     
00533     int harmonic = 2;
00534 
00535     while (harmonic < 100) {
00536 
00537         float hx = lrintf(getXForFrequency(fundamental * harmonic, w));
00538         hx += xorigin;
00539 
00540         if (hx < xorigin || hx > v->width()) break;
00541         
00542         int len = 7;
00543 
00544         if (harmonic % 2 == 0) {
00545             if (harmonic % 4 == 0) {
00546                 len = 12;
00547             } else {
00548                 len = 10;
00549             }
00550         }
00551 
00552         paint.drawLine(int(hx),
00553                        cursorPos.y(),
00554                        int(hx),
00555                        cursorPos.y() + len);
00556 
00557         ++harmonic;
00558     }
00559 
00560     paint.restore();
00561 }
00562 
00563 QString
00564 SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const
00565 {
00566     if (!m_sliceableModel) return "";
00567 
00568     int minbin = 0, maxbin = 0, range = 0;
00569     QString genericDesc = SliceLayer::getFeatureDescriptionAux
00570         (v, p, false, minbin, maxbin, range);
00571 
00572     if (genericDesc == "") return "";
00573 
00574     float minvalue = 0.f;
00575     if (minbin < int(m_values.size())) minvalue = m_values[minbin];
00576 
00577     float maxvalue = minvalue;
00578     if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
00579         
00580     if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
00581     
00582     QString binstr;
00583     QString hzstr;
00584     int minfreq = lrintf((minbin * m_sliceableModel->getSampleRate()) /
00585                          m_windowSize);
00586     int maxfreq = lrintf((std::max(maxbin, minbin+1)
00587                            * m_sliceableModel->getSampleRate()) /
00588                           m_windowSize);
00589 
00590     if (maxbin != minbin) {
00591         binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
00592     } else {
00593         binstr = QString("%1").arg(minbin+1);
00594     }
00595     if (minfreq != maxfreq) {
00596         hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
00597     } else {
00598         hzstr = tr("%1 Hz").arg(minfreq);
00599     }
00600     
00601     QString valuestr;
00602     if (maxvalue != minvalue) {
00603         valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
00604     } else {
00605         valuestr = QString("%1").arg(minvalue);
00606     }
00607     
00608     QString dbstr;
00609     float mindb = AudioLevel::multiplier_to_dB(minvalue);
00610     float maxdb = AudioLevel::multiplier_to_dB(maxvalue);
00611     QString mindbstr;
00612     QString maxdbstr;
00613     if (mindb == AudioLevel::DB_FLOOR) {
00614         mindbstr = tr("-Inf");
00615     } else {
00616         mindbstr = QString("%1").arg(lrintf(mindb));
00617     }
00618     if (maxdb == AudioLevel::DB_FLOOR) {
00619         maxdbstr = tr("-Inf");
00620     } else {
00621         maxdbstr = QString("%1").arg(lrintf(maxdb));
00622     }
00623     if (lrintf(mindb) != lrintf(maxdb)) {
00624         dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
00625     } else {
00626         dbstr = tr("%1").arg(mindbstr);
00627     }
00628 
00629     QString description;
00630 
00631     if (range > int(m_sliceableModel->getResolution())) {
00632         description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
00633             .arg(genericDesc)
00634             .arg(binstr)
00635             .arg(hzstr)
00636             .arg(m_samplingMode == NearestSample ? tr("First") :
00637                  m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
00638             .arg(valuestr)
00639             .arg(dbstr);
00640     } else {
00641         description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
00642             .arg(genericDesc)
00643             .arg(binstr)
00644             .arg(hzstr)
00645             .arg(valuestr)
00646             .arg(dbstr);
00647     }
00648     
00649     return description;
00650 }
00651 
00652 void
00653 SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
00654 {
00655     if (!m_originModel || !m_originModel->isOK() ||
00656         !m_originModel->isReady()) {
00657         SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
00658         return;
00659     }
00660 
00661     if (m_newFFTNeeded) {
00662         SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
00663         const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
00664     }
00665 
00666     FFTModel *fft = dynamic_cast<FFTModel *>
00667         (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
00668 
00669     float thresh = (powf(10, -6) / m_gain) * (m_windowSize / 2.f); // -60dB adj
00670 
00671     int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
00672     int w = v->width() - xorigin - 1;
00673 
00674     int pkh = 0;
00676         pkh = 10;
00678 
00679     paint.save();
00680 
00681     if (fft && m_showPeaks) {
00682 
00683         // draw peak lines
00684 
00685 //        SVDEBUG << "Showing peaks..." << endl;
00686 
00687         int col = v->getCentreFrame() / fft->getResolution();
00688 
00689         paint.save();
00690         paint.setRenderHint(QPainter::Antialiasing, false);
00691         paint.setPen(QColor(160, 160, 160)); 
00692 
00693         int peakminbin = 0;
00694         int peakmaxbin = fft->getHeight() - 1;
00695         float peakmaxfreq = Pitch::getFrequencyForPitch(128);
00696         peakmaxbin = ((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate());
00697         
00698         FFTModel::PeakSet peaks = fft->getPeakFrequencies
00699             (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
00700 
00701         ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
00702 
00703         BiasCurve curve;
00704         getBiasCurve(curve);
00705         int cs = curve.size();
00706 
00707         std::vector<float> values;
00708         
00709         for (int bin = 0; bin < fft->getHeight(); ++bin) {
00710             float value = m_sliceableModel->getValueAt(col, bin);
00711             if (bin < cs) value *= curve[bin];
00712             values.push_back(value);
00713         }
00714 
00715         for (FFTModel::PeakSet::iterator i = peaks.begin();
00716              i != peaks.end(); ++i) {
00717 
00718             int bin = i->first;
00719             
00720 //            cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
00721 
00722             if (!fft->isOverThreshold(col, bin, thresh)) continue;
00723             
00724             float freq = i->second;
00725           
00726             int x = lrintf(getXForFrequency(freq, w));
00727 
00728             float norm = 0.f;
00729             (void)getYForValue(values[bin], v, norm); // don't need return value, need norm
00730 
00731             paint.setPen(mapper.map(norm));
00732             paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1);
00733         }
00734 
00735         paint.restore();
00736     }
00737     
00738     SliceLayer::paint(v, paint, rect);
00739 
00741     //(keyboard, crosshairs etc) should be applicable to any slice
00742     //layer whose model has a vertical scale unit of Hz.  However, the
00743     //dense 3d model at the moment doesn't record its vertical scale
00744     //unit -- we need to fix that and hoist this code as appropriate.
00745     //Same really goes for any code in SpectrogramLayer that could be
00746     //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger
00747     //proposition.
00748 
00749 //    if (m_binScale == LogBins) {
00750 
00751 //        int pkh = 10;
00752         int h = v->height();
00753 
00754         // piano keyboard
00756         // nice to have a piano keyboard class, of course
00757 
00758         paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1);
00759 
00760         int px = xorigin, ppx = xorigin;
00761         paint.setBrush(paint.pen().color());
00762 
00763         for (int i = 0; i < 128; ++i) {
00764 
00765             float f = Pitch::getFrequencyForPitch(i);
00766             int x = lrintf(getXForFrequency(f, w));
00767                            
00768             x += xorigin;
00769 
00770             if (i == 0) {
00771                 px = ppx = x;
00772             }
00773             if (i == 1) {
00774                 ppx = px - (x - px);
00775             }
00776 
00777             if (x < xorigin) {
00778                 ppx = px;
00779                 px = x;
00780                 continue;
00781             }
00782                 
00783             if (x > w) {
00784                 break;
00785             }
00786 
00787             int n = (i % 12);
00788 
00789             if (n == 1) {
00790                 // C# -- fill the C from here
00791                 QColor col = Qt::gray;
00792                 if (i == 61) { // filling middle C
00793                     col = Qt::blue;
00794                     col = col.light(150);
00795                 }
00796                 if (x - ppx > 2) {
00797                     paint.fillRect((px + ppx) / 2 + 1,
00798                                    h - pkh,
00799                                    x - (px + ppx) / 2 - 1,
00800                                    pkh,
00801                                    col);
00802                 }
00803             }
00804 
00805             if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
00806                 // black notes
00807                 paint.drawLine(x, h - pkh, x, h);
00808                 int rw = lrintf(float(x - px) / 4) * 2;
00809                 if (rw < 2) rw = 2;
00810                 paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
00811             } else if (n == 0 || n == 5) {
00812                 // C, F
00813                 if (px < w) {
00814                     paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h);
00815                 }
00816             }
00817 
00818             ppx = px;
00819             px = x;
00820         }
00821 //    }
00822 
00823     paint.restore();
00824 }
00825 
00826 void
00827 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
00828 {
00829     curve = m_biasCurve;
00830 }
00831 
00832 void
00833 SpectrumLayer::toXml(QTextStream &stream,
00834                      QString indent, QString extraAttributes) const
00835 {
00836     QString s = QString("windowSize=\"%1\" "
00837                         "windowHopLevel=\"%2\" "
00838                         "showPeaks=\"%3\" ")
00839         .arg(m_windowSize)
00840         .arg(m_windowHopLevel)
00841         .arg(m_showPeaks ? "true" : "false");
00842 
00843     SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
00844 }
00845 
00846 void
00847 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
00848 {
00849     SliceLayer::setProperties(attributes);
00850 
00851     bool ok = false;
00852 
00853     int windowSize = attributes.value("windowSize").toUInt(&ok);
00854     if (ok) setWindowSize(windowSize);
00855 
00856     int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
00857     if (ok) setWindowHopLevel(windowHopLevel);
00858 
00859     bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
00860     setShowPeaks(showPeaks);
00861 }
00862 
00863