| 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 } |