<digital-art aria-hidden="true">
  <script type="frag">
    const int ITERS = 100;
    const float PI = 3.141592654;
    uniform float time;
    uniform float meter;
    uniform float mouseX;
    uniform float mouseY;
    uniform vec2 resolution;
    varying vec2 vUv;

    // Calculate cameras "orthonormal basis", i.e. its transform matrix components
    vec3 getCameraRayDir(vec2 uv, vec3 camPos, vec3 camTarget) {
      vec3 camForward = normalize(camTarget - camPos);
      vec3 camRight = normalize(cross(vec3(0.0, 1.0, 0.0), camForward));
      vec3 camUp = normalize(cross(camForward, camRight));

      float fPersp = 2.0;
      vec3 vDir = normalize(uv.x * camRight + uv.y * camUp + camForward * fPersp);
      return vDir;
    }

    // distance function for a sphere
    float sphere(vec3 p, float r)
    {
        return length(p) - r;
    }

    float deformation(vec3 pos) {
      return -.07 * abs(sin(atan(pos.z, pos.x) * 8.));
    }

    float subtract(float a, float b)
    {
      return max(-a, b);
    }
    
    float pumpkin(vec3 pos, vec3 center) {
      float eyeR = sin(time * 1.7) * .2;
      float eyeL = cos(time * 1.7) * .2;
      float d = deformation(pos - center);
      vec3 spherePos = vec3(0) + center;
      vec3 eyeLeftPos = vec3(-1., 1., -3. + eyeL) + center;
      vec3 eyeRightPos = vec3(1., 1., -3. + eyeR) + center;
      vec3 mouthPos = vec3(0.0, -1., -3.) + center;
      vec3 mouth2Pos = vec3(0.0, -.6 - meter * .06 + sin(time * 2.) * .3, -3.) + center;
      vec3 mouthScale = vec3(0.6, 1., 1.);
      float t = sphere(pos - spherePos, 3.0) + d;
      t = subtract(sphere(pos - spherePos, 2.9), t);
      t = subtract(sphere(pos - eyeLeftPos, .7), t);
      t = subtract(sphere(pos - eyeRightPos, .7) , t);
      float mouth = sphere((pos - mouthPos) * mouthScale, .9) + d;
      mouth = subtract(sphere((pos - mouth2Pos) * mouthScale, .89) + d, mouth);
      
      t = subtract(mouth, t);
      return t;
    }
    
    float scene(vec3 pos) {

      float t = pumpkin(mod(pos, 10.), vec3(5.));
      //float t = pumpkin(pos, vec3(0.));
      t = min(t, pumpkin(pos, vec3(0.)));
      return t;
    }

    // cast a ray along a direction and return 
    // the distance to the first thing it hits
    // if nothing was hit, return -1
    float castRay(in vec3 rayOrigin, in vec3 rayDir)
    {
      float maxd = 80.0;
      float t = 0.1;
      for (int i=0; i< ITERS; i++) {
        float h = scene(rayOrigin + rayDir * t );
        if (h<(0.001 * t) || t>maxd) break;
        t += h;
      }
      if( t>maxd ) t=-1.0;
      return t;
    }
    

    // calculate normal:
    vec3 calcNormal(vec3 pos)
    {
      // Center sample
      float c = scene(pos);
      // Use offset samples to compute gradient / normal
      vec2 eps_zero = vec2(0.001, 0.0);
      return normalize(vec3( scene(pos + eps_zero.xyy), scene(pos + eps_zero.yxy), scene(pos + eps_zero.yyx) ) - c);
    }

    float S(float a, float b, float c) {
      return sin(a * b + c);
    }

    vec3 background(vec2 p, float time) {
      float s = S(.4, time, time);
      float u = S(2.5 + 1. * s * 2., .5 * p.x, 1. + time);
      float v = S(6.4 + 3. * s * s, .5 * p.y, 2. + time);
      float w = S(2.3 + 2. * s, p.y + p.x, 3. + time );
      float r = .3 + .05 * u + .1 * v + .05 * w;
      float g = .1 + .05 * u + .025 * v + .05 * w;
      float b = .0 + .01 * u + .01 * v + .005 * w;
      return vec3(r, g, b);
    }  

    // Visualize depth based on the distance
    vec3 render(vec2 p, vec3 rayOrigin, vec3 rayDir)
    {
      float t = castRay(rayOrigin, rayDir);
      if (t == -1.0) {
        return background(p * (1.2), time * .3);
      }
      // shading based on the distance
      // vec3 col = vec3(4.0 - t * 0.35) * vec3(.7, 0, 1.0);

      // shading based on the normals
      vec3 pos = rayOrigin + rayDir * t;
      vec3 N = calcNormal(pos);
      vec3 L = normalize(vec3(sin(time *.1), 2.0, -0.5));
      // L is vector from surface point to light
      // N is surface normal. N and L must be normalized!
      float NoL = max(dot(N, L), 0.0);
      vec3 LDirectional = vec3(1.0, 0.9, 0.8) * NoL;
      vec3 LAmbient = vec3(0.2);
      vec3 col = vec3(1., .3, .0); 
      vec3 diffuse = col * (LDirectional + LAmbient);  
      return diffuse;
    }
    
    // normalize coords and correct for aspect ratio
    vec2 normalizeScreenCoords()
    {
      float aspectRatio = resolution.x / resolution.y;
      vec2 result = 2.0 * (gl_FragCoord.xy / resolution - 0.5);
      result.x *= aspectRatio; 
      return result;
    }

    void main() {
      float rotation = sin(time * .3) * PI / 4.;
      vec3 camPos = vec3(12. * sin(rotation), -mouseY * 4., -12.0 * cos(rotation));
      vec3 camTarget = vec3(0);
      float aspectRatio = resolution.x / resolution.y;
      vec2 p = (vUv - .5) * vec2(aspectRatio, 1.) * 2.;
      
      vec3 rayDir = getCameraRayDir(p, camPos, camTarget);
  vec3 col = render(p, camPos, rayDir);
      gl_FragColor = vec4(col, 1.);
    }
  </script>
  <script type="vert">
    varying vec2 vUv;
    void main()	{
      vUv = uv;
      gl_Position = vec4( position, 1.0 );
    }
  </script>
</digital-art>
<button>Play Music</button>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

body {
  background: #222;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0;
  height: 100vh;
}

button {
  position: absolute;
  background: orange;
  right: 16px;
  top: 16px;
  font-family: 'Press Start 2P', monospace;
  font-size: 1em;
  padding: 16px;
  color: black;
  border: none;
  cursor: pointer;
}

button:focus {
  outline: 2px solid black;
}

button:active {
  outline: 2px solid white;
}


digital-art {
  display: block;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

digital-art * {
  display: none;
}

digital-art canvas {
  display: block;
}
class Music {
  
  constructor() {}
  
  async init() {
    await Tone.start();
    const meter = new Tone.Meter();
    const gain = new Tone.Gain(.2);
    gain.connect(Tone.Destination);
    const noiseGain = new Tone.Gain(.1);
    noiseGain.connect(meter);
    meter.connect(gain);
    this.digitalArt = document.querySelector('digital-art');
    const noiseSynth = new Tone.NoiseSynth().connect(noiseGain);
    const synth = new Tone.FMSynth().connect(meter);
    const synth2 = new Tone.AMSynth().connect(meter);
    const seq = new Tone.Sequence((time, note) => {
      synth.triggerAttackRelease(note, "1n", time);
    }, ["C2", ["D#2", "D2"], "G#2", "G2"], 2);
    const rythm = new Tone.Sequence((time, note) => {
      noiseSynth.triggerAttackRelease("16n", time);
    }, ['x', 'x', 'x', 'x'], .25);
    const seq2 = new Tone.Sequence((time, note) => {
      synth2.triggerAttackRelease(note, "8n", time);
    }, ['C3', 'D#3', 'G3', 'C4', 'G4', 'C4', 'G3', 'D#3'], .25)

    rythm.start(0);
    seq.start(0); // 4
    seq2.start(0); // 20
    
    this.noiseSynth = noiseSynth;
    this.synth = synth;
    this.synth2 = synth2;
    this.seq = seq;
    this.seq2 = seq2;
    this.rythm = rythm;
    this.meter = meter;
    this.meterTimer = -1;
  }
  
  get state() {
    return Tone.Transport.state;
  }
  
  async start() {
    if (!this.synth) {
      await this.init();
    }
    Tone.Transport.start('+0', 0);
    this.meterTimer = setInterval(() => {
      if (this.digitalArt) {
        this.digitalArt.setAttribute('meter', this.meter.getValue());
      }
      
    }, 10)
  }
  
  stop() {
    Tone.Transport.stop();
    clearInterval(this.meterTimer);
    if (this.digitalArt) {
      this.digitalArt.setAttribute('meter', '0');
    }
    this.meterTimer = -1;
  }
}

class DigitalArt extends HTMLElement {
  
  constructor() {
    super();
    this.renderer = null;
    this.scene = null;
    this.camera = null;
    this.uniforms = {};
    this.clock = null;
    this.onResize = this.onResize.bind(this);
    this.onMouseMove = this.onMouseMove.bind(this);
    this.animate = this.animate.bind(this);
    this.resources = [];
    this.frag = this.querySelector('script[type=frag]').textContent.trim();
    this.vert = this.querySelector('script[type=vert]').textContent.trim();
  }
  
  static register() {
    customElements.define("digital-art", DigitalArt);
  }
  
  static get observedAttributes() {
    return ['meter'];
  }
  
  get meter() {
    return parseInt(this.getAttribute('meter'), 10);
  }
  
  attributeChangedCallback(name, oldValue, newValue) { 
    if (name === 'meter') {
      this.uniforms.meter.value = parseInt(newValue, 10)
    }
  }
  
  
  connectedCallback() {
    if (! this.renderer) {
      this.setup();      
    }
  }
  
  disconnectedCallback() {
    this.dispose();
  }
  
  setup() {
    this.renderer = new THREE.WebGLRenderer();
    this.renderer.setPixelRatio(1);
    this.appendChild(this.renderer.domElement);
    this.clock = new THREE.Clock();
    this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    this.scene = new THREE.Scene();
    this.uniforms = {
      time: { value: 1.0 },
      resolution: { value: new THREE.Vector2(427,842) },
      meter: { value: 0. },
      mouseX: { value: 0. },
      mouseY: { value: 0. }
    }
    
    const geometry = new THREE.PlaneBufferGeometry(2, 2);
    
    const material = new THREE.ShaderMaterial({
      uniforms: this.uniforms,
      vertexShader: this.vert,
      fragmentShader: this.frag,
    });
    this.resources.push(geometry, material);

    const mesh = new THREE.Mesh(geometry, material);
    this.scene.add(mesh);
    
    this.onResize();
    window.addEventListener('resize', this.onResize, false); 
    this.frame = requestAnimationFrame(this.animate);
    
    window.addEventListener('mousemove', this.onMouseMove, false);
    
    
  }
  
  onMouseMove(e) {
    this.uniforms.mouseY.value = e.clientY / innerHeight - .5;
    this.uniforms.mouseX.value = e.clientX / innerWidth - .5;
  }
  
  onResize() {
    const { renderer, uniforms } = this;
    const width = this.clientWidth;
    const height = this.clientHeight;
    renderer.setSize(width, height);
    uniforms.resolution.value.x = width;
    uniforms.resolution.value.y = height;
  }
  
  animate(timestamp) {
    const { animate, uniforms, renderer, clock, scene, camera } = this;
    this.frame = requestAnimationFrame(animate);
    uniforms.time.value = clock.getElapsedTime();
    renderer.render(scene, camera);
  }
  
  cleanupScene(sceneOrGroup) {
    if (!sceneOrGroup) {
      sceneOrGroup = this.scene;
    }
    for (let item of [...sceneOrGroup.children]) {
      if (item.type === 'group') {
        this.cleanupScene(item);
      }
      sceneOrGroup.remove(item);
    }
  }
  
  dispose() {
    cancelAnimationFrame(this.frame);
    window.removeEventListener('resize', this.onResize, false);
    window.removeEventListener('mousemove', this.onMouseMove, false);
    this.cleanupScene();
    const removedResources = this.resources.splice(0, this.resources.length);
    for (let item of removedResources) {
      if (typeof item.dispose === 'function') {
        item.dispose();
      }
    }
    this.removeChild(this.renderer.domElement);
    this.renderer.dispose();
    this.renderer = null;
    this.scene = null;
    this.camera = null;
    this.uniforms = {};
    this.clock = null;
  }
}

DigitalArt.register();
const music = new Music();
const button = document.querySelector('button');


button.addEventListener('click', async () => {
  if (music.state == "started") {
		music.stop();
		button.textContent = "Restart Music";
	} else {
    music.start();
    button.textContent = "Stop Music"
  }
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.61/Tone.js
  2. https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js