diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index a90d20bb..71741b5a 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -53,6 +53,10 @@ namespace Default constexpr SfzLoopMode loopMode { SfzLoopMode::no_loop }; constexpr Range loopRange { 0, std::numeric_limits::max() }; + // Wavetable oscillator + constexpr float oscillatorPhase { 0.0 }; + constexpr Range oscillatorPhaseRange { -1.0, 360.0 }; + // Instrument setting: voice lifecycle constexpr uint32_t group { 0 }; constexpr Range groupRange { 0, std::numeric_limits::max() }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 10c5415a..8d8f14ee 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -95,6 +95,11 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setRangeStartFromOpcode(opcode, loopRange, Default::loopRange); break; + // Wavetable oscillator + case hash("oscillator_phase"): + setValueFromOpcode(opcode, oscillatorPhase, Default::oscillatorPhaseRange); + break; + // Instrument settings: voice lifecycle case hash("group"): // fallthrough case hash("polyphony_group"): diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index fa5ea786..3ef284d0 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -226,6 +226,9 @@ struct Region { absl::optional loopMode {}; // loopmode Range loopRange { Default::loopRange }; //loopstart and loopend + // Wavetable oscillator + float oscillatorPhase { Default::oscillatorPhase }; + // Instrument settings: voice lifecycle uint32_t group { Default::group }; // group absl::optional offBy {}; // off_by diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index f0a39a34..743c00f7 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -58,6 +58,17 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value break; } waveOscillator.setWavetable(wave); + + float phase; + const float phaseParam = region->oscillatorPhase; + if (phaseParam >= 0) { + phase = phaseParam * (1.0f / 360.0f); + phase -= static_cast(phase); + } else { + std::uniform_real_distribution phaseDist { 0.0001f, 0.9999f }; + phase = phaseDist(Random::randomGenerator); + } + waveOscillator.setPhase(phase); } else { currentPromise = resources.filePool.getFilePromise(region->sample); if (currentPromise == nullptr) { diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 1e27853b..355b9fca 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -30,6 +30,12 @@ void WavetableOscillator::setWavetable(const WavetableMulti* wave) _multi = wave ? wave : &silenceMulti; } +void WavetableOscillator::setPhase(float phase) +{ + ASSERT(phase >= 0.0f && phase <= 1.0f); + _phase = phase; +} + void WavetableOscillator::process(float frequency, float* output, unsigned nframes) { float phase = _phase; diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index e6b8dff2..bdc715bc 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -37,6 +37,11 @@ public: */ void setWavetable(const WavetableMulti* wave); + /** + Set the current phase of this oscillator, between 0 and 1 excluded. + */ + void setPhase(float phase); + /** Compute a cycle of the oscillator, with constant frequency. */ diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 2e049658..f65ad406 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1392,6 +1392,19 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "effect3", "-50.65" }); REQUIRE(region.gainToEffect[3] == 0.0f); } + + SECTION("Wavetable phase") + { + REQUIRE(region.oscillatorPhase == 0.0f); + region.parseOpcode({ "oscillator_phase", "45" }); + REQUIRE(region.oscillatorPhase == 45.0f); + region.parseOpcode({ "oscillator_phase", "45.32" }); + REQUIRE(region.oscillatorPhase == 45.32_a); + region.parseOpcode({ "oscillator_phase", "-1" }); + REQUIRE(region.oscillatorPhase == -1.0f); + region.parseOpcode({ "oscillator_phase", "361" }); + REQUIRE(region.oscillatorPhase == 360.0f); + } } // Specific region bugs