Trying to create soothing hydrogen sound generator but web audio api is too limited or I'm missing something. No onbufferend or onrequestmoredata or similar. Only thing that exist is onended from AudioBufferSourceNode. Is what I'm trying to do not possible?
stackoverflow is whining about I should add more details because the post contains mostly code but I have no more details to add.
document.addEventListener("DOMContentLoaded", () => {
const button = document.querySelector('button');
const buttonStop = document.querySelector('#buttonStop');
let AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx;
// Mono
const channels = 1;
function init() {
audioCtx = new AudioContext();
}
buttonStop.onclick = function() {
audioCtx.close();
audioCtx = null;
}
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const hz_REAL_TIME_FREQUENCY = 440;
let dk;
let dPos = 0.0;
let firsttime = true;
const table = [];
function sum(t) {
if (firsttime) {
for (var i = 0; i < 9; ++i) {
let n = i + 2; // todo: this should continually increase, 2 -> infinite
table[i] = [];
table[i][0] = ((1 - 1/Math.pow( n , 2)) );
table[i][1] = ((1/4 - 1/Math.pow((n+1), 2)) );
table[i][2] = ((1/9 - 1/Math.pow((n+2), 2)) );
table[i][3] = (((1 / Math.pow(4, 2)) - 1/Math.pow((n+3), 2)) );
table[i][4] = (((1 / Math.pow(5, 2)) - 1/Math.pow((n+4), 2)) );
table[i][5] = (((1 / Math.pow(6, 2)) - 1/Math.pow((n+5), 2)) );
}
firsttime = false;
}
let sum_value = 0.0;
for (let i = 0; i < 9; ++i)
{
sum_value += Math.sin(table[i][0] * t);
sum_value += Math.sin(table[i][1] * t);
sum_value += Math.sin(table[i][2] * t);
sum_value += Math.sin(table[i][3] * t);
sum_value += Math.sin(table[i][4] * t);
sum_value += Math.sin(table[i][5] * t);
}
return sum_value;
}
button.onclick = function() {
if(!audioCtx) {
init();
dk = hz_REAL_TIME_FREQUENCY * 2 * Math.PI / audioCtx.sampleRate;
}
// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
let frameCount_buffersize = audioCtx.sampleRate * 2.0;
let myArrayBuffer = audioCtx.createBuffer(channels, frameCount_buffersize, audioCtx.sampleRate);
function fillAudioBuffer() {
// Fill the buffer with white noise;
//just random values between -1.0 and 1.0
for (let channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
let nowBuffering = myArrayBuffer.getChannelData(channel);
for (let i_sampleNumber = 0; i_sampleNumber < frameCount_buffersize; i_sampleNumber++) {
// audio needs to be in [-1.0; 1.0]
nowBuffering[i_sampleNumber] = clamp(sum(dPos) * 0.03, -1.0, 1.0);
dPos += dk;
}
//console.log(nowBuffering);
}
}
function continueSource() {
// Get an AudioBufferSourceNode.
// This is the AudioNode to use when we want to play an AudioBuffer
let source = audioCtx.createBufferSource();
// set the buffer in the AudioBufferSourceNode
fillAudioBuffer();
source.buffer = myArrayBuffer;
// connect the AudioBufferSourceNode to the
// destination so we can hear the sound
source.connect(audioCtx.destination);
// OR
/*let gain = audioCtx.createGain();
// Set parameters
gain.gain.value = 0.1;
// Connect graph
source.connect(gain);
gain.connect(audioCtx.destination);/**/
// start the source playing
source.start();
source.onended = () => {
source.disconnect(audioCtx.destination);
continueSource();
}
}
continueSource();
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Hydrogen sound</title>
</head>
<body>
<h1>Hydrogen sound</h1>
<button>Make hydrogen sound</button>
<button id="buttonStop">Stop</button>
</body>
</html>
Edit:
document.addEventListener("DOMContentLoaded", () => {
const button = document.querySelector('button');
const buttonStop = document.querySelector('#buttonStop');
let AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx;
// Mono
const channels = 1;
function init() {
audioCtx = new AudioContext();
}
buttonStop.onclick = function() {
audioCtx.close();
audioCtx = null;
}
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const hz_REAL_TIME_FREQUENCY = 440;
let dk;
let dPos = 0.0;
let firsttime = true;
const table = [];
function sum(t) {
if (firsttime) {
for (var i = 0; i < 9; ++i) {
let n = i + 2; // todo: this should continually increase, 2 -> infinite
table[i] = [];
table[i][0] = ((1 - 1/Math.pow( n , 2)) );
table[i][1] = ((1/4 - 1/Math.pow((n+1), 2)) );
table[i][2] = ((1/9 - 1/Math.pow((n+2), 2)) );
table[i][3] = (((1 / Math.pow(4, 2)) - 1/Math.pow((n+3), 2)) );
table[i][4] = (((1 / Math.pow(5, 2)) - 1/Math.pow((n+4), 2)) );
table[i][5] = (((1 / Math.pow(6, 2)) - 1/Math.pow((n+5), 2)) );
}
firsttime = false;
}
let sum_value = 0.0;
for (let i = 0; i < 9; ++i)
{
sum_value += Math.sin(table[i][0] * t);
sum_value += Math.sin(table[i][1] * t);
sum_value += Math.sin(table[i][2] * t);
sum_value += Math.sin(table[i][3] * t);
sum_value += Math.sin(table[i][4] * t);
sum_value += Math.sin(table[i][5] * t);
}
return sum_value;
}
button.onclick = function() {
if(!audioCtx) {
init();
dk = hz_REAL_TIME_FREQUENCY * 2 * Math.PI / audioCtx.sampleRate;
}
// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
let frameCount_buffersize = audioCtx.sampleRate * 2.0;
let myArrayBuffer = audioCtx.createBuffer(channels, frameCount_buffersize, audioCtx.sampleRate);
let myArrayBuffer2 = audioCtx.createBuffer(channels, frameCount_buffersize, audioCtx.sampleRate);
function fillAudioBuffer() {
// Fill the buffer with white noise;
//just random values between -1.0 and 1.0
for (let channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
let nowBuffering = myArrayBuffer.getChannelData(channel);
for (let i_sampleNumber = 0; i_sampleNumber < frameCount_buffersize; i_sampleNumber++) {
// audio needs to be in [-1.0; 1.0]
nowBuffering[i_sampleNumber] = clamp(sum(dPos) * 0.03, -1.0, 1.0);
dPos += dk;
}
//console.log(nowBuffering);
}
}
function fillAudioBuffer2() {
// Fill the buffer with white noise;
//just random values between -1.0 and 1.0
for (let channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
let nowBuffering = myArrayBuffer2.getChannelData(channel);
for (let i_sampleNumber = 0; i_sampleNumber < frameCount_buffersize; i_sampleNumber++) {
// audio needs to be in [-1.0; 1.0]
nowBuffering[i_sampleNumber] = clamp(sum(dPos) * 0.03, -1.0, 1.0);
dPos += dk;
}
//console.log(nowBuffering);
}
}
let i = 0;
fillAudioBuffer();
function continueSource() {
// Get an AudioBufferSourceNode.
// This is the AudioNode to use when we want to play an AudioBuffer
let source = audioCtx.createBufferSource();
// set the buffer in the AudioBufferSourceNode
if (i++ & 1) {
fillAudioBuffer();
source.buffer = myArrayBuffer2;
} else {
fillAudioBuffer2();
source.buffer = myArrayBuffer;
}
// connect the AudioBufferSourceNode to the
// destination so we can hear the sound
source.connect(audioCtx.destination);
// OR
/*let gain = audioCtx.createGain();
// Set parameters
gain.gain.value = 0.1;
// Connect graph
source.connect(gain);
gain.connect(audioCtx.destination);/**/
// start the source playing
source.start();
source.onended = () => {
source.disconnect(audioCtx.destination);
continueSource();
}
}
continueSource();
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Hydrogen sound</title>
</head>
<body>
<h1>Hydrogen sound</h1>
<button>Make hydrogen sound</button>
<button id="buttonStop">Stop</button>
</body>
</html>
Edit two. Buffer size increased (no needed) and fillAudioBuffer functions offloaded from main thread. This works. I didn't need to do this on my C++ version. Javascript is slower than I thought.
document.addEventListener("DOMContentLoaded", () => {
const button = document.querySelector('button');
const buttonStop = document.querySelector('#buttonStop');
let AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx;
// Mono
const channels = 1;
function init() {
audioCtx = new AudioContext();
}
buttonStop.onclick = function() {
audioCtx.close();
audioCtx = null;
}
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const hz_REAL_TIME_FREQUENCY = 440;
let dk;
let dPos = 0.0;
let firsttime = true;
const table = [];
function sum(t) {
if (firsttime) {
for (var i = 0; i < 9; ++i) {
let n = i + 2; // todo: this should continually increase, 2 -> infinite
table[i] = [];
table[i][0] = ((1 - 1/Math.pow( n , 2)) );
table[i][1] = ((1/4 - 1/Math.pow((n+1), 2)) );
table[i][2] = ((1/9 - 1/Math.pow((n+2), 2)) );
table[i][3] = (((1 / Math.pow(4, 2)) - 1/Math.pow((n+3), 2)) );
table[i][4] = (((1 / Math.pow(5, 2)) - 1/Math.pow((n+4), 2)) );
table[i][5] = (((1 / Math.pow(6, 2)) - 1/Math.pow((n+5), 2)) );
}
firsttime = false;
}
let sum_value = 0.0;
for (let i = 0; i < 9; ++i)
{
sum_value += Math.sin(table[i][0] * t);
sum_value += Math.sin(table[i][1] * t);
sum_value += Math.sin(table[i][2] * t);
sum_value += Math.sin(table[i][3] * t);
sum_value += Math.sin(table[i][4] * t);
sum_value += Math.sin(table[i][5] * t);
}
return sum_value;
}
button.onclick = function() {
if(!audioCtx) {
init();
dk = hz_REAL_TIME_FREQUENCY * 2 * Math.PI / audioCtx.sampleRate;
}
// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
let frameCount_buffersize = audioCtx.sampleRate * 20.0;
let myArrayBuffer = audioCtx.createBuffer(channels, frameCount_buffersize, audioCtx.sampleRate);
let myArrayBuffer2 = audioCtx.createBuffer(channels, frameCount_buffersize, audioCtx.sampleRate);
function fillAudioBuffer() {
// Fill the buffer with white noise;
//just random values between -1.0 and 1.0
for (let channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
let nowBuffering = myArrayBuffer.getChannelData(channel);
for (let i_sampleNumber = 0; i_sampleNumber < frameCount_buffersize; i_sampleNumber++) {
// audio needs to be in [-1.0; 1.0]
nowBuffering[i_sampleNumber] = clamp(sum(dPos) * 0.03, -1.0, 1.0);
dPos += dk;
}
//console.log(nowBuffering);
}
}
function fillAudioBuffer2() {
// Fill the buffer with white noise;
//just random values between -1.0 and 1.0
for (let channel = 0; channel < channels; channel++) {
// This gives us the actual array that contains the data
let nowBuffering = myArrayBuffer2.getChannelData(channel);
for (let i_sampleNumber = 0; i_sampleNumber < frameCount_buffersize; i_sampleNumber++) {
// audio needs to be in [-1.0; 1.0]
nowBuffering[i_sampleNumber] = clamp(sum(dPos) * 0.03, -1.0, 1.0);
dPos += dk;
}
//console.log(nowBuffering);
}
}
let i = 0;
fillAudioBuffer();
function continueSource() {
// Get an AudioBufferSourceNode.
// This is the AudioNode to use when we want to play an AudioBuffer
let source = audioCtx.createBufferSource();
// set the buffer in the AudioBufferSourceNode
if (i++ & 1) {
console.log('Using myArrayBuffer2', i);
setTimeout(() => fillAudioBuffer(), 0);
source.buffer = myArrayBuffer2;
} else {
console.log('Using myArrayBuffer', i);
setTimeout(() => fillAudioBuffer2(), 0);
source.buffer = myArrayBuffer;
}
// connect the AudioBufferSourceNode to the
// destination so we can hear the sound
source.connect(audioCtx.destination);
// OR
/*let gain = audioCtx.createGain();
// Set parameters
gain.gain.value = 0.1;
// Connect graph
source.connect(gain);
gain.connect(audioCtx.destination);/**/
// start the source playing
source.start();
source.onended = () => {
source.disconnect(audioCtx.destination);
continueSource();
}
}
continueSource();
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Hydrogen sound</title>
</head>
<body>
<h1>Hydrogen sound</h1>
<button>Make hydrogen sound</button>
<button id="buttonStop">Stop</button>
</body>
</html>