Hi robz,
thanks for testing this and creating a patch. I tried to integrate this into an additional library, but have some problems getting the whole thing to run. Unfortunately the whole thing seems to crash. The test program for the RAM works though. So I hope you have an idea what is going wrong?
Before posting my code, these are small changes I made:
- I renamed the class to AudioEffectDelayExternal8 and the enum type to AudioEffectDelayMemoryType8_t to be able to use the library next to the original library.
- I extended the array static uint32_t allocated[4]; to four (and initialized it with zeroes), because of the four different memory types used.
- Last, I reduced the memsize for initialization to 2^22 (see post above): memsize = (2^22); // 2^23 = 8388608 bytes, but 2^22 = 4194304 samples!
Here is my library code and my test program:
effect_delay_ext8.h:
Code:
#ifndef effect_delay_ext8_h_
#define effect_delay_ext8_h_
#include "Arduino.h"
#include "AudioStream.h"
#include "spi_interrupt.h"
enum AudioEffectDelayMemoryType8_t {
AUDIO_MEMORY8_23LC1024 = 0, // 128k x 8 S-RAM
AUDIO_MEMORY8_MEMORYBOARD = 1,
AUDIO_MEMORY8_CY15B104 = 2, // 512k x 8 F-RAM
AUDIO_MEMORY8_PSRAM_8MB = 3, // 8192k x 8 PS-RAM
AUDIO_MEMORY8_UNDEFINED = 4
};
class AudioEffectDelayExternal8 : public AudioStream
{
public:
AudioEffectDelayExternal8() : AudioStream(1, inputQueueArray) {
initialize(AUDIO_MEMORY8_23LC1024, 65536);
}
AudioEffectDelayExternal8(AudioEffectDelayMemoryType8_t type, float milliseconds=1e6)
: AudioStream(1, inputQueueArray) {
uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f;
initialize(type, n);
}
void delay(uint8_t channel, float milliseconds) {
if (channel >= 8 || memory_type >= AUDIO_MEMORY8_UNDEFINED) return;
if (milliseconds < 0.0f) milliseconds = 0.0f;
uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f;
n += AUDIO_BLOCK_SAMPLES;
if (n > memory_length - AUDIO_BLOCK_SAMPLES)
n = memory_length - AUDIO_BLOCK_SAMPLES;
delay_length[channel] = n;
uint8_t mask = activemask;
if (activemask == 0) AudioStartUsingSPI();
activemask = mask | (1<<channel);
}
void disable(uint8_t channel) {
if (channel >= 8) return;
uint8_t mask = activemask & ~(1<<channel);
activemask = mask;
if (mask == 0) AudioStopUsingSPI();
}
virtual void update(void);
private:
void initialize(AudioEffectDelayMemoryType8_t type, uint32_t samples);
void read(uint32_t address, uint32_t count, int16_t *data);
void write(uint32_t address, uint32_t count, const int16_t *data);
void zero(uint32_t address, uint32_t count) {
write(address, count, NULL);
}
uint32_t memory_begin; // the first address in the memory we're using
uint32_t memory_length; // the amount of memory we're using
uint32_t head_offset; // head index (incoming) data into external memory
uint32_t delay_length[8]; // # of sample delay for each channel (128 = no delay)
uint8_t activemask; // which output channels are active
uint8_t memory_type; // 0=23LC1024, 1=Frank's Memoryboard, 3=CY15B104 ,4=APS6404L-3SQR
static uint32_t allocated[4];
audio_block_t *inputQueueArray[1];
};
#endif
effect_delay_ext8.cpp
Code:
#include <Arduino.h>
#include "effect_delay_ext8.h"
//#define INTERNAL_TEST
// While 20 MHz (Teensy actually uses 16 MHz in most cases) and even 24 MHz
// have worked well in testing at room temperature with 3.3V power, to fully
// meet all the worst case timing specs, the SPI clock low time would need
// to be 40ns (12.5 MHz clock) for the single chip case and 51ns (9.8 MHz
// clock) for the 6-chip memoryboard with 74LCX126 buffers.
//
// Timing analysis and info is here:
// https://forum.pjrc.com/threads/29276-Limits-of-delay-effect-in-audio-library?p=97506&viewfull=1#post97506
#define SPISETTING SPISettings(20000000, MSBFIRST, SPI_MODE0)
// Use these with the audio adaptor board (should be adjustable by the user...)
//#define SPIRAM_MOSI_PIN 7
//#define SPIRAM_MISO_PIN 12
//#define SPIRAM_SCK_PIN 14
//#define SPIRAM_CS_PIN 6
#define SPIRAM_MOSI_PIN 11 //-----------------
#define SPIRAM_MISO_PIN 12 // for Teensy 4.1
#define SPIRAM_SCK_PIN 13 //
#define SPIRAM_CS_PIN 36 //-----------------
//
#define MEMBOARD_CS0_PIN 2
#define MEMBOARD_CS1_PIN 3
#define MEMBOARD_CS2_PIN 4
uint32_t AudioEffectDelayExternal8::allocated[4] = {0, 0, 0, 0};
void AudioEffectDelayExternal8::update(void)
{
audio_block_t *block;
uint32_t n, channel, read_offset;
// grab incoming data and put it into the memory
block = receiveReadOnly();
if (memory_type >= AUDIO_MEMORY8_UNDEFINED) {
// ignore input and do nothing if undefined memory type
release(block);
return;
}
if (block) {
if (head_offset + AUDIO_BLOCK_SAMPLES <= memory_length) {
// a single write is enough
write(head_offset, AUDIO_BLOCK_SAMPLES, block->data);
head_offset += AUDIO_BLOCK_SAMPLES;
} else {
// write wraps across end-of-memory
n = memory_length - head_offset;
write(head_offset, n, block->data);
head_offset = AUDIO_BLOCK_SAMPLES - n;
write(0, head_offset, block->data + n);
}
release(block);
} else {
// if no input, store zeros, so later playback will
// not be random garbage previously stored in memory
if (head_offset + AUDIO_BLOCK_SAMPLES <= memory_length) {
zero(head_offset, AUDIO_BLOCK_SAMPLES);
head_offset += AUDIO_BLOCK_SAMPLES;
} else {
n = memory_length - head_offset;
zero(head_offset, n);
head_offset = AUDIO_BLOCK_SAMPLES - n;
zero(0, head_offset);
}
}
// transmit the delayed outputs
for (channel = 0; channel < 8; channel++) {
if (!(activemask & (1<<channel))) continue;
block = allocate();
if (!block) continue;
// compute the delayed location where we read
if (delay_length[channel] <= head_offset) {
read_offset = head_offset - delay_length[channel];
} else {
read_offset = memory_length + head_offset - delay_length[channel];
}
if (read_offset + AUDIO_BLOCK_SAMPLES <= memory_length) {
// a single read will do it
read(read_offset, AUDIO_BLOCK_SAMPLES, block->data);
} else {
// read wraps across end-of-memory
n = memory_length - read_offset;
read(read_offset, n, block->data);
read(0, AUDIO_BLOCK_SAMPLES - n, block->data + n);
}
transmit(block, channel);
release(block);
}
}
void AudioEffectDelayExternal8::initialize(AudioEffectDelayMemoryType8_t type, uint32_t samples)
{
uint32_t memsize, avail;
activemask = 0;
head_offset = 0;
memory_type = type;
SPI.setMOSI(SPIRAM_MOSI_PIN);
SPI.setMISO(SPIRAM_MISO_PIN);
SPI.setSCK(SPIRAM_SCK_PIN);
SPI.setCS(SPIRAM_CS_PIN); // added for Teensy 4.1
SPI.begin();
if (type == AUDIO_MEMORY8_23LC1024) {
#ifdef INTERNAL_TEST
memsize = 8000;
#else
memsize = 65536;
#endif
pinMode(SPIRAM_CS_PIN, OUTPUT);
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
} else if (type == AUDIO_MEMORY8_MEMORYBOARD) {
memsize = 393216;
pinMode(MEMBOARD_CS0_PIN, OUTPUT);
pinMode(MEMBOARD_CS1_PIN, OUTPUT);
pinMode(MEMBOARD_CS2_PIN, OUTPUT);
digitalWriteFast(MEMBOARD_CS0_PIN, LOW);
digitalWriteFast(MEMBOARD_CS1_PIN, LOW);
digitalWriteFast(MEMBOARD_CS2_PIN, LOW);
} else if (type == AUDIO_MEMORY8_CY15B104) {
#ifdef INTERNAL_TEST
memsize = 8000;
#else
memsize = 262144;
#endif
pinMode(SPIRAM_CS_PIN, OUTPUT);
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
} else if (type == AUDIO_MEMORY8_PSRAM_8MB) {
#ifdef INTERNAL_TEST
memsize = 8000;
#else
memsize = (2^22); // 2^23 = 8388608 bytes
#endif
pinMode(SPIRAM_CS_PIN, OUTPUT);
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
} else {
return;
}
avail = memsize - allocated[type];
if (avail < AUDIO_BLOCK_SAMPLES*2+1) {
memory_type = AUDIO_MEMORY8_UNDEFINED;
return;
}
if (samples > avail) samples = avail;
memory_begin = allocated[type];
allocated[type] += samples;
memory_length = samples;
zero(0, memory_length);
}
#ifdef INTERNAL_TEST
static int16_t testmem[8000]; // testing only
#endif
void AudioEffectDelayExternal8::read(uint32_t offset, uint32_t count, int16_t *data)
{
uint32_t addr = memory_begin + offset;
#ifdef INTERNAL_TEST
while (count) { *data++ = testmem[addr++]; count--; } // testing only
#else
if (memory_type == AUDIO_MEMORY8_23LC1024 ||
memory_type == AUDIO_MEMORY8_CY15B104 ||
memory_type == AUDIO_MEMORY8_PSRAM_8MB)
{
addr *= 2;
SPI.beginTransaction(SPISETTING);
digitalWriteFast(SPIRAM_CS_PIN, LOW);
SPI.transfer16((0x03 << 8) | (addr >> 16));
SPI.transfer16(addr & 0xFFFF);
while (count) {
*data++ = (int16_t)(SPI.transfer16(0));
count--;
}
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
SPI.endTransaction();
} else if (memory_type == AUDIO_MEMORY8_MEMORYBOARD) {
SPI.beginTransaction(SPISETTING);
while (count) {
uint32_t chip = (addr >> 16) + 1;
digitalWriteFast(MEMBOARD_CS0_PIN, chip & 1);
digitalWriteFast(MEMBOARD_CS1_PIN, chip & 2);
digitalWriteFast(MEMBOARD_CS2_PIN, chip & 4);
uint32_t chipaddr = (addr & 0xFFFF) << 1;
SPI.transfer16((0x03 << 8) | (chipaddr >> 16));
SPI.transfer16(chipaddr & 0xFFFF);
uint32_t num = 0x10000 - (addr & 0xFFFF);
if (num > count) num = count;
count -= num;
addr += num;
do {
*data++ = (int16_t)(SPI.transfer16(0));
} while (--num > 0);
}
digitalWriteFast(MEMBOARD_CS0_PIN, LOW);
digitalWriteFast(MEMBOARD_CS1_PIN, LOW);
digitalWriteFast(MEMBOARD_CS2_PIN, LOW);
SPI.endTransaction();
}
#endif
}
void AudioEffectDelayExternal8::write(uint32_t offset, uint32_t count, const int16_t *data)
{
uint32_t addr = memory_begin + offset;
#ifdef INTERNAL_TEST
while (count) { testmem[addr++] = *data++; count--; } // testing only
#else
if (memory_type == AUDIO_MEMORY8_23LC1024 ||
memory_type == AUDIO_MEMORY8_PSRAM_8MB ) {
addr *= 2;
SPI.beginTransaction(SPISETTING);
digitalWriteFast(SPIRAM_CS_PIN, LOW);
SPI.transfer16((0x02 << 8) | (addr >> 16));
SPI.transfer16(addr & 0xFFFF);
while (count) {
int16_t w = 0;
if (data) w = *data++;
SPI.transfer16(w);
count--;
}
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
SPI.endTransaction();
} else if (memory_type == AUDIO_MEMORY8_CY15B104) {
addr *= 2;
SPI.beginTransaction(SPISETTING);
digitalWriteFast(SPIRAM_CS_PIN, LOW);
SPI.transfer(0x06); //write-enable before every write
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
asm volatile ("NOP\n NOP\n NOP\n NOP\n NOP\n NOP\n");
digitalWriteFast(SPIRAM_CS_PIN, LOW);
SPI.transfer16((0x02 << 8) | (addr >> 16));
SPI.transfer16(addr & 0xFFFF);
while (count) {
int16_t w = 0;
if (data) w = *data++;
SPI.transfer16(w);
count--;
}
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
SPI.endTransaction();
} else if (memory_type == AUDIO_MEMORY8_MEMORYBOARD) {
SPI.beginTransaction(SPISETTING);
while (count) {
uint32_t chip = (addr >> 16) + 1;
digitalWriteFast(MEMBOARD_CS0_PIN, chip & 1);
digitalWriteFast(MEMBOARD_CS1_PIN, chip & 2);
digitalWriteFast(MEMBOARD_CS2_PIN, chip & 4);
uint32_t chipaddr = (addr & 0xFFFF) << 1;
SPI.transfer16((0x02 << 8) | (chipaddr >> 16));
SPI.transfer16(chipaddr & 0xFFFF);
uint32_t num = 0x10000 - (addr & 0xFFFF);
if (num > count) num = count;
count -= num;
addr += num;
do {
int16_t w = 0;
if (data) w = *data++;
SPI.transfer16(w);
} while (--num > 0);
}
digitalWriteFast(MEMBOARD_CS0_PIN, LOW);
digitalWriteFast(MEMBOARD_CS1_PIN, LOW);
digitalWriteFast(MEMBOARD_CS2_PIN, LOW);
SPI.endTransaction();
}
#endif
}
... and the test program:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <effect_delay_ext8.h>
// GUItool: begin automatically generated code
AudioSynthWaveformSine sine1; //xy=382,432
AudioAmplifier amp1; //xy=606,441
AudioEffectDelayExternal8 delayExt1(AUDIO_MEMORY_PSRAM_8MB,95108); //xy=770,321
AudioMixer4 mixer1; //xy=983,386
AudioOutputI2S i2s1; //xy=1131,343
AudioConnection patchCord1(sine1, amp1);
AudioConnection patchCord2(amp1, delayExt1);
AudioConnection patchCord3(amp1, 0, mixer1, 1);
AudioConnection patchCord4(delayExt1, 0, mixer1, 0);
AudioConnection patchCord5(mixer1, 0, i2s1, 0);
AudioConnection patchCord6(mixer1, 0, i2s1, 1);
AudioControlSGTL5000 sgtl5000; //xy=943,541
// GUItool: end automatically generated code
void setup() {
AudioMemory(100);
sgtl5000.enable();
sgtl5000.lineOutLevel(29);
sgtl5000.dacVolumeRamp();
sgtl5000.dacVolume(1.0);
sgtl5000.unmuteHeadphone();
sgtl5000.unmuteLineout();
sgtl5000.volume(0.8,0.8); // Headphone volume
sgtl5000.audioProcessorDisable();
sgtl5000.autoVolumeDisable();
sgtl5000.surroundSoundDisable();
sgtl5000.enhanceBassDisable();
delayExt1.delay(0,100);
sine1.amplitude(1.0);
sine1.frequency(440);
sine1.phase(0.0);
mixer1.gain(0,0.5);
mixer1.gain(1,1.0);
amp1.gain(1.0);
}
void loop() {
amp1.gain(1.0);
delay(200);
amp1.gain(0.0);
delay(2000);
}
The first problem I am getting: error: 'AUDIO_MEMORY_PSRAM_8MB' was not declared in this scop, so I replaced it with "3" for 8MB memory. But my test program does not work - even serial messages are not shown. Do you have an idea what is going wrong?
TIA, Holger