Close

Javascript Oscilloscope Wrencher

A project log for Microhacks

A collection of small ideas

ted-yapoTed Yapo 02/03/2018 at 14:189 Comments

There was a blog post about oscilloscope art from waveforms generated in Javascript recently.  I made a wrencher.

You can display it on your own scope by hooking the left and right audio outputs from your soundcard (or smartphone, which is how I generated the image above - and photographed it at the same time!) to two oscilloscope inputs in X-Y mode.  Here's a link to the jsfiddle which you can run directly.

The original idea and code to create audio and play it in JS come from Neil Fraser.  You can find it on his blog.

I grabbed a wrencher bitmap I had around, and pasted it into xfig, an old-school vector drawing program that I can't unlearn, no matter how hard I try.  I traced the outline of the wrencher parts with polylines in xfig, and saved the result.  The fig file format is ASCII, so I was able to just cut and paste the points directly into JS.  All the scaling and translation is done in the JS code so I could just use the points directly.  You can probably paste pretty much anything in there, and it will do something, maybe.

EDIT: @Thomas asked about a y-t view of the waveforms and the spectra.  Here they are (not very instructive, it turns out):

I don't know how long jsfiddles live, so here's the code, too.  Save it with an html extension, then open in your browser.

<html>
<body>
<input type="button" value="logo" onclick="had_logo()" />
</body>
<script>
function had_logo() {
  var points = [

    [4752, 5407, 4757, 5131, 4832, 4847, 4859, 4808, 4974, 4617, 5139, 4444,
      5214, 4390, 5409, 4266, 5684, 4200, 5884, 4222, 6141, 4297, 6292, 4386,
      6417, 4510, 6528, 4652, 6634, 4816, 6710, 5052, 6736, 5300, 6705, 5584,
      6656, 5735, 6550, 5948, 6479, 6050, 6369, 6151, 6301, 6210, 6341, 6285,
      6301, 6436, 6150, 6507, 6031, 6463, 5955, 6379, 5964, 6285, 5920, 6276,
      5920, 6374, 5866, 6445, 5769, 6503, 5627, 6467, 5556, 6387, 5547, 6281,
      5529, 6276, 5511, 6387, 5445, 6472, 5338, 6507, 5232, 6467, 5165, 6365,
      5161, 6237, 5181, 6203, 5171, 6195, 5118, 6139, 5121, 6143, 4961, 5979,
      4868, 5806, 4823, 5722
    ],
    
    [5070, 6205, 4721, 6525, 4717, 6738, 4633, 6924, 4455, 7080, 4269, 7133,
      4100, 7133, 4038, 7115, 4384, 6809, 4025, 6410, 3674, 6711, 3661, 6560,
      3723, 6352, 3834, 6201, 3985, 6121, 4193, 6077, 4357, 6094, 4766, 5762
    ],

     
         [3683, 3778, 3683, 3769, 3670, 3893, 3696, 4071, 3789, 4217, 3923, 4346,
     4136, 4408, 4357, 4395, 4791, 4761, 4797, 4754, 4859, 4648, 4926, 4532,
     5123, 4342, 5134, 4333, 5098, 4301, 4717, 3951, 4717, 3898, 4721, 3782,
     4677, 3658, 4593, 3525, 4477, 3423, 4357, 3370, 4167, 3356, 4056, 3374,
     4380, 3676, 4020, 4075, 3683, 3778],

    [6701, 4737, 6607, 4612, 6536, 4488, 6368, 4346, 6346, 4336, 6763, 3960,
      6776, 3756, 6838, 3600, 6954, 3463, 7140, 3374, 7286, 3356, 7428, 3370,
      7105, 3671, 7464, 4080, 7806, 3782, 7819, 3862, 7815, 4018, 7699, 4235,
      7539, 4359, 7375, 4417, 7247, 4421, 7118, 4404, 6713, 4750, 6701, 4737
    ],

    [6088, 5043, 6257, 5034, 6377, 5123, 6448, 5225, 6452, 5367, 6430, 5500,
      6355, 5566, 6284, 5606, 6257, 5509, 6159, 5438, 5999, 5358, 5928, 5273,
      5920, 5149, 6017, 5078, 6070, 5047, 6088, 5043
    ],

    [5737, 5591, 5720, 5611, 5671, 5731, 5658, 5850, 5662, 5926, 5711, 5850,
      5755, 5802, 5791, 5864, 5818, 5930, 5840, 5864, 5822, 5731, 5782, 5620,
      5756, 5589, 5737, 5591
    ],


         [6439, 6205, 6559, 6068, 6674, 5890, 6727, 5753, 6734, 5744, 7140, 6094,
      7286, 6072, 7460, 6090, 7615, 6188, 7739, 6316, 7819, 6503, 7815, 6698,
      7477, 6414, 7100, 6809, 7451, 7115, 7344, 7146, 7087, 7115, 6896, 6973,
      6763, 6738, 6767, 6521, 6430, 6218, 6439, 6205
    ],
 
    [5321, 5022, 5431, 5038, 5542, 5114, 5578, 5229, 5542, 5327, 5391, 5416,
      5276, 5455, 5232, 5504, 5227, 5602, 5152, 5602, 5041, 5469, 5019, 5309,
      5063, 5167, 5174, 5065, 5303, 5025, 5321, 5022
    ]

  ];
  min = 10000;
  max = 0;
  for (var i = 0; i < points.length; i++) {
    segment = points[i];
    for (var j = 0; j < segment.length; j++) {
      p = segment[j];
      min = Math.min(min, p);
      max = Math.max(max, p);
    }
  }

  left = [];
  right = [];
  left.push(128);
  right.push(128);
  for (var k = 0; k < points.length; k++) {
    xy = points[k];
    oldx = xy[0];
    oldy = xy[1];
    xy.push(oldx);
    xy.push(oldy);
    while (xy.length) {
      x = xy.shift();
      y = xy.shift();
      d = Math.sqrt((oldx - x) * (oldx - x) + (oldy - y) * (oldy - y));
      n_pts = Math.floor(d /50) + 1;
      console.log(n_pts);
      for (var i = 0; i < n_pts; i++) {
        xp = oldx + (x - oldx) * (1.0*i / n_pts);
        yp = oldy + (y - oldy) * (1.0*i / n_pts);
        left.push(Math.max(Math.min(255 * (xp - min) / (max - min), 255), 0));
        right.push(Math.max(Math.min(255 - 255 * (yp - min) / (max - min), 255), 0));
      }
      oldx = x;
      oldy = y;
    }
  }
  left.push(128);
  right.push(128);
  left = left.concat(left);
  right = right.concat(right);
 left = left.concat(left);
  right = right.concat(right);
  left = left.concat(left);
  right = right.concat(right);
    left = left.concat(left);
  right = right.concat(right);
    left = left.concat(left);
  right = right.concat(right);
    left = left.concat(left);
  right = right.concat(right);
  console.log(left.length);
  

  var wav = makeWav(left, right);
  var audio = new Audio();
  audio.src = 'data:audio/x-wav;base64,' + btoa(wav);
  audio.loop = true;
  audio.play();
}

// from https://neil.fraser.name/news/2018/01/25/
function makeWav(left, right) {
  // Return a stereo WAV file built from the provided data arrays.
  var min = Math.min(left.length, right.length);
  var SubChunk2Size = min * 2;
  // RIFF chunk descriptor.
  var file = 'RIFF';
  file += numToLong(36 + SubChunk2Size); // ChunkSize
  file += 'WAVE';
  // The 'fmt ' sub-chunk.
  file += 'fmt ';
  file += numToLong(16); // Subchunk1Size
  file += numToShort(1); // AudioFormat
  file += numToShort(2); // NumChannels
  file += numToLong(48000); // SampleRate
  file += numToLong(48000 * 2); // ByteRate
  file += numToShort(2); // BlockAlign
  file += numToShort(8); // BitsPerSample

  // The 'data' sub-chunk.
  file += 'data';
  file += numToLong(SubChunk2Size);
  for (var i = 0; i < min; i++) {
    file += numToChar(left[i]) + numToChar(right[i]);
  }
  return file;
}

function numToChar(num) {
  // num is 0 - 255
  return String.fromCharCode(num);
}

function numToShort(num) {
  // num is 0 - 65536
  var b0 = num % 256; // low
  var b1 = (num - b0) / 256; // high
  return String.fromCharCode(b0) + String.fromCharCode(b1);
}

function numToLong(num) {
  // num is 0 - 4.2billion
  var b0 = num % 256;
  num = (num - b0) / 256;
  var b1 = num % 256;
  num = (num - b1) / 256;
  var b2 = num % 256;
  num = (num - b2) / 256;
  var b3 = num;
  return String.fromCharCode(b0) + String.fromCharCode(b1) +
    String.fromCharCode(b2) + String.fromCharCode(b3);
}

</script>
</html>

Discussions

epignosis567 wrote 07/26/2019 at 23:57 point

Hi. Is it possible to do this without Xfig? I am on a Mac. I found an online converter, so I drew a shape in Adobe Illustrator, exported it as a .png, and converted it into .fig online, then opened the file in the Mac text edit app, but I don't think it works that way, right?

  Are you sure? yes | no

Thomas wrote 02/04/2018 at 14:43 point

Thanks for adding the scope traces. The slew rate of the DAC and the audio outputs is pretty impressive (and the spectrum is quite mean). It should be possible to use an audio output as a MF transmitter.

  Are you sure? yes | no

Ted Yapo wrote 02/04/2018 at 15:31 point

Well, you've got two channels, so producing I and Q is easy.  You just need to mix with a carrier to transmit whatever modulation on whatever band you like.  There are SDRs out there that do exactly that.

Some soundcards have input sampling frequencies up to 192kHz.  I'm not sure if their outputs work the same way.

  Are you sure? yes | no

Thomas wrote 02/04/2018 at 14:34 point

I checked whether a 5" b/w TV can be turned into a X/Y oscilloscope (think Asteroids) but I only found 16kHz fixed rate audio "scopes". It shouldn't be that difficult ...

  Are you sure? yes | no

Ted Yapo wrote 02/04/2018 at 15:27 point

In my college days (late 1980s), I converted a (13"?) b/w TV into a rough audio vectorscope - I just brought the deflection coil terminals back to a pair of RCA jacks mounted on the back panel.  I would hook them in parallel with my stereo speakers for parties.  It was a modest hit.

  Are you sure? yes | no

Thomas wrote 02/04/2018 at 17:20 point

"Modest hit" sounds familiar. The light show I made for the high school disco was nothing compared to the super-8 skin flicks someone projected on a bed linen from the back side.

  Are you sure? yes | no

Ted Yapo wrote 02/04/2018 at 17:27 point

@Thomas some things are difficult to compete with.

  Are you sure? yes | no

Thomas wrote 02/04/2018 at 08:24 point

I'm impressed how much can be done with the 3.5mm audio jack right to my wrist. Two channel traces (Y1, Y2, X=t), and a the spectrum of one channel would be interesting.

Edit:

Ah, now I can hear it :-)

I had a look at Neil Fraser's experiments. Nice! 

  Are you sure? yes | no

Ted Yapo wrote 02/04/2018 at 14:17 point

I think this could be a new art medium: youtube videos whose audio makes some cool animation on your oscilloscope.

  Are you sure? yes | no