浏览代码

Add pages

Get page titles from notes.
master
TheoryOfNekomata 1 个月前
父节点
当前提交
767b74f357
共有 37 个文件被更改,包括 704 次插入810 次删除
  1. +21
    -5
      astro.config.mjs
  2. +7
    -7
      package.json
  3. +225
    -620
      pnpm-lock.yaml
  4. 二进制
      public/cover.jpg
  5. +105
    -0
      scripts/epubify.ts
  6. +23
    -0
      src/components/Score.astro
  7. +0
    -29
      src/components/Score/index.tsx
  8. +45
    -0
      src/layouts/Cover.astro
  9. +17
    -3
      src/layouts/Default.astro
  10. +89
    -0
      src/load-score.ts
  11. +1
    -2
      src/pages/ch01-introduction.mdx
  12. +5
    -0
      src/pages/ch02-speed.mdx
  13. +5
    -0
      src/pages/ch03-analysis.mdx
  14. +3
    -0
      src/pages/ch04-sound-projection.mdx
  15. +0
    -0
      src/pages/ch05-limits.mdx
  16. +0
    -0
      src/pages/ch06-overcoming-doubts.mdx
  17. +0
    -0
      src/pages/ch07-fingering.mdx
  18. +0
    -0
      src/pages/ch08-pulse.mdx
  19. +0
    -0
      src/pages/ch09-sensation.mdx
  20. +0
    -0
      src/pages/ch10-autopilot.mdx
  21. +9
    -0
      src/pages/ch11-musicality.mdx
  22. +0
    -0
      src/pages/ch12-the-musician.mdx
  23. +0
    -0
      src/pages/ch13-ornaments.mdx
  24. +0
    -0
      src/pages/ch14-rests.mdx
  25. +0
    -0
      src/pages/ch15-gestures.mdx
  26. +0
    -0
      src/pages/ch16-articulation.mdx
  27. +0
    -0
      src/pages/ch17-expression.mdx
  28. +0
    -0
      src/pages/ch18-mistakes.mdx
  29. +0
    -0
      src/pages/ch19-performance.mdx
  30. +0
    -3
      src/pages/ch20-trills.mdx
  31. +21
    -0
      src/pages/ch21-speed-and-anticipation.mdx
  32. +0
    -0
      src/pages/ch22-thirds.mdx
  33. +43
    -0
      src/pages/foreword.mdx
  34. +3
    -4
      src/pages/index.mdx
  35. +0
    -137
      src/pages/scores/[asset].svg.ts
  36. +34
    -0
      src/pages/scores/[id].svg.ts
  37. +48
    -0
      src/pages/toc.astro

+ 21
- 5
astro.config.mjs 查看文件

@@ -1,21 +1,37 @@
import { defineConfig } from 'astro/config';
import react from "@astrojs/react";
import tailwind from "@astrojs/tailwind";
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
import AutoImport from 'astro-auto-import';

import mdx from "@astrojs/mdx";
const defaultLayoutPlugin = () => (tree, file) => {
const path = file.history.at(-1).split('/').at(-1);
file.data.astro.frontmatter.layout = (
path.startsWith('index.')
? '../layouts/Cover.astro'
: '../layouts/Default.astro'
);
};

// https://astro.build/config
export default defineConfig({
trailingSlash: 'never',
output: 'static',
build: {
format: 'file',
},
compressHTML: false,
markdown: {
remarkPlugins: [defaultLayoutPlugin],
extendDefaultPlugins: true,
},
integrations: [
react(),
tailwind({
applyBaseStyles: false,
}),
AutoImport({
imports: [
'./src/components/Score.astro',
],
}),
mdx()
],
});

+ 7
- 7
package.json 查看文件

@@ -6,27 +6,27 @@
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"epubify": "tsx ./scripts/epubify.ts",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.5.10",
"@astrojs/mdx": "^2.2.4",
"@astrojs/react": "^3.1.1",
"@astrojs/tailwind": "^5.1.0",
"@types/react": "^18.2.74",
"@types/react-dom": "^18.2.24",
"astro": "^4.5.16",
"jsdom": "^24.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.4",
"verovio": "^4.1.0"
},
"devDependencies": {
"@types/archiver": "^6.0.2",
"@types/jsdom": "^21.1.6",
"@types/node": "^20.12.7",
"@types/verovio": "^3.13.4"
"@types/verovio": "^3.13.4",
"archiver": "^7.0.1",
"astro-auto-import": "^0.4.2",
"tsx": "^4.7.2"
}
}
}

+ 225
- 620
pnpm-lock.yaml
文件差异内容过多而无法显示
查看文件


二进制
public/cover.jpg 查看文件

之前 之后
宽度: 5242  |  高度: 2949  |  大小: 2.7 MiB

+ 105
- 0
scripts/epubify.ts 查看文件

@@ -0,0 +1,105 @@
// import { readFile } from 'fs/promises';
import archiver from 'archiver';
import {createWriteStream} from 'fs';
import {readdir} from 'fs/promises';
import assert from 'assert';

// const act = <T extends (...args: any[]) => any>(fn: T) => {
// return async (...args: Parameters<T>) => {
// let r;
// try {
// r = await fn(...args);
// } catch {
// // noop
// }
// return r;
// }
// };

const main = async () => {

// await act(cp)('dist', 'book', {recursive: true});
// await act(rm)('dist', {recursive: true, force: true});
// await act(mkdir)('dist');
// await act(cp)('book', 'dist/book', {recursive: true});

const { default: astroConfig } = await import('../astro.config.mjs');
const { outDir = 'dist/', output = 'static', build, compressHTML = false } = astroConfig;
assert(output === 'static');
assert(build?.format === 'file');
assert(!compressHTML)
const distFiles = await readdir(outDir);
// TODO get all assets and declare to manifest
console.log(distFiles);

return new Promise<number>((resolve, reject) => {
const output = createWriteStream('book.epub');
const archive = archiver('zip', {
zlib: {
level: 9,
},
});

output.on('close', () => {
resolve(archive.pointer());
});

archive.on('error', (err) => {
reject(err);
});

archive.pipe(output);

archive.append('application/epub+zip', { name: 'mimetype' });
archive.append(`<?xml version="1.0"?>
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
<rootfiles>
<rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>
`, { name: 'META-INF/container.xml' });
archive.append(`<?xml version="1.0"?>
<package xmlns="http://www.idpf.org/2007/opf" xmlns:opf="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="BookID">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:identifier id="BookID" opf:scheme="isbn">978-3-86680-192-9</dc:identifier>
<dc:identifier>3b622266-b838-4003-bcb8-b126ee6ae1a2</dc:identifier>
<dc:title>The title</dc:title>
<dc:language>fr</dc:language>
<dc:publisher>The publisher</dc:publisher>
<dc:creator>The author</dc:creator>
<dc:contributor>A contributor</dc:contributor>
<dc:description>A description</dc:description>
<dc:subject>A subject of the publication</dc:subject>
<dc:subject>Another subject of the publication</dc:subject>
<dc:rights>© copyright notice</dc:rights>
<meta property="dcterms:modified">2020-01-01T01:01:01Z</meta>
</metadata>
<manifest>
<item id="coverimage" href="OEBPS/cover.jpg" media-type="image/jpeg" properties="cover-image"/>
<item id="cover" href="OEBPS/index.xhtml" media-type="application/xhtml+xml"/>
<item id="toc" href="toc.html" media-type="application/xhtml+xml" properties="nav"/>
<item id="chapter-1" href="Text/chapter-1.xhtml" media-type="application/xhtml+xml"/>
<item id="chapter-2" href="Text/chapter-2.xhtml" media-type="application/xhtml+xml"/>
<item id="css" href="Styles/publication.css" media-type="text/css"/>
<item id="font1" href="Fonts/Andada-Italic.otf" media-type="application/vnd.ms-opentype"/>
<item id="font2" href="Fonts/Andada-Regular.otf" media-type="application/vnd.ms-opentype"/>
<item id="glyph" href="Images/glyph.png" media-type="image/png"/>
</manifest>
<spine>
<itemref idref="cover"/>
<itemref idref="toc"/>
<itemref idref="chapter-1"/>
<itemref idref="chapter-2"/>
</spine>
</package>
`, { name: 'content.opf' });
archive.directory(outDir, 'OEBPS');
});
};

main().then((bytes) => {
console.log(`${bytes} written.`);
});

+ 23
- 0
src/components/Score.astro 查看文件

@@ -0,0 +1,23 @@
---
import {loadScore} from '../load-score';

const { id, alt } = Astro.props;
const scoreXml = await loadScore(id);

---

<div class="score-wrapper">
<a href={`scores/${id}.svg`}>
<figure>
<Fragment set:html={scoreXml} />
<figcaption>
{alt}
</figcaption>
</figure>
</a>
<div class="screen-controls">
<a href={`scores/${id}.musicxml`}>
Download MusicXML
</a>
</div>
</div>

+ 0
- 29
src/components/Score/index.tsx 查看文件

@@ -1,29 +0,0 @@
import * as React from 'react';

export interface ScoreProps {
id: string;
alt: string;
}

export const Score: React.FC<ScoreProps> = ({
id,
alt,
}) => {
return (
<div className="score-wrapper">
<a href={`scores/${id}.svg`}>
<figure>
<img className="score" src={`scores/${id}.svg`} alt={alt} />
<figcaption>
{alt}
</figcaption>
</figure>
</a>
<div className="screen-controls">
<a href={`scores/${id}.musicxml`}>
Download MusicXML
</a>
</div>
</div>
);
};

+ 45
- 0
src/layouts/Cover.astro 查看文件

@@ -0,0 +1,45 @@
---
const { title } = Astro.props.frontmatter || Astro.props;
---

<!doctype html>
<html lang="en-PH">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<title>{title}</title>
<style is:global>
html {
@apply w-full;
@apply h-full;
}

body {
@apply w-full;
@apply h-full;
@apply m-0;
}

a {
@apply block;
@apply w-full;
@apply h-full;
}

img {
@apply w-full;
@apply h-full;
@apply object-center;
@apply object-cover;
}

@page {
margin: 0;
}
</style>
</head>
<body>
<slot />
</body>
</html>

+ 17
- 3
src/layouts/Default.astro 查看文件

@@ -55,17 +55,30 @@ const { title } = Astro.props.frontmatter || Astro.props;
}

@media only screen {
html {
@apply text-gray-800;
}

body {
@apply px-8;
@apply my-16;
}

div.score-wrapper figure svg {
@apply text-gray-800;
}
}

@media only print {
head {
@apply block;
@apply text-black;
}

div.score-wrapper figure svg {
@apply text-black;
}

title {
@apply block;
@apply text-5xl;
@@ -103,6 +116,10 @@ const { title } = Astro.props.frontmatter || Astro.props;
@apply text-white;
}

div.score-wrapper figure svg {
@apply text-white;
}

img.score {
filter: invert(100%) grayscale(100%);
}
@@ -123,8 +140,5 @@ const { title } = Astro.props.frontmatter || Astro.props;
</head>
<body>
<slot />
<div class="">

</div>
</body>
</html>

+ 89
- 0
src/load-score.ts 查看文件

@@ -0,0 +1,89 @@
import {JSDOM} from 'jsdom';
import createVerovioModule from 'verovio/wasm';
import {readFile} from 'node:fs/promises';
import {VerovioToolkit} from 'verovio/esm';

const processOutput = (xmlData: string) => {
const jsdom = new JSDOM(xmlData, { pretendToBeVisual: true, contentType: 'image/svg+xml' });
const win = jsdom.window;

const [svgElemsRoot, svgElemsMain] = Array.from(win.document.getElementsByTagName('svg'));
if (typeof svgElemsRoot === 'undefined') {
return '';
}

if (typeof svgElemsMain === 'undefined') {
return '';
}

Array.from(svgElemsRoot.children).forEach((h) => {
if (h !== svgElemsMain) {
h.remove();
if (h.tagName.toLowerCase() === 'desc') {
return;
}
if (h.tagName.toLowerCase() === 'style') {
h.innerHTML = h.innerHTML.replace(/font-family:(.+?);/g, '');
h.innerHTML += 'path,polygon,text{fill:currentColor;}';
}
if (svgElemsMain.children[0]) {
svgElemsMain.insertBefore(h, svgElemsMain.children[0]);
return;
}
svgElemsMain.appendChild(h);
}
});

Array.from(win.document.getElementsByClassName('pgHead')).forEach(h => {
h.remove();
});

Array.from(win.document.getElementsByClassName('pgFoot')).forEach(h => {
h.remove();
});

svgElemsMain.removeAttribute('color');

return svgElemsMain.outerHTML;
// return `<?xml version="1.0" encoding="utf-8"?>${svgElemsMain.outerHTML}`
};

export const loadScore = async (id: string) => {
const verovioModule = await createVerovioModule();
const score = await readFile(`public/scores/${id}.musicxml`, 'utf-8');
const verovioToolkit = new VerovioToolkit(verovioModule);
const isSuccessful = verovioToolkit.loadData(score);
let raw = '';
if (isSuccessful) {
verovioToolkit.setOptions({
breaks: 'none',
font: 'Bravura',
bottomMarginHeader: 0,
bottomMarginArtic: 0,
bottomMarginHarm: 0,
topMarginHarm: 0,
topMarginArtic: 0,
topMarginPgFooter: 0,
pageMarginTop: 0,
pageMarginBottom: 0,
pageMarginRight: 0,
pageMarginLeft: 0,
defaultBottomMargin: 0,
defaultTopMargin: 0,
defaultLeftMargin: 0,
defaultRightMargin: 0,
});

try {
raw = verovioToolkit.renderToSVG(1)
.replace(/xmlns:mei="(.+?)"/g, '')
.replace(/xlink:/g, '');
} catch (err) {
console.error(err);
}

return processOutput(raw);
}

return '';
};

+ 1
- 2
src/pages/ch01-introduction.mdx 查看文件

@@ -1,6 +1,5 @@
---
title: Introduction
layout: ../layouts/Default.astro
---

Detail about me here
It was like a masterclass.

+ 5
- 0
src/pages/ch02-speed.mdx 查看文件

@@ -0,0 +1,5 @@
---
title: Speed
---

Two Part Invention.

+ 5
- 0
src/pages/ch03-analysis.mdx 查看文件

@@ -0,0 +1,5 @@
---
title: Analysis
---

Discuss strategies on studying pieces here.

+ 3
- 0
src/pages/ch04-sound-projection.mdx 查看文件

@@ -0,0 +1,3 @@
---
title: Sound Projection
---

+ 0
- 0
src/pages/ch05-limits.mdx 查看文件


+ 0
- 0
src/pages/ch06-overcoming-doubts.mdx 查看文件


+ 0
- 0
src/pages/ch07-fingering.mdx 查看文件


+ 0
- 0
src/pages/ch08-pulse.mdx 查看文件


+ 0
- 0
src/pages/ch09-sensation.mdx 查看文件


+ 0
- 0
src/pages/ch10-autopilot.mdx 查看文件


+ 9
- 0
src/pages/ch11-musicality.mdx 查看文件

@@ -0,0 +1,9 @@
---
title: Musicality
---

(The beauty of simple pieces like Czerny etudes and Hanon exercises, and how you can still evoke musicality from them.)

"There is beauty in the simplest things"

"Like a solitary flower"

+ 0
- 0
src/pages/ch12-the-musician.mdx 查看文件


+ 0
- 0
src/pages/ch13-ornaments.mdx 查看文件


+ 0
- 0
src/pages/ch14-rests.mdx 查看文件


+ 0
- 0
src/pages/ch15-gestures.mdx 查看文件


+ 0
- 0
src/pages/ch16-articulation.mdx 查看文件


+ 0
- 0
src/pages/ch17-expression.mdx 查看文件


+ 0
- 0
src/pages/ch18-mistakes.mdx 查看文件


+ 0
- 0
src/pages/ch19-performance.mdx 查看文件


+ 0
- 3
src/pages/ch20-trills.mdx 查看文件

@@ -1,10 +1,7 @@
---
title: Trills
layout: ../layouts/Default.astro
---

import {Score} from '../components/Score';

I have attended the session where I was supposed to demonstrate the trills I had been working for the past few days. I
started with the third movement of Haydn Piano Sonata No. 6:



+ 21
- 0
src/pages/ch21-speed-and-anticipation.mdx 查看文件

@@ -0,0 +1,21 @@
---
title: Speed and Anticipation
---

We went back to the Czerny etudes, Op. 799 (?) No. 28 (?). I described how I was struggling with speed on the 16th note
passages. So Sir instructed me to play the etude at a comfortable tempo. I noticed I had experienced more ease with
playing now. With a bit of hiccups (because I was failing to anticipate certain passages of the piece), I had managed
to finish the etude. Then Sir instructed me to play the etude as fast as I could.

He related to me the "Argerich technique"--the hands likened to a praying mantis' limbs, and the fingers swiftly brushing
to hit the keys sharply but providing flexibility in the navigation of the other fingers when playing rapid passages.

Then he told me to play the thirds, and recalled the illusion of "connectedness" of the thirds, given there are "pivots"
where you can play in a somewhat legato manner. This is a fake legato, but it can be convincing enough.

I played the rest of the Czerny etudes which I have played before, and I have never felt so good. It's interesting to observe
the body naturally shaping legato passages when the shoulders are broad and open--which has been pointed out by Sir. I
think this is an adaptation to the geometry of the keyboard being lateral...thus the body being lateral makes sense
when operating on the geometry of the keyboard.

[Add image of Maene-Viñoly curved keyboards here.]

+ 0
- 0
src/pages/ch22-thirds.mdx 查看文件


+ 43
- 0
src/pages/foreword.mdx 查看文件

@@ -0,0 +1,43 @@
---
title: Foreword
---

I'm Allan, and welcome to my humble book of memoirs about piano playing.

The idea of taking notes about my lessons with Sir Dingdong Fiel started ever since I met him on our
first sessions. He handed me a small notebook to put down notes as we go along the journey of technique
and expression upon approaching the instrument. I have since filled a lot of its pages, and we were
equally proud of what we've accomplished after numerous productive weeks. One of our sessions, I decided
I'd want to progress this even further, so with the still-evolving knowledge of authoring books combined
with my experience in computers and technology (I work as a software developer by trade), I embarked on
a project in order to manage my thoughts, as well as to produce a living document of our experiences,
learnings, and aspirations.

I have been playing piano ever since I was young, at a very nascent age of 2 years. One day, a family
friend brought a keyboard at our home in Valenzuela (it was a Casio CTK-330)--unaware of anything else
but enthusiastic, I decided to try playing the instrument with a short selection of folk songs that
were taught to me (_Sitsirit-sit_, _Ako Ay May Lobo_), as well as some timeless classics like _Ode to
Joy_ (I credit Casio for putting "song banks" in their keyboards). I hope the reader would not see this
as hubris to know I was considered a "gifted child" and that I drank the best milk formula to make me
smart (that was always the branding of milk formulae ever since). Despite all those, I still consider
myself not anything out of the ordinary, that I led a relatively normal life. I had a period where
I played less piano, either simply I was unable to, or that I felt clueless and lost with which way to
go. I got my first piano purchase (a second-hand Clavinova) with my first paycheck, but I was not
immediately aiming to develop my skills, but to rekindle my interest with playing. When I moved out and
got a place of my own is when I started to try expanding my skills and repertoire, and since then I
set my goals to become better at piano. A series of events had led me to Sir Dingdong Fiel from a
Facebook ad regarding his mentorship program. I contacted him during the later days of 2023, while I was
on a serene and relaxing vacation from Batanes with my girlfriend. As soon as we got back home, on New
Year's Eve, then I got into a video call with Sir. I have played Chopin's Nocturne Op. 48 No. 1 for him.
We talked for a bit about music, and he told me I got into his program. I was glad and grateful, and
I anticipated our meetings in person.

The memoirs I present are a mix of experiences for the most part, as well as ideas that might be useful
to you, dear reader, that you could incorporate in your playing, or discuss with your peers when talking
about piano technique and musicality. This book, like any other material, serves to inspire instead of
forcing to a certain way of approaching the instrument. Each pianist will need to get accustomed to
various factors such as their physique and their mental workings in order to develop virtuosity in the
instrument.

If you found this book to add value to your playing, and ultimately to your life, then I am honored to
have given you a worthwhile experience. Thank you.

+ 3
- 4
src/pages/index.mdx 查看文件

@@ -1,8 +1,7 @@
---
title: Book Name
layout: ../layouts/Default.astro
---

Hello world!
Book cover here
<a href="toc.html">
<img src="cover.jpg" alt={frontmatter.title} />
</a>

+ 0
- 137
src/pages/scores/[asset].svg.ts 查看文件

@@ -1,137 +0,0 @@
import createVerovioModule from 'verovio/wasm';
import { VerovioToolkit } from 'verovio/esm';
import { readFile, readdir } from 'node:fs/promises';
import { JSDOM } from 'jsdom';
import type {APIRoute, GetStaticPaths} from 'astro';
// @ts-ignore
import tailwindConfigRaw from '../../../tailwind.config.mjs';

const filter = (musicXml: string) => {
const jsdom = new JSDOM(musicXml, { pretendToBeVisual: true, contentType: 'image/svg+xml' });
const win = jsdom.window;
// const win = jsdom.window;
return win.document.documentElement.outerHTML;
};

const processOutput = (xmlData: string) => {
const jsdom = new JSDOM(xmlData, { pretendToBeVisual: true, contentType: 'image/svg+xml' });
const win = jsdom.window;

const [svgElemsRoot, svgElemsMain] = Array.from(win.document.getElementsByTagName('svg'));
if (typeof svgElemsRoot === 'undefined') {
return '';
}

if (typeof svgElemsMain === 'undefined') {
return '';
}

// svgElemsRoot.getAttributeNames().forEach((a) => {
// const attr = svgElemsRoot.getAttribute(a);
// if (!attr) {
// return;
// }
// svgElemsMain.setAttribute(a, attr);
// });

Array.from(svgElemsRoot.children).forEach((h) => {
if (h !== svgElemsMain) {
h.remove();
if (h.tagName.toLowerCase() === 'desc') {
return;
}
if (h.tagName.toLowerCase() === 'style') {
const tailwindConfig = tailwindConfigRaw as any;
h.innerHTML = h.innerHTML.replace(/Times,serif/g, tailwindConfig.theme.fontFamily.body.join(','));
}
if (svgElemsMain.children[0]) {
svgElemsMain.insertBefore(h, svgElemsMain.children[0]);
return;
}
svgElemsMain.appendChild(h);
}
});

Array.from(win.document.getElementsByClassName('pgHead')).forEach(h => {
h.remove();
});

Array.from(win.document.getElementsByClassName('pgFoot')).forEach(h => {
h.remove();
});

Array.from(win.document.getElementsByTagName('defs')).forEach(h => {
h.remove();
if (svgElemsMain) {
if (svgElemsMain.children[0]) {
svgElemsMain.insertBefore(h, svgElemsMain.children[0]);
} else {
svgElemsMain.appendChild(h);
}
}
});

return `<?xml version="1.0" encoding="utf-8"?>${svgElemsMain.outerHTML}`
};

export const GET: APIRoute = async ({ params }) => {
const verovioModule = await createVerovioModule();
const score = await readFile(`public/scores/${params.asset}.musicxml`, 'utf-8');
const verovioToolkit = new VerovioToolkit(verovioModule);
const filteredScore = filter(score);
const isSuccessful = verovioToolkit.loadData(filteredScore);
if (!isSuccessful) {
return new Response(null, { status: 500 });
}

verovioToolkit.setOptions({
breaks: 'none',
font: 'Bravura',
bottomMarginHeader: 0,
bottomMarginArtic: 0,
bottomMarginHarm: 0,
topMarginHarm: 0,
topMarginArtic: 0,
topMarginPgFooter: 0,
pageMarginTop: 0,
pageMarginBottom: 0,
pageMarginRight: 0,
pageMarginLeft: 0,
defaultBottomMargin: 0,
defaultTopMargin: 0,
defaultLeftMargin: 0,
defaultRightMargin: 0,
});

let data: string;
try {
const raw = verovioToolkit.renderToSVG(1)
.replace(/xmlns:mei="(.+?)"/g, '')
.replace(/xlink:/g, '');
data = processOutput(raw);
} catch (err) {
console.error(err);
return new Response(null, { status: 500 });
}

return new Response(
data,
{
headers: {
'Content-Type': 'image/svg+xml',
},
status: 200,
}
);
};

export const getStaticPaths: GetStaticPaths = async () => {
const files = await readdir('public/scores');
return files
.filter((f) => f.endsWith('.musicxml'))
.map((f) => ({
params: {
asset: f.replace(/\.musicxml/g, ''),
},
}));
};

+ 34
- 0
src/pages/scores/[id].svg.ts 查看文件

@@ -0,0 +1,34 @@
import type {APIRoute, GetStaticPaths} from 'astro';
import {loadScore} from '../../load-score.ts';
import {readdir} from 'fs/promises';

export const GET: APIRoute = async ({ params }) => {
let data: string;
try {
data = await loadScore(params.id as string);
} catch (err) {
console.error(err);
return new Response(null, { status: 500 });
}

return new Response(
data,
{
headers: {
'Content-Type': 'image/svg+xml',
},
status: 200,
}
);
};

export const getStaticPaths: GetStaticPaths = async () => {
const files = await readdir('public/scores');
return files
.filter((f) => f.endsWith('.musicxml'))
.map((f) => ({
params: {
id: f.replace(/\.musicxml/g, ''),
},
}));
};

+ 48
- 0
src/pages/toc.astro 查看文件

@@ -0,0 +1,48 @@
---
import { readdir, readFile } from 'node:fs/promises';
const { default: Default } = await import('../layouts/Default.astro');

type Frontmatter = Record<string, unknown>;

interface PageProps {
frontmatter: Frontmatter;
}

const title = 'Table of Contents'
const allPages = await readdir('src/pages');
const pages = allPages.filter((p) => (
!p.startsWith('index.')
&& !p.endsWith('.astro')
&& p.endsWith('.mdx')
));

const pagesContentImported = await Promise.all(
pages.map(async (p) => {
const fileBuffer = await readFile(`src/pages/${p}`);
const file = fileBuffer.toString('utf-8');

const [, frontmatterRaw] = file.split('---');
const frontmatterLines = (frontmatterRaw?.split('\n') ?? []) as string[];
const frontmatter = Object.fromEntries(
frontmatterLines.map(l => l.split(':').map((s) => s.trim()))
) as Frontmatter;
return [
p.replace('.mdx', '.html'),
{
frontmatter,
},
] as [string, PageProps];
})
) as [string, PageProps][];
---
<Default title={title}>
<ol>
{pagesContentImported.map(([p, f]) => (
<li>
<a href={p.replace('.mdx', '')}>
{f.frontmatter.title}
</a>
</li>
))}
</ol>
</Default>

正在加载...
取消
保存