Chris@1068: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1068: Chris@1068: /* Chris@1068: Sonic Visualiser Chris@1068: An audio file viewer and annotation editor. Chris@1068: Centre for Digital Music, Queen Mary, University of London. Chris@1068: This file copyright 2006-2016 Chris Cannam and QMUL. Chris@1068: Chris@1068: This program is free software; you can redistribute it and/or Chris@1068: modify it under the terms of the GNU General Public License as Chris@1068: published by the Free Software Foundation; either version 2 of the Chris@1068: License, or (at your option) any later version. See the file Chris@1068: COPYING included with this distribution for more information. Chris@1068: */ Chris@1068: Chris@1068: #include "ColourScale.h" Chris@1068: Chris@1068: #include "base/AudioLevel.h" Chris@1068: #include "base/LogRange.h" Chris@1068: Chris@1068: #include Chris@1129: #include Chris@1129: Chris@1129: using namespace std; Chris@1068: Chris@1068: int ColourScale::m_maxPixel = 255; Chris@1068: Chris@1070: ColourScale::ColourScale(Parameters parameters) : Chris@1070: m_params(parameters), Chris@1070: m_mapper(m_params.colourMap, 1.f, double(m_maxPixel)) Chris@1068: { Chris@1070: if (m_params.minValue >= m_params.maxValue) { Chris@1265: SVCERR << "ERROR: ColourScale::ColourScale: minValue = " Chris@1129: << m_params.minValue << ", maxValue = " << m_params.maxValue << endl; Chris@1266: throw std::logic_error("maxValue must be greater than minValue"); Chris@1068: } Chris@1068: Chris@1070: m_mappedMin = m_params.minValue; Chris@1070: m_mappedMax = m_params.maxValue; Chris@1068: Chris@1127: if (m_mappedMin < m_params.threshold) { Chris@1127: m_mappedMin = m_params.threshold; Chris@1127: } Chris@1127: Chris@1137: if (m_params.scaleType == ColourScaleType::Log) { Chris@1068: Chris@1253: // When used in e.g. spectrogram, we have a range with a min Chris@1253: // value of zero. The LogRange converts that to a threshold Chris@1253: // value of -10, so for a range of e.g. (0,1) we end up with Chris@1253: // (-10,0) as the mapped range. Chris@1253: // Chris@1253: // But in other contexts we could end up with a mapped range Chris@1253: // much larger than that if we have a small non-zero minimum Chris@1253: // value (less than 1e-10), or a particularly large Chris@1253: // maximum. That's unlikely to give us good results, so let's Chris@1253: // insist that the mapped log range has no more than 10 Chris@1253: // difference between min and max, to match the behaviour when Chris@1253: // min == 0 at the input. Chris@1253: // Chris@1253: double threshold = -10.0; Chris@1253: LogRange::mapRange(m_mappedMin, m_mappedMax, threshold); Chris@1253: if (m_mappedMin < m_mappedMax + threshold) { Chris@1253: m_mappedMin = m_mappedMax + threshold; Chris@1253: } Chris@1266: Chris@1137: } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { Chris@1266: Chris@1266: m_mappedMin = -1.0; Chris@1266: m_mappedMax = 1.0; Chris@1068: Chris@1137: } else if (m_params.scaleType == ColourScaleType::Absolute) { Chris@1068: Chris@1266: m_mappedMin = fabs(m_mappedMin); Chris@1266: m_mappedMax = fabs(m_mappedMax); Chris@1266: if (m_mappedMin >= m_mappedMax) { Chris@1266: std::swap(m_mappedMin, m_mappedMax); Chris@1266: } Chris@1068: } Chris@1068: Chris@1068: if (m_mappedMin >= m_mappedMax) { Chris@1265: SVCERR << "ERROR: ColourScale::ColourScale: minValue = " << m_params.minValue Chris@1129: << ", maxValue = " << m_params.maxValue Chris@1129: << ", threshold = " << m_params.threshold Chris@1137: << ", scale = " << int(m_params.scaleType) Chris@1129: << " resulting in mapped minValue = " << m_mappedMin Chris@1129: << ", mapped maxValue = " << m_mappedMax << endl; Chris@1266: throw std::logic_error("maxValue must be greater than minValue [after mapping]"); Chris@1068: } Chris@1068: } Chris@1068: Chris@1071: ColourScale::~ColourScale() Chris@1071: { Chris@1071: } Chris@1071: Chris@1105: ColourScaleType Chris@1079: ColourScale::getScale() const Chris@1079: { Chris@1137: return m_params.scaleType; Chris@1079: } Chris@1079: Chris@1068: int Chris@1079: ColourScale::getPixel(double value) const Chris@1068: { Chris@1068: double maxPixF = m_maxPixel; Chris@1068: Chris@1137: if (m_params.scaleType == ColourScaleType::Phase) { Chris@1266: double half = (maxPixF - 1.f) / 2.f; Chris@1138: int pixel = 1 + int((value * half) / M_PI + half); Chris@1265: // SVCERR << "phase = " << value << " pixel = " << pixel << endl; Chris@1138: return pixel; Chris@1068: } Chris@1068: Chris@1070: value *= m_params.gain; Chris@1137: Chris@1070: if (value < m_params.threshold) return 0; Chris@1068: Chris@1068: double mapped = value; Chris@1068: Chris@1137: if (m_params.scaleType == ColourScaleType::Log) { Chris@1266: mapped = LogRange::map(value); Chris@1137: } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { Chris@1266: if (mapped < -1.f) mapped = -1.f; Chris@1266: if (mapped > 1.f) mapped = 1.f; Chris@1137: } else if (m_params.scaleType == ColourScaleType::Absolute) { Chris@1266: if (mapped < 0.f) mapped = -mapped; Chris@1068: } Chris@1137: Chris@1137: mapped *= m_params.multiple; Chris@1137: Chris@1068: if (mapped < m_mappedMin) { Chris@1266: mapped = m_mappedMin; Chris@1068: } Chris@1068: if (mapped > m_mappedMax) { Chris@1266: mapped = m_mappedMax; Chris@1068: } Chris@1068: Chris@1068: double proportion = (mapped - m_mappedMin) / (m_mappedMax - m_mappedMin); Chris@1068: Chris@1068: int pixel = 0; Chris@1068: Chris@1137: if (m_params.scaleType == ColourScaleType::Meter) { Chris@1266: pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1; Chris@1068: } else { Chris@1266: pixel = int(proportion * maxPixF) + 1; Chris@1068: } Chris@1068: Chris@1070: if (pixel < 0) { Chris@1266: pixel = 0; Chris@1070: } Chris@1070: if (pixel > m_maxPixel) { Chris@1266: pixel = m_maxPixel; Chris@1070: } Chris@1068: return pixel; Chris@1068: } Chris@1068: Chris@1068: QColor Chris@1079: ColourScale::getColourForPixel(int pixel, int rotation) const Chris@1068: { Chris@1068: if (pixel < 0) { Chris@1266: pixel = 0; Chris@1068: } Chris@1068: if (pixel > m_maxPixel) { Chris@1266: pixel = m_maxPixel; Chris@1068: } Chris@1068: if (pixel == 0) { Chris@1266: if (m_mapper.hasLightBackground()) { Chris@1266: return Qt::white; Chris@1266: } else { Chris@1266: return Qt::black; Chris@1266: } Chris@1068: } else { Chris@1266: int target = int(pixel) + rotation; Chris@1266: while (target < 1) target += m_maxPixel; Chris@1266: while (target > m_maxPixel) target -= m_maxPixel; Chris@1266: return m_mapper.map(double(target)); Chris@1068: } Chris@1068: }