From 095ee79e15a0ccc6af73bc5dcd96ba6a6b350cfb Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 13 Oct 2018 10:45:22 -0400 Subject: [PATCH] LFO now works as a vector --- .../TremoloDemoExpansion.ino | 3 +- src/AudioEffectTremolo.h | 4 +- src/LibBasicFunctions.h | 60 ++++----- src/common/LowFrequencyOscillator.cpp | 119 ++++++++++++------ src/effects/AudioEffectTremolo.cpp | 32 ++++- 5 files changed, 146 insertions(+), 72 deletions(-) diff --git a/examples/Modulation/TremoloDemoExpansion/TremoloDemoExpansion.ino b/examples/Modulation/TremoloDemoExpansion/TremoloDemoExpansion.ino index f7eb925..6892fe2 100644 --- a/examples/Modulation/TremoloDemoExpansion/TremoloDemoExpansion.ino +++ b/examples/Modulation/TremoloDemoExpansion/TremoloDemoExpansion.ino @@ -180,7 +180,8 @@ void loop() { // Use the loopCounter to roughly measure human timescales. Every few seconds, print the CPU usage // to the serial port. About 500,000 loops! - if (loopCount % 524288 == 0) { + //if (loopCount % 524288 == 0) { + if (loopCount % 25000 == 0) { Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage()); Serial.print("% "); Serial.print(" tremolo: "); Serial.print(tremolo.processorUsage()); diff --git a/src/AudioEffectTremolo.h b/src/AudioEffectTremolo.h index f01c1c9..58de0cd 100644 --- a/src/AudioEffectTremolo.h +++ b/src/AudioEffectTremolo.h @@ -49,7 +49,7 @@ public: virtual ~AudioEffectTremolo(); ///< Destructor // *** PARAMETERS *** - void rate(float rateValue) { m_rate = rateValue; } + void rate(float rateValue); void depth(float depthValue) { m_depth = depthValue; } @@ -108,7 +108,7 @@ public: private: audio_block_t *m_inputQueueArray[1]; - BALibrary::LowFrequencyOscillator *m_osc = nullptr; + BALibrary::LowFrequencyOscillatorVector m_osc; int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping bool m_isOmni = false; bool m_bypass = true; diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index c1f8fdf..5022038 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -22,6 +22,7 @@ #include #include +#include #include #include "Arduino.h" @@ -411,44 +412,47 @@ enum class Waveform : unsigned { * The LFO is commonly used on modulation effects where some parameter (delay, * volume, etc.) is modulated via waveform at a frequency below 20 Hz. Waveforms * vary between -1.0f and +1.0f. + * @details this LFO is for operating on vectors of audio block samples. *****************************************************************************/ template -class LowFrequencyOscillator { +class LowFrequencyOscillatorVector { public: + /// Default constructor, uses SINE as default waveform + LowFrequencyOscillatorVector() {} -// /// Supported waveform precisions -// enum class Precision { -// FLOAT, ///< single-precision floating point -// DOUBLE, ///< double-precision floating point -// INT16, ///< Q15 integer precision -// INT32, ///< Q31 integer precision -// }; + /// Specifies the desired waveform at construction time. + /// @param waveform specifies desired waveform + LowFrequencyOscillatorVector(Waveform waveform) : m_waveform(waveform) {}; + /// Default destructor + ~LowFrequencyOscillatorVector() {} - // TODO: permit changing the mode/rate without destruction - LowFrequencyOscillator() = delete; - LowFrequencyOscillator(Waveform mode, unsigned frequencyHz); - ~LowFrequencyOscillator(); + /// Change the waveform + /// @param waveform specifies desired waveform + void setWaveform(Waveform waveform) { m_waveform = waveform; } - /// Reset the waveform back to initial phase - void reset(); + /// Set the LFO rate in Hertz + /// @param frequencyHz the LFO frequency in Hertz + void setRateAudio(float frequencyHz); - /// Get the next waveform value - /// @returns the next value as a float - T getNext(); + /// Set the LFO rate as a fraction + /// @param ratio the radians/sample will be 2*pi*ratio + void setRateRatio(float ratio); - /// Get a vector of the next AUDIO_BLOCK_SAMPLES waveform samples as - /// q15, a signed fixed point number form -1 to +0.999.. - T getVector(T *targetVector); + /// Get the next waveform value + /// @returns the next vector of phase values + T *getNextVector(); private: - void updatePhase(); - Waveform m_mode; - T *m_waveformLut = nullptr; - size_t m_periodSamples = 0; - float m_radiansPerSample = 0.0f; - float m_phase = 0.0f; - float m_volume = 1.0f; - + void m_updatePhase(); ///< called internally when updating the phase vector + void m_initPhase(T radiansPerSample); ///< called internally to reset phase upon rate change + Waveform m_waveform = Waveform::SINE; ///< LFO waveform + T m_phaseVec[AUDIO_BLOCK_SAMPLES]; ///< the vector of next phase values + T m_radiansPerBlock = 0.0f; ///< stores the change in radians over one block of data + T m_outputVec[AUDIO_BLOCK_SAMPLES]; ///< stores the output LFO values + std::atomic_flag m_phaseLock = ATOMIC_FLAG_INIT; ///< used for thread-safety on m_phaseVec + const T PI_F = 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899; ///< 2*PI + const T TWO_PI_F = 2.0 * 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899; ///< 2*PI + const T PI_DIV2_F = 0.5 * 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899; ///< PI/2 }; } // BALibrary diff --git a/src/common/LowFrequencyOscillator.cpp b/src/common/LowFrequencyOscillator.cpp index 2cf8501..44924f3 100644 --- a/src/common/LowFrequencyOscillator.cpp +++ b/src/common/LowFrequencyOscillator.cpp @@ -24,64 +24,110 @@ namespace BALibrary { -constexpr float TWO_PI_F = 2.0f*3.1415927; - template -LowFrequencyOscillator::LowFrequencyOscillator(Waveform mode, unsigned frequencyHz) +void LowFrequencyOscillatorVector::m_initPhase(T radiansPerSample) { - // Given the fixed sample rate, determine how many samples are in the waveform. - m_periodSamples = AUDIO_SAMPLE_RATE_EXACT / frequencyHz; - m_radiansPerSample = (float)TWO_PI_F / (float)m_periodSamples; - m_mode = mode; - - switch(mode) { - case Waveform::SINE : - break; - case Waveform::SQUARE : - break; - case Waveform::TRIANGLE : - break; - case Waveform::RANDOM : - break; - default : - assert(0); // This occurs if a Waveform type is missing from the switch statement + // Initialize the phase vector starting at 0 radians, and incrementing + // by radiansPerSample for each element in the vector. + T initialPhase[AUDIO_BLOCK_SAMPLES]; + for (auto i=0; i -LowFrequencyOscillator::~LowFrequencyOscillator() +void LowFrequencyOscillatorVector::setRateAudio(float frequencyHz) { - + T radiansPerSample; + if (frequencyHz == 0) { + radiansPerSample = 0; + } else { + T periodSamples = AUDIO_SAMPLE_RATE_EXACT / frequencyHz; + radiansPerSample = (T)TWO_PI_F / periodSamples; + } + m_initPhase(radiansPerSample); } + +// This function is used when the LFO is being called at some rate other than +// the audio rate. Here you can manually set the radians per sample as a fraction +// of 2*PI template -void LowFrequencyOscillator::reset() +void LowFrequencyOscillatorVector::setRateRatio(float ratio) { - m_phase = 0; + T radiansPerSample; + if (ratio == 0) { + radiansPerSample = 0; + } else { + radiansPerSample = (T)TWO_PI_F * ratio; + } + m_initPhase(radiansPerSample); } +// When this function is called, it will update the phase vector by incrementing by +// radians per block which is radians per sample * block size. template -inline void LowFrequencyOscillator::updatePhase() +inline void LowFrequencyOscillatorVector::m_updatePhase() { - //if (m_phase < m_periodSamples-1) { m_phase++; } - //else { m_phase = 0; } - m_phase += m_radiansPerSample; - //if (m_phase < (TWO_PI_F-m_radiansPerSample)) { m_phase += m_radiansPerSample; } - //else { m_phase = 0.0f; } + if (m_phaseLock.test_and_set()) { return; } + + if (m_phaseVec[0] > TWO_PI_F) { + arm_offset_f32(m_phaseVec, -TWO_PI_F + m_radiansPerBlock, m_phaseVec, AUDIO_BLOCK_SAMPLES); + } else { + arm_offset_f32(m_phaseVec, m_radiansPerBlock, m_phaseVec, AUDIO_BLOCK_SAMPLES); + } + m_phaseLock.clear(); } +// This function will compute the vector of samples for the output waveform using +// the current phase vector. template -T LowFrequencyOscillator::getNext() +T *LowFrequencyOscillatorVector::getNextVector() { - T value = 0.0f; - updatePhase(); - switch(m_mode) { + switch(m_waveform) { case Waveform::SINE : - value = sin(m_phase); + for (auto i=0; i 3*PI_F) { + m_outputVec[i] = 0.0f; + } + else if (m_phaseVec[i] > 2*PI_F) { + m_outputVec[i] = 1.0f; + } + else if (m_phaseVec[i] > PI_F) { + m_outputVec[i] = 0.0f; + } else { + m_outputVec[i] = 1.0f; + } + } break; case Waveform::TRIANGLE : +// for (auto i=0; i 3*PI_F) { +// m_outputVec[i] = ; +// } +// else if (m_phaseVec[i] > 2*PI_F) { +// m_outputVec[i] = 1.0f; +// } +// else if (m_phaseVec[i] > PI_F) { +// m_outputVec[i] = 0.0f; +// } else { +// m_outputVec[i] = 1.0f; +// } +// } break; case Waveform::RANDOM : break; @@ -89,10 +135,11 @@ T LowFrequencyOscillator::getNext() assert(0); // This occurs if a Waveform type is missing from the switch statement } - return value; + m_updatePhase(); + return m_outputVec; } -template class LowFrequencyOscillator; +template class LowFrequencyOscillatorVector; } // namespace BALibrary diff --git a/src/effects/AudioEffectTremolo.cpp b/src/effects/AudioEffectTremolo.cpp index b0fcdf0..0bb4d67 100644 --- a/src/effects/AudioEffectTremolo.cpp +++ b/src/effects/AudioEffectTremolo.cpp @@ -4,6 +4,7 @@ * Created on: Jan 7, 2018 * Author: slascos */ +#include // std::roundf #include "AudioEffectTremolo.h" using namespace BALibrary; @@ -13,10 +14,12 @@ namespace BAEffects { constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; +constexpr float MAX_RATE_HZ = 20.0f; + AudioEffectTremolo::AudioEffectTremolo() : AudioStream(1, m_inputQueueArray) { - m_osc = new LowFrequencyOscillator(Waveform::SINE, 4*128); + m_osc.setWaveform(m_waveform); } AudioEffectTremolo::~AudioEffectTremolo() @@ -52,20 +55,39 @@ void AudioEffectTremolo::update(void) // DO PROCESSING // apply modulation wave - float mod = (m_osc->getNext()+1.0f)/2.0f; // value between -1.0 and +1.0f - float modVolume = (1.0f - m_depth) + mod*m_depth; // value between 0 to depth - float finalVolume = m_volume * modVolume; + float *mod = m_osc.getNextVector(); + for (auto i=0; idata[i]); + inputAudioBlock->data[i] = (int16_t)sample; + } + //Serial.println(String("mod: ") + mod[0]); + + + + //float mod = (m_osc.getNext()+1.0f)/2.0f; // value between -1.0 and +1.0f + //float modVolume = (1.0f - m_depth) + mod*m_depth; // value between 0 to depth + //float finalVolume = m_volume * modVolume; // Set the output volume - gainAdjust(inputAudioBlock, inputAudioBlock, finalVolume, 1); + //gainAdjust(inputAudioBlock, inputAudioBlock, finalVolume, 1); transmit(inputAudioBlock); release(inputAudioBlock); } +void AudioEffectTremolo::rate(float rateValue) +{ + float rateAudioBlock = rateValue * MAX_RATE_HZ; + m_osc.setRateAudio(rateAudioBlock); +} + void AudioEffectTremolo::setWaveform(Waveform waveform) { m_waveform = waveform; + m_osc.setWaveform(waveform); } void AudioEffectTremolo::processMidi(int channel, int control, int value)