console
function toRhythm() {
const abc = document.querySelector("#input").value;
let lines = abc.split("\n");
lines = replaceStaff(lines);
lines = relaceNote(lines);
const result = lines.join("\n").replaceAll(/%stretchlast/gi, "%%stretchlast");
const tagged = identifyRhythmType(result);
console.log("基本节奏型:", tagged.basicTypes);
console.log("变化节奏型:", tagged.modifiedTypes);
console.log(tagged.rhythm);
return tagged;
}
function replaceStaff(lines) {
let keySignFound = false;
for(let i = 0; i < lines.length; i++) {
if(lines[i].startsWith("K:")) {
lines[i] = "K: C perc stafflines=1";
lines.splice(i + 1, 0, "[I:MIDI = program 0]");
keySignFound = true;
}
else if(lines[i].startsWith("%%stretchlast")) {
if(keySignFound) {
lines.splice(i, 0, "[I:MIDI = program 0]");
}
else {
lines.splice(i, 0, "K: C perc stafflines=1", "[I:MIDI = program 0]");
}
break;
}
else if(lines[i].indexOf("|") > 0) {
if(keySignFound) {
lines.splice(i-1, 0, "[I:MIDI = program 0]", "%%stretchlast");
}
else {
lines.splice(i-1, 0, "K: C perc stafflines=1", "[I:MIDI = program 0]", "%%stretchlast");
}
break;
}
}
return lines;
}
function relaceNote(lines) {
let start = 0;
for(;start < lines.length; start++) {
if(lines[start].startsWith("%%stretchlast")) {
break;
}
}
for(let i = start + 1;i < lines.length; i++) {
lines[i] = lines[i].replaceAll(/[a-g],*/gi, "B");
lines[i] = removeDecorations(lines[i]);
lines[i] = removeAnnotations(lines[i]);
}
return lines;
}
function removeDecorations(line) {
return line.replaceAll(/!.*?!/gi, "");
}
function removeAnnotations(line) {
return line.replaceAll(/".*?"/gi, "");
}
const rhythmTypes = [
{
name: "二八",
type: "basic",
regex: /(\s|\|)(B\/[\(\)]?B\/)(\s|\|)/,
},
{
name: "四十六",
type: "basic",
regex: /(\s|\|)(B\/\/B\/\/B\/\/B\/\/)(\s|\|)/,
},
{
name: "前八后十六",
type: "basic",
regex: /(\s|\|)(B\/B\/\/B\/\/)(\s|\|)/,
},
{
name: "前十六后八",
type: "basic",
regex: /(\s|\|)(B\/\/B\/\/B\/)[\s\|](\s|\|)/,
},
{
name: "小切分",
type: "basic",
regex: /(\s|\|)(B\/\/B\/B\/\/)(\s|\|)/,
},
{
name: "大切分",
type: "basic",
regex: /(\s|\|)(B\/BB\/)(\s|\|)/,
},
{
name: "小附点",
type: "basic",
regex: /(\s|\|)(B\/>B\/)(\s|\|)/,
},
{
name: "大附点",
type: "basic",
regex: /(\s|\|)(B>B)()/,
},
{
name: "三连音",
type: "basic",
regex: /(\s|\|)(\(3B\/B\/B\/)()/,
},
{
name: "二八后休",
type: "modified",
regex: /(\s|\|)(B\/\s*z\/)(\s|\|)/,
},
{
name: "二八前休",
type: "modified",
regex: /(\s|\|)(z\/\s*B\/)(\s|\|)/,
},
{
name: "四十六前休",
type: "modified",
regex: /(\s|\|)(z\/\/B\/\/B\/\/B\/\/)(\s|\|)/,
},
{
name: "前八后十六前休",
type: "modified",
regex: /(\s|\|)(z\/B\/\/B\/\/)(\s|\|)/,
},
{
name: "三连音前休",
type: "modified",
regex: /(\s|\|)(\(3z\/B\/B\/)(\s|\|)()/,
},
{
name: "三连音后休",
type: "modified",
regex: /(\s|\|)(\(3B\/B\/z\/)(\s|\|)()/,
replacement: '$1"三连音后休"$2'
},
]
function identifyRhythmType(abc) {
const basicTypes = [];
const modifiedTypes = [];
for(let i = 0; i < rhythmTypes.length; i++) {
const {name, type, regex} = rhythmTypes[i];
const replacement = `$1"${name}"$2$3`
if(regex.test(abc)) {
if(type === "basic") {
basicTypes.push(name);
} else {
modifiedTypes.push(name);
}
abc = abc.replace(regex, replacement);
}
}
return {
basicTypes,
modifiedTypes,
rhythm: abc
};
}
<html>
<body>
<textarea id="input">
X: 18
T: 春江花月夜
C: 古曲
M: 4/4
L: 1/4
Q: 102
K: G
%%stretchlast
!mf!(E E/E/ G A/E/|D2 D/)z/ ~(E|D D/D/ E G/A/|B,4)|
(B, A,/B,/ D B,/D/|E>GA>B)|(G A/B/ A/G/ E|D2D)(G|
E/G/ A E/G/D/A,/|B,4)|(B, E/G/ D/E/D/B,/|A,4)||
</textarea>
<button onclick="toRhythm()">转节奏</button>
</body>
</html>
#input {
width: 100%;
height: 240px;
}