p5.js · 一行
p5.js 一行接上
同一行還能出聲音、開一台鼓機(節奏可以用一行字寫)、聽麥克風,也能把畫布連同聲音錄成影片。每一樣都像讀滑鼠:讀一個變數,寫一個函式。
現場試玩:範例 15 → 手冊裡的同一章 和 Strudel 一起用 GitHub 上的 p5/ ↗
那一頁的草圖就是普通的 p5.js,第一行是這一行。點 🎹、轉旋鈕、敲打擊墊。🔊、🥁、⏺ 也在同一個面板裡。
現成草圖: 粒子 · OP Configurator ↗ · 麥克風 · 彈出聲音 · 鼓機與小節 · 一行節奏 · 看得見的節奏 · 節奏實驗台 · 錄成影片 · Lag、包絡與示波器 · 會動的數值 · 跟 Strudel 一起 · 音名
任何一份 p5.js 草圖(OpenProcessing、p5 編輯器、你自己的網頁)在最前面加一行,就能用 MIDI 控制器、螢幕上的控制器或手機來彈:
document.head.prepend(Object.assign(document.createElement('script'), { src: 'https://openaudiovisual.com/p5/oav-p5.js' }));在 OpenProcessing 也可以不寫這行,改把網址貼到右側 Libraries 的 +(自訂函式庫)。p5 1.x、2.x 的全域模式都能用,
setup() 裡不用呼叫任何東西。帶著這一行、現在就能彈的草圖:範例 15。
像讀滑鼠一樣讀它#
function draw() {
background(knob1 * 255); // knob 1, 0..1, like mouseX
circle(width / 2, height / 2, 50 + knob2 * 300);
}
function notePressed(note, velocity) { // like keyPressed(): any key, or a pad
circle(random(width), random(height), velocity * 100);
}| 讀 | 值 |
|---|---|
knob1 … knob8、knob(n) | 0 到 1,編號照控制器上印的順序 |
fader1 … fader8、fader(n) | 0 到 1 |
pad1 … pad16、pad(n) | 按著的時候是力度,放開回到 0 |
note、velocity | 最後按的鍵(MIDI 編號)和多用力(0 到 1) |
noteIsPressed、noteIsDown(60) | 跟 keyIsPressed、keyIsDown() 一樣 |
| 寫這個函式 | 什麼時候被叫 |
|---|---|
notePressed(note, velocity)、noteReleased(note) | 按下、放開一個鍵(打擊墊也算,除非你寫了 padPressed) |
knobMoved(n, value)、faderMoved(n, value) | 轉旋鈕、推推桿 |
padPressed(n, velocity)、padReleased(n) | 打擊墊按下、放開 |
旋鈕 1 就是你手上那台的旋鈕 1:控制器設定檔記著每顆旋鈕送哪個訊息(MiniLab 3 的旋鈕 1 是 CC 74,
LPD8 是 CC 70)。OAV 沒有設定檔的控制器,你先轉的那顆就是 knob1,再轉的是 knob2。草圖裡自己已經有
note、velocity、knob1 這些名字的話,原本的會留著。
音名#
收音符號碼的地方,也都能直接寫音名(0.8):
function draw() {
if (noteIsDown('C4')) circle(width / 2, height / 2, 100); // the same as noteIsDown(60)
}
function mousePressed() {
midi.play('C#4', 0.8); // sends note 61, as a key would
}| 寫 | 得到 |
|---|---|
noteNumber('C4') | 60;給數字就原樣回來,夾在 0 到 127 |
noteName(61) | 'C#4';noteName(61, { flats: true }) 是 'Db4',noteName(61, { octave: false }) 是 'C#' |
noteFreq('A4')、noteFreq(69) | 440(赫茲),給自己合成聲音的草圖用;noteFreq(69.5) 是高四分之一音 |
noteIsDown('C4')、midi.isDown('C4')、midi.play('C#4', 0.8) | 跟寫 60、61 一樣 |
音名是字母 A 到 G(大小寫都行)、可有可無的 # 或 b(♯、♭ 也行),再加八度數字:C4、c#4、Db3、F♯2,從 C-1(0)到 G9(127)。C4 是 60,跟課程模組用的科學音高記法一致;有些軟體(Ableton、Yamaha)把 60 叫 C3,從那邊抄來的音名會差一個八度。這也是 Strudel 的寫法(c4 e4 g4,升記號 #,降記號 b)。其他寫法,像 H4、C-2,會停下來並跳出一張一行的卡片,告訴你正確怎麼寫。
notePressed(note, velocity) 拿到的仍然是號碼:noteName(note) 把它寫回音名。
搭配 OP Configurator#
OP Configurator 3000(OPC,OpenProcessing 內建)幫變數取名字、定範圍。頁面上有這一行,控制器就照宣告順序接上這些變數,不用一個一個對應:
OPC.slider('radius', 60, 10, 300); // knob 1
OPC.slider('speed', 1, 0, 5, 0.1); // knob 2
OPC.toggle('trails', true); // pad 1 flips it旋鈕依序接 slider,推桿接在旋鈕後面,打擊墊接 toggle 和 button。轉旋鈕時 OPC 面板上的滑桿跟著動,
parameterChanged() 照常會被叫。OpenProcessing 的 OPC 面板自己也能學實體控制器(面板上的 MIDI 開關),一看到它的對應在動,實體控制器就交給它,這一行繼續接螢幕上的控制器和手機(這兩個 OpenProcessing 看不到)。
OPC 變數不要取 p5 已經有的函式名稱(hue、fill),p5 1.x 會撞名。
一顆旋鈕只做一件事。 草圖自己也直接讀 knob1 的話,旋鈕 1 就歸草圖:OPC 的第一條拉桿不再跟著它(改在 OPC 面板上拉),其他旋鈕照接,並跳一次提示說明。🎹 面板的第一行就是接線表,例如「接線:旋鈕1 → knob1(草圖;radius 改用滑桿) · 旋鈕2 → speed(OPC)」。
麥克風#
同一行也接得上麥克風,草圖讀它的方式一樣:讀變數。
function draw() {
background(230, 40, 10);
fill(200 + bass * 160, 70, 100); // bass: the low end, 0..1
circle(width / 2, height / 2, 50 + micLevel * 300); // micLevel: how loud, 0..1
}
function beatDetected(level) { // like keyPressed(): a beat just landed
// flash, spawn, jump to the next scene
}| 讀 | 值 |
|---|---|
micLevel | 0 到 1,聽起來多大聲:房間本身的底噪是 0,說話大約 0.4–0.6,大喊接近 1 |
bass、mid、treble | 0 到 1,20–250 Hz、250–2000 Hz、2000–8000 Hz 三段的能量 |
beat | 每個拍子有一格是 true,像按著滑鼠時的 mouseIsPressed |
micIsOn | 麥克風有沒有在跑 |
startMic()、stopMic() | 用程式開、關(回傳 promise) |
micSensitivity(2) | 靈敏度變兩倍;micSensitivity() 回傳現在的值 |
| 寫這個函式 | 什麼時候被叫 |
|---|---|
beatDetected(level) | 一個拍子:音量比房間剛才的樣子突然跳起來(level 0 到 1) |
kickDetected(level)、snareDetected(level)、hatDetected(level) | 低、中、高頻段各自打一下(20–120 Hz、150–800 Hz、6–14 kHz) |
草圖開口要,它才開。 頁面載入時什麼都不會要。草圖第一次讀到 micLevel、bass、mid、treble、beat,或呼叫 startMic(),瀏覽器才會問你要不要給麥克風;答案回來之前,每個值都是 0。只寫了 beatDetected()、什麼值都沒讀的草圖,會在第一次點擊或按鍵時開。🎹 面板裡的 🎤 按鈕可以手動開關、顯示音量;瀏覽器擋下麥克風時,它會寫出來(從網址列左邊重新允許之後,再按一次)。
- 瀏覽器要你先點一下的話。 瀏覽器在頁面被點過之前不讓聲音動起來。這個檔在
draw()一讀到值就去要,瀏覽器擋住的話,第一次點擊或按鍵再開一次,所以不用寫userStartAudio()。 - 跟 p5.sound 並存。 分析用的是
@openav/audio(一個AnalyserNode),第一次用到時從這個檔旁邊載入,有自己的AudioContext。同一頁可以放p5.sound(p5.AudioIn、p5.FFT),兩邊的麥克風互不相干,這裡新增的名字也沒有一個跟 p5 或 p5.sound 的名字重複。p5 1.x、2.x 的全域模式都能用。 - 多大聲算大聲。
micLevel是以分貝算的「聽起來的大小」,從房間量起:最低點跟著房間自己的底噪走(一兩秒內就定下來,往上每秒最多 1.5 dB,所以一直講話不會被當成新的安靜),−10 dBFS(對著筆電大喊)是 1。所以50 + micLevel * 300在有人說話時就看得出變大。麥克風太小聲或離太遠:micSensitivity(3)(micLevel多 9.5 dB、三個頻段乘三,上限還是 1)。原始數值在OAVp5.mic.rms(0 到 1)、OAVp5.mic.db、OAVp5.mic.floor(房間底噪,dBFS)。OAVp5.mic.on('beat', fn)跟定義beatDetected一樣,回傳一個可以取消的函式。 - 你自己的名字優先。 草圖裡已經有
bass、mid(let、函式、別的函式庫)的話,原本的會留著,這個檔也不會為了它去開麥克風。 - 關掉自動開。
?mic=click(或window.OAV_P5 = { mic: 'click' }):只有 🎤 和startMic()能開;mic=off就什麼都開不起來(名字還在,值一直是 0)。跟所有用麥克風的頁面一樣,要https或localhost。 - 現成可開的麥克風草圖:
p5/examples/mic/。
聲音:彈什麼就聽到什麼#
function setup() {
createCanvas(windowWidth, windowHeight);
startSound('piano'); // every key is heard: a keyboard, the 🎹 on screen, a phone
}| 呼叫/讀 | 值 |
|---|---|
startSound(instrument) | 開聲音(預設 'piano');開著的時候再叫一次就換樂器 |
stopSound() | 關聲音 |
soundIsOn | 現在彈下去有沒有聲音 |
樂器來自 @openav/sound:piano(Salamander 平台鋼琴,第一次要載 2 MB 的取樣,載入中先用電鋼琴頂著,載不到就一直用電鋼琴)、epiano、organ、strings、choir、harp、marimba、vibraphone、xylophone、
glockenspiel、music-box,還有 synth/… 一系列合成器。🎹 面板上的 🔊 可以開關聲音,打開後有樂器選單和載入進度。
- 沒叫它就不載。 呼叫
startSound()或按 🔊 之前什麼都不載;之後從這個檔旁邊載@openav/sound,從 jsDelivr 載 Tone.js 15 (選項tone=<網址>可以改從別的地方載)。 - 先點一下。 瀏覽器要網頁被點過才讓它出聲,按 MIDI 鍵盤不算。
setup()裡呼叫startSound()的草圖,第一次點擊或按鍵之後才聽得到;在那之前 🎹 按鈕上的 🔊 是黃的。 - 打擊墊是鼓。 第 10 軌的音(MiniLab 3、LPD8 的打擊墊,General MIDI 的鼓軌)打的是鼓組,不會彈成鋼琴。延音踏板會讓音延長。
- 一行程式都不用寫。 頁面網址加
?oav_sound=piano(任何樂器名;OpenProcessing 用?sketch_oav_sound=piano),任何草圖點一下之後就有聲音;sound=off則保持安靜。 - 現成可開、邊彈邊畫的草圖:
p5/examples/keys/。
鼓機#
function setup() {
startDrums('breakbeat', 110); // a 16-step grid on screen; click a cell to change the beat
}
function kickDetected(level) { // the microphone's own callback
// swell, flash, spawn
}
function drumHit(lane, velocity) { // every hit: 'kick', 'snare', 'hat' or 'clap'
}| 讀 | 值 |
|---|---|
drumsAreOn | 鼓機有沒有在打 |
drumStep | 0 到 15,剛打到第幾格 |
drumBar | 1、2、3……,從 startDrums() 開始數第幾小節 |
drumBpm | 速度(格子上的滑桿也會改它) |
drumPhase | 0 到 1,這一小節走到哪,連續的、照聽到的時間算:rotate(drumPhase * TWO_PI) 一小節轉一圈 |
beatPhase | 0 到 1,這一拍(四分音符)走到哪:1 - beatPhase 每一拍一開始最大,慢慢縮到下一拍 |
startDrums(pattern, bpm)、stopDrums() | 開始(打著的時候再叫一次就換節奏、換速度)、停 |
| 寫這個函式 | 什麼時候被叫 |
|---|---|
drumHit(lane, velocity) | 每打一下,每一種鼓都叫(一行節奏裡的碎音鈸、疊音鈸、中鼓也是) |
kickDetected(level)、snareDetected(level)、hatDetected(level) | 大鼓;小鼓、拍手或鼓邊;鈸(閉鈸、開鈸、踩鈸) |
beatDetected(level) | 大鼓或小鼓,每一格最多一次(beat 也會有一格是 true) |
rhythmChanged(src, how) | 那一行換了:startDrums()、點格子、打字、↶(0.7) |
寫給麥克風的草圖,換成鼓機照樣動。 鼓機每打一下,叫的就是麥克風會叫的那幾個函式,所以麥克風草圖不用改就跟著鼓機動。草圖開了鼓機之後,只是寫了 kickDetected() 不會在第一次點擊時去開麥克風(有讀 micLevel 的草圖還是會問,那時兩邊都會叫它)。
- 節奏。
'four on floor'、breakbeat、'half time'、latin、sparse,自己寫的格子startDrums({ kick: 'x...x...x...x...', snare: '....x.......x...', hat: 'x.x.x.x.x.x.x.x.' })(沒寫的那軌不打),或一行節奏(下面一節)。 - 鼓機卡。 中文瀏覽器的軌道名是中文(大鼓、小鼓、鈸、拍手),速度旁邊顯示現在第幾小節,正在響的那一欄是黃框(跟深色卡片的對比 13:1,投影也看得清楚)。
- 格子放在哪。 畫面上方正中間:在 OpenProcessing 是 🎹 和 OPC 圖示中間,不擋到下面的 console;🎹 面板打開時移到面板下面。「–」收起格子,「×」關掉鼓機;🎹 面板裡的 🥁 不寫程式也能開關。🥁 打開的鼓機先不播放,勾格子上的播放框才會出聲(草圖自己寫的
startDrums()會直接播放)。 - 鼓組。
@openav/drums的合成鼓組(沒有取樣),跟聲音一樣點一下之後才聽得到;回呼從一開始就會被叫。
畫面再重,鼓都不歪。 鼓是照音訊時鐘提前排好的:Web Worker 裡的心跳每 25 毫秒醒一次,把接下來 0.1 秒內要打的鼓,照精確的時間交給鼓組。用鼓組自己的輸出量(draw() 每一格故意忙 30 毫秒):大鼓到大鼓的間隔,差異是 0.000 毫秒;
0.5 版在畫面輪到那一格才打,差異是 15 毫秒。回呼還是在畫面的某一格叫,叫的是聲音聽得到的那一格(排定的時間加上瀏覽器回報的輸出延遲),在 draw() 之前,順序跟以前一樣。
- 切到別的分頁:聲音照響、照數小節,
drumBar跟牆上的時間一致,cue 照樣觸發,barStarted每小節叫一次。每一下的鼓和stepStarted在沒人看得到的時候先不叫,回到這個分頁也不補,所以「每打一下就加一顆粒子」的草圖不會一回來就爆出一大堆。 draw()一格超過 0.1 秒:鼓機從那之後提前 0.3 秒排(🩺 會寫);代價是點格子改節奏,最多晚 0.3 秒才聽到。- 藍牙喇叭會晚 0.15 到 0.25 秒;瀏覽器有回報時回呼會等它,也可以用
?oav_drumlatency=200(毫秒)自己設。 - 保險:
?oav_drums=frame(OpenProcessing 是?sketch_oav_drums=frame)退回 0.5 版逐幀推進的鼓機。
一行節奏#
function setup() {
startDrums("bd*4, [~ sd]*2, hh*8", 110); // a kick on every beat, a snare on 2 and 4, eight hats
}不是節奏名稱的字串,就當成一行節奏;節奏名稱、自己寫的格子、數字,行為跟以前一模一樣。這一行字會顯示在格子上方,正在響的那個字會亮起來、再淡掉。
| 寫 | 意思 | 例 |
|---|---|---|
| 一個字 | 一種鼓:bd 大鼓、sd 小鼓、hh 鈸、oh 開鈸、cp 拍手、rim 鼓邊、lt mt ht 中鼓、cr 碎音鈸、rd 疊音鈸、cb 牛鈴 | "bd sd bd sd" |
~ | 休息 | "bd ~ sd ~" |
| 空白 | 幾個字平分一小節 | "bd hh sd hh" |
[ ] | 一組擠進一格 | "bd [sd sd]" |
*n | 在那一格打 n 下 | "hh*8" |
, | 再疊一行 | "bd*4, hh*8" |
< > | 每小節輪一個 | "<bd*4 [bd ~ bd ~]>" |
(k,n) | n 格裡平均打 k 下 | "bd(3,8)" |
完整的寫法在 @openav/pattern,跟鼓機一起從 oav-p5.js 旁邊載入。格子上沒有那一軌的鼓(碎音鈸、疊音鈸、中鼓、開鈸)會多一列;不在 16 格上的音(三連音)畫在它落在那一格裡的位置。startDrums("…") 寫錯了會跳一張卡(照頁面語言)說第幾個字、為什麼、怎麼改,鼓機先打 'four on floor';只寫 'x...x...'(格子的一軌)會提醒你放進 { kick: … }。
從 strudel.cc 複製來的也能用:startDrums('s("bd*4, [~ sd]*2").fast(2)') 打的是 [bd*4, [~ sd]*2]*2。讀得懂
s("…")、sound("…")("、'、` 都行;好幾個 $: s(…) 會疊在一起)、.fast(n)、.slow(n);setcpm(110/4) 或
setcps(…) 在 startDrums() 沒給速度時拿來當速度。.bank("…") 先略過(OAV 用自己合成的鼓組),鏈上其他的(.jux(rev)、
.every(…)、.gain(…))會跳一張卡說 OAV 還不支援、請按「在 Strudel 打開」;其餘照打。
一行節奏的寫法來自 TidalCycles/Strudel(strudel.cc),OAV 是依公開文件獨立實作。
格子和那一行互相改#
0.7 起,格子上方那一行一直都在(節奏名稱也會顯示它的格子寫成一行的樣子:'four on floor' 就是 bd*4, [~ sd]*2, hh*8),兩個方向都能改:
- 點格子:那一行照人會寫的樣子改寫,鼓機馬上照新的那一行打。整齊的寫成
bd*4/hh*8,每組一下的寫成[~ sd]*2,歐幾里得節奏寫成bd(3,8)(含旋轉:bd(3,8,2)晚兩格開始),其他的一拍一拍寫:bd [~ ~ ~ bd] bd*2 ~;各軌用,疊起來。如果那一行有 16 格寫不出來的東西(三連音、< >、/2、?、bd:2),第一次點只跳一張卡說哪些會被換掉,再點一次才改。 - 直接打字:點那一行就能改;按 Enter 或停手 400 ms 就套用,格子跟著變。打錯的字標紅、跳一張卡說哪裡錯,
鼓機繼續打上一個能用的版本(不會跳回
'four on floor');Esc 放棄還沒套用的部分。打的字不會觸發草圖的keyPressed()。 - 卡上的 ↶(或碰過鼓機卡之後按 ⌘Z/Ctrl+Z)回到上一版;
OAVp5.drums.history是改過的每一版。 - 複製:把
startDrums("…", 110)放進剪貼簿,直接貼進setup()。 - 在 Strudel 打開:用新分頁在 strudel.cc 打開同一行,寫成
s("…"),前面加setcpm(bpm/4)(OAV 一小節是 Strudel 的一個 cycle、四拍;strudel.cc 文件寫的是setcpm(bpm/bpc))。OAVp5.drums.strudelUrl是那個連結。
function rhythmChanged(src, how) { // how: 'code' · 'cell' · 'type' · 'undo' · 'menu'
line = src; // e.g. draw the line on the canvas, so a recording shows it
}OAVp5.drums.src 是正在打的那一行,OAVp5.drums.edit("…") 像打字一樣換一行,OAVp5.drums.undo() 回上一版。按鍵換節奏、上一步、開到 Strudel 的草圖:p5/examples/rhythm-lab/。
把節奏畫在畫布上#
鼓機卡是網頁的一部分、不在畫布上,所以錄影錄不到它。drawRhythm() 把節奏畫在畫布上:
function draw() {
background(0);
drawRhythm(20, 20, 360, 90, 'grid'); // the sixteen steps, a playhead
drawRhythm(20, 130, 300, 300, 'circle'); // a ring of 16 per drum, a hand turned by drumPhase
drawRhythm(340, 130, 300, 300, 'spiral', { bars: 4, colors: { kick: '#ff7b54' } }); // the last 4 bars, wound outwards
}drawRhythm(x, y, 寬, 高, 樣式, 選項):樣式是 'grid'、'circle' 或 'spiral';每一下在聽到的那一刻亮起來(跟
drumPhase 同一個時鐘),三連音畫在它落下的位置。選項:colors({ kick: '#f66', … },p5 認得的顏色都行)、color
(全部同一色)、off(空的格子)、playhead(顏色,或 false 不畫)、bg、bars(螺旋幾小節)。在 push()/pop()
裡用 p5 自己的圖形畫,2D 和 WEBGL、p5 1.x 和 2.x 都行;WEBGL 的 (0, 0) 在畫布正中間:
drawRhythm(-width / 2, -height / 2, width, height, 'circle')。不給位置就畫在底部一條('grid')或正中間一個正方形。三種一起用的草圖:p5/examples/rhythm-visual/。
時間軸:小節與 cue#
鼓機也是時鐘:一小節十六格,從 startDrums() 開始數。宣告第幾小節要發生什麼:
function setup() {
startDrums('breakbeat', 110);
cue(1, 'calm', 12); // bar 1, and again every 12 bars
cue(5, 'bloom', 12);
cue(9, 'storm', 12);
}
function cueFired(name, bar) { // or read cueName in draw()
look = SCENES[name];
}| 呼叫/讀/定義 | |
|---|---|
cue(bar, name, every) | 第 bar 小節(1 是第一小節,4.5 是第 4 小節的一半);有給 every 就每隔那麼多小節再來一次 |
cueName | 最近一次觸發的 cue(第一次之前是 '') |
cueFired(name, bar) | 到了某個 cue 的那一小節 |
barStarted(bar)、stepStarted(step, bar) | 每一小節的第一格;每一格 |
同樣的 cue() 叫兩次不會多一個,所以放在 draw() 裡也可以。同一格上先照宣告順序觸發 cue,再來 barStarted、stepStarted、那一格的鼓。只要時鐘不要聲音:startDrums({})。全部用上的草圖:p5/examples/drums/;一行節奏加 drumPhase、beatPhase:p5/examples/rhythm/;把節奏畫在畫布上:p5/examples/rhythm-visual/。
數值隨時間:lag、env、speed、spring、trail#
旋鈕和 micLevel 是跳著變的;好看的是它在兩次跳動之間怎麼動。TouchDesigner 用 CHOP 做這件事;這裡有同樣的運算子,在 draw() 裡一個一行,不用宣告;秒就是秒(30 fps 和 144 fps 畫出同一條曲線):
circle(width / 2, height / 2, 50 + lag(micLevel, 0.03, 0.4) * 300); // a VU meter: opens at once, closes slowly
scale(1 + env('kick', 0.01, 0.3)); // the drum machine: every kick swells it, gone in 0.3 s
rotate(spring(knob1, 100, 6) * TWO_PI); // flick a knob: it overshoots and settles第一行像 VU 表:一出聲立刻撐開、安靜下來慢慢收。第二行:鼓機每打一下大鼓就撐開一下,0.3 秒收回。第三行:旋鈕一甩,會衝過頭再彈回來停下。
| TouchDesigner | oav-p5 | |
|---|---|---|
| Lag CHOP(Lag 1/Lag 2) | lag(value, up, down) | 跟著 value 走:往上用 up 秒、往下用 down 秒(不給 down 就等於 up;預設 0.1) |
| Trigger CHOP(attack、decay) | env('kick', attack, decay)、trigger('boom') | 0..1:每個事件之後 attack 秒升到 1,decay 秒回到 0(預設 0.01、0.3)。事件:'kick' 'snare' 'hat' 'beat'(麥克風或鼓機,跟 kickDetected 聽到的是同一套)、'note'(任何琴鍵)、'bar' 'step'(鼓機)、cue 的名字、草圖自己的 trigger('boom');也可以給開關:env(mouseIsPressed, …) |
| Speed CHOP | speed(rate) | 每秒加 rate:rotate(speed(TWO_PI)) 一秒轉一圈 |
| Spring CHOP | spring(value, stiffness, damping) | 像掛在彈簧上的東西一樣跟著走(100、6:彈兩三下;damping 20:不彈) |
| Trail CHOP | trail(value, seconds) | 最近 seconds 秒(2)的值,一個陣列,每秒 60 個,舊的在前、最新的在最後 |
| Slope CHOP | slope(value) | 每秒變化多少 |
| Count CHOP | count(beat)、count(beat, 4)、count('kick') | 打開過幾次;給 4 就 0 1 2 3 0 … 循環;給名字:那個事件到現在幾次 |
| CHOP 的檢視器 | scope(value, 'label') | 頁面角落的小示波器(見下面) |
drawTrail(values, x, y, w, h) | 同樣的曲線畫在畫布上,作品裡看得到、錄影也錄得到 |
秒數跟 TouchDesigner 一樣。 lag 的秒數就是 Lag CHOP 的秒數:大約是跟上一次跳動 90% 所需的時間,所以這裡的
lag(x, 0.1) 動起來就像 Lag CHOP 填 0.1(Lag CHOP 文件)。底下是一個時間常數為「秒數 ÷ 2.3(ln 10)」的單極平滑;@openav/mapping 的 smooth 仍照它原本 63% 的時間常數。attack、decay 兩邊都是長度。
不用宣告。 draw() 裡第一個 lag() 記第一個 lag 的值、第二個記第二個,每一幀都一樣;spring()、speed()、trail() …
各自分開數。把它放進 if,或放在長度會變的迴圈裡,順序就會錯開:畫面上會有一張提示說明,之後 oav-p5 改成照「寫在哪一行」對應。最穩的是給它名字,最後一個參數放字串:lag(p.size, 0.1, 0.5, 'p' + p.id)。有名字的在哪裡呼叫都是同一個,draw()、setup()、
mousePressed() 都行;沒名字的在 draw() 外面沒有東西可以對,直接回傳原值(speed 回 0、trail 回 []),也會有提示。
env()、trigger()、count('kick') 是跟著事件走的,在哪裡都能用。草圖自己有 speed 或 count 變數的話照用自己的;
OAVp5.chop.speed(…) 是同一個函式換個地方拿。
看得見的值。 scope(value, 'label') 會把 value 原樣回傳,所以可以直接包住一段算式:
circle(x, y, scope(lag(micLevel, 0.03, 0.4), 'lag') * 300)。每個標籤是一條線,畫在 🎹 對面角落的小示波器裡:最近 4 秒和現在的值;旁邊再放一個 scope(micLevel, 'mic'),就看得到 lag 做了什麼。它在網頁上、不在畫布上:不會錄進影片和 📷,也不會擋到點擊;
hints=off 或 scope=off 就不顯示。要畫進作品裡:stroke(255); drawTrail(trail(micLevel, 3), 0, height - 100, width, 100)
(或 drawTrail(micLevel, …),它自己記 2 秒):框的底是 0、頂是 1(h 後面可以給 lo、hi),用草圖的 stroke()(noStroke() 時用白色)。
數學在 @openav/chop,第一次呼叫時從這支檔案旁邊載入;載好之前(幾幀)每個函式都直接回傳原值。參數意義參考 TouchDesigner 官方文件(Derivative),程式由 OAV 自行實作。原值和 lag 後並排、配 scope 的範例:p5/examples/lag/;用 env、spring、speed、count、slope 做一個大鼓撐開的圓盤(只靠 startDrums(),不用麥克風):p5/examples/chop/。
錄影:畫面和聲音一起錄#
function keyPressed() {
if (key === 'r') recordingIsOn ? stopRecording() : startRecording(); // startRecording(10): ten seconds, then it stops
}
function recordingSaved(info) { // info.name, info.seconds, info.bytes, info.format ('mp4' or 'webm')
}| 呼叫/讀/定義 | |
|---|---|
startRecording()、startRecording(秒數)、startRecording({ seconds, fps, mic, name, canvas, bitrate }) | 錄下畫布和作品的聲音;給秒數就錄那麼久自動停;一段最長 5 分鐘。canvas 指定要錄哪個畫布(p5 的畫布或 createGraphics(),預設是頁面上最大的 p5 畫布),bitrate 是每秒位元數(預設依畫布大小和幀率算:4 到 16 Mb/s,幀率高過 30 時最高 24 Mb/s) |
stopRecording() | 停止,檔案自動下載 |
recordingIsOn | 現在有沒有在錄 |
recordingSaved(info) | 存好了:name、seconds、bytes、format、width、height、audio、sources、warnings、blob、url |
🎹 面板上的 ⏺ 不寫程式也能開始、停止;錄影中 🎹 按鈕上有紅點和時間(點紅點就停)。📷 把畫布存成一張 PNG。
- 影片裡有什麼。 p5 的畫布,用它實際的像素(Retina 螢幕是兩倍),每秒 30 格;加上這個檔發出的所有聲音:
startSound()、鼓機和打擊墊、開著的麥克風、p5.sound 1.x,混成一軌,喇叭照常出聲。🎹、鼓機格子、提示卡片、OPC 面板都不在畫布上,所以不會進影片。 - 檔案。 瀏覽器能錄 MP4(H.264+AAC)就錄 MP4:Chrome 126 以後、Edge、Safari,IG、YouTube、剪輯軟體都直接吃。其他瀏覽器(Firefox、舊版 Chrome)錄成 WebM,卡片會說 IG 不收。檔名像
oav-20261015-183005.mp4;電腦上直接存到「下載」,手機上卡片有 分享 按鈕,可以存進相簿或直接傳到 IG。 - 每錄完一段都會檢查。 卡片寫出錄到什麼(格式、長度、大小、有沒有聲音);有問題就說發生什麼、為什麼:開著聲音卻什麼都沒錄進去(還沒點過畫面、沒有彈)、錄成 WebM、檔案幾乎是空的、畫布有畫面但影片全黑(Safari 錄 WebGL 畫布,WebKit 229611)、畫布上有別的網站的圖(沒有 CORS)、這個瀏覽器不能錄。
- 麥克風開著就會一起錄:戴耳機,不然喇叭的聲音會被錄兩次。
record=nomic不收麥克風。 - p5.sound。 給 p5 1.x 的 p5.sound 1.x 會一起錄。給 p5 2.x 的 p5.sound 把每個聲音直接接到喇叭,沒有總輸出可以接進錄影:錄不到,卡片會說。草圖改用 p5 1.11,或改用
startSound()、startDrums()。草圖自己用 Tone.js、Web Audio 做的聲音也錄不到。 - 靜態圖。 📷(或
OAVp5.record.shot())只存畫布,檔名oav-<日期>-<時間>.png,不含 🎹 和卡片;在程式裡用 p5 自己的saveCanvas('名字', 'png')也一樣。 - 選項。
record=nomic(不收麥克風)、record=off(沒有 ⏺ 和 📷,startRecording()什麼都不做)、recfps=60、reclimit=600(秒)、title=作品名(放進檔名);只改這一段:startRecording({ seconds, fps, mic: false, name, canvas, bitrate })。 - 全部用上的草圖:
p5/examples/record/。
三種輸入#
| 輸入 | 怎麼用 |
|---|---|
| 實體控制器 | Chrome、Edge、Firefox 的 Web MIDI,瀏覽器問一次就記住 |
| 螢幕上的控制器 | 按 🎹 打開 <oav-controller>,接上哪台控制器就換成哪台 |
| 手機 | 面板上的 📱 顯示 QR code,手機遙控頁的 MIDI 分頁就能彈這份草圖 |
三種輸入進來都是同一串 MIDI,草圖分不出是誰彈的(想知道的話看 midi.last.source)。Safari 和 iPad 沒有
Web MIDI,螢幕上的控制器和手機照樣能用。草圖自己寫了 Web MIDI(navigator.requestMIDIAccess、WebMidi.js)的話,螢幕和手機會變成多出來的一個輸入,名字叫 OAV Virtual MIDI。
別的程式是第四種輸入(0.7)。程式送進 OAV Virtual MIDI 輸出的東西——Strudel 的 .midi('OAV Virtual MIDI')——會在它標的那個時刻到草圖(midi.last.source 是 virtual-out);外部 MIDI 時鐘推動 drumPhase、drumBar 和鼓機;
link=<房間> 把另一個分頁、另一台電腦的程式接進這份草圖。全部寫在和 Strudel 一起用。
出狀況的時候#
彈的時候沒人會去看 OpenProcessing 畫布下面的 console,所以出狀況會直接寫在畫面上:🎹 旁邊一張小卡片,不擋畫布中間,用瀏覽器的語言,同一則只跳一次,按 × 關掉。每張都寫三件事:發生什麼、為什麼、下一步按哪裡,console 也同步寫一樣的話。
- 麥克風:被擋下(網址列左邊的鎖頭 → 允許 → 回來點一下畫面)、被電腦的系統設定擋住、在沒有
allow="microphone"的嵌入框裡、找不到麥克風、被別的程式(Zoom、OBS)占用、網址不是 https、瀏覽器還在等你按允許。 - MIDI:瀏覽器沒有 Web MIDI(Safari、iPad:改用 🎹 或手機)、MIDI 權限被擋。只對有用 MIDI 的草圖說;什麼功能都沒用到的草圖不會跳任何卡片。
- 你自己的程式:
notePressed()、kickDetected()、drumHit()這些回呼裡出錯,卡片寫出錯誤訊息、哪個檔第幾行,草圖照樣繼續跑。草圖自己宣告了note、knob1(let、var、function),原本的會留著,卡片告訴你改讀什麼(midi.last.note、knob(1))。 - 聲音和鼓機:要先點一下畫面、鋼琴還在載入(先用電鋼琴頂著)、某個樂器載不到(改用備用的)、Tone.js 連不上、鼓機在打但還沒點過畫面所以沒聲音。
- 錄影:開始錄了(錄到哪些聲音、怎麼停)、錄好了(格式、長度、大小、有沒有聲音)、錄到的影片沒有聲音、錄成 WebM(IG 不收)、整段黑、檔案幾乎是空的、畫布上有別的網站的圖、這個瀏覽器不能錄。
?oav_hints=off(或 window.OAV_P5 = { hints: 'off' })只寫在 console;OAVp5.hints 列出說過的提示。
🩺 體檢。 🎹 面板上的 🩺(或 OAVp5.check())把版本、頁面、瀏覽器、麥克風、MIDI、聲音、鼓機、錄影(這裡能錄 MP4 還是 WebM、上一段)、接線、草圖自己的名字、目前為止的提示整理成一張卡,截圖貼到 Discord 就能遠端看;有「複製文字」按鈕,也連到完整的裝置檢查。
在 Hydra 裡用#
Hydra 有自己的載入函式;同一個檔在那裡也能用,Hydra 的箭頭函式每一格都會重讀變數:
await loadScript('https://openaudiovisual.com/p5/oav-p5.js')
osc(10, 0.1, () => knob1 * 2).out() // knob 1 sets the colour offsetosc(10, 0.1, () => 1 + micLevel * 3).out() // the microphone, asked for when this line first runsnote、velocity、knob1 … knob8、fader1 …、pad1 …、drumStep,還有 🎹 螢幕上的控制器,都一樣在。
Hydra 不是 p5,沒有可以定義的 notePressed() 回呼,改用訂閱:
midi.on('notePressed', (note, velocity) => { … }) 或 OAVp5.drums.on('hit', (lane, velocity) => { … })(都回傳一個取消訂閱的函式)。測試用的是 hydra-synth 1.4.0,也就是編輯器跑的那個函式庫(p5/test/hydra-smoke.cjs)。
細節#
- 為什麼用
prepend:p5 的友善錯誤檢查會把頁面上最後一個<script>當成你的草圖。這個檔放在最前面,就不會被誤當成你的程式。 - 為什麼非同步載入的一行就夠:p5 等到視窗的
load事件才開始全域模式,而頁面載入中插進來的 script 會讓load等它跑完,所以setup()裡就已經有knob1。 - 這一行沒載到時(沒網路、網址打錯),讀
knob1的程式會停在「knob1 is not defined」。 - 嵌到別的網站:OpenProcessing 自己的頁面允許草圖用 MIDI 和麥克風,它給的嵌入碼沒有。要讓嵌入的草圖聽實體控制器,在那個
<iframe>加上allow="midi";要它聽麥克風,加allow="microphone"。 - 選項、MIDI 層的寫法和測試:
p5/README.md。範例:p5/examples/particles/、p5/examples/opc/、p5/examples/mic/、p5/examples/keys/、p5/examples/drums/、p5/examples/rhythm/、p5/examples/rhythm-visual/、p5/examples/rhythm-lab/、p5/examples/record/、p5/examples/lag/、p5/examples/chop/、p5/examples/with-strudel/、p5/examples/note-names/。