Auto-enable oscillator=on when a wavetable is provided

This commit is contained in:
Jean Pierre Cimalando 2020-09-22 15:53:33 +02:00
parent 6494cdac10
commit 85883481c9
10 changed files with 154 additions and 69 deletions

View file

@ -55,7 +55,7 @@ void ADSREnvelope<Type>::reset(const EGDescription& desc, const Region& region,
shouldRelease = false;
freeRunning = (
(this->sustain == 0.0f)
|| (region.loopMode == SfzLoopMode::one_shot && (region.isGenerator() || region.oscillator))
|| (region.loopMode == SfzLoopMode::one_shot && region.isOscillator())
);
currentValue = this->start;
currentState = State::Delay;

View file

@ -113,6 +113,11 @@ namespace config {
static constexpr double amplitudeTriangle = 1.0;
static constexpr double amplitudeSaw = 0.8164965809277261; // sqrt(2)/sqrt(3)
static constexpr double amplitudeSquare = 0.8164965809277261; // should have been sqrt(2)?
/**
Frame count high limit, for automatically loading a sound file as wavetable.
Set to 3000 according to Cakewalk.
*/
static constexpr unsigned wavetableMaxFrames = 3000;
/**
Background file loading
*/

View file

@ -25,7 +25,6 @@
#include "FilePool.h"
#include "AudioReader.h"
#include "FileMetadata.h"
#include "Buffer.h"
#include "AudioBuffer.h"
#include "AudioSpan.h"
@ -218,13 +217,21 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
SF_INSTRUMENT instrumentInfo {};
FileMetadataReader mdReader;
bool mdReaderOpened = mdReader.open(file);
if (!reader->getInstrument(&instrumentInfo)) {
// if no instrument, then try extracting from embedded RIFF chunks (flac)
FileMetadataReader mdReader;
if (mdReader.open(file))
if (mdReaderOpened)
mdReader.extractRiffInstrument(instrumentInfo);
}
if (mdReaderOpened) {
WavetableInfo wt;
if (mdReader.extractWavetableInfo(wt))
returnedValue.wavetable = wt;
}
if (!fileId.isReverse()) {
if (instrumentInfo.loop_count > 0) {
returnedValue.hasLoop = true;

View file

@ -31,6 +31,7 @@
#include "AudioBuffer.h"
#include "AudioSpan.h"
#include "FileId.h"
#include "FileMetadata.h"
#include "SIMDHelpers.h"
#include "utility/SpinMutex.h"
#include "ghc/fs_std.hpp"
@ -55,6 +56,7 @@ struct FileInformation {
bool hasLoop { false };
double sampleRate { config::defaultSampleRate };
int numChannels { 0 };
absl::optional<WavetableInfo> wavetable;
};
// Strict C++11 disallows member initialization if aggregate initialization is to be used...

View file

@ -145,7 +145,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
break;
case hash("oscillator"):
if (auto value = readBooleanFromOpcode(opcode))
oscillator = *value;
oscillatorEnabled = *value ? OscillatorEnabled::On : OscillatorEnabled::Off;
break;
case hash("oscillator_multi"):
setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange);

View file

@ -76,13 +76,28 @@ struct Region {
* @return false
*/
bool isGenerator() const noexcept { return sampleId.filename().size() > 0 ? sampleId.filename()[0] == '*' : false; }
/**
* @brief Is an oscillator (generator or wavetable)?
*
* @return true
* @return false
*/
bool isOscillator() const noexcept
{
if (isGenerator())
return true;
else if (oscillatorEnabled != OscillatorEnabled::Auto)
return oscillatorEnabled == OscillatorEnabled::On;
else
return hasWavetableSample;
}
/**
* @brief Is stereo (has stereo sample or is unison oscillator)?
*
* @return true
* @return false
*/
bool isStereo() const noexcept { return hasStereoSample || ((oscillator || isGenerator()) && oscillatorMulti >= 3); }
bool isStereo() const noexcept { return hasStereoSample || (isOscillator() && oscillatorMulti >= 3); }
/**
* @brief Is a looping region (at least potentially)?
*
@ -284,7 +299,9 @@ struct Region {
// Wavetable oscillator
float oscillatorPhase { Default::oscillatorPhase };
bool oscillator = false;
enum class OscillatorEnabled { Auto = -1, Off = 0, On = 1 };
OscillatorEnabled oscillatorEnabled = OscillatorEnabled::Auto; // oscillator
bool hasWavetableSample = false; // (set according to sample file)
int oscillatorMulti = Default::oscillatorMulti;
float oscillatorDetune = Default::oscillatorDetune;
absl::optional<int> oscillatorQuality;

View file

@ -495,18 +495,25 @@ void sfz::Synth::finalizeSfzLoad()
while (currentRegionIndex < currentRegionCount) {
auto region = regions[currentRegionIndex].get();
if (!region->oscillator && !region->isGenerator()) {
absl::optional<FileInformation> fileInformation;
if (!region->isGenerator()) {
if (!resources.filePool.checkSampleId(region->sampleId)) {
removeCurrentRegion();
continue;
}
const auto fileInformation = resources.filePool.getFileInformation(region->sampleId);
fileInformation = resources.filePool.getFileInformation(region->sampleId);
if (!fileInformation) {
removeCurrentRegion();
continue;
}
region->hasWavetableSample = fileInformation->wavetable ||
fileInformation->end < config::wavetableMaxFrames;
}
if (!region->isOscillator()) {
region->sampleEnd = std::min(region->sampleEnd, fileInformation->end);
if (fileInformation->hasLoop) {
@ -539,12 +546,8 @@ void sfz::Synth::finalizeSfzLoad()
if (!resources.filePool.preloadFile(region->sampleId, maxOffset))
removeCurrentRegion();
} else if (region->oscillator && !region->isGenerator()) {
if (!resources.filePool.checkSampleId(region->sampleId)) {
removeCurrentRegion();
continue;
}
}
else if (!region->isGenerator()) {
if (!resources.wavePool.createFileWave(resources.filePool, std::string(region->sampleId.filename()))) {
removeCurrentRegion();
continue;

View file

@ -58,25 +58,29 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event
if (delay < 0)
delay = 0;
if (region->isGenerator()) {
if (region->isOscillator()) {
const WavetableMulti* wave = nullptr;
switch (hash(region->sampleId.filename())) {
default:
case hash("*silence"):
break;
case hash("*sine"):
wave = resources.wavePool.getWaveSin();
break;
case hash("*triangle"): // fallthrough
case hash("*tri"):
wave = resources.wavePool.getWaveTriangle();
break;
case hash("*square"):
wave = resources.wavePool.getWaveSquare();
break;
case hash("*saw"):
wave = resources.wavePool.getWaveSaw();
break;
if (!region->isGenerator())
wave = resources.wavePool.getFileWave(region->sampleId.filename());
else {
switch (hash(region->sampleId.filename())) {
default:
case hash("*silence"):
break;
case hash("*sine"):
wave = resources.wavePool.getWaveSin();
break;
case hash("*triangle"): // fallthrough
case hash("*tri"):
wave = resources.wavePool.getWaveTriangle();
break;
case hash("*square"):
wave = resources.wavePool.getWaveSquare();
break;
case hash("*saw"):
wave = resources.wavePool.getWaveSaw();
break;
}
}
const float phase = region->getPhase();
const int quality = region->oscillatorQuality.value_or(Default::oscillatorQuality);
@ -86,16 +90,6 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event
osc.setQuality(quality);
}
setupOscillatorUnison();
} else if (region->oscillator) {
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sampleId.filename());
const float phase = region->getPhase();
const int quality = region->oscillatorQuality.value_or(Default::oscillatorQuality);
for (WavetableOscillator& osc : waveOscillators) {
osc.setWavetable(wave);
osc.setPhase(phase);
osc.setQuality(quality);
}
setupOscillatorUnison();
} else {
currentPromise = resources.filePool.getFilePromise(region->sampleId);
if (currentPromise == nullptr) {
@ -276,7 +270,7 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
{ // Fill buffer with raw data
ScopedTiming logger { dataDuration };
if (region->isGenerator() || region->oscillator)
if (region->isOscillator())
fillWithGenerator(delayed_buffer);
else
fillWithData(delayed_buffer);

View file

@ -270,37 +270,90 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)")
{
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz");
REQUIRE(synth.getNumRegions() == 6);
REQUIRE(synth.getNumRegions() == 10);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "*sine");
REQUIRE(!synth.getRegionView(0)->isStereo());
REQUIRE(synth.getRegionView(0)->isGenerator());
REQUIRE(!synth.getRegionView(0)->oscillator);
int regionNumber = 0;
const Region* region = nullptr;
REQUIRE(synth.getRegionView(1)->sampleId.filename() == "*sine");
REQUIRE(synth.getRegionView(1)->isStereo());
REQUIRE(synth.getRegionView(1)->isGenerator());
REQUIRE(!synth.getRegionView(1)->oscillator);
// generator only
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "*sine");
REQUIRE(!region->isStereo());
REQUIRE(region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
REQUIRE(synth.getRegionView(2)->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!synth.getRegionView(2)->isStereo());
REQUIRE(!synth.getRegionView(2)->isGenerator());
REQUIRE(synth.getRegionView(2)->oscillator);
// generator with multi
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "*sine");
REQUIRE(region->isStereo());
REQUIRE(region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
REQUIRE(synth.getRegionView(3)->sampleId.filename() == "ramp_wave.wav");
REQUIRE(synth.getRegionView(3)->isStereo());
REQUIRE(!synth.getRegionView(3)->isGenerator());
REQUIRE(synth.getRegionView(3)->oscillator);
// explicit wavetable
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::On);
REQUIRE(synth.getRegionView(4)->sampleId.filename() == "*sine");
REQUIRE(!synth.getRegionView(4)->isStereo());
REQUIRE(synth.getRegionView(4)->isGenerator());
REQUIRE(!synth.getRegionView(4)->oscillator);
// explicit wavetable with multi
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::On);
REQUIRE(synth.getRegionView(5)->sampleId.filename() == "*sine");
REQUIRE(!synth.getRegionView(5)->isStereo());
REQUIRE(synth.getRegionView(5)->isGenerator());
REQUIRE(!synth.getRegionView(5)->oscillator);
// explicit disabled wavetable
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(!region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Off);
// explicit disabled wavetable with multi
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(!region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Off);
// implicit wavetable (sound file < 3000 frames)
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
// implicit non-wavetable (sound file >= 3000 frames)
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "snare.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(!region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
// generator with multi=1 (single)
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "*sine");
REQUIRE(!region->isStereo());
REQUIRE(region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
// generator with multi=2 (ring modulation)
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId.filename() == "*sine");
REQUIRE(!region->isStereo());
REQUIRE(region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
}
TEST_CASE("[Files] sw_default")

View file

@ -2,5 +2,9 @@
<region> sample=*sine oscillator_multi=3
<region> sample=ramp_wave.wav oscillator=on
<region> sample=ramp_wave.wav oscillator=on oscillator_multi=3
<region> sample=ramp_wave.wav oscillator=off
<region> sample=ramp_wave.wav oscillator=off oscillator_multi=3
<region> sample=ramp_wave.wav
<region> sample=snare.wav
<region> sample=*sine oscillator_multi=1
<region> sample=*sine oscillator_multi=2