forked from wirtz/BALibrary
Compare commits
1 Commits
feature/Au
...
master
Author | SHA1 | Date |
---|---|---|
Blackaddr Audio | d38be82ea6 | 6 years ago |
@ -1,227 +0,0 @@ |
||||
/*************************************************************************
|
||||
* 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++; |
||||
|
||||
} |
@ -1,19 +0,0 @@ |
||||
// 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 |
||||
}; |
@ -0,0 +1,149 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,126 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,135 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,97 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,76 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,121 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,313 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,221 @@ |
||||
/**************************************************************************//**
|
||||
* @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
@ -0,0 +1,212 @@ |
||||
/**************************************************************************//**
|
||||
* @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 */ |
@ -0,0 +1,235 @@ |
||||
/*
|
||||
* 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); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,122 @@ |
||||
/*
|
||||
* 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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
@ -1,329 +0,0 @@ |
||||
/*
|
||||
* 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
@ -0,0 +1,324 @@ |
||||
/*
|
||||
* 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
@ -0,0 +1,345 @@ |
||||
/* |
||||
* 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
@ -0,0 +1,294 @@ |
||||
/*
|
||||
* 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 */ |
@ -0,0 +1,304 @@ |
||||
/*
|
||||
* 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
|
||||
|
||||
|
||||
|
@ -0,0 +1,163 @@ |
||||
/*
|
||||
* 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
|
@ -0,0 +1,383 @@ |
||||
/*
|
||||
* 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 */ |
@ -0,0 +1,88 @@ |
||||
/*
|
||||
* 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 */ |
@ -0,0 +1,352 @@ |
||||
/*
|
||||
* 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
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,471 @@ |
||||
/*
|
||||
* 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 */ |
@ -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…
Reference in new issue