File size: 3,569 Bytes
d605f27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

// eslint-disable-next-line
import {MusicNotation} from "@k-l-lambda/music-widgets";



interface Note {
	index: number;
	pitch: number;
	softIndex: number;
	baseCsi?: number;
};


const notePitchDistance = (n1: Note, n2: Note): number => {
	let differ = Math.abs(n1.pitch - n2.pitch);
	if (differ > 7)
		differ -= 11.5;

	return differ * 2;
};


const fuzzyMatchNotes = (path: number[], cnotes: Note[], snotes: Note[], pitchTolerance: number, offsetTolerance = 2): {fixed: number, matched: number} => {
	const candidates: {[key: number]: {si: number, distance: number}} = {};

	const matched = snotes.reduce((count, snote) => {
		const pcan = cnotes.filter(cnote => notePitchDistance(cnote, snote) <= pitchTolerance
			&& Math.abs(snote.baseCsi - cnote.softIndex) < offsetTolerance)
			.sort((n1, n2) => Math.abs(snote.baseCsi - n1.softIndex) - Math.abs(snote.baseCsi - n2.softIndex));

		if (pcan.length) {
			const bestDistance = notePitchDistance(pcan[0], snote);
			const last = candidates[pcan[0].index];
			if (!last || bestDistance < last.distance)
				candidates[pcan[0].index] = {si: snote.index, distance: bestDistance};

			++count;
		}

		return count;
	}, 0);

	const fixed = Object.entries(candidates).map(([ci, {si}]) => {
		path[si] = Number(ci);

		return [si, Number(ci)];
	});
	//console.debug("fuzzyMatch.fixed:", fixed.map(pair => pair.join()));

	return {fixed: fixed.length, matched};
};


const fuzzyMatchNotations = (path: number[], criterion: MusicNotation.NotationData, sample: MusicNotation.NotationData, {pitchToleranceMax = 4} = {}) => {
	// unmatch overlapped indices
	const overlapped = new Set();
	path.reduce((set, ci) => {
		if (ci >= 0) {
			if (set.has(ci))
				overlapped.add(ci);
			else
				set.add(ci);
		}

		return set;
	}, new Set());
	//console.debug("overlapped:", overlapped, [...path]);
	path.forEach((ci, si) => {
		if (overlapped.has(ci))
			path[si] = -1;
	});

	// assign base offset on sample notes
	let offset = null;
	for (let i = 0; i < sample.notes.length; ++i) {
		const note = sample.notes[i];
		const ci = path[i];

		if (ci < 0)
			(note as any).baseOffset = offset;
		else
			offset = note.softIndex - criterion.notes[ci].softIndex;
	}
	for (let i = sample.notes.length - 1; i >= 0; --i) {
		const note = sample.notes[i];
		const ci = path[i];

		if (ci < 0) {
			const lastOffset = (note as any).baseOffset;
			(note as any).baseOffset = Number.isFinite(lastOffset) ? (lastOffset + offset) / 2 : offset;
		}
		else
			offset = note.softIndex - criterion.notes[ci].softIndex;
	}

	let pitchTolerance = 0;
	while (true) {
		const cnotes = criterion.notes.filter(note => !path.some(ci => ci === note.index)).map(note => ({index: note.index, pitch: note.pitch, softIndex: note.softIndex}));
		const snotes = sample.notes.filter(note => path[note.index] < 0 && Number.isFinite((note as any).baseOffset))
			.map(note => ({index: note.index, pitch: note.pitch, softIndex: note.softIndex, baseCsi: note.softIndex - (note as any).baseOffset}));

		//console.debug("fuzzyMatch.notes:", cnotes.map(note => note.index), snotes.map(note => note.index));

		if (!cnotes.length || !snotes.length)
			break;

		const {fixed, matched} = fuzzyMatchNotes(path, cnotes, snotes, pitchTolerance);
		if (matched)
			console.debug("fuzzyMatch.pass:", `c:${cnotes.length}, s:${snotes.length},`, pitchTolerance, `${fixed}/${matched}`);

		if (fixed >= matched) {
			++pitchTolerance;
			if (pitchTolerance > pitchToleranceMax)
				break;
		}
	}
	//console.debug("fuzzyMatch.path:", path);
};



export {
	fuzzyMatchNotations,
};