Skip to content

Commit cd912e1

Browse files
authored
Merge pull request #5218 from jepler/issue-5092
Fix audio sample rate on rp2040
2 parents da320c3 + bef0796 commit cd912e1

File tree

1 file changed

+55
-22
lines changed

1 file changed

+55
-22
lines changed

ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "common-hal/audiopwmio/PWMAudioOut.h"
2828

29+
#include <math.h>
2930
#include <stdint.h>
3031
#include <string.h>
3132

@@ -51,6 +52,58 @@
5152
#define SAMPLE_BITS_TO_DISCARD (16 - BITS_PER_SAMPLE)
5253
#define PWM_TOP ((1 << BITS_PER_SAMPLE) - 1)
5354

55+
56+
static uint32_t gcd(uint32_t a, uint32_t b) {
57+
while (b) {
58+
uint32_t tmp = a % b;
59+
a = b;
60+
b = tmp;
61+
}
62+
return a;
63+
}
64+
65+
static uint32_t limit_denominator(uint32_t max_denominator, uint32_t num_in, uint32_t den_in, uint32_t *den_out) {
66+
// Algorithm based on Python's limit_denominator
67+
uint32_t p0 = 0, q0 = 1, p1 = 1, q1 = 0;
68+
uint32_t d = den_in, n = num_in;
69+
uint32_t g = gcd(n, d);
70+
d /= g;
71+
n /= g;
72+
if (d < max_denominator) {
73+
*den_out = d;
74+
return n;
75+
}
76+
while (1) {
77+
uint32_t a = n / d;
78+
uint32_t q2 = q0 + a * q1;
79+
if (q2 > max_denominator) {
80+
break;
81+
}
82+
83+
uint32_t p_tmp = p0 + a * p1;
84+
p0 = p1;
85+
q0 = q1;
86+
p1 = p_tmp;
87+
q1 = q2;
88+
89+
uint32_t d_tmp = n - a * d;
90+
n = d;
91+
d = d_tmp;
92+
}
93+
uint32_t k = (max_denominator - q0) / q1;
94+
uint32_t bound1_num = p0 + k * p1, bound1_den = q0 + k * q1;
95+
uint32_t bound2_num = p1, bound2_den = q1;
96+
97+
if (fabsf((float)bound1_num / bound1_den - (float)num_in / den_in) <=
98+
fabsf((float)bound2_num / bound2_den - (float)num_in / den_in)) {
99+
*den_out = bound2_den;
100+
return bound2_num;
101+
}
102+
103+
*den_out = bound1_den;
104+
return bound1_num;
105+
}
106+
54107
void audiopwmout_reset() {
55108
for (size_t i = 0; i < NUM_DMA_TIMERS; i++) {
56109
dma_hw->timer[i] = 0;
@@ -170,30 +223,10 @@ void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self,
170223
uint32_t sample_rate = audiosample_sample_rate(sample);
171224

172225
uint32_t system_clock = common_hal_mcu_processor_get_frequency();
173-
uint32_t best_numerator = 0;
174-
uint32_t best_denominator = 0;
175-
uint32_t best_error = system_clock;
176-
177-
for (uint32_t denominator = 0xffff; denominator > 0; denominator--) {
178-
uint32_t numerator = (denominator * sample_rate) / system_clock;
179-
uint32_t remainder = (denominator * sample_rate) % system_clock;
180-
if (remainder > (system_clock / 2)) {
181-
numerator += 1;
182-
remainder = system_clock - remainder;
183-
}
184-
if (remainder < best_error) {
185-
best_denominator = denominator;
186-
best_numerator = numerator;
187-
best_error = remainder;
188-
// Stop early if we can't do better.
189-
if (remainder == 0) {
190-
break;
191-
}
192-
}
193-
}
226+
uint32_t best_denominator;
227+
uint32_t best_numerator = limit_denominator(0xffff, sample_rate, system_clock, &best_denominator);
194228

195229
dma_hw->timer[pacing_timer] = best_numerator << 16 | best_denominator;
196-
197230
audio_dma_result result = audio_dma_setup_playback(
198231
&self->dma,
199232
sample,

0 commit comments

Comments
 (0)