Skip to content

MIDI Visualizer

An experiment in audio-visual effects. This experiment requires Web MIDI.

(Note that the code only check for connected MIDI devices when it's started; refresh the page if you can't see your device.)

Click to get a new set of colors.

A visualization of MIDI inputs

let midiSelect
let channelSelect

function setup() {
  let canvas = createCanvas()
  canvas.style('filter', 'blur(4px) contrast(300%)')
  midiSelect = createSelect()
  midiSelect.style('background', color(255))
  midiSelect.style('margin', 0)
  midiSelect.style('border', '1px solid')
  midiSelect.style('border-color', color(0))
  midiSelect.parent(canvas.parent())
  windowResized()
  
  WebMidi.addListener('connected', ({port}) => {
    if(port.type == 'input') {
      midiSelect.option(port.name, port.id)
    }
  })
  for (let port of WebMidi.inputs) {
    midiSelect.option(port.name, port.id)
  }
  
  let listeners = []
  let updateListener = () => {
    for (let listener of listeners) {
      listener.remove()
    }
    listeners = []
    let inp = WebMidi.getInputById(midiSelect.selected())
    if (inp) {
      listeners.push(...inp.addListener('noteon', noteon))
      listeners.push(...inp.addListener('noteoff', noteon))
    }
    console.log(inp, listeners)
  }
  
  midiSelect.changed(() => {
    storeItem('device', midiSelect.selected())
    updateListener()
  })
  midiSelect.selected(getItem('device') || '')
  
  WebMidi.enable().then(updateListener)
  
  canvas.mousePressed(resetColors)
  resetColors()
}
function windowResized() {
  resizeCanvas(windowWidth, windowHeight - 30)
  midiSelect.size(windowWidth, 30)
}

function resetColors() {
  colors = Array(7).fill().map((_, i) => Array(8).fill().map(_ => 
    [random()*random(0.5, 1)*255, random()*random(0.5, 1)*255, random()*random(0.5, 1)*255, random()+random()]
  ))
}
let pitches = Array(127).fill(0)
let pitchesD = Array(127).fill(0)
function noteon({note, value}) {
  pitches[note.number] = value
}
function noteoff({note}) {
  notes[note.number] = 0
}

let objs = new Set()
let inhib = Array(pitches.length).fill(0)
let colors = []

function draw() {
  let notes = Array(12).fill(0)
  for (let i = 0; i < pitches.length; i ++) {
    /*circle(map(i, 0, pitches.length, 0, width), 100, notes[i] * 60 + 2)
    text(i, map(i, 0, pitches.length, 0, width), 60)*/
    pitches[i] *= 0.99
    pitchesD[i] += pitches[i]
    pitchesD[i] *= 0.94
    notes[i % 12] += pitchesD[i]
  }
  let intervals = Array(colors.length).fill().map(x => Array(12).fill(0))
  for (let k = 0; k < intervals.length; k ++) {
    for (let i = 0; i < 12; i ++) {
      for (let j = 0; j < 12; j ++) {
        intervals[k][(j - i + 12) % 12] += (notes[i] * notes[(i + k + 1) % 12] * notes[j]) ** (1/3)
      }
    }
  }
  let totalWeight = colors.reduce((e, x, i) => e + x.reduce((e, x, j) => e + x[3] * intervals[i][j], 0), 0)
  let col = Array(3).fill().map((_, c) => totalWeight > 0.01 ? colors.reduce((e, x, i) => e + x.reduce((e, x, j) => e + x[c] * x[3] * intervals[i][j] / totalWeight, 0), 0) : 100).map(x => x * 0.9)
  col = color(...col)
  
  background(20)
  noStroke()
  
  for (let i = 0; i < pitches.length; i ++) {
    inhib[i] -= pitches[i]
    inhib[i] = min(inhib[i], pitches[i] * 6)
    while (inhib[i] < 0) {
      inhib[i] += 6
      let x = (i + random(0.2)) / pitches.length
      x = (x - 0.5)
      //x = pow(x * 3, 3) / 3 + 0.5
      x = exp(x * 5) / (1 + exp(x * 5))
      x = x * width
      objs.add({c: col, x: x, y: -20, r: sqrt(pitches[i]) * 50})
    }
  }
  blendMode(ADD)
  for (let o of objs) {
    fill(o.c)
    ellipse(o.x, o.y, o.r * 0.7, o.r)
    o.y += random(1, 2)
    o.x += random(-1, 1) * 0.3
    if (o.y > height + 30) {
      objs.delete(o)
    }
  }
  for (let i = 0; i < 1; i+=0.02) {
    fill(red(col), green(col), blue(col), 255 * pow(1 - i, 2))
    rect(0, 200 * i, width, 200 * 0.02)
  }
  blendMode(BLEND)
}

(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/-oFnFwfbq)

Experiments tagged p5 (15/85)

|← Experiments tagged music (1/2)

Experiments on this site (15/85)