@@ -1,21 +1,37 @@ | |||||
import { defineConfig } from 'astro/config'; | 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({ | export default defineConfig({ | ||||
trailingSlash: 'never', | trailingSlash: 'never', | ||||
output: 'static', | output: 'static', | ||||
build: { | build: { | ||||
format: 'file', | format: 'file', | ||||
}, | }, | ||||
compressHTML: false, | |||||
markdown: { | |||||
remarkPlugins: [defaultLayoutPlugin], | |||||
extendDefaultPlugins: true, | |||||
}, | |||||
integrations: [ | integrations: [ | ||||
react(), | |||||
tailwind({ | tailwind({ | ||||
applyBaseStyles: false, | applyBaseStyles: false, | ||||
}), | }), | ||||
AutoImport({ | |||||
imports: [ | |||||
'./src/components/Score.astro', | |||||
], | |||||
}), | |||||
mdx() | mdx() | ||||
], | ], | ||||
}); | }); |
@@ -6,27 +6,27 @@ | |||||
"dev": "astro dev", | "dev": "astro dev", | ||||
"start": "astro dev", | "start": "astro dev", | ||||
"build": "astro check && astro build", | "build": "astro check && astro build", | ||||
"epubify": "tsx ./scripts/epubify.ts", | |||||
"preview": "astro preview", | "preview": "astro preview", | ||||
"astro": "astro" | "astro": "astro" | ||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"@astrojs/check": "^0.5.10", | "@astrojs/check": "^0.5.10", | ||||
"@astrojs/mdx": "^2.2.4", | "@astrojs/mdx": "^2.2.4", | ||||
"@astrojs/react": "^3.1.1", | |||||
"@astrojs/tailwind": "^5.1.0", | "@astrojs/tailwind": "^5.1.0", | ||||
"@types/react": "^18.2.74", | |||||
"@types/react-dom": "^18.2.24", | |||||
"astro": "^4.5.16", | "astro": "^4.5.16", | ||||
"jsdom": "^24.0.0", | "jsdom": "^24.0.0", | ||||
"react": "^18.2.0", | |||||
"react-dom": "^18.2.0", | |||||
"tailwindcss": "^3.4.3", | "tailwindcss": "^3.4.3", | ||||
"typescript": "^5.4.4", | "typescript": "^5.4.4", | ||||
"verovio": "^4.1.0" | "verovio": "^4.1.0" | ||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@types/archiver": "^6.0.2", | |||||
"@types/jsdom": "^21.1.6", | "@types/jsdom": "^21.1.6", | ||||
"@types/node": "^20.12.7", | "@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" | |||||
} | } | ||||
} | |||||
} |
@@ -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.`); | |||||
}); |
@@ -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> |
@@ -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> | |||||
); | |||||
}; |
@@ -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> |
@@ -55,17 +55,30 @@ const { title } = Astro.props.frontmatter || Astro.props; | |||||
} | } | ||||
@media only screen { | @media only screen { | ||||
html { | |||||
@apply text-gray-800; | |||||
} | |||||
body { | body { | ||||
@apply px-8; | @apply px-8; | ||||
@apply my-16; | @apply my-16; | ||||
} | } | ||||
div.score-wrapper figure svg { | |||||
@apply text-gray-800; | |||||
} | |||||
} | } | ||||
@media only print { | @media only print { | ||||
head { | head { | ||||
@apply block; | @apply block; | ||||
@apply text-black; | |||||
} | } | ||||
div.score-wrapper figure svg { | |||||
@apply text-black; | |||||
} | |||||
title { | title { | ||||
@apply block; | @apply block; | ||||
@apply text-5xl; | @apply text-5xl; | ||||
@@ -103,6 +116,10 @@ const { title } = Astro.props.frontmatter || Astro.props; | |||||
@apply text-white; | @apply text-white; | ||||
} | } | ||||
div.score-wrapper figure svg { | |||||
@apply text-white; | |||||
} | |||||
img.score { | img.score { | ||||
filter: invert(100%) grayscale(100%); | filter: invert(100%) grayscale(100%); | ||||
} | } | ||||
@@ -123,8 +140,5 @@ const { title } = Astro.props.frontmatter || Astro.props; | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<slot /> | <slot /> | ||||
<div class=""> | |||||
</div> | |||||
</body> | </body> | ||||
</html> | </html> |
@@ -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,6 +1,5 @@ | |||||
--- | --- | ||||
title: Introduction | title: Introduction | ||||
layout: ../layouts/Default.astro | |||||
--- | --- | ||||
Detail about me here | |||||
It was like a masterclass. |
@@ -0,0 +1,5 @@ | |||||
--- | |||||
title: Speed | |||||
--- | |||||
Two Part Invention. |
@@ -0,0 +1,5 @@ | |||||
--- | |||||
title: Analysis | |||||
--- | |||||
Discuss strategies on studying pieces here. |
@@ -0,0 +1,3 @@ | |||||
--- | |||||
title: Sound Projection | |||||
--- |
@@ -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" |
@@ -1,10 +1,7 @@ | |||||
--- | --- | ||||
title: Trills | 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 | 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: | started with the third movement of Haydn Piano Sonata No. 6: | ||||
@@ -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 +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. |
@@ -1,8 +1,7 @@ | |||||
--- | --- | ||||
title: Book Name | title: Book Name | ||||
layout: ../layouts/Default.astro | |||||
--- | --- | ||||
Hello world! | |||||
Book cover here | |||||
<a href="toc.html"> | |||||
<img src="cover.jpg" alt={frontmatter.title} /> | |||||
</a> |
@@ -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, ''), | |||||
}, | |||||
})); | |||||
}; |
@@ -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, ''), | |||||
}, | |||||
})); | |||||
}; |
@@ -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> |