Added Expansion demo for SOS, DmaSPi is now internal

master
Steve Lascos 6 years ago
parent 10818bcd78
commit abebab76a8
  1. 141
      examples/Delay/AnalogDelayDemoExpansion/AnalogDelayDemoExpansion.ino
  2. 219
      examples/Delay/SoundOnSoundExpansionDemo/SoundOnSoundExpansionDemo.ino
  3. 19
      examples/Delay/SoundOnSoundExpansionDemo/name.c
  4. 11
      src/AudioEffectAnalogDelay.h
  5. 3
      src/AudioEffectSOS.h
  6. 4
      src/BAAudioControlWM8731.h
  7. 166
      src/BAPhysicalControls.h
  8. 1024
      src/DmaSpi.h
  9. 40
      src/effects/AudioEffectAnalogDelay.cpp
  10. 49
      src/peripherals/BAAudioControlWM8731.cpp
  11. 76
      src/peripherals/BAPhysicalControls.cpp
  12. 13
      src/peripherals/DmaSpi.cpp

@ -1,11 +1,22 @@
#include <MIDI.h>
#define TGA_PRO_REVB
#define TGA_PRO_EXPAND_REV2
/*************************************************************************
* This demo uses the BAGuitar 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/BAGuitar
*
* This demo combines the Blackaddr Audio Expansion board to add physical controls
* to the BAAudioEffectAnalogDelay.
*
* You can control the amount of delay, feedback and mix in realtime, as well as cycle
* through the various analog filters built into the effect.
*
*/
#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 "BAGuitar.h"
using namespace midi;
using namespace BAEffects;
using namespace BALibrary;
@ -16,7 +27,7 @@ BAAudioControlWM8731 codec;
//#define USE_EXT // uncomment this line to use External MEM0
#ifdef USE_EXT
// If using external SPI memory, we will instantiance an SRAM
// 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;
@ -42,30 +53,60 @@ AudioConnection delayOut(analogDelay, 0, cabFilter, 0);
AudioConnection leftOut(cabFilter,0, i2sOut, 0);
AudioConnection rightOut(cabFilter,0, i2sOut, 1);
//////////////////////////////////////////
// SETUP PHYSICAL CONTROLS
// - POT1 (left) will control the amount of delay
// - POT2 (right) will control the amount of feedback
// - 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.
//////////////////////////////////////////
BAPhysicalControls controls(BA_EXPAND_NUM_SW, BA_EXPAND_NUM_POT, 0);
// 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 = MAX_HEADPHONE_VOL; // 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, delayHandle, feedbackHandle, mixHandle, led1Handle, led2Handle; // Handles for the various controls
void setup() {
delay(100);
delay(100); // wait a bit for serial to be available
Serial.begin(57600); // Start the serial port
delay(100);
// Setup the controls
controls.addSwitch(BA_EXPAND_SW1_PIN);
controls.addSwitch(BA_EXPAND_SW2_PIN);
controls.addPot(BA_EXPAND_POT1_PIN,
// Disable the codec first
// 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
delayHandle = controls.addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); // control the amount of delay
feedbackHandle = 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 the codec
// 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
@ -77,8 +118,8 @@ void setup() {
Serial.println("Using INTERNAL memory");
#endif
// Besure to enable the delay. When disabled, audio is is completely blocked
// to minimize resources to nearly zero.
// Besure to enable the delay. When disabled, audio is is completely blocked by the effect
// to minimize resource usage to nearly to nearly zero.
analogDelay.enable();
// Set some default values.
@ -89,18 +130,79 @@ void setup() {
//////////////////////////////////
// AnalogDelay filter selection //
// Uncomment to tryout the 3 different built-in filters.
// These are commented out, in this example we'll use SW2 to cycle through the different filters
//analogDelay.setFilter(AudioEffectAnalogDelay::Filter::DM3); // The default filter. Naturally bright echo (highs stay, lows fade away)
//analogDelay.setFilter(AudioEffectAnalogDelay::Filter::WARM); // A warm filter with a smooth frequency rolloff above 2Khz
//analogDelay.setFilter(AudioEffectAnalogDelay::Filter::DARK); // A very dark filter, with a sharp rolloff above 1Khz
// Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
// 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 = analogDelay.isBypass(); // get the current state
bypass = !bypass; // change it
analogDelay.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 AudioEffectAnalogDelay::Filter
analogDelay.setFilter(static_cast<AudioEffectAnalogDelay::Filter>(filterIndex)); // will cycle through 0 to 2
Serial.println(String("Filter set to ") + filterIndex);
}
// Use POT1 (left) to control the delay setting
if (controls.checkPotValue(delayHandle, potValue)) {
// Pot has changed
Serial.println(String("New DELAY setting: ") + potValue);
analogDelay.delayFractionMax(potValue);
}
// Use POT2 (right) to control the feedback setting
if (controls.checkPotValue(feedbackHandle, potValue)) {
// Pot has changed
Serial.println(String("New FEEDBACK setting: ") + potValue);
analogDelay.feedback(potValue);
}
// Use POT3 (centre) to control the mix setting
if (controls.checkPotValue(mixHandle, potValue)) {
// Pot has changed
Serial.println(String("New MIX setting: ") + potValue);
analogDelay.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("% ");
@ -109,5 +211,4 @@ void loop() {
}
loopCount++;
}

@ -0,0 +1,219 @@
/*************************************************************************
* This demo uses the BAGuitar 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/BAGuitar
*
* THIS DEMO REQUIRES BOTH THE EXTERNAL SRAM AND EXPANSION BOARD ADD-ONS
*
* This demo combines the Blackaddr Audio Expansion board with the BAAudioEffectSOS,
* which provides sound-on-sound. The pushbuttons control the opening of the effect
* gate, as well as clearing the sound being held.
*
* The pots control the feedback, as well as the gate opening and close times.
*
*/
#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 "BAGuitar.h"
using namespace BAEffects;
using namespace BALibrary;
AudioInputI2S i2sIn;
AudioOutputI2S i2sOut;
BAAudioControlWM8731 codec;
// External SRAM is required for this effect due to the very long
// delays required.
ExternalSramManager externalSram;
ExtMemSlot delaySlot; // Declare an external memory slot.
AudioEffectSOS sos(&delaySlot);
// Add some effects for our soloing channel
AudioEffectDelay delayModule; // we'll add a little slapback echo
AudioMixer4 gainModule; // This will be used simply to reduce the gain before the reverb
AudioEffectReverb reverb; // Add a bit of 'verb to our tone
AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
AudioMixer4 mixer;
// Connect the input
AudioConnection inputToSos(i2sIn, 0, sos, 0);
AudioConnection inputToSolo(i2sIn, 0, delayModule, 0);
// Patch cables for the SOLO channel
AudioConnection inputToGain(delayModule, 0, gainModule, 0);
AudioConnection inputToReverb(gainModule, 0, reverb, 0);
// Output Mixer
AudioConnection mixer0input(i2sIn, 0, mixer, 0); // SOLO Dry Channel
AudioConnection mixer1input(reverb, 0, mixer, 1); // SOLO Wet Channel
AudioConnection mixer2input(sos, 0, mixer, 2); // SOS Channel
AudioConnection inputToCab(mixer, 0, cabFilter, 0);
// CODEC Outputs
AudioConnection outputLeft(cabFilter, 0, i2sOut, 0);
AudioConnection outputRight(cabFilter, 0, i2sOut, 1);
//////////////////////////////////////////
// SETUP PHYSICAL CONTROLS
// - POT1 (left) will control the GATE OPEN time
// - POT2 (right) will control the GATE CLOSE TIME
// - POT3 (centre) will control the EFFECT VOLUME
// - SW1 (left) will be used as the GATE TRIGGER
// - LED1 (left) will be illuminated while the GATE is open
// - SW2 (right) will be used as the CLEAR FEEDBACK TRIGGER
// - 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;
constexpr unsigned MAX_HEADPHONE_VOL = 10;
unsigned headphoneVolume = MAX_HEADPHONE_VOL; // control headphone volume from 0 to 10.
constexpr float MAX_GATE_TIME_MS = 4000.0f; // set maximum gate time of 4 seconds.
// BAPhysicalControls returns a handle when you register a new control. We'll uses these handles when working with the controls.
int gateHandle, clearHandle, openHandle, closeHandle, volumeHandle, led1Handle, led2Handle; // Handles for the various controls
void setup() {
delay(100);
delay(100); // wait a bit for serial to be available
Serial.begin(57600); // Start the serial port
delay(100); // wait a bit for serial to be available
// Setup the controls. The return value is the handle to use when checking for control changes, etc.
// pushbuttons
gateHandle = controls.addSwitch(BA_EXPAND_SW1_PIN); // will be used for bypass control
clearHandle = controls.addSwitch(BA_EXPAND_SW2_PIN); // will be used for stepping through filters
// pots
openHandle = controls.addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); // control the amount of delay
closeHandle = controls.addPot(BA_EXPAND_POT2_PIN, potCalibMin, potCalibMax, potSwapDirection);
volumeHandle = 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 the codec
Serial.println("Enabling codec...\n");
codec.enable();
codec.setHeadphoneVolume(1.0f); // Max headphone volume
// We have to request memory be allocated to our slot.
externalSram.requestMemory(&delaySlot, SPI_MEM0_SIZE_BYTES, MemSelect::MEM0, true);
// Configure the LED to indicate the gate status, this is controlled directly by SOS effect, not by
// by BAPhysicalControls
sos.setGateLedGpio(BA_EXPAND_LED1_PIN);
// Besure to enable the delay. When disabled, audio is is completely blocked
// to minimize resources to nearly zero.
sos.enable();
// Set some default values.
// These can be changed by sending MIDI CC messages over the USB using
// the BAMidiTester application.
sos.bypass(false);
sos.gateOpenTime(3000.0f);
sos.gateCloseTime(1000.0f);
sos.feedback(0.9f);
// Setup effects on the SOLO channel
gainModule.gain(0, 0.25); // the reverb unit clips easily if the input is too high
delayModule.delay(0, 50.0f); // 50 ms slapback delay
// 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);
// Setup the Mixer
mixer.gain(0, 0.5f); // SOLO Dry gain
mixer.gain(1, 0.5f); // SOLO Wet gain
mixer.gain(1, 1.0f); // SOS gain
}
void loop() {
float potValue;
// Check if SW1 has been toggled (pushed) and trigger the gate
// LED1 will be directly control by the SOS effect, not by BAPhysicalControls
if (controls.isSwitchToggled(gateHandle)) {
sos.trigger();
Serial.println("GATE OPEN is triggered");
}
// Use SW2 to clear out the SOS delayline
controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle));
if (controls.isSwitchToggled(clearHandle)) {
sos.clear();
Serial.println("GATE CLEAR is triggered");
}
// Use POT1 (left) to control the OPEN GATE time
if (controls.checkPotValue(openHandle, potValue)) {
// Pot has changed
sos.gateOpenTime(potValue * MAX_GATE_TIME_MS);
Serial.println(String("New OPEN GATE setting (ms): ") + (potValue * MAX_GATE_TIME_MS));
}
// Use POT2 (right) to control the feedback setting
if (controls.checkPotValue(closeHandle, potValue)) {
// Pot has changed
sos.gateOpenTime(potValue * MAX_GATE_TIME_MS);
Serial.println(String("New CLOSE GATE setting (ms): ") + (potValue * MAX_GATE_TIME_MS));
}
// Use POT3 (centre) to control the sos effect volume
if (controls.checkPotValue(volumeHandle, potValue)) {
// Pot has changed
Serial.println(String("New SOS VOLUME setting: ") + potValue);
sos.volume(potValue);
}
// Use the 'u' and 'd' keys to adjust headphone 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));
}
}
}
if (loopCount % 524288 == 0) {
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage());
Serial.print("% ");
Serial.print(" sos: "); Serial.print(sos.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
};

@ -86,11 +86,22 @@ public:
/// @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.
/// The value should be between 0.0f and 1.0f
void delayFractionMax(float delayFraction);
/// 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 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; }

@ -75,6 +75,9 @@ public:
/// 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

@ -72,6 +72,10 @@ public:
///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);

@ -4,7 +4,7 @@
* @company Blackaddr Audio
*
* BAPhysicalControls is a general purpose class for handling an array of
* pots and switches.
* 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
@ -28,58 +28,115 @@
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;
int m_val = 0;
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 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;
unsigned max;
bool swap;
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(uint8_t pin, unsigned minCalibration, unsigned maxCalibration)
: m_pin(pin), m_swapDirection(false), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration) {}
Potentiometer(uint8_t pin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection)
: m_pin(pin), m_swapDirection(swapDirection), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration) {}
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)
: m_pin(analogPin), m_swapDirection(swapDirection), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration) {}
/// 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);
static Calib calibrate(uint8_t pin);
/// 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);
/// 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;
bool m_swapDirection;
unsigned m_minCalibration;
unsigned m_maxCalibration;
unsigned m_lastValue = 0;
unsigned m_lastValue2 = 0;
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
};
constexpr bool ENCODER_SWAP = true;
constexpr bool ENCODER_NOSWAP = false;
/// 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;
int32_t m_lastPosition = 0;
int32_t m_divider;
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
@ -91,9 +148,17 @@ enum class ControlType : unsigned {
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() {}
@ -124,24 +189,55 @@ public:
/// @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);
void setOutput(unsigned index, int val);
void toggleOutput(unsigned index);
int getRotaryAdjustUnit(unsigned index);
bool checkPotValue(unsigned index, float &value);
bool isSwitchToggled(unsigned index);
/// 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);
/// 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);
/// 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.
int getSwitchValue(unsigned handle);
private:
std::vector<Potentiometer> m_pots;
std::vector<RotaryEncoder> m_encoders;
std::vector<Bounce> m_switches;
std::vector<DigitalOutput> m_outputs;
std::vector<Potentiometer> m_pots; ///< a vector of all added pots
std::vector<RotaryEncoder> m_encoders; ///< a vector of all added encoders
std::vector<Bounce> m_switches; ///< a vector of all added switches
std::vector<DigitalOutput> m_outputs; ///< a vector of all added outputs
};
} // BALibrary

File diff suppressed because it is too large Load Diff

@ -177,11 +177,11 @@ void AudioEffectAnalogDelay::delay(float milliseconds)
if (!m_externalMemory) {
// internal memory
QueuePosition queuePosition = calcQueuePosition(milliseconds);
Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
//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);
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); }
@ -200,17 +200,43 @@ void AudioEffectAnalogDelay::delay(size_t delaySamples)
if (!m_externalMemory) {
// internal memory
QueuePosition queuePosition = calcQueuePosition(delaySamples);
Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
//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);
//Serial.println(String("CONFIG: delay:") + delaySamples);
ExtMemSlot *slot = m_memory->getSlot();
if (!slot->isEnabled()) {
slot->enable();
}
}
m_delaySamples= delaySamples;
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)

@ -60,6 +60,26 @@ 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;
@ -157,11 +177,11 @@ void BAAudioControlWM8731::enable(void)
setRightInMute(false);
setDacMute(false); // unmute the DAC
// mute the headphone outputs
write(WM8731_REG_LHEADOUT, 0x00); // volume off
regArray[WM8731_REG_LHEADOUT] = 0x00;
write(WM8731_REG_RHEADOUT, 0x00);
regArray[WM8731_REG_RHEADOUT] = 0x00;
// 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
@ -251,6 +271,25 @@ void BAAudioControlWM8731::setLeftRightSwap(bool val)
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)
{

@ -78,21 +78,21 @@ unsigned BAPhysicalControls::addOutput(uint8_t pin) {
return m_outputs.size()-1;
}
void BAPhysicalControls::setOutput(unsigned index, int val) {
if (index >= m_outputs.size()) { return; }
m_outputs[index].set(val);
void BAPhysicalControls::setOutput(unsigned handle, int val) {
if (handle >= m_outputs.size()) { return; }
m_outputs[handle].set(val);
}
void BAPhysicalControls::toggleOutput(unsigned index) {
if (index >= m_outputs.size()) { return; }
m_outputs[index].toggle();
void BAPhysicalControls::toggleOutput(unsigned handle) {
if (handle >= m_outputs.size()) { return; }
m_outputs[handle].toggle();
}
int BAPhysicalControls::getRotaryAdjustUnit(unsigned index) {
if (index >= m_encoders.size()) { return 0; } // index is greater than number of encoders
int BAPhysicalControls::getRotaryAdjustUnit(unsigned handle) {
if (handle >= m_encoders.size()) { return 0; } // handle is greater than number of encoders
int encoderAdjust = m_encoders[index].getChange();
int encoderAdjust = m_encoders[handle].getChange();
if (encoderAdjust != 0) {
// clip the adjust to maximum abs value of 1.
int encoderAdjust = (encoderAdjust > 0) ? 1 : -1;
@ -101,14 +101,14 @@ int BAPhysicalControls::getRotaryAdjustUnit(unsigned index) {
return encoderAdjust;
}
bool BAPhysicalControls::checkPotValue(unsigned index, float &value) {
if (index >= m_pots.size()) { return false;} // index is greater than number of pots
return m_pots[index].getValue(value);
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);
}
bool BAPhysicalControls::isSwitchToggled(unsigned index) {
if (index >= m_switches.size()) { return 0; } // index is greater than number of switches
Bounce &sw = m_switches[index];
bool BAPhysicalControls::isSwitchToggled(unsigned handle) {
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
Bounce &sw = m_switches[handle];
if (sw.update() && sw.fallingEdge()) {
return true;
@ -117,6 +117,26 @@ bool BAPhysicalControls::isSwitchToggled(unsigned index) {
}
}
bool BAPhysicalControls::isSwitchHeld(unsigned handle)
{
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
Bounce &sw = m_switches[handle];
if (sw.read()) {
return true;
} else {
return false;
}
}
int BAPhysicalControls::getSwitchValue(unsigned handle)
{
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
Bounce &sw = m_switches[handle];
return sw.read();
}
///////////////////////////
void DigitalOutput::set(int val) {
@ -129,25 +149,31 @@ void DigitalOutput::toggle(void) {
digitalWriteFast(m_pin, m_val);
}
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
// Return false if the value is the same as the last sample, or 2 samples ago. The second check is to
// prevent oscillating between two values.
if ((val == m_lastValue) || (val == m_lastValue2)) {
return false;
}
// Use an IIR filter to smooth out the noise in the pot readings
unsigned valFilter = ( (1.0f - m_feedbackFitlerValue)*val + m_feedbackFitlerValue*m_lastValue);
// Otherwise update the last value
m_lastValue = val;
// constrain it within the calibration values, them map it to the desired range.
val = constrain(val, m_minCalibration, m_maxCalibration);
value = static_cast<float>(val - m_minCalibration) / static_cast<float>(m_maxCalibration);
valFilter = constrain(valFilter, m_minCalibration, m_maxCalibration);
if (valFilter == m_lastValue) {
newValue = false;
}
m_lastValue = valFilter;
value = static_cast<float>(valFilter - m_minCalibration) / static_cast<float>(m_maxCalibration);
if (m_swapDirection) {
value = 1.0f - value;
}
return true;
return newValue;
}
int Potentiometer::getRawValue() {

@ -0,0 +1,13 @@
#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