Files
bettergi-scripts-list/repo/js/AutoLyre/main.js
提瓦特钓鱼玳师 f704ba61fd JS脚本:原琴·五线谱版 (#370)
* update repo.json

* JS脚本:原琴·五线谱版

功能及其强大的原琴脚本-适配五线谱翻谱
核心功能------------------------------>
1.轻松实现根据五线谱翻版琴谱,支持单音、和弦
2.曲谱支持录入BPM、拍号
3.特殊音符支持休止符、浮点音符、(三/六)连音、(三/六)连音标记线、装饰音·倚音
注意事项------------------------------>
1.音域只有3个八度,受原琴音域限制,本脚本的上限取决于翻谱的大佬(卑微
2.实际上装饰音·倚音的时长视为基础时值单位(比如拍号2/4的基础时值单位就是4分音符)的1/16
3.制铺说明:曲谱JSON文件的notes必须保证为一行且不能包括空白符(空格和换行符等);小节之间用|隔开,|不是必要的,作用是方便曲谱维护

* update repo.json

* Delete repo/js/README.md

* Delete repo/js/main.js

* Delete repo/js/manifest.json

* Delete repo/js/settings.json

* update repo.json

* JS脚本:原琴·五线谱版

* update repo.json
2025-02-26 22:39:30 +08:00

470 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(async function () { // 更改了连音
// 乐曲名(带序号)
const music_list = ["1.小星星", "2.小星星变奏曲", "3.Unknown Mother Goose [アンノウン・マザーグース]"]
const base_path = "assets/"
/**
*
* 执行单音
*
* @param key {string}
*
*/
async function play_note(key) {
keyDown(key);
keyUp(key);
}
/**
*
* 执行和弦
*
* @param keys {Array.string}
*
*/
async function play_chord(keys) {
for (const key of keys) {
play_note(key);
}
}
/**
*
* 根据乐曲文件名生成乐曲文件路径
*
* @param music_name 乐曲文件名
* @returns {string} 乐曲文件路径
*/
function path_join(music_name) {
return base_path + music_name + ".json";
}
/**
*
* 计算当前音符的时长(检测音符后是否有装饰音)
*
* @param sheet_list {Object[][]} 解析后的乐谱
* @param symbol_time 每一拍的时间
* @param symbol 以几分音符为一拍
* @param note_type 音符类型
* @param count 当前音符下标
* @param note_time 当前音符的时长默认为undefined不为空时symbol note_type count实效
* @returns {number}
*/
function cal_time_ornament(sheet_list, symbol_time, symbol, note_type, count, note_time=undefined) {
try {
if (note_time === undefined) {
// 该音符的正常时长
note_time = Math.round(symbol_time * (symbol / note_type));
}
// 装饰音时长
let ornament_time = Math.round(symbol_time / 16)
let check_count = count + 1;
let ornament_count = 0; // 装饰音计数
while (check_count < sheet_list.length) { // 装饰音不可能在曲谱末尾else会在匹配不到装饰音的循环触发
if (sheet_list[check_count]["spl"] === "#") {
ornament_count += 1;
} else {
if (ornament_count === 0) {
return note_time;
} else {
// 装饰音占用的时间过长就不预留时间
if (ornament_time * ornament_count < note_time) {
return note_time - ornament_time * ornament_count;
} else {
return note_time;
}
}
}
check_count += 1;
}
} catch (error) {
log.error(`出错(cal_time_ornament): ${error}`);
}
}
/**
* 获取JS脚本配置
*
* @returns {Object} 包含解析后JS脚本配置的对象具有以下属性
* - type {string} 执行类型:单曲和队列
* - repeat {integer} 单曲重复次数
* - interval {integer} 队列间隔时间(type为"single"时无此属性)
* - music {string}|{Array.string} 乐曲名type为"single"时为 {string}, type为"queue"时为 {Array.<string>}
*
*/
function get_settings() {
try{
// 读取选择的单曲
let music_single = typeof(settings.music_selector) === 'undefined' ? 0 : settings.music_selector;
// 读取循环次数
let music_repeat = typeof(settings.music_repeat) === 'undefined' ? 1 : parseInt(settings.music_repeat, 10);
// 读取循环间隔时间
let repeat_interval = typeof(settings.repeat_interval) === 'undefined' ? 0 : parseInt(settings.repeat_interval, 10);
// 读取循环模式
let repeat_mode = typeof(settings.repeat_mode) === 'undefined' ? "单曲循环" : settings.repeat_mode;
// 读取乐曲队列
let music_queue = typeof(settings.music_queue) === 'undefined' ? 0 : settings.music_queue;
// 读取队列间隔时间
let music_interval = typeof(settings.music_interval) === 'undefined' ? 0 : parseInt(settings.music_interval, 10);
if (music_queue === 0) { // 单曲执行
if (music_single !== 0) {
return {
"type": "single",
"repeat": music_repeat,
"repeat_interval": repeat_interval,
"music": music_single,
};
} else {
log.error(`错误JS脚本配置有误单曲未选择`);
}
} else { // 队列执行
let local_music_dic = {}; // 存储本地乐曲对照字典
let temp_music_list = []; // 存储乐曲名
// 写入本地乐曲对照字典
for (const each of music_list) {
if (each !== "example") {
// 从文件名提取编号
let temp_num = each.split(".")[0];
local_music_dic[temp_num] = each;
}
}
// 读取乐曲队列配置
for (const num of music_queue.split(" ")) {
if (Object.keys(local_music_dic).includes(num)) {
temp_music_list.push(local_music_dic[num]);
log.info(`乐曲: ${local_music_dic[num]} 已加入队列`)
} else {
log.info(`编号不存在,已跳过(编号:${num})`)
}
}
return {
"type": "queue",
"repeat": music_repeat,
"repeat_interval": repeat_interval,
"repeat_mode": repeat_mode,
"interval": music_interval,
"music": temp_music_list
};
}
} catch (error) {
log.error(`读取JS脚本配置时出错${error}`);
}
}
/**
*
* 读取并解析一个乐谱文件
*
* @param music_name {string} 乐曲文件名
* @returns {Promise<{}|null>}
*/
async function get_music_msg(music_name) {
let music_path = path_join(music_name);
let file_text = ""; // 存储乐曲文件内容
try{
file_text = file.readTextSync(music_path);
} catch (error) {
log.error(`文件无法读取:${music_path}\nerror:${error}`);
}
if(file_text == null){ // 检测文件是否读取
log.error(`读取文件 ${music_path} 错误,文件为空`);
return null ;
} else {
log.info(`文件读取成功: ${music_path}`);
}
let music_msg_dic = {};
// 正则表达式,用于匹配如下内容
let regex_name = /(?<="name": ")[\s\S]*?(?=")/
let regex_author = /(?<="author": ")[\s\S]*?(?=")/
let regex_bpm = /(?<="bpm": ")[\s\S]*?(?=")/
let regex_time_signature = /(?<="time_signature": ")[\s\S]*?(?=")/
let regex_composer = /(?<="composer": ")[\s\S]*?(?=")/
let regex_arranger = /(?<="arranger": ")[\s\S]*?(?=")/
let regex_notes = /(?<="notes": ")[\s\S]*?(?=")/
let regex_blank = /[\\n]/g
try {
// 歌曲名
music_msg_dic["name"] = file_text.match(regex_name)[0];
// 录谱人
music_msg_dic["author"] = file_text.match(regex_author)[0];
// 歌曲BPM
music_msg_dic["bpm"] = file_text.match(regex_bpm)[0];
// 拍号
music_msg_dic["time_signature"] = file_text.match(regex_time_signature)[0];
// 曲师
music_msg_dic["composer"] = file_text.match(regex_composer)[0];
// 谱师
music_msg_dic["arranger"] = file_text.match(regex_arranger)[0];
// 曲谱内容(删除换行符)
music_msg_dic["notes"] = file_text.match(regex_notes)[0].replace(regex_blank, '');
} catch(error) {
log.info(`曲谱解析错误:${error}\n请检查曲谱文件格式是否正确`);
return null;
}
return music_msg_dic;
}
/**
*
* 解析乐谱字符串乐谱JSON文件中的notes
*
* 小节之间用|隔开且乐谱中不能有空格,单个小节的解析规则如下:
* A[4] 表示按下A键A键视作四分音符
* (ASD)[4-#] 表示同时按下ASD键这个和弦视作四分音符的装饰音
* A[4-3](AS)[4-3](ASD)[4-3] 表示等分四分音符的三连音(-后填3必须要连着写三个这样的音符按顺序按下A、AS、ASD键
* @[4] 表示休止符中括号内标明这是几分休止符例如这里表示4分休止符
* 附:
* 中括号(-前表示音符类型-后用于区分特殊音符):[填4表示4分音符填16表示16分音符...-填#表示装饰音填3表示三连音] 例:[16-#]
*
* @param sheet {string} 乐谱
* @returns {Object[][]}
*/
function parseMusicSheet(sheet) {
// 将输入字符串按照小节分割
let bars = sheet.split('|');
let result = [];
// 遍历每个小节
bars.forEach(bar => {
let i = 0;
// 逐个字符解析小节中的音符及其属性
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++;
}
// 解析音符类型(用方括号包裹)
if (bar[i] === '[') {
i++;
while (bar[i] !== ']') {
type += bar[i];
i++;
}
i++; // 跳过闭合方括号
}
// 解析特殊音符属性如果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
});
}
});
return result;
}
/**
*
* 根据解析后的乐谱进行演奏
*
* @param sheet_list {Object[][]} 解析后的乐谱
* @param bpm BPM (240)
* @param ts 拍号 (3/4)
* @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"]); // 和弦
} 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, 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"]); // 单音
}
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"] === '@') { // 休止符
await sleep(time_current);
} 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 {
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;
}
}
}
async function main() {
const settings_dic = get_settings();
if (settings_dic["type"] === "single") { // 单曲
if (settings_dic["repeat"] === 1) {
log.info(`1`);
let music_msg = await get_music_msg(settings_dic["music"]);
const music_sheet = parseMusicSheet(music_msg["notes"]);
await play_sheet(music_sheet, music_msg["bpm"], music_msg["time_signature"]);
} else {
for (let i = 0; i < settings_dic["repeat"]; i++) {
log.info(`2`);
await sleep(settings_dic["repeat_interval"] * 1000); // 循环间隔
const music_msg = await get_music_msg(settings_dic["music"]);
const music_sheet = parseMusicSheet(music_msg["notes"]);
await play_sheet(music_sheet, music_msg["bpm"], music_msg["time_signature"]);
}
}
} else { // 队列
let repeat_queue = 1;
if (settings_dic["repeat_mode"] === "队列循环") { // 队列循环
log.info(`3`);
repeat_queue = settings_dic["repeat"];
}
for (let r = 0; r < repeat_queue; r++) {
for (const music_name of settings_dic["music"]) {
// 读取乐谱
const music_msg = await get_music_msg(music_name);
// 解析乐谱
const music_sheet = parseMusicSheet(music_msg["notes"]);
if (settings_dic["repeat"] === 1) {
await play_sheet(music_sheet, music_msg["bpm"], music_msg["time_signature"]);
} else {
let repeat_single = 1;
if (settings_dic["repeat_mode"] !== "队列循环") { // 单曲循环
log.info(`4`);
repeat_single = settings_dic["repeat"];
}
for (let i = 0; i < repeat_single; i++) {
await play_sheet(music_sheet, music_msg["bpm"], music_msg["time_signature"]);
log.info(`曲目: ${music_name} 演奏完成`);
if (repeat_single !== 1) {
await sleep(settings_dic["repeat_interval"] * 1000); // 单曲循环间隔
}
}
}
// 队列内间隔
await sleep(settings_dic["interval"] * 1000);
}
if (repeat_queue !== 1) {
await sleep(settings_dic["repeat_interval"] * 1000); // 队列循环间隔
}
}
}
}
await main();
})();