Skip to content

Instantly share code, notes, and snippets.

@tomhicks
Last active March 18, 2024 02:23
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
@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