forked from wirtz/BALibrary
parent
d38be82ea6
commit
070c305322
@ -0,0 +1,180 @@ |
||||
/*************************************************************************
|
||||
* This demo uses the BALibrary library to provide enhanced control of |
||||
* the TGA Pro board. |
||||
*
|
||||
* The latest copy of the BA Guitar library can be obtained from |
||||
* https://github.com/Blackaddr/BALibrary
|
||||
*
|
||||
* This example demonstrates teh BAAudioEffectsPitchShift effect. It can |
||||
* be controlled using the Blackaddr Audio "Expansion Control Board". |
||||
*
|
||||
* POT1 (left) controls amount of delay |
||||
* POT2 (right) controls amount of feedback |
||||
* POT3 (center) controls the wet/dry mix |
||||
* SW1 will enable/bypass the audio effect. LED1 will be on when effect is enabled. |
||||
* SW2 will cycle through the 3 pre-programmed analog filters. LED2 will be on when SW2 is pressed. |
||||
*
|
||||
*
|
||||
* Using the Serial Montitor, send 'u' and 'd' characters to increase or decrease |
||||
* the headphone volume between values of 0 and 9. |
||||
*/ |
||||
#define TGA_PRO_REVB // Set which hardware revision of the TGA Pro we're using
|
||||
#define TGA_PRO_EXPAND_REV2 // pull in the pin definitions for the Blackaddr Audio Expansion Board.
|
||||
|
||||
#include "BALibrary.h" |
||||
#include "BAEffects.h" |
||||
|
||||
using namespace BAEffects; |
||||
using namespace BALibrary; |
||||
|
||||
AudioInputI2S i2sIn; |
||||
AudioOutputI2S i2sOut; |
||||
BAAudioControlWM8731 codec; |
||||
|
||||
AudioEffectPitchShift pitchShift; |
||||
|
||||
AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
|
||||
|
||||
// Simply connect the input to the delay, and the output
|
||||
// to both i2s channels
|
||||
AudioConnection input(i2sIn,0, pitchShift,0); |
||||
AudioConnection effectOut(pitchShift, 0, cabFilter, 0); |
||||
AudioConnection leftOut(cabFilter,0, i2sOut, 0); |
||||
AudioConnection rightOut(cabFilter,0, i2sOut, 1); |
||||
|
||||
|
||||
//////////////////////////////////////////
|
||||
// SETUP PHYSICAL CONTROLS
|
||||
// - POT1 (left) will control the rate
|
||||
// - POT2 (right) will control the depth
|
||||
// - POT3 (centre) will control the volume
|
||||
// - SW1 (left) will be used as a bypass control
|
||||
// - LED1 (left) will be illuminated when the effect is ON (not bypass)
|
||||
// - SW2 (right) will be used to cycle through the the waveforms
|
||||
// - LED2 (right) will illuminate when pressing SW2.
|
||||
//////////////////////////////////////////
|
||||
// To get the calibration values for your particular board, first run the
|
||||
// BAExpansionCalibrate.ino example and
|
||||
constexpr int potCalibMin = 1; |
||||
constexpr int potCalibMax = 1018; |
||||
constexpr bool potSwapDirection = true; |
||||
|
||||
// Create a control object using the number of switches, pots, encoders and outputs on the
|
||||
// Blackaddr Audio Expansion Board.
|
||||
BAPhysicalControls controls(BA_EXPAND_NUM_SW, BA_EXPAND_NUM_POT, BA_EXPAND_NUM_ENC, BA_EXPAND_NUM_LED); |
||||
|
||||
int loopCount = 0; |
||||
constexpr unsigned MAX_HEADPHONE_VOL = 10; |
||||
unsigned headphoneVolume = 8; // control headphone volume from 0 to 10.
|
||||
|
||||
// BAPhysicalControls returns a handle when you register a new control. We'll uses these handles when working with the controls.
|
||||
int bypassHandle, volumeHandle, led1Handle, led2Handle; // Handles for the various controls
|
||||
|
||||
void setup() { |
||||
delay(100); // wait a bit for serial to be available
|
||||
Serial.begin(57600); // Start the serial port
|
||||
delay(100); |
||||
|
||||
// Setup the controls. The return value is the handle to use when checking for control changes, etc.
|
||||
// pushbuttons
|
||||
bypassHandle = controls.addSwitch(BA_EXPAND_SW1_PIN); // will be used for bypass control
|
||||
//button2Handle = controls.addSwitch(BA_EXPAND_SW2_PIN); // will be used for stepping through filters
|
||||
// pots
|
||||
//rateHandle = controls.addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); // control the amount of delay
|
||||
//depthHandle = controls.addPot(BA_EXPAND_POT2_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||
volumeHandle = controls.addPot(BA_EXPAND_POT3_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||
// leds
|
||||
led1Handle = controls.addOutput(BA_EXPAND_LED1_PIN); |
||||
led2Handle = controls.addOutput(BA_EXPAND_LED2_PIN); // will illuminate when pressing SW2
|
||||
|
||||
// Disable the audio codec first
|
||||
codec.disable(); |
||||
AudioMemory(128); |
||||
|
||||
// Enable and configure the codec
|
||||
Serial.println("Enabling codec...\n"); |
||||
codec.enable(); |
||||
codec.setHeadphoneVolume(1.0f); // Max headphone volume
|
||||
|
||||
// Besure to enable the pitchShift. When disabled, audio is is completely blocked by the effect
|
||||
// to minimize resource usage to nearly to nearly zero.
|
||||
pitchShift.enable();
|
||||
|
||||
// Set some default values.
|
||||
// These can be changed using the controls on the Blackaddr Audio Expansion Board
|
||||
pitchShift.bypass(false); |
||||
|
||||
// Guitar cabinet: Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
|
||||
cabFilter.setLowpass(0, 4500, .7071); |
||||
cabFilter.setLowpass(1, 4500, .7071); |
||||
} |
||||
|
||||
void loop() { |
||||
|
||||
float potValue; |
||||
|
||||
// Check if SW1 has been toggled (pushed)
|
||||
if (controls.isSwitchToggled(bypassHandle)) { |
||||
bool bypass = pitchShift.isBypass(); // get the current state
|
||||
bypass = !bypass; // change it
|
||||
pitchShift.bypass(bypass); // set the new state
|
||||
controls.setOutput(led1Handle, !bypass); // Set the LED when NOT bypassed
|
||||
Serial.println(String("BYPASS is ") + bypass); |
||||
} |
||||
|
||||
// // Use SW2 to cycle through the waveforms
|
||||
// controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle));
|
||||
// if (controls.isSwitchToggled(waveformHandle)) {
|
||||
// }
|
||||
|
||||
// // Use POT1 (left) to control the rate setting
|
||||
// if (controls.checkPotValue(rateHandle, potValue)) {
|
||||
// // Pot has changed
|
||||
// Serial.println(String("New RATE setting: ") + potValue);
|
||||
// pitchShift.rate(potValue);
|
||||
// }
|
||||
|
||||
// // Use POT2 (right) to control the depth setting
|
||||
// if (controls.checkPotValue(depthHandle, potValue)) {
|
||||
// // Pot has changed
|
||||
// Serial.println(String("New DEPTH setting: ") + potValue);
|
||||
// pitchShift.depth(potValue);
|
||||
// }
|
||||
|
||||
// Use POT3 (centre) to control the volume setting
|
||||
if (controls.checkPotValue(volumeHandle, potValue)) { |
||||
// Pot has changed
|
||||
Serial.println(String("New VOLUME setting: ") + potValue); |
||||
pitchShift.volume(potValue); |
||||
} |
||||
|
||||
// Use the 'u' and 'd' keys to adjust volume across ten levels.
|
||||
if (Serial) { |
||||
if (Serial.available() > 0) { |
||||
while (Serial.available()) { |
||||
char key = Serial.read(); |
||||
if (key == 'u') {
|
||||
headphoneVolume = (headphoneVolume + 1) % MAX_HEADPHONE_VOL; |
||||
Serial.println(String("Increasing HEADPHONE volume to ") + headphoneVolume); |
||||
} |
||||
else if (key == 'd') {
|
||||
headphoneVolume = (headphoneVolume - 1) % MAX_HEADPHONE_VOL; |
||||
Serial.println(String("Decreasing HEADPHONE volume to ") + headphoneVolume); |
||||
} |
||||
codec.setHeadphoneVolume(static_cast<float>(headphoneVolume) / static_cast<float>(MAX_HEADPHONE_VOL)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Use the loopCounter to roughly measure human timescales. Every few seconds, print the CPU usage
|
||||
// to the serial port. About 500,000 loops!
|
||||
//if (loopCount % 524288 == 0) {
|
||||
if (loopCount % 25000 == 0) { |
||||
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage()); |
||||
Serial.print("% "); |
||||
Serial.print(" pitchShift: "); Serial.print(pitchShift.processorUsage()); |
||||
Serial.println("%"); |
||||
} |
||||
loopCount++; |
||||
|
||||
} |
@ -0,0 +1,180 @@ |
||||
/*************************************************************************
|
||||
* This demo uses the BALibrary library to provide enhanced control of |
||||
* the TGA Pro board. |
||||
*
|
||||
* The latest copy of the BA Guitar library can be obtained from |
||||
* https://github.com/Blackaddr/BALibrary
|
||||
*
|
||||
* This example demonstrates teh BAAudioEffectsTemplate effect. It can |
||||
* be controlled using the Blackaddr Audio "Expansion Control Board". |
||||
*
|
||||
* POT1 (left) controls amount of delay |
||||
* POT2 (right) controls amount of feedback |
||||
* POT3 (center) controls the wet/dry mix |
||||
* SW1 will enable/bypass the audio effect. LED1 will be on when effect is enabled. |
||||
* SW2 will cycle through the 3 pre-programmed analog filters. LED2 will be on when SW2 is pressed. |
||||
*
|
||||
*
|
||||
* Using the Serial Montitor, send 'u' and 'd' characters to increase or decrease |
||||
* the headphone volume between values of 0 and 9. |
||||
*/ |
||||
#define TGA_PRO_REVB // Set which hardware revision of the TGA Pro we're using
|
||||
#define TGA_PRO_EXPAND_REV2 // pull in the pin definitions for the Blackaddr Audio Expansion Board.
|
||||
|
||||
#include "BALibrary.h" |
||||
#include "BAEffects.h" |
||||
|
||||
using namespace BAEffects; |
||||
using namespace BALibrary; |
||||
|
||||
AudioInputI2S i2sIn; |
||||
AudioOutputI2S i2sOut; |
||||
BAAudioControlWM8731 codec; |
||||
|
||||
AudioEffectTemplate templateEffect; |
||||
|
||||
AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
|
||||
|
||||
// Simply connect the input to the delay, and the output
|
||||
// to both i2s channels
|
||||
AudioConnection input(i2sIn,0, templateEffect,0); |
||||
AudioConnection effectOut(templateEffect, 0, cabFilter, 0); |
||||
AudioConnection leftOut(cabFilter,0, i2sOut, 0); |
||||
AudioConnection rightOut(cabFilter,0, i2sOut, 1); |
||||
|
||||
|
||||
//////////////////////////////////////////
|
||||
// SETUP PHYSICAL CONTROLS
|
||||
// - POT1 (left) will control the rate
|
||||
// - POT2 (right) will control the depth
|
||||
// - POT3 (centre) will control the volume
|
||||
// - SW1 (left) will be used as a bypass control
|
||||
// - LED1 (left) will be illuminated when the effect is ON (not bypass)
|
||||
// - SW2 (right) will be used to cycle through the the waveforms
|
||||
// - LED2 (right) will illuminate when pressing SW2.
|
||||
//////////////////////////////////////////
|
||||
// To get the calibration values for your particular board, first run the
|
||||
// BAExpansionCalibrate.ino example and
|
||||
constexpr int potCalibMin = 1; |
||||
constexpr int potCalibMax = 1018; |
||||
constexpr bool potSwapDirection = true; |
||||
|
||||
// Create a control object using the number of switches, pots, encoders and outputs on the
|
||||
// Blackaddr Audio Expansion Board.
|
||||
BAPhysicalControls controls(BA_EXPAND_NUM_SW, BA_EXPAND_NUM_POT, BA_EXPAND_NUM_ENC, BA_EXPAND_NUM_LED); |
||||
|
||||
int loopCount = 0; |
||||
constexpr unsigned MAX_HEADPHONE_VOL = 10; |
||||
unsigned headphoneVolume = 8; // control headphone volume from 0 to 10.
|
||||
|
||||
// BAPhysicalControls returns a handle when you register a new control. We'll uses these handles when working with the controls.
|
||||
int bypassHandle, volumeHandle, led1Handle, led2Handle; // Handles for the various controls
|
||||
|
||||
void setup() { |
||||
delay(100); // wait a bit for serial to be available
|
||||
Serial.begin(57600); // Start the serial port
|
||||
delay(100); |
||||
|
||||
// Setup the controls. The return value is the handle to use when checking for control changes, etc.
|
||||
// pushbuttons
|
||||
bypassHandle = controls.addSwitch(BA_EXPAND_SW1_PIN); // will be used for bypass control
|
||||
//button2Handle = controls.addSwitch(BA_EXPAND_SW2_PIN); // will be used for stepping through filters
|
||||
// pots
|
||||
//rateHandle = controls.addPot(BA_EXPAND_POT1_PIN, potCalibMin, potCalibMax, potSwapDirection); // control the amount of delay
|
||||
//depthHandle = controls.addPot(BA_EXPAND_POT2_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||
volumeHandle = controls.addPot(BA_EXPAND_POT3_PIN, potCalibMin, potCalibMax, potSwapDirection);
|
||||
// leds
|
||||
led1Handle = controls.addOutput(BA_EXPAND_LED1_PIN); |
||||
led2Handle = controls.addOutput(BA_EXPAND_LED2_PIN); // will illuminate when pressing SW2
|
||||
|
||||
// Disable the audio codec first
|
||||
codec.disable(); |
||||
AudioMemory(128); |
||||
|
||||
// Enable and configure the codec
|
||||
Serial.println("Enabling codec...\n"); |
||||
codec.enable(); |
||||
codec.setHeadphoneVolume(1.0f); // Max headphone volume
|
||||
|
||||
// Besure to enable the templateEffect. When disabled, audio is is completely blocked by the effect
|
||||
// to minimize resource usage to nearly to nearly zero.
|
||||
templateEffect.enable();
|
||||
|
||||
// Set some default values.
|
||||
// These can be changed using the controls on the Blackaddr Audio Expansion Board
|
||||
templateEffect.bypass(false); |
||||
|
||||
// Guitar cabinet: Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
|
||||
cabFilter.setLowpass(0, 4500, .7071); |
||||
cabFilter.setLowpass(1, 4500, .7071); |
||||
} |
||||
|
||||
void loop() { |
||||
|
||||
float potValue; |
||||
|
||||
// Check if SW1 has been toggled (pushed)
|
||||
if (controls.isSwitchToggled(bypassHandle)) { |
||||
bool bypass = templateEffect.isBypass(); // get the current state
|
||||
bypass = !bypass; // change it
|
||||
templateEffect.bypass(bypass); // set the new state
|
||||
controls.setOutput(led1Handle, !bypass); // Set the LED when NOT bypassed
|
||||
Serial.println(String("BYPASS is ") + bypass); |
||||
} |
||||
|
||||
// // Use SW2 to cycle through the waveforms
|
||||
// controls.setOutput(led2Handle, controls.getSwitchValue(led2Handle));
|
||||
// if (controls.isSwitchToggled(waveformHandle)) {
|
||||
// }
|
||||
|
||||
// // Use POT1 (left) to control the rate setting
|
||||
// if (controls.checkPotValue(rateHandle, potValue)) {
|
||||
// // Pot has changed
|
||||
// Serial.println(String("New RATE setting: ") + potValue);
|
||||
// templateEffect.rate(potValue);
|
||||
// }
|
||||
|
||||
// // Use POT2 (right) to control the depth setting
|
||||
// if (controls.checkPotValue(depthHandle, potValue)) {
|
||||
// // Pot has changed
|
||||
// Serial.println(String("New DEPTH setting: ") + potValue);
|
||||
// templateEffect.depth(potValue);
|
||||
// }
|
||||
|
||||
// Use POT3 (centre) to control the volume setting
|
||||
if (controls.checkPotValue(volumeHandle, potValue)) { |
||||
// Pot has changed
|
||||
Serial.println(String("New VOLUME setting: ") + potValue); |
||||
templateEffect.volume(potValue); |
||||
} |
||||
|
||||
// Use the 'u' and 'd' keys to adjust volume across ten levels.
|
||||
if (Serial) { |
||||
if (Serial.available() > 0) { |
||||
while (Serial.available()) { |
||||
char key = Serial.read(); |
||||
if (key == 'u') {
|
||||
headphoneVolume = (headphoneVolume + 1) % MAX_HEADPHONE_VOL; |
||||
Serial.println(String("Increasing HEADPHONE volume to ") + headphoneVolume); |
||||
} |
||||
else if (key == 'd') {
|
||||
headphoneVolume = (headphoneVolume - 1) % MAX_HEADPHONE_VOL; |
||||
Serial.println(String("Decreasing HEADPHONE volume to ") + headphoneVolume); |
||||
} |
||||
codec.setHeadphoneVolume(static_cast<float>(headphoneVolume) / static_cast<float>(MAX_HEADPHONE_VOL)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Use the loopCounter to roughly measure human timescales. Every few seconds, print the CPU usage
|
||||
// to the serial port. About 500,000 loops!
|
||||
//if (loopCount % 524288 == 0) {
|
||||
if (loopCount % 25000 == 0) { |
||||
Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage()); |
||||
Serial.print("% "); |
||||
Serial.print(" templateEffect: "); Serial.print(templateEffect.processorUsage()); |
||||
Serial.println("%"); |
||||
} |
||||
loopCount++; |
||||
|
||||
} |
@ -0,0 +1,148 @@ |
||||
/**************************************************************************//**
|
||||
* @file |
||||
* @author Steve Lascos |
||||
* @company Blackaddr Audio |
||||
* |
||||
* PitchShift for creating your own audio effects |
||||
* |
||||
* @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_AUDIOEFFECTPITCHSHIFT_H |
||||
#define __BAEFFECTS_AUDIOEFFECTPITCHSHIFT_H |
||||
|
||||
#include <Audio.h> |
||||
#include "BATypes.h" |
||||
#include "LibBasicFunctions.h" |
||||
|
||||
namespace BAEffects { |
||||
|
||||
/**************************************************************************//**
|
||||
* AudioEffectPitchShift |
||||
*****************************************************************************/ |
||||
class AudioEffectPitchShift : public AudioStream { |
||||
public: |
||||
|
||||
static constexpr unsigned ANALYSIS_SIZE = 1024; |
||||
static constexpr unsigned FFT_OVERSAMPLE_FACTOR = 1; |
||||
static constexpr float FFT_OVERSAMPLE_FACTOR_F = 1.0f; |
||||
static constexpr unsigned SYNTHESIS_SIZE = ANALYSIS_SIZE * FFT_OVERSAMPLE_FACTOR; |
||||
static constexpr float SYNTHESIS_SIZE_F = (float)(ANALYSIS_SIZE * FFT_OVERSAMPLE_FACTOR); |
||||
static constexpr float OVERLAP_FACTOR_F = (float)ANALYSIS_SIZE / (float)AUDIO_BLOCK_SAMPLES; |
||||
|
||||
///< List of AudioEffectTremolo MIDI controllable parameters
|
||||
enum { |
||||
BYPASS = 0, ///< controls effect bypass
|
||||
VOLUME, ///< controls the output volume level
|
||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
||||
}; |
||||
|
||||
// *** CONSTRUCTORS ***
|
||||
AudioEffectPitchShift(); |
||||
|
||||
virtual ~AudioEffectPitchShift(); ///< Destructor
|
||||
|
||||
// *** PARAMETERS ***
|
||||
|
||||
/// 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::RingBuffer<audio_block_t*> m_inputFifo = BALibrary::RingBuffer<audio_block_t*>(ANALYSIS_SIZE/AUDIO_BLOCK_SAMPLES);
|
||||
float m_analysisBuffer[ANALYSIS_SIZE]; |
||||
float m_analysisFreqBuffer[2*ANALYSIS_SIZE]; |
||||
float m_synthesisFreqBuffer[2*SYNTHESIS_SIZE]; |
||||
float m_synthesisBuffer[SYNTHESIS_SIZE]; |
||||
|
||||
bool m_initFailed = false; |
||||
|
||||
unsigned m_frameIndex = 0; |
||||
|
||||
// arm_rfft_instance_f32 fftFwdReal, fftInvReal;
|
||||
// arm_cfft_radix4_instance_f32 fftFwdComplex, fftInvComplex;
|
||||
// float32_t *bufInputReal;
|
||||
// float32_t *bufInputComplex;
|
||||
// float32_t *bufOutputReal;
|
||||
// float32_t *bufOutputComplex;
|
||||
|
||||
//arm_cfft_radix4_instance_f32 fft_inst_fwd, fft_inst_inv;
|
||||
//arm_rfft_instance_f32 rfftForwardInst, rfftInverseInst;
|
||||
arm_cfft_radix4_instance_f32 cfftForwardInst, cfftInverseInst; |
||||
|
||||
//uint8_t ifftFlag = 0; // 0 is FFT, 1 is IFFT
|
||||
//uint8_t doBitReverse = 1;
|
||||
|
||||
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_volume = 1.0f; |
||||
float m_pitchScale = 1.0f; |
||||
|
||||
void m_ocean(float *inputFreq, float *outputFreq, float frameIndex, float pitchScale); |
||||
|
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif /* __BAEFFECTS_AUDIOEFFECTPITCHSHIFT_H */ |
@ -0,0 +1,113 @@ |
||||
/**************************************************************************//**
|
||||
* @file |
||||
* @author Steve Lascos |
||||
* @company Blackaddr Audio |
||||
* |
||||
* Template for creating your own audio effects |
||||
* |
||||
* @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_AUDIOEFFECTTEMPLATE_H |
||||
#define __BAEFFECTS_AUDIOEFFECTTEMPLATE_H |
||||
|
||||
#include <Audio.h> |
||||
#include "LibBasicFunctions.h" |
||||
|
||||
namespace BAEffects { |
||||
|
||||
/**************************************************************************//**
|
||||
* AudioEffectTemplate |
||||
*****************************************************************************/ |
||||
class AudioEffectTemplate : public AudioStream { |
||||
public: |
||||
|
||||
///< List of AudioEffectTremolo MIDI controllable parameters
|
||||
enum { |
||||
BYPASS = 0, ///< controls effect bypass
|
||||
VOLUME, ///< controls the output volume level
|
||||
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
||||
}; |
||||
|
||||
// *** CONSTRUCTORS ***
|
||||
AudioEffectTemplate(); |
||||
|
||||
virtual ~AudioEffectTemplate(); ///< Destructor
|
||||
|
||||
// *** PARAMETERS ***
|
||||
|
||||
/// 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]; |
||||
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_volume = 1.0f; |
||||
|
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif /* __BAEFFECTS_AUDIOEFFECTTEMPLATE_H */ |
@ -0,0 +1,203 @@ |
||||
/*
|
||||
* AudioEffectPitchShift.cpp |
||||
* |
||||
* Created on: June 20, 2019 |
||||
* Author: slascos |
||||
*/ |
||||
#include <cmath> // std::roundf |
||||
#include "AudioEffectPitchShift.h" |
||||
|
||||
using namespace BALibrary; |
||||
|
||||
namespace BAEffects { |
||||
|
||||
constexpr int MIDI_CHANNEL = 0; |
||||
constexpr int MIDI_CONTROL = 1; |
||||
|
||||
constexpr unsigned NUM_AUDIO_BLOCKS = AudioEffectPitchShift::ANALYSIS_SIZE / AUDIO_BLOCK_SAMPLES; |
||||
constexpr uint32_t FFT_FORWARD = 0; |
||||
constexpr uint32_t FFT_INVERSE = 1; |
||||
constexpr uint32_t FFT_DO_BIT_REVERSE = 1; |
||||
|
||||
AudioEffectPitchShift::AudioEffectPitchShift() |
||||
: AudioStream(1, m_inputQueueArray) |
||||
{ |
||||
// clear the audio buffer to avoid pops
|
||||
for (unsigned i=0; i<AudioEffectPitchShift::ANALYSIS_SIZE; i++) { |
||||
m_analysisBuffer[i] = 0.0f; |
||||
} |
||||
|
||||
// Configure the FFT
|
||||
// arm_rfft_init_f32(&rfftForwardInst, &cfftForwardInst, AudioEffectPitchShift::ANALYSIS_SIZE,
|
||||
// FFT_FORWARD, FFT_DO_BIT_REVERSE);
|
||||
// arm_rfft_init_f32(&rfftInverseInst, &cfftInverseInst, AudioEffectPitchShift::SYNTHESIS_SIZE,
|
||||
// FFT_INVERSE, FFT_DO_BIT_REVERSE);
|
||||
unsigned ret; |
||||
ret = arm_cfft_radix4_init_f32(&cfftForwardInst, ANALYSIS_SIZE, FFT_FORWARD, FFT_DO_BIT_REVERSE); //init FFT
|
||||
if (!ret) { m_initFailed = true; }; |
||||
ret = arm_cfft_radix4_init_f32(&cfftInverseInst, SYNTHESIS_SIZE, FFT_INVERSE, FFT_DO_BIT_REVERSE); //init FFT
|
||||
if (!ret) { m_initFailed = true; }; |
||||
} |
||||
|
||||
AudioEffectPitchShift::~AudioEffectPitchShift() |
||||
{ |
||||
} |
||||
|
||||
void AudioEffectPitchShift::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); |
||||
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 HERE
|
||||
// Update the fifo
|
||||
// m_inputFifo.push_back(inputAudioBlock); // insert the new block
|
||||
// release(m_inputFifo.front()); //
|
||||
// m_inputFifo.pop_front();
|
||||
|
||||
// Convert the contents of the audio blocks to the contiguous buffer
|
||||
// 1) Be aware the audio library stores audio samples in reverse temporal order.
|
||||
// This means the first sample (in time) is in the last location of the buffer.
|
||||
// 2) the oldest audio is at the front of the queue, the latest at the back
|
||||
float *analysisPtr = &m_analysisBuffer[0]; |
||||
float *analysisFreqPtr = &m_analysisFreqBuffer[0]; |
||||
float *synthesisFreqPtr = &m_synthesisFreqBuffer[0]; |
||||
float *synthesisPtr = &m_synthesisBuffer[0]; |
||||
|
||||
// first shift the contents of the float buffer up by AUDIO_BLOCK SAMPLES
|
||||
for (unsigned i=0; i<NUM_AUDIO_BLOCKS-1; i++) { |
||||
memcpy(&analysisPtr[i*AUDIO_BLOCK_SAMPLES], &analysisPtr[(i+1)*AUDIO_BLOCK_SAMPLES], AUDIO_BLOCK_SAMPLES*sizeof(float)); |
||||
} |
||||
// Convert the newest incoming audio block to float
|
||||
arm_q15_to_float(inputAudioBlock->data, &analysisPtr[(NUM_AUDIO_BLOCKS-1)*AUDIO_BLOCK_SAMPLES], AUDIO_BLOCK_SAMPLES); |
||||
release(inputAudioBlock); // were done with it now
|
||||
|
||||
//if (m_initFailed) { Serial.println("FFT INIT FAILED"); }
|
||||
|
||||
// Construct the interleaved FFT buffer
|
||||
unsigned idx = 0; |
||||
for (unsigned i=0; i<ANALYSIS_SIZE; i++) { |
||||
m_analysisFreqBuffer[idx] = analysisPtr[i]; |
||||
m_analysisFreqBuffer[idx+1] = 0; |
||||
idx += 2; |
||||
} |
||||
|
||||
// Perform the FFT
|
||||
arm_cfft_radix4_f32(&cfftForwardInst, analysisFreqPtr); |
||||
|
||||
|
||||
// perform the ocean pitch shift
|
||||
m_ocean(analysisFreqPtr, synthesisFreqPtr, (float)(m_frameIndex), m_pitchScale); |
||||
//memcpy(synthesisFreqPtr, analysisFreqPtr, 2*ANALYSIS_SIZE*sizeof(float));
|
||||
|
||||
// Perform the inverse FFT
|
||||
arm_cfft_radix4_f32(&cfftInverseInst, synthesisFreqPtr); |
||||
|
||||
// Deinterleave the synthesis buffer
|
||||
idx = 0; |
||||
for (unsigned i=0; i<(2*SYNTHESIS_SIZE); i=i+2) { |
||||
m_synthesisBuffer[idx] = synthesisFreqPtr[i]; |
||||
idx++; |
||||
} |
||||
|
||||
// Convert the float buffer back to integer
|
||||
audio_block_t *outputBlock = allocate(); |
||||
arm_float_to_q15 (synthesisPtr, outputBlock->data, AUDIO_BLOCK_SAMPLES); |
||||
|
||||
transmit(outputBlock); |
||||
release(outputBlock); |
||||
m_frameIndex++; |
||||
} |
||||
|
||||
void AudioEffectPitchShift::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("AudioEffectPitchShift::not bypassed -> ON") + value); } |
||||
else { bypass(true); Serial.println(String("AudioEffectPitchShift::bypassed -> OFF") + value); } |
||||
return; |
||||
} |
||||
|
||||
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
||||
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
||||
// Volume
|
||||
Serial.println(String("AudioEffectPitchShift::volume: ") + 100*val + String("%")); |
||||
volume(val); |
||||
return; |
||||
} |
||||
|
||||
} |
||||
|
||||
void AudioEffectPitchShift::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; |
||||
} |
||||
|
||||
void AudioEffectPitchShift::m_ocean(float *inputFreq, float *outputFreq, float frameIndex, float pitchScale) |
||||
{ |
||||
// zero the output buffer
|
||||
for (unsigned i=0; i<(2*SYNTHESIS_SIZE); i++) { |
||||
outputFreq[i] = 0.0f; |
||||
} |
||||
|
||||
float phaseAdjustFactor = -((2.0f*((float)(M_PI))*frameIndex) |
||||
/ (OVERLAP_FACTOR_F * FFT_OVERSAMPLE_FACTOR_F * SYNTHESIS_SIZE_F)); |
||||
|
||||
for (unsigned k=1; k < SYNTHESIS_SIZE/2; k++) { |
||||
|
||||
float a = (float)k; |
||||
// b = mka + 0.5
|
||||
// where m is the FFT oversample factor, k is the pitch scaling, a
|
||||
// is the original bin number
|
||||
float b = std::roundf( (FFT_OVERSAMPLE_FACTOR_F * pitchScale * a)); |
||||
unsigned b_int = (unsigned)(b); |
||||
|
||||
if (b_int < SYNTHESIS_SIZE/2) { |
||||
|
||||
// phaseAdjust = (b-ma) * phaseAdjustFactor
|
||||
float phaseAdjust = (b - (FFT_OVERSAMPLE_FACTOR_F * a)) * phaseAdjustFactor; |
||||
|
||||
float a_real = inputFreq[2*k]; |
||||
float a_imag = inputFreq[2*k+1]; |
||||
|
||||
outputFreq[2*b_int] = (a_real * arm_cos_f32(phaseAdjust)) - (a_imag * arm_sin_f32(phaseAdjust)); |
||||
outputFreq[2*b_int+1] = (a_real * arm_sin_f32(phaseAdjust)) + (a_imag * arm_cos_f32(phaseAdjust)); |
||||
} |
||||
|
||||
// update the imag components
|
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
|
@ -0,0 +1,94 @@ |
||||
/*
|
||||
* AudioEffectTemplate.cpp |
||||
* |
||||
* Created on: June 20, 2019 |
||||
* Author: slascos |
||||
*/ |
||||
#include <cmath> // std::roundf |
||||
#include "AudioEffectTemplate.h" |
||||
|
||||
using namespace BALibrary; |
||||
|
||||
namespace BAEffects { |
||||
|
||||
constexpr int MIDI_CHANNEL = 0; |
||||
constexpr int MIDI_CONTROL = 1; |
||||
|
||||
AudioEffectTemplate::AudioEffectTemplate() |
||||
: AudioStream(1, m_inputQueueArray) |
||||
{ |
||||
} |
||||
|
||||
AudioEffectTemplate::~AudioEffectTemplate() |
||||
{ |
||||
} |
||||
|
||||
void AudioEffectTemplate::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 HERE
|
||||
|
||||
transmit(inputAudioBlock); |
||||
release(inputAudioBlock); |
||||
} |
||||
|
||||
void AudioEffectTemplate::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("AudioEffectTemplate::not bypassed -> ON") + value); } |
||||
else { bypass(true); Serial.println(String("AudioEffectTemplate::bypassed -> OFF") + value); } |
||||
return; |
||||
} |
||||
|
||||
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
||||
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
||||
// Volume
|
||||
Serial.println(String("AudioEffectTemplate::volume: ") + 100*val + String("%")); |
||||
volume(val); |
||||
return; |
||||
} |
||||
|
||||
} |
||||
|
||||
void AudioEffectTemplate::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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
|
Loading…
Reference in new issue