I have a program that can take a waveform, such as a sine wave or sawtooth wave and detune it with another waveform that is slightly sharper or flatter. It takes the frequency of said waveform and adds or subtracts the amount of hertz to detune from it depending on which threads are activated. If I have a sine wave at 261.62Hz (middle C), and detune it to three threads - one sharp, one flat and one still at unison pitch (no change) - I end up with a detuned sine wave.
Now if I have a PCM audio sample (say a wave file of a trumpet with loop points) at middle C pitch with a play sample rate of 44100, how do I calculate the amount to add to or subtract from the play sample rate to detune it one hertz? How do I do this with other sample rates?
I need a formula that can detune the PCM sampled data at any sample rate to detune any possible threads up or down by one hertz.
Code for detuner
public class Detuner implements ISample {
// constants
public static final double DEF_DETUNING_VALUE = 0;
public static final boolean DEF_UNNISON_TUNING = true;
public static final boolean DEF_DETUNING = false;
public static final byte DEF_DETUNE_THREADS = 1;
// instance variables
private double celesteTuning; // frequency to offset the tuning by
private boolean sharpTuning; // turns on sharp tuning
private boolean flatTuning; // turns on flat tuning
private boolean unisonTuning; // turns on unison tuning
private double unisonToDetuningRatio; // defines how loud the unison is to the detuning
private double detuningToProcess; // use in certain scenarios
private ISample baseWaveform; // the base waveform
private ISample[] sharpWaveforms; // sharp
private ISample[] flatWaveforms; // flat
private ISample unisonWaveGeneration; // uninson
private byte detuneThreads; // number of threads for each detune side
private double averageThreadDetuneAmount; // the amount to add on each thread
private double unisonFrequency;
private AdsrEnvelope adsrEnvelope;
// constructor
public Detuner(double celesteTuning, boolean sharpTuning,
boolean flatTuning, boolean unisonTuning,
double unisonToDetuningRatio, ISample detuneWaveform,
byte detuneThreads)
throws CloneNotSupportedException {
this.celesteTuning = celesteTuning;
this.sharpTuning = sharpTuning;
this.flatTuning = flatTuning;
this.unisonTuning = unisonTuning;
this.unisonToDetuningRatio = unisonToDetuningRatio;
this.detuneThreads = detuneThreads;
// if sharp, flat and unison tunings are used
if (sharpTuning && flatTuning && !unisonTuning) {
// detuningToProcess is celesteTuning / 2
detuningToProcess = celesteTuning / 2;
} // otherwise it is celesteTuning
else {
detuningToProcess = celesteTuning;
}
// set average thread detune and offset amount
averageThreadDetuneAmount = detuningToProcess / detuneThreads;
baseWaveform = (ISample) detuneWaveform;
unisonFrequency = baseWaveform.getFrequency();
// random offset to frequency
double randomFrequencyOffset;
// if unnison to detuning ratio is zero set it to division of all threads
if (unisonToDetuningRatio == 0) {
int totalThreads = detuneThreads;
if (sharpTuning && flatTuning) {
totalThreads *= 2;
}
// increment for unison thread
totalThreads++;
this.unisonToDetuningRatio = (double) 1 / (double) totalThreads;
}
if (sharpTuning) {
// detuning holder
double detuningHolder = averageThreadDetuneAmount;
// initialise the array
sharpWaveforms = new ISample[detuneThreads];
// define averate amount to add to each thread
for (int i = 0; i < sharpWaveforms.length; i++) {
sharpWaveforms[i] = (ISample) baseWaveform.clone();
if (i == 0) {
randomFrequencyOffset = detuningToProcess;
} else {
randomFrequencyOffset = detuningHolder
+ (Math.random() * averageThreadDetuneAmount
- averageThreadDetuneAmount / 2 / detuneThreads);
detuningHolder += averageThreadDetuneAmount;
}
// if it is an audio sample up the play sample rate
if (baseWaveform instanceof SamplePlayer) {
// formula should go here and extract the sample rate of the sample player
}
sharpWaveforms[i].addToFrequency(randomFrequencyOffset);
if (detuneThreads != 1) {
sharpWaveforms[i].setOffset(Math.random());
}
}
}
if (flatTuning) {
// detuning holder
double detuningHolder = averageThreadDetuneAmount;
// initialise the array
flatWaveforms = new ISample[detuneThreads];
// define averate amount to add to each thread
for (int i = 0; i < flatWaveforms.length; i++) {
flatWaveforms[i] = (ISample) baseWaveform.clone();
if (i == 0) {
randomFrequencyOffset = detuningToProcess;
} else {
randomFrequencyOffset = detuningHolder
+ (Math.random() * averageThreadDetuneAmount
- averageThreadDetuneAmount / 2 / detuneThreads);
detuningHolder += averageThreadDetuneAmount;
}
// if it is an audio sample up the play sample rate
if (baseWaveform instanceof SamplePlayer) {
// formula also goes here
}
flatWaveforms[i].subtractFromFrequency(randomFrequencyOffset);
if (detuneThreads != 1) {
flatWaveforms[i].setOffset(Math.random());
}
}
}
if (unisonTuning) {
unisonWaveGeneration = (ISample) baseWaveform;
}
}
```