From ecb0ba0aa8dc32ca94ec14e0df90774e3539a4f6 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 23 Dec 2017 09:46:08 -0500 Subject: [PATCH 01/27] Added bulk transfer functions to BASpiMemory, and start work on some library routines for external SRAM management and. NOT WORKING YET --- src/BAHardware.h | 1 + src/BASpiMemory.cpp | 112 +++++++++++++++++++++++++++++++++-- src/BASpiMemory.h | 20 +++++-- src/LibBasicFunctions.cpp | 35 +++++++++++ src/LibBasicFunctions.h | 23 +++++++ src/LibExtMemoryManagement.h | 66 +++++++++++++++++++++ 6 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 src/LibBasicFunctions.cpp create mode 100644 src/LibBasicFunctions.h create mode 100644 src/LibExtMemoryManagement.h diff --git a/src/BAHardware.h b/src/BAHardware.h index 0f13306..690b1da 100644 --- a/src/BAHardware.h +++ b/src/BAHardware.h @@ -57,6 +57,7 @@ enum class GPIO : uint8_t { /**************************************************************************//** * Optionally installed SPI RAM *****************************************************************************/ +constexpr unsigned NUM_MEM_SLOTS = 2; enum MemSelect : unsigned { MEM0 = 0, ///< SPI RAM MEM0 MEM1 = 1 ///< SPI RAM MEM1 diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index 36f9522..f618f86 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -96,7 +96,7 @@ BASpiMemory::~BASpiMemory() { } // Single address write -void BASpiMemory::write(int address, int data) +void BASpiMemory::write(size_t address, uint8_t data) { m_spi->beginTransaction(m_settings); digitalWrite(m_csPin, LOW); @@ -109,7 +109,42 @@ void BASpiMemory::write(int address, int data) digitalWrite(m_csPin, HIGH); } -void BASpiMemory::write16(int address, uint16_t data) +// Single address write +void BASpiMemory::write(size_t address, uint8_t *data, size_t numBytes) +{ + uint8_t *dataPtr = 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)); + + 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); @@ -120,8 +155,40 @@ void BASpiMemory::write16(int address, uint16_t data) digitalWrite(m_csPin, HIGH); } +void BASpiMemory::write16(size_t address, uint16_t *data, size_t numWords) +{ + uint16_t *dataPtr = data; + + 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); +} + // single address read -int BASpiMemory::read(int address) +uint8_t BASpiMemory::read(size_t address) { int data; @@ -137,7 +204,27 @@ int BASpiMemory::read(int address) return data; } -uint16_t BASpiMemory::read16(int address) + +void BASpiMemory::read(size_t address, uint8_t *data, size_t numBytes) +{ + uint8_t *dataPtr = 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)); + + for (size_t i=0; itransfer(0); + } + + m_spi->endTransaction(); + digitalWrite(m_csPin, HIGH); +} + +uint16_t BASpiMemory::read16(size_t address) { uint16_t data; @@ -152,4 +239,21 @@ uint16_t BASpiMemory::read16(int address) return data; } +void BASpiMemory::read16(size_t address, uint16_t *data, size_t numWords) +{ + + uint16_t *dataPtr = data; + 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); +} + } /* namespace BAGuitar */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index fc5450e..d62c925 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -39,10 +39,10 @@ public: BASpiMemory() = delete; /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2). /// @details default is 20 Mhz - /// @param memDeviceId specify which MEM to controlw with SpiDeviceId. + /// @param memDeviceId specify which MEM to control with SpiDeviceId. BASpiMemory(SpiDeviceId memDeviceId); /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2) - /// @param memDeviceId specify which MEM to controlw with SpiDeviceId. + /// @param memDeviceId specify which MEM to control with SpiDeviceId. /// @param speedHz specify the desired speed in Hz. BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz); virtual ~BASpiMemory(); @@ -52,19 +52,27 @@ public: /// write a single data word to the specified address /// @param address the address in the SPI RAM to write to /// @param data the value to write - void write(int address, int data); + void write(size_t address, uint8_t data); + void write(size_t address, uint8_t *data, size_t numBytes); + void zero(size_t address, size_t numBytes); - void write16(int address, uint16_t data); + void write16(size_t address, uint16_t data); + void write16(size_t address, uint16_t *data, size_t numBytes); + + void zero16(size_t address, size_t numBytes); /// read a single 8-bit data word from the specified address /// @param address the address in the SPI RAM to read from /// @return the data that was read - int read(int address); + uint8_t read(size_t address); + void read(size_t address, uint8_t *data, size_t numBytes); /// read a single 16-bit data word from the specified address /// @param address the address in the SPI RAM to read from /// @return the data that was read - uint16_t read16(int address); + uint16_t read16(size_t address); + void read16(size_t address, uint16_t *data, size_t numBytes); + private: SPIClass *m_spi = nullptr; diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp new file mode 100644 index 0000000..59ce791 --- /dev/null +++ b/src/LibBasicFunctions.cpp @@ -0,0 +1,35 @@ +/* + * LibBasicFunctions.cpp + * + * Created on: Dec 23, 2017 + * Author: slascos + */ + +#include "LibBasicFunctions.h" + +namespace BAGuitar { + +void updateAudioMemorySlot(BASpiMemory *mem, MemSlot slot, audio_block_t *block) +{ + if (block) { + if (slot.currentPosition + AUDIO_BLOCK_SAMPLES-1 <= slot.end) { + // entire block fits in memory slot without wrapping + mem->write16(slot.currentPosition, (uint16_t *)block->data, AUDIO_BLOCK_SAMPLES); // cast audio data to uint. + } else { + // this write will wrap the memory slot + size_t numBytes = slot.end - slot.currentPosition + 1; + mem->write16(slot.currentPosition, (uint16_t *)block->data, numBytes); + size_t remainingBytes = AUDIO_BLOCK_SAMPLES - numBytes; // calculate the remaining bytes + mem->write16(slot.start, (uint16_t *)block->data + numBytes, remainingBytes); // write remaining bytes are start + } + } +} + + +void zeroMemorySlot(BASpiMemory *mem, MemSlot slot) +{ + mem->zero16(slot.start, slot.end-slot.start+1); +} + +} + diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h new file mode 100644 index 0000000..e007cf0 --- /dev/null +++ b/src/LibBasicFunctions.h @@ -0,0 +1,23 @@ +/* + * LibBasicFunctions.h + * + * Created on: Dec 23, 2017 + * Author: slascos + */ + +#ifndef SRC_LIBBASICFUNCTIONS_H_ +#define SRC_LIBBASICFUNCTIONS_H_ + +#include "Audio.h" +#include "LibExtMemoryManagement.h" +#include "BASpiMemory.h" + +namespace BAGuitar { + +void updateAudioMemorySlot(BAGuitar::BASpiMemory *mem, MemSlot slot, audio_block_t *block); +void zeroMemorySlot(BAGuitar::BASpiMemory *mem, MemSlot slot); + +} + + +#endif /* SRC_LIBBASICFUNCTIONS_H_ */ diff --git a/src/LibExtMemoryManagement.h b/src/LibExtMemoryManagement.h new file mode 100644 index 0000000..7350d58 --- /dev/null +++ b/src/LibExtMemoryManagement.h @@ -0,0 +1,66 @@ +/* + * ExtMemoryManagement.h + * + * Created on: Dec 23, 2017 + * Author: slascos + */ + +#ifndef SRC_LIBEXTMEMORYMANAGEMENT_H_ +#define SRC_LIBEXTMEMORYMANAGEMENT_H_ + +#include + +#include "BAHardware.h" + +namespace BAGuitar { + +struct MemConfig { + size_t size; + size_t totalAvailable; + size_t nextAvailable; +}; + +struct MemSlot { + size_t start; + size_t end; + size_t currentPosition; +}; + +class ExternalSramManager { +public: + ExternalSramManager() = delete; + ExternalSramManager(BAGuitar::MemSelect mem) { + + // Initialize the static memory configuration structs + m_memConfig[MEM0].size = MEM0_MAX_ADDR; + m_memConfig[MEM0].totalAvailable = MEM0_MAX_ADDR; + m_memConfig[MEM0].nextAvailable = 0; + + m_memConfig[MEM0].size = MEM0_MAX_ADDR; + m_memConfig[MEM0].totalAvailable = MEM0_MAX_ADDR; + m_memConfig[MEM0].nextAvailable = 0; + } + + + bool getMemory(BAGuitar::MemSelect mem, size_t size, MemSlot &slot) { + if (m_memConfig[mem].totalAvailable >= size) { + slot.start = m_memConfig[mem].nextAvailable; + slot.end = slot.start + size -1; + slot.currentPosition = slot.start; + + // Update the mem config + m_memConfig[mem].nextAvailable = slot.end+1; + m_memConfig[mem].totalAvailable -= size; + return true; + } else { + return false; + } + } + + static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; + +}; + +} + +#endif /* SRC_LIBEXTMEMORYMANAGEMENT_H_ */ From 988174912b00139a2bcb9211dca876c59ce7ed6e Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sun, 24 Dec 2017 08:24:52 -0500 Subject: [PATCH 02/27] Updated BA2 1MEM example --- .../BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino | 84 +++++++++++++------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino b/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino index 2e644df..2649ae1 100644 --- a/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino +++ b/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino @@ -8,9 +8,13 @@ * This demo will provide an audio passthrough, as well as exercise the * MIDI interface. * - * It will also perform a sweeo of the SPI MEM0 external RAM. + * It will also peform a sweep of SPI MEM0. + * + * NOTE: SPI MEM0 can be used by a Teensy 3.1/3.2/3.5/3.6. + * pins. * */ +//#include #include #include #include @@ -19,7 +23,6 @@ MIDI_CREATE_DEFAULT_INSTANCE(); using namespace midi; - using namespace BAGuitar; AudioInputI2S i2sIn; @@ -32,23 +35,25 @@ AudioConnection patch1(i2sIn,1, i2sOut, 1); BAAudioControlWM8731 codecControl; BAGpio gpio; // access to User LED BASpiMemory spiMem0(SpiDeviceId::SPI_DEVICE0); -BASpiMemory spiMem1(SpiDeviceId::SPI_DEVICE1); unsigned long t=0; // SPI stuff int spiAddress0 = 0; -int spiData0 = 0xff; +uint16_t spiData0 = 0xABCD; int spiErrorCount0 = 0; -int spiAddress1 = 0; -int spiData1 = 0xff; -int spiErrorCount1 = 0; +constexpr int mask0 = 0x5555; +constexpr int mask1 = 0xaaaa; + +int maskPhase = 0; +int loopPhase = 0; void setup() { MIDI.begin(MIDI_CHANNEL_OMNI); Serial.begin(57600); + while (!Serial) {} delay(5); // If the codec was already powered up (due to reboot) power itd own first @@ -56,26 +61,53 @@ void setup() { delay(100); AudioMemory(24); - Serial.println("Enabling codec...\n"); + Serial.println("Sketch: Enabling codec...\n"); codecControl.enable(); delay(100); + + Serial.println("Enabling SPI"); + spiMem0.begin(); } -void loop() { +int calcData(int spiAddress, int loopPhase, int maskPhase) +{ + int data; + + int phase = ((loopPhase << 1) + maskPhase) & 0x3; + switch(phase) + { + case 0 : + data = spiAddress ^ mask0; + break; + case 1: + data = spiAddress ^ mask1; + break; + case 2: + data = ~spiAddress ^ mask0; + break; + case 3: + data = ~spiAddress ^ mask1; + + } + return (data & 0xffff); +} + +void loop() { ////////////////////////////////////////////////////////////////// // Write test data to the SPI Memory 0 ////////////////////////////////////////////////////////////////// - for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0++) { + maskPhase = 0; + for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0=spiAddress0+2) { if ((spiAddress0 % 32768) == 0) { //Serial.print("Writing to "); //Serial.println(spiAddress0, HEX); } - //mem0Write(spiAddress0, spiData0); - spiMem0.write(spiAddress0, spiData0); - spiData0 = (spiData0-1) & 0xff; + spiData0 = calcData(spiAddress0, loopPhase, maskPhase); + spiMem0.write16(spiAddress0, spiData0); + maskPhase = (maskPhase+1) % 2; } Serial.println("SPI0 writing DONE!"); @@ -84,39 +116,41 @@ void loop() { /////////////////////////////////////////////////////////////////// spiErrorCount0 = 0; spiAddress0 = 0; - spiData0 = 0xff; + maskPhase = 0; - for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0++) { + for (spiAddress0=0; spiAddress0 <= SPI_MAX_ADDR; spiAddress0=spiAddress0+2) { if ((spiAddress0 % 32768) == 0) { - //Serial.print("Reading "); - //Serial.print(spiAddress0, HEX); +// Serial.print("Reading "); +// Serial.print(spiAddress0, HEX); } - //int data = mem0Read(spiAddress0); - int data = spiMem0.read(spiAddress0); + spiData0 = calcData(spiAddress0, loopPhase, maskPhase); + int data = spiMem0.read16(spiAddress0); if (data != spiData0) { spiErrorCount0++; Serial.println(""); Serial.print("ERROR MEM0: (expected) (actual):"); Serial.print(spiData0, HEX); Serial.print(":"); - Serial.println(data, HEX); + Serial.print(data, HEX); + delay(100); } + maskPhase = (maskPhase+1) % 2; if ((spiAddress0 % 32768) == 0) { - //Serial.print(", data = "); - //Serial.println(data, HEX); +// Serial.print(", data = "); +// Serial.println(data, HEX); +// Serial.println(String(" loopPhase: ") + loopPhase + String(" maskPhase: ") + maskPhase); } - spiData0 = (spiData0-1) & 0xff; - // Break out of test once the error count reaches 10 if (spiErrorCount0 > 10) { break; } } if (spiErrorCount0 == 0) { Serial.println("SPI0 TEST PASSED!!!"); } - + + loopPhase = (loopPhase+1) % 2; /////////////////////////////////////////////////////////////////////// // MIDI TESTING From d2a8227f9108d9fc829e3d4d8850301234c5ca1b Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Tue, 9 Jan 2018 15:50:32 -0500 Subject: [PATCH 03/27] Development checkin --- src/AudioEffectAnalogDelay.cpp | 118 +++++++++++ src/AudioEffectAnalogDelay.h | 45 +++++ src/BAGuitar.h | 2 + src/BAHardware.h | 13 +- src/BASpiMemory.h | 4 +- src/LibBasicFunctions.cpp | 21 -- src/LibBasicFunctions.h | 80 +++++++- src/LibExtMemoryManagement.h | 66 ------ src/LibMemoryManagement.h | 119 +++++++++++ src/LibMemoryManagment.cpp | 360 +++++++++++++++++++++++++++++++++ 10 files changed, 728 insertions(+), 100 deletions(-) create mode 100644 src/AudioEffectAnalogDelay.cpp create mode 100644 src/AudioEffectAnalogDelay.h delete mode 100644 src/LibExtMemoryManagement.h create mode 100644 src/LibMemoryManagement.h create mode 100644 src/LibMemoryManagment.cpp diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp new file mode 100644 index 0000000..ff2ec8a --- /dev/null +++ b/src/AudioEffectAnalogDelay.cpp @@ -0,0 +1,118 @@ +/* + * AudioEffectAnalogDelay.cpp + * + * Created on: Jan 7, 2018 + * Author: slascos + */ +#include +#include "AudioEffectAnalogDelay.h" + +namespace BAGuitar { + +AudioEffectAnalogDelay::AudioEffectAnalogDelay(INTERNAL_MEMORY, float maxDelay) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new MemAudioBlock(maxDelay); + for (int i=0; iclear(); +} + +void AudioEffectAnalogDelay::update(void) +{ + audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples + + if (m_externalMemory) { + // external mem requires an actual write to memory with the new data block + if (inputAudioBlock) { + // valid block + m_memory->writeAdvance16(inputAudioBlock->data, AUDIO_BLOCK_SAMPLES); + } else { + // write zeros instead + m_memory->zeroAdvance16(AUDIO_BLOCK_SAMPLES); + } + } else { + // internal memory only requires updating the queue of audio block pointers. + MemAudioBlock *memory = reinterpret_cast(m_memory); + memory->push(inputAudioBlock); // MemAudioBlock supports nullptrs, no need to check + + // check to see if the queue has reached full size yet + if (memory->getNumQueues() > m_numQueues) { + release(memory->pop()); + } + } + + // For each active channel, output the delayed audio + audio_block_t *blockToOutput = nullptr; + for (int channel=0; channel(m_memory); + QueuePosition queuePosition = calcQueuePosition(m_channelOffsets[channel]); + if (queuePosition.offset == 0) { + // only one queue needs to be read out + blockToOutput= memory->getQueue(queuePosition.index); // could be nullptr! + transmit(blockToOutput); + } else { + blockToOutput = allocate(); // allocate if spanning 2 queues + } + } else { + blockToOutput = allocate(); // allocate always for external memory + } + + // copy the output data + if (!blockToOutput) continue; // skip this channel due to failure + // copy over data + m_memory->read16(blockToOutput->data, 0, m_channelOffsets[channel], AUDIO_BLOCK_SAMPLES); + transmit(blockToOutput); + release(blockToOutput); + } +} + +bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) +{ + if (channel > MAX_DELAY_CHANNELS-1) // channel id too high + return false; + + size_t delaySamples = calcAudioSamples(milliseconds); + + if (!m_externalMemory) { + // Internal memory (AudioStream buffers) + QueuePosition queuePosition = calcQueuePosition(milliseconds); + size_t numQueues = queuePosition.index+1; + if (numQueues > m_numQueues) + m_numQueues = numQueues; + } else { + // External memory + if (delaySamples > m_memory->getSize()) { + // error, the external memory is not large enough + return false; + } + } + + m_channelOffsets[channel] = delaySamples; + m_activeChannels |= 1< + +#include +#include "LibMemoryManagement.h" + +namespace BAGuitar { + + + +class AudioEffectAnalogDelay : public AudioStream { +public: + static constexpr int MAX_DELAY_CHANNELS = 8; + + AudioEffectAnalogDelay() = delete; + AudioEffectAnalogDelay(INTERNAL_MEMORY, float maxDelay); + AudioEffectAnalogDelay(EXTERNAL_MEMORY, MemSlot &slot); // requires sufficiently sized pre-allocated memory + virtual ~AudioEffectAnalogDelay() {} + + virtual void update(void); + bool delay(unsigned channel, float milliseconds); + void disable(unsigned channel); + +private: + audio_block_t *m_inputQueueArray[1]; + unsigned m_activeChannels = 0; + bool m_externalMemory = false; + MemBufferIF *m_memory = nullptr; + + size_t m_numQueues = 0; + size_t m_channelOffsets[MAX_DELAY_CHANNELS]; +}; + +} + +#endif /* SRC_AUDIOEFFECTANALOGDELAY_H_ */ diff --git a/src/BAGuitar.h b/src/BAGuitar.h index 0e3119e..65de0c9 100644 --- a/src/BAGuitar.h +++ b/src/BAGuitar.h @@ -28,4 +28,6 @@ #include "BAGpio.h" #include "BAAudioEffectDelayExternal.h" +#include "AudioEffectAnalogDelay.h" + #endif /* __SRC_BATGUITAR_H */ diff --git a/src/BAHardware.h b/src/BAHardware.h index 690b1da..100da33 100644 --- a/src/BAHardware.h +++ b/src/BAHardware.h @@ -23,6 +23,8 @@ #ifndef SRC_BAHARDWARE_H_ #define SRC_BAHARDWARE_H_ +#include + /**************************************************************************//** * BAGuitar is a namespace/Library for Guitar processing from Blackaddr Audio. *****************************************************************************/ @@ -62,15 +64,16 @@ enum MemSelect : unsigned { MEM0 = 0, ///< SPI RAM MEM0 MEM1 = 1 ///< SPI RAM MEM1 }; -constexpr int MEM0_MAX_ADDR = 131071; ///< Max address size per chip -constexpr int MEM1_MAX_ADDR = 131071; ///< Max address size per chip +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 *****************************************************************************/ -enum class SpiDeviceId { - SPI_DEVICE0, ///< Arduino SPI device - SPI_DEVICE1 ///< Arduino SPI1 device +enum SpiDeviceId : unsigned { + SPI_DEVICE0 = 0, ///< Arduino SPI device + SPI_DEVICE1 = 1 ///< Arduino SPI1 device }; constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index d62c925..fc5046f 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -57,7 +57,7 @@ public: void zero(size_t address, size_t numBytes); void write16(size_t address, uint16_t data); - void write16(size_t address, uint16_t *data, size_t numBytes); + void write16(size_t address, uint16_t *data, size_t numWords); void zero16(size_t address, size_t numBytes); @@ -71,7 +71,7 @@ public: /// @param address the address in the SPI RAM to read from /// @return the data that was read uint16_t read16(size_t address); - void read16(size_t address, uint16_t *data, size_t numBytes); + void read16(size_t address, uint16_t *data, size_t numWords); private: diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 59ce791..25e3178 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -9,27 +9,6 @@ namespace BAGuitar { -void updateAudioMemorySlot(BASpiMemory *mem, MemSlot slot, audio_block_t *block) -{ - if (block) { - if (slot.currentPosition + AUDIO_BLOCK_SAMPLES-1 <= slot.end) { - // entire block fits in memory slot without wrapping - mem->write16(slot.currentPosition, (uint16_t *)block->data, AUDIO_BLOCK_SAMPLES); // cast audio data to uint. - } else { - // this write will wrap the memory slot - size_t numBytes = slot.end - slot.currentPosition + 1; - mem->write16(slot.currentPosition, (uint16_t *)block->data, numBytes); - size_t remainingBytes = AUDIO_BLOCK_SAMPLES - numBytes; // calculate the remaining bytes - mem->write16(slot.start, (uint16_t *)block->data + numBytes, remainingBytes); // write remaining bytes are start - } - } -} - - -void zeroMemorySlot(BASpiMemory *mem, MemSlot slot) -{ - mem->zero16(slot.start, slot.end-slot.start+1); -} } diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index e007cf0..386fabf 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -5,17 +5,85 @@ * Author: slascos */ +#include +#include + +#include "Arduino.h" + #ifndef SRC_LIBBASICFUNCTIONS_H_ #define SRC_LIBBASICFUNCTIONS_H_ -#include "Audio.h" -#include "LibExtMemoryManagement.h" -#include "BASpiMemory.h" - namespace BAGuitar { -void updateAudioMemorySlot(BAGuitar::BASpiMemory *mem, MemSlot slot, audio_block_t *block); -void zeroMemorySlot(BAGuitar::BASpiMemory *mem, MemSlot slot); +struct INTERNAL_MEMORY {}; +struct EXTERNAL_MEMORY {}; + +template +class RingBuffer { +public: + RingBuffer() = delete; + RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { + m_buffer = new T[maxSize]; + } + virtual ~RingBuffer(){ + if (m_buffer) delete [] m_buffer; + } + + int push_back(T element) { + + if ( (m_head == m_tail) && (m_size > 0) ) { + // overflow + Serial.println("RingBuffer::push_back: overflow"); + return -1; + } + + m_buffer[m_head] = element; + if (m_head < m_maxSize-1) { + m_head++; + } else { + m_head = 0; + } + m_size++; + return 0; + } + + int pop_front() { + if (m_size == 0) { + // buffer is empty + Serial.println("RingBuffer::pop_front: buffer is empty\n"); + return -1; + } + if (m_tail < m_maxSize-1) { + m_tail++; + } else { + m_tail = 0; + } + m_size--; + return 0; + } + + T front() const { + return m_buffer[m_tail]; + } + + size_t size() const { + size_t size = 0; + if (m_head == m_tail) { size = 0; } + else if (m_head > m_tail) { size = (m_head - m_tail); } + else { size = (m_tail - m_head + 1); } + return size; + } + + T& operator[] (size_t index) { + return m_buffer[index]; + } +private: + size_t m_head=0; + size_t m_tail=0; + size_t m_size=0; + T *m_buffer = nullptr; + const size_t m_maxSize; +}; } diff --git a/src/LibExtMemoryManagement.h b/src/LibExtMemoryManagement.h deleted file mode 100644 index 7350d58..0000000 --- a/src/LibExtMemoryManagement.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * ExtMemoryManagement.h - * - * Created on: Dec 23, 2017 - * Author: slascos - */ - -#ifndef SRC_LIBEXTMEMORYMANAGEMENT_H_ -#define SRC_LIBEXTMEMORYMANAGEMENT_H_ - -#include - -#include "BAHardware.h" - -namespace BAGuitar { - -struct MemConfig { - size_t size; - size_t totalAvailable; - size_t nextAvailable; -}; - -struct MemSlot { - size_t start; - size_t end; - size_t currentPosition; -}; - -class ExternalSramManager { -public: - ExternalSramManager() = delete; - ExternalSramManager(BAGuitar::MemSelect mem) { - - // Initialize the static memory configuration structs - m_memConfig[MEM0].size = MEM0_MAX_ADDR; - m_memConfig[MEM0].totalAvailable = MEM0_MAX_ADDR; - m_memConfig[MEM0].nextAvailable = 0; - - m_memConfig[MEM0].size = MEM0_MAX_ADDR; - m_memConfig[MEM0].totalAvailable = MEM0_MAX_ADDR; - m_memConfig[MEM0].nextAvailable = 0; - } - - - bool getMemory(BAGuitar::MemSelect mem, size_t size, MemSlot &slot) { - if (m_memConfig[mem].totalAvailable >= size) { - slot.start = m_memConfig[mem].nextAvailable; - slot.end = slot.start + size -1; - slot.currentPosition = slot.start; - - // Update the mem config - m_memConfig[mem].nextAvailable = slot.end+1; - m_memConfig[mem].totalAvailable -= size; - return true; - } else { - return false; - } - } - - static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; - -}; - -} - -#endif /* SRC_LIBEXTMEMORYMANAGEMENT_H_ */ diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h new file mode 100644 index 0000000..118fbfd --- /dev/null +++ b/src/LibMemoryManagement.h @@ -0,0 +1,119 @@ +/* + * ExtMemoryManagement.h + * + * Created on: Dec 23, 2017 + * Author: slascos + */ + +#ifndef SRC_LIBMEMORYMANAGEMENT_H_ +#define SRC_LIBMEMORYMANAGEMENT_H_ + +#include + + +#include "Audio.h" + +#include "BAHardware.h" +#include "BASpiMemory.h" +#include "LibBasicFunctions.h" + +namespace BAGuitar { + +struct QueuePosition { + int offset; + int index; +}; +QueuePosition calcQueuePosition(float milliseconds); +QueuePosition calcQueuePosition(size_t numSamples); +size_t calcAudioSamples(float milliseconds); + +struct MemConfig { + size_t size; + size_t totalAvailable; + size_t nextAvailable; + BASpiMemory *m_spi = nullptr; +}; + +class ExternalSramManager; // forward declare so MemSlot can setup friendship + +class MemBufferIF { +public: + size_t getSize() const { return m_size; } + virtual bool clear() = 0; + virtual bool write16(size_t offset, int16_t *dataPtr, size_t numData) = 0; + virtual bool zero16(size_t offset, size_t numData) = 0; + virtual bool read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numData) = 0; + virtual bool writeAdvance16(int16_t *dataPtr, size_t numData) = 0; + virtual bool zeroAdvance16(size_t numData) = 0; + virtual ~MemBufferIF() {} +protected: + bool m_valid = false; + size_t m_size = 0; +}; + +class MemAudioBlock : public MemBufferIF { +public: + //MemAudioBlock(); + MemAudioBlock() = delete; + MemAudioBlock(size_t numSamples); + MemAudioBlock(float milliseconds); + + virtual ~MemAudioBlock(); + + bool push(audio_block_t *block); + audio_block_t *pop(); + audio_block_t *getQueue(size_t index) { return m_queues[index]; } + size_t getNumQueues() const { return m_queues.size(); } + bool clear() override; + bool write16(size_t offset, int16_t *dataPtr, size_t numData) override; + bool zero16(size_t offset, size_t numSamples) override; + bool read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numSamples); + bool writeAdvance16(int16_t *dataPtr, size_t numData) override; + bool zeroAdvance16(size_t numData) override; +private: + //size_t m_numQueues; + BAGuitar::RingBuffer m_queues; + QueuePosition m_currentPosition = {0,0}; + +}; + + +class MemSlot : public MemBufferIF { +public: + + bool clear() override; + bool write16(size_t offset, int16_t *dataPtr, size_t numData) override; + bool zero16(size_t offset, size_t numData) override; + bool read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numData); + bool writeAdvance16(int16_t *dataPtr, size_t numData) override; + bool zeroAdvance16(size_t numData) override; + +private: + friend ExternalSramManager; + size_t m_start; + size_t m_end; + size_t m_currentPosition; + BASpiMemory *m_spi = nullptr; +}; + + +class ExternalSramManager final { +public: + ExternalSramManager() = delete; + ExternalSramManager(unsigned numMemories); + virtual ~ExternalSramManager(); + + size_t availableMemory(BAGuitar::MemSelect mem); + bool requestMemory(MemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(MemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + +private: + static bool m_configured; + static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; + +}; + + +} + +#endif /* SRC_LIBMEMORYMANAGEMENT_H_ */ diff --git a/src/LibMemoryManagment.cpp b/src/LibMemoryManagment.cpp new file mode 100644 index 0000000..576ff67 --- /dev/null +++ b/src/LibMemoryManagment.cpp @@ -0,0 +1,360 @@ + +#include +#include + +#include "LibMemoryManagement.h" + +namespace BAGuitar { + + + +bool ExternalSramManager::m_configured = false; +MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS]; + +inline 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; +} + +///////////////////////////////////////////////////////////////////////////// +// MEM BUFFER IF +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// MEM VIRTUAL +///////////////////////////////////////////////////////////////////////////// +MemAudioBlock::MemAudioBlock(size_t numSamples) +: m_queues((numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES) +{ +// // round up to an integer multiple of AUDIO_BLOCK_SAMPLES +// int numQueues = (numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES; +// +// // Preload the queue with nullptrs to set the queue depth to the correct size +// for (int i=0; i < numQueues; i++) { +// m_queues.push_back(nullptr); +// } +} + +MemAudioBlock::MemAudioBlock(float milliseconds) +: MemAudioBlock(calcAudioSamples(milliseconds)) +{ +} + +MemAudioBlock::~MemAudioBlock() +{ + +} + +bool MemAudioBlock::push(audio_block_t *block) +{ + m_queues.push_back(block); + return true; +} + +audio_block_t* MemAudioBlock::pop() +{ + audio_block_t* block = m_queues.front(); + m_queues.pop_front(); + return block; +} + +bool MemAudioBlock::clear() +{ + for (size_t i=0; i < m_queues.size(); i++) { + if (m_queues[i]) { + memset(m_queues[i]->data, 0, AUDIO_BLOCK_SAMPLES * sizeof(int16_t)); + } + } + return true; +} + +bool MemAudioBlock::write16(size_t offset, int16_t *dataPtr, size_t numData) +{ + // Calculate the queue position + auto position = calcQueuePosition(offset); + int writeOffset = position.offset; + size_t index = position.index; + + if ( (index+1) > m_queues.size()) return false; // out of range + + // loop over a series of memcpys until all data is transferred. + size_t samplesRemaining = numData; + + while (samplesRemaining > 0) { + size_t numSamplesToWrite; + + void *start = static_cast(m_queues[index]->data + writeOffset); + + // determine if the transfer will complete or will hit the end of a block first + if ( (writeOffset + samplesRemaining) > AUDIO_BLOCK_SAMPLES ) { + // goes past end of the queue + numSamplesToWrite = (AUDIO_BLOCK_SAMPLES - writeOffset); + writeOffset = 0; + index++; + } else { + // transfer ends in this audio block + numSamplesToWrite = samplesRemaining; + writeOffset += numSamplesToWrite; + } + + // perform the transfer + if (!m_queues[index]) { + // no allocated audio block, skip the copy + } else { + if (dataPtr) { + memcpy(start, dataPtr, numSamplesToWrite * sizeof(int16_t)); + } else { + memset(start, 0, numSamplesToWrite * sizeof(int16_t)); + } + } + samplesRemaining -= numSamplesToWrite; + } + m_currentPosition.offset = writeOffset; + m_currentPosition.index = index; + return true; +} + +inline bool MemAudioBlock::zero16(size_t offset, size_t numData) +{ + return write16(offset, nullptr, numData); +} + +bool MemAudioBlock::read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numSamples) +{ + if (!dest) return false; // destination is not valid + + // Calculate the queue position + auto position = calcQueuePosition(srcOffset); + int readOffset = position.offset; + size_t index = position.index; + + if ( (index+1) > m_queues.size()) return false; // out of range + + // loop over a series of memcpys until all data is transferred. + size_t samplesRemaining = numSamples; + + while (samplesRemaining > 0) { + size_t numSamplesToRead; + + void *srcStart = static_cast(m_queues[index]->data + readOffset); + void *destStart = static_cast(dest + destOffset); + + // determine if the transfer will complete or will hit the end of a block first + if ( (readOffset + samplesRemaining) > AUDIO_BLOCK_SAMPLES ) { + // goes past end of the queue + numSamplesToRead = (AUDIO_BLOCK_SAMPLES - readOffset); + readOffset = 0; + index++; + } else { + // transfer ends in this audio block + numSamplesToRead = samplesRemaining; + readOffset += numSamplesToRead; + } + + // perform the transfer + if (!m_queues[index]) { + // no allocated audio block, copy zeros + memset(destStart, 0, numSamplesToRead * sizeof(int16_t)); + } else { + memcpy(srcStart, destStart, numSamplesToRead * sizeof(int16_t)); + } + samplesRemaining -= numSamplesToRead; + } + return true; +} + +// If this function hits the end of the queues it will wrap to the start +bool MemAudioBlock::writeAdvance16(int16_t *dataPtr, size_t numData) +{ + auto globalOffset = calcOffset(m_currentPosition); + auto end = globalOffset + numData; + + if ( end >= (m_queues.size() * AUDIO_BLOCK_SAMPLES) ) { + // transfer will wrap, so break into two + auto samplesToWrite = end - globalOffset; + // write the first chunk + write16(globalOffset, dataPtr, samplesToWrite); + // write the scond chunk + int16_t *ptr; + if (dataPtr) { + // valid dataptr, advance the pointer + ptr = dataPtr+samplesToWrite; + } else { + // dataPtr was nullptr + ptr = nullptr; + } + write16(0, ptr, numData-samplesToWrite); + } else { + // no wrap + write16(globalOffset, dataPtr, numData); + } + return true; +} + +bool MemAudioBlock::zeroAdvance16(size_t numData) +{ + return writeAdvance16(nullptr, numData); +} + +///////////////////////////////////////////////////////////////////////////// +// MEM SLOT +///////////////////////////////////////////////////////////////////////////// +bool MemSlot::clear() +{ + if (!m_valid) { return false; } + m_spi->zero16(m_start, m_size); + return true; +} + +bool MemSlot::write16(size_t offset, int16_t *dataPtr, size_t dataSize) +{ + if (!m_valid) { return false; } + if ((offset + dataSize-1) <= m_end) { + m_spi->write16(offset, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint + return true; + } else { + // this would go past the end of the memory slot, do not perform the write + return false; + } +} + +bool MemSlot::zero16(size_t offset, size_t dataSize) +{ + if (!m_valid) { return false; } + if ((offset + dataSize-1) <= m_end) { + m_spi->zero16(offset, dataSize); // cast audio data to uint + return true; + } else { + // this would go past the end of the memory slot, do not perform the write + return false; + } +} + +bool MemSlot::read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numData) +{ + if (!dest) return false; // invalid destination + if ((srcOffset + (numData*sizeof(int16_t))-1) <= m_end) { + m_spi->read16(srcOffset, reinterpret_cast(dest), numData); + return true; + } else { + // this would go past the end of the memory slot, do not perform the write + return false; + } +} + +bool MemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) +{ + if (!m_valid) { return false; } + if (m_currentPosition + dataSize-1 <= m_end) { + // entire block fits in memory slot without wrapping + m_spi->write16(m_currentPosition, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint. + m_currentPosition += dataSize; + + } else { + // this write will wrap the memory slot + size_t numBytes = m_end - m_currentPosition + 1; + m_spi->write16(m_currentPosition, reinterpret_cast(dataPtr), numBytes); + size_t remainingBytes = dataSize - numBytes; // calculate the remaining bytes + m_spi->write16(m_start, reinterpret_cast(dataPtr + numBytes), remainingBytes); // write remaining bytes are start + m_currentPosition = m_start + remainingBytes; + } + return true; +} + +bool MemSlot::zeroAdvance16(size_t dataSize) +{ + if (!m_valid) { return false; } + if (m_currentPosition + dataSize-1 <= m_end) { + // entire block fits in memory slot without wrapping + m_spi->zero16(m_currentPosition, dataSize); // cast audio data to uint. + m_currentPosition += dataSize; + + } else { + // this write will wrap the memory slot + size_t numBytes = m_end - m_currentPosition + 1; + m_spi->zero16(m_currentPosition, numBytes); + size_t remainingBytes = dataSize - numBytes; // calculate the remaining bytes + m_spi->zero16(m_start, remainingBytes); // write remaining bytes are start + m_currentPosition = m_start + remainingBytes; + } + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +// EXTERNAL SRAM MANAGER +///////////////////////////////////////////////////////////////////////////// +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 = new BAGuitar::BASpiMemory(static_cast(i)); + } + 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(MemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem) +{ + // 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, mem); +} + +bool ExternalSramManager::requestMemory(MemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem) +{ + if (m_memConfig[mem].totalAvailable >= sizeBytes) { + // 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_currentPosition = slot.m_start; // init to start of slot + slot.m_size = sizeBytes; + 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; + return true; + } else { + // there is not enough memory available for the request + return false; + } +} + +} + From f47ffc78db0bb6c0eb79dab7a6d9ac198c2e10ac Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Wed, 17 Jan 2018 17:42:36 -0500 Subject: [PATCH 04/27] try new AudioDelay class...wip --- src/LibBasicFunctions.cpp | 49 +++++++++++++++++++++++++++++++++++++++ src/LibBasicFunctions.h | 28 ++++++++++++++++++++++ src/LibMemoryManagement.h | 2 +- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 25e3178..43bc0c2 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -9,6 +9,55 @@ namespace BAGuitar { +AudioDelay::AudioDelay(MemType type, size_t maxSamples) +{ + m_type = type; + if (type == MemType::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. + } else { + // TODO EXTERNAL memory + + } +} + +AudioDelay::AudioDelay(MemType type, float delayTimeMs) +: AudioDelay(type, calcAudioSamples(delayTimeMs)) +{ + +} + +AudioDelay::~AudioDelay() +{ + if (m_ringBuffer) delete m_ringBuffer; +} + +bool AudioDelay::addBlock(audio_block_t *block) +{ + if (m_type != MemType::INTERNAL) return false; // ERROR + + // purposefully don't check if block is valid, the ringBuffer can support nullptrs + if ( m_ringBuffer->size() < m_ringBuffer->max_size() ) { + // pop before adding + release(m_ringBuffer->front()); + m_ringBuffer->pop_front(); + } + + // add the new buffer + m_ringBuffer->push_back(block); + +} + +void AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES) +{ + QueuePosition pos = calcQueuePosition(offset); + size_t readOffset = pos.offset; + size_t index = pos.index; + + // Audio is stored in reverse order. That means the first sample (in time) goes in the last location in the audio block. + +} } diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 386fabf..7ba0f5c 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -9,15 +9,39 @@ #include #include "Arduino.h" +#include "Audio.H" +#include "LibMemoryManagement.h" #ifndef SRC_LIBBASICFUNCTIONS_H_ #define SRC_LIBBASICFUNCTIONS_H_ namespace BAGuitar { +class RingBuffer; // forward declare + +enum MemType : unsigned { + INTERNAL, + EXTERNAL +}; + struct INTERNAL_MEMORY {}; struct EXTERNAL_MEMORY {}; +class AudioDelay { + + AudioDelay() = delete; + AudioDelay(MemType type, size_t maxSamples); + AudioDelay(MemType type, float delayTimeMs); + ~AudioDelay(); + + void addBlock(audio_block_t *block); + + void getSamples(size_t offset, size_t numSamples); +private: + MemType m_type; + RingBuffer *m_ringBuffer = nullptr; +}; + template class RingBuffer { public: @@ -74,6 +98,10 @@ public: return size; } + size_t max_size() const { + return m_maxSize; + } + T& operator[] (size_t index) { return m_buffer[index]; } diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 118fbfd..e2ab3bc 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -15,7 +15,7 @@ #include "BAHardware.h" #include "BASpiMemory.h" -#include "LibBasicFunctions.h" +//#include "LibBasicFunctions.h" namespace BAGuitar { From 03da5145b3b7e7999b47649bf929b4af5b66c15e Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Wed, 17 Jan 2018 18:54:25 -0500 Subject: [PATCH 05/27] Further development --- src/AudioEffectAnalogDelay.cpp | 101 ++++++++++++++++-- src/AudioEffectAnalogDelay.h | 4 + src/LibBasicFunctions.h | 43 ++++++-- ...yManagment.cpp => LibMemoryManagement.cpp} | 96 ++++++++++------- src/LibMemoryManagement.h | 3 +- 5 files changed, 194 insertions(+), 53 deletions(-) rename src/{LibMemoryManagment.cpp => LibMemoryManagement.cpp} (73%) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index ff2ec8a..43dcf37 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -18,6 +18,15 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(INTERNAL_MEMORY, float maxDelay) } } +AudioEffectAnalogDelay::AudioEffectAnalogDelay(INTERNAL_MEMORY, size_t numSamples) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new MemAudioBlock(numSamples); + for (int i=0; i 1400) { +// if (inputAudioBlock) release(inputAudioBlock); +// return; +// } + m_callCount++; + Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); + if (m_externalMemory) { // external mem requires an actual write to memory with the new data block @@ -45,13 +79,30 @@ void AudioEffectAnalogDelay::update(void) } else { // internal memory only requires updating the queue of audio block pointers. MemAudioBlock *memory = reinterpret_cast(m_memory); - memory->push(inputAudioBlock); // MemAudioBlock supports nullptrs, no need to check // check to see if the queue has reached full size yet - if (memory->getNumQueues() > m_numQueues) { + //Serial.println(String("memory queues:") + memory->getNumQueues() + String(" m_numQueues:") + m_numQueues); + if (memory->getNumQueues() >= m_numQueues) { release(memory->pop()); } + + // now push in the newest audio block + //Serial.println(String("push ") + (uint32_t)inputAudioBlock); + memory->push(inputAudioBlock); // MemAudioBlock supports nullptrs, no need to check + //Serial.println("done memory->push()"); + +// audio_block_t *output = memory->getQueueBack(); +// Serial.println("got the output"); +// if (output) { +// transmit(output, 0); +// } +// Serial.println("Done transmit"); } +// return; + + + //Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); + // For each active channel, output the delayed audio audio_block_t *blockToOutput = nullptr; @@ -62,10 +113,13 @@ void AudioEffectAnalogDelay::update(void) // internal memory MemAudioBlock *memory = reinterpret_cast(m_memory); QueuePosition queuePosition = calcQueuePosition(m_channelOffsets[channel]); + Serial.println(String("Position info: ") + queuePosition.index + " : " + queuePosition.offset); if (queuePosition.offset == 0) { // only one queue needs to be read out - blockToOutput= memory->getQueue(queuePosition.index); // could be nullptr! - transmit(blockToOutput); + //Serial.println(String("Directly sending queue offset ") + queuePosition.index); + blockToOutput= memory->getQueueBack(queuePosition.index); // could be nullptr! + if (blockToOutput) transmit(blockToOutput); + continue; } else { blockToOutput = allocate(); // allocate if spanning 2 queues } @@ -91,8 +145,43 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) if (!m_externalMemory) { // Internal memory (AudioStream buffers) + MemAudioBlock *memory = reinterpret_cast(m_memory); + QueuePosition queuePosition = calcQueuePosition(milliseconds); - size_t numQueues = queuePosition.index+1; + Serial.println(String("CONFIG(") + memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); + // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this + // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. + size_t numQueues = queuePosition.index+2; + if (numQueues > m_numQueues) + m_numQueues = numQueues; + } else { + // External memory + if (delaySamples > m_memory->getSize()) { + // error, the external memory is not large enough + return false; + } + } + + m_channelOffsets[channel] = delaySamples; + m_activeChannels |= 1< MAX_DELAY_CHANNELS-1) // channel id too high + return false; + + if (!m_externalMemory) { + // Internal memory (AudioStream buffers) + MemAudioBlock *memory = reinterpret_cast(m_memory); + + //QueuePosition queuePosition = calcQueuePosition(milliseconds); + QueuePosition queuePosition = calcQueuePosition(delaySamples); + Serial.println(String("CONFIG(") + memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); + // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this + // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. + size_t numQueues = queuePosition.index+2; if (numQueues > m_numQueues) m_numQueues = numQueues; } else { diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index c14066b..e6be2bb 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -23,11 +23,13 @@ public: AudioEffectAnalogDelay() = delete; AudioEffectAnalogDelay(INTERNAL_MEMORY, float maxDelay); + AudioEffectAnalogDelay(INTERNAL_MEMORY, size_t numSamples); AudioEffectAnalogDelay(EXTERNAL_MEMORY, MemSlot &slot); // requires sufficiently sized pre-allocated memory virtual ~AudioEffectAnalogDelay() {} virtual void update(void); bool delay(unsigned channel, float milliseconds); + bool delay(unsigned channel, size_t delaySamples); void disable(unsigned channel); private: @@ -38,6 +40,8 @@ private: size_t m_numQueues = 0; size_t m_channelOffsets[MAX_DELAY_CHANNELS]; + + size_t m_callCount = 0; }; } diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 386fabf..02146aa 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -24,6 +24,7 @@ public: RingBuffer() = delete; RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { m_buffer = new T[maxSize]; + //Serial.println(String("New RingBuffer: max size is ") + m_maxSize); } virtual ~RingBuffer(){ if (m_buffer) delete [] m_buffer; @@ -31,23 +32,28 @@ public: int push_back(T element) { + Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); if ( (m_head == m_tail) && (m_size > 0) ) { // overflow Serial.println("RingBuffer::push_back: overflow"); + while(1) {} // TODO REMOVE return -1; } m_buffer[m_head] = element; - if (m_head < m_maxSize-1) { + if (m_head < (m_maxSize-1) ) { m_head++; } else { m_head = 0; } m_size++; + //Serial.println(" ...Done push"); + return 0; } int pop_front() { + if (m_size == 0) { // buffer is empty Serial.println("RingBuffer::pop_front: buffer is empty\n"); @@ -59,6 +65,7 @@ public: m_tail = 0; } m_size--; + Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); return 0; } @@ -66,17 +73,41 @@ public: return m_buffer[m_tail]; } + T back() const { + return m_buffer[m_head-1]; + } + + size_t getBackIndex(size_t offset = 0) const { + // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; + size_t idx = (m_maxSize + m_head -1 - offset); + + if ( idx >= m_maxSize) { + idx -= m_maxSize; + } + + //Serial.println(String("BackIndex is ") + idx + String(" address: ") + (uint32_t)m_buffer[idx] + String(" data: ") + (uint32_t)m_buffer[idx]->data); +// for (int i=0; idata); +// } + return idx; + } + size_t size() const { - size_t size = 0; - if (m_head == m_tail) { size = 0; } - else if (m_head > m_tail) { size = (m_head - m_tail); } - else { size = (m_tail - m_head + 1); } - return size; + //Serial.println(String("RingBuffer::size: ") + m_head + String(":") + m_tail + String(":") + m_size); + return m_size; } + size_t getMaxSize() const { return m_maxSize; } + T& operator[] (size_t index) { return m_buffer[index]; } + + void print() const { + for (int idx=0; idxdata); + } + } private: size_t m_head=0; size_t m_tail=0; diff --git a/src/LibMemoryManagment.cpp b/src/LibMemoryManagement.cpp similarity index 73% rename from src/LibMemoryManagment.cpp rename to src/LibMemoryManagement.cpp index 576ff67..137c507 100644 --- a/src/LibMemoryManagment.cpp +++ b/src/LibMemoryManagement.cpp @@ -6,12 +6,10 @@ namespace BAGuitar { - - bool ExternalSramManager::m_configured = false; MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS]; -inline size_t calcAudioSamples(float milliseconds) +size_t calcAudioSamples(float milliseconds) { return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); } @@ -42,7 +40,7 @@ size_t calcOffset(QueuePosition position) // MEM VIRTUAL ///////////////////////////////////////////////////////////////////////////// MemAudioBlock::MemAudioBlock(size_t numSamples) -: m_queues((numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES) +: m_queues(((numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES) +1) { // // round up to an integer multiple of AUDIO_BLOCK_SAMPLES // int numQueues = (numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES; @@ -63,14 +61,28 @@ MemAudioBlock::~MemAudioBlock() } +// the index is referenced from the head +audio_block_t *MemAudioBlock::getQueueBack(size_t offset) +{ + +// for (int i=0; idata); +// } +// Serial.println(String("Returning ") + (uint32_t)m_queues[m_queues.getBackIndex(offset)]); + return m_queues[m_queues.getBackIndex(offset)]; +} + bool MemAudioBlock::push(audio_block_t *block) { + //Serial.println("MemAudioBlock::push()"); m_queues.push_back(block); + //Serial.println("MemAudioBlock::push() done"); return true; } audio_block_t* MemAudioBlock::pop() { + //Serial.println("MemAudioBlock::pop()"); audio_block_t* block = m_queues.front(); m_queues.pop_front(); return block; @@ -86,7 +98,7 @@ bool MemAudioBlock::clear() return true; } -bool MemAudioBlock::write16(size_t offset, int16_t *dataPtr, size_t numData) +bool MemAudioBlock::write16(size_t offset, int16_t *srcDataPtr, size_t numData) { // Calculate the queue position auto position = calcQueuePosition(offset); @@ -98,33 +110,38 @@ bool MemAudioBlock::write16(size_t offset, int16_t *dataPtr, size_t numData) // loop over a series of memcpys until all data is transferred. size_t samplesRemaining = numData; + int16_t *srcStart = srcDataPtr; // this will increment during each loop iteration + while (samplesRemaining > 0) { size_t numSamplesToWrite; - void *start = static_cast(m_queues[index]->data + writeOffset); + void *destStart = static_cast(m_queues[index]->data + writeOffset); // determine if the transfer will complete or will hit the end of a block first if ( (writeOffset + samplesRemaining) > AUDIO_BLOCK_SAMPLES ) { // goes past end of the queue numSamplesToWrite = (AUDIO_BLOCK_SAMPLES - writeOffset); - writeOffset = 0; - index++; + //writeOffset = 0; + //index++; } else { // transfer ends in this audio block numSamplesToWrite = samplesRemaining; - writeOffset += numSamplesToWrite; + //writeOffset += numSamplesToWrite; } // perform the transfer if (!m_queues[index]) { // no allocated audio block, skip the copy } else { - if (dataPtr) { - memcpy(start, dataPtr, numSamplesToWrite * sizeof(int16_t)); + if (srcDataPtr) { + memcpy(destStart, static_cast(srcStart), numSamplesToWrite * sizeof(int16_t)); } else { - memset(start, 0, numSamplesToWrite * sizeof(int16_t)); + memset(destStart, 0, numSamplesToWrite * sizeof(int16_t)); } } + writeOffset = 0; + index++; + srcStart += numSamplesToWrite; samplesRemaining -= numSamplesToWrite; } m_currentPosition.offset = writeOffset; @@ -140,44 +157,43 @@ inline bool MemAudioBlock::zero16(size_t offset, size_t numData) bool MemAudioBlock::read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numSamples) { if (!dest) return false; // destination is not valid + (void)destOffset; // not supported with audio_block_t; + //Serial.println("*************************************************************************"); + //Serial.println(String("read16():") + (uint32_t)dest + String(":") + destOffset + String(":") + srcOffset + String(":") + numSamples); // Calculate the queue position auto position = calcQueuePosition(srcOffset); - int readOffset = position.offset; size_t index = position.index; - if ( (index+1) > m_queues.size()) return false; // out of range + // Break the transfer in two. Note that the audio is stored first sample (in time) last (in memory). - // loop over a series of memcpys until all data is transferred. - size_t samplesRemaining = numSamples; + int16_t *destStart = dest; + audio_block_t *currentQueue; + int16_t *srcStart; - while (samplesRemaining > 0) { - size_t numSamplesToRead; + // Break the transfer into two. Note that the audio + //Serial.println("Calling getQueue"); + currentQueue = getQueueBack(index+1); // buffer indexes go backwards from the back + //Serial.println(String("Q. Address: ") + (uint32_t)currentQueue + String(" Data: ") + (uint32_t)currentQueue->data); + srcStart = (currentQueue->data + AUDIO_BLOCK_SAMPLES - position.offset); + size_t numData = position.offset; + //Serial.println(String("Source Start1: ") + (uint32_t)currentQueue->data + String(" Dest start1: ") + (uint32_t)dest); + //Serial.println(String("copying to ") + (uint32_t)destStart + String(" from ") + (uint32_t)srcStart + String(" numData= ") + numData); + memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); - void *srcStart = static_cast(m_queues[index]->data + readOffset); - void *destStart = static_cast(dest + destOffset); - // determine if the transfer will complete or will hit the end of a block first - if ( (readOffset + samplesRemaining) > AUDIO_BLOCK_SAMPLES ) { - // goes past end of the queue - numSamplesToRead = (AUDIO_BLOCK_SAMPLES - readOffset); - readOffset = 0; - index++; - } else { - // transfer ends in this audio block - numSamplesToRead = samplesRemaining; - readOffset += numSamplesToRead; - } + currentQueue = getQueueBack(index); // buffer indexes go backwards from the back + //Serial.println(String("Q. Address: ") + (uint32_t)currentQueue + String(" Data: ") + (uint32_t)currentQueue->data); + destStart += numData; + srcStart = (currentQueue->data); + numData = AUDIO_BLOCK_SAMPLES - numData; + //Serial.println(String("Source Start2: ") + (uint32_t)currentQueue->data + String(" Dest start2: ") + (uint32_t)dest); + //Serial.println(String("copying to ") + (uint32_t)destStart + String(" from ") + (uint32_t)srcStart + String(" numData= ") + numData); + memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); + + //m_queues.print(); + //Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - // perform the transfer - if (!m_queues[index]) { - // no allocated audio block, copy zeros - memset(destStart, 0, numSamplesToRead * sizeof(int16_t)); - } else { - memcpy(srcStart, destStart, numSamplesToRead * sizeof(int16_t)); - } - samplesRemaining -= numSamplesToRead; - } return true; } diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 118fbfd..ea163b8 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -62,8 +62,9 @@ public: bool push(audio_block_t *block); audio_block_t *pop(); - audio_block_t *getQueue(size_t index) { return m_queues[index]; } + audio_block_t *getQueueBack(size_t offset=0); size_t getNumQueues() const { return m_queues.size(); } + size_t getMaxSize() const { return m_queues.getMaxSize(); } bool clear() override; bool write16(size_t offset, int16_t *dataPtr, size_t numData) override; bool zero16(size_t offset, size_t numSamples) override; From eb86b4313f972897d5895001a95d86c6a4145cfa Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Wed, 17 Jan 2018 21:16:55 -0500 Subject: [PATCH 06/27] internal memory AudioDelay seems to work, starton on external support --- src/AudioEffectAnalogDelay.cpp | 140 +++++++---------- src/AudioEffectAnalogDelay.h | 15 +- src/LibBasicFunctions.cpp | 139 +++++++++++++---- src/LibBasicFunctions.h | 44 ++++-- src/LibMemoryManagement.cpp | 265 ++++----------------------------- src/LibMemoryManagement.h | 83 +++-------- 6 files changed, 257 insertions(+), 429 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 43dcf37..19c86a2 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -9,38 +9,38 @@ namespace BAGuitar { -AudioEffectAnalogDelay::AudioEffectAnalogDelay(INTERNAL_MEMORY, float maxDelay) +AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelay) : AudioStream(1, m_inputQueueArray) { - m_memory = new MemAudioBlock(maxDelay); + m_memory = new AudioDelay(maxDelay); for (int i=0; iclear(); +// m_memory = &slot; +// for (int i=0; iclear(); } void AudioEffectAnalogDelay::update(void) { - audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samplestransmit(inputAudioBlock, 0); + audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples if (!inputAudioBlock) { return; @@ -66,39 +66,8 @@ void AudioEffectAnalogDelay::update(void) m_callCount++; Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); - - if (m_externalMemory) { - // external mem requires an actual write to memory with the new data block - if (inputAudioBlock) { - // valid block - m_memory->writeAdvance16(inputAudioBlock->data, AUDIO_BLOCK_SAMPLES); - } else { - // write zeros instead - m_memory->zeroAdvance16(AUDIO_BLOCK_SAMPLES); - } - } else { - // internal memory only requires updating the queue of audio block pointers. - MemAudioBlock *memory = reinterpret_cast(m_memory); - - // check to see if the queue has reached full size yet - //Serial.println(String("memory queues:") + memory->getNumQueues() + String(" m_numQueues:") + m_numQueues); - if (memory->getNumQueues() >= m_numQueues) { - release(memory->pop()); - } - - // now push in the newest audio block - //Serial.println(String("push ") + (uint32_t)inputAudioBlock); - memory->push(inputAudioBlock); // MemAudioBlock supports nullptrs, no need to check - //Serial.println("done memory->push()"); - -// audio_block_t *output = memory->getQueueBack(); -// Serial.println("got the output"); -// if (output) { -// transmit(output, 0); -// } -// Serial.println("Done transmit"); - } -// return; + audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock); + if (blockToRelease) release(blockToRelease); //Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); @@ -111,13 +80,12 @@ void AudioEffectAnalogDelay::update(void) if (!m_externalMemory) { // internal memory - MemAudioBlock *memory = reinterpret_cast(m_memory); QueuePosition queuePosition = calcQueuePosition(m_channelOffsets[channel]); Serial.println(String("Position info: ") + queuePosition.index + " : " + queuePosition.offset); if (queuePosition.offset == 0) { // only one queue needs to be read out //Serial.println(String("Directly sending queue offset ") + queuePosition.index); - blockToOutput= memory->getQueueBack(queuePosition.index); // could be nullptr! + blockToOutput= m_memory->getBlock(queuePosition.index); // could be nullptr! if (blockToOutput) transmit(blockToOutput); continue; } else { @@ -130,7 +98,8 @@ void AudioEffectAnalogDelay::update(void) // copy the output data if (!blockToOutput) continue; // skip this channel due to failure // copy over data - m_memory->read16(blockToOutput->data, 0, m_channelOffsets[channel], AUDIO_BLOCK_SAMPLES); + m_memory->getSamples(blockToOutput, m_channelOffsets[channel]); + //m_memory->read16(blockToOutput->data, 0, m_channelOffsets[channel], AUDIO_BLOCK_SAMPLES); transmit(blockToOutput); release(blockToOutput); } @@ -143,25 +112,26 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) size_t delaySamples = calcAudioSamples(milliseconds); - if (!m_externalMemory) { - // Internal memory (AudioStream buffers) - MemAudioBlock *memory = reinterpret_cast(m_memory); - - QueuePosition queuePosition = calcQueuePosition(milliseconds); - Serial.println(String("CONFIG(") + memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); - // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this - // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. - size_t numQueues = queuePosition.index+2; - if (numQueues > m_numQueues) - m_numQueues = numQueues; - } else { - // External memory - if (delaySamples > m_memory->getSize()) { - // error, the external memory is not large enough - return false; - } - } +// if (!m_externalMemory) { +// // Internal memory (AudioStream buffers) +// +// QueuePosition queuePosition = calcQueuePosition(milliseconds); +// Serial.println(String("CONFIG(") + m_memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); +// // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this +// // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. +// size_t numQueues = queuePosition.index+2; +// if (numQueues > m_numQueues) +// m_numQueues = numQueues; +// } else { +//// // External memory +//// if (delaySamples > m_memory->getSize()) { +//// // error, the external memory is not large enough +//// return false; +//// } +// } + QueuePosition queuePosition = calcQueuePosition(milliseconds); + Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); m_channelOffsets[channel] = delaySamples; m_activeChannels |= 1< MAX_DELAY_CHANNELS-1) // channel id too high return false; - if (!m_externalMemory) { - // Internal memory (AudioStream buffers) - MemAudioBlock *memory = reinterpret_cast(m_memory); - - //QueuePosition queuePosition = calcQueuePosition(milliseconds); - QueuePosition queuePosition = calcQueuePosition(delaySamples); - Serial.println(String("CONFIG(") + memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); - // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this - // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. - size_t numQueues = queuePosition.index+2; - if (numQueues > m_numQueues) - m_numQueues = numQueues; - } else { - // External memory - if (delaySamples > m_memory->getSize()) { - // error, the external memory is not large enough - return false; - } - } +// if (!m_externalMemory) { +// // Internal memory (AudioStream buffers) +// MemAudioBlock *memory = reinterpret_cast(m_memory); +// +// //QueuePosition queuePosition = calcQueuePosition(milliseconds); +// QueuePosition queuePosition = calcQueuePosition(delaySamples); +// Serial.println(String("CONFIG(") + memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); +// // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this +// // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. +// size_t numQueues = queuePosition.index+2; +// if (numQueues > m_numQueues) +// m_numQueues = numQueues; +// } else { +//// // External memory +//// if (delaySamples > m_memory->getSize()) { +//// // error, the external memory is not large enough +//// return false; +//// } +// } + QueuePosition queuePosition = calcQueuePosition(delaySamples); + Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); m_channelOffsets[channel] = delaySamples; m_activeChannels |= 1< +//#include #include -#include "LibMemoryManagement.h" +#include "LibBasicFunctions.h" namespace BAGuitar { @@ -22,9 +22,10 @@ public: static constexpr int MAX_DELAY_CHANNELS = 8; AudioEffectAnalogDelay() = delete; - AudioEffectAnalogDelay(INTERNAL_MEMORY, float maxDelay); - AudioEffectAnalogDelay(INTERNAL_MEMORY, size_t numSamples); - AudioEffectAnalogDelay(EXTERNAL_MEMORY, MemSlot &slot); // requires sufficiently sized pre-allocated memory + AudioEffectAnalogDelay(float maxDelay); + AudioEffectAnalogDelay(size_t numSamples); + + AudioEffectAnalogDelay(ExtMemSlot &slot); // requires sufficiently sized pre-allocated memory virtual ~AudioEffectAnalogDelay() {} virtual void update(void); @@ -36,9 +37,9 @@ private: audio_block_t *m_inputQueueArray[1]; unsigned m_activeChannels = 0; bool m_externalMemory = false; - MemBufferIF *m_memory = nullptr; + AudioDelay *m_memory = nullptr; - size_t m_numQueues = 0; + //size_t m_numQueues = 0; size_t m_channelOffsets[MAX_DELAY_CHANNELS]; size_t m_callCount = 0; diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 43bc0c2..7505f36 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -5,57 +5,140 @@ * Author: slascos */ +#include "Audio.h" #include "LibBasicFunctions.h" namespace BAGuitar { -AudioDelay::AudioDelay(MemType type, size_t maxSamples) +size_t calcAudioSamples(float milliseconds) { - m_type = type; - if (type == MemType::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. - } else { - // TODO EXTERNAL memory + 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); } -AudioDelay::AudioDelay(MemType type, float delayTimeMs) -: AudioDelay(type, calcAudioSamples(delayTimeMs)) +size_t calcOffset(QueuePosition position) { + return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset; +} + +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() +AudioDelay::AudioDelay(float maxDelayTimeMs) +: AudioDelay(calcAudioSamples(maxDelayTimeMs)) { - if (m_ringBuffer) delete m_ringBuffer; + } -bool AudioDelay::addBlock(audio_block_t *block) +AudioDelay::AudioDelay(ExtMemSlot &slot) +: m_slot(slot) { - if (m_type != MemType::INTERNAL) return false; // ERROR + m_type = MemType::MEM_EXTERNAL; +} - // purposefully don't check if block is valid, the ringBuffer can support nullptrs - if ( m_ringBuffer->size() < m_ringBuffer->max_size() ) { - // pop before adding - release(m_ringBuffer->front()); - m_ringBuffer->pop_front(); - } +AudioDelay::~AudioDelay() +{ + if (m_ringBuffer) delete m_ringBuffer; +} - // add the new buffer - m_ringBuffer->push_back(block); +audio_block_t* AudioDelay::addBlock(audio_block_t *block) +{ + if (m_type == MemType::MEM_INTERNAL) { + // INTERNAL memory + audio_block_t *blockToRelease = nullptr; + // 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 + m_slot.writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); + return nullptr; + + } } -void AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES) +audio_block_t* AudioDelay::getBlock(size_t index) { - QueuePosition pos = calcQueuePosition(offset); - size_t readOffset = pos.offset; - size_t index = pos.index; + if (m_type == MemType::MEM_INTERNAL) { + return m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); + } else { + return nullptr; + } +} - // Audio is stored in reverse order. That means the first sample (in time) goes in the last location in the audio block. +bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSamples) +{ + if (!dest) return false; + + if (m_type == MemType::MEM_INTERNAL) { + QueuePosition position = calcQueuePosition(offset); + size_t index = position.index; + + if (position.offset == 0) { + // single transfer + audio_block_t *currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); + memcpy(static_cast(dest->data), static_cast(currentQueue->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; + audio_block_t *currentQueue; + 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 = (currentQueue->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 = (currentQueue->data); + numData = AUDIO_BLOCK_SAMPLES - numData; + memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); + + return true; + + } else { + // EXTERNAL Memory + if (numSamples < m_slot.size() ) { + m_slot.read16FromCurrent(dest->data, offset, numSamples); + return true; + } else { + // numSampmles is > than total slot size + return false; + } + } } diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 3031166..cee3b26 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -9,7 +9,8 @@ #include #include "Arduino.h" -#include "Audio.H" +#include "Audio.h" + #include "LibMemoryManagement.h" #ifndef SRC_LIBBASICFUNCTIONS_H_ @@ -17,29 +18,46 @@ namespace BAGuitar { +struct QueuePosition { + int offset; + int index; +}; +QueuePosition calcQueuePosition(float milliseconds); +QueuePosition calcQueuePosition(size_t numSamples); +size_t calcAudioSamples(float milliseconds); +size_t calcOffset(QueuePosition position); + +template class RingBuffer; // forward declare -enum MemType : unsigned { - INTERNAL, - EXTERNAL +enum class MemType : unsigned { + MEM_INTERNAL = 0, + MEM_EXTERNAL }; struct INTERNAL_MEMORY {}; struct EXTERNAL_MEMORY {}; class AudioDelay { - +public: AudioDelay() = delete; - AudioDelay(MemType type, size_t maxSamples); - AudioDelay(MemType type, float delayTimeMs); + AudioDelay(size_t maxSamples); + AudioDelay(float maxDelayTimeMs); + AudioDelay(ExtMemSlot &slot); ~AudioDelay(); - void addBlock(audio_block_t *block); + // Internal memory member functions + audio_block_t *addBlock(audio_block_t *blockIn); + audio_block_t *getBlock(size_t index); + bool getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES); + + // External memory member functions + //bool writeBlock(audio_blocK_t *blockIn); - void getSamples(size_t offset, size_t numSamples); private: MemType m_type; - RingBuffer *m_ringBuffer = nullptr; + RingBuffer *m_ringBuffer = nullptr; + ExtMemSlot &m_slot; }; template @@ -101,7 +119,7 @@ public: return m_buffer[m_head-1]; } - size_t getBackIndex(size_t offset = 0) const { + size_t get_index_from_back(size_t offset = 0) const { // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; size_t idx = (m_maxSize + m_head -1 - offset); @@ -129,6 +147,10 @@ public: return m_buffer[index]; } + T at(size_t index) const { + return m_buffer[index]; + } + void print() const { for (int idx=0; idxdata); diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 137c507..9a25772 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -9,242 +9,23 @@ namespace BAGuitar { bool ExternalSramManager::m_configured = false; MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS]; -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; -} - -///////////////////////////////////////////////////////////////////////////// -// MEM BUFFER IF -///////////////////////////////////////////////////////////////////////////// - - -///////////////////////////////////////////////////////////////////////////// -// MEM VIRTUAL -///////////////////////////////////////////////////////////////////////////// -MemAudioBlock::MemAudioBlock(size_t numSamples) -: m_queues(((numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES) +1) -{ -// // round up to an integer multiple of AUDIO_BLOCK_SAMPLES -// int numQueues = (numSamples + AUDIO_BLOCK_SAMPLES - 1)/AUDIO_BLOCK_SAMPLES; -// -// // Preload the queue with nullptrs to set the queue depth to the correct size -// for (int i=0; i < numQueues; i++) { -// m_queues.push_back(nullptr); -// } -} - -MemAudioBlock::MemAudioBlock(float milliseconds) -: MemAudioBlock(calcAudioSamples(milliseconds)) -{ -} - -MemAudioBlock::~MemAudioBlock() -{ - -} - -// the index is referenced from the head -audio_block_t *MemAudioBlock::getQueueBack(size_t offset) -{ - -// for (int i=0; idata); -// } -// Serial.println(String("Returning ") + (uint32_t)m_queues[m_queues.getBackIndex(offset)]); - return m_queues[m_queues.getBackIndex(offset)]; -} - -bool MemAudioBlock::push(audio_block_t *block) -{ - //Serial.println("MemAudioBlock::push()"); - m_queues.push_back(block); - //Serial.println("MemAudioBlock::push() done"); - return true; -} - -audio_block_t* MemAudioBlock::pop() -{ - //Serial.println("MemAudioBlock::pop()"); - audio_block_t* block = m_queues.front(); - m_queues.pop_front(); - return block; -} - -bool MemAudioBlock::clear() -{ - for (size_t i=0; i < m_queues.size(); i++) { - if (m_queues[i]) { - memset(m_queues[i]->data, 0, AUDIO_BLOCK_SAMPLES * sizeof(int16_t)); - } - } - return true; -} - -bool MemAudioBlock::write16(size_t offset, int16_t *srcDataPtr, size_t numData) -{ - // Calculate the queue position - auto position = calcQueuePosition(offset); - int writeOffset = position.offset; - size_t index = position.index; - - if ( (index+1) > m_queues.size()) return false; // out of range - - // loop over a series of memcpys until all data is transferred. - size_t samplesRemaining = numData; - - int16_t *srcStart = srcDataPtr; // this will increment during each loop iteration - - while (samplesRemaining > 0) { - size_t numSamplesToWrite; - - void *destStart = static_cast(m_queues[index]->data + writeOffset); - - // determine if the transfer will complete or will hit the end of a block first - if ( (writeOffset + samplesRemaining) > AUDIO_BLOCK_SAMPLES ) { - // goes past end of the queue - numSamplesToWrite = (AUDIO_BLOCK_SAMPLES - writeOffset); - //writeOffset = 0; - //index++; - } else { - // transfer ends in this audio block - numSamplesToWrite = samplesRemaining; - //writeOffset += numSamplesToWrite; - } - - // perform the transfer - if (!m_queues[index]) { - // no allocated audio block, skip the copy - } else { - if (srcDataPtr) { - memcpy(destStart, static_cast(srcStart), numSamplesToWrite * sizeof(int16_t)); - } else { - memset(destStart, 0, numSamplesToWrite * sizeof(int16_t)); - } - } - writeOffset = 0; - index++; - srcStart += numSamplesToWrite; - samplesRemaining -= numSamplesToWrite; - } - m_currentPosition.offset = writeOffset; - m_currentPosition.index = index; - return true; -} - -inline bool MemAudioBlock::zero16(size_t offset, size_t numData) -{ - return write16(offset, nullptr, numData); -} - -bool MemAudioBlock::read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numSamples) -{ - if (!dest) return false; // destination is not valid - (void)destOffset; // not supported with audio_block_t; - //Serial.println("*************************************************************************"); - //Serial.println(String("read16():") + (uint32_t)dest + String(":") + destOffset + String(":") + srcOffset + String(":") + numSamples); - - // Calculate the queue position - auto position = calcQueuePosition(srcOffset); - size_t index = position.index; - - // Break the transfer in two. Note that the audio is stored first sample (in time) last (in memory). - - int16_t *destStart = dest; - audio_block_t *currentQueue; - int16_t *srcStart; - - // Break the transfer into two. Note that the audio - //Serial.println("Calling getQueue"); - currentQueue = getQueueBack(index+1); // buffer indexes go backwards from the back - //Serial.println(String("Q. Address: ") + (uint32_t)currentQueue + String(" Data: ") + (uint32_t)currentQueue->data); - srcStart = (currentQueue->data + AUDIO_BLOCK_SAMPLES - position.offset); - size_t numData = position.offset; - //Serial.println(String("Source Start1: ") + (uint32_t)currentQueue->data + String(" Dest start1: ") + (uint32_t)dest); - //Serial.println(String("copying to ") + (uint32_t)destStart + String(" from ") + (uint32_t)srcStart + String(" numData= ") + numData); - memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); - - - currentQueue = getQueueBack(index); // buffer indexes go backwards from the back - //Serial.println(String("Q. Address: ") + (uint32_t)currentQueue + String(" Data: ") + (uint32_t)currentQueue->data); - destStart += numData; - srcStart = (currentQueue->data); - numData = AUDIO_BLOCK_SAMPLES - numData; - //Serial.println(String("Source Start2: ") + (uint32_t)currentQueue->data + String(" Dest start2: ") + (uint32_t)dest); - //Serial.println(String("copying to ") + (uint32_t)destStart + String(" from ") + (uint32_t)srcStart + String(" numData= ") + numData); - memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); - - //m_queues.print(); - //Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - - return true; -} - -// If this function hits the end of the queues it will wrap to the start -bool MemAudioBlock::writeAdvance16(int16_t *dataPtr, size_t numData) -{ - auto globalOffset = calcOffset(m_currentPosition); - auto end = globalOffset + numData; - - if ( end >= (m_queues.size() * AUDIO_BLOCK_SAMPLES) ) { - // transfer will wrap, so break into two - auto samplesToWrite = end - globalOffset; - // write the first chunk - write16(globalOffset, dataPtr, samplesToWrite); - // write the scond chunk - int16_t *ptr; - if (dataPtr) { - // valid dataptr, advance the pointer - ptr = dataPtr+samplesToWrite; - } else { - // dataPtr was nullptr - ptr = nullptr; - } - write16(0, ptr, numData-samplesToWrite); - } else { - // no wrap - write16(globalOffset, dataPtr, numData); - } - return true; -} - -bool MemAudioBlock::zeroAdvance16(size_t numData) -{ - return writeAdvance16(nullptr, numData); -} ///////////////////////////////////////////////////////////////////////////// // MEM SLOT ///////////////////////////////////////////////////////////////////////////// -bool MemSlot::clear() +bool ExtMemSlot::clear() { if (!m_valid) { return false; } m_spi->zero16(m_start, m_size); return true; } -bool MemSlot::write16(size_t offset, int16_t *dataPtr, size_t dataSize) +bool ExtMemSlot::write16(size_t offset, int16_t *dataPtr, size_t dataSize) { if (!m_valid) { return false; } - if ((offset + dataSize-1) <= m_end) { - m_spi->write16(offset, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint + size_t writeStart = m_start + offset; + if ((writeStart + dataSize-1) <= m_end) { + m_spi->write16(writeStart, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint return true; } else { // this would go past the end of the memory slot, do not perform the write @@ -252,11 +33,12 @@ bool MemSlot::write16(size_t offset, int16_t *dataPtr, size_t dataSize) } } -bool MemSlot::zero16(size_t offset, size_t dataSize) +bool ExtMemSlot::zero16(size_t offset, size_t dataSize) { if (!m_valid) { return false; } - if ((offset + dataSize-1) <= m_end) { - m_spi->zero16(offset, dataSize); // cast audio data to uint + size_t writeStart = m_start + offset; + if ((writeStart + dataSize-1) <= m_end) { + m_spi->zero16(writeStart, dataSize); // cast audio data to uint return true; } else { // this would go past the end of the memory slot, do not perform the write @@ -264,11 +46,13 @@ bool MemSlot::zero16(size_t offset, size_t dataSize) } } -bool MemSlot::read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numData) +bool ExtMemSlot::read16(int16_t *dest, size_t srcOffset, size_t numData) { if (!dest) return false; // invalid destination - if ((srcOffset + (numData*sizeof(int16_t))-1) <= m_end) { - m_spi->read16(srcOffset, reinterpret_cast(dest), numData); + size_t readOffset = m_start + srcOffset; + + if ((readOffset + (numData*sizeof(int16_t))-1) <= m_end) { + m_spi->read16(readOffset, reinterpret_cast(dest), numData); return true; } else { // this would go past the end of the memory slot, do not perform the write @@ -276,7 +60,20 @@ bool MemSlot::read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t } } -bool MemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) +size_t ExtMemSlot::read16FromCurrent(int16_t *dest, size_t currentOffset, size_t numData) +{ + size_t readStart; + if (m_currentPosition + currentOffset <= m_end) { + readStart = m_currentPosition + currentOffset; + } else { + // this offset will wrap the memory slot + size_t numBytesToEnd = m_end - m_currentPosition + 1; + readStart = m_start + (currentOffset - numBytesToEnd); + } + m_spi->read16(dest, readStart, numData); +} + +bool ExtMemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) { if (!m_valid) { return false; } if (m_currentPosition + dataSize-1 <= m_end) { @@ -295,7 +92,7 @@ bool MemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) return true; } -bool MemSlot::zeroAdvance16(size_t dataSize) +bool ExtMemSlot::zeroAdvance16(size_t dataSize) { if (!m_valid) { return false; } if (m_currentPosition + dataSize-1 <= m_end) { @@ -344,14 +141,14 @@ size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem) return m_memConfig[mem].totalAvailable; } -bool ExternalSramManager::requestMemory(MemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem) +bool ExternalSramManager::requestMemory(ExtMemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem) { // 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, mem); } -bool ExternalSramManager::requestMemory(MemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem) +bool ExternalSramManager::requestMemory(ExtMemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem) { if (m_memConfig[mem].totalAvailable >= sizeBytes) { // there is enough available memory for this request diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 319a18f..22821a5 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -11,7 +11,7 @@ #include -#include "Audio.h" +//#include "Audio.h" #include "BAHardware.h" #include "BASpiMemory.h" @@ -19,14 +19,6 @@ namespace BAGuitar { -struct QueuePosition { - int offset; - int index; -}; -QueuePosition calcQueuePosition(float milliseconds); -QueuePosition calcQueuePosition(size_t numSamples); -size_t calcAudioSamples(float milliseconds); - struct MemConfig { size_t size; size_t totalAvailable; @@ -34,66 +26,27 @@ struct MemConfig { BASpiMemory *m_spi = nullptr; }; -class ExternalSramManager; // forward declare so MemSlot can setup friendship +class ExternalSramManager; // forward declare so ExtMemSlot can setup friendship -class MemBufferIF { +class ExtMemSlot { public: - size_t getSize() const { return m_size; } - virtual bool clear() = 0; - virtual bool write16(size_t offset, int16_t *dataPtr, size_t numData) = 0; - virtual bool zero16(size_t offset, size_t numData) = 0; - virtual bool read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numData) = 0; - virtual bool writeAdvance16(int16_t *dataPtr, size_t numData) = 0; - virtual bool zeroAdvance16(size_t numData) = 0; - virtual ~MemBufferIF() {} -protected: - bool m_valid = false; - size_t m_size = 0; -}; -class MemAudioBlock : public MemBufferIF { -public: - //MemAudioBlock(); - MemAudioBlock() = delete; - MemAudioBlock(size_t numSamples); - MemAudioBlock(float milliseconds); - - virtual ~MemAudioBlock(); - - bool push(audio_block_t *block); - audio_block_t *pop(); - audio_block_t *getQueueBack(size_t offset=0); - size_t getNumQueues() const { return m_queues.size(); } - size_t getMaxSize() const { return m_queues.getMaxSize(); } - bool clear() override; - bool write16(size_t offset, int16_t *dataPtr, size_t numData) override; - bool zero16(size_t offset, size_t numSamples) override; - bool read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numSamples); - bool writeAdvance16(int16_t *dataPtr, size_t numData) override; - bool zeroAdvance16(size_t numData) override; -private: - //size_t m_numQueues; - BAGuitar::RingBuffer m_queues; - QueuePosition m_currentPosition = {0,0}; - -}; - - -class MemSlot : public MemBufferIF { -public: - - bool clear() override; - bool write16(size_t offset, int16_t *dataPtr, size_t numData) override; - bool zero16(size_t offset, size_t numData) override; - bool read16(int16_t *dest, size_t destOffset, size_t srcOffset, size_t numData); - bool writeAdvance16(int16_t *dataPtr, size_t numData) override; - bool zeroAdvance16(size_t numData) override; + bool clear(); + bool write16(size_t offset, int16_t *dataPtr, size_t numData); + bool zero16(size_t offset, size_t numData); + bool read16(int16_t *dest, size_t srcOffset, size_t numData); + bool writeAdvance16(int16_t *dataPtr, size_t numData); + bool zeroAdvance16(size_t numData); + size_t read16FromCurrent(int16_t *dest, size_t Offset, size_t numData); + size_t size() const { return m_size; } private: friend ExternalSramManager; - size_t m_start; - size_t m_end; - size_t m_currentPosition; + bool m_valid = false; + size_t m_start = 0; + size_t m_end = 0; + size_t m_currentPosition = 0; + size_t m_size = 0; BASpiMemory *m_spi = nullptr; }; @@ -105,8 +58,8 @@ public: virtual ~ExternalSramManager(); size_t availableMemory(BAGuitar::MemSelect mem); - bool requestMemory(MemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); - bool requestMemory(MemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(ExtMemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(ExtMemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); private: static bool m_configured; From 9fd96aa5e36aba8fd573af79a860072841bc69d3 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Thu, 18 Jan 2018 21:01:39 -0500 Subject: [PATCH 07/27] Development checkin --- src/AudioEffectAnalogDelay.cpp | 94 +++++++++++----------- src/AudioEffectAnalogDelay.h | 5 +- src/BASpiMemory.cpp | 7 +- src/BASpiMemory.h | 9 ++- src/LibBasicFunctions.cpp | 33 ++++++-- src/LibBasicFunctions.h | 6 +- src/LibMemoryManagement.cpp | 140 +++++++++++++++++++++++++-------- src/LibMemoryManagement.h | 22 +++++- 8 files changed, 216 insertions(+), 100 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 19c86a2..97e2bfb 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -28,14 +28,19 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) } // requires preallocated memory large enough -AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot &slot) +AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) : AudioStream(1, m_inputQueueArray) { -// m_memory = &slot; -// for (int i=0; iclear(); + m_memory = new AudioDelay(slot); + m_externalMemory = true; + for (int i=0; igetSlot()->printStatus(); audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock); + +// if (inputAudioBlock) { +// transmit(inputAudioBlock, 0); +// release(inputAudioBlock); +// } +// return; + if (blockToRelease) release(blockToRelease); - //Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); + Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); // For each active channel, output the delayed audio @@ -92,6 +105,7 @@ void AudioEffectAnalogDelay::update(void) blockToOutput = allocate(); // allocate if spanning 2 queues } } else { + // external memory blockToOutput = allocate(); // allocate always for external memory } @@ -112,26 +126,21 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) size_t delaySamples = calcAudioSamples(milliseconds); -// if (!m_externalMemory) { -// // Internal memory (AudioStream buffers) -// -// QueuePosition queuePosition = calcQueuePosition(milliseconds); -// Serial.println(String("CONFIG(") + m_memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); -// // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this -// // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. -// size_t numQueues = queuePosition.index+2; -// if (numQueues > m_numQueues) -// m_numQueues = numQueues; -// } else { -//// // External memory -//// if (delaySamples > m_memory->getSize()) { -//// // error, the external memory is not large enough -//// return false; -//// } -// } + if (!m_externalMemory) { + // internal memory + QueuePosition queuePosition = calcQueuePosition(milliseconds); + Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); + } else { + // external memory + Serial.println(String("CONFIG: delay:") + delaySamples); + ExtMemSlot *slot = m_memory->getSlot(); + if (!slot->isEnabled()) { + slot->enable(); + } else { + Serial.println("ERROR: slot ptr is not valid"); + } + } - QueuePosition queuePosition = calcQueuePosition(milliseconds); - Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); m_channelOffsets[channel] = delaySamples; m_activeChannels |= 1< MAX_DELAY_CHANNELS-1) // channel id too high return false; -// if (!m_externalMemory) { -// // Internal memory (AudioStream buffers) -// MemAudioBlock *memory = reinterpret_cast(m_memory); -// -// //QueuePosition queuePosition = calcQueuePosition(milliseconds); -// QueuePosition queuePosition = calcQueuePosition(delaySamples); -// Serial.println(String("CONFIG(") + memory->getMaxSize() + String("): delay: queue position ") + queuePosition.index + String(":") + queuePosition.offset); -// // we have to take the queue position and add 1 to get the number of queues. But we need to add one more since this -// // is the start of the audio buffer, and the AUDIO_BLOCK_SAMPLES will then flow into the next one, so add 2 overall. -// size_t numQueues = queuePosition.index+2; -// if (numQueues > m_numQueues) -// m_numQueues = numQueues; -// } else { -//// // External memory -//// if (delaySamples > m_memory->getSize()) { -//// // error, the external memory is not large enough -//// return false; -//// } -// } - - QueuePosition queuePosition = calcQueuePosition(delaySamples); - Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); + if (!m_externalMemory) { + // internal memory + QueuePosition queuePosition = calcQueuePosition(delaySamples); + Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); + } else { + // external memory + ExtMemSlot *slot = m_memory->getSlot(); + if (!slot->isEnabled()) { + slot->enable(); + } + } m_channelOffsets[channel] = delaySamples; m_activeChannels |= 1<endTransaction(); digitalWrite(m_csPin, HIGH); + Serial.println("DONE!"); } // single address read @@ -239,10 +240,10 @@ uint16_t BASpiMemory::read16(size_t address) return data; } -void BASpiMemory::read16(size_t address, uint16_t *data, size_t numWords) +void BASpiMemory::read16(size_t address, uint16_t *dest, size_t numWords) { - uint16_t *dataPtr = data; + uint16_t *dataPtr = dest; m_spi->beginTransaction(m_settings); digitalWrite(m_csPin, LOW); m_spi->transfer16((SPI_READ_CMD << 8) | (address >> 16) ); diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index fc5046f..efc2acc 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -71,14 +71,21 @@ public: /// @param address the address in the SPI RAM to read from /// @return the data that was read uint16_t read16(size_t address); - void read16(size_t address, uint16_t *data, size_t numWords); + /// read a block 16-bit data word from the specified address + /// @param address the address in the SPI RAM to read from + /// @param dest the pointer to the destination + /// @param numWords the number of 16-bit words to transfer + void read16(size_t address, uint16_t *dest, size_t numWords); + + bool isStarted() const { return m_started; } private: SPIClass *m_spi = nullptr; SpiDeviceId m_memDeviceId; // the MEM device being control with this instance uint8_t m_csPin; // the IO pin number for the CS on the controlled SPI device SPISettings m_settings; // the Wire settings for this SPI port + bool m_started = false; }; diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 7505f36..e78049a 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -49,7 +49,7 @@ AudioDelay::AudioDelay(float maxDelayTimeMs) } -AudioDelay::AudioDelay(ExtMemSlot &slot) +AudioDelay::AudioDelay(ExtMemSlot *slot) : m_slot(slot) { m_type = MemType::MEM_EXTERNAL; @@ -77,8 +77,17 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block) return blockToRelease; } else { // EXTERNAL memory - m_slot.writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); - return nullptr; + + //m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); + + // 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--; + } + return block; } @@ -131,8 +140,22 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } else { // EXTERNAL Memory - if (numSamples < m_slot.size() ) { - m_slot.read16FromCurrent(dest->data, offset, numSamples); + if (numSamples <= m_slot->size() ) { + int currentPosition = (int)m_slot->getWritePosition() - (int)AUDIO_BLOCK_SAMPLES; + + if ((int)offset <= currentPosition) { + m_slot->setReadPosition(currentPosition - offset); + } else { + // It's going to wrap around to the end of the slot + int readPosition = (int)m_slot->size() + currentPosition - offset; + m_slot->setReadPosition((size_t)readPosition); + } + + // write the data to the destination block in reverse + int16_t *destPtr = dest->data + AUDIO_BLOCK_SAMPLES-1; + for (int i=0; ireadAdvance16(); + } return true; } else { // numSampmles is > than total slot size diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index cee3b26..139625e 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -43,7 +43,7 @@ public: AudioDelay() = delete; AudioDelay(size_t maxSamples); AudioDelay(float maxDelayTimeMs); - AudioDelay(ExtMemSlot &slot); + AudioDelay(ExtMemSlot *slot); ~AudioDelay(); // Internal memory member functions @@ -52,12 +52,12 @@ public: bool getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES); // External memory member functions - //bool writeBlock(audio_blocK_t *blockIn); + ExtMemSlot *getSlot() const { return m_slot; } private: MemType m_type; RingBuffer *m_ringBuffer = nullptr; - ExtMemSlot &m_slot; + ExtMemSlot *m_slot = nullptr; }; template diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 9a25772..9f413b1 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -2,13 +2,12 @@ #include #include +#include "Audio.h" + #include "LibMemoryManagement.h" namespace BAGuitar { -bool ExternalSramManager::m_configured = false; -MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS]; - ///////////////////////////////////////////////////////////////////////////// // MEM SLOT @@ -60,34 +59,59 @@ bool ExtMemSlot::read16(int16_t *dest, size_t srcOffset, size_t numData) } } -size_t ExtMemSlot::read16FromCurrent(int16_t *dest, size_t currentOffset, size_t numData) +uint16_t ExtMemSlot::readAdvance16() { - size_t readStart; - if (m_currentPosition + currentOffset <= m_end) { - readStart = m_currentPosition + currentOffset; + uint16_t val = m_spi->read16(m_currentRdPosition); + if (m_currentRdPosition < m_end) { + m_currentRdPosition++; } else { - // this offset will wrap the memory slot - size_t numBytesToEnd = m_end - m_currentPosition + 1; - readStart = m_start + (currentOffset - numBytesToEnd); + m_currentRdPosition = m_start; } - m_spi->read16(dest, readStart, numData); + return val; } + +//void ExtMemSlot::read16FromPast(int16_t *dest, size_t currentOffset, size_t numData) +//{ +// size_t readStart; +// if (m_currentPosition - currentOffset >= m_start) { +// readStart = m_currentPosition - currentOffset; +// } else { +// // this offset will wrap the memory slot +// size_t numBytesToStart = m_currentPosition; +// readStart = m_end - (currentOffset - numBytesToStart); +// } +// m_spi->read16(readStart, reinterpret_cast(dest), numData); +//} + bool ExtMemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) { if (!m_valid) { return false; } - if (m_currentPosition + dataSize-1 <= m_end) { + if (m_currentWrPosition + dataSize-1 <= m_end) { // entire block fits in memory slot without wrapping - m_spi->write16(m_currentPosition, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint. - m_currentPosition += dataSize; + m_spi->write16(m_currentWrPosition, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint. + m_currentWrPosition += dataSize; } else { // this write will wrap the memory slot - size_t numBytes = m_end - m_currentPosition + 1; - m_spi->write16(m_currentPosition, reinterpret_cast(dataPtr), numBytes); + size_t numBytes = m_end - m_currentWrPosition + 1; + m_spi->write16(m_currentWrPosition, reinterpret_cast(dataPtr), numBytes); size_t remainingBytes = dataSize - numBytes; // calculate the remaining bytes m_spi->write16(m_start, reinterpret_cast(dataPtr + numBytes), remainingBytes); // write remaining bytes are start - m_currentPosition = m_start + remainingBytes; + m_currentWrPosition = m_start + remainingBytes; + } + return true; +} + +bool ExtMemSlot::writeAdvance16(int16_t data) +{ + if (!m_valid) { return false; } + + m_spi->write16(m_currentWrPosition, static_cast(data)); + if (m_currentWrPosition < m_end) { + m_currentWrPosition++; + } else { + m_currentWrPosition = m_start; } return true; } @@ -95,26 +119,55 @@ bool ExtMemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) bool ExtMemSlot::zeroAdvance16(size_t dataSize) { if (!m_valid) { return false; } - if (m_currentPosition + dataSize-1 <= m_end) { + if (m_currentWrPosition + dataSize-1 <= m_end) { // entire block fits in memory slot without wrapping - m_spi->zero16(m_currentPosition, dataSize); // cast audio data to uint. - m_currentPosition += dataSize; + m_spi->zero16(m_currentWrPosition, dataSize); // cast audio data to uint. + m_currentWrPosition += dataSize; } else { // this write will wrap the memory slot - size_t numBytes = m_end - m_currentPosition + 1; - m_spi->zero16(m_currentPosition, numBytes); + size_t numBytes = m_end - m_currentWrPosition + 1; + m_spi->zero16(m_currentWrPosition, numBytes); size_t remainingBytes = dataSize - numBytes; // calculate the remaining bytes m_spi->zero16(m_start, remainingBytes); // write remaining bytes are start - m_currentPosition = m_start + remainingBytes; + m_currentWrPosition = m_start + remainingBytes; } return true; } +bool ExtMemSlot::enable() const +{ + if (m_spi) { + Serial.println("ExtMemSlot::enable()"); + m_spi->begin(); + return true; + } + else { + Serial.println("ExtMemSlot m_spi is nullptr"); + return false; + } +} + +bool ExtMemSlot::isEnabled() const +{ + if (m_spi) { return m_spi->isStarted(); } + else return false; +} + +void ExtMemSlot::printStatus(void) const +{ + Serial.println(String("valid:") + m_valid + String(" m_start:") + m_start + \ + String(" m_end:") + m_end + String(" m_currentWrPosition: ") + m_currentWrPosition + \ + 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 @@ -123,7 +176,13 @@ ExternalSramManager::ExternalSramManager(unsigned numMemories) 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 = new BAGuitar::BASpiMemory(static_cast(i)); + + m_memConfig[i].m_spi = nullptr; +// if (i < numMemories) { +// m_memConfig[i].m_spi = new BAGuitar::BASpiMemory(static_cast(i)); +// } else { +// m_memConfig[i].m_spi = nullptr; +// } } m_configured = true; } @@ -141,30 +200,45 @@ size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem) return m_memConfig[mem].totalAvailable; } -bool ExternalSramManager::requestMemory(ExtMemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem) { // 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, mem); } -bool ExternalSramManager::requestMemory(ExtMemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem) { + 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_currentPosition = slot.m_start; // init to start of slot - slot.m_size = sizeBytes; - slot.m_spi = m_memConfig[mem].m_spi; + 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) { + m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast(mem)); + if (!m_memConfig[mem].m_spi) { + Serial.println("requestMemory: new failed! m_spi is a nullptr"); + } else { + 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].nextAvailable = slot->m_end+1; m_memConfig[mem].totalAvailable -= sizeBytes; - slot.m_valid = true; + slot->m_valid = true; + if (!slot->isEnabled()) { slot->enable(); } + slot->clear(); return true; } else { // there is not enough memory available for the request + return false; } } diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 22821a5..9fedbc8 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -32,21 +32,35 @@ class ExtMemSlot { public: bool clear(); + bool setWritePosition(size_t offset) { m_currentWrPosition = m_start + offset; return true;} // TODO add range check + size_t getWritePosition() const { return m_currentWrPosition-m_start; } + + bool setReadPosition(size_t offset) { m_currentRdPosition = m_start + offset; return true;} // TODO add range check + size_t getReadPosition() const { return m_currentRdPosition-m_start; } + bool write16(size_t offset, int16_t *dataPtr, size_t numData); bool zero16(size_t offset, size_t numData); bool read16(int16_t *dest, size_t srcOffset, size_t numData); + + uint16_t readAdvance16(); bool writeAdvance16(int16_t *dataPtr, size_t numData); + bool writeAdvance16(int16_t data); // write just one data bool zeroAdvance16(size_t numData); - size_t read16FromCurrent(int16_t *dest, size_t Offset, size_t numData); + //void read16FromPast(int16_t *dest, size_t Offset, size_t numData); size_t size() const { return m_size; } + bool enable() const; + bool isEnabled() const; + void printStatus(void) const; private: friend ExternalSramManager; bool m_valid = false; size_t m_start = 0; size_t m_end = 0; - size_t m_currentPosition = 0; + size_t m_currentWrPosition = 0; + size_t m_currentRdPosition = 0; size_t m_size = 0; + SpiDeviceId m_spiId; BASpiMemory *m_spi = nullptr; }; @@ -58,8 +72,8 @@ public: virtual ~ExternalSramManager(); size_t availableMemory(BAGuitar::MemSelect mem); - bool requestMemory(ExtMemSlot &slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); - bool requestMemory(ExtMemSlot &slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); private: static bool m_configured; From 7cf4b57bc614683cf1a2ef9fa8d0b6647c97312e Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Fri, 19 Jan 2018 12:19:58 -0500 Subject: [PATCH 08/27] cleanup up LibMemoryManagement.* --- src/AudioEffectAnalogDelay.cpp | 14 ++- src/BAAudioControlWM8731.h | 6 +- src/BASpiMemory.h | 2 +- src/LibBasicFunctions.cpp | 49 ++++++--- src/LibMemoryManagement.cpp | 151 +++++++++++++++----------- src/LibMemoryManagement.h | 189 ++++++++++++++++++++++++++------- 6 files changed, 288 insertions(+), 123 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 97e2bfb..e08ecf7 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -71,7 +71,7 @@ void AudioEffectAnalogDelay::update(void) m_callCount++; Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); - m_memory->getSlot()->printStatus(); + //m_memory->getSlot()->printStatus(); audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock); // if (inputAudioBlock) { @@ -113,7 +113,7 @@ void AudioEffectAnalogDelay::update(void) if (!blockToOutput) continue; // skip this channel due to failure // copy over data m_memory->getSamples(blockToOutput, m_channelOffsets[channel]); - //m_memory->read16(blockToOutput->data, 0, m_channelOffsets[channel], AUDIO_BLOCK_SAMPLES); + transmit(blockToOutput); release(blockToOutput); } @@ -126,6 +126,8 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) size_t delaySamples = calcAudioSamples(milliseconds); + if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } + if (!m_externalMemory) { // internal memory QueuePosition queuePosition = calcQueuePosition(milliseconds); @@ -134,10 +136,11 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) // external memory Serial.println(String("CONFIG: delay:") + delaySamples); ExtMemSlot *slot = m_memory->getSlot(); + + if (!slot) { Serial.println("ERROR: slot ptr is not valid"); } if (!slot->isEnabled()) { slot->enable(); - } else { - Serial.println("ERROR: slot ptr is not valid"); + Serial.println("WEIRD: slot was not enabled"); } } @@ -151,12 +154,15 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, size_t delaySamples) if (channel > MAX_DELAY_CHANNELS-1) // channel id too high return false; + if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } + if (!m_externalMemory) { // internal memory QueuePosition queuePosition = calcQueuePosition(delaySamples); Serial.println(String("CONFIG: delay:") + delaySamples + String(" queue position ") + queuePosition.index + String(":") + queuePosition.offset); } else { // external memory + Serial.println(String("CONFIG: delay:") + delaySamples); ExtMemSlot *slot = m_memory->getSlot(); if (!slot->isEnabled()) { slot->enable(); diff --git a/src/BAAudioControlWM8731.h b/src/BAAudioControlWM8731.h index af00261..7d930c5 100644 --- a/src/BAAudioControlWM8731.h +++ b/src/BAAudioControlWM8731.h @@ -22,8 +22,8 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __INC_BAAUDIOCONTROLWM8731_H -#define __INC_BAAUDIOCONTROLWM8731_H +#ifndef __BAAUDIOCONTROLWM8731_H +#define __BAAUDIOCONTROLWM8731_H namespace BAGuitar { @@ -128,4 +128,4 @@ private: } /* namespace BAGuitar */ -#endif /* __INC_BAAUDIOCONTROLWM8731_H */ +#endif /* __BAAUDIOCONTROLWM8731_H */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index efc2acc..2f2a765 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -59,7 +59,7 @@ public: void write16(size_t address, uint16_t data); void write16(size_t address, uint16_t *data, size_t numWords); - void zero16(size_t address, size_t numBytes); + void zero16(size_t address, size_t numWords); /// read a single 8-bit data word from the specified address /// @param address the address in the SPI RAM to read from diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index e78049a..1272703 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -50,9 +50,10 @@ AudioDelay::AudioDelay(float maxDelayTimeMs) } AudioDelay::AudioDelay(ExtMemSlot *slot) -: m_slot(slot) +//: m_slot(slot) { m_type = MemType::MEM_EXTERNAL; + m_slot = slot; } AudioDelay::~AudioDelay() @@ -77,15 +78,24 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block) return blockToRelease; } else { // EXTERNAL memory + if (!m_slot) { Serial.println("addBlock(): m_slot is not valid"); } - //m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); + 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--; +// } + + int16_t *srcPtr = block->data; + for (int i=0; iwriteAdvance16(*srcPtr); + srcPtr++; + } - // 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--; } return block; @@ -140,25 +150,36 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } else { // EXTERNAL Memory - if (numSamples <= m_slot->size() ) { - int currentPosition = (int)m_slot->getWritePosition() - (int)AUDIO_BLOCK_SAMPLES; + if (numSamples*sizeof(int16_t) <= m_slot->size() ) { + int currentPositionBytes = (int)m_slot->getWritePosition() - (int)(AUDIO_BLOCK_SAMPLES*sizeof(int16_t)); + size_t offsetBytes = offset * sizeof(int16_t); - if ((int)offset <= currentPosition) { - m_slot->setReadPosition(currentPosition - offset); + 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() + currentPosition - offset; + 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; +// int16_t *destPtr = dest->data + AUDIO_BLOCK_SAMPLES-1; +// for (int i=0; ireadAdvance16(); +// destPtr--; +// } + + 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; } } diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 9f413b1..9927121 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -1,9 +1,26 @@ - +/* + * 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 { @@ -19,12 +36,21 @@ bool ExtMemSlot::clear() return true; } -bool ExtMemSlot::write16(size_t offset, int16_t *dataPtr, size_t dataSize) +bool ExtMemSlot::setWritePosition(size_t offsetBytes) +{ + if (m_start + offsetBytes <= m_end) { + m_currentWrPosition = m_start + offsetBytes; + return true; + } else { return false; } +} + +bool ExtMemSlot::write16(size_t offsetBytes, int16_t *dest, size_t numWords) { if (!m_valid) { return false; } - size_t writeStart = m_start + offset; - if ((writeStart + dataSize-1) <= m_end) { - m_spi->write16(writeStart, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint + size_t writeStart = m_start + offsetBytes; // 2x because int16 is two bytes per data + size_t numBytes = sizeof(int16_t)*numWords; + if ((writeStart + numBytes-1) <= m_end) { + m_spi->write16(writeStart, reinterpret_cast(dest), numWords); // cast audio data to uint return true; } else { // this would go past the end of the memory slot, do not perform the write @@ -32,12 +58,23 @@ bool ExtMemSlot::write16(size_t offset, int16_t *dataPtr, size_t dataSize) } } -bool ExtMemSlot::zero16(size_t offset, size_t dataSize) +bool ExtMemSlot::setReadPosition(size_t offsetBytes) +{ + if (m_start + offsetBytes <= m_end) { + m_currentRdPosition = m_start + offsetBytes; + return true; + } else { + return false; + } +} + +bool ExtMemSlot::zero16(size_t offsetBytes, size_t numWords) { if (!m_valid) { return false; } - size_t writeStart = m_start + offset; - if ((writeStart + dataSize-1) <= m_end) { - m_spi->zero16(writeStart, dataSize); // cast audio data to uint + size_t writeStart = m_start + offsetBytes; + size_t numBytes = sizeof(int16_t)*numWords; + if ((writeStart + numBytes-1) <= m_end) { + m_spi->zero16(writeStart, numWords); // cast audio data to uint return true; } else { // this would go past the end of the memory slot, do not perform the write @@ -45,16 +82,17 @@ bool ExtMemSlot::zero16(size_t offset, size_t dataSize) } } -bool ExtMemSlot::read16(int16_t *dest, size_t srcOffset, size_t numData) +bool ExtMemSlot::read16(int16_t *dest, size_t offsetBytes, size_t numWords) { if (!dest) return false; // invalid destination - size_t readOffset = m_start + srcOffset; + size_t readOffset = m_start + offsetBytes; + size_t numBytes = sizeof(int16_t)*numWords; - if ((readOffset + (numData*sizeof(int16_t))-1) <= m_end) { - m_spi->read16(readOffset, reinterpret_cast(dest), numData); + if ((readOffset + numBytes-1) <= m_end) { + m_spi->read16(readOffset, reinterpret_cast(dest), numWords); return true; } else { - // this would go past the end of the memory slot, do not perform the write + // this would go past the end of the memory slot, do not perform the read return false; } } @@ -62,8 +100,8 @@ bool ExtMemSlot::read16(int16_t *dest, size_t srcOffset, size_t numData) uint16_t ExtMemSlot::readAdvance16() { uint16_t val = m_spi->read16(m_currentRdPosition); - if (m_currentRdPosition < m_end) { - m_currentRdPosition++; + if (m_currentRdPosition < m_end-1) { + m_currentRdPosition +=2; // position is in bytes and we read two } else { m_currentRdPosition = m_start; } @@ -71,70 +109,65 @@ uint16_t ExtMemSlot::readAdvance16() } -//void ExtMemSlot::read16FromPast(int16_t *dest, size_t currentOffset, size_t numData) -//{ -// size_t readStart; -// if (m_currentPosition - currentOffset >= m_start) { -// readStart = m_currentPosition - currentOffset; -// } else { -// // this offset will wrap the memory slot -// size_t numBytesToStart = m_currentPosition; -// readStart = m_end - (currentOffset - numBytesToStart); -// } -// m_spi->read16(readStart, reinterpret_cast(dest), numData); -//} - -bool ExtMemSlot::writeAdvance16(int16_t *dataPtr, size_t dataSize) +bool ExtMemSlot::writeAdvance16(int16_t *src, size_t numWords) { if (!m_valid) { return false; } - if (m_currentWrPosition + dataSize-1 <= m_end) { + size_t numBytes = sizeof(int16_t)*numWords; + + if (m_currentWrPosition + numBytes-1 <= m_end) { // entire block fits in memory slot without wrapping - m_spi->write16(m_currentWrPosition, reinterpret_cast(dataPtr), dataSize); // cast audio data to uint. - m_currentWrPosition += dataSize; + m_spi->write16(m_currentWrPosition, reinterpret_cast(src), numWords); // cast audio data to uint. + m_currentWrPosition += numBytes; } else { // this write will wrap the memory slot - size_t numBytes = m_end - m_currentWrPosition + 1; - m_spi->write16(m_currentWrPosition, reinterpret_cast(dataPtr), numBytes); - size_t remainingBytes = dataSize - numBytes; // calculate the remaining bytes - m_spi->write16(m_start, reinterpret_cast(dataPtr + numBytes), remainingBytes); // write remaining bytes are start - m_currentWrPosition = m_start + remainingBytes; + size_t wrBytes = m_end - m_currentWrPosition + 1; + size_t wrDataNum = wrBytes >> 1; // divide by two to get the number of data + m_spi->write16(m_currentWrPosition, reinterpret_cast(src), wrDataNum); + size_t remainingData = numWords - wrDataNum; + m_spi->write16(m_start, reinterpret_cast(src + wrDataNum), remainingData); // write remaining bytes are start + m_currentWrPosition = m_start + (remainingData*sizeof(int16_t)); } return true; } -bool ExtMemSlot::writeAdvance16(int16_t data) + +bool ExtMemSlot::zeroAdvance16(size_t numWords) { if (!m_valid) { return false; } + size_t numBytes = 2*numWords; + if (m_currentWrPosition + numBytes-1 <= m_end) { + // entire block fits in memory slot without wrapping + m_spi->zero16(m_currentWrPosition, numWords); // cast audio data to uint. + m_currentWrPosition += numBytes; - m_spi->write16(m_currentWrPosition, static_cast(data)); - if (m_currentWrPosition < m_end) { - m_currentWrPosition++; } else { - m_currentWrPosition = m_start; + // this write will wrap the memory slot + size_t wrBytes = m_end - m_currentWrPosition + 1; + size_t wrDataNum = wrBytes >> 1; + m_spi->zero16(m_currentWrPosition, wrDataNum); + size_t remainingWords = numWords - wrDataNum; // calculate the remaining bytes + m_spi->zero16(m_start, remainingWords); // write remaining bytes are start + m_currentWrPosition = m_start + remainingWords*sizeof(int16_t); } return true; } -bool ExtMemSlot::zeroAdvance16(size_t dataSize) + +bool ExtMemSlot::writeAdvance16(int16_t data) { if (!m_valid) { return false; } - if (m_currentWrPosition + dataSize-1 <= m_end) { - // entire block fits in memory slot without wrapping - m_spi->zero16(m_currentWrPosition, dataSize); // cast audio data to uint. - m_currentWrPosition += dataSize; + m_spi->write16(m_currentWrPosition, static_cast(data)); + if (m_currentWrPosition < m_end-1) { + m_currentWrPosition+=2; // wrote two bytes } else { - // this write will wrap the memory slot - size_t numBytes = m_end - m_currentWrPosition + 1; - m_spi->zero16(m_currentWrPosition, numBytes); - size_t remainingBytes = dataSize - numBytes; // calculate the remaining bytes - m_spi->zero16(m_start, remainingBytes); // write remaining bytes are start - m_currentWrPosition = m_start + remainingBytes; + m_currentWrPosition = m_start; } return true; } + bool ExtMemSlot::enable() const { if (m_spi) { @@ -158,6 +191,7 @@ void ExtMemSlot::printStatus(void) const { Serial.println(String("valid:") + m_valid + String(" m_start:") + m_start + \ String(" m_end:") + m_end + String(" m_currentWrPosition: ") + m_currentWrPosition + \ + String(" m_currentRdPosition: ") + m_currentRdPosition + \ String(" m_size:") + m_size); } @@ -178,11 +212,6 @@ ExternalSramManager::ExternalSramManager(unsigned numMemories) m_memConfig[i].nextAvailable = 0; m_memConfig[i].m_spi = nullptr; -// if (i < numMemories) { -// m_memConfig[i].m_spi = new BAGuitar::BASpiMemory(static_cast(i)); -// } else { -// m_memConfig[i].m_spi = nullptr; -// } } m_configured = true; } @@ -204,7 +233,7 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMillisecond { // 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, mem); + return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem); } bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem) diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 9fedbc8..e4e44c8 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -1,87 +1,196 @@ -/* - * ExtMemoryManagement.h +/**************************************************************************//** + * @file + * @author Steve Lascos + * @company Blackaddr Audio * - * Created on: Dec 23, 2017 - * Author: slascos - */ + * LibMemoryManagment is a class for providing access to external SPI based + * SRAM with the optional convience of breaking it up into 'slots' which are smaller + * memory entities. + * @details This class treats an external memory as a pool from which the user requests + * a block. When using that block, the user need not be concerned with where the pool + * it came from, they only deal with offsets from the start of their memory block. Ie. + * Your particular slot of memory appears to you to start at 0 and end at whatever size + * was requested. + * + * @copyright This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version.* + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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_LIBMEMORYMANAGEMENT_H_ -#define SRC_LIBMEMORYMANAGEMENT_H_ +#ifndef __LIBMEMORYMANAGEMENT_H +#define __LIBMEMORYMANAGEMENT_H #include - -//#include "Audio.h" - #include "BAHardware.h" #include "BASpiMemory.h" -//#include "LibBasicFunctions.h" namespace BAGuitar { +/**************************************************************************//** + * MemConfig contains the configuration information associated with a particular + * SPI interface. + *****************************************************************************/ struct MemConfig { - size_t size; - size_t totalAvailable; - size_t nextAvailable; - BASpiMemory *m_spi = nullptr; + size_t size; ///< the total size of the external SPI memory + size_t totalAvailable; ///< the number of bytes available (remaining) + size_t nextAvailable; ///< the starting point for the next available slot + BASpiMemory *m_spi = nullptr; ///< handle to the SPI interface }; -class ExternalSramManager; // forward declare so ExtMemSlot can setup friendship +class ExternalSramManager; // forward declare so ExtMemSlot can declared friendship with it +/**************************************************************************//** + * ExtMemSlot provides a convenient interface to a particular slot of an + * external memory. + * @details the memory can be access randomly, as a single word, as a block of + * data, or as circular queue. + *****************************************************************************/ class ExtMemSlot { public: + /// clear the entire contents of the slot by writing zeros + /// @returns true on success bool clear(); - bool setWritePosition(size_t offset) { m_currentWrPosition = m_start + offset; return true;} // TODO add range check + + /// set a new write position (in bytes) for circular operation + /// @param offsetBytes moves the write pointer to the specified offset from the slot start + /// @returns true on success, else false if offset is beyond slot boundaries. + bool setWritePosition(size_t offsetBytes); + + /// returns the currently set write pointer pointer + /// @returns the write position value size_t getWritePosition() const { return m_currentWrPosition-m_start; } - bool setReadPosition(size_t offset) { m_currentRdPosition = m_start + offset; return true;} // TODO add range check - size_t getReadPosition() const { return m_currentRdPosition-m_start; } + /// set a new read position (in bytes) for circular operation + /// @param offsetBytes moves the read pointer to the specified offset from the slot start + /// @returns true on success, else false if offset is beyond slot boundaries. + bool setReadPosition(size_t offsetBytes); - bool write16(size_t offset, int16_t *dataPtr, size_t numData); - bool zero16(size_t offset, size_t numData); - bool read16(int16_t *dest, size_t srcOffset, size_t numData); + /// returns the currently set read pointer pointer + /// @returns the read position value + size_t getReadPosition() const { return m_currentRdPosition-m_start; } + /// Write a block of 16-bit data to the memory at the specified offset + /// @param offsetBytes offset in bytes from start of slot + /// @param dataPtr pointer to start of block of 16-bit data + /// @param numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + bool write16(size_t offsetBytes, int16_t *dest, size_t numWords); + + /// Write a block of zeros (16-bit) to the memory at the specified offset + /// @param offsetBytes offset in bytes from start of slot + /// @param numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + bool zero16(size_t offsetBytes, size_t numWords); + + /// Read a block of 16-bit data from the memory at the specified location + /// @param dest pointer to destination for the read data + /// @param offsetBytes offset in bytes from start of slot + /// @param numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + bool read16(int16_t *dest, size_t offsetBytes, size_t numWords); + + /// Read the next in memory during circular operation + /// @returns the next 16-bit data word in memory uint16_t readAdvance16(); - bool writeAdvance16(int16_t *dataPtr, size_t numData); + + /// Write a block of 16-bit data from the specified location in circular operation + /// @param src pointer to the start of the block of data to write to memory + /// @param numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + bool writeAdvance16(int16_t *src, size_t numWords); + + /// Write a single 16-bit data to the next location in circular operation + /// @param data the 16-bit word to transfer + /// @returns true on success, else false on error bool writeAdvance16(int16_t data); // write just one data - bool zeroAdvance16(size_t numData); - //void read16FromPast(int16_t *dest, size_t Offset, size_t numData); + + /// Write a block of 16-bit data zeros in circular operation + /// @param numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + bool zeroAdvance16(size_t numWords); + + + /// Get the size of the memory slot + /// @returns size of the slot in bytes size_t size() const { return m_size; } + + /// Ensures the underlying SPI interface is enabled + /// @returns true on success, false on error bool enable() const; + + /// Checks whether underlying SPI interface is enabled + /// @returns true if enabled, false if not enabled bool isEnabled() const; + + /// DEBUG USE: prints out the slot member variables void printStatus(void) const; private: - friend ExternalSramManager; - bool m_valid = false; - size_t m_start = 0; - size_t m_end = 0; - size_t m_currentWrPosition = 0; - size_t m_currentRdPosition = 0; - size_t m_size = 0; - SpiDeviceId m_spiId; - BASpiMemory *m_spi = nullptr; + friend ExternalSramManager; ///< gives the manager access to the private variables + bool m_valid = false; ///< After a slot is successfully configured by the manager it becomes valid + size_t m_start = 0; ///< the external memory address in bytes where this slot starts + size_t m_end = 0; ///< the external memory address in bytes where this slot ends (inclusive) + size_t m_currentWrPosition = 0; ///< current write pointer for circular operation + size_t m_currentRdPosition = 0; ///< current read pointer for circular operation + size_t m_size = 0; ///< size of this slot in bytes + SpiDeviceId m_spiId; ///< the SPI Device ID + BASpiMemory *m_spi = nullptr; ///< pointer to an instance of the BASpiMemory interface class }; +/**************************************************************************//** + * ExternalSramManager provides a class to handle dividing an external SPI RAM + * into independent slots for general use. + * @details the class does not support deallocated memory because this would cause + * fragmentation. + *****************************************************************************/ class ExternalSramManager final { public: ExternalSramManager() = delete; - ExternalSramManager(unsigned numMemories); + + /// 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); virtual ~ExternalSramManager(); - size_t availableMemory(BAGuitar::MemSelect mem); + /// Query the amount of available (unallocated) memory + /// @details note that currently, memory cannot be allocated. + /// @param mem specifies which memory to query, default is memory 0 + /// @returns the available memory in bytes + size_t availableMemory(BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + + /// Request memory be allocated for the provided slot + /// @param slot a pointer to the global slot object to which memory will be allocated + /// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes. + /// @param mem specify which external memory to allocate from + /// @returns true on success, otherwise false on error bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); - bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + + /// Request memory be allocated for the provided slot + /// @param slot a pointer to the global slot object to which memory will be allocated + /// @param sizeBytes request the amount of memory in bytes to request + /// @param mem specify which external memory to allocate from + /// @returns true on success, otherwise false on error + bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); private: - static bool m_configured; - static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; + static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project + static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; ///< store the configuration information for each external memory }; } -#endif /* SRC_LIBMEMORYMANAGEMENT_H_ */ +#endif /* __LIBMEMORYMANAGEMENT_H */ From ed355a03f8ffaeb1f518598fa32f72e32fc4df59 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Fri, 19 Jan 2018 14:34:32 -0500 Subject: [PATCH 09/27] incremental cleanup of LibBasicFunction --- src/AudioEffectAnalogDelay.cpp | 24 +----- src/LibBasicFunctions.cpp | 26 +++--- src/LibBasicFunctions.h | 145 +++++++++++++++++++++++++-------- 3 files changed, 129 insertions(+), 66 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index e08ecf7..2298d1c 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -47,15 +47,6 @@ void AudioEffectAnalogDelay::update(void) { audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples - if (!inputAudioBlock) { - return; - } -// else { -// transmit(inputAudioBlock, 0); -// release(inputAudioBlock); -// return; -// } - if (m_callCount < 1024) { if (inputAudioBlock) { transmit(inputAudioBlock, 0); @@ -64,15 +55,14 @@ void AudioEffectAnalogDelay::update(void) m_callCount++; return; } -// else if (m_callCount > 1400) { -// if (inputAudioBlock) release(inputAudioBlock); -// return; -// } + m_callCount++; Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); - //m_memory->getSlot()->printStatus(); audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock); + if (blockToRelease) release(blockToRelease); + + Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); // if (inputAudioBlock) { // transmit(inputAudioBlock, 0); @@ -80,12 +70,6 @@ void AudioEffectAnalogDelay::update(void) // } // return; - if (blockToRelease) release(blockToRelease); - - - Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); - - // For each active channel, output the delayed audio audio_block_t *blockToOutput = nullptr; for (int channel=0; channelsize() >= m_ringBuffer->max_size() ) { // pop before adding @@ -97,26 +98,25 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block) } } - return block; - + blockToRelease = block; } - + return blockToRelease; } audio_block_t* AudioDelay::getBlock(size_t index) { - if (m_type == MemType::MEM_INTERNAL) { - return m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); - } else { - return nullptr; + 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 offset, size_t numSamples) { if (!dest) return false; - if (m_type == MemType::MEM_INTERNAL) { + if (m_type == (MemType::MEM_INTERNAL)) { QueuePosition position = calcQueuePosition(offset); size_t index = position.index; diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 139625e..9ff8a1d 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -1,9 +1,24 @@ -/* - * LibBasicFunctions.h +/**************************************************************************//** + * @file + * @author Steve Lascos + * @company Blackaddr Audio * - * Created on: Dec 23, 2017 - * Author: slascos - */ + * LibBasicFunctions is a collection of helpful functions and classes that make + * it easier to perform common tasks in Audio applications. + * + * @copyright This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version.* + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; 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 @@ -13,60 +28,131 @@ #include "LibMemoryManagement.h" -#ifndef SRC_LIBBASICFUNCTIONS_H_ -#define SRC_LIBBASICFUNCTIONS_H_ +#ifndef __LIBBASICFUNCTIONS_H +#define __LIBBASICFUNCTIONS_H namespace BAGuitar { +/**************************************************************************//** + * QueuePosition is used for storing the index (in an array of queues) and the + * offset within an audio_block_t data buffer. Useful for dealing with large + * windows of audio spread across multiple audio data blocks. + *****************************************************************************/ struct QueuePosition { - int offset; - int index; + int offset; ///< offset in samples within an audio_block_t data buffer + int index; ///< index in an array of audio data blocks }; + +/// Calculate the exact sample position in an array of audio blocks that corresponds +/// to a particular offset given as time. +/// @param milliseconds length of the interval in milliseconds +/// @returns a struct containing the index and offset QueuePosition calcQueuePosition(float milliseconds); + +/// Calculate the exact sample position in an array of audio blocks that corresponds +/// to a particular offset given as a number of samples +/// @param milliseconds length of the interval in milliseconds +/// @returns a struct containing the index and offset QueuePosition calcQueuePosition(size_t numSamples); -size_t calcAudioSamples(float milliseconds); -size_t calcOffset(QueuePosition position); -template -class RingBuffer; // forward declare +/// Calculate the number of audio samples (rounded up) that correspond to a +/// given length of time. +/// @param milliseconds length of the interval in milliseconds +/// @returns the number of corresonding audio samples. +size_t calcAudioSamples(float milliseconds); -enum class MemType : unsigned { - MEM_INTERNAL = 0, - MEM_EXTERNAL -}; +/// Calculate the number of audio samples (usually an offset) from +/// a queue position. +/// @param position specifies the index and offset within a queue +/// @returns the number of samples from the start of the queue array to the +/// specified position. +size_t calcOffset(QueuePosition position); -struct INTERNAL_MEMORY {}; -struct EXTERNAL_MEMORY {}; +template +class RingBuffer; // forward declare so AudioDelay can use it. + + +/**************************************************************************//** + * Audio delays are a very common function in audio processing. In addition to + * being used for simply create a delay effect, it can also be used for buffering + * a sliding window in time of audio samples. This is useful when combining + * several audio_block_t data buffers together to form one large buffer for + * FFTs, etc. + * @details The buffer works like a queue. You add new audio_block_t when available, + * and the class will return an old buffer when it is to be discarded from the queue.
+ * Note that using INTERNAL memory means the class will only store a queue + * of pointers to audio_block_t buffers, since the Teensy Audio uses a shared memory + * approach. When using EXTERNAL memory, data is actually copyied to/from an external + * SRAM device. + *****************************************************************************/ class AudioDelay { public: AudioDelay() = delete; + + /// Construct an audio buffer using INTERNAL memory by specifying the max number + /// of audio samples you will want. + /// @param maxSamples equal or greater than your longest delay requirement AudioDelay(size_t maxSamples); + + /// Construct an audio buffer using INTERNAL memory by specifying the max amount of + /// time you will want available in the buffer. + /// @param maxDelayTimeMs max length of time you want in the buffer specified in milliseconds AudioDelay(float maxDelayTimeMs); + + /// Construct an audio buffer using a slot configured with the BAGuitar::ExternalSramManager + /// @param slot a pointer to the slot representing the memory you wish to use for the buffer. AudioDelay(ExtMemSlot *slot); + ~AudioDelay(); - // Internal memory member functions + /// Add a new audio block into the buffer. When the buffer is filled, + /// adding a new block will push out the oldest once which is returned. + /// @param blockIn pointer to the most recent block of audio + /// @returns the buffer to be discarded, or nullptr if not filled (INTERNAL), or + /// not applicable (EXTERNAL). audio_block_t *addBlock(audio_block_t *blockIn); + + /// When using INTERNAL memory, returns the pointer for the specified index into buffer. + /// @details, the most recent block is 0, 2nd most recent is 1, ..., etc. + /// @param index the specifies how many buffers older than the current to retrieve + /// @returns a pointer to the requested audio_block_t audio_block_t *getBlock(size_t index); + + /// Retrieve an audio block (or samples) from the buffer. + /// @details when using INTERNAL memory, only supported size is AUDIO_BLOCK_SAMPLES. When using + /// EXTERNAL, a size smaller than AUDIO_BLOCK_SAMPLES can be requested. + /// @param dest pointer to the target audio block to write the samples to. + /// @param offset data will start being transferred offset samples from the start of the audio buffer + /// @param numSamples default value is AUDIO_BLOCK_SAMPLES, so typically you don't have to specify this parameter. + /// @returns true on success, false on error. bool getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES); - // External memory member functions + /// When using EXTERNAL memory, this function can return a pointer to the underlying ExtMemSlot object associated + /// with the buffer. + /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } private: - MemType m_type; - RingBuffer *m_ringBuffer = nullptr; - ExtMemSlot *m_slot = nullptr; + + /// enumerates whether the underlying memory buffer uses INTERNAL or EXTERNAL memory + enum class MemType : unsigned { + MEM_INTERNAL = 0, ///< internal audio_block_t from the Teensy Audio Library is used + MEM_EXTERNAL ///< external SPI based ram is used + }; + + MemType m_type; ///< when 0, INTERNAL memory, when 1, external MEMORY. + RingBuffer *m_ringBuffer = nullptr; ///< When using INTERNAL memory, a RingBuffer will be created. + ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided. }; + template class RingBuffer { public: RingBuffer() = delete; RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { m_buffer = new T[maxSize]; - //Serial.println(String("New RingBuffer: max size is ") + m_maxSize); } virtual ~RingBuffer(){ if (m_buffer) delete [] m_buffer; @@ -78,7 +164,6 @@ public: if ( (m_head == m_tail) && (m_size > 0) ) { // overflow Serial.println("RingBuffer::push_back: overflow"); - while(1) {} // TODO REMOVE return -1; } @@ -89,7 +174,6 @@ public: m_head = 0; } m_size++; - //Serial.println(" ...Done push"); return 0; } @@ -127,15 +211,10 @@ public: idx -= m_maxSize; } - //Serial.println(String("BackIndex is ") + idx + String(" address: ") + (uint32_t)m_buffer[idx] + String(" data: ") + (uint32_t)m_buffer[idx]->data); -// for (int i=0; idata); -// } return idx; } size_t size() const { - //Serial.println(String("RingBuffer::size: ") + m_head + String(":") + m_tail + String(":") + m_size); return m_size; } @@ -167,4 +246,4 @@ private: } -#endif /* SRC_LIBBASICFUNCTIONS_H_ */ +#endif /* __LIBBASICFUNCTIONS_H */ From 12fbdcff8ddc4f00f00f1f034e30071f00690f58 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Fri, 19 Jan 2018 14:45:39 -0500 Subject: [PATCH 10/27] finished code cleanup of LibBasicFunctions --- src/LibBasicFunctions.h | 42 ++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 9ff8a1d..57b00df 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -146,11 +146,16 @@ private: ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided. }; - +/**************************************************************************//** + * Customer RingBuffer with random access + *****************************************************************************/ template class RingBuffer { public: RingBuffer() = delete; + + /// Construct a RingBuffer of specified max size + /// @param maxSize number of entries in ring buffer RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { m_buffer = new T[maxSize]; } @@ -158,9 +163,12 @@ public: if (m_buffer) delete [] m_buffer; } + /// Add an element to the back of the queue + /// @param element element to add to queue + /// returns 0 if success, otherwise error int push_back(T element) { - Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); + //Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); if ( (m_head == m_tail) && (m_size > 0) ) { // overflow Serial.println("RingBuffer::push_back: overflow"); @@ -178,6 +186,8 @@ public: return 0; } + /// Remove the element at teh front of the queue + /// @returns 0 if success, otherwise error int pop_front() { if (m_size == 0) { @@ -195,14 +205,21 @@ public: return 0; } + /// Get the element at the front of the queue + /// @returns element at front of queue T front() const { return m_buffer[m_tail]; } + /// get the element at the back of the queue + /// @returns element at the back of the queue T back() const { return m_buffer[m_head-1]; } + /// Get a previously pushed elememt + /// @param offset zero is last pushed, 1 is second last, etc. + /// @returns the absolute index corresponding to the requested offset. size_t get_index_from_back(size_t offset = 0) const { // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; size_t idx = (m_maxSize + m_head -1 - offset); @@ -214,33 +231,44 @@ public: return idx; } + /// get the current size of the queue + /// @returns size of the queue size_t size() const { return m_size; } + /// get the maximum size the queue can hold + /// @returns maximum size of the queue size_t max_size() const { return m_maxSize; } + /// get the element at the specified absolute index + /// @param index element to retrieve from absolute queue position + /// @returns the request element T& operator[] (size_t index) { return m_buffer[index]; } + /// get the element at the specified absolute index + /// @param index element to retrieve from absolute queue position + /// @returns the request element T at(size_t index) const { return m_buffer[index]; } + /// DEBUG: Prints the status of the Ringbuffer void print() const { for (int idx=0; idxdata); } } private: - size_t m_head=0; - size_t m_tail=0; - size_t m_size=0; - T *m_buffer = nullptr; - const size_t m_maxSize; + size_t m_head=0; ///< back of the queue + size_t m_tail=0; ///< front of the queue + size_t m_size=0; ///< current size of the qeueu + T *m_buffer = nullptr; ///< pointer to the allocated buffer array + const size_t m_maxSize; ///< maximum size of the queue }; } From 6b59f022b387960ae06f5dc2fe6b74bcf6a6ca5d Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Fri, 19 Jan 2018 16:19:51 -0500 Subject: [PATCH 11/27] cleand up AudioEffectAnalogDelay, added wet/dry mix and feedback controls, removed independent channels --- src/AudioEffectAnalogDelay.cpp | 123 +++++++++++++++++---------------- src/AudioEffectAnalogDelay.h | 22 +++--- src/LibBasicFunctions.cpp | 12 ++++ src/LibBasicFunctions.h | 7 ++ 4 files changed, 98 insertions(+), 66 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 2298d1c..0dfa4f1 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -13,18 +13,12 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelay) : AudioStream(1, m_inputQueueArray) { m_memory = new AudioDelay(maxDelay); - for (int i=0; igetRingBuffer()->size() > 0) { + audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); + m_memory->getRingBuffer()->pop_front(); + if (releaseBlock) release(releaseBlock); + } + } + } + if (m_callCount < 1024) { if (inputAudioBlock) { transmit(inputAudioBlock, 0); @@ -59,10 +76,12 @@ void AudioEffectAnalogDelay::update(void) m_callCount++; Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); - audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock); - if (blockToRelease) release(blockToRelease); + // Preprocessing + audio_block_t *preProcessed = allocate(); + m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); - Serial.print("Active channels: "); Serial.print(m_activeChannels, HEX); Serial.println(""); + audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); + if (blockToRelease) release(blockToRelease); // if (inputAudioBlock) { // transmit(inputAudioBlock, 0); @@ -70,44 +89,25 @@ void AudioEffectAnalogDelay::update(void) // } // return; - // For each active channel, output the delayed audio + // OUTPUT PROCESSING audio_block_t *blockToOutput = nullptr; - for (int channel=0; channelgetBlock(queuePosition.index); // could be nullptr! - if (blockToOutput) transmit(blockToOutput); - continue; - } else { - blockToOutput = allocate(); // allocate if spanning 2 queues - } - } else { - // external memory - blockToOutput = allocate(); // allocate always for external memory - } - - // copy the output data - if (!blockToOutput) continue; // skip this channel due to failure - // copy over data - m_memory->getSamples(blockToOutput, m_channelOffsets[channel]); - - transmit(blockToOutput); - release(blockToOutput); - } + blockToOutput = allocate(); + + // copy the output data + if (!blockToOutput) return; // skip this time due to failure + // copy over data + m_memory->getSamples(blockToOutput, m_delaySamples); + // perform the mix + m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); + transmit(blockToOutput); + + release(inputAudioBlock); + release(m_previousBlock); + m_previousBlock = blockToOutput; } -bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) +void AudioEffectAnalogDelay::delay(float milliseconds) { - if (channel > MAX_DELAY_CHANNELS-1) // channel id too high - return false; - size_t delaySamples = calcAudioSamples(milliseconds); if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } @@ -128,16 +128,11 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, float milliseconds) } } - m_channelOffsets[channel] = delaySamples; - m_activeChannels |= 1< MAX_DELAY_CHANNELS-1) // channel id too high - return false; - if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } if (!m_externalMemory) { @@ -152,14 +147,26 @@ bool AudioEffectAnalogDelay::delay(unsigned channel, size_t delaySamples) slot->enable(); } } - m_channelOffsets[channel] = delaySamples; - m_activeChannels |= 1<data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + } +} + +void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +{ + if ( out && dry && wet) { + alphaBlend(out, dry, wet, m_mix); + } else if (dry) { + memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + } } } diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index 678bce0..92cbfc0 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -15,8 +15,6 @@ namespace BAGuitar { - - class AudioEffectAnalogDelay : public AudioStream { public: static constexpr int MAX_DELAY_CHANNELS = 8; @@ -28,20 +26,28 @@ public: virtual ~AudioEffectAnalogDelay(); virtual void update(void); - bool delay(unsigned channel, float milliseconds); - bool delay(unsigned channel, size_t delaySamples); - void disable(unsigned channel); + void delay(float milliseconds); + void delay(size_t delaySamples); + void feedback(float feedback) { m_feedback = feedback; } + void mix(float mix) { m_mix = mix; } + void enable() { m_enable = true; } + void disable() { m_enable = false; } private: audio_block_t *m_inputQueueArray[1]; - unsigned m_activeChannels = 0; + bool m_enable = false; bool m_externalMemory = false; AudioDelay *m_memory = nullptr; - //size_t m_numQueues = 0; - size_t m_channelOffsets[MAX_DELAY_CHANNELS]; + size_t m_delaySamples = 0; + float m_feedback = 0.0f; + float m_mix = 0.0f; size_t m_callCount = 0; + + audio_block_t *m_previousBlock = nullptr; + void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); + void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); }; } diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index d7f3a45..a94bb53 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -32,6 +32,18 @@ size_t calcOffset(QueuePosition position) return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset; } +audio_block_t alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) +{ + for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { + out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); + } +} + +void clearAudioBlock(audio_block_t *block) +{ + memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); +} + AudioDelay::AudioDelay(size_t maxSamples) : m_slot(nullptr) diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 57b00df..cdaa811 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -68,6 +68,11 @@ size_t calcAudioSamples(float milliseconds); /// specified position. size_t calcOffset(QueuePosition position); +void clearAudioBlock(audio_block_t *block); + + +audio_block_t alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix); + template class RingBuffer; // forward declare so AudioDelay can use it. @@ -133,6 +138,8 @@ public: /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } + RingBuffer *getRingBuffer() const { return m_ringBuffer; } + private: /// enumerates whether the underlying memory buffer uses INTERNAL or EXTERNAL memory From 61d45bff5f23978675e8e7efcd579eaf6c499508 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 20 Jan 2018 21:07:40 -0500 Subject: [PATCH 12/27] added some midi controls --- src/AudioEffectAnalogDelay.cpp | 77 +++++++++++++++++++++++++++++++++- src/AudioEffectAnalogDelay.h | 17 +++++++- src/LibBasicFunctions.cpp | 2 +- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 0dfa4f1..73d9b6a 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -9,16 +9,28 @@ namespace BAGuitar { +constexpr int MIDI_NUM_PARAMS = 4; +constexpr int MIDI_CHANNEL = 0; +constexpr int MIDI_CONTROL = 1; + +constexpr int MIDI_ENABLE = 0; +constexpr int MIDI_DELAY = 1; +constexpr int MIDI_FEEDBACK = 2; +constexpr int MIDI_MIX = 3; + + AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelay) : AudioStream(1, m_inputQueueArray) { m_memory = new AudioDelay(maxDelay); + m_maxDelaySamples = calcAudioSamples(maxDelay); } AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) : AudioStream(1, m_inputQueueArray) { m_memory = new AudioDelay(numSamples); + m_maxDelaySamples = numSamples; } // requires preallocated memory large enough @@ -26,6 +38,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) : AudioStream(1, m_inputQueueArray) { m_memory = new AudioDelay(slot); + m_maxDelaySamples = slot->size(); m_externalMemory = true; } @@ -74,7 +87,7 @@ void AudioEffectAnalogDelay::update(void) m_callCount++; - Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); + //Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); // Preprocessing audio_block_t *preProcessed = allocate(); @@ -151,6 +164,68 @@ void AudioEffectAnalogDelay::delay(size_t delaySamples) } +void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) +{ + float val = (float)value / 127.0f; + + if ((m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIDI_DELAY][MIDI_CONTROL] == control)) { + // Delay + Serial.println(String("AudioEffectAnalogDelay::delay: ") + val); + delay((size_t)(val * m_maxDelaySamples)); + return; + } + + if ((m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] == control)) { + // Enable + if (val >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON") + value); } + else { disable(); Serial.println(String("AudioEffectAnalogDelay::enable: OFF") + value); } + return; + } + + if ((m_midiConfig[MIDI_FEEDBACK][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIDI_FEEDBACK][MIDI_CONTROL] == control)) { + // Feedback + Serial.println(String("AudioEffectAnalogDelay::feedback: ") + val); + feedback(val); + return; + } + + if ((m_midiConfig[MIDI_MIX][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIDI_MIX][MIDI_CONTROL] == control)) { + // Mix + Serial.println(String("AudioEffectAnalogDelay::mix: ") + val); + mix(val); + return; + } + +} +void AudioEffectAnalogDelay::mapMidiDelay(int control, int channel) +{ + m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] = channel; + m_midiConfig[MIDI_DELAY][MIDI_CONTROL] = control; +} + +void AudioEffectAnalogDelay::mapMidiEnable(int control, int channel) +{ + m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] = channel; + m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] = control; +} + +void AudioEffectAnalogDelay::mapMidiFeedback(int control, int channel) +{ + m_midiConfig[MIDI_FEEDBACK][MIDI_CHANNEL] = channel; + m_midiConfig[MIDI_FEEDBACK][MIDI_CONTROL] = control; +} + +void AudioEffectAnalogDelay::mapMidiMix(int control, int channel) +{ + m_midiConfig[MIDI_MIX][MIDI_CHANNEL] = channel; + m_midiConfig[MIDI_MIX][MIDI_CONTROL] = control; +} + + void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) { if ( out && dry && wet) { diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index 92cbfc0..b354dc3 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -33,21 +33,34 @@ public: void enable() { m_enable = true; } void disable() { m_enable = false; } + void processMidi(int channel, int control, int value); + void mapMidiEnable(int control, int channel = 0); + void mapMidiDelay(int control, int channel = 0); + void mapMidiFeedback(int control, int channel = 0); + void mapMidiMix(int control, int channel = 0); + private: audio_block_t *m_inputQueueArray[1]; bool m_enable = false; bool m_externalMemory = false; AudioDelay *m_memory = nullptr; + size_t m_maxDelaySamples = 0; + // Controls + int m_midiConfig[4][2]; + //int m_midiEnable[2] = {0,16}; size_t m_delaySamples = 0; + //int m_midiDelay[2] = {0,20}; float m_feedback = 0.0f; + //int m_midiFeedback[2] = {0,21}; float m_mix = 0.0f; - - size_t m_callCount = 0; + //int m_midiMix[2] = {0,22}; audio_block_t *m_previousBlock = nullptr; void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); + + size_t m_callCount = 0; }; } diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index a94bb53..dba786e 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -174,7 +174,7 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample m_slot->setReadPosition((size_t)readPosition); } - m_slot->printStatus(); + //m_slot->printStatus(); // write the data to the destination block in reverse // int16_t *destPtr = dest->data + AUDIO_BLOCK_SAMPLES-1; From fbb0acfc94a207b4a0075729baf08cfe8f8b71d3 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sun, 21 Jan 2018 02:44:46 -0500 Subject: [PATCH 13/27] Fixes to some midi code --- src/AudioEffectAnalogDelay.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 73d9b6a..dd216d6 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -171,16 +171,17 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) if ((m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] == channel) && (m_midiConfig[MIDI_DELAY][MIDI_CONTROL] == control)) { // Delay - Serial.println(String("AudioEffectAnalogDelay::delay: ") + val); - delay((size_t)(val * m_maxDelaySamples)); + m_maxDelaySamples = m_memory->getSlot()->size(); + Serial.println(String("AudioEffectAnalogDelay::delay: ") + val + String(" out of ") + m_maxDelaySamples); + delay((size_t)(val * (float)m_maxDelaySamples)); return; } if ((m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] == channel) && (m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] == control)) { // Enable - if (val >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON") + value); } - else { disable(); Serial.println(String("AudioEffectAnalogDelay::enable: OFF") + value); } + if (value >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON")); } + else { disable(); Serial.println(String("AudioEffectAnalogDelay::enable: OFF")); } return; } From 17bbe8216ae61624a20e2685e9fec2ece42f1918 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Wed, 24 Jan 2018 21:14:57 -0500 Subject: [PATCH 14/27] Fixed a problem with INTERNAL memory on initial filling of RingBuffer --- src/AudioEffectAnalogDelay.cpp | 2 +- src/LibBasicFunctions.cpp | 32 ++++++++++++++++++++++---------- src/LibBasicFunctions.h | 13 +++++++------ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 73d9b6a..cfac170 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -179,7 +179,7 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) if ((m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] == channel) && (m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] == control)) { // Enable - if (val >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON") + value); } + if (value >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON") + value); } else { disable(); Serial.println(String("AudioEffectAnalogDelay::enable: OFF") + value); } return; } diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index dba786e..67debd6 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -32,7 +32,7 @@ size_t calcOffset(QueuePosition position) return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset; } -audio_block_t alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) +void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) { for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); @@ -89,6 +89,7 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block) // 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"); } @@ -126,35 +127,46 @@ audio_block_t* AudioDelay::getBlock(size_t index) bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSamples) { - if (!dest) return false; + if (!dest) { + Serial.println("getSamples(): dest is invalid"); + return false; + } if (m_type == (MemType::MEM_INTERNAL)) { QueuePosition position = calcQueuePosition(offset); 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 - audio_block_t *currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); - memcpy(static_cast(dest->data), static_cast(currentQueue->data), numSamples * sizeof(int16_t)); + 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; - audio_block_t *currentQueue; 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 = (currentQueue->data + AUDIO_BLOCK_SAMPLES - position.offset); + //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 + //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 = (currentQueue->data); + srcStart = (currentQueue0->data); numData = AUDIO_BLOCK_SAMPLES - numData; memcpy(static_cast(destStart), static_cast(srcStart), numData * sizeof(int16_t)); diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index cdaa811..5b2dc3b 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -71,7 +71,7 @@ size_t calcOffset(QueuePosition position); void clearAudioBlock(audio_block_t *block); -audio_block_t alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix); +void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix); template @@ -164,7 +164,7 @@ public: /// Construct a RingBuffer of specified max size /// @param maxSize number of entries in ring buffer RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { - m_buffer = new T[maxSize]; + m_buffer = new T[maxSize](); } virtual ~RingBuffer(){ if (m_buffer) delete [] m_buffer; @@ -199,7 +199,7 @@ public: if (m_size == 0) { // buffer is empty - Serial.println("RingBuffer::pop_front: buffer is empty\n"); + //Serial.println("RingBuffer::pop_front: buffer is empty\n"); return -1; } if (m_tail < m_maxSize-1) { @@ -208,7 +208,7 @@ public: m_tail = 0; } m_size--; - Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); + //Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); return 0; } @@ -264,10 +264,11 @@ public: return m_buffer[index]; } - /// DEBUG: Prints the status of the Ringbuffer + /// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches void print() const { for (int idx=0; idxdata); + Serial.print(idx + String(" address: ")); Serial.print((uint32_t)m_buffer[idx], HEX); + Serial.print(" data: "); Serial.println((uint32_t)m_buffer[idx]->data, HEX); } } private: From 442f871e014d56e01f5da6464b73f910f805d37d Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 3 Feb 2018 16:51:46 -0500 Subject: [PATCH 15/27] Added low pass filters in the delay path --- src/AudioEffectAnalogDelay.cpp | 84 +++++++++++--------- src/AudioEffectAnalogDelay.h | 16 ++-- src/LibBasicFunctions.cpp | 139 ++++++++++++++++++++++++++++++++- src/LibBasicFunctions.h | 60 ++++++++++++++ 4 files changed, 249 insertions(+), 50 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index c570d03..d074afc 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -13,17 +13,28 @@ constexpr int MIDI_NUM_PARAMS = 4; constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; -constexpr int MIDI_ENABLE = 0; +constexpr int MIDI_BYPASS = 0; constexpr int MIDI_DELAY = 1; constexpr int MIDI_FEEDBACK = 2; constexpr int MIDI_MIX = 3; +// BOSS DM-3 Filters +constexpr unsigned NUM_IIR_STAGES = 4; +constexpr unsigned IIR_COEFF_SHIFT = 2; +constexpr int32_t DEFAULT_COEFFS[5*NUM_IIR_STAGES] = { + 536870912, 988616936, 455608573, 834606945, -482959709, + 536870912, 1031466345, 498793368, 965834205, -467402235, + 536870912, 1105821939, 573646688, 928470657, -448083489, + 2339, 5093, 2776, 302068995, 4412722 +}; + AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelay) : AudioStream(1, m_inputQueueArray) { m_memory = new AudioDelay(maxDelay); m_maxDelaySamples = calcAudioSamples(maxDelay); + m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); } AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) @@ -31,6 +42,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) { m_memory = new AudioDelay(numSamples); m_maxDelaySamples = numSamples; + m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); } // requires preallocated memory large enough @@ -40,68 +52,64 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) m_memory = new AudioDelay(slot); m_maxDelaySamples = slot->size(); m_externalMemory = true; + m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); } AudioEffectAnalogDelay::~AudioEffectAnalogDelay() { if (m_memory) delete m_memory; + if (m_iir) delete m_iir; } void AudioEffectAnalogDelay::update(void) { audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples - if (!inputAudioBlock) { - // create silence - inputAudioBlock = allocate(); - if (!inputAudioBlock) { return; } // failed to allocate - else { - clearAudioBlock(inputAudioBlock); - } - } - + // Check is block is disabled if (m_enable == false) { - // release all held memory resources - transmit(inputAudioBlock); - release(inputAudioBlock); inputAudioBlock = nullptr; + // do not transmit or process any audio, return as quickly as possible. + if (inputAudioBlock) release(inputAudioBlock); + // release all held memory resources if (m_previousBlock) { release(m_previousBlock); m_previousBlock = nullptr; } if (!m_externalMemory) { + // when using internal memory we have to release all references in the ring buffer while (m_memory->getRingBuffer()->size() > 0) { audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); m_memory->getRingBuffer()->pop_front(); if (releaseBlock) release(releaseBlock); } } + return; } - if (m_callCount < 1024) { - if (inputAudioBlock) { - transmit(inputAudioBlock, 0); - release(inputAudioBlock); + // Check is block is bypassed, if so either transmit input directly or create silence + if (m_bypass == true) { + // transmit the input directly + if (!inputAudioBlock) { + // create silence + inputAudioBlock = allocate(); + if (!inputAudioBlock) { return; } // failed to allocate + else { + clearAudioBlock(inputAudioBlock); + } } - m_callCount++; return; + transmit(inputAudioBlock, 0); + release(inputAudioBlock); + return; } - - m_callCount++; - //Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); - + // Otherwise perform normal processing // Preprocessing audio_block_t *preProcessed = allocate(); + // mix the input with the feedback path in the pre-processing stage m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); if (blockToRelease) release(blockToRelease); -// if (inputAudioBlock) { -// transmit(inputAudioBlock, 0); -// release(inputAudioBlock); -// } -// return; - // OUTPUT PROCESSING audio_block_t *blockToOutput = nullptr; blockToOutput = allocate(); @@ -110,7 +118,7 @@ void AudioEffectAnalogDelay::update(void) if (!blockToOutput) return; // skip this time due to failure // copy over data m_memory->getSamples(blockToOutput, m_delaySamples); - // perform the mix + // perform the wet/dry mix mix m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); transmit(blockToOutput); @@ -177,11 +185,11 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) return; } - if ((m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] == control)) { - // Enable - if (value >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON") + value); } - else { disable(); Serial.println(String("AudioEffectAnalogDelay::enable: OFF") + value); } + if ((m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] == control)) { + // Bypass + if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); } + else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); } return; } @@ -208,10 +216,10 @@ void AudioEffectAnalogDelay::mapMidiDelay(int control, int channel) m_midiConfig[MIDI_DELAY][MIDI_CONTROL] = control; } -void AudioEffectAnalogDelay::mapMidiEnable(int control, int channel) +void AudioEffectAnalogDelay::mapMidiBypass(int control, int channel) { - m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] = control; + m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] = channel; + m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] = control; } void AudioEffectAnalogDelay::mapMidiFeedback(int control, int channel) @@ -239,6 +247,8 @@ void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t * void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) { if ( out && dry && wet) { + // Simulate the LPF IIR nature of the analog systems + m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); alphaBlend(out, dry, wet, m_mix); } else if (dry) { memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index b354dc3..b4220fa 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -8,8 +8,6 @@ #ifndef SRC_AUDIOEFFECTANALOGDELAY_H_ #define SRC_AUDIOEFFECTANALOGDELAY_H_ -//#include - #include #include "LibBasicFunctions.h" @@ -17,8 +15,6 @@ namespace BAGuitar { class AudioEffectAnalogDelay : public AudioStream { public: - static constexpr int MAX_DELAY_CHANNELS = 8; - AudioEffectAnalogDelay() = delete; AudioEffectAnalogDelay(float maxDelay); AudioEffectAnalogDelay(size_t numSamples); @@ -28,35 +24,35 @@ public: virtual void update(void); void delay(float milliseconds); void delay(size_t delaySamples); + + void bypass(bool byp) { m_bypass = byp; } void feedback(float feedback) { m_feedback = feedback; } void mix(float mix) { m_mix = mix; } void enable() { m_enable = true; } void disable() { m_enable = false; } void processMidi(int channel, int control, int value); - void mapMidiEnable(int control, int channel = 0); + void mapMidiBypass(int control, int channel = 0); void mapMidiDelay(int control, int channel = 0); void mapMidiFeedback(int control, int channel = 0); void mapMidiMix(int control, int channel = 0); private: audio_block_t *m_inputQueueArray[1]; + bool m_bypass = true; bool m_enable = false; bool m_externalMemory = false; AudioDelay *m_memory = nullptr; size_t m_maxDelaySamples = 0; + audio_block_t *m_previousBlock = nullptr; + IirBiQuadFilterHQ *m_iir = nullptr; // Controls int m_midiConfig[4][2]; - //int m_midiEnable[2] = {0,16}; size_t m_delaySamples = 0; - //int m_midiDelay[2] = {0,20}; float m_feedback = 0.0f; - //int m_midiFeedback[2] = {0,21}; float m_mix = 0.0f; - //int m_midiMix[2] = {0,22}; - audio_block_t *m_previousBlock = nullptr; void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 67debd6..305ff20 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -34,9 +34,21 @@ size_t calcOffset(QueuePosition position) void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) { - for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { - out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * 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) @@ -45,6 +57,9 @@ void clearAudioBlock(audio_block_t *block) } +//////////////////////////////////////////////////// +// AudioDelay +//////////////////////////////////////////////////// AudioDelay::AudioDelay(size_t maxSamples) : m_slot(nullptr) { @@ -210,5 +225,123 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } +//////////////////////////////////////////////////// +// 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 (int i=0; i #include +#include #include "Arduino.h" #include "Audio.h" @@ -153,6 +154,65 @@ private: ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided. }; +/**************************************************************************//** + * IIR BiQuad Filter - Direct Form I
+ * y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]
+ * Some design tools (like Matlab assume the feedback coefficients 'a' are negated. You + * may have to negate your 'a' coefficients. + * @details Note that the ARM CMSIS-DSP library requires an extra zero between first + * and second 'b' coefficients. E.g.
+ * {b10, 0, b11, b12, a11, a12, b20, 0, b21, b22, a21, a22, ...} + *****************************************************************************/ +class IirBiQuadFilter { +public: + IirBiQuadFilter() = delete; + IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + virtual ~IirBiQuadFilter(); + bool process(int16_t *output, int16_t *input, size_t numSamples); +private: + const unsigned NUM_STAGES; + int32_t *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_casd_df1_inst_q31 m_iirCfg; + int32_t *m_state = nullptr; +}; + + +class IirBiQuadFilterHQ { +public: + IirBiQuadFilterHQ() = delete; + IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + virtual ~IirBiQuadFilterHQ(); + bool process(int16_t *output, int16_t *input, size_t numSamples); +private: + + + const unsigned NUM_STAGES; + int32_t *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_cas_df1_32x64_ins_q31 m_iirCfg; + int64_t *m_state = nullptr; + +}; + +class IirBiQuadFilterFloat { +public: + IirBiQuadFilterFloat() = delete; + IirBiQuadFilterFloat(unsigned numStages, const float *coeffs); + virtual ~IirBiQuadFilterFloat(); + bool process(float *output, float *input, size_t numSamples); +private: + const unsigned NUM_STAGES; + float *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_cascade_df2T_instance_f32 m_iirCfg; + float *m_state = nullptr; + +}; + /**************************************************************************//** * Customer RingBuffer with random access *****************************************************************************/ From b86c583fb5a8046481b8f70a6fcdaf8729782d99 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Thu, 8 Feb 2018 21:43:28 -0500 Subject: [PATCH 16/27] development checking --- src/BASpiMemory.cpp | 161 ++++++++++++++++++++++++++ src/BASpiMemory.h | 69 +++++++++-- src/BATypes.h | 142 +++++++++++++++++++++++ src/LibBasicFunctions.h | 251 ++++++++++++++++++++-------------------- 4 files changed, 489 insertions(+), 134 deletions(-) create mode 100644 src/BATypes.h diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index 9ad8410..5f7a2fa 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -36,6 +36,7 @@ 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; @@ -128,6 +129,7 @@ void BASpiMemory::write(size_t address, uint8_t *data, size_t numBytes) digitalWrite(m_csPin, HIGH); } + void BASpiMemory::zero(size_t address, size_t numBytes) { m_spi->beginTransaction(m_settings); @@ -257,4 +259,163 @@ void BASpiMemory::read16(size_t address, uint16_t *dest, size_t numWords) digitalWrite(m_csPin, HIGH); } +///////////////////////////////////////////////////////////////////////////// +// BASpiMemoryDMA +///////////////////////////////////////////////////////////////////////////// +BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes) +: BASpiMemory(memDeviceId), m_bufferSize(bufferSizeBytes) +{ + int cs; + switch (memDeviceId) { + case SpiDeviceId::SPI_DEVICE0 : + cs = SPI_CS_MEM0; + break; + case SpiDeviceId::SPI_DEVICE1 : + cs = SPI_CS_MEM1; + break; + default : + cs = SPI_CS_MEM0; + } + m_cs = new ActiveLowChipSelect(cs, m_settings); + // add 4 bytes to buffer for SPI CMD and 3 bytes of addresse + m_txBuffer = new uint8_t[bufferSizeBytes+4]; + m_rxBuffer = new uint8_t[bufferSizeBytes+4]; +} + +BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t bufferSizeBytes) +: BASpiMemory(memDeviceId, speedHz), m_bufferSize(bufferSizeBytes) +{ + int cs; + switch (memDeviceId) { + case SpiDeviceId::SPI_DEVICE0 : + cs = SPI_CS_MEM0; + break; + case SpiDeviceId::SPI_DEVICE1 : + cs = SPI_CS_MEM1; + break; + } + m_cs = new ActiveLowChipSelect(cs, m_settings); + m_txBuffer = new uint8_t[bufferSizeBytes+4]; + m_rxBuffer = new uint8_t[bufferSizeBytes+4]; +} + +BASpiMemoryDMA::~BASpiMemoryDMA() +{ + delete m_cs; + if (m_txBuffer) delete [] m_txBuffer; + if (m_rxBuffer) delete [] m_rxBuffer; +} + +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; + 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 = &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 *data, size_t numBytes) +{ + while ( m_txTransfer->busy()) {} + uint16_t transferCount = numBytes + 4; + m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); + memcpy(m_txBuffer+4, data, numBytes); + *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); + m_spiDma->registerTransfer(*m_txTransfer); +} + +void BASpiMemoryDMA::zero(size_t address, size_t numBytes) +{ + while ( m_txTransfer->busy()) {} + uint16_t transferCount = numBytes + 4; + m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); + *m_txTransfer = DmaSpi::Transfer(nullptr, transferCount, nullptr, 0, m_cs); + m_spiDma->registerTransfer(*m_txTransfer); +} + +void BASpiMemoryDMA::write16(size_t address, uint16_t *data, size_t numWords) +{ + while ( m_txTransfer->busy()) {} + size_t numBytes = sizeof(uint16_t)*numWords; + uint16_t transferCount = numBytes + 4; + m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); + memcpy(m_txBuffer+4, data, numBytes); + *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); + m_spiDma->registerTransfer(*m_txTransfer); +} + +void BASpiMemoryDMA::zero16(size_t address, size_t numWords) +{ + while ( m_txTransfer->busy()) {} + size_t numBytes = sizeof(uint16_t)*numWords; + uint16_t transferCount = numBytes + 4; + m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); + memset(m_txBuffer+4, 0, numBytes); + *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); + m_spiDma->registerTransfer(*m_txTransfer); +} + +void BASpiMemoryDMA::read(size_t address, uint8_t *data, size_t numBytes) +{ + while ( m_rxTransfer->busy()) {} + uint16_t transferCount = numBytes + 4; + m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); + *m_rxTransfer = DmaSpi::Transfer(nullptr, transferCount, m_rxBuffer, 0, m_cs); + m_spiDma->registerTransfer(*m_rxTransfer); +} + +void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) +{ + while ( m_rxTransfer->busy()) {} + m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); + size_t numBytes = sizeof(uint16_t)*numWords; + uint16_t transferCount = numBytes + 4; + *m_rxTransfer = DmaSpi::Transfer(nullptr, transferCount, m_rxBuffer, 0, m_cs); + m_spiDma->registerTransfer(*m_rxTransfer); +} + +void BASpiMemoryDMA::readBufferContents(size_t bufferOffset, uint8_t *dest, size_t numBytes) +{ + memcpy(dest, m_rxBuffer+4, numBytes); +} + } /* namespace BAGuitar */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index 2f2a765..c0a6149 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -23,7 +23,9 @@ #define __SRC_BASPIMEMORY_H #include +#include +#include "BATypes.h" #include "BAHardware.h" namespace BAGuitar { @@ -47,25 +49,25 @@ public: BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz); virtual ~BASpiMemory(); - void begin(void); + virtual void begin(); /// write a single data word to the specified address /// @param address the address in the SPI RAM to write to /// @param data the value to write void write(size_t address, uint8_t data); - void write(size_t address, uint8_t *data, size_t numBytes); - void zero(size_t address, size_t numBytes); + virtual void write(size_t address, uint8_t *data, size_t numBytes); + virtual void zero(size_t address, size_t numBytes); void write16(size_t address, uint16_t data); - void write16(size_t address, uint16_t *data, size_t numWords); + virtual void write16(size_t address, uint16_t *data, size_t numWords); - void zero16(size_t address, size_t numWords); + virtual void zero16(size_t address, size_t numWords); /// read a single 8-bit data word from the specified address /// @param address the address in the SPI RAM to read from /// @return the data that was read uint8_t read(size_t address); - void read(size_t address, uint8_t *data, size_t numBytes); + virtual void read(size_t address, uint8_t *data, size_t numBytes); /// read a single 16-bit data word from the specified address /// @param address the address in the SPI RAM to read from @@ -76,11 +78,11 @@ public: /// @param address the address in the SPI RAM to read from /// @param dest the pointer to the destination /// @param numWords the number of 16-bit words to transfer - void read16(size_t address, uint16_t *dest, size_t numWords); + virtual void read16(size_t address, uint16_t *dest, size_t numWords); bool isStarted() const { return m_started; } -private: +protected: SPIClass *m_spi = nullptr; SpiDeviceId m_memDeviceId; // the MEM device being control with this instance uint8_t m_csPin; // the IO pin number for the CS on the controlled SPI device @@ -89,7 +91,56 @@ private: }; -class BASpiMemoryException {}; +//constexpr int MAX_DMA_XFERS = 4; + +class BASpiMemoryDMA : public BASpiMemory { + BASpiMemoryDMA() = delete; + /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2). + /// @details default is 20 Mhz + /// @param memDeviceId specify which MEM to control with SpiDeviceId. + /// @param bufferSize size of buffer to store DMA transfers + BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes); + /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2) + /// @param memDeviceId specify which MEM to control with SpiDeviceId. + /// @param speedHz specify the desired speed in Hz. + /// @param bufferSize size of buffer to store DMA transfers + BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t bufferSizeBytes); + virtual ~BASpiMemoryDMA(); + + void write(size_t address, uint8_t *data, size_t numBytes) override; + void zero(size_t address, size_t numBytes) override; + void write16(size_t address, uint16_t *data, size_t numWords) override; + void zero16(size_t address, size_t numWords) override; + + /// read a single 8-bit data word from the specified address + /// @param address the address in the SPI RAM to read from + /// @return the data that was read + void read(size_t address, uint8_t *data, size_t numBytes) override; + + /// read a block 16-bit data word from the specified address + /// @param address the address in the SPI RAM to read from + /// @param dest the pointer to the destination + /// @param numWords the number of 16-bit words to transfer + void read16(size_t address, uint16_t *dest, size_t numWords) override; + + + void begin() override; + void readBufferContents(size_t bufferOffset, uint8_t *dest, size_t numBytes); + +private: + AbstractDmaSpi *m_spiDma = nullptr; + ActiveLowChipSelect *m_cs = nullptr; + size_t m_bufferSize; + uint8_t *m_txBuffer = nullptr; + DmaSpi::Transfer *m_txTransfer; + uint8_t *m_rxBuffer = nullptr; + DmaSpi::Transfer *m_rxTransfer; + + void m_setSpiCmdAddr(int command, size_t address, uint8_t *dest); +// RingBuffer m_txFifo(MAX_DMA_XFERS); +// RingBuffer m_rxFifo(MAX_DMA_XFERS); +}; + } /* namespace BAGuitar */ diff --git a/src/BATypes.h b/src/BATypes.h new file mode 100644 index 0000000..2c2bbf9 --- /dev/null +++ b/src/BATypes.h @@ -0,0 +1,142 @@ +/* + * BATypes.h + * + * Created on: Feb 7, 2018 + * Author: slascos + */ + +#ifndef SRC_BATYPES_H_ +#define SRC_BATYPES_H_ + +namespace BAGuitar { + +/**************************************************************************//** + * Customer RingBuffer with random access + *****************************************************************************/ +template +class RingBuffer { +public: + RingBuffer() = delete; + + /// Construct a RingBuffer of specified max size + /// @param maxSize number of entries in ring buffer + RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { + m_buffer = new T[maxSize](); + } + virtual ~RingBuffer(){ + if (m_buffer) delete [] m_buffer; + } + + /// Add an element to the back of the queue + /// @param element element to add to queue + /// returns 0 if success, otherwise error + int push_back(T element) { + + //Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); + if ( (m_head == m_tail) && (m_size > 0) ) { + // overflow + Serial.println("RingBuffer::push_back: overflow"); + return -1; + } + + m_buffer[m_head] = element; + if (m_head < (m_maxSize-1) ) { + m_head++; + } else { + m_head = 0; + } + m_size++; + + return 0; + } + + /// Remove the element at teh front of the queue + /// @returns 0 if success, otherwise error + int pop_front() { + + if (m_size == 0) { + // buffer is empty + //Serial.println("RingBuffer::pop_front: buffer is empty\n"); + return -1; + } + if (m_tail < m_maxSize-1) { + m_tail++; + } else { + m_tail = 0; + } + m_size--; + //Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); + return 0; + } + + /// Get the element at the front of the queue + /// @returns element at front of queue + T front() const { + return m_buffer[m_tail]; + } + + /// get the element at the back of the queue + /// @returns element at the back of the queue + T back() const { + return m_buffer[m_head-1]; + } + + /// Get a previously pushed elememt + /// @param offset zero is last pushed, 1 is second last, etc. + /// @returns the absolute index corresponding to the requested offset. + size_t get_index_from_back(size_t offset = 0) const { + // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; + size_t idx = (m_maxSize + m_head -1 - offset); + + if ( idx >= m_maxSize) { + idx -= m_maxSize; + } + + return idx; + } + + /// get the current size of the queue + /// @returns size of the queue + size_t size() const { + return m_size; + } + + /// get the maximum size the queue can hold + /// @returns maximum size of the queue + size_t max_size() const { + return m_maxSize; + } + + /// get the element at the specified absolute index + /// @param index element to retrieve from absolute queue position + /// @returns the request element + T& operator[] (size_t index) { + return m_buffer[index]; + } + + /// get the element at the specified absolute index + /// @param index element to retrieve from absolute queue position + /// @returns the request element + T at(size_t index) const { + return m_buffer[index]; + } + + /// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches + void print() const { + for (int idx=0; idxdata, HEX); + } + } +private: + size_t m_head=0; ///< back of the queue + size_t m_tail=0; ///< front of the queue + size_t m_size=0; ///< current size of the qeueu + T *m_buffer = nullptr; ///< pointer to the allocated buffer array + const size_t m_maxSize; ///< maximum size of the queue +}; + +} // BAGuitar + + +#endif /* SRC_BATYPES_H_ */ diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 2498155..757ce4d 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -27,6 +27,7 @@ #include "Arduino.h" #include "Audio.h" +#include "BATypes.h" #include "LibMemoryManagement.h" #ifndef __LIBBASICFUNCTIONS_H @@ -213,131 +214,131 @@ private: }; -/**************************************************************************//** - * Customer RingBuffer with random access - *****************************************************************************/ -template -class RingBuffer { -public: - RingBuffer() = delete; - - /// Construct a RingBuffer of specified max size - /// @param maxSize number of entries in ring buffer - RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { - m_buffer = new T[maxSize](); - } - virtual ~RingBuffer(){ - if (m_buffer) delete [] m_buffer; - } - - /// Add an element to the back of the queue - /// @param element element to add to queue - /// returns 0 if success, otherwise error - int push_back(T element) { - - //Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); - if ( (m_head == m_tail) && (m_size > 0) ) { - // overflow - Serial.println("RingBuffer::push_back: overflow"); - return -1; - } - - m_buffer[m_head] = element; - if (m_head < (m_maxSize-1) ) { - m_head++; - } else { - m_head = 0; - } - m_size++; - - return 0; - } - - /// Remove the element at teh front of the queue - /// @returns 0 if success, otherwise error - int pop_front() { - - if (m_size == 0) { - // buffer is empty - //Serial.println("RingBuffer::pop_front: buffer is empty\n"); - return -1; - } - if (m_tail < m_maxSize-1) { - m_tail++; - } else { - m_tail = 0; - } - m_size--; - //Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); - return 0; - } - - /// Get the element at the front of the queue - /// @returns element at front of queue - T front() const { - return m_buffer[m_tail]; - } - - /// get the element at the back of the queue - /// @returns element at the back of the queue - T back() const { - return m_buffer[m_head-1]; - } - - /// Get a previously pushed elememt - /// @param offset zero is last pushed, 1 is second last, etc. - /// @returns the absolute index corresponding to the requested offset. - size_t get_index_from_back(size_t offset = 0) const { - // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; - size_t idx = (m_maxSize + m_head -1 - offset); - - if ( idx >= m_maxSize) { - idx -= m_maxSize; - } - - return idx; - } - - /// get the current size of the queue - /// @returns size of the queue - size_t size() const { - return m_size; - } - - /// get the maximum size the queue can hold - /// @returns maximum size of the queue - size_t max_size() const { - return m_maxSize; - } - - /// get the element at the specified absolute index - /// @param index element to retrieve from absolute queue position - /// @returns the request element - T& operator[] (size_t index) { - return m_buffer[index]; - } - - /// get the element at the specified absolute index - /// @param index element to retrieve from absolute queue position - /// @returns the request element - T at(size_t index) const { - return m_buffer[index]; - } - - /// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches - void print() const { - for (int idx=0; idxdata, HEX); - } - } -private: - size_t m_head=0; ///< back of the queue - size_t m_tail=0; ///< front of the queue - size_t m_size=0; ///< current size of the qeueu - T *m_buffer = nullptr; ///< pointer to the allocated buffer array - const size_t m_maxSize; ///< maximum size of the queue -}; +///**************************************************************************//** +// * Customer RingBuffer with random access +// *****************************************************************************/ +//template +//class RingBuffer { +//public: +// RingBuffer() = delete; +// +// /// Construct a RingBuffer of specified max size +// /// @param maxSize number of entries in ring buffer +// RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { +// m_buffer = new T[maxSize](); +// } +// virtual ~RingBuffer(){ +// if (m_buffer) delete [] m_buffer; +// } +// +// /// Add an element to the back of the queue +// /// @param element element to add to queue +// /// returns 0 if success, otherwise error +// int push_back(T element) { +// +// //Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); +// if ( (m_head == m_tail) && (m_size > 0) ) { +// // overflow +// Serial.println("RingBuffer::push_back: overflow"); +// return -1; +// } +// +// m_buffer[m_head] = element; +// if (m_head < (m_maxSize-1) ) { +// m_head++; +// } else { +// m_head = 0; +// } +// m_size++; +// +// return 0; +// } +// +// /// Remove the element at teh front of the queue +// /// @returns 0 if success, otherwise error +// int pop_front() { +// +// if (m_size == 0) { +// // buffer is empty +// //Serial.println("RingBuffer::pop_front: buffer is empty\n"); +// return -1; +// } +// if (m_tail < m_maxSize-1) { +// m_tail++; +// } else { +// m_tail = 0; +// } +// m_size--; +// //Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); +// return 0; +// } +// +// /// Get the element at the front of the queue +// /// @returns element at front of queue +// T front() const { +// return m_buffer[m_tail]; +// } +// +// /// get the element at the back of the queue +// /// @returns element at the back of the queue +// T back() const { +// return m_buffer[m_head-1]; +// } +// +// /// Get a previously pushed elememt +// /// @param offset zero is last pushed, 1 is second last, etc. +// /// @returns the absolute index corresponding to the requested offset. +// size_t get_index_from_back(size_t offset = 0) const { +// // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; +// size_t idx = (m_maxSize + m_head -1 - offset); +// +// if ( idx >= m_maxSize) { +// idx -= m_maxSize; +// } +// +// return idx; +// } +// +// /// get the current size of the queue +// /// @returns size of the queue +// size_t size() const { +// return m_size; +// } +// +// /// get the maximum size the queue can hold +// /// @returns maximum size of the queue +// size_t max_size() const { +// return m_maxSize; +// } +// +// /// get the element at the specified absolute index +// /// @param index element to retrieve from absolute queue position +// /// @returns the request element +// T& operator[] (size_t index) { +// return m_buffer[index]; +// } +// +// /// get the element at the specified absolute index +// /// @param index element to retrieve from absolute queue position +// /// @returns the request element +// T at(size_t index) const { +// return m_buffer[index]; +// } +// +// /// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches +// void print() const { +// for (int idx=0; idxdata, HEX); +// } +// } +//private: +// size_t m_head=0; ///< back of the queue +// size_t m_tail=0; ///< front of the queue +// size_t m_size=0; ///< current size of the qeueu +// T *m_buffer = nullptr; ///< pointer to the allocated buffer array +// const size_t m_maxSize; ///< maximum size of the queue +//}; } From 19baaa34bea7c8b6ac9b6c31e0940a581f7c891f Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Fri, 9 Feb 2018 16:13:03 -0500 Subject: [PATCH 17/27] More code development --- src/AudioEffectAnalogDelay.cpp | 34 ++++++++++++++++++++++++----- src/BASpiMemory.cpp | 13 ++++++----- src/BASpiMemory.h | 6 ++--- src/LibBasicFunctions.cpp | 32 ++++++++++++++++++--------- src/LibBasicFunctions.h | 4 +++- src/LibMemoryManagement.cpp | 40 ++++++++++++++++++++++++++++++---- src/LibMemoryManagement.h | 16 ++++++++++++-- 7 files changed, 113 insertions(+), 32 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index d074afc..df493f8 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -102,22 +102,44 @@ void AudioEffectAnalogDelay::update(void) } // Otherwise perform normal processing + // In order to make use of the SPI DMA, we need to request the read from memory first, + // then do other processing while it fills in the back. + audio_block_t *blockToOutput = nullptr; // this will hold the output audio + blockToOutput = allocate(); + if (!blockToOutput) return; // skip this update cycle due to failure + + // get the data. If using external memory with DMA, this won't be filled until + // later. + m_memory->getSamples(blockToOutput, m_delaySamples); + + // If using DMA, we need something else to do while that read executes, so + // move on to input preprocessing + // Preprocessing audio_block_t *preProcessed = allocate(); // mix the input with the feedback path in the pre-processing stage m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); + // consider doing the BBD post processing here to use up more time while waiting + // for the read data to come back audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); if (blockToRelease) release(blockToRelease); - // OUTPUT PROCESSING - audio_block_t *blockToOutput = nullptr; - blockToOutput = allocate(); + // 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); +// 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 copy out of the DMA buffer + if (m_externalMemory && m_memory->getSlot()->isUseDma()) { + // Using DMA + m_memory->readDmaBufferContents(blockToOutput); + } + // perform the wet/dry mix mix m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); transmit(blockToOutput); diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index 5f7a2fa..c130e49 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -355,8 +355,8 @@ void BASpiMemoryDMA::begin(void) // are done before continuing. void BASpiMemoryDMA::write(size_t address, uint8_t *data, size_t numBytes) { - while ( m_txTransfer->busy()) {} - uint16_t transferCount = numBytes + 4; + while ( m_txTransfer->busy()) {} // wait until not busy + uint16_t transferCount = numBytes + 4; // transfer must be increased by the SPI command and address m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); memcpy(m_txBuffer+4, data, numBytes); *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); @@ -394,8 +394,9 @@ void BASpiMemoryDMA::zero16(size_t address, size_t numWords) m_spiDma->registerTransfer(*m_txTransfer); } -void BASpiMemoryDMA::read(size_t address, uint8_t *data, size_t numBytes) +void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) { + UNUSED(dest) while ( m_rxTransfer->busy()) {} uint16_t transferCount = numBytes + 4; m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); @@ -405,6 +406,7 @@ void BASpiMemoryDMA::read(size_t address, uint8_t *data, size_t numBytes) void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) { + UNUSED(dest) while ( m_rxTransfer->busy()) {} m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); size_t numBytes = sizeof(uint16_t)*numWords; @@ -413,9 +415,10 @@ void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) m_spiDma->registerTransfer(*m_rxTransfer); } -void BASpiMemoryDMA::readBufferContents(size_t bufferOffset, uint8_t *dest, size_t numBytes) +void BASpiMemoryDMA::readBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset) { - memcpy(dest, m_rxBuffer+4, numBytes); + while (m_rxTransfer->busy()) {} // ensure transfer is complete + memcpy(dest, m_rxBuffer+4+bufferOffset, numBytes); } } /* namespace BAGuitar */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index c0a6149..ed9c375 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -115,7 +115,7 @@ class BASpiMemoryDMA : public BASpiMemory { /// read a single 8-bit data word from the specified address /// @param address the address in the SPI RAM to read from /// @return the data that was read - void read(size_t address, uint8_t *data, size_t numBytes) override; + void read(size_t address, uint8_t *dest, size_t numBytes) override; /// read a block 16-bit data word from the specified address /// @param address the address in the SPI RAM to read from @@ -125,7 +125,7 @@ class BASpiMemoryDMA : public BASpiMemory { void begin() override; - void readBufferContents(size_t bufferOffset, uint8_t *dest, size_t numBytes); + void readBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset = 0); private: AbstractDmaSpi *m_spiDma = nullptr; @@ -137,8 +137,6 @@ private: DmaSpi::Transfer *m_rxTransfer; void m_setSpiCmdAddr(int command, size_t address, uint8_t *dest); -// RingBuffer m_txFifo(MAX_DMA_XFERS); -// RingBuffer m_rxFifo(MAX_DMA_XFERS); }; diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 305ff20..19c581a 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -119,11 +119,14 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block) // srcPtr--; // } - int16_t *srcPtr = block->data; - for (int i=0; iwriteAdvance16(*srcPtr); - srcPtr++; - } + 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; @@ -210,11 +213,15 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample // destPtr--; // } - int16_t *destPtr = dest->data; - for (int i=0; ireadAdvance16(); - destPtr++; - } + m_slot->readAdvance16(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 @@ -225,6 +232,11 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } +void AudioDelay::readDmaBufferContents(audio_block_t *dest, size_t numSamples, size_t bufferOffset) +{ + m_slot->readDmaBufferContents(reinterpret_cast(dest->data), sizeof(int16_t)*numSamples, sizeof(int16_t)*bufferOffset); +} + //////////////////////////////////////////////////// // IirBiQuadFilter //////////////////////////////////////////////////// diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 757ce4d..63b5a23 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -109,7 +109,7 @@ public: /// Construct an audio buffer using a slot configured with the BAGuitar::ExternalSramManager /// @param slot a pointer to the slot representing the memory you wish to use for the buffer. - AudioDelay(ExtMemSlot *slot); + AudioDelay(ExtMemSlot *slot, bool useDma=true); ~AudioDelay(); @@ -140,6 +140,8 @@ public: /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } + void readDmaBufferContents(audio_block_t *dest, size_t numSamples = AUDIO_BLOCK_SAMPLES, size_t bufferOffset = 0); + RingBuffer *getRingBuffer() const { return m_ringBuffer; } private: diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 9927121..592bfc9 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -108,6 +108,28 @@ uint16_t ExtMemSlot::readAdvance16() return val; } +bool ExtMemSlot::readAdvance16(int16_t *dest=nullptr, size_t numWords) +{ + if (!m_valid) { return false; } + size_t numBytes = sizeof(int16_t)*numWords; + + if (m_currentRdPosition + numBytes-1 <= m_end) { + // entire block fits in memory slot without wrapping + m_spi->read16(m_currentWrPosition, reinterpret_cast(dest), numWords); // cast audio data to uint. + m_currentRdPosition += numBytes; + + } else { + // this read will wrap the memory slot + size_t rdBytes = m_end - m_currentRdPosition + 1; + size_t rdDataNum = rdBytes >> 1; // divide by two to get the number of data + m_spi->read16(m_currentRdPosition, reinterpret_cast(dest), rdDataNum); + size_t remainingData = numWords - rdDataNum; + m_spi->read16(m_start, reinterpret_cast(dest + rdDataNum), remainingData); // write remaining bytes are start + m_currentRdPosition = m_start + (remainingData*sizeof(int16_t)); + } + return true; +} + bool ExtMemSlot::writeAdvance16(int16_t *src, size_t numWords) { @@ -153,6 +175,11 @@ bool ExtMemSlot::zeroAdvance16(size_t numWords) return true; } +void ExtMemSlot::readDmaBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset = 0) +{ + m_spi->readBufferContents(dest, numBytes, bufferOffset); +} + bool ExtMemSlot::writeAdvance16(int16_t data) { @@ -229,14 +256,14 @@ size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem) return m_memConfig[mem].totalAvailable; } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem) +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); + return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, useDma); } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, bool useDma) { if (m_memConfig[mem].totalAvailable >= sizeBytes) { @@ -249,7 +276,11 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu slot->m_size = sizeBytes; if (!m_memConfig[mem].m_spi) { - m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast(mem)); + if (useDma) { + m_memConfig[mem].m_spi = new BAGuitar::BASpiMemoryDma(static_cast(mem)); + } else { + m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast(mem)); + } if (!m_memConfig[mem].m_spi) { Serial.println("requestMemory: new failed! m_spi is a nullptr"); } else { @@ -264,6 +295,7 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu slot->m_valid = true; if (!slot->isEnabled()) { slot->enable(); } slot->clear(); + slot->m_useDma = useDma; return true; } else { // there is not enough memory available for the request diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index e4e44c8..18b507e 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -104,6 +104,14 @@ public: /// @returns the next 16-bit data word in memory uint16_t readAdvance16(); + + /// Read the next block of numWords during circular operation + /// @details, dest is ignored when using DMA + /// @param dest pointer to the destination of the read. + /// @param numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + bool readAdvance16(int16_t *dest=nullptr, size_t numWords); + /// Write a block of 16-bit data from the specified location in circular operation /// @param src pointer to the start of the block of data to write to memory /// @param numWords number of 16-bit words to transfer @@ -120,6 +128,7 @@ public: /// @returns true on success, else false on error bool zeroAdvance16(size_t numWords); + void readDmaBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset = 0); /// Get the size of the memory slot /// @returns size of the slot in bytes @@ -133,6 +142,8 @@ public: /// @returns true if enabled, false if not enabled bool isEnabled() const; + bool isUseDma() const { return m_useDma; } + /// DEBUG USE: prints out the slot member variables void printStatus(void) const; @@ -144,6 +155,7 @@ private: size_t m_currentWrPosition = 0; ///< current write pointer for circular operation size_t m_currentRdPosition = 0; ///< current read pointer for circular operation size_t m_size = 0; ///< size of this slot in bytes + bool m_useDma = false; ///< when TRUE, BASpiMemoryDMA will be used. SpiDeviceId m_spiId; ///< the SPI Device ID BASpiMemory *m_spi = nullptr; ///< pointer to an instance of the BASpiMemory interface class }; @@ -175,14 +187,14 @@ public: /// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes. /// @param mem specify which external memory to allocate from /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = true); /// Request memory be allocated for the provided slot /// @param slot a pointer to the global slot object to which memory will be allocated /// @param sizeBytes request the amount of memory in bytes to request /// @param mem specify which external memory to allocate from /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = true); private: static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project From c5383595c19ed5868940f003beefb1364a56207d Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 10 Feb 2018 09:03:55 -0500 Subject: [PATCH 18/27] development check in, now compiles --- src/BASpiMemory.cpp | 6 ++++-- src/BASpiMemory.h | 1 + src/BATypes.h | 2 ++ src/LibBasicFunctions.cpp | 10 +++++----- src/LibBasicFunctions.h | 2 +- src/LibMemoryManagement.cpp | 24 +++++++++++++++--------- src/LibMemoryManagement.h | 8 +++++--- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index c130e49..0d9d1fd 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -293,6 +293,8 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t case SpiDeviceId::SPI_DEVICE1 : cs = SPI_CS_MEM1; break; + default : + cs = SPI_CS_MEM0; } m_cs = new ActiveLowChipSelect(cs, m_settings); m_txBuffer = new uint8_t[bufferSizeBytes+4]; @@ -396,7 +398,7 @@ void BASpiMemoryDMA::zero16(size_t address, size_t numWords) void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) { - UNUSED(dest) + UNUSED(dest); while ( m_rxTransfer->busy()) {} uint16_t transferCount = numBytes + 4; m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); @@ -406,7 +408,7 @@ void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) { - UNUSED(dest) + UNUSED(dest); while ( m_rxTransfer->busy()) {} m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); size_t numBytes = sizeof(uint16_t)*numWords; diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index ed9c375..fa7e3d1 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -94,6 +94,7 @@ protected: //constexpr int MAX_DMA_XFERS = 4; class BASpiMemoryDMA : public BASpiMemory { +public: BASpiMemoryDMA() = delete; /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2). /// @details default is 20 Mhz diff --git a/src/BATypes.h b/src/BATypes.h index 2c2bbf9..0177c67 100644 --- a/src/BATypes.h +++ b/src/BATypes.h @@ -10,6 +10,8 @@ namespace BAGuitar { +#define UNUSED(x) (void)(x) + /**************************************************************************//** * Customer RingBuffer with random access *****************************************************************************/ diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 19c581a..4170b92 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -213,7 +213,7 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample // destPtr--; // } - m_slot->readAdvance16(AUDIO_BLOCK_SAMPLES); + m_slot->readAdvance16(dest->data, AUDIO_BLOCK_SAMPLES); // // Code below worked // int16_t *destPtr = dest->data; @@ -268,13 +268,13 @@ bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples // create convertion buffers on teh stack int32_t input32[numSamples]; int32_t output32[numSamples]; - for (int i=0; ireadBufferContents(dest, numBytes, bufferOffset); + if (m_useDma) { + (static_cast(m_spi))->readBufferContents(dest, numBytes, bufferOffset); +// BASpiMemoryDMA *spi = nullptr; +// spi = static_cast(m_spi); +// spi->readBufferContents(dest, numBytes, bufferOffset); + } } @@ -256,14 +261,14 @@ size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem) return m_memConfig[mem].totalAvailable; } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem, bool useDma) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem, size_t dmaBufferSize) { // 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); + return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, dmaBufferSize); } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, bool useDma) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, size_t dmaBufferSize) { if (m_memConfig[mem].totalAvailable >= sizeBytes) { @@ -276,10 +281,12 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu slot->m_size = sizeBytes; if (!m_memConfig[mem].m_spi) { - if (useDma) { - m_memConfig[mem].m_spi = new BAGuitar::BASpiMemoryDma(static_cast(mem)); + if (dmaBufferSize > 0) { + m_memConfig[mem].m_spi = new BAGuitar::BASpiMemoryDMA(static_cast(mem), dmaBufferSize); + 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) { Serial.println("requestMemory: new failed! m_spi is a nullptr"); @@ -295,7 +302,6 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu slot->m_valid = true; if (!slot->isEnabled()) { slot->enable(); } slot->clear(); - slot->m_useDma = useDma; return true; } else { // there is not enough memory available for the request diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 18b507e..fe9948c 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -110,7 +110,7 @@ public: /// @param dest pointer to the destination of the read. /// @param numWords number of 16-bit words to transfer /// @returns true on success, else false on error - bool readAdvance16(int16_t *dest=nullptr, size_t numWords); + bool readAdvance16(int16_t *dest, size_t numWords); /// Write a block of 16-bit data from the specified location in circular operation /// @param src pointer to the start of the block of data to write to memory @@ -186,15 +186,17 @@ public: /// @param slot a pointer to the global slot object to which memory will be allocated /// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes. /// @param mem specify which external memory to allocate from + /// @param dmaBufferSize When > 0, DMA mode is used with the specified DMA buffer size /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = true); + bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, size_t dmaBufferSize = 0); /// Request memory be allocated for the provided slot /// @param slot a pointer to the global slot object to which memory will be allocated /// @param sizeBytes request the amount of memory in bytes to request /// @param mem specify which external memory to allocate from + /// @param dmaBufferSize When > 0, DMA mode is used with the specified DMA buffer size /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = true); + bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, size_t dmaBufferSize = 0); private: static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project From d3aa6667ceb2cc6241e739fcb455ac74fa68a144 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 10 Feb 2018 12:41:16 -0500 Subject: [PATCH 19/27] Working check in of BASpiMemoryDMA for SPI0 --- src/BASpiMemory.cpp | 52 ++++++++++++++++++-------- src/BASpiMemory.h | 91 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 116 insertions(+), 27 deletions(-) diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index 0d9d1fd..e970940 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -111,9 +111,9 @@ void BASpiMemory::write(size_t address, uint8_t data) } // Single address write -void BASpiMemory::write(size_t address, uint8_t *data, size_t numBytes) +void BASpiMemory::write(size_t address, uint8_t *src, size_t numBytes) { - uint8_t *dataPtr = data; + uint8_t *dataPtr = src; m_spi->beginTransaction(m_settings); digitalWrite(m_csPin, LOW); @@ -157,9 +157,9 @@ void BASpiMemory::write16(size_t address, uint16_t data) digitalWrite(m_csPin, HIGH); } -void BASpiMemory::write16(size_t address, uint16_t *data, size_t numWords) +void BASpiMemory::write16(size_t address, uint16_t *src, size_t numWords) { - uint16_t *dataPtr = data; + uint16_t *dataPtr = src; m_spi->beginTransaction(m_settings); digitalWrite(m_csPin, LOW); @@ -208,9 +208,9 @@ uint8_t BASpiMemory::read(size_t address) } -void BASpiMemory::read(size_t address, uint8_t *data, size_t numBytes) +void BASpiMemory::read(size_t address, uint8_t *dest, size_t numBytes) { - uint8_t *dataPtr = data; + uint8_t *dataPtr = dest; m_spi->beginTransaction(m_settings); digitalWrite(m_csPin, LOW); @@ -280,6 +280,8 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes) // add 4 bytes to buffer for SPI CMD and 3 bytes of addresse m_txBuffer = new uint8_t[bufferSizeBytes+4]; m_rxBuffer = new uint8_t[bufferSizeBytes+4]; + m_txTransfer = new DmaSpi::Transfer(); + m_rxTransfer = new DmaSpi::Transfer(); } BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t bufferSizeBytes) @@ -299,6 +301,8 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t m_cs = new ActiveLowChipSelect(cs, m_settings); m_txBuffer = new uint8_t[bufferSizeBytes+4]; m_rxBuffer = new uint8_t[bufferSizeBytes+4]; + m_txTransfer = new DmaSpi::Transfer(); + m_rxTransfer = new DmaSpi::Transfer(); } BASpiMemoryDMA::~BASpiMemoryDMA() @@ -306,6 +310,8 @@ BASpiMemoryDMA::~BASpiMemoryDMA() delete m_cs; if (m_txBuffer) delete [] m_txBuffer; if (m_rxBuffer) delete [] m_rxBuffer; + if (m_txTransfer) delete m_txTransfer; + if (m_rxTransfer) delete m_rxTransfer; } void BASpiMemoryDMA::m_setSpiCmdAddr(int command, size_t address, uint8_t *dest) @@ -355,12 +361,12 @@ void BASpiMemoryDMA::begin(void) // 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 *data, size_t numBytes) +void BASpiMemoryDMA::write(size_t address, uint8_t *src, size_t numBytes) { while ( m_txTransfer->busy()) {} // wait until not busy uint16_t transferCount = numBytes + 4; // transfer must be increased by the SPI command and address m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - memcpy(m_txBuffer+4, data, numBytes); + memcpy(m_txBuffer+4, src, numBytes); *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); m_spiDma->registerTransfer(*m_txTransfer); } @@ -370,17 +376,18 @@ void BASpiMemoryDMA::zero(size_t address, size_t numBytes) while ( m_txTransfer->busy()) {} uint16_t transferCount = numBytes + 4; m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - *m_txTransfer = DmaSpi::Transfer(nullptr, transferCount, nullptr, 0, m_cs); + memset(m_txBuffer+4, 0, numBytes); + *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); m_spiDma->registerTransfer(*m_txTransfer); } -void BASpiMemoryDMA::write16(size_t address, uint16_t *data, size_t numWords) +void BASpiMemoryDMA::write16(size_t address, uint16_t *src, size_t numWords) { while ( m_txTransfer->busy()) {} size_t numBytes = sizeof(uint16_t)*numWords; uint16_t transferCount = numBytes + 4; m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - memcpy(m_txBuffer+4, data, numBytes); + memcpy(m_txBuffer+4, src, numBytes); *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); m_spiDma->registerTransfer(*m_txTransfer); } @@ -402,7 +409,7 @@ void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) while ( m_rxTransfer->busy()) {} uint16_t transferCount = numBytes + 4; m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); - *m_rxTransfer = DmaSpi::Transfer(nullptr, transferCount, m_rxBuffer, 0, m_cs); + *m_rxTransfer = DmaSpi::Transfer(m_rxBuffer, transferCount, m_rxBuffer, 0, m_cs); m_spiDma->registerTransfer(*m_rxTransfer); } @@ -413,14 +420,29 @@ void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); size_t numBytes = sizeof(uint16_t)*numWords; uint16_t transferCount = numBytes + 4; - *m_rxTransfer = DmaSpi::Transfer(nullptr, transferCount, m_rxBuffer, 0, m_cs); + *m_rxTransfer = DmaSpi::Transfer(m_rxBuffer, transferCount, m_rxBuffer, 0, m_cs); m_spiDma->registerTransfer(*m_rxTransfer); } -void BASpiMemoryDMA::readBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset) +bool BASpiMemoryDMA::isWriteBusy(void) +{ + return m_txTransfer->busy(); +} + +bool BASpiMemoryDMA::isReadBusy(void) +{ + return m_rxTransfer->busy(); +} + +void BASpiMemoryDMA::readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset) +{ + readBufferContents(reinterpret_cast(dest), sizeof(uint16_t)*numWords, sizeof(uint16_t)*wordOffset); +} + +void BASpiMemoryDMA::readBufferContents(uint8_t *dest, size_t numBytes, size_t byteOffset) { while (m_rxTransfer->busy()) {} // ensure transfer is complete - memcpy(dest, m_rxBuffer+4+bufferOffset, numBytes); + memcpy(dest, m_rxBuffer+4+byteOffset, numBytes); } } /* namespace BAGuitar */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index fa7e3d1..5fb58f8 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -49,25 +49,51 @@ public: BASpiMemory(SpiDeviceId memDeviceId, uint32_t speedHz); virtual ~BASpiMemory(); + /// initialize and configure the SPI peripheral virtual void begin(); - /// write a single data word to the specified address + /// write a single 8-bit word to the specified address /// @param address the address in the SPI RAM to write to /// @param data the value to write void write(size_t address, uint8_t data); - virtual void write(size_t address, uint8_t *data, size_t numBytes); + + /// Write a block of 8-bit data to the specified address + /// @param address the address in the SPI RAM to write to + /// @param src pointer to the source data block + /// @param numBytes size of the data block in bytes + virtual void write(size_t address, uint8_t *src, size_t numBytes); + + /// Write a block of zeros to the specified address + /// @param address the address in the SPI RAM to write to + /// @param numBytes size of the data block in bytes virtual void zero(size_t address, size_t numBytes); + /// write a single 16-bit word to the specified address + /// @param address the address in the SPI RAM to write to + /// @param data the value to write void write16(size_t address, uint16_t data); - virtual void write16(size_t address, uint16_t *data, size_t numWords); + /// Write a block of 16-bit data to the specified address + /// @param address the address in the SPI RAM to write to + /// @param src pointer to the source data block + /// @param numWords size of the data block in 16-bit words + virtual void write16(size_t address, uint16_t *src, size_t numWords); + + /// Write a block of 16-bit zeros to the specified address + /// @param address the address in the SPI RAM to write to + /// @param numWords size of the data block in 16-bit words virtual void zero16(size_t address, size_t numWords); /// read a single 8-bit data word from the specified address /// @param address the address in the SPI RAM to read from /// @return the data that was read uint8_t read(size_t address); - virtual void read(size_t address, uint8_t *data, size_t numBytes); + + /// Read a block of 8-bit data from the specified address + /// @param address the address in the SPI RAM to write to + /// @param dest pointer to the destination + /// @param numBytes size of the data block in bytes + virtual void read(size_t address, uint8_t *dest, size_t numBytes); /// read a single 16-bit data word from the specified address /// @param address the address in the SPI RAM to read from @@ -80,6 +106,8 @@ public: /// @param numWords the number of 16-bit words to transfer virtual void read16(size_t address, uint16_t *dest, size_t numWords); + /// Check if the class has been configured by a previous begin() call + /// @returns true if initialized, false if not yet initialized bool isStarted() const { return m_started; } protected: @@ -96,11 +124,13 @@ protected: class BASpiMemoryDMA : public BASpiMemory { public: BASpiMemoryDMA() = delete; + /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2). /// @details default is 20 Mhz /// @param memDeviceId specify which MEM to control with SpiDeviceId. /// @param bufferSize size of buffer to store DMA transfers BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes); + /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2) /// @param memDeviceId specify which MEM to control with SpiDeviceId. /// @param speedHz specify the desired speed in Hz. @@ -108,14 +138,35 @@ public: BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t bufferSizeBytes); virtual ~BASpiMemoryDMA(); - void write(size_t address, uint8_t *data, size_t numBytes) override; + /// initialize and configure the SPI peripheral + void begin() override; + + /// Write a block of 8-bit data to the specified address + /// @param address the address in the SPI RAM to write to + /// @param src pointer to the source data block + /// @param numBytes size of the data block in bytes + void write(size_t address, uint8_t *src, size_t numBytes) override; + + /// Write a block of zeros to the specified address + /// @param address the address in the SPI RAM to write to + /// @param numBytes size of the data block in bytes void zero(size_t address, size_t numBytes) override; - void write16(size_t address, uint16_t *data, size_t numWords) override; + + /// Write a block of 16-bit data to the specified address + /// @param address the address in the SPI RAM to write to + /// @param src pointer to the source data block + /// @param numWords size of the data block in 16-bit words + void write16(size_t address, uint16_t *src, size_t numWords) override; + + /// Write a block of 16-bit zeros to the specified address + /// @param address the address in the SPI RAM to write to + /// @param numWords size of the data block in 16-bit words void zero16(size_t address, size_t numWords) override; - /// read a single 8-bit data word from the specified address - /// @param address the address in the SPI RAM to read from - /// @return the data that was read + /// Read a block of 8-bit data from the specified address + /// @param address the address in the SPI RAM to write to + /// @param dest pointer to the destination + /// @param numBytes size of the data block in bytes void read(size_t address, uint8_t *dest, size_t numBytes) override; /// read a block 16-bit data word from the specified address @@ -124,9 +175,25 @@ public: /// @param numWords the number of 16-bit words to transfer void read16(size_t address, uint16_t *dest, size_t numWords) override; - - void begin() override; - void readBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset = 0); + /// Check if a DMA write is in progress + /// @returns true if a write DMA is in progress, else false + bool isWriteBusy(); + + /// Check if a DMA read is in progress + /// @returns true if a read DMA is in progress, else false + bool isReadBusy(); + + /// Readout the 8-bit contents of the DMA storage buffer to the specified destination + /// @param dest pointer to the destination + /// @param numBytes number of bytes to read out + /// @param byteOffset, offset from the start of the DMA buffer in bytes to begin reading + void readBufferContents(uint8_t *dest, size_t numBytes, size_t byteOffset = 0); + + /// Readout the 8-bit contents of the DMA storage buffer to the specified destination + /// @param dest pointer to the destination + /// @param numWords number of 16-bit words to read out + /// @param wordOffset, offset from the start of the DMA buffer in words to begin reading + void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0); private: AbstractDmaSpi *m_spiDma = nullptr; From 7124306700a6fbe0e1d178874d31fab3445e8932 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 10 Feb 2018 13:52:10 -0500 Subject: [PATCH 20/27] Fixed a type with readAdvance16() --- src/AudioEffectAnalogDelay.cpp | 3 ++- src/LibBasicFunctions.cpp | 4 +++- src/LibMemoryManagement.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index df493f8..9be70e5 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -261,6 +261,7 @@ void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t * { if ( out && dry && wet) { alphaBlend(out, dry, wet, m_feedback); + m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); } else if (dry) { memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); } @@ -270,7 +271,7 @@ void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t { if ( out && dry && wet) { // Simulate the LPF IIR nature of the analog systems - m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); + //m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); alphaBlend(out, dry, wet, m_mix); } else if (dry) { memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 4170b92..23159cc 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -119,6 +119,7 @@ audio_block_t* AudioDelay::addBlock(audio_block_t *block) // srcPtr--; // } + // this causes pops m_slot->writeAdvance16(block->data, AUDIO_BLOCK_SAMPLES); // Code below worked @@ -213,9 +214,10 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample // destPtr--; // } + // This causes pops m_slot->readAdvance16(dest->data, AUDIO_BLOCK_SAMPLES); -// // Code below worked + // Code below worked // int16_t *destPtr = dest->data; // for (int i=0; ireadAdvance16(); diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 72be901..529844b 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -115,7 +115,7 @@ bool ExtMemSlot::readAdvance16(int16_t *dest, size_t numWords) if (m_currentRdPosition + numBytes-1 <= m_end) { // entire block fits in memory slot without wrapping - m_spi->read16(m_currentWrPosition, reinterpret_cast(dest), numWords); // cast audio data to uint. + m_spi->read16(m_currentRdPosition, reinterpret_cast(dest), numWords); // cast audio data to uint. m_currentRdPosition += numBytes; } else { From 2f5e8e72ee68afc7783bc4681e77a0f9cdcade7c Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 10 Feb 2018 21:23:03 -0500 Subject: [PATCH 21/27] Working checking, needs performance enhancement --- src/AudioEffectAnalogDelay.cpp | 13 ++- src/BASpiMemory.cpp | 153 ++++++++++++++++++--------------- src/BASpiMemory.h | 21 +++-- src/LibBasicFunctions.cpp | 5 -- src/LibBasicFunctions.h | 3 +- src/LibMemoryManagement.cpp | 39 +++++---- src/LibMemoryManagement.h | 10 ++- 7 files changed, 135 insertions(+), 109 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 9be70e5..8782923 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -123,7 +123,7 @@ void AudioEffectAnalogDelay::update(void) // consider doing the BBD post processing here to use up more time while waiting // for the read data to come back audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); - if (blockToRelease) release(blockToRelease); + // BACK TO OUTPUT PROCESSING // audio_block_t *blockToOutput = nullptr; @@ -134,10 +134,11 @@ void AudioEffectAnalogDelay::update(void) // // copy over data // m_memory->getSamples(blockToOutput, m_delaySamples); - // Check if external DMA, if so, we need to copy out of the DMA buffer + // Check if external DMA, if so, we need to be sure the read is completed if (m_externalMemory && m_memory->getSlot()->isUseDma()) { // Using DMA - m_memory->readDmaBufferContents(blockToOutput); + unsigned loopCount = 0; + while (m_memory->getSlot()->isReadBusy()) { /*Serial.println(String("RB:") + loopCount); loopCount++; */} } // perform the wet/dry mix mix @@ -147,6 +148,12 @@ void AudioEffectAnalogDelay::update(void) release(inputAudioBlock); release(m_previousBlock); m_previousBlock = blockToOutput; + if (m_externalMemory && m_memory->getSlot()->isUseDma()) { + // Using DMA + unsigned loopCount = 0; + while (m_memory->getSlot()->isWriteBusy()) { /*Serial.println(String("WB:") + loopCount); loopCount++; */} + } + if (blockToRelease) release(blockToRelease); } void AudioEffectAnalogDelay::delay(float milliseconds) diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index e970940..b091f73 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -45,6 +45,8 @@ 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) { @@ -262,8 +264,8 @@ void BASpiMemory::read16(size_t address, uint16_t *dest, size_t numWords) ///////////////////////////////////////////////////////////////////////////// // BASpiMemoryDMA ///////////////////////////////////////////////////////////////////////////// -BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes) -: BASpiMemory(memDeviceId), m_bufferSize(bufferSizeBytes) +BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId) +: BASpiMemory(memDeviceId) { int cs; switch (memDeviceId) { @@ -277,15 +279,15 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes) cs = SPI_CS_MEM0; } m_cs = new ActiveLowChipSelect(cs, m_settings); - // add 4 bytes to buffer for SPI CMD and 3 bytes of addresse - m_txBuffer = new uint8_t[bufferSizeBytes+4]; - m_rxBuffer = new uint8_t[bufferSizeBytes+4]; - m_txTransfer = new DmaSpi::Transfer(); - m_rxTransfer = new DmaSpi::Transfer(); + // 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, size_t bufferSizeBytes) -: BASpiMemory(memDeviceId, speedHz), m_bufferSize(bufferSizeBytes) +BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz) +: BASpiMemory(memDeviceId, speedHz) { int cs; switch (memDeviceId) { @@ -299,19 +301,19 @@ BASpiMemoryDMA::BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t cs = SPI_CS_MEM0; } m_cs = new ActiveLowChipSelect(cs, m_settings); - m_txBuffer = new uint8_t[bufferSizeBytes+4]; - m_rxBuffer = new uint8_t[bufferSizeBytes+4]; - m_txTransfer = new DmaSpi::Transfer(); - m_rxTransfer = new DmaSpi::Transfer(); + 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_txBuffer) delete [] m_txBuffer; - if (m_rxBuffer) delete [] m_rxBuffer; - if (m_txTransfer) delete m_txTransfer; - if (m_rxTransfer) delete m_rxTransfer; + 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) @@ -358,91 +360,102 @@ void BASpiMemoryDMA::begin(void) 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) { - while ( m_txTransfer->busy()) {} // wait until not busy - uint16_t transferCount = numBytes + 4; // transfer must be increased by the SPI command and address - m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - memcpy(m_txBuffer+4, src, numBytes); - *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); - m_spiDma->registerTransfer(*m_txTransfer); + 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()) {Serial.println("W1");} // 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()) { Serial.println("W2");} // 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) { - while ( m_txTransfer->busy()) {} - uint16_t transferCount = numBytes + 4; - m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - memset(m_txBuffer+4, 0, numBytes); - *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); - m_spiDma->registerTransfer(*m_txTransfer); + 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) { - while ( m_txTransfer->busy()) {} - size_t numBytes = sizeof(uint16_t)*numWords; - uint16_t transferCount = numBytes + 4; - m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - memcpy(m_txBuffer+4, src, numBytes); - *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); - m_spiDma->registerTransfer(*m_txTransfer); + write(address, reinterpret_cast(src), sizeof(uint16_t)*numWords); } void BASpiMemoryDMA::zero16(size_t address, size_t numWords) { - while ( m_txTransfer->busy()) {} - size_t numBytes = sizeof(uint16_t)*numWords; - uint16_t transferCount = numBytes + 4; - m_setSpiCmdAddr(SPI_WRITE_CMD, address, m_txBuffer); - memset(m_txBuffer+4, 0, numBytes); - *m_txTransfer = DmaSpi::Transfer(m_txBuffer, transferCount, nullptr, 0, m_cs); - m_spiDma->registerTransfer(*m_txTransfer); + zero(address, sizeof(uint16_t)*numWords); } + void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) { - UNUSED(dest); - while ( m_rxTransfer->busy()) {} - uint16_t transferCount = numBytes + 4; - m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); - *m_rxTransfer = DmaSpi::Transfer(m_rxBuffer, transferCount, m_rxBuffer, 0, m_cs); - m_spiDma->registerTransfer(*m_rxTransfer); + 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()) { Serial.println("R1"); } + 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()) {Serial.println("R2");} + 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) -{ - UNUSED(dest); - while ( m_rxTransfer->busy()) {} - m_setSpiCmdAddr(SPI_READ_CMD, address, m_rxBuffer); - size_t numBytes = sizeof(uint16_t)*numWords; - uint16_t transferCount = numBytes + 4; - *m_rxTransfer = DmaSpi::Transfer(m_rxBuffer, transferCount, m_rxBuffer, 0, m_cs); - m_spiDma->registerTransfer(*m_rxTransfer); -} -bool BASpiMemoryDMA::isWriteBusy(void) +void BASpiMemoryDMA::read16(size_t address, uint16_t *dest, size_t numWords) { - return m_txTransfer->busy(); + read(address, reinterpret_cast(dest), sizeof(uint16_t)*numWords); } -bool BASpiMemoryDMA::isReadBusy(void) -{ - return m_rxTransfer->busy(); -} -void BASpiMemoryDMA::readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset) +bool BASpiMemoryDMA::isWriteBusy(void) const { - readBufferContents(reinterpret_cast(dest), sizeof(uint16_t)*numWords, sizeof(uint16_t)*wordOffset); + return (m_txTransfer[0].busy() or m_txTransfer[1].busy()); } -void BASpiMemoryDMA::readBufferContents(uint8_t *dest, size_t numBytes, size_t byteOffset) +bool BASpiMemoryDMA::isReadBusy(void) const { - while (m_rxTransfer->busy()) {} // ensure transfer is complete - memcpy(dest, m_rxBuffer+4+byteOffset, numBytes); + return (m_rxTransfer[0].busy() or m_rxTransfer[1].busy()); } } /* namespace BAGuitar */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index 5fb58f8..e31c7e9 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -128,14 +128,12 @@ public: /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2). /// @details default is 20 Mhz /// @param memDeviceId specify which MEM to control with SpiDeviceId. - /// @param bufferSize size of buffer to store DMA transfers - BASpiMemoryDMA(SpiDeviceId memDeviceId, size_t bufferSizeBytes); + BASpiMemoryDMA(SpiDeviceId memDeviceId); /// Create an object to control either MEM0 (via SPI1) or MEM1 (via SPI2) /// @param memDeviceId specify which MEM to control with SpiDeviceId. /// @param speedHz specify the desired speed in Hz. - /// @param bufferSize size of buffer to store DMA transfers - BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz, size_t bufferSizeBytes); + BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz); virtual ~BASpiMemoryDMA(); /// initialize and configure the SPI peripheral @@ -177,11 +175,11 @@ public: /// Check if a DMA write is in progress /// @returns true if a write DMA is in progress, else false - bool isWriteBusy(); + bool isWriteBusy() const; /// Check if a DMA read is in progress /// @returns true if a read DMA is in progress, else false - bool isReadBusy(); + bool isReadBusy() const; /// Readout the 8-bit contents of the DMA storage buffer to the specified destination /// @param dest pointer to the destination @@ -198,12 +196,17 @@ public: private: AbstractDmaSpi *m_spiDma = nullptr; ActiveLowChipSelect *m_cs = nullptr; - size_t m_bufferSize; - uint8_t *m_txBuffer = 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_rxBuffer = nullptr; + uint8_t *m_rxCommandBuffer = nullptr; DmaSpi::Transfer *m_rxTransfer; + uint16_t m_txXferCount; + uint16_t m_rxXferCount; + void m_setSpiCmdAddr(int command, size_t address, uint8_t *dest); }; diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 23159cc..74ec2c3 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -234,11 +234,6 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } -void AudioDelay::readDmaBufferContents(audio_block_t *dest, size_t numSamples, size_t bufferOffset) -{ - m_slot->readDmaBufferContents(reinterpret_cast(dest->data), sizeof(int16_t)*numSamples, sizeof(int16_t)*bufferOffset); -} - //////////////////////////////////////////////////// // IirBiQuadFilter //////////////////////////////////////////////////// diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 2f88c2f..32872cc 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -93,6 +93,7 @@ class RingBuffer; // forward declare so AudioDelay can use it. * approach. When using EXTERNAL memory, data is actually copyied to/from an external * SRAM device. *****************************************************************************/ +constexpr size_t AUDIO_BLOCK_SIZE = sizeof(int16_t)*AUDIO_BLOCK_SAMPLES; class AudioDelay { public: AudioDelay() = delete; @@ -140,8 +141,6 @@ public: /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } - void readDmaBufferContents(audio_block_t *dest, size_t numSamples = AUDIO_BLOCK_SAMPLES, size_t bufferOffset = 0); - RingBuffer *getRingBuffer() const { return m_ringBuffer; } private: diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 529844b..07c8c02 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -175,16 +175,6 @@ bool ExtMemSlot::zeroAdvance16(size_t numWords) return true; } -void ExtMemSlot::readDmaBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset) -{ - if (m_useDma) { - (static_cast(m_spi))->readBufferContents(dest, numBytes, bufferOffset); -// BASpiMemoryDMA *spi = nullptr; -// spi = static_cast(m_spi); -// spi->readBufferContents(dest, numBytes, bufferOffset); - } -} - bool ExtMemSlot::writeAdvance16(int16_t data) { @@ -219,6 +209,21 @@ bool ExtMemSlot::isEnabled() const else return false; } +bool ExtMemSlot::isWriteBusy() const +{ + if (m_useDma) { + return (static_cast(m_spi))->isWriteBusy(); + } else { return false; } +} + +bool ExtMemSlot::isReadBusy() const +{ + if (m_useDma) { + return (static_cast(m_spi))->isReadBusy(); + } else { return false; } +} + + void ExtMemSlot::printStatus(void) const { Serial.println(String("valid:") + m_valid + String(" m_start:") + m_start + \ @@ -261,14 +266,14 @@ size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem) return m_memConfig[mem].totalAvailable; } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem, size_t dmaBufferSize) +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, dmaBufferSize); + return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, useDma); } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, size_t dmaBufferSize) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, bool useDma) { if (m_memConfig[mem].totalAvailable >= sizeBytes) { @@ -281,16 +286,16 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu slot->m_size = sizeBytes; if (!m_memConfig[mem].m_spi) { - if (dmaBufferSize > 0) { - m_memConfig[mem].m_spi = new BAGuitar::BASpiMemoryDMA(static_cast(mem), dmaBufferSize); + 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) { - Serial.println("requestMemory: new failed! m_spi is a nullptr"); } else { + Serial.println("Calling spi begin()"); m_memConfig[mem].m_spi->begin(); } } @@ -301,7 +306,9 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu 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 diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index fe9948c..8ce5074 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -128,8 +128,6 @@ public: /// @returns true on success, else false on error bool zeroAdvance16(size_t numWords); - void readDmaBufferContents(uint8_t *dest, size_t numBytes, size_t bufferOffset = 0); - /// Get the size of the memory slot /// @returns size of the slot in bytes size_t size() const { return m_size; } @@ -144,6 +142,10 @@ public: bool isUseDma() const { return m_useDma; } + bool isWriteBusy() const; + + bool isReadBusy() const; + /// DEBUG USE: prints out the slot member variables void printStatus(void) const; @@ -188,7 +190,7 @@ public: /// @param mem specify which external memory to allocate from /// @param dmaBufferSize When > 0, DMA mode is used with the specified DMA buffer size /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, size_t dmaBufferSize = 0); + bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false); /// Request memory be allocated for the provided slot /// @param slot a pointer to the global slot object to which memory will be allocated @@ -196,7 +198,7 @@ public: /// @param mem specify which external memory to allocate from /// @param dmaBufferSize When > 0, DMA mode is used with the specified DMA buffer size /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, size_t dmaBufferSize = 0); + bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false); private: static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project From 7c614e64507879c8a9f6261abc205086e4b18f03 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 10 Feb 2018 22:40:05 -0500 Subject: [PATCH 22/27] Achieved performance goal on DMA --- src/AudioEffectAnalogDelay.cpp | 8 ++++---- src/AudioEffectAnalogDelay.h | 1 + src/BASpiMemory.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index 8782923..15fd05f 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -138,7 +138,7 @@ void AudioEffectAnalogDelay::update(void) if (m_externalMemory && m_memory->getSlot()->isUseDma()) { // Using DMA unsigned loopCount = 0; - while (m_memory->getSlot()->isReadBusy()) { /*Serial.println(String("RB:") + loopCount); loopCount++; */} + while (m_memory->getSlot()->isReadBusy()) {} } // perform the wet/dry mix mix @@ -148,12 +148,12 @@ void AudioEffectAnalogDelay::update(void) release(inputAudioBlock); release(m_previousBlock); m_previousBlock = blockToOutput; + if (m_externalMemory && m_memory->getSlot()->isUseDma()) { // Using DMA - unsigned loopCount = 0; - while (m_memory->getSlot()->isWriteBusy()) { /*Serial.println(String("WB:") + loopCount); loopCount++; */} + if (m_blockToRelease) release(m_blockToRelease); + m_blockToRelease = blockToRelease; } - if (blockToRelease) release(blockToRelease); } void AudioEffectAnalogDelay::delay(float milliseconds) diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index b4220fa..a1a3fab 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -45,6 +45,7 @@ private: AudioDelay *m_memory = nullptr; size_t m_maxDelaySamples = 0; audio_block_t *m_previousBlock = nullptr; + audio_block_t *m_blockToRelease = nullptr; IirBiQuadFilterHQ *m_iir = nullptr; // Controls diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp index b091f73..f42d47a 100644 --- a/src/BASpiMemory.cpp +++ b/src/BASpiMemory.cpp @@ -372,12 +372,12 @@ void BASpiMemoryDMA::write(size_t address, uint8_t *src, size_t numBytes) size_t nextAddress = address; while (bytesRemaining > 0) { m_txXferCount = min(bytesRemaining, MAX_DMA_XFER_SIZE); - while ( m_txTransfer[1].busy()) {Serial.println("W1");} // wait until not busy + 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()) { Serial.println("W2");} // wait until not busy + 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; @@ -426,12 +426,12 @@ void BASpiMemoryDMA::read(size_t address, uint8_t *dest, size_t numBytes) while (bytesRemaining > 0) { m_setSpiCmdAddr(SPI_READ_CMD, nextAddress, m_rxCommandBuffer); - while ( m_rxTransfer[1].busy()) { Serial.println("R1"); } + 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()) {Serial.println("R2");} + 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]); From cf7528ee8a9499d1dd68e30543103b10dd99dde0 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sun, 11 Feb 2018 13:02:32 -0500 Subject: [PATCH 23/27] Switched some 16-bit function offsets from bytes to words --- src/LibBasicFunctions.cpp | 6 +- src/LibBasicFunctions.h | 180 ++++++++++-------------------------- src/LibMemoryManagement.cpp | 14 +-- src/LibMemoryManagement.h | 18 ++-- 4 files changed, 67 insertions(+), 151 deletions(-) diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 74ec2c3..9fc0c71 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -144,7 +144,7 @@ audio_block_t* AudioDelay::getBlock(size_t index) return ret; } -bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSamples) +bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples) { if (!dest) { Serial.println("getSamples(): dest is invalid"); @@ -152,7 +152,7 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } if (m_type == (MemType::MEM_INTERNAL)) { - QueuePosition position = calcQueuePosition(offset); + QueuePosition position = calcQueuePosition(offsetSamples); size_t index = position.index; audio_block_t *currentQueue0 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); @@ -195,7 +195,7 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample // 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 = offset * sizeof(int16_t); + size_t offsetBytes = offsetSamples * sizeof(int16_t); if ((int)offsetBytes <= currentPositionBytes) { m_slot->setReadPosition(currentPositionBytes - offsetBytes); diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 32872cc..830d5e6 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -131,16 +131,19 @@ public: /// @details when using INTERNAL memory, only supported size is AUDIO_BLOCK_SAMPLES. When using /// EXTERNAL, a size smaller than AUDIO_BLOCK_SAMPLES can be requested. /// @param dest pointer to the target audio block to write the samples to. - /// @param offset data will start being transferred offset samples from the start of the audio buffer + /// @param offsetSamples data will start being transferred offset samples from the start of the audio buffer /// @param numSamples default value is AUDIO_BLOCK_SAMPLES, so typically you don't have to specify this parameter. /// @returns true on success, false on error. - bool getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES); + bool getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples = AUDIO_BLOCK_SAMPLES); /// When using EXTERNAL memory, this function can return a pointer to the underlying ExtMemSlot object associated /// with the buffer. /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } + /// Ween using INTERNAL memory, thsi function can return a pointer to the underlying RingBuffer that contains + /// audio_block_t * pointers. + /// @returns pointer to the underlying RingBuffer RingBuffer *getRingBuffer() const { return m_ringBuffer; } private: @@ -168,8 +171,19 @@ private: class IirBiQuadFilter { public: IirBiQuadFilter() = delete; + /// Construct a Biquad filter with specified number of stages, coefficients and scaling. + /// @details See CMSIS-DSP documentation for more details + /// @param numStages number of biquad stages. Each stage has 5 coefficients. + /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) + /// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); virtual ~IirBiQuadFilter(); + + /// Process the data using the configured IIR filter + /// @details output and input can be the same pointer if in-place modification is desired + /// @param output pointer to where the output results will be written + /// @param input pointer to where the input data will be read from + /// @param numSampmles number of samples to process bool process(int16_t *output, int16_t *input, size_t numSamples); private: const unsigned NUM_STAGES; @@ -180,30 +194,58 @@ private: int32_t *m_state = nullptr; }; - +/**************************************************************************//** + * A High-precision version of IirBiQuadFilter often necessary for complex, multistage + * filters. This class uses CMSIS-DSP biquads with 64-bit internal precision instead + * of 32-bit. + *****************************************************************************/ class IirBiQuadFilterHQ { public: IirBiQuadFilterHQ() = delete; + /// Construct a Biquad filter with specified number of stages, coefficients and scaling. + /// @details See CMSIS-DSP documentation for more details + /// @param numStages number of biquad stages. Each stage has 5 coefficients. + /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) + /// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); virtual ~IirBiQuadFilterHQ(); + + /// Process the data using the configured IIR filter + /// @details output and input can be the same pointer if in-place modification is desired + /// @param output pointer to where the output results will be written + /// @param input pointer to where the input data will be read from + /// @param numSampmles number of samples to process bool process(int16_t *output, int16_t *input, size_t numSamples); private: - - const unsigned NUM_STAGES; int32_t *m_coeffs = nullptr; // ARM DSP Math library filter instance arm_biquad_cas_df1_32x64_ins_q31 m_iirCfg; int64_t *m_state = nullptr; - }; +/**************************************************************************//** + * A single-precision floating-point biquad using CMSIS-DSP hardware instructions. + * @details Use this when IirBiQuadFilterHQ is insufficient, since that version + * is still faster with 64-bit fixed-point arithmetic. + *****************************************************************************/ class IirBiQuadFilterFloat { public: IirBiQuadFilterFloat() = delete; + + /// Construct a Biquad filter with specified number of stages and coefficients + /// @details See CMSIS-DSP documentation for more details + /// @param numStages number of biquad stages. Each stage has 5 coefficients. + /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) IirBiQuadFilterFloat(unsigned numStages, const float *coeffs); virtual ~IirBiQuadFilterFloat(); + + /// Process the data using the configured IIR filter + /// @details output and input can be the same pointer if in-place modification is desired + /// @param output pointer to where the output results will be written + /// @param input pointer to where the input data will be read from + /// @param numberSampmles number of samples to process bool process(float *output, float *input, size_t numSamples); private: const unsigned NUM_STAGES; @@ -215,132 +257,6 @@ private: }; -///**************************************************************************//** -// * Customer RingBuffer with random access -// *****************************************************************************/ -//template -//class RingBuffer { -//public: -// RingBuffer() = delete; -// -// /// Construct a RingBuffer of specified max size -// /// @param maxSize number of entries in ring buffer -// RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { -// m_buffer = new T[maxSize](); -// } -// virtual ~RingBuffer(){ -// if (m_buffer) delete [] m_buffer; -// } -// -// /// Add an element to the back of the queue -// /// @param element element to add to queue -// /// returns 0 if success, otherwise error -// int push_back(T element) { -// -// //Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); -// if ( (m_head == m_tail) && (m_size > 0) ) { -// // overflow -// Serial.println("RingBuffer::push_back: overflow"); -// return -1; -// } -// -// m_buffer[m_head] = element; -// if (m_head < (m_maxSize-1) ) { -// m_head++; -// } else { -// m_head = 0; -// } -// m_size++; -// -// return 0; -// } -// -// /// Remove the element at teh front of the queue -// /// @returns 0 if success, otherwise error -// int pop_front() { -// -// if (m_size == 0) { -// // buffer is empty -// //Serial.println("RingBuffer::pop_front: buffer is empty\n"); -// return -1; -// } -// if (m_tail < m_maxSize-1) { -// m_tail++; -// } else { -// m_tail = 0; -// } -// m_size--; -// //Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); -// return 0; -// } -// -// /// Get the element at the front of the queue -// /// @returns element at front of queue -// T front() const { -// return m_buffer[m_tail]; -// } -// -// /// get the element at the back of the queue -// /// @returns element at the back of the queue -// T back() const { -// return m_buffer[m_head-1]; -// } -// -// /// Get a previously pushed elememt -// /// @param offset zero is last pushed, 1 is second last, etc. -// /// @returns the absolute index corresponding to the requested offset. -// size_t get_index_from_back(size_t offset = 0) const { -// // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; -// size_t idx = (m_maxSize + m_head -1 - offset); -// -// if ( idx >= m_maxSize) { -// idx -= m_maxSize; -// } -// -// return idx; -// } -// -// /// get the current size of the queue -// /// @returns size of the queue -// size_t size() const { -// return m_size; -// } -// -// /// get the maximum size the queue can hold -// /// @returns maximum size of the queue -// size_t max_size() const { -// return m_maxSize; -// } -// -// /// get the element at the specified absolute index -// /// @param index element to retrieve from absolute queue position -// /// @returns the request element -// T& operator[] (size_t index) { -// return m_buffer[index]; -// } -// -// /// get the element at the specified absolute index -// /// @param index element to retrieve from absolute queue position -// /// @returns the request element -// T at(size_t index) const { -// return m_buffer[index]; -// } -// -// /// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches -// void print() const { -// for (int idx=0; idxdata, HEX); -// } -// } -//private: -// size_t m_head=0; ///< back of the queue -// size_t m_tail=0; ///< front of the queue -// size_t m_size=0; ///< current size of the qeueu -// T *m_buffer = nullptr; ///< pointer to the allocated buffer array -// const size_t m_maxSize; ///< maximum size of the queue -//}; - } diff --git a/src/LibMemoryManagement.cpp b/src/LibMemoryManagement.cpp index 07c8c02..ae1fb0d 100644 --- a/src/LibMemoryManagement.cpp +++ b/src/LibMemoryManagement.cpp @@ -44,13 +44,13 @@ bool ExtMemSlot::setWritePosition(size_t offsetBytes) } else { return false; } } -bool ExtMemSlot::write16(size_t offsetBytes, int16_t *dest, size_t numWords) +bool ExtMemSlot::write16(size_t offsetWords, int16_t *src, size_t numWords) { if (!m_valid) { return false; } - size_t writeStart = m_start + offsetBytes; // 2x because int16 is two bytes per data + size_t writeStart = m_start + sizeof(int16_t)*offsetWords; // 2x because int16 is two bytes per data size_t numBytes = sizeof(int16_t)*numWords; if ((writeStart + numBytes-1) <= m_end) { - m_spi->write16(writeStart, reinterpret_cast(dest), numWords); // cast audio data to uint + m_spi->write16(writeStart, reinterpret_cast(src), numWords); // cast audio data to uint return true; } else { // this would go past the end of the memory slot, do not perform the write @@ -68,10 +68,10 @@ bool ExtMemSlot::setReadPosition(size_t offsetBytes) } } -bool ExtMemSlot::zero16(size_t offsetBytes, size_t numWords) +bool ExtMemSlot::zero16(size_t offsetWords, size_t numWords) { if (!m_valid) { return false; } - size_t writeStart = m_start + offsetBytes; + size_t writeStart = m_start + sizeof(int16_t)*offsetWords; size_t numBytes = sizeof(int16_t)*numWords; if ((writeStart + numBytes-1) <= m_end) { m_spi->zero16(writeStart, numWords); // cast audio data to uint @@ -82,10 +82,10 @@ bool ExtMemSlot::zero16(size_t offsetBytes, size_t numWords) } } -bool ExtMemSlot::read16(int16_t *dest, size_t offsetBytes, size_t numWords) +bool ExtMemSlot::read16(size_t offsetWords, int16_t *dest, size_t numWords) { if (!dest) return false; // invalid destination - size_t readOffset = m_start + offsetBytes; + size_t readOffset = m_start + sizeof(int16_t)*offsetWords; size_t numBytes = sizeof(int16_t)*numWords; if ((readOffset + numBytes-1) <= m_end) { diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index 8ce5074..574b7d9 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -81,24 +81,24 @@ public: size_t getReadPosition() const { return m_currentRdPosition-m_start; } /// Write a block of 16-bit data to the memory at the specified offset - /// @param offsetBytes offset in bytes from start of slot - /// @param dataPtr pointer to start of block of 16-bit data + /// @param offsetWords offset in 16-bit words from start of slot + /// @param src pointer to start of block of 16-bit data /// @param numWords number of 16-bit words to transfer /// @returns true on success, else false on error - bool write16(size_t offsetBytes, int16_t *dest, size_t numWords); + bool write16(size_t offsetWords, int16_t *src, size_t numWords); /// Write a block of zeros (16-bit) to the memory at the specified offset - /// @param offsetBytes offset in bytes from start of slot + /// @param offsetWords offset in 16-bit words from start of slot /// @param numWords number of 16-bit words to transfer /// @returns true on success, else false on error - bool zero16(size_t offsetBytes, size_t numWords); + bool zero16(size_t offsetWords, size_t numWords); /// Read a block of 16-bit data from the memory at the specified location + /// @param offsetWords offset in 16-bit words from start of slot /// @param dest pointer to destination for the read data - /// @param offsetBytes offset in bytes from start of slot /// @param numWords number of 16-bit words to transfer /// @returns true on success, else false on error - bool read16(int16_t *dest, size_t offsetBytes, size_t numWords); + bool read16(size_t offsetWords, int16_t *dest, size_t numWords); /// Read the next in memory during circular operation /// @returns the next 16-bit data word in memory @@ -188,7 +188,7 @@ public: /// @param slot a pointer to the global slot object to which memory will be allocated /// @param delayMilliseconds request the amount of memory based on required time for audio samples, rather than number of bytes. /// @param mem specify which external memory to allocate from - /// @param dmaBufferSize When > 0, DMA mode is used with the specified DMA buffer size + /// @param useDma when true, DMA is used for SPI port, else transfers block until complete /// @returns true on success, otherwise false on error bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false); @@ -196,7 +196,7 @@ public: /// @param slot a pointer to the global slot object to which memory will be allocated /// @param sizeBytes request the amount of memory in bytes to request /// @param mem specify which external memory to allocate from - /// @param dmaBufferSize When > 0, DMA mode is used with the specified DMA buffer size + /// @param useDma when true, DMA is used for SPI port, else transfers block until complete /// @returns true on success, otherwise false on error bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false); From e89431845133bd74fbbacd6f089b0a528ae0cf65 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Mon, 19 Feb 2018 12:28:43 -0500 Subject: [PATCH 24/27] DMA now works with both SPI0 and SPI1 --- src/BASpiMemory.cpp | 461 -------------------------------------------- src/BASpiMemory.h | 8 +- 2 files changed, 6 insertions(+), 463 deletions(-) delete mode 100644 src/BASpiMemory.cpp diff --git a/src/BASpiMemory.cpp b/src/BASpiMemory.cpp deleted file mode 100644 index f42d47a..0000000 --- a/src/BASpiMemory.cpp +++ /dev/null @@ -1,461 +0,0 @@ -/* - * 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; - break; - case SpiDeviceId::SPI_DEVICE1 : - cs = SPI_CS_MEM1; - break; - default : - cs = SPI_CS_MEM0; - } - m_cs = new ActiveLowChipSelect(cs, m_settings); - // 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; - break; - case SpiDeviceId::SPI_DEVICE1 : - cs = SPI_CS_MEM1; - break; - default : - cs = SPI_CS_MEM0; - } - m_cs = new ActiveLowChipSelect(cs, m_settings); - 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; - 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 = &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 */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index e31c7e9..04fb732 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -194,8 +194,12 @@ public: void readBufferContents(uint16_t *dest, size_t numWords, size_t wordOffset = 0); private: - AbstractDmaSpi *m_spiDma = nullptr; - ActiveLowChipSelect *m_cs = nullptr; + //AbstractDmaSpi *m_spiDma = nullptr; + //AbstractDmaSpi *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; From b649550cb35af00c47f579dd332d2739da7ab71e Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Mon, 19 Feb 2018 20:17:04 -0500 Subject: [PATCH 25/27] 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 */ From 429c37f8aca0b5d88b86a75606cecbc156a183c8 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Tue, 20 Feb 2018 20:58:48 -0500 Subject: [PATCH 26/27] Added volume control, some code cleanup --- .../Delay/AnalogDelayDemo/AnalogDelayDemo.ino | 65 ++++++----- src/AudioEffectAnalogDelay.h | 53 ++++++++- src/LibBasicFunctions.h | 17 ++- src/common/AudioHelpers.cpp | 12 +- src/effects/AudioEffectAnalogDelay.cpp | 109 ++++++++---------- 5 files changed, 159 insertions(+), 97 deletions(-) diff --git a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino index 1c4b657..fb452fb 100644 --- a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino +++ b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino @@ -1,7 +1,8 @@ -//#include +#include #include "BAGuitar.h" using namespace BAGuitar; + AudioInputI2S i2sIn; AudioOutputI2S i2sOut; BAAudioControlWM8731 codec; @@ -9,22 +10,35 @@ BAAudioControlWM8731 codec; #define USE_EXT #ifdef USE_EXT -ExternalSramManager externalSram(1); // Manage both SRAMs -ExtMemSlot delaySlot; // For the external memory +// 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. +ExtMemSlot delaySlot; // Declare an external memory slot. + +// Instantiate the AudioEffectAnalogDelay to use external memory by +/// passing it the delay slot. AudioEffectAnalogDelay myDelay(&delaySlot); #else -AudioEffectAnalogDelay myDelay(200.0f); +// 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. #endif -AudioMixer4 mixer; // Used to mix the original dry with the wet (effects) path. +//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 input(i2sIn,0, myDelay,0); +AudioConnection leftOut(myDelay,0, i2sOut, 0); -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); +int loopCount = 0; -unsigned loopCount = 0; +AudioConnection rightOut(myDelay,0, i2sOut, 1); void setup() { delay(100); @@ -43,32 +57,31 @@ void setup() { #ifdef USE_EXT Serial.println("Using EXTERNAL memory"); - //externalSram.requestMemory(&delaySlot, 1400.0f); - //externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM0, true); + // We have to request memory be allocated to our slot. 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); - - + // 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); - myDelay.mapMidiBypass(16); - myDelay.mapMidiDelay(20); - myDelay.mapMidiFeedback(21); - myDelay.mapMidiMix(22); + // Besure to enable the delay, by default it's processing is off. + myDelay.enable(); - 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 +// mixer.gain(0, 0.0f); // unity gain on the dry +// mixer.gain(1, 1.0f); // unity gain on the wet } diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index 0ff2d2f..7fd2475 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -39,7 +39,21 @@ namespace BAGuitar { *****************************************************************************/ class AudioEffectAnalogDelay : public AudioStream { public: + + ///< List of AudioEffectAnalogDelay MIDI controllable parameters + enum { + BYPASS = 0, ///< controls effect bypass + DELAY, ///< controls the amount of delay + FEEDBACK, ///< controls the amount of echo feedback (regen) + MIX, ///< controls the the mix of input and echo signals + VOLUME, ///< controls the output volume level + NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls + }; + AudioEffectAnalogDelay() = delete; + + // *** CONSTRUCTORS *** + /// 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. @@ -57,6 +71,8 @@ public: virtual ~AudioEffectAnalogDelay(); ///< Destructor + // *** PARAMETERS *** + /// Set the delay in milliseconds. /// @param milliseconds the request delay in milliseconds. Must be less than max delay. void delay(float milliseconds); @@ -79,22 +95,46 @@ public: /// 0.5, output is 50% Dry, 50% Wet. void mix(float mix) { m_mix = mix; } + /// Set the output volume. This affect both the wet and dry signals. + /// @details The default is 1.0. + /// @param vol Sets the output volume between -1.0 and +1.0 + void volume(float vol) {m_volume = vol; } + + // ** ENABLE / DISABLE ** + /// Enables audio processing. Note: when not enabled, CPU load is nearly zero. void enable() { m_enable = true; } /// Disables audio process. When disabled, CPU load is nearly zero. void disable() { m_enable = false; } - void processMidi(int channel, int control, int value); - void mapMidiBypass(int control, int channel = 0); - void mapMidiDelay(int control, int channel = 0); - void mapMidiFeedback(int control, int channel = 0); - void mapMidiMix(int control, int channel = 0); + // ** MIDI ** + + /// Sets whether MIDI OMNI channel is processig on or off. When on, + /// all midi channels are used for matching CCs. + /// @param isOmni when true, all channels are processed, when false, channel + /// must match configured value. + void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } + + /// Configure an effect parameter to be controlled by a MIDI CC + /// number on a particular channel. + /// @param parameter one of the parameter names in the class enum + /// @param midiCC the CC number from 0 to 127 + /// @param midiChannel the effect will only response to the CC on this channel + /// when OMNI mode is off. + void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); + + /// process a MIDI Continous-Controller (CC) message + /// @param channel the MIDI channel from 0 to 15) + /// @param midiCC the CC number from 0 to 127 + /// @param value the CC value from 0 to 127 + void processMidi(int channel, int midiCC, int value); virtual void update(void); ///< update automatically called by the Teesny Audio Library private: audio_block_t *m_inputQueueArray[1]; + bool m_isOmni = false; bool m_bypass = true; bool m_enable = false; bool m_externalMemory = false; @@ -105,10 +145,11 @@ private: IirBiQuadFilterHQ *m_iir = nullptr; // Controls - int m_midiConfig[4][2]; + int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping size_t m_delaySamples = 0; float m_feedback = 0.0f; float m_mix = 0.0f; + float m_volume = 1.0f; void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index a335ac0..802bc5c 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -70,11 +70,26 @@ size_t calcAudioSamples(float milliseconds); /// specified position. size_t calcOffset(QueuePosition position); +/// Clear the contents of an audio block to zero +/// @param block pointer to the audio block to clear void clearAudioBlock(audio_block_t *block); - +/// Perform an alpha blend between to audio blocks. Performs
+/// out = dry*(1-mix) + wet*(mix) +/// @param out pointer to the destination audio block +/// @param dry pointer to the dry audio +/// @param wet pointer to the wet audio +/// @param mix float between 0.0 and 1.0. void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix); +/// Applies a gain to the audio via fixed-point scaling accoring to
+/// out = int * (vol * 2^coeffShift) +/// @param out pointer to output audio block +/// @param in pointer to input audio block +/// @param vol volume cofficient between -1.0 and +1.0 +/// @param coeffShift number of bits to shiftt the coefficient +void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift = 0); + template class RingBuffer; // forward declare so AudioDelay can use it. diff --git a/src/common/AudioHelpers.cpp b/src/common/AudioHelpers.cpp index ca3a02c..ee09395 100644 --- a/src/common/AudioHelpers.cpp +++ b/src/common/AudioHelpers.cpp @@ -47,12 +47,6 @@ size_t calcOffset(QueuePosition position) 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]; @@ -64,6 +58,12 @@ void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, floa arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES); } +void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift) +{ + int16_t scale = (int16_t)(vol * 32767.0f); + arm_scale_q15(in->data, scale, coeffShift, 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/effects/AudioEffectAnalogDelay.cpp b/src/effects/AudioEffectAnalogDelay.cpp index d7b1a0f..3881e57 100644 --- a/src/effects/AudioEffectAnalogDelay.cpp +++ b/src/effects/AudioEffectAnalogDelay.cpp @@ -9,23 +9,17 @@ namespace BAGuitar { -constexpr int MIDI_NUM_PARAMS = 4; constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; -constexpr int MIDI_BYPASS = 0; -constexpr int MIDI_DELAY = 1; -constexpr int MIDI_FEEDBACK = 2; -constexpr int MIDI_MIX = 3; - // BOSS DM-3 Filters constexpr unsigned NUM_IIR_STAGES = 4; constexpr unsigned IIR_COEFF_SHIFT = 2; constexpr int32_t DEFAULT_COEFFS[5*NUM_IIR_STAGES] = { - 536870912, 988616936, 455608573, 834606945, -482959709, - 536870912, 1031466345, 498793368, 965834205, -467402235, - 536870912, 1105821939, 573646688, 928470657, -448083489, - 2339, 5093, 2776, 302068995, 4412722 + 536870912, 988616936, 455608573, 834606945, -482959709, + 536870912, 1031466345, 498793368, 965834205, -467402235, + 536870912, 1105821939, 573646688, 928470657, -448083489, + 2339, 5093, 2776, 302068995, 4412722 }; @@ -200,13 +194,40 @@ void AudioEffectAnalogDelay::delay(size_t delaySamples) m_delaySamples= delaySamples; } +void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +{ + if ( out && dry && wet) { + alphaBlend(out, dry, wet, m_feedback); + m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); + } else if (dry) { + memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + } +} + +void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +{ + if (!out) return; // no valid output buffer + + if ( out && dry && wet) { + // Simulate the LPF IIR nature of the analog systems + //m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); + alphaBlend(out, dry, wet, m_mix); + } else if (dry) { + memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + } + // Set the output volume + gainAdjust(out, out, m_volume, 1); + +} + void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) { + float val = (float)value / 127.0f; - if ((m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_DELAY][MIDI_CONTROL] == control)) { + 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); @@ -214,75 +235,47 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) return; } - if ((m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] == control)) { + if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && + (m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { // Bypass if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); } else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); } return; } - if ((m_midiConfig[MIDI_FEEDBACK][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_FEEDBACK][MIDI_CONTROL] == control)) { + if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && + (m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { // Feedback Serial.println(String("AudioEffectAnalogDelay::feedback: ") + val); feedback(val); return; } - if ((m_midiConfig[MIDI_MIX][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_MIX][MIDI_CONTROL] == control)) { + if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIX][MIDI_CONTROL] == control)) { // Mix Serial.println(String("AudioEffectAnalogDelay::mix: ") + val); mix(val); return; } -} -void AudioEffectAnalogDelay::mapMidiDelay(int control, int channel) -{ - m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_DELAY][MIDI_CONTROL] = control; -} - -void AudioEffectAnalogDelay::mapMidiBypass(int control, int channel) -{ - m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] = control; -} - -void AudioEffectAnalogDelay::mapMidiFeedback(int control, int channel) -{ - m_midiConfig[MIDI_FEEDBACK][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_FEEDBACK][MIDI_CONTROL] = control; -} - -void AudioEffectAnalogDelay::mapMidiMix(int control, int channel) -{ - m_midiConfig[MIDI_MIX][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_MIX][MIDI_CONTROL] = control; -} - - -void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) -{ - if ( out && dry && wet) { - alphaBlend(out, dry, wet, m_feedback); - m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); - } else if (dry) { - memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && + (m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { + // Volume + Serial.println(String("AudioEffectAnalogDelay::volume: ") + val); + volume(val); + return; } + } -void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel) { - if ( out && dry && wet) { - // Simulate the LPF IIR nature of the analog systems - //m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); - alphaBlend(out, dry, wet, m_mix); - } else if (dry) { - memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + if (parameter >= NUM_CONTROLS) { + return ; // Invalid midi parameter } + m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; + m_midiConfig[parameter][MIDI_CONTROL] = midiCC; } } From 62f01fc62a945ee15d6f8c02aab2902684ff319b Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Thu, 22 Feb 2018 20:50:59 -0500 Subject: [PATCH 27/27] 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; }