function calculateToneSamples(targetFreqs, sampleRate, sampleDataBits, terminator, maxErrorPct, maxCycles = 10) {
let totalSamples = 0;
const tones = targetFreqs.map((targetFreq) => {
const exactSamplesPerCycle = sampleRate / targetFreq;
let cycles = 0;
let intSamples, actualFreq, errorPct;
do {
++cycles;
intSamples = Math.round(exactSamplesPerCycle * cycles);
actualFreq = sampleRate * cycles / intSamples;
errorPct = (actualFreq - targetFreq) * 100 / targetFreq;
} while(Math.abs(errorPct) > maxErrorPct && cycles < maxCycles);
totalSamples += intSamples;
const data = [];
const halfAmplitude = ((1 << sampleDataBits) - 1) / 2;
for (i = 0; i < intSamples; ++i) {
let value = Math.round(halfAmplitude + halfAmplitude * (Math.sin(i * cycles * 2 * Math.PI / intSamples)));
if (value === terminator) {
++value;
}
data.push(value);
}
data.push(terminator);
return {
targetFreq,
actualFreq,
samples: intSamples,
cycles,
error: errorPct,
data
};
});
// Add one sample per tone for a terminator value.
// Multiple by 2 because each byte of data takes up a word of program memory
const totalBytes = (totalSamples + tones.length) * 2;
const csvReport = [
["Sample Rate", sampleRate].join("\t"),
["Max Error", `${maxErrorPct}%`].join("\t"),
["Target Hz", "Samples","Cycles", "Actual Hz", "Percent Error"].join("\t"),
...tones.map((tone) => [tone.targetFreq, tone.samples, tone.cycles, tone.actualFreq, `${tone.error}%`].join("\t")),
["Total Samples", totalSamples].join("\t"),
["Total Bytes", totalBytes].join("\t")
].join("\n");
console.log(csvReport);
return {
totalSamples,
totalBytes,
tones
};
}