Merge pull request #436 from jpcima/oscillators-fm

Add FM synthesis with 2 operators
This commit is contained in:
JP Cimalando 2020-09-23 18:09:46 +02:00 committed by GitHub
commit 296080bb56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 133 additions and 24 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, 10000 }; // depth%, allowed to be >100 for FM
constexpr Range<float> oscillatorModDepthCCRange { 0, 10000 };
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

@ -305,8 +305,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

@ -797,7 +797,6 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
activeVoices = 0;
{ // Main render block
ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration };
tempSpan->fill(0.0f);
tempMixSpan->fill(0.0f);
resources.filePool.cleanupPromises();

View file

@ -673,19 +673,29 @@ 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 tempLeftSpan = resources.bufferPool.getBuffer(numFrames);
auto tempRightSpan = resources.bufferPool.getBuffer(numFrames);
if (!tempSpan || !tempLeftSpan || !tempRightSpan)
return;
const float* detuneMod = resources.modMatrix.getModulation(oscillatorDetuneTarget);
for (unsigned u = 0, uSize = waveUnisonSize; u < uSize; ++u) {
WavetableOscillator& osc = waveOscillators[u];
@ -697,9 +707,76 @@ 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);
if (u == 0) {
applyGain1<float>(waveLeftGain[u], *tempSpan, *tempLeftSpan);
applyGain1<float>(waveRightGain[u], *tempSpan, *tempRightSpan);
}
else {
multiplyAdd1<float>(waveLeftGain[u], *tempSpan, *tempLeftSpan);
multiplyAdd1<float>(waveRightGain[u], *tempSpan, *tempRightSpan);
}
}
copy<float>(*tempLeftSpan, leftSpan);
copy<float>(*tempRightSpan, 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
// Note(jpc): not implemented, just do FM instead
goto fm_synthesis;
break;
case 2: // FM synthesis
fm_synthesis:
fill(*detuneSpan, 1.0f);
multiplyAdd<float>(*modulatorSpan, *frequencies, *frequencies);
oscCar.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames());
break;
}
copy<float>(*tempSpan, leftSpan);
copy<float>(*tempSpan, rightSpan);
}
}
@ -828,16 +905,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 +938,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 +1017,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

@ -36,6 +36,14 @@ void WavetableOscillator::setPhase(float phase)
_phase = phase;
}
static float incrementAndWrap(float phase, float inc)
{
phase += inc;
phase -= static_cast<int>(phase);
phase += phase < 0.0f; // in case of negative frequencies
return phase;
}
template <InterpolatorModel M>
void WavetableOscillator::processSingle(float frequency, float detuneRatio, float* output, unsigned nframes)
{
@ -52,8 +60,7 @@ void WavetableOscillator::processSingle(float frequency, float detuneRatio, floa
float frac = position - index;
output[i] = interpolate<M>(&table[index], frac);
phase += phaseInc;
phase -= static_cast<int>(phase);
phase = incrementAndWrap(phase, phaseInc);
}
_phase = phase;
@ -78,8 +85,7 @@ void WavetableOscillator::processModulatedSingle(const float* frequencies, const
float frac = position - index;
output[i] = interpolate<M>(&table[index], frac);
phase += phaseInc;
phase -= static_cast<int>(phase);
phase = incrementAndWrap(phase, phaseInc);
}
_phase = phase;
@ -103,8 +109,7 @@ void WavetableOscillator::processDual(float frequency, float detuneRatio, float*
(1 - dt.delta) * interpolate<M>(&dt.table1[index], frac) +
dt.delta * interpolate<M>(&dt.table2[index], frac);
phase += phaseInc;
phase -= static_cast<int>(phase);
phase = incrementAndWrap(phase, phaseInc);
}
_phase = phase;
@ -132,8 +137,7 @@ void WavetableOscillator::processModulatedDual(const float* frequencies, const f
(1 - dt.delta) * interpolate<M>(&dt.table1[index], frac) +
dt.delta * interpolate<M>(&dt.table2[index], frac);
phase += phaseInc;
phase -= static_cast<int>(phase);
phase = incrementAndWrap(phase, phaseInc);
}
_phase = phase;

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 {};