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 <cmath>
Chris@1129: #include <iostream>
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@1129:         cerr << "ERROR: ColourScale::ColourScale: minValue = "
Chris@1129:              << m_params.minValue << ", maxValue = " << m_params.maxValue << endl;
Chris@1068: 	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@1068: 	LogRange::mapRange(m_mappedMin, m_mappedMax);
Chris@1068: 	
Chris@1137:     } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) {
Chris@1068: 	
Chris@1068: 	m_mappedMin = -1.0;
Chris@1068: 	m_mappedMax =  1.0;
Chris@1068: 
Chris@1137:     } else if (m_params.scaleType == ColourScaleType::Absolute) {
Chris@1068: 
Chris@1068: 	m_mappedMin = fabs(m_mappedMin);
Chris@1068: 	m_mappedMax = fabs(m_mappedMax);
Chris@1068: 	if (m_mappedMin >= m_mappedMax) {
Chris@1068: 	    std::swap(m_mappedMin, m_mappedMax);
Chris@1068: 	}
Chris@1068:     }
Chris@1068: 
Chris@1068:     if (m_mappedMin >= m_mappedMax) {
Chris@1129:         cerr << "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@1068: 	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@1068: 	double half = (maxPixF - 1.f) / 2.f;
Chris@1138:         int pixel = 1 + int((value * half) / M_PI + half);
Chris@1143: //        cerr << "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@1068: 	mapped = LogRange::map(value);
Chris@1137:     } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) {
Chris@1068: 	if (mapped < -1.f) mapped = -1.f;
Chris@1068: 	if (mapped > 1.f) mapped = 1.f;
Chris@1137:     } else if (m_params.scaleType == ColourScaleType::Absolute) {
Chris@1068: 	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@1068: 	mapped = m_mappedMin;
Chris@1068:     }
Chris@1068:     if (mapped > m_mappedMax) {
Chris@1068: 	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@1068: 	pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1;
Chris@1068:     } else {
Chris@1068: 	pixel = int(proportion * maxPixF) + 1;
Chris@1068:     }
Chris@1068: 
Chris@1070:     if (pixel < 0) {
Chris@1070: 	pixel = 0;
Chris@1070:     }
Chris@1070:     if (pixel > m_maxPixel) {
Chris@1070: 	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@1068: 	pixel = 0;
Chris@1068:     }
Chris@1068:     if (pixel > m_maxPixel) {
Chris@1068: 	pixel = m_maxPixel;
Chris@1068:     }
Chris@1068:     if (pixel == 0) {
Chris@1068: 	if (m_mapper.hasLightBackground()) {
Chris@1068: 	    return Qt::white;
Chris@1068: 	} else {
Chris@1068: 	    return Qt::black;
Chris@1068: 	}
Chris@1068:     } else {
Chris@1068: 	int target = int(pixel) + rotation;
Chris@1068: 	while (target < 1) target += m_maxPixel;
Chris@1068: 	while (target > m_maxPixel) target -= m_maxPixel;
Chris@1068: 	return m_mapper.map(double(target));
Chris@1068:     }
Chris@1068: }