Skip to content

Commit

Permalink
provide short parameter names (support for control surfaces)
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiapoirier committed Aug 22, 2020
1 parent 53e9b0f commit 78d33e1
Show file tree
Hide file tree
Showing 24 changed files with 479 additions and 237 deletions.
54 changes: 28 additions & 26 deletions bufferoverride/bufferoverrideformalities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ To contact the author, use the contact form at http:https://destroyfx.org/

#include <cmath>

#include "dfxmisc.h"


#pragma mark _________init_________

Expand All @@ -37,42 +39,42 @@ BufferOverride::BufferOverride(TARGET_API_BASE_INSTANCE_TYPE inInstance)
{
auto const numTempoRates = mTempoRateTable.getNumRates();
auto const unitTempoRateIndex = mTempoRateTable.getNearestTempoRateIndex(1.0f);
initparameter_f(kDivisor, "buffer divisor", 1.92, 1.92, 1.92, 222.0, DfxParam::Unit::Divisor, DfxParam::Curve::Squared);
initparameter_f(kBufferSize_MS, "forced buffer size (free)", 90.0, 33.3, 1.0, 999.0, DfxParam::Unit::MS, DfxParam::Curve::Squared);
initparameter_list(kBufferSize_Sync, "forced buffer size (sync)", unitTempoRateIndex, unitTempoRateIndex, numTempoRates, DfxParam::Unit::Beats);
initparameter_b(kBufferTempoSync, "forced buffer tempo sync", false, false);
initparameter_b(kBufferInterrupt, "buffer interrupt", true, true);
initparameter_f(kDivisorLFORate_Hz, "divisor LFO rate (free)", 0.3, 3.0, 0.03, 21.0, DfxParam::Unit::Hz, DfxParam::Curve::Squared);
initparameter_list(kDivisorLFORate_Sync, "divisor LFO rate (sync)", unitTempoRateIndex, unitTempoRateIndex, numTempoRates, DfxParam::Unit::Beats);
initparameter_f(kDivisorLFODepth, "divisor LFO depth", 0.0, 0.0, 0.0, 100.0, DfxParam::Unit::Percent);
initparameter_list(kDivisorLFOShape, "divisor LFO shape", 0, 0, dfx::LFO::kNumShapes);
initparameter_b(kDivisorLFOTempoSync, "divisor LFO tempo sync", false, false);
initparameter_f(kBufferLFORate_Hz, "buffer LFO rate (free)", 3.0, 3.0, 0.03, 21.0, DfxParam::Unit::Hz, DfxParam::Curve::Exp);//DfxParam::Curve::Squared);
initparameter_list(kBufferLFORate_Sync, "buffer LFO rate (sync)", unitTempoRateIndex, unitTempoRateIndex, numTempoRates, DfxParam::Unit::Beats);
initparameter_f(kBufferLFODepth, "buffer LFO depth", 0.0, 0.0, 0.0, 100.0, DfxParam::Unit::Percent);
initparameter_list(kBufferLFOShape, "buffer LFO shape", 0, 0, dfx::LFO::kNumShapes);
initparameter_b(kBufferLFOTempoSync, "buffer LFO tempo sync", false, false);
initparameter_f(kSmooth, "smooth", 9.0, 3.0, 0.0, 100.0, DfxParam::Unit::Percent);
initparameter_f(kDryWetMix, "dry/wet mix", 100.0, 50.0, 0.0, 100.0, DfxParam::Unit::DryWetMix);
initparameter_f(kPitchbend, "pitch bend range", 6.0, 3.0, 0.0, DfxMidi::kPitchBendSemitonesMax, DfxParam::Unit::Semitones);
initparameter_list(kMidiMode, "MIDI mode", kMidiMode_Nudge, kMidiMode_Nudge, kNumMidiModes);
initparameter_f(kTempo, "tempo", 120.0, 120.0, 57.0, 480.0, DfxParam::Unit::BPM);
initparameter_b(kTempoAuto, "sync to host tempo", true, true);
initparameter_f(kDivisor, {"buffer divisor", "BufDiv", "BfDv"}, 1.92, 1.92, 1.92, 222.0, DfxParam::Unit::Divisor, DfxParam::Curve::Squared);
initparameter_f(kBufferSize_MS, {"forced buffer size (free)", "BufSizF", "BufSzF", "BfSF"}, 90.0, 33.3, 1.0, 999.0, DfxParam::Unit::MS, DfxParam::Curve::Squared);
initparameter_list(kBufferSize_Sync, {"forced buffer size (sync)", "BufSizS", "BufSzS", "BfSS"}, unitTempoRateIndex, unitTempoRateIndex, numTempoRates, DfxParam::Unit::Beats);
initparameter_b(kBufferTempoSync, {"forced buffer tempo sync", "BufSync", "BufSnc", "BfSc"}, false, false);
initparameter_b(kBufferInterrupt, {"buffer interrupt", "BufRupt", "BufInt", "BfIn"}, true, true);
initparameter_f(kDivisorLFORate_Hz, {"divisor LFO rate (free)", "DvLFORF", "DLFORF", "DLRF"}, 0.3, 3.0, 0.03, 21.0, DfxParam::Unit::Hz, DfxParam::Curve::Squared);
initparameter_list(kDivisorLFORate_Sync, {"divisor LFO rate (sync)", "DvLFORS", "DLFORS", "DLRS"}, unitTempoRateIndex, unitTempoRateIndex, numTempoRates, DfxParam::Unit::Beats);
initparameter_f(kDivisorLFODepth, {"divisor LFO depth", "DvLFODp", "DvLFOD", "DvLD"}, 0.0, 0.0, 0.0, 100.0, DfxParam::Unit::Percent);
initparameter_list(kDivisorLFOShape, {"divisor LFO shape", "DvLFOSh", "DLFOSh", "DLSh"}, 0, 0, dfx::LFO::kNumShapes);
initparameter_b(kDivisorLFOTempoSync, {"divisor LFO tempo sync", "DvLFOSc", "DLFOSc", "DLSc"}, false, false);
initparameter_f(kBufferLFORate_Hz, {"buffer LFO rate (free)", "BfLFORF", "BLFORF", "BLRF"}, 3.0, 3.0, 0.03, 21.0, DfxParam::Unit::Hz, DfxParam::Curve::Exp);//DfxParam::Curve::Squared);
initparameter_list(kBufferLFORate_Sync, {"buffer LFO rate (sync)", "BfLFORS", "BLFORS", "BLRS"}, unitTempoRateIndex, unitTempoRateIndex, numTempoRates, DfxParam::Unit::Beats);
initparameter_f(kBufferLFODepth, {"buffer LFO depth", "BfLFODp", "BfLFOD", "BfLD"}, 0.0, 0.0, 0.0, 100.0, DfxParam::Unit::Percent);
initparameter_list(kBufferLFOShape, {"buffer LFO shape", "BfLFOSh", "BLFOSh", "BLSh"}, 0, 0, dfx::LFO::kNumShapes);
initparameter_b(kBufferLFOTempoSync, {"buffer LFO tempo sync", "BfLFOSc", "BLFOSc", "BLSc"}, false, false);
initparameter_f(kSmooth, {"smooth", "Smth"}, 9.0, 3.0, 0.0, 100.0, DfxParam::Unit::Percent);
initparameter_f(kDryWetMix, dfx::MakeParameterNames(dfx::kParameterNames_DryWetMix), 100.0, 50.0, 0.0, 100.0, DfxParam::Unit::DryWetMix);
initparameter_f(kPitchbend, dfx::MakeParameterNames(dfx::kParameterNames_PitchBendRange), 6.0, 3.0, 0.0, DfxMidi::kPitchBendSemitonesMax, DfxParam::Unit::Semitones);
initparameter_list(kMidiMode, dfx::MakeParameterNames(dfx::kParameterNames_MidiMode), kMidiMode_Nudge, kMidiMode_Nudge, kNumMidiModes);
initparameter_f(kTempo, dfx::MakeParameterNames(dfx::kParameterNames_Tempo), 120.0, 120.0, 57.0, 480.0, DfxParam::Unit::BPM);
initparameter_b(kTempoAuto, dfx::MakeParameterNames(dfx::kParameterNames_TempoAuto), true, true);

// set the value strings for the LFO shape parameters
for (dfx::LFO::Shape i = 0; i < dfx::LFO::kNumShapes; i++)
{
auto const shapeName = dfx::LFO::getShapeName(i);
setparametervaluestring(kDivisorLFOShape, i, shapeName.c_str());
setparametervaluestring(kBufferLFOShape, i, shapeName.c_str());
setparametervaluestring(kDivisorLFOShape, i, shapeName);
setparametervaluestring(kBufferLFOShape, i, shapeName);
}
// set the value strings for the sync rate parameters
for (int i = 0; i < mTempoRateTable.getNumRates(); i++)
{
auto const& tempoRateName = mTempoRateTable.getDisplay(i);
setparametervaluestring(kBufferSize_Sync, i, tempoRateName.c_str());
setparametervaluestring(kDivisorLFORate_Sync, i, tempoRateName.c_str());
setparametervaluestring(kBufferLFORate_Sync, i, tempoRateName.c_str());
setparametervaluestring(kBufferSize_Sync, i, tempoRateName);
setparametervaluestring(kDivisorLFORate_Sync, i, tempoRateName);
setparametervaluestring(kBufferLFORate_Sync, i, tempoRateName);
}
// set the value strings for the MIDI mode parameter
setparametervaluestring(kMidiMode, kMidiMode_Nudge, "nudge");
Expand Down
21 changes: 21 additions & 0 deletions dfx-library/dfxmisc.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ These are some generally useful functions.
#pragma once


#include <array>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

#ifdef __MACH__
#include <CoreFoundation/CoreFoundation.h>
Expand Down Expand Up @@ -117,5 +119,24 @@ std::string GetNameForMIDINote(long inMidiNote);
std::unique_ptr<char[]> CreateCStringFromCFString(CFStringRef inCFString, CFStringEncoding inCStringEncoding = kCFStringEncodingUTF8);
#endif

// coming up with these sets of short parameter names can be annoying, so here are some sets we use repeatedly
// TODO: C++20 just use std::vector (and eliminate MakeParameterNames) since it gains constexpr constructors
static constexpr std::array<std::string_view, 2> kParameterNames_Tempo = {"tempo", "Tmpo"};
static constexpr std::array<std::string_view, 4> kParameterNames_TempoSync = {"tempo sync", "TmpoSnc", "TpoSnc", "Sync"};
static constexpr std::array<std::string_view, 4> kParameterNames_TempoAuto = {"sync to host tempo", "HstTmpo", "HstTmp", "HTmp"};
static constexpr std::array<std::string_view, 3> kParameterNames_DryWetMix = {"dry/wet mix", "DryWet", "DrWt"};
static constexpr std::array<std::string_view, 2> kParameterNames_Floor = {"floor", "Flor"};
static constexpr std::array<std::string_view, 2> kParameterNames_Freeze = {"freeze", "Frez"};
static constexpr std::array<std::string_view, 2> kParameterNames_Attack = {"attack", "Attk"};
static constexpr std::array<std::string_view, 3> kParameterNames_Release = {"release", "Releas", "Rles"};
static constexpr std::array<std::string_view, 4> kParameterNames_VelocityInfluence = {"velocity influence", "VelInfl", "VelInf", "Velo"};
static constexpr std::array<std::string_view, 4> kParameterNames_PitchBendRange = {"pitch bend range", "PtchBnd", "PtchBd", "PB"};
static constexpr std::array<std::string_view, 4> kParameterNames_MidiMode = {"MIDI mode", "MIDIMod", "MIDIMd", "MIDI"};
// convenience function to produce the required initparameter_* argument from the above compile-time representation
template <auto Size>
auto MakeParameterNames(std::array<std::string_view, Size> const& inNamesArray)
{
return std::vector(inNamesArray.cbegin(), inNamesArray.cend());
}

} // namespace
93 changes: 81 additions & 12 deletions dfx-library/dfxparameter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ This is our class for doing all kinds of fancy plugin parameter stuff.
#include <algorithm>
#include <cassert>
#include <cmath>
#include <limits>
#include <string.h> // for strcpy
#include <unordered_set>

#include "dfxmath.h"

Expand All @@ -52,23 +54,20 @@ static dfx::UniqueCFType<CFStringRef> CreateCFStringWithStringView(std::string_v
#pragma mark -

//-----------------------------------------------------------------------------
void DfxParam::init(std::string_view inName, ValueType inType,
void DfxParam::init(std::vector<std::string_view> const& inNames, ValueType inType,
Value inInitialValue, Value inDefaultValue,
Value inMinValue, Value inMaxValue,
Unit inUnit, Curve inCurve)
{
assert(!inName.empty());
assert(mName.empty()); // shortcut test ensuring init is only called once

// accept all of the incoming init values
initNames(inNames);
mValueType = inType;
mValue = mOldValue = inInitialValue;
mDefaultValue = inDefaultValue;
mMinValue = inMinValue;
mMaxValue = inMaxValue;
mName.assign(inName, 0, dfx::kParameterNameMaxLength - 1);
#ifdef TARGET_API_AUDIOUNIT
mCFName = CreateCFStringWithStringView(inName);
#endif
mCurve = inCurve;
mUnit = inUnit;
if (mUnit == Unit::List)
Expand Down Expand Up @@ -122,7 +121,8 @@ void DfxParam::init(std::string_view inName, ValueType inType,

//-----------------------------------------------------------------------------
// convenience wrapper of init() for initializing with float variable type
void DfxParam::init_f(std::string_view inName, double inInitialValue, double inDefaultValue,
void DfxParam::init_f(std::vector<std::string_view> const& inNames,
double inInitialValue, double inDefaultValue,
double inMinValue, double inMaxValue,
Unit inUnit, Curve inCurve)
{
Expand All @@ -131,11 +131,12 @@ void DfxParam::init_f(std::string_view inName, double inInitialValue, double inD
def.f = inDefaultValue;
min.f = inMinValue;
max.f = inMaxValue;
init(inName, ValueType::Float, val, def, min, max, inUnit, inCurve);
init(inNames, ValueType::Float, val, def, min, max, inUnit, inCurve);
}
//-----------------------------------------------------------------------------
// convenience wrapper of init() for initializing with int variable type
void DfxParam::init_i(std::string_view inName, int64_t inInitialValue, int64_t inDefaultValue,
void DfxParam::init_i(std::vector<std::string_view> const& inNames,
int64_t inInitialValue, int64_t inDefaultValue,
int64_t inMinValue, int64_t inMaxValue,
Unit inUnit, Curve inCurve)
{
Expand All @@ -144,18 +145,51 @@ void DfxParam::init_i(std::string_view inName, int64_t inInitialValue, int64_t i
def.i = inDefaultValue;
min.i = inMinValue;
max.i = inMaxValue;
init(inName, ValueType::Int, val, def, min, max, inUnit, inCurve);
init(inNames, ValueType::Int, val, def, min, max, inUnit, inCurve);
}
//-----------------------------------------------------------------------------
// convenience wrapper of init() for initializing with boolean variable type
void DfxParam::init_b(std::string_view inName, bool inInitialValue, bool inDefaultValue, Unit inUnit)
void DfxParam::init_b(std::vector<std::string_view> const& inNames,
bool inInitialValue, bool inDefaultValue, Unit inUnit)
{
Value val {}, def {}, min {}, max {};
val.b = inInitialValue;
def.b = inDefaultValue;
min.b = false;
max.b = true;
init(inName, ValueType::Boolean, val, def, min, max, inUnit, Curve::Linear);
init(inNames, ValueType::Boolean, val, def, min, max, inUnit, Curve::Linear);
}

//-----------------------------------------------------------------------------
void DfxParam::initNames(std::vector<std::string_view> const& inNames)
{
auto const stringLength = [](auto const& string)
{
return string.length();
};

assert(!inNames.empty());
assert(std::all_of(inNames.cbegin(), inNames.cend(), [](auto const& name){ return !name.empty(); }));
{
std::vector<size_t> lengths;
std::transform(inNames.cbegin(), inNames.cend(), std::back_inserter(lengths), stringLength);
assert(std::unordered_set<size_t>(lengths.cbegin(), lengths.cend()).size() == lengths.size());
}

// sort the names by length (ascending)
mShortNames.assign(inNames.cbegin(), inNames.cend());
std::sort(mShortNames.begin(), mShortNames.end(), [stringLength](auto const& a, auto const& b)
{
return stringLength(a) < stringLength(b);
});

mName.assign(mShortNames.back(), 0, dfx::kParameterNameMaxLength - 1);
assert(mName == mShortNames.back()); // if not, your parameter name is too long!
#ifdef TARGET_API_AUDIOUNIT
mCFName = CreateCFStringWithStringView(mShortNames.back());
#endif
// done pulling out the full name, so remove it now from the list of short names
mShortNames.pop_back();
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -729,6 +763,41 @@ bool DfxParam::setchanged(bool inChanged) noexcept
#pragma mark info
#pragma mark -

//-----------------------------------------------------------------------------
std::string DfxParam::getname(size_t inMaxLength) const
{
if (mName.length() <= inMaxLength)
{
return mName;
}
if (mShortNames.empty())
{
return {mName, 0, inMaxLength};
}

// we want the nearest length match that is equal to or less than the requested maximum
auto const bestMatch = std::min_element(mShortNames.cbegin(), mShortNames.cend(), [inMaxLength](auto const& a, auto const& b)
{
auto const reduce = [inMaxLength](auto const& string)
{
return (string.length() <= inMaxLength) ? (inMaxLength - string.length()) : std::numeric_limits<size_t>::max();
};
return reduce(a) < reduce(b);
});
assert(bestMatch != mShortNames.cend());
// but if nothing was short enough to fully qualify, truncate the shortest of what we have
if (bestMatch->length() > inMaxLength)
{
auto const shortest = std::min_element(mShortNames.cbegin(), mShortNames.cend(), [](auto const& a, auto const& b)
{
return a.length() < b.length();
});
assert(shortest != mShortNames.cend());
return {*shortest, 0, inMaxLength};
}
return *bestMatch;
}

//-----------------------------------------------------------------------------
// get a text string of the unit type
std::string DfxParam::getunitstring() const
Expand Down
19 changes: 15 additions & 4 deletions dfx-library/dfxparameter.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,21 +279,27 @@ class DfxParam
DfxParam() = default;

// initialize a parameter with values, value types, curve types, etc.
void init(std::string_view inName, ValueType inType,
// the longest name in the array of names is assumed to be the full name,
// and any additional are used to match for short name requests.
// recommended short lengths to support (common in control surfaces): 6, 4, 7
void init(std::vector<std::string_view> const& inNames, ValueType inType,
Value inInitialValue, Value inDefaultValue,
Value inMinValue, Value inMaxValue,
Unit inUnit = Unit::Generic,
Curve inCurve = Curve::Linear);
// the rest of these are just convenience wrappers for initializing with a certain variable type
void init_f(std::string_view inName, double inInitialValue, double inDefaultValue,
void init_f(std::vector<std::string_view> const& inNames,
double inInitialValue, double inDefaultValue,
double inMinValue, double inMaxValue,
Unit inUnit = Unit::Generic,
Curve inCurve = Curve::Linear);
void init_i(std::string_view inName, int64_t inInitialValue, int64_t inDefaultValue,
void init_i(std::vector<std::string_view> const& inNames,
int64_t inInitialValue, int64_t inDefaultValue,
int64_t inMinValue, int64_t inMaxValue,
Unit inUnit = Unit::Generic,
Curve inCurve = Curve::Stepped);
void init_b(std::string_view inName, bool inInitialValue, bool inDefaultValue,
void init_b(std::vector<std::string_view> const& inNames,
bool inInitialValue, bool inDefaultValue,
Unit inUnit = Unit::Generic);

// set/get whether or not to use an array of strings for custom value display
Expand Down Expand Up @@ -427,6 +433,8 @@ class DfxParam
{
return mName;
}
// get a copy of the parameter name up to a desired maximum number of characters
std::string getname(size_t inMaxLength) const;
#ifdef TARGET_API_AUDIOUNIT
// get a pointer to the CFString version of the parameter name
CFStringRef getcfname() const noexcept
Expand Down Expand Up @@ -511,6 +519,8 @@ class DfxParam


private:
void initNames(std::vector<std::string_view> const& inNames);

// set a Value with a value of a specific type
// (perform type conversion if the incoming variable type is not "native")
// returns whether the provided Value changed upon accepting the scalar value
Expand All @@ -528,6 +538,7 @@ class DfxParam
// when this is enabled, out of range values are "bounced" into range
bool mEnforceValueLimits = false; // default to allowing values outside of the min/max range
std::string mName;
std::vector<std::string> mShortNames;
Value mValue {}, mDefaultValue {}, mMinValue {}, mMaxValue {}, mOldValue {};
ValueType mValueType = ValueType::Float; // the variable type of the parameter values
Unit mUnit = Unit::Generic; // the unit type of the parameter
Expand Down
32 changes: 32 additions & 0 deletions dfx-library/dfxplugin-audiounit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ OSStatus DfxPlugin::GetPropertyInfo(AudioUnitPropertyID inPropertyID,
#endif
break;

case kAudioUnitProperty_ParameterIDName:
outDataSize = sizeof(AudioUnitParameterIDName);
outWritable = false;
status = noErr;
break;

case kAudioUnitProperty_AUHostIdentifier:
outDataSize = sizeof(AUHostVersionIdentifier);
outWritable = true;
Expand Down Expand Up @@ -452,6 +458,32 @@ OSStatus DfxPlugin::GetProperty(AudioUnitPropertyID inPropertyID,
}
#endif

case kAudioUnitProperty_ParameterIDName:
{
auto& parameterIDName = *static_cast<AudioUnitParameterIDName*>(outData);
assert(parameterIDName.inID == inElement); // XXX the specification seems to be redundant in this regard?
auto const parameterID = inElement;
if (inScope != kAudioUnitScope_Global)
{
status = kAudioUnitErr_InvalidScope;
}
else if (parameterIDName.inDesiredLength == kAudioUnitParameterName_Full)
{
parameterIDName.outName = getparametercfname(parameterID);
CFRetain(parameterIDName.outName);
}
else if (parameterIDName.inDesiredLength <= 0)
{
status = kAudio_ParamError;
}
else
{
auto const shortName = getparametername(parameterID, static_cast<size_t>(parameterIDName.inDesiredLength));
parameterIDName.outName = CFStringCreateWithCString(kCFAllocatorDefault, shortName.c_str(), DfxParam::kDefaultCStringEncoding);
}
break;
}

case kAudioUnitMigrateProperty_FromPlugin:
{
// VST counterpart description
Expand Down
Loading

0 comments on commit 78d33e1

Please sign in to comment.