/*
* Copyright (c) 2018 John-Michael Reed
* bleeplabs.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Arduino.h>
#include "effect_granular_ext.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.
//
#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 MEMBOARD_CS0_PIN 2
#define MEMBOARD_CS1_PIN 3
#define MEMBOARD_CS2_PIN 4
void AudioEffectGranularExternal::begin(int16_t *sample_bank_def, int16_t max_len_def)
{
max_sample_len = max_len_def;
grain_mode = 0;
read_head = 0;
write_head = 0;
prev_input = 0;
playpack_rate = 65536;
accumulator = 0;
allow_len_change = true;
sample_loaded = false;
sample_bank = sample_bank_def;
}
void AudioEffectGranularExternal::beginFreeze_int(int grain_samples)
{
__disable_irq();
grain_mode = 1;
if (grain_samples < max_sample_len) {
freeze_len = grain_samples;
} else {
freeze_len = grain_samples;
}
sample_loaded = false;
write_en = false;
sample_req = true;
__enable_irq();
}
void AudioEffectGranularExternal::beginPitchShift_int(int grain_samples)
{
__disable_irq();
grain_mode = 2;
if (allow_len_change) {
if (grain_samples < 100) grain_samples = 100;
int maximum = (max_sample_len - 1) / 3;
if (grain_samples > maximum) grain_samples = maximum;
glitch_len = grain_samples;
}
sample_loaded = false;
write_en = false;
sample_req = true;
__enable_irq();
}
void AudioEffectGranularExternal::stop()
{
grain_mode = 0;
allow_len_change = true;
}
void AudioEffectGranularExternal::update(void)
{
audio_block_t *block;
// This is where the magic needs to happen
if (sample_bank == NULL) {
//block = receiveReadOnly(0);
//if (block) release(block); // Copied the following from delay_ext:
block = receiveReadOnly();
if (memory_type >= AUDIO_MEMORY_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);
}
}
return; // ?
}
block = receiveWritable(0); // receiveWritable?
if (!block) return;
// I don’t understand enough of how the code works to merge these functions together
if (grain_mode == 0)
{
// passthrough, no granular effect
prev_input = block->data[AUDIO_BLOCK_SAMPLES-1];
}
else if (grain_mode == 1) {
// Freeze - sample 1 grain, then repeatedly play it back
for (int j = 0; j < AUDIO_BLOCK_SAMPLES; j++) {
if (sample_req) {
// only begin capture on zero cross
int16_t current_input = block->data[j];
if ((current_input < 0 && prev_input >= 0) ||
(current_input >= 0 && prev_input < 0)) {
write_en = true;
write_head = 0;
read_head = 0;
sample_req = false;
} else {
prev_input = current_input;
}
}
if (write_en) {
sample_bank[write_head++] = block->data[j];
if (write_head >= freeze_len) {
sample_loaded = true;
}
if (write_head >= max_sample_len) {
write_en = false;
}
}
if (sample_loaded) {
if (playpack_rate >= 0) {
accumulator += playpack_rate;
read_head = accumulator >> 16;
}
if (read_head >= freeze_len) {
accumulator = 0;
read_head = 0;
}
block->data[j] = sample_bank[read_head];
}
}
}
else if (grain_mode == 2) {
//GLITCH SHIFT
//basic granular synth thingy
// the shorter the sample the max_sample_len the more tonal it is.
// Longer it has more definition. It's a bit roboty either way which
// is obv great and good enough for noise music.
for (int k = 0; k < AUDIO_BLOCK_SAMPLES; k++) {
// only start recording when the audio is crossing zero to minimize pops
if (sample_req) {
int16_t current_input = block->data[k];
if ((current_input < 0 && prev_input >= 0) ||
(current_input >= 0 && prev_input < 0)) {
write_en = true;
} else {
prev_input = current_input;
}
}
if (write_en) {
sample_req = false;
allow_len_change = true; // Reduces noise by not allowing the
// length to change after the sample has been
// recored. Kind of not too much though
if (write_head >= glitch_len) {
write_head = 0;
sample_loaded = true;
write_en = false;
allow_len_change = false;
}
sample_bank[write_head] = block->data[k];
write_head++;
}
if (sample_loaded) {
//move it to the middle third of the bank.
//3 "seperate" banks are used
float fade_len = 20.00;
int16_t m2 = fade_len;
for (int m = 0; m < 2; m++) {
// I'm off by one somewhere? why is there a tick at the
// beginning of this only when it's combined with the
// fade out???? ooor am i osbserving that incorrectly
// either wait it works enough
sample_bank[m + glitch_len] = 0;
}
for (int m = 2; m < glitch_len-m2; m++) {
sample_bank[m + glitch_len] = sample_bank[m];
}
for (int m = glitch_len-m2; m < glitch_len; m++) {
// fade out the end. You can just make fadet=0
// but it's a little too daleky
float fadet = sample_bank[m] * (m2 / fade_len);
sample_bank[m + glitch_len] = (int16_t)fadet;
m2--;
}
sample_loaded = false;
prev_input = block->data[k];
sample_req = true;
}
accumulator += playpack_rate;
read_head = (accumulator >> 16);
if (read_head >= glitch_len) {
read_head -= glitch_len;
accumulator = 0;
for (int m = 0; m < glitch_len; m++) {
sample_bank[m + (glitch_len*2)] = sample_bank[m+glitch_len];
// sample_bank[m + (glitch_len*2)] = (m%20)*1000;
}
}
block->data[k] = sample_bank[read_head + (glitch_len*2)];
}
}
transmit(block);
release(block);
}
// Notes: AudioEffectGranularExternal needs Initialise, Read and Write functions to talk to the SPI memory chip. What’s happening and how to get it out and mapped back into the granular function is not the easiest for me to work out.
//Also unsure what this line means in the delay code:
//uint32_t AudioEffectDelayExternal::allocated[2] = {0, 0};
//Granular function looks like it’s moving the samples between three different banks, sample_req (Record?), write_en and sample_loaded. Record, write and loaded I suspect it should be re-written after the “sample_bank == NULL” part whether is still needs wrapping in that section
void AudioEffectGranularExternal::initialize(AudioEffectDelayMemoryType_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.begin();
if (type == AUDIO_MEMORY_23LC1024) {
#ifdef INTERNAL_TEST
memsize = 8000;
#else
memsize = 65536;
#endif
pinMode(SPIRAM_CS_PIN, OUTPUT);
digitalWriteFast(SPIRAM_CS_PIN, HIGH);
} else if (type == AUDIO_MEMORY_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_MEMORY_CY15B104) {
#ifdef INTERNAL_TEST
memsize = 8000;
#else
memsize = 262144;
#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_MEMORY_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 AudioEffectGranularExternal::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_MEMORY_23LC1024 ||
memory_type == AUDIO_MEMORY_CY15B104) {
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_MEMORY_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 AudioEffectGranularExternal::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_MEMORY_23LC1024) {
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_MEMORY_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_MEMORY_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
}