UNPKG

maia-markov

Version:

Markov analysis and generation functions supporting various applications by Music Artificial Intelligence Algorithms, Inc.

1,185 lines (1,145 loc) 140 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = XmlImport; // Imports var fs = require('fs'); var xmlpstr = require('xml2js').parseString; var convert = require('xml-js').xml2js; var mu = require('maia-util'); // Constructor for XmlImport object function XmlImport(_fpath) { // Workaround for JS context peculiarities. // const self = this; this.fpath = _fpath; this.data = this.get_data(); this.compObj = this.xml2json(); // console.log("this.compObj.notes.slice(0, 10):", this.compObj.notes.slice(0, 10)) // Npo stands for no preservation of order, and has issues with non-maximal // backup values, that can be caused by things like voices ending partway // through a bar, and/or cue notes. It is deprecated and will be removed from // the constructor in a future release. // this.compObjNpo = this.xml2jsonNpo() // Possible to return something. // return sth; } // Methods for XmlImport object XmlImport.prototype = { constructor: XmlImport, get_data: function get_data() { return fs.readFileSync(this.fpath, "utf8"); }, xml2json: function xml2json() { var self = this; var logs = false; var co = {}; var rawJson = convert(self.data, { compact: false, spaces: 2 }); // Find score partwise. var spw = rawJson.elements.find(function (obj) { return obj.name === "score-partwise"; }); // console.log("spw:", spw) if (spw === undefined) { console.log("Could not find score-partwise. Returning early."); return; } var credit = []; spw.elements.forEach(function (obj) { if (obj.name === "credit") { obj.elements.filter(function (obj) { return obj.name === "credit-words"; }).forEach(function (obj) { // console.log("obj:", obj) var currJustify = obj.attributes && obj.attributes.justify; var currValign = obj.attributes && obj.attributes.valign; obj.elements.forEach(function (obj) { credit.push({ "justify": currJustify, "valign": currValign, "text": obj.text.trim() }); }); }); } }); // Try to assign name, copyright, composer, lyricist. // Put everything else in remarks. var name = void 0, copyright = void 0, composer = void 0, lyricist = void 0, remark = void 0; var possNameIdx = credit.findIndex(function (c) { return c.justify === "center" && c.valign === "top"; }); // console.log("possNameIdx:", possNameIdx) if (possNameIdx >= 0) { name = [mu.timelapse_object()]; name[0].name = credit[possNameIdx].text; credit.splice(possNameIdx, 1); } else { name = []; } var possCopyrightIdx = credit.findIndex(function (c) { return c.text.indexOf("Copyright") >= 0 || c.text.indexOf("copyright") >= 0 || c.text.indexOf("(C)") >= 0 || c.text.indexOf(String.fromCharCode(169)) >= 0; }); // console.log("possCopyrightIdx:", possCopyrightIdx) if (possCopyrightIdx >= 0) { copyright = [mu.timelapse_object()]; copyright[0].displayName = credit[possCopyrightIdx].text; credit.splice(possCopyrightIdx, 1); } else { copyright = []; } var possComposerIdx = credit.findIndex(function (c) { return c.justify === "right" && c.valign === "bottom"; }); // console.log("possComposerIdx:", possComposerIdx) if (possComposerIdx >= 0) { composer = [mu.timelapse_object()]; composer[0].displayName = credit[possComposerIdx].text; credit.splice(possComposerIdx, 1); } else { composer = []; } var possLyricistIdx = credit.findIndex(function (c) { return c.justify === "left" && c.valign === "bottom"; }); // console.log("possLyricistIdx:", possLyricistIdx) if (possLyricistIdx >= 0) { lyricist = [mu.timelapse_object()]; lyricist[0].displayName = credit[possLyricistIdx].text; credit.splice(possLyricistIdx, 1); } else { lyricist = []; } remark = credit.map(function (c) { var r = mu.timelapse_object(); r.remark = c.text; return r; }); co.name = name, co.remark = remark, co.copyright = copyright, co.composer = composer, co.lyricist = lyricist; // Populate layer. var layer = []; var staffNo = 0; // Possibly obsolete // Find the part list. var pl = spw.elements.find(function (obj) { return obj.name === "part-list"; }); // Find the things called part in elements of spw. var part = spw.elements.filter(function (obj) { return obj.name === "part"; }); // Find the score part. var sp = pl.elements.filter(function (obj) { return obj.name === "score-part"; }); // Use it to define layers. sp.forEach(function (obj) { var currLayer = mu.timelapse_object(); currLayer.type = "instrument"; currLayer.vexflow = {}; currLayer.vexflow.id = obj.attributes.id; // console.log("currLayer.vexflow.id:", currLayer.vexflow.id) var pn = obj.elements.find(function (obj) { return obj.name === "part-name"; }); if (pn !== undefined && pn.elements !== undefined && pn.elements.length > 0) { currLayer.vexflow.name = pn.elements[0].text; // console.log("currLayer.vexflow.name:", currLayer.vexflow.name) } // Search for and assign an idInstrument. // ... // Relevant clefs // Note that MusicXML files created by hum2xml have this information // stored directly on attributes and not on clef properties of the // elements of attributes. So if we work with such files, the code below // would need editing to take this into account. // Also, at the moment, we assume it's always possible to associate one or // more clefs with a part. If we allow for this not to be possible, again // the code below would need editing. // See xml2jsonNpo for solutions to these implemented previously. var relClefs = part.find(function (p) { return p.attributes.id === obj.attributes.id; }).elements.find(function (obj) { return obj.name === "measure"; }).elements.find(function (m) { return m.name === "attributes"; }).elements.filter(function (a) { return a.name === "clef"; }); // console.log("relClefs:", relClefs) relClefs.forEach(function (clef, idx) { currLayer.staffNo = staffNo; // console.log("clef.elements:", clef.elements) clef.elements.forEach(function (el) { var propName = void 0; switch (el.name) { case "sign": propName = "clefSign"; break; case "line": propName = "clefLine"; break; case "clef-octave-change": propName = "clefOctaveChange"; break; default: // console.log("Unrecognised clef property.", clef) } if (propName) { currLayer.vexflow[propName] = el.elements[0].text; } }); currLayer.vexflow.clef = mu.clef_sign_and_line2clef_name(currLayer.vexflow.clefSign, currLayer.vexflow.clefLine, currLayer.vexflow.clefOctaveChange); layer.push(mu.copy_array_object(currLayer)); staffNo++; }); }); // console.log("layer:", layer) // Find the parts. // Should be one of each of these for the whole piece of music. var divisions = void 0, anacrusis = void 0; var clefChanges = [], keySignatures = [], timeSignatures = [], notes = [], rests = [], ties = []; ///// // Time signatures need more work. // Just putting in a default for now. //// var timeSig = mu.timelapse_object(); timeSig.barNo = 1, timeSig.topNo = 4, timeSig.bottomNo = 4, timeSig.ontime = 0; timeSignatures = [timeSig]; // Iterate over them to define stuff like notes. part.forEach(function (obj, partIdx) { // Catching an anacrusis and initialising ontime and intOnt are handled as // part of case "divisions" below. var ontime = void 0, intOnt = void 0; if (anacrusis !== undefined && divisions !== undefined) { ontime = anacrusis; intOnt = Math.round(divisions * anacrusis); } // Get relevant staffNo. Obsolete? // const currStaffNo = layer.find(function(l){ // return l.vexflow.id === obj.attributes.id // }).staffNo // console.log("\ncurrStaffNo:", currStaffNo) // Get relevant staffNos. var staffNosForId = layer.filter(function (l) { return l.vexflow.id === obj.attributes.id; }).map(function (l) { return l.staffNo; }); // console.log("staffNosForId:", staffNosForId) var measure = obj.elements.filter(function (obj) { return obj.name === "measure"; }); // console.log("measure:", measure) measure.forEach(function (obj) { // Need to do this in order for ontime to be correct. var measureNumber = parseInt(obj.attributes.number); // console.log("measureNumber:", measureNumber, "ontime:", ontime) var elMeasure = obj.elements; elMeasure.forEach(function (obj, idx) { // console.log("Elements of the measure:") // console.log(obj) var intDur = void 0; switch (obj.name) { case "attributes": var el = obj.elements.forEach(function (obj2) { switch (obj2.name) { case "divisions": if (divisions !== undefined) { console.log("Redefining divisions, which is unusual/not permitted."); } divisions = parseInt(obj2.elements[0].text); // Now that we have a divisions value, we can determine if there // is an anacrusis at the beginning of this piece. var aAndCpb = self.convert_1st_bar2anacrusis_val(elMeasure, divisions); // console.log("aAndCpb:", aAndCpb) anacrusis = aAndCpb[0]; // crotchetsPerBar = aAndCpb[1] ontime = anacrusis; if (divisions * anacrusis !== Math.floor(divisions * anacrusis)) { console.log("divisions*anacrusis is not an integer, but it should be!"); } intOnt = Math.round(divisions * anacrusis); // console.log( // "divisions:", divisions, "anacrusis:", anacrusis, // "crotchetsPerBar:", crotchetsPerBar, "ontime:", ontime, // "intOnt:", intOnt // ) break; case "key": var currKey = mu.timelapse_object(); currKey.barNo = measureNumber + (anacrusis === 0); var possFifths = obj2.elements.find(function (obj) { return obj.name === "fifths"; }); if (possFifths !== undefined) { possFifths = parseInt(possFifths.elements[0].text); } currKey.fifths = possFifths || 0; var possMode = obj2.elements.find(function (obj) { return obj.name === "mode"; }); if (possMode !== undefined) { possMode = possMode.elements[0].text; } currKey.mode = possMode || 0; currKey.keyName = mu.nos_symbols_and_mode2key_name(currKey.fifths, currKey.mode); // It is important to realise that when a MusicXML file says // fifths, what it means is the number of sharps (positive // integer) or flats (negative integer) in the key signature. So // A minor will have a fifths value of 0, but A is three steps // clockwise from C on the circle of fifths, so this code adjusts // the fifths value of minor keys to reflect this. if (currKey.mode === "minor") { currKey.fifthSteps += 3; } switch (currKey.mode) { case "major": currKey.mode = 0; break; case "minor": currKey.mode = 5; break; case "ionian": currKey.mode = 0; break; case "dorian": currKey.mode = 1; break; case "phrygian": currKey.mode = 2; break; case "lydian": currKey.mode = 3; break; case "mixolydian": currKey.mode = 4; break; case "aeolian": currKey.mode = 5; break; case "locrian": currKey.mode = 6; break; } /////////// // MORE WORK REQUIRED HERE. /////////// // currKey.staffNo = []; // Populated in for loop below. // // Get ontime from bar number rather than from the ontime // // variable, because there could still be rounding errors here. // currKey.ontime // = mu.ontime_of_bar_and_beat_number(currKey.barNo, 1, time_sig_array); // for (let staffi = 0; staffi < staff_nos_for_this_id.length; staffi++){ // currKey.staffNo = staff_nos_for_this_id[staffi]; // key_sig_array.push(currKey); // } keySignatures.push(currKey); break; default: // console.log("Should not get here in switch over measure.elements.", obj.name) } }); break; case "note": var currNote = mu.timelapse_object(); var restTf = false, graceTf = false, cueTf = false, tieArr = []; obj.elements.forEach(function (obj) { switch (obj.name) { case "pitch": var xmlPitch = {}; obj.elements.forEach(function (obj) { xmlPitch[obj.name] = obj.elements[0].text; }); // console.log("xmlPitch:", xmlPitch) currNote.pitch = self.xml_pitch2pitch_class_and_octave(xmlPitch); var mnnMpn = mu.pitch_and_octave2midi_note_morphetic_pair(currNote.pitch); currNote.MNN = mnnMpn[0]; currNote.MPN = mnnMpn[1]; break; case "rest": restTf = true; break; case "duration": intDur = parseInt(obj.elements[0].text); break; case "staff": // console.log("Got to a staff!") // console.log("obj.elements:", obj.elements) break; case "voice": // console.log("Got to a voice!") // console.log("obj.elements:", obj.elements) var staffVoiceNos = mu.staff_voice_xml2staff_voice_json(obj.elements[0].text, staffNosForId, partIdx); // console.log("staffVoiceNos:", staffVoiceNos) currNote.staffNo = staffVoiceNos[0]; currNote.voiceNo = staffVoiceNos[1]; break; case "type": break; case "time-modification": // This (and perhaps other parts of this converter) need more // work to handle stuff like // <time-modification> // <actual-notes>3</actual-notes> // <normal-notes>2</normal-notes> // <normal-type>16th</normal-type> // <normal-dot/> // <normal-dot/> // </time-modification> var timeMod = {}; obj.elements.forEach(function (obj) { if (obj.elements !== undefined && obj.elements.length > 0 && obj.elements[0].text !== undefined) { timeMod[obj.name] = obj.elements[0].text; } else { // console.log("obj:", obj) } }); currNote.timeMod = timeMod; break; case "stem": break; case "beam": break; case "tie": tieArr.push(obj.attributes); break; case "accidental": currNote.accidental = obj.elements[0].text; break; case "notations": break; default: case "dot": break; case "grace": graceTf = true; break; case "cue": cueTf = true; break; // console.log("Should not get here in switch over note's obj.elements:", obj.name) } // Ends switch(obj.name) }); // Ends iteration over elements of the note. if (!graceTf && !cueTf) { // Update ontime etc. here. var duration = Math.round(intDur / divisions * 100000) / 100000; // This is offtime in crotchet beats rounded to 5 decimal places. var offtime = Math.round((intOnt + intDur) / divisions * 100000) / 100000; var barBeat = mu.bar_and_beat_number_of_ontime(ontime, timeSignatures); var barOn = barBeat[0]; var beatOn = Math.round(barBeat[1] * 100000) / 100000; barBeat = mu.bar_and_beat_number_of_ontime(offtime, timeSignatures); var barOff = barBeat[0]; var beatOff = Math.round(barBeat[1] * 100000) / 100000; if (!restTf) { currNote.barOn = barOn; currNote.beatOn = beatOn; currNote.ontime = ontime; currNote.duration = duration; currNote.barOff = barOff; currNote.beatOff = beatOff; currNote.offtime = offtime; //// // THIS NEEDS REVISITING! //// // let staff_and_voice_nos // = mu.staff_voice_xml2staff_voice_json( // notes[note_index].voice, staff_nos_for_this_id, part_idx); // currNote.staffNo = staff_and_voice_nos[0]; // currNote.voiceNo = staff_and_voice_nos[1]; // Could add some more properties here, like integer duration // as expressed in the MusicXML file, stem direction, etc. NB, // if there are ties here, properties such as intDur, type, // stem, beam, etc. are not accurate reflections of the summary // oblong properties, and they are removed by resolve_ties. // Lyric. // Once it's established whether a note is part of a tie or not, // we can either push it to notes or to ties. if (tieArr.length === 0) { notes.push(currNote); } else { // console.log("tieArr:", tieArr) if (tieArr.length > 1) { currNote.tieType = "stop and start"; } else { currNote.tieType = tieArr[0].type; } ties.push(currNote); } } else {} // ... // If the note is the first, second,..., (n - 1)th note of an n- // note chord, then do not increment these variables. Wait till // the nth note. if (idx < elMeasure.length - 1 && elMeasure[idx + 1].elements !== undefined && elMeasure[idx + 1].elements.find(function (obj) { return obj.name === "chord"; })) { // Do nothing! } else { ontime = offtime; intOnt += intDur; // console.log("restTf:", restTf, "ontime:", ontime, "intOnt:", intOnt) } // ... } else if (!cueTf) { // Handle grace notes here. NB grace notes have no duration. // ... } else { // Handle cue notes here. NB cue notes have no duration. // ... } break; case "backup": // console.log("Got to a backup.") intDur = parseInt(obj.elements[0].elements[0].text); // console.log("backup amount:", intDur) intOnt -= intDur; ontime = Math.round(intOnt / divisions * 100000) / 100000; // console.log("intOnt:", intOnt, "ontime:", ontime) break; case "forward": // console.log("Got to a forward.") intDur = parseInt(obj.elements[0].elements[0].text); // console.log("forward amount:", intDur) intOnt += intDur; ontime = Math.round(intOnt / divisions * 100000) / 100000; // console.log("intOnt:", intOnt, "ontime:", ontime) break; default: // console.log("Should not get here in switch over measure.elements.", obj.name) } }); // elMeasure.forEach(function(obj, idx) }); // measure.forEach(function(obj) }); // part.forEach(function(obj) var notesAndTied = notes.concat(self.resolve_ties(ties)); co.notes = notesAndTied.sort(mu.sort_points_asc); co.layer = layer; co.timeSignatures = timeSignatures; co.keySignatures = keySignatures; // Append some miscellaneous information. if (co.miscImport === undefined) { co.miscImport = {}; } if (co.miscImport.musicXml === undefined) { co.miscImport.musicXml = { "divisions": divisions, "anacrusis": anacrusis }; } return co; // Staff and clef names. // Get the staff names, abbreviations, IDs, and initial associated clefs // (for clef changes, see further below). We include initial associated // clefs because often people use these instead of instrument names to // refer to staves. // let staff_and_clef_names = []; // let staff_no = 0; // if (xmlAsJson["score-partwise"]["part-list"]){ // let part_list = xmlAsJson["score-partwise"]["part-list"]; // if (part_list[0]["score-part"]){ // for (let parti = 0; parti < part_list[0]["score-part"].length; parti++){ // // console.log('score_part:'); // // console.log(part_list[0]["score-part"][parti]); // let curr_staff = {}; // curr_staff.name = part_list[0]["score-part"][parti]["part-name"][0]; // if (part_list[0]["score-part"][parti]["part-abbreviation"]){ // curr_staff.abbreviation // = part_list[0]["score-part"][parti]["part-abbreviation"][0]; // // } // curr_staff.id = part_list[0]["score-part"][parti].$.id; // // Use the ID to find the initial associated clef. // curr_staff.clef = "unknown"; // let target_idx = -1; // if (xmlAsJson["score-partwise"]["part"]){ // let partj = 0; // while (partj < xmlAsJson["score-partwise"]["part"].length){ // if (xmlAsJson["score-partwise"]["part"][partj].$.id == curr_staff.id){ // target_idx = partj; // partj = xmlAsJson["score-partwise"]["part"].length - 1; // } // partj++; // } // } // // console.log('target_idx:'); // // console.log(target_idx); // if (target_idx >= 0 && // xmlAsJson["score-partwise"]["part"][target_idx] && // xmlAsJson["score-partwise"]["part"][target_idx].measure && // xmlAsJson["score-partwise"]["part"][target_idx].measure[0].attributes){ // let curr_attr = xmlAsJson["score-partwise"]["part"][target_idx].measure[0].attributes; // // console.log('curr_attr:'); // // console.log(curr_attr); // // We found the associated part - try to find the associated clef. // let clef_attr = xmlAsJson["score-partwise"]["part"][target_idx].measure[0].attributes[0].clef; // // Handle MusicXML files created by hum2xml. // if (clef_attr == undefined){ // let attri = 0; // while (attri < curr_attr.length){ // if (curr_attr[attri].clef){ // clef_attr = curr_attr[attri].clef; // attri = curr_attr.length - 1; // } // attri++; // } // } // if (clef_attr == undefined){ // console.log('Could not associate any clefs with part ID: ' + // curr_staff.id); // console.log('We recommend editing the MusicXML file to ' + // 'explicity specify clefs for each staff, prior to ' + // 'upload.'); // curr_staff.staffNo = staff_no; // // console.log('curr_staff:'); // // console.log(curr_staff); // staff_and_clef_names.push(mu.copy_array_object(curr_staff)); // staff_no = staff_no + 1; // } // else{ // // console.log('clef_attr:'); // // console.log(clef_attr); // for (let clefi = 0; clefi < clef_attr.length; clefi++){ // curr_staff.clefSign = clef_attr[clefi].sign[0]; // curr_staff.clefLine = parseInt(clef_attr[clefi].line[0]); // if (clef_attr[clefi]["clef-octave-change"]){ // curr_staff.clefOctaveChange = clef_attr[clefi]["clef-octave-change"][0]; // } // curr_staff.clef = mu.clef_sign_and_line2clef_name(curr_staff.clefSign, // curr_staff.clefLine, // curr_staff.clefOctaveChange); // curr_staff.staffNo = staff_no; // // console.log('curr_staff:'); // // console.log(curr_staff); // staff_and_clef_names.push(mu.copy_array_object(curr_staff)); // staff_no = staff_no + 1; // } // } // } // } // } // } // co.staffAndClefNames = staff_and_clef_names; // // // Key signatures. // let key_sig_array = []; // co.keySignatures = key_sig_array; // // This is populated in the iteration over measures within each part, // // because different parts can have independent key signatures. // // // Retrieve all parts in the Music XML file. // let part = xmlAsJson['score-partwise'].part; // // // Focus on the top staff first, to get things like the divisions value // // and any time signature changes. // let measure = part[0].measure; // // // Define the divisions value. There should be one of these for the whole // // piece of music. // let divisions // if(measure[0].attributes){ // let attributes = measure[0].attributes; // for(let j = 0; j < attributes.length; j++){ // if(attributes[j].divisions){ // divisions = parseInt(attributes[j].divisions[0]); // console.log('Divisions: ' + divisions); // } // } // } // // // Handle an anacrusis here. // // console.log('bar_1:'); // // console.log(measure[0]); // let anacrusis_and_crotchets_per_bar // = mu.convert_1st_bar2anacrusis_val(measure[0], divisions); // let anacrusis = anacrusis_and_crotchets_per_bar[0]; // let crotchets_per_bar = anacrusis_and_crotchets_per_bar[1]; // console.log('anacrusis:'); // console.log(anacrusis); // console.log('crotchets_per_bar:'); // console.log(crotchets_per_bar); // // // Time signatures array. We only need to do this for one staff. It should // // apply across all other staves. // let time_sig_array = []; // for (let measure_index = 0; measure_index < measure.length; measure_index++){ // if (measure[measure_index].attributes){ // let attributes = measure[measure_index].attributes; // for (let j = 0; j < attributes.length; j++){ // if (attributes[j].time){ // // Assuming there is only one time per attribute... // let time_sig_curr = {}; // time_sig_curr.barNo = measure_index + (anacrusis == 0); // time_sig_curr.topNo = parseInt(attributes[j].time[0].beats[0]); // time_sig_curr.bottomNo = parseInt(attributes[j].time[0]['beat-type'][0]); // console.log('A time signature in bar: ' + time_sig_curr.barNo + ', top number: ' + time_sig_curr.topNo // + ', bottom number: ' + time_sig_curr.bottomNo); // // console.log(attributes[j].time[0].beats[0])+"\n"; // time_sig_array.push(time_sig_curr); // } // } // } // } // if (anacrusis != 0) { // time_sig_array // = mu.append_ontimes_to_time_signatures( // time_sig_array, crotchets_per_bar); // } // else { // time_sig_array = mu.append_ontimes_to_time_signatures(time_sig_array); // } // // console.log('Time signatures array: ' + time_sig_array); // co.timeSignatures = time_sig_array; // // // Tempo changes. // let tempo_changes = []; // co.tempi = tempo_changes; // // // Clef changes. // let clef_changes = []; // co.clefChanges = []; // // // Sequencing (repeat marks, 1st, 2nd time, da capo, etc.). We only need to // // do this for one staff. It should apply across all other staves. // let sequencing = []; // for (let measure_index = 0; measure_index < measure.length; measure_index++){ // // Direction to do with barline, or 1st, 2nd-time bars. // if (measure[measure_index].barline){ // let barline = measure[measure_index].barline; // for (let j = 0; j < barline.length; j++){ // // console.log('sequencing command:'); // // console.log(barline[j].repeat); // let curr_sequence = {}; // curr_sequence.barNo = measure_index + (anacrusis == 0); // curr_sequence.type = "barline"; // if (barline[j].$ && barline[j].$.location){ // curr_sequence.location = barline[j].$.location; // } // if (barline[j].ending){ // curr_sequence.endingNo = barline[j].ending[0].$.number; // curr_sequence.endingType = barline[j].ending[0].$.type; // } // if (barline[j].style){ // curr_sequence.style = barline[j].style; // } // if (barline[j].repeat){ // curr_sequence.repeatDir = barline[j].repeat[0].$.direction; // } // // console.log('Bar number:'); // // console.log(curr_sequence.barNo); // // console.log('curr_sequence:'); // // console.log(curr_sequence); // curr_sequence.ontime // = mu.ontime_of_bar_and_beat_number( // curr_sequence.barNo, 1, time_sig_array); // sequencing.push(curr_sequence); // } // } // // Direction like dal segno. // if (measure[measure_index].direction){ // let direction = measure[measure_index].direction; // for (let j = 0; j < direction.length; j++){ // if (direction[j]["direction-type"] && // direction[j]["direction-type"][0].words){ // // console.log('direction:'); // // console.log(direction[j]); // let poss_commands = ["Fine", "D.C.", "D.C. al Fine", // "D.C. al Coda", "D.S. al Coda", // "D.S. al Fine", "D.S.", "To Coda"]; // let target_idx // = poss_commands.indexOf(direction[j]["direction-type"][0].words[0]); // // console.log('target_idx:'); // // console.log(target_idx); // if (target_idx >= 0){ // let curr_sequence = {}; // curr_sequence.barNo = measure_index + (anacrusis == 0); // curr_sequence.type = "command"; // if (direction[j].$ !== undefined){ // curr_sequence.placement = direction[j].$.placement; // } // curr_sequence.words = direction[j]["direction-type"][0].words[0]; // curr_sequence.ontime // = mu.ontime_of_bar_and_beat_number( // curr_sequence.barNo, 1, time_sig_array); // sequencing.push(curr_sequence); // } // } // } // } // } // // // Define the page layout array object, which contains information relating // // to system breaks, page breaks, system spacers, etc. For page and system // // breaks, current thinking is we only need to do this for one staff, // // because it should apply. Spacers (which put a bit more or less space // // between pairs of staves within or between systems when required) do not // // seem to be exported in the MusicXML file, but if they were, these would // // need identifying across all parts. // let page_layout = {}; // let page_breaks = []; // let system_breaks = []; // // let spacers = []; // for (let measure_index = 0; measure_index < measure.length; measure_index++){ // if(measure[measure_index].print){ // // console.log('Print command!'); // // console.log(measure[measure_index].print); // let print_array = measure[measure_index].print; // for (let printi = 0; printi < print_array.length; printi++){ // if (print_array[printi].$ && // print_array[printi].$["new-page"]){ // page_breaks.push(measure_index + (anacrusis == 0)); // } // if (print_array[printi].$ && // print_array[printi].$["new-system"]){ // system_breaks.push(measure_index + (anacrusis == 0)); // } // } // } // } // if (page_breaks.length == 0 && system_breaks.length == 0){ // // Insert default page and system breaks. // let page_and_system_breaks // = mu.default_page_and_system_breaks( // staff_and_clef_names, measure.length); // page_breaks = page_and_system_breaks[0]; // system_breaks = page_and_system_breaks[1]; // } // page_layout.pageBreaks = page_breaks; // page_layout.systemBreaks = system_breaks; // // // Iterate over each part and build up the notes array. // // // Define the notes array. // let notes_array = []; // let noteID = 0; // let tied_array = []; // let grace_array = []; // // Define the rests array. This is not necessary for displaying a freshjam // // project, but the information is present in the MusicXML file (and could // // help us display the traditional staff notation). So in the interests of // // lossless conversion, I'm storing the rest information too. // let rests_array = []; // let restID = 0; // // Define the expressions array. This is not necessary for displaying a // // freshjam project, but the information is present in the MusicXML file // // (and could help us display the traditional staff notation). So in the // // interests of lossless conversion, I'm storing the rest information too. // let xprss_array = []; // let xprssID = 0; // // for (let part_idx = 0; part_idx < part.length; part_idx++){ // // console.log('Part: ' + part_idx); // let ontime = anacrusis; // // Incrementing integer representation of ontime, using divisions. // let intOnt = anacrusis*divisions; // let part_id = part[part_idx].$.id; // // This variable tells you which staff number(s) should be associated // // with a particular part. In MusicXML 2.0, keyboard instruments such as // // piano or harpsichord will have two staves written within one part. // let staff_nos_for_this_id = []; // for (let staffi = 0; staffi < staff_and_clef_names.length; staffi++){ // if (staff_and_clef_names[staffi].id == part_id){ // staff_nos_for_this_id.push(staff_and_clef_names[staffi].staffNo); // } // } // // console.log('staff_nos_for_this_id:'); // // console.log(staff_nos_for_this_id); // // measure = part[part_idx].measure; // for (let measure_index = 0; measure_index < measure.length; measure_index++){ // // // console.log('\nMeasure: ' + measure_index); // // // Key signatures and clef changes. // if(measure[measure_index].attributes){ // let attributes = measure[measure_index].attributes; // // console.log('attributes:'); // // console.log(attributes); // for(let j = 0; j < attributes.length; j++){ // // Key signatures. // if(attributes[j].key){ // // console.log('key:'); // // console.log(attributes[j].key); // let curr_key = {}; // curr_key.barNo = measure_index + (anacrusis == 0); // if (attributes[j].key[0].mode == undefined){ // attributes[j].key[0].mode = ['major']; // } // curr_key.keyName // = mu.nos_symbols_and_mode2key_name(attributes[j].key[0].fifths[0], // attributes[j].key[0].mode[0]); // // // It is important to realise that when a MusicXML file says // // fifths, what it means is the number of sharps (positive // // integer) or flats (negative integer) in the key signature. So // // A minor will have a fifths value of 0, but A is three steps // // clockwise from C on the circle of fifths, so this code adjusts // // the fifths value of minor keys to reflect this. // switch(attributes[j].key[0].mode[0]){ // case 'minor': // curr_key.fifthSteps = parseInt(attributes[j].key[0].fifths[0]) + 3; // break; // default: // curr_key.fifthSteps = parseInt(attributes[j].key[0].fifths[0]); // break; // } // switch(attributes[j].key[0].mode[0]){ // case 'major': // curr_key.mode = 0; // break; // case 'minor': // curr_key.mode = 5; // break; // case 'ionian': // curr_key.mode = 0; // break; // case 'dorian': // curr_key.mode = 1; // break; // case 'phrygian': // curr_key.mode = 2; // break; // case 'lydian': // curr_key.mode = 3; // break; // case 'mixolydian': // curr_key.mode = 4; // break; // case 'aeolian': // curr_key.mode = 5; // break; // case 'locrian': // curr_key.mode = 6; // break; // } // curr_key.staffNo = []; // Populated in for loop below. // // Get ontime from bar number rather than from the ontime // // variable, because there could still be rounding errors here. // curr_key.ontime // = mu.ontime_of_bar_and_beat_number(curr_key.barNo, 1, time_sig_array); // for (let staffi = 0; staffi < staff_nos_for_this_id.length; staffi++){ // curr_key.staffNo = staff_nos_for_this_id[staffi]; // key_sig_array.push(mu.copy_array_object(curr_key)); // } // } // // // Clef changes. // if(attributes[j].clef){ // let clef_attr = attributes[j].clef; // // console.log('clef in measure ' + measure_index + ':'); // // console.log(clef_attr); // let curr_clef = {}; // curr_clef.barNo = measure_index + (anacrusis == 0); // // Get ontime from bar number rather than from the ontime // // variable, because there could still be rounding errors here. // curr_clef.ontime // = mu.ontime_of_bar_and_beat_number(curr_clef.barNo, 1, time_sig_array); // curr_clef.clef = "unknown"; // Populated below. // for (let clefi = 0; clefi < clef_attr.length; clefi++){ // curr_clef.clefSign = clef_attr[clefi].sign[0]; // curr_clef.clefLine = parseInt(clef_attr[clefi].line[0]); // if (clef_attr[clefi]["clef-octave-change"]){ // curr_clef.clefOctaveChange = clef_attr[clefi]["clef-octave-change"][0]; // } // curr_clef.clef = mu.clef_sign_and_line2clef_name(curr_clef.clefSign, // curr_clef.clefLine, // curr_clef.clefOctaveChange); // if (clef_attr[clefi].$ && clef_attr[clefi].$.number){ // // console.log('clef number:'); // // console.log(clef_attr[clefi].$.number); // curr_clef.staffNo // = staff_nos_for_this_id[parseInt(clef_attr[clefi].$.number[0]) - 1]; // } // else{ // curr_clef.staffNo = staff_nos_for_this_id[0]; // } // // curr_clef.staffNo = staff_no; // // console.log('curr_staff:'); // // console.log(curr_staff); // clef_changes.push(mu.copy_array_object(curr_clef)); // // staff_no = staff_no + 1; // } // } // } // } // // // Tempo changes and expressions. // if (measure[measure_index].direction){ // let direction = measure[measure_index].direction; // for (let j = 0; j < direction.length; j++){ // // Tempo change. // if (direction[j].sound && // direction[j].sound[0].$ && // direction[j].sound[0].$.tempo){ // let curr_tempo = {}; // // Timing will need updating to be more precise. // curr_tempo.barOn = measure_index + (anacrusis == 0); // curr_tempo.beatOn = 1; // curr_tempo.ontime // = mu.ontime_of_bar_and_beat_number( // curr_tempo.barOn, 1, time_sig_array); // curr_tempo.bpm = parseFloat(direction[j].sound[0].$.tempo); // // console.log('direction-type:'); // // console.log(direction[j]["direction-type"]); // if (direction[j]["direction-type"] && // direction[j]["direction-type"][0].words){ // curr_tempo.tempo = direction[j]["direction-type"][0].words[0]; // } // if (mu.array_object_index_of( // tempo_changes, curr_tempo.ontime, "ontime") == -1){ // // Some MusicXML files contain duplicate tempo instructions. // // The check above will not allow tempo instructions with the // // same ontime as an existing tempo instruction to be inserted // // in the tempo_changes array. // tempo_changes.push(curr_tempo); // } // } // // Expression - dynamic. // if (direction[j]["direction-type"] && // direction[j]["direction-type"][0].dynamics){ // let curr_xprss = {}; // curr_xprss.ID = xprssID.toString(); // // Timing will need updating to be more precise. // curr_xprss.barOn = measure_index + (anacrusis == 0); // curr_xprss.beatOn = 1; // curr_xprss.ontime // = mu.ontime_of_bar_and_beat_number( // curr_xprss.barOn, 1, time_sig_array); // for (let key in direction[j]["direction-type"][0].dynamics[0]){ // // This is not really a loop because there is probably only one // // key. // curr_xprss.type = { "dynamics": key }; // if (direction[j].$ !== undefined){ // curr_xprss.placement = direction[j].$.placement; // } // if (direction[j].staff){ // curr_xprss.staffNo // = staff_nos_for_this_id[parseInt(direction[j].staff[0]) - 1]; // } // else{ // curr_xprss.staffNo = staff_nos_for_this_id[0]; // } // xprss_array.push(curr_xprss); // xprssID++; // } // } // // Expression - wedge. // if (direction[j]["direction-type"] && // direction[j]["direction-type"][0].wedge){ // let curr_xprss = {}; // curr_xprss.ID = xprssID.toString(); // // Timing will need updating to be more precise. // curr_xprss.barOn = measure_index + (anacrusis == 0); // curr_xprss.beatOn = 1; // curr_xprss.ontime // = mu.ontime_of_bar_and_beat_number( // curr_xprss.barOn, 1, time_sig_array); // // console.log('wedge:'); // // console.log(direction[j]["direction-type"][0].wedge[0]); // curr_xprss.type = { "wedge": direction[j]["direction-type"][0].wedge[0].$.type }; // if (direction[j].$ !== undefined){ // curr_xprss.placement = direction[j].$.placement; // } // if (direction[j].staff){ // curr_xprss.staffNo // = staff_nos_for_this_id[parseInt(direction[j].staff[0]) - 1]; // } // else{ // curr_xprss.staffNo = staff_nos_for_this_id[0]; // } // xprss_array.push(curr_xprss); // xprssID++; // } // } // } // // // Grab the number of backups, which are used to encode multiple voices // // in one measure on one staff. // let backups, time_at_end_of_this_bar // if (measure[measure_index].backup){ // backups = measure[measure_index].backup; // // Filter out any backup values that are not equal to the maximum // // backup value. A POTENTIALLY DANGEROUS STRATEGY, but need a way to // // take account of backups that are associated with cue notes and so // // do not advance voiceNo in the usual way. // const maxBackup = mu.max_argmax(backups.map(function(b){ // return b.duration[0] // }))[0] // const fullBarBackups = [] // const partBarBackups = [] // backups.forEach(function(b){ // if (b.duration[0] === maxBackup){ // fullBarBackups.push(b) // } // else { // partBarBackups.push(b) // } // }) // backups = fullBarBackups // // // console.log('Backup: ' + backups); // time_at_end_of_this_bar = // mu.ontime_of_bar_and_beat_number( // measure_index + (anacrusis == 0) + 1, 1, time_sig_array); // // console.log('Time at end of bar: ' + time_at_end_of_this_bar); // } // // if (measure[measure_index].note){ // let notes = measure[measure_index].note; // // console.log('notes:', notes) // // let voiceNo = 0; // Increment this with appearances of backup. // for (let note_index = 0; note_index < notes.length; note_index++){ // // // console.log('Note index: ' + note_index); // let note_curr = {}; // let rest = 0; // Detect if it is a rest instead of a note. // let rest_curr = {}; // // if ( // notes[note_index].grace === undefined && // notes[note_index].cue === undefined // ){ // // Handle pitch information. // // console.log("notes[note_index].pitch:", notes[note_index].pitch) // if (notes[note_index].pitch){ // // console.log("INSIDE!") // // console.log("notes[note_index].pitch[0]:", notes[note_index].pitch[0]) // let final_pitch = // self.xml_pitch2pitch_class_and_octave(notes[note_index].pitch[0]); // // console.log("final_pitch:", final_pitch) // if (final_pitch == undefined){ // console.log("notes[note_index].pitch[0]:", notes[note_index].pitch[0]) // console.log("final_pitch:", final_pitch) // } // let MNN_MPN = mu.pitch_and_octave2midi_note_morphetic_pair(final_pitch); // // Populate note_curr properties. // note_curr.ID = noteID.toString(); // // console.log('NoteID: ' + note_curr.ID); // noteID++; // note_curr.pitch = final_pitch; // note_curr.MNN = MNN_MPN[0]; // note_curr.MPN = MNN_MPN[1]; // // console.log('Pitch: ' + final_pitch + ', MNN: ' + MNN_MPN[0] + ', MPN: ' + MNN_MPN[1]); // } // else { // Rest. // rest = 1; // rest_curr.ID = restID.toString(); // restID++; // } // // // Handle timing information. // // Begin with the integer