#!/usr/bin/perl ## ## Squarepusher v0.1 written by Bowie J. Poag, 1/29/13 ## ## Squarepusher converts a 1-bit 256x256 PBM image to a WAV audio bitstream ## suitable for playing on an XY-mode oscilloscope display. ## ## Usage: ./squarepusher.pl <#frames> ## Example: ./squarepusher.pl turbo 50 20 3 foo.pbm ## English: Using turbo mode, spit out 50 frames with a lossyness value of 20, ## rendering every 3rd line of the image foo.pbm. ## ## Valid mode names are standard, turbo, and plow. ## ## Standard: Straightforward left-to-right traversal of the image, This will ## render the image on the screen in a sawtooth sort of fashion, like one might ## read words a sentence in a paragraph. ## ## Turbo: This method attempts to optimize beam time by only rendering the outline ## of a filled area, rather than the area itself. Useful if you have a high-detail ## image that flickers a lot, and doesn't need precise detail. ## ## Plow: Like "Standard", but boustrophedonic traversal. The picture is drawn ## on the screen in a zig-zag fashion, alternating left-to-right and right-to- ## left. This saves time in that the beam does not need to return to a zero position ## to render the next scanline. ## ## Image conversion is beyond the scope of this script. If you want to use some ## other image format with this script, you'll need to first convert it using ## netpbm tools, GIMP, Photoshop, or some other image processing tool capable ## of exporting 1-bit images to ASCII PBM $mode=$ARGV[0]; $frames=$ARGV[1]; $lossy=$ARGV[2]; $skip=$ARGV[3]; @bitmap=`cat $ARGV[4]`; ## This is a 48 KHz 2-channel (stereo) 8-bit WAV file header. ## Normally, we would want to have correct subchunk values, but, ## i'm a lazy bastard, so we're just going to tell whatever's ## going to play this audio sample that it's 0xFFFFFFFF bytes ## in size, and have them deal with the underrun. ## ## I do this because I'd rather be waterboarded than have to ## deal with a file format that uses mixed big- and little-endian ## data. ## $header="\x52\x49\x46\x46\xFF\xFF\xFF\xFF\x57\x41\x56\x45\x66\x6D\x74\x20\x10\x00\x00\x00\x01\x00\x02\x00\x80\xBB\x00\x00\x00\x77\x01\x00\x02\x00\x08\x00\x64\x61\x74\x61\xFF\xFF\xFF\xFF"; if ($lossy==1) { $frames=1; } if ($lossy<=0 || $lossy >=255) { $lossy=1; } # Strip the header information off.. shift(@bitmap); shift(@bitmap); shift(@bitmap); # A little cleanup.. foreach $item (@bitmap) { chomp($item); $total.=$item; } @image=split(//,"$total"); # The weird conditional block below is a hack. # It's basically a quick convolve filter, that will # produce a vector outline of any solid white area. # this saves beam traversal time, and significantly # reduces image flicker on images with a large ratio # of white to black pixels. # # In English, it will take any solid white shape, # and draw it as simply the outline of that shape. # # If you don't want to use this optimization, just # comment out the whole if statement. for ($q=0;$q<$frames;$q++) { if ($mode=~/turbo/) { for($x=0;$x<(256*256);$x+=rand($lossy)) { if(($image[$x]=="0" && $image[$x+1]!="0") || ($image[$x]=="1" && $image[$x+1]!="1") || ($image[$x]=="0" && $image[$x+257]!="0") || ($image[$x]=="1" && $image[$x+257]!="1")) { $col=$x%256; $row=int($x/256); if ($x<(256*256)-256) # Don't try to optimize the last line. { if ($row<256 && $col<256 && $row%((rand($skip))+1)==0) { $sample.=chr($row); $sample.=chr($col); } } } } } if ($mode=~/standard/) { for($x=0;$x<(256*256);$x+=rand($lossy)) { if ($image[$x]=="0") { $col=$x%256; $row=int($x/256); if ($row<256 && $col<256 && $row%((rand($skip))+1)==0) { $sample.=chr($row); $sample.=chr($col); } } } } if ($mode=~/plow/) #Boustrophedontastic! :) { for ($x=(256*256);$x>0;$x-=256) { $thisRow=int($x/256); if ($thisRow%2==0) { for ($c=0;$c<256;$c+=rand($lossy)) { if ($image[$x+$c]=="0") { $col=$c; $row=$thisRow; if ($row<256 && $col<256 && $row%((rand($skip))+1)==0) { $sample.=chr($row); $sample.=chr($col); } } } } if ($thisRow%2==1) { for ($c=256;$c>0;$c-=rand($lossy)) { if ($image[$x+$c]=="0") { $col=$c; $row=$thisRow; if ($row<256 && $col<256 && $row%((rand($skip))+1)==0) { $sample.=chr($row); $sample.=chr($col); } } } } } } } # We now have a rather large scalar who's contents are identical to an 8-bit two-channel WAV audio stream, suitable for framing.. # $dump=$header.$sample; print $dump;