mirror of
https://codeberg.org/armin/mindball.git
synced 2026-09-01 03:40:49 +02:00
init
This commit is contained in:
parent
1b541ba587
commit
78e52e612f
9 changed files with 1553 additions and 0 deletions
282
Source/DelayEngine.h
Normal file
282
Source/DelayEngine.h
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
#pragma once
|
||||
|
||||
#include "DelayLine.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace mindball
|
||||
{
|
||||
|
||||
class DelayEngine
|
||||
{
|
||||
public:
|
||||
enum Mode
|
||||
{
|
||||
normal = 0,
|
||||
invert,
|
||||
pingpong,
|
||||
superpong
|
||||
};
|
||||
|
||||
struct Params
|
||||
{
|
||||
float feedback = 0.35f;
|
||||
float loCutHz = 100.0f;
|
||||
float hiCutHz = 12000.0f;
|
||||
float dryWet = 0.4f;
|
||||
float timeMs = 300.0f;
|
||||
int timeSyncIndex = 0;
|
||||
int mode = normal;
|
||||
bool sync = false;
|
||||
bool doubleTap = false;
|
||||
bool center = false;
|
||||
bool autoPan = false;
|
||||
float panDepth = 1.0f;
|
||||
double bpm = 120.0;
|
||||
};
|
||||
|
||||
void prepare (double sampleRate, double maxDelaySeconds)
|
||||
{
|
||||
sr = static_cast<float> (sampleRate);
|
||||
dl.prepare (static_cast<float> (maxDelaySeconds * sampleRate));
|
||||
dr.prepare (static_cast<float> (maxDelaySeconds * sampleRate));
|
||||
clear();
|
||||
|
||||
sFeedback = 0.0f;
|
||||
sLoCut = 20.0f;
|
||||
sHiCut = 18000.0f;
|
||||
sDryWet = 0.0f;
|
||||
head1 = 1.0f;
|
||||
head2 = 1.0f;
|
||||
blend = 0.0f;
|
||||
lfoPhase = 0.0f;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
dl.clear();
|
||||
dr.clear();
|
||||
|
||||
lpL.reset(); hpL.reset();
|
||||
lpR.reset(); hpR.reset();
|
||||
|
||||
lfoPhase = 0.0f;
|
||||
}
|
||||
|
||||
void process (const float* inL, const float* inR,
|
||||
float* outL, float* outR,
|
||||
int numSamples, const Params& p,
|
||||
float* wetLOut = nullptr, float* wetROut = nullptr)
|
||||
{
|
||||
constexpr float pi = 3.14159265358979f;
|
||||
|
||||
const float kPar = 1.0f - std::exp (-1.0f / (0.02f * sr));
|
||||
|
||||
const float fadeInc = 1.0f / (0.012f * sr);
|
||||
|
||||
float targetTimeSamples;
|
||||
if (p.sync)
|
||||
{
|
||||
const int idx = std::clamp (p.timeSyncIndex, 0, numDivisions - 1);
|
||||
const double secondsPerBeat = 60.0 / std::max (1.0, p.bpm);
|
||||
targetTimeSamples = static_cast<float> (divisionBeats[static_cast<std::size_t> (idx)]
|
||||
* secondsPerBeat * sr);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetTimeSamples = p.timeMs * 0.001f * sr;
|
||||
}
|
||||
targetTimeSamples = std::max (2.0f, targetTimeSamples);
|
||||
|
||||
const float targetFeedback = p.feedback;
|
||||
const float targetLoCut = p.loCutHz;
|
||||
const float targetHiCut = p.hiCutHz;
|
||||
const float targetDryWet = p.dryWet;
|
||||
|
||||
const bool dbl = p.doubleTap;
|
||||
const bool ctr = p.center;
|
||||
const bool needB = dbl || (ctr && (p.mode == pingpong || p.mode == superpong));
|
||||
|
||||
const float lfoRate = p.autoPan ? (p.sync ? static_cast<float> (p.bpm / 240.0) : 0.25f) : 0.0f;
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
sFeedback += (targetFeedback - sFeedback) * kPar;
|
||||
sLoCut += (targetLoCut - sLoCut) * kPar;
|
||||
sHiCut += (targetHiCut - sHiCut) * kPar;
|
||||
sDryWet += (targetDryWet - sDryWet) * kPar;
|
||||
|
||||
// Delay time changes are done with two stationary read heads and a
|
||||
// short crossfade, never by gliding a read position. This avoids the
|
||||
// pitch-bend "sped up / slowed down" artifacts that come from a
|
||||
// moving read head. When the target moves, snap head2 to it and fade
|
||||
// between the two fixed heads; once blended, head2 takes over.
|
||||
if (blend == 0.0f && std::fabs (targetTimeSamples - head1) > 0.5f)
|
||||
head2 = targetTimeSamples;
|
||||
|
||||
if (blend < 1.0f)
|
||||
{
|
||||
blend += fadeInc;
|
||||
if (blend >= 1.0f)
|
||||
{
|
||||
head1 = head2;
|
||||
blend = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
lpL.setFc (sHiCut, sr); hpL.setFc (sLoCut, sr);
|
||||
lpR.setFc (sHiCut, sr); hpR.setFc (sLoCut, sr);
|
||||
|
||||
const float h1L = dl.read (head1);
|
||||
const float h2L = dl.read (head2);
|
||||
const float h1R = dr.read (head1);
|
||||
const float h2R = dr.read (head2);
|
||||
const float aL = h1L + (h2L - h1L) * blend;
|
||||
const float aR = h1R + (h2R - h1R) * blend;
|
||||
|
||||
float bL = 0.0f, bR = 0.0f;
|
||||
if (needB)
|
||||
{
|
||||
const float two1 = 2.0f * head1;
|
||||
const float two2 = 2.0f * head2;
|
||||
bL = dl.read (two1) + (dl.read (two2) - dl.read (two1)) * blend;
|
||||
bR = dr.read (two1) + (dr.read (two2) - dr.read (two1)) * blend;
|
||||
}
|
||||
|
||||
float fbL = 0.0f, fbR = 0.0f;
|
||||
float wetL = 0.0f, wetR = 0.0f;
|
||||
|
||||
switch (p.mode)
|
||||
{
|
||||
case normal:
|
||||
fbL = aL;
|
||||
fbR = aR;
|
||||
wetL = aL + bL;
|
||||
wetR = aR + bR;
|
||||
break;
|
||||
|
||||
case invert:
|
||||
fbL = aL;
|
||||
fbR = -aR;
|
||||
wetL = aL + bL;
|
||||
wetR = -(aR + bR);
|
||||
break;
|
||||
|
||||
case pingpong:
|
||||
fbL = aR;
|
||||
fbR = aL;
|
||||
wetL = aL + bL;
|
||||
wetR = aR + bR;
|
||||
break;
|
||||
|
||||
case superpong:
|
||||
{
|
||||
const float sumA = aL + aR;
|
||||
const float sumAB = sumA + (needB ? bL + bR : 0.0f);
|
||||
fbL = 0.5f * sumA;
|
||||
fbR = 0.5f * sumA;
|
||||
wetL = sumAB * 0.5f;
|
||||
wetR = sumAB * 0.5f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctr && (p.mode == pingpong || p.mode == superpong))
|
||||
{
|
||||
const float c = 0.5f * (aL + aR);
|
||||
wetL = c + (needB ? bL : 0.0f);
|
||||
wetR = c + (needB ? bR : 0.0f);
|
||||
}
|
||||
|
||||
if (p.autoPan)
|
||||
{
|
||||
const float pan = std::sin (2.0f * pi * lfoPhase);
|
||||
lfoPhase += lfoRate / sr;
|
||||
if (lfoPhase >= 1.0f)
|
||||
lfoPhase -= std::floor (lfoPhase);
|
||||
|
||||
const float s = std::clamp (p.panDepth, 0.0f, 1.0f);
|
||||
const float angle = (1.0f - s) * pi * 0.25f + s * (pan + 1.0f) * pi * 0.25f;
|
||||
const float gL = std::cos (angle);
|
||||
const float gR = std::sin (angle);
|
||||
wetL *= gL;
|
||||
wetR *= gR;
|
||||
}
|
||||
|
||||
const float g = sFeedback * (1.0f + 0.002f * std::pow (sFeedback, 100.0f));
|
||||
dl.write (inL[i] + softClip (lpL.processLP (hpL.processHP (fbL * g))));
|
||||
dr.write (inR[i] + softClip (lpR.processLP (hpR.processHP (fbR * g))));
|
||||
|
||||
const float w = sDryWet;
|
||||
float dryGain, wetGain;
|
||||
if (w <= 0.5f)
|
||||
{
|
||||
dryGain = 1.0f;
|
||||
wetGain = 2.0f * w;
|
||||
}
|
||||
else
|
||||
{
|
||||
dryGain = 2.0f * (1.0f - w);
|
||||
wetGain = 1.0f;
|
||||
}
|
||||
outL[i] = inL[i] * dryGain + wetL * wetGain;
|
||||
outR[i] = inR[i] * dryGain + wetR * wetGain;
|
||||
if (wetLOut != nullptr)
|
||||
wetLOut[i] = wetL * wetGain;
|
||||
if (wetROut != nullptr)
|
||||
wetROut[i] = wetR * wetGain;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static float softClip (float x)
|
||||
{
|
||||
const float ax = std::fabs (x);
|
||||
if (ax < 0.8f)
|
||||
return x;
|
||||
return std::copysign (0.8f + 0.2f * std::tanh ((ax - 0.8f) / 0.2f), x);
|
||||
}
|
||||
|
||||
struct OnePole
|
||||
{
|
||||
float alpha = 0.0f;
|
||||
float state = 0.0f;
|
||||
float xPrev = 0.0f;
|
||||
|
||||
void reset() { state = 0.0f; xPrev = 0.0f; }
|
||||
|
||||
void setFc (float fc, float sr)
|
||||
{
|
||||
constexpr float pi = 3.14159265358979f;
|
||||
alpha = 1.0f - std::exp (-2.0f * pi * fc / sr);
|
||||
}
|
||||
|
||||
float processLP (float x) { state += alpha * (x - state); return state; }
|
||||
float processHP (float x) { state = x - xPrev + (1.0f - alpha) * state; xPrev = x; return state; }
|
||||
};
|
||||
|
||||
static constexpr int numDivisions = 12;
|
||||
static constexpr std::array<float, numDivisions> divisionBeats = {
|
||||
1.0f / 16.0f, 1.0f / 8.0f, 1.0f / 4.0f, 1.0f / 3.0f, 1.0f / 2.0f, 3.0f / 4.0f,
|
||||
1.0f, 3.0f / 2.0f, 2.0f, 3.0f, 4.0f, 8.0f
|
||||
};
|
||||
|
||||
DelayLine dl, dr;
|
||||
OnePole lpL, hpL, lpR, hpR;
|
||||
|
||||
float sr = 48000.0f;
|
||||
float lfoPhase = 0.0f;
|
||||
|
||||
float sFeedback = 0.0f;
|
||||
float sLoCut = 0.0f;
|
||||
float sHiCut = 0.0f;
|
||||
float sDryWet = 0.0f;
|
||||
float head1 = 1.0f;
|
||||
float head2 = 1.0f;
|
||||
float blend = 0.0f;
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue