forked from wirtz/BALibrary
parent
7bf2ed6906
commit
1500f353cb
@ -1,199 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* AudioEffectAnalogDelay is a class for simulating a classic BBD based delay |
|
||||||
* like the Boss DM-2. This class works with either internal RAM, or external |
|
||||||
* SPI RAM for longer delays. The exteranl ram uses DMA to minimize load on the |
|
||||||
* CPU. |
|
||||||
* |
|
||||||
* @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_BAAUDIOEFFECTANALOGDELAY_H |
|
||||||
#define __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "LibBasicFunctions.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* AudioEffectAnalogDelay models BBD based analog delays. It provides controls |
|
||||||
* for delay, feedback (or regen), mix and output level. All parameters can be |
|
||||||
* controlled by MIDI. The class supports internal memory, or external SPI |
|
||||||
* memory by providing an ExtMemSlot. External memory access uses DMA to reduce |
|
||||||
* process load. |
|
||||||
*****************************************************************************/ |
|
||||||
class AudioEffectAnalogDelay : public AudioStream { |
|
||||||
public: |
|
||||||
|
|
||||||
///< List of AudioEffectAnalogDelay MIDI controllable parameters
|
|
||||||
enum { |
|
||||||
BYPASS = 0, ///< controls effect bypass
|
|
||||||
DELAY, ///< controls the amount of delay
|
|
||||||
FEEDBACK, ///< controls the amount of echo feedback (regen)
|
|
||||||
MIX, ///< controls the the mix of input and echo signals
|
|
||||||
VOLUME, ///< controls the output volume level
|
|
||||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
|
||||||
}; |
|
||||||
|
|
||||||
enum class Filter { |
|
||||||
DM3 = 0, |
|
||||||
WARM, |
|
||||||
DARK |
|
||||||
}; |
|
||||||
|
|
||||||
// *** CONSTRUCTORS ***
|
|
||||||
AudioEffectAnalogDelay() = delete; |
|
||||||
|
|
||||||
/// Construct an analog delay using internal memory by specifying the maximum
|
|
||||||
/// delay in milliseconds.
|
|
||||||
/// @param maxDelayMs maximum delay in milliseconds. Larger delays use more memory.
|
|
||||||
AudioEffectAnalogDelay(float maxDelayMs); |
|
||||||
|
|
||||||
/// Construct an analog delay using internal memory by specifying the maximum
|
|
||||||
/// delay in audio samples.
|
|
||||||
/// @param numSamples maximum delay in audio samples. Larger delays use more memory.
|
|
||||||
AudioEffectAnalogDelay(size_t numSamples); |
|
||||||
|
|
||||||
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
|
|
||||||
/// delay will be determined by the amount of memory in the slot.
|
|
||||||
/// @param slot A pointer to the ExtMemSlot to use for the delay.
|
|
||||||
AudioEffectAnalogDelay(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
|
|
||||||
|
|
||||||
virtual ~AudioEffectAnalogDelay(); ///< Destructor
|
|
||||||
|
|
||||||
// *** PARAMETERS ***
|
|
||||||
|
|
||||||
/// Set the delay in milliseconds.
|
|
||||||
/// @param milliseconds the request delay in milliseconds. Must be less than max delay.
|
|
||||||
void delay(float milliseconds); |
|
||||||
|
|
||||||
/// Set the delay in number of audio samples.
|
|
||||||
/// @param delaySamples the request delay in audio samples. Must be less than max delay.
|
|
||||||
void delay(size_t delaySamples); |
|
||||||
|
|
||||||
/// Set the delay as a fraction of the maximum delay.
|
|
||||||
/// The value should be between 0.0f and 1.0f
|
|
||||||
void delayFractionMax(float delayFraction); |
|
||||||
|
|
||||||
/// Bypass the effect.
|
|
||||||
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
|
|
||||||
/// Note that audio still passes through when bypass is enabled.
|
|
||||||
void bypass(bool byp) { m_bypass = byp; } |
|
||||||
|
|
||||||
/// Get if the effect is bypassed
|
|
||||||
/// @returns true if bypassed, false if not bypassed
|
|
||||||
bool isBypass() { return m_bypass; } |
|
||||||
|
|
||||||
/// Toggle the bypass effect
|
|
||||||
void toggleBypass() { m_bypass = !m_bypass; } |
|
||||||
|
|
||||||
/// Set the amount of echo feedback (a.k.a regeneration).
|
|
||||||
/// @param feedback a floating point number between 0.0 and 1.0.
|
|
||||||
void feedback(float feedback) { m_feedback = feedback; } |
|
||||||
|
|
||||||
/// Set the amount of blending between dry and wet (echo) at the output.
|
|
||||||
/// @param mix When 0.0, output is 100% dry, when 1.0, output is 100% wet. When
|
|
||||||
/// 0.5, output is 50% Dry, 50% Wet.
|
|
||||||
void mix(float mix) { m_mix = mix; } |
|
||||||
|
|
||||||
/// 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); |
|
||||||
|
|
||||||
// ** FILTER COEFFICIENTS **
|
|
||||||
|
|
||||||
/// Set the filter coefficients to one of the presets. See AudioEffectAnalogDelay::Filter
|
|
||||||
/// for options.
|
|
||||||
/// @details See AudioEffectAnalogDelayFIlters.h for more details.
|
|
||||||
/// @param filter the preset filter. E.g. AudioEffectAnalogDelay::Filter::WARM
|
|
||||||
void setFilter(Filter filter); |
|
||||||
|
|
||||||
/// Override the default coefficients with your own. The number of filters stages affects how
|
|
||||||
/// much CPU is consumed.
|
|
||||||
/// @details The effect uses the CMSIS-DSP library for biquads which requires coefficents.
|
|
||||||
/// be in q31 format, which means they are 32-bit signed integers representing -1.0 to slightly
|
|
||||||
/// less than +1.0. The coeffShift parameter effectively multiplies the coefficients by 2^shift. <br>
|
|
||||||
/// Example: If you really want +1.5, must instead use +0.75 * 2^1, thus 0.75 in q31 format is
|
|
||||||
/// (0.75 * 2^31) = 1610612736 and coeffShift = 1.
|
|
||||||
/// @param numStages the actual number of filter stages you want to use. Must be <= MAX_NUM_FILTER_STAGES.
|
|
||||||
/// @param coeffs pointer to an integer array of coefficients in q31 format.
|
|
||||||
/// @param coeffShift Coefficient scaling factor = 2^coeffShift.
|
|
||||||
void setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift); |
|
||||||
|
|
||||||
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; |
|
||||||
bool m_externalMemory = false; |
|
||||||
BALibrary::AudioDelay *m_memory = nullptr; |
|
||||||
size_t m_maxDelaySamples = 0; |
|
||||||
audio_block_t *m_previousBlock = nullptr; |
|
||||||
audio_block_t *m_blockToRelease = nullptr; |
|
||||||
BALibrary::IirBiQuadFilterHQ *m_iir = nullptr; |
|
||||||
|
|
||||||
// Controls
|
|
||||||
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
|
|
||||||
size_t m_delaySamples = 0; |
|
||||||
float m_feedback = 0.0f; |
|
||||||
float m_mix = 0.0f; |
|
||||||
float m_volume = 1.0f; |
|
||||||
|
|
||||||
void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); |
|
||||||
void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); |
|
||||||
|
|
||||||
// Coefficients
|
|
||||||
void m_constructFilter(void); |
|
||||||
}; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H */ |
|
@ -1,149 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* AudioEffectSOS is a class f |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BAEFFECTS_BAAUDIOEFFECTSOS_H |
|
||||||
#define __BAEFFECTS_BAAUDIOEFFECTSOS_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "LibBasicFunctions.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* AudioEffectSOS |
|
||||||
*****************************************************************************/ |
|
||||||
class AudioEffectSOS : public AudioStream { |
|
||||||
public: |
|
||||||
|
|
||||||
///< List of AudioEffectAnalogDelay MIDI controllable parameters
|
|
||||||
enum { |
|
||||||
BYPASS = 0, ///< controls effect bypass
|
|
||||||
GATE_TRIGGER, ///< begins the gate sequence
|
|
||||||
GATE_OPEN_TIME, ///< controls how long it takes to open the gate
|
|
||||||
//GATE_HOLD_TIME, ///< controls how long the gate stays open at unity
|
|
||||||
GATE_CLOSE_TIME, ///< controls how long it takes to close the gate (release)
|
|
||||||
CLEAR_FEEDBACK_TRIGGER, ///< begins the sequence to clear out the looping feedback
|
|
||||||
FEEDBACK, ///< controls the amount of feedback, more gives longer SOS sustain
|
|
||||||
VOLUME, ///< controls the output volume level
|
|
||||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
|
||||||
}; |
|
||||||
|
|
||||||
// *** CONSTRUCTORS ***
|
|
||||||
AudioEffectSOS() = delete; |
|
||||||
AudioEffectSOS(float maxDelayMs); |
|
||||||
AudioEffectSOS(size_t numSamples); |
|
||||||
|
|
||||||
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
|
|
||||||
/// delay will be determined by the amount of memory in the slot.
|
|
||||||
/// @param slot A pointer to the ExtMemSlot to use for the delay.
|
|
||||||
AudioEffectSOS(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
|
|
||||||
|
|
||||||
virtual ~AudioEffectSOS(); ///< Destructor
|
|
||||||
|
|
||||||
void setGateLedGpio(int pinId); |
|
||||||
|
|
||||||
// *** PARAMETERS ***
|
|
||||||
void gateOpenTime(float milliseconds); |
|
||||||
|
|
||||||
void gateCloseTime(float milliseconds); |
|
||||||
|
|
||||||
void feedback(float feedback) { m_feedback = feedback; } |
|
||||||
|
|
||||||
/// Bypass the effect.
|
|
||||||
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
|
|
||||||
/// Note that audio still passes through when bypass is enabled.
|
|
||||||
void bypass(bool byp) { m_bypass = byp; } |
|
||||||
|
|
||||||
/// Activate the gate automation. Input gate will open, then close.
|
|
||||||
void trigger() { m_inputGateAuto.trigger(); } |
|
||||||
|
|
||||||
/// Activate the delay clearing automation. Input signal will mute, gate will open, then close.
|
|
||||||
void clear() { m_clearFeedbackAuto.trigger(); } |
|
||||||
|
|
||||||
/// Set the output volume. This affect both the wet and dry signals.
|
|
||||||
/// @details The default is 1.0.
|
|
||||||
/// @param vol Sets the output volume between -1.0 and +1.0
|
|
||||||
void volume(float vol) {m_volume = vol; } |
|
||||||
|
|
||||||
// ** ENABLE / DISABLE **
|
|
||||||
|
|
||||||
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
|
|
||||||
void enable(); |
|
||||||
|
|
||||||
/// Disables audio process. When disabled, CPU load is nearly zero.
|
|
||||||
void disable() { m_enable = false; } |
|
||||||
|
|
||||||
// ** MIDI **
|
|
||||||
|
|
||||||
/// Sets whether MIDI OMNI channel is processig on or off. When on,
|
|
||||||
/// all midi channels are used for matching CCs.
|
|
||||||
/// @param isOmni when true, all channels are processed, when false, channel
|
|
||||||
/// must match configured value.
|
|
||||||
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } |
|
||||||
|
|
||||||
/// Configure an effect parameter to be controlled by a MIDI CC
|
|
||||||
/// number on a particular channel.
|
|
||||||
/// @param parameter one of the parameter names in the class enum
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param midiChannel the effect will only response to the CC on this channel
|
|
||||||
/// when OMNI mode is off.
|
|
||||||
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); |
|
||||||
|
|
||||||
/// process a MIDI Continous-Controller (CC) message
|
|
||||||
/// @param channel the MIDI channel from 0 to 15)
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param value the CC value from 0 to 127
|
|
||||||
void processMidi(int channel, int midiCC, int value); |
|
||||||
|
|
||||||
virtual void update(void); ///< update automatically called by the Teesny Audio Library
|
|
||||||
|
|
||||||
private: |
|
||||||
audio_block_t *m_inputQueueArray[1]; |
|
||||||
bool m_isOmni = false; |
|
||||||
bool m_bypass = true; |
|
||||||
bool m_enable = false; |
|
||||||
BALibrary::AudioDelay *m_memory = nullptr; |
|
||||||
bool m_externalMemory = true; |
|
||||||
audio_block_t *m_previousBlock = nullptr; |
|
||||||
audio_block_t *m_blockToRelease = nullptr; |
|
||||||
size_t m_maxDelaySamples = 0; |
|
||||||
int m_gateLedPinId = -1; |
|
||||||
|
|
||||||
// Controls
|
|
||||||
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
|
|
||||||
size_t m_delaySamples = 0; |
|
||||||
float m_openTimeMs = 0.0f; |
|
||||||
float m_closeTimeMs = 0.0f; |
|
||||||
float m_feedback = 0.0f; |
|
||||||
float m_volume = 1.0f; |
|
||||||
|
|
||||||
// Automated Controls
|
|
||||||
BALibrary::ParameterAutomationSequence<float> m_inputGateAuto = BALibrary::ParameterAutomationSequence<float>(3); |
|
||||||
BALibrary::ParameterAutomationSequence<float> m_clearFeedbackAuto = BALibrary::ParameterAutomationSequence<float>(3); |
|
||||||
|
|
||||||
// Private functions
|
|
||||||
void m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal); |
|
||||||
void m_postProcessing(audio_block_t *out, audio_block_t *input); |
|
||||||
}; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_BAAUDIOEFFECTSOS_H */ |
|
@ -1,126 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* Tremolo is a classic volume modulate effect. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY__BAEFFECTS_AUDIOEFFECTDELAYEXTERNAL_H; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BAEFFECTS_AUDIOEFFECTTREMOLO_H |
|
||||||
#define __BAEFFECTS_AUDIOEFFECTTREMOLO_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "LibBasicFunctions.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* AudioEffectTremolo |
|
||||||
*****************************************************************************/ |
|
||||||
class AudioEffectTremolo : public AudioStream { |
|
||||||
public: |
|
||||||
|
|
||||||
///< List of AudioEffectTremolo MIDI controllable parameters
|
|
||||||
enum { |
|
||||||
BYPASS = 0, ///< controls effect bypass
|
|
||||||
RATE, ///< controls the rate of the modulation
|
|
||||||
DEPTH, ///< controls the depth of the modulation
|
|
||||||
WAVEFORM, ///< select the modulation waveform
|
|
||||||
VOLUME, ///< controls the output volume level
|
|
||||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
|
||||||
}; |
|
||||||
|
|
||||||
// *** CONSTRUCTORS ***
|
|
||||||
AudioEffectTremolo(); |
|
||||||
|
|
||||||
virtual ~AudioEffectTremolo(); ///< Destructor
|
|
||||||
|
|
||||||
// *** PARAMETERS ***
|
|
||||||
void rate(float rateValue); |
|
||||||
|
|
||||||
void depth(float depthValue) { m_depth = depthValue; } |
|
||||||
|
|
||||||
void setWaveform(BALibrary::Waveform waveform); |
|
||||||
|
|
||||||
|
|
||||||
/// Bypass the effect.
|
|
||||||
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
|
|
||||||
/// Note that audio still passes through when bypass is enabled.
|
|
||||||
void bypass(bool byp) { m_bypass = byp; } |
|
||||||
|
|
||||||
/// Get if the effect is bypassed
|
|
||||||
/// @returns true if bypassed, false if not bypassed
|
|
||||||
bool isBypass() { return m_bypass; } |
|
||||||
|
|
||||||
/// Toggle the bypass effect
|
|
||||||
void toggleBypass() { m_bypass = !m_bypass; } |
|
||||||
|
|
||||||
/// Set the output volume. This affect both the wet and dry signals.
|
|
||||||
/// @details The default is 1.0.
|
|
||||||
/// @param vol Sets the output volume between -1.0 and +1.0
|
|
||||||
void volume(float vol) {m_volume = vol; } |
|
||||||
|
|
||||||
// ** ENABLE / DISABLE **
|
|
||||||
|
|
||||||
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
|
|
||||||
void enable() { m_enable = true; } |
|
||||||
|
|
||||||
/// Disables audio process. When disabled, CPU load is nearly zero.
|
|
||||||
void disable() { m_enable = false; } |
|
||||||
|
|
||||||
// ** MIDI **
|
|
||||||
|
|
||||||
/// Sets whether MIDI OMNI channel is processig on or off. When on,
|
|
||||||
/// all midi channels are used for matching CCs.
|
|
||||||
/// @param isOmni when true, all channels are processed, when false, channel
|
|
||||||
/// must match configured value.
|
|
||||||
void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } |
|
||||||
|
|
||||||
/// Configure an effect parameter to be controlled by a MIDI CC
|
|
||||||
/// number on a particular channel.
|
|
||||||
/// @param parameter one of the parameter names in the class enum
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param midiChannel the effect will only response to the CC on this channel
|
|
||||||
/// when OMNI mode is off.
|
|
||||||
void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); |
|
||||||
|
|
||||||
/// process a MIDI Continous-Controller (CC) message
|
|
||||||
/// @param channel the MIDI channel from 0 to 15)
|
|
||||||
/// @param midiCC the CC number from 0 to 127
|
|
||||||
/// @param value the CC value from 0 to 127
|
|
||||||
void processMidi(int channel, int midiCC, int value); |
|
||||||
|
|
||||||
|
|
||||||
virtual void update(void); ///< update automatically called by the Teesny Audio Library
|
|
||||||
|
|
||||||
private: |
|
||||||
audio_block_t *m_inputQueueArray[1]; |
|
||||||
BALibrary::LowFrequencyOscillatorVector<float> m_osc; |
|
||||||
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
|
|
||||||
bool m_isOmni = false; |
|
||||||
bool m_bypass = true; |
|
||||||
bool m_enable = false; |
|
||||||
|
|
||||||
float m_rate = 0.0f; |
|
||||||
float m_depth = 0.0f; |
|
||||||
BALibrary::Waveform m_waveform = BALibrary::Waveform::SINE; |
|
||||||
float m_volume = 1.0f; |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_AUDIOEFFECTTREMOLO_H */ |
|
@ -1,135 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BAAudioContromWM8731 is a class for controlling a WM8731 Codec via I2C. |
|
||||||
* @details The Codec power ups in a silent state, with non-optimal |
|
||||||
* configuration. This class will enable codec and set some initial gain levels. |
|
||||||
* The user can than use the API to changing settings for their specific needs. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BALIBRARY_BAAUDIOCONTROLWM8731_H |
|
||||||
#define __BALIBRARY_BAAUDIOCONTROLWM8731_H |
|
||||||
|
|
||||||
namespace BALibrary { |
|
||||||
|
|
||||||
constexpr int WM8731_NUM_REGS = 10; // Number of registers in the internal shadow array
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* BAAudioControlWM8731 provides an API for configuring the internal registers |
|
||||||
* of the WM8731 codec. |
|
||||||
* @details Not every single command is provided, please ask if you need something |
|
||||||
* added that is not already present. You can also directly write any register |
|
||||||
* you wish with the writeI2C() method. |
|
||||||
*****************************************************************************/ |
|
||||||
class BAAudioControlWM8731 { |
|
||||||
public: |
|
||||||
BAAudioControlWM8731(); |
|
||||||
virtual ~BAAudioControlWM8731(); |
|
||||||
|
|
||||||
/// Mute and power down the codec.
|
|
||||||
void disable(void); |
|
||||||
|
|
||||||
/// First disable, then cleanly power up and unmute the codec.
|
|
||||||
void enable(void); |
|
||||||
|
|
||||||
/// Set the input gain of the codec's PGA for the left (TRS Tip) channel.
|
|
||||||
/// @param val an interger value from 31 = +12dB . . 1.5dB steps down to 0 = -34.5dB
|
|
||||||
void setLeftInputGain(int val); |
|
||||||
|
|
||||||
/// Set the input gain of the codec's PGA for the right (TRS Ring) channel.
|
|
||||||
/// @param val an interger value from 31 = +12dB . . 1.5dB steps down to 0 = -34.5dB
|
|
||||||
void setRightInputGain(int val); |
|
||||||
|
|
||||||
/// Mute/unmute the Left (TRS Tip) channel at the ADC input.
|
|
||||||
/// @param val when true, channel is muted, when false, channel is unmuted
|
|
||||||
void setLeftInMute(bool val); |
|
||||||
|
|
||||||
/// Mute/unmute the Right (TRS Ring) channel at the ADC input.
|
|
||||||
/// @param val when true, channel is muted, when false, channel is unmuted
|
|
||||||
void setRightInMute(bool val); |
|
||||||
|
|
||||||
/// Links the Left/Right channel contols for mute and input gain.
|
|
||||||
/// @details when true, changing either the left or right gain/mute controls will
|
|
||||||
/// affect both channels.
|
|
||||||
/// @param val when true, channels are linked, when false, they are controlled separately
|
|
||||||
void setLinkLeftRightIn(bool val); |
|
||||||
/// Swaps the left and right channels in the codec.
|
|
||||||
///param val when true, channels are swapped, else normal.
|
|
||||||
void setLeftRightSwap(bool val); |
|
||||||
|
|
||||||
/// Set the volume for the codec's built-in headphone amp
|
|
||||||
/// @param volume the input volume level from 0.0f to 1.0f;
|
|
||||||
void setHeadphoneVolume(float volume); |
|
||||||
|
|
||||||
/// Mute/unmute the output DAC, affects both Left and Right output channels.
|
|
||||||
/// @param when true, output DAC is muted, when false, unmuted.
|
|
||||||
void setDacMute(bool val); |
|
||||||
|
|
||||||
/// Control when the DAC is feeding the output analog circuitry.
|
|
||||||
/// @param val when true, the DAC output is connected to the analog output. When
|
|
||||||
/// false, the DAC is disconnected.
|
|
||||||
void setDacSelect(bool val); |
|
||||||
|
|
||||||
/// ADC Bypass control.
|
|
||||||
/// @details This permits the analog audio from the Codec's PGA to bypass the ADC
|
|
||||||
/// and go directly to the analog output of the codec, bypassing the digital domain
|
|
||||||
/// entirely.
|
|
||||||
/// @param val when true, analog ADC input is fed directly to codec analog otuput.
|
|
||||||
void setAdcBypass(bool val); |
|
||||||
|
|
||||||
/// Digital High Pass Filter disable. RECOMMENDED ALWAYS TRUE!
|
|
||||||
/// @details this controls a HPF in the codec that attempts to remove the lowest
|
|
||||||
/// frequency (i.e. < 10 Hz) to improve headroom by dynamically measuring DC level.
|
|
||||||
/// In most cases, it introduces noise components by modulating the filter. This
|
|
||||||
/// is not suitable for applications where the audio is used for output, but might
|
|
||||||
/// be useful for applications like tuning, pitch detection, whre the noise components
|
|
||||||
/// can be tolerated.
|
|
||||||
/// @param val when true (recommended) the dynamic HPF is disabled, otherwise enabled.
|
|
||||||
void setHPFDisable(bool val); |
|
||||||
|
|
||||||
/// Activates the I2S interface on the codec.
|
|
||||||
/// @param val when true, I2S interface is active, when false it is disabled.
|
|
||||||
void setActivate(bool val); |
|
||||||
|
|
||||||
/// Soft-reset the codec.
|
|
||||||
/// @details This will cause the codec to reset its internal registers to default values.
|
|
||||||
void resetCodec(void); |
|
||||||
|
|
||||||
/// Write a custom command to the codec via I2C control interface.
|
|
||||||
/// @details See WM8731 datasheet for register map details.
|
|
||||||
/// @param addr The register address you wish to write to, range 0 to 15.
|
|
||||||
/// @param val the 9-bit data value you wish to write at the address specifed.
|
|
||||||
bool writeI2C(unsigned int addr, unsigned int val); |
|
||||||
|
|
||||||
protected: |
|
||||||
// A shadow array for the registers on the codec since the interface is write-only.
|
|
||||||
int regArray[WM8731_NUM_REGS]; |
|
||||||
|
|
||||||
private: |
|
||||||
// low-level write command
|
|
||||||
bool write(unsigned int reg, unsigned int val); |
|
||||||
// resets the internal shadow register array
|
|
||||||
void resetInternalReg(void); |
|
||||||
|
|
||||||
bool m_wireStarted = false; |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
} /* namespace BALibrary */ |
|
||||||
|
|
||||||
#endif /* __BALIBRARY_BAAUDIOCONTROLWM8731_H */ |
|
@ -1,97 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* BAAudioEffectDelayExternal is a class for using an external SPI SRAM chip |
|
||||||
* as an audio delay line. The external memory can be shared among several |
|
||||||
* different instances of BAAudioEffectDelayExternal by specifying the max delay |
|
||||||
* length during construction. |
|
||||||
* |
|
||||||
* @copyright This program is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version.* |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
|
|
||||||
#ifndef __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H |
|
||||||
#define __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H |
|
||||||
|
|
||||||
#include <Audio.h> |
|
||||||
#include "AudioStream.h" |
|
||||||
|
|
||||||
#include "BAHardware.h" |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* BAAudioEffectDelayExternal can use external SPI RAM for delay rather than |
|
||||||
* the limited RAM available on the Teensy itself. |
|
||||||
*****************************************************************************/ |
|
||||||
class BAAudioEffectDelayExternal : public AudioStream |
|
||||||
{ |
|
||||||
public: |
|
||||||
|
|
||||||
/// Default constructor will use all memory available in MEM0
|
|
||||||
BAAudioEffectDelayExternal(); |
|
||||||
|
|
||||||
/// Specifiy which external memory to use
|
|
||||||
/// @param type specify which memory to use
|
|
||||||
BAAudioEffectDelayExternal(BALibrary::MemSelect type); |
|
||||||
|
|
||||||
/// Specify external memory, and how much of the memory to use
|
|
||||||
/// @param type specify which memory to use
|
|
||||||
/// @param delayLengthMs maximum delay length in milliseconds
|
|
||||||
BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs); |
|
||||||
virtual ~BAAudioEffectDelayExternal(); |
|
||||||
|
|
||||||
/// set the actual amount of delay on a given delay tap
|
|
||||||
/// @param channel specify channel tap 1-8
|
|
||||||
/// @param milliseconds specify how much delay for the specified tap
|
|
||||||
void delay(uint8_t channel, float milliseconds); |
|
||||||
|
|
||||||
/// Disable a delay channel tap
|
|
||||||
/// @param channel specify channel tap 1-8
|
|
||||||
void disable(uint8_t channel); |
|
||||||
|
|
||||||
virtual void update(void); |
|
||||||
|
|
||||||
static unsigned m_usingSPICount[2]; // internal use for all instances
|
|
||||||
|
|
||||||
private: |
|
||||||
void initialize(BALibrary::MemSelect mem, unsigned delayLength = 1e6); |
|
||||||
void read(uint32_t address, uint32_t count, int16_t *data); |
|
||||||
void write(uint32_t address, uint32_t count, const int16_t *data); |
|
||||||
void zero(uint32_t address, uint32_t count); |
|
||||||
unsigned m_memoryStart; // the first address in the memory we're using
|
|
||||||
unsigned m_memoryLength; // the amount of memory we're using
|
|
||||||
unsigned m_headOffset; // head index (incoming) data into external memory
|
|
||||||
unsigned m_channelDelayLength[8]; // # of sample delay for each channel (128 = no delay)
|
|
||||||
unsigned m_activeMask; // which output channels are active
|
|
||||||
static unsigned m_allocated[2]; |
|
||||||
audio_block_t *m_inputQueueArray[1]; |
|
||||||
|
|
||||||
BALibrary::MemSelect m_mem; |
|
||||||
SPIClass *m_spi = nullptr; |
|
||||||
int m_spiChannel = 0; |
|
||||||
int m_misoPin = 0; |
|
||||||
int m_mosiPin = 0; |
|
||||||
int m_sckPin = 0; |
|
||||||
int m_csPin = 0; |
|
||||||
|
|
||||||
void m_startUsingSPI(int spiBus); |
|
||||||
void m_stopUsingSPI(int spiBus); |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
} /* namespace BAEffects */ |
|
||||||
|
|
||||||
#endif /* __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H */ |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
@ -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,83 +0,0 @@ |
|||||||
/**************************************************************************//**
|
|
||||||
* @file |
|
||||||
* @author Steve Lascos |
|
||||||
* @company Blackaddr Audio |
|
||||||
* |
|
||||||
* This file constains precomputed co-efficients for the AudioEffectAnalogDelay |
|
||||||
* class. |
|
||||||
* |
|
||||||
* @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/>.
|
|
||||||
*****************************************************************************/ |
|
||||||
#include <cstdint> |
|
||||||
|
|
||||||
namespace BAEffects { |
|
||||||
|
|
||||||
// The number of stages in the analog-response Biquad filter
|
|
||||||
constexpr unsigned MAX_NUM_FILTER_STAGES = 4; |
|
||||||
constexpr unsigned NUM_COEFFS_PER_STAGE = 5; |
|
||||||
|
|
||||||
// Matlab/Octave can be helpful to design a filter. Once you have the IIR filter (bz,az) coefficients
|
|
||||||
// in the z-domain, they can be converted to second-order-sections. AudioEffectAnalogDelay is designed
|
|
||||||
// to accept up to a maximum of an 8th order filter, broken into four, 2nd order stages.
|
|
||||||
//
|
|
||||||
// Second order sections can be created with:
|
|
||||||
// [sos] = tf2sos(bz,az);
|
|
||||||
// The results coefficents must be converted the Q31 format required by the ARM CMSIS-DSP library. This means
|
|
||||||
// all coefficients must lie between -1.0 and +0.9999. If your (bz,az) coefficients exceed this, you must divide
|
|
||||||
// them down by a power of 2. For example, if your largest magnitude coefficient is -3.5, you must divide by
|
|
||||||
// 2^shift where 4=2^2 and thus shift = 2. You must then mutliply by 2^31 to get a 32-bit signed integer value
|
|
||||||
// that represents the required Q31 coefficient.
|
|
||||||
|
|
||||||
// BOSS DM-3 Filters
|
|
||||||
// b(z) = 1.0e-03 * (0.0032 0.0257 0.0900 0.1800 0.2250 0.1800 0.0900 0.0257 0.0032)
|
|
||||||
// a(z) = 1.0000 -5.7677 14.6935 -21.3811 19.1491 -10.5202 3.2584 -0.4244 -0.0067
|
|
||||||
constexpr unsigned DM3_NUM_STAGES = 4; |
|
||||||
constexpr unsigned DM3_COEFF_SHIFT = 2; |
|
||||||
constexpr int32_t DM3[5*MAX_NUM_FILTER_STAGES] = { |
|
||||||
536870912, 988616936, 455608573, 834606945, -482959709, |
|
||||||
536870912, 1031466345, 498793368, 965834205, -467402235, |
|
||||||
536870912, 1105821939, 573646688, 928470657, -448083489, |
|
||||||
2339, 5093, 2776, 302068995, 4412722 |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
// Blackaddr WARM Filter
|
|
||||||
// Butterworth, 8th order, cutoff = 2000 Hz
|
|
||||||
// Matlab/Octave command: [bz, az] = butter(8, 2000/44100/2);
|
|
||||||
// b(z) = 1.0e-05 * (0.0086 0.0689 0.2411 0.4821 0.6027 0.4821 0.2411 0.0689 0.0086_
|
|
||||||
// a(z) = 1.0000 -6.5399 18.8246 -31.1340 32.3473 -21.6114 9.0643 -2.1815 0.2306
|
|
||||||
constexpr unsigned WARM_NUM_STAGES = 4; |
|
||||||
constexpr unsigned WARM_COEFF_SHIFT = 2; |
|
||||||
constexpr int32_t WARM[5*MAX_NUM_FILTER_STAGES] = { |
|
||||||
536870912,1060309346,523602393,976869875,-481046241, |
|
||||||
536870912,1073413910,536711084,891250612,-391829326, |
|
||||||
536870912,1087173998,550475248,835222426,-333446881, |
|
||||||
46,92,46,807741349,-304811072 |
|
||||||
}; |
|
||||||
|
|
||||||
// Blackaddr DARK Filter
|
|
||||||
// Chebychev Type II, 8th order, stopband = 60db, cutoff = 1000 Hz
|
|
||||||
// Matlab command: [bz, az] = cheby2(8, 60, 1000/44100/2);
|
|
||||||
// b(z) = 0.0009 -0.0066 0.0219 -0.0423 0.0522 -0.0423 0.0219 -0.0066 0.0009
|
|
||||||
// a(z) = 1.0000 -7.4618 24.3762 -45.5356 53.1991 -39.8032 18.6245 -4.9829 0.5836
|
|
||||||
constexpr unsigned DARK_NUM_STAGES = 4; |
|
||||||
constexpr unsigned DARK_COEFF_SHIFT = 1; |
|
||||||
constexpr int32_t DARK[5*MAX_NUM_FILTER_STAGES] = { |
|
||||||
1073741824,-2124867808,1073741824,2107780229,-1043948409, |
|
||||||
1073741824,-2116080466,1073741824,2042553796,-979786242, |
|
||||||
1073741824,-2077777790,1073741824,1964779896,-904264933, |
|
||||||
957356,-1462833,957356,1896884898,-838694612 |
|
||||||
}; |
|
||||||
|
|
||||||
}; |
|
@ -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