<h2 class="message">PLAY</h2>
<a id="badge" href="http://www.chromeexperiments.com/experiment/audio-cloud/" target="_blank"><img src="data:image/png;base64,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" alt="See my Experiment on ChromeExperiments.com"></a>>
html, body {
	margin:0;
  overflow:hidden;
  background:#000;
  color:white;
  font-family:Arial;
}

.message{
  padding:20px;
  position:absolute;
  top:50%;
  left:50%;
  transform:translate(-50%, -50%);
  background:black;
  cursor:pointer;
  font-size:14px;
  border:1px solid white;
}

#badge{
  position: absolute;
  bottom: 0;
  left: 0;
  height: 92px;
}


a:link, a:hover, a:visited, a:active{
  color:white;
  text-decoration: none;
}
.experiment-url{
  position:absolute;
  bottom:10px;
  right:10px;
  padding:8px;
  z-index: 1;
  font-size: 11px;
  background:black;
  letter-spacing:0.5px;
  border: 1px solid white;
}
AUDIO_URL = "https://ma77os-media-assets.s3.us-east-2.amazonaws.com/audio/paradise_circus.mp3"

modes = ["cubic", "conic"]
themes = {
  pinkBlue:[0xFF0032, 0xFF5C00, 0x00FFB8, 0x53FF00]
  yellowGreen:[0xF7F6AF, 0x9BD6A3, 0x4E8264, 0x1C2124, 0xD62822]
  yellowRed:[0xECD078, 0xD95B43, 0xC02942, 0x542437, 0x53777A]
  blueGray:[0x343838, 0x005F6B, 0x008C9E, 0x00B4CC, 0x00DFFC]
  blackWhite:[0xFFFFFF, 0x000000, 0xFFFFFF, 0x000000, 0xFFFFFF]
}

themesNames = []
for k, v of themes
  themesNames.push k

# PARAMETERS
params = {
  # public
  mode: modes[0]
  theme:themesNames[0]
  radius: 3
  distance: 600
  size: .5

  # private
  numParticles: 5000
  sizeW: 1
  sizeH: 1
  radiusParticle: 60
  themeArr:themes[this.theme]
}


TOTAL_BANDS = 256
cp = new PIXI.Point()
mouseX = 0
mouseY = 0
mousePt = new PIXI.Point()
windowW = 0
windowH = 0

stage = null
renderer = null
texCircle = null
circlesContainer = null
arrCircles = []
hammertime = null
message = null
# audio
audio = null
analyser = null
analyserDataArray = null
isPlaying = false
canplay = false
# gui
gui = null

init = ->
  
  initGestures()
  
  message = $(".message")
  message.on("click", play)

  resize()
  build()
  resize()

  mousePt.x = cp.x
  mousePt.y = cp.y

  $(window).resize(resize)

  startAnimation()
  initGUI()

play = ->
  return if isPlaying
  initAudio()
  
  message.css("cursor", "default")
  
  if canplay
    message.hide()
  else
    message.html("LOADING MUSIC...")
  audio.play()
  isPlaying = true
  
initGUI = ->
  gui = new dat.GUI()
  # if window.innerWidth < 500
  gui.close()
    
  modeController = gui.add params, 'mode', modes
  modeController.onChange (value) ->
    changeMode value
    

  themeController = gui.add(params, 'theme', themesNames)
  themeController.onChange (value) ->
    changeTheme params.theme

  gui.add params, 'radius', 1, 8
  gui.add params, 'distance', 100, 1000
  sizeController = gui.add params, 'size', 0, 1
  sizeController.onChange (value) ->
    resize value

initAudio = ->
  context = new (window.AudioContext || window.webkitAudioContext)()
  analyser = context.createAnalyser()
#   analyser.smoothingTimeConstant = 0.5

  source = null 
  
  audio = new Audio()
  audio.crossOrigin = "anonymous"
  audio.src = AUDIO_URL
  
  audio.addEventListener 'canplay', ->
    if(isPlaying)
      message.hide()
      
    canplay = true

    source = context.createMediaElementSource audio
    source.connect analyser
    source.connect context.destination

    analyser.fftSize = TOTAL_BANDS * 2
    bufferLength = analyser.frequencyBinCount
    analyserDataArray = new Uint8Array bufferLength

  

startAnimation = ->
  requestAnimFrame(update)
  

initGestures = ->
   $(window).on 'mousemove touchmove', (e) ->
      if e.type == 'mousemove'
        mouseX = e.clientX
        mouseY = e.clientY
      else
        mouseX = e.originalEvent.changedTouches[0].clientX
        mouseY = e.originalEvent.changedTouches[0].clientY



build = ->
  stage = new PIXI.Stage 0x000000
  renderer = PIXI.autoDetectRenderer {
    width: $(window).width()
    height:$(window).height()
    antialias:true
    resolution:window.devicePixelRatio
  }

  $(document.body).append renderer.view

  texCircle = createCircleTex()

  buildCircles()

buildCircles = ->
  circlesContainer = new PIXI.DisplayObjectContainer()
  stage.addChild(circlesContainer)

  for i in [0..params.numParticles-1]
    circle = new PIXI.Sprite texCircle
    circle.anchor.x = 0.5
    circle.anchor.y = 0.5
    
    circle.position.x = circle.xInit = cp.x
    circle.position.y = circle.yInit = cp.y
    circle.mouseRad = Math.random()
    
    circlesContainer.addChild(circle)
    arrCircles.push(circle)


  changeTheme params.theme
  

createCircleTex = ->
  gCircle = new PIXI.Graphics()
  gCircle.beginFill(0xFFFFFF)
  gCircle.drawCircle(0, 0, params.radiusParticle)
  gCircle.endFill()

  gCircle.generateTexture()

resize = ->
  windowW = $(window).width()
  windowH = $(window).height()
  cp.x = windowW * .5
  cp.y = windowH * .5

  params.sizeW = windowH * params.size
  params.sizeH = windowH * params.size

  changeMode(params.mode)

  if renderer
    renderer.resize(windowW, windowH)

changeTheme = (name)->
  params.themeArr = themes[name]
  indexColor = 0
  padColor = Math.ceil params.numParticles / params.themeArr.length
  for i in [0..params.numParticles-1]
    circle = arrCircles[i]
    group = indexColor * padColor / params.numParticles
    circle.blendMode = if params.theme == "blackWhite" then PIXI.blendModes.NORMAL else PIXI.blendModes.ADD
    circle.indexBand = Math.round(group * (TOTAL_BANDS-56))-1
    if circle.indexBand <= 0
      circle.indexBand = 49
    circle.s = (Math.random() + (params.themeArr.length-indexColor)*0.2)*0.1
    circle.scale = new PIXI.Point(circle.s, circle.s)
    if i % padColor == 0
      indexColor++

    circle.tint = params.themeArr[indexColor - 1]


changeMode = (value)->
  return if !arrCircles || arrCircles.length == 0

  # randomize mode if not specified
  if !value
    value = modes[Math.floor(Math.random()*modes.length)]

  params.mode = value

  for i in [0..params.numParticles-1]
    circle = arrCircles[i]
    
    switch params.mode
      # cubic
      when modes[0]
        circle.xInit = cp.x + (Math.random() * params.sizeW - params.sizeW/2)
        circle.yInit = cp.y + (Math.random() * params.sizeH - params.sizeH/2)

      # circular
      when modes[1]
        angle = Math.random() * (Math.PI * 2)
        circle.xInit = cp.x + (Math.cos(angle)*params.sizeW)
        circle.yInit = cp.y + (Math.sin(angle)*params.sizeH)

update = ->
  requestAnimFrame(update)
  
  t = performance.now() / 60

  if analyserDataArray && isPlaying
    analyser.getByteFrequencyData analyserDataArray


  if mouseX > 0 && mouseY > 0
    mousePt.x += (mouseX - mousePt.x) * 0.03
    mousePt.y += (mouseY - mousePt.y) * 0.03
  else
    a = t*0.05
    mousePt.x = cp.x + Math.cos(a) * 100
    mousePt.y = cp.y + Math.sin(a) * 100

  for i in [0..params.numParticles-1]
    circle = arrCircles[i]

    if analyserDataArray && isPlaying
      n = analyserDataArray[circle.indexBand]
      scale = ((n / 256)) * circle.s*2
    else
      scale = circle.s*.1

    scale *= params.radius

    circle.scale.x += (scale - circle.scale.x) * 0.3
    circle.scale.y = circle.scale.x

    dx = mousePt.x - circle.xInit
    dy = mousePt.y - circle.yInit
    dist = Math.sqrt(dx * dx + dy * dy)
    angle = Math.atan2(dy, dx)

    r = circle.mouseRad * params.distance + 30
    xpos = circle.xInit - Math.cos(angle) * r
    ypos = circle.yInit - Math.sin(angle) * r
    circle.position.x += (xpos - circle.position.x) * 0.1
    circle.position.y += (ypos - circle.position.y) * 0.1

  renderer.render(stage)

init()
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js
  2. //cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js
  3. //cdnjs.cloudflare.com/ajax/libs/pixi.js/1.6.1/pixi.js