Initial commit

main
Rich Heslip 4 years ago
parent ec5515c91a
commit ee76bf2e32
  1. 157
      DaisySP/Control/adenv.cpp
  2. 92
      DaisySP/Control/adenv.h
  3. 82
      DaisySP/Control/adsr.cpp
  4. 77
      DaisySP/Control/adsr.h
  5. 41
      DaisySP/Control/line.cpp
  6. 39
      DaisySP/Control/line.h
  7. 27
      DaisySP/Control/phasor.cpp
  8. 63
      DaisySP/Control/phasor.h
  9. 192
      DaisySP/Drums/analogbassdrum.cpp
  10. 105
      DaisySP/Drums/analogbassdrum.h
  11. 219
      DaisySP/Drums/analogsnaredrum.cpp
  12. 98
      DaisySP/Drums/analogsnaredrum.h
  13. 96
      DaisySP/Drums/hihat.cpp
  14. 269
      DaisySP/Drums/hihat.h
  15. 231
      DaisySP/Drums/synthbassdrum.cpp
  16. 179
      DaisySP/Drums/synthbassdrum.h
  17. 201
      DaisySP/Drums/synthsnaredrum.cpp
  18. 97
      DaisySP/Drums/synthsnaredrum.h
  19. 56
      DaisySP/Dynamics/balance.cpp
  20. 47
      DaisySP/Dynamics/balance.h
  21. 76
      DaisySP/Dynamics/compressor.cpp
  22. 199
      DaisySP/Dynamics/compressor.h
  23. 37
      DaisySP/Dynamics/crossfade.cpp
  24. 78
      DaisySP/Dynamics/crossfade.h
  25. 29
      DaisySP/Dynamics/limiter.cpp
  26. 33
      DaisySP/Dynamics/limiter.h
  27. 65
      DaisySP/Effects/autowah.cpp
  28. 52
      DaisySP/Effects/autowah.h
  29. 34
      DaisySP/Effects/bitcrush.cpp
  30. 47
      DaisySP/Effects/bitcrush.h
  31. 187
      DaisySP/Effects/chorus.cpp
  32. 182
      DaisySP/Effects/chorus.h
  33. 32
      DaisySP/Effects/decimator.cpp
  34. 69
      DaisySP/Effects/decimator.h
  35. 83
      DaisySP/Effects/flanger.cpp
  36. 75
      DaisySP/Effects/flanger.h
  37. 31
      DaisySP/Effects/fold.cpp
  38. 44
      DaisySP/Effects/fold.h
  39. 32
      DaisySP/Effects/overdrive.cpp
  40. 46
      DaisySP/Effects/overdrive.h
  41. 138
      DaisySP/Effects/phaser.cpp
  42. 131
      DaisySP/Effects/phaser.h
  43. 211
      DaisySP/Effects/pitchshifter.h
  44. 283
      DaisySP/Effects/reverbsc.cpp
  45. 75
      DaisySP/Effects/reverbsc.h
  46. 43
      DaisySP/Effects/sampleratereducer.cpp
  47. 48
      DaisySP/Effects/sampleratereducer.h
  48. 36
      DaisySP/Effects/tremolo.cpp
  49. 60
      DaisySP/Effects/tremolo.h
  50. 42
      DaisySP/Filters/allpass.cpp
  51. 60
      DaisySP/Filters/allpass.h
  52. 32
      DaisySP/Filters/atone.cpp
  53. 55
      DaisySP/Filters/atone.h
  54. 59
      DaisySP/Filters/biquad.cpp
  55. 61
      DaisySP/Filters/biquad.h
  56. 62
      DaisySP/Filters/comb.cpp
  57. 61
      DaisySP/Filters/comb.h
  58. 343
      DaisySP/Filters/fir.h
  59. 89
      DaisySP/Filters/mode.cpp
  60. 53
      DaisySP/Filters/mode.h
  61. 105
      DaisySP/Filters/moogladder.cpp
  62. 50
      DaisySP/Filters/moogladder.h
  63. 95
      DaisySP/Filters/nlfilt.cpp
  64. 80
      DaisySP/Filters/nlfilt.h
  65. 86
      DaisySP/Filters/svf.cpp
  66. 79
      DaisySP/Filters/svf.h
  67. 34
      DaisySP/Filters/tone.cpp
  68. 49
      DaisySP/Filters/tone.h
  69. 59
      DaisySP/Noise/clockednoise.cpp
  70. 58
      DaisySP/Noise/clockednoise.h
  71. 54
      DaisySP/Noise/dust.h
  72. 82
      DaisySP/Noise/fractal_noise.h
  73. 146
      DaisySP/Noise/grainlet.cpp
  74. 84
      DaisySP/Noise/grainlet.h
  75. 95
      DaisySP/Noise/particle.cpp
  76. 92
      DaisySP/Noise/particle.h
  77. 43
      DaisySP/Noise/whitenoise.h
  78. 197
      DaisySP/PhysicalModeling/KarplusString.cpp
  79. 103
      DaisySP/PhysicalModeling/KarplusString.h
  80. 90
      DaisySP/PhysicalModeling/PolyPluck.h
  81. 213
      DaisySP/PhysicalModeling/drip.cpp
  82. 53
      DaisySP/PhysicalModeling/drip.h
  83. 109
      DaisySP/PhysicalModeling/modalvoice.cpp
  84. 91
      DaisySP/PhysicalModeling/modalvoice.h
  85. 122
      DaisySP/PhysicalModeling/pluck.cpp
  86. 100
      DaisySP/PhysicalModeling/pluck.h
  87. 158
      DaisySP/PhysicalModeling/resonator.cpp
  88. 183
      DaisySP/PhysicalModeling/resonator.h
  89. 124
      DaisySP/PhysicalModeling/stringvoice.cpp
  90. 94
      DaisySP/PhysicalModeling/stringvoice.h
  91. 140
      DaisySP/Synthesis/blosc.cpp
  92. 76
      DaisySP/Synthesis/blosc.h
  93. 65
      DaisySP/Synthesis/fm2.cpp
  94. 65
      DaisySP/Synthesis/fm2.h
  95. 96
      DaisySP/Synthesis/formantosc.cpp
  96. 73
      DaisySP/Synthesis/formantosc.h
  97. 166
      DaisySP/Synthesis/harmonic_osc.h
  98. 86
      DaisySP/Synthesis/oscillator.cpp
  99. 113
      DaisySP/Synthesis/oscillator.h
  100. 147
      DaisySP/Synthesis/oscillatorbank.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,157 @@
#include <math.h>
#include "adenv.h"
using namespace daisysp;
//#define EXPF expf
// This causes with infinity with certain curves,
// which then causes NaN erros...
#define EXPF expf_fast
// To resolve annoying bugs when using this you can:
// if (val != val)
// val = 0.0f; // This will un-NaN the value.
// Fast Exp approximation
// 8x multiply version
//inline float expf_fast(float x)
//{
// x = 1.0f + x / 256.0f;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// return x;
//}
// 10x multiply version
inline float expf_fast(float x)
{
x = 1.0f + x / 1024.0f;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
return x;
}
// Private Functions
void AdEnv::Init(float sample_rate)
{
sample_rate_ = sample_rate;
current_segment_ = ADENV_SEG_IDLE;
curve_scalar_ = 0.0f; // full linear
phase_ = 0;
min_ = 0.0f;
max_ = 1.0f;
output_ = 0.0001f;
for(uint8_t i = 0; i < ADENV_SEG_LAST; i++)
{
segment_time_[i] = 0.05f;
}
}
float AdEnv::Process()
{
uint32_t time_samps;
float val, out, end, beg, inc;
// Handle Retriggering
if(trigger_)
{
trigger_ = 0;
current_segment_ = ADENV_SEG_ATTACK;
phase_ = 0;
curve_x_ = 0.0f;
retrig_val_ = output_;
}
time_samps = (uint32_t)(segment_time_[current_segment_] * sample_rate_);
// Fixed for now, but we could always make this a more flexible multi-segment envelope
switch(current_segment_)
{
case ADENV_SEG_ATTACK:
beg = retrig_val_;
end = 1.0f;
break;
case ADENV_SEG_DECAY:
beg = 1.0f;
end = 0.0f;
break;
case ADENV_SEG_IDLE:
default:
beg = 0;
end = 0;
break;
}
if(prev_segment_ != current_segment_)
{
//Reset at segment beginning
curve_x_ = 0;
phase_ = 0;
}
//recalculate increment value
if(curve_scalar_ == 0.0f)
{
c_inc_ = (end - beg) / time_samps;
}
else
{
c_inc_ = (end - beg) / (1.0f - EXPF(curve_scalar_));
}
// update output
val = output_;
inc = c_inc_;
out = val;
if(curve_scalar_ == 0.0f)
{
val += inc;
}
else
{
curve_x_ += (curve_scalar_ / time_samps);
val = beg + inc * (1.0f - EXPF(curve_x_));
if(val != val)
val = 0.0f; // NaN check
}
// Update Segment
phase_ += 1;
prev_segment_ = current_segment_;
if(current_segment_ != ADENV_SEG_IDLE)
{
if((out >= 1.f && current_segment_ == ADENV_SEG_ATTACK)
|| (out <= 0.f && current_segment_ == ADENV_SEG_DECAY))
{
// Advance segment
current_segment_++;
// TODO: Add Cycling feature here.
if(current_segment_ > ADENV_SEG_DECAY)
{
current_segment_ = ADENV_SEG_IDLE;
}
}
}
if(current_segment_ == ADENV_SEG_IDLE)
{
val = out = 0.0f;
}
output_ = val;
return out * (max_ - min_) + min_;
}

@ -0,0 +1,92 @@
#pragma once
#ifndef ADENV_H
#define ADENV_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Distinct stages that the phase of the envelope can be located in.
@see AdEnv
*/
enum AdEnvSegment
{
/** located at phase location 0, and not currently running */
ADENV_SEG_IDLE,
/** First segment of envelope where phase moves from MIN value to MAX value */
ADENV_SEG_ATTACK,
/** Second segment of envelope where phase moves from MAX to MIN value */
ADENV_SEG_DECAY,
/** The final segment of the envelope (currently decay) */
ADENV_SEG_LAST,
};
/** Trigger-able envelope with adjustable min/max, and independent per-segment time control.
\author shensley
\todo - Add Cycling
\todo - Implement Curve (its only linear for now).
\todo - Maybe make this an ADsr_ that has AD/AR/Asr_ modes.
*/
class AdEnv
{
public:
AdEnv() {}
~AdEnv() {}
/** Initializes the ad envelope.
Defaults:
- current segment = idle
- curve = linear
- phase = 0
- min = 0
- max = 1
\param sample_rate sample rate of the audio engine being run
*/
void Init(float sample_rate);
/** Processes the current sample of the envelope. This should be called once
per sample period.
\return the current envelope value.
*/
float Process();
/** Starts or retriggers the envelope.*/
inline void Trigger() { trigger_ = 1; }
/** Sets the length of time (in seconds) for a specific segment. */
inline void SetTime(uint8_t seg, float time) { segment_time_[seg] = time; }
/** Sets the amount of curve applied. A positve value will create a log
curve. Input range: -100 to 100. (or more)
*/
inline void SetCurve(float scalar) { curve_scalar_ = scalar; }
/** Sets the minimum value of the envelope output.
Input range: -FLTmax_, to FLTmax_
*/
inline void SetMin(float min) { min_ = min; }
/** Sets the maximum value of the envelope output.
Input range: -FLTmax_, to FLTmax_
*/
inline void SetMax(float max) { max_ = max; }
/** Returns the current output value without processing the next sample */
inline float GetValue() const { return (output_ * (max_ - min_)) + min_; }
/** Returns the segment of the envelope that the phase is currently located
in.
*/
inline uint8_t GetCurrentSegment() { return current_segment_; }
/** Returns true if the envelope is currently in any stage apart from idle.
*/
inline bool IsRunning() const { return current_segment_ != ADENV_SEG_IDLE; }
private:
uint8_t current_segment_, prev_segment_;
float segment_time_[ADENV_SEG_LAST];
float sample_rate_, min_, max_, output_, curve_scalar_;
float c_inc_, curve_x_, retrig_val_;
uint32_t phase_;
uint8_t trigger_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,82 @@
#include "adsr.h"
#include <math.h>
using namespace daisysp;
float Adsr::Tau2Pole(float tau)
{
return expf(-1.0f / (tau * sample_rate_));
}
float Adsr::AdsrFilter()
{
y_ = b_ * x_ + a_ * y_;
return y_;
}
void Adsr::Init(float sample_rate)
{
seg_time_[ADSR_SEG_ATTACK] = 0.1f;
seg_time_[ADSR_SEG_DECAY] = 0.1f;
sus_ = 0.7f;
seg_time_[ADSR_SEG_RELEASE] = 0.1f;
//timer_ = 0;
a_ = 0.0f;
b_ = 0.0f;
x_ = 0.0f;
y_ = 0.0f;
prev_ = 0.0f;
atk_time_ = seg_time_[ADSR_SEG_ATTACK] * sample_rate;
sample_rate_ = sample_rate;
}
float Adsr::Process(bool gate)
{
float pole, out;
out = 0.0f;
if(gate && mode_ != ADSR_SEG_DECAY)
{
mode_ = ADSR_SEG_ATTACK;
//timer_ = 0;
pole = Tau2Pole(seg_time_[ADSR_SEG_ATTACK] * 0.6f);
atk_time_ = seg_time_[ADSR_SEG_ATTACK] * sample_rate_;
a_ = pole;
b_ = 1.0f - pole;
}
else if(!gate && mode_ != ADSR_SEG_IDLE)
{
mode_ = ADSR_SEG_RELEASE;
pole = Tau2Pole(seg_time_[ADSR_SEG_RELEASE]);
a_ = pole;
b_ = 1.0f - pole;
}
x_ = (int)gate;
prev_ = (int)gate;
switch(mode_)
{
case ADSR_SEG_IDLE: out = 0.0f; break;
case ADSR_SEG_ATTACK:
out = AdsrFilter();
if(out > .99f)
{
mode_ = ADSR_SEG_DECAY;
pole = Tau2Pole(seg_time_[ADSR_SEG_DECAY]);
a_ = pole;
b_ = 1.0f - pole;
}
break;
case ADSR_SEG_DECAY:
case ADSR_SEG_RELEASE:
x_ *= sus_;
out = AdsrFilter();
if(out <= 0.01f)
mode_ = ADSR_SEG_IDLE;
default: break;
}
return out;
}

@ -0,0 +1,77 @@
#pragma once
#ifndef DSY_ADSR_H
#define DSY_ADSR_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Distinct stages that the phase of the envelope can be located in.
- IDLE = located at phase location 0, and not currently running
- ATTACK = First segment of envelope where phase moves from 0 to 1
- DECAY = Second segment of envelope where phase moves from 1 to SUSTAIN value
- SUSTAIN = Third segment of envelope, stays at SUSTAIN level until GATE is released
- RELEASE = Fourth segment of envelop where phase moves from SUSTAIN to 0
- LAST = Last segment, aka release
*/
enum
{
ADSR_SEG_IDLE,
ADSR_SEG_ATTACK,
ADSR_SEG_DECAY,
ADSR_SEG_SUSTAIN,
ADSR_SEG_RELEASE,
ADSR_SEG_LAST,
};
/** adsr envelope module
Original author(s) : Paul Batchelor
Ported from Soundpipe by Ben Sergentanis, May 2020
*/
class Adsr
{
public:
Adsr() {}
~Adsr() {}
/** Initializes the Adsr module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Processes one sample through the filter and returns one sample.
\param gate - trigger the envelope, hold it to sustain
*/
float Process(bool gate);
/** Sets time
Set time per segment in seconds
*/
inline void SetTime(int seg, float time) { seg_time_[seg] = time; }
/** Sustain level
\param sus_level - sets sustain level
*/
inline void SetSustainLevel(float sus_level) { sus_ = sus_level; }
/** get the current envelope segment
\return the segment of the envelope that the phase is currently located in.
*/
inline uint8_t GetCurrentSegment() { return mode_; }
/** Tells whether envelope is active
\return true if the envelope is currently in any stage apart from idle.
*/
inline bool IsRunning() const { return mode_ != ADSR_SEG_IDLE; }
private:
float sus_, seg_time_[ADSR_SEG_LAST], a_, b_, y_, x_, prev_, atk_time_;
int sample_rate_;
uint8_t mode_;
float Tau2Pole(float tau);
float AdsrFilter();
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,41 @@
#include "line.h"
using namespace daisysp;
void Line::Init(float sample_rate)
{
sample_rate_ = sample_rate;
dur_ = 0.5f;
end_ = 0.0f;
start_ = 1.0f;
val_ = 1.0f;
}
void Line::Start(float start, float end, float dur)
{
start_ = start;
end_ = end;
dur_ = dur;
inc_ = (end - start) / ((sample_rate_ * dur_));
val_ = start_;
finished_ = 0;
}
float Line::Process(uint8_t *finished)
{
float out;
out = val_;
if((end_ > start_ && out >= end_) || (end_ < start_ && out <= end_))
{
finished_ = 1;
val_ = end_;
out = end_;
}
else
{
val_ += inc_;
}
*finished = finished_;
return out;
}

@ -0,0 +1,39 @@
#pragma once
#ifndef LINE_H
#define LINE_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** creates a Line segment signal
*/
class Line
{
public:
Line() {}
~Line() {}
/** Initializes Line module.
*/
void Init(float sample_rate);
/** Processes Line segment. Returns one sample.
value of finished will be updated to a 1, upon completion of the Line's trajectory.
*/
float Process(uint8_t *finished);
/** Begin creation of Line.
\param start - beginning value
\param end - ending value
\param dur - duration in seconds of Line segment
*/
void Start(float start, float end, float dur);
private:
float start_, end_, dur_;
float inc_, val_, sample_rate_;
uint8_t finished_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,27 @@
#include <math.h>
#include "phasor.h"
#include "../utility/dsp.h"
using namespace daisysp;
void Phasor::SetFreq(float freq)
{
freq_ = freq;
inc_ = (TWOPI_F * freq_) / sample_rate_;
}
float Phasor::Process()
{
float out;
out = phs_ / TWOPI_F;
phs_ += inc_;
if(phs_ > TWOPI_F)
{
phs_ -= TWOPI_F;
}
if(phs_ < 0.0f)
{
phs_ = 0.0f;
}
return out;
}

@ -0,0 +1,63 @@
#pragma once
#ifndef DSY_PHASOR_H
#define DSY_PHASOR_H
#ifdef __cplusplus
namespace daisysp
{
/** Generates a normalized signal moving from 0-1 at the specified frequency.
\todo Selecting which channels should be initialized/included in the sequence conversion.
\todo Setup a similar start function for an external mux, but that seems outside the scope of this file.
*/
class Phasor
{
public:
Phasor() {}
~Phasor() {}
/** Initializes the Phasor module
sample rate, and freq are in Hz
initial phase is in radians
Additional Init functions have defaults when arg is not specified:
- phs = 0.0f
- freq = 1.0f
*/
inline void Init(float sample_rate, float freq, float initial_phase)
{
sample_rate_ = sample_rate;
phs_ = initial_phase;
SetFreq(freq);
}
/** Initialize phasor with samplerate and freq
*/
inline void Init(float sample_rate, float freq)
{
Init(sample_rate, freq, 0.0f);
}
/** Initialize phasor with samplerate
*/
inline void Init(float sample_rate) { Init(sample_rate, 1.0f, 0.0f); }
/** processes Phasor and returns current value
*/
float Process();
/** Sets frequency of the Phasor in Hz
*/
void SetFreq(float freq);
/** Returns current frequency value in Hz
*/
inline float GetFreq() { return freq_; }
private:
float freq_;
float sample_rate_, inc_, phs_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,192 @@
#include "../utility/dsp.h"
#include "analogbassdrum.h"
#include <cmath>
using namespace daisysp;
void AnalogBassDrum::Init(float sample_rate)
{
sample_rate_ = sample_rate;
trig_ = false;
pulse_remaining_samples_ = 0;
fm_pulse_remaining_samples_ = 0;
pulse_ = 0.0f;
pulse_height_ = 0.0f;
pulse_lp_ = 0.0f;
fm_pulse_lp_ = 0.0f;
retrig_pulse_ = 0.0f;
lp_out_ = 0.0f;
tone_lp_ = 0.0f;
sustain_gain_ = 0.0f;
phase_ = 0.f;
SetSustain(false);
SetAccent(.1f);
SetFreq(50.f);
SetTone(.1f);
SetDecay(.3f);
SetSelfFmAmount(1.f);
SetAttackFmAmount(.5f);
resonator_.Init(sample_rate_);
}
inline float AnalogBassDrum::Diode(float x)
{
if(x >= 0.0f)
{
return x;
}
else
{
x *= 2.0f;
return 0.7f * x / (1.0f + fabsf(x));
}
}
float AnalogBassDrum::Process(bool trigger)
{
const int kTriggerPulseDuration = static_cast<int>(1.0e-3f * sample_rate_);
const int kFMPulseDuration = static_cast<int>(6.0e-3f * sample_rate_);
const float kPulseDecayTime = 0.2e-3f * sample_rate_;
const float kPulseFilterTime = 0.1e-3f * sample_rate_;
const float kRetrigPulseDuration = 0.05f * sample_rate_;
const float scale = 0.001f / f0_;
const float q = 1500.0f * powf(2.f, kOneTwelfth * decay_ * 80.0f);
const float tone_f
= fmin(4.0f * f0_ * powf(2.f, kOneTwelfth * tone_ * 108.0f), 1.0f);
const float exciter_leak = 0.08f * (tone_ + 0.25f);
if(trigger || trig_)
{
trig_ = false;
pulse_remaining_samples_ = kTriggerPulseDuration;
fm_pulse_remaining_samples_ = kFMPulseDuration;
pulse_height_ = 3.0f + 7.0f * accent_;
lp_out_ = 0.0f;
}
// Q39 / Q40
float pulse = 0.0f;
if(pulse_remaining_samples_)
{
--pulse_remaining_samples_;
pulse = pulse_remaining_samples_ ? pulse_height_ : pulse_height_ - 1.0f;
pulse_ = pulse;
}
else
{
pulse_ *= 1.0f - 1.0f / kPulseDecayTime;
pulse = pulse_;
}
if(sustain_)
{
pulse = 0.0f;
}
// C40 / R163 / R162 / D83
fonepole(pulse_lp_, pulse, 1.0f / kPulseFilterTime);
pulse = Diode((pulse - pulse_lp_) + pulse * 0.044f);
// Q41 / Q42
float fm_pulse = 0.0f;
if(fm_pulse_remaining_samples_)
{
--fm_pulse_remaining_samples_;
fm_pulse = 1.0f;
// C39 / C52
retrig_pulse_ = fm_pulse_remaining_samples_ ? 0.0f : -0.8f;
}
else
{
// C39 / R161
retrig_pulse_ *= 1.0f - 1.0f / kRetrigPulseDuration;
}
if(sustain_)
{
fm_pulse = 0.0f;
}
fonepole(fm_pulse_lp_, fm_pulse, 1.0f / kPulseFilterTime);
// Q43 and R170 leakage
float punch = 0.7f + Diode(10.0f * lp_out_ - 1.0f);
// Q43 / R165
float attack_fm = fm_pulse_lp_ * 1.7f * attack_fm_amount_;
float self_fm = punch * 0.08f * self_fm_amount_;
float f = f0_ * (1.0f + attack_fm + self_fm);
f = fclamp(f, 0.0f, 0.4f);
float resonator_out;
if(sustain_)
{
sustain_gain_ = accent_ * decay_;
phase_ += f;
phase_ = phase_ >= 1.f ? phase_ - 1.f : phase_;
resonator_out = sin(TWOPI_F * phase_) * sustain_gain_;
lp_out_ = cos(TWOPI_F * phase_) * sustain_gain_;
}
else
{
resonator_.SetFreq(f * sample_rate_);
//resonator_.SetRes(1.0f + q * f);
resonator_.SetRes(.4f * q * f);
resonator_.Process((pulse - retrig_pulse_ * 0.2f) * scale);
resonator_out = resonator_.Band();
lp_out_ = resonator_.Low();
}
fonepole(tone_lp_, pulse * exciter_leak + resonator_out, tone_f);
return tone_lp_;
}
void AnalogBassDrum::Trig()
{
trig_ = true;
}
void AnalogBassDrum::SetSustain(bool sustain)
{
sustain_ = sustain;
}
void AnalogBassDrum::SetAccent(float accent)
{
accent_ = fclamp(accent, 0.f, 1.f);
}
void AnalogBassDrum::SetFreq(float f0)
{
f0 /= sample_rate_;
f0_ = fclamp(f0, 0.f, .5f);
}
void AnalogBassDrum::SetTone(float tone)
{
tone_ = fclamp(tone, 0.f, 1.f);
}
void AnalogBassDrum::SetDecay(float decay)
{
decay_ = decay * .1f;
decay_ -= .1f;
}
void AnalogBassDrum::SetAttackFmAmount(float attack_fm_amount)
{
attack_fm_amount_ = attack_fm_amount * 50.f;
}
void AnalogBassDrum::SetSelfFmAmount(float self_fm_amount)
{
self_fm_amount_ = self_fm_amount * 50.f;
}

@ -0,0 +1,105 @@
#pragma once
#ifndef DSY_ANALOG_BD_H
#define DSY_ANALOG_BD_H
#include <stdint.h>
#ifdef __cplusplus
#include "Synthesis/oscillator.h"
#include "Filters/svf.h"
/** @file analogbassdrum.h */
namespace daisysp
{
/**
@brief 808 bass drum model, revisited.
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/analog_bass_drum.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class AnalogBassDrum
{
public:
AnalogBassDrum() {}
~AnalogBassDrum() {}
/** Initialize the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the next sample.
\param trigger True strikes the drum. Defaults to false.
*/
float Process(bool trigger = false);
/** Strikes the drum. */
void Trig();
/** Set the bassdrum to play infinitely
\param sustain True = infinite length
*/
void SetSustain(bool sustain);
/** Set a small accent.
\param accent Works 0-1
*/
void SetAccent(float accent);
/** Set the drum's root frequency
\param f0 Frequency in Hz
*/
void SetFreq(float f0);
/** Set the amount of click.
\param tone Works 0-1.
*/
void SetTone(float tone);
/** Set the decay length of the drum.
\param decay Works best 0-1.
*/
void SetDecay(float decay);
/** Set the amount of fm attack. Works together with self fm.
\param attack_fm_amount Works best 0-1.
*/
void SetAttackFmAmount(float attack_fm_amount);
/**Set the amount of self fm. Also affects fm attack, and volume decay.
\param self_fm_amount Works best 0-1.
*/
void SetSelfFmAmount(float self_fm_amount);
private:
inline float Diode(float x);
float sample_rate_;
float accent_, f0_, tone_, decay_;
float attack_fm_amount_, self_fm_amount_;
bool trig_, sustain_;
int pulse_remaining_samples_;
int fm_pulse_remaining_samples_;
float pulse_;
float pulse_height_;
float pulse_lp_;
float fm_pulse_lp_;
float retrig_pulse_;
float lp_out_;
float tone_lp_;
float sustain_gain_;
Svf resonator_;
//for use in sin + cos osc. in sustain mode
float phase_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,219 @@
#include "../utility/dsp.h"
#include "analogsnaredrum.h"
#include <math.h>
#include <stdlib.h>
using namespace daisysp;
static const int kNumModes = 5;
void AnalogSnareDrum::Init(float sample_rate)
{
sample_rate_ = sample_rate;
trig_ = false;
pulse_remaining_samples_ = 0;
pulse_ = 0.0f;
pulse_height_ = 0.0f;
pulse_lp_ = 0.0f;
noise_envelope_ = 0.0f;
sustain_gain_ = 0.0f;
SetSustain(false);
SetAccent(.6f);
SetFreq(200.f);
SetDecay(.3f);
SetSnappy(.7f);
SetTone(.5f);
for(int i = 0; i < kNumModes; ++i)
{
resonator_[i].Init(sample_rate_);
phase_[i] = 0.f;
}
noise_filter_.Init(sample_rate_);
}
/** Trigger the drum */
void AnalogSnareDrum::Trig()
{
trig_ = true;
}
void AnalogSnareDrum::SetSustain(bool sustain)
{
sustain_ = sustain;
}
void AnalogSnareDrum::SetAccent(float accent)
{
accent_ = fclamp(accent, 0.f, 1.f);
}
void AnalogSnareDrum::SetFreq(float f0)
{
f0 = f0 / sample_rate_;
f0_ = fclamp(f0, 0.f, .4f);
}
void AnalogSnareDrum::SetTone(float tone)
{
tone_ = fclamp(tone, 0.f, 1.f);
tone_ *= 2.f;
}
void AnalogSnareDrum::SetDecay(float decay)
{
decay_ = decay;
return;
decay_ = fmax(decay, 0.f);
}
void AnalogSnareDrum::SetSnappy(float snappy)
{
snappy_ = fclamp(snappy, 0.f, 1.f);
}
float AnalogSnareDrum::Process(bool trigger)
{
const float decay_xt = decay_ * (1.0f + decay_ * (decay_ - 1.0f));
const int kTriggerPulseDuration = 1.0e-3 * sample_rate_;
const float kPulseDecayTime = 0.1e-3 * sample_rate_;
const float q = 2000.0f * powf(2.f, kOneTwelfth * decay_xt * 84.0f);
const float noise_envelope_decay
= 1.0f
- 0.0017f
* powf(2.f,
kOneTwelfth * (-decay_ * (50.0f + snappy_ * 10.0f)));
const float exciter_leak = snappy_ * (2.0f - snappy_) * 0.1f;
float snappy = snappy_ * 1.1f - 0.05f;
snappy = fclamp(snappy, 0.0f, 1.0f);
float tone = tone_;
if(trigger || trig_)
{
trig_ = false;
pulse_remaining_samples_ = kTriggerPulseDuration;
pulse_height_ = 3.0f + 7.0f * accent_;
noise_envelope_ = 2.0f;
}
static const float kModeFrequencies[kNumModes]
= {1.00f, 2.00f, 3.18f, 4.16f, 5.62f};
float f[kNumModes];
float gain[kNumModes];
for(int i = 0; i < kNumModes; ++i)
{
f[i] = fmin(f0_ * kModeFrequencies[i], 0.499f);
resonator_[i].SetFreq(f[i] * sample_rate_);
// resonator_[i].SetRes(1.0f + f[i] * (i == 0 ? q : q * 0.25f));
resonator_[i].SetRes((f[i] * (i == 0 ? q : q * 0.25f)) * .2);
}
if(tone < 0.666667f)
{
// 808-style (2 modes)
tone *= 1.5f;
gain[0] = 1.5f + (1.0f - tone) * (1.0f - tone) * 4.5f;
gain[1] = 2.0f * tone + 0.15f;
for(int i = 2; i < kNumModes; i++)
{
gain[i] = 0.f;
}
}
else
{
// What the 808 could have been if there were extra modes!
tone = (tone - 0.666667f) * 3.0f;
gain[0] = 1.5f - tone * 0.5f;
gain[1] = 2.15f - tone * 0.7f;
for(int i = 2; i < kNumModes; ++i)
{
gain[i] = tone;
tone *= tone;
}
}
float f_noise = f0_ * 16.0f;
fclamp(f_noise, 0.0f, 0.499f);
noise_filter_.SetFreq(f_noise * sample_rate_);
//noise_filter_.SetRes(1.0f + f_noise * 1.5f);
noise_filter_.SetRes(f_noise * 1.5f);
// Q45 / Q46
float pulse = 0.0f;
if(pulse_remaining_samples_)
{
--pulse_remaining_samples_;
pulse = pulse_remaining_samples_ ? pulse_height_ : pulse_height_ - 1.0f;
pulse_ = pulse;
}
else
{
pulse_ *= 1.0f - 1.0f / kPulseDecayTime;
pulse = pulse_;
}
float sustain_gain_value = sustain_gain_ = accent_ * decay_;
// R189 / C57 / R190 + C58 / C59 / R197 / R196 / IC14
pulse_lp_ = fclamp(pulse_lp_, pulse, 0.75f);
float shell = 0.0f;
for(int i = 0; i < kNumModes; ++i)
{
float excitation
= i == 0 ? (pulse - pulse_lp_) + 0.006f * pulse : 0.026f * pulse;
phase_[i] += f[i];
phase_[i] = phase_[i] >= 1.f ? phase_[i] - 1.f : phase_[i];
resonator_[i].Process(excitation);
shell += gain[i]
* (sustain_
? sin(phase_[i] * TWOPI_F) * sustain_gain_value * 0.25f
: resonator_[i].Band() + excitation * exciter_leak);
}
shell = SoftClip(shell);
// C56 / R194 / Q48 / C54 / R188 / D54
float noise = 2.0f * rand() * kRandFrac - 1.0f;
if(noise < 0.0f)
noise = 0.0f;
noise_envelope_ *= noise_envelope_decay;
noise *= (sustain_ ? sustain_gain_value : noise_envelope_) * snappy * 2.0f;
// C66 / R201 / C67 / R202 / R203 / Q49
noise_filter_.Process(noise);
noise = noise_filter_.Band();
// IC13
return noise + shell * (1.0f - snappy);
}
inline float AnalogSnareDrum::SoftLimit(float x)
{
return x * (27.0f + x * x) / (27.0f + 9.0f * x * x);
}
inline float AnalogSnareDrum::SoftClip(float x)
{
if(x < -3.0f)
{
return -1.0f;
}
else if(x > 3.0f)
{
return 1.0f;
}
else
{
return SoftLimit(x);
}
}

@ -0,0 +1,98 @@
#pragma once
#ifndef DSY_ANALOG_SNARE_H
#define DSY_ANALOG_SNARE_H
#include "Filters/svf.h"
#include <stdint.h>
#ifdef __cplusplus
/** @file analogsnaredrum.h */
namespace daisysp
{
/**
@brief 808 snare drum model, revisited.
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/analog_snare_drum.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class AnalogSnareDrum
{
public:
AnalogSnareDrum() {}
~AnalogSnareDrum() {}
static const int kNumModes = 5;
/** Init the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the next sample
\param trigger Hit the drum with true. Defaults to false.
*/
float Process(bool trigger = false);
/** Trigger the drum */
void Trig();
/** Init the module
\param sample_rate Audio engine sample rate
*/
void SetSustain(bool sustain);
/** Set how much accent to use
\param accent Works 0-1.
*/
void SetAccent(float accent);
/** Set the drum's root frequency
\param f0 Freq in Hz
*/
void SetFreq(float f0);
/** Set the brightness of the drum tone.
\param tone Works 0-1. 1 = bright, 0 = dark.
*/
void SetTone(float tone);
/** Set the length of the drum decay
\param decay Works with positive numbers
*/
void SetDecay(float decay);
/** Sets the mix between snare and drum.
\param snappy 1 = just snare. 0 = just drum.
*/
void SetSnappy(float snappy);
private:
float sample_rate_;
float f0_, tone_, accent_, snappy_, decay_;
bool sustain_;
bool trig_;
inline float SoftLimit(float x);
inline float SoftClip(float x);
int pulse_remaining_samples_;
float pulse_;
float pulse_height_;
float pulse_lp_;
float noise_envelope_;
float sustain_gain_;
Svf resonator_[kNumModes];
Svf noise_filter_;
// Replace the resonators in "free running" (sustain) mode.
float phase_[kNumModes];
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,96 @@
#include "../utility/dsp.h"
#include "hihat.h"
#include <math.h>
using namespace daisysp;
void SquareNoise::Init(float sample_rate)
{
for(int i = 0; i < 6; i++)
{
phase_[i] = 0;
}
}
float SquareNoise::Process(float f0)
{
const float ratios[6] = {// Nominal f0: 414 Hz
1.0f,
1.304f,
1.466f,
1.787f,
1.932f,
2.536f};
uint32_t increment[6];
uint32_t phase[6];
for(int i = 0; i < 6; ++i)
{
float f = f0 * ratios[i];
if(f >= 0.499f)
f = 0.499f;
increment[i] = static_cast<uint32_t>(f * 4294967296.0f);
phase[i] = phase_[i];
}
phase[0] += increment[0];
phase[1] += increment[1];
phase[2] += increment[2];
phase[3] += increment[3];
phase[4] += increment[4];
phase[5] += increment[5];
uint32_t noise = 0;
noise += (phase[0] >> 31);
noise += (phase[1] >> 31);
noise += (phase[2] >> 31);
noise += (phase[3] >> 31);
noise += (phase[4] >> 31);
noise += (phase[5] >> 31);
for(int i = 0; i < 6; ++i)
{
phase_[i] = phase[i];
}
return 0.33f * static_cast<float>(noise) - 1.0f;
}
void RingModNoise::Init(float sample_rate)
{
sample_rate_ = sample_rate;
for(int i = 0; i < 6; ++i)
{
oscillator_[i].Init(sample_rate_);
}
}
float RingModNoise::Process(float f0)
{
const float ratio = f0 / (0.01f + f0);
const float f1a = 200.0f / sample_rate_ * ratio;
const float f1b = 7530.0f / sample_rate_ * ratio;
const float f2a = 510.0f / sample_rate_ * ratio;
const float f2b = 8075.0f / sample_rate_ * ratio;
const float f3a = 730.0f / sample_rate_ * ratio;
const float f3b = 10500.0f / sample_rate_ * ratio;
float out = ProcessPair(&oscillator_[0], f1a, f1b);
out += ProcessPair(&oscillator_[2], f2a, f2b);
out += ProcessPair(&oscillator_[4], f3a, f3b);
return out;
}
float RingModNoise::ProcessPair(Oscillator* osc, float f1, float f2)
{
osc[0].SetWaveform(Oscillator::WAVE_SQUARE);
osc[0].SetFreq(f1 * sample_rate_);
float temp_1 = osc[0].Process();
osc[1].SetWaveform(Oscillator::WAVE_SAW);
osc[1].SetFreq(f2 * sample_rate_);
float temp_2 = osc[1].Process();
return temp_1 * temp_2;
}

@ -0,0 +1,269 @@
#pragma once
#ifndef DSY_HIHAT_H
#define DSY_HIHAT_H
#include "Filters/svf.h"
#include "Synthesis/oscillator.h"
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
/** @file hihat.h */
namespace daisysp
{
/**
@brief 808 style "metallic noise" with 6 square oscillators.
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/hihat.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SquareNoise
{
public:
SquareNoise() {}
~SquareNoise() {}
void Init(float sample_rate);
float Process(float f0);
private:
uint32_t phase_[6];
};
/**
@brief Ring mod style metallic noise generator.
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/hihat.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class RingModNoise
{
public:
RingModNoise() {}
~RingModNoise() {}
void Init(float sample_rate);
float Process(float f0);
private:
float ProcessPair(Oscillator* osc, float f1, float f2);
Oscillator oscillator_[6];
float sample_rate_;
};
/**
@brief Swing type VCA
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/hihat.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SwingVCA
{
public:
float operator()(float s, float gain)
{
s *= s > 0.0f ? 10.0f : 0.1f;
s = s / (1.0f + fabsf(s));
return (s + 1.0f) * gain;
}
};
/**
@brief Linear type VCA
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/hihat.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class LinearVCA
{
public:
float operator()(float s, float gain) { return s * gain; }
};
/**
@brief 808 HH, with a few extra parameters to push things to the CY territory...
@author Ben Sergentanis
@date Jan 2021
The template parameter MetallicNoiseSource allows another kind of "metallic \n
noise" to be used, for results which are more similar to KR-55 or FM hi-hats. \n \n
Ported from pichenettes/eurorack/plaits/dsp/drums/hihat.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
template <typename MetallicNoiseSource = SquareNoise,
typename VCA = LinearVCA,
bool resonance = true>
class HiHat
{
public:
HiHat() {}
~HiHat() {}
/** Initialize the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate)
{
sample_rate_ = sample_rate;
trig_ = false;
envelope_ = 0.0f;
noise_clock_ = 0.0f;
noise_sample_ = 0.0f;
sustain_gain_ = 0.0f;
SetFreq(3000.f);
SetTone(.5f);
SetDecay(.2f);
SetNoisiness(.8f);
SetAccent(.8f);
SetSustain(false);
metallic_noise_.Init(sample_rate_);
noise_coloration_svf_.Init(sample_rate_);
hpf_.Init(sample_rate_);
}
/** Get the next sample
\param trigger Hit the hihat with true. Defaults to false.
*/
float Process(bool trigger = false)
{
const float envelope_decay
= 1.0f - 0.003f * SemitonesToRatio(-decay_ * 84.0f);
const float cut_decay
= 1.0f - 0.0025f * SemitonesToRatio(-decay_ * 36.0f);
if(trigger || trig_)
{
trig_ = false;
envelope_
= (1.5f + 0.5f * (1.0f - decay_)) * (0.3f + 0.7f * accent_);
}
// Process the metallic noise.
float out = metallic_noise_.Process(2.0f * f0_);
// Apply BPF on the metallic noise.
float cutoff = 150.0f / sample_rate_ * SemitonesToRatio(tone_ * 72.0f);
cutoff = fclamp(cutoff, 0.0f, 16000.0f / sample_rate_);
noise_coloration_svf_.SetFreq(cutoff * sample_rate_);
noise_coloration_svf_.SetRes(resonance ? 3.0f + 6.0f * tone_ : 1.0f);
noise_coloration_svf_.Process(out);
out = noise_coloration_svf_.Band();
// This is not at all part of the 808 circuit! But to add more variety, we
// add a variable amount of clocked noise to the output of the 6 schmitt
// trigger oscillators.
float noise_f = f0_ * (16.0f + 16.0f * (1.0f - noisiness_));
noise_f = fclamp(noise_f, 0.0f, 0.5f);
noise_clock_ += noise_f;
if(noise_clock_ >= 1.0f)
{
noise_clock_ -= 1.0f;
noise_sample_ = rand() * kRandFrac - 0.5f;
}
out += noisiness_ * (noise_sample_ - out);
// Apply VCA.
sustain_gain_ = accent_ * decay_;
VCA vca;
envelope_ *= envelope_ > 0.5f ? envelope_decay : cut_decay;
out = vca(out, sustain_ ? sustain_gain_ : envelope_);
hpf_.SetFreq(cutoff * sample_rate_);
hpf_.SetRes(.5f);
hpf_.Process(out);
out = hpf_.High();
return out;
}
/** Trigger the hihat */
void Trig() { trig_ = true; }
/** Make the hihat ring out infinitely.
\param sustain True = infinite sustain.
*/
void SetSustain(bool sustain) { sustain_ = sustain; }
/** Set how much accent to use
\param accent Works 0-1.
*/
void SetAccent(float accent) { accent_ = fclamp(accent, 0.f, 1.f); }
/** Set the hihat tone's root frequency
\param f0 Freq in Hz
*/
void SetFreq(float f0)
{
f0 /= sample_rate_;
f0_ = fclamp(f0, 0.f, 1.f);
}
/** Set the overall brightness / darkness of the hihat.
\param tone Works from 0-1.
*/
void SetTone(float tone) { tone_ = fclamp(tone, 0.f, 1.f); }
/** Set the length of the hihat decay
\param decay Works > 0. Tuned for 0-1.
*/
void SetDecay(float decay)
{
decay_ = fmax(decay, 0.f);
decay_ *= 1.7;
decay_ -= 1.2;
}
/** Sets the mix between tone and noise
\param snappy 1 = just noise. 0 = just tone.
*/
void SetNoisiness(float noisiness)
{
noisiness_ = fclamp(noisiness, 0.f, 1.f);
noisiness_ *= noisiness_;
}
private:
float sample_rate_;
float accent_, f0_, tone_, decay_, noisiness_;
bool sustain_;
bool trig_;
float SemitonesToRatio(float in) { return powf(2.f, in * kOneTwelfth); }
float envelope_;
float noise_clock_;
float noise_sample_;
float sustain_gain_;
MetallicNoiseSource metallic_noise_;
Svf noise_coloration_svf_;
Svf hpf_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,231 @@
#include "synthbassdrum.h"
#include <math.h>
#include <stdlib.h>
using namespace daisysp;
void SyntheticBassDrumClick::Init(float sample_rate)
{
lp_ = 0.0f;
hp_ = 0.0f;
filter_.Init(sample_rate);
filter_.SetFreq(5000.0f);
filter_.SetRes(1.f); //2.f
}
float SyntheticBassDrumClick::Process(float in)
{
//SLOPE(lp_, in, 0.5f, 0.1f);
float error = in - lp_;
lp_ += (error > 0 ? .5f : .1f) * error;
fonepole(hp_, lp_, 0.04f);
filter_.Process(lp_ - hp_);
return filter_.Low();
}
void SyntheticBassDrumAttackNoise::Init()
{
lp_ = 0.0f;
hp_ = 0.0f;
}
float SyntheticBassDrumAttackNoise::Process()
{
float sample = rand() * kRandFrac;
fonepole(lp_, sample, 0.05f);
fonepole(hp_, lp_, 0.005f);
return lp_ - hp_;
}
void SyntheticBassDrum::Init(float sample_rate)
{
sample_rate_ = sample_rate;
trig_ = false;
phase_ = 0.0f;
phase_noise_ = 0.0f;
f0_ = 0.0f;
fm_ = 0.0f;
fm_lp_ = 0.0f;
body_env_lp_ = 0.0f;
body_env_ = 0.0f;
body_env_pulse_width_ = 0;
fm_pulse_width_ = 0;
tone_lp_ = 0.0f;
sustain_gain_ = 0.0f;
SetFreq(100.f);
SetSustain(false);
SetAccent(.2f);
SetTone(.6f);
SetDecay(.7f);
SetDirtiness(.3f);
SetFmEnvelopeAmount(.6);
SetFmEnvelopeDecay(.3);
click_.Init(sample_rate);
noise_.Init();
}
inline float SyntheticBassDrum::DistortedSine(float phase,
float phase_noise,
float dirtiness)
{
phase += phase_noise * dirtiness;
//MAKE_INTEGRAL_FRACTIONAL(phase);
int32_t phase_integral = static_cast<int32_t>(phase);
float phase_fractional = phase - static_cast<float>(phase_integral);
phase = phase_fractional;
float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.0f;
float sine = 2.0f * triangle / (1.0f + fabsf(triangle));
float clean_sine = sinf(TWOPI_F * (phase + 0.75f));
return sine + (1.0f - dirtiness) * (clean_sine - sine);
}
inline float SyntheticBassDrum::TransistorVCA(float s, float gain)
{
s = (s - 0.6f) * gain;
return 3.0f * s / (2.0f + fabsf(s)) + gain * 0.3f;
}
float SyntheticBassDrum::Process(bool trigger)
{
float dirtiness = dirtiness_;
dirtiness *= fmax(1.0f - 8.0f * new_f0_, 0.0f);
const float fm_decay
= 1.0f
- 1.0f / (0.008f * (1.0f + fm_envelope_decay_ * 4.0f) * sample_rate_);
const float body_env_decay
= 1.0f
- 1.0f / (0.02f * sample_rate_)
* powf(2.f, (-decay_ * 60.0f) * kOneTwelfth);
const float transient_env_decay = 1.0f - 1.0f / (0.005f * sample_rate_);
const float tone_f = fmin(
4.0f * new_f0_ * powf(2.f, (tone_ * 108.0f) * kOneTwelfth), 1.0f);
const float transient_level = tone_;
if(trigger || trig_)
{
trig_ = false;
fm_ = 1.0f;
body_env_ = transient_env_ = 0.3f + 0.7f * accent_;
body_env_pulse_width_ = sample_rate_ * 0.001f;
fm_pulse_width_ = sample_rate_ * 0.0013f;
}
sustain_gain_ = accent_ * decay_;
fonepole(phase_noise_, rand() * kRandFrac - 0.5f, 0.002f);
float mix = 0.0f;
if(sustain_)
{
f0_ = new_f0_;
phase_ += f0_;
if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
}
float body = DistortedSine(phase_, phase_noise_, dirtiness);
mix -= TransistorVCA(body, sustain_gain_);
}
else
{
if(fm_pulse_width_)
{
--fm_pulse_width_;
phase_ = 0.25f;
}
else
{
fm_ *= fm_decay;
float fm = 1.0f + fm_envelope_amount_ * 3.5f * fm_lp_;
f0_ = new_f0_;
phase_ += fmin(f0_ * fm, 0.5f);
if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
}
}
if(body_env_pulse_width_)
{
--body_env_pulse_width_;
}
else
{
body_env_ *= body_env_decay;
transient_env_ *= transient_env_decay;
}
const float envelope_lp_f = 0.1f;
fonepole(body_env_lp_, body_env_, envelope_lp_f);
fonepole(transient_env_lp_, transient_env_, envelope_lp_f);
fonepole(fm_lp_, fm_, envelope_lp_f);
float body = DistortedSine(phase_, phase_noise_, dirtiness);
float transient = click_.Process(body_env_pulse_width_ ? 0.0f : 1.0f)
+ noise_.Process();
mix -= TransistorVCA(body, body_env_lp_);
mix -= transient * transient_env_lp_ * transient_level;
}
fonepole(tone_lp_, mix, tone_f);
return tone_lp_;
}
void SyntheticBassDrum::Trig()
{
trig_ = true;
}
void SyntheticBassDrum::SetSustain(bool sustain)
{
sustain_ = sustain;
}
void SyntheticBassDrum::SetAccent(float accent)
{
accent_ = fclamp(accent, 0.f, 1.f);
}
void SyntheticBassDrum::SetFreq(float freq)
{
freq /= sample_rate_;
new_f0_ = fclamp(freq, 0.f, 1.f);
}
void SyntheticBassDrum::SetTone(float tone)
{
tone_ = fclamp(tone, 0.f, 1.f);
}
void SyntheticBassDrum::SetDecay(float decay)
{
decay = fclamp(decay, 0.f, 1.f);
decay_ = decay * decay;
}
void SyntheticBassDrum::SetDirtiness(float dirtiness)
{
dirtiness_ = fclamp(dirtiness, 0.f, 1.f);
}
void SyntheticBassDrum::SetFmEnvelopeAmount(float fm_envelope_amount)
{
fm_envelope_amount_ = fclamp(fm_envelope_amount, 0.f, 1.f);
}
void SyntheticBassDrum::SetFmEnvelopeDecay(float fm_envelope_decay)
{
fm_envelope_decay = fclamp(fm_envelope_decay, 0.f, 1.f);
fm_envelope_decay_ = fm_envelope_decay * fm_envelope_decay;
}

@ -0,0 +1,179 @@
#pragma once
#ifndef DSY_SYNTHBD_H
#define DSY_SYNTHBD_H
#include "Filters/svf.h"
#include "Utility/dsp.h"
#include <stdint.h>
#ifdef __cplusplus
/** @file synthbassdrum.h */
namespace daisysp
{
/**
@brief Click noise for SyntheticBassDrum
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/synthetic_bass_drum.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SyntheticBassDrumClick
{
public:
SyntheticBassDrumClick() {}
~SyntheticBassDrumClick() {}
/** Init the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Get the next sample.
\param in Trigger the click.
*/
float Process(float in);
private:
float lp_;
float hp_;
Svf filter_;
};
/**
@brief Attack Noise generator for SyntheticBassDrum.
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/drums/synthetic_bass_drum.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SyntheticBassDrumAttackNoise
{
public:
SyntheticBassDrumAttackNoise() {}
~SyntheticBassDrumAttackNoise() {}
/** Init the module */
void Init();
/** Get the next sample. */
float Process();
private:
float lp_;
float hp_;
};
/**
@brief Naive bass drum model (modulated oscillator with FM + envelope).
@author Ben Sergentanis
@date Jan 2021
Inadvertently 909-ish. \n \n
Ported from pichenettes/eurorack/plaits/dsp/drums/synthetic_bass_drum.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SyntheticBassDrum
{
public:
SyntheticBassDrum() {}
~SyntheticBassDrum() {}
/** Init the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Generates a distorted sine wave */
inline float DistortedSine(float phase, float phase_noise, float dirtiness);
/** Transistor VCA simulation.
\param s Input sample.
\param gain VCA gain.
*/
inline float TransistorVCA(float s, float gain);
/** Get the next sample.
\param trigger True triggers the BD. This is optional.
*/
float Process(bool trigger = false);
/** Trigger the drum */
void Trig();
/** Allows the drum to play continuously
\param sustain True sets the drum on infinite sustain.
*/
void SetSustain(bool sustain);
/** Sets the amount of accent.
\param accent Works 0-1.
*/
void SetAccent(float accent);
/** Set the bass drum's root frequency.
\param Frequency in Hz.
*/
void SetFreq(float freq);
/** Sets the overall bright / darkness of the drum.
\param tone Works 0-1.
*/
void SetTone(float tone);
/** Sets how long the drum's volume takes to decay.
\param Works 0-1.
*/
void SetDecay(float decay);
/** Makes things grimy
\param dirtiness Works 0-1.
*/
void SetDirtiness(float dirtiness);
/** Sets how much of a pitch sweep the drum experiences when triggered.
\param fm_envelope_amount Works 0-1.
*/
void SetFmEnvelopeAmount(float fm_envelope_amount);
/** Sets how long the initial pitch sweep takes.
\param fm_envelope_decay Works 0-1.
*/
void SetFmEnvelopeDecay(float fm_envelope_decay);
private:
float sample_rate_;
bool trig_;
bool sustain_;
float accent_, new_f0_, tone_, decay_;
float dirtiness_, fm_envelope_amount_, fm_envelope_decay_;
float f0_;
float phase_;
float phase_noise_;
float fm_;
float fm_lp_;
float body_env_;
float body_env_lp_;
float transient_env_;
float transient_env_lp_;
float sustain_gain_;
float tone_lp_;
SyntheticBassDrumClick click_;
SyntheticBassDrumAttackNoise noise_;
int body_env_pulse_width_;
int fm_pulse_width_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,201 @@
#include "../utility/dsp.h"
#include "synthsnaredrum.h"
#include <math.h>
#include <stdlib.h>
using namespace daisysp;
void SyntheticSnareDrum::Init(float sample_rate)
{
sample_rate_ = sample_rate;
phase_[0] = 0.0f;
phase_[1] = 0.0f;
drum_amplitude_ = 0.0f;
snare_amplitude_ = 0.0f;
fm_ = 0.0f;
hold_counter_ = 0;
sustain_gain_ = 0.0f;
SetSustain(false);
SetAccent(.6f);
SetFreq(200.f);
SetFmAmount(.1f);
SetDecay(.3f);
SetSnappy(.7f);
trig_ = false;
drum_lp_.Init(sample_rate_);
snare_hp_.Init(sample_rate_);
snare_lp_.Init(sample_rate_);
}
inline float SyntheticSnareDrum::DistortedSine(float phase)
{
float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.3f;
return 2.0f * triangle / (1.0f + fabsf(triangle));
}
bool even = true;
float SyntheticSnareDrum::Process(bool trigger)
{
const float decay_xt = decay_ * (1.0f + decay_ * (decay_ - 1.0f));
const float drum_decay
= 1.0f
- 1.0f / (0.015f * sample_rate_)
* powf(2.f,
kOneTwelfth
* (-decay_xt * 72.0f - fm_amount_ * 12.0f
+ snappy_ * 7.0f));
const float snare_decay
= 1.0f
- 1.0f / (0.01f * sample_rate_)
* powf(2.f, kOneTwelfth * (-decay_ * 60.0f - snappy_ * 7.0f));
const float fm_decay = 1.0f - 1.0f / (0.007f * sample_rate_);
float snappy = snappy_ * 1.1f - 0.05f;
snappy = fclamp(snappy, 0.0f, 1.0f);
const float drum_level = sqrtf(1.0f - snappy);
const float snare_level = sqrtf(snappy);
const float snare_f_min = fmin(10.0f * f0_, 0.5f);
const float snare_f_max = fmin(35.0f * f0_, 0.5f);
snare_hp_.SetFreq(snare_f_min * sample_rate_);
snare_lp_.SetFreq(snare_f_max * sample_rate_);
snare_lp_.SetRes(0.5f + 2.0f * snappy);
drum_lp_.SetFreq(3.0f * f0_ * sample_rate_);
if(trigger || trig_)
{
trig_ = false;
snare_amplitude_ = drum_amplitude_ = 0.3f + 0.7f * accent_;
fm_ = 1.0f;
phase_[0] = phase_[1] = 0.0f;
hold_counter_
= static_cast<int>((0.04f + decay_ * 0.03f) * sample_rate_);
}
even = !even;
if(sustain_)
{
sustain_gain_ = snare_amplitude_ = accent_ * decay_;
drum_amplitude_ = snare_amplitude_;
fm_ = 0.0f;
}
else
{
// Compute all D envelopes.
// The envelope for the drum has a very long tail.
// The envelope for the snare has a "hold" stage which lasts between
// 40 and 70 ms
drum_amplitude_
*= (drum_amplitude_ > 0.03f || even) ? drum_decay : 1.0f;
if(hold_counter_)
{
--hold_counter_;
}
else
{
snare_amplitude_ *= snare_decay;
}
fm_ *= fm_decay;
}
// The 909 circuit has a funny kind of oscillator coupling - the signal
// leaving Q40's collector and resetting all oscillators allow some
// intermodulation.
float reset_noise = 0.0f;
float reset_noise_amount = (0.125f - f0_) * 8.0f;
reset_noise_amount = fclamp(reset_noise_amount, 0.0f, 1.0f);
reset_noise_amount *= reset_noise_amount;
reset_noise_amount *= fm_amount_;
reset_noise += phase_[0] > 0.5f ? -1.0f : 1.0f;
reset_noise += phase_[1] > 0.5f ? -1.0f : 1.0f;
reset_noise *= reset_noise_amount * 0.025f;
float f = f0_ * (1.0f + fm_amount_ * (4.0f * fm_));
phase_[0] += f;
phase_[1] += f * 1.47f;
if(reset_noise_amount > 0.1f)
{
if(phase_[0] >= 1.0f + reset_noise)
{
phase_[0] = 1.0f - phase_[0];
}
if(phase_[1] >= 1.0f + reset_noise)
{
phase_[1] = 1.0f - phase_[1];
}
}
else
{
if(phase_[0] >= 1.0f)
{
phase_[0] -= 1.0f;
}
if(phase_[1] >= 1.0f)
{
phase_[1] -= 1.0f;
}
}
float drum = -0.1f;
drum += DistortedSine(phase_[0]) * 0.60f;
drum += DistortedSine(phase_[1]) * 0.25f;
drum *= drum_amplitude_ * drum_level;
drum_lp_.Process(drum);
drum = drum_lp_.Low();
float noise = rand() * kRandFrac;
snare_lp_.Process(noise);
float snare = snare_lp_.Low();
snare_hp_.Process(snare);
snare = snare_hp_.High();
snare = (snare + 0.1f) * (snare_amplitude_ + fm_) * snare_level;
return snare + drum; // It's a snare, it's a drum, it's a snare drum.
}
void SyntheticSnareDrum::Trig()
{
trig_ = true;
}
void SyntheticSnareDrum::SetSustain(bool sustain)
{
sustain_ = sustain;
}
void SyntheticSnareDrum::SetAccent(float accent)
{
accent_ = fclamp(accent, 0.f, 1.f);
}
void SyntheticSnareDrum::SetFreq(float f0)
{
f0 /= sample_rate_;
f0_ = fclamp(f0, 0.f, 1.f);
}
void SyntheticSnareDrum::SetFmAmount(float fm_amount)
{
fm_amount = fclamp(fm_amount, 0.f, 1.f);
fm_amount_ = fm_amount * fm_amount;
}
void SyntheticSnareDrum::SetDecay(float decay)
{
decay_ = fmax(decay, 0.f);
}
void SyntheticSnareDrum::SetSnappy(float snappy)
{
snappy_ = fclamp(snappy, 0.f, 1.f);
}

@ -0,0 +1,97 @@
#pragma once
#ifndef DSY_SYNTHSD_H
#define DSY_SYNTHSD_H
#include "Filters/svf.h"
#include <stdint.h>
#ifdef __cplusplus
/** @file synthsnaredrum.h */
namespace daisysp
{
/**
@brief Naive snare drum model (two modulated oscillators + filtered noise).
@author Ben Sergentanis
@date Jan 2021
Uses a few magic numbers taken from the 909 schematics: \n
- Ratio between the two modes of the drum set to 1.47. \n
- Funky coupling between the two modes. \n
- Noise coloration filters and envelope shapes for the snare. \n \n
Ported from pichenettes/eurorack/plaits/dsp/drums/synthetic_snare_drum.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SyntheticSnareDrum
{
public:
SyntheticSnareDrum() {}
~SyntheticSnareDrum() {}
/** Init the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the next sample.
\param trigger True = hit the drum. This argument is optional.
*/
float Process(bool trigger = false);
/** Trigger the drum */
void Trig();
/** Make the drum ring out infinitely.
\param sustain True = infinite sustain.
*/
void SetSustain(bool sustain);
/** Set how much accent to use
\param accent Works 0-1.
*/
void SetAccent(float accent);
/** Set the drum's root frequency
\param f0 Freq in Hz
*/
void SetFreq(float f0);
/** Set the amount of fm sweep.
\param fm_amount Works from 0 - 1.
*/
void SetFmAmount(float fm_amount);
/** Set the length of the drum decay
\param decay Works with positive numbers
*/
void SetDecay(float decay);
/** Sets the mix between snare and drum.
\param snappy 1 = just snare. 0 = just drum.
*/
void SetSnappy(float snappy);
private:
inline float DistortedSine(float phase);
float sample_rate_;
bool trig_;
bool sustain_;
float accent_, f0_, fm_amount_, decay_, snappy_;
float phase_[2];
float drum_amplitude_;
float snare_amplitude_;
float fm_;
float sustain_gain_;
int hold_counter_;
Svf drum_lp_;
Svf snare_hp_;
Svf snare_lp_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,56 @@
#include "balance.h"
#include <math.h>
#include "../utility/dsp.h"
using namespace daisysp;
void Balance::Init(float sample_rate)
{
float b;
sample_rate_ = sample_rate;
ihp_ = 10.0f;
b = 2.0f - cosf(ihp_ * (TWOPI_F / sample_rate_));
c2_ = b - sqrtf(b * b - 1.0f);
c1_ = 1.0f - c2_;
prvq_ = prvr_ = prva_ = 0.0f;
}
float Balance::Process(float sig, float comp)
{
float q, r, a, diff, out;
float c1 = c1_;
float c2 = c2_;
q = prvq_;
r = prvr_;
float as = sig;
float cs = comp;
q = c1 * as * as + c2 * q;
r = c1 * cs * cs + c2 * r;
prvq_ = q;
prvr_ = r;
if(q != 0.0f)
{
a = sqrtf(r / q);
}
else
{
a = sqrtf(r);
}
if((diff = a - prva_) != 0.0f)
{
out = sig * prva_;
}
else
{
out = sig * a;
}
prva_ = a;
return out;
}

@ -0,0 +1,47 @@
#pragma once
#ifndef DSY_BALANCE_H
#define DSY_BALANCE_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Balances two sound sources. Sig is boosted to the level of comp.
*Original author(s) : Barry Vercoe, john ffitch, Gabriel Maldonado
*Year: 1991
*Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Balance
{
public:
Balance() {}
~Balance() {}
/** Initializes the balance module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** adjust sig level to level of comp
*/
float Process(float sig, float comp);
/** adjusts the rate at which level compensation happens
\param cutoff : Sets half power point of special internal cutoff filter.
defaults to 10
*/
inline void SetCutoff(float cutoff) { ihp_ = cutoff; }
private:
float sample_rate_, ihp_, c2_, c1_, prvq_, prvr_, prva_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,76 @@
// # compressor
//
// Author: shensley, AvAars
//
#include <cmath>
#include <stdlib.h>
#include <stdint.h>
#include "compressor.h"
using namespace daisysp;
#ifndef max
#define max(a, b) ((a < b) ? b : a)
#endif
#ifndef min
#define min(a, b) ((a < b) ? a : b)
#endif
void Compressor::Init(float sample_rate)
{
sample_rate_ = min(192000, max(1, sample_rate));
sample_rate_inv_ = 1.0f / (float)sample_rate_;
sample_rate_inv2_ = 2.0f / (float)sample_rate_;
// Initializing the params in this order to avoid dividing by zero
SetRatio(2.0f);
SetAttack(0.1f);
SetRelease(0.1f);
SetThreshold(-12.0f);
AutoMakeup(true);
gain_rec_ = 0.1f;
slope_rec_ = 0.1f;
}
float Compressor::Process(float in)
{
float inAbs = fabsf(in);
float cur_slo = ((slope_rec_ > inAbs) ? rel_slo_ : atk_slo_);
slope_rec_ = ((slope_rec_ * cur_slo) + ((1.0f - cur_slo) * inAbs));
gain_rec_ = ((atk_slo2_ * gain_rec_)
+ (ratio_mul_
* fmax(((20.f * fastlog10f(slope_rec_)) - thresh_), 0.f)));
gain_ = pow10f(0.05f * (gain_rec_ + makeup_gain_));
return gain_ * in;
}
void Compressor::ProcessBlock(float *in, float *out, float *key, size_t size)
{
for(size_t i = 0; i < size; i++)
{
Process(key[i]);
out[i] = Apply(in[i]);
}
}
// Multi-channel block processing
void Compressor::ProcessBlock(float **in,
float **out,
float * key,
size_t channels,
size_t size)
{
for(size_t i = 0; i < size; i++)
{
Process(key[i]);
for(size_t c = 0; c < channels; c++)
{
out[c][i] = Apply(in[c][i]);
}
}
}

@ -0,0 +1,199 @@
#pragma once
#ifndef DSY_COMPRESSOR_H
#define DSY_COMPRESSOR_H
#include "Utility/dsp.h"
namespace daisysp
{
/** dynamics compressor
influenced by compressor in soundpipe (from faust).
Modifications made to do:
- Less calculations during each process loop (coefficients recalculated on parameter change).
- C++-ified
- added sidechain support
- pulled gain apart for monitoring and multichannel support
- improved readability
- improved makeup-gain calculations
- changing controls now costs a lot less
- a lot less expensive
by: shensley, improved upon by AvAars
\todo Add soft/hard knee settings
*/
class Compressor
{
public:
Compressor() {}
~Compressor() {}
/** Initializes compressor
\param sample_rate rate at which samples will be produced by the audio engine.
*/
void Init(float sample_rate);
/** Compress the audio input signal, saves the calculated gain
\param in audio input signal
*/
float Process(float in);
/** Compresses the audio input signal, keyed by a secondary input.
\param in audio input signal (to be compressed)
\param key audio input that will be used to side-chain the compressor
*/
float Process(float in, float key)
{
Process(key);
return Apply(in);
}
/** Apply compression to the audio signal, based on the previously calculated gain
\param in audio input signal
*/
float Apply(float in) { return gain_ * in; }
/** Compresses a block of audio
\param in audio input signal
\param out audio output signal
\param size the size of the block
*/
void ProcessBlock(float *in, float *out, size_t size)
{
ProcessBlock(in, out, in, size);
}
/** Compresses a block of audio, keyed by a secondary input
\param in audio input signal (to be compressed)
\param out audio output signal
\param key audio input that will be used to side-chain the compressor
\param size the size of the block
*/
void ProcessBlock(float *in, float *out, float *key, size_t size);
/** Compresses a block of multiple channels of audio, keyed by a secondary input
\param in audio input signals (to be compressed)
\param out audio output signals
\param key audio input that will be used to side-chain the compressor
\param channels the number of audio channels
\param size the size of the block
*/
void ProcessBlock(float **in,
float **out,
float * key,
size_t channels,
size_t size);
/** Gets the amount of gain reduction */
float GetRatio() { return ratio_; }
/** Sets the amount of gain reduction applied to compressed signals
\param ratio Expects 1.0 -> 40. (untested with values < 1.0)
*/
void SetRatio(float ratio)
{
ratio_ = ratio;
RecalculateRatio();
}
/** Gets the threshold in dB */
float GetThreshold() { return thresh_; }
/** Sets the threshold in dB at which compression will be applied
\param threshold Expects 0.0 -> -80.
*/
void SetThreshold(float threshold)
{
thresh_ = threshold;
RecalculateMakeup();
}
/** Gets the envelope time for onset of compression */
float GetAttack() { return atk_; }
/** Sets the envelope time for onset of compression for signals above the threshold.
\param attack Expects 0.001 -> 10
*/
void SetAttack(float attack)
{
atk_ = attack;
RecalculateAttack();
}
/** Gets the envelope time for release of compression */
float GetRelease() { return rel_; }
/** Sets the envelope time for release of compression as input signal falls below threshold.
\param release Expects 0.001 -> 10
*/
void SetRelease(float release)
{
rel_ = release;
RecalculateRelease();
}
/** Gets the additional gain to make up for the compression */
float GetMakeup() { return makeup_gain_; }
/** Manually sets the additional gain to make up for the compression
\param gain Expects 0.0 -> 80
*/
void SetMakeup(float gain) { makeup_gain_ = gain; }
/** Enables or disables the automatic makeup gain. Disabling sets the makeup gain to 0.0
\param enable true to enable, false to disable
*/
void AutoMakeup(bool enable)
{
makeup_auto_ = enable;
makeup_gain_ = 0.0f;
RecalculateMakeup();
}
/** Gets the gain reduction in dB
*/
float GetGain() { return fastlog10f(gain_) * 20.0f; }
private:
float ratio_, thresh_, atk_, rel_;
float makeup_gain_;
float gain_;
// Recorded slope and gain, used in next sample
float slope_rec_, gain_rec_;
// Internals from faust
float atk_slo2_, ratio_mul_, atk_slo_, rel_slo_;
int sample_rate_;
float sample_rate_inv2_, sample_rate_inv_;
// Auto makeup gain enable
bool makeup_auto_;
// Methods for recalculating internals
void RecalculateRatio()
{
ratio_mul_ = ((1.0f - atk_slo2_) * ((1.0f / ratio_) - 1.0f));
}
void RecalculateAttack()
{
atk_slo_ = expf(-(sample_rate_inv_ / atk_));
atk_slo2_ = expf(-(sample_rate_inv2_ / atk_));
RecalculateRatio();
}
void RecalculateRelease() { rel_slo_ = expf((-(sample_rate_inv_ / rel_))); }
void RecalculateMakeup()
{
if(makeup_auto_)
makeup_gain_ = fabsf(thresh_ - thresh_ / ratio_) * 0.5f;
}
};
} // namespace daisysp
#endif // DSY_COMPRESSOR_H

@ -0,0 +1,37 @@
#include <math.h>
#include "crossfade.h"
#include "../utility/dsp.h"
#define REALLYSMALLFLOAT 0.000001f
using namespace daisysp;
const float kCrossLogMin = logf(REALLYSMALLFLOAT);
const float kCrossLogMax = logf(1.0f);
float CrossFade::Process(float &in1, float &in2)
{
float scalar_1, scalar_2;
switch(curve_)
{
case CROSSFADE_LIN:
scalar_1 = pos_;
return (in1 * (1.0f - scalar_1)) + (in2 * scalar_1);
case CROSSFADE_CPOW:
scalar_1 = sinf(pos_ * HALFPI_F);
scalar_2 = sinf((1.0f - pos_) * HALFPI_F);
return (in1 * scalar_2) + (in2 * scalar_1);
case CROSSFADE_LOG:
scalar_1
= expf(pos_ * (kCrossLogMax - kCrossLogMin) + kCrossLogMin);
return (in1 * (1.0f - scalar_1)) + (in2 * scalar_1);
case CROSSFADE_EXP:
scalar_1 = pos_ * pos_;
return (in1 * (1.0f - scalar_1)) + (in2 * scalar_1);
default: return 0;
}
}

@ -0,0 +1,78 @@
#pragma once
#ifndef DSY_CROSSFADE_H
#define DSY_CROSSFADE_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Curve applied to the CrossFade
- LIN = linear
- CPOW = constant power
- LOG = logarithmic
- EXP exponential
- LAST = end of enum (used for array indexing)
*/
enum
{
CROSSFADE_LIN,
CROSSFADE_CPOW,
CROSSFADE_LOG,
CROSSFADE_EXP,
CROSSFADE_LAST,
};
/** Performs a CrossFade between two signals
Original author: Paul Batchelor
Ported from Soundpipe by Andrew Ikenberry
added curve option for constant power, etc.
*/
class CrossFade
{
public:
CrossFade() {}
~CrossFade() {}
/** Initializes CrossFade module
Defaults
- current position = .5
- curve = linear
*/
inline void Init(int curve)
{
pos_ = 0.5f;
curve_ = curve < CROSSFADE_LAST ? curve : CROSSFADE_LIN;
}
/** Initialize with default linear curve
*/
inline void Init() { Init(CROSSFADE_LIN); }
/** processes CrossFade and returns single sample
*/
float Process(float &in1, float &in2);
/** Sets position of CrossFade between two input signals
Input range: 0 to 1
*/
inline void SetPos(float pos) { pos_ = pos; }
/** Sets current curve applied to CrossFade
Expected input: See [Curve Options](##curve-options)
*/
inline void SetCurve(uint8_t curve) { curve_ = curve; }
/** Returns current position
*/
inline float GetPos(float pos) { return pos_; }
/** Returns current curve
*/
inline uint8_t GetCurve(uint8_t curve) { return curve_; }
private:
float pos_;
uint8_t curve_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,29 @@
#include "../utility/dsp.h"
#include "limiter.h"
#define SLOPE(out, in, positive, negative) \
{ \
float error = (in)-out; \
out += (error > 0 ? positive : negative) * error; \
}
namespace daisysp
{
void Limiter::Init()
{
peak_ = 0.5f;
}
void Limiter::ProcessBlock(float *in, size_t size, float pre_gain)
{
while(size--)
{
float pre = *in * pre_gain;
float peak = fabsf(pre);
SLOPE(peak_, peak, 0.05f, 0.00002f);
float gain = (peak_ <= 1.0f ? 1.0f : 1.0f / peak_);
*in++ = SoftLimit(pre * gain * 0.7f);
}
}
} //namespace daisysp

@ -0,0 +1,33 @@
#pragma once
#ifndef LIMITER_H
#define LIMITER_H
#include <stdlib.h>
namespace daisysp
{
/** Simple Peak Limiter
This was extracted from pichenettes/stmlib.
Credit to pichenettes/Mutable Instruments
*/
class Limiter
{
public:
Limiter() {}
~Limiter() {}
/** Initializes the Limiter instance.
*/
void Init();
/** Processes a block of audio through the limiter.
\param in - pointer to a block of audio samples to be processed. The buffer is operated on directly.
\param size - size of the buffer "in"
\param pre_gain - amount of pre_gain applied to the signal.
*/
void ProcessBlock(float *in, size_t size, float pre_gain);
private:
float peak_;
};
} // namespace daisysp
#endif

@ -0,0 +1,65 @@
#include "autowah.h"
#include <math.h>
using namespace daisysp;
void Autowah::Init(float sample_rate)
{
sampling_freq_ = sample_rate;
const1_ = 1413.72f / sampling_freq_;
const2_ = expf(0.0f - (100.0f / sampling_freq_));
const4_ = expf(0.0f - (10.0f / sampling_freq_));
wet_dry_ = 100.0f;
level_ = 0.1f;
wah_ = 0.0;
for(int i = 0; i < 2; i++)
{
rec1_[i] = rec2_[i] = rec3_[i] = rec4_[i] = rec5_[i] = 0.0f;
}
for(int i = 0; i < 3; i++)
{
rec0_[i] = 0.0f;
}
}
float Autowah::Process(float in)
{
float out;
float fSlow2 = (0.01f * (wet_dry_ * level_));
float fSlow3 = (1.0f - 0.01f * wet_dry_) + (1.f - wah_);
float fTemp1 = fabs(in);
rec3_[0]
= fmaxf(fTemp1, (const4_ * rec3_[1]) + ((1.0f - const4_) * fTemp1));
rec2_[0] = (const2_ * rec2_[1]) + ((1.0f - const2_) * rec3_[0]);
float fTemp2 = fminf(1.0f, rec2_[0]);
float fTemp3 = powf(2.0f, (2.3f * fTemp2));
float fTemp4
= 1.0f
- (const1_ * fTemp3 / powf(2.0f, (1.0f + 2.0f * (1.0f - fTemp2))));
rec1_[0]
= ((0.999f * rec1_[1])
+ (0.001f
* (0.0f - (2.0f * (fTemp4 * cosf((const1_ * 2 * fTemp3)))))));
rec4_[0] = ((0.999f * rec4_[1]) + (0.001f * fTemp4 * fTemp4));
rec5_[0] = ((0.999f * rec5_[1]) + (0.0001f * powf(4.0f, fTemp2)));
rec0_[0] = (0.0f
- (((rec1_[0] * rec0_[1]) + (rec4_[0] * rec0_[2]))
- (fSlow2 * (rec5_[0] * in))));
out = ((wah_ * (rec0_[0] - rec0_[1])) + (fSlow3 * in));
rec3_[1] = rec3_[0];
rec2_[1] = rec2_[0];
rec1_[1] = rec1_[0];
rec4_[1] = rec4_[0];
rec5_[1] = rec5_[0];
rec0_[2] = rec0_[1];
rec0_[1] = rec0_[0];
return out;
}

@ -0,0 +1,52 @@
#pragma once
#ifndef DSY_AUTOWAH_H
#define DSY_AUTOWAH_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Autowah module
Original author(s) :
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Autowah
{
public:
Autowah() {}
~Autowah() {}
/** Initializes the Autowah module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Initializes the Autowah module.
\param in - input signal to be wah'd
*/
float Process(float in);
/** sets wah
\param wah : set wah amount
*/
inline void SetWah(float wah) { wah_ = wah; }
/** sets mix amount
\param drywet : set effect dry/wet
*/
inline void SetDryWet(float drywet) { wet_dry_ = drywet; }
/** sets wah level
\param level : set wah level
*/
inline void SetLevel(float level) { level_ = level; }
private:
float sampling_freq_, const1_, const2_, const4_, wah_, level_, wet_dry_,
rec0_[3], rec1_[2], rec2_[2], rec3_[2], rec4_[2], rec5_[2];
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,34 @@
#include "bitcrush.h"
#include "fold.h"
#include <math.h>
using namespace daisysp;
static Fold fold;
void Bitcrush::Init(float sample_rate)
{
bit_depth_ = 8;
crush_rate_ = 10000;
sample_rate_ = sample_rate;
fold.Init();
}
float Bitcrush::Process(float in)
{
float bits = pow(2, bit_depth_);
float foldamt = sample_rate_ / crush_rate_;
float out;
out = in * 65536.0f;
out += 32768;
out *= (bits / 65536.0f);
out = floor(out);
out *= (65536.0f / bits) - 32768;
fold.SetIncrement(foldamt);
out = fold.Process(out);
out /= 65536.0;
return out;
}

@ -0,0 +1,47 @@
#pragma once
#ifndef DSY_BITCRUSH_H
#define DSY_BITCRUSH_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** bitcrush module
Original author(s) : Paul Batchelor,
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Bitcrush
{
public:
Bitcrush() {}
~Bitcrush() {}
/** Initializes the bitcrush module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** bit crushes and downsamples the input
*/
float Process(float in);
/** adjusts bitdepth
\param bitdepth : Sets bit depth.
*/
inline void SetBitDepth(int bitdepth) { bit_depth_ = bitdepth; }
/** adjusts the downsampling frequency
\param crushrate : Sets rate to downsample to.
*/
inline void SetCrushRate(float crushrate) { crush_rate_ = crushrate; }
private:
float sample_rate_, crush_rate_;
int bit_depth_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,187 @@
#include "../utility/dsp.h"
#include "chorus.h"
#include <math.h>
using namespace daisysp;
//ChorusEngine stuff
void ChorusEngine::Init(float sample_rate)
{
sample_rate_ = sample_rate;
del_.Init();
lfo_amp_ = 0.f;
feedback_ = .2f;
SetDelay(.75);
lfo_phase_ = 0.f;
SetLfoFreq(.3f);
SetLfoDepth(.9f);
}
float ChorusEngine::Process(float in)
{
float lfo_sig = ProcessLfo();
del_.SetDelay(lfo_sig + delay_);
float out = del_.Read();
del_.Write(in + out * feedback_);
return (in + out) * .5f; //equal mix
}
void ChorusEngine::SetLfoDepth(float depth)
{
depth = fclamp(depth, 0.f, .93f);
lfo_amp_ = depth * delay_;
}
void ChorusEngine::SetLfoFreq(float freq)
{
freq = 4.f * freq / sample_rate_;
freq *= lfo_freq_ < 0.f ? -1.f : 1.f; //if we're headed down, keep going
lfo_freq_ = fclamp(freq, -.25f, .25f); //clip at +/- .125 * sr
}
void ChorusEngine::SetDelay(float delay)
{
delay = (.1f + delay * 7.9f); //.1 to 8 ms
SetDelayMs(delay);
}
void ChorusEngine::SetDelayMs(float ms)
{
ms = fmax(.1f, ms);
delay_ = ms * .001f * sample_rate_; //ms to samples
lfo_amp_ = fmin(lfo_amp_, delay_); //clip this if needed
}
void ChorusEngine::SetFeedback(float feedback)
{
feedback_ = fclamp(feedback, 0.f, 1.f);
}
float ChorusEngine::ProcessLfo()
{
lfo_phase_ += lfo_freq_;
//wrap around and flip direction
if(lfo_phase_ > 1.f)
{
lfo_phase_ = 1.f - (lfo_phase_ - 1.f);
lfo_freq_ *= -1.f;
}
else if(lfo_phase_ < -1.f)
{
lfo_phase_ = -1.f - (lfo_phase_ + 1.f);
lfo_freq_ *= -1.f;
}
return lfo_phase_ * lfo_amp_;
}
//Chorus Stuff
void Chorus::Init(float sample_rate)
{
engines_[0].Init(sample_rate);
engines_[1].Init(sample_rate);
SetPan(.25f, .75f);
gain_frac_ = .5f;
sigl_ = sigr_ = 0.f;
}
float Chorus::Process(float in)
{
sigl_ = 0.f;
sigr_ = 0.f;
for(int i = 0; i < 2; i++)
{
float sig = engines_[i].Process(in);
sigl_ += (1.f - pan_[i]) * sig;
sigr_ += pan_[i] * sig;
}
sigl_ *= gain_frac_;
sigr_ *= gain_frac_;
return sigl_;
}
float Chorus::GetLeft()
{
return sigl_;
}
float Chorus::GetRight()
{
return sigr_;
}
void Chorus::SetPan(float panl, float panr)
{
pan_[0] = fclamp(panl, 0.f, 1.f);
pan_[1] = fclamp(panr, 0.f, 1.f);
}
void Chorus::SetPan(float pan)
{
SetPan(pan, pan);
}
void Chorus::SetLfoDepth(float depthl, float depthr)
{
engines_[0].SetLfoDepth(depthl);
engines_[1].SetLfoDepth(depthr);
}
void Chorus::SetLfoDepth(float depth)
{
SetLfoDepth(depth, depth);
}
void Chorus::SetLfoFreq(float freql, float freqr)
{
engines_[0].SetLfoFreq(freql);
engines_[1].SetLfoFreq(freqr);
}
void Chorus::SetLfoFreq(float freq)
{
SetLfoFreq(freq, freq);
}
void Chorus::SetDelay(float delayl, float delayr)
{
engines_[0].SetDelay(delayl);
engines_[1].SetDelay(delayr);
}
void Chorus::SetDelay(float delay)
{
SetDelay(delay, delay);
}
void Chorus::SetDelayMs(float msl, float msr)
{
engines_[0].SetDelayMs(msl);
engines_[1].SetDelayMs(msr);
}
void Chorus::SetDelayMs(float ms)
{
SetDelayMs(ms, ms);
}
void Chorus::SetFeedback(float feedbackl, float feedbackr)
{
engines_[0].SetFeedback(feedbackl);
engines_[1].SetFeedback(feedbackr);
}
void Chorus::SetFeedback(float feedback)
{
SetFeedback(feedback, feedback);
}

@ -0,0 +1,182 @@
#pragma once
#ifndef DSY_CHORUS_H
#define DSY_CHORUS_H
#ifdef __cplusplus
#include <stdint.h>
#include "Utility/delayline.h"
/** @file chorus.h */
namespace daisysp
{
/**
@brief Single Chorus engine. Used in Chorus.
@author Ben Sergentanis
*/
class ChorusEngine
{
public:
ChorusEngine() {}
~ChorusEngine() {}
/** Initialize the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Get the next sample
\param in Sample to process
*/
float Process(float in);
/** How much to modulate the delay by.
\param depth Works 0-1.
*/
void SetLfoDepth(float depth);
/** Set lfo frequency.
\param freq Frequency in Hz
*/
void SetLfoFreq(float freq);
/** Set the internal delay rate.
\param delay Tuned for 0-1. Maps to .1 to 50 ms.
*/
void SetDelay(float delay);
/** Set the delay time in ms.
\param ms Delay time in ms.
*/
void SetDelayMs(float ms);
/** Set the feedback amount.
\param feedback Amount from 0-1.
*/
void SetFeedback(float feedback);
private:
float sample_rate_;
static constexpr int32_t kDelayLength
= 2400; // 50 ms at 48kHz = .05 * 48000
//triangle lfos
float lfo_phase_;
float lfo_freq_;
float lfo_amp_;
float feedback_;
float delay_;
DelayLine<float, kDelayLength> del_;
float ProcessLfo();
};
//wraps up all of the chorus engines
/**
@brief Chorus Effect.
@author Ben Sergentanis
@date Jan 2021
Based on https://www.izotope.com/en/learn/understanding-chorus-flangers-and-phasers-in-audio-production.html \n
and https://www.researchgate.net/publication/236629475_Implementing_Professional_Audio_Effects_with_DSPs \n
*/
class Chorus
{
public:
Chorus() {}
~Chorus() {}
/** Initialize the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the net floating point sample. Defaults to left channel.
\param in Sample to process
*/
float Process(float in);
/** Get the left channel's last sample */
float GetLeft();
/** Get the right channel's last sample */
float GetRight();
/** Pan both channels individually.
\param panl Pan the left channel. 0 is left, 1 is right.
\param panr Pan the right channel.
*/
void SetPan(float panl, float panr);
/** Pan both channels.
\param pan Where to pan both channels to. 0 is left, 1 is right.
*/
void SetPan(float pan);
/** Set both lfo depths individually.
\param depthl Left channel lfo depth. Works 0-1.
\param depthr Right channel lfo depth.
*/
void SetLfoDepth(float depthl, float depthr);
/** Set both lfo depths.
\param depth Both channels lfo depth. Works 0-1.
*/
void SetLfoDepth(float depth);
/** Set both lfo frequencies individually.
\param depthl Left channel lfo freq in Hz.
\param depthr Right channel lfo freq in Hz.
*/
void SetLfoFreq(float freql, float freqr);
/** Set both lfo frequencies.
\param depth Both channel lfo freqs in Hz.
*/
void SetLfoFreq(float freq);
/** Set both channel delay amounts individually.
\param delayl Left channel delay amount. Works 0-1.
\param delayr Right channel delay amount.
*/
void SetDelay(float delayl, float delayr);
/** Set both channel delay amounts.
\param delay Both channel delay amount. Works 0-1.
*/
void SetDelay(float delay);
/** Set both channel delay individually.
\param msl Left channel delay in ms.
\param msr Right channel delay in ms.
*/
void SetDelayMs(float msl, float msr);
/** Set both channel delay in ms.
\param ms Both channel delay amounts in ms.
*/
void SetDelayMs(float ms);
/** Set both channels feedback individually.
\param feedbackl Left channel feedback. Works 0-1.
\param feedbackr Right channel feedback.
*/
void SetFeedback(float feedbackl, float feedbackr);
/** Set both channels feedback.
\param feedback Both channel feedback. Works 0-1.
*/
void SetFeedback(float feedback);
private:
ChorusEngine engines_[2];
float gain_frac_;
float pan_[2];
float sigl_, sigr_;
};
} //namespace daisysp
#endif
#endif

@ -0,0 +1,32 @@
#include "decimator.h"
using namespace daisysp;
void Decimator::Init()
{
downsample_factor_ = 1.0f;
bitcrush_factor_ = 0.0f;
downsampled_ = 0.0f;
bitcrushed_ = 0.0f;
inc_ = 0;
threshold_ = 0;
}
float Decimator::Process(float input)
{
int32_t temp;
//downsample
threshold_ = (uint32_t)((downsample_factor_ * downsample_factor_) * 96.0f);
inc_ += 1;
if(inc_ > threshold_)
{
inc_ = 0;
downsampled_ = input;
}
//bitcrush
temp = (int32_t)(downsampled_ * 65536.0f);
temp >>= bits_to_crush_; // shift off
temp <<= bits_to_crush_; // move back with zeros
bitcrushed_ = (float)temp / 65536.0f;
return bitcrushed_;
}

@ -0,0 +1,69 @@
#pragma once
#ifndef DECIMATOR_H
#define DECIMATOR_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Performs downsampling and bitcrush effects
*/
class Decimator
{
public:
Decimator() {}
~Decimator() {}
/** Initializes downsample module
*/
void Init();
/** Applies downsample and bitcrush effects to input signal.
\return one sample. This should be called once per sample period.
*/
float Process(float input);
/** Sets amount of downsample
Input range:
*/
inline void SetDownsampleFactor(float downsample_factor)
{
downsample_factor_ = downsample_factor;
}
/** Sets amount of bitcrushing
Input range:
*/
inline void SetBitcrushFactor(float bitcrush_factor)
{
// bitcrush_factor_ = bitcrush_factor;
bits_to_crush_ = (uint32_t)(bitcrush_factor * kMaxBitsToCrush);
}
/** Sets the exact number of bits to crush
0-16 bits
*/
inline void SetBitsToCrush(const uint8_t &bits)
{
bits_to_crush_ = bits <= kMaxBitsToCrush ? bits : kMaxBitsToCrush;
}
/** Returns current setting of downsample
*/
inline float GetDownsampleFactor() { return downsample_factor_; }
/** Returns current setting of bitcrush
*/
inline float GetBitcrushFactor() { return bitcrush_factor_; }
private:
const uint8_t kMaxBitsToCrush = 16;
float downsample_factor_, bitcrush_factor_;
uint32_t bits_to_crush_;
float downsampled_, bitcrushed_;
uint32_t inc_, threshold_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,83 @@
#include "../utility/dsp.h"
#include "flanger.h"
#include <math.h>
using namespace daisysp;
void Flanger::Init(float sample_rate)
{
sample_rate_ = sample_rate;
SetFeedback(.2f);
del_.Init();
lfo_amp_ = 0.f;
SetDelay(.75);
lfo_phase_ = 0.f;
SetLfoFreq(.3);
SetLfoDepth(.9);
}
float Flanger::Process(float in)
{
float lfo_sig = ProcessLfo();
del_.SetDelay(1.f + lfo_sig + delay_);
float out = del_.Read();
del_.Write(in + out * feedback_);
return (in + out) * .5f; //equal mix
}
void Flanger::SetFeedback(float feedback)
{
feedback_ = fclamp(feedback, 0.f, 1.f);
feedback_ *= .97f;
}
void Flanger::SetLfoDepth(float depth)
{
depth = fclamp(depth, 0.f, .93f);
lfo_amp_ = depth * delay_;
}
void Flanger::SetLfoFreq(float freq)
{
freq = 4.f * freq / sample_rate_;
freq *= lfo_freq_ < 0.f ? -1.f : 1.f; //if we're headed down, keep going
lfo_freq_ = fclamp(freq, -.25f, .25f); //clip at +/- .125 * sr
}
void Flanger::SetDelay(float delay)
{
delay = (.1f + delay * 6.9); //.1 to 7 ms
SetDelayMs(delay);
}
void Flanger::SetDelayMs(float ms)
{
ms = fmax(.1, ms);
delay_ = ms * .001f * sample_rate_; //ms to samples
lfo_amp_ = fmin(lfo_amp_, delay_); //clip this if needed
}
float Flanger::ProcessLfo()
{
lfo_phase_ += lfo_freq_;
//wrap around and flip direction
if(lfo_phase_ > 1.f)
{
lfo_phase_ = 1.f - (lfo_phase_ - 1.f);
lfo_freq_ *= -1.f;
}
else if(lfo_phase_ < -1.f)
{
lfo_phase_ = -1.f - (lfo_phase_ + 1.f);
lfo_freq_ *= -1.f;
}
return lfo_phase_ * lfo_amp_;
}

@ -0,0 +1,75 @@
#pragma once
#ifndef DSY_FLANGER_H
#define DSY_FLANGER_H
#ifdef __cplusplus
#include <stdint.h>
#include "Utility/delayline.h"
/** @file flanger.h */
namespace daisysp
{
/** @brief Flanging Audio Effect
*
* Generates a modulating phase shifted copy of a signal, and recombines
* with the original to create a 'flanging' sound effect.
*/
class Flanger
{
public:
/** Initialize the modules
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Get the next sample
\param in Sample to process
*/
float Process(float in);
/** How much of the signal to feedback into the delay line.
\param feedback Works 0-1.
*/
void SetFeedback(float feedback);
/** How much to modulate the delay by.
\param depth Works 0-1.
*/
void SetLfoDepth(float depth);
/** Set lfo frequency.
\param freq Frequency in Hz
*/
void SetLfoFreq(float freq);
/** Set the internal delay rate.
\param delay Tuned for 0-1. Maps to .1 to 7 ms.
*/
void SetDelay(float delay);
/** Set the delay time in ms.
\param ms Delay time in ms.
*/
void SetDelayMs(float ms);
private:
float sample_rate_;
static constexpr int32_t kDelayLength = 960; // 20 ms at 48kHz = .02 * 48000
float feedback_;
//triangle lfos
float lfo_phase_;
float lfo_freq_;
float lfo_amp_;
float delay_;
DelayLine<float, kDelayLength> del_;
float ProcessLfo();
};
} //namespace daisysp
#endif
#endif

@ -0,0 +1,31 @@
#include "fold.h"
#include <math.h>
using namespace daisysp;
void Fold::Init()
{
incr_ = 1000.f;
sample_index_ = 0;
index_ = 0.0f;
value_ = 0.0f;
}
float Fold::Process(float in)
{
float out;
if(index_ < sample_index_)
{
index_ += incr_;
out = value_ = in;
}
else
{
out = value_;
}
sample_index_++;
return out;
}

@ -0,0 +1,44 @@
#pragma once
#ifndef DSY_FOLD_H
#define DSY_FOLD_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** fold module
Original author(s) : John FFitch, Gabriel Maldonado
Year : 1998
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Fold
{
public:
Fold() {}
~Fold() {}
/** Initializes the fold module.
*/
void Init();
/** applies foldvoer distortion to input
*/
float Process(float in);
/**
\param incr : set fold increment
*/
inline void SetIncrement(float incr) { incr_ = incr; }
private:
float incr_, index_, value_;
int sample_index_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,32 @@
#include "../utility/dsp.h"
#include "overdrive.h"
#include <algorithm>
namespace daisysp
{
void Overdrive::Init()
{
SetDrive(.5f);
}
float Overdrive::Process(float in)
{
float pre = pre_gain_ * in;
return SoftClip(pre) * post_gain_;
}
void Overdrive::SetDrive(float drive)
{
drive = fclamp(drive, 0.f, 1.f);
drive_ = 2.f * drive;
const float drive_2 = drive_ * drive_;
const float pre_gain_a = drive_ * 0.5f;
const float pre_gain_b = drive_2 * drive_2 * drive_ * 24.0f;
pre_gain_ = pre_gain_a + (pre_gain_b - pre_gain_a) * drive_2;
const float drive_squashed = drive_ * (2.0f - drive_);
post_gain_ = 1.0f / SoftClip(0.33f + drive_squashed * (pre_gain_ - 0.33f));
}
} // namespace daisysp

@ -0,0 +1,46 @@
#pragma once
#ifndef DSY_OVERDRIVE_H
#define DSY_OVERDRIVE_H
#include <stdint.h>
#ifdef __cplusplus
/** @file overdrive.h */
namespace daisysp
{
/**
@brief Distortion / Overdrive Module
@author Ported by Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/fx/overdrive.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2014. \n
*/
class Overdrive
{
public:
Overdrive() {}
~Overdrive() {}
/** Initializes the module with 0 gain */
void Init();
/** Get the next sample
\param in Input to be overdriven
*/
float Process(float in);
/** Set the amount of drive
\param drive Works from 0-1
*/
void SetDrive(float drive);
private:
float drive_;
float pre_gain_;
float post_gain_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,138 @@
#include "../utility/dsp.h"
#include "phaser.h"
#include <math.h>
using namespace daisysp;
//PhaserEngine stuff
void PhaserEngine::Init(float sample_rate)
{
sample_rate_ = sample_rate;
del_.Init();
lfo_amp_ = 0.f;
feedback_ = .2f;
SetFreq(200.f);
del_.SetDelay(0.f);
os_ = 30.f; //30 hertz frequency offset, lower than this introduces crunch
deltime_ = 0.f;
last_sample_ = 0.f;
lfo_phase_ = 0.f;
SetLfoFreq(.3);
SetLfoDepth(.9);
}
float PhaserEngine::Process(float in)
{
float lfo_sig = ProcessLfo();
fonepole(deltime_, sample_rate_ / (lfo_sig + ap_freq_ + os_), .0001f);
last_sample_ = del_.Allpass(in + feedback_ * last_sample_, deltime_, .3f);
return (in + last_sample_) * .5f; //equal mix
}
void PhaserEngine::SetLfoDepth(float depth)
{
lfo_amp_ = fclamp(depth, 0.f, 1.f);
}
void PhaserEngine::SetLfoFreq(float lfo_freq)
{
lfo_freq = 4.f * lfo_freq / sample_rate_;
lfo_freq *= lfo_freq_ < 0.f ? -1.f : 1.f; //if we're headed down, keep going
lfo_freq_ = fclamp(lfo_freq, -.25f, .25f); //clip at +/- .125 * sr
}
void PhaserEngine::SetFreq(float ap_freq)
{
ap_freq_ = fclamp(ap_freq, 0.f, 20000.f); //0 - 20kHz
}
void PhaserEngine::SetFeedback(float feedback)
{
feedback_ = fclamp(feedback, 0.f, .75f);
}
float PhaserEngine::ProcessLfo()
{
lfo_phase_ += lfo_freq_;
//wrap around and flip direction
if(lfo_phase_ > 1.f)
{
lfo_phase_ = 1.f - (lfo_phase_ - 1.f);
lfo_freq_ *= -1.f;
}
else if(lfo_phase_ < -1.f)
{
lfo_phase_ = -1.f - (lfo_phase_ + 1.f);
lfo_freq_ *= -1.f;
}
return lfo_phase_ * lfo_amp_ * ap_freq_;
}
//Phaser Stuff
void Phaser::Init(float sample_rate)
{
for(size_t i = 0; i < kMaxPoles; i++)
{
engines_[i].Init(sample_rate);
}
poles_ = 4;
gain_frac_ = .5f;
}
float Phaser::Process(float in)
{
float sig = 0.f;
for(int i = 0; i < poles_; i++)
{
sig += engines_[i].Process(in);
}
return sig;
}
void Phaser::SetPoles(int poles)
{
poles_ = DSY_CLAMP(poles, 1, 8);
}
void Phaser::SetLfoDepth(float depth)
{
for(int i = 0; i < kMaxPoles; i++)
{
engines_[i].SetLfoDepth(depth);
}
}
void Phaser::SetLfoFreq(float lfo_freq)
{
for(int i = 0; i < kMaxPoles; i++)
{
engines_[i].SetLfoFreq(lfo_freq);
}
}
void Phaser::SetFreq(float ap_freq)
{
for(int i = 0; i < kMaxPoles; i++)
{
engines_[i].SetFreq(ap_freq);
}
}
void Phaser::SetFeedback(float feedback)
{
for(int i = 0; i < kMaxPoles; i++)
{
engines_[i].SetFeedback(feedback);
}
}

@ -0,0 +1,131 @@
#pragma once
#ifndef DSY_PHASER_H
#define DSY_PHASER_H
#ifdef __cplusplus
#include <stdint.h>
#include "Utility/delayline.h"
/** @file phaser.h */
namespace daisysp
{
/**
@brief Single Phaser engine. Used in Phaser.
@author Ben Sergentanis
*/
class PhaserEngine
{
public:
PhaserEngine() {}
~PhaserEngine() {}
/** Initialize the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Get the next sample
\param in Sample to process
*/
float Process(float in);
/** How much to modulate the allpass filter by.
\param depth Works 0-1.
*/
void SetLfoDepth(float depth);
/** Set lfo frequency.
\param lfo_freq Frequency in Hz
*/
void SetLfoFreq(float lfo_freq);
/** Set the allpass frequency
\param ap_freq Frequency in Hz.
*/
void SetFreq(float ap_freq);
/** Set the feedback amount.
\param feedback Amount from 0-1.
*/
void SetFeedback(float feedback);
private:
float sample_rate_;
static constexpr int32_t kDelayLength
= 2400; // 50 ms at 48kHz = .05 * 48000
//triangle lfo
float lfo_phase_;
float lfo_freq_;
float lfo_amp_;
float os_;
float feedback_;
float ap_freq_;
float deltime_;
float last_sample_;
DelayLine<float, kDelayLength> del_;
float ProcessLfo();
};
//wraps up all of the phaser engines
/**
@brief Phaser Effect.
@author Ben Sergentanis
@date Jan 2021
*/
class Phaser
{
public:
Phaser() {}
~Phaser() {}
/** Initialize the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the next floating point sample.
\param in Sample to process
*/
float Process(float in);
/** Number of allpass stages.
\param poles Works 1 to 8.
*/
void SetPoles(int poles);
/** Set all lfo depths
\param depth Works 0-1.
*/
void SetLfoDepth(float depth);
/** Set all lfo frequencies.
\param lfo_freq Lfo freq in Hz.
*/
void SetLfoFreq(float lfo_freq);
/** Set all channel allpass freq in Hz.
\param ap_freq Frequency in Hz.
*/
void SetFreq(float ap_freq);
/** Set all channels feedback.
\param feedback Works 0-1.
*/
void SetFeedback(float feedback);
private:
static constexpr int kMaxPoles = 8;
PhaserEngine engines_[kMaxPoles];
float gain_frac_;
int poles_;
};
} //namespace daisysp
#endif
#endif

@ -0,0 +1,211 @@
#pragma once
#ifndef DSY_PITCHSHIFTER_H
#define DSY_PITCHSHIFTER_H
#include <stdint.h>
#include <cmath>
#ifdef USE_ARM_DSP
#include "arm_math.h"
#endif
#include "Utility/dsp.h"
#include "Utility/delayline.h"
#include "Control/phasor.h"
/** Shift can be 30-100 ms lets just start with 50 for now.
0.050 * SR = 2400 samples (at 48kHz)
*/
#define SHIFT_BUFFER_SIZE 16384
//#define SHIFT_BUFFER_SIZE 4800
//#define SHIFT_BUFFER_SIZE 8192
//#define SHIFT_BUFFER_SIZE 1024
namespace daisysp
{
static inline uint32_t hash_xs32(uint32_t x)
{
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
}
inline uint32_t myrand()
{
static uint32_t seed = 1;
seed = hash_xs32(seed);
return seed;
}
/** time-domain pitchshifter
Author: shensley
Based on "Pitch Shifting" from ucsd.edu
t = 1 - ((s *f) / R)
where:
s is the size of the delay
f is the frequency of the lfo
r is the sample_rate
solving for t = 12.0
f = (12 - 1) * 48000 / SHIFT_BUFFER_SIZE;
\todo - move hash_xs32 and myrand to dsp.h and give appropriate names
*/
class PitchShifter
{
public:
PitchShifter() {}
~PitchShifter() {}
/** Initialize pitch shifter
*/
void Init(float sr)
{
force_recalc_ = false;
sr_ = sr;
mod_freq_ = 5.0f;
SetSemitones();
for(uint8_t i = 0; i < 2; i++)
{
gain_[i] = 0.0f;
d_[i].Init();
phs_[i].Init(sr, 50, i == 0 ? 0 : PI_F);
}
shift_up_ = true;
del_size_ = SHIFT_BUFFER_SIZE;
SetDelSize(del_size_);
fun_ = 0.0f;
}
/** process pitch shifter
*/
float Process(float &in)
{
float val, fade1, fade2;
// First Process delay mod/crossfade
fade1 = phs_[0].Process();
fade2 = phs_[1].Process();
if(prev_phs_a_ > fade1)
{
mod_a_amt_ = fun_ * ((float)(myrand() % 255) / 255.0f)
* (del_size_ * 0.5f);
mod_coeff_[0]
= 0.0002f + (((float)(myrand() % 255) / 255.0f) * 0.001f);
}
if(prev_phs_b_ > fade2)
{
mod_b_amt_ = fun_ * ((float)(myrand() % 255) / 255.0f)
* (del_size_ * 0.5f);
mod_coeff_[1]
= 0.0002f + (((float)(myrand() % 255) / 255.0f) * 0.001f);
}
slewed_mod_[0] += mod_coeff_[0] * (mod_a_amt_ - slewed_mod_[0]);
slewed_mod_[1] += mod_coeff_[1] * (mod_b_amt_ - slewed_mod_[1]);
prev_phs_a_ = fade1;
prev_phs_b_ = fade2;
if(shift_up_)
{
fade1 = 1.0f - fade1;
fade2 = 1.0f - fade2;
}
mod_[0] = fade1 * (del_size_ - 1);
mod_[1] = fade2 * (del_size_ - 1);
#ifdef USE_ARM_DSP
gain_[0] = arm_sin_f32(fade1 * (float)M_PI);
gain_[1] = arm_sin_f32(fade2 * (float)M_PI);
#else
gain_[0] = sinf(fade1 * PI_F);
gain_[1] = sinf(fade2 * PI_F);
#endif
// Handle Delay Writing
d_[0].Write(in);
d_[1].Write(in);
// Modulate Delay Lines
//mod_a_amt = mod_b_amt = 0.0f;
d_[0].SetDelay(mod_[0] + mod_a_amt_);
d_[1].SetDelay(mod_[1] + mod_b_amt_);
d_[0].SetDelay(mod_[0] + slewed_mod_[0]);
d_[1].SetDelay(mod_[1] + slewed_mod_[1]);
val = 0.0f;
val += (d_[0].Read() * gain_[0]);
val += (d_[1].Read() * gain_[1]);
return val;
}
/** sets transposition in semitones
*/
void SetTransposition(const float &transpose)
{
float ratio;
uint8_t idx;
if(transpose_ != transpose || force_recalc_)
{
transpose_ = transpose;
idx = (uint8_t)fabsf(transpose);
ratio = semitone_ratios_[idx % 12];
ratio *= (uint8_t)(fabsf(transpose) / 12) + 1;
if(transpose > 0.0f)
{
shift_up_ = true;
}
else
{
shift_up_ = false;
}
mod_freq_ = ((ratio - 1.0f) * sr_) / del_size_;
if(mod_freq_ < 0.0f)
{
mod_freq_ = 0.0f;
}
phs_[0].SetFreq(mod_freq_);
phs_[1].SetFreq(mod_freq_);
if(force_recalc_)
{
force_recalc_ = false;
}
}
}
/** sets delay size changing the timbre of the pitchshifting
*/
void SetDelSize(uint32_t size)
{
del_size_ = size < SHIFT_BUFFER_SIZE ? size : SHIFT_BUFFER_SIZE;
force_recalc_ = true;
SetTransposition(transpose_);
}
/** sets an amount of internal random modulation, kind of sounds like tape-flutter
*/
inline void SetFun(float f) { fun_ = f; }
private:
inline void SetSemitones()
{
for(size_t i = 0; i < 12; i++)
{
semitone_ratios_[i] = powf(2.0f, (float)i / 12);
}
}
typedef DelayLine<float, SHIFT_BUFFER_SIZE> ShiftDelay;
ShiftDelay d_[2];
float pitch_shift_, mod_freq_;
uint32_t del_size_;
/** lfo stuff
*/
bool force_recalc_;
float sr_;
bool shift_up_;
Phasor phs_[2];
float gain_[2], mod_[2], transpose_;
float fun_, mod_a_amt_, mod_b_amt_, prev_phs_a_, prev_phs_b_;
float slewed_mod_[2], mod_coeff_[2];
/** pitch stuff
*/
float semitone_ratios_[12];
};
} // namespace daisysp
#endif

@ -0,0 +1,283 @@
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "reverbsc.h"
#define REVSC_OK 0
#define REVSC_NOT_OK 1
#define DEFAULT_SRATE 48000.0
#define MIN_SRATE 5000.0
#define MAX_SRATE 1000000.0
#define MAX_PITCHMOD 20.0
#define DELAYPOS_SHIFT 28
#define DELAYPOS_SCALE 0x10000000
#define DELAYPOS_MASK 0x0FFFFFFF
#ifndef M_PI
#define M_PI 3.14159265358979323846 /* pi */
#endif
using namespace daisysp;
/* kReverbParams[n][0] = delay time (in seconds) */
/* kReverbParams[n][1] = random variation in delay time (in seconds) */
/* kReverbParams[n][2] = random variation frequency (in 1/sec) */
/* kReverbParams[n][3] = random seed (0 - 32767) */
static const float kReverbParams[8][4]
= {{(2473.0 / DEFAULT_SRATE), 0.0010, 3.100, 1966.0},
{(2767.0 / DEFAULT_SRATE), 0.0011, 3.500, 29491.0},
{(3217.0 / DEFAULT_SRATE), 0.0017, 1.110, 22937.0},
{(3557.0 / DEFAULT_SRATE), 0.0006, 3.973, 9830.0},
{(3907.0 / DEFAULT_SRATE), 0.0010, 2.341, 20643.0},
{(4127.0 / DEFAULT_SRATE), 0.0011, 1.897, 22937.0},
{(2143.0 / DEFAULT_SRATE), 0.0017, 0.891, 29491.0},
{(1933.0 / DEFAULT_SRATE), 0.0006, 3.221, 14417.0}};
static int DelayLineMaxSamples(float sr, float i_pitch_mod, int n);
//static int InitDelayLine(dsy_reverbsc_dl *lp, int n);
static int DelayLineBytesAlloc(float sr, float i_pitch_mod, int n);
static const float kOutputGain = 0.35;
static const float kJpScale = 0.25;
int ReverbSc::Init(float sr)
{
i_sample_rate_ = sr;
sample_rate_ = sr;
feedback_ = 0.97;
lpfreq_ = 10000;
i_pitch_mod_ = 1;
i_skip_init_ = 0;
damp_fact_ = 1.0;
prv_lpfreq_ = 0.0;
init_done_ = 1;
int i, n_bytes = 0;
n_bytes = 0;
for(i = 0; i < 8; i++)
{
if(n_bytes > DSY_REVERBSC_MAX_SIZE)
return 1;
delay_lines_[i].buf = (aux_) + n_bytes;
InitDelayLine(&delay_lines_[i], i);
n_bytes += DelayLineBytesAlloc(sr, 1, i);
}
return 0;
}
static int DelayLineMaxSamples(float sr, float i_pitch_mod, int n)
{
float max_del;
max_del = kReverbParams[n][0];
max_del += (kReverbParams[n][1] * (float)i_pitch_mod * 1.125);
return (int)(max_del * sr + 16.5);
}
static int DelayLineBytesAlloc(float sr, float i_pitch_mod, int n)
{
int n_bytes = 0;
n_bytes += (DelayLineMaxSamples(sr, i_pitch_mod, n) * (int)sizeof(float));
return n_bytes;
}
void ReverbSc::NextRandomLineseg(ReverbScDl *lp, int n)
{
float prv_del, nxt_del, phs_inc_val;
/* update random seed */
if(lp->seed_val < 0)
lp->seed_val += 0x10000;
lp->seed_val = (lp->seed_val * 15625 + 1) & 0xFFFF;
if(lp->seed_val >= 0x8000)
lp->seed_val -= 0x10000;
/* length of next segment in samples */
lp->rand_line_cnt = (int)((sample_rate_ / kReverbParams[n][2]) + 0.5);
prv_del = (float)lp->write_pos;
prv_del -= ((float)lp->read_pos
+ ((float)lp->read_pos_frac / (float)DELAYPOS_SCALE));
while(prv_del < 0.0)
prv_del += lp->buffer_size;
prv_del = prv_del / sample_rate_; /* previous delay time in seconds */
nxt_del = (float)lp->seed_val * kReverbParams[n][1] / 32768.0;
/* next delay time in seconds */
nxt_del = kReverbParams[n][0] + (nxt_del * (float)i_pitch_mod_);
/* calculate phase increment per sample */
phs_inc_val = (prv_del - nxt_del) / (float)lp->rand_line_cnt;
phs_inc_val = phs_inc_val * sample_rate_ + 1.0;
lp->read_pos_frac_inc = (int)(phs_inc_val * DELAYPOS_SCALE + 0.5);
}
int ReverbSc::InitDelayLine(ReverbScDl *lp, int n)
{
float read_pos;
/* int i; */
/* calculate length of delay line */
lp->buffer_size = DelayLineMaxSamples(sample_rate_, 1, n);
lp->dummy = 0;
lp->write_pos = 0;
/* set random seed */
lp->seed_val = (int)(kReverbParams[n][3] + 0.5);
/* set initial delay time */
read_pos = (float)lp->seed_val * kReverbParams[n][1] / 32768;
read_pos = kReverbParams[n][0] + (read_pos * (float)i_pitch_mod_);
read_pos = (float)lp->buffer_size - (read_pos * sample_rate_);
lp->read_pos = (int)read_pos;
read_pos = (read_pos - (float)lp->read_pos) * (float)DELAYPOS_SCALE;
lp->read_pos_frac = (int)(read_pos + 0.5);
/* initialise first random line segment */
NextRandomLineseg(lp, n);
/* clear delay line to zero */
lp->filter_state = 0.0;
for(int i = 0; i < lp->buffer_size; i++)
{
lp->buf[i] = 0;
}
return REVSC_OK;
}
int ReverbSc::Process(const float &in1,
const float &in2,
float * out1,
float * out2)
{
float a_in_l, a_in_r, a_out_l, a_out_r;
float vm1, v0, v1, v2, am1, a0, a1, a2, frac;
ReverbScDl *lp;
int read_pos;
uint32_t n;
int buffer_size; /* Local copy */
float damp_fact = damp_fact_;
//if (init_done_ <= 0) return REVSC_NOT_OK;
if(init_done_ <= 0)
return REVSC_NOT_OK;
/* calculate tone filter coefficient if frequency changed */
if(lpfreq_ != prv_lpfreq_)
{
prv_lpfreq_ = lpfreq_;
damp_fact
= 2.0f - cosf(prv_lpfreq_ * (2.0f * (float)M_PI) / sample_rate_);
damp_fact = damp_fact_
= damp_fact - sqrtf(damp_fact * damp_fact - 1.0f);
}
/* calculate "resultant junction pressure" and mix to input signals */
a_in_l = a_out_l = a_out_r = 0.0;
for(n = 0; n < 8; n++)
{
a_in_l += delay_lines_[n].filter_state;
}
a_in_l *= kJpScale;
a_in_r = a_in_l + in2;
a_in_l = a_in_l + in1;
/* loop through all delay lines */
for(n = 0; n < 8; n++)
{
lp = &delay_lines_[n];
buffer_size = lp->buffer_size;
/* send input signal and feedback to delay line */
lp->buf[lp->write_pos]
= (float)((n & 1 ? a_in_r : a_in_l) - lp->filter_state);
if(++lp->write_pos >= buffer_size)
{
lp->write_pos -= buffer_size;
}
/* read from delay line with cubic interpolation */
if(lp->read_pos_frac >= DELAYPOS_SCALE)
{
lp->read_pos += (lp->read_pos_frac >> DELAYPOS_SHIFT);
lp->read_pos_frac &= DELAYPOS_MASK;
}
if(lp->read_pos >= buffer_size)
lp->read_pos -= buffer_size;
read_pos = lp->read_pos;
frac = (float)lp->read_pos_frac * (1.0 / (float)DELAYPOS_SCALE);
/* calculate interpolation coefficients */
a2 = frac * frac;
a2 -= 1.0;
a2 *= (1.0 / 6.0);
a1 = frac;
a1 += 1.0;
a1 *= 0.5;
am1 = a1 - 1.0;
a0 = 3.0 * a2;
a1 -= a0;
am1 -= a2;
a0 -= frac;
/* read four samples for interpolation */
if(read_pos > 0 && read_pos < (buffer_size - 2))
{
vm1 = (float)(lp->buf[read_pos - 1]);
v0 = (float)(lp->buf[read_pos]);
v1 = (float)(lp->buf[read_pos + 1]);
v2 = (float)(lp->buf[read_pos + 2]);
}
else
{
/* at buffer wrap-around, need to check index */
if(--read_pos < 0)
read_pos += buffer_size;
vm1 = (float)lp->buf[read_pos];
if(++read_pos >= buffer_size)
read_pos -= buffer_size;
v0 = (float)lp->buf[read_pos];
if(++read_pos >= buffer_size)
read_pos -= buffer_size;
v1 = (float)lp->buf[read_pos];
if(++read_pos >= buffer_size)
read_pos -= buffer_size;
v2 = (float)lp->buf[read_pos];
}
v0 = (am1 * vm1 + a0 * v0 + a1 * v1 + a2 * v2) * frac + v0;
/* update buffer read position */
lp->read_pos_frac += lp->read_pos_frac_inc;
/* apply feedback gain and lowpass filter */
v0 *= (float)feedback_;
v0 = (lp->filter_state - v0) * damp_fact + v0;
lp->filter_state = v0;
/* mix to output */
if(n & 1)
{
a_out_r += v0;
}
else
{
a_out_l += v0;
}
/* start next random line segment if current one has reached endpoint */
if(--(lp->rand_line_cnt) <= 0)
{
NextRandomLineseg(lp, n);
}
}
/* someday, use a_out_r for multimono out */
*out1 = a_out_l * kOutputGain;
*out2 = a_out_r * kOutputGain;
return REVSC_OK;
}

@ -0,0 +1,75 @@
#pragma once
#ifndef DSYSP_REVERBSC_H
#define DSYSP_REVERBSC_H
#define DSY_REVERBSC_MAX_SIZE 98936
namespace daisysp
{
/**Delay line for internal reverb use
*/
typedef struct
{
int write_pos; /**< write position */
int buffer_size; /**< buffer size */
int read_pos; /**< read position */
int read_pos_frac; /**< fractional component of read pos */
int read_pos_frac_inc; /**< increment for fractional */
int dummy; /**< dummy var */
int seed_val; /**< randseed */
int rand_line_cnt; /**< number of random lines */
float filter_state; /**< state of filter */
float *buf; /**< buffer ptr */
} ReverbScDl;
/** Stereo Reverb
Reverb SC: Ported from csound/soundpipe
Original author(s): Sean Costello, Istvan Varga
Year: 1999, 2005
Ported to soundpipe by: Paul Batchelor
Ported by: Stephen Hensley
*/
class ReverbSc
{
public:
ReverbSc() {}
~ReverbSc() {}
/** Initializes the reverb module, and sets the sample_rate at which the Process function will be called.
Returns 0 if all good, or 1 if it runs out of delay times exceed maximum allowed.
*/
int Init(float sample_rate);
/** Process the input through the reverb, and updates values of out1, and out2 with the new processed signal.
*/
int Process(const float &in1, const float &in2, float *out1, float *out2);
/** controls the reverb time. reverb tail becomes infinite when set to 1.0
\param fb - sets reverb time. range: 0.0 to 1.0
*/
inline void SetFeedback(const float &fb) { feedback_ = fb; }
/** controls the internal dampening filter's cutoff frequency.
\param freq - low pass frequency. range: 0.0 to sample_rate / 2
*/
inline void SetLpFreq(const float &freq) { lpfreq_ = freq; }
private:
void NextRandomLineseg(ReverbScDl *lp, int n);
int InitDelayLine(ReverbScDl *lp, int n);
float feedback_, lpfreq_;
float i_sample_rate_, i_pitch_mod_, i_skip_init_;
float sample_rate_;
float damp_fact_;
float prv_lpfreq_;
int init_done_;
ReverbScDl delay_lines_[8];
float aux_[DSY_REVERBSC_MAX_SIZE];
};
} // namespace daisysp
#endif

@ -0,0 +1,43 @@
#include "../utility/dsp.h"
#include "sampleratereducer.h"
#include <math.h>
using namespace daisysp;
void SampleRateReducer::Init()
{
frequency_ = .2f;
phase_ = 0.0f;
sample_ = 0.0f;
next_sample_ = 0.0f;
previous_sample_ = 0.0f;
}
float SampleRateReducer::Process(float in)
{
float this_sample = next_sample_;
next_sample_ = 0.f;
phase_ += frequency_;
if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
float t = phase_ / frequency_;
// t = 0: the transition occurred right at this sample.
// t = 1: the transition occurred at the previous sample.
// Use linear interpolation to recover the fractional sample.
float new_sample
= previous_sample_ + (in - previous_sample_) * (1.0f - t);
float discontinuity = new_sample - sample_;
this_sample += discontinuity * ThisBlepSample(t);
next_sample_ = discontinuity * NextBlepSample(t);
sample_ = new_sample;
}
next_sample_ += sample_;
previous_sample_ = in;
return this_sample;
}
void SampleRateReducer::SetFreq(float frequency)
{
frequency_ = fclamp(frequency, 0.f, 1.f);
}

@ -0,0 +1,48 @@
#pragma once
#ifndef DSY_SR_REDUCER_H
#define DSY_SR_REDUCER_H
#include <stdint.h>
#ifdef __cplusplus
/** @file sampleratereducer.h */
namespace daisysp
{
/**
@brief Sample rate reducer.
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/fx/sample_rate_reducer.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2014. \n
*/
class SampleRateReducer
{
public:
SampleRateReducer() {}
~SampleRateReducer() {}
/** Initialize the module */
void Init();
/** Get the next floating point sample
\param in Sample to be processed.
*/
float Process(float in);
/** Set the new sample rate.
\param Works over 0-1. 1 is full quality, .5 is half sample rate, etc.
*/
void SetFreq(float frequency);
private:
float frequency_;
float phase_;
float sample_;
float previous_sample_;
float next_sample_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,36 @@
#include "tremolo.h"
#include <math.h>
using namespace daisysp;
void Tremolo::Init(float sample_rate)
{
sample_rate_ = sample_rate;
osc_.Init(sample_rate_);
SetDepth(1.f);
SetFreq(1.f);
}
float Tremolo::Process(float in)
{
float modsig = dc_os_ + osc_.Process();
return in * modsig;
}
void Tremolo::SetFreq(float freq)
{
osc_.SetFreq(freq);
}
void Tremolo::SetWaveform(int waveform)
{
osc_.SetWaveform(waveform);
}
void Tremolo::SetDepth(float depth)
{
depth = fclamp(depth, 0.f, 1.f);
depth *= .5f;
osc_.SetAmp(depth);
dc_os_ = 1.f - depth;
}

@ -0,0 +1,60 @@
#pragma once
#ifndef DSY_TREMOLO_H
#define DSY_TREMOLO_H
#include <stdint.h>
#ifdef __cplusplus
#include <math.h>
#include "Synthesis/oscillator.h"
/** @file tremolo.h */
namespace daisysp
{
/**
@brief Tremolo effect.
@author Ben Sergentanis
@date Jan 2021
Based on https://christianfloisand.wordpress.com/2012/04/18/coding-some-tremolo/ \n
*/
class Tremolo
{
public:
Tremolo() {}
~Tremolo() {}
/** Initializes the module
\param sample_rate The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/**
\param in Input sample.
\return Next floating point sample.
*/
float Process(float in);
/** Sets the tremolo rate.
\param freq Tremolo freq in Hz.
*/
void SetFreq(float freq);
/** Shape of the modulating lfo
\param waveform Oscillator waveform. Use Oscillator::WAVE_SIN for example.
*/
void SetWaveform(int waveform);
/** How much to modulate your volume.
\param depth Works 0-1.
*/
void SetDepth(float depth);
private:
float sample_rate_, dc_os_;
Oscillator osc_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,42 @@
#include "allpass.h"
#include <math.h>
using namespace daisysp;
void Allpass::Init(float sample_rate, float* buff, size_t size)
{
sample_rate_ = sample_rate;
rev_time_ = 3.5;
max_loop_time_ = ((float)size / sample_rate_) - .01;
loop_time_ = max_loop_time_;
mod_ = (int)(loop_time_ * sample_rate_);
buf_ = buff;
prvt_ = 0.0;
coef_ = 0.0;
buf_pos_ = 0;
}
float Allpass::Process(float in)
{
float y, z, out;
if(prvt_ != rev_time_)
{
prvt_ = rev_time_;
coef_ = expf(-6.9078 * loop_time_ / prvt_);
}
y = buf_[buf_pos_];
z = coef_ * y + in;
buf_[buf_pos_] = z;
out = y - coef_ * z;
buf_pos_++;
buf_pos_ %= mod_;
return out;
}
void Allpass::SetFreq(float freq)
{
loop_time_ = fmaxf(fminf(freq, max_loop_time_), .0001);
mod_ = fmaxf(loop_time_ * sample_rate_, 0);
}

@ -0,0 +1,60 @@
#pragma once
#ifndef DSY_ALLPASS_H
#define DSY_ALLPASS_H
#include <stdint.h>
#ifdef __cplusplus
#include <math.h>
/** @file allpass.h */
namespace daisysp
{
/**
Allpass filter module \n
Passes all frequencies at their original levels, with a phase shift. \n
Ported from soundpipe by Ben Sergentanis, May 2020
@author Barry Vercoe, John ffitch
@date 1991
*/
class Allpass
{
public:
Allpass() {}
~Allpass() {}
/**
Initializes the allpass module.
\param sample_rate The sample rate of the audio engine being run.
\param buff Buffer for allpass to use.
\param size Size of buff.
*/
void Init(float sample_rate, float* buff, size_t size);
/**
\param in Input sample.
\return Next floating point sample.
*/
float Process(float in);
/**
Sets the filter frequency (Implemented by delay time).
\param looptime Filter looptime in seconds.
*/
void SetFreq(float looptime);
/**
\param revtime Reverb time in seconds.
*/
inline void SetRevTime(float revtime) { rev_time_ = revtime; }
private:
float sample_rate_, rev_time_, loop_time_, prvt_, coef_, max_loop_time_;
float* buf_;
int buf_pos_, mod_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,32 @@
#include "atone.h"
#include <math.h>
#include "../utility/dsp.h"
using namespace daisysp;
void ATone::Init(float sample_rate)
{
prevout_ = 0.0f;
freq_ = 1000.0f;
c2_ = 0.5f;
sample_rate_ = sample_rate;
}
float ATone::Process(float &in)
{
float out;
out = c2_ * (prevout_ + in);
prevout_ = out - in;
return out;
}
void ATone::CalculateCoefficients()
{
float b, c2;
b = 2.0f - cosf(TWOPI_F * freq_ / sample_rate_);
c2 = b - sqrtf(b * b - 1.0f);
c2_ = c2;
}

@ -0,0 +1,55 @@
#pragma once
#ifndef DSY_ATONE_H
#define DSY_ATONE_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** A first-order recursive high-pass filter with variable frequency response.
Original Author(s): Barry Vercoe, John FFitch, Gabriel Maldonado
Year: 1991
Original Location: Csound -- OOps/ugens5.c
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class ATone
{
public:
ATone() {}
~ATone() {}
/** Initializes the ATone module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Processes one sample through the filter and returns one sample.
\param in - input signal
*/
float Process(float &in);
/** Sets the cutoff frequency or half-way point of the filter.
\param freq - frequency value in Hz. Range: Any positive value.
*/
inline void SetFreq(float &freq)
{
freq_ = freq;
CalculateCoefficients();
}
/** get current frequency
\return the current value for the cutoff frequency or half-way point of the filter.
*/
inline float GetFreq() { return freq_; }
private:
void CalculateCoefficients();
float out_, prevout_, in_, freq_, c2_, sample_rate_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,59 @@
#include "biquad.h"
#include <math.h>
#include "../utility/dsp.h"
using namespace daisysp;
void Biquad::Reset()
{
float con = cutoff_ * two_pi_d_sr_;
float alpha = 1.0f - 2.0f * res_ * cosf(con) * cosf(con)
+ res_ * res_ * cosf(2 * con);
float beta = 1.0f + cosf(con);
float gamma = 1 + cosf(con);
float m1 = alpha * gamma + beta * sinf(con);
float m2 = alpha * gamma - beta * sinf(con);
float den = sqrtf(m1 * m1 + m2 * m2);
b0_ = 1.5f * (alpha * alpha + beta * beta) / den;
b1_ = b0_;
b2_ = 0.0f;
a0_ = 1.0f;
a1_ = -2.0 * res_ * cosf(con);
a2_ = res_ * res_;
}
void Biquad::Init(float sample_rate)
{
sample_rate_ = sample_rate;
two_pi_d_sr_ = TWOPI_F / sample_rate_;
cutoff_ = 500;
res_ = 0.7;
Reset();
xnm1_ = xnm2_ = ynm1_ = ynm2_ = 0.0f;
}
float Biquad::Process(float in)
{
float xn, yn;
float a0 = a0_, a1 = a1_, a2 = a2_;
float b0 = b0_, b1 = b1_, b2 = b2_;
float xnm1 = xnm1_, xnm2 = xnm2_, ynm1 = ynm1_, ynm2 = ynm2_;
xn = in;
yn = (b0 * xn + b1 * xnm1 + b2 * xnm2 - a1 * ynm1 - a2 * ynm2) / a0;
xnm2 = xnm1;
xnm1 = xn;
ynm2 = ynm1;
ynm1 = yn;
xnm1_ = xnm1;
xnm2_ = xnm2;
ynm1_ = ynm1;
ynm2_ = ynm2;
return yn;
}

@ -0,0 +1,61 @@
#pragma once
#ifndef DSY_BIQUAD_H
#define DSY_BIQUAD_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Two pole recursive filter
Original author(s) : Hans Mikelson
Year: 1998
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Biquad
{
public:
Biquad() {}
~Biquad() {}
/** Initializes the biquad module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Filters the input signal
\return filtered output
*/
float Process(float in);
/** Sets resonance amount
\param res : Set filter resonance.
*/
inline void SetRes(float res)
{
res_ = res;
Reset();
}
/** Sets filter cutoff in Hz
\param cutoff : Set filter cutoff.
*/
inline void SetCutoff(float cutoff)
{
cutoff_ = cutoff;
Reset();
}
private:
float sample_rate_, cutoff_, res_, b0_, b1_, b2_, a0_, a1_, a2_,
two_pi_d_sr_, xnm1_, xnm2_, ynm1_, ynm2_;
void Reset();
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,62 @@
#include "comb.h"
#include <math.h>
using namespace daisysp;
static float log001 = -6.9078f; // log .001
void Comb::Init(float sample_rate, float* buff, size_t size)
{
sample_rate_ = sample_rate;
rev_time_ = 3.5;
max_size_ = size;
max_loop_time_ = ((float)size / sample_rate_) - .01;
loop_time_ = max_loop_time_;
mod_ = sample_rate_ * loop_time_;
buf_ = buff;
prvt_ = 0.0f;
coef_ = 0.0f;
buf_pos_ = 0;
}
float Comb::Process(float in)
{
float tmp = 0;
float coef = coef_;
float outsamp = 0;
if(prvt_ != rev_time_)
{
prvt_ = rev_time_;
float exp_arg = (float)(log001 * loop_time_ / prvt_);
if(exp_arg < -36.8413615)
{
coef = coef_ = 0;
}
else
{
coef = coef_ = expf(exp_arg);
}
}
// internal delay line
outsamp = buf_[(buf_pos_ + mod_) % max_size_];
tmp = (outsamp * coef) + in;
buf_[(size_t)buf_pos_] = tmp;
buf_pos_ = (buf_pos_ - 1 + max_size_) % max_size_;
return outsamp;
}
void Comb::SetPeriod(float looptime)
{
if(looptime > 0)
{
loop_time_ = fminf(looptime, max_loop_time_);
mod_ = loop_time_ * sample_rate_;
if(mod_ > max_size_)
{
mod_ = max_size_ - 1;
}
}
}

@ -0,0 +1,61 @@
#pragma once
#ifndef DSY_COMB_H
#define DSY_COMB_H
#ifdef __cplusplus
#include "Utility/dsp.h"
namespace daisysp
{
/** Comb filter module
Original author(s) :
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Comb
{
public:
Comb() {}
~Comb() {}
/** Initializes the Comb module.
\param sample_rate - The sample rate of the audio engine being run.
\param buff - input buffer, kept in either main() or global space
\param size - size of buff
*/
void Init(float sample_rate, float* buff, size_t size);
/** processes the comb filter
*/
float Process(float in);
/** Sets the period of the comb filter in seconds
*/
void SetPeriod(float looptime);
/** Sets the frequency of the comb filter in Hz
*/
inline void SetFreq(float freq)
{
if(freq > 0)
{
SetPeriod(1.f / freq);
}
}
/** Sets the decay time of the comb filter
*/
inline void SetRevTime(float revtime) { rev_time_ = revtime; }
private:
float sample_rate_, rev_time_, loop_time_, prvt_, coef_, max_loop_time_;
float* buf_;
size_t buf_pos_, mod_, max_size_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,343 @@
#pragma once
#ifndef DSY_FIRFILTER_H
#define DSY_FIRFILTER_H
#include <cstdint>
#include <cstring> // for memset
#include <cassert>
#include <utility>
#ifdef USE_ARM_DSP
#include <arm_math.h> // required for platform-optimized version
#endif
/** @brief FIR Filter implementation, generic and ARM CMSIS DSP based
* @author Alexander Petrov-Savchenko (axp@soft-amp.com)
* @date February 2021
*/
namespace daisysp
{
/* use this as a template parameter to indicate user-provided memory storage */
#define FIRFILTER_USER_MEMORY 0, 0
/** Helper class that defines the memory model - internal or user-provided
* \param max_size - maximal filter length
* \param max_block - maximal length of the block processing
* if both parameters are 0, does NOT allocate any memory and instead
* requires user-provided memory blocks to be passed as parameters.
*
* Not intended to be used directly, so constructor is not exposed
*/
template <size_t max_size, size_t max_block>
struct FIRMemory
{
/* Public part of the API to be passed through to the FIR users */
public:
/* Reset the internal filter state (but not the coefficients) */
void Reset() { memset(state_, 0, state_size_ * sizeof(state_[0])); }
protected:
FIRMemory() : state_{0}, coefs_{0}, size_(0) {}
/* Expression for the maximum block size */
static constexpr size_t MaxBlock() { return max_block; }
/** Configure the filter coefficients
* \param coefs - pointer to coefficients (tail-first order)
* \param size - number of coefficients pointed by coefs (filter length)
* \param reverse - flag to perform reversing of the filter
* \return true if all conditions are met and the filter is configured
*/
bool SetCoefs(const float coefs[], size_t size, bool reverse)
{
/* truncate silently */
size_ = DSY_MIN(size, max_size);
if(reverse)
{
/* reverse the IR */
for(size_t i = 0; i < size_; i++)
{
/* start from size, not size_! */
coefs_[i] = coefs[size - 1u - i];
}
}
else
{
/* just copy as is */
memcpy(coefs_, coefs, size_ * sizeof(coefs[0]));
}
return true;
}
static constexpr size_t state_size_ = max_size + max_block - 1u;
float state_[state_size_]; /*< Internal state buffer */
float coefs_[max_size]; /*< Filter coefficients */
size_t size_; /*< Active filter length (<= max_size) */
};
/* Specialization for user-provided memory */
template <>
struct FIRMemory<FIRFILTER_USER_MEMORY>
{
/* Public part of the API to be passed through to the FIRFilter user */
public:
/** Set user-provided state buffer
* \param state - pointer to the allocated memory block
* \param length - length of the provided memory block (in elements)
* The length should be determined as follows
* length >= max_filter_size + max_processing_block - 1
*/
void SetStateBuffer(float state[], size_t length)
{
state_ = state;
state_size_ = length;
}
/* Reset the internal filter state (but not the coefficients) */
void Reset()
{
assert(nullptr != state_);
assert(0 != state_size_);
if(nullptr != state_)
{
memset(state_, 0, state_size_ * sizeof(state_[0]));
}
}
protected:
FIRMemory() : state_(nullptr), coefs_(nullptr), size_(0), state_size_(0) {}
/* Expression for the maximum processing block size currently supported */
size_t MaxBlock() const
{
return state_size_ + 1u > size_ ? state_size_ + 1u - size_ : 0;
}
/** Configure the filter coefficients
* \param coefs - pointer to coefficients (tail-first order)
* \param size - number of coefficients pointed by coefs (filter length)
* \param reverse - flag to perform reversing of the filter
* \return true if all conditions are met and the filter is configured
*/
bool SetCoefs(const float coefs[], size_t size, bool reverse)
{
/* reversing of external IR is not supported*/
assert(false == reverse);
assert(nullptr != coefs || 0 == size);
if(false == reverse && (nullptr != coefs || 0 == size))
{
coefs_ = coefs;
size_ = size;
return true;
}
return false;
}
/* Internal member variables */
float* state_; /*< Internal state buffer */
const float* coefs_; /*< Filter coefficients */
size_t size_; /*< number of filter coefficients */
size_t state_size_; /*< length of the state buffer */
};
/** Generic FIR implementation is always available
* \param max_size - maximal filter length
* \param max_block - maximal block size for ProcessBlock()
* if both parameters are 0 (via FIRFILTER_USER_MEMORY macro)
* Assumes the user will provide own memory buffers
* via SetIR() and SetStateBuffer() functions
* Otherwise statically allocates the necessary buffers itself
*/
template <size_t max_size, size_t max_block>
class FIRFilterImplGeneric : public FIRMemory<max_size, max_block>
{
private:
using FIRMem = FIRMemory<max_size, max_block>; // just a shorthand
public:
/* Default constructor */
FIRFilterImplGeneric() {}
/* Reset filter state (but not the coefficients) */
using FIRMem::Reset;
/* FIR Latency is always 0, but API is unified with FFT and fast convolution */
static constexpr size_t GetLatency() { return 0; }
/* Process one sample at a time */
float Process(float in)
{
assert(size_ > 0u);
/* Feed data into the buffer */
state_[size_ - 1u] = in;
/* Convolution loop */
float acc(0);
for(size_t i = 0; i < size_ - 1; i++)
{
acc += state_[i] * coefs_[i];
/** Shift the state simulatenously
* (note: better solutions are available)
*/
state_[i] = state_[1 + i];
}
acc += in * coefs_[size_ - 1u];
return acc;
}
/* Process a block of data */
void ProcessBlock(const float* pSrc, float* pDst, size_t block)
{
/* be sure to run debug version from time to time */
assert(block <= FIRMem::MaxBlock());
assert(size_ > 0u);
assert(nullptr != pSrc);
assert(nullptr != pDst);
/* Process the block of data */
for(size_t j = 0; j < block; j++)
{
/* Feed data into the buffer */
state_[size_ - 1u + j] = pSrc[j];
/* Convolution loop */
float acc = 0.0f;
for(size_t i = 0; i < size_; i++)
{
acc += state_[j + i] * coefs_[i];
}
/* Write output */
pDst[j] = acc;
}
/* Copy data tail for the next block */
for(size_t i = 0; i < size_ - 1u; i++)
{
state_[i] = state_[block + i];
}
}
/** Set filter coefficients (aka Impulse Response)
* Coefficients need to be in reversed order (tail-first)
* If internal storage is used, makes a local copy
* and allows reversing the impulse response
*/
bool SetIR(const float* ir, size_t len, bool reverse)
{
/* Function order is important */
const bool result = FIRMem::SetCoefs(ir, len, reverse);
Reset();
return result;
}
/* Create an alias to comply with DaisySP API conventions */
template <typename... Args>
inline auto Init(Args&&... args)
-> decltype(SetIR(std::forward<Args>(args)...))
{
return SetIR(std::forward<Args>(args)...);
}
protected:
using FIRMem::coefs_; /*< FIR coefficients buffer or pointer */
using FIRMem::size_; /*< FIR length */
using FIRMem::state_; /*< FIR state buffer or pointer */
};
#if(defined(USE_ARM_DSP) && defined(__arm__))
/** ARM-specific FIR implementation, expose only on __arm__ platforms
* \param max_size - maximal filter length
* \param max_block - maximal block size for ProcessBlock()
* if both parameters are 0 (via FIRFILTER_USER_MEMORY macro)
* Assumes the user will provide own memory buffers
* Otherwise statically allocates the necessary buffers
*/
template <size_t max_size, size_t max_block>
class FIRFilterImplARM : public FIRMemory<max_size, max_block>
{
private:
using FIRMem = FIRMemory<max_size, max_block>; // just a shorthand
public:
/* Default constructor */
FIRFilterImplARM() : fir_{0} {}
/* Reset filter state (but not the coefficients) */
using FIRMem::Reset;
/* FIR Latency is always 0, but API is unified with FFT and FastConv */
static constexpr size_t GetLatency() { return 0; }
/* Process one sample at a time */
float Process(float in)
{
float out(0);
arm_fir_f32(&fir_, &in, &out, 1);
return out;
}
/* Process a block of data */
void ProcessBlock(float* pSrc, float* pDst, size_t block)
{
assert(block <= FIRMem::MaxBlock());
arm_fir_f32(&fir_, pSrc, pDst, block);
}
/** Set filter coefficients (aka Impulse Response)
* Coefficients need to be in reversed order (tail-first)
* If internal storage is used, makes a local copy
* and allows reversing the impulse response
*/
bool SetIR(const float* ir, size_t len, bool reverse)
{
/* Function order is important */
const bool result = FIRMem::SetCoefs(ir, len, reverse);
arm_fir_init_f32(&fir_, len, (float*)coefs_, state_, max_block);
return result;
}
/* Create an alias to comply with DaisySP API conventions */
template <typename... Args>
inline auto Init(Args&&... args)
-> decltype(SetIR(std::forward<Args>(args)...))
{
return SetIR(std::forward<Args>(args)...);
}
protected:
arm_fir_instance_f32 fir_; /*< ARM CMSIS DSP library FIR filter instance */
using FIRMem::coefs_; /*< FIR coefficients buffer or pointer */
using FIRMem::size_; /*< FIR length*/
using FIRMem::state_; /*< FIR state buffer or pointer */
};
/* default to ARM implementation */
template <size_t max_size, size_t max_block>
using FIR = FIRFilterImplARM<max_size, max_block>;
#else // USE_ARM_DSP
/* default to generic implementation */
template <size_t max_size, size_t max_block>
using FIR = FIRFilterImplGeneric<max_size, max_block>;
#endif // USE_ARM_DSP
} // namespace daisysp
#endif // DSY_FIRFILTER_H

@ -0,0 +1,89 @@
//
// Mode
//
// This code has been extracted from the soundpipe opcode "mode".
// It has been modified to work as a Daisy module.
//
// Originally extracted from Csound.
//
// Original Author(s): Francois Blanc, Steven Yi
// Year: 2001
// Location: Opcodes/biquad.c
//
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#define ROOT2 (1.4142135623730950488f)
#ifndef M_PI
#define M_PI 3.14159265358979323846f /* pi */
#endif
#include "mode.h"
using namespace daisysp;
void Mode::Init(float sample_rate)
{
freq_ = 500.0f;
q_ = 50;
xnm1_ = ynm1_ = ynm2_ = 0.0f;
a0_ = a1_ = a2_ = d_ = 0.0f;
lfq_ = lq_ = -1.0f;
sr_ = sample_rate;
}
void Mode::Clear()
{
xnm1_ = ynm1_ = ynm2_ = 0.0f;
a0_ = a1_ = a2_ = 0.0f;
d_ = 0.0f;
lfq_ = -1.0f;
lq_ = -1.0f;
}
float Mode::Process(float in)
{
float out;
float lfq = lfq_, lq = lq_;
float xn, yn, a0 = a0_, a1 = a1_, a2 = a2_, d = d_;
float xnm1 = xnm1_, ynm1 = ynm1_, ynm2 = ynm2_;
float kfq = freq_;
float kq = q_;
if(lfq != kfq || lq != kq)
{
float kfreq = kfq * (2.0f * (float)M_PI);
float kalpha = (sr_ / kfreq);
float kbeta = kalpha * kalpha;
d = 0.5f * kalpha;
lq = kq;
lfq = kfq;
a0 = 1.0f / (kbeta + d / kfreq);
a1 = a0 * (1.0f - 2.0f * kbeta);
a2 = a0 * (kbeta - d / kq);
}
xn = in;
yn = a0 * xnm1 - a1 * ynm1 - a2 * ynm2;
xnm1 = xn;
ynm2 = ynm1;
ynm1 = yn;
yn = yn * d;
out = yn;
xnm1_ = xnm1;
ynm1_ = ynm1;
ynm2_ = ynm2;
lfq_ = lfq;
lq_ = lq;
d_ = d;
a0_ = a0;
a1_ = a1;
a2_ = a2;
return out;
}

@ -0,0 +1,53 @@
#pragma once
#ifndef DAISY_MODE
#define DAISY_MODE
namespace daisysp
{
/** Resonant Modal Filter
Extracted from soundpipe to work as a Daisy Module,
originally extracted from csound by Paul Batchelor.
Original Author(s): Francois Blanc, Steven Yi
Year: 2001
Location: Opcodes/biquad.c (csound)
*/
class Mode
{
public:
Mode() {}
~Mode() {}
/** Initializes the instance of the module.
sample_rate: frequency of the audio engine in Hz
*/
void Init(float sample_rate);
/** Processes one input sample through the filter, and returns the output.
*/
float Process(float in);
/** Clears the filter, returning the output to 0.0
*/
void Clear();
/** Sets the resonant frequency of the modal filter.
Range: Any frequency such that sample_rate / freq < PI (about 15.2kHz at 48kHz)
*/
inline void SetFreq(float freq) { freq_ = freq; }
/** Sets the quality factor of the filter.
Range: Positive Numbers (Good values range from 70 to 1400)
*/
inline void SetQ(float q) { q_ = q; }
private:
float freq_, q_;
float xnm1_, ynm1_, ynm2_, a0_, a1_, a2_;
float d_, lfq_, lq_, sr_;
};
} // namespace daisysp
#endif

@ -0,0 +1,105 @@
#include "moogladder.h"
#include "../utility/dsp.h"
using namespace daisysp;
float MoogLadder::my_tanh(float x)
{
int sign = 1;
if(x < 0)
{
sign = -1;
x = -x;
return x * sign;
}
else if(x >= 4.0f)
{
return sign;
}
else if(x < 0.5f)
{
return x * sign;
}
return sign * tanhf(x);
}
void MoogLadder::Init(float sample_rate)
{
sample_rate_ = sample_rate;
istor_ = 0.0f;
res_ = 0.4f;
freq_ = 1000.0f;
for(int i = 0; i < 6; i++)
{
delay_[i] = 0.0;
tanhstg_[i % 3] = 0.0;
}
old_freq_ = 0.0f;
old_res_ = -1.0f;
}
float MoogLadder::Process(float in)
{
float freq = freq_;
float res = res_;
float res4;
float* delay = delay_;
float* tanhstg = tanhstg_;
float stg[4];
float acr, tune;
float THERMAL = 0.000025;
if(res < 0)
{
res = 0;
}
if(old_freq_ != freq || old_res_ != res)
{
float f, fc, fc2, fc3, fcr;
old_freq_ = freq;
fc = (freq / sample_rate_);
f = 0.5f * fc;
fc2 = fc * fc;
fc3 = fc2 * fc2;
fcr = 1.8730f * fc3 + 0.4955f * fc2 - 0.6490f * fc + 0.9988f;
acr = -3.9364f * fc2 + 1.8409f * fc + 0.9968f;
tune = (1.0f - expf(-((2 * PI_F) * f * fcr))) / THERMAL;
old_res_ = res;
old_acr_ = acr;
old_tune_ = tune;
}
else
{
res = old_res_;
acr = old_acr_;
tune = old_tune_;
}
res4 = 4.0f * res * acr;
for(int j = 0; j < 2; j++)
{
in -= res4 * delay[5];
delay[0] = stg[0]
= delay[0] + tune * (my_tanh(in * THERMAL) - tanhstg[0]);
for(int k = 1; k < 4; k++)
{
in = stg[k - 1];
stg[k] = delay[k]
+ tune
* ((tanhstg[k - 1] = my_tanh(in * THERMAL))
- (k != 3 ? tanhstg[k]
: my_tanh(delay[k] * THERMAL)));
delay[k] = stg[k];
}
delay[5] = (stg[3] + delay[4]) * 0.5f;
delay[4] = stg[3];
}
return delay[5];
}

@ -0,0 +1,50 @@
#pragma once
#ifndef DSY_MOOGLADDER_H
#define DSY_MOOGLADDER_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Moog ladder filter module
Ported from soundpipe
Original author(s) : Victor Lazzarini, John ffitch (fast tanh), Bob Moog
*/
class MoogLadder
{
public:
MoogLadder() {}
~MoogLadder() {}
/** Initializes the MoogLadder module.
sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Processes the lowpass filter
*/
float Process(float in);
/**
Sets the cutoff frequency or half-way point of the filter.
Arguments
- freq - frequency value in Hz. Range: Any positive value.
*/
inline void SetFreq(float freq) { freq_ = freq; }
/**
Sets the resonance of the filter.
*/
inline void SetRes(float res) { res_ = res; }
private:
float istor_, res_, freq_, delay_[6], tanhstg_[3], old_freq_, old_res_,
sample_rate_, old_acr_, old_tune_;
float my_tanh(float x);
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,95 @@
#include <string.h>
#include <math.h>
#include "nlfilt.h"
#define OK 0
#define NOT_OK 1
#define FL (float)
#define TANH tanhf
#define MAX_DELAY (1024)
#define MAXAMP (FL(64000.0f))
using namespace daisysp;
void NlFilt::Init()
{
point_ = 0; // Set delay pointer
Set();
}
/* Revised version due to Risto Holopainen 12 Mar 2004 */
/* Y{n} =tanh(a Y{n-1} + b Y{n-2} + d Y^2{n-L} + X{n} - C) */
void NlFilt::ProcessBlock(float *in, float *out, size_t size)
{
uint32_t offset = 0;
uint32_t n, nsmps = size;
int32_t point = point_;
int32_t nm1 = point;
int32_t nm2 = point - 1;
int32_t nmL;
float ynm1, ynm2, ynmL;
float a = a_, b = b_, d = d_, C = C_;
float * fp = (float *)delay_;
float L = L_;
float maxamp, dvmaxamp, maxampd2;
/* L is k-rate so need to check */
if(L < FL(1.0f))
L = FL(1.0f);
else if(L >= MAX_DELAY)
{
L = (float)MAX_DELAY;
}
nmL = point - (int32_t)(L)-1;
if(nm1 < 0)
nm1 += MAX_DELAY; /* Deal with the wrapping */
if(nm2 < 0)
nm2 += MAX_DELAY;
if(nmL < 0)
nmL += MAX_DELAY;
ynm1 = fp[nm1]; /* Pick up running values */
ynm2 = fp[nm2];
ynmL = fp[nmL];
nsmps = size;
maxamp = 1.935125f;
dvmaxamp = FL(1.0f) / maxamp;
maxampd2 = maxamp * FL(0.5F);
for(n = offset; n < nsmps; n++)
{
float yn;
float outv;
yn = a * ynm1 + b * ynm2 + d * ynmL * ynmL - C;
yn += in[n] * dvmaxamp; /* Must work in small amplitudes */
outv = yn * maxampd2; /* Write output */
if(outv > maxamp)
outv = maxampd2;
else if(outv < -maxamp)
outv = -maxampd2;
out[n] = outv;
if(++point == MAX_DELAY)
{
point = 0;
}
yn = TANH(yn);
fp[point] = yn; /* and delay line */
if(++nmL == MAX_DELAY)
{
nmL = 0;
}
ynm2 = ynm1; /* Shuffle along */
ynm1 = yn;
ynmL = fp[nmL];
}
point_ = point;
}
int32_t NlFilt::Set()
{
// Initializes delay buffer.
for(size_t i = 0; i < MAX_DELAY; i++)
{
delay_[i] = 0;
}
return OK;
}

@ -0,0 +1,80 @@
#pragma once
#ifndef DSY_NLFILT_H
#define DSY_NLFILT_H
#include <stdlib.h>
#include <stdint.h>
#define DSY_NLFILT_MAX_DELAY 1024
namespace daisysp
{
/** Non-linear filter
port by: Stephen Hensley, December 2019
The four 5-coefficients: a, b, d, C, and L are used to configure different filter types.
Structure for Dobson/Fitch nonlinear filter
Revised Formula from Risto Holopainen 12 Mar 2004
`Y{n} =tanh(a Y{n-1} + b Y{n-2} + d Y^2{n-L} + X{n} - C)`
Though traditional filter types can be made,
the effect will always respond differently to different input.
This Source is a heavily modified version of the original
source from Csound.
\todo make this work on a single sample instead of just on blocks at a time.
*/
class NlFilt
{
public:
/** Initializes the NlFilt object.
*/
void Init();
/** Process the array pointed to by \*in and updates the output to \*out;
This works on a block of audio at once, the size of which is set with the size.
*/
void ProcessBlock(float *in, float *out, size_t size);
/** inputs these are the five coefficients for the filter.
*/
inline void SetCoefficients(float a, float b, float d, float C, float L)
{
a_ = a;
b_ = b;
d_ = d;
C_ = C;
L_ = L;
}
/** Set Coefficient a
*/
inline void SetA(float a) { a_ = a; }
/** Set Coefficient b
*/
inline void SetB(float b) { b_ = b; }
/** Set Coefficient d
*/
inline void SetD(float d) { d_ = d; }
/** Set Coefficient C
*/
inline void SetC(float C) { C_ = C; }
/** Set Coefficient L
*/
inline void SetL(float L) { L_ = L; }
private:
int32_t Set();
float in_, a_, b_, d_, C_, L_;
float delay_[DSY_NLFILT_MAX_DELAY];
int32_t point_;
};
} // namespace daisysp
#endif

@ -0,0 +1,86 @@
#include <math.h>
#include "svf.h"
#include "../utility/dsp.h"
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
using namespace daisysp;
void Svf::Init(float sample_rate)
{
sr_ = sample_rate;
fc_ = 200.0f;
res_ = 0.5f;
drive_ = 0.5f;
pre_drive_ = 0.5f;
freq_ = 0.25f;
damp_ = 0.0f;
notch_ = 0.0f;
low_ = 0.0f;
high_ = 0.0f;
band_ = 0.0f;
peak_ = 0.0f;
input_ = 0.0f;
out_notch_ = 0.0f;
out_low_ = 0.0f;
out_high_ = 0.0f;
out_peak_ = 0.0f;
out_band_ = 0.0f;
fc_max_ = sr_ / 3.f;
}
void Svf::Process(float in)
{
input_ = in;
// first pass
notch_ = input_ - damp_ * band_;
low_ = low_ + freq_ * band_;
high_ = notch_ - low_;
band_ = freq_ * high_ + band_ - drive_ * band_ * band_ * band_;
// take first sample of output
out_low_ = 0.5f * low_;
out_high_ = 0.5f * high_;
out_band_ = 0.5f * band_;
out_peak_ = 0.5f * (low_ - high_);
out_notch_ = 0.5f * notch_;
// second pass
notch_ = input_ - damp_ * band_;
low_ = low_ + freq_ * band_;
high_ = notch_ - low_;
band_ = freq_ * high_ + band_ - drive_ * band_ * band_ * band_;
// average second pass outputs
out_low_ += 0.5f * low_;
out_high_ += 0.5f * high_;
out_band_ += 0.5f * band_;
out_peak_ += 0.5f * (low_ - high_);
out_notch_ += 0.5f * notch_;
}
void Svf::SetFreq(float f)
{
fc_ = fclamp(f, 1.0e-6, fc_max_);
// Set Internal Frequency for fc_
freq_ = 2.0f
* sinf(PI_F
* MIN(0.25f,
fc_ / (sr_ * 2.0f))); // fs*2 because double sampled
// recalculate damp
damp_ = MIN(2.0f * (1.0f - powf(res_, 0.25f)),
MIN(2.0f, 2.0f / freq_ - freq_ * 0.5f));
}
void Svf::SetRes(float r)
{
float res = fclamp(r, 0.f, 1.f);
res_ = res;
// recalculate damp
damp_ = MIN(2.0f * (1.0f - powf(res_, 0.25f)),
MIN(2.0f, 2.0f / freq_ - freq_ * 0.5f));
drive_ = pre_drive_ * res_;
}
void Svf::SetDrive(float d)
{
float drv = fclamp(d * 0.1f, 0.f, 1.f);
pre_drive_ = drv;
drive_ = pre_drive_ * res_;
}

@ -0,0 +1,79 @@
#pragma once
#ifndef DSY_SVF_H
#define DSY_SVF_H
namespace daisysp
{
/** Double Sampled, Stable State Variable Filter
Credit to Andrew Simper from musicdsp.org
This is his "State Variable Filter (Double Sampled, Stable)"
Additional thanks to Laurent de Soras for stability limit, and
Stefan Diedrichsen for the correct notch output
Ported by: Stephen Hensley
*/
class Svf
{
public:
Svf() {}
~Svf() {}
/** Initializes the filter
float sample_rate - sample rate of the audio engine being run, and the frequency that the Process function will be called.
*/
void Init(float sample_rate);
/**
Process the input signal, updating all of the outputs.
*/
void Process(float in);
/** sets the frequency of the cutoff frequency.
f must be between 0.0 and sample_rate / 3
*/
void SetFreq(float f);
/** sets the resonance of the filter.
Must be between 0.0 and 1.0 to ensure stability.
*/
void SetRes(float r);
/** sets the drive of the filter
affects the response of the resonance of the filter
*/
void SetDrive(float d);
/** lowpass output
\return low pass output of the filter
*/
inline float Low() { return out_low_; }
/** highpass output
\return high pass output of the filter
*/
inline float High() { return out_high_; }
/** bandpass output
\return band pass output of the filter
*/
inline float Band() { return out_band_; }
/** notchpass output
\return notch pass output of the filter
*/
inline float Notch() { return out_notch_; }
/** peak output
\return peak output of the filter
*/
inline float Peak() { return out_peak_; }
private:
float sr_, fc_, res_, drive_, freq_, damp_;
float notch_, low_, high_, band_, peak_;
float input_;
float out_low_, out_high_, out_band_, out_peak_, out_notch_;
float pre_drive_, fc_max_;
};
} // namespace daisysp
#endif

@ -0,0 +1,34 @@
#include "tone.h"
#include <math.h>
#include "../utility/dsp.h"
using namespace daisysp;
void Tone::Init(float sample_rate)
{
prevout_ = 0.0f;
freq_ = 100.0f;
c1_ = 0.5f;
c2_ = 0.5f;
sample_rate_ = sample_rate;
}
float Tone::Process(float &in)
{
float out;
out = c1_ * in + c2_ * prevout_;
prevout_ = out;
return out;
}
void Tone::CalculateCoefficients()
{
float b, c1, c2;
b = 2.0f - cosf(TWOPI_F * freq_ / sample_rate_);
c2 = b - sqrtf(b * b - 1.0f);
c1 = 1.0f - c2;
c1_ = c1;
c2_ = c2;
}

@ -0,0 +1,49 @@
#pragma once
#ifndef DSY_TONE_H
#define DSY_TONE_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** A first-order recursive low-pass filter with variable frequency response.
*/
class Tone
{
public:
Tone() {}
~Tone() {}
/** Initializes the Tone module.
sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Processes one sample through the filter and returns one sample.
in - input signal
*/
float Process(float &in);
/** Sets the cutoff frequency or half-way point of the filter.
\param freq - frequency value in Hz. Range: Any positive value.
*/
inline void SetFreq(float &freq)
{
freq_ = freq;
CalculateCoefficients();
}
/**
\return the current value for the cutoff frequency or half-way point of the filter.
*/
inline float GetFreq() { return freq_; }
private:
void CalculateCoefficients();
float out_, prevout_, in_, freq_, c1_, c2_, sample_rate_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,59 @@
#include <random>
#include "../utility/dsp.h"
#include "clockednoise.h"
using namespace daisysp;
void ClockedNoise::Init(float sample_rate)
{
sample_rate_ = sample_rate;
phase_ = 0.0f;
sample_ = 0.0f;
next_sample_ = 0.0f;
frequency_ = 0.001f;
}
float ClockedNoise::Process()
{
float next_sample = next_sample_;
float sample = sample_;
float this_sample = next_sample;
next_sample = 0.0f;
const float raw_sample = rand() * kRandFrac * 2.0f - 1.0f;
float raw_amount = 4.0f * (frequency_ - 0.25f);
raw_amount = fclamp(raw_amount, 0.0f, 1.0f);
phase_ += frequency_;
if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
float t = phase_ / frequency_;
float new_sample = raw_sample;
float discontinuity = new_sample - sample;
this_sample += discontinuity * ThisBlepSample(t);
next_sample += discontinuity * NextBlepSample(t);
sample = new_sample;
}
next_sample += sample;
next_sample_ = next_sample;
sample_ = sample;
return this_sample + raw_amount * (raw_sample - this_sample);
}
void ClockedNoise::SetFreq(float freq)
{
freq = freq / sample_rate_;
freq = fclamp(freq, 0.0f, 1.0f);
frequency_ = freq;
}
void ClockedNoise::Sync()
{
phase_ = 1.0f;
}

@ -0,0 +1,58 @@
#pragma once
#ifndef DSY_CLOCKEDNOISE_H
#define DSY_CLOCKEDNOISE_H
#include <stdint.h>
#ifdef __cplusplus
/** @file clockednoise.h */
namespace daisysp
{
/**
@brief Clocked Noise Module
@author Ported by Ben Sergentanis
@date Jan 2021
Noise processed by a sample and hold running at a target frequency. \n \n
Ported from pichenettes/eurorack/plaits/dsp/noise/clocked_noise.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class ClockedNoise
{
public:
ClockedNoise() {}
~ClockedNoise() {}
/** Initialize module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the next floating point sample */
float Process();
/** Set the frequency at which the next sample is generated.
\param freq Frequency in Hz
*/
void SetFreq(float freq);
/** Calling this forces another random float to be generated */
void Sync();
private:
// Oscillator state.
float phase_;
float sample_;
float next_sample_;
// For interpolation of parameters.
float frequency_;
float sample_rate_;
static constexpr float kRandFrac = 1.f / (float)RAND_MAX;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,54 @@
#pragma once
#ifndef DSY_DUST_H
#define DSY_DUST_H
#include <cstdlib>
#include <random>
#include "Utility/dsp.h"
#ifdef __cplusplus
/** @file dust.h */
namespace daisysp
{
/**
@brief Dust Module
@author Ported by Ben Sergentanis
@date Jan 2021
Randomly Clocked Samples \n \n
Ported from pichenettes/eurorack/plaits/dsp/noise/dust.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class Dust
{
public:
Dust() {}
~Dust() {}
void Init() { SetDensity(.5f); }
float Process()
{
float inv_density = 1.0f / density_;
float u = rand() * kRandFrac;
if(u < density_)
{
return u * inv_density;
}
return 0.0f;
}
void SetDensity(float density)
{
density_ = fclamp(density, 0.f, 1.f);
density_ = density_ * .3f;
}
private:
float density_;
static constexpr float kRandFrac = 1.f / (float)RAND_MAX;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,82 @@
#pragma once
#ifndef DSY_FRACTAL_H
#define DSY_FRACTAL_H
#include <stdint.h>
#ifdef __cplusplus
/** @file fractal_noise.h */
namespace daisysp
{
/**
@brief Fractal Noise, stacks octaves of a noise source.
@author Ported by Ben Sergentanis
@date Jan 2021
T is the noise source to use. T must have SetFreq() and Init(sample_rate) functions. \n
Order is the number of noise sources to stack. \n \n
Ported from pichenettes/eurorack/plaits/dsp/noise/fractal_random_generator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
template <typename T, int order>
class FractalRandomGenerator
{
public:
FractalRandomGenerator() {}
~FractalRandomGenerator() {}
/** Initialize the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate)
{
sample_rate_ = sample_rate;
SetColor(.5f);
SetFreq(440.f);
for(int i = 0; i < order; ++i)
{
generator_[i].Init(sample_rate_);
}
}
/** Get the next sample. */
float Process()
{
float gain = 0.5f;
float sum = 0.0f;
float frequency = frequency_;
for(int i = 0; i < order; ++i)
{
generator_[i].SetFreq(frequency);
sum += generator_[i].Process() * gain;
gain *= decay_;
frequency *= 2.0f;
}
return sum;
}
/** Set the lowest noise frequency.
\param freq Frequency of the lowest noise source in Hz.
*/
void SetFreq(float freq) { frequency_ = fclamp(freq, 0.f, sample_rate_); }
/** Sets the amount of high frequency noise.
\** Works 0-1. 1 is the brightest, and 0 is the darkest.
*/
void SetColor(float color) { decay_ = fclamp(color, 0.f, 1.f); }
private:
float sample_rate_;
float frequency_;
float decay_;
T generator_[order];
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,146 @@
#include "../utility/dsp.h"
#include "grainlet.h"
#include <math.h>
using namespace daisysp;
void GrainletOscillator::Init(float sample_rate)
{
sample_rate_ = sample_rate;
carrier_phase_ = 0.0f;
formant_phase_ = 0.0f;
next_sample_ = 0.0f;
carrier_shape_ = 0.f;
carrier_bleed_ = 0.f;
SetFreq(440.f);
SetFormantFreq(220.f);
SetShape(.5f);
SetBleed(.5f);
}
float GrainletOscillator::Process()
{
float this_sample = next_sample_;
float next_sample = 0.0f;
carrier_phase_ += carrier_frequency_;
if(carrier_phase_ >= 1.0f)
{
carrier_phase_ -= 1.0f;
float reset_time = carrier_phase_ / carrier_frequency_;
float shape_inc = new_carrier_shape_ - carrier_shape_;
float bleed_inc = new_carrier_bleed_ - carrier_bleed_;
float before = Grainlet(
1.0f,
formant_phase_ + (1.0f - reset_time) * formant_frequency_,
new_carrier_shape_ + shape_inc * (1.0f - reset_time),
new_carrier_bleed_ + bleed_inc * (1.0f - reset_time));
float after
= Grainlet(0.0f, 0.0f, new_carrier_shape_, new_carrier_bleed_);
float discontinuity = after - before;
this_sample += discontinuity * ThisBlepSample(reset_time);
next_sample += discontinuity * NextBlepSample(reset_time);
formant_phase_ = reset_time * formant_frequency_;
}
else
{
formant_phase_ += formant_frequency_;
if(formant_phase_ >= 1.0f)
{
formant_phase_ -= 1.0f;
}
}
carrier_bleed_ = new_carrier_bleed_;
carrier_shape_ = new_carrier_shape_;
next_sample += Grainlet(
carrier_phase_, formant_phase_, carrier_shape_, carrier_bleed_);
next_sample_ = next_sample;
return this_sample;
}
void GrainletOscillator::SetFreq(float freq)
{
carrier_frequency_ = freq / sample_rate_;
carrier_frequency_ = carrier_frequency_ > 0.5f ? 0.5f : carrier_frequency_;
}
void GrainletOscillator::SetFormantFreq(float freq)
{
formant_frequency_ = freq / sample_rate_;
formant_frequency_ = formant_frequency_ > 0.5f ? 0.5f : formant_frequency_;
}
void GrainletOscillator::SetShape(float shape)
{
new_carrier_shape_ = shape;
}
void GrainletOscillator::SetBleed(float bleed)
{
new_carrier_bleed_ = bleed;
}
float GrainletOscillator::Sine(float phase)
{
return sinf(phase * TWOPI_F);
}
float GrainletOscillator::Carrier(float phase, float shape)
{
shape *= 3.0f;
int shape_integral = static_cast<int>(shape);
float shape_fractional = shape - static_cast<float>(shape_integral);
float t = 1.0f - shape_fractional;
if(shape_integral == 0)
{
phase = phase * (1.0f + t * t * t * 15.0f);
if(phase >= 1.0f)
{
phase = 1.0f;
}
phase += 0.75f;
}
else if(shape_integral == 1)
{
float breakpoint = 0.001f + 0.499f * t * t * t;
if(phase < breakpoint)
{
phase *= (0.5f / breakpoint);
}
else
{
phase = 0.5f + (phase - breakpoint) * 0.5f / (1.0f - breakpoint);
}
phase += 0.75f;
}
else
{
t = 1.0f - t;
phase = 0.25f + phase * (0.5f + t * t * t * 14.5f);
if(phase >= 0.75f)
phase = 0.75f;
}
return (Sine(phase) + 1.0f) * 0.25f;
}
float GrainletOscillator::Grainlet(float carrier_phase,
float formant_phase,
float shape,
float bleed)
{
float carrier = Carrier(carrier_phase, shape);
float formant = Sine(formant_phase);
return carrier * (formant + bleed) / (1.0f + bleed);
}

@ -0,0 +1,84 @@
#pragma once
#ifndef DSY_GRAINLET_H
#define DSY_GRAINLET_H
#include <stdint.h>
#ifdef __cplusplus
/** @file grainlet.h */
namespace daisysp
{
/**
@brief Granular Oscillator Module.
@author Ben Sergentanis
@date Dec 2020
A phase-distorted single cycle sine * another continuously running sine, \n
the whole thing synced to a main oscillator. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/grainlet_oscillator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class GrainletOscillator
{
public:
GrainletOscillator() {}
~GrainletOscillator() {}
/** Initialize the oscillator
\param sample_rate Sample rate of audio engine
*/
void Init(float sample_rate);
/** Get the next sample */
float Process();
/** Sets the carrier frequency
\param freq Frequency in Hz
*/
void SetFreq(float freq);
/** Sets the formant frequency
\param freq Frequency in Hz
*/
void SetFormantFreq(float freq);
/** Waveshaping
\param shape Shapes differently from 0-1, 1-2, and > 2.
*/
void SetShape(float shape);
/** Sets the amount of formant to bleed through
\param bleed Works best 0-1
*/
void SetBleed(float bleed);
private:
float Sine(float phase);
float Carrier(float phase, float shape);
float Grainlet(float carrier_phase,
float formant_phase,
float shape,
float bleed);
// Oscillator state.
float carrier_phase_;
float formant_phase_;
float next_sample_;
// For interpolation of parameters.
float carrier_frequency_;
float formant_frequency_;
float carrier_shape_;
float carrier_bleed_;
float new_carrier_shape_;
float new_carrier_bleed_;
float sample_rate_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,95 @@
#include "../utility/dsp.h"
#include "particle.h"
#include <math.h>
using namespace daisysp;
void Particle::Init(float sample_rate)
{
sample_rate_ = sample_rate;
sync_ = false;
aux_ = 0.f;
SetFreq(440.f);
resonance_ = .9f;
density_ = .5f;
gain_ = 1.f;
spread_ = 1.f;
SetRandomFreq(sample_rate_ / 48.f); //48 is the default block size
rand_phase_ = 0.f;
pre_gain_ = 0.0f;
filter_.Init(sample_rate_);
filter_.SetDrive(.7f);
}
float Particle::Process()
{
float u = rand() * kRandFrac;
float s = 0.0f;
if(u <= density_ || sync_)
{
s = u <= density_ ? u * gain_ : s;
rand_phase_ += rand_freq_;
if(rand_phase_ >= 1.f || sync_)
{
rand_phase_ = rand_phase_ >= 1.f ? rand_phase_ - 1.f : rand_phase_;
const float u = 2.0f * rand() * kRandFrac - 1.0f;
const float f
= fmin(powf(2.f, kRatioFrac * spread_ * u) * frequency_, .25f);
pre_gain_ = 0.5f / sqrtf(resonance_ * f * sqrtf(density_));
filter_.SetFreq(f * sample_rate_);
filter_.SetRes(resonance_);
}
}
aux_ = s;
filter_.Process(pre_gain_ * s);
return filter_.Band();
}
float Particle::GetNoise()
{
return aux_;
}
void Particle::SetFreq(float freq)
{
freq /= sample_rate_;
frequency_ = fclamp(freq, 0.f, 1.f);
}
void Particle::SetResonance(float resonance)
{
resonance_ = fclamp(resonance, 0.f, 1.f);
}
void Particle::SetRandomFreq(float freq)
{
freq /= sample_rate_;
rand_freq_ = fclamp(freq, 0.f, 1.f);
}
void Particle::SetDensity(float density)
{
density_ = fclamp(density * .3f, 0.f, 1.f);
}
void Particle::SetGain(float gain)
{
gain_ = fclamp(gain, 0.f, 1.f);
}
void Particle::SetSpread(float spread)
{
spread_ = spread < 0.f ? 0.f : spread;
}
void Particle::SetSync(bool sync)
{
sync_ = sync;
}

@ -0,0 +1,92 @@
#pragma once
#ifndef DSY_PARTICLE_H
#define DSY_PARTICLE_H
#include "Filters/svf.h"
#include <stdint.h>
#include <cstdlib>
#ifdef __cplusplus
/** @file particle.h */
namespace daisysp
{
/**
@brief Random impulse train processed by a resonant filter.
@author Ported by Ben Sergentanis
@date Jan 2021
Noise processed by a sample and hold running at a target frequency. \n \n
Ported from pichenettes/eurorack/plaits/dsp/noise/particle.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class Particle
{
public:
Particle() {}
~Particle() {}
/** Initialize the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Get the next sample */
float Process();
/** Get the raw noise output. Must call Process() first. */
float GetNoise();
/** Set the resonant filter frequency
\param freq Frequency in Hz
*/
void SetFreq(float frequency);
/** Set the filter resonance
\param resonance Works 0-1
*/
void SetResonance(float resonance);
/** How often to randomize filter frequency
\param freq Frequency in Hz.
*/
void SetRandomFreq(float freq);
/** Noise density
\param Works 0-1.
*/
void SetDensity(float density);
/** Overall module gain
\param Works 0-1.
*/
void SetGain(float gain);
/** How much to randomize the set filter frequency.
\param spread Works over positive numbers.
*/
void SetSpread(float spread);
/** Force randomize the frequency.
\param sync True to randomize freq.
*/
void SetSync(bool sync);
private:
static constexpr float kRandFrac = 1.f / (float)RAND_MAX;
static constexpr float kRatioFrac = 1.f / 12.f;
float sample_rate_;
float aux_, frequency_, density_, gain_, spread_, resonance_;
bool sync_;
float rand_phase_;
float rand_freq_;
float pre_gain_;
Svf filter_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,43 @@
#pragma once
#ifndef DSY_WHITENOISE_H
#define DSY_WHITENOISE_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** fast white noise generator
I think this came from musicdsp.org at some point
*/
class WhiteNoise
{
public:
WhiteNoise() {}
~WhiteNoise() {}
/** Initializes the WhiteNoise object
*/
void Init()
{
amp_ = 1.0f;
randseed_ = 1;
}
/** sets the amplitude of the noise output
*/
inline void SetAmp(float a) { amp_ = a; }
/** returns a new sample of noise in the range of -amp_ to amp_
*/
inline float Process()
{
randseed_ *= 16807;
return (randseed_ * coeff_) * amp_;
}
private:
static constexpr float coeff_ = 4.6566129e-010f;
float amp_;
int32_t randseed_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,197 @@
#include <cmath>
#include "../utility/dsp.h"
#include "KarplusString.h"
#include <stdlib.h>
using namespace daisysp;
void String::Init(float sample_rate)
{
sample_rate_ = sample_rate;
SetFreq(440.f);
non_linearity_amount_ = .5f;
brightness_ = .5f;
damping_ = .5f;
string_.Init();
stretch_.Init();
Reset();
SetFreq(440.f);
SetDamping(.8f);
SetNonLinearity(.1f);
SetBrightness(.5f);
crossfade_.Init();
}
void String::Reset()
{
string_.Reset();
stretch_.Reset();
iir_damping_filter_.Init(sample_rate_);
dc_blocker_.Init(sample_rate_);
dispersion_noise_ = 0.0f;
curved_bridge_ = 0.0f;
out_sample_[0] = out_sample_[1] = 0.0f;
src_phase_ = 0.0f;
}
float String::Process(const float in)
{
if(non_linearity_amount_ <= 0.0f)
{
non_linearity_amount_ *= -1;
float ret = ProcessInternal<NON_LINEARITY_CURVED_BRIDGE>(in);
non_linearity_amount_ *= -1;
return ret;
}
else
{
return ProcessInternal<NON_LINEARITY_DISPERSION>(in);
}
}
void String::SetFreq(float freq)
{
freq /= sample_rate_;
frequency_ = fclamp(freq, 0.f, .25f);
}
void String::SetNonLinearity(float non_linearity_amount)
{
non_linearity_amount_ = fclamp(non_linearity_amount, 0.f, 1.f);
}
void String::SetBrightness(float brightness)
{
brightness_ = fclamp(brightness, 0.f, 1.f);
}
void String::SetDamping(float damping)
{
damping_ = fclamp(damping, 0.f, 1.f);
}
template <String::StringNonLinearity non_linearity>
float String::ProcessInternal(const float in)
{
float brightness = brightness_;
float delay = 1.0f / frequency_;
delay = fclamp(delay, 4.f, kDelayLineSize - 4.0f);
// If there is not enough delay time in the delay line, we play at the
// lowest possible note and we upsample on the fly with a shitty linear
// interpolator. We don't care because it's a corner case (frequency_ < 11.7Hz)
float src_ratio = delay * frequency_;
if(src_ratio >= 0.9999f)
{
// When we are above 11.7 Hz, we make sure that the linear interpolator
// does not get in the way.
src_phase_ = 1.0f;
src_ratio = 1.0f;
}
float damping_cutoff
= fmin(12.0f + damping_ * damping_ * 60.0f + brightness * 24.0f, 84.0f);
float damping_f
= fmin(frequency_ * powf(2.f, damping_cutoff * kOneTwelfth), 0.499f);
// Crossfade to infinite decay.
if(damping_ >= 0.95f)
{
float to_infinite = 20.0f * (damping_ - 0.95f);
brightness += to_infinite * (1.0f - brightness);
damping_f += to_infinite * (0.4999f - damping_f);
damping_cutoff += to_infinite * (128.0f - damping_cutoff);
}
float temp_f = damping_f * sample_rate_;
iir_damping_filter_.SetFreq(temp_f);
float ratio = powf(2.f, damping_cutoff * kOneTwelfth);
float damping_compensation = 1.f - 2.f * atanf(1.f / ratio) / (TWOPI_F);
float stretch_point
= non_linearity_amount_ * (2.0f - non_linearity_amount_) * 0.225f;
float stretch_correction = (160.0f / sample_rate_) * delay;
stretch_correction = fclamp(stretch_correction, 1.f, 2.1f);
float noise_amount_sqrt = non_linearity_amount_ > 0.75f
? 4.0f * (non_linearity_amount_ - 0.75f)
: 0.0f;
float noise_amount = noise_amount_sqrt * noise_amount_sqrt * 0.1f;
float noise_filter = 0.06f + 0.94f * brightness * brightness;
float bridge_curving_sqrt = non_linearity_amount_;
float bridge_curving = bridge_curving_sqrt * bridge_curving_sqrt * 0.01f;
float ap_gain = -0.618f * non_linearity_amount_
/ (0.15f + fabsf(non_linearity_amount_));
src_phase_ += src_ratio;
if(src_phase_ > 1.0f)
{
src_phase_ -= 1.0f;
delay = delay * damping_compensation;
float s = 0.0f;
if(non_linearity == NON_LINEARITY_DISPERSION)
{
float noise = rand() * kRandFrac - 0.5f;
fonepole(dispersion_noise_, noise, noise_filter);
delay *= 1.0f + dispersion_noise_ * noise_amount;
}
else
{
delay *= 1.0f - curved_bridge_ * bridge_curving;
}
if(non_linearity == NON_LINEARITY_DISPERSION)
{
float ap_delay = delay * stretch_point;
float main_delay = delay
- ap_delay * (0.408f - stretch_point * 0.308f)
* stretch_correction;
if(ap_delay >= 4.0f && main_delay >= 4.0f)
{
s = string_.Read(main_delay);
s = stretch_.Allpass(s, ap_delay, ap_gain);
}
else
{
s = string_.ReadHermite(delay);
}
}
else
{
s = string_.ReadHermite(delay);
}
if(non_linearity == NON_LINEARITY_CURVED_BRIDGE)
{
float value = fabsf(s) - 0.025f;
float sign = s > 0.0f ? 1.0f : -1.5f;
curved_bridge_ = (fabsf(value) + value) * sign;
}
s += in;
s = fclamp(s, -20.f, +20.f);
s = dc_blocker_.Process(s);
s = iir_damping_filter_.Process(s);
string_.Write(s);
out_sample_[1] = out_sample_[0];
out_sample_[0] = s;
}
crossfade_.SetPos(src_phase_);
return crossfade_.Process(out_sample_[1], out_sample_[0]);
}

@ -0,0 +1,103 @@
#pragma once
#ifndef DSY_STRING_H
#define DSY_STRING_H
#include <stdint.h>
#include "Dynamics/crossfade.h"
#include "Utility/dcblock.h"
#include "Utility/delayline.h"
#include "Filters/svf.h"
#include "Filters/tone.h"
#ifdef __cplusplus
/** @file KarplusString.h */
namespace daisysp
{
/**
@brief Comb filter / KS string.
@author Ben Sergentanis
@date Jan 2021
"Lite" version of the implementation used in Rings \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/formant_oscillator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class String
{
public:
String() {}
~String() {}
/** Initialize the module.
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Clear the delay line */
void Reset();
/** Get the next floating point sample
\param in Signal to excite the string.
*/
float Process(const float in);
/** Set the string frequency.
\param freq Frequency in Hz
*/
void SetFreq(float freq);
/** Set the string's behavior.
\param -1 to 0 is curved bridge, 0 to 1 is dispersion.
*/
void SetNonLinearity(float non_linearity_amount);
/** Set the string's overall brightness
\param Works 0-1.
*/
void SetBrightness(float brightness);
/** Set the string's decay time.
\param damping Works 0-1.
*/
void SetDamping(float damping);
private:
static constexpr size_t kDelayLineSize = 1024;
enum StringNonLinearity
{
NON_LINEARITY_CURVED_BRIDGE,
NON_LINEARITY_DISPERSION
};
template <String::StringNonLinearity non_linearity>
float ProcessInternal(const float in);
DelayLine<float, kDelayLineSize> string_;
DelayLine<float, kDelayLineSize / 4> stretch_;
float frequency_, non_linearity_amount_, brightness_, damping_;
float sample_rate_;
Tone iir_damping_filter_;
DcBlock dc_blocker_;
CrossFade crossfade_;
float dispersion_noise_;
float curved_bridge_;
// Very crappy linear interpolation upsampler used for low pitches that
// do not fit the delay line. Rarely used.
float src_phase_;
float out_sample_[2];
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,90 @@
#pragma once
#ifndef DSY_POLYPLUCK_H
#define DSY_POLYPLUCK_H
#include <stdlib.h>
#include "Utility/dsp.h"
#include "PhysicalModeling/pluck.h"
#include "Utility/dcblock.h"
namespace daisysp
{
/** Simplified Pseudo-Polyphonic Pluck Voice
Template Based Pluck Voice, with configurable number of voices and simple pseudo-polyphony.
DC Blocking included to prevent biases from causing unwanted saturation distortion.
**Author**: shensley
**Date Added**: March 2020
*/
template <size_t num_voices>
class PolyPluck
{
public:
/** Initializes the PolyPluck instance.
\param sample_rate: rate in Hz that the Process() function will be called.
*/
void Init(float sample_rate)
{
active_voice_ = 0;
p_damp_ = 0.95f;
p_decay_ = 0.75f;
for(size_t i = 0; i < num_voices; i++)
{
plk_[i].Init(sample_rate, plkbuff_[i], 256, PLUCK_MODE_RECURSIVE);
plk_[i].SetDamp(0.85f);
plk_[i].SetAmp(0.18f);
plk_[i].SetDecay(0.85f);
}
blk_.Init(sample_rate);
}
/** Process function, synthesizes and sums the output of all voices,
triggering a new voice with frequency of MIDI note number when trig > 0.
\param trig: value by reference of trig. When trig > 0 a the next voice will be triggered, and trig will be set to 0.
\param note: MIDI note number for the active_voice.
*/
float Process(float &trig, float note)
{
float sig, tval;
sig = 0.0f;
if(trig > 0.0f)
{
// increment active voice
active_voice_ = (active_voice_ + 1) % num_voices;
// set new voice to new note
plk_[active_voice_].SetDamp(p_damp_);
plk_[active_voice_].SetDecay(p_decay_);
plk_[active_voice_].SetAmp(0.25f);
}
plk_[active_voice_].SetFreq(mtof(note));
for(size_t i = 0; i < num_voices; i++)
{
tval = (trig > 0.0f && i == active_voice_) ? 1.0f : 0.0f;
sig += plk_[i].Process(tval);
}
if(trig > 0.0f)
trig = 0.0f;
return blk_.Process(sig);
}
/** Sets the decay coefficients of the pluck voices.
\param p expects 0.0-1.0 input.
*/
void SetDecay(float p) { p_damp_ = p; }
private:
DcBlock blk_;
Pluck plk_[num_voices];
float plkbuff_[num_voices][256];
float p_damp_, p_decay_;
size_t active_voice_;
};
} // namespace daisysp
#endif

@ -0,0 +1,213 @@
#include "drip.h"
#include <math.h>
#include <cstdlib>
#include "../utility/dsp.h"
using namespace daisysp;
#define WUTR_SOUND_DECAY 0.95f
#define WUTR_SYSTEM_DECAY 0.996f
#define WUTR_GAIN 1.0f
#define WUTR_NUM_SOURCES 10.0f
#define WUTR_CENTER_FREQ0 450.0f
#define WUTR_CENTER_FREQ1 600.0f
#define WUTR_CENTER_FREQ2 750.0f
#define WUTR_RESON 0.9985f
#define WUTR_FREQ_SWEEP 1.0001f
#define MAX_SHAKE 2000.0f
int Drip::my_random(int max)
{
return (rand() % (max + 1));
}
float Drip::noise_tick()
{
float temp;
temp = 1.0f * rand() - 1073741823.5f;
return temp * (1.0f / 1073741823.0f);
}
void Drip::Init(float sample_rate, float dettack)
{
sample_rate_ = sample_rate;
float temp;
dettack_ = dettack;
num_tubes_ = 10;
damp_ = 0.2f;
shake_max_ = 0.0f;
freq_ = 450.0f;
freq1_ = 600.0f;
freq2_ = 720.0f;
amp_ = 0.3f;
snd_level_ = 0.0;
float tpidsr = 2.0 * PI_F / sample_rate_;
kloop_ = (sample_rate_ * dettack_);
outputs00_ = 0.0f;
outputs01_ = 0.0f;
outputs10_ = 0.0f;
outputs11_ = 0.0f;
outputs20_ = 0.0f;
outputs21_ = 0.0f;
total_energy_ = 0.0f;
center_freqs0_ = res_freq0_ = WUTR_CENTER_FREQ0;
center_freqs1_ = res_freq1_ = WUTR_CENTER_FREQ1;
center_freqs2_ = res_freq2_ = WUTR_CENTER_FREQ2;
num_objects_save_ = num_objects_ = WUTR_NUM_SOURCES;
sound_decay_ = WUTR_SOUND_DECAY;
system_decay_ = WUTR_SYSTEM_DECAY;
temp = logf(WUTR_NUM_SOURCES) * WUTR_GAIN / WUTR_NUM_SOURCES;
gains0_ = gains1_ = gains2_ = temp;
coeffs01_ = WUTR_RESON * WUTR_RESON;
coeffs00_ = -WUTR_RESON * 2.0f * cosf(WUTR_CENTER_FREQ0 * tpidsr);
coeffs11_ = WUTR_RESON * WUTR_RESON;
coeffs10_ = -WUTR_RESON * 2.0f * cosf(WUTR_CENTER_FREQ1 * tpidsr);
coeffs21_ = WUTR_RESON * WUTR_RESON;
coeffs20_ = -WUTR_RESON * 2.0f * cosf(WUTR_CENTER_FREQ2 * tpidsr);
shake_energy_ = amp_ * 1.0f * MAX_SHAKE * 0.1f;
shake_damp_ = 0.0f;
if(shake_energy_ > MAX_SHAKE)
shake_energy_ = MAX_SHAKE;
shake_max_save_ = 0.0f;
num_objects_ = 10.0f;
finalZ0_ = finalZ1_ = finalZ2_ = 0.0f;
}
float Drip::Process(bool trig)
{
float data;
float lastOutput;
float tpidsr = 2.0f * PI_F / sample_rate_;
if(trig)
{
Init(sample_rate_, dettack_);
}
if(num_tubes_ != 0.0f && num_tubes_ != num_objects_)
{
num_objects_ = num_tubes_;
if(num_objects_ < 1.0f)
{
num_objects_ = 1.0f;
}
}
if(freq_ != 0.0f && freq_ != res_freq0_)
{
res_freq0_ = freq_;
coeffs00_ = -WUTR_RESON * 2.0f * cosf(res_freq0_ * tpidsr);
}
if(damp_ != 0.0f && damp_ != shake_damp_)
{
shake_damp_ = damp_;
system_decay_ = WUTR_SYSTEM_DECAY + (shake_damp_ * 0.002f);
}
if(shake_max_ != 0.0f && shake_max_ != shake_max_save_)
{
shake_max_save_ = shake_max_;
shake_energy_ += shake_max_save_ * MAX_SHAKE * 0.1f;
if(shake_energy_ > MAX_SHAKE)
shake_energy_ = MAX_SHAKE;
}
if(freq1_ != 0.0f && freq1_ != res_freq1_)
{
res_freq1_ = freq1_;
coeffs10_ = -WUTR_RESON * 2.0f * cosf(res_freq1_ * tpidsr);
}
if(freq2_ != 0.0f && freq2_ != res_freq2_)
{
res_freq2_ = freq2_;
coeffs20_ = -WUTR_RESON * 2.0f * cosf(res_freq2_ * tpidsr);
}
if((--kloop_) == 0.0f)
{
shake_energy_ = 0.0f;
}
float shakeEnergy = shake_energy_;
float systemDecay = system_decay_;
float sndLevel = snd_level_;
float num_objects = num_objects_;
float soundDecay = sound_decay_;
float inputs0, inputs1, inputs2;
shakeEnergy *= systemDecay; /* Exponential system decay */
sndLevel = shakeEnergy;
if(my_random(32767) < num_objects)
{
int j;
j = my_random(3);
if(j == 0)
{
center_freqs0_ = res_freq1_ * (0.75f + (0.25f * noise_tick()));
gains0_ = fabsf(noise_tick());
}
else if(j == 1)
{
center_freqs1_ = res_freq1_ * (1.0f + (0.25f * noise_tick()));
gains1_ = fabsf(noise_tick());
}
else
{
center_freqs2_ = res_freq1_ * (1.25f + (0.25f * noise_tick()));
gains2_ = fabsf(noise_tick());
}
}
gains0_ *= WUTR_RESON;
if(gains0_ > 0.001f)
{
center_freqs0_ *= WUTR_FREQ_SWEEP;
coeffs00_ = -WUTR_RESON * 2.0f * cosf(center_freqs0_ * tpidsr);
}
gains1_ *= WUTR_RESON;
if(gains1_ > 0.00f)
{
center_freqs1_ *= WUTR_FREQ_SWEEP;
coeffs10_ = -WUTR_RESON * 2.0f * cosf(center_freqs1_ * tpidsr);
}
gains2_ *= WUTR_RESON;
if(gains2_ > 0.001f)
{
center_freqs2_ *= WUTR_FREQ_SWEEP;
coeffs20_ = -WUTR_RESON * 2.0f * cosf(center_freqs2_ * tpidsr);
}
sndLevel *= soundDecay;
inputs0 = sndLevel;
inputs0 *= noise_tick();
inputs1 = inputs0 * gains1_;
inputs2 = inputs0 * gains2_;
inputs0 *= gains0_;
inputs0 -= outputs00_ * coeffs00_;
inputs0 -= outputs01_ * coeffs01_;
outputs01_ = outputs00_;
outputs00_ = inputs0;
data = gains0_ * outputs00_;
inputs1 -= outputs10_ * coeffs10_;
inputs1 -= outputs11_ * coeffs11_;
outputs11_ = outputs10_;
outputs10_ = inputs1_;
data += gains1_ * outputs10_;
inputs2 -= outputs20_ * coeffs20_;
inputs2 -= outputs21_ * coeffs21_;
outputs21_ = outputs20_;
outputs20_ = inputs2_;
data += gains2_ * outputs20_;
finalZ2_ = finalZ1_;
finalZ1_ = finalZ0_;
finalZ0_ = data * 4.0f;
lastOutput = finalZ2_ - finalZ0_;
lastOutput *= 0.005f;
shake_energy_ = shakeEnergy;
snd_level_ = sndLevel;
return lastOutput;
}

@ -0,0 +1,53 @@
#pragma once
#ifndef DSY_DRIP_H
#define DSY_DRIP_H
#include <stdint.h>
#ifdef __cplusplus
/** @file drip.h */
namespace daisysp
{
/**
Imitates the sound of dripping water via Physical Modeling Synthesis. \n
Ported from soundpipe by Ben Sergentanis, May 2020
@author Perry Cook
@date 2000
*/
class Drip
{
public:
Drip() {}
~Drip() {}
/**
Initializes the Drip module.
\param sample_rate The sample rate of the audio engine being run.
\param dettack The period of time over which all sound is stopped.
*/
void Init(float sample_rate, float dettack);
/**
Process the next floating point sample.
\param trig If true, begins a new drip.
\return Next sample.
*/
float Process(bool trig);
private:
float gains0_, gains1_, gains2_, kloop_, dettack_, num_tubes_, damp_,
shake_max_, freq_, freq1_, freq2_, amp_, snd_level_, outputs00_,
outputs01_, outputs10_, outputs11_, outputs20_, outputs21_,
total_energy_, center_freqs0_, center_freqs1_, center_freqs2_,
num_objects_save_, sound_decay_, system_decay_, finalZ0_, finalZ1_,
finalZ2_, coeffs01_, coeffs00_, coeffs11_, coeffs10_, coeffs21_,
coeffs20_, shake_energy_, shake_damp_, shake_max_save_, num_objects_,
sample_rate_, res_freq0_, res_freq1_, res_freq2_, inputs1_, inputs2_;
int my_random(int max);
float noise_tick();
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,109 @@
#include "modalvoice.h"
#include <algorithm>
using namespace daisysp;
void ModalVoice::Init(float sample_rate)
{
sample_rate_ = sample_rate;
aux_ = 0.f;
excitation_filter_.Init();
resonator_.Init(0.015f, 24, sample_rate_);
excitation_filter_.Init();
dust_.Init();
SetSustain(false);
SetFreq(440.f);
SetAccent(.3f);
SetStructure(.6f);
SetBrightness(.8f);
SetDamping(.6f);
}
void ModalVoice::SetSustain(bool sustain)
{
sustain_ = sustain;
}
void ModalVoice::Trig()
{
trig_ = true;
}
void ModalVoice::SetFreq(float freq)
{
resonator_.SetFreq(freq);
f0_ = freq / sample_rate_;
f0_ = fclamp(f0_, 0.f, .25f);
}
void ModalVoice::SetAccent(float accent)
{
accent_ = fclamp(accent, 0.f, 1.f);
}
void ModalVoice::SetStructure(float structure)
{
resonator_.SetStructure(structure);
}
void ModalVoice::SetBrightness(float brightness)
{
brightness_ = fclamp(brightness, 0.f, 1.f);
density_ = brightness_ * brightness_;
}
void ModalVoice::SetDamping(float damping)
{
damping_ = fclamp(damping, 0.f, 1.f);
}
float ModalVoice::GetAux()
{
return aux_;
}
float ModalVoice::Process(bool trigger)
{
float brightness = brightness_ + 0.25f * accent_ * (1.0f - brightness_);
float damping = damping_ + 0.25f * accent_ * (1.0f - damping_);
const float range = sustain_ ? 36.0f : 60.0f;
const float f = sustain_ ? 4.0f * f0_ : 2.0f * f0_;
const float cutoff = fmin(
f
* powf(2.f,
kOneTwelfth
* ((brightness * (2.0f - brightness) - 0.5f) * range)),
0.499f);
const float q = sustain_ ? 0.7f : 1.5f;
float temp = 0.f;
// Synthesize excitation signal.
if(sustain_)
{
const float dust_f = 0.00005f + 0.99995f * density_ * density_;
dust_.SetDensity(dust_f);
temp = dust_.Process() * (4.0f - dust_f * 3.0f) * accent_;
}
else if(trigger || trig_)
{
const float attenuation = 1.0f - damping * 0.5f;
const float amplitude = (0.12f + 0.08f * accent_) * attenuation;
temp = amplitude * powf(2.f, kOneTwelfth * (cutoff * cutoff * 24.0f))
/ cutoff;
trig_ = false;
}
const float one = 1.0f;
excitation_filter_.Process<ResonatorSvf<1>::LOW_PASS, false>(
&cutoff, &q, &one, temp, &temp);
aux_ = temp;
resonator_.SetBrightness(brightness);
resonator_.SetDamping(damping);
return resonator_.Process(temp);
}

@ -0,0 +1,91 @@
#pragma once
#ifndef DSY_MODAL_H
#define DSY_MODAL_H
#include <stdint.h>
#include "Filters/svf.h"
#include "PhysicalModeling/resonator.h"
#include "Noise/dust.h"
#ifdef __cplusplus
/** @file modalvoice.h */
namespace daisysp
{
/**
@brief Simple modal synthesis voice with a mallet exciter: click -> LPF -> resonator.
@author Ben Sergentanis
@date Jan 2021
The click can be replaced by continuous white noise. \n \n
Ported from pichenettes/eurorack/plaits/dsp/physical_modelling/modal_voice.h \n
and pichenettes/eurorack/plaits/dsp/physical_modelling/modal_voice.cc \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class ModalVoice
{
public:
ModalVoice() {}
~ModalVoice() {}
/** Initialize the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get the next sample
\param trigger Strike the resonator. Defaults to false.
*/
float Process(bool trigger = false);
/** Continually excite the resonator with noise.
\param sustain True turns on the noise.
*/
void SetSustain(bool sustain);
/** Strike the resonator. */
void Trig();
/** Set the resonator root frequency.
\param freq Frequency in Hz.
*/
void SetFreq(float freq);
/** Hit the resonator a bit harder.
\param accent Works 0-1.
*/
void SetAccent(float accent);
/** Changes the general charater of the resonator (stiffness, brightness)
\param structure Works best from 0-1
*/
void SetStructure(float structure);
/** Set the brighness of the resonator, and the noise density.
\param brightness Works best 0-1
*/
void SetBrightness(float brightness);
/** How long the resonant body takes to decay.
\param damping Works best 0-1
*/
void SetDamping(float damping);
/** Get the raw excitation signal. Must call Process() first. */
float GetAux();
private:
float sample_rate_;
bool sustain_, trig_;
float f0_, structure_, brightness_, damping_;
float density_, accent_;
float aux_;
ResonatorSvf<1> excitation_filter_;
Resonator resonator_;
Dust dust_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,122 @@
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "pluck.h"
#define PLUKMIN 64
#define INTERPFACTOR 256.0f
#define INTERPFACTOR_I 255
using namespace daisysp;
void Pluck::Reinit()
{
int n;
float val = 0;
float *ap = buf_;
//npts_ = (int32_t)roundf(decay_ * (float)(maxpts_ - PLUKMIN) + PLUKMIN);
npts_ = (int32_t)(decay_ * (float)(maxpts_ - PLUKMIN) + PLUKMIN);
//sicps_ = ((float)npts_ * INTERPFACTOR + INTERPFACTOR/2.0f) * (1.0f / _sr);
sicps_ = ((float)npts_ * 256.0f + 128.0f) * (1.0f / sample_rate_);
for(n = npts_; n--;)
{
val = (float)((float)rand() / (float)RAND_MAX);
*ap++ = (val * 2.0f) - 1.0f;
}
phs256_ = 0;
}
void Pluck::Init(float sample_rate, float *buf, int32_t npts, int32_t mode)
{
amp_ = 0.5f;
freq_ = 300;
decay_ = 1.0f;
sample_rate_ = sample_rate;
mode_ = mode;
maxpts_ = npts;
npts_ = npts;
buf_ = buf;
Reinit();
/* tuned pitch convt */
sicps_ = (npts * 256.0f + 128.0f) * (1.0f / sample_rate_);
init_ = 1;
}
float Pluck::Process(float &trig)
{
float * fp, out;
int32_t phs256, phsinc, ltwopi, offset;
float coeff;
// unused variable
// float inv_coeff;
float frac, diff;
float dampmin = 0.42f;
if(trig != 0)
{
init_ = 0;
Reinit();
}
if(init_)
{
return 0;
}
// Set Coeff for mode.
switch(mode_)
{
case PLUCK_MODE_RECURSIVE:
coeff = ((0.5f - dampmin) * damp_) + dampmin;
break;
case PLUCK_MODE_WEIGHTED_AVERAGE:
coeff = 0.05f + (damp_ * 0.90f);
break;
default: coeff = 0.5f; break;
}
// variable set but not used
//inv_coeff = 1.0f - coeff;
phsinc = (int32_t)(freq_ * sicps_);
phs256 = phs256_;
ltwopi = npts_ << 8;
offset = phs256 >> 8;
fp = (float *)buf_ + offset; /* lookup position */
diff = fp[1] - fp[0];
frac = (float)(phs256 & 255) / 256.0f; /* w. interpolation */
out = (fp[0] + diff * frac) * amp_; /* gives output val */
if((phs256 += phsinc) >= ltwopi)
{
int nn;
float preval;
phs256 -= ltwopi;
fp = buf_;
preval = fp[0];
fp[0] = fp[npts_];
fp++;
nn = npts_;
do
{
/* 1st order recursive filter*/
//preval = (*fp + preval) * coeff;
/* weighted average - stretches decay times */
switch(mode_)
{
case PLUCK_MODE_RECURSIVE:
preval = (*fp + preval) * coeff;
break;
case PLUCK_MODE_WEIGHTED_AVERAGE:
preval = (*fp * coeff) + (preval * (1.0f - coeff));
break;
default: break;
}
*fp++ = preval;
} while(--nn);
}
phs256_ = phs256;
return out;
}

@ -0,0 +1,100 @@
#pragma once
#ifndef DSY_PLUCK_H
#define DSY_PLUCK_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** The method of natural decay that the algorithm will use.
- RECURSIVE: 1st order recursive filter, with coefs .5.
- WEIGHTED_AVERAGE: weighted averaging.
*/
enum
{
PLUCK_MODE_RECURSIVE,
PLUCK_MODE_WEIGHTED_AVERAGE,
PLUCK_LAST,
};
/** Produces a naturally decaying plucked string or drum sound based on the Karplus-Strong algorithms.
Ported from soundpipe to DaisySP
This code was originally extracted from the Csound opcode "pluck"
Original Author(s): Barry Vercoe, John ffitch
Year: 1991
Location: OOps/ugens4.c
*/
class Pluck
{
public:
Pluck() {}
~Pluck() {}
/** Initializes the Pluck module.
\param sample_rate: Sample rate of the audio engine being run.
\param buf: buffer used as an impulse when triggering the Pluck algorithm
\param npt: number of elementes in buf.
\param mode: Sets the mode of the algorithm.
*/
void Init(float sample_rate, float *buf, int32_t npt, int32_t mode);
/** Processes the waveform to be generated, returning one sample. This should be called once per sample period.
*/
float Process(float &trig);
/**
Sets the amplitude of the output signal.
Input range: 0-1?
*/
inline void SetAmp(float amp) { amp_ = amp; }
/** Sets the frequency of the output signal in Hz.
Input range: Any positive value
*/
inline void SetFreq(float freq) { freq_ = freq; }
/** Sets the time it takes for a triggered note to end in seconds.
Input range: 0-1
*/
inline void SetDecay(float decay) { decay_ = decay; }
/** Sets the dampening factor applied by the filter (based on PLUCK_MODE)
Input range: 0-1
*/
inline void SetDamp(float damp) { damp_ = damp; }
/** Sets the mode of the algorithm.
*/
inline void SetMode(int32_t mode) { mode_ = mode; }
/** Returns the current value for amp.
*/
inline float GetAmp() { return amp_; }
/** Returns the current value for freq.
*/
inline float GetFreq() { return freq_; }
/** Returns the current value for decay.
*/
inline float GetDecay() { return decay_; }
/** Returns the current value for damp.
*/
inline float GetDamp() { return damp_; }
/** Returns the current value for mode.
*/
inline int32_t GetMode() { return mode_; }
private:
void Reinit();
float amp_, freq_, decay_, damp_, ifreq_;
float sicps_;
int32_t phs256_, npts_, maxpts_;
float * buf_;
float sample_rate_;
char init_;
int32_t mode_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,158 @@
#include "resonator.h"
#include <math.h>
using namespace daisysp;
void Resonator::Init(float position, int resolution, float sample_rate)
{
sample_rate_ = sample_rate;
SetFreq(440.f);
SetStructure(.5f);
SetBrightness(.5f);
SetDamping(.5f);
resolution_ = fmin(resolution, kMaxNumModes);
for(int i = 0; i < resolution; ++i)
{
mode_amplitude_[i] = cos(position * TWOPI_F) * 0.25f;
}
for(int i = 0; i < kMaxNumModes / kModeBatchSize; ++i)
{
mode_filters_[i].Init();
}
}
inline float NthHarmonicCompensation(int n, float stiffness)
{
float stretch_factor = 1.0f;
for(int i = 0; i < n - 1; ++i)
{
stretch_factor += stiffness;
if(stiffness < 0.0f)
{
stiffness *= 0.93f;
}
else
{
stiffness *= 0.98f;
}
}
return 1.0f / stretch_factor;
}
float Resonator::Process(const float in)
{
//convert Hz to cycles / sample
float out = 0.f;
float stiffness = CalcStiff(structure_);
float f0 = frequency_ * NthHarmonicCompensation(3, stiffness);
float brightness = brightness_;
float harmonic = f0;
float stretch_factor = 1.0f;
float input = damping_ * 79.7f;
float q_sqrt = powf(2.f, input * ratiofrac_);
float q = 500.0f * q_sqrt * q_sqrt;
brightness *= 1.0f - structure_ * 0.3f;
brightness *= 1.0f - damping_ * 0.3f;
float q_loss = brightness * (2.0f - brightness) * 0.85f + 0.15f;
float mode_q[kModeBatchSize];
float mode_f[kModeBatchSize];
float mode_a[kModeBatchSize];
int batch_counter = 0;
ResonatorSvf<kModeBatchSize>* batch_processor = &mode_filters_[0];
for(int i = 0; i < resolution_; ++i)
{
float mode_frequency = harmonic * stretch_factor;
if(mode_frequency >= 0.499f)
{
mode_frequency = 0.499f;
}
const float mode_attenuation = 1.0f - mode_frequency * 2.0f;
mode_f[batch_counter] = mode_frequency;
mode_q[batch_counter] = 1.0f + mode_frequency * q;
mode_a[batch_counter] = mode_amplitude_[i] * mode_attenuation;
++batch_counter;
if(batch_counter == kModeBatchSize)
{
batch_counter = 0;
batch_processor
->Process<ResonatorSvf<kModeBatchSize>::BAND_PASS, true>(
mode_f, mode_q, mode_a, in, &out);
++batch_processor;
}
stretch_factor += stiffness;
if(stiffness < 0.0f)
{
// Make sure that the partials do not fold back into negative frequencies.
stiffness *= 0.93f;
}
else
{
// This helps adding a few extra partials in the highest frequencies.
stiffness *= 0.98f;
}
harmonic += f0;
q *= q_loss;
}
return out;
}
void Resonator::SetFreq(float freq)
{
frequency_ = freq / sample_rate_;
}
void Resonator::SetStructure(float structure)
{
structure_ = fmax(fmin(structure, 1.f), 0.f);
}
void Resonator::SetBrightness(float brightness)
{
brightness_ = fmax(fmin(brightness, 1.f), 0.f);
}
void Resonator::SetDamping(float damping)
{
damping_ = fmax(fmin(damping, 1.f), 0.f);
}
float Resonator::CalcStiff(float sig)
{
if(sig < .25f)
{
sig = .25 - sig;
sig = -sig * .25;
}
else if(sig < .3f)
{
sig = 0.f;
}
else if(sig < .9f)
{
sig -= .3f;
sig *= stiff_frac_2;
}
else
{
sig -= .9f;
sig *= 10; // div by .1
sig *= sig;
sig = 1.5 - cos(sig * PI_F) * .5f;
}
return sig;
}

@ -0,0 +1,183 @@
#pragma once
#ifndef DSY_RESONATOR_H
#define DSY_RESONATOR_H
#include <stdint.h>
#include <stddef.h>
#include "Utility/dsp.h"
#ifdef __cplusplus
/** @file resonator.h */
namespace daisysp
{
// We render 4 modes simultaneously since there are enough registers to hold
// all state variables.
/**
@brief SVF for use in the Resonator Class \n
@author Ported by Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/physical_modelling/resonator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
template <int batch_size>
class ResonatorSvf
{
public:
enum FilterMode
{
LOW_PASS,
BAND_PASS,
BAND_PASS_NORMALIZED,
HIGH_PASS
};
ResonatorSvf() {}
~ResonatorSvf() {}
void Init()
{
for(int i = 0; i < batch_size; ++i)
{
state_1_[i] = state_2_[i] = 0.0f;
}
}
template <FilterMode mode, bool add>
void Process(const float* f,
const float* q,
const float* gain,
const float in,
float* out)
{
float g[batch_size];
float r[batch_size];
float r_plus_g[batch_size];
float h[batch_size];
float state_1[batch_size];
float state_2[batch_size];
float gains[batch_size];
for(int i = 0; i < batch_size; ++i)
{
g[i] = fasttan(f[i]);
r[i] = 1.0f / q[i];
h[i] = 1.0f / (1.0f + r[i] * g[i] + g[i] * g[i]);
r_plus_g[i] = r[i] + g[i];
state_1[i] = state_1_[i];
state_2[i] = state_2_[i];
gains[i] = gain[i];
}
float s_in = in;
float s_out = 0.0f;
for(int i = 0; i < batch_size; ++i)
{
const float hp
= (s_in - r_plus_g[i] * state_1[i] - state_2[i]) * h[i];
const float bp = g[i] * hp + state_1[i];
state_1[i] = g[i] * hp + bp;
const float lp = g[i] * bp + state_2[i];
state_2[i] = g[i] * bp + lp;
s_out += gains[i] * ((mode == LOW_PASS) ? lp : bp);
}
if(add)
{
*out++ += s_out;
}
else
{
*out++ = s_out;
}
for(int i = 0; i < batch_size; ++i)
{
state_1_[i] = state_1[i];
state_2_[i] = state_2[i];
}
}
private:
static constexpr float kPiPow3 = PI_F * PI_F * PI_F;
static constexpr float kPiPow5 = kPiPow3 * PI_F * PI_F;
static inline float fasttan(float f)
{
const float a = 3.260e-01 * kPiPow3;
const float b = 1.823e-01 * kPiPow5;
float f2 = f * f;
return f * (PI_F + f2 * (a + b * f2));
}
float state_1_[batch_size];
float state_2_[batch_size];
};
/**
@brief Resonant Body Simulation
@author Ported by Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/physical_modelling/resonator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class Resonator
{
public:
Resonator() {}
~Resonator() {}
/** Initialize the module
\param position Offset the phase of the amplitudes. 0-1
\param resolution Quality vs speed scalar
\param sample_rate Samplerate of the audio engine being run.
*/
void Init(float position, int resolution, float sample_rate);
/** Get the next sample_rate
\param in The signal to excited the resonant body
*/
float Process(const float in);
/** Resonator frequency.
\param freq Frequency in Hz.
*/
void SetFreq(float freq);
/** Changes the general charater of the resonator (stiffness, brightness)
\param structure Works best from 0-1
*/
void SetStructure(float structure);
/** Set the brighness of the resonator
\param brightness Works best 0-1
*/
void SetBrightness(float brightness);
/** How long the resonant body takes to decay.
\param damping Works best 0-1
*/
void SetDamping(float damping);
private:
int resolution_;
float frequency_, brightness_, structure_, damping_;
static constexpr int kMaxNumModes = 24;
static constexpr int kModeBatchSize = 4;
static constexpr float ratiofrac_ = 1.f / 12.f;
static constexpr float stiff_frac_ = 1.f / 64.f;
static constexpr float stiff_frac_2 = 1.f / .6f;
float sample_rate_;
float CalcStiff(float sig);
float mode_amplitude_[kMaxNumModes];
ResonatorSvf<kModeBatchSize> mode_filters_[kMaxNumModes / kModeBatchSize];
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,124 @@
#include "stringvoice.h"
#include <algorithm>
#include "../utility/dsp.h"
using namespace daisysp;
void StringVoice::Init(float sample_rate)
{
sample_rate_ = sample_rate;
excitation_filter_.Init(sample_rate);
string_.Init(sample_rate_);
dust_.Init();
remaining_noise_samples_ = 0;
SetSustain(false);
SetFreq(440.f);
SetAccent(.8f);
SetStructure(.7f);
SetBrightness(.2f);
SetDamping(.7f);
}
void StringVoice::Reset()
{
string_.Reset();
}
void StringVoice::SetSustain(bool sustain)
{
sustain_ = sustain;
}
void StringVoice::Trig()
{
trig_ = true;
}
void StringVoice::SetFreq(float freq)
{
string_.SetFreq(freq);
f0_ = freq / sample_rate_;
f0_ = fclamp(f0_, 0.f, .25f);
}
void StringVoice::SetAccent(float accent)
{
accent_ = fclamp(accent, 0.f, 1.f);
}
void StringVoice::SetStructure(float structure)
{
structure = fclamp(structure, 0.f, 1.f);
const float non_linearity
= structure < 0.24f
? (structure - 0.24f) * 4.166f
: (structure > 0.26f ? (structure - 0.26f) * 1.35135f : 0.0f);
string_.SetNonLinearity(non_linearity);
}
void StringVoice::SetBrightness(float brightness)
{
brightness_ = fclamp(brightness, 0.f, 1.f);
density_ = brightness_ * brightness_;
}
void StringVoice::SetDamping(float damping)
{
damping_ = fclamp(damping, 0.f, 1.f);
}
float StringVoice::GetAux()
{
return aux_;
}
float StringVoice::Process(bool trigger)
{
const float brightness = brightness_ + .25 * accent_ * (1.f - brightness_);
const float damping = damping_ + .25 * accent_ * (1.f - damping_);
// Synthesize excitation signal.
if(trigger || trig_ || sustain_)
{
trig_ = false;
const float range = 72.0f;
const float f = 4.0f * f0_;
const float cutoff = fmin(
f
* powf(2.f,
kOneTwelfth * (brightness * (2.0f - brightness) - 0.5f)
* range),
0.499f);
const float q = sustain_ ? 1.0f : 0.5f;
remaining_noise_samples_ = static_cast<size_t>(1.0f / f0_);
excitation_filter_.SetFreq(cutoff * sample_rate_);
excitation_filter_.SetRes(q);
}
float temp = 0.f;
if(sustain_)
{
const float dust_f = 0.00005f + 0.99995f * density_ * density_;
dust_.SetDensity(dust_f);
temp = dust_.Process() * (8.0f - dust_f * 6.0f) * accent_;
}
else if(remaining_noise_samples_)
{
temp = 2.0f * rand() * kRandFrac - 1.0f;
remaining_noise_samples_--;
remaining_noise_samples_ = DSY_MAX(remaining_noise_samples_, 0.f);
}
excitation_filter_.Process(temp);
temp = excitation_filter_.Low();
aux_ = temp;
string_.SetBrightness(brightness);
string_.SetDamping(damping);
return string_.Process(temp);
}

@ -0,0 +1,94 @@
#pragma once
#ifndef DSY_STRINGVOICE_H
#define DSY_STRINGVOICE_H
#include "Filters/svf.h"
#include "PhysicalModeling/KarplusString.h"
#include "Noise/dust.h"
#include <stdint.h>
#ifdef __cplusplus
/** @file stringvoice.h */
namespace daisysp
{
/**
@brief Extended Karplus-Strong, with all the niceties from Rings
@author Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/physical_modelling/string_voice.h \n
and pichenettes/eurorack/plaits/dsp/physical_modelling/string_voice.cc \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class StringVoice
{
public:
StringVoice() {}
~StringVoice() {}
/** Initialize the module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Reset the string oscillator */
void Reset();
/** Get the next sample
\param trigger Strike the string. Defaults to false.
*/
float Process(bool trigger = false);
/** Continually excite the string with noise.
\param sustain True turns on the noise.
*/
void SetSustain(bool sustain);
/** Strike the string. */
void Trig();
/** Set the string root frequency.
\param freq Frequency in Hz.
*/
void SetFreq(float freq);
/** Hit the string a bit harder. Influences brightness and decay.
\param accent Works 0-1.
*/
void SetAccent(float accent);
/** Changes the string's nonlinearity (string type).
\param structure Works 0-1. 0-.26 is curved bridge, .26-1 is dispersion.
*/
void SetStructure(float structure);
/** Set the brighness of the string, and the noise density.
\param brightness Works best 0-1
*/
void SetBrightness(float brightness);
/** How long the resonant body takes to decay relative to the accent level.
\param damping Works best 0-1. Full damp is only achieved with full accent.
*/
void SetDamping(float damping);
/** Get the raw excitation signal. Must call Process() first. */
float GetAux();
private:
float sample_rate_;
bool sustain_, trig_;
float f0_, brightness_, damping_;
float density_, accent_;
float aux_;
Dust dust_;
Svf excitation_filter_;
String string_;
size_t remaining_noise_samples_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,140 @@
#include "blosc.h"
#include <math.h>
using namespace daisysp;
void BlOsc::Init(float sample_rate)
{
sampling_freq_ = sample_rate;
half_sr_ = 0.5 * sampling_freq_;
quarter_sr_ = sampling_freq_ * 0.25;
sec_per_sample_ = 1.0 / sampling_freq_;
two_over_sr_ = (float)(2.0 / sampling_freq_);
four_over_sr_ = 4.0 / sampling_freq_;
freq_ = 440;
amp_ = 0.5;
pw_ = 0.5;
iota_ = 0;
mode_ = WAVE_TRIANGLE;
for(int i = 0; i < 2; i++)
{
rec0_[i] = rec1_[i] = vec0_[i] = vec1_[i] = 0.0;
}
for(int i = 0; i < 4096; i++)
{
vec2_[i] = 0.0;
}
}
float BlOsc::ProcessSquare()
{
float out;
float fSlow2 = fmin(2047.0, sampling_freq_ * (pw_ / freq_));
float fSlow5 = (float)((int)fSlow2 + 1) - fSlow2;
float fSlow6 = (quarter_sr_ / freq_);
float fSlow7 = (sec_per_sample_ * freq_);
float fSlow8 = fSlow2 - (int)fSlow2;
rec0_[0] = fmodf(rec0_[1] + fSlow7, 1.0);
float fTemp0 = 2.0 * rec0_[0] - 1.0;
fTemp0 *= fTemp0; //mult faster than fpow for squaring?
vec1_[0] = fTemp0;
float fTemp1 = (fSlow6 * ((fTemp0 - vec1_[1])));
vec2_[iota_ & 4095] = fTemp1;
out = amp_
* (0.0
- ((fSlow5 * vec2_[(iota_ - (int)fSlow2) & 4095]
+ fSlow8 * vec2_[(iota_ - ((int)fSlow2 + 1)) & 4095])
- fTemp1));
rec0_[1] = rec0_[0];
vec1_[1] = vec1_[0];
iota_++;
return out;
}
float BlOsc::ProcessTriangle()
{
float out;
float fSlow1 = four_over_sr_ * (amp_ * freq_);
float fSlow3 = half_sr_ / freq_;
int iSlow4 = (int)fSlow3;
int iSlow5 = 1 + iSlow4;
float fSlow6 = iSlow5 - fSlow3;
float fSlow7 = quarter_sr_ / freq_;
float fSlow8 = sec_per_sample_ * freq_;
float fSlow9 = fSlow3 - iSlow4; //decimal portion
rec1_[0] = fmodf((fSlow8 + rec1_[1]), 1.0);
float fTemp0 = 2.0 * rec1_[0] - 1.0;
fTemp0 *= fTemp0; //mult faster than fpow for squaring?
vec1_[0] = fTemp0;
float fTemp1 = fSlow7 * (fTemp0 - vec1_[1]);
vec2_[iota_ & 4095] = fTemp1;
rec0_[0] = 0.0
- ((fSlow6 * vec2_[(iota_ - iSlow4) & 4095]
+ fSlow9 * vec2_[(iota_ - iSlow5) & 4095])
- (.999 * rec0_[1] + fTemp1));
out = (float)(fSlow1 * rec0_[0]);
rec1_[1] = rec1_[0];
rec0_[1] = rec0_[0];
vec1_[1] = vec1_[0];
iota_++;
return out;
}
float BlOsc::ProcessSaw()
{
float out;
//fSlow0 = Slider1 = freq
float fSlow1 = sampling_freq_ * (amp_ / freq_);
float fSlow2 = (two_over_sr_ * freq_);
float fSlow3 = (sampling_freq_ / freq_);
rec0_[0] = fmod((1.0 + rec0_[1]), fSlow3);
float fTemp0 = fSlow2 * rec0_[0] - 1.0;
fTemp0 *= fTemp0; //mult faster than fpow for squaring?
vec0_[0] = fTemp0;
vec1_[0] = 0.25;
out = (float)(fSlow1 * ((fTemp0 - vec0_[1]) * vec1_[1]));
rec0_[1] = rec0_[0];
vec0_[1] = vec0_[0];
vec1_[1] = vec1_[0];
return out;
}
void BlOsc::Reset()
{
iota_ = 0;
for(int i = 0; i < 2; i++)
{
rec0_[i] = rec1_[i] = vec0_[i] = vec1_[i] = 0.0;
}
for(int i = 0; i < 4096; i++)
{
vec2_[i] = 0.0;
}
}
float BlOsc::Process()
{
switch(mode_)
{
case WAVE_TRIANGLE: return ProcessTriangle();
case WAVE_SAW: return ProcessSaw();
case WAVE_SQUARE: return ProcessSquare();
default: break;
}
return 0.0;
}

@ -0,0 +1,76 @@
#pragma once
#ifndef DSY_BLOSC_H
#define DSY_BLOSC_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Band Limited Oscillator
Based on bltriangle, blsaw, blsquare from soundpipe
Original Author(s): Paul Batchelor, saw2 Faust by Julius Smith
Ported by Ben Sergentanis, May 2020
*/
class BlOsc
{
public:
BlOsc() {}
~BlOsc() {}
/** Bl Waveforms
*/
enum Waveforms
{
WAVE_TRIANGLE,
WAVE_SAW,
WAVE_SQUARE,
WAVE_OFF,
};
/** -Initialize oscillator.
-Defaults to: 440Hz, .5 amplitude, .5 pw, Triangle.
*/
void Init(float sample_rate);
/** - Get next floating point oscillator sample.
*/
float Process();
/** - Float freq: Set oscillator frequency in Hz.
*/
inline void SetFreq(float freq) { freq_ = freq; };
/** - Float amp: Set oscillator amplitude, 0 to 1.
*/
inline void SetAmp(float amp) { amp_ = amp; };
/** - Float pw: Set square osc pulsewidth, 0 to 1. (no thru 0 at the moment)
*/
inline void SetPw(float pw) { pw_ = 1 - pw; };
/** - uint8_t waveform: select between waveforms from enum above.
- i.e. SetWaveform(BL_WAVEFORM_SAW); to set waveform to saw
*/
inline void SetWaveform(uint8_t waveform) { mode_ = waveform; }
/** - reset the phase of the oscillator.
*/
void Reset();
private:
float rec0_[2], rec1_[2], vec0_[2], vec1_[2], vec2_[4096], freq_, amp_, pw_,
sampling_freq_, half_sr_, quarter_sr_, sec_per_sample_, two_over_sr_,
four_over_sr_;
uint8_t mode_;
int iota_;
float ProcessSquare();
float ProcessTriangle();
float ProcessSaw();
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,65 @@
#include "fm2.h"
using namespace daisysp;
void Fm2::Init(float samplerate)
{
//init oscillators
car_.Init(samplerate);
mod_.Init(samplerate);
//set some reasonable values
lfreq_ = 440.f;
lratio_ = 2.f;
SetFrequency(lfreq_);
SetRatio(lratio_);
car_.SetAmp(1.f);
mod_.SetAmp(1.f);
car_.SetWaveform(Oscillator::WAVE_SIN);
mod_.SetWaveform(Oscillator::WAVE_SIN);
idx_ = 1.f;
}
float Fm2::Process()
{
if(lratio_ != ratio_ || lfreq_ != freq_)
{
lratio_ = ratio_;
lfreq_ = freq_;
car_.SetFreq(lfreq_);
mod_.SetFreq(lfreq_ * lratio_);
}
float modval = mod_.Process();
car_.PhaseAdd(modval * idx_);
return car_.Process();
}
void Fm2::SetFrequency(float freq)
{
freq_ = fabsf(freq);
}
void Fm2::SetRatio(float ratio)
{
ratio_ = fabsf(ratio);
}
void Fm2::SetIndex(float index)
{
idx_ = index * kIdxScalar;
}
float Fm2::GetIndex()
{
return idx_ * kIdxScalarRecip;
}
void Fm2::Reset()
{
car_.Reset();
mod_.Reset();
}

@ -0,0 +1,65 @@
#pragma once
#ifndef DSY_FM2_H
#define DSY_FM2_H
#include <stdint.h>
#include "Synthesis/oscillator.h"
#ifdef __cplusplus
namespace daisysp
{
/** Simple 2 operator FM synth voice.
Date: November, 2020
Author: Ben Sergentanis
*/
class Fm2
{
public:
Fm2() {}
~Fm2() {}
/** Initializes the FM2 module.
\param samplerate - The sample rate of the audio engine being run.
*/
void Init(float samplerate);
/** Returns the next sample
*/
float Process();
/** Carrier freq. setter
\param freq Carrier frequency in Hz
*/
void SetFrequency(float freq);
/** Set modulator freq. relative to carrier
\param ratio New modulator freq = carrier freq. * ratio
*/
void SetRatio(float ratio);
/** Index setter
\param FM depth, 5 = 2PI rads
*/
void SetIndex(float index);
/** Returns the current FM index. */
float GetIndex();
/** Resets both oscillators */
void Reset();
private:
static constexpr float kIdxScalar = 0.2f;
static constexpr float kIdxScalarRecip = 1.f / kIdxScalar;
Oscillator mod_, car_;
float idx_;
float freq_, lfreq_, ratio_, lratio_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,96 @@
#include "../utility/dsp.h"
#include "formantosc.h"
#include <math.h>
using namespace daisysp;
void FormantOscillator::Init(float sample_rate)
{
carrier_phase_ = 0.0f;
formant_phase_ = 0.0f;
next_sample_ = 0.0f;
carrier_frequency_ = 0.0f;
formant_frequency_ = 100.f;
phase_shift_ = 0.0f;
sample_rate_ = sample_rate;
}
float FormantOscillator::Process()
{
float this_sample = next_sample_;
float next_sample = 0.0f;
carrier_phase_ += carrier_frequency_;
if(carrier_phase_ >= 1.0f)
{
carrier_phase_ -= 1.0f;
float reset_time = carrier_phase_ / carrier_frequency_;
float formant_phase_at_reset
= formant_phase_ + (1.0f - reset_time) * formant_frequency_;
float before = Sine(formant_phase_at_reset + phase_shift_
+ (ps_inc_ * (1.0f - reset_time)));
float after = Sine(0.0f + phase_shift_ + ps_inc_);
float discontinuity = after - before;
this_sample += discontinuity * ThisBlepSample(reset_time);
next_sample += discontinuity * NextBlepSample(reset_time);
formant_phase_ = reset_time * formant_frequency_;
}
else
{
formant_phase_ += formant_frequency_;
if(formant_phase_ >= 1.0f)
{
formant_phase_ -= 1.0f;
}
}
phase_shift_ += ps_inc_;
ps_inc_ = 0.f;
next_sample += Sine(formant_phase_ + phase_shift_);
next_sample_ = next_sample;
return this_sample;
}
void FormantOscillator::SetFormantFreq(float freq)
{
//convert from Hz to phase_inc / sample
formant_frequency_ = freq / sample_rate_;
formant_frequency_ = formant_frequency_ >= .25f ? .25f : formant_frequency_;
formant_frequency_
= formant_frequency_ <= -.25f ? -.25f : formant_frequency_;
}
void FormantOscillator::SetCarrierFreq(float freq)
{
//convert from Hz to phase_inc / sample
carrier_frequency_ = freq / sample_rate_;
carrier_frequency_ = carrier_frequency_ >= .25f ? .25f : carrier_frequency_;
carrier_frequency_
= carrier_frequency_ <= -.25f ? -.25f : carrier_frequency_;
}
void FormantOscillator::SetPhaseShift(float ps)
{
ps_inc_ = ps - phase_shift_;
}
inline float FormantOscillator::Sine(float phase)
{
return sinf(phase * TWOPI_F);
}
inline float FormantOscillator::ThisBlepSample(float t)
{
return 0.5f * t * t;
}
inline float FormantOscillator::NextBlepSample(float t)
{
t = 1.0f - t;
return -0.5f * t * t;
}

@ -0,0 +1,73 @@
#pragma once
#ifndef DSY_FORMANTOSCILLATOR_H
#define DSY_FORMANTOSCILLATOR_H
#include <stdint.h>
#ifdef __cplusplus
/** @file formantosc.h */
namespace daisysp
{
/**
@brief Formant Oscillator Module.
@author Ben Sergentanis
@date Dec 2020
Sinewave with aliasing-free phase reset. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/formant_oscillator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class FormantOscillator
{
public:
FormantOscillator() {}
~FormantOscillator() {}
/** Initializes the FormantOscillator module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);
/** Get the next sample
*/
float Process();
/** Set the formant frequency.
\param freq Frequency in Hz
*/
void SetFormantFreq(float freq);
/** Set the carrier frequency. This is the "main" frequency.
\param freq Frequency in Hz
*/
void SetCarrierFreq(float freq);
/** Set the amount of phase shift
\param ps Typically 0-1. Works with other values though, including negative.
*/
void SetPhaseShift(float ps);
private:
inline float Sine(float phase);
inline float ThisBlepSample(float t);
inline float NextBlepSample(float t);
// Oscillator state.
float carrier_phase_;
float formant_phase_;
float next_sample_;
// For interpolation of parameters.
float carrier_frequency_;
float formant_frequency_;
float phase_shift_;
float ps_inc_;
float sample_rate_;
//DISALLOW_COPY_AND_ASSIGN(FormantOscillator);
};
} //namespace daisysp
#endif
#endif

@ -0,0 +1,166 @@
#pragma once
#ifndef DSY_HARMONIC_H
#define DSY_HARMONIC_H
#include <stdint.h>
#include "Utility/dsp.h"
#ifdef __cplusplus
/** @file harmonic_osc.h */
namespace daisysp
{
/**
@brief Harmonic Oscillator Module based on Chebyshev polynomials.
@author Ben Sergentanis
@date Dec 2020
Harmonic Oscillator Module based on Chebyshev polynomials \n
Works well for a small number of harmonics. For the higher order harmonics. \n
We need to reinitialize the recurrence by computing two high harmonics. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/harmonic_oscillator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
template <int num_harmonics = 16>
class HarmonicOscillator
{
public:
HarmonicOscillator() {}
~HarmonicOscillator() {}
/** Initialize harmonic oscillator
\param sample_rate Audio engine samplerate
*/
void Init(float sample_rate)
{
sample_rate_ = sample_rate;
phase_ = 0.0f;
for(int i = 0; i < num_harmonics; ++i)
{
amplitude_[i] = 0.0f;
newamplitude_[i] = 0.f;
}
amplitude_[0] = 1.f;
newamplitude_[0] = 1.f;
SetFirstHarmIdx(1);
SetFreq(440.f);
recalc_ = false;
}
/** Get the next floating point sample */
float Process()
{
if(recalc_)
{
recalc_ = false;
for(int i = 0; i < num_harmonics; ++i)
{
float f = frequency_
* static_cast<float>(first_harmonic_index_ + i);
if(f >= 0.5f)
{
f = 0.5f;
}
amplitude_[i] = newamplitude_[i] * (1.0f - f * 2.0f);
}
}
phase_ += frequency_;
if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
}
const float two_x = 2.0f * sinf(phase_ * TWOPI_F);
float previous, current;
if(first_harmonic_index_ == 1)
{
previous = 1.0f;
current = two_x * 0.5f;
}
else
{
const float k = first_harmonic_index_;
previous = sinf((phase_ * (k - 1.0f) + 0.25f) * TWOPI_F);
current = sinf((phase_ * k) * TWOPI_F);
}
float sum = 0.0f;
for(int i = 0; i < num_harmonics; ++i)
{
sum += amplitude_[i] * current;
float temp = current;
current = two_x * current - previous;
previous = temp;
}
return sum;
}
/** Set the main frequency
\param freq Freq to be set in Hz.
*/
void SetFreq(float freq)
{
//convert from Hz to phase_inc / sample
freq = freq / sample_rate_;
freq = freq >= .5f ? .5f : freq;
freq = freq <= -.5f ? -.5f : freq;
recalc_ = cmp(freq, frequency_) || recalc_;
frequency_ = freq;
}
/** Offset the set of harmonics. Passing in 3 means "harmonic 0" is the 3rd harm., 1 is the 4th, etc.
\param idx Default behavior is 1. Values < 0 default to 1.
*/
void SetFirstHarmIdx(int idx)
{
idx = idx < 1 ? 1 : idx;
recalc_ = cmp(idx, first_harmonic_index_) || recalc_;
first_harmonic_index_ = idx;
}
/** Set the amplitudes of each harmonic of the root.
\param amplitudes Amplitudes to set. Sum of all amplitudes must be < 1. The array referenced must be at least as large as num_harmonics.
*/
void SetAmplitudes(const float* amplitudes)
{
for(int i = 0; i < num_harmonics; i++)
{
recalc_ = cmp(newamplitude_[i], amplitudes[i]) || recalc_;
newamplitude_[i] = amplitudes[i];
}
}
/** Sets one amplitude. Does nothing if idx out of range.
\param amp Amplitude to set
\param idx Which harmonic to set.
*/
void SetSingleAmp(const float amp, int idx)
{
if(idx < 0 || idx >= num_harmonics)
{
return;
}
recalc_ = cmp(amplitude_[idx], amp) || recalc_;
newamplitude_[idx] = amp;
}
private:
bool cmp(float a, float b) { return fabsf(a - b) > .000001f; }
float sample_rate_;
float phase_;
float frequency_;
float amplitude_[num_harmonics];
float newamplitude_[num_harmonics];
bool recalc_;
int first_harmonic_index_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,86 @@
#include "../utility/dsp.h"
#include "oscillator.h"
using namespace daisysp;
static inline float Polyblep(float phase_inc, float t);
constexpr float TWO_PI_RECIP = 1.0f / TWOPI_F;
float Oscillator::Process()
{
float out, t;
switch(waveform_)
{
case WAVE_SIN: out = sinf(phase_); break;
case WAVE_TRI:
t = -1.0f + (2.0f * phase_ * TWO_PI_RECIP);
out = 2.0f * (fabsf(t) - 0.5f);
break;
case WAVE_SAW:
out = -1.0f * (((phase_ * TWO_PI_RECIP * 2.0f)) - 1.0f);
break;
case WAVE_RAMP: out = ((phase_ * TWO_PI_RECIP * 2.0f)) - 1.0f; break;
case WAVE_SQUARE: out = phase_ < PI_F ? (1.0f) : -1.0f; break;
case WAVE_POLYBLEP_TRI:
t = phase_ * TWO_PI_RECIP;
out = phase_ < PI_F ? 1.0f : -1.0f;
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fmodf(t + 0.5f, 1.0f));
// Leaky Integrator:
// y[n] = A + x[n] + (1 - A) * y[n-1]
out = phase_inc_ * out + (1.0f - phase_inc_) * last_out_;
last_out_ = out;
break;
case WAVE_POLYBLEP_SAW:
t = phase_ * TWO_PI_RECIP;
out = (2.0f * t) - 1.0f;
out -= Polyblep(phase_inc_, t);
out *= -1.0f;
break;
case WAVE_POLYBLEP_SQUARE:
t = phase_ * TWO_PI_RECIP;
out = phase_ < PI_F ? 1.0f : -1.0f;
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fmodf(t + 0.5f, 1.0f));
out *= 0.707f; // ?
break;
default: out = 0.0f; break;
}
phase_ += phase_inc_;
if(phase_ > TWOPI_F)
{
phase_ -= TWOPI_F;
eoc_ = true;
}
else
{
eoc_ = false;
}
eor_ = (phase_ - phase_inc_ < PI_F && phase_ >= PI_F);
return out * amp_;
}
float Oscillator::CalcPhaseInc(float f)
{
return (TWOPI_F * f) * sr_recip_;
}
static float Polyblep(float phase_inc, float t)
{
float dt = phase_inc * TWO_PI_RECIP;
if(t < dt)
{
t /= dt;
return t + t - t * t - 1.0f;
}
else if(t > 1.0f - dt)
{
t = (t - 1.0f) / dt;
return t * t + t + t + 1.0f;
}
else
{
return 0.0f;
}
}

@ -0,0 +1,113 @@
#pragma once
#ifndef DSY_OSCILLATOR_H
#define DSY_OSCILLATOR_H
#include <stdint.h>
#include "Utility/dsp.h"
#ifdef __cplusplus
namespace daisysp
{
/** Synthesis of several waveforms, including polyBLEP bandlimited waveforms.
*/
class Oscillator
{
public:
Oscillator() {}
~Oscillator() {}
/** Choices for output waveforms, POLYBLEP are appropriately labeled. Others are naive forms.
*/
enum
{
WAVE_SIN,
WAVE_TRI,
WAVE_SAW,
WAVE_RAMP,
WAVE_SQUARE,
WAVE_POLYBLEP_TRI,
WAVE_POLYBLEP_SAW,
WAVE_POLYBLEP_SQUARE,
WAVE_LAST,
};
/** Initializes the Oscillator
\param sample_rate - sample rate of the audio engine being run, and the frequency that the Process function will be called.
Defaults:
- freq_ = 100 Hz
- amp_ = 0.5
- waveform_ = sine wave.
*/
void Init(float sample_rate)
{
sr_ = sample_rate;
sr_recip_ = 1.0f / sample_rate;
freq_ = 100.0f;
amp_ = 0.5f;
phase_ = 0.0f;
phase_inc_ = CalcPhaseInc(freq_);
waveform_ = WAVE_SIN;
eoc_ = true;
eor_ = true;
}
/** Changes the frequency of the Oscillator, and recalculates phase increment.
*/
inline void SetFreq(const float f)
{
freq_ = f;
phase_inc_ = CalcPhaseInc(f);
}
/** Sets the amplitude of the waveform.
*/
inline void SetAmp(const float a) { amp_ = a; }
/** Sets the waveform to be synthesized by the Process() function.
*/
inline void SetWaveform(const uint8_t wf)
{
waveform_ = wf < WAVE_LAST ? wf : WAVE_SIN;
}
/** Returns true if cycle is at end of rise. Set during call to Process.
*/
inline bool IsEOR() { return eor_; }
/** Returns true if cycle is at end of cycle. Set during call to Process.
*/
inline bool IsEOC() { return eoc_; }
/** Returns true if cycle rising.
*/
inline bool IsRising() { return phase_ < PI_F; }
/** Returns true if cycle falling.
*/
inline bool IsFalling() { return phase_ >= PI_F; }
/** Processes the waveform to be generated, returning one sample. This should be called once per sample period.
*/
float Process();
/** Adds a value 0.0-1.0 (mapped to 0.0-TWO_PI) to the current phase. Useful for PM and "FM" synthesis.
*/
void PhaseAdd(float _phase) { phase_ += (_phase * TWOPI_F); }
/** Resets the phase to the input argument. If no argumeNt is present, it will reset phase to 0.0;
*/
void Reset(float _phase = 0.0f) { phase_ = _phase; }
private:
float CalcPhaseInc(float f);
uint8_t waveform_;
float amp_, freq_;
float sr_, sr_recip_, phase_, phase_inc_;
float last_out_, last_freq_;
bool eor_, eoc_;
};
} // namespace daisysp
#endif
#endif

@ -0,0 +1,147 @@
#include "../utility/dsp.h"
#include "oscillatorbank.h"
#include <math.h>
using namespace daisysp;
void OscillatorBank::Init(float sample_rate)
{
sample_rate_ = sample_rate;
phase_ = 0.0f;
next_sample_ = 0.0f;
segment_ = 0.0f;
frequency_ = 0.f;
saw_8_gain_ = 0.0f;
saw_4_gain_ = 0.0f;
saw_2_gain_ = 0.0f;
saw_1_gain_ = 0.0f;
recalc_ = recalc_gain_ = true;
SetGain(1.f);
for(int i = 0; i < 7; i++)
{
registration_[i] = 0.f;
unshifted_registration_[i] = 0.f;
}
SetSingleAmp(1.f, 0);
SetFreq(440.f);
}
float OscillatorBank::Process()
{
if(recalc_)
{
recalc_ = false;
frequency_ *= 8.0f;
// Deal with very high frequencies by shifting everything 1 or 2 octave
// down: Instead of playing the 1st harmonic of a 8kHz wave, we play the
// second harmonic of a 4kHz wave.
size_t shift = 0;
while(frequency_ > 0.5f)
{
shift += 2;
frequency_ *= 0.5f;
}
for(int i = 0; i < 7; i++)
{
registration_[i] = 0.f;
}
for(size_t i = 0; i < 7 - shift; i++)
{
registration_[i + shift] = unshifted_registration_[i];
}
}
if(recalc_gain_ || recalc_)
{
saw_8_gain_ = (registration_[0] + 2.0f * registration_[1]) * gain_;
saw_4_gain_
= (registration_[2] - registration_[1] + 2.0f * registration_[3])
* gain_;
saw_2_gain_
= (registration_[4] - registration_[3] + 2.0f * registration_[5])
* gain_;
saw_1_gain_ = (registration_[6] - registration_[5]) * gain_;
}
float this_sample_ = next_sample_;
next_sample_ = 0.0f;
phase_ += frequency_;
int next_segment_ = static_cast<int>(phase_);
if(next_segment_ != segment_)
{
float discontinuity = 0.0f;
if(next_segment_ == 8)
{
phase_ -= 8.0f;
next_segment_ -= 8;
discontinuity -= saw_8_gain_;
}
if((next_segment_ & 3) == 0)
{
discontinuity -= saw_4_gain_;
}
if((next_segment_ & 1) == 0)
{
discontinuity -= saw_2_gain_;
}
discontinuity -= saw_1_gain_;
if(discontinuity != 0.0f)
{
float fraction = phase_ - static_cast<float>(next_segment_);
float t = fraction / frequency_;
this_sample_ += ThisBlepSample(t) * discontinuity;
next_sample_ += NextBlepSample(t) * discontinuity;
}
}
segment_ = next_segment_;
next_sample_ += (phase_ - 4.0f) * saw_8_gain_ * 0.125f;
next_sample_ += (phase_ - float(segment_ & 4) - 2.0f) * saw_4_gain_ * 0.25f;
next_sample_ += (phase_ - float(segment_ & 6) - 1.0f) * saw_2_gain_ * 0.5f;
next_sample_ += (phase_ - float(segment_ & 7) - 0.5f) * saw_1_gain_;
return 2.0f * this_sample_;
}
void OscillatorBank::SetFreq(float freq)
{
freq = freq / sample_rate_;
freq = freq > 0.5f ? 0.5f : freq;
recalc_ = cmp(freq, frequency_) || recalc_;
frequency_ = freq;
}
void OscillatorBank::SetAmplitudes(const float* amplitudes)
{
for(int i = 0; i < 7; i++)
{
recalc_ = cmp(unshifted_registration_[i], amplitudes[i]) || recalc_;
unshifted_registration_[i] = amplitudes[i];
}
}
void OscillatorBank::SetSingleAmp(float amp, int idx)
{
if(idx < 0 || idx > 6)
{
return;
}
recalc_ = cmp(unshifted_registration_[idx], amp) || recalc_;
unshifted_registration_[idx] = amp;
}
void OscillatorBank::SetGain(float gain)
{
gain = gain > 1.f ? 1.f : gain;
gain = gain < 0.f ? 0.f : gain;
recalc_gain_ = cmp(gain, gain_) || recalc_gain_;
gain_ = gain;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save