← Back to context

Comment by Joeboy

1 day ago

It's not kid friendly, but in case anybody's interested I just wrote up how I made a simple "hardware" synth by bodging together a Raspberry Pi Pico 2 and I2S audio module, total cost around £10 on Amazon UK.

The hardware's very cheap and easy. The "default" synthesis is pretty simple but also pretty hackable (in Rust) if you want to customize it.

https://github.com/Joeboy/oxynth/

Thanks for sharing!

I did some similar playing around with an ESP32 and I2S a few years ago (lockdowns were an odd time). Where I seem to remember getting stuck was how to get the phase to line up, so that each sample looped at a zero-crossing point (which is different for each frequency).

For the lazy, what did you do?

https://gitlab.com/afandian/melodicornamuse/-/blob/main/melo...

  • I don't really understand your issue sorry, but my thing basically just writes the audio output to a buffer, and the DMA and PIO "automatically" send that to the I2S output. There's also some messing about with ping ponging between two DMA buffers to avoid gaps between each buffer write audio. I guess things might be quite different on the ESP32.

    Edit: Ok I glanced at your code, if I read it right it seems like you're writing sin waves into buffers at "init" time then copying the appropriate buffer at "run" time. Which is not what I'd do, but then I'm used to more luxurious devices. Maybe try using a fast sin approximation rather than the precomputed buffer table?

    https://bmtechjournal.wordpress.com/2020/05/27/super-fast-qu... might be helpful there.

    • Thanks! This was just some playing round years ago, so I don't remember much about it. I don't remember if calculating sin() at runtime was too slow, or I wanted to avoid polyphonic O(n) inside the buffer loop, or what. I bet it uses a LUT anyway.

      I much prefer your method of just rotating the phase!

  • were you using single cycle waveforms or longer samples? in the former case, I guess there's not much to it, you just cycle through the waveform (in which case the waveform you choose would usually start and end at a zero crossing by construction, like sines, triangles, etc - or, if it does not, well, that will just create extra harmonics that might or not be desirable). For the second case, it's more subtle. Most samplers that implement this feature will assume correct loop points (usually, but not necessarily at a zero crossing) are chosen manually by the user. Some of them implement cross-fading at the looping point to make that more forgiving, but that may be CPU/RAM intensive for some devices. If you're referring to small clicks you may get at the start and stop of sample playback, it's fairly common to use very short (ms or less) fade-in/fade-out to avoid that. There's a lot of books out there, but the main one I've read and enjoyed is this one, that happens to be free: https://cs.gmu.edu/~sean/book/synthesis/. it's more of a textbook than a cookbook.

    • Thanks! Reading the code again, looks like I was filling up buffer of 256 x 16 bit samples.

      I think the issue with looping at arbitrary points was word alignment. You need to give it a whole buffer. So you'd have to do some nasty bit-shifting.

      Per other reply, I think doing it live is probably easisest!

      Thanks for the recommendation. If I ever get back into this I'll take a look.