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/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 diff --git a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino new file mode 100644 index 0000000..3cb413a --- /dev/null +++ b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino @@ -0,0 +1,142 @@ +#include +#include "BAGuitar.h" + +using namespace BAGuitar; + +AudioInputI2S i2sIn; +AudioOutputI2S i2sOut; +BAAudioControlWM8731 codec; + +/// 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; +ExtMemSlot delaySlot; // Declare an external memory slot. + +// Instantiate the AudioEffectAnalogDelay to use external memory by +/// passing it the delay slot. +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 analogDelay(200.0f); // max delay of 200 ms. +#endif + +AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab. + +// Record the audio to the PC +//AudioOutputUSB usb; + +// 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); + +int loopCount = 0; + +void setup() { + delay(100); + Serial.begin(57600); // Start the serial port + + // Disable the codec first + codec.disable(); + delay(100); + AudioMemory(128); + delay(5); + + // 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, 500.0f, MemSelect::MEM0, true); + #else + Serial.println("Using INTERNAL memory"); + #endif + + // 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) { + analogDelay.processMidi(channel, control, value); + #ifdef MIDI_DEBUG + 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(); + #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. + + 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 + 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/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 __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: + + ///< 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. + 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(); ///< Destructor + + // *** PARAMETERS *** + + /// 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; } + + /// 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; } + + // ** 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; + 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 + 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); + + size_t m_callCount = 0; +}; + +} + +#endif /* __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H */ diff --git a/src/BAAudioControlWM8731.h b/src/BAAudioControlWM8731.h index af00261..18b3de9 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 __BAGUITAR__BAAUDIOCONTROLWM8731_H +#define __BAGUITAR__BAAUDIOCONTROLWM8731_H namespace BAGuitar { @@ -128,4 +128,4 @@ private: } /* namespace BAGuitar */ -#endif /* __INC_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 0e3119e..1fc8319 100644 --- a/src/BAGuitar.h +++ b/src/BAGuitar.h @@ -18,14 +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 0f13306..5637bca 100644 --- a/src/BAHardware.h +++ b/src/BAHardware.h @@ -20,8 +20,10 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef SRC_BAHARDWARE_H_ -#define SRC_BAHARDWARE_H_ +#ifndef __BAGUTIAR_BAHARDWARE_H +#define __BAGUTIAR_BAHARDWARE_H + +#include /**************************************************************************//** * BAGuitar is a namespace/Library for Guitar processing from Blackaddr Audio. @@ -57,19 +59,24 @@ 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 }; -constexpr int MEM0_MAX_ADDR = 131071; ///< Max address size per chip -constexpr int MEM1_MAX_ADDR = 131071; ///< Max address size per chip + +/**************************************************************************//** + * Set the maximum address (byte-based) in the external SPI memories + *****************************************************************************/ +constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 }; + /**************************************************************************//** * 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 @@ -83,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.cpp b/src/BASpiMemory.cpp deleted file mode 100644 index 36f9522..0000000 --- a/src/BASpiMemory.cpp +++ /dev/null @@ -1,155 +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_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; - - -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); - -} - -BASpiMemory::~BASpiMemory() { -} - -// Single address write -void BASpiMemory::write(int address, int 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); -} - -void BASpiMemory::write16(int 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); -} - -// single address read -int BASpiMemory::read(int 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; -} - -uint16_t BASpiMemory::read16(int 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; -} - -} /* namespace BAGuitar */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index fc5450e..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,11 +20,13 @@ * 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 +#include "BATypes.h" #include "BAHardware.h" namespace BAGuitar { @@ -39,43 +42,174 @@ 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(); - void begin(void); + /// 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(int address, int data); + void write(size_t address, uint8_t data); - void write16(int address, uint16_t data); + /// 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); + + /// 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 - int read(int address); + uint8_t read(size_t address); + + /// 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 /// @return the data that was read - uint16_t read16(int address); + uint16_t read16(size_t address); -private: + /// 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 + 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: 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; + +}; + + +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. + 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. + BASpiMemoryDMA(SpiDeviceId memDeviceId, uint32_t speedHz); + virtual ~BASpiMemoryDMA(); + + /// 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; + + /// 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 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 + /// @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; + + /// Check if a DMA write is in progress + /// @returns true if a write DMA is in progress, else false + bool isWriteBusy() const; + + /// Check if a DMA read is in progress + /// @returns true if a read DMA is in progress, else false + bool isReadBusy() const; + + /// 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: + + DmaSpiGeneric *m_spiDma = nullptr; + AbstractChipSelect *m_cs = nullptr; + + uint8_t *m_txCommandBuffer = nullptr; + DmaSpi::Transfer *m_txTransfer; + 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); }; -class BASpiMemoryException {}; } /* namespace BAGuitar */ -#endif /* __SRC_BASPIMEMORY_H */ +#endif /* __BAGUITAR_BASPIMEMORY_H */ diff --git a/src/BATypes.h b/src/BATypes.h new file mode 100644 index 0000000..294cb42 --- /dev/null +++ b/src/BATypes.h @@ -0,0 +1,158 @@ +/**************************************************************************//** + * @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.* + * + * 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 __BAGUITAR_BATYPES_H +#define __BAGUITAR_BATYPES_H + +namespace BAGuitar { + +#define UNUSED(x) (void)(x) + +/**************************************************************************//** + * 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 /* __BAGUITAR_BATYPES_H */ diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h new file mode 100644 index 0000000..b035031 --- /dev/null +++ b/src/LibBasicFunctions.h @@ -0,0 +1,283 @@ +/**************************************************************************//** + * @file + * @author Steve Lascos + * @company Blackaddr Audio + * + * 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 + +#include +#include "Arduino.h" +#include "Audio.h" + +#include "BATypes.h" +#include "LibMemoryManagement.h" + +#ifndef __BAGUITAR_LIBBASICFUNCTIONS_H +#define __BAGUITAR_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; ///< 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); + +/// 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); + +/// 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. +/// @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); + +/// 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. + + +/**************************************************************************//** + * 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. + *****************************************************************************/ +constexpr size_t AUDIO_BLOCK_SIZE = sizeof(int16_t)*AUDIO_BLOCK_SAMPLES; +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(); + + /// 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 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 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: + + /// 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. +}; + +/**************************************************************************//** + * 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; + /// 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; + int32_t *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_casd_df1_inst_q31 m_iirCfg; + 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; + float *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_cascade_df2T_instance_f32 m_iirCfg; + float *m_state = nullptr; + +}; + +} + + +#endif /* __BAGUITAR_LIBBASICFUNCTIONS_H */ diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h new file mode 100644 index 0000000..bb450d1 --- /dev/null +++ b/src/LibMemoryManagement.h @@ -0,0 +1,212 @@ +/**************************************************************************//** + * @file + * @author Steve Lascos + * @company Blackaddr Audio + * + * 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 __BAGUITAR_LIBMEMORYMANAGEMENT_H +#define __BAGUITAR_LIBMEMORYMANAGEMENT_H + +#include + +#include "BAHardware.h" +#include "BASpiMemory.h" + +namespace BAGuitar { + +/**************************************************************************//** + * MemConfig contains the configuration information associated with a particular + * SPI interface. + *****************************************************************************/ +struct MemConfig { + 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 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(); + + /// 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; } + + /// 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); + + /// 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 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 offsetWords, int16_t *src, size_t numWords); + + /// Write a block of zeros (16-bit) to the memory at the specified offset + /// @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 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 numWords number of 16-bit words to transfer + /// @returns true on success, else false on error + 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 + 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, 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 + /// @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 + + /// 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; + + bool isUseDma() const { return m_useDma; } + + bool isWriteBusy() const; + + bool isReadBusy() const; + + /// DEBUG USE: prints out the slot member variables + void printStatus(void) const; + +private: + 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 + 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 +}; + + +/**************************************************************************//** + * 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(); + + /// The manager is constructed by specifying how many external memories to handle allocations for + /// @param numMemories the number of external memories + ExternalSramManager(unsigned numMemories); + virtual ~ExternalSramManager(); + + /// 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 + /// @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); + + /// 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 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); + +private: + 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 /* __LIBMEMORYMANAGEMENT_H */ 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..ae7cfcf --- /dev/null +++ b/src/common/AudioHelpers.cpp @@ -0,0 +1,78 @@ +/* + * 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); +} + +float calcAudioTimeMs(size_t numSamples) +{ + return ((float)(numSamples) / AUDIO_SAMPLE_RATE_EXACT) * 1000.0f; +} + +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) +{ + // 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 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/common/ExtMemSlot.cpp b/src/common/ExtMemSlot.cpp new file mode 100644 index 0000000..57385d7 --- /dev/null +++ b/src/common/ExtMemSlot.cpp @@ -0,0 +1,235 @@ +/* + * ExtMemSlot.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 { + +///////////////////////////////////////////////////////////////////////////// +// MEM SLOT +///////////////////////////////////////////////////////////////////////////// +bool ExtMemSlot::clear() +{ + if (!m_valid) { return false; } + m_spi->zero16(m_start, m_size); + return true; +} + +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 offsetWords, int16_t *src, size_t numWords) +{ + if (!m_valid) { return false; } + 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(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 + return false; + } +} + +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 offsetWords, size_t numWords) +{ + if (!m_valid) { return false; } + 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 + return true; + } else { + // this would go past the end of the memory slot, do not perform the write + return false; + } +} + +bool ExtMemSlot::read16(size_t offsetWords, int16_t *dest, size_t numWords) +{ + if (!dest) return false; // invalid destination + size_t readOffset = m_start + sizeof(int16_t)*offsetWords; + size_t numBytes = sizeof(int16_t)*numWords; + + 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 read + return false; + } +} + +uint16_t ExtMemSlot::readAdvance16() +{ + uint16_t val = m_spi->read16(m_currentRdPosition); + if (m_currentRdPosition < m_end-1) { + m_currentRdPosition +=2; // position is in bytes and we read two + } else { + m_currentRdPosition = m_start; + } + return val; +} + +bool ExtMemSlot::readAdvance16(int16_t *dest, 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_currentRdPosition, 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) +{ + if (!m_valid) { return false; } + 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(src), numWords); // cast audio data to uint. + m_currentWrPosition += numBytes; + + } else { + // this write will wrap the memory slot + 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::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; + + } else { + // 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::writeAdvance16(int16_t data) +{ + if (!m_valid) { return false; } + + m_spi->write16(m_currentWrPosition, static_cast(data)); + if (m_currentWrPosition < m_end-1) { + m_currentWrPosition+=2; // wrote two bytes + } else { + m_currentWrPosition = m_start; + } + 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; +} + +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 + \ + String(" m_end:") + m_end + String(" m_currentWrPosition: ") + m_currentWrPosition + \ + String(" m_currentRdPosition: ") + m_currentRdPosition + \ + String(" m_size:") + m_size); +} + +} + diff --git a/src/common/ExternalSramManager.cpp b/src/common/ExternalSramManager.cpp new file mode 100644 index 0000000..cc0f7f9 --- /dev/null +++ b/src/common/ExternalSramManager.cpp @@ -0,0 +1,120 @@ +/* + * 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() +: ExternalSramManager(1) +{ + +} + +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 +#include "AudioEffectAnalogDelay.h" + +namespace BAGuitar { + +constexpr int MIDI_CHANNEL = 0; +constexpr int MIDI_CONTROL = 1; + +// 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 maxDelayMs) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new AudioDelay(maxDelayMs); + m_maxDelaySamples = calcAudioSamples(maxDelayMs); + m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); +} + +AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) +: AudioStream(1, m_inputQueueArray) +{ + 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 +AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new AudioDelay(slot); + m_maxDelaySamples = (slot->size() / sizeof(int16_t)); + 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 + + // Check is block is disabled + if (m_enable == false) { + // 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; + } + + // 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); + } + } + transmit(inputAudioBlock, 0); + release(inputAudioBlock); + return; + } + + // 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); + + + // BACK TO OUTPUT PROCESSING + // Check if external DMA, if so, we need to be sure the read is completed + if (m_externalMemory && m_memory->getSlot()->isUseDma()) { + // Using DMA + while (m_memory->getSlot()->isReadBusy()) {} + } + + // perform the wet/dry mix mix + m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); + transmit(blockToOutput); + + release(inputAudioBlock); + 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_blockToRelease) release(m_blockToRelease); + m_blockToRelease = blockToRelease; +} + +void AudioEffectAnalogDelay::delay(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); + 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) { Serial.println("ERROR: slot ptr is not valid"); } + if (!slot->isEnabled()) { + slot->enable(); + Serial.println("WEIRD: slot was not enabled"); + } + } + + m_delaySamples = delaySamples; +} + +void AudioEffectAnalogDelay::delay(size_t delaySamples) +{ + 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(); + } + } + 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[DELAY][MIDI_CHANNEL] == channel) && + (m_midiConfig[DELAY][MIDI_CONTROL] == control)) { + // Delay + 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; + } + + 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[FEEDBACK][MIDI_CHANNEL] == channel) && + (m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { + // Feedback + Serial.println(String("AudioEffectAnalogDelay::feedback: ") + 100*val + String("%")); + feedback(val); + return; + } + + if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIX][MIDI_CONTROL] == control)) { + // Mix + Serial.println(String("AudioEffectAnalogDelay::mix: Dry: ") + 100*(1-val) + String("% Wet: ") + 100*val ); + mix(val); + return; + } + + if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && + (m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { + // Volume + Serial.println(String("AudioEffectAnalogDelay::volume: ") + 100*val + String("%")); + volume(val); + return; + } + +} + +void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel) +{ + if (parameter >= NUM_CONTROLS) { + return ; // Invalid midi parameter + } + m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; + m_midiConfig[parameter][MIDI_CONTROL] = midiCC; +} + +} + + 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..3c0403d --- /dev/null +++ b/src/peripherals/BASpiMemory.cpp @@ -0,0 +1,471 @@ +/* + * 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; +#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; + } + + // 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; +#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; + } + + 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 */