#ifndef ENVELOPE_H #define ENVELOPE_H #include "arke.h" #include "IModule.h" #include class Envelope : public IModule { public: Envelope(); ~Envelope(); void noteOn(); void noteOff(); void reset(); bool update(); void process(float* buffer, uint sampleFrames); float run(uint samplesSince); void setAdsr(float att, float dec, float sus, float rel); private: struct Stage { float speed; float target; float threshold; }; bool active; bool gate; uint stage; float position; uint loopStart; uint loopEnd; Stage stages[4]; }; #endif //ENVELOPE_H #include "Envelope.h" #include "arke.h" #include const float kThreshold = 0.001; Envelope::Envelope() { position = 0.0f; stage = 0; setAdsr(1.0f, 1.0f, 1.0f, 1.0f); } Envelope::~Envelope() { } void Envelope::setAdsr(float att, float dec, float sus, float rel) { stages[0].target = 1.25f; stages[0].threshold = 1.0f; stages[0].speed = att; stages[1].target = sus; stages[1].threshold = sus; stages[1].speed = dec; stages[2].target = sus; stages[2].threshold = sus; stages[2].speed = dec; stages[3].target = 0.0f; stages[3].threshold = 0.0f; stages[3].speed = rel; } void Envelope::noteOff() { gate = false; } void Envelope::noteOn() { gate = true; active = true; position = 0.0f; stage = 0; } void Envelope::reset() { active = false; gate = false; } bool Envelope::update() { if(!gate) { stage = 3; } if(fabs(position - stages[stage].threshold) < kThreshold) { position = stages[stage].threshold; stage++; if(stage == 4) { reset(); } if((stage == 3) && gate) { stage = 2; } } return active; } void Envelope::process(float *buffer, uint sampleFrames) { // linear interpolation between envelope updates (user won't notice or care) float target = run(sampleFrames); float inc = (target - position) / (float) sampleFrames; while(sampleFrames--) { *buffer *= position; position += inc; buffer++; } } float Envelope::run(uint samplesSince) { /* The envelope is produced by an RC filter receiving different * coefficients depending on stage, receiving the stage target * as input. This would usually mean processing for samplesSince * samples, but I'm a cheap bastard so the filter is processed at * samplerate/samplesSince for one iteration. Slower for one or * two samples, faster for anything more since this function now * becomes an O(1) operation. Yay! */ // number of samples at sampleRate float timeConstant = stages[stage].speed * sampleRate / 1000.0f; // adjusted rate timeConstant = timeConstant / (float) samplesSince; // coefficient, adjusted to the new sampleRate float coeff = powf(1000.0f, -1.0f / timeConstant); // one "iteration" position = coeff * position + (1.0f - coeff) * stages[stage].target; // clip the value if(position > 1.0f) { position = 1.0f; } else if(position < 0.0f) { position = 0.0f; } // return return position; }