AutoTune plugin

georgerosar

Active member
I just had deepseek make this, you can add it to the teensy audio library if you want, just credit Kap10 George.

Considered Free Code too, even for sale of it

(You should be able to modify this easily to work)
Code:
#ifndef AUTOTUNE_PLUGIN_H
#define AUTOTUNE_PLUGIN_H 1
#include <assert.h>
#include <algorithm>  // Add this

#import <Audio.h>
#import <AudioStream.h>
#include <arm_math.h>
template<typename T>
static inline T autotuneConstrain(T value, T minVal, T maxVal) {
    if (value < minVal) return minVal;
    if (value > maxVal) return maxVal;
    return value;
}

// Or using standard library functions:
template<typename T>
static inline T autotuneClamp(T value, T minVal, T maxVal) {
    return std::max(minVal, std::min(maxVal, value));
}

// Autotune scales
enum AutotuneScale {
    AUTOTUNE_CHROMATIC,    // All notes
    AUTOTUNE_MAJOR,        // Major scale
    AUTOTUNE_MINOR,        // Natural minor
    AUTOTUNE_PENTATONIC,   // Major pentatonic
    AUTOTUNE_BLUES,        // Blues scale
    AUTOTUNE_WHOLE_TONE,   // Whole tone scale
    AUTOTUNE_CUSTOM        // Custom scale
};

class AutotunePlugin : public AudioStream {
private:
    // Audio buffers
    audio_block_t *inputQueueArray[1];
  
    // Pitch detection
    float pitchBuffer[256];           // Buffer for pitch analysis
    float windowBuffer[256];          // Window function
    float correlationBuffer[128];     // Autocorrelation buffer
    float detectedPitch;              // Detected pitch in Hz
    float targetPitch;                // Target pitch (corrected) in Hz
    bool pitchDetected;               // Flag if pitch is detected
  
    // Autotune parameters
    AutotuneScale currentScale;
    float scaleNotes[12];             // Which notes are in scale (1.0 = in scale, 0.0 = not)
    float strength;                   // 0.0 = no correction, 1.0 = full correction
    float correctionSpeed;            // Speed of pitch correction
    float formantPreservation;        // Formant preservation amount
  
    // MIDI parameters
    float midiNote;                   // Current MIDI note (0-127)
    float midiPitchBend;              // Pitch bend amount (-1.0 to 1.0)
    bool midiActive;                  // Is MIDI active?
  
    // Pitch shifting variables
    float phaseAccumulator;           // Phase accumulator for pitch shifting
    float phaseIncrement;             // Current phase increment
    float targetPhaseIncrement;       // Target phase increment
    float formantShiftFactor;         // Formant shift factor
  
    // Formant preservation buffer
    float formantBuffer[512];
    int formantBufferIndex;
  

  
    // Helper functions
    float detectPitch(float *buffer, int length);
    float snapToScale(float frequency);
    float calculateFormantShift(float originalFreq, float targetFreq);
    void updateScaleNotes();
    void applyFormantPreservation(float *input, float *output, int length);
  
public:
    static constexpr int majorScale[7] = {0, 2, 4, 5, 7, 9, 11};


    static constexpr int minorScale[7] = {0, 2, 3, 5, 7, 8, 10};


    static constexpr int pentatonicScale[5] = {0, 2, 4, 7, 9};


    static constexpr int bluesScale[6] = {0, 3, 5, 6, 7, 10};


    static constexpr int wholeToneScale[6] = {0, 2, 4, 6, 8, 10};


    static constexpr float midiNoteFreq[128] = {


        8.1758, 8.6620, 9.1770, 9.7227, 10.3009, 10.9134, 11.5623, 12.2499, 12.9783, 13.7500, 14.5676, 15.4339,


        16.3516, 17.3239, 18.3540, 19.4454, 20.6017, 21.8268, 23.1247, 24.4997, 25.9565, 27.5000, 29.1352, 30.8677,


        32.7032, 34.6478, 36.7081, 38.8909, 41.2034, 43.6535, 46.2493, 48.9994, 51.9131, 55.0000, 58.2705, 61.7354,


        65.4064, 69.2957, 73.4162, 77.7817, 82.4069, 87.3071, 92.4986, 97.9989, 103.8262, 110.0000, 116.5409, 123.4708,


        130.8128, 138.5913, 146.8324, 155.5635, 164.8138, 174.6141, 184.9972, 195.9977, 207.6523, 220.0000, 233.0819, 246.9417,


        261.6256, 277.1826, 293.6648, 311.1270, 329.6276, 349.2282, 369.9944, 391.9954, 415.3047, 440.0000, 466.1638, 493.8833,


        523.2511, 554.3653, 587.3295, 622.2540, 659.2551, 698.4565, 739.9888, 783.9909, 830.6094, 880.0000, 932.3275, 987.7666,


        1046.5023, 1108.7305, 1174.6591, 1244.5079, 1318.5102, 1396.9129, 1479.9777, 1567.9817, 1661.2188, 1760.0000, 1864.6550, 1975.5332,


        2093.0045, 2217.4610, 2349.3181, 2489.0159, 2637.0205, 2793.8259, 2959.9554, 3135.9635, 3322.4376, 3520.0000, 3729.3101, 3951.0664,


        4186.0090, 4434.9221, 4698.6363, 4978.0317, 5274.0409, 5587.6517, 5919.9108, 6271.9270, 6644.8752, 7040.0000, 7458.6202, 7902.1328,


        8372.0181, 8869.8442, 9397.2725, 9956.0635, 10548.0818, 11175.3034, 11839.8215, 12543.8540


    };
    AutotunePlugin() : AudioStream(1, inputQueueArray),
        detectedPitch(0.0f), targetPitch(0.0f), pitchDetected(false),
        currentScale(AUTOTUNE_CHROMATIC), strength(1.0f),
        correctionSpeed(0.1f), formantPreservation(0.5f),
        midiNote(69.0f), midiPitchBend(0.0f), midiActive(false),
        phaseAccumulator(0.0f), phaseIncrement(0.0f),
        targetPhaseIncrement(0.0f), formantShiftFactor(1.0f),
        formantBufferIndex(0) {
      
        // Initialize window function (Hanning)
        for (int i = 0; i < 256; i++) {
            windowBuffer[i] = 0.5f * (1.0f - cosf(2.0f * M_PI * i / 255.0f));
        }
      
        // Initialize scale notes
        updateScaleNotes();
      
        // Initialize formant buffer
        for (int i = 0; i < 512; i++) {
            formantBuffer[i] = 0.0f;
        }
    }
  
    // Audio processing
    virtual void update();
        void setStrength(float s) { strength = autotuneConstrain(s, 0.0f, 1.0f); }
    void setScale(AutotuneScale scale) {
        currentScale = scale;
        updateScaleNotes();
    }
    void setCorrectionSpeed(float speed) {
        correctionSpeed = autotuneConstrain(speed, 0.001f, 1.0f);
    }
    void setFormantPreservation(float amount) {
        formantPreservation = autotuneConstrain(amount, 0.0f, 1.0f);
    }
  
    // MIDI control
    void setMidiNote(float note) {
        // Also constrain the MIDI note to valid range
        if (note < 0.0f) midiNote = 0.0f;
        else if (note > 127.0f) midiNote = 127.0f;
        else midiNote = note;
        midiActive = true;
    }
      
    // MIDI control
    void noteOn(Byte channel, Byte note, Byte velocity);
    void noteOff(Byte channel, Byte note, Byte velocity);
    void pitchBend(Byte channel, int bend);  // bend: -8192 to 8191
        void setMidiActive(bool active) { midiActive = active; }
  
    // Custom scale
    void setCustomScale(bool notes[12]);
  
    // Status getters
    float getDetectedPitch() const { return detectedPitch; }
    float getTargetPitch() const { return targetPitch; }
    bool isPitchDetected() const { return pitchDetected; }
    float getCurrentPitch() const {
        return midiActive ? midiNoteFreq[(int)midiNote] * pow(2.0f, midiPitchBend / 12.0f) : targetPitch;
    }
};
#endif // AUTOTUNE_PLUGIN_H


.mm or .cpp
Code:
//
//  autoTunePlugin.mm
//  Audio iOS + Synth
//
//  Created by George Rosar on 1/8/26.
//

#include "autoTunePlugin.h"

void AutotunePlugin::update() {
    audio_block_t *inputBlock, *outputBlock;
  
    // Get input block
    inputBlock = receiveReadOnly(0);
    if (!inputBlock) return;
  
    // Allocate output block
    outputBlock = allocate();
    if (!outputBlock) {
        release(inputBlock);
        return;
    }
  
    // Convert audio blocks to float for processing
    int16_t *inputData = inputBlock->data;
    int16_t *outputData = outputBlock->data;
  
    // Process audio in chunks
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i += 256) {
        int chunkSize = fmin(256, AUDIO_BLOCK_SAMPLES - i);
      
        // Fill pitch buffer with windowed audio
        for (int j = 0; j < chunkSize; j++) {
            int index = i + j;
            if (index < AUDIO_BLOCK_SAMPLES) {
                pitchBuffer[j] = (float)inputData[index] / 32768.0f * windowBuffer[j];
            } else {
                pitchBuffer[j] = 0.0f;
            }
        }
      
        // Detect pitch
        detectedPitch = detectPitch(pitchBuffer, chunkSize);
        pitchDetected = (detectedPitch > 20.0f && detectedPitch < 2000.0f);
      
        // Calculate target pitch
        if (midiActive) {
            // Use MIDI note as target
            float baseFreq = midiNoteFreq[(int)midiNote];
            targetPitch = baseFreq * pow(2.0f, midiPitchBend / 12.0f);
        } else if (pitchDetected && strength > 0.001f) {
            // Autotune detected pitch
            float snappedPitch = snapToScale(detectedPitch);
            targetPitch = detectedPitch * (1.0f - strength) + snappedPitch * strength;
        } else {
            // No correction
            targetPitch = detectedPitch;
        }
      
        // Calculate phase increment for pitch shifting
        if (pitchDetected && detectedPitch > 0.0f) {
            float ratio = targetPitch / detectedPitch;
            targetPhaseIncrement = ratio;
          
            // Smooth phase increment change
            phaseIncrement = phaseIncrement * (1.0f - correctionSpeed) +
                           targetPhaseIncrement * correctionSpeed;
          
            // Calculate formant shift for preservation
            formantShiftFactor = calculateFormantShift(detectedPitch, targetPitch);
        } else {
            phaseIncrement = 1.0f; // No shift
            formantShiftFactor = 1.0f;
        }
      
        // Process audio chunk with pitch shifting
        for (int j = 0; j < chunkSize; j++) {
            int inputIndex = i + j;
            if (inputIndex >= AUDIO_BLOCK_SAMPLES) break;
          
            // Get input sample
            float inputSample = (float)inputData[inputIndex] / 32768.0f;
          
            // Store in formant buffer for preservation
            formantBuffer[formantBufferIndex] = inputSample;
            formantBufferIndex = (formantBufferIndex + 1) % 512;
          
            // Phase vocoder style pitch shifting (simplified)
            float outputSample = 0.0f;
          
            if (phaseIncrement != 1.0f && formantPreservation > 0.001f) {
                // Time-stretch with formant preservation
                float readPosition = phaseAccumulator * 512.0f;
                int pos1 = (int)readPosition;
                int pos2 = (pos1 + 1) % 512;
                float frac = readPosition - pos1;
              
                // Read from formant buffer with interpolation
                float delayedSample = formantBuffer[pos1] * (1.0f - frac) +
                                    formantBuffer[pos2] * frac;
              
                // Mix with direct signal based on formant preservation
                outputSample = inputSample * (1.0f - formantPreservation) +
                             delayedSample * formantPreservation;
            } else if (phaseIncrement != 1.0f) {
                // Simple pitch shifting (no formant preservation)
                float readPosition = phaseAccumulator * 512.0f;
                int pos1 = (int)readPosition;
                int pos2 = (pos1 + 1) % 512;
                float frac = readPosition - pos1;
              
                outputSample = formantBuffer[pos1] * (1.0f - frac) +
                             formantBuffer[pos2] * frac;
            } else {
                // No pitch shifting
                outputSample = inputSample;
            }
          
            // Update phase accumulator
            phaseAccumulator += 1.0f / 512.0f * phaseIncrement;
            while (phaseAccumulator >= 1.0f) phaseAccumulator -= 1.0f;
          
            // Write output
            outputData[inputIndex] = (int16_t)(outputSample * 32767.0f);
        }
    }
  
    // Transmit output block
    transmit(outputBlock, 0);
    release(inputBlock);
    release(outputBlock);
}

float AutotunePlugin::detectPitch(float *buffer, int length) {
    // Autocorrelation pitch detection
    float maxCorrelation = 0.0f;
    int maxLag = 0;
  
    // Calculate autocorrelation
    for (int lag = 20; lag < 128; lag++) { // 20 = ~2kHz, 128 = ~344Hz @ 44.1kHz
        float correlation = 0.0f;
      
        for (int i = 0; i < length - lag; i++) {
            correlation += buffer[i] * buffer[i + lag];
        }
      
        correlationBuffer[lag] = correlation;
      
        // Find maximum correlation (excluding very short lags)
        if (correlation > maxCorrelation && lag > 20) {
            maxCorrelation = correlation;
            maxLag = lag;
        }
    }
  
    // Calculate pitch in Hz
    if (maxCorrelation > 0.01f && maxLag > 0) {
        // Find precise lag using parabolic interpolation
        if (maxLag > 1 && maxLag < 126) {
            float y1 = correlationBuffer[maxLag - 1];
            float y2 = correlationBuffer[maxLag];
            float y3 = correlationBuffer[maxLag + 1];
          
            float preciseLag = maxLag + (y3 - y1) / (2.0f * (2.0f * y2 - y1 - y3));
            return AUDIO_SAMPLE_RATE_EXACT / preciseLag;
        } else {
            return AUDIO_SAMPLE_RATE_EXACT / maxLag;
        }
    }
  
    return 0.0f; // No pitch detected
}

float AutotunePlugin::snapToScale(float frequency) {
    if (frequency <= 0.0f) return frequency;
  
    // Convert frequency to MIDI note number
    float note = 69.0f + 12.0f * log2f(frequency / 440.0f);
  
    // Find nearest note in scale
    float nearestNote = floorf(note + 0.5f);
    int midiNoteInt = (int)nearestNote;
  
    // Check if note is in scale
    int noteClass = midiNoteInt % 12;
    if (noteClass < 0) noteClass += 12;
  
    // If note is not in scale, find nearest scale note
    if (scaleNotes[noteClass] < 0.5f) {
        // Find nearest scale note
        float bestDistance = 1000.0f;
        float bestNote = nearestNote;
      
        for (int i = 0; i < 12; i++) {
            if (scaleNotes[i] > 0.5f) {
                // This note is in scale
                float scaleNote = floorf(note) + i - noteClass;
                float distance = fabsf(scaleNote - note);
              
                if (distance < bestDistance) {
                    bestDistance = distance;
                    bestNote = scaleNote;
                }
            }
        }
      
        nearestNote = bestNote;
    }
  
    // Convert back to frequency
    return 440.0f * powf(2.0f, (nearestNote - 69.0f) / 12.0f);
}

float AutotunePlugin::calculateFormantShift(float originalFreq, float targetFreq) {
    if (originalFreq <= 0.0f || targetFreq <= 0.0f) return 1.0f;
  
    float ratio = targetFreq / originalFreq;
  
    // Preserve formants by using inverse of pitch shift ratio
    // This helps keep vocal characteristics while changing pitch
    if (ratio > 1.0f) {
        // Pitch up - compress formants
        return 1.0f / ratio;
    } else {
        // Pitch down - expand formants
        return 1.0f;
    }
}

void AutotunePlugin::updateScaleNotes() {
    // Clear scale notes
    for (int i = 0; i < 12; i++) {
        scaleNotes[i] = 0.0f;
    }
  
    // Set scale notes based on current scale
    switch (currentScale) {
        case AUTOTUNE_CHROMATIC:
            for (int i = 0; i < 12; i++) scaleNotes[i] = 1.0f;
            break;
          
        case AUTOTUNE_MAJOR:
            for (int i = 0; i < 7; i++) {
                scaleNotes[majorScale[i]] = 1.0f;
            }
            break;
          
        case AUTOTUNE_MINOR:
            for (int i = 0; i < 7; i++) {
                scaleNotes[minorScale[i]] = 1.0f;
            }
            break;
          
        case AUTOTUNE_PENTATONIC:
            for (int i = 0; i < 5; i++) {
                scaleNotes[pentatonicScale[i]] = 1.0f;
            }
            break;
          
        case AUTOTUNE_BLUES:
            for (int i = 0; i < 6; i++) {
                scaleNotes[bluesScale[i]] = 1.0f;
            }
            break;
          
        case AUTOTUNE_WHOLE_TONE:
            for (int i = 0; i < 6; i++) {
                scaleNotes[wholeToneScale[i]] = 1.0f;
            }
            break;
          
        case AUTOTUNE_CUSTOM:
            // Custom scale set separately
            break;
    }
}

void AutotunePlugin::noteOn(Byte channel, Byte note, Byte velocity) {
    // Ignore channel for now (could implement multi-channel later)
    (void)channel;
  
    if (velocity > 0) {
        midiNote = note;
        midiActive = true;
    } else {
        noteOff(channel, note, velocity);
    }
}

void AutotunePlugin::noteOff(Byte channel, Byte note, Byte velocity) {
    (void)channel;
    (void)velocity;
  
    if ((Byte)midiNote == note) {
        midiActive = false;
    }
}

void AutotunePlugin::pitchBend(Byte channel, int bend) {
    (void)channel;
  
    // Convert bend to semitones (-2 to +2 semitones typical)
    midiPitchBend = (float)bend / 8192.0f * 2.0f;
}

void AutotunePlugin::setCustomScale(bool notes[12]) {
    currentScale = AUTOTUNE_CUSTOM;
    for (int i = 0; i < 12; i++) {
        scaleNotes[i] = notes[i] ? 1.0f : 0.0f;
    }
}
 
Last edited:
Code:
// In your main sketch
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include "autoTunePlugin.h"

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=105,63
AutotunePlugin           autotune;       //xy=281,73
AudioOutputI2S           i2s2;           //xy=470,120
AudioConnection          patchCord1(i2s1, 0, autotune, 0);
AudioConnection          patchCord2(autotune, 0, i2s2, 0);
AudioConnection          patchCord3(autotune, 0, i2s2, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265,212
// GUItool: end automatically generated code

// MIDI handling
void handleNoteOn(byte channel, byte pitch, byte velocity) {
    autotune.noteOn(channel, pitch, velocity);
}

void handleNoteOff(byte channel, byte pitch, byte velocity) {
    autotune.noteOff(channel, pitch, velocity);
}

void handlePitchBend(byte channel, int bend) {
    autotune.pitchBend(channel, bend);
}

void setup() {
    Serial.begin(115200);
    
    // Audio memory
    AudioMemory(20);
    
    // Enable the audio shield
    sgtl5000_1.enable();
    sgtl5000_1.volume(0.5);
    sgtl5000_1.inputSelect(AUDIO_INPUT_MIC);
    sgtl5000_1.micGain(40);
    
    // Configure autotune
    autotune.setStrength(1.0);      // Full autotune
    autotune.setScale(AUTOTUNE_MAJOR); // Major scale
    autotune.setCorrectionSpeed(0.1);  // Medium correction speed
    autotune.setFormantPreservation(0.7); // Preserve vocal formants
    
    // Setup MIDI
    usbMIDI.setHandleNoteOn(handleNoteOn);
    usbMIDI.setHandleNoteOff(handleNoteOff);
    usbMIDI.setHandlePitchBend(handlePitchBend);
    
    Serial.println("Autotune Plugin Ready!");
}

void loop() {
    // Handle MIDI
    usbMIDI.read();
    
    // Print pitch info periodically
    static elapsedMillis printTimer = 0;
    if (printTimer > 1000) {
        printTimer = 0;
        
        Serial.print("Detected Pitch: ");
        Serial.print(autotune.getDetectedPitch());
        Serial.print(" Hz, Target: ");
        Serial.print(autotune.getTargetPitch());
        Serial.print(" Hz, MIDI Active: ");
        Serial.println(autotune.getCurrentPitch());
    }
    
    // You can also control autotune parameters with potentiometers
    /*
    int strengthPot = analogRead(A0);
    autotune.setStrength(strengthPot / 1023.0);
    
    int scalePot = analogRead(A1);
    int scale = map(scalePot, 0, 1023, 0, 5);
    autotune.setScale((AutotuneScale)scale);
    */
}
 
Back
Top