From dd63b3aed4c3448fbdb43ebefdcd6abfd0d33c72 Mon Sep 17 00:00:00 2001 From: Tom-on64 Date: Fri, 24 Apr 2026 14:46:05 +0200 Subject: [PATCH] Added DaisySP --- daisysp/Control/adenv.cpp | 167 ++++++++++ daisysp/Control/adenv.h | 100 ++++++ daisysp/Control/adsr.cpp | 136 ++++++++ daisysp/Control/adsr.h | 106 +++++++ daisysp/Control/phasor.cpp | 27 ++ daisysp/Control/phasor.h | 71 +++++ daisysp/Drums/analogbassdrum.cpp | 192 +++++++++++ daisysp/Drums/analogbassdrum.h | 113 +++++++ daisysp/Drums/analogsnaredrum.cpp | 219 +++++++++++++ daisysp/Drums/analogsnaredrum.h | 106 +++++++ daisysp/Drums/hihat.cpp | 96 ++++++ daisysp/Drums/hihat.h | 277 ++++++++++++++++ daisysp/Drums/synthbassdrum.cpp | 231 ++++++++++++++ daisysp/Drums/synthbassdrum.h | 187 +++++++++++ daisysp/Drums/synthsnaredrum.cpp | 201 ++++++++++++ daisysp/Drums/synthsnaredrum.h | 105 ++++++ daisysp/Dynamics/crossfade.cpp | 37 +++ daisysp/Dynamics/crossfade.h | 86 +++++ daisysp/Dynamics/limiter.cpp | 29 ++ daisysp/Dynamics/limiter.h | 41 +++ daisysp/Effects/autowah.cpp | 65 ++++ daisysp/Effects/autowah.h | 60 ++++ daisysp/Effects/chorus.cpp | 187 +++++++++++ daisysp/Effects/chorus.h | 190 +++++++++++ daisysp/Effects/decimator.cpp | 46 +++ daisysp/Effects/decimator.h | 94 ++++++ daisysp/Effects/flanger.cpp | 83 +++++ daisysp/Effects/flanger.h | 83 +++++ daisysp/Effects/overdrive.cpp | 32 ++ daisysp/Effects/overdrive.h | 54 ++++ daisysp/Effects/phaser.cpp | 138 ++++++++ daisysp/Effects/phaser.h | 139 ++++++++ daisysp/Effects/pitchshifter.h | 219 +++++++++++++ daisysp/Effects/sampleratereducer.cpp | 43 +++ daisysp/Effects/sampleratereducer.h | 56 ++++ daisysp/Effects/tremolo.cpp | 36 +++ daisysp/Effects/tremolo.h | 68 ++++ daisysp/Effects/wavefolder.cpp | 20 ++ daisysp/Effects/wavefolder.h | 53 ++++ daisysp/Filters/fir.h | 351 ++++++++++++++++++++ daisysp/Filters/ladder.cpp | 180 +++++++++++ daisysp/Filters/ladder.h | 144 +++++++++ daisysp/Filters/onepole.h | 110 +++++++ daisysp/Filters/soap.cpp | 65 ++++ daisysp/Filters/soap.h | 70 ++++ daisysp/Filters/svf.cpp | 86 +++++ daisysp/Filters/svf.h | 87 +++++ daisysp/Noise/clockednoise.cpp | 59 ++++ daisysp/Noise/clockednoise.h | 66 ++++ daisysp/Noise/dust.h | 62 ++++ daisysp/Noise/fractal_noise.h | 90 ++++++ daisysp/Noise/grainlet.cpp | 146 +++++++++ daisysp/Noise/grainlet.h | 92 ++++++ daisysp/Noise/particle.cpp | 95 ++++++ daisysp/Noise/particle.h | 92 ++++++ daisysp/Noise/whitenoise.h | 54 ++++ daisysp/PhysicalModeling/KarplusString.cpp | 197 ++++++++++++ daisysp/PhysicalModeling/KarplusString.h | 110 +++++++ daisysp/PhysicalModeling/drip.cpp | 213 +++++++++++++ daisysp/PhysicalModeling/drip.h | 61 ++++ daisysp/PhysicalModeling/modalvoice.cpp | 109 +++++++ daisysp/PhysicalModeling/modalvoice.h | 99 ++++++ daisysp/PhysicalModeling/resonator.cpp | 158 +++++++++ daisysp/PhysicalModeling/resonator.h | 191 +++++++++++ daisysp/PhysicalModeling/stringvoice.cpp | 124 ++++++++ daisysp/PhysicalModeling/stringvoice.h | 102 ++++++ daisysp/Sampling/granularplayer.cpp | 79 +++++ daisysp/Sampling/granularplayer.h | 96 ++++++ daisysp/Synthesis/fm2.cpp | 65 ++++ daisysp/Synthesis/fm2.h | 73 +++++ daisysp/Synthesis/formantosc.cpp | 96 ++++++ daisysp/Synthesis/formantosc.h | 81 +++++ daisysp/Synthesis/harmonic_osc.h | 174 ++++++++++ daisysp/Synthesis/oscillator.cpp | 83 +++++ daisysp/Synthesis/oscillator.h | 125 ++++++++ daisysp/Synthesis/oscillatorbank.cpp | 147 +++++++++ daisysp/Synthesis/oscillatorbank.h | 88 ++++++ daisysp/Synthesis/variablesawosc.cpp | 106 +++++++ daisysp/Synthesis/variablesawosc.h | 80 +++++ daisysp/Synthesis/variableshapeosc.cpp | 182 +++++++++++ daisysp/Synthesis/variableshapeosc.h | 95 ++++++ daisysp/Synthesis/vosim.cpp | 81 +++++ daisysp/Synthesis/vosim.h | 82 +++++ daisysp/Synthesis/zoscillator.cpp | 159 ++++++++++ daisysp/Synthesis/zoscillator.h | 84 +++++ daisysp/Utility/dcblock.cpp | 20 ++ daisysp/Utility/dcblock.h | 37 +++ daisysp/Utility/delayline.h | 129 ++++++++ daisysp/Utility/dsp.h | 352 +++++++++++++++++++++ daisysp/Utility/looper.h | 314 ++++++++++++++++++ daisysp/Utility/maytrig.h | 44 +++ daisysp/Utility/metro.cpp | 30 ++ daisysp/Utility/metro.h | 54 ++++ daisysp/Utility/samplehold.h | 69 ++++ daisysp/Utility/smooth_random.h | 85 +++++ daisysp/daisysp.h | 97 ++++++ 96 files changed, 10613 insertions(+) create mode 100644 daisysp/Control/adenv.cpp create mode 100644 daisysp/Control/adenv.h create mode 100644 daisysp/Control/adsr.cpp create mode 100644 daisysp/Control/adsr.h create mode 100644 daisysp/Control/phasor.cpp create mode 100644 daisysp/Control/phasor.h create mode 100644 daisysp/Drums/analogbassdrum.cpp create mode 100644 daisysp/Drums/analogbassdrum.h create mode 100644 daisysp/Drums/analogsnaredrum.cpp create mode 100644 daisysp/Drums/analogsnaredrum.h create mode 100644 daisysp/Drums/hihat.cpp create mode 100644 daisysp/Drums/hihat.h create mode 100644 daisysp/Drums/synthbassdrum.cpp create mode 100644 daisysp/Drums/synthbassdrum.h create mode 100644 daisysp/Drums/synthsnaredrum.cpp create mode 100644 daisysp/Drums/synthsnaredrum.h create mode 100644 daisysp/Dynamics/crossfade.cpp create mode 100644 daisysp/Dynamics/crossfade.h create mode 100644 daisysp/Dynamics/limiter.cpp create mode 100644 daisysp/Dynamics/limiter.h create mode 100644 daisysp/Effects/autowah.cpp create mode 100644 daisysp/Effects/autowah.h create mode 100644 daisysp/Effects/chorus.cpp create mode 100644 daisysp/Effects/chorus.h create mode 100644 daisysp/Effects/decimator.cpp create mode 100644 daisysp/Effects/decimator.h create mode 100644 daisysp/Effects/flanger.cpp create mode 100644 daisysp/Effects/flanger.h create mode 100644 daisysp/Effects/overdrive.cpp create mode 100644 daisysp/Effects/overdrive.h create mode 100644 daisysp/Effects/phaser.cpp create mode 100644 daisysp/Effects/phaser.h create mode 100644 daisysp/Effects/pitchshifter.h create mode 100644 daisysp/Effects/sampleratereducer.cpp create mode 100644 daisysp/Effects/sampleratereducer.h create mode 100644 daisysp/Effects/tremolo.cpp create mode 100644 daisysp/Effects/tremolo.h create mode 100644 daisysp/Effects/wavefolder.cpp create mode 100644 daisysp/Effects/wavefolder.h create mode 100644 daisysp/Filters/fir.h create mode 100644 daisysp/Filters/ladder.cpp create mode 100644 daisysp/Filters/ladder.h create mode 100644 daisysp/Filters/onepole.h create mode 100644 daisysp/Filters/soap.cpp create mode 100644 daisysp/Filters/soap.h create mode 100644 daisysp/Filters/svf.cpp create mode 100644 daisysp/Filters/svf.h create mode 100644 daisysp/Noise/clockednoise.cpp create mode 100644 daisysp/Noise/clockednoise.h create mode 100644 daisysp/Noise/dust.h create mode 100644 daisysp/Noise/fractal_noise.h create mode 100644 daisysp/Noise/grainlet.cpp create mode 100644 daisysp/Noise/grainlet.h create mode 100644 daisysp/Noise/particle.cpp create mode 100644 daisysp/Noise/particle.h create mode 100644 daisysp/Noise/whitenoise.h create mode 100644 daisysp/PhysicalModeling/KarplusString.cpp create mode 100644 daisysp/PhysicalModeling/KarplusString.h create mode 100644 daisysp/PhysicalModeling/drip.cpp create mode 100644 daisysp/PhysicalModeling/drip.h create mode 100644 daisysp/PhysicalModeling/modalvoice.cpp create mode 100644 daisysp/PhysicalModeling/modalvoice.h create mode 100644 daisysp/PhysicalModeling/resonator.cpp create mode 100644 daisysp/PhysicalModeling/resonator.h create mode 100644 daisysp/PhysicalModeling/stringvoice.cpp create mode 100644 daisysp/PhysicalModeling/stringvoice.h create mode 100644 daisysp/Sampling/granularplayer.cpp create mode 100644 daisysp/Sampling/granularplayer.h create mode 100644 daisysp/Synthesis/fm2.cpp create mode 100644 daisysp/Synthesis/fm2.h create mode 100644 daisysp/Synthesis/formantosc.cpp create mode 100644 daisysp/Synthesis/formantosc.h create mode 100644 daisysp/Synthesis/harmonic_osc.h create mode 100644 daisysp/Synthesis/oscillator.cpp create mode 100644 daisysp/Synthesis/oscillator.h create mode 100644 daisysp/Synthesis/oscillatorbank.cpp create mode 100644 daisysp/Synthesis/oscillatorbank.h create mode 100644 daisysp/Synthesis/variablesawosc.cpp create mode 100644 daisysp/Synthesis/variablesawosc.h create mode 100644 daisysp/Synthesis/variableshapeosc.cpp create mode 100644 daisysp/Synthesis/variableshapeosc.h create mode 100644 daisysp/Synthesis/vosim.cpp create mode 100644 daisysp/Synthesis/vosim.h create mode 100644 daisysp/Synthesis/zoscillator.cpp create mode 100644 daisysp/Synthesis/zoscillator.h create mode 100644 daisysp/Utility/dcblock.cpp create mode 100644 daisysp/Utility/dcblock.h create mode 100644 daisysp/Utility/delayline.h create mode 100644 daisysp/Utility/dsp.h create mode 100644 daisysp/Utility/looper.h create mode 100644 daisysp/Utility/maytrig.h create mode 100644 daisysp/Utility/metro.cpp create mode 100644 daisysp/Utility/metro.h create mode 100644 daisysp/Utility/samplehold.h create mode 100644 daisysp/Utility/smooth_random.h create mode 100644 daisysp/daisysp.h diff --git a/daisysp/Control/adenv.cpp b/daisysp/Control/adenv.cpp new file mode 100644 index 0000000..4e9568b --- /dev/null +++ b/daisysp/Control/adenv.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#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::epsilon()); + } + else + { + c_inc_ = std::min(c_inc_, -std::numeric_limits::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_; +} diff --git a/daisysp/Control/adenv.h b/daisysp/Control/adenv.h new file mode 100644 index 0000000..fb115ae --- /dev/null +++ b/daisysp/Control/adenv.h @@ -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 +#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 diff --git a/daisysp/Control/adsr.cpp b/daisysp/Control/adsr.cpp new file mode 100644 index 0000000..cd62787 --- /dev/null +++ b/daisysp/Control/adsr.cpp @@ -0,0 +1,136 @@ +#include "adsr.h" +#include + +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; +} diff --git a/daisysp/Control/adsr.h b/daisysp/Control/adsr.h new file mode 100644 index 0000000..d85d8aa --- /dev/null +++ b/daisysp/Control/adsr.h @@ -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 +#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 diff --git a/daisysp/Control/phasor.cpp b/daisysp/Control/phasor.cpp new file mode 100644 index 0000000..33ff920 --- /dev/null +++ b/daisysp/Control/phasor.cpp @@ -0,0 +1,27 @@ +#include +#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; +} diff --git a/daisysp/Control/phasor.h b/daisysp/Control/phasor.h new file mode 100644 index 0000000..63ba336 --- /dev/null +++ b/daisysp/Control/phasor.h @@ -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 diff --git a/daisysp/Drums/analogbassdrum.cpp b/daisysp/Drums/analogbassdrum.cpp new file mode 100644 index 0000000..00e1837 --- /dev/null +++ b/daisysp/Drums/analogbassdrum.cpp @@ -0,0 +1,192 @@ +#include "dsp.h" +#include "analogbassdrum.h" +#include + +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(1.0e-3f * sample_rate_); + const int kFMPulseDuration = static_cast(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; +} diff --git a/daisysp/Drums/analogbassdrum.h b/daisysp/Drums/analogbassdrum.h new file mode 100644 index 0000000..6b7705f --- /dev/null +++ b/daisysp/Drums/analogbassdrum.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Drums/analogsnaredrum.cpp b/daisysp/Drums/analogsnaredrum.cpp new file mode 100644 index 0000000..6298181 --- /dev/null +++ b/daisysp/Drums/analogsnaredrum.cpp @@ -0,0 +1,219 @@ +#include "dsp.h" +#include "analogsnaredrum.h" +#include +#include + +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); + } +} \ No newline at end of file diff --git a/daisysp/Drums/analogsnaredrum.h b/daisysp/Drums/analogsnaredrum.h new file mode 100644 index 0000000..322591b --- /dev/null +++ b/daisysp/Drums/analogsnaredrum.h @@ -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 +#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 diff --git a/daisysp/Drums/hihat.cpp b/daisysp/Drums/hihat.cpp new file mode 100644 index 0000000..bbe8096 --- /dev/null +++ b/daisysp/Drums/hihat.cpp @@ -0,0 +1,96 @@ +#include "dsp.h" +#include "hihat.h" +#include + +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(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(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; +} \ No newline at end of file diff --git a/daisysp/Drums/hihat.h b/daisysp/Drums/hihat.h new file mode 100644 index 0000000..5c84171 --- /dev/null +++ b/daisysp/Drums/hihat.h @@ -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 +#include +#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 +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 \ No newline at end of file diff --git a/daisysp/Drums/synthbassdrum.cpp b/daisysp/Drums/synthbassdrum.cpp new file mode 100644 index 0000000..24adf8b --- /dev/null +++ b/daisysp/Drums/synthbassdrum.cpp @@ -0,0 +1,231 @@ +#include "synthbassdrum.h" +#include +#include + +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(phase); + float phase_fractional = phase - static_cast(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; +} \ No newline at end of file diff --git a/daisysp/Drums/synthbassdrum.h b/daisysp/Drums/synthbassdrum.h new file mode 100644 index 0000000..53b5982 --- /dev/null +++ b/daisysp/Drums/synthbassdrum.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Drums/synthsnaredrum.cpp b/daisysp/Drums/synthsnaredrum.cpp new file mode 100644 index 0000000..47bdb7f --- /dev/null +++ b/daisysp/Drums/synthsnaredrum.cpp @@ -0,0 +1,201 @@ +#include "dsp.h" +#include "synthsnaredrum.h" +#include +#include + +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((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); +} diff --git a/daisysp/Drums/synthsnaredrum.h b/daisysp/Drums/synthsnaredrum.h new file mode 100644 index 0000000..6c4552c --- /dev/null +++ b/daisysp/Drums/synthsnaredrum.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Dynamics/crossfade.cpp b/daisysp/Dynamics/crossfade.cpp new file mode 100644 index 0000000..5beffc9 --- /dev/null +++ b/daisysp/Dynamics/crossfade.cpp @@ -0,0 +1,37 @@ +#include +#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; + } +} diff --git a/daisysp/Dynamics/crossfade.h b/daisysp/Dynamics/crossfade.h new file mode 100644 index 0000000..3672b78 --- /dev/null +++ b/daisysp/Dynamics/crossfade.h @@ -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 +#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 diff --git a/daisysp/Dynamics/limiter.cpp b/daisysp/Dynamics/limiter.cpp new file mode 100644 index 0000000..e5bb6fe --- /dev/null +++ b/daisysp/Dynamics/limiter.cpp @@ -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 \ No newline at end of file diff --git a/daisysp/Dynamics/limiter.h b/daisysp/Dynamics/limiter.h new file mode 100644 index 0000000..2c539d5 --- /dev/null +++ b/daisysp/Dynamics/limiter.h @@ -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 +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 diff --git a/daisysp/Effects/autowah.cpp b/daisysp/Effects/autowah.cpp new file mode 100644 index 0000000..42e5193 --- /dev/null +++ b/daisysp/Effects/autowah.cpp @@ -0,0 +1,65 @@ +#include "autowah.h" +#include + +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; +} diff --git a/daisysp/Effects/autowah.h b/daisysp/Effects/autowah.h new file mode 100644 index 0000000..580a725 --- /dev/null +++ b/daisysp/Effects/autowah.h @@ -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 +#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 diff --git a/daisysp/Effects/chorus.cpp b/daisysp/Effects/chorus.cpp new file mode 100644 index 0000000..d805279 --- /dev/null +++ b/daisysp/Effects/chorus.cpp @@ -0,0 +1,187 @@ +#include "dsp.h" +#include "chorus.h" +#include + +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); +} diff --git a/daisysp/Effects/chorus.h b/daisysp/Effects/chorus.h new file mode 100644 index 0000000..1b317c5 --- /dev/null +++ b/daisysp/Effects/chorus.h @@ -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 +#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 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 \ No newline at end of file diff --git a/daisysp/Effects/decimator.cpp b/daisysp/Effects/decimator.cpp new file mode 100644 index 0000000..1713eac --- /dev/null +++ b/daisysp/Effects/decimator.cpp @@ -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_; +} diff --git a/daisysp/Effects/decimator.h b/daisysp/Effects/decimator.h new file mode 100644 index 0000000..ed55629 --- /dev/null +++ b/daisysp/Effects/decimator.h @@ -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 +#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 diff --git a/daisysp/Effects/flanger.cpp b/daisysp/Effects/flanger.cpp new file mode 100644 index 0000000..de75de7 --- /dev/null +++ b/daisysp/Effects/flanger.cpp @@ -0,0 +1,83 @@ +#include "dsp.h" +#include "flanger.h" +#include + +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_; +} \ No newline at end of file diff --git a/daisysp/Effects/flanger.h b/daisysp/Effects/flanger.h new file mode 100644 index 0000000..703bd29 --- /dev/null +++ b/daisysp/Effects/flanger.h @@ -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 +#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 del_; + + float ProcessLfo(); +}; +} //namespace daisysp +#endif +#endif diff --git a/daisysp/Effects/overdrive.cpp b/daisysp/Effects/overdrive.cpp new file mode 100644 index 0000000..690f4fc --- /dev/null +++ b/daisysp/Effects/overdrive.cpp @@ -0,0 +1,32 @@ +#include "dsp.h" +#include "overdrive.h" +#include + +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 \ No newline at end of file diff --git a/daisysp/Effects/overdrive.h b/daisysp/Effects/overdrive.h new file mode 100644 index 0000000..dafea3e --- /dev/null +++ b/daisysp/Effects/overdrive.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Effects/phaser.cpp b/daisysp/Effects/phaser.cpp new file mode 100644 index 0000000..9609652 --- /dev/null +++ b/daisysp/Effects/phaser.cpp @@ -0,0 +1,138 @@ +#include "dsp.h" +#include "phaser.h" +#include + +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); + } +} diff --git a/daisysp/Effects/phaser.h b/daisysp/Effects/phaser.h new file mode 100644 index 0000000..9f7279e --- /dev/null +++ b/daisysp/Effects/phaser.h @@ -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 +#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 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 diff --git a/daisysp/Effects/pitchshifter.h b/daisysp/Effects/pitchshifter.h new file mode 100644 index 0000000..a921513 --- /dev/null +++ b/daisysp/Effects/pitchshifter.h @@ -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 +#include +#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 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 diff --git a/daisysp/Effects/sampleratereducer.cpp b/daisysp/Effects/sampleratereducer.cpp new file mode 100644 index 0000000..2681368 --- /dev/null +++ b/daisysp/Effects/sampleratereducer.cpp @@ -0,0 +1,43 @@ +#include "dsp.h" +#include "sampleratereducer.h" +#include + +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); +} diff --git a/daisysp/Effects/sampleratereducer.h b/daisysp/Effects/sampleratereducer.h new file mode 100644 index 0000000..735672b --- /dev/null +++ b/daisysp/Effects/sampleratereducer.h @@ -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 +#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 diff --git a/daisysp/Effects/tremolo.cpp b/daisysp/Effects/tremolo.cpp new file mode 100644 index 0000000..7fbe00a --- /dev/null +++ b/daisysp/Effects/tremolo.cpp @@ -0,0 +1,36 @@ +#include "tremolo.h" +#include + +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; +} \ No newline at end of file diff --git a/daisysp/Effects/tremolo.h b/daisysp/Effects/tremolo.h new file mode 100644 index 0000000..1cac156 --- /dev/null +++ b/daisysp/Effects/tremolo.h @@ -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 +#ifdef __cplusplus + +#include +#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 diff --git a/daisysp/Effects/wavefolder.cpp b/daisysp/Effects/wavefolder.cpp new file mode 100644 index 0000000..8d0704d --- /dev/null +++ b/daisysp/Effects/wavefolder.cpp @@ -0,0 +1,20 @@ +#include "wavefolder.h" +#include + +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(ft) % 2 == 0 ? 1.0f : -1.0f; + return sgn * (in - 2.0f * ft); +} \ No newline at end of file diff --git a/daisysp/Effects/wavefolder.h b/daisysp/Effects/wavefolder.h new file mode 100644 index 0000000..287f6d9 --- /dev/null +++ b/daisysp/Effects/wavefolder.h @@ -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 +#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 diff --git a/daisysp/Filters/fir.h b/daisysp/Filters/fir.h new file mode 100644 index 0000000..1d33fee --- /dev/null +++ b/daisysp/Filters/fir.h @@ -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 +#include // for memset +#include +#include + +#ifdef USE_ARM_DSP +#include // 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 +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 +{ + /* 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 +class FIRFilterImplGeneric : public FIRMemory +{ + private: + using FIRMem = FIRMemory; // 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 + inline auto Init(Args&&... args) + -> decltype(SetIR(std::forward(args)...)) + { + return SetIR(std::forward(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 +class FIRFilterImplARM : public FIRMemory +{ + private: + using FIRMem = FIRMemory; // 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 + inline auto Init(Args&&... args) + -> decltype(SetIR(std::forward(args)...)) + { + return SetIR(std::forward(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 +using FIR = FIRFilterImplARM; + + +#else // USE_ARM_DSP + +/* default to generic implementation */ +template +using FIR = FIRFilterImplGeneric; + +#endif // USE_ARM_DSP + + +} // namespace daisysp + +#endif // DSY_FIRFILTER_H diff --git a/daisysp/Filters/ladder.cpp b/daisysp/Filters/ladder.cpp new file mode 100644 index 0000000..922997c --- /dev/null +++ b/daisysp/Filters/ladder.cpp @@ -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& 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; + } +} diff --git a/daisysp/Filters/ladder.h b/daisysp/Filters/ladder.h new file mode 100644 index 0000000..8506f01 --- /dev/null +++ b/daisysp/Filters/ladder.h @@ -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 +#include +#include +#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& stage_outs); +}; + + +} // namespace daisysp +#endif +#endif diff --git a/daisysp/Filters/onepole.h b/daisysp/Filters/onepole.h new file mode 100644 index 0000000..b7001c2 --- /dev/null +++ b/daisysp/Filters/onepole.h @@ -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 +#include + +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 diff --git a/daisysp/Filters/soap.cpp b/daisysp/Filters/soap.cpp new file mode 100644 index 0000000..ea95289 --- /dev/null +++ b/daisysp/Filters/soap.cpp @@ -0,0 +1,65 @@ +#include "soap.h" +#include + +#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; +} \ No newline at end of file diff --git a/daisysp/Filters/soap.h b/daisysp/Filters/soap.h new file mode 100644 index 0000000..3d4b4b6 --- /dev/null +++ b/daisysp/Filters/soap.h @@ -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 diff --git a/daisysp/Filters/svf.cpp b/daisysp/Filters/svf.cpp new file mode 100644 index 0000000..9a1d86a --- /dev/null +++ b/daisysp/Filters/svf.cpp @@ -0,0 +1,86 @@ +#include +#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_; +} diff --git a/daisysp/Filters/svf.h b/daisysp/Filters/svf.h new file mode 100644 index 0000000..e41eef5 --- /dev/null +++ b/daisysp/Filters/svf.h @@ -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 diff --git a/daisysp/Noise/clockednoise.cpp b/daisysp/Noise/clockednoise.cpp new file mode 100644 index 0000000..d626be9 --- /dev/null +++ b/daisysp/Noise/clockednoise.cpp @@ -0,0 +1,59 @@ +#include +#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; +} \ No newline at end of file diff --git a/daisysp/Noise/clockednoise.h b/daisysp/Noise/clockednoise.h new file mode 100644 index 0000000..e64c9a9 --- /dev/null +++ b/daisysp/Noise/clockednoise.h @@ -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 +#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 diff --git a/daisysp/Noise/dust.h b/daisysp/Noise/dust.h new file mode 100644 index 0000000..e5f80f5 --- /dev/null +++ b/daisysp/Noise/dust.h @@ -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 +#include +#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 diff --git a/daisysp/Noise/fractal_noise.h b/daisysp/Noise/fractal_noise.h new file mode 100644 index 0000000..0d4ff9b --- /dev/null +++ b/daisysp/Noise/fractal_noise.h @@ -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 +#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 +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 \ No newline at end of file diff --git a/daisysp/Noise/grainlet.cpp b/daisysp/Noise/grainlet.cpp new file mode 100644 index 0000000..6730b68 --- /dev/null +++ b/daisysp/Noise/grainlet.cpp @@ -0,0 +1,146 @@ +#include "dsp.h" +#include "grainlet.h" +#include + +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(shape); + float shape_fractional = shape - static_cast(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); +} \ No newline at end of file diff --git a/daisysp/Noise/grainlet.h b/daisysp/Noise/grainlet.h new file mode 100644 index 0000000..9734084 --- /dev/null +++ b/daisysp/Noise/grainlet.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Noise/particle.cpp b/daisysp/Noise/particle.cpp new file mode 100644 index 0000000..c187ce5 --- /dev/null +++ b/daisysp/Noise/particle.cpp @@ -0,0 +1,95 @@ +#include "dsp.h" +#include "particle.h" +#include + +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; +} \ No newline at end of file diff --git a/daisysp/Noise/particle.h b/daisysp/Noise/particle.h new file mode 100644 index 0000000..e57afd3 --- /dev/null +++ b/daisysp/Noise/particle.h @@ -0,0 +1,92 @@ +#pragma once +#ifndef DSY_PARTICLE_H +#define DSY_PARTICLE_H + +#include "Filters/svf.h" +#include +#include +#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 diff --git a/daisysp/Noise/whitenoise.h b/daisysp/Noise/whitenoise.h new file mode 100644 index 0000000..cb4b28a --- /dev/null +++ b/daisysp/Noise/whitenoise.h @@ -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 +#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 diff --git a/daisysp/PhysicalModeling/KarplusString.cpp b/daisysp/PhysicalModeling/KarplusString.cpp new file mode 100644 index 0000000..8fa4a43 --- /dev/null +++ b/daisysp/PhysicalModeling/KarplusString.cpp @@ -0,0 +1,197 @@ +#include +#include "dsp.h" +#include "KarplusString.h" +#include + +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(in); + non_linearity_amount_ *= -1; + return ret; + } + else + { + return ProcessInternal(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 +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]); +} diff --git a/daisysp/PhysicalModeling/KarplusString.h b/daisysp/PhysicalModeling/KarplusString.h new file mode 100644 index 0000000..8fcec4e --- /dev/null +++ b/daisysp/PhysicalModeling/KarplusString.h @@ -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 + +#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 + float ProcessInternal(const float in); + + DelayLine string_; + DelayLine 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 diff --git a/daisysp/PhysicalModeling/drip.cpp b/daisysp/PhysicalModeling/drip.cpp new file mode 100644 index 0000000..5ec475f --- /dev/null +++ b/daisysp/PhysicalModeling/drip.cpp @@ -0,0 +1,213 @@ +#include "drip.h" +#include +#include +#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; +} diff --git a/daisysp/PhysicalModeling/drip.h b/daisysp/PhysicalModeling/drip.h new file mode 100644 index 0000000..abaccd5 --- /dev/null +++ b/daisysp/PhysicalModeling/drip.h @@ -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 +#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 diff --git a/daisysp/PhysicalModeling/modalvoice.cpp b/daisysp/PhysicalModeling/modalvoice.cpp new file mode 100644 index 0000000..b34dcb5 --- /dev/null +++ b/daisysp/PhysicalModeling/modalvoice.cpp @@ -0,0 +1,109 @@ +#include "modalvoice.h" +#include + +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::LOW_PASS, false>( + &cutoff, &q, &one, temp, &temp); + + aux_ = temp; + + resonator_.SetBrightness(brightness); + resonator_.SetDamping(damping); + + return resonator_.Process(temp); +} \ No newline at end of file diff --git a/daisysp/PhysicalModeling/modalvoice.h b/daisysp/PhysicalModeling/modalvoice.h new file mode 100644 index 0000000..b299c17 --- /dev/null +++ b/daisysp/PhysicalModeling/modalvoice.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/PhysicalModeling/resonator.cpp b/daisysp/PhysicalModeling/resonator.cpp new file mode 100644 index 0000000..cdef166 --- /dev/null +++ b/daisysp/PhysicalModeling/resonator.cpp @@ -0,0 +1,158 @@ +#include "resonator.h" +#include + +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* 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::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; +} diff --git a/daisysp/PhysicalModeling/resonator.h b/daisysp/PhysicalModeling/resonator.h new file mode 100644 index 0000000..61edcec --- /dev/null +++ b/daisysp/PhysicalModeling/resonator.h @@ -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 +#include +#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 +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 + 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 mode_filters_[kMaxNumModes / kModeBatchSize]; +}; + +} // namespace daisysp +#endif +#endif diff --git a/daisysp/PhysicalModeling/stringvoice.cpp b/daisysp/PhysicalModeling/stringvoice.cpp new file mode 100644 index 0000000..64cd529 --- /dev/null +++ b/daisysp/PhysicalModeling/stringvoice.cpp @@ -0,0 +1,124 @@ +#include "stringvoice.h" +#include +#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(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); +} diff --git a/daisysp/PhysicalModeling/stringvoice.h b/daisysp/PhysicalModeling/stringvoice.h new file mode 100644 index 0000000..8829402 --- /dev/null +++ b/daisysp/PhysicalModeling/stringvoice.h @@ -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 +#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 diff --git a/daisysp/Sampling/granularplayer.cpp b/daisysp/Sampling/granularplayer.cpp new file mode 100644 index 0000000..2456548 --- /dev/null +++ b/daisysp/Sampling/granularplayer.cpp @@ -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; +} \ No newline at end of file diff --git a/daisysp/Sampling/granularplayer.h b/daisysp/Sampling/granularplayer.h new file mode 100644 index 0000000..d049c1a --- /dev/null +++ b/daisysp/Sampling/granularplayer.h @@ -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 +#include +#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 \ No newline at end of file diff --git a/daisysp/Synthesis/fm2.cpp b/daisysp/Synthesis/fm2.cpp new file mode 100644 index 0000000..4560013 --- /dev/null +++ b/daisysp/Synthesis/fm2.cpp @@ -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(); +} diff --git a/daisysp/Synthesis/fm2.h b/daisysp/Synthesis/fm2.h new file mode 100644 index 0000000..7c31ccc --- /dev/null +++ b/daisysp/Synthesis/fm2.h @@ -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 +#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 diff --git a/daisysp/Synthesis/formantosc.cpp b/daisysp/Synthesis/formantosc.cpp new file mode 100644 index 0000000..28af210 --- /dev/null +++ b/daisysp/Synthesis/formantosc.cpp @@ -0,0 +1,96 @@ +#include "dsp.h" +#include "formantosc.h" +#include + +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; +} diff --git a/daisysp/Synthesis/formantosc.h b/daisysp/Synthesis/formantosc.h new file mode 100644 index 0000000..33fda86 --- /dev/null +++ b/daisysp/Synthesis/formantosc.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Synthesis/harmonic_osc.h b/daisysp/Synthesis/harmonic_osc.h new file mode 100644 index 0000000..be924a5 --- /dev/null +++ b/daisysp/Synthesis/harmonic_osc.h @@ -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 +#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 +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(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 \ No newline at end of file diff --git a/daisysp/Synthesis/oscillator.cpp b/daisysp/Synthesis/oscillator.cpp new file mode 100644 index 0000000..42c098e --- /dev/null +++ b/daisysp/Synthesis/oscillator.cpp @@ -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; + } +} diff --git a/daisysp/Synthesis/oscillator.h b/daisysp/Synthesis/oscillator.h new file mode 100644 index 0000000..469d09a --- /dev/null +++ b/daisysp/Synthesis/oscillator.h @@ -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 +#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 diff --git a/daisysp/Synthesis/oscillatorbank.cpp b/daisysp/Synthesis/oscillatorbank.cpp new file mode 100644 index 0000000..fa2155f --- /dev/null +++ b/daisysp/Synthesis/oscillatorbank.cpp @@ -0,0 +1,147 @@ +#include "dsp.h" +#include "oscillatorbank.h" +#include + +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(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(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; +} \ No newline at end of file diff --git a/daisysp/Synthesis/oscillatorbank.h b/daisysp/Synthesis/oscillatorbank.h new file mode 100644 index 0000000..0266743 --- /dev/null +++ b/daisysp/Synthesis/oscillatorbank.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Synthesis/variablesawosc.cpp b/daisysp/Synthesis/variablesawosc.cpp new file mode 100644 index 0000000..1db3b06 --- /dev/null +++ b/daisysp/Synthesis/variablesawosc.cpp @@ -0,0 +1,106 @@ +#include "dsp.h" +#include "variablesawosc.h" +#include + +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; +} \ No newline at end of file diff --git a/daisysp/Synthesis/variablesawosc.h b/daisysp/Synthesis/variablesawosc.h new file mode 100644 index 0000000..c9d57eb --- /dev/null +++ b/daisysp/Synthesis/variablesawosc.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Synthesis/variableshapeosc.cpp b/daisysp/Synthesis/variableshapeosc.cpp new file mode 100644 index 0000000..408161a --- /dev/null +++ b/daisysp/Synthesis/variableshapeosc.cpp @@ -0,0 +1,182 @@ +#include "dsp.h" +#include "variableshapeosc.h" +#include + +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; +} \ No newline at end of file diff --git a/daisysp/Synthesis/variableshapeosc.h b/daisysp/Synthesis/variableshapeosc.h new file mode 100644 index 0000000..e94d92b --- /dev/null +++ b/daisysp/Synthesis/variableshapeosc.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Synthesis/vosim.cpp b/daisysp/Synthesis/vosim.cpp new file mode 100644 index 0000000..9503b25 --- /dev/null +++ b/daisysp/Synthesis/vosim.cpp @@ -0,0 +1,81 @@ +#include "dsp.h" +#include "vosim.h" +#include + +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); +} \ No newline at end of file diff --git a/daisysp/Synthesis/vosim.h b/daisysp/Synthesis/vosim.h new file mode 100644 index 0000000..0173d88 --- /dev/null +++ b/daisysp/Synthesis/vosim.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Synthesis/zoscillator.cpp b/daisysp/Synthesis/zoscillator.cpp new file mode 100644 index 0000000..8a4de0b --- /dev/null +++ b/daisysp/Synthesis/zoscillator.cpp @@ -0,0 +1,159 @@ +#include "dsp.h" +#include "zoscillator.h" +#include + +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; +} \ No newline at end of file diff --git a/daisysp/Synthesis/zoscillator.h b/daisysp/Synthesis/zoscillator.h new file mode 100644 index 0000000..0ef1095 --- /dev/null +++ b/daisysp/Synthesis/zoscillator.h @@ -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 +#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 \ No newline at end of file diff --git a/daisysp/Utility/dcblock.cpp b/daisysp/Utility/dcblock.cpp new file mode 100644 index 0000000..5cbe4ad --- /dev/null +++ b/daisysp/Utility/dcblock.cpp @@ -0,0 +1,20 @@ +#include +#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; +} diff --git a/daisysp/Utility/dcblock.h b/daisysp/Utility/dcblock.h new file mode 100644 index 0000000..184173b --- /dev/null +++ b/daisysp/Utility/dcblock.h @@ -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 diff --git a/daisysp/Utility/delayline.h b/daisysp/Utility/delayline.h new file mode 100644 index 0000000..56be5a2 --- /dev/null +++ b/daisysp/Utility/delayline.h @@ -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 +#include +namespace daisysp +{ +/** Simple Delay line. +November 2019 + +Converted to Template December 2019 + +declaration example: (1 second of floats) + +DelayLine del; + +By: shensley +*/ +template +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(delay); + frac_ = delay - static_cast(int_delay); + delay_ = static_cast(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(delay); + float delay_fractional = delay - static_cast(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(delay); + float delay_fractional = delay - static_cast(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 diff --git a/daisysp/Utility/dsp.h b/daisysp/Utility/dsp.h new file mode 100644 index 0000000..619abdb --- /dev/null +++ b/daisysp/Utility/dsp.h @@ -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 +#include +#include +#include + +/** 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 +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 diff --git a/daisysp/Utility/looper.h b/daisysp/Utility/looper.h new file mode 100644 index 0000000..86a009c --- /dev/null +++ b/daisysp/Utility/looper.h @@ -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 +#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(mode_); + m = m + 1; + if(m > kNumModes - 1) + m = 0; + mode_ = static_cast(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(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 diff --git a/daisysp/Utility/maytrig.h b/daisysp/Utility/maytrig.h new file mode 100644 index 0000000..28f7f64 --- /dev/null +++ b/daisysp/Utility/maytrig.h @@ -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 +#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 diff --git a/daisysp/Utility/metro.cpp b/daisysp/Utility/metro.cpp new file mode 100644 index 0000000..217a3d8 --- /dev/null +++ b/daisysp/Utility/metro.cpp @@ -0,0 +1,30 @@ +#include +#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_; +} diff --git a/daisysp/Utility/metro.h b/daisysp/Utility/metro.h new file mode 100644 index 0000000..6f94d06 --- /dev/null +++ b/daisysp/Utility/metro.h @@ -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 +#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 diff --git a/daisysp/Utility/samplehold.h b/daisysp/Utility/samplehold.h new file mode 100644 index 0000000..7bbcfc3 --- /dev/null +++ b/daisysp/Utility/samplehold.h @@ -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 +#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 diff --git a/daisysp/Utility/smooth_random.h b/daisysp/Utility/smooth_random.h new file mode 100644 index 0000000..b5734c6 --- /dev/null +++ b/daisysp/Utility/smooth_random.h @@ -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 +#include +#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 \ No newline at end of file diff --git a/daisysp/daisysp.h b/daisysp/daisysp.h new file mode 100644 index 0000000..646cfa1 --- /dev/null +++ b/daisysp/daisysp.h @@ -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