Added Expansion demo for SOS, DmaSPi is now internal

pull/1/head
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. 38
      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> /*************************************************************************
* This demo uses the BAGuitar library to provide enhanced control of
#define TGA_PRO_REVB * the TGA Pro board.
#define TGA_PRO_EXPAND_REV2 *
* 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" #include "BAGuitar.h"
using namespace midi;
using namespace BAEffects; using namespace BAEffects;
using namespace BALibrary; using namespace BALibrary;
@ -16,7 +27,7 @@ BAAudioControlWM8731 codec;
//#define USE_EXT // uncomment this line to use External MEM0 //#define USE_EXT // uncomment this line to use External MEM0
#ifdef USE_EXT #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 // manager and create an external memory slot to use as the memory
// for our audio delay // for our audio delay
ExternalSramManager externalSram; ExternalSramManager externalSram;
@ -42,30 +53,60 @@ AudioConnection delayOut(analogDelay, 0, cabFilter, 0);
AudioConnection leftOut(cabFilter,0, i2sOut, 0); AudioConnection leftOut(cabFilter,0, i2sOut, 0);
AudioConnection rightOut(cabFilter,0, i2sOut, 1); AudioConnection rightOut(cabFilter,0, i2sOut, 1);
////////////////////////////////////////// //////////////////////////////////////////
// SETUP PHYSICAL CONTROLS // 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; 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() { void setup() {
delay(100); delay(100); // wait a bit for serial to be available
Serial.begin(57600); // Start the serial port Serial.begin(57600); // Start the serial port
delay(100); delay(100);
// Setup the controls // Setup the controls. The return value is the handle to use when checking for control changes, etc.
controls.addSwitch(BA_EXPAND_SW1_PIN); // pushbuttons
controls.addSwitch(BA_EXPAND_SW2_PIN); bypassHandle = controls.addSwitch(BA_EXPAND_SW1_PIN); // will be used for bypass control
controls.addPot(BA_EXPAND_POT1_PIN, filterHandle = controls.addSwitch(BA_EXPAND_SW2_PIN); // will be used for stepping through filters
// pots
// Disable the codec first 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(); codec.disable();
AudioMemory(128); AudioMemory(128);
// Enable the codec // Enable and configure the codec
Serial.println("Enabling codec...\n"); Serial.println("Enabling codec...\n");
codec.enable(); codec.enable();
codec.setHeadphoneVolume(1.0f); // Max headphone volume
// If using external memory request request memory from the manager // If using external memory request request memory from the manager
// for the slot // for the slot
@ -77,8 +118,8 @@ void setup() {
Serial.println("Using INTERNAL memory"); Serial.println("Using INTERNAL memory");
#endif #endif
// Besure to enable the delay. When disabled, audio is is completely blocked // Besure to enable the delay. When disabled, audio is is completely blocked by the effect
// to minimize resources to nearly zero. // to minimize resource usage to nearly to nearly zero.
analogDelay.enable(); analogDelay.enable();
// Set some default values. // Set some default values.
@ -89,18 +130,79 @@ void setup() {
////////////////////////////////// //////////////////////////////////
// AnalogDelay filter selection // // 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::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::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 //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(0, 4500, .7071);
cabFilter.setLowpass(1, 4500, .7071); cabFilter.setLowpass(1, 4500, .7071);
} }
void loop() { 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) { if (loopCount % 524288 == 0) {
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage()); Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage());
Serial.print("% "); Serial.print("% ");
@ -109,5 +211,4 @@ void loop() {
} }
loopCount++; 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. /// @param delaySamples the request delay in audio samples. Must be less than max delay.
void delay(size_t delaySamples); 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. /// Bypass the effect.
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled. /// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
/// Note that audio still passes through when bypass is enabled. /// Note that audio still passes through when bypass is enabled.
void bypass(bool byp) { m_bypass = byp; } 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). /// Set the amount of echo feedback (a.k.a regeneration).
/// @param feedback a floating point number between 0.0 and 1.0. /// @param feedback a floating point number between 0.0 and 1.0.
void feedback(float feedback) { m_feedback = feedback; } void feedback(float feedback) { m_feedback = feedback; }

@ -75,6 +75,9 @@ public:
/// Activate the gate automation. Input gate will open, then close. /// Activate the gate automation. Input gate will open, then close.
void trigger() { m_inputGateAuto.trigger(); } 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. /// Set the output volume. This affect both the wet and dry signals.
/// @details The default is 1.0. /// @details The default is 1.0.
/// @param vol Sets the output volume between -1.0 and +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. ///param val when true, channels are swapped, else normal.
void setLeftRightSwap(bool val); 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. /// Mute/unmute the output DAC, affects both Left and Right output channels.
/// @param when true, output DAC is muted, when false, unmuted. /// @param when true, output DAC is muted, when false, unmuted.
void setDacMute(bool val); void setDacMute(bool val);

@ -4,7 +4,7 @@
* @company Blackaddr Audio * @company Blackaddr Audio
* *
* BAPhysicalControls is a general purpose class for handling an array of * 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 * @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 * it under the terms of the GNU General Public License as published by
@ -28,58 +28,115 @@
namespace BALibrary { 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 { class DigitalOutput {
public: 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) {} 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); void set(int val);
/// Toggle the output value current state
void toggle(void); void toggle(void);
private: private:
uint8_t m_pin; uint8_t m_pin; ///< store the pin associated with this output
int m_val = 0; 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 { class Potentiometer {
public: 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 { struct Calib {
unsigned min; unsigned min; ///< The value from analogRead() when the pot is fully counter-clockwise (normal orientation)
unsigned max; unsigned max; ///< The value from analogRead() when the pot is fully clockwise (normal orientation)
bool swap; 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) Potentiometer() = delete; // delete the default constructor
: m_pin(pin), m_swapDirection(swapDirection), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration) {}
/// 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); bool getValue(float &value);
/// Get the raw int value directly from analogRead()
/// @returns an integer between 0 and 1023.
int getRawValue(); 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); 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: private:
uint8_t m_pin; uint8_t m_pin; ///< store the Arduino pin literal, e.g. A17
bool m_swapDirection; bool m_swapDirection; ///< swap when pot orientation is upside down
unsigned m_minCalibration; unsigned m_minCalibration; ///< stores the min pot value
unsigned m_maxCalibration; unsigned m_maxCalibration; ///< stores the max pot value
unsigned m_lastValue = 0; unsigned m_lastValue = 0; ///< stores previous value
unsigned m_lastValue2 = 0; float m_feedbackFitlerValue = 0.9f; ///< feedback value for POT filter
}; };
constexpr bool ENCODER_SWAP = true; /// Convenience class for rotary (quadrature) encoders. Uses Arduino Encoder under the hood.
constexpr bool ENCODER_NOSWAP = false;
class RotaryEncoder : public Encoder { class RotaryEncoder : public Encoder {
public: 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) {} 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(); 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); void setDivider(int divider);
private: private:
bool m_swapDirection; bool m_swapDirection; ///< specifies if increment/decrement should be swapped
int32_t m_lastPosition = 0; int32_t m_lastPosition = 0; ///< store the last recorded position
int32_t m_divider; int32_t m_divider; ///< divides down the magnitude of change read by the encoder.
}; };
/// Specifies the type of control /// Specifies the type of control
@ -91,9 +148,17 @@ enum class ControlType : unsigned {
UNDEFINED = 255 ///< undefined or uninitialized 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 { class BAPhysicalControls {
public: public:
BAPhysicalControls() = delete; 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(unsigned numSwitches, unsigned numPots, unsigned numEncoders = 0, unsigned numOutputs = 0);
~BAPhysicalControls() {} ~BAPhysicalControls() {}
@ -124,24 +189,55 @@ public:
/// @param swapDirection reverses the which direction is considered pot minimum value /// @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 /// @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); 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); unsigned addOutput(uint8_t pin);
void setOutput(unsigned index, int val);
void toggleOutput(unsigned index); /// Set the output specified by the provided handle
int getRotaryAdjustUnit(unsigned index); /// @param handle the handle that was provided previously by calling addOutput()
bool checkPotValue(unsigned index, float &value); /// @param val the value to set the output. 0 is low, not zero is high.
bool isSwitchToggled(unsigned index); 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: private:
std::vector<Potentiometer> m_pots; std::vector<Potentiometer> m_pots; ///< a vector of all added pots
std::vector<RotaryEncoder> m_encoders; std::vector<RotaryEncoder> m_encoders; ///< a vector of all added encoders
std::vector<Bounce> m_switches; std::vector<Bounce> m_switches; ///< a vector of all added switches
std::vector<DigitalOutput> m_outputs; std::vector<DigitalOutput> m_outputs; ///< a vector of all added outputs
}; };
} // BALibrary } // BALibrary

File diff suppressed because it is too large Load Diff

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

@ -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_ADDR = 1;
constexpr int WM8731_LINK_RIGHT_LEFT_IN_MASK = 0x100; constexpr int WM8731_LINK_RIGHT_LEFT_IN_MASK = 0x100;
constexpr int WM8731_LINK_RIGHT_LEFT_IN_SHIFT = 8; 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 // Register 4
constexpr int WM8731_ADC_BYPASS_ADDR = 4; constexpr int WM8731_ADC_BYPASS_ADDR = 4;
constexpr int WM8731_ADC_BYPASS_MASK = 0x8; constexpr int WM8731_ADC_BYPASS_MASK = 0x8;
@ -157,11 +177,11 @@ void BAAudioControlWM8731::enable(void)
setRightInMute(false); setRightInMute(false);
setDacMute(false); // unmute the DAC setDacMute(false); // unmute the DAC
// mute the headphone outputs // link, but mute the headphone outputs
write(WM8731_REG_LHEADOUT, 0x00); // volume off regArray[WM8731_REG_LHEADOUT] = WM8731_LEFT_HEADPHONE_LINK_MASK;
regArray[WM8731_REG_LHEADOUT] = 0x00; write(WM8731_REG_LHEADOUT, regArray[WM8731_REG_LHEADOUT]); // volume off
write(WM8731_REG_RHEADOUT, 0x00); regArray[WM8731_REG_RHEADOUT] = WM8731_RIGHT_HEADPHONE_LINK_MASK;
regArray[WM8731_REG_RHEADOUT] = 0x00; write(WM8731_REG_RHEADOUT, regArray[WM8731_REG_RHEADOUT]);
/// Configure the audio interface /// Configure the audio interface
write(WM8731_REG_INTERFACE, 0x02); // I2S, 16 bit, MCLK slave 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]); 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 // Dac output mute control
void BAAudioControlWM8731::setDacMute(bool val) void BAAudioControlWM8731::setDacMute(bool val)
{ {

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