Skip to content

Commit

Permalink
feat: MediaElementAudioSourceNode
Browse files Browse the repository at this point in the history
  • Loading branch information
Korilakkuma committed Apr 21, 2024
1 parent f3f502f commit 4e0c4d8
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
Binary file not shown.
196 changes: 196 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,202 @@ <h3>start メソッド / stop メソッド</h3>
</p>
</section>
</section>
<section id="section-media-element-audio-source-node">
<h2>MediaElementAudioSourceNode</h2>
<p>
Web Audio API において, 楽曲データに対してなんらかのオーディオ信号処理を適用したい場合に利用するのが
<b><code>MediaElementAudioSourceNode</code></b> です. もっと言ってしまえば, <code>HTMLMediaElement</code> (<code>HTMLAudioElement</code>
<code>HTMLVideoElement</code>) のオーディオデータに対するオーディオ信号処理を適用する場合に利用します.
</p>
<p>
<code>HTMLMediaElement</code> を音源にするので, <code>MediaElementAudioSourceNode</code> のコンストラクタ (もしくは, ファクトリメソッドの
<code>createMediaElementSource</code>) には, <code>HTMLMediaElement</code> を引数に指定する必要があります.
</p>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-html line-numbers">&lt;audio src=&quot;https://korilakkuma.github.io/Web-Music-Documentation/assets/medias/Schubert-Symphony-No8-Unfinished-1st-2020-VR.mp3&quot; controls /&gt;</code></pre>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-js line-numbers">const context = new AudioContext();

const audioElement = document.querySelector(&apos;audio&apos;);

const source = new MediaElementAudioSourceNode(context, { mediaElement: audioElement });

// If use `createMediaElementSource`
// const source = context.createMediaElementSource(audioElement);

source.connect(context.destination);</code></pre>
<p>
<code>MediaElementAudioSourceNode</code> インスタンス生成には 2 点注意すべき点があります. 上記のサンプルコードのように, `HTMLMediaElement` に HTML
パース時点で, <code>src</code> 属性にメディアファイルが指定されている場合は, 特に問題ありませんが, インタラクティブに, 例えば,
ユーザーのファイルシステムからメディアファイルを選択するような場合,
<b><code>HTMLMediaElement</code><code>loadstart</code> イベント発火以降にインスタンスを生成する必要があります</b> (逆に, HTML パース時点で
<code>src</code> 属性にメディアファイルを指定している場合, <code>loadstart</code> イベントは発火しないので注意が必要です).
<code>loadstart</code> イベント以降に発火するイベントであればよいので, <code>canplaythrough</code> イベントハンドラで
<code>MediaElementAudioSourceNode</code> インスタンスを生成してもよいでしょう.
</p>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-html line-numbers">&lt;input type=&quot;file&quot; /&gt;
&lt;audio controls /&gt;</code></pre>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-js line-numbers">const context = new AudioContext();

const inputElement = document.querySelector(&apos;input[type=&quot;file&quot;]&apos;);
const audioElement = document.querySelector(&apos;audio&apos;);

inputElement.addEventListener(&apos;change&apos;, (event) =&gt; {
const file = event.currentTarget.files[0];

audioElement.src = window.URL.createObjectURL(file);
});

audioElement.addEventListener(&apos;loadstart&apos;, () =&gt; {
const source = new MediaElementAudioSourceNode(context, { mediaElement: audioElement });

source.connect(context.destination);
});</code></pre>
<p>
もう 1 点は, 1 つの <code>HTMLMediaElement</code> に対して 1 つの <code>MediaElementAudioSourceNode</code> インスタンスが対応しているという点です.
例えば, <code>HTMLMediaElement</code><code>src</code> 属性のみを変更する場合,
<code>MediaElementAudioSourceNode</code> インスタンスを再度生成するとエラーが発生します (逆に, 別のオブジェクトとなる
<code>HTMLMediaElement</code> を指定する場合, <code>MediaElementAudioSourceNode</code> インスタンスを生成する必要があります).
</p>
<p>
したがって, 先ほどのサンプルコードだと, 2 回以上, ファイルを選択してしまうと, 同じ <code>HTMLAudioElement</code> に対して, 複数回
<code>MediaElementAudioSourceNode</code> インスタンスが生成されてエラーが発生してしまうので, 以下のように変更します.
</p>
<p>
また, <code>File API</code> から選択した楽曲データを, <code>HTMLMediaElement</code><code>src</code> 属性に指定する場合, Object URL を利用します
(<code>FileReader API</code> を使って Data URL を利用しても可能ですが, 実装が増えるだけなので, なんらかの理由がなければ
<code>createObjectURL</code> を利用して Object URL を設定するのがよいでしょう).
</p>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-html line-numbers">&lt;input type=&quot;file&quot; /&gt;
&lt;audio controls /&gt;</code></pre>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-js line-numbers">const context = new AudioContext();

const inputElement = document.querySelector(&apos;input[type=&quot;file&quot;]&apos;);
const audioElement = document.querySelector(&apos;audio&apos;);

let source = null;

inputElement.addEventListener(&apos;change&apos;, (event) =&gt; {
const file = event.currentTarget.files[0];

audioElement.src = window.URL.createObjectURL(file);

// If use Data URL,
//
// const reader = new FileReader();
//
// reader.onload = () =&gt; {
// audioElement.src = reader.result;
// };
//
// reader.readAsDataURL(file);
});

audioElement.addEventListener(&apos;loadstart&apos;, () =&gt; {
if (source === null) {
source = new MediaElementAudioSourceNode(context, { mediaElement: audioElement });
}

source.connect(context.destination);
});</code></pre>
<section id="section-media-element-audio-source-node-start-and-stop">
<h3>再生と停止</h3>
<p>
<code>MediaElementAudioSourceNode</code> に楽曲データを再生・停止するためのメソッドはありません. 再生や一時停止は, コンストラクタの引数に指定した
<code>HTMLMediaElement</code><code>play</code>, <code>pause</code> メソッド実行します. したがって, <code>OscillatorNode</code>
<code>AudioBufferSourceNode</code> のように使い捨てのノードではない, つまり, インスタンスを再度生成して
<code>AudioDestinationNode</code> に再度接続する必要もないので, この点は直感的な仕様と言えます.
</p>
<p>
<code>AudioDestinationNode</code> に接続すれば, 再生・停止することは簡単ですが, これでは
<code>HTMLMediaElement</code> をそのまま利用するほうが合理的なので, 簡単にオーディオ信号処理を適用していることがわかるように,
<code>BiquadFilterNode</code> を利用して Low-Pass Filter (低域通過フィルタ) を使ったサンプルコードです. カットオフ周波数を変更すると,
音の輪郭が変わることを確認してみてください (<code>BiquadFilterNode</code> に関しては, 別のセクションで詳細を解説します).
</p>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-html line-numbers">&lt;label for=&quot;range-cutoff&quot;&gt;cutoff&lt;/label&gt;
&lt;input type=&quot;range&quot; id=&quot;range-cutoff&quot; value=&quot;4000&quot; min=&quot;350&quot; max=&quot;8000&quot; step=&quot;1&quot; /&gt;
&lt;span id=&quot;print-cutoff-value&quot;&gt;4000 Hz&lt;/span&gt;
&lt;input type=&quot;file&quot; /&gt;
&lt;audio controls /&gt;</code></pre>
<pre
data-prismjs-copy="クリップボードにコピー"
data-prismjs-copy-success="コピーしました"
><code class="language-js line-numbers">const context = new AudioContext();

const inputElement = document.querySelector(&apos;input[type=&quot;file&quot;]&apos;);
const audioElement = document.querySelector(&apos;audio&apos;);

const inputCutoffElement = document.getElementById(&apos;range-cutoff&apos;);
const spanElement = document.getElementById(&apos;print-cutoff-value&apos;);

let source = null;

const lowpass = new BiquadFilterNode(context, { type: &apos;lowpass&apos;, frequency: 4000 });

inputElement.addEventListener(&apos;change&apos;, (event) =&gt; {
const file = event.currentTarget.files[0];

audioElement.src = window.URL.createObjectURL(file);

// If use Data URL,
//
// const reader = new FileReader();
//
// reader.onload = () =&gt; {
// audioElement.src = reader.result;
// };
//
// reader.readAsDataURL(file);
});

inputCutoffElement.addEventListener(&apos;input&apos;, (event) =&gt; {
lowpass.frequency.value = event.currentTarget.valueAsNumber;

spanElement.textContent = `${lowpass.frequency.value} Hz`;
});

// UI (by `controls` attribute) plays and pauses media
audioElement.addEventListener(&apos;loadstart&apos;, () =&gt; {
if (source === null) {
source = new MediaElementAudioSourceNode(context, { mediaElement: audioElement });
}

source.connect(lowpass);
lowpass.connect(context.destination);
});</code></pre>
</section>
<section id="section-media-element-audio-source-node-start-and-stop">
<h3>HTMLMediaElement と MediaElementAudioSourceNode</h3>
<p>
すでにサンプルコードを実行して, お気づきになったかもしれませんが,
<code>HTMLMediaElement</code> のプロパティやイベントハンドラはすべて利用することが可能です. <code>volume</code><code>muted</code>,
<code>playbackRate</code> は再生する楽曲データそのものに影響します. <code>autoplay</code><code>loop</code> は再生における UX に影響します. また,
実際のプロダクトでは, <code>loadedmetadata</code> イベント, <code>canplaythrough</code>イベント, <code>timeupdate</code> イベント,
<code>ended</code> イベントなどで, UI を更新するイベントハンドラを実行することも多いでしょう. このドキュメントですべてを解説することはできないので,
<a href="https://html.spec.whatwg.org/multipage/media.html" target="_blank" rel="noopener noreferrer">HTMLMediaElement</a>
の仕様などを参考にしてください.
</p>
</section>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/prismjs@latest/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@latest/plugins/line-numbers/prism-line-numbers.min.js"></script>
Expand Down

0 comments on commit 4e0c4d8

Please sign in to comment.