| // By Richard van Hoesel | // By Richard van Hoesel | ||||
| // https://forum.pjrc.com/threads/60488?p=269756&viewfull=1#post269756 | // https://forum.pjrc.com/threads/60488?p=269756&viewfull=1#post269756 | ||||
| // Ladder filter demo with continuous 3-saw drone into the filter | |||||
| // with separate LFOs modulating filter frequency and resonance. | |||||
| // By Richard van Hoesel | |||||
| // https://forum.pjrc.com/threads/60488?p=269756&viewfull=1#post269756 | |||||
| #include <Audio.h> | #include <Audio.h> | ||||
| AudioSynthWaveform waveform1; | AudioSynthWaveform waveform1; | ||||
| sgtl5000_1.enable(); | sgtl5000_1.enable(); | ||||
| sgtl5000_1.volume(0.6); | sgtl5000_1.volume(0.6); | ||||
| filter1.resonance(0.55); | |||||
| filter1.frequency(4000); | |||||
| filter1.resonance(0.55); // "lfo2" waveform overrides this setting | |||||
| filter1.frequency(800); // "lfo1" modifies this 800 Hz setting | |||||
| filter1.octaveControl(2.6); // up 2.6 octaves (4850 Hz) & down 2.6 octaves (132 Hz) | |||||
| waveform1.frequency(50); | waveform1.frequency(50); | ||||
| waveform2.frequency(100.1); | waveform2.frequency(100.1); | ||||
| waveform3.frequency(150.3); | waveform3.frequency(150.3); | ||||
| waveform2.begin(WAVEFORM_BANDLIMIT_SAWTOOTH); | waveform2.begin(WAVEFORM_BANDLIMIT_SAWTOOTH); | ||||
| waveform3.begin(WAVEFORM_BANDLIMIT_SAWTOOTH); | waveform3.begin(WAVEFORM_BANDLIMIT_SAWTOOTH); | ||||
| lfo1.frequency(0.2); | lfo1.frequency(0.2); | ||||
| lfo1.amplitude(0.985); | |||||
| lfo1.amplitude(0.99); | |||||
| lfo1.phase(270); | lfo1.phase(270); | ||||
| lfo1.begin(WAVEFORM_TRIANGLE); | |||||
| lfo2.frequency(0.07); | lfo2.frequency(0.07); | ||||
| lfo2.amplitude(0.55); | lfo2.amplitude(0.55); | ||||
| lfo2.phase(270); | lfo2.phase(270); | ||||
| lfo2.begin(WAVEFORM_SINE); | |||||
| } | } | ||||
| void loop() { | void loop() { | ||||
| Serial.print("CPU Usage: "); | |||||
| Serial.print("Filter CPU Usage: "); | |||||
| Serial.print(filter1.processorUsageMax()); | |||||
| Serial.print("%, Total CPU Usage: "); | |||||
| Serial.print(AudioProcessorUsageMax()); | Serial.print(AudioProcessorUsageMax()); | ||||
| Serial.println("%"); | Serial.println("%"); | ||||
| filter1.processorUsageMaxReset(); | |||||
| AudioProcessorUsageMaxReset(); | AudioProcessorUsageMaxReset(); | ||||
| delay(1000); | delay(1000); | ||||
| } | } |
| #define MOOG_PI ((float)3.14159265358979323846264338327950288) | #define MOOG_PI ((float)3.14159265358979323846264338327950288) | ||||
| #define MAX_RESONANCE ((float)1.1) | #define MAX_RESONANCE ((float)1.1) | ||||
| #define MAX_FREQUENCY ((float)(AUDIO_SAMPLE_RATE_EXACT * 0.49f)) | |||||
| float AudioFilterLadder::LPF(float s, int i) | float AudioFilterLadder::LPF(float s, int i) | ||||
| { | { | ||||
| compute_coeffs(c); | compute_coeffs(c); | ||||
| } | } | ||||
| void AudioFilterLadder::octaveControl(float octaves) | |||||
| { | |||||
| if (octaves > 7.0f) { | |||||
| octaves = 7.0f; | |||||
| } else if (octaves < 0.0f) { | |||||
| octaves = 0.0f; | |||||
| } | |||||
| octaveScale = octaves / 32768.0f; | |||||
| } | |||||
| void AudioFilterLadder::compute_coeffs(float c) | void AudioFilterLadder::compute_coeffs(float c) | ||||
| { | { | ||||
| if (c > 0.49f * AUDIO_SAMPLE_RATE_EXACT) { | |||||
| c = 0.49f * AUDIO_SAMPLE_RATE_EXACT; | |||||
| if (c > MAX_FREQUENCY) { | |||||
| c = MAX_FREQUENCY; | |||||
| } else if (c < 1.0f) { | } else if (c < 1.0f) { | ||||
| c = 1.0f; | c = 1.0f; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| static inline float fast_exp2f(float x) | |||||
| { | |||||
| float i; | |||||
| float f = modff(x, &i); | |||||
| f *= 0.693147f / 256.0f; | |||||
| f += 1.0f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f *= f; | |||||
| f = ldexpf(f, i); | |||||
| return f; | |||||
| } | |||||
| static inline float fast_tanh(float x) | static inline float fast_tanh(float x) | ||||
| { | { | ||||
| float x2 = x * x; | float x2 = x * x; | ||||
| for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | ||||
| float input = blocka->data[i] * (1.0f/32768.0f); | float input = blocka->data[i] * (1.0f/32768.0f); | ||||
| if (FCmodActive) { | if (FCmodActive) { | ||||
| float FCmod = blockb->data[i] * (1.0f/32768.0f); | |||||
| // TODO: should this be "volts per octave"? | |||||
| float ftot = Fbase + Fbase * FCmod; | |||||
| float FCmod = blockb->data[i] * octaveScale; | |||||
| float ftot = Fbase * fast_exp2f(FCmod); | |||||
| if (ftot > MAX_FREQUENCY) ftot = MAX_FREQUENCY; | |||||
| if (FCmod != 0) compute_coeffs(ftot); | if (FCmod != 0) compute_coeffs(ftot); | ||||
| } | } | ||||
| if (QmodActive) { | if (QmodActive) { |
| AudioFilterLadder() : AudioStream(3, inputQueueArray) {}; | AudioFilterLadder() : AudioStream(3, inputQueueArray) {}; | ||||
| void frequency(float FC); | void frequency(float FC); | ||||
| void resonance(float reson); | void resonance(float reson); | ||||
| void octaveControl(float octaves); | |||||
| virtual void update(void); | virtual void update(void); | ||||
| private: | private: | ||||
| float LPF(float s, int i); | float LPF(float s, int i); | ||||
| float z1[4] = {0.0, 0.0, 0.0, 0.0}; | float z1[4] = {0.0, 0.0, 0.0, 0.0}; | ||||
| float K = 1.0; | float K = 1.0; | ||||
| float Fbase = 1000; | float Fbase = 1000; | ||||
| float octaveScale = 1.0f/32768.0f; | |||||
| //float Qbase = 0.5; | //float Qbase = 0.5; | ||||
| //float overdrive = 1.0; | //float overdrive = 1.0; | ||||
| audio_block_t *inputQueueArray[3]; | audio_block_t *inputQueueArray[3]; |