Compare commits

..

3 Commits

Author SHA1 Message Date
Tom-on 6f70cf0a3d Insane changes 2026-04-24 14:52:45 +02:00
Tom-on d687c2e24c Merge branch 'master' of https://git.pupes.org/PoliEcho/sint-gauntlet 2026-04-24 14:49:24 +02:00
Tom-on dd63b3aed4 Added DaisySP 2026-04-24 14:46:05 +02:00
100 changed files with 10633 additions and 4 deletions
+2 -1
View File
@@ -25,7 +25,7 @@ pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(sint-gauntlet sint-gauntlet.c vco.c )
add_executable(sint-gauntlet main.cc vco.c mux.c)
pico_set_program_name(sint-gauntlet "sint-gauntlet")
pico_set_program_version(sint-gauntlet "0.1")
@@ -39,6 +39,7 @@ target_link_libraries(sint-gauntlet
# Add the standard include files to the build
target_include_directories(sint-gauntlet PRIVATE
${CMAKE_CURRENT_LIST_DIR}/daisysp
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)
+167
View File
@@ -0,0 +1,167 @@
#include <algorithm>
#include <limits>
#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_));
}
if(c_inc_ >= 0.0f)
{
c_inc_ = std::max(c_inc_, std::numeric_limits<float>::epsilon());
}
else
{
c_inc_ = std::min(c_inc_, -std::numeric_limits<float>::epsilon());
}
// 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_;
}
+100
View File
@@ -0,0 +1,100 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+136
View File
@@ -0,0 +1,136 @@
#include "adsr.h"
#include <math.h>
using namespace daisysp;
void Adsr::Init(float sample_rate, int blockSize)
{
sample_rate_ = sample_rate / blockSize;
attackShape_ = -1.f;
attackTarget_ = 0.0f;
attackTime_ = -1.f;
decayTime_ = -1.f;
releaseTime_ = -1.f;
sus_level_ = 0.7f;
x_ = 0.0f;
gate_ = false;
mode_ = ADSR_SEG_IDLE;
SetTime(ADSR_SEG_ATTACK, 0.1f);
SetTime(ADSR_SEG_DECAY, 0.1f);
SetTime(ADSR_SEG_RELEASE, 0.1f);
}
void Adsr::Retrigger(bool hard)
{
mode_ = ADSR_SEG_ATTACK;
if(hard)
x_ = 0.f;
}
void Adsr::SetTime(int seg, float time)
{
switch(seg)
{
case ADSR_SEG_ATTACK: SetAttackTime(time, 0.0f); break;
case ADSR_SEG_DECAY:
{
SetTimeConstant(time, decayTime_, decayD0_);
}
break;
case ADSR_SEG_RELEASE:
{
SetTimeConstant(time, releaseTime_, releaseD0_);
}
break;
default: return;
}
}
void Adsr::SetAttackTime(float timeInS, float shape)
{
if((timeInS != attackTime_) || (shape != attackShape_))
{
attackTime_ = timeInS;
attackShape_ = shape;
if(timeInS > 0.f)
{
float x = shape;
float target = 9.f * powf(x, 10.f) + 0.3f * x + 1.01f;
attackTarget_ = target;
float logTarget = logf(1.f - (1.f / target)); // -1 for decay
attackD0_ = 1.f - expf(logTarget / (timeInS * sample_rate_));
}
else
attackD0_ = 1.f; // instant change
}
}
void Adsr::SetDecayTime(float timeInS)
{
SetTimeConstant(timeInS, decayTime_, decayD0_);
}
void Adsr::SetReleaseTime(float timeInS)
{
SetTimeConstant(timeInS, releaseTime_, releaseD0_);
}
void Adsr::SetTimeConstant(float timeInS, float& time, float& coeff)
{
if(timeInS != time)
{
time = timeInS;
if(time > 0.f)
{
const float target = logf(1. / M_E);
coeff = 1.f - expf(target / (time * sample_rate_));
}
else
coeff = 1.f; // instant change
}
}
float Adsr::Process(bool gate)
{
float out = 0.0f;
if(gate && !gate_) // rising edge
mode_ = ADSR_SEG_ATTACK;
else if(!gate && gate_) // falling edge
mode_ = ADSR_SEG_RELEASE;
gate_ = gate;
float D0(attackD0_);
if(mode_ == ADSR_SEG_DECAY)
D0 = decayD0_;
else if(mode_ == ADSR_SEG_RELEASE)
D0 = releaseD0_;
float target = mode_ == ADSR_SEG_DECAY ? sus_level_ : -0.01f;
switch(mode_)
{
case ADSR_SEG_IDLE: out = 0.0f; break;
case ADSR_SEG_ATTACK:
x_ += D0 * (attackTarget_ - x_);
out = x_;
if(out > 1.f)
{
x_ = out = 1.f;
mode_ = ADSR_SEG_DECAY;
}
break;
case ADSR_SEG_DECAY:
case ADSR_SEG_RELEASE:
x_ += D0 * (target - x_);
out = x_;
if(out < 0.0f)
{
x_ = out = 0.f;
mode_ = ADSR_SEG_IDLE;
}
default: break;
}
return out;
}
+106
View File
@@ -0,0 +1,106 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
- RELEASE = Fourth segment of envelop where phase moves from SUSTAIN to 0
*/
enum
{
ADSR_SEG_IDLE = 0,
ADSR_SEG_ATTACK = 1,
ADSR_SEG_DECAY = 2,
ADSR_SEG_RELEASE = 4
};
/** adsr envelope module
Original author(s) : Paul Batchelor
Ported from Soundpipe by Ben Sergentanis, May 2020
Remake by Steffan DIedrichsen, May 2021
*/
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, int blockSize = 1);
/**
\function Retrigger forces the envelope back to attack phase
\param hard resets the history to zero, results in a click.
*/
void Retrigger(bool hard);
/** 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
*/
void SetTime(int seg, float time);
void SetAttackTime(float timeInS, float shape = 0.0f);
void SetDecayTime(float timeInS);
void SetReleaseTime(float timeInS);
private:
void SetTimeConstant(float timeInS, float& time, float& coeff);
public:
/** Sustain level
\param sus_level - sets sustain level, 0...1.0
*/
inline void SetSustainLevel(float sus_level)
{
sus_level = (sus_level <= 0.f) ? -0.01f // forces envelope into idle
: (sus_level > 1.f) ? 1.f : sus_level;
sus_level_ = 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_level_{0.f};
float x_{0.f};
float attackShape_{-1.f};
float attackTarget_{0.0f};
float attackTime_{-1.0f};
float decayTime_{-1.0f};
float releaseTime_{-1.0f};
float attackD0_{0.f};
float decayD0_{0.f};
float releaseD0_{0.f};
int sample_rate_;
uint8_t mode_{ADSR_SEG_IDLE};
bool gate_{false};
};
} // namespace daisysp
#endif
#endif
+27
View File
@@ -0,0 +1,27 @@
#include <math.h>
#include "phasor.h"
#include "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;
}
+71
View File
@@ -0,0 +1,71 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+192
View File
@@ -0,0 +1,192 @@
#include "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;
}
+113
View File
@@ -0,0 +1,113 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+219
View File
@@ -0,0 +1,219 @@
#include "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);
}
}
+106
View File
@@ -0,0 +1,106 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+96
View File
@@ -0,0 +1,96 @@
#include "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;
}
+277
View File
@@ -0,0 +1,277 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+231
View File
@@ -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;
}
+187
View File
@@ -0,0 +1,187 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+201
View File
@@ -0,0 +1,201 @@
#include "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);
}
+105
View File
@@ -0,0 +1,105 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+37
View File
@@ -0,0 +1,37 @@
#include <math.h>
#include "crossfade.h"
#include "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;
}
}
+86
View File
@@ -0,0 +1,86 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+29
View File
@@ -0,0 +1,29 @@
#include "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
+41
View File
@@ -0,0 +1,41 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+65
View File
@@ -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;
}
+60
View File
@@ -0,0 +1,60 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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, , 0...1.0
*/
inline void SetWah(float wah) { wah_ = wah; }
/** sets mix amount
\param drywet : set effect dry/wet, 0...100.0
*/
inline void SetDryWet(float drywet) { wet_dry_ = drywet; }
/** sets wah level
\param level : set wah level, 0...1.0
*/
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
+187
View File
@@ -0,0 +1,187 @@
#include "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);
}
+190
View File
@@ -0,0 +1,190 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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, 0 to 50 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
+46
View File
@@ -0,0 +1,46 @@
#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;
smooth_crushing_ = false;
bit_overflow_ = 1.0f;
}
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
if(smooth_crushing_)
{
temp = (int32_t)(downsampled_ * 65536.0f * bit_overflow_);
temp >>= bits_to_crush_ + 1; // shift off
temp <<= bits_to_crush_ + 1; // move back with zeros
bitcrushed_ = (float)temp / (65536.0f * bit_overflow_);
}
else
{
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_;
}
+94
View File
@@ -0,0 +1,94 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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: 0...1.0
*/
inline void SetBitcrushFactor(float bitcrush_factor)
{
bitcrush_factor_ = bitcrush_factor;
bits_to_crush_ = (uint32_t)(bitcrush_factor * kMaxBitsToCrush);
bit_overflow_
= 2.0f - (bitcrush_factor * 16.0f) + (float)(bits_to_crush_);
}
/** Sets the exact number of bits to crush and disables smooth crushing
0-16 bits
*/
inline void SetBitsToCrush(const uint8_t &bits)
{
bits_to_crush_ = bits <= kMaxBitsToCrush ? bits : kMaxBitsToCrush;
smooth_crushing_ = false;
}
/** Sets the smooth crushing on or off
true/false
*/
inline void SetSmoothCrushing(bool smooth_crushing)
{
smooth_crushing_ = smooth_crushing;
}
/** Returns current setting of smooth crushing
*/
inline bool GetSmoothCrushing() { return smooth_crushing_; }
/** Returns current setting of downsample
*/
inline float GetDownsampleFactor() { return downsample_factor_; }
/** Returns current setting of bitcrush
*/
inline float GetBitcrushFactor() { return bitcrush_factor_; }
/** Returns current bitcrush setting in bits
*/
inline int GetBitsToCrush() { return bits_to_crush_; }
private:
const uint8_t kMaxBitsToCrush = 16;
float downsample_factor_, bitcrush_factor_;
uint32_t bits_to_crush_;
float downsampled_, bitcrushed_;
uint32_t inc_, threshold_;
bool smooth_crushing_;
float bit_overflow_;
};
} // namespace daisysp
#endif
#endif
+83
View File
@@ -0,0 +1,83 @@
#include "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_;
}
+83
View File
@@ -0,0 +1,83 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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, .1 to 7 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
+32
View File
@@ -0,0 +1,32 @@
#include "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
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+138
View File
@@ -0,0 +1,138 @@
#include "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);
}
}
+139
View File
@@ -0,0 +1,139 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+219
View File
@@ -0,0 +1,219 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+43
View File
@@ -0,0 +1,43 @@
#include "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);
}
+56
View File
@@ -0,0 +1,56 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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 frequency 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
+36
View File
@@ -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;
}
+68
View File
@@ -0,0 +1,68 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+20
View File
@@ -0,0 +1,20 @@
#include "wavefolder.h"
#include <math.h>
using namespace daisysp;
void Wavefolder::Init()
{
gain_ = 1.0f;
offset_ = 0.0f;
}
float Wavefolder::Process(float in)
{
float ft, sgn;
in += offset_;
in *= gain_;
ft = floorf((in + 1.0f) * 0.5f);
sgn = static_cast<int>(ft) % 2 == 0 ? 1.0f : -1.0f;
return sgn * (in - 2.0f * ft);
}
+53
View File
@@ -0,0 +1,53 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Nick Donaldson
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_WAVEFOFOLDER_H
#define DSY_WAVEFOFOLDER_H
#include <stdint.h>
#include "Utility/dcblock.h"
#ifdef __cplusplus
namespace daisysp
{
/** Basic wavefolder module.
Amplitude of input determines level of folding.
Amplitudes of magnitude > 1.0 will start to fold.
Original author(s) : Nick Donaldson
Year : 2022
*/
class Wavefolder
{
public:
Wavefolder() {}
~Wavefolder() {}
/** Initializes the wavefolder module.
*/
void Init();
/** applies wavefolding to input
*/
float Process(float in);
/**
\param gain Set input gain.
Supports negative values for thru-zero
*/
inline void SetGain(float gain) { gain_ = gain; }
/**
\param offset Offset odded to input (pre-gain) for asymmetrical folding.
*/
inline void SetOffset(float offset) { offset_ = offset; }
private:
float gain_, offset_;
};
} // namespace daisysp
#endif
#endif
+351
View File
@@ -0,0 +1,351 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Alexander Petrov-Savchenko
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+180
View File
@@ -0,0 +1,180 @@
/* Ported from Audio Library for Teensy, Ladder Filter
* Copyright (c) 2021, Richard van Hoesel
* Copyright (c) 2024, Infrasonic Audio LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//-----------------------------------------------------------
// Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Richard van Hoesel, v. 1.03, Feb. 14 2021
// v1.7 (Infrasonic/Daisy) add configurable filter mode
// v1.6 (Infrasonic/Daisy) removes polyphase FIR, uses 4x linear
// oversampling for performance reasons
// v1.5 adds polyphase FIR or Linear interpolation
// v1.4 FC extended to 18.7kHz, max res to 1.8, 4x oversampling,
// and a minor Q-tuning adjustment
// v.1.03 adds oversampling, extended resonance,
// and exposes parameters input_drive and passband_gain
// v.1.02 now includes both cutoff and resonance "CV" modulation inputs
// please retain this header if you use this code.
//-----------------------------------------------------------
#include "ladder.h"
#include "Utility/dsp.h"
using namespace daisysp;
static inline float fast_tanh(float x)
{
if(x > 3.0f)
return 1.0f;
if(x < -3.0f)
return -1.0f;
float x2 = x * x;
return x * (27.0f + x2) / (27.0f + 9.0f * x2);
}
void LadderFilter::Init(float sample_rate)
{
sample_rate_ = sample_rate;
sr_int_recip_ = 1.0f / (sample_rate * kInterpolation);
alpha_ = 1.0f;
K_ = 1.0f;
Fbase_ = 1000.0f;
Qadjust_ = 1.0f;
oldinput_ = 0.f;
mode_ = FilterMode::LP24;
SetPassbandGain(0.5f);
SetInputDrive(0.5f);
SetFreq(5000.f);
SetRes(0.2f);
}
float LadderFilter::Process(float in)
{
float input = in * drive_scaled_;
float total = 0.0f;
float interp = 0.0f;
for(size_t os = 0; os < kInterpolation; os++)
{
float in_interp = (interp * oldinput_ + (1.0f - interp) * input);
float u = in_interp - (z1_[3] - pbg_ * in_interp) * K_ * Qadjust_;
u = fast_tanh(u);
float stage1 = LPF(u, 0);
float stage2 = LPF(stage1, 1);
float stage3 = LPF(stage2, 2);
float stage4 = LPF(stage3, 3);
total += weightedSumForCurrentMode({u, stage1, stage2, stage3, stage4})
* kInterpolationRecip;
interp += kInterpolationRecip;
}
oldinput_ = input;
return total;
}
__attribute__((optimize("unroll-loops"))) void
LadderFilter::ProcessBlock(float* buf, size_t size)
{
for(size_t i = 0; i < size; i++)
{
buf[i] = Process(buf[i]);
}
}
void LadderFilter::SetFreq(float freq)
{
Fbase_ = freq;
compute_coeffs(freq);
}
void LadderFilter::SetRes(float res)
{
// maps resonance = 0->1 to K = 0 -> 4
res = daisysp::fclamp(res, 0.0f, kMaxResonance);
K_ = 4.0f * res;
}
void LadderFilter::SetPassbandGain(float pbg)
{
pbg_ = daisysp::fclamp(pbg, 0.0f, 0.5f);
SetInputDrive(drive_);
}
void LadderFilter::SetInputDrive(float odrv)
{
drive_ = daisysp::fmax(odrv, 0.0f);
if(drive_ > 1.0f)
{
drive_ = fmin(drive_, 4.0f);
// max is 4 when pbg = 0, and 2.5 when pbg is 0.5
drive_scaled_ = 1.0f + (drive_ - 1.0f) * (1.0f - pbg_);
}
else
{
drive_scaled_ = drive_;
}
}
float LadderFilter::LPF(float s, int i)
{
// (1.0 / 1.3) (0.3 / 1.3)
float ft = s * 0.76923077f + 0.23076923f * z0_[i] - z1_[i];
ft = ft * alpha_ + z1_[i];
z1_[i] = ft;
z0_[i] = s;
return ft;
}
void LadderFilter::compute_coeffs(float freq)
{
freq = daisysp::fclamp(freq, 5.0f, sample_rate_ * 0.425f);
float wc = freq * 2.0f * PI_F * sr_int_recip_;
float wc2 = wc * wc;
alpha_ = 0.9892f * wc - 0.4324f * wc2 + 0.1381f * wc * wc2
- 0.0202f * wc2 * wc2;
//Qadjust = 1.0029f + 0.0526f * wc - 0.0926 * wc2 + 0.0218* wc * wc2;
Qadjust_ = 1.006f + 0.0536f * wc - 0.095f * wc2 - 0.05f * wc2 * wc2;
// revised hfQ (rvh - feb 14 2021)
}
float LadderFilter::weightedSumForCurrentMode(
const std::array<float, 5>& stage_outs)
{
// Weighted filter stage mixing to achieve selected response
// as described in "Oscillator and Filter Algorithms for Virtual Analog Synthesis"
// Välimäki and Huovilainen, Computer Music Journal, vol 60, 2006
switch(mode_)
{
case FilterMode::LP24: return stage_outs[4];
case FilterMode::LP12: return stage_outs[2];
case FilterMode::BP24:
return (stage_outs[2] + stage_outs[4]) * 4.0f
- stage_outs[3] * 8.0f;
case FilterMode::BP12: return (stage_outs[1] - stage_outs[2]) * 2.0f;
case FilterMode::HP24:
return stage_outs[0] + stage_outs[4]
- ((stage_outs[1] + stage_outs[3]) * 4.0f)
+ stage_outs[2] * 6.0f;
case FilterMode::HP12:
return stage_outs[0] + stage_outs[2] - stage_outs[1] * 2.0f;
default: return 0.0f;
}
}
+144
View File
@@ -0,0 +1,144 @@
/* Ported from Audio Library for Teensy, Ladder Filter
* Copyright (c) 2021, Richard van Hoesel
* Copyright (c) 2024, Infrasonic Audio LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//-----------------------------------------------------------
// Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Richard van Hoesel, v. 1.03, Feb. 14 2021
// v1.7 (Infrasonic/Daisy) add configurable filter mode
// v1.6 (Infrasonic/Daisy) removes polyphase FIR, uses 4x linear
// oversampling for performance reasons
// v1.5 adds polyphase FIR or Linear interpolation
// v1.4 FC extended to 18.7kHz, max res to 1.8, 4x oversampling,
// and a minor Q-tuning adjustment
// v.1.03 adds oversampling, extended resonance,
// and exposes parameters input_drive and passband_gain
// v.1.02 now includes both cutoff and resonance "CV" modulation inputs
// please retain this header if you use this code.
//-----------------------------------------------------------
#pragma once
#ifndef DSY_LADDER_H
#define DSY_LADDER_H
#include <stdlib.h>
#include <stdint.h>
#include <array>
#ifdef __cplusplus
namespace daisysp
{
/**
* 4-pole ladder filter model with selectable filter type (LP/BP/HP 12 or 24 dB/oct),
* drive, passband gain compensation, and stable self-oscillation.
*
*
*/
class LadderFilter
{
public:
enum class FilterMode
{
LP24,
LP12,
BP24,
BP12,
HP24,
HP12
};
LadderFilter() = default;
~LadderFilter() = default;
/** Initializes the ladder filter module.
*/
void Init(float sample_rate);
/** Process single sample */
float Process(float in);
/** Process mono buffer/block of samples in place */
void ProcessBlock(float* buf, size_t size);
/**
Sets the cutoff frequency of the filter.
Units of hz, valid in range 5 - ~nyquist (samp_rate / 2)
Internally clamped to this range.
*/
void SetFreq(float freq);
/**
Sets the resonance of the filter.
Filter will stably self oscillate at higher values.
Valid in range 0 - 1.8
Internally clamped to this range.
*/
void SetRes(float res);
/**
Set "passband gain" compensation factor to mitigate
loss of energy in passband at higher resonance values.
Drive and passband gain have a dependent relationship.
Valid in range 0 - 0.5
Internally clamped to this range.
*/
void SetPassbandGain(float pbg);
/**
Sets drive of the input stage into the tanh clipper
Valid in range 0 - 4.0
*/
void SetInputDrive(float drv);
/**
Sets the filter mode/response
Defaults to classic lowpass 24dB/oct
*/
inline void SetFilterMode(FilterMode mode) { mode_ = mode; }
private:
static constexpr uint8_t kInterpolation = 4;
static constexpr float kInterpolationRecip = 1.0f / kInterpolation;
static constexpr float kMaxResonance = 1.8f;
float sample_rate_, sr_int_recip_;
float alpha_;
float beta_[4] = {0.0, 0.0, 0.0, 0.0};
float z0_[4] = {0.0, 0.0, 0.0, 0.0};
float z1_[4] = {0.0, 0.0, 0.0, 0.0};
float K_;
float Fbase_;
float Qadjust_;
float pbg_;
float drive_, drive_scaled_;
float oldinput_;
FilterMode mode_;
float LPF(float s, int i);
void compute_coeffs(float fc);
float weightedSumForCurrentMode(const std::array<float, 5>& stage_outs);
};
} // namespace daisysp
#endif
#endif
+110
View File
@@ -0,0 +1,110 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#ifndef DSY_ONEPOLE_H
#define DSY_ONEPOLE_H
#include "Utility/dsp.h"
#include <cmath>
#include <algorithm>
namespace daisysp
{
/**
@brief One Pole Lowpass / Highpass Filter
@author beserge
@date Dec 2023
Ported from pichenettes/stmlib/dsp/filter.h \n
\n to an independent module. \n
Original code written by Emilie Gillet. \n
*/
class OnePole
{
public:
OnePole() {}
~OnePole() {}
/**
* @brief Operational modes of the filter
* - LOW_PASS = cuts out high frequency sound, lets low frequency sound pass through
* - HIGH_PASS = cuts out low frequency sound, lets high frequency sound pass through
*/
enum FilterMode
{
FILTER_MODE_LOW_PASS,
FILTER_MODE_HIGH_PASS
};
/** Initializes the module */
void Init()
{
Reset();
mode_ = FILTER_MODE_LOW_PASS;
}
/** Reset the module to its default state */
inline void Reset() { state_ = 0.0f; }
/** Set the filter cutoff frequency
* \param freq Cutoff frequency. Valid range from 0 to .497f
*/
inline void SetFrequency(float freq)
{
// Clip coefficient to about 100.
freq = freq < 0.497f ? freq : 0.497f;
g_ = tanf(PI_F * freq);
gi_ = 1.f / (1.f + g_);
}
/** Set the filter mode
* \param mode Filter mode. Can be lowpass or highpass
*/
inline void SetFilterMode(FilterMode mode) { mode_ = mode; }
/** Process audio through the filter
* \param in The next sample to be processed
*/
inline float Process(float in)
{
float lp;
lp = (g_ * in + state_) * gi_;
state_ = g_ * (in - lp) + lp;
switch(mode_)
{
case FILTER_MODE_LOW_PASS: return lp;
case FILTER_MODE_HIGH_PASS: return in - lp;
}
return 0.0f;
}
/** Process a block of audio through the filter
* \param in_out Pointer to the block of samples to be processed
* \param size Size of the block of samples to be processed.
*/
inline void ProcessBlock(float* in_out, size_t size)
{
while(size--)
{
*in_out = Process(*in_out);
++in_out;
}
}
private:
float g_;
float gi_;
float state_;
FilterMode mode_;
};
} // namespace daisysp
#endif // DSY_ONEPOLE_H
+65
View File
@@ -0,0 +1,65 @@
#include "soap.h"
#include <cmath>
#define PI 3.141592653589793
using namespace daisysp;
void Soap::Init(float sample_rate)
{
soap_center_freq_ = 400.0;
soap_bandwidth_ = 50.0;
in_0_ = 0.0; // input x0
din_1_ = 0.0; // delayed input x1
din_2_ = 0.0; // delayed input x2
dout_1_ = 0.0; // delayed output y1
dout_2_ = 0.0; // delayed output y2
all_output_ = 0.0; // all pass output y0
out_bandpass_ = 0.0; // bandpass output
out_bandreject_ = 0.0; // bandreject output
sr_ = sample_rate;
return;
}
void Soap::Process(float in)
{
// recalculate the coefficients, later move this to a lookup table
float d = -std::cos(2.0 * PI * (soap_center_freq_ / sr_));
// tangent bandwidth
float tf = std::tan(PI * (soap_bandwidth_ / sr_));
// coefficient
float c = (tf - 1.0) / (tf + 1.0);
in_0_ = in;
all_output_ = -c * in_0_ + (d - d * c) * din_1_ + din_2_
- (d - d * c) * dout_1_ + c * dout_2_;
// move samples in delay for next sample
din_2_ = din_1_;
din_1_ = in_0_;
dout_2_ = dout_1_;
dout_1_ = all_output_;
// make factor -1.0 to create a bandpass
out_bandpass_ = (in_0_ + all_output_ * -1.0) * 0.5;
// make factor +1.0 to create a bandreject
out_bandreject_ = (in_0_ + all_output_ * 0.99) * 0.5;
return;
}
void Soap::SetCenterFreq(float f)
{
soap_center_freq_ = f;
return;
}
void Soap::SetFilterBandwidth(float b)
{
soap_bandwidth_ = b;
return;
}
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Brian Tice, Tom Erbe
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
namespace daisysp
{
/** Second Order All Pass Filter
This is from Tom Erbe's synth notes. The filter
can be configured to be a band pass or band reject
Ported by: Brian Tice
*/
class Soap
{
public:
Soap() {}
~Soap() {}
/** 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 center frequency of the filter.
*/
void SetCenterFreq(float f);
/**
Sets the low frequency threshold of the filter.
*/
void SetFilterBandwidth(float b);
/** Bandpass output
\return bandpass output of the filter
*/
inline float Bandpass() { return out_bandpass_; }
/** Bandreject output
\return bandreject output of the filter
*/
inline float Bandreject() { return out_bandreject_; }
private:
float soap_center_freq_;
float soap_bandwidth_;
float in_0_;
float din_1_;
float din_2_;
float dout_1_;
float dout_2_;
float all_output_;
float out_bandpass_;
float out_bandreject_;
float sr_;
};
} // namespace daisysp
+86
View File
@@ -0,0 +1,86 @@
#include <math.h>
#include "svf.h"
#include "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_;
}
+87
View File
@@ -0,0 +1,87 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+59
View File
@@ -0,0 +1,59 @@
#include <random>
#include "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;
}
+66
View File
@@ -0,0 +1,66 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+62
View File
@@ -0,0 +1,62 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+146
View File
@@ -0,0 +1,146 @@
#include "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);
}
+92
View File
@@ -0,0 +1,92 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+95
View File
@@ -0,0 +1,95 @@
#include "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;
}
+92
View File
@@ -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
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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_;
}
/** sets the seed (and corrects a seed of 0 to 1) */
inline void SetSeed(int32_t s) { randseed_ = s == 0 ? 1 : s; }
private:
static constexpr float coeff_ = 4.6566129e-010f;
float amp_;
int32_t randseed_;
};
} // namespace daisysp
#endif
#endif
+197
View File
@@ -0,0 +1,197 @@
#include <cmath>
#include "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();
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;
iir_damping_filter_.SetFrequency(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]);
}
+110
View File
@@ -0,0 +1,110 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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/onepole.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_;
OnePole 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
+213
View File
@@ -0,0 +1,213 @@
#include "drip.h"
#include <math.h>
#include <cstdlib>
#include "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;
}
+61
View File
@@ -0,0 +1,61 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+109
View File
@@ -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);
}
+99
View File
@@ -0,0 +1,99 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+158
View File
@@ -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;
}
+191
View File
@@ -0,0 +1,191 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+124
View File
@@ -0,0 +1,124 @@
#include "stringvoice.h"
#include <algorithm>
#include "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);
}
+102
View File
@@ -0,0 +1,102 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+79
View File
@@ -0,0 +1,79 @@
#include "granularplayer.h"
using namespace daisysp;
void GranularPlayer::Init(float* sample, int size, float sample_rate)
{
/*initialize variables to private members*/
sample_ = sample;
size_ = size;
sample_rate_ = sample_rate;
/*initialize phasors. phs2_ is initialized with a phase offset of 0.5f to create an overlapping effect*/
phs_.Init(sample_rate_, 0, 0);
phsImp_.Init(sample_rate_, 0, 0);
phs2_.Init(sample_rate_, 0, 0.5f);
phsImp2_.Init(sample_rate_, 0, 0);
/*calculate sample frequency*/
sample_frequency_ = sample_rate_ / size_;
/*initialize half cosine envelope*/
for(int i = 0; i < 256; i++)
{
cosEnv_[i] = sinf((i / 256.0f) * M_PI);
}
}
uint32_t GranularPlayer::WrapIdx(uint32_t idx, uint32_t sz)
{
/*wraps idx to sz*/
if(idx > sz)
{
idx = idx - sz;
return idx;
}
return idx;
}
float GranularPlayer::CentsToRatio(float cents)
{
/*converts cents to ratio*/
return powf(2.0f, cents / 1200.0f);
}
float GranularPlayer::MsToSamps(float ms, float samplerate)
{
/*converts milliseconds to number of samples*/
return (ms * 0.001f) * samplerate;
}
float GranularPlayer::NegativeInvert(Phasor* phs, float frequency)
{
/*inverts the phase of the phasor if the frequency is negative, mimicking pure data's phasor~ object*/
return (frequency > 0) ? phs->Process() : ((phs->Process() * -1) + 1);
}
float GranularPlayer::Process(float speed,
float transposition,
float grain_size)
{
grain_size_ = grain_size;
speed_ = speed * sample_frequency_;
transposition_ = (CentsToRatio(transposition) - speed)
* (grain_size >= 1 ? 1000 / grain_size_ : 1);
phs_.SetFreq(fabs((speed_ / 2)));
phs2_.SetFreq(fabs((speed_ / 2)));
phsImp_.SetFreq(fabs(transposition_));
phsImp2_.SetFreq(fabs(transposition_));
idxSpeed_ = NegativeInvert(&phs_, speed_) * size_;
idxSpeed2_ = NegativeInvert(&phs2_, speed_) * size_;
idxTransp_ = (NegativeInvert(&phsImp_, transposition_)
* MsToSamps(grain_size_, sample_rate_));
idxTransp2_ = (NegativeInvert(&phsImp2_, transposition_)
* MsToSamps(grain_size_, sample_rate_));
idx_ = WrapIdx((uint32_t)(idxSpeed_ + idxTransp_), size_);
idx2_ = WrapIdx((uint32_t)(idxSpeed2_ + idxTransp2_), size_);
sig_ = sample_[idx_] * cosEnv_[(uint32_t)(phs_.Process() * 256)];
sig2_ = sample_[idx2_] * cosEnv_[(uint32_t)(phs2_.Process() * 256)];
return (sig_ + sig2_) / 2;
}
+96
View File
@@ -0,0 +1,96 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Vinícius Fernandes
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_GRANULARPLAYER_H
#define DSY_GRANULARPLAYER_H
#include <stdint.h>
#include <cmath>
#include "Control/phasor.h"
#ifdef __cplusplus
#ifndef M_PI
#define M_PI 3.14159265358979323846 /* pi */
#endif
namespace daisysp
{
/** GranularPlayer Module
Date: November, 2023
Author: Vinícius Fernandes
GranularPlayer is a lookup table player that provides independent time stretching and pitch shifting
via granulation.
Inspired by the grain.player object from else pure data's library.
*/
class GranularPlayer
{
public:
GranularPlayer() {}
~GranularPlayer() {}
/** Initializes the GranularPlayer module.
\param sample pointer to the sample to be played
\param size number of elements in the sample array
\param sample_rate audio engine sample rate
*/
void Init(float* sample, int size, float sample_rate);
/** Processes the granular player.
\param speed playback speed. 1 is normal speed, 2 is double speed, 0.5 is half speed, etc. Negative values play the sample backwards.
\param transposition transposition in cents. 100 cents is one semitone. Negative values transpose down, positive values transpose up.
\param grain_size grain size in milliseconds. 1 is 1 millisecond, 1000 is 1 second. Does not accept negative values. Minimum value is 1.
*/
float Process(float speed, float transposition, float grain_size);
private:
//Wraps an index to the size of the sample array
uint32_t WrapIdx(uint32_t idx, uint32_t size);
//Converts cents(1/100th of a semitone) to a ratio
float CentsToRatio(float cents);
//Converts milliseconds to number of samples
float MsToSamps(float ms, float samplerate);
//Inverts the phase of the phasor if the frequency is negative, mimicking pure data's phasor~ object
float NegativeInvert(Phasor* phs, float frequency);
float* sample_; //pointer to the sample to be played
float sample_rate_; //audio engine sample rate
int size_; //number of elements in the sample array
float grain_size_; //grain size in milliseconds
float speed_; //processed playback speed.
float transposition_; //processed transpotion.
float sample_frequency_;
float cosEnv_[256] = {0}; //cosine envelope for crossfading between grains
float
idxTransp_; // Adjusted Transposition value contribution to idx of first grain
float
idxTransp2_; // Adjusted Transposition value contribution to idx of second grain
float idxSpeed_; // Adjusted Speed value contribution to idx of first grain
float
idxSpeed2_; // Adjusted Speed value contribution to idx of second grain
float sig_; // Output of first grain
float sig2_; // Output of second grain
uint32_t idx_; // Index of first grain
uint32_t idx2_; // Index of second grain
Phasor phs_; // Phasor for speed
Phasor phsImp_; // Phasor for transposition
Phasor phs2_; // Phasor for speed
Phasor phsImp2_; // Phasor for transposition
};
} // namespace daisysp
#endif
#endif
+65
View File
@@ -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();
}
+73
View File
@@ -0,0 +1,73 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+96
View File
@@ -0,0 +1,96 @@
#include "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;
}
+81
View File
@@ -0,0 +1,81 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+174
View File
@@ -0,0 +1,174 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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
+83
View File
@@ -0,0 +1,83 @@
#include "dsp.h"
#include "oscillator.h"
using namespace daisysp;
static inline float Polyblep(float phase_inc, float t);
float Oscillator::Process()
{
float out, t;
switch(waveform_)
{
case WAVE_SIN: out = sinf(phase_ * TWOPI_F); break;
case WAVE_TRI:
t = -1.0f + (2.0f * phase_);
out = 2.0f * (fabsf(t) - 0.5f);
break;
case WAVE_SAW: out = -1.0f * (((phase_ * 2.0f)) - 1.0f); break;
case WAVE_RAMP: out = ((phase_ * 2.0f)) - 1.0f; break;
case WAVE_SQUARE: out = phase_ < pw_ ? (1.0f) : -1.0f; break;
case WAVE_POLYBLEP_TRI:
t = phase_;
out = phase_ < 0.5f ? 1.0f : -1.0f;
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fastmod1f(t + 0.5f));
// 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;
out *= 4.f; // normalize amplitude after leaky integration
break;
case WAVE_POLYBLEP_SAW:
t = phase_;
out = (2.0f * t) - 1.0f;
out -= Polyblep(phase_inc_, t);
out *= -1.0f;
break;
case WAVE_POLYBLEP_SQUARE:
t = phase_;
out = phase_ < pw_ ? 1.0f : -1.0f;
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fastmod1f(t + (1.0f - pw_)));
out *= 0.707f; // ?
break;
default: out = 0.0f; break;
}
phase_ += phase_inc_;
if(phase_ > 1.0f)
{
phase_ -= 1.0f;
eoc_ = true;
}
else
{
eoc_ = false;
}
eor_ = (phase_ - phase_inc_ < 0.5f && phase_ >= 0.5f);
return out * amp_;
}
float Oscillator::CalcPhaseInc(float f)
{
return f * sr_recip_;
}
static float Polyblep(float phase_inc, float t)
{
float dt = phase_inc;
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;
}
}
+125
View File
@@ -0,0 +1,125 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#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;
pw_ = 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;
}
/** Sets the pulse width for WAVE_SQUARE and WAVE_POLYBLEP_SQUARE (range 0 - 1)
*/
inline void SetPw(const float pw) { pw_ = fclamp(pw, 0.0f, 1.0f); }
/** 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_ < 0.5f; }
/** Returns true if cycle falling.
*/
inline bool IsFalling() { return phase_ >= 0.5f; }
/** 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 (equivalent to 0.0-TWO_PI) to the current phase. Useful for PM and "FM" synthesis.
*/
void PhaseAdd(float _phase) { phase_ += _phase; }
/** 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_, pw_;
float sr_, sr_recip_, phase_, phase_inc_;
float last_out_, last_freq_;
bool eor_, eoc_;
};
} // namespace daisysp
#endif
#endif
+147
View File
@@ -0,0 +1,147 @@
#include "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;
}
+88
View File
@@ -0,0 +1,88 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_OSCILLATORBANK_H
#define DSY_OSCILLATORBANK_H
#include <stdint.h>
#ifdef __cplusplus
/** @file oscillatorbank.h */
namespace daisysp
{
/**
@brief Oscillator Bank module.
@author Ben Sergentanis
@date Dec 2020
A mixture of 7 sawtooth and square waveforms in the style of divide-down organs \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/string_synth_oscillator.h \n
\n to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class OscillatorBank
{
public:
OscillatorBank() {}
~OscillatorBank() {}
/** Init string synth module
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get next floating point sample
*/
float Process();
/** Set oscillator frequency (8' oscillator)
\param freq Frequency in Hz
*/
void SetFreq(float freq);
/** Set amplitudes of 7 oscillators. 0-6 are Saw 8', Square 8', Saw 4', Square 4', Saw 2', Square 2', Saw 1'
\param amplitudes array of 7 floating point amplitudes. Must sum to 1.
*/
void SetAmplitudes(const float* amplitudes);
/** Set a single amplitude
\param amp Amplitude to set.
\param idx Which wave's amp to set
*/
void SetSingleAmp(float amp, int idx);
/** Set overall gain.
\param gain Gain to set. 0-1.
*/
void SetGain(float gain);
private:
// Oscillator state.
float phase_;
float next_sample_;
int segment_;
float gain_;
float registration_[7];
float unshifted_registration_[7];
float frequency_;
float saw_8_gain_;
float saw_4_gain_;
float saw_2_gain_;
float saw_1_gain_;
float sample_rate_;
bool recalc_, recalc_gain_;
bool cmp(float a, float b) { return fabsf(a - b) > .0000001; }
};
} // namespace daisysp
#endif
#endif
+106
View File
@@ -0,0 +1,106 @@
#include "dsp.h"
#include "variablesawosc.h"
#include <math.h>
using namespace daisysp;
void VariableSawOscillator::Init(float sample_rate)
{
sample_rate_ = sample_rate;
phase_ = 0.0f;
next_sample_ = 0.0f;
previous_pw_ = 0.5f;
high_ = false;
SetFreq(220.f);
SetPW(0.f);
SetWaveshape(1.f);
}
float VariableSawOscillator::Process()
{
float next_sample = next_sample_;
float this_sample = next_sample;
next_sample = 0.0f;
const float triangle_amount = waveshape_;
const float notch_amount = 1.0f - waveshape_;
const float slope_up = 1.0f / (pw_);
const float slope_down = 1.0f / (1.0f - pw_);
phase_ += frequency_;
if(!high_ && phase_ >= pw_)
{
const float triangle_step
= (slope_up + slope_down) * frequency_ * triangle_amount;
const float notch
= (kVariableSawNotchDepth + 1.0f - pw_) * notch_amount;
const float t = (phase_ - pw_) / (previous_pw_ - pw_ + frequency_);
this_sample += notch * ThisBlepSample(t);
next_sample += notch * NextBlepSample(t);
this_sample -= triangle_step * ThisIntegratedBlepSample(t);
next_sample -= triangle_step * NextIntegratedBlepSample(t);
high_ = true;
}
else if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
const float triangle_step
= (slope_up + slope_down) * frequency_ * triangle_amount;
const float notch = (kVariableSawNotchDepth + 1.0f) * notch_amount;
const float t = phase_ / frequency_;
this_sample -= notch * ThisBlepSample(t);
next_sample -= notch * NextBlepSample(t);
this_sample += triangle_step * ThisIntegratedBlepSample(t);
next_sample += triangle_step * NextIntegratedBlepSample(t);
high_ = false;
}
next_sample += ComputeNaiveSample(
phase_, pw_, slope_up, slope_down, triangle_amount, notch_amount);
previous_pw_ = pw_;
next_sample_ = next_sample;
return (2.0f * this_sample - 1.0f) / (1.0f + kVariableSawNotchDepth);
}
void VariableSawOscillator::SetFreq(float frequency)
{
frequency = frequency / sample_rate_;
frequency = frequency >= .25f ? .25f : frequency;
pw_ = frequency >= .25f ? .5f : pw_;
frequency_ = frequency;
}
void VariableSawOscillator::SetPW(float pw)
{
if(frequency_ >= .25f)
{
pw_ = .5f;
}
else
{
pw_ = fclamp(pw, frequency_ * 2.0f, 1.0f - 2.0f * frequency_);
}
}
void VariableSawOscillator::SetWaveshape(float waveshape)
{
waveshape_ = waveshape;
}
float VariableSawOscillator::ComputeNaiveSample(float phase,
float pw,
float slope_up,
float slope_down,
float triangle_amount,
float notch_amount)
{
float notch_saw = phase < pw ? phase : 1.0f + kVariableSawNotchDepth;
float triangle
= phase < pw ? phase * slope_up : 1.0f - (phase - pw) * slope_down;
return notch_saw * notch_amount + triangle * triangle_amount;
}
+80
View File
@@ -0,0 +1,80 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_VARISAWOSCILLATOR_H
#define DSY_VARISAWOSCILLATOR_H
#include <stdint.h>
#ifdef __cplusplus
/** @file variablesawosc.h */
namespace daisysp
{
/**
@brief Variable Saw Oscillator.
@author Ben Sergentanis
@date Dec 2020
Saw with variable slope or notch. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/variable_saw_oscillator.h \n
\n to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class VariableSawOscillator
{
public:
VariableSawOscillator() {}
~VariableSawOscillator() {}
void Init(float sample_rate);
/** Get the next sample */
float Process();
/** Set master freq.
\param frequency Freq in Hz.
*/
void SetFreq(float frequency);
/** Adjust the wave depending on the shape
\param pw Notch or slope. Works best -1 to 1.
*/
void SetPW(float pw);
/** Slope or notch
\param waveshape 0 = notch, 1 = slope
*/
void SetWaveshape(float waveshape);
private:
float ComputeNaiveSample(float phase,
float pw,
float slope_up,
float slope_down,
float triangle_amount,
float notch_amount);
float sample_rate_;
// Oscillator state.
float phase_;
float next_sample_;
float previous_pw_;
bool high_;
const float kVariableSawNotchDepth = 0.2f;
// For interpolation of parameters.
float frequency_;
float pw_;
float waveshape_;
};
} // namespace daisysp
#endif
#endif
+182
View File
@@ -0,0 +1,182 @@
#include "dsp.h"
#include "variableshapeosc.h"
#include <math.h>
using namespace daisysp;
void VariableShapeOscillator::Init(float sample_rate)
{
sample_rate_ = sample_rate;
master_phase_ = 0.0f;
slave_phase_ = 0.0f;
next_sample_ = 0.0f;
previous_pw_ = 0.5f;
high_ = false;
SetFreq(440.f);
SetWaveshape(0.f);
SetPW(0.f);
SetSync(false);
SetSyncFreq(220.f);
}
float VariableShapeOscillator::Process()
{
float next_sample = next_sample_;
bool reset = false;
bool transition_during_reset = false;
float reset_time = 0.0f;
float this_sample = next_sample;
next_sample = 0.0f;
const float square_amount = fmax(waveshape_ - 0.5f, 0.0f) * 2.0f;
const float triangle_amount = fmax(1.0f - waveshape_ * 2.0f, 0.0f);
const float slope_up = 1.0f / (pw_);
const float slope_down = 1.0f / (1.0f - pw_);
if(enable_sync_)
{
master_phase_ += master_frequency_;
if(master_phase_ >= 1.0f)
{
master_phase_ -= 1.0f;
reset_time = master_phase_ / master_frequency_;
float slave_phase_at_reset
= slave_phase_ + (1.0f - reset_time) * slave_frequency_;
reset = true;
if(slave_phase_at_reset >= 1.0f)
{
slave_phase_at_reset -= 1.0f;
transition_during_reset = true;
}
if(!high_ && slave_phase_at_reset >= pw_)
{
transition_during_reset = true;
}
float value = ComputeNaiveSample(slave_phase_at_reset,
pw_,
slope_up,
slope_down,
triangle_amount,
square_amount);
this_sample -= value * ThisBlepSample(reset_time);
next_sample -= value * NextBlepSample(reset_time);
}
}
slave_phase_ += slave_frequency_;
while(transition_during_reset || !reset)
{
if(!high_)
{
if(slave_phase_ < pw_)
{
break;
}
float t = (slave_phase_ - pw_)
/ (previous_pw_ - pw_ + slave_frequency_);
float triangle_step = (slope_up + slope_down) * slave_frequency_;
triangle_step *= triangle_amount;
this_sample += square_amount * ThisBlepSample(t);
next_sample += square_amount * NextBlepSample(t);
this_sample -= triangle_step * ThisIntegratedBlepSample(t);
next_sample -= triangle_step * NextIntegratedBlepSample(t);
high_ = true;
}
if(high_)
{
if(slave_phase_ < 1.0f)
{
break;
}
slave_phase_ -= 1.0f;
float t = slave_phase_ / slave_frequency_;
float triangle_step = (slope_up + slope_down) * slave_frequency_;
triangle_step *= triangle_amount;
this_sample -= (1.0f - triangle_amount) * ThisBlepSample(t);
next_sample -= (1.0f - triangle_amount) * NextBlepSample(t);
this_sample += triangle_step * ThisIntegratedBlepSample(t);
next_sample += triangle_step * NextIntegratedBlepSample(t);
high_ = false;
}
}
if(enable_sync_ && reset)
{
slave_phase_ = reset_time * slave_frequency_;
high_ = false;
}
next_sample += ComputeNaiveSample(slave_phase_,
pw_,
slope_up,
slope_down,
triangle_amount,
square_amount);
previous_pw_ = pw_;
next_sample_ = next_sample;
return (2.0f * this_sample - 1.0f);
}
void VariableShapeOscillator::SetFreq(float frequency)
{
frequency = frequency / sample_rate_;
frequency = frequency >= .25f ? .25f : frequency;
master_frequency_ = frequency;
}
void VariableShapeOscillator::SetPW(float pw)
{
if(slave_frequency_ >= .25f)
{
pw_ = .5f;
}
else
{
pw_ = fclamp(
pw, slave_frequency_ * 2.0f, 1.0f - 2.0f * slave_frequency_);
}
}
void VariableShapeOscillator::SetWaveshape(float waveshape)
{
waveshape_ = waveshape;
}
void VariableShapeOscillator::SetSync(bool enable_sync)
{
enable_sync_ = enable_sync;
}
void VariableShapeOscillator::SetSyncFreq(float frequency)
{
frequency = frequency / sample_rate_;
pw_ = frequency >= .25f ? .5f : pw_;
frequency = frequency >= .25f ? .25f : frequency;
slave_frequency_ = frequency;
}
float VariableShapeOscillator::ComputeNaiveSample(float phase,
float pw,
float slope_up,
float slope_down,
float triangle_amount,
float square_amount)
{
float saw = phase;
float square = phase < pw ? 0.0f : 1.0f;
float triangle
= phase < pw ? phase * slope_up : 1.0f - (phase - pw) * slope_down;
saw += (square - saw) * square_amount;
saw += (triangle - saw) * triangle_amount;
return saw;
}
+95
View File
@@ -0,0 +1,95 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_VARIABLESHAPEOSCILLATOR_H
#define DSY_VARIABLESHAPEOSCILLATOR_H
#include <stdint.h>
#ifdef __cplusplus
/** @file variableshapeosc.h */
namespace daisysp
{
/**
@brief Variable Waveshape Oscillator.
@author Ben Sergentanis
@date Dec 2020
Continuously variable waveform. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/variable_shape_oscillator.h \n
\n to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class VariableShapeOscillator
{
public:
VariableShapeOscillator() {}
~VariableShapeOscillator() {}
/** Initialize the oscillator
\param sample_rate Audio engine sample rate
*/
void Init(float sample_rate);
/** Get next sample
*/
float Process();
/** Set master freq.
\param frequency Freq in Hz.
*/
void SetFreq(float frequency);
/** Set pulse width / saw, ramp, tri.
\param pw PW when shape is square. Saw, ramp, tri otherwise.
*/
void SetPW(float pw);
/** Switch from saw/ramp/tri to square.
\param waveshape 0 is saw/ramp/tri, 1 is square.
*/
void SetWaveshape(float waveshape);
/** Whether or not to sync to the sync oscillator
\param enable_sync True to turn sync on.
*/
void SetSync(bool enable_sync);
/** Set sync oscillator freq.
\param frequency Freq in Hz.
*/
void SetSyncFreq(float frequency);
private:
float ComputeNaiveSample(float phase,
float pw,
float slope_up,
float slope_down,
float triangle_amount,
float square_amount);
float sample_rate_;
bool enable_sync_;
// Oscillator state.
float master_phase_;
float slave_phase_;
float next_sample_;
float previous_pw_;
bool high_;
// For interpolation of parameters.
float master_frequency_;
float slave_frequency_;
float pw_;
float waveshape_;
};
} // namespace daisysp
#endif
#endif
+81
View File
@@ -0,0 +1,81 @@
#include "dsp.h"
#include "vosim.h"
#include <math.h>
using namespace daisysp;
void VosimOscillator::Init(float sample_rate)
{
sample_rate_ = sample_rate;
carrier_phase_ = 0.0f;
formant_1_phase_ = 0.0f;
formant_2_phase_ = 0.0f;
SetFreq(105.f);
SetForm1Freq(1390.f);
SetForm2Freq(817.f);
SetShape(.5f);
}
float VosimOscillator::Process()
{
carrier_phase_ += carrier_frequency_;
if(carrier_phase_ >= 1.0f)
{
carrier_phase_ -= 1.0f;
float reset_time = carrier_phase_ / carrier_frequency_;
formant_1_phase_ = reset_time * formant_1_frequency_;
formant_2_phase_ = reset_time * formant_2_frequency_;
}
else
{
formant_1_phase_ += formant_1_frequency_;
if(formant_1_phase_ >= 1.0f)
{
formant_1_phase_ -= 1.0f;
}
formant_2_phase_ += formant_2_frequency_;
if(formant_2_phase_ >= 1.0f)
{
formant_2_phase_ -= 1.0f;
}
}
float carrier = Sine(carrier_phase_ * 0.5f + 0.25f) + 1.0f;
float reset_phase = 0.75f - 0.25f * carrier_shape_;
float reset_amplitude = Sine(reset_phase);
float formant_0 = Sine(formant_1_phase_ + reset_phase) - reset_amplitude;
float formant_1 = Sine(formant_2_phase_ + reset_phase) - reset_amplitude;
return carrier * (formant_0 + formant_1) * 0.25f + reset_amplitude;
}
void VosimOscillator::SetFreq(float freq)
{
carrier_frequency_ = freq / sample_rate_;
carrier_frequency_ = carrier_frequency_ > .25f ? .25f : carrier_frequency_;
}
void VosimOscillator::SetForm1Freq(float freq)
{
formant_1_frequency_ = freq / sample_rate_;
formant_1_frequency_
= formant_1_frequency_ > .25f ? .25f : formant_1_frequency_;
}
void VosimOscillator::SetForm2Freq(float freq)
{
formant_2_frequency_ = freq / sample_rate_;
formant_2_frequency_
= formant_2_frequency_ > .25f ? .25f : formant_2_frequency_;
}
void VosimOscillator::SetShape(float shape)
{
carrier_shape_ = shape;
}
float VosimOscillator::Sine(float phase)
{
return sinf(TWOPI_F * phase);
}
+82
View File
@@ -0,0 +1,82 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_VOSIM_H
#define DSY_VOSIM_H
#include <stdint.h>
#ifdef __cplusplus
/** @file vosim.h */
namespace daisysp
{
/**
@brief Vosim Oscillator Module \n
@author Ben Sergentanis
@date Dec 2020
Two sinewaves multiplied by and sync'ed to a carrier. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/vosim_oscillator.h \n
\n to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class VosimOscillator
{
public:
VosimOscillator() {}
~VosimOscillator() {}
/** 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 carrier frequency.
\param freq Frequency in Hz.
*/
void SetFreq(float freq);
/** Set formant 1 frequency.
\param freq Frequency in Hz.
*/
void SetForm1Freq(float freq);
/** Set formant 2 frequency.
\param freq Frequency in Hz.
*/
void SetForm2Freq(float freq);
/** Waveshaping
\param shape Shape to set. Works -1 to 1
*/
void SetShape(float shape);
private:
float Sine(float phase);
float sample_rate_;
// Oscillator state.
float carrier_phase_;
float formant_1_phase_;
float formant_2_phase_;
// For interpolation of parameters.
float carrier_frequency_;
float formant_1_frequency_;
float formant_2_frequency_;
float carrier_shape_;
};
} // namespace daisysp
#endif
#endif
+159
View File
@@ -0,0 +1,159 @@
#include "dsp.h"
#include "zoscillator.h"
#include <math.h>
using namespace daisysp;
void ZOscillator::Init(float sample_rate)
{
sample_rate_ = sample_rate;
carrier_phase_ = 0.0f;
discontinuity_phase_ = 0.0f;
formant_phase_ = 0.0f;
next_sample_ = 0.0f;
SetFreq(220.f);
SetFormantFreq(550.f);
SetMode(0.f);
SetShape(1.f);
}
float ZOscillator::Process()
{
float next_sample = next_sample_;
bool reset = false;
float reset_time = 0.0f;
float this_sample = next_sample;
next_sample = 0.0f;
discontinuity_phase_ += 2.0f * carrier_frequency_;
carrier_phase_ += carrier_frequency_;
reset = discontinuity_phase_ >= 1.0f;
if(reset)
{
discontinuity_phase_ -= 1.0f;
reset_time = discontinuity_phase_ / (2.0f * carrier_frequency_);
float carrier_phase_before = carrier_phase_ >= 1.0f ? 1.0f : 0.5f;
float carrier_phase_after = carrier_phase_ >= 1.0f ? 0.0f : 0.5f;
float mode_sub = mode_ + (1.f - reset_time) * (mode_ - mode_new_);
float shape_sub = carrier_shape_
+ (1.0f - reset_time) * (carrier_shape_ - shape_new_);
float before
= Z(carrier_phase_before,
1.0f,
formant_phase_ + (1.0f - reset_time) * formant_frequency_,
shape_sub,
mode_sub);
float after = Z(carrier_phase_after, 0.0f, 0.0f, shape_new_, mode_new_);
float discontinuity = after - before;
this_sample += discontinuity * ThisBlepSample(reset_time);
next_sample += discontinuity * NextBlepSample(reset_time);
formant_phase_ = reset_time * formant_frequency_;
if(carrier_phase_ > 1.0f)
{
carrier_phase_ = discontinuity_phase_ * 0.5f;
}
}
else
{
formant_phase_ += formant_frequency_;
if(formant_phase_ >= 1.0f)
{
formant_phase_ -= 1.0f;
}
}
if(carrier_phase_ >= 1.0f)
{
carrier_phase_ -= 1.0f;
}
carrier_shape_ = shape_new_;
mode_ = mode_new_;
next_sample += Z(carrier_phase_,
discontinuity_phase_,
formant_phase_,
carrier_shape_,
mode_);
next_sample_ = next_sample;
return this_sample;
}
inline float ZOscillator::Sine(float phase)
{
return sinf(phase * TWOPI_F);
}
void ZOscillator::SetFreq(float freq)
{
//convert from Hz to phase_inc / sample
carrier_frequency_ = freq / sample_rate_;
carrier_frequency_ = carrier_frequency_ >= .25f ? .25f : carrier_frequency_;
}
void ZOscillator::SetFormantFreq(float freq)
{
//convert from Hz to phase_inc / sample
formant_frequency_ = freq / sample_rate_;
formant_frequency_ = formant_frequency_ >= .25f ? .25f : formant_frequency_;
}
void ZOscillator::SetShape(float shape)
{
shape_new_ = shape;
}
void ZOscillator::SetMode(float mode)
{
mode_new_ = mode;
}
inline float ZOscillator::Z(float c, float d, float f, float shape, float mode)
{
float ramp_down = 0.5f * (1.0f + Sine(0.5f * d + 0.25f));
float offset;
float phase_shift;
if(mode < 0.333f)
{
offset = 1.0f;
phase_shift = 0.25f + mode * 1.50f;
}
else if(mode < 0.666f)
{
phase_shift = 0.7495f - (mode - 0.33f) * 0.75f;
offset = -Sine(phase_shift);
}
else
{
phase_shift = 0.7495f - (mode - 0.33f) * 0.75f;
offset = 0.001f;
}
float discontinuity = Sine(f + phase_shift);
float contour;
if(shape < 0.5f)
{
shape *= 2.0f;
if(c >= 0.5f)
{
ramp_down *= shape;
}
contour = 1.0f + (Sine(c + 0.25f) - 1.0f) * shape;
}
else
{
contour = Sine(c + shape * 0.5f);
}
return (ramp_down * (offset + discontinuity) - offset) * contour;
}
+84
View File
@@ -0,0 +1,84 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_ZOSCILLATOR_H
#define DSY_ZOSCILLATOR_H
#include <stdint.h>
#ifdef __cplusplus
/** @file zoscillator.h */
namespace daisysp
{
/**
@brief ZOscillator Module \n
@author Ben Sergentanis
@date Dec 2020
Sinewave multiplied by and sync'ed to a carrier. \n \n
Ported from pichenettes/eurorack/plaits/dsp/oscillator/z_oscillator.h \n
\n to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class ZOscillator
{
public:
ZOscillator() {}
~ZOscillator() {}
/** Init ZOscillator module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate);
/** Get next sample
*/
float Process();
/** Set the carrier frequency
\param freq Frequency in Hz.
*/
void SetFreq(float freq);
/** Set the formant osc. freq
\param freq Frequency in Hz.
*/
void SetFormantFreq(float freq);
/** Adjust the contour of the waveform.
\param shape Waveshape to set. Works best 0-1.
*/
void SetShape(float shape);
/** Set the offset amount and phase shift. \n
< 1/3 is just phase shift, > 2/3 is just offset, and between them is both. \n
\param mode Mode to set. Works best -1 to 1
*/
void SetMode(float mode);
private:
inline float Sine(float phase);
inline float Z(float c, float d, float f, float shape, float mode);
float sample_rate_;
// Oscillator state.
float carrier_phase_;
float discontinuity_phase_;
float formant_phase_;
float next_sample_;
// For interpolation of parameters.
float carrier_frequency_;
float formant_frequency_;
float carrier_shape_, shape_new_;
float mode_, mode_new_;
};
} // namespace daisysp
#endif
#endif
+20
View File
@@ -0,0 +1,20 @@
#include <math.h>
#include "dcblock.h"
using namespace daisysp;
void DcBlock::Init(float sample_rate)
{
output_ = 0.0;
input_ = 0.0;
gain_ = 1.0 - 10.f / sample_rate;
}
float DcBlock::Process(float in)
{
float out;
out = in - input_ + (gain_ * output_);
output_ = out;
input_ = in;
return out;
}
+37
View File
@@ -0,0 +1,37 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_DCBLOCK_H
#define DSY_DCBLOCK_H
#ifdef __cplusplus
namespace daisysp
{
/** Removes DC component of a signal
*/
class DcBlock
{
public:
DcBlock(){};
~DcBlock(){};
/** Initializes DcBlock module
*/
void Init(float sample_rate);
/** performs DcBlock Process
*/
float Process(float in);
private:
float input_, output_, gain_;
};
} // namespace daisysp
#endif
#endif
+129
View File
@@ -0,0 +1,129 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_DELAY_H
#define DSY_DELAY_H
#include <stdlib.h>
#include <stdint.h>
namespace daisysp
{
/** Simple Delay line.
November 2019
Converted to Template December 2019
declaration example: (1 second of floats)
DelayLine<float, SAMPLE_RATE> del;
By: shensley
*/
template <typename T, size_t max_size>
class DelayLine
{
public:
DelayLine() {}
~DelayLine() {}
/** initializes the delay line by clearing the values within, and setting delay to 1 sample.
*/
void Init() { Reset(); }
/** clears buffer, sets write ptr to 0, and delay to 1 sample.
*/
void Reset()
{
for(size_t i = 0; i < max_size; i++)
{
line_[i] = T(0);
}
write_ptr_ = 0;
delay_ = 1;
}
/** sets the delay time in samples
If a float is passed in, a fractional component will be calculated for interpolating the delay line.
*/
inline void SetDelay(size_t delay)
{
frac_ = 0.0f;
delay_ = delay < max_size ? delay : max_size - 1;
}
/** sets the delay time in samples
If a float is passed in, a fractional component will be calculated for interpolating the delay line.
*/
inline void SetDelay(float delay)
{
int32_t int_delay = static_cast<int32_t>(delay);
frac_ = delay - static_cast<float>(int_delay);
delay_ = static_cast<size_t>(int_delay) < max_size ? int_delay
: max_size - 1;
}
/** writes the sample of type T to the delay line, and advances the write ptr
*/
inline void Write(const T sample)
{
line_[write_ptr_] = sample;
write_ptr_ = (write_ptr_ - 1 + max_size) % max_size;
}
/** returns the next sample of type T in the delay line, interpolated if necessary.
*/
inline const T Read() const
{
T a = line_[(write_ptr_ + delay_) % max_size];
T b = line_[(write_ptr_ + delay_ + 1) % max_size];
return a + (b - a) * frac_;
}
/** Read from a set location */
inline const T Read(float delay) const
{
int32_t delay_integral = static_cast<int32_t>(delay);
float delay_fractional = delay - static_cast<float>(delay_integral);
const T a = line_[(write_ptr_ + delay_integral) % max_size];
const T b = line_[(write_ptr_ + delay_integral + 1) % max_size];
return a + (b - a) * delay_fractional;
}
inline const T ReadHermite(float delay) const
{
int32_t delay_integral = static_cast<int32_t>(delay);
float delay_fractional = delay - static_cast<float>(delay_integral);
int32_t t = (write_ptr_ + delay_integral + max_size);
const T xm1 = line_[(t - 1) % max_size];
const T x0 = line_[(t) % max_size];
const T x1 = line_[(t + 1) % max_size];
const T x2 = line_[(t + 2) % max_size];
const float c = (x1 - xm1) * 0.5f;
const float v = x0 - x1;
const float w = c + v;
const float a = w + v + (x2 - x0) * 0.5f;
const float b_neg = w + a;
const float f = delay_fractional;
return (((a * f) - b_neg) * f + c) * f + x0;
}
inline const T Allpass(const T sample, size_t delay, const T coefficient)
{
T read = line_[(write_ptr_ + delay) % max_size];
T write = sample + coefficient * read;
Write(write);
return -write * coefficient + read;
}
private:
float frac_;
size_t write_ptr_;
size_t delay_;
T line_[max_size];
};
} // namespace daisysp
#endif
+352
View File
@@ -0,0 +1,352 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
/** Helpful defines, functions, and other utilities for use in/with daisysp modules.
*/
#pragma once
#ifndef DSY_CORE_DSP
#define DSY_CORE_DSP
#include <cassert>
#include <cstdint>
#include <random>
#include <cmath>
/** PIs
*/
#define PI_F 3.1415927410125732421875f
#define TWOPI_F (2.0f * PI_F)
#define HALFPI_F (PI_F * 0.5f)
#define DSY_MIN(in, mn) (in < mn ? in : mn)
#define DSY_MAX(in, mx) (in > mx ? in : mx)
#define DSY_CLAMP(in, mn, mx) (DSY_MIN(DSY_MAX(in, mn), mx))
#define DSY_COUNTOF(_arr) (sizeof(_arr) / sizeof(_arr[0]))
namespace daisysp
{
//Avoids division for random floats. e.g. rand() * kRandFrac
static constexpr float kRandFrac = 1.f / (float)RAND_MAX;
//Convert from semitones to other units. e.g. 2 ^ (kOneTwelfth * x)
static constexpr float kOneTwelfth = 1.f / 12.f;
/** efficient floating point min/max
c/o stephen mccaul
*/
inline float fmax(float a, float b)
{
float r;
#ifdef __arm__
asm("vmaxnm.f32 %[d], %[n], %[m]" : [d] "=t"(r) : [n] "t"(a), [m] "t"(b) :);
#else
r = (a > b) ? a : b;
#endif // __arm__
return r;
}
inline float fmin(float a, float b)
{
float r;
#ifdef __arm__
asm("vminnm.f32 %[d], %[n], %[m]" : [d] "=t"(r) : [n] "t"(a), [m] "t"(b) :);
#else
r = (a < b) ? a : b;
#endif // __arm__
return r;
}
/** quick fp clamp
*/
inline float fclamp(float in, float min, float max)
{
return fmin(fmax(in, min), max);
}
/** From Musicdsp.org "Fast power and root estimates for 32bit floats)
Original code by Stefan Stenzel
These are approximations
*/
inline float fastpower(float f, int n)
{
long *lp, l;
lp = (long *)(&f);
l = *lp;
l -= 0x3F800000;
l <<= (n - 1);
l += 0x3F800000;
*lp = l;
return f;
}
inline float fastroot(float f, int n)
{
long *lp, l;
lp = (long *)(&f);
l = *lp;
l -= 0x3F800000;
l >>= (n - 1);
l += 0x3F800000;
*lp = l;
return f;
}
/** Significantly more efficient than fmodf(x, 1.0f) for calculating
* the decimal part of a floating point value.
*/
inline float fastmod1f(float x)
{
return x - floorf(x);
}
/** From http://openaudio.blogspot.com/2017/02/faster-log10-and-pow.html
No approximation, pow10f(x) gives a 90% speed increase over powf(10.f, x)
*/
inline float pow10f(float f)
{
return expf(2.302585092994046f * f);
}
/* Original code for fastlog2f by Dr. Paul Beckmann from the ARM community forum, adapted from the CMSIS-DSP library
About 25% performance increase over std::log10f
*/
inline float fastlog2f(float f)
{
float frac;
int exp;
frac = frexpf(fabsf(f), &exp);
f = 1.23149591368684f;
f *= frac;
f += -4.11852516267426f;
f *= frac;
f += 6.02197014179219f;
f *= frac;
f += -3.13396450166353f;
f += exp;
return (f);
}
inline float fastlog10f(float f)
{
return fastlog2f(f) * 0.3010299956639812f;
}
/** Midi to frequency helper
*/
inline float mtof(float m)
{
return powf(2, (m - 69.0f) / 12.0f) * 440.0f;
}
/** one pole lpf
out is passed by reference, and must be retained between
calls to properly filter the signal
coeff can be calculated:
coeff = 1.0 / (time * sample_rate) ; where time is in seconds
*/
inline void fonepole(float &out, float in, float coeff)
{
out += coeff * (in - out);
}
/** Curves to use with the fmap function */
enum class Mapping
{
LINEAR,
EXP,
LOG,
};
/** Maps a float between a specified range, using a specified curve.
*
* \param in a value between 0 to 1 that will be mapped to the new range.
* \param min the new minimum value
* \param max the new maxmimum value
* \param curve a Mapping Value to adjust the response curve of the transformation
* defaults to Linear. @see Mapping
*
* When using the log curve min and max, must be greater than zero.
*
* \retval returns the transformed float within the new range.
*/
inline float
fmap(float in, float min, float max, Mapping curve = Mapping::LINEAR)
{
switch(curve)
{
case Mapping::EXP:
return fclamp(min + (in * in) * (max - min), min, max);
case Mapping::LOG:
{
const float a = 1.f / log10f(max / min);
return fclamp(min * powf(10, in / a), min, max);
}
case Mapping::LINEAR:
default: return fclamp(min + in * (max - min), min, max);
}
}
/** Simple 3-point median filter
c/o stephen mccaul
*/
template <typename T>
T median(T a, T b, T c)
{
return (b < a) ? (b < c) ? (c < a) ? c : a : b
: (a < c) ? (c < b) ? c : b : a;
}
/** Ported from pichenettes/eurorack/plaits/dsp/oscillator/oscillator.h
*/
inline float ThisBlepSample(float t)
{
return 0.5f * t * t;
}
/** Ported from pichenettes/eurorack/plaits/dsp/oscillator/oscillator.h
*/
inline float NextBlepSample(float t)
{
t = 1.0f - t;
return -0.5f * t * t;
}
/** Ported from pichenettes/eurorack/plaits/dsp/oscillator/oscillator.h
*/
inline float NextIntegratedBlepSample(float t)
{
const float t1 = 0.5f * t;
const float t2 = t1 * t1;
const float t4 = t2 * t2;
return 0.1875f - t1 + 1.5f * t2 - t4;
}
/** Ported from pichenettes/eurorack/plaits/dsp/oscillator/oscillator.h
*/
inline float ThisIntegratedBlepSample(float t)
{
return NextIntegratedBlepSample(1.0f - t);
}
/** Soft Limiting function ported extracted from pichenettes/stmlib */
inline float SoftLimit(float x)
{
return x * (27.f + x * x) / (27.f + 9.f * x * x);
}
/** Soft Clipping function extracted from pichenettes/stmlib */
inline float SoftClip(float x)
{
if(x < -3.0f)
return -1.0f;
else if(x > 3.0f)
return 1.0f;
else
return SoftLimit(x);
}
/** Quick check for Invalid float values (NaN, Inf, out of range)
** \param x value passed by reference, replaced by y if invalid.
** \param y value to replace x if invalidity is found.
**
** When DEBUG is true in the build, this will halt
** execution for tracing the reason for the invalidity. */
inline void TestFloat(float &x, float y = 0.f)
{
if(!std::isnormal(x) && x != 0)
{
#if defined(__arm__) && defined(DEBUG)
asm("bkpt 255");
#else
x = y;
#endif
}
}
/** Based on soft saturate from:
[musicdsp.org](musicdsp.org/en/latest/Effects/42-soft-saturation.html)
Bram de Jong (2002-01-17)
This still needs to be tested/fixed. Definitely does some weird stuff
described as:
x < a:
f(x) = x
x > a:
f(x) = a + (x-a)/(1+((x-a)/(1-a))^2)
x > 1:
f(x) = (a + 1)/2
*/
inline float soft_saturate(float in, float thresh)
{
bool flip;
float val, out;
//val = fabsf(in);
out = 0.f;
flip = in < 0.0f;
val = flip ? -in : in;
if(val < thresh)
{
out = in;
}
else if(val > 1.0f)
{
out = (thresh + 1.0f) / 2.0f;
if(flip)
out *= -1.0f;
}
else if(val > thresh)
{
float temp;
temp = (val - thresh) / (1 - thresh);
out = thresh + (val - thresh) / (1.0f + (temp * temp));
if(flip)
out *= -1.0f;
}
return out;
// return val < thresh
// ? val
// : val > 1.0f
// ? (thresh + 1.0f) / 2.0f
// : thresh
// + (val - thresh)
// / (1.0f
// + (((val - thresh) / (1.0f - thresh))
// * ((val - thresh) / (1.0f - thresh))));
}
constexpr bool is_power2(uint32_t x)
{
return ((x - 1) & x) == 0;
}
/** Prior to C++14 constexpr functions were required to be a single return statement.
* So this clause guards against that behavior to allow the library, and this function
* to continue to work with C++11.
* The function itself is not currently (12 May 2021) used within the library itself.
*/
#if __cplusplus <= 201103L
inline uint32_t get_next_power2(uint32_t x)
#else
constexpr uint32_t get_next_power2(uint32_t x)
#endif
{
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
assert(is_power2(x));
return x;
}
} // namespace daisysp
#endif
#ifdef DSY_CUSTOM_DSP
#include "custom_dsp.h"
#endif
+314
View File
@@ -0,0 +1,314 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#include <algorithm>
#include "dsp.h"
namespace daisysp
{
/** Multimode audio looper
*
* Modes are:
* - Normal
* - Onetime Dub
* - Replace
* - Frippertronics
*
* Read more about the looper modes in the mode enum documentation.
*/
class Looper
{
public:
Looper() {}
~Looper() {}
/**
** Normal Mode: Input is added to the existing loop infinitely while recording
**
** Onetime Dub Mode: Recording starts at the first sample of the buffer and is added
** to the existing buffer contents. Recording automatically stops after one full loop.
**
** Replace Mode: Audio in the buffer is replaced while recording is on.
**
** Frippertronics Mode: infinite looping recording with fixed decay on each loop. The module acts like tape-delay set up.
*/
enum class Mode
{
NORMAL,
ONETIME_DUB,
REPLACE,
FRIPPERTRONICS,
};
void Init(float *mem, size_t size)
{
buffer_size_ = size;
buff_ = mem;
InitBuff();
state_ = State::EMPTY;
mode_ = Mode::NORMAL;
half_speed_ = false;
reverse_ = false;
rec_queue_ = false;
win_idx_ = 0;
increment_size = 1.0;
}
/** Handles reading/writing to the Buffer depending on the mode. */
float Process(const float input)
{
float sig = 0.f;
float inc;
bool hitloop = false;
// Record forward at normal speed during the first loop no matter what.
inc = state_ == State::EMPTY || state_ == State::REC_FIRST
? 1.f
: GetIncrementSize();
win_ = WindowVal(win_idx_ * kWindowFactor);
switch(state_)
{
case State::EMPTY:
sig = 0.0f;
pos_ = 0;
recsize_ = 0;
break;
case State::REC_FIRST:
sig = 0.f;
Write(pos_, input * win_);
if(win_idx_ < kWindowSamps - 1)
win_idx_ += 1;
recsize_ = pos_;
pos_ += inc;
if(pos_ > buffer_size_ - 1)
{
state_ = State::PLAYING;
recsize_ = pos_ - 1;
pos_ = 0;
}
break;
case State::PLAYING:
sig = Read(pos_);
/** This is a way of 'seamless looping'
** The first N samps after recording is done are recorded with the input faded out.
*/
if(win_idx_ < kWindowSamps - 1)
{
Write(pos_, sig + input * (1.f - win_));
win_idx_ += 1;
}
pos_ += inc;
if(pos_ > recsize_ - 1)
{
pos_ = 0;
hitloop = true;
}
else if(pos_ < 0)
{
pos_ = recsize_ - 1;
hitloop = true;
}
if(hitloop)
{
if(rec_queue_ && mode_ == Mode::ONETIME_DUB)
{
rec_queue_ = false;
state_ = State::REC_DUB;
win_idx_ = 0;
}
}
break;
case State::REC_DUB:
sig = Read(pos_);
switch(mode_)
{
case Mode::REPLACE: Write(pos_, input * win_); break;
case Mode::FRIPPERTRONICS:
Write(pos_, (input * win_) + (sig * kFripDecayVal));
break;
case Mode::NORMAL:
case Mode::ONETIME_DUB:
default: Write(pos_, (input * win_) + sig); break;
}
if(win_idx_ < kWindowSamps - 1)
win_idx_ += 1;
pos_ += inc;
if(pos_ > recsize_ - 1)
{
pos_ = 0;
hitloop = true;
}
else if(pos_ < 0)
{
pos_ = recsize_ - 1;
hitloop = true;
}
if(hitloop && mode_ == Mode::ONETIME_DUB)
{
state_ = State::PLAYING;
win_idx_ = 0;
}
break;
default: break;
}
near_beginning_ = state_ != State::EMPTY && !Recording() && pos_ < 4800
? true
: false;
return sig;
}
/** Effectively erases the buffer
** Note: This does not actually change what is in the buffer */
inline void Clear() { state_ = State::EMPTY; }
/** Engages/Disengages the recording, depending on Mode.
** In all modes, the first time this is triggered a new loop will be started.
** The second trigger will set the loop size, and begin playback of the loop.
*/
inline void TrigRecord()
{
switch(state_)
{
case State::EMPTY:
pos_ = 0;
recsize_ = 0;
state_ = State::REC_FIRST;
half_speed_ = false;
reverse_ = false;
break;
case State::REC_FIRST:
case State::REC_DUB: state_ = State::PLAYING; break;
case State::PLAYING:
if(mode_ == Mode::ONETIME_DUB)
rec_queue_ = true;
else
state_ = State::REC_DUB;
break;
default: state_ = State::EMPTY; break;
}
if(!rec_queue_)
win_idx_ = 0;
}
/** Returns true if the looper is currently being written to. */
inline const bool Recording() const
{
return state_ == State::REC_DUB || state_ == State::REC_FIRST;
}
inline const bool RecordingQueued() const { return rec_queue_; }
/** Increments the Mode by one step useful for buttons, etc. that need to step through the Looper modes. */
inline void IncrementMode()
{
int m = static_cast<int>(mode_);
m = m + 1;
if(m > kNumModes - 1)
m = 0;
mode_ = static_cast<Mode>(m);
}
/** Sets the recording mode to the specified Mode. */
inline void SetMode(Mode mode) { mode_ = mode; }
/** Returns the specific recording mode that is currently set. */
inline const Mode GetMode() const { return mode_; }
inline void ToggleReverse() { reverse_ = !reverse_; }
inline void SetReverse(bool state) { reverse_ = state; }
inline bool GetReverse() const { return reverse_; }
inline void ToggleHalfSpeed() { half_speed_ = !half_speed_; }
inline void SetHalfSpeed(bool state) { half_speed_ = state; }
inline bool GetHalfSpeed() const { return half_speed_; }
inline bool IsNearBeginning() const { return near_beginning_; }
inline float GetIncrementSize() const
{
float inc = increment_size;
if(half_speed_)
inc *= 0.5f;
return reverse_ ? -inc : inc;
}
void SetIncrementSize(float increment) { increment_size = increment; }
inline float GetPos() const { return pos_; }
inline size_t GetRecSize() const { return recsize_; }
private:
/** Constants */
/** Decay value for frippertronics mode is sin(PI / 4) */
static constexpr float kFripDecayVal = 0.7071067811865476f;
static constexpr int kNumModes = 4;
static constexpr int kNumPlaybackSpeeds = 3;
static constexpr int kWindowSamps = 1200;
static constexpr float kWindowFactor = (1.f / kWindowSamps);
/** Private Member Functions */
/** Initialize the buffer */
void InitBuff() { std::fill(&buff_[0], &buff_[buffer_size_ - 1], 0); }
/** Get a floating point sample from the buffer */
inline const float Read(size_t pos) const { return buff_[pos]; }
/** Reads from a specified point in the delay line using linear interpolation */
float ReadF(float pos)
{
float a, b, frac;
uint32_t i_idx = static_cast<uint32_t>(pos);
frac = pos - i_idx;
a = buff_[i_idx];
b = buff_[(i_idx + 1) % buffer_size_];
return a + (b - a) * frac;
}
/** Write to a known location in the buffer */
inline void Write(size_t pos, float val) { buff_[pos] = val; }
/** Linear to Constpower approximation for windowing*/
float WindowVal(float in) { return sin(HALFPI_F * in); }
// Private Enums
/** Internal looper state */
enum class State
{
EMPTY,
REC_FIRST,
PLAYING,
REC_DUB,
};
/** Private Member Variables */
Mode mode_;
State state_;
float *buff_;
size_t buffer_size_;
float pos_, win_;
size_t win_idx_;
bool half_speed_;
bool reverse_;
size_t recsize_;
bool rec_queue_;
bool near_beginning_;
float increment_size;
};
} // namespace daisysp
+44
View File
@@ -0,0 +1,44 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_MAYTRIG_H
#define DSY_MAYTRIG_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Probabilistic trigger module
Original author(s) : Paul Batchelor
Ported from soundpipe by Ben Sergentanis, May 2020
*/
class Maytrig
{
public:
Maytrig() {}
~Maytrig() {}
/** probabilistically generates triggers
\param prob (1 always returns true, 0 always false)
\return given a probability 0 to 1, returns true or false.
*/
inline float Process(float prob)
{
return ((float)rand() / (float)RAND_MAX) <= prob ? true : false;
}
private:
};
} // namespace daisysp
#endif
#endif
+30
View File
@@ -0,0 +1,30 @@
#include <math.h>
#include "metro.h"
#include "dsp.h"
using namespace daisysp;
void Metro::Init(float freq, float sample_rate)
{
freq_ = freq;
phs_ = 0.0f;
sample_rate_ = sample_rate;
phs_inc_ = (TWOPI_F * freq_) / sample_rate_;
}
uint8_t Metro::Process()
{
phs_ += phs_inc_;
if(phs_ >= TWOPI_F)
{
phs_ -= TWOPI_F;
return 1;
}
return 0;
}
void Metro::SetFreq(float freq)
{
freq_ = freq;
phs_inc_ = (TWOPI_F * freq_) / sample_rate_;
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_METRO_H
#define DSY_METRO_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Creates a clock signal at a specific frequency.
*/
class Metro
{
public:
Metro() {}
~Metro() {}
/** Initializes Metro module.
Arguments:
- freq: frequency at which new clock signals will be generated
Input Range:
- sample_rate: sample rate of audio engine
Input range:
*/
void Init(float freq, float sample_rate);
/** checks current state of Metro object and updates state if necesary.
*/
uint8_t Process();
/** resets phase to 0
*/
inline void Reset() { phs_ = 0.0f; }
/** Sets frequency at which Metro module will run at.
*/
void SetFreq(float freq);
/** Returns current value for frequency.
*/
inline float GetFreq() { return freq_; }
private:
float freq_;
float phs_, sample_rate_, phs_inc_;
};
} // namespace daisysp
#endif
#endif
+69
View File
@@ -0,0 +1,69 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_SAMPLEHOLD_H
#define DSY_SAMPLEHOLD_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Dual track and hold / Sample and hold module. \n
Ported from soundpipe by Ben Sergentanis, June 2020.
@author Paul Batchelor
@date 2015
*/
class SampleHold
{
public:
SampleHold() {}
~SampleHold() {}
enum Mode
{
MODE_SAMPLE_HOLD,
MODE_TRACK_HOLD,
MODE_LAST,
};
/** Process the next sample. Both sample and track and hold are run in parallel
\param trigger Trigger the sample/track and hold
\param input Signal to be sampled/tracked and held
\param mode Whether to output the tracked or sampled values.
*/
inline float
Process(bool trigger, float input, Mode mode = MODE_SAMPLE_HOLD)
{
Update(trigger, input);
return mode == MODE_SAMPLE_HOLD ? sample_ : track_;
}
private:
float track_ = 0;
float sample_ = 0;
bool previous_ = false;
inline void Update(bool trigger, float input)
{
if(trigger)
{
if(!previous_)
{
sample_ = input;
}
track_ = input;
}
previous_ = trigger;
}
};
} // namespace daisysp
#endif
#endif
+85
View File
@@ -0,0 +1,85 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_SMOOTHRANDOM_H
#define DSY_SMOOTHRANDOM_H
#include "dsp.h"
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
/** @file smooth_random.h */
namespace daisysp
{
/**
@brief Smooth random generator for internal modulation. \n
@author Ported by Ben Sergentanis
@date Jan 2021
Ported from pichenettes/eurorack/plaits/dsp/noise/smooth_random_generator.h \n
to an independent module. \n
Original code written by Emilie Gillet in 2016. \n
*/
class SmoothRandomGenerator
{
public:
SmoothRandomGenerator() {}
~SmoothRandomGenerator() {}
/** Initialize the module
\param sample_rate Audio engine sample rate.
*/
void Init(float sample_rate)
{
sample_rate_ = sample_rate;
SetFreq(1.f);
phase_ = 0.0f;
from_ = 0.0f;
interval_ = 0.0f;
}
/** Get the next float. Ranges from -1 to 1. */
float Process()
{
phase_ += frequency_;
if(phase_ >= 1.0f)
{
phase_ -= 1.0f;
from_ += interval_;
interval_ = rand() * kRandFrac * 2.0f - 1.0f - from_;
}
float t = phase_ * phase_ * (3.0f - 2.0f * phase_);
return from_ + interval_ * t;
}
/** How often to slew to a new random value
\param freq Rate in Hz
*/
void SetFreq(float freq)
{
freq = freq / sample_rate_;
frequency_ = fclamp(freq, 0.f, 1.f);
}
private:
float frequency_;
float phase_;
float from_;
float interval_;
float sample_rate_;
static constexpr float kRandFrac = 1.f / (float)RAND_MAX;
};
} // namespace daisysp
#endif
#endif
+97
View File
@@ -0,0 +1,97 @@
// DaisySP is a DSP Library targeted at the Electrosmith Daisy Product Line.
// Author: Stephen Hensley, 2019
//
// However, this is decoupled from the hardware in such a way that it
// should be useful outside of the ARM context with different build configurations.
//
// A few general notes about the contents of the library:
// - all memory usage is static.
// - in cases of potentially large memory usage: the user will either supply a buffer and a size, or the class will be a template that can have size set at compile time.
// - all modules will have an Init() function, and a Process() function.
// - all modules, unless otherwise noted, will process a single sample at a time.
// - all processing will be done with 'float' type unless otherwise noted.
//
#pragma once
#ifndef DSYSP_H
#define DSYSP_H
/** Control Modules */
#include "Control/adenv.h"
#include "Control/adsr.h"
#include "Control/phasor.h"
/** Drum Modules */
#include "Drums/analogbassdrum.h"
#include "Drums/analogsnaredrum.h"
#include "Drums/hihat.h"
#include "Drums/synthbassdrum.h"
#include "Drums/synthsnaredrum.h"
/** Dynamics Modules */
#include "Dynamics/crossfade.h"
#include "Dynamics/limiter.h"
/** Effects Modules */
#include "Effects/autowah.h"
#include "Effects/chorus.h"
#include "Effects/decimator.h"
#include "Effects/flanger.h"
#include "Effects/overdrive.h"
#include "Effects/pitchshifter.h"
#include "Effects/phaser.h"
#include "Effects/sampleratereducer.h"
#include "Effects/tremolo.h"
#include "Effects/wavefolder.h"
/** Filter Modules */
#include "Filters/ladder.h"
#include "Filters/onepole.h"
#include "Filters/svf.h"
#include "Filters/fir.h"
#include "Filters/soap.h"
/** Noise Modules */
#include "Noise/clockednoise.h"
#include "Noise/dust.h"
#include "Noise/fractal_noise.h"
#include "Noise/grainlet.h"
#include "Noise/particle.h"
#include "Noise/whitenoise.h"
/** Physical Modeling Modules */
#include "PhysicalModeling/drip.h"
#include "PhysicalModeling/KarplusString.h"
#include "PhysicalModeling/modalvoice.h"
#include "PhysicalModeling/resonator.h"
#include "PhysicalModeling/stringvoice.h"
/** Sampling Modules */
#include "Sampling/granularplayer.h"
/** Synthesis Modules */
#include "Synthesis/fm2.h"
#include "Synthesis/formantosc.h"
#include "Synthesis/harmonic_osc.h"
#include "Synthesis/oscillator.h"
#include "Synthesis/oscillatorbank.h"
#include "Synthesis/variablesawosc.h"
#include "Synthesis/variableshapeosc.h"
#include "Synthesis/vosim.h"
#include "Synthesis/zoscillator.h"
/** Utility Modules */
#include "Utility/dcblock.h"
#include "Utility/delayline.h"
#include "Utility/dsp.h"
#include "Utility/looper.h"
#include "Utility/maytrig.h"
#include "Utility/metro.h"
#include "Utility/samplehold.h"
#include "Utility/smooth_random.h"
/** LGPL Modules */
#ifdef USE_DAISYSP_LGPL
#include "daisysp-lgpl.h"
#endif
#endif
+4
View File
@@ -9,6 +9,10 @@
#include "hardware/pwm.h"
#include "hardware/adc.h"
#include "state.h"
#include "daisysp.h"
daisysp::Oscillator osc;
daisysp::Svf filter;
state_t state;
+2 -2
View File
@@ -9,11 +9,11 @@ void set_mux_addr(uint8_t addr) {
}
float read_value_from_mux() {
return ;
return 0.0f;
}
void update_inputs() {
for(uint8_t i = 0; i < 2; i++) {
}
}
}
+12 -1
View File
@@ -1,4 +1,15 @@
#include "const.h"
#include "state.h"
#include "vco.h"
float phase = 0.0f;
float vco(void) {
float phase_inc = (state.vco_freq * 1760) / SAMPLE_RATE;
phase += phase_inc;
float vco(void);
if (phase >= 1.0f) phase -= 1.0f;
return (phase * 2.0f) - 1.0f;
}