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; shouldRelease = false;
freeRunning = ( freeRunning = (
(this->sustain == 0.0f) (this->sustain == 0.0f)
|| (region.loopMode == SfzLoopMode::one_shot && (region.isGenerator() || region.oscillator)) || (region.loopMode == SfzLoopMode::one_shot && region.isOscillator())
); );
currentValue = this->start; currentValue = this->start;
currentState = State::Delay; currentState = State::Delay;

View file

@ -113,6 +113,11 @@ namespace config {
static constexpr double amplitudeTriangle = 1.0; static constexpr double amplitudeTriangle = 1.0;
static constexpr double amplitudeSaw = 0.8164965809277261; // sqrt(2)/sqrt(3) static constexpr double amplitudeSaw = 0.8164965809277261; // sqrt(2)/sqrt(3)
static constexpr double amplitudeSquare = 0.8164965809277261; // should have been sqrt(2)? 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 Background file loading
*/ */

View file

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

View file

@ -31,6 +31,7 @@
#include "AudioBuffer.h" #include "AudioBuffer.h"
#include "AudioSpan.h" #include "AudioSpan.h"
#include "FileId.h" #include "FileId.h"
#include "FileMetadata.h"
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
#include "utility/SpinMutex.h" #include "utility/SpinMutex.h"
#include "ghc/fs_std.hpp" #include "ghc/fs_std.hpp"
@ -55,6 +56,7 @@ struct FileInformation {
bool hasLoop { false }; bool hasLoop { false };
double sampleRate { config::defaultSampleRate }; double sampleRate { config::defaultSampleRate };
int numChannels { 0 }; int numChannels { 0 };
absl::optional<WavetableInfo> wavetable;
}; };
// Strict C++11 disallows member initialization if aggregate initialization is to be used... // 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; break;
case hash("oscillator"): case hash("oscillator"):
if (auto value = readBooleanFromOpcode(opcode)) if (auto value = readBooleanFromOpcode(opcode))
oscillator = *value; oscillatorEnabled = *value ? OscillatorEnabled::On : OscillatorEnabled::Off;
break; break;
case hash("oscillator_multi"): case hash("oscillator_multi"):
setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange); setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange);

View file

@ -76,13 +76,28 @@ struct Region {
* @return false * @return false
*/ */
bool isGenerator() const noexcept { return sampleId.filename().size() > 0 ? sampleId.filename()[0] == '*' : 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)? * @brief Is stereo (has stereo sample or is unison oscillator)?
* *
* @return true * @return true
* @return false * @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)? * @brief Is a looping region (at least potentially)?
* *
@ -284,7 +299,9 @@ struct Region {
// Wavetable oscillator // Wavetable oscillator
float oscillatorPhase { Default::oscillatorPhase }; 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; int oscillatorMulti = Default::oscillatorMulti;
float oscillatorDetune = Default::oscillatorDetune; float oscillatorDetune = Default::oscillatorDetune;
absl::optional<int> oscillatorQuality; absl::optional<int> oscillatorQuality;

View file

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

View file

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

View file

@ -270,37 +270,90 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)")
{ {
Synth synth; Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz"); 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"); int regionNumber = 0;
REQUIRE(!synth.getRegionView(0)->isStereo()); const Region* region = nullptr;
REQUIRE(synth.getRegionView(0)->isGenerator());
REQUIRE(!synth.getRegionView(0)->oscillator);
REQUIRE(synth.getRegionView(1)->sampleId.filename() == "*sine"); // generator only
REQUIRE(synth.getRegionView(1)->isStereo()); region = synth.getRegionView(regionNumber++);
REQUIRE(synth.getRegionView(1)->isGenerator()); REQUIRE(region->sampleId.filename() == "*sine");
REQUIRE(!synth.getRegionView(1)->oscillator); REQUIRE(!region->isStereo());
REQUIRE(region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
REQUIRE(synth.getRegionView(2)->sampleId.filename() == "ramp_wave.wav"); // generator with multi
REQUIRE(!synth.getRegionView(2)->isStereo()); region = synth.getRegionView(regionNumber++);
REQUIRE(!synth.getRegionView(2)->isGenerator()); REQUIRE(region->sampleId.filename() == "*sine");
REQUIRE(synth.getRegionView(2)->oscillator); REQUIRE(region->isStereo());
REQUIRE(region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::Auto);
REQUIRE(synth.getRegionView(3)->sampleId.filename() == "ramp_wave.wav"); // explicit wavetable
REQUIRE(synth.getRegionView(3)->isStereo()); region = synth.getRegionView(regionNumber++);
REQUIRE(!synth.getRegionView(3)->isGenerator()); REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(synth.getRegionView(3)->oscillator); REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::On);
REQUIRE(synth.getRegionView(4)->sampleId.filename() == "*sine"); // explicit wavetable with multi
REQUIRE(!synth.getRegionView(4)->isStereo()); region = synth.getRegionView(regionNumber++);
REQUIRE(synth.getRegionView(4)->isGenerator()); REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!synth.getRegionView(4)->oscillator); REQUIRE(region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == Region::OscillatorEnabled::On);
REQUIRE(synth.getRegionView(5)->sampleId.filename() == "*sine"); // explicit disabled wavetable
REQUIRE(!synth.getRegionView(5)->isStereo()); region = synth.getRegionView(regionNumber++);
REQUIRE(synth.getRegionView(5)->isGenerator()); REQUIRE(region->sampleId.filename() == "ramp_wave.wav");
REQUIRE(!synth.getRegionView(5)->oscillator); 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") TEST_CASE("[Files] sw_default")

View file

@ -2,5 +2,9 @@
<region> sample=*sine oscillator_multi=3 <region> sample=*sine oscillator_multi=3
<region> sample=ramp_wave.wav oscillator=on <region> sample=ramp_wave.wav oscillator=on
<region> sample=ramp_wave.wav oscillator=on oscillator_multi=3 <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=1
<region> sample=*sine oscillator_multi=2 <region> sample=*sine oscillator_multi=2