Skip to content

Instantly share code, notes, and snippets.

@sonota88
Last active January 19, 2025 13:35
Show Gist options
  • Save sonota88/015f30ffd7d2d4c6dc047d5fe12d62ec to your computer and use it in GitHub Desktop.
Save sonota88/015f30ffd7d2d4c6dc047d5fe12d62ec to your computer and use it in GitHub Desktop.
Web Audio API: GainNode の調査
<!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>
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