在网上冲浪时,无意中发现了 abcjs 这个将文字渲染成乐谱的软件,感觉很有趣,而且还有开发者为 Obsidian 维护了 obsidian-plugin-abcjs 插件,我想试试能不能移植到 quartz 上。
失败的引入
在阅读 ObsidianFlavoredMarkdown 的处理代码的时候,我发现对 mermaid 的处理符合我的要求:
quartz\plugins\transformers\ofm.ts if (opts.mermaid) {
js. push ({
script: `
let mermaidImport = undefined
document.addEventListener('nav', async () => {
if (document.querySelector("code.mermaid")) {
mermaidImport ||= await import('https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.esm.min.mjs')
const mermaid = mermaidImport.default
const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
theme: darkMode ? 'dark' : 'default'
})
await mermaid.run({
querySelector: '.mermaid'
})
}
});
` ,
loadTime: "afterDOMReady" ,
moduleType: "module" ,
contentType: "inline" ,
})
}
简单来说就是在运行时动态导入 mermaid,而且它的选项开启也很简单:
if (opts.mermaid) {
plugins. push (() => {
return ( tree : Root , _file ) => {
visit (tree, "code" , ( node : Code ) => {
if (node.lang === "mermaid" ) {
node.data = {
hProperties: {
className: [ "mermaid" ],
},
}
}
})
}
})
}
在检查到 mermaid 代码的时候做替换。
那我们可不可以用相同的方法导入 abcjs 呢?很遗憾,不可以。简单总结导入的代码如下:
< html >
<! doctype html >
< script type = "module" >
async function load () {
let abcObj = await import ( 'https://cdn.jsdelivr.net/npm/[email protected] /dist/abcjs-basic-min.js' );
}
load ();
</ script >
< button >Click me</ button >
</ html >
报错
abcjs-basic-min.js:3 Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'ABCJS')
at abcjs-basic-min.js:3:186
at abcjs-basic-min.js:3:191
对比了一下 mermaid 提供的 mjs 和 abcjs 提供的 js,问题其实在于 abcjs 没有提供 export,我们只能导入整个文件。
成功的引入
在阅读了 latex.ts
引入的方式之后,我有了一点新的想法。我们可以将 abcjs 作为 externalResources
引入,仿照 ofs.ts
处理。简单来说流程是:
在解析代码时将带有 music-abc
的代码赋予 abcjs
类;
在渲染时将 abcjs 类的数据交给 ABCJS.renderAbc
渲染;
最终显示在屏幕上。
整体代码如下所示:
quartz/plugins/transformers/musicabc.ts import { PluggableList } from "unified"
import { QuartzTransformerPlugin } from "../types"
import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast"
import { SKIP, visit } from "unist-util-visit"
import { JSResource } from "../../util/resources"
// Configuration documented at https://remark42.com/docs/configuration/frontend/
interface Options {
}
export const MusicABC : QuartzTransformerPlugin < Partial < Options > | undefined > = () => {
return {
name: "MusicABC" ,
markdownPlugins ( _ctx ) {
const plugins : PluggableList = []
plugins. push (() => {
return ( tree : Root , _file ) => {
visit (tree, "code" , ( node : Code ) => {
if (node.lang === "music-abc" ) {
node.data = {
hProperties: {
className: [ "abcjs" ],
},
}
}
})
}
})
return plugins
},
externalResources () {
const js : JSResource [] = []
js. push ({
script: `
document.addEventListener('nav', async () => {
if (document.querySelector("code.abcjs")) {
document.querySelectorAll("code.abcjs").forEach((element) => {
const abcNotation = element.textContent;
var abcElement = document.createElement('p');
element.parentNode.replaceWith(abcElement);
ABCJS.renderAbc(abcElement, abcNotation);
abcElement.style.overflow = null;
});
}
});
` ,
loadTime: "afterDOMReady" ,
moduleType: "module" ,
contentType: "inline" ,
})
js. push ({
src: "https://cdn.jsdelivr.net/npm/[email protected] /dist/abcjs-basic-min.js" ,
loadTime: "afterDOMReady" ,
contentType: "external" ,
})
return {
css: [
"https://cdn.jsdelivr.net/npm/[email protected] /abcjs-audio.css" ,
],
js: js,
}
},
}
}
这里面做了一堆操作,在渲染的时候将父节点替换成 <p></p>
然后在渲染之后将 overflow
这个选项设置为 null
。因为我发现如果不将 overflow
设置为 null
或在 code
这个节点上渲染的话,乐谱不会渲染完全,因此就在父节点上渲染了。
Usage
目前还没有合并进主线,我也没想好是否要合并(感觉可以趁机练习一下 Cherry Pick),等解决下面的问题之后再去提交 issue 吧。
现在的用法很简单,将上面的代码保存为 quartz/plugins/transformers/musicabc.ts
,在 quartz/plugins/transformers/index.ts
上导出,然后在 quartz.config.ts
里面的 transformers
使用即可。
Problems
在手机上或者小屏幕乐谱可能会超出边界
%
标记渲染错误
abc 记号法用两个 %
标记一些额外信息,比如 MIDI 声道之类的,但是由于两个 %
在 Obsidian 中是注释标记,位于行首的时候似乎会被 quartz 忽略导致渲染错误。
嚯,看上去是上游的问题,我直接打两个 %
会被 quartz 渲染出错。找个时间确认一下吧。
我尝试将插件的编译位置提前,但是并没有解决。
之前添加的标记:
\%\%MIDI program 0
\%\%barnumbers 0
\%\%MIDI program 0
\%\%barnumbers 0
TODOs
番外
尝试添加一段乐谱(在这个乐谱 上面做了一些修改),拙劣的弹奏见【原神】清昼细雨 :
X:1
T: 清昼细雨
C: 陈致逸, HOYO-MiX
L: 1/4
Q: 1/4=155
M: 4/4
K: G
V:1
z4 | z4 | [f'4-^c''4-] | [f'4c''4] |
V:2
"_LEGATO" \
B d a f- | f4 | z4 | z4 |
V:1
z4 | z4 | [^c'4-a'4-] | [c'4a'4] |
V:2
F A e ^c- | c4 | z4 | z4 |
V:1
z4 | z4 | [f'4-^c''4-] | [f'4c''4] |
V:2
B d a f- | f4 | z4 | z4 |
V:1
z4 | z4 | [^c'4-a'4-] | [c'4a'4] |
V:2
F A e ^c- | c4 | z4 | z4 |
V:1
b z f a- | a4 | [ae'] z z2 | z4 |
V:2
E B z F- | F4 | F A e2- | e4 |
V:1
d'' z z2 | z4 | [e'^c''] z z2 | z4 |
V:2
B d a f- | f4 | ^c e a2- | a4 |
V:1
b z f a- | a4 | [ae'] z z2 | z4 |
V:2
E B z F- | F4 | F A e2- | e4 |
V:1
B d a f- | f4 | [f'4-^c''4-] | [f'4c''4] |]
V:2
B, z z2 | z4 | z4 | z4 |]