Add oscillator unison
This commit is contained in:
parent
5a61f3f4a9
commit
cea3e4715b
10 changed files with 139 additions and 25 deletions
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue