|
26 | 26 |
|
27 | 27 | #include "common-hal/audiopwmio/PWMAudioOut.h"
|
28 | 28 |
|
| 29 | +#include <math.h> |
29 | 30 | #include <stdint.h>
|
30 | 31 | #include <string.h>
|
31 | 32 |
|
|
51 | 52 | #define SAMPLE_BITS_TO_DISCARD (16 - BITS_PER_SAMPLE)
|
52 | 53 | #define PWM_TOP ((1 << BITS_PER_SAMPLE) - 1)
|
53 | 54 |
|
| 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 | + |
54 | 107 | void audiopwmout_reset() {
|
55 | 108 | for (size_t i = 0; i < NUM_DMA_TIMERS; i++) {
|
56 | 109 | dma_hw->timer[i] = 0;
|
@@ -170,30 +223,10 @@ void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self,
|
170 | 223 | uint32_t sample_rate = audiosample_sample_rate(sample);
|
171 | 224 |
|
172 | 225 | 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); |
194 | 228 |
|
195 | 229 | dma_hw->timer[pacing_timer] = best_numerator << 16 | best_denominator;
|
196 |
| - |
197 | 230 | audio_dma_result result = audio_dma_setup_playback(
|
198 | 231 | &self->dma,
|
199 | 232 | sample,
|
|
0 commit comments