|
|
<!DOCTYPE html> |
|
|
<html lang="ja"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>Audio Combine Speed Test</title> |
|
|
<style> |
|
|
body { font-family: sans-serif; padding: 20px; } |
|
|
button { margin: 10px 0; padding: 5px 10px; } |
|
|
.result { margin-top: 15px; } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<h1>音声合成速度比較テスト</h1> |
|
|
<p>対象ファイル: p.mp3, a.mp3, t.mp3, s.mp3, k.mp3</p> |
|
|
<button id="test-button">速度比較を実行</button> |
|
|
|
|
|
<div class="result"> |
|
|
<p id="loop-result">従来ループ方式: 未測定</p> |
|
|
<p id="offline-result">OfflineAudioContext方式: 未測定</p> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
|
|
const audioFiles = ['p', 'a', 't', 's', 'k']; |
|
|
const basePath = './t/'; |
|
|
|
|
|
|
|
|
async function loadAudioBuffer(file) { |
|
|
const res = await fetch(`${basePath}${file}.mp3`); |
|
|
const buf = await res.arrayBuffer(); |
|
|
return await audioContext.decodeAudioData(buf); |
|
|
} |
|
|
|
|
|
|
|
|
async function combineByLoop(buffers) { |
|
|
const maxDuration = Math.max(...buffers.map(b => b.duration)); |
|
|
const combined = audioContext.createBuffer( |
|
|
2, Math.ceil(audioContext.sampleRate * maxDuration), audioContext.sampleRate |
|
|
); |
|
|
|
|
|
buffers.forEach(buffer => { |
|
|
for (let ch = 0; ch < 2; ch++) { |
|
|
const input = buffer.getChannelData(ch % buffer.numberOfChannels); |
|
|
const output = combined.getChannelData(ch); |
|
|
for (let i = 0; i < input.length; i++) { |
|
|
output[i] += input[i]; |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
return combined; |
|
|
} |
|
|
|
|
|
|
|
|
async function combineByOffline(buffers) { |
|
|
const maxDuration = Math.max(...buffers.map(b => b.duration)); |
|
|
const offline = new OfflineAudioContext( |
|
|
2, Math.ceil(audioContext.sampleRate * maxDuration), audioContext.sampleRate |
|
|
); |
|
|
|
|
|
buffers.forEach(buffer => { |
|
|
const src = offline.createBufferSource(); |
|
|
src.buffer = buffer; |
|
|
const gain = offline.createGain(); |
|
|
gain.gain.value = 1.0; |
|
|
src.connect(gain).connect(offline.destination); |
|
|
src.start(0); |
|
|
}); |
|
|
|
|
|
return await offline.startRendering(); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('test-button').addEventListener('click', async () => { |
|
|
document.getElementById('loop-result').textContent = "従来ループ方式: 実行中..."; |
|
|
document.getElementById('offline-result').textContent = "Offline方式: 実行中..."; |
|
|
|
|
|
|
|
|
const buffers = await Promise.all(audioFiles.map(f => loadAudioBuffer(f))); |
|
|
|
|
|
|
|
|
let start = performance.now(); |
|
|
await combineByLoop(buffers); |
|
|
let end = performance.now(); |
|
|
document.getElementById('loop-result').textContent = |
|
|
`従来ループ方式: ${(end - start).toFixed(2)} ms`; |
|
|
|
|
|
|
|
|
start = performance.now(); |
|
|
await combineByOffline(buffers); |
|
|
end = performance.now(); |
|
|
document.getElementById('offline-result').textContent = |
|
|
`Offline方式: ${(end - start).toFixed(2)} ms`; |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|