From b649550cb35af00c47f579dd332d2739da7ab71e Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Mon, 19 Feb 2018 20:17:04 -0500 Subject: [PATCH] General library cleanup, added DMA SPI MEM support --- .../Delay/AnalogDelayDemo/AnalogDelayDemo.ino | 114 +++++ examples/Delay/AnalogDelayDemo/name.c | 19 + .../Tests/DMA_MEM0_test/DMA_MEM0_test.ino | 316 ++++++++++++ .../Tests/DMA_MEM1_test/DMA_MEM1_test.ino | 316 ++++++++++++ src/AudioEffectAnalogDelay.h | 78 ++- src/BAAudioControlWM8731.h | 6 +- src/BAGpio.h | 7 +- src/BAGuitar.h | 10 +- src/BAHardware.h | 13 +- src/BASpiMemory.h | 17 +- src/BATypes.h | 30 +- src/LibBasicFunctions.cpp | 356 ------------- src/LibBasicFunctions.h | 6 +- src/LibMemoryManagement.h | 4 +- src/common/AudioDelay.cpp | 171 +++++++ src/common/AudioHelpers.cpp | 73 +++ .../ExtMemSlot.cpp} | 88 +--- src/common/ExternalSramManager.cpp | 113 +++++ src/common/IirBiquadFilter.cpp | 145 ++++++ src/{ => effects}/AudioEffectAnalogDelay.cpp | 6 +- .../BAAudioEffectDelayExternal.cpp | 0 .../BAAudioControlWM8731.cpp | 0 src/{ => peripherals}/BAGpio.cpp | 0 src/peripherals/BASpiMemory.cpp | 467 ++++++++++++++++++ 24 files changed, 1859 insertions(+), 496 deletions(-) create mode 100644 examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino create mode 100644 examples/Delay/AnalogDelayDemo/name.c create mode 100644 examples/Tests/DMA_MEM0_test/DMA_MEM0_test.ino create mode 100644 examples/Tests/DMA_MEM1_test/DMA_MEM1_test.ino delete mode 100644 src/LibBasicFunctions.cpp create mode 100644 src/common/AudioDelay.cpp create mode 100644 src/common/AudioHelpers.cpp rename src/{LibMemoryManagement.cpp => common/ExtMemSlot.cpp} (71%) create mode 100644 src/common/ExternalSramManager.cpp create mode 100644 src/common/IirBiquadFilter.cpp rename src/{ => effects}/AudioEffectAnalogDelay.cpp (98%) rename src/{ => effects}/BAAudioEffectDelayExternal.cpp (100%) rename src/{ => peripherals}/BAAudioControlWM8731.cpp (100%) rename src/{ => peripherals}/BAGpio.cpp (100%) create mode 100644 src/peripherals/BASpiMemory.cpp diff --git a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino new file mode 100644 index 0000000..1c4b657 --- /dev/null +++ b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino @@ -0,0 +1,114 @@ +//#include +#include "BAGuitar.h" + +using namespace BAGuitar; +AudioInputI2S i2sIn; +AudioOutputI2S i2sOut; +BAAudioControlWM8731 codec; + +#define USE_EXT + +#ifdef USE_EXT +ExternalSramManager externalSram(1); // Manage both SRAMs +ExtMemSlot delaySlot; // For the external memory +AudioEffectAnalogDelay myDelay(&delaySlot); +#else +AudioEffectAnalogDelay myDelay(200.0f); +#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); +AudioConnection leftOut(mixer,0, i2sOut, 0); +AudioConnection rightOut(mixer,0, i2sOut, 1); + +unsigned loopCount = 0; + +void setup() { + delay(100); + Serial.begin(57600); + codec.disable(); + delay(100); + AudioMemory(128); + delay(5); + + // Setup MIDI + //usbMIDI.setHandleControlChange(OnControlChange); + Serial.println("Enabling codec...\n"); + codec.enable(); + delay(100); + + + #ifdef USE_EXT + Serial.println("Using EXTERNAL memory"); + //externalSram.requestMemory(&delaySlot, 1400.0f); + //externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM0, true); + externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM1, true); + #else + Serial.println("Using INTERNAL memory"); + #endif + + myDelay.delay(200.0f); + //myDelay.delay( 128.0f/44100.0f*1000.0f); + //myDelay.delay(0, 0.0f); + //myDelay.delay((size_t)8192); + + + + myDelay.mapMidiBypass(16); + myDelay.mapMidiDelay(20); + myDelay.mapMidiFeedback(21); + myDelay.mapMidiMix(22); + + myDelay.enable(); + 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 + +} + +void OnControlChange(byte channel, byte control, byte value) { + myDelay.processMidi(channel, control, value); + Serial.print("Control Change, ch="); + Serial.print(channel, DEC); + Serial.print(", control="); + Serial.print(control, DEC); + Serial.print(", value="); + Serial.print(value, DEC); + Serial.println(); + + +} + +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()); + } + loopCount++; + + if (usbMIDI.read()) { + 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) { + OnControlChange(channel-1, data1, data2); + } + } + +} diff --git a/examples/Delay/AnalogDelayDemo/name.c b/examples/Delay/AnalogDelayDemo/name.c new file mode 100644 index 0000000..5ea00fe --- /dev/null +++ b/examples/Delay/AnalogDelayDemo/name.c @@ -0,0 +1,19 @@ +// To give your project a unique name, this code must be +// placed into a .c file (its own tab). It can not be in +// a .cpp file or your main sketch (the .ino file). + +#include "usb_names.h" + +// Edit these lines to create your own name. The length must +// match the number of characters in your custom name. + +#define MIDI_NAME {'B','l','a','c','k','a','d','d','r',' ','A','u','d','i','o',' ','T','G','A',' ','P','r','o'} +#define MIDI_NAME_LEN 23 + +// Do not change this part. This exact format is required by USB. + +struct usb_string_descriptor_struct usb_string_product_name = { + 2 + MIDI_NAME_LEN * 2, + 3, + MIDI_NAME +}; diff --git a/examples/Tests/DMA_MEM0_test/DMA_MEM0_test.ino b/examples/Tests/DMA_MEM0_test/DMA_MEM0_test.ino new file mode 100644 index 0000000..8eb5859 --- /dev/null +++ b/examples/Tests/DMA_MEM0_test/DMA_MEM0_test.ino @@ -0,0 +1,316 @@ +/************************************************************************* + * This demo uses the BAGuitar 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/BAGuitar + * + * This demo will perform a DMA memory test on MEM0 attached + * to SPI. + * + * NOTE: SPI MEM0 can be used by a Teensy 3.2/3.5/3.6. + * pins. + * + */ +#include +#include "BAGuitar.h" + +using namespace BAGuitar; + +//#define SANITY +#define DMA_SIZE 256 + +#define SPI_WRITE_CMD 0x2 +#define SPI_READ_CMD 0x3 +#define SPI_ADDR_2_MASK 0xFF0000 +#define SPI_ADDR_2_SHIFT 16 +#define SPI_ADDR_1_MASK 0x00FF00 +#define SPI_ADDR_1_SHIFT 8 +#define SPI_ADDR_0_MASK 0x0000FF +SPISettings memSettings(20000000, MSBFIRST, SPI_MODE0); +const int cs0pin = 15; + +BAGpio gpio; // access to User LED +BASpiMemoryDMA spiMem0(SpiDeviceId::SPI_DEVICE0); + + + +bool compareBuffers(uint8_t *a, uint8_t *b, size_t numBytes) +{ + bool pass=true; + int errorCount = 0; + for (size_t i=0; i(a), reinterpret_cast(b), sizeof(uint16_t)*numWords); +} + +constexpr size_t TEST_END = SPI_MAX_ADDR; + +void setup() { + + Serial.begin(57600); + while (!Serial) {} + delay(5); + + Serial.println("Enabling SPI"); + spiMem0.begin(); +} + + +bool spi8BitTest(void) { + + size_t spiAddress = 0; + int spiPhase = 0; + constexpr uint8_t MASK80 = 0xaa; + constexpr uint8_t MASK81 = 0x55; + + uint8_t src8[DMA_SIZE]; + uint8_t dest8[DMA_SIZE]; + + // Write to the memory using 8-bit transfers + Serial.println("\nStarting 8-bit test Write/Read"); + while (spiPhase < 4) { + spiAddress = 0; + while (spiAddress < TEST_END) { + + // fill the write data buffer + switch (spiPhase) { + case 0 : + for (int i=0; i +#include "BAGuitar.h" + +using namespace BAGuitar; + +//#define SANITY +#define DMA_SIZE 256 + +#define SPI_WRITE_CMD 0x2 +#define SPI_READ_CMD 0x3 +#define SPI_ADDR_2_MASK 0xFF0000 +#define SPI_ADDR_2_SHIFT 16 +#define SPI_ADDR_1_MASK 0x00FF00 +#define SPI_ADDR_1_SHIFT 8 +#define SPI_ADDR_0_MASK 0x0000FF +SPISettings memSettings(20000000, MSBFIRST, SPI_MODE0); +const int cs0pin = 15; + +BAGpio gpio; // access to User LED +BASpiMemoryDMA spiMem1(SpiDeviceId::SPI_DEVICE1); + + + +bool compareBuffers(uint8_t *a, uint8_t *b, size_t numBytes) +{ + bool pass=true; + int errorCount = 0; + for (size_t i=0; i(a), reinterpret_cast(b), sizeof(uint16_t)*numWords); +} + +constexpr size_t TEST_END = SPI_MAX_ADDR; + +void setup() { + + Serial.begin(57600); + while (!Serial) {} + delay(5); + + Serial.println("Enabling SPI"); + spiMem1.begin(); +} + + +bool spi8BitTest(void) { + + size_t spiAddress = 0; + int spiPhase = 0; + constexpr uint8_t MASK80 = 0xaa; + constexpr uint8_t MASK81 = 0x55; + + uint8_t src8[DMA_SIZE]; + uint8_t dest8[DMA_SIZE]; + + // Write to the memory using 8-bit transfers + Serial.println("\nStarting 8-bit test Write/Read"); + while (spiPhase < 4) { + spiAddress = 0; + while (spiAddress < TEST_END) { + + // fill the write data buffer + switch (spiPhase) { + case 0 : + for (int i=0; i. + *****************************************************************************/ -#ifndef SRC_AUDIOEFFECTANALOGDELAY_H_ -#define SRC_AUDIOEFFECTANALOGDELAY_H_ +#ifndef __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H +#define __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H #include #include "LibBasicFunctions.h" namespace BAGuitar { +/**************************************************************************//** + * 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: AudioEffectAnalogDelay() = delete; - AudioEffectAnalogDelay(float maxDelay); + /// 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(ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory - virtual ~AudioEffectAnalogDelay(); - virtual void update(void); + virtual ~AudioEffectAnalogDelay(); ///< Destructor + + /// 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); + /// 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; } + + /// 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; } + + /// 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; } void processMidi(int channel, int control, int value); @@ -37,6 +91,8 @@ public: void mapMidiFeedback(int control, int channel = 0); void mapMidiMix(int control, int channel = 0); + virtual void update(void); ///< update automatically called by the Teesny Audio Library + private: audio_block_t *m_inputQueueArray[1]; bool m_bypass = true; @@ -62,4 +118,4 @@ private: } -#endif /* SRC_AUDIOEFFECTANALOGDELAY_H_ */ +#endif /* __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H */ diff --git a/src/BAAudioControlWM8731.h b/src/BAAudioControlWM8731.h index 7d930c5..18b3de9 100644 --- a/src/BAAudioControlWM8731.h +++ b/src/BAAudioControlWM8731.h @@ -22,8 +22,8 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAAUDIOCONTROLWM8731_H -#define __BAAUDIOCONTROLWM8731_H +#ifndef __BAGUITAR__BAAUDIOCONTROLWM8731_H +#define __BAGUITAR__BAAUDIOCONTROLWM8731_H namespace BAGuitar { @@ -128,4 +128,4 @@ private: } /* namespace BAGuitar */ -#endif /* __BAAUDIOCONTROLWM8731_H */ +#endif /* __BAGUITAR__BAAUDIOCONTROLWM8731_H */ diff --git a/src/BAGpio.h b/src/BAGpio.h index fe45273..53ade35 100644 --- a/src/BAGpio.h +++ b/src/BAGpio.h @@ -20,8 +20,8 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __SRC_BAGPIO_H -#define __SRC_BAGPIO_H +#ifndef __BAGUITAR_BAGPIO_H +#define __BAGUITAR_BAGPIO_H #include "BAHardware.h" @@ -35,6 +35,7 @@ namespace BAGuitar { *****************************************************************************/ class BAGpio { public: + /// Construct a GPIO object for controlling the various GPIO and user pins BAGpio(); virtual ~BAGpio(); @@ -72,4 +73,4 @@ private: } /* namespace BAGuitar */ -#endif /* __SRC_BAGPIO_H */ +#endif /* __BAGUITAR_BAGPIO_H */ diff --git a/src/BAGuitar.h b/src/BAGuitar.h index 65de0c9..1fc8319 100644 --- a/src/BAGuitar.h +++ b/src/BAGuitar.h @@ -18,16 +18,18 @@ * along with this program. If not, see . */ -#ifndef __SRC_BATGUITAR_H -#define __SRC_BATGUITAR_H +#ifndef __BATGUITAR_H +#define __BATGUITAR_H #include "BAHardware.h" // contains the Blackaddr hardware board definitions +#include "BATypes.h" #include "BAAudioControlWM8731.h" // Codec Control #include "BASpiMemory.h" #include "BAGpio.h" #include "BAAudioEffectDelayExternal.h" - #include "AudioEffectAnalogDelay.h" +#include "LibBasicFunctions.h" +#include "LibMemoryManagement.h" -#endif /* __SRC_BATGUITAR_H */ +#endif /* __BATGUITAR_H */ diff --git a/src/BAHardware.h b/src/BAHardware.h index 100da33..5637bca 100644 --- a/src/BAHardware.h +++ b/src/BAHardware.h @@ -20,8 +20,8 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef SRC_BAHARDWARE_H_ -#define SRC_BAHARDWARE_H_ +#ifndef __BAGUTIAR_BAHARDWARE_H +#define __BAGUTIAR_BAHARDWARE_H #include @@ -64,9 +64,12 @@ 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 }; -//constexpr int MEM0_MAX_ADDR = 131071; ///< Max address size per chip -//constexpr int MEM1_MAX_ADDR = 131071; ///< Max address size per chip + /**************************************************************************//** * General Purpose SPI Interfaces @@ -87,4 +90,4 @@ constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip } // namespace BAGuitar -#endif /* SRC_BAHARDWARE_H_ */ +#endif /* __BAGUTIAR_BAHARDWARE_H */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index 04fb732..d5395bb 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -4,7 +4,8 @@ * @company Blackaddr Audio * * BASpiMemory is convenience class for accessing the optional SPI RAMs on - * the GTA Series boards. + * 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 @@ -19,8 +20,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *****************************************************************************/ -#ifndef __SRC_BASPIMEMORY_H -#define __SRC_BASPIMEMORY_H +#ifndef __BAGUITAR_BASPIMEMORY_H +#define __BAGUITAR_BASPIMEMORY_H #include #include @@ -119,7 +120,6 @@ protected: }; -//constexpr int MAX_DMA_XFERS = 4; class BASpiMemoryDMA : public BASpiMemory { public: @@ -194,17 +194,12 @@ public: void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0); private: - //AbstractDmaSpi *m_spiDma = nullptr; - //AbstractDmaSpi *m_spiDma = nullptr; - DmaSpiGeneric *m_spiDma = nullptr; + DmaSpiGeneric *m_spiDma = nullptr; AbstractChipSelect *m_cs = nullptr; - //size_t m_bufferSize; - //uint8_t *m_txBuffer = nullptr; uint8_t *m_txCommandBuffer = nullptr; DmaSpi::Transfer *m_txTransfer; - //uint8_t *m_rxBuffer = nullptr; uint8_t *m_rxCommandBuffer = nullptr; DmaSpi::Transfer *m_rxTransfer; @@ -217,4 +212,4 @@ private: } /* namespace BAGuitar */ -#endif /* __SRC_BASPIMEMORY_H */ +#endif /* __BAGUITAR_BASPIMEMORY_H */ diff --git a/src/BATypes.h b/src/BATypes.h index 0177c67..294cb42 100644 --- a/src/BATypes.h +++ b/src/BATypes.h @@ -1,12 +1,26 @@ -/* - * BATypes.h +/**************************************************************************//** + * @file + * @author Steve Lascos + * @company Blackaddr Audio + * + * This file contains some custom types used by the rest of the BAGuitar library. + * + * @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.* * - * Created on: Feb 7, 2018 - * Author: slascos - */ + * 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 . + *****************************************************************************/ -#ifndef SRC_BATYPES_H_ -#define SRC_BATYPES_H_ +#ifndef __BAGUITAR_BATYPES_H +#define __BAGUITAR_BATYPES_H namespace BAGuitar { @@ -141,4 +155,4 @@ private: } // BAGuitar -#endif /* SRC_BATYPES_H_ */ +#endif /* __BAGUITAR_BATYPES_H */ diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp deleted file mode 100644 index 9fc0c71..0000000 --- a/src/LibBasicFunctions.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* - * LibBasicFunctions.cpp - * - * Created on: Dec 23, 2017 - * Author: slascos - */ - -#include "Audio.h" -#include "LibBasicFunctions.h" - -namespace BAGuitar { - -size_t calcAudioSamples(float milliseconds) -{ - return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); -} - -QueuePosition calcQueuePosition(size_t numSamples) -{ - QueuePosition queuePosition; - queuePosition.index = (int)(numSamples / AUDIO_BLOCK_SAMPLES); - queuePosition.offset = numSamples % AUDIO_BLOCK_SAMPLES; - return queuePosition; -} -QueuePosition calcQueuePosition(float milliseconds) { - size_t numSamples = (int)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); - return calcQueuePosition(numSamples); -} - -size_t calcOffset(QueuePosition position) -{ - return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset; -} - -void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) -{ - //Non-optimized version for illustrative purposes -// for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { -// out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); -// } -// return; - - // ARM DSP optimized - int16_t wetBuffer[AUDIO_BLOCK_SAMPLES]; - int16_t dryBuffer[AUDIO_BLOCK_SAMPLES]; - int16_t scaleFractWet = (int16_t)(mix * 32767.0f); - int16_t scaleFractDry = 32767-scaleFractWet; - - arm_scale_q15(dry->data, scaleFractDry, 0, dryBuffer, AUDIO_BLOCK_SAMPLES); - arm_scale_q15(wet->data, scaleFractWet, 0, wetBuffer, AUDIO_BLOCK_SAMPLES); - arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES); -} - -void clearAudioBlock(audio_block_t *block) -{ - memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); -} - - -//////////////////////////////////////////////////// -// AudioDelay -//////////////////////////////////////////////////// -AudioDelay::AudioDelay(size_t maxSamples) -: m_slot(nullptr) -{ - m_type = (MemType::MEM_INTERNAL); - - // INTERNAL memory consisting of audio_block_t data structures. - QueuePosition pos = calcQueuePosition(maxSamples); - m_ringBuffer = new RingBuffer(pos.index+2); // If the delay is in queue x, we need to overflow into x+1, thus x+2 total buffers. -} - -AudioDelay::AudioDelay(float maxDelayTimeMs) -: AudioDelay(calcAudioSamples(maxDelayTimeMs)) -{ - -} - -AudioDelay::AudioDelay(ExtMemSlot *slot) -{ - m_type = (MemType::MEM_EXTERNAL); - m_slot = slot; -} - -AudioDelay::~AudioDelay() -{ - if (m_ringBuffer) delete m_ringBuffer; -} - -audio_block_t* AudioDelay::addBlock(audio_block_t *block) -{ - audio_block_t *blockToRelease = nullptr; - - if (m_type == (MemType::MEM_INTERNAL)) { - // INTERNAL memory - - // purposefully don't check if block is valid, the ringBuffer can support nullptrs - if ( m_ringBuffer->size() >= m_ringBuffer->max_size() ) { - // pop before adding - blockToRelease = m_ringBuffer->front(); - m_ringBuffer->pop_front(); - } - - // add the new buffer - m_ringBuffer->push_back(block); - return blockToRelease; - - } else { - // EXTERNAL memory - if (!m_slot) { Serial.println("addBlock(): m_slot is not valid"); } - - if (block) { - - // Audio is stored in reverse in block so we need to write it backwards to external memory - // to maintain temporal coherency. -// int16_t *srcPtr = block->data + AUDIO_BLOCK_SAMPLES - 1; -// for (int i=0; iwriteAdvance16(*srcPtr); -// srcPtr--; -// } - - // this causes pops - m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); - - // Code below worked -// int16_t *srcPtr = block->data; -// for (int i=0; iwriteAdvance16(*srcPtr); -// srcPtr++; -// } - - } - blockToRelease = block; - } - return blockToRelease; -} - -audio_block_t* AudioDelay::getBlock(size_t index) -{ - audio_block_t *ret = nullptr; - if (m_type == (MemType::MEM_INTERNAL)) { - ret = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); - } - return ret; -} - -bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples) -{ - if (!dest) { - Serial.println("getSamples(): dest is invalid"); - return false; - } - - if (m_type == (MemType::MEM_INTERNAL)) { - QueuePosition position = calcQueuePosition(offsetSamples); - size_t index = position.index; - - audio_block_t *currentQueue0 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); - // The latest buffer is at the back. We need index+1 counting from the back. - audio_block_t *currentQueue1 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); - - // check if either queue is invalid, if so just zero the destination buffer - if ( (!currentQueue0) || (!currentQueue0) ) { - // a valid entry is not in all queue positions while it is filling, use zeros - memset(static_cast(dest->data), 0, numSamples * sizeof(int16_t)); - return true; - } - - if (position.offset == 0) { - // single transfer - memcpy(static_cast(dest->data), static_cast(currentQueue0->data), numSamples * sizeof(int16_t)); - return true; - } - - // Otherwise we need to break the transfer into two memcpy because it will go across two source queues. - // Audio is stored in reverse order. That means the first sample (in time) goes in the last location in the audio block. - int16_t *destStart = dest->data; - int16_t *srcStart; - - // Break the transfer into two. Copy the 'older' data first then the 'newer' data with respect to current time. - //currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); // The latest buffer is at the back. We need index+1 counting from the back. - srcStart = (currentQueue1->data + AUDIO_BLOCK_SAMPLES - position.offset); - size_t numData = position.offset; - memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); - - //currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); // now grab the queue where the 'first' data sample was - destStart += numData; // we already wrote numData so advance by this much. - srcStart = (currentQueue0->data); - numData = AUDIO_BLOCK_SAMPLES - numData; - memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); - - return true; - - } else { - // EXTERNAL Memory - if (numSamples*sizeof(int16_t) <= m_slot->size() ) { - int currentPositionBytes = (int)m_slot->getWritePosition() - (int)(AUDIO_BLOCK_SAMPLES*sizeof(int16_t)); - size_t offsetBytes = offsetSamples * sizeof(int16_t); - - if ((int)offsetBytes <= currentPositionBytes) { - m_slot->setReadPosition(currentPositionBytes - offsetBytes); - } else { - // It's going to wrap around to the end of the slot - int readPosition = (int)m_slot->size() + currentPositionBytes - offsetBytes; - m_slot->setReadPosition((size_t)readPosition); - } - - //m_slot->printStatus(); - - // write the data to the destination block in reverse -// int16_t *destPtr = dest->data + AUDIO_BLOCK_SAMPLES-1; -// for (int i=0; ireadAdvance16(); -// destPtr--; -// } - - // This causes pops - m_slot->readAdvance16(dest->data, AUDIO_BLOCK_SAMPLES); - - // Code below worked -// int16_t *destPtr = dest->data; -// for (int i=0; ireadAdvance16(); -// destPtr++; -// } - - return true; - } else { - // numSampmles is > than total slot size - Serial.println("getSamples(): ERROR numSamples > total slot size"); - return false; - } - } - -} - -//////////////////////////////////////////////////// -// IirBiQuadFilter -//////////////////////////////////////////////////// -IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift) -: NUM_STAGES(numStages) -{ - m_coeffs = new int32_t[5*numStages]; - memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); - - m_state = new int32_t[4*numStages]; - arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); -} - -IirBiQuadFilter::~IirBiQuadFilter() -{ - if (m_coeffs) delete [] m_coeffs; - if (m_state) delete [] m_state; -} - - -bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples) -{ - if (!output) return false; - if (!input) { - // send zeros - memset(output, 0, numSamples * sizeof(int16_t)); - } else { - - // create convertion buffers on teh stack - int32_t input32[numSamples]; - int32_t output32[numSamples]; - for (size_t i=0; i. *****************************************************************************/ -#ifndef __LIBMEMORYMANAGEMENT_H -#define __LIBMEMORYMANAGEMENT_H +#ifndef __BAGUITAR_LIBMEMORYMANAGEMENT_H +#define __BAGUITAR_LIBMEMORYMANAGEMENT_H #include diff --git a/src/common/AudioDelay.cpp b/src/common/AudioDelay.cpp new file mode 100644 index 0000000..704233c --- /dev/null +++ b/src/common/AudioDelay.cpp @@ -0,0 +1,171 @@ +/* + * AudioDelay.cpp + * + * Created on: January 1, 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 . +*/ + +#include "Audio.h" +#include "LibBasicFunctions.h" + +namespace BAGuitar { + +//////////////////////////////////////////////////// +// AudioDelay +//////////////////////////////////////////////////// +AudioDelay::AudioDelay(size_t maxSamples) +: m_slot(nullptr) +{ + m_type = (MemType::MEM_INTERNAL); + + // INTERNAL memory consisting of audio_block_t data structures. + QueuePosition pos = calcQueuePosition(maxSamples); + m_ringBuffer = new RingBuffer(pos.index+2); // If the delay is in queue x, we need to overflow into x+1, thus x+2 total buffers. +} + +AudioDelay::AudioDelay(float maxDelayTimeMs) +: AudioDelay(calcAudioSamples(maxDelayTimeMs)) +{ + +} + +AudioDelay::AudioDelay(ExtMemSlot *slot) +{ + m_type = (MemType::MEM_EXTERNAL); + m_slot = slot; +} + +AudioDelay::~AudioDelay() +{ + if (m_ringBuffer) delete m_ringBuffer; +} + +audio_block_t* AudioDelay::addBlock(audio_block_t *block) +{ + audio_block_t *blockToRelease = nullptr; + + if (m_type == (MemType::MEM_INTERNAL)) { + // INTERNAL memory + + // purposefully don't check if block is valid, the ringBuffer can support nullptrs + if ( m_ringBuffer->size() >= m_ringBuffer->max_size() ) { + // pop before adding + blockToRelease = m_ringBuffer->front(); + m_ringBuffer->pop_front(); + } + + // add the new buffer + m_ringBuffer->push_back(block); + return blockToRelease; + + } else { + // EXTERNAL memory + if (!m_slot) { Serial.println("addBlock(): m_slot is not valid"); } + + if (block) { + // this causes pops + m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); + } + blockToRelease = block; + } + return blockToRelease; +} + +audio_block_t* AudioDelay::getBlock(size_t index) +{ + audio_block_t *ret = nullptr; + if (m_type == (MemType::MEM_INTERNAL)) { + ret = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); + } + return ret; +} + +bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples) +{ + if (!dest) { + Serial.println("getSamples(): dest is invalid"); + return false; + } + + if (m_type == (MemType::MEM_INTERNAL)) { + QueuePosition position = calcQueuePosition(offsetSamples); + size_t index = position.index; + + audio_block_t *currentQueue0 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); + // The latest buffer is at the back. We need index+1 counting from the back. + audio_block_t *currentQueue1 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); + + // check if either queue is invalid, if so just zero the destination buffer + if ( (!currentQueue0) || (!currentQueue0) ) { + // a valid entry is not in all queue positions while it is filling, use zeros + memset(static_cast(dest->data), 0, numSamples * sizeof(int16_t)); + return true; + } + + if (position.offset == 0) { + // single transfer + memcpy(static_cast(dest->data), static_cast(currentQueue0->data), numSamples * sizeof(int16_t)); + return true; + } + + // Otherwise we need to break the transfer into two memcpy because it will go across two source queues. + // Audio is stored in reverse order. That means the first sample (in time) goes in the last location in the audio block. + int16_t *destStart = dest->data; + int16_t *srcStart; + + // Break the transfer into two. Copy the 'older' data first then the 'newer' data with respect to current time. + //currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); // The latest buffer is at the back. We need index+1 counting from the back. + srcStart = (currentQueue1->data + AUDIO_BLOCK_SAMPLES - position.offset); + size_t numData = position.offset; + memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); + + //currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); // now grab the queue where the 'first' data sample was + destStart += numData; // we already wrote numData so advance by this much. + srcStart = (currentQueue0->data); + numData = AUDIO_BLOCK_SAMPLES - numData; + memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); + + return true; + + } else { + // EXTERNAL Memory + if (numSamples*sizeof(int16_t) <= m_slot->size() ) { + int currentPositionBytes = (int)m_slot->getWritePosition() - (int)(AUDIO_BLOCK_SAMPLES*sizeof(int16_t)); + size_t offsetBytes = offsetSamples * sizeof(int16_t); + + if ((int)offsetBytes <= currentPositionBytes) { + m_slot->setReadPosition(currentPositionBytes - offsetBytes); + } else { + // It's going to wrap around to the end of the slot + int readPosition = (int)m_slot->size() + currentPositionBytes - offsetBytes; + m_slot->setReadPosition((size_t)readPosition); + } + + // This causes pops + m_slot->readAdvance16(dest->data, AUDIO_BLOCK_SAMPLES); + + return true; + } else { + // numSampmles is > than total slot size + Serial.println("getSamples(): ERROR numSamples > total slot size"); + return false; + } + } + +} + +} + diff --git a/src/common/AudioHelpers.cpp b/src/common/AudioHelpers.cpp new file mode 100644 index 0000000..ca3a02c --- /dev/null +++ b/src/common/AudioHelpers.cpp @@ -0,0 +1,73 @@ +/* + * AudioHelpers.cpp + * + * Created on: January 1, 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 . +*/ + +#include "Audio.h" +#include "LibBasicFunctions.h" + +namespace BAGuitar { + +size_t calcAudioSamples(float milliseconds) +{ + return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); +} + +QueuePosition calcQueuePosition(size_t numSamples) +{ + QueuePosition queuePosition; + queuePosition.index = (int)(numSamples / AUDIO_BLOCK_SAMPLES); + queuePosition.offset = numSamples % AUDIO_BLOCK_SAMPLES; + return queuePosition; +} +QueuePosition calcQueuePosition(float milliseconds) { + size_t numSamples = (int)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); + return calcQueuePosition(numSamples); +} + +size_t calcOffset(QueuePosition position) +{ + return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset; +} + +void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) +{ + //Non-optimized version for illustrative purposes +// for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { +// out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); +// } +// return; + + // ARM DSP optimized + int16_t wetBuffer[AUDIO_BLOCK_SAMPLES]; + int16_t dryBuffer[AUDIO_BLOCK_SAMPLES]; + int16_t scaleFractWet = (int16_t)(mix * 32767.0f); + int16_t scaleFractDry = 32767-scaleFractWet; + + arm_scale_q15(dry->data, scaleFractDry, 0, dryBuffer, AUDIO_BLOCK_SAMPLES); + arm_scale_q15(wet->data, scaleFractWet, 0, wetBuffer, AUDIO_BLOCK_SAMPLES); + arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES); +} + +void clearAudioBlock(audio_block_t *block) +{ + memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); +} + +} + diff --git a/src/LibMemoryManagement.cpp b/src/common/ExtMemSlot.cpp similarity index 71% rename from src/LibMemoryManagement.cpp rename to src/common/ExtMemSlot.cpp index ae1fb0d..57385d7 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/common/ExtMemSlot.cpp @@ -1,5 +1,5 @@ /* - * LibMemoryManagement.cpp + * ExtMemSlot.cpp * * Created on: Jan 19, 2018 * Author: slascos @@ -25,7 +25,6 @@ namespace BAGuitar { - ///////////////////////////////////////////////////////////////////////////// // MEM SLOT ///////////////////////////////////////////////////////////////////////////// @@ -232,90 +231,5 @@ void ExtMemSlot::printStatus(void) const String(" m_size:") + m_size); } - -///////////////////////////////////////////////////////////////////////////// -// EXTERNAL SRAM MANAGER -///////////////////////////////////////////////////////////////////////////// -bool ExternalSramManager::m_configured = false; -MemConfig ExternalSramManager::m_memConfig[BAGuitar::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]; - m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]; - m_memConfig[i].nextAvailable = 0; - - m_memConfig[i].m_spi = nullptr; - } - m_configured = true; - } -} - -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(BAGuitar::MemSelect mem) -{ - return m_memConfig[mem].totalAvailable; -} - -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::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, BAGuitar::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 BAGuitar::BASpiMemoryDMA(static_cast(mem)); - slot->m_useDma = true; - } else { - m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast(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 - - return false; - } -} - } diff --git a/src/common/ExternalSramManager.cpp b/src/common/ExternalSramManager.cpp new file mode 100644 index 0000000..597181d --- /dev/null +++ b/src/common/ExternalSramManager.cpp @@ -0,0 +1,113 @@ +/* + * 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 . +*/ +#include +#include + +#include "Audio.h" +#include "LibMemoryManagement.h" + +namespace BAGuitar { + +///////////////////////////////////////////////////////////////////////////// +// EXTERNAL SRAM MANAGER +///////////////////////////////////////////////////////////////////////////// +bool ExternalSramManager::m_configured = false; +MemConfig ExternalSramManager::m_memConfig[BAGuitar::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]; + m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]; + m_memConfig[i].nextAvailable = 0; + + m_memConfig[i].m_spi = nullptr; + } + m_configured = true; + } +} + +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(BAGuitar::MemSelect mem) +{ + return m_memConfig[mem].totalAvailable; +} + +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::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, BAGuitar::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 BAGuitar::BASpiMemoryDMA(static_cast(mem)); + slot->m_useDma = true; + } else { + m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast(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 + + return false; + } +} + +} + diff --git a/src/common/IirBiquadFilter.cpp b/src/common/IirBiquadFilter.cpp new file mode 100644 index 0000000..59b5432 --- /dev/null +++ b/src/common/IirBiquadFilter.cpp @@ -0,0 +1,145 @@ +/* + * IirBiquadFilter.cpp + * + * Created on: January 1, 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 . +*/ + +#include "Audio.h" +#include "LibBasicFunctions.h" + +namespace BAGuitar { + +//////////////////////////////////////////////////// +// IirBiQuadFilter +//////////////////////////////////////////////////// +IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift) +: NUM_STAGES(numStages) +{ + m_coeffs = new int32_t[5*numStages]; + memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); + + m_state = new int32_t[4*numStages]; + arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); +} + +IirBiQuadFilter::~IirBiQuadFilter() +{ + if (m_coeffs) delete [] m_coeffs; + if (m_state) delete [] m_state; +} + + +bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples) +{ + if (!output) return false; + if (!input) { + // send zeros + memset(output, 0, numSamples * sizeof(int16_t)); + } else { + + // create convertion buffers on teh stack + int32_t input32[numSamples]; + int32_t output32[numSamples]; + for (size_t i=0; i(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); } diff --git a/src/BAAudioEffectDelayExternal.cpp b/src/effects/BAAudioEffectDelayExternal.cpp similarity index 100% rename from src/BAAudioEffectDelayExternal.cpp rename to src/effects/BAAudioEffectDelayExternal.cpp diff --git a/src/BAAudioControlWM8731.cpp b/src/peripherals/BAAudioControlWM8731.cpp similarity index 100% rename from src/BAAudioControlWM8731.cpp rename to src/peripherals/BAAudioControlWM8731.cpp diff --git a/src/BAGpio.cpp b/src/peripherals/BAGpio.cpp similarity index 100% rename from src/BAGpio.cpp rename to src/peripherals/BAGpio.cpp diff --git a/src/peripherals/BASpiMemory.cpp b/src/peripherals/BASpiMemory.cpp new file mode 100644 index 0000000..099935b --- /dev/null +++ b/src/peripherals/BASpiMemory.cpp @@ -0,0 +1,467 @@ +/* + * 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 . +*/ + +#include "Arduino.h" +#include "BASpiMemory.h" + +namespace BAGuitar { + +// 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; itransfer16(*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; itransfer16(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; itransfer(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; itransfer16(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; + case SpiDeviceId::SPI_DEVICE1 : + cs = SPI_CS_MEM1; + m_cs = new ActiveLowChipSelect1(cs, m_settings); + break; + 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; + case SpiDeviceId::SPI_DEVICE1 : + cs = SPI_CS_MEM1; + m_cs = new ActiveLowChipSelect1(cs, m_settings); + break; + 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(__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(); + 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, 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, 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(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, 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(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 BAGuitar */