Skip to content

Commit

Permalink
Add grit/eurorack/kyma
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiashienzsch committed Feb 20, 2024
1 parent d8f1a5a commit 8f49963
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 95 deletions.
2 changes: 2 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ target_sources(gritwave-eurorack

"grit/eurorack/ares.cpp"
"grit/eurorack/ares.hpp"
"grit/eurorack/kyma.cpp"
"grit/eurorack/kyma.hpp"
"grit/eurorack/poseidon.cpp"
"grit/eurorack/poseidon.hpp"
)
82 changes: 82 additions & 0 deletions lib/grit/eurorack/kyma.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include "kyma.hpp"

#include <grit/audio/music/note.hpp>
#include <grit/math/remap.hpp>
#include <grit/unit/decibel.hpp>

namespace grit {

auto Kyma::prepare(float sampleRate, etl::size_t blockSize) -> void
{
_sampleRate = sampleRate;
oscillator.setSampleRate(sampleRate);
subOscillator.setSampleRate(sampleRate);

auto const blockRate = sampleRate / static_cast<float>(blockSize);
_pitchKnob.setSampleRate(blockRate);
_morphKnob.setSampleRate(blockRate);
_attackKnob.setSampleRate(blockRate);
_releaseKnob.setSampleRate(blockRate);
_vOctCV.setSampleRate(blockRate);
_morphCV.setSampleRate(blockRate);
_subGainCV.setSampleRate(blockRate);
_subMorphCV.setSampleRate(blockRate);
}

auto Kyma::process(StereoBlock<float> const& buffer, ControlInput const& inputs) -> float
{
auto const pitchKnob = _pitchKnob(inputs.pitchKnob);
auto const attackKnob = _morphKnob(inputs.morphKnob);
auto const morphKnob = _attackKnob(inputs.attackKnob);
auto const releaseKnob = _releaseKnob(inputs.releaseKnob);

auto const vOctCv = _vOctCV(inputs.vOctCV);
auto const morphCv = _morphCV(inputs.morphCV);
auto const subGainCv = _subGainCV(inputs.subGainCV);
auto const subMorphCv = _subMorphCV(inputs.subMorphCV);

auto const pitch = grit::remap(pitchKnob, 36.0F, 96.0F);
auto const voltsPerOctave = grit::remap(vOctCv, 0.0F, 60.0F);
auto const note = etl::clamp(pitch + voltsPerOctave, 0.0F, 127.0F);
auto const morph = etl::clamp(morphKnob + morphCv, 0.0F, 1.0F);

auto const subOffset = inputs.subShift ? 12.0F : 24.0F;
auto const subNoteNumber = etl::clamp(note - subOffset, 0.0F, 127.0F);
auto const subMorph = etl::clamp(subMorphCv, 0.0F, 1.0F);
auto const subGain = grit::remap(subGainCv, 0.0F, 1.0F);

auto const attack = grit::remap(attackKnob, 0.0F, 0.750F);
auto const release = grit::remap(releaseKnob, 0.0F, 2.5F);

// oscillator.setWavetable(SineWavetable);
// subOscillator.setWavetable(SineWavetable);
// oscillator.setShapeMorph(morph);
// subOscillator.setShapeMorph(subMorph);
etl::ignore_unused(subMorph, morph);

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) {
auto const fmModulator = buffer(0, i);
auto const fmAmount = buffer(1, i);
oscillator.addPhaseOffset(fmModulator * fmAmount);
env = adsr();

auto const osc = oscillator() * env;
auto const sub = subOscillator() * env * subGain;

buffer(0, i) = sub * 0.75F;
buffer(1, i) = osc * 0.75F;
}

return env;
}

} // namespace grit
54 changes: 54 additions & 0 deletions lib/grit/eurorack/kyma.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once

#include <grit/audio/envelope/envelope_adsr.hpp>
#include <grit/audio/filter/dynamic_smoothing.hpp>
#include <grit/audio/oscillator/variable_shape_oscillator.hpp>
#include <grit/audio/oscillator/wavetable_oscillator.hpp>
#include <grit/audio/stereo/stereo_block.hpp>

namespace grit {

struct Kyma
{
struct ControlInput
{
float pitchKnob{0};
float morphKnob{0};
float attackKnob{0};
float releaseKnob{0};

float vOctCV{0};
float morphCV{0};
float subGainCV{0};
float subMorphCV{0};

bool gate{false};
bool subShift{false};
};

Kyma() = default;

auto prepare(float sampleRate, etl::size_t blockSize) -> void;
auto process(StereoBlock<float> const& buffer, ControlInput const& inputs) -> float;

private:
static constexpr auto sine = makeSineWavetable<float, 2048>();
static constexpr auto wavetable = etl::mdspan{sine.data(), etl::extents<etl::size_t, sine.size()>{}};

float _sampleRate{};

DynamicSmoothing<float> _pitchKnob{};
DynamicSmoothing<float> _morphKnob{};
DynamicSmoothing<float> _attackKnob{};
DynamicSmoothing<float> _releaseKnob{};
DynamicSmoothing<float> _vOctCV{};
DynamicSmoothing<float> _morphCV{};
DynamicSmoothing<float> _subGainCV{};
DynamicSmoothing<float> _subMorphCV{};

EnvelopeADSR adsr{};
WavetableOscillator<float, sine.size()> oscillator{wavetable};
WavetableOscillator<float, sine.size()> subOscillator{wavetable};
};

} // namespace grit
135 changes: 40 additions & 95 deletions src/kyma/main.cpp
Original file line number Diff line number Diff line change
@@ -1,115 +1,60 @@
#include <grit/audio/delay/static_delay_line.hpp>
#include <grit/audio/envelope/envelope_adsr.hpp>
#include <grit/audio/filter/dynamic_smoothing.hpp>
#include <grit/audio/music/note.hpp>
#include <grit/audio/oscillator/variable_shape_oscillator.hpp>
#include <grit/audio/oscillator/wavetable_oscillator.hpp>
#include <grit/math/remap.hpp>
#include <grit/unit/decibel.hpp>
#include <grit/eurorack/kyma.hpp>

#include <daisy_patch_sm.h>

namespace kyma {
static constexpr auto blockSize = 16U;
static constexpr auto sampleRate = 96'000.0F;
static constexpr auto wavetableSine = grit::makeSineWavetable<float, 2048>();
static constexpr auto sineWavetable = etl::mdspan{
wavetableSine.data(),
etl::extents<etl::size_t, wavetableSine.size()>{},
};

auto subOctaveToggle = daisy::Switch{};
auto envTriggerButton = daisy::Switch{};
auto patch = daisy::patch_sm::DaisyPatchSM{};
auto& envelopeGate = patch.gate_in_1;
static constexpr auto blockSize = 16U;
static constexpr auto sampleRate = 96'000.0F;

auto adsr = grit::EnvelopeADSR{};
auto oscillator = grit::WavetableOscillator<float, wavetableSine.size()>{sineWavetable};
auto subOscillator = grit::WavetableOscillator<float, wavetableSine.size()>{sineWavetable};
auto patch = daisy::patch_sm::DaisyPatchSM{};
auto toggle = daisy::Switch{};
auto button = daisy::Switch{};
auto processor = grit::Kyma{};

// auto smoothE = grit::DynamicSmoothing<float, grit::DynamicSmoothingType::Efficient>{};
// auto smoothA = grit::DynamicSmoothing<float, grit::DynamicSmoothingType::Accurate>{};
// auto delayN = grit::StaticDelayLine<float, 32, grit::BufferInterpolation::None>{};
// auto delayL = grit::StaticDelayLine<float, 32, grit::BufferInterpolation::Linear>{};
// auto delayH = grit::StaticDelayLine<float, 32, grit::BufferInterpolation::Hermite>{};

auto audioCallback(daisy::AudioHandle::InputBuffer in, daisy::AudioHandle::OutputBuffer out, size_t size) -> void
auto audioCallback(
daisy::AudioHandle::InterleavingInputBuffer in,
daisy::AudioHandle::InterleavingOutputBuffer out,
size_t size
) -> void
{
patch.ProcessAllControls();

auto const pitchKnob = patch.GetAdcValue(daisy::patch_sm::CV_1);
auto const attackKnob = patch.GetAdcValue(daisy::patch_sm::CV_2);
auto const morphKnob = patch.GetAdcValue(daisy::patch_sm::CV_3);
auto const releaseKnob = patch.GetAdcValue(daisy::patch_sm::CV_4);

auto const vOctCv = patch.GetAdcValue(daisy::patch_sm::CV_5);
auto const morphCv = patch.GetAdcValue(daisy::patch_sm::CV_6);
auto const subGainCv = patch.GetAdcValue(daisy::patch_sm::CV_7);
auto const subMorphCv = patch.GetAdcValue(daisy::patch_sm::CV_8);

auto const pitch = grit::remap(pitchKnob, 36.0F, 96.0F);
auto const voltsPerOctave = grit::remap(vOctCv, 0.0F, 60.0F);
auto const note = etl::clamp(pitch + voltsPerOctave, 0.0F, 127.0F);
auto const morph = etl::clamp(morphKnob + morphCv, 0.0F, 1.0F);

auto const subOffset = subOctaveToggle.Pressed() ? 12.0F : 24.0F;
auto const subNoteNumber = etl::clamp(note - subOffset, 0.0F, 127.0F);
auto const subMorph = etl::clamp(subMorphCv, 0.0F, 1.0F);
auto const subGain = grit::remap(subGainCv, 0.0F, 1.0F);

auto const attack = grit::remap(attackKnob, 0.0F, 0.750F);
auto const release = grit::remap(releaseKnob, 0.0F, 2.5F);

// oscillator.setWavetable(SineWavetable);
// subOscillator.setWavetable(SineWavetable);
// oscillator.setShapeMorph(morph);
// subOscillator.setShapeMorph(subMorph);
etl::ignore_unused(subMorph, morph);

oscillator.setFrequency(grit::noteToHertz(note));
subOscillator.setFrequency(grit::noteToHertz(subNoteNumber));

adsr.setAttack(attack * sampleRate);
adsr.setRelease(release * sampleRate);
adsr.gate(envelopeGate.State() or envTriggerButton.Pressed());

for (size_t i = 0; i < size; ++i) {
auto const fmModulator = IN_L[i];
auto const fmAmount = IN_R[i];
oscillator.addPhaseOffset(fmModulator * fmAmount);

auto const env = adsr();
patch.WriteCvOut(daisy::patch_sm::CV_OUT_1, env * 5.0F);
patch.WriteCvOut(daisy::patch_sm::CV_OUT_2, env * 5.0F);

auto const osc = oscillator() * env;
auto const sub = subOscillator() * env * subGain;

OUT_L[i] = sub * 0.75F;
OUT_R[i] = osc * 0.75F;
}
toggle.Debounce();
button.Debounce();
patch.SetLed(not toggle.Pressed());

auto const controls = grit::Kyma::ControlInput{
.pitchKnob = patch.GetAdcValue(daisy::patch_sm::CV_1),
.morphKnob = patch.GetAdcValue(daisy::patch_sm::CV_3),
.attackKnob = patch.GetAdcValue(daisy::patch_sm::CV_2),
.releaseKnob = patch.GetAdcValue(daisy::patch_sm::CV_4),
.vOctCV = patch.GetAdcValue(daisy::patch_sm::CV_5),
.morphCV = patch.GetAdcValue(daisy::patch_sm::CV_6),
.subGainCV = patch.GetAdcValue(daisy::patch_sm::CV_7),
.subMorphCV = patch.GetAdcValue(daisy::patch_sm::CV_8),
.gate = patch.gate_in_1.State() or button.Pressed(),
.subShift = toggle.Pressed(),
};

auto const block = grit::StereoBlock<float>{out, size};
auto const env = processor.process(block, controls);
patch.WriteCvOut(daisy::patch_sm::CV_OUT_2, env * 5.0F);
}

} // namespace kyma

auto main() -> int
{
using namespace kyma;
kyma::patch.Init();

patch.Init();
patch.SetAudioSampleRate(sampleRate);
patch.SetAudioBlockSize(blockSize);
patch.StartAudio(audioCallback);
kyma::toggle.Init(daisy::patch_sm::DaisyPatchSM::B8);
kyma::button.Init(daisy::patch_sm::DaisyPatchSM::B7);

subOctaveToggle.Init(daisy::patch_sm::DaisyPatchSM::B8);
envTriggerButton.Init(daisy::patch_sm::DaisyPatchSM::B7);
kyma::processor.prepare(kyma::sampleRate, kyma::blockSize);

oscillator.setSampleRate(sampleRate);
subOscillator.setSampleRate(sampleRate);
kyma::patch.SetAudioSampleRate(kyma::sampleRate);
kyma::patch.SetAudioBlockSize(kyma::blockSize);
kyma::patch.StartAudio(kyma::audioCallback);

while (true) {
subOctaveToggle.Debounce();
envTriggerButton.Debounce();
patch.SetLed(not subOctaveToggle.Pressed());
}
while (true) {}
}

0 comments on commit 8f49963

Please sign in to comment.