2017/11/21

Web Audio APIとVue.jsでオシレーターをつくる(カスタム波形、正弦波、矩形波、ノコギリ波、三角波)

前回、Web Audio APIとVue.jsでシンプルなシンセサイザーをつくった。ただ「シンプル」ということで正弦波(サイン波)しか鳴らせない。

もっといろんな音を鳴らしたい!

ということで正弦波はもちろん、矩形波、ノコギリ波、三角波、カスタム波形をだせる
オシレーターをつくる。

機能としては以下のとおり。

  • 出す波形を選択できる
    • 正弦波(Sine wave)
    • 矩形波(Squere wave)
    • ノコギリ波(Sawtooth wave)
    • 三角波(Triangle wave)
    • カスタム波形(Custom)
  • 鳴らす周波数を変更できる
  • セント単位で変更できる

出す波形を決める


Web Audio APIには、あらかじめ正弦波、矩形波、ノコギリ波、三角波のプリセットがあり、OscillatorNodeのtypeプロパティを変更することで簡単に波形を変更することができる。
const ctx = new AudioContext();
const osc = ctx.createOscillator();
osc.connect(ctx.destination);

// OscillatorNode.typeプロパティで波形を設定
osc.type = 'sine' | 'squere' | 'sawtooth' | 'triangle';


加えて、Web Audio APIではカスタム波形も設定できる。
カスタム波形とは、波形1周期分にフーリエ変換をかけた結果を設定することで、オリジナルの波形をつくることができる機能だ。

世の中の「音」は正弦波の和で表すことができるため、カスタム波形を用いれば、理論上どんな音色でも出すことができる。

このあたりは言葉で説明するより実際にさわってもらったほうがわかりやすいだろう。
※ 私自身、ちゃんと説明できないので…。
const ctx = new AudioContext();
const osc = ctx.createOscillator();
osc.connect(ctx.destination);

// カスタム波形
osc.type = 'custom';

// 同じ長さのFloat32Array
// ↓の例は基音のみの正弦波
const real = [0, 1.0];  // 余弦項
const imag = [0, 0];    // 正弦項

const wave = ctx.createPeriodicWave(real, imag);
osc.setPeriodicWave(wave);



周波数を決定する


出力する周波数については、以下の記事を参考にしてほしい。
OscillatorNode.frequency.value = 440のように変更できる。




セント単位で音を変える(デチューン)


Web Audio APIにはセント単位で音を変えられるDetune(デチューン/ディチューン)プロパティがある。
こちらも周波数同様にOscillatorNode.detune.value = 10のように変更できる。

ちなみにセントとは、1オクターブを1200セントと定義されている。十二平均律の半音なら100セント、全音なら200セントといった具合だ。そのため1セントが何Hzなのかということは定義できない。



オシレーターをつくる


Web Audio APIとVue.jsを使い、下図のようなオシレーターをつくる。

<div id="app">
  <form class="controller-form">
    <div class="wave">
      <h3>Wave form</h3>
      <template v-for="wave in waveForms">
        <label>
          <input type="radio" :value="wave" v-model="form.waveForm">
          {{ wave }}
        </label>
      </template>
      <!-- カスタム波形が選択された場合だけ表示する -->
      <div v-if="form.waveForm === 'custom'" class="custom-form">
        <table>
          <thead>
            <th>harmonic</th>
            <th>real</th>
            <th>imag</th>
          </thead>
          <tbody>
            <!-- index0は0固定 -->
            <tr v-for="i in 7">
              <td>{{ i }}</td>
              <td><input type="range" min="0" max="1" step="any" v-model="form.harmonics.real[i]"></td>
              <td><input type="range" min="0" max="1" step="any" v-model="form.harmonics.imag[i]"></td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    <div class="frequency">
      <h3>Frequency</h3>
      <input type="number" min="0" max="20000" v-model="form.freq">
    </div>
    <div class="detune">
      <h3>Detune</h3>
      <input type="range" min="-100" max="100" step="any" v-model="form.detune">
    </div>
  </form>
  <div class="osc-controller">
    <button type="button" @click="start">Start</button>
    <button type="button" @click="stop">Stop</button>
  </div>
</div>
new Vue({
  el: '#app',
  data() {
    return {
      ctx: new AudioContext(),
      osc: null,
      form: {
        waveForm: 'sine',
        detune: 0,
        freq: 440,
        harmonics: {
          real: [0, 1, 0, 0, 0, 0, 0, 0],
          imag: [0, 0, 0, 0, 0, 0, 0, 0]
        }
      },
      waveForms: ['sine', 'square', 'sawtooth', 'triangle', 'custom']
    }
  },
  methods: {
    start() {
      this.osc = this.ctx.createOscillator();
      this.osc.connect(this.ctx.destination);
      this.osc.frequency.value = this.form.freq;
      this.osc.detune.value = this.form.detune;
      
      if (this.form.waveForm === 'custom') {
        // カスタム波形
        const wave = this.ctx.createPeriodicWave([...this.form.harmonics.real], [...this.form.harmonics.imag]);
        this.osc.setPeriodicWave(wave);
      } else {
        this.osc.type = this.form.waveForm;
      }
       
      this.osc.start();
    },
    stop() {
      this.osc.stop();
    }
  }
});

カスタム波形を選択するとrealとimagのスライドバーが表示される。
ちなみにreal、imagの0要素目は「0固定」と決まっているので、UI上は表示していない。

実際にさわってみるとわかると思うが、real、imagのスライドバーを動かすことで倍音が増えていく。


気が向いたらフーリエ変換の方法などもまとめようと思う。



以上

written by @bc_rikko

0 件のコメント :

コメントを投稿