【学習】駆け出しエンジニアがWeb Audio API音を鳴らす仕組みの実装をしてみた

## はじめに
今回は、前回同様、簡単なミニアプリ作成において使用したAPIについて。
「Web Audio API」というものを使用しました。
参考サイトから、こんな便利な機能があるとは。驚きです。

## 参考サイト

https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API

https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API/Using_Web_Audio_API

https://qiita.com/ishi720/items/320e557e0aa8372d803f


## こちらのサイトで動作確認できます
以下に示すコードを自分の手元で試してみたい方はこちらのサイトを利用されてみる事をお勧めします。（私も今回初めて知りました。）

https://jsfiddle.net/


## こんな感じのコード
以下コードはあくまで参考。
**ボタンを押したら音が再生する**というものになります。

👇まずはhtmlファイルにボタンを配置
```.smilr.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebAudio API テスト</title>
</head>
<body>
    <h1>ボタンを押すと音が鳴る</h1>
    <button id="myButton">ボタン</button>

    <script src="smilescript.js"></script>
</body>
</html>
```

👇次にボタンを押したら音が鳴る仕組みを作る
```.smilescript.js
// 音を鳴らす関数（周波数と長さを指定）
function playTone(frequency, duration) {
    return new Promise(resolve => {
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        const oscillator = audioCtx.createOscillator();
        const gainNode = audioCtx.createGain();

        oscillator.type = "sine"; // 正弦波
        oscillator.frequency.value = frequency; // 周波数を設定
        gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime); // 音量調整

        oscillator.connect(gainNode);
        gainNode.connect(audioCtx.destination);

        oscillator.start();
        setTimeout(() => {
            oscillator.stop();
            resolve();
        }, duration);
    });
}

// 休符（無音時間を作る）
function rest(duration) {
    return new Promise(resolve => setTimeout(resolve, duration));
}

// 休符 → ド（C4）の音を鳴らす
async function playRestThenDo() {
    await rest(1000); // 休符（1秒）
    await playTone(261.63, 1000); // ドの音（1秒）
}

// ボタンがクリックされたときの処理
document.getElementById("myButton").addEventListener("click", () => {
    playRestThenDo();
});
```


## コードの解説（Javascript）
「〇〇でもわかる！」を目指してコードをかみ砕いてみます

#### :one: **ボタンを押したら音を鳴らす**
```js
document.getElementById("myButton").addEventListener("click", () => {
    playRestThenDo();
});
```
上からコードを見ていくと順番が「？」に感じることがあるのですが、まずは「ボタン」の処理からスタートします
ここで、ボタンがクリックされたときに `playRestThenDo()` を実行します。


#### :two: **音を鳴らす**
```js
// 休符 → ド（C4）の音を鳴らす
async function playRestThenDo() {
    await rest(1000); // 休符（1秒）
    await playTone(261.63, 1000); // ドの音（1秒）
}
```
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

> async function 宣言は、AsyncFunction オブジェクトを作成します。非同期関数が呼び出されるたびに、新しいプロミス (Promise) が返され、非同期関数によって返された値で解決されます。または、非同期関数内で捕捉されなかった例外で拒否されます。

「エイシンク　ファンクション」によって、`await` を使って処理の順番をコントロールしています。

`rest(1000)` は 1秒後に完了する `Promise` を返す → 「1秒待つ」
`playTone(261.63, 1000)` も `Promise` を返す → 「ドの音を1秒鳴らす」
**`await` を使うことで、「1秒待ってから、ドの音を1秒鳴らす」**
という動きが可能に。


#### :three: 実際に音を鳴らす関数での処理
```js
// 音を鳴らす関数（周波数と長さを指定）
function playTone(frequency, duration) {
    return new Promise(resolve => {
        // audioCtx = 音の作業場。
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        const oscillator = audioCtx.createOscillator();
        const gainNode = audioCtx.createGain();

        oscillator.type = "sine"; // 正弦波
        oscillator.frequency.value = frequency; // 周波数を設定
        gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime); // 音量調整

        oscillator.connect(gainNode);  // 発振器と音量調整を接続する
        gainNode.connect(audioCtx.destination);  // 音をスピーカーに送る

        oscillator.start();  // 音を鳴らす
        setTimeout(() => {
            oscillator.stop();  // 一定時間経ったら音を止める
            resolve();  // 終わったら次に進む
        }, duration);
    });
}

// 休符（無音時間を作る）
function rest(duration) {
    return new Promise(resolve => setTimeout(resolve, duration));
}
```

やたら`playTone`に関する部分が多いので、「？？？」が多くなりますが、少しずつ見ていくと意外と単純。公式の説明を見たうえで、ここではコメント形式で意味を記載します。

また、`swetTimeout`については以下を参考にすると次のような意味になります。

https://developer.mozilla.org/ja/docs/Web/API/Window/setTimeout

`rest(1000)` を実行
`new Promise(resolve => setTimeout(resolve, 1000))` が作られる
`setTimeout()` が 1秒後に `resolve()` を実行
`await` があるので 「1秒経つまで次の処理を止める」
1秒後に `Promise` が完了し、次の処理へ進む



## さいごに
今回実装にあたり、定数の名前で「？」が生じました。一般的に使用されているものですが、いままでなじみがなかったので知る事、発見が多いです。
今回の記事が何か参考になれば幸いです。