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, briefChordLocations?: Set, lyricLocations?: Set}; 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 = {}) => { 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(true); const argsGen = new SingleLock(true); const hashKeys = new Set(); 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, };