#分享 【專案分享】雙拇指手彈52首特攝/動漫配樂:手刻Web Audio合成器、FFT 聲學視覺化,與Python解決人體工學瓶頸+日文Unicode編碼地雷 🎹⚡
📌 前言:拒絕套版 TodoList,把「真實演奏」做成前端音訊工作站
很多工程師在累積作品集時,往往照著網路教學刻計數器、購物車或串接開源 API。但對我來說,最有靈魂的專案始終來自「個人熱忱」與「打破沙鍋問到底的工程直覺」。
平時我習慣在手機上使用 Perfect Piano(極致鋼琴)App,僅靠左手與右手大拇指彈奏特攝戰歌、經典港片、動漫與生活流行配樂,腦中記有數十首曲目的指法與旋律。
一般網頁播放音樂就是塞一個 <audio src="song.mp3">,但我不想依賴預錄好的靜態音訊檔案,而是好奇:
1. 如何利用純前端 Web Audio API,從零合成物理波形(Triangle / Sawtooth / Square / Sine)並精準控制 ADSR 包絡線?
2. 如何解決傳統音訊排程無法「精準暫停、進度條拖曳(Seek)」的硬傷?
3. 在 Canvas 上如何同時渲染「時域振幅波形(示波器)」與「頻率域 FFT 頻譜儀」?
4. 即時轉調演算法為何堅持用 -1 而非樂理上等音的 +11?
5. 為什麼手機開啟實體靜音鍵時,網頁音訊會被底層 Audio Session 攔截啞巴?
6. 當長篇曲目遇到拇指觸控疲勞的生理極限,以及日文檔名撞上 Windows Unicode(NFC vs NFD)「幽靈空格」時,如何用 Python 搭建分治(Divide & Conquer)自動化管線在二進位層面無縫修剪重組?
於是我打造了這個免安裝、零外部音檔依賴的網頁端 MIDI 數位音訊合成與可視化工作站。
🛠️ 一、 核心架構:Web Audio API 物理波形合成與 ADSR 包絡線
系統底層基於 Web Audio API 與 Tone.js 框架,不載入任何 MP3/WAV 採樣,而是透過程式碼控制 OscillatorNode(訊號產生器)與 AmplitudeEnvelope(振幅包絡線)動態計算空氣振動:
圓潤鋼琴風(Triangle 三角波):奇數諧波能量隨著頻率平方極快衰減(1/n^2),音色溫潤純淨。搭配 5ms 極短 Attack、長達 1.2s Release 的動態衰減包絡線,能精確還原鋼琴或打擊樂器敲擊後餘音繚繞的物理動態。
明亮電子琴(Sawtooth 鋸齒波):包含所有整數倍的連續諧波(基頻、二次、三次諧波等),高頻能量豐富飽滿,重現 80 年代昭和特攝與超級戰隊配樂中標誌性的類比合成器(Analog Synthesizer)音色。
復古 8-bit(Square 方波):僅含強烈奇數諧波(1/n 衰減),邊緣銳利、帶有生硬的數位感,還原早期紅白機(NES)遊戲音樂的復古氛圍。
柔和純音(Sine 正弦波):最純粹的單一頻率基音,無任何泛音成分,聽感極度平滑。
📊 二、 聲學可視化雙引擎:時域示波器 vs 頻率域 FFT
為了將抽象的音訊轉化為具體的視覺反饋,音訊鏈路(Audio Routing Graph)中平行掛載了兩個分析節點:
[Tone.Part 音符事件] ──► [Tone.PolySynth 多音合成器] ──► [喇叭輸出]
│
├──► [Tone.Waveform (1024)] ──► Canvas 時域示波器
└──► [Tone.FFT (64)] ──► Canvas 頻率域頻譜儀
示波器波形圖(Tone.Waveform(1024):時域分析):以每秒 60 次的頻率捕捉 [-1.0, 1.0] 的時間域振幅浮點數,映射至 Canvas 畫布的 Y 軸座標:
const y = (0.5 + (v * 0.6)) * waveCanvas.height;
可直觀看見三角波的平緩折角、方波的垂直陡升,以及正弦波的柔和圓弧。
頻譜分析儀(Tone.FFT(64):頻率域分析):透過快速傅立葉變換(FFT)將時間序列拆解為各頻率分量的能量分貝值(dB)。畫面左側代表低音貝斯(Bass)能量,右側代表高音與泛音(Treble)。播放明亮鋸齒波時,右側高頻柱會活躍跳動;轉調時,整座能量群即時水平平移。
⏱️ 三、 破解排程死穴:Tone.Transport 與 Tone.Part 非阻塞時間軸主控
傳統做法直接呼叫 synth.triggerAttackRelease(),是把音符一次性鎖死在 AudioContext 硬體時鐘上,導致使用者無法暫停、進度條無法對齊,更不可能拖曳。
本系統改寫為三層抽象控制:
1. 時間座標抽象化:解析 MIDI 數據,將整首曲目的音符拆解為抽象的時間事件群組(帶有 time, midi, duration, velocity)。
2. 主控時鐘掛載:載入至 Tone.Part,由全域主控時鐘 Tone.Transport 統一驅動。
3. 任意跳轉(Seek)與暫停實作:
暫停:呼叫 Tone.Transport.pause() 並觸發合成器的 releaseAll() 切斷當前發聲,進度完全保留。
跳轉:拖曳進度條時更新 Tone.Transport.seconds,即時對齊時間指針,達成流暢的快進與倒帶。
🎼 四、 樂理與聲學物理探討:即時轉調為何選「-1」而非「+11」?
在數位音樂協議中,音階對應 0 到 127 的整數(MIDI Number),轉調只需在發聲前計算:

為了讓雙拇指在狹窄的手機螢幕上順暢演奏,部分曲目我改編為最適合鍵盤指法特徵的 C 大調錄製(例如原曲 B 大調的《不敗的英雄》、降 D 大調的《淡淡的愛意》)。
在十二平均律數學上,「降 1 個半音(-1)」與「升 11 個半音(+11)」名義上是等音(C 降半音是 B,C 升 11 個半音也是高八度的 B)。
但在數位合成器中,選用 +11 會把所有音符集體往上拉近一個八度,引發八度暴走失真(Octave Displacement),原本厚實的中音演奏會被強行逼入尖銳刺耳區;改採 -1 時,音符維持在原本演奏的同一個八度音域內,才能完美保留飽滿的中低音基頻。
📱 五、 行動端踩坑:為什麼 iPhone 靜音模式會讓網頁完全沒聲音?
用手機開啟網頁時常有人反應「怎麼點播放沒聲音?」,這不是程式寫壞,而是撞到了行動作業系統的底層通道機制:
Web Audio API 在前端動態即時合成波形,在 iOS(WebKit 核心)與部分 Android 系統的底層音訊通道設計(Audio Session Category)中,預設會被標記為 Ambient(環境音效)。
當手機開啟側邊實體靜音鍵時,作業系統為了不打擾使用者,會在硬體層級強制壓制所有 Ambient 類別的音訊輸出(如同手遊背景音效會被切斷,但 YouTube 等預錄影片的 Playback 類別不會被切斷)。
💡 解決方式:使用手機瀏覽此類 Web Audio 專案時,必須將側邊「實體靜音開關」切換至響鈴模式!
🛠️ 六、 長曲目人體工學瓶頸 & 日文 Unicode 編碼地雷實證(Python 自動化管線)
這是我在開發過程中最硬核的兩大實戰收穫:
1. 長曲目的人體工學瓶頸與分治策略(Divide and Conquer)
在手機觸控螢幕上彈奏,雙拇指缺乏實體琴鍵的觸覺回饋。像《七龍珠 GT》神曲《漸漸被你吸引》時長數分鐘、伴奏聲部繁複,彈到後半段拇指肌肉極易僵硬;只要在最後數秒按錯一個音,整條錄音就直接報銷(單點故障)。
解法:將樂曲拆解為多個 Take 分段錄音,並撰寫 Python 腳本(基於 mido),自動抓取每段第一個與最後一個 Note On/Off 事件,剪除按下錄音與按停之間的空白發呆時間,並跨軌道重算絕對物理時間與 Delta Time,無縫重構出 91 秒的緊密完整音軌!
終端機實證如下:
PS C:\Users\chouy\Desktop\midi_merge> python merge.py
正在修剪頭尾空白並緊密無縫拼接...
✅ 第 1 段 [漸漸被你吸引.mid] 有效演奏長度: 23.75 秒(已剪除開頭發呆時間)
✅ 第 2 段 [漸漸被你吸引2.mid] 有效演奏長度: 39.98 秒(已剪除開頭發呆時間)
✅ 第 3 段 [漸漸被你吸引3.mid] 有效演奏長度: 27.30 秒(已剪除開頭發呆時間)
🎉 無縫拼接完成!最終緊湊時長: 91.09 秒,間隔已消除!
2. 深度避坑:日文濁音/促音與 Unicode「幽靈空格」陷阱
在為《超人力霸王捷德》主題曲(ジードの証.mid)進行二合一拼接時,控制台竟無情報錯:
FileNotFoundError: [Errno 2] No such file or directory: 'ジードの証.mid',甚至複製貼上字串後,假名之間會出現宛如按了空白鍵的間隙!
原理剖析:這是 Unicode NFC(合成形式)與 NFD(分解形式)標準衝突。
NFC:將基底字元與濁點融合成單一碼位(如 ジ 為 \u30b8,Windows 檔案系統常用)。
NFD:將字元拆成基底字元 + 結合濁音符號(如 シ \u30b7 加上結合音標 ゛ \u3099)。
某些終端機字型渲染 NFD 時,會把濁點獨立當成一個全形或半形字元渲染,外觀就像多了一個「刪不掉的空白鍵」;而作業系統底層是以二進位逐字節比對,檔名 byte 不一致直接引發 FileNotFoundError!
工程最佳實踐:
「後台管線 ASCII-Safe,前台介面 Localized」:後端處理與二進位檔案一律採純英數底線命名(如 Geed_part1.mid、Geed_No_Akashi_Full.mid),徹底避開 Windows I/O 與瀏覽器 fetch URL 編碼衝突;日文假名僅保留在前端 UI 按鈕純作顯示。
程式碼解法:在 Python 腳本中調用 unicodedata.normalize('NFC', filename) 將輸入檔名強制正規化。
🎵 七、 52 首手動演奏曲目庫存與動態篩選矩陣
網頁收錄了 52 首全數由本人在 Perfect Piano 中以雙拇指在狹窄觸控螢幕上錄製的原始演奏數據,並在前端使用 data-category 屬性與原生 JS 實現無刷新即時分類篩選:
🦸 特攝英雄宇宙(34 首):
超人力霸王:初代、賽文、傑克、衛司、太郎、雷歐、迪卡(Take Me Higher / 奇蹟再現)、帝納(正版降D轉E / 抒情版F轉降A)、蓋亞(片尾曲 / 悲傷的沼澤)、高斯(Spirit / 君にできるなにか)、納克斯(英雄)、梅比斯(主題曲 / 奇蹟)、傑洛、歐布、捷德(雙檔無縫合併完整版)。
假面騎士:空我、555 (Justifaiz)、Build (Be The One)。
超級戰隊:忍風戰隊破裏劍者主題曲與插曲(ハリケンジャー参上、影となりて悪を討つ、幸せShaking Hands)。
其他特攝經典:東寶《超星神格蘭賽沙》、松竹《魔彈戰記龍劍道》、國產特攝《鎧甲勇士》(光影傳奇、帝皇俠、刑天、拿瓦全系列主題曲與插曲)。
👻 林正英靈幻經典(5 首):
《鬼新娘》(「她的眼光,好似星星發光」)
《殭屍先生》同名旋律(九叔與四目道長合力制伏殭屍片段)
《殭屍復活II》(《殭屍家族》溫馨配樂)
《一眉道人》
《百鬼霧林》(《新殭屍先生》恐怖配樂)
📱 生活日常與流行鈴聲(8 首):
全家便利商店新鈴聲
資源回收車配樂《收酒矸》
經典廣告《感冒用斯斯》(改編自日文歌《嵐を呼ぶ男》)
經典廣告《益可膚》(改編自《小毛驢》)
三星手機鈴聲 Homecoming
Way Back Home
科目三洗腦神曲《一笑江湖》
台灣民謠《我愛鄉村》
🎮 動漫與遊戲配樂(5 首):
《七龍珠 GT》漸漸被你吸引(91 秒三 Take 無縫完整版)
《神魔之塔》夜幕下的旅途
《灌籃高手》好想大聲說喜歡你
《犬夜叉》穿越時空的思念
《犬夜叉》哀歌
💡 結語:從數據結構到聲學靈魂的工程實踐
演算法可以輕易改變調性與音色,但優質的旋律數據始終源於真實的演奏功底。這 52 首曲子完整保留了透過 Perfect Piano 在快節奏(BPM 130+)下,雙拇指克服小螢幕鍵位的人性化時序與微幅力度起伏。
當我們能以計算機科學的視角剖析音訊、用嚴謹的前端架構實現聲學互動、用 Python 自動化解決生理人體工學與編碼壁壘,並以多年淬鍊的演奏手感作為數據基石時,技術與音樂便不再是彼此平行的領域,而是交融為兼具工程深度與藝術溫度的完整作品!
🔗 線上實機 Demo(免安裝、點開即測,手機請切換為響鈴模式):
👉
歡迎對 Web Audio API、前端多媒體處理、Python 音訊管線或特攝/動漫配樂感興趣的夥伴一起交流討論!😄


