Search code examples
javascriptweb-audio-api

Webaudio playing two oscillator sounds in a same time causes vibration sound


Using Web Audio, I generated a sound, but it sounds really bad (vibration). If I use very low gain, then it's better, but too quiet. Same as using low pass filter with very low value. What's the correct way to play multiple frequencies in a same time?

You can see that clicking "Play 1" button would work fine for an "A" note. Play 2 button would work for a "C" note. "Play both" button would sound very bad when both are played in a same time.

<html>
    <head>
        <title>Audio</title>
        <script>

            var context = null;
            var oscillatorNode1 = null;
            var oscillatorNode2 = null;

            function stop() {
                if ( oscillatorNode1 != null ) {
                    oscillatorNode1.stop(context.currentTime);
                    oscillatorNode1 = null;
                }
                if ( oscillatorNode2 != null ) {
                    oscillatorNode2.stop(context.currentTime);
                    oscillatorNode2 = null;
                }
            }

            function play1() {
                stop();
                if ( context === null) {
                    context = new AudioContext();
                }
                oscillatorNode1 = context.createOscillator();
                oscillatorNode1.type = 'sine';
                oscillatorNode1.frequency.value = 220;

                oscillatorNode1.connect(context.destination);
                oscillatorNode1.start();

                oscillatorNode2 = context.createOscillator();
                oscillatorNode2.type = 'sine';
                oscillatorNode2.frequency.value = 261.6255653005986;

                oscillatorNode2.connect(context.destination);
                oscillatorNode2.start();
            }

            function play2() {
                stop();
                if ( context === null) {
                    context = new AudioContext();
                }
                oscillatorNode1 = context.createOscillator();
                oscillatorNode1.type = 'sine';
                oscillatorNode1.frequency.value = 220;

                oscillatorNode1.connect(context.destination);
                oscillatorNode1.start();

            }

            function play3() {
                stop();
                if ( context === null) {
                    context = new AudioContext();
                }

                oscillatorNode2 = context.createOscillator();
                oscillatorNode2.type = 'sine';
                oscillatorNode2.frequency.value = 261.6255653005986;

                oscillatorNode2.connect(context.destination);
                oscillatorNode2.start();
            }

        </script>
    </head>
    <body>
        <form>
            <input type="button" onclick="play1()" value="Play both" />
            <input type="button" onclick="play2()" value="Play 1" />
            <input type="button" onclick="play3()" value="Play 2" />
            <input type="button" onclick="stop()" value="Stop" />
        </form>
    </body>
</html>

Solution

  • Using the "stereo" concept I mentioned above, I use context.createChannelMerger, then connect each sine wave to each output channel and it works pretty well. It seems to work better on one pair of speakers than another. It doesn't work that well on phone. However, adding gain to it make it very good on all speakers and phone I tested so far. I still wish to have a good solution on merging these that makes it sound like real air merge.

    I also just tried pasting the updated code into the Canopy, but it didn't sound good. One reason is that the audio context was already created and creating a new one works good. So the existing one doesn't work well on that Canopy page.

                    context = new AudioContext();
                    oscillatorNode1 = context.createOscillator();
                    oscillatorNode1.type = 'sine';
                    oscillatorNode1.frequency.value = 220;
    
                    var merger = context.createChannelMerger(2);
                    merger.connect(context.destination);
    
                    oscillatorNode1.connect(merger, 0, 0);
                    oscillatorNode1.start();
    
                    oscillatorNode2 = context.createOscillator();
                    oscillatorNode2.type = 'sine';
                    oscillatorNode2.frequency.value = 261.6255653005986;
    
                    oscillatorNode2.connect(merger, 0, 1);
                    oscillatorNode2.start();