web页面能发出声音的方法有两种,一种是 autio
、 video
这些标签,另外一种就是音频上下文 AudioContext
。接下来我们看一下如何使用 AudioContext
,写简易钢琴和曲子。然后赶在七夕之前,给心爱的人作一首曲子吧
1. AudioContext如何发出声音
Mdn上面有具体介绍,我们这里只用下面几个
// 创建音频上下文 const audioCtx = new AudioContext(); // 创建音调控制对象 const oscillator = audioCtx.createOscillator(); // 创建音量控制对象 const gainNode = audioCtx.createGain(); // 音调音量关联 oscillator.connect(gainNode); // 音量和设备关联 gainNode.connect(audioCtx.destination); // 音调类型指定为正弦波。sin好听一些 oscillator.type = "sine"; // 设置音调频率(作曲的关键) oscillator.frequency.value = 400; // 先把当前音量设为0 gainNode.gain.setValueAtTime(0, audioCtx.currentTime); // 0.01秒时间内音量从0到1线性变化,突然变化的话很生硬 gainNode.gain.linearRampToValueAtTime( 1, audioCtx.currentTime + 0.01 ); // 声音开始 oscillator.start(audioCtx.currentTime); 复制代码
不,这还没完,直接copy这段代码放你js文件里面是没用的
这里还需要一步:地址栏中输入chrome://flags/#autoplay-policy,把autoplay-policy改成图中所示
ok,现在代码可以发出声音了,但是不会停下来,我们需要把音频停下来:
oscillator.stop(audioCtx.currentTime + 1); 复制代码
2. 简谱怎么来
现在我们知道怎么发出声音了,接下来是如何发出想要的声音,即是如何知道哆来咪发唆这些音所对应的频率是多少。
搜索关键词:简谱频率、乐谱频率。很快,就可以找到映射表格
我们要做的就是把 oscillator.frequency.value = 400;
这里的数字改成频率即可发出对应的声音。不妨先试一下
3. 根据简谱映射表输出对应的音频
上面那个表抄了一份,具体如下,代表着低中高的哆来咪发唆啦希
点击查看简谱数组
[[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]] 复制代码
只要把它和点击事件联系起来,就可以做到一个小钢琴了。先使用js渲染出每一个按键
// 简谱映射 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>`; // 用data-属性辅助 i++; } const container = document.createElement("section"); container.className = `container${level}`; // ------------------------ // 等下这里会加一些事件绑定 // ------------------------ container.innerHTML += res; document.body.appendChild(container); } // 渲染节点 renderBtns(0); renderBtns(1); renderBtns(2); 复制代码
点击查看样式
.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; } 复制代码
最终效果如下
绑定事件
renderBtns方法加上事件绑定,移动端只需手动换成touch系列事件。
// 音频开始 function handleStart({ target }, level) { const { dataset: { index } } = target; if (index !== undefined) { console.log(index, "start"); playAudio.call(target, index, level); // 后面加上playAudio的实现 } } // 停止音频 function handleStop({ target }) { const { dataset: { index } } = target; if (index !== undefined) { console.log(index, "stop"); stopAudio.call(target); // 后面加上stopAudio的实现 } } 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}