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