portemento, adsr
This commit is contained in:
@ -20,9 +20,9 @@ void Shifter::Init()
|
||||
volume = 1;
|
||||
helm.setframesize(1024);
|
||||
helm.setoverlap(1);
|
||||
for (int i = 0; i < MAX_VOICES + 1; ++i)
|
||||
for (int i = 0; i < MAX_VOICES; ++i)
|
||||
{
|
||||
out_midi[i] = -1;
|
||||
voices[i].Init(48000);
|
||||
}
|
||||
for (int i = 0; i < BUFFER_SIZE; ++i)
|
||||
{
|
||||
@ -33,7 +33,6 @@ void Shifter::Init()
|
||||
for (int i = 0; i < 8192; ++i) {
|
||||
cos_lookup[i] = cos(2 * PI_F * i / 8192.0);
|
||||
}
|
||||
out_panning[MAX_VOICES] = 0.5f;
|
||||
}
|
||||
|
||||
void Shifter::Process(const float* const* in,
|
||||
@ -104,22 +103,25 @@ void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
|
||||
|
||||
if (midi != last_autotune_midi) {
|
||||
last_autotune_midi = midi;
|
||||
out_periods[MAX_VOICES] = in_period;
|
||||
out_period = in_period;
|
||||
}
|
||||
|
||||
float error = target_out_period - out_periods[MAX_VOICES];
|
||||
float error = target_out_period - out_period;
|
||||
float adjustment = error * out_period_filter_amount;
|
||||
|
||||
//target_out_period = in_period * out_period_filter_amount + target_out_period * (1 - out_period_filter_amount);
|
||||
out_midi[MAX_VOICES] = midi;
|
||||
out_periods[MAX_VOICES] += adjustment;
|
||||
out_midi = midi;
|
||||
out_period += adjustment;
|
||||
}
|
||||
|
||||
void Shifter::SetRates() {}
|
||||
|
||||
float Shifter::GetOutputEnvelopePeriod(int out_voice) {
|
||||
if (out_voice >= MAX_VOICES) {
|
||||
return in_period * formant_preserve + out_period *(1.0 - formant_preserve);
|
||||
}
|
||||
//TODO add something so that low pitch ratios end up reducing formant_preservation
|
||||
return in_period * formant_preserve + out_periods[out_voice] * (1.0 - formant_preserve);
|
||||
return in_period * formant_preserve + voices[out_voice].CurrentPeriod() * (1.0 - formant_preserve);
|
||||
}
|
||||
|
||||
int Shifter::GetPeakIndex() {
|
||||
@ -198,8 +200,16 @@ void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_pe
|
||||
float interp = f_index - (int)f_index;
|
||||
mult = .5 * (1 - cos_lookup[(int)((float)j / (resampling_period * 2.0) * 8191.0)]);
|
||||
float value = ((1 - interp) * in_buffer[(int)f_index] + (interp)*in_buffer[(int)(f_index + 1) % 8192]) * mult;
|
||||
out_buffer[0][out_index] += value * (1 - out_panning[voice]);
|
||||
out_buffer[1][out_index] += value * out_panning[voice];
|
||||
if(voice >= MAX_VOICES) {
|
||||
//value *= volume;
|
||||
out_buffer[0][out_index] += value;
|
||||
out_buffer[1][out_index] += value;
|
||||
} else {
|
||||
value *= voices[voice].CurrentAmplitude() * volume;
|
||||
out_buffer[0][out_index] += value * voices[voice].GetPanning(0);
|
||||
out_buffer[1][out_index] += value * voices[voice].GetPanning(1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
f_index += period_ratio;
|
||||
@ -220,13 +230,12 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
||||
{
|
||||
|
||||
//add new samples if necessary
|
||||
for (int out_p = 0; out_p < MAX_VOICES + 1; ++out_p)
|
||||
for (int out_p = 0; out_p < MAX_VOICES; ++out_p)
|
||||
{
|
||||
if (out_midi[out_p] == -1) continue;
|
||||
if (out_period_counters[out_p] > out_periods[out_p])
|
||||
if (!voices[out_p].IsActive()) continue;
|
||||
voices[out_p].Process();
|
||||
if (voices[out_p].PeriodOverflow())
|
||||
{
|
||||
out_period_counters[out_p] -= out_periods[out_p];
|
||||
|
||||
float resampling_period = GetOutputEnvelopePeriod(out_p);
|
||||
|
||||
|
||||
@ -237,6 +246,19 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
||||
AddInterpolatedFrame(out_p, max_index, resampling_period);
|
||||
}
|
||||
}
|
||||
if (out_period_counter > out_period)
|
||||
{
|
||||
out_period_counter -= out_period;
|
||||
float resampling_period = GetOutputEnvelopePeriod(MAX_VOICES);
|
||||
|
||||
|
||||
|
||||
//find the start index
|
||||
int max_index = GetPeakIndex();
|
||||
|
||||
//add samples centered on that max
|
||||
AddInterpolatedFrame(MAX_VOICES, max_index, resampling_period);
|
||||
}
|
||||
//add input samples
|
||||
in_buffer[in_playhead] = input[i];
|
||||
|
||||
@ -256,27 +278,20 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
||||
{
|
||||
out_playhead -= BUFFER_SIZE;
|
||||
}
|
||||
for (int out_p = 0; out_p < MAX_VOICES + 1; ++out_p)
|
||||
{
|
||||
if (out_midi[out_p] == -1) continue;
|
||||
out_period_counters[out_p] += 1;
|
||||
}
|
||||
out_period_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Shifter::AddMidiNote(int note) {
|
||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||
if (out_midi[i] == note) {
|
||||
if (voices[i].IsActive() && voices[i].GetMidiNote() == note) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||
if (out_midi[i] == -1) {
|
||||
out_midi[i] = note;
|
||||
out_periods[i] = 48000.0f / mtof(note);
|
||||
out_period_counters[i] = 0;
|
||||
out_panning[i] = rand() / (float)RAND_MAX;
|
||||
if (!voices[i].IsActive()) {
|
||||
voices[i].Trigger(note);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -285,8 +300,8 @@ void Shifter::AddMidiNote(int note) {
|
||||
|
||||
void Shifter::RemoveMidiNote(int note) {
|
||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||
if (out_midi[i] == note) {
|
||||
out_midi[i] = -1;
|
||||
if (voices[i].IsActive() && voices[i].GetMidiNote() == note) {
|
||||
voices[i].Release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#define SHIFTER_H
|
||||
|
||||
#include "Helmholtz.h"
|
||||
#include "shifter_voice.h"
|
||||
|
||||
#define BUFFER_SIZE 8192
|
||||
#define MAX_VOICES 12
|
||||
@ -84,7 +85,7 @@ public:
|
||||
void SetFormantPreserve(float val) { formant_preserve = val; }
|
||||
void SetAutoTuneSpeed(float val) { out_period_filter_amount = 1 - val; }
|
||||
|
||||
int out_midi[MAX_VOICES + 1] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 };
|
||||
int out_midi = -1;
|
||||
|
||||
private:
|
||||
void DetectPitch(const float* const* in, float** out, size_t size);
|
||||
@ -133,11 +134,12 @@ private:
|
||||
int last_autotune_midi = -1;
|
||||
|
||||
float out_period_filter_amount = 0.7f; // You can expose this as a parameter
|
||||
|
||||
ShifterVoice voices[MAX_VOICES];
|
||||
|
||||
float out_periods[MAX_VOICES + 1] = { 0,0,0,0,0,0,0,0,0,0,0,0 }; //C3
|
||||
float out_panning[MAX_VOICES + 1] = { 0,0,0,0,0,0,0,0,0,0,0,.5 }; //C3
|
||||
float out_period = 0; //C3
|
||||
float in_period = 366.936;
|
||||
float out_period_counters[MAX_VOICES + 1] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
|
||||
float out_period_counter = 0;
|
||||
float cos_lookup[8192];
|
||||
};
|
||||
#endif
|
||||
@ -288,7 +288,7 @@ WebViewPluginAudioProcessor::WebViewPluginAudioProcessor(AudioProcessorValueTree
|
||||
void WebViewPluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
|
||||
{
|
||||
const auto channels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels());
|
||||
|
||||
shifter.Init();
|
||||
if (channels == 0)
|
||||
return;
|
||||
|
||||
@ -335,7 +335,7 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
||||
{
|
||||
//DBG(shifter.out_midi[MAX_VOICES]);
|
||||
//push midi note
|
||||
spectralBars.push(shifter.out_midi[MAX_VOICES]);
|
||||
//spectralBars.push(shifter.out_midi[MAX_VOICES]);
|
||||
const SpinLock::ScopedTryLockType lock(spectrumDataLock);
|
||||
|
||||
if (!lock.isLocked())
|
||||
|
||||
148
Source/adsr.cpp
Normal file
148
Source/adsr.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
adsr.cpp
|
||||
Created: 25 Oct 2025 2:09:32pm
|
||||
Author: mickl
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "adsr.h"
|
||||
#include "math.h"
|
||||
|
||||
#ifndef M_E
|
||||
#define M_E 2.71828182845904523536
|
||||
#endif
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
98
Source/adsr.h
Normal file
98
Source/adsr.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
adsr.h
|
||||
Created: 25 Oct 2025 2:09:32pm
|
||||
Author: mickl
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
/** 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 };
|
||||
};
|
||||
34
Source/port.cpp
Normal file
34
Source/port.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
port.cpp
|
||||
Created: 25 Oct 2025 2:09:16pm
|
||||
Author: mickl
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "port.h"
|
||||
#include "math.h"
|
||||
|
||||
void Port::Init(float sample_rate, float htime)
|
||||
{
|
||||
yt1_ = 0;
|
||||
prvhtim_ = -100.0;
|
||||
htime_ = htime;
|
||||
|
||||
sample_rate_ = sample_rate;
|
||||
onedsr_ = 1.0 / sample_rate_;
|
||||
}
|
||||
|
||||
float Port::Process(float in)
|
||||
{
|
||||
if (prvhtim_ != htime_)
|
||||
{
|
||||
c2_ = powf(0.5, onedsr_ / htime_);
|
||||
c1_ = 1.0 - c2_;
|
||||
prvhtim_ = htime_;
|
||||
}
|
||||
|
||||
return yt1_ = c1_ * in + c2_ * yt1_;
|
||||
}
|
||||
42
Source/port.h
Normal file
42
Source/port.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
port.h
|
||||
Created: 25 Oct 2025 2:09:16pm
|
||||
Author: mickl
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
class Port
|
||||
{
|
||||
public:
|
||||
Port() {}
|
||||
~Port() {}
|
||||
/** Initializes Port module
|
||||
|
||||
\param sample_rate: sample rate of audio engine
|
||||
\param htime: half-time of the function, in seconds.
|
||||
*/
|
||||
|
||||
void Init(float sample_rate, float htime);
|
||||
|
||||
/** Applies portamento to input signal and returns processed signal.
|
||||
\return slewed output signal
|
||||
*/
|
||||
float Process(float in);
|
||||
|
||||
|
||||
/** Sets htime
|
||||
*/
|
||||
inline void SetHtime(float htime) { htime_ = htime; }
|
||||
/** returns current value of htime
|
||||
*/
|
||||
inline float GetHtime() { return htime_; }
|
||||
|
||||
private:
|
||||
float htime_;
|
||||
float c1_, c2_, yt1_, prvhtim_;
|
||||
float sample_rate_, onedsr_;
|
||||
};
|
||||
91
Source/shifter_voice.cpp
Normal file
91
Source/shifter_voice.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
shifter_voice.cpp
|
||||
Created: 25 Oct 2025 2:09:42pm
|
||||
Author: mickl
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "shifter_voice.h"
|
||||
|
||||
|
||||
static inline float mtof(float m)
|
||||
{
|
||||
return powf(2, (m - 69.0f) / 12.0f) * 440.0f;
|
||||
}
|
||||
|
||||
|
||||
void ShifterVoice::Init(float sample_rate) {
|
||||
portamento_.Init(sample_rate, 0.05f); //default portamento time
|
||||
amplitude_envelope_.Init(sample_rate);
|
||||
amplitude_envelope_.SetAttackTime(0.2f);
|
||||
amplitude_envelope_.SetDecayTime(0.2f);
|
||||
amplitude_envelope_.SetReleaseTime(1.0f);
|
||||
onoff_ = false;
|
||||
overflow_ = false;
|
||||
current_midi = 60;
|
||||
current_period_ = 48000.0f / mtof((float)current_midi);
|
||||
current_amplitude = 0.0f;
|
||||
period_counter = 0.0f;
|
||||
panning = 0.5f;
|
||||
}
|
||||
|
||||
|
||||
bool ShifterVoice::IsActive() { return amplitude_envelope_.IsRunning(); }
|
||||
|
||||
void ShifterVoice::Trigger(int midi_note) {
|
||||
current_midi = midi_note;
|
||||
// Retrigger envelope
|
||||
amplitude_envelope_.Retrigger(false);
|
||||
onoff_ = true;
|
||||
panning = rand() / (float)RAND_MAX;
|
||||
}
|
||||
|
||||
void ShifterVoice::Release() {
|
||||
onoff_ = false;
|
||||
}
|
||||
|
||||
void ShifterVoice::Process() {
|
||||
current_amplitude = amplitude_envelope_.Process(onoff_);
|
||||
current_period_ = 48000.0f / mtof(portamento_.Process((float)current_midi));
|
||||
period_counter++;
|
||||
overflow_ = period_counter >= current_period_;
|
||||
if (overflow_) {
|
||||
period_counter -= current_period_;
|
||||
}
|
||||
}
|
||||
|
||||
float ShifterVoice::CurrentAmplitude() {
|
||||
return current_amplitude;
|
||||
}
|
||||
|
||||
float ShifterVoice::CurrentPeriod() {
|
||||
return current_period_;
|
||||
}
|
||||
|
||||
bool ShifterVoice::PeriodOverflow() {
|
||||
return overflow_;
|
||||
}
|
||||
|
||||
void ShifterVoice::SetPortamentoTime(float time) {
|
||||
portamento_.SetHtime(time);
|
||||
}
|
||||
|
||||
void ShifterVoice::SetAdsrTimes(float attack, float decay, float release) {
|
||||
amplitude_envelope_.SetAttackTime(attack);
|
||||
amplitude_envelope_.SetDecayTime(decay);
|
||||
amplitude_envelope_.SetReleaseTime(release);
|
||||
amplitude_envelope_.SetSustainLevel(1.0);
|
||||
}
|
||||
|
||||
float ShifterVoice::GetPanning(int channel) const {
|
||||
switch (channel) {
|
||||
default:
|
||||
case 0:
|
||||
return panning < .5 ? 1.0 : 2.0f - (panning * 2.0f);
|
||||
case 1:
|
||||
return panning > .5 ? 1.0 : panning * 2.0f;
|
||||
}
|
||||
}
|
||||
46
Source/shifter_voice.h
Normal file
46
Source/shifter_voice.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
shifter_voice.h
|
||||
Created: 25 Oct 2025 2:09:42pm
|
||||
Author: mickl
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "adsr.h"
|
||||
#include "port.h"
|
||||
#include "math.h"
|
||||
#include <juce_core/juce_core.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
class ShifterVoice {
|
||||
public:
|
||||
ShifterVoice() {}
|
||||
~ShifterVoice() {}
|
||||
void Init(float sample_rate);
|
||||
bool IsActive();
|
||||
void Trigger(int midi_note);
|
||||
void Release();
|
||||
void Process();
|
||||
float CurrentAmplitude();
|
||||
float CurrentPeriod();
|
||||
bool PeriodOverflow();
|
||||
void SetPortamentoTime(float time);
|
||||
void SetAdsrTimes(float attack, float decay, float release);
|
||||
float GetPanning(int channel) const;
|
||||
int GetMidiNote() const { return current_midi; }
|
||||
|
||||
private:
|
||||
Port portamento_;
|
||||
Adsr amplitude_envelope_;
|
||||
bool onoff_;
|
||||
bool overflow_;
|
||||
int current_midi;
|
||||
float current_period_;
|
||||
float current_amplitude;
|
||||
float period_counter;
|
||||
float panning;
|
||||
};
|
||||
Reference in New Issue
Block a user