Skip to content

Commit

Permalink
Add EnvelopeADSR::Parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiashienzsch committed Feb 25, 2024
1 parent b693605 commit 3a01c50
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 28 deletions.
50 changes: 43 additions & 7 deletions lib/grit/audio/envelope/envelope_adsr.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <grit/unit/time.hpp>

#include <etl/algorithm.hpp>
#include <etl/cmath.hpp>
#include <etl/concepts.hpp>
Expand All @@ -17,15 +19,18 @@ struct EnvelopeADSR
{
using SampleType = Float;

constexpr EnvelopeADSR();
struct Parameter
{
Milliseconds<Float> attack{0};
Milliseconds<Float> decay{0};
Float sustain{1};
Milliseconds<Float> release{0};
};

constexpr auto setAttack(Float rate) -> void;
constexpr auto setDecay(Float rate) -> void;
constexpr auto setSustain(Float level) -> void;
constexpr auto setRelease(Float rate) -> void;
constexpr EnvelopeADSR();

constexpr auto setTargetRatioA(Float ratio) -> void;
constexpr auto setTargetRatioDr(Float ratio) -> void;
constexpr auto setParameter(Parameter const& parameter) -> void;
constexpr auto setSampleRate(Float sampleRate) -> void;

constexpr auto gate(bool isOn) -> void;
constexpr auto reset() -> void;
Expand All @@ -42,6 +47,14 @@ struct EnvelopeADSR
Release
};

constexpr auto setAttack(Float rate) -> void;
constexpr auto setDecay(Float rate) -> void;
constexpr auto setSustain(Float level) -> void;
constexpr auto setRelease(Float rate) -> void;

constexpr auto setTargetRatioA(Float ratio) -> void;
constexpr auto setTargetRatioDr(Float ratio) -> void;

static constexpr auto calcCoef(Float rate, Float targetRatio) -> Float
{
return etl::exp(-etl::log((Float(1) + targetRatio) / targetRatio) / rate);
Expand All @@ -66,6 +79,9 @@ struct EnvelopeADSR

Float _targetRatioA{};
Float _targetRatioDr{};

Parameter _parameter{};
Float _sampleRate{};
};

template<etl::floating_point Float>
Expand All @@ -81,6 +97,26 @@ constexpr EnvelopeADSR<Float>::EnvelopeADSR()
setTargetRatioDr(Float(0.0001));
}

template<etl::floating_point Float>
constexpr auto EnvelopeADSR<Float>::setParameter(Parameter const& parameter) -> void
{
_parameter = parameter;
setAttack(Seconds<Float>{parameter.attack}.count() * _sampleRate);
setDecay(Seconds<Float>{parameter.decay}.count() * _sampleRate);
setSustain(parameter.sustain);
setRelease(Seconds<Float>{parameter.release}.count() * _sampleRate);
setTargetRatioA(Float(0.3));
setTargetRatioDr(Float(0.0001));
}

template<etl::floating_point Float>
constexpr auto EnvelopeADSR<Float>::setSampleRate(Float sampleRate) -> void
{
_sampleRate = sampleRate;
setParameter(_parameter);
reset();
}

template<etl::floating_point Float>
constexpr auto EnvelopeADSR<Float>::setAttack(Float rate) -> void
{
Expand Down
43 changes: 26 additions & 17 deletions lib/grit/audio/envelope/envelope_adsr_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,50 @@
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>

TEMPLATE_TEST_CASE("audio/envelope: EnvelopeADSR", "", float, double)
TEMPLATE_TEST_CASE("audio/envelope: EnvelopeADSR", "", double)
{
using Float = TestType;

// TODO: Check why the error with float32 is so high
static constexpr auto tolerance = sizeof(Float) == 4 ? 1e-2 : 1e-6;

auto const advance = [](auto& env, auto count) {
auto const sampleRate = GENERATE(Float(24000), Float(48000), Float(96000));
auto const attack = GENERATE(as<grit::Milliseconds<Float>>{}, Float(125), Float(1000));
auto const sustain = GENERATE(Float(0.75), Float(0.9));

CAPTURE(sampleRate);
CAPTURE(attack.count());
CAPTURE(sustain);

auto const decay = attack;
auto const release = attack;

auto adsr = grit::EnvelopeADSR<Float>{};
adsr.setSampleRate(sampleRate);
adsr.setParameter({
.attack = attack,
.decay = decay,
.sustain = sustain,
.release = release,
});

auto const advance = [sampleRate](grit::EnvelopeADSR<Float>& env, grit::Seconds<Float> seconds) {
auto const count = static_cast<int>(seconds.count() * sampleRate);
for (auto i{0}; i < count - 1; ++i) {
[[maybe_unused]] auto out = env();
}
return env();
};

auto const attack = GENERATE(2000, 44'100, 96'000);
auto const sustain = GENERATE(Float(0.75), Float(0.9));

auto const decay = attack / 4;
auto const release = attack / 2;

auto adsr = grit::EnvelopeADSR<Float>{};
adsr.setAttack(Float(attack));
adsr.setDecay(Float(decay));
adsr.setSustain(sustain);
adsr.setRelease(Float(release));

for (auto i{0}; i < 100; ++i) {
CAPTURE(i);
REQUIRE_THAT(adsr(), Catch::Matchers::WithinAbs(Float(0), tolerance));
}

adsr.gate(true);
REQUIRE_THAT(advance(adsr, attack), Catch::Matchers::WithinAbs(Float(1), tolerance)); // attack
REQUIRE_THAT(advance(adsr, decay), Catch::Matchers::WithinAbs(sustain, tolerance)); // decay -> sustain
REQUIRE_THAT(advance(adsr, decay * 2), Catch::Matchers::WithinAbs(sustain, tolerance)); // sustain
REQUIRE_THAT(advance(adsr, attack), Catch::Matchers::WithinAbs(Float(1), tolerance)); // attack
REQUIRE_THAT(advance(adsr, decay), Catch::Matchers::WithinAbs(sustain, tolerance)); // decay -> sustain
REQUIRE_THAT(advance(adsr, decay), Catch::Matchers::WithinAbs(sustain, tolerance)); // sustain

adsr.gate(false);
REQUIRE_THAT(advance(adsr, release), Catch::Matchers::WithinAbs(Float(0), tolerance)); // release
Expand Down
13 changes: 9 additions & 4 deletions lib/grit/eurorack/kyma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace grit {
auto Kyma::prepare(float sampleRate, etl::size_t blockSize) -> void
{
_sampleRate = sampleRate;
_adsr.setSampleRate(sampleRate);
_oscillator.setSampleRate(sampleRate);
_subOscillator.setSampleRate(sampleRate);

Expand Down Expand Up @@ -48,6 +49,14 @@ auto Kyma::process(StereoBlock<float> const& buffer, ControlInput const& inputs)
auto const attack = grit::remap(attackKnob, 0.0F, 0.750F);
auto const release = grit::remap(releaseKnob, 0.0F, 2.5F);

_adsr.gate(inputs.gate);
_adsr.setParameter({
.attack = grit::Seconds<float>{attack},
.decay = grit::Seconds<float>{0.0F},
.sustain = 1.0F,
.release = grit::Seconds<float>{release},
});

// oscillator.setWavetable(SineWavetable);
// subOscillator.setWavetable(SineWavetable);
// oscillator.setShapeMorph(morph);
Expand All @@ -57,10 +66,6 @@ auto Kyma::process(StereoBlock<float> const& buffer, ControlInput const& inputs)
_oscillator.setFrequency(grit::noteToHertz(note));
_subOscillator.setFrequency(grit::noteToHertz(subNoteNumber));

_adsr.setAttack(attack * _sampleRate);
_adsr.setRelease(release * _sampleRate);
_adsr.gate(inputs.gate);

auto env = 0.0F;

for (size_t i = 0; i < buffer.extent(1); ++i) {
Expand Down

0 comments on commit 3a01c50

Please sign in to comment.