让模型写一段音乐最容易展示的是一份像模像样的 JSON。真正麻烦的是下一步第八小节有没有写满单声道里两个音是否叠在一起提示词里的占空比有没有进入合成代码如果这些问题没处理播放成功只能说明代码没有立刻抛异常。10 月 22 日Anthropic 宣布将 Agent Skills 捐赠给 AAIF。对音乐工具来说我更关心其中可执行的流程约束生成器可以换输入谱、校验规则和失败记录应留下。下面先做一个不调用模型的八小节夹具验证通过后才允许进入 Web Audio。它是芯片风合成不是 NES 硬件的精确仿真。先把一份谱的边界写死本例采用 4/4 拍每小节 16 个十六分音符位置共 128 步。四条通道分别是两个脉冲声部、三角波低音和噪声打击。每条通道按单声部处理因此任何时间重叠都拒绝需要和弦就使用多条声部而不是让同一声部偷偷变成复音合成器。Schema 不只管字段类型。bpm、bars、音域、起止位置和同声部重叠都是合同的一部分。模型给出的未知字段也应被拒绝否则一个看起来有用、实际上从未消费的 duty 字段会制造“已经控制音色”的错觉。下面保存为 score.mjs。校验器、确定性噪声和浏览器回放入口都在同一文件中无第三方依赖。const channels [pulse1, pulse2, triangle, noise]; function keys(o, required, optional []) { if (!o || typeof o ! object || Array.isArray(o)) throw Error(object required); if (required.some(k !Object.hasOwn(o, k)) || Object.keys(o).some(k !required.includes(k) !optional.includes(k))) throw Error(fields); } export function validate(s) { keys(s, [bpm, bars, channels]); if (!Number.isFinite(s.bpm) || s.bpm 40 || s.bpm 240 || s.bars ! 8) throw Error(tempo/bars); keys(s.channels, channels); for (const ch of channels) { const events s.channels[ch]; if (!Array.isArray(events) || events.length 128) throw Error(events); let end 0; for (const e of [...events].sort((x, y) x.step - y.step)) { const pulse ch.startsWith(pulse); keys(e, ch noise ? [step, len] : [step, len, midi], pulse ? [duty] : []); if (!Number.isInteger(e.step) || !Number.isInteger(e.len) || e.step 0 || e.len 1 || e.step e.len 128 || e.step end) throw Error(time/overlap); if (ch ! noise (!Number.isInteger(e.midi) || e.midi 0 || e.midi 127)) throw Error(pitch); if (pulse ![0.125, 0.25, 0.5, 0.75].includes(e.duty ?? 0.5)) throw Error(duty); end e.step e.len; } } return s; } export function noise(n, seed 0x1ace) { if (!Number.isInteger(n) || n 1 || n 192000) throw Error(size); if (!Number.isInteger(seed) || seed 1 || seed 32767) throw Error(seed); const data new Float32Array(n); for (let i 0; i n; i) { const bit (seed ^ (seed 1)) 1; seed (seed 1) | (bit 14); data[i] (seed 1) ? 0.25 : -0.25; } return data; } export async function play(ctx, score) { validate(score); await ctx.resume(); // 必须由页面的用户操作触发此入口 const master ctx.createGain(); master.gain.value 0.12; master.connect(ctx.destination); const buffer ctx.createBuffer(1, ctx.sampleRate, ctx.sampleRate); buffer.copyToChannel(noise(ctx.sampleRate), 0); const start ctx.currentTime 0.1, stepSeconds 60 / score.bpm / 4; const live new Set(); for (const ch of channels) for (const e of score.channels[ch]) { const t start e.step * stepSeconds, duration e.len * stepSeconds; const src ch noise ? ctx.createBufferSource() : ctx.createOscillator(); if (ch noise) { src.buffer buffer; src.loop true; } else { const f 440 * 2 ** ((e.midi - 69) / 12); src.frequency.value f; if (ch triangle) src.type triangle; else { const count Math.min(32, Math.floor(ctx.sampleRate / (2 * f))); const real new Float32Array(count 1), imag new Float32Array(count 1); const d e.duty ?? 0.5; for (let k 1; k count; k) { real[k] 2 * Math.sin(2 * Math.PI * k * d) / (Math.PI * k); imag[k] 2 * (1 - Math.cos(2 * Math.PI * k * d)) / (Math.PI * k); } src.setPeriodicWave(ctx.createPeriodicWave(real, imag)); } } const gain ctx.createGain(), attack Math.min(0.003, duration / 4); gain.gain.setValueAtTime(0, t); gain.gain.linearRampToValueAtTime(1, t attack); gain.gain.setValueAtTime(1, t duration - attack); gain.gain.linearRampToValueAtTime(0, t duration); src.connect(gain).connect(master); live.add(src); src.onended () { live.delete(src); src.disconnect(); gain.disconnect(); if (!live.size) master.disconnect(); }; src.start(t); src.stop(t duration); } if (!live.size) master.disconnect(); return () { for (const src of live) src.stop(ctx.currentTime); }; }回放和生成分别验收在页面按钮的点击处理器中创建或复用 AudioContext再调用 play(ctx, score)。这里按音频时钟一次排程界面动画不负责“准点发声”。浏览器自动播放策略、设备输出延迟和实际音量仍需单独测量。函数接收调用方的上下文结束后只释放本次节点不擅自关闭别的组件共享的声卡资源。脉冲占空比在这里确实进入了傅里叶系数计算谐波数量也按采样率和音高设上限。不能先随意生成混叠再声称串一个低通就能找回原来的频率。噪声使用显式 LFSR 状态不拿线性同余生成器的最低位冒充硬件噪声。不过寄存器、包络和计时都没有逐项复刻 2A03音色相似性还需要听审。图示区分了合法音符、同通道重叠和越出允许音域。它是生成的规则示意图不是模型输出或频谱实测图中的音域带可以由具体乐器规则进一步收窄。给模型的合同可以要求它只返回八小节谱失败时返回错误说明。工具侧仍要重新校验。不要静默把 MIDI 999 改成 69也不要把缺失通道补成“生成成功”这会把模型失败藏进播放器。本例的本地验证只覆盖结构、时间边界、占空比和噪声确定性。没有调用音乐模型没有在浏览器录制音频也没有做主观听感评分。下一轮需要保留原始模型输出、修复前后差异和导出的 WAV再讨论生成质量。复现记录本轮在 Node.js v26.3.0 中运行随文核心逻辑的构造样例进程退出码为 0。测试输出PASS: valid score, missing channel, overlap, pitch, duty, deterministic LFSR, zero-seed rejection将上方 JavaScript 保存为 example.mjs可用 node example.mjs 加载浏览器、音频节点、摄像头及外部服务入口另需对应环境。构造样例不等于真实业务效果。资料与时效Donating Agent Skills to the Agentic AI Foundation2026-10-22。Agent Skills Specification首次发布日期未核实。Web Audio API 1.12026-09-22。本稿对应原预排时段实际发布前须人工复核来源更正、SDK/API 版本及可用性不据此预言 10 月 24–31 日的新事件。
阅读完成 · 觉得有帮助?