尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于p5.js与Tone.js构建实时音画交互系统:从Live Coding到创意编程实践

基于p5.js与Tone.js构建实时音画交互系统:从Live Coding到创意编程实践 最近在整理设计类活动资料时发现很多开发者对“Qbert IQ Scratch Session At Designer Con 2016”这个标题感到困惑。这看起来像是一场艺术或设计活动和编程有什么关系实际上这背后隐藏着一个非常有趣的技术交叉点视觉艺术、现场表演与实时编程Live Coding的结合。对于从事创意编程、交互设计或音画互动VJing的开发者而言理解如何将类似“Scratch Session”即兴刮擦/创作环节的现场感转化为代码驱动的实时生成内容是一项极具价值的技能。本文将从一个开发者的视角重新解构这个主题。我们不会复现2016年的某场具体活动而是聚焦于其核心精神如何使用现代技术栈如Python、p5.js、Web Audio API等构建一个能够进行实时视觉与音频“Scratch”即兴操控的交互系统。无论你是想为艺术项目增加现场编程能力还是探索创意编程的边界这篇文章都将提供从概念到实战的完整路径。1. 核心概念什么是“Scratch Session”与实时编程在进入实战之前我们需要厘清几个关键概念这对于理解我们要构建的系统至关重要。1.1 Scratch Session 在设计活动中的含义“Scratch”一词在此语境下并非指麻省理工学院的Scratch图形化编程语言而是引申自DJ打碟中的“搓碟”Scratching动作意为即兴的、现场的、通过手动操控产生独特效果的过程。在Designer Con这类设计师大会上一个“Scratch Session”可能指的是艺术家或设计师使用数字工具进行现场创作、修改和表演的环节。从技术实现角度看这要求系统具备低延迟响应艺术家的每一个操作如滑动鼠标、按下按键、旋转MIDI控制器都需要近乎实时地反映在视觉或音频输出上。状态可逆与分支能够快速撤销、重做或切换到不同的创作“分支”类似于代码的版本控制但发生在表演的瞬间。参数化操控将复杂的视觉效果或声音分解为一系列可以实时调节的参数如颜色、速度、波形、滤镜强度。1.2 实时编程Live Coding作为技术载体实时编程是连接“Scratch Session”理念与代码实现的最佳桥梁。它指的是在程序运行的同时动态地修改并重新执行代码立即看到变化效果。这不仅是表演艺术也是一种强大的原型设计和调试工具。在我们的项目中实时编程意味着分离逻辑与表现层核心的图形渲染、音频处理逻辑在一个持续运行的循环中。暴露控制接口通过GUI滑块、键盘快捷键、或外部硬件如MIDI设备来映射到这些逻辑的参数。热重载能力在不重启主程序的情况下动态加载新的效果脚本、着色器或音频处理单元。1.3 技术栈选型为什么选择这些工具为了实现上述目标我们需要一个兼顾高效、灵活和跨平台的技术组合处理/p5.js作为创意编程的标杆p5.jsJavaScript或ProcessingJava提供了极其友好的图形API和活跃的社区。对于Web部署和快速原型p5.js是首选。它允许我们专注于视觉逻辑无需陷入复杂的WebGL初始化。PythonPyGame/PyOpenGL/VisPy如果需要更底层的控制、复杂的数学运算或与机器学习库结合Python生态是强大的选择。PyGame适合2DVisPy或ModernGL适合高性能的2D/3D渲染。Web Audio API / Tone.js负责音频部分。Web Audio API提供了专业的节点化音频处理能力而Tone.js在此基础上封装了更易用的合成器、效果器和时序控制。外部控制器集成使用WebMIDI API浏览器或python-rtmidiPython来连接MIDI控制器将物理旋钮和推子映射到软件参数。本文将主要以p5.js Tone.js WebMIDI这一全Web技术栈为例进行讲解因为它无需安装直接在浏览器中运行最适合分享和快速体验。Python方案将作为进阶补充。2. 环境准备与项目初始化我们将创建一个完整的、模块化的实时视觉音频“Scratch”系统。首先从搭建开发环境开始。2.1 基础开发环境操作系统Windows 10/11, macOS, 或 Linux均可。本文示例在macOS和Windows上测试通过。代码编辑器Visual Studio Code推荐并安装Live Server插件用于本地热重载。浏览器最新版的Google Chrome或Microsoft Edge以支持WebMIDI API。可选硬件一个标准的MIDI控制器如AKAI APC Mini, Novation Launchpad, 或KORG nanoKONTROL。没有硬件也可用虚拟MIDI软件或键盘鼠标模拟。2.2 初始化项目结构在你的工作目录下创建如下文件和文件夹realtime-scratch-session/ ├── index.html # 主入口HTML文件 ├── style.css # 基础样式 ├── sketch.js # p5.js主视觉程序 ├── audioEngine.js # Tone.js音频引擎 ├── midiController.js # WebMIDI控制逻辑 └── effects/ # 存放可热加载的视觉效果模块 ├── baseEffect.js └── waveEffect.js2.3 编写基础HTML骨架index.html文件负责引入所有必要的库并搭建基础界面。!DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleReal-Time Visual Audio Scratch Pad/title link relstylesheet hrefstyle.css !-- 引入 p5.js 库 -- script srchttps://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js/script !-- 引入 Tone.js 库 -- script srchttps://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.min.js/script style body { margin: 0; padding: 20px; font-family: sans-serif; background: #222; color: #eee; } #canvas-container { margin: 20px auto; text-align: center; } #controls { background: #333; padding: 15px; border-radius: 8px; margin-top: 20px; } .param { margin: 10px 0; } label { display: inline-block; width: 150px; } input[typerange] { width: 300px; } /style /head body h1️ Real-Time Visual Audio Scratch Session/h1 p连接MIDI控制器或使用下方滑块/键盘实时操控视觉与音频参数。/p div idcanvas-container !-- p5.js画布将在此处生成 -- /div div idcontrols h3 主控参数/h3 div classparam label forsize粒子大小:/label input typerange idsize min1 max50 value10 span idsize-value10/span /div div classparam label forspeed运动速度:/label input typerange idspeed min0 max200 value100 span idspeed-value100/span /div div classparam label forhue色调:/label input typerange idhue min0 max360 value180 span idhue-value180/span /div div classparam label forvolume主音量:/label input typerange idvolume min-60 max0 value-20 span idvolume-value-20 dB/span /div button ideffect1加载波浪效果/button button ideffect2加载粒子效果/button p键盘快捷键按 1/2 切换效果空格键 播放/停止音频 R 重置参数。/p /div div idmidi-status h3 MIDI 控制器状态/h3 p未检测到MIDI设备。请连接设备并刷新页面。/p /div !-- 引入我们自己的JS模块 -- script srcaudioEngine.js/script script srcmidiController.js/script script srcsketch.js/script /body /html2.4 基础样式style.css文件让界面更清晰。body { font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; max-width: 1200px; margin: 0 auto; padding: 20px; } #canvas-container canvas { border: 2px solid #444; border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); } button { background-color: #4CAF50; border: none; color: white; padding: 10px 20px; margin: 5px; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background-color: #45a049; } input[typerange] { vertical-align: middle; }3. 构建核心引擎视觉与音频的实时管道系统核心是三个相互通信的模块视觉引擎、音频引擎和控制桥接器。3.1 视觉引擎 (sketch.js) - 基于 p5.js这个文件是p5.js的主程序它创建一个动画循环并根据全局状态参数渲染图形。// sketch.js // 全局状态对象所有模块都读写这个对象以实现通信 let globalParams { particleSize: 10, movementSpeed: 1.0, baseHue: 180, currentEffect: wave, audioLevel: 0 // 将从音频引擎获取 }; let currentEffectFunction null; let particles []; // p5.js 的 setup 函数在页面加载时运行一次 function setup() { const container document.getElementById(canvas-container); const canvas createCanvas(800, 600); canvas.parent(container); colorMode(HSB, 360, 100, 100, 1); // 使用HSB颜色模式便于色调控制 noStroke(); // 初始化粒子数组 for (let i 0; i 100; i) { particles.push({ x: random(width), y: random(height), vx: random(-1, 1), vy: random(-1, 1), size: random(5, 15) }); } // 默认加载波浪效果 loadEffect(wave); // 绑定HTML控件到全局参数 bindControls(); } // 绑定HTML滑块和按钮事件 function bindControls() { const sizeSlider document.getElementById(size); const speedSlider document.getElementById(speed); const hueSlider document.getElementById(hue); const volumeSlider document.getElementById(volume); sizeSlider.addEventListener(input, (e) { globalParams.particleSize parseInt(e.target.value); document.getElementById(size-value).textContent e.target.value; }); speedSlider.addEventListener(input, (e) { globalParams.movementSpeed parseInt(e.target.value) / 100.0; // 归一化到0-2 document.getElementById(speed-value).textContent e.target.value; }); hueSlider.addEventListener(input, (e) { globalParams.baseHue parseInt(e.target.value); document.getElementById(hue-value).textContent e.target.value; }); volumeSlider.addEventListener(input, (e) { // 音量控制委托给音频引擎 const db parseInt(e.target.value); if (window.audioEngine) { window.audioEngine.setVolume(db); } document.getElementById(volume-value).textContent db dB; }); document.getElementById(effect1).addEventListener(click, () loadEffect(wave)); document.getElementById(effect2).addEventListener(click, () loadEffect(particle)); } // 动态加载效果模块模拟热重载 function loadEffect(effectName) { // 在实际项目中这里可以通过fetch动态加载JS文件 // 此处简化为内置函数切换 globalParams.currentEffect effectName; console.log(切换到效果: ${effectName}); if (effectName wave) { currentEffectFunction waveEffect; } else if (effectName particle) { currentEffectFunction particleEffect; } else { currentEffectFunction () { background(30); text(未定义的效果, width/2, height/2); }; } } // p5.js 的 draw 函数每帧运行约60fps function draw() { // 根据音频引擎的实时电平微调背景亮度一个简单的音频反应 let audioReactiveBrightness map(globalParams.audioLevel, 0, 1, 20, 60); background(0, 0, audioReactiveBrightness); // HSB: 色调饱和度亮度 // 如果有当前效果函数则执行它 if (currentEffectFunction) { currentEffectFunction(); } // 更新并绘制所有粒子作为基础图层 particles.forEach(p { p.x p.vx * globalParams.movementSpeed; p.y p.vy * globalParams.movementSpeed; // 边界检查 if (p.x 0 || p.x width) p.vx * -1; if (p.y 0 || p.y height) p.vy * -1; fill(globalParams.baseHue, 70, 90, 0.7); circle(p.x, p.y, p.size * (globalParams.particleSize / 10.0)); }); } // 键盘控制 function keyPressed() { if (key 1) loadEffect(wave); if (key 2) loadEffect(particle); if (key ) { if (window.audioEngine) { window.audioEngine.togglePlayback(); } } if (key r || key R) { globalParams.particleSize 10; globalParams.movementSpeed 1.0; globalParams.baseHue 180; // 同步更新滑块UI document.getElementById(size).value 10; document.getElementById(speed).value 100; document.getElementById(hue).value 180; document.getElementById(size-value).textContent 10; document.getElementById(speed-value).textContent 100; document.getElementById(hue-value).textContent 180; } } // --- 内置效果函数 --- function waveEffect() { // 绘制一个随参数变化的波浪图形 fill(globalParams.baseHue, 80, 100, 0.5); for (let x 0; x width; x 20) { let angle (x * 0.02) (frameCount * 0.02 * globalParams.movementSpeed); let y height/2 sin(angle) * 100; circle(x, y, globalParams.particleSize); } } function particleEffect() { // 在鼠标位置生成粒子 if (mouseIsPressed) { for (let i 0; i 3; i) { fill((globalParams.baseHue i*30) % 360, 90, 90, 0.8); let px mouseX random(-20, 20); let py mouseY random(-20, 20); circle(px, py, globalParams.particleSize * random(0.5, 1.5)); } } }3.2 音频引擎 (audioEngine.js) - 基于 Tone.js这个模块负责生成和控制声音并将实时音频分析数据如电平提供给视觉引擎。// audioEngine.js class AudioEngine { constructor() { this.isPlaying false; this.volume -20; // dB this.analyser null; this.audioLevel 0; // 归一化的实时电平 0-1 // 初始化Tone.js上下文 Tone.start(); // 恢复音频上下文需用户交互后 this.initSynthAndEffects(); this.initAnalyser(); this.bindGlobalEvents(); } initSynthAndEffects() { // 创建一个合成器 this.synth new Tone.PolySynth(Tone.Synth).toDestination(); this.synth.volume.value this.volume; // 添加一些效果器 this.delay new Tone.FeedbackDelay(8n, 0.5).toDestination(); this.reverb new Tone.Reverb(3).toDestination(); this.synth.connect(this.delay); this.synth.connect(this.reverb); // 创建一个简单的琶音序列 this.sequence new Tone.Sequence((time, note) { this.synth.triggerAttackRelease(note, 8n, time); // 触发一个事件通知视觉引擎可用于更复杂的音频反应 if (window.dispatchEvent) { window.dispatchEvent(new CustomEvent(audioNote, { detail: { note, time } })); } }, [C4, E4, G4, B4, C5], 4n).start(0); } initAnalyser() { // 创建一个分析器来获取音频电平 this.analyser new Tone.Analyser(waveform, 256); this.synth.connect(this.analyser); // 每帧更新音频电平这个函数将在p5.js的draw循环中被调用 this.updateLevel () { const value this.analyser.getValue(); if (value value.length 0) { // 计算平均绝对值作为电平 let sum 0; for (let i 0; i value.length; i) { sum Math.abs(value[i]); } this.audioLevel sum / value.length; // 将电平数据存入全局状态供sketch.js使用 if (window.globalParams) { window.globalParams.audioLevel this.audioLevel; } } }; } bindGlobalEvents() { // 将音频引擎实例挂载到window方便其他模块调用 window.audioEngine this; // 监听来自MIDI控制器的全局事件由midiController.js触发 window.addEventListener(midiControlChange, (event) { const { controller, value } event.detail; // 映射MIDI控制器编号到音频参数 if (controller 1) { // 假设CC1是滤波器频率 const freq Tone.Frequency(value * 1000 100, hz); // 映射到100-1100Hz this.synth.set({ filterFrequency: freq }); } else if (controller 2) { // CC2是延迟反馈量 this.delay.wet.value value; } }); } togglePlayback() { if (this.isPlaying) { Tone.Transport.stop(); this.isPlaying false; console.log(音频停止); } else { Tone.Transport.start(); this.isPlaying true; console.log(音频播放); } } setVolume(db) { this.volume db; this.synth.volume.value db; } // 在动画循环中调用此函数以更新电平 update() { if (this.updateLevel) { this.updateLevel(); } } } // 当页面加载后等待用户交互如点击再初始化音频以符合浏览器自动播放策略 document.addEventListener(DOMContentLoaded, () { const initAudio () { if (!window.audioEngine) { new AudioEngine(); console.log(音频引擎已初始化); // 从p5.js的draw循环中调用audioEngine.update() // 这需要在sketch.js中稍作修改 } document.removeEventListener(click, initAudio); }; // 用户首次点击页面任意位置时初始化音频 document.addEventListener(click, initAudio); });3.3 MIDI控制器集成 (midiController.js)这个模块负责与WebMIDI API交互将物理控制器的输入转换为我们的软件可以理解的事件。// midiController.js class MidiController { constructor() { this.midiAccess null; this.inputs []; this.statusElement document.getElementById(midi-status); this.initMIDI(); } async initMIDI() { // 检查浏览器是否支持WebMIDI if (!navigator.requestMIDIAccess) { this.updateStatus(您的浏览器不支持WebMIDI API。请使用Chrome、Edge或Opera。); return; } try { this.midiAccess await navigator.requestMIDIAccess(); this.updateStatus(MIDI支持已启用。找到 ${this.midiAccess.inputs.size} 个输入设备。); this.listenToInputs(); this.midiAccess.onstatechange this.handleStateChange.bind(this); } catch (err) { this.updateStatus(无法访问MIDI设备: ${err.message}); } } updateStatus(message) { if (this.statusElement) { this.statusElement.innerHTML h3 MIDI 控制器状态/h3p${message}/p; } console.log(MIDI Status:, message); } handleStateChange(event) { console.log(MIDI 设备状态变化:, event.port.name, event.port.state); this.updateStatus(设备 ${event.port.name} 状态变为: ${event.port.state}); this.listenToInputs(); // 重新监听 } listenToInputs() { this.inputs []; // 清空旧监听器 const inputs this.midiAccess.inputs.values(); for (let input of inputs) { input.onmidimessage this.handleMIDIMessage.bind(this); this.inputs.push(input); this.updateStatus(已连接: ${input.name}); } } handleMIDIMessage(message) { // MIDI消息是一个Uint8Array: [status, data1, data2] const [status, data1, data2] message.data; const command status 4; // 高4位是命令 const channel status 0x0f; // 低4位是通道 // 我们主要关心两种消息控制改变(CC)和音符开 if (command 0xB) { // 控制改变 (CC) const controller data1; const value data2 / 127.0; // 归一化到0-1 this.handleControlChange(controller, value, channel); } else if (command 0x9) { // 音符开 const note data1; const velocity data2 / 127.0; this.handleNoteOn(note, velocity, channel); } // 可以忽略其他消息如音符关(0x8)、弯音等 } handleControlChange(controller, value, channel) { console.log(MIDI CC: 通道${channel}, 控制器${controller}, 值${value.toFixed(2)}); // 将事件派发到全局让其他模块如audioEngine, sketch响应 const event new CustomEvent(midiControlChange, { detail: { controller, value, channel } }); window.dispatchEvent(event); // 示例映射几个常用控制器到我们的全局参数 if (controller 7) { // CC7通常是主音量 if (window.globalParams) { // 映射到粒子大小 (1-50) window.globalParams.particleSize Math.floor(1 value * 49); // 同步更新UI滑块可选 const slider document.getElementById(size); if (slider) { slider.value window.globalParams.particleSize; document.getElementById(size-value).textContent window.globalParams.particleSize; } } } else if (controller 10) { // CC10是声像这里映射到色调 if (window.globalParams) { window.globalParams.baseHue Math.floor(value * 360); const slider document.getElementById(hue); if (slider) { slider.value window.globalParams.baseHue; document.getElementById(hue-value).textContent window.globalParams.baseHue; } } } } handleNoteOn(note, velocity, channel) { console.log(MIDI Note On: 通道${channel}, 音符${note}, 力度${velocity.toFixed(2)}); // 触发音频引擎播放音符 if (window.audioEngine window.audioEngine.synth) { const noteName this.midiNoteToName(note); window.audioEngine.synth.triggerAttackRelease(noteName, 8n); } // 也可以触发视觉事件 const event new CustomEvent(midiNoteOn, { detail: { note, velocity, channel } }); window.dispatchEvent(event); } midiNoteToName(midiNumber) { const notes [C, C#, D, D#, E, F, F#, G, G#, A, A#, B]; const octave Math.floor(midiNumber / 12) - 1; const noteIndex midiNumber % 12; return notes[noteIndex] octave; } } // 页面加载后初始化MIDI控制器 document.addEventListener(DOMContentLoaded, () { new MidiController(); });4. 系统集成与运行现在我们已经有了三个核心模块。最后一步是让它们协同工作。4.1 修改 sketch.js 以集成音频更新在sketch.js的draw()函数末尾添加对音频引擎更新的调用以确保每帧都获取最新的音频电平。// 在 sketch.js 的 draw() 函数末尾添加 function draw() { // ... 原有的绘制代码 ... // 更新音频分析器数据 if (window.audioEngine window.audioEngine.update) { window.audioEngine.update(); } }4.2 运行你的实时“Scratch Session”将以上所有文件 (index.html,style.css,sketch.js,audioEngine.js,midiController.js) 放在同一个文件夹。用 VS Code 打开该文件夹右键点击index.html选择 “Open with Live Server”。或者直接在浏览器中打开index.html注意直接文件打开可能因CORS限制导致MIDI API不可用Live Server是最佳方式。页面加载后点击页面任意位置以激活音频这是浏览器的自动播放策略要求。你现在应该能看到一个动态的画布上面有移动的粒子和波浪。使用屏幕上的滑块或按下键盘1/2切换效果空格键控制音频播放/停止R重置参数。如果你连接了MIDI控制器刷新页面后浏览器可能会请求MIDI访问权限。授权后旋转旋钮或按下按键你将看到视觉参数实时变化并听到声音。4.3 效果演示与交互视觉反馈调整“色调”滑块或对应的MIDI控制器如CC10整个画面的颜色会平滑过渡。音频反应播放音频按空格键后背景亮度会随着音乐节奏轻微脉动因为globalParams.audioLevel被用于计算背景亮度。实时切换点击“加载波浪效果”或“加载粒子效果”按钮主视觉模式会立即改变而粒子背景层继续独立运行。MIDI控制如果你的控制器有旋钮发送CC信息尝试转动它们观察粒子大小和颜色的变化。按下琴键会触发一个合成器音符。5. 常见问题与排查思路在构建和运行此类实时交互系统时你可能会遇到以下典型问题。问题现象可能原因解决思路页面打开无声音控制台无错误浏览器自动播放策略阻止。必须有点击/触摸等用户手势后音频上下文才能启动。确保已点击页面激活音频。检查audioEngine.js中的Tone.start()和点击事件监听。MIDI控制器已连接但无响应1. 浏览器不支持WebMIDI。2. 未授予页面MIDI访问权限。3. MIDI信号未映射到正确参数。1. 使用Chrome/Edge/Opera。2. 刷新页面注意浏览器地址栏附近的MIDI权限弹窗。3. 打开浏览器开发者工具(F12)的Console查看是否有MIDI消息日志。检查handleControlChange函数中的控制器编号映射。动画卡顿帧率低1.draw()函数中的计算过于复杂。2. 粒子或图形对象太多。3. 同时进行复杂的音频分析。1. 优化绘图代码避免在draw()中创建新对象。2. 减少粒子数量或使用更简单的图形。3. 降低Analyser的FFT大小如从2048改为256。使用requestAnimationFrame保证流畅。音频播放有延迟或爆音1. 音频缓冲区大小问题。2. Tone.js调度时间不精确。3. 系统音频驱动问题。1. 尝试调整Tone.context.latencyHint为balanced或playback。2. 使用Tone.Transport进行时序调度而非setTimeout。3. 检查系统默认音频输出设备关闭其他占用音频的程序。切换效果时页面卡死或出错1. 新效果JS文件加载失败。2. 效果函数内有未定义变量或死循环。3. 内存泄漏。1. 使用try...catch包裹效果加载逻辑。2. 确保效果函数不会阻塞主线程避免同步的无限循环。3. 在效果卸载时清理旧的定时器、事件监听器和DOM引用。滑块控制不跟手有延迟1.input事件处理函数执行太慢。2. 参数更新与画面重绘不同步。1. 简化事件处理函数中的逻辑。2. 确保参数更新直接修改globalParams由下一帧draw()自然读取并渲染不要在一次事件中强制重绘。6. 最佳实践与进阶扩展方向一个基础的实时“Scratch”系统已经搭建完成。要将其用于更专业的场景或提升代码质量可以参考以下实践和建议。6.1 代码组织与架构优化状态集中管理我们使用了globalParams对象这是一个好的开始。对于更复杂的项目可以考虑使用专门的状态管理库如 Redux 模式的最小实现确保状态变更可预测、可调试。模块化与依赖注入将效果、音频单元、控制器映射定义为独立的模块或类通过一个中央的“表演者”Performer或“场景”Scene类来组合和切换它们而不是依赖全局函数和变量。使用 Web Workers将费时的计算如复杂的粒子物理、FFT分析移到 Web Worker 中避免阻塞UI线程和动画循环。6.2 性能优化图形渲染对于静态或变化少的背景将其绘制到离屏p5.Graphics缓冲区每帧直接贴图而非重新计算。使用p5.js的drawingContext直接调用 WebGL API如果熟悉WebGL以获得最高性能。合理使用p5.js的pixelDensity(1)来在高DPI屏幕上平衡画质和性能。音频处理限制同时发声的合成器复音数避免创建过多音频节点。对于采样播放使用Tone.Player并预加载音频文件。在不需要分析时断开Analyser节点以节省CPU。6.3 提升交互与表现力更丰富的MIDI/OSC映射实现一个可配置的映射系统允许用户将任意MIDI控制器CC、音符、触后映射到任意视觉/音频参数并保存/加载映射预设。集成 OSC 协议除了MIDIOSCOpen Sound Control是新媒体艺术中更灵活的网络协议。使用osc-js等库可以接收来自TouchDesigner、Max/MSP、Processing等软件的控制信号。视频输入与计算机视觉使用p5.js的capture功能或ml5.js库将摄像头视频流作为输入实现基于人体姿势、面部识别或颜色追踪的交互。着色器Shader效果将核心视觉效果用 GLSL 着色器实现在 p5.js 中可通过createShader()实现。着色器运行在GPU上能实现极其复杂、高性能的实时图形效果如流体模拟、光影变形这是“Scratch”视觉表现力的终极武器。6.4 项目部署与分享打包为单页应用使用如Parcel或Vite这样的构建工具打包项目优化资源加载。服务器部署由于涉及WebMIDI和可能的WebSocket用于OSC或多人协作你需要一个HTTPS服务器。可以使用Netlify、Vercel或任何支持静态托管的服务。录制与回放实现一个“录制”功能将一段时间内的所有参数变化和事件序列化如记录为JSON。之后可以“回放”这段表演这对于创作和调试至关重要。从一场设计师大会的“Scratch Session”概念出发我们构建了一个完全在浏览器中运行的、由代码驱动的实时音画交互系统。这个项目演示了如何将艺术创作的即兴精神通过模块化的代码视觉引擎、音频引擎、控制层转化为可扩展的技术实践。你不仅获得了一个有趣的玩具更掌握了一套方法如何设计状态流、如何处理实时事件、如何桥接不同领域的API图形、音频、硬件。真正的“Scratch”精神在于现场探索。尝试修改waveEffect和particleEffect函数里的数学公式连接你手边的硬件控制器或者引入一个新的JS库来生成不同的声音。每一次代码的修改和参数的实时调节都是一次新的即兴创作。
返回列表