[BUG] audio playback difference in web browser vs simulator (and how to avoid)

For a while, myself and some users have been confused about why audio sometimes -- but not always -- sounds different comparing the web browser and playdate. I believe I have identified the issue.

First and foremost, the volume is considerably louder on the playdate than expected, leading often to oversaturation. This could be resolved with a master volume variable, or on the developer's end by manually editing the volume of all sounds and music. This is not the main problem.

The incongruity seems to come from a difference in how the two implementations (device and browser) deal with notes that end before they have finished their attack-decay. On the device, if a note is released before it has finished attack-decay, it will fade out over its release interval starting from the amplitude it reached during the attack-decay envelope at the time when it was interrupted. In contrast, in the web browser, if a note is to be released before its attack-decay envelope is completed, then the attack-decay envelope is accelerated by a factor k such that it ends when the note is released (AND, as an additional quirk, the sustain level is also decreased by a factor of k as well). Here is an image of audio capture that illustrates this:

Left: device. Right: browser. (Top/Bottom is just the audio channel.) Attack=0.005, Decay=0.1, BPM=400, sustain = 0.2, release = 0.1, note length = 1 tick.

With a BPM of 400, or TPS (ticks-per-second) of 26.6, the note length in seconds is 0.0375. In both cases, the note is (correctly) released after what looks to me like 0.0375 s. (Perhaps the device holds the note longer due to quantizing to the frame rate?). However, on the device (left), the release starts from an amplitude of around 50% (above the sustain level of 0.2, which would be the expected/desired amount), whereas on the browser, the amplitude immediately after release is about 5% -- i.e. lower than the sustain!

How to avoid (for pulp users)

Avoiding this behaviour is fairly straightforward. Simply ensure that in every channel, the sum of attack + decay is less than the length of the shortest note in that channel in seconds. In other words, reduce the attack and decay such that inequality holds for all notes:

(attack + decay) < 15 * (note length) / BPM

To ensure safety for any possible note, assume the note length is minimal, i.e. 1:

(attack + decay) < 15 / BPM

Thanks for taking the time to write this up and find a workaround. Coincidentally, we just tracked down the bug and there will be a fix going live tomorrow!

1 Like

whew! I regret not writing this up sooner. Maybe it would have helped!

1 Like