Add oscillator unison

This commit is contained in:
Jean Pierre Cimalando 2020-04-03 15:37:47 +02:00
parent 5a61f3f4a9
commit cea3e4715b
10 changed files with 139 additions and 25 deletions

View file

@ -63,6 +63,10 @@ namespace Default
// Wavetable oscillator
constexpr float oscillatorPhase { 0.0 };
constexpr Range<float> oscillatorPhaseRange { -1.0, 360.0 };
constexpr int oscillatorMulti { 1 };
constexpr Range<int> oscillatorMultiRange { 1, 9 };
constexpr float oscillatorDetune { 0 };
constexpr Range<float> oscillatorDetuneRange { -9600, 9600 };
// Instrument setting: voice lifecycle
constexpr uint32_t group { 0 };

View file

@ -28,8 +28,8 @@ public:
{
}
Type getStart() const noexcept { return _start; }
Type getEnd() const noexcept { return _end; }
constexpr Type getStart() const noexcept { return _start; }
constexpr Type getEnd() const noexcept { return _end; }
/**
* @brief Get the range as an std::pair of the endpoints
*

View file

@ -102,6 +102,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
if (auto value = readBooleanFromOpcode(opcode))
oscillator = *value;
break;
case hash("oscillator_multi"):
setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange);
break;
case hash("oscillator_detune"):
setValueFromOpcode(opcode, oscillatorDetune, Default::oscillatorDetuneRange);
break;
// Instrument settings: voice lifecycle
case hash("group"): // fallthrough

View file

@ -60,6 +60,13 @@ struct Region {
* @return false
*/
bool isGenerator() const noexcept { return sample.size() > 0 ? sample[0] == '*' : false; }
/**
* @brief Is stereo (has stereo sample or is unison oscillator)?
*
* @return true
* @return false
*/
bool isStereo() const noexcept { return hasStereoSample || ((oscillator || isGenerator()) && oscillatorMulti >= 3); }
/**
* @brief Is a looping region (at least potentially)?
*
@ -235,6 +242,8 @@ struct Region {
// Wavetable oscillator
float oscillatorPhase { Default::oscillatorPhase };
bool oscillator = false;
int oscillatorMulti = Default::oscillatorMulti;
float oscillatorDetune = Default::oscillatorDetune;
// Instrument settings: voice lifecycle
uint32_t group { Default::group }; // group
@ -318,7 +327,7 @@ struct Region {
EGDescription pitchEG;
EGDescription filterEG;
bool isStereo { false };
bool hasStereoSample { false };
// Effects
std::vector<float> gainToEffect;

View file

@ -379,7 +379,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
}
if (fileInformation->numChannels == 2)
region->isStereo = true;
region->hasStereoSample = true;
// TODO: adjust with LFO targets
const auto maxOffset = region->offset + region->offsetRandom;

View file

@ -21,7 +21,8 @@ sfz::Voice::Voice(sfz::Resources& resources)
filters.reserve(config::filtersPerVoice);
equalizers.reserve(config::eqsPerVoice);
waveOscillator.init(sampleRate);
for (WavetableOscillator& osc : waveOscillators)
osc.init(sampleRate);
}
void sfz::Voice::startVoice(Region* region, int delay, int number, float value, sfz::Voice::TriggerType triggerType) noexcept
@ -59,12 +60,18 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
wave = resources.wavePool.getWaveSaw();
break;
}
waveOscillator.setWavetable(wave);
waveOscillator.setPhase(region->getPhase());
for (WavetableOscillator& osc : waveOscillators) {
osc.setWavetable(wave);
osc.setPhase(region->getPhase());
}
setupOscillatorUnison();
} else if (region->oscillator) {
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample);
waveOscillator.setWavetable(wave);
waveOscillator.setPhase(region->getPhase());
for (WavetableOscillator& osc : waveOscillators) {
osc.setWavetable(wave);
osc.setPhase(region->getPhase());
}
setupOscillatorUnison();
} else {
currentPromise = resources.filePool.getFilePromise(region->sample);
if (currentPromise == nullptr) {
@ -83,7 +90,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size());
const unsigned numChannels = region->isStereo ? 2 : 1;
const unsigned numChannels = region->isStereo() ? 2 : 1;
for (auto& filter: region->filters) {
auto newFilter = resources.filterPool.getFilter(filter, numChannels, number, value);
if (newFilter)
@ -187,7 +194,8 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
{
this->sampleRate = sampleRate;
waveOscillator.init(sampleRate);
for (WavetableOscillator& osc : waveOscillators)
osc.init(sampleRate);
}
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
@ -218,7 +226,7 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
fillWithData(delayed_buffer);
}
if (region->isStereo) {
if (region->isStereo()) {
ampStageStereo(buffer);
panStageStereo(buffer);
filterStageStereo(buffer);
@ -518,9 +526,10 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
} else {
const auto numSamples = buffer.getNumFrames();
auto frequencies = resources.bufferPool.getBuffer(numSamples);
auto bends = resources.bufferPool.getBuffer(numSamples);
const auto numFrames = buffer.getNumFrames();
auto frequencies = resources.bufferPool.getBuffer(numFrames);
auto bends = resources.bufferPool.getBuffer(numFrames);
if (!frequencies || !bends)
return;
@ -544,8 +553,25 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
applyGain<float>(*bends, *frequencies);
}
waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames());
copy<float>(leftSpan, rightSpan);
if (waveUnisonSize == 1) {
WavetableOscillator& osc = waveOscillators[0];
osc.processModulated(frequencies->data(), 1.0, leftSpan.data(), buffer.getNumFrames());
copy<float>(leftSpan, rightSpan);
}
else {
buffer.fill(0.0f);
auto tempSpan = resources.bufferPool.getBuffer(numFrames);
if (!tempSpan)
return;
for (unsigned i = 0, n = waveUnisonSize; i < n; ++i) {
WavetableOscillator& osc = waveOscillators[i];
osc.processModulated(frequencies->data(), waveDetuneRatio[i], tempSpan->data(), numFrames);
sfz::multiplyAdd<float>(waveLeftGain[i], *tempSpan, leftSpan);
sfz::multiplyAdd<float>(waveRightGain[i], *tempSpan, rightSpan);
}
}
}
}
@ -620,3 +646,60 @@ void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
ASSERT(equalizers.size() == 0);
equalizers.reserve(numFilters);
}
void sfz::Voice::setupOscillatorUnison()
{
int m = region->oscillatorMulti;
float d = region->oscillatorDetune;
// 3-9: unison mode, 1: normal/RM, 2: PM/FM
// TODO(jpc) RM/FM/PM synthesis
if (m < 3) {
waveUnisonSize = 1;
waveDetuneRatio[0] = 1.0;
waveLeftGain[0] = 1.0;
waveRightGain[0] = 1.0;
return;
}
// oscillator count, aka. unison size
waveUnisonSize = m;
// detune (cents)
float detunes[maxWaveOscillators];
detunes[0] = 0.0;
detunes[1] = -d;
detunes[2] = +d;
for (int i = 3; i < m; ++i)
detunes[i] = d * ((i & 1) ? -0.25f : +0.25f) * ((i - 1) / 2);
// detune (ratio)
for (int i = 0; i < m; ++i)
waveDetuneRatio[i] = std::exp2(detunes[i] * (0.01f / 12.0f));
// gains
waveLeftGain[0] = 0.0;
waveRightGain[m - 1] = 0.0;
for (int i = 0; i < m - 1; ++i) {
float g = 1 - i / float(m - 1);
waveLeftGain[m - 1 - i] = g;
waveRightGain[i] = g;
}
#if 0
fprintf(stderr, "\n");
fprintf(stderr, "# Left:\n");
for (int i = m - 1; i >= 0; --i) {
if (waveLeftGain[i] != 0)
fprintf(stderr, "[%d] %10g cents, %10g dB\n", i, detunes[i], 20.0f * std::log10(waveLeftGain[i]));
}
fprintf(stderr, "\n");
fprintf(stderr, "# Right:\n");
for (int i = 0; i < m; ++i) {
if (waveRightGain[i] != 0)
fprintf(stderr, "[%d] %10g cents, %10g dB\n", i, detunes[i], 20.0f * std::log10(waveRightGain[i]));
}
#endif
}
constexpr unsigned sfz::Voice::maxWaveOscillators;

View file

@ -244,6 +244,10 @@ private:
void panStageStereo(AudioSpan<float> buffer) noexcept;
void filterStageMono(AudioSpan<float> buffer) noexcept;
void filterStageStereo(AudioSpan<float> buffer) noexcept;
/**
* @brief Initialize frequency and gain coefficients for the oscillators.
*/
void setupOscillatorUnison();
Region* region { nullptr };
@ -284,7 +288,15 @@ private:
ADSREnvelope<float> egEnvelope;
float bendStepFactor { centsFactor(1) };
WavetableOscillator waveOscillator;
static constexpr unsigned maxWaveOscillators = Default::oscillatorMultiRange.getEnd();
WavetableOscillator waveOscillators[maxWaveOscillators];
// unison of oscillators
unsigned waveUnisonSize { 0 };
float waveDetuneRatio[maxWaveOscillators] { };
float waveLeftGain[maxWaveOscillators] { };
float waveRightGain[maxWaveOscillators] { };
Duration dataDuration;
Duration amplitudeDuration;

View file

@ -34,10 +34,10 @@ void WavetableOscillator::setPhase(float phase)
_phase = phase;
}
void WavetableOscillator::process(float frequency, float* output, unsigned nframes)
void WavetableOscillator::process(float frequency, float detuneRatio, float* output, unsigned nframes)
{
float phase = _phase;
float phaseInc = frequency * _sampleInterval;
float phaseInc = frequency * (detuneRatio * _sampleInterval);
const WavetableMulti& multi = *_multi;
unsigned tableSize = multi.tableSize();
@ -56,7 +56,7 @@ void WavetableOscillator::process(float frequency, float* output, unsigned nfram
_phase = phase;
}
void WavetableOscillator::processModulated(const float* frequencies, float* output, unsigned nframes)
void WavetableOscillator::processModulated(const float* frequencies, float detuneRatio, float* output, unsigned nframes)
{
float phase = _phase;
float sampleInterval = _sampleInterval;
@ -66,7 +66,7 @@ void WavetableOscillator::processModulated(const float* frequencies, float* outp
for (unsigned i = 0; i < nframes; ++i) {
float frequency = frequencies[i];
float phaseInc = frequency * sampleInterval;
float phaseInc = frequency * (detuneRatio * sampleInterval);
absl::Span<const float> table = multi.getTableForFrequency(frequency);
float position = phase * tableSize;

View file

@ -47,12 +47,12 @@ public:
/**
Compute a cycle of the oscillator, with constant frequency.
*/
void process(float frequency, float* output, unsigned nframes);
void process(float frequency, float detuneRatio, float* output, unsigned nframes);
/**
Compute a cycle of the oscillator, with varying frequency.
*/
void processModulated(const float* frequencies, float* output, unsigned nframes);
void processModulated(const float* frequencies, float detuneRatio, float* output, unsigned nframes);
private:
/**

View file

@ -172,7 +172,7 @@ int DemoApp::processAudio(jack_nframes_t nframes, void* cbdata)
self->fSweepCurrent = sweepCurrent;
// compute oscillator
osc.processModulated(frequency, left, nframes);
osc.processModulated(frequency, 1.0, left, nframes);
std::memcpy(right, left, nframes * sizeof(float));
return 0;