Anyone Doing Pitch Shifting?

Status
Not open for further replies.

lerxstrulz

Well-known member
Hey Everyone,

Just wondering if anyone is doing pitch shifting (i.e. voice changing/modulation) with Teensy 3.2 and Audio Shield?

I have been doing some research on how to accomplish this and have experimented with some code (using the existing audio library objects) to see if I could accomplish it but so far have not had any success.

Any help would be appreciated!

Thanks!
 
Yes, I used a delay object made by Byron Jacqout at Sparkfun. This object has a input for a modulation signal i.e the input determine the length of the delay. And then I feed that input with a triangle wave. Then the pitch will change up or down dependent of the direction and frequency of the triangle wave.
But, you need to have two "signal paths" and change the phase of each triangle wave and mix the signals in the end...

This article describe this:
https://se.mathworks.com/help/audio/examples/delay-based-pitch-shifter.html?requestedDomain=www.mathworks.com

The delay object can be found here:
https://github.com/Jacquot-SFE/Synth-drum/tree/master/teensy-based/fx/cosmic-delay-sketch
mod-delay.cpp and mod-delay.h is the files you will need.

I have some code that is functional but far from optimal!!
This is used with the openeffectsproject box and includes some other functions. You should look what is happening to waveform1 and 2 and you need to set up the delay buffers: delaybuf[LEN] ..

Pot 1 is used to adjust the pitch down.
Schematic can be found here:
http://openeffectsproject.com/wp-content/uploads/2017/03/Schematic2_2.pdf



Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Bounce.h> 
#include <EEPROM.h> 
#include "mod-delay.h"
using namespace std;

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define PIN            8  //changed from last rev //pin3 on rev 2, pin 8 on rev 2.1 ->
#define NUMPIXELS      10 //changed from last rev
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);


// GUItool: begin automatically generated code
AudioInputI2S            i2s2;           //xy=94,397
AudioSynthWaveform       waveform1;      //xy=95,302
AudioSynthWaveform       waveform2;      //xy=98,476
AudioSynthWaveformDc     dc1;            //xy=100,341
AudioAnalyzePeak         peak1;          //xy=251,372
AudioMixer4              inmix;          //xy=251,424
AudioMixer4              mixer2;         //xy=252,321
AudioMixer4              mixer3;         //xy=252,495
AudioSynthWaveformDc     dc2;            //xy=265,256
AudioFilterBiquad        biquad2;        //xy=377,364
AudioSynthWaveform       waveform4;      //xy=423,464
AudioFilterStateVariable filter3;        //xy=427,249
AudioEffectModDelay      xdly2;          //xy=501,399
AudioEffectModDelay      xdly;           //xy=503,327
AudioSynthWaveform       waveform3;      //xy=512,363
AudioAnalyzeNoteFrequency notefreq1;      //xy=554,236
AudioEffectMultiply      multiply1;      //xy=646,352
AudioEffectMultiply      multiply2;      //xy=650,404
AudioMixer4              outmix;         //xy=778,393
AudioSynthWaveform       waveform5;      //xy=869,493
AudioFilterBiquad        biquad1;        //xy=904,402
AudioMixer4              mixer1;         //xy=1036,441
AudioAnalyzePeak         peak2;          //xy=1037,537
AudioSynthWaveformDc     dc3;            //xy=1038,497
AudioEffectMultiply      multiply3;      //xy=1160,471
AudioOutputI2S           i2s1;           //xy=1292,473
AudioConnection          patchCord1(i2s2, 0, inmix, 0);
AudioConnection          patchCord2(i2s2, 0, peak1, 0);
AudioConnection          patchCord3(waveform1, 0, mixer2, 0);
AudioConnection          patchCord4(waveform2, 0, mixer3, 0);
AudioConnection          patchCord5(dc1, 0, mixer2, 1);
AudioConnection          patchCord6(dc1, 0, mixer3, 1);
AudioConnection          patchCord7(inmix, 0, mixer1, 1);
AudioConnection          patchCord8(inmix, 0, filter3, 0);
AudioConnection          patchCord9(inmix, biquad2);
AudioConnection          patchCord10(mixer2, 0, xdly, 1);
AudioConnection          patchCord11(mixer3, 0, xdly2, 1);
AudioConnection          patchCord12(dc2, 0, filter3, 1);
AudioConnection          patchCord13(biquad2, 0, xdly2, 0);
AudioConnection          patchCord14(biquad2, 0, xdly, 0);
AudioConnection          patchCord15(waveform4, 0, multiply2, 1);
AudioConnection          patchCord16(filter3, 0, notefreq1, 0);
AudioConnection          patchCord17(xdly2, 0, multiply2, 0);
AudioConnection          patchCord18(xdly, 0, multiply1, 0);
AudioConnection          patchCord19(waveform3, 0, multiply1, 1);
AudioConnection          patchCord20(multiply1, 0, outmix, 0);
AudioConnection          patchCord21(multiply2, 0, outmix, 1);
AudioConnection          patchCord22(outmix, biquad1);
AudioConnection          patchCord23(waveform5, peak2);
AudioConnection          patchCord24(waveform5, 0, mixer1, 2);
AudioConnection          patchCord25(biquad1, 0, mixer1, 0);
AudioConnection          patchCord26(mixer1, 0, multiply3, 0);
AudioConnection          patchCord27(dc3, 0, multiply3, 1);
AudioConnection          patchCord28(multiply3, 0, i2s1, 0);
AudioConnection          patchCord29(multiply3, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=579,550
// GUItool: end automatically generated code


// This is about max (97%!) for internal RAM
static const uint32_t LEN = 0x01500; //1500ok...
int16_t delaybuf[LEN];
int16_t delaybuf2[LEN];

uint32_t next;

int16_t ArbWave[256] = {0,0,0,0,7327,10362,12691,14654,16384,17947,19385,20724,21981,23170,24301,25381,26418,27415,28377,29308,30210,31086,31937,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,31937,31086,30210,29308,28377,27415,26418,25381,24301,
23170,21981,20724,19385,17947,16384,14654,12691,10362,7327,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

/***************/

//Pinout board rev1
int Pot1 = A6;    // select the input pin for the potentiometer
int Pot2 = A3;    // select the input pin for the potentiometer
int Pot3 = A2;    // select the input pin for the potentiometer
int Pot4 = A1;    // select the input pin for the potentiometer
int CV1 = A10;
int CV2 = A11;

int Tap1 = 2;
int Tap2 = 3;

int SW1 = A12;
int SW2 = A13;

//int ledPin2 = 4;      // select the pin for the LED
//int ledPin3 = 5;      // select the pin for the LED
int relayL = 4;      // select the pin for the RelayL
int relayR = 5;      // select the pin for the RelayR

//Variables for values
boolean Tap1Value = 0;
boolean Tap1ValueOld = 1; 
boolean Tap2Value = 0;
float Pot1Value = 0;  // variable to store the value coming from the sensor
float Pot2Value = 0;  // variable to store the value coming from the sensor
float Pot3Value = 0;  // variable to store the value coming from the sensor
float Pot4Value = 0;  // variable to store the value coming from the sensor
int CV1Value = 0;
int CV2Value = 0;
int SW1Value = 0;
int SW2Value = 0;
float Pot1ValueOld = 0;  // variable to store the previous value
float Pot2ValueOld = 0;  
float Pot3ValueOld = 0;  
float Pot4ValueOld = 0;  
int SW1ValueOld = 0;
int SW2ValueOld = 0;
int CV1ValueOld = 0;
int CV2ValueOld = 0;
float margin  = 0.01;
int InLevel = 0;
int OutLevel = 16;
int Tap1Timer = 0;

// Variables 
int ledStateONOFF = LOW;         // the current state of the output pin
int ledStateONOFF_old = LOW;
int buttonState0;             // the current reading from the input pin
int lastButtonState0 = LOW;   // the previous reading from the input pin
int ledStateBOOST = LOW;         // the current state of the output pin
int ledStateBOOST_old = LOW;
int buttonState1;             // the current reading from the input pin
int lastButtonState1 = LOW;   // the previous reading from the input pin

unsigned long lastDebounceTime0 = 0;  // the last time the output pin was toggled
unsigned long lastDebounceTime1 = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 20;    // the debounce time; increase if the output flickers

const int myInput = AUDIO_INPUT_LINEIN;

int i,k;

float s_freq = 3;
float s_depth = 1;

long sum, sum_old;
long thresh = 0;
float freq_per = 0;
byte pd_state = 0;
const float sample_freq = 44100;
float Waveform_multi = 2.5;
uint32_t off =pixels.Color(0,0,0);
uint32_t blue =pixels.Color(0,0,50);
uint32_t red =pixels.Color(50,0,0);
uint32_t green =pixels.Color(0,40,0);
uint32_t yellow =pixels.Color(40,40,0);
uint32_t white =pixels.Color(30,30,30);

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
Bounce OnOff = Bounce(Tap1, 30 ); 
Bounce BoostOnOff = Bounce(Tap2, 30); 
elapsedMillis ClippingInterval;  //timer that is used for peak reading
elapsedMillis HoldValue;  //timer that is used for Volume Adjustment

void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  delay(100);
  AudioMemory(100);
  pixels.begin();
  for (int i = 0;i<NUMPIXELS;i++){
    pixels.setPixelColor(i,off);
   } 
  pixels.show();
  float fc = 300;
  biquad1.setHighpass(0,5,0.7);
  biquad1.setHighpass(1,5,0.7);  
  biquad1.setLowpass(2,fc,0.7);  
  biquad1.setLowpass(3,fc,0.7);    
 // biquad2.setHighpass(0,5,0.7);
 // biquad2.setHighpass(1,5,0.7); 
  biquad2.setLowpass(0,fc,0.7);  
  biquad2.setLowpass(1,fc,0.7);   
  biquad2.setLowpass(2,fc,0.7);  
  biquad2.setLowpass(3,fc,0.7);  
  AudioNoInterrupts();
  float mod_freq = 4;//4
  waveform1.begin(1,mod_freq,WAVEFORM_ARBITRARY);
  waveform1.arbitraryWaveform(DelayWave,50);
  waveform2.phase(20);
  waveform2.begin(1,mod_freq,WAVEFORM_ARBITRARY);
  waveform2.arbitraryWaveform(DelayWave,50);  
  waveform2.phase(200);
  
  waveform3.begin(1,mod_freq,WAVEFORM_ARBITRARY);  
  waveform3.arbitraryWaveform(ArbWave,50);
  waveform3.phase(0);  
  waveform4.begin(1,mod_freq,WAVEFORM_ARBITRARY); 
  waveform4.arbitraryWaveform(ArbWave,50);
  waveform4.phase(180);  
  
  notefreq1.begin(.8);
  waveform5.begin(.5,20,WAVEFORM_TRIANGLE);
  inmix.gain(0, 2);
  //inmix.gain(1, 0.9);// set by knob
  //inmix.gain(2, 0.02);

  outmix.gain(0, 1.0);
  outmix.gain(1, 1.0);
  mixer1.gain(0,0.7);
  mixer1.gain(1,0.7);
  mixer1.gain(2,.3);
  mixer1.gain(3,0);
 
  mixer2.gain(0,1);
  mixer2.gain(1, 0);
  mixer3.gain(0,1);
  mixer3.gain(1, 0);
  
  filter3.frequency(300);
  filter3.resonance(.7);  

  xdly.setbuf(LEN, delaybuf);
  xdly2.setbuf(LEN, delaybuf2);
  
  //sgtl5000_1.enable();
  //sgtl5000_1.volume(0.5);
  sgtl5000_1.enable();  // Enable the audio shield
  //sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.unmuteHeadphone();
  sgtl5000_1.lineInLevel(EEPROM.read(0));   // stored input gain
  sgtl5000_1.lineOutLevel(17); // 2,53Vp-p
  sgtl5000_1.dacVolume (1);
  
  AudioInterrupts();
  pinMode(relayL, OUTPUT);
  pinMode(relayR, OUTPUT);
  pinMode(Tap1, INPUT);
  pinMode(Tap2, INPUT);
  sgtl5000_1.autoVolumeControl(0,0,0,-3.0,200,2000);  //maxGain,response,hard limit,threshold,attack, decay
  Serial.println("Setup complete.");
  dc1.amplitude(0);
  dc2.amplitude(0);
  dc3.amplitude(1);
  pixels.setPixelColor(0,red);
  pixels.show();
}

void loop() {
  // put your main code here, to run repeatedly:
  OnOff.update();      //update the bounce
  BoostOnOff.update();

  int reading0 = digitalRead(Tap1);
  int reading1 = digitalRead(Tap2);
  // read the value from the sensor:
  Pot1Value = analogRead(Pot1);    //Sub
  Pot2Value = analogRead(Pot2);    //Tone
  Pot3Value = analogRead(Pot3);    //Mix
  Pot4Value = analogRead(Pot4);    //Master
  Tap1Value = digitalRead(Tap1);   //On off
  Tap2Value = digitalRead(Tap2);   //Sub only
  SW1Value = analogRead(SW1);      //Tone setting
  SW2Value = analogRead(SW2);      //Octave up
  CV1Value = analogRead(CV1);      //EXP1 input
  CV2Value = analogRead(CV2);      //EXP2 input

  SW1Value = map(SW1Value, 0, 1023, 5, 0);            //Tone
  SW1Value = constrain(SW1Value, 1, 5);
  SW2Value = map(SW2Value, 0, 1023, 5, 0);              
  SW2Value = constrain(SW2Value, 1, 5);          //Octave up
  
//********************ON -OFF handling**********************
  buttonState0 = OnOff.read();
  if (lastButtonState0 != buttonState0){   
      if (buttonState0 == LOW) {
        ledStateONOFF = !ledStateONOFF;
      }
  }
  buttonState1 = BoostOnOff.read();
  if (lastButtonState1 != buttonState1){ 
      if (buttonState1 == LOW) {
        ledStateBOOST = !ledStateBOOST;
      }
  } 
  // set the outputs:
if (ledStateONOFF != ledStateONOFF_old){  
  pixels.setPixelColor(2, pixels.Color(ledStateONOFF*30,(ledStateONOFF*30),ledStateONOFF*30)); // ONOFF bright white color. 
  digitalWrite(relayL, ledStateONOFF); 
  digitalWrite(relayR, ledStateONOFF); 
  ledStateONOFF_old = ledStateONOFF; 
  pixels.show(); // This sends the updated pixel color to the hardware.
}
if (ledStateBOOST != ledStateBOOST_old){  
  pixels.setPixelColor(1, pixels.Color(ledStateBOOST*30,(ledStateBOOST*30),ledStateBOOST*30)); // OCTAVE ONLY bright white color. 
  ledStateBOOST_old = ledStateBOOST; 
  pixels.show(); // This sends the updated pixel color to the hardware.
}
  lastButtonState0 = buttonState0;  //save readings to next round
  lastButtonState1 = buttonState1; 

/////////////////////////////////////////////////////
int margin = 12;

  //Change tone controller/shape    
if (SW1Value != SW1ValueOld){
    if (SW1Value == 3)waveform5.begin(WAVEFORM_SQUARE);
    else if (SW1Value == 5)waveform5.begin(WAVEFORM_TRIANGLE);
    else waveform5.begin(WAVEFORM_SAWTOOTH);
    Serial.println(" New wave: ");
    Serial.println(SW1Value);
    SW1ValueOld = SW1Value;    
}

  if (notefreq1.available()){
    if (SW2Value == 1){
      waveform5.frequency(notefreq1.read()* .5); //1 octave down
      waveform5.amplitude(peak1.read());
      }
    else if (SW2Value == 5){
        waveform5.frequency(notefreq1.read()* 2.5); //1.5 octave up
        waveform5.amplitude(peak1.read());
        }
    else {
        waveform5.frequency(notefreq1.read()* 2.); //1 octave up.
        waveform5.amplitude(peak1.read());
    } 
}
  
if(!ledStateONOFF){  //OFF
  //mixer1.gain(0,0);
  //mixer1.gain(1,1);
 // mixer1.gain(2, 0);
  //mixer1.gain(3, 0);  
  }
else if(ledStateBOOST && ledStateONOFF){ //sub only
  mixer1.gain(0,1);
  mixer1.gain(1,0);
//  mixer1.gain(2, 0);
  mixer1.gain(3, 0);  
  }
else{              //On - mixed signals
    mixer1.gain(0,(float)Pot3Value/0x3ff);
    mixer1.gain(1,(1-(float)Pot3Value/0x3ff));
 //   mixer1.gain(2, 0);
    mixer1.gain(3, 0);
  }

if ((Pot1Value <= (Pot1ValueOld-margin)) || (Pot1Value >= (Pot1ValueOld+margin)))  {
  float note =  (float)Pot1Value/0x3ff;
  if (note > 0.85)note = 0.93;
  else if ((note <= 0.85)&&(note > 0.5))note = 0.62;
  else if ((note <= 0.5)&&(note > 0.32))note = 0.37;
  else if ((note <= 0.32)&&(note > 0.0))note = 0.27;
  AudioNoInterrupts();
  waveform1.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  waveform2.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  AudioInterrupts();
  Pot1ValueOld = Pot1Value;
  Serial.print("Pot1:");
  Serial.println(note);
    }

if ((CV1Value <= (CV1ValueOld-margin)) || (CV1Value >= (CV1ValueOld+margin)))  {
  float note =  mapfloat(CV1Value,0,190,0,1.0);
  if (note > 0.85)note = 0.93;
 // else if ((note <= 0.85)&&(note > 0.5))note = 0.62;
 // else if ((note <= 0.5)&&(note > 0.32))note = 0.37;
 // else if ((note <= 0.32)&&(note > 0.0))note = 0.27;
  else if (note <= 0.1)note = 0.1;
  //else note = 0.27;
  Serial.print("CV1:");
  Serial.println(note);
  waveform1.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  waveform2.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  CV1ValueOld = CV1Value;
    }    
   
if ((Pot2Value <= (Pot2ValueOld-margin)) || (Pot2Value >= (Pot2ValueOld+margin)))  {  
  Pot2ValueOld = Pot2Value;
  if (Pot2Value > 990)Pot2Value = 1024; 
  mixer1.gain(2,1-(float)(Pot2Value/0x3ff));
  Serial.print("Pot2:");
  Serial.println(Pot2Value);  

    }
if ((CV2Value <= (CV2ValueOld-margin)) || (CV2Value >= (CV2ValueOld+margin)))  {  
  float mixLevel = mapfloat(CV2Value,0,190,0,1.0);
  mixer1.gain(2,mixLevel);
  Serial.print("Cv2:");
  Serial.println(mixLevel);
  CV2ValueOld = CV2Value;
    }    
    
if ((Pot3Value <= (Pot3ValueOld-margin)) || (Pot3Value >= (Pot3ValueOld+margin)))  {  
  mixer1.gain(0,(float)Pot3Value/0x3ff);
  mixer1.gain(1,(1-(float)Pot3Value/0x3ff));
  Serial.print("Pot3:");
  Serial.println(Pot3Value);
  Pot3ValueOld = Pot3Value;
    }
    
if ((Pot4Value <= (Pot4ValueOld-margin)) || (Pot4Value >= (Pot4ValueOld+margin)))  {  
  int outlevel = map(Pot4Value,0,1023,13,31);
  sgtl5000_1.lineOutLevel(outlevel);
  Serial.print("Pot4:");
  Serial.println(Pot4Value);  
  Pot4ValueOld = Pot4Value;
    }   
     

//*******************INPUT/OUTPUT LEVEL ADJUST************************

 if (!Tap1Value && (Tap1Value == Tap1ValueOld)){    //When Mode/tap is held down you can adjust the input gain of the input
   Tap1Timer++;
   delay(100);
  if (Tap1Timer > 15){
    InLevel = map(Pot4Value,0,1023,15,0);
    sgtl5000_1.lineInLevel(InLevel);
    pixels.setPixelColor(0, pixels.Color(InLevel*15,(InLevel*15),InLevel*0));
    pixels.show(); // This sends the updated pixel color to the hardware.   
    digitalWrite(relayL, 1); 
    digitalWrite(relayR, 1); 
    ledStateONOFF = 1;
    Pot4ValueOld = Pot4Value;
    }
  }
 else if (Tap1Timer > 15){    //only write to EEPROM once
    EEPROM.write(0, InLevel);
    Serial.println(InLevel);
    pixels.setPixelColor(0, red);
    pixels.show(); // This sends the updated pixel color to the hardware.  
    Tap1Timer = 0;
  }
 else if (Tap1Value){
   Tap1Timer = 0;
  }


/*
if (SW1Value == 1){    //HP pos for Output level
 if (!Tap1Value && (Tap1Value == Tap1ValueOld)){    //When Mode/tap is held down you can adjust the input gain of the input
 // Serial.print("TIME");
   Tap1Timer++;
  if (Tap1Timer > 15){
    outLevel = analogRead(Pot1);
    outLevel = mapfloat(outLevel,0,1023,10,1);
    delay(100);
    //analogWrite(ledPin2, 255);  
    //display.clearDisplay();   
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(5, 10);
    display.print("Output:");
    display.print(outLevel); 
    display.display();
    }
  }
  else if (Tap1Timer > 15){    //only write to EEPROM once
    EEPROM.write(1, outLevel);
    Tap1Timer = 0;
  }
  else if (Tap1Value){
  //  peak = peak1.read();   //read the signal output peak
  if (peak == -1)(peak = 0);
  //analogWrite(ledPin2, peak*100);
  }
}
*/
   /*
  
   Serial.print(" Pot1: ");
   Serial.print(Pot1Value);   
   Serial.print(" Pot2: ");
   Serial.print(Pot2Value);
   Serial.print(" Pot3: ");
   Serial.print(Pot3Value);
   Serial.print(" Pot4: ");
   Serial.print(Pot4Value);
   Serial.print(" SW1: ");
   Serial.print(SW1Value);
   Serial.print(" SW2: ");
   Serial.print(SW2Value);
  
   Serial.print(" CV1: ");
   Serial.print(CV1Value);
   Serial.print(" CV2: ");
   Serial.println(CV2Value);
 */
 

   SW2ValueOld = SW2Value; 
   Tap1ValueOld = Tap1Value;

   if (ClippingInterval > 1000){
    if (ledStateONOFF){
        float peakLevel = peak1.readPeakToPeak();
        if (peakLevel > 1) pixels.setPixelColor(2,red); //indicate high input level
        else pixels.setPixelColor(2,white);
        pixels.show(); // This sends the updated pixel color to the hardware.
        ClippingInterval = 0;
    }
   }
   
/*
    Serial.print("Diagnostics: ");
    Serial.print(" max, buffs: ");
    Serial.print(AudioProcessorUsageMax());
    Serial.print(" ");
    Serial.println(AudioMemoryUsageMax());
    AudioProcessorUsageMaxReset();
    xdly.inspect();
    */
}
 
Yes, I used a delay object made by Byron Jacqout at Sparkfun. This object has a input for a modulation signal i.e the input determine the length of the delay. And then I feed that input with a triangle wave. Then the pitch will change up or down dependent of the direction and frequency of the triangle wave.
But, you need to have two "signal paths" and change the phase of each triangle wave and mix the signals in the end...

This article describe this:
https://se.mathworks.com/help/audio/examples/delay-based-pitch-shifter.html?requestedDomain=www.mathworks.com

The delay object can be found here:
https://github.com/Jacquot-SFE/Synth-drum/tree/master/teensy-based/fx/cosmic-delay-sketch
mod-delay.cpp and mod-delay.h is the files you will need.

I have some code that is functional but far from optimal!!
This is used with the openeffectsproject box and includes some other functions. You should look what is happening to waveform1 and 2 and you need to set up the delay buffers: delaybuf[LEN] ..

Pot 1 is used to adjust the pitch down.
Schematic can be found here:
http://openeffectsproject.com/wp-content/uploads/2017/03/Schematic2_2.pdf



Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Bounce.h> 
#include <EEPROM.h> 
#include "mod-delay.h"
using namespace std;

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define PIN            8  //changed from last rev //pin3 on rev 2, pin 8 on rev 2.1 ->
#define NUMPIXELS      10 //changed from last rev
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);


// GUItool: begin automatically generated code
AudioInputI2S            i2s2;           //xy=94,397
AudioSynthWaveform       waveform1;      //xy=95,302
AudioSynthWaveform       waveform2;      //xy=98,476
AudioSynthWaveformDc     dc1;            //xy=100,341
AudioAnalyzePeak         peak1;          //xy=251,372
AudioMixer4              inmix;          //xy=251,424
AudioMixer4              mixer2;         //xy=252,321
AudioMixer4              mixer3;         //xy=252,495
AudioSynthWaveformDc     dc2;            //xy=265,256
AudioFilterBiquad        biquad2;        //xy=377,364
AudioSynthWaveform       waveform4;      //xy=423,464
AudioFilterStateVariable filter3;        //xy=427,249
AudioEffectModDelay      xdly2;          //xy=501,399
AudioEffectModDelay      xdly;           //xy=503,327
AudioSynthWaveform       waveform3;      //xy=512,363
AudioAnalyzeNoteFrequency notefreq1;      //xy=554,236
AudioEffectMultiply      multiply1;      //xy=646,352
AudioEffectMultiply      multiply2;      //xy=650,404
AudioMixer4              outmix;         //xy=778,393
AudioSynthWaveform       waveform5;      //xy=869,493
AudioFilterBiquad        biquad1;        //xy=904,402
AudioMixer4              mixer1;         //xy=1036,441
AudioAnalyzePeak         peak2;          //xy=1037,537
AudioSynthWaveformDc     dc3;            //xy=1038,497
AudioEffectMultiply      multiply3;      //xy=1160,471
AudioOutputI2S           i2s1;           //xy=1292,473
AudioConnection          patchCord1(i2s2, 0, inmix, 0);
AudioConnection          patchCord2(i2s2, 0, peak1, 0);
AudioConnection          patchCord3(waveform1, 0, mixer2, 0);
AudioConnection          patchCord4(waveform2, 0, mixer3, 0);
AudioConnection          patchCord5(dc1, 0, mixer2, 1);
AudioConnection          patchCord6(dc1, 0, mixer3, 1);
AudioConnection          patchCord7(inmix, 0, mixer1, 1);
AudioConnection          patchCord8(inmix, 0, filter3, 0);
AudioConnection          patchCord9(inmix, biquad2);
AudioConnection          patchCord10(mixer2, 0, xdly, 1);
AudioConnection          patchCord11(mixer3, 0, xdly2, 1);
AudioConnection          patchCord12(dc2, 0, filter3, 1);
AudioConnection          patchCord13(biquad2, 0, xdly2, 0);
AudioConnection          patchCord14(biquad2, 0, xdly, 0);
AudioConnection          patchCord15(waveform4, 0, multiply2, 1);
AudioConnection          patchCord16(filter3, 0, notefreq1, 0);
AudioConnection          patchCord17(xdly2, 0, multiply2, 0);
AudioConnection          patchCord18(xdly, 0, multiply1, 0);
AudioConnection          patchCord19(waveform3, 0, multiply1, 1);
AudioConnection          patchCord20(multiply1, 0, outmix, 0);
AudioConnection          patchCord21(multiply2, 0, outmix, 1);
AudioConnection          patchCord22(outmix, biquad1);
AudioConnection          patchCord23(waveform5, peak2);
AudioConnection          patchCord24(waveform5, 0, mixer1, 2);
AudioConnection          patchCord25(biquad1, 0, mixer1, 0);
AudioConnection          patchCord26(mixer1, 0, multiply3, 0);
AudioConnection          patchCord27(dc3, 0, multiply3, 1);
AudioConnection          patchCord28(multiply3, 0, i2s1, 0);
AudioConnection          patchCord29(multiply3, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=579,550
// GUItool: end automatically generated code


// This is about max (97%!) for internal RAM
static const uint32_t LEN = 0x01500; //1500ok...
int16_t delaybuf[LEN];
int16_t delaybuf2[LEN];

uint32_t next;

int16_t ArbWave[256] = {0,0,0,0,7327,10362,12691,14654,16384,17947,19385,20724,21981,23170,24301,25381,26418,27415,28377,29308,30210,31086,31937,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,31937,31086,30210,29308,28377,27415,26418,25381,24301,
23170,21981,20724,19385,17947,16384,14654,12691,10362,7327,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

/***************/

//Pinout board rev1
int Pot1 = A6;    // select the input pin for the potentiometer
int Pot2 = A3;    // select the input pin for the potentiometer
int Pot3 = A2;    // select the input pin for the potentiometer
int Pot4 = A1;    // select the input pin for the potentiometer
int CV1 = A10;
int CV2 = A11;

int Tap1 = 2;
int Tap2 = 3;

int SW1 = A12;
int SW2 = A13;

//int ledPin2 = 4;      // select the pin for the LED
//int ledPin3 = 5;      // select the pin for the LED
int relayL = 4;      // select the pin for the RelayL
int relayR = 5;      // select the pin for the RelayR

//Variables for values
boolean Tap1Value = 0;
boolean Tap1ValueOld = 1; 
boolean Tap2Value = 0;
float Pot1Value = 0;  // variable to store the value coming from the sensor
float Pot2Value = 0;  // variable to store the value coming from the sensor
float Pot3Value = 0;  // variable to store the value coming from the sensor
float Pot4Value = 0;  // variable to store the value coming from the sensor
int CV1Value = 0;
int CV2Value = 0;
int SW1Value = 0;
int SW2Value = 0;
float Pot1ValueOld = 0;  // variable to store the previous value
float Pot2ValueOld = 0;  
float Pot3ValueOld = 0;  
float Pot4ValueOld = 0;  
int SW1ValueOld = 0;
int SW2ValueOld = 0;
int CV1ValueOld = 0;
int CV2ValueOld = 0;
float margin  = 0.01;
int InLevel = 0;
int OutLevel = 16;
int Tap1Timer = 0;

// Variables 
int ledStateONOFF = LOW;         // the current state of the output pin
int ledStateONOFF_old = LOW;
int buttonState0;             // the current reading from the input pin
int lastButtonState0 = LOW;   // the previous reading from the input pin
int ledStateBOOST = LOW;         // the current state of the output pin
int ledStateBOOST_old = LOW;
int buttonState1;             // the current reading from the input pin
int lastButtonState1 = LOW;   // the previous reading from the input pin

unsigned long lastDebounceTime0 = 0;  // the last time the output pin was toggled
unsigned long lastDebounceTime1 = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 20;    // the debounce time; increase if the output flickers

const int myInput = AUDIO_INPUT_LINEIN;

int i,k;

float s_freq = 3;
float s_depth = 1;

long sum, sum_old;
long thresh = 0;
float freq_per = 0;
byte pd_state = 0;
const float sample_freq = 44100;
float Waveform_multi = 2.5;
uint32_t off =pixels.Color(0,0,0);
uint32_t blue =pixels.Color(0,0,50);
uint32_t red =pixels.Color(50,0,0);
uint32_t green =pixels.Color(0,40,0);
uint32_t yellow =pixels.Color(40,40,0);
uint32_t white =pixels.Color(30,30,30);

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
Bounce OnOff = Bounce(Tap1, 30 ); 
Bounce BoostOnOff = Bounce(Tap2, 30); 
elapsedMillis ClippingInterval;  //timer that is used for peak reading
elapsedMillis HoldValue;  //timer that is used for Volume Adjustment

void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  delay(100);
  AudioMemory(100);
  pixels.begin();
  for (int i = 0;i<NUMPIXELS;i++){
    pixels.setPixelColor(i,off);
   } 
  pixels.show();
  float fc = 300;
  biquad1.setHighpass(0,5,0.7);
  biquad1.setHighpass(1,5,0.7);  
  biquad1.setLowpass(2,fc,0.7);  
  biquad1.setLowpass(3,fc,0.7);    
 // biquad2.setHighpass(0,5,0.7);
 // biquad2.setHighpass(1,5,0.7); 
  biquad2.setLowpass(0,fc,0.7);  
  biquad2.setLowpass(1,fc,0.7);   
  biquad2.setLowpass(2,fc,0.7);  
  biquad2.setLowpass(3,fc,0.7);  
  AudioNoInterrupts();
  float mod_freq = 4;//4
  waveform1.begin(1,mod_freq,WAVEFORM_ARBITRARY);
  waveform1.arbitraryWaveform(DelayWave,50);
  waveform2.phase(20);
  waveform2.begin(1,mod_freq,WAVEFORM_ARBITRARY);
  waveform2.arbitraryWaveform(DelayWave,50);  
  waveform2.phase(200);
  
  waveform3.begin(1,mod_freq,WAVEFORM_ARBITRARY);  
  waveform3.arbitraryWaveform(ArbWave,50);
  waveform3.phase(0);  
  waveform4.begin(1,mod_freq,WAVEFORM_ARBITRARY); 
  waveform4.arbitraryWaveform(ArbWave,50);
  waveform4.phase(180);  
  
  notefreq1.begin(.8);
  waveform5.begin(.5,20,WAVEFORM_TRIANGLE);
  inmix.gain(0, 2);
  //inmix.gain(1, 0.9);// set by knob
  //inmix.gain(2, 0.02);

  outmix.gain(0, 1.0);
  outmix.gain(1, 1.0);
  mixer1.gain(0,0.7);
  mixer1.gain(1,0.7);
  mixer1.gain(2,.3);
  mixer1.gain(3,0);
 
  mixer2.gain(0,1);
  mixer2.gain(1, 0);
  mixer3.gain(0,1);
  mixer3.gain(1, 0);
  
  filter3.frequency(300);
  filter3.resonance(.7);  

  xdly.setbuf(LEN, delaybuf);
  xdly2.setbuf(LEN, delaybuf2);
  
  //sgtl5000_1.enable();
  //sgtl5000_1.volume(0.5);
  sgtl5000_1.enable();  // Enable the audio shield
  //sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.unmuteHeadphone();
  sgtl5000_1.lineInLevel(EEPROM.read(0));   // stored input gain
  sgtl5000_1.lineOutLevel(17); // 2,53Vp-p
  sgtl5000_1.dacVolume (1);
  
  AudioInterrupts();
  pinMode(relayL, OUTPUT);
  pinMode(relayR, OUTPUT);
  pinMode(Tap1, INPUT);
  pinMode(Tap2, INPUT);
  sgtl5000_1.autoVolumeControl(0,0,0,-3.0,200,2000);  //maxGain,response,hard limit,threshold,attack, decay
  Serial.println("Setup complete.");
  dc1.amplitude(0);
  dc2.amplitude(0);
  dc3.amplitude(1);
  pixels.setPixelColor(0,red);
  pixels.show();
}

void loop() {
  // put your main code here, to run repeatedly:
  OnOff.update();      //update the bounce
  BoostOnOff.update();

  int reading0 = digitalRead(Tap1);
  int reading1 = digitalRead(Tap2);
  // read the value from the sensor:
  Pot1Value = analogRead(Pot1);    //Sub
  Pot2Value = analogRead(Pot2);    //Tone
  Pot3Value = analogRead(Pot3);    //Mix
  Pot4Value = analogRead(Pot4);    //Master
  Tap1Value = digitalRead(Tap1);   //On off
  Tap2Value = digitalRead(Tap2);   //Sub only
  SW1Value = analogRead(SW1);      //Tone setting
  SW2Value = analogRead(SW2);      //Octave up
  CV1Value = analogRead(CV1);      //EXP1 input
  CV2Value = analogRead(CV2);      //EXP2 input

  SW1Value = map(SW1Value, 0, 1023, 5, 0);            //Tone
  SW1Value = constrain(SW1Value, 1, 5);
  SW2Value = map(SW2Value, 0, 1023, 5, 0);              
  SW2Value = constrain(SW2Value, 1, 5);          //Octave up
  
//********************ON -OFF handling**********************
  buttonState0 = OnOff.read();
  if (lastButtonState0 != buttonState0){   
      if (buttonState0 == LOW) {
        ledStateONOFF = !ledStateONOFF;
      }
  }
  buttonState1 = BoostOnOff.read();
  if (lastButtonState1 != buttonState1){ 
      if (buttonState1 == LOW) {
        ledStateBOOST = !ledStateBOOST;
      }
  } 
  // set the outputs:
if (ledStateONOFF != ledStateONOFF_old){  
  pixels.setPixelColor(2, pixels.Color(ledStateONOFF*30,(ledStateONOFF*30),ledStateONOFF*30)); // ONOFF bright white color. 
  digitalWrite(relayL, ledStateONOFF); 
  digitalWrite(relayR, ledStateONOFF); 
  ledStateONOFF_old = ledStateONOFF; 
  pixels.show(); // This sends the updated pixel color to the hardware.
}
if (ledStateBOOST != ledStateBOOST_old){  
  pixels.setPixelColor(1, pixels.Color(ledStateBOOST*30,(ledStateBOOST*30),ledStateBOOST*30)); // OCTAVE ONLY bright white color. 
  ledStateBOOST_old = ledStateBOOST; 
  pixels.show(); // This sends the updated pixel color to the hardware.
}
  lastButtonState0 = buttonState0;  //save readings to next round
  lastButtonState1 = buttonState1; 

/////////////////////////////////////////////////////
int margin = 12;

  //Change tone controller/shape    
if (SW1Value != SW1ValueOld){
    if (SW1Value == 3)waveform5.begin(WAVEFORM_SQUARE);
    else if (SW1Value == 5)waveform5.begin(WAVEFORM_TRIANGLE);
    else waveform5.begin(WAVEFORM_SAWTOOTH);
    Serial.println(" New wave: ");
    Serial.println(SW1Value);
    SW1ValueOld = SW1Value;    
}

  if (notefreq1.available()){
    if (SW2Value == 1){
      waveform5.frequency(notefreq1.read()* .5); //1 octave down
      waveform5.amplitude(peak1.read());
      }
    else if (SW2Value == 5){
        waveform5.frequency(notefreq1.read()* 2.5); //1.5 octave up
        waveform5.amplitude(peak1.read());
        }
    else {
        waveform5.frequency(notefreq1.read()* 2.); //1 octave up.
        waveform5.amplitude(peak1.read());
    } 
}
  
if(!ledStateONOFF){  //OFF
  //mixer1.gain(0,0);
  //mixer1.gain(1,1);
 // mixer1.gain(2, 0);
  //mixer1.gain(3, 0);  
  }
else if(ledStateBOOST && ledStateONOFF){ //sub only
  mixer1.gain(0,1);
  mixer1.gain(1,0);
//  mixer1.gain(2, 0);
  mixer1.gain(3, 0);  
  }
else{              //On - mixed signals
    mixer1.gain(0,(float)Pot3Value/0x3ff);
    mixer1.gain(1,(1-(float)Pot3Value/0x3ff));
 //   mixer1.gain(2, 0);
    mixer1.gain(3, 0);
  }

if ((Pot1Value <= (Pot1ValueOld-margin)) || (Pot1Value >= (Pot1ValueOld+margin)))  {
  float note =  (float)Pot1Value/0x3ff;
  if (note > 0.85)note = 0.93;
  else if ((note <= 0.85)&&(note > 0.5))note = 0.62;
  else if ((note <= 0.5)&&(note > 0.32))note = 0.37;
  else if ((note <= 0.32)&&(note > 0.0))note = 0.27;
  AudioNoInterrupts();
  waveform1.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  waveform2.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  AudioInterrupts();
  Pot1ValueOld = Pot1Value;
  Serial.print("Pot1:");
  Serial.println(note);
    }

if ((CV1Value <= (CV1ValueOld-margin)) || (CV1Value >= (CV1ValueOld+margin)))  {
  float note =  mapfloat(CV1Value,0,190,0,1.0);
  if (note > 0.85)note = 0.93;
 // else if ((note <= 0.85)&&(note > 0.5))note = 0.62;
 // else if ((note <= 0.5)&&(note > 0.32))note = 0.37;
 // else if ((note <= 0.32)&&(note > 0.0))note = 0.27;
  else if (note <= 0.1)note = 0.1;
  //else note = 0.27;
  Serial.print("CV1:");
  Serial.println(note);
  waveform1.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  waveform2.amplitude(note);//working values: 0.93 / 0.62 / 0.37 / 0.27
  CV1ValueOld = CV1Value;
    }    
   
if ((Pot2Value <= (Pot2ValueOld-margin)) || (Pot2Value >= (Pot2ValueOld+margin)))  {  
  Pot2ValueOld = Pot2Value;
  if (Pot2Value > 990)Pot2Value = 1024; 
  mixer1.gain(2,1-(float)(Pot2Value/0x3ff));
  Serial.print("Pot2:");
  Serial.println(Pot2Value);  

    }
if ((CV2Value <= (CV2ValueOld-margin)) || (CV2Value >= (CV2ValueOld+margin)))  {  
  float mixLevel = mapfloat(CV2Value,0,190,0,1.0);
  mixer1.gain(2,mixLevel);
  Serial.print("Cv2:");
  Serial.println(mixLevel);
  CV2ValueOld = CV2Value;
    }    
    
if ((Pot3Value <= (Pot3ValueOld-margin)) || (Pot3Value >= (Pot3ValueOld+margin)))  {  
  mixer1.gain(0,(float)Pot3Value/0x3ff);
  mixer1.gain(1,(1-(float)Pot3Value/0x3ff));
  Serial.print("Pot3:");
  Serial.println(Pot3Value);
  Pot3ValueOld = Pot3Value;
    }
    
if ((Pot4Value <= (Pot4ValueOld-margin)) || (Pot4Value >= (Pot4ValueOld+margin)))  {  
  int outlevel = map(Pot4Value,0,1023,13,31);
  sgtl5000_1.lineOutLevel(outlevel);
  Serial.print("Pot4:");
  Serial.println(Pot4Value);  
  Pot4ValueOld = Pot4Value;
    }   
     

//*******************INPUT/OUTPUT LEVEL ADJUST************************

 if (!Tap1Value && (Tap1Value == Tap1ValueOld)){    //When Mode/tap is held down you can adjust the input gain of the input
   Tap1Timer++;
   delay(100);
  if (Tap1Timer > 15){
    InLevel = map(Pot4Value,0,1023,15,0);
    sgtl5000_1.lineInLevel(InLevel);
    pixels.setPixelColor(0, pixels.Color(InLevel*15,(InLevel*15),InLevel*0));
    pixels.show(); // This sends the updated pixel color to the hardware.   
    digitalWrite(relayL, 1); 
    digitalWrite(relayR, 1); 
    ledStateONOFF = 1;
    Pot4ValueOld = Pot4Value;
    }
  }
 else if (Tap1Timer > 15){    //only write to EEPROM once
    EEPROM.write(0, InLevel);
    Serial.println(InLevel);
    pixels.setPixelColor(0, red);
    pixels.show(); // This sends the updated pixel color to the hardware.  
    Tap1Timer = 0;
  }
 else if (Tap1Value){
   Tap1Timer = 0;
  }


/*
if (SW1Value == 1){    //HP pos for Output level
 if (!Tap1Value && (Tap1Value == Tap1ValueOld)){    //When Mode/tap is held down you can adjust the input gain of the input
 // Serial.print("TIME");
   Tap1Timer++;
  if (Tap1Timer > 15){
    outLevel = analogRead(Pot1);
    outLevel = mapfloat(outLevel,0,1023,10,1);
    delay(100);
    //analogWrite(ledPin2, 255);  
    //display.clearDisplay();   
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(5, 10);
    display.print("Output:");
    display.print(outLevel); 
    display.display();
    }
  }
  else if (Tap1Timer > 15){    //only write to EEPROM once
    EEPROM.write(1, outLevel);
    Tap1Timer = 0;
  }
  else if (Tap1Value){
  //  peak = peak1.read();   //read the signal output peak
  if (peak == -1)(peak = 0);
  //analogWrite(ledPin2, peak*100);
  }
}
*/
   /*
  
   Serial.print(" Pot1: ");
   Serial.print(Pot1Value);   
   Serial.print(" Pot2: ");
   Serial.print(Pot2Value);
   Serial.print(" Pot3: ");
   Serial.print(Pot3Value);
   Serial.print(" Pot4: ");
   Serial.print(Pot4Value);
   Serial.print(" SW1: ");
   Serial.print(SW1Value);
   Serial.print(" SW2: ");
   Serial.print(SW2Value);
  
   Serial.print(" CV1: ");
   Serial.print(CV1Value);
   Serial.print(" CV2: ");
   Serial.println(CV2Value);
 */
 

   SW2ValueOld = SW2Value; 
   Tap1ValueOld = Tap1Value;

   if (ClippingInterval > 1000){
    if (ledStateONOFF){
        float peakLevel = peak1.readPeakToPeak();
        if (peakLevel > 1) pixels.setPixelColor(2,red); //indicate high input level
        else pixels.setPixelColor(2,white);
        pixels.show(); // This sends the updated pixel color to the hardware.
        ClippingInterval = 0;
    }
   }
   
/*
    Serial.print("Diagnostics: ");
    Serial.print(" max, buffs: ");
    Serial.print(AudioProcessorUsageMax());
    Serial.print(" ");
    Serial.println(AudioMemoryUsageMax());
    AudioProcessorUsageMaxReset();
    xdly.inspect();
    */
}

Thank you so much! I will definitely try this tonight!
 
@omjanger Getting a compiler error that 'DelayWave' is not declared. I do not see it anywhere in the example you provided. Is this something you declare in your code somewhere?

Code:
'DelayWave' was not declared in this scope

Thank you!
 
@omjanger Getting a compiler error that 'DelayWave' is not declared. I do not see it anywhere in the example you provided. Is this something you declare in your code somewhere?

Code:
'DelayWave' was not declared in this scope

Thank you!

Yes... I took out some code to clean it up a little, but I removed too much=)

Put this array into the code after the ArbWave:
Code:
int16_t DelayWave[256] = {500,500,500,500,572,715,858,1001,1144,1287,1430,1573,1716,1859,2002,2145,2288,2431,2574,2717,2860,3003,3146,3289,3432,3575,3718,3861,4004,4147,
4290,4433,4576,4719,4862,5005,5148,5291,5434,5577,5720,5863,6006,6149,6292,6435,6578,6721,6864,7007,7150,7293,7436,7579,7722,7865,8008,8151,8294,
8437,8580,8723,8866,9009,9152,9295,9438,9581,9724,9867,10010,10153,10296,10439,10582,10725,10868,11011,11154,11297,11440,11583,11726,11869,12012,
12155,12298,12441,12584,12727,12870,13013,13156,13299,13442,13585,13728,13871,14014,14157,14300,14443,14586,14729,14872,15015,15158,15301,15444,
15587,15730,15873,16016,16159,16302,16445,16588,16731,16874,17017,17160,17303,17446,17589,17732,17875,18018,18161,18304,18447,18590,18733,18876,
19019,19162,19305,19448,19591,19734,19877,20020,20163,20306,20449,20592,20735,20878,21021,21164,21307,21450,21593,21736,21879,22022,22165,22308,
22451,22594,22737,22880,23023,23166,23309,23452,23595,23738,23881,24024,24167,24310,24453,24596,24739,24882,25025,25168,25311,25454,25597,25740,
25883,26026,26169,26312,26455,26598,26741,26884,27027,27170,27313,27456,27599,27742,27885,28028,28171,28314,28457,28600,28743,28886,29029,29172,
29315,29458,29601,29744,29887,30030,30173,30316,30459,30602,30745,30888,31031,31174,31317,31440,31443,31443,31443,31443,31443,31443,31443,31200,
31000,30700,30139,28835,27530,26226,24922,23617,22313,21009,19705,18401,17096,15792,14488,13184,11880,10575,9271,7967,6663,5359,4054,2750,1446,
500,500};
 
Yes... I took out some code to clean it up a little, but I removed too much=)

Put this array into the code after the ArbWave:
Code:
int16_t DelayWave[256] = {500,500,500,500,572,715,858,1001,1144,1287,1430,1573,1716,1859,2002,2145,2288,2431,2574,2717,2860,3003,3146,3289,3432,3575,3718,3861,4004,4147,
4290,4433,4576,4719,4862,5005,5148,5291,5434,5577,5720,5863,6006,6149,6292,6435,6578,6721,6864,7007,7150,7293,7436,7579,7722,7865,8008,8151,8294,
8437,8580,8723,8866,9009,9152,9295,9438,9581,9724,9867,10010,10153,10296,10439,10582,10725,10868,11011,11154,11297,11440,11583,11726,11869,12012,
12155,12298,12441,12584,12727,12870,13013,13156,13299,13442,13585,13728,13871,14014,14157,14300,14443,14586,14729,14872,15015,15158,15301,15444,
15587,15730,15873,16016,16159,16302,16445,16588,16731,16874,17017,17160,17303,17446,17589,17732,17875,18018,18161,18304,18447,18590,18733,18876,
19019,19162,19305,19448,19591,19734,19877,20020,20163,20306,20449,20592,20735,20878,21021,21164,21307,21450,21593,21736,21879,22022,22165,22308,
22451,22594,22737,22880,23023,23166,23309,23452,23595,23738,23881,24024,24167,24310,24453,24596,24739,24882,25025,25168,25311,25454,25597,25740,
25883,26026,26169,26312,26455,26598,26741,26884,27027,27170,27313,27456,27599,27742,27885,28028,28171,28314,28457,28600,28743,28886,29029,29172,
29315,29458,29601,29744,29887,30030,30173,30316,30459,30602,30745,30888,31031,31174,31317,31440,31443,31443,31443,31443,31443,31443,31443,31200,
31000,30700,30139,28835,27530,26226,24922,23617,22313,21009,19705,18401,17096,15792,14488,13184,11880,10575,9271,7967,6663,5359,4054,2750,1446,
500,500};

Great thank you...got it working. Also checked out the video on the link you provided.

I'm wanting to use this with a voice input and change the pitch of the voice up or down. It's compiled and running, but the voice is full of static and the waveforms are creating the random sounds ;) I think this is a good start, though, so I will see what I can do with it and see if it will accomplish what I'm after. Thank you so much for the help!
 
Good to hear! Would love to see what you can get from this.
And yes, you should disable the "nintendo" sounds=) And you might want to mix the pitched voice with a bit of the original to get the right attack. Do you get some ticking artifacts in the sound from the delay line? This comes from when the delay line gets to the endpoints, and I didn't manage to get rid of all the artifacts in the sound.
I have rounded the endpoints of the delayline modulation curve so that it never goes to zero and not change very fast. That helped. Maybe a solution would be to add a third delayline to ensure that the delay is always near the middle of the sawtooth wave and not even close to the top or bottom?
 
Great thank you...got it working. Also checked out the video on the link you provided.

I'm wanting to use this with a voice input and change the pitch of the voice up or down. It's compiled and running, but the voice is full of static and the waveforms are creating the random sounds ;) I think this is a good start, though, so I will see what I can do with it and see if it will accomplish what I'm after. Thank you so much for the help!

Did you manage to get it working?
 
So, it’s been awhile, but I finally got a chance to try out the technique referenced in Post #2. The Audio System connections look like this:

Audio Object Connections.jpg

The blocks labeled “modulatedDelay1” and “modulatedDelay2” perform both the variable delay and amplitude modulation functions required by this technique.

The input signal source is an I2S Microphone. The output drives a 3W I2S Amplifier and speaker.

Here’s the electrical connection diagram:

Electrical Connections.jpg

The rotary encoders adjust the amount of pitch shift (up to +/- 1 octave in the current implementation) and the ratio of unshifted to shifted input that is applied to the output.

The full code is at my GitHub: ToneShift. This includes the code for the modulatedDelay Audio Library class. It’s based on the variable delay class linked in Post #2 above.

The code for the rotary encoder library I used is also on my GitHub: NewEncoder. I wrote this because the standard encoder library has issues with the encoder that I used. First, because it’s a full-quadrature encoder, the standard library double counts on every step between detents. This encoder also has significant mechanical contact bounce that comes through using the standard library. My library address both of these issues as well as adds a few features.

Bottom line with this Pitch Shift technique is that it produces a Really Cool voice changer -- especially if you combine it with some feedback or other audio effects.

However, the overlap between the two delay lines (as one transitions off and the other transitions on) does produce artifacts in the signal. I think that’s unavoidable. You’re essentially trying to change the sampling rate while still playing the audio over the same time period. You’re either going to have to throw away or reuse samples. Those artifacts may bother an audio purist. Fortunately, I’m not one.
 
Did you manage to get it working?

I got it working, but was still having some tonal quality issues and then had to put it down for a while.

In the meantime, the Audio lib has been updated with a pitch shifter that works pretty well. It does have some artifacting depending upon settings.
 
I got it working, but was still having some tonal quality issues and then had to put it down for a while.

In the meantime, the Audio lib has been updated with a pitch shifter that works pretty well. It does have some artifacting depending upon settings.

Could you tell me where in the library is the feature? please. I have searched it and I can't find it.
 
The only pitch shifting currently in the audio library is inside the granular effect.

https://www.pjrc.com/teensy/gui/?info=AudioEffectGranular

If you're expecting a beautiful sound of frequency domain shifting with special algorithms for detecting & handling percussion, prepare yourself to be underwhelmed. It sometimes does work surprisingly well for certain material if you tune the grain size well. Sometimes. Many other times, the effect is rather, well... granular.
 
The only pitch shifting currently in the audio library is inside the granular effect.
Many other times, the effect is rather, well... granular.
But it makes a great voice changer. Sounds just like those mob people interviewed on TV who don't want to use their real voice or show their face :eek:
 
I've been working on a phase vocoder off and on for awhile now and I can tell you its not easy, well not easy for realtime pitch shifting using Audio library.

First off the q15 data type for CMSIS FFT's will lose to much spectral information so when finally doing the inverse FFT it's already added quite a bit of noise to the signal. I had to convert the q15 audio data to f32 data and work on it there. That right there means you have to have a micro that has a FPU, again for real time applications.

Another issue is 75% (every 256 data points) overlap is not sufficient in my tests, that corresponds to doing a FFT and IFFT every other data block in the Audio library. I found the 87.5% (every 128 data points) overlap is the bare minimum. That corresponds to FFT, IFFT every block of data. I'm using the CMSIS f32 1024 point FFT's by the way. This also means that doing the Analysis and Synthesis stages have to be done in a single block time (~2.9ms) if you want to send the processed data out to other Audio Library modules.

Another is that using 1024 point FFT means your frequency range is limited on resolution and low end frequencies. So for me I wanted to use it with a bass guitar and it won't work with say an open low E note (41.2Hz). So if your using it for say multiple instruments or such the low frequencies get real phasey or static sounding and won't be very pleasant to your ears:( It just can't reconstruct those low end frequencies which means you have to use larger point FFT that intern adds to much latency also! A 1024 point FFT is about 23ms of latency.

Lastly the buffers needed to hold intermediary stages data become quite big, not a deal breaker but do eat up a lot of precious RAM.

I have read a bunch of scholarly papers that claim to do realtime pitch shifting but they use novel tricks in the frequency domain that I either don't understand or are special case implementations of the vocoder such as only able to shift correctly on the frequencies that align exactly with the bin frequency of the FFT or such. You can do pitch sifting in time domain but I have not investigated this yet (SOLA). When I finally got a Not Real-Time version working and ported it to the Audio library, on my teensy 3.5 at a 144MHz it had a processor usage of over 250%! So I don't even know what it sounds like:( I'm hoping the T4 has power to do this but haven't tried it out because the issue with how the RAM is sectioned, plus I moved on to other things so hopefully in the future I can get back to this once the T4 api is more stable.
 
If you're willing to use a teensy 3.5 or 3.6, you can use flowing point FFTs, which will make this much more feasible, given that we're just hobbyists.

I've done a formant shifter on the Hoxton Owl, which is a similar class of ARM M4F as the Teensy 3.6. Between being floating point and focusing only on formants (instead of pitch) it sounded really smooth (not like the mobster informant on TV).

With a formant shifter, you can gender bend voices or pretend you've been breathing helium. It's fun.

I've been hoping to port it over to Teensy someday. Maybe I can make some time to do that.

Chip
 
Status
Not open for further replies.
Back
Top