See the code below. How I understand things:
beat is a square wave oscillating between -1 and 1.
Connecting beat to multiplier.gain adds the square wave of beat to the default gain of 1. The result is a gain that oscillates between 0 and 2.
As tone is connected to multiplier, I expect to hear a tone of 440Hz for two seconds, then a pause for two seconds, then the tone again, and so on.
However, where I expect the gain to be 0, I still hear a tone, only muted. What am I doing wrong?
I tested with Chrome 74 and Firefox 66, both on Windows 10.
Code:
<!doctype html>
<meta charset=utf-8>
<script>
var context = new window.AudioContext();
var tone = context.createOscillator();
var beat = context.createOscillator();
beat.frequency.value = 0.25;
beat.type = "square";
var multiplier = context.createGain();
tone.connect(multiplier);
beat.connect(multiplier.gain);
multiplier.connect(context.destination);
tone.start();
beat.start();
</script>
<button onclick="context.resume()">Play</button>
The problem is that the 'square' type doesn't really oscillate between -1 and 1. The range is more or less from -0.848 to 0.848. Setting the GainNode's gain AudioParam to this value should work.
multiplier.gain.value = 0.848;
To see the actual output of an oscillator you could for example use Canopy. It can run Web Audio code and then visualizes the results.
If you do for example execute the following snippet, it will show you the corresponding waveform.
var osc = new OscillatorNode(context);
osc.type = "square";
osc.connect(context.destination);
osc.start();
I hope this helps.
Related
I'm trying to create a complex periodic sound with long period. I want to define frequencies as accurately as I can, so I'm using step sampleRate*0.5/tableLen. But I have some issues with large wave tables. The sound becomes distorted and loses high frequencies.
Here is a minimal example with ~440 Hz sine wave. When I use table with length 8192, the resulting sine wave is quite recognizable:
https://jsfiddle.net/zxqzntf0/
var gAudioCtx = new AudioContext();
var osc = gAudioCtx.createOscillator();
var tableSize = 8192;
var real = new Float32Array(tableSize);
var imag = new Float32Array(tableSize);
var freq = 440;
var step = gAudioCtx.sampleRate*0.5/tableSize;
real[Math.floor(freq/step)] = 1;
var wave = gAudioCtx.createPeriodicWave(real, imag, {disableNormalization: true});
osc.frequency.value = step;
osc.setPeriodicWave(wave);
osc.connect(gAudioCtx.destination);
osc.start();
But when I increase my table size, I'm getting something strange. Result is not a sine wave at all!
https://jsfiddle.net/0cc75nnm/
This problem reproduces in all browsers (Chrome, Firefox, Edge), so it doesn't seem to be a browser bug. But I've found nothing about this in documentation.
Added
I found that if oscillator frequency is a whole number >= 2 Hz, I have no any artifacts in resulting sound with table size 16384. I think it is quite acceptable for my needs for now. But someday I may want to create longer periods. If someone explains me why I get sound artifacts when step is less than 2 Hz, I will accept his answer.
There is an example of a complex sound melody that I generate in JavaScript:
https://jsfiddle.net/h9rfzrnL/1/
You're creating you periodic waves incorrectly. When filling the arrays for the periodic wave, assume the sample rate is 1. Then if you want an oscillator at a frequency of 440 Hz, set the oscillator frequency to 440 Hz.
Thus, for a sine wave, the real array should be all zeroes and the imaginary array is [0, 1]. (You're actually creating a cosine wave, but that doesn't really matter too much.)
I wrote the following code to play a note with a contoured lowpass filter:
var ac = new AudioContext;
var master = ac.createGain();
master.connect(ac.destination);
master.gain.value = 0.7;
var filter = ac.createBiquadFilter();
filter.connect(master);
filter.type = 'lowpass';
filter.Q.value = 2;
var osc = ac.createOscillator();
osc.connect(filter);
osc.type = 'square';
osc.frequency.value = 55;
var now = ac.currentTime;
osc.start(now);
//osc.stop(now+0.2);
filter.frequency.setValueAtTime(0, now);
filter.frequency.linearRampToValueAtTime(440, now+0.02);
filter.frequency.linearRampToValueAtTime(0, now+0.12);
The note sounds as expected (the filter opens quickly, then completely closes a bit more slowly) but at the very end of it I can hear a click. The lower the note, the louder the click.
I already tried uncommenting the commented line, as well as adding a contour to the master, but nothing worked.
Edit: By "adding contour to the master" I mean I tried ramping down the master gain to 0 exactly at the same time as the filter reaches 0. This would not work.
How can I prevent the click at the end of the note?
While, intuitively, lowering a filter to zero should cut off all signal, filters don't "cut off" all signal above the specified frequency. So you should expect some energy to be in the signal even with the filter you've created.
However, filters DO cut off signal in direct proportion to how far those signals are from the cutoff frequency. So it makes perfect sense that, the lower your signal is, the louder the pop of turning it off is (because lower frequencies are closer to your cutoff of zero and therefore less attenuated by the filter).
You can solve this problem by doing a linear ramp of the GAIN of the signal immediately before you schedule the signal to stop altogether. You can ramp down in the last millisecond or so in order to avoid a pop.
Describe what "adding a contour to the master" means.
In your example, I would ramp down the master gain to zero just before the filter frequency goes to 0.
I would like to try making a synth using JavaScript, but I can't find any basic examples on how to do so.
What I have figured out from research is that it appears to be possible and that you should use a Canvas Pixel Array rather than normal ECMA arrays
I've also found info in MDN Audio, and I have seen audio elements used for continuous playback by web radio players before, although I couldn't figure out how.
My goal is to make something which allows me to synthesize continuous sin waves and play them using my keyboard without using pre-made samples.
EDIT: One of the comments below pointed me in the right direction. I'm currently working on a solution, but if you would like to post one as well, feel free.
Here is a basic example from which anyone should be able to figure out how to play sine waves with their keyboard:
<script type="text/javascript">
//WARNING: VERY LOUD. TURN DOWN YOUR SPEAKERS BEFORE TESTING
// create web audio api context
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// create Oscillator node
var oscillator = audioCtx.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.value = 750; // value in hertz
oscillator.connect(audioCtx.destination);
oscillator.start();
//uncomment for fun
// setInterval(changeFreq, 100);
//choose a random interval from a list of consonant ratios
var intervals = [1.0, 0.5, 0.3333, 1.5, 1.3333, 2.0, 2.25]
function changeFreq() {
var intervalIndex = ~~(Math.random() * intervals.length);
var noteFreq = oscillator.frequency.value * intervals[intervalIndex];
//because this is random, make an effort to keep it in comfortable frequency range.
if(noteFreq > 1600)
noteFreq *= 0.5;
else if(noteFreq < 250)
noteFreq *= 2;
oscillator.frequency.value = noteFreq;
}
</script>
<body>
<button onclick="changeFreq()">Change Places!</button>
</body>
How can I detect if a sound is playing in the hearing range?
For example,
Show a silence icon if:
The range is lower than 20 Hz,
The range is higher than 20 kHz.
instead stream.paused is false.
You can use this pitch detection audio library.
Example:
var voice = new Wad({source : 'mic' });
var tuner = new Wad.Poly();
tuner.add(voice);
voice.play();
tuner.updatePitch() // The tuner is now calculating the pitch and note name of its input 60 times per second. These values are stored in tuner.pitch and tuner.noteName.
var logPitch = function(){
console.log(tuner.pitch, tuner.noteName)
requestAnimationFrame(logPitch)
};
logPitch();
// If you sing into your microphone, your pitch will be logged to the console in real time.
tuner.stopUpdatingPitch(); // Stop calculating the pitch if you don't need to know it anymore.
I don't know if you can find the hz with javascript. You'll probably have to use the web audio API. However, with javascript you can definitely check and set volume on a numeric scale. Check out the details and sample code here: http://www.w3schools.com/tags/av_prop_volume.asp
Example values:
1.0 is highest volume (100%. This is default)
0.5 is half volume (50%)
0.0 is silent (same as mute)
I've the following problem:
I analyse audio data using javascript and FFT. I can already write the FFT data into an array:
audioCtx = new AudioContext();
analyser = audioCtx.createAnalyser();
source = audioCtx.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioCtx.destination);
analyser.fftSize = 64;
var frequencyData = new Uint8Array(analyser.frequencyBinCount);
Every time I want to have new data I call:
analyser.getByteFrequencyData(frequencyData);
The variable "audio" is a mp3 file defined in HTML:
<audio id="audio" src="test.mp3"></audio>
So far so good.
My problem now is that I want to check if the current array "frequencyData" includes a specific frequency. For example: I place a 1000 Hz signal somewhere in the mp3 file and want to get a notification if this part of the mp3 file is currently in the array "frequencyData".
In a first step it would help me to solve the problem when the important part of the mp3 file only contains a 1000 Hz signal. In a second step I would also like to find the part if there is an overlay with music.
frequencyData is an array of amplitudes and each element of the array basically represents a range of frequencies. The size of each range is defined by the sample rate divided by the number of FFT points, 64 in your case. So if your sample rate was 48000 and your FFT size is 64 then each element covers a range of 48000/64 = 750 Hz. That means frequencyData[0] are the frequencies 0Hz-750Hz, frequencyData[1] is 750Hz-1500Hz, and so on. In this example the presence of a 1kHz tone would be seen as a peak in the first bin. Also, with such a small FFT you probably noticed that the resolution is very coarse. If you want to increase the frequency resolution you'll need to do a larger FFT.