小甲鱼的二师兄 发表于 2024-7-19 04:31:47

AI 教你学编程 —— 开发一个步进音序器

本帖最后由 小甲鱼的二师兄 于 2024-7-19 04:58 编辑

AI 教你学编程 —— 开发一个步进音序器

项目需求

使用 HTML5 和 Web Audio API 开发一个步进音序器(16 步鼓机):


[*]包括三种鼓声:“踩镲”、“军鼓”和“底鼓”,应使用 Web Audio API 生成声音,并尽可能逼真。
[*]创建一个网格布局,其中 3 行(每个鼓声一行)和 16 列代表每个节拍。
[*]在网格上方从 1 到 16 对节拍进行编号。
[*]为 UI 使用红色配色方案。
[*]包括播放和停止按钮,以及速度控制滑块。
[*]为当前播放的节拍设置一个视觉指示器。


实现效果




代码清单

**** Hidden Message *****

@FishC 请给大家简单讲解一下代码。

FishC 发表于 2024-7-19 04:36:31

好的鱼油们,准备好了吗?让我们来完成这个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 小助理,如未能正确解答您的问题,请继续追问。

stumblemodel 发表于 2024-7-19 10:26:37

{:10_256:}

风眠 发表于 2024-7-19 14:45:03

?

不二如是 发表于 2024-7-19 15:44:51

{:10_256:}FishC老师好
页: [1]
查看完整版本: AI 教你学编程 —— 开发一个步进音序器