Added DaisySP

This commit is contained in:
2026-04-24 14:46:05 +02:00
parent 82190216c3
commit dd63b3aed4
96 changed files with 10613 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
#include <algorithm>
#include <limits>
#include <math.h>
#include "adenv.h"
using namespace daisysp;
//#define EXPF expf
// This causes with infinity with certain curves,
// which then causes NaN erros...
#define EXPF expf_fast
// To resolve annoying bugs when using this you can:
// if (val != val)
// val = 0.0f; // This will un-NaN the value.
// Fast Exp approximation
// 8x multiply version
//inline float expf_fast(float x)
//{
// x = 1.0f + x / 256.0f;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// x *= x;
// return x;
//}
// 10x multiply version
inline float expf_fast(float x)
{
x = 1.0f + x / 1024.0f;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
x *= x;
return x;
}
// Private Functions
void AdEnv::Init(float sample_rate)
{
sample_rate_ = sample_rate;
current_segment_ = ADENV_SEG_IDLE;
curve_scalar_ = 0.0f; // full linear
phase_ = 0;
min_ = 0.0f;
max_ = 1.0f;
output_ = 0.0001f;
for(uint8_t i = 0; i < ADENV_SEG_LAST; i++)
{
segment_time_[i] = 0.05f;
}
}
float AdEnv::Process()
{
uint32_t time_samps;
float val, out, end, beg, inc;
// Handle Retriggering
if(trigger_)
{
trigger_ = 0;
current_segment_ = ADENV_SEG_ATTACK;
phase_ = 0;
curve_x_ = 0.0f;
retrig_val_ = output_;
}
time_samps = (uint32_t)(segment_time_[current_segment_] * sample_rate_);
// Fixed for now, but we could always make this a more flexible multi-segment envelope
switch(current_segment_)
{
case ADENV_SEG_ATTACK:
beg = retrig_val_;
end = 1.0f;
break;
case ADENV_SEG_DECAY:
beg = 1.0f;
end = 0.0f;
break;
case ADENV_SEG_IDLE:
default:
beg = 0;
end = 0;
break;
}
if(prev_segment_ != current_segment_)
{
//Reset at segment beginning
curve_x_ = 0;
phase_ = 0;
}
//recalculate increment value
if(curve_scalar_ == 0.0f)
{
c_inc_ = (end - beg) / time_samps;
}
else
{
c_inc_ = (end - beg) / (1.0f - EXPF(curve_scalar_));
}
if(c_inc_ >= 0.0f)
{
c_inc_ = std::max(c_inc_, std::numeric_limits<float>::epsilon());
}
else
{
c_inc_ = std::min(c_inc_, -std::numeric_limits<float>::epsilon());
}
// update output
val = output_;
inc = c_inc_;
out = val;
if(curve_scalar_ == 0.0f)
{
val += inc;
}
else
{
curve_x_ += (curve_scalar_ / time_samps);
val = beg + inc * (1.0f - EXPF(curve_x_));
if(val != val)
val = 0.0f; // NaN check
}
// Update Segment
phase_ += 1;
prev_segment_ = current_segment_;
if(current_segment_ != ADENV_SEG_IDLE)
{
if((out >= 1.f && current_segment_ == ADENV_SEG_ATTACK)
|| (out <= 0.f && current_segment_ == ADENV_SEG_DECAY))
{
// Advance segment
current_segment_++;
// TODO: Add Cycling feature here.
if(current_segment_ > ADENV_SEG_DECAY)
{
current_segment_ = ADENV_SEG_IDLE;
}
}
}
if(current_segment_ == ADENV_SEG_IDLE)
{
val = out = 0.0f;
}
output_ = val;
return out * (max_ - min_) + min_;
}
+100
View File
@@ -0,0 +1,100 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef ADENV_H
#define ADENV_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Distinct stages that the phase of the envelope can be located in.
@see AdEnv
*/
enum AdEnvSegment
{
/** located at phase location 0, and not currently running */
ADENV_SEG_IDLE,
/** First segment of envelope where phase moves from MIN value to MAX value */
ADENV_SEG_ATTACK,
/** Second segment of envelope where phase moves from MAX to MIN value */
ADENV_SEG_DECAY,
/** The final segment of the envelope (currently decay) */
ADENV_SEG_LAST,
};
/** Trigger-able envelope with adjustable min/max, and independent per-segment time control.
\author shensley
\todo - Add Cycling
\todo - Implement Curve (its only linear for now).
\todo - Maybe make this an ADsr_ that has AD/AR/Asr_ modes.
*/
class AdEnv
{
public:
AdEnv() {}
~AdEnv() {}
/** Initializes the ad envelope.
Defaults:
- current segment = idle
- curve = linear
- phase = 0
- min = 0
- max = 1
\param sample_rate sample rate of the audio engine being run
*/
void Init(float sample_rate);
/** Processes the current sample of the envelope. This should be called once
per sample period.
\return the current envelope value.
*/
float Process();
/** Starts or retriggers the envelope.*/
inline void Trigger() { trigger_ = 1; }
/** Sets the length of time (in seconds) for a specific segment. */
inline void SetTime(uint8_t seg, float time) { segment_time_[seg] = time; }
/** Sets the amount of curve applied. A positve value will create a log
curve. Input range: -100 to 100. (or more)
*/
inline void SetCurve(float scalar) { curve_scalar_ = scalar; }
/** Sets the minimum value of the envelope output.
Input range: -FLTmax_, to FLTmax_
*/
inline void SetMin(float min) { min_ = min; }
/** Sets the maximum value of the envelope output.
Input range: -FLTmax_, to FLTmax_
*/
inline void SetMax(float max) { max_ = max; }
/** Returns the current output value without processing the next sample */
inline float GetValue() const { return (output_ * (max_ - min_)) + min_; }
/** Returns the segment of the envelope that the phase is currently located
in.
*/
inline uint8_t GetCurrentSegment() { return current_segment_; }
/** Returns true if the envelope is currently in any stage apart from idle.
*/
inline bool IsRunning() const { return current_segment_ != ADENV_SEG_IDLE; }
private:
uint8_t current_segment_, prev_segment_;
float segment_time_[ADENV_SEG_LAST];
float sample_rate_, min_, max_, output_, curve_scalar_;
float c_inc_, curve_x_, retrig_val_;
uint32_t phase_;
uint8_t trigger_;
};
} // namespace daisysp
#endif
#endif
+136
View File
@@ -0,0 +1,136 @@
#include "adsr.h"
#include <math.h>
using namespace daisysp;
void Adsr::Init(float sample_rate, int blockSize)
{
sample_rate_ = sample_rate / blockSize;
attackShape_ = -1.f;
attackTarget_ = 0.0f;
attackTime_ = -1.f;
decayTime_ = -1.f;
releaseTime_ = -1.f;
sus_level_ = 0.7f;
x_ = 0.0f;
gate_ = false;
mode_ = ADSR_SEG_IDLE;
SetTime(ADSR_SEG_ATTACK, 0.1f);
SetTime(ADSR_SEG_DECAY, 0.1f);
SetTime(ADSR_SEG_RELEASE, 0.1f);
}
void Adsr::Retrigger(bool hard)
{
mode_ = ADSR_SEG_ATTACK;
if(hard)
x_ = 0.f;
}
void Adsr::SetTime(int seg, float time)
{
switch(seg)
{
case ADSR_SEG_ATTACK: SetAttackTime(time, 0.0f); break;
case ADSR_SEG_DECAY:
{
SetTimeConstant(time, decayTime_, decayD0_);
}
break;
case ADSR_SEG_RELEASE:
{
SetTimeConstant(time, releaseTime_, releaseD0_);
}
break;
default: return;
}
}
void Adsr::SetAttackTime(float timeInS, float shape)
{
if((timeInS != attackTime_) || (shape != attackShape_))
{
attackTime_ = timeInS;
attackShape_ = shape;
if(timeInS > 0.f)
{
float x = shape;
float target = 9.f * powf(x, 10.f) + 0.3f * x + 1.01f;
attackTarget_ = target;
float logTarget = logf(1.f - (1.f / target)); // -1 for decay
attackD0_ = 1.f - expf(logTarget / (timeInS * sample_rate_));
}
else
attackD0_ = 1.f; // instant change
}
}
void Adsr::SetDecayTime(float timeInS)
{
SetTimeConstant(timeInS, decayTime_, decayD0_);
}
void Adsr::SetReleaseTime(float timeInS)
{
SetTimeConstant(timeInS, releaseTime_, releaseD0_);
}
void Adsr::SetTimeConstant(float timeInS, float& time, float& coeff)
{
if(timeInS != time)
{
time = timeInS;
if(time > 0.f)
{
const float target = logf(1. / M_E);
coeff = 1.f - expf(target / (time * sample_rate_));
}
else
coeff = 1.f; // instant change
}
}
float Adsr::Process(bool gate)
{
float out = 0.0f;
if(gate && !gate_) // rising edge
mode_ = ADSR_SEG_ATTACK;
else if(!gate && gate_) // falling edge
mode_ = ADSR_SEG_RELEASE;
gate_ = gate;
float D0(attackD0_);
if(mode_ == ADSR_SEG_DECAY)
D0 = decayD0_;
else if(mode_ == ADSR_SEG_RELEASE)
D0 = releaseD0_;
float target = mode_ == ADSR_SEG_DECAY ? sus_level_ : -0.01f;
switch(mode_)
{
case ADSR_SEG_IDLE: out = 0.0f; break;
case ADSR_SEG_ATTACK:
x_ += D0 * (attackTarget_ - x_);
out = x_;
if(out > 1.f)
{
x_ = out = 1.f;
mode_ = ADSR_SEG_DECAY;
}
break;
case ADSR_SEG_DECAY:
case ADSR_SEG_RELEASE:
x_ += D0 * (target - x_);
out = x_;
if(out < 0.0f)
{
x_ = out = 0.f;
mode_ = ADSR_SEG_IDLE;
}
default: break;
}
return out;
}
+106
View File
@@ -0,0 +1,106 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Paul Batchelor
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_ADSR_H
#define DSY_ADSR_H
#include <stdint.h>
#ifdef __cplusplus
namespace daisysp
{
/** Distinct stages that the phase of the envelope can be located in.
- IDLE = located at phase location 0, and not currently running
- ATTACK = First segment of envelope where phase moves from 0 to 1
- DECAY = Second segment of envelope where phase moves from 1 to SUSTAIN value
- RELEASE = Fourth segment of envelop where phase moves from SUSTAIN to 0
*/
enum
{
ADSR_SEG_IDLE = 0,
ADSR_SEG_ATTACK = 1,
ADSR_SEG_DECAY = 2,
ADSR_SEG_RELEASE = 4
};
/** adsr envelope module
Original author(s) : Paul Batchelor
Ported from Soundpipe by Ben Sergentanis, May 2020
Remake by Steffan DIedrichsen, May 2021
*/
class Adsr
{
public:
Adsr() {}
~Adsr() {}
/** Initializes the Adsr module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate, int blockSize = 1);
/**
\function Retrigger forces the envelope back to attack phase
\param hard resets the history to zero, results in a click.
*/
void Retrigger(bool hard);
/** Processes one sample through the filter and returns one sample.
\param gate - trigger the envelope, hold it to sustain
*/
float Process(bool gate);
/** Sets time
Set time per segment in seconds
*/
void SetTime(int seg, float time);
void SetAttackTime(float timeInS, float shape = 0.0f);
void SetDecayTime(float timeInS);
void SetReleaseTime(float timeInS);
private:
void SetTimeConstant(float timeInS, float& time, float& coeff);
public:
/** Sustain level
\param sus_level - sets sustain level, 0...1.0
*/
inline void SetSustainLevel(float sus_level)
{
sus_level = (sus_level <= 0.f) ? -0.01f // forces envelope into idle
: (sus_level > 1.f) ? 1.f : sus_level;
sus_level_ = sus_level;
}
/** get the current envelope segment
\return the segment of the envelope that the phase is currently located in.
*/
inline uint8_t GetCurrentSegment() { return mode_; }
/** Tells whether envelope is active
\return true if the envelope is currently in any stage apart from idle.
*/
inline bool IsRunning() const { return mode_ != ADSR_SEG_IDLE; }
private:
float sus_level_{0.f};
float x_{0.f};
float attackShape_{-1.f};
float attackTarget_{0.0f};
float attackTime_{-1.0f};
float decayTime_{-1.0f};
float releaseTime_{-1.0f};
float attackD0_{0.f};
float decayD0_{0.f};
float releaseD0_{0.f};
int sample_rate_;
uint8_t mode_{ADSR_SEG_IDLE};
bool gate_{false};
};
} // namespace daisysp
#endif
#endif
+27
View File
@@ -0,0 +1,27 @@
#include <math.h>
#include "phasor.h"
#include "dsp.h"
using namespace daisysp;
void Phasor::SetFreq(float freq)
{
freq_ = freq;
inc_ = (TWOPI_F * freq_) / sample_rate_;
}
float Phasor::Process()
{
float out;
out = phs_ / TWOPI_F;
phs_ += inc_;
if(phs_ > TWOPI_F)
{
phs_ -= TWOPI_F;
}
if(phs_ < 0.0f)
{
phs_ = 0.0f;
}
return out;
}
+71
View File
@@ -0,0 +1,71 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_PHASOR_H
#define DSY_PHASOR_H
#ifdef __cplusplus
namespace daisysp
{
/** Generates a normalized signal moving from 0-1 at the specified frequency.
\todo Selecting which channels should be initialized/included in the sequence conversion.
\todo Setup a similar start function for an external mux, but that seems outside the scope of this file.
*/
class Phasor
{
public:
Phasor() {}
~Phasor() {}
/** Initializes the Phasor module
sample rate, and freq are in Hz
initial phase is in radians
Additional Init functions have defaults when arg is not specified:
- phs = 0.0f
- freq = 1.0f
*/
inline void Init(float sample_rate, float freq, float initial_phase)
{
sample_rate_ = sample_rate;
phs_ = initial_phase;
SetFreq(freq);
}
/** Initialize phasor with samplerate and freq
*/
inline void Init(float sample_rate, float freq)
{
Init(sample_rate, freq, 0.0f);
}
/** Initialize phasor with samplerate
*/
inline void Init(float sample_rate) { Init(sample_rate, 1.0f, 0.0f); }
/** processes Phasor and returns current value
*/
float Process();
/** Sets frequency of the Phasor in Hz
*/
void SetFreq(float freq);
/** Returns current frequency value in Hz
*/
inline float GetFreq() { return freq_; }
private:
float freq_;
float sample_rate_, inc_, phs_;
};
} // namespace daisysp
#endif
#endif