Skip to content

Instantly share code, notes, and snippets.

@wcalvert
Created March 1, 2021 02:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wcalvert/2a5b5f1c9d0d4066cab27fa6ac526a60 to your computer and use it in GitHub Desktop.
Save wcalvert/2a5b5f1c9d0d4066cab27fa6ac526a60 to your computer and use it in GitHub Desktop.
Mutable Instruments envelopes adapted to Audio library
#include <Arduino.h>
#include "envlut.h"
#include "arm_math.h"
// Todo: handle noteOff occurring before reaching SUSTAIN.
void AudioEffectEnvelopeLUT::noteOn(void) {
__disable_irq();
sample = 0;
count = 0;
state = ATTACK;
__enable_irq();
}
void AudioEffectEnvelopeLUT::noteOff(void) {
__disable_irq();
if(state != IDLE) {
count = 0;
sample = SAMPLES-1.0f;
state = RELEASE;
}
__enable_irq();
}
void AudioEffectEnvelopeLUT::update(void) {
audio_block_t *block = receiveWritable();
if (!block) return;
if (state == IDLE) {
AudioStream::release(block);
return;
}
float32_t level[AUDIO_BLOCK_SAMPLES] = {0.0f};
for(int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
switch(state) {
case IDLE:
level[i] = 0;
break;
case ATTACK:
if(count < a_samples) {
level[i] = (float32_t)block->data[i] * LUT[(uint32_t)sample];
sample += a_inc;
} else {
// duplicate code - we have to do the decay processing here or it will be skipped due to loop
level[i] = (float32_t)block->data[i] * (sustain + ((1.0f-sustain) * LUT[(uint32_t)sample]));
// now setup for next state
count = 0;
sample = SAMPLES-1.0f;
state = DECAY;
}
break;
case DECAY:
if(count < d_samples) {
level[i] = (float32_t)block->data[i] * (sustain + ((1.0f-sustain) * LUT[(uint32_t)sample]));
sample -= d_inc;
} else {
// duplicate code - we have to do the sustian processing here or it will be skipped due to loop
level[i] = (float32_t)block->data[i] * sustain;
// now setup for next state
sample = 0;
count = 0;
state = SUSTAIN;
}
break;
case SUSTAIN:
level[i] = (float32_t)block->data[i] * sustain;
break;
case RELEASE:
if(count < r_samples) {
level[i] = (float32_t)block->data[i] * sustain * LUT[(uint32_t)sample];
sample -= r_inc;
} else {
sample = 0;
state = IDLE;
}
break;
}
count++;
}
// Hack alert, I was getting an output glitch when trying to cast the calculations to int16 without this intermediate step.
// Maybe the compiler got confused and let something wrap around negative or something?
for(int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
//Serial.println(level[i]);
block->data[i] = (int16_t)level[i];
}
transmit(block);
AudioStream::release(block);
}
bool AudioEffectEnvelopeLUT::isActive() {
EnvelopeState current_state = *(volatile EnvelopeState *)&state;
if (current_state == IDLE) return false;
return true;
}
bool AudioEffectEnvelopeLUT::isSustain() {
EnvelopeState current_state = *(volatile EnvelopeState *)&state;
if (current_state == SUSTAIN) return true;
return false;
}
#ifndef effect_envelope_h_
#define effect_envelope_h_
#include "Arduino.h"
#include "Audio.h"
#include "arm_math.h"
const float32_t lut_env_expo[] = {
0.00000, 0.01550, 0.03077, 0.04579,
0.06059, 0.07515, 0.08949, 0.10361,
0.11750, 0.13118, 0.14465, 0.15792,
0.17097, 0.18382, 0.19648, 0.20893,
0.22120, 0.23327, 0.24516, 0.25686,
0.26838, 0.27973, 0.29089, 0.30189,
0.31271, 0.32337, 0.33386, 0.34418,
0.35435, 0.36436, 0.37422, 0.38392,
0.39347, 0.40287, 0.41213, 0.42124,
0.43022, 0.43905, 0.44775, 0.45631,
0.46474, 0.47304, 0.48121, 0.48925,
0.49717, 0.50496, 0.51264, 0.52019,
0.52763, 0.53496, 0.54217, 0.54926,
0.55625, 0.56313, 0.56991, 0.57657,
0.58314, 0.58960, 0.59596, 0.60223,
0.60839, 0.61447, 0.62044, 0.62633,
0.63212, 0.63782, 0.64344, 0.64897,
0.65441, 0.65977, 0.66504, 0.67024,
0.67535, 0.68038, 0.68534, 0.69021,
0.69502, 0.69975, 0.70440, 0.70898,
0.71350, 0.71794, 0.72231, 0.72662,
0.73085, 0.73503, 0.73913, 0.74318,
0.74716, 0.75108, 0.75494, 0.75874,
0.76248, 0.76616, 0.76979, 0.77336,
0.77687, 0.78033, 0.78373, 0.78709,
0.79039, 0.79364, 0.79684, 0.79999,
0.80309, 0.80614, 0.80915, 0.81211,
0.81502, 0.81789, 0.82071, 0.82349,
0.82623, 0.82892, 0.83157, 0.83418,
0.83675, 0.83929, 0.84178, 0.84423,
0.84665, 0.84902, 0.85136, 0.85367,
0.85594, 0.85817, 0.86037, 0.86253,
0.86466, 0.86676, 0.86883, 0.87086,
0.87286, 0.87484, 0.87678, 0.87869,
0.88057, 0.88242, 0.88424, 0.88604,
0.88780, 0.88954, 0.89126, 0.89294,
0.89460, 0.89623, 0.89784, 0.89943,
0.90099, 0.90252, 0.90403, 0.90552,
0.90699, 0.90843, 0.90985, 0.91124,
0.91262, 0.91398, 0.91531, 0.91662,
0.91792, 0.91919, 0.92044, 0.92167,
0.92289, 0.92408, 0.92526, 0.92642,
0.92756, 0.92868, 0.92979, 0.93088,
0.93195, 0.93300, 0.93404, 0.93507,
0.93607, 0.93706, 0.93804, 0.93900,
0.93995, 0.94088, 0.94179, 0.94270,
0.94358, 0.94446, 0.94532, 0.94617,
0.94700, 0.94782, 0.94863, 0.94943,
0.95021, 0.95098, 0.95174, 0.95249,
0.95323, 0.95395, 0.95467, 0.95537,
0.95606, 0.95674, 0.95741, 0.95808,
0.95873, 0.95936, 0.95999, 0.96062,
0.96123, 0.96183, 0.96242, 0.96300,
0.96358, 0.96414, 0.96470, 0.96524,
0.96578, 0.96631, 0.96683, 0.96735,
0.96786, 0.96835, 0.96884, 0.96933,
0.96980, 0.97027, 0.97073, 0.97119,
0.97163, 0.97207, 0.97250, 0.97293,
0.97335, 0.97376, 0.97417, 0.97457,
0.97497, 0.97535, 0.97574, 0.97611,
0.97648, 0.97685, 0.97721, 0.97756,
0.97791, 0.97825, 0.97859, 0.97892,
0.97925, 0.97957, 0.97988, 0.98020,
0.98050, 0.98081, 0.98110, 0.98140,
0.98140
};
const float32_t lut_env_quartic[] = {
0.00000, 0.00000, 0.00000, 0.00000,
0.00000, 0.00000, 0.00000, 0.00001,
0.00001, 0.00001, 0.00002, 0.00003,
0.00004, 0.00005, 0.00006, 0.00008,
0.00010, 0.00012, 0.00015, 0.00018,
0.00021, 0.00025, 0.00029, 0.00034,
0.00039, 0.00044, 0.00050, 0.00057,
0.00064, 0.00072, 0.00081, 0.00090,
0.00100, 0.00111, 0.00123, 0.00135,
0.00148, 0.00163, 0.00178, 0.00194,
0.00211, 0.00229, 0.00248, 0.00268,
0.00289, 0.00311, 0.00335, 0.00360,
0.00386, 0.00413, 0.00442, 0.00472,
0.00503, 0.00536, 0.00570, 0.00606,
0.00644, 0.00683, 0.00723, 0.00765,
0.00809, 0.00855, 0.00902, 0.00952,
0.01003, 0.01056, 0.01111, 0.01167,
0.01226, 0.01287, 0.01350, 0.01415,
0.01482, 0.01552, 0.01624, 0.01698,
0.01774, 0.01853, 0.01934, 0.02017,
0.02103, 0.02192, 0.02283, 0.02377,
0.02473, 0.02572, 0.02674, 0.02779,
0.02886, 0.02997, 0.03110, 0.03226,
0.03345, 0.03467, 0.03593, 0.03721,
0.03853, 0.03988, 0.04126, 0.04267,
0.04412, 0.04560, 0.04712, 0.04867,
0.05026, 0.05188, 0.05354, 0.05523,
0.05697, 0.05874, 0.06054, 0.06239,
0.06428, 0.06620, 0.06817, 0.07017,
0.07222, 0.07431, 0.07643, 0.07861,
0.08082, 0.08308, 0.08538, 0.08773,
0.09012, 0.09255, 0.09503, 0.09756,
0.10013, 0.10275, 0.10542, 0.10814,
0.11090, 0.11372, 0.11658, 0.11950,
0.12246, 0.12547, 0.12854, 0.13166,
0.13483, 0.13805, 0.14133, 0.14466,
0.14805, 0.15149, 0.15499, 0.15854,
0.16215, 0.16581, 0.16954, 0.17332,
0.17716, 0.18106, 0.18502, 0.18904,
0.19312, 0.19726, 0.20146, 0.20572,
0.21005, 0.21444, 0.21889, 0.22341,
0.22799, 0.23264, 0.23736, 0.24214,
0.24698, 0.25190, 0.25688, 0.26193,
0.26705, 0.27224, 0.27750, 0.28283,
0.28823, 0.29371, 0.29925, 0.30487,
0.31056, 0.31633, 0.32217, 0.32808,
0.33407, 0.34014, 0.34628, 0.35250,
0.35880, 0.36517, 0.37163, 0.37816,
0.38477, 0.39147, 0.39824, 0.40510,
0.41203, 0.41906, 0.42616, 0.43335,
0.44062, 0.44798, 0.45542, 0.46295,
0.47056, 0.47826, 0.48605, 0.49393,
0.50190, 0.50995, 0.51810, 0.52633,
0.53466, 0.54308, 0.55159, 0.56019,
0.56889, 0.57768, 0.58657, 0.59555,
0.60463, 0.61380, 0.62307, 0.63243,
0.64190, 0.65146, 0.66112, 0.67089,
0.68075, 0.69071, 0.70078, 0.71094,
0.72121, 0.73159, 0.74206, 0.75264,
0.76333, 0.77412, 0.78502, 0.79602,
0.80713, 0.81835, 0.82968, 0.84112,
0.85266, 0.86432, 0.87609, 0.88797,
0.89996, 0.91206, 0.92428, 0.93661,
0.94906, 0.96162, 0.97430, 0.98709,
0.98709
};
const float32_t lut_raised_cosine[] = {
0.00000, 0.00004, 0.00015, 0.00034,
0.00060, 0.00094, 0.00135, 0.00184,
0.00241, 0.00305, 0.00376, 0.00455,
0.00541, 0.00635, 0.00736, 0.00845,
0.00961, 0.01084, 0.01215, 0.01353,
0.01498, 0.01651, 0.01811, 0.01978,
0.02153, 0.02335, 0.02524, 0.02720,
0.02923, 0.03133, 0.03350, 0.03575,
0.03806, 0.04044, 0.04290, 0.04542,
0.04801, 0.05066, 0.05339, 0.05618,
0.05904, 0.06196, 0.06496, 0.06801,
0.07114, 0.07432, 0.07757, 0.08089,
0.08427, 0.08771, 0.09121, 0.09477,
0.09840, 0.10208, 0.10583, 0.10963,
0.11349, 0.11742, 0.12140, 0.12543,
0.12952, 0.13367, 0.13788, 0.14213,
0.14645, 0.15081, 0.15523, 0.15970,
0.16422, 0.16879, 0.17341, 0.17808,
0.18280, 0.18757, 0.19238, 0.19724,
0.20215, 0.20710, 0.21210, 0.21713,
0.22221, 0.22734, 0.23250, 0.23771,
0.24295, 0.24823, 0.25355, 0.25891,
0.26430, 0.26973, 0.27519, 0.28069,
0.28622, 0.29179, 0.29738, 0.30300,
0.30866, 0.31434, 0.32005, 0.32579,
0.33156, 0.33734, 0.34316, 0.34900,
0.35486, 0.36074, 0.36664, 0.37257,
0.37851, 0.38447, 0.39045, 0.39644,
0.40245, 0.40848, 0.41452, 0.42057,
0.42663, 0.43271, 0.43879, 0.44489,
0.45099, 0.45710, 0.46322, 0.46934,
0.47547, 0.48160, 0.48773, 0.49386,
0.50000, 0.50614, 0.51227, 0.51840,
0.52453, 0.53066, 0.53678, 0.54290,
0.54901, 0.55511, 0.56121, 0.56729,
0.57337, 0.57943, 0.58548, 0.59152,
0.59755, 0.60356, 0.60955, 0.61553,
0.62149, 0.62743, 0.63336, 0.63926,
0.64514, 0.65100, 0.65684, 0.66266,
0.66844, 0.67421, 0.67995, 0.68566,
0.69134, 0.69700, 0.70262, 0.70821,
0.71378, 0.71931, 0.72481, 0.73027,
0.73570, 0.74109, 0.74645, 0.75177,
0.75705, 0.76229, 0.76750, 0.77266,
0.77779, 0.78287, 0.78790, 0.79290,
0.79785, 0.80276, 0.80762, 0.81243,
0.81720, 0.82192, 0.82659, 0.83121,
0.83578, 0.84030, 0.84477, 0.84919,
0.85355, 0.85787, 0.86212, 0.86633,
0.87048, 0.87457, 0.87860, 0.88258,
0.88651, 0.89037, 0.89417, 0.89792,
0.90160, 0.90523, 0.90879, 0.91229,
0.91573, 0.91911, 0.92243, 0.92568,
0.92886, 0.93199, 0.93504, 0.93804,
0.94096, 0.94382, 0.94661, 0.94934,
0.95199, 0.95458, 0.95710, 0.95956,
0.96194, 0.96425, 0.96650, 0.96867,
0.97077, 0.97280, 0.97476, 0.97665,
0.97847, 0.98022, 0.98189, 0.98349,
0.98502, 0.98647, 0.98785, 0.98916,
0.99039, 0.99155, 0.99264, 0.99365,
0.99459, 0.99545, 0.99624, 0.99695,
0.99759, 0.99816, 0.99865, 0.99906,
0.99940, 0.99966, 0.99985, 0.99996,
0.99996
};
#define SAMPLES 257.0f
class AudioEffectEnvelopeLUT : public AudioStream {
public:
enum LUTType {
EXPO,
QUARTIC,
RAISED_COSINE
};
enum EnvelopeState {
IDLE,
ATTACK,
DECAY,
SUSTAIN,
RELEASE
};
AudioEffectEnvelopeLUT() : AudioStream(1, inputQueueArray) {
updateVariables();
}
void noteOn(void);
void noteOff(void);
bool isActive();
bool isSustain();
virtual void update(void);
void setAttack(float32_t attack_) {
attack = attack_;
updateVariables();
}
void setDecay(float32_t decay_) {
decay = decay_;
updateVariables();
}
void setSustain(float32_t sustain_) {
sustain = sustain_;
updateVariables();
}
void setRelease(float32_t release_) {
release = release_;
updateVariables();
}
void setLUT(LUTType lutType) {
switch(lutType) {
case EXPO:
LUT = lut_env_expo;
break;
case QUARTIC:
LUT = lut_env_quartic;
break;
case RAISED_COSINE:
LUT = lut_raised_cosine;
break;
}
}
private:
audio_block_t *inputQueueArray[1];
EnvelopeState state = IDLE;
float32_t attack = 50.0f;
float32_t decay = 50.0f;
float32_t sustain = .7f;
float32_t release = 1000.0;
float32_t sample = 0; // index into the LUT
int32_t count=0; // counter for required number of samples in each state.
int32_t a_samples, d_samples, r_samples;
float32_t a_inc, d_inc, r_inc;
const float32_t *LUT = lut_raised_cosine;
void updateVariables(void) {
a_samples = (attack/1000.0f) * AUDIO_SAMPLE_RATE_EXACT;
d_samples = (decay/1000.0f) * AUDIO_SAMPLE_RATE_EXACT;
r_samples = (release/1000.0f) * AUDIO_SAMPLE_RATE_EXACT;
a_inc = SAMPLES / a_samples;
d_inc = SAMPLES / d_samples;
r_inc = SAMPLES / r_samples;
}
};
#undef SAMPLES_PER_MSEC
#endif
#include <Arduino.h>
#include "envlut.h"
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// GUItool: begin automatically generated code
AudioSynthWaveform waveform1; //xy=171,365
AudioEffectEnvelopeLUT envelope1; //xy=356,351
AudioOutputI2S i2s1; //xy=373,400
AudioOutputAnalogStereo dacs1; //xy=553,351
AudioConnection patchCord1(waveform1, envelope1);
AudioConnection patchCord2(envelope1, 0, i2s1, 0);
AudioConnection patchCord3(envelope1, 0, i2s1, 1);
AudioConnection patchCord4(envelope1, 0, dacs1, 0);
AudioConnection patchCord5(envelope1, 0, dacs1, 1);
AudioControlWM8731 wm8731_1; //xy=507,500
// GUItool: end automatically generated code
elapsedMillis debounce = 0;
#define BUTTON 2
void isr(void) {
if(debounce<300) {
return;
}
debounce = 0;
static int lutnum = 0;
if(envelope1.isSustain()) {
envelope1.noteOff();
if(++lutnum == 3) {
lutnum = 0;
}
switch(lutnum) {
case 0:
envelope1.setLUT(AudioEffectEnvelopeLUT::EXPO);
break;
case 1:
envelope1.setLUT(AudioEffectEnvelopeLUT::QUARTIC);
break;
case 2:
envelope1.setLUT(AudioEffectEnvelopeLUT::RAISED_COSINE);
break;
}
} else {
envelope1.noteOn();
}
}
void setup() {
AudioMemory(30);
wm8731_1.enable();
wm8731_1.volume(1.0f);
waveform1.begin(.5f, 80, WAVEFORM_SQUARE);
envelope1.setAttack(100);
envelope1.setDecay(100);
envelope1.setSustain(.7f);
envelope1.setRelease(500.0f);
Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP);
attachInterrupt(BUTTON, isr, CHANGE);
}
void loop() {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment