Mercurial > hg > svgui
view layer/WaveformLayer.cpp @ 182:42118892f428
* Update copyright notes as appropriate.
author | Chris Cannam |
---|---|
date | Fri, 17 Nov 2006 16:27:39 +0000 |
parents | 53b9c7656798 |
children | 5b7472db612b |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This file copyright 2006 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "WaveformLayer.h" #include "base/AudioLevel.h" #include "view/View.h" #include "base/Profiler.h" #include "base/RangeMapper.h" #include <QPainter> #include <QPixmap> #include <iostream> #include <cmath> //#define DEBUG_WAVEFORM_PAINT 1 using std::cerr; using std::endl; WaveformLayer::WaveformLayer() : Layer(), m_model(0), m_gain(1.0f), m_autoNormalize(false), m_colour(Qt::black), m_showMeans(true), m_greyscale(true), m_channelMode(SeparateChannels), m_channel(-1), m_scale(LinearScale), m_aggressive(false), m_cache(0), m_cacheValid(false) { } WaveformLayer::~WaveformLayer() { delete m_cache; } void WaveformLayer::setModel(const RangeSummarisableTimeValueModel *model) { bool channelsChanged = false; if (m_channel == -1) { if (!m_model) { if (model) { channelsChanged = true; } } else { if (model && m_model->getChannelCount() != model->getChannelCount()) { channelsChanged = true; } } } m_model = model; m_cacheValid = false; if (!m_model || !m_model->isOK()) return; connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); connect(m_model, SIGNAL(modelChanged(size_t, size_t)), this, SIGNAL(modelChanged(size_t, size_t))); connect(m_model, SIGNAL(completionChanged()), this, SIGNAL(modelCompletionChanged())); emit modelReplaced(); if (channelsChanged) emit layerParametersChanged(); } Layer::PropertyList WaveformLayer::getProperties() const { PropertyList list; list.push_back("Colour"); list.push_back("Scale"); list.push_back("Gain"); list.push_back("Normalize Visible Area"); if (m_model && m_model->getChannelCount() > 1 && m_channel == -1) { list.push_back("Channels"); } return list; } QString WaveformLayer::getPropertyLabel(const PropertyName &name) const { if (name == "Colour") return tr("Colour"); if (name == "Scale") return tr("Scale"); if (name == "Gain") return tr("Gain"); if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); if (name == "Channels") return tr("Channels"); return ""; } Layer::PropertyType WaveformLayer::getPropertyType(const PropertyName &name) const { if (name == "Gain") return RangeProperty; if (name == "Normalize Visible Area") return ToggleProperty; if (name == "Colour") return ValueProperty; if (name == "Channels") return ValueProperty; if (name == "Scale") return ValueProperty; return InvalidProperty; } QString WaveformLayer::getPropertyGroupName(const PropertyName &name) const { if (name == "Gain" || name == "Normalize Visible Area" || name == "Scale") return tr("Scale"); return QString(); } int WaveformLayer::getPropertyRangeAndValue(const PropertyName &name, int *min, int *max) const { int deft = 0; int garbage0, garbage1; if (!min) min = &garbage0; if (!max) max = &garbage1; if (name == "Gain") { *min = -50; *max = 50; deft = lrint(log10(m_gain) * 20.0); if (deft < *min) deft = *min; if (deft > *max) deft = *max; } else if (name == "Normalize Visible Area") { deft = (m_autoNormalize ? 1 : 0); } else if (name == "Colour") { *min = 0; *max = 5; if (m_colour == Qt::black) deft = 0; else if (m_colour == Qt::darkRed) deft = 1; else if (m_colour == Qt::darkBlue) deft = 2; else if (m_colour == Qt::darkGreen) deft = 3; else if (m_colour == QColor(200, 50, 255)) deft = 4; else if (m_colour == QColor(255, 150, 50)) deft = 5; } else if (name == "Channels") { *min = 0; *max = 2; if (m_channelMode == MixChannels) deft = 1; else if (m_channelMode == MergeChannels) deft = 2; else deft = 0; } else if (name == "Scale") { *min = 0; *max = 2; deft = (int)m_scale; } else { deft = Layer::getPropertyRangeAndValue(name, min, max); } return deft; } QString WaveformLayer::getPropertyValueLabel(const PropertyName &name, int value) const { if (name == "Colour") { switch (value) { default: case 0: return tr("Black"); case 1: return tr("Red"); case 2: return tr("Blue"); case 3: return tr("Green"); case 4: return tr("Purple"); case 5: return tr("Orange"); } } if (name == "Scale") { switch (value) { default: case 0: return tr("Linear"); case 1: return tr("Meter"); case 2: return tr("dB"); } } if (name == "Channels") { switch (value) { default: case 0: return tr("Separate"); case 1: return tr("Mean"); case 2: return tr("Butterfly"); } } return tr("<unknown>"); } RangeMapper * WaveformLayer::getNewPropertyRangeMapper(const PropertyName &name) const { if (name == "Gain") { return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); } return 0; } void WaveformLayer::setProperty(const PropertyName &name, int value) { if (name == "Gain") { setGain(pow(10, float(value)/20.0)); } else if (name == "Normalize Visible Area") { setAutoNormalize(value ? true : false); } else if (name == "Colour") { switch (value) { default: case 0: setBaseColour(Qt::black); break; case 1: setBaseColour(Qt::darkRed); break; case 2: setBaseColour(Qt::darkBlue); break; case 3: setBaseColour(Qt::darkGreen); break; case 4: setBaseColour(QColor(200, 50, 255)); break; case 5: setBaseColour(QColor(255, 150, 50)); break; } } else if (name == "Channels") { if (value == 1) setChannelMode(MixChannels); else if (value == 2) setChannelMode(MergeChannels); else setChannelMode(SeparateChannels); } else if (name == "Scale") { switch (value) { default: case 0: setScale(LinearScale); break; case 1: setScale(MeterScale); break; case 2: setScale(dBScale); break; } } } void WaveformLayer::setGain(float gain) { if (m_gain == gain) return; m_gain = gain; m_cacheValid = false; emit layerParametersChanged(); emit verticalZoomChanged(); } void WaveformLayer::setAutoNormalize(bool autoNormalize) { if (m_autoNormalize == autoNormalize) return; m_autoNormalize = autoNormalize; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setBaseColour(QColor colour) { if (m_colour == colour) return; m_colour = colour; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setShowMeans(bool showMeans) { if (m_showMeans == showMeans) return; m_showMeans = showMeans; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setUseGreyscale(bool useGreyscale) { if (m_greyscale == useGreyscale) return; m_greyscale = useGreyscale; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setChannelMode(ChannelMode channelMode) { if (m_channelMode == channelMode) return; m_channelMode = channelMode; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setChannel(int channel) { // std::cerr << "WaveformLayer::setChannel(" << channel << ")" << std::endl; if (m_channel == channel) return; m_channel = channel; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setScale(Scale scale) { if (m_scale == scale) return; m_scale = scale; m_cacheValid = false; emit layerParametersChanged(); } void WaveformLayer::setAggressiveCacheing(bool aggressive) { if (m_aggressive == aggressive) return; m_aggressive = aggressive; m_cacheValid = false; emit layerParametersChanged(); } int WaveformLayer::getCompletion(View *) const { int completion = 100; if (!m_model || !m_model->isOK()) return completion; if (m_model->isReady(&completion)) return 100; return completion; } bool WaveformLayer::getValueExtents(float &min, float &max, bool &log, QString &unit) const { if (m_scale == LinearScale) { min = 0.0; max = 1.0; unit = "V"; } else if (m_scale == MeterScale) { return false; //!!! } else { min = AudioLevel::multiplier_to_dB(0.0); max = AudioLevel::multiplier_to_dB(1.0); unit = "dB"; } return true; } int WaveformLayer::dBscale(float sample, int m) const { //!!! if (sample < 0.0) return -dBscale(-sample, m); if (sample < 0.0) return dBscale(-sample, m); float dB = AudioLevel::multiplier_to_dB(sample); if (dB < -50.0) return 0; if (dB > 0.0) return m; return int(((dB + 50.0) * m) / 50.0 + 0.1); } size_t WaveformLayer::getChannelArrangement(size_t &min, size_t &max, bool &merging, bool &mixing) const { if (!m_model || !m_model->isOK()) return 0; size_t channels = m_model->getChannelCount(); if (channels == 0) return 0; size_t rawChannels = channels; if (m_channel == -1) { min = 0; if (m_channelMode == MergeChannels || m_channelMode == MixChannels) { max = 0; channels = 1; } else { max = channels - 1; } } else { min = m_channel; max = m_channel; rawChannels = 1; channels = 1; } merging = (m_channelMode == MergeChannels && rawChannels > 1); mixing = (m_channelMode == MixChannels && rawChannels > 1); // std::cerr << "WaveformLayer::getChannelArrangement: min " << min << ", max " << max << ", merging " << merging << ", channels " << channels << std::endl; return channels; } bool WaveformLayer::isLayerScrollable(const View *) const { return !m_autoNormalize; } static float meterdbs[] = { -40, -30, -20, -15, -10, -5, -3, -2, -1, -0.5, 0 }; void WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const { if (!m_model || !m_model->isOK()) { return; } long startFrame = v->getStartFrame(); int zoomLevel = v->getZoomLevel(); #ifdef DEBUG_WAVEFORM_PAINT Profiler profiler("WaveformLayer::paint", true); std::cerr << "WaveformLayer::paint (" << rect.x() << "," << rect.y() << ") [" << rect.width() << "x" << rect.height() << "]: zoom " << zoomLevel << ", start " << startFrame << std::endl; #endif size_t channels = 0, minChannel = 0, maxChannel = 0; bool mergingChannels = false, mixingChannels = false; channels = getChannelArrangement(minChannel, maxChannel, mergingChannels, mixingChannels); if (channels == 0) return; int w = v->width(); int h = v->height(); bool ready = m_model->isReady(); QPainter *paint; if (m_aggressive) { if (m_cacheValid && (zoomLevel != m_cacheZoomLevel)) { m_cacheValid = false; } if (m_cacheValid) { viewPainter.drawPixmap(rect, *m_cache, rect); return; } if (!m_cache || m_cache->width() != w || m_cache->height() != h) { delete m_cache; m_cache = new QPixmap(w, h); } paint = new QPainter(m_cache); paint->setPen(Qt::NoPen); paint->setBrush(v->palette().background()); paint->drawRect(rect); paint->setPen(Qt::black); paint->setBrush(Qt::NoBrush); } else { paint = &viewPainter; } paint->setRenderHint(QPainter::Antialiasing, false); int x0 = 0, x1 = w - 1; int y0 = 0, y1 = h - 1; x0 = rect.left(); x1 = rect.right(); y0 = rect.top(); y1 = rect.bottom(); if (x0 > 0) --x0; if (x1 < v->width()) ++x1; long frame0 = v->getFrameForX(x0); long frame1 = v->getFrameForX(x1 + 1); #ifdef DEBUG_WAVEFORM_PAINT std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << ")" << std::endl; #endif RangeSummarisableTimeValueModel::RangeBlock ranges; RangeSummarisableTimeValueModel::RangeBlock otherChannelRanges; RangeSummarisableTimeValueModel::Range range; QColor greys[3]; if (m_colour == Qt::black) { for (int i = 0; i < 3; ++i) { // 0 lightest, 2 darkest int level = 192 - 64 * i; greys[i] = QColor(level, level, level); } } else { int hue, sat, val; m_colour.getHsv(&hue, &sat, &val); for (int i = 0; i < 3; ++i) { // 0 lightest, 2 darkest if (v->hasLightBackground()) { greys[i] = QColor::fromHsv(hue, sat * (i + 1) / 4, val); } else { greys[i] = QColor::fromHsv(hue, sat * (3 - i) / 4, val); } } } QColor midColour = m_colour; if (midColour == Qt::black) { midColour = Qt::gray; } else if (v->hasLightBackground()) { midColour = midColour.light(150); } else { midColour = midColour.light(50); } while (m_effectiveGains.size() <= maxChannel) { m_effectiveGains.push_back(m_gain); } for (size_t ch = minChannel; ch <= maxChannel; ++ch) { int prevRangeBottom = -1, prevRangeTop = -1; QColor prevRangeBottomColour = m_colour, prevRangeTopColour = m_colour; m_effectiveGains[ch] = m_gain; if (m_autoNormalize) { RangeSummarisableTimeValueModel::Range range = m_model->getRange(ch, startFrame < 0 ? 0 : startFrame, v->getEndFrame()); if (mergingChannels || mixingChannels) { RangeSummarisableTimeValueModel::Range otherRange = m_model->getRange(1, startFrame < 0 ? 0 : startFrame, v->getEndFrame()); range.max = std::max(range.max, otherRange.max); range.min = std::min(range.min, otherRange.min); range.absmean = std::min(range.absmean, otherRange.absmean); } m_effectiveGains[ch] = 1.0 / std::max(fabsf(range.max), fabsf(range.min)); } float gain = m_effectiveGains[ch]; int m = (h / channels) / 2; int my = m + (((ch - minChannel) * h) / channels); // std::cerr << "ch = " << ch << ", channels = " << channels << ", m = " << m << ", my = " << my << ", h = " << h << std::endl; if (my - m > y1 || my + m < y0) continue; if ((m_scale == dBScale || m_scale == MeterScale) && m_channelMode != MergeChannels) { m = (h / channels); my = m + (((ch - minChannel) * h) / channels); } paint->setPen(greys[0]); paint->drawLine(x0, my, x1, my); int n = 10; int py = -1; if (v->hasLightBackground()) { paint->setPen(QColor(240, 240, 240)); for (int i = 1; i < n; ++i) { float val = 0.0, nval = 0.0; switch (m_scale) { case LinearScale: val = (i * gain) / n; if (i > 0) nval = -val; break; case MeterScale: val = AudioLevel::dB_to_multiplier(meterdbs[i]) * gain; break; case dBScale: val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10) * gain; break; } if (val < -1.0 || val > 1.0) continue; int y = getYForValue(v, m_scale, val, ch, minChannel, maxChannel); if (py >= 0 && abs(y - py) < 10) continue; else py = y; int ny = y; if (nval != 0.0) { ny = getYForValue(v, m_scale, nval, ch, minChannel, maxChannel); } paint->drawLine(x0, y, x1, y); if (ny != y) { paint->drawLine(x0, ny, x1, ny); } } } if (frame1 <= 0) continue; size_t modelZoomLevel = zoomLevel; ranges = m_model->getRanges (ch, frame0 < 0 ? 0 : frame0, frame1, modelZoomLevel); if (mergingChannels || mixingChannels) { if (m_model->getChannelCount() > 1) { otherChannelRanges = m_model->getRanges (1, frame0 < 0 ? 0 : frame0, frame1, modelZoomLevel); } else { otherChannelRanges = ranges; } } for (int x = x0; x <= x1; ++x) { range = RangeSummarisableTimeValueModel::Range(); size_t index = x - x0; size_t maxIndex = index; if (frame0 < 0) { if (index < size_t(-frame0 / zoomLevel)) { continue; } else { index -= -frame0 / zoomLevel; maxIndex = index; } } if (int(modelZoomLevel) != zoomLevel) { index = size_t((double(index) * zoomLevel) / modelZoomLevel); if (int(modelZoomLevel) < zoomLevel) { // Peaks may be missed! The model should avoid // this by rounding zoom levels up rather than // down, but we'd better cope in case it doesn't maxIndex = index; } else { maxIndex = size_t((double(index + 1) * zoomLevel) / modelZoomLevel) - 1; } } if (index < ranges.size()) { range = ranges[index]; if (maxIndex > index && maxIndex < ranges.size()) { range.max = std::max(range.max, ranges[maxIndex].max); range.min = std::min(range.min, ranges[maxIndex].min); range.absmean = (range.absmean + ranges[maxIndex].absmean) / 2; } } else { continue; } int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; if (mergingChannels) { if (index < otherChannelRanges.size()) { range.max = fabsf(range.max); range.min = -fabsf(otherChannelRanges[index].max); range.absmean = (range.absmean + otherChannelRanges[index].absmean) / 2; if (maxIndex > index && maxIndex < ranges.size()) { // let's not concern ourselves about the mean range.min = std::min (range.min, -fabsf(otherChannelRanges[maxIndex].max)); } } } else if (mixingChannels) { if (index < otherChannelRanges.size()) { range.max = (range.max + otherChannelRanges[index].max) / 2; range.min = (range.min + otherChannelRanges[index].min) / 2; range.absmean = (range.absmean + otherChannelRanges[index].absmean) / 2; } } int greyLevels = 1; if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4; switch (m_scale) { case LinearScale: rangeBottom = int( m * greyLevels * range.min * gain); rangeTop = int( m * greyLevels * range.max * gain); meanBottom = int(-m * range.absmean * gain); meanTop = int( m * range.absmean * gain); break; case dBScale: if (!mergingChannels) { int db0 = dBscale(range.min * gain, m); int db1 = dBscale(range.max * gain, m); rangeTop = std::max(db0, db1); meanTop = std::min(db0, db1); if (mixingChannels) rangeBottom = meanTop; else rangeBottom = dBscale(range.absmean * gain, m); meanBottom = rangeBottom; } else { rangeBottom = -dBscale(range.min * gain, m * greyLevels); rangeTop = dBscale(range.max * gain, m * greyLevels); meanBottom = -dBscale(range.absmean * gain, m); meanTop = dBscale(range.absmean * gain, m); } break; case MeterScale: if (!mergingChannels) { int r0 = abs(AudioLevel::multiplier_to_preview(range.min * gain, m)); int r1 = abs(AudioLevel::multiplier_to_preview(range.max * gain, m)); rangeTop = std::max(r0, r1); meanTop = std::min(r0, r1); if (mixingChannels) rangeBottom = meanTop; else rangeBottom = AudioLevel::multiplier_to_preview(range.absmean * gain, m); meanBottom = rangeBottom; } else { rangeBottom = AudioLevel::multiplier_to_preview(range.min * gain, m * greyLevels); rangeTop = AudioLevel::multiplier_to_preview(range.max * gain, m * greyLevels); meanBottom = -AudioLevel::multiplier_to_preview(range.absmean * gain, m); meanTop = AudioLevel::multiplier_to_preview(range.absmean * gain, m); } break; } rangeBottom = my * greyLevels - rangeBottom; rangeTop = my * greyLevels - rangeTop; meanBottom = my - meanBottom; meanTop = my - meanTop; int topFill = (rangeTop % greyLevels); if (topFill > 0) topFill = greyLevels - topFill; int bottomFill = (rangeBottom % greyLevels); rangeTop = rangeTop / greyLevels; rangeBottom = rangeBottom / greyLevels; bool clipped = false; if (rangeTop < my - m) { rangeTop = my - m; } if (rangeTop > my + m) { rangeTop = my + m; } if (rangeBottom < my - m) { rangeBottom = my - m; } if (rangeBottom > my + m) { rangeBottom = my + m; } if (range.max <= -1.0 || range.max >= 1.0) clipped = true; if (meanBottom > rangeBottom) meanBottom = rangeBottom; if (meanTop < rangeTop) meanTop = rangeTop; bool drawMean = m_showMeans; if (meanTop == rangeTop) { if (meanTop < meanBottom) ++meanTop; else drawMean = false; } if (meanBottom == rangeBottom && m_scale == LinearScale) { if (meanBottom > meanTop) --meanBottom; else drawMean = false; } if (x != x0 && prevRangeBottom != -1) { if (prevRangeBottom > rangeBottom && prevRangeTop > rangeBottom) { // paint->setPen(midColour); paint->setPen(m_colour); paint->drawLine(x-1, prevRangeTop, x, rangeBottom); paint->setPen(prevRangeTopColour); paint->drawPoint(x-1, prevRangeTop); } else if (prevRangeBottom < rangeTop && prevRangeTop < rangeTop) { // paint->setPen(midColour); paint->setPen(m_colour); paint->drawLine(x-1, prevRangeBottom, x, rangeTop); paint->setPen(prevRangeBottomColour); paint->drawPoint(x-1, prevRangeBottom); } } if (ready) { if (clipped /*!!! || range.min * gain <= -1.0 || range.max * gain >= 1.0 */) { paint->setPen(Qt::red); } else { paint->setPen(m_colour); } } else { paint->setPen(midColour); } paint->drawLine(x, rangeBottom, x, rangeTop); prevRangeTopColour = m_colour; prevRangeBottomColour = m_colour; if (m_greyscale && (m_scale == LinearScale) && ready) { if (!clipped) { if (rangeTop < rangeBottom) { if (topFill > 0 && (!drawMean || (rangeTop < meanTop - 1))) { paint->setPen(greys[topFill - 1]); paint->drawPoint(x, rangeTop); prevRangeTopColour = greys[topFill - 1]; } if (bottomFill > 0 && (!drawMean || (rangeBottom > meanBottom + 1))) { paint->setPen(greys[bottomFill - 1]); paint->drawPoint(x, rangeBottom); prevRangeBottomColour = greys[bottomFill - 1]; } } } } if (drawMean) { paint->setPen(midColour); paint->drawLine(x, meanBottom, x, meanTop); } prevRangeBottom = rangeBottom; prevRangeTop = rangeTop; } } if (m_aggressive) { if (ready && rect == v->rect()) { m_cacheValid = true; m_cacheZoomLevel = zoomLevel; } paint->end(); delete paint; viewPainter.drawPixmap(rect, *m_cache, rect); } } QString WaveformLayer::getFeatureDescription(View *v, QPoint &pos) const { int x = pos.x(); if (!m_model || !m_model->isOK()) return ""; long f0 = v->getFrameForX(x); long f1 = v->getFrameForX(x + 1); if (f0 < 0) f0 = 0; if (f1 <= f0) return ""; QString text; RealTime rt0 = RealTime::frame2RealTime(f0, m_model->getSampleRate()); RealTime rt1 = RealTime::frame2RealTime(f1, m_model->getSampleRate()); if (f1 != f0 + 1 && (rt0.sec != rt1.sec || rt0.msec() != rt1.msec())) { text += tr("Time:\t%1 - %2") .arg(rt0.toText(true).c_str()) .arg(rt1.toText(true).c_str()); } else { text += tr("Time:\t%1") .arg(rt0.toText(true).c_str()); } size_t channels = 0, minChannel = 0, maxChannel = 0; bool mergingChannels = false, mixingChannels = false; channels = getChannelArrangement(minChannel, maxChannel, mergingChannels, mixingChannels); if (channels == 0) return ""; for (size_t ch = minChannel; ch <= maxChannel; ++ch) { size_t blockSize = v->getZoomLevel(); RangeSummarisableTimeValueModel::RangeBlock ranges = m_model->getRanges(ch, f0, f1, blockSize); if (ranges.empty()) continue; RangeSummarisableTimeValueModel::Range range = ranges[0]; QString label = tr("Level:"); if (minChannel != maxChannel) { if (ch == 0) label = tr("Left:"); else if (ch == 1) label = tr("Right:"); else label = tr("Channel %1").arg(ch + 1); } bool singleValue = false; float min, max; if (fabs(range.min) < 0.01) { min = range.min; max = range.max; singleValue = (min == max); } else { int imin = int(range.min * 1000); int imax = int(range.max * 1000); singleValue = (imin == imax); min = float(imin)/1000; max = float(imax)/1000; } int db = int(AudioLevel::multiplier_to_dB(std::max(fabsf(range.min), fabsf(range.max))) * 100); if (!singleValue) { text += tr("\n%1\t%2 - %3 (%4 dB peak)") .arg(label).arg(min).arg(max).arg(float(db)/100); } else { text += tr("\n%1\t%2 (%3 dB peak)") .arg(label).arg(min).arg(float(db)/100); } } return text; } int WaveformLayer::getYForValue(View *v, Scale scale, float value, size_t channel, size_t minChannel, size_t maxChannel) const { if (maxChannel < minChannel || channel < minChannel) return 0; int w = v->width(); int h = v->height(); int channels = maxChannel - minChannel + 1; int m = (h / channels) / 2; int my = m + (((channel - minChannel) * h) / channels); if ((m_scale == dBScale || m_scale == MeterScale) && m_channelMode != MergeChannels) { m = (h / channels); my = m + (((channel - minChannel) * h) / channels); } int vy = 0; switch (scale) { case LinearScale: vy = int(m * value); break; case MeterScale: vy = AudioLevel::multiplier_to_preview(value, m); break; case dBScale: vy = dBscale(value, m); break; } return my - vy; } int WaveformLayer::getVerticalScaleWidth(View *v, QPainter &paint) const { if (m_scale == LinearScale) { return paint.fontMetrics().width("0.0") + 13; } else { return std::max(paint.fontMetrics().width(tr("0dB")), paint.fontMetrics().width(tr("-Inf"))) + 13; } } void WaveformLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) { return; } size_t channels = 0, minChannel = 0, maxChannel = 0; bool mergingChannels = false, mixingChannels = false; channels = getChannelArrangement(minChannel, maxChannel, mergingChannels, mixingChannels); if (channels == 0) return; int h = rect.height(), w = rect.width(); int textHeight = paint.fontMetrics().height(); int toff = -textHeight/2 + paint.fontMetrics().ascent() + 1; float gain = m_gain; for (size_t ch = minChannel; ch <= maxChannel; ++ch) { int lastLabelledY = -1; if (ch < m_effectiveGains.size()) gain = m_effectiveGains[ch]; int n = 10; for (int i = 0; i <= n; ++i) { float val = 0.0, nval = 0.0; QString text = ""; switch (m_scale) { case LinearScale: val = (i * gain) / n; text = QString("%1").arg(float(i) / n); if (i == 0) text = "0.0"; else { nval = -val; if (i == n) text = "1.0"; } break; case MeterScale: val = AudioLevel::dB_to_multiplier(meterdbs[i]) * gain; text = QString("%1").arg(meterdbs[i]); if (i == n) text = tr("0dB"); if (i == 0) { text = tr("-Inf"); val = 0.0; } break; case dBScale: val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10) * gain; text = QString("%1").arg(-(10*n) + i * 10); if (i == n) text = tr("0dB"); if (i == 0) { text = tr("-Inf"); val = 0.0; } break; } if (val < -1.0 || val > 1.0) continue; int y = getYForValue(v, m_scale, val, ch, minChannel, maxChannel); int ny = y; if (nval != 0.0) { ny = getYForValue(v, m_scale, nval, ch, minChannel, maxChannel); } bool spaceForLabel = (i == 0 || abs(y - lastLabelledY) >= textHeight - 1); if (spaceForLabel) { int tx = 3; if (m_scale != LinearScale) { tx = w - 10 - paint.fontMetrics().width(text); } int ty = y; if (ty < paint.fontMetrics().ascent()) { ty = paint.fontMetrics().ascent(); } else if (ty > h - paint.fontMetrics().descent()) { ty = h - paint.fontMetrics().descent(); } else { ty += toff; } paint.drawText(tx, ty, text); lastLabelledY = ty - toff; if (ny != y) { ty = ny; if (ty < paint.fontMetrics().ascent()) { ty = paint.fontMetrics().ascent(); } else if (ty > h - paint.fontMetrics().descent()) { ty = h - paint.fontMetrics().descent(); } else { ty += toff; } paint.drawText(tx, ty, text); } paint.drawLine(w - 7, y, w, y); if (ny != y) paint.drawLine(w - 7, ny, w, ny); } else { paint.drawLine(w - 4, y, w, y); if (ny != y) paint.drawLine(w - 4, ny, w, ny); } } } } QString WaveformLayer::toXmlString(QString indent, QString extraAttributes) const { QString s; s += QString("gain=\"%1\" " "colour=\"%2\" " "showMeans=\"%3\" " "greyscale=\"%4\" " "channelMode=\"%5\" " "channel=\"%6\" " "scale=\"%7\" " "aggressive=\"%8\" " "autoNormalize=\"%9\"") .arg(m_gain) .arg(encodeColour(m_colour)) .arg(m_showMeans) .arg(m_greyscale) .arg(m_channelMode) .arg(m_channel) .arg(m_scale) .arg(m_aggressive) .arg(m_autoNormalize); return Layer::toXmlString(indent, extraAttributes + " " + s); } void WaveformLayer::setProperties(const QXmlAttributes &attributes) { bool ok = false; float gain = attributes.value("gain").toFloat(&ok); if (ok) setGain(gain); QString colourSpec = attributes.value("colour"); if (colourSpec != "") { QColor colour(colourSpec); if (colour.isValid()) { setBaseColour(QColor(colourSpec)); } } bool showMeans = (attributes.value("showMeans") == "1" || attributes.value("showMeans") == "true"); setShowMeans(showMeans); bool greyscale = (attributes.value("greyscale") == "1" || attributes.value("greyscale") == "true"); setUseGreyscale(greyscale); ChannelMode channelMode = (ChannelMode) attributes.value("channelMode").toInt(&ok); if (ok) setChannelMode(channelMode); int channel = attributes.value("channel").toInt(&ok); if (ok) setChannel(channel); Scale scale = (Scale) attributes.value("scale").toInt(&ok); if (ok) setScale(scale); bool aggressive = (attributes.value("aggressive") == "1" || attributes.value("aggressive") == "true"); setUseGreyscale(aggressive); bool autoNormalize = (attributes.value("autoNormalize") == "1" || attributes.value("autoNormalize") == "true"); setAutoNormalize(autoNormalize); } int WaveformLayer::getVerticalZoomSteps(int &defaultStep) const { defaultStep = 50; return 100; } int WaveformLayer::getCurrentVerticalZoomStep() const { int val = lrint(log10(m_gain) * 20.0) + 50; if (val < 0) val = 0; if (val > 100) val = 100; return val; } void WaveformLayer::setVerticalZoomStep(int step) { setGain(pow(10, float(step - 50) / 20.0)); }