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
|
// Wavetable oscillator
|
||||||
constexpr float oscillatorPhase { 0.0 };
|
constexpr float oscillatorPhase { 0.0 };
|
||||||
constexpr Range<float> oscillatorPhaseRange { -1.0, 360.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
|
// Instrument setting: voice lifecycle
|
||||||
constexpr uint32_t group { 0 };
|
constexpr uint32_t group { 0 };
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ public:
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
Type getStart() const noexcept { return _start; }
|
constexpr Type getStart() const noexcept { return _start; }
|
||||||
Type getEnd() const noexcept { return _end; }
|
constexpr Type getEnd() const noexcept { return _end; }
|
||||||
/**
|
/**
|
||||||
* @brief Get the range as an std::pair of the endpoints
|
* @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))
|
if (auto value = readBooleanFromOpcode(opcode))
|
||||||
oscillator = *value;
|
oscillator = *value;
|
||||||
break;
|
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
|
// Instrument settings: voice lifecycle
|
||||||
case hash("group"): // fallthrough
|
case hash("group"): // fallthrough
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,13 @@ struct Region {
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool isGenerator() const noexcept { return sample.size() > 0 ? sample[0] == '*' : 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)?
|
* @brief Is a looping region (at least potentially)?
|
||||||
*
|
*
|
||||||
|
|
@ -235,6 +242,8 @@ struct Region {
|
||||||
// Wavetable oscillator
|
// Wavetable oscillator
|
||||||
float oscillatorPhase { Default::oscillatorPhase };
|
float oscillatorPhase { Default::oscillatorPhase };
|
||||||
bool oscillator = false;
|
bool oscillator = false;
|
||||||
|
int oscillatorMulti = Default::oscillatorMulti;
|
||||||
|
float oscillatorDetune = Default::oscillatorDetune;
|
||||||
|
|
||||||
// Instrument settings: voice lifecycle
|
// Instrument settings: voice lifecycle
|
||||||
uint32_t group { Default::group }; // group
|
uint32_t group { Default::group }; // group
|
||||||
|
|
@ -318,7 +327,7 @@ struct Region {
|
||||||
EGDescription pitchEG;
|
EGDescription pitchEG;
|
||||||
EGDescription filterEG;
|
EGDescription filterEG;
|
||||||
|
|
||||||
bool isStereo { false };
|
bool hasStereoSample { false };
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
std::vector<float> gainToEffect;
|
std::vector<float> gainToEffect;
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileInformation->numChannels == 2)
|
if (fileInformation->numChannels == 2)
|
||||||
region->isStereo = true;
|
region->hasStereoSample = true;
|
||||||
|
|
||||||
// TODO: adjust with LFO targets
|
// TODO: adjust with LFO targets
|
||||||
const auto maxOffset = region->offset + region->offsetRandom;
|
const auto maxOffset = region->offset + region->offsetRandom;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ sfz::Voice::Voice(sfz::Resources& resources)
|
||||||
filters.reserve(config::filtersPerVoice);
|
filters.reserve(config::filtersPerVoice);
|
||||||
equalizers.reserve(config::eqsPerVoice);
|
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
|
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();
|
wave = resources.wavePool.getWaveSaw();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
waveOscillator.setWavetable(wave);
|
for (WavetableOscillator& osc : waveOscillators) {
|
||||||
waveOscillator.setPhase(region->getPhase());
|
osc.setWavetable(wave);
|
||||||
|
osc.setPhase(region->getPhase());
|
||||||
|
}
|
||||||
|
setupOscillatorUnison();
|
||||||
} else if (region->oscillator) {
|
} else if (region->oscillator) {
|
||||||
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample);
|
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample);
|
||||||
waveOscillator.setWavetable(wave);
|
for (WavetableOscillator& osc : waveOscillators) {
|
||||||
waveOscillator.setPhase(region->getPhase());
|
osc.setWavetable(wave);
|
||||||
|
osc.setPhase(region->getPhase());
|
||||||
|
}
|
||||||
|
setupOscillatorUnison();
|
||||||
} else {
|
} else {
|
||||||
currentPromise = resources.filePool.getFilePromise(region->sample);
|
currentPromise = resources.filePool.getFilePromise(region->sample);
|
||||||
if (currentPromise == nullptr) {
|
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((filters.capacity() - filters.size()) >= region->filters.size());
|
||||||
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.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) {
|
for (auto& filter: region->filters) {
|
||||||
auto newFilter = resources.filterPool.getFilter(filter, numChannels, number, value);
|
auto newFilter = resources.filterPool.getFilter(filter, numChannels, number, value);
|
||||||
if (newFilter)
|
if (newFilter)
|
||||||
|
|
@ -187,7 +194,8 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
||||||
{
|
{
|
||||||
this->sampleRate = sampleRate;
|
this->sampleRate = sampleRate;
|
||||||
|
|
||||||
waveOscillator.init(sampleRate);
|
for (WavetableOscillator& osc : waveOscillators)
|
||||||
|
osc.init(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
|
@ -218,7 +226,7 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
fillWithData(delayed_buffer);
|
fillWithData(delayed_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (region->isStereo) {
|
if (region->isStereo()) {
|
||||||
ampStageStereo(buffer);
|
ampStageStereo(buffer);
|
||||||
panStageStereo(buffer);
|
panStageStereo(buffer);
|
||||||
filterStageStereo(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(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
} else {
|
} else {
|
||||||
const auto numSamples = buffer.getNumFrames();
|
const auto numFrames = buffer.getNumFrames();
|
||||||
auto frequencies = resources.bufferPool.getBuffer(numSamples);
|
|
||||||
auto bends = resources.bufferPool.getBuffer(numSamples);
|
auto frequencies = resources.bufferPool.getBuffer(numFrames);
|
||||||
|
auto bends = resources.bufferPool.getBuffer(numFrames);
|
||||||
if (!frequencies || !bends)
|
if (!frequencies || !bends)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -544,8 +553,25 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
applyGain<float>(*bends, *frequencies);
|
applyGain<float>(*bends, *frequencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames());
|
if (waveUnisonSize == 1) {
|
||||||
copy<float>(leftSpan, rightSpan);
|
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);
|
ASSERT(equalizers.size() == 0);
|
||||||
equalizers.reserve(numFilters);
|
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 panStageStereo(AudioSpan<float> buffer) noexcept;
|
||||||
void filterStageMono(AudioSpan<float> buffer) noexcept;
|
void filterStageMono(AudioSpan<float> buffer) noexcept;
|
||||||
void filterStageStereo(AudioSpan<float> buffer) noexcept;
|
void filterStageStereo(AudioSpan<float> buffer) noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Initialize frequency and gain coefficients for the oscillators.
|
||||||
|
*/
|
||||||
|
void setupOscillatorUnison();
|
||||||
|
|
||||||
Region* region { nullptr };
|
Region* region { nullptr };
|
||||||
|
|
||||||
|
|
@ -284,7 +288,15 @@ private:
|
||||||
ADSREnvelope<float> egEnvelope;
|
ADSREnvelope<float> egEnvelope;
|
||||||
float bendStepFactor { centsFactor(1) };
|
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 dataDuration;
|
||||||
Duration amplitudeDuration;
|
Duration amplitudeDuration;
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ void WavetableOscillator::setPhase(float phase)
|
||||||
_phase = 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 phase = _phase;
|
||||||
float phaseInc = frequency * _sampleInterval;
|
float phaseInc = frequency * (detuneRatio * _sampleInterval);
|
||||||
|
|
||||||
const WavetableMulti& multi = *_multi;
|
const WavetableMulti& multi = *_multi;
|
||||||
unsigned tableSize = multi.tableSize();
|
unsigned tableSize = multi.tableSize();
|
||||||
|
|
@ -56,7 +56,7 @@ void WavetableOscillator::process(float frequency, float* output, unsigned nfram
|
||||||
_phase = phase;
|
_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 phase = _phase;
|
||||||
float sampleInterval = _sampleInterval;
|
float sampleInterval = _sampleInterval;
|
||||||
|
|
@ -66,7 +66,7 @@ void WavetableOscillator::processModulated(const float* frequencies, float* outp
|
||||||
|
|
||||||
for (unsigned i = 0; i < nframes; ++i) {
|
for (unsigned i = 0; i < nframes; ++i) {
|
||||||
float frequency = frequencies[i];
|
float frequency = frequencies[i];
|
||||||
float phaseInc = frequency * sampleInterval;
|
float phaseInc = frequency * (detuneRatio * sampleInterval);
|
||||||
absl::Span<const float> table = multi.getTableForFrequency(frequency);
|
absl::Span<const float> table = multi.getTableForFrequency(frequency);
|
||||||
|
|
||||||
float position = phase * tableSize;
|
float position = phase * tableSize;
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,12 @@ public:
|
||||||
/**
|
/**
|
||||||
Compute a cycle of the oscillator, with constant frequency.
|
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.
|
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:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ int DemoApp::processAudio(jack_nframes_t nframes, void* cbdata)
|
||||||
self->fSweepCurrent = sweepCurrent;
|
self->fSweepCurrent = sweepCurrent;
|
||||||
|
|
||||||
// compute oscillator
|
// compute oscillator
|
||||||
osc.processModulated(frequency, left, nframes);
|
osc.processModulated(frequency, 1.0, left, nframes);
|
||||||
std::memcpy(right, left, nframes * sizeof(float));
|
std::memcpy(right, left, nframes * sizeof(float));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue