NoteDigger
在线前端频谱分析扒谱 front-end music transcription
Install / Use
/learn @madderscientist/NoteDiggerREADME
noteDigger!
“NoteDigger”——音符挖掘者,即扒谱。模仿的是软件wavetone,但是是双击即用、现代UI的纯前端应用。<br> 就要重复造轮子!即:不使用框架、不使用外部库;目的是减小项目大小,并掌握各个环节。目前频谱分析的软件非常多,功能也超级强大,自知比不过……所以唯一能一战的就是项目体积了!作为一个纯前端项目,就要把易用的优点完全发扬!<br> 在线使用<br> 视频演示(视频发布于更新节奏对齐之前) <a href="https://www.bilibili.com/video/BV1XA4m1G7k4/" target="_blank"><img src="https://i1.hdslb.com/bfs/archive/4700166054a855cfe779f8d93f5ec1fb84293a12.jpg" alt="点击封面跳转视频" /></a>
使用流程
- 在线or下载到本地,用主流现代浏览器打开(开发使用Chrome)。
- 导入音频——文件-上传,或直接将音频拖拽进去!
- 选择声道分析,或者导入之前分析的结果(只有选择音频之后才有导入之前结果的接口)
- 根据频谱分析,开始绘制midi音符!可参考下文推荐流程。
- 导出为midi等,或者暂时导出项目(下次继续)
*建议的扒谱流程
- 导入时选择CQT。视情况勾选GPU。
- 在“分析”页面,点击“节奏分析”、“去除谐波”、“调性分析”(建议最后一个运行)。
- 勾选“设置”-“播放节拍”,修正节拍;接着使用小节栏右键菜单的“合并下一小节”等功能修正节奏型。
- 点击“分析”-“人工智障扒谱”,并在完成后将对应音轨锁定、静音,作为扒谱参考。
- 调整顶部滑条控制频谱强度,人工完成后续扒谱。
导入导出说明
- 导出进度: 结果是.nd的二进制文件,保存频谱图、音符、音轨、小节等信息。导入的时候并不会强制要求匹配原曲!(会根据文件名判断一下,但不强制)
- 导出为midi: 有两个模式。模式二只保证能听,节拍默认4/4,bpm默认60,midi类型默认1(同步多音轨),时间精度和设置的精度一致(因此如果midi先导入再导出会有量化误差);模式一会根据小节线进行对齐(需要用户设置好小节线),可以直接用于制谱,算法概述见下面“节奏对齐”。由于midi协议规定第十轨用于打击乐,因此扒谱时旋律需要避开第十轨,可以设置一个空音轨占位。本应用没有设计避开第十轨,也没设计扒鼓点,因此扒谱时第十轨虽然听起来还是乐音,但导出为midi后会变成鼓点。
- 导入midi: 将midi音符导入,只保证音轨、音符、音色能对应,音量默认127。如果导入后没有超过总音轨数,会在后面增加;否则会覆盖后面几轨(有提示)。
常规操作
- 空格: 播放
- 双击时间轴: 从双击的位置开始播放
- 在时间轴上拖拽: 设置重复区间
- 在时间轴上拉动小节线: 设置小节bpm;若按住shift拖动,只改变该小节线位置、不影响后续小节
- 鼠标中键时间轴: 将时间设置到点击位置,播放状态保持上一刻
- 鼠标右键时间轴(上半/下半): 具体设置重复时间/小节
- 按住空白拖动: 在当前音轨绘制一个音符
- 按住音符左半边拖动: 改变位置
- 按住音符右半边拖动: 改变时长
- Ctrl+点击音符: 多选音符
- delete: 删除选中的音符
- Ctrl+滚轮: 横向缩放
- 按住中键拖拽、触摸板滑动: 移动视野
快捷键
只有在导入并分析音频之后才能使用这些快捷键
- Ctrl+Z: 撤销(只记录16次历史,音轨、音符、小节线操作均会被记录)
- Ctrl+Y: 重做
- Ctrl+A: 全选当前音轨
- Ctrl+Shift+A: 全选所有音轨
- Ctrl+D: 取消选中
- Ctrl+C: 复制选中的音符
- Ctrl+X: 剪贴选中的音符
- Ctrl+V: 粘贴到选中的音轨上(暂不实现跨页面粘贴)
- Ctrl+B: 呼出/收回音轨面板
- Shift+右键: 菜单,包含撤销/重做、复制/粘贴、反选当前轨、删除
- ←↑→↓: 视野移动一格
- PageUp、PageDown:向前翻页/向后翻页
- Home:设置播放位置为0,播放状态保持上一刻
小细节
- 滑动条,如果旁边有数字,点击就可以恢复初始值。
- 多次点击“笔”右侧的选择工具,可以切换选择模式。(注意,只能选中当前音轨的音符)
- 点击某个音符可以选中该轨。
- 选择乐器时,展开下拉框并且按首字母可以快速跳转(浏览器下拉框自带)。
- 音轨中,“闭眼”只是看不见,还是可以操作的;一般要搭配“锁定”使用,默认两者会联动。
支持的格式
推荐使用常见的mp3、wav文件;除此之外,视频类文件也可以使用,比如mp4、mov、m4v。 但是如下格式不支持(浏览器API不支持解析)(仅仅在Chrome浏览器尝试过):
- aiff(苹果的音频格式)
对于ios的Safari浏览器,上传音频文件也许有些困难。可以选择视频。(不过为什么要用触屏控制啊,根本没适配)
其他说明
分析-自动填充,原理是将大于阈值的标记出来,效果不堪入目……于是研究并引入了基于神经网络的扒谱(分析-人工智障扒谱),但是效果非常初级。如有想法欢迎call me。
关于节奏对齐
“丑话说在前面”,绘制音符大概是不可能对齐小节线了(但是导出midi的时候会对齐),需要强迫症忍受一下。<br> 乐谱的单位是"x分音符",而音乐的单位是"秒"。如果要实现"小节对齐",单位要换成"x分音符"。整个程序时间轴一定要按照"秒"为单位,这是由频谱分析决定的;如果要实现制谱软件一样的对齐,那么音符绘制需要换成"x分音符"的对齐方式。这意味着在120bpm的小节下的音符,拉到60bpm的小节下,在以秒为尺度的时间轴下,音符会变长。wavetone就是这样处理的。<br> 但是对着原曲扒谱,最好还是根据"秒"来绘制音符。用wavetone扒谱的体验中,我最讨厌的就是被"x分音符"限制。用秒可以保证和原曲完全贴合,使用很灵活。但是这样导出的midi就不能直接制谱。按照"x分音符"来绘制音符还会导致程序很难写。开发者和使用者都不快乐。<br> 扒谱用秒为单位合适,而制谱用x分音符合适。为了跨越这个鸿沟,我这样设计了程序:使用midi文件作为对外的桥梁,在我的程序内用秒为单位扒谱,导出为midi的时候根据小节进行四舍五入的量化,形成规整的midi用于制谱。具体实现是:在秒轴上加入小节轴,用户可以拖动小节轴的某个小节调节后面紧跟的bpm相同的小节。小节轴只提供视觉上的辅助,对于画音符没一点限制。<br> 对齐算法有一定的限制,比如四分音符按照八分音符的划分对齐、八分音符按十六分音符的划分对齐……比如四分音符不可能在第三个16分音符开始,只可能在整数倍个8分音符的时长处开始。所以,绘制音符的时候到底可以偏差小节线多远心里有数了吧?
文件结构
│ app.js: 最重要的文件,主程序
│ favicon.ico: 小图标
│ index.html: 程序入口, 其js主要是按钮的onclick
│ jsconfig.json: 开发用 跨文件JS解析
│ LICENSE
│ README.md
│
├─core: app.js用到的核心组件
│ app_analyser.js: 算法相关
│ app_audioplayer.js: 音频播放
│ app_beatbar.js: 小节轴
│ app_hscrollbar.js: 底部滑动条
│ app_io.js: 管理输入与输出
│ app_keyboard.js: 左侧键盘
│ app_midiaction.js: 与音符的交互
│ app_midiplayer.js: 音符播放
│ app_spectrogram.js: 频谱绘制
│ app_timebar.js: 时间轴
│
├─lib
│ beatBar.js: 节奏信息的稀疏存储
│ fakeAudio.js: 模拟了不会响的Audio,用于midi编辑器模式
│ midi.js: midi创建、解析类
│ saver.js: 二进制保存相关
│ snapshot.js: 快照类, 实现撤销和重做
│ tinySynth.js: 合成器类, 负责播放音频
|
├─ui
│ channelDiv.js: 多音轨的UI界面类, 可拖拽列表
│ contextMenu.js: 右键菜单类
│ myRange.js: 横向滑动条的封装类
│ siderMenu.js: 侧边栏菜单类
│
├─plugins
│ chordEst.js
│ pitchName.js
|
├─dataProcess
| | aboutANA.md: 自动音符对齐的数学建模
| | ANA.js: 自动音符对齐
│ │ bpmEst.js: 节奏分析
| │ analyser.js: 频域数据分析与简化
| │ fft_real.js: 执行实数FFT获取频域数据
| │ stftGPU.js: 使用WebGPU加速STFT
| | NNLS.js: 非负最小二乘 用于去除谐波
| |
| ├─AI
│ │ │ AIEntrance.js: 开启AI的worker的入口
│ │ │ basicamt_44100.onnx: 音色无关转录 神经网络模型
│ │ │ basicamt_worker.js: 音色无关转录 新线程
│ │ │ postprocess.js:神经网络结果到音符的后处理
│ │ │ septimbre_44100.onnx: 音色分离转录 神经网络模型
│ │ │ septimbre_worker.js: 音色分离转录 新线程
│ │ │ SpectralClustering.js: 音色分离转录需要的 谱聚类
| │ │
| │ └─dist: onnxruntime打包
| │ bundle.min.js
| │ ort-wasm-simd.wasm
| |
│ └─CQT
│ cqt.js: 开启worker进行后台CQT
│ cqt_worker.js: CQT类与新线程
│
├─docs
│ todo.md: 一些设计思路和权衡
│
├─img
│ bilibili-white.png
│ github-mark-white.png
│ logo-small.png
│ logo.png
│ logo_text.png
│
└─style
│ askUI.css: 达到类似<dialog>效果
│ channelDiv.css: 多音轨UI样式
│ contextMenu.css: 右键菜单样式
│ myRange.css: 包装滑动条
│ siderMenu.css: 侧边菜单样式
│ style.css: index中独立元素的样式
│
└─icon: 从阿里图标库得到的icon
iconfont.css
iconfont.ttf
iconfont.woff
iconfont.woff2
重要更新记录
2026 2 22
增加了和弦识别功能,并优化了项目结构。
2026 2 14
利用NNLS完成了谐波的去除/基频的增强。<br> 代码位于reduceHarmonic。原理为用谐波模板拟合频谱,进而得到谐波,并收集谐波的能量用于加强基频。该方法能大大增强基频的明显程度。
2026 1 28
完成了基于WebGPU的STFT、CQT计算,即使在核显上也有显著加速。美中不足的是无法得知GPU算的进度,因此交互较弱<br> 也尝试了利用CQT相位分析瞬时频率,但效果太鸡肋被放弃,相关代码在另一个分支。
2026 1 23
更新了自动节奏检测功能,相关功能设置在“分析-节奏分析”中。<br> 使用传统信号处理算法,基于已经分析的频谱进行,原理点这。目前节拍识别较为准确,但节奏型与重拍的跟踪(勾选“自动节奏型”)效果不佳。建议在CQT分析前后都进行一次(等10秒左右会发现频谱改变,改变前是STFT,改变后是CQT),基于STFT的节奏全局更准,基于CQT的局部更精确。目前的算法参数仅仅根据20帧/秒时调整,其他分辨率不保证效果。<br> 为了让人工后处理更为便捷,在节奏栏右键增加了许多功能。
2025 12
更新了音色无关转录模型,效果比之前好,且运行时间更短。<br> 加入了音色分离转录,但是效果不怎么好,只适用于几个音色差异较大的简单场景,不同音色的音符不能重叠。推荐用于二重奏的情况,比如钢琴伴奏的某乐器独奏。
2025 9 20
降低重绘频率,降低闲时CPU占用为原来的1/10。感谢Initsnow的pr指出问题。<br> 有三类地方会触发刷新:
-
播放时保持高刷 在
AudioPlayer.update中触发刷新 -
所有键鼠操作。最初的想法是需要的地方触发刷新,但是牵扯的非常多——
- 快捷键。有些快捷键需要触发刷新,不如一刀切。
- 鼠标操作
- 在频谱画布上的操作:
MidiAction.updateView触发更新还不够(只负责了音符绘制模式),还有选择区域 - 在时间轴上的动作:重复区间 -> 可以在
TimeBar.setRepeat中触发更新;设置节拍 -> 可以在回调中触发更新 - 在钢琴画布上的动作:垂直方向移动,回调触发即可
- 画布之外的操作 只能设置统一的api;而音轨操作这种却不好加
- 没考虑到的功能——谁知道漏了会发生什么问题
- 在频谱画布上的操作:
综合考虑下,选择终极一刀切——所有用户操作都会触发刷新,仅需额外添加回调,不需要考虑具体动作,便于维护。
-
MidiAction.updateView处理一切音符变化。之所以不能被键鼠操作取代,因为ai扒谱延迟修改了音符。<br> 由于scroll2调用了updateView,因此还覆盖了resize、Spectrogram setter
2025 8 21
实现了自动音符对齐(auto note alignment),输入数字谱,得到和音频同步的音符,即“数字谱+音频→midi”。入口为:“分析”-“数字谱对齐音频”,输入单声部的数字谱,并调整八度范围,确定即可。注意,"1"对应的是C5,比常规约定高了八度,这是因为我发现往往分析泛音更为准确。如果效果不好,可以通过前后增加中/小括号实现八度升降。<br> 效果并不优秀,因为使用了传统的数学建模方法,但胜在计算量小。相关说明与建模见aboutANA.md。<br> 2025/12/27 将此算法整理为文章。
2025 8 14
项目代码的整理,将功能拆分到单独文件(虽然模块间仍未解耦,但终于拆分了!),并增加了开发配置文件(感觉TS是对的啊!但放不下JS里面的奇技淫巧)。<br> 修复了陈年bug:调整小节时不时“拉不动”、STFT偏早。
2025 8 12
进行了频谱的归一化,便于后续的研究与分析。归一化基于能量谱,同timbreAMT的做法,简而言之:令每一帧的能量的方差为1。<br> 重大的改动:WASM的CQT → JS的CQT。早期实验发现两者用时接近,为了纪念第一次使用WASM加速而保留。但升级CPU后发现JS版本用时不到WASM的一半,而且代码更少、文件更小。遂欣然废除。若要学习“C++编译为WASM”可以查看此前的历史提交。
2025 3 25
引入了“自动音乐转录”,即“AI扒谱”,导入音频后(或进入MIDI编辑器模式)在“分析”页面点击“人工智障扒谱”选项,一首两分半的曲子大概需要半分钟分析。由于追求低内存开销,我没保存音频数据,因此AI扒谱前要重新选择文件。<br> 使用的模型是我毕设的一部分,设计与训练过程请查看timbreAMT的basicamt文件夹,我称之为“音色无关转录”,即不会根据乐器种类分轨输出,但对大部分音色有适用性。对标的是basicPitch,效果接近且更加轻量,但无论是我的还是他的,结果都仅仅能听,而我的相比basicPitch优点在于集成在了noteDigger中,可以便捷地进行人工后处理,如删去多余音符、对齐节奏等。<br> 为了支持人工后处理,有了前几次更新,最重要的是:
- 音符力度用透明度体现,便于用户看清AI扒谱结果中重要的音符。在“设置”中可以关掉透明度。
- 音轨的锁,用于锁定AI扒谱结果,用户可以在新的音轨中“描”一遍,相当于把AI扒谱结果当做频谱。
2024 8 29
引入了理论上更精确的CQT分析。非file协议时(不是双击html文件打开时),当STFT(默认的计算方法)计算完成会在后台自动开启CQT计算,CQT结果将与当前频谱融合(会发现突然频谱变了)。CQT计算非常慢,因此在后台计算以防阻塞,且用C++实现、编译为WASM以提速。<br> 中途遇到很多坑,记录分布在/dataProcess/CQT的各个文件中,但效果其实并不值得这样的计算量。5分30秒的音频进行双声道CQT分析,需要45秒(从开启worker开始算),和直接进行js版的CQT用时差不多,加速了个寂寞。<br> 关于CQT的研究,记录在《CQT:从理论到代码实现》。<br> 此外尝试了“一边分析一边绘制频谱”,试图通过删除进度条达到感官上加速的效果。但是放在主线程造成严重卡顿,放弃。
2024 8 2
完成了issue2:不导入音频的midi编辑器。点击文件菜单下的“MIDI编辑器模式”就可以进入。<br> 视野的宽度取决于最后一个音符,模仿的是signal。也尝试过自动增加视野,可以一直往右拉,但是这样在播放的时候,开启“自动翻页”会永远停不下来(翻一页就自动拓展宽度)。<br> 扒谱框架下的midi编辑器还是有些反人类,因为绘制音符时的单位是时间而不是x分音符。不过也能用。<br> 原理是实现了一个空壳的Audio,只有计时功能,没有发声功能。一些做法写在了todo.md上。
2024 2 22
加入了节拍对齐功能,使用逻辑是:扒谱界面提供视觉辅助,导出midi会自动对齐,以实现制谱友好。详细对齐的原理请参看“关于节奏对齐”板块和midiExport.js文件。<br> 有一些细节:<br>
-
如果每个小节bpm都不一样(原曲的速度不稳,有波动),那导出midi前的对齐操作会以上一小节bpm为基准进行动态适应:先根据本小节的bpm量化音符为"x分音符",如果本小节bpm和上一小节的bpm差别在一定范围内,则再将"x分音符"的bpm设置全局量BPM;否则将全局BPM设置为当前小节的bpm。这个算法的要求是:的确要变速的前后bpm差异应该较大。<br>
-
在一个小节内,音符的近似方法:
- 记一个四分音符的格数为aqt(因为音符的实际使用单位是格。这里隐含了一个时间到格数的变换),某时刻t对应音符长度为ntlen,小节开始时刻记为mt。首先获取音符时刻相对小节开头的位置nt=t-mt。(音符时刻:将一个音符拆分为开始时刻和结束时刻。一个音符可能跨好几个小节,因此这样处理最为合适)
- 假设前提:时长长的音符的起点和终点的精度也低(精度这里指最小单位时长,低精度指单位时长对应的实际时间长)。因此近似精度accu采用自适应的方式:该音符可以用(ntlen/aqt)个四份音符表示,设其可以用一个(4*2^n)分音符近似,其中n满足:(1/2)^n<=ntlen/aqt<(1/2)^(n-1),则该音符的时长为aqt/(2^n),则精度设置为这个近似音符的一半:accu = aqt/(2^(n+1))。比如四份音符的精度是一个八分音符的时长。
- 近似后的时刻为:round(nt/accu)*accu。同时设置一个最低精度:八分音符。因此accu=min(aqt/2, aqt/(2^(n+1))),其中(1/2)^n<=ntlen/aqt<(1/2)^(n-1)。
-
小节信息如何存储、数据结构如何设计需要好好想想。大部分情况下(在原音频节奏稳定的情况下)只会变速几次,此时存变动时刻的bpm值就足矣。极端情况下每个小节都单独设置了bpm。如何设计数据结构能在两种情况下都取得较好的性能?使用稀疏数组。
2024 2 9
在今年完成了所有基本功能!本次更新了设置相关,简单地设计了调性分析的算法,已经完全可以用了!【随后在bilibil投稿了视频】
2024 2 8
文件系统已经完善!已经可以随心所欲导入导出保存啦!同时修复了一些小bug、完善了一些api。<br> 界面上,本打算将文件相关选项放到logo上,但是侧边菜单似乎有些空了,于是就加入到侧边栏,而logo设置为刷新或开新界面(考察了其他网站的logo的用途)。同时给侧边菜单加入了“设置”和“分析”,但本次更新没做。<br> midi相关操作来自[我的另一个项目](https://github.com/madderscien
Related Skills
node-connect
333.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
82.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
333.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
82.0kCommit, push, and open a PR
