Web Audio API フルスタックライブラリ XSound.js

  • 73
    いいね
  • 0
    コメント

XSound.jsとは ?

Web Audio APIのライブラリの1つで,

  • サウンドの生成
  • ワンショットオーディオの再生
  • 楽曲データの再生
  • メディアエレメントの再生
  • WebRTCによるストリーミング
  • Web MIDI APIとの連携
  • MMLによる自動演奏
  • 10種類以上のエフェクター
  • ビジュアライゼーション
  • マルチトラックレコーディング
  • WebSocketによるセッション

などの豊富な機能を簡単に実装することができます.

また, これらの機能をフルに利用できるアプリケーションも公開されています.

Twitter公式チャンネル

使い方

ライブラリの読み込み

<script type="text/javascript" src="xsound.min.js"></script>

基本的な記述

XSound.jsにはXSoundとXという2つの関数オブジェクトが定義されています (Xは, XSoundのエイリアスです). XSound.jsにおける処理はこのオブジェクトが起点となります.

例えば, 1つのオシレーターを440 Hzで再生する場合の処理は以下のような感じになります.

X('oscillator').setup(true).ready(0, 0).start(440);

これをWeb Audio APIに翻訳すると,

const audiocontext = new AudioContext();
const oscillator   = audiocontext.createOscillator();

oscillator.connect(audiocontext.destination);

oscillator.frequency.value = 440;

oscillator.start(0);

となります. この程度の処理が1行で記述できます.

以降のセクションでは, それぞれの機能を実装するためのコードを解説していきます.

サウンドの生成

setupメソッド

利用するオシレーターの初期状態を設定します.
例えば, 利用するオシレーターが1つの場合で, かつ, アクティブな状態にする場合は,

X('oscillator').setup(true);

と記述します. もちろん, 複数のオシレーターを利用することも可能で, その場合はブーリアンの配列を指定します.

X('oscillator').setup([true, true, true, false]);

オシレーターの状態は後から変更することも可能で, 4つ目のオシレーターもtrue (アクティブ状態)にするには,

X('oscillator', 3).state(true);

または,

X('oscillator').get(3).state(true);

と記述します.

readyメソッド

readyメソッドはサウンドの開始時刻と終了時刻を相対的に指定するためのメソッドです.
ちなみに, 即時に生成して, イベントなどで停止する場合には不要なので省略してもOKです.

例えば, 5秒後に開始して, 10秒後に停止する場合は以下のように記述します.

X('oscillator').setup(true).ready(5, 10).start(440).stop();

startメソッド

サウンドを生成するためのメソッドで, 周波数 (の配列) を引数にとります.
また, XSoundのクラスメソッドであるtoFrequenciesメソッドを利用することで, 簡単に和音を生成できます.
toFrequenciesメソッドに指定するインデックス (の配列) は, ピアノ88鍵に対応しています.

// 4つのオシレーターを初期化
X('oscillator').setup([true, true, true, false]);

const base = 40;

// C major
X('oscillator').ready(0, 0).start(X.toFrequencies([base, (base + 4), (base + 7)]));

// C minor
X('oscillator').ready(0, 0).start(X.toFrequencies([base, (base + 3), (base + 7)]));

X('oscillator', 3).state(true);

// C7
X('oscillator').ready(0, 0).start(X.toFrequencies([base, (base + 4), (base + 7), (base + 10)]));

// Cm7
X('oscillator').ready(0, 0).start(X.toFrequencies([base, (base + 3), (base + 7), (base + 10)]));

stopメソッド

すべてのオシレーターを停止します.

X('oscillator').stop();

paramメソッド

パラメータを取得・設定するためのメソッドです.
オシレーターの場合は, マスターボリュームの取得・設定が可能です.

// Getter
const mastervolume = X('oscillator').param('mastervolume');

// Setter
X('oscillator').param({mastervolume : 0.5});

また, 個々のオシレーターごとのparamメソッドもあります.  
波形, ボリューム, オクターブ, ファインチューンがそれぞれ取得・設定可能です.

// Getter
const type   = X('oscillator', 0).param('type');
const volume = X('oscillator', 0).param('volume');
const octave = X('oscillator', 0).param('octave');
const fine   = X('oscillator', 0).param('fine');

// Setter
X('oscillator', 0).param({
    type   : 'sawtooth',
    volume : 0.5,
    octave : 1.0,
    fine   : 100
});

エンベロープジェネレータ

オシレーターではエンベロープジェネレータの使用が可能です.

// Getter
const a = X('oscillator').module('envelopegenerator').param('attack');
const d = X('oscillator').module('envelopegenerator').param('decay');
const s = X('oscillator').module('envelopegenerator').param('sustain');
const r = X('oscillator').module('envelopegenerator').param('release');

// Setter
X('oscillator').module('envelopegenerator').param({
    attack  : 0.5,
    decay   : 0.5,
    sustain : 0.5,
    release : 0.5
});

グライド

オシレーターではグライド (ポルタメント) の使用も可能です.

// Getter
const type = X('oscillator').module('glide').param('type');
const time = X('oscillator').module('glide').param('time');

// Setter
X('oscillator').module('glide').param({
    type : 'exponential',
    time : 5
});

ワンショットオーディオの再生

setupメソッド

ワンショットオーディオデータを取得し, AudioBufferインスタンスを生成する処理をします.
例えば, ドレミファソラシのワンショットオーディオをピアノ88鍵に対応させるには以下のように初期化します.

const BASE_URL = 'https://korilakkuma.github.io/X-Sound/resources/';

const oneshots = [
    `${BASE_URL}one-shot/piano-2/C.wav`,
    `${BASE_URL}one-shot/piano-2/D.wav`,
    `${BASE_URL}one-shot/piano-2/E.wav`,
    `${BASE_URL}one-shot/piano-2/F.wav`,
    `${BASE_URL}one-shot/piano-2/G.wav`,
    `${BASE_URL}one-shot/piano-2/A.wav`,
    `${BASE_URL}one-shot/piano-2/B.wav`,
];

const getBufferIndex = pianoIndex => {
    switch (parseInt((pianoIndex + 9) % 12)) {
        case  0 :
        case  1 :
            return 0;
        case  2 :
        case  3 :
            return 1;
        case  4 :
            return 2;
        case  5 :
        case  6 :
            return 3;
        case  7 :
        case  8 :
            return 4;
        case  9 :
        case 10 :
            return 5;
        case 11 :
            return 6;
        default :
            break;
    }
};

const calculateRate = pianoIndex => {
    const sharps  = [1, 4, 6, 9, 11, 13, 16, 18, 21, 23, 25, 28, 30, 33, 35, 37, 40, 42, 45, 47, 49, 52, 54, 57, 59, 61, 64, 66, 69, 71, 73, 76, 78, 81, 83, 85];
    const isSharp = (sharps.indexOf(pianoIndex) !== -1) ? true : false;

    let rate = 0;

    if ((pianoIndex >= 0) && (pianoIndex <= 2)) {
        rate = 0.0625;
    } else if ((pianoIndex >= 3) && (pianoIndex <= 14)) {
        rate = 0.125;
    } else if ((pianoIndex >= 15) && (pianoIndex <= 26)) {
        rate = 0.25;
    } else if ((pianoIndex >= 27) && (pianoIndex <= 38)) {
        rate = 0.5;
    } else if ((pianoIndex >= 39) && (pianoIndex <= 50)) {
        rate = 1;
    } else if ((pianoIndex >= 51) && (pianoIndex <= 62)) {
        rate = 2;
    } else if ((pianoIndex >= 63) && (pianoIndex <= 74)) {
        rate = 4;
    } else if ((pianoIndex >= 75) && (pianoIndex <= 86)) {
        rate = 8;
    } else if ((pianoIndex >= 87) && (pianoIndex <= 98)) {
        rate = 16;
    }

    if (isSharp) {
        rate *= Math.pow(2, (1 / 12));
    }

    return rate;
};

const createOneshotSettings = () => {
    const NUMBER_OF_ONESHOTS = 88;

    const settings = new Array(NUMBER_OF_ONESHOTS);

    for (let i = 0; i < NUMBER_OF_ONESHOTS; i++) {
        const setting = {
            buffer : 0,
            rate   : 1,
            loop   : false,
            start  : 0,
            end    : 0,
            volume : 1
        };

        setting.buffer = getBufferIndex(i);
        setting.rate   = calculateRate(i);

        settings[i] = setting;
    }

    return settings;
};

// ワンショットオーディオデータの取得とデコード
try {
    X('oneshot').setup({
        resources : oneshots,
        settings  : createOneshotSettings(),
        timeout   : 60000,
        success : (event, buffers) => {
            // 第1引数は, XMLHttpRequestProgressEventインスタンス
            // 第2引数は, AudioBufferインスタンスの配列
        },
        error : (event, textStatus) => {
            // 第1引数は, XMLHttpRequestProgressEventインスタンス, Errorインスタンス, nullのいずれか
            // 第2引数は, 文字列 'error', 'timeout', 'decode'のいずれか
        },
        progress : event => {
            // 第1引数は, XMLHttpRequestProgressEventインスタンス
        }
    });
} catch (error) {
    window.alert(error.message);
}

readyメソッド

readyメソッドはサウンドの開始時刻と終了時刻を相対的に指定するためのメソッドです.
ちなみに, 即時に生成して, イベントなどで停止する場合には不要なので省略してもOKです.

X('oneshot').ready(5, 10).start(48).stop(48);

startメソッド

ワンショットオーディオを再生するためのメソッドで, インデックスを引数にとります.
先ほどのドレミファソラシのワンショットオーディオをピアノ88鍵に対応させた初期化処理をしている場合, 440 Hzのラのワンショットオーディオを再生するには, インデックス48を指定してstartメソッドを実行します.

X('oneshot').ready(0, 0).start(48);

stopメソッド

ワンショットオーディオを停止するためのメソッドで, インデックスを引数にとります.
引数の要領はstartメソッドと同じです.

X('oneshot').stop(48);

paramメソッド

パラメータを取得・設定するためのメソッドです.
ワンショットオーディオの場合は, マスターボリュームとトランスポーズの取得・設定が可能です.

// Getter
const volume    = X('oneshot').param('mastervolume');
const transpose = X('oneshot').param('transpose');

// Setter
X('oneshot').param({
    mastervolume : 0.5,
    transpose    : 1.5
});

エンベロープジェネレータ

ワンショットオーディオではエンベロープジェネレータの使用が可能です.

// Getter
const a = X('oneshot').module('envelopegenerator').param('attack');
const d = X('oneshot').module('envelopegenerator').param('decay');
const s = X('oneshot').module('envelopegenerator').param('sustain');
const r = X('oneshot').module('envelopegenerator').param('release');

// Setter
X('oneshot').module('envelopegenerator').param({
    attack  : 0.5,
    decay   : 0.5,
    sustain : 0.5,
    release : 0.5
});

楽曲データの再生

setupメソッド

setupメソッドにはコールバック関数を指定します.

X('audio').setup({
    decode : arrayBuffer => {
        // decodeAudioDataの実行中に呼び出される
        // 第1引数は, オーディオデータのArrayBuffer
    },
    ready : buffer => {
        // decodeAudioDataの終了時に呼び出される
        // 第1引数は, AudioBufferインスタンス
    },
    start : (source, currentTime) => {
        // オーディオ再生直前に呼び出される
        // 第1引数は, AudioBufferSourceNodeインスタンス
        // 第2引数は, 楽曲の再生位置
    },
    stop : (source, currentTime) => {
        // オーディオの一時停止時に呼び出される
        // 第1引数は, AudioBufferSourceNodeインスタンス
        // 第2引数は, 楽曲の再生位置
    },
    update : (source, currentTime) => {
        // オーディオ再生中に定期的に呼び出される
        // 第1引数は, AudioBufferSourceNodeインスタンス
        // 第2引数は, 楽曲の再生位置
    },
    ended : (source, currentTime) => {
        // オーディオ停止時に呼び出される
        // 第1引数は, AudioBufferSourceNodeインスタンス
        // 第2引数は, 楽曲の再生位置
    },
    error : error => {
        // デコードオーディオデータ失敗時に呼び出される
        // 第1引数は, Errorインスタンスかnull
    }
});

readyメソッド

readyメソッドには楽曲データのArrayBufferを指定します.
XSoundのクラスメソッドである, ajaxメソッドやfileメソッドを利用すると簡単に取得できます.

// Ajax
X.ajax({
    url     : 'http://xxx.jp/sample.wav',
    timeout : 60000,
    success : (event, arrayBuffer) => {
        // 第1引数は, XMLHttpRequestProgressEventインスタンス
        // 第2引数は, ArrayBuffer

        // ArrayBuffer -> AudioBuffer -> AudioBufferSourceNode
        X('audio').ready.call(X('audio'), arrayBuffer);
    },
    error : (event, textStatus) => {
        // 第1引数は, XMLHttpRequestProgressEvent
        // 第2引数は, 文字列 'error', 'timeout' のどちらか
    },
    progress : event => {
        // 第1引数は, XMLHttpRequestProgressEventインスタンス
    }
});

// <input type="file">
document.querySelector('[type="file"]').addEventListener('change', event => {
    try {
        const file = X.file(
            event   : event,
            type    : 'ArrayBuffer',
            success : (event, arrayBuffer) => {
                // 第1引数は, FileReaderのonloadイベントオブジェクト
                // 第2引数は, ArrayBuffer

                // the instance of File -> ArrayBuffer -> AudioBuffer -> AudioBufferSourceNode
                X('audio').ready.call(X('audio'), arrayBuffer);
            },
            error : (event, error) => {
                // 第1引数は, FileReaderの"onerror"イベントオブジェクト
                // 第2引数は, FileReaderのエラーコード
            },
            progress : (event) => {
                // 第1引数は, FileReaderの"onprogress"イベントオブジェクト
            }
        });
    } catch (error) {
        window.alert(error.message);
    }
}, false);

startメソッド

楽曲の再生を開始します. 引数には, 楽曲の再生位置を指定できます.

X('audio').start(0);

stopメソッド

楽曲の再生を一時停止します.

X('audio').stop();

toggleメソッド

楽曲の再生と一時停止を簡単に実装可能にするメソッドです.

X('audio').toggle(X('audio').param('currentTime'));

paramメソッド

パラメータを取得・設定するためのメソッドです.
楽曲データの場合は, 様々なパラメータの取得・設定が可能です.

// Getter
const volume      = X('audio').param('mastervolume');
const rate        = X('audio').param('playbackRate');
const loop        = X('audio').param('loop');
const currentTime = X('audio').param('currentTime');
const duration    = X('audio').param('duration');
const smpleRate   = X('audio').param('sampleRate');
const channels    = X('audio').param('channels');

// Setter
X('audio').param({
    mastervolume : 0.5,
    playbackRate : 0.5,
    loop         : false,
    currentTime  : 60
});

ボーカルキャンセラ

楽曲データではボーカルキャンセラの使用が可能です.

// Getter
const depth = X('audio').module('vocalcanceler').param('depth');

// Setter
X('audio').module('vocalcanceler').param({depth : 0.5});

メディアエレメントの再生

setupメソッド

HTMLAudioElemet, または, HTMLVideoエレメント, 対象のメディフォーマットの配列,
そして, コールバック関数を指定します.

// HTMLMediaElementで定義されているイベント名がキー, リスナー関数がバリューのハッシュ
const listeners = {
    loadstart      : event => {/* do something ... */},
    loadedmetadata : event => {/* do something ... */},
    canplaythrough : event => {/* do something ... */}
    // ...
};

try {
    X('media').setup({
        media     : document.querySelector('audio'),
        formats   : ['wav', 'ogg', 'mp3'],
        listeners : listeners
    });
} catch (error) {
    // Cannot use HTMLMediaElement (for example, less than IE9)
    window.alert(error.message);
}

readyメソッド

メディアリソースのURL (パス) やObject URLを指定します.
URL (パス) の場合, 拡張子は除いて指定する必要があります.

X('media').ready('http://xxx.jp/sample');

startメソッド

メディアの再生を開始します. 引数には, メディアの再生位置を指定できます.

X('media').start(0);

stopメソッド

メディアの再生を一時停止します.

X('media').stop();

toggleメソッド

メディアの再生と一時停止を簡単に実装可能にするメソッドです.

X('media').toggle(X('media').param('currentTime'));

paramメソッド

パラメータを取得・設定するためのメソッドです.
メディアエレメントの場合は, 様々なパラメータの取得・設定が可能です.

// Getter
const volume      = X('media').param('mastervolume');
const rate        = X('media').param('playbackRate');
const currentTime = X('media').param('currentTime');
const loop        = X('media').param('loop');
const muted       = X('media').param('muted');
const controls    = X('media').param('controls');
const duration    = X('media').param('duration');
const channels    = X('media').param('channels');

// Setter
X('media').param({
    mastervolume : 0.5,
    playbackRate : 0.5,
    currentTime  : 60,
    loop         : false,
    muted        : false,
    controls     : false
});

ボーカルキャンセラ

メディアエレメントではボーカルキャンセラの使用が可能です.

// Getter
const depth = X('media').module('vocalcanceler').param('depth');

// Setter
X('media').module('vocalcanceler').param({depth : 0.5});

WebRTCによるストリーミング

setupメソッド

動画を利用するかどうかのフラグと, デバイスへのアクセスが成功した場合に実行されるコールバック関数と失敗した場合に実行されるコールバック関数を指定します.

const isVideo = true;

const streamCallback = stream => {
     // 第1引数は, MediaStreamインスタンス
};

const errorCallback = error => {
     // 第1引数は, NavigatorUserMediaErrorインスタンス
};

X('stream').setup(isVideo, streamCallback, errorCallback);

startメソッド

WebRTCによるストリーミングを開始します.
WebRTCを利用できないブラウザの場合は, 例外が発生します.

try {
    X('stream').start();
} catch (error) {
    window.alert(error.message);
}

stopメソッド

WebRTCによるストリーミングを停止します.

X('stream').stop();

paramメソッド

パラメータを取得・設定するためのメソッドです.
ストリームの場合は, マスターボリュームの取得・設定が可能です.

// Getter
const volume = X('stream').param('mastervolume');

// Setter
X('stream').param({mastervolume : 0.5}); 

ノイズゲート

ストリームではノイズゲートの使用が可能です.

// Getter
const level = X('stream').module('noisegate').param('level');

// Setter
X('stream').module('noisegate').param({level : 0.3});

Web MIDI APIとの連携

setupメソッド

システムエクスクルーシブメッセージを使用するかどうかのブーリアンと,
MIDIデバイスへのアクセスが成功した場合に実行されるコールバック関数と
失敗した場合に実行されるコールバック関数を指定します.

const sysex = true;

const successCallback = (midiAccess, inputs, outputs) => {
    // 第1引数は, MIDIAccessインスタンス
    // 第2引数は, MIDIInputインスタンス
    // 第3引数は, MIDIOutputインスタンス

    const MAX_VELOCITY       = 127;
    const NOTE_NUMBER_OFFSET = 21;

    const indexes = [];
    const volumes = [];

    const noteOn = (noteNumber, velocity) => {
        if ((noteNumber < NOTE_NUMBER_OFFSET) || (noteNumber > 127)) {
            return;
        }

        if ((velocity < 1) || (velocity > MAX_VELOCITY)) {
            return;
        }

        const targetIndex = noteNumber - NOTE_NUMBER_OFFSET;
        const volume      = velocity / MAX_VELOCITY;


        X('oneshot').reset(targetIndex, 'volume', volume)
                    .ready(0, 0)
                    .start(targetIndex);
    };

    const noteOff = (noteNumber, velocity) => {
        if ((noteNumber < NOTE_NUMBER_OFFSET) || (noteNumber > 127)) {
            return;
        }

        if ((velocity < 1) || (velocity > MAX_VELOCITY)) {
            return;
        }

        const targetIndex = noteNumber - NOTE_NUMBER_OFFSET;

        X('oneshot').stop(targetIndex)
                    .reset(targetIndex, 'volume', 1);
    };

    if (inputs.length > 0) {
        inputs[0].onmidimessage = event => {
            switch (event.data[0] & 0xf0) {
                case 0x90 :
                    noteOn(event.data[1], event.data[2]);
                    break;
                case 0x80 :
                    noteOff(event.data[1], event.data[2]);
                    break;
                default :
                    break;
            }
        };
    }
};

const errorCallback = error => {
    // 第1引数は, DOMExceptionインスタンス
    // do something ...
};

X('midi').setup(sysex, successCallback, errorCallback);

MMLによる自動演奏

setupメソッド

setupメソッドにはコールバック関数を指定します.

X('mml').setup({
    start : (sequence, index) => {
        // 音符の演奏開始時に呼び出される
        // 第1引数は, MMLの演奏情報を含んだハッシュで以下のプロパティをもちます
        //    sequence.indexes;      // ピアノ88鍵に対応させたインデックスの配列
        //    sequence.frequencies;  // 周波数の配列
        //    sequence.start;        // 音符の開始時間
        //    sequence.duration;     // 音符の持続時間
        //    sequence.stop;         // 音符の停止時間 (開始時間 + 持続時間)
        // 第2引数は, MMLの何番目の音符が再生されているかを表すインデックス
    },
    stop : (sequence, index) => {
        // 音符の停止時に呼び出される
        // 引数はstartコールバック関数と同じ
    },
    ended : () => {
        // MMLの演奏終了時に呼び出される
    },
    error : (error, note) => {
        // MMLの記述にエラーがあった場合に呼び出される
        // 第1引数は, 文字列 'TEMPO', 'OCTAVE', 'NOTE', 'MML' のいずれか
        // 第2引数は, エラー原因となったMMLの文字列
    }
});

readyメソッド

第1引数には, 演奏対象となる, X('oscillator') または, X('oneshot') を指定します.
第2引数には, MMLの文字列の配列を指定します.

const mainPart = 'T74O4AF+DB2AEB4G+4AF+C+2&AF+C+8F+8A8O5F+8AF+D2AEB4G+4 AF+D2BB4O6C+AE+2.&C+AE+8O5BG+16AF+16BG+E2G+B8AC+8G+B8EG+8F+C+A4.F+F+8G+G+8F+F+8G+G+4AF+C+2.BB4AEB2.&AEB8G+16F+16G+EB2R4';
const subPart  = 'T74O2B2O3C+2D1O2B2O3C+2D2E2O2A1E1F+2E2O3DD1EE1&EE1';

X('mml').ready(X('oneshot'), [mainPart, subPart]);

MMLの記述ルールはおおよそ標準に沿っていますが, 和音の記述が可能となっています.

MUSIC MML
Scale C D E F G A B
Duration 1 2 4 8 16 32 64 128 256
Triplet / Nonuplet 6 8 12 18 24 36 48 72 96 144 192
Sharp +, #
Flat -
Rest R
Dotted note .
Tie &
Octave O
Tempo T

startメソッド

MMLの演奏を開始します. 引数には, 演奏パートのインデックスを指定します.

const parts = X('mml').get();

for (let i = 0, len = parts.length; i < len; i++) {
    X('mml').start(i);
}

stopメソッド

MMLの演奏を停止します.

X('mml').stop();

オシレーターでマルチパート演奏する場合

オシレーターでマルチパート演奏する場合は, XSoundのクラスメソッドであるcloneメソッドを利用してX Sound関数オブジェクトを複製する必要があります.

例えば, 2つのパートをオシレーターで演奏するには, 以下のようなコードになります.

const C = X.clone();

const mainPart = 'T74O4AF+DB2AEB4G+4AF+C+2&AF+C+8F+8A8O5F+8AF+D2AEB4G+4 AF+D2BB4O6C+AE+2.&C+AE+8O5BG+16AF+16BG+E2G+B8AC+8G+B8EG+8F+C+A4.F+F+8G+G+8F+F+8G+G+4AF+C+2.BB4AEB2.&AEB8G+16F+16G+EB2R4';
const subPart  = 'T74O2B2O3C+2D1O2B2O3C+2D2E2O2A1E1F+2E2O3DD1EE1&EE1';

X('mml').ready(X('oscillator'), mainPart);
C('mml').ready(C('oscillator'), mainPart);

X('mml').start(0);
C('mml').start(0);

X('mixer').mix([X('oscillator'), C('oscillator')]);

(正直なところ, この仕様はなんとかしたいです…)

ちなみに, XSoundのクラスメソッドであるfreeメソッドを利用して, 不要なオブジェクトを削除することが可能です. 先ほどの複製では, オシレーターとMML以外は不要なので, それら以外を削除しておくとメモリの節約になります.

C.free([
    C('oneshot'),
    C('audio'),
    C('media'),
    C('fallback'),
    C('stream'),
    C('mixer'),
    C('midi')
]);

エフェクター

エフェクターが適用可能なオブジェクトは, 以下の6つです.

  • X('oscillator')
  • X('oneshot')
  • X('audio')
  • X('media')
  • X('stream')
  • X('mixer')

コンプレッサー

moduleメソッドでコンプレッサーオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// コンプレッサーはデフォルトでアクティブ状態
// X(source).module('compressor').state(true);

// Getter
const threshold = X(source).module('compressor').param('threshold');
const knee      = X(source).module('compressor').param('knee');
const ratio     = X(source).module('compressor').param('ratio');
const attack    = X(source).module('compressor').param('attack');
const release   = X(source).module('compressor').param('release');

// Setter
X(source).module('compressor').param({
    threshold : 24,
    knee      : 30,
    ratio     : 12,
    attack    : 0.003,
    release   : 0.25
});

ディストーション

moduleメソッドでディストーションオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// ディストーションはデフォルトで非アクティブ状態
X(source).module('distortion').state(true);

// Getter
const curve   = X(source).module('distortion').param('curve');
const samples = X(source).module('distortion').param('samples');
const drive   = X(source).module('distortion').param('drive');
const color   = X(source).module('distortion').param('color');
const tone    = X(source).module('distortion').param('tone');

// Setter
X(source).module('distortion').param({
    curve   : 'overdrive',
    samples : 4096,
    drive   : 0.5,
    color   : 4000,
    tone    : 4000
});

ワウ

moduleメソッドでワウオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// ワウはデフォルトで非アクティブ状態
X(source).module('wah').state(true);

// Getter
const cutoff    = X(source).module('wah').param('cutoff');
const depth     = X(source).module('wah').param('depth');
const rate      = X(source).module('wah').param('rate');
const resonance = X(source).module('wah').param('resonance');

// Setter
X(source).module('wah').param({
    cutoff    : 1000,
    depth     : 0.5,
    rate      : 5,
    resonance : 20
});

イコライザー

moduleメソッドでイコライザーオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// イコライザーはデフォルトで非アクティブ状態
X(source).module('equalizer').state(true);

// Getter
const bass     = X(source).module('equalizer').param('bass');
const middle   = X(source).module('equalizer').param('middle');
const treble   = X(source).module('equalizer').param('treble');
const presence = X(source).module('equalizer').param('presence');

// Setter
X(source).module('equalizer').param({
    bass     : 18,
    middle   : 18,
    treble   : 18,
    presence : 18
});

フィルタ

moduleメソッドでフィルタオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// フィルタはデフォルトで非アクティブ状態
X(source).module('filter').state(true);

// Getter
const type      = X(source).module('filter').param('type');
const frequency = X(source).module('filter').param('frequency');
const resonance = X(source).module('filter').param('Q');
const gain      = X(source).module('filter').param('gain');
const range     = X(source).module('filter').param('range');
const attack    = X(source).module('filter').param('attack');
const decay     = X(source).module('filter').param('decay');
const sustain   = X(source).module('filter').param('sustain');
const release   = X(source).module('filter').param('release');

// Setter

X(source).module('filter').param({
    type      : 'lowpass',
    frequency : 1000,
    Q         : 20,
    gain      : 18,
    range     : 0.5,
    attack    : 0.5,
    decay     : 0.5,
    sustain   : 0.5,
    release   : 0.5
});

オートパン

moduleメソッドでオートパンオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// オートパンはデフォルトで非アクティブ状態
X(source).module('autopanner').state(true);

// Getter
const depth = X(source).module('autopanner').param('depth');
const rate  = X(source).module('autopanner').param('rate');

// Setter
X(source).module('autopanner').param({
    depth : 0.5,
    rate  : 0.5
});

トレモロ

moduleメソッドでトレモロオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// トレモロはデフォルトで非アクティブ状態
X(source).module('tremolo').state(true);

// Getter
const depth = X(source).module('tremolo').param('depth');
const rate  = X(source).module('tremolo').param('rate');
const wave  = X(source).module('tremolo').param('wave');

// Setter
X(source).module('tremolo').param({
    depth : 0.5,
    rate  : 5,
    wave  : 'triangle'
});

リングモジュレーター

moduleメソッドでリングモジュレーターオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// リングモジュレーターはデフォルトで非アクティブ状態
X(source).module('ringmodulator').state(true);

// Getter
const depth = X(source).module('ringmodulator').param('depth');
const rate  = X(source).module('ringmodulator').param('rate');

// Setter
X(source).module('ringmodulator').param({
    depth : 0.5,
    rate  : 1000
});

フェイザー

moduleメソッドでフェイザーオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// フェイザーはデフォルトで非アクティブ状態
X(source).module('phaser').state(true);

// Getter
const stage     = X(source).module('phaser').param('stage');
const frequency = X(source).module('phaser').param('frequency');
const resonance = X(source).module('phaser').param('resonance');
const depth     = X(source).module('phaser').param('depth');
const rate      = X(source).module('phaser').param('rate');
const mix       = X(source).module('phaser').param('mix');
const feedback  = X(source).module('phaser').param('feedback');

// Setter
X(source).module('phaser').param({
    stage     : 8,
    frequency : 1000,
    resonance : 10,
    depth     : 0.5,
    rate      : 5,
    mix       : 0.5,
    feedback  : 0.5
});

フランジャー

moduleメソッドでフランジャーオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// フランジャーはデフォルトで非アクティブ状態
X(source).module('flanger').state(true);

// Getter
const time     = X(source).module('flanger').param('time');
const depth    = X(source).module('flanger').param('depth');
const rate     = X(source).module('flanger').param('rate');
const mix      = X(source).module('flanger').param('mix');
const tone     = X(source).module('flanger').param('tone');
const feedback = X(source).module('flanger').param('feedback');

// Setter
X(source).module('flanger').param({
    time     : 0.005,
    depth    : 0.5,
    rate     : 5,
    mix      : 0.5,
    tone     : 4000,
    feedback : 0.5
});

コーラス

moduleメソッドでコーラスオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// コーラスはデフォルトで非アクティブ状態
X(source).module('chorus').state(true);

// Getter
const time     = X(source).module('chorus').param('time');
const depth    = X(source).module('chorus').param('depth');
const rate     = X(source).module('chorus').param('rate');
const mix      = X(source).module('chorus').param('mix');
const tone     = X(source).module('chorus').param('tone');
const feedback = X(source).module('chorus').param('feedback');

// Setter
X(source).module('chorus').param({
    time     : 0.020,
    depth    : 0.05,
    rate     : 0.5,
    mix      : 0.5,
    tone     : 4000,
    feedback : 0.05
});

ディレイ

moduleメソッドでディレイオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// ディレイはデフォルトで非アクティブ状態
X(source).module('delay').state(true);

// Getter
const time     = X(source).module('delay').param('delayTime');
const dry      = X(source).module('delay').param('dry');
const wet      = X(source).module('delay').param('wet');
const tone     = X(source).module('delay').param('tone');
const feedback = X(source).module('delay').param('feedback');

// Setter
X(source).module('delay').param({
    delayTime : 0.500,
    dry       : 0.5,
    wet       : 0.5,
    tone      : 4000,
    feedback  : 0.5
});

リバーブ

リバーブの場合, インパルス応答のオーディオデータのAudioBufferインスタンスが必要となります.
リバーブオブジェクトのpresetメソッドを利用してAudioBufferインスタンスをセットします.

const BASE_URL = 'https://korilakkuma.github.io/X-Sound/resources/';

const rirs = [
    (BASE_URL + 'impulse-responses/s1_r1_b.wav'),
    (BASE_URL + 'impulse-responses/s1_r2_b.wav'),
    (BASE_URL + 'impulse-responses/s1_r3_b.wav'),
    (BASE_URL + 'impulse-responses/s1_r4_b.wav'),
    (BASE_URL + 'impulse-responses/s2_r1_b.wav'),
    (BASE_URL + 'impulse-responses/s2_r2_b.wav'),
    (BASE_URL + 'impulse-responses/s2_r3_b.wav'),
    (BASE_URL + 'impulse-responses/s2_r4_b.wav'),
    (BASE_URL + 'impulse-responses/s3_r1_b.wav'),
    (BASE_URL + 'impulse-responses/s3_r2_b.wav'),
    (BASE_URL + 'impulse-responses/s3_r3_b.wav'),
    (BASE_URL + 'impulse-responses/s3_r4_b.wav'),
];

const reverbs = [];

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

for (let i = 0, len = rirs.length; i < len; i++) {
    X.ajax({
        url     : rirs[i],
        timeout : 60000,
        success : (event, arrayBuffer) => {
            X.decode(X.get(), arrayBuffer, audioBuffer => {
                reverbs.push(audioBuffer);

                if (reverbs.length === len) {
                    X(source).module('reverb').preset(reverbs);
                }
            }, error => {
                // デコードエラーの場合
            });
        },
        error   : (event, textStatus) => {
            // Ajaxエラーの場合
        }
    });
}

インパルス応答のAudioBufferインスタンスがセットできれば, ほかのエフェクターと同様に,
moduleメソッドでリバーブオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.

// リバーブはデフォルトで非アクティブ状態
X(source).module('reverb').state(true);

// Getter
const dry  = X(source).module('reverb').param('dry');
const wet  = X(source).module('reverb').param('wet');
const tone = X(source).module('reverb').param('tone');
const type = X(source).module('reverb').param('type');
const rirs = X(source).module('reverb').param('rirs');

// Setter
X(source).module('reverb').param({
    dry  : 0.5,
    wet  : 0.5,
    tone : 4000
});

パンナー / リスナー

moduleメソッドでパンナーオブジェクトにアクセスし,
stateメソッドでアクティブ状態を切り替え, paramメソッドで値を取得・設定します.
パンナーをアクティブ状態にすることで, リスナーもアクティブ状態になります.

パンナー

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// パンナー / リスナーはデフォルトで非アクティブ状態
X(source).module('panner').state(true);

// Getter
const x = X(source).module('panner').param('x');
const y = X(source).module('panner').param('y');
const z = X(source).module('panner').param('z');

const ox = X(source).module('panner').param('ox');
const oy = X(source).module('panner').param('oy');
const oz = X(source).module('panner').param('oz');

const vx = X(source).module('panner').param('vx');
const vy = X(source).module('panner').param('vy');
const vz = X(source).module('panner').param('vz');

const refDistance   = X(source).module('panner').param('refDistance');
const maxDistance   = X(source).module('panner').param('maxDistance');
const rolloffFactor = X(source).module('panner').param('rolloffFactor');

const coneInnerAngle = X(source).module('panner').param('coneInnerAngle');
const coneOuterAngle = X(source).module('panner').param('coneOuterAngle');
const coneOuterGain  = X(source).module('panner').param('coneOuterGain');

const pannningModel = X(source).module('panner').param('panningModel');
const distanceModel = X(source).module('panner').param('distanceModel');

// Setter
X(source).module('panner').param({
    x  : 0,
    y  : 0,
    z  : 0,
    ox : 1,
    oy : 0,
    oz : 0,
    vx : 0,
    vy : 0,
    vz : 0,
    refDistance    : 1,
    maxDistance    : 10000,
    rolloffFactor  : 1,
    coneInnerAngle : 360,
    coneOuterAngle : 360,
    coneOuterGain  : 0,
    panningModel   : 'HRTF',
    distanceModel  : 'inverse'
});

リスナー

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// Getter
const dopplerFactor = X(source).module('listener').param('dopplerFactor');
const speedOfSound  = X(source).module('listener').param('speedOfSound');

const x = X(source).module('listener').param('x');
const y = X(source).module('listener').param('y');
const z = X(source).module('listener').param('z');

const fx = X(source).module('listener').param('fx');
const fy = X(source).module('listener').param('fy');
const fz = X(source).module('listener').param('fz');
const ux = X(source).module('listener').param('ux');
const uy = X(source).module('listener').param('uy');
const uz = X(source).module('listener').param('uz');

const vx = X(source).module('listener').param('vx');
const vy = X(source).module('listener').param('vy');
const vz = X(source).module('listener').param('vz');

// Setter
X(source).module('listener').param({
    dopplerFactor : 1,
    speedOfSound  : 343.3,
    x  : 0,
    y  : 0,
    z  : 0,
    fx : 0,
    fy : 0,
    fz : -1,
    ux : 0,
    uy : 1,
    uz : 0,
    vx : 0,
    vy : 0,
    vz : 0
});

ビジュアライゼーション

ビジュアライゼーションが可能なオブジェクトは, 以下の6つです.

  • X('oscillator')
  • X('oneshot')
  • X('audio')
  • X('media')
  • X('stream')
  • X('mixer')

Analyserオブジェクト

moduleメソッドでAnalyserオブジェクトにアクセスします.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

X(source).module('analyser');

domainメソッド

Analyserオブジェクトのメソッドで, 描画したい波形 (オーディオの波形, 時間領域の波形, 周波数領域の波形) に対応したオブジェクトを取得します.

X(source).module('analyser').domain('timeOverviewL');  // オーディオの波形 (左チャンネル)
X(source).module('analyser').domain('timeOverviewR');  // オーディオの波形 (右チャンネル)
X(source).module('analyser').domain('time');           // 時間領域の波形
X(source).module('analyser').domain('fft');            // 周波数領域の波形 (スペクトル)

paramメソッド

fftSizeやsmoothingTimeConstantなど, AnalyserNodeで定義されているプロパティを取得・設定します.

// Getter
const fftSize               = X(source).module('analyser').param('fftSize');
const minDecibels           = X(source).module('analyser').param('mindecibels');
const maxdecibels           = X(source).module('analyser').param('maxDecibels');
const smoothingTimeConstant = X(source).module('analyser').param('smoothingTimeConstant');

// Setter
X(source).module('analyser').param({
    fftSize               : 2048,
    mindecibels           : -100,
    maxDecibels           : -30,
    smoothingTimeConstant : 0.8
});

オーディオの波形を描画する

setupメソッド

引数には, HTMLCanvasElement, または, SVGElementを指定します.
サンプルコードでは, 左チャンネルを対象にしていますが, 右チャンネルでも同様です.

// Canvasで描画する場合
X('audio').module('analyser').domain('timeOverviewL').setup(document.querySelector('canvas'));

// SVGで描画する場合
X('audio').module('analyser').domain('timeOverviewL').setup(document.querySelector('svg'));

デフォルトでは, 波形描画は非アクティブ状態なので, stateメソッドでアクティブ状態にする必要があります.

X('audio').module('analyser').domain('timeOverviewL').state(true);

paramメソッド

描画スタイルを取得・設定するためのメソッドです.
設定可能なパラメータが非常に多いので, APIドキュメントを参考にして視覚的に理解していただければと思います.

const params = {
    shape        : 'line',
    wave         : 'rgba(0, 0, 255, 1.0)',
    grid         : 'rgba(255, 0, 0, 1.0)',
    currentTime  : 'rgba(255, 255, 255, 1.0)',
    text         : 'rgba(255, 255, 255, 1.0)',
    font         : {
        family : 'Arial',
        size   : '13px',
        style  : 'normal',
        weight : 'normal'
    },
    top          : 15,
    right        : 15,
    bottom       : 15,
    left         : 15,
    width        : 1.5,
    cap          : 'round',
    join         : 'miter',
    plotInterval : 0.0625,
    textInterval : 60
};

X('audio').module('analyser').domain('timeOverviewL').param(params);

時間領域の波形をリアルタイムで描画する

setupメソッド

引数には, HTMLCanvasElement, または, SVGElementを指定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// Canvasで描画する場合
X(source).module('analyser').domain('time').setup(document.querySelector('canvas'));

// SVGで描画する場合
X(source).module('analyser').domain('time').setup(document.querySelector('svg'));

デフォルトでは, 波形描画は非アクティブ状態なので, stateメソッドでアクティブ状態にする必要があります.

X(source).module('analyser').domain('time').state(true);

paramメソッド

描画スタイルを取得・設定するためのメソッドです.
設定可能なパラメータが非常に多いので, APIドキュメントを参考にして視覚的に理解していただければと思います.

const params = {
    interval     : 500,
    shape        : 'line',
    wave         : 'rgba(0, 0, 255, 1.0)',
    grid         : 'rgba(255, 0, 0, 1.0)',
    text         : 'rgba(255, 255, 255, 1.0)',
    font         : {
        family : 'Arial',
        size   : '13px',
        style  : 'normal',
        weight : 'normal'
    },
    top          : 15,
    right        : 15,
    bottom       : 15,
    left         : 15,
    width        : 1.5,
    cap          : 'round',
    join         : 'miter',
    textinterval : 60
};

X(source).module('analyser').domain('time').param(params);

周波数領域の波形 (スペクトル) をリアルタイムで描画する

引数には, HTMLCanvasElement, または, SVGElementを指定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

// Canvasで描画する場合
X(source).module('analyser').domain('fft').setup(document.querySelector('canvas'));

// SVGで描画する場合
X(source).module('analyser').domain('fft').setup(document.querySelector('svg'));

デフォルトでは, 波形描画は非アクティブ状態なので, stateメソッドでアクティブ状態にする必要があります.

X(source).module('analyser').domain('fft').state(true);

paramメソッド

描画スタイルを取得・設定するためのメソッドです.
設定可能なパラメータが非常に多いので, APIドキュメントを参考にして視覚的に理解していただければと思います.

const params = {
    interval     : 500,
    shape        : 'line',
    wave         : 'rgba(0, 0, 255, 1.0)',
    grid         : 'rgba(255, 0, 0, 1.0)',
    text         : 'rgba(255, 255, 255, 1.0)',
    font         : {
        family : 'Arial',
        size   : '13px',
        style  : 'normal',
        weight : 'normal'
    },
    top          : 15,
    right        : 15,
    bottom       : 15,
    left         : 15,
    width        : 1.5,
    cap          : 'round',
    join         : 'miter',
    range        : 256,
    textinterval : 1000
};

X(source).module('analyser').domain('fft').param(params);

マルチトラックレコーディング

レコーディングが可能なオブジェクトは, 以下の6つです.

  • X('oscillator')
  • X('oneshot')
  • X('audio')
  • X('media')
  • X('stream')
  • X('mixer')

setupメソッド

moduleメソッドでレコーダーオブジェクにアクセスし, setupメソッドの引数に必要なトラック数を指定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

X(source).module('recorder').setup(4);

readyメソッド

レコーディングのターゲットとするトラック番号を指定します.
トラック番号は0から始まるので, 例えば, setupメソッドで4トラックを指定した場合は,
0 〜 3までのいずれかのトラック番号を指定します.

X(source).module('recorder').ready(0);

また, アクティブになったトラック番号は, getActiveTrackメソッドで取得可能です.

X(source).module('recorder').setup(4);

console.log((source).module('recorder').getActiveTrack());  // -> -1

X(source).module('recorder').ready(0);

console.log((source).module('recorder').getActiveTrack());  // -> 0

startメソッド

レコーディングを開始します. startメソッドが実行されてから, stopメソッドが実行されるまでに
生成されたサウンドソースに対応するサウンド (X('stream') であればストリーミングされた音) がレコードされます.

X(source).module('recorder').start();
X(source).start(/* arguments */);

stopメソッド

レコーディングを停止します.

X(source).module('recorder').stop();

paramメソッド

左右のチャンネルのゲインを取得・設定するメソッドです.

// Getter
const gainL = X(source).module('recorder').param('gainL');
const gainR = X(source).module('recorder').param('gainR');

// Setter
X(source).module('recorder').param({
    gainL : 0.5,
    gainR : 0.5
});

createメソッド

レコードされたサウンドデータから, WAVEファイル (Object URL または, Data URL) を生成します.
第1引数には, 対象となるトラック番号を ('all'を指定すればすべてのトラックが対象になります),
第2引数には, チャンネル数 (1 or 2) を, 第3引数には, 量子化ビット (8 or 16) を指定します.

 const wave = X(source).module('recorder').create('all', 2, 16);

clearメソッド

トラックにレコードされているサウンドデータを消去します.
引数には, トラック番号を指定します ('all' を指定するとすべてのトラックをクリアします).

X(source).module('recorder').clear(0);

X(source).module('recorder').clear('all');

WebSocketによるセッション

WebSocketによるセッションが可能なオブジェクトは, 以下の6つです.

  • X('oscillator')
  • X('oneshot')
  • X('audio')
  • X('media')
  • X('stream')
  • X('mixer')

setupメソッド

引数には, WebSocketサーバーへの接続に必要となる情報と
WebSocketのイベントハンドラーを値としてもつハッシュを指定します.

const source = /* 'oscillator', 'oneshot', 'audio', 'media', 'stream', 'mixer' のいずれか */;

try {
    X(source).module('session').setup({
        tls   : true,  // trueの場合, 'wss:' を利用する
        host  : 'x-sound-server.herokuapp.com/',
        port  :'8000',
        path  : '/app/websocket/',
        open  : event => {
            // 第1引数は, WebSocketのonopenイベントオブジェクト
        },
        close : event => {
            // 第1引数は, WebSocketのoncloseイベントオブジェクト
        },
        error : event => {
            // 第1引数は, WebSocketのonerrorイベントオブジェクト
        }
    });
} catch (error) {
    window.alert(error.message);
}

デフォルトでは, セッションは非アクティブ状態なので, stateメソッドでアクティブ状態にする必要があります.

X(source).module('session').state(true);

startメソッド

セッションを開始します.

X(source).module('session').start();
X(source).start(/* arguments */);

セッションを停止するには, stateメソッドを利用します.

X(source).module('session').state(false);

closeメソッド

WebSocketサーバーとのコネクションを切断します.

X(source).module('session').close();

XSound.js

この投稿は WebAudio Web MIDI API Advent Calendar 201625日目の記事です。
  • この記事は以下の記事からリンクされています
  • ふぁぼ整理 5からリンク