/* Copyright (C) 2014 Paul Davis Author: David Robillard This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include "pbd/control_math.h" #include "ardour/amp.h" #include "ardour/dB.h" #include "ardour/parameter_descriptor.h" #include "ardour/rc_configuration.h" #include "ardour/types.h" #include "ardour/utils.h" #include "pbd/i18n.h" namespace ARDOUR { ParameterDescriptor::ParameterDescriptor(const Evoral::Parameter& parameter) : Evoral::ParameterDescriptor() , key((uint32_t)-1) , datatype(Variant::NOTHING) , type((AutomationType)parameter.type()) , unit(NONE) , step(0) , smallstep(0) , largestep(0) , integer_step(parameter.type() >= MidiCCAutomation && parameter.type() <= MidiChannelPressureAutomation) , sr_dependent(false) , enumeration(false) { ScalePoints sp; /* Note: defaults in Evoral::ParameterDescriptor */ switch((AutomationType)parameter.type()) { case GainAutomation: case BusSendLevel: upper = Config->get_max_gain(); normal = 1.0f; break; case BusSendEnable: normal = 1.0f; toggled = true; break; case TrimAutomation: upper = 10; // +20dB lower = .1; // -20dB normal = 1.0f; logarithmic = true; break; case PanAzimuthAutomation: normal = 0.5f; // there really is no _normal but this works for stereo, sort of upper = 1.0f; break; case PanWidthAutomation: lower = -1.0; upper = 1.0; normal = 0.0f; break; case RecEnableAutomation: case RecSafeAutomation: lower = 0.0; upper = 1.0; toggled = true; break; case FadeInAutomation: case FadeOutAutomation: case EnvelopeAutomation: upper = 2.0f; normal = 1.0f; break; case SoloAutomation: case MuteAutomation: upper = 1.0f; normal = 0.0f; toggled = true; break; case MidiCCAutomation: case MidiPgmChangeAutomation: case MidiChannelPressureAutomation: case MidiNotePressureAutomation: lower = 0.0; normal = 0.0; upper = 127.0; print_fmt = "%.0f"; break; case MidiPitchBenderAutomation: lower = 0.0; normal = 8192.0; upper = 16383.0; print_fmt = "%.0f"; break; case PhaseAutomation: toggled = true; scale_points = boost::shared_ptr(new ScalePoints()); scale_points->insert (std::make_pair (_("Normal"), 0)); scale_points->insert (std::make_pair (_("Invert"), 1)); break; case MonitoringAutomation: enumeration = true; integer_step = true; lower = MonitorAuto; upper = MonitorDisk; /* XXX bump when we add MonitorCue */ scale_points = boost::shared_ptr(new ScalePoints()); scale_points->insert (std::make_pair (_("Auto"), MonitorAuto)); scale_points->insert (std::make_pair (_("Input"), MonitorInput)); scale_points->insert (std::make_pair (_("Disk"), MonitorDisk)); break; case SoloIsolateAutomation: case SoloSafeAutomation: toggled = true; break; default: break; } update_steps(); } ParameterDescriptor::ParameterDescriptor() : Evoral::ParameterDescriptor() , key((uint32_t)-1) , datatype(Variant::NOTHING) , type(NullAutomation) , unit(NONE) , step(0) , smallstep(0) , largestep(0) , integer_step(false) , sr_dependent(false) , enumeration(false) {} void ParameterDescriptor::update_steps() { /* sanitize flags */ if (toggled || enumeration) { logarithmic = false; } if (logarithmic && sr_dependent && upper > lower && lower == 0) { /* work-around for plugins with a log-scale control 0..SR; log (0) is not defined */ lower = upper / 1000.f; } if (logarithmic && (upper <= lower || lower * upper <= 0)) { /* log-scale params need upper > lower and both values need the same sign */ logarithmic = false; } if (rangesteps < 2) { rangesteps = 0; } if (enumeration) { /* enums need scale-points. * The GUI is more restrictive, a dropdown is displayed * IIF scale_points.size() == (1 + upper - lower) */ if (!scale_points || scale_points->empty ()) { enumeration = false; } } if (integer_step) { if (lower >= upper) { integer_step = false; } } /* upper == lower does not make any sense */ if (lower == upper) { upper = lower + 0.01; // add some arbitrary value } /* set steps */ if (unit == ParameterDescriptor::MIDI_NOTE) { step = smallstep = 1; // semitone largestep = 12; // octave } else if (type == GainAutomation || type == TrimAutomation) { /* dB_coeff_step gives a step normalized for [0, max_gain]. This is like "slider position", so we convert from "slider position" to gain to have the correct unit here. */ largestep = position_to_gain (dB_coeff_step(upper)); step = position_to_gain (largestep / 10.0); smallstep = step; } else if (logarithmic) { /* ignore logscale rangesteps. {small|large}steps are used with the spinbox. * gtk-spinbox shows the internal (not interface) value and up/down * arrows linearly increase. * The AutomationController uses internal_to_interface(): * ui-step [0..1] -> log (1 + largestep / lower) / log (upper / lower) * so we use a step that's a multiple of "lower" for the interface step: * log (1 + x) / log (upper / lower) */ smallstep = step = lower / 11; largestep = lower / 3; /* NOTE: the actual value does use rangesteps via * logscale_to_position_with_steps(), position_to_logscale_with_steps() * when it is converted. */ } else if (rangesteps > 1) { const float delta = upper - lower; if (integer_step) { smallstep = step = 1.0; largestep = std::max(1.f, rintf (delta / (rangesteps - 1.f))); } else { step = smallstep = delta / (rangesteps - 1.f); largestep = std::min ((delta / 4.0f), 10.f * smallstep); } } else { const float delta = upper - lower; /* 30 steps between min/max (300 for fine-grained) */ if (integer_step) { smallstep = step = 1.0; largestep = std::max(1.f, rintf (delta / 30.f)); } else { step = smallstep = (delta / 300.0f); largestep = (delta / 30.0f); } } } std::string ParameterDescriptor::midi_note_name (const uint8_t b, bool translate) { char buf[16]; if (b > 127) { snprintf(buf, sizeof(buf), "%d", b); return buf; } static const char* en_notes[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; static const char* notes[] = { S_("Note|C"), S_("Note|C#"), S_("Note|D"), S_("Note|D#"), S_("Note|E"), S_("Note|F"), S_("Note|F#"), S_("Note|G"), S_("Note|G#"), S_("Note|A"), S_("Note|A#"), S_("Note|B") }; /* MIDI note 0 is in octave -1 (in scientific pitch notation) */ const int octave = b / 12 - 1; const size_t p = b % 12; snprintf (buf, sizeof (buf), "%s%d", translate ? notes[p] : en_notes[p], octave); return buf; } std::string ParameterDescriptor::normalize_note_name(const std::string& name) { // Remove whitespaces and convert to lower case for a more resilient parser return boost::to_lower_copy(boost::erase_all_copy(name, " ")); }; ParameterDescriptor::NameNumMap ParameterDescriptor::build_midi_name2num() { NameNumMap name2num; for (uint8_t num = 0; num < 128; num++) { name2num[normalize_note_name(midi_note_name(num))] = num; } return name2num; } uint8_t ParameterDescriptor::midi_note_num (const std::string& name) { static NameNumMap name2num = build_midi_name2num(); uint8_t num = -1; // -1 (or 255) is returned in case of failure NameNumMap::const_iterator it = name2num.find(normalize_note_name(name)); if (it != name2num.end()) num = it->second; return num; } float ParameterDescriptor::to_interface (float val) const { val = std::min (upper, std::max (lower, val)); switch(type) { case GainAutomation: case BusSendLevel: case EnvelopeAutomation: val = gain_to_slider_position_with_max (val, upper); break; case TrimAutomation: { const float lower_db = accurate_coefficient_to_dB (lower); const float range_db = accurate_coefficient_to_dB (upper) - lower_db; val = (accurate_coefficient_to_dB (val) - lower_db) / range_db; } break; case PanAzimuthAutomation: case PanElevationAutomation: val = val; break; case PanWidthAutomation: val = .5f + val * .5f; break; default: if (logarithmic) { if (rangesteps > 1) { val = logscale_to_position_with_steps (val, lower, upper, rangesteps); } else { val = logscale_to_position (val, lower, upper); } } else if (toggled) { return (val - lower) / (upper - lower) >= 0.5f ? 1.f : 0.f; } else if (integer_step) { /* evenly-divide steps. lower,upper inclusive * e.g. 5 integers 0,1,2,3,4 are mapped to a fader * [0.0 ... 0.2 | 0.2 ... 0.4 | 0.4 ... 0.6 | 0.6 ... 0.8 | 0.8 ... 1.0] * 0 1 2 3 4 * 0.1 0.3 0.5 0.7 0.9 */ val = (val + .5f - lower) / (1.f + upper - lower); } else { val = (val - lower) / (upper - lower); } break; } val = std::max (0.f, std::min (1.f, val)); return val; } float ParameterDescriptor::from_interface (float val) const { val = std::max (0.f, std::min (1.f, val)); switch(type) { case GainAutomation: case EnvelopeAutomation: case BusSendLevel: val = slider_position_to_gain_with_max (val, upper); break; case TrimAutomation: { const float lower_db = accurate_coefficient_to_dB (lower); const float range_db = accurate_coefficient_to_dB (upper) - lower_db; val = dB_to_coefficient (lower_db + val * range_db); } break; case PanAzimuthAutomation: case PanElevationAutomation: val = val; break; case PanWidthAutomation: val = 2.f * val - 1.f; break; default: if (logarithmic) { assert (!toggled && !integer_step); // update_steps() should prevent that. if (rangesteps > 1) { val = position_to_logscale_with_steps (val, lower, upper, rangesteps); } else { val = position_to_logscale (val, lower, upper); } } else if (toggled) { val = val > 0 ? upper : lower; } else if (integer_step) { /* upper and lower are inclusive. use evenly-divided steps * e.g. 5 integers 0,1,2,3,4 are mapped to a fader * [0.0 .. 0.2 | 0.2 .. 0.4 | 0.4 .. 0.6 | 0.6 .. 0.8 | 0.8 .. 1.0] */ val = floor (lower + val * (1.f + upper - lower)); } else if (rangesteps > 1) { /* similar to above, but for float controls */ val = round (val * (rangesteps - 1.f)) / (rangesteps - 1.f); // XXX val = val * (upper - lower) + lower; } else { val = val * (upper - lower) + lower; } break; } val = std::min (upper, std::max (lower, val)); return val; } bool ParameterDescriptor::is_linear () const { if (logarithmic) { return false; } switch(type) { case GainAutomation: case EnvelopeAutomation: case BusSendLevel: return false; default: break; } return true; } float ParameterDescriptor::compute_delta (float from, float to) const { if (is_linear ()) { return to - from; } if (from == 0) { return 0; } return to / from; } float ParameterDescriptor::apply_delta (float val, float delta) const { if (is_linear ()) { return val + delta; } else { return val * delta; } } float ParameterDescriptor::step_enum (float val, bool prev) const { if (!enumeration) { return val; } assert (scale_points && !scale_points->empty ()); float rv = scale_points->begin()->second; float delta = fabsf (val - rv); std::vector avail; for (ScalePoints::const_iterator i = scale_points->begin (); i != scale_points->end (); ++i) { float s = i->second; avail.push_back (s); if (fabsf (val - s) < delta) { rv = s; delta = fabsf (val - s); } } /* ScalePoints map is sorted by text string */ std::sort (avail.begin (), avail.end ()); std::vector::const_iterator it = std::find (avail.begin (), avail.end (), rv); assert (it != avail.end()); if (prev) { if (it == avail.begin()) { return rv; } return *(--it); } else { if (++it == avail.end()) { return rv; } return *(it); } } } // namespace ARDOUR