commit
d7fdb462d3
@ -0,0 +1,142 @@ |
|||||||
|
#include <MIDI.h> |
||||||
|
#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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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 |
||||||
|
}; |
@ -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 <Wire.h> |
||||||
|
#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<numBytes; i++) { |
||||||
|
if (a[i] != b[i]) { |
||||||
|
if (errorCount < 10) { |
||||||
|
Serial.print(i); Serial.print(":: a:"); Serial.print(a[i]); Serial.print(" does not match b:"); Serial.println(b[i]); |
||||||
|
pass=false; |
||||||
|
errorCount++; |
||||||
|
} else { return false; } |
||||||
|
} |
||||||
|
} |
||||||
|
return pass; |
||||||
|
} |
||||||
|
|
||||||
|
bool compareBuffers16(uint16_t *a, uint16_t *b, size_t numWords) |
||||||
|
{ |
||||||
|
return compareBuffers(reinterpret_cast<uint8_t *>(a), reinterpret_cast<uint8_t*>(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<DMA_SIZE; i++) { |
||||||
|
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81); |
||||||
|
} |
||||||
|
break; |
||||||
|
}
|
||||||
|
|
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem0.write(spiAddress, src8, DMA_SIZE); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem0.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Read back the data using 8-bit transfers
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < TEST_END) { |
||||||
|
// generate the golden data
|
||||||
|
switch (spiPhase) { |
||||||
|
case 0 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Read back the DMA data
|
||||||
|
spiMem0.read(spiAddress, dest8, DMA_SIZE); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem0.isReadBusy()) {} |
||||||
|
if (!compareBuffers(src8, dest8, DMA_SIZE)) {
|
||||||
|
Serial.println(String("ERROR @") + spiAddress); |
||||||
|
return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String("Phase ") + spiPhase + String(": 8-bit Write/Read test passed!")); |
||||||
|
spiPhase++; |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef SANITY |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
Serial.print(src8[i], HEX); Serial.print("="); Serial.println(dest8[i],HEX); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
Serial.println("\nStarting 8-bit test Zero/Read"); |
||||||
|
// Clear the memory
|
||||||
|
spiAddress = 0; |
||||||
|
memset(src8, 0, DMA_SIZE); |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem0.zero(spiAddress, DMA_SIZE); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem0.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Check the memory is clear
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Read back the DMA data
|
||||||
|
spiMem0.read(spiAddress, dest8, DMA_SIZE); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem0.isReadBusy()) {} |
||||||
|
if (!compareBuffers(src8, dest8, DMA_SIZE)) { return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String(": 8-bit Zero/Read test passed!")); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool spi16BitTest(void) { |
||||||
|
size_t spiAddress = 0; |
||||||
|
int spiPhase = 0; |
||||||
|
constexpr uint16_t MASK160 = 0xaaaa; |
||||||
|
constexpr uint16_t MASK161 = 0x5555; |
||||||
|
constexpr int NUM_DATA = DMA_SIZE / sizeof(uint16_t); |
||||||
|
|
||||||
|
uint16_t src16[NUM_DATA]; |
||||||
|
uint16_t dest16[NUM_DATA]; |
||||||
|
|
||||||
|
// Write to the memory using 16-bit transfers
|
||||||
|
Serial.println("\nStarting 16-bit test"); |
||||||
|
while (spiPhase < 4) { |
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
|
||||||
|
// fill the write data buffer
|
||||||
|
switch (spiPhase) { |
||||||
|
case 0 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem0.write16(spiAddress, src16, NUM_DATA); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem0.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Read back the data using 8-bit transfers
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// generate the golden data
|
||||||
|
switch (spiPhase) { |
||||||
|
case 0 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Read back the DMA data
|
||||||
|
spiMem0.read16(spiAddress, dest16, NUM_DATA); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem0.isReadBusy()) {} |
||||||
|
if (!compareBuffers16(src16, dest16, NUM_DATA)) {
|
||||||
|
Serial.print("ERROR @"); Serial.println(spiAddress, HEX); |
||||||
|
return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String("Phase ") + spiPhase + String(": 16-bit test passed!")); |
||||||
|
spiPhase++; |
||||||
|
} |
||||||
|
#ifdef SANITY |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
Serial.print(src16[i], HEX); Serial.print("="); Serial.println(dest16[i],HEX); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
Serial.println("\nStarting 16-bit test Zero/Read"); |
||||||
|
// Clear the memory
|
||||||
|
spiAddress = 0; |
||||||
|
memset(src16, 0, DMA_SIZE); |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem0.zero16(spiAddress, NUM_DATA); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem0.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Check the memory is clear
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Read back the DMA data
|
||||||
|
spiMem0.read16(spiAddress, dest16, NUM_DATA); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem0.isReadBusy()) {} |
||||||
|
if (!compareBuffers16(src16, dest16, NUM_DATA)) { return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String(": 16-bit Zero/Read test passed!")); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
spi8BitTest(); |
||||||
|
spi16BitTest(); |
||||||
|
while(true) {} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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 MEM1 attached |
||||||
|
* to SPI1. |
||||||
|
*
|
||||||
|
* NOTE: SPI MEM1 can be used by a Teensy 3.5/3.6. |
||||||
|
* pins. |
||||||
|
*
|
||||||
|
*/ |
||||||
|
#include <Wire.h> |
||||||
|
#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<numBytes; i++) { |
||||||
|
if (a[i] != b[i]) { |
||||||
|
if (errorCount < 10) { |
||||||
|
Serial.print(i); Serial.print(":: a:"); Serial.print(a[i]); Serial.print(" does not match b:"); Serial.println(b[i]); |
||||||
|
pass=false; |
||||||
|
errorCount++; |
||||||
|
} else { return false; } |
||||||
|
} |
||||||
|
} |
||||||
|
return pass; |
||||||
|
} |
||||||
|
|
||||||
|
bool compareBuffers16(uint16_t *a, uint16_t *b, size_t numWords) |
||||||
|
{ |
||||||
|
return compareBuffers(reinterpret_cast<uint8_t *>(a), reinterpret_cast<uint8_t*>(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<DMA_SIZE; i++) { |
||||||
|
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81); |
||||||
|
} |
||||||
|
break; |
||||||
|
}
|
||||||
|
|
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem1.write(spiAddress, src8, DMA_SIZE); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem1.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Read back the data using 8-bit transfers
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < TEST_END) { |
||||||
|
// generate the golden data
|
||||||
|
switch (spiPhase) { |
||||||
|
case 0 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ( (spiAddress+i) & 0xff) ^ MASK80; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ MASK81; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK80); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
src8[i] = ((spiAddress+i) & 0xff) ^ (!MASK81); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Read back the DMA data
|
||||||
|
spiMem1.read(spiAddress, dest8, DMA_SIZE); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem1.isReadBusy()) {} |
||||||
|
if (!compareBuffers(src8, dest8, DMA_SIZE)) {
|
||||||
|
Serial.println(String("ERROR @") + spiAddress); |
||||||
|
return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String("Phase ") + spiPhase + String(": 8-bit Write/Read test passed!")); |
||||||
|
spiPhase++; |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef SANITY |
||||||
|
for (int i=0; i<DMA_SIZE; i++) { |
||||||
|
Serial.print(src8[i], HEX); Serial.print("="); Serial.println(dest8[i],HEX); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
Serial.println("\nStarting 8-bit test Zero/Read"); |
||||||
|
// Clear the memory
|
||||||
|
spiAddress = 0; |
||||||
|
memset(src8, 0, DMA_SIZE); |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem1.zero(spiAddress, DMA_SIZE); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem1.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Check the memory is clear
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Read back the DMA data
|
||||||
|
spiMem1.read(spiAddress, dest8, DMA_SIZE); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem1.isReadBusy()) {} |
||||||
|
if (!compareBuffers(src8, dest8, DMA_SIZE)) { return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String(": 8-bit Zero/Read test passed!")); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool spi16BitTest(void) { |
||||||
|
size_t spiAddress = 0; |
||||||
|
int spiPhase = 0; |
||||||
|
constexpr uint16_t MASK160 = 0xaaaa; |
||||||
|
constexpr uint16_t MASK161 = 0x5555; |
||||||
|
constexpr int NUM_DATA = DMA_SIZE / sizeof(uint16_t); |
||||||
|
|
||||||
|
uint16_t src16[NUM_DATA]; |
||||||
|
uint16_t dest16[NUM_DATA]; |
||||||
|
|
||||||
|
// Write to the memory using 16-bit transfers
|
||||||
|
Serial.println("\nStarting 16-bit test"); |
||||||
|
while (spiPhase < 4) { |
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
|
||||||
|
// fill the write data buffer
|
||||||
|
switch (spiPhase) { |
||||||
|
case 0 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem1.write16(spiAddress, src16, NUM_DATA); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem1.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Read back the data using 8-bit transfers
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// generate the golden data
|
||||||
|
switch (spiPhase) { |
||||||
|
case 0 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ( (spiAddress+i) & 0xffff) ^ MASK160; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 1 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ MASK161; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 2 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK160); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 3 : |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
src16[i] = ((spiAddress+i) & 0xffff) ^ (!MASK161); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Read back the DMA data
|
||||||
|
spiMem1.read16(spiAddress, dest16, NUM_DATA); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem1.isReadBusy()) {} |
||||||
|
if (!compareBuffers16(src16, dest16, NUM_DATA)) {
|
||||||
|
Serial.print("ERROR @"); Serial.println(spiAddress, HEX); |
||||||
|
return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String("Phase ") + spiPhase + String(": 16-bit test passed!")); |
||||||
|
spiPhase++; |
||||||
|
} |
||||||
|
#ifdef SANITY |
||||||
|
for (int i=0; i<NUM_DATA; i++) { |
||||||
|
Serial.print(src16[i], HEX); Serial.print("="); Serial.println(dest16[i],HEX); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
Serial.println("\nStarting 16-bit test Zero/Read"); |
||||||
|
// Clear the memory
|
||||||
|
spiAddress = 0; |
||||||
|
memset(src16, 0, DMA_SIZE); |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Send the DMA transfer
|
||||||
|
spiMem1.zero16(spiAddress, NUM_DATA); |
||||||
|
// wait until write is done
|
||||||
|
while (spiMem1.isWriteBusy()) {} |
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
|
||||||
|
// Check the memory is clear
|
||||||
|
spiAddress = 0; |
||||||
|
while (spiAddress < SPI_MAX_ADDR) { |
||||||
|
// Read back the DMA data
|
||||||
|
spiMem1.read16(spiAddress, dest16, NUM_DATA); |
||||||
|
// wait until read is done
|
||||||
|
while (spiMem1.isReadBusy()) {} |
||||||
|
if (!compareBuffers16(src16, dest16, NUM_DATA)) { return false; } // stop the test
|
||||||
|
spiAddress += DMA_SIZE;
|
||||||
|
} |
||||||
|
Serial.println(String(": 16-bit Zero/Read test passed!")); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
spi8BitTest(); |
||||||
|
spi16BitTest(); |
||||||
|
while(true) {} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,162 @@ |
|||||||
|
/**************************************************************************//**
|
||||||
|
* @file |
||||||
|
* @author Steve Lascos |
||||||
|
* @company Blackaddr Audio |
||||||
|
* |
||||||
|
* AudioEffectAnalogDelay is a class for simulating a classic BBD based delay |
||||||
|
* like the Boss DM-2. This class works with either internal RAM, or external |
||||||
|
* SPI RAM for longer delays. The exteranl ram uses DMA to minimize load on the |
||||||
|
* CPU. |
||||||
|
* |
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*****************************************************************************/ |
||||||
|
|
||||||
|
#ifndef __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H |
||||||
|
#define __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H |
||||||
|
|
||||||
|
#include <Audio.h> |
||||||
|
#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 */ |
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#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 */ |
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*****************************************************************************/ |
||||||
|
|
||||||
|
#ifndef __BAGUITAR_BATYPES_H |
||||||
|
#define __BAGUITAR_BATYPES_H |
||||||
|
|
||||||
|
namespace BAGuitar { |
||||||
|
|
||||||
|
#define UNUSED(x) (void)(x) |
||||||
|
|
||||||
|
/**************************************************************************//**
|
||||||
|
* Customer RingBuffer with random access |
||||||
|
*****************************************************************************/ |
||||||
|
template <class T> |
||||||
|
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; idx<m_maxSize; idx++) { |
||||||
|
Serial.print(idx + String(" address: ")); Serial.print((uint32_t)m_buffer[idx], HEX); |
||||||
|
Serial.print(" data: "); Serial.println((uint32_t)m_buffer[idx]->data, HEX); |
||||||
|
} |
||||||
|
} |
||||||
|
private: |
||||||
|
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 */ |
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*****************************************************************************/ |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
#include <new> |
||||||
|
|
||||||
|
#include <arm_math.h> |
||||||
|
#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 <br>
|
||||||
|
/// 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 <br>
|
||||||
|
/// 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 T> |
||||||
|
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.<br> |
||||||
|
* 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<audio_block_t*> *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<audio_block_t *> *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 <br> |
||||||
|
* y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]<br> |
||||||
|
* 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. <br> |
||||||
|
* {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 */ |
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*****************************************************************************/ |
||||||
|
|
||||||
|
#ifndef __BAGUITAR_LIBMEMORYMANAGEMENT_H |
||||||
|
#define __BAGUITAR_LIBMEMORYMANAGEMENT_H |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
|
||||||
|
#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 */ |
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
#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<audio_block_t *>(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<void*>(dest->data), 0, numSamples * sizeof(int16_t)); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (position.offset == 0) { |
||||||
|
// single transfer
|
||||||
|
memcpy(static_cast<void*>(dest->data), static_cast<void*>(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<void*>(destStart), static_cast<void*>(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<void*>(destStart), static_cast<void*>(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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
#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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
#include <cstring> |
||||||
|
#include <new> |
||||||
|
|
||||||
|
#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<uint16_t*>(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<uint16_t*>(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<uint16_t*>(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<uint16_t*>(dest), rdDataNum); |
||||||
|
size_t remainingData = numWords - rdDataNum; |
||||||
|
m_spi->read16(m_start, reinterpret_cast<uint16_t*>(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<uint16_t*>(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<uint16_t*>(src), wrDataNum); |
||||||
|
size_t remainingData = numWords - wrDataNum; |
||||||
|
m_spi->write16(m_start, reinterpret_cast<uint16_t*>(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<uint16_t>(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<BASpiMemoryDMA*>(m_spi))->isWriteBusy(); |
||||||
|
} else { return false; } |
||||||
|
} |
||||||
|
|
||||||
|
bool ExtMemSlot::isReadBusy() const |
||||||
|
{ |
||||||
|
if (m_useDma) { |
||||||
|
return (static_cast<BASpiMemoryDMA*>(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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
#include <cstring> |
||||||
|
#include <new> |
||||||
|
|
||||||
|
#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<BAGuitar::SpiDeviceId>(mem)); |
||||||
|
slot->m_useDma = true; |
||||||
|
} else { |
||||||
|
m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast<BAGuitar::SpiDeviceId>(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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
#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<numSamples; i++) { |
||||||
|
input32[i] = (int32_t)(input[i]); |
||||||
|
} |
||||||
|
|
||||||
|
arm_biquad_cascade_df1_fast_q31(&m_iirCfg, input32, output32, numSamples); |
||||||
|
|
||||||
|
for (size_t i=0; i<numSamples; i++) { |
||||||
|
output[i] = (int16_t)(output32[i] & 0xffff); |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// HIGH QUALITY
|
||||||
|
IirBiQuadFilterHQ::IirBiQuadFilterHQ(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 int64_t[4*numStages];; |
||||||
|
arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); |
||||||
|
} |
||||||
|
|
||||||
|
IirBiQuadFilterHQ::~IirBiQuadFilterHQ() |
||||||
|
{ |
||||||
|
if (m_coeffs) delete [] m_coeffs; |
||||||
|
if (m_state) delete [] m_state; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
bool IirBiQuadFilterHQ::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<numSamples; i++) { |
||||||
|
input32[i] = (int32_t)(input[i]); |
||||||
|
} |
||||||
|
|
||||||
|
arm_biquad_cas_df1_32x64_q31(&m_iirCfg, input32, output32, numSamples); |
||||||
|
|
||||||
|
for (size_t i=0; i<numSamples; i++) { |
||||||
|
output[i] = (int16_t)(output32[i] & 0xffff); |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// FLOAT
|
||||||
|
IirBiQuadFilterFloat::IirBiQuadFilterFloat(unsigned numStages, const float *coeffs) |
||||||
|
: NUM_STAGES(numStages) |
||||||
|
{ |
||||||
|
m_coeffs = new float[5*numStages]; |
||||||
|
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(float)); |
||||||
|
|
||||||
|
m_state = new float[4*numStages];; |
||||||
|
arm_biquad_cascade_df2T_init_f32(&m_iirCfg, numStages, m_coeffs, m_state); |
||||||
|
} |
||||||
|
|
||||||
|
IirBiQuadFilterFloat::~IirBiQuadFilterFloat() |
||||||
|
{ |
||||||
|
if (m_coeffs) delete [] m_coeffs; |
||||||
|
if (m_state) delete [] m_state; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
bool IirBiQuadFilterFloat::process(float *output, float *input, size_t numSamples) |
||||||
|
{ |
||||||
|
if (!output) return false; |
||||||
|
if (!input) { |
||||||
|
// send zeros
|
||||||
|
memset(output, 0, numSamples * sizeof(float)); |
||||||
|
} else { |
||||||
|
|
||||||
|
arm_biquad_cascade_df2T_f32(&m_iirCfg, input, output, numSamples); |
||||||
|
|
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,279 @@ |
|||||||
|
/*
|
||||||
|
* AudioEffectAnalogDelay.cpp |
||||||
|
* |
||||||
|
* Created on: Jan 7, 2018 |
||||||
|
* Author: slascos |
||||||
|
*/ |
||||||
|
#include <new> |
||||||
|
#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<const int32_t *>(&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<const int32_t *>(&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<const int32_t *>(&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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
#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; i<numWords; i++) { |
||||||
|
m_spi->transfer16(*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; i<numWords; i++) { |
||||||
|
m_spi->transfer16(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; i<numBytes; i++) { |
||||||
|
*dataPtr++ = m_spi->transfer(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; i<numWords; i++) { |
||||||
|
*dataPtr++ = m_spi->transfer16(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<uint8_t*>(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<uint8_t*>(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 */ |
Loading…
Reference in new issue