SOURCE

console 命令行工具 X clear

                    
>
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;
}

/**
 * 替换五线谱为节奏谱线
 * 
 * 1. 全部为C调
 * 
 */
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;
}

/**
 * 将所有音符全部替换为B
 */
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;
}