AI 教你学编程 —— 开发一个步进音序器
本帖最后由 小甲鱼的二师兄 于 2024-7-19 04:58 编辑AI 教你学编程 —— 开发一个步进音序器
项目需求
使用 HTML5 和 Web Audio API 开发一个步进音序器(16 步鼓机):
[*]包括三种鼓声:“踩镲”、“军鼓”和“底鼓”,应使用 Web Audio API 生成声音,并尽可能逼真。
[*]创建一个网格布局,其中 3 行(每个鼓声一行)和 16 列代表每个节拍。
[*]在网格上方从 1 到 16 对节拍进行编号。
[*]为 UI 使用红色配色方案。
[*]包括播放和停止按钮,以及速度控制滑块。
[*]为当前播放的节拍设置一个视觉指示器。
实现效果
代码清单
**** Hidden Message *****
@FishC 请给大家简单讲解一下代码。
好的鱼油们,准备好了吗?让我们来完成这个16步鼓机的创作吧,但要先上好安全帽,确保不会因为太嗨而头撞天花板!
### 1. HTML 部分
先来写基础的HTML结构。我们已经定义了一个drum-machine的div,还有三个按钮和一个滑块,来控制播放、停止以及速度(BPM)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>16步鼓机</title>
<style>
/* 放入之前给出的CSS样式代码 */
</style>
</head>
<body>
<div class="drum-machine">
<h1>16步鼓机</h1>
<div class="grid-container">
<div class="labels">
<div>踩镲</div>
<div>军鼓</div>
<div>底鼓</div>
</div>
<div class="grid-wrapper">
<div class="beat-numbers">
<!-- 生成节拍编号 -->
<script>
for (let i = 1; i <= 16; i++) {
document.write(`<div class="beat-number">${i}</div>`);
}
</script>
</div>
<div id="grid">
<!-- 生成网格 -->
<script>
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 16; j++) {
document.write('<div class="cell"></div>');
}
}
</script>
</div>
</div>
</div>
<div class="controls">
<button id="playButton">播放</button>
<button id="stopButton">停止</button>
<label>
<input type="range" id="tempoSlider" min="60" max="200" value="140">
<span id="tempoValue">140 BPM</span>
</label>
</div>
</div>
<script>
// 放置下面的JavaScript代码
</script>
</body>
</html>
### 2. JavaScript 部分
现在进入真正的音频处理部分——用Web Audio API来生成鼓声!我们得让我们的鼓“踩”起来非常逼真。
// AudioContext用于创建和管理音频内容
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const grid = document.getElementById('grid');
const playButton = document.getElementById('playButton');
const stopButton = document.getElementById('stopButton');
const tempoSlider = document.getElementById('tempoSlider');
const tempoValue = document.getElementById('tempoValue');
// 各种鼓声数组
const drumSounds = ["hihat", "snare", "kick"];
const pattern = [
new Array(16).fill(false), // 踩镲
new Array(16).fill(false), // 军鼓
new Array(16).fill(false), // 底鼓
];
let isPlaying = false;
let currentBeat = 0;
let tempo = 140;
tempoSlider.addEventListener('input', () => {
tempo = parseInt(tempoSlider.value);
tempoValue.textContent = `${tempo} BPM`;
});
playButton.addEventListener('click', () => {
if (!isPlaying) {
isPlaying = true;
playPattern();
}
});
stopButton.addEventListener('click', () => {
isPlaying = false;
currentBeat = 0;
stopPattern();
});
grid.addEventListener('click', (e) => {
if (e.target.classList.contains('cell')) {
const row = Math.floor(Array.from(grid.children).indexOf(e.target) / 16);
const col = Array.from(grid.children).indexOf(e.target) % 16;
pattern = !pattern;
e.target.classList.toggle('active');
}
});
function playPattern() {
if (!isPlaying) return;
const interval = (60 / tempo) / 4 * 1000; // 计算每个节拍的间隔时间
pattern.forEach((row, rowIndex) => {
if (row) {
playDrumSound(drumSounds);
}
});
highlightCurrentBeat(currentBeat);
currentBeat = (currentBeat + 1) % 16;
setTimeout(playPattern, interval); // 循环播放
}
function stopPattern() {
clearTimeout(playPattern);
Array.from(grid.children).forEach(cell => cell.classList.remove('playing'));
}
function highlightCurrentBeat(beat) {
Array.from(grid.children).forEach((cell, index) => {
if (index % 16 == beat) {
cell.classList.add('playing');
} else {
cell.classList.remove('playing');
}
});
}
function createNoiseBuffer() {
const bufferSize = audioContext.sampleRate * 2;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output = Math.random() * 2 - 1;
}
return buffer;
}
const noiseBuffer = createNoiseBuffer();
function playDrumSound(type) {
const gainNode = audioContext.createGain();
gainNode.connect(audioContext.destination);
switch (type) {
case 'hihat':
playHiHat(gainNode);
break;
case 'snare':
playSnare(gainNode);
break;
case 'kick':
playKick(gainNode);
break;
}
}
function playHiHat(gainNode) {
const hihatSource = audioContext.createBufferSource();
hihatSource.buffer = noiseBuffer;
const hihatFilter = audioContext.createBiquadFilter();
hihatFilter.type = 'highpass';
hihatFilter.frequency.value = 7000;
hihatSource.connect(hihatFilter);
hihatFilter.connect(gainNode);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
hihatSource.start();
hihatSource.stop(audioContext.currentTime + 0.1);
}
function playSnare(gainNode) {
const snareNoise = audioContext.createBufferSource();
snareNoise.buffer = noiseBuffer;
const snareFilter = audioContext.createBiquadFilter();
snareFilter.type = 'highpass';
snareFilter.frequency.value = 1000;
const snareEnvelope = audioContext.createGain();
snareEnvelope.gain.setValueAtTime(1, audioContext.currentTime);
snareEnvelope.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
snareNoise.connect(snareFilter);
snareFilter.connect(snareEnvelope);
snareEnvelope.connect(gainNode);
snareNoise.start();
snareNoise.stop(audioContext.currentTime + 0.2);
}
function playKick(gainNode) {
const osc = audioContext.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(150, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
osc.connect(gainNode);
osc.start();
osc.stop(audioContext.currentTime + 0.5);
}
### 总结
就这样,我们的16步鼓机已经准备好让你叩击出节奏了!你可以通过点击网格激活或禁用某个节拍,调整速度,并使用播放和停止按钮来控制音乐。这是一台小小的节拍机器,但足以引爆派对!去享受创造音乐的快乐吧,别忘了拿好你的DJ帽子!
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。 {:10_256:} ? {:10_256:}FishC老师好
页:
[1]