Spaces:
Sleeping
Sleeping
| import {Writable} from "stream"; | |
| import {MusicNotation} from "@k-l-lambda/music-widgets"; | |
| import {DOMParser} from "xmldom"; | |
| import {LilyDocument, docLocationSet} from "../inc/lilyParser"; | |
| import {engraveSvg} from "./lilyCommands"; | |
| import {SingleLock} from "../inc/mutex"; | |
| import * as staffSvg from "../inc/staffSvg"; | |
| import * as LilyNotation from "../inc/lilyNotation"; | |
| import LogRecorder from "../inc/logRecorder"; | |
| type StaffArguments = {attributes: staffSvg.StaffAttributes, tieLocations?: Set<string>, briefChordLocations?: Set<string>, lyricLocations?: Set<string>}; | |
| interface EngraverOptions { | |
| streamSeparator: string; | |
| includeFolders: string[]; | |
| withMIDI: boolean; | |
| withNotation: boolean; | |
| withLilyDoc: boolean; | |
| withLilyNotation: boolean; | |
| logger: LogRecorder; | |
| lilyNotation: LilyNotation.Notation; | |
| staffArgs: StaffArguments; | |
| }; | |
| interface GrammarParser { | |
| parse (source: string): any; | |
| }; | |
| const STREAM_SEPARATOR = "\n\n\n\n"; | |
| const advancedEngrave = async (source: string, lilyParser: GrammarParser, output: Writable, options: Partial<EngraverOptions> = {}) => { | |
| const {streamSeparator = STREAM_SEPARATOR} = options; | |
| const outputJSON = data => setImmediate(() => { | |
| output.write(JSON.stringify(data)); | |
| output.write(streamSeparator); | |
| }); | |
| const t0 = Date.now(); | |
| const notatioinGen = new SingleLock<LilyNotation.Notation>(true); | |
| const argsGen = new SingleLock<StaffArguments>(true); | |
| const hashKeys = new Set<string>(); | |
| const engraving = await engraveSvg(source, { | |
| includeFolders: options.includeFolders, | |
| // do some work during lilypond process running to save time | |
| onProcStart: () => { | |
| //console.log("tp.0:", Date.now() - t0); | |
| if (options.staffArgs) | |
| argsGen.release(options.staffArgs); | |
| if (!options.withLilyNotation && !options.withLilyDoc && options.staffArgs) | |
| return; | |
| const lilyDocument = new LilyDocument(lilyParser.parse(source)); | |
| if (!options.lilyNotation) { | |
| const interpreter = lilyDocument.interpret(); | |
| options.lilyNotation = interpreter.getNotation(); | |
| notatioinGen.release(options.lilyNotation); | |
| //console.log("tp.1:", Date.now() - t0); | |
| } | |
| if (options.withLilyDoc) | |
| outputJSON({lilyDocument: lilyDocument.root}); | |
| if (!options.staffArgs) { | |
| const attributes = lilyDocument.globalAttributes({readonly: true}) as staffSvg.StaffAttributes; | |
| const tieLocations = docLocationSet(lilyDocument.getTiedNoteLocations2()); | |
| const briefChordLocations = docLocationSet(lilyDocument.getBriefChordLocations()); | |
| const lyricLocations = docLocationSet(lilyDocument.getLyricLocations()); | |
| //console.log("tp.2:", Date.now() - t0); | |
| options.staffArgs = {attributes, tieLocations, briefChordLocations, lyricLocations}; | |
| argsGen.release(options.staffArgs); | |
| //console.log("tp.3:", Date.now() - t0); | |
| } | |
| }, | |
| onMidiRead: async midi => { | |
| //console.log("tm.0:", Date.now() - t0); | |
| if (options.withMIDI) | |
| outputJSON({midi}); | |
| if (options.withNotation && midi) { | |
| const midiNotation = MusicNotation.Notation.parseMidi(midi); | |
| outputJSON({midiNotation}); | |
| } | |
| if (options.withLilyNotation && midi) { | |
| const lilyNotation = options.lilyNotation || await notatioinGen.wait(); | |
| //console.log("tm.2:", Date.now() - t0); | |
| await LilyNotation.matchWithExactMIDI(lilyNotation, midi); | |
| //console.log("tm.3:", Date.now() - t0); | |
| outputJSON({lilyNotation}); | |
| } | |
| //console.log("tm.4:", Date.now() - t0); | |
| }, | |
| onSvgRead: async (index, svg) => { | |
| //console.log("ts.0:", index, Date.now() - t0); | |
| const args = options.staffArgs || await argsGen.wait(); | |
| //console.log("ts.1:", index, Date.now() - t0); | |
| const page = staffSvg.parseSvgPage(svg, source, {DOMParser, logger: options.logger, ...args}); | |
| // select incremental keys to send | |
| const hashTable = {}; | |
| Object.entries(page.hashTable).forEach(([key, elem]) => { | |
| if (!hashKeys.has(key)) | |
| hashTable[key] = elem; | |
| }); | |
| // rectify page data by lilyNotation | |
| const lilyNotation = options.lilyNotation || await notatioinGen.wait(); | |
| //console.log("ts.2:", index, Date.now() - t0); | |
| if (lilyNotation) { | |
| const sheetDocument = new staffSvg.SheetDocument({pages: [page.structure]}, {initialize: true}); | |
| sheetDocument.alignTokensWithNotation(options.lilyNotation, {partial: true}); | |
| sheetDocument.updateMatchedTokens(options.lilyNotation.idSet); | |
| } | |
| outputJSON({ | |
| page: index, | |
| structure: page.structure, | |
| hashTable, | |
| }); | |
| Object.keys(hashTable).forEach(key => hashKeys.add(key)); | |
| //console.log("ts.3:", index, Date.now() - t0); | |
| }, | |
| }); | |
| const tn = Date.now(); | |
| //console.log("tn:", tn - t0); | |
| options.logger.append("advancedEngraver.profile.engraving", {cost: tn - t0}); | |
| await new Promise(resolve => setImmediate(resolve)); | |
| output.write(JSON.stringify({ | |
| logs: engraving.logs, | |
| logger: options.logger, | |
| errorLevel: engraving.errorLevel, | |
| })); | |
| }; | |
| export { | |
| advancedEngrave, | |
| }; | |