Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
纯鼓点演示页面,不加载 mid 文件,直接播放 JSON 鼓序列 - Joyber 的博客

用 Web Audio 原生 API,不引入外部 soundfont 库,用内置正弦波简单模拟鼓声,完全不依赖外网音源,不会报undefined。

这个原生版本优势

完全不需要外部 CDN 音源,不会报 xxx is not defined
浏览器直接打开,离线可用
用 WebAudio 原生振荡器 + 噪声模拟底鼓、军鼓、踩镲
可以直接对接 PHP 输出的 JSON 鼓点数组,替换 drumEvents 即可

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>鼓点时序演示(原生WebAudio,无外部依赖)</title>
<style>
*{box-sizing:border-box;font-family:system-ui}
body{max-width:800px;margin:20px auto;padding:0 10px}
#log{height:300px;overflow:auto;border:1px solid #ccc;padding:8px;margin:10px 0;font-family:monospace;font-size:12px}
button{padding:8px 16px;margin:4px;cursor:pointer}
</style>
</head>
<body>
<h2>原生WebAudio 鼓点演示(无外部CDN音源)</h2>
<div>
<button id="btnPlay">播放鼓序列</button>
<button id="btnStop">停止</button>
</div>
<div id="log"></div>

<script>
const logEl = document.getElementById('log');
const btnPlay = document.getElementById('btnPlay');
const btnStop = document.getElementById('btnStop');

let audioCtx = null;
let stopFlag = false;

// 鼓点时序,和PHP输出JSON格式一致
const drumEvents = [
    {time:0.0, note:36, vel:100},
    {time:0.5, note:42, vel:80},
    {time:1.0, note:38, vel:95},
    {time:1.5, note:42, vel:80},
    {time:2.0, note:36, vel:100},
    {time:2.5, note:42, vel:80},
    {time:3.0, note:38, vel:95},
    {time:3.5, note:42, vel:80},
    {time:4.0, note:36, vel:100},
];

function appendLog(text){
    logEl.innerHTML += text + "\n";
    logEl.scrollTop = logEl.scrollHeight;
}

// 简易合成鼓音色
function playDrum(ctx, note, vel, startTime) {
    const gain = vel / 127;
    const osc = ctx.createOscillator();
    const gainNode = ctx.createGain();
    osc.connect(gainNode);
    gainNode.connect(ctx.destination);

    if(note === 36){
        // 底鼓
        osc.type = 'sine';
        osc.frequency.setValueAtTime(60, startTime);
        osc.frequency.exponentialRampToValueAtTime(20, startTime + 0.25);
        gainNode.gain.setValueAtTime(gain*0.8, startTime);
        gainNode.gain.exponentialRampToValueAtTime(0.001, startTime + 0.3);
        osc.start(startTime);
        osc.stop(startTime + 0.3);
    }else if(note === 38){
        // 军鼓
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(180, startTime);
        gainNode.gain.setValueAtTime(gain*0.6, startTime);
        gainNode.gain.exponentialRampToValueAtTime(0.001, startTime + 0.2);
        osc.start(startTime);
        osc.stop(startTime + 0.2);
    }else if(note === 42){
        // 闭踩镲(白噪声模拟)
        const bufferSize = ctx.sampleRate * 0.15;
        const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i=0;i<bufferSize;i++) data[i] = Math.random()*2-1;
        const noise = ctx.createBufferSource();
        noise.buffer = buffer;
        const filter = ctx.createBiquadFilter();
        filter.type = "highpass";
        filter.frequency.value = 7000;
        noise.connect(filter);
        filter.connect(gainNode);
        gainNode.gain.setValueAtTime(gain*0.4, startTime);
        gainNode.gain.exponentialRampToValueAtTime(0.001, startTime + 0.12);
        noise.start(startTime);
    }
}

btnPlay.onclick = async ()=>{
    if(!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    stopFlag = false;
    const start = audioCtx.currentTime;
    appendLog("=== 开始播放鼓序列 ===");
    drumEvents.forEach(ev=>{
        const t = start + ev.time;
        playDrum(audioCtx, ev.note, ev.vel, t);
        appendLog(`预定: ${ev.time.toFixed(2)}s | note:${ev.note} vel:${ev.vel}`);
    })
};
btnStop.onclick = ()=>{
    if(audioCtx) audioCtx.close();
    audioCtx = null;
    appendLog(" 停止");
}
</script>
</body>
</html>

标签: none

添加新评论