Generic CC processing: volume, amplitude, pan, position, width, pitch

This commit is contained in:
Jean Pierre Cimalando 2020-05-05 07:39:43 +02:00
parent b0c84ad99a
commit a27182268c
5 changed files with 84 additions and 149 deletions

View file

@ -111,21 +111,17 @@ namespace Default
constexpr float volume { 0.0f };
constexpr Range<float> volumeRange { -144.0, 6.0 };
constexpr Range<float> volumeCCRange { -144.0, 48.0 };
constexpr Range<float> volumeStepRange { 0, 48.0 };
constexpr float amplitude { 100.0 };
constexpr Range<float> amplitudeRange { 0.0, 100.0 };
constexpr float pan { 0.0 };
constexpr Range<float> panRange { -100.0, 100.0 };
constexpr Range<float> panCCRange { -200.0, 200.0 };
constexpr Range<float> panStepRange { 0.0, 200.0 };
constexpr float position { 0.0 };
constexpr Range<float> positionRange { -100.0, 100.0 };
constexpr Range<float> positionCCRange { -200.0, 200.0 };
constexpr Range<float> positionStepRange { 0.0, 200.0 };
constexpr float width { 100.0 };
constexpr Range<float> widthRange { -100.0, 100.0 };
constexpr Range<float> widthCCRange { -200.0, 200.0 };
constexpr Range<float> widthStepRange { 0.0, 200.0 };
constexpr uint8_t ampKeycenter { 60 };
constexpr float ampKeytrack { 0.0 };
constexpr Range<float> ampKeytrackRange { -96, 12 };
@ -200,8 +196,7 @@ namespace Default
constexpr Range<int> transposeRange { -127, 127 };
constexpr int tune { 0 };
constexpr Range<int> tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA
constexpr Range<int> tuneCCRange { -9600, 9600 };
constexpr Range<int> tuneStepRange { 0, 9600 };
constexpr Range<float> tuneCCRange { -9600, 9600 };
constexpr Range<int> bendBoundRange { -9600, 9600 };
constexpr Range<int> bendStepRange { 1, 1200 };
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted

View file

@ -81,6 +81,16 @@ struct Opcode {
*/
std::string getDerivedName(OpcodeCategory newCategory, unsigned number = ~0u) const;
/**
* @brief Get whether the opcode categorizes as `ccN` of any kind.
* @return true if `ccN`, otherwise false
*/
bool isAnyCcN() const
{
return category == kOpcodeOnCcN || category == kOpcodeCurveCcN ||
category == kOpcodeStepCcN || category == kOpcodeSmoothCcN;
}
private:
static OpcodeCategory identifyCategory(absl::string_view name);
LEAK_DETECTOR(Opcode);

View file

@ -15,6 +15,7 @@
#include "absl/strings/str_cat.h"
#include "absl/algorithm/container.h"
#include <random>
#include <cassert>
template<class T>
bool extendIfNecessary(std::vector<T>& vec, unsigned size, unsigned defaultCapacity)
@ -36,6 +37,13 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
const Opcode opcode = rawOpcode.cleanUp(kOpcodeScopeRegion);
switch (opcode.lettersOnlyHash) {
// Helper for ccN processing
#define case_any_ccN(x) \
case hash(x "_oncc&"): \
case hash(x "_curvecc&"): \
case hash(x "_stepcc&"): \
case hash(x "_smoothcc&")
// Sound source: sample playback
case hash("sample"):
{
@ -306,141 +314,36 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
case hash("volume"): // also gain
setValueFromOpcode(opcode, volume, Default::volumeRange);
break;
case hash("volume_curvecc&"): // also gain_curvecc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
volumeCC[opcode.parameters.back()].curve = *value;
break;
case hash("volume_stepcc&"): // also gain_stepcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::volumeStepRange))
volumeCC[opcode.parameters.back()].step = *value;
break;
case hash("volume_smoothcc&"): // also gain_smoothcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
volumeCC[opcode.parameters.back()].smooth = *value;
break;
case hash("volume_oncc&"): // also gain_oncc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::volumeCCRange))
volumeCC[opcode.parameters.back()].value = *value;
case_any_ccN("volume"): // also gain
processGenericCc(opcode, Default::volumeCCRange, &volumeCC);
break;
case hash("amplitude"):
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
amplitude = normalizePercents(*value);
break;
case hash("amplitude_curvecc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
amplitudeCC[opcode.parameters.back()].curve = *value;
break;
case hash("amplitude_stepcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
amplitudeCC[opcode.parameters.back()].step = normalizePercents(*value);
break;
case hash("amplitude_smoothcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
amplitudeCC[opcode.parameters.back()].smooth = *value;
break;
case hash("amplitude_oncc&"): // also amplitude_cc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
amplitudeCC[opcode.parameters.back()].value = normalizePercents(*value);
case_any_ccN("amplitude"):
processGenericCc(opcode, Default::amplitudeRange, &amplitudeCC);
break;
case hash("pan"):
if (auto value = readOpcode(opcode.value, Default::panRange))
pan = normalizePercents(*value);
break;
case hash("pan_curvecc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
panCC[opcode.parameters.back()].curve = *value;
break;
case hash("pan_stepcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::panStepRange))
panCC[opcode.parameters.back()].step = normalizePercents(*value);
break;
case hash("pan_smoothcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
panCC[opcode.parameters.back()].smooth = *value;
break;
case hash("pan_oncc&"): // also pan_cc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::panCCRange))
panCC[opcode.parameters.back()].value = normalizePercents(*value);
case_any_ccN("pan"):
processGenericCc(opcode, Default::panCCRange, &panCC);
break;
case hash("position"):
if (auto value = readOpcode(opcode.value, Default::positionRange))
position = normalizePercents(*value);
break;
case hash("position_curvecc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
positionCC[opcode.parameters.back()].curve = *value;
break;
case hash("position_stepcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::positionStepRange))
positionCC[opcode.parameters.back()].step = normalizePercents(*value);
break;
case hash("position_smoothcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
positionCC[opcode.parameters.back()].smooth = *value;
break;
case hash("position_oncc&"): // also position_cc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::positionCCRange))
positionCC[opcode.parameters.back()].value = normalizePercents(*value);
case_any_ccN("position"):
processGenericCc(opcode, Default::positionCCRange, &positionCC);
break;
case hash("width"):
if (auto value = readOpcode(opcode.value, Default::widthRange))
width = normalizePercents(*value);
break;
case hash("width_curvecc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
widthCC[opcode.parameters.back()].curve = *value;
break;
case hash("width_stepcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::widthStepRange))
widthCC[opcode.parameters.back()].step = normalizePercents(*value);
break;
case hash("width_smoothcc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
widthCC[opcode.parameters.back()].smooth = *value;
break;
case hash("width_oncc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::widthCCRange))
widthCC[opcode.parameters.back()].value = normalizePercents(*value);
case_any_ccN("width"):
processGenericCc(opcode, Default::widthCCRange, &widthCC);
break;
case hash("amp_keycenter"):
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
@ -802,29 +705,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
case hash("pitch"): // also tune
setValueFromOpcode(opcode, tune, Default::tuneRange);
break;
case hash("pitch_curvecc&"): // also tune_curvecc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
tuneCC[opcode.parameters.back()].curve = *value;
break;
case hash("pitch_stepcc&"): // also tune_stepcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::tuneStepRange))
tuneCC[opcode.parameters.back()].step = *value;
break;
case hash("pitch_smoothcc&"): // also tune_smoothcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
tuneCC[opcode.parameters.back()].smooth = *value;
break;
case hash("pitch_oncc&"): // also pitch_cc&, tune_cc&, tune_oncc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::tuneCCRange))
tuneCC[opcode.parameters.back()].value = *value;
case_any_ccN("pitch"): // also tune
processGenericCc(opcode, Default::tuneCCRange, &tuneCC);
break;
case hash("bend_up"): // also bendup
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
@ -932,11 +814,49 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
break;
default:
return false;
#undef case_any_ccN
}
return true;
}
bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap)
{
if (!opcode.isAnyCcN())
return false;
const auto ccNumber = opcode.parameters.back();
if (ccNumber >= config::numCCs)
return false;
if (ccMap) {
Modifier& modifier = (*ccMap)[ccNumber];
switch (opcode.category) {
case kOpcodeOnCcN:
setValueFromOpcode(opcode, modifier.value, range);
break;
case kOpcodeCurveCcN:
setValueFromOpcode(opcode, modifier.curve, Default::curveCCRange);
break;
case kOpcodeStepCcN:
{
const Range<float> stepCCRange { 0.0f, std::max(std::abs(range.getStart()), std::abs(range.getEnd())) };
setValueFromOpcode(opcode, modifier.step, stepCCRange);
}
break;
case kOpcodeSmoothCcN:
setValueFromOpcode(opcode, modifier.smooth, Default::smoothCCRange);
break;
default:
assert(false);
break;
}
}
return true;
}
bool sfz::Region::isSwitchedOn() const noexcept
{
return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all();

View file

@ -213,6 +213,16 @@ struct Region {
* @return false
*/
bool parseOpcode(const Opcode& opcode);
/**
* @brief Process a generic CC opcode, and fill the modulation parameters.
*
* @param opcode
* @param range
* @param ccMap
* @return true if the opcode was properly read and stored.
* @return false
*/
bool processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap);
void offsetAllKeys(int offset) noexcept;

View file

@ -262,7 +262,7 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
// Amplitude envelope
applyGain<float>(baseGain, modulationSpan);
for (const auto& mod : region->amplitudeCC) {
linearModifier(resources, *tempSpan, mod);
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
applyGain<float>(*tempSpan, modulationSpan);
}
@ -339,7 +339,7 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
// Apply panning
fill<float>(*modulationSpan, region->pan);
for (const auto& mod : region->panCC) {
linearModifier(resources, *tempSpan, mod);
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
add<float>(*tempSpan, *modulationSpan);
}
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
@ -360,7 +360,7 @@ void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
// Apply panning
fill<float>(*modulationSpan, region->pan);
for (const auto& mod : region->panCC) {
linearModifier(resources, *tempSpan, mod);
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
add<float>(*tempSpan, *modulationSpan);
}
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
@ -368,14 +368,14 @@ void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
// Apply the width/position process
fill<float>(*modulationSpan, region->width);
for (const auto& mod : region->widthCC) {
linearModifier(resources, *tempSpan, mod);
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
add<float>(*tempSpan, *modulationSpan);
}
width<float>(*modulationSpan, leftBuffer, rightBuffer);
fill<float>(*modulationSpan, region->position);
for (const auto& mod : region->positionCC) {
linearModifier(resources, *tempSpan, mod);
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
add<float>(*tempSpan, *modulationSpan);
}
pan<float>(*modulationSpan, leftBuffer, rightBuffer);