By Ben Nitkin on
I've seen people attaching microcontrollers to 2-channel oscilloscopes to draw pretty patterns. It looked cool, so I decided to try it myself. One problem: I don't know heads from tails when it comes to microcontrollers. I can, however, program. And my computer has a sound card, capable of producing two channels of 8-bit wrath.
The process was actually beautifully sequential. I'll spare you the details of how I found a library - suffice it to say that PyAudio allows for injection of sound into a stream and reached maturity. I reverse-engineered the sound format using some of the sample recipes provided. PyAudio's streams store and read sound from an ASCII string. Initially, I assumed that the transform was based on chr(), where chr(0) corresponded to 0 volts, and chr(255) reflected maximum volume. I was wrong.
My first tests involved dumping a sine wave on one channel and a cosine on the other to make a circle. It didn't work (1st picture). After a little twiddling, I realized that there's a discontinuity in the transform from integers (0-256) to characters. (Every character has a number associated with it for the computers usage.) The range 0-127 spans the louder half of the speaker cards' ability. 128 jumps to the very bottom, and as the 8-bit number climbs towards 256, the card approaches half volume. In practice, the mapping looks like this:
0 > 50%
64 > 75%
127 > 100%
128 > 0%
192 > 25%
255 > 50%
That was weird. It took me some time and screwing around to realize what was going on. Easy to fix, though: adding 128 to the values and then taking them mod 256 gave me a linear map.
With a decent image, I started to make my code more versatile. I taught the computer how to create the coordiantes, write them to the sound card, draw a line, and draw a circle. Since I was working in Python and because sound is saved as text, I made some interesting transforms. For instance, different shapes can be added together to blit multiple things at once. I wrote a test program that drew some lines and a circle, then played with an animated circle wandering around.
My next step is parsing more interesting vectors, like SVG's. I'd like to play a vector movie, but need to find a file to read, first.
Some stumbling blocks I ran into along the way:
- Innumerable errors with code. I'd write things, realize they didn't work, then delete them again.
- If the soundcard is told to maintain the same pitch over time, it gradually reduces its output voltage to zero, ruining my image. (This is healthy for speakers - running full current through the coils for hours could overhead the coils and damage the speaker. Powering down saves power, too.)
- If the sample rate is too high, the soundcard empties the stream's buffer faster than I can fill it. When the buffer is completely empty, the card turns off, and the scope draws a dot at the center of the screen. (Not my goal.)