前言
身為一個喜歡搞創意編程的後端工程師,我一直覺得視覺只是表現形式的一半。真正讓人起雞皮疙瘩的互動裝置,通常都有聲音的參與——想想看博物館裡那些你走過去就會發出聲響的裝置,或是隨著你的動作改變音樂和燈光的互動作品。
要做到這件事,有一個工具我覺得特別適合入門:PureData(Pd)。
PureData 是一個開源的視覺化程式語言,專門用來做即時的音訊處理、多媒體互動、和聲光藝術。它不像 p5.js 那樣用文字寫程式,而是用「連線」的方式把音訊處理的模組接在一起——有點像在接樂高。
這篇文章會帶你從安裝開始,一路到做出一個能用感測器控制的簡單聲光互動裝置。
PureData 是什麼?
歷史背景
PureData(通常簡稱 Pd)是由 Miller Puckette 在 1990 年代開發的。他也是 Max/MSP 的原始作者——Max/MSP 是音樂和多媒體領域最知名的商業軟體之一(一年授權要好幾百美金)。
Pd 本質上是 Max 的開源版本。雖然介面比較陽春,但核心功能不輸 Max。很多聲音藝術家、互動設計師、和新媒體藝術家都在用它。
Pd 的版本
- Pd-vanilla:Miller Puckette 維護的原始版本,功能較基本
- Purr Data(Pd-l2ork):加了現代化 UI 和更多外部物件
- Pd-extended:已停止維護,但很多教學還是基於這個版本
建議從 Purr Data 開始,它的 UI 比較友善,而且內建很多實用的外部物件。
安裝
# macOS(用 Homebrew)
brew install --cask purr-data
# Ubuntu/Debian
sudo apt-get install purr-data
# Windows
# 從官網下載安裝包:https://agraef.github.io/purr-data/
Pd 的基本概念
視覺化程式設計
Pd 的程式不是用文字寫的,而是用物件(Object) 和連線(Patch Cord) 組成的。
一個基本的 Pd patch 看起來像這樣:
[osc~ 440] ← 產生 440Hz 的正弦波
|
[*~ 0.3] ← 乘以 0.3(降低音量)
|
[dac~] ← 輸出到音效卡(Digital-to-Analog Converter)
兩種資料流
Pd 有兩種資料流:
- 控制訊號(Control):離散的數值和訊息(整數、浮點數、符號)
- 音訊訊號(Audio):連續的音訊串流(通常 44100 samples/sec)
帶有 ~ 的物件處理音訊訊號,不帶的處理控制訊號:
[metro 500] ← 控制訊號:每 500ms 發送一個 bang
[osc~ 440] ← 音訊訊號:440Hz 正弦波
基本物件
| 物件 | 功能 | 類型 |
|——|——|——|
| [osc~ freq] | 正弦波振盪器 | 音訊 |
| [noise~] | 白噪音 | 音訊 |
| [phasor~ freq] | 鋸齒波 | 音訊 |
| [*~ value] | 乘法(音量控制) | 音訊 |
| [+~ value] | 加法 | 音訊 |
| [dac~] | 音訊輸出 | 音訊 |
| [adc~] | 音訊輸入(麥克風) | 音訊 |
| [metro ms] | 定時觸發 | 控制 |
| [random max] | 隨機數 | 控制 |
| [print] | 印出值(除錯用) | 控制 |
第一個 Patch:會唱歌的 Pd
讓我們做一個簡單的音調產生器。在 Pd 中,你需要:
- 打開 Pd
- 按 Ctrl+1 建立新物件
- 把以下物件放到畫布上,然後用滑鼠連線
Patch 1:基本音調
[440( ← 訊息框:點擊會送出 440
|
[osc~] ← 正弦波振盪器
|
[*~ 0.2] ← 降低音量,保護耳朵!
|
[dac~] ← 輸出到喇叭
點擊 [440( 訊息框,你應該會聽到一個 A4 音調。
Patch 2:可調頻率的音調
[hslider] ← 水平滑桿(0~127)
|
[* 10] ← 乘以 10,範圍變成 0~1270
|
[+ 100] ← 加 100,範圍變成 100~1370 Hz
|
[osc~]
|
[*~ 0.2]
|
[dac~]
現在你可以用滑桿控制頻率了。
Patch 3:簡單的合成器
[notein] ← 接收 MIDI 輸入
| |
| [mtof] ← MIDI 音符轉頻率
| |
| [osc~]
| |
| [*~]─────┐
| | |
[/ 127] | ← 力度轉 0~1 的音量
| |
[sig~]─────┘ ← 控制訊號轉音訊訊號
|
[dac~]
這個 patch 接收 MIDI 鍵盤的輸入,把 MIDI 音符轉成頻率,力度轉成音量。如果你有 MIDI 鍵盤,接上去就能彈了。
音訊處理基礎
濾波器
濾波器是聲音設計的核心工具:
[noise~] ← 白噪音(包含所有頻率)
|
[lop~ 500] ← Low-pass filter,只讓 500Hz 以下通過
| → 聲音變得「悶悶的」
[*~ 0.3]
|
[dac~]
常用的濾波器:
[lop~]:低通(Low-pass),讓低頻通過[hip~]:高通(High-pass),讓高頻通過[bp~]:帶通(Band-pass),只讓特定頻率範圍通過[vcf~]:電壓控制濾波器,可以即時改變截止頻率
延遲效果
[adc~] ← 麥克風輸入
|
[delwrite~ myDelay 1000] ← 寫入 1 秒的延遲緩衝
|
[delread~ myDelay 300] ← 讀取 300ms 前的聲音
|
[*~ 0.5] ← 延遲訊號的音量
|
[+~]─────────[adc~] ← 混合原始和延遲訊號
|
[dac~]
包絡線(Envelope)
讓聲音有「起音-持續-消退」的變化,而不是突然開關:
[bang( ← 觸發
|
[vline~] ← 包絡線產生器
| 接收 "目標值 時間ms" 的訊息
[*~]─────[osc~ 440]
|
[dac~]
--- 觸發包絡線的訊息序列 ---
[0, 1 10, 0.7 100, 0 500(
← 立刻到 0
← 10ms 升到 1(Attack)
← 100ms 降到 0.7(Decay/Sustain)
← 500ms 降到 0(Release)
感測器輸入:Arduino 整合
這裡是事情開始變得有趣的地方——把實體感測器接上 Pd。
硬體需求
- Arduino(Uno、Nano 都可以)
- 各種感測器:超音波距離感測器、光敏電阻、加速度計等
- 幾條杜邦線
Arduino 端:Firmata 協議
最簡單的方式是在 Arduino 上燒錄 Firmata 韌體。Firmata 是一個標準化的通訊協議,讓電腦可以直接讀寫 Arduino 的腳位:
// Arduino IDE: File → Examples → Firmata → StandardFirmata
// 直接上傳就好,不需要改任何程式碼
Pd 端:Pduino
Pd 有一個外部物件叫 Pduino,可以跟 Firmata 通訊:
[open /dev/ttyUSB0( ← 打開 Arduino 的序列埠
|
[arduino] ← Pduino 物件
|
[route analog] ← 篩選類比輸入
|
[route 0] ← 篩選第 0 號類比腳位(A0)
|
[/ 1023] ← 正規化到 0~1
|
[* 1000] ← 映射到 0~1000 Hz
|
[+ 100] ← 偏移到 100~1100 Hz
|
[osc~]
|
[*~ 0.2]
|
[dac~]
這樣你就能用一個旋鈕(接在 A0)來控制音調了。
另一種方式:Serial 直接通訊
如果不想用 Firmata,也可以自己寫 Arduino 程式,用 Serial 送資料:
// Arduino 端
const int SENSOR_PIN = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int value = analogRead(SENSOR_PIN);
// 送出格式:sensor_id value;
Serial.print("0 ");
Serial.print(value);
Serial.println(";");
delay(20); // 50 Hz 更新率
}
--- Pd 端 ---
[comport 0 9600] ← 開啟序列埠
|
[zl group 32] ← 收集字元直到分隔符
|
[list trim] ← 清理
|
[route 0] ← 篩選感測器 ID
|
[unpack f] ← 解包數值
|
... 連到音訊處理 ...
聲光同步:Pd + 視覺
方法 1:Pd + GEM
GEM(Graphics Environment for Multimedia)是 Pd 內建的 OpenGL 圖形函式庫:
[gemhead] ← GEM 的渲染起點
|
[color 1 0 0] ← 紅色
|
[sphere 1] ← 畫一個球
|
[gemwin] ← GEM 視窗
--- 用音訊控制視覺 ---
[osc~ 2] ← 2Hz 的正弦波
|
[snapshot~] ← 音訊轉控制訊號
|
[* 2]
|
[+ 1]
|
[size $1 $1 $1( ← 控制球的大小
|
[gemhead]
方法 2:Pd + Processing/p5.js(OSC 通訊)
我個人比較推薦的做法是:Pd 處理聲音,p5.js 處理視覺,兩者用 OSC 通訊。
OSC(Open Sound Control)是一個網路通訊協議,專門為即時音樂/多媒體設計:
--- Pd 端:送出 OSC 訊息 ---
[osc~ 440]
|
[env~] ← 追蹤音量包絡
|
[oscformat /audio/level] ← 打包成 OSC 訊息
|
[oscnet_send localhost 9000] ← 送到 p5.js
// p5.js 端:接收 OSC 訊息(使用 osc.js 函式庫)
const osc = new OSC();
osc.open({ port: 9000 });
let audioLevel = 0;
osc.on('/audio/level', (message) => {
audioLevel = message.args[0];
});
function draw() {
background(20);
// 根據音訊音量改變視覺
let size = map(audioLevel, 0, 1, 50, 400);
let hue = map(audioLevel, 0, 1, 200, 360);
fill(hsl(${hue}, 80%, 60%));
noStroke();
circle(width/2, height/2, size);
}
這樣你就能做到:感測器 → Arduino → Pd(聲音處理)→ OSC → p5.js(視覺化) 的完整互動流程。
實作:互動聲光裝置
讓我們把以上的知識整合,設計一個簡單的互動裝置:
概念
- 超音波感測器偵測觀眾的距離
- 距離近 → 高頻音調 + 亮色
- 距離遠 → 低頻音調 + 暗色
- 加上延遲和混響效果,讓聲音有空間感
Arduino 端
// 超音波感測器 HC-SR04
const int TRIG_PIN = 9;
const int ECHO_PIN = 10;
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
// 發射超音波
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 計算距離(cm)
long duration = pulseIn(ECHO_PIN, HIGH);
float distance = duration * 0.034 / 2.0;
// 限制在有效範圍
distance = constrain(distance, 5, 200);
// 送出資料
Serial.println(distance);
delay(50);
}
Pd Patch(概念描述)
--- 距離輸入 ---
[comport 0 115200]
|
[atof] ← 字串轉浮點數
|
[clip 5 200] ← 限制範圍
|
[smooth 20] ← 平滑化(自訂 abstraction)
| |
| [oscformat /distance]
| |
| [oscnet_send localhost 9000] ← 送給 p5.js
|
--- 頻率映射 ---
[scale 5 200 800 100] ← 距離 5-200cm → 頻率 800-100Hz
|
[osc~] ← 正弦波
|
--- 音量映射 ---
[scale 5 200 0.4 0.05] ← 近的時候大聲,遠的時候小聲
|
[*~]
|
--- 延遲效果 ---
[delwrite~ delay1 500]
[delread~ delay1 200]
|
[*~ 0.3] ← 延遲音量
|
[+~] ← 混合原始和延遲
|
--- 簡單混響 ---
[rev3~ 80 60 70 0.2] ← Pd 內建的混響
|
[*~ 0.7]
|
[dac~]
p5.js 端
// 接收距離資料,產生對應的視覺
let distance = 100;
let particles = [];
function setup() {
createCanvas(windowWidth, windowHeight);
// 連接 OSC(需要 WebSocket bridge)
connectOSC();
}
function draw() {
background(20, 30);
// 根據距離產生粒子
let rate = map(distance, 5, 200, 10, 1);
for (let i = 0; i < rate; i++) {
particles.push({
x: width / 2 + random(-50, 50),
y: height / 2 + random(-50, 50),
vx: random(-2, 2),
vy: random(-3, -1),
life: 255,
size: map(distance, 5, 200, 15, 3),
hue: map(distance, 5, 200, 0, 240),
});
}
// 更新和繪製粒子
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= 3;
if (p.life <= 0) {
particles.splice(i, 1);
continue;
}
noStroke();
fill(hsla(${p.hue}, 80%, 60%, ${p.life / 255}));
circle(p.x, p.y, p.size);
}
// 中心圓:大小隨距離變化
let centerSize = map(distance, 5, 200, 300, 50);
let centerHue = map(distance, 5, 200, 0, 240);
fill(hsla(${centerHue}, 70%, 50%, 0.3));
circle(width / 2, height / 2, centerSize);
}
學習路線建議
第一階段:聲音基礎(1-2 週)
- 安裝 Purr Data
- 做出基本的音調產生器
- 理解控制訊號和音訊訊號的差別
- 試玩濾波器和延遲效果
第二階段:MIDI 和互動(2-3 週)
- 接上 MIDI 鍵盤(或用虛擬 MIDI)
- 做一個簡單的合成器
- 加上包絡線(ADSR)
- 試試序列器(Sequencer)
第三階段:感測器整合(2-3 週)
- Arduino + Firmata + Pduino
- 超音波感測器控制音調
- 光敏電阻控制濾波器
- 加速度計控制效果參數
第四階段:聲光同步(2-4 週)
- 學會 OSC 通訊
- Pd + p5.js 的整合
- 做一個完整的互動裝置
- 展覽!
小結
PureData 是一個低調但強大的工具。它可能沒有 Ableton Live 那麼漂亮,沒有 Max/MSP 那麼精緻,但它是免費的、開源的、跨平台的,而且社群已經活躍了超過 25 年。
對一個做視覺創作的後端工程師來說,加入聲音的維度會讓你的作品有質的飛躍。視覺只能看,但聲光結合的裝置可以讓人沉浸。
而且老實說,Pd 的「接線」方式對工程師來說其實很直覺——它就像是把函式呼叫和資料流視覺化了。如果你能理解 Unix pipe,你就能理解 Pd。
延伸閱讀
- Purr Data — 推薦的 Pd 發行版
- Programming Electronic Music in Pd — 最完整的 Pd 教學書(免費線上版)
- Designing Sound — Andy Farnell 的聲音設計經典
- Pd Community — PureData 社群論壇
- OSC Protocol — Open Sound Control 官方文件