UNPKG

maia-markov

Version:

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

264 lines (250 loc) 9 kB
// Copyright Tom Collins, 20.8.2020 // Requires. const path = require("path") const fs = require("fs") const sr = require('seed-random') const uu = require("uuid/v4") const { Midi } = require('@tonejs/midi') const mu = require("maia-util") const an = require("./analyze") const plotlib = require('nodeplotlib') // Parameters // Individual user paths const mainPaths = { "tom": { "midi": "/Users/tomthecollins/Shizz/York/Students/PhD/Alex\ \(Zongyu\)\ Yin/listening\ study/string\ quartets/data/", "midiDirs": [ "midi" ], "testItems": "/Users/tomthecollins/Shizz/York/Students/PhD/Alex\ \(Zongyu\)\ Yin/listening\ study/string\ quartets/data/midi/", // "testItem": "/Users/tomthecollins/Shizz/York/Students/PhD/Alex\ \(Zongyu\)\ Yin/listening\ study/new_magenta_quartet_examples/20200820-002703.mid", "outputDir": __dirname + "/originality_reports/", // "outputFileName": "um" }, "alex": { "midi": path.join(__dirname, "candidates"), "midiDirs": [ "midi" ], "testItems": path.join(__dirname, "candidates", "midi"), "outputDir": path.join(__dirname, "originality_reports"), // "outputFileName": "um" }, "anotherUser": { // ... } } // const targetChannel = 0 // Analyzing all channels here. const holdoutIds = ["1211", "1219", "1240", "1827", "1893", "2322", "2368"] const windowOverlapSizes = [ { "winSize": 16, "overlap": 8 }, // { "winSize": 8, "overlap": 4 }, // { "winSize": 4, "overlap": 2 } ] const sampleSize = 50 sr("this is the seed!", {global: true}) // Grab user name from command line to set path to data. let nextU = false let mainPath process.argv.forEach(function(arg, ind){ if (arg === "-u"){ nextU = true } else if (nextU){ mainPath = mainPaths[arg] nextU = false } }) // fs.mkdir(outdir) // Import and the MIDI files that act as the comparison set. let pointSets = [] let midiDirs = fs.readdirSync(mainPath["midi"]) console.log("midiDirs:", midiDirs) midiDirs = midiDirs.filter(function(midiDir){ return mainPath["midiDirs"].indexOf(midiDir) >= 0 }) console.log("midiDirs:", midiDirs) midiDirs.forEach(function(midiDir, jDir){ console.log("Working on midiDir:", midiDir, "jDir:", jDir) let pFiles = fs.readdirSync(path.join(mainPath["midi"], midiDir)) pFiles = pFiles.filter(function(pFile){ return (pFile.split(".")[1] === "mid" || pFile.split(".")[1] === "midi") && holdoutIds.indexOf(pFile.split(".")[0]) === -1 }) console.log("pFiles.length:", pFiles.length) pFiles.forEach(function(pFile, iFile){ console.log("pFile:", pFile) if (iFile % 10 == 0){ console.log("!!! PFILE " + (iFile + 1) + " OF " + pFiles.length + " !!!") } try { const midiData = fs.readFileSync(path.join(mainPath["midi"], midiDir, pFile)) const midi = new Midi(midiData) const timeSigs = [midi.header.timeSignatures.map(function(ts){ return { "barNo": ts.measures + 1, "topNo": ts.timeSignature[0], "bottomNo": ts.timeSignature[1], "ontime": ts.ticks / midi.header.ppq } })[0]] // SUPER HACKY. REVISE LATER! console.log("timeSigs:", timeSigs) let points = [] midi.tracks.forEach(function(track){ // if (track.channel == targetChannel){ track.notes.forEach(function(n){ points.push([ n.ticks / midi.header.ppq, n.midi, n.durationTicks / midi.header.ppq, track.channel, Math.round(1000 * n.velocity) / 1000 ]) }) // } }) points = mu.sort_rows(points)[0] // console.log("points.slice(0, 50):", points.slice(0, 50)) const fsm = mu.fifth_steps_mode(points, mu.krumhansl_and_kessler_key_profiles) console.log("fsm:", fsm) points.forEach(function(p){ p.splice(2, 0, mu.guess_morphetic(p[1], fsm[2], fsm[3])) }) // let comp = an.note_point_set2comp_obj( // points, timeSigs, false, [0, 1/4, 1/2, 3/4, 1]//, [0, 1/6, 1/4, 1/3, 1/2, 2/3, 3/4, 5/6, 1] // ) // console.log("comp:", comp) // Strip off file extension. pFile = pFile.split(".")[0] // comp["id"] = pFile // comp["id"] = uu() // comp["idGmd"] = pFile // comp["name"] = pFile // comp["name"] = midi.header.name || mFile.split(".")[0] // "_new" // comp["composers"] = [{"id": "default_composer", "name": "none", "displayName": "None"}] pointSets.push({"id": pFile, "points": points}) } catch (e){ console.log(e) } }) }) // midiDirs.forEach() console.log("pointSets.length:", pointSets.length) // Load the test items. let tiPointSets = [] let tiFiles = fs.readdirSync(mainPath["testItems"]) tiFiles = tiFiles.filter(function(tiFile){ return (tiFile.split(".")[1] === "mid" || tiFile.split(".")[1] === "midi") && holdoutIds.indexOf(tiFile.split(".")[0]) >= 0 }) tiFiles.forEach(function(tiFile, tiFIdx){ const tiMidiData = fs.readFileSync(path.join(mainPath["testItems"], tiFile)) const tiMidi = new Midi(tiMidiData) let tiPoints = [] tiMidi.tracks.forEach(function(track){ // if (track.channel == targetChannel){ track.notes.forEach(function(n){ tiPoints.push([ n.ticks / tiMidi.header.ppq, n.midi, n.durationTicks / tiMidi.header.ppq, track.channel, Math.round(1000 * n.velocity) / 1000 ]) }) }) tiPoints = mu.sort_rows(tiPoints)[0] // console.log("tiPoints.slice(0, 50):", tiPoints.slice(0, 50)) const tiFsm = mu.fifth_steps_mode(tiPoints, mu.krumhansl_and_kessler_key_profiles) // console.log("tiFsm:", tiFsm) tiPoints.forEach(function(p){ p.splice(2, 0, mu.guess_morphetic(p[1], tiFsm[2], tiFsm[3])) }) // console.log("tiPoints.slice(0, 5):", tiPoints.slice(0, 5)) tiPointSets.push(tiPoints) }) // tiFiles.forEach( console.log("tiPointSets.length:", tiPointSets.length) windowOverlapSizes.forEach(function(wo){ // Form the segments that will go into the cardinality score calculations. let segs = [] // pointSets.slice(0, 5).forEach(function(c){ pointSets.forEach(function(c){ const points = c.points // const points = mu.comp_obj2note_point_set(c) let ontimeInSrc = 0 let win = wo.winSize let overlap = wo.overlap let lastOntime = points[points.length - 1][0] while (ontimeInSrc < lastOntime){ let obj = { "ontimeInSrc": ontimeInSrc, "points": mu.points_belonging_to_interval(points, ontimeInSrc, ontimeInSrc + win), "pieceId": c.id } segs.push(obj) ontimeInSrc += overlap } }) console.log("segs.length:", segs.length) let src = segs.map(function(seg){ return seg.pieceId }) // Form the segments that will go into the cardinality score calculations. let allMaxSimilarities = [] tiFiles.forEach(function(tiFile, tiFIdx){ let tiPoints = tiPointSets[tiFIdx] let ontimeInGen = 0 // let genSegmentOntimes = [] let win = wo.winSize let overlap = wo.overlap let lastOntime = tiPoints[tiPoints.length - 1][0] // let maxSimilarities = [] while (ontimeInGen < lastOntime){ // console.log("ontimeInGen:", ontimeInGen) // console.log("lastOntime:", lastOntime) // genSegmentOntimes.push(ontimeInGen) let obj = { "ontimeInGen": ontimeInGen, "idGen": tiFile.split(".")[0], "points": null, "maxSimilarity": null, "maxPieceId": null, "maxPoints": null } obj.points = mu.points_belonging_to_interval(tiPoints, ontimeInGen, ontimeInGen + win) // console.log("tiPointsSegment:", tiPointsSegment) allMaxSimilarities.push(obj) ontimeInGen += overlap } }) // Sample from allMaxSimilarities without replacement. let sample = mu.sample_without_replacement(allMaxSimilarities, sampleSize) // console.log("sample:", sample) // Compute similarities. sample.forEach(function(obj, idx){ console.log("Working on sample item " + (idx + 1) + " of " + sample.length + ".") let cardScores = segs.map(function(seg){ let cs = 0 if (seg.points.length > 0 && obj.points.length > 0){ const a = mu.unique_rows( seg.points.map(function(p){ return [Math.round(24 * p[0]), p[2]] }) )[0] const b = mu.unique_rows( obj.points.map(function(p){ return [Math.round(24 * p[0]), p[2]] }) )[0] cs = mu.cardinality_score(a, b) } return cs[0] }) const ma = mu.max_argmax(cardScores) obj.maxSimilarity = ma[0] obj.maxPieceId = src[ma[1]] obj.maxPoints = segs[ma[1]] }) console.log("sample:", sample) const observations = sample.map(function(s){ return s.maxSimilarity }) console.log("observations:", observations) console.log("mu.mean(observations):", mu.mean(observations)) console.log("mu.std(observations):", mu.std(observations)) }) // windowOverlapSizes.forEach()