adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
MIT License
3.98k stars 1.17k forks source link

audioio does not maintain synchonisation of A0/A1 looping some audioio.RawSample data #1992

Closed kevinjwalters closed 4 years ago

kevinjwalters commented 5 years ago

I appear to have made some data which despite being even in length (4930 samples = 2465*2) when passed to audio.RawSample with channel_count=2 and then sent to output with play() with loop=True it will play but the A0/A1 will not stay synchronised and drift around. At 100kHz playback it takes 60 seconds to return to being very briefly in sync. I might expect this if I passed an odd number of samples, e.g. 9 could end up as 5 looping on DAC0 and 4 looping on DAC1.

Code is rather large because it contains the data as a list, see https://github.com/kevinjwalters/circuitpython-examples/blob/master/pygamer/audioio-phasebug.py. In that example there's a boolean which controls whether to replace the data values with an alternate set of rising values without changing the total number of values (samples). Doing that makes the bug go away, the bug appears to be very dependent on the data values with no obvious explanation.

This does seem rather unlikely so there's a minor chance I have made an error here...

BTW, what's the intended behaviour for audioio.RawSample if it's given a number of samples which are not exactly divisble by the channel_count?

tannewt commented 5 years ago

I'd expect it to throw an exception when the sample count isn't divisible by channel_count. I'm not positive it does though.

I'd encourage you to dig into the C side of the audio APIs. I bet you could fix a number of these issues.

kevinjwalters commented 5 years ago

I'm in the middle of some application programming at the moment but will have a look when I get to end of that.

I've also discovered another problem with dacs.play(something, loop=True) ceasing output and possibly leaving output on the last sample in something. I thought it was time based but it's not, sometimes it's 20 seconds, sometimes over a 100, sometimes seems to run ok "forever". It's very unclear what the trigger is for cessation of looping! The code in question was just doing a while True: pass after the play().

kevinjwalters commented 5 years ago

I'm running with a smaller amount of data now (882 * 2 = 1764) and if I transform those values keeping length identical sometimes A0/A1 stay in sync and sometimes they don't.

"When they are in sync there might be a few samples missing too". I was wrong here, this was due to a bit of visual confusion from an in-place update on an existing "H" array for audioio.RawSample() causing the update to go out gradually as I slowly updated the array. I've intentionally started using "h" now to take advantage of its creation of a second buffer for DAC output.

kevinjwalters commented 5 years ago

Another observation, I was looking for a workaround and I've noted for similar code/data I've found the bug appears if I use the range 0-32768 on DAC but with same data rescaled to 0-25000 the sync bug goes away.

kevinjwalters commented 5 years ago

It's possible that a bizarre workaround for this is to do a single

rawdata.append(0)

on the end of the data. This doesn't make sense as for 2 channels one would expect the array to always need to be even in length!

CORRECTION, it takes three magic 0s! This makes everything ok.

bizarre_workaround = True
if bizarre_workaround:
    rawdata.append(0)
    rawdata.append(0)
    rawdata.append(0)
kevinjwalters commented 5 years ago

Reproducible on a Feather M4 too. The bizarre_workaround also fixes it on that Feather.

kevinjwalters commented 4 years ago

Also on a Metro M4.

kevinjwalters commented 4 years ago

Worth monitoring #1908 as this also relates to (more fatal) problems that are being attributed to DMA woes.

jepler commented 4 years ago

Reproduced on Metro M4 express. On my scope, the one trace slowly moves with respect to the other one at a constant rate. Just to add to the findings, if I decrease the sample rate enough (24 * 1000) the problem goes away, at least for this sample data. As the original reporter states, somehow this is dependent on specific sample values, too, which really is a problem for any explanations I might have put forward. Debugging hasn't yielded any insights yet.

kevinjwalters commented 4 years ago

Nice to know it's not just me!

jepler commented 4 years ago

Not only is it data-dependent, but for some data combinations the order of relative movement is reversed!

kevinjwalters commented 4 years ago

I'm not saying this is related but there's more unexplained, weird stuff going on with these DACs: Adafruit Forums: SAMD51 M4 DAC has problems getting it up

jepler commented 4 years ago

CircuitPython does not intentionally enable the oversampling or dithering modes.

jepler commented 4 years ago

fwiw your stair step on 0-to-65535 I can also reproduce, and the "data dependent" nature of the problem seems to be that when the "stair step" is going on at the moment the buffer flips over, the problem doesn't manifest. weirder and weirder.

jepler commented 4 years ago

MSO1104Z_2019-08-31_12 03 07

jepler commented 4 years ago

Smaller reproducer:

import board 
import audioio
import array
import time

X = 8191
Y = 65535
data = array.array('H', [0, 0, X, X, X, Y])
sample = audioio.RawSample(data, sample_rate=200 * 1000, channel_count=2)
a = audioio.AudioOut(board.A0, right_channel=board.A1)

a.play(sample, loop=True)

while 1:
    time.sleep(1)

Change the single Y back to X and the waveforms stay in sync. Change a left-channel X to Y and the waveforms stay in sync.

jepler commented 4 years ago

With 3 samples per channel and a sample rate of 200kHz, the observed frequency should be (200/3) = 66.7kHz. Instead, I measure ~55.5kHz with [0,0,X,X,X,X], and 12.8kHz with [0,0,Y,Y,Y,Y].

jepler commented 4 years ago

Since #2552 we have just one DMA channel, so becoming out of sync like this should be impossible. Please feel free to reopen this if you still reproduce the problem on 5.3.0.