Added DaisySP
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user