Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f70cf0a3d | |||
| d687c2e24c | |||
| dd63b3aed4 |
+2
-1
@@ -25,7 +25,7 @@ pico_sdk_init()
|
|||||||
|
|
||||||
# Add executable. Default name is the project name, version 0.1
|
# 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_name(sint-gauntlet "sint-gauntlet")
|
||||||
pico_set_program_version(sint-gauntlet "0.1")
|
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
|
# Add the standard include files to the build
|
||||||
target_include_directories(sint-gauntlet PRIVATE
|
target_include_directories(sint-gauntlet PRIVATE
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/daisysp
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
|
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
#include "synthbassdrum.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
using namespace daisysp;
|
||||||
|
|
||||||
|
void SyntheticBassDrumClick::Init(float sample_rate)
|
||||||
|
{
|
||||||
|
lp_ = 0.0f;
|
||||||
|
hp_ = 0.0f;
|
||||||
|
filter_.Init(sample_rate);
|
||||||
|
filter_.SetFreq(5000.0f);
|
||||||
|
filter_.SetRes(1.f); //2.f
|
||||||
|
}
|
||||||
|
|
||||||
|
float SyntheticBassDrumClick::Process(float in)
|
||||||
|
{
|
||||||
|
//SLOPE(lp_, in, 0.5f, 0.1f);
|
||||||
|
float error = in - lp_;
|
||||||
|
lp_ += (error > 0 ? .5f : .1f) * error;
|
||||||
|
|
||||||
|
fonepole(hp_, lp_, 0.04f);
|
||||||
|
filter_.Process(lp_ - hp_);
|
||||||
|
return filter_.Low();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrumAttackNoise::Init()
|
||||||
|
{
|
||||||
|
lp_ = 0.0f;
|
||||||
|
hp_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SyntheticBassDrumAttackNoise::Process()
|
||||||
|
{
|
||||||
|
float sample = rand() * kRandFrac;
|
||||||
|
fonepole(lp_, sample, 0.05f);
|
||||||
|
fonepole(hp_, lp_, 0.005f);
|
||||||
|
return lp_ - hp_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::Init(float sample_rate)
|
||||||
|
{
|
||||||
|
sample_rate_ = sample_rate;
|
||||||
|
|
||||||
|
trig_ = false;
|
||||||
|
|
||||||
|
phase_ = 0.0f;
|
||||||
|
phase_noise_ = 0.0f;
|
||||||
|
f0_ = 0.0f;
|
||||||
|
fm_ = 0.0f;
|
||||||
|
fm_lp_ = 0.0f;
|
||||||
|
body_env_lp_ = 0.0f;
|
||||||
|
body_env_ = 0.0f;
|
||||||
|
body_env_pulse_width_ = 0;
|
||||||
|
fm_pulse_width_ = 0;
|
||||||
|
tone_lp_ = 0.0f;
|
||||||
|
sustain_gain_ = 0.0f;
|
||||||
|
|
||||||
|
SetFreq(100.f);
|
||||||
|
SetSustain(false);
|
||||||
|
SetAccent(.2f);
|
||||||
|
SetTone(.6f);
|
||||||
|
SetDecay(.7f);
|
||||||
|
SetDirtiness(.3f);
|
||||||
|
SetFmEnvelopeAmount(.6);
|
||||||
|
SetFmEnvelopeDecay(.3);
|
||||||
|
|
||||||
|
click_.Init(sample_rate);
|
||||||
|
noise_.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float SyntheticBassDrum::DistortedSine(float phase,
|
||||||
|
float phase_noise,
|
||||||
|
float dirtiness)
|
||||||
|
{
|
||||||
|
phase += phase_noise * dirtiness;
|
||||||
|
|
||||||
|
//MAKE_INTEGRAL_FRACTIONAL(phase);
|
||||||
|
int32_t phase_integral = static_cast<int32_t>(phase);
|
||||||
|
float phase_fractional = phase - static_cast<float>(phase_integral);
|
||||||
|
|
||||||
|
phase = phase_fractional;
|
||||||
|
float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.0f;
|
||||||
|
float sine = 2.0f * triangle / (1.0f + fabsf(triangle));
|
||||||
|
float clean_sine = sinf(TWOPI_F * (phase + 0.75f));
|
||||||
|
return sine + (1.0f - dirtiness) * (clean_sine - sine);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float SyntheticBassDrum::TransistorVCA(float s, float gain)
|
||||||
|
{
|
||||||
|
s = (s - 0.6f) * gain;
|
||||||
|
return 3.0f * s / (2.0f + fabsf(s)) + gain * 0.3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SyntheticBassDrum::Process(bool trigger)
|
||||||
|
{
|
||||||
|
float dirtiness = dirtiness_;
|
||||||
|
dirtiness *= fmax(1.0f - 8.0f * new_f0_, 0.0f);
|
||||||
|
|
||||||
|
const float fm_decay
|
||||||
|
= 1.0f
|
||||||
|
- 1.0f / (0.008f * (1.0f + fm_envelope_decay_ * 4.0f) * sample_rate_);
|
||||||
|
|
||||||
|
const float body_env_decay
|
||||||
|
= 1.0f
|
||||||
|
- 1.0f / (0.02f * sample_rate_)
|
||||||
|
* powf(2.f, (-decay_ * 60.0f) * kOneTwelfth);
|
||||||
|
const float transient_env_decay = 1.0f - 1.0f / (0.005f * sample_rate_);
|
||||||
|
const float tone_f = fmin(
|
||||||
|
4.0f * new_f0_ * powf(2.f, (tone_ * 108.0f) * kOneTwelfth), 1.0f);
|
||||||
|
const float transient_level = tone_;
|
||||||
|
|
||||||
|
if(trigger || trig_)
|
||||||
|
{
|
||||||
|
trig_ = false;
|
||||||
|
fm_ = 1.0f;
|
||||||
|
body_env_ = transient_env_ = 0.3f + 0.7f * accent_;
|
||||||
|
body_env_pulse_width_ = sample_rate_ * 0.001f;
|
||||||
|
fm_pulse_width_ = sample_rate_ * 0.0013f;
|
||||||
|
}
|
||||||
|
|
||||||
|
sustain_gain_ = accent_ * decay_;
|
||||||
|
|
||||||
|
fonepole(phase_noise_, rand() * kRandFrac - 0.5f, 0.002f);
|
||||||
|
|
||||||
|
float mix = 0.0f;
|
||||||
|
|
||||||
|
if(sustain_)
|
||||||
|
{
|
||||||
|
f0_ = new_f0_;
|
||||||
|
phase_ += f0_;
|
||||||
|
if(phase_ >= 1.0f)
|
||||||
|
{
|
||||||
|
phase_ -= 1.0f;
|
||||||
|
}
|
||||||
|
float body = DistortedSine(phase_, phase_noise_, dirtiness);
|
||||||
|
mix -= TransistorVCA(body, sustain_gain_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(fm_pulse_width_)
|
||||||
|
{
|
||||||
|
--fm_pulse_width_;
|
||||||
|
phase_ = 0.25f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fm_ *= fm_decay;
|
||||||
|
float fm = 1.0f + fm_envelope_amount_ * 3.5f * fm_lp_;
|
||||||
|
f0_ = new_f0_;
|
||||||
|
phase_ += fmin(f0_ * fm, 0.5f);
|
||||||
|
if(phase_ >= 1.0f)
|
||||||
|
{
|
||||||
|
phase_ -= 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(body_env_pulse_width_)
|
||||||
|
{
|
||||||
|
--body_env_pulse_width_;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
body_env_ *= body_env_decay;
|
||||||
|
transient_env_ *= transient_env_decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float envelope_lp_f = 0.1f;
|
||||||
|
fonepole(body_env_lp_, body_env_, envelope_lp_f);
|
||||||
|
fonepole(transient_env_lp_, transient_env_, envelope_lp_f);
|
||||||
|
fonepole(fm_lp_, fm_, envelope_lp_f);
|
||||||
|
|
||||||
|
float body = DistortedSine(phase_, phase_noise_, dirtiness);
|
||||||
|
float transient = click_.Process(body_env_pulse_width_ ? 0.0f : 1.0f)
|
||||||
|
+ noise_.Process();
|
||||||
|
|
||||||
|
mix -= TransistorVCA(body, body_env_lp_);
|
||||||
|
mix -= transient * transient_env_lp_ * transient_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
fonepole(tone_lp_, mix, tone_f);
|
||||||
|
return tone_lp_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::Trig()
|
||||||
|
{
|
||||||
|
trig_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetSustain(bool sustain)
|
||||||
|
{
|
||||||
|
sustain_ = sustain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetAccent(float accent)
|
||||||
|
{
|
||||||
|
accent_ = fclamp(accent, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetFreq(float freq)
|
||||||
|
{
|
||||||
|
freq /= sample_rate_;
|
||||||
|
new_f0_ = fclamp(freq, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetTone(float tone)
|
||||||
|
{
|
||||||
|
tone_ = fclamp(tone, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetDecay(float decay)
|
||||||
|
{
|
||||||
|
decay = fclamp(decay, 0.f, 1.f);
|
||||||
|
decay_ = decay * decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetDirtiness(float dirtiness)
|
||||||
|
{
|
||||||
|
dirtiness_ = fclamp(dirtiness, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetFmEnvelopeAmount(float fm_envelope_amount)
|
||||||
|
{
|
||||||
|
fm_envelope_amount_ = fclamp(fm_envelope_amount, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyntheticBassDrum::SetFmEnvelopeDecay(float fm_envelope_decay)
|
||||||
|
{
|
||||||
|
fm_envelope_decay = fclamp(fm_envelope_decay, 0.f, 1.f);
|
||||||
|
fm_envelope_decay_ = fm_envelope_decay * fm_envelope_decay;
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#include "autowah.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using namespace daisysp;
|
||||||
|
|
||||||
|
|
||||||
|
void Autowah::Init(float sample_rate)
|
||||||
|
{
|
||||||
|
sampling_freq_ = sample_rate;
|
||||||
|
const1_ = 1413.72f / sampling_freq_;
|
||||||
|
const2_ = expf(0.0f - (100.0f / sampling_freq_));
|
||||||
|
const4_ = expf(0.0f - (10.0f / sampling_freq_));
|
||||||
|
|
||||||
|
wet_dry_ = 100.0f;
|
||||||
|
level_ = 0.1f;
|
||||||
|
wah_ = 0.0;
|
||||||
|
|
||||||
|
|
||||||
|
for(int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
rec1_[i] = rec2_[i] = rec3_[i] = rec4_[i] = rec5_[i] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
rec0_[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Autowah::Process(float in)
|
||||||
|
{
|
||||||
|
float out;
|
||||||
|
float fSlow2 = (0.01f * (wet_dry_ * level_));
|
||||||
|
float fSlow3 = (1.0f - 0.01f * wet_dry_) + (1.f - wah_);
|
||||||
|
|
||||||
|
float fTemp1 = fabs(in);
|
||||||
|
rec3_[0]
|
||||||
|
= fmaxf(fTemp1, (const4_ * rec3_[1]) + ((1.0f - const4_) * fTemp1));
|
||||||
|
rec2_[0] = (const2_ * rec2_[1]) + ((1.0f - const2_) * rec3_[0]);
|
||||||
|
float fTemp2 = fminf(1.0f, rec2_[0]);
|
||||||
|
float fTemp3 = powf(2.0f, (2.3f * fTemp2));
|
||||||
|
float fTemp4
|
||||||
|
= 1.0f
|
||||||
|
- (const1_ * fTemp3 / powf(2.0f, (1.0f + 2.0f * (1.0f - fTemp2))));
|
||||||
|
rec1_[0]
|
||||||
|
= ((0.999f * rec1_[1])
|
||||||
|
+ (0.001f
|
||||||
|
* (0.0f - (2.0f * (fTemp4 * cosf((const1_ * 2 * fTemp3)))))));
|
||||||
|
rec4_[0] = ((0.999f * rec4_[1]) + (0.001f * fTemp4 * fTemp4));
|
||||||
|
rec5_[0] = ((0.999f * rec5_[1]) + (0.0001f * powf(4.0f, fTemp2)));
|
||||||
|
rec0_[0] = (0.0f
|
||||||
|
- (((rec1_[0] * rec0_[1]) + (rec4_[0] * rec0_[2]))
|
||||||
|
- (fSlow2 * (rec5_[0] * in))));
|
||||||
|
|
||||||
|
out = ((wah_ * (rec0_[0] - rec0_[1])) + (fSlow3 * in));
|
||||||
|
rec3_[1] = rec3_[0];
|
||||||
|
rec2_[1] = rec2_[0];
|
||||||
|
rec1_[1] = rec1_[0];
|
||||||
|
rec4_[1] = rec4_[0];
|
||||||
|
rec5_[1] = rec5_[0];
|
||||||
|
rec0_[2] = rec0_[1];
|
||||||
|
rec0_[1] = rec0_[0];
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include "tremolo.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using namespace daisysp;
|
||||||
|
|
||||||
|
void Tremolo::Init(float sample_rate)
|
||||||
|
{
|
||||||
|
sample_rate_ = sample_rate;
|
||||||
|
|
||||||
|
osc_.Init(sample_rate_);
|
||||||
|
SetDepth(1.f);
|
||||||
|
SetFreq(1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Tremolo::Process(float in)
|
||||||
|
{
|
||||||
|
float modsig = dc_os_ + osc_.Process();
|
||||||
|
return in * modsig;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tremolo::SetFreq(float freq)
|
||||||
|
{
|
||||||
|
osc_.SetFreq(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tremolo::SetWaveform(int waveform)
|
||||||
|
{
|
||||||
|
osc_.SetWaveform(waveform);
|
||||||
|
}
|
||||||
|
void Tremolo::SetDepth(float depth)
|
||||||
|
{
|
||||||
|
depth = fclamp(depth, 0.f, 1.f);
|
||||||
|
depth *= .5f;
|
||||||
|
osc_.SetAmp(depth);
|
||||||
|
dc_os_ = 1.f - depth;
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef DSY_PARTICLE_H
|
||||||
|
#define DSY_PARTICLE_H
|
||||||
|
|
||||||
|
#include "Filters/svf.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
/** @file particle.h */
|
||||||
|
|
||||||
|
namespace daisysp
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
@brief Random impulse train processed by a resonant filter.
|
||||||
|
@author Ported by Ben Sergentanis
|
||||||
|
@date Jan 2021
|
||||||
|
Noise processed by a sample and hold running at a target frequency. \n \n
|
||||||
|
Ported from pichenettes/eurorack/plaits/dsp/noise/particle.h \n
|
||||||
|
to an independent module. \n
|
||||||
|
Original code written by Emilie Gillet in 2016. \n
|
||||||
|
*/
|
||||||
|
class Particle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Particle() {}
|
||||||
|
~Particle() {}
|
||||||
|
|
||||||
|
/** Initialize the module
|
||||||
|
\param sample_rate Audio engine sample rate.
|
||||||
|
*/
|
||||||
|
void Init(float sample_rate);
|
||||||
|
|
||||||
|
/** Get the next sample */
|
||||||
|
float Process();
|
||||||
|
|
||||||
|
/** Get the raw noise output. Must call Process() first. */
|
||||||
|
float GetNoise();
|
||||||
|
|
||||||
|
/** Set the resonant filter frequency
|
||||||
|
\param freq Frequency in Hz
|
||||||
|
*/
|
||||||
|
void SetFreq(float frequency);
|
||||||
|
|
||||||
|
/** Set the filter resonance
|
||||||
|
\param resonance Works 0-1
|
||||||
|
*/
|
||||||
|
void SetResonance(float resonance);
|
||||||
|
|
||||||
|
/** How often to randomize filter frequency
|
||||||
|
\param freq Frequency in Hz.
|
||||||
|
*/
|
||||||
|
void SetRandomFreq(float freq);
|
||||||
|
|
||||||
|
/** Noise density
|
||||||
|
\param Works 0-1.
|
||||||
|
*/
|
||||||
|
void SetDensity(float density);
|
||||||
|
|
||||||
|
/** Overall module gain
|
||||||
|
\param Works 0-1.
|
||||||
|
*/
|
||||||
|
void SetGain(float gain);
|
||||||
|
|
||||||
|
/** How much to randomize the set filter frequency.
|
||||||
|
\param spread Works over positive numbers.
|
||||||
|
*/
|
||||||
|
void SetSpread(float spread);
|
||||||
|
|
||||||
|
/** Force randomize the frequency.
|
||||||
|
\param sync True to randomize freq.
|
||||||
|
*/
|
||||||
|
void SetSync(bool sync);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr float kRandFrac = 1.f / (float)RAND_MAX;
|
||||||
|
static constexpr float kRatioFrac = 1.f / 12.f;
|
||||||
|
float sample_rate_;
|
||||||
|
float aux_, frequency_, density_, gain_, spread_, resonance_;
|
||||||
|
bool sync_;
|
||||||
|
|
||||||
|
|
||||||
|
float rand_phase_;
|
||||||
|
float rand_freq_;
|
||||||
|
|
||||||
|
|
||||||
|
float pre_gain_;
|
||||||
|
Svf filter_;
|
||||||
|
};
|
||||||
|
} // namespace daisysp
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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]);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#include "modalvoice.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace daisysp;
|
||||||
|
|
||||||
|
void ModalVoice::Init(float sample_rate)
|
||||||
|
{
|
||||||
|
sample_rate_ = sample_rate;
|
||||||
|
aux_ = 0.f;
|
||||||
|
|
||||||
|
excitation_filter_.Init();
|
||||||
|
resonator_.Init(0.015f, 24, sample_rate_);
|
||||||
|
excitation_filter_.Init();
|
||||||
|
dust_.Init();
|
||||||
|
|
||||||
|
SetSustain(false);
|
||||||
|
SetFreq(440.f);
|
||||||
|
SetAccent(.3f);
|
||||||
|
SetStructure(.6f);
|
||||||
|
SetBrightness(.8f);
|
||||||
|
SetDamping(.6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::SetSustain(bool sustain)
|
||||||
|
{
|
||||||
|
sustain_ = sustain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::Trig()
|
||||||
|
{
|
||||||
|
trig_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::SetFreq(float freq)
|
||||||
|
{
|
||||||
|
resonator_.SetFreq(freq);
|
||||||
|
f0_ = freq / sample_rate_;
|
||||||
|
f0_ = fclamp(f0_, 0.f, .25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::SetAccent(float accent)
|
||||||
|
{
|
||||||
|
accent_ = fclamp(accent, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::SetStructure(float structure)
|
||||||
|
{
|
||||||
|
resonator_.SetStructure(structure);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::SetBrightness(float brightness)
|
||||||
|
{
|
||||||
|
brightness_ = fclamp(brightness, 0.f, 1.f);
|
||||||
|
density_ = brightness_ * brightness_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModalVoice::SetDamping(float damping)
|
||||||
|
{
|
||||||
|
damping_ = fclamp(damping, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ModalVoice::GetAux()
|
||||||
|
{
|
||||||
|
return aux_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ModalVoice::Process(bool trigger)
|
||||||
|
{
|
||||||
|
float brightness = brightness_ + 0.25f * accent_ * (1.0f - brightness_);
|
||||||
|
float damping = damping_ + 0.25f * accent_ * (1.0f - damping_);
|
||||||
|
|
||||||
|
const float range = sustain_ ? 36.0f : 60.0f;
|
||||||
|
const float f = sustain_ ? 4.0f * f0_ : 2.0f * f0_;
|
||||||
|
const float cutoff = fmin(
|
||||||
|
f
|
||||||
|
* powf(2.f,
|
||||||
|
kOneTwelfth
|
||||||
|
* ((brightness * (2.0f - brightness) - 0.5f) * range)),
|
||||||
|
0.499f);
|
||||||
|
const float q = sustain_ ? 0.7f : 1.5f;
|
||||||
|
|
||||||
|
float temp = 0.f;
|
||||||
|
// Synthesize excitation signal.
|
||||||
|
if(sustain_)
|
||||||
|
{
|
||||||
|
const float dust_f = 0.00005f + 0.99995f * density_ * density_;
|
||||||
|
dust_.SetDensity(dust_f);
|
||||||
|
temp = dust_.Process() * (4.0f - dust_f * 3.0f) * accent_;
|
||||||
|
}
|
||||||
|
else if(trigger || trig_)
|
||||||
|
{
|
||||||
|
const float attenuation = 1.0f - damping * 0.5f;
|
||||||
|
const float amplitude = (0.12f + 0.08f * accent_) * attenuation;
|
||||||
|
temp = amplitude * powf(2.f, kOneTwelfth * (cutoff * cutoff * 24.0f))
|
||||||
|
/ cutoff;
|
||||||
|
trig_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float one = 1.0f;
|
||||||
|
excitation_filter_.Process<ResonatorSvf<1>::LOW_PASS, false>(
|
||||||
|
&cutoff, &q, &one, temp, &temp);
|
||||||
|
|
||||||
|
aux_ = temp;
|
||||||
|
|
||||||
|
resonator_.SetBrightness(brightness);
|
||||||
|
resonator_.SetDamping(damping);
|
||||||
|
|
||||||
|
return resonator_.Process(temp);
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
#include "resonator.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using namespace daisysp;
|
||||||
|
|
||||||
|
void Resonator::Init(float position, int resolution, float sample_rate)
|
||||||
|
{
|
||||||
|
sample_rate_ = sample_rate;
|
||||||
|
|
||||||
|
SetFreq(440.f);
|
||||||
|
SetStructure(.5f);
|
||||||
|
SetBrightness(.5f);
|
||||||
|
SetDamping(.5f);
|
||||||
|
|
||||||
|
resolution_ = fmin(resolution, kMaxNumModes);
|
||||||
|
|
||||||
|
for(int i = 0; i < resolution; ++i)
|
||||||
|
{
|
||||||
|
mode_amplitude_[i] = cos(position * TWOPI_F) * 0.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < kMaxNumModes / kModeBatchSize; ++i)
|
||||||
|
{
|
||||||
|
mode_filters_[i].Init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float NthHarmonicCompensation(int n, float stiffness)
|
||||||
|
{
|
||||||
|
float stretch_factor = 1.0f;
|
||||||
|
for(int i = 0; i < n - 1; ++i)
|
||||||
|
{
|
||||||
|
stretch_factor += stiffness;
|
||||||
|
if(stiffness < 0.0f)
|
||||||
|
{
|
||||||
|
stiffness *= 0.93f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stiffness *= 0.98f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0f / stretch_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Resonator::Process(const float in)
|
||||||
|
{
|
||||||
|
//convert Hz to cycles / sample
|
||||||
|
float out = 0.f;
|
||||||
|
|
||||||
|
float stiffness = CalcStiff(structure_);
|
||||||
|
float f0 = frequency_ * NthHarmonicCompensation(3, stiffness);
|
||||||
|
float brightness = brightness_;
|
||||||
|
|
||||||
|
float harmonic = f0;
|
||||||
|
float stretch_factor = 1.0f;
|
||||||
|
|
||||||
|
float input = damping_ * 79.7f;
|
||||||
|
float q_sqrt = powf(2.f, input * ratiofrac_);
|
||||||
|
|
||||||
|
float q = 500.0f * q_sqrt * q_sqrt;
|
||||||
|
brightness *= 1.0f - structure_ * 0.3f;
|
||||||
|
brightness *= 1.0f - damping_ * 0.3f;
|
||||||
|
float q_loss = brightness * (2.0f - brightness) * 0.85f + 0.15f;
|
||||||
|
|
||||||
|
float mode_q[kModeBatchSize];
|
||||||
|
float mode_f[kModeBatchSize];
|
||||||
|
float mode_a[kModeBatchSize];
|
||||||
|
int batch_counter = 0;
|
||||||
|
|
||||||
|
ResonatorSvf<kModeBatchSize>* batch_processor = &mode_filters_[0];
|
||||||
|
|
||||||
|
for(int i = 0; i < resolution_; ++i)
|
||||||
|
{
|
||||||
|
float mode_frequency = harmonic * stretch_factor;
|
||||||
|
if(mode_frequency >= 0.499f)
|
||||||
|
{
|
||||||
|
mode_frequency = 0.499f;
|
||||||
|
}
|
||||||
|
const float mode_attenuation = 1.0f - mode_frequency * 2.0f;
|
||||||
|
|
||||||
|
mode_f[batch_counter] = mode_frequency;
|
||||||
|
mode_q[batch_counter] = 1.0f + mode_frequency * q;
|
||||||
|
mode_a[batch_counter] = mode_amplitude_[i] * mode_attenuation;
|
||||||
|
++batch_counter;
|
||||||
|
|
||||||
|
if(batch_counter == kModeBatchSize)
|
||||||
|
{
|
||||||
|
batch_counter = 0;
|
||||||
|
batch_processor
|
||||||
|
->Process<ResonatorSvf<kModeBatchSize>::BAND_PASS, true>(
|
||||||
|
mode_f, mode_q, mode_a, in, &out);
|
||||||
|
++batch_processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
stretch_factor += stiffness;
|
||||||
|
if(stiffness < 0.0f)
|
||||||
|
{
|
||||||
|
// Make sure that the partials do not fold back into negative frequencies.
|
||||||
|
stiffness *= 0.93f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This helps adding a few extra partials in the highest frequencies.
|
||||||
|
stiffness *= 0.98f;
|
||||||
|
}
|
||||||
|
harmonic += f0;
|
||||||
|
q *= q_loss;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resonator::SetFreq(float freq)
|
||||||
|
{
|
||||||
|
frequency_ = freq / sample_rate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resonator::SetStructure(float structure)
|
||||||
|
{
|
||||||
|
structure_ = fmax(fmin(structure, 1.f), 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resonator::SetBrightness(float brightness)
|
||||||
|
{
|
||||||
|
brightness_ = fmax(fmin(brightness, 1.f), 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resonator::SetDamping(float damping)
|
||||||
|
{
|
||||||
|
damping_ = fmax(fmin(damping, 1.f), 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Resonator::CalcStiff(float sig)
|
||||||
|
{
|
||||||
|
if(sig < .25f)
|
||||||
|
{
|
||||||
|
sig = .25 - sig;
|
||||||
|
sig = -sig * .25;
|
||||||
|
}
|
||||||
|
else if(sig < .3f)
|
||||||
|
{
|
||||||
|
sig = 0.f;
|
||||||
|
}
|
||||||
|
else if(sig < .9f)
|
||||||
|
{
|
||||||
|
sig -= .3f;
|
||||||
|
sig *= stiff_frac_2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sig -= .9f;
|
||||||
|
sig *= 10; // div by .1
|
||||||
|
sig *= sig;
|
||||||
|
sig = 1.5 - cos(sig * PI_F) * .5f;
|
||||||
|
}
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#include "fm2.h"
|
||||||
|
|
||||||
|
using namespace daisysp;
|
||||||
|
|
||||||
|
void Fm2::Init(float samplerate)
|
||||||
|
{
|
||||||
|
//init oscillators
|
||||||
|
car_.Init(samplerate);
|
||||||
|
mod_.Init(samplerate);
|
||||||
|
|
||||||
|
//set some reasonable values
|
||||||
|
lfreq_ = 440.f;
|
||||||
|
lratio_ = 2.f;
|
||||||
|
SetFrequency(lfreq_);
|
||||||
|
SetRatio(lratio_);
|
||||||
|
|
||||||
|
car_.SetAmp(1.f);
|
||||||
|
mod_.SetAmp(1.f);
|
||||||
|
|
||||||
|
car_.SetWaveform(Oscillator::WAVE_SIN);
|
||||||
|
mod_.SetWaveform(Oscillator::WAVE_SIN);
|
||||||
|
|
||||||
|
idx_ = 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Fm2::Process()
|
||||||
|
{
|
||||||
|
if(lratio_ != ratio_ || lfreq_ != freq_)
|
||||||
|
{
|
||||||
|
lratio_ = ratio_;
|
||||||
|
lfreq_ = freq_;
|
||||||
|
car_.SetFreq(lfreq_);
|
||||||
|
mod_.SetFreq(lfreq_ * lratio_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float modval = mod_.Process();
|
||||||
|
car_.PhaseAdd(modval * idx_);
|
||||||
|
return car_.Process();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fm2::SetFrequency(float freq)
|
||||||
|
{
|
||||||
|
freq_ = fabsf(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fm2::SetRatio(float ratio)
|
||||||
|
{
|
||||||
|
ratio_ = fabsf(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fm2::SetIndex(float index)
|
||||||
|
{
|
||||||
|
idx_ = index * kIdxScalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Fm2::GetIndex()
|
||||||
|
{
|
||||||
|
return idx_ * kIdxScalarRecip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fm2::Reset()
|
||||||
|
{
|
||||||
|
car_.Reset();
|
||||||
|
mod_.Reset();
|
||||||
|
}
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
#include "hardware/pwm.h"
|
#include "hardware/pwm.h"
|
||||||
#include "hardware/adc.h"
|
#include "hardware/adc.h"
|
||||||
#include "state.h"
|
#include "state.h"
|
||||||
|
#include "daisysp.h"
|
||||||
|
|
||||||
|
daisysp::Oscillator osc;
|
||||||
|
daisysp::Svf filter;
|
||||||
|
|
||||||
state_t state;
|
state_t state;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ void set_mux_addr(uint8_t addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float read_value_from_mux() {
|
float read_value_from_mux() {
|
||||||
return ;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_inputs() {
|
void update_inputs() {
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
|
#include "const.h"
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
|
#include "vco.h"
|
||||||
|
|
||||||
float phase = 0.0f;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user