Implement RM synthesis
This commit is contained in:
parent
5c1931de2c
commit
05b5728d88
8 changed files with 111 additions and 15 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, 100 };
|
||||
constexpr Range<float> oscillatorModDepthCCRange { 0, 100 };
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -495,6 +495,7 @@ private:
|
|||
ModMatrix::TargetId widthTarget;
|
||||
ModMatrix::TargetId pitchTarget;
|
||||
ModMatrix::TargetId oscillatorDetuneTarget;
|
||||
ModMatrix::TargetId oscillatorModDepthTarget;
|
||||
|
||||
PowerFollower powerFollower;
|
||||
|
||||
|
|
|
|||
|
|
@ -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