From 62f01fc62a945ee15d6f8c02aab2902684ff319b Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Thu, 22 Feb 2018 20:50:59 -0500 Subject: [PATCH] Added new AudioEffectAnalogDelay, and restructured the library --- README.md | 5 +- .../BA4_TGA_Pro_delay_reverb.ino | 3 +- .../Delay/AnalogDelayDemo/AnalogDelayDemo.ino | 113 ++++++++++-------- src/LibBasicFunctions.h | 7 +- src/LibMemoryManagement.h | 4 +- src/common/AudioHelpers.cpp | 5 + src/common/ExternalSramManager.cpp | 7 ++ src/effects/AudioEffectAnalogDelay.cpp | 38 +++--- src/peripherals/BASpiMemory.cpp | 4 + 9 files changed, 111 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 6f32f74..ecb9a46 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino b/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino index 9953821..3cc7190 100644 --- a/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino +++ b/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino @@ -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() { diff --git a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino index fb452fb..3cb413a 100644 --- a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino +++ b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino @@ -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); } } diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 802bc5c..b035031 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -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. diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 742765c..bb450d1 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -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 diff --git a/src/common/AudioHelpers.cpp b/src/common/AudioHelpers.cpp index ee09395..ae7cfcf 100644 --- a/src/common/AudioHelpers.cpp +++ b/src/common/AudioHelpers.cpp @@ -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; diff --git a/src/common/ExternalSramManager.cpp b/src/common/ExternalSramManager.cpp index 597181d..cc0f7f9 100644 --- a/src/common/ExternalSramManager.cpp +++ b/src/common/ExternalSramManager.cpp @@ -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++) { diff --git a/src/effects/AudioEffectAnalogDelay.cpp b/src/effects/AudioEffectAnalogDelay.cpp index 3881e57..69b4c55 100644 --- a/src/effects/AudioEffectAnalogDelay.cpp +++ b/src/effects/AudioEffectAnalogDelay.cpp @@ -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(&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; } diff --git a/src/peripherals/BASpiMemory.cpp b/src/peripherals/BASpiMemory.cpp index 099935b..3c0403d 100644 --- a/src/peripherals/BASpiMemory.cpp +++ b/src/peripherals/BASpiMemory.cpp @@ -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; }