maia-markov
Version:
Markov analysis and generation functions supporting various applications by Music Artificial Intelligence Algorithms, Inc.
266 lines (248 loc) • 8.63 kB
JavaScript
// Copyright Tom Collins, 20.8.2020
// Requires.
const path = require("path")
const fs = require("fs")
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": __dirname + "/out/strq2020_triple/",
// "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, "original"),
"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 windowOverlapSizes = [
{ "winSize": 16, "overlap": 8 },
// { "winSize": 8, "overlap": 4 },
// { "winSize": 4, "overlap": 2 }
]
// 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(".")[0] == "2327"
})
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()
// Load the test items.
let tiFiles = fs.readdirSync(mainPath["testItems"])
tiFiles = tiFiles.filter(function(tiFile){
return tiFile.split(".")[1] === "mid" || tiFile.split(".")[1] === "midi"
// && pFile.split(".")[0] == "2327"
})
console.log("tiFiles.length:", tiFiles.length)
tiFiles.forEach(function(tiFile, tiFIdx){
console.log("Working on " + tiFile.split(".")[0] + ", test item " + (tiFIdx + 1) + " of " + tiFiles.length + ".")
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))
// Compute similarities to the comparison set.
windowOverlapSizes.forEach(function (wo) {
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 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,
"maxSimilarity": null,
"maxPieceId": null,
"maxPoints": null
}
let tiPointsSegment = mu.points_belonging_to_interval(tiPoints, ontimeInGen, ontimeInGen + win)
// console.log("tiPointsSegment:", tiPointsSegment)
// Calculate the similarities.
let src = segs.map(function (seg) {
return seg.pieceId
})
let cardScores = segs.map(function (seg) {
let cs = 0
if (seg.points.length > 0 && tiPointsSegment.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(
tiPointsSegment.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]]
maxSimilarities.push(obj)
ontimeInGen += overlap
}
console.log("maxSimilarities:", maxSimilarities)
console.log(
"mu.mean():",
mu.mean(maxSimilarities.map(function(ms){ return ms.maxSimilarity }))
)
console.log(
"mu.count_rows():",
mu.count_rows(maxSimilarities.map(function(ms){ return [ms.maxPieceId] }))
)
// Plot it.
let data = [{
"x": genSegmentOntimes,
"y": maxSimilarities.map(function (ms) {
return ms.maxSimilarity
}),
"type": "line",
}]
const layout = {
"yaxis": {"range": [0, 1]},
"title": {"text": tiFile}
}
// console.log("data[0]:", data[0])
plotlib.stack(data, layout)
}) // windowOverlapSizes.forEach()
}) // tiFiles.forEach(
plotlib.plot()