UNPKG

maia-markov

Version:

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

1,293 lines (1,221 loc) 277 kB
var mm = (function () { 'use strict'; // Imports // import 'maia-util' // import mu from 'maia-util' const fs$5 = require('fs'); const path$1 = require('path'); const mu$6 = require('maia-util'); // const uu = require('uuid') // Constructor for Analyzer object function Analyzer$1(){ // Workaround for JS context peculiarities. // var self = this; // Possible to return something. // return sth; } // Methods for Analyzer object Analyzer$1.prototype = { constructor: Analyzer$1, comp_obj2beat_mnn_states: function( compObj, onAndOff = true, idxOn = 0, idxMNN = 1, idxDur = 3 ){ // Tom Collins 11/1/2015. // This function converts a compObj variable to an array consisting of // json variables. Each json variable contains state and context information // for a segment of the input compObj variable. This can then be analysed // (by another function) for possible continuations between states in one or // more pieces. var out_array = []; // console.log('compObj:'); // console.log(compObj); if (compObj.timeSignatures.length > 1){ console.log("More than one time signature in this piece."); console.log("Might not be the best idea to analyse with this function."); console.log("Stopping analysis for this piece."); return out_array; } else { var D = mu$6.comp_obj2note_point_set(compObj); var segE = mu$6.segment(D, onAndOff, idxOn, idxDur); // Iterate over segE, converting the ontime of each segment to a beat // number and extracting the MIDI note numbers. for (let i = 0; i < segE.length; i++){ var bar_beat = mu$6.bar_and_beat_number_of_ontime( segE[i].ontime, compObj.timeSignatures ); // This is beat of the bar in crotchet beats rounded to 5 decimal places. var beat_round = Math.round(bar_beat[1]*100000)/100000; var MNN = new Array(segE[i].points.length); for (let j = 0; j < segE[i].points.length; j++){ MNN[j] = segE[i].points[j][idxMNN]; } // Sort the MNN_rel entries and retain only the unique members. var unqAndIdx = mu$6.unique_rows(MNN.map(function(m){ return [m] })); var unqMNN = unqAndIdx[0].map(function(arr){ return arr[0] }); // Want to switch the mapping from this [[0, 2], [1], [3]] to [0, 1, 0, 2] var mapSwitch = new Array(MNN.length); unqAndIdx[1].map(function(arr, idx){ arr.map(function(el){ mapSwitch[el] = idx; }); }); // var unqMNN = mu.get_unique( // MNN_rel.sort(function(a, b) { return a - b; }) // ); // Tuplets in scores can cause rounding errors when states are created, so // check for very small time differences between consecutive states, and // ignore a state if it is followed really soon after by another. // ********************************************************************* // 20200218. Also now getting rid of rest states here. They add noise. * // ********************************************************************* if (segE[i].offtime - segE[i].ontime >= .00002 && unqMNN.length > 0){ out_array.push({ "beat_MNN_state": [beat_round, unqMNN], "context": { "piece_id": compObj.id, "orig_points": segE[i].points, "orig_ontime": segE[i].ontime, // Useful for identifying phrase boundaries. "map_into_state": mapSwitch } }); } // else{ // console.log('A state was thrown out because of being a rest or ' + // ' followed really soon after by another.'); // console.log('segE[i]:'); // console.log(segE[i]); // console.log('segE[i + 1]:'); // console.log(segE[i + 1]); // } } return out_array; } }, comp_obj2beat_rel_mnn_states: function( compObj, onAndOff = true, idxOn = 0, idxMNN = 1, idxDur = 3 ){ // Tom Collins 11/1/2015. // This function converts a compObj variable to an array consisting of // json variables. Each json variable contains state and context information // for a segment of the input compObj variable. This can then be analysed // (by another function) for possible continuations between states in one or // more pieces. var idxMNN = 1; var out_array = []; if (compObj.timeSignatures.length > 1){ console.log("More than one time signature in this piece."); console.log("Might not be the best idea to analyse with this function."); console.log("Stopping analysis for this piece."); return out_array; } else { var D = mu$6.comp_obj2note_point_set(compObj); var segD = mu$6.segment(D, onAndOff, idxOn, idxDur); // console.log("compObj.keySignatures[0]:", compObj.keySignatures[0]) var fifth_steps = compObj.keySignatures[0].fifthSteps; var mode = compObj.keySignatures[0].mode; var trans_pair_and_c_point_set = this.centre_point_set( [fifth_steps, mode], mu$6.copy_point_set(D) ); var trans_pair = trans_pair_and_c_point_set[0]; // console.log('trans_pair:'); // console.log(trans_pair); var E = trans_pair_and_c_point_set[1]; var segE = mu$6.segment(E, onAndOff, idxOn, idxDur); // console.log('segments:'); // console.log(segE); // Iterate over segE, converting the ontime of each segment to a beat // number and extracting the relative MIDI note numbers. for (let i = 0; i < segE.length; i++){ var bar_beat = mu$6.bar_and_beat_number_of_ontime( segE[i].ontime, compObj.timeSignatures ); // This is beat of the bar in crotchet beats rounded to 5 decimal places. var beat_round = Math.round(bar_beat[1]*100000)/100000; var rel_MNN = new Array(segE[i].points.length); for (let j = 0; j < segE[i].points.length; j++){ rel_MNN[j] = segE[i].points[j][idxMNN]; } // Sort the rel_MNN entries and retain only the unique members. var unqAndIdx = mu$6.unique_rows(rel_MNN.map(function(m){ return [m] })); var unqRelMNN = unqAndIdx[0].map(function(arr){ return arr[0] }); // Want to switch the mapping from this [[0, 2], [1], [3]] to [0, 1, 0, 2] var mapSwitch = new Array(rel_MNN.length); unqAndIdx[1].map(function(arr, idx){ arr.map(function(el){ mapSwitch[el] = idx; }); }); // var unqRelMNN = mu.get_unique( // rel_MNN.sort(function(a, b) { return a - b; }) // ); // Tuplets in scores can cause rounding errors when states are created, so // check for very small time differences between consecutive states, and // ignore a state if it is followed really soon after by another. // *************************************************** // 20200218. Get rid of rest states. They add noise. * // *************************************************** if (segE[i].offtime - segE[i].ontime >= .00002 && unqRelMNN.length > 0){ out_array.push({ "beat_rel_MNN_state": [beat_round, unqRelMNN], "context": { "piece_id": compObj.id, "orig_points": segD[i].points, "orig_ontime": segD[i].ontime, // Useful for identifying phrase boundaries. "map_into_state": mapSwitch, "tonic_pitch_closest": trans_pair, "fifth_steps_mode": [fifth_steps, mode] } }); } // else { // console.log('A state was thrown out because of being followed really' + // ' soon after by another.'); // console.log('segE[i]:'); // console.log(segE[i]); // console.log('segE[i + 1]:'); // console.log(segE[i + 1]); // } } return out_array; } }, comp_obj2beat_rel_sq_mnn_states: function( compObj, onAndOff = true, squashRange = 12, idxOn = 0, idxMNN = 1, idxDur = 3 ){ // Tom Collins 26/2/2020. // This function converts a compObj variable to an array consisting of // json variables. Each json variable contains state and context information // for a segment of the input compObj variable. This can then be analysed // (by another function) for possible continuations between states in one or // more pieces. var idxMNN = 1; var out_array = []; if (compObj.timeSignatures.length > 1){ console.log("More than one time signature in this piece."); console.log("Might not be the best idea to analyse with this function."); console.log("Stopping analysis for this piece."); return out_array; } else { var D = mu$6.comp_obj2note_point_set(compObj); var segD = mu$6.segment(D, onAndOff, idxOn, idxDur); // console.log("compObj.keySignatures[0]:", compObj.keySignatures[0]) var fifth_steps = compObj.keySignatures[0].fifthSteps; var mode = compObj.keySignatures[0].mode; var trans_pair_and_c_point_set = this.centre_point_set( [fifth_steps, mode], mu$6.copy_point_set(D) ); var trans_pair = trans_pair_and_c_point_set[0]; // console.log('trans_pair:'); // console.log(trans_pair); var E = trans_pair_and_c_point_set[1]; var segE = mu$6.segment(E, onAndOff, idxOn, idxDur); // console.log('segments:'); // console.log(segE); // Iterate over segE, converting the ontime of each segment to a beat // number and extracting the relative MIDI note numbers. for (let i = 0; i < segE.length; i++){ var bar_beat = mu$6.bar_and_beat_number_of_ontime( segE[i].ontime, compObj.timeSignatures ); // This is beat of the bar in crotchet beats rounded to 5 decimal places. var beat_round = Math.round(bar_beat[1]*100000)/100000; var rel_sq_MNN = new Array(segE[i].points.length); for (let j = 0; j < segE[i].points.length; j++){ // console.log("j:", j) //************* // Squashing! * //************* let m = segE[i].points[j][idxMNN]; // console.log("m:", m) while (m > squashRange || m < -squashRange){ if (m > squashRange){ m -= squashRange; } else { m += squashRange; } } rel_sq_MNN[j] = m; } // Sort the rel_sq_MNN entries and retain only the unique members. var unqAndIdx = mu$6.unique_rows(rel_sq_MNN.map(function(m){ return [m] })); var unq_rel_sq_MNN = unqAndIdx[0].map(function(arr){ return arr[0] }); // Want to switch the mapping from this [[0, 2], [1], [3]] to [0, 1, 0, 2] var mapSwitch = new Array(rel_sq_MNN.length); unqAndIdx[1].map(function(arr, idx){ arr.map(function(el){ mapSwitch[el] = idx; }); }); // var unq_rel_sq_MNN = mu.get_unique( // unq_rel_sq_MNN.sort(function(a, b) { return a - b; }) // ); // Tuplets in scores can cause rounding errors when states are created, so // check for very small time differences between consecutive states, and // ignore a state if it is followed really soon after by another. // *************************************************** // 20200218. Get rid of rest states. They add noise. * // *************************************************** if (segE[i].offtime - segE[i].ontime >= .00002 && rel_sq_MNN.length > 0){ out_array.push({ "beat_rel_sq_MNN_state": [beat_round, unq_rel_sq_MNN], "context": { "piece_id": compObj.id, "orig_points": segD[i].points, "orig_ontime": segD[i].ontime, // Useful for identifying phrase boundaries. "map_into_state": mapSwitch, "tonic_pitch_closest": trans_pair, "fifth_steps_mode": [fifth_steps, mode] } }); } // else { // console.log('A state was thrown out because of being followed really' + // ' soon after by another.'); // console.log('segE[i]:'); // console.log(segE[i]); // console.log('segE[i + 1]:'); // console.log(segE[i + 1]); // } } return out_array; } }, lyrics_obj2lyrics_states: function(lyricsObj){ // Tom Collins 11/1/2015. // This function converts a lyricsObj variable to an array consisting of // json variables. Each json variable contains state and context information // for a segment of the input lyricsObj variable. This can then be analysed // (by another function) for possible continuations between states in one or // more pieces. var out_array = []; lyricsObj.lyricsArr.forEach(function(line, idx){ line.forEach(function(word, jdx){ let state = []; state.push(word); if (jdx < line.length - 1){ state.push(line[jdx + 1]); } else { if (idx < lyricsObj.lyricsArr.length - 1){ state.push(lyricsObj.lyricsArr[idx + 1][0]); } } if (state.length == 2){ out_array.push({ "lyrics_state": state, "context": { "piece_id": lyricsObj.id, "index_in_line": jdx } }); } }); }); return out_array }, construct_prune_write_stm: function(_comps, _param){ let anStm = this.construct_stm(_comps, _param); console.log("anStm.length:", anStm.length); anStm = this.prune_stm(anStm, _param); console.log("pruned anStm.length:", anStm.length); if (anStm.length > 0){ // console.log("anStm[0].beat_mnn_state:", anStm[0].beat_mnn_state); // console.log("anStm.slice(0, 1):", anStm.slice(0, 1)); fs$5.writeFileSync( path$1.join(_param.outPath, _param.filename + "_stm.js"), JSON.stringify(anStm)//, null, 2) ); if (_param.stmTimer){ clearTimeout(_param.stmTimer); } return anStm } // Else, return undefined. }, construct_stm: function(compObjs, param){ // Tom Collins 27/1/2015. // This function takes an array of json_score variables as input, and // constructs an array known as a state transition matrix. In reality, it is // an array not a matrix: an array of objects where each object contains a // beat_MNN_state property and a continuations property. The beat_MNN_state // property value is an array, something like // [1, [42, 60]], which means a musical event/segment that begins on beat 1 // of the bar and consists of two MIDI note numbers, 42 and 60. The // continuations property value is an array consisting of state-context // pairs: it is all the events/segments that follow on from [1, [42, 60]], // say, in the json_score variables. const stateType = param.stateType; const onAndOff = param.onAndOff; const squashRange = param.squashRangeMidi; // Could check that each of the compObjs have just one time signature, and // that they are all equal to one another... var nscr = compObjs.length; var state_context_pairs = []; for (let iscr = 0; iscr < nscr; iscr++){ switch (stateType){ case "beat_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_mnn_states( compObjs[iscr], onAndOff ); break; case "beat_rel_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_rel_mnn_states( compObjs[iscr], onAndOff ); break; case "beat_rel_sq_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_rel_sq_mnn_states( compObjs[iscr], onAndOff, squashRange ); break; case "lyrics_state": state_context_pairs[iscr] = this.lyrics_obj2lyrics_states(compObjs[iscr]); break; default: console.log("SHOULD NOT GET HERE!"); } //if (iscr == 0){ // console.log('state_context_pairs[iscr]:', state_context_pairs[iscr]); //} } var stm = []; for (let iscr = 0; iscr < nscr; iscr++){ for (let jstate = 0; jstate < state_context_pairs[iscr].length - 1; jstate++){ // console.log('Curr state:'); // console.log(state_context_pairs[iscr][jstate]["beat_MNN_state"]); var rel_idx = mu$6.array_object_index_of_array( stm, state_context_pairs[iscr][jstate][stateType], stateType ); if (rel_idx >= 0){ // The current state already appears in the stm. Push its continuation // to the array of continuations for this state. stm[rel_idx]["continuations"].push(state_context_pairs[iscr][jstate + 1]); } else { // The current state has not appeared in the stm before. Push it and // its first continuation observed here to the stm. var newObj = {}; newObj[stateType] = state_context_pairs[iscr][jstate][stateType]; newObj.continuations = [state_context_pairs[iscr][jstate + 1]]; stm.push(newObj); } } // console.log('Completed processing composition ' + iscr); } return stm; }, prune_stm: function(stm, param){ const self = this; const stateType = param.stateType; const nosConsecutives = param.nosConsecutives; // Identify dead-ends. stm.map(function(stateConts, idx){ if (idx % 500 == 0){ console.log("Pruning at index " + idx + " of " + stm.length + "."); } stateConts.deadEnd = self.prune_helper(stateConts, stm, stateType, nosConsecutives); }); // console.log("Dead-ends identified, stm.length:", stm.length) // Remove them and associated continuations. for (let i = stm.length - 1; i >= 0; i--){ if (i % 500 == 0){ console.log("Checking removal need at index " + i + " of " + stm.length + "."); } if (stm[i].deadEnd){ stm = self.prune_remover(stm[i][stateType], stm, stateType); } } // console.log("Dead-ends and associated continuations removed, stm.length:", stm.length) // Remove any states whose continuations array is now of length zero. for (let i = stm.length - 1; i >= 0; i--){ if (i % 500 == 0){ console.log("Checking continuations length at " + i + " of " + stm.length + "."); } if (stm[i].continuations.length == 0){ stm.splice(i, 1); } } // Delete deadEnd properties, since we're done with them now. stm = stm.map(function(stateConts){ delete stateConts.deadEnd; return stateConts }); return stm }, prune_helper: function(stateConts, stm, stateType, nosConsecutives){ let pruneAns = []; // Defining as an array to enforce pass by reference. this.prune_helper_2(stateConts, stm, stateType, nosConsecutives, pruneAns); // if (pruneAns.length > 1){ // console.log("NOT SURE THIS SHOULD GROW TO LENGTH 2+!") // } return pruneAns[0] }, prune_helper_2: function(stateConts, stm, stateType, nosConsecutives, pruneAns, consecCount = 0){ const self = this; // console.log("consecCount:", consecCount) if (consecCount == nosConsecutives){ // We reached the limit of how far we can go from the original state without // encountering at least one other different piece_id, so the original state // is considered a dead end for this value of nosConsecutives. pruneAns.push(true); return } const unqIds = mu$6.get_unique( stateConts["continuations"].map(function(c){ return c.context.piece_id }) ); // console.log("unqIds:", unqIds) if (unqIds.length > 1){ // At least one other different piece_id, so we're good. pruneAns.push(false); return } stateConts["continuations"].forEach(function(c){ // Keep looking for each continuation of this state. // console.log("c[stateType]:", c[stateType]) const relIdx = mu$6.array_object_index_of_array(stm, c[stateType], stateType); // console.log("relIdx:", relIdx) if (relIdx >= 0){ self.prune_helper_2(stm[relIdx], stm, stateType, nosConsecutives, pruneAns, consecCount + 1); } // If we get here, then a terminal state must have been encountered. // console.log("Terminal state encountered!") }); }, prune_remover: function(state, stm, stateType){ // Remove all occurrences of the dead-end state from continuations. const relIdx = mu$6.array_object_index_of_array(stm, state, stateType); stm = stm.map(function(sc){ sc.continuations = sc.continuations.filter(function(c){ return !c[stateType].equals(state) }); return sc }); // Remove the state itself from the stm. stm.splice(relIdx, 1); return stm }, note_point_set2comp_obj: function( ps, timeSigs = [{"barNo": 1, "topNo": 4, "bottomNo": 4, "ontime": 0}], isPerc = false, f = mu$6.farey(4), onIdx = 0, mnnIdx = 1, durIdx = 3, chanIdx = 4, velIdx = 5 ){ var comp = {}; var notes = []; // Split up MIDI notes by channel number. var unqChan = []; var psByChan = []; ps.map(function(n){ if (unqChan.indexOf(n[chanIdx]) === -1){ unqChan.push(n[chanIdx]); psByChan[n[chanIdx]] = [n]; } else { psByChan[n[chanIdx]].push(n); } return; }); // console.log("unqChan:", unqChan); // console.log("psByChan:", psByChan); var numTracks = 0; comp["layer"] = []; unqChan.map(function(track, ind){ numTracks++; comp.layer.push({ staffNo: track }); return; }); // console.log("comp.layer:", comp.layer); // console.log("numTracks:", numTracks); if (numTracks === 0){ console.log("No tracks found in this MIDI file."); return; } comp["layer"] = comp["layer"].map(function(track, layerNum){ var layer = {}; if (track == 10){ layer["idInstrument"] = "edm_drum_kit"; } else { layer["idInstrument"] = "acoustic_grand_piano"; } // layer["idInstrument"] = assign_instrument_basic(track) // This won"t work because we"re no longer importing from MIDI. // console.log("track.instrumentNumber:", track.instrumentNumber); // layer["idInstrument"] = an.assign_instrument_basic(track.instrumentNumber) layer["staffNo"] = layerNum; layer["timestampLastUsed"] = ""; layer["vexflow"] = { "name": "", "abbreviation": "", "staffOrderNo": layerNum }; // This won't work because we're no longer importing from MIDI. // layer["vexflow"] = { "name": track.name || "", "abbreviation": "", "staffOrderNo": layerNum } if (psByChan[track.staffNo] !== undefined){ // Get the track.notes and quantise them. var ps2 = psByChan[track.staffNo].filter(function(note){ // Get rid of quiet notes. return note[velIdx] > 0.05; }) // .map(function(note){ // return [ // note[0], // note[1], // note[2], // note[3], // note[4] // ]; // }) .filter(function(p){ // Gets rid of really low/high notes. return p[mnnIdx] >= 21 && p[mnnIdx] <= 108; }); // console.log("unquantised ps2.slice(0, 3):", ps2.slice(0, 3)) if (f !== null){ ps2 = mu$6.farey_quantise(ps2, f, [onIdx, durIdx]); ps2 = mu$6.unique_rows(ps2, true)[0]; } // console.log("quantised ps2.slice(0, 3):", ps2.slice(0, 3)) notes.push(...ps2.map(function(p){ var compNote = mu$6.timelapse_object(); // var compNote = {} // compNote["id"] = uu() // but it has implications in terms of file size. compNote["ontime"] = p[onIdx]; if (p[durIdx] > 8){ compNote["duration"] = 8; // console.log("Long duration corrected.") } else { compNote["duration"] = p[durIdx]; } compNote["offtime"] = compNote.ontime + compNote.duration; var barBeat = mu$6.bar_and_beat_number_of_ontime(compNote.ontime, timeSigs); compNote["barOn"] = barBeat[0]; compNote["beatOn"] = barBeat[1]; barBeat = mu$6.bar_and_beat_number_of_ontime(compNote.offtime, timeSigs); compNote["barOff"] = barBeat[0]; compNote["beatOff"] = barBeat[1]; // compNote["pitch"] = note.name compNote["MNN"] = p[mnnIdx]; // compNote["MPN"] = 0 compNote["staffNo"] = p[chanIdx]; compNote["tonejs"] = { "volume": p[velIdx] // Math.round(100*p[velIdx]/127)/100 }; compNote["voiceNo"] = 0; // compNote["isPerc"] = true return compNote })); // console.log("notes.slice(0, 10):", notes.slice(0, 10)); } return layer }); // console.log("notes.length:", notes.length); var keySig; if (!isPerc){ keySig = mu$6.fifth_steps_mode(ps, mu$6.krumhansl_and_kessler_key_profiles); } else { keySig = ["C major", 1, 0, 0]; } comp["keySignatures"] = [{ "barNo": 1, "keyName": keySig[0], "fifthSteps": keySig[2], "mode": keySig[3], "ontime": 0 }]; // console.log("keySig:", keySig); comp["timeSignatures"] = timeSigs; // Guess note names. notes.forEach(function (note) { note["MPN"] = mu$6.guess_morphetic(note.MNN, keySig[2], keySig[3]); note["pitch"] = mu$6.midi_note_morphetic_pair2pitch_and_octave(note.MNN, note.MPN); }); comp["notes"] = notes.sort(mu$6.sort_points_asc); // comp["sequencing"] = [{"ontime": 0, "offtime": 16, "repetitionNo": 1}] comp["tempi"] = [{"barNo": 1, "ontime": 0, "bpm": 120, "tempo": ""}]; // comp["tempi"] = [{"barNo": 1, "ontime": 0, "bpm": midi.header.bpm, "tempo": ""}] return comp; }, centre_point_set:function ( fifth_steps_mode, point_set, idxMNN = 1, idxMPN = 2 ){ // Tom Collins 26/1/2015. // Translates the point set so that the tonic pitch class closest to the mean // MIDI note number is represented by the pair [0, 0]. var MNN_MPN_pair = this.fifth_steps_mode2MNN_MPN(fifth_steps_mode); // console.log("MNN_MPN_pair:", MNN_MPN_pair) // Find the MIDI note number of the tonic pitch class closest to the mean // MIDI note number of the piece. Start by finding the mean MNN. var MNNs = []; for (i = 0; i < point_set.length; i++){ MNNs.push(point_set[i][1]); } var MNN_mu = mu$6.mean(MNNs); // console.log('Mean MNN:'); // console.log(MNN_mu); // Get the MNNs for all the tonic pitch classes. var MNN_tonal=[]; for(var i = MNN_MPN_pair[0] - 60; i <= MNN_MPN_pair[0] + 72; i = i + 12){ MNN_tonal[MNN_tonal.length] = i; } // Now find the MNN of the tonic pitch class closest to the mean MNN. var dist = []; var n_tonal = MNN_tonal.length; for (i = 0; i < n_tonal; i++){ dist[i] = Math.abs(MNN_tonal[i] - MNN_mu); } var min_stuff = mu$6.min_argmin(dist); var trans_pair = [ MNN_MPN_pair[0] + 12*(min_stuff[1] - 5), MNN_MPN_pair[1] + 7*(min_stuff[1] - 5) ]; for (i = 0; i < point_set.length; i++){ var new_MNN = point_set[i][idxMNN] - trans_pair[0]; var new_MPN = point_set[i][idxMPN] - trans_pair[1]; point_set[i].splice(idxMNN, 1, new_MNN); point_set[i].splice(idxMPN, 1, new_MPN); } return [trans_pair, point_set]; }, construct_prune_write_initial: function(_comps, _stm, _param){ let initialDistbn = this.construct_initial(_comps, _param); initialDistbn = this.prune_initial(initialDistbn, _stm, _param); if (initialDistbn.length > 0){ fs$5.writeFileSync( path$1.join(_param.outPath, _param.filename + "_initial.js"), JSON.stringify(initialDistbn)//, null, 2) ); if (_param.initialTimer){ clearTimeout(_param.initialTimer); } return initialDistbn } // Else, return undefined. }, construct_initial: function(compObjs, param){ const stateType = param.stateType; const onAndOff = param.onAndOff; const squashRange = param.squashRangeMidi; const phraseBoundaryPropName = param.phraseBoundaryPropName; // Could check that each of the compObjs have just one time signature, and // that they are all equal to one another... var nscr = compObjs.length; var state_context_pairs = []; for (let iscr = 0; iscr < nscr; iscr++){ switch (stateType){ case "beat_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_mnn_states( compObjs[iscr], onAndOff ); break; case "beat_rel_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_rel_mnn_states( compObjs[iscr], onAndOff ); break; case "beat_rel_sq_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_rel_sq_mnn_states( compObjs[iscr], onAndOff, squashRange ); break; case "lyrics_state": state_context_pairs[iscr] = this.lyrics_obj2lyrics_states(compObjs[iscr]); break; default: console.log("SHOULD NOT GET HERE!"); } //if (iscr == 0){ // console.log('state_context_pairs[iscr]:', state_context_pairs[iscr]); //} } var initial = []; if (stateType == "lyrics_state"){ for (let iscr = 0; iscr < nscr; iscr++){ for (let jstate = 0; jstate < state_context_pairs[iscr].length - 1; jstate++){ // console.log('Curr state:'); // console.log(state_context_pairs[iscr][jstate]["beat_MNN_state"]); let scPair = state_context_pairs[iscr][jstate]; if (scPair.context.index_in_line == 0){ initial.push(scPair); } } } } else { for (let iscr = 0; iscr < nscr; iscr++){ for (let jstate = 0; jstate < state_context_pairs[iscr].length - 1; jstate++){ // console.log('Curr state:'); // console.log(state_context_pairs[iscr][jstate]["beat_MNN_state"]); let scPair = state_context_pairs[iscr][jstate]; if (phraseBoundaryPropName){ // Is there a phrase boundary ontime sufficiently close to the // ontime of this segment? if ( compObjs[iscr][phraseBoundaryPropName].find(function(o){ return Math.abs(o - scPair["context"]["orig_ontime"]) < .00002 }) ){ // Yes initial.push(scPair); } } else { if (scPair[stateType][0] == 1){ initial.push(scPair); } } } } } return initial; }, construct_scl: function(compObjs, param){ const stateType = param.stateType; const onAndOff = param.onAndOff; const squashRange = param.squashRangeMidi; param.phraseBoundaryPropName; // Could check that each of the compObjs have just one time signature, and // that they are all equal to one another... const nscr = compObjs.length; const state_context_pairs = []; for (let iscr = 0; iscr < nscr; iscr++){ switch (stateType){ case "beat_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_mnn_states( compObjs[iscr], onAndOff ); break; case "beat_rel_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_rel_mnn_states( compObjs[iscr], onAndOff ); break; case "beat_rel_sq_MNN_state": state_context_pairs[iscr] = this.comp_obj2beat_rel_sq_mnn_states( compObjs[iscr], onAndOff, squashRange ); break; case "lyrics_state": state_context_pairs[iscr] = this.lyrics_obj2lyrics_states(compObjs[iscr]); break; default: console.log("SHOULD NOT GET HERE!"); } //if (iscr == 0){ // console.log('state_context_pairs[iscr]:', state_context_pairs[iscr]); //} } const scl = {}; for (let iscr = 0; iscr < nscr; iscr++){ for (let jstate = 0; jstate < state_context_pairs[iscr].length - 1; jstate++){ const scPair = state_context_pairs[iscr][jstate]; let key; if (stateType == "lyrics_state"){ // State is already a string. key = scPair[stateType]; } else { // State is not a string, but we can make it so. key = this.state2string(scPair[stateType]); } if (scl[key] !== undefined){ scl[key].push(scPair["context"]); } else { scl[key] = [scPair["context"]]; } } } return scl }, prune_initial: function(initialDistbn, stm, param){ const stateType = param.stateType; return initialDistbn.filter(function(scPair){ return mu$6.array_object_index_of_array( stm, scPair[stateType], stateType ) >= 0 }) }, fifth_steps_mode2MNN_MPN: function(fifth_steps_mode){ // Tom Collins 26/1/2015. // A pair consisting of position on the circle of fifths and mode (0 for // Ionian, 1 for Dorian, etc.) is converted to a pair consisting of a MIDI // note number and morphetic pitch number for the tonic. var conversion = [// Major keys (Ionian modes). [[0, 0], [60, 60]], [[1, 0], [67, 64]], [[2, 0], [62, 61]], [[3, 0], [69, 65]], [[4, 0], [64, 62]], [[5, 0], [71, 66]], [[6, 0], [66, 63]], [[7, 0], [61, 60]], [[8, 0], [68, 64]], [[9, 0], [63, 61]], [[10, 0], [70, 65]], [[-1, 0], [65, 63]], [[-2, 0], [70, 66]], [[-3, 0], [63, 62]], [[-4, 0], [68, 65]], [[-5, 0], [61, 61]], [[-6, 0], [66, 64]], [[-7, 0], [71, 67]], [[-8, 0], [64, 63]], // Minor keys (Aeolian modes). [[0, 5], [63, 62]], [[1, 5], [70, 66]], [[2, 5], [65, 63]], [[3, 5], [60, 60]], [[4, 5], [67, 64]], [[5, 5], [62, 61]], [[6, 5], [69, 65]], [[7, 5], [64, 62]], [[8, 5], [71, 66]], [[9, 5], [66, 63]], [[-1, 5], [68, 65]], [[-2, 5], [61, 61]], [[-3, 5], [66, 64]], [[-4, 5], [71, 66]], [[-5, 5], [64, 62]], [[-6, 5], [69, 65]] ]; var i = 0; while (i < conversion.length){ if ( conversion[i][0][0] == fifth_steps_mode[0] && conversion[i][0][1] == fifth_steps_mode[1] ){ var MNN_MPN_pair = conversion[i][1]; i = conversion.length; } else { i++; } } return MNN_MPN_pair; }, string2lyrics: function(str){ // Retain characters that are alphanumeric, a space, or a line break. let lines = str.split("\n") // Get rid of multiple line breaks. .filter(function(line){ return line.length > 0 }) .map(function(line){ let words = line.replace(/[^a-z0-9\ ]/gi, "").toLowerCase(); return words.split("\ ") }); // console.log("lines:", lines) return lines }, state2string: function(state){ // Tom Collins 26/5/2020. // Assumption here is that the state will be of the form // [1, [ -7, -3, 0, 12 ]] // and the output will be of the form // "1|-7,-3,0,12" return state[0] + "|" + state[1] // state[1].map(function(m, idx){ // if (idx == state[1].length - 1){ // return m // } // return m + "," // }) }, string2state: function(str){ // Tom Collins 26/5/2020. // Assumption here is that the string will be of the form // "1|-7,-3,0,12" // and the output will be of the form // [1, [ -7, -3, 0, 12 ]] let split = str.split("|"); let arrStr = split[1].split(","); return [ parseFloat(split[0]), arrStr.map(function(num){ return parseInt(num) }) ] } }; // Imports const mu$5 = require('maia-util'); // import 'maia-util' // import mu from 'maia-util' // Constructor for Generator object function Generator$1(){ // Workaround for JS context peculiarities. // var self = this; // Possible to return something. // return sth; } // Methods for Generator object Generator$1.prototype = { constructor: Generator$1, // Tom Collins 6/4/2016. // Defining a modulo function because by default the modulus of a negative // number in javascript is negative. mod: function(a, n){ return a - (n*Math.floor(a/n)) }, get_lyrics_from_states: function(stateContextPairs, param){ const stateType = param.stateType; // Make a fresh copy because I was getting some weird-ass problems with idx. let scp = JSON.parse(JSON.stringify(stateContextPairs)); // console.log("scp:", scp) // Unpack states into a string. console.log("scp[0]:", scp[0]); let lyrics = ""; scp.forEach(function(s){ lyrics += s[stateType][0] + " "; }); lyrics += scp[scp.length - 1][stateType][1]; // lyrics = lyrics.slice(0, -1) // Too clever, doesn't work! // const lyrics = scp.reduce(function(total, s){ // return total + " " + s[stateType][0] // }) // console.log("lyrics", lyrics) return lyrics }, // Tom Collins 6/4/2016. // This function converts beat-relative-MNN states into points consisting of // ontimes, MNNs, MPNs, durations, and staff numbers. get_points_from_states: function(stateContextPairs, param){ const self = this; const stateType = param.stateType; param.pointReconstruction; const currTimeSig = param.timeSignatures[0]; const crotchetBeatsInBar = 4*currTimeSig.topNo/currTimeSig.bottomNo; param.indices.ontime; const idxMNN = param.indices.MNN; const idxMPN = param.indices.MPN; param.indices.duration; const idxChan = param.indices.channel; const idxVel = param.indices.velocity; // stateContextPairs, stateType = "beat_rel_sq_MNN_state", // currentTimeSignature = { "topNo": 4, "bottomNo": 4 } // const idxOn = 0, idxMNN = 1, idxMPN = 2, idxDur = 3, idxChan = 4, idxVel = 5 // var crotchetBeatsInBar = 4*currentTimeSignature.topNo/currentTimeSignature.bottomNo; // Make a fresh copy because I was getting some weird-ass problems with idx. let scp = JSON.parse(JSON.stringify(stateContextPairs)); // console.log("scp:", scp) // Unpack states into MNNs and MPNs. scp.forEach(function(s){ let MNNs = []; let MPNs = []; s.context.orig_points.forEach(function(p){ const mnnMpnPair = self.state_representation_of_pitch( [p[idxMNN], p[idxMPN]], param, s.context.tonic_pitch_closest ); MNNs.push(mnnMpnPair[0]); MPNs.push(mnnMpnPair[1]); }); s.MNNs = MNNs; s.MPNs = MPNs; }); // Get the ontimes for each state. let ons = self.state_ontimes(scp, stateType, crotchetBeatsInBar); scp.map(function(s, idx){ s.ontime = ons[idx]; }); // Dovetail durations. scp = self.dovetail_durations(scp, param); // console.log("scp:", scp) // Define points. let points = []; scp.map(function(s){ s.MNNs.map(function(m, idx){ if (!s.dovetailed[idx]){ points.push([ s.ontime, m, s.MPNs[idx], s.durations[idx], s.context.orig_points[idx][idxChan], s.context.orig_points[idx][idxVel] ]); } }); }); return points.sort(mu$5.lex_more) }, dovetail_durations: function(stateContextPairs, param){ const stateType = param.stateType; const idxOn = param.indices.ontime; param.indices.MNN; param.indices.MPN; const idxDur = param.indices.duration; param.indices.channel; param.indices.velocity; // Get a last offtime. // This is the ontime at which the final selected state began in the original // piece. const ontimeOfLastState = mu$5.max_argmax( stateContextPairs[stateContextPairs.length - 1].context.orig_points.map(function(p){ return p[idxOn] }) )[0]; // This is the maximum offtime of a note in that state. const offtimeOfLastState = mu$5.max_argmax( stateContextPairs[stateContextPairs.length - 1].context.orig_points.map(function(p){ return p[idxOn] + p[idxDur] }) )[0]; // The difference between these two, // offtimeOfLastState - ontimeOfLastState, // will give us an acceptable value for an offtime for the final selected // state in the new context, when added to the ontime for the final selected // state. const lastOfftime = stateContextPairs[stateContextPairs.length - 1].ontime + offtimeOfLastState - ontimeOfLastState; // console.log("stateContextPairs[stateContextPairs.length - 1].ontime:", stateContextPairs[stateContextPairs.length - 1].ontime) // console.log("ontimeOfLastState:", ontimeOfLastState) // console.log("offtimeOfLastState:", offtimeOfLastState) // console.log("lastOfftime:", lastOfftime) // Dovetailing // This involves going through each original point and seeing whether it // lasted longer than the state to which it belongs. There are three cases to // consider: // (A) It does not. We assign a duration based on how long it does last in the // state and we're done; // (B) It does but the same pitch does not appear in the following selected // state. We assign a duration up until where the next selected state begins // and we're done; // (C) It does and the same pitch appears in the following selected state. We // carry on the search with this note in the following selected state now, and // ask the same questions above (while-loop), until we find (A) or (B) be // true. This is the most complex scenario, is tracked using an array called // dovetail, and can lead to a pitch being tied across multiple selected // states when being turned into a note. // Set up durations and dovetailed. stateContextPairs.map(function(s){ s.durations = new Array(s.context.orig_points.length); s.dovetailed = new Array(s.context.orig_points.length); s.context.orig_points.map(function(p, idx){ s.durations[idx] = 0; s.dovetailed[idx] = false; }); }); stateContextPairs.map(function(s, idx){ // console.log("s['beat_MNN_state']:", s['beat_MNN_state']) // Ontime where state began in original context. const ontimeOfState = mu$5.max_argmax( s.context.orig_points.map(function(p){ return p[idxOn] }) )[0]; // console.log("ontimeOfState:", ontimeOfState) // Durations left in state // console.log("orig_points:", s.context.orig_points) const dlis = s.context.orig_points.map(function(p){ return p[idxOn] + p[idxDur] - ontimeOfState }); // console.log("dlis:", dlis) // These are the durations we will assign to each note (is parallel with // the MNNs and MPNs properties, which should be present). s.context.orig_points.map(function(p, kdx){ // Stands for map into index. const mii = s.context.map_into_state[kdx]; // const m = p[idxMNN] // console.log("m:", m) // Have a look in the next state. let jdx = idx + 1; while (jdx <= stateContextPairs.length){ let compareOntime, lins; // Need to be careful about end case, where jdx == stateContextPairs.length if (jdx < stateContextPairs.length){ // Where this next state begins in new context. // console.log("Regular case. jdx = " + jdx + ", stateContextPairs[jdx]:", stateContextPairs[jdx]) compareOntime = stateContextPairs[jdx].ontime; // Stands for "look in next state". Is this "pitch" present? //*********************************************** // 26.02.2020. FIXED FOR DIFFERENT STATE TYPES! * //*********************************************** lins = stateContextPairs[jdx][stateType][1] .indexOf(s[stateType][1][mii]); } else { // End case. // console.log("End case") compareOntime = lastOfftime; // Use value calculated above. lins = -1; // Nothing to look for. } // console.log("compareOntime:", compareOntime, "lins:", lins) if (dlis[kdx] <= compareOntime - stateContextPairs[jdx - 1].ontime){ // Case (A) // console.log("Case (A)") s.durations[kdx] += dlis[kdx]; jdx = stateContextPairs.length; } else if (lins == -1){ // Case (B) // console.log("Case (B)") s.durations[kdx] += compareOntime - stateContextPairs[jdx - 1].ontime; jdx = stateContextPairs.length; } else { // Case (C) // console.log("Case (C)") s.durations[kdx] += compareOntime - stateContextPairs[jdx - 1].ontime; stateContextPairs[jdx].dovetailed[lins] = true; // s.dovetailed[kdx] = true } jdx++; } }); // console.log("s.durations:", s.durations, "s.dovetailed:", s.dovetailed) }); return stateContextPairs }, state_ontimes: function( stateContextPairs, stateType = "beat_rel_sq_MNN_state", crotchetBeatsInBar = 4 ){ let interStateDurations = stateContextPairs.map(function(s, idx){ if (idx > 0){ let d = s[stateType][0] - stateContextPairs[idx - 1][stateType][0]; if (d < 0){ d = mu$5.mod(d, crotchetBeatsInBar); } else if (d == 0){ d = crotchetBeatsInBar; } return d } }); // console.log("interStateDurations:", interStateDurations) interStateDurations = interStateDurations.slice(1); const ontimes = new Array(stateContextPairs.length); ontimes[0] = stateContextPairs[0][stateType][0] - 1; interStateDurations.map(function(isd, idx){ ontimes[idx + 1] = ontimes[idx] + isd; }); // console.log("ontimes:", ontimes) return ontimes }, state_representation_of_pitch: function(midiMorphPair, param, tpc){ const pointReconstruction = param.pointReconstruction; const squashRange = param.squashRangeMidiMorph; let mnn = midiMorphPair[0], mpn = midiMorphPair[1]; switch (pointReconstruction){ case "rel_sq_MNN": // Remove tonic pitch closest. mnn -= tpc[0]; mpn -= tpc[1]; // Squash. while (mnn > squashRange[0] || mnn < -squashRange[0]){ if (mnn > squashRange[0]){ mnn -= squashRange[0]; mpn -= squashRange[1]; } else { mnn += squashRange[0]; mpn += squashRange[1]; } } break case "rel_MNN": // Remove tonic pitch closest. mnn -= tpc[0]; mpn -= tpc[1]; break case "MNN": // No manipulation required. break default: console.log("SHOULD NOT GET HERE!"); } return [mnn, mpn