diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 57ebb4da..27b7c240 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -64,11 +64,16 @@ namespace Default // Wavetable oscillator constexpr float oscillatorPhase { 0.0 }; constexpr Range oscillatorPhaseRange { -1.0, 360.0 }; + constexpr int oscillatorMode { 0 }; constexpr int oscillatorMulti { 1 }; + constexpr Range oscillatorModeRange { 0, 2 }; constexpr Range oscillatorMultiRange { 1, config::oscillatorsPerVoice }; constexpr float oscillatorDetune { 0 }; constexpr Range oscillatorDetuneRange { -9600, 9600 }; constexpr Range oscillatorDetuneCCRange { -9600, 9600 }; + constexpr float oscillatorModDepth { 0 }; + constexpr Range oscillatorModDepthRange { 0, 10000 }; // depth%, allowed to be >100 for FM + constexpr Range oscillatorModDepthCCRange { 0, 10000 }; constexpr int oscillatorQuality { 1 }; constexpr Range oscillatorQualityRange { 0, 3 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 5129c275..a98dac2a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -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(); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 8023070c..d11e87a9 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -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 oscillatorQuality; // Instrument settings: voice lifecycle diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 4dd86f5e..c08dde96 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -797,7 +797,6 @@ void sfz::Synth::renderBlock(AudioSpan 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(); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index ad236fe7..d165d426 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -673,19 +673,29 @@ void sfz::Voice::fillWithGenerator(AudioSpan 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(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(*tempSpan, leftSpan); + copy(*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 buffer) noexcept applyGain1(waveDetuneRatio[u], *detuneSpan); } osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), numFrames); - multiplyAdd1(waveLeftGain[u], *tempSpan, leftSpan); - multiplyAdd1(waveRightGain[u], *tempSpan, rightSpan); + if (u == 0) { + applyGain1(waveLeftGain[u], *tempSpan, *tempLeftSpan); + applyGain1(waveRightGain[u], *tempSpan, *tempRightSpan); + } + else { + multiplyAdd1(waveLeftGain[u], *tempSpan, *tempLeftSpan); + multiplyAdd1(waveRightGain[u], *tempSpan, *tempRightSpan); + } } + + copy(*tempLeftSpan, leftSpan); + copy(*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(*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(*modulatorSpan, *frequencies, *frequencies); + oscCar.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames()); + break; + } + + copy(*tempSpan, leftSpan); + copy(*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())); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 4224c927..348022f6 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -495,6 +495,7 @@ private: ModMatrix::TargetId widthTarget; ModMatrix::TargetId pitchTarget; ModMatrix::TargetId oscillatorDetuneTarget; + ModMatrix::TargetId oscillatorModDepthTarget; PowerFollower powerFollower; diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 2a668a13..c8cba91c 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -36,6 +36,14 @@ void WavetableOscillator::setPhase(float phase) _phase = phase; } +static float incrementAndWrap(float phase, float inc) +{ + phase += inc; + phase -= static_cast(phase); + phase += phase < 0.0f; // in case of negative frequencies + return phase; +} + template 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(&table[index], frac); - phase += phaseInc; - phase -= static_cast(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(&table[index], frac); - phase += phaseInc; - phase -= static_cast(phase); + phase = incrementAndWrap(phase, phaseInc); } _phase = phase; @@ -103,8 +109,7 @@ void WavetableOscillator::processDual(float frequency, float detuneRatio, float* (1 - dt.delta) * interpolate(&dt.table1[index], frac) + dt.delta * interpolate(&dt.table2[index], frac); - phase += phaseInc; - phase -= static_cast(phase); + phase = incrementAndWrap(phase, phaseInc); } _phase = phase; @@ -132,8 +137,7 @@ void WavetableOscillator::processModulatedDual(const float* frequencies, const f (1 - dt.delta) * interpolate(&dt.table1[index], frac) + dt.delta * interpolate(&dt.table2[index], frac); - phase += phaseInc; - phase -= static_cast(phase); + phase = incrementAndWrap(phase, phaseInc); } _phase = phase; diff --git a/src/sfizz/modulations/ModId.cpp b/src/sfizz/modulations/ModId.cpp index ed1c6fc0..f294a0bc 100644 --- a/src/sfizz/modulations/ModId.cpp +++ b/src/sfizz/modulations/ModId.cpp @@ -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: diff --git a/src/sfizz/modulations/ModId.h b/src/sfizz/modulations/ModId.h index f7402e39..daf7f72f 100644 --- a/src/sfizz/modulations/ModId.h +++ b/src/sfizz/modulations/ModId.h @@ -44,6 +44,7 @@ enum class ModId : int { EqFrequency, EqBandwidth, OscillatorDetune, + OscillatorModDepth, _TargetsEnd, // [/targets] -------------------------------------------------------------- diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 7a7a3a3c..efb6c8a0 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -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 {};