From one-bit music - meowbit

The previous article devoted to the Meowbit training board and Python implementations for it ended with a mention of CircuitPython's inability to play music simultaneously with the game: CircuitPython does not allow interrupt handlers to be written in Python, and without this, a delay for screen redrawing (about 0.15 s) "hangs" the sound ... Nevertheless, the background sound is often needed, and for most of the supported boards (100 out of 189) CircuitPython includes a module audioio



or audiopwmio



that implements the background sound in ways native to the board. Unfortunately, for Meowbit (and in general for STM32 based boards ) neither one nor the other module is implemented; but in an open source project, this is fixable. Find the easter egg in the photo First of all: why are there two different modules for playing sound with completely identical APIs, and either one or the other is supported on different boards?













This is what ⅒ seconds of a regular (16-bit)

WAV file looks like in an audio editor (like Audacity) :





The value changes smoothly within the range from about -0.2 to +0.2 "conventional units". If the voltage supplied to the electrodynamic loudspeaker is changed in the same way , then the membrane will oscillate just as smoothly - from about 0.2 of its maximum possible deviation in one direction, to 0.2 deviation in the other direction. The module audioio



implements just such sound playback - through the DAC it smoothly changes the voltage at the output connected to the speaker.



But in Meowbit, instead of a speaker, there is a cheap piezo tweeter, incapable of deflecting the membrane to intermediate positions: it very quickly moves from one extreme position to another extreme, and remains there until the next transition. Think of it as sound with a resolution of one bit per sample:





In this way, it is impossible to transmit the change in sound volume, but it is theoretically possible to transmit all the harmonics present in it - if, in parallel with the 32768-fold reduction in resolution, the sampling frequency is increased by the same amount (i.e. up to hundreds of megahertz). It is unlikely that a piezo tweeter membrane can vibrate at that frequency; but this can be used to your advantage - if you learn how to switch the voltage on the buzzer, when the membrane is halfway, you can make sounds of intermediate volume! A patent search confirms that people are indeed exploring the possibilities of using the piezo tweeter in this way. We will not go deep into this jungle, and will leave the usual WAV sampling rate of tens of kilohertz. For music where the fundamental harmonics are in the kilohertz region, this is sufficient; speech, however, turns into a barely intelligible noise.You can compare how the eight-second sound sample I used, played on a one-bit piezo squeaker, is perceived: first the original, then the one-bit version, then the Meowbit recording with a microphone.





The module audiopwmio



implements the playback of sound through a digital output using PWM : a one-bit audio recording turns into a sequence of delays between switching the

output to the opposite value.



So, the general implementation plan audiopwmio



for Meowbit is as follows:



  1. We translate the audio recording transmitted by the user into PWM format (a list of delays between switches);
  2. . pulseio



    , , – – Python .


It wasn't immediately obvious that there was another aspect of the implementation to take care of - audio buffering. My 8-second test sample is 8 * 22050 * 2 ≈ 340 KB - three times the size of the entire Meowbit RAM; therefore, it will have to be loaded into memory piece by piece. The standard implementation audiocore.WaveFile



loads the WAV file in 256 byte chunks, which corresponds to 128 samples or 5.8ms of play time. This means that on average, every 5.8 ms audiopwmio



will have to request re-filling of the buffer; there is no way out, except to place this call in the same timer interrupt handler - otherwise, redrawing the screen may delay the filling of the buffer by a good hundred milliseconds. This, however, does not completely solve the problem: what happens if a timer interrupt occurs while the screen is redrawing? The Meowbit screen is connected via the SPI bus , the flash drive is connected via it, which means that accessing the flash while redrawing the screen is still impossible!



The result is an implementation audiopwmio



capable of playing audio recordings from memory (or procedurally generated) in the highest quality possible on Meowbit; but audio recordings from files are played only in the absence of simultaneous calls to the screen and to the flash. This is quite enough for the soundtrack of simple games. PR with my implementation has been waiting for a review for more than a week, and it audiopwmio



is not known when Meowbit will appear in the official version of CircuitPython; but that doesn't stop anyone wishing to compile CircuitPython for themselves with my add-on.










All Articles