A Web page can generate sound in two ways: autio and video tags, and AudioContext. Now let’s see how you can use an AudioContext to write simple pianos and tunes. Then make a song for your loved one before Tanabata

1. How does an AudioContext sound

Mdn has a specific introduction above, we only use the following several

      // Create audio context
      const audioCtx = new AudioContext();
      // Create a tone control object
      const oscillator = audioCtx.createOscillator();
      // Create a volume control object
      const gainNode = audioCtx.createGain();
      // Tone volume correlation
      oscillator.connect(gainNode);
      // Volume is associated with the device
      gainNode.connect(audioCtx.destination);
      // The tone type is specified as a sine wave. Sine of theta is better
      oscillator.type = "sine";
      // Set the tone frequency (key to composing music)
      oscillator.frequency.value = 400;
      // Set the current volume to 0
      gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
      // The volume changes linearly from 0 to 1 in 0.01 seconds. Sudden changes are very abrupt
      gainNode.gain.linearRampToValueAtTime(
        1,
        audioCtx.currentTime + 0.01
      );
      // The sound starts
      oscillator.start(audioCtx.currentTime);
Copy the code

No, that’s not all. It’s useless to copy this code directly into your JS file

One more step is required: enter chrome://flags/#autoplay-policy in the address bar and change the autoplay-policy to the one shown in the image

Ok, now the code can sound, but it won’t stop, we need to stop the audio:

oscillator.stop(audioCtx.currentTime + 1);
Copy the code

2. How to get simplified music

Now that we know how to make the sound, the next step is how to make the sound, which is how to know the frequency of do re mi.

Search keywords: spectral frequency, music frequency. Soon, you can find the mapping table

. We need to do is put the oscillator frequency. The value = 400; The number here is changed to the frequency to produce the corresponding sound. Might as well have a try first

3. Output corresponding audio according to the spectral mapping table

Here’s a copy of the top chart, do re mi fa ma La Xi, middle, low and high

Click to view the spectral array
[[261.63.293.67.329.63.349.23.391.99.440.493.88], [523.25.587.33.659.26.698.46.783.99.880.987.77], [1046.5.1174.66.1318.51.1396.92.1567.98.1760.1975.52]]
Copy the code

Just associate it with the click event, and you can make a baby piano. First render each key with JS

    // Simple spectral mapping
    const VOICE_MAP = {
      0: [261.63.293.67.329.63.349.23.391.99.440.493.88].1: [523.25.587.33.659.26.698.46.783.99.880.987.77].2: [1046.5.1174.66.1318.51.1396.92.1567.98.1760.1975.52]};function renderBtns(level) {
      let i = 0;
      let res = "";
      while (i < 7) {
        res += `<span class="btn level${level}" data-index=${i}>${i +
          1}</span>`; // Use the data- attribute to assist
        i++;
      }
      const container = document.createElement("section");
      container.className = `container${level}`;
      // ------------------------
      // There will be some event binding later
      // ------------------------
      container.innerHTML += res;
      document.body.appendChild(container);
    }

    // Render the node
    renderBtns(0);
    renderBtns(1);
    renderBtns(2);

Copy the code
Click to view styles
      .btn {
        cursor: pointer;
        display: inline-block;
        width: 100px;
        height: 30px;
        line-height: 30px;
        user-select: none;
        text-align: center;
        border: 1px #a12d21 solid;
        margin: 2px;
      }

      .level0::after {
        content: ".";
        position: relative;
        top: 4px;
        left: -7px;
      }

      .level2::before {
        content: ".";
        position: relative;
        top: -16px;
        left: 7px;
      }

Copy the code

The end result is as follows

The binding event

RenderBtns method with event binding, mobile side just manually switch to touch series of events.

    // The audio starts
    function handleStart({ target }, level) {
      const {
        dataset: { index }
      } = target;
      if(index ! = =undefined) {
        console.log(index, "start");
        playAudio.call(target, index, level); // Add the playAudio implementation to the end}}// Stop the audio
    function handleStop({ target }) {
      const {
        dataset: { index }
      } = target;
      if(index ! = =undefined) {
        console.log(index, "stop");
        stopAudio.call(target); // The implementation of stopAudio is appended}}function renderBtns(level) {
      let i = 0;
      let res = "";
      while (i < 7) {
        res += `<span class="btn level${level}" data-index=${i}>${i +
          1}</span>`;
        i++;
      }
      const container = document.createElement("section");
      container.className = `container${level}`;
      // Pass in e and level, where level refers to a low, middle or high note
      const particalStart = e= > handleStart(e, level);
      container.addEventListener("mousedown", e => {
        particalStart(e);
        container.addEventListener("mouseout", handleStop);
      });
      container.addEventListener("mouseup", handleStop);
      container.innerHTML += res;
      document.body.appendChild(container);
    }
Copy the code

Why call? In this way, dom nodes can be one-to-one bound to events and audio

4. Play audio and stop audio

    // Audio context
    const audioCtx = new AudioContext();

    function playAudio(index, level) {
      // If it was playing before, delete the previous audio
      this.gainNode &&
        this.gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
      this.oscillator && this.oscillator.stop(audioCtx.currentTime + 1);
      // Create a tone control object
      this.oscillator = audioCtx.createOscillator();
      // Create a volume control object
      this.gainNode = audioCtx.createGain();
      // Tone volume correlation
      this.oscillator.connect(this.gainNode);
      // Volume is associated with the device
      this.gainNode.connect(audioCtx.destination);
      // The tone type is specified as a sine wave. Sine of theta is better
      this.oscillator.type = "sine";
      // Set the tone frequency
      this.oscillator.frequency.value = VOICE_MAP[level][index]; // Read the corresponding spectral frequency
      // Set the current volume to 0
      this.gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
      // The volume changes linearly from 0 to 1 in 0.01 seconds
      this.gainNode.gain.linearRampToValueAtTime(
        1,
        audioCtx.currentTime + 0.01
      );
      // The sound starts
      this.oscillator.start(audioCtx.currentTime);
    }

    function stopAudio() {
      this.gainNode &&
        this.gainNode.gain.exponentialRampToValueAtTime(
          0.001,
          audioCtx.currentTime + 0.8
        );
      // Stop the sound in 0.8 seconds
      this.oscillator && this.oscillator.stop(audioCtx.currentTime + 0.8);
      this.oscillator = this.gainNode = null;
    }
Copy the code
All the above JS code
    const VOICE_MAP = {
      0: [261.63.293.67.329.63.349.23.391.99.440.493.88].1: [523.25.587.33.659.26.698.46.783.99.880.987.77].2: [1046.5.1174.66.1318.51.1396.92.1567.98.1760.1975.52]};function handleStart({ target }, level) {
      const {
        dataset: { index }
      } = target;
      if(index ! = =undefined) {
        console.log(index, "start"); playAudio.call(target, index, level); }}function handleStop({ target }) {
      const {
        dataset: { index }
      } = target;
      if(index ! = =undefined) {
        console.log(index, "stop"); stopAudio.call(target); }}function renderBtns(level) {
      let i = 0;
      let res = "";
      while (i < 7) {
        res += `<span class="btn level${level}" data-index=${i}>${i +
          1}</span>`;
        i++;
      }
      const container = document.createElement("section");
      container.className = `container${level}`;
      const particalStart = e= > handleStart(e, level);
      container.addEventListener("mousedown", e => {
        particalStart(e);
        container.addEventListener("mouseout", handleStop);
      });
      container.addEventListener("mouseup", handleStop);
      container.innerHTML += res;
      document.body.appendChild(container);
    }

    renderBtns(0);
    renderBtns(1);
    renderBtns(2);

    // Audio context
    const audioCtx = new AudioContext();

    function playAudio(index, level) {
      // If it was playing before, delete the previous audio
      this.gainNode &&
        this.gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
      this.oscillator && this.oscillator.stop(audioCtx.currentTime + 1);
      // Create a tone control object
      this.oscillator = audioCtx.createOscillator();
      // Create a volume control object
      this.gainNode = audioCtx.createGain();
      // Tone volume correlation
      this.oscillator.connect(this.gainNode);
      // Volume is associated with the device
      this.gainNode.connect(audioCtx.destination);
      // The tone type is specified as a sine wave. Sine of theta is better
      this.oscillator.type = "sine";
      // Set the tone frequency
      this.oscillator.frequency.value = VOICE_MAP[level][index];
      // Set the current volume to 0
      this.gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
      // The volume changes linearly from 0 to 1 in 0.01 seconds
      this.gainNode.gain.linearRampToValueAtTime(
        1,
        audioCtx.currentTime + 0.01
      );
      // The sound starts
      this.oscillator.start(audioCtx.currentTime);
    }

    function stopAudio() {
      // Stop sound after 0.8 seconds
      this.gainNode &&
        this.gainNode.gain.exponentialRampToValueAtTime(
          0.001,
          audioCtx.currentTime + 0.8
        );
      this.oscillator && this.oscillator.stop(audioCtx.currentTime + 0.8);
      this.oscillator = this.gainNode = null;
    }
Copy the code

Now it’s a little piano, and you can play your own songs. So, the question is, if you want to play a song, how do you press the buttons? Search: XXX simple spectrum, on the play can

5. Auto play

Of course, for programmers certainly want to do automatic. Now that we know how to output the audio we want, it’s time to save the actual song as a JS data structure and output it using the AudioContext API. My implementation simply reuses the event binding code above and uses a script to trigger native events. Of course there are many other ways to do it.

    // Let's start with a sleep
    function sleep(delay = 80) {
      return new Promise(r= >
        setTimeout((a)= > {
          r();
        }, delay)
      );
    }
    /** * @params arr array * @example * {level: 0, index: 0} bass do * {stop: true} next loop nothing * {delay: True} The next loop does nothing */
    async function diyPlay(arr) {
      let cursor = 0;
      const a = [...arr];
      const containers = document.querySelectorAll("section");
      let ele;
      // Walk through the array of songs one by one
      while (arr.length) {
        // A delay will prevent the last note from coming to an abrupt halt
        await sleep(300);
        const current = a.shift();
        // Leave a delay interface, that is, to extend the previous tone
        if (current && current.delay) {
          continue;
        }
        // Next button, stop the previous tone
        if (ele) {
          // Manually use js to trigger native events to stop audio
          const evPre = document.createEvent("MouseEvents");
          evPre.initMouseEvent("mouseout".true.true.window);
          ele.dispatchEvent(evPre);
        }
        if(! arr.length || ! current) {return;
        }
        // 
        if (current.stop) {
          continue;
        }
        await sleep(50); // Add a bit of delay to make multiple consecutive identical sounds more natural
        const ev = document.createEvent("MouseEvents");
        ele = containers[current.level].children[current.index - 1];
        // Manually invoke js to trigger native events to start audio
        if (ele) {
          ev.initMouseEvent("mousedown".true.true.window); ele.dispatchEvent(ev); }}}Copy the code

How do YOU turn a simple spectrum into a usable data structure

For example:

[{level: 1.index: 3 }, 
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 1.index: 7 },
      { delay: true }, // 7 will be delayed
      { level: 1.index: 7 }, 
      { level: 1.index: 6 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { delay: true }, // 6 is connected to 6, delay
      { stop: true }, // Stop for a moment
]
Copy the code

As a rule, we search for any song and copy it and then we can code it out

So, let’s start with this douyin song:

    // Wait for the subway
    diyPlay([{"level":2."index":3}, {"level":2."index":4}, {"level":2."index":5}, {"level":2."index":5}, {"level":2."index":5}, {"level":2."index":3}, {"level":2."index":2}, {"level":2."index":2}, {"level":2."index":5}, {"delay":true}, {"stop":true}, {"stop":true}, {"level":2."index":1}, {"level":2."index":1}, {"level":1."index":7}, {"level":2."index":3}, {"level":2."index":3}, {"level":2."index":1}, {"level":1."index":7}, {"delay":true}, {"level":2."index":3}, {"delay":true}, {"stop":true}, {"stop":true}, {"level":1."index":6}, {"level":2."index":1}, {"level":2."index":1}, {"level":1."index":6}, {"level":1."index":5}, {"level":1."index":5}, {"level":2."index":1}, {"delay":true}, {"stop":true}, {"stop":true}, {"level":2."index":4}, {"level":2."index":3}, {"level":2."index":1}, {"level":2."index":2}, {"level":2."index":3}, {"delay":true}, {"level":2."index":2}, {"stop":true}, {"stop":true}, {"level":2."index":5}, {"level":2."index":5}, {"level":2."index":5}, {"level":2."index":3}, {"level":2."index":2}, {"delay":true}, {"level":2."index":5}, {"level":2."index":5}, {"level":2."index":2}, {"stop":true}, {"stop":true}, {"level":2."index":1}, {"level":2."index":3}, {"level":2."index":3}, {"level":2."index":1}, {"level":1."index":7}, {"delay":true}, {"level":2."index":3}, {"stop":true}, {"stop":true}, {"level":1."index":6}, {"level":2."index":1}, {"level":2."index":1}, {"level":2."index":6}, {"level":1."index":5}, {"delay":true}, {"level":1."index":6}, {"level":2."index":1}, {"level":2."index":2}, {"level":2."index":3}, {"stop":true}, {"stop":true}, {"level":2."index":4}, {"level":2."index":3}, {"level":2."index":1}, {"level":2."index":2}, {"delay":true}, {"delay":true}, {"stop":true}]);
Copy the code
Simple Notation Code for Little Luck
// It was written in a hurry, with some inaccuracies
diyPlay([
      { level: 1.index: 3 }, // I hear rain falling on the green grass
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 7 }, 
      { level: 1.index: 6 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { delay: true },
      { stop: true },
      { level: 1.index: 6 }, // I hear the bell ringing in the distance
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { level: 1.index: 7 },
      { level: 2.index: 3 },
      { level: 2.index: 3 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 5 }, 
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { delay: true },

      { level: 1.index: 3 },// But I don't hear you
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { level: 2.index: 1 },
      { delay: true },

      { level: 1.index: 7 }, 
      { delay: true },
      { level: 1.index: 7 },
      { level: 1.index: 6 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { delay: true },

      { level: 1.index: 6 }, // Call my name carefully
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { delay: true },
      { stop: true },
      { stop: true },
      { level: 1.index: 3 },// I don't understand feelings when I fall in love with you
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 7 },
      { level: 1.index: 6 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { delay: true },
      { stop: true },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { level: 1.index: 7 },
      { level: 2.index: 3 },
      { level: 2.index: 3 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 5 },
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { delay: true },
      { stop: true },
      { level: 1.index: 3 },
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 7 },
      { level: 1.index: 6 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { delay: true },
      { stop: true },
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { level: 2.index: 3 },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { delay: true },
      { delay: true },
      { stop: true },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { delay: true },
      { level: 1.index: 7 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { delay: true },
      { level: 2.index: 2 },
      { stop: true },
      { stop: true },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 6 },
      { stop: true },
      { stop: true },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { delay: true },
      { level: 1.index: 3 },
      { level: 1.index: 5 },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { delay: true },
      { delay: true },
      { stop: true },
      { stop: true },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 1.index: 5 },
      { level: 1.index: 5 },
      { level: 1.index: 1 },
      { level: 1.index: 3 },
      { level: 1.index: 2 },
      { level: 1.index: 6 },
      { delay: true },
      { stop: true },
      { stop: true },
      { level: 1.index: 6 },
      { delay: true },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { level: 1.index: 6 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 1.index: 6 },
      { level: 2.index: 1 },
      { level: 1.index: 6 },
      { delay: true },
      { stop: true },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 2.index: 2 },

      { delay: true },
      { delay: true },
      { stop: true },
      { level: 1.index: 5 },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { level: 2.index: 2 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 1.index: 5 },
      { level: 2.index: 2 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 1.index: 5 },
      { level: 2.index: 2 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 2.index: 2 },
      { level: 2.index: 3 },
      { level: 2.index: 4 },
      // { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 1.index: 7 },
      // { stop: true },
      { level: 2.index: 1 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 2.index: 1 },
      { delay: true },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { level: 1.index: 7 },
      { level: 1.index: 7 },
      { level: 2.index: 3 },
      { level: 2.index: 5 }, 
      { level: 2.index: 3 },
      { level: 2.index: 1 },
      { level: 1.index: 7 }, 
      { level: 1.index: 6 }, 
      { level: 2.index: 4 },
      { level: 2.index: 4 },
      { delay: true },
      { level: 2.index: 5 },
      { level: 2.index: 4 },
      { level: 2.index: 3 },
      { level: 1.index: 5 },
      { level: 2.index: 3 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 4 },
      { level: 2.index: 3 },
      { level: 2.index: 1 },
      { level: 1.index: 4 },
      { delay: true },
      { level: 2.index: 2 },
      { level: 2.index: 2 },
      { delay: true },
      { level: 2.index: 2 },
      { delay: true },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 2 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 1.index: 5 },
      { level: 2.index: 2 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 1.index: 5 },
      { level: 2.index: 2 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 4 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 2.index: 1 },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 2.index: 1 },
      { delay: true },
      { level: 1.index: 3 },
      { level: 1.index: 6 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 7 },
      { level: 1.index: 7 },
      { level: 2.index: 3 },
      { level: 2.index: 5 },
      { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 1 },
      { level: 1.index: 7 },
      { delay: true },
      { level: 1.index: 6 },
      { level: 2.index: 4 },
      { level: 2.index: 4 },
      { delay: true },
      { stop: true },
      { level: 2.index: 5 },
      { level: 2.index: 4 },
      { level: 2.index: 3 },
      { level: 2.index: 5 },
      { level: 2.index: 3 },
      { delay: true },
      { level: 2.index: 3 },
      { delay: true },
      { stop: true },
      { level: 2.index: 4 },
      { level: 2.index: 3 },
      { level: 2.index: 1 },
      { level: 2.index: 4 },
      { level: 2.index: 2 },
      { level: 2.index: 2 },
      { delay: true },
      { delay: true },
      { level: 2.index: 3 },
      { level: 2.index: 1 },
      { level: 2.index: 1 },
      { level: 2.index: 3 },
      { level: 2.index: 2 },
      { delay: true },
      { level: 2.index: 1 },
      { delay: true},]);Copy the code

Pay attention to the public account “Different front-end”, learn front-end from a different perspective, grow fast, play with the latest technology, explore all kinds of black technology