鱼C论坛

 找回密码
 立即注册
查看: 167|回复: 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 请给大家简单讲解一下代码。

本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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[row][col] = !pattern[row][col];
        e.target.classList.toggle('active');
    }
});

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

    pattern.forEach((row, rowIndex) => {
        if (row[currentBeat]) {
            playDrumSound(drumSounds[rowIndex]);
        }
    });

    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[i] = 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 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-7-19 10:26:37 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2024-7-19 14:45:03 | 显示全部楼层
?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2024-7-19 15:44:51 | 显示全部楼层
FishC老师好
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-9-17 02:55

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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