Merge pull request #213 from jpcima/cc-processing

Cc processing
This commit is contained in:
Paul Ferrand 2020-05-13 22:22:06 +02:00 committed by GitHub
commit 6ed597a0bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 164 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

@ -270,7 +270,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);
}
@ -347,7 +347,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);
@ -368,7 +368,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);
@ -376,14 +376,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);

View file

@ -351,7 +351,7 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines")
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty());
REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10));
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 0.34f);
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 34.0f);
}
TEST_CASE("[Files] Specific bug: relative path with backslashes")

View file

@ -501,7 +501,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.panCC.empty());
region.parseOpcode({ "pan_oncc45", "4.2" });
REQUIRE(region.panCC.contains(45));
REQUIRE(region.panCC[45].value == 0.042_a);
REQUIRE(region.panCC[45].value == 4.2_a);
region.parseOpcode({ "pan_curvecc17", "18" });
REQUIRE(region.panCC[17].curve == 18);
region.parseOpcode({ "pan_curvecc17", "15482" });
@ -515,9 +515,9 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "pan_smoothcc14", "-2" });
REQUIRE(region.panCC[14].smooth == 0);
region.parseOpcode({ "pan_stepcc120", "24" });
REQUIRE(region.panCC[120].step == 0.24_a);
REQUIRE(region.panCC[120].step == 24.0_a);
region.parseOpcode({ "pan_stepcc120", "15482" });
REQUIRE(region.panCC[120].step == 2.0_a);
REQUIRE(region.panCC[120].step == 200.0_a);
region.parseOpcode({ "pan_stepcc120", "-2" });
REQUIRE(region.panCC[120].step == 0.0f);
}
@ -540,7 +540,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.widthCC.empty());
region.parseOpcode({ "width_oncc45", "4.2" });
REQUIRE(region.widthCC.contains(45));
REQUIRE(region.widthCC[45].value == 0.042_a);
REQUIRE(region.widthCC[45].value == 4.2_a);
region.parseOpcode({ "width_curvecc17", "18" });
REQUIRE(region.widthCC[17].curve == 18);
region.parseOpcode({ "width_curvecc17", "15482" });
@ -554,9 +554,9 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "width_smoothcc14", "-2" });
REQUIRE(region.widthCC[14].smooth == 0);
region.parseOpcode({ "width_stepcc120", "24" });
REQUIRE(region.widthCC[120].step == 0.24_a);
REQUIRE(region.widthCC[120].step == 24.0_a);
region.parseOpcode({ "width_stepcc120", "15482" });
REQUIRE(region.widthCC[120].step == 2.0_a);
REQUIRE(region.widthCC[120].step == 200.0_a);
region.parseOpcode({ "width_stepcc120", "-20" });
REQUIRE(region.widthCC[120].step == 0.0f);
}
@ -579,7 +579,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.positionCC.empty());
region.parseOpcode({ "position_oncc45", "4.2" });
REQUIRE(region.positionCC.contains(45));
REQUIRE(region.positionCC[45].value == 0.042_a);
REQUIRE(region.positionCC[45].value == 4.2_a);
region.parseOpcode({ "position_curvecc17", "18" });
REQUIRE(region.positionCC[17].curve == 18);
region.parseOpcode({ "position_curvecc17", "15482" });
@ -593,9 +593,9 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "position_smoothcc14", "-2" });
REQUIRE(region.positionCC[14].smooth == 0);
region.parseOpcode({ "position_stepcc120", "24" });
REQUIRE(region.positionCC[120].step == 0.24_a);
REQUIRE(region.positionCC[120].step == 24.0_a);
region.parseOpcode({ "position_stepcc120", "15482" });
REQUIRE(region.positionCC[120].step == 2.0_a);
REQUIRE(region.positionCC[120].step == 200.0_a);
region.parseOpcode({ "position_stepcc120", "-2" });
REQUIRE(region.positionCC[120].step == 0.0f);
}
@ -1533,10 +1533,10 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.amplitudeCC.empty());
region.parseOpcode({ "amplitude_cc1", "40" });
REQUIRE(region.amplitudeCC.contains(1));
REQUIRE(region.amplitudeCC[1].value == 0.40_a);
REQUIRE(region.amplitudeCC[1].value == 40.0_a);
region.parseOpcode({ "amplitude_oncc2", "30" });
REQUIRE(region.amplitudeCC.contains(2));
REQUIRE(region.amplitudeCC[2].value == 0.30_a);
REQUIRE(region.amplitudeCC[2].value == 30.0_a);
region.parseOpcode({ "amplitude_curvecc17", "18" });
REQUIRE(region.amplitudeCC[17].curve == 18);
region.parseOpcode({ "amplitude_curvecc17", "15482" });
@ -1550,9 +1550,9 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "amplitude_smoothcc14", "-2" });
REQUIRE(region.amplitudeCC[14].smooth == 0);
region.parseOpcode({ "amplitude_stepcc120", "24" });
REQUIRE(region.amplitudeCC[120].step == 0.24_a);
REQUIRE(region.amplitudeCC[120].step == 24.0_a);
region.parseOpcode({ "amplitude_stepcc120", "15482" });
REQUIRE(region.amplitudeCC[120].step == 1.0_a);
REQUIRE(region.amplitudeCC[120].step == 100.0_a);
region.parseOpcode({ "amplitude_stepcc120", "-2" });
REQUIRE(region.amplitudeCC[120].step == 0.0f);
}
@ -1584,7 +1584,7 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "volume_stepcc120", "24" });
REQUIRE(region.volumeCC[120].step == 24.0f);
region.parseOpcode({ "volume_stepcc120", "15482" });
REQUIRE(region.volumeCC[120].step == 48.0f);
REQUIRE(region.volumeCC[120].step == 144.0f);
region.parseOpcode({ "volume_stepcc120", "-2" });
REQUIRE(region.volumeCC[120].step == 0.0f);
}