forked from wirtz/BALibrary
Compare commits
5 Commits
master
...
feature/Au
Author | SHA1 | Date |
---|---|---|
Holger Wirtz | 1500f353cb | 5 years ago |
Steve Lascos | 7bf2ed6906 | 6 years ago |
Steve Lascos | 9b09021ef0 | 6 years ago |
Steve Lascos | 362d0928d2 | 6 years ago |
Steve Lascos | d954f0aced | 6 years ago |
@ -0,0 +1,227 @@ |
|||||||
|
/*************************************************************************
|
||||||
|
* This demo uses the BALibrary library to provide enhanced control of |
||||||
|
* the TGA Pro board. |
||||||
|
*
|
||||||
|
* The latest copy of the BA Guitar library can be obtained from |
||||||
|
* https://github.com/Blackaddr/BALibrary
|
||||||
|
*
|
||||||
|
* This example demonstrates teh BAAudioEffectsAnalogChorus effect. It can |
||||||
|
* be controlled using the Blackaddr Audio "Expansion Control Board". |
||||||
|
*
|
||||||
|
* POT1 (left) controls the modulation rate |
||||||
|
* POT2 (right) controls the modulation depth |
||||||
|
* POT3 (center) controls the wet/dry mix |
||||||
|
* SW1 will enable/bypass the audio effect. LED1 will be on when effect is enabled. |
||||||
|
* SW2 will cycle through the 3 pre-programmed analog filters. LED2 will be on when SW2 is pressed. |
||||||
|
*
|
||||||
|
* Using the Serial Montitor, send 'u' and 'd' characters to increase or decrease |
||||||
|
* the headphone volume between values of 0 and 9. |
||||||
|
*/ |
||||||
|
#define TGA_PRO_REVB // Set which hardware revision of the TGA Pro we're using
|
||||||
|
#define TGA_PRO_EXPAND_REV2 // pull in the pin definitions for the Blackaddr Audio Expansion Board.
|
||||||
|
|
||||||
|
#include "BALibrary.h" |
||||||
|
#include "BAEffects.h" |
||||||
|
|
||||||
|
using namespace BAEffects; |
||||||
|
using namespace BALibrary; |
||||||
|
|
||||||
|
AudioInputI2S i2sIn; |
||||||
|
AudioOutputI2S i2sOut; |
||||||
|
BAAudioControlWM8731 codec; |
||||||
|
|
||||||
|
//#define USE_EXT // uncomment this line to use External MEM0
|
||||||
|
|
||||||
|
#ifdef USE_EXT |
||||||
|
// If using external SPI memory, we will instantiate a SRAM
|
||||||
|
// manager and create an external memory slot to use as the memory
|
||||||
|
// for our audio delay
|
||||||
|
ExternalSramManager externalSram; |
||||||
|
ExtMemSlot delaySlot; // Declare an external memory slot.
|
||||||
|
|
||||||
|
// Instantiate the AudioEffectAnalogChorus to use external memory by
|
||||||
|
/// passing it the delay slot.
|
||||||
|
AudioEffectAnalogChorus analogChorus(&delaySlot); |
||||||
|
#else |
||||||
|
AudioEffectAnalogChorus analogChorus; // default chorus delays
|
||||||
|
#endif |
||||||
|
|
||||||
|
AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
|
||||||
|
|
||||||
|
// Simply connect the input to the delay, and the output
|
||||||
|
// to both i2s channels
|
||||||
|
AudioConnection input(i2sIn,0, analogChorus,0); |
||||||
|
AudioConnection chorusOut(analogChorus, 0, cabFilter, 0); |
||||||
|
AudioConnection leftOut(cabFilter,0, i2sOut, 0); |
||||||
|
AudioConnection rightOut(cabFilter,0, i2sOut, 1); |
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// SETUP PHYSICAL CONTROLS
|
||||||
|
// - POT1 (left) will control the rate
|
||||||
|
// - POT2 (right) will control the depth
|
||||||
|
// - POT3 (centre) will control the wet/dry mix.
|
||||||
|
// - SW1 (left) will be used as a bypass control
|
||||||
|
// - LED1 (left) will be illuminated when the effect is ON (not bypass)
|
||||||
|
// - SW2 (right) will be used to cycle through the three built in analog filter styles available.
|
||||||
|
// - LED2 (right) will illuminate when pressing SW2.
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// To get the calibration values for your particular board, first run the
|
||||||
|
// BAExpansionCalibrate.ino example and
|
||||||
|
constexpr int potCalibMin = 1; |
||||||
|
constexpr int potCalibMax = 1018; |
||||||
|
constexpr bool potSwapDirection = true; |
||||||
|
|
||||||
|
// Create a control object using the number of switches, pots, encoders and outputs on the
|
||||||
|
// Blackaddr Audio Expansion Board.
|
||||||
|
BAPhysicalControls controls(BA_EXPAND_NUM_SW, BA_EXPAND_NUM_POT, BA_EXPAND_NUM_ENC, BA_EXPAND_NUM_LED); |
||||||
|
|
||||||
|
int loopCount = 0; |
||||||
|
unsigned filterIndex = 0; // variable for storing which analog filter we're currently using.
|
||||||
|
constexpr unsigned MAX_HEADPHONE_VOL = 10; |
||||||
|
unsigned headphoneVolume = 8; // control headphone volume from 0 to 10.
|
||||||
|
|
||||||
|
// BAPhysicalControls returns a handle when you register a new control. We'll uses these handles when working with the controls.
|
||||||
|
int bypassHandle, filterHandle, rateHandle, depthHandle, mixHandle, led1Handle, led2Handle; // Handles for the various controls
|
||||||
|
|
||||||
|
void setup() { |
||||||
|
delay(100); // wait a bit for serial to be available
|
||||||
|
Serial.begin(57600); // Start the serial port
|
||||||
|
delay(500); |
||||||
|
|
||||||
|
// Setup the controls. The return value is the handle to use when checking for control changes, etc.
|
||||||
|
// pushbuttons
|
||||||
|
bypassHandle = controls.addSwitch(BA_EXPAND_SW1_PIN); // will be used for bypass control
|
||||||
|
filterHandle = controls.addSwitch(BA_EXPAND_SW2_PIN); // will be used for stepping through filters
|
||||||
|
// pots
|
||||||
|
rateHandle = controls.addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); // control the amount of delay
|
||||||
|
depthHandle = controls.addPot(BA_EXPAND_POT2_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||||
|
mixHandle = controls.addPot(BA_EXPAND_POT3_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||||
|
// leds
|
||||||
|
led1Handle = controls.addOutput(BA_EXPAND_LED1_PIN); |
||||||
|
led2Handle = controls.addOutput(BA_EXPAND_LED2_PIN); // will illuminate when pressing SW2
|
||||||
|
|
||||||
|
// Disable the audio codec first
|
||||||
|
codec.disable(); |
||||||
|
AudioMemory(128); |
||||||
|
|
||||||
|
// Enable and configure the codec
|
||||||
|
Serial.println("Enabling codec...\n"); |
||||||
|
codec.enable(); |
||||||
|
codec.setHeadphoneVolume(1.0f); // Max headphone volume
|
||||||
|
|
||||||
|
// If using external memory request request memory from the manager
|
||||||
|
// for the slot
|
||||||
|
#ifdef USE_EXT |
||||||
|
Serial.println("Using EXTERNAL memory"); |
||||||
|
// We have to request memory be allocated to our slot.
|
||||||
|
externalSram.requestMemory(&delaySlot, 40.0f, MemSelect::MEM0, true); // 40 ms is enough to handle the full range of the chorus delay
|
||||||
|
#else |
||||||
|
Serial.println("Using INTERNAL memory"); |
||||||
|
#endif |
||||||
|
|
||||||
|
// Besure to enable the delay. When disabled, audio is is completely blocked by the effect
|
||||||
|
// to minimize resource usage to nearly to nearly zero.
|
||||||
|
analogChorus.enable();
|
||||||
|
|
||||||
|
// Set some default values.
|
||||||
|
// These can be changed using the controls on the Blackaddr Audio Expansion Board
|
||||||
|
analogChorus.bypass(false); |
||||||
|
analogChorus.rate(0.5f); |
||||||
|
analogChorus.mix(0.5f); |
||||||
|
analogChorus.depth(1.0f); |
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// AnalogChorus filter selection //
|
||||||
|
// These are commented out, in this example we'll use SW2 to cycle through the different filters
|
||||||
|
//analogChorus.setFilter(AudioEffectAnalogChorus::Filter::CE2); // The default filter. Naturally bright echo (highs stay, lows fade away)
|
||||||
|
//analogChorus.setFilter(AudioEffectAnalogChorus::Filter::WARM); // A warm filter with a smooth frequency rolloff above 2Khz
|
||||||
|
//analogChorus.setFilter(AudioEffectAnalogChorus::Filter::DARK); // A very dark filter, with a sharp rolloff above 1Khz
|
||||||
|
analogChorus.setFilter(AudioEffectAnalogChorus::Filter::WARM); // A very dark filter, with a sharp rolloff above 1Khz
|
||||||
|
|
||||||
|
// Guitar cabinet: Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
|
||||||
|
cabFilter.setLowpass(0, 4500, .7071); |
||||||
|
cabFilter.setLowpass(1, 4500, .7071); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
float potValue; |
||||||
|
|
||||||
|
// Check if SW1 has been toggled (pushed)
|
||||||
|
if (controls.isSwitchToggled(bypassHandle)) { |
||||||
|
bool bypass = analogChorus.isBypass(); // get the current state
|
||||||
|
bypass = !bypass; // change it
|
||||||
|
analogChorus.bypass(bypass); // set the new state
|
||||||
|
controls.setOutput(led1Handle, !bypass); // Set the LED when NOT bypassed
|
||||||
|
Serial.println(String("BYPASS is ") + bypass); |
||||||
|
} |
||||||
|
|
||||||
|
// Use SW2 to cycle through the filters
|
||||||
|
// controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle));
|
||||||
|
// if (controls.isSwitchToggled(filterHandle)) {
|
||||||
|
// filterIndex = (filterIndex + 1) % 3; // update and potentionall roll the counter 0, 1, 2, 0, 1, 2, ...
|
||||||
|
// // cast the index between 0 to 2 to the enum class AudioEffectAnalogChorus::Filter
|
||||||
|
// analogChorus.setFilter(static_cast<AudioEffectAnalogChorus::Filter>(filterIndex)); // will cycle through 0 to 2
|
||||||
|
// Serial.println(String("Filter set to ") + filterIndex);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Use SW2 to cycle through the waveforms
|
||||||
|
controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle)); |
||||||
|
if (controls.isSwitchToggled(filterHandle)) { |
||||||
|
filterIndex = (filterIndex + 1) % 4; // update and potentionall roll the counter 0, 1, 2, 0, 1, 2, ...
|
||||||
|
// cast the index between 0 to 3 to the enum class AudioEffectAnalogChorus::Filter
|
||||||
|
analogChorus.setWaveform(static_cast<Waveform>(filterIndex)); // will cycle through 0 to 2
|
||||||
|
Serial.println(String("Waveform set to ") + filterIndex); |
||||||
|
} |
||||||
|
|
||||||
|
// Use POT1 (left) to control the rate setting
|
||||||
|
if ( (controls.checkPotValue(rateHandle, potValue)) || (loopCount == 0) ) { |
||||||
|
// Pot has changed
|
||||||
|
Serial.println(String("New RATE setting: ") + potValue); |
||||||
|
analogChorus.rate(potValue); |
||||||
|
} |
||||||
|
|
||||||
|
// Use POT2 (right) to control the depth setting
|
||||||
|
if ( (controls.checkPotValue(depthHandle, potValue)) || (loopCount == 0) ) { |
||||||
|
// Pot has changed
|
||||||
|
Serial.println(String("New DEPTH setting: ") + potValue); |
||||||
|
analogChorus.depth(potValue); |
||||||
|
} |
||||||
|
|
||||||
|
// Use POT3 (centre) to control the mix setting
|
||||||
|
if ( (controls.checkPotValue(mixHandle, potValue)) || (loopCount == 0) ) { |
||||||
|
// Pot has changed
|
||||||
|
Serial.println(String("New MIX setting: ") + potValue); |
||||||
|
analogChorus.mix(potValue); |
||||||
|
} |
||||||
|
|
||||||
|
// Use the 'u' and 'd' keys to adjust volume across ten levels.
|
||||||
|
if (Serial) { |
||||||
|
if (Serial.available() > 0) { |
||||||
|
while (Serial.available()) { |
||||||
|
char key = Serial.read(); |
||||||
|
if (key == 'u') {
|
||||||
|
headphoneVolume = (headphoneVolume + 1) % MAX_HEADPHONE_VOL; |
||||||
|
Serial.println(String("Increasing HEADPHONE volume to ") + headphoneVolume); |
||||||
|
} |
||||||
|
else if (key == 'd') {
|
||||||
|
headphoneVolume = (headphoneVolume - 1) % MAX_HEADPHONE_VOL; |
||||||
|
Serial.println(String("Decreasing HEADPHONE volume to ") + headphoneVolume); |
||||||
|
} |
||||||
|
codec.setHeadphoneVolume(static_cast<float>(headphoneVolume) / static_cast<float>(MAX_HEADPHONE_VOL)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Use the loopCounter to roughly measure human timescales. Every few seconds, print the CPU usage
|
||||||
|
// to the serial port. About 500,000 loops!
|
||||||
|
if (loopCount % 524288 == 0) { |
||||||
|
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage()); |
||||||
|
Serial.print("% "); |
||||||
|
Serial.print(" AnalogChorus: "); Serial.print(analogChorus.processorUsage()); |
||||||
|
Serial.println("%"); |
||||||
|
} |
||||||
|
loopCount++; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
// To give your project a unique name, this code must be
|
||||||
|
// placed into a .c file (its own tab). It can not be in
|
||||||
|
// a .cpp file or your main sketch (the .ino file).
|
||||||
|
|
||||||
|
#include "usb_names.h" |
||||||
|
|
||||||
|
// Edit these lines to create your own name. The length must
|
||||||
|
// match the number of characters in your custom name.
|
||||||
|
|
||||||
|
#define MIDI_NAME {'B','l','a','c','k','a','d','d','r',' ','A','u','d','i','o',' ','T','G','A',' ','P','r','o'} |
||||||
|
#define MIDI_NAME_LEN 23 |
||||||
|
|
||||||
|
// Do not change this part. This exact format is required by USB.
|
||||||
|
|
||||||
|
struct usb_string_descriptor_struct usb_string_product_name = { |
||||||
|
2 + MIDI_NAME_LEN * 2, |
||||||
|
3, |
||||||
|
MIDI_NAME |
||||||
|
}; |
@ -1,149 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* AudioEffectSOS is a class f |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BAEFFECTS_BAAUDIOEFFECTSOS_H |
|
||||||
#define __BAEFFECTS_BAAUDIOEFFECTSOS_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "LibBasicFunctions.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* AudioEffectSOS |
|
||||||
*****************************************************************************/ |
|
||||||
class AudioEffectSOS : public AudioStream { |
|
||||||
public: |
|
||||||
|
|
||||||
///< List of AudioEffectAnalogDelay MIDI controllable parameters
|
|
||||||
enum { |
|
||||||
BYPASS = 0, ///< controls effect bypass
|
|
||||||
GATE_TRIGGER, ///< begins the gate sequence
|
|
||||||
GATE_OPEN_TIME, ///< controls how long it takes to open the gate
|
|
||||||
//GATE_HOLD_TIME, ///< controls how long the gate stays open at unity
|
|
||||||
GATE_CLOSE_TIME, ///< controls how long it takes to close the gate (release)
|
|
||||||
CLEAR_FEEDBACK_TRIGGER, ///< begins the sequence to clear out the looping feedback
|
|
||||||
FEEDBACK, ///< controls the amount of feedback, more gives longer SOS sustain
|
|
||||||
VOLUME, ///< controls the output volume level
|
|
||||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
|
||||||
}; |
|
||||||
|
|
||||||
// *** CONSTRUCTORS ***
|
|
||||||
AudioEffectSOS() = delete; |
|
||||||
AudioEffectSOS(float maxDelayMs); |
|
||||||
AudioEffectSOS(size_t numSamples); |
|
||||||
|
|
||||||
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
|
|
||||||
/// delay will be determined by the amount of memory in the slot.
|
|
||||||
/// @param slot A pointer to the ExtMemSlot to use for the delay.
|
|
||||||
AudioEffectSOS(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
|
|
||||||
|
|
||||||
virtual ~AudioEffectSOS(); ///< Destructor
|
|
||||||
|
|
||||||
void setGateLedGpio(int pinId); |
|
||||||
|
|
||||||
// *** PARAMETERS ***
|
|
||||||
void gateOpenTime(float milliseconds); |
|
||||||
|
|
||||||
void gateCloseTime(float milliseconds); |
|
||||||
|
|
||||||
void feedback(float feedback) { m_feedback = feedback; } |
|
||||||
|
|
||||||
/// Bypass the effect.
|
|
||||||
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
|
|
||||||
/// Note that audio still passes through when bypass is enabled.
|
|
||||||
void bypass(bool byp) { m_bypass = byp; } |
|
||||||
|
|
||||||
/// Activate the gate automation. Input gate will open, then close.
|
|
||||||
void trigger() { m_inputGateAuto.trigger(); } |
|
||||||
|
|
||||||
/// Activate the delay clearing automation. Input signal will mute, gate will open, then close.
|
|
||||||
void clear() { m_clearFeedbackAuto.trigger(); } |
|
||||||
|
|
||||||
/// Set the output volume. This affect both the wet and dry signals.
|
|
||||||
/// @details The default is 1.0.
|
|
||||||
/// @param vol Sets the output volume between -1.0 and +1.0
|
|
||||||
void volume(float vol) {m_volume = vol; } |
|
||||||
|
|
||||||
// ** ENABLE / DISABLE **
|
|
||||||
|
|
||||||
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
|
|
||||||
void enable(); |
|
||||||
|
|
||||||
/// Disables audio process. When disabled, CPU load is nearly zero.
|
|
||||||
void disable() { m_enable = false; } |
|
||||||
|
|
||||||
// ** MIDI **
|
|
||||||
|
|
||||||
/// Sets whether MIDI OMNI channel is processig on or off. When on,
|
|
||||||
/// all midi channels are used for matching CCs.
|
|
||||||
/// @param isOmni when true, all channels are processed, when false, channel
|
|
||||||
/// must match configured value.
|
|
||||||
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } |
|
||||||
|
|
||||||
/// Configure an effect parameter to be controlled by a MIDI CC
|
|
||||||
/// number on a particular channel.
|
|
||||||
/// @param parameter one of the parameter names in the class enum
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param midiChannel the effect will only response to the CC on this channel
|
|
||||||
/// when OMNI mode is off.
|
|
||||||
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); |
|
||||||
|
|
||||||
/// process a MIDI Continous-Controller (CC) message
|
|
||||||
/// @param channel the MIDI channel from 0 to 15)
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param value the CC value from 0 to 127
|
|
||||||
void processMidi(int channel, int midiCC, int value); |
|
||||||
|
|
||||||
virtual void update(void); ///< update automatically called by the Teesny Audio Library
|
|
||||||
|
|
||||||
private: |
|
||||||
audio_block_t *m_inputQueueArray[1]; |
|
||||||
bool m_isOmni = false; |
|
||||||
bool m_bypass = true; |
|
||||||
bool m_enable = false; |
|
||||||
BALibrary::AudioDelay *m_memory = nullptr; |
|
||||||
bool m_externalMemory = true; |
|
||||||
audio_block_t *m_previousBlock = nullptr; |
|
||||||
audio_block_t *m_blockToRelease = nullptr; |
|
||||||
size_t m_maxDelaySamples = 0; |
|
||||||
int m_gateLedPinId = -1; |
|
||||||
|
|
||||||
// Controls
|
|
||||||
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
|
|
||||||
size_t m_delaySamples = 0; |
|
||||||
float m_openTimeMs = 0.0f; |
|
||||||
float m_closeTimeMs = 0.0f; |
|
||||||
float m_feedback = 0.0f; |
|
||||||
float m_volume = 1.0f; |
|
||||||
|
|
||||||
// Automated Controls
|
|
||||||
BALibrary::ParameterAutomationSequence<float> m_inputGateAuto = BALibrary::ParameterAutomationSequence<float>(3); |
|
||||||
BALibrary::ParameterAutomationSequence<float> m_clearFeedbackAuto = BALibrary::ParameterAutomationSequence<float>(3); |
|
||||||
|
|
||||||
// Private functions
|
|
||||||
void m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal); |
|
||||||
void m_postProcessing(audio_block_t *out, audio_block_t *input); |
|
||||||
}; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_BAAUDIOEFFECTSOS_H */ |
|
@ -1,126 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* Tremolo is a classic volume modulate effect. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY__BAEFFECTS_AUDIOEFFECTDELAYEXTERNAL_H; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BAEFFECTS_AUDIOEFFECTTREMOLO_H |
|
||||||
#define __BAEFFECTS_AUDIOEFFECTTREMOLO_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "LibBasicFunctions.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* AudioEffectTremolo |
|
||||||
*****************************************************************************/ |
|
||||||
class AudioEffectTremolo : public AudioStream { |
|
||||||
public: |
|
||||||
|
|
||||||
///< List of AudioEffectTremolo MIDI controllable parameters
|
|
||||||
enum { |
|
||||||
BYPASS = 0, ///< controls effect bypass
|
|
||||||
RATE, ///< controls the rate of the modulation
|
|
||||||
DEPTH, ///< controls the depth of the modulation
|
|
||||||
WAVEFORM, ///< select the modulation waveform
|
|
||||||
VOLUME, ///< controls the output volume level
|
|
||||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
|
||||||
}; |
|
||||||
|
|
||||||
// *** CONSTRUCTORS ***
|
|
||||||
AudioEffectTremolo(); |
|
||||||
|
|
||||||
virtual ~AudioEffectTremolo(); ///< Destructor
|
|
||||||
|
|
||||||
// *** PARAMETERS ***
|
|
||||||
void rate(float rateValue); |
|
||||||
|
|
||||||
void depth(float depthValue) { m_depth = depthValue; } |
|
||||||
|
|
||||||
void setWaveform(BALibrary::Waveform waveform); |
|
||||||
|
|
||||||
|
|
||||||
/// Bypass the effect.
|
|
||||||
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
|
|
||||||
/// Note that audio still passes through when bypass is enabled.
|
|
||||||
void bypass(bool byp) { m_bypass = byp; } |
|
||||||
|
|
||||||
/// Get if the effect is bypassed
|
|
||||||
/// @returns true if bypassed, false if not bypassed
|
|
||||||
bool isBypass() { return m_bypass; } |
|
||||||
|
|
||||||
/// Toggle the bypass effect
|
|
||||||
void toggleBypass() { m_bypass = !m_bypass; } |
|
||||||
|
|
||||||
/// Set the output volume. This affect both the wet and dry signals.
|
|
||||||
/// @details The default is 1.0.
|
|
||||||
/// @param vol Sets the output volume between -1.0 and +1.0
|
|
||||||
void volume(float vol) {m_volume = vol; } |
|
||||||
|
|
||||||
// ** ENABLE / DISABLE **
|
|
||||||
|
|
||||||
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
|
|
||||||
void enable() { m_enable = true; } |
|
||||||
|
|
||||||
/// Disables audio process. When disabled, CPU load is nearly zero.
|
|
||||||
void disable() { m_enable = false; } |
|
||||||
|
|
||||||
// ** MIDI **
|
|
||||||
|
|
||||||
/// Sets whether MIDI OMNI channel is processig on or off. When on,
|
|
||||||
/// all midi channels are used for matching CCs.
|
|
||||||
/// @param isOmni when true, all channels are processed, when false, channel
|
|
||||||
/// must match configured value.
|
|
||||||
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } |
|
||||||
|
|
||||||
/// Configure an effect parameter to be controlled by a MIDI CC
|
|
||||||
/// number on a particular channel.
|
|
||||||
/// @param parameter one of the parameter names in the class enum
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param midiChannel the effect will only response to the CC on this channel
|
|
||||||
/// when OMNI mode is off.
|
|
||||||
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); |
|
||||||
|
|
||||||
/// process a MIDI Continous-Controller (CC) message
|
|
||||||
/// @param channel the MIDI channel from 0 to 15)
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param value the CC value from 0 to 127
|
|
||||||
void processMidi(int channel, int midiCC, int value); |
|
||||||
|
|
||||||
|
|
||||||
virtual void update(void); ///< update automatically called by the Teesny Audio Library
|
|
||||||
|
|
||||||
private: |
|
||||||
audio_block_t *m_inputQueueArray[1]; |
|
||||||
BALibrary::LowFrequencyOscillatorVector<float> m_osc; |
|
||||||
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
|
|
||||||
bool m_isOmni = false; |
|
||||||
bool m_bypass = true; |
|
||||||
bool m_enable = false; |
|
||||||
|
|
||||||
float m_rate = 0.0f; |
|
||||||
float m_depth = 0.0f; |
|
||||||
BALibrary::Waveform m_waveform = BALibrary::Waveform::SINE; |
|
||||||
float m_volume = 1.0f; |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_AUDIOEFFECTTREMOLO_H */ |
|
@ -1,135 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BAAudioContromWM8731 is a class for controlling a WM8731 Codec via I2C. |
|
||||||
* @details The Codec power ups in a silent state, with non-optimal |
|
||||||
* configuration. This class will enable codec and set some initial gain levels. |
|
||||||
* The user can than use the API to changing settings for their specific needs. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BALIBRARY_BAAUDIOCONTROLWM8731_H |
|
||||||
#define __BALIBRARY_BAAUDIOCONTROLWM8731_H |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
constexpr int WM8731_NUM_REGS = 10; // Number of registers in the internal shadow array
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* BAAudioControlWM8731 provides an API for configuring the internal registers |
|
||||||
* of the WM8731 codec. |
|
||||||
* @details Not every single command is provided, please ask if you need something |
|
||||||
* added that is not already present. You can also directly write any register |
|
||||||
* you wish with the writeI2C() method. |
|
||||||
*****************************************************************************/ |
|
||||||
class BAAudioControlWM8731 { |
|
||||||
public: |
|
||||||
BAAudioControlWM8731(); |
|
||||||
virtual ~BAAudioControlWM8731(); |
|
||||||
|
|
||||||
/// Mute and power down the codec.
|
|
||||||
void disable(void); |
|
||||||
|
|
||||||
/// First disable, then cleanly power up and unmute the codec.
|
|
||||||
void enable(void); |
|
||||||
|
|
||||||
/// Set the input gain of the codec's PGA for the left (TRS Tip) channel.
|
|
||||||
/// @param val an interger value from 31 = +12dB . . 1.5dB steps down to 0 = -34.5dB
|
|
||||||
void setLeftInputGain(int val); |
|
||||||
|
|
||||||
/// Set the input gain of the codec's PGA for the right (TRS Ring) channel.
|
|
||||||
/// @param val an interger value from 31 = +12dB . . 1.5dB steps down to 0 = -34.5dB
|
|
||||||
void setRightInputGain(int val); |
|
||||||
|
|
||||||
/// Mute/unmute the Left (TRS Tip) channel at the ADC input.
|
|
||||||
/// @param val when true, channel is muted, when false, channel is unmuted
|
|
||||||
void setLeftInMute(bool val); |
|
||||||
|
|
||||||
/// Mute/unmute the Right (TRS Ring) channel at the ADC input.
|
|
||||||
/// @param val when true, channel is muted, when false, channel is unmuted
|
|
||||||
void setRightInMute(bool val); |
|
||||||
|
|
||||||
/// Links the Left/Right channel contols for mute and input gain.
|
|
||||||
/// @details when true, changing either the left or right gain/mute controls will
|
|
||||||
/// affect both channels.
|
|
||||||
/// @param val when true, channels are linked, when false, they are controlled separately
|
|
||||||
void setLinkLeftRightIn(bool val); |
|
||||||
/// Swaps the left and right channels in the codec.
|
|
||||||
///param val when true, channels are swapped, else normal.
|
|
||||||
void setLeftRightSwap(bool val); |
|
||||||
|
|
||||||
/// Set the volume for the codec's built-in headphone amp
|
|
||||||
/// @param volume the input volume level from 0.0f to 1.0f;
|
|
||||||
void setHeadphoneVolume(float volume); |
|
||||||
|
|
||||||
/// Mute/unmute the output DAC, affects both Left and Right output channels.
|
|
||||||
/// @param when true, output DAC is muted, when false, unmuted.
|
|
||||||
void setDacMute(bool val); |
|
||||||
|
|
||||||
/// Control when the DAC is feeding the output analog circuitry.
|
|
||||||
/// @param val when true, the DAC output is connected to the analog output. When
|
|
||||||
/// false, the DAC is disconnected.
|
|
||||||
void setDacSelect(bool val); |
|
||||||
|
|
||||||
/// ADC Bypass control.
|
|
||||||
/// @details This permits the analog audio from the Codec's PGA to bypass the ADC
|
|
||||||
/// and go directly to the analog output of the codec, bypassing the digital domain
|
|
||||||
/// entirely.
|
|
||||||
/// @param val when true, analog ADC input is fed directly to codec analog otuput.
|
|
||||||
void setAdcBypass(bool val); |
|
||||||
|
|
||||||
/// Digital High Pass Filter disable. RECOMMENDED ALWAYS TRUE!
|
|
||||||
/// @details this controls a HPF in the codec that attempts to remove the lowest
|
|
||||||
/// frequency (i.e. < 10 Hz) to improve headroom by dynamically measuring DC level.
|
|
||||||
/// In most cases, it introduces noise components by modulating the filter. This
|
|
||||||
/// is not suitable for applications where the audio is used for output, but might
|
|
||||||
/// be useful for applications like tuning, pitch detection, whre the noise components
|
|
||||||
/// can be tolerated.
|
|
||||||
/// @param val when true (recommended) the dynamic HPF is disabled, otherwise enabled.
|
|
||||||
void setHPFDisable(bool val); |
|
||||||
|
|
||||||
/// Activates the I2S interface on the codec.
|
|
||||||
/// @param val when true, I2S interface is active, when false it is disabled.
|
|
||||||
void setActivate(bool val); |
|
||||||
|
|
||||||
/// Soft-reset the codec.
|
|
||||||
/// @details This will cause the codec to reset its internal registers to default values.
|
|
||||||
void resetCodec(void); |
|
||||||
|
|
||||||
/// Write a custom command to the codec via I2C control interface.
|
|
||||||
/// @details See WM8731 datasheet for register map details.
|
|
||||||
/// @param addr The register address you wish to write to, range 0 to 15.
|
|
||||||
/// @param val the 9-bit data value you wish to write at the address specifed.
|
|
||||||
bool writeI2C(unsigned int addr, unsigned int val); |
|
||||||
|
|
||||||
protected: |
|
||||||
// A shadow array for the registers on the codec since the interface is write-only.
|
|
||||||
int regArray[WM8731_NUM_REGS]; |
|
||||||
|
|
||||||
private: |
|
||||||
// low-level write command
|
|
||||||
bool write(unsigned int reg, unsigned int val); |
|
||||||
// resets the internal shadow register array
|
|
||||||
void resetInternalReg(void); |
|
||||||
|
|
||||||
bool m_wireStarted = false; |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
||||||
|
|
||||||
#endif /* __BALIBRARY_BAAUDIOCONTROLWM8731_H */ |
|
@ -1,97 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BAAudioEffectDelayExternal is a class for using an external SPI SRAM chip |
|
||||||
* as an audio delay line. The external memory can be shared among several |
|
||||||
* different instances of BAAudioEffectDelayExternal by specifying the max delay |
|
||||||
* length during construction. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H |
|
||||||
#define __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "AudioStream.h" |
|
||||||
|
|
||||||
#include "BAHardware.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* BAAudioEffectDelayExternal can use external SPI RAM for delay rather than |
|
||||||
* the limited RAM available on the Teensy itself. |
|
||||||
*****************************************************************************/ |
|
||||||
class BAAudioEffectDelayExternal : public AudioStream |
|
||||||
{ |
|
||||||
public: |
|
||||||
|
|
||||||
/// Default constructor will use all memory available in MEM0
|
|
||||||
BAAudioEffectDelayExternal(); |
|
||||||
|
|
||||||
/// Specifiy which external memory to use
|
|
||||||
/// @param type specify which memory to use
|
|
||||||
BAAudioEffectDelayExternal(BALibrary::MemSelect type); |
|
||||||
|
|
||||||
/// Specify external memory, and how much of the memory to use
|
|
||||||
/// @param type specify which memory to use
|
|
||||||
/// @param delayLengthMs maximum delay length in milliseconds
|
|
||||||
BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs); |
|
||||||
virtual ~BAAudioEffectDelayExternal(); |
|
||||||
|
|
||||||
/// set the actual amount of delay on a given delay tap
|
|
||||||
/// @param channel specify channel tap 1-8
|
|
||||||
/// @param milliseconds specify how much delay for the specified tap
|
|
||||||
void delay(uint8_t channel, float milliseconds); |
|
||||||
|
|
||||||
/// Disable a delay channel tap
|
|
||||||
/// @param channel specify channel tap 1-8
|
|
||||||
void disable(uint8_t channel); |
|
||||||
|
|
||||||
virtual void update(void); |
|
||||||
|
|
||||||
static unsigned m_usingSPICount[2]; // internal use for all instances
|
|
||||||
|
|
||||||
private: |
|
||||||
void initialize(BALibrary::MemSelect mem, unsigned delayLength = 1e6); |
|
||||||
void read(uint32_t address, uint32_t count, int16_t *data); |
|
||||||
void write(uint32_t address, uint32_t count, const int16_t *data); |
|
||||||
void zero(uint32_t address, uint32_t count); |
|
||||||
unsigned m_memoryStart; // the first address in the memory we're using
|
|
||||||
unsigned m_memoryLength; // the amount of memory we're using
|
|
||||||
unsigned m_headOffset; // head index (incoming) data into external memory
|
|
||||||
unsigned m_channelDelayLength[8]; // # of sample delay for each channel (128 = no delay)
|
|
||||||
unsigned m_activeMask; // which output channels are active
|
|
||||||
static unsigned m_allocated[2]; |
|
||||||
audio_block_t *m_inputQueueArray[1]; |
|
||||||
|
|
||||||
BALibrary::MemSelect m_mem; |
|
||||||
SPIClass *m_spi = nullptr; |
|
||||||
int m_spiChannel = 0; |
|
||||||
int m_misoPin = 0; |
|
||||||
int m_mosiPin = 0; |
|
||||||
int m_sckPin = 0; |
|
||||||
int m_csPin = 0; |
|
||||||
|
|
||||||
void m_startUsingSPI(int spiBus); |
|
||||||
void m_stopUsingSPI(int spiBus); |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
} /* namespace BAEffects */ |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H */ |
|
@ -1,76 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BAGPio is convenience class for accessing the the various GPIOs available |
|
||||||
* on the Teensy Guitar Audio series boards. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BALIBRARY_BAGPIO_H |
|
||||||
#define __BALIBRARY_BAGPIO_H |
|
||||||
|
|
||||||
#include "BAHardware.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* BAGpio provides a convince class to easily control the direction and state |
|
||||||
* of the GPIO pins available on the TGA headers. |
|
||||||
* @details you can always control this directly with Arduino commands like |
|
||||||
* digitalWrite(), etc. |
|
||||||
*****************************************************************************/ |
|
||||||
class BAGpio { |
|
||||||
public: |
|
||||||
/// Construct a GPIO object for controlling the various GPIO and user pins
|
|
||||||
BAGpio(); |
|
||||||
virtual ~BAGpio(); |
|
||||||
|
|
||||||
/// Set the direction of the specified GPIO pin.
|
|
||||||
/// @param gpioId Specify a GPIO pin such as GPIO::GPIO0
|
|
||||||
/// @param specify direction as INPUT or OUTPUT which are Arduino constants
|
|
||||||
void setGPIODirection(GPIO gpioId, int direction); |
|
||||||
|
|
||||||
/// Set the state of the specified GPIO to high
|
|
||||||
/// @param gpioId gpioId Specify a GPIO pin such as GPIO::GPIO0
|
|
||||||
void setGPIO(GPIO gpioId); |
|
||||||
|
|
||||||
/// Clear the state of the specified GPIO pin.
|
|
||||||
/// @param gpioId gpioId Specify a GPIO pin such as GPIO::GPIO0
|
|
||||||
void clearGPIO(GPIO gpioId); |
|
||||||
|
|
||||||
/// Toggle the state of the specified GPIO pin. Only works if configured as output.
|
|
||||||
/// @param gpioId gpioId Specify a GPIO pin such as GPIO::GPIO0
|
|
||||||
/// @returns the new state of the pin
|
|
||||||
int toggleGPIO(GPIO gpioId); |
|
||||||
|
|
||||||
/// Turn on the user LED
|
|
||||||
void setLed(); |
|
||||||
|
|
||||||
/// Turn off the user LED
|
|
||||||
void clearLed(); |
|
||||||
|
|
||||||
/// Toggle the stage of the user LED
|
|
||||||
/// @returns the new stage of the user LED.
|
|
||||||
int toggleLed(); |
|
||||||
|
|
||||||
private: |
|
||||||
uint8_t m_ledState; |
|
||||||
}; |
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
||||||
|
|
||||||
#endif /* __BALIBRARY_BAGPIO_H */ |
|
@ -1,121 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* This file contains specific definitions for each Blackaddr Audio hardware |
|
||||||
* board. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BALIBRARY_BAHARDWARE_H |
|
||||||
#define __BALIBRARY_BAHARDWARE_H |
|
||||||
|
|
||||||
#include <Arduino.h> |
|
||||||
#include <cstdint> |
|
||||||
#include <cstddef> |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* BALibrary is a namespace/Library for Guitar processing from Blackaddr Audio. |
|
||||||
*****************************************************************************/ |
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
// uncomment the line that corresponds to your hardware
|
|
||||||
//#define TGA_PRO_REVA
|
|
||||||
#if (!defined(TGA_PRO_REVA) && !defined(TGA_PRO_REVB)) |
|
||||||
#define TGA_PRO_REVA |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TGA_PRO_REVA) || defined(TGA_PRO_REVB) |
|
||||||
|
|
||||||
constexpr uint8_t USR_LED_ID = 16; ///< Teensy IO number for the user LED.
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* GPIOs and Testpoints are accessed via enumerated class constants. |
|
||||||
*****************************************************************************/ |
|
||||||
enum class GPIO : uint8_t { |
|
||||||
GPIO0 = 2, |
|
||||||
GPIO1 = 3, |
|
||||||
GPIO2 = 4, |
|
||||||
GPIO3 = 6, |
|
||||||
|
|
||||||
GPIO4 = 12, |
|
||||||
GPIO5 = 32, |
|
||||||
GPIO6 = 27, |
|
||||||
GPIO7 = 28, |
|
||||||
|
|
||||||
TP1 = 34, |
|
||||||
TP2 = 33 |
|
||||||
}; |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* Optionally installed SPI RAM |
|
||||||
*****************************************************************************/ |
|
||||||
constexpr unsigned NUM_MEM_SLOTS = 2; |
|
||||||
enum MemSelect : unsigned { |
|
||||||
MEM0 = 0, ///< SPI RAM MEM0
|
|
||||||
MEM1 = 1 ///< SPI RAM MEM1
|
|
||||||
}; |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* Set the maximum address (byte-based) in the external SPI memories |
|
||||||
*****************************************************************************/ |
|
||||||
constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 }; |
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* General Purpose SPI Interfaces |
|
||||||
*****************************************************************************/ |
|
||||||
enum class SpiDeviceId : unsigned { |
|
||||||
SPI_DEVICE0 = 0, ///< Arduino SPI device
|
|
||||||
SPI_DEVICE1 = 1 ///< Arduino SPI1 device
|
|
||||||
}; |
|
||||||
constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip
|
|
||||||
constexpr size_t SPI_MEM0_SIZE_BYTES = 131072; |
|
||||||
constexpr size_t SPI_MEM0_MAX_AUDIO_SAMPLES = SPI_MEM0_SIZE_BYTES/sizeof(int16_t); |
|
||||||
|
|
||||||
constexpr size_t SPI_MEM1_SIZE_BYTES = 131072; |
|
||||||
constexpr size_t SPI_MEM1_MAX_AUDIO_SAMPLES = SPI_MEM1_SIZE_BYTES/sizeof(int16_t); |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#else |
|
||||||
|
|
||||||
#error "No hardware declared" |
|
||||||
|
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined (TGA_PRO_EXPAND_REV2) |
|
||||||
/**************************************************************************//**
|
|
||||||
* Blackaddr Audio Expansion Board |
|
||||||
*****************************************************************************/ |
|
||||||
constexpr unsigned BA_EXPAND_NUM_POT = 3; |
|
||||||
constexpr unsigned BA_EXPAND_NUM_SW = 2; |
|
||||||
constexpr unsigned BA_EXPAND_NUM_LED = 2; |
|
||||||
constexpr unsigned BA_EXPAND_NUM_ENC = 0; |
|
||||||
|
|
||||||
constexpr uint8_t BA_EXPAND_POT1_PIN = A16; // 35_A16_PWM
|
|
||||||
constexpr uint8_t BA_EXPAND_POT2_PIN = A17; // 36_A17_PWM
|
|
||||||
constexpr uint8_t BA_EXPAND_POT3_PIN = A18; // 37_SCL1_A18_PWM
|
|
||||||
constexpr uint8_t BA_EXPAND_SW1_PIN = 2; // 2)PWM
|
|
||||||
constexpr uint8_t BA_EXPAND_SW2_PIN = 3; // 3_SCL2_PWM
|
|
||||||
constexpr uint8_t BA_EXPAND_LED1_PIN = 4; // 4_SDA2_PWM
|
|
||||||
constexpr uint8_t BA_EXPAND_LED2_PIN = 6; // 6_PWM
|
|
||||||
#endif |
|
||||||
|
|
||||||
} // namespace BALibrary
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* __BALIBRARY_BAHARDWARE_H */ |
|
@ -1,313 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BAPhysicalControls is a general purpose class for handling an array of |
|
||||||
* pots, switches, rotary encoders and outputs (for LEDs or relays). |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
#ifndef __BAPHYSICALCONTROLS_H |
|
||||||
#define __BAPHYSICALCONTROLS_H |
|
||||||
|
|
||||||
#include <vector> |
|
||||||
#include <Encoder.h> |
|
||||||
#include <Bounce2.h> |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
constexpr bool SWAP_DIRECTION = true; ///< Use when specifying direction should be swapped
|
|
||||||
constexpr bool NOSWAP_DIRECTION = false; ///< Use when specifying direction should not be swapped
|
|
||||||
|
|
||||||
/// Convenience class for handling a digital output with regard to setting and toggling stage
|
|
||||||
class DigitalOutput { |
|
||||||
public: |
|
||||||
DigitalOutput() = delete; // delete the default constructor
|
|
||||||
|
|
||||||
/// Construct an object to control the specified pin
|
|
||||||
/// @param pin the "Logical Arduino" pin number (not the physical pin number
|
|
||||||
DigitalOutput(uint8_t pin) : m_pin(pin) {} |
|
||||||
|
|
||||||
/// Set the output value
|
|
||||||
/// @param val when zero output is low, otherwise output his high
|
|
||||||
void set(int val); |
|
||||||
|
|
||||||
/// Toggle the output value current state
|
|
||||||
void toggle(void); |
|
||||||
|
|
||||||
private: |
|
||||||
uint8_t m_pin; ///< store the pin associated with this output
|
|
||||||
int m_val = 0; ///< store the value to support toggling
|
|
||||||
}; |
|
||||||
|
|
||||||
/// Convenience class for handling digital inputs. This wraps Bounce with the abilty
|
|
||||||
/// to invert the polarity of the pin.
|
|
||||||
class DigitalInput : public Bounce { |
|
||||||
public: |
|
||||||
/// Create an input where digital low is return true. Most switches ground when pressed.
|
|
||||||
DigitalInput() : m_isPolarityInverted(true) {} |
|
||||||
|
|
||||||
/// Create an input and specify if input polarity is inverted.
|
|
||||||
/// @param isPolarityInverted when false, a high voltage on the pin returns true, 0V returns false.
|
|
||||||
DigitalInput(bool isPolarityInverted) : m_isPolarityInverted(isPolarityInverted) {} |
|
||||||
|
|
||||||
/// Read the state of the pin according to the polarity
|
|
||||||
/// @returns true when the input should be interpreted as the switch is closed, else false.
|
|
||||||
bool read() { return Bounce::read() != m_isPolarityInverted; } // logical XOR to conditionally invert polarity
|
|
||||||
|
|
||||||
/// Set whether the input pin polarity
|
|
||||||
/// @param polarity when true, a low voltage on the pin is considered true by read(), else false.
|
|
||||||
void setPolarityInverted(bool polarity) { m_isPolarityInverted = polarity; } |
|
||||||
|
|
||||||
/// Check if input has toggled from low to high to low by looking for falling edges
|
|
||||||
/// @returns true if the switch has toggled
|
|
||||||
bool hasInputToggled(); |
|
||||||
|
|
||||||
/// Check if the input is asserted
|
|
||||||
/// @returns true if the switch is held
|
|
||||||
bool isInputAssert(); |
|
||||||
|
|
||||||
/// Get the raw input value (ignores polarity inversion)
|
|
||||||
/// @returns returns true is physical pin is high, else false
|
|
||||||
bool getPinInputValue(); |
|
||||||
|
|
||||||
/// Store the current switch state and return true if it has changed.
|
|
||||||
/// @param switchState variable to store switch state in
|
|
||||||
/// @returns true when switch stage has changed since last check
|
|
||||||
bool hasInputChanged(bool &switchState); |
|
||||||
|
|
||||||
private: |
|
||||||
bool m_isPolarityInverted; |
|
||||||
bool m_isPushed; |
|
||||||
}; |
|
||||||
|
|
||||||
/// Convenience class for handling an analog pot as a control. When calibrated,
|
|
||||||
/// returns a float between 0.0 and 1.0.
|
|
||||||
class Potentiometer { |
|
||||||
public: |
|
||||||
/// Calibration data for a pot includes it's min and max value, as well as whether
|
|
||||||
/// direction should be swapped. Swapping depends on the physical orientation of the
|
|
||||||
/// pot.
|
|
||||||
struct Calib { |
|
||||||
unsigned min; ///< The value from analogRead() when the pot is fully counter-clockwise (normal orientation)
|
|
||||||
unsigned max; ///< The value from analogRead() when the pot is fully clockwise (normal orientation)
|
|
||||||
bool swap; ///< when orientation is such that fully clockwise would give max reading, swap changes it to the min
|
|
||||||
}; |
|
||||||
|
|
||||||
Potentiometer() = delete; // delete the default constructor
|
|
||||||
|
|
||||||
/// Construction requires the Arduino analog pin number, as well as calibration values.
|
|
||||||
/// @param analogPin The analog Arduino pin literal. This the number on the Teensy pinout preceeeded by an A in the local pin name. E.g. "A17".
|
|
||||||
/// @param minCalibration See Potentiometer::calibrate()
|
|
||||||
/// @param maxCalibration See Potentiometer::calibrate()
|
|
||||||
/// @param swapDirection Optional param. See Potentiometer::calibrate()
|
|
||||||
Potentiometer(uint8_t analogPin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection = false); |
|
||||||
|
|
||||||
/// Get new value from the pot.
|
|
||||||
/// @param value reference to a float, the new value will be written here. Value is between 0.0 and 1.0f.
|
|
||||||
/// @returns true if the value has changed, false if it has not
|
|
||||||
bool getValue(float &value); |
|
||||||
|
|
||||||
/// Get the raw int value directly from analogRead()
|
|
||||||
/// @returns an integer between 0 and 1023.
|
|
||||||
int getRawValue(); |
|
||||||
|
|
||||||
/// Adjust the calibrate threshold. This is a factor that shrinks the calibration range slightly to
|
|
||||||
/// ensure the full range from 0.0f to 1.0f can be obtained.
|
|
||||||
/// @details temperature change can slightly alter the calibration values. This factor causes the value
|
|
||||||
/// to hit min or max just before the end of the pots travel to compensate.
|
|
||||||
/// @param thresholdFactor typical value is 0.01f to 0.05f
|
|
||||||
void adjustCalibrationThreshold(float thresholdFactor); |
|
||||||
|
|
||||||
/// Set the amount of feedback in the IIR filter used to smooth the pot readings
|
|
||||||
/// @details actual filter reponse deptnds on the rate you call getValue()
|
|
||||||
/// @param filterValue typical values are 0.80f to 0.95f
|
|
||||||
void setFeedbackFitlerValue(float fitlerValue); |
|
||||||
|
|
||||||
void setCalibrationValues(unsigned min, unsigned max, bool swapDirection); |
|
||||||
|
|
||||||
/// Call this static function before creating the object to obtain calibration data. The sequence
|
|
||||||
/// involves prompts over the Serial port.
|
|
||||||
/// @details E.g. call Potentiometer::calibrate(PIN). See BAExpansionCalibrate.ino in the library examples.
|
|
||||||
/// @param analogPin the Arduino analog pin number connected to the pot you wish to calibraate.
|
|
||||||
/// @returns populated Potentiometer::Calib structure
|
|
||||||
static Calib calibrate(uint8_t analogPin); |
|
||||||
|
|
||||||
private: |
|
||||||
uint8_t m_pin; ///< store the Arduino pin literal, e.g. A17
|
|
||||||
bool m_swapDirection; ///< swap when pot orientation is upside down
|
|
||||||
unsigned m_minCalibration; ///< stores the min pot value
|
|
||||||
unsigned m_maxCalibration; ///< stores the max pot value
|
|
||||||
unsigned m_lastValue = 0; ///< stores previous value
|
|
||||||
float m_feedbackFitlerValue = 0.9f; ///< feedback value for POT filter
|
|
||||||
float m_thresholdFactor = 0.05f; ///< threshold factor causes values pot to saturate faster at the limits, default is 5%
|
|
||||||
unsigned m_minCalibrationThresholded; ///< stores the min pot value after thresholding
|
|
||||||
unsigned m_maxCalibrationThresholded; ///< stores the max pot value after thresholding
|
|
||||||
unsigned m_rangeThresholded; ///< stores the range of max - min after thresholding
|
|
||||||
}; |
|
||||||
|
|
||||||
/// Convenience class for rotary (quadrature) encoders. Uses Arduino Encoder under the hood.
|
|
||||||
class RotaryEncoder : public Encoder { |
|
||||||
public: |
|
||||||
RotaryEncoder() = delete; // delete default constructor
|
|
||||||
|
|
||||||
/// Constructor an encoder with the specified pins. Optionally swap direction and divide down the number of encoder ticks
|
|
||||||
/// @param pin1 the Arduino logical pin number for the 'A' on the encoder.
|
|
||||||
/// @param pin2 the Arduino logical pin number for the 'B' on the encoder.
|
|
||||||
/// @param swapDirection (OPTIONAL) set to true or false to obtain clockwise increments the counter-clockwise decrements
|
|
||||||
/// @param divider (OPTIONAL) controls the sensitivity of the divider. Use powers of 2. E.g. 1, 2, 4, 8, etc.
|
|
||||||
RotaryEncoder(uint8_t pin1, uint8_t pin2, bool swapDirection = false, int divider = 1) : Encoder(pin1,pin2), m_swapDirection(swapDirection), m_divider(divider) {} |
|
||||||
|
|
||||||
/// Get the delta (as a positive or negative number) since last call
|
|
||||||
/// @returns an integer representing the net change since last call
|
|
||||||
int getChange(); |
|
||||||
|
|
||||||
/// Set the divider on the internal counter. High resolution encoders without detents can be overly sensitive.
|
|
||||||
/// This will helf reduce sensisitive by increasing the divider. Default = 1.
|
|
||||||
/// @pram divider controls the sensitivity of the divider. Use powers of 2. E.g. 1, 2, 4, 8, etc.
|
|
||||||
void setDivider(int divider); |
|
||||||
|
|
||||||
private: |
|
||||||
bool m_swapDirection; ///< specifies if increment/decrement should be swapped
|
|
||||||
int32_t m_lastPosition = 0; ///< store the last recorded position
|
|
||||||
int32_t m_divider; ///< divides down the magnitude of change read by the encoder.
|
|
||||||
}; |
|
||||||
|
|
||||||
/// Specifies the type of control
|
|
||||||
enum class ControlType : unsigned { |
|
||||||
SWITCH_MOMENTARY = 0, ///< a momentary switch, which is only on when pressed.
|
|
||||||
SWITCH_LATCHING = 1, ///< a latching switch, which toggles between on and off with each press and release
|
|
||||||
ROTARY_KNOB = 2, ///< a rotary encoder knob
|
|
||||||
POT = 3, ///< an analog potentiometer
|
|
||||||
UNDEFINED = 255 ///< undefined or uninitialized
|
|
||||||
}; |
|
||||||
|
|
||||||
/// Tjis class provides convenient interface for combinary an arbitrary number of controls of different types into
|
|
||||||
/// one object. Supports switches, pots, encoders and digital outputs (useful for LEDs, relays).
|
|
||||||
class BAPhysicalControls { |
|
||||||
public: |
|
||||||
BAPhysicalControls() = delete; |
|
||||||
|
|
||||||
/// Construct an object and reserve memory for the specified number of controls. Encoders and outptus are optional params.
|
|
||||||
/// @param numSwitches the number of switches or buttons
|
|
||||||
/// @param numPots the number of analog potentiometers
|
|
||||||
/// @param numEncoders the number of quadrature encoders
|
|
||||||
/// @param numOutputs the number of digital outputs. E.g. LEDs, relays, etc.
|
|
||||||
BAPhysicalControls(unsigned numSwitches, unsigned numPots, unsigned numEncoders = 0, unsigned numOutputs = 0); |
|
||||||
~BAPhysicalControls() {} |
|
||||||
|
|
||||||
/// add a rotary encoders to the controls
|
|
||||||
/// @param pin1 the pin number corresponding to 'A' on the encoder
|
|
||||||
/// @param pin2 the pin number corresponding to 'B' on the encoder
|
|
||||||
/// @param swapDirection When true, reverses which rotation direction is positive, and which is negative
|
|
||||||
/// @param divider optional, for encoders with high resolution this divides down the rotation measurement.
|
|
||||||
/// @returns the index in the encoder vector the new encoder was placed at.
|
|
||||||
unsigned addRotary(uint8_t pin1, uint8_t pin2, bool swapDirection = false, int divider = 1); |
|
||||||
|
|
||||||
/// add a switch to the controls
|
|
||||||
/// @param pin the pin number connected to the switch
|
|
||||||
/// @param intervalMilliseconds, optional, specifies the filtering time to debounce a switch
|
|
||||||
/// @returns the index in the switch vector the new switch was placed at.
|
|
||||||
unsigned addSwitch(uint8_t pin, unsigned long intervalMilliseconds = 10); |
|
||||||
|
|
||||||
/// add a pot to the controls
|
|
||||||
/// @param pin the pin number connected to the wiper of the pot
|
|
||||||
/// @param minCalibration the value corresponding to lowest pot setting
|
|
||||||
/// @param maxCalibration the value corresponding to the highest pot setting
|
|
||||||
unsigned addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration); |
|
||||||
|
|
||||||
/// add a pot to the controls
|
|
||||||
/// @param pin the pin number connected to the wiper of the pot
|
|
||||||
/// @param minCalibration the value corresponding to lowest pot setting
|
|
||||||
/// @param maxCalibration the value corresponding to the highest pot setting
|
|
||||||
/// @param swapDirection reverses the which direction is considered pot minimum value
|
|
||||||
/// @param range the pot raw value will be mapped into a range of 0 to range
|
|
||||||
unsigned addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection); |
|
||||||
|
|
||||||
/// add an output to the controls
|
|
||||||
/// @param pin the pin number connected to the Arduino output
|
|
||||||
/// @returns a handle (unsigned) to the added output. Use this to access the output.
|
|
||||||
unsigned addOutput(uint8_t pin); |
|
||||||
|
|
||||||
/// Set the output specified by the provided handle
|
|
||||||
/// @param handle the handle that was provided previously by calling addOutput()
|
|
||||||
/// @param val the value to set the output. 0 is low, not zero is high.
|
|
||||||
void setOutput(unsigned handle, int val); |
|
||||||
|
|
||||||
/// Set the output specified by the provided handle
|
|
||||||
/// @param handle the handle that was provided previously by calling addOutput()
|
|
||||||
/// @param val the value to set the output. True is high, false is low.
|
|
||||||
void setOutput(unsigned handle, bool val); |
|
||||||
|
|
||||||
/// Toggle the output specified by the provided handle
|
|
||||||
/// @param handle the handle that was provided previously by calling addOutput()
|
|
||||||
void toggleOutput(unsigned handle); |
|
||||||
|
|
||||||
/// Retrieve the change in position on the specified rotary encoder
|
|
||||||
/// @param handle the handle that was provided previously by calling addRotary()
|
|
||||||
/// @returns an integer value. Positive is clockwise, negative is counter-clockwise rotation.
|
|
||||||
int getRotaryAdjustUnit(unsigned handle); |
|
||||||
|
|
||||||
/// Check if the pot specified by the handle has been updated.
|
|
||||||
/// @param handle the handle that was provided previously by calling addPot()
|
|
||||||
/// @param value a reference to a float, the pot value will be written to this variable.
|
|
||||||
/// @returns true if the pot value has changed since previous check, otherwise false
|
|
||||||
bool checkPotValue(unsigned handle, float &value); |
|
||||||
|
|
||||||
/// Get the raw uncalibrated value from the pot
|
|
||||||
/// @returns uncalibrated pot value
|
|
||||||
int getPotRawValue(unsigned handle); |
|
||||||
|
|
||||||
/// Override the calibration values with new values
|
|
||||||
/// @param handle handle the handle that was provided previously by calling addPot()
|
|
||||||
/// @param min the min raw value for the pot
|
|
||||||
/// @param max the max raw value for the pot
|
|
||||||
/// @param swapDirection when true, max raw value will mean min control value
|
|
||||||
/// @returns false when handle is out of range
|
|
||||||
bool setCalibrationValues(unsigned handle, unsigned min, unsigned max, bool swapDirection); |
|
||||||
|
|
||||||
/// Check if the switch has been toggled since last call
|
|
||||||
/// @param handle the handle that was provided previously by calling addSwitch()
|
|
||||||
/// @returns true if the switch changed state, otherwise false
|
|
||||||
bool isSwitchToggled(unsigned handle); |
|
||||||
|
|
||||||
/// Check if the switch is currently being pressed (held)
|
|
||||||
/// @param handle the handle that was provided previously by calling addSwitch()
|
|
||||||
/// @returns true if the switch is held in a pressed or closed state
|
|
||||||
bool isSwitchHeld(unsigned handle); |
|
||||||
|
|
||||||
/// Get the value of the switch
|
|
||||||
/// @param handle the handle that was provided previously by calling addSwitch()
|
|
||||||
/// @returns the value at the switch pin, either 0 or 1.
|
|
||||||
bool getSwitchValue(unsigned handle); |
|
||||||
|
|
||||||
/// Determine if a switch has changed value
|
|
||||||
/// @param handle the handle that was provided previously by calling addSwitch()
|
|
||||||
/// @param switchValue a boolean to store the new switch value
|
|
||||||
/// @returns true if the switch has changed
|
|
||||||
bool hasSwitchChanged(unsigned handle, bool &switchValue); |
|
||||||
|
|
||||||
private: |
|
||||||
std::vector<Potentiometer> m_pots; ///< a vector of all added pots
|
|
||||||
std::vector<RotaryEncoder> m_encoders; ///< a vector of all added encoders
|
|
||||||
std::vector<DigitalInput> m_switches; ///< a vector of all added switches
|
|
||||||
std::vector<DigitalOutput> m_outputs; ///< a vector of all added outputs
|
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
} // BALibrary
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* __BAPHYSICALCONTROLS_H */ |
|
@ -1,221 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BASpiMemory is convenience class for accessing the optional SPI RAMs on |
|
||||||
* the GTA Series boards. BASpiMemoryDma works the same but uses DMA to reduce |
|
||||||
* load on the processor. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
#ifndef __BALIBRARY_BASPIMEMORY_H |
|
||||||
#define __BALIBRARY_BASPIMEMORY_H |
|
||||||
|
|
||||||
#include <SPI.h> |
|
||||||
#include <DmaSpi.h> |
|
||||||
|
|
||||||
#include "BATypes.h" |
|
||||||
#include "BAHardware.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* This wrapper class uses the Arduino SPI (Wire) library to access the SPI ram. |
|
||||||
* @details The purpose of this class is primilary for functional testing since |
|
||||||
* it currently support single-word access. High performance access should be |
|
||||||
* done using DMA techniques in the Teensy library. |
|
||||||
*****************************************************************************/ |
|
||||||
class BASpiMemory { |
|
||||||
public: |
|
||||||
BASpiMemory() = delete; |
|
||||||
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2).
|
|
||||||
/// @details default is 20 Mhz
|
|
||||||
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
|
|
||||||
BASpiMemory(SpiDeviceId memDeviceId); |
|
||||||
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2)
|
|
||||||
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
|
|
||||||
/// @param speedHz specify the desired speed in Hz.
|
|
||||||
BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz); |
|
||||||
virtual ~BASpiMemory(); |
|
||||||
|
|
||||||
/// initialize and configure the SPI peripheral
|
|
||||||
virtual void begin(); |
|
||||||
|
|
||||||
/// write a single 8-bit word to the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param data the value to write
|
|
||||||
void write(size_t address, uint8_t data); |
|
||||||
|
|
||||||
/// Write a block of 8-bit data to the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param src pointer to the source data block
|
|
||||||
/// @param numBytes size of the data block in bytes
|
|
||||||
virtual void write(size_t address, uint8_t *src, size_t numBytes); |
|
||||||
|
|
||||||
/// Write a block of zeros to the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param numBytes size of the data block in bytes
|
|
||||||
virtual void zero(size_t address, size_t numBytes); |
|
||||||
|
|
||||||
/// write a single 16-bit word to the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param data the value to write
|
|
||||||
void write16(size_t address, uint16_t data); |
|
||||||
|
|
||||||
/// Write a block of 16-bit data to the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param src pointer to the source data block
|
|
||||||
/// @param numWords size of the data block in 16-bit words
|
|
||||||
virtual void write16(size_t address, uint16_t *src, size_t numWords); |
|
||||||
|
|
||||||
/// Write a block of 16-bit zeros to the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param numWords size of the data block in 16-bit words
|
|
||||||
virtual void zero16(size_t address, size_t numWords); |
|
||||||
|
|
||||||
/// read a single 8-bit data word from the specified address
|
|
||||||
/// @param address the address in the SPI RAM to read from
|
|
||||||
/// @return the data that was read
|
|
||||||
uint8_t read(size_t address); |
|
||||||
|
|
||||||
/// Read a block of 8-bit data from the specified address
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param dest pointer to the destination
|
|
||||||
/// @param numBytes size of the data block in bytes
|
|
||||||
virtual void read(size_t address, uint8_t *dest, size_t numBytes); |
|
||||||
|
|
||||||
/// read a single 16-bit data word from the specified address
|
|
||||||
/// @param address the address in the SPI RAM to read from
|
|
||||||
/// @return the data that was read
|
|
||||||
uint16_t read16(size_t address); |
|
||||||
|
|
||||||
/// read a block 16-bit data word from the specified address
|
|
||||||
/// @param address the address in the SPI RAM to read from
|
|
||||||
/// @param dest the pointer to the destination
|
|
||||||
/// @param numWords the number of 16-bit words to transfer
|
|
||||||
virtual void read16(size_t address, uint16_t *dest, size_t numWords); |
|
||||||
|
|
||||||
/// Check if the class has been configured by a previous begin() call
|
|
||||||
/// @returns true if initialized, false if not yet initialized
|
|
||||||
bool isStarted() const { return m_started; } |
|
||||||
|
|
||||||
protected: |
|
||||||
SPIClass *m_spi = nullptr; |
|
||||||
SpiDeviceId m_memDeviceId; // the MEM device being control with this instance
|
|
||||||
uint8_t m_csPin; // the IO pin number for the CS on the controlled SPI device
|
|
||||||
SPISettings m_settings; // the Wire settings for this SPI port
|
|
||||||
bool m_started = false; |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
class BASpiMemoryDMA : public BASpiMemory { |
|
||||||
public: |
|
||||||
BASpiMemoryDMA() = delete; |
|
||||||
|
|
||||||
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2).
|
|
||||||
/// @details default is 20 Mhz
|
|
||||||
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
|
|
||||||
BASpiMemoryDMA(SpiDeviceId memDeviceId); |
|
||||||
|
|
||||||
/// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2)
|
|
||||||
/// @param memDeviceId specify which MEM to control with SpiDeviceId.
|
|
||||||
/// @param speedHz specify the desired speed in Hz.
|
|
||||||
BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz); |
|
||||||
virtual ~BASpiMemoryDMA(); |
|
||||||
|
|
||||||
/// initialize and configure the SPI peripheral
|
|
||||||
void begin() override; |
|
||||||
|
|
||||||
/// Write a block of 8-bit data to the specified address. Be check
|
|
||||||
/// isWriteBusy() before sending the next DMA transfer.
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param src pointer to the source data block
|
|
||||||
/// @param numBytes size of the data block in bytes
|
|
||||||
void write(size_t address, uint8_t *src, size_t numBytes) override; |
|
||||||
|
|
||||||
/// Write a block of zeros to the specified address. Be check
|
|
||||||
/// isWriteBusy() before sending the next DMA transfer.
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param numBytes size of the data block in bytes
|
|
||||||
void zero(size_t address, size_t numBytes) override; |
|
||||||
|
|
||||||
/// Write a block of 16-bit data to the specified address. Be check
|
|
||||||
/// isWriteBusy() before sending the next DMA transfer.
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param src pointer to the source data block
|
|
||||||
/// @param numWords size of the data block in 16-bit words
|
|
||||||
void write16(size_t address, uint16_t *src, size_t numWords) override; |
|
||||||
|
|
||||||
/// Write a block of 16-bit zeros to the specified address. Be check
|
|
||||||
/// isWriteBusy() before sending the next DMA transfer.
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param numWords size of the data block in 16-bit words
|
|
||||||
void zero16(size_t address, size_t numWords) override; |
|
||||||
|
|
||||||
/// Read a block of 8-bit data from the specified address. Be check
|
|
||||||
/// isReadBusy() before sending the next DMA transfer.
|
|
||||||
/// @param address the address in the SPI RAM to write to
|
|
||||||
/// @param dest pointer to the destination
|
|
||||||
/// @param numBytes size of the data block in bytes
|
|
||||||
void read(size_t address, uint8_t *dest, size_t numBytes) override; |
|
||||||
|
|
||||||
/// read a block 16-bit data word from the specified address. Be check
|
|
||||||
/// isReadBusy() before sending the next DMA transfer.
|
|
||||||
/// @param address the address in the SPI RAM to read from
|
|
||||||
/// @param dest the pointer to the destination
|
|
||||||
/// @param numWords the number of 16-bit words to transfer
|
|
||||||
void read16(size_t address, uint16_t *dest, size_t numWords) override; |
|
||||||
|
|
||||||
/// Check if a DMA write is in progress
|
|
||||||
/// @returns true if a write DMA is in progress, else false
|
|
||||||
bool isWriteBusy() const; |
|
||||||
|
|
||||||
/// Check if a DMA read is in progress
|
|
||||||
/// @returns true if a read DMA is in progress, else false
|
|
||||||
bool isReadBusy() const; |
|
||||||
|
|
||||||
/// Readout the 8-bit contents of the DMA storage buffer to the specified destination
|
|
||||||
/// @param dest pointer to the destination
|
|
||||||
/// @param numBytes number of bytes to read out
|
|
||||||
/// @param byteOffset, offset from the start of the DMA buffer in bytes to begin reading
|
|
||||||
void readBufferContents(uint8_t *dest, size_t numBytes, size_t byteOffset = 0); |
|
||||||
|
|
||||||
/// Readout the 8-bit contents of the DMA storage buffer to the specified destination
|
|
||||||
/// @param dest pointer to the destination
|
|
||||||
/// @param numWords number of 16-bit words to read out
|
|
||||||
/// @param wordOffset, offset from the start of the DMA buffer in words to begin reading
|
|
||||||
void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0); |
|
||||||
|
|
||||||
private: |
|
||||||
|
|
||||||
DmaSpiGeneric *m_spiDma = nullptr; |
|
||||||
AbstractChipSelect *m_cs = nullptr; |
|
||||||
|
|
||||||
uint8_t *m_txCommandBuffer = nullptr; |
|
||||||
DmaSpi::Transfer *m_txTransfer; |
|
||||||
uint8_t *m_rxCommandBuffer = nullptr; |
|
||||||
DmaSpi::Transfer *m_rxTransfer; |
|
||||||
|
|
||||||
uint16_t m_txXferCount; |
|
||||||
uint16_t m_rxXferCount; |
|
||||||
|
|
||||||
void m_setSpiCmdAddr(int command, size_t address, uint8_t *dest); |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
||||||
|
|
||||||
#endif /* __BALIBRARY_BASPIMEMORY_H */ |
|
File diff suppressed because it is too large
Load Diff
@ -1,212 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* LibMemoryManagment is a class for providing access to external SPI based |
|
||||||
* SRAM with the optional convience of breaking it up into 'slots' which are smaller |
|
||||||
* memory entities. |
|
||||||
* @details This class treats an external memory as a pool from which the user requests |
|
||||||
* a block. When using that block, the user need not be concerned with where the pool |
|
||||||
* it came from, they only deal with offsets from the start of their memory block. Ie. |
|
||||||
* Your particular slot of memory appears to you to start at 0 and end at whatever size |
|
||||||
* was requested. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BALIBRARY_LIBMEMORYMANAGEMENT_H |
|
||||||
#define __BALIBRARY_LIBMEMORYMANAGEMENT_H |
|
||||||
|
|
||||||
#include <cstddef> |
|
||||||
|
|
||||||
#include "BAHardware.h" |
|
||||||
#include "BASpiMemory.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* MemConfig contains the configuration information associated with a particular |
|
||||||
* SPI interface. |
|
||||||
*****************************************************************************/ |
|
||||||
struct MemConfig { |
|
||||||
size_t size; ///< the total size of the external SPI memory
|
|
||||||
size_t totalAvailable; ///< the number of bytes available (remaining)
|
|
||||||
size_t nextAvailable; ///< the starting point for the next available slot
|
|
||||||
BASpiMemory *m_spi = nullptr; ///< handle to the SPI interface
|
|
||||||
}; |
|
||||||
|
|
||||||
class ExternalSramManager; // forward declare so ExtMemSlot can declared friendship with it
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* ExtMemSlot provides a convenient interface to a particular slot of an |
|
||||||
* external memory. |
|
||||||
* @details the memory can be access randomly, as a single word, as a block of |
|
||||||
* data, or as circular queue. |
|
||||||
*****************************************************************************/ |
|
||||||
class ExtMemSlot { |
|
||||||
public: |
|
||||||
|
|
||||||
/// clear the entire contents of the slot by writing zeros
|
|
||||||
/// @returns true on success
|
|
||||||
bool clear(); |
|
||||||
|
|
||||||
/// set a new write position (in bytes) for circular operation
|
|
||||||
/// @param offsetBytes moves the write pointer to the specified offset from the slot start
|
|
||||||
/// @returns true on success, else false if offset is beyond slot boundaries.
|
|
||||||
bool setWritePosition(size_t offsetBytes); |
|
||||||
|
|
||||||
/// returns the currently set write pointer pointer
|
|
||||||
/// @returns the write position value
|
|
||||||
size_t getWritePosition() const { return m_currentWrPosition-m_start; } |
|
||||||
|
|
||||||
/// set a new read position (in bytes) for circular operation
|
|
||||||
/// @param offsetBytes moves the read pointer to the specified offset from the slot start
|
|
||||||
/// @returns true on success, else false if offset is beyond slot boundaries.
|
|
||||||
bool setReadPosition(size_t offsetBytes); |
|
||||||
|
|
||||||
/// returns the currently set read pointer pointer
|
|
||||||
/// @returns the read position value
|
|
||||||
size_t getReadPosition() const { return m_currentRdPosition-m_start; } |
|
||||||
|
|
||||||
/// Write a block of 16-bit data to the memory at the specified offset
|
|
||||||
/// @param offsetWords offset in 16-bit words from start of slot
|
|
||||||
/// @param src pointer to start of block of 16-bit data
|
|
||||||
/// @param numWords number of 16-bit words to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool write16(size_t offsetWords, int16_t *src, size_t numWords); |
|
||||||
|
|
||||||
/// Write a block of zeros (16-bit) to the memory at the specified offset
|
|
||||||
/// @param offsetWords offset in 16-bit words from start of slot
|
|
||||||
/// @param numWords number of 16-bit words to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool zero16(size_t offsetWords, size_t numWords); |
|
||||||
|
|
||||||
/// Read a block of 16-bit data from the memory at the specified location
|
|
||||||
/// @param offsetWords offset in 16-bit words from start of slot
|
|
||||||
/// @param dest pointer to destination for the read data
|
|
||||||
/// @param numWords number of 16-bit words to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool read16(size_t offsetWords, int16_t *dest, size_t numWords); |
|
||||||
|
|
||||||
/// Read the next in memory during circular operation
|
|
||||||
/// @returns the next 16-bit data word in memory
|
|
||||||
uint16_t readAdvance16(); |
|
||||||
|
|
||||||
|
|
||||||
/// Read the next block of numWords during circular operation
|
|
||||||
/// @details, dest is ignored when using DMA
|
|
||||||
/// @param dest pointer to the destination of the read.
|
|
||||||
/// @param numWords number of 16-bit words to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool readAdvance16(int16_t *dest, size_t numWords); |
|
||||||
|
|
||||||
/// Write a block of 16-bit data from the specified location in circular operation
|
|
||||||
/// @param src pointer to the start of the block of data to write to memory
|
|
||||||
/// @param numWords number of 16-bit words to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool writeAdvance16(int16_t *src, size_t numWords); |
|
||||||
|
|
||||||
/// Write a single 16-bit data to the next location in circular operation
|
|
||||||
/// @param data the 16-bit word to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool writeAdvance16(int16_t data); // write just one data
|
|
||||||
|
|
||||||
/// Write a block of 16-bit data zeros in circular operation
|
|
||||||
/// @param numWords number of 16-bit words to transfer
|
|
||||||
/// @returns true on success, else false on error
|
|
||||||
bool zeroAdvance16(size_t numWords); |
|
||||||
|
|
||||||
/// Get the size of the memory slot
|
|
||||||
/// @returns size of the slot in bytes
|
|
||||||
size_t size() const { return m_size; } |
|
||||||
|
|
||||||
/// Ensures the underlying SPI interface is enabled
|
|
||||||
/// @returns true on success, false on error
|
|
||||||
bool enable() const; |
|
||||||
|
|
||||||
/// Checks whether underlying SPI interface is enabled
|
|
||||||
/// @returns true if enabled, false if not enabled
|
|
||||||
bool isEnabled() const; |
|
||||||
|
|
||||||
bool isUseDma() const { return m_useDma; } |
|
||||||
|
|
||||||
bool isWriteBusy() const; |
|
||||||
|
|
||||||
bool isReadBusy() const; |
|
||||||
|
|
||||||
/// DEBUG USE: prints out the slot member variables
|
|
||||||
void printStatus(void) const; |
|
||||||
|
|
||||||
private: |
|
||||||
friend ExternalSramManager; ///< gives the manager access to the private variables
|
|
||||||
bool m_valid = false; ///< After a slot is successfully configured by the manager it becomes valid
|
|
||||||
size_t m_start = 0; ///< the external memory address in bytes where this slot starts
|
|
||||||
size_t m_end = 0; ///< the external memory address in bytes where this slot ends (inclusive)
|
|
||||||
size_t m_currentWrPosition = 0; ///< current write pointer for circular operation
|
|
||||||
size_t m_currentRdPosition = 0; ///< current read pointer for circular operation
|
|
||||||
size_t m_size = 0; ///< size of this slot in bytes
|
|
||||||
bool m_useDma = false; ///< when TRUE, BASpiMemoryDMA will be used.
|
|
||||||
SpiDeviceId m_spiId; ///< the SPI Device ID
|
|
||||||
BASpiMemory *m_spi = nullptr; ///< pointer to an instance of the BASpiMemory interface class
|
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* ExternalSramManager provides a class to handle dividing an external SPI RAM |
|
||||||
* into independent slots for general use. |
|
||||||
* @details the class does not support deallocated memory because this would cause |
|
||||||
* fragmentation. |
|
||||||
*****************************************************************************/ |
|
||||||
class ExternalSramManager final { |
|
||||||
public: |
|
||||||
ExternalSramManager(); |
|
||||||
|
|
||||||
/// The manager is constructed by specifying how many external memories to handle allocations for
|
|
||||||
/// @param numMemories the number of external memories
|
|
||||||
ExternalSramManager(unsigned numMemories); |
|
||||||
virtual ~ExternalSramManager(); |
|
||||||
|
|
||||||
/// Query the amount of available (unallocated) memory
|
|
||||||
/// @details note that currently, memory cannot be allocated.
|
|
||||||
/// @param mem specifies which memory to query, default is memory 0
|
|
||||||
/// @returns the available memory in bytes
|
|
||||||
size_t availableMemory(BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0); |
|
||||||
|
|
||||||
/// Request memory be allocated for the provided slot
|
|
||||||
/// @param slot a pointer to the global slot object to which memory will be allocated
|
|
||||||
/// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes.
|
|
||||||
/// @param mem specify which external memory to allocate from
|
|
||||||
/// @param useDma when true, DMA is used for SPI port, else transfers block until complete
|
|
||||||
/// @returns true on success, otherwise false on error
|
|
||||||
bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0, bool useDma = false); |
|
||||||
|
|
||||||
/// Request memory be allocated for the provided slot
|
|
||||||
/// @param slot a pointer to the global slot object to which memory will be allocated
|
|
||||||
/// @param sizeBytes request the amount of memory in bytes to request
|
|
||||||
/// @param mem specify which external memory to allocate from
|
|
||||||
/// @param useDma when true, DMA is used for SPI port, else transfers block until complete
|
|
||||||
/// @returns true on success, otherwise false on error
|
|
||||||
bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0, bool useDma = false); |
|
||||||
|
|
||||||
private: |
|
||||||
static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project
|
|
||||||
static MemConfig m_memConfig[BALibrary::NUM_MEM_SLOTS]; ///< store the configuration information for each external memory
|
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
} // BALibrary
|
|
||||||
|
|
||||||
#endif /* __BALIBRARY_LIBMEMORYMANAGEMENT_H */ |
|
@ -1,235 +0,0 @@ |
|||||||
/*
|
|
||||||
* ExtMemSlot.cpp |
|
||||||
* |
|
||||||
* Created on: Jan 19, 2018 |
|
||||||
* Author: slascos |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
#include <cstring> |
|
||||||
#include <new> |
|
||||||
|
|
||||||
#include "Audio.h" |
|
||||||
#include "LibMemoryManagement.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// MEM SLOT
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
bool ExtMemSlot::clear() |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
m_spi->zero16(m_start, m_size); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::setWritePosition(size_t offsetBytes) |
|
||||||
{ |
|
||||||
if (m_start + offsetBytes <= m_end) { |
|
||||||
m_currentWrPosition = m_start + offsetBytes; |
|
||||||
return true; |
|
||||||
} else { return false; } |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::write16(size_t offsetWords, int16_t *src, size_t numWords) |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
size_t writeStart = m_start + sizeof(int16_t)*offsetWords; // 2x because int16 is two bytes per data
|
|
||||||
size_t numBytes = sizeof(int16_t)*numWords; |
|
||||||
if ((writeStart + numBytes-1) <= m_end) { |
|
||||||
m_spi->write16(writeStart, reinterpret_cast<uint16_t*>(src), numWords); // cast audio data to uint
|
|
||||||
return true; |
|
||||||
} else { |
|
||||||
// this would go past the end of the memory slot, do not perform the write
|
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::setReadPosition(size_t offsetBytes) |
|
||||||
{ |
|
||||||
if (m_start + offsetBytes <= m_end) { |
|
||||||
m_currentRdPosition = m_start + offsetBytes; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::zero16(size_t offsetWords, size_t numWords) |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
size_t writeStart = m_start + sizeof(int16_t)*offsetWords; |
|
||||||
size_t numBytes = sizeof(int16_t)*numWords; |
|
||||||
if ((writeStart + numBytes-1) <= m_end) { |
|
||||||
m_spi->zero16(writeStart, numWords); // cast audio data to uint
|
|
||||||
return true; |
|
||||||
} else { |
|
||||||
// this would go past the end of the memory slot, do not perform the write
|
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::read16(size_t offsetWords, int16_t *dest, size_t numWords) |
|
||||||
{ |
|
||||||
if (!dest) return false; // invalid destination
|
|
||||||
size_t readOffset = m_start + sizeof(int16_t)*offsetWords; |
|
||||||
size_t numBytes = sizeof(int16_t)*numWords; |
|
||||||
|
|
||||||
if ((readOffset + numBytes-1) <= m_end) { |
|
||||||
m_spi->read16(readOffset, reinterpret_cast<uint16_t*>(dest), numWords); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
// this would go past the end of the memory slot, do not perform the read
|
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
uint16_t ExtMemSlot::readAdvance16() |
|
||||||
{ |
|
||||||
uint16_t val = m_spi->read16(m_currentRdPosition); |
|
||||||
if (m_currentRdPosition < m_end-1) { |
|
||||||
m_currentRdPosition +=2; // position is in bytes and we read two
|
|
||||||
} else { |
|
||||||
m_currentRdPosition = m_start; |
|
||||||
} |
|
||||||
return val; |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::readAdvance16(int16_t *dest, size_t numWords) |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
size_t numBytes = sizeof(int16_t)*numWords; |
|
||||||
|
|
||||||
if (m_currentRdPosition + numBytes-1 <= m_end) { |
|
||||||
// entire block fits in memory slot without wrapping
|
|
||||||
m_spi->read16(m_currentRdPosition, reinterpret_cast<uint16_t*>(dest), numWords); // cast audio data to uint.
|
|
||||||
m_currentRdPosition += numBytes; |
|
||||||
|
|
||||||
} else { |
|
||||||
// this read will wrap the memory slot
|
|
||||||
size_t rdBytes = m_end - m_currentRdPosition + 1; |
|
||||||
size_t rdDataNum = rdBytes >> 1; // divide by two to get the number of data
|
|
||||||
m_spi->read16(m_currentRdPosition, reinterpret_cast<uint16_t*>(dest), rdDataNum); |
|
||||||
size_t remainingData = numWords - rdDataNum; |
|
||||||
m_spi->read16(m_start, reinterpret_cast<uint16_t*>(dest + rdDataNum), remainingData); // write remaining bytes are start
|
|
||||||
m_currentRdPosition = m_start + (remainingData*sizeof(int16_t)); |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
bool ExtMemSlot::writeAdvance16(int16_t *src, size_t numWords) |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
size_t numBytes = sizeof(int16_t)*numWords; |
|
||||||
|
|
||||||
if (m_currentWrPosition + numBytes-1 <= m_end) { |
|
||||||
// entire block fits in memory slot without wrapping
|
|
||||||
m_spi->write16(m_currentWrPosition, reinterpret_cast<uint16_t*>(src), numWords); // cast audio data to uint.
|
|
||||||
m_currentWrPosition += numBytes; |
|
||||||
|
|
||||||
} else { |
|
||||||
// this write will wrap the memory slot
|
|
||||||
size_t wrBytes = m_end - m_currentWrPosition + 1; |
|
||||||
size_t wrDataNum = wrBytes >> 1; // divide by two to get the number of data
|
|
||||||
m_spi->write16(m_currentWrPosition, reinterpret_cast<uint16_t*>(src), wrDataNum); |
|
||||||
size_t remainingData = numWords - wrDataNum; |
|
||||||
m_spi->write16(m_start, reinterpret_cast<uint16_t*>(src + wrDataNum), remainingData); // write remaining bytes are start
|
|
||||||
m_currentWrPosition = m_start + (remainingData*sizeof(int16_t)); |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
bool ExtMemSlot::zeroAdvance16(size_t numWords) |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
size_t numBytes = 2*numWords; |
|
||||||
if (m_currentWrPosition + numBytes-1 <= m_end) { |
|
||||||
// entire block fits in memory slot without wrapping
|
|
||||||
m_spi->zero16(m_currentWrPosition, numWords); // cast audio data to uint.
|
|
||||||
m_currentWrPosition += numBytes; |
|
||||||
|
|
||||||
} else { |
|
||||||
// this write will wrap the memory slot
|
|
||||||
size_t wrBytes = m_end - m_currentWrPosition + 1; |
|
||||||
size_t wrDataNum = wrBytes >> 1; |
|
||||||
m_spi->zero16(m_currentWrPosition, wrDataNum); |
|
||||||
size_t remainingWords = numWords - wrDataNum; // calculate the remaining bytes
|
|
||||||
m_spi->zero16(m_start, remainingWords); // write remaining bytes are start
|
|
||||||
m_currentWrPosition = m_start + remainingWords*sizeof(int16_t); |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
bool ExtMemSlot::writeAdvance16(int16_t data) |
|
||||||
{ |
|
||||||
if (!m_valid) { return false; } |
|
||||||
|
|
||||||
m_spi->write16(m_currentWrPosition, static_cast<uint16_t>(data)); |
|
||||||
if (m_currentWrPosition < m_end-1) { |
|
||||||
m_currentWrPosition+=2; // wrote two bytes
|
|
||||||
} else { |
|
||||||
m_currentWrPosition = m_start; |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
bool ExtMemSlot::enable() const |
|
||||||
{ |
|
||||||
if (m_spi) { |
|
||||||
Serial.println("ExtMemSlot::enable()"); |
|
||||||
m_spi->begin(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
else { |
|
||||||
Serial.println("ExtMemSlot m_spi is nullptr"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::isEnabled() const |
|
||||||
{ |
|
||||||
if (m_spi) { return m_spi->isStarted(); } |
|
||||||
else return false; |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::isWriteBusy() const |
|
||||||
{ |
|
||||||
if (m_useDma) { |
|
||||||
return (static_cast<BASpiMemoryDMA*>(m_spi))->isWriteBusy(); |
|
||||||
} else { return false; } |
|
||||||
} |
|
||||||
|
|
||||||
bool ExtMemSlot::isReadBusy() const |
|
||||||
{ |
|
||||||
if (m_useDma) { |
|
||||||
return (static_cast<BASpiMemoryDMA*>(m_spi))->isReadBusy(); |
|
||||||
} else { return false; } |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void ExtMemSlot::printStatus(void) const |
|
||||||
{ |
|
||||||
Serial.println(String("valid:") + m_valid + String(" m_start:") + m_start + \
|
|
||||||
String(" m_end:") + m_end + String(" m_currentWrPosition: ") + m_currentWrPosition + \
|
|
||||||
String(" m_currentRdPosition: ") + m_currentRdPosition + \
|
|
||||||
String(" m_size:") + m_size); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
@ -1,122 +0,0 @@ |
|||||||
/*
|
|
||||||
* LibMemoryManagement.cpp |
|
||||||
* |
|
||||||
* Created on: Jan 19, 2018 |
|
||||||
* Author: slascos |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
#include <cstring> |
|
||||||
#include <new> |
|
||||||
|
|
||||||
#include "Audio.h" |
|
||||||
#include "LibMemoryManagement.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// EXTERNAL SRAM MANAGER
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
bool ExternalSramManager::m_configured = false; |
|
||||||
MemConfig ExternalSramManager::m_memConfig[BALibrary::NUM_MEM_SLOTS]; |
|
||||||
|
|
||||||
|
|
||||||
ExternalSramManager::ExternalSramManager(unsigned numMemories) |
|
||||||
{ |
|
||||||
// Initialize the static memory configuration structs
|
|
||||||
if (!m_configured) { |
|
||||||
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) { |
|
||||||
m_memConfig[i].size = MEM_MAX_ADDR[i]+1; |
|
||||||
m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]+1; |
|
||||||
m_memConfig[i].nextAvailable = 0; |
|
||||||
|
|
||||||
m_memConfig[i].m_spi = nullptr; |
|
||||||
} |
|
||||||
m_configured = true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ExternalSramManager::ExternalSramManager() |
|
||||||
: ExternalSramManager(1) |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
ExternalSramManager::~ExternalSramManager() |
|
||||||
{ |
|
||||||
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) { |
|
||||||
if (m_memConfig[i].m_spi) { delete m_memConfig[i].m_spi; } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
size_t ExternalSramManager::availableMemory(BALibrary::MemSelect mem) |
|
||||||
{ |
|
||||||
return m_memConfig[mem].totalAvailable; |
|
||||||
} |
|
||||||
|
|
||||||
bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BALibrary::MemSelect mem, bool useDma) |
|
||||||
{ |
|
||||||
// convert the time to numer of samples
|
|
||||||
size_t delayLengthInt = (size_t)((delayMilliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); |
|
||||||
return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, useDma); |
|
||||||
} |
|
||||||
|
|
||||||
bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BALibrary::MemSelect mem, bool useDma) |
|
||||||
{ |
|
||||||
|
|
||||||
if (m_memConfig[mem].totalAvailable >= sizeBytes) { |
|
||||||
Serial.println(String("Configuring a slot for mem ") + mem); |
|
||||||
// there is enough available memory for this request
|
|
||||||
slot->m_start = m_memConfig[mem].nextAvailable; |
|
||||||
slot->m_end = slot->m_start + sizeBytes -1; |
|
||||||
slot->m_currentWrPosition = slot->m_start; // init to start of slot
|
|
||||||
slot->m_currentRdPosition = slot->m_start; // init to start of slot
|
|
||||||
slot->m_size = sizeBytes; |
|
||||||
|
|
||||||
if (!m_memConfig[mem].m_spi) { |
|
||||||
if (useDma) { |
|
||||||
m_memConfig[mem].m_spi = new BALibrary::BASpiMemoryDMA(static_cast<BALibrary::SpiDeviceId>(mem)); |
|
||||||
slot->m_useDma = true; |
|
||||||
} else { |
|
||||||
m_memConfig[mem].m_spi = new BALibrary::BASpiMemory(static_cast<BALibrary::SpiDeviceId>(mem)); |
|
||||||
slot->m_useDma = false; |
|
||||||
} |
|
||||||
if (!m_memConfig[mem].m_spi) { |
|
||||||
} else { |
|
||||||
Serial.println("Calling spi begin()"); |
|
||||||
m_memConfig[mem].m_spi->begin(); |
|
||||||
} |
|
||||||
} |
|
||||||
slot->m_spi = m_memConfig[mem].m_spi; |
|
||||||
|
|
||||||
// Update the mem config
|
|
||||||
m_memConfig[mem].nextAvailable = slot->m_end+1; |
|
||||||
m_memConfig[mem].totalAvailable -= sizeBytes; |
|
||||||
slot->m_valid = true; |
|
||||||
if (!slot->isEnabled()) { slot->enable(); } |
|
||||||
Serial.println("Clear the memory\n"); Serial.flush(); |
|
||||||
slot->clear(); |
|
||||||
Serial.println("Done Request memory\n"); Serial.flush(); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
// there is not enough memory available for the request
|
|
||||||
Serial.println(String("ExternalSramManager::requestMemory(): Insufficient memory in slot, request/available: ") |
|
||||||
+ sizeBytes + String(" : ") |
|
||||||
+ m_memConfig[mem].totalAvailable); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
@ -0,0 +1,329 @@ |
|||||||
|
/*
|
||||||
|
* AudioEffectAnalogChorus.cpp |
||||||
|
* |
||||||
|
* Created on: Jan 7, 2018 |
||||||
|
* Author: slascos |
||||||
|
*/ |
||||||
|
#include <new> |
||||||
|
#include <cmath> |
||||||
|
#include "AudioEffectAnalogChorusFilters.h" |
||||||
|
#include "AudioEffectAnalogChorus.h" |
||||||
|
|
||||||
|
using namespace BALibrary; |
||||||
|
|
||||||
|
namespace BAEffects { |
||||||
|
|
||||||
|
constexpr int MIDI_CHANNEL = 0; |
||||||
|
constexpr int MIDI_CONTROL = 1; |
||||||
|
|
||||||
|
constexpr float DELAY_REFERENCE_F = static_cast<float>(AUDIO_BLOCK_SAMPLES/2); |
||||||
|
|
||||||
|
AudioEffectAnalogChorus::AudioEffectAnalogChorus() |
||||||
|
: AudioStream(1, m_inputQueueArray) |
||||||
|
{ |
||||||
|
m_memory = new AudioDelay(m_DEFAULT_AVERAGE_DELAY_MS + m_DELAY_RANGE); |
||||||
|
m_maxDelaySamples = calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS + m_DELAY_RANGE); |
||||||
|
m_averageDelaySamples = static_cast<float>(calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS)); |
||||||
|
m_delayRange = static_cast<float>(calcAudioSamples(m_DELAY_RANGE)); |
||||||
|
|
||||||
|
m_constructFilter(); |
||||||
|
m_lfo.setWaveform(Waveform::TRIANGLE); |
||||||
|
m_lfo.setRateAudio(4.0f); // Default to 4 Hz
|
||||||
|
} |
||||||
|
|
||||||
|
AudioEffectAnalogChorus::~AudioEffectAnalogChorus() |
||||||
|
{ |
||||||
|
if (m_memory) delete m_memory; |
||||||
|
if (m_iir) delete m_iir; |
||||||
|
} |
||||||
|
|
||||||
|
// This function just sets up the default filter and coefficients
|
||||||
|
void AudioEffectAnalogChorus::m_constructFilter(void) |
||||||
|
{ |
||||||
|
// Use CE2 coefficients by default
|
||||||
|
m_iir = new IirBiQuadFilterHQ(CE2_NUM_STAGES, reinterpret_cast<const int32_t *>(&CE2), CE2_COEFF_SHIFT); |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::setWaveform(BALibrary::Waveform waveform) |
||||||
|
{ |
||||||
|
switch(waveform) { |
||||||
|
case Waveform::SINE : |
||||||
|
case Waveform::TRIANGLE : |
||||||
|
case Waveform::SAWTOOTH : |
||||||
|
m_lfo.setWaveform(waveform); |
||||||
|
break; |
||||||
|
default : |
||||||
|
Serial.println("AudioEffectAnalogChorus::setWaveform: Unsupported Waveform"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift) |
||||||
|
{ |
||||||
|
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift); |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::setFilter(Filter filter) |
||||||
|
{ |
||||||
|
switch(filter) { |
||||||
|
case Filter::WARM : |
||||||
|
m_iir->changeFilterCoeffs(WARM_NUM_STAGES, reinterpret_cast<const int32_t *>(&WARM), WARM_COEFF_SHIFT); |
||||||
|
break; |
||||||
|
case Filter::DARK : |
||||||
|
m_iir->changeFilterCoeffs(DARK_NUM_STAGES, reinterpret_cast<const int32_t *>(&DARK), DARK_COEFF_SHIFT); |
||||||
|
break; |
||||||
|
case Filter::CE2 : |
||||||
|
default: |
||||||
|
m_iir->changeFilterCoeffs(CE2_NUM_STAGES, reinterpret_cast<const int32_t *>(&CE2), CE2_COEFF_SHIFT); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::update(void) |
||||||
|
{ |
||||||
|
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
|
||||||
|
|
||||||
|
// Check is block is disabled
|
||||||
|
if (m_enable == false) { |
||||||
|
// do not transmit or proess any audio, return as quickly as possible.
|
||||||
|
if (inputAudioBlock) release(inputAudioBlock); |
||||||
|
|
||||||
|
// release all held memory resources
|
||||||
|
if (m_previousBlock) { |
||||||
|
release(m_previousBlock); m_previousBlock = nullptr; |
||||||
|
} |
||||||
|
if (!m_externalMemory) { |
||||||
|
// when using internal memory we have to release all references in the ring buffer
|
||||||
|
while (m_memory->getRingBuffer()->size() > 0) { |
||||||
|
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); |
||||||
|
m_memory->getRingBuffer()->pop_front(); |
||||||
|
if (releaseBlock) release(releaseBlock); |
||||||
|
} |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Check is block is bypassed, if so either transmit input directly or create silence
|
||||||
|
if (m_bypass == true) { |
||||||
|
// transmit the input directly
|
||||||
|
if (!inputAudioBlock) { |
||||||
|
// create silence
|
||||||
|
inputAudioBlock = allocate(); |
||||||
|
if (!inputAudioBlock) { return; } // failed to allocate
|
||||||
|
else { |
||||||
|
clearAudioBlock(inputAudioBlock); |
||||||
|
} |
||||||
|
} |
||||||
|
transmit(inputAudioBlock, 0); |
||||||
|
release(inputAudioBlock); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Otherwise perform normal processing
|
||||||
|
// In order to make use of the SPI DMA, we need to request the read from memory first,
|
||||||
|
// then do other processing while it fills in the back.
|
||||||
|
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
|
||||||
|
blockToOutput = allocate(); |
||||||
|
if (!blockToOutput) return; // skip this update cycle due to failure
|
||||||
|
|
||||||
|
// get the data. If using external memory with DMA, this won't be filled until
|
||||||
|
// later.
|
||||||
|
// We need to grab two blocks of audio since the modulating delay value from the LFO
|
||||||
|
// can exceed the length of one audio block during the time frame of one audio block.
|
||||||
|
int16_t extendedBuffer[(2*AUDIO_BLOCK_SAMPLES)]; // need one more sample for interpolating between 128th and 129th (last sample)
|
||||||
|
|
||||||
|
// Get next vector of lfo values, they will range range from -1.0 to +1.0f.
|
||||||
|
float *lfoValues = m_lfo.getNextVector(); |
||||||
|
//float lfoValues[128];
|
||||||
|
for (int i=0; i<128; i++) { lfoValues[i] = lfoValues[i] * m_lfoDepth; } |
||||||
|
|
||||||
|
// Calculate the starting delay from the first lfo sample. This will represent the 'reference' delay
|
||||||
|
// for this output block
|
||||||
|
float referenceDelay = m_averageDelaySamples + (lfoValues[0] * m_delayRange); |
||||||
|
unsigned delaySamples = static_cast<unsigned>(referenceDelay); // round down to the nearest audio sample for indexing into AudioDelay class
|
||||||
|
|
||||||
|
// From a given current delay value, while reading out the next 128, the delay could slew up or down
|
||||||
|
// AUDIO_BLOCK_SAMPLES/2 cycles of delay. For example...
|
||||||
|
// Pitching up : current + 128 + 64
|
||||||
|
// Pitching down: current - 64 + 128
|
||||||
|
// We need to grab 2*AUDIO_BLOCK_SAMPLES. Be aware that audio samples are stored BACKWARDS in the buffers.
|
||||||
|
// m_memory->getSamples(extendedBuffer , delaySamples - (AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
|
||||||
|
// m_memory->getSamples(extendedBuffer + AUDIO_BLOCK_SAMPLES, delaySamples +( AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
|
||||||
|
m_memory->getSamples(extendedBuffer + AUDIO_BLOCK_SAMPLES, delaySamples - (AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES); |
||||||
|
m_memory->getSamples(extendedBuffer , delaySamples +( AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES); |
||||||
|
// If using DMA, we need something else to do while that read executes, so
|
||||||
|
// move on to input preprocessing
|
||||||
|
|
||||||
|
// Preprocessing
|
||||||
|
audio_block_t *preProcessed = allocate(); |
||||||
|
// mix the input with the feedback path in the pre-processing stage
|
||||||
|
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); |
||||||
|
|
||||||
|
// consider doing the BBD post processing here to use up more time while waiting
|
||||||
|
// for the read data to come back
|
||||||
|
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); |
||||||
|
|
||||||
|
|
||||||
|
// BACK TO OUTPUT PROCESSING
|
||||||
|
double bufferIndexFloat; |
||||||
|
int delayIndex; |
||||||
|
for (int i=0, j=AUDIO_BLOCK_SAMPLES-1; i<AUDIO_BLOCK_SAMPLES; i++,j--) { |
||||||
|
// each output sample will be an interpolated value between two samples
|
||||||
|
// the precise delay value will be based on the LFO vector values.
|
||||||
|
// For each output sample, calculate the floating point delay offset from the reference delay.
|
||||||
|
// This will be an offset from location AUDIO_BLOCK_SAMPLES/2 (e.g. 64) in the buffer.
|
||||||
|
float offsetDelayFromRef = m_averageDelaySamples + (lfoValues[i] * m_delayRange) - referenceDelay; |
||||||
|
float bufferPosition = DELAY_REFERENCE_F + offsetDelayFromRef; |
||||||
|
|
||||||
|
// Get the interpolation coefficients from the fractional part of the buffer position
|
||||||
|
float fraction1 = modf(bufferPosition, &bufferIndexFloat); |
||||||
|
float fraction2 = 1.0f - fraction1; |
||||||
|
//fraction1 = 0.5f;
|
||||||
|
//fraction2 = 0.5f;
|
||||||
|
delayIndex = static_cast<unsigned>(bufferIndexFloat); |
||||||
|
if ( (delayIndex < 0) || (delayIndex > 256) ) { |
||||||
|
Serial.println(String("lfoValues[") + i + String("]:") + lfoValues[i] + |
||||||
|
String(" referenceDelay:") + referenceDelay + |
||||||
|
String(" bufferPosition:") + bufferPosition + |
||||||
|
String(" delayIndex:") + delayIndex) ; |
||||||
|
} |
||||||
|
|
||||||
|
//delayIndex = 64+i;
|
||||||
|
blockToOutput->data[j] = static_cast<int16_t>( |
||||||
|
(static_cast<float>(extendedBuffer[j+delayIndex]) * fraction1) + |
||||||
|
(static_cast<float>(extendedBuffer[j+delayIndex+1]) * fraction2) ); |
||||||
|
//blockToOutput->data[i] = extendedBuffer[64+i];
|
||||||
|
} |
||||||
|
|
||||||
|
// perform the wet/dry mix mix
|
||||||
|
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); |
||||||
|
transmit(blockToOutput); |
||||||
|
|
||||||
|
release(inputAudioBlock); |
||||||
|
release(m_previousBlock); |
||||||
|
m_previousBlock = blockToOutput; |
||||||
|
|
||||||
|
// if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
|
||||||
|
// // Using DMA
|
||||||
|
// if (m_blockToRelease) release(m_blockToRelease);
|
||||||
|
// m_blockToRelease = blockToRelease;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (m_blockToRelease) release(m_blockToRelease); |
||||||
|
m_blockToRelease = blockToRelease; |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
||||||
|
{ |
||||||
|
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
||||||
|
// TODO: Clean this up with proper preprocessing
|
||||||
|
// if ( out && dry && wet) {
|
||||||
|
// alphaBlend(out, dry, wet, m_feedback);
|
||||||
|
// m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES);
|
||||||
|
// } else if (dry) {
|
||||||
|
// memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
|
||||||
|
// }
|
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
||||||
|
{ |
||||||
|
if (!out) return; // no valid output buffer
|
||||||
|
|
||||||
|
if ( out && dry && wet) { |
||||||
|
// Simulate the LPF IIR nature of the analog systems
|
||||||
|
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES);
|
||||||
|
alphaBlend(out, dry, wet, m_mix); |
||||||
|
} else if (dry) { |
||||||
|
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
||||||
|
} |
||||||
|
// Set the output volume
|
||||||
|
gainAdjust(out, out, m_volume, 1); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::setDelayConfig(float averageDelayMs, float delayRangeMs) |
||||||
|
{ |
||||||
|
setDelayConfig(calcAudioSamples(averageDelayMs), calcAudioSamples(delayRangeMs)); |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::setDelayConfig(size_t averageDelayNumSamples, size_t delayRangeNumSamples) |
||||||
|
{ |
||||||
|
size_t delaySamples = averageDelayNumSamples + delayRangeNumSamples; |
||||||
|
m_averageDelaySamples = averageDelayNumSamples; |
||||||
|
m_delayRange = delayRangeNumSamples; |
||||||
|
|
||||||
|
if (delaySamples > m_memory->getMaxDelaySamples()) { |
||||||
|
// this exceeds max delay value, limit it.
|
||||||
|
delaySamples = m_memory->getMaxDelaySamples(); |
||||||
|
m_averageDelaySamples = delaySamples/2; |
||||||
|
m_delayRange = delaySamples/2; |
||||||
|
} |
||||||
|
|
||||||
|
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::rate(float rate) |
||||||
|
{ |
||||||
|
// update the LFO by mapping the rate into the MIN/MAX range, pass to LFO in milliseconds
|
||||||
|
m_lfo.setRateAudio(m_LFO_MIN_RATE + (rate * m_LFO_RANGE)); |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::processMidi(int channel, int control, int value) |
||||||
|
{ |
||||||
|
|
||||||
|
float val = (float)value / 127.0f; |
||||||
|
|
||||||
|
if ((m_midiConfig[RATE][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[RATE][MIDI_CONTROL] == control)) { |
||||||
|
// Rate
|
||||||
|
Serial.println(String("AudioEffectAnalogChorus::rate: ") + 100*val + String("%")); |
||||||
|
rate(val); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { |
||||||
|
// Bypass
|
||||||
|
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogChorus::not bypassed -> ON") + value); } |
||||||
|
else { bypass(true); Serial.println(String("AudioEffectAnalogChorus::bypassed -> OFF") + value); } |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[DEPTH][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[DEPTH][MIDI_CONTROL] == control)) { |
||||||
|
// depth
|
||||||
|
Serial.println(String("AudioEffectAnalogChorus::depth: ") + 100*val + String("%")); |
||||||
|
depth(val); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[MIX][MIDI_CONTROL] == control)) { |
||||||
|
// Mix
|
||||||
|
Serial.println(String("AudioEffectAnalogChorus::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val ); |
||||||
|
mix(val); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
||||||
|
// Volume
|
||||||
|
Serial.println(String("AudioEffectAnalogChorus::volume: ") + 100*val + String("%")); |
||||||
|
volume(val); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectAnalogChorus::mapMidiControl(int parameter, int midiCC, int midiChannel) |
||||||
|
{ |
||||||
|
if (parameter >= NUM_CONTROLS) { |
||||||
|
return ; // Invalid midi parameter
|
||||||
|
} |
||||||
|
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; |
||||||
|
m_midiConfig[parameter][MIDI_CONTROL] = midiCC; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
@ -1,324 +0,0 @@ |
|||||||
/*
|
|
||||||
* AudioEffectAnalogDelay.cpp |
|
||||||
* |
|
||||||
* Created on: Jan 7, 2018 |
|
||||||
* Author: slascos |
|
||||||
*/ |
|
||||||
#include <new> |
|
||||||
#include "AudioEffectAnalogDelayFilters.h" |
|
||||||
#include "AudioEffectAnalogDelay.h" |
|
||||||
|
|
||||||
using namespace BALibrary; |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
constexpr int MIDI_CHANNEL = 0; |
|
||||||
constexpr int MIDI_CONTROL = 1; |
|
||||||
|
|
||||||
AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(maxDelayMs); |
|
||||||
m_maxDelaySamples = calcAudioSamples(maxDelayMs); |
|
||||||
m_constructFilter(); |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(numSamples); |
|
||||||
m_maxDelaySamples = numSamples; |
|
||||||
m_constructFilter(); |
|
||||||
} |
|
||||||
|
|
||||||
// requires preallocated memory large enough
|
|
||||||
AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(slot); |
|
||||||
m_maxDelaySamples = (slot->size() / sizeof(int16_t)); |
|
||||||
m_externalMemory = true; |
|
||||||
m_constructFilter(); |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectAnalogDelay::~AudioEffectAnalogDelay() |
|
||||||
{ |
|
||||||
if (m_memory) delete m_memory; |
|
||||||
if (m_iir) delete m_iir; |
|
||||||
} |
|
||||||
|
|
||||||
// This function just sets up the default filter and coefficients
|
|
||||||
void AudioEffectAnalogDelay::m_constructFilter(void) |
|
||||||
{ |
|
||||||
// Use DM3 coefficients by default
|
|
||||||
m_iir = new IirBiQuadFilterHQ(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift) |
|
||||||
{ |
|
||||||
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::setFilter(Filter filter) |
|
||||||
{ |
|
||||||
switch(filter) { |
|
||||||
case Filter::WARM : |
|
||||||
m_iir->changeFilterCoeffs(WARM_NUM_STAGES, reinterpret_cast<const int32_t *>(&WARM), WARM_COEFF_SHIFT); |
|
||||||
break; |
|
||||||
case Filter::DARK : |
|
||||||
m_iir->changeFilterCoeffs(DARK_NUM_STAGES, reinterpret_cast<const int32_t *>(&DARK), DARK_COEFF_SHIFT); |
|
||||||
break; |
|
||||||
case Filter::DM3 : |
|
||||||
default: |
|
||||||
m_iir->changeFilterCoeffs(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::update(void) |
|
||||||
{ |
|
||||||
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
|
|
||||||
|
|
||||||
// Check is block is disabled
|
|
||||||
if (m_enable == false) { |
|
||||||
// do not transmit or process any audio, return as quickly as possible.
|
|
||||||
if (inputAudioBlock) release(inputAudioBlock); |
|
||||||
|
|
||||||
// release all held memory resources
|
|
||||||
if (m_previousBlock) { |
|
||||||
release(m_previousBlock); m_previousBlock = nullptr; |
|
||||||
} |
|
||||||
if (!m_externalMemory) { |
|
||||||
// when using internal memory we have to release all references in the ring buffer
|
|
||||||
while (m_memory->getRingBuffer()->size() > 0) { |
|
||||||
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); |
|
||||||
m_memory->getRingBuffer()->pop_front(); |
|
||||||
if (releaseBlock) release(releaseBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Check is block is bypassed, if so either transmit input directly or create silence
|
|
||||||
if (m_bypass == true) { |
|
||||||
// transmit the input directly
|
|
||||||
if (!inputAudioBlock) { |
|
||||||
// create silence
|
|
||||||
inputAudioBlock = allocate(); |
|
||||||
if (!inputAudioBlock) { return; } // failed to allocate
|
|
||||||
else { |
|
||||||
clearAudioBlock(inputAudioBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
transmit(inputAudioBlock, 0); |
|
||||||
release(inputAudioBlock); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Otherwise perform normal processing
|
|
||||||
// In order to make use of the SPI DMA, we need to request the read from memory first,
|
|
||||||
// then do other processing while it fills in the back.
|
|
||||||
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
|
|
||||||
blockToOutput = allocate(); |
|
||||||
if (!blockToOutput) return; // skip this update cycle due to failure
|
|
||||||
|
|
||||||
// get the data. If using external memory with DMA, this won't be filled until
|
|
||||||
// later.
|
|
||||||
m_memory->getSamples(blockToOutput, m_delaySamples); |
|
||||||
|
|
||||||
// If using DMA, we need something else to do while that read executes, so
|
|
||||||
// move on to input preprocessing
|
|
||||||
|
|
||||||
// Preprocessing
|
|
||||||
audio_block_t *preProcessed = allocate(); |
|
||||||
// mix the input with the feedback path in the pre-processing stage
|
|
||||||
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); |
|
||||||
|
|
||||||
// consider doing the BBD post processing here to use up more time while waiting
|
|
||||||
// for the read data to come back
|
|
||||||
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); |
|
||||||
|
|
||||||
|
|
||||||
// BACK TO OUTPUT PROCESSING
|
|
||||||
// Check if external DMA, if so, we need to be sure the read is completed
|
|
||||||
if (m_externalMemory && m_memory->getSlot()->isUseDma()) { |
|
||||||
// Using DMA
|
|
||||||
while (m_memory->getSlot()->isReadBusy()) {} |
|
||||||
} |
|
||||||
|
|
||||||
// perform the wet/dry mix mix
|
|
||||||
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); |
|
||||||
transmit(blockToOutput); |
|
||||||
|
|
||||||
release(inputAudioBlock); |
|
||||||
release(m_previousBlock); |
|
||||||
m_previousBlock = blockToOutput; |
|
||||||
|
|
||||||
if (m_blockToRelease) release(m_blockToRelease); |
|
||||||
m_blockToRelease = blockToRelease; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::delay(float milliseconds) |
|
||||||
{ |
|
||||||
size_t delaySamples = calcAudioSamples(milliseconds); |
|
||||||
|
|
||||||
if (delaySamples > m_memory->getMaxDelaySamples()) { |
|
||||||
// this exceeds max delay value, limit it.
|
|
||||||
delaySamples = m_memory->getMaxDelaySamples(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
||||||
|
|
||||||
if (!m_externalMemory) { |
|
||||||
// internal memory
|
|
||||||
//QueuePosition queuePosition = calcQueuePosition(milliseconds);
|
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
|
|
||||||
} else { |
|
||||||
// external memory
|
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples);
|
|
||||||
ExtMemSlot *slot = m_memory->getSlot(); |
|
||||||
|
|
||||||
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); } |
|
||||||
if (!slot->isEnabled()) { |
|
||||||
slot->enable(); |
|
||||||
Serial.println("WEIRD: slot was not enabled"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
m_delaySamples = delaySamples; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::delay(size_t delaySamples) |
|
||||||
{ |
|
||||||
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
||||||
|
|
||||||
if (!m_externalMemory) { |
|
||||||
// internal memory
|
|
||||||
//QueuePosition queuePosition = calcQueuePosition(delaySamples);
|
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
|
|
||||||
} else { |
|
||||||
// external memory
|
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples);
|
|
||||||
ExtMemSlot *slot = m_memory->getSlot(); |
|
||||||
if (!slot->isEnabled()) { |
|
||||||
slot->enable(); |
|
||||||
} |
|
||||||
} |
|
||||||
m_delaySamples = delaySamples; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::delayFractionMax(float delayFraction) |
|
||||||
{ |
|
||||||
size_t delaySamples = static_cast<size_t>(static_cast<float>(m_memory->getMaxDelaySamples()) * delayFraction); |
|
||||||
|
|
||||||
if (delaySamples > m_memory->getMaxDelaySamples()) { |
|
||||||
// this exceeds max delay value, limit it.
|
|
||||||
delaySamples = m_memory->getMaxDelaySamples(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
||||||
|
|
||||||
if (!m_externalMemory) { |
|
||||||
// internal memory
|
|
||||||
//QueuePosition queuePosition = calcQueuePosition(delaySamples);
|
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset);
|
|
||||||
} else { |
|
||||||
// external memory
|
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples);
|
|
||||||
ExtMemSlot *slot = m_memory->getSlot(); |
|
||||||
if (!slot->isEnabled()) { |
|
||||||
slot->enable(); |
|
||||||
} |
|
||||||
} |
|
||||||
m_delaySamples = delaySamples; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
|
||||||
{ |
|
||||||
if ( out && dry && wet) { |
|
||||||
alphaBlend(out, dry, wet, m_feedback); |
|
||||||
m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); |
|
||||||
} else if (dry) { |
|
||||||
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
|
||||||
{ |
|
||||||
if (!out) return; // no valid output buffer
|
|
||||||
|
|
||||||
if ( out && dry && wet) { |
|
||||||
// Simulate the LPF IIR nature of the analog systems
|
|
||||||
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES);
|
|
||||||
alphaBlend(out, dry, wet, m_mix); |
|
||||||
} else if (dry) { |
|
||||||
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
|
||||||
} |
|
||||||
// Set the output volume
|
|
||||||
gainAdjust(out, out, m_volume, 1); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) |
|
||||||
{ |
|
||||||
|
|
||||||
float val = (float)value / 127.0f; |
|
||||||
|
|
||||||
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) { |
|
||||||
// Delay
|
|
||||||
if (m_externalMemory) { m_maxDelaySamples = m_memory->getSlot()->size() / sizeof(int16_t); } |
|
||||||
size_t delayVal = (size_t)(val * (float)m_maxDelaySamples); |
|
||||||
delay(delayVal); |
|
||||||
Serial.println(String("AudioEffectAnalogDelay::delay (ms): ") + calcAudioTimeMs(delayVal) |
|
||||||
+ String(" (samples): ") + delayVal + String(" out of ") + m_maxDelaySamples); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { |
|
||||||
// Bypass
|
|
||||||
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); } |
|
||||||
else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); } |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { |
|
||||||
// Feedback
|
|
||||||
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%")); |
|
||||||
feedback(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[MIX][MIDI_CONTROL] == control)) { |
|
||||||
// Mix
|
|
||||||
Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val ); |
|
||||||
mix(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
|
||||||
// Volume
|
|
||||||
Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%")); |
|
||||||
volume(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel) |
|
||||||
{ |
|
||||||
if (parameter >= NUM_CONTROLS) { |
|
||||||
return ; // Invalid midi parameter
|
|
||||||
} |
|
||||||
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; |
|
||||||
m_midiConfig[parameter][MIDI_CONTROL] = midiCC; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
@ -1,345 +0,0 @@ |
|||||||
/* |
|
||||||
* AudioEffectAnalogDelay.cpp |
|
||||||
* |
|
||||||
* Created on: Jan 7, 2018 |
|
||||||
* Author: slascos |
|
||||||
*/ |
|
||||||
#include <new> |
|
||||||
#include "AudioEffectAnalogDelayFilters.h" |
|
||||||
#include "AudioEffectAnalogDelay.h" |
|
||||||
|
|
||||||
using namespace BALibrary; |
|
||||||
|
|
||||||
#define INTERPOLATED_DELAY Uncomment this line to test the inteprolated delay which adds 1/10th of a sample |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
constexpr int MIDI_CHANNEL = 0; |
|
||||||
constexpr int MIDI_CONTROL = 1; |
|
||||||
|
|
||||||
AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(maxDelayMs); |
|
||||||
m_maxDelaySamples = calcAudioSamples(maxDelayMs); |
|
||||||
m_constructFilter(); |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(numSamples); |
|
||||||
m_maxDelaySamples = numSamples; |
|
||||||
m_constructFilter(); |
|
||||||
} |
|
||||||
|
|
||||||
// requires preallocated memory large enough |
|
||||||
AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(slot); |
|
||||||
m_maxDelaySamples = (slot->size() / sizeof(int16_t)); |
|
||||||
m_externalMemory = true; |
|
||||||
m_constructFilter(); |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectAnalogDelay::~AudioEffectAnalogDelay() |
|
||||||
{ |
|
||||||
if (m_memory) delete m_memory; |
|
||||||
if (m_iir) delete m_iir; |
|
||||||
} |
|
||||||
|
|
||||||
// This function just sets up the default filter and coefficients |
|
||||||
void AudioEffectAnalogDelay::m_constructFilter(void) |
|
||||||
{ |
|
||||||
// Use DM3 coefficients by default |
|
||||||
m_iir = new IirBiQuadFilterHQ(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift) |
|
||||||
{ |
|
||||||
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::setFilter(Filter filter) |
|
||||||
{ |
|
||||||
switch(filter) { |
|
||||||
case Filter::WARM : |
|
||||||
m_iir->changeFilterCoeffs(WARM_NUM_STAGES, reinterpret_cast<const int32_t *>(&WARM), WARM_COEFF_SHIFT); |
|
||||||
break; |
|
||||||
case Filter::DARK : |
|
||||||
m_iir->changeFilterCoeffs(DARK_NUM_STAGES, reinterpret_cast<const int32_t *>(&DARK), DARK_COEFF_SHIFT); |
|
||||||
break; |
|
||||||
case Filter::DM3 : |
|
||||||
default: |
|
||||||
m_iir->changeFilterCoeffs(DM3_NUM_STAGES, reinterpret_cast<const int32_t *>(&DM3), DM3_COEFF_SHIFT); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::update(void) |
|
||||||
{ |
|
||||||
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples |
|
||||||
|
|
||||||
// Check is block is disabled |
|
||||||
if (m_enable == false) { |
|
||||||
// do not transmit or process any audio, return as quickly as possible. |
|
||||||
if (inputAudioBlock) release(inputAudioBlock); |
|
||||||
|
|
||||||
// release all held memory resources |
|
||||||
if (m_previousBlock) { |
|
||||||
release(m_previousBlock); m_previousBlock = nullptr; |
|
||||||
} |
|
||||||
if (!m_externalMemory) { |
|
||||||
// when using internal memory we have to release all references in the ring buffer |
|
||||||
while (m_memory->getRingBuffer()->size() > 0) { |
|
||||||
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); |
|
||||||
m_memory->getRingBuffer()->pop_front(); |
|
||||||
if (releaseBlock) release(releaseBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Check is block is bypassed, if so either transmit input directly or create silence |
|
||||||
if (m_bypass == true) { |
|
||||||
// transmit the input directly |
|
||||||
if (!inputAudioBlock) { |
|
||||||
// create silence |
|
||||||
inputAudioBlock = allocate(); |
|
||||||
if (!inputAudioBlock) { return; } // failed to allocate |
|
||||||
else { |
|
||||||
clearAudioBlock(inputAudioBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
transmit(inputAudioBlock, 0); |
|
||||||
release(inputAudioBlock); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Otherwise perform normal processing |
|
||||||
// In order to make use of the SPI DMA, we need to request the read from memory first, |
|
||||||
// then do other processing while it fills in the back. |
|
||||||
audio_block_t *blockToOutput = nullptr; // this will hold the output audio |
|
||||||
blockToOutput = allocate(); |
|
||||||
if (!blockToOutput) return; // skip this update cycle due to failure |
|
||||||
|
|
||||||
// get the data. If using external memory with DMA, this won't be filled until |
|
||||||
// later. |
|
||||||
#ifdef INTERPOLATED_DELAY |
|
||||||
int16_t extendedBuffer[AUDIO_BLOCK_SAMPLES+1]; // need one more sample for intepolating between 128th and 129th (last sample) |
|
||||||
m_memory->getSamples(extendedBuffer, m_delaySamples, AUDIO_BLOCK_SAMPLES+1); |
|
||||||
#else |
|
||||||
m_memory->getSamples(blockToOutput, m_delaySamples); |
|
||||||
#endif |
|
||||||
|
|
||||||
|
|
||||||
// If using DMA, we need something else to do while that read executes, so |
|
||||||
// move on to input preprocessing |
|
||||||
|
|
||||||
// Preprocessing |
|
||||||
audio_block_t *preProcessed = allocate(); |
|
||||||
// mix the input with the feedback path in the pre-processing stage |
|
||||||
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); |
|
||||||
|
|
||||||
// consider doing the BBD post processing here to use up more time while waiting |
|
||||||
// for the read data to come back |
|
||||||
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); |
|
||||||
|
|
||||||
|
|
||||||
// BACK TO OUTPUT PROCESSING |
|
||||||
// Check if external DMA, if so, we need to be sure the read is completed |
|
||||||
if (m_externalMemory && m_memory->getSlot()->isUseDma()) { |
|
||||||
// Using DMA |
|
||||||
while (m_memory->getSlot()->isReadBusy()) {} |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef INTERPOLATED_DELAY |
|
||||||
// TODO: partial delay testing |
|
||||||
// extendedBuffer is oversized |
|
||||||
//memcpy(blockToOutput->data, &extendedBuffer[1], sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); |
|
||||||
m_memory->interpolateDelay(extendedBuffer, blockToOutput->data, 0.1f, AUDIO_BLOCK_SAMPLES); |
|
||||||
#endif |
|
||||||
|
|
||||||
// perform the wet/dry mix mix |
|
||||||
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); |
|
||||||
transmit(blockToOutput); |
|
||||||
|
|
||||||
release(inputAudioBlock); |
|
||||||
release(m_previousBlock); |
|
||||||
m_previousBlock = blockToOutput; |
|
||||||
|
|
||||||
// if (m_externalMemory && m_memory->getSlot()->isUseDma()) { |
|
||||||
// // Using DMA |
|
||||||
// if (m_blockToRelease) release(m_blockToRelease); |
|
||||||
// m_blockToRelease = blockToRelease; |
|
||||||
// } |
|
||||||
|
|
||||||
if (m_blockToRelease) release(m_blockToRelease); |
|
||||||
m_blockToRelease = blockToRelease; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::delay(float milliseconds) |
|
||||||
{ |
|
||||||
size_t delaySamples = calcAudioSamples(milliseconds); |
|
||||||
|
|
||||||
if (delaySamples > m_memory->getMaxDelaySamples()) { |
|
||||||
// this exceeds max delay value, limit it. |
|
||||||
delaySamples = m_memory->getMaxDelaySamples(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
||||||
|
|
||||||
if (!m_externalMemory) { |
|
||||||
// internal memory |
|
||||||
//QueuePosition queuePosition = calcQueuePosition(milliseconds); |
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); |
|
||||||
} else { |
|
||||||
// external memory |
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples); |
|
||||||
ExtMemSlot *slot = m_memory->getSlot(); |
|
||||||
|
|
||||||
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); } |
|
||||||
if (!slot->isEnabled()) { |
|
||||||
slot->enable(); |
|
||||||
Serial.println("WEIRD: slot was not enabled"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
m_delaySamples = delaySamples; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::delay(size_t delaySamples) |
|
||||||
{ |
|
||||||
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
||||||
|
|
||||||
if (!m_externalMemory) { |
|
||||||
// internal memory |
|
||||||
//QueuePosition queuePosition = calcQueuePosition(delaySamples); |
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); |
|
||||||
} else { |
|
||||||
// external memory |
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples); |
|
||||||
ExtMemSlot *slot = m_memory->getSlot(); |
|
||||||
if (!slot->isEnabled()) { |
|
||||||
slot->enable(); |
|
||||||
} |
|
||||||
} |
|
||||||
m_delaySamples = delaySamples; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::delayFractionMax(float delayFraction) |
|
||||||
{ |
|
||||||
size_t delaySamples = static_cast<size_t>(static_cast<float>(m_memory->getMaxDelaySamples()) * delayFraction); |
|
||||||
|
|
||||||
if (delaySamples > m_memory->getMaxDelaySamples()) { |
|
||||||
// this exceeds max delay value, limit it. |
|
||||||
delaySamples = m_memory->getMaxDelaySamples(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
||||||
|
|
||||||
if (!m_externalMemory) { |
|
||||||
// internal memory |
|
||||||
//QueuePosition queuePosition = calcQueuePosition(delaySamples); |
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); |
|
||||||
} else { |
|
||||||
// external memory |
|
||||||
//Serial.println(String("CONFIG: delay:") + delaySamples); |
|
||||||
ExtMemSlot *slot = m_memory->getSlot(); |
|
||||||
if (!slot->isEnabled()) { |
|
||||||
slot->enable(); |
|
||||||
} |
|
||||||
} |
|
||||||
m_delaySamples = delaySamples; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
|
||||||
{ |
|
||||||
if ( out && dry && wet) { |
|
||||||
alphaBlend(out, dry, wet, m_feedback); |
|
||||||
m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); |
|
||||||
} else if (dry) { |
|
||||||
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
|
||||||
{ |
|
||||||
if (!out) return; // no valid output buffer |
|
||||||
|
|
||||||
if ( out && dry && wet) { |
|
||||||
// Simulate the LPF IIR nature of the analog systems |
|
||||||
//m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); |
|
||||||
alphaBlend(out, dry, wet, m_mix); |
|
||||||
} else if (dry) { |
|
||||||
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
|
||||||
} |
|
||||||
// Set the output volume |
|
||||||
gainAdjust(out, out, m_volume, 1); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) |
|
||||||
{ |
|
||||||
|
|
||||||
float val = (float)value / 127.0f; |
|
||||||
|
|
||||||
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) { |
|
||||||
// Delay |
|
||||||
if (m_externalMemory) { m_maxDelaySamples = m_memory->getSlot()->size() / sizeof(int16_t); } |
|
||||||
size_t delayVal = (size_t)(val * (float)m_maxDelaySamples); |
|
||||||
delay(delayVal); |
|
||||||
Serial.println(String("AudioEffectAnalogDelay::delay (ms): ") + calcAudioTimeMs(delayVal) |
|
||||||
+ String(" (samples): ") + delayVal + String(" out of ") + m_maxDelaySamples); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { |
|
||||||
// Bypass |
|
||||||
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); } |
|
||||||
else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); } |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { |
|
||||||
// Feedback |
|
||||||
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%")); |
|
||||||
feedback(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[MIX][MIDI_CONTROL] == control)) { |
|
||||||
// Mix |
|
||||||
Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val ); |
|
||||||
mix(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
|
||||||
// Volume |
|
||||||
Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%")); |
|
||||||
volume(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel) |
|
||||||
{ |
|
||||||
if (parameter >= NUM_CONTROLS) { |
|
||||||
return ; // Invalid midi parameter |
|
||||||
} |
|
||||||
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; |
|
||||||
m_midiConfig[parameter][MIDI_CONTROL] = midiCC; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
@ -1,294 +0,0 @@ |
|||||||
/*
|
|
||||||
* BAAudioEffectDelayExternal.cpp |
|
||||||
* |
|
||||||
* Created on: November 1, 2017 |
|
||||||
* Author: slascos |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#include "BAAudioEffectDelayExternal.h" |
|
||||||
|
|
||||||
using namespace BALibrary; |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
#define SPISETTING SPISettings(20000000, MSBFIRST, SPI_MODE0) |
|
||||||
|
|
||||||
struct MemSpiConfig { |
|
||||||
unsigned mosiPin; |
|
||||||
unsigned misoPin; |
|
||||||
unsigned sckPin; |
|
||||||
unsigned csPin; |
|
||||||
unsigned memSize; |
|
||||||
}; |
|
||||||
|
|
||||||
constexpr MemSpiConfig Mem0Config = {7, 8, 14, 15, 65536 }; |
|
||||||
constexpr MemSpiConfig Mem1Config = {21, 5, 20, 31, 65536 }; |
|
||||||
|
|
||||||
unsigned BAAudioEffectDelayExternal::m_usingSPICount[2] = {0,0}; |
|
||||||
|
|
||||||
BAAudioEffectDelayExternal::BAAudioEffectDelayExternal() |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
initialize(MemSelect::MEM0); |
|
||||||
} |
|
||||||
|
|
||||||
BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(MemSelect mem) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
initialize(mem); |
|
||||||
} |
|
||||||
|
|
||||||
BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
unsigned delayLengthInt = (delayLengthMs*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f; |
|
||||||
initialize(type, delayLengthInt); |
|
||||||
} |
|
||||||
|
|
||||||
BAAudioEffectDelayExternal::~BAAudioEffectDelayExternal() |
|
||||||
{ |
|
||||||
if (m_spi) delete m_spi; |
|
||||||
} |
|
||||||
|
|
||||||
void BAAudioEffectDelayExternal::delay(uint8_t channel, float milliseconds) { |
|
||||||
|
|
||||||
if (channel >= 8) return; |
|
||||||
if (milliseconds < 0.0) milliseconds = 0.0; |
|
||||||
uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f; |
|
||||||
n += AUDIO_BLOCK_SAMPLES; |
|
||||||
if (n > m_memoryLength - AUDIO_BLOCK_SAMPLES) |
|
||||||
n = m_memoryLength - AUDIO_BLOCK_SAMPLES; |
|
||||||
m_channelDelayLength[channel] = n; |
|
||||||
unsigned mask = m_activeMask; |
|
||||||
if (m_activeMask == 0) m_startUsingSPI(m_spiChannel); |
|
||||||
m_activeMask = mask | (1<<channel); |
|
||||||
} |
|
||||||
|
|
||||||
void BAAudioEffectDelayExternal::disable(uint8_t channel) { |
|
||||||
if (channel >= 8) return; |
|
||||||
uint8_t mask = m_activeMask & ~(1<<channel); |
|
||||||
m_activeMask = mask; |
|
||||||
if (mask == 0) m_stopUsingSPI(m_spiChannel); |
|
||||||
} |
|
||||||
|
|
||||||
void BAAudioEffectDelayExternal::update(void) |
|
||||||
{ |
|
||||||
audio_block_t *block; |
|
||||||
uint32_t n, channel, read_offset; |
|
||||||
|
|
||||||
// grab incoming data and put it into the memory
|
|
||||||
block = receiveReadOnly(); |
|
||||||
|
|
||||||
if (block) { |
|
||||||
if (m_headOffset + AUDIO_BLOCK_SAMPLES <= m_memoryLength) { |
|
||||||
// a single write is enough
|
|
||||||
write(m_headOffset, AUDIO_BLOCK_SAMPLES, block->data); |
|
||||||
m_headOffset += AUDIO_BLOCK_SAMPLES; |
|
||||||
} else { |
|
||||||
// write wraps across end-of-memory
|
|
||||||
n = m_memoryLength - m_headOffset; |
|
||||||
write(m_headOffset, n, block->data); |
|
||||||
m_headOffset = AUDIO_BLOCK_SAMPLES - n; |
|
||||||
write(0, m_headOffset, block->data + n); |
|
||||||
} |
|
||||||
release(block); |
|
||||||
} else { |
|
||||||
// if no input, store zeros, so later playback will
|
|
||||||
// not be random garbage previously stored in memory
|
|
||||||
if (m_headOffset + AUDIO_BLOCK_SAMPLES <= m_memoryLength) { |
|
||||||
zero(m_headOffset, AUDIO_BLOCK_SAMPLES); |
|
||||||
m_headOffset += AUDIO_BLOCK_SAMPLES; |
|
||||||
} else { |
|
||||||
n = m_memoryLength - m_headOffset; |
|
||||||
zero(m_headOffset, n); |
|
||||||
m_headOffset = AUDIO_BLOCK_SAMPLES - n; |
|
||||||
zero(0, m_headOffset); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// transmit the delayed outputs
|
|
||||||
for (channel = 0; channel < 8; channel++) { |
|
||||||
if (!(m_activeMask & (1<<channel))) continue; |
|
||||||
block = allocate(); |
|
||||||
if (!block) continue; |
|
||||||
// compute the delayed location where we read
|
|
||||||
if (m_channelDelayLength[channel] <= m_headOffset) { |
|
||||||
read_offset = m_headOffset - m_channelDelayLength[channel]; |
|
||||||
} else { |
|
||||||
read_offset = m_memoryLength + m_headOffset - m_channelDelayLength[channel]; |
|
||||||
} |
|
||||||
if (read_offset + AUDIO_BLOCK_SAMPLES <= m_memoryLength) { |
|
||||||
// a single read will do it
|
|
||||||
read(read_offset, AUDIO_BLOCK_SAMPLES, block->data); |
|
||||||
} else { |
|
||||||
// read wraps across end-of-memory
|
|
||||||
n = m_memoryLength - read_offset; |
|
||||||
read(read_offset, n, block->data); |
|
||||||
read(0, AUDIO_BLOCK_SAMPLES - n, block->data + n); |
|
||||||
} |
|
||||||
transmit(block, channel); |
|
||||||
release(block); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
unsigned BAAudioEffectDelayExternal::m_allocated[2] = {0, 0}; |
|
||||||
|
|
||||||
void BAAudioEffectDelayExternal::initialize(MemSelect mem, unsigned delayLength) |
|
||||||
{ |
|
||||||
unsigned samples = 0; |
|
||||||
unsigned memsize, avail; |
|
||||||
|
|
||||||
m_activeMask = 0; |
|
||||||
m_headOffset = 0; |
|
||||||
m_mem = mem; |
|
||||||
|
|
||||||
switch (mem) { |
|
||||||
case MemSelect::MEM0 : |
|
||||||
{ |
|
||||||
memsize = Mem0Config.memSize; |
|
||||||
m_spi = &SPI; |
|
||||||
m_spiChannel = 0; |
|
||||||
m_misoPin = Mem0Config.misoPin; |
|
||||||
m_mosiPin = Mem0Config.mosiPin; |
|
||||||
m_sckPin = Mem0Config.sckPin; |
|
||||||
m_csPin = Mem0Config.csPin; |
|
||||||
|
|
||||||
m_spi->setMOSI(m_mosiPin); |
|
||||||
m_spi->setMISO(m_misoPin); |
|
||||||
m_spi->setSCK(m_sckPin); |
|
||||||
m_spi->begin(); |
|
||||||
break; |
|
||||||
} |
|
||||||
case MemSelect::MEM1 : |
|
||||||
{ |
|
||||||
#if defined(__MK64FX512__) || defined(__MK66FX1M0__) |
|
||||||
memsize = Mem1Config.memSize; |
|
||||||
m_spi = &SPI1; |
|
||||||
m_spiChannel = 1; |
|
||||||
m_misoPin = Mem1Config.misoPin; |
|
||||||
m_mosiPin = Mem1Config.mosiPin; |
|
||||||
m_sckPin = Mem1Config.sckPin; |
|
||||||
m_csPin = Mem1Config.csPin; |
|
||||||
|
|
||||||
m_spi->setMOSI(m_mosiPin); |
|
||||||
m_spi->setMISO(m_misoPin); |
|
||||||
m_spi->setSCK(m_sckPin); |
|
||||||
m_spi->begin(); |
|
||||||
#endif |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
pinMode(m_csPin, OUTPUT); |
|
||||||
digitalWriteFast(m_csPin, HIGH); |
|
||||||
|
|
||||||
avail = memsize - m_allocated[mem]; |
|
||||||
|
|
||||||
if (delayLength > avail) samples = avail; |
|
||||||
m_memoryStart = m_allocated[mem]; |
|
||||||
m_allocated[mem] += samples; |
|
||||||
m_memoryLength = samples; |
|
||||||
|
|
||||||
zero(0, m_memoryLength); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BAAudioEffectDelayExternal::read(uint32_t offset, uint32_t count, int16_t *data) |
|
||||||
{ |
|
||||||
uint32_t addr = m_memoryStart + offset; |
|
||||||
addr *= 2; |
|
||||||
|
|
||||||
m_spi->beginTransaction(SPISETTING); |
|
||||||
digitalWriteFast(m_csPin, LOW); |
|
||||||
m_spi->transfer16((0x03 << 8) | (addr >> 16)); |
|
||||||
m_spi->transfer16(addr & 0xFFFF); |
|
||||||
|
|
||||||
while (count) { |
|
||||||
*data++ = (int16_t)(m_spi->transfer16(0)); |
|
||||||
count--; |
|
||||||
} |
|
||||||
digitalWriteFast(m_csPin, HIGH); |
|
||||||
m_spi->endTransaction(); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
void BAAudioEffectDelayExternal::write(uint32_t offset, uint32_t count, const int16_t *data) |
|
||||||
{ |
|
||||||
uint32_t addr = m_memoryStart + offset; |
|
||||||
|
|
||||||
addr *= 2; |
|
||||||
m_spi->beginTransaction(SPISETTING); |
|
||||||
digitalWriteFast(m_csPin, LOW); |
|
||||||
m_spi->transfer16((0x02 << 8) | (addr >> 16)); |
|
||||||
m_spi->transfer16(addr & 0xFFFF); |
|
||||||
while (count) { |
|
||||||
int16_t w = 0; |
|
||||||
if (data) w = *data++; |
|
||||||
m_spi->transfer16(w); |
|
||||||
count--; |
|
||||||
} |
|
||||||
digitalWriteFast(m_csPin, HIGH); |
|
||||||
m_spi->endTransaction(); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
|
||||||
// PRIVATE METHODS
|
|
||||||
///////////////////////////////////////////////////////////////////
|
|
||||||
void BAAudioEffectDelayExternal::zero(uint32_t address, uint32_t count) { |
|
||||||
write(address, count, NULL); |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef SPI_HAS_NOTUSINGINTERRUPT |
|
||||||
inline void BAAudioEffectDelayExternal::m_startUsingSPI(int spiBus) { |
|
||||||
if (spiBus == 0) { |
|
||||||
m_spi->usingInterrupt(IRQ_SOFTWARE); |
|
||||||
} else if (spiBus == 1) { |
|
||||||
m_spi->usingInterrupt(IRQ_SOFTWARE); |
|
||||||
} |
|
||||||
m_usingSPICount[spiBus]++; |
|
||||||
} |
|
||||||
|
|
||||||
inline void BAAudioEffectDelayExternal::m_stopUsingSPI(int spiBus) { |
|
||||||
if (m_usingSPICount[spiBus] == 0 || --m_usingSPICount[spiBus] == 0) |
|
||||||
{ |
|
||||||
if (spiBus == 0) { |
|
||||||
m_spi->notUsingInterrupt(IRQ_SOFTWARE); |
|
||||||
} else if (spiBus == 1) { |
|
||||||
m_spi->notUsingInterrupt(IRQ_SOFTWARE); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#else |
|
||||||
inline void BAAudioEffectDelayExternal::m_startUsingSPI(int spiBus) { |
|
||||||
if (spiBus == 0) { |
|
||||||
m_spi->usingInterrupt(IRQ_SOFTWARE); |
|
||||||
} else if (spiBus == 1) { |
|
||||||
m_spi->usingInterrupt(IRQ_SOFTWARE); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
inline void BAAudioEffectDelayExternal::m_stopUsingSPI(int spiBus) { |
|
||||||
} |
|
||||||
|
|
||||||
#endif |
|
||||||
|
|
||||||
|
|
||||||
} /* namespace BAEffects */ |
|
@ -1,304 +0,0 @@ |
|||||||
/*
|
|
||||||
* AudioEffectSOS.cpp |
|
||||||
* |
|
||||||
* Created on: Apr 14, 2018 |
|
||||||
* Author: blackaddr |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "AudioEffectSOS.h" |
|
||||||
#include "LibBasicFunctions.h" |
|
||||||
|
|
||||||
using namespace BALibrary; |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
constexpr int MIDI_CHANNEL = 0; |
|
||||||
constexpr int MIDI_CONTROL = 1; |
|
||||||
|
|
||||||
constexpr float MAX_GATE_OPEN_TIME_MS = 3000.0f; |
|
||||||
constexpr float MAX_GATE_CLOSE_TIME_MS = 1000.0f; |
|
||||||
|
|
||||||
constexpr int GATE_OPEN_STAGE = 0; |
|
||||||
constexpr int GATE_HOLD_STAGE = 1; |
|
||||||
constexpr int GATE_CLOSE_STAGE = 2; |
|
||||||
|
|
||||||
AudioEffectSOS::AudioEffectSOS(float maxDelayMs) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(maxDelayMs); |
|
||||||
m_maxDelaySamples = calcAudioSamples(maxDelayMs); |
|
||||||
m_externalMemory = false; |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectSOS::AudioEffectSOS(size_t numSamples) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(numSamples); |
|
||||||
m_maxDelaySamples = numSamples; |
|
||||||
m_externalMemory = false; |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectSOS::AudioEffectSOS(ExtMemSlot *slot) |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_memory = new AudioDelay(slot); |
|
||||||
m_externalMemory = true; |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectSOS::~AudioEffectSOS() |
|
||||||
{ |
|
||||||
if (m_memory) delete m_memory; |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectSOS::setGateLedGpio(int pinId) |
|
||||||
{ |
|
||||||
m_gateLedPinId = pinId; |
|
||||||
pinMode(static_cast<uint8_t>(m_gateLedPinId), OUTPUT); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectSOS::enable(void) |
|
||||||
{ |
|
||||||
m_enable = true; |
|
||||||
if (m_externalMemory) { |
|
||||||
// Because we hold the previous output buffer for an update cycle, the maximum delay is actually
|
|
||||||
// 1 audio block mess then the max delay returnable from the memory.
|
|
||||||
m_maxDelaySamples = m_memory->getMaxDelaySamples(); |
|
||||||
Serial.println(String("SOS Enabled with delay length ") + m_maxDelaySamples + String(" samples")); |
|
||||||
} |
|
||||||
m_delaySamples = m_maxDelaySamples; |
|
||||||
m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL); |
|
||||||
m_inputGateAuto.setupParameter(GATE_HOLD_STAGE, 1.0f, 1.0f, m_maxDelaySamples, ParameterAutomation<float>::Function::HOLD); |
|
||||||
m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL); |
|
||||||
|
|
||||||
m_clearFeedbackAuto.setupParameter(GATE_OPEN_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL); |
|
||||||
m_clearFeedbackAuto.setupParameter(GATE_HOLD_STAGE, 0.0f, 0.0f, m_maxDelaySamples, ParameterAutomation<float>::Function::HOLD); |
|
||||||
m_clearFeedbackAuto.setupParameter(GATE_CLOSE_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation<float>::Function::EXPONENTIAL); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectSOS::update(void) |
|
||||||
{ |
|
||||||
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
|
|
||||||
|
|
||||||
// Check is block is disabled
|
|
||||||
if (m_enable == false) { |
|
||||||
// do not transmit or process any audio, return as quickly as possible.
|
|
||||||
if (inputAudioBlock) release(inputAudioBlock); |
|
||||||
|
|
||||||
// release all held memory resources
|
|
||||||
if (m_previousBlock) { |
|
||||||
release(m_previousBlock); m_previousBlock = nullptr; |
|
||||||
} |
|
||||||
if (!m_externalMemory) { |
|
||||||
// when using internal memory we have to release all references in the ring buffer
|
|
||||||
while (m_memory->getRingBuffer()->size() > 0) { |
|
||||||
audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); |
|
||||||
m_memory->getRingBuffer()->pop_front(); |
|
||||||
if (releaseBlock) release(releaseBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Check is block is bypassed, if so either transmit input directly or create silence
|
|
||||||
if ( (m_bypass == true) || (!inputAudioBlock) ) { |
|
||||||
// transmit the input directly
|
|
||||||
if (!inputAudioBlock) { |
|
||||||
// create silence
|
|
||||||
inputAudioBlock = allocate(); |
|
||||||
if (!inputAudioBlock) { return; } // failed to allocate
|
|
||||||
else { |
|
||||||
clearAudioBlock(inputAudioBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
transmit(inputAudioBlock, 0); |
|
||||||
release(inputAudioBlock); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (!inputAudioBlock) return; |
|
||||||
|
|
||||||
// Otherwise perform normal processing
|
|
||||||
// In order to make use of the SPI DMA, we need to request the read from memory first,
|
|
||||||
// then do other processing while it fills in the back.
|
|
||||||
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
|
|
||||||
blockToOutput = allocate(); |
|
||||||
if (!blockToOutput) return; // skip this update cycle due to failure
|
|
||||||
|
|
||||||
// get the data. If using external memory with DMA, this won't be filled until
|
|
||||||
// later.
|
|
||||||
m_memory->getSamples(blockToOutput, m_delaySamples); |
|
||||||
//Serial.println(String("Delay samples:") + m_delaySamples);
|
|
||||||
//Serial.println(String("Use dma: ") + m_memory->getSlot()->isUseDma());
|
|
||||||
|
|
||||||
// If using DMA, we need something else to do while that read executes, so
|
|
||||||
// move on to input preprocessing
|
|
||||||
|
|
||||||
// Preprocessing
|
|
||||||
audio_block_t *preProcessed = allocate(); |
|
||||||
// mix the input with the feedback path in the pre-processing stage
|
|
||||||
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); |
|
||||||
|
|
||||||
// consider doing the BBD post processing here to use up more time while waiting
|
|
||||||
// for the read data to come back
|
|
||||||
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); |
|
||||||
//audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock);
|
|
||||||
//Serial.println("Done adding new block");
|
|
||||||
|
|
||||||
|
|
||||||
// BACK TO OUTPUT PROCESSING
|
|
||||||
// Check if external DMA, if so, we need to be sure the read is completed
|
|
||||||
if (m_externalMemory && m_memory->getSlot()->isUseDma()) { |
|
||||||
// Using DMA
|
|
||||||
while (m_memory->getSlot()->isReadBusy()) {} |
|
||||||
} |
|
||||||
|
|
||||||
// perform the wet/dry mix mix
|
|
||||||
m_postProcessing(blockToOutput, blockToOutput); |
|
||||||
transmit(blockToOutput); |
|
||||||
|
|
||||||
release(inputAudioBlock); |
|
||||||
|
|
||||||
if (m_previousBlock) |
|
||||||
release(m_previousBlock); |
|
||||||
m_previousBlock = blockToOutput; |
|
||||||
|
|
||||||
if (m_blockToRelease == m_previousBlock) { |
|
||||||
Serial.println("ERROR: POINTER COLLISION"); |
|
||||||
} |
|
||||||
|
|
||||||
if (m_blockToRelease) release(m_blockToRelease); |
|
||||||
m_blockToRelease = blockToRelease; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void AudioEffectSOS::gateOpenTime(float milliseconds) |
|
||||||
{ |
|
||||||
// TODO - change the paramter automation to an automation sequence
|
|
||||||
m_openTimeMs = milliseconds; |
|
||||||
m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, m_openTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL); |
|
||||||
//m_clearFeedbackAuto.setupParameter(GATE_OPEN_STAGE, 1.0f, 0.0f, m_openTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL);
|
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectSOS::gateCloseTime(float milliseconds) |
|
||||||
{ |
|
||||||
m_closeTimeMs = milliseconds; |
|
||||||
m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, m_closeTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL); |
|
||||||
//m_clearFeedbackAuto.setupParameter(GATE_CLOSE_STAGE, 0.0f, 1.0f, m_closeTimeMs, ParameterAutomation<float>::Function::EXPONENTIAL);
|
|
||||||
} |
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
// MIDI PROCESSING
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
void AudioEffectSOS::processMidi(int channel, int control, int value) |
|
||||||
{ |
|
||||||
|
|
||||||
float val = (float)value / 127.0f; |
|
||||||
|
|
||||||
if ((m_midiConfig[GATE_OPEN_TIME][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[GATE_OPEN_TIME][MIDI_CONTROL] == control)) { |
|
||||||
// Gate Open Time
|
|
||||||
gateOpenTime(val * MAX_GATE_OPEN_TIME_MS); |
|
||||||
Serial.println(String("AudioEffectSOS::gate open time (ms): ") + m_openTimeMs); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[GATE_CLOSE_TIME][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[GATE_CLOSE_TIME][MIDI_CONTROL] == control)) { |
|
||||||
// Gate Close Time
|
|
||||||
gateCloseTime(val * MAX_GATE_CLOSE_TIME_MS); |
|
||||||
Serial.println(String("AudioEffectSOS::gate close time (ms): ") + m_closeTimeMs); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { |
|
||||||
// Feedback
|
|
||||||
Serial.println(String("AudioEffectSOS::feedback: ") + 100*val + String("%")); |
|
||||||
feedback(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
|
||||||
// Volume
|
|
||||||
Serial.println(String("AudioEffectSOS::volume: ") + 100*val + String("%")); |
|
||||||
volume(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { |
|
||||||
// Bypass
|
|
||||||
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectSOS::not bypassed -> ON") + value); } |
|
||||||
else { bypass(true); Serial.println(String("AudioEffectSOS::bypassed -> OFF") + value); } |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[GATE_TRIGGER][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[GATE_TRIGGER][MIDI_CONTROL] == control)) { |
|
||||||
// The gate is triggered by any value
|
|
||||||
Serial.println(String("AudioEffectSOS::Gate Triggered!")); |
|
||||||
m_inputGateAuto.trigger(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[CLEAR_FEEDBACK_TRIGGER][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[CLEAR_FEEDBACK_TRIGGER][MIDI_CONTROL] == control)) { |
|
||||||
// The gate is triggered by any value
|
|
||||||
Serial.println(String("AudioEffectSOS::Clear feedback Triggered!")); |
|
||||||
m_clearFeedbackAuto.trigger(); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectSOS::mapMidiControl(int parameter, int midiCC, int midiChannel) |
|
||||||
{ |
|
||||||
if (parameter >= NUM_CONTROLS) { |
|
||||||
return ; // Invalid midi parameter
|
|
||||||
} |
|
||||||
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; |
|
||||||
m_midiConfig[parameter][MIDI_CONTROL] = midiCC; |
|
||||||
} |
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
// PRIVATE FUNCTIONS
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
void AudioEffectSOS::m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal) |
|
||||||
{ |
|
||||||
if ( out && input && delayedSignal) { |
|
||||||
// Multiply the input signal by the automated gate value
|
|
||||||
// Multiply the delayed signal by the user set feedback value
|
|
||||||
// Then combine the two
|
|
||||||
|
|
||||||
float gateVol = m_inputGateAuto.getNextValue(); |
|
||||||
float feedbackAdjust = m_clearFeedbackAuto.getNextValue(); |
|
||||||
audio_block_t tempAudioBuffer; |
|
||||||
|
|
||||||
gainAdjust(out, input, gateVol, 0); // last paremeter is coeff shift, 0 bits
|
|
||||||
gainAdjust(&tempAudioBuffer, delayedSignal, m_feedback*feedbackAdjust, 0); // last parameter is coeff shift, 0 bits
|
|
||||||
combine(out, out, &tempAudioBuffer); |
|
||||||
|
|
||||||
} else if (input) { |
|
||||||
memcpy(out->data, input->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
|
||||||
} |
|
||||||
|
|
||||||
// Update the gate LED
|
|
||||||
if (m_gateLedPinId >= 0) { |
|
||||||
if (m_inputGateAuto.isFinished() && m_clearFeedbackAuto.isFinished()) { |
|
||||||
digitalWriteFast(m_gateLedPinId, 0x0); |
|
||||||
} else { |
|
||||||
digitalWriteFast(m_gateLedPinId, 0x1); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectSOS::m_postProcessing(audio_block_t *out, audio_block_t *in) |
|
||||||
{ |
|
||||||
gainAdjust(out, out, m_volume, 0); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} // namespace BAEffects
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,163 +0,0 @@ |
|||||||
/*
|
|
||||||
* AudioEffectTremolo.cpp |
|
||||||
* |
|
||||||
* Created on: Jan 7, 2018 |
|
||||||
* Author: slascos |
|
||||||
*/ |
|
||||||
#include <cmath> // std::roundf |
|
||||||
#include "AudioEffectTremolo.h" |
|
||||||
|
|
||||||
using namespace BALibrary; |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
constexpr int MIDI_CHANNEL = 0; |
|
||||||
constexpr int MIDI_CONTROL = 1; |
|
||||||
|
|
||||||
constexpr float MAX_RATE_HZ = 20.0f; |
|
||||||
|
|
||||||
AudioEffectTremolo::AudioEffectTremolo() |
|
||||||
: AudioStream(1, m_inputQueueArray) |
|
||||||
{ |
|
||||||
m_osc.setWaveform(m_waveform); |
|
||||||
} |
|
||||||
|
|
||||||
AudioEffectTremolo::~AudioEffectTremolo() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectTremolo::update(void) |
|
||||||
{ |
|
||||||
audio_block_t *inputAudioBlock = receiveWritable(); // get the next block of input samples
|
|
||||||
|
|
||||||
// Check is block is disabled
|
|
||||||
if (m_enable == false) { |
|
||||||
// do not transmit or process any audio, return as quickly as possible.
|
|
||||||
if (inputAudioBlock) release(inputAudioBlock); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Check is block is bypassed, if so either transmit input directly or create silence
|
|
||||||
if (m_bypass == true) { |
|
||||||
// transmit the input directly
|
|
||||||
if (!inputAudioBlock) { |
|
||||||
// create silence
|
|
||||||
inputAudioBlock = allocate(); |
|
||||||
if (!inputAudioBlock) { return; } // failed to allocate
|
|
||||||
else { |
|
||||||
clearAudioBlock(inputAudioBlock); |
|
||||||
} |
|
||||||
} |
|
||||||
transmit(inputAudioBlock, 0); |
|
||||||
release(inputAudioBlock); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// DO PROCESSING
|
|
||||||
// apply modulation wave
|
|
||||||
float *mod = m_osc.getNextVector(); |
|
||||||
for (auto i=0; i<AUDIO_BLOCK_SAMPLES; i++) { |
|
||||||
mod[i] = (mod[i] + 1.0f) / 2.0f; |
|
||||||
mod[i] = (1.0f - m_depth) + mod[i]*m_depth; |
|
||||||
mod[i] = m_volume * mod[i]; |
|
||||||
float sample = std::roundf(mod[i] * (float)inputAudioBlock->data[i]); |
|
||||||
inputAudioBlock->data[i] = (int16_t)sample; |
|
||||||
} |
|
||||||
//Serial.println(String("mod: ") + mod[0]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//float mod = (m_osc.getNext()+1.0f)/2.0f; // value between -1.0 and +1.0f
|
|
||||||
//float modVolume = (1.0f - m_depth) + mod*m_depth; // value between 0 to depth
|
|
||||||
//float finalVolume = m_volume * modVolume;
|
|
||||||
|
|
||||||
// Set the output volume
|
|
||||||
//gainAdjust(inputAudioBlock, inputAudioBlock, finalVolume, 1);
|
|
||||||
|
|
||||||
transmit(inputAudioBlock); |
|
||||||
release(inputAudioBlock); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectTremolo::rate(float rateValue) |
|
||||||
{ |
|
||||||
float rateAudioBlock = rateValue * MAX_RATE_HZ; |
|
||||||
m_osc.setRateAudio(rateAudioBlock); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectTremolo::setWaveform(Waveform waveform) |
|
||||||
{ |
|
||||||
m_waveform = waveform; |
|
||||||
m_osc.setWaveform(waveform); |
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectTremolo::processMidi(int channel, int control, int value) |
|
||||||
{ |
|
||||||
|
|
||||||
float val = (float)value / 127.0f; |
|
||||||
|
|
||||||
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { |
|
||||||
// Bypass
|
|
||||||
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectTremolo::not bypassed -> ON") + value); } |
|
||||||
else { bypass(true); Serial.println(String("AudioEffectTremolo::bypassed -> OFF") + value); } |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[RATE][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[RATE][MIDI_CONTROL] == control)) { |
|
||||||
// Rate
|
|
||||||
rate(val); |
|
||||||
Serial.println(String("AudioEffectTremolo::rate: ") + m_rate); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[DEPTH][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[DEPTH][MIDI_CONTROL] == control)) { |
|
||||||
// Depth
|
|
||||||
depth(val); |
|
||||||
Serial.println(String("AudioEffectTremolo::depth: ") + m_depth); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[WAVEFORM][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[WAVEFORM][MIDI_CONTROL] == control)) { |
|
||||||
// Waveform
|
|
||||||
if (value < 16) { |
|
||||||
m_waveform = Waveform::SINE; |
|
||||||
} else if (value < 32) { |
|
||||||
m_waveform = Waveform::TRIANGLE; |
|
||||||
} else if (value < 48) { |
|
||||||
m_waveform = Waveform::SQUARE; |
|
||||||
} else if (value < 64) { |
|
||||||
m_waveform = Waveform::SAWTOOTH; |
|
||||||
} else if (value < 80) { |
|
||||||
m_waveform = Waveform::RANDOM; |
|
||||||
} |
|
||||||
|
|
||||||
Serial.println(String("AudioEffectTremolo::waveform: ") + static_cast<unsigned>(m_waveform)); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
|
||||||
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
|
||||||
// Volume
|
|
||||||
Serial.println(String("AudioEffectTremolo::volume: ") + 100*val + String("%")); |
|
||||||
volume(val); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
void AudioEffectTremolo::mapMidiControl(int parameter, int midiCC, int midiChannel) |
|
||||||
{ |
|
||||||
if (parameter >= NUM_CONTROLS) { |
|
||||||
return ; // Invalid midi parameter
|
|
||||||
} |
|
||||||
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; |
|
||||||
m_midiConfig[parameter][MIDI_CONTROL] = midiCC; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,383 +0,0 @@ |
|||||||
/*
|
|
||||||
* BAAudioControlWM8731.cpp |
|
||||||
* |
|
||||||
* Created on: May 22, 2017 |
|
||||||
* Author: slascos |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#include <Wire.h> |
|
||||||
#include "BAAudioControlWM8731.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
// use const instead of define for proper scoping
|
|
||||||
constexpr int WM8731_I2C_ADDR = 0x1A; |
|
||||||
|
|
||||||
// The WM8731 register map
|
|
||||||
constexpr int WM8731_REG_LLINEIN = 0; |
|
||||||
constexpr int WM8731_REG_RLINEIN = 1; |
|
||||||
constexpr int WM8731_REG_LHEADOUT = 2; |
|
||||||
constexpr int WM8731_REG_RHEADOUT = 3; |
|
||||||
constexpr int WM8731_REG_ANALOG =4; |
|
||||||
constexpr int WM8731_REG_DIGITAL = 5; |
|
||||||
constexpr int WM8731_REG_POWERDOWN = 6; |
|
||||||
constexpr int WM8731_REG_INTERFACE = 7; |
|
||||||
constexpr int WM8731_REG_SAMPLING = 8; |
|
||||||
constexpr int WM8731_REG_ACTIVE = 9; |
|
||||||
constexpr int WM8731_REG_RESET = 15; |
|
||||||
|
|
||||||
// Register Masks and Shifts
|
|
||||||
// Register 0
|
|
||||||
constexpr int WM8731_LEFT_INPUT_GAIN_ADDR = 0; |
|
||||||
constexpr int WM8731_LEFT_INPUT_GAIN_MASK = 0x1F; |
|
||||||
constexpr int WM8731_LEFT_INPUT_GAIN_SHIFT = 0; |
|
||||||
constexpr int WM8731_LEFT_INPUT_MUTE_ADDR = 0; |
|
||||||
constexpr int WM8731_LEFT_INPUT_MUTE_MASK = 0x80; |
|
||||||
constexpr int WM8731_LEFT_INPUT_MUTE_SHIFT = 7; |
|
||||||
constexpr int WM8731_LINK_LEFT_RIGHT_IN_ADDR = 0; |
|
||||||
constexpr int WM8731_LINK_LEFT_RIGHT_IN_MASK = 0x100; |
|
||||||
constexpr int WM8731_LINK_LEFT_RIGHT_IN_SHIFT = 8; |
|
||||||
// Register 1
|
|
||||||
constexpr int WM8731_RIGHT_INPUT_GAIN_ADDR = 1; |
|
||||||
constexpr int WM8731_RIGHT_INPUT_GAIN_MASK = 0x1F; |
|
||||||
constexpr int WM8731_RIGHT_INPUT_GAIN_SHIFT = 0; |
|
||||||
constexpr int WM8731_RIGHT_INPUT_MUTE_ADDR = 1; |
|
||||||
constexpr int WM8731_RIGHT_INPUT_MUTE_MASK = 0x80; |
|
||||||
constexpr int WM8731_RIGHT_INPUT_MUTE_SHIFT = 7; |
|
||||||
constexpr int WM8731_LINK_RIGHT_LEFT_IN_ADDR = 1; |
|
||||||
constexpr int WM8731_LINK_RIGHT_LEFT_IN_MASK = 0x100; |
|
||||||
constexpr int WM8731_LINK_RIGHT_LEFT_IN_SHIFT = 8; |
|
||||||
// Register 2
|
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_VOL_ADDR = 2; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_VOL_MASK = 0x7F; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_VOL_SHIFT = 0; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_ZCD_ADDR = 2; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_ZCD_MASK = 0x80; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_ZCD_SHIFT = 7; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_LINK_ADDR = 2; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_LINK_MASK = 0x100; |
|
||||||
constexpr int WM8731_LEFT_HEADPHONE_LINK_SHIFT = 8; |
|
||||||
// Register 3
|
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_VOL_ADDR = 3; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_VOL_MASK = 0x7F; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_VOL_SHIFT = 0; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_ZCD_ADDR = 3; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_ZCD_MASK = 0x80; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_ZCD_SHIFT = 7; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_LINK_ADDR = 3; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_LINK_MASK = 0x100; |
|
||||||
constexpr int WM8731_RIGHT_HEADPHONE_LINK_SHIFT = 8; |
|
||||||
// Register 4
|
|
||||||
constexpr int WM8731_ADC_BYPASS_ADDR = 4; |
|
||||||
constexpr int WM8731_ADC_BYPASS_MASK = 0x8; |
|
||||||
constexpr int WM8731_ADC_BYPASS_SHIFT = 3; |
|
||||||
constexpr int WM8731_DAC_SELECT_ADDR = 4; |
|
||||||
constexpr int WM8731_DAC_SELECT_MASK = 0x10; |
|
||||||
constexpr int WM8731_DAC_SELECT_SHIFT = 4; |
|
||||||
// Register 5
|
|
||||||
constexpr int WM8731_DAC_MUTE_ADDR = 5; |
|
||||||
constexpr int WM8731_DAC_MUTE_MASK = 0x8; |
|
||||||
constexpr int WM8731_DAC_MUTE_SHIFT = 3; |
|
||||||
constexpr int WM8731_HPF_DISABLE_ADDR = 5; |
|
||||||
constexpr int WM8731_HPF_DISABLE_MASK = 0x1; |
|
||||||
constexpr int WM8731_HPF_DISABLE_SHIFT = 0; |
|
||||||
// Register 7
|
|
||||||
constexpr int WM8731_LRSWAP_ADDR = 5; |
|
||||||
constexpr int WM8731_LRSWAP_MASK = 0x20; |
|
||||||
constexpr int WM8731_LRSWAPE_SHIFT = 5; |
|
||||||
|
|
||||||
// Register 9
|
|
||||||
constexpr int WM8731_ACTIVATE_ADDR = 9; |
|
||||||
constexpr int WM8731_ACTIVATE_MASK = 0x1; |
|
||||||
|
|
||||||
|
|
||||||
// Reset the internal shadow register array to match
|
|
||||||
// the reset state of the codec.
|
|
||||||
void BAAudioControlWM8731::resetInternalReg(void) { |
|
||||||
// Set to reset state
|
|
||||||
regArray[0] = 0x97; |
|
||||||
regArray[1] = 0x97; |
|
||||||
regArray[2] = 0x79; |
|
||||||
regArray[3] = 0x79; |
|
||||||
regArray[4] = 0x0a; |
|
||||||
regArray[5] = 0x8; |
|
||||||
regArray[6] = 0x9f; |
|
||||||
regArray[7] = 0xa; |
|
||||||
regArray[8] = 0; |
|
||||||
regArray[9] = 0; |
|
||||||
} |
|
||||||
|
|
||||||
BAAudioControlWM8731::BAAudioControlWM8731() |
|
||||||
{ |
|
||||||
resetInternalReg(); |
|
||||||
} |
|
||||||
|
|
||||||
BAAudioControlWM8731::~BAAudioControlWM8731() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
// Powerdown and disable the codec
|
|
||||||
void BAAudioControlWM8731::disable(void) |
|
||||||
{ |
|
||||||
|
|
||||||
//Serial.println("Disabling codec");
|
|
||||||
if (m_wireStarted == false) { Wire.begin(); m_wireStarted = true; } |
|
||||||
|
|
||||||
// set OUTPD to '1' (powerdown), which is bit 4
|
|
||||||
regArray[WM8731_REG_POWERDOWN] |= 0x10; |
|
||||||
write(WM8731_REG_POWERDOWN, regArray[WM8731_REG_POWERDOWN]); |
|
||||||
delay(100); // wait for power down
|
|
||||||
|
|
||||||
// power down the rest of the supplies
|
|
||||||
write(WM8731_REG_POWERDOWN, 0x9f); // complete codec powerdown
|
|
||||||
delay(100); |
|
||||||
|
|
||||||
resetCodec(); |
|
||||||
} |
|
||||||
|
|
||||||
// Powerup and unmute the codec
|
|
||||||
void BAAudioControlWM8731::enable(void) |
|
||||||
{ |
|
||||||
|
|
||||||
disable(); // disable first in case it was already powered up
|
|
||||||
|
|
||||||
//Serial.println("Enabling codec");
|
|
||||||
if (m_wireStarted == false) { Wire.begin(); m_wireStarted = true; } |
|
||||||
// Sequence from WAN0111.pdf
|
|
||||||
|
|
||||||
// Begin configuring the codec
|
|
||||||
resetCodec(); |
|
||||||
delay(100); // wait for reset
|
|
||||||
|
|
||||||
// Power up all domains except OUTPD and microphone
|
|
||||||
regArray[WM8731_REG_POWERDOWN] = 0x12; |
|
||||||
write(WM8731_REG_POWERDOWN, regArray[WM8731_REG_POWERDOWN]); |
|
||||||
delay(100); // wait for codec powerup
|
|
||||||
|
|
||||||
|
|
||||||
setAdcBypass(false); // causes a slight click
|
|
||||||
setDacSelect(true); |
|
||||||
setHPFDisable(true); |
|
||||||
setLeftInputGain(0x17); // default input gain
|
|
||||||
setRightInputGain(0x17); |
|
||||||
setLeftInMute(false); // no input mute
|
|
||||||
setRightInMute(false); |
|
||||||
setDacMute(false); // unmute the DAC
|
|
||||||
|
|
||||||
// link, but mute the headphone outputs
|
|
||||||
regArray[WM8731_REG_LHEADOUT] = WM8731_LEFT_HEADPHONE_LINK_MASK; |
|
||||||
write(WM8731_REG_LHEADOUT, regArray[WM8731_REG_LHEADOUT]); // volume off
|
|
||||||
regArray[WM8731_REG_RHEADOUT] = WM8731_RIGHT_HEADPHONE_LINK_MASK; |
|
||||||
write(WM8731_REG_RHEADOUT, regArray[WM8731_REG_RHEADOUT]); |
|
||||||
|
|
||||||
/// Configure the audio interface
|
|
||||||
write(WM8731_REG_INTERFACE, 0x02); // I2S, 16 bit, MCLK slave
|
|
||||||
regArray[WM8731_REG_INTERFACE] = 0x2; |
|
||||||
|
|
||||||
write(WM8731_REG_SAMPLING, 0x20); // 256*Fs, 44.1 kHz, MCLK/1
|
|
||||||
regArray[WM8731_REG_SAMPLING] = 0x20; |
|
||||||
delay(100); // wait for interface config
|
|
||||||
|
|
||||||
// Activate the audio interface
|
|
||||||
setActivate(true); |
|
||||||
delay(100); |
|
||||||
|
|
||||||
write(WM8731_REG_POWERDOWN, 0x02); // power up outputs
|
|
||||||
regArray[WM8731_REG_POWERDOWN] = 0x02; |
|
||||||
delay(500); // wait for output to power up
|
|
||||||
|
|
||||||
//Serial.println("Done codec config");
|
|
||||||
|
|
||||||
|
|
||||||
delay(100); // wait for mute ramp
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
// Set the PGA gain on the Left channel
|
|
||||||
void BAAudioControlWM8731::setLeftInputGain(int val) |
|
||||||
{ |
|
||||||
regArray[WM8731_LEFT_INPUT_GAIN_ADDR] &= ~WM8731_LEFT_INPUT_GAIN_MASK; |
|
||||||
regArray[WM8731_LEFT_INPUT_GAIN_ADDR] |= |
|
||||||
((val << WM8731_LEFT_INPUT_GAIN_SHIFT) & WM8731_LEFT_INPUT_GAIN_MASK); |
|
||||||
write(WM8731_LEFT_INPUT_GAIN_ADDR, regArray[WM8731_LEFT_INPUT_GAIN_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Mute control on the ADC Left channel
|
|
||||||
void BAAudioControlWM8731::setLeftInMute(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_LEFT_INPUT_MUTE_ADDR] |= WM8731_LEFT_INPUT_MUTE_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_LEFT_INPUT_MUTE_ADDR] &= ~WM8731_LEFT_INPUT_MUTE_MASK; |
|
||||||
} |
|
||||||
write(WM8731_LEFT_INPUT_MUTE_ADDR, regArray[WM8731_LEFT_INPUT_MUTE_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Link the gain/mute controls for Left and Right channels
|
|
||||||
void BAAudioControlWM8731::setLinkLeftRightIn(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_LINK_LEFT_RIGHT_IN_ADDR] |= WM8731_LINK_LEFT_RIGHT_IN_MASK; |
|
||||||
regArray[WM8731_LINK_RIGHT_LEFT_IN_ADDR] |= WM8731_LINK_RIGHT_LEFT_IN_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_LINK_LEFT_RIGHT_IN_ADDR] &= ~WM8731_LINK_LEFT_RIGHT_IN_MASK; |
|
||||||
regArray[WM8731_LINK_RIGHT_LEFT_IN_ADDR] &= ~WM8731_LINK_RIGHT_LEFT_IN_MASK; |
|
||||||
} |
|
||||||
write(WM8731_LINK_LEFT_RIGHT_IN_ADDR, regArray[WM8731_LINK_LEFT_RIGHT_IN_ADDR]); |
|
||||||
write(WM8731_LINK_RIGHT_LEFT_IN_ADDR, regArray[WM8731_LINK_RIGHT_LEFT_IN_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Set the PGA input gain on the Right channel
|
|
||||||
void BAAudioControlWM8731::setRightInputGain(int val) |
|
||||||
{ |
|
||||||
regArray[WM8731_RIGHT_INPUT_GAIN_ADDR] &= ~WM8731_RIGHT_INPUT_GAIN_MASK; |
|
||||||
regArray[WM8731_RIGHT_INPUT_GAIN_ADDR] |= |
|
||||||
((val << WM8731_RIGHT_INPUT_GAIN_SHIFT) & WM8731_RIGHT_INPUT_GAIN_MASK); |
|
||||||
write(WM8731_RIGHT_INPUT_GAIN_ADDR, regArray[WM8731_RIGHT_INPUT_GAIN_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Mute control on the input ADC right channel
|
|
||||||
void BAAudioControlWM8731::setRightInMute(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_RIGHT_INPUT_MUTE_ADDR] |= WM8731_RIGHT_INPUT_MUTE_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_RIGHT_INPUT_MUTE_ADDR] &= ~WM8731_RIGHT_INPUT_MUTE_MASK; |
|
||||||
} |
|
||||||
write(WM8731_RIGHT_INPUT_MUTE_ADDR, regArray[WM8731_RIGHT_INPUT_MUTE_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Left/right swap control
|
|
||||||
void BAAudioControlWM8731::setLeftRightSwap(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_LRSWAP_ADDR] |= WM8731_LRSWAP_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_LRSWAP_ADDR] &= ~WM8731_LRSWAP_MASK; |
|
||||||
} |
|
||||||
write(WM8731_LRSWAP_ADDR, regArray[WM8731_LRSWAP_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
void BAAudioControlWM8731::setHeadphoneVolume(float volume) |
|
||||||
{ |
|
||||||
// the codec volume goes from 0x30 to 0x7F. Anything below 0x30 is mute.
|
|
||||||
// 0dB gain is 0x79. Total range is 0x50 (80) possible values.
|
|
||||||
unsigned vol; |
|
||||||
constexpr unsigned RANGE = 80.0f; |
|
||||||
if (volume < 0.0f) { |
|
||||||
vol = 0; |
|
||||||
} else if (volume > 1.0f) { |
|
||||||
vol = 0x7f; |
|
||||||
} else { |
|
||||||
vol = 0x2f + static_cast<unsigned>(volume * RANGE); |
|
||||||
} |
|
||||||
regArray[WM8731_LEFT_HEADPHONE_VOL_ADDR] &= ~WM8731_LEFT_HEADPHONE_VOL_MASK; // clear the volume first
|
|
||||||
regArray[WM8731_LEFT_HEADPHONE_VOL_ADDR] |= |
|
||||||
((vol << WM8731_LEFT_HEADPHONE_VOL_SHIFT) & WM8731_LEFT_HEADPHONE_VOL_MASK); |
|
||||||
write(WM8731_LEFT_HEADPHONE_VOL_ADDR, regArray[WM8731_LEFT_HEADPHONE_VOL_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Dac output mute control
|
|
||||||
void BAAudioControlWM8731::setDacMute(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_DAC_MUTE_ADDR] |= WM8731_DAC_MUTE_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_DAC_MUTE_ADDR] &= ~WM8731_DAC_MUTE_MASK; |
|
||||||
} |
|
||||||
write(WM8731_DAC_MUTE_ADDR, regArray[WM8731_DAC_MUTE_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Switches the DAC audio in/out of the output path
|
|
||||||
void BAAudioControlWM8731::setDacSelect(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_DAC_SELECT_ADDR] |= WM8731_DAC_SELECT_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_DAC_SELECT_ADDR] &= ~WM8731_DAC_SELECT_MASK; |
|
||||||
} |
|
||||||
write(WM8731_DAC_SELECT_ADDR, regArray[WM8731_DAC_SELECT_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Bypass sends the ADC input audio (analog) directly to analog output stage
|
|
||||||
// bypassing all digital processing
|
|
||||||
void BAAudioControlWM8731::setAdcBypass(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_ADC_BYPASS_ADDR] |= WM8731_ADC_BYPASS_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_ADC_BYPASS_ADDR] &= ~WM8731_ADC_BYPASS_MASK; |
|
||||||
} |
|
||||||
write(WM8731_ADC_BYPASS_ADDR, regArray[WM8731_ADC_BYPASS_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Enable/disable the dynamic HPF (recommended, it creates noise)
|
|
||||||
void BAAudioControlWM8731::setHPFDisable(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
regArray[WM8731_HPF_DISABLE_ADDR] |= WM8731_HPF_DISABLE_MASK; |
|
||||||
} else { |
|
||||||
regArray[WM8731_HPF_DISABLE_ADDR] &= ~WM8731_HPF_DISABLE_MASK; |
|
||||||
} |
|
||||||
write(WM8731_HPF_DISABLE_ADDR, regArray[WM8731_HPF_DISABLE_ADDR]); |
|
||||||
} |
|
||||||
|
|
||||||
// Activate/deactive the I2S audio interface
|
|
||||||
void BAAudioControlWM8731::setActivate(bool val) |
|
||||||
{ |
|
||||||
if (val) { |
|
||||||
write(WM8731_ACTIVATE_ADDR, WM8731_ACTIVATE_MASK); |
|
||||||
} else { |
|
||||||
write(WM8731_ACTIVATE_ADDR, 0); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
// Trigger the on-chip codec reset
|
|
||||||
void BAAudioControlWM8731::resetCodec(void) |
|
||||||
{ |
|
||||||
write(WM8731_REG_RESET, 0x0); |
|
||||||
resetInternalReg(); |
|
||||||
} |
|
||||||
|
|
||||||
// Direct write control to the codec
|
|
||||||
bool BAAudioControlWM8731::writeI2C(unsigned int addr, unsigned int val) |
|
||||||
{ |
|
||||||
return write(addr, val); |
|
||||||
} |
|
||||||
|
|
||||||
// Low level write control for the codec via the Teensy I2C interface
|
|
||||||
bool BAAudioControlWM8731::write(unsigned int reg, unsigned int val) |
|
||||||
{ |
|
||||||
bool done = false; |
|
||||||
|
|
||||||
while (!done) { |
|
||||||
Wire.beginTransmission(WM8731_I2C_ADDR); |
|
||||||
Wire.write((reg << 1) | ((val >> 8) & 1)); |
|
||||||
Wire.write(val & 0xFF); |
|
||||||
if (byte error = Wire.endTransmission() ) { |
|
||||||
(void)error; // supress warning about unused variable
|
|
||||||
//Serial.println(String("Wire::Error: ") + error + String(" retrying..."));
|
|
||||||
} else { |
|
||||||
done = true; |
|
||||||
//Serial.println("Wire::SUCCESS!");
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
@ -1,88 +0,0 @@ |
|||||||
/*
|
|
||||||
* BAGpio.cpp |
|
||||||
* |
|
||||||
* Created on: November 1, 2017 |
|
||||||
* Author: slascos |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#include "Arduino.h" |
|
||||||
#include "BAGpio.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
BAGpio::BAGpio() |
|
||||||
{ |
|
||||||
// Set all GPIOs to input
|
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO0), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO1), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO2), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO3), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO4), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO5), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO6), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::GPIO7), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::TP1), INPUT); |
|
||||||
pinMode(static_cast<uint8_t>(GPIO::TP2), INPUT); |
|
||||||
|
|
||||||
// Set the LED ot ouput
|
|
||||||
pinMode(USR_LED_ID, OUTPUT); |
|
||||||
clearLed(); // turn off the LED
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
BAGpio::~BAGpio() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
void BAGpio::setGPIODirection(GPIO gpioId, int direction) |
|
||||||
{ |
|
||||||
pinMode(static_cast<uint8_t>(gpioId), direction); |
|
||||||
} |
|
||||||
void BAGpio::setGPIO(GPIO gpioId) |
|
||||||
{ |
|
||||||
digitalWrite(static_cast<uint8_t>(gpioId), 0x1); |
|
||||||
} |
|
||||||
void BAGpio::clearGPIO(GPIO gpioId) |
|
||||||
{ |
|
||||||
digitalWrite(static_cast<uint8_t>(gpioId), 0); |
|
||||||
|
|
||||||
} |
|
||||||
int BAGpio::toggleGPIO(GPIO gpioId) |
|
||||||
{ |
|
||||||
int data = digitalRead(static_cast<uint8_t>(gpioId)); |
|
||||||
digitalWrite(static_cast<uint8_t>(gpioId), ~data); |
|
||||||
return ~data; |
|
||||||
} |
|
||||||
|
|
||||||
void BAGpio::setLed() |
|
||||||
{ |
|
||||||
digitalWrite(USR_LED_ID, 0x1); |
|
||||||
m_ledState = 1; |
|
||||||
} |
|
||||||
void BAGpio::clearLed() |
|
||||||
{ |
|
||||||
digitalWrite(USR_LED_ID, 0); |
|
||||||
m_ledState = 0; |
|
||||||
} |
|
||||||
int BAGpio::toggleLed() |
|
||||||
{ |
|
||||||
m_ledState = ~m_ledState; |
|
||||||
digitalWrite(USR_LED_ID, m_ledState); |
|
||||||
return m_ledState; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
@ -1,352 +0,0 @@ |
|||||||
/*
|
|
||||||
* BAPhysicalControls.cpp |
|
||||||
* |
|
||||||
* This file provides a class for handling physical controls such as |
|
||||||
* switches, pots and rotary encoders. |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
#include "BAPhysicalControls.h" |
|
||||||
|
|
||||||
// These calls must be define in order to get vector to work on arduino
|
|
||||||
namespace std { |
|
||||||
void __throw_bad_alloc() { |
|
||||||
Serial.println("Unable to allocate memory"); |
|
||||||
abort(); |
|
||||||
} |
|
||||||
void __throw_length_error( char const*e ) { |
|
||||||
Serial.print("Length Error :"); Serial.println(e); |
|
||||||
abort(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
BAPhysicalControls::BAPhysicalControls(unsigned numSwitches, unsigned numPots, unsigned numEncoders, unsigned numOutputs) { |
|
||||||
if (numSwitches > 0) { |
|
||||||
m_switches.reserve(numSwitches); |
|
||||||
} |
|
||||||
if (numPots > 0) { |
|
||||||
m_pots.reserve(numPots); |
|
||||||
} |
|
||||||
if (numEncoders > 0) { |
|
||||||
m_encoders.reserve(numEncoders); |
|
||||||
} |
|
||||||
if (numOutputs > 0) { |
|
||||||
m_outputs.reserve(numOutputs); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
unsigned BAPhysicalControls::addRotary(uint8_t pin1, uint8_t pin2, bool swapDirection, int divider) { |
|
||||||
m_encoders.emplace_back(pin1, pin2, swapDirection, divider); |
|
||||||
pinMode(pin1, INPUT); |
|
||||||
pinMode(pin2, INPUT); |
|
||||||
return m_encoders.size()-1; |
|
||||||
} |
|
||||||
|
|
||||||
unsigned BAPhysicalControls::addSwitch(uint8_t pin, unsigned long intervalMilliseconds) { |
|
||||||
//m_switches.emplace_back(pin, intervalMilliseconds);'
|
|
||||||
m_switches.emplace_back(); |
|
||||||
m_switches.back().attach(pin); |
|
||||||
m_switches.back().interval(10); |
|
||||||
pinMode(pin, INPUT); |
|
||||||
return m_switches.size()-1; |
|
||||||
} |
|
||||||
|
|
||||||
unsigned BAPhysicalControls::addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration) { |
|
||||||
m_pots.emplace_back(pin, minCalibration, maxCalibration); |
|
||||||
pinMode(pin, INPUT); |
|
||||||
return m_pots.size()-1; |
|
||||||
} |
|
||||||
|
|
||||||
unsigned BAPhysicalControls::addPot(uint8_t pin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection) { |
|
||||||
m_pots.emplace_back(pin, minCalibration, maxCalibration, swapDirection); |
|
||||||
pinMode(pin, INPUT); |
|
||||||
return m_pots.size()-1; |
|
||||||
} |
|
||||||
|
|
||||||
unsigned BAPhysicalControls::addOutput(uint8_t pin) { |
|
||||||
m_outputs.emplace_back(pin); |
|
||||||
pinMode(pin, OUTPUT); |
|
||||||
return m_outputs.size()-1; |
|
||||||
} |
|
||||||
|
|
||||||
void BAPhysicalControls::setOutput(unsigned handle, int val) { |
|
||||||
if (handle >= m_outputs.size()) { return; } |
|
||||||
m_outputs[handle].set(val); |
|
||||||
} |
|
||||||
|
|
||||||
void BAPhysicalControls::setOutput(unsigned handle, bool val) { |
|
||||||
if (handle >= m_outputs.size()) { return; } |
|
||||||
unsigned value = val ? 1 : 0; |
|
||||||
m_outputs[handle].set(value); |
|
||||||
} |
|
||||||
|
|
||||||
void BAPhysicalControls::toggleOutput(unsigned handle) { |
|
||||||
if (handle >= m_outputs.size()) { return; } |
|
||||||
m_outputs[handle].toggle(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
int BAPhysicalControls::getRotaryAdjustUnit(unsigned handle) { |
|
||||||
if (handle >= m_encoders.size()) { return 0; } // handle is greater than number of encoders
|
|
||||||
|
|
||||||
int encoderAdjust = m_encoders[handle].getChange(); |
|
||||||
if (encoderAdjust != 0) { |
|
||||||
// clip the adjust to maximum abs value of 1.
|
|
||||||
int encoderAdjust = (encoderAdjust > 0) ? 1 : -1; |
|
||||||
} |
|
||||||
|
|
||||||
return encoderAdjust; |
|
||||||
} |
|
||||||
|
|
||||||
bool BAPhysicalControls::checkPotValue(unsigned handle, float &value) { |
|
||||||
if (handle >= m_pots.size()) { return false;} // handle is greater than number of pots
|
|
||||||
return m_pots[handle].getValue(value); |
|
||||||
} |
|
||||||
|
|
||||||
int BAPhysicalControls::getPotRawValue(unsigned handle) |
|
||||||
{ |
|
||||||
if (handle >= m_pots.size()) { return false;} // handle is greater than number of pots
|
|
||||||
return m_pots[handle].getRawValue(); |
|
||||||
} |
|
||||||
|
|
||||||
bool BAPhysicalControls::setCalibrationValues(unsigned handle, unsigned min, unsigned max, bool swapDirection) |
|
||||||
{ |
|
||||||
if (handle >= m_pots.size()) { return false;} // handle is greater than number of pots
|
|
||||||
m_pots[handle].setCalibrationValues(min, max, swapDirection); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
bool BAPhysicalControls::isSwitchToggled(unsigned handle) { |
|
||||||
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
|
|
||||||
DigitalInput &sw = m_switches[handle]; |
|
||||||
|
|
||||||
return sw.hasInputToggled(); |
|
||||||
} |
|
||||||
|
|
||||||
bool BAPhysicalControls::isSwitchHeld(unsigned handle) |
|
||||||
{ |
|
||||||
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
|
|
||||||
DigitalInput &sw = m_switches[handle]; |
|
||||||
|
|
||||||
return sw.isInputAssert(); |
|
||||||
} |
|
||||||
|
|
||||||
bool BAPhysicalControls::getSwitchValue(unsigned handle) |
|
||||||
{ |
|
||||||
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
|
|
||||||
DigitalInput &sw = m_switches[handle]; |
|
||||||
|
|
||||||
return sw.read(); |
|
||||||
} |
|
||||||
|
|
||||||
bool BAPhysicalControls::hasSwitchChanged(unsigned handle, bool &switchValue) |
|
||||||
{ |
|
||||||
if (handle >= m_switches.size()) { return 0; } // handle is greater than number of switches
|
|
||||||
DigitalInput &sw = m_switches[handle]; |
|
||||||
|
|
||||||
return sw.hasInputChanged(switchValue); |
|
||||||
} |
|
||||||
|
|
||||||
///////////////////////////
|
|
||||||
// DigitalInput
|
|
||||||
///////////////////////////
|
|
||||||
bool DigitalInput::hasInputToggled() { |
|
||||||
|
|
||||||
update(); |
|
||||||
if (fell() && (m_isPolarityInverted == false)) { |
|
||||||
// switch fell and polarity is not inverted
|
|
||||||
return true; |
|
||||||
} else if (rose() && (m_isPolarityInverted == true)) { |
|
||||||
// switch rose and polarity is inveretd
|
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool DigitalInput::isInputAssert() |
|
||||||
{ |
|
||||||
update(); |
|
||||||
// if polarity is inverted, return the opposite state
|
|
||||||
bool retValue = Bounce::read() ^ m_isPolarityInverted; |
|
||||||
return retValue; |
|
||||||
} |
|
||||||
|
|
||||||
bool DigitalInput::getPinInputValue() |
|
||||||
{ |
|
||||||
update(); |
|
||||||
return Bounce::read(); |
|
||||||
} |
|
||||||
|
|
||||||
bool DigitalInput::hasInputChanged(bool &switchState) |
|
||||||
{ |
|
||||||
update(); |
|
||||||
if (rose()) { |
|
||||||
// return true if not inverted
|
|
||||||
switchState = m_isPolarityInverted ? false : true; |
|
||||||
return true; |
|
||||||
} else if (fell()) { |
|
||||||
// return false if not inverted
|
|
||||||
switchState = m_isPolarityInverted ? true : false; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
// return current value
|
|
||||||
switchState = Bounce::read() != m_isPolarityInverted; |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
///////////////////////////
|
|
||||||
// DigitalOutput
|
|
||||||
///////////////////////////
|
|
||||||
void DigitalOutput::set(int val) { |
|
||||||
m_val = val; |
|
||||||
digitalWriteFast(m_pin, m_val); |
|
||||||
} |
|
||||||
|
|
||||||
void DigitalOutput::toggle(void) { |
|
||||||
m_val = !m_val; |
|
||||||
digitalWriteFast(m_pin, m_val); |
|
||||||
} |
|
||||||
|
|
||||||
///////////////////////////
|
|
||||||
// Potentiometer
|
|
||||||
///////////////////////////
|
|
||||||
Potentiometer::Potentiometer(uint8_t analogPin, unsigned minCalibration, unsigned maxCalibration, bool swapDirection) |
|
||||||
: m_pin(analogPin), m_swapDirection(swapDirection), m_minCalibration(minCalibration), m_maxCalibration(maxCalibration) |
|
||||||
{ |
|
||||||
adjustCalibrationThreshold(m_thresholdFactor); // Calculate the thresholded values
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void Potentiometer::setFeedbackFitlerValue(float fitlerValue) |
|
||||||
{ |
|
||||||
m_feedbackFitlerValue = fitlerValue; |
|
||||||
} |
|
||||||
|
|
||||||
bool Potentiometer::getValue(float &value) { |
|
||||||
|
|
||||||
bool newValue = true; |
|
||||||
|
|
||||||
unsigned val = analogRead(m_pin); // read the raw value
|
|
||||||
|
|
||||||
// constrain it within the calibration values, them map it to the desired range.
|
|
||||||
val = constrain(val, m_minCalibration, m_maxCalibration); |
|
||||||
|
|
||||||
// Use an IIR filter to smooth out the noise in the pot readings
|
|
||||||
unsigned valFilter = static_cast<unsigned>( (1.0f - m_feedbackFitlerValue)*val + (m_feedbackFitlerValue*m_lastValue)); |
|
||||||
|
|
||||||
if (valFilter == m_lastValue) { |
|
||||||
newValue = false; |
|
||||||
} |
|
||||||
m_lastValue = valFilter; |
|
||||||
|
|
||||||
//
|
|
||||||
if (valFilter < m_minCalibrationThresholded) { value = 0.0f; } |
|
||||||
else if (valFilter > m_maxCalibrationThresholded) { value = 1.0f; } |
|
||||||
else { |
|
||||||
value = static_cast<float>(valFilter - m_minCalibrationThresholded) / static_cast<float>(m_rangeThresholded); |
|
||||||
} |
|
||||||
|
|
||||||
if (m_swapDirection) { |
|
||||||
value = 1.0f - value; |
|
||||||
} |
|
||||||
return newValue; |
|
||||||
} |
|
||||||
|
|
||||||
int Potentiometer::getRawValue() { |
|
||||||
return analogRead(m_pin); |
|
||||||
} |
|
||||||
|
|
||||||
// Recalculate thresholded limits based on thresholdFactor
|
|
||||||
void Potentiometer::adjustCalibrationThreshold(float thresholdFactor) |
|
||||||
{ |
|
||||||
m_thresholdFactor = thresholdFactor; |
|
||||||
// the threshold is specificed as a fraction of the min/max range.
|
|
||||||
unsigned threshold = static_cast<unsigned>((m_maxCalibration - m_minCalibration) * thresholdFactor); |
|
||||||
|
|
||||||
// Update the thresholded values
|
|
||||||
m_minCalibrationThresholded = m_minCalibration + threshold; |
|
||||||
m_maxCalibrationThresholded = m_maxCalibration - threshold; |
|
||||||
m_rangeThresholded = m_maxCalibrationThresholded - m_minCalibrationThresholded; |
|
||||||
} |
|
||||||
|
|
||||||
void Potentiometer::setCalibrationValues(unsigned min, unsigned max, bool swapDirection) |
|
||||||
{ |
|
||||||
m_minCalibration = min; |
|
||||||
m_maxCalibration = max; |
|
||||||
m_swapDirection = swapDirection; |
|
||||||
adjustCalibrationThreshold(m_thresholdFactor); |
|
||||||
} |
|
||||||
|
|
||||||
Potentiometer::Calib Potentiometer::calibrate(uint8_t pin) { |
|
||||||
Calib calib; |
|
||||||
|
|
||||||
Serial.print("Calibration pin "); Serial.println(pin); |
|
||||||
Serial.println("Move the pot fully counter-clockwise to the minimum setting and press any key then ENTER"); |
|
||||||
while (true) { |
|
||||||
delay(100); |
|
||||||
if (Serial.available() > 0) { |
|
||||||
calib.min = analogRead(pin); |
|
||||||
while (Serial.available()) { Serial.read(); } |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Serial.println("Move the pot fully clockwise to the maximum setting and press any key then ENTER"); |
|
||||||
while (true) { |
|
||||||
delay(100); |
|
||||||
if (Serial.available() > 0) { |
|
||||||
calib.max = analogRead(pin); |
|
||||||
while (Serial.available()) { Serial.read(); } |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (calib.min > calib.max) { |
|
||||||
unsigned tmp = calib.max; |
|
||||||
calib.max = calib.min; |
|
||||||
calib.min = tmp; |
|
||||||
calib.swap = true; |
|
||||||
} |
|
||||||
|
|
||||||
Serial.print("The calibration for pin "); Serial.print(pin); |
|
||||||
Serial.print(" is min:"); Serial.print(calib.min); |
|
||||||
Serial.print(" max:"); Serial.print(calib.max); |
|
||||||
Serial.print(" swap: "); Serial.println(calib.swap); |
|
||||||
|
|
||||||
return calib; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
int RotaryEncoder::getChange() { |
|
||||||
int32_t newPosition = read(); |
|
||||||
int delta = newPosition - m_lastPosition; |
|
||||||
m_lastPosition = newPosition; |
|
||||||
if (m_swapDirection) { delta = -delta; } |
|
||||||
return delta/m_divider; |
|
||||||
} |
|
||||||
|
|
||||||
void RotaryEncoder::setDivider(int divider) { |
|
||||||
m_divider = divider; |
|
||||||
} |
|
||||||
|
|
||||||
} // BALibrary
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,471 +0,0 @@ |
|||||||
/*
|
|
||||||
* BASpiMemory.cpp |
|
||||||
* |
|
||||||
* Created on: May 22, 2017 |
|
||||||
* Author: slascos |
|
||||||
* |
|
||||||
* This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#include "Arduino.h" |
|
||||||
#include "BASpiMemory.h" |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
// MEM0 Settings
|
|
||||||
constexpr int SPI_CS_MEM0 = 15; |
|
||||||
constexpr int SPI_MOSI_MEM0 = 7; |
|
||||||
constexpr int SPI_MISO_MEM0 = 8; |
|
||||||
constexpr int SPI_SCK_MEM0 = 14; |
|
||||||
|
|
||||||
// MEM1 Settings
|
|
||||||
constexpr int SPI_CS_MEM1 = 31; |
|
||||||
constexpr int SPI_MOSI_MEM1 = 21; |
|
||||||
constexpr int SPI_MISO_MEM1 = 5; |
|
||||||
constexpr int SPI_SCK_MEM1 = 20; |
|
||||||
|
|
||||||
// SPI Constants
|
|
||||||
constexpr int SPI_WRITE_MODE_REG = 0x1; |
|
||||||
constexpr int SPI_WRITE_CMD = 0x2; |
|
||||||
constexpr int SPI_READ_CMD = 0x3; |
|
||||||
constexpr int SPI_ADDR_2_MASK = 0xFF0000; |
|
||||||
constexpr int SPI_ADDR_2_SHIFT = 16; |
|
||||||
constexpr int SPI_ADDR_1_MASK = 0x00FF00; |
|
||||||
constexpr int SPI_ADDR_1_SHIFT = 8; |
|
||||||
constexpr int SPI_ADDR_0_MASK = 0x0000FF; |
|
||||||
|
|
||||||
constexpr int CMD_ADDRESS_SIZE = 4; |
|
||||||
constexpr int MAX_DMA_XFER_SIZE = 0x4000; |
|
||||||
|
|
||||||
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId) |
|
||||||
{ |
|
||||||
m_memDeviceId = memDeviceId; |
|
||||||
m_settings = {20000000, MSBFIRST, SPI_MODE0}; |
|
||||||
} |
|
||||||
|
|
||||||
BASpiMemory::BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz) |
|
||||||
{ |
|
||||||
m_memDeviceId = memDeviceId; |
|
||||||
m_settings = {speedHz, MSBFIRST, SPI_MODE0}; |
|
||||||
} |
|
||||||
|
|
||||||
// Intitialize the correct Arduino SPI interface
|
|
||||||
void BASpiMemory::begin() |
|
||||||
{ |
|
||||||
switch (m_memDeviceId) { |
|
||||||
case SpiDeviceId::SPI_DEVICE0 : |
|
||||||
m_csPin = SPI_CS_MEM0; |
|
||||||
m_spi = &SPI; |
|
||||||
m_spi->setMOSI(SPI_MOSI_MEM0); |
|
||||||
m_spi->setMISO(SPI_MISO_MEM0); |
|
||||||
m_spi->setSCK(SPI_SCK_MEM0); |
|
||||||
m_spi->begin(); |
|
||||||
break; |
|
||||||
|
|
||||||
#if defined(__MK64FX512__) || defined(__MK66FX1M0__) |
|
||||||
case SpiDeviceId::SPI_DEVICE1 : |
|
||||||
m_csPin = SPI_CS_MEM1; |
|
||||||
m_spi = &SPI1; |
|
||||||
m_spi->setMOSI(SPI_MOSI_MEM1); |
|
||||||
m_spi->setMISO(SPI_MISO_MEM1); |
|
||||||
m_spi->setSCK(SPI_SCK_MEM1); |
|
||||||
m_spi->begin(); |
|
||||||
break; |
|
||||||
#endif |
|
||||||
|
|
||||||
default : |
|
||||||
// unreachable since memDeviceId is an enumerated class
|
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
pinMode(m_csPin, OUTPUT); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
m_started = true; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
BASpiMemory::~BASpiMemory() { |
|
||||||
} |
|
||||||
|
|
||||||
// Single address write
|
|
||||||
void BASpiMemory::write(size_t address, uint8_t data) |
|
||||||
{ |
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer(SPI_WRITE_CMD); |
|
||||||
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_0_MASK)); |
|
||||||
m_spi->transfer(data); |
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
// Single address write
|
|
||||||
void BASpiMemory::write(size_t address, uint8_t *src, size_t numBytes) |
|
||||||
{ |
|
||||||
uint8_t *dataPtr = src; |
|
||||||
|
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer(SPI_WRITE_CMD); |
|
||||||
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_0_MASK)); |
|
||||||
|
|
||||||
for (size_t i=0; i < numBytes; i++) { |
|
||||||
m_spi->transfer(*dataPtr++); |
|
||||||
} |
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BASpiMemory::zero(size_t address, size_t numBytes) |
|
||||||
{ |
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer(SPI_WRITE_CMD); |
|
||||||
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_0_MASK)); |
|
||||||
|
|
||||||
for (size_t i=0; i < numBytes; i++) { |
|
||||||
m_spi->transfer(0); |
|
||||||
} |
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemory::write16(size_t address, uint16_t data) |
|
||||||
{ |
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) ); |
|
||||||
m_spi->transfer16(address & 0xFFFF); |
|
||||||
m_spi->transfer16(data); |
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemory::write16(size_t address, uint16_t *src, size_t numWords) |
|
||||||
{ |
|
||||||
uint16_t *dataPtr = src; |
|
||||||
|
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) ); |
|
||||||
m_spi->transfer16(address & 0xFFFF); |
|
||||||
|
|
||||||
for (size_t i=0; i<numWords; i++) { |
|
||||||
m_spi->transfer16(*dataPtr++); |
|
||||||
} |
|
||||||
|
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemory::zero16(size_t address, size_t numWords) |
|
||||||
{ |
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer16((SPI_WRITE_CMD << 8) | (address >> 16) ); |
|
||||||
m_spi->transfer16(address & 0xFFFF); |
|
||||||
|
|
||||||
for (size_t i=0; i<numWords; i++) { |
|
||||||
m_spi->transfer16(0); |
|
||||||
} |
|
||||||
|
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
Serial.println("DONE!"); |
|
||||||
} |
|
||||||
|
|
||||||
// single address read
|
|
||||||
uint8_t BASpiMemory::read(size_t address) |
|
||||||
{ |
|
||||||
int data; |
|
||||||
|
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer(SPI_READ_CMD); |
|
||||||
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_0_MASK)); |
|
||||||
data = m_spi->transfer(0); |
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
return data; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BASpiMemory::read(size_t address, uint8_t *dest, size_t numBytes) |
|
||||||
{ |
|
||||||
uint8_t *dataPtr = dest; |
|
||||||
|
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer(SPI_READ_CMD); |
|
||||||
m_spi->transfer((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT); |
|
||||||
m_spi->transfer((address & SPI_ADDR_0_MASK)); |
|
||||||
|
|
||||||
for (size_t i=0; i<numBytes; i++) { |
|
||||||
*dataPtr++ = m_spi->transfer(0); |
|
||||||
} |
|
||||||
|
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
uint16_t BASpiMemory::read16(size_t address) |
|
||||||
{ |
|
||||||
|
|
||||||
uint16_t data; |
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) ); |
|
||||||
m_spi->transfer16(address & 0xFFFF); |
|
||||||
data = m_spi->transfer16(0); |
|
||||||
m_spi->endTransaction(); |
|
||||||
|
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
return data; |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemory::read16(size_t address, uint16_t *dest, size_t numWords) |
|
||||||
{ |
|
||||||
|
|
||||||
uint16_t *dataPtr = dest; |
|
||||||
m_spi->beginTransaction(m_settings); |
|
||||||
digitalWrite(m_csPin, LOW); |
|
||||||
m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) ); |
|
||||||
m_spi->transfer16(address & 0xFFFF); |
|
||||||
|
|
||||||
for (size_t i=0; i<numWords; i++) { |
|
||||||
*dataPtr++ = m_spi->transfer16(0); |
|
||||||
} |
|
||||||
|
|
||||||
m_spi->endTransaction(); |
|
||||||
digitalWrite(m_csPin, HIGH); |
|
||||||
} |
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// BASpiMemoryDMA
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId) |
|
||||||
: BASpiMemory(memDeviceId) |
|
||||||
{ |
|
||||||
int cs; |
|
||||||
switch (memDeviceId) { |
|
||||||
case SpiDeviceId::SPI_DEVICE0 : |
|
||||||
cs = SPI_CS_MEM0; |
|
||||||
m_cs = new ActiveLowChipSelect(cs, m_settings); |
|
||||||
break; |
|
||||||
#if defined(__MK66FX1M0__) |
|
||||||
case SpiDeviceId::SPI_DEVICE1 : |
|
||||||
cs = SPI_CS_MEM1; |
|
||||||
m_cs = new ActiveLowChipSelect1(cs, m_settings); |
|
||||||
break; |
|
||||||
#endif |
|
||||||
default : |
|
||||||
cs = SPI_CS_MEM0; |
|
||||||
} |
|
||||||
|
|
||||||
// add 4 bytes to buffer for SPI CMD and 3 bytes of address
|
|
||||||
m_txCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE]; |
|
||||||
m_rxCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE]; |
|
||||||
m_txTransfer = new DmaSpi::Transfer[2]; |
|
||||||
m_rxTransfer = new DmaSpi::Transfer[2]; |
|
||||||
} |
|
||||||
|
|
||||||
BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz) |
|
||||||
: BASpiMemory(memDeviceId, speedHz) |
|
||||||
{ |
|
||||||
int cs; |
|
||||||
switch (memDeviceId) { |
|
||||||
case SpiDeviceId::SPI_DEVICE0 : |
|
||||||
cs = SPI_CS_MEM0; |
|
||||||
m_cs = new ActiveLowChipSelect(cs, m_settings); |
|
||||||
break; |
|
||||||
#if defined(__MK66FX1M0__) |
|
||||||
case SpiDeviceId::SPI_DEVICE1 : |
|
||||||
cs = SPI_CS_MEM1; |
|
||||||
m_cs = new ActiveLowChipSelect1(cs, m_settings); |
|
||||||
break; |
|
||||||
#endif |
|
||||||
default : |
|
||||||
cs = SPI_CS_MEM0; |
|
||||||
} |
|
||||||
|
|
||||||
m_txCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE]; |
|
||||||
m_rxCommandBuffer = new uint8_t[CMD_ADDRESS_SIZE]; |
|
||||||
m_txTransfer = new DmaSpi::Transfer[2]; |
|
||||||
m_rxTransfer = new DmaSpi::Transfer[2]; |
|
||||||
} |
|
||||||
|
|
||||||
BASpiMemoryDMA::~BASpiMemoryDMA() |
|
||||||
{ |
|
||||||
delete m_cs; |
|
||||||
if (m_txTransfer) delete [] m_txTransfer; |
|
||||||
if (m_rxTransfer) delete [] m_rxTransfer; |
|
||||||
if (m_txCommandBuffer) delete [] m_txCommandBuffer; |
|
||||||
if (m_rxCommandBuffer) delete [] m_txCommandBuffer; |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemoryDMA::m_setSpiCmdAddr(int command, size_t address, uint8_t *dest) |
|
||||||
{ |
|
||||||
dest[0] = command; |
|
||||||
dest[1] = ((address & SPI_ADDR_2_MASK) >> SPI_ADDR_2_SHIFT); |
|
||||||
dest[2] = ((address & SPI_ADDR_1_MASK) >> SPI_ADDR_1_SHIFT); |
|
||||||
dest[3] = ((address & SPI_ADDR_0_MASK)); |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemoryDMA::begin(void) |
|
||||||
{ |
|
||||||
switch (m_memDeviceId) { |
|
||||||
case SpiDeviceId::SPI_DEVICE0 : |
|
||||||
m_csPin = SPI_CS_MEM0; |
|
||||||
m_spi = &SPI; |
|
||||||
m_spi->setMOSI(SPI_MOSI_MEM0); |
|
||||||
m_spi->setMISO(SPI_MISO_MEM0); |
|
||||||
m_spi->setSCK(SPI_SCK_MEM0); |
|
||||||
m_spi->begin(); |
|
||||||
//m_spiDma = &DMASPI0;
|
|
||||||
m_spiDma = new DmaSpiGeneric(); |
|
||||||
break; |
|
||||||
|
|
||||||
#if defined(__MK66FX1M0__) // DMA on SPI1 is only supported on T3.6
|
|
||||||
case SpiDeviceId::SPI_DEVICE1 : |
|
||||||
m_csPin = SPI_CS_MEM1; |
|
||||||
m_spi = &SPI1; |
|
||||||
m_spi->setMOSI(SPI_MOSI_MEM1); |
|
||||||
m_spi->setMISO(SPI_MISO_MEM1); |
|
||||||
m_spi->setSCK(SPI_SCK_MEM1); |
|
||||||
m_spi->begin(); |
|
||||||
m_spiDma = new DmaSpiGeneric(1); |
|
||||||
//m_spiDma = &DMASPI1;
|
|
||||||
break; |
|
||||||
#endif |
|
||||||
|
|
||||||
default : |
|
||||||
// unreachable since memDeviceId is an enumerated class
|
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
m_spiDma->begin(); |
|
||||||
m_spiDma->start(); |
|
||||||
|
|
||||||
m_started = true; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// SPI must build up a payload that starts the teh CMD/Address first. It will cycle
|
|
||||||
// through the payloads in a circular buffer and use the transfer objects to check if they
|
|
||||||
// are done before continuing.
|
|
||||||
void BASpiMemoryDMA::write(size_t address, uint8_t *src, size_t numBytes) |
|
||||||
{ |
|
||||||
size_t bytesRemaining = numBytes; |
|
||||||
uint8_t *srcPtr = src; |
|
||||||
size_t nextAddress = address; |
|
||||||
while (bytesRemaining > 0) { |
|
||||||
m_txXferCount = min(bytesRemaining, static_cast<size_t>(MAX_DMA_XFER_SIZE)); |
|
||||||
while ( m_txTransfer[1].busy()) {} // wait until not busy
|
|
||||||
m_setSpiCmdAddr(SPI_WRITE_CMD, nextAddress, m_txCommandBuffer); |
|
||||||
m_txTransfer[1] = DmaSpi::Transfer(m_txCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS); |
|
||||||
m_spiDma->registerTransfer(m_txTransfer[1]); |
|
||||||
|
|
||||||
while ( m_txTransfer[0].busy()) {} // wait until not busy
|
|
||||||
m_txTransfer[0] = DmaSpi::Transfer(srcPtr, m_txXferCount, nullptr, 0, m_cs, TransferType::NO_START_CS); |
|
||||||
m_spiDma->registerTransfer(m_txTransfer[0]); |
|
||||||
bytesRemaining -= m_txXferCount; |
|
||||||
srcPtr += m_txXferCount; |
|
||||||
nextAddress += m_txXferCount; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BASpiMemoryDMA::zero(size_t address, size_t numBytes) |
|
||||||
{ |
|
||||||
size_t bytesRemaining = numBytes; |
|
||||||
size_t nextAddress = address; |
|
||||||
while (bytesRemaining > 0) { |
|
||||||
m_txXferCount = min(bytesRemaining, static_cast<size_t>(MAX_DMA_XFER_SIZE)); |
|
||||||
while ( m_txTransfer[1].busy()) {} // wait until not busy
|
|
||||||
m_setSpiCmdAddr(SPI_WRITE_CMD, nextAddress, m_txCommandBuffer); |
|
||||||
m_txTransfer[1] = DmaSpi::Transfer(m_txCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS); |
|
||||||
m_spiDma->registerTransfer(m_txTransfer[1]); |
|
||||||
|
|
||||||
while ( m_txTransfer[0].busy()) {} // wait until not busy
|
|
||||||
m_txTransfer[0] = DmaSpi::Transfer(nullptr, m_txXferCount, nullptr, 0, m_cs, TransferType::NO_START_CS); |
|
||||||
m_spiDma->registerTransfer(m_txTransfer[0]); |
|
||||||
bytesRemaining -= m_txXferCount; |
|
||||||
nextAddress += m_txXferCount; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BASpiMemoryDMA::write16(size_t address, uint16_t *src, size_t numWords) |
|
||||||
{ |
|
||||||
write(address, reinterpret_cast<uint8_t*>(src), sizeof(uint16_t)*numWords); |
|
||||||
} |
|
||||||
|
|
||||||
void BASpiMemoryDMA::zero16(size_t address, size_t numWords) |
|
||||||
{ |
|
||||||
zero(address, sizeof(uint16_t)*numWords); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) |
|
||||||
{ |
|
||||||
size_t bytesRemaining = numBytes; |
|
||||||
uint8_t *destPtr = dest; |
|
||||||
size_t nextAddress = address; |
|
||||||
while (bytesRemaining > 0) { |
|
||||||
m_setSpiCmdAddr(SPI_READ_CMD, nextAddress, m_rxCommandBuffer); |
|
||||||
|
|
||||||
while ( m_rxTransfer[1].busy()) {} |
|
||||||
m_rxTransfer[1] = DmaSpi::Transfer(m_rxCommandBuffer, CMD_ADDRESS_SIZE, nullptr, 0, m_cs, TransferType::NO_END_CS); |
|
||||||
m_spiDma->registerTransfer(m_rxTransfer[1]); |
|
||||||
|
|
||||||
m_rxXferCount = min(bytesRemaining, static_cast<size_t>(MAX_DMA_XFER_SIZE)); |
|
||||||
while ( m_rxTransfer[0].busy()) {} |
|
||||||
m_rxTransfer[0] = DmaSpi::Transfer(nullptr, m_rxXferCount, destPtr, 0, m_cs, TransferType::NO_START_CS); |
|
||||||
m_spiDma->registerTransfer(m_rxTransfer[0]); |
|
||||||
|
|
||||||
bytesRemaining -= m_rxXferCount; |
|
||||||
destPtr += m_rxXferCount; |
|
||||||
nextAddress += m_rxXferCount; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) |
|
||||||
{ |
|
||||||
read(address, reinterpret_cast<uint8_t*>(dest), sizeof(uint16_t)*numWords); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
bool BASpiMemoryDMA::isWriteBusy(void) const |
|
||||||
{ |
|
||||||
return (m_txTransfer[0].busy() or m_txTransfer[1].busy()); |
|
||||||
} |
|
||||||
|
|
||||||
bool BASpiMemoryDMA::isReadBusy(void) const |
|
||||||
{ |
|
||||||
return (m_rxTransfer[0].busy() or m_rxTransfer[1].busy()); |
|
||||||
} |
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
@ -1,13 +0,0 @@ |
|||||||
#include "DmaSpi.h" |
|
||||||
|
|
||||||
#if defined(KINETISK) |
|
||||||
DmaSpi0 DMASPI0; |
|
||||||
#if defined(__MK66FX1M0__) |
|
||||||
DmaSpi1 DMASPI1; |
|
||||||
//DmaSpi2 DMASPI2;
|
|
||||||
#endif |
|
||||||
#elif defined (KINETISL) |
|
||||||
DmaSpi0 DMASPI0; |
|
||||||
DmaSpi1 DMASPI1; |
|
||||||
#else |
|
||||||
#endif // defined
|
|
Loading…
Reference in new issue