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
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. ***
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

@ -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 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 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 patch5b(cabFilter, 0, i2sOut, 1); // connect the cab filter to the output.
void setup() {

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

@ -61,7 +61,12 @@ QueuePosition calcQueuePosition(size_t numSamples);
/// given length of time.
/// @param milliseconds length of the interval in milliseconds
/// @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
/// a queue position.

@ -171,11 +171,11 @@ private:
*****************************************************************************/
class ExternalSramManager final {
public:
ExternalSramManager() = delete;
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 = 1);
ExternalSramManager(unsigned numMemories);
virtual ~ExternalSramManager();
/// 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);
}
float calcAudioTimeMs(size_t numSamples)
{
return ((float)(numSamples) / AUDIO_SAMPLE_RATE_EXACT) * 1000.0f;
}
QueuePosition calcQueuePosition(size_t numSamples)
{
QueuePosition queuePosition;

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

@ -44,7 +44,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(slot);
m_maxDelaySamples = slot->size();
m_maxDelaySamples = (slot->size() / sizeof(int16_t));
m_externalMemory = true;
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
// 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
if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA
unsigned loopCount = 0;
while (m_memory->getSlot()->isReadBusy()) {}
}
@ -143,11 +134,14 @@ void AudioEffectAnalogDelay::update(void)
release(m_previousBlock);
m_previousBlock = blockToOutput;
if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// Using DMA
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
// if (m_externalMemory && m_memory->getSlot()->isUseDma()) {
// // Using DMA
// if (m_blockToRelease) release(m_blockToRelease);
// m_blockToRelease = blockToRelease;
// }
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
void AudioEffectAnalogDelay::delay(float milliseconds)
@ -229,9 +223,11 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) &&
(m_midiConfig[DELAY][MIDI_CONTROL] == control)) {
// Delay
m_maxDelaySamples = m_memory->getSlot()->size();
Serial.println(String("AudioEffectAnalogDelay::delay: ") + val + String(" out of ") + m_maxDelaySamples);
delay((size_t)(val * (float)m_maxDelaySamples));
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;
}
@ -246,7 +242,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) &&
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) {
// Feedback
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + val);
Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%"));
feedback(val);
return;
}
@ -254,7 +250,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) &&
(m_midiConfig[MIX][MIDI_CONTROL] == control)) {
// Mix
Serial.println(String("AudioEffectAnalogDelay::mix: ") + val);
Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val );
mix(val);
return;
}
@ -262,7 +258,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value)
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) &&
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) {
// Volume
Serial.println(String("AudioEffectAnalogDelay::volume: ") + val);
Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%"));
volume(val);
return;
}

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

Loading…
Cancel
Save