Auto-enable oscillator=on when a wavetable is provided
This commit is contained in:
parent
6494cdac10
commit
85883481c9
10 changed files with 154 additions and 69 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
103
tests/FilesT.cpp
103
tests/FilesT.cpp
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue