LFO now works as a vector

pull/1/head
Steve Lascos 6 years ago
parent 4ef153e858
commit 095ee79e15
  1. 3
      examples/Modulation/TremoloDemoExpansion/TremoloDemoExpansion.ino
  2. 4
      src/AudioEffectTremolo.h
  3. 60
      src/LibBasicFunctions.h
  4. 119
      src/common/LowFrequencyOscillator.cpp
  5. 32
      src/effects/AudioEffectTremolo.cpp

@ -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());

@ -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<float> *m_osc = nullptr;
BALibrary::LowFrequencyOscillatorVector<float> m_osc;
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
bool m_isOmni = false;
bool m_bypass = true;

@ -22,6 +22,7 @@
#include <cstddef>
#include <new>
#include <atomic>
#include <arm_math.h>
#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 T>
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

@ -24,64 +24,110 @@
namespace BALibrary {
constexpr float TWO_PI_F = 2.0f*3.1415927;
template <class T>
LowFrequencyOscillator<T>::LowFrequencyOscillator(Waveform mode, unsigned frequencyHz)
void LowFrequencyOscillatorVector<T>::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<AUDIO_BLOCK_SAMPLES; i++) {
initialPhase[i] = (T)i * radiansPerSample;
}
m_radiansPerBlock = radiansPerSample * (T)AUDIO_BLOCK_SAMPLES;
// there could be different threads controlling the LFO rate and consuming
// the LFO output, so we need to protected the m_phaseVec for thread-safety.
while (m_phaseLock.test_and_set()) {}
memcpy(m_phaseVec, initialPhase, sizeof(T)*AUDIO_BLOCK_SAMPLES);
m_phaseLock.clear();
}
// This function takes in the frequency of the LFO in hertz and uses knowledge
// about the the audio sample rate to calcuate the correct radians per sample.
template <class T>
LowFrequencyOscillator<T>::~LowFrequencyOscillator()
void LowFrequencyOscillatorVector<T>::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 <class T>
void LowFrequencyOscillator<T>::reset()
void LowFrequencyOscillatorVector<T>::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 <class T>
inline void LowFrequencyOscillator<T>::updatePhase()
inline void LowFrequencyOscillatorVector<T>::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 <class T>
T LowFrequencyOscillator<T>::getNext()
T *LowFrequencyOscillatorVector<T>::getNextVector()
{
T value = 0.0f;
updatePhase();
switch(m_mode) {
switch(m_waveform) {
case Waveform::SINE :
value = sin(m_phase);
for (auto i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
m_outputVec[i] = arm_sin_f32(m_phaseVec[i]);
}
break;
case Waveform::SQUARE :
for (auto i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
if (m_phaseVec[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<AUDIO_BLOCK_SAMPLES; i++) {
// if (m_phaseVec[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<T>::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<float>;
template class LowFrequencyOscillatorVector<float>;
} // namespace BALibrary

@ -4,6 +4,7 @@
* Created on: Jan 7, 2018
* Author: slascos
*/
#include <cmath> // 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<float>(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; i<AUDIO_BLOCK_SAMPLES; i++) {
mod[i] = (mod[i] + 1.0f) / 2.0f;
mod[i] = (1.0f - m_depth) + mod[i]*m_depth;
mod[i] = m_volume * mod[i];
float sample = std::roundf(mod[i] * (float)inputAudioBlock->data[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)

Loading…
Cancel
Save