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
This commit is contained in:
132
repo/js/AutoLyre/README.md
Normal file
132
repo/js/AutoLyre/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# 曲谱 JSON 文件说明
|
||||
此文档供曲谱制作人阅读,本文档详细说明了如何书写一个标准格式的曲谱.json文件,包括各个字段的解释以及曲谱内容的格式要求。
|
||||
|
||||
重要:即使制作了曲谱的JSON文件,放到了正确的路径下,在调度器的JS脚本配置里也不会出现你制作的曲谱(上传方法如下)
|
||||
|
||||
## 上传方法
|
||||
1.上传到BetterGI脚本仓库的repo\js\AutoLyre\assets路径下,根据已存在的曲谱在你的文件名前添加序号(例如 10.曲名.json),完成后请联系BetterGI v7群主更新JS脚本
|
||||
|
||||
2.联系BetterGI v7(1029539994)群主帮你更新到仓库
|
||||
|
||||
3.发送邮件到hijiwos@hotmail.com并说明,你的谱子将会在一段时间内更新到仓库
|
||||
|
||||
## 曲谱制作问题
|
||||
\assets\五线谱注解.png包含了五线谱(高音区和低音区)对应的4组键盘键位(相邻的红蓝大写字母为一组,每组音域为三个八度)
|
||||
|
||||
有不懂的地方请在\assets\example.json内找,这个谱子内包含了该脚本的五线谱相关的所有功能
|
||||
|
||||
## 曲谱文件位置
|
||||
所有的曲谱文件应放置于 AutoLyre\assets 目录下,并在文件名前添加正确的序号
|
||||
|
||||
## 文件结构
|
||||
一个标准的曲谱.json文件的基本结构如下:
|
||||
|
||||
{
|
||||
"name": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"bpm": "",
|
||||
"time_signature": "",
|
||||
"composer": "",
|
||||
"arranger": "",
|
||||
"notes": ""
|
||||
}
|
||||
|
||||
注意:仅: 右侧的双引号内的部分可以更改,具体的曲谱格式请参考assets文件夹下的example.json
|
||||
## 字段说明
|
||||
name: 曲谱名,必填。
|
||||
|
||||
author: 录谱人,制作这个.json曲谱的作者名。
|
||||
|
||||
description: 描述,可以随意填写关于该曲谱的附加信息
|
||||
|
||||
bpm: 曲谱的BPM(Beats Per Minute),必填。
|
||||
|
||||
time_signature: 拍号,必填,例如 3/4 代表 以四分音符为一拍每小节三拍(被设为一拍的音符仅支持2的幂)。
|
||||
|
||||
composer: 曲师,选填。
|
||||
|
||||
arranger: 谱师,选填。
|
||||
|
||||
notes: 曲谱内容,必填,具体格式请参考以下解析规则。
|
||||
|
||||
## Notes 解析规则(重要)
|
||||
notes 字段中包含的是乐谱内容。音符-必须使用-大写字母,乐谱内容使用字符串表示,小节之间用 | 隔开。单个小节的解析规则如下:
|
||||
|
||||
A[4]: 表示按下A键,A键视作四分音符。
|
||||
|
||||
(ASD)[4-#]: 表示装饰音·倚音
|
||||
|
||||
同时按下ASD键,这个和弦视作四分音符的装饰音,该装饰音的时值固定为拍号中的标准时值(3/4的标准时值为四分音符的时值)的1/16
|
||||
|
||||
A[4-8.3](AS)[4-8.3](ASD)[4-8.&]: 表示一个三连音(六连音用法与此相似,仅需将3改成6)
|
||||
|
||||
A[4-8.3]: 4表示该三连音的总时值相当于四分音符,8表示当前音符在乐谱上显示的时值相当于八分音符的时值,3表示这是一个三连音的音符
|
||||
|
||||
(AS)[4-8.3]: 同上,只不过这里举的例子是和弦
|
||||
|
||||
(ASD)[4-8.&]: $表示这是当前连音的最后一个音符
|
||||
|
||||
H[4-4.3]G[4-8.&]: 表示一个三连音连音线(与三连音用法相同,区别于三连音,三连音连音线允许连线内出现不同类型的音符)
|
||||
|
||||
H[4-4.3]: 第一个4表示整个三连音的总时值为一个四分音符,第二个4表示当前音符在乐谱上显示的时值相当于四分音符的时值,3表示这是一个三连音的音符
|
||||
|
||||
G[4-8.&]: 4表示整个三连音的总时值为一个四分音符,8表示这是一个八分音符,$表示这是当前连音的最后一个音符
|
||||
|
||||
@[2-8.6](AF)[2-16.6]N[2-16.6](AF)[2-16.6]N[2-16.6](AF)[2-16.6]N[2-16.6](AF)[2-16.6]N[2-16.6](AF)[2-16.6]N[2-16.$]: 表示一个六连音连音线(乐谱上表示为一个六连音连音线内包含1个八分休止符和10个十六分音符,区别于三连音,六连音的.后面的数字是6)
|
||||
|
||||
@[2-8.6]: 2表示该六连音的总时值相当于一个二分音符,8表示当前音符在乐谱上显示的时值相当于八分音符的时值,6表示这是一个六连音
|
||||
|
||||
N[2-16.$]: 16表示当前音符在乐谱上显示的时值相当于十六分音符的时值,$表示这是当前连音的最后一个音符
|
||||
|
||||
@[4]: 表示一个休止符
|
||||
|
||||
中括号内标明这是几分休止符,例如这里表示四分休止符。
|
||||
|
||||
A[4-*]: 表示一个附点四分音符
|
||||
|
||||
表示按下A键,A键视作附点四分音符。
|
||||
|
||||
## 代码美化
|
||||
曲谱JSON文件的"notes"的值视作一个字符串,在这个字符串内可以使用空格和换行符美化代码(notes内的空格和换行符不会被读取执行)
|
||||
|
||||
例如:
|
||||
{
|
||||
"name": "示例曲谱",
|
||||
"author": "录谱人A",
|
||||
"bpm": "120",
|
||||
"description": 预计时长: xxxxx, 五线谱网址: xxx.xxxx.xxx,
|
||||
"time_signature": "4/4",
|
||||
"composer": "曲师B",
|
||||
"arranger": "谱师C",
|
||||
"notes": "A[4](ASD)[8]Y[8-#]F[8-#](DFG)[8]R[4-*]T[8]|\n@[4](DFG)[8](CVB)[8]D[4]A[4]|\nA[4](ASD)[8]Y[8-#]F[8-#](DFG)[8]R[4]T[4]|\n@[4](DFG)[8](CVB)[8]D[4]A[4]"
|
||||
}
|
||||
|
||||
## 附:
|
||||
中括号 [](- 前表示音符类型,- 后用于区分特殊音符)
|
||||
|
||||
[4] 表示四分音符。
|
||||
|
||||
[16] 表示十六分音符。
|
||||
|
||||
[-#] 表示装饰音。
|
||||
|
||||
[-3] 表示三连音(使用时必须保证是三个连续的三连音)。
|
||||
|
||||
例如:[16-#] 表示十六分音符的装饰音,A[4-3]S[4-3]D[4-3] 表示一个时值为4分音符的三连音。
|
||||
|
||||
## 示例
|
||||
一个完整的曲谱.json文件示例如下(仅包含两个小节):
|
||||
|
||||
文件名: 示例曲谱.json
|
||||
|
||||
{
|
||||
"name": "示例曲谱",
|
||||
"author": "录谱人A",
|
||||
"bpm": "120",
|
||||
"description": "曲谱信息",
|
||||
"time_signature": "4/4",
|
||||
"composer": "曲师B",
|
||||
"arranger": "谱师C",
|
||||
"notes": "A[4](ASD)[8]Y[8-#]F[8-#](DFG)[8]R[4-*]T[8]|\n@[4](DFG)[8](CVB)[8]D[4]A[4]|\nA[4](ASD)[8]Y[8-#]F[8-#](DFG)[8]R[4]T[4]|\n@[4](DFG)[8](CVB)[8]D[4]A[4]"
|
||||
}
|
||||
9
repo/js/AutoLyre/assets/1.小星星.json
Normal file
9
repo/js/AutoLyre/assets/1.小星星.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "小星星",
|
||||
"author": "提瓦特钓鱼玳师",
|
||||
"bpm": "80",
|
||||
"time_signature": "4/4",
|
||||
"composer": "none",
|
||||
"arranger": "none",
|
||||
"notes": "Z[4]Z[4]B[4]B[4]|N[4]N[4]B[2]|V[4]V[4]C[4]C[4]|X[4]X[4]Z[2]|B[4]B[4]V[4]V[4]|C[4]C[4]X[2]|B[4]B[4]V[4]V[4]|C[4]C[4]X[2]|Z[4]Z[4]B[4]B[4]|N[4]N[4]B[2]|V[4]V[4]C[4]C[4]|X[4]X[4]Z[2]|"
|
||||
}
|
||||
10
repo/js/AutoLyre/assets/2.小星星变奏曲.json
Normal file
10
repo/js/AutoLyre/assets/2.小星星变奏曲.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10
repo/js/AutoLyre/assets/example.json
Normal file
10
repo/js/AutoLyre/assets/example.json
Normal file
File diff suppressed because one or more lines are too long
BIN
repo/js/AutoLyre/assets/五线谱注解.png
Normal file
BIN
repo/js/AutoLyre/assets/五线谱注解.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
470
repo/js/AutoLyre/main.js
Normal file
470
repo/js/AutoLyre/main.js
Normal file
@@ -0,0 +1,470 @@
|
||||
(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();
|
||||
})();
|
||||
15
repo/js/AutoLyre/manifest.json
Normal file
15
repo/js/AutoLyre/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "原琴·五线谱版",
|
||||
"version": "1.0",
|
||||
"bgi_version": "0.42.0",
|
||||
"description": "功能描述:功能及其强大的原琴脚本\n核心功能------------------------------>\n1.轻松实现根据五线谱翻版琴谱,支持单音、和弦\n2.曲谱支持录入BPM、拍号\n3.特殊音符支持休止符、浮点音符、(三/六)连音、(三/六)连音标记线、装饰音·倚音\n注意事项------------------------------>\n1.音域只有3个八度,受原琴音域限制,本脚本的上限取决于翻谱的大佬(卑微\n2.实际上装饰音·倚音的时长视为基础时值单位(比如拍号2/4的基础时值单位就是4分音符)的1/16\n3.制铺说明:曲谱JSON文件的notes必须保证为一行且不能包括空白符(空格和换行符等);小节之间用|隔开,|不是必要的,作用是方便曲谱维护\n---------------------------------------->\n版本:ver1.0\n作者:提瓦特钓鱼玳师\n脚本反馈邮箱:hijiwos@hotmail.com",
|
||||
"authors": [
|
||||
{
|
||||
"name": "提瓦特钓鱼玳师",
|
||||
"url": "https://github.com/Hijiwos"
|
||||
}
|
||||
],
|
||||
"settings_ui": "settings.json",
|
||||
"main": "main.js"
|
||||
}
|
||||
41
repo/js/AutoLyre/settings.json
Normal file
41
repo/js/AutoLyre/settings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
[
|
||||
{
|
||||
"name": "music_selector",
|
||||
"type": "select",
|
||||
"label": "选择乐曲(队列执行启用后该选项实效)",
|
||||
"options": [
|
||||
"1.小星星",
|
||||
"2.小星星变奏曲",
|
||||
"3.Unknown Mother Goose [アンノウン・マザーグース]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "music_repeat",
|
||||
"type": "input-text",
|
||||
"label": "循环执行次数(不填默认不循环)"
|
||||
},
|
||||
{
|
||||
"name": "repeat_interval",
|
||||
"type": "input-text",
|
||||
"label": "循环间隔时间(单位:s, 循环执行启用时生效,不填默认0s)"
|
||||
},
|
||||
{
|
||||
"name": "repeat_mode",
|
||||
"type": "select",
|
||||
"label": "循环模式(不填默认为单曲循环)",
|
||||
"options": [
|
||||
"单曲循环",
|
||||
"队列循环"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "music_queue",
|
||||
"type": "input-text",
|
||||
"label": "队列执行(填写数字[乐曲前的序号],空格隔开,不填默认不启用)"
|
||||
},
|
||||
{
|
||||
"name": "music_interval",
|
||||
"type": "input-text",
|
||||
"label": "队列内间隔时间(单位:s, 队列执行启用时生效)"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user