Currently working on implementing some simple granular processing to do some pitch shifting. I’m only doing downward pitch shifting to keep it causal, no time delay. Got most of the theory from here.
This code “works,” as in the audio is pitched down for values of alpha < 1, but there is a strong high frequency noise artifact that I can’t get rid of. If you flash the code you can see what I mean. Changed the grain length, window period, different curves for the window; noise is unaffected.
Anything clearly wrong with the code? Not sure where this noise is coming from. Aliasing maybe? Bad windowing? Would also love if anyone has resources on granular processing or examples to share.
#include "daisy_pod.h"
#include "daisysp.h"
#include <cmath>
#define GRAIN_LEN 2048 // length of grain
#define TAPER_LEN 256 // length of window
#define GRAIN_STRIDE (GRAIN_LEN - TAPER_LEN) // spacing between grains
#define BUF_LEN 2048 // same as grain length
using namespace daisy;
using namespace daisysp;
const float pi_2 = 1.57079632; // pi/2
static float DSY_SDRAM_BSS buf[BUF_LEN]; //buffer array
float taper[TAPER_LEN]; // Window for lerp
int buf_index = 0; //input index
float alpha = 0.75; //playback speed
DaisyPod hw;
void Window(); //generate window
void WriteBuffer(float in); // add current input into buffer
float Resample(float alpha); // Resample at new rate fs * alpha, ALPHA < 1 TO REMAIN CAUSAL
float Lerp(float index_f); // linear interpolation between samples
void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
hw.ProcessAllControls();
for (size_t i = 0; i < size; i++)
{
WriteBuffer(in[0][i]); // add current input into buffer
out[0][i] = Resample(alpha); // resample from grain, also applies window and prev grain
buf_index++;
if (buf_index > GRAIN_STRIDE){ // reset and repeat
buf_index = 0;
}
}
}
int main(void)
{
hw.Init();
hw.SetAudioBlockSize(4); // number of samples handled per callback
hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
Window();
hw.StartAdc();
hw.StartAudio(AudioCallback);
while(1) {}
}
void Window(){
for (int i = 0; i < TAPER_LEN; i++){
taper[i] = pow(sin(i * pi_2 / TAPER_LEN), 2);
}
}
void WriteBuffer(float in){ // write sample to buffer including overlap
buf[buf_index] = in; // add current sample to buffer, "current grain"
if (buf_index < TAPER_LEN){ // for length of taper:
buf[buf_index + GRAIN_STRIDE] = in; // add current sample to "previous grain"
}
}
float Resample(float alpha){ //
float out = Lerp(alpha * buf_index); //resample current grain at fractional index
if (buf_index < TAPER_LEN){ // if in taper section
float prev_grain_index = alpha * (buf_index + GRAIN_STRIDE); //index buffer for prev grain resample
out *= taper[buf_index]; // taper
out += Lerp(prev_grain_index) * (1 - taper[buf_index]); //add from prev grain and taper
}
return out;
}
float Lerp(float index_f){
int i = (int)index_f; //get int index
float c1 = index_f - i; //get fractional
float c0 = 1 - c1;
return (c0 * buf[i]) + (c1 * buf[i + 1]); //interpolate
}