You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hexefx_audiolib_F32/src/basic_pitch.h

122 lines
3.9 KiB

11 months ago
#ifndef _BASIC_PITCH_H_
#define _BASIC_PITCH_H_
#include <Arduino.h>
#include "Audio.h"
#define BASIC_PITCH_BUF_BITS (12)
#define BASIC_PITCH_BUF_SIZE (1<<BASIC_PITCH_BUF_BITS)
#define BASIC_PITCH_BUF_SIZE_HALF (1<<(BASIC_PITCH_BUF_BITS-1))
#define BASIC_PITCH_BUF_MASK (BASIC_PITCH_BUF_SIZE-1)
#define BASIC_PITCH_BUF_FRAC_MASK ((1<<(32-BASIC_PITCH_BUF_BITS))-1)
#define BASIC_PITCH_XFADE_BITS (10)
#define BASIC_PITCH_XFADE_LEN (1<<BASIC_PITCH_XFADE_BITS)
#define BASIC_PITCH_XFADE_LEN_HALF (BASIC_PITCH_XFADE_LEN>>1)
#define BASIC_PITCH_XFADE_MASK (BASIC_PITCH_XFADE_LEN-1)
extern "C" {
extern const float AudioWaveformFader_f32[]; // crossfade waveform
extern const float music_intevals[]; // semitone intervals -1oct to +2oct
}
class AudioBasicPitch
{
public:
bool init()
{
outFilter.init(hp_f, (float *)&hp_gain, lp_f, &lp_gain);
bf = (float *)malloc(BASIC_PITCH_BUF_SIZE*sizeof(float)); // allocate buffer
if (!bf) return false;
reset();
return true;
}
void setPitch(float ratio)
{
readAdder = (float)pitchDelta0 * ratio;
}
void setPitchSemintone(int8_t s)
{
s = constrain(s, -12, +24); // limit to the predefined range
setPitch(music_intevals[s + 12]);
}
void setTone(float t)
{
//lp_f = constrain(t, 0.01f, 1.0f);
lp_gain = constrain(t, 0.0f, 1.0f);
}
float process(float newSample)
{
uint32_t idx1, idx2;
uint32_t delta, delta_acc;
float k_frac, delta_frac, s_n, s_half, xf0, xf1;
bf[writeAddr] = newSample; // write new sample
readAddr = readAddr + readAdder; // update read pointer, readAdder controls the pitch
// bypass mode is at mix = 0 or if no pitch change
if (mix == 0.0f || readAdder == pitchDelta0)
{
writeAddr = (writeAddr + 1) & BASIC_PITCH_BUF_MASK;
return newSample;
}
// sample end
idx1 = (readAddr >> (32-BASIC_PITCH_BUF_BITS)) & BASIC_PITCH_BUF_MASK; // index of the last sample
k_frac = (float)(readAddr & BASIC_PITCH_BUF_FRAC_MASK) / (float)BASIC_PITCH_BUF_FRAC_MASK; // fractional part
s_n = bf[idx1] * (1.0f-k_frac);
s_n += bf[(idx1 + 1) & BASIC_PITCH_BUF_MASK] * k_frac; // interpolated sample
// sample half
idx2 = ((readAddr + 0x80000000) >> (32-BASIC_PITCH_BUF_BITS)) & BASIC_PITCH_BUF_MASK;
k_frac = (float)((readAddr+0x80000000) & BASIC_PITCH_BUF_FRAC_MASK) / (float)BASIC_PITCH_BUF_FRAC_MASK;
s_half = bf[idx2] * (1.0f - k_frac);
s_half += bf[(idx2 + 1) & BASIC_PITCH_BUF_MASK] * k_frac;
delta_acc = readAddr - (writeAddr<<(32-BASIC_PITCH_BUF_BITS)); // distance between the write and read pointer
delta = (delta_acc >> (32-9)) & 0x1FF; // 9 bit value = 2x fade table length (fade in + fade out)
delta_frac = (float)(delta_acc & ((1<<23)-1)) / (float)((1<<23)-1); // fractional part for the xfade curve
idx2 = delta&0xFF;
xf0 = AudioWaveformFader_f32[idx2];
xf1 = AudioWaveformFader_f32[idx2+1];
k_frac = xf0 * (1.0f-delta_frac) + xf1 * delta_frac; // interpolated smooth crossfade coeff.
if (delta > 0xFF) k_frac = 1.0f-k_frac; // invert the curve for the fade out part
s_n = s_n * k_frac + s_half * (1.0f - k_frac); // crossfade the last and mid sample
writeAddr = (writeAddr + 1) & BASIC_PITCH_BUF_MASK; // update the write pointer
s_n = outFilter.process(s_n); // apply output lowpass
return (s_n * mix + newSample * (1.0f-mix)); // do dry/wet mix
}
void setMix(float mixRatio)
{
mix = constrain(mixRatio, 0.0f, 1.0f);
}
void reset()
{
memset(bf, 0, BASIC_PITCH_BUF_SIZE*sizeof(float));
readAddr = 0;
writeAddr = 0;
readAdder = pitchDelta0;
mix = 1.0f;
}
private:
float *bf;
float mix;
uint32_t readAddr;
uint32_t readAdder;
uint16_t writeAddr;
static const uint32_t pitchDelta0 = BASIC_PITCH_BUF_FRAC_MASK+1;
AudioFilterShelvingLPHP outFilter;
static constexpr float hp_f = 0.003f;
const float hp_gain = 0.0f;
static constexpr float lp_f = 0.26f;
float lp_gain = 1.0f;
};
#endif // _BASIC_PITCH_H_