I am creating a synth voice which contains three oscillators as you can see in the following snippet.
const ctx = new AudioContext();
const osc1 = ctx.createOscillator();
osc1.type = "sawtooth";
osc1.start();
const osc2 = ctx.createOscillator();
osc2.type = "sawtooth";
osc2.detune.setValueAtTime(1200, ctx.currentTime);
osc2.start();
const osc3 = ctx.createOscillator();
osc3.type = "sawtooth";
osc3.detune.setValueAtTime(1900, ctx.currentTime);
osc3.start();
const gain = ctx.createGain();
gain.gain.value = 0.2;
gain.connect(ctx.destination);
osc1.connect(gain);
osc2.connect(gain);
osc3.connect(gain);
osc1.frequency.value = 160;
osc2.frequency.value = 160;
osc3.frequency.value = 160;
To play a melody, I would like to control the frequency of all the oscillators by one AudioParam. I found that it should be done via ConstantSourceNode. But for some reason it is now playing different frequency then I set.
const ctx = new AudioContext();
const osc1 = ctx.createOscillator();
osc1.type = "sawtooth";
osc1.start();
const osc2 = ctx.createOscillator();
osc2.type = "sawtooth";
osc2.detune.setValueAtTime(1200, ctx.currentTime);
osc2.start();
const osc3 = ctx.createOscillator();
osc3.type = "sawtooth";
osc3.detune.setValueAtTime(1900, ctx.currentTime);
osc3.start();
const gain = ctx.createGain();
gain.gain.value = 0.2;
gain.connect(ctx.destination);
osc1.connect(gain);
osc2.connect(gain);
osc3.connect(gain);
const control = ctx.createConstantSource();
control.connect(osc1.frequency);
control.connect(osc2.frequency);
control.connect(osc3.frequency);
control.start();
control.offset.value = 160;
I noticed that minValue and maxValue are different for oscillator's frequency AudioParam and ConstantSource's offset AudioParam. Is it somehow scaled? Did I miss something?
I think the problem is that when you connect an AudioNode to an AudioParam the output signal of the AudioNode gets mixed with the intrinsic value of the AudioParam.
In you particular example this means that the actual frequency will be 600. The default frequency of an Oscillator is 440. This gets mixed with the value that comes from the ConstantSourceNode.
440 + 160 === 600
To achieve your desired result you can set the value of each oscillator to zero.
osc1.frequency.setValueAtTime(0, ctx.currentTime);
osc2.frequency.setValueAtTime(0, ctx.currentTime);
osc3.frequency.setValueAtTime(0, ctx.currentTime);
Here is a link to the part of the spec which describes this behavior: https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output
The default for an OscillatorNode frequency is 440, but you're setting the default offset to 160.
Related
I faced a browser policy problem "The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page."
I wanted to execute that below code when page loaded.
So, I tried to mock the user click gesture using a hidden button and load event listener but failed.
Is it possible??
let my_array = [];
function my_function() {
let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let analyser = audioCtx.createAnalyser();
let oscillator = audioCtx.createOscillator();
oscillator.type = "triangle"; // Set oscillator to output triangle wave
oscillator.connect(analyser); // Connect oscillator output to analyser input
let gain = audioCtx.createGain();
let scriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1);
analyser.connect(scriptProcessor); // Connect analyser output to scriptProcessor input
scriptProcessor.connect(gain); // Connect scriptProcessor output to gain input
gain.connect(audioCtx.destination); // Connect gain output to audiocontext destination
gain.gain.value = 0; // Disable volume
scriptProcessor.onaudioprocess = function (bins) {
bins = new Float32Array(analyser.frequencyBinCount);
analyser.getFloatFrequencyData(bins);
for (var i = 0; i < bins.length; i = i + 1) {
my_array.push(bins[i]);
}
analyser.disconnect();
scriptProcessor.disconnect();
gain.disconnect();
};
// audioCtx.resume().then(() => {
// oscillator.start(0);
// });
oscillator.start(0);
}
I'm trying to make oscillator play when mouse is on canvas and stop when it's not. However, with current code, it works just once after loading page, when mouse is on canvas second time error occurs:
"Uncaught InvalidStateError: Failed to execute 'start' on 'OscillatorNode': cannot call start more than once.
var ac = new window.AudioContext() || new window.webkitAudioContext();
var osc = ac.createOscillator();
var canvas1 = document.getElementById("canvas1");
canvas1.addEventListener("mouseover", playosc);
canvas1.addEventListener("mouseout", stoposc);
function playosc() {
osc.frequency.value = 440;
osc.connect(ac.destination);
osc.start();
}
function stoposc() {
osc.stop();
}
How to restart oscillator? Thanks.
You would need to create an Oscillator object each time as the OscillatorNodes are not reusable. Example:
var canvas1 = document.getElementById("canvas1");
canvas1.addEventListener("mouseover", playosc);
canvas1.addEventListener("mouseout", stoposc);
var ac = new window.AudioContext() || new window.webkitAudioContext();
var osc;
function playosc() {
osc = ac.createOscillator()
osc.frequency.value = 440;
osc.connect(ac.destination);
osc.start();
}
function stoposc() {
osc.stop();
}
Please refer to this excellent blog post for more guidance.
A simpler way is to have the oscillator start and connect to a gain node which you modulate between 0 and 1 to determine if the oscillator outputs or not.
var ac = new window.AudioContext() || new window.webkitAudioContext();
var osc = ac.createOscillator();
var gain = ac.createGain();
var canvas1 = document.getElementById("canvas1");
canvas1.addEventListener("mouseover", playosc);
canvas1.addEventListener("mouseout", stoposc);
gain.gain.value = 0;
osc.connect(ac.gain);
gain.connect(ac.destination);
osc.start();
function playosc() {
osc.frequency.value = 440;
gain.gain.value = 1;
}
function stoposc() {
gain.gain.value = 0;
}
You can also (and should) use the ramping. Plus you can modulate the gain further (such as based on mouse position within the canvas) and so on.
Yes it will use more CPU usage (since the oscillator is always running). However gain nodes of values at 0 cheat and don't actually multiply, they just spit out arrays of zeros.
I'm trying to make oscillator play when mouse is on canvas and stop when it's not. However, with current code, it works just once after loading page, when mouse is on canvas second time error occurs:
"Uncaught InvalidStateError: Failed to execute 'start' on 'OscillatorNode': cannot call start more than once.
var ac = new window.AudioContext() || new window.webkitAudioContext();
var osc = ac.createOscillator();
var canvas1 = document.getElementById("canvas1");
canvas1.addEventListener("mouseover", playosc);
canvas1.addEventListener("mouseout", stoposc);
function playosc() {
osc.frequency.value = 440;
osc.connect(ac.destination);
osc.start();
}
function stoposc() {
osc.stop();
}
How to restart oscillator? Thanks.
You would need to create an Oscillator object each time as the OscillatorNodes are not reusable. Example:
var canvas1 = document.getElementById("canvas1");
canvas1.addEventListener("mouseover", playosc);
canvas1.addEventListener("mouseout", stoposc);
var ac = new window.AudioContext() || new window.webkitAudioContext();
var osc;
function playosc() {
osc = ac.createOscillator()
osc.frequency.value = 440;
osc.connect(ac.destination);
osc.start();
}
function stoposc() {
osc.stop();
}
Please refer to this excellent blog post for more guidance.
A simpler way is to have the oscillator start and connect to a gain node which you modulate between 0 and 1 to determine if the oscillator outputs or not.
var ac = new window.AudioContext() || new window.webkitAudioContext();
var osc = ac.createOscillator();
var gain = ac.createGain();
var canvas1 = document.getElementById("canvas1");
canvas1.addEventListener("mouseover", playosc);
canvas1.addEventListener("mouseout", stoposc);
gain.gain.value = 0;
osc.connect(ac.gain);
gain.connect(ac.destination);
osc.start();
function playosc() {
osc.frequency.value = 440;
gain.gain.value = 1;
}
function stoposc() {
gain.gain.value = 0;
}
You can also (and should) use the ramping. Plus you can modulate the gain further (such as based on mouse position within the canvas) and so on.
Yes it will use more CPU usage (since the oscillator is always running). However gain nodes of values at 0 cheat and don't actually multiply, they just spit out arrays of zeros.
I'm trying to modulate saw frequency with the pulse from the slow oscillator sine. What am I doing wrong? jsFiddle here: https://jsfiddle.net/06ua9zLo/
window.AudioContext = window.AudioContext||window.webkitAudioContext;
var context = new AudioContext();
var saw = context.createOscillator(),
sine = context.createOscillator(),
sineGain = context.createGain();
//set up our oscillator types
saw.type = 'sawtooth';
sine.type = 'sine';
//set the amplitude of the modulation
sineGain.gain.value = 10;
//connect the dots
sine.connect(sineGain);
sineGain.connect(saw.frequency);
saw.connect(context.destination);
sine.start();
saw.start();
Your setup is fine. Just try changing the frequency of the modulator and add a little more gain, and you'll see that you're in fact doing FM. For example:
sine.frequency.value = 15;
sineGain.gain.value = 100;
will definitely tell you that it works.
I've been experimenting with sound design in the web audio API, and run into a point of frustration: I'd like to connect an oscillator to the frequency value on a lowpass biquad filter, which can easily be done to a gain node's gain value. Thus far, I've had no success, but no helpful error messages, either.
var ctx = new window.webkitAudioContext();
var currentKeysDown = [];
var filter = ctx.createBiquadFilter();
filter.connect(ctx.destination);
filter.type = 'lowpass';
filter.frequency.value = 400;
var osc = ctx.createOscillator();
osc.connect(filter);
osc.type = 'square';
osc.frequency.value = 200;
osc.start(0)
//Osc2 should modify the filter's frequency value.
var osc2 = ctx.createOscillator();
osc2.connect(filter.frequency);
osc2.frequency.value = 1;
osc2.start(0)
Does anyone know how to make this work, or perhaps there is some other work around?
Thanks!