Skip to content

Commit bef0796

Browse files
committed
raspberrypi: audiopwmout: subtle for #5092
I noticed that the loop over 65535 possible denominators took a long time, causing up to 100ms wait for a sound sample to start playing! This algorithm, adapted from an algorithm shown in Python's fractions.py, is guaranteed to find the best denominator in a small number of steps (I think log2-many steps but I'm not sure). In practice, it means the time between samples playing is just 10ms, and some of that is recreating the sine wave sample in Python each time. It often finds the same solution as the old code, but sometimes it finds one a bit better since it compares the ratios using float point instead of integer arithmetic.
1 parent 77b0c76 commit bef0796

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 = ((uint64_t)denominator * sample_rate) / system_clock;
179-
uint32_t remainder = ((uint64_t)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)