annotate layer/ColourScale.cpp @ 1551:e79731086b0f

Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author Chris Cannam
date Thu, 21 Nov 2019 14:02:57 +0000
parents d79e21855aef
children
rev   line source
Chris@1068 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1068 2
Chris@1068 3 /*
Chris@1068 4 Sonic Visualiser
Chris@1068 5 An audio file viewer and annotation editor.
Chris@1068 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1068 7 This file copyright 2006-2016 Chris Cannam and QMUL.
Chris@1068 8
Chris@1068 9 This program is free software; you can redistribute it and/or
Chris@1068 10 modify it under the terms of the GNU General Public License as
Chris@1068 11 published by the Free Software Foundation; either version 2 of the
Chris@1068 12 License, or (at your option) any later version. See the file
Chris@1068 13 COPYING included with this distribution for more information.
Chris@1068 14 */
Chris@1068 15
Chris@1068 16 #include "ColourScale.h"
Chris@1068 17
Chris@1068 18 #include "base/AudioLevel.h"
Chris@1068 19 #include "base/LogRange.h"
Chris@1068 20
Chris@1068 21 #include <cmath>
Chris@1129 22 #include <iostream>
Chris@1129 23
Chris@1129 24 using namespace std;
Chris@1068 25
Chris@1068 26 int ColourScale::m_maxPixel = 255;
Chris@1068 27
Chris@1070 28 ColourScale::ColourScale(Parameters parameters) :
Chris@1070 29 m_params(parameters),
Chris@1362 30 m_mapper(m_params.colourMap, m_params.inverted, 1.f, double(m_maxPixel))
Chris@1068 31 {
Chris@1070 32 if (m_params.minValue >= m_params.maxValue) {
Chris@1265 33 SVCERR << "ERROR: ColourScale::ColourScale: minValue = "
Chris@1129 34 << m_params.minValue << ", maxValue = " << m_params.maxValue << endl;
Chris@1266 35 throw std::logic_error("maxValue must be greater than minValue");
Chris@1068 36 }
Chris@1068 37
Chris@1070 38 m_mappedMin = m_params.minValue;
Chris@1070 39 m_mappedMax = m_params.maxValue;
Chris@1068 40
Chris@1127 41 if (m_mappedMin < m_params.threshold) {
Chris@1127 42 m_mappedMin = m_params.threshold;
Chris@1127 43 }
Chris@1127 44
Chris@1137 45 if (m_params.scaleType == ColourScaleType::Log) {
Chris@1068 46
Chris@1253 47 // When used in e.g. spectrogram, we have a range with a min
Chris@1253 48 // value of zero. The LogRange converts that to a threshold
Chris@1253 49 // value of -10, so for a range of e.g. (0,1) we end up with
Chris@1253 50 // (-10,0) as the mapped range.
Chris@1253 51 //
Chris@1253 52 // But in other contexts we could end up with a mapped range
Chris@1253 53 // much larger than that if we have a small non-zero minimum
Chris@1253 54 // value (less than 1e-10), or a particularly large
Chris@1253 55 // maximum. That's unlikely to give us good results, so let's
Chris@1253 56 // insist that the mapped log range has no more than 10
Chris@1253 57 // difference between min and max, to match the behaviour when
Chris@1253 58 // min == 0 at the input.
Chris@1253 59 //
Chris@1253 60 double threshold = -10.0;
Chris@1253 61 LogRange::mapRange(m_mappedMin, m_mappedMax, threshold);
Chris@1253 62 if (m_mappedMin < m_mappedMax + threshold) {
Chris@1253 63 m_mappedMin = m_mappedMax + threshold;
Chris@1253 64 }
Chris@1266 65
Chris@1137 66 } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) {
Chris@1266 67
Chris@1266 68 m_mappedMin = -1.0;
Chris@1266 69 m_mappedMax = 1.0;
Chris@1068 70
Chris@1137 71 } else if (m_params.scaleType == ColourScaleType::Absolute) {
Chris@1068 72
Chris@1266 73 m_mappedMin = fabs(m_mappedMin);
Chris@1266 74 m_mappedMax = fabs(m_mappedMax);
Chris@1266 75 if (m_mappedMin >= m_mappedMax) {
Chris@1266 76 std::swap(m_mappedMin, m_mappedMax);
Chris@1266 77 }
Chris@1068 78 }
Chris@1068 79
Chris@1068 80 if (m_mappedMin >= m_mappedMax) {
Chris@1265 81 SVCERR << "ERROR: ColourScale::ColourScale: minValue = " << m_params.minValue
Chris@1129 82 << ", maxValue = " << m_params.maxValue
Chris@1129 83 << ", threshold = " << m_params.threshold
Chris@1137 84 << ", scale = " << int(m_params.scaleType)
Chris@1129 85 << " resulting in mapped minValue = " << m_mappedMin
Chris@1129 86 << ", mapped maxValue = " << m_mappedMax << endl;
Chris@1266 87 throw std::logic_error("maxValue must be greater than minValue [after mapping]");
Chris@1068 88 }
Chris@1068 89 }
Chris@1068 90
Chris@1071 91 ColourScale::~ColourScale()
Chris@1071 92 {
Chris@1071 93 }
Chris@1071 94
Chris@1105 95 ColourScaleType
Chris@1079 96 ColourScale::getScale() const
Chris@1079 97 {
Chris@1137 98 return m_params.scaleType;
Chris@1079 99 }
Chris@1079 100
Chris@1068 101 int
Chris@1079 102 ColourScale::getPixel(double value) const
Chris@1068 103 {
Chris@1068 104 double maxPixF = m_maxPixel;
Chris@1068 105
Chris@1137 106 if (m_params.scaleType == ColourScaleType::Phase) {
Chris@1266 107 double half = (maxPixF - 1.f) / 2.f;
Chris@1138 108 int pixel = 1 + int((value * half) / M_PI + half);
Chris@1265 109 // SVCERR << "phase = " << value << " pixel = " << pixel << endl;
Chris@1138 110 return pixel;
Chris@1068 111 }
Chris@1068 112
Chris@1070 113 value *= m_params.gain;
Chris@1137 114
Chris@1070 115 if (value < m_params.threshold) return 0;
Chris@1068 116
Chris@1068 117 double mapped = value;
Chris@1068 118
Chris@1137 119 if (m_params.scaleType == ColourScaleType::Log) {
Chris@1266 120 mapped = LogRange::map(value);
Chris@1137 121 } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) {
Chris@1266 122 if (mapped < -1.f) mapped = -1.f;
Chris@1266 123 if (mapped > 1.f) mapped = 1.f;
Chris@1137 124 } else if (m_params.scaleType == ColourScaleType::Absolute) {
Chris@1266 125 if (mapped < 0.f) mapped = -mapped;
Chris@1068 126 }
Chris@1137 127
Chris@1137 128 mapped *= m_params.multiple;
Chris@1137 129
Chris@1068 130 if (mapped < m_mappedMin) {
Chris@1266 131 mapped = m_mappedMin;
Chris@1068 132 }
Chris@1068 133 if (mapped > m_mappedMax) {
Chris@1266 134 mapped = m_mappedMax;
Chris@1068 135 }
Chris@1068 136
Chris@1068 137 double proportion = (mapped - m_mappedMin) / (m_mappedMax - m_mappedMin);
Chris@1068 138
Chris@1068 139 int pixel = 0;
Chris@1068 140
Chris@1137 141 if (m_params.scaleType == ColourScaleType::Meter) {
Chris@1266 142 pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1;
Chris@1068 143 } else {
Chris@1266 144 pixel = int(proportion * maxPixF) + 1;
Chris@1068 145 }
Chris@1068 146
Chris@1070 147 if (pixel < 0) {
Chris@1266 148 pixel = 0;
Chris@1070 149 }
Chris@1070 150 if (pixel > m_maxPixel) {
Chris@1266 151 pixel = m_maxPixel;
Chris@1070 152 }
Chris@1068 153 return pixel;
Chris@1068 154 }
Chris@1068 155
Chris@1068 156 QColor
Chris@1079 157 ColourScale::getColourForPixel(int pixel, int rotation) const
Chris@1068 158 {
Chris@1068 159 if (pixel < 0) {
Chris@1266 160 pixel = 0;
Chris@1068 161 }
Chris@1068 162 if (pixel > m_maxPixel) {
Chris@1266 163 pixel = m_maxPixel;
Chris@1068 164 }
Chris@1068 165 if (pixel == 0) {
Chris@1266 166 if (m_mapper.hasLightBackground()) {
Chris@1266 167 return Qt::white;
Chris@1266 168 } else {
Chris@1266 169 return Qt::black;
Chris@1266 170 }
Chris@1068 171 } else {
Chris@1266 172 int target = int(pixel) + rotation;
Chris@1266 173 while (target < 1) target += m_maxPixel;
Chris@1266 174 while (target > m_maxPixel) target -= m_maxPixel;
Chris@1266 175 return m_mapper.map(double(target));
Chris@1068 176 }
Chris@1068 177 }