Eason's blog

【100sites #010】Piano,用P5js的sound函式庫實作虛擬鋼琴

Published on

Piano,用 P5js 的 sound 函式庫實作虛擬鋼琴

點我玩線上 Demo

點我查看程式碼

螢幕快照 2016-03-26 上午1.18.01.png

要營造出好氣氛,一定要有適合的音樂,於是今天我想要玩音樂,所以就研究了一下P5.jssound函式庫,做出了今天的 Piano 虛擬鋼琴。

這個函式庫有不少功能可以使用,播音錄音波型轉換都可以做到,今天的 Piano 使用了 sine 函數的 oscillator 製造聲音,再用 FFT.analyze()來顯示波形。

本次的程式碼:

<!DOCTYPE html>
<html>
  <head>
    <meta charset-"UTF-8">
    <title>Piano</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
    <script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.23/p5.js"></script>
    <script src="p5.sound.min.js"></script>
    <script src="piano.js"></script>
  </head>

  <body></body>
</html>
* {
  margin: 0;
  padding: 0;
}

body {
  overflow: hidden;
}
var attackLevel = 2.0
var releaseLevel = 0

var attackTime = 0.001
var decayTime = 0.2
var susPercent = 0.2
var releaseTime = 0.5

var env, triOsc
var notePressed = [
  false,
  false,
  false,
  false,
  false,
  false,
  false,
  false,
  false,
  false,
  false,
  false,
]
var fft

function setup() {
  createCanvas(windowWidth, windowHeight)

  textAlign(CENTER)
  textSize(40)

  env = new p5.Env()
  env.setADSR(attackTime, decayTime, susPercent, releaseTime)
  env.setRange(attackLevel, releaseLevel)

  triOsc = new p5.Oscillator('sine')
  triOsc.amp(env)
  triOsc.start()

  ellipseMode(CENTER)
  strokeWeight(2)
  rectMode(CENTER)

  fft = new p5.FFT()
}

function draw() {
  background(200)

  // title
  fill(255)
  strokeWeight(5)
  textSize(120)
  textFont('Georgia')
  text('PIANO', width / 2, 150)
  textFont('Normal')

  // spectrum
  var spectrum = fft.analyze()
  noStroke()
  fill(230)
  for (var i = 0; i < spectrum.length; i++) {
    var x = map(i, 0, spectrum.length, 0, width / 2)
    var h = -height / 2 + map(spectrum[i], 0, 255, height / 2, 0)
    rect(x, height / 2, width / spectrum.length, h)
    rect(width - x, height / 2, width / spectrum.length, h)
  }

  // black note
  drawNote(windowWidth / 2 - 150, windowHeight / 2 - 60, 'E', 'b', notePressed[0])
  drawNote(windowWidth / 2 - 90, windowHeight / 2 - 60, 'R', 'b', notePressed[1])
  drawNote(windowWidth / 2 + 30, windowHeight / 2 - 60, 'Y', 'b', notePressed[2])
  drawNote(windowWidth / 2 + 90, windowHeight / 2 - 60, 'U', 'b', notePressed[3])
  drawNote(windowWidth / 2 + 150, windowHeight / 2 - 60, 'I', 'b', notePressed[4])

  // white note
  drawNote(windowWidth / 2 - 180, windowHeight / 2, 'S', 'w', notePressed[5])
  drawNote(windowWidth / 2 - 120, windowHeight / 2, 'D', 'w', notePressed[6])
  drawNote(windowWidth / 2 - 60, windowHeight / 2, 'F', 'w', notePressed[7])
  drawNote(windowWidth / 2, windowHeight / 2, 'G', 'w', notePressed[8])
  drawNote(windowWidth / 2 + 60, windowHeight / 2, 'H', 'w', notePressed[9])
  drawNote(windowWidth / 2 + 120, windowHeight / 2, 'J', 'w', notePressed[10])
  drawNote(windowWidth / 2 + 180, windowHeight / 2, 'K', 'w', notePressed[11])
}

function playEnv(f) {
  triOsc.freq(f)
  env.play()
}

function drawNote(xpos, ypos, key, color, pressed) {
  // black note
  var big = 0
  if (pressed) {
    big = 8
  }
  // shadow
  fill(100)
  strokeWeight(0)
  ellipse(xpos + 3, ypos + 3, 50 + big, 50 + big)
  // note
  if (color == 'b') {
    fill(0)
    stroke(255)
  } else {
    fill(255)
    stroke(0)
  }
  strokeWeight(2)
  ellipse(xpos, ypos, 50 + big, 50 + big)
  // text
  if (color == 'b') {
    fill(255)
  } else {
    fill(0)
  }
  textSize(40)
  if (pressed) {
    textSize(44)
  }
  text(key, xpos, ypos + 17)
}

function keyPressed() {
  switch (keyCode) {
    case 83: // S
    case 115: // s
      playEnv(261) // C note
      notePressed[5] = true
      break
    case 69: // E
    case 101: // e
      playEnv(277) // C# note
      notePressed[0] = true
      break
    case 68: // D
    case 110: // d
      playEnv(293) // D note
      notePressed[6] = true
      break
    case 82: // R
    case 114: // r
      playEnv(311) // D# note
      notePressed[1] = true
      break
    case 70: // F
    case 102: // f
      playEnv(329) // E note
      notePressed[7] = true
      break
    case 71: // G
    case 103: // g
      playEnv(349) // F note
      notePressed[8] = true
      break
    case 89: // Y
    case 121: // y
      playEnv(369) // F# note
      notePressed[2] = true
      break
    case 72: // H
    case 104: // h
      playEnv(392) // G note
      notePressed[9] = true
      break
    case 85: // U
    case 117: // u
      playEnv(415) // G# note
      notePressed[3] = true
      break
    case 74: // J
    case 106: // j
      playEnv(440) // A note
      notePressed[10] = true
      break
    case 73: // U
    case 105: // u
      playEnv(466) // A# note
      notePressed[4] = true
      break
    case 75: // K
    case 107: // k
      playEnv(493) // B note
      notePressed[11] = true
      break
    default:
      break
  }
}

function keyReleased() {
  switch (keyCode) {
    case 83: // S
    case 115: // s
      // C note
      notePressed[5] = false
      break
    case 69: // E
    case 101: // e
      // C# note
      notePressed[0] = false
      break
    case 68: // D
    case 110: // d
      // D note
      notePressed[6] = false
      break
    case 82: // R
    case 114: // r
      // D# note
      notePressed[1] = false
      break
    case 70: // F
    case 102: // f
      // E note
      notePressed[7] = false
      break
    case 71: // G
    case 103: // g
      // F note
      notePressed[8] = false
      break
    case 89: // Y
    case 121: // y
      // F# note
      notePressed[2] = false
      break
    case 72: // H
    case 104: // h
      // G note
      notePressed[9] = false
      break
    case 85: // U
    case 117: // u
      // G# note
      notePressed[3] = false
      break
    case 74: // J
    case 106: // j
      // A note
      notePressed[10] = false
      break
    case 73: // U
    case 105: // u
      // A# note
      notePressed[4] = false
      break
    case 75: // K
    case 107: // k
      // B note
      notePressed[11] = false
      break
    default:
      break
  }
}