Skip to content

Commit

Permalink
Merge pull request #169 from OTTO-project/feature/delay-fx
Browse files Browse the repository at this point in the history
Delay effect
  • Loading branch information
jmidt authored May 10, 2020
2 parents 50c61a3 + a824205 commit abbb40d
Show file tree
Hide file tree
Showing 14 changed files with 692 additions and 7 deletions.
2 changes: 1 addition & 1 deletion boards/parts/audio/rtaudio/include/board/audio_driver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace otto::services {

private:
AlsaMixer(util::string_ref card, int subdevice_idx, util::string_ref elem_name, bool is_capture);
#ifdef __LINUX_ALSA__
#ifndef OTTO_USE_ALSAMIXER
::snd_mixer_t* snd_mixer_ = nullptr;
::snd_mixer_elem_t* snd_mixer_elem_ = nullptr;
long min_vol = 0;
Expand Down
4 changes: 2 additions & 2 deletions boards/parts/audio/rtaudio/src/audio_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace otto::services {

#ifndef __LINUX_ALSA__
#ifndef OTTO_USE_ALSAMIXER
AlsaMixer::AlsaMixer() = default;
AlsaMixer::~AlsaMixer() = default;

Expand Down Expand Up @@ -292,7 +292,7 @@ namespace otto::services {
return 0;
}

#ifndef __LINUX_ALSA__
#ifndef OTTO_USE_ALSAMIXER
void RTAudioAudioManager::line_in_gain_l(float gain) {}
void RTAudioAudioManager::line_in_gain_r(float) {}
void RTAudioAudioManager::output_vol(float) {}
Expand Down
2 changes: 1 addition & 1 deletion external/fmt
Submodule fmt updated 118 files
2 changes: 1 addition & 1 deletion external/nanovg
11 changes: 11 additions & 0 deletions src/core/engine/engine_dispatcher.inl
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,17 @@ namespace otto::core::engine {
ctx.stroke(i.color, i.line_width);
};
}
if (engine_name == "PingPong") {
return [](ui::IconData& i, nvg::Canvas& ctx) {
ctx.beginPath();
ctx.arc({0, i.size.h / 2.f}, i.size.h / 4.f, -M_PI_2, M_PI_2);
ctx.stroke(i.color, i.line_width);

ctx.beginPath();
ctx.arc({i.size.w / 2.f, i.size.h / 2.f}, i.size.h / 2.f, -M_PI_2, M_PI_2);
ctx.stroke(i.color, i.line_width);
};
}
// Default icon
return [](ui::IconData& i, nvg::Canvas& ctx) {
ctx.beginPath();
Expand Down
62 changes: 62 additions & 0 deletions src/engines/fx/pingpong/audio.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "audio.hpp"

#include "services/audio_manager.hpp"

namespace otto::engines::pingpong {

Audio::Audio() noexcept
{
for (auto& dl : delay_line) dl.maxDelay(6.f);
output_delay.maxDelay(spread_max);
input_filter.set_position(0.6f);
}

void Audio::action(itc::prop_change<&Props::delaytime>, float t) noexcept
{
for (auto& d : delay_line) d.delay(t);
}

void Audio::action(itc::prop_change<&Props::feedback>, float f) noexcept
{
feedback_ = f * 1.5;
}

void Audio::action(itc::prop_change<&Props::filter>, float f) noexcept
{
input_filter.set_position(f);
for (auto& lf : loop_filter) lf.set_position(f);
}

void Audio::action(itc::prop_change<&Props::stereo>, float s) noexcept
{
spread_ = std::max(1.f - 2.f * s, 0.f);
spread_ *= spread_;
output_delay.delaySamplesR(1.f + spread_ * spread_max_samples);
pingpong_ = std::max(0.f, 2.f * s - 1.f);
}

void Audio::action(itc::prop_change<&Props::stereo_invert>, bool inv) noexcept
{
invert_ = inv;
}

audio::ProcessData<2> Audio::process(audio::ProcessData<1> data) noexcept
{
auto buf = services::AudioManager::current().buffer_pool().allocate_multi<2>();
for (auto&& [dat, bufL, bufR] : util::zip(data.audio, buf[0], buf[1])) {
auto in = input_filter(dat); // Filter
// Read next value from first delay line - don't input new one yet
auto out0 = delay_line[0]();
auto out1 = delay_line[1](feedback_ * loop_filter[0](out0));
delay_line[0](in + feedback_ * loop_filter[1](out1));

// Transform outputs to left/right signals
bufL = output_delay(out0 + (1.f - pingpong_) * out1);
bufR = (1.f - pingpong_) * out0 + out1;

if (invert_) std::swap(bufL, bufR);
}
return data.with(buf);
}

} // namespace otto::engines::pingpong
43 changes: 43 additions & 0 deletions src/engines/fx/pingpong/audio.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include "Gamma/Delay.h"
#include "pingpong.hpp"
#include "services/application.hpp"
#include "util/dsp/dj_filter.hpp"

namespace otto::engines::pingpong {

using namespace core;

struct Audio {
Audio() noexcept;
audio::ProcessData<2> process(audio::ProcessData<1>) noexcept;


void action(itc::prop_change<&Props::filter>, float f) noexcept;

void action(itc::prop_change<&Props::stereo>, float s) noexcept;
void action(itc::prop_change<&Props::stereo_invert>, bool inv) noexcept;

void action(itc::prop_change<&Props::delaytime>, float t) noexcept;

void action(itc::prop_change<&Props::feedback>, float f) noexcept;

private:
/// Feeds into each other
std::array<gam::Delay<>, 2> delay_line;
/// Gets output of delay line 0. Used for stereo spread.
gam::Delay<> output_delay;
/// Filters
DJFilter<gam::Biquad<>> input_filter{400, 10000};
std::array<DJFilter<gam::OnePole<>>, 2> loop_filter = {{{300, 15000}, {300, 15000}}};


float feedback_ = 0.5;
float pingpong_ = 0;
float spread_ = 0;
const float spread_max = 0.100f;
const float spread_max_samples = spread_max * gam::sampleRate() - 2.f;
bool invert_ = false;
};
} // namespace otto::engines::pingpong
88 changes: 88 additions & 0 deletions src/engines/fx/pingpong/pingpong.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "pingpong.hpp"

#include "core/service.hpp"
#include "services/application.hpp"
#include "services/audio_manager.hpp"
#include "services/clock_manager.hpp"
#include "services/log_manager.hpp"
#include "services/ui_manager.hpp"
#include "util/math.hpp"

namespace otto::engines::pingpong {

PingPong::PingPong()
: audio(std::make_unique<Audio>()), screen_(std::make_unique<Screen>()), props{{*audio, *screen_}}
{
props.timetype.on_change()
.connect([this](bool tt) {
props.delaytime.set(calculate_delaytime(props.timetype, props.free_time, props.subdivision));
})
.call_now();
}

using namespace core::input;

bool PingPong::keypress(Key key)
{
switch (key) {
case Key::blue_click: props.timetype = !props.timetype; break;
case Key::red_click: props.stereo_invert = !props.stereo_invert; break;
default: return false; ;
}
return true;
}

void PingPong::encoder(EncoderEvent ev)
{
switch (ev.encoder) {
case Encoder::blue: {
if (props.timetype) {
props.subdivision.step(util::math::sgn(ev.steps));
} else {
props.free_time.step(ev.steps);
}
props.delaytime.set(calculate_delaytime(props.timetype, props.free_time, props.subdivision));
break;
}
case Encoder::green: props.feedback.step(ev.steps); break;
case Encoder::yellow: props.filter.step(ev.steps); break;
case Encoder::red: props.stereo.step(ev.steps); break;
}
}

core::ui::ScreenAndInput PingPong::screen()
{
return {*screen_, *this};
}

// TODO: This will NOT update when the bpm changes. It should do that.
float PingPong::calculate_delaytime(bool type, float free, SubdivisionEnum sd)
{
if (type) {
// Using subdivisions. Result is sec. per bar (whole note)
auto res = 240.f / services::ClockManager::current().bpm();
// Identification of the subdivisions
switch (sd) {
case SubdivisionEnum::thirtysecondth: res *= 1.f/32.f; break;
case SubdivisionEnum::sixteenth: res *= 1.f/16.f; break;
case SubdivisionEnum::eighthtriplet: res *= 1.f/12.f; break;
case SubdivisionEnum::eighth: res *= 1.f/8.f; break;
case SubdivisionEnum::doteighth: res *= 3.f/16.f; break;
case SubdivisionEnum::quartertriplet: res *= 1.f / 6.f; break;
case SubdivisionEnum::quarter: res *= 1.f/4.f; break;
case SubdivisionEnum::dotquarter: res *= 3.f/8.f; break;
case SubdivisionEnum::halftriplet: res *= 1.f/3.f; break;
case SubdivisionEnum::half: res *= 1.f/2.f; break;
case SubdivisionEnum::dothalf: res *= 3.f/4.f; break;
case SubdivisionEnum::whole: break;
default: OTTO_UNREACHABLE;
}
return res;
} else {
// Using free time control
auto fff = free * free * free;
return 0.05f * (1.f - fff) + 6.f * fff;
}
}

} // namespace otto::engines::pingpong
65 changes: 65 additions & 0 deletions src/engines/fx/pingpong/pingpong.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

#include "core/audio/clock.hpp"
#include "core/engine/engine.hpp"
#include "core/input.hpp"
#include "itc/itc.hpp"

namespace otto::engines::pingpong {

using namespace core::props;

struct Screen;
struct Audio;

using Sender = core::engine::EngineSender<Audio, Screen>;

BETTER_ENUM(SubdivisionEnum, std::int8_t, thirtysecondth, sixteenth, eighthtriplet, eighth, quartertriplet, doteighth, quarter, halftriplet, dotquarter, half, dothalf, whole);

struct Props {
Sender sender;

/// Changes between free delay time and subdivisions
Sender::Prop<struct timetype_tag, bool> timetype = {sender, false};
/// Only changed when timetype is false.
Sender::Prop<struct free_time_tag, float> free_time = {sender, 0, limits(0, 1), step_size(0.01)};
/// Only changed when timetype is true
Sender::Prop<struct subdivision_tag, SubdivisionEnum> subdivision = {sender, SubdivisionEnum::quarter};
/// Actual delaytime in ms. Sent to the audio thread
///
/// 6 seconds is the length of a measure at 40 bpm
Sender::Prop<struct delaytime_tag, float> delaytime = {sender, 0.5, limits(0, 6)};

/// 0.5 is neutral
Sender::Prop<struct filter_tag, float> filter = {sender, 0.5, limits(0, 1), step_size(0.01)};

/// 0.5 is neutral
Sender::Prop<struct stereo_tag, float> stereo = {sender, 0.5, limits(0, 1), step_size(0.01)};
Sender::Prop<struct stereo_invert_tag, bool> stereo_invert = {sender, false};

Sender::Prop<struct feedback_tag, float> feedback = {sender, 0.2, limits(0, 0.99), step_size(0.01)};

DECL_REFLECTION(Props, free_time, delaytime, subdivision, timetype, filter, stereo, stereo_invert, feedback);
};

struct PingPong : core::engine::EffectEngine<PingPong> {
static constexpr util::string_ref name = "PingPong";
PingPong();

float calculate_delaytime(bool type, float free, SubdivisionEnum sd);

bool keypress(core::input::Key k) override;
void encoder(core::input::EncoderEvent e) override;

core::ui::ScreenAndInput screen() override;

std::unique_ptr<Audio> audio;
std::unique_ptr<Screen> screen_;

Props props;
};

} // namespace otto::engines::pingpong

#include "audio.hpp"
#include "screen.hpp"
Loading

0 comments on commit abbb40d

Please sign in to comment.