Skip to content

Instantly share code, notes, and snippets.

@tomhicks
Last active November 12, 2024 19:08
Show Gist options
  • Save tomhicks/6cb5e827723c4eaef638bf9f7686d2d8 to your computer and use it in GitHub Desktop.
Save tomhicks/6cb5e827723c4eaef638bf9f7686d2d8 to your computer and use it in GitHub Desktop.
Listen to your web pages
@jcraigk
Copy link

jcraigk commented Feb 15, 2020

This is absolutely brilliant. And useful.

@sbrichardson
Copy link

sbrichardson commented Feb 15, 2020

Here's a funny variation that speaks out the counts in different pitches/rates

const speechSynthesis = window.speechSynthesis
const min = 0
const max = 100
const bad = 15 // just a random max to enable 'bad developer' mode

const observer = new MutationObserver(function(mutationsList) {
  const msg = new SpeechSynthesisUtterance()
  const len = mutationsList.length
  
  if (len > bad) {
    msg.text = 'Bad Developer'
  } else {
    const n = len > max ? max : len
    const pitch = ((n - min) / (100 - min)) * 2
    msg.text = len
    msg.rate = 4 - pitch * 4
    msg.pitch = 2 - (pitch * 1.6 * 1.2)
  }

  speechSynthesis.speak(msg)
})

observer.observe(document, {
  attributes: true,
  childList: true,
  subtree: true,
  characterData: true,
})  

@makew0rld
Copy link

Can anyone upload what they hear on some sites?

@larscmagnusson
Copy link

Hahaha, great work!

@lxsmnsyc
Copy link

Can anyone upload what they hear on some sites?

You'll just hear a Chewbacca Jr. talking

@nathanvogel
Copy link

Hahaha great idea! 👍 (could be a nice prank too)
Didn't work on Firefox, but Chrome is ✔️

@nickdotht
Copy link

I honestly didn't like it at first, then I realized how useful it can actually be :) . So I turned it into a basic Chrome extension: https://github.com/R4meau/plink-plonk

I'll be working on it every Sunday (very slow for now) and I'm open for any contributions at any time. Thanks for the head start @tomhicks.

@iAnatoly
Copy link

iAnatoly commented Feb 16, 2020

I have moved out the constants into variables, to be able to play with the settings more easily.
I also added a random component. to make it sound less monotonous:

// origin: https://gist.github.com/tomhicks/6cb5e827723c4eaef638bf9f7686d2d8 ,  tomhicks/plink-plonk.js

const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
var delay = 0.03
var freqbase = 600
var freqrnd = 400
const observer = new MutationObserver(function(mutationsList) {
  const oscillator = audioCtx.createOscillator()

  oscillator.connect(audioCtx.destination)
  oscillator.type = "sine"
  oscillator.frequency.setValueAtTime(
    Math.log(mutationsList.length + 5) * (Math.random()*freqrnd + freqbase),
    audioCtx.currentTime,
  )

  oscillator.start()
  oscillator.stop(audioCtx.currentTime + delay)
})

observer.observe(document, {
  attributes: true,
  childList: true,
  subtree: true,
  characterData: true,
})  

Then you can play with delay and freq on the fly.

@aflemi2
Copy link

aflemi2 commented Feb 16, 2020

Love this!

@iAnatoly
Copy link

This one randomly decreases/increases tone until it reaches a threshold. Makes it a bit more harmonious:

// origin: https://gist.github.com/tomhicks/6cb5e827723c4eaef638bf9f7686d2d8 ,  tomhicks/plink-plonk.js

const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
var delay = 0.03
// parameters of the random increment/decrement
var freqstart = 600
var freqlow = 400
var freqhigh = 900
var freqspeed = 50
// initial values
var freqinc = 25
var freq = 600

const observer = new MutationObserver(function(mutationsList) {
  const oscillator = audioCtx.createOscillator()

  oscillator.connect(audioCtx.destination)
  oscillator.type = "sine"
  oscillator.frequency.setValueAtTime(
    Math.log(mutationsList.length + 5) * freq,
    audioCtx.currentTime,
  )
  freq += freqinc
  if (freq > freqhigh || freq<freqlow) {
    // reset if we have reached the high or low bound
    freq = freqstart
    freqinc = (Math.random()-0.5)*freqspeed
  }

  oscillator.start()
  oscillator.stop(audioCtx.currentTime + delay)
})

observer.observe(document, {
  attributes: true,
  childList: true,
  subtree: true,
  characterData: true,
})  

@epranka
Copy link

epranka commented Feb 17, 2020

Nice work! Check this out https://soundcode.now.sh too 🙂

@ttcremers
Copy link

Absolutely wonderful! 😄

@fbedussi
Copy link

awesome! My computer now sounds like one from a '70s TV serial

@soundmasteraj
Copy link

Could replace the pulses with Rice Krispies sounds. Now, web == tasty breakfast!

@ricokahler
Copy link

try this on a typing test website. very enjoyable lol

@MarkArts
Copy link

Added quantization and randomness + delay for a cuter effect 😇

https://gist.github.com/MarkArts/3d4217f957df8a30802a8cbf962fa204

// origin: https://gist.github.com/tomhicks/6cb5e827723c4eaef638bf9f7686d2d8 ,  tomhicks/plink-plonk.js

/*
Copy this into the console of any web page that is interactive and doesn't
do hard reloads. You will hear your DOM changes as different pitches of
audio.
I have found this interesting for debugging, but also fun to hear web pages
render like UIs do in movies.
*/

// dorian (-)   C     E      F     G-     A     B-   
let scale = [
  264, 330, 352, 391.1, 440, 488.9 
]
scale = scale.concat(scale.map(x=>x*2))

console.log(scale)

function quantize(scale, freq) {
  return scale.reduce(function(prev, curr){
    return (Math.abs(curr - freq) < Math.abs(prev - freq) ? curr : prev);
  });
}

const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
const observer = new MutationObserver(observe)

function observe(mutationsList) {
  // with delay
  delayNote(gain => playNote(mutationsList, gain), 300, 0.2)  
  // without
  // playNote(mutationsList)
}


// Compressor as final stage to prevent clipping
const compressor = audioCtx.createDynamicsCompressor()
compressor.threshold.setValueAtTime(-40, audioCtx.currentTime);
compressor.knee.setValueAtTime(40, audioCtx.currentTime);
compressor.ratio.setValueAtTime(12, audioCtx.currentTime);
compressor.attack.setValueAtTime(0, audioCtx.currentTime);
compressor.release.setValueAtTime(0.25, audioCtx.currentTime);
compressor.connect(audioCtx.destination)

async function playNote(mutationsList, gain = 1) {  
  audioCtx.resume()
  
  const oscillator = audioCtx.createOscillator()
  oscillator.type = "triangle"
  const biquadFilter = audioCtx.createBiquadFilter();
  biquadFilter.type = "lowpass";
  const gainNode = audioCtx.createGain();
  const panNode = audioCtx.createStereoPanner();  
  
  // Setup audio chain 
  oscillator.connect(biquadFilter);
  biquadFilter.connect(gainNode);
  gainNode.connect(panNode);
  panNode.connect(compressor)
  
  let freq = quantize(scale, 440 * (Math.random() * 3))
  
  oscillator.frequency.setValueAtTime(
    quantize(scale, freq),
    audioCtx.currentTime,
  )
  
  // Low pass gate 
  biquadFilter.frequency.setValueAtTime(
    quantize(scale, freq * 4), 
    audioCtx.currentTime
  );
  
  biquadFilter.frequency.setTargetAtTime(
    freq, 
    audioCtx.currentTime,
    0.09,
  );  
  
  // accend the low pass gate with normal attenuatiob
  gainNode.gain.setValueAtTime(
    gain, 
    audioCtx.currentTime
  );    
  
  gainNode.gain.setTargetAtTime(
    0, 
    audioCtx.currentTime,
    0.1,
  );  

  // random stereo pan
  panNode.pan.setValueAtTime(
    Math.random() * 2 - 1, 
    audioCtx.currentTime
  );
  
  oscillator.start()
  oscillator.stop(audioCtx.currentTime + 1)
}

async function delayNote(f, time, decay, gain = 1){  
  if (gain <= 0) {
    return // stop repeats when they become inaudible
  }
  
  f(gain)
  
  setTimeout( _ => delayNote(f, time, decay, gain - decay), time);
}

observer.observe(document, {
  attributes: true,
  childList: true,
  subtree: true,
  characterData: true,
})  

@luigimannoni
Copy link

Deploy this on production but use an Arnold Schwarzenegger sound bank which plays a random line at each DOM mutation.

@albertodeago
Copy link

Wow this is probably the best gist I've ever seen

@dodds-cc
Copy link

love this

@nanxiaobei
Copy link

Added quantization and randomness + delay for a cuter effect 😇

https://gist.github.com/MarkArts/3d4217f957df8a30802a8cbf962fa204

// origin: https://gist.github.com/tomhicks/6cb5e827723c4eaef638bf9f7686d2d8 ,  tomhicks/plink-plonk.js

/*
Copy this into the console of any web page that is interactive and doesn't
do hard reloads. You will hear your DOM changes as different pitches of
audio.
I have found this interesting for debugging, but also fun to hear web pages
render like UIs do in movies.
*/

// dorian (-)   C     E      F     G-     A     B-   
let scale = [
  264, 330, 352, 391.1, 440, 488.9 
]
scale = scale.concat(scale.map(x=>x*2))

console.log(scale)

function quantize(scale, freq) {
  return scale.reduce(function(prev, curr){
    return (Math.abs(curr - freq) < Math.abs(prev - freq) ? curr : prev);
  });
}

const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
const observer = new MutationObserver(observe)

function observe(mutationsList) {
  // with delay
  delayNote(gain => playNote(mutationsList, gain), 300, 0.2)  
  // without
  // playNote(mutationsList)
}


// Compressor as final stage to prevent clipping
const compressor = audioCtx.createDynamicsCompressor()
compressor.threshold.setValueAtTime(-40, audioCtx.currentTime);
compressor.knee.setValueAtTime(40, audioCtx.currentTime);
compressor.ratio.setValueAtTime(12, audioCtx.currentTime);
compressor.attack.setValueAtTime(0, audioCtx.currentTime);
compressor.release.setValueAtTime(0.25, audioCtx.currentTime);
compressor.connect(audioCtx.destination)

async function playNote(mutationsList, gain = 1) {  
  audioCtx.resume()
  
  const oscillator = audioCtx.createOscillator()
  oscillator.type = "triangle"
  const biquadFilter = audioCtx.createBiquadFilter();
  biquadFilter.type = "lowpass";
  const gainNode = audioCtx.createGain();
  const panNode = audioCtx.createStereoPanner();  
  
  // Setup audio chain 
  oscillator.connect(biquadFilter);
  biquadFilter.connect(gainNode);
  gainNode.connect(panNode);
  panNode.connect(compressor)
  
  let freq = quantize(scale, 440 * (Math.random() * 3))
  
  oscillator.frequency.setValueAtTime(
    quantize(scale, freq),
    audioCtx.currentTime,
  )
  
  // Low pass gate 
  biquadFilter.frequency.setValueAtTime(
    quantize(scale, freq * 4), 
    audioCtx.currentTime
  );
  
  biquadFilter.frequency.setTargetAtTime(
    freq, 
    audioCtx.currentTime,
    0.09,
  );  
  
  // accend the low pass gate with normal attenuatiob
  gainNode.gain.setValueAtTime(
    gain, 
    audioCtx.currentTime
  );    
  
  gainNode.gain.setTargetAtTime(
    0, 
    audioCtx.currentTime,
    0.1,
  );  

  // random stereo pan
  panNode.pan.setValueAtTime(
    Math.random() * 2 - 1, 
    audioCtx.currentTime
  );
  
  oscillator.start()
  oscillator.stop(audioCtx.currentTime + 1)
}

async function delayNote(f, time, decay, gain = 1){  
  if (gain <= 0) {
    return // stop repeats when they become inaudible
  }
  
  f(gain)
  
  setTimeout( _ => delayNote(f, time, decay, gain - decay), time);
}

observer.observe(document, {
  attributes: true,
  childList: true,
  subtree: true,
  characterData: true,
})  

This one's great~ 😇

@everaldo
Copy link

Awesome idea!

It would be nice to create some debugging library with sounds for Ajax Requests, Ajax failures, console errors etc.

@nickdotht
Copy link

nickdotht commented Feb 19, 2020

@everaldo I'm working on it at the moment: https://github.com/r4meau/plink-plonk

Also, great idea about the console errors. Added this to the list of features in the README :). For the requests, it's already in the list.

@everaldo
Copy link

@R4meau, that's awesome!

@Adrinalin4ik
Copy link

Maybe someone makes some chrome extension?

@AlexRatmansky
Copy link

And the bookmarklet version:

javascript:(function(){const%20audioCtx=new(window.AudioContext||window.webkitAudioContext);const%20oscillator=audioCtx.createOscillator();oscillator.connect(audioCtx.destination);oscillator.type="sine";let%20numItems=0;oscillator.frequency.setValueAtTime(1,audioCtx.currentTime);oscillator.start();const%20observer=new%20MutationObserver(function(mutationsList){numItems+=mutationsList.length;oscillator.frequency.setValueAtTime(Math.log(numItems+1)*440,audioCtx.currentTime);setTimeout(()=>{numItems-=mutationsList.length;if(numItems===0){oscillator.frequency.setValueAtTime(1,audioCtx.currentTime)}else{oscillator.frequency.setValueAtTime(Math.log(numItems+1)*440,audioCtx.currentTime)}},100)});observer.observe(document,{attributes:true,childList:true,subtree:true,characterData:true})})();

@aibolik
Copy link

aibolik commented Feb 21, 2020

Maybe someone makes some chrome extension?

Yeah, let's do it... (adding to backlog) 😅

@nickdotht
Copy link

nickdotht commented Feb 21, 2020

@aibolik Already in the making: https://github.com/r4meau/plink-plonk

Feel free to fork and contribute... or start your own. 😄

I'm gonna go slow on it for now (Sunday only), but I'll accept useful PRs at anytime.

@mahnouel
Copy link

mahnouel commented Feb 28, 2020

Could anyone add reload support? This is so wonderful 🥰 Maybe via localStorage? Or as Firefox Extension?

@iamnmanoj
Copy link

Its so helpful to find out the DOM manipulations happening behind the eyes!!. Good job @R4meau

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment