iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 22
5

昨天我們聊到的 Tone.js 的強力功能 - 排程播放,那麼就來玩玩看許多高手前輩都做過的 音序機 吧~

Sequencer

音序機,顧名思義,就是能夠把聲音依照使用者想要的順序,依序播放出來的機器。在這趟旅程 第二天Google Doodle 範例,我們就已經看過音序機囉~在 Tone.js 的範例 Demo 中,也非常多這類的創作應用。另外,去年的鐵人 vibertthio 也製作過相當精彩的作品 - Beact,大家也不仿嘗試看看。

Beact

透過 Tone.js 的排序,音序機的程式絕對不會是難以達成的;就讓我們跟著高手前輩們的腳步,一起挑戰看看吧!

Demo

第一步,當然是製作樂器;今天我們先專注在 鼓機,也就是節奏樂器上。

節奏樂器,當然不能缺少了大鼓 & Hi-Hat:

const kick = new Tone.MembraneSynth({
  octaves: 3,
  envelope  : {
    sustain: 0.2,
  }
}).toMaster()

const hihat = new Tone.NoiseSynth({
  playbackRate: 5,
  envelope  : {
    sustain  : 0.0001,
  }
}).toMaster()

透過 Tone.js 已經封裝好的 MembraneSynthNoiseSynth 兩種合成器,我們只需要逐步調整欲設定的參數,就能夠慢慢製作出想要的聲響啦。

比較困難的是小鼓。由於小鼓的構造是由 鼓膜 及 響弦 構成,敲擊時的聲響是多質感的聲波相互疊加而成的。

筆者參考了 vibertthio 去年的實作,再稍作修改而成。

// Class: Snare
const noise = new NoiseSynth({
  volume: volume,
  noise: {
    type: 'pink',
    playbackRate: 3
  },
  envelope: {
    attack: 0.001,
    decay: 0.15,
    sustain: 0.0001,
    release: 0.05
  }
}).connect(lowPass)

const poly = new PolySynth(6, Synth, {
  volume: volume + 6,
  oscillator: {
    partials: [0, 2, 3, 4]
  },
  envelope: {
    attack: 0.001,
    decay: 0.17,
    sustain: 0.0001,
    release: 0.08,
    releaseCurve: 'exponential'
  }
}).toMaster()

trigger(time) {
  this.noise.triggerAttack(time)
  this.poly.triggerAttackRelease(['C2', 'D#2', 'G2'], '16n', time)
}

...

// Usage
const snare = new Snare().toMaster()

由於要做出綜合的質感,必須同時觸發兩種聲響。

再稍微調整一下各樂器的音量平衡,這部分就完成囉!接著是設計資料結構。

const defaultSequencer = {
  drum: {
    kick: new Array(16),
    hihat: new Array(16),
    snare: new Array(16),
    tomL: new Array(16),
    tomM: new Array(16),
    tomH: new Array(16),
  }
  ...
}

這邊是想要做每種樂器一個陣列,顯示時再分別對陣列做綁定。

筆者在調大鼓音色的過程中順手做了幾顆 Tom-Tom,就一併放上去了 XD

資料綁定的部分:

...
<div class="hihat">
  <div v-for="i in 16" :key="`hihat_${i}`" 
      :class="{'item': true, 'active': !!sequencer.drum.hihat[i-1] }" 
      @click="$set(sequencer.drum.hihat, i-1, !sequencer.drum.hihat[i-1])" />
</div>
...

在使用 Vue 實作時要注意,設定陣列的資料時必須透過 $set(),才可以讓 Vue 觀測到資料變動喔!

最後就是讓資料驅動各個樂器在拍點上發出聲響:

Tone.Transport.scheduleRepeat((time) => {
  let i = Math.round(Tone.Transport.getSecondsAtTime() * (this.BPM / 60) % 16)
  this.index = i
  const { 
    drum: { 
      kick = new Array(16),
      tomL = new Array(16),
      tomM = new Array(16),
      tomH = new Array(16),
      snare = new Array(16),
      hihat = new Array(16)
    }
  } = this.sequencer

  if(kick[i]) this.kick.triggerAttackRelease("C2", "4n", time)
  if(hihat[i]) this.hihat.triggerAttackRelease("8n", time)
  if(snare[i]) this.snare.trigger(time)
  if(tomL[i]) this.tomL.triggerAttackRelease("E2", "4n", time)
  if(tomM[i]) this.tomM.triggerAttackRelease("G2", "4n", time)
  if(tomH[i]) this.tomH.triggerAttackRelease("A#2", "4n", time)

}, "4n")

這邊是每一 tick 為四分音符,實際運用上也可以切成更細小的拍點。

最後就是畫面的優化,稍微配色、排版一下,就完成鼓機的部分啦!

Live Demo

result

其實做到後來自己就玩上癮了,難怪各位高手前輩都稍有著墨過音序機,這個真的很有趣 XD

那麼今天的音序機第一部分就到這邊啦,明天繼續!

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
20. Tone.js - 排程
下一篇
22. 音序機 Part.2
系列文
JavaScript 音樂漫遊 - 30 天探索 Web Audio!31
1
CK Chuang
iT邦新手 5 級 ‧ 2018-11-06 09:53:10

跪著玩

Gary iT邦新手 5 級 ‧ 2018-11-06 09:56:04 檢舉

我也跪著玩 大師的作品 /images/emoticon/emoticon34.gif

CK Chuang iT邦新手 5 級 ‧ 2018-11-06 15:41:53 檢舉

/images/emoticon/emoticon46.gif

都別這樣跪這種事還是讓小的來!

1
SunAllen
iT邦高手 1 級 ‧ 2018-11-14 09:38:16

療愈啊~~

https://ithelp.ithome.com.tw/upload/images/20181114/2000613262FWmxaUMU.png

Gary iT邦新手 5 級 ‧ 2018-11-14 09:49:14 檢舉

/images/emoticon/emoticon41.gif

1
King Tzeng
iT邦新手 4 級 ‧ 2019-10-18 15:44:37

過了一年後才來玩!!真的太酷了!!玩到停不下來XD

Gary iT邦新手 5 級 ‧ 2019-10-19 00:04:56 檢舉

/images/emoticon/emoticon41.gif

我要留言

立即登入留言