Last active
January 19, 2025 13:35
-
-
Save sonota88/015f30ffd7d2d4c6dc047d5fe12d62ec to your computer and use it in GitHub Desktop.
Web Audio API: GainNode の調査
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>GainNode test</title> | |
<script src="test.js"></script> | |
</head> | |
<body style="background: #f8f8f8; padding: 1rem;"> | |
<p>ページのどこかをクリック、またはスペースキー押下で再生開始</p> | |
<pre style=" | |
font-family: monospace; | |
font-size: 9px; | |
line-height: 125%; | |
"></pre> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const SAMPLE_RATE = 44100; | |
const DRAW_SIZE = 40; | |
// -------------------------------- | |
// 画面表示関連 | |
const View = {}; | |
// 波形表示の1行を作る | |
View._toLine = (val) => { | |
let line = ""; | |
const numChars = 96; | |
for (let i = 0; i < numChars; i++) { | |
const val2 = i / (numChars / 3) - 1.5; // -1.5<= .. <=1.5 | |
let fill = null; | |
if (val < 0) { | |
fill = (val <= val2 && val2 <= 0); | |
} else { | |
fill = (0 <= val2 && val2 <= val); | |
} | |
line += fill ? "|" : "."; | |
} | |
return line + " " + val; | |
}; | |
View.drawText = (text) => { | |
document.querySelector("pre").textContent = text; | |
}; | |
View.drawData = (data, i0) => { | |
const lines = []; | |
for (let i = i0; i < i0 + DRAW_SIZE; i++) { | |
const val = data[i]; | |
lines.push(View._toLine(val)); | |
} | |
const min = Math.min(...data); | |
const max = Math.max(...data); | |
lines.push(`max (${max})`); | |
lines.push(`min (${min})`); | |
View.drawText(lines.join("\n")); | |
}; | |
// -------------------------------- | |
// 波形データ生成 | |
function createBuffer(context) { | |
const durationMsec = 100; | |
const numSamples = Math.floor(SAMPLE_RATE * (durationMsec / 1000)); | |
const buf = context.createBuffer( | |
1, // numOfChannels | |
numSamples, // length | |
SAMPLE_RATE | |
); | |
const dataArray = buf.getChannelData(0); | |
const patterns1 = [ | |
0, -1.0, | |
0, -0.5, | |
0, 0.5, | |
0, 1.0 | |
]; | |
// patterns1 の各値を 4 サンプル分に伸ばす | |
const patterns2 = []; | |
patterns1.forEach(val => { | |
for (let i = 0; i < 4; i++) { | |
patterns2.push(val); | |
} | |
}); | |
for (let i = 0; i < dataArray.length; i++) { | |
const i2 = i % patterns2.length; | |
dataArray[i] = patterns2[i2]; | |
} | |
return buf; | |
} | |
function play() { | |
console.log("play"); | |
View.drawText(""); | |
const context = new AudioContext(); | |
const srcNode = new AudioBufferSourceNode( | |
context, | |
{ buffer: createBuffer(context) } | |
); | |
const analyserNode = new AnalyserNode(context); | |
const gainNode = new GainNode(context); | |
gainNode.gain.value = -1; | |
srcNode | |
.connect(gainNode) | |
.connect(analyserNode) | |
.connect(context.destination) | |
; | |
const dataArray = new Float32Array(analyserNode.frequencyBinCount); | |
let n = 0; | |
const timer = setInterval( | |
()=>{ | |
n++; | |
analyserNode.getFloatTimeDomainData(dataArray); | |
const i0 = dataArray.findIndex(val => val !== 0); // 音声の開始位置 | |
if (i0 >= 0) { | |
clearInterval(timer); | |
console.log(`interval done (${n}) (${i0})`); | |
View.drawData(dataArray, i0); | |
} | |
}, | |
0 | |
); | |
console.log("start"); | |
srcNode.start(0); | |
} | |
// -------------------------------- | |
// イベントハンドリング | |
window.addEventListener("click", ()=>{ | |
play(); | |
}); | |
window.addEventListener("keydown", (ev)=>{ | |
if (ev.code === "Space") { | |
play(); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment