sonarium 0.6.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -0
  3. package/dist/index.d.ts +1000 -0
  4. package/dist/index.js +2716 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/sonarium.iife.js +236 -0
  7. package/dist/sonarium.iife.js.map +1 -0
  8. package/package.json +63 -0
  9. package/src/core/engine.ts +468 -0
  10. package/src/core/listener.ts +61 -0
  11. package/src/core/matter-voice.ts +240 -0
  12. package/src/core/profile.ts +313 -0
  13. package/src/core/room.ts +135 -0
  14. package/src/core/scanner.ts +176 -0
  15. package/src/core/voices.ts +222 -0
  16. package/src/index.ts +74 -0
  17. package/src/interact/activate.ts +56 -0
  18. package/src/interact/drag.ts +52 -0
  19. package/src/interact/keyboard.ts +48 -0
  20. package/src/interact/midi.ts +40 -0
  21. package/src/interact/motion.ts +61 -0
  22. package/src/interact/pointer.ts +65 -0
  23. package/src/interact/scroll.ts +43 -0
  24. package/src/math/chroma.ts +111 -0
  25. package/src/math/mapping.ts +100 -0
  26. package/src/math/matter.ts +201 -0
  27. package/src/math/modular.ts +99 -0
  28. package/src/math/pulse.ts +64 -0
  29. package/src/math/scales.ts +90 -0
  30. package/src/math/util.ts +16 -0
  31. package/src/spatial/backend.ts +166 -0
  32. package/src/spatial/bus.ts +114 -0
  33. package/src/spatial/decoder.ts +63 -0
  34. package/src/spatial/encoder.ts +54 -0
  35. package/src/spatial/field.ts +75 -0
  36. package/src/spatial/perceptual.ts +43 -0
  37. package/src/spatial/room-foa.ts +81 -0
  38. package/src/spatial/rotation.ts +73 -0
  39. package/src/spatial/sh.ts +38 -0
  40. package/src/spatial/sphere.ts +36 -0
  41. package/src/themes/index.ts +60 -0
  42. package/src/types.ts +157 -0
  43. package/src/ui/gate.ts +65 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Che-Yu Wu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # Sonarium 🔊
2
+
3
+ **Drop-in acoustic UX — one script tag turns any webpage into a spatial sound field.**
4
+
5
+ [Live demo](https://frank890417.github.io/sonarium/) ·
6
+ [Mapping Canon](./docs/MAPPING.md) ·
7
+ [Research](./docs/RESEARCH.md) ·
8
+ [Master Plan](./docs/PLAN.md) ·
9
+ [繁體中文](#繁體中文簡介)
10
+
11
+ ![deploy](https://github.com/frank890417/sonarium/actions/workflows/pages.yml/badge.svg)
12
+
13
+ ```html
14
+ <script src="https://cdn.jsdelivr.net/gh/frank890417/sonarium@main/dist/sonarium.iife.js" data-auto></script>
15
+ ```
16
+
17
+ That's the whole integration. The page now *hears itself*:
18
+
19
+ - **The page becomes an ambisonic field.** Every element is encoded into one rotatable
20
+ full-sphere sound field (AmbiX FOA) around your head, IRCAM-Spat style — a button on the left
21
+ *sounds* from the left, decoded binaurally through virtual speakers. Moving the mouse doesn't
22
+ move a listener point: it **rotates the entire field**, like turning your head.
23
+ Spec: [docs/SPATIAL.md](./docs/SPATIAL.md) · live: [the Sound Sphere 聲球 demo](https://frank890417.github.io/sonarium/examples/sphere.html).
24
+ - **The page is the patch (v0.5).** Modular-synth depth, cabled from CSS: heavy borders drive a
25
+ **wavefolder**, rough sharp surfaces ring with **audio-rate FM**, massive elements thicken with
26
+ **unison**, a CSS-animated element **audibly pulses at its own animation rate**, dashed borders
27
+ **chop** (the S&H of CSS), transitions become portamento, bold type carries weight. Press and
28
+ **drag any control** and it becomes a ribbon controller sweeping the page's scale. Idle voices
29
+ sleep (silent pages idle at ~zero CPU). Spec: [docs/MODULAR.md](./docs/MODULAR.md).
30
+ - **It's an instrument bench too (v0.6).** [The Patcher](https://frank890417.github.io/sonarium/examples/patch.html)
31
+ edits a page's whole sound system and exports it as a JSON patch; [the Lab](https://frank890417.github.io/sonarium/examples/lab.html)
32
+ runs the thesis mismatch experiment on your own ears; `midi: true` mirrors every trigger to
33
+ Web MIDI (record your browsing into a DAW).
34
+ - **Geometry is a material, not a setting (v0.3).** Visual properties condense into four
35
+ macro-dimensions — **edge, mass, texture, air** — and each one moves *bundles* of co-varying
36
+ cues, so the ear hears one coherent object: a sharp button literally **clicks** (a /k/ noise
37
+ burst), bites bright and stays dry; a round pill **glides in**, hums near-pure and *blooms*
38
+ into the reverb; a translucent card **breathes**; a long bar sounds **hollow** like the pipe
39
+ it is (odd harmonics — real physics). Spectra are synthesized per element (24 additive
40
+ partials), not picked from presets. The weave matrix: [docs/MATTER.md](./docs/MATTER.md).
41
+ - **Every element is a sound sphere (聲球).** Size sets its *extent* — big blocks wrap around
42
+ you, buttons are point sources; roundness sets its *directivity* — sharp elements beam at you,
43
+ round ones radiate (Kiki/Bouba extended into space). Five Spat-style perceptual factors
44
+ (presence, room presence, envelopment, warmth, brilliance) steer the whole scene.
45
+ - **Geometry becomes timbre.** Corner radius morphs each element's voice from biting **square**
46
+ through **sawtooth** and **triangle** to breathing **sine** — the
47
+ [Kiki/Bouba effect](./docs/RESEARCH.md#2-cross-modal-correspondence-literature), made executable.
48
+ Big elements speak low and loud; long elements sweep, small ones tick.
49
+ - **The DOM tree becomes harmony.** Nesting depth pushes sounds farther away and muffles them;
50
+ siblings climb one shared scale, so a nav is a melodic run and a list is an arpeggio.
51
+ - **The viewport becomes the room.** Page width sets the reverb — a phone is a dry booth,
52
+ a wide desktop is a concert hall.
53
+ - **Your body becomes the listener.** The cursor is your ears on desktop; on mobile, tilting the
54
+ device steers the listener and a shake strums everything on screen, left to right.
55
+ - **Color is mood; the palette is the key (v0.4).** Element colors tint their voices — warm
56
+ strikes eagerly with a full body, cool opens unhurried, vivid colors get vivid spectra — and
57
+ the **page palette chooses the musical mode** (warm-bright → lydian … cool-dark → minor
58
+ pentatonic) while the hostname keeps the root. Spec: [docs/CHROMA.md](./docs/CHROMA.md).
59
+ - **The page has a pulse (v0.4).** Layout density sets a tempo (56–116 bpm); strong clicks get a
60
+ quiet **echo on the next 8th note** (the primary hit is never delayed — feel first, meter
61
+ second); and with ambience on, the page softly **plays its own layout in reading order** —
62
+ scroll moves the playhead through the score. Repeated sounds politely duck and recover (the
63
+ calm system). Spec: [docs/PULSE.md](./docs/PULSE.md).
64
+ - **Designers can author it in CSS.** The aural stylesheet: `--sonic-*` custom properties
65
+ inherit down the tree (`nav { --sonic-wave: sine }`, `--sonic-key`/`--sonic-tempo` at root).
66
+ - **Every site gets a sonic identity.** The hostname deterministically picks the root pitch —
67
+ same site, same tonal center, every visit. Nothing ever plays outside the scale.
68
+
69
+ 🎧 *Wear headphones. Then go click around the [demo](https://frank890417.github.io/sonarium/).*
70
+
71
+ ## Why
72
+
73
+ The visual web has CSS; the acoustic web has nothing. W3C actually specced spatial UI audio in
74
+ 1998 (CSS2 aural stylesheets, with `azimuth` and `elevation`!) and it never shipped. Sonarium
75
+ implements that dream with the 2026 toolchain — Web Audio `PannerNode`, Tone.js synthesis, DOM
76
+ observers — and grounds every mapping in cross-modal perception research rather than taste.
77
+ The long-term goal is a **universal interactive sound framework for the web**: read
78
+ [docs/PLAN.md](./docs/PLAN.md) for the full vision, layer model, and roadmap.
79
+
80
+ It grew out of the author's NYU master's thesis,
81
+ [*Sound and Cognition — Building an experimental web-based visual music composing tool*](https://cheyuwu.com/thesis)
82
+ (2020), which studied how people without musical training map shapes to sounds — those findings
83
+ (sharp ↔ bright, big ↔ low/loud, long ↔ sustained, closed ↔ percussive) are now formulas in
84
+ [`src/math/mapping.ts`](./src/math/mapping.ts), each one unit-tested against
85
+ [docs/MAPPING.md](./docs/MAPPING.md).
86
+
87
+ ## Install
88
+
89
+ **Zero-code (recommended for trying it):**
90
+
91
+ ```html
92
+ <script src="https://cdn.jsdelivr.net/gh/frank890417/sonarium@main/dist/sonarium.iife.js"
93
+ data-auto data-theme="aurora" data-ambient="0.12"></script>
94
+ ```
95
+
96
+ **As a module:**
97
+
98
+ ```bash
99
+ npm install github:frank890417/sonarium # npm publish coming in v0.2
100
+ ```
101
+
102
+ ```js
103
+ import { create } from 'sonarium'
104
+
105
+ const space = create({
106
+ theme: 'aurora', // 'aurora' | 'mono' | 'paper' | custom Theme object
107
+ key: 'auto', // 'auto' = per-domain identity, or e.g. 'D dorian'
108
+ spatial: 'ambisonic', // the FOA field (default) | 'panner' = v0.1 per-voice HRTF
109
+ listener: 'pointer', // mouse-look rotates the field ('center' to fix it)
110
+ perceptual: { presence: 0.7, envelopment: 0.55 }, // the spat5.oper surface
111
+ ambient: 0.12, // room tone + sparkles, 0 to disable
112
+ volume: -10, // master dB
113
+ })
114
+
115
+ space.setPerceptual({ warmth: 0.8 }) // live perceptual control
116
+
117
+ space.on('trigger', ({ el, profile }) => { /* drive visuals from sound */ })
118
+ space.describe(document.querySelector('button')) // → why does it sound like that?
119
+ ```
120
+
121
+ No sound plays before the user's first gesture (autoplay policy treated as a feature). A small
122
+ floating chip handles unlock + mute; mute persists across visits.
123
+
124
+ ## How it hears your page
125
+
126
+ | Visual property | Acoustic property | In one line |
127
+ |---|---|---|
128
+ | x position | azimuth (pan) | left button = left sound |
129
+ | y position | elevation + brightness | top of page rings brighter |
130
+ | area | pitch (inverted) + loudness | big = low & loud, small = high & quiet |
131
+ | border-radius | waveform, attack, resonance | kiki ◻ = square/2 ms; bouba ◯ = sine/45 ms |
132
+ | aspect ratio | note duration | a divider sweeps, a chip ticks |
133
+ | DOM depth | distance + low-pass | deeper = farther & duller |
134
+ | sibling index | scale steps | a nav is a melody |
135
+ | viewport width | reverb size | phone = booth, desktop = hall |
136
+ | box-shadow blur | reverb send | elevated UI floats in the room |
137
+ | hostname | key + scale | every site has a signature |
138
+
139
+ Interactions: hover/focus previews (quiet), press = full note, pressing a container strums its
140
+ children left→right, typing ticks rise as the field fills, checkboxes answer with rising/falling
141
+ fifths, elements whisper when they scroll into view (rate-limited), tab-focus sounds like hover
142
+ (keyboard parity), shake-to-strum on mobile, and the page plays a 5-note **intro motif** of its
143
+ own landmarks when sound first unlocks.
144
+
145
+ Formulas, constants, and the research citation for every row:
146
+ [docs/MAPPING.md](./docs/MAPPING.md) · [docs/RESEARCH.md](./docs/RESEARCH.md).
147
+
148
+ ## Authoring overrides
149
+
150
+ ```html
151
+ <div data-sonic="off">…this subtree is silent…</div>
152
+ <div data-sonic="quiet">…40% velocity…</div>
153
+ <button data-sonic-note="E4" data-sonic-wave="square">pinned voice</button>
154
+ <div data-sonic-role="button">sound like a button</div>
155
+ ```
156
+
157
+ ## API surface
158
+
159
+ | | |
160
+ |---|---|
161
+ | `create(options) → engine` | safe pre-gesture; arms and waits for the first interaction |
162
+ | `engine.start()` | manual unlock (call from your own gesture handler) |
163
+ | `engine.describe(el)` | full acoustic profile **with human-readable reasons** |
164
+ | `engine.strum(els, velocity)` | play a set of elements left→right |
165
+ | `engine.excite(el, velocity, articulation)` | sound one element through its profile |
166
+ | `engine.toggleMute()` / `engine.dispose()` | what they say |
167
+ | `engine.on('start'\|'trigger'\|'mute'\|'dispose', fn)` | event hooks (demos draw ripples from `trigger`) |
168
+ | `engine.setPerceptual({...})` | live Spat factors: presence, roomPresence, envelopment, warmth, brilliance |
169
+ | `engine.rig.lookAt(yaw, pitch)` | rotate the field directly (head tracking / WebXR plug here) |
170
+ | `siteKey(host)`, `parseKey('D dorian')`, `THEMES`, `mapping.*` | the pure layer, exported for reuse |
171
+ | `foaGains`, `lookMatrix`, `decodeMatrix`, `sphereMapping.*` | the pure ambisonic layer (SPATIAL.md), reusable beyond the DOM |
172
+
173
+ Options: `root, theme, key, listener, ambient, motion, gate ('chip'|'none'), volume, maxVoices,
174
+ panning ('hrtf'|'equalpower'), reverb ('auto'|seconds), respectReducedMotion`.
175
+
176
+ ## Sound ethics
177
+
178
+ Sound UX died once already (`<bgsound>`, autoplay ads). Sonarium's defaults are deliberately
179
+ humble: **silent until a gesture, −10 dB master with a −1 dB limiter, quiet hover previews,
180
+ persistent one-tap mute, fades out in background tabs, softens under `prefers-reduced-motion`,
181
+ and never, ever plays a note outside the page's scale.** If you ship it, keep it that way.
182
+ Screen-reader users: Sonarium never speaks and the mute chip is keyboard-reachable; treat it as
183
+ a layer SR users can opt out of in one tap (a formal audit is on the roadmap).
184
+
185
+ ## Project layout
186
+
187
+ ```
188
+ docs/ PLAN.md (vision/roadmap/handoff) · RESEARCH.md (theory) · MAPPING.md (the canon) ·
189
+ SPATIAL.md (ambisonics & the sound sphere) · ARCHITECTURE.md (how code implements it)
190
+ src/math/ pure mapping formulas + musical quantizer — no DOM, no Tone, fully tested
191
+ src/spatial/ the ambisonic engine: pure SH/rotation/decode math (tested) + FOA bus, encoders,
192
+ field rig, Spat room model, perceptual factors
193
+ src/core/ profile (element→sound identity), scanner, voice pool, room, engine
194
+ src/interact/ pointer, activate, keyboard, scroll, motion drivers
195
+ src/themes/ sound palettes as plain data
196
+ examples/ landing playground · sound sphere 聲球 · one-line dashboard · depth · motion
197
+ ```
198
+
199
+ Build `npm run build` · test `npm test` · demos `npm run serve` → `/examples/`.
200
+
201
+ ## Roadmap
202
+
203
+ **v0.2 "Spherical" — shipped**: the ambisonic field, the sound sphere, Spat perceptual factors
204
+ ([docs/SPATIAL.md](./docs/SPATIAL.md)); next there: HOA orders 2–3, worklet decode with measured
205
+ HRIRs, head tracking, AmbiX field export.
206
+ **v0.3 "Woven 織" — shipped**: the Matter model ([docs/MATTER.md](./docs/MATTER.md)) — continuous
207
+ per-element spectra, transient bursts, breath layers, glides, reverb color/bloom, scroll
208
+ air-rush; every weave carries a blindfold-test acceptance criterion.
209
+ **v0.4 "Alive 活" — shipped**: the Chroma weave + the Pulse ([CHROMA.md](./docs/CHROMA.md) /
210
+ [PULSE.md](./docs/PULSE.md)) — color as mood, palette as mode, layout tempo, metered echoes,
211
+ phrase engine, the aural stylesheet, the calm system.
212
+ **v0.5 "Modular 模組" — shipped**: the modular patch ([MODULAR.md](./docs/MODULAR.md)) —
213
+ wavefolder, FM, unison, CSS-cabled LFOs, ribbon glissando, voice sleeping, ghost-trigger fixes.
214
+ **v0.6 "Instrumented" — shipped**: the Lab (mismatch experiment), the Patcher (JSON patches),
215
+ Web MIDI out, flick gestures, npm-ready packaging.
216
+ **Gated** (waiting on the world, fully specified in [PLAN.md](./docs/PLAN.md) §P6): npm publish
217
+ (one `npm login` away), WebXR head tracking, multi-user rooms, HOA + measured HRIRs, the formal
218
+ a11y study, `--sonic-*` standardization.
219
+
220
+ ## 繁體中文簡介
221
+
222
+ **Sonarium 讓任何網頁加上一行 script,就變成一個立體聲學空間。**
223
+
224
+ - **顏色是情緒,調色盤就是調性**(v0.4):元素的顏色為它的聲音上色 — 暖色急切有力、冷色從容開展、
225
+ 飽和的顏色頻譜更豐富;而**整頁的配色決定音樂調式**(暖亮 → lydian…冷暗 → 小調五聲),域名仍保留
226
+ 主音(識別不變)。版面密度決定頁面的**速度**(56–116 bpm);用力點擊會在下一個八分音符得到安靜的
227
+ 回聲(主要聲音永不延遲 — 手感優先);開啟環境音時,頁面會以閱讀順序輕聲**演奏自己的版面**,捲動
228
+ 就是移動樂譜的播放頭。重複的聲音會禮貌地退讓再恢復(calm 系統)。設計師可以直接在 CSS 裡寫聲音:
229
+ `--sonic-*` 自訂屬性會沿著樹繼承。規格:[CHROMA.md](./docs/CHROMA.md)、[PULSE.md](./docs/PULSE.md)。
230
+ - **整個頁面就是一張 patch**(v0.5–v0.6):模組合成器的深度,由 CSS 接線 — 粗邊框推動波形摺疊器、
231
+ 粗糙銳利的表面有 FM 金屬鳴響、巨大元素有 unison 厚度;**有 CSS 動畫的元素會用自己的動畫速率震動**、
232
+ 虛線邊框產生方波斷奏、transition 變成滑音、粗體字有重量。按住任何控制項**拖曳就是 ribbon 滑奏**
233
+ (沿著頁面音階量化)。閒置聲部會睡眠(安靜頁面 CPU 趨近零)。再加上 [Patcher](https://frank890417.github.io/sonarium/examples/patch.html)
234
+ (編輯整頁聲音系統、匯出 JSON patch — 2020 碩論 patcher 還魂)、[Lab](https://frank890417.github.io/sonarium/examples/lab.html)
235
+ (錯配實驗線上版)、Web MIDI 輸出(把瀏覽行為錄進 DAW)。規格:[MODULAR.md](./docs/MODULAR.md)。
236
+ - **幾何是材質,不是參數**(v0.3):視覺屬性凝聚成四個宏觀維度 — **銳(edge)、質(mass)、
237
+ 紋(texture)、距(air)** — 每個維度同時牽動一整束同步變化的聲音線索(Bregman 聽覺場景分析:
238
+ 同步共變的線索會融合成「一個物體」)。尖銳的按鈕真的會「喀」一聲(/k/ 噪聲爆發)、亮且乾;
239
+ 圓潤的膠囊滑音進場、近乎純音、並在音符過程中「綻放」進殘響;半透明的卡片有氣音;細長的條狀
240
+ 元素是中空的管子(奇次諧波 — 真實物理)。頻譜逐元素即時合成(24 個加法泛音),不再是四種
241
+ 預設波形。完整編織矩陣:[docs/MATTER.md](./docs/MATTER.md)。
242
+ - **整個頁面是一個 Ambisonic 聲場**(v0.2):所有元素編碼進同一個可旋轉的全球面聲場(AmbiX FOA),
243
+ 以 IRCAM Spat 的架構管理 — 滑鼠移動不是移動「聽者的點」,而是**旋轉整個聲場**,就像轉頭一樣;
244
+ 手機上則由裝置姿態驅動。規格書:[docs/SPATIAL.md](./docs/SPATIAL.md),
245
+ 現場展示:[聲球 demo](https://frank890417.github.io/sonarium/examples/sphere.html)。
246
+ - **每個元素是一顆聲球**:尺寸決定它的「張角」(大區塊把你包進去、小按鈕是點音源);
247
+ 圓角決定指向性(尖銳的元素像光束射向你、圓潤的元素全向放射)。五個 Spat 知覺參數
248
+ (presence/room presence/envelopment/warmth/brilliance)即時控制整個場景。
249
+ - **版面變成聲場**:元素在畫面上的位置,就是聲音在 3D 空間裡的位置 — 左邊的按鈕從左邊發聲。
250
+ - **幾何變成音色**:圓角半徑讓波形從銳利的 square 一路滑到柔軟的 sine(Kiki/Bouba 效應的可執行版本);
251
+ 越大的元素音高越低、越大聲;越長的元素音越長。
252
+ - **DOM 樹變成和聲**:巢狀越深聲音越遠、越悶;同層兄弟元素爬同一個音階 — 導覽列是一句旋律,清單是一段琶音。
253
+ - **視窗變成房間**:頁面寬度決定殘響 — 手機是錄音間,寬螢幕是音樂廳。
254
+ - **身體變成聆聽者**:桌機上滑鼠就是你的耳朵;手機上傾斜裝置移動聽者位置、搖一搖就由左至右掃過整頁。
255
+ - **每個網站有自己的調性**:域名雜湊決定調與音階,所有聲音都經過音階量化 — 永遠不會走音。
256
+
257
+ 這個專案源自作者的 NYU 碩士論文《Sound and Cognition》(2020):研究沒有音樂背景的人如何把形狀對應到聲音,
258
+ 那些發現(尖銳↔明亮、大↔低沉、長↔延續、封閉↔顆粒)現在都是 `src/math/mapping.ts` 裡有單元測試的公式。
259
+ 終極目標是建立**網頁版的互動聲音通用框架** — 完整願景、分層架構與路線圖請讀
260
+ [docs/PLAN.md](./docs/PLAN.md)(為後續接手的協作者或 AI agent 而寫,越詳細越好)。
261
+
262
+ 預設非常克制:使用者互動前完全靜音、主音量 −10 dB、一鍵永久靜音、背景分頁自動淡出。
263
+ 試玩:**[frank890417.github.io/sonarium](https://frank890417.github.io/sonarium/)**(請戴耳機 🎧)。
264
+
265
+ ## Credits & license
266
+
267
+ Built on [Tone.js](https://tonejs.github.io/) by Yotam Mann. Research lineage: Köhler, Gaver's
268
+ auditory icons, Blattner's earcons, Spence's crossmodal correspondences — full citations in
269
+ [docs/RESEARCH.md](./docs/RESEARCH.md).
270
+
271
+ MIT © 2026 [Che-Yu Wu](https://cheyuwu.com)