Added new AudioEffectAnalogDelay, and restructured the library

master
Steve Lascos 6 years ago
parent 429c37f8ac
commit 62f01fc62a
  1. 5
      README.md
  2. 3
      examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino
  3. 113
      examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino
  4. 7
      src/LibBasicFunctions.h
  5. 4
      src/LibMemoryManagement.h
  6. 5
      src/common/AudioHelpers.cpp
  7. 7
      src/common/ExternalSramManager.cpp
  8. 38
      src/effects/AudioEffectAnalogDelay.cpp
  9. 4
      src/peripherals/BASpiMemory.cpp

@ -5,9 +5,12 @@ Tested with:
Arduino IDE: 1.8.4 Arduino IDE: 1.8.4
Teensyduinio: 1.39 Teensyduinio: 1.39
*** This library requires a forked version of DmaSpi. You must download the version from here: ***
*** https://github.com/Blackaddr/DmaSpi ***
*** If this library fails to compile, please ensure you have updated your Arduino IDE/Teensyduino at least to the versions listed above. *** *** If this library fails to compile, please ensure you have updated your Arduino IDE/Teensyduino at least to the versions listed above. ***
This library contains convience C++ classes to allow full and easy access to the features of the Teensy Guitar Audio Series of boards. This library contains convienence C++ classes to allow full and easy access to the features of the Teensy Guitar Audio Series of boards.
"Teensy" is an Arduino compatible series of boards from www.pjrc.com "Teensy" is an Arduino compatible series of boards from www.pjrc.com

@ -35,13 +35,14 @@ AudioConnection patch2(delayModule,0, gainModule, 0); // send the delay to
AudioConnection patch2b(gainModule, 0, reverb, 0); // then to the reverb AudioConnection patch2b(gainModule, 0, reverb, 0); // then to the reverb
AudioConnection patch1(i2sIn,1, mixer,0); // mixer input 0 is our original dry signal AudioConnection patch1(i2sIn,0, mixer,0); // mixer input 0 is our original dry signal
AudioConnection patch3(reverb, 0, mixer, 1); // mixer input 1 is our wet AudioConnection patch3(reverb, 0, mixer, 1); // mixer input 1 is our wet
AudioConnection patch4(mixer, 0, cabFilter, 0); // mixer outpt to the cabinet filter AudioConnection patch4(mixer, 0, cabFilter, 0); // mixer outpt to the cabinet filter
AudioConnection patch5(cabFilter, 0, i2sOut, 0); // connect the cab filter to the output. AudioConnection patch5(cabFilter, 0, i2sOut, 0); // connect the cab filter to the output.
AudioConnection patch5b(cabFilter, 0, i2sOut, 1); // connect the cab filter to the output.
void setup() { void setup() {

@ -7,86 +7,99 @@ AudioInputI2S i2sIn;
AudioOutputI2S i2sOut; AudioOutputI2S i2sOut;
BAAudioControlWM8731 codec; BAAudioControlWM8731 codec;
#define USE_EXT /// IMPORTANT /////
// YOU MUST COMPILE THIS DEMO USING Serial + Midi
//#define USE_EXT // uncomment this line to use External MEM0
#define MIDI_DEBUG // uncomment to see raw MIDI info in terminal
#ifdef USE_EXT #ifdef USE_EXT
// If using external SPI memory, we will instantiance an SRAM // If using external SPI memory, we will instantiance an SRAM
// manager and create an external memory slot to use as the memory // manager and create an external memory slot to use as the memory
// for our audio delay // for our audio delay
ExternalSramManager externalSram(1); // Manage only one SRAM. ExternalSramManager externalSram;
ExtMemSlot delaySlot; // Declare an external memory slot. ExtMemSlot delaySlot; // Declare an external memory slot.
// Instantiate the AudioEffectAnalogDelay to use external memory by // Instantiate the AudioEffectAnalogDelay to use external memory by
/// passing it the delay slot. /// passing it the delay slot.
AudioEffectAnalogDelay myDelay(&delaySlot); AudioEffectAnalogDelay analogDelay(&delaySlot);
#else #else
// If using internal memory, we will instantiate the AudioEffectAnalogDelay // If using internal memory, we will instantiate the AudioEffectAnalogDelay
// by passing it the maximum amount of delay we will use in millseconds. Note that // by passing it the maximum amount of delay we will use in millseconds. Note that
// audio delay lengths are very limited when using internal memory due to limited // audio delay lengths are very limited when using internal memory due to limited
// internal RAM size. // internal RAM size.
AudioEffectAnalogDelay myDelay(200.0f); // max delay of 200 ms. AudioEffectAnalogDelay analogDelay(200.0f); // max delay of 200 ms.
#endif #endif
//AudioMixer4 mixer; // Used to mix the original dry with the wet (effects) path. AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
//
//AudioConnection patch0(i2sIn,0, myDelay,0);
//AudioConnection mixerDry(i2sIn,0, mixer,0);
//AudioConnection mixerWet(myDelay,0, mixer,1);
AudioConnection input(i2sIn,0, myDelay,0); // Record the audio to the PC
AudioConnection leftOut(myDelay,0, i2sOut, 0); //AudioOutputUSB usb;
int loopCount = 0; // Simply connect the input to the delay, and the output
// to both i2s channels
AudioConnection input(i2sIn,0, analogDelay,0);
AudioConnection delayOut(analogDelay, 0, cabFilter, 0);
AudioConnection leftOut(cabFilter,0, i2sOut, 0);
AudioConnection rightOut(cabFilter,0, i2sOut, 1);
//AudioConnection leftOutUSB(cabFilter,0, usb, 0);
//AudioConnection rightOutUSB(cabFilter,0, usb, 1);
AudioConnection rightOut(myDelay,0, i2sOut, 1); int loopCount = 0;
void setup() { void setup() {
delay(100); delay(100);
Serial.begin(57600); Serial.begin(57600); // Start the serial port
// Disable the codec first
codec.disable(); codec.disable();
delay(100); delay(100);
AudioMemory(128); AudioMemory(128);
delay(5); delay(5);
// Setup MIDI // Enable the codec
//usbMIDI.setHandleControlChange(OnControlChange);
Serial.println("Enabling codec...\n"); Serial.println("Enabling codec...\n");
codec.enable(); codec.enable();
delay(100); delay(100);
// If using external memory request request memory from the manager
// for the slot
#ifdef USE_EXT #ifdef USE_EXT
Serial.println("Using EXTERNAL memory"); Serial.println("Using EXTERNAL memory");
// We have to request memory be allocated to our slot. // We have to request memory be allocated to our slot.
externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM1, true); externalSram.requestMemory(&delaySlot, 500.0f, MemSelect::MEM0, true);
#else #else
Serial.println("Using INTERNAL memory"); Serial.println("Using INTERNAL memory");
#endif #endif
// Configure which MIDI CC's will control the effects // Configure which MIDI CC's will control the effect parameters
myDelay.mapMidiControl(AudioEffectAnalogDelay::BYPASS,16); analogDelay.mapMidiControl(AudioEffectAnalogDelay::BYPASS,16);
myDelay.mapMidiControl(AudioEffectAnalogDelay::DELAY,20); analogDelay.mapMidiControl(AudioEffectAnalogDelay::DELAY,20);
myDelay.mapMidiControl(AudioEffectAnalogDelay::FEEDBACK,21); analogDelay.mapMidiControl(AudioEffectAnalogDelay::FEEDBACK,21);
myDelay.mapMidiControl(AudioEffectAnalogDelay::MIX,22); analogDelay.mapMidiControl(AudioEffectAnalogDelay::MIX,22);
myDelay.mapMidiControl(AudioEffectAnalogDelay::VOLUME,23); analogDelay.mapMidiControl(AudioEffectAnalogDelay::VOLUME,23);
// Besure to enable the delay, by default it's processing is off. // Besure to enable the delay. When disabled, audio is is completely blocked
myDelay.enable(); // to minimize resources to nearly zero.
analogDelay.enable();
// Set some default values. They can be changed by sending MIDI CC messages
// over the USB. // Set some default values.
myDelay.delay(200.0f); // These can be changed by sending MIDI CC messages over the USB using
myDelay.bypass(false); // the BAMidiTester application.
myDelay.mix(1.0f); analogDelay.delay(200.0f); // initial delay of 200 ms
myDelay.feedback(0.0f); analogDelay.bypass(false);
analogDelay.mix(0.5f);
// mixer.gain(0, 0.0f); // unity gain on the dry analogDelay.feedback(0.0f);
// mixer.gain(1, 1.0f); // unity gain on the wet
// 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 OnControlChange(byte channel, byte control, byte value) { void OnControlChange(byte channel, byte control, byte value) {
myDelay.processMidi(channel, control, value); analogDelay.processMidi(channel, control, value);
#ifdef MIDI_DEBUG
Serial.print("Control Change, ch="); Serial.print("Control Change, ch=");
Serial.print(channel, DEC); Serial.print(channel, DEC);
Serial.print(", control="); Serial.print(", control=");
@ -94,32 +107,34 @@ void OnControlChange(byte channel, byte control, byte value) {
Serial.print(", value="); Serial.print(", value=");
Serial.print(value, DEC); Serial.print(value, DEC);
Serial.println(); Serial.println();
#endif
} }
void loop() { void loop() {
// usbMIDI.read() needs to be called rapidly from loop(). When // usbMIDI.read() needs to be called rapidly from loop(). When
// each MIDI messages arrives, it return true. The message must // each MIDI messages arrives, it return true. The message must
// be fully processed before usbMIDI.read() is called again. // be fully processed before usbMIDI.read() is called again.
//Serial.println(".");
if (loopCount % 262144 == 0) { if (loopCount % 524288 == 0) {
Serial.print("Processor Usage: "); Serial.print(AudioProcessorUsage()); Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage());
Serial.print(" "); Serial.print(AudioProcessorUsageMax()); Serial.print("% ");
Serial.print(" Delay: "); Serial.print(myDelay.processorUsage()); Serial.print(" analogDelay: "); Serial.print(analogDelay.processorUsage());
Serial.print(" "); Serial.println(myDelay.processorUsageMax()); Serial.println("%");
} }
loopCount++; loopCount++;
// check for new MIDI from USB
if (usbMIDI.read()) { if (usbMIDI.read()) {
// this code entered only if new MIDI received
byte type, channel, data1, data2, cable; byte type, channel, data1, data2, cable;
type = usbMIDI.getType(); // which MIDI message, 128-255 type = usbMIDI.getType(); // which MIDI message, 128-255
channel = usbMIDI.getChannel(); // which MIDI channel, 1-16 channel = usbMIDI.getChannel(); // which MIDI channel, 1-16
data1 = usbMIDI.getData1(); // first data byte of message, 0-127 data1 = usbMIDI.getData1(); // first data byte of message, 0-127
data2 = usbMIDI.getData2(); // second data byte of message, 0-127 data2 = usbMIDI.getData2(); // second data byte of message, 0-127
//cable = usbMIDI.getCable(); // which virtual cable with MIDIx8, 0-7
if (type == 3) { if (type == 3) {
// if type is 3, it's a CC MIDI Message
// Note: the Arduino MIDI library encodes channels as 1-16 instead
// of 0 to 15 as it should, so we must subtract one.
OnControlChange(channel-1, data1, data2); OnControlChange(channel-1, data1, data2);
} }
} }

@ -61,7 +61,12 @@ QueuePosition calcQueuePosition(size_t numSamples);
/// given length of time. /// given length of time.
/// @param milliseconds length of the interval in milliseconds /// @param milliseconds length of the interval in milliseconds
/// @returns the number of corresonding audio samples. /// @returns the number of corresonding audio samples.
size_t calcAudioSamples(float milliseconds); size_t calcAudioSamples(float milliseconds);
/// Calculate a length of time in milliseconds from the number of audio samples.
/// @param numSamples Number of audio samples to convert to time
/// @return the equivalent time in milliseconds.
float calcAudioTimeMs(size_t numSamples);
/// Calculate the number of audio samples (usually an offset) from /// Calculate the number of audio samples (usually an offset) from
/// a queue position. /// a queue position.

@ -171,11 +171,11 @@ private:
*****************************************************************************/ *****************************************************************************/
class ExternalSramManager final { class ExternalSramManager final {
public: public:
ExternalSramManager() = delete; ExternalSramManager();
/// The manager is constructed by specifying how many external memories to handle allocations for /// The manager is constructed by specifying how many external memories to handle allocations for
/// @param numMemories the number of external memories /// @param numMemories the number of external memories
ExternalSramManager(unsigned numMemories = 1); ExternalSramManager(unsigned numMemories);
virtual ~ExternalSramManager(); virtual ~ExternalSramManager();
/// Query the amount of available (unallocated) memory /// Query the amount of available (unallocated) memory

@ -28,6 +28,11 @@ size_t calcAudioSamples(float milliseconds)
return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f);
} }
float calcAudioTimeMs(size_t numSamples)
{
return ((float)(numSamples) / AUDIO_SAMPLE_RATE_EXACT) * 1000.0f;
}
QueuePosition calcQueuePosition(size_t numSamples) QueuePosition calcQueuePosition(size_t numSamples)
{ {
QueuePosition queuePosition; QueuePosition queuePosition;

@ -31,6 +31,7 @@ namespace BAGuitar {
bool ExternalSramManager::m_configured = false; bool ExternalSramManager::m_configured = false;
MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS]; MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS];
ExternalSramManager::ExternalSramManager(unsigned numMemories) ExternalSramManager::ExternalSramManager(unsigned numMemories)
{ {
// Initialize the static memory configuration structs // Initialize the static memory configuration structs
@ -46,6 +47,12 @@ ExternalSramManager::ExternalSramManager(unsigned numMemories)
} }
} }
ExternalSramManager::ExternalSramManager()
: ExternalSramManager(1)
{
}
ExternalSramManager::~ExternalSramManager() ExternalSramManager::~ExternalSramManager()
{ {
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) { for (unsigned i=0; i < NUM_MEM_SLOTS; i++) {

@ -44,7 +44,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot)
: AudioStream(1, m_inputQueueArray) : AudioStream(1, m_inputQueueArray)
{ {
m_memory = new AudioDelay(slot); m_memory = new AudioDelay(slot);
m_maxDelaySamples = slot->size(); m_maxDelaySamples = (slot->size() / sizeof(int16_t));
m_externalMemory = true; m_externalMemory = true;
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT);
} }
@ -120,18 +120,9 @@ void AudioEffectAnalogDelay::update(void)
// BACK TO OUTPUT PROCESSING // BACK TO OUTPUT PROCESSING
// audio_block_t *blockToOutput = nullptr;
// blockToOutput = allocate();
// copy the output data
// if (!blockToOutput) return; // skip this time due to failure
// // copy over data
// m_memory->getSamples(blockToOutput, m_delaySamples);
// Check if external DMA, if so, we need to be sure the read is completed // Check if external DMA, if so, we need to be sure the read is completed
if (m_externalMemory && m_memory->getSlot()->isUseDma()) { if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA // Using DMA
unsigned loopCount = 0;
while (m_memory->getSlot()->isReadBusy()) {} while (m_memory->getSlot()->isReadBusy()) {}
} }
@ -143,11 +134,14 @@ void AudioEffectAnalogDelay::update(void)
release(m_previousBlock); release(m_previousBlock);
m_previousBlock = blockToOutput; m_previousBlock = blockToOutput;
if (m_externalMemory && m_memory->getSlot()->isUseDma()) { // if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA // // Using DMA
if (m_blockToRelease) release(m_blockToRelease); // if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease; // m_blockToRelease = blockToRelease;
} // }
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
} }
void AudioEffectAnalogDelay::delay(float milliseconds) void AudioEffectAnalogDelay::delay(float milliseconds)
@ -229,9 +223,11 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) && if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) { (m_midiConfig[DELAY][MIDI_CONTROL] == control)) {
// Delay // Delay
m_maxDelaySamples = m_memory->getSlot()->size(); if (m_externalMemory) { m_maxDelaySamples = m_memory->getSlot()->size() / sizeof(int16_t); }
Serial.println(String("AudioEffectAnalogDelay::delay: ") + val + String(" out of ") + m_maxDelaySamples); size_t delayVal = (size_t)(val * (float)m_maxDelaySamples);
delay((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; return;
} }
@ -246,7 +242,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) &&
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { (m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) {
// Feedback // Feedback
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + val); Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%"));
feedback(val); feedback(val);
return; return;
} }
@ -254,7 +250,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) &&
(m_midiConfig[MIX][MIDI_CONTROL] == control)) { (m_midiConfig[MIX][MIDI_CONTROL] == control)) {
// Mix // Mix
Serial.println(String("AudioEffectAnalogDelay::mix: ") + val); Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val );
mix(val); mix(val);
return; return;
} }
@ -262,7 +258,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { (m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume // Volume
Serial.println(String("AudioEffectAnalogDelay::volume: ") + val); Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%"));
volume(val); volume(val);
return; return;
} }

@ -273,10 +273,12 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId)
cs = SPI_CS_MEM0; cs = SPI_CS_MEM0;
m_cs = new ActiveLowChipSelect(cs, m_settings); m_cs = new ActiveLowChipSelect(cs, m_settings);
break; break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 : case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1; cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings); m_cs = new ActiveLowChipSelect1(cs, m_settings);
break; break;
#endif
default : default :
cs = SPI_CS_MEM0; cs = SPI_CS_MEM0;
} }
@ -297,10 +299,12 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz)
cs = SPI_CS_MEM0; cs = SPI_CS_MEM0;
m_cs = new ActiveLowChipSelect(cs, m_settings); m_cs = new ActiveLowChipSelect(cs, m_settings);
break; break;
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
case SpiDeviceId::SPI_DEVICE1 : case SpiDeviceId::SPI_DEVICE1 :
cs = SPI_CS_MEM1; cs = SPI_CS_MEM1;
m_cs = new ActiveLowChipSelect1(cs, m_settings); m_cs = new ActiveLowChipSelect1(cs, m_settings);
break; break;
#endif
default : default :
cs = SPI_CS_MEM0; cs = SPI_CS_MEM0;
} }

Loading…
Cancel
Save