Merge pull request #436 from jpcima/oscillators-fm
Add FM synthesis with 2 operators
This commit is contained in:
commit
296080bb56
10 changed files with 133 additions and 24 deletions
|
|
@ -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 };
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -495,6 +495,7 @@ private:
|
|||
ModMatrix::TargetId widthTarget;
|
||||
ModMatrix::TargetId pitchTarget;
|
||||
ModMatrix::TargetId oscillatorDetuneTarget;
|
||||
ModMatrix::TargetId oscillatorModDepthTarget;
|
||||
|
||||
PowerFollower powerFollower;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ enum class ModId : int {
|
|||
EqFrequency,
|
||||
EqBandwidth,
|
||||
OscillatorDetune,
|
||||
OscillatorModDepth,
|
||||
|
||||
_TargetsEnd,
|
||||
// [/targets] --------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue