Chris@60: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@60: 
Chris@60: /*
Chris@60:     Sonic Visualiser
Chris@60:     An audio file viewer and annotation editor.
Chris@60:     Centre for Digital Music, Queen Mary, University of London.
Chris@182:     This file copyright 2006 Chris Cannam and QMUL.
Chris@60:     
Chris@60:     This program is free software; you can redistribute it and/or
Chris@60:     modify it under the terms of the GNU General Public License as
Chris@60:     published by the Free Software Foundation; either version 2 of the
Chris@60:     License, or (at your option) any later version.  See the file
Chris@60:     COPYING included with this distribution for more information.
Chris@60: */
Chris@60: 
Chris@60: #include "PluginParameterBox.h"
Chris@60: 
Chris@60: #include "AudioDial.h"
Chris@60: 
Chris@71: #include "plugin/PluginXml.h"
Chris@342: #include "plugin/RealTimePluginInstance.h" // for PortHint stuff
Chris@71: 
Chris@167: #include "base/RangeMapper.h"
Chris@167: 
Chris@60: #include <QDoubleSpinBox>
Chris@60: #include <QGridLayout>
Chris@63: #include <QComboBox>
Chris@63: #include <QCheckBox>
Chris@60: #include <QLayout>
Chris@60: #include <QLabel>
Chris@60: 
Chris@60: #include <iostream>
Chris@60: #include <string>
Chris@60: 
Chris@78: #include <cmath>
Chris@78: 
Chris@71: PluginParameterBox::PluginParameterBox(Vamp::PluginBase *plugin, QWidget *parent) :
Chris@62:     QFrame(parent),
Chris@293:     m_plugin(plugin),
Chris@293:     m_programCombo(0)
Chris@60: {
Chris@60:     m_layout = new QGridLayout;
Chris@60:     setLayout(m_layout);
Chris@60:     populate();
Chris@60: }
Chris@60: 
Chris@60: PluginParameterBox::~PluginParameterBox()
Chris@60: {
Chris@60: }
Chris@60: 
Chris@60: void
Chris@60: PluginParameterBox::populate()
Chris@60: {
Chris@71:     Vamp::PluginBase::ParameterList params = m_plugin->getParameterDescriptors();
Chris@293:     m_programs = m_plugin->getPrograms();
Chris@60: 
Chris@60:     m_params.clear();
Chris@60: 
Chris@293:     if (params.empty() && m_programs.empty()) {
Chris@62:         m_layout->addWidget
Chris@62:             (new QLabel(tr("This plugin has no adjustable parameters.")),
Chris@62:              0, 0);
Chris@62:     }
Chris@62: 
Chris@63:     int offset = 0;
Chris@63: 
Chris@293:     if (!m_programs.empty()) {
Chris@63: 
Chris@63:         std::string currentProgram = m_plugin->getCurrentProgram();
Chris@63: 
Chris@293:         m_programCombo = new QComboBox;
Chris@293:         m_programCombo->setMaxVisibleItems
Chris@908:             (int(m_programs.size() < 25 ? m_programs.size() : 20));
Chris@63: 
Chris@908:         for (int i = 0; in_range_for(m_programs, i); ++i) {
Chris@293:             m_programCombo->addItem(m_programs[i].c_str());
Chris@293:             if (m_programs[i] == currentProgram) {
Chris@908:                 m_programCombo->setCurrentIndex(int(i));
Chris@63:             }
Chris@63:         }
Chris@63: 
Chris@63:         m_layout->addWidget(new QLabel(tr("Program")), 0, 0);
Chris@293:         m_layout->addWidget(m_programCombo, 0, 1, 1, 2);
Chris@63: 
Chris@293:         connect(m_programCombo, SIGNAL(currentIndexChanged(const QString &)),
Chris@63:                 this, SLOT(programComboChanged(const QString &)));
Chris@63: 
Chris@63:         offset = 1;
Chris@63:     }
Chris@63: 
Chris@908:     for (int i = 0; in_range_for(params, i); ++i) {
Chris@60: 
Chris@207:         QString identifier = params[i].identifier.c_str();
Chris@60:         QString name = params[i].name.c_str();
Chris@60:         QString unit = params[i].unit.c_str();
Chris@60: 
Chris@60:         float min = params[i].minValue;
Chris@60:         float max = params[i].maxValue;
Chris@60:         float deft = params[i].defaultValue;
Chris@207:         float value = m_plugin->getParameter(params[i].identifier);
Chris@60: 
Chris@342:         int hint = PortHint::NoHint;
Chris@342:         RealTimePluginInstance *rtpi = dynamic_cast<RealTimePluginInstance *>
Chris@342:             (m_plugin);
Chris@342:         if (rtpi) {
Chris@342:             hint = rtpi->getParameterDisplayHint(i);
Chris@342:         }
Chris@342: 
Chris@60:         float qtz = 0.0;
Chris@60:         if (params[i].isQuantized) qtz = params[i].quantizeStep;
Chris@60: 
Chris@682: //        cerr << "PluginParameterBox: hint = " << hint << ", min = " << min << ", max = "
Chris@682: //                  << max << ", qtz = " << qtz << endl;
Chris@342: 
Chris@74:         std::vector<std::string> valueNames = params[i].valueNames;
Chris@74: 
Chris@60:         // construct an integer range
Chris@60: 
Chris@60:         int imin = 0, imax = 100;
Chris@60: 
Chris@342:         if (!(hint & PortHint::Logarithmic)) {
Chris@342:             if (qtz > 0.0) {
Chris@908:                 imax = int(lrintf((max - min) / qtz));
Chris@1348:                 if (imax <= imin) {
Chris@1348:                     imax = 100;
Chris@1348:                     qtz = (max - min) / 100.f;
Chris@1348:                 }
Chris@342:             } else {
Chris@908:                 qtz = (max - min) / 100.f;
Chris@342:             }
Chris@60:         }
Chris@60: 
Chris@60:         //!!! would be nice to ensure the default value corresponds to
Chris@60:         // an integer!
Chris@60: 
Chris@207:         QLabel *label = new QLabel(name);
Chris@208:         if (params[i].description != "") {
Chris@828:             label->setToolTip(QString("<qt>%1</qt>")
Chris@828:                               .arg(params[i].description.c_str())
Chris@828:                               .replace("\n", "<br>"));
Chris@208:         }
Chris@63:         m_layout->addWidget(label, i + offset, 0);
Chris@60: 
Chris@60:         ParamRec rec;
Chris@60:         rec.param = params[i];
Chris@63:         rec.dial = 0;
Chris@63:         rec.spin = 0;
Chris@63:         rec.check = 0;
Chris@74:         rec.combo = 0;
Chris@63:         
Chris@74:         if (params[i].isQuantized && !valueNames.empty()) {
Chris@74:             
Chris@74:             QComboBox *combobox = new QComboBox;
Chris@207:             combobox->setObjectName(identifier);
Chris@74:             for (unsigned int j = 0; j < valueNames.size(); ++j) {
Chris@74:                 combobox->addItem(valueNames[j].c_str());
Chris@249:                 if ((unsigned int)(lrintf(fabsf((value - min) / qtz))) == j) {
Chris@74:                     combobox->setCurrentIndex(j);
Chris@74:                 }
Chris@74:             }
Chris@74:             connect(combobox, SIGNAL(activated(int)),
Chris@74:                     this, SLOT(dialChanged(int)));
Chris@74:             m_layout->addWidget(combobox, i + offset, 1, 1, 2);
Chris@74:             rec.combo = combobox;
Chris@74: 
Chris@74:         } else if (min == 0.0 && max == 1.0 && qtz == 1.0) {
Chris@63:             
Chris@63:             QCheckBox *checkbox = new QCheckBox;
Chris@207:             checkbox->setObjectName(identifier);
Chris@293:             checkbox->setCheckState(value < 0.5 ? Qt::Unchecked : Qt::Checked);
Chris@63:             connect(checkbox, SIGNAL(stateChanged(int)),
Chris@63:                     this, SLOT(checkBoxChanged(int)));
Chris@63:             m_layout->addWidget(checkbox, i + offset, 2);
Chris@63:             rec.check = checkbox;
Chris@63: 
Chris@63:         } else {
Chris@63:             
Chris@63:             AudioDial *dial = new AudioDial;
Chris@207:             dial->setObjectName(name);
Chris@63:             dial->setMinimum(imin);
Chris@63:             dial->setMaximum(imax);
Chris@63:             dial->setPageStep(1);
Chris@63:             dial->setNotchesVisible((imax - imin) <= 12);
Chris@342: //!!!            dial->setDefaultValue(lrintf((deft - min) / qtz));
Chris@342: //            dial->setValue(lrintf((value - min) / qtz));
Chris@63:             dial->setFixedWidth(32);
Chris@63:             dial->setFixedHeight(32);
Chris@1348:             if (max == min || imax == imin) {
Chris@1348:                 SVCERR << "WARNING: for parameter \"" << name
Chris@1348:                        << "\" of plugin \"" << m_plugin->getName()
Chris@1348:                        << "\": invalid range " << min << " -> " << max
Chris@1348:                        << " with quantize step " << qtz << endl;
Chris@342:             } else {
Chris@1348:                 RangeMapper *rm = 0;
Chris@1348:                 if (hint & PortHint::Logarithmic) {
Chris@1348:                     rm = new LogRangeMapper(imin, imax, min, max, unit);
Chris@1348:                 } else {
Chris@1348:                     rm = new LinearRangeMapper(imin, imax, min, max, unit);
Chris@1348:                 }
Chris@1348:                 dial->setRangeMapper(rm);
Chris@1348:                 dial->setDefaultValue(rm->getPositionForValue(deft));
Chris@1348:                 dial->setValue(rm->getPositionForValue(value));
Chris@342:             }
Chris@168:             dial->setShowToolTip(true);
Chris@63:             connect(dial, SIGNAL(valueChanged(int)),
Chris@63:                     this, SLOT(dialChanged(int)));
Chris@63:             m_layout->addWidget(dial, i + offset, 1);
Chris@63: 
Chris@63:             QDoubleSpinBox *spinbox = new QDoubleSpinBox;
Chris@207:             spinbox->setObjectName(identifier);
Chris@63:             spinbox->setMinimum(min);
Chris@63:             spinbox->setMaximum(max);
Chris@63:             spinbox->setSuffix(QString(" %1").arg(unit));
Chris@342:             if (qtz != 0) spinbox->setSingleStep(qtz);
Chris@63:             spinbox->setValue(value);
Chris@103:             spinbox->setDecimals(4);
Chris@63:             connect(spinbox, SIGNAL(valueChanged(double)),
Chris@63:                     this, SLOT(spinBoxChanged(double)));
Chris@63:             m_layout->addWidget(spinbox, i + offset, 2);
Chris@63:             rec.dial = dial;
Chris@63:             rec.spin = spinbox;
Chris@63:         }
Chris@63: 
Chris@207:         m_params[identifier] = rec;
Chris@207:         m_nameMap[name] = identifier;
Chris@60:     }
Chris@60: }
Chris@60: 
Chris@60: void
Chris@60: PluginParameterBox::dialChanged(int ival)
Chris@60: {
Chris@60:     QObject *obj = sender();
Chris@207:     QString identifier = obj->objectName();
Chris@60: 
Chris@207:     if (m_params.find(identifier) == m_params.end() &&
Chris@207:         m_nameMap.find(identifier) != m_nameMap.end()) {
Chris@207:         identifier = m_nameMap[identifier];
Chris@167:     }
Chris@167: 
Chris@207:     if (m_params.find(identifier) == m_params.end()) {
Chris@682:         cerr << "WARNING: PluginParameterBox::dialChanged: Unknown parameter \"" << identifier << "\"" << endl;
Chris@60:         return;
Chris@60:     }
Chris@60: 
Chris@207:     Vamp::PluginBase::ParameterDescriptor params = m_params[identifier].param;
Chris@60: 
Chris@60:     float min = params.minValue;
Chris@60:     float max = params.maxValue;
Chris@60: 
Chris@168:     float newValue;
Chris@168: 
Chris@60:     float qtz = 0.0;
Chris@60:     if (params.isQuantized) qtz = params.quantizeStep;
Chris@168: 
Chris@168:     AudioDial *ad = dynamic_cast<AudioDial *>(obj);
Chris@60:     
Chris@168:     if (ad && ad->rangeMapper()) {
Chris@168:         
Chris@908:         newValue = float(ad->mappedValue());
Chris@168:         if (newValue < min) newValue = min;
Chris@168:         if (newValue > max) newValue = max;
Chris@168:         if (qtz != 0.0) {
Chris@908:             ival = int(lrintf((newValue - min) / qtz));
Chris@908:             newValue = min + float(ival) * qtz;
Chris@168:         }
Chris@168: 
Chris@168:     } else {
Chris@908:         if (qtz == 0.f) {
Chris@908:             qtz = (max - min) / 100.f;
Chris@168:         }
Chris@908:         newValue = min + float(ival) * qtz;
Chris@60:     }
Chris@60: 
Chris@587: //    SVDEBUG << "PluginParameterBox::dialChanged: newValue = " << newValue << endl;
Chris@342: 
Chris@207:     QDoubleSpinBox *spin = m_params[identifier].spin;
Chris@63:     if (spin) {
Chris@63:         spin->blockSignals(true);
Chris@63:         spin->setValue(newValue);
Chris@63:         spin->blockSignals(false);
Chris@63:     }
Chris@60: 
Chris@587: //    SVDEBUG << "setting plugin parameter \"" << identifier << "\" to value " << newValue << endl;
Chris@530: 
Chris@207:     m_plugin->setParameter(identifier.toStdString(), newValue);
Chris@64: 
Chris@293:     updateProgramCombo();
Chris@293: 
Chris@71:     emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString());
Chris@60: }
Chris@60: 
Chris@60: void
Chris@63: PluginParameterBox::checkBoxChanged(int state)
Chris@63: {
Chris@63:     QObject *obj = sender();
Chris@207:     QString identifier = obj->objectName();
Chris@63: 
Chris@207:     if (m_params.find(identifier) == m_params.end() &&
Chris@207:         m_nameMap.find(identifier) != m_nameMap.end()) {
Chris@207:         identifier = m_nameMap[identifier];
Chris@167:     }
Chris@167: 
Chris@207:     if (m_params.find(identifier) == m_params.end()) {
Chris@682:         cerr << "WARNING: PluginParameterBox::checkBoxChanged: Unknown parameter \"" << identifier << "\"" << endl;
Chris@63:         return;
Chris@63:     }
Chris@63: 
Chris@207:     Vamp::PluginBase::ParameterDescriptor params = m_params[identifier].param;
Chris@63: 
Chris@207:     if (state) m_plugin->setParameter(identifier.toStdString(), 1.0);
Chris@207:     else m_plugin->setParameter(identifier.toStdString(), 0.0);
Chris@64: 
Chris@293:     updateProgramCombo();
Chris@293: 
Chris@71:     emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString());
Chris@63: }
Chris@63: 
Chris@63: void
Chris@60: PluginParameterBox::spinBoxChanged(double value)
Chris@60: {
Chris@60:     QObject *obj = sender();
Chris@207:     QString identifier = obj->objectName();
Chris@60: 
Chris@207:     if (m_params.find(identifier) == m_params.end() &&
Chris@207:         m_nameMap.find(identifier) != m_nameMap.end()) {
Chris@207:         identifier = m_nameMap[identifier];
Chris@167:     }
Chris@167: 
Chris@207:     if (m_params.find(identifier) == m_params.end()) {
Chris@682:         cerr << "WARNING: PluginParameterBox::spinBoxChanged: Unknown parameter \"" << identifier << "\"" << endl;
Chris@60:         return;
Chris@60:     }
Chris@60: 
Chris@207:     Vamp::PluginBase::ParameterDescriptor params = m_params[identifier].param;
Chris@60: 
Chris@60:     float min = params.minValue;
Chris@60:     float max = params.maxValue;
Chris@60: 
Chris@60:     float qtz = 0.0;
Chris@60:     if (params.isQuantized) qtz = params.quantizeStep;
Chris@60:     
Chris@60:     if (qtz > 0.0) {
Chris@908:         int step = int(lrintf(float(value - min) / qtz));
Chris@908:         value = min + float(step) * qtz;
Chris@60:     }
Chris@60: 
Chris@807: //    int imax = 100;
Chris@60:     
Chris@60:     if (qtz > 0.0) {
Chris@807: //        imax = lrintf((max - min) / qtz);
Chris@60:     } else {
Chris@908:         qtz = (max - min) / 100.f;
Chris@60:     }
Chris@60: 
Chris@908:     int ival = int(lrintf(float(value - min) / qtz));
Chris@60: 
Chris@207:     AudioDial *dial = m_params[identifier].dial;
Chris@63:     if (dial) {
Chris@63:         dial->blockSignals(true);
Chris@342:         if (dial->rangeMapper()) {
Chris@342:             dial->setMappedValue(value);
Chris@342:         } else {
Chris@342:             dial->setValue(ival);
Chris@342:         }
Chris@63:         dial->blockSignals(false);
Chris@63:     }
Chris@60: 
Chris@587:     SVDEBUG << "setting plugin parameter \"" << identifier << "\" to value " << value << endl;
Chris@530: 
Chris@908:     m_plugin->setParameter(identifier.toStdString(), float(value));
Chris@64: 
Chris@293:     updateProgramCombo();
Chris@293: 
Chris@71:     emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString());
Chris@60: }
Chris@60: 
Chris@63: void
Chris@63: PluginParameterBox::programComboChanged(const QString &newProgram)
Chris@63: {
Chris@63:     m_plugin->selectProgram(newProgram.toStdString());
Chris@60: 
Chris@63:     for (std::map<QString, ParamRec>::iterator i = m_params.begin();
Chris@63:          i != m_params.end(); ++i) {
Chris@63: 
Chris@71:         Vamp::PluginBase::ParameterDescriptor &param = i->second.param;
Chris@207:         float value = m_plugin->getParameter(param.identifier);
Chris@63: 
Chris@63:         if (i->second.spin) {
Chris@63:             i->second.spin->blockSignals(true);
Chris@63:             i->second.spin->setValue(value);
Chris@63:             i->second.spin->blockSignals(false);
Chris@63:         }
Chris@63: 
Chris@63:         if (i->second.dial) {
Chris@63: 
Chris@63:             float min = param.minValue;
Chris@63:             float max = param.maxValue;
Chris@63: 
Chris@63:             float qtz = 0.0;
Chris@63:             if (param.isQuantized) qtz = param.quantizeStep;
Chris@63: 
Chris@63:             if (qtz == 0.0) {
Chris@908:                 qtz = (max - min) / 100.f;
Chris@63:             }
Chris@63: 
Chris@63:             i->second.dial->blockSignals(true);
Chris@908:             i->second.dial->setValue(int(lrintf(float(value - min) / qtz)));
Chris@63:             i->second.dial->blockSignals(false);
Chris@63:         }
Chris@74: 
Chris@74:         if (i->second.combo) {
Chris@74:             i->second.combo->blockSignals(true);
Chris@908:             i->second.combo->setCurrentIndex(int(lrintf(value)));
Chris@74:             i->second.combo->blockSignals(false);
Chris@74:         }
Chris@293: 
Chris@293:         if (i->second.check) {
Chris@293:             i->second.check->blockSignals(true);
Chris@293:             i->second.check->setCheckState(value < 0.5 ? Qt::Unchecked : Qt::Checked);
Chris@293:             i->second.check->blockSignals(false);
Chris@293:         }            
Chris@63:     }
Chris@64: 
Chris@71:     emit pluginConfigurationChanged(PluginXml(m_plugin).toXmlString());
Chris@63: }
Chris@63: 
Chris@293: void
Chris@293: PluginParameterBox::updateProgramCombo()
Chris@293: {
Chris@293:     if (!m_programCombo || m_programs.empty()) return;
Chris@293: 
Chris@293:     std::string currentProgram = m_plugin->getCurrentProgram();
Chris@293: 
Chris@908:     for (int i = 0; in_range_for(m_programs, i); ++i) {
Chris@293:         if (m_programs[i] == currentProgram) {
Chris@293:             m_programCombo->setCurrentIndex(i);
Chris@293:         }
Chris@293:     }
Chris@293: }
Chris@293: 
Chris@293: