block by alexmacy 29966cadaab0053d3ed5c8c85a5e4582

Compound Radial Waves v2

Full Screen

This is another in a series exploring how to calculate and display the combinations of multiple waves. See the previous blocks:

This makes a radial rendering of the compound wave made from three different frequencies. The resulting wave can be very long - easily getting above 100,000 or even above 1,000,000 points! That’s why the sliders are limited to a maximum of 100 each. This is way beyond what can cleanly be done with SVG, so I used canvas for the rendering. But that was still running slow, so I removed d3 altogether and rebiult it in VanillaJS.

The base frequencies are randomly selected when the page is loaded, so refreshing will produce a new compound wave, and there’s also capability to save a combination of frequencies to be accessed later. The button at the bottom will bring up a prompt with a URL for current waveform - note that this will only work when loaded ‘raw’ here.

For example: 78,30,49

index.html

<!DOCTYPE html>
<html>
<head>
<style>
  body {
    background-color: black;
    color: rgb(200, 200, 200);
    margin: 0px;
  }
  table {
    margin-left: 10px;
  }
</style>
</head>
<body>
  <canvas></canvas>
  <table>
    <tr id="feedback"></tr>
    <tr>
      <td><input type="range" min="10" max="100" oninput="draw()"></td>
      <td><input type="range" min="10" max="100" oninput="draw()"></td>
      <td><input type="range" min="10" max="100" oninput="draw()"></td>
      <td><button onclick="newURL()">Save This Wave</button></td>
    </tr>
  </table>
</body>
<script>
const width = innerWidth, height = 600;

const canvas = document.getElementsByTagName('canvas')[0];
canvas.width = width;
canvas.height = height;

const canvasCtx = canvas.getContext('2d')
const inputs = document.getElementsByTagName('input');

const search = window.location.search.split('?')[1];
let fVals = search ? search.split(',') : [];

for (let i in inputs) {
  inputs[i].value = fVals[i] ? +fVals[i] : 10 + Math.random() * 50
}

draw();

function draw() {
  canvasCtx.fillStyle = 'rgb(0, 0, 0)';
  canvasCtx.fillRect(0, 0, width, height);

  fVals = [];
  for (let d of document.getElementsByTagName('input')) fVals.push(+d.value);
  
  const LCM = getLCM(getLCM(fVals[0], fVals[1]), fVals[2]) * 2 + 1;


  document.getElementById('feedback').innerHTML = '' + 
    '<td>freq. #1: ' + fVals[0] + '</td>' + 
    '<td>freq. #2: ' + fVals[1] + '</td>' + 
    '<td>freq. #3: ' + fVals[2] + '</td>' + 
    '<td>Compound wavelength: ' + LCM.toLocaleString() + '</td>';  
  
  if (LCM > 500000) canvasCtx.lineWidth = .1
  else if (LCM > 100000) canvasCtx.lineWidth = .2
  else if (LCM > 10000) canvasCtx.lineWidth = .35
  else if (LCM > 1000) canvasCtx.lineWidth = .75
  else canvasCtx.lineWidth = 1

  canvasCtx.strokeStyle = 'rgb(255, 50, 50)';
  canvasCtx.beginPath();

  for (let i=0; i<LCM; i++) {
    const thisAngle = i / (LCM-1) * Math.PI * 2
    let combinedVal = 0;

    for (d of fVals) {
      combinedVal += (1 - Math.cos(Math.PI * (i % (d * 2) - d) / d)) / 2;
    }

    const x = width/2 + combinedVal * 100 * Math.cos(thisAngle);
    const y = height/2 + combinedVal * 100 * Math.sin(thisAngle);
    i === 0 ?  canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y);
  }

  canvasCtx.stroke();
}

function newURL() {
  prompt('URL for these frequencies:', window.location.href.split('?')[0] + '?' + fVals.join(','))
}

function getLCM(a, b) {
  return (a / getGCD(a, b) * b)
}

function getGCD(a,b) {
  while (true) {
    if (b == 0) return a;
    a %= b;
    if (a == 0) return b;
    b %= a;
  }
}
</script>
</html>