Compare commits

...

5 Commits

  1. 227
      examples/Modulation/AnalogChorusDemoExpansion/AnalogChorusDemoExpansion.ino
  2. 19
      examples/Modulation/AnalogChorusDemoExpansion/name.c
  3. 104
      src/AudioEffectAnalogChorus.h
  4. 149
      src/AudioEffectSOS.h
  5. 126
      src/AudioEffectTremolo.h
  6. 135
      src/BAAudioControlWM8731.h
  7. 97
      src/BAAudioEffectDelayExternal.h
  8. 5
      src/BAEffects.h
  9. 76
      src/BAGpio.h
  10. 121
      src/BAHardware.h
  11. 8
      src/BALibrary.h
  12. 313
      src/BAPhysicalControls.h
  13. 221
      src/BASpiMemory.h
  14. 1024
      src/DmaSpi.h
  15. 23
      src/LibBasicFunctions.h
  16. 212
      src/LibMemoryManagement.h
  17. 55
      src/common/AudioDelay.cpp
  18. 235
      src/common/ExtMemSlot.cpp
  19. 122
      src/common/ExternalSramManager.cpp
  20. 49
      src/common/LowFrequencyOscillator.cpp
  21. 329
      src/effects/AudioEffectAnalogChorus.cpp
  22. 10
      src/effects/AudioEffectAnalogChorusFilters.h
  23. 324
      src/effects/AudioEffectAnalogDelay.cpp
  24. 345
      src/effects/AudioEffectAnalogDelay.cpp.interpolated
  25. 294
      src/effects/AudioEffectDelayExternal.cpp
  26. 304
      src/effects/AudioEffectSOS.cpp
  27. 163
      src/effects/AudioEffectTremolo.cpp
  28. 383
      src/peripherals/BAAudioControlWM8731.cpp
  29. 88
      src/peripherals/BAGpio.cpp
  30. 352
      src/peripherals/BAPhysicalControls.cpp
  31. 471
      src/peripherals/BASpiMemory.cpp
  32. 13
      src/peripherals/DmaSpi.cpp

@ -0,0 +1,227 @@
/*************************************************************************
* This demo uses the BALibrary library to provide enhanced control of
* the TGA Pro board.
*
* The latest copy of the BA Guitar library can be obtained from
* https://github.com/Blackaddr/BALibrary
*
* This example demonstrates teh BAAudioEffectsAnalogChorus effect. It can
* be controlled using the Blackaddr Audio "Expansion Control Board".
*
* POT1 (left) controls the modulation rate
* POT2 (right) controls the modulation depth
* POT3 (center) controls the wet/dry mix
* SW1 will enable/bypass the audio effect. LED1 will be on when effect is enabled.
* SW2 will cycle through the 3 pre-programmed analog filters. LED2 will be on when SW2 is pressed.
*
* Using the Serial Montitor, send 'u' and 'd' characters to increase or decrease
* the headphone volume between values of 0 and 9.
*/
#define TGA_PRO_REVB // Set which hardware revision of the TGA Pro we're using
#define TGA_PRO_EXPAND_REV2 // pull in the pin definitions for the Blackaddr Audio Expansion Board.
#include "BALibrary.h"
#include "BAEffects.h"
using namespace BAEffects;
using namespace BALibrary;
AudioInputI2S i2sIn;
AudioOutputI2S i2sOut;
BAAudioControlWM8731 codec;
//#define USE_EXT // uncomment this line to use External MEM0
#ifdef USE_EXT
// If using external SPI memory, we will instantiate a SRAM
// manager and create an external memory slot to use as the memory
// for our audio delay
ExternalSramManager externalSram;
ExtMemSlot delaySlot; // Declare an external memory slot.
// Instantiate the AudioEffectAnalogChorus to use external memory by
/// passing it the delay slot.
AudioEffectAnalogChorus analogChorus(&delaySlot);
#else
AudioEffectAnalogChorus analogChorus; // default chorus delays
#endif
AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
// Simply connect the input to the delay, and the output
// to both i2s channels
AudioConnection input(i2sIn,0, analogChorus,0);
AudioConnection chorusOut(analogChorus, 0, cabFilter, 0);
AudioConnection leftOut(cabFilter,0, i2sOut, 0);
AudioConnection rightOut(cabFilter,0, i2sOut, 1);
//////////////////////////////////////////
// SETUP PHYSICAL CONTROLS
// - POT1 (left) will control the rate
// - POT2 (right) will control the depth
// - POT3 (centre) will control the wet/dry mix.
// - SW1 (left) will be used as a bypass control
// - LED1 (left) will be illuminated when the effect is ON (not bypass)
// - SW2 (right) will be used to cycle through the three built in analog filter styles available.
// - LED2 (right) will illuminate when pressing SW2.
//////////////////////////////////////////
// To get the calibration values for your particular board, first run the
// BAExpansionCalibrate.ino example and
constexpr int potCalibMin = 1;
constexpr int potCalibMax = 1018;
constexpr bool potSwapDirection = true;
// Create a control object using the number of switches, pots, encoders and outputs on the
// Blackaddr Audio Expansion Board.
BAPhysicalControls controls(BA_EXPAND_NUM_SW, BA_EXPAND_NUM_POT, BA_EXPAND_NUM_ENC, BA_EXPAND_NUM_LED);
int loopCount = 0;
unsigned filterIndex = 0; // variable for storing which analog filter we're currently using.
constexpr unsigned MAX_HEADPHONE_VOL = 10;
unsigned headphoneVolume = 8; // control headphone volume from 0 to 10.
// BAPhysicalControls returns a handle when you register a new control. We'll uses these handles when working with the controls.
int bypassHandle, filterHandle, rateHandle, depthHandle, mixHandle, led1Handle, led2Handle; // Handles for the various controls
void setup() {
delay(100); // wait a bit for serial to be available
Serial.begin(57600); // Start the serial port
delay(500);
// Setup the controls. The return value is the handle to use when checking for control changes, etc.
// pushbuttons
bypassHandle = controls.addSwitch(BA_EXPAND_SW1_PIN); // will be used for bypass control
filterHandle = controls.addSwitch(BA_EXPAND_SW2_PIN); // will be used for stepping through filters
// pots
rateHandle = controls.addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); // control the amount of delay
depthHandle = controls.addPot(BA_EXPAND_POT2_PIN, potCalibMin, potCalibMax, potSwapDirection);
mixHandle = controls.addPot(BA_EXPAND_POT3_PIN, potCalibMin, potCalibMax, potSwapDirection);
// leds
led1Handle = controls.addOutput(BA_EXPAND_LED1_PIN);
led2Handle = controls.addOutput(BA_EXPAND_LED2_PIN); // will illuminate when pressing SW2
// Disable the audio codec first
codec.disable();
AudioMemory(128);
// Enable and configure the codec
Serial.println("Enabling codec...\n");
codec.enable();
codec.setHeadphoneVolume(1.0f); // Max headphone volume
// If using external memory request request memory from the manager
// for the slot
#ifdef USE_EXT
Serial.println("Using EXTERNAL memory");
// We have to request memory be allocated to our slot.
externalSram.requestMemory(&delaySlot, 40.0f, MemSelect::MEM0, true); // 40 ms is enough to handle the full range of the chorus delay
#else
Serial.println("Using INTERNAL memory");
#endif
// Besure to enable the delay. When disabled, audio is is completely blocked by the effect
// to minimize resource usage to nearly to nearly zero.
analogChorus.enable();
// Set some default values.
// These can be changed using the controls on the Blackaddr Audio Expansion Board
analogChorus.bypass(false);
analogChorus.rate(0.5f);
analogChorus.mix(0.5f);
analogChorus.depth(1.0f);
//////////////////////////////////
// AnalogChorus filter selection //
// These are commented out, in this example we'll use SW2 to cycle through the different filters
//analogChorus.setFilter(AudioEffectAnalogChorus::Filter::CE2); // The default filter. Naturally bright echo (highs stay, lows fade away)
//analogChorus.setFilter(AudioEffectAnalogChorus::Filter::WARM); // A warm filter with a smooth frequency rolloff above 2Khz
//analogChorus.setFilter(AudioEffectAnalogChorus::Filter::DARK); // A very dark filter, with a sharp rolloff above 1Khz
analogChorus.setFilter(AudioEffectAnalogChorus::Filter::WARM); // A very dark filter, with a sharp rolloff above 1Khz
// Guitar cabinet: Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
cabFilter.setLowpass(0, 4500, .7071);
cabFilter.setLowpass(1, 4500, .7071);
}
void loop() {
float potValue;
// Check if SW1 has been toggled (pushed)
if (controls.isSwitchToggled(bypassHandle)) {
bool bypass = analogChorus.isBypass(); // get the current state
bypass = !bypass; // change it
analogChorus.bypass(bypass); // set the new state
controls.setOutput(led1Handle, !bypass); // Set the LED when NOT bypassed
Serial.println(String("BYPASS is ") + bypass);
}
// Use SW2 to cycle through the filters
// controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle));
// if (controls.isSwitchToggled(filterHandle)) {
// filterIndex = (filterIndex + 1) % 3; // update and potentionall roll the counter 0, 1, 2, 0, 1, 2, ...
// // cast the index between 0 to 2 to the enum class AudioEffectAnalogChorus::Filter
// analogChorus.setFilter(static_cast<AudioEffectAnalogChorus::Filter>(filterIndex)); // will cycle through 0 to 2
// Serial.println(String("Filter set to ") + filterIndex);
// }
// Use SW2 to cycle through the waveforms
controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle));
if (controls.isSwitchToggled(filterHandle)) {
filterIndex = (filterIndex + 1) % 4; // update and potentionall roll the counter 0, 1, 2, 0, 1, 2, ...
// cast the index between 0 to 3 to the enum class AudioEffectAnalogChorus::Filter
analogChorus.setWaveform(static_cast<Waveform>(filterIndex)); // will cycle through 0 to 2
Serial.println(String("Waveform set to ") + filterIndex);
}
// Use POT1 (left) to control the rate setting
if ( (controls.checkPotValue(rateHandle, potValue)) || (loopCount == 0) ) {
// Pot has changed
Serial.println(String("New RATE setting: ") + potValue);
analogChorus.rate(potValue);
}
// Use POT2 (right) to control the depth setting
if ( (controls.checkPotValue(depthHandle, potValue)) || (loopCount == 0) ) {
// Pot has changed
Serial.println(String("New DEPTH setting: ") + potValue);
analogChorus.depth(potValue);
}
// Use POT3 (centre) to control the mix setting
if ( (controls.checkPotValue(mixHandle, potValue)) || (loopCount == 0) ) {
// Pot has changed
Serial.println(String("New MIX setting: ") + potValue);
analogChorus.mix(potValue);
}
// Use the 'u' and 'd' keys to adjust volume across ten levels.
if (Serial) {
if (Serial.available() > 0) {
while (Serial.available()) {
char key = Serial.read();
if (key == 'u') {
headphoneVolume = (headphoneVolume + 1) % MAX_HEADPHONE_VOL;
Serial.println(String("Increasing HEADPHONE volume to ") + headphoneVolume);
}
else if (key == 'd') {
headphoneVolume = (headphoneVolume - 1) % MAX_HEADPHONE_VOL;
Serial.println(String("Decreasing HEADPHONE volume to ") + headphoneVolume);
}
codec.setHeadphoneVolume(static_cast<float>(headphoneVolume) / static_cast<float>(MAX_HEADPHONE_VOL));
}
}
}
// 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) {
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage());
Serial.print("% ");
Serial.print(" AnalogChorus: "); Serial.print(analogChorus.processorUsage());
Serial.println("%");
}
loopCount++;
}

@ -0,0 +1,19 @@
// To give your project a unique name, this code must be
// placed into a .c file (its own tab). It can not be in
// a .cpp file or your main sketch (the .ino file).
#include "usb_names.h"
// Edit these lines to create your own name. The length must
// match the number of characters in your custom name.
#define MIDI_NAME {'B','l','a','c','k','a','d','d','r',' ','A','u','d','i','o',' ','T','G','A',' ','P','r','o'}
#define MIDI_NAME_LEN 23
// Do not change this part. This exact format is required by USB.
struct usb_string_descriptor_struct usb_string_product_name = {
2 + MIDI_NAME_LEN * 2,
3,
MIDI_NAME
};

@ -3,9 +3,9 @@
* @author Steve Lascos
* @company Blackaddr Audio
*
* AudioEffectAnalogDelay is a class for simulating a classic BBD based delay
* like the Boss DM-2. This class works with either internal RAM, or external
* SPI RAM for longer delays. The exteranl ram uses DMA to minimize load on the
* AudioEffectAnalogChorus is a class for simulating a classic BBD based chorus
* like the Boss CE-2. This class works with either internal RAM, or external
* SPI RAM. The external RAM uses DMA to minimize load on the
* CPU.
*
* @copyright This program is free software: you can redistribute it and/or modify
@ -22,8 +22,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H
#define __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H
#ifndef __BAEFFECTS_BAAUDIOEFFECTANALOGCHORUS_H
#define __BAEFFECTS_BAAUDIOEFFECTANALOGCHORUS_H
#include <Audio.h>
#include "LibBasicFunctions.h"
@ -31,64 +31,51 @@
namespace BAEffects {
/**************************************************************************//**
* AudioEffectAnalogDelay models BBD based analog delays. It provides controls
* for delay, feedback (or regen), mix and output level. All parameters can be
* AudioEffectAnalogChorus models BBD based analog chorus. It provides controls
* for rate, depth, mix and output level. All parameters can be
* controlled by MIDI. The class supports internal memory, or external SPI
* memory by providing an ExtMemSlot. External memory access uses DMA to reduce
* process load.
*****************************************************************************/
class AudioEffectAnalogDelay : public AudioStream {
class AudioEffectAnalogChorus : public AudioStream {
public:
///< List of AudioEffectAnalogDelay MIDI controllable parameters
///< List of AudioEffectAnalogChorus MIDI controllable parameters
enum {
BYPASS = 0, ///< controls effect bypass
DELAY, ///< controls the amount of delay
FEEDBACK, ///< controls the amount of echo feedback (regen)
MIX, ///< controls the the mix of input and echo signals
RATE, ///< controls the modulate rate of the LFO
DEPTH, ///< controls the depth of modulation of the LFO
MIX, ///< controls the the mix of input and chorus signals
VOLUME, ///< controls the output volume level
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
};
enum class Filter {
DM3 = 0,
CE2 = 0,
WARM,
DARK
};
// *** CONSTRUCTORS ***
AudioEffectAnalogDelay() = delete;
/// Construct an analog delay using internal memory by specifying the maximum
/// delay in milliseconds.
/// @param maxDelayMs maximum delay in milliseconds. Larger delays use more memory.
AudioEffectAnalogDelay(float maxDelayMs);
/// Construct an analog chorus using internal memory. The chorus will have the
/// default average delay.
AudioEffectAnalogChorus();
/// Construct an analog delay using internal memory by specifying the maximum
/// delay in audio samples.
/// @param numSamples maximum delay in audio samples. Larger delays use more memory.
AudioEffectAnalogDelay(size_t numSamples);
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
/// delay will be determined by the amount of memory in the slot.
/// Construct an analog chorus using external SPI via an ExtMemSlot. The chorus will have
/// the default average delay.
/// @param slot A pointer to the ExtMemSlot to use for the delay.
AudioEffectAnalogDelay(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
virtual ~AudioEffectAnalogDelay(); ///< Destructor
virtual ~AudioEffectAnalogChorus(); ///< Destructor
// *** PARAMETERS ***
/// Set the delay in milliseconds.
/// @param milliseconds the request delay in milliseconds. Must be less than max delay.
void delay(float milliseconds);
/// Set the delay in number of audio samples.
/// @param delaySamples the request delay in audio samples. Must be less than max delay.
void delay(size_t delaySamples);
/// Set the delay as a fraction of the maximum delay.
/// Set the chorus average delay in milliseconds
/// The value should be between 0.0f and 1.0f
void delayFractionMax(float delayFraction);
void setDelayConfig(float averageDelayMs, float delayRangeMs);
/// Set the chorus average delay in number of audio samples
/// The value should be between 0.0f and 1.0f
void setDelayConfig(size_t averageDelayNumSamples, size_t delayRangeNumSamples);
/// Bypass the effect.
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
@ -102,9 +89,16 @@ public:
/// Toggle the bypass effect
void toggleBypass() { m_bypass = !m_bypass; }
/// Set the amount of echo feedback (a.k.a regeneration).
/// @param feedback a floating point number between 0.0 and 1.0.
void feedback(float feedback) { m_feedback = feedback; }
/// Set the LFO waveform
/// @param waveform the LFO waveform to modulate chorus delay with
void setWaveform(BALibrary::Waveform waveform);
/// Set the LFO frequency where 0.0f is MIN and 1.0f is MAX
void rate(float rate);
/// Set the depth of LFO modulation.
/// @param lfoDepth must be a float between 0.0f and 1.0f
void depth(float lfoDepth) { m_lfoDepth = lfoDepth; }
/// Set the amount of blending between dry and wet (echo) at the output.
/// @param mix When 0.0, output is 100% dry, when 1.0, output is 100% wet. When
@ -148,10 +142,10 @@ public:
// ** FILTER COEFFICIENTS **
/// Set the filter coefficients to one of the presets. See AudioEffectAnalogDelay::Filter
/// Set the filter coefficients to one of the presets. See AudioEffectAnalogChorus::Filter
/// for options.
/// @details See AudioEffectAnalogDelayFIlters.h for more details.
/// @param filter the preset filter. E.g. AudioEffectAnalogDelay::Filter::WARM
/// @details See AudioEffectAnalogChorusFIlters.h for more details.
/// @param filter the preset filter. E.g. AudioEffectAnalogChorus::Filter::WARM
void setFilter(Filter filter);
/// Override the default coefficients with your own. The number of filters stages affects how
@ -169,25 +163,35 @@ public:
virtual void update(void); ///< update automatically called by the Teesny Audio Library
private:
static constexpr float m_DEFAULT_AVERAGE_DELAY_MS = 20.0f; ///< default average delay of chorus in milliseconds
static constexpr float m_DELAY_RANGE = 15.0f; ///< default range of delay variation in milliseconds
static constexpr float m_LFO_MIN_RATE = 2.0f; ///< slowest possible LFO rate in milliseconds
static constexpr float m_LFO_RANGE = 8.0f; ///< fastest possible LFO rate in milliseconds
audio_block_t *m_inputQueueArray[1];
bool m_isOmni = false;
bool m_bypass = true;
bool m_enable = false;
bool m_externalMemory = false;
BALibrary::AudioDelay *m_memory = nullptr;
audio_block_t *m_previousBlock = nullptr;
audio_block_t *m_blockToRelease = nullptr;
BALibrary::LowFrequencyOscillatorVector<float> m_lfo;
size_t m_maxDelaySamples = 0;
audio_block_t *m_previousBlock = nullptr;
audio_block_t *m_blockToRelease = nullptr;
//size_t m_currentDelayOffset = 0;
float m_delayRange = 0;
BALibrary::IirBiQuadFilterHQ *m_iir = nullptr;
// Controls
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
size_t m_delaySamples = 0;
float m_feedback = 0.0f;
float m_averageDelaySamples = 0;
float m_lfoDepth = 0.0f;
float m_mix = 0.0f;
float m_volume = 1.0f;
void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet);
void m_preProcessing (audio_block_t *out, audio_block_t *dry, audio_block_t *wet);
void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet);
// Coefficients
@ -196,4 +200,4 @@ private:
}
#endif /* __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H */
#endif /* __BAEFFECTS_BAAUDIOEFFECTAnalogChorus_H */

@ -1,149 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* AudioEffectSOS is a class f
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAEFFECTS_BAAUDIOEFFECTSOS_H
#define __BAEFFECTS_BAAUDIOEFFECTSOS_H
#include <Audio.h>
#include "LibBasicFunctions.h"
namespace BAEffects {
/**************************************************************************//**
* AudioEffectSOS
*****************************************************************************/
class AudioEffectSOS : public AudioStream {
public:
///< List of AudioEffectAnalogDelay MIDI controllable parameters
enum {
BYPASS = 0, ///< controls effect bypass
GATE_TRIGGER, ///< begins the gate sequence
GATE_OPEN_TIME, ///< controls how long it takes to open the gate
//GATE_HOLD_TIME, ///< controls how long the gate stays open at unity
GATE_CLOSE_TIME, ///< controls how long it takes to close the gate (release)
CLEAR_FEEDBACK_TRIGGER, ///< begins the sequence to clear out the looping feedback
FEEDBACK, ///< controls the amount of feedback, more gives longer SOS sustain
VOLUME, ///< controls the output volume level
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
};
// *** CONSTRUCTORS ***
AudioEffectSOS() = delete;
AudioEffectSOS(float maxDelayMs);
AudioEffectSOS(size_t numSamples);
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
/// delay will be determined by the amount of memory in the slot.
/// @param slot A pointer to the ExtMemSlot to use for the delay.
AudioEffectSOS(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
virtual ~AudioEffectSOS(); ///< Destructor
void setGateLedGpio(int pinId);
// *** PARAMETERS ***
void gateOpenTime(float milliseconds);
void gateCloseTime(float milliseconds);
void feedback(float feedback) { m_feedback = feedback; }
/// Bypass the effect.
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
/// Note that audio still passes through when bypass is enabled.
void bypass(bool byp) { m_bypass = byp; }
/// Activate the gate automation. Input gate will open, then close.
void trigger() { m_inputGateAuto.trigger(); }
/// Activate the delay clearing automation. Input signal will mute, gate will open, then close.
void clear() { m_clearFeedbackAuto.trigger(); }
/// Set the output volume. This affect both the wet and dry signals.
/// @details The default is 1.0.
/// @param vol Sets the output volume between -1.0 and +1.0
void volume(float vol) {m_volume = vol; }
// ** ENABLE / DISABLE **
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
void enable();
/// Disables audio process. When disabled, CPU load is nearly zero.
void disable() { m_enable = false; }
// ** MIDI **
/// Sets whether MIDI OMNI channel is processig on or off. When on,
/// all midi channels are used for matching CCs.
/// @param isOmni when true, all channels are processed, when false, channel
/// must match configured value.
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; }
/// Configure an effect parameter to be controlled by a MIDI CC
/// number on a particular channel.
/// @param parameter one of the parameter names in the class enum
/// @param midiCC the CC number from 0 to 127
/// @param midiChannel the effect will only response to the CC on this channel
/// when OMNI mode is off.
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0);
/// process a MIDI Continous-Controller (CC) message
/// @param channel the MIDI channel from 0 to 15)
/// @param midiCC the CC number from 0 to 127
/// @param value the CC value from 0 to 127
void processMidi(int channel, int midiCC, int value);
virtual void update(void); ///< update automatically called by the Teesny Audio Library
private:
audio_block_t *m_inputQueueArray[1];
bool m_isOmni = false;
bool m_bypass = true;
bool m_enable = false;
BALibrary::AudioDelay *m_memory = nullptr;
bool m_externalMemory = true;
audio_block_t *m_previousBlock = nullptr;
audio_block_t *m_blockToRelease = nullptr;
size_t m_maxDelaySamples = 0;
int m_gateLedPinId = -1;
// Controls
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
size_t m_delaySamples = 0;
float m_openTimeMs = 0.0f;
float m_closeTimeMs = 0.0f;
float m_feedback = 0.0f;
float m_volume = 1.0f;
// Automated Controls
BALibrary::ParameterAutomationSequence<float> m_inputGateAuto = BALibrary::ParameterAutomationSequence<float>(3);
BALibrary::ParameterAutomationSequence<float> m_clearFeedbackAuto = BALibrary::ParameterAutomationSequence<float>(3);
// Private functions
void m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal);
void m_postProcessing(audio_block_t *out, audio_block_t *input);
};
}
#endif /* __BAEFFECTS_BAAUDIOEFFECTSOS_H */

@ -1,126 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* Tremolo is a classic volume modulate effect.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY__BAEFFECTS_AUDIOEFFECTDELAYEXTERNAL_H; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAEFFECTS_AUDIOEFFECTTREMOLO_H
#define __BAEFFECTS_AUDIOEFFECTTREMOLO_H
#include <Audio.h>
#include "LibBasicFunctions.h"
namespace BAEffects {
/**************************************************************************//**
* AudioEffectTremolo
*****************************************************************************/
class AudioEffectTremolo : public AudioStream {
public:
///< List of AudioEffectTremolo MIDI controllable parameters
enum {
BYPASS = 0, ///< controls effect bypass
RATE, ///< controls the rate of the modulation
DEPTH, ///< controls the depth of the modulation
WAVEFORM, ///< select the modulation waveform
VOLUME, ///< controls the output volume level
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
};
// *** CONSTRUCTORS ***
AudioEffectTremolo();
virtual ~AudioEffectTremolo(); ///< Destructor
// *** PARAMETERS ***
void rate(float rateValue);
void depth(float depthValue) { m_depth = depthValue; }
void setWaveform(BALibrary::Waveform waveform);
/// Bypass the effect.
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
/// Note that audio still passes through when bypass is enabled.
void bypass(bool byp) { m_bypass = byp; }
/// Get if the effect is bypassed
/// @returns true if bypassed, false if not bypassed
bool isBypass() { return m_bypass; }
/// Toggle the bypass effect
void toggleBypass() { m_bypass = !m_bypass; }
/// Set the output volume. This affect both the wet and dry signals.
/// @details The default is 1.0.
/// @param vol Sets the output volume between -1.0 and +1.0
void volume(float vol) {m_volume = vol; }
// ** ENABLE / DISABLE **
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
void enable() { m_enable = true; }
/// Disables audio process. When disabled, CPU load is nearly zero.
void disable() { m_enable = false; }
// ** MIDI **
/// Sets whether MIDI OMNI channel is processig on or off. When on,
/// all midi channels are used for matching CCs.
/// @param isOmni when true, all channels are processed, when false, channel
/// must match configured value.
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; }
/// Configure an effect parameter to be controlled by a MIDI CC
/// number on a particular channel.
/// @param parameter one of the parameter names in the class enum
/// @param midiCC the CC number from 0 to 127
/// @param midiChannel the effect will only response to the CC on this channel
/// when OMNI mode is off.
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0);
/// process a MIDI Continous-Controller (CC) message
/// @param channel the MIDI channel from 0 to 15)
/// @param midiCC the CC number from 0 to 127
/// @param value the CC value from 0 to 127
void processMidi(int channel, int midiCC, int value);
virtual void update(void); ///< update automatically called by the Teesny Audio Library
private:
audio_block_t *m_inputQueueArray[1];
BALibrary::LowFrequencyOscillatorVector<float> m_osc;
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
bool m_isOmni = false;
bool m_bypass = true;
bool m_enable = false;
float m_rate = 0.0f;
float m_depth = 0.0f;
BALibrary::Waveform m_waveform = BALibrary::Waveform::SINE;
float m_volume = 1.0f;
};
}
#endif /* __BAEFFECTS_AUDIOEFFECTTREMOLO_H */

@ -1,135 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* BAAudioContromWM8731 is a class for controlling a WM8731 Codec via I2C.
* @details The Codec power ups in a silent state, with non-optimal
* configuration. This class will enable codec and set some initial gain levels.
* The user can than use the API to changing settings for their specific needs.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BALIBRARY_BAAUDIOCONTROLWM8731_H
#define __BALIBRARY_BAAUDIOCONTROLWM8731_H
namespace BALibrary {
constexpr int WM8731_NUM_REGS = 10; // Number of registers in the internal shadow array
/**************************************************************************//**
* BAAudioControlWM8731 provides an API for configuring the internal registers
* of the WM8731 codec.
* @details Not every single command is provided, please ask if you need something
* added that is not already present. You can also directly write any register
* you wish with the writeI2C() method.
*****************************************************************************/
class BAAudioControlWM8731 {
public:
BAAudioControlWM8731();
virtual ~BAAudioControlWM8731();
/// Mute and power down the codec.
void disable(void);
/// First disable, then cleanly power up and unmute the codec.
void enable(void);
/// Set the input gain of the codec's PGA for the left (TRS Tip) channel.
/// @param val an interger value from 31 = +12dB . . 1.5dB steps down to 0 = -34.5dB
void setLeftInputGain(int val);
/// Set the input gain of the codec's PGA for the right (TRS Ring) channel.
/// @param val an interger value from 31 = +12dB . . 1.5dB steps down to 0 = -34.5dB
void setRightInputGain(int val);
/// Mute/unmute the Left (TRS Tip) channel at the ADC input.
/// @param val when true, channel is muted, when false, channel is unmuted
void setLeftInMute(bool val);
/// Mute/unmute the Right (TRS Ring) channel at the ADC input.
/// @param val when true, channel is muted, when false, channel is unmuted
void setRightInMute(bool val);
/// Links the Left/Right channel contols for mute and input gain.
/// @details when true, changing either the left or right gain/mute controls will
/// affect both channels.
/// @param val when true, channels are linked, when false, they are controlled separately
void setLinkLeftRightIn(bool val);
/// Swaps the left and right channels in the codec.
///param val when true, channels are swapped, else normal.
void setLeftRightSwap(bool val);
/// Set the volume for the codec's built-in headphone amp
/// @param volume the input volume level from 0.0f to 1.0f;
void setHeadphoneVolume(float volume);
/// Mute/unmute the output DAC, affects both Left and Right output channels.
/// @param when true, output DAC is muted, when false, unmuted.
void setDacMute(bool val);
/// Control when the DAC is feeding the output analog circuitry.
/// @param val when true, the DAC output is connected to the analog output. When
/// false, the DAC is disconnected.
void setDacSelect(bool val);
/// ADC Bypass control.
/// @details This permits the analog audio from the Codec's PGA to bypass the ADC
/// and go directly to the analog output of the codec, bypassing the digital domain
/// entirely.
/// @param val when true, analog ADC input is fed directly to codec analog otuput.
void setAdcBypass(bool val);
/// Digital High Pass Filter disable. RECOMMENDED ALWAYS TRUE!
/// @details this controls a HPF in the codec that attempts to remove the lowest
/// frequency (i.e. < 10 Hz) to improve headroom by dynamically measuring DC level.
/// In most cases, it introduces noise components by modulating the filter. This
/// is not suitable for applications where the audio is used for output, but might
/// be useful for applications like tuning, pitch detection, whre the noise components
/// can be tolerated.
/// @param val when true (recommended) the dynamic HPF is disabled, otherwise enabled.
void setHPFDisable(bool val);
/// Activates the I2S interface on the codec.
/// @param val when true, I2S interface is active, when false it is disabled.
void setActivate(bool val);
/// Soft-reset the codec.
/// @details This will cause the codec to reset its internal registers to default values.
void resetCodec(void);
/// Write a custom command to the codec via I2C control interface.
/// @details See WM8731 datasheet for register map details.
/// @param addr The register address you wish to write to, range 0 to 15.
/// @param val the 9-bit data value you wish to write at the address specifed.
bool writeI2C(unsigned int addr, unsigned int val);
protected:
// A shadow array for the registers on the codec since the interface is write-only.
int regArray[WM8731_NUM_REGS];
private:
// low-level write command
bool write(unsigned int reg, unsigned int val);
// resets the internal shadow register array
void resetInternalReg(void);
bool m_wireStarted = false;
};
} /* namespace BALibrary */
#endif /* __BALIBRARY_BAAUDIOCONTROLWM8731_H */

@ -1,97 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* BAAudioEffectDelayExternal is a class for using an external SPI SRAM chip
* as an audio delay line. The external memory can be shared among several
* different instances of BAAudioEffectDelayExternal by specifying the max delay
* length during construction.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H
#define __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H
#include <Audio.h>
#include "AudioStream.h"
#include "BAHardware.h"
namespace BAEffects {
/**************************************************************************//**
* BAAudioEffectDelayExternal can use external SPI RAM for delay rather than
* the limited RAM available on the Teensy itself.
*****************************************************************************/
class BAAudioEffectDelayExternal : public AudioStream
{
public:
/// Default constructor will use all memory available in MEM0
BAAudioEffectDelayExternal();
/// Specifiy which external memory to use
/// @param type specify which memory to use
BAAudioEffectDelayExternal(BALibrary::MemSelect type);
/// Specify external memory, and how much of the memory to use
/// @param type specify which memory to use
/// @param delayLengthMs maximum delay length in milliseconds
BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs);
virtual ~BAAudioEffectDelayExternal();
/// set the actual amount of delay on a given delay tap
/// @param channel specify channel tap 1-8
/// @param milliseconds specify how much delay for the specified tap
void delay(uint8_t channel, float milliseconds);
/// Disable a delay channel tap
/// @param channel specify channel tap 1-8
void disable(uint8_t channel);
virtual void update(void);
static unsigned m_usingSPICount[2]; // internal use for all instances
private:
void initialize(BALibrary::MemSelect mem, unsigned delayLength = 1e6);
void read(uint32_t address, uint32_t count, int16_t *data);
void write(uint32_t address, uint32_t count, const int16_t *data);
void zero(uint32_t address, uint32_t count);
unsigned m_memoryStart; // the first address in the memory we're using
unsigned m_memoryLength; // the amount of memory we're using
unsigned m_headOffset; // head index (incoming) data into external memory
unsigned m_channelDelayLength[8]; // # of sample delay for each channel (128 = no delay)
unsigned m_activeMask; // which output channels are active
static unsigned m_allocated[2];
audio_block_t *m_inputQueueArray[1];
BALibrary::MemSelect m_mem;
SPIClass *m_spi = nullptr;
int m_spiChannel = 0;
int m_misoPin = 0;
int m_mosiPin = 0;
int m_sckPin = 0;
int m_csPin = 0;
void m_startUsingSPI(int spiBus);
void m_stopUsingSPI(int spiBus);
};
} /* namespace BAEffects */
#endif /* __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H */

@ -22,9 +22,6 @@
#include "BALibrary.h" // contains the Blackaddr hardware board definitions
#include "BAAudioEffectDelayExternal.h"
#include "AudioEffectAnalogDelay.h"
#include "AudioEffectSOS.h"
#include "AudioEffectTremolo.h"
#include "AudioEffectAnalogChorus.h"
#endif /* __BAEFFECTS_H */

@ -1,76 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* BAGPio is convenience class for accessing the the various GPIOs available
* on the Teensy Guitar Audio series boards.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BALIBRARY_BAGPIO_H
#define __BALIBRARY_BAGPIO_H
#include "BAHardware.h"
namespace BALibrary {
/**************************************************************************//**
* BAGpio provides a convince class to easily control the direction and state
* of the GPIO pins available on the TGA headers.
* @details you can always control this directly with Arduino commands like
* digitalWrite(), etc.
*****************************************************************************/
class BAGpio {
public:
/// Construct a GPIO object for controlling the various GPIO and user pins
BAGpio();
virtual ~BAGpio();
/// Set the direction of the specified GPIO pin.
/// @param gpioId Specify a GPIO pin such as GPIO::GPIO0
/// @param specify direction as INPUT or OUTPUT which are Arduino constants
void setGPIODirection(GPIO gpioId, int direction);
/// Set the state of the specified GPIO to high
/// @param gpioId gpioId Specify a GPIO pin such as GPIO::GPIO0
void setGPIO(GPIO gpioId);
/// Clear the state of the specified GPIO pin.
/// @param gpioId gpioId Specify a GPIO pin such as GPIO::GPIO0
void clearGPIO(GPIO gpioId);
/// Toggle the state of the specified GPIO pin. Only works if configured as output.
/// @param gpioId gpioId Specify a GPIO pin such as GPIO::GPIO0
/// @returns the new state of the pin
int toggleGPIO(GPIO gpioId);
/// Turn on the user LED
void setLed();
/// Turn off the user LED
void clearLed();
/// Toggle the stage of the user LED
/// @returns the new stage of the user LED.
int toggleLed();
private:
uint8_t m_ledState;
};
} /* namespace BALibrary */
#endif /* __BALIBRARY_BAGPIO_H */

@ -1,121 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* This file contains specific definitions for each Blackaddr Audio hardware
* board.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BALIBRARY_BAHARDWARE_H
#define __BALIBRARY_BAHARDWARE_H
#include <Arduino.h>
#include <cstdint>
#include <cstddef>
/**************************************************************************//**
* BALibrary is a namespace/Library for Guitar processing from Blackaddr Audio.
*****************************************************************************/
namespace BALibrary {
// uncomment the line that corresponds to your hardware
//#define TGA_PRO_REVA
#if (!defined(TGA_PRO_REVA) && !defined(TGA_PRO_REVB))
#define TGA_PRO_REVA
#endif
#if defined(TGA_PRO_REVA) || defined(TGA_PRO_REVB)
constexpr uint8_t USR_LED_ID = 16; ///< Teensy IO number for the user LED.
/**************************************************************************//**
* GPIOs and Testpoints are accessed via enumerated class constants.
*****************************************************************************/
enum class GPIO : uint8_t {
GPIO0 = 2,
GPIO1 = 3,
GPIO2 = 4,
GPIO3 = 6,
GPIO4 = 12,
GPIO5 = 32,
GPIO6 = 27,
GPIO7 = 28,
TP1 = 34,
TP2 = 33
};
/**************************************************************************//**
* Optionally installed SPI RAM
*****************************************************************************/
constexpr unsigned NUM_MEM_SLOTS = 2;
enum MemSelect : unsigned {
MEM0 = 0, ///< SPI RAM MEM0
MEM1 = 1 ///< SPI RAM MEM1
};
/**************************************************************************//**
* Set the maximum address (byte-based) in the external SPI memories
*****************************************************************************/
constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 };
/**************************************************************************//**
* General Purpose SPI Interfaces
*****************************************************************************/
enum class SpiDeviceId : unsigned {
SPI_DEVICE0 = 0, ///< Arduino SPI device
SPI_DEVICE1 = 1 ///< Arduino SPI1 device
};
constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip
constexpr size_t SPI_MEM0_SIZE_BYTES = 131072;
constexpr size_t SPI_MEM0_MAX_AUDIO_SAMPLES = SPI_MEM0_SIZE_BYTES/sizeof(int16_t);
constexpr size_t SPI_MEM1_SIZE_BYTES = 131072;
constexpr size_t SPI_MEM1_MAX_AUDIO_SAMPLES = SPI_MEM1_SIZE_BYTES/sizeof(int16_t);
#else
#error "No hardware declared"
#endif
#if defined (TGA_PRO_EXPAND_REV2)
/**************************************************************************//**
* Blackaddr Audio Expansion Board
*****************************************************************************/
constexpr unsigned BA_EXPAND_NUM_POT = 3;
constexpr unsigned BA_EXPAND_NUM_SW = 2;
constexpr unsigned BA_EXPAND_NUM_LED = 2;
constexpr unsigned BA_EXPAND_NUM_ENC = 0;
constexpr uint8_t BA_EXPAND_POT1_PIN = A16; // 35_A16_PWM
constexpr uint8_t BA_EXPAND_POT2_PIN = A17; // 36_A17_PWM
constexpr uint8_t BA_EXPAND_POT3_PIN = A18; // 37_SCL1_A18_PWM
constexpr uint8_t BA_EXPAND_SW1_PIN = 2; // 2)PWM
constexpr uint8_t BA_EXPAND_SW2_PIN = 3; // 3_SCL2_PWM
constexpr uint8_t BA_EXPAND_LED1_PIN = 4; // 4_SDA2_PWM
constexpr uint8_t BA_EXPAND_LED2_PIN = 6; // 6_PWM
#endif
} // namespace BALibrary
#endif /* __BALIBRARY_BAHARDWARE_H */

@ -20,15 +20,7 @@
#ifndef __BALIBRARY_H
#define __BALIBRARY_H
#include "BAHardware.h" // contains the Blackaddr hardware board definitions
#include "BATypes.h"
#include "LibBasicFunctions.h"
#include "LibMemoryManagement.h"
#include "BAAudioControlWM8731.h" // Codec Control
#include "BASpiMemory.h"
#include "BAGpio.h"
#include "BAPhysicalControls.h"
#endif /* __BALIBRARY_H */

@ -1,313 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* BAPhysicalControls is a general purpose class for handling an array of
* pots, switches, rotary encoders and outputs (for LEDs or relays).
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BAPHYSICALCONTROLS_H
#define __BAPHYSICALCONTROLS_H
#include <vector>
#include <Encoder.h>
#include <Bounce2.h>
namespace BALibrary {
constexpr bool SWAP_DIRECTION = true; ///< Use when specifying direction should be swapped
constexpr bool NOSWAP_DIRECTION = false; ///< Use when specifying direction should not be swapped
/// Convenience class for handling a digital output with regard to setting and toggling stage
class DigitalOutput {
public:
DigitalOutput() = delete; // delete the default constructor
/// Construct an object to control the specified pin
/// @param pin the "Logical Arduino" pin number (not the physical pin number
DigitalOutput(uint8_t pin) : m_pin(pin) {}
/// Set the output value
/// @param val when zero output is low, otherwise output his high
void set(int val);
/// Toggle the output value current state
void toggle(void);
private:
uint8_t m_pin; ///< store the pin associated with this output
int m_val = 0; ///< store the value to support toggling
};
/// Convenience class for handling digital inputs. This wraps Bounce with the abilty
/// to invert the polarity of the pin.
class DigitalInput : public Bounce {
public:
/// Create an input where digital low is return true. Most switches ground when pressed.
DigitalInput() : m_isPolarityInverted(true) {}
/// Create an input and specify if input polarity is inverted.
/// @param isPolarityInverted when false, a high voltage on the pin returns true, 0V returns false.
DigitalInput(bool isPolarityInverted) : m_isPolarityInverted(isPolarityInverted) {}
/// Read the state of the pin according to the polarity
/// @returns true when the input should be interpreted as the switch is closed, else false.
bool read() { return Bounce::read() != m_isPolarityInverted; } // logical XOR to conditionally invert polarity
/// Set whether the input pin polarity
/// @param polarity when true, a low voltage on the pin is considered true by read(), else false.
void setPolarityInverted(bool polarity) { m_isPolarityInverted = polarity; }
/// Check if input has toggled from low to high to low by looking for falling edges
/// @returns true if the switch has toggled
bool hasInputToggled();
/// Check if the input is asserted
/// @returns true if the switch is held
bool isInputAssert();
/// Get the raw input value (ignores polarity inversion)
/// @returns returns true is physical pin is high, else false
bool getPinInputValue();
/// Store the current switch state and return true if it has changed.
/// @param switchState variable to store switch state in
/// @returns true when switch stage has changed since last check
bool hasInputChanged(bool &switchState);
private:
bool m_isPolarityInverted;
bool m_isPushed;
};
/// Convenience class for handling an analog pot as a control. When calibrated,
/// returns a float between 0.0 and 1.0.
class Potentiometer {
public:
/// Calibration data for a pot includes it's min and max value, as well as whether
/// direction should be swapped. Swapping depends on the physical orientation of the
/// pot.
struct Calib {
unsigned min; ///< The value from analogRead() when the pot is fully counter-clockwise (normal orientation)
unsigned max; ///< The value from analogRead() when the pot is fully clockwise (normal orientation)
bool swap; ///< when orientation is such that fully clockwise would give max reading, swap changes it to the min
};
Potentiometer() = delete; // delete the default constructor
/// Construction requires the Arduino analog pin number, as well as calibration values.
/// @param analogPin The analog Arduino pin literal. This the number on the Teensy pinout preceeeded by an A in the local pin name. E.g. "A17".
/// @param minCalibration See Potentiometer::calibrate()
/// @param maxCalibration See Potentiometer::calibrate()
/// @param swapDirection Optional param. See Potentiometer::calibrate()
Potentiometer(uint8_t analogPin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection = false);
/// Get new value from the pot.
/// @param value reference to a float, the new value will be written here. Value is between 0.0 and 1.0f.
/// @returns true if the value has changed, false if it has not
bool getValue(float &value);
/// Get the raw int value directly from analogRead()
/// @returns an integer between 0 and 1023.
int getRawValue();
/// Adjust the calibrate threshold. This is a factor that shrinks the calibration range slightly to
/// ensure the full range from 0.0f to 1.0f can be obtained.
/// @details temperature change can slightly alter the calibration values. This factor causes the value
/// to hit min or max just before the end of the pots travel to compensate.
/// @param thresholdFactor typical value is 0.01f to 0.05f
void adjustCalibrationThreshold(float thresholdFactor);
/// Set the amount of feedback in the IIR filter used to smooth the pot readings
/// @details actual filter reponse deptnds on the rate you call getValue()
/// @param filterValue typical values are 0.80f to 0.95f
void setFeedbackFitlerValue(float fitlerValue);
void setCalibrationValues(unsigned min, unsigned max, bool swapDirection);
/// Call this static function before creating the object to obtain calibration data. The sequence
/// involves prompts over the Serial port.
/// @details E.g. call Potentiometer::calibrate(PIN). See BAExpansionCalibrate.ino in the library examples.
/// @param analogPin the Arduino analog pin number connected to the pot you wish to calibraate.
/// @returns populated Potentiometer::Calib structure
static Calib calibrate(uint8_t analogPin);
private:
uint8_t m_pin; ///< store the Arduino pin literal, e.g. A17
bool m_swapDirection; ///< swap when pot orientation is upside down
unsigned m_minCalibration; ///< stores the min pot value
unsigned m_maxCalibration; ///< stores the max pot value
unsigned m_lastValue = 0; ///< stores previous value
float m_feedbackFitlerValue = 0.9f; ///< feedback value for POT filter
float m_thresholdFactor = 0.05f; ///< threshold factor causes values pot to saturate faster at the limits, default is 5%
unsigned m_minCalibrationThresholded; ///< stores the min pot value after thresholding
unsigned m_maxCalibrationThresholded; ///< stores the max pot value after thresholding
unsigned m_rangeThresholded; ///< stores the range of max - min after thresholding
};
/// Convenience class for rotary (quadrature) encoders. Uses Arduino Encoder under the hood.
class RotaryEncoder : public Encoder {
public:
RotaryEncoder() = delete; // delete default constructor
/// Constructor an encoder with the specified pins. Optionally swap direction and divide down the number of encoder ticks
/// @param pin1 the Arduino logical pin number for the 'A' on the encoder.
/// @param pin2 the Arduino logical pin number for the 'B' on the encoder.
/// @param swapDirection (OPTIONAL) set to true or false to obtain clockwise increments the counter-clockwise decrements
/// @param divider (OPTIONAL) controls the sensitivity of the divider. Use powers of 2. E.g. 1, 2, 4, 8, etc.
RotaryEncoder(uint8_t pin1, uint8_t pin2, bool swapDirection = false, int divider = 1) : Encoder(pin1,pin2), m_swapDirection(swapDirection), m_divider(divider) {}
/// Get the delta (as a positive or negative number) since last call
/// @returns an integer representing the net change since last call
int getChange();
/// Set the divider on the internal counter. High resolution encoders without detents can be overly sensitive.
/// This will helf reduce sensisitive by increasing the divider. Default = 1.
/// @pram divider controls the sensitivity of the divider. Use powers of 2. E.g. 1, 2, 4, 8, etc.
void setDivider(int divider);
private:
bool m_swapDirection; ///< specifies if increment/decrement should be swapped
int32_t m_lastPosition = 0; ///< store the last recorded position
int32_t m_divider; ///< divides down the magnitude of change read by the encoder.
};
/// Specifies the type of control
enum class ControlType : unsigned {
SWITCH_MOMENTARY = 0, ///< a momentary switch, which is only on when pressed.
SWITCH_LATCHING = 1, ///< a latching switch, which toggles between on and off with each press and release
ROTARY_KNOB = 2, ///< a rotary encoder knob
POT = 3, ///< an analog potentiometer
UNDEFINED = 255 ///< undefined or uninitialized
};
/// Tjis class provides convenient interface for combinary an arbitrary number of controls of different types into
/// one object. Supports switches, pots, encoders and digital outputs (useful for LEDs, relays).
class BAPhysicalControls {
public:
BAPhysicalControls() = delete;
/// Construct an object and reserve memory for the specified number of controls. Encoders and outptus are optional params.
/// @param numSwitches the number of switches or buttons
/// @param numPots the number of analog potentiometers
/// @param numEncoders the number of quadrature encoders
/// @param numOutputs the number of digital outputs. E.g. LEDs, relays, etc.
BAPhysicalControls(unsigned numSwitches, unsigned numPots, unsigned numEncoders = 0, unsigned numOutputs = 0);
~BAPhysicalControls() {}
/// add a rotary encoders to the controls
/// @param pin1 the pin number corresponding to 'A' on the encoder
/// @param pin2 the pin number corresponding to 'B' on the encoder
/// @param swapDirection When true, reverses which rotation direction is positive, and which is negative
/// @param divider optional, for encoders with high resolution this divides down the rotation measurement.
/// @returns the index in the encoder vector the new encoder was placed at.
unsigned addRotary(uint8_t pin1, uint8_t pin2, bool swapDirection = false, int divider = 1);
/// add a switch to the controls
/// @param pin the pin number connected to the switch
/// @param intervalMilliseconds, optional, specifies the filtering time to debounce a switch
/// @returns the index in the switch vector the new switch was placed at.
unsigned addSwitch(uint8_t pin, unsigned long intervalMilliseconds = 10);
/// add a pot to the controls
/// @param pin the pin number connected to the wiper of the pot
/// @param minCalibration the value corresponding to lowest pot setting
/// @param maxCalibration the value corresponding to the highest pot setting
unsigned addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration);
/// add a pot to the controls
/// @param pin the pin number connected to the wiper of the pot
/// @param minCalibration the value corresponding to lowest pot setting
/// @param maxCalibration the value corresponding to the highest pot setting
/// @param swapDirection reverses the which direction is considered pot minimum value
/// @param range the pot raw value will be mapped into a range of 0 to range
unsigned addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection);
/// add an output to the controls
/// @param pin the pin number connected to the Arduino output
/// @returns a handle (unsigned) to the added output. Use this to access the output.
unsigned addOutput(uint8_t pin);
/// Set the output specified by the provided handle
/// @param handle the handle that was provided previously by calling addOutput()
/// @param val the value to set the output. 0 is low, not zero is high.
void setOutput(unsigned handle, int val);
/// Set the output specified by the provided handle
/// @param handle the handle that was provided previously by calling addOutput()
/// @param val the value to set the output. True is high, false is low.
void setOutput(unsigned handle, bool val);
/// Toggle the output specified by the provided handle
/// @param handle the handle that was provided previously by calling addOutput()
void toggleOutput(unsigned handle);
/// Retrieve the change in position on the specified rotary encoder
/// @param handle the handle that was provided previously by calling addRotary()
/// @returns an integer value. Positive is clockwise, negative is counter-clockwise rotation.
int getRotaryAdjustUnit(unsigned handle);
/// Check if the pot specified by the handle has been updated.
/// @param handle the handle that was provided previously by calling addPot()
/// @param value a reference to a float, the pot value will be written to this variable.
/// @returns true if the pot value has changed since previous check, otherwise false
bool checkPotValue(unsigned handle, float &value);
/// Get the raw uncalibrated value from the pot
/// @returns uncalibrated pot value
int getPotRawValue(unsigned handle);
/// Override the calibration values with new values
/// @param handle handle the handle that was provided previously by calling addPot()
/// @param min the min raw value for the pot
/// @param max the max raw value for the pot
/// @param swapDirection when true, max raw value will mean min control value
/// @returns false when handle is out of range
bool setCalibrationValues(unsigned handle, unsigned min, unsigned max, bool swapDirection);
/// Check if the switch has been toggled since last call
/// @param handle the handle that was provided previously by calling addSwitch()
/// @returns true if the switch changed state, otherwise false
bool isSwitchToggled(unsigned handle);
/// Check if the switch is currently being pressed (held)
/// @param handle the handle that was provided previously by calling addSwitch()
/// @returns true if the switch is held in a pressed or closed state
bool isSwitchHeld(unsigned handle);
/// Get the value of the switch
/// @param handle the handle that was provided previously by calling addSwitch()
/// @returns the value at the switch pin, either 0 or 1.
bool getSwitchValue(unsigned handle);
/// Determine if a switch has changed value
/// @param handle the handle that was provided previously by calling addSwitch()
/// @param switchValue a boolean to store the new switch value
/// @returns true if the switch has changed
bool hasSwitchChanged(unsigned handle, bool &switchValue);
private:
std::vector<Potentiometer> m_pots; ///< a vector of all added pots
std::vector<RotaryEncoder> m_encoders; ///< a vector of all added encoders
std::vector<DigitalInput> m_switches; ///< a vector of all added switches
std::vector<DigitalOutput> m_outputs; ///< a vector of all added outputs
};
} // BALibrary
#endif /* __BAPHYSICALCONTROLS_H */

@ -1,221 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* BASpiMemory is convenience class for accessing the optional SPI RAMs on
* the GTA Series boards. BASpiMemoryDma works the same but uses DMA to reduce
* load on the processor.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BALIBRARY_BASPIMEMORY_H
#define __BALIBRARY_BASPIMEMORY_H
#include <SPI.h>
#include <DmaSpi.h>
#include "BATypes.h"
#include "BAHardware.h"
namespace BALibrary {
/**************************************************************************//**
* This wrapper class uses the Arduino SPI (Wire) library to access the SPI ram.
* @details The purpose of this class is primilary for functional testing since
* it currently support single-word access. High performance access should be
* done using DMA techniques in the Teensy library.
*****************************************************************************/
class BASpiMemory {
public:
BASpiMemory() = delete;
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2).
/// @details default is 20 Mhz
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
BASpiMemory(SpiDeviceId memDeviceId);
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2)
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
/// @param speedHz specify the desired speed in Hz.
BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz);
virtual ~BASpiMemory();
/// initialize and configure the SPI peripheral
virtual void begin();
/// write a single 8-bit word to the specified address
/// @param address the address in the SPI RAM to write to
/// @param data the value to write
void write(size_t address, uint8_t data);
/// Write a block of 8-bit data to the specified address
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numBytes size of the data block in bytes
virtual void write(size_t address, uint8_t *src, size_t numBytes);
/// Write a block of zeros to the specified address
/// @param address the address in the SPI RAM to write to
/// @param numBytes size of the data block in bytes
virtual void zero(size_t address, size_t numBytes);
/// write a single 16-bit word to the specified address
/// @param address the address in the SPI RAM to write to
/// @param data the value to write
void write16(size_t address, uint16_t data);
/// Write a block of 16-bit data to the specified address
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numWords size of the data block in 16-bit words
virtual void write16(size_t address, uint16_t *src, size_t numWords);
/// Write a block of 16-bit zeros to the specified address
/// @param address the address in the SPI RAM to write to
/// @param numWords size of the data block in 16-bit words
virtual void zero16(size_t address, size_t numWords);
/// read a single 8-bit data word from the specified address
/// @param address the address in the SPI RAM to read from
/// @return the data that was read
uint8_t read(size_t address);
/// Read a block of 8-bit data from the specified address
/// @param address the address in the SPI RAM to write to
/// @param dest pointer to the destination
/// @param numBytes size of the data block in bytes
virtual void read(size_t address, uint8_t *dest, size_t numBytes);
/// read a single 16-bit data word from the specified address
/// @param address the address in the SPI RAM to read from
/// @return the data that was read
uint16_t read16(size_t address);
/// read a block 16-bit data word from the specified address
/// @param address the address in the SPI RAM to read from
/// @param dest the pointer to the destination
/// @param numWords the number of 16-bit words to transfer
virtual void read16(size_t address, uint16_t *dest, size_t numWords);
/// Check if the class has been configured by a previous begin() call
/// @returns true if initialized, false if not yet initialized
bool isStarted() const { return m_started; }
protected:
SPIClass *m_spi = nullptr;
SpiDeviceId m_memDeviceId; // the MEM device being control with this instance
uint8_t m_csPin; // the IO pin number for the CS on the controlled SPI device
SPISettings m_settings; // the Wire settings for this SPI port
bool m_started = false;
};
class BASpiMemoryDMA : public BASpiMemory {
public:
BASpiMemoryDMA() = delete;
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2).
/// @details default is 20 Mhz
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
BASpiMemoryDMA(SpiDeviceId memDeviceId);
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2)
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
/// @param speedHz specify the desired speed in Hz.
BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz);
virtual ~BASpiMemoryDMA();
/// initialize and configure the SPI peripheral
void begin() override;
/// Write a block of 8-bit data to the specified address. Be check
/// isWriteBusy() before sending the next DMA transfer.
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numBytes size of the data block in bytes
void write(size_t address, uint8_t *src, size_t numBytes) override;
/// Write a block of zeros to the specified address. Be check
/// isWriteBusy() before sending the next DMA transfer.
/// @param address the address in the SPI RAM to write to
/// @param numBytes size of the data block in bytes
void zero(size_t address, size_t numBytes) override;
/// Write a block of 16-bit data to the specified address. Be check
/// isWriteBusy() before sending the next DMA transfer.
/// @param address the address in the SPI RAM to write to
/// @param src pointer to the source data block
/// @param numWords size of the data block in 16-bit words
void write16(size_t address, uint16_t *src, size_t numWords) override;
/// Write a block of 16-bit zeros to the specified address. Be check
/// isWriteBusy() before sending the next DMA transfer.
/// @param address the address in the SPI RAM to write to
/// @param numWords size of the data block in 16-bit words
void zero16(size_t address, size_t numWords) override;
/// Read a block of 8-bit data from the specified address. Be check
/// isReadBusy() before sending the next DMA transfer.
/// @param address the address in the SPI RAM to write to
/// @param dest pointer to the destination
/// @param numBytes size of the data block in bytes
void read(size_t address, uint8_t *dest, size_t numBytes) override;
/// read a block 16-bit data word from the specified address. Be check
/// isReadBusy() before sending the next DMA transfer.
/// @param address the address in the SPI RAM to read from
/// @param dest the pointer to the destination
/// @param numWords the number of 16-bit words to transfer
void read16(size_t address, uint16_t *dest, size_t numWords) override;
/// Check if a DMA write is in progress
/// @returns true if a write DMA is in progress, else false
bool isWriteBusy() const;
/// Check if a DMA read is in progress
/// @returns true if a read DMA is in progress, else false
bool isReadBusy() const;
/// Readout the 8-bit contents of the DMA storage buffer to the specified destination
/// @param dest pointer to the destination
/// @param numBytes number of bytes to read out
/// @param byteOffset, offset from the start of the DMA buffer in bytes to begin reading
void readBufferContents(uint8_t *dest, size_t numBytes, size_t byteOffset = 0);
/// Readout the 8-bit contents of the DMA storage buffer to the specified destination
/// @param dest pointer to the destination
/// @param numWords number of 16-bit words to read out
/// @param wordOffset, offset from the start of the DMA buffer in words to begin reading
void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0);
private:
DmaSpiGeneric *m_spiDma = nullptr;
AbstractChipSelect *m_cs = nullptr;
uint8_t *m_txCommandBuffer = nullptr;
DmaSpi::Transfer *m_txTransfer;
uint8_t *m_rxCommandBuffer = nullptr;
DmaSpi::Transfer *m_rxTransfer;
uint16_t m_txXferCount;
uint16_t m_rxXferCount;
void m_setSpiCmdAddr(int command, size_t address, uint8_t *dest);
};
} /* namespace BALibrary */
#endif /* __BALIBRARY_BASPIMEMORY_H */

File diff suppressed because it is too large Load Diff

@ -29,7 +29,6 @@
#include "Audio.h"
#include "BATypes.h"
#include "LibMemoryManagement.h"
#ifndef __BALIBRARY_LIBBASICFUNCTIONS_H
#define __BALIBRARY_LIBBASICFUNCTIONS_H
@ -137,7 +136,6 @@ public:
/// Construct an audio buffer using a slot configured with the BALibrary::ExternalSramManager
/// @param slot a pointer to the slot representing the memory you wish to use for the buffer.
AudioDelay(ExtMemSlot *slot);
~AudioDelay();
@ -191,11 +189,23 @@ public:
/// @returns true on success, false on error.
bool interpolateDelay(int16_t *extendedSourceBuffer, int16_t *destBuffer, float fraction, size_t numSamples = AUDIO_BLOCK_SAMPLES);
/// Provides linearly interpolated samples between discrete samples in the sample buffer. The interpolation point for each samples
/// comes from a provided vector of floats which values between 0.0f and 1.0f;
/// The SOURCE buffer MUST BE OVERSIZED
/// to numSamples+1. This is because the last output sample is interpolated from between NUM_SAMPLES and NUM_SAMPLES+1.
/// @details this function is typically not used with audio blocks directly since you need AUDIO_BLOCK_SAMPLES+1 as the source size
/// even though output size is still only AUDIO_BLOCK_SAMPLES. Manually create an oversized buffer and fill it with AUDIO_BLOCK_SAMPLES+1.
/// e.g. 129 instead of 128 samples. The destBuffer does not need to be oversized.
/// @param extendedSourceBuffer A source array that contains one more input sample than output samples needed.
/// @param dest pointer to the target sample array to write the samples to.
/// @param fraction a vector of values between 0.0f and 1.0f that sets the interpolation point between the discrete samples.
/// @param numSamples number of samples to transfer
/// @returns true on success, false on error.
bool interpolateDelayVector(int16_t *extendedSourceBuffer, int16_t *destBuffer, float *fractionVector, size_t numSamples = AUDIO_BLOCK_SAMPLES);
/// When using EXTERNAL memory, this function can return a pointer to the underlying ExtMemSlot object associated
/// with the buffer.
/// @returns pointer to the underlying ExtMemSlot.
ExtMemSlot *getSlot() const { return m_slot; }
/// Ween using INTERNAL memory, thsi function can return a pointer to the underlying RingBuffer that contains
@ -213,7 +223,6 @@ private:
MemType m_type; ///< when 0, INTERNAL memory, when 1, external MEMORY.
RingBuffer<audio_block_t *> *m_ringBuffer = nullptr; ///< When using INTERNAL memory, a RingBuffer will be created.
ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided.
size_t m_maxDelaySamples = 0; ///< stores the number of audio samples in the AudioDelay.
bool m_getSamples(int16_t *dest, size_t offsetSamples, size_t numSamples); ///< operates directly on int16_y buffers
};
@ -476,6 +485,10 @@ private:
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
const T THREE_PI_DIV2_F = 1.5 * 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899; ///< 3*PI/2
const T TRIANGE_POS_SLOPE = 2.0f/PI_F;
const T TRIANGE_NEG_SLOPE = -2.0f/PI_F;
const T SAWTOOTH_SLOPE = -1.0f/PI_F;
};
} // BALibrary

@ -1,212 +0,0 @@
/**************************************************************************//**
* @file
* @author Steve Lascos
* @company Blackaddr Audio
*
* LibMemoryManagment is a class for providing access to external SPI based
* SRAM with the optional convience of breaking it up into 'slots' which are smaller
* memory entities.
* @details This class treats an external memory as a pool from which the user requests
* a block. When using that block, the user need not be concerned with where the pool
* it came from, they only deal with offsets from the start of their memory block. Ie.
* Your particular slot of memory appears to you to start at 0 and end at whatever size
* was requested.
*
* @copyright This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef __BALIBRARY_LIBMEMORYMANAGEMENT_H
#define __BALIBRARY_LIBMEMORYMANAGEMENT_H
#include <cstddef>
#include "BAHardware.h"
#include "BASpiMemory.h"
namespace BALibrary {
/**************************************************************************//**
* MemConfig contains the configuration information associated with a particular
* SPI interface.
*****************************************************************************/
struct MemConfig {
size_t size; ///< the total size of the external SPI memory
size_t totalAvailable; ///< the number of bytes available (remaining)
size_t nextAvailable; ///< the starting point for the next available slot
BASpiMemory *m_spi = nullptr; ///< handle to the SPI interface
};
class ExternalSramManager; // forward declare so ExtMemSlot can declared friendship with it
/**************************************************************************//**
* ExtMemSlot provides a convenient interface to a particular slot of an
* external memory.
* @details the memory can be access randomly, as a single word, as a block of
* data, or as circular queue.
*****************************************************************************/
class ExtMemSlot {
public:
/// clear the entire contents of the slot by writing zeros
/// @returns true on success
bool clear();
/// set a new write position (in bytes) for circular operation
/// @param offsetBytes moves the write pointer to the specified offset from the slot start
/// @returns true on success, else false if offset is beyond slot boundaries.
bool setWritePosition(size_t offsetBytes);
/// returns the currently set write pointer pointer
/// @returns the write position value
size_t getWritePosition() const { return m_currentWrPosition-m_start; }
/// set a new read position (in bytes) for circular operation
/// @param offsetBytes moves the read pointer to the specified offset from the slot start
/// @returns true on success, else false if offset is beyond slot boundaries.
bool setReadPosition(size_t offsetBytes);
/// returns the currently set read pointer pointer
/// @returns the read position value
size_t getReadPosition() const { return m_currentRdPosition-m_start; }
/// Write a block of 16-bit data to the memory at the specified offset
/// @param offsetWords offset in 16-bit words from start of slot
/// @param src pointer to start of block of 16-bit data
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool write16(size_t offsetWords, int16_t *src, size_t numWords);
/// Write a block of zeros (16-bit) to the memory at the specified offset
/// @param offsetWords offset in 16-bit words from start of slot
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool zero16(size_t offsetWords, size_t numWords);
/// Read a block of 16-bit data from the memory at the specified location
/// @param offsetWords offset in 16-bit words from start of slot
/// @param dest pointer to destination for the read data
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool read16(size_t offsetWords, int16_t *dest, size_t numWords);
/// Read the next in memory during circular operation
/// @returns the next 16-bit data word in memory
uint16_t readAdvance16();
/// Read the next block of numWords during circular operation
/// @details, dest is ignored when using DMA
/// @param dest pointer to the destination of the read.
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool readAdvance16(int16_t *dest, size_t numWords);
/// Write a block of 16-bit data from the specified location in circular operation
/// @param src pointer to the start of the block of data to write to memory
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool writeAdvance16(int16_t *src, size_t numWords);
/// Write a single 16-bit data to the next location in circular operation
/// @param data the 16-bit word to transfer
/// @returns true on success, else false on error
bool writeAdvance16(int16_t data); // write just one data
/// Write a block of 16-bit data zeros in circular operation
/// @param numWords number of 16-bit words to transfer
/// @returns true on success, else false on error
bool zeroAdvance16(size_t numWords);
/// Get the size of the memory slot
/// @returns size of the slot in bytes
size_t size() const { return m_size; }
/// Ensures the underlying SPI interface is enabled
/// @returns true on success, false on error
bool enable() const;
/// Checks whether underlying SPI interface is enabled
/// @returns true if enabled, false if not enabled
bool isEnabled() const;
bool isUseDma() const { return m_useDma; }
bool isWriteBusy() const;
bool isReadBusy() const;
/// DEBUG USE: prints out the slot member variables
void printStatus(void) const;
private:
friend ExternalSramManager; ///< gives the manager access to the private variables
bool m_valid = false; ///< After a slot is successfully configured by the manager it becomes valid
size_t m_start = 0; ///< the external memory address in bytes where this slot starts
size_t m_end = 0; ///< the external memory address in bytes where this slot ends (inclusive)
size_t m_currentWrPosition = 0; ///< current write pointer for circular operation
size_t m_currentRdPosition = 0; ///< current read pointer for circular operation
size_t m_size = 0; ///< size of this slot in bytes
bool m_useDma = false; ///< when TRUE, BASpiMemoryDMA will be used.
SpiDeviceId m_spiId; ///< the SPI Device ID
BASpiMemory *m_spi = nullptr; ///< pointer to an instance of the BASpiMemory interface class
};
/**************************************************************************//**
* ExternalSramManager provides a class to handle dividing an external SPI RAM
* into independent slots for general use.
* @details the class does not support deallocated memory because this would cause
* fragmentation.
*****************************************************************************/
class ExternalSramManager final {
public:
ExternalSramManager();
/// The manager is constructed by specifying how many external memories to handle allocations for
/// @param numMemories the number of external memories
ExternalSramManager(unsigned numMemories);
virtual ~ExternalSramManager();
/// Query the amount of available (unallocated) memory
/// @details note that currently, memory cannot be allocated.
/// @param mem specifies which memory to query, default is memory 0
/// @returns the available memory in bytes
size_t availableMemory(BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0);
/// Request memory be allocated for the provided slot
/// @param slot a pointer to the global slot object to which memory will be allocated
/// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes.
/// @param mem specify which external memory to allocate from
/// @param useDma when true, DMA is used for SPI port, else transfers block until complete
/// @returns true on success, otherwise false on error
bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0, bool useDma = false);
/// Request memory be allocated for the provided slot
/// @param slot a pointer to the global slot object to which memory will be allocated
/// @param sizeBytes request the amount of memory in bytes to request
/// @param mem specify which external memory to allocate from
/// @param useDma when true, DMA is used for SPI port, else transfers block until complete
/// @returns true on success, otherwise false on error
bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0, bool useDma = false);
private:
static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project
static MemConfig m_memConfig[BALibrary::NUM_MEM_SLOTS]; ///< store the configuration information for each external memory
};
} // BALibrary
#endif /* __BALIBRARY_LIBMEMORYMANAGEMENT_H */

@ -28,7 +28,6 @@ namespace BALibrary {
// AudioDelay
////////////////////////////////////////////////////
AudioDelay::AudioDelay(size_t maxSamples)
: m_slot(nullptr)
{
m_type = (MemType::MEM_INTERNAL);
@ -44,13 +43,6 @@ AudioDelay::AudioDelay(float maxDelayTimeMs)
}
AudioDelay::AudioDelay(ExtMemSlot *slot)
{
m_type = (MemType::MEM_EXTERNAL);
m_slot = slot;
m_maxDelaySamples = (slot->size() / sizeof(int16_t)) - AUDIO_BLOCK_SAMPLES;
}
AudioDelay::~AudioDelay()
{
if (m_ringBuffer) delete m_ringBuffer;
@ -74,16 +66,7 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block)
m_ringBuffer->push_back(block);
return blockToRelease;
} else {
// EXTERNAL memory
if (!m_slot) { Serial.println("addBlock(): m_slot is not valid"); }
if (block) {
// this causes pops
m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES);
}
blockToRelease = block;
}
}
return blockToRelease;
}
@ -98,10 +81,6 @@ audio_block_t* AudioDelay::getBlock(size_t index)
size_t AudioDelay::getMaxDelaySamples()
{
if (m_type == MemType::MEM_EXTERNAL) {
// update the max delay sample size
m_maxDelaySamples = (m_slot->size() / sizeof(int16_t)) - AUDIO_BLOCK_SAMPLES;
}
return m_maxDelaySamples;
}
@ -160,37 +139,9 @@ bool AudioDelay::m_getSamples(int16_t *dest, size_t offsetSamples, size_t numSam
memcpy(static_cast<void*>(destStart), static_cast<void*>(srcStart), numData * sizeof(int16_t));
return true;
} else {
// EXTERNAL Memory
if (numSamples*sizeof(int16_t) <= m_slot->size() ) { // check for overflow
// current position is considered the write position subtracted by the number of samples we're going
// to read since this is the smallest delay we can get without reading past the write position into
// the "future".
int currentPositionBytes = (int)m_slot->getWritePosition() - (int)(numSamples*sizeof(int16_t));
size_t offsetBytes = offsetSamples * sizeof(int16_t);
if ((int)offsetBytes <= currentPositionBytes) {
// when we back up to read, we won't wrap over the beginning of the slot
m_slot->setReadPosition(currentPositionBytes - offsetBytes);
} else {
// It's going to wrap around to the from the beginning to the end of the slot.
int readPosition = (int)m_slot->size() + currentPositionBytes - offsetBytes;
m_slot->setReadPosition((size_t)readPosition);
}
// Read the number of samples
m_slot->readAdvance16(dest, numSamples);
return true;
} else {
// numSamples is > than total slot size
Serial.println("getSamples(): ERROR numSamples > total slot size");
Serial.println(numSamples + String(" > ") + m_slot->size());
return false;
}
}
return false;
}
bool AudioDelay::interpolateDelay(int16_t *extendedSourceBuffer, int16_t *destBuffer, float fraction, size_t numSamples)
@ -203,7 +154,7 @@ bool AudioDelay::interpolateDelay(int16_t *extendedSourceBuffer, int16_t *destBu
}
/// @todo optimize this later
for (int i=0; i<numSamples; i++) {
for (uint i=0; i<numSamples; i++) {
destBuffer[i] = ((frac1*extendedSourceBuffer[i]) >> 16) + ((frac2*extendedSourceBuffer[i+1]) >> 16);
}
return true;

@ -1,235 +0,0 @@
/*
* ExtMemSlot.cpp
*
* Created on: Jan 19, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include <new>
#include "Audio.h"
#include "LibMemoryManagement.h"
namespace BALibrary {
/////////////////////////////////////////////////////////////////////////////
// MEM SLOT
/////////////////////////////////////////////////////////////////////////////
bool ExtMemSlot::clear()
{
if (!m_valid) { return false; }
m_spi->zero16(m_start, m_size);
return true;
}
bool ExtMemSlot::setWritePosition(size_t offsetBytes)
{
if (m_start + offsetBytes <= m_end) {
m_currentWrPosition = m_start + offsetBytes;
return true;
} else { return false; }
}
bool ExtMemSlot::write16(size_t offsetWords, int16_t *src, size_t numWords)
{
if (!m_valid) { return false; }
size_t writeStart = m_start + sizeof(int16_t)*offsetWords; // 2x because int16 is two bytes per data
size_t numBytes = sizeof(int16_t)*numWords;
if ((writeStart + numBytes-1) <= m_end) {
m_spi->write16(writeStart, reinterpret_cast<uint16_t*>(src), numWords); // cast audio data to uint
return true;
} else {
// this would go past the end of the memory slot, do not perform the write
return false;
}
}
bool ExtMemSlot::setReadPosition(size_t offsetBytes)
{
if (m_start + offsetBytes <= m_end) {
m_currentRdPosition = m_start + offsetBytes;
return true;
} else {
return false;
}
}
bool ExtMemSlot::zero16(size_t offsetWords, size_t numWords)
{
if (!m_valid) { return false; }
size_t writeStart = m_start + sizeof(int16_t)*offsetWords;
size_t numBytes = sizeof(int16_t)*numWords;
if ((writeStart + numBytes-1) <= m_end) {
m_spi->zero16(writeStart, numWords); // cast audio data to uint
return true;
} else {
// this would go past the end of the memory slot, do not perform the write
return false;
}
}
bool ExtMemSlot::read16(size_t offsetWords, int16_t *dest, size_t numWords)
{
if (!dest) return false; // invalid destination
size_t readOffset = m_start + sizeof(int16_t)*offsetWords;
size_t numBytes = sizeof(int16_t)*numWords;
if ((readOffset + numBytes-1) <= m_end) {
m_spi->read16(readOffset, reinterpret_cast<uint16_t*>(dest), numWords);
return true;
} else {
// this would go past the end of the memory slot, do not perform the read
return false;
}
}
uint16_t ExtMemSlot::readAdvance16()
{
uint16_t val = m_spi->read16(m_currentRdPosition);
if (m_currentRdPosition < m_end-1) {
m_currentRdPosition +=2; // position is in bytes and we read two
} else {
m_currentRdPosition = m_start;
}
return val;
}
bool ExtMemSlot::readAdvance16(int16_t *dest, size_t numWords)
{
if (!m_valid) { return false; }
size_t numBytes = sizeof(int16_t)*numWords;
if (m_currentRdPosition + numBytes-1 <= m_end) {
// entire block fits in memory slot without wrapping
m_spi->read16(m_currentRdPosition, reinterpret_cast<uint16_t*>(dest), numWords); // cast audio data to uint.
m_currentRdPosition += numBytes;
} else {
// this read will wrap the memory slot
size_t rdBytes = m_end - m_currentRdPosition + 1;
size_t rdDataNum = rdBytes >> 1; // divide by two to get the number of data
m_spi->read16(m_currentRdPosition, reinterpret_cast<uint16_t*>(dest), rdDataNum);
size_t remainingData = numWords - rdDataNum;
m_spi->read16(m_start, reinterpret_cast<uint16_t*>(dest + rdDataNum), remainingData); // write remaining bytes are start
m_currentRdPosition = m_start + (remainingData*sizeof(int16_t));
}
return true;
}
bool ExtMemSlot::writeAdvance16(int16_t *src, size_t numWords)
{
if (!m_valid) { return false; }
size_t numBytes = sizeof(int16_t)*numWords;
if (m_currentWrPosition + numBytes-1 <= m_end) {
// entire block fits in memory slot without wrapping
m_spi->write16(m_currentWrPosition, reinterpret_cast<uint16_t*>(src), numWords); // cast audio data to uint.
m_currentWrPosition += numBytes;
} else {
// this write will wrap the memory slot
size_t wrBytes = m_end - m_currentWrPosition + 1;
size_t wrDataNum = wrBytes >> 1; // divide by two to get the number of data
m_spi->write16(m_currentWrPosition, reinterpret_cast<uint16_t*>(src), wrDataNum);
size_t remainingData = numWords - wrDataNum;
m_spi->write16(m_start, reinterpret_cast<uint16_t*>(src + wrDataNum), remainingData); // write remaining bytes are start
m_currentWrPosition = m_start + (remainingData*sizeof(int16_t));
}
return true;
}
bool ExtMemSlot::zeroAdvance16(size_t numWords)
{
if (!m_valid) { return false; }
size_t numBytes = 2*numWords;
if (m_currentWrPosition + numBytes-1 <= m_end) {
// entire block fits in memory slot without wrapping
m_spi->zero16(m_currentWrPosition, numWords); // cast audio data to uint.
m_currentWrPosition += numBytes;
} else {
// this write will wrap the memory slot
size_t wrBytes = m_end - m_currentWrPosition + 1;
size_t wrDataNum = wrBytes >> 1;
m_spi->zero16(m_currentWrPosition, wrDataNum);
size_t remainingWords = numWords - wrDataNum; // calculate the remaining bytes
m_spi->zero16(m_start, remainingWords); // write remaining bytes are start
m_currentWrPosition = m_start + remainingWords*sizeof(int16_t);
}
return true;
}
bool ExtMemSlot::writeAdvance16(int16_t data)
{
if (!m_valid) { return false; }
m_spi->write16(m_currentWrPosition, static_cast<uint16_t>(data));
if (m_currentWrPosition < m_end-1) {
m_currentWrPosition+=2; // wrote two bytes
} else {
m_currentWrPosition = m_start;
}
return true;
}
bool ExtMemSlot::enable() const
{
if (m_spi) {
Serial.println("ExtMemSlot::enable()");
m_spi->begin();
return true;
}
else {
Serial.println("ExtMemSlot m_spi is nullptr");
return false;
}
}
bool ExtMemSlot::isEnabled() const
{
if (m_spi) { return m_spi->isStarted(); }
else return false;
}
bool ExtMemSlot::isWriteBusy() const
{
if (m_useDma) {
return (static_cast<BASpiMemoryDMA*>(m_spi))->isWriteBusy();
} else { return false; }
}
bool ExtMemSlot::isReadBusy() const
{
if (m_useDma) {
return (static_cast<BASpiMemoryDMA*>(m_spi))->isReadBusy();
} else { return false; }
}
void ExtMemSlot::printStatus(void) const
{
Serial.println(String("valid:") + m_valid + String(" m_start:") + m_start + \
String(" m_end:") + m_end + String(" m_currentWrPosition: ") + m_currentWrPosition + \
String(" m_currentRdPosition: ") + m_currentRdPosition + \
String(" m_size:") + m_size);
}
}

@ -1,122 +0,0 @@
/*
* LibMemoryManagement.cpp
*
* Created on: Jan 19, 2018
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include <new>
#include "Audio.h"
#include "LibMemoryManagement.h"
namespace BALibrary {
/////////////////////////////////////////////////////////////////////////////
// EXTERNAL SRAM MANAGER
/////////////////////////////////////////////////////////////////////////////
bool ExternalSramManager::m_configured = false;
MemConfig ExternalSramManager::m_memConfig[BALibrary::NUM_MEM_SLOTS];
ExternalSramManager::ExternalSramManager(unsigned numMemories)
{
// Initialize the static memory configuration structs
if (!m_configured) {
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) {
m_memConfig[i].size = MEM_MAX_ADDR[i]+1;
m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]+1;
m_memConfig[i].nextAvailable = 0;
m_memConfig[i].m_spi = nullptr;
}
m_configured = true;
}
}
ExternalSramManager::ExternalSramManager()
: ExternalSramManager(1)
{
}
ExternalSramManager::~ExternalSramManager()
{
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) {
if (m_memConfig[i].m_spi) { delete m_memConfig[i].m_spi; }
}
}
size_t ExternalSramManager::availableMemory(BALibrary::MemSelect mem)
{
return m_memConfig[mem].totalAvailable;
}
bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BALibrary::MemSelect mem, bool useDma)
{
// convert the time to numer of samples
size_t delayLengthInt = (size_t)((delayMilliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f);
return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, useDma);
}
bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BALibrary::MemSelect mem, bool useDma)
{
if (m_memConfig[mem].totalAvailable >= sizeBytes) {
Serial.println(String("Configuring a slot for mem ") + mem);
// there is enough available memory for this request
slot->m_start = m_memConfig[mem].nextAvailable;
slot->m_end = slot->m_start + sizeBytes -1;
slot->m_currentWrPosition = slot->m_start; // init to start of slot
slot->m_currentRdPosition = slot->m_start; // init to start of slot
slot->m_size = sizeBytes;
if (!m_memConfig[mem].m_spi) {
if (useDma) {
m_memConfig[mem].m_spi = new BALibrary::BASpiMemoryDMA(static_cast<BALibrary::SpiDeviceId>(mem));
slot->m_useDma = true;
} else {
m_memConfig[mem].m_spi = new BALibrary::BASpiMemory(static_cast<BALibrary::SpiDeviceId>(mem));
slot->m_useDma = false;
}
if (!m_memConfig[mem].m_spi) {
} else {
Serial.println("Calling spi begin()");
m_memConfig[mem].m_spi->begin();
}
}
slot->m_spi = m_memConfig[mem].m_spi;
// Update the mem config
m_memConfig[mem].nextAvailable = slot->m_end+1;
m_memConfig[mem].totalAvailable -= sizeBytes;
slot->m_valid = true;
if (!slot->isEnabled()) { slot->enable(); }
Serial.println("Clear the memory\n"); Serial.flush();
slot->clear();
Serial.println("Done Request memory\n"); Serial.flush();
return true;
} else {
// there is not enough memory available for the request
Serial.println(String("ExternalSramManager::requestMemory(): Insufficient memory in slot, request/available: ")
+ sizeBytes + String(" : ")
+ m_memConfig[mem].totalAvailable);
return false;
}
}
}

@ -101,34 +101,35 @@ T *LowFrequencyOscillatorVector<T>::getNextVector()
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;
}
if (m_phaseVec[i] < PI_F) {
m_outputVec[i] = -1.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;
// }
// }
// A triangle is made up from two different line equations of form y=mx+b
// where m = +2/pi or -2/pi. A "Triangle Cos" starts at +1.0 and moves to
// -1.0 from angles 0 to PI radians.
for (auto i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
if (m_phaseVec[i] < PI_F) {
// y = (-2/pi)*x + 1.0
m_outputVec[i] = (TRIANGE_NEG_SLOPE * m_phaseVec[i]) + 1.0f;
} else {
// y = (2/pi)*x -1.0
m_outputVec[i] = (TRIANGE_POS_SLOPE * (m_phaseVec[i]-PI_F)) - 1.0f;
}
}
break;
case Waveform::SAWTOOTH :
// A sawtooth is made up of a single equation of the form y=mx+b
// where m = -pi + 1.0
for (auto i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
m_outputVec[i] = (SAWTOOTH_SLOPE * m_phaseVec[i]) + 1.0f;
}
break;
case Waveform::RANDOM :
break;
default :

@ -0,0 +1,329 @@
/*
* AudioEffectAnalogChorus.cpp
*
* Created on: Jan 7, 2018
* Author: slascos
*/
#include <new>
#include <cmath>
#include "AudioEffectAnalogChorusFilters.h"
#include "AudioEffectAnalogChorus.h"
using namespace BALibrary;
namespace BAEffects {
constexpr int MIDI_CHANNEL = 0;
constexpr int MIDI_CONTROL = 1;
constexpr float DELAY_REFERENCE_F = static_cast<float>(AUDIO_BLOCK_SAMPLES/2);
AudioEffectAnalogChorus::AudioEffectAnalogChorus()
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(m_DEFAULT_AVERAGE_DELAY_MS + m_DELAY_RANGE);
m_maxDelaySamples = calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS + m_DELAY_RANGE);
m_averageDelaySamples = static_cast<float>(calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS));
m_delayRange = static_cast<float>(calcAudioSamples(m_DELAY_RANGE));
m_constructFilter();
m_lfo.setWaveform(Waveform::TRIANGLE);
m_lfo.setRateAudio(4.0f); // Default to 4 Hz
}
AudioEffectAnalogChorus::~AudioEffectAnalogChorus()
{
if (m_memory) delete m_memory;
if (m_iir) delete m_iir;
}
// This function just sets up the default filter and coefficients
void AudioEffectAnalogChorus::m_constructFilter(void)
{
// Use CE2 coefficients by default
m_iir = new IirBiQuadFilterHQ(CE2_NUM_STAGES, reinterpret_cast<const int32_t *>(&CE2), CE2_COEFF_SHIFT);
}
void AudioEffectAnalogChorus::setWaveform(BALibrary::Waveform waveform)
{
switch(waveform) {
case Waveform::SINE :
case Waveform::TRIANGLE :
case Waveform::SAWTOOTH :
m_lfo.setWaveform(waveform);
break;
default :
Serial.println("AudioEffectAnalogChorus::setWaveform: Unsupported Waveform");
}
}
void AudioEffectAnalogChorus::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift)
{
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift);
}
void AudioEffectAnalogChorus::setFilter(Filter filter)
{
switch(filter) {
case Filter::WARM :
m_iir->changeFilterCoeffs(WARM_NUM_STAGES, reinterpret_cast<const int32_t *>(&WARM), WARM_COEFF_SHIFT);
break;
case Filter::DARK :
m_iir->changeFilterCoeffs(DARK_NUM_STAGES, reinterpret_cast<const int32_t *>(&DARK), DARK_COEFF_SHIFT);
break;
case Filter::CE2 :
default:
m_iir->changeFilterCoeffs(CE2_NUM_STAGES, reinterpret_cast<const int32_t *>(&CE2), CE2_COEFF_SHIFT);
break;
}
}
void AudioEffectAnalogChorus::update(void)
{
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
// Check is block is disabled
if (m_enable == false) {
// do not transmit or proess any audio, return as quickly as possible.
if (inputAudioBlock) release(inputAudioBlock);
// release all held memory resources
if (m_previousBlock) {
release(m_previousBlock); m_previousBlock = nullptr;
}
if (!m_externalMemory) {
// when using internal memory we have to release all references in the ring buffer
while (m_memory->getRingBuffer()->size() > 0) {
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front();
m_memory->getRingBuffer()->pop_front();
if (releaseBlock) release(releaseBlock);
}
}
return;
}
// Check is block is bypassed, if so either transmit input directly or create silence
if (m_bypass == true) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
inputAudioBlock = allocate();
if (!inputAudioBlock) { return; } // failed to allocate
else {
clearAudioBlock(inputAudioBlock);
}
}
transmit(inputAudioBlock, 0);
release(inputAudioBlock);
return;
}
// Otherwise perform normal processing
// In order to make use of the SPI DMA, we need to request the read from memory first,
// then do other processing while it fills in the back.
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
blockToOutput = allocate();
if (!blockToOutput) return; // skip this update cycle due to failure
// get the data. If using external memory with DMA, this won't be filled until
// later.
// We need to grab two blocks of audio since the modulating delay value from the LFO
// can exceed the length of one audio block during the time frame of one audio block.
int16_t extendedBuffer[(2*AUDIO_BLOCK_SAMPLES)]; // need one more sample for interpolating between 128th and 129th (last sample)
// Get next vector of lfo values, they will range range from -1.0 to +1.0f.
float *lfoValues = m_lfo.getNextVector();
//float lfoValues[128];
for (int i=0; i<128; i++) { lfoValues[i] = lfoValues[i] * m_lfoDepth; }
// Calculate the starting delay from the first lfo sample. This will represent the 'reference' delay
// for this output block
float referenceDelay = m_averageDelaySamples + (lfoValues[0] * m_delayRange);
unsigned delaySamples = static_cast<unsigned>(referenceDelay); // round down to the nearest audio sample for indexing into AudioDelay class
// From a given current delay value, while reading out the next 128, the delay could slew up or down
// AUDIO_BLOCK_SAMPLES/2 cycles of delay. For example...
// Pitching up : current + 128 + 64
// Pitching down: current - 64 + 128
// We need to grab 2*AUDIO_BLOCK_SAMPLES. Be aware that audio samples are stored BACKWARDS in the buffers.
// m_memory->getSamples(extendedBuffer , delaySamples - (AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
// m_memory->getSamples(extendedBuffer + AUDIO_BLOCK_SAMPLES, delaySamples +( AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
m_memory->getSamples(extendedBuffer + AUDIO_BLOCK_SAMPLES, delaySamples - (AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
m_memory->getSamples(extendedBuffer , delaySamples +( AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
// If using DMA, we need something else to do while that read executes, so
// move on to input preprocessing
// Preprocessing
audio_block_t *preProcessed = allocate();
// mix the input with the feedback path in the pre-processing stage
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock);
// consider doing the BBD post processing here to use up more time while waiting
// for the read data to come back
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed);
// BACK TO OUTPUT PROCESSING
double bufferIndexFloat;
int delayIndex;
for (int i=0, j=AUDIO_BLOCK_SAMPLES-1; i<AUDIO_BLOCK_SAMPLES; i++,j--) {
// each output sample will be an interpolated value between two samples
// the precise delay value will be based on the LFO vector values.
// For each output sample, calculate the floating point delay offset from the reference delay.
// This will be an offset from location AUDIO_BLOCK_SAMPLES/2 (e.g. 64) in the buffer.
float offsetDelayFromRef = m_averageDelaySamples + (lfoValues[i] * m_delayRange) - referenceDelay;
float bufferPosition = DELAY_REFERENCE_F + offsetDelayFromRef;
// Get the interpolation coefficients from the fractional part of the buffer position
float fraction1 = modf(bufferPosition, &bufferIndexFloat);
float fraction2 = 1.0f - fraction1;
//fraction1 = 0.5f;
//fraction2 = 0.5f;
delayIndex = static_cast<unsigned>(bufferIndexFloat);
if ( (delayIndex < 0) || (delayIndex > 256) ) {
Serial.println(String("lfoValues[") + i + String("]:") + lfoValues[i] +
String(" referenceDelay:") + referenceDelay +
String(" bufferPosition:") + bufferPosition +
String(" delayIndex:") + delayIndex) ;
}
//delayIndex = 64+i;
blockToOutput->data[j] = static_cast<int16_t>(
(static_cast<float>(extendedBuffer[j+delayIndex]) * fraction1) +
(static_cast<float>(extendedBuffer[j+delayIndex+1]) * fraction2) );
//blockToOutput->data[i] = extendedBuffer[64+i];
}
// perform the wet/dry mix mix
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput);
transmit(blockToOutput);
release(inputAudioBlock);
release(m_previousBlock);
m_previousBlock = blockToOutput;
// if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// // Using DMA
// if (m_blockToRelease) release(m_blockToRelease);
// m_blockToRelease = blockToRelease;
// }
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
void AudioEffectAnalogChorus::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
// TODO: Clean this up with proper preprocessing
// if ( out && dry && wet) {
// alphaBlend(out, dry, wet, m_feedback);
// m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES);
// } else if (dry) {
// memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
// }
}
void AudioEffectAnalogChorus::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if (!out) return; // no valid output buffer
if ( out && dry && wet) {
// Simulate the LPF IIR nature of the analog systems
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES);
alphaBlend(out, dry, wet, m_mix);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
// Set the output volume
gainAdjust(out, out, m_volume, 1);
}
void AudioEffectAnalogChorus::setDelayConfig(float averageDelayMs, float delayRangeMs)
{
setDelayConfig(calcAudioSamples(averageDelayMs), calcAudioSamples(delayRangeMs));
}
void AudioEffectAnalogChorus::setDelayConfig(size_t averageDelayNumSamples, size_t delayRangeNumSamples)
{
size_t delaySamples = averageDelayNumSamples + delayRangeNumSamples;
m_averageDelaySamples = averageDelayNumSamples;
m_delayRange = delayRangeNumSamples;
if (delaySamples > m_memory->getMaxDelaySamples()) {
// this exceeds max delay value, limit it.
delaySamples = m_memory->getMaxDelaySamples();
m_averageDelaySamples = delaySamples/2;
m_delayRange = delaySamples/2;
}
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
}
void AudioEffectAnalogChorus::rate(float rate)
{
// update the LFO by mapping the rate into the MIN/MAX range, pass to LFO in milliseconds
m_lfo.setRateAudio(m_LFO_MIN_RATE + (rate * m_LFO_RANGE));
}
void AudioEffectAnalogChorus::processMidi(int channel, int control, int value)
{
float val = (float)value / 127.0f;
if ((m_midiConfig[RATE][MIDI_CHANNEL] == channel) &&
(m_midiConfig[RATE][MIDI_CONTROL] == control)) {
// Rate
Serial.println(String("AudioEffectAnalogChorus::rate: ") + 100*val + String("%"));
rate(val);
return;
}
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) &&
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) {
// Bypass
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogChorus::not bypassed -> ON") + value); }
else { bypass(true); Serial.println(String("AudioEffectAnalogChorus::bypassed -> OFF") + value); }
return;
}
if ((m_midiConfig[DEPTH][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DEPTH][MIDI_CONTROL] == control)) {
// depth
Serial.println(String("AudioEffectAnalogChorus::depth: ") + 100*val + String("%"));
depth(val);
return;
}
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) &&
(m_midiConfig[MIX][MIDI_CONTROL] == control)) {
// Mix
Serial.println(String("AudioEffectAnalogChorus::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val );
mix(val);
return;
}
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectAnalogChorus::volume: ") + 100*val + String("%"));
volume(val);
return;
}
}
void AudioEffectAnalogChorus::mapMidiControl(int parameter, int midiCC, int midiChannel)
{
if (parameter >= NUM_CONTROLS) {
return ; // Invalid midi parameter
}
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel;
m_midiConfig[parameter][MIDI_CONTROL] = midiCC;
}
}

@ -3,7 +3,7 @@
* @author Steve Lascos
* @company Blackaddr Audio
*
* This file constains precomputed co-efficients for the AudioEffectAnalogDelay
* This file constains precomputed co-efficients for the AudioEffectAnalogChorus
* class.
*
* @copyright This program is free software: you can redistribute it and/or modify
@ -28,7 +28,7 @@ constexpr unsigned MAX_NUM_FILTER_STAGES = 4;
constexpr unsigned NUM_COEFFS_PER_STAGE = 5;
// Matlab/Octave can be helpful to design a filter. Once you have the IIR filter (bz,az) coefficients
// in the z-domain, they can be converted to second-order-sections. AudioEffectAnalogDelay is designed
// in the z-domain, they can be converted to second-order-sections. AudioEffectAnalogChorus is designed
// to accept up to a maximum of an 8th order filter, broken into four, 2nd order stages.
//
// Second order sections can be created with:
@ -42,9 +42,9 @@ constexpr unsigned NUM_COEFFS_PER_STAGE = 5;
// BOSS DM-3 Filters
// b(z) = 1.0e-03 * (0.0032 0.0257 0.0900 0.1800 0.2250 0.1800 0.0900 0.0257 0.0032)
// a(z) = 1.0000 -5.7677 14.6935 -21.3811 19.1491 -10.5202 3.2584 -0.4244 -0.0067
constexpr unsigned DM3_NUM_STAGES = 4;
constexpr unsigned DM3_COEFF_SHIFT = 2;
constexpr int32_t DM3[5*MAX_NUM_FILTER_STAGES] = {
constexpr unsigned CE2_NUM_STAGES = 4;
constexpr unsigned CE2_COEFF_SHIFT = 2;
constexpr int32_t CE2[5*MAX_NUM_FILTER_STAGES] = {
536870912, 988616936, 455608573, 834606945, -482959709,
536870912, 1031466345, 498793368, 965834205, -467402235,
536870912, 1105821939, 573646688, 928470657, -448083489,

@ -1,324 +0,0 @@
/*
* AudioEffectAnalogDelay.cpp
*
* Created on: Jan 7, 2018
* Author: slascos
*/
#include <new>
#include "AudioEffectAnalogDelayFilters.h"
#include "AudioEffectAnalogDelay.h"
using namespace BALibrary;
namespace BAEffects {
constexpr int MIDI_CHANNEL = 0;
constexpr int MIDI_CONTROL = 1;
AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_constructFilter();
}
AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(numSamples);
m_maxDelaySamples = numSamples;
m_constructFilter();
}
// requires preallocated memory large enough
AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(slot);
m_maxDelaySamples = (slot->size() / sizeof(int16_t));
m_externalMemory = true;
m_constructFilter();
}
AudioEffectAnalogDelay::~AudioEffectAnalogDelay()
{
if (m_memory) delete m_memory;
if (m_iir) delete m_iir;
}
// This function just sets up the default filter and coefficients
void AudioEffectAnalogDelay::m_constructFilter(void)
{
// Use DM3 coefficients by default
m_iir = new IirBiQuadFilterHQ(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT);
}
void AudioEffectAnalogDelay::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift)
{
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift);
}
void AudioEffectAnalogDelay::setFilter(Filter filter)
{
switch(filter) {
case Filter::WARM :
m_iir->changeFilterCoeffs(WARM_NUM_STAGES, reinterpret_cast<const int32_t *>(&WARM), WARM_COEFF_SHIFT);
break;
case Filter::DARK :
m_iir->changeFilterCoeffs(DARK_NUM_STAGES, reinterpret_cast<const int32_t *>(&DARK), DARK_COEFF_SHIFT);
break;
case Filter::DM3 :
default:
m_iir->changeFilterCoeffs(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT);
break;
}
}
void AudioEffectAnalogDelay::update(void)
{
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
// Check is block is disabled
if (m_enable == false) {
// do not transmit or process any audio, return as quickly as possible.
if (inputAudioBlock) release(inputAudioBlock);
// release all held memory resources
if (m_previousBlock) {
release(m_previousBlock); m_previousBlock = nullptr;
}
if (!m_externalMemory) {
// when using internal memory we have to release all references in the ring buffer
while (m_memory->getRingBuffer()->size() > 0) {
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front();
m_memory->getRingBuffer()->pop_front();
if (releaseBlock) release(releaseBlock);
}
}
return;
}
// Check is block is bypassed, if so either transmit input directly or create silence
if (m_bypass == true) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
inputAudioBlock = allocate();
if (!inputAudioBlock) { return; } // failed to allocate
else {
clearAudioBlock(inputAudioBlock);
}
}
transmit(inputAudioBlock, 0);
release(inputAudioBlock);
return;
}
// Otherwise perform normal processing
// In order to make use of the SPI DMA, we need to request the read from memory first,
// then do other processing while it fills in the back.
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
blockToOutput = allocate();
if (!blockToOutput) return; // skip this update cycle due to failure
// get the data. If using external memory with DMA, this won't be filled until
// later.
m_memory->getSamples(blockToOutput, m_delaySamples);
// If using DMA, we need something else to do while that read executes, so
// move on to input preprocessing
// Preprocessing
audio_block_t *preProcessed = allocate();
// mix the input with the feedback path in the pre-processing stage
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock);
// consider doing the BBD post processing here to use up more time while waiting
// for the read data to come back
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed);
// BACK TO OUTPUT PROCESSING
// Check if external DMA, if so, we need to be sure the read is completed
if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA
while (m_memory->getSlot()->isReadBusy()) {}
}
// perform the wet/dry mix mix
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput);
transmit(blockToOutput);
release(inputAudioBlock);
release(m_previousBlock);
m_previousBlock = blockToOutput;
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
void AudioEffectAnalogDelay::delay(float milliseconds)
{
size_t delaySamples = calcAudioSamples(milliseconds);
if (delaySamples > m_memory->getMaxDelaySamples()) {
// this exceeds max delay value, limit it.
delaySamples = m_memory->getMaxDelaySamples();
}
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
//QueuePosition queuePosition = calcQueuePosition(milliseconds);
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); }
if (!slot->isEnabled()) {
slot->enable();
Serial.println("WEIRD: slot was not enabled");
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::delay(size_t delaySamples)
{
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
//QueuePosition queuePosition = calcQueuePosition(delaySamples);
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot->isEnabled()) {
slot->enable();
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::delayFractionMax(float delayFraction)
{
size_t delaySamples = static_cast<size_t>(static_cast<float>(m_memory->getMaxDelaySamples()) * delayFraction);
if (delaySamples > m_memory->getMaxDelaySamples()) {
// this exceeds max delay value, limit it.
delaySamples = m_memory->getMaxDelaySamples();
}
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
//QueuePosition queuePosition = calcQueuePosition(delaySamples);
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot->isEnabled()) {
slot->enable();
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if ( out && dry && wet) {
alphaBlend(out, dry, wet, m_feedback);
m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
}
void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if (!out) return; // no valid output buffer
if ( out && dry && wet) {
// Simulate the LPF IIR nature of the analog systems
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES);
alphaBlend(out, dry, wet, m_mix);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
// Set the output volume
gainAdjust(out, out, m_volume, 1);
}
void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
{
float val = (float)value / 127.0f;
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) {
// Delay
if (m_externalMemory) { m_maxDelaySamples = m_memory->getSlot()->size() / sizeof(int16_t); }
size_t delayVal = (size_t)(val * (float)m_maxDelaySamples);
delay(delayVal);
Serial.println(String("AudioEffectAnalogDelay::delay (ms): ") + calcAudioTimeMs(delayVal)
+ String(" (samples): ") + delayVal + String(" out of ") + m_maxDelaySamples);
return;
}
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) &&
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) {
// Bypass
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); }
else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); }
return;
}
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) &&
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) {
// Feedback
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%"));
feedback(val);
return;
}
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) &&
(m_midiConfig[MIX][MIDI_CONTROL] == control)) {
// Mix
Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val );
mix(val);
return;
}
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%"));
volume(val);
return;
}
}
void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel)
{
if (parameter >= NUM_CONTROLS) {
return ; // Invalid midi parameter
}
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel;
m_midiConfig[parameter][MIDI_CONTROL] = midiCC;
}
}

@ -1,345 +0,0 @@
/*
* AudioEffectAnalogDelay.cpp
*
* Created on: Jan 7, 2018
* Author: slascos
*/
#include <new>
#include "AudioEffectAnalogDelayFilters.h"
#include "AudioEffectAnalogDelay.h"
using namespace BALibrary;
#define INTERPOLATED_DELAY Uncomment this line to test the inteprolated delay which adds 1/10th of a sample
namespace BAEffects {
constexpr int MIDI_CHANNEL = 0;
constexpr int MIDI_CONTROL = 1;
AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_constructFilter();
}
AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(numSamples);
m_maxDelaySamples = numSamples;
m_constructFilter();
}
// requires preallocated memory large enough
AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(slot);
m_maxDelaySamples = (slot->size() / sizeof(int16_t));
m_externalMemory = true;
m_constructFilter();
}
AudioEffectAnalogDelay::~AudioEffectAnalogDelay()
{
if (m_memory) delete m_memory;
if (m_iir) delete m_iir;
}
// This function just sets up the default filter and coefficients
void AudioEffectAnalogDelay::m_constructFilter(void)
{
// Use DM3 coefficients by default
m_iir = new IirBiQuadFilterHQ(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT);
}
void AudioEffectAnalogDelay::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift)
{
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift);
}
void AudioEffectAnalogDelay::setFilter(Filter filter)
{
switch(filter) {
case Filter::WARM :
m_iir->changeFilterCoeffs(WARM_NUM_STAGES, reinterpret_cast<const int32_t *>(&WARM), WARM_COEFF_SHIFT);
break;
case Filter::DARK :
m_iir->changeFilterCoeffs(DARK_NUM_STAGES, reinterpret_cast<const int32_t *>(&DARK), DARK_COEFF_SHIFT);
break;
case Filter::DM3 :
default:
m_iir->changeFilterCoeffs(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT);
break;
}
}
void AudioEffectAnalogDelay::update(void)
{
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
// Check is block is disabled
if (m_enable == false) {
// do not transmit or process any audio, return as quickly as possible.
if (inputAudioBlock) release(inputAudioBlock);
// release all held memory resources
if (m_previousBlock) {
release(m_previousBlock); m_previousBlock = nullptr;
}
if (!m_externalMemory) {
// when using internal memory we have to release all references in the ring buffer
while (m_memory->getRingBuffer()->size() > 0) {
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front();
m_memory->getRingBuffer()->pop_front();
if (releaseBlock) release(releaseBlock);
}
}
return;
}
// Check is block is bypassed, if so either transmit input directly or create silence
if (m_bypass == true) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
inputAudioBlock = allocate();
if (!inputAudioBlock) { return; } // failed to allocate
else {
clearAudioBlock(inputAudioBlock);
}
}
transmit(inputAudioBlock, 0);
release(inputAudioBlock);
return;
}
// Otherwise perform normal processing
// In order to make use of the SPI DMA, we need to request the read from memory first,
// then do other processing while it fills in the back.
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
blockToOutput = allocate();
if (!blockToOutput) return; // skip this update cycle due to failure
// get the data. If using external memory with DMA, this won't be filled until
// later.
#ifdef INTERPOLATED_DELAY
int16_t extendedBuffer[AUDIO_BLOCK_SAMPLES+1]; // need one more sample for intepolating between 128th and 129th (last sample)
m_memory->getSamples(extendedBuffer, m_delaySamples, AUDIO_BLOCK_SAMPLES+1);
#else
m_memory->getSamples(blockToOutput, m_delaySamples);
#endif
// If using DMA, we need something else to do while that read executes, so
// move on to input preprocessing
// Preprocessing
audio_block_t *preProcessed = allocate();
// mix the input with the feedback path in the pre-processing stage
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock);
// consider doing the BBD post processing here to use up more time while waiting
// for the read data to come back
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed);
// BACK TO OUTPUT PROCESSING
// Check if external DMA, if so, we need to be sure the read is completed
if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA
while (m_memory->getSlot()->isReadBusy()) {}
}
#ifdef INTERPOLATED_DELAY
// TODO: partial delay testing
// extendedBuffer is oversized
//memcpy(blockToOutput->data, &extendedBuffer[1], sizeof(int16_t)*AUDIO_BLOCK_SAMPLES);
m_memory->interpolateDelay(extendedBuffer, blockToOutput->data, 0.1f, AUDIO_BLOCK_SAMPLES);
#endif
// perform the wet/dry mix mix
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput);
transmit(blockToOutput);
release(inputAudioBlock);
release(m_previousBlock);
m_previousBlock = blockToOutput;
// if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// // Using DMA
// if (m_blockToRelease) release(m_blockToRelease);
// m_blockToRelease = blockToRelease;
// }
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
void AudioEffectAnalogDelay::delay(float milliseconds)
{
size_t delaySamples = calcAudioSamples(milliseconds);
if (delaySamples > m_memory->getMaxDelaySamples()) {
// this exceeds max delay value, limit it.
delaySamples = m_memory->getMaxDelaySamples();
}
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
//QueuePosition queuePosition = calcQueuePosition(milliseconds);
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); }
if (!slot->isEnabled()) {
slot->enable();
Serial.println("WEIRD: slot was not enabled");
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::delay(size_t delaySamples)
{
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
//QueuePosition queuePosition = calcQueuePosition(delaySamples);
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot->isEnabled()) {
slot->enable();
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::delayFractionMax(float delayFraction)
{
size_t delaySamples = static_cast<size_t>(static_cast<float>(m_memory->getMaxDelaySamples()) * delayFraction);
if (delaySamples > m_memory->getMaxDelaySamples()) {
// this exceeds max delay value, limit it.
delaySamples = m_memory->getMaxDelaySamples();
}
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {
// internal memory
//QueuePosition queuePosition = calcQueuePosition(delaySamples);
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
} else {
// external memory
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot->isEnabled()) {
slot->enable();
}
}
m_delaySamples = delaySamples;
}
void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if ( out && dry && wet) {
alphaBlend(out, dry, wet, m_feedback);
m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
}
void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet)
{
if (!out) return; // no valid output buffer
if ( out && dry && wet) {
// Simulate the LPF IIR nature of the analog systems
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES);
alphaBlend(out, dry, wet, m_mix);
} else if (dry) {
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
// Set the output volume
gainAdjust(out, out, m_volume, 1);
}
void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
{
float val = (float)value / 127.0f;
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) {
// Delay
if (m_externalMemory) { m_maxDelaySamples = m_memory->getSlot()->size() / sizeof(int16_t); }
size_t delayVal = (size_t)(val * (float)m_maxDelaySamples);
delay(delayVal);
Serial.println(String("AudioEffectAnalogDelay::delay (ms): ") + calcAudioTimeMs(delayVal)
+ String(" (samples): ") + delayVal + String(" out of ") + m_maxDelaySamples);
return;
}
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) &&
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) {
// Bypass
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); }
else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); }
return;
}
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) &&
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) {
// Feedback
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%"));
feedback(val);
return;
}
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) &&
(m_midiConfig[MIX][MIDI_CONTROL] == control)) {
// Mix
Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val );
mix(val);
return;
}
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%"));
volume(val);
return;
}
}
void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel)
{
if (parameter >= NUM_CONTROLS) {
return ; // Invalid midi parameter
}
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel;
m_midiConfig[parameter][MIDI_CONTROL] = midiCC;
}
}

@ -1,294 +0,0 @@
/*
* BAAudioEffectDelayExternal.cpp
*
* Created on: November 1, 2017
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BAAudioEffectDelayExternal.h"
using namespace BALibrary;
namespace BAEffects {
#define SPISETTING SPISettings(20000000, MSBFIRST, SPI_MODE0)
struct MemSpiConfig {
unsigned mosiPin;
unsigned misoPin;
unsigned sckPin;
unsigned csPin;
unsigned memSize;
};
constexpr MemSpiConfig Mem0Config = {7, 8, 14, 15, 65536 };
constexpr MemSpiConfig Mem1Config = {21, 5, 20, 31, 65536 };
unsigned BAAudioEffectDelayExternal::m_usingSPICount[2] = {0,0};
BAAudioEffectDelayExternal::BAAudioEffectDelayExternal()
: AudioStream(1, m_inputQueueArray)
{
initialize(MemSelect::MEM0);
}
BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(MemSelect mem)
: AudioStream(1, m_inputQueueArray)
{
initialize(mem);
}
BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs)
: AudioStream(1, m_inputQueueArray)
{
unsigned delayLengthInt = (delayLengthMs*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f;
initialize(type, delayLengthInt);
}
BAAudioEffectDelayExternal::~BAAudioEffectDelayExternal()
{
if (m_spi) delete m_spi;
}
void BAAudioEffectDelayExternal::delay(uint8_t channel, float milliseconds) {
if (channel >= 8) return;
if (milliseconds < 0.0) milliseconds = 0.0;
uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f;
n += AUDIO_BLOCK_SAMPLES;
if (n > m_memoryLength - AUDIO_BLOCK_SAMPLES)
n = m_memoryLength - AUDIO_BLOCK_SAMPLES;
m_channelDelayLength[channel] = n;
unsigned mask = m_activeMask;
if (m_activeMask == 0) m_startUsingSPI(m_spiChannel);
m_activeMask = mask | (1<<channel);
}
void BAAudioEffectDelayExternal::disable(uint8_t channel) {
if (channel >= 8) return;
uint8_t mask = m_activeMask & ~(1<<channel);
m_activeMask = mask;
if (mask == 0) m_stopUsingSPI(m_spiChannel);
}
void BAAudioEffectDelayExternal::update(void)
{
audio_block_t *block;
uint32_t n, channel, read_offset;
// grab incoming data and put it into the memory
block = receiveReadOnly();
if (block) {
if (m_headOffset + AUDIO_BLOCK_SAMPLES <= m_memoryLength) {
// a single write is enough
write(m_headOffset, AUDIO_BLOCK_SAMPLES, block->data);
m_headOffset += AUDIO_BLOCK_SAMPLES;
} else {
// write wraps across end-of-memory
n = m_memoryLength - m_headOffset;
write(m_headOffset, n, block->data);
m_headOffset = AUDIO_BLOCK_SAMPLES - n;
write(0, m_headOffset, block->data + n);
}
release(block);
} else {
// if no input, store zeros, so later playback will
// not be random garbage previously stored in memory
if (m_headOffset + AUDIO_BLOCK_SAMPLES <= m_memoryLength) {
zero(m_headOffset, AUDIO_BLOCK_SAMPLES);
m_headOffset += AUDIO_BLOCK_SAMPLES;
} else {
n = m_memoryLength - m_headOffset;
zero(m_headOffset, n);
m_headOffset = AUDIO_BLOCK_SAMPLES - n;
zero(0, m_headOffset);
}
}
// transmit the delayed outputs
for (channel = 0; channel < 8; channel++) {
if (!(m_activeMask & (1<<channel))) continue;
block = allocate();
if (!block) continue;
// compute the delayed location where we read
if (m_channelDelayLength[channel] <= m_headOffset) {
read_offset = m_headOffset - m_channelDelayLength[channel];
} else {
read_offset = m_memoryLength + m_headOffset - m_channelDelayLength[channel];
}
if (read_offset + AUDIO_BLOCK_SAMPLES <= m_memoryLength) {
// a single read will do it
read(read_offset, AUDIO_BLOCK_SAMPLES, block->data);
} else {
// read wraps across end-of-memory
n = m_memoryLength - read_offset;
read(read_offset, n, block->data);
read(0, AUDIO_BLOCK_SAMPLES - n, block->data + n);
}
transmit(block, channel);
release(block);
}
}
unsigned BAAudioEffectDelayExternal::m_allocated[2] = {0, 0};
void BAAudioEffectDelayExternal::initialize(MemSelect mem, unsigned delayLength)
{
unsigned samples = 0;
unsigned memsize, avail;
m_activeMask = 0;
m_headOffset = 0;
m_mem = mem;
switch (mem) {
case MemSelect::MEM0 :
{
memsize = Mem0Config.memSize;
m_spi = &SPI;
m_spiChannel = 0;
m_misoPin = Mem0Config.misoPin;
m_mosiPin = Mem0Config.mosiPin;
m_sckPin = Mem0Config.sckPin;
m_csPin = Mem0Config.csPin;
m_spi->setMOSI(m_mosiPin);
m_spi->setMISO(m_misoPin);
m_spi->setSCK(m_sckPin);
m_spi->begin();
break;
}
case MemSelect::MEM1 :
{
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
memsize = Mem1Config.memSize;
m_spi = &SPI1;
m_spiChannel = 1;
m_misoPin = Mem1Config.misoPin;
m_mosiPin = Mem1Config.mosiPin;
m_sckPin = Mem1Config.sckPin;
m_csPin = Mem1Config.csPin;
m_spi->setMOSI(m_mosiPin);
m_spi->setMISO(m_misoPin);
m_spi->setSCK(m_sckPin);
m_spi->begin();
#endif
break;
}
}
pinMode(m_csPin, OUTPUT);
digitalWriteFast(m_csPin, HIGH);
avail = memsize - m_allocated[mem];
if (delayLength > avail) samples = avail;
m_memoryStart = m_allocated[mem];
m_allocated[mem] += samples;
m_memoryLength = samples;
zero(0, m_memoryLength);
}
void BAAudioEffectDelayExternal::read(uint32_t offset, uint32_t count, int16_t *data)
{
uint32_t addr = m_memoryStart + offset;
addr *= 2;
m_spi->beginTransaction(SPISETTING);
digitalWriteFast(m_csPin, LOW);
m_spi->transfer16((0x03 << 8) | (addr >> 16));
m_spi->transfer16(addr & 0xFFFF);
while (count) {
*data++ = (int16_t)(m_spi->transfer16(0));
count--;
}
digitalWriteFast(m_csPin, HIGH);
m_spi->endTransaction();
}
void BAAudioEffectDelayExternal::write(uint32_t offset, uint32_t count, const int16_t *data)
{
uint32_t addr = m_memoryStart + offset;
addr *= 2;
m_spi->beginTransaction(SPISETTING);
digitalWriteFast(m_csPin, LOW);
m_spi->transfer16((0x02 << 8) | (addr >> 16));
m_spi->transfer16(addr & 0xFFFF);
while (count) {
int16_t w = 0;
if (data) w = *data++;
m_spi->transfer16(w);
count--;
}
digitalWriteFast(m_csPin, HIGH);
m_spi->endTransaction();
}
///////////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////////
void BAAudioEffectDelayExternal::zero(uint32_t address, uint32_t count) {
write(address, count, NULL);
}
#ifdef SPI_HAS_NOTUSINGINTERRUPT
inline void BAAudioEffectDelayExternal::m_startUsingSPI(int spiBus) {
if (spiBus == 0) {
m_spi->usingInterrupt(IRQ_SOFTWARE);
} else if (spiBus == 1) {
m_spi->usingInterrupt(IRQ_SOFTWARE);
}
m_usingSPICount[spiBus]++;
}
inline void BAAudioEffectDelayExternal::m_stopUsingSPI(int spiBus) {
if (m_usingSPICount[spiBus] == 0 || --m_usingSPICount[spiBus] == 0)
{
if (spiBus == 0) {
m_spi->notUsingInterrupt(IRQ_SOFTWARE);
} else if (spiBus == 1) {
m_spi->notUsingInterrupt(IRQ_SOFTWARE);
}
}
}
#else
inline void BAAudioEffectDelayExternal::m_startUsingSPI(int spiBus) {
if (spiBus == 0) {
m_spi->usingInterrupt(IRQ_SOFTWARE);
} else if (spiBus == 1) {
m_spi->usingInterrupt(IRQ_SOFTWARE);
}
}
inline void BAAudioEffectDelayExternal::m_stopUsingSPI(int spiBus) {
}
#endif
} /* namespace BAEffects */

@ -1,304 +0,0 @@
/*
* AudioEffectSOS.cpp
*
* Created on: Apr 14, 2018
* Author: blackaddr
*/
#include "AudioEffectSOS.h"
#include "LibBasicFunctions.h"
using namespace BALibrary;
namespace BAEffects {
constexpr int MIDI_CHANNEL = 0;
constexpr int MIDI_CONTROL = 1;
constexpr float MAX_GATE_OPEN_TIME_MS = 3000.0f;
constexpr float MAX_GATE_CLOSE_TIME_MS = 1000.0f;
constexpr int GATE_OPEN_STAGE = 0;
constexpr int GATE_HOLD_STAGE = 1;
constexpr int GATE_CLOSE_STAGE = 2;
AudioEffectSOS::AudioEffectSOS(float maxDelayMs)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_externalMemory = false;
}
AudioEffectSOS::AudioEffectSOS(size_t numSamples)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(numSamples);
m_maxDelaySamples = numSamples;
m_externalMemory = false;
}
AudioEffectSOS::AudioEffectSOS(ExtMemSlot *slot)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(slot);
m_externalMemory = true;
}
AudioEffectSOS::~AudioEffectSOS()
{
if (m_memory) delete m_memory;
}
void AudioEffectSOS::setGateLedGpio(int pinId)
{
m_gateLedPinId = pinId;
pinMode(static_cast<uint8_t>(m_gateLedPinId), OUTPUT);
}
void AudioEffectSOS::enable(void)
{
m_enable = true;
if (m_externalMemory) {
// Because we hold the previous output buffer for an update cycle, the maximum delay is actually
// 1 audio block mess then the max delay returnable from the memory.
m_maxDelaySamples = m_memory->getMaxDelaySamples();
Serial.println(String("SOS Enabled with delay length ") + m_maxDelaySamples + String(" samples"));
}
m_delaySamples = m_maxDelaySamples;
m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL);
m_inputGateAuto.setupParameter(GATE_HOLD_STAGE, 1.0f, 1.0f, m_maxDelaySamples, ParameterAutomation<float>::Function::HOLD);
m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL);
m_clearFeedbackAuto.setupParameter(GATE_OPEN_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL);
m_clearFeedbackAuto.setupParameter(GATE_HOLD_STAGE, 0.0f, 0.0f, m_maxDelaySamples, ParameterAutomation<float>::Function::HOLD);
m_clearFeedbackAuto.setupParameter(GATE_CLOSE_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL);
}
void AudioEffectSOS::update(void)
{
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
// Check is block is disabled
if (m_enable == false) {
// do not transmit or process any audio, return as quickly as possible.
if (inputAudioBlock) release(inputAudioBlock);
// release all held memory resources
if (m_previousBlock) {
release(m_previousBlock); m_previousBlock = nullptr;
}
if (!m_externalMemory) {
// when using internal memory we have to release all references in the ring buffer
while (m_memory->getRingBuffer()->size() > 0) {
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front();
m_memory->getRingBuffer()->pop_front();
if (releaseBlock) release(releaseBlock);
}
}
return;
}
// Check is block is bypassed, if so either transmit input directly or create silence
if ( (m_bypass == true) || (!inputAudioBlock) ) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
inputAudioBlock = allocate();
if (!inputAudioBlock) { return; } // failed to allocate
else {
clearAudioBlock(inputAudioBlock);
}
}
transmit(inputAudioBlock, 0);
release(inputAudioBlock);
return;
}
if (!inputAudioBlock) return;
// Otherwise perform normal processing
// In order to make use of the SPI DMA, we need to request the read from memory first,
// then do other processing while it fills in the back.
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
blockToOutput = allocate();
if (!blockToOutput) return; // skip this update cycle due to failure
// get the data. If using external memory with DMA, this won't be filled until
// later.
m_memory->getSamples(blockToOutput, m_delaySamples);
//Serial.println(String("Delay samples:") + m_delaySamples);
//Serial.println(String("Use dma: ") + m_memory->getSlot()->isUseDma());
// If using DMA, we need something else to do while that read executes, so
// move on to input preprocessing
// Preprocessing
audio_block_t *preProcessed = allocate();
// mix the input with the feedback path in the pre-processing stage
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock);
// consider doing the BBD post processing here to use up more time while waiting
// for the read data to come back
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed);
//audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock);
//Serial.println("Done adding new block");
// BACK TO OUTPUT PROCESSING
// Check if external DMA, if so, we need to be sure the read is completed
if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA
while (m_memory->getSlot()->isReadBusy()) {}
}
// perform the wet/dry mix mix
m_postProcessing(blockToOutput, blockToOutput);
transmit(blockToOutput);
release(inputAudioBlock);
if (m_previousBlock)
release(m_previousBlock);
m_previousBlock = blockToOutput;
if (m_blockToRelease == m_previousBlock) {
Serial.println("ERROR: POINTER COLLISION");
}
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
void AudioEffectSOS::gateOpenTime(float milliseconds)
{
// TODO - change the paramter automation to an automation sequence
m_openTimeMs = milliseconds;
m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, m_openTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL);
//m_clearFeedbackAuto.setupParameter(GATE_OPEN_STAGE, 1.0f, 0.0f, m_openTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL);
}
void AudioEffectSOS::gateCloseTime(float milliseconds)
{
m_closeTimeMs = milliseconds;
m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, m_closeTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL);
//m_clearFeedbackAuto.setupParameter(GATE_CLOSE_STAGE, 0.0f, 1.0f, m_closeTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL);
}
////////////////////////////////////////////////////////////////////////
// MIDI PROCESSING
////////////////////////////////////////////////////////////////////////
void AudioEffectSOS::processMidi(int channel, int control, int value)
{
float val = (float)value / 127.0f;
if ((m_midiConfig[GATE_OPEN_TIME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[GATE_OPEN_TIME][MIDI_CONTROL] == control)) {
// Gate Open Time
gateOpenTime(val * MAX_GATE_OPEN_TIME_MS);
Serial.println(String("AudioEffectSOS::gate open time (ms): ") + m_openTimeMs);
return;
}
if ((m_midiConfig[GATE_CLOSE_TIME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[GATE_CLOSE_TIME][MIDI_CONTROL] == control)) {
// Gate Close Time
gateCloseTime(val * MAX_GATE_CLOSE_TIME_MS);
Serial.println(String("AudioEffectSOS::gate close time (ms): ") + m_closeTimeMs);
return;
}
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) &&
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) {
// Feedback
Serial.println(String("AudioEffectSOS::feedback: ") + 100*val + String("%"));
feedback(val);
return;
}
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectSOS::volume: ") + 100*val + String("%"));
volume(val);
return;
}
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) &&
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) {
// Bypass
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectSOS::not bypassed -> ON") + value); }
else { bypass(true); Serial.println(String("AudioEffectSOS::bypassed -> OFF") + value); }
return;
}
if ((m_midiConfig[GATE_TRIGGER][MIDI_CHANNEL] == channel) &&
(m_midiConfig[GATE_TRIGGER][MIDI_CONTROL] == control)) {
// The gate is triggered by any value
Serial.println(String("AudioEffectSOS::Gate Triggered!"));
m_inputGateAuto.trigger();
return;
}
if ((m_midiConfig[CLEAR_FEEDBACK_TRIGGER][MIDI_CHANNEL] == channel) &&
(m_midiConfig[CLEAR_FEEDBACK_TRIGGER][MIDI_CONTROL] == control)) {
// The gate is triggered by any value
Serial.println(String("AudioEffectSOS::Clear feedback Triggered!"));
m_clearFeedbackAuto.trigger();
return;
}
}
void AudioEffectSOS::mapMidiControl(int parameter, int midiCC, int midiChannel)
{
if (parameter >= NUM_CONTROLS) {
return ; // Invalid midi parameter
}
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel;
m_midiConfig[parameter][MIDI_CONTROL] = midiCC;
}
//////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS
//////////////////////////////////////////////////////////////////////
void AudioEffectSOS::m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal)
{
if ( out && input && delayedSignal) {
// Multiply the input signal by the automated gate value
// Multiply the delayed signal by the user set feedback value
// Then combine the two
float gateVol = m_inputGateAuto.getNextValue();
float feedbackAdjust = m_clearFeedbackAuto.getNextValue();
audio_block_t tempAudioBuffer;
gainAdjust(out, input, gateVol, 0); // last paremeter is coeff shift, 0 bits
gainAdjust(&tempAudioBuffer, delayedSignal, m_feedback*feedbackAdjust, 0); // last parameter is coeff shift, 0 bits
combine(out, out, &tempAudioBuffer);
} else if (input) {
memcpy(out->data, input->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
// Update the gate LED
if (m_gateLedPinId >= 0) {
if (m_inputGateAuto.isFinished() && m_clearFeedbackAuto.isFinished()) {
digitalWriteFast(m_gateLedPinId, 0x0);
} else {
digitalWriteFast(m_gateLedPinId, 0x1);
}
}
}
void AudioEffectSOS::m_postProcessing(audio_block_t *out, audio_block_t *in)
{
gainAdjust(out, out, m_volume, 0);
}
} // namespace BAEffects

@ -1,163 +0,0 @@
/*
* AudioEffectTremolo.cpp
*
* Created on: Jan 7, 2018
* Author: slascos
*/
#include <cmath> // std::roundf
#include "AudioEffectTremolo.h"
using namespace BALibrary;
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.setWaveform(m_waveform);
}
AudioEffectTremolo::~AudioEffectTremolo()
{
}
void AudioEffectTremolo::update(void)
{
audio_block_t *inputAudioBlock = receiveWritable(); // get the next block of input samples
// Check is block is disabled
if (m_enable == false) {
// do not transmit or process any audio, return as quickly as possible.
if (inputAudioBlock) release(inputAudioBlock);
return;
}
// Check is block is bypassed, if so either transmit input directly or create silence
if (m_bypass == true) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
inputAudioBlock = allocate();
if (!inputAudioBlock) { return; } // failed to allocate
else {
clearAudioBlock(inputAudioBlock);
}
}
transmit(inputAudioBlock, 0);
release(inputAudioBlock);
return;
}
// DO PROCESSING
// apply modulation wave
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);
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)
{
float val = (float)value / 127.0f;
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) &&
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) {
// Bypass
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectTremolo::not bypassed -> ON") + value); }
else { bypass(true); Serial.println(String("AudioEffectTremolo::bypassed -> OFF") + value); }
return;
}
if ((m_midiConfig[RATE][MIDI_CHANNEL] == channel) &&
(m_midiConfig[RATE][MIDI_CONTROL] == control)) {
// Rate
rate(val);
Serial.println(String("AudioEffectTremolo::rate: ") + m_rate);
return;
}
if ((m_midiConfig[DEPTH][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DEPTH][MIDI_CONTROL] == control)) {
// Depth
depth(val);
Serial.println(String("AudioEffectTremolo::depth: ") + m_depth);
return;
}
if ((m_midiConfig[WAVEFORM][MIDI_CHANNEL] == channel) &&
(m_midiConfig[WAVEFORM][MIDI_CONTROL] == control)) {
// Waveform
if (value < 16) {
m_waveform = Waveform::SINE;
} else if (value < 32) {
m_waveform = Waveform::TRIANGLE;
} else if (value < 48) {
m_waveform = Waveform::SQUARE;
} else if (value < 64) {
m_waveform = Waveform::SAWTOOTH;
} else if (value < 80) {
m_waveform = Waveform::RANDOM;
}
Serial.println(String("AudioEffectTremolo::waveform: ") + static_cast<unsigned>(m_waveform));
return;
}
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectTremolo::volume: ") + 100*val + String("%"));
volume(val);
return;
}
}
void AudioEffectTremolo::mapMidiControl(int parameter, int midiCC, int midiChannel)
{
if (parameter >= NUM_CONTROLS) {
return ; // Invalid midi parameter
}
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel;
m_midiConfig[parameter][MIDI_CONTROL] = midiCC;
}
}

@ -1,383 +0,0 @@
/*
* BAAudioControlWM8731.cpp
*
* Created on: May 22, 2017
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wire.h>
#include "BAAudioControlWM8731.h"
namespace BALibrary {
// use const instead of define for proper scoping
constexpr int WM8731_I2C_ADDR = 0x1A;
// The WM8731 register map
constexpr int WM8731_REG_LLINEIN = 0;
constexpr int WM8731_REG_RLINEIN = 1;
constexpr int WM8731_REG_LHEADOUT = 2;
constexpr int WM8731_REG_RHEADOUT = 3;
constexpr int WM8731_REG_ANALOG =4;
constexpr int WM8731_REG_DIGITAL = 5;
constexpr int WM8731_REG_POWERDOWN = 6;
constexpr int WM8731_REG_INTERFACE = 7;
constexpr int WM8731_REG_SAMPLING = 8;
constexpr int WM8731_REG_ACTIVE = 9;
constexpr int WM8731_REG_RESET = 15;
// Register Masks and Shifts
// Register 0
constexpr int WM8731_LEFT_INPUT_GAIN_ADDR = 0;
constexpr int WM8731_LEFT_INPUT_GAIN_MASK = 0x1F;
constexpr int WM8731_LEFT_INPUT_GAIN_SHIFT = 0;
constexpr int WM8731_LEFT_INPUT_MUTE_ADDR = 0;
constexpr int WM8731_LEFT_INPUT_MUTE_MASK = 0x80;
constexpr int WM8731_LEFT_INPUT_MUTE_SHIFT = 7;
constexpr int WM8731_LINK_LEFT_RIGHT_IN_ADDR = 0;
constexpr int WM8731_LINK_LEFT_RIGHT_IN_MASK = 0x100;
constexpr int WM8731_LINK_LEFT_RIGHT_IN_SHIFT = 8;
// Register 1
constexpr int WM8731_RIGHT_INPUT_GAIN_ADDR = 1;
constexpr int WM8731_RIGHT_INPUT_GAIN_MASK = 0x1F;
constexpr int WM8731_RIGHT_INPUT_GAIN_SHIFT = 0;
constexpr int WM8731_RIGHT_INPUT_MUTE_ADDR = 1;
constexpr int WM8731_RIGHT_INPUT_MUTE_MASK = 0x80;
constexpr int WM8731_RIGHT_INPUT_MUTE_SHIFT = 7;
constexpr int WM8731_LINK_RIGHT_LEFT_IN_ADDR = 1;
constexpr int WM8731_LINK_RIGHT_LEFT_IN_MASK = 0x100;
constexpr int WM8731_LINK_RIGHT_LEFT_IN_SHIFT = 8;
// Register 2
constexpr int WM8731_LEFT_HEADPHONE_VOL_ADDR = 2;
constexpr int WM8731_LEFT_HEADPHONE_VOL_MASK = 0x7F;
constexpr int WM8731_LEFT_HEADPHONE_VOL_SHIFT = 0;
constexpr int WM8731_LEFT_HEADPHONE_ZCD_ADDR = 2;
constexpr int WM8731_LEFT_HEADPHONE_ZCD_MASK = 0x80;
constexpr int WM8731_LEFT_HEADPHONE_ZCD_SHIFT = 7;
constexpr int WM8731_LEFT_HEADPHONE_LINK_ADDR = 2;
constexpr int WM8731_LEFT_HEADPHONE_LINK_MASK = 0x100;
constexpr int WM8731_LEFT_HEADPHONE_LINK_SHIFT = 8;
// Register 3
constexpr int WM8731_RIGHT_HEADPHONE_VOL_ADDR = 3;
constexpr int WM8731_RIGHT_HEADPHONE_VOL_MASK = 0x7F;
constexpr int WM8731_RIGHT_HEADPHONE_VOL_SHIFT = 0;
constexpr int WM8731_RIGHT_HEADPHONE_ZCD_ADDR = 3;
constexpr int WM8731_RIGHT_HEADPHONE_ZCD_MASK = 0x80;
constexpr int WM8731_RIGHT_HEADPHONE_ZCD_SHIFT = 7;
constexpr int WM8731_RIGHT_HEADPHONE_LINK_ADDR = 3;
constexpr int WM8731_RIGHT_HEADPHONE_LINK_MASK = 0x100;
constexpr int WM8731_RIGHT_HEADPHONE_LINK_SHIFT = 8;
// Register 4
constexpr int WM8731_ADC_BYPASS_ADDR = 4;
constexpr int WM8731_ADC_BYPASS_MASK = 0x8;
constexpr int WM8731_ADC_BYPASS_SHIFT = 3;
constexpr int WM8731_DAC_SELECT_ADDR = 4;
constexpr int WM8731_DAC_SELECT_MASK = 0x10;
constexpr int WM8731_DAC_SELECT_SHIFT = 4;
// Register 5
constexpr int WM8731_DAC_MUTE_ADDR = 5;
constexpr int WM8731_DAC_MUTE_MASK = 0x8;
constexpr int WM8731_DAC_MUTE_SHIFT = 3;
constexpr int WM8731_HPF_DISABLE_ADDR = 5;
constexpr int WM8731_HPF_DISABLE_MASK = 0x1;
constexpr int WM8731_HPF_DISABLE_SHIFT = 0;
// Register 7
constexpr int WM8731_LRSWAP_ADDR = 5;
constexpr int WM8731_LRSWAP_MASK = 0x20;
constexpr int WM8731_LRSWAPE_SHIFT = 5;
// Register 9
constexpr int WM8731_ACTIVATE_ADDR = 9;
constexpr int WM8731_ACTIVATE_MASK = 0x1;
// Reset the internal shadow register array to match
// the reset state of the codec.
void BAAudioControlWM8731::resetInternalReg(void) {
// Set to reset state
regArray[0] = 0x97;
regArray[1] = 0x97;
regArray[2] = 0x79;
regArray[3] = 0x79;
regArray[4] = 0x0a;
regArray[5] = 0x8;
regArray[6] = 0x9f;
regArray[7] = 0xa;
regArray[8] = 0;
regArray[9] = 0;
}
BAAudioControlWM8731::BAAudioControlWM8731()
{
resetInternalReg();
}
BAAudioControlWM8731::~BAAudioControlWM8731()
{
}
// Powerdown and disable the codec
void BAAudioControlWM8731::disable(void)
{
//Serial.println("Disabling codec");
if (m_wireStarted == false) { Wire.begin(); m_wireStarted = true; }
// set OUTPD to '1' (powerdown), which is bit 4
regArray[WM8731_REG_POWERDOWN] |= 0x10;
write(WM8731_REG_POWERDOWN, regArray[WM8731_REG_POWERDOWN]);
delay(100); // wait for power down
// power down the rest of the supplies
write(WM8731_REG_POWERDOWN, 0x9f); // complete codec powerdown
delay(100);
resetCodec();
}
// Powerup and unmute the codec
void BAAudioControlWM8731::enable(void)
{
disable(); // disable first in case it was already powered up
//Serial.println("Enabling codec");
if (m_wireStarted == false) { Wire.begin(); m_wireStarted = true; }
// Sequence from WAN0111.pdf
// Begin configuring the codec
resetCodec();
delay(100); // wait for reset
// Power up all domains except OUTPD and microphone
regArray[WM8731_REG_POWERDOWN] = 0x12;
write(WM8731_REG_POWERDOWN, regArray[WM8731_REG_POWERDOWN]);
delay(100); // wait for codec powerup
setAdcBypass(false); // causes a slight click
setDacSelect(true);
setHPFDisable(true);
setLeftInputGain(0x17); // default input gain
setRightInputGain(0x17);
setLeftInMute(false); // no input mute
setRightInMute(false);
setDacMute(false); // unmute the DAC
// link, but mute the headphone outputs
regArray[WM8731_REG_LHEADOUT] = WM8731_LEFT_HEADPHONE_LINK_MASK;
write(WM8731_REG_LHEADOUT, regArray[WM8731_REG_LHEADOUT]); // volume off
regArray[WM8731_REG_RHEADOUT] = WM8731_RIGHT_HEADPHONE_LINK_MASK;
write(WM8731_REG_RHEADOUT, regArray[WM8731_REG_RHEADOUT]);
/// Configure the audio interface
write(WM8731_REG_INTERFACE, 0x02); // I2S, 16 bit, MCLK slave
regArray[WM8731_REG_INTERFACE] = 0x2;
write(WM8731_REG_SAMPLING, 0x20); // 256*Fs, 44.1 kHz, MCLK/1
regArray[WM8731_REG_SAMPLING] = 0x20;
delay(100); // wait for interface config
// Activate the audio interface
setActivate(true);
delay(100);
write(WM8731_REG_POWERDOWN, 0x02); // power up outputs
regArray[WM8731_REG_POWERDOWN] = 0x02;
delay(500); // wait for output to power up
//Serial.println("Done codec config");
delay(100); // wait for mute ramp
}
// Set the PGA gain on the Left channel
void BAAudioControlWM8731::setLeftInputGain(int val)
{
regArray[WM8731_LEFT_INPUT_GAIN_ADDR] &= ~WM8731_LEFT_INPUT_GAIN_MASK;
regArray[WM8731_LEFT_INPUT_GAIN_ADDR] |=
((val << WM8731_LEFT_INPUT_GAIN_SHIFT) & WM8731_LEFT_INPUT_GAIN_MASK);
write(WM8731_LEFT_INPUT_GAIN_ADDR, regArray[WM8731_LEFT_INPUT_GAIN_ADDR]);
}
// Mute control on the ADC Left channel
void BAAudioControlWM8731::setLeftInMute(bool val)
{
if (val) {
regArray[WM8731_LEFT_INPUT_MUTE_ADDR] |= WM8731_LEFT_INPUT_MUTE_MASK;
} else {
regArray[WM8731_LEFT_INPUT_MUTE_ADDR] &= ~WM8731_LEFT_INPUT_MUTE_MASK;
}
write(WM8731_LEFT_INPUT_MUTE_ADDR, regArray[WM8731_LEFT_INPUT_MUTE_ADDR]);
}
// Link the gain/mute controls for Left and Right channels
void BAAudioControlWM8731::setLinkLeftRightIn(bool val)
{
if (val) {
regArray[WM8731_LINK_LEFT_RIGHT_IN_ADDR] |= WM8731_LINK_LEFT_RIGHT_IN_MASK;
regArray[WM8731_LINK_RIGHT_LEFT_IN_ADDR] |= WM8731_LINK_RIGHT_LEFT_IN_MASK;
} else {
regArray[WM8731_LINK_LEFT_RIGHT_IN_ADDR] &= ~WM8731_LINK_LEFT_RIGHT_IN_MASK;
regArray[WM8731_LINK_RIGHT_LEFT_IN_ADDR] &= ~WM8731_LINK_RIGHT_LEFT_IN_MASK;
}
write(WM8731_LINK_LEFT_RIGHT_IN_ADDR, regArray[WM8731_LINK_LEFT_RIGHT_IN_ADDR]);
write(WM8731_LINK_RIGHT_LEFT_IN_ADDR, regArray[WM8731_LINK_RIGHT_LEFT_IN_ADDR]);
}
// Set the PGA input gain on the Right channel
void BAAudioControlWM8731::setRightInputGain(int val)
{
regArray[WM8731_RIGHT_INPUT_GAIN_ADDR] &= ~WM8731_RIGHT_INPUT_GAIN_MASK;
regArray[WM8731_RIGHT_INPUT_GAIN_ADDR] |=
((val << WM8731_RIGHT_INPUT_GAIN_SHIFT) & WM8731_RIGHT_INPUT_GAIN_MASK);
write(WM8731_RIGHT_INPUT_GAIN_ADDR, regArray[WM8731_RIGHT_INPUT_GAIN_ADDR]);
}
// Mute control on the input ADC right channel
void BAAudioControlWM8731::setRightInMute(bool val)
{
if (val) {
regArray[WM8731_RIGHT_INPUT_MUTE_ADDR] |= WM8731_RIGHT_INPUT_MUTE_MASK;
} else {
regArray[WM8731_RIGHT_INPUT_MUTE_ADDR] &= ~WM8731_RIGHT_INPUT_MUTE_MASK;
}
write(WM8731_RIGHT_INPUT_MUTE_ADDR, regArray[WM8731_RIGHT_INPUT_MUTE_ADDR]);
}
// Left/right swap control
void BAAudioControlWM8731::setLeftRightSwap(bool val)
{
if (val) {
regArray[WM8731_LRSWAP_ADDR] |= WM8731_LRSWAP_MASK;
} else {
regArray[WM8731_LRSWAP_ADDR] &= ~WM8731_LRSWAP_MASK;
}
write(WM8731_LRSWAP_ADDR, regArray[WM8731_LRSWAP_ADDR]);
}
void BAAudioControlWM8731::setHeadphoneVolume(float volume)
{
// the codec volume goes from 0x30 to 0x7F. Anything below 0x30 is mute.
// 0dB gain is 0x79. Total range is 0x50 (80) possible values.
unsigned vol;
constexpr unsigned RANGE = 80.0f;
if (volume < 0.0f) {
vol = 0;
} else if (volume > 1.0f) {
vol = 0x7f;
} else {
vol = 0x2f + static_cast<unsigned>(volume * RANGE);
}
regArray[WM8731_LEFT_HEADPHONE_VOL_ADDR] &= ~WM8731_LEFT_HEADPHONE_VOL_MASK; // clear the volume first
regArray[WM8731_LEFT_HEADPHONE_VOL_ADDR] |=
((vol << WM8731_LEFT_HEADPHONE_VOL_SHIFT) & WM8731_LEFT_HEADPHONE_VOL_MASK);
write(WM8731_LEFT_HEADPHONE_VOL_ADDR, regArray[WM8731_LEFT_HEADPHONE_VOL_ADDR]);
}
// Dac output mute control
void BAAudioControlWM8731::setDacMute(bool val)
{
if (val) {
regArray[WM8731_DAC_MUTE_ADDR] |= WM8731_DAC_MUTE_MASK;
} else {
regArray[WM8731_DAC_MUTE_ADDR] &= ~WM8731_DAC_MUTE_MASK;
}
write(WM8731_DAC_MUTE_ADDR, regArray[WM8731_DAC_MUTE_ADDR]);
}
// Switches the DAC audio in/out of the output path
void BAAudioControlWM8731::setDacSelect(bool val)
{
if (val) {
regArray[WM8731_DAC_SELECT_ADDR] |= WM8731_DAC_SELECT_MASK;
} else {
regArray[WM8731_DAC_SELECT_ADDR] &= ~WM8731_DAC_SELECT_MASK;
}
write(WM8731_DAC_SELECT_ADDR, regArray[WM8731_DAC_SELECT_ADDR]);
}
// Bypass sends the ADC input audio (analog) directly to analog output stage
// bypassing all digital processing
void BAAudioControlWM8731::setAdcBypass(bool val)
{
if (val) {
regArray[WM8731_ADC_BYPASS_ADDR] |= WM8731_ADC_BYPASS_MASK;
} else {
regArray[WM8731_ADC_BYPASS_ADDR] &= ~WM8731_ADC_BYPASS_MASK;
}
write(WM8731_ADC_BYPASS_ADDR, regArray[WM8731_ADC_BYPASS_ADDR]);
}
// Enable/disable the dynamic HPF (recommended, it creates noise)
void BAAudioControlWM8731::setHPFDisable(bool val)
{
if (val) {
regArray[WM8731_HPF_DISABLE_ADDR] |= WM8731_HPF_DISABLE_MASK;
} else {
regArray[WM8731_HPF_DISABLE_ADDR] &= ~WM8731_HPF_DISABLE_MASK;
}
write(WM8731_HPF_DISABLE_ADDR, regArray[WM8731_HPF_DISABLE_ADDR]);
}
// Activate/deactive the I2S audio interface
void BAAudioControlWM8731::setActivate(bool val)
{
if (val) {
write(WM8731_ACTIVATE_ADDR, WM8731_ACTIVATE_MASK);
} else {
write(WM8731_ACTIVATE_ADDR, 0);
}
}
// Trigger the on-chip codec reset
void BAAudioControlWM8731::resetCodec(void)
{
write(WM8731_REG_RESET, 0x0);
resetInternalReg();
}
// Direct write control to the codec
bool BAAudioControlWM8731::writeI2C(unsigned int addr, unsigned int val)
{
return write(addr, val);
}
// Low level write control for the codec via the Teensy I2C interface
bool BAAudioControlWM8731::write(unsigned int reg, unsigned int val)
{
bool done = false;
while (!done) {
Wire.beginTransmission(WM8731_I2C_ADDR);
Wire.write((reg << 1) | ((val >> 8) & 1));
Wire.write(val & 0xFF);
if (byte error = Wire.endTransmission() ) {
(void)error; // supress warning about unused variable
//Serial.println(String("Wire::Error: ") + error + String(" retrying..."));
} else {
done = true;
//Serial.println("Wire::SUCCESS!");
}
}
return true;
}
} /* namespace BALibrary */

@ -1,88 +0,0 @@
/*
* BAGpio.cpp
*
* Created on: November 1, 2017
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Arduino.h"
#include "BAGpio.h"
namespace BALibrary {
BAGpio::BAGpio()
{
// Set all GPIOs to input
pinMode(static_cast<uint8_t>(GPIO::GPIO0), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO1), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO2), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO3), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO4), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO5), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO6), INPUT);
pinMode(static_cast<uint8_t>(GPIO::GPIO7), INPUT);
pinMode(static_cast<uint8_t>(GPIO::TP1), INPUT);
pinMode(static_cast<uint8_t>(GPIO::TP2), INPUT);
// Set the LED ot ouput
pinMode(USR_LED_ID, OUTPUT);
clearLed(); // turn off the LED
}
BAGpio::~BAGpio()
{
}
void BAGpio::setGPIODirection(GPIO gpioId, int direction)
{
pinMode(static_cast<uint8_t>(gpioId), direction);
}
void BAGpio::setGPIO(GPIO gpioId)
{
digitalWrite(static_cast<uint8_t>(gpioId), 0x1);
}
void BAGpio::clearGPIO(GPIO gpioId)
{
digitalWrite(static_cast<uint8_t>(gpioId), 0);
}
int BAGpio::toggleGPIO(GPIO gpioId)
{
int data = digitalRead(static_cast<uint8_t>(gpioId));
digitalWrite(static_cast<uint8_t>(gpioId), ~data);
return ~data;
}
void BAGpio::setLed()
{
digitalWrite(USR_LED_ID, 0x1);
m_ledState = 1;
}
void BAGpio::clearLed()
{
digitalWrite(USR_LED_ID, 0);
m_ledState = 0;
}
int BAGpio::toggleLed()
{
m_ledState = ~m_ledState;
digitalWrite(USR_LED_ID, m_ledState);
return m_ledState;
}
} /* namespace BALibrary */

@ -1,352 +0,0 @@
/*
* BAPhysicalControls.cpp
*
* This file provides a class for handling physical controls such as
* switches, pots and rotary encoders.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BAPhysicalControls.h"
// These calls must be define in order to get vector to work on arduino
namespace std {
void __throw_bad_alloc() {
Serial.println("Unable to allocate memory");
abort();
}
void __throw_length_error( char const*e ) {
Serial.print("Length Error :"); Serial.println(e);
abort();
}
}
namespace BALibrary {
BAPhysicalControls::BAPhysicalControls(unsigned numSwitches, unsigned numPots, unsigned numEncoders, unsigned numOutputs) {
if (numSwitches > 0) {
m_switches.reserve(numSwitches);
}
if (numPots > 0) {
m_pots.reserve(numPots);
}
if (numEncoders > 0) {
m_encoders.reserve(numEncoders);
}
if (numOutputs > 0) {
m_outputs.reserve(numOutputs);
}
}
unsigned BAPhysicalControls::addRotary(uint8_t pin1, uint8_t pin2, bool swapDirection, int divider) {
m_encoders.emplace_back(pin1, pin2, swapDirection, divider);
pinMode(pin1, INPUT);
pinMode(pin2, INPUT);
return m_encoders.size()-1;
}
unsigned BAPhysicalControls::addSwitch(uint8_t pin, unsigned long intervalMilliseconds) {
//m_switches.emplace_back(pin, intervalMilliseconds);'
m_switches.emplace_back();
m_switches.back().attach(pin);
m_switches.back().interval(10);
pinMode(pin, INPUT);
return m_switches.size()-1;
}
unsigned BAPhysicalControls::addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration) {
m_pots.emplace_back(pin, minCalibration, maxCalibration);
pinMode(pin, INPUT);
return m_pots.size()-1;
}
unsigned BAPhysicalControls::addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection) {
m_pots.emplace_back(pin, minCalibration, maxCalibration, swapDirection);
pinMode(pin, INPUT);
return m_pots.size()-1;
}
unsigned BAPhysicalControls::addOutput(uint8_t pin) {
m_outputs.emplace_back(pin);
pinMode(pin, OUTPUT);
return m_outputs.size()-1;
}
void BAPhysicalControls::setOutput(unsigned handle, int val) {
if (handle >= m_outputs.size()) { return; }
m_outputs[handle].set(val);
}
void BAPhysicalControls::setOutput(unsigned handle, bool val) {
if (handle >= m_outputs.size()) { return; }
unsigned value = val ? 1 : 0;
m_outputs[handle].set(value);
}
void BAPhysicalControls::toggleOutput(unsigned handle) {
if (handle >= m_outputs.size()) { return; }
m_outputs[handle].toggle();
}
int BAPhysicalControls::getRotaryAdjustUnit(unsigned handle) {
if (handle >= m_encoders.size()) { return 0; } // handle is greater than number of encoders
int encoderAdjust = m_encoders[handle].getChange();
if (encoderAdjust != 0) {
// clip the adjust to maximum abs value of 1.
int encoderAdjust = (encoderAdjust > 0) ? 1 : -1;
}
return encoderAdjust;
}
bool BAPhysicalControls::checkPotValue(unsigned handle, float &value) {
if (handle >= m_pots.size()) { return false;} // handle is greater than number of pots
return m_pots[handle].getValue(value);
}
int BAPhysicalControls::getPotRawValue(unsigned handle)
{
if (handle >= m_pots.size()) { return false;} // handle is greater than number of pots
return m_pots[handle].getRawValue();
}
bool BAPhysicalControls::setCalibrationValues(unsigned handle, unsigned min, unsigned max, bool swapDirection)
{
if (handle >= m_pots.size()) { return false;} // handle is greater than number of pots
m_pots[handle].setCalibrationValues(min, max, swapDirection);
return true;
}
bool BAPhysicalControls::isSwitchToggled(unsigned handle) {
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
DigitalInput &sw = m_switches[handle];
return sw.hasInputToggled();
}
bool BAPhysicalControls::isSwitchHeld(unsigned handle)
{
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
DigitalInput &sw = m_switches[handle];
return sw.isInputAssert();
}
bool BAPhysicalControls::getSwitchValue(unsigned handle)
{
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
DigitalInput &sw = m_switches[handle];
return sw.read();
}
bool BAPhysicalControls::hasSwitchChanged(unsigned handle, bool &switchValue)
{
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
DigitalInput &sw = m_switches[handle];
return sw.hasInputChanged(switchValue);
}
///////////////////////////
// DigitalInput
///////////////////////////
bool DigitalInput::hasInputToggled() {
update();
if (fell() && (m_isPolarityInverted == false)) {
// switch fell and polarity is not inverted
return true;
} else if (rose() && (m_isPolarityInverted == true)) {
// switch rose and polarity is inveretd
return true;
} else {
return false;
}
}
bool DigitalInput::isInputAssert()
{
update();
// if polarity is inverted, return the opposite state
bool retValue = Bounce::read() ^ m_isPolarityInverted;
return retValue;
}
bool DigitalInput::getPinInputValue()
{
update();
return Bounce::read();
}
bool DigitalInput::hasInputChanged(bool &switchState)
{
update();
if (rose()) {
// return true if not inverted
switchState = m_isPolarityInverted ? false : true;
return true;
} else if (fell()) {
// return false if not inverted
switchState = m_isPolarityInverted ? true : false;
return true;
} else {
// return current value
switchState = Bounce::read() != m_isPolarityInverted;
return false;
}
}
///////////////////////////
// DigitalOutput
///////////////////////////
void DigitalOutput::set(int val) {
m_val = val;
digitalWriteFast(m_pin, m_val);
}
void DigitalOutput::toggle(void) {
m_val = !m_val;
digitalWriteFast(m_pin, m_val);
}
///////////////////////////
// Potentiometer
///////////////////////////
Potentiometer::Potentiometer(uint8_t analogPin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection)
: m_pin(analogPin), m_swapDirection(swapDirection), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration)
{
adjustCalibrationThreshold(m_thresholdFactor); // Calculate the thresholded values
}
void Potentiometer::setFeedbackFitlerValue(float fitlerValue)
{
m_feedbackFitlerValue = fitlerValue;
}
bool Potentiometer::getValue(float &value) {
bool newValue = true;
unsigned val = analogRead(m_pin); // read the raw value
// constrain it within the calibration values, them map it to the desired range.
val = constrain(val, m_minCalibration, m_maxCalibration);
// Use an IIR filter to smooth out the noise in the pot readings
unsigned valFilter = static_cast<unsigned>( (1.0f - m_feedbackFitlerValue)*val + (m_feedbackFitlerValue*m_lastValue));
if (valFilter == m_lastValue) {
newValue = false;
}
m_lastValue = valFilter;
//
if (valFilter < m_minCalibrationThresholded) { value = 0.0f; }
else if (valFilter > m_maxCalibrationThresholded) { value = 1.0f; }
else {
value = static_cast<float>(valFilter - m_minCalibrationThresholded) / static_cast<float>(m_rangeThresholded);
}
if (m_swapDirection) {
value = 1.0f - value;
}
return newValue;
}
int Potentiometer::getRawValue() {
return analogRead(m_pin);
}
// Recalculate thresholded limits based on thresholdFactor
void Potentiometer::adjustCalibrationThreshold(float thresholdFactor)
{
m_thresholdFactor = thresholdFactor;
// the threshold is specificed as a fraction of the min/max range.
unsigned threshold = static_cast<unsigned>((m_maxCalibration - m_minCalibration) * thresholdFactor);
// Update the thresholded values
m_minCalibrationThresholded = m_minCalibration + threshold;
m_maxCalibrationThresholded = m_maxCalibration - threshold;
m_rangeThresholded = m_maxCalibrationThresholded - m_minCalibrationThresholded;
}
void Potentiometer::setCalibrationValues(unsigned min, unsigned max, bool swapDirection)
{
m_minCalibration = min;
m_maxCalibration = max;
m_swapDirection = swapDirection;
adjustCalibrationThreshold(m_thresholdFactor);
}
Potentiometer::Calib Potentiometer::calibrate(uint8_t pin) {
Calib calib;
Serial.print("Calibration pin "); Serial.println(pin);
Serial.println("Move the pot fully counter-clockwise to the minimum setting and press any key then ENTER");
while (true) {
delay(100);
if (Serial.available() > 0) {
calib.min = analogRead(pin);
while (Serial.available()) { Serial.read(); }
break;
}
}
Serial.println("Move the pot fully clockwise to the maximum setting and press any key then ENTER");
while (true) {
delay(100);
if (Serial.available() > 0) {
calib.max = analogRead(pin);
while (Serial.available()) { Serial.read(); }
break;
}
}
if (calib.min > calib.max) {
unsigned tmp = calib.max;
calib.max = calib.min;
calib.min = tmp;
calib.swap = true;
}
Serial.print("The calibration for pin "); Serial.print(pin);
Serial.print(" is min:"); Serial.print(calib.min);
Serial.print(" max:"); Serial.print(calib.max);
Serial.print(" swap: "); Serial.println(calib.swap);
return calib;
}
int RotaryEncoder::getChange() {
int32_t newPosition = read();
int delta = newPosition - m_lastPosition;
m_lastPosition = newPosition;
if (m_swapDirection) { delta = -delta; }
return delta/m_divider;
}
void RotaryEncoder::setDivider(int divider) {
m_divider = divider;
}
} // BALibrary

@ -1,471 +0,0 @@
/*
* BASpiMemory.cpp
*
* Created on: May 22, 2017
* Author: slascos
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Arduino.h"
#include "BASpiMemory.h"
namespace BALibrary {
// MEM0 Settings
constexpr int SPI_CS_MEM0 = 15;
constexpr int SPI_MOSI_MEM0 = 7;
constexpr int SPI_MISO_MEM0 = 8;
constexpr int SPI_SCK_MEM0 = 14;
// MEM1 Settings
constexpr int SPI_CS_MEM1 = 31;
constexpr int SPI_MOSI_MEM1 = 21;
constexpr int SPI_MISO_MEM1 = 5;
constexpr int SPI_SCK_MEM1 = 20;
// SPI Constants
constexpr int SPI_WRITE_MODE_REG = 0x1;
constexpr int SPI_WRITE_CMD = 0x2;
constexpr int SPI_READ_CMD = 0x3;
constexpr int SPI_ADDR_2_MASK = 0xFF0000;
constexpr int SPI_ADDR_2_SHIFT = 16;
constexpr int SPI_ADDR_1_MASK = 0x00FF00;
constexpr int SPI_ADDR_1_SHIFT = 8;
constexpr int SPI_ADDR_0_MASK = 0x0000FF;
constexpr int CMD_ADDRESS_SIZE = 4;
constexpr int MAX_DMA_XFER_SIZE = 0x4000;
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId)
{
m_memDeviceId = memDeviceId;
m_settings = {20000000, MSBFIRST, SPI_MODE0};
}
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz)
{
m_memDeviceId = memDeviceId;
m_settings = {speedHz, MSBFIRST, SPI_MODE0};
}
// Intitialize the correct Arduino SPI interface
void BASpiMemory::begin()
{
switch (m_memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
m_csPin = SPI_CS_MEM0;
m_spi = &SPI;
m_spi->setMOSI(SPI_MOSI_MEM0);
m_spi->setMISO(SPI_MISO_MEM0);
m_spi->setSCK(SPI_SCK_MEM0);
m_spi->begin();
break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
m_csPin = SPI_CS_MEM1;
m_spi = &SPI1;
m_spi->setMOSI(SPI_MOSI_MEM1);
m_spi->setMISO(SPI_MISO_MEM1);
m_spi->setSCK(SPI_SCK_MEM1);
m_spi->begin();
break;
#endif
default :
// unreachable since memDeviceId is an enumerated class
return;
}
pinMode(m_csPin, OUTPUT);
digitalWrite(m_csPin, HIGH);
m_started = true;
}
BASpiMemory::~BASpiMemory() {
}
// Single address write
void BASpiMemory::write(size_t address, uint8_t data)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
m_spi->transfer(data);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
// Single address write
void BASpiMemory::write(size_t address, uint8_t *src, size_t numBytes)
{
uint8_t *dataPtr = src;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
for (size_t i=0; i < numBytes; i++) {
m_spi->transfer(*dataPtr++);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::zero(size_t address, size_t numBytes)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_WRITE_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
for (size_t i=0; i < numBytes; i++) {
m_spi->transfer(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::write16(size_t address, uint16_t data)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
m_spi->transfer16(data);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::write16(size_t address, uint16_t *src, size_t numWords)
{
uint16_t *dataPtr = src;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
for (size_t i=0; i<numWords; i++) {
m_spi->transfer16(*dataPtr++);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
void BASpiMemory::zero16(size_t address, size_t numWords)
{
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
for (size_t i=0; i<numWords; i++) {
m_spi->transfer16(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
Serial.println("DONE!");
}
// single address read
uint8_t BASpiMemory::read(size_t address)
{
int data;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_READ_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
data = m_spi->transfer(0);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
return data;
}
void BASpiMemory::read(size_t address, uint8_t *dest, size_t numBytes)
{
uint8_t *dataPtr = dest;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer(SPI_READ_CMD);
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
m_spi->transfer((address & SPI_ADDR_0_MASK));
for (size_t i=0; i<numBytes; i++) {
*dataPtr++ = m_spi->transfer(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
uint16_t BASpiMemory::read16(size_t address)
{
uint16_t data;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
data = m_spi->transfer16(0);
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
return data;
}
void BASpiMemory::read16(size_t address, uint16_t *dest, size_t numWords)
{
uint16_t *dataPtr = dest;
m_spi->beginTransaction(m_settings);
digitalWrite(m_csPin, LOW);
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) );
m_spi->transfer16(address & 0xFFFF);
for (size_t i=0; i<numWords; i++) {
*dataPtr++ = m_spi->transfer16(0);
}
m_spi->endTransaction();
digitalWrite(m_csPin, HIGH);
}
/////////////////////////////////////////////////////////////////////////////
// BASpiMemoryDMA
/////////////////////////////////////////////////////////////////////////////
BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId)
: BASpiMemory(memDeviceId)
{
int cs;
switch (memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
cs = SPI_CS_MEM0;
m_cs = new ActiveLowChipSelect(cs, m_settings);
break;
#if defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings);
break;
#endif
default :
cs = SPI_CS_MEM0;
}
// add 4 bytes to buffer for SPI CMD and 3 bytes of address
m_txCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_rxCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_txTransfer = new DmaSpi::Transfer[2];
m_rxTransfer = new DmaSpi::Transfer[2];
}
BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz)
: BASpiMemory(memDeviceId, speedHz)
{
int cs;
switch (memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
cs = SPI_CS_MEM0;
m_cs = new ActiveLowChipSelect(cs, m_settings);
break;
#if defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings);
break;
#endif
default :
cs = SPI_CS_MEM0;
}
m_txCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_rxCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE];
m_txTransfer = new DmaSpi::Transfer[2];
m_rxTransfer = new DmaSpi::Transfer[2];
}
BASpiMemoryDMA::~BASpiMemoryDMA()
{
delete m_cs;
if (m_txTransfer) delete [] m_txTransfer;
if (m_rxTransfer) delete [] m_rxTransfer;
if (m_txCommandBuffer) delete [] m_txCommandBuffer;
if (m_rxCommandBuffer) delete [] m_txCommandBuffer;
}
void BASpiMemoryDMA::m_setSpiCmdAddr(int command, size_t address, uint8_t *dest)
{
dest[0] = command;
dest[1] = ((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT);
dest[2] = ((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT);
dest[3] = ((address & SPI_ADDR_0_MASK));
}
void BASpiMemoryDMA::begin(void)
{
switch (m_memDeviceId) {
case SpiDeviceId::SPI_DEVICE0 :
m_csPin = SPI_CS_MEM0;
m_spi = &SPI;
m_spi->setMOSI(SPI_MOSI_MEM0);
m_spi->setMISO(SPI_MISO_MEM0);
m_spi->setSCK(SPI_SCK_MEM0);
m_spi->begin();
//m_spiDma = &DMASPI0;
m_spiDma = new DmaSpiGeneric();
break;
#if defined(__MK66FX1M0__) // DMA on SPI1 is only supported on T3.6
case SpiDeviceId::SPI_DEVICE1 :
m_csPin = SPI_CS_MEM1;
m_spi = &SPI1;
m_spi->setMOSI(SPI_MOSI_MEM1);
m_spi->setMISO(SPI_MISO_MEM1);
m_spi->setSCK(SPI_SCK_MEM1);
m_spi->begin();
m_spiDma = new DmaSpiGeneric(1);
//m_spiDma = &DMASPI1;
break;
#endif
default :
// unreachable since memDeviceId is an enumerated class
return;
}
m_spiDma->begin();
m_spiDma->start();
m_started = true;
}
// SPI must build up a payload that starts the teh CMD/Address first. It will cycle
// through the payloads in a circular buffer and use the transfer objects to check if they
// are done before continuing.
void BASpiMemoryDMA::write(size_t address, uint8_t *src, size_t numBytes)
{
size_t bytesRemaining = numBytes;
uint8_t *srcPtr = src;
size_t nextAddress = address;
while (bytesRemaining > 0) {
m_txXferCount = min(bytesRemaining, static_cast<size_t>(MAX_DMA_XFER_SIZE));
while ( m_txTransfer[1].busy()) {} // wait until not busy
m_setSpiCmdAddr(SPI_WRITE_CMD, nextAddress, m_txCommandBuffer);
m_txTransfer[1] = DmaSpi::Transfer(m_txCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS);
m_spiDma->registerTransfer(m_txTransfer[1]);
while ( m_txTransfer[0].busy()) {} // wait until not busy
m_txTransfer[0] = DmaSpi::Transfer(srcPtr, m_txXferCount, nullptr, 0, m_cs, TransferType::NO_START_CS);
m_spiDma->registerTransfer(m_txTransfer[0]);
bytesRemaining -= m_txXferCount;
srcPtr += m_txXferCount;
nextAddress += m_txXferCount;
}
}
void BASpiMemoryDMA::zero(size_t address, size_t numBytes)
{
size_t bytesRemaining = numBytes;
size_t nextAddress = address;
while (bytesRemaining > 0) {
m_txXferCount = min(bytesRemaining, static_cast<size_t>(MAX_DMA_XFER_SIZE));
while ( m_txTransfer[1].busy()) {} // wait until not busy
m_setSpiCmdAddr(SPI_WRITE_CMD, nextAddress, m_txCommandBuffer);
m_txTransfer[1] = DmaSpi::Transfer(m_txCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS);
m_spiDma->registerTransfer(m_txTransfer[1]);
while ( m_txTransfer[0].busy()) {} // wait until not busy
m_txTransfer[0] = DmaSpi::Transfer(nullptr, m_txXferCount, nullptr, 0, m_cs, TransferType::NO_START_CS);
m_spiDma->registerTransfer(m_txTransfer[0]);
bytesRemaining -= m_txXferCount;
nextAddress += m_txXferCount;
}
}
void BASpiMemoryDMA::write16(size_t address, uint16_t *src, size_t numWords)
{
write(address, reinterpret_cast<uint8_t*>(src), sizeof(uint16_t)*numWords);
}
void BASpiMemoryDMA::zero16(size_t address, size_t numWords)
{
zero(address, sizeof(uint16_t)*numWords);
}
void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes)
{
size_t bytesRemaining = numBytes;
uint8_t *destPtr = dest;
size_t nextAddress = address;
while (bytesRemaining > 0) {
m_setSpiCmdAddr(SPI_READ_CMD, nextAddress, m_rxCommandBuffer);
while ( m_rxTransfer[1].busy()) {}
m_rxTransfer[1] = DmaSpi::Transfer(m_rxCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS);
m_spiDma->registerTransfer(m_rxTransfer[1]);
m_rxXferCount = min(bytesRemaining, static_cast<size_t>(MAX_DMA_XFER_SIZE));
while ( m_rxTransfer[0].busy()) {}
m_rxTransfer[0] = DmaSpi::Transfer(nullptr, m_rxXferCount, destPtr, 0, m_cs, TransferType::NO_START_CS);
m_spiDma->registerTransfer(m_rxTransfer[0]);
bytesRemaining -= m_rxXferCount;
destPtr += m_rxXferCount;
nextAddress += m_rxXferCount;
}
}
void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords)
{
read(address, reinterpret_cast<uint8_t*>(dest), sizeof(uint16_t)*numWords);
}
bool BASpiMemoryDMA::isWriteBusy(void) const
{
return (m_txTransfer[0].busy() or m_txTransfer[1].busy());
}
bool BASpiMemoryDMA::isReadBusy(void) const
{
return (m_rxTransfer[0].busy() or m_rxTransfer[1].busy());
}
} /* namespace BALibrary */

@ -1,13 +0,0 @@
#include "DmaSpi.h"
#if defined(KINETISK)
DmaSpi0 DMASPI0;
#if defined(__MK66FX1M0__)
DmaSpi1 DMASPI1;
//DmaSpi2 DMASPI2;
#endif
#elif defined (KINETISL)
DmaSpi0 DMASPI0;
DmaSpi1 DMASPI1;
#else
#endif // defined
Loading…
Cancel
Save