mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-04-04 06:46:19 +08:00
JS脚本:自动原琴(五线谱版)【更新】 (#1838)
* Delete repo/js/AutoYuanQin directory * 更新了MIDI直接转换为JSON曲谱(HTML+JS纯代码实现)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 曲谱 JSON 文件说明
|
||||
* **注意**
|
||||
- 制谱优先使用AutoYuanQin\assets\tutorial_file目录下的制谱软件(index.html),有任何疑问请来看这个使用说明
|
||||
- 制谱优先使用AutoYuanQin\assets\tutorial_file目录下的制谱软件(五线谱制谱器.html),有任何疑问请来看这个使用说明
|
||||
此文档供曲谱制作人阅读,本文档详细说明了一个标准格式的曲谱.json文件格式,包括各个字段的解释以及曲谱内容的格式要求。
|
||||
|
||||
重要:即使制作了曲谱的JSON文件,放到了正确的路径下,在调度器的JS脚本配置里也不会出现你制作的曲谱(上传方法如下)
|
||||
@@ -12,8 +12,14 @@
|
||||
|
||||
3.发送邮件到hijiwos@hotmail.com并说明,你的谱子将会在一段时间内更新到仓库
|
||||
|
||||
## MIDI翻谱器使用方法
|
||||
**MIDI翻谱器: AutoYuanQin\assets\tutorial_file\MIDI翻谱器.html(请确保 五线谱注解.png与制谱器位于同一目录下)**
|
||||
**声明:本制谱器生成的曲谱文件为标准格式(区别于五线谱制谱器,生成的是MIDI版本的JSON标准格式)**
|
||||
|
||||
使用浏览器打开```MIDI翻谱器.html```即可,注意**千万不要手动修改生成的JSON文件中的author**
|
||||
|
||||
## 曲谱制作器使用方法
|
||||
**制谱器路径: AutoYuanQin\assets\tutorial_file\index.html(请确保 五线谱注解.png与制谱器位于同一目录下)**
|
||||
**制谱器路径: AutoYuanQin\assets\tutorial_file\五线谱制谱器.html(请确保 五线谱注解.png与制谱器位于同一目录下)**
|
||||
**声明:本制谱器生成的曲谱文件为标准格式**
|
||||
|
||||
* 使用步骤如下(顺序)
|
||||
|
||||
6741
repo/js/AutoYuanQin/assets/score_file/10.卡农(MIDI转谱).json
Normal file
6741
repo/js/AutoYuanQin/assets/score_file/10.卡农(MIDI转谱).json
Normal file
File diff suppressed because it is too large
Load Diff
10
repo/js/AutoYuanQin/assets/score_file/9.One Last Kiss.json
Normal file
10
repo/js/AutoYuanQin/assets/score_file/9.One Last Kiss.json
Normal file
File diff suppressed because one or more lines are too long
250
repo/js/AutoYuanQin/assets/tutorial_file/MIDI翻谱器.html
Normal file
250
repo/js/AutoYuanQin/assets/tutorial_file/MIDI翻谱器.html
Normal file
@@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>MIDI 解析器(纯JS版)</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; padding: 20px; }
|
||||
pre { background: #f4f4f4; padding: 10px; border-radius: 5px; white-space: pre-wrap; word-break: break-word; }
|
||||
button { margin-left: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>MIDI 解析器(纯JS版)</h1>
|
||||
<input type="file" id="midiFile" accept=".mid">
|
||||
<button id="exportBtn" disabled>导出 JSON</button>
|
||||
<pre id="output"></pre>
|
||||
|
||||
<script>
|
||||
// ===== JS 版 best_three_octave_natural_dict =====
|
||||
function bestThreeOctaveNaturalDict(midiList) {
|
||||
const noteNames = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];
|
||||
const naturalMap = {
|
||||
"C":"C","C#":"C",
|
||||
"D":"D","D#":"D",
|
||||
"E":"E",
|
||||
"F":"F","F#":"F",
|
||||
"G":"G","G#":"G",
|
||||
"A":"A","A#":"A",
|
||||
"B":"B"
|
||||
};
|
||||
const naturalOrder = ["C","D","E","F","G","A","B"];
|
||||
const labels = "ZXCVBNMASDFGHJQWERTYU".split("");
|
||||
|
||||
const midiInts = Array.from(new Set(midiList.map(n=>parseInt(n)))).sort((a,b)=>a-b);
|
||||
if (!midiInts.length) return {};
|
||||
|
||||
const midiToNatOct = m => {
|
||||
const nName = noteNames[m % 12];
|
||||
const naturalName = naturalMap[nName];
|
||||
const octave = Math.floor(m / 12) - 1;
|
||||
return [naturalName, octave];
|
||||
};
|
||||
|
||||
const inputNotes = midiInts.map(midiToNatOct);
|
||||
const minOct = Math.min(...inputNotes.map(n=>n[1]));
|
||||
const maxOct = Math.max(...inputNotes.map(n=>n[1]));
|
||||
|
||||
let bestStart = null, bestCover = -1, bestCenterDiff = Infinity;
|
||||
|
||||
for (let startOct = minOct - 3; startOct <= maxOct; startOct++) {
|
||||
for (let startNat of naturalOrder) {
|
||||
let seq = [];
|
||||
let noteIdx = naturalOrder.indexOf(startNat);
|
||||
let octv = startOct;
|
||||
for (let i=0; i<21; i++) {
|
||||
seq.push([naturalOrder[noteIdx], octv]);
|
||||
noteIdx++;
|
||||
if (noteIdx >= naturalOrder.length) { noteIdx = 0; octv++; }
|
||||
}
|
||||
const coverCount = inputNotes.filter(n => seq.some(s => s[0]===n[0] && s[1]===n[1])).length;
|
||||
const seqMidOct = seq[Math.floor(seq.length/2)][1];
|
||||
const inputMidOct = inputNotes.sort((a,b)=>a[1]-b[1])[Math.floor(inputNotes.length/2)][1];
|
||||
const centerDiff = Math.abs(seqMidOct - inputMidOct);
|
||||
|
||||
if (coverCount > bestCover || (coverCount === bestCover && centerDiff < bestCenterDiff)) {
|
||||
bestCover = coverCount;
|
||||
bestStart = seq[0];
|
||||
bestCenterDiff = centerDiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mappingDict = {};
|
||||
let [startNat, startOct] = bestStart;
|
||||
let noteIdx = naturalOrder.indexOf(startNat);
|
||||
let octv = startOct;
|
||||
let labelIdx = 0;
|
||||
|
||||
for (let i=0; i<21; i++) {
|
||||
const natName = naturalOrder[noteIdx];
|
||||
const midiBase = noteNames.indexOf(natName) + (octv + 1) * 12;
|
||||
for (let m = midiBase; m < midiBase + 2; m++) {
|
||||
if (noteNames[m % 12] === natName || naturalMap[noteNames[m % 12]] === natName) {
|
||||
mappingDict[m.toString()] = labels[labelIdx];
|
||||
}
|
||||
}
|
||||
noteIdx++;
|
||||
labelIdx++;
|
||||
if (noteIdx >= naturalOrder.length) { noteIdx = 0; octv++; }
|
||||
}
|
||||
return mappingDict;
|
||||
}
|
||||
|
||||
// ===== MIDI 解析函数 =====
|
||||
function parseMidi(arrayBuffer) {
|
||||
const data = new DataView(arrayBuffer);
|
||||
let pos = 0;
|
||||
|
||||
function readStr(len) {
|
||||
let s = "";
|
||||
for (let i=0; i<len; i++) s += String.fromCharCode(data.getUint8(pos++));
|
||||
return s;
|
||||
}
|
||||
function readUint32() {
|
||||
const v = data.getUint32(pos);
|
||||
pos += 4;
|
||||
return v;
|
||||
}
|
||||
function readUint16() {
|
||||
const v = data.getUint16(pos);
|
||||
pos += 2;
|
||||
return v;
|
||||
}
|
||||
function readVarLen() {
|
||||
let value = 0;
|
||||
while (true) {
|
||||
let b = data.getUint8(pos++);
|
||||
if (b & 0x80) {
|
||||
value += (b & 0x7F);
|
||||
value <<= 7;
|
||||
} else {
|
||||
value += b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Header
|
||||
const headerChunkType = readStr(4);
|
||||
const headerLength = readUint32();
|
||||
const formatType = readUint16();
|
||||
const numTracks = readUint16();
|
||||
const division = readUint16();
|
||||
|
||||
pos = 8 + headerLength; // 跳到第一个轨道块
|
||||
|
||||
let allNotes = [];
|
||||
let notesList = [];
|
||||
let tempo = 500000; // 默认 120bpm
|
||||
const ticksPerBeat = division;
|
||||
|
||||
for (let t=0; t<numTracks; t++) {
|
||||
const trackType = readStr(4);
|
||||
const trackLength = readUint32();
|
||||
const trackEnd = pos + trackLength;
|
||||
let lastStatus = null;
|
||||
|
||||
while (pos < trackEnd) {
|
||||
const deltaTime = readVarLen();
|
||||
let statusByte = data.getUint8(pos++);
|
||||
if (statusByte < 0x80) {
|
||||
pos--;
|
||||
statusByte = lastStatus;
|
||||
} else {
|
||||
lastStatus = statusByte;
|
||||
}
|
||||
|
||||
if (statusByte === 0xFF) {
|
||||
const metaType = data.getUint8(pos++);
|
||||
const len = readVarLen();
|
||||
if (metaType === 0x51) {
|
||||
tempo = (data.getUint8(pos)<<16) | (data.getUint8(pos+1)<<8) | data.getUint8(pos+2);
|
||||
}
|
||||
pos += len;
|
||||
} else if ((statusByte & 0xF0) === 0x90 || (statusByte & 0xF0) === 0x80) {
|
||||
const note = data.getUint8(pos++);
|
||||
const velocity = data.getUint8(pos++);
|
||||
allNotes.push(note.toString());
|
||||
notesList.push({
|
||||
type: ((statusByte & 0xF0) === 0x90 && velocity > 0) ? "on" : "off",
|
||||
note: note,
|
||||
time: deltaTime
|
||||
});
|
||||
} else {
|
||||
let paramLen = 2;
|
||||
if ((statusByte & 0xF0) === 0xC0 || (statusByte & 0xF0) === 0xD0) paramLen = 1;
|
||||
pos += paramLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mappingDict = bestThreeOctaveNaturalDict(allNotes);
|
||||
const msPerTick = tempo / ticksPerBeat / 1000.0;
|
||||
const mappedNotes = notesList.map(n => ({
|
||||
type: n.type,
|
||||
note: mappingDict[n.note.toString()] || "K",
|
||||
time: +(n.time * msPerTick).toFixed(3)
|
||||
}));
|
||||
|
||||
return {
|
||||
name: "示例曲谱",
|
||||
author: "MidiTrans",
|
||||
bpm: Math.round(60000000 / tempo).toString(),
|
||||
description: "曲谱信息",
|
||||
time_signature: "4/4",
|
||||
composer: "曲师",
|
||||
arranger: "谱师",
|
||||
notes: mappedNotes
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 全局变量保存解析结果和文件名 =====
|
||||
let lastScoreJson = null;
|
||||
let lastMidiFileName = "";
|
||||
|
||||
// ===== 文件选择事件 =====
|
||||
document.getElementById('midiFile').addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
lastMidiFileName = file.name; // 保存原始文件名
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(evt) {
|
||||
try {
|
||||
const arrayBuffer = evt.target.result;
|
||||
lastScoreJson = parseMidi(arrayBuffer);
|
||||
document.getElementById('output').textContent = JSON.stringify(lastScoreJson, null, 2);
|
||||
// 解析成功后启用导出按钮
|
||||
document.getElementById('exportBtn').disabled = false;
|
||||
} catch (err) {
|
||||
document.getElementById('output').textContent = "解析出错: " + err.message;
|
||||
lastScoreJson = null;
|
||||
document.getElementById('exportBtn').disabled = true;
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
|
||||
// ===== 导出 JSON 按钮事件 =====
|
||||
document.getElementById('exportBtn').addEventListener('click', function() {
|
||||
if (!lastScoreJson) return;
|
||||
const blob = new Blob([JSON.stringify(lastScoreJson, null, 2)], {type: "application/json"});
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// 用原 MIDI 文件名生成 JSON 文件名
|
||||
let jsonFileName = lastMidiFileName.replace(/\.[^/.]+$/, "") + ".json";
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = jsonFileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,9 @@
|
||||
"5.Flower Dance",
|
||||
"6.起风了",
|
||||
"7.千本樱 (Eric Chen)",
|
||||
"8.春よ、来い(春天,来吧)"
|
||||
"8.春よ、来い(春天,来吧)",
|
||||
"9.One Last Kiss",
|
||||
"10.卡农(MIDI转谱)"
|
||||
]
|
||||
const base_path = "assets/score_file/"
|
||||
|
||||
@@ -225,7 +227,11 @@
|
||||
// 谱师
|
||||
music_msg_dic["arranger"] = file_text.match(regex_arranger)[0];
|
||||
// 曲谱内容(删除换行符)
|
||||
music_msg_dic["notes"] = file_text.match(regex_notes)[0].replace(regex_blank, '');
|
||||
if (music_msg_dic["author"] !== "MidiTrans") {
|
||||
music_msg_dic["notes"] = file_text.match(regex_notes)[0].replace(regex_blank, '');
|
||||
} else {
|
||||
music_msg_dic["notes"] = JSON.parse(file_text)["notes"];
|
||||
}
|
||||
} catch(error) {
|
||||
log.info(`曲谱解析错误:${error}\n请检查曲谱文件格式是否正确`);
|
||||
return null;
|
||||
@@ -246,69 +252,74 @@
|
||||
* 附:
|
||||
* 中括号(-前表示音符类型-后用于区分特殊音符):[填4表示4分音符,填16表示16分音符...-填#表示装饰音,填3表示三连音] 例:[16-#]
|
||||
*
|
||||
* @param sheet {string} 乐谱
|
||||
* @param sheet {string} 乐谱 [DEBUG]更新midi后这里也会是一个字典
|
||||
* @returns {Object[][]}
|
||||
*/
|
||||
function parseMusicSheet(sheet) {
|
||||
// 将输入字符串按照小节分割
|
||||
let bars = sheet.split('|');
|
||||
let result = [];
|
||||
|
||||
// 遍历每个小节
|
||||
bars.forEach(bar => {
|
||||
let i = 0;
|
||||
if (typeof(sheet) === "object") {
|
||||
result = sheet;
|
||||
} else {
|
||||
// 将输入字符串按照小节分割
|
||||
let bars = sheet.split('|');
|
||||
|
||||
// 逐个字符解析小节中的音符及其属性
|
||||
while (i < bar.length) {
|
||||
let note = ''; // 存储音符
|
||||
let type = ''; // 存储音符类型
|
||||
let chord = false; // 判断是否为和弦
|
||||
let spl = 'none'; // 存储特殊音符属性,默认值为 "none"
|
||||
// 遍历每个小节
|
||||
bars.forEach(bar => {
|
||||
let i = 0;
|
||||
|
||||
// 检查是否为和弦(和弦用圆括号包裹)
|
||||
if (bar[i] === '(') {
|
||||
chord = true;
|
||||
i++;
|
||||
while (bar[i] !== ')') {
|
||||
note += bar[i];
|
||||
// 逐个字符解析小节中的音符及其属性
|
||||
while (i < bar.length) {
|
||||
let note = ''; // 存储音符
|
||||
let type = ''; // 存储音符类型
|
||||
let chord = false; // 判断是否为和弦
|
||||
let spl = 'none'; // 存储特殊音符属性,默认值为 "none"
|
||||
|
||||
// 检查是否为和弦(和弦用圆括号包裹)
|
||||
if (bar[i] === '(') {
|
||||
chord = true;
|
||||
i++;
|
||||
while (bar[i] !== ')') {
|
||||
note += bar[i];
|
||||
i++;
|
||||
}
|
||||
i++; // 跳过闭合圆括号
|
||||
} else if (bar[i] === '@') {
|
||||
// 处理休止符
|
||||
note = '@';
|
||||
i++;
|
||||
} else {
|
||||
note = bar[i];
|
||||
i++;
|
||||
}
|
||||
i++; // 跳过闭合圆括号
|
||||
} else if (bar[i] === '@') {
|
||||
// 处理休止符
|
||||
note = '@';
|
||||
i++;
|
||||
} else {
|
||||
note = bar[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
// 解析音符类型(用方括号包裹)
|
||||
if (bar[i] === '[') {
|
||||
i++;
|
||||
while (bar[i] !== ']') {
|
||||
type += bar[i];
|
||||
// 解析音符类型(用方括号包裹)
|
||||
if (bar[i] === '[') {
|
||||
i++;
|
||||
while (bar[i] !== ']') {
|
||||
type += bar[i];
|
||||
i++;
|
||||
}
|
||||
i++; // 跳过闭合方括号
|
||||
}
|
||||
i++; // 跳过闭合方括号
|
||||
}
|
||||
|
||||
// 解析特殊音符属性(如果type中包含'-')
|
||||
if (type.includes('-')) {
|
||||
let splIndex = type.indexOf('-');
|
||||
spl = type.slice(splIndex + 1);
|
||||
type = parseInt(type.slice(0, splIndex), 10);
|
||||
}
|
||||
// 解析特殊音符属性(如果type中包含'-')
|
||||
if (type.includes('-')) {
|
||||
let splIndex = type.indexOf('-');
|
||||
spl = type.slice(splIndex + 1);
|
||||
type = parseInt(type.slice(0, splIndex), 10);
|
||||
}
|
||||
|
||||
// 将解析结果添加到parsedNotes数组中
|
||||
result.push({
|
||||
"note": note,
|
||||
"type": type,
|
||||
"chord": chord,
|
||||
"spl": spl
|
||||
});
|
||||
}
|
||||
});
|
||||
// 将解析结果添加到parsedNotes数组中
|
||||
result.push({
|
||||
"note": note,
|
||||
"type": type,
|
||||
"chord": chord,
|
||||
"spl": spl
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -323,101 +334,112 @@
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function play_sheet(sheet_list, bpm, ts) {
|
||||
// 确定是以几分音符为一拍
|
||||
let symbol = parseInt(ts.split("/")[1], 10);
|
||||
// 每拍所需的时间
|
||||
let symbol_time = Math.round(60000 / bpm);
|
||||
// 装饰音时长
|
||||
let ornament_time = Math.round(symbol_time / 16)
|
||||
// 存储连音
|
||||
let temp_legato = [];
|
||||
|
||||
// test 需要额外计算装饰音时值的影响
|
||||
for (let i = 0; i < sheet_list.length; i++) {
|
||||
// 显示正在演奏的音符
|
||||
log.info(`${sheet_list[i]["note"]}[${sheet_list[i]["type"]}-${sheet_list[i]["spl"]}]`);
|
||||
if (sheet_list[i]["spl"] === 'none') { // 单音、休止符或和弦
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
if (Object.keys(sheet_list[0]).length === 3) {
|
||||
for (let i = 0; i < sheet_list.length; i++) {
|
||||
await sleep(Math.round(sheet_list[i]["time"]));
|
||||
if (sheet_list[i]["type"] === "on") {
|
||||
keyDown(sheet_list[i]["note"]);
|
||||
} else {
|
||||
if (sheet_list[i]["note"] === '@') { // 休止符
|
||||
// pass
|
||||
keyUp(sheet_list[i]["note"]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 确定是以几分音符为一拍
|
||||
let symbol = parseInt(ts.split("/")[1], 10);
|
||||
// 每拍所需的时间
|
||||
let symbol_time = Math.round(60000 / bpm);
|
||||
// 装饰音时长
|
||||
let ornament_time = Math.round(symbol_time / 16)
|
||||
// 存储连音
|
||||
let temp_legato = [];
|
||||
|
||||
// test 需要额外计算装饰音时值的影响
|
||||
for (let i = 0; i < sheet_list.length; i++) {
|
||||
// 显示正在演奏的音符
|
||||
log.info(`${sheet_list[i]["note"]}[${sheet_list[i]["type"]}-${sheet_list[i]["spl"]}]`);
|
||||
if (sheet_list[i]["spl"] === 'none') { // 单音、休止符或和弦
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
} else {
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
}
|
||||
|
||||
if (i !== sheet_list.length - 1) {
|
||||
await sleep(cal_time_ornament(sheet_list, symbol_time, symbol, sheet_list[i]["type"], i));
|
||||
}
|
||||
} else if (sheet_list[i]["spl"] === '#') { // 装饰音(不会包含休止符),时值为symbol的时值的1/16
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
} else {
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
if (i !== sheet_list.length - 1) {
|
||||
await sleep(ornament_time);
|
||||
}
|
||||
} else if (/\.3|\.6|\.\$/.test(sheet_list[i]["spl"])) { // 三连音/六连音(可能包含休止符)
|
||||
temp_legato.push({
|
||||
"note": sheet_list[i]["note"],
|
||||
"chord": sheet_list[i]["chord"],
|
||||
"type": sheet_list[i]["type"]
|
||||
});
|
||||
|
||||
// 演奏连音
|
||||
if ("$".includes(sheet_list[i]["spl"])) {
|
||||
// 连音的总时长
|
||||
let time_legato = Math.round(symbol_time * (symbol / sheet_list[i]["type"]));
|
||||
// 当前音符类型
|
||||
let current_type = parseInt(sheet_list[i]["spl"].split(".")[0])
|
||||
// 连音的音符数值总和(用于计算当前音符时长)
|
||||
let time_all = temp_legato.reduce((sum, each) => sum + 1 / parseInt(each["spl"].split(".")[0]), 0);
|
||||
// 当前音符时长
|
||||
let time_current = Math.round(time_legato * (1 / current_type) / time_all);
|
||||
// 计数
|
||||
let count = undefined;
|
||||
|
||||
for (const note_legato of temp_legato) {
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
if (sheet_list[i]["note"] === '@') { // 休止符
|
||||
// pass
|
||||
} else {
|
||||
if (sheet_list[i]["note"] === '@') { // 休止符
|
||||
// pass
|
||||
} else {
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
|
||||
if (count === temp_legato.length - 1 && i !== sheet_list.length - 1) {
|
||||
// 计算连音的最后一个音的时值(计算装饰音)
|
||||
await sleep(cal_time_ornament(sheet_list, symbol_time, symbol, sheet_list[i]["type"], i, time_current));
|
||||
// 重置连音缓存区
|
||||
temp_legato = [];
|
||||
} else if (i !== sheet_list.length - 1) {
|
||||
await sleep(time_current);
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
} else if (sheet_list[i]["spl"] === '*') { // 附点音符
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
} else {
|
||||
if (sheet_list[i]["note"] === '@') { // 休止符
|
||||
// pass
|
||||
|
||||
if (i !== sheet_list.length - 1) {
|
||||
await sleep(cal_time_ornament(sheet_list, symbol_time, symbol, sheet_list[i]["type"], i));
|
||||
}
|
||||
} else if (sheet_list[i]["spl"] === '#') { // 装饰音(不会包含休止符),时值为symbol的时值的1/16
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
} else {
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
if (i !== sheet_list.length - 1) {
|
||||
await sleep(ornament_time);
|
||||
}
|
||||
} else if (/\.3|\.6|\.\$/.test(sheet_list[i]["spl"])) { // 三连音/六连音(可能包含休止符)
|
||||
temp_legato.push({
|
||||
"note": sheet_list[i]["note"],
|
||||
"chord": sheet_list[i]["chord"],
|
||||
"type": sheet_list[i]["type"]
|
||||
});
|
||||
|
||||
// 演奏连音
|
||||
if ("$".includes(sheet_list[i]["spl"])) {
|
||||
// 连音的总时长
|
||||
let time_legato = Math.round(symbol_time * (symbol / sheet_list[i]["type"]));
|
||||
// 当前音符类型
|
||||
let current_type = parseInt(sheet_list[i]["spl"].split(".")[0])
|
||||
// 连音的音符数值总和(用于计算当前音符时长)
|
||||
let time_all = temp_legato.reduce((sum, each) => sum + 1 / parseInt(each["spl"].split(".")[0]), 0);
|
||||
// 当前音符时长
|
||||
let time_current = Math.round(time_legato * (1 / current_type) / time_all);
|
||||
// 计数
|
||||
let count = undefined;
|
||||
|
||||
for (const note_legato of temp_legato) {
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
} else {
|
||||
if (sheet_list[i]["note"] === '@') { // 休止符
|
||||
// pass
|
||||
} else {
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
}
|
||||
|
||||
if (count === temp_legato.length - 1 && i !== sheet_list.length - 1) {
|
||||
// 计算连音的最后一个音的时值(计算装饰音)
|
||||
await sleep(cal_time_ornament(sheet_list, symbol_time, symbol, sheet_list[i]["type"], i, time_current));
|
||||
// 重置连音缓存区
|
||||
temp_legato = [];
|
||||
} else if (i !== sheet_list.length - 1) {
|
||||
await sleep(time_current);
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
} else if (sheet_list[i]["spl"] === '*') { // 附点音符
|
||||
if (sheet_list[i]["chord"]) {
|
||||
await play_chord(sheet_list[i]["note"]); // 和弦
|
||||
} else {
|
||||
if (sheet_list[i]["note"] === '@') { // 休止符
|
||||
// pass
|
||||
} else {
|
||||
await play_note(sheet_list[i]["note"]); // 单音
|
||||
}
|
||||
}
|
||||
// 排除尾音
|
||||
if (i !== sheet_list.length - 1) {
|
||||
await sleep(cal_time_ornament(sheet_list, symbol_time * 1.5, symbol, sheet_list[i]["type"], i));
|
||||
}
|
||||
} else {
|
||||
log.info(`错误: ${sheet_list[i]["spl"]}`);
|
||||
return null;
|
||||
}
|
||||
// 排除尾音
|
||||
if (i !== sheet_list.length - 1) {
|
||||
await sleep(cal_time_ornament(sheet_list, symbol_time * 1.5, symbol, sheet_list[i]["type"], i));
|
||||
}
|
||||
} else {
|
||||
log.info(`错误: ${sheet_list[i]["spl"]}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,7 +449,7 @@
|
||||
if (settings_msg == null) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
// try {
|
||||
if (settings_msg["type"] === "single") { // 单曲
|
||||
// 读取乐谱
|
||||
const music_msg = await get_music_msg(settings_msg["music"]);
|
||||
@@ -475,9 +497,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`出现错误: ${error}`);
|
||||
}
|
||||
// } catch (error) {
|
||||
// log.error(`出现错误: ${error}`);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "原琴·五线谱版",
|
||||
"version": "2.1.1",
|
||||
"version": "3.0",
|
||||
"bgi_version": "0.43.1",
|
||||
"description": "功能描述:功能及其强大的原琴脚本\n核心功能------------------------------>\n1.轻松实现根据五线谱翻版琴谱,支持单音、和弦\n2.曲谱支持录入BPM、拍号\n3.特殊音符支持休止符、浮点音符、(三/六)连音、(三/六)连音标记线、装饰音·倚音\n4.含有制谱器,方便制作曲谱\n注意事项------------------------------>\n1.使用前请装备原琴\n2.音域只有3个八度,受原琴音域限制,本脚本的上限取决于翻谱的大佬(卑微\n3.实际上装饰音·倚音的时长视为基础时值单位(比如拍号2/4的基础时值单位就是4分音符)的1/16\n4.制铺说明:曲谱JSON文件的notes必须保证为一行且不能包括空白符(换行符除外);小节之间用|隔开,|不是必要的,作用是方便曲谱维护\n---------------------------------------->\n作者:提瓦特钓鱼玳师\n脚本反馈邮箱:hijiwos@hotmail.com",
|
||||
"authors": [
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
"5.Flower Dance-[5:20]",
|
||||
"6.起风了-[3:22]",
|
||||
"7.千本樱 (Eric Chen)-[4:03]",
|
||||
"8.春よ、来い(春天,来吧)-[4:02]"
|
||||
"8.春よ、来い(春天,来吧)-[4:02]",
|
||||
"9.One Last Kiss-[4:12]",
|
||||
"10.卡农(MIDI转谱).json"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user