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)
.mm or .cpp
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: