鱼C论坛

 找回密码
 立即注册
查看: 581|回复: 4

[技术交流] AI 教你学编程 —— 开发一个步进音序器

[复制链接]
发表于 2024-7-19 04:31:47 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

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

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


项目需求

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

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


实现效果

屏幕截图 2024-07-19 043056.png


代码清单

游客,如果您要查看本帖隐藏内容请回复


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

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-7-19 04:36:31 | 显示全部楼层
好的鱼油们,准备好了吗?让我们来完成这个16步鼓机的创作吧,但要先上好安全帽,确保不会因为太嗨而头撞天花板!

### 1. HTML 部分
先来写基础的HTML结构。我们已经定义了一个drum-machine的div,还有三个按钮和一个滑块,来控制播放、停止以及速度(BPM)。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>16步鼓机</title>
  7.     <style>
  8.         /* 放入之前给出的CSS样式代码 */
  9.     </style>
  10. </head>
  11. <body>
  12.     <div class="drum-machine">
  13.         <h1>16步鼓机</h1>
  14.         <div class="grid-container">
  15.             <div class="labels">
  16.                 <div>踩镲</div>
  17.                 <div>军鼓</div>
  18.                 <div>底鼓</div>
  19.             </div>
  20.             <div class="grid-wrapper">
  21.                 <div class="beat-numbers">
  22.                     <!-- 生成节拍编号 -->
  23.                     <script>
  24.                         for (let i = 1; i <= 16; i++) {
  25.                             document.write(`<div class="beat-number">${i}</div>`);
  26.                         }
  27.                     </script>
  28.                 </div>
  29.                 <div id="grid">
  30.                     <!-- 生成网格 -->
  31.                     <script>
  32.                         for (let i = 0; i < 3; i++) {
  33.                             for (let j = 0; j < 16; j++) {
  34.                                 document.write('<div class="cell"></div>');
  35.                             }
  36.                         }
  37.                     </script>
  38.                 </div>
  39.             </div>
  40.         </div>
  41.         <div class="controls">
  42.             <button id="playButton">播放</button>
  43.             <button id="stopButton">停止</button>
  44.             <label>
  45.                 <input type="range" id="tempoSlider" min="60" max="200" value="140">
  46.                 <span id="tempoValue">140 BPM</span>
  47.             </label>
  48.         </div>
  49.     </div>
  50.     <script>
  51.         // 放置下面的JavaScript代码
  52.     </script>
  53. </body>
  54. </html>
复制代码


### 2. JavaScript 部分
现在进入真正的音频处理部分——用Web Audio API来生成鼓声!我们得让我们的鼓“踩”起来非常逼真。

  1. // AudioContext用于创建和管理音频内容
  2. const audioContext = new (window.AudioContext || window.webkitAudioContext)();
  3. const grid = document.getElementById('grid');
  4. const playButton = document.getElementById('playButton');
  5. const stopButton = document.getElementById('stopButton');
  6. const tempoSlider = document.getElementById('tempoSlider');
  7. const tempoValue = document.getElementById('tempoValue');

  8. // 各种鼓声数组
  9. const drumSounds = ["hihat", "snare", "kick"];
  10. const pattern = [
  11.   new Array(16).fill(false), // 踩镲
  12.   new Array(16).fill(false), // 军鼓
  13.   new Array(16).fill(false), // 底鼓
  14. ];
  15. let isPlaying = false;
  16. let currentBeat = 0;
  17. let tempo = 140;

  18. tempoSlider.addEventListener('input', () => {
  19.     tempo = parseInt(tempoSlider.value);
  20.     tempoValue.textContent = `${tempo} BPM`;
  21. });

  22. playButton.addEventListener('click', () => {
  23.     if (!isPlaying) {
  24.         isPlaying = true;
  25.         playPattern();
  26.     }
  27. });

  28. stopButton.addEventListener('click', () => {
  29.     isPlaying = false;
  30.     currentBeat = 0;
  31.     stopPattern();
  32. });

  33. grid.addEventListener('click', (e) => {
  34.     if (e.target.classList.contains('cell')) {
  35.         const row = Math.floor(Array.from(grid.children).indexOf(e.target) / 16);
  36.         const col = Array.from(grid.children).indexOf(e.target) % 16;
  37.         pattern[row][col] = !pattern[row][col];
  38.         e.target.classList.toggle('active');
  39.     }
  40. });

  41. function playPattern() {
  42.     if (!isPlaying) return;
  43.    
  44.     const interval = (60 / tempo) / 4 * 1000; // 计算每个节拍的间隔时间

  45.     pattern.forEach((row, rowIndex) => {
  46.         if (row[currentBeat]) {
  47.             playDrumSound(drumSounds[rowIndex]);
  48.         }
  49.     });

  50.     highlightCurrentBeat(currentBeat);
  51.     currentBeat = (currentBeat + 1) % 16;

  52.     setTimeout(playPattern, interval); // 循环播放
  53. }

  54. function stopPattern() {
  55.     clearTimeout(playPattern);
  56.     Array.from(grid.children).forEach(cell => cell.classList.remove('playing'));
  57. }

  58. function highlightCurrentBeat(beat) {
  59.     Array.from(grid.children).forEach((cell, index) => {
  60.         if (index % 16 == beat) {
  61.             cell.classList.add('playing');
  62.         } else {
  63.             cell.classList.remove('playing');
  64.         }
  65.     });
  66. }

  67. function createNoiseBuffer() {
  68.     const bufferSize = audioContext.sampleRate * 2;
  69.     const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
  70.     const output = buffer.getChannelData(0);
  71.     for (let i = 0; i < bufferSize; i++) {
  72.         output[i] = Math.random() * 2 - 1;
  73.     }
  74.     return buffer;
  75. }

  76. const noiseBuffer = createNoiseBuffer();

  77. function playDrumSound(type) {
  78.     const gainNode = audioContext.createGain();
  79.     gainNode.connect(audioContext.destination);

  80.     switch (type) {
  81.         case 'hihat':
  82.             playHiHat(gainNode);
  83.             break;

  84.         case 'snare':
  85.             playSnare(gainNode);
  86.             break;

  87.         case 'kick':
  88.             playKick(gainNode);
  89.             break;
  90.     }
  91. }

  92. function playHiHat(gainNode) {
  93.     const hihatSource = audioContext.createBufferSource();
  94.     hihatSource.buffer = noiseBuffer;

  95.     const hihatFilter = audioContext.createBiquadFilter();
  96.     hihatFilter.type = 'highpass';
  97.     hihatFilter.frequency.value = 7000;

  98.     hihatSource.connect(hihatFilter);
  99.     hihatFilter.connect(gainNode);

  100.     gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
  101.     gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);

  102.     hihatSource.start();
  103.     hihatSource.stop(audioContext.currentTime + 0.1);
  104. }

  105. function playSnare(gainNode) {
  106.     const snareNoise = audioContext.createBufferSource();
  107.     snareNoise.buffer = noiseBuffer;

  108.     const snareFilter = audioContext.createBiquadFilter();
  109.     snareFilter.type = 'highpass';
  110.     snareFilter.frequency.value = 1000;

  111.     const snareEnvelope = audioContext.createGain();
  112.     snareEnvelope.gain.setValueAtTime(1, audioContext.currentTime);
  113.     snareEnvelope.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);

  114.     snareNoise.connect(snareFilter);
  115.     snareFilter.connect(snareEnvelope);
  116.     snareEnvelope.connect(gainNode);

  117.     snareNoise.start();
  118.     snareNoise.stop(audioContext.currentTime + 0.2);
  119. }

  120. function playKick(gainNode) {
  121.     const osc = audioContext.createOscillator();
  122.     osc.type = 'sine';
  123.     osc.frequency.setValueAtTime(150, audioContext.currentTime);
  124.     osc.frequency.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5);
  125.    
  126.     gainNode.gain.setValueAtTime(1, audioContext.currentTime);
  127.     gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
  128.    
  129.     osc.connect(gainNode);
  130.     osc.start();
  131.     osc.stop(audioContext.currentTime + 0.5);
  132. }
复制代码


### 总结
就这样,我们的16步鼓机已经准备好让你叩击出节奏了!你可以通过点击网格激活或禁用某个节拍,调整速度,并使用播放和停止按钮来控制音乐。这是一台小小的节拍机器,但足以引爆派对!去享受创造音乐的快乐吧,别忘了拿好你的DJ帽子!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-7-19 10:26:37 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-7-19 14:45:03 | 显示全部楼层
?
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-7-19 15:44:51 | 显示全部楼层
FishC老师好
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-5-13 21:20

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表