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