Implement RM synthesis

This commit is contained in:
Jean Pierre Cimalando 2020-09-22 21:49:22 +02:00
parent 5c1931de2c
commit 05b5728d88
8 changed files with 111 additions and 15 deletions

View file

@ -64,11 +64,16 @@ namespace Default
// Wavetable oscillator
constexpr float oscillatorPhase { 0.0 };
constexpr Range<float> oscillatorPhaseRange { -1.0, 360.0 };
constexpr int oscillatorMode { 0 };
constexpr int oscillatorMulti { 1 };
constexpr Range<int> oscillatorModeRange { 0, 2 };
constexpr Range<int> oscillatorMultiRange { 1, config::oscillatorsPerVoice };
constexpr float oscillatorDetune { 0 };
constexpr Range<float> oscillatorDetuneRange { -9600, 9600 };
constexpr Range<float> oscillatorDetuneCCRange { -9600, 9600 };
constexpr float oscillatorModDepth { 0 };
constexpr Range<float> oscillatorModDepthRange { 0, 100 };
constexpr Range<float> oscillatorModDepthCCRange { 0, 100 };
constexpr int oscillatorQuality { 1 };
constexpr Range<int> oscillatorQualityRange { 0, 3 };

View file

@ -147,6 +147,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
if (auto value = readBooleanFromOpcode(opcode))
oscillatorEnabled = *value ? OscillatorEnabled::On : OscillatorEnabled::Off;
break;
case hash("oscillator_mode"):
setValueFromOpcode(opcode, oscillatorMode, Default::oscillatorModeRange);
break;
case hash("oscillator_multi"):
setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange);
break;
@ -156,6 +159,13 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
case_any_ccN("oscillator_detune"):
processGenericCc(opcode, Default::oscillatorDetuneCCRange, ModKey::createNXYZ(ModId::OscillatorDetune, id));
break;
case hash("oscillator_mod_depth"):
if (auto value = readOpcode(opcode.value, Default::oscillatorModDepthRange))
oscillatorModDepth = normalizePercents(*value);
break;
case_any_ccN("oscillator_mod_depth"):
processGenericCc(opcode, Default::oscillatorModDepthCCRange, ModKey::createNXYZ(ModId::OscillatorModDepth, id));
break;
case hash("oscillator_quality"):
if (opcode.value == "-1")
oscillatorQuality.reset();

View file

@ -302,8 +302,10 @@ struct Region {
enum class OscillatorEnabled { Auto = -1, Off = 0, On = 1 };
OscillatorEnabled oscillatorEnabled = OscillatorEnabled::Auto; // oscillator
bool hasWavetableSample = false; // (set according to sample file)
int oscillatorMode = Default::oscillatorMode;
int oscillatorMulti = Default::oscillatorMulti;
float oscillatorDetune = Default::oscillatorDetune;
float oscillatorModDepth = Default::oscillatorModDepth;
absl::optional<int> oscillatorQuality;
// Instrument settings: voice lifecycle

View file

@ -661,6 +661,8 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
} else {
const auto numFrames = buffer.getNumFrames();
buffer.fill(0.0f);
auto frequencies = resources.bufferPool.getBuffer(numFrames);
if (!frequencies)
return;
@ -673,19 +675,28 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
if (!detuneSpan)
return;
if (waveUnisonSize == 1) {
WavetableOscillator& osc = waveOscillators[0];
fill(*detuneSpan, 1.0f);
osc.processModulated(frequencies->data(), detuneSpan->data(), leftSpan.data(), buffer.getNumFrames());
copy<float>(leftSpan, rightSpan);
}
else {
buffer.fill(0.0f);
const int oscillatorMode = region->oscillatorMode;
const int oscillatorMulti = region->oscillatorMulti;
if (oscillatorMode <= 0 && oscillatorMulti < 2) {
// single oscillator
auto tempSpan = resources.bufferPool.getBuffer(numFrames);
if (!tempSpan)
return;
WavetableOscillator& osc = waveOscillators[0];
fill(*detuneSpan, 1.0f);
osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames());
copy<float>(*tempSpan, leftSpan);
copy<float>(*tempSpan, rightSpan);
}
else if (oscillatorMode <= 0 && oscillatorMulti >= 3) {
// unison oscillator
auto tempSpan = resources.bufferPool.getBuffer(numFrames);
auto temp2Span = resources.bufferPool.getBuffer(numFrames);
if (!tempSpan || !temp2Span)
return;
const float* detuneMod = resources.modMatrix.getModulation(oscillatorDetuneTarget);
for (unsigned u = 0, uSize = waveUnisonSize; u < uSize; ++u) {
WavetableOscillator& osc = waveOscillators[u];
@ -697,10 +708,66 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
applyGain1(waveDetuneRatio[u], *detuneSpan);
}
osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), numFrames);
multiplyAdd1<float>(waveLeftGain[u], *tempSpan, leftSpan);
multiplyAdd1<float>(waveRightGain[u], *tempSpan, rightSpan);
multiplyAdd1<float>(waveLeftGain[u], *tempSpan, *temp2Span);
copy<float>(*temp2Span, leftSpan);
multiplyAdd1<float>(waveRightGain[u], *tempSpan, *temp2Span);
copy<float>(*temp2Span, rightSpan);
}
}
else {
// modulated oscillator
auto tempSpan = resources.bufferPool.getBuffer(numFrames);
if (!tempSpan)
return;
WavetableOscillator& oscCar = waveOscillators[0];
WavetableOscillator& oscMod = waveOscillators[1];
// compute the modulator
auto modulatorSpan = resources.bufferPool.getBuffer(numFrames);
if (!modulatorSpan)
return;
const float* detuneMod = resources.modMatrix.getModulation(oscillatorDetuneTarget);
if (!detuneMod)
fill(*detuneSpan, waveDetuneRatio[1]);
else {
for (size_t i = 0; i < numFrames; ++i)
(*detuneSpan)[i] = centsFactor(detuneMod[i]);
applyGain1(waveDetuneRatio[1], *detuneSpan);
}
oscMod.processModulated(frequencies->data(), detuneSpan->data(), modulatorSpan->data(), numFrames);
// scale the modulator
const float oscillatorModDepth = region->oscillatorModDepth;
if (oscillatorModDepth != 1.0f)
applyGain1(oscillatorModDepth, *modulatorSpan);
const float* modDepthMod = resources.modMatrix.getModulation(oscillatorModDepthTarget);
if (modDepthMod)
multiplyMul1(0.01f, absl::MakeConstSpan(modDepthMod, numFrames), *modulatorSpan);
// compute carrier×modulator
switch (region->oscillatorMode) {
case 0: // RM synthesis
default:
fill(*detuneSpan, 1.0f);
oscCar.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames());
applyGain<float>(*modulatorSpan, *tempSpan);
break;
case 1: // PM synthesis
return; // Note(jpc): not yet implemented
break;
case 2: // FM synthesis
return; // Note(jpc): not yet implemented
break;
}
copy<float>(*tempSpan, leftSpan);
copy<float>(*tempSpan, rightSpan);
}
}
#if 0
@ -828,16 +895,21 @@ void sfz::Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs)
void sfz::Voice::setupOscillatorUnison()
{
int m = region->oscillatorMulti;
float d = region->oscillatorDetune;
const int m = region->oscillatorMulti;
const float d = region->oscillatorDetune;
// 3-9: unison mode, 1: normal/RM, 2: PM/FM
// TODO(jpc) RM/FM/PM synthesis
if (m < 3) {
if (m < 3 || region->oscillatorMode > 0) {
waveUnisonSize = 1;
// carrier
waveDetuneRatio[0] = 1.0;
waveLeftGain[0] = 1.0;
waveRightGain[0] = 1.0;
// modulator
const float modDepth = region->oscillatorModDepth;
waveDetuneRatio[1] = centsFactor(d);
waveLeftGain[1] = modDepth;
waveRightGain[1] = modDepth;
return;
}
@ -856,7 +928,7 @@ void sfz::Voice::setupOscillatorUnison()
// detune (ratio)
for (int i = 0; i < m; ++i)
waveDetuneRatio[i] = std::exp2(detunes[i] * (0.01f / 12.0f));
waveDetuneRatio[i] = centsFactor(detunes[i]);
// gains
waveLeftGain[0] = 0.0;
@ -935,4 +1007,5 @@ void sfz::Voice::saveModulationTargets(const Region* region) noexcept
widthTarget = mm.findTarget(ModKey::createNXYZ(ModId::Width, region->getId()));
pitchTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pitch, region->getId()));
oscillatorDetuneTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorDetune, region->getId()));
oscillatorModDepthTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorModDepth, region->getId()));
}

View file

@ -495,6 +495,7 @@ private:
ModMatrix::TargetId widthTarget;
ModMatrix::TargetId pitchTarget;
ModMatrix::TargetId oscillatorDetuneTarget;
ModMatrix::TargetId oscillatorModDepthTarget;
PowerFollower powerFollower;

View file

@ -58,6 +58,8 @@ int ModIds::flags(ModId id) noexcept
return kModIsPerVoice|kModIsAdditive;
case ModId::OscillatorDetune:
return kModIsPerVoice|kModIsAdditive;
case ModId::OscillatorModDepth:
return kModIsPerVoice|kModIsPercentMultiplicative;
// unknown
default:

View file

@ -44,6 +44,7 @@ enum class ModId : int {
EqFrequency,
EqBandwidth,
OscillatorDetune,
OscillatorModDepth,
_TargetsEnd,
// [/targets] --------------------------------------------------------------

View file

@ -101,6 +101,8 @@ std::string ModKey::toString() const
return absl::StrCat("EqBandwidth {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::OscillatorDetune:
return absl::StrCat("OscillatorDetune {", region_.number(), ", N=", 1 + params_.N, "}");
case ModId::OscillatorModDepth:
return absl::StrCat("OscillatorModDepth {", region_.number(), ", N=", 1 + params_.N, "}");
default:
return {};