arduino-pico/tools/makepacer.cpp
Earle F. Philhower, III d732ac82ba
Precalculate common PWMAudio dividers, avoid noise (#2714)
Calculating the DMA clock divider for PWMAudio can take a very long time
because there are 65K floating point divisions required.  But most audio
will be one of a dozen sample rates, so it is possible to precalculate
the rates on the host and use a small lookup table to avoid any math at
all.  Removes occasional scratching in PWMAudio when the BackgroundAudio
library changes sample rates.
2024-12-22 14:05:44 -08:00

81 lines
2.7 KiB
C++

// Generates a header with precalculated best dividers for PWMAudio at
// standard clock frequencies and audio rates.
//
#include <stdint.h>
#include <stdio.h>
#include <math.h>
void find_pacer_fraction(int F_CPU, int target, uint16_t *numerator, uint16_t *denominator) {
const uint16_t max = 0xFFFF;
/*Cache last results so we dont have to recalculate*/
static int last_target;
static uint16_t bestNum;
static uint16_t bestDenom;
/*Check if we can load the previous values*/
if (target == last_target) {
*numerator = bestNum;
*denominator = bestDenom;
return;
}
float targetRatio = (float)F_CPU / target;
float lowestError = 10000000;
for (uint16_t denom = 1; denom < max; denom++) {
uint16_t num = (int)((targetRatio * denom) + 0.5f); /*Calculate numerator, rounding to nearest integer*/
/*Check if numerator is within bounds*/
if (num > 0 && num < max) {
float actualRatio = (float)num / denom;
float error = fabsf(actualRatio - targetRatio);
if (error < lowestError) {
bestNum = num;
bestDenom = denom;
lowestError = error;
if (error == 0) {
break;
}
}
}
}
last_target = target;
*numerator = bestNum;
*denominator = bestDenom;
}
int main(int argc, char **argv) {
(void) argc;
(void) argv;
int M = 1000000;
int fsys[] = {50*M, 100*M, 120*M, 125*M, 128*M, 133*M, 150*M, 175*M, 200*M, 225*M, 240*M, 250*M, 275*M, 300*M};
int freq[] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000};
FILE *f = fopen("../libraries/PWMAudio/src/PWMAudioPrecalc.h", "w");
fprintf(f, "// Generated by tools/makepacer.cpp, do not edit\n");
fprintf(f, "typedef struct {\n");
fprintf(f, " uint32_t freq;\n");
fprintf(f, " uint16_t n;\n");
fprintf(f, " uint16_t d;\n");
fprintf(f, "} PWMPacerPrecalc;\n");
for (int i = 0; i < sizeof(fsys)/sizeof(fsys[0]); i++) {
fprintf(f, "#%s F_CPU == %d\n", i == 0 ? "if" : "elif", fsys[i]);
fprintf(f, "static const PWMPacerPrecalc __PWMAudio_pacer[] = {");
for (int j = 0; j < sizeof(freq)/sizeof(freq[0]); j++) {
uint16_t n, d;
find_pacer_fraction(fsys[i], freq[j], &n, &d);
fprintf(f, "{%d, %d, %d}", freq[j], n, d);
if (j < sizeof(freq)/sizeof(freq[0]) - 1) {
fprintf(f, ", ");
}
}
fprintf(f, "};\n");
}
fprintf(f, "#else\n");
fprintf(f, "const PWMPacerPrecalc __PWMAudio_pacer[] = {{1, 1, 1}}; // Invalid, should never match\n");
fprintf(f, "#endif\n");
fclose(f);
}