From 05b5728d88b67da4005643f17d8204b0a5c3076e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 22 Sep 2020 21:49:22 +0200 Subject: [PATCH 1/6] Implement RM synthesis --- src/sfizz/Defaults.h | 5 ++ src/sfizz/Region.cpp | 10 +++ src/sfizz/Region.h | 2 + src/sfizz/Voice.cpp | 103 ++++++++++++++++++++++++++----- src/sfizz/Voice.h | 1 + src/sfizz/modulations/ModId.cpp | 2 + src/sfizz/modulations/ModId.h | 1 + src/sfizz/modulations/ModKey.cpp | 2 + 8 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 1fb2255f..df71352e 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, 100 }; + constexpr Range oscillatorModDepthCCRange { 0, 100 }; constexpr int oscillatorQuality { 1 }; constexpr Range oscillatorQualityRange { 0, 3 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index affc4997..4f6fc2a9 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 1d402bbe..083fdd7a 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -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 oscillatorQuality; // Instrument settings: voice lifecycle diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index ad236fe7..8d5479e2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -661,6 +661,8 @@ void sfz::Voice::fillWithGenerator(AudioSpan 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 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 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 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); + multiplyAdd1(waveLeftGain[u], *tempSpan, *temp2Span); + copy(*temp2Span, leftSpan); + multiplyAdd1(waveRightGain[u], *tempSpan, *temp2Span); + copy(*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(*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(*tempSpan, leftSpan); + copy(*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())); } 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/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 {}; From a587b4035b2d217301142973520af72caad63155 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 00:20:32 +0200 Subject: [PATCH 2/6] Give room for FM index greater than 1 --- src/sfizz/Defaults.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index df71352e..dc54a6c2 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -72,8 +72,8 @@ namespace Default constexpr Range oscillatorDetuneRange { -9600, 9600 }; constexpr Range oscillatorDetuneCCRange { -9600, 9600 }; constexpr float oscillatorModDepth { 0 }; - constexpr Range oscillatorModDepthRange { 0, 100 }; - constexpr Range oscillatorModDepthCCRange { 0, 100 }; + 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 }; From e19c20e90ef8a9e78a878fc0c64b2c63e4d7c0f9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 01:09:37 +0200 Subject: [PATCH 3/6] Allow oscillator frequency to be modulated into negatives --- src/sfizz/Wavetables.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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; From 2b2a373b13985b8931f76db52149c910d2dcc9f8 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 01:10:24 +0200 Subject: [PATCH 4/6] Add FM synthesis --- src/sfizz/Voice.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 8d5479e2..ae49816a 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -757,11 +757,15 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept break; case 1: // PM synthesis - return; // Note(jpc): not yet implemented + // Note(jpc): not implemented, just do FM instead + goto fm_synthesis; break; case 2: // FM synthesis - return; // Note(jpc): not yet implemented + fm_synthesis: + fill(*detuneSpan, 1.0f); + multiplyAdd(*modulatorSpan, *frequencies, *frequencies); + oscCar.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames()); break; } From f3fbc1e3277a86538975f69b7b7c0389a9134139 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 14:02:29 +0200 Subject: [PATCH 5/6] Fix the unison osc broken after alignment work --- src/sfizz/Voice.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index ae49816a..bc952482 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -693,8 +693,9 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept else if (oscillatorMode <= 0 && oscillatorMulti >= 3) { // unison oscillator auto tempSpan = resources.bufferPool.getBuffer(numFrames); - auto temp2Span = resources.bufferPool.getBuffer(numFrames); - if (!tempSpan || !temp2Span) + auto tempLeftSpan = resources.bufferPool.getBuffer(numFrames); + auto tempRightSpan = resources.bufferPool.getBuffer(numFrames); + if (!tempSpan || !tempLeftSpan || !tempRightSpan) return; const float* detuneMod = resources.modMatrix.getModulation(oscillatorDetuneTarget); @@ -708,11 +709,18 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept applyGain1(waveDetuneRatio[u], *detuneSpan); } osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), numFrames); - multiplyAdd1(waveLeftGain[u], *tempSpan, *temp2Span); - copy(*temp2Span, leftSpan); - multiplyAdd1(waveRightGain[u], *tempSpan, *temp2Span); - copy(*temp2Span, 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 From 08dc206ad1de56f2591d3624183e3aca1c99da17 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 23 Sep 2020 17:52:00 +0200 Subject: [PATCH 6/6] Clear the voice span before processing each voice --- src/sfizz/Synth.cpp | 1 - src/sfizz/Voice.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e63803c1..78dd7336 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -794,7 +794,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 bc952482..d165d426 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -661,8 +661,6 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept } else { const auto numFrames = buffer.getNumFrames(); - buffer.fill(0.0f); - auto frequencies = resources.bufferPool.getBuffer(numFrames); if (!frequencies) return;