polyphon 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +374 -2
- package/README.zh-TW.md +375 -0
- package/dist/engine/automation.d.ts +24 -0
- package/dist/engine/automation.d.ts.map +1 -0
- package/dist/engine/automation.js +55 -0
- package/dist/engine/automation.js.map +1 -0
- package/dist/engine/compile.d.ts +36 -0
- package/dist/engine/compile.d.ts.map +1 -0
- package/dist/engine/compile.js +103 -0
- package/dist/engine/compile.js.map +1 -0
- package/dist/engine/jukebox.d.ts +113 -0
- package/dist/engine/jukebox.d.ts.map +1 -0
- package/dist/engine/jukebox.js +287 -0
- package/dist/engine/jukebox.js.map +1 -0
- package/dist/engine/mixer.d.ts +29 -0
- package/dist/engine/mixer.d.ts.map +1 -0
- package/dist/engine/mixer.js +141 -0
- package/dist/engine/mixer.js.map +1 -0
- package/dist/engine/output.d.ts +14 -0
- package/dist/engine/output.d.ts.map +1 -0
- package/dist/engine/output.js +51 -0
- package/dist/engine/output.js.map +1 -0
- package/dist/engine/playback.d.ts +97 -0
- package/dist/engine/playback.d.ts.map +1 -0
- package/dist/engine/playback.js +311 -0
- package/dist/engine/playback.js.map +1 -0
- package/dist/engine/prepare.d.ts +6 -0
- package/dist/engine/prepare.d.ts.map +1 -0
- package/dist/engine/prepare.js +12 -0
- package/dist/engine/prepare.js.map +1 -0
- package/dist/engine/render.d.ts +14 -0
- package/dist/engine/render.d.ts.map +1 -0
- package/dist/engine/render.js +60 -0
- package/dist/engine/render.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/instruments/bowed.d.ts +357 -0
- package/dist/instruments/bowed.d.ts.map +1 -0
- package/dist/instruments/bowed.js +502 -0
- package/dist/instruments/bowed.js.map +1 -0
- package/dist/instruments/builtin.d.ts +261 -0
- package/dist/instruments/builtin.d.ts.map +1 -0
- package/dist/instruments/builtin.js +160 -0
- package/dist/instruments/builtin.js.map +1 -0
- package/dist/instruments/definition.d.ts +46 -0
- package/dist/instruments/definition.d.ts.map +1 -0
- package/dist/instruments/definition.js +5 -0
- package/dist/instruments/definition.js.map +1 -0
- package/dist/instruments/dsp/ambience.d.ts +17 -0
- package/dist/instruments/dsp/ambience.d.ts.map +1 -0
- package/dist/instruments/dsp/ambience.js +146 -0
- package/dist/instruments/dsp/ambience.js.map +1 -0
- package/dist/instruments/dsp/core.d.ts +22 -0
- package/dist/instruments/dsp/core.d.ts.map +1 -0
- package/dist/instruments/dsp/core.js +100 -0
- package/dist/instruments/dsp/core.js.map +1 -0
- package/dist/instruments/dsp/drums.d.ts +112 -0
- package/dist/instruments/dsp/drums.d.ts.map +1 -0
- package/dist/instruments/dsp/drums.js +566 -0
- package/dist/instruments/dsp/drums.js.map +1 -0
- package/dist/instruments/dsp/modal.d.ts +30 -0
- package/dist/instruments/dsp/modal.d.ts.map +1 -0
- package/dist/instruments/dsp/modal.js +54 -0
- package/dist/instruments/dsp/modal.js.map +1 -0
- package/dist/instruments/dsp/piano.d.ts +9 -0
- package/dist/instruments/dsp/piano.d.ts.map +1 -0
- package/dist/instruments/dsp/piano.js +230 -0
- package/dist/instruments/dsp/piano.js.map +1 -0
- package/dist/instruments/dsp/pluck.d.ts +53 -0
- package/dist/instruments/dsp/pluck.d.ts.map +1 -0
- package/dist/instruments/dsp/pluck.js +195 -0
- package/dist/instruments/dsp/pluck.js.map +1 -0
- package/dist/instruments/dsp/reverb.d.ts +16 -0
- package/dist/instruments/dsp/reverb.d.ts.map +1 -0
- package/dist/instruments/dsp/reverb.js +47 -0
- package/dist/instruments/dsp/reverb.js.map +1 -0
- package/dist/instruments/dsp/staccato.d.ts +18 -0
- package/dist/instruments/dsp/staccato.d.ts.map +1 -0
- package/dist/instruments/dsp/staccato.js +147 -0
- package/dist/instruments/dsp/staccato.js.map +1 -0
- package/dist/instruments/play.d.ts +21 -0
- package/dist/instruments/play.d.ts.map +1 -0
- package/dist/instruments/play.js +140 -0
- package/dist/instruments/play.js.map +1 -0
- package/dist/instruments/registry.d.ts +63 -0
- package/dist/instruments/registry.d.ts.map +1 -0
- package/dist/instruments/registry.js +50 -0
- package/dist/instruments/registry.js.map +1 -0
- package/dist/instruments/samples.d.ts +30 -0
- package/dist/instruments/samples.d.ts.map +1 -0
- package/dist/instruments/samples.js +109 -0
- package/dist/instruments/samples.js.map +1 -0
- package/dist/instruments/voices.d.ts +76 -0
- package/dist/instruments/voices.d.ts.map +1 -0
- package/dist/instruments/voices.js +1061 -0
- package/dist/instruments/voices.js.map +1 -0
- package/dist/midi.d.ts +15 -0
- package/dist/midi.d.ts.map +1 -0
- package/dist/midi.js +280 -0
- package/dist/midi.js.map +1 -0
- package/dist/notation/format.d.ts +12 -0
- package/dist/notation/format.d.ts.map +1 -0
- package/dist/notation/format.js +102 -0
- package/dist/notation/format.js.map +1 -0
- package/dist/notation/harmony.d.ts +83 -0
- package/dist/notation/harmony.d.ts.map +1 -0
- package/dist/notation/harmony.js +191 -0
- package/dist/notation/harmony.js.map +1 -0
- package/dist/notation/melody.d.ts +25 -0
- package/dist/notation/melody.d.ts.map +1 -0
- package/dist/notation/melody.js +138 -0
- package/dist/notation/melody.js.map +1 -0
- package/dist/notation/part.d.ts +34 -0
- package/dist/notation/part.d.ts.map +1 -0
- package/dist/notation/part.js +15 -0
- package/dist/notation/part.js.map +1 -0
- package/dist/notation/transform.d.ts +28 -0
- package/dist/notation/transform.d.ts.map +1 -0
- package/dist/notation/transform.js +67 -0
- package/dist/notation/transform.js.map +1 -0
- package/dist/song.d.ts +140 -0
- package/dist/song.d.ts.map +1 -0
- package/dist/song.js +45 -0
- package/dist/song.js.map +1 -0
- package/dist/tempo.d.ts +36 -0
- package/dist/tempo.d.ts.map +1 -0
- package/dist/tempo.js +81 -0
- package/dist/tempo.js.map +1 -0
- package/dist/theory.d.ts +70 -0
- package/dist/theory.d.ts.map +1 -0
- package/dist/theory.js +183 -0
- package/dist/theory.js.map +1 -0
- package/dist/transcribe.d.ts +81 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/transcribe.js +433 -0
- package/dist/transcribe.js.map +1 -0
- package/package.json +57 -4
- package/src/engine/automation.ts +65 -0
- package/src/engine/compile.ts +124 -0
- package/src/engine/jukebox.ts +322 -0
- package/src/engine/mixer.ts +166 -0
- package/src/engine/output.ts +59 -0
- package/src/engine/playback.ts +349 -0
- package/src/engine/prepare.ts +15 -0
- package/src/engine/render.ts +71 -0
- package/src/index.ts +28 -0
- package/src/instruments/bowed.ts +660 -0
- package/src/instruments/builtin.ts +180 -0
- package/src/instruments/definition.ts +52 -0
- package/src/instruments/dsp/ambience.ts +142 -0
- package/src/instruments/dsp/core.ts +97 -0
- package/src/instruments/dsp/drums.ts +565 -0
- package/src/instruments/dsp/modal.ts +76 -0
- package/src/instruments/dsp/piano.ts +226 -0
- package/src/instruments/dsp/pluck.ts +240 -0
- package/src/instruments/dsp/reverb.ts +56 -0
- package/src/instruments/dsp/staccato.ts +141 -0
- package/src/instruments/play.ts +151 -0
- package/src/instruments/registry.ts +100 -0
- package/src/instruments/samples.ts +119 -0
- package/src/instruments/voices.ts +1168 -0
- package/src/midi.ts +285 -0
- package/src/notation/format.ts +96 -0
- package/src/notation/harmony.ts +233 -0
- package/src/notation/melody.ts +133 -0
- package/src/notation/part.ts +42 -0
- package/src/notation/transform.ts +78 -0
- package/src/song.ts +170 -0
- package/src/tempo.ts +108 -0
- package/src/theory.ts +208 -0
- package/src/transcribe.ts +487 -0
package/README.zh-TW.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Polyphon
|
|
2
|
+
|
|
3
|
+
https://github.com/user-attachments/assets/da754130-8c39-4e8d-962d-78e3fc75a40d
|
|
4
|
+
|
|
5
|
+
<p align="center"><em>Por una Cabeza</em> 電子探戈版。每個音都由 Polyphon 合成,沒有任何錄音。</p>
|
|
6
|
+
|
|
7
|
+
**用程式碼寫音樂,在瀏覽器裡演奏。** 一套記譜語法、一支 120 種樂器的合成樂團,加上一個排程器,全部建立在 Web Audio API 上。沒有任何音檔:每個音都是在瀏覽器裡算出來的。
|
|
8
|
+
|
|
9
|
+
[English](README.md)
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Jukebox, bassline, chords, defineSong, melody, pad, rhythm } from 'polyphon';
|
|
13
|
+
|
|
14
|
+
const harmony = chords('C | Am | F | G');
|
|
15
|
+
|
|
16
|
+
const theme = defineSong({
|
|
17
|
+
title: 'Morning Market',
|
|
18
|
+
bpm: 112,
|
|
19
|
+
tracks: [
|
|
20
|
+
{ instrument: 'marimba', part: 'E5:2 G5:2 C6:4 B5:2 G5:2 E5:4 | A5:4 G5:2 E5:2 C5:8 | F5:2 A5:2 C6:4 A5:2 F5:2 D5:4 | G5:6 F5:2 E5:4 D5:4 |' },
|
|
21
|
+
{ instrument: 'strPad', part: pad(harmony, { center: 'C4', velocity: 0.4 }), gain: 0.6 },
|
|
22
|
+
{ instrument: 'pizzBass', part: bassline(harmony, 'x...x.x.x...x.x.', '1 5 8 5') },
|
|
23
|
+
{ instrument: 'kick', part: rhythm('X.......x.......') },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const music = new Jukebox();
|
|
28
|
+
music.play(theme); // 瀏覽器要求使用者互動時,會在第一次點擊或按鍵時開始
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Polyphon 源自生物對戰 RPG [Eldor(艾爾多)](https://github.com/alan890104/eldor) 的音樂引擎。
|
|
32
|
+
|
|
33
|
+
## 聆聽
|
|
34
|
+
|
|
35
|
+
所有範例都由 Polyphon 自己算出來,沒有任何錄音。其中六首在它們的音樂盒唱盤上播放:
|
|
36
|
+
|
|
37
|
+
https://github.com/user-attachments/assets/3dd6375e-73da-4b24-bd01-fde3ada79b8c
|
|
38
|
+
|
|
39
|
+
https://github.com/user-attachments/assets/e0cf3e6e-2149-4503-a2e3-3facc73d2753
|
|
40
|
+
|
|
41
|
+
https://github.com/user-attachments/assets/7bf0cd1c-3810-4a21-96bb-052546b22d0d
|
|
42
|
+
|
|
43
|
+
https://github.com/user-attachments/assets/32e30b01-1e8a-49c2-b495-7dad8942bd93
|
|
44
|
+
|
|
45
|
+
https://github.com/user-attachments/assets/b75ce45f-5b55-4657-b7ac-4200df6921b1
|
|
46
|
+
|
|
47
|
+
https://github.com/user-attachments/assets/2b1fafc0-cbc6-424c-b731-b079f3d0606e
|
|
48
|
+
|
|
49
|
+
全部的 MP3:
|
|
50
|
+
|
|
51
|
+
| 曲目 | 內容 | |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Morning Market | 原創,C 大調的市集小曲:馬林巴主旋律,撥奏低音、沙鈴與輕柔的弦樂 | [▶ 0:24](docs/audio/morning-market.mp3) |
|
|
54
|
+
| Night Waltz | 原創,A 小調圓舞曲:鋼琴配豎琴琶音、弦樂墊音與大提琴 | [▶ 0:20](docs/audio/night-waltz.mp3) |
|
|
55
|
+
| After Midnight | 原創,B 小調鋼琴抒情曲,慢速 R&B 律動,加上弦樂與小提琴 | [▶ 1:42](docs/audio/after-midnight.mp3) |
|
|
56
|
+
| Neon Tide | 原創,F 小調前衛浩室:推升、抽吸的 supersaw drop、鋼琴間奏、最終 drop | [▶ 2:35](docs/audio/neon-tide.mp3) |
|
|
57
|
+
| Por una Cabeza (Electro Tango) | Gardel 的探戈改編成電子探戈:副歌由獨奏小提琴和弦樂唱出,大鼓踩著探戈的 3-3-2;中段是新寫的 A 小調旋律,由大提琴獨奏;推升、全體靜止一拍,最後的副歌以八度齊奏 | [▶ 1:44](docs/audio/por-una-cabeza.mp3) |
|
|
58
|
+
| Moonlight Sonata | 貝多芬《月光》第一樂章開頭,用建模鋼琴演奏 | [▶ 0:43](docs/audio/moonlight.mp3) |
|
|
59
|
+
| Canon in D | 帕海貝爾《卡農》全曲:三把小提琴輪唱,大提琴反覆低音 | [▶ 4:08](docs/audio/canon.mp3) |
|
|
60
|
+
| Canon in D Minor | 同一首卡農移到 D 小調,以原曲轉換而成 | [▶ 4:08](docs/audio/canon-minor.mp3) |
|
|
61
|
+
| Symphony No. 5 | 貝多芬《第五號交響曲》第一樂章呈示部,完整管弦樂團 | [▶ 1:19](docs/audio/symphony5.mp3) |
|
|
62
|
+
| Carmen: Prelude | 比才《卡門》第一幕前奏曲全曲,完整管弦樂團:鬥牛士進場、鬥牛士之歌、命運動機 | [▶ 3:36](docs/audio/carmen.mp3) |
|
|
63
|
+
| Carmen: Habanera | 比才《卡門》哈巴涅拉舞曲〈愛情是隻不羈的鳥〉的開頭:她的旋律由小提琴獨奏,走在大提琴的哈巴涅拉節奏上,從 D 小調走進 D 大調的合唱 | [▶ 0:50](docs/audio/habanera.mp3) |
|
|
64
|
+
|
|
65
|
+
## 它能做什麼
|
|
66
|
+
|
|
67
|
+
- **讀起來像樂譜的記譜法。** 旋律是短字串(`'E5:4 D5:2 C5:2 |'`),鼓是格子(`'X...x...'`),和聲是和弦記號(`'Am7 | D7 | Gmaj7'`)。墊音、琶音、低音線、刷弦都從和弦進行長出來。所有東西都是純資料,你也可以自己產生音符。
|
|
68
|
+
- **沒有取樣的樂團。** 鋼琴和撥弦用物理模型,弓弦樂一位一位演奏者分開算,管樂、銅管、合唱、合成器即時合成。取樣型樂器的每個音只算一次並快取,播放時幾乎不耗 CPU。
|
|
69
|
+
- **給遊戲用的點唱機。** 換曲淡入淡出、預先準備、暫停/繼續/跳轉、在音效下壓低音樂、前奏接循環,並自動處理瀏覽器的音訊解鎖。
|
|
70
|
+
- **匯入與匯出。** `songFromMidi` 讀標準 MIDI 檔,`transcribe` 把哼唱的旋律寫成樂譜;`renderSong` 加 `encodeWav` 把任何曲子輸出成 WAV。
|
|
71
|
+
- **音樂鑑賞室。** `npm run dev` 打開播放器,收錄所有範例,附分軌混音台;每首歌都依自己的音符畫成一張打孔圓盤(就像 polyphon 音樂盒的圓盤),播放時跟著轉動。
|
|
72
|
+
|
|
73
|
+
## 安裝
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install github:alan890104/polyphon
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
內含 TypeScript 型別,沒有任何執行期相依套件。
|
|
80
|
+
|
|
81
|
+
## 寫音樂
|
|
82
|
+
|
|
83
|
+
時間以 **step** 計:一個 step 是一個十六分音符,四分音符是 4,4/4 拍一小節是 16。
|
|
84
|
+
|
|
85
|
+
### 旋律
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
melody('E5:4 D5:2 C5:2 | B4:8 r:8 |')
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| 寫法 | 意思 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `E5:4` | E5,長 4 個 step(四分音符) |
|
|
94
|
+
| `E5 D5` | 省略長度就沿用上一個 |
|
|
95
|
+
| `r:8` 或 `.` | 休止 |
|
|
96
|
+
| `R` | 整小節休止 |
|
|
97
|
+
| `\|` | 小節線:會檢查每小節長度,寫錯會直接報出是第幾小節 |
|
|
98
|
+
| `C4+E4+G4:16` | 和弦 |
|
|
99
|
+
| `!E5` / `?E5` | 重音/輕音 |
|
|
100
|
+
| `E5:4>` | 滑音到下一個音(支援滑音的樂器) |
|
|
101
|
+
| `G4:8~ \| G4:4` | 連結線:同一個音跨過小節線延續 |
|
|
102
|
+
| `E5:4/3` | 分數長度:三連音八分音符是 `4/3` |
|
|
103
|
+
| `(E5:2 D5:2)*4` | 重複一組 |
|
|
104
|
+
|
|
105
|
+
其他拍號傳入拍號即可:`melody('G4:6 A4:2 B4:4 |', '3/4')`。旋律字串也可以直接當軌道的 `part`,會用曲子的拍號解讀。
|
|
106
|
+
|
|
107
|
+
### 節奏
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
rhythm('X...x...X..xx...') // X 重音、x 一般、o 輕、. 或 - 休止
|
|
111
|
+
rhythm('x.x.x.x.', { pitch: 'G2' }) // 每一下都打同一個音高
|
|
112
|
+
rhythm('x.x.', { step: 2 }) // 每個字元 2 個 step
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 和聲
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const harmony = chords('Am | F | C G | E7'); // 同一小節的和弦平分時間
|
|
119
|
+
chords('Dm:12 A7:4 | %'); // 明確寫長度;% 表示上一個和弦再延一小節
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
支援的和弦:大三、`m`、`dim`、`aug`、`sus2`、`sus4`、`5`、`6`、`m6`、`7`、`maj7`、`m7`、`mmaj7`、`m7b5`、`dim7`、`7sus4`、`add9`、`madd9`、`9`、`maj9`、`m9`、`7b9`、`7#9`、`m11`、`11`、`maj7s11`、`6/9`、`q` 與 `q4`(四度疊置),以及斜線低音(`C/E`)。
|
|
123
|
+
|
|
124
|
+
從和弦進行產生聲部:
|
|
125
|
+
|
|
126
|
+
| 函式 | 產生 |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `pad(harmony, { center, size, velocity, gap })` | 長音和弦,聲部會挑移動最少的轉位 |
|
|
129
|
+
| `arpeggio(harmony, '0 1 2 1', { step, center, size, velocity, accent })` | 分解和弦:數字從下往上選和弦音(超過就升八度),`.` 休止,`-` 延長 |
|
|
130
|
+
| `bassline(harmony, 'x...x.x.', '1 5 8 5', { register, velocity })` | 低音線:節奏每小節循環,級數依序使用(`1 3 5 8 -8 b7 7 2 4 6 b2 -5 -4`) |
|
|
131
|
+
| `roots(harmony, { register, fifth, velocity })` | 每個和弦一個長低音,可疊空五度 |
|
|
132
|
+
| `strum(harmony, 'x.x-', { center, size, spread, velocity })` | 刷弦 |
|
|
133
|
+
|
|
134
|
+
`center` 和 `register` 可以寫音名(`'C4'`)或 MIDI 數字。
|
|
135
|
+
|
|
136
|
+
### 組合聲部
|
|
137
|
+
|
|
138
|
+
| 函式 | 作用 |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `sequence(a, b, ...)` | 一段接一段(字串視為 4/4 旋律) |
|
|
141
|
+
| `stack(a, b, ...)` | 同時發聲 |
|
|
142
|
+
| `repeat(part, n)` | 連續 n 次 |
|
|
143
|
+
| `rest(bars, meter?)` | 休止幾小節 |
|
|
144
|
+
| `transpose(part, semitones)` | 移調 |
|
|
145
|
+
| `mapPitch(part, fn)` | 改寫音高(例如 `remapToMode` 換調式) |
|
|
146
|
+
| `scaleVelocity(part, factor)` | 整體變大聲或變小聲 |
|
|
147
|
+
| `slice(part, from, to)` | 切出一段 step 範圍 |
|
|
148
|
+
| `pedal(part, changes)` | 延音踏板:音會一直響到下一次換踩(每 n 個 step、指定的 step 清單,或跟著和弦進行換),或同一個鍵再被敲下 |
|
|
149
|
+
|
|
150
|
+
樂理工具:`midi('C#4')`、`frequency('A4')`、`noteName(61)`、`parseChord('F#m7b5')`、`voiceChord`、`scaleDegree('D4', 'dorian', 2)`、`remapToMode`、`romanChord('iv7', 'D4', 'aeolian')`,以及 `MODES`(從 ionian 到 locrian、和聲與旋律小調、弗里吉亞屬調、匈牙利小調、雙和聲、五聲音階、全音、八聲音階等)。
|
|
151
|
+
|
|
152
|
+
### 曲子
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
defineSong({
|
|
156
|
+
title: 'Night Waltz',
|
|
157
|
+
description: 'A 小調,3/4',
|
|
158
|
+
bpm: 84, // 開頭的速度
|
|
159
|
+
tempo: [{ bar: 33, bpm: 72, over: 4 }], // 速度變化,見下方
|
|
160
|
+
silences: [{ bar: 32.75, beats: 1 }], // 全體靜止,見下方
|
|
161
|
+
meter: '3/4', // 預設 '4/4'
|
|
162
|
+
swing: 0, // 八分音符搖擺 0-0.5;十六分音符用 swing16
|
|
163
|
+
reverb: { size: 2.6, damp: 0.5, preDelay: 0.02, early: 0.3, mix: 1 },
|
|
164
|
+
delay: { steps: 3, feedback: 0.3, tone: 2800, mix: 0.8 }, // 跟速度同步的左右乒乓延遲
|
|
165
|
+
gain: 1,
|
|
166
|
+
master: { warm: 2, cut: 1, trim: -1 }, // 母帶等化(dB);glue 與 loud 見下方
|
|
167
|
+
intro: [/* 只播一次的軌道 */],
|
|
168
|
+
tracks: [/* 循環的軌道 */],
|
|
169
|
+
loop: true, // false:只播一次(短曲、號角)
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
一個段落的長度等於最長的軌道(補足整小節),較短的軌道會重複填滿,除非設 `repeat: false`。
|
|
174
|
+
|
|
175
|
+
速度可以在曲中改變,可以突然換,也可以逐漸變快變慢。小節從整首歌的開頭算起(前奏的小節在前),循環的主體每一輪都會重新走一次這些變化。
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
tempo: [
|
|
179
|
+
{ bar: 17, bpm: 66 }, // 從第 17 小節起換成新速度
|
|
180
|
+
{ bar: 29, bpm: 50, over: 4 }, // 漸慢:第 29 到 32 小節逐漸慢到 50
|
|
181
|
+
{ bar: 33, bpm: 84 }, // 回原速
|
|
182
|
+
{ bar: 40.5, bpm: 40 }, // 第 40 小節的後半放慢撐長:延長記號
|
|
183
|
+
{ bar: 41, bpm: 84 },
|
|
184
|
+
]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`tempoClock(song)` 可以把樂譜上的位置換算成秒數(也能反過來),給任何需要跟著音樂走的東西使用。
|
|
188
|
+
|
|
189
|
+
`silences` 是全體靜止:從指定的小節(算法同上)開始,所有聲音停住指定的拍數,連殘響和延遲的尾音都一起切掉,像整個空間突然被關掉,結束後音樂以原本的音量直接砸回來。只把音符拿掉是休止符;`silences` 做的是 drop 之前那一拍什麼都沒有的真空。
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
silences: [{ bar: 32.75, beats: 1 }], // 第 32 小節的最後一拍
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
軌道欄位:
|
|
196
|
+
|
|
197
|
+
| 欄位 | 預設 | |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| `instrument` | | 內建樂器名稱(編輯器會自動補全)或自己註冊的名稱 |
|
|
200
|
+
| `part` | | 一個 part,或一段旋律字串 |
|
|
201
|
+
| `name` | 樂器名稱 | 混音台上顯示的名字 |
|
|
202
|
+
| `gain` | 1 | 音量 |
|
|
203
|
+
| `pan` | 0 | -1 左到 1 右 |
|
|
204
|
+
| `reverb` | 0.25 | 送到殘響的量 |
|
|
205
|
+
| `delay` | 0 | 送到延遲的量 |
|
|
206
|
+
| `duck` | | 跟著另一軌的敲擊下沉:那一軌的名字(`'Kick'`),或 `{ by, depth, release }` |
|
|
207
|
+
| `lowpass` | | 低通截止頻率(Hz),或掃頻 `{ step: Hz }` |
|
|
208
|
+
| `highpass` | | 高通截止頻率(Hz),或掃頻 |
|
|
209
|
+
| `transpose` | 0 | 半音 |
|
|
210
|
+
| `humanize` | 0.008 秒(鼓 0.003) | 時間上的人味 |
|
|
211
|
+
| `params` | | 樂器參數:`attack`、`release`、`bright`、`glide`、`ring`、`tune` 等 |
|
|
212
|
+
| `repeat` | true | 重複填滿段落 |
|
|
213
|
+
|
|
214
|
+
### 電子音樂的混音
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const harmony = chords('Fm | Db | Ab | Eb');
|
|
218
|
+
|
|
219
|
+
defineSong({
|
|
220
|
+
title: 'Drop',
|
|
221
|
+
bpm: 128,
|
|
222
|
+
master: { glue: 0.5, loud: 8 },
|
|
223
|
+
tracks: [
|
|
224
|
+
{ name: 'Kick', instrument: 'bigKick', part: rhythm('X...'.repeat(64), { pitch: 'F1' }) },
|
|
225
|
+
{ name: 'Chords', instrument: 'supersaw', part: pad(harmony, { center: 'Ab4' }), duck: 'Kick' },
|
|
226
|
+
{ name: 'Sub', instrument: 'sub', part: roots(harmony, { register: 'F1' }), duck: { by: 'Kick', depth: 0.9 } },
|
|
227
|
+
{ name: 'Pluck', instrument: 'pluck', part: arpeggio(harmony, '0 1 2 3', { center: 'C5' }), lowpass: { 0: 400, 128: 400, 256: 18000 } },
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
- **側鏈抽吸。** `duck: 'Kick'` 讓這一軌在同一段落裡名為 Kick 的那一軌每敲一下就往下沉,再慢慢回升:也就是 house 與 EDM 那種「抽吸」的律動。`depth` 是下沉多深(0-1,預設 0.7),`release` 是幾個 step 回到原音量(預設 4,一拍)。它跟著音符走而不是跟著聲音,所以跳轉和循環時都準確,大鼓靜音或 `gain: 0` 時也照樣會抽吸。
|
|
233
|
+
- **濾波掃頻。** `lowpass` 與 `highpass` 接受一個截止頻率(Hz),或從段落開頭算起的轉折點 `{ step: Hz }`。截止頻率在轉折點之間依音高均勻滑動,第一個之前與最後一個之後維持不變,所以上面的撥音前八小節是暗的,接下來八小節逐漸打開。每次循環都會重新掃一次。
|
|
234
|
+
- **母帶。** `glue`(0-1)是讓整體混音黏在一起的匯流排壓縮。`loud` 把歌曲做成大聲的母帶:推進磚牆限幅器的 dB 數,連取樣點之間的峰值都壓在 -1 dBFS 以下(EDM 大約 6 到 10)。設了 `loud` 的歌會跳過其他歌共用的溫和限幅器,不會被壓縮兩次。
|
|
235
|
+
|
|
236
|
+
## 播放
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const music = new Jukebox({
|
|
240
|
+
songs: { // 可選:用名稱播放的曲目
|
|
241
|
+
town: () => import('./music/town.ts'), // 第一次用到時才載入、準備
|
|
242
|
+
battle: battleSong,
|
|
243
|
+
},
|
|
244
|
+
volume: 0.8,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
music.play('town'); // 從正在播的曲子淡入淡出切過來
|
|
248
|
+
music.preload('battle'); // 先把樣本算好
|
|
249
|
+
await music.play('battle', { fadeOut: 0.5, fadeIn: 0.05 });
|
|
250
|
+
music.duck(-10); // 大音效時壓低 10 dB,0.35 秒後回來
|
|
251
|
+
music.duck(-30, { attack: 0.12, hold: 4, release: 0.6 });
|
|
252
|
+
music.pause();
|
|
253
|
+
music.resume();
|
|
254
|
+
music.seek(30);
|
|
255
|
+
music.playOnce(victoryJingle); // 疊在上面播
|
|
256
|
+
music.stop();
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`play` 在曲子開始時回傳 `Playback`;如果在準備好之前又要求了別首,則回傳 `null`(`music.playback` 是目前這一個)。`Playback` 提供 `position()`、`duration`、`introDuration`、`loopDuration`;它的 `tracks` 可以逐軌靜音(`track.muted = true`),並提供 `output` 節點接音量表;有結尾的曲子播完時 `finished` 會 resolve。`play` 傳入 `{ loops: n }` 可以在播完 n 輪後結束。
|
|
260
|
+
|
|
261
|
+
Jukebox 會在第一次使用時建立 `AudioContext`,並在第一次點擊、觸控或按鍵時啟動。要跟音效共用同一個 context,傳入 `context` 和 `destination` 即可。
|
|
262
|
+
|
|
263
|
+
## 樂器
|
|
264
|
+
|
|
265
|
+
| 類別 | 樂器 |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| 鍵盤 | `piano` `softPiano` `epiano` `revPiano` `organ` `organFull` `accordion` |
|
|
268
|
+
| 撥弦 | `harp` `nylon` `steel` `lute` `dulcimer` `pizz` `pizzBass` |
|
|
269
|
+
| 敲擊旋律與鐘 | `kalimba` `marimba` `vibes` `glock` `celesta` `musicbox` `bell` `chime` `steelpan` |
|
|
270
|
+
| 弓弦 | `strings` `strPad` `violin` `cello` `celli` `contrabass` `fiddle` `morin` `spicc` |
|
|
271
|
+
| 木管 | `flute` `panflute` `ocarina` `whistle` `clarinet` `oboe` `bassoon` `sax` `tenorSax` |
|
|
272
|
+
| 銅管 | `horn` `horns` `trumpet` `trumpets` `trombone` `tuba` `brassStab` |
|
|
273
|
+
| 人聲 | `choir` `choirOo` `choirO` `menChoir` `chant` `soprano` `throat` |
|
|
274
|
+
| 墊音與持續音 | `warmPad` `glassPad` `darkPad` `airPad` `glass` `drone` |
|
|
275
|
+
| 合成器與吉他 | `supersaw` `synthLead` `pluck` `powerGtr` `muteGtr` |
|
|
276
|
+
| 低音 | `bass` `synthBass` `reese` `acid` `bass808` `sub` `distBass` |
|
|
277
|
+
| 鼓 | `kick` `boom` `snare` `field` `rim` `clap` `hat` `ohat` `ride` `crash` `swell` `tom` `taiko` `timpani` `frame` `darbuka` |
|
|
278
|
+
| 電子鼓 | `kick808` `kick909` `bigKick` `snare909` `clap909` `hat909` `ohat909` |
|
|
279
|
+
| 打擊 | `shaker` `tamb` `woodblock` `clave` `anvil` `triangle` `gong` `chain` `snap` `brush` `drip` `thunder` `thunderclap` |
|
|
280
|
+
| 環境音與效果 | `wind` `sea` `fire` `crickets` `rain` `void` `cave` `birds` `riser` `downlifter` `impact` |
|
|
281
|
+
|
|
282
|
+
它們是怎麼做的:
|
|
283
|
+
|
|
284
|
+
- **鋼琴**:每根弦(每個音一到三根,彼此微微走音)都用 modal synthesis 計算,包含非諧和性、擴展調律、兩段式衰減、隨頻率增加的弦損耗;槌子的毛氈會把高音的上方泛音修圓;其他弦的共鳴;響板、制音器的聲音與放鍵後的微弱餘韻;隨力度變化的明亮度;以及從低音到高音左右展開的立體聲。
|
|
285
|
+
- **撥弦**:Karplus-Strong,以符合物理的撥弦波形激發(第 n 個泛音 ∝ sin(nπ·撥弦位置)/n),弦損耗隨頻率增加,用 allpass 精準調音,再加上琴身共鳴(吉他、魯特琴、豎琴、揚琴、小提琴);低音撥弦用加法合成。
|
|
286
|
+
- **弓弦**:聲部裡每位演奏者分開算,各自有音準、顫音、運弓點和弓毛雜音,再通過上百個共鳴的琴身(依真實小提琴、大提琴、低音提琴的錄音擬合),讓顫音帶動高頻泛音閃動。弓弦波形的圓角落在固定的泛音次數,所以低音弦比高音弦暗。持續段可以無縫循環;跳弓在弓彈離後讓弦自然餘響。
|
|
287
|
+
- **敲擊旋律與鐘**:依真實琴鍵、簧片與管鐘的分音比例做 modal synthesis(教堂鐘有 hum、prime、tierce、quint、nominal;管鐘的音高是由約 2 : 3 : 4 的分音聽出來的),琴槌的接觸時間會讓高音的泛音變圓潤。
|
|
288
|
+
- **管樂與銅管**:把嘴唇、簧片或氣流建模成一個閥門(正弦波通過非線性曲線),所以音色會像真的樂器一樣隨力度變亮:小聲的小號柔和、大聲的嘹亮,單簧管低音區以奇次泛音為主,薩克斯風吹得用力時會帶點沙啞。管身與喇叭口用濾波器和共振峰表現;聲部裡的每位演奏者進來的時間差幾毫秒、音準差幾音分。
|
|
289
|
+
- **人聲**:共振峰(合唱真的在唱母音)、氣音和每個音各自的顫音。
|
|
290
|
+
- **鼓**:鼓皮用圓形薄膜的振動模態計算,敲擊後音高會從略高處滑落,並帶有鼓棒或鼓槌的撞擊聲;定音鼓的音高來自鼓腔內空氣把模態推成約 1 : 1.5 : 2 : 2.5 : 3;鈸的「刷」聲在敲擊後由低往高擴散,高頻先消失;敲得越輕越暗;每種鼓有隨機變體,連打不會像機關槍。
|
|
291
|
+
- **合成器**:類比減法合成。振盪器自由運轉(每個音從波形的隨機位置開始,音準偏差一兩音分),送進四極低通濾波器(24 dB/oct,像電晶體梯形濾波器),截止頻率跟著音高走,濾波包絡彈得越用力開得越大。`supersaw` 依 Adam Szabo 對 Roland JP-8000 的量測重現(七個鋸齒波,照原機的走音與混音曲線,並在基頻處高通);`synthLead` 是 Minimoog 風格的主奏,`pluck` 是 trance 琶音的撥音,`warmPad` 與 `darkPad` 是 Juno 風格帶合唱效果的墊音。`acid` 是 TB-303(`!` 重音、`>` 滑音),`reese` 是翻滾的走音低音,`bass808` 是會滑音的 808;低音在 120 Hz 以下都維持單聲道。`cut`、`q`、`env`、`detune`、`attack`、`release`、`glide` 可以調整每種音色。
|
|
292
|
+
- **電子鼓**:依 Roland 鼓機建模,並對照錄音擬合。808 大鼓是橋式 T 型共振器,敲擊瞬間音高會短暫上揚;909 大鼓是帶快速音高包絡與雜訊敲擊聲的振盪器;拍手是相隔約 11 毫秒的三次雜訊加上一段殘響尾巴;909 腳踏鈸和原機的記憶體一樣是 6 位元,閉合的腳踏鈸會切斷還在響的開放腳踏鈸。`bigKick` 是現代分層的 EDM 大鼓:音高急墜的衝擊、削波的超低音和高頻敲擊聲。給大鼓一個音高就能配合歌曲調音(`rhythm('X...X...X...X...', { pitch: 'F1' })`);`riser` 與 `downlifter` 的長度等於音符長度,所以 `melody('C4:128')` 就是八小節的推升。
|
|
293
|
+
- **空間**:每首歌有自己程式產生的捲積殘響。
|
|
294
|
+
|
|
295
|
+
自訂樂器:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
registerInstrument('beep', {
|
|
299
|
+
play({ context, destination, time, duration, frequency, velocity }) {
|
|
300
|
+
const osc = new OscillatorNode(context, { type: 'square', frequency });
|
|
301
|
+
const amp = new GainNode(context, { gain: velocity * 0.2 });
|
|
302
|
+
osc.connect(amp).connect(destination);
|
|
303
|
+
osc.start(time);
|
|
304
|
+
osc.stop(time + duration);
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
registerInstrument('myBell', {
|
|
309
|
+
render: (sampleRate, midi) => renderBell(sampleRate, midi), // 回傳 Float32Array,每個音高只算一次
|
|
310
|
+
sustain: 'ring',
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## MIDI、音檔與哼唱
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const song = songFromMidi(await file.arrayBuffer(), { title: file.name });
|
|
318
|
+
music.play(song);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`songFromMidi` 會讓音符留在檔案的拍子格線上,速度變化則成為歌曲本身的速度變化,並讀取拍號、延音踏板、音量與聲像。General MIDI 音色會對應到樂團裡的樂器,第 10 頻道對應到鼓組。
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
const buffer = await renderSong(song, { loops: 1 }); // OfflineAudioContext,比即時快
|
|
325
|
+
const wav = encodeWav(buffer); // 16-bit PCM
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
WAV 是無損的,但檔案很大,每分鐘約 10 MB。要 MP3(大約七分之一大)就把 buffer 交給 MP3 編碼器;鑑賞室是在 worker 裡用 [lamejs](https://github.com/zhuker/lamejs) 編碼(`player/src/mp3.ts`),所以函式庫本身不需要任何編碼器。
|
|
329
|
+
|
|
330
|
+
一次唱一個音的旋律(哼唱或歌唱)可以寫成樂譜。`PitchTracker` 隨著聲音進來追蹤音高,來源可以是麥克風或音檔;`transcribe` 再把聽到的內容放到拍子格線上:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
const tracker = new PitchTracker(audio.sampleRate);
|
|
334
|
+
tracker.push(audio.getChannelData(0));
|
|
335
|
+
const { melody, bpm, key } = transcribe(tracker.frames);
|
|
336
|
+
// 'r:12 Bb4:4 | Bb4:3 C5 D5:2 D5:8 | A4:12 G4:4 | ...', 96.8, 'G minor'
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
這不需要訓練過的模型:人聲的音高就藏在聲音本身的週期裡。唱得整體偏高、偏低或越唱越偏,都會被跟上;落在兩個半音中間的音,會選調內的那一個。跟著節拍器唱時(`{ bpm, start }`:它的速度和第一小節從哪裡開始),音符會落在節拍器的拍子上;沒有節拍器時,速度和強拍位置會從音符本身推算出來。音高改變、聲音停了再出來,或同一個音重新發出,都算新的一個音;只有明顯的停頓才會寫成休止符。`formatMelody` 能把任何單聲部的段落寫回旋律字串,並依調號拼寫升降記號。
|
|
340
|
+
|
|
341
|
+
## 音樂鑑賞室
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
npm install
|
|
345
|
+
npm run dev
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
播放器收錄所有範例(貝多芬《月光》與《第五號交響曲》、帕海貝爾《D 大調卡農》與移到 D 小調的同一首卡農,以及幾首短曲),有可以靜音或獨奏各軌的混音台、MP3 與 WAV 下載和 MIDI 匯入。每首歌的封面就是它的樂譜:音符打成圓盤上的孔,角度是時間、半徑是音高,主旋律是黃銅色。播放時圓盤在簧片梳下轉動,孔經過時會亮起來。
|
|
349
|
+
|
|
350
|
+
按 **F**(或視覺化按鈕)進入沉浸模式:全螢幕,聆聽時控制列會自動淡出。可以選擇樣式(預設是一台 3D 的圓盤音樂盒:你聽到每個音的同時,它的孔正好撥動簧片,盒裡的燈把正在響的音投到盒蓋上;另有被頻譜環繞的轉動圓盤、落到鋼琴鍵盤上的音符、頻譜、波形、山脊圖、粒子)、背景(墨色、這首歌的圓盤、極光、星空或你自己的圖片;音樂盒有自己的房間)和配色主題,設定會被記住。音樂盒、落下的音符和圓盤都直接照樂譜走,和你聽到的聲音同步。匯入的 MIDI 只留在你的瀏覽器裡。
|
|
351
|
+
|
|
352
|
+
按側欄的 **Hum a tune** 把旋律寫成樂譜:對著麥克風用「da」哼或唱,可以開節拍器跟著拍子,也可以照自己的速度;或者直接打開一段錄音。唱的時候畫面會即時畫出你的音高,唱完音符會排在捲軸上,用你選的樂器演奏(也可以把你的聲音疊在下面對照),樂譜可以直接複製,一個按鈕就能把這首曲子加進 Your songs,和匯入的 MIDI 放在一起。你唱的東西不會離開瀏覽器。
|
|
353
|
+
|
|
354
|
+
## 開發
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
npm test # 記譜、樂理、MIDI 匯入、哼唱轉譜,以及每一首範例曲(Node,不需要瀏覽器)
|
|
358
|
+
npm run typecheck
|
|
359
|
+
npm run build # 函式庫,輸出到 dist/
|
|
360
|
+
npm run build:player # 鑑賞室,輸出到 player/dist/
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Node 22.18 以上可以直接執行 TypeScript 原始碼。
|
|
364
|
+
|
|
365
|
+
## 致謝
|
|
366
|
+
|
|
367
|
+
- 《月光》範例的音高對照 [Mutopia Project](https://www.mutopiaproject.org) 的版本(樂曲本身是公有領域;該版本以 CC BY-SA 2.5 授權)。
|
|
368
|
+
- 《第五號交響曲》範例的音符對照 [Mutopia Project](https://www.mutopiaproject.org) 的版本(樂曲與該版本都是公有領域)。
|
|
369
|
+
- 《卡農》範例的音符對照 [Mutopia Project](https://www.mutopiaproject.org) 的版本(樂曲本身是公有領域;該版本以 CC BY 4.0 授權)。
|
|
370
|
+
- 鋼琴的弦損耗模型參考 A. Chaigne 與 A. Askenfelt 的 *Numerical simulations of piano strings*(1994)。
|
|
371
|
+
- 鑑賞室的 MP3 下載使用 [lamejs](https://github.com/zhuker/lamejs),它是 [LAME](https://lame.sourceforge.io) 的 JavaScript 移植版(LGPL-3.0),未經修改、以獨立檔案載入,只在下載歌曲時才會用到。
|
|
372
|
+
|
|
373
|
+
## 授權
|
|
374
|
+
|
|
375
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Sweep } from '../song.ts';
|
|
2
|
+
/** A ducking track's gain, and how it pumps */
|
|
3
|
+
export interface Pump {
|
|
4
|
+
gain: AudioParam;
|
|
5
|
+
/** How far it dips, 0-1 */
|
|
6
|
+
depth: number;
|
|
7
|
+
/** Seconds until it is back up */
|
|
8
|
+
release: number;
|
|
9
|
+
}
|
|
10
|
+
/** Level of a ducking track `t` seconds after a hit: down for the first quarter of the release, then swelling back (95% there at the end) */
|
|
11
|
+
export declare function duckLevel(depth: number, release: number, t: number): number;
|
|
12
|
+
/** Pump at a hit (context seconds). A hit before `from`, when a playback starts mid-swell, is picked up where it would be by then */
|
|
13
|
+
export declare function pump({ gain, depth, release }: Pump, hit: number, from?: number): void;
|
|
14
|
+
/** A sweep's breakpoints as [step, Hz], in order */
|
|
15
|
+
export declare function breakpoints(sweep: Sweep): [number, number][];
|
|
16
|
+
/** A sweep's cutoff at a step: held before the first breakpoint and after the last, gliding exponentially (evenly in pitch) between */
|
|
17
|
+
export declare function cutoffAt(points: [number, number][], step: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Schedule one pass of a sweep through a section that lasts `steps`, from `from` steps in; `time` gives the context time of a step.
|
|
20
|
+
* A pass that follows another (`again`) glides back from where the last one ended: a filter retuned in one jump while it carries
|
|
21
|
+
* sound thumps.
|
|
22
|
+
*/
|
|
23
|
+
export declare function sweep(cutoff: AudioParam, points: [number, number][], from: number, steps: number, time: (step: number) => number, again?: boolean): void;
|
|
24
|
+
//# sourceMappingURL=automation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automation.d.ts","sourceRoot":"","sources":["../../src/engine/automation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,+CAA+C;AAC/C,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD,6IAA6I;AAC7I,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED,qIAAqI;AACrI,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,IAAI,CAKlF;AAED,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAI5D;AAED,uIAAuI;AACvI,wBAAgB,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzE;AAKD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,KAAK,UAAQ,GAAG,IAAI,CAQtJ"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Mixer automation, scheduled ahead like the notes: sidechain pumping (a track dips at every hit of another, then swells back)
|
|
2
|
+
// and filter sweeps through a section.
|
|
3
|
+
/** The dip starts this far ahead of the hit, so the level is down when the hit lands (seconds) */
|
|
4
|
+
const LEAD = 0.004;
|
|
5
|
+
/** Level of a ducking track `t` seconds after a hit: down for the first quarter of the release, then swelling back (95% there at the end) */
|
|
6
|
+
export function duckLevel(depth, release, t) {
|
|
7
|
+
const swell = release / 4;
|
|
8
|
+
return 1 - depth * Math.exp(-Math.max(0, t - swell) / swell);
|
|
9
|
+
}
|
|
10
|
+
/** Pump at a hit (context seconds). A hit before `from`, when a playback starts mid-swell, is picked up where it would be by then */
|
|
11
|
+
export function pump({ gain, depth, release }, hit, from = hit) {
|
|
12
|
+
const swell = release / 4;
|
|
13
|
+
if (from > hit)
|
|
14
|
+
gain.setValueAtTime(duckLevel(depth, release, from - hit), from);
|
|
15
|
+
else
|
|
16
|
+
gain.setTargetAtTime(1 - depth, hit - LEAD, LEAD / 3);
|
|
17
|
+
gain.setTargetAtTime(1, Math.max(from, hit + swell), swell);
|
|
18
|
+
}
|
|
19
|
+
/** A sweep's breakpoints as [step, Hz], in order */
|
|
20
|
+
export function breakpoints(sweep) {
|
|
21
|
+
return Object.entries(sweep)
|
|
22
|
+
.map(([step, hz]) => [Number(step), Math.max(10, hz)])
|
|
23
|
+
.sort((a, b) => a[0] - b[0]);
|
|
24
|
+
}
|
|
25
|
+
/** A sweep's cutoff at a step: held before the first breakpoint and after the last, gliding exponentially (evenly in pitch) between */
|
|
26
|
+
export function cutoffAt(points, step) {
|
|
27
|
+
const i = points.findIndex(([s]) => s > step);
|
|
28
|
+
if (i === 0)
|
|
29
|
+
return points[0][1];
|
|
30
|
+
if (i < 0)
|
|
31
|
+
return points[points.length - 1][1];
|
|
32
|
+
const [s0, f0] = points[i - 1];
|
|
33
|
+
const [s1, f1] = points[i];
|
|
34
|
+
return f0 * Math.pow(f1 / f0, (step - s0) / (s1 - s0));
|
|
35
|
+
}
|
|
36
|
+
/** How long a sweep takes to get back to its start when its section repeats (seconds) */
|
|
37
|
+
const GLIDE = 0.02;
|
|
38
|
+
/**
|
|
39
|
+
* Schedule one pass of a sweep through a section that lasts `steps`, from `from` steps in; `time` gives the context time of a step.
|
|
40
|
+
* A pass that follows another (`again`) glides back from where the last one ended: a filter retuned in one jump while it carries
|
|
41
|
+
* sound thumps.
|
|
42
|
+
*/
|
|
43
|
+
export function sweep(cutoff, points, from, steps, time, again = false) {
|
|
44
|
+
const at = time(from);
|
|
45
|
+
if (again) {
|
|
46
|
+
cutoff.setValueAtTime(cutoffAt(points, steps), at);
|
|
47
|
+
cutoff.exponentialRampToValueAtTime(cutoffAt(points, from), at + GLIDE);
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
cutoff.setValueAtTime(cutoffAt(points, from), at);
|
|
51
|
+
const knots = new Set(points.map(([s]) => Math.min(s, steps)).filter((s) => s > from));
|
|
52
|
+
for (const s of knots)
|
|
53
|
+
cutoff.exponentialRampToValueAtTime(cutoffAt(points, s), time(s));
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=automation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automation.js","sourceRoot":"","sources":["../../src/engine/automation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type SampleRequest } from '../instruments/samples.ts';
|
|
2
|
+
import type { Meter } from '../notation/part.ts';
|
|
3
|
+
import { type Song, type Track } from '../song.ts';
|
|
4
|
+
/** A note ready to schedule */
|
|
5
|
+
export interface ScheduledNote {
|
|
6
|
+
/** Steps from the start of the section */
|
|
7
|
+
time: number;
|
|
8
|
+
duration: number;
|
|
9
|
+
pitches: number[];
|
|
10
|
+
velocity: number;
|
|
11
|
+
/** Index of the track in its section */
|
|
12
|
+
track: number;
|
|
13
|
+
/** Glides merged into this note: [steps after its start, target MIDI pitch] */
|
|
14
|
+
bends?: [number, number][];
|
|
15
|
+
strum?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface CompiledSection {
|
|
18
|
+
/** Length in steps (whole bars) */
|
|
19
|
+
duration: number;
|
|
20
|
+
notes: ScheduledNote[];
|
|
21
|
+
/** For each track, the tracks that duck under its hits */
|
|
22
|
+
ducks: number[][];
|
|
23
|
+
/** For each track, the desk it plays at (see deskList) */
|
|
24
|
+
desks: number[];
|
|
25
|
+
}
|
|
26
|
+
/** Compile one section into sorted notes; shorter tracks repeat to fill the whole section */
|
|
27
|
+
export declare function compileSection(tracks: Song['tracks'], meter: Meter): CompiledSection;
|
|
28
|
+
/**
|
|
29
|
+
* For each track, the desk it plays at. Tracks of one section that share a bowed instrument (or its samples: strPad plays the strings') are
|
|
30
|
+
* different players, as the desks of a string section are: the first sits at desk 0 and sounds as a lone track always has, the next at desk 1
|
|
31
|
+
* with its own tuning, vibrato and bow (see DESKS), then desk 2, and round again. Other tracks are at desk 0.
|
|
32
|
+
*/
|
|
33
|
+
export declare function deskList(tracks: Track[]): number[];
|
|
34
|
+
/** Every sample a song will play, so they can be rendered before it starts */
|
|
35
|
+
export declare function sampleRequests(song: Song): SampleRequest[];
|
|
36
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/engine/compile.ts"],"names":[],"mappings":"AAKA,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAsC,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEvF,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAClB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,eAAe,CAkCpF;AAeD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CASlD;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,aAAa,EAAE,CAqB1D"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Compile a song's sections into flat, time-sorted note lists, and list the samples a song needs rendered.
|
|
2
|
+
import { DESKS } from '../instruments/bowed.js';
|
|
3
|
+
import { getInstrument } from '../instruments/registry.js';
|
|
4
|
+
import { bowLayers } from '../instruments/samples.js';
|
|
5
|
+
import { sectionSteps, trackName, trackPart } from '../song.js';
|
|
6
|
+
/** Compile one section into sorted notes; shorter tracks repeat to fill the whole section */
|
|
7
|
+
export function compileSection(tracks, meter) {
|
|
8
|
+
const parts = tracks.map((t) => trackPart(t, meter));
|
|
9
|
+
const duration = sectionSteps(parts, meter);
|
|
10
|
+
const notes = [];
|
|
11
|
+
tracks.forEach((track, ti) => {
|
|
12
|
+
const p = parts[ti];
|
|
13
|
+
if (!p.duration)
|
|
14
|
+
return;
|
|
15
|
+
const reps = track.repeat === false ? 1 : Math.max(1, Math.floor(duration / p.duration + 1e-6));
|
|
16
|
+
const src = [...p.notes].sort((a, b) => a.time - b.time);
|
|
17
|
+
const glides = getInstrument(track.instrument).glide ?? false;
|
|
18
|
+
for (let r = 0; r < reps; r++) {
|
|
19
|
+
const off = r * p.duration;
|
|
20
|
+
for (let i = 0; i < src.length; i++) {
|
|
21
|
+
const n = src[i];
|
|
22
|
+
if (n.time + off >= duration)
|
|
23
|
+
break;
|
|
24
|
+
const c = { time: n.time + off, duration: n.duration, pitches: n.pitches, velocity: n.velocity, track: ti, strum: n.strum };
|
|
25
|
+
// Glide: merge the following notes in, turning them into pitch changes of the same voice
|
|
26
|
+
if (glides && n.glide) {
|
|
27
|
+
c.bends = [];
|
|
28
|
+
let j = i;
|
|
29
|
+
while (src[j]?.glide && src[j + 1]) {
|
|
30
|
+
const next = src[j + 1];
|
|
31
|
+
c.bends.push([next.time - n.time, next.pitches[0]]);
|
|
32
|
+
c.duration = next.time + next.duration - n.time;
|
|
33
|
+
j++;
|
|
34
|
+
}
|
|
35
|
+
i = j;
|
|
36
|
+
}
|
|
37
|
+
notes.push(c);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
notes.sort((a, b) => a.time - b.time);
|
|
42
|
+
return { duration, notes, ducks: duckLists(tracks), desks: deskList(tracks) };
|
|
43
|
+
}
|
|
44
|
+
/** For each track, the tracks that duck under its hits (a pad with `duck: 'Kick'` is on the kick's list) */
|
|
45
|
+
function duckLists(tracks) {
|
|
46
|
+
const ducks = tracks.map(() => []);
|
|
47
|
+
tracks.forEach((track, i) => {
|
|
48
|
+
if (!track.duck)
|
|
49
|
+
return;
|
|
50
|
+
const by = typeof track.duck === 'string' ? track.duck : track.duck.by;
|
|
51
|
+
const triggers = tracks.flatMap((t, k) => (k !== i && trackName(t) === by ? [k] : []));
|
|
52
|
+
if (!triggers.length)
|
|
53
|
+
throw new Error(`"${trackName(track)}" ducks under "${by}", but no other track in its section is called that`);
|
|
54
|
+
for (const k of triggers)
|
|
55
|
+
ducks[k].push(i);
|
|
56
|
+
});
|
|
57
|
+
return ducks;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* For each track, the desk it plays at. Tracks of one section that share a bowed instrument (or its samples: strPad plays the strings') are
|
|
61
|
+
* different players, as the desks of a string section are: the first sits at desk 0 and sounds as a lone track always has, the next at desk 1
|
|
62
|
+
* with its own tuning, vibrato and bow (see DESKS), then desk 2, and round again. Other tracks are at desk 0.
|
|
63
|
+
*/
|
|
64
|
+
export function deskList(tracks) {
|
|
65
|
+
const seated = new Map();
|
|
66
|
+
return tracks.map((track) => {
|
|
67
|
+
const def = getInstrument(track.instrument);
|
|
68
|
+
if (def.kind !== 'sample' || !def.bow)
|
|
69
|
+
return 0;
|
|
70
|
+
const k = seated.get(def.bow.name) ?? 0;
|
|
71
|
+
seated.set(def.bow.name, k + 1);
|
|
72
|
+
return k % DESKS.length;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Every sample a song will play, so they can be rendered before it starts */
|
|
76
|
+
export function sampleRequests(song) {
|
|
77
|
+
const out = new Map();
|
|
78
|
+
const meter = song.meter ?? 16;
|
|
79
|
+
for (const tracks of [song.intro ?? [], song.tracks]) {
|
|
80
|
+
const desks = deskList(tracks);
|
|
81
|
+
tracks.forEach((track, ti) => {
|
|
82
|
+
const def = getInstrument(track.instrument);
|
|
83
|
+
if (def.kind !== 'sample')
|
|
84
|
+
return;
|
|
85
|
+
const desk = desks[ti];
|
|
86
|
+
for (const n of trackPart(track, meter).notes) {
|
|
87
|
+
for (const m of n.pitches.length ? n.pitches : [0]) {
|
|
88
|
+
const midi = m + (track.transpose ?? 0);
|
|
89
|
+
if (def.bow) {
|
|
90
|
+
// Bowed strings: only the velocity layers that will be used, at the track's desk
|
|
91
|
+
for (const layer of bowLayers(def, n.velocity, track.params?.bright))
|
|
92
|
+
out.set(`${track.instrument}|${midi}|${layer}|${desk}`, request(def, track.instrument, midi, layer, desk));
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
out.set(`${track.instrument}|${midi}`, request(def, track.instrument, midi));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return [...out.values()];
|
|
101
|
+
}
|
|
102
|
+
const request = (def, name, midi, layer, desk) => ({ def, name, midi, layer, desk });
|
|
103
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/engine/compile.ts"],"names":[],"mappings":"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"}
|