349 Commits

Author SHA1 Message Date
EillesWan
d4901cf3dc 更新 TODO 文件 2026-02-07 05:55:27 +08:00
EillesWan
13512df9ce 完成插件系统的基本设计,接着需要优化一些内容,标记了 TODO 2026-02-07 05:44:57 +08:00
EillesWan
1d9931f79d 完美,同志,完美!!!!!!! 2026-02-04 00:06:14 +08:00
EillesWan
841f6e53c6 临时上传,仍在开发过程中 2026-02-02 01:33:47 +08:00
EillesWan
0de959c396 基本的设计已经完成,今天休息一下 2026-01-26 09:39:22 +08:00
EillesWan
734ee2dd66 加了一个描述 2026-01-24 05:40:17 +08:00
EillesWan
32b7930b26 基本数据结构做好了,接下来就是解析功能了 2026-01-24 05:37:38 +08:00
EillesWan
583ca04ac9 2026-01-22 09:23:33 +08:00
EillesWan
1e0698c85d Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2026-01-16 01:30:01 +08:00
EillesWan
dc3be4120d 什么?还有合并内容? 2026-01-16 01:29:15 +08:00
Eilles
bf173a9f81 没有变化 2026-01-08 03:18:20 +08:00
Eilles
6bea46977f 下次要建一个dev分支到仓库里啊…… 2025-11-30 02:51:59 +08:00
Eilles
3b8532af61 2025-11-28 05:21:58 +08:00
Eilles
9f35e9e16a 啊? 2025-11-27 04:18:16 +08:00
Eilles
65358b12d0 气笑了 2025-11-27 04:17:26 +08:00
Eilles
046c2af27c Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-11-27 04:03:41 +08:00
Eilles
fa6cfb4873 神笔编码 2025-11-27 04:03:39 +08:00
EillesWan
3d6d0e17a2 去人工智能那边看看,我直接抄吧 2025-11-20 16:05:30 +08:00
EillesWan
f1bea1d57e Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-10-22 11:50:12 +08:00
EillesWan
63b59442a4 重构么?不重构吧…… 2025-10-22 11:50:04 +08:00
Eilles
7f74b29569 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-09-27 16:56:09 +08:00
Eilles
885dc085ea 我改了啥我都不记得了,但是同步一下代码吧 2025-09-27 16:56:06 +08:00
EillesWan
24742a140c 也是绝了,版本改了之后版本标识没更新,对,我说的是MSQ和FSQ 2025-09-26 09:45:19 +08:00
EillesWan
6f94f477f2 暑假回学校,同步所有更改内容 2025-08-30 19:48:41 +08:00
EillesWan
c056eb1fcb 小问题修复,回复 https://gitee.com/ElapsingDreams/MusicPreview/issues/ICUEFX 的问题 2025-08-24 00:11:01 +08:00
EillesWan
b796a363d8 更丰富的音符附加信息支持 2025-08-20 18:57:21 +08:00
EillesWan
774f78359c Midi歌词支持,文档格式更新 2025-08-19 04:37:11 +08:00
EillesWan
8df63df26c 更新一处细节,避免声相偏移无法听见 2025-07-08 06:00:32 +08:00
EillesWan
9764019f08 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-07-06 02:59:14 +08:00
EillesWan
250857c78f 同步本机更改? 2025-07-06 02:59:10 +08:00
EillesWan
7ab6b77cff Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-06-19 02:30:45 +08:00
EillesWan
321f80540c 完整的全景声相支持,修复了之前音量大小确定错误的问题 2025-06-19 02:30:24 +08:00
EillesWan
50373b05d0 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2025-05-26 17:36:24 +08:00
EillesWan
b65483b32d 合并双机器工作之变化性更改 2025-05-26 17:33:39 +08:00
EillesWan
45e9adbbd1 支持神羽资源包 2025-05-25 04:20:55 +08:00
EillesWan
7a1ca86132 新增部分可指定的默认值,增加默认乐器替换表 2025-04-15 21:14:38 +08:00
EillesWan
64048a5e31 修复插件兼容性问题,Sorry 2025-04-15 20:21:40 +08:00
EillesWan
3739138059 切换为UV进行包管理 2025-04-15 20:01:43 +08:00
EillesWan
048b631bd6 强化文档内容 2025-04-11 16:58:11 +08:00
EillesWan
23bf69619b 2.3.0,基于FSQ的完整流式音符信息传输支持。 2025-04-11 16:31:54 +08:00
EillesWan
b4e16353ec 更新上传文件 2025-04-08 18:14:50 +08:00
EillesWan
c14489f3a7 MSQ 流式适配与校验增强,新增 NBS 音色表 2025-04-08 17:49:49 +08:00
EillesWan
889f8f9641 Java版!MSQ流式解析初适配! 2025-04-02 02:52:41 +08:00
EillesWan
2df8d6a270 我服了你个老六 2024-12-29 21:00:43 +08:00
EillesWan
78eabf2df6 接口小兼容 2024-12-29 20:48:37 +08:00
EillesWan
b3ddfc7ade 文档改动 2024-12-26 02:02:13 +08:00
EillesWan
bd031ef547 Bug小修 2024-10-22 00:33:54 +08:00
EillesWan
e4304dc3d1 部分API小幅度新增内容 2024-10-22 00:19:39 +08:00
EillesWan
794ee6d080 2024-08-19 02:33:00 +08:00
EillesWan
b67554c7c9 增加一个英文致谢 2024-08-19 02:31:49 +08:00
EillesWan
6f391567ba 部分API小幅新增,修复新版本指令的计分板播放器附加包无法播放的问题 2024-08-19 02:28:28 +08:00
EillesWan
932b9a34e0 修改协议 2024-07-18 16:21:10 +08:00
EillesWan
f6b884e38f 高精度时间支持,修复TEMPO设置错误的问题 2024-06-11 00:54:53 +08:00
EillesWan
bf1b7b99d8 高精度时间支持,修复tempo设置错误的问题 2024-06-11 00:54:50 +08:00
EillesWan
fe7c11636b 解决一些小问题 2024-05-02 02:00:04 +08:00
EillesWan
382b0e1601 websocket播放支持 2024-05-01 01:16:05 +08:00
EillesWan
c17ff0e53a 直接让MusicSave的函数放在主类里吧,更好用一些 2024-03-10 18:52:18 +08:00
EillesWan
f1ab2373b5 完成2.0.0正式版构建,现已支持音乐保存…… 2024-03-10 18:47:10 +08:00
Eilles Wan
77351d767b !4 update Musicreater/constants.INSTRUMENT_OFFSET_POS_TABLE
Merge pull request !4 from ElapsingDreams/N/A
非常好的偏移表
2024-03-10 10:37:19 +00:00
ElapsingDreams
69624df711 update Musicreater/constants.INSTRUMENT_OFFSET_POS_TABLE
依据wiki更改 不同乐器的音调偏离对照表 ,其数值为偏离标准音C3的数值

Signed-off-by: ElapsingDreams <9063072+ElapsingDreams@user.noreply.gitee.com>
2024-03-10 10:26:28 +00:00
EillesWan
d4925e4d75 进一步优化结构,提高插件兼容 2024-03-10 02:22:12 +08:00
EillesWan
95c0ff1b47 2.0.0全新版本,更好的代码组织形式 2024-03-04 00:04:05 +08:00
EillesWan
3831c41b9a 我觉得能用了 2024-02-24 21:31:39 +08:00
EillesWan
a4565b8529 如改 2024-02-08 18:29:29 +08:00
EillesWan
041c64ff65 优化example结构,支持mcstructure的计分板播放 2024-02-03 16:54:43 +08:00
EillesWan
b0bdb7b445 修复了乐音乐器的execute前缀缺失的问题 2024-02-02 20:50:35 +08:00
EillesWan
99a7564648 修复了打击乐器转换时,注释部分format代码报错的问题 2024-02-02 18:12:29 +08:00
EillesWan
dbb3f4c83f 修复了一个小兼容性bug 2024-01-23 18:36:25 +08:00
EillesWan
edd40c078f 加个copy方法 2024-01-23 18:17:15 +08:00
EillesWan
d3b637a0c8 修复类型定义的重复问题-2 2024-01-23 18:14:41 +08:00
EillesWan
d7e3c62deb 修复类型定义的重复问题 2024-01-23 17:01:42 +08:00
EillesWan
7b319268fe 改个版本号 2024-01-14 22:08:27 +08:00
EillesWan
41883f7798 可以用了(?) 2024-01-14 22:05:55 +08:00
EillesWan
fc138f1dbf 新的转换算法测试,更好的声音适配 2024-01-08 00:08:52 +08:00
EillesWan
0b0328bc44 更新乐器对照表 2023-10-22 12:37:53 +08:00
EillesWan
72dfdfeb34 改进Channel的应用,减少内存占用,优化结构生成,提高MIDI兼容,修改API结构 2023-10-02 18:24:09 +08:00
EillesWan
9a580132e5 新增midi对应钢琴键盘的音符表 2023-09-30 02:27:51 +08:00
EillesWan
29380d4151 优化生成结构 2023-09-24 12:44:00 +08:00
EillesWan
76eff25a1d 文档全面升级,部分架构更新 2023-09-24 00:38:31 +08:00
EillesWan
7b60d3f9ea 新增红石包子 2023-09-10 19:26:42 +08:00
EillesWan
483e45dcc2 成了吗?如成! 2023-09-10 19:13:39 +08:00
EillesWan
627a26f64a 支持mcstructure结构导出旧版指令 2023-09-10 11:43:39 +08:00
EillesWan
357cb18c5b 提交文档,增加作者署名 2023-08-12 15:10:42 +08:00
EillesWan
83c9750db3 重大更新:优化算法+红石指令音乐生成 2023-08-12 12:39:30 +08:00
EillesWan
5c86a28b44 okay 2023-07-31 23:25:45 +09:00
EillesWan
70674ec6f7 增加对于不正规Midi兼容性的支持,避免转换错误的情况发生。 2023-07-31 22:28:14 +09:00
EillesWan
f73c1be944 更高效的算法管理与兼容性和代码格式更新,详见样例代码;同时新增实验算法,在其中尝试下次更新的内容 2023-07-01 19:11:10 +08:00
EillesWan
36f8db722b Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-06-11 13:10:42 +08:00
EillesWan
bedb3924c6 新增一个感谢人 2023-06-11 13:10:39 +08:00
EillesWan
81ae7ae146 修复一些小问题 2023-06-11 13:09:11 +08:00
bgArray
c39c6c4488 Remove feudal superstition 2023-06-04 21:33:20 +08:00
EillesWan
556ce74cfb 修复打击乐器播放时的错误,即将步入红石音乐生成阶段 2023-06-04 17:01:59 +08:00
EillesWan
3da472052e 代码架构升级,功能分割,减少强制性依赖,API全新换代,文档全面更新,新一代音·创,音·创1.0.0 2023-05-27 18:41:02 +08:00
EillesWan
ffe5837c9f 不要更新!不要更新!命运的齿轮尚未开始转动,前方的道路将会铺满沙尘。 2023-05-07 19:25:51 +08:00
EillesWan
42e2fcdd98 要更新,要更新 2023-05-01 19:34:22 +08:00
EillesWan
755de846c7 小改example 2023-05-01 13:58:11 +08:00
EillesWan
cb95c51a47 修复重大问题:延迟播放器中音符的延迟过长,感谢Mono 2023-05-01 12:40:40 +08:00
bgArray
6fe8e41dfa update 2023/4/30 pypi update 2023-04-30 22:45:03 +08:00
EillesWan
b8bae7b913 你觉得我为什么还在用3.8 2023-04-30 22:05:43 +08:00
EillesWan
7663bf8838 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-04-30 21:57:40 +08:00
EillesWan
8aa503710d 紧急更新,修复安卓无法使用的小bug 2023-04-30 21:57:14 +08:00
bgArray
d5d7230537 update 2023/4/29 pypi update 2023-04-29 22:04:41 +08:00
EillesWan
5d48fcd96a Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-04-29 21:32:16 +08:00
EillesWan
aa94e3758b 更新一下版本 2023-04-29 21:32:09 +08:00
mingfengpigeon
8b06078a90 Format. 2023-04-29 21:28:36 +08:00
EillesWan
799b9b664d Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-04-29 17:00:36 +08:00
EillesWan
4874ace92d 史诗级更新!支持导出mcstructure库,更改部分API和文档,修改了部分代码格式,新增红乐测试 2023-04-29 17:00:32 +08:00
bgArray
54cd6b196e update 2023/4/19 pypi update2 2023-04-19 21:45:43 +08:00
bgArray
882fa4175e Merge remote-tracking branch 'origin/master' 2023-04-19 21:18:04 +08:00
bgArray
b15c89b6c8 update 2023/4/19 pypi update & v0.4.0 update 2023-04-19 21:17:51 +08:00
EillesWan
b758a2f967 更改依赖版本 2023-04-16 19:14:13 +08:00
EillesWan
a9ec7582a9 更新版本 2023-04-16 19:12:10 +08:00
EillesWan
48f5a975db 革命尚未成功?也许是库的问题,但是没时间检查了,先走了((( 2023-04-16 19:11:55 +08:00
EillesWan
acb347b491 新增一个依赖,减少一个文档,把文档移动到了MCS库里 2023-03-26 19:18:00 +08:00
EillesWan
6ca8820fa8 改个东西 2023-03-12 17:57:40 +08:00
EillesWan
fb4cd07dc6 新增MCSTRUCTURE结构文档 2023-03-12 17:43:52 +08:00
EillesWan
3db35b24ab Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-03-12 14:35:26 +08:00
EillesWan
a07f723d7c 这改了不啦,一代一代更新有个鬼用,什么都没变嘛;我带的是什么队啊,我带的是睿穆啊,先把这个理念搞懂嘛 2023-03-12 14:35:23 +08:00
EillesWan
7cc70d6948 修改部分文档 2023-03-11 18:23:40 +08:00
EillesWan
b1fb82712d Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-03-05 18:58:08 +08:00
EillesWan
bd329082c2 这不是最终版本,请不要提交更新 2023-03-05 18:58:05 +08:00
Eilles Wan
16be83ea29 update README.md.
Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2023-02-19 02:25:00 +00:00
EillesWan
5ffb654ec9 修复method3的时长bug 2023-02-12 17:34:03 +08:00
bgArray
7f5efea038 update 2023/2/12 bug fixed 2023-02-12 16:25:52 +08:00
EillesWan
5efabd5463 更新版本号 2023-02-12 16:11:21 +08:00
EillesWan
24fd0121a3 更新文档与依赖描述 2023-02-12 16:08:54 +08:00
EillesWan
a65be910a5 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-02-12 15:55:24 +08:00
EillesWan
15cd0ad777 little update 2023-02-12 15:55:09 +08:00
bgArray
4904f612e1 update 2023/2/5 小改 2023-02-05 17:56:06 +08:00
bgArray
27c6556693 update 2023/2/5 小改 2023-02-05 17:52:20 +08:00
EillesWan
26fbd346d8 新增第三种算法,解决通道与音轨的乐器问题;修复to_DICT中的音符时间问题 2023-02-05 16:16:12 +08:00
EillesWan
c419adc274 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2023-02-01 22:40:36 +08:00
EillesWan
f8926d2f4f 删除一些没用的文件 2023-02-01 22:40:24 +08:00
bgArray
4268139313 Merge remote-tracking branch 'origin/master' 2023-02-01 22:35:21 +08:00
bgArray
8b8334202c 2023/2/1 update pypi update 2023-02-01 22:35:09 +08:00
EillesWan
1cfbfe1ba1 新增安装教程 2023-02-01 22:31:21 +08:00
EillesWan
3669b5f804 Merge branch 'pkgver' of https://gitee.com/TriM-Organization/Musicreater 2023-02-01 22:10:35 +08:00
EillesWan
7146d17051 删除不必要的文件 2023-02-01 21:51:31 +08:00
EillesWan
8a21b6c3e7 同步仓库描述,准备合并至主版本 2023-02-01 21:48:12 +08:00
EillesWan
5cce59135a 修bug,准备合并 2023-02-01 19:19:40 +08:00
EillesWan
53fda9c986 修复部分bug,准备合并 2023-02-01 18:48:30 +08:00
EillesWan
75f94dab4c 准备与pkgver合并 2023-02-01 18:24:44 +08:00
bgArray
6aa557dcd2 2023/2/1 update fug fixed 2.1 2023-02-01 11:59:14 +08:00
bgArray
215bcafb64 2023/2/1 update fug fixed 2.0 2023-02-01 10:55:07 +08:00
bgArray
f568803b0d 2023/2/1 update fug fixed 2023-02-01 10:09:16 +08:00
EillesWan
a8c0c9e1d5 bug fixed 2023-01-31 21:06:22 +08:00
EillesWan
4d7a1b614a 修复部分bug again 2023-01-31 20:49:53 +08:00
EillesWan
975dbd71e3 新增一点教程 2023-01-31 20:47:09 +08:00
EillesWan
8722d4fd83 修复部分bug 2023-01-31 20:39:50 +08:00
EillesWan
8b9d9a78a3 bug fixed 2023-01-29 13:02:43 +08:00
bgArray
032bbb81c4 update 2023/1/28 formatting and blacking codes 2023-01-28 11:56:09 +08:00
bgArray
49c4cfcbe2 update 2023/1/27 formatting and fixing bugs 2023-01-27 23:25:28 +08:00
EillesWan
352807cba0 小bug能奈我何? 2023-01-27 22:16:15 +08:00
EillesWan
8e938b4f84 Merge branch 'pkgver' of https://gitee.com/TriM-Organization/Musicreater into pkgver 2023-01-27 02:19:56 +08:00
EillesWan
bf0ef8ebb2 新增 延迟播放器算法2、修复进度条无法显示播放百分比、播放时最大积分错误的bug 2023-01-27 02:19:53 +08:00
bgArray
c4664f0e0c add .gitattributes.
Signed-off-by: bgArray <474037765@qq.com>
2023-01-26 02:05:22 +00:00
EillesWan
65a21dcfa7 Bug Fixed 2023-01-24 17:28:05 +08:00
EillesWan
032e2f0f0a 更新部分文档,以及英文语言支持 2023-01-24 16:24:02 +08:00
EillesWan
87e80406a3 移除部分内容 2023-01-23 01:35:56 +08:00
EillesWan
41a6a84342 新增言论,修改其余部分的协议声明 2023-01-23 01:30:44 +08:00
EillesWan
4b9550db21 bug不过年 2023-01-22 00:04:09 +08:00
EillesWan
3d66c06e6d 修复一个小问题 2023-01-21 19:39:14 +08:00
EillesWan
9872a69a66 同时修复bug 2023-01-21 04:12:31 +08:00
EillesWan
0ce72fc4e3 避免GPL传染,我fo了 2023-01-21 04:11:53 +08:00
EillesWan
e0557da3cf 今年就这样罢,我摆了,紧急更新,修复一点点小bug 2023-01-20 22:26:04 +08:00
EillesWan
7d6faebc5b 增加调试功能,降低容错率(不是,我真的没有反向更新(((
但是修改了算法1,算法1的响度错误解决了。但是我仍然推荐算法2
2023-01-20 21:34:15 +08:00
EillesWan
502f4eb54d 紧急修复bug 2023-01-20 01:33:57 +08:00
EillesWan
290db45c23 更新文档 2023-01-20 01:03:59 +08:00
EillesWan
782395b6c9 除旧迎新,0.2.0大更新! 2023-01-20 00:33:09 +08:00
EillesWan
392e74d8d9 0.2.0前的预备更新 2023-01-20 00:31:45 +08:00
EillesWan
ebfaeafcda 更新新手指南文档 2023-01-07 01:19:13 +08:00
EillesWan
6955bb8eaf 完善文档 2023-01-06 12:32:18 +08:00
EillesWan
582e66cd58 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2023-01-05 17:30:39 +08:00
EillesWan
d64540bfd0 新增文档,修复小bug,更新版本 2023-01-05 17:30:36 +08:00
bgArray
63499732eb 2023/1/2 update-新手答疑指南.md 2023-01-02 16:40:17 +08:00
EillesWan
be40f8f920 改错版本号了qwq 2023-01-02 14:32:25 +08:00
EillesWan
190ef70fc4 忘了改版本号 2023-01-02 14:31:23 +08:00
EillesWan
a74fd4b4de 更加优秀的打击乐器判断! 2023-01-02 14:28:25 +08:00
EillesWan
13f7c0cda0 修改协议声明并新增名言警句 2023-01-02 00:35:08 +08:00
EillesWan
2ebdf6be24 修改版权声明以及部分教程文档 2023-01-01 03:23:48 +08:00
EillesWan
9ca0788eb2 准备修改协议 2022-12-31 13:21:30 +08:00
EillesWan
fba83ef968 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-12-31 13:20:32 +08:00
EillesWan
68e019412d 没写完,但是新算法 2022-12-31 13:20:30 +08:00
bgArray
bde2b4e378 update msctPkgver/main.py.
Signed-off-by: bgArray <474037765@qq.com>
2022-12-29 11:52:10 +00:00
bgArray
b04c01772f 更新指令格式 2022-12-29 11:09:30 +08:00
Eilles Wan
3cd85897fc update README.md.
Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2022-12-10 11:09:21 +00:00
Eilles Wan
00c2dcdeab update README.md.
Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2022-12-10 11:08:49 +00:00
EillesWan
88d36f94f0 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-11-27 18:13:05 +08:00
EillesWan
8eda5c4736 别急,没写完 2022-11-27 18:13:01 +08:00
Eilles Wan
37ccbe3dae 没翻译完,差不多就行
没翻译完

Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2022-11-25 04:40:51 +00:00
EillesWan
be43e4a92a 修复mcpack制作过程中的问题 2022-11-20 15:28:09 +08:00
EillesWan
5929ae417d 改太快了,出问题了,修复了 2022-11-20 12:26:28 +08:00
EillesWan
517c7db112 好玩~ 2022-11-20 12:16:59 +08:00
Eilles Wan
91c31d4e90 update resources/myWords.txt.
Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2022-11-20 04:09:02 +00:00
EillesWan
3ae456c4d1 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-11-20 12:02:45 +08:00
EillesWan
b0af5f4950 紧急修复文件路径问题和进度条问题(没修进度条,但是胜似修了) 2022-11-20 12:02:40 +08:00
Eilles Wan
1ccaa8e9aa update docs/功能使用说明.md.
Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2022-11-20 02:59:00 +00:00
EillesWan
8d1e6b2ecf 习语进人 2022-11-06 18:24:12 +08:00
EillesWan
75fabf9c37 行了,重要的不急 2022-10-30 01:58:29 +08:00
EillesWan
95e34f5c7b 不好意思,我急了 2022-10-30 01:40:53 +08:00
EillesWan
5bb0227a7a 没写完,不急 2022-10-30 01:39:20 +08:00
EillesWan
397825b483 更新 言·论 2022-10-29 22:26:52 +08:00
EillesWan
9c9ebe0a08 Bug++ 2022-10-23 02:12:11 +08:00
EillesWan
54d434404c 更新言·论 2022-10-16 23:32:13 +08:00
EillesWan
0b3a19faad 更新安卓端的使用方法说明 2022-10-16 23:16:46 +08:00
EillesWan
6cde168bf3 学校的更改 2022-10-16 21:19:14 +08:00
Eilles Wan
451d1d8e80 update msctPkgver/main.py.
插值OK

Signed-off-by: Eilles Wan <w-yi_doctoryi@outlook.com>
2022-10-07 12:23:02 +00:00
EillesWan
085ab3f4c0 没写完 2022-10-07 19:25:28 +08:00
EillesWan
a16109f29a 搞错了再来一发 2022-10-07 16:52:17 +08:00
EillesWan
813186bef9 快好了,不急不急 2022-10-07 16:49:47 +08:00
EillesWan
3921190832 okk准备睡觉 2022-10-06 00:06:41 +08:00
EillesWan
c32b94592a 更新文档及其翻译……,另外对无法批量转换带进度条的bug做了更正……现在批量可以用进度条了 2022-10-05 23:59:06 +08:00
EillesWan
77b7344277 把教程改了个名 2022-10-05 22:29:07 +08:00
EillesWan
0a17f8b8f8 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-10-05 22:27:55 +08:00
EillesWan
f8d707ac11 由于Merge错误,我得提交未完成的修改 2022-10-05 22:21:13 +08:00
EillesWan
ad7e047abc Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-10-05 22:18:15 +08:00
EillesWan
5ea47c54cb 这是学校里做的更新 2022-10-05 22:18:03 +08:00
EillesWan
5956e27d83 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-10-05 01:12:42 +08:00
EillesWan
b73a041883 更新文案 2022-10-05 01:12:37 +08:00
bgArray
36f1831cd1 update README.md.
Signed-off-by: bgArray <474037765@qq.com>
2022-09-25 14:58:11 +00:00
bgArray
b20f350da4 update README.md.
Signed-off-by: bgArray <474037765@qq.com>
2022-09-10 12:58:14 +00:00
Eilles
5aaae83020 呼呼,教程写完啦 2022-09-10 00:02:55 +08:00
Eilles
85e6340570 教程写完了,欧耶 2022-09-10 00:00:58 +08:00
Eilles
662c6506d8 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-09-09 22:58:59 +08:00
Eilles
ad2fcd6f14 更新demo的输入操作,一并增加教程 2022-09-09 22:58:05 +08:00
bgArray
5dde31d081 帮金羿提交,顺便改点小语法 2022-09-09 14:14:51 +08:00
bgArray
2e11a9949b 格式化 2022-09-08 22:36:01 +08:00
EillesWan
0a9c70f97c 更新依赖 2022-08-09 18:45:17 +08:00
EillesWan
814ab2ab0e 更新自动启动工具 2022-08-09 16:07:59 +08:00
EillesWan
3b53846ade ok 2022-08-09 15:13:25 +08:00
EillesWan
96af5183ac 库版 0.0.1 正式推出 2022-08-09 15:10:52 +08:00
EillesWan
4aebf9db16 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-08-09 12:54:25 +08:00
EillesWan
fff689446a 我真的不鸽 2022-08-09 12:54:18 +08:00
bgArray
59fd2645e4 update README.md. 2022-08-08 16:07:19 +00:00
bgArray
33ce7ed8c8 update README.md. 2022-08-08 15:57:07 +00:00
bgArray
14f7d6fd7e update README.md. 2022-08-08 15:52:01 +00:00
bgArray
fb2fd13b21 readme 更新 2022-08-08 23:49:22 +08:00
EillesWan
5025cba356 魔法计划启动! 2022-08-08 22:08:08 +08:00
EillesWan
abb53a7499 没改 2022-07-22 17:06:09 +08:00
EillesWan
73854be7be 格式化一下文档 2022-07-12 11:48:32 +08:00
EillesWan
6f6a77d0b4 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-07-12 11:46:49 +08:00
EillesWan
e0a3399fed 新增更新工具 2022-07-12 11:46:37 +08:00
CaryXiong
2afd6beb3f 更新 2022-06-30 12:14:22 +08:00
bgArray
9517c49ec5 修正库导入BUG 2022-06-28 07:43:15 +08:00
bgArray
887452184b update msctPkgver/load_InstrumentLabel.py. 2022-06-26 12:06:52 +00:00
Eilles Wan
d36134e525 诸葛八卦没改完
我来给他改一下,改完来,这家伙没料到我后面留了一手吧,qwq
2022-06-25 16:17:42 +00:00
bgArray
453ca745af 修改算法,修正bug 2022-06-25 23:20:56 +08:00
EillesWan
7d814bcda2 !!update 2022-06-25 22:21:12 +08:00
EillesWan
50dff06ac2 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-06-25 22:06:24 +08:00
EillesWan
d88e50819d 更了?没更 2022-06-25 22:06:19 +08:00
EillesWan
325bb37210 更了?没更…… 2022-06-25 22:04:01 +08:00
CaryXiong
55b933ac85 更新算法,使之适配多乐器 2022-06-24 13:46:46 +08:00
EillesWan
56fb029e75 新增批量转换新样例 2022-06-19 02:04:17 +08:00
EillesWan
c5a7dfb608 小改动 2022-06-19 01:39:50 +08:00
EillesWan
0a640267ff 修复进度条显示错误 2022-06-19 01:36:52 +08:00
EillesWan
a6d7022f87 修复错误 2022-06-19 01:26:53 +08:00
EillesWan
dcc62ca66d 完成延时播放器 2022-06-19 01:07:07 +08:00
EillesWan
4b53938adf 装逼 2022-06-12 18:38:31 +08:00
EillesWan
8502a02880 小改动 2022-06-12 18:22:07 +08:00
bgArray
80fab8ced0 update resources/msctDevAuthors.txt. 2022-06-12 00:19:36 +00:00
EillesWan
1980aeb8de 浅浅更新一下 2022-06-11 13:53:59 +08:00
CaryXiong
1fde8ca253 小改动 2022-06-11 12:19:15 +08:00
CaryXiong
0ce24bff92 小更新 2022-06-09 16:21:08 +08:00
EillesWan
992572cbf1 更新了一下文件结构 2022-06-08 01:50:14 +08:00
EillesWan
ce1099a246 我再更新一些小东西 2022-06-07 23:49:04 +08:00
EillesWan
2340741bb9 更新便于使用的预览程序 2022-06-07 23:43:14 +08:00
EillesWan
1f94f558c4 Merge 2022-06-07 23:12:42 +08:00
EillesWan
0c50ce1134 Merge 2022-06-07 23:12:06 +08:00
EillesWan
2e0dd06db6 更新延期!详见群内 2022-06-07 23:07:55 +08:00
Eilles Wan
9fd32d499f Update sth. 2022-05-26 18:40:33 +08:00
EillesWan
5c501a3466 更新个屁,Gitee脸都不要了 2022-05-21 00:47:54 +08:00
EillesWan
e872c19676 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-05-21 00:45:17 +08:00
EillesWan
51d248cdc2 更新个屁,Gitee脸都不要了 2022-05-21 00:45:10 +08:00
bgArray
a1dfe65042 add readme2.md. 2022-05-16 13:29:04 +00:00
bgArray
70fc686057 删除文件 readme2.md 2022-05-16 13:27:08 +00:00
bgArray
ba691d177a 删除文件 readme3.md 2022-05-16 13:27:02 +00:00
bgArray
b81860dadd update readme3.md. 2022-05-16 13:26:18 +00:00
bgArray
343a62be47 add readme3.md. 2022-05-16 13:24:32 +00:00
bgArray
ab26c82bf9 add readme2.md. 2022-05-16 13:23:47 +00:00
bgArray
a2ae755e87 删除文件 readme2.md 2022-05-16 13:23:21 +00:00
bgArray
e94af73c4f add readme2.md. 2022-05-16 13:22:00 +00:00
Eilles Wan
bc247e1507 Nothing, at all 2022-05-16 13:10:54 +00:00
Eilles Wan
874cd1aea9 Nothing 2022-05-16 13:09:34 +00:00
bgArray
5a6d874204 删除文件 readme2.md 2022-05-16 13:06:29 +00:00
bgArray
08ffa1a6e2 add readme2.md. 2022-05-16 13:05:51 +00:00
bgArray
16d0ecf009 删除文件 .idea 2022-05-16 13:00:01 +00:00
bgArray
f57f4257fb readme-- 2022-05-16 20:59:36 +08:00
EillesWan
3f0f714a7c Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-05-16 16:02:48 +08:00
EillesWan
65c23ca427 没问题 2022-05-16 16:02:43 +08:00
EillesWan
bc99f8f4be 更新requirement.txt 2022-05-11 23:41:08 +08:00
bgArray
3b6cb3865f 教程 2022-05-11 23:38:26 +08:00
EillesWan
64292a21b3 更新一点点 2022-05-11 21:34:16 +08:00
EillesWan
3ba8fdad8c Merge 2022-05-11 15:23:44 +08:00
EillesWan
7357a09e5f 无主要的修改,即将更换窗口库 2022-05-11 15:19:40 +08:00
EillesWan
cae0b7f23d 有bug 2022-05-08 18:33:28 +08:00
EillesWan
e1d4cd9933 更新自动重置积分、进度条的功能 2022-05-08 01:30:30 +08:00
bgArray
3bc19ac396 update README.md. 2022-05-06 11:59:04 +00:00
EillesWan
3499c05eb4 修复x轴不增加的bug 2022-04-29 23:17:14 +08:00
EillesWan
259fb04980 完成了更改,现在支持bdx导出 2022-04-29 18:45:06 +08:00
EillesWan
8ce7a9fa83 update format 2022-04-29 11:34:41 +08:00
EillesWan
affb5b5404 Merge branch 'pkgver' of https://gitee.com/EillesWan/Musicreater into pkgver 2022-04-29 11:33:52 +08:00
EillesWan
1805ab53c0 新增bdx转换功能 2022-04-29 11:29:49 +08:00
EillesWan
f3c5044800 撤销了disp类,但是bug依旧没有变化。 2022-04-24 17:56:57 +08:00
EillesWan
ca2e8c9155 更新。 2022-04-21 23:52:16 +08:00
bgArray
eda3a957cf 修复已知问题 2022-04-12 18:11:57 +08:00
bgArray
4788d8949b 格式化 2022-04-12 14:15:33 +08:00
EillesWan
1715735800 Merge 2022-04-08 01:29:39 +08:00
EillesWan
d22b5b0c42 我不知道这样有没有用,但是这确实是更新了bdx操作,我不知道bug在哪里 2022-04-08 01:26:35 +08:00
Eilles Wan
549dd3dbf9 删除文件 msctplugin 2022-04-07 16:19:48 +00:00
Eilles Wan
a65bfeba20 删除文件 Musicreater.New.py 2022-04-07 16:19:33 +00:00
EillesWan
7edf979aee 修复bdx生成不了的问题 2022-04-07 18:07:32 +08:00
EillesWan
8f6cc04780 重设样例代码 2022-04-07 08:27:43 +08:00
EillesWan
6cb5c9ba89 提交包模式的更新 2022-04-05 23:43:03 +08:00
EillesWan
ae83f9c21e 更新了内部数据格式文档,修复部分bug 2022-04-05 00:55:43 +08:00
EillesWan
dd34ac9998 紧急更新,修复了无法将整首歌转换bdx的bug,其余bug正在发现。 2022-04-03 21:38:17 +08:00
EillesWan
2da06136c5 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-04-03 00:51:39 +08:00
EillesWan
e9f4230a2d 金羿生日快乐~ 2022-04-03 00:51:34 +08:00
bgArray
1b7ab73c02 update README_EN.md. 2022-04-01 07:54:34 +00:00
EillesWan
2624c081bb 生活不易,改README出气 2022-04-01 15:09:03 +08:00
EillesWan
87cc189da8 修正单词拼写错误 2022-04-01 15:07:22 +08:00
Eilles Wan
668bf480b2 重命名 README_en.md 为 README_EN.md 2022-04-01 06:42:30 +00:00
EillesWan
4cc3f2678f 没事?没事就改README! 2022-04-01 14:38:50 +08:00
EillesWan
701e9d5129 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-04-01 14:23:08 +08:00
EillesWan
8dd2694e82 左改改右改改,就是不改代码/qwq 2022-04-01 14:23:03 +08:00
bgArray
ac962f0ab7 update README.md. 2022-04-01 05:50:48 +00:00
EillesWan
b800384547 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-04-01 12:48:41 +08:00
EillesWan
5da1041664 喵喵喵? 2022-04-01 12:48:35 +08:00
Eilles Wan
966ce515c3 update README.md. 2022-04-01 04:22:57 +00:00
EillesWan
349c255f5f LOGO 2022-04-01 11:53:43 +08:00
bgArray
cbb77086d0 update README.md. 2022-04-01 03:17:14 +00:00
bgArray
280e50f4cb update README.md. 2022-04-01 02:55:58 +00:00
bgArray
a2a37be8ef update README.md. 2022-04-01 02:33:10 +00:00
Eilles Wan
7acd6b7e38 update resources/myWords.txt. 移除上一次更改 2022-03-31 01:23:11 +00:00
bgArray
4c95f76cf9 update 道法重大更新 2022-03-30 03:01:08 +00:00
Eilles Wan
8a1a159eb8 update README.md. 2022-03-29 08:51:53 +00:00
Eilles Wan
cb43caef26 update README.md. 2022-03-29 08:51:29 +00:00
Eilles Wan
200167ef84 update README.md. 2022-03-29 08:51:23 +00:00
EillesWan
7e0a127406 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-03-29 16:46:40 +08:00
EillesWan
5e70dd73a1 重大更新,我新增了很多名言名句!!! 2022-03-29 16:46:31 +08:00
bgArray
7229b12e99 删除文件 languages/log 2022-03-28 15:13:37 +00:00
bgArray
074124bc3d 删除文件 .idea 2022-03-28 15:13:09 +00:00
bgArray
91446bfd81 翻译保存器 2022-03-28 23:12:24 +08:00
EillesWan
5011efcdee Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-03-28 20:42:12 +08:00
EillesWan
3b4af19379 暂存更改以便pull 2022-03-28 20:40:17 +08:00
EillesWan
d3d89d2567 增强对于Linux的支持 2022-03-28 20:29:36 +08:00
EillesWan
441d1e9982 我把旧的MMFM的日志一起加入进来,作为历史的参考。 2022-03-28 14:04:30 +08:00
EillesWan
77e738cb7e 修改读我档 2022-03-28 12:33:35 +08:00
EillesWan
46b6884e64 Merge branch 'master' of https://gitee.com/EillesWan/Musicreater 2022-03-26 21:01:29 +08:00
EillesWan
d0a63d0f8e 为了pull,暂存更改 2022-03-26 21:01:23 +08:00
204 changed files with 17952 additions and 8114 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
*.yaml linguist-language=Python
*.xml linguist-language=Python
*.md linguist-language=Python

175
.gitignore vendored Normal file
View File

@@ -0,0 +1,175 @@
# sth. can't open
/msctPkgver/secrets/*.py
/msctPkgver/secrets/*.c
/fool/
# mystuff
/*.zip
/.vscode
/*.mid
/*.midi
/*.mcpack
/*.bdx
/*.msq
/*.fsq
/*.json
/*.mcstructure
.mscbackup
/logs
/languages
/llc_cli.py
/utils
test.py
RES.txt
/MSCT_Packer.py
/Packer/*.MPK
/Packer/checksum.txt
/bgArrayLib
/fcwslib
test_lyric-mido.py
# Byte-compiled / optimized
__pycache__/
*.pyc
*$py.class
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
.pdm-build/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# Pycharm
/.idea
# log
/.log
# package
.7z

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.10

View File

@@ -1,221 +0,0 @@
# -*- coding: utf-8 -*-
# W-YI 金羿
# QQ 2647547478
# 音·创 开发交流群 861684859
# Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
# 版权所有 Team-Ryoun 金羿("Eilles Wan")
# 若需转载或借鉴 请附作者
"""
音·创自动安装器 (Musicreater Auto Installer)
对音·创的自动安装提供支持的独立软件
Musicreater Auto Installer (音·创自动安装器)
A software that used for installing Musicreater automatically
Copyright 2022 Team-Ryoun
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# 代码写的并非十分的漂亮还请大佬多多包涵本软件源代码依照Apache软件协议公开
# 下面为正文
from sys import platform
from platform import architecture
import urllib.request
import zipfile
from os import system as srun
from os import walk, rename, remove, path, chdir, listdir
from shutil import rmtree, move
if platform == "win32":
nowpath = __file__[: len(__file__) - __file__[len(__file__) :: -1].index('\\')]
if srun('python -V'):
print('\033[7m{}\033[0m'.format("正在下载python\nDownloading Python"))
try:
urllib.request.urlretrieve(
"https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe"
if architecture()[0] == "32bit"
else "https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe",
"./pythonInstaller.exe",
)
# urllib.request.urlretrieve("https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe","./pythonInstaller.exe")
except Exception as E:
input(str(E) + "\n自动下载失败,按下回车取消")
exit()
print('正在安装python\nInstalling Python')
# open('install.bat','w').write(f'.\\pythonInstaller.exe /passive InstallAllUsers=0 TargetDir="{nowpath}python38" DefaultJustForMeTargetDir="{nowpath}python38" AssociateFiles=0 CompileAll=1 PrependPath=0 Shortcuts=0 Include_doc=0 Include_launcher=0 InstallLauncherAllUsers=0 Include_test=0 Include_tools=0')
srun(
f'.\\pythonInstaller.exe /passive InstallAllUsers=1 AssociateFiles=1 CompileAll=1 PrependPath=1 Shortcuts=1 Include_doc=0 Include_exe=1 Include_pip=1 Include_lib=1 Include_tcltk=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_tools=0'
)
remove('./pythonInstaller.exe')
# print('\033[7m{}\033[0m'.format("正在下载pip安装工具\nDownloading get-pip tool"))
# try:
# urllib.request.urlretrieve(
# "https://bootstrap.pypa.io/get-pip.py", "./python38/get-pip.py"
# )
# except Exception as E:
# input(str(E) + "\n自动下载失败按下回车取消")
# exit()
# print('\033[7m{}\033[0m'.format("正在下载pip\nDownloading pip"))
# chdir('./python38')
# srun(r'".\python.exe get-pip.py')
# print('\033[7m{}\033[0m'.format('正在安装pip\nInstalling pip'))
# for dire in listdir('./Lib/site-packages/'):
# move('./Lib/site-packages/'+dire,'./'+dire)
# print('\033[7m{}\033[0m'.format("完成!"))
# chdir('../')
try:
choseurl = int(
input(
'\033[7m{}\033[0m'.format(
"""请选择 音·创 下载源,默认为0
Please choose a download source of Musicreater(default 0)
[0] 私有服务器<暂无> | Private Server<Haven't been built>
[1] Gitee
[2] Github\n:"""
)
)
)
except Exception as E:
print('\033[7m{}\033[0m'.format(str(E) + "\n将使用默认源\nUsing default source"))
choseurl = 0
myurl = ""
Giteeurl = "https://gitee.com/EillesWan/Musicreater/repository/blazearchive/master.zip?Expires=1647771436&Signature=%2BkqLHwmvzScCd4cPQDP0LHLpqeZUxOrOv17QpRy%2FTzs%3D"
Githuburl = (
"https://codeload.github.com/EillesWan/Musicreater/zip/refs/heads/master"
)
url = (
myurl
if choseurl == 0
else Giteeurl
if choseurl == 1
else Githuburl
if choseurl == 2
else myurl
)
print('\033[7m{}\033[0m'.format("正在下载音·创\nDownloading Musicreater"))
try:
urllib.request.urlretrieve(url, "./master.zip")
except Exception as E:
input('\033[0{}\033[0m'.format(str(E) + "\n自动下载失败,按下回车取消"))
exit()
print('\033[7m{}\033[0m'.format("安装音·创\nInstalling Musicreater"))
zipfile.ZipFile("./master.zip", "r").extractall()
remove("./master.zip")
try:
rmtree("./Musicreater")
except:
pass
rename("./Musicreater-master/", "./Musicreater/")
elif platform == 'linux':
srun("sudo apt-get install python3")
srun("sudo apt-get install python3-pip")
srun("sudo apt-get install git")
try:
choseurl = int(
input(
'\033[0{}\033[0m'.format(
"""请选择 音·创 下载源,默认为1
Please choose a download source of Musicreater(default 1)
[1] Gitee
[2] Github\n:"""
)
)
)
except Exception as E:
print(str(E) + "\n将使用默认源\nUsing default source")
choseurl = 1
url = (
"https://gitee.com/EillesWan/Musicreater.git"
if choseurl == 1
else "https://github.com/EillesWan/Musicreater.git"
if choseurl == 2
else "https://gitee.com/EillesWan/Musicreater.git"
)
srun(f"sudo git clone {url}")
print('\033[7m{}\033[0m'.format("编译音·创\nCompiling Musicreater"))
if platform == "linux":
srun("python3 -O -m compileall -b ./Musicreater/")
elif platform == "win32":
srun("python -O -m compileall -b ./Musicreater/")
for parent, dirnames, filenames in walk("./Musicreater"):
for filename in filenames:
if filename[-3:] == ".py":
fn = path.join(parent, filename)
remove(fn)
print(f"删除文件 {fn}")
for dirname in dirnames:
if dirname == "__pycache__":
pn = path.join(parent, dirname)
rmtree(pn)
print(f"删除目录 {pn}")
print(
'\033[7m{}\033[0m'.format(
"""您可以开始使用音·创了
我们将在后台为您安装各项支持库
您可以运行Musicreater文件夹中的Musicreater.pyc文件来运行音·创
You can use Musicreater now,
We will setup the libraries ineed for you in background,
You can now open Musicreater.PYC in the directory of ./Musicreater to run Musicreater
"""
)
)
if platform == "linux":
srun("python3 ./Musicreater/补全库.pyc")
elif platform == "win32":
srun("python ./Musicreater/补全库.pyc")

192
LICENSE
View File

@@ -1,192 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2022 Team-Ryoun 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray")
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

123
LICENSE.md Normal file
View File

@@ -0,0 +1,123 @@
# 汉钰律许可协议,第一版
**总第一版 第二次修订 · 二〇二四年七月七日编 二〇二五年四月二十六日修订**
## 一、重要须知
1. 为保护采用本协议的作品在开源过程中其著作权人所应有的权益根据**中华人民共和国著作权法和相关法律法规**制定本协议
2. 本协议履行过程中请注意本协议中**免除或限制**民事主体**责任或权利**的条款法律适用和争议解决条款尤其是加有特殊标记的条款这些条款应在中国法律所允许的范围内最大程度地适用
3. 若本协议所涉及的自然人**未满 18 周岁**该自然人应在监护人的陪同下阅读本协议及有关本协议的条款内容并在取得其监护人同意后开始或继续应用本协议所授权的行为
4. 由于互联网服务互联网内容的特殊性若本协议以电子协议形式分发并签订其依然有效您一旦开始对本协议所授权之作品进行本协议所授权的行为即视为您已经阅读理解并同意并已经接受本协议的全部条款
5. 本协议的订立履行解释及争议的解决均**适用中华人民共和国法律并排除其他一切冲突法的适用**_本协议订立于许可证最初的颁发者的地址若颁发者为自然人则订立于该自然人户籍所在地若为法人或非法人组织则订立于其注册地_本协议的订立各方应友好协商解决于协议所规定之行为的履行相关的争议如协商不成任何一方均可向合同签订地有管辖权的人民法院提起诉讼
6. 本协议的原本仅为现代汉语书写于简体中文若存在其他语言的翻译或其他同等语言但非简体中文文本的版本应当无法律效力
## 二、术语定义
1. **许可证****协议**后文称本协议是指根据本文档中所列举的全部术语定义条款限制等文本是本合同的简称称谓本合同全称是 **汉钰律许可协议第一版**
2. **协议颁发者**后文称颁发者是将条款或协议应用于其拥有著作财产权的作品的民事主体或由其指定从而拥有颁发者身份的民事主体
3. ****形式是指对包括但不限于 软件硬件文档配置项 等种类的作品进行修改编辑的首选形式若不存在首选形式则初次编辑该作品所需的形式即为源形式
4. **目标**形式是指对源形式进行机械转换翻译打印制造加工等同类型活动后形成的结果形式包括但不限于源代码编译后的目标软件生成的文件转换出的媒体制造出的机械打印出的实体文本加工后的零件
5. **采用本协议的作品**后文称此作品是指经颁发者授权而使用本协议进行授权的任何作品该作品应在自然人可见处明确附加一个自然人可读的版权通知可以参考文末附录中提供的示例若在一个可分割的作品中部分地采用本协议进行授权则该部分应当视为一个独立的采用本协议的作品该作品应当在自然人可见处明确附加一个自然人可读的范围限定和版权通知同样可以参考文末附录中提供的示例
6. **贡献**是指对作品进行的意在提交给此作品颁发者以让著作权人包含在其作品中的任何修订或补充该修订或补充同样属于一种作品依据此定义**提交**一词表示经由此作品颁发者所指定的形式将其所进行的修改发送给此作品颁发者该形式应当包括在此作品颁发者指定的平台内发送易于编辑的修改信息在此作品颁发者指定的电子邮箱中发送易于编辑的修改信息在此作品颁发者指定的源码控制系统或发布跟踪系统上提交的易于编辑的修改信息但由著作权人以明显标注或指定为非贡献的活动除外颁发者自己对作品进行的修改同样视作对作品的贡献
7. **贡献者**是指此作品颁发者接受的贡献的提交者或包含在作品的贡献清单中的民事主体贡献者在提交贡献并经此作品颁发者通过且该贡献已经被应用于此作品中后该贡献者应当视为此作品的著作权人之一但不应视为此作品非其贡献的部分的著作权人一个作品的颁发者同样属于其贡献者**请注意**针对贡献者提交的贡献该贡献者应被视为该贡献的协议颁发者但不应视作本作品的颁发者
8. **用户****使用者**是指行使本协议所授权之行为的民事主体据此贡献者亦属于用户
9. **商业性使用****商用**是指任何以谋取利益为目的的使用包括但不限于以贩卖出租的形式对作品进行使用但若将该获取利益之活动明确指示为捐赠且在获利者在进行本协议所授权的活动时不以捐赠数额为标准而区别之则此种的获取利益的捐赠行为不属于商业性使用
## 三、权利授予
1. 任何由颁发者所进行的特殊声明特别注意等此类内容应当在法律效力上高于本协议的条款或声明这些声明若与本协议冲突本协议的该冲突部分无效本协议与这些声明共同构成颁发者与用户之间的合同
2. 此作品的贡献者享有其贡献的完整著作权
3. 此作品的贡献者将自己的贡献的全部著作财产权免费公开不可撤销无限期非专有地授予此作品的全部著作权人并准许其在全世界范围内使用上述权利若无明确的标识贡献者允许此作品的颁发者对其贡献进行免费公开不可撤销无限期非专有世界范围内的商业性使用
4. 此作品的著作权人及贡献者授予用户**免费公开不可撤销非专有非商用**地以任意形式**复制发行展览表演放映广播信息网络传播摄制改编翻译汇编二次授权**的权利准许其在此作品颁发者所指定的区域与时间内行使上述权利若此作品颁发者未特别指定的则视作在全世界范围内无限期地授权若此作品颁发者特别指定在特定情况下可以商用则应当按照其所指定的条件进行商业性使用商用的过程中应当明确标识此作品的著作权人
5. 一旦此作品有任意由非贡献形式而产生的更改更改的部分将不视为此作品的一部分除非该部分不可离开此作品单独存在若该部分必须依赖此作品而不可与此作品分离从而单独存在则更改后的作品不视作此作品在这种情况下除非此更改后的作品已获得此作品颁发者的特殊许可或更改者即为此作品颁发者本人否则对该作品进行的任何活动都应当遵守本协议
6. 经贡献而产生的对此作品的更改属于此作品的一部分在此情况下更改后的作品依旧视作此作品
7. 依据本款的第 4 若用户在本协议的授权下将此作品授予他人进行任何形式的活动二次授权二次分发则应确保其使用的协议或授权内容与本协议的条款不冲突当存在与本协议条款的冲突时则该冲突内容无效被授权的第三方应依照本协议的条款进行活动除非该用户获得了此作品颁发者的特殊许可或该用户即为此作品颁发者本人
8. 依据本款的第 5 若由非贡献形式而产生更改的部分是可分割而不需依赖此作品即可单独存在的若该部分明确注明不使用本协议进行授权或明确声明了其他授权条款则该部分不视作采用本协议但未更改的部分仍应视作原此作品的一部分需要采用本协议进行授权除非此更改后的作品已获得此作品颁发者的特殊许可或更改者即为此作品颁发者本人
9. 若此作品或所提交的贡献包含其著作权人的专利则该专利所有人即此作品的著作权人应准许此作品全体著作权人**免费公开不可撤销非专有无版权费的专利许可**以便贡献者对作品进行本协议所授权进行的活动
10. 上述专利许可的授予仅适用于在所提交的贡献中可由专利所有者授予的且在对此作品进行本协议所授权的活动中必须使用的专利
11. 如果用户对任何民事主体因其在进行本协议所授权进行的活动中侵犯该用户的专利而提起诉讼那么根据本协议授予该用户的所有关于此作品的任何其他专利许可将在提起上述诉讼之日起终止
12. 如果本作品作为用户的其他作品的不可分割的一部分进行任何民事活动本协议依旧对本作品即该用户的其他作品的一部分生效若本作品完全融入该用户的其他作品之中而不可独立存在则该用户需要保证其作品存在与本协议冲突的条款除非该作品已获得此作品颁发者的特殊许可或该用户即为此作品颁发者本人
## 四、使用条件
在对此作品进行本协议所授权的民事活动中应当同时满足以下条款
1. 用户必须为此作品的任何其他接收者提供本协议的副本在不得已无法提供副本的情况下也应明确指示其他接收者可查阅本协议的位置
2. 用户必须在修改后的作品中附带明显的通知声明用户已更改文件并注明更改位置
3. 若用户二次分发此作品可以选择向此作品的接收者提供无偿或有偿的担保维修支持服务或其他责任义务但是该用户只可以其自己的名义提供上述内容不得以任何其他贡献者的名义且该用户必须明确表明任何此类责任或义务是由其个人独立提供且其同意并应当承担赔偿此作品的全体贡献者因其个人承担上述责任义务而产生的任何赔偿责任
4. 用户不得删除或更改此作品中包含的任何许可声明包括版权声明专利声明免责声明或赔偿责任限制除非该更改是对已知事实错误的修补或其已获得此作品颁发者的特殊许可或更改者即为此作品颁发者本人
5. 若此作品将权益的声明通知作为一部分那么由用户分发的任何版本的作品中须至少在下列三处之一包含该声明通知的自然人可读副本
- 该作品的权益声明通知中
- 在源形式的文件中当且仅当该作品开放源代码
- 在惯例中作为第三方通知出现之处当且仅当该作品会产生画面且该画面可被自然人详细观察
该通知的内容仅供信息提供不应对许可证进行任何文字上的修改用户可在其分发的作品中在不构成修改本协议的前提下在作品自身的声明通知或属性描述后或作为附录添加
6. 依据本款第3条若用户二次分发此作品时选择向作品的接收者提供收费的担保服务则必须明确告知该接收者本协议全部内容与此作品原出处并确保其知悉上述内容但若用户在二次分发此作品时不选择提供任何服务则该用户不允许向作品的接收者收取任何费用除非该用户获得了此作品颁发者的特殊许可或该用户即为此作品颁发者本人
## 五、提交贡献
除非贡献者明确声明在本作品中由该贡献者向颁发者的提供的提交必须符合本协议的条款并与本协议的条款不存在冲突除非此贡献中与本协议冲突的附加条款已获得颁发者的特殊许可或贡献者即为此作品颁发者本人
## 六、商标相关
本协议并未授予用户将颁发者的商标专属标记或特定产品名称用于合理的或惯例性的描述或此类声明之外其他任何位置的权利
## 七、免责声明
1. 若非因法律要求或经过了特殊准许此作品在根据本协议原样提供的基础上**不予提供任何形式的担保任何明示任何暗示或类似承诺**此类包括但不限于担保此作品毫无缺陷担保此作品适于贩卖担保此作品适于特定目的担保使用此作品绝不侵权用户将自行承担因此作品的质量或性能问题而产生的全部风险若此作品在任何方面欠妥将由用户而非任何贡献者而非任何颁发者承担所有必要的服务维修或除错的任何成本本免责声明是本许可的重要组成部分当且仅当遵守本免责声明时本协议的其他条款中对本作品的使用授权方可生效
2. 无论是因何种原因如果不是在法律规定的特殊情况确为贡献者的故意或重大过失下或者经过了特殊准许即使贡献者事先已知发生损害的可能在使用本作品时用户产生的任何直接间接特殊偶然或必然造成的损失包括但不限于商誉损失工作延误计算机系统故障等**均不由任一贡献者承担**
**以上是本许可协议的全部条款**
---
附录
**如何在自己的作品中应用 汉钰律许可协议**
若要在自己源形式的作品应用本协议请在其中附加下面的通知模板并将六角括号中的字段替换成自身的实际信息来替换不包括括号本身这些文本必须以对应文件格式适当的注释句法包含在其中可以是实体的纸质文档也可以是网络公告或者计算机文件或者脱离该源之外另起一个新的文件使之指向要应用本协议的那个作品同时也建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的页面上作为版权通知的整体这样更加容易的区分出第三方内容
若需要在自己以目标形式存在的作品中应用本协议同样需要附加下面的通知模板并更改六角括号中的字样但是这些文本可以是位于作品的标签上位于作品的用户可见且能被自然人详细观察的画面之中或者按照惯例中许可协议应该出现的位置同时这些文本的所处位置应当能够明确指示到本协议应用的那个作品另外建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的位置上作为版权通知的整体这样更加容易的区分出第三方内容
**通知模板**
```
版权所有 © 年份 著作权人
或者版权所有 (C) 年份 著作权人
该作品根据 汉钰律许可协议第一版本协议授权
任何人皆可从以下地址获得本协议副本本协议副本所在地址
若非因法律要求或经过了特殊准许此作品在根据本协议原样提供的基础上不予提供任何形式的担保任何明示任何暗示或类似承诺也就是说用户将自行承担因此作品的质量或性能问题而产生的全部风险
详细的准许和限制条款请见原协议文本
```

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# W-YI 金羿
# QQ 2647547478
# 音·创 开发交流群 861684859
# Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
# 版权所有 Team-Ryoun 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray")
# 若需转载或借鉴 请附作者
"""
音·创 (Musicreater)
一款免费开源的 《我的世界:基岩版》 音乐制作软件
Musicreater (音·创)
A free opensource software which is used for creating all kinds of musics in Minecraft
Copyright 2022 Team-Ryoun
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# 代码写的并非十分的漂亮还请大佬多多包涵本软件源代码依照Apache软件协议公开
# 下面为正文
from msctspt.bugReporter import version
__ver__ = f'{version.version[1]} {version.version[0]}'
__author__ = '金羿Eilles'
from msctLib.display import *
from msctLib.function import *
from msctLib.data import uniteIO
def __main__():
disp()
if __name__ == '__main__':
__main__()

File diff suppressed because it is too large Load Diff

71
Musicreater/__init__.py Normal file
View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""
音·创
是一款免费开源的《我的世界》数字音频支持库。
Musicreater (音·创)
A free and open-source library for handling with **Minecraft** digital music.
版权所有 © 2026 睿乐组织
Copyright © 2026 TriM-Organization
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
任何人皆可从以下地址获得本协议副本:
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
详细的准许和限制条款请见原协议文本。
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__version__ = "3.0.0-alpha"
__author__ = (
("金羿", "Eilles"),
("玉衡Alioth", "YuhengAlioth"),
("鱼旧梦", "ElapsingDreams"),
("偷吃不是Touch", "Touch"),
)
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
from .data import (
SingleMusic,
SingleTrack,
SingleNote,
SoundAtmos,
MineNote,
CurvableParam,
)
from .plugins import load_plugin_module
from .main import MusiCreater
__all__ = [
"__version__",
"__author__",
# 参数曲线相关
"ParamCurve",
"InterpolationMethod",
"BoundaryBehaviour",
# 音乐数据结构
"SingleMusic",
"SingleTrack",
"SingleNote",
"SoundAtmos",
"MineNote",
"CurvableParam",
# 工程项目相关
"load_plugin_module",
"MusiCreater",
]

632
Musicreater/_plugin_abc.py Normal file
View File

@@ -0,0 +1,632 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 的插件基类,提供抽象接口以供实际插件使用
"""
"""
版权所有 © 2026 金羿
Copyright © 2026 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# =====================
# NOTE: [WARNING]
# 这个文件是一坨屎山代码
# 请勿模仿,请多包容
# =====================
import sys
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import (
Dict,
Any,
Optional,
List,
Tuple,
Union,
Sequence,
BinaryIO,
Generator,
Iterator,
Set,
)
if sys.version_info >= (3, 11):
import tomllib
import tomli_w
else:
import tomli as tomllib # 第三方包
import tomli_w
from .exceptions import (
PluginConfigDumpError,
PluginConfigLoadError,
PluginMetainfoNotFoundError,
PluginMetainfoTypeError,
PluginMetainfoValueError,
PluginAttributeNotFoundError,
ParameterTypeError,
PluginInstanceNotFoundError,
)
from .data import SingleMusic, SingleTrack
# 已经全部由 plugins.py 提供接口
# 请用户从 plugins.py 导入
# 不要在这里导,会坏掉的
# __all__ = [
# # 枚举类
# "PluginType",
# # 抽象基类/数据类(插件参数定义)
# "PluginConfig",
# "PluginMetaInformation",
# # 抽象基类(插件定义)
# "MusicInputPlugin",
# "TrackInputPlugin",
# "MusicOperatePlugin",
# "TrackOperatePlugin",
# "MusicOutputPlugin",
# "TrackOutputPlugin",
# "ServicePlugin",
# "LibraryPlugin",
# # 插件注册用装饰函数
# "music_input_plugin",
# "track_input_plugin",
# "music_operate_plugin",
# "track_operate_plugin",
# "music_output_plugin",
# "track_output_plugin",
# "service_plugin",
# "library_plugin",
# ]
# ========================
# 枚举类
# ========================
class PluginTypes(str, Enum):
"""插件类型枚举"""
FUNCTION_MUSIC_IMPORT = "import_music_data"
FUNCTION_TRACK_IMPORT = "import_track_data"
FUNCTION_MUSIC_OPERATE = "music_data_operating"
FUNCTION_TRACK_OPERATE = "track_data_operating"
FUNCTION_MUSIC_EXPORT = "export_music_data"
FUNCTION_TRACK_EXPORT = "export_track_data"
SERVICE = "service"
LIBRARY = "library"
# ========================
# 数据类
# ========================
@dataclass
class PluginConfig(ABC):
"""插件配置基类"""
def to_dict(self) -> Dict[str, Any]:
"""将配置内容转换为字典
返回
====
Dict[str, Any]
配置项的字典表示,不包含以下划线开头的私有属性
"""
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig":
"""从字典创建配置实例
参数
====
data: Dict[str, Any]
包含配置字段的字典
返回
====
PluginConfig
配置类的实例
"""
# 只保留类中定义的字段
field_names = {f.name for f in cls.__dataclass_fields__.values()}
filtered_data = {k: v for k, v in data.items() if k in field_names}
return cls(**filtered_data)
def save_to_file(self, file_path: Path) -> None:
"""保存配置到 TOML 文件
参数
====
file_path: Path
目标文件路径;必须以 .toml 为后缀
异常
====
PluginConfigDumpError
当文件后缀不是 .toml 或写入失败时抛出
"""
if file_path.suffix.upper() == ".TOML":
file_path.parent.mkdir(parents=True, exist_ok=True)
else:
raise PluginConfigDumpError(
"插件配置文件类型不应为`{}`,须为`TOML`格式。".format(file_path.suffix)
)
try:
with file_path.open("wb") as f:
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
except Exception as e:
raise PluginConfigDumpError(e)
@classmethod
def load_from_file(cls, file_path: Path) -> "PluginConfig":
"""从 TOML 文件加载配置
参数
====
file_path: Path
源文件路径
返回
====
PluginConfig
加载后的配置实例
异常
====
PluginConfigLoadError
当读取或解析失败时抛出
"""
try:
with file_path.open("rb") as f:
return cls.from_dict(tomllib.load(f))
except Exception as e:
raise PluginConfigLoadError(e)
@dataclass
class PluginMetaInformation(ABC):
"""插件元信息"""
name: str
"""插件名称,应为惟一之名"""
author: str
"""插件作者"""
description: str
"""插件简介"""
version: Tuple[int, ...]
"""插件版本号"""
type: PluginTypes
"""插件类型"""
license: str = "MIT License"
"""插件发布时采用的许可协议"""
dependencies: Sequence[str] = tuple()
"""插件是否对其他插件存在依赖"""
# ========================
# 抽象基类
# ========================
class TopPluginBase(ABC):
"""所有插件的抽象基类"""
metainfo: PluginMetaInformation
"""插件元信息"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if hasattr(cls, "metainfo"):
if not isinstance(cls.metainfo, PluginMetaInformation):
raise PluginMetainfoTypeError(
"类`{cls_name}`之属性`metainfo`的类型,必须为`PluginMetaInformation`".format(
cls_name=cls.__name__
)
)
else:
if not cls.__name__.endswith("PluginBase"):
raise PluginMetainfoNotFoundError(
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
cls_name=cls.__name__
)
)
class TopInOutPluginBase(TopPluginBase, ABC):
"""导入导出用抽象基类"""
supported_formats: Tuple[str, ...] = tuple()
"""支持的格式(定义后会自动转大写)"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if hasattr(cls, "supported_formats"):
if cls.supported_formats:
# 强制转换为大写,并使用元组
cls.supported_formats = tuple(map(str.upper, cls.supported_formats))
else:
cls.supported_formats = tuple()
else:
raise PluginAttributeNotFoundError(
"用于导入导出数据的类`{cls_name}`必须定义一个`supported_formats`属性。".format(
cls_name=cls.__name__
)
)
def can_handle_file(self, file_path: Path) -> bool:
"""判断是否可处理某个文件
参数
====
file_path: Path
待检测的文件路径
返回
====
bool
若文件后缀已在本类中定义,则返回 True
"""
return file_path.suffix.upper().endswith(self.supported_formats)
def can_handle_format(self, format_name: str) -> bool:
"""判断是否可处理某个格式
参数
====
format_name: str
格式名称(如 'MIDI', 'WAV'
返回
====
bool
若格式名本类中已经定义,则返回 True
"""
return format_name.upper().endswith(self.supported_formats)
class MusicInputPluginBase(TopInOutPluginBase, ABC):
"""导入用插件抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_IMPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def loadbytes(
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
) -> "SingleMusic":
"""从字节流加载数据到完整曲目
参数
====
bytes_buffer_in: BinaryIO
输入的二进制字节流
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
SingleMusic
解析得到的完整曲目对象
"""
pass
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
"""从文件加载数据到完整曲目
参数
====
file_path: Path
输入文件路径
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
SingleMusic
解析得到的完整曲目对象
"""
with file_path.open("rb") as f:
return self.loadbytes(f, config)
class TrackInputPluginBase(TopInOutPluginBase, ABC):
"""导入用插件抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_IMPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def loadbytes(
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
) -> "SingleTrack":
"""从字节流加载音符数据到单个音轨
参数
====
bytes_buffer_in: BinaryIO
输入的二进制字节流
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
SingleTrack
解析得到的单个音轨对象
"""
pass
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
"""从文件加载音符数据到单个音轨
参数
====
file_path: Path
输入文件路径
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
SingleTrack
解析得到的单个音轨对象
"""
with file_path.open("rb") as f:
return self.loadbytes(f, config)
class MusicOperatePluginBase(TopPluginBase, ABC):
"""音乐处理用插件抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_OPERATE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def process(
self, data: "SingleMusic", config: Optional[PluginConfig]
) -> "SingleMusic":
"""处理完整曲目的数据
参数
====
data: SingleMusic
待处理的完整曲目
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
SingleMusic
处理后的完整曲目
"""
pass
class TrackOperatePluginBase(TopPluginBase, ABC):
"""音乐处理用插件抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_OPERATE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def process(
self, data: "SingleTrack", config: Optional[PluginConfig]
) -> "SingleTrack":
"""处理单个音轨的音符数据
参数
====
data: SingleTrack
待处理的单个音轨
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
SingleTrack
处理后的单个音轨
"""
pass
class MusicOutputPluginBase(TopInOutPluginBase, ABC):
"""导出用插件的抽象基类-完整曲目"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_EXPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def dumpbytes(
self, data: "SingleMusic", config: Optional[PluginConfig]
) -> BinaryIO:
"""将完整曲目导出为对应格式的字节流
参数
====
data: SingleMusic
待导出的完整曲目
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
BinaryIO
导出后的二进制字节流
"""
pass
@abstractmethod
def dump(
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
):
"""将完整曲目导出为对应格式的文件
参数
====
data: SingleMusic
待导出的完整曲目
file_path: Path
输出文件路径
config: Optional[PluginConfig]
插件配置;**可选**
"""
pass
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
"""导出用插件的抽象基类-单个音轨"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_EXPORT:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def dumpbytes(
self, data: "SingleTrack", config: Optional[PluginConfig]
) -> BinaryIO:
"""将单个音轨导出为对应格式的字节流
参数
====
data: SingleTrack
待导出的单个音轨
config: Optional[PluginConfig]
插件配置;**可选**
返回
====
BinaryIO
导出后的二进制字节流
"""
pass
@abstractmethod
def dump(
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
):
"""将单个音轨导出为对应格式的文件
参数
====
data: SingleTrack
待导出的单个音轨
file_path: Path
输出文件路径
config: Optional[PluginConfig]
插件配置;**可选**
"""
pass
class ServicePluginBase(TopPluginBase, ABC):
"""服务插件抽象基类"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.SERVICE:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
@abstractmethod
def serve(self, config: Optional[PluginConfig], *args) -> None:
"""服务插件的运行逻辑
参数
====
config: Optional[PluginConfig]
插件配置;**可选**
*args: Any
其他运行时参数
"""
pass
class LibraryPluginBase(TopPluginBase, ABC):
"""插件依赖库的抽象基类"""
def __init_subclass__(cls) -> None:
super().__init_subclass__()
if cls.metainfo.type != PluginTypes.LIBRARY:
raise PluginMetainfoValueError(
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
cls_name=cls.__name__,
cls_type=cls.metainfo.type.name,
)
)
# 怎么?
# 插件的彼此依赖就不需要什么调用了吧

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""
音·创 v3 内置的 Midi 读取插件
"""
"""
版权所有 © 2026 金羿、玉衡Alioth
Copyright © 2026 Eilles, YuhengAlioth
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import mido
from pathlib import Path
from typing import BinaryIO, Optional
from Musicreater import SingleMusic
from Musicreater.plugins import (
music_input_plugin,
PluginConfig,
PluginMetaInformation,
PluginTypes,
MusicInputPluginBase,
)
@music_input_plugin("midi_2_music_plugin")
class MidiImport2MusicPlugin(MusicInputPluginBase):
"""Midi 音乐数据导入插件"""
metainfo = PluginMetaInformation(
name="Midi 导入插件",
author="金羿、玉衡Alioth",
description="从 Midi 文件导入音乐数据",
version=(0, 0, 1),
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
license="Same as Musicreater",
)
supported_formats = ("MID", "MIDI")
def loadbytes(
self, bytes_buffer_in: BinaryIO, config: PluginConfig | None
) -> SingleMusic:
midi_file = mido.MidiFile(file=bytes_buffer_in)
return SingleMusic() # =========================== TODO: 等待制作
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
"""从 Midi 文件导入音乐数据"""
midi_file = mido.MidiFile(filename=file_path)
return SingleMusic() # =========================== TODO: 等待制作

1461
Musicreater/constants.py Normal file

File diff suppressed because it is too large Load Diff

786
Musicreater/data.py Normal file
View File

@@ -0,0 +1,786 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 的内部数据类
"""
"""
版权所有 © 2026 金羿
Copyright © 2026 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# “
# 把代码 洒落在这里
# 和音符 留下的沙砾
# 一点一点爬进你类定义的缝隙
# ” —— 乐曲访问 by resnah
import heapq
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
from dataclasses import dataclass
from typing import (
Optional,
Any,
List,
Tuple,
Union,
Dict,
Set,
Sequence,
Callable,
Generator,
Iterable,
Iterator,
Literal,
Hashable,
TypeVar,
)
from enum import Enum
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
from .paramcurve import ParamCurve
T = TypeVar("T")
class SoundAtmos:
"""声源方位类"""
sound_distance: float
"""声源距离 方块"""
sound_azimuth: Tuple[float, float]
"""声源方位 角度(rV左右 rH上下)"""
def __init__(
self,
distance: Optional[float] = None,
azimuth: Optional[Tuple[float, float]] = None,
) -> None:
"""
定义一个发声方位
Parameters
------------
distance: float
发声源距离玩家的距离(半径 `r`
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
azimuth: tuple[float, float]
声源方位
此参数为tuple包含两个元素分别表示
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
"""
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
"""声源方位"""
# 如果指定为零,那么为零,但如果不指定或者指定为负数,则为 0.01 的距离
self.sound_distance = (
(16 if distance > 16 else (distance if distance >= 0 else 0.01))
if distance is not None
else 0.01
)
"""声源距离"""
@classmethod
def from_displacement(
cls,
displacement: Optional[Tuple[float, float, float]] = None,
) -> "SoundAtmos":
if displacement is None:
# displacement = (0, 0, 0)
return cls()
else:
r = sqrt(displacement[0] ** 2 + displacement[1] ** 2 + displacement[2] ** 2)
if r == 0:
return cls(distance=0, azimuth=(0, 0))
else:
beta_h = round(degrees(asin(displacement[1] / r)), 8)
if displacement[2] == 0:
alpha_v = -90 if displacement[0] > 0 else 90
else:
alpha_v = round(
degrees(atan(-displacement[0] / displacement[2])), 8
)
return cls(distance=r, azimuth=(alpha_v, beta_h))
@property
def position_displacement(self) -> Tuple[float, float, float]:
"""声像位移,直接可应用于我的世界的相对视角的坐标参考系中(^x ^y ^z"""
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
return (
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
self.sound_distance * round(sin(radians(self.sound_azimuth[1])), 8),
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
)
@dataclass(init=False)
class SingleNote:
"""存储单个音符的类"""
note_pitch: int
"""midi音高"""
velocity: int
"""力度"""
start_time: int
"""开始之时 命令刻"""
duration: int
"""音符持续时间 命令刻"""
high_precision_start_time: int
"""高精度开始时间偏量 1/1250 秒"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
extra_information: Dict[str, Any] = {},
):
"""
用于存储单个音符的类
Parameters
------------
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
extra_information: Dict[str, Any]
附加信息,尽量存储为字典
Returns
---------
MineNote 类
"""
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
"""midi音高"""
self.velocity: int = midi_velocity
"""响度(力度)"""
self.start_time: int = start_time
"""开始之时 命令刻"""
self.duration: int = last_time
"""音符持续时间 命令刻"""
self.high_precision_start_time: int = mass_precision_time
"""高精度开始时间偏量 0.4 毫秒"""
self.extra_info = extra_information if extra_information else {}
@classmethod
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出 SingleNote 类"""
duration_ = (
group_1 := int.from_bytes(code_buffer[:6], "big")
) & 0b11111111111111111
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
note_velocity_ = (group_1 := group_1 >> 17) & 0b1111111
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
try:
return cls(
midi_pitch=note_pitch_,
midi_velocity=note_velocity_,
start_time=start_tick_,
last_time=duration_,
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
)
except Exception as e:
# 我也不知道为什么这里要放一个异常处理
# 之前用到过吗?
# —— 2026.01.25 金羿
print(
"[Exception] 单音符解析错误,字节码`{}`{}启用高精度时间偏移\n".format(
code_buffer, "" if is_high_time_precision else ""
)
)
raise SingleNoteDecodeError(
e,
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
group_1, code_buffer
),
)
def encode(self, is_high_time_precision: bool = True) -> bytes:
"""
将数据打包为字节码
Parameters
------------
is_high_time_precision: bool
是否启用高精度,默认为**是**
Returns
---------
bytes
打包好的字节码
"""
# SingleNote 的字节码
# note_pitch 7 位 支持到 127
# velocity 长度 7 位 支持到 127
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# 共 48 位 合 6 字节
# high_time_precision可选长度 8 位 支持到 255 合 1 字节 支持 1/1250 秒]
return (
(
(
((((self.note_pitch << 7) + self.velocity) << 17) + self.start_time)
<< 17
)
+ self.duration
).to_bytes(6, "big")
# + self.track_no.to_bytes(1, "big")
+ (
self.high_precision_start_time.to_bytes(1, "big")
if is_high_time_precision
else b""
)
)
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""
if isinstance(key, str):
self.extra_info[key] = value
elif (
isinstance(key, Sequence)
and isinstance(value, Sequence)
and (k := len(key)) == len(value)
):
for i in range(k):
self.extra_info[key[i]] = value[i]
else:
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
raise ParameterTypeError(
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
)
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)
def stringize(self, include_extra_data: bool = False) -> str:
return "TrackedNote(Pitch = {}, Velocity = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
self.note_pitch,
self.velocity,
self.start_time,
self.duration,
self.high_precision_start_time,
) + (
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
)
# def __list__(self) -> List[int]:
# 我不认为这个类应当被作为列表使用
def __tuple__(
self,
) -> Tuple[int, int, int, int, int]:
return (
self.note_pitch,
self.velocity,
self.start_time,
self.duration,
self.high_precision_start_time,
)
def __dict__(self):
return {
"Pitch": self.note_pitch,
"Velocity": self.velocity,
"StartTick": self.start_time,
"Duration": self.duration,
"TimeOffset": self.high_precision_start_time,
"ExtraData": self.extra_info,
}
def __eq__(self, other) -> bool:
"""比较两个音符是否具有相同的属性,不计附加信息"""
if not isinstance(other, self.__class__):
return False
return self.__tuple__() == other.__tuple__()
def __lt__(self, other) -> bool:
"""比较自己是否在开始时间上早于另一个音符"""
if self.start_time == other.start_tick:
return self.high_precision_start_time < other.high_precision_time
else:
return self.start_time < other.start_tick
def __gt__(self, other) -> bool:
"""比较自己是否在开始时间上晚于另一个音符"""
if self.start_time == other.start_tick:
return self.high_precision_start_time > other.high_precision_time
else:
return self.start_time > other.start_tick
class CurvableParam(str, Enum):
"""可曲线化的参数枚举类"""
PITCH = "adjust_note_pitch"
VELOCITY = "adjust_note_velocity"
VOLUME = "adjust_note_volume"
DISTANCE = "adjust_note_sound_distance"
LR_PANNING = "adjust_note_leftright_panning_degree"
UD_PANNING = "adjust_note_updown_panning_degree"
@dataclass
class MineNote:
"""我的世界音符对象(仅提供我的世界相关接口)"""
pitch: float
"""midi音高"""
instrument: str
"""乐器ID"""
velocity: float
"""力度"""
volume: float
"""音量"""
start_tick: int
"""开始之时 命令刻"""
duration_tick: int
"""音符持续时间 命令刻"""
persiced_time: int
"""高精度开始时间偏量 1/1250 秒"""
percussive: bool
"""是否作为打击乐器启用"""
position: SoundAtmos
"""声像方位"""
@classmethod
def from_single_note(
cls,
note: SingleNote,
note_instrument: str,
sound_volume: float,
is_persiced_time: bool,
is_percussive_note: bool,
sound_position: SoundAtmos,
adjust_note_pitch: float = 0.0,
adjust_note_velocity: float = 0.0,
adjust_note_volume: float = 0.0,
adjust_note_sound_distance: float = 0.0,
adjust_note_leftright_panning_degree: float = 0.0,
adjust_note_updown_panning_degree: float = 0.0,
) -> "MineNote":
"""从SingleNote对象创建MineNote对象"""
sound_position.sound_distance += adjust_note_sound_distance
sound_position.sound_azimuth = (
sound_position.sound_azimuth[0] + adjust_note_leftright_panning_degree,
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
)
return cls(
pitch=note.note_pitch + adjust_note_pitch,
instrument=note_instrument,
velocity=note.velocity + adjust_note_velocity,
volume=sound_volume + adjust_note_volume,
start_tick=note.start_time,
duration_tick=note.duration,
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
percussive=is_percussive_note,
position=sound_position,
)
class SingleTrack(List[SingleNote]):
"""存储单个轨道的类"""
track_name: str
"""轨道之名称"""
is_enabled: bool = True
"""该音轨是否启用"""
track_instrument: str
"""乐器ID"""
track_volume: float
"""该音轨的音量"""
is_high_time_precision: bool
"""该音轨是否使用高精度时间"""
is_percussive: bool
"""该音轨是否标记为打击乐器轨道"""
sound_position: SoundAtmos
"""声像方位"""
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
"""参数曲线"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
name: str = "未命名音轨",
instrument: str = "",
volume: float = 0,
precise_time: bool = True,
percussion: bool = False,
sound_direction: SoundAtmos = SoundAtmos(),
extra_information: Dict[str, Any] = {},
*args: SingleNote,
):
self.track_name = name
"""音轨名称"""
self.track_instrument = instrument
"""乐器ID"""
self.track_volume = volume
"""音量"""
self.is_high_time_precision = precise_time
"""是否使用高精度时间"""
self.is_percussive = percussion
"""是否为打击乐器"""
self.sound_position = sound_direction
"""声像方位"""
self.extra_info = extra_information if extra_information else {}
self.argument_curves = {item: None for item in CurvableParam}
super().__init__(*args)
super().sort()
def disable(self) -> None:
"""禁用音轨"""
self.is_enabled = False
def enable(self) -> None:
"""启用音轨"""
self.is_enabled = True
def toggle_able(self) -> None:
"""切换音轨的启用状态"""
self.is_enabled = not self.is_enabled
def append(self, object: SingleNote) -> None:
"""
添加一个音符,推荐使用 add 方法
"""
return self.add(object)
def add(self, item: SingleNote) -> None:
"""
在音轨里添加一个音符
"""
if not isinstance(item, SingleNote):
raise ParameterTypeError(
"单音轨类的元素类型须为单音符(`SingleNote`),不可为:`{}`".format(
type(item).__name__
)
)
super().append(item)
super().sort() # =========================== TODO 需要优化
def update(self, items: Iterable[SingleNote]):
"""
拼接两个音轨
"""
super().extend(items)
super().sort() # =========================== TODO 需要优化
def get(self, time: int) -> Generator[SingleNote, None, None]:
"""通过开始时间来获取音符"""
return (x for x in self if x.start_time == time)
def get_notes(
self, start_time: float, end_time: float = inf
) -> Generator[SingleNote, None, None]:
"""通过开始时间和结束时间来获取音符"""
if end_time < start_time:
raise ParameterValueError(
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".format(
end_time, start_time
)
)
elif start_time < 0 or end_time < 0:
raise ParameterValueError(
"获取音符的时间范围有误,终止时间`{}`和起始时间`{}`皆不可为负数".format(
end_time, start_time
)
)
return (
x
for x in self
if (x.start_time >= start_time) and (x.start_time <= end_time)
)
def get_minenotes(
self, range_start_time: float = 0, range_end_time: float = inf
) -> Generator[MineNote, Any, None]:
"""获取能够用以在我的世界播放的音符数据类"""
for _note in self.get_notes(range_start_time, range_end_time):
yield MineNote.from_single_note(
note=_note,
note_instrument=self.track_instrument,
sound_volume=self.track_volume,
is_persiced_time=self.is_high_time_precision,
is_percussive_note=self.is_percussive,
sound_position=self.sound_position,
**{
item.value: argcrv.value_at(_note.start_time)
for item in CurvableParam
if (argcrv := self.argument_curves[item])
},
)
@property
def note_amount(self) -> int:
"""音符数"""
return len(self)
@property
def track_notes(self) -> List[SingleNote]:
"""音符列表"""
return self
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""
if isinstance(key, str):
self.extra_info[key] = value
elif (
isinstance(key, Sequence)
and isinstance(value, Sequence)
and (k := len(key)) == len(value)
):
for i in range(k):
self.extra_info[key[i]] = value[i]
else:
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
raise ParameterTypeError(
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
)
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)
class SingleMusic(List[SingleTrack]):
"""存储单个曲子的类"""
music_name: str
"""乐曲名称"""
music_creator: str
"""本我的世界曲目的制作者"""
music_original_author: str
"""曲目的原作者"""
music_description: str
"""当前曲目的简介"""
music_credits: str
"""曲目的版权信息"""
# 感叹一下什么交冗余设计啊!(叉腰)
extra_info: Dict[str, Any]
"""这还得放东西?"""
def __init__(
self,
name: str = "未命名乐曲",
creator: str = "未命名制作者",
original_author: str = "未命名原作者",
description: str = "未命名简介",
credits: str = "未命名版权信息",
*args: SingleTrack,
extra_information: Dict[str, Any] = {},
):
self.music_name = name
"""乐曲名称"""
self.music_creator = creator
"""曲目制作者"""
self.music_original_author = original_author
"""乐曲原作者"""
self.music_description = description
"""简介"""
self.music_credits = credits
"""版权信息"""
self.extra_info = extra_information if extra_information else {}
super().__init__(*args)
@property
def track_amount(self) -> int:
"""音轨数"""
return len(self)
@property
def music_tracks(self) -> Iterator[SingleTrack]:
"""音轨列表,不包含被禁用的音轨"""
return (track for track in self if track.is_enabled)
@staticmethod
def yield_from_tracks(
tracks: Sequence[Iterator[T]],
sort_key: Callable[[T], Any],
is_subseq_sorted: bool = True,
) -> Iterator[T]:
"""从任意迭代器列表迭代符合顺序的元素
(惰性多路归并多个迭代器,按 sort_key 排序)
参数
----
tracks: Sequence[Iterator[T]]
迭代器列表
sort_key: Callable[[T], Any]
接受 T 元素,返回可比较的键
is_subseq_sorted: bool = True
子序列是否已排序
迭代
----
归并后的每个元素,按 sort_key 升序
"""
if is_subseq_sorted:
return heapq.merge(*tracks, key=sort_key)
else:
# 初始化堆
heap_pool: List[Tuple[Any, int, T]] = []
for _index, _track in enumerate(tracks):
try:
item = next(_track)
heapq.heappush(heap_pool, (sort_key(item), _index, item))
except StopIteration:
continue
# 归并主循环
while heap_pool:
_key, _index, item = heapq.heappop(heap_pool)
yield item
try:
next_item = next(tracks[_index])
heapq.heappush(heap_pool, (sort_key(next_item), _index, next_item))
except StopIteration:
pass
# NEVER REACH:
# pool: List[Tuple[str, T]] = []
# remove_track: List[str] = []
# for _name, _track in tracks.items():
# try:
# pool.append((_name, next(_track)))
# except StopIteration:
# remove_track.append(_name)
# for _x in remove_track:
# tracks.pop(_x)
# del remove_track
# while tracks and pool:
# yield (_x := min(pool, key=sort_key))[1]
# try:
# pool.append((_x[0], next(tracks[_x[0]])))
# except StopIteration:
# tracks.pop(_x[0])
# pool.sort(key=sort_key)
# for _remain in pool:
# yield _remain[1]
def get_tracked_notes(
self, start_time: float, end_time: float = inf
) -> Generator[Iterator[SingleNote], Any, None]:
"""获取指定时间段的各个音轨的音符数据"""
return (track.get_notes(start_time, end_time) for track in self.music_tracks)
def get_tracked_minenotes(
self, start_time: float, end_time: float = inf
) -> Generator[Iterator[MineNote], Any, None]:
"""获取指定时间段的各个音轨的,供我的世界播放的音符数据类"""
return (
track.get_minenotes(start_time, end_time) for track in self.music_tracks
)
def get_notes(
self, start_time: float, end_time: float = inf
) -> Iterator[SingleNote]:
"""获取指定时间段的所有音符数据,按照时间顺序"""
if self.track_amount == 0:
return iter(())
return self.yield_from_tracks(
[track.get_notes(start_time, end_time) for track in self.music_tracks],
sort_key=lambda x: x.start_time,
)
def get_minenotes(
self, start_time: float, end_time: float = inf
) -> Generator[MineNote, Any, None]:
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
if self.track_amount == 0:
return
yield from self.yield_from_tracks(
[track.get_minenotes(start_time, end_time) for track in self.music_tracks],
sort_key=lambda x: x.start_tick,
)
def set_info(self, key: Union[str, Sequence[str]], value: Any):
"""设置附加信息"""
if isinstance(key, str):
self.extra_info[key] = value
elif (
isinstance(key, Sequence)
and isinstance(value, Sequence)
and (k := len(key)) == len(value)
):
for i in range(k):
self.extra_info[key[i]] = value[i]
else:
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
raise ParameterTypeError(
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
)
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)

266
Musicreater/exceptions.py Normal file
View File

@@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 用到的一些报错类型
"""
"""
版权所有 © 2026 金羿 & 玉衡Alioth
Copyright © 2026 Eilles & YuhengAlioth
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# “
# There are planty of "exception"s in this library
# for I know I will always go with my heart.
# ” —— Cyberdevil by resnah
class MusicreaterBaseException(Exception):
"""音·创 v3 的所有错误均继承于此"""
def __init__(self, *args):
"""音·创 的所有错误均继承于此"""
super().__init__("[音·创] - ", *args)
def meow(self):
for i in self.args:
print(i + "喵~", end="")
def crash_it(self):
raise self
def __str__(self) -> str:
return "".join(self.args)
# =====================================
# NOTE
# 面对用户时候爆出去的我们认为这就是“外部错误”
# 如果是在程序内部数据传输等情况下出现的就是“内部错误”
# 例如,无法读取文件,这就是一个外部错误
# 某个参数的数据类型错误,这就是内部错误
# =====================================
class MusicreaterInnerlyError(MusicreaterBaseException):
"""内部错误"""
def __init__(self, *args):
"""内部错误(面向开发者的报错信息)"""
super().__init__("内部错误 - ", *args)
class MusicreaterOuterlyError(MusicreaterBaseException):
"""外部错误"""
def __init__(self, *args):
"""外部错误(面向用户的报错信息)"""
super().__init__("外部错误 - ", *args)
class InnerlyParameterError(MusicreaterInnerlyError):
"""内部传参错误"""
def __init__(self, *args):
"""参数错误"""
super().__init__("传参错误 - ", *args)
class ParameterTypeError(InnerlyParameterError, TypeError):
"""参数类型错误"""
def __init__(self, *args):
"""参数类型错误"""
super().__init__("参数类型错误:", *args)
class ParameterValueError(InnerlyParameterError, ValueError):
"""参数值存在错误"""
def __init__(self, *args):
"""参数其值存在错误"""
super().__init__("参数数值错误:", *args)
class PluginNotSpecifiedError(InnerlyParameterError, LookupError):
"""未指定插件"""
def __init__(self, *args):
"""未指定插件"""
super().__init__("未指定插件:", *args)
class OuterlyParameterError(MusicreaterOuterlyError):
"""外部参数错误"""
def __init__(self, *args):
"""参数错误"""
super().__init__("参数错误 - ", *args)
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
"""以0作为播放速度的错误"""
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为零:", *args)
class IllegalMinimumVolumeError(OuterlyParameterError, ValueError):
"""最小播放音量有误的错误"""
def __init__(self, *args):
"""最小播放音量错误"""
super().__init__("最小播放音量超出范围:", *args)
class FileFormatNotSupportedError(MusicreaterOuterlyError):
"""不支持的文件格式"""
def __init__(self, *args):
"""文件格式不受支持"""
super().__init__("不支持的文件格式:", *args)
class NoteBinaryDecodeError(MusicreaterOuterlyError):
"""音乐存储二进制数据解码错误"""
def __init__(self, *args):
"""音乐存储二进制数据无法正确解码"""
super().__init__("解码音乐存储二进制数据时出现问题 - ", *args)
class SingleNoteDecodeError(NoteBinaryDecodeError):
"""单个音符的二进制数据解码错误"""
def __init__(self, *args):
"""单个音符的二进制数据无法正确解码"""
super().__init__("音符解码出错:", *args)
class NoteBinaryFileTypeError(NoteBinaryDecodeError):
"""音乐存储二进制数据的文件类型错误"""
def __init__(self, *args):
"""无法识别音乐存储文件的类型"""
super().__init__("无法识别音乐存储文件对应的类型:", *args)
class NoteBinaryFileVerificationFailed(NoteBinaryDecodeError):
"""音乐存储二进制数据校验失败"""
def __init__(self, *args):
"""音乐存储文件与其校验值不一致"""
super().__init__("音乐存储文件校验失败:", *args)
class PluginDefineError(MusicreaterInnerlyError):
"""插件定义错误(内部相关)"""
def __init__(self, *args):
"""插件本身存在错误"""
super().__init__("插件内部错误 - ", *args)
class PluginInstanceNotFoundError(PluginDefineError, LookupError):
"""插件实例未找到"""
def __init__(self, *args):
"""插件实例未找到"""
super().__init__("插件实例未找到:", *args)
class PluginAttributeNotFoundError(PluginDefineError, AttributeError):
"""插件属性定义错误"""
def __init__(self, *args):
"""插件属性定义错误"""
super().__init__("插件类的必要属性不存在:", *args)
class PluginMetainfoError(PluginDefineError):
"""插件元信息定义错误"""
def __init__(self, *args):
"""插件元信息定义错误"""
super().__init__("插件元信息定义错误 - ", *args)
class PluginMetainfoTypeError(PluginMetainfoError, TypeError):
"""插件元信息定义类型错误"""
def __init__(self, *args):
"""插件元信息定义类型错误"""
super().__init__("插件元信息类型错误:", *args)
class PluginMetainfoValueError(PluginMetainfoError, ValueError):
"""插件元信息定义值错误"""
def __init__(self, *args):
"""插件元信息定义值错误"""
super().__init__("插件元信息数值错误:", *args)
class PluginMetainfoNotFoundError(PluginMetainfoError, PluginAttributeNotFoundError):
"""插件元信息定义缺少错误"""
def __init__(self, *args):
"""插件元信息定义缺少错误"""
super().__init__("插件元信息未定义:", *args)
class PluginLoadError(MusicreaterOuterlyError):
"""插件加载错误(外部相关)"""
def __init__(self, *args):
"""插件加载错误"""
super().__init__("插件加载错误 - ", *args)
class PluginNotFoundError(PluginLoadError):
"""插件未找到"""
def __init__(self, *args):
"""插件未找到"""
super().__init__("插件未找到:", *args)
class PluginRegisteredError(PluginLoadError):
"""插件重复注册"""
def __init__(self, *args):
"""插件已被注册注册"""
super().__init__("插件重复注册:", *args)
class PluginConfigRelatedError(MusicreaterOuterlyError):
"""插件配置相关错误"""
def __init__(self, *args):
"""插件配置相关错误"""
super().__init__("插件配置相关错误 - ", *args)
class PluginConfigLoadError(PluginLoadError, PluginConfigRelatedError):
"""插件配置加载错误"""
def __init__(self, *args):
"""配置文件无法加载"""
super().__init__("插件配置文件加载错误:", *args)
class PluginConfigDumpError(PluginConfigRelatedError):
"""插件配置保存错误"""
def __init__(self, *args):
"""配置文件无法保存"""
super().__init__("插件配置文件保存错误:", *args)

285
Musicreater/main.py Normal file
View File

@@ -0,0 +1,285 @@
# -*- coding: utf-8 -*-
"""
音·创
是一款免费开源的《我的世界》数字音频支持库。
Musicreater (音·创)
A free and open-source library for handling with **Minecraft** digital music.
版权所有 © 2026 睿乐组织
Copyright © 2026 TriM-Organization
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
任何人皆可从以下地址获得本协议副本:
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
详细的准许和限制条款请见原协议文本。
"""
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
# BUG退散BUG退散
# 异常与错误作乱之时
# 二六字组!万国码合!二六字组!万国码合!
# 赶快呼叫 程序员Let's Go
# BUG退散BUG退散
# 異常、誤りが、困った時は
# パラメータ メソッド!パラメータ メソッド!
# 助けてもらおう、開発者!レッツゴー!
# Bug retreat! Bug retreat!
# Exceptions and errors are causing chaos
# Words combine! Codes unite!
# Hurry to call the programmer! Let's Go!
import re
from difflib import get_close_matches
from typing import Dict, Generator, List, Optional, Tuple, Union, Mapping, Callable
from pathlib import Path
from .data import SingleMusic, SingleTrack
from .exceptions import (
FileFormatNotSupportedError,
PluginNotSpecifiedError,
PluginNotFoundError,
)
from ._plugin_abc import TopPluginBase
from .plugins import (
_global_plugin_registry,
PluginRegistry,
PluginConfig,
PluginTypes,
load_plugin_module,
T_IOPlugin,
T_Plugin,
)
class MusiCreater:
"""
音·创 v3 主要控制类
“创建者”一词的英文应该是“Creator”
"""
__plugin_registry: PluginRegistry
"""插件注册表实例"""
_plugin_cache: Dict[str, TopPluginBase]
"""插件缓存字典插件id为键、插件实例为值"""
music: SingleMusic
"""当前曲目实例"""
def __init__(self, whole_music: SingleMusic) -> None:
global _global_plugin_registry
self.__plugin_registry = _global_plugin_registry
self._plugin_cache = {}
self._cache_all_plugins()
self.music = whole_music
@staticmethod
def _get_plugin_within_iousage(
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
fpath: Path,
plg_regdict: Dict[str, T_IOPlugin],
plg_id: Optional[str],
) -> T_IOPlugin:
__plugin: Optional[T_IOPlugin] = None
if plg_id:
__plugin = plg_regdict.get(plg_id)
else:
for __plg in get_func(fpath):
if __plugin:
raise PluginNotSpecifiedError(
"文件类型`{}`可被多个插件处理,请在调用函数的参数中指定插件名称".format(
fpath.suffix.upper()
)
)
__plugin = __plg
if __plugin:
return __plugin
else:
raise FileFormatNotSupportedError(
"无法找到处理`{}`类型文件的插件".format(fpath.suffix.upper())
)
@classmethod
def import_music(
cls,
file_path: Path,
plugin_id: Optional[str] = None,
plugin_config: Optional[PluginConfig] = None,
) -> "MusiCreater":
return cls(
whole_music=cls._get_plugin_within_iousage(
_global_plugin_registry.get_music_input_plugin_by_format,
file_path,
_global_plugin_registry._music_input_plugins,
plugin_id,
).load(file_path, plugin_config)
)
def import_track(
self,
file_path: Path,
plugin_id: Optional[str] = None,
plugin_config: Optional[PluginConfig] = None,
) -> SingleTrack:
self.music.append(
self._get_plugin_within_iousage(
self.__plugin_registry.get_track_input_plugin_by_format,
file_path,
self.__plugin_registry._track_input_plugins,
plugin_id,
).load(file_path, plugin_config)
)
return self.music[-1]
def export_music(
self,
file_path: Path,
plugin_id: Optional[str] = None,
plugin_config: Optional[PluginConfig] = None,
) -> None:
self._get_plugin_within_iousage(
self.__plugin_registry.get_music_output_plugin_by_format,
file_path,
self.__plugin_registry._music_output_plugins,
plugin_id,
).dump(self.music, file_path, plugin_config)
def export_track(
self,
track_index: int,
file_path: Path,
plugin_id: Optional[str] = None,
plugin_config: Optional[PluginConfig] = None,
) -> None:
self._get_plugin_within_iousage(
self.__plugin_registry.get_track_output_plugin_by_format,
file_path,
self.__plugin_registry._track_output_plugins,
plugin_id,
).dump(self.music[track_index], file_path, plugin_config)
def perform_operation_on_music(
self, plugin_id: str, plugin_config: Optional[PluginConfig] = None
):
if __plugin := self.__plugin_registry._music_operate_plugins.get(plugin_id):
# 这样做是为了兼容以后的*撤回/重做*功能
self.music = __plugin.process(self.music, plugin_config)
else:
raise PluginNotFoundError(
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
)
def perform_operation_on_track(
self,
track_index: int,
plugin_id: str,
plugin_config: Optional[PluginConfig] = None,
):
if __plugin := self.__plugin_registry._track_operate_plugins.get(plugin_id):
# 这样做是为了兼容以后的*撤回/重做*功能
self.music[track_index] = __plugin.process(
self.music[track_index], plugin_config
)
else:
raise PluginNotFoundError(
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
)
@staticmethod
def _camel_to_snake(name: str) -> str:
"""
将驼峰命名转换为蛇形命名
CyberAngel -> cyber_angel
"""
return re.sub(
"([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
).lower()
def _parse_plugin_id(self, attr_name: str) -> Optional[str]:
"""解析属性名称为插件惟一识别码"""
# 尝试去除 _plugin 后缀
if attr_name.endswith("_plugin"):
candidate_name = attr_name[:-7] # 去除 "_plugin"
if candidate_name in self._plugin_cache:
return candidate_name
# 尝试转换为 snake_case如果插件名是驼峰式
snake_case_name = self._camel_to_snake(attr_name)
if snake_case_name != attr_name: # 避免重复转换
if snake_case_name in self._plugin_cache: # 尝试转换后的插件名
return snake_case_name
else:
return self._parse_plugin_id(snake_case_name)
return None
def _get_closest_plugin_id(self, requested_id: str) -> Optional[str]:
"""找到最接近的插件识别码(用于更好的错误提示)"""
matches = get_close_matches(
requested_id, self._plugin_cache.keys(), n=1, cutoff=0.6
)
return matches[0] if matches else None
def get_plugin_by_id(self, plg_id: str):
"""获取插件实例,并缓存起来,提高性能"""
if plg_id.startswith("_"):
raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(plg_id))
if plg_id in self._plugin_cache:
return self._plugin_cache[plg_id]
else:
plugin_name = self._parse_plugin_id(plg_id)
if plugin_name:
self._plugin_cache[plg_id] = self._plugin_cache[plugin_name]
return self._plugin_cache[plg_id]
closest = self._get_closest_plugin_id(plg_id)
raise AttributeError(
"插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id)
+ (
";或者阁下可能想要使用的是`{}`插件?".format(closest)
if closest
else ""
)
)
def __getattr__(self, plugin_id: str):
"""动态属性访问,允许直接 实例.插件名 来访问插件"""
return self.get_plugin_by_id(plugin_id)
def _cache_all_plugins(self):
"""获取所有已注册插件的名称"""
for __plugin_type, __plugins_map in self.__plugin_registry:
for __plugin_id, __plugin in __plugins_map.items():
if __plugin_id in self._plugin_cache: # 避免重复缓存
if (
__plugin.metainfo.version
<= self._plugin_cache[__plugin_id].metainfo.version
): # 优先使用版本号最大的插件
continue
self._plugin_cache[__plugin_id] = __plugin

583
Musicreater/paramcurve.py Normal file
View File

@@ -0,0 +1,583 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 内部数据使用的参数曲线
"""
"""
版权所有 © 2026 金羿
Copyright © 2026 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# WARNING 本文件所含之功能未经完整测试
# 鉴于白谭若佬给出的建议:本功能应是处于低优先级开发的
# 因此暂时用处不大,可以稍微放一会再进行开发
# 目前用人工智能生成了部分代码,只经过简单的测试
# 可以等伶伦工作站开发出来后再进行完整的测试
from math import ceil
from dataclasses import dataclass
from typing import Optional, Any, List, Tuple
from enum import Enum
import bisect
from .types import FittingFunctionType
def _evaluate_bezier_segment(
t0: float,
v0: float,
t1: float,
v1: float,
out_tangent: Optional[Tuple[float, float]],
in_tangent: Optional[Tuple[float, float]],
u: float,
) -> float:
"""
计算贝塞尔区间 [t0, t1] 在归一化参数 u ∈ [0,1] 处的 y 值。
控制点:
P0 = (t0, v0)
P1 = (t0 + out_dt, v0 + out_dv)
P2 = (t1 - in_dt, v1 - in_dv) ← 注意in_tangent 是相对于 t1 的偏移
P3 = (t1, v1)
"""
# 默认控制点:退化为线性
p0 = (t0, v0)
p3 = (t1, v1)
if out_tangent is not None:
p1 = (t0 + out_tangent[0], v0 + out_tangent[1])
else:
p1 = p0 # 无出手柄 → 与起点重合
if in_tangent is not None:
p2 = (t1 - in_tangent[0], v1 - in_tangent[1])
else:
p2 = p3 # 无入手柄 → 与终点重合
# 三次贝塞尔 y(t)
mt = 1.0 - u
return mt**3 * p0[1] + 3 * mt**2 * u * p1[1] + 3 * mt * u**2 * p2[1] + u**3 * p3[1]
class InterpolationMethod:
"""
预定义的标准化插值函数集合。所有函数接受归一化输入 u ∈ [0,1],返回 v ∈ [0,1]。
"""
@staticmethod
def linear(u: float) -> float:
"""
线性插值。
Parameters
----------
u : float
归一化时间,范围 [0, 1]。
Returns
-------
float
插值权重,范围 [0, 1]。
"""
return u
@staticmethod
def ease_in_quad(u: float) -> float:
"""
二次缓入(慢进快出)。
Parameters
----------
u : float
归一化时间,范围 [0, 1]。
Returns
-------
float
插值权重。
"""
return u * u
@staticmethod
def ease_out_quad(u: float) -> float:
"""
二次缓出(快进慢出)。
Parameters
----------
u : float
归一化时间,范围 [0, 1]。
Returns
-------
float
插值权重。
"""
return 1 - (1 - u) ** 2
@staticmethod
def ease_in_out_quad(u: float) -> float:
"""
二次缓入缓出。
Parameters
----------
u : float
归一化时间,范围 [0, 1]。
Returns
-------
float
插值权重。
"""
if u < 0.5:
return 2 * u * u
else:
return 1 - pow(-2 * u + 2, 2) / 2
@staticmethod
def hold(u: float) -> float:
"""
阶梯保持模式占位函数。实际插值逻辑在 ParamCurve.value_at 中特殊处理。
Parameters
----------
u : float
归一化时间(忽略)。
Returns
-------
float
无意义,仅作标识。
"""
return 0.0
@dataclass
class Keyframe:
"""
参数曲线上的一个关键帧,支持完整的入/出切线控制。
插值优先级:
1. 若 use_bezier=True → 使用贝塞尔模式(需 in_tangent / out_tangent
2. 否则 → 使用 out_interp 函数in_interp 被忽略)
"""
time: float
value: float
# 函数插值模式
out_interp: Optional[FittingFunctionType] = None
# 贝塞尔模式
in_tangent: Optional[Tuple[float, float]] = (
None # (dt, dv) ← 相对于自身(负 dt 表示左侧)
)
out_tangent: Optional[Tuple[float, float]] = (
None # (dt, dv) → 相对于自身(正 dt 表示右侧)
)
use_bezier: bool = False
class BoundaryBehaviour(str, Enum):
"""
边界行为枚举。
"""
CONSTANT = "constant"
"""返回默认基线值"""
HOLD = "hold"
"""保持首/尾关键帧的值"""
class ParamCurve:
"""
参数曲线类
"""
"""
支持动态节点编辑
用户通过添加/修改关键帧(时间-值对)来定义曲线,类自动在相邻关键帧之间生成插值段。
支持多种插值模式:线性('linear')、平滑缓动('smooth')、保持('hold')或自定义函数。
"""
base_line: float = 0.0
"""基线/默认值"""
base_interpolation_function: FittingFunctionType
"""默认(未指定区间时的)关键帧插值模式"""
boundary_behaviour: BoundaryBehaviour
"""边界行为,控制参数曲线在已定义的范围外的返回值"""
_keys: List[Keyframe]
"""关键帧列表"""
def __init__(
self,
base_value: float = 0.0,
default_interpolation_function: FittingFunctionType = InterpolationMethod.linear,
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
):
"""
初始化参数曲线。
Parameters
----------
base_value : float
边界外默认值(当 boundary_mode 为 BoundaryBehaviour.CONSTANT 时使用)。
default_interpolation_function : FittingFunctionType
新关键帧的默认 out_interp。
boundary_mode : BoundaryBehaviour
范围外行为:
- BoundaryBehaviour.CONSTANT: 返回 base_value
- BoundaryBehaviour.HOLD: 保持首/尾关键帧值
"""
self.base_line = base_value
self.base_interpolation_function = default_interpolation_function
self.boundary_behaviour = boundary_mode
self._keys: List[Keyframe] = []
def __bool__(self) -> bool:
return bool(self._keys) or (self.base_line != 0)
def add_key(
self,
time: float,
value: float,
out_interp: Optional[FittingFunctionType] = None,
in_tangent: Optional[Tuple[float, float]] = None,
out_tangent: Optional[Tuple[float, float]] = None,
use_bezier: bool = False,
):
"""
添加或更新关键帧。
Parameters
----------
time : float
关键帧时间。
value : float
参数值。
out_interp : Optional[Callable]
出插值函数(若 use_bezier=False
in_tangent : Optional[Tuple[float, float]]
入切线偏移 (dt, dv)。dt 通常为负(表示左侧),但存储为绝对偏移。
out_tangent : Optional[Tuple[float, float]]
出切线偏移 (dt, dv)。dt 通常为正。
use_bezier : bool
是否使用贝塞尔插值。
Returns
-------
None
Notes
-----
若时间已存在,更新该关键帧的所有属性。
"""
interp = (
out_interp if out_interp is not None else self.base_interpolation_function
)
new_key = Keyframe(time, value, interp, in_tangent, out_tangent, use_bezier)
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
self._keys[idx] = new_key
else:
self._keys.insert(idx, new_key)
def remove_key(self, time: float):
"""
移除指定时间的关键帧。
Parameters
----------
time : float
要移除的关键帧时间。
Returns
-------
None
"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
del self._keys[idx]
def update_key_value(self, time: float, new_value: float):
"""更新关键帧值,保留其他属性。"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
self._keys[idx] = Keyframe(
time, new_value, k.out_interp, k.in_tangent, k.out_tangent, k.use_bezier
)
def update_key_interp(
self,
time: float,
out_interp: Optional[FittingFunctionType] = None,
in_tangent: Optional[Tuple[float, float]] = None,
out_tangent: Optional[Tuple[float, float]] = None,
use_bezier: bool = False,
):
"""更新关键帧的插值属性。"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
new_value = k.value
interp = out_interp if out_interp is not None else k.out_interp
self._keys[idx] = Keyframe(
time, new_value, interp, in_tangent, out_tangent, use_bezier
)
def set_key_tangents(
self,
time: float,
in_tangent: Optional[Tuple[float, float]] = None,
out_tangent: Optional[Tuple[float, float]] = None,
use_bezier: bool = True,
):
"""单独设置关键帧的切线,不改变值。"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
self._keys[idx] = Keyframe(
time,
k.value,
out_interp=k.out_interp,
in_tangent=in_tangent,
out_tangent=out_tangent,
use_bezier=use_bezier,
)
def make_key_smooth(self, time: float):
"""
将关键帧设为“平滑”模式(自动对称切线,并设为贝塞尔模式)。
切线长度基于相邻关键帧的时间和值差。
"""
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
if idx < len(self._keys) and self._keys[idx].time == time:
k = self._keys[idx]
prev_k = self._keys[idx - 1] if idx > 0 else None
next_k = self._keys[idx + 1] if idx + 1 < len(self._keys) else None
# 默认切线长度:时间差的 1/3值差按比例
dt_in = dt_out = 0.1
dv_in = dv_out = 0.0
if prev_k and next_k:
dt_total = next_k.time - prev_k.time
dv_total = next_k.value - prev_k.value
dt_in = dt_out = dt_total / 3.0
dv_in = dv_out = dv_total / 3.0
elif prev_k:
dt_out = (k.time - prev_k.time) / 2.0
dv_out = (k.value - prev_k.value) / 2.0
dt_in = dt_out
dv_in = dv_out
elif next_k:
dt_in = (next_k.time - k.time) / 2.0
dv_in = (next_k.value - k.value) / 2.0
dt_out = dt_in
dv_out = dv_in
self.set_key_tangents(
time,
in_tangent=(-dt_in, -dv_in), # in_tangent 存储为偏移,使用时做减法
out_tangent=(dt_out, dv_out),
use_bezier=True,
)
def _get_boundary_value(self, t: float) -> float:
"""根据 boundary_mode 获取范围外的值。"""
if not self._keys:
return self.base_line
if self.boundary_behaviour == BoundaryBehaviour.CONSTANT:
return self.base_line
elif self.boundary_behaviour == BoundaryBehaviour.HOLD:
if t < self._keys[0].time:
return self._keys[0].value
else:
return self._keys[-1].value
else: # 可能会有别的模式吗?
return self.base_line
def value_at(self, t: float) -> float:
"""
计算时间 t 处的曲线值。
Parameters
----------
t : float
查询时间。
Returns
-------
float
插值结果。
"""
keys = self._keys
if not keys:
return self._get_boundary_value(t)
if t < keys[0].time or t > keys[-1].time:
return self._get_boundary_value(t)
times = [k.time for k in keys]
idx = bisect.bisect_right(times, t) - 1
if idx < 0:
return self._get_boundary_value(t)
if idx >= len(keys) - 1:
return keys[-1].value
k0 = keys[idx]
k1 = keys[idx + 1]
if k0.time == k1.time:
return k0.value
if k0.time == t:
return k0.value
if k1.time == t:
return k1.value
t0, v0 = k0.time, k0.value
t1, v1 = k1.time, k1.value
u = (t - t0) / (t1 - t0)
u = max(0.0, min(1.0, u))
# 贝塞尔模式(高优先级)
if k0.use_bezier or k1.use_bezier:
return _evaluate_bezier_segment(
t0,
v0,
t1,
v1,
out_tangent=k0.out_tangent,
in_tangent=k1.in_tangent, # ← 关键:使用下一帧的 in_tangent
u=u,
)
# 函数插值模式,优先处理阶梯保持模式
elif k0.out_interp is InterpolationMethod.hold:
return v0
interp_func = k0.out_interp or self.base_interpolation_function
v_norm = interp_func(u)
return v0 + v_norm * (v1 - v0)
def __call__(self, t: float) -> float:
return self.value_at(t)
def get_all_keys(self) -> List[Tuple[float, float]]:
"""返回 (time, value) 列表。"""
return [(k.time, k.value) for k in self._keys]
def set_default_interpolation_function(self, interp_func: FittingFunctionType):
"""设置默认插值函数。"""
self.base_interpolation_function = interp_func
def set_boundary_mode(
self, mode: BoundaryBehaviour, base_value: Optional[float] = None
):
"""
设置边界行为。
Parameters
----------
mode : BoundaryBehaviour
边界行为设定
base_value : Optional[float]
当 mode=BoundaryBehaviour.CONSTANT 时,指定新的默认值。
"""
self.boundary_behaviour = mode
if base_value is not None:
self.base_line = base_value
def bake(
self,
start: float,
end: float,
sample_rate: Optional[float] = None,
num_samples: Optional[int] = None,
dtype: Any = None,
) -> "np.ndarray": # type: ignore 这里这样用会报错吗?不知道,但是人工智能这样写了都,大抵是能用的吧
"""
将参数曲线在指定时间范围内烘焙为 NumPy 数组,用于高性能实时查询或音频渲染。
Parameters
----------
start : float
烘焙起始时间(包含)。
end : float
烘焙结束时间(不包含)。
sample_rate : Optional[float]
采样率(单位:样本/时间单位。例如若时间单位为秒sample_rate=48000 表示每秒 48k 样本。
必须与 `num_samples` 二选一提供。
num_samples : Optional[int]
输出数组的总样本数。若提供,则忽略 `sample_rate`。
dtype : Any, optional
输出数组的数据类型(如 np.float32。默认为 np.float64。
Returns
-------
np.ndarray
一维 NumPy 数组,长度为 `num_samples``arr[i] ≈ curve(start + i / sample_rate)`。
Exceptions
----------
ValueError
- 若 `start >= end`
- 若未提供 `sample_rate` 且未提供 `num_samples`
- 若 `num_samples <= 0`
Notes
-----
- 内部使用 `np.linspace` 生成时间轴,然后逐点调用 `self.value_at(t)`。
- 虽然目前是 Python 循环,但对于典型自动化曲线(<1000 关键帧NumPy 向量化优势主要体现在内存布局和后续处理。
- 如需极致性能(如 >1M 样本),可未来优化为 C++/Numba 加速,但当前已满足 DAW 自动化需求。
"""
if start >= end:
raise ValueError("起始值须小于结束值。")
if num_samples is not None:
if num_samples <= 0:
raise ValueError("烘焙的采样数须为非零自然数。")
n = num_samples
elif sample_rate is not None:
if sample_rate <= 0:
raise ValueError("烘焙的采样率须为正值。")
duration = end - start
n = int(ceil(duration * sample_rate))
# 别因为小数数值会产生的问题而越界了来着
if n == 0:
n = 1
else:
raise ValueError("烘焙参数时,须提供采样率或采样数。")
import numpy as np
# 生成对应时间的节点:[start, ..., end - dt]
times = np.linspace(start, end, n, endpoint=False)
# 计算每个时间节点上的参数值
# 我们认为在数字音频工作站的环境里,此值可能最多到 ~1e6 的样子,因此这样 for 一下应当可以接受
# WARNING: 人工智能是这样理解的,如果有问题的话后续可能需要更改
values = np.empty(n, dtype=dtype or np.float64)
for i in range(n):
values[i] = self.value_at(float(times[i]))
return values

424
Musicreater/plugins.py Normal file
View File

@@ -0,0 +1,424 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 的插件接口与管理相关内容
"""
"""
版权所有 © 2026 金羿
Copyright © 2026 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import importlib
from pathlib import Path
from typing import (
Dict,
Any,
Optional,
List,
Tuple,
Union,
Generator,
Set,
Iterable,
Iterator,
TypeVar,
Mapping,
Callable,
)
from itertools import chain
from ._plugin_abc import (
# 枚举类
PluginTypes,
# 抽象基类/数据类(插件参数定义)
PluginConfig,
PluginMetaInformation,
# 抽象基类(插件定义)
MusicInputPluginBase,
TrackInputPluginBase,
MusicOperatePluginBase,
TrackOperatePluginBase,
MusicOutputPluginBase,
TrackOutputPluginBase,
ServicePluginBase,
LibraryPluginBase,
# 顶层插件定义
TopPluginBase,
)
from .exceptions import (
PluginMetainfoNotFoundError,
ParameterTypeError,
PluginInstanceNotFoundError,
PluginRegisteredError,
)
__all__ = [
# 枚举类
"PluginTypes",
# 抽象基类/数据类(插件参数定义)
"PluginConfig",
"PluginMetaInformation",
# 抽象基类(插件定义)
"MusicInputPluginBase",
"TrackInputPluginBase",
"MusicOperatePluginBase",
"TrackOperatePluginBase",
"MusicOutputPluginBase",
"TrackOutputPluginBase",
"ServicePluginBase",
"LibraryPluginBase",
# 插件注册用装饰函数
"music_input_plugin",
"track_input_plugin",
"music_operate_plugin",
"track_operate_plugin",
"music_output_plugin",
"track_output_plugin",
"service_plugin",
"library_plugin",
]
T_IOPlugin = TypeVar(
"T_IOPlugin",
MusicInputPluginBase,
TrackInputPluginBase,
MusicOutputPluginBase,
TrackOutputPluginBase,
)
T_Plugin = TypeVar(
"T_Plugin",
MusicInputPluginBase,
TrackInputPluginBase,
MusicOperatePluginBase,
TrackOperatePluginBase,
MusicOutputPluginBase,
TrackOutputPluginBase,
ServicePluginBase,
LibraryPluginBase,
)
def load_plugin_module(package: Union[Path, str]):
"""自动发现并加载插件包中的插件
参数:
=====
package: Path | str, 可选
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
"""
if isinstance(package, Path):
relative_path = package.resolve().relative_to(Path.cwd().resolve())
if relative_path.stem == "__init__":
return importlib.import_module(".".join(relative_path.parts[:-1]))
else:
return importlib.import_module(
".".join(relative_path.parts[:-1] + (relative_path.stem,))
)
else:
return importlib.import_module(package)
class PluginRegistry:
"""插件注册管理器(注册表)"""
def __init__(self):
self._music_input_plugins: Dict[str, MusicInputPluginBase] = {}
self._track_input_plugins: Dict[str, TrackInputPluginBase] = {}
self._music_operate_plugins: Dict[str, MusicOperatePluginBase] = {}
self._track_operate_plugins: Dict[str, TrackOperatePluginBase] = {}
self._music_output_plugins: Dict[str, MusicOutputPluginBase] = {}
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
self._service_plugins: Dict[str, ServicePluginBase] = {}
self._library_plugins: Dict[str, LibraryPluginBase] = {}
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
"""迭代器,返回所有插件"""
return iter(
(
(PluginTypes.FUNCTION_MUSIC_IMPORT, self._music_input_plugins),
(PluginTypes.FUNCTION_TRACK_IMPORT, self._track_input_plugins),
(PluginTypes.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins),
(PluginTypes.FUNCTION_TRACK_OPERATE, self._track_operate_plugins),
(PluginTypes.FUNCTION_MUSIC_EXPORT, self._music_output_plugins),
(PluginTypes.FUNCTION_TRACK_EXPORT, self._track_output_plugins),
(PluginTypes.SERVICE, self._service_plugins),
(PluginTypes.LIBRARY, self._library_plugins),
)
)
@staticmethod
def _register_plugin(cls_dict: dict, plg_class: type, plg_id: str) -> None:
"""注册插件"""
if plg_id in cls_dict:
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
raise PluginRegisteredError(
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件!".format(
plg_id, plg_class.metainfo
)
)
cls_dict[plg_id] = plg_class()
def register_music_input_plugin(
self,
plugin_class: type,
plugin_id: str,
) -> None:
"""注册输入插件-整首曲目"""
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
def register_track_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册输入插件-单个音轨"""
self._register_plugin(self._track_input_plugins, plugin_class, plugin_id)
def register_music_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册曲目处理插件"""
self._register_plugin(self._music_operate_plugins, plugin_class, plugin_id)
def register_track_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册音轨处理插件"""
self._register_plugin(self._track_operate_plugins, plugin_class, plugin_id)
def register_music_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册输出插件-整首曲目"""
self._register_plugin(self._music_output_plugins, plugin_class, plugin_id)
def register_track_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册输出插件-单个音轨"""
self._register_plugin(self._track_output_plugins, plugin_class, plugin_id)
def register_service_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册服务插件"""
self._register_plugin(self._service_plugins, plugin_class, plugin_id)
def register_library_plugin(self, plugin_class: type, plugin_id: str) -> None:
"""注册支持库插件"""
self._register_plugin(self._library_plugins, plugin_class, plugin_id)
@staticmethod
def _get_io_plugin_by_format(
plugin_regdict: Dict[str, T_IOPlugin], fpath_or_format: Union[Path, str]
) -> Generator[T_IOPlugin, None, None]:
if isinstance(fpath_or_format, str):
return (
plugin
for plugin in plugin_regdict.values()
if plugin.can_handle_format(fpath_or_format)
)
elif isinstance(fpath_or_format, Path):
return (
plugin
for plugin in plugin_regdict.values()
if plugin.can_handle_file(fpath_or_format)
)
else:
raise ParameterTypeError(
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
type(fpath_or_format), fpath_or_format
)
)
def get_music_input_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[MusicInputPluginBase, None, None]:
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
return self._get_io_plugin_by_format(
self._music_input_plugins, filepath_or_format
)
def get_track_input_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[TrackInputPluginBase, None, None]:
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
return self._get_io_plugin_by_format(
self._track_input_plugins, filepath_or_format
)
def get_music_output_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[MusicOutputPluginBase, None, None]:
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
return self._get_io_plugin_by_format(
self._music_output_plugins, filepath_or_format
)
def get_track_output_plugin_by_format(
self, filepath_or_format: Union[Path, str]
) -> Generator[TrackOutputPluginBase, None, None]:
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
return self._get_io_plugin_by_format(
self._track_output_plugins, filepath_or_format
)
def _get_plugin_by_name(
self,
plugin_regdict: Mapping[str, T_Plugin],
plugin_name: str,
plugin_usage: str = "",
) -> T_Plugin:
"""通过指定名称,以获取对应的插件,当名称重叠时,取版本号最大的"""
try:
return max(
[
plugin
for plugin in plugin_regdict.values()
if plugin.metainfo.name == plugin_name
],
key=lambda plugin: plugin.metainfo.version,
)
except ValueError:
raise PluginInstanceNotFoundError(
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
)
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(
self._music_input_plugins, plugin_name, "导入全曲"
)
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPluginBase:
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(
self._track_input_plugins, plugin_name, "导入单轨"
)
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePluginBase:
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(
self._music_operate_plugins, plugin_name, "处理整个曲目"
)
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePluginBase:
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(
self._track_operate_plugins, plugin_name, "处理单个音轨"
)
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPluginBase:
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(
self._music_output_plugins, plugin_name, "导出完整曲目"
)
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPluginBase:
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(
self._track_output_plugins, plugin_name, "导出单个音轨"
)
def get_service_plugin(self, plugin_name: str) -> ServicePluginBase:
"""获取服务用插件,当名称重叠时,取版本号最大的"""
return self._get_plugin_by_name(self._service_plugins, plugin_name, "提供服务")
def get_library_plugin(self, plugin_name: str) -> LibraryPluginBase:
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
return self._get_plugin_by_name(
self._library_plugins, plugin_name, "作为依赖库"
)
def supported_input_formats(self) -> Set[str]:
"""所有支持的导入格式"""
return set(
chain.from_iterable(
plugin.supported_formats
for plugin in chain(
self._music_input_plugins.values(),
self._track_input_plugins.values(),
)
)
)
def supported_output_formats(self) -> Set[str]:
"""所有支持的导出格式"""
return set(
chain.from_iterable(
plugin.supported_formats
for plugin in chain(
self._music_output_plugins.values(),
self._track_output_plugins.values(),
)
)
)
_global_plugin_registry = PluginRegistry()
"""全局插件注册表实例"""
def __plugin_regist_decorator(plg_id: str, rgst_func: Callable[[type, str], None]):
def decorator(cls):
global _global_plugin_registry
cls.id = plg_id
rgst_func(cls, plg_id)
return cls
return decorator
def music_input_plugin(plugin_id: str):
"""全曲输入用插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_music_input_plugin
)
def track_input_plugin(plugin_id: str):
"""单轨输入用插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_track_input_plugin
)
def music_operate_plugin(plugin_id: str):
"""全曲处理用插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_music_operate_plugin
)
def track_operate_plugin(plugin_id: str):
"""音轨处理插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_track_operate_plugin
)
def music_output_plugin(plugin_id: str):
"""乐曲输出用插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_music_output_plugin
)
def track_output_plugin(plugin_id: str):
"""音轨输出用插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_track_output_plugin
)
def service_plugin(plugin_id: str):
"""服务插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_service_plugin
)
def library_plugin(plugin_id: str):
"""支持库插件装饰器"""
return __plugin_regist_decorator(
plugin_id, _global_plugin_registry.register_library_plugin
)

25
Musicreater/types.py Normal file
View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
存储 音·创 v3 定义的一些数据类型,可以用于类型检查器
"""
"""
版权所有 © 2026 金羿 & 玉衡Alioth
Copyright © 2026 Eilles & YuhengAlioth
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
FittingFunctionType = Callable[[float], float]
"""
拟合函数类型
"""

183
README.md
View File

@@ -1,115 +1,136 @@
# 音·创 Musicreater [Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
[Bilibili: 玉衡Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge
[![Licence: Apache (shields.io)](https://img.shields.io/badge/Licence-Apache-blueviolet)](https://choosealicense.com/licenses/apache-2.0/) <h1 align="center">· Musicreater </h1>
[![Bilibili: 凌云金羿 (shields.io)]( https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-blueviolet)](https://space.bilibili.com/397369002/)
[![Bilibili: 诸葛亮与八卦阵 (shields.io)](https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-blueviolet)](https://space.bilibili.com/604072474) <p align="center">
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) <img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png">
</img>
</p>
<h3 align="center">一款免费开源的我的世界数字音频支持库</h3>
<p align="center">
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
<a href='https://gitee.com/TriM-Organization/Musicreater'>
<img align="right" src='https://gitee.com/TriM-Organization/Musicreater/widgets/widget_1.svg' alt='Fork me on Gitee'>
</img>
</a>
<p>
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474)
[![CodeStyle: black]](https://github.com/psf/black)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
[![][release]](../../releases)
[![GiteeStar](https://gitee.com/TriM-Organization/Musicreater/badge/star.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/stargazers)
[![GiteeFork](https://gitee.com/TriM-Organization/Musicreater/badge/fork.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/members)
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/forks)
简体中文🇨🇳 | [English🇬🇧](README_EN.md)
## 介绍 🚀
· 是一款免费开源的针对 **我的世界** 音乐的支持库
欢迎加群[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
> **注意** 本仓库内的项目仅仅是支持库其用户为基岩版音乐相关软件的开发者
>
> 面向常规用户的 **基岩版音乐转换工具** 请参阅[伶伦转换器](../../../Linglun-Converter)
>
> 我们也正在开发面向高级用户的 **基岩版音乐编辑工具**数字音频工作站[伶伦](../../../LinglunStudio)
## 安装 🔳
### 介绍 - 使用 pypi
· Musicreater 是由凌天之云创新应用软件开发团队开发的一款免费开源的 **我的世界基岩版** 音乐制作软件 ```bash
pip install --upgrade Musicreater
```
欢迎加群861684859 - 如果无法更新最新可以尝试
### 作者 ```bash
pip install --upgrade -i https://pypi.python.org/simple Musicreater
```
金羿 Eilles我的世界基岩版指令师个人开发者B站不知名UP主在校高中生 - 克隆仓库并安装最新内容但**不推荐**
诸葛亮与八卦阵 bgArray我的世界基岩版玩家喜欢编程和音乐深圳初一学生 ```bash
git clone https://gitee.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
### 软件架构 以上命令中 `python``pip` 请依照各个环境不同灵活更换可能为`python3``pip3`之类
软件采用 *Python* 作为第一语言目前还没有使用其他语言辅助使用 *Tkinter* 为图形库 ## 文档 📄
支持 Windows7+ 以及各个支持 Python3.8 Linux [生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
***各位开发人员注意多语言支持请使用函数`_`加载文字如需补充请在简体中文的语言文件(zh-CN.lang)中补充*** [仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
## 作者 ✒
### 安装教程 **金羿 Eilles**我的世界基岩版指令作者个人开发者B 站不知名 UP
下载音·创自动安装器将其放在你希望安装音·创的位置运行后将自动安装 **玉衡Alioth Alioth**我的世界基岩版玩家喜欢编程和音乐学生
提示下载源最好选择2GitHub **偷吃不是Touch Touch**我的世界基岩版指令制作者提供测试支持
### 从源代码运行教程 ## 致谢 🙏
#### Windows7+ 本致谢列表排名无顺序
0. [Gitee下载需要登陆](https://gitee.com/EillesWan/Musicreater) - 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误 bug 并指正
[Github下载](https://github.com/EillesWan/Musicreater)本程序源代码 - 感谢由 **Charlie_Ping 查理平** 带来的 BDX 文件转换参考以及 MIDI-我的世界对应乐器 参考表格
1. 安装Python 3.8.10 - 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
[下载64位Python安装包](https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe) - 感谢由 **[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法我们将其改编并应用同时感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力让我们在原本一骑绝尘的摸鱼道路上转向开发
[下载32位Python安装包](https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe) - 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题辅助我们找到了视窗操作系统下的兼容性问题感谢其反馈延迟播放器出现的重大问题让我们得以修改全部延迟播放错误尤其感谢他对于我们的软件的大力宣传
2. 以管理员身份运行 补全库.py : - 感谢 **Ammelia 艾米利亚**\<QQ2838334637\> 敦促我们进行新的功能开发并为新功能提出了非常优秀的大量建议以及提供的 BDX 导入测试支持为我们的新结构生成算法提供了大量的实际理论支持
- 点击 开始 菜单搜索 `命令提示符` - 感谢 **[神羽 SnowyKami](https://www.sfkm.me/)** 对我们项目的支持与宣传非常感谢他为我们提供的服务器
- 右键点击 `命令提示符` 左键点击 以管理员身份运行 - 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误并提醒我们修复一个一直存在的大 bug
- 补全库.py 拖拽入开启的窗口按下回车 - 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误并提醒修复 bug
3. 等待安装完成后双击运行 Musicreater.py - 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题
- <table><tr><td>感谢 **油炸**&lt;QQ2836146704&gt; 激励我们不断开发新的内容</td><td><img height="50" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg"></td></tr></table>
- 感谢 ****\<QQ237667809\> 反馈在新版本的指令格式下计分板播放器的附加包无法播放的问题
- 感谢 **梦幻duang**\<QQ13753593\> 为我们提供 Java 1.12.2 版本命令格式参考
- 感谢 [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio) 项目的开发为我们提供持续的追赶动力
#### Linux > 感谢广大群友为此库提供的测试和建议等
> 若您对我们有所贡献但您的名字没有出现在此列表中请联系我们
0. 若你没有足够优秀的环境推荐先在终端敲 ## 联系 📞
```bash
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install python3
sudo apt-get install python3-pip
sudo apt-get install git
```
1. 若你足够自信该整的都整了就在你想下载此程序的地方打开终端
```bash
sudo git clone https://gitee.com/EillesWan/Musicreater.git
cd Musicreater
python3 补全库.py
python3 Musicreater.py
```
若遇到库中的问题欢迎在[](https://gitee.com/TriM-Organization/Musicreater/issues/new)提出你的 issue
### 使用说明 如果需要与开发组进行交流欢迎加入我们的[开发闲聊 Q ](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
1. 直接运行就好 亦可以联系我们[睿乐组织官方邮箱](mailto:TriM-Organization@hotmail.com)取得进一步联系
2. 看得懂简体中文字的不一定全会用
3. 最好要懂一点点英文
---
### 诸葛亮与八卦阵的说明(不必要) 此项目并非一个官方 我的世界_Minecraft_项目
1. 首先这里的提示是给想使用多音色资源包的人的如果你想用就请下载 [神羽资源包神羽自己的链接](https://pan.baidu.com/s/11uoq5zwN7c3rX-98DqVpJg)提取码:ek3t 此项目不隶属或关联于 Mojang Studios 微软
2. 下载到你自己电脑上某个位置可以不放置于本项目下音色资源包较大可以选取只下载
`神羽资源包_乐器音源的资源包\羽音缭绕-midiout_25.0` 这个文件夹再嫌麻烦的话也可以只下载其中的
`神羽资源包_乐器\音源的资源包\羽音缭绕-midiout_25.0\mcpack(国际版推荐)格式_25.0` 或者
`神羽资源包_乐器\音源的资源包\羽音缭绕-midiout_25.0\zip格式_25.0`
4. 接下来就是关键了*音创*中绑定资源包
首先先打开 *音创*->帮助与疑问->\[神羽资源包位置选择\]选择文件夹... 这时候会跳出选择框
关键来了选择***您下载的`羽音缭绕-midiout_25.0`文件夹或者`mcpack(国际版推荐)格式_25.0``zip格式_25.0`的上级目录***
举个例子我的文件路径是这样的
`L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0`这里面有`神羽资源包_25.0_使用方法.xls`
`mcpack(国际版推荐)格式_25.0``zip格式_25.0`两个文件夹和一个.xls文件而你在音创中
也应该选择这个文件夹**L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0**
6. 如果你想使用音色资源包来制作函数那么解析时你应该用 *音创*->编辑->从midi导入音轨且用新方法解析
然后再使用 *音创*->函数->下面的四个新函数
### 致谢 此项目亦不隶属或关联于 网易
1. 感谢由 [Fuckcraft](https://github.com/fuckcraft) 鸣凤鸽子 带来的我的世界websocket服务器功能 Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为必要的介绍性使用
2. 感谢 昀梦\<QQ1515399885\> 找出指令生成错误bug并指正
3. 感谢由 Charlie_Ping 查理平 带来的bdx转换功能
4. 感谢由 CMA_2401PT 带来的 BDXWorkShop 供本程序对于bdx操作的指导
5. 感谢由 Miracle Plume 神羽 \<QQshenyu40403\>带来的羽音缭绕基岩版音色资源包
6. 感谢 Arthur Morgan 对本程序的排错提出了最大的支持
7. 感谢广大群友为此程序提供的测试等支持
8. 若您对我们有所贡献但您的名字没有显示在此列表中请联系我
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易璀璨网络科技有限公司
### 作者\<*金羿*\>(W-YI)联系方式 NOT AN OFFICIAL MINECRAFT PRODUCT.
1. QQ 2647547478 NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
2. 电邮 EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
3. 微信 WYI_DoctorYI
### 作者\<*诸葛亮与八卦阵*\>(bgArray) 联系方式 NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
1. QQ 4740437765

138
README_EN.md Normal file
View File

@@ -0,0 +1,138 @@
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-00A1E7?style=for-the-badge
[Bilibili: Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge
<h1 align="center">
· Musicreater
</h1>
<p align="center">
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
</img>
</p>
<h3 align="center">A free and open-source library for handling with <i>Minecraft</i> digital music.</h3>
<p align="center">
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
</img>
<p>
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
[![][Bilibili: Alioth]](https://space.bilibili.com/604072474)
[![CodeStyle: black]](https://github.com/psf/black)
[![][python]](https://www.python.org/)
[![][license]](LICENSE)
[![][release]](../../releases)
[![GiteeStar](https://gitee.com/TriM-Organization/Musicreater/badge/star.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/stargazers)
[![GiteeFork](https://gitee.com/TriM-Organization/Musicreater/badge/fork.svg?theme=gray)](https://gitee.com/TriM-Organization/Musicreater/members)
[![GitHub Repo stars](https://img.shields.io/github/stars/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/stargazers)
[![GitHub Repo Forks](https://img.shields.io/github/forks/TriM-Organization/Musicreater?color=white&logo=GitHub&style=plastic)](https://github.com/TriM-Organization/Musicreater/forks)
[简体中文 🇨🇳](README.md) | English🇬🇧
**Notice that the localizations of documents may probably NOT be up-to-date. The original document is in Chinese.**
## Introduction🚀
Musicreater is a free open-source library used for handling with digital musics that being able to be played in _Minecraft_.
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
> **NOTICE** The project inside this repository is only the support library, which is mainly for the developers of music related software for _Minecraft: Bedrock Edition_.
>
> The _Bedrock Edition music convertor_ which is for common users is in [Linglun Converter](../../../Linglun-Converter).
>
> We are also developing a _BE music editor_ (digital audio workstation): [Linglun](../../../LinglunStudio)
## Installation 🔳
- Via pypi
```bash
pip install Musicreater --upgrade
```
- If above command cannot fetch latest version, try:
```bash
pip install -i https://pypi.python.org/simple Musicreater --upgrade
```
- Clone repo and Install (Latest but **NOT RECOMMANDED**):
```bash
git clone https://github.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
Commands such as `python``pip` could be changed to some like `python3` or `pip3` according to the difference of platforms.
## Documentation 📄
(Not in English yet)
[生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
[仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
### Authors ✒
**Eilles (金羿)**A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
**Alioth (玉衡Alioth)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
**Touch (偷吃不是 Touch)**: A man who is good at using command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
## Acknowledgements 🙏
This list is not in any order.
- Thank _昀梦_\<QQ1515399885\> for finding and correcting the bugs in the commands that _Musicreater_ generated.
- Thank _Charlie_Ping 查理平_ for the bdx convert function for reference, and the reference chart that's used to convert the mid's instruments into Minecraft's instruments.
- Thank _[CMA_2401PT](https://github.com/CMA2401PT)_ for BDXWorkShop for reference of the .bdx structure's operation, and his guidance in some aspects of our development.
- Thank _[Dislink Sforza](https://github.com/Dislink) 断联·斯福尔扎_ \<QQ1600515314\> for his midi analysis algorithm brought to us, we had adapted it and made it applied in one of our working method; Also, thank him for the [WebConvertor](https://dislink.github.io/midi2bdx/) which brought us so much pressure and power to develop as well as update our projects better, instead of loaf on our project.
- Thank _Mono_\<QQ738893087\> for reporting problems while installing
- Thank _Ammelia 艾米利亚_\<QQ2838334637\> for urging us to develop new functions, and put forward a lot of excellent suggestions for new functions, as well as the BDX file's importing test support provided, which has given a lot of practical theoretical support for our new Structure Generating Algorithm
- Thank _[神羽](https://gitee.com/snowykami) [SnowyKami](https://github.com/snowyfirefly)_ for supporting and promoting our project, and also thanks him for his server which given us to use for free.
- Thank _指令师\_苦力怕 playjuice123_\<QQ240667197\> for finding bugs within our code, and noticed us to repair a big problem.
- Thank _雷霆_\<QQ3555268519\> for his annoying and provoking operations which may awake some problems within the program by chance and reminding us to repair.
- Thank _小埋_\<QQ2039310975\> for reporting the empty add-on packs title and description problem.
- <table><tr><td>Thank <i>油炸</i> &lt;QQ2836146704&gt; for inspiring us to constantly develop something new.</td><td><img width="260" src="https://foruda.gitee.com/images/1695478907647543027/08ea9909_9911226.jpeg" alt="The groupmate on the picture was saying that our convert-QQ-bot had once brought him great convinience but now it closed down by some reason so he was feeling regretful." title="&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;&#10;&quot;Until lose, I finally know cannot chase back what I needs&quot;"></td><td><small>&quot;It was once, a convert-QQ-bot is just in front my eyes&quot;<br>&quot;Until lose, I finally know cannot chase back what I needs&quot;</small></td></tr></table>
- Thank _雨_\<QQ237667809\> for give us report that under the new `execute` command format that the scoreboard player's add-on packs cannot play correctly.
- Thank _梦幻duang_\<QQ13753593\> for providing us with his knowlodeg of the command format in Minecraft: Java Edition Version 1.12.2.
- Thank [_Open Note Block Studio_](https://github.com/OpenNBS/NoteBlockStudio)'s Project for giving us the power and energy of continual developing.
> Thanks for the support and help of a lot of groupmates
> If you have given contributions but have not been in the list, please contact us!
## Contact Us 📞
Meet problems? Welcome to give out your issue [here](../../issues/new)!
Want to get in contact of developers? Welcome to join our [Chat QQ group](https://jq.qq.com/?_wv=1027&k=hpeRxrYr).
Or contact us via [TriM-Org Official Email](mailto:TriM-Organization@hotmail.com)!
---
NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
此项目并非一个官方 我的世界_Minecraft_项目
此项目不隶属或关联于 Mojang Studios 微软
此项目亦不隶属或关联于 网易 相关
Minecraft Mojang Synergies AB 的商标此项目中所有对于我的世界Minecraft等相关称呼均为必要的介绍性使用
- 上文提及的 网易 公司指代的是在中国大陆运营我的世界中国版的上海网之易璀璨网络科技有限公司

View File

@@ -1,114 +0,0 @@
# Musicreater
[![Licence: Apache (shields.io)](https://img.shields.io/badge/Licence-Apache-blueviolet)](https://choosealicense.com/licenses/apache-2.0/)
[![Bilibili: 凌云金羿 (shields.io)]( https://img.shields.io/badge/Bilibili-%E5%87%8C%E4%BA%91%E9%87%91%E7%BE%BF-blueviolet)](https://space.bilibili.com/397369002/)
[![Bilibili: 诸葛亮与八卦阵 (shields.io)](https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-blueviolet)](https://space.bilibili.com/604072474)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
### Introduction
Musicreater(·) is an Eilles(*W-YI*)'s app that is used for creating musics in **Minecraft: Bedrock Edition**.
Welcome to join our QQ group: 861684859
### Author
Eilles (金羿)The main author, who developed the most and especially the principal parts of Musicreater, and its predecessors, which has been called as "*Minecraft Function Music Maker*", "*Note Fun Creater*", "*Note World Creater*"(now ,they are united as ***Musicreater***!)
bgArray "诸葛亮与八卦阵": Fix bugs, improve code aesthetics, add new functions, change data format, etc.
### Framework
Using *Python* to develop, using Tkinter as a graphics library.
Support Windows7+ && Linux (that supports Python3.8)
***ATTENTION TO DEVELOPERS!!! TO SUPPORT DIFFERENT LANGUAGES, PLEASE USE CONSTANT `READABLETEXT` TO OUTPUT!!! IF YOU NEED TO SUPPLEMENT, PLEASE ADD THEM IN SIMPLEFIED CHINESE\'S LANGUAGE FILE(zhCN.py), WHEATHER WHAT LANGUAGE YOU USE!!!***
### Installation
Download the *[MSCT Auto Installer]()*, put it in a directory that you want to install *Musicreater* into.
Then run the auto installer and it will help you to install the *Musicreator*
Tips: You'd better choose the 2GitHub download source
### Run with Source Code
#### Windows
0. First, download the source code pack of Musicreater.
[Download from Gitee (Need to Login)](https://gitee.com/EillesWan/Musicreater/repository/archive/master.zip)
[Download from Github](https://github.com/EillesWan/Musicreater/archive/refs/heads/master.zip)
1. Install Python 3.8.10
[Download the 64-bit Python Installer](https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe)
[Download the 32-bit Python Installer](https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe)
2. After completing installation, we need to install the libraries :
- Open "Start Menu" and find `cmd`
- Run `cmd` as Administrator
- Drag "补全库.py" into the opened window and press Enter
3. After completing installationdouble click Musicreater.py to run
#### Linux
0. If you 're not sure whether your environment is good enough, please run these commands on Terminal
```bash
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install python3
sudo apt-get install python3-pip
sudo apt-get install git
```
1. Now if you are confident enough about your runtime environment, open Terminal on the place which you want to download Musicreater, and run these
```bash
sudo git clone https://gitee.com/EillesWan/Musicreater.git
cd Musicreater
python3 补全库.py
python3 Musicreater.py
```
### Instructions
1. Just make u understand the Chinese
2. If u dont understand, u can come to the QQ group or email me to ask questions
3. The English Edition is comming soon.
### Explanation of bgArray 诸葛亮与八卦阵 (unnecessary)
1. First! The tips here are for those who want to use the multi tone resource package, [Shenyu resource package (Shenyu's own link)](https://pan.baidu.com/s/11uoq5zwN7c3rX-98DqVpJg) \(Extraction code: `ek3t`\)
2. Download it to any location on your PC. Note that it does ***not*** need to be placed in the directory where *Musicreater* are. The audio resource package is large, so you can choose to download only:`神羽资源包_乐器音源的资源包\羽音缭绕-midiout_25.0`.
Also, you can download only `神羽资源包_乐器\音源的资源包\羽音缭绕-midiout_25.0\mcpack(国际版推荐)格式_25.0` or
`神羽资源包_乐器\音源的资源包\羽音缭绕-midiout_25.0\zip格式_25.0`.
4. The next step is the most IMPORTANT: to bind the resource package to *Musicreater*
First, open *Musicreater*->Q&A->Select \[MiraclePlumeResourcePack\]... .At this time, in the selection box,
the IMPORTANT step comes, select: ***The directory you downloaded: `羽音缭绕-midiout_25.0`, or also the parent directory `mcpack(国际版推荐)格式_25.0`or`zip格式_25.0`***
For example, my file path is as follows:
`L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0` and in the directory, there are two folders and one .xls file:
`神羽资源包_25.0_使用方法.xls`, `mcpack(国际版推荐)格式_25.0` and `zip格式_25.0`, so in *Musicreater* you should also select this folder: **L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0**
6. If you want to use the Miracle Plume Bedrock Edition Audio Resource Pack to make .mcfunction s, you should use Musicreater -> Edit - > Import audio tracks from MIDI and parse them with a new method, and then use it
Musicreater - > function (package) - > the following four new functions
### Thanks
1. Thank [Fuckcraft](https://github.com/fuckcraft) *(鸣凤鸽子 ,etc)* for the function of Creating the Websocket Server for Minecraft: Bedrock Edition.
- *!! They have given me the rights to directly copy the lib into Musicreater*
2. Thank *昀梦*\<QQ1515399885\> for finding and correcting the bugs in the commands that *Musicreater* Created.
3. Thank *Charlie_Ping 查理平* for bdx convert funtion.
4. Thank *CMA_2401PT* for BDXWorkShop as the .bdx structure's operation guide.
5. Thank *Miracle Plume 神羽* \<QQshenyu40403\> for the Miracle Plume Bedrock Edition Audio Resource Pack
6. Thank *Arthur Morgan* for his/her biggest support for the debugging of Musicreater
7. Thanks for a lot of groupmates who support me and help me to test the program.
8. If you have give me some help but u haven't been in the list, please contact me.
### Contact *Eilles(W-YI)*(金羿)
1. QQ 2647547478
2. E-mail EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
3. WeChat WYI_DoctorYI
### Contact *bgArray*(诸葛亮与八卦阵)
1. QQ 4740437765

48
TO-DO.md Normal file
View File

@@ -0,0 +1,48 @@
# 任务清单
## 待办事项
- 乐曲文件格式设计
目前想到的是
1. 使用 `.MCT` 作为项目文件的后缀然后考虑一下格式是否和之前的 MusicSequence 兼容如果兼容的话可以照旧用 `.MSQ`如不的话可以试试想一个新的后缀名作为数据文件后缀
2. 要求数据文件支持完全流式读入
- 音轨静音处理
当前没有处理
- 优化音轨的存储方式
当前是用列表且每一次变动元素都要重新排序这样消耗太大了需要优化改用最小堆形式heapq
- 移植 v2 功能到内置插件
目前 v2 的功能有很多都要移植到 v3
1. 导入 Midi 文件到全曲
2. 导入 Midi 文件到指定轨道
3. 导出到延迟播放器的结构文件MCSTRUCTUREBDX
4. 导出到延迟播放器的附加包
5. 导出到积分板播放器的以上两种形式
6. 导出到中继器播放器的以上两种形式
7. WebSocket 播放器中播放
8. 导出到支持神羽资源包的以上 7 种形式
9. 对于 Midi 歌词的实验性功能
10. 对于 Java 版本适配的实验性功能
11. 对于听感优化的实验性功能插值偏移
- 测试参数曲线的功能
- 支持导出音符盒构成的音乐
- 支持导出成 schematic 结构
## 讨论
1. [x] 是否应该在插件注册表 PluginRegistry 中采用 `Dict[插件名, 插件对象]` 的形式存储插件
当前不采用这种方式是认为可以兼容一些极端场景下用户将一堆同名插件放在一起的情况但是就算是插件放在一起我们也可以有选择地读入注册表比如依照版本号只读取最高版本的插件并不需要全部存储在插件注册表中所以其实用字典来存储是有利的
**当前已解决**
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了我写完所有代码之后才想到插件名称是中文还带空格的任意字符串
2. 服务插件到底该怎么写总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧
3. 插件依赖性的优化目前没有处理各个插件依赖关系的问题如果插件之间彼此依赖要怎么做
我的想法是这个依赖的处理由调用端来完成比如我们的 伶伦工作站 是以 · 为核心的一个可视化数字音频工作站
那么应该由伶伦来处理依赖关系并加载之

View File

@@ -1,24 +0,0 @@
instrument_list = {'0': 'harp', '1': 'harp', '2': 'pling', '3': 'harp', '4': 'pling', '5': 'pling', '6': 'harp',
'7': 'harp',
'8': 'share', '9': 'harp', '10': 'didgeridoo', '11': 'harp', '12': 'xylophone', '13': 'chime',
'14': 'harp', '15': 'harp', '16': 'bass', '17': 'harp', '18': 'harp', '19': 'harp', '20': 'harp',
'21': 'harp', '22': 'harp', '23': 'guitar', '24': 'guitar', '25': 'guitar', '26': 'guitar',
'27': 'guitar', '28': 'guitar', '29': 'guitar', '30': 'guitar', '31': 'bass', '32': 'bass',
'33': 'bass',
'34': 'bass', '35': 'bass', '36': 'bass', '37': 'bass', '38': 'bass', '39': 'bass', '40': 'harp',
'41': 'harp', '42': 'harp', '43': 'harp', '44': 'iron_xylophone', '45': 'guitar', '46': 'harp',
'47': 'harp', '48': 'guitar', '49': 'guitar', '50': 'bit', '51': 'bit', '52': 'harp', '53': 'harp',
'54': 'bit', '55': 'flute', '56': 'flute', '57': 'flute', '58': 'flute', '59': 'flute',
'60': 'flute',
'61': 'flute', '62': 'flute', '63': 'flute', '64': 'bit', '65': 'bit', '66': 'bit', '67': 'bit',
'68': 'flute', '69': 'harp', '70': 'harp', '71': 'flute', '72': 'flute', '73': 'flute', '74': 'harp',
'75': 'flute', '76': 'harp', '77': 'harp', '78': 'harp', '79': 'harp', '80': 'bit', '81': 'bit',
'82': 'bit', '83': 'bit', '84': 'bit', '85': 'bit', '86': 'bit', '87': 'bit', '88': 'bit',
'89': 'bit',
'90': 'bit', '91': 'bit', '92': 'bit', '93': 'bit', '94': 'bit', '95': 'bit', '96': 'bit',
'97': 'bit',
'98': 'bit', '99': 'bit', '100': 'bit', '101': 'bit', '102': 'bit', '103': 'bit', '104': 'harp',
'105': 'banjo', '106': 'harp', '107': 'harp', '108': 'harp', '109': 'harp', '110': 'harp',
'111': 'guitar', '112': 'harp', '113': 'bell', '114': 'harp', '115': 'cow_bell', '116': 'basedrum',
'117': 'bass', '118': 'bit', '119': 'basedrum', '120': 'guitar', '121': 'harp', '122': 'harp',
'123': 'harp', '124': 'harp', '125': 'hat', '126': 'basedrum', '127': 'snare'}

View File

@@ -1,92 +0,0 @@
zip_name = {-1: '-1.Acoustic_Kit_打击乐.zip', 0: '0.Acoustic_Grand_Piano_大钢琴.zip', 1: '1.Bright_Acoustic_Piano_亮音大钢琴.zip',
10: '10.Music_Box_八音盒.zip', 100: '100.FX_brightness_合成特效-亮音.zip', 101: '101.FX_goblins_合成特效-小妖.zip',
102: '102.FX_echoes_合成特效-回声.zip', 103: '103.FX_sci-fi_合成特效-科幻.zip', 104: '104.Sitar_锡塔尔.zip',
105: '105.Banjo_班卓.zip', 106: '106.Shamisen_三味线.zip', 107: '107.Koto_筝.zip', 108: '108.Kalimba_卡林巴.zip',
109: '109.Bagpipe_风笛.zip', 11: '11.Vibraphone_电颤琴.zip', 110: '110.Fiddle_古提琴.zip', 111: '111.Shanai_唢呐.zip',
112: '112.Tinkle_Bell_铃铛.zip', 113: '113.Agogo_拉丁打铃.zip', 114: '114.Steel_Drums_钢鼓.zip',
115: '115.Woodblock_木块.zip', 116: '116.Taiko_Drum_太鼓.zip', 117: '117.Melodic_Tom_嗵鼓.zip',
118: '118.Synth_Drum_合成鼓.zip', 119: '119.Reverse_Cymbal_镲波形反转.zip', 12: '12.Marimba_马林巴.zip',
13: '13.Xylophone_木琴.zip', 14: '14.Tubular_Bells_管钟.zip', 15: '15.Dulcimer_扬琴.zip',
16: '16.Drawbar_Organ_击杆风琴.zip', 17: '17.Percussive_Organ_打击型风琴.zip', 18: '18.Rock_Organ_摇滚风琴.zip',
19: '19.Church_Organ_管风琴.zip', 2: '2.Electric_Grand_Piano_电子大钢琴.zip', 20: '20.Reed_Organ_簧风琴.zip',
21: '21.Accordion_手风琴.zip', 22: '22.Harmonica_口琴.zip', 23: '23.Tango_Accordian_探戈手风琴.zip',
24: '24.Acoustic_Guitar_(nylon)_尼龙弦吉他.zip', 25: '25.Acoustic_Guitar(steel)_钢弦吉他.zip',
26: '26.Electric_Guitar_(jazz)_爵士乐电吉他.zip', 27: '27.Electric_Guitar_(clean)_清音电吉他.zip',
28: '28.Electric_Guitar_(muted)_弱音电吉他.zip', 29: '29.Overdriven_Guitar_驱动音效吉他.zip',
3: '3.Honky-Tonk_Piano_酒吧钢琴.zip', 30: '30.Distortion_Guitar_失真音效吉他.zip', 31: '31.Guitar_Harmonics_吉他泛音.zip',
32: '32.Acoustic_Bass_原声贝司.zip', 33: '33.Electric_Bass(finger)_指拨电贝司.zip',
34: '34.Electric_Bass(pick)_拨片拨电贝司.zip', 35: '35.Fretless_Bass_无品贝司.zip', 36: '36.Slap_Bass_A_击弦贝司A.zip',
37: '37.Slap_Bass_B_击弦贝司B.zip', 38: '38.Synth_Bass_A_合成贝司A.zip', 39: '39.Synth_Bass_B_合成贝司B.zip',
4: '4.Electric_Piano_1_电钢琴A.zip', 40: '40.Violin_小提琴.zip', 41: '41.Viola_中提琴.zip', 42: '42.Cello_大提琴.zip',
43: '43.Contrabass_低音提琴.zip', 44: '44.Tremolo_Strings_弦乐震音.zip', 45: '45.Pizzicato_Strings_弦乐拨奏.zip',
46: '46.Orchestral_Harp_竖琴.zip', 47: '47.Timpani_定音鼓.zip', 48: '48.String_Ensemble_A_弦乐合奏A.zip',
49: '49.String_Ensemble_B_弦乐合奏B.zip', 5: '5.Electric_Piano_2_电钢琴B.zip', 50: '50.SynthStrings_A_合成弦乐A.zip',
51: '51.SynthStrings_B_合成弦乐B.zip', 52: '52.Choir_Aahs_合唱“啊”音.zip', 53: '53.Voice_Oohs_人声“哦”音.zip',
54: '54.Synth_Voice_合成人声.zip', 55: '55.Orchestra_Hit_乐队打击乐.zip', 56: '56.Trumpet_小号.zip',
57: '57.Trombone_长号.zip', 58: '58.Tuba_大号.zip', 59: '59.Muted_Trumpet_弱音小号.zip',
6: '6.Harpsichord_拨弦古钢琴.zip', 60: '60.French_Horn_圆号.zip', 61: '61.Brass_Section_铜管组.zip',
62: '62.Synth_Brass_A_合成铜管A.zip', 63: '63.Synth_Brass_A_合成铜管B.zip', 64: '64.Soprano_Sax_高音萨克斯.zip',
65: '65.Alto_Sax_中音萨克斯.zip', 66: '66.Tenor_Sax_次中音萨克斯.zip', 67: '67.Baritone_Sax_上低音萨克斯.zip',
68: '68.Oboe_双簧管.zip', 69: '69.English_Horn_英国管.zip', 7: '7.Clavinet_击弦古钢琴.zip', 70: '70.Bassoon_大管.zip',
71: '71.Clarinet_单簧管.zip', 72: '72.Piccolo_短笛.zip', 73: '73.Flute_长笛.zip', 74: '74.Recorder_竖笛.zip',
75: '75.Pan_Flute_排笛.zip', 76: '76.Bottle_Blow_吹瓶口.zip', 77: '77.Skakuhachi_尺八.zip', 78: '78.Whistle_哨.zip',
79: '79.Ocarina_洋埙.zip', 8: '8.Celesta_钢片琴.zip', 80: '80.Lead_square_合成主音-方波.zip',
81: '81.Lead_sawtooth_合成主音-锯齿波.zip', 82: '82.Lead_calliope_lead_合成主音-汽笛风琴.zip',
83: '83.Lead_chiff_lead_合成主音-吹管.zip', 84: '84.Lead_charang_合成主音5-吉他.zip', 85: '85.Lead_voice_合成主音-人声.zip',
86: '86.Lead_fifths_合成主音-五度.zip', 87: '87.Lead_bass+lead_合成主音-低音加主音.zip', 88: '88.Pad_new_age_合成柔音-新时代.zip',
89: '89.Pad_warm_合成柔音-暖音.zip', 9: '9.Glockenspiel_钟琴.zip', 90: '90.Pad_polysynth_合成柔音-复合成.zip',
91: '91.Pad_choir_合成柔音-合唱.zip', 92: '92.Pad_bowed_合成柔音-弓弦.zip', 93: '93.Pad_metallic_合成柔音-金属.zip',
94: '94.Pad_halo_合成柔音-光环.zip', 95: '95.Pad_sweep_合成柔音-扫弦.zip', 96: '96.FX_rain_合成特效-雨.zip',
97: '97.FX_soundtrack_合成特效-音轨.zip', 98: '98.FX_crystal_合成特效-水晶.zip', 99: '99.FX_atmosphere_合成特效-大气.zip'}
mcpack_name = {-1: '-1.Acoustic_Kit_打击乐.mcpack', 0: '0.Acoustic_Grand_Piano_大钢琴.mcpack',
1: '1.Bright_Acoustic_Piano_亮音大钢琴.mcpack', 10: '10.Music_Box_八音盒.mcpack',
100: '100.FX_brightness_合成特效-亮音.mcpack', 101: '101.FX_goblins_合成特效-小妖.mcpack',
102: '102.FX_echoes_合成特效-回声.mcpack', 103: '103.FX_sci-fi_合成特效-科幻.mcpack', 104: '104.Sitar_锡塔尔.mcpack',
105: '105.Banjo_班卓.mcpack', 106: '106.Shamisen_三味线.mcpack', 107: '107.Koto_筝.mcpack',
108: '108.Kalimba_卡林巴.mcpack', 109: '109.Bagpipe_风笛.mcpack', 11: '11.Vibraphone_电颤琴.mcpack',
110: '110.Fiddle_古提琴.mcpack', 111: '111.Shanai_唢呐.mcpack', 112: '112.Tinkle_Bell_铃铛.mcpack',
113: '113.Agogo_拉丁打铃.mcpack', 114: '114.Steel_Drums_钢鼓.mcpack', 115: '115.Woodblock_木块.mcpack',
116: '116.Taiko_Drum_太鼓.mcpack', 117: '117.Melodic_Tom_嗵鼓.mcpack', 118: '118.Synth_Drum_合成鼓.mcpack',
119: '119.Reverse_Cymbal_镲波形反转.mcpack', 12: '12.Marimba_马林巴.mcpack', 13: '13.Xylophone_木琴.mcpack',
14: '14.Tubular_Bells_管钟.mcpack', 15: '15.Dulcimer_扬琴.mcpack', 16: '16.Drawbar_Organ_击杆风琴.mcpack',
17: '17.Percussive_Organ_打击型风琴.mcpack', 18: '18.Rock_Organ_摇滚风琴.mcpack',
19: '19.Church_Organ_管风琴.mcpack', 2: '2.Electric_Grand_Piano_电子大钢琴.mcpack',
20: '20.Reed_Organ_簧风琴.mcpack', 21: '21.Accordion_手风琴.mcpack', 22: '22.Harmonica_口琴.mcpack',
23: '23.Tango_Accordian_探戈手风琴.mcpack', 24: '24.Acoustic_Guitar_(nylon)_尼龙弦吉他.mcpack',
25: '25.Acoustic_Guitar(steel)_钢弦吉他.mcpack', 26: '26.Electric_Guitar_(jazz)_爵士乐电吉他.mcpack',
27: '27.Electric_Guitar_(clean)_清音电吉他.mcpack', 28: '28.Electric_Guitar_(muted)_弱音电吉他.mcpack',
29: '29.Overdriven_Guitar_驱动音效吉他.mcpack', 3: '3.Honky-Tonk_Piano_酒吧钢琴.mcpack',
30: '30.Distortion_Guitar_失真音效吉他.mcpack', 31: '31.Guitar_Harmonics_吉他泛音.mcpack',
32: '32.Acoustic_Bass_原声贝司.mcpack', 33: '33.Electric_Bass(finger)_指拨电贝司.mcpack',
34: '34.Electric_Bass(pick)_拨片拨电贝司.mcpack', 35: '35.Fretless_Bass_无品贝司.mcpack',
36: '36.Slap_Bass_A_击弦贝司A.mcpack', 37: '37.Slap_Bass_B_击弦贝司B.mcpack', 38: '38.Synth_Bass_A_合成贝司A.mcpack',
39: '39.Synth_Bass_B_合成贝司B.mcpack', 4: '4.Electric_Piano_1_电钢琴A.mcpack', 40: '40.Violin_小提琴.mcpack',
41: '41.Viola_中提琴.mcpack', 42: '42.Cello_大提琴.mcpack', 43: '43.Contrabass_低音提琴.mcpack',
44: '44.Tremolo_Strings_弦乐震音.mcpack', 45: '45.Pizzicato_Strings_弦乐拨奏.mcpack',
46: '46.Orchestral_Harp_竖琴.mcpack', 47: '47.Timpani_定音鼓.mcpack', 48: '48.String_Ensemble_A_弦乐合奏A.mcpack',
49: '49.String_Ensemble_B_弦乐合奏B.mcpack', 5: '5.Electric_Piano_2_电钢琴B.mcpack',
50: '50.SynthStrings_A_合成弦乐A.mcpack', 51: '51.SynthStrings_B_合成弦乐B.mcpack',
52: '52.Choir_Aahs_合唱“啊”音.mcpack', 53: '53.Voice_Oohs_人声“哦”音.mcpack', 54: '54.Synth_Voice_合成人声.mcpack',
55: '55.Orchestra_Hit_乐队打击乐.mcpack', 56: '56.Trumpet_小号.mcpack', 57: '57.Trombone_长号.mcpack',
58: '58.Tuba_大号.mcpack', 59: '59.Muted_Trumpet_弱音小号.mcpack', 6: '6.Harpsichord_拨弦古钢琴.mcpack',
60: '60.French_Horn_圆号.mcpack', 61: '61.Brass_Section_铜管组.mcpack', 62: '62.Synth_Brass_A_合成铜管A.mcpack',
63: '63.Synth_Brass_A_合成铜管B.mcpack', 64: '64.Soprano_Sax_高音萨克斯.mcpack', 65: '65.Alto_Sax_中音萨克斯.mcpack',
66: '66.Tenor_Sax_次中音萨克斯.mcpack', 67: '67.Baritone_Sax_上低音萨克斯.mcpack', 68: '68.Oboe_双簧管.mcpack',
69: '69.English_Horn_英国管.mcpack', 7: '7.Clavinet_击弦古钢琴.mcpack', 70: '70.Bassoon_大管.mcpack',
71: '71.Clarinet_单簧管.mcpack', 72: '72.Piccolo_短笛.mcpack', 73: '73.Flute_长笛.mcpack',
74: '74.Recorder_竖笛.mcpack', 75: '75.Pan_Flute_排笛.mcpack', 76: '76.Bottle_Blow_吹瓶口.mcpack',
77: '77.Skakuhachi_尺八.mcpack', 78: '78.Whistle_哨.mcpack', 79: '79.Ocarina_洋埙.mcpack',
8: '8.Celesta_钢片琴.mcpack', 80: '80.Lead_square_合成主音-方波.mcpack', 81: '81.Lead_sawtooth_合成主音-锯齿波.mcpack',
82: '82.Lead_calliope_lead_合成主音-汽笛风琴.mcpack', 83: '83.Lead_chiff_lead_合成主音-吹管.mcpack',
84: '84.Lead_charang_合成主音5-吉他.mcpack', 85: '85.Lead_voice_合成主音-人声.mcpack',
86: '86.Lead_fifths_合成主音-五度.mcpack', 87: '87.Lead_bass+lead_合成主音-低音加主音.mcpack',
88: '88.Pad_new_age_合成柔音-新时代.mcpack', 89: '89.Pad_warm_合成柔音-暖音.mcpack', 9: '9.Glockenspiel_钟琴.mcpack',
90: '90.Pad_polysynth_合成柔音-复合成.mcpack', 91: '91.Pad_choir_合成柔音-合唱.mcpack',
92: '92.Pad_bowed_合成柔音-弓弦.mcpack', 93: '93.Pad_metallic_合成柔音-金属.mcpack',
94: '94.Pad_halo_合成柔音-光环.mcpack', 95: '95.Pad_sweep_合成柔音-扫弦.mcpack', 96: '96.FX_rain_合成特效-雨.mcpack',
97: '97.FX_soundtrack_合成特效-音轨.mcpack', 98: '98.FX_crystal_合成特效-水晶.mcpack',
99: '99.FX_atmosphere_合成特效-大气.mcpack'}
if __name__ == '__main__':
print(zip_name[0])

View File

@@ -1,133 +0,0 @@
pitch = {
'0': '0.0220970869120796',
'1': '0.0234110480761981',
'2': '0.0248031414370031',
'3': '0.0262780129766786',
'4': '0.0278405849418856',
'5': '0.0294960722713029',
'6': '0.03125',
'7': '0.033108221698728',
'8': '0.0350769390096679',
'9': '0.037162722343835',
'10': '0.0393725328092148',
'11': '0.0417137454428136',
'12': '0.0441941738241592',
'13': '0.0468220961523963',
'14': '0.0496062828740062',
'15': '0.0525560259533572',
'16': '0.0556811698837712',
'17': '0.0589921445426059',
'18': '0.0625',
'19': '0.066216443397456',
'20': '0.0701538780193358',
'21': '0.0743254446876701',
'22': '0.0787450656184296',
'23': '0.0834274908856271',
'24': '0.0883883476483184',
'25': '0.0936441923047926',
'26': '0.0992125657480125',
'27': '0.105112051906714',
'28': '0.111362339767542',
'29': '0.117984289085212',
'30': '0.125',
'31': '0.132432886794912',
'32': '0.140307756038672',
'33': '0.14865088937534',
'34': '0.157490131236859',
'35': '0.166854981771254',
'36': '0.176776695296637',
'37': '0.187288384609585',
'38': '0.198425131496025',
'39': '0.210224103813429',
'40': '0.222724679535085',
'41': '0.235968578170423',
'42': '0.25',
'43': '0.264865773589824',
'44': '0.280615512077343',
'45': '0.29730177875068',
'46': '0.314980262473718',
'47': '0.333709963542509',
'48': '0.353553390593274',
'49': '0.37457676921917',
'50': '0.39685026299205',
'51': '0.420448207626857',
'52': '0.44544935907017',
'53': '0.471937156340847',
'54': '0.5',
'55': '0.529731547179648',
'56': '0.561231024154687',
'57': '0.594603557501361',
'58': '0.629960524947437',
'59': '0.667419927085017',
'60': '0.707106781186548',
'61': '0.749153538438341',
'62': '0.7937005259841',
'63': '0.840896415253715',
'64': '0.890898718140339',
'65': '0.943874312681694',
'66': '1',
'67': '1.0594630943593',
'68': '1.12246204830937',
'69': '1.18920711500272',
'70': '1.25992104989487',
'71': '1.33483985417003',
'72': '1.4142135623731',
'73': '1.49830707687668',
'74': '1.5874010519682',
'75': '1.68179283050743',
'76': '1.78179743628068',
'77': '1.88774862536339',
'78': '2',
'79': '2.11892618871859',
'80': '2.24492409661875',
'81': '2.37841423000544',
'82': '2.51984209978975',
'83': '2.66967970834007',
'84': '2.82842712474619',
'85': '2.99661415375336',
'86': '3.1748021039364',
'87': '3.36358566101486',
'88': '3.56359487256136',
'89': '3.77549725072677',
'90': '4',
'91': '4.23785237743718',
'92': '4.48984819323749',
'93': '4.75682846001088',
'94': '5.03968419957949',
'95': '5.33935941668014',
'96': '5.65685424949238',
'97': '5.99322830750673',
'98': '6.3496042078728',
'99': '6.72717132202972',
'100': '7.12718974512272',
'101': '7.55099450145355',
'102': '8',
'103': '8.47570475487436',
'104': '8.97969638647498',
'105': '9.51365692002177',
'106': '10.079368399159',
'107': '10.6787188333603',
'108': '11.3137084989848',
'109': '11.9864566150135',
'110': '12.6992084157456',
'111': '13.4543426440594',
'112': '14.2543794902454',
'113': '15.1019890029071',
'114': '16',
'115': '16.9514095097487',
'116': '17.95939277295',
'117': '19.0273138400435',
'118': '20.158736798318',
'119': '21.3574376667206',
'120': '22.6274169979695',
'121': '23.9729132300269',
'122': '25.3984168314912',
'123': '26.9086852881189',
'124': '28.5087589804909',
'125': '30.2039780058142',
'126': '32',
'127': '33.9028190194974',
'128': '35.9187855458999',
'129': '38.0546276800871',
'130': '40.3174735966359',
'131': '42.7148753334411'}

22
clean_update.py Normal file
View File

@@ -0,0 +1,22 @@
import shutil
import os
from rich.console import Console
from rich.progress import track
console = Console()
def main():
with console.status("寻众迹于 .egg-info 内"):
egg_info: list = []
for file in os.listdir():
if file.endswith(".egg-info"):
egg_info.append(file)
console.print(file)
for file in track(["build", "dist", "logs", *egg_info], description="正删档"):
if os.path.isdir(file) and os.access(file, os.W_OK):
shutil.rmtree(file)
if __name__ == "__main__":
main()

13
docs/API.md Normal file
View File

@@ -0,0 +1,13 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
**此为开发相关文档内容包括库的简单调用所生成文件结构的详细说明特殊参数的详细解释**
# [main.py](../Musicreater/main.py)
## [类] MidiConvert
### [类函数] from_midi_file

View File

@@ -0,0 +1,315 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
**此为开发相关文档内容包括库的简单调用所生成文件结构的详细说明特殊参数的详细解释**
# 库的简单调用
参见[example.py的相关部分](../example.py)使用此库进行MIDI转换非常简单
- 在导入转换库后使用 MidiConvert 类建立转换对象读取Midi文件
·创库支持新旧两种execute语法需要在对象实例化时指定
```python
# 导入音·创库
import Musicreater
# 指定是否使用旧的execute指令语法即1.18及以前的《我的世界:基岩版》语法)
old_execute_format = False
# 可以通过文件地址自动读取
cvt_mid = Musicreater.MidiConvert.from_midi_file(
"Midi文件地址",
old_exe_format=old_execute_format
)
# 也可以导入Mido对象
cvt_mid = Musicreater.MidiConvert(
mido.MidiFile("Midi文件地址"),
"音乐名称",
old_exe_format=old_execute_format
)
```
- 获取 Midi 音乐经转换后的播放指令
```python
# 通过函数 to_command_list_in_score, to_command_list_in_delay
# 分别可以得到
# 以计分板作为播放器的指令对象列表、以延迟作为播放器的指令对象列表
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
# 使用 to_command_list_in_score 函数进行转换之后,返回值有三个
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
# 也就是列表套列表
# 但是,在对象内部所存储的数据却不会如此嵌套
command_channel_list, command_count, max_score = cvt_mid.to_command_list_in_score(
"计分板名称",
1.0, # 音量比率
1.0, # 速度倍率
)
# 使用 to_command_list_in_delay 转换后的返回值只有两个
# 但是第一个返回值没有列表套列表
command_list, max_delay = cvt_mid.to_command_list_in_delay(
1.0, # 音量比率
1.0, # 速度倍率
"@a", # 玩家选择器
)
# 运行之后,指令和总延迟会存储至对象内
print(
"音乐长度:{}/游戏刻".format(
cvt_mid.music_tick_num
)
)
print(
"指令如下:\n{}".format(
cvt_mid.music_command_list
)
)
```
- 除了获取播放指令外还可以获取进度条指令
```python
# 通过函数 form_progress_bar 可以获得
# 以计分板为载体所生成的进度条的指令对象列表
# 数据不仅存储在对象本身内,也会以返回值的形式返回,详见代码内文档
# 使用 form_progress_bar 函数进行转换之后,返回值有三个
# 值得注意的是第一个返回值返回的是依照midi频道存储的指令对象列表
# 也就是列表套列表
cvt_mid.form_progress_bar(
max_score, # 音乐时长游戏刻
scoreboard_name, # 进度条使用的计分板名称
progressbar_style, # 进度条样式组(详见下方)
)
# 同上面生成播放指令的理,进度条指令也会存储至对象内
print(
"进度条指令如下:\n{}".format(
cvt_mid.progress_bar_command
)
)
```
在上面的代码中进度条样式是可以自定义的详见[下方说明](%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md#进度条自定义)。
- 转换成指令是一个方面接下来是再转换为可以导入MC的格式我们提供了 **·** 内置的附加组件可以借助 `MidiConvert` 对象转换为相应格式
```python
# 导入 Musicreater
import Musicreater
# 导入附加组件功能
import Musicreater.plugin
# 导入相应的文件格式转换功能
# 转换为函数附加包
import Musicreater.plugin.funpack
# 转换为 BDX 结构文件
import Musicreater.plugin.bdxfile
# 转换为 mcstructure 结构文件
import Musicreater.plugin.mcstructfile
# 转换为结构附加包
import Musicreater.plugin.mcstructpack
# 直接通过 websocket 功能播放(正在开发)
import Musicreater.plugin.websocket
# 定义转换参数
cvt_cfg = Musicreater.plugin.ConvertConfig(
output_path,
volumn, # 音量大小参数
speed, # 速度倍率
progressbar, # 进度条样式组(详见下方)
)
# 使用附加组件转换,其调用的函数应为:
# Musicreater.plugin.输出格式.播放器格式
# 值得注意的是,并非所有输出格式都支持所有播放器格式
# 调用的时候还请注意甄别
# 例如,以下函数是将 MidiConvert 对象 cvt_mid
# 以 cvt_cfg 指定的参数
# 以延迟播放器转换为 mcstructure 文件
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
cvt_mid,
cvt_cfg,
)
```
# 生成文件结构
## 名词解释
|名词|解释|备注|
|--------|-----------|----------|
|指令区|一个用于放置指令系统的区域通常是常加载区|常见于服务器指令系统好友联机房间中|
|指令链|与链式指令方块不同一个指令链通常指代的是一串由某种非链式指令方块作为开头后面连着一串链式指令方块的结构|通常的链都应用于需要单次激活而多指令的简单功能|
|起始块|链最初的那个非链式指令方块|此方块为脉冲方块或重复方块皆可|
|指令系统系统|指令系统通常指的是由一个或多个指令链以及相关红石机构相互配合一同组成的为达到某种特定的功能而构建的整体结构|通常的系统都应用于需要综合调配指令的复杂功能可由多个实现不同功能的模块构成不同系统之间可以相互调用各自的模块|
|游戏刻|游戏的一刻是指我的世界的游戏进程循环运行一次所占用的时间[详见我的世界中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E6%B8%B8%E6%88%8F%E5%88%BB))。指令方块的延迟功能(即指令方块的“延迟刻数”设置项,此项的名称被误译为“已选中项的延迟”)的单位即为`1`游戏刻。|正常情况下,游戏固定以每秒钟 $20$ 刻的速率运行。但是,由于游戏内的绝大多数操作都是基于游戏进程循环而非现实中的时间来计时并进行的,一次游戏循环内也许会发生大量的操作,更多情况下,一秒对应的游戏刻会更少。|
|红石刻|一个红石刻代表了两个游戏刻[详见我的世界中文维基](https://minecraft.fandom.com/zh/wiki/%E5%88%BB#%E7%BA%A2%E7%9F%B3%E5%88%BB))。红石中继器会带来 $1$~$4$ 个红石刻的延迟,其默认的延迟时间为 $1$ 红石刻。|正常情况下,红石信号在一个红石电路中传输回存在 $\frac{1}{10}$ 秒左右的延迟。但是,同理于游戏刻,一秒对应的红石刻是不定的。|
## 播放器
**·**生成的文件可以采用多种方式播放一类播放方式我们称其为**播放器**例如**延迟播放器****计分板播放器**等等以后推出的新的播放器届时也会在此处更新
为什么要设计这么多播放器是为了适应不同的播放环境需要通常情况下一个音乐中含有多个音符音符与音符之间存在间隔这里就产生了不一样的实现音符间时间间隔的方式而不同的应用环境下又会产生不一样的要求接下来将对不同的播放器进行详细介绍
### 参数释义
|参数|说明|备注|
|--------|-----------|----------|
|`ScBd`|指定的计分板名称||
|`Tg`|播放对象|选择器或玩家名|
|`x`|音发出时对应的分数值||
|`InstID`|声音效果ID|不同的声音ID可以对应不同的乐器详见转换[乐器对照表](./%E8%BD%AC%E6%8D%A2%E4%B9%90%E5%99%A8%E5%AF%B9%E7%85%A7%E8%A1%A8.md)|
|`Ht`|播放点对玩家的距离|通过距离来表达声音的响度 $S$ 表示此参数`Ht`以Vol表示音量百分比则计算公式为 $S = \frac{1}{Vol}-1$ |
|`Vlct`|原生我的世界中规定的播放力度|这个参数是一个谜一样的存在似乎它的值毫不重要因为无论这个值是多少我们听起来都差不多当此音符所在MIDI通道为第一通道则这个值为 $0.7$ 倍MIDI指定力度其他则为 $0.9$ |
|`Ptc`|音符的音高|这是决定音调的参数 $P$ 表示此参数 $n$ 表示其在MIDI中的编号 $x$ 表示一定的音调偏移则计算公式为 $P = 2^\frac{n-60-x}{12}$之所以存在音调偏移是因为在我的世界不同的[乐器存在不同的音域](https://zh.minecraft.wiki/wiki/%E9%9F%B3%E7%AC%A6%E7%9B%92#%E4%B9%90%E5%99%A8),我们通过音调偏移来进行调整。|
### 播放器内容
1. 计分板播放器
计分板播放器是一种传统的我的世界音乐播放方式通过对于计分板加分来实现播放不同的音符一个很简单的原理就是**用不同的计分板分值对应不同的音符**再通过加分来达到那个分值即播放出来
**·**用来达到这种效果的指令是这样的
```mcfunction
execute @a[scores={ScBd=x}] ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
```
后四个参数决定了这个音的性质而前两个参数仅仅是为了决定音播放的时间
2. 延迟播放器
延迟播放器是通过我的世界游戏中指令方块的设置项延迟刻数来达到定位音符的效果**将所有的音符依照其播放时距离乐曲开始时的时间毫秒放在一个序列内再计算音符两两之间对应的时间差值转换为我的世界内对应的游戏刻数之后填入指令方块的设置中**
**·**由于此方式播放的音乐不需要用计分板所以播放指令是这样的
```mcfunction
execute Tg ~ ~ ~ playsound InstID @s ^ ^ ^Ht Vlct Ptc
```
其中后四个参数决定了这个音的性质
由于这样的延迟数据是依赖于指令方块的设置项所以使用这种播放器所转换出的结果仅可以存储在包含方块NBT信息及方块实体NBT信息的结构文件中或者直接输出至世界
3. 中继器播放器
中继器播放器是一种传统的我的世界红石音乐播放方式利用游戏内红石组件红石中继器以达到定位音符之用**但是中继器的延迟为1红石刻**
## 文件格式
1. 附加包格式`.mcpack`
使用附加包格式导出音乐若采用计分板 播放器则音乐会以指令函数文件`.mcfunction`存储于附加包内而若为延迟或中继器播放器则音乐回以结构文件`.mcstructure`存储在所生成的附加包中函数文件的存储结构应为
- `functions\`
- `index.mcfunction`
- `stop.mcfunction`
- `mscply\`
- `progressShow.mcfunction`
- `track1.mcfunction`
- `track2.mcfunction`
- ...
- `trackN.mcfunction`
- `structures\`
- `XXX_main.mcstructure`
- `XXX_start.mcstructure`
- `XXX_reset.mcstructure`
- `XXX_pgb.mcstructure`
如图其中`index.mcfunction`文件`stop.mcfunction`文件和`mscply`文件夹存在于函数目录的根下`mscply`目录中包含音乐导出的众多音轨播放文件`trackX.mcfunction`同时若使用计分板播放器生成此包时启用生成进度条则会包含`progressShow.mcfunction`文件若选择延迟或中继器播放器则会生成`structures`目录以及相关`.mcstructure`文件其中`mian`表示音乐播放用的主要结构`start`是用于初始化播放的部分仅包含一个指令方块即起始块`reset``pgb`仅在启用生成进度条时出现前者用于重置临时计分板后者用于显示进度条
`index.mcfunction`用于开始播放
1. 若为计分板播放器则其中包含打开各个音轨对应函数的指令以及加分指令这里的加分是将**播放计分板的值大于等于 $1$ 的所有玩家**的播放计分板分数增加 $1$同时若生成此包时选择了自动重置计分板的选项则会包含一条重置计分板的指令
2. 若为延迟或中继器播放器则其中的指令仅包含用以正确加载结构的`structure`指令
`stop.mcfunction`用于终止播放
1. 若为计分板播放器则其中包含将**全体玩家的播放计分板**重置的指令
2. 若为延迟或中继器播放器则其中包含**停用命令方块****启用命令方块**的指令~~然鹅实际上对于播放而言是一点用也没有~~
> 你知道吗·创的最早期版本我的世界函数音乐生成器正是用函数来播放不过这个版本采取的读入数据的形式大有不同
2. 生成结构的方式
无论是音·创生成的是何种结构`MCSTRUCTURE`还是`BDX`都会依照此处的格式来生成此处我们想说明的结构的格式不是结构文件存储的格式而是结构导出之后方块摆放的方式结构文件存储的格式这一点在各个我的世界开发的相关网站上都可能会有说明
考虑到进行我的世界游戏开发时为了节约常加载区域很多游戏会将指令区设立为一种层叠式的结构这种结构会限制每一层的指令系统的高度但是虽然长宽也是有限的却仍然比其纵轴延伸得更加自由
所以结构的生成形状依照给定的高度和内含指令的数量决定 $Z$ 轴延伸长度为指令方块数量对于给定高度之商的向下取整结果的平方根的向下取整用数学公式的方式表达则是
$$ MaxZ = \left\lfloor\sqrt{\left\lfloor{\frac{NoC}{MaxH}}\right\rfloor}\right\rfloor $$
其中$MaxZ$ 即生成结构的$Z$轴最大延伸长度$NoC$ 表示链结构中所含指令方块的个数$MaxH$ 表示给定的生成结构的最大高度
我们的结构生成器在生成指令链时将首先以相对坐标系 $(0, 0, 0)$ 即相对原点开始自下向上堆叠高度轴 $Y$ 的长当高度轴达到了限制的高度时便将 $Z$ 轴向正方向堆叠 $1$ 个方块并开始自上向下重新堆叠直至高度轴坐标达到相对为 $0$若当所生成结构的 $Z$ 轴长达到了其最大延伸长度则此结构生成器将反转 $Z$ 轴的堆叠方向直至 $Z$ 轴坐标相对为 $0$如此往复直至指令链堆叠完成
# 进度条自定义
因为我们提供了可以自动转换进度条的功能因此在这里给出进度条自定义参数的详细解释
一个进度条明显地**固定部分****可变部分**来构成而可变部分又包括了文字和图形两种当然我的世界里头的进度条可变的图形也就是那个这一点你需要了解因为后文中包含了很多这方面的概念需要你了解
进度条的自定义功能使用一个字符串来定义自己的样式其中包含众多**标识符**来表示可变部分
标识符如下注意大小写
| 标识符 | 指定的可变量 |
|---------|----------------|
| `%%N` | 乐曲名(即传入的文件名)|
| `%%s` | 当前计分板值 |
| `%^s` | 计分板最大值 |
| `%%t` | 当前播放时间 |
| `%^t` | 曲目总时长 |
| `%%%` | 当前进度比率 |
| `_` | 用以表示进度条占位|
表示进度条占位的 `_` 是用来标识你的进度条的也就是可变部分的唯一的图形部分
**样式定义字符串基础样式**的样例如下这也是默认进度条的基础样式
``` %%N [ %%s/%^s %%% __________ %%t|%^t]```
这是单独一行的进度条当然你也可以制作多行的如果是一行的输出时所使用的指令便是 `title`而如果是多行的话输出就会用 `titleraw` 作为进度条字幕
哦对了上面的只不过是样式定义同时还需要定义的是可变图形的部分也就是进度条上那个真正的
对于这个我们就采用了固定参数的方法对于一个进度条无非就是已经播放过的没播放过的两种形态例如我们默认的进度**可变样式**的定义是这样的
**可变样式甲已播放样式**`'§e=§r'`
**可变样式乙未播放样式**`'§7=§r'`
综合起来把这些参数传给函数需要一个参数整合使用位于 `Musicreater/subclass.py` 下的 `ProgressBarStyle` 类进行定义
我们的默认定义参数如下
```python
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
r"%%N [ %%s/%^s %%% __________ %%t|%^t ]",
r"§e=§r",
r"§7=§r",
)
```
*为了避免生成错误请尽量避免使用标识符作为定义样式字符串的其他部分*

View File

@@ -0,0 +1,56 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
# 生成文件的使用
*这是本库所生成文件的使用声明不是使用本库的教程若要查看**本库的文档**可点击[此处](./%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)若要查看有关文件结构的内容可以点击[此处](./%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E8%AF%B4%E6%98%8E.md)*
## 附加包格式
支持的文件后缀`.MCPACK`
- 计分板播放器
1. 导入附加包
2. 在一个循环方块中输入指令 `function index`
3. 将需要聆听音乐的实体的播放所用计分板设置为 `1`
4. 激活循环方块
5. 若想要暂停播放可以停止循环指令方块的激活状态
6. 若想要重置某实体的播放可以将其播放用的计分板重置
7. 若要终止全部玩家的播放在聊天框输入指令 `function stop`
> 其中 步骤三 步骤四 的顺序可以调换
- 延迟播放器
1. 导入附加包
2. 在聊天框输入指令 `function index`
3. 同时激活所生成的循环和脉冲指令方块
4. 若要终止播放在聊天框输入指令 `function stop` 试试看不确保有用
> 需要注意的是循环指令方块需要一直激活直到音乐结束
## 结构格式
支持的文件后缀`.MCSTRUCTURE``.BDX`
1. 将结构导入世界
- 延迟播放器
2. 将结构生成的第一个指令方块之模式更改为**脉冲**
3. 激活脉冲方块
4. 若欲重置播放可以停止对此链的激活例如停止区块加载
5. 此播放器不支持暂停
- 计分板播放器
2. 在所生成的第一个指令方块前放置一个循环指令方块其朝向应当对着所生成的第一个方块
3. 在循环指令方块中输入使播放对象的播放用计分板加分的指令延迟为 `0`每次循环增加 `1`
4. 激活循环方块
5. 若想要暂停播放可以停止循环指令方块的激活状态
6. 若想要重置某实体的播放可以将其播放用的计分板重置

View File

@@ -0,0 +1,222 @@
<h1 align="center">· Musicreater</h1>
<p align="center">
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
</p>
# 转换乐器对照表
**_注意本文档中的对照表版权归属于音·创作者并按照本仓库根目录下 LICENSE.md 中规定开源_**
原表格请见[constant.py](../Musicreater/constants.py#176)
**_使用时请遵循协议规定_**
- 版权所有 © 2025 金羿 & 诸葛亮与八卦阵
- Copyright © 2025 Eilles & bgArray
* ·本项目的协议颁发者为 金羿诸葛亮与八卦阵
* The Licensor of Musicreater("this project") is Eilles, bgArray.
本项目根据 第一版 汉钰律许可协议本协议授权
任何人皆可从以下地址获得本协议副本https://gitee.com/EillesWan/YulvLicenses
若非因法律要求或经过了特殊准许此作品在根据本协议原样提供的基础上不予提供任何形式的担保任何明示任何暗示或类似承诺也就是说用户将自行承担因此作品的质量或性能问题而产生的全部风险
详细的准许和限制条款请见原协议文本
· 开发交流群 861684859\
Email TriM-Organization@hotmail.com\
若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
### 名词解释
| 名词 | 说明 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| 音符名称 | 我的世界游戏内用于播放音乐的 `playsound` 指令所规定的 `Sound ID` |
| 音调偏移参数 | 我的世界不同乐器的音域不同对应的 `pitch` 值也不尽相同该参数的解释请参考[文档说明](库的生成与功能文档.md#参数释义) |
# 乐音乐器
对照表版本2023 0527
| Midi 乐器值 | 音符名称 | 音调偏移参数 |
| ----------- | ------------------- | ------------ |
| 0 | note.harp | 6 |
| 1 | note.harp | 6 |
| 2 | note.pling | 6 |
| 3 | note.harp | 6 |
| 4 | note.pling | 6 |
| 5 | note.pling | 6 |
| 6 | note.harp | 6 |
| 7 | note.harp | 6 |
| 8 | note.share | 7 |
| 9 | note.harp | 6 |
| 10 | note.didgeridoo | 8 |
| 11 | note.harp | 6 |
| 12 | note.xylophone | 4 |
| 13 | note.chime | 4 |
| 14 | note.harp | 6 |
| 15 | note.harp | 6 |
| 16 | note.bass | 8 |
| 17 | note.harp | 6 |
| 18 | note.harp | 6 |
| 19 | note.harp | 6 |
| 20 | note.harp | 6 |
| 21 | note.harp | 6 |
| 22 | note.harp | 6 |
| 23 | note.guitar | 7 |
| 24 | note.guitar | 7 |
| 25 | note.guitar | 7 |
| 26 | note.guitar | 7 |
| 27 | note.guitar | 7 |
| 28 | note.guitar | 7 |
| 29 | note.guitar | 7 |
| 30 | note.guitar | 7 |
| 31 | note.bass | 8 |
| 32 | note.bass | 8 |
| 33 | note.bass | 8 |
| 34 | note.bass | 8 |
| 35 | note.bass | 8 |
| 36 | note.bass | 8 |
| 37 | note.bass | 8 |
| 38 | note.bass | 8 |
| 39 | note.bass | 8 |
| 40 | note.harp | 6 |
| 41 | note.harp | 6 |
| 42 | note.harp | 6 |
| 43 | note.harp | 6 |
| 44 | note.iron_xylophone | 6 |
| 45 | note.guitar | 7 |
| 46 | note.harp | 6 |
| 47 | note.harp | 6 |
| 48 | note.guitar | 7 |
| 49 | note.guitar | 7 |
| 50 | note.bit | 6 |
| 51 | note.bit | 6 |
| 52 | note.harp | 6 |
| 53 | note.harp | 6 |
| 54 | note.bit | 6 |
| 55 | note.flute | 5 |
| 56 | note.flute | 5 |
| 57 | note.flute | 5 |
| 58 | note.flute | 5 |
| 59 | note.flute | 5 |
| 60 | note.flute | 5 |
| 61 | note.flute | 5 |
| 62 | note.flute | 5 |
| 63 | note.flute | 5 |
| 64 | note.bit | 6 |
| 65 | note.bit | 6 |
| 66 | note.bit | 6 |
| 67 | note.bit | 6 |
| 68 | note.flute | 5 |
| 69 | note.harp | 6 |
| 70 | note.harp | 6 |
| 71 | note.flute | 5 |
| 72 | note.flute | 5 |
| 73 | note.flute | 5 |
| 74 | note.harp | 6 |
| 75 | note.flute | 5 |
| 76 | note.harp | 6 |
| 77 | note.harp | 6 |
| 78 | note.harp | 6 |
| 79 | note.harp | 6 |
| 80 | note.bit | 6 |
| 81 | note.bit | 6 |
| 82 | note.bit | 6 |
| 83 | note.bit | 6 |
| 84 | note.bit | 6 |
| 85 | note.bit | 6 |
| 86 | note.bit | 6 |
| 87 | note.bit | 6 |
| 88 | note.bit | 6 |
| 89 | note.bit | 6 |
| 90 | note.bit | 6 |
| 91 | note.bit | 6 |
| 92 | note.bit | 6 |
| 93 | note.bit | 6 |
| 94 | note.bit | 6 |
| 95 | note.bit | 6 |
| 96 | note.bit | 6 |
| 97 | note.bit | 6 |
| 98 | note.bit | 6 |
| 99 | note.bit | 6 |
| 100 | note.bit | 6 |
| 101 | note.bit | 6 |
| 102 | note.bit | 6 |
| 103 | note.bit | 6 |
| 104 | note.harp | 6 |
| 105 | note.banjo | 6 |
| 106 | note.harp | 6 |
| 107 | note.harp | 6 |
| 108 | note.harp | 6 |
| 109 | note.harp | 6 |
| 110 | note.harp | 6 |
| 111 | note.guitar | 7 |
| 112 | note.harp | 6 |
| 113 | note.bell | 4 |
| 114 | note.harp | 6 |
| 115 | note.cow_bell | 5 |
| 116 | note.bd | 7 |
| 117 | note.bass | 8 |
| 118 | note.bit | 6 |
| 119 | note.bd | 7 |
| 120 | note.guitar | 7 |
| 121 | note.harp | 6 |
| 122 | note.harp | 6 |
| 123 | note.harp | 6 |
| 124 | note.harp | 6 |
| 125 | note.hat | 7 |
| 126 | note.bd | 7 |
| 127 | note.snare | 7 |
# 打击乐器
| Midi 打击乐器值 | 音符名称 | 音调偏移参数 |
| --------------- | ------------------- | ------------ |
| 34 | note.bd | 7 |
| 35 | note.bd | 7 |
| 36 | note.hat | 7 |
| 37 | note.snare | 7 |
| 38 | note.snare | 7 |
| 39 | note.snare | 7 |
| 40 | note.hat | 7 |
| 41 | note.snare | 7 |
| 42 | note.hat | 7 |
| 43 | note.snare | 7 |
| 44 | note.snare | 7 |
| 45 | note.bell | 4 |
| 46 | note.snare | 7 |
| 47 | note.snare | 7 |
| 48 | note.bell | 4 |
| 49 | note.hat | 7 |
| 50 | note.bell | 4 |
| 51 | note.bell | 4 |
| 52 | note.bell | 4 |
| 53 | note.bell | 4 |
| 54 | note.bell | 4 |
| 55 | note.bell | 4 |
| 56 | note.snare | 7 |
| 57 | note.hat | 7 |
| 58 | note.chime | 4 |
| 59 | note.iron_xylophone | 6 |
| 60 | note.bd | 7 |
| 61 | note.bd | 7 |
| 62 | note.xylophone | 4 |
| 63 | note.xylophone | 4 |
| 64 | note.xylophone | 4 |
| 65 | note.hat | 7 |
| 66 | note.bell | 4 |
| 67 | note.bell | 4 |
| 68 | note.hat | 7 |
| 69 | note.hat | 7 |
| 70 | note.flute | 5 |
| 71 | note.flute | 5 |
| 72 | note.hat | 7 |
| 73 | note.hat | 7 |
| 74 | note.xylophone | 4 |
| 75 | note.hat | 7 |
| 76 | note.hat | 7 |
| 77 | note.xylophone | 4 |
| 78 | note.xylophone | 4 |
| 79 | note.bell | 4 |
| 80 | note.bell | 4 |

View File

@@ -0,0 +1,149 @@
# 音乐序列文件格式
· 库的音符序列文件格式包含两种一种是常规的音乐序列存储采用的 MSQ 格式另一种是为了流式读取音符而采用的 FSQ 格式
## MSQ 数据格式
MSQ 格式是 · 库存储音符序列的一种字节码格式取自 **M**usic**S**e**Q**uence 类之名
现在 · 库及其上游软件使用的是在 第二版 的基础上增设校验功能的 MSQ 第三版
### MSQ 第三版
第三版 MSQ 格式的码头是 `MSQ!` 这一版中所有的**字符串**皆以 _**GB18030**_ 编码进行编解码**数值**皆是以 _**大端字节序**_ 存储的无符号整数
码头是字节码前四个字节的内容这一部分内容是可读的 ASCII 字串因此第三版的字节码中前四个字节的内容必为 `MSQ!`
第二版 MSQ `MSQ@` 作为码头是因为美式键盘上 @ Shift+2 按下取得的故代表 MSQ 第二版
你猜为什么第三版是 `MSQ!`
#### 元信息
| 信息名称 | 西文代号 | 位长多少个 0 1 | 支持说明 |
| ------------------------------ | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **码头** | _无_ | 32 | 值为 `MSQ!` |
| **音乐名称长度** | music_name_length | 6 | 支持数值 0~63 |
| **最小音量** | minimum_volume | 10 | 支持数值 0~1023注意这里每个 1 代表最小音量的 0.001 个单位即取值是此处表示数字的千分倍 |
| **是否启用高精度音符时间控制** | enable_high_precision_time | 1 | 1 是启用反之同理 |
| **总音调偏移** | music_deviation | 15 | 支持数值 -16383~16383这里也是表示三位小数的和最小音量一样这里 15 位中的第一位从左往右是正负标记若为 1 则为负数反之为正数后面的 14 位是数值 |
| **音乐名称** | music_name | 依据先前定义 | 最多可支持 31 个中文字符 63 个西文字符其长度取决于先前获知的 音乐名称长度 的定义 |
在这一元信息中**音乐名称长度****最小音量**合计共 2 字节**高精度音符时间控制启用****总音调偏移**合计共 2 字节因此**音乐名称**为任意长度前四字节内容均为固定
#### 音符序列
每个序列前 4 字节为一个用以表示当前通道中音符数量的值也就是**通道音符数**notes_count也即是说一个通道内的音符可以是 0~4294967295
在这之后就是这些数量的音符了其中每个音符的信息存储方式如下
| 信息名称 | 西文代号 | 位长 | 支持说明 |
| ---------------------------- | ------------------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **乐器名称长度** | name_length | 6 | 支持数值 0~63 |
| **Midi 音高** | note_pitch | 7 | 支持数值 0~127 |
| **开始时刻** | start_tick | 17 | 单位 二十分之一秒即约为 1 命令刻支持数值 0~131071 109.22583 分钟 1.8204305 小时 |
| **音符持续刻数** | duration | 17 | 同上 |
| **是否作为打击乐器** | percussive | 1 | 1 是启用反之同理 |
| **响度力度** | velocity | 7 | 支持数值 0~127 |
| **是否启用声像位移** | is_displacement_included | 1 | 1 是启用反之同理 |
| **时间精度提升值**非必含 | high_time_precision | 8 | 支持数值 0~255若在 元信息 中启用**高精度音符时间控制**则此值启用代表音符时间控制精度偏移此值每增加 1则音符开始时刻向后增加 1/1250 |
| **乐器名称** | sound_name | 依据先前定义 | 最多可支持 31 个中文字符 63 个西文字符其长度取决于先前获知的 乐器名称长度 的定义 |
| **声像位移**非必含 | position_displacement | 共三个值每个值 16 48 | 若前述**是否启用声像位移**已启用则此值启用三个值分别代表 xyz 轴上的偏移每个值支持数值 0~65535注意这里每个 1 代表最小音量的 0.001 个单位即取值是此处表示数字的千分倍 |
#### 序列校验
_第三版新增_
在每个音符序列结尾包含一个 128 位的校验值用以标识该序列结束的同时验证该序列的完整性
在这 128 位里 64 位是该通道音符数的 XXHASH64 校验值 3 作为种子值
64 位是整个通道全部字节串的 XXHASH64 校验值包括通道开头的音符数 该通道音符数 作为种子值
#### 总体校验
_第三版新增_
在所有有效数据之后包含一个 128 位的校验值用以标识整个字节串结束的同时验证整个字节码数据的完整性
128 位的校验值是 包括码头在内的元信息的 XXHASH64 校验值种子值是全曲音符数 对于前述所有校验值彼此异或的异或 所得值之 XXHASH128 校验值 全曲音符总数 作为种子值
请注意是前述每个 XXHASH64 校验值的异或每次取 XXHASH64 都计一遍也就并非是每个序列结尾那个已经合并了的 128 位校验值再彼此异或对于这个异或值再取其种子是 全曲音符数 XXHASH128 校验字节码
听起来很复杂我来举个例子以下是该算法的伪代码我们设
- `meta_info` : `bytes` 元信息字节串
- `note_seq_1` : `bytes` 第一个音符序列的编码字节串
- `note_seq_2` : `bytes` 第二个音符序列的编码字节串
- `XXH64(bytes, seed)` : `bytes` XXHASH64 校验函数
- `XXH128(bytes, seed)` : `bytes` XXHASH128 校验函数
- `XOR(bytesLike, bytesLike)` : `bytes` 异或 函数
- `note_count` : `int` 全曲音符数
- `seq_1_note_count` : `int` 第一个音符序列的音符数
- `seq_2_note_count` : `int` 第二个音符序列的音符数
为了简化我们假设只有两个序列实际上每个通道都是一个序列最多 16 个序列
那么一个完整的 MSQ 文件应当如下排列其字节串
```assembly
ADD meta_info
ADD note_seq_1
ADD XXH64(seq_1_note_count, 3)
ADD XXH64(note_seq_1, seq_1_note_count)
ADD note_seq_2
ADD XXH64(seq_2_note_count, 3)
ADD XXH64(note_seq_2, seq_2_note_count)
ADD XXH128(
XOR(
XOR(
XXH64(meta_info, note_count),
XOR(
XXH64(seq_1_note_count, 3),
XXH64(note_seq_1, seq_1_note_count)
),
),
XOR(
XXH64(seq_2_note_count, 3),
XXH64(note_seq_2, seq_2_note_count),
)
),
note_count
)
```
## FSQ 数据格式
FSQ 格式是 · 库存储音符序列的一种字节码格式取自 **F**lowing Music **S**e**q**uence 之名
现在 · 库及其上游软件使用的是在 MSQ 第三版 的基础上进行流式的兼容性变动
### FSQ 第一版
第一版的码头是 `FSQ!` 这一版中所有的**字符串**皆以 _**GB18030**_ 编码进行编解码**数值**皆是以 _**大端字节序**_ 存储的无符号整数
码头是字节串前四个字节的内容这一部分内容是可读的 ASCII 字串因此这一版的文件前四个字节的内容必为 `FSQ!`
因为这一版本是在 MSQ 第三版的基础上演变而来的因此取自 MSQ 码头的 `MSQ!` 而改作 `FSQ!`
#### 元信息
FSQ 第一版的元信息是在 MSQ 第三版的元信息的基础上增加了一个占 5 字节的全曲音符总数
也就是说 MSQ 第三版一致的在这一组信息中**音乐名称长度****最小音量**合计共 2 字节**高精度音符时间控制启用****总音调偏移**合计共 2 字节因此**音乐名称**为任意长度前四字节内容均为固定而最后增加 5 字节作为全曲音符总数
#### 音符序列
FSQ 格式不包含音符的通道信息在读取处理时默认将同一乐器的音符视为同一通道也就是说仅存在一个序列其中每个音符的信息存储方式与 MSQ 第三版一致
音符序列的存储顺序是按照音符的**开始时间**进行排序的
但是注意有可能一个较长的音符的开始到结束时间内还包含有音符此时如有要适配的读取器还请继续读取直到下一个音符的开始时间大于此较长音符的结束时间
在每 100 个音符后插入一段 32 位的 XXHASH32 校验码其所校验的内容为这一百个音符中每个音符的第 6 个字节彼此异或之结果种子值为这一百个音符中每个音符的第 2 个字节的彼此异或结果
若最后不满足 100 个音符则不插入上述校验码
#### 总体校验
在所有有效数据之后包含一个 128 位的校验值用以标识整个字节串结束的同时验证整个字节码数据的完整性
128 位的校验值是 包括码头在内的元信息的 XXHASH64 校验值种子值是全曲音符数 对于前述所有 XXHASH32 校验值彼此异或的异或 所得值之 XXHASH128 校验值 全曲音符总数 作为种子值

View File

@@ -1,170 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 诸葛亮与八卦阵帮忙修改语法 日期:---2022年1月19日
# 统计致命三级错误2个---未解决警告二级错误2个语法一级错误17个
__version__ = '0.0.1'
__all__ = []
__author__ = 'Fuckcraft <https://gitee.com/fuckcraft>'
'''
Fuckcraft Websocket Library (FCWSLIB)
A library to develop minecraft websocket server easily.
Copyright (C) 2021 Fuckcraft
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
'''
from main import *
# import os
import json
import uuid
# import logging
import asyncio
import time
import websockets
# 写这段代码的时候,只有我和上帝知道这段代码是干什么的。
# 现在只有上帝知道。
# ----
# 没毛病,我讨厌两种人:一种是要我写注释的人,一种是给我代码看但没有写注释的人。
# 此函数用于向 Minecraft 订阅请求
async def subscribe(websocket, event_name):
"""
参数:
: websocket : websocket 对象 :
: event_name : 需要订阅的请求 :
返回:
None
"""
response = {
'body': {
'eventName': str(event_name) # 示例PlayerMessage
},
'header': {
'requestId': str(uuid.uuid4()),
'messagePurpose': 'subscribe',
'version': 1,
'messageType': 'commandRequest'
}
}
# 增加 json 的可读性
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
response = json.dumps(response)
await websocket.send(response)
# 此函数用于向 Minecraft 消除订阅请求
async def unsubscribe(webscket):
"""
参数:
: websocket : websocket 对象 :
: event_name : 需要消除订阅的请求 :
返回:
None
"""
print(webscket)
response = {
"body": {
"eventName": str(event_name) # PlayerMessage
},
"header": {
"requestId": str(uuid.uuid4()),
"messagePurpose": "unsubscribe",
"version": 1,
"messageType": "commandRequest"
}
}
# 增加 json 的可读性
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
response = json.dumps(response)
await websocket.send(response)
# 此函数用于向 Minecraft 执行命令
async def send_command(websocket, command):
"""
参数:
: websocket : websocket 对象 :
: command : 执行的命令 :
返回:
None
"""
response = {
'body': {
'origin': {
'type': 'player'
},
'commandLine': str(command),
'version': 1
},
'header': {
'requestId': str(uuid.uuid4()),
'messagePurpose': 'commandRequest',
'version': 1,
'messageType': 'commandRequest'
}
}
# 增加 json 的可读性
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
response = json.dumps(response)
await websocket.send(response)
# 此函数用于向 Minecraft 发送消息
async def tellraw(websocket, message):
"""
参数:
: websocket : websocket 对象 :
: message : 发送的消息 :
返回:
None
"""
command = {
'rawtext': [
{
'text': '[{}] {}'.format(time.asctime(), message)
}
]
}
# 增加 json 可读性
# command = json.dumps(command, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
command = json.dumps(command)
command = 'tellraw @a {}'.format(command)
await send_command(websocket, command)
def run_server(function):
# 修改 ip 地址和端口
start_server = websockets.serve(function, 'localhost', 8080)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

View File

@@ -1,160 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
__version__ = '0.0.1'
__all__ = ['run_server', 'subscribe', 'unsubscribe', 'send_command', 'tellraw']
__author__ = 'Fuckcraft <https://gitee.com/fuckcraft>'
'''
Fuckcraft Websocket Library (FCWSLIB)
A library to develop minecraft websocket server easily.
Copyright (C) 2021 Fuckcraft
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
'''
import os
import json
import uuid
import logging
import asyncio
import time
import websockets
# 写这段代码的时候,只有我和上帝知道这段代码是干什么的。
# 现在只有上帝知道。
# 此函数用于向 Minecraft 订阅请求
async def subscribe(websocket, event_name):
'''
输入:
: websocket : websocket 对象 :
: event_name : 需要订阅的请求 :
输出:
None
'''
response = {
'body': {
'eventName': str(event_name) # 示例PlayerMessage
},
'header': {
'requestId': str(uuid.uuid4()),
'messagePurpose': 'subscribe',
'version': 1,
'messageType': 'commandRequest'
}
}
# 增加 json 的可读性
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
response = json.dumps(response)
await websocket.send(response)
# 此函数用于向 Minecraft 消除订阅请求
async def unsubscribe(webscket):
'''
输入:
: websocket : websocket 对象 :
: event_name : 需要消除订阅的请求 :
输出:
None
'''
response = {
"body": {
"eventName": str(event_name) # 示例PlayerMessage
},
"header": {
"requestId": str(uuid.uuid4()),
"messagePurpose": "unsubscribe",
"version": 1,
"messageType": "commandRequest"
}
}
# 增加 json 的可读性
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
response = json.dumps(response)
await websocket.send(response)
# 我个人不负责这块的质量,因为他们逼迫我违心的写了这段代码
# 此函数用于向 Minecraft 执行命令
async def send_command(websocket, command):
'''
输入:
: websocket : websocket 对象 :
: command : 执行的命令 :
输出:
None
'''
response = {
'body': {
'origin': {
'type': 'player'
},
'commandLine': str(command),
'version': 1
},
'header': {
'requestId': str(uuid.uuid4()),
'messagePurpose': 'commandRequest',
'version': 1,
'messageType': 'commandRequest'
}
}
# 增加 json 的可读性
# response = json.dumps(response, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
response = json.dumps(response)
await websocket.send(response)
# 此函数用于向 Minecraft 发送消息
async def tellraw(websocket, message):
'''
输入:
: websocket : websocket 对象 :
: message : 发送的消息 :
输出:
None
'''
command = {
'rawtext':[
{
'text':'[{}] {}'.format(time.asctime(), message)
}
]
}
# 增加 json 可读性
# command = json.dumps(command, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False)
command = json.dumps(command)
command = 'tellraw @a {}'.format(command)
await send_command(websocket, command)
def run_server(function):
# 修改 ip 地址和端口
start_server = websockets.serve(function, 'localhost', 8080)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

View File

View File

@@ -1,5 +0,0 @@
zh-CN
简体中文 中国大陆
Simplified Chinese, Chinese Mainland
简体中文 中国大陆

View File

@@ -1,99 +0,0 @@
# -*- coding:utf-8 -*-
'''此功能已废弃'''
# W-YI 金羿
# QQ 2647547478
# 音·创 开发交流群 861684859
# Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
# 版权所有 Team-Ryoun 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray")
# 若需转载或借鉴 请附作者
"""
Copyright 2022 Team-Ryoun 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray")
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# 代码写的并非十分的漂亮还请大佬多多包涵本软件源代码依照Apache软件协议公开
# -----------------------------分割线-----------------------------
# 诸葛亮与八卦阵帮忙修改语法 日期:---2022年1月19日
# 统计致命三级错误0个警告二级错误0个语法一级错误12个
# 目前我的Pycharm并没有显示任何错误有错误可以向
# bgArray 诸葛亮与八卦阵
# QQ 474037765 或最好加入:音·创 开发交流群 861684859
# ------------------------- split line-----------------------------
# Zhuge Liang and Bagua array help to modify the grammar date: -- January 19, 2022
# Statistics: fatal (Level 3) errors: 0; Warning (Level 2) errors: 15; Syntax (Level 1) error: 597
# At present, my Pycham does not display any errors. If there are errors, you can report them to me
# Bgarray Zhuge Liang and Bagua array
# QQ 474037765 or better join: Musicreater development exchange group 861684859
# ------------------------- split line-----------------------------
# 下面为正文
# 将程序中用双引号""括起来的字符串
# 转为字符串列表 list[str, str, ...]
# 方便进行语言翻译支持。
import sys
startWith = 0
def __main__():
textList = []
for fileName in sys.argv[1:]:
print('读取文件: {}'.format(fileName))
fileText = []
for line in open(fileName, 'r', encoding='utf-8'):
while line.count('"') >= 2:
# 只有上帝看得懂我在写什么。
if line[
line.index('"'):2 + line[line.index('"') + 1:].index('"') + len(line[:line.index('"')])] in textList:
thisText = textList.index(
line[line.index('"'):2 + line[line.index('"') + 1:].index('"') + len(line[:line.index('"')])])
else:
thisText = len(textList)
textList.append(
line[line.index('"'):2 + line[line.index('"') + 1:].index('"') + len(line[:line.index('"')])])
line = line.replace(
line[line.index('"'):2 + line[line.index('"') + 1:].index('"') + len(line[:line.index('"')])],
'READABLETEXT[{}]'.format(thisText + startWith)
)
fileText.append(line)
open(fileName + '_C', 'w', encoding='utf-8').writelines(fileText)
outFile = open('lang__.py', 'w', encoding='utf-8')
outFile.write('''# -*- coding:utf-8 -*-
# 由金羿翻译工具生成字符串列表
# 请在所需翻译文件前from 此文件 import READABLETEXT
READABLETEXT = {
''')
for i in range(len(textList)):
outFile.write(" {}:{},\n".format(i + startWith, textList[i]))
outFile.write('}')
outFile.close()
if __name__ == '__main__':
__main__()

View File

@@ -1,180 +0,0 @@
# -*- coding:utf-8 -*-
# 由金羿翻译工具生成字符串列表
# 请在所需翻译的文件前from 此文件 import READABLETEXT
READABLETEXT = {
'Translator': (("Eilles Wan (金羿)", True),),
# 此处是语言翻译者列表,其中每个元组第一项为显示文本,第二项为此文本是否为开发者名字
0: "ERROR❌",
1: "TIPS❗",
2: "Clearing log(this wont be in the file)",
3: "Could not clear the temporary files or logs",
4: "saved",
5: "New Musicreater Project",
6: "Select old-type project",
7: "Select Musicreater Project",
8: "Cant open:{}, please check if youve entered the right name",
9: "Musicreat - About",
10: "Musicreater",
11: "Ver. {}",
12: """Team-Ryoun for Minecraft\n×\nTeam-Ryoun for Software Development""",
13: "OK",
14: "Inpute Notes",
15: (("- Developers -", False),
("Eilles Wan (金羿)", True), ("EillesWan@outlook.com", False), ("QQ 2647547478", False),
("bgArray “诸葛亮与八卦阵”", True), ("QQ 474037765", False)),
# 此处是开发者列表,其中每个元组第一项为显示文本,第二项为此文本是否为开发者名字
16: "- Translators -",
# 17:"",
18: "QQ Group: 861684859",
19: "Musicreater - Help",
20: "Select sound file",
21: "Select MIDI file",
22: "Select NoteText file",
23: "Get Note info",
24: "Write in Note info: {}",
25: "Select generating file",
26: "Select generating folder",
27: "Select generating .mcpack file",
28: "Input position info",
29: "Select generating world folder",
30: "Select generating Function Pack",
31: "Select .mcfunction file ",
32: "Select .bdx file ",
33: "DONE✔",
34: "Input playing rate",
35: "Generating",
36: "Select a world folder",
37: "Make sure",
38: "Generate .RyStruct file",
39: "FAILED❌",
40: "Report message inpution",
41: "Musicreater - {}",
42: "ExecutingEntityName: {}",
43: "ScoreboardName: {}",
44: "Instrument: {}",
45: "TrackName: {}",
46: "PackName: {}",
47: "MusicTitle: {}",
48: "IsRepeat?: {}",
49: "Player'sTargetSelector: {}",
50: "Modify Main Option",
51: "Modify Track Option",
52: "Default Instrument: Enter English\n",
53: "Open...",
54: "Open Old Project...",
55: "Save",
56: "Save as...",
57: "Exit",
58: "File",
59: "Load tracks from sound",
60: "Load tracks from Midi",
61: "Load tracks from Text",
62: "Input notes to track",
63: "Edit",
64: "Generate file...",
65: "Generate function pack...",
66: "Generate .mcpack file...",
67: "Functions(Pack)",
68: "Save music as blocks into a map",
69: "Save music as blocks into a exist map...",
70: "Save music as commands into a map",
71: "Save music as commands into a exist map...",
72: "Save music as notebox into a map",
73: "Save music as notebox into a exist map...",
74: "World",
75: "Generate a function that fits current music...",
76: "Export selected track as commands in .bdx...",
77: "Export .bdx file from map...",
78: "Export .RyStruct file from map...",
79: "Load functions into a world...",
80: "Separate long .mcfunction file into small ones and set them into a world as a chain...",
81: "Additional Functions",
82: "Show generating result",
83: "Set a websocket server on localhost:8080 and play the selected track",
84: "Experimental Functions",
85: "Clear log file",
86: "Clear save file(obsolete)",
87: "Help",
88: "About",
89: "Send a bug report",
90: "Q&A",
91: "Main Options",
92: "Export music as .BDX...",
93: "请输入指令链生成最高相对高度(≥5)",
94: "❌You should input a number which is not lower then 4, please reinput again.",
95: "Structure",
96: "Reset Main Options",
97: "Track Options",
# 98:"",
# 99:"",
# 100:"",
# 101:"",
102: "Delete Selected Track",
# 103:"",
# 104:"",
105: "Error with finding or reading file😢{}",
106: "Project is unsaved, save before close?",
107: "Saved in: {}",
108: ("Musicreater 0.0.X Project","Musicreater 0.1+ Project","Musicreater 0.1+ TESTver Project"),
109: "Any Type",
110: "NoteFunCreater Project",
111: "MMFM (V0.0.6) Project",
112: "All Types",
113: ".MP3 file(piano sound)",
114: "Midi file",
115: "Text file",
116: "Position Inpution",
117: "Format Error❌, please Reinput!",
118: ".MCFUNCTION",
119: "The position of the ChainCB for execution:",
# 120: "",
121: "您的函数文件不大于一万条指令,无需进行分割操作。",
122: "请输入执行链生成相对坐标:",
123: "FastBuilder Structure",
124: "Done!\n{}",
125: "一秒,音乐走几拍?",
126: "按下确认后在游戏中使用connect指令连接localhost:8080即可播放",
127: "请输入区域选择的开始坐标:",
128: "请输入区域选择的结束坐标:",
129: "Whether air block remain when export?",
130: "Musicreater Structure",
131: "Done😃\n{}",
132: "Failed❌\n{}\n{}",
133: "Have not developed yet...",
134: "Your name",
135: "Your contact",
136: "Your description of Problem",
137: "Log file will be cleared when you exit.",
138: "Log file will NOT be cleared when you exit.",
139: "修改包名",
140: "修改音乐标题",
141: "修改玩家选择器\n注意!要加上中括号“[]”",
142: "修改本音轨的执行实体名",
143: "修改本音轨所用的积分板",
144: "修改本音轨所用乐器",
145: "您输入的乐器并非游戏内置乐器,是否继续用您输入的字符作为乐器?",
146: "修改本音轨生成的文件名",
# -----2022.1.25更新
147: "生成新文件至...",
148: "从midi导入音轨且用新方法解析",
149: "Open New: Musicreater Project...",
150: "保存为新项目",
151: "另存为新项...",
152: "(开发调试)关闭本次日志记录",
153: "生成新函数包至...",
154: "生成新函数附加包文件至...",
155: "生成新函数附加包文件,并将神羽资源包以散包形式放置至...",
156: "Select [MiraclePlumeResourcePack]...",
157: "没有路径文件(.rpposi文件)请仔细阅读Readme或先试用帮助与疑问->[神羽资源包位置选择]:选择文件夹... 方法添加路径文件吧!",
158: "有路径文件(.rpposi文件)但路径指示错误请仔细阅读Readme或先用帮助与疑问->[神羽资源包位置选择]:选择文件夹... 更改路径!",
159: "更改路径文件(.rpposi文件)成功!!",
160: "从midi导入音轨且用类方法解析",
161: "打开 类方法: 音·创项目...",
162: "保存为类方法项目",
163: "另存为类方法项...",
}

View File

@@ -1,203 +0,0 @@
# -*- coding:utf-8 -*-
'''对于音·创的语言支持兼语言文件编辑器'''
"""
Copyright 2022 Team-Ryoun
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
DEFAULTLANGUAGE = 'zh-CN'
LANGUAGELIST = {
# 第一个是语言的中文名称和地区
# 第二个是语言的英文名称和地区
# 第三个是语言的本地名称和地区
'zh-CN': (
"简体中文 中国大陆",
"Simplified Chinese - China Mainland",
"简体中文 中国大陆",
),
'zh-TW': (
"繁体中文 中国台湾省",
"Traditional Chinese - Taiwan, China",
"正體中文,中国台灣省",
),
# 'zh-HK': (
# "繁体中文 香港",
# "Traditional Chinese - the Hong Kong Special Administrative Region",
# "繁體中文,香港特別行政區",
# ),
# 'zh-MO': (
# "繁体中文 澳门",
# "Traditional Chinese - the Macao Special Administrative Region",
# "繁體中文,澳門特別行政區",
# ),
'en-GB': (
"英语 英国",
"British English - the United Kingdom",
"British English - the United Kingdom",
),
'zh-ME' : (
"喵喵文 中国大陆",
"Meow Catsese - China Mainland"
"喵喵喵~ 祖国喵~"
)
}
# 对于旧版本音·创的语言支持
# 重构之后将停止使用
try:
from languages.zhCN import READABLETEXT
except:
pass
from msctLib.log import log
def __loadLanguage(languageFilename: str):
with open(languageFilename, 'r', encoding='utf-8') as languageFile:
_text = {}
for line in languageFile:
if line.startswith('#'):
continue
line = line.split(' ', 1)
_text[line[0]] = line[1].replace('\n', '')
langkeys = _text.keys()
with open(languageFilename.replace(languageFilename[-10:-5], 'zh-CN'), 'r', encoding='utf-8') as defaultLangFile:
for line in defaultLangFile:
if line.startswith('#'):
continue
line = line.split(' ', 1)
if not line[0] in langkeys:
_text[line[0]] = line[1].replace('\n', '')
from msctLib.log import log
log(f'丢失对于 {line[0]} 的本地化文本', 'WARRING')
langkeys = _text.keys()
return _text
if not DEFAULTLANGUAGE == 'zh-CN':
if DEFAULTLANGUAGE in LANGUAGELIST.keys():
_TEXT = __loadLanguage('./languages/' + DEFAULTLANGUAGE + '.lang')
else:
raise KeyError(f'无法打开默认语言{DEFAULTLANGUAGE}')
def wordTranslate(singleWord: str, debug: bool = False):
import requests
try:
return \
requests.post('https://fanyi.baidu.com/sug', data={'kw': f'{singleWord}'}).json()['data'][0]['v'].split(
'; ')[0]
except:
log(f"无法翻译文本{singleWord}", level='WARRING', isPrinted=debug)
return None
def _(text: str, debug: bool = False):
try:
return _TEXT[text]
except:
if debug:
raise KeyError(f'无法找到翻译文本{text}')
else:
log(f'无法找到本地化文本{text}','ERROR')
return ''
if __name__ == '__main__':
# 启动语言编辑器
import tkinter as tk
from tkinter.filedialog import askopenfilename as askfilen
LANGNAME = _('LANGLOCALNAME')
def _changeDefaultLang():
global _TEXT
global DEFAULTLANGUAGE
fileName = askfilen(title='选择所翻译的语言文件', initialdir=r'./',
filetypes=[('音·创语言文件', '.lang'), ('所有文件', '*')],
defaultextension='.lang',
initialfile='.lang')
_TEXT = __loadLanguage(fileName)
DEFAULTLANGUAGE = _('LANGKEY')
LANGNAME = _('LANGLOCALNAME')
orignText = ''
transText = ''
for i, j in _TEXT.items():
orignText += i + '\n'
transText += j + '\n'
Origntextbar.insert('end', orignText)
Translatetextbar.insert('end', transText)
global setlangbutton
setlangbutton['text'] = f'对标语言{LANGNAME}'
def _autoSave(event=None):
with open('autosave.tmp.txt', 'w', encoding='utf-8') as f:
f.write(Translatetextbar.get(1.0, 'end'))
print(str(event))
root = tk.Tk()
root.geometry('600x500')
root.bind("<Motion>", _autoSave)
nowText = ''
Orignrame = tk.Frame(root, bd=2)
Translaterame = tk.Frame(root, bd=2)
Orignscrollbar = tk.Scrollbar(Orignrame)
Origntextbar = tk.Text(Orignrame, width=35, height=40)
Translatetextbar = tk.Text(Translaterame, width=40, height=37, undo=True)
Translatescrollbar = tk.Scrollbar(Translaterame)
def ctrlZ():
Translatetextbar.edit_undo()
Translatetextbar.bind("<Control-z>", ctrlZ)
def ctrlY():
Translatetextbar.edit_redo()
Translatetextbar.bind("<Control-y>", ctrlY)
tk.Button(Translaterame, text='保存', command=_autoSave).pack(side='bottom', fill='x')
tk.Label(Orignrame, text='中文原文').pack(side='top')
Origntextbar.pack(side='left', fill='y')
Orignscrollbar.pack(side='left', fill='y')
setlangbutton = tk.Button(Translaterame, text=f'对标语言{LANGNAME}', command=_changeDefaultLang)
setlangbutton.pack(side='top')
Translatescrollbar.pack(side='right', fill='y')
Translatetextbar.pack(side='right', fill='y')
Orignscrollbar.config(command=Origntextbar.yview)
Origntextbar.config(yscrollcommand=Orignscrollbar.set)
Translatescrollbar.config(command=Translatetextbar.yview)
Translatetextbar.config(yscrollcommand=Translatescrollbar.set)
Orignrame.pack(side='left')
Translaterame.pack(side='right')
tk.mainloop()

View File

@@ -1,16 +0,0 @@
# 音·创 本地化语言文件
# 使用 空格 把键与对应文本隔开
# 使用 井字符 在每一行的开头编写注释
# 注意!井字符请开头放,切勿含有空格
# 也切勿在正文结尾放!
LANGKEY zh-CN
LANGCHINESENAME 简体中文 中国大陆
LANGENGLIFHNAME Simplified Chinese, Chinese Mainland
LANGLOCALNAME 简体中文 中国大陆
MSCT 音·创
F音创 音·创 Musicreater
关于 音·创 - 关于
凌云pairs 凌天之云创新我的世界开发团队\n×\n凌天之云创新计算机应用软件开发团队
开发者 - 开发人员 -
译者 - 翻译人员 -
确定 确定

View File

@@ -1,17 +0,0 @@
# 音创创喵的 本地化语言文件
# 使用 空格 把键与对应文本隔开
# 使用 井字符 在每一行的开头编写注释
# 注意!井字符请开头放,切勿含有空格
# 也切勿在正文结尾放!
LANGKEY zh-ME
LANGCHINESENAME 喵喵文 中国大陆
LANGENGLIFHNAME Meow Catsese, China Mainland
LANGLOCALNAME 喵喵喵~ 祖国喵~
TRANSLATERS 金羿,Email EillesWan@outlook.com,QQ 2647547478
MSCT 音创创喵~
F音创 音·创 Musicreater
关于 音创创喵的主人们
凌云pairs 凌天之云创新我的世界开发团队\n×\n凌天之云创新计算机应用软件开发团队
开发者 ~ 主人们 ~
译者 ~ 爸爸妈妈们 ~
确定 好哒~

View File

@@ -1,185 +0,0 @@
# -*- coding:utf-8 -*-
# 由金羿翻译工具生成字符串列表
# 请在所需翻译文件前from 此文件 import READABLETEXT
READABLETEXT = {
'Translator': (("金羿 Eilles 原稿", True),),
# 此处是语言翻译者列表,其中每个元组第一项为显示文本,第二项为此文本是否为开发者名字
0: "错误❌",
1: "提示❗",
2: "清除log此句不载入日志",
3: "无法清除日志及临时文件",
4: "已存储",
5: "新建 音·创 项目",
6: "请选择旧类型的项目",
7: "请选择 音·创 项目",
8: "无法打开文件:{},请查看您是否输入正确",
9: "音·创 - 关于",
10: "音·创 Musicreater",
11: "当前版本:{}",
12: """凌云我的世界开发团队\n×\n凌云计算机应用软件开发团队""",
13: "确定",
14: "请输入音符",
15: (("- 开发者 -", False),
("金羿 Eilles", True), ("EillesWan@outlook.com", False), ("QQ 2647547478", False),
("bgArray “诸葛亮与八卦阵”", True), ("QQ 474037765", False),
),
# 此处是开发者列表,其中每个元组第一项为显示文本,第二项为此文本是否为开发者名字
16: "- 翻译者 -",
# 17:"",
18: "讨论群: 861684859",
19: "音·创 - 帮助",
20: "请选择钢琴声音的音乐文件",
21: "请选择 MIDI 文件",
22: "请选择 音符文本 文件",
23: "获取音符信息",
24: "音符数据写入{}",
25: "请选择文件生成的位置",
26: "请选择文件夹生成的位置",
27: "请选择.mcpack文件生成的位置",
28: "坐标信息输入",
29: "请选择世界文件夹生成的位置",
30: "请选择函数包生成的位置",
31: "请选择 .mcfunction 文件",
32: "请选择需要生成的.bdx文件",
33: "完成✔",
34: "输入播放速度",
35: "创建中",
36: "请选择世界文件夹所在的位置",
37: "请确认",
38: "生成.RyStruct文件",
39: "失败❌",
40: "邮件反馈信息输入",
41: "音·创 - {}",
42: "执行实体名:{}",
43: "使用计分板:{}",
44: "所用的乐器:{}",
45: "当前音轨名:{}",
46: "包名:{}",
47: "音乐标题:{}",
48: "是否重复:{}",
49: "玩家选择器:{}",
50: "修改主设置",
51: "修改节设置",
52: "游戏内置乐器如下:请输入英文\n",
53: "打开音·创项目...",
54: "打开旧项目...",
55: "保存项目",
56: "另存为...",
57: "退出",
58: "文件",
59: "从钢琴MP3导入音轨",
60: "从midi导入音轨",
61: "从文本文件导入音轨",
62: "输入音符至音轨",
63: "编辑",
64: "生成文件至...",
65: "生成函数包至...",
66: "生成附加包文件至...",
67: "函数(包)",
68: "将音乐以方块存储生成地图",
69: "将音乐以方块存储载入地图…",
70: "将音乐以指令存储生成地图",
71: "将音乐以指令存储载入地图…",
72: "将音乐以音符盒存储生成地图",
73: "将音乐以音符盒存储载入地图…",
74: "世界",
75: "生成符合当前音乐的函数播放器…",
76: "将选中音轨以指令存储生成.bdx文件…",
77: "由地图导出至.bdx文件…",
78: "由地图导出至.RyStruct文件…",
79: "将函数载入世界…",
80: "将大函数分割并建立执行链…",
81: "辅助功能",
82: "展示生成结果",
83: "建立位于localhost:8080上的websocket服务器播放选中音轨",
84: "实验性功能",
85: "清除日志文件",
86: "清除早期版本的存储文件",
87: "帮助",
88: "关于",
89: "发送错误日志反馈",
90: "帮助与疑问",
91: "音乐总设置(项目设置)",
# =============================================================此处有新增
92: "将音乐导出为BDX",
93: "请输入指令链生成最高相对高度(≥5)",
94: "您输入的数据有误❌相对高度请输入一个不小于4的值请重新输入。",
95: "结构操作",
96: "重置项目设置",
97: "当前音轨设置(段落设置)",
# 98:"",
# 99:"",
# 100:"",
# 101:"",
102: "删除选中音轨",
# 103:"",
# 104:"",
105: "找不到或无法读取文件😢:{}",
106: "您当前的项目已修改但未存储,是否先保存当前项目?",
107: "项目已经存储至:{}",
108: ("音·创0.0.X工程文件", "音·创0.1+工程文件", "音·创0.1+TEST工程文件"),
109: "任意类型",
110: "函数音创工程文件",
111: "MMFM0.0.6版本工程文件",
112: "全部类型",
113: "钢琴声音的音频文件",
114: "Midi文件",
115: "文本文件",
116: "请输入坐标:",
117: "您输入的格式有误❌,请重新输入。",
118: "我的世界指令函数文件",
119: "请输入执行链生成坐标:",
# 120: "",
121: "您的函数文件不大于一万条指令,无需进行分割操作。",
122: "请输入执行链生成相对坐标:",
123: "FastBuilder结构文件",
124: "转换结束!\n{}",
125: "一秒,音乐走几拍?",
126: "按下确认后在游戏中使用connect指令连接localhost:8080即可播放",
127: "请输入区域选择的开始坐标:",
128: "请输入区域选择的结束坐标:",
129: "所选区块导出时是否需要保留空气方块?",
130: "音·创结构文件",
131: "文件已生成\n{}",
132: "文件无法生成\n{}\n{}",
133: "本功能尚未开发。",
134: "您的称呼",
135: "您的联系方式",
136: "您对问题的描述",
137: "在程序结束后将清除日志及临时文件信息。",
138: "在程序结束后将不会清除日志及临时文件信息。",
139: "修改包名",
140: "修改音乐标题",
141: "修改玩家选择器\n注意!要加上中括号“[]”",
142: "修改本音轨的执行实体名",
143: "修改本音轨所用的积分板",
144: "修改本音轨所用乐器",
145: "您输入的乐器并非游戏内置乐器,是否继续用您输入的字符作为乐器?",
146: "修改本音轨生成的文件名",
# -----2022.1.25更新
147: "生成乐器文件至...",
148: "从midi导入音轨且用新方法解析",
# 149: "打开 新: 音·创项目...",
# 150: "保存为新项目",
# 151: "另存为新项...",
152: "(开发调试)关闭本次日志记录",
153: "生成乐器函数包至...",
154: "生成乐器函数附加包文件至...",
155: "生成乐器函数附加包文件,并将神羽资源包以散包形式放置至...",
156: "[神羽资源包位置选择]:选择文件夹...",
157: "没有路径文件(.rpposi文件)请仔细阅读Readme或先试用帮助与疑问->[神羽资源包位置选择]:选择文件夹... 方法添加路径文件吧!",
158: "有路径文件(.rpposi文件)但路径指示错误请仔细阅读Readme或先用帮助与疑问->[神羽资源包位置选择]:选择文件夹... 更改路径!",
159: "更改路径文件(.rpposi文件)成功!!",
160: "从midi导入音轨且用类方法解析",
# 161: "打开 类方法: 音·创项目...",
# 162: "保存为类方法项目",
# 163: "另存为类方法项...",
164: "生成新文件至...",
165: "生成新函数包至...",
166: "生成新函数附加包文件至...",
167: "这个midi文件读取不了mido解析报错"
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

View File

@@ -1,50 +0,0 @@
# -*- coding: UTF-8 -*-
"""音·创的核心内置组件功能集合"""
class version:
libraries = (
'mido', 'amulet', 'amulet-core', 'amulet-nbt', 'piano_transcription_inference', 'pypinyin',
'pyinstaller', 'py7zr','websockets', 'torch', 'requests'
)
"""当前所需库"""
version = ('0.2.0', 'Delta',)
"""当前版本"""
def __init__(self) -> None:
self.libraries = version.libraries
"""当前所需库"""
self.version = version.version
"""当前版本"""
def installLibraries(self,index:str = 'https://pypi.tuna.tsinghua.edu.cn/simple'):
"""安装全部开发用库"""
from sys import platform
import os
if platform == 'win32':
import shutil
try:
shutil.rmtree(os.getenv('APPDATA') + '\\Musicreater\\')
except FileNotFoundError:
pass
for i in self.libraries:
print("安装库:" + i)
os.system(f"python -m pip install {i} -i {index}")
elif platform == 'linux':
os.system("sudo apt-get install python3-pip")
os.system("sudo apt-get install python3-tk")
os.system("sudo apt-get install python3-tkinter")
for i in self.libraries:
print("安装库:" + i)
os.system(f"sudo python3 -m pip install {i} -i {index}")
def __call__(self):
'''直接安装库,顺便返回一下当前版本'''
self.installLibraries()
return self.version

View File

@@ -1,151 +0,0 @@
# -*- coding:utf-8 -*-
import pickle
import json
from typing import Any, Iterable
class pickleIO:
def __init__(self,fileName:str,data: Any = None) -> None:
'''简单的pickle操作功能'''
self.file = fileName
if data:
self._data = data
else:
with open (self.file, 'rb') as f:
self._data = pickle.load(f)
def __call__(self, *args: Any, **kwds: Any) -> Any:
return self.data
def write(self):
'''将数据写入pickle'''
with open (self.file, 'wb') as f:
pickle.dump(self._data, f)
def load(self) -> Any:
'''从文件读取数据'''
with open (self.file, 'rb') as f:
self._data = pickle.load(f)
return self.data
@property
def data(self):
'''返回数据值'''
if self._data is None:
raise ValueError('无可用值载入或值为None')
else:
return self._data
class jsonIO:
def __init__(self,fileName:str,data: Any = None) -> None:
'''简单的json操作功能'''
self.file = fileName
if data:
self._data = data
else:
with open (self.file, 'r', encoding='utf-8') as f:
self._data = json.load(f)
def __call__(self, *args: Any, **kwds: Any) -> Any:
return self.data
def write(self):
'''将数据写入json'''
with open (self.file, 'w', encoding='utf-8') as f:
json.dump(self._data, f)
def load(self) -> Any:
'''从文件读取数据'''
with open (self.file, 'r', encoding='utf-8') as f:
self._data = json.load(f)
return self.data
@property
def data(self):
'''返回数据值'''
return self._data
class uniteIO:
def __init__(self,fileName:str,fileType = None,data: Any = None) -> None:
'''简单的文件数据IO操作功能'''
self.filename = fileName
if not fileType is None:
self._type = fileType
else:
try:
with open (self.filename, 'r', encoding='utf-8') as f:
self._type = json
except:
with open (self.file, 'rb') as f:
self._type = pickle
if not data is None:
self._data = data
else:
self._data = self.load()
def __call__(self, *args: Any, **kwds: Any) -> Any:
return self.data
def write(self):
'''将数据写入文件'''
if self._type == json:
self._wfile = open(self.filename, 'w', encoding='utf-8')
elif self._type == pickle:
self._wfile = open(self.file, 'wb')
self._type.dump(self._data, self._wfile)
def load(self) -> Any:
'''从文件读取数据'''
if self._type == json:
self._rfile = open(self.filename, 'r', encoding='utf-8')
elif self._type == pickle:
self._rfile = open(self.file, 'rb')
self._data = self._type.load(self._rfile)
return self.data
@property
def data(self):
'''返回数据值'''
return self._data
if __name__ == '__main__':
from sys import argv
if argv[1]:
input(uniteIO(argv[1]).data)

View File

@@ -1,297 +0,0 @@
# -*- coding: utf-8 -*-
'''音·创的GUI窗口界面显示库
:若要使用其他界面显示,请详见:
:开发说明|指南'''
import tkinter as tk
import tkinter.simpledialog as sdialog
import tkinter.filedialog as fdialog
from msctLib.log import log
DEFAULTBLUE = (0, 137, 242)
WEAKBLUE = (0, 161, 231)
LIGHTBLUE = (38, 226, 255)
RED = (255, 52, 50)
PURPLE = (171, 112, 255)
GREEN = (0, 255, 33)
WHITE = (242, 244, 246)
BLACK = (18, 17, 16)
backgroundColor = WHITE
frontgroundColor = BLACK
loadingColor = DEFAULTBLUE
errorColor = RED
okColor = GREEN
tipsColor = PURPLE
# 注UI界面字体、代码字体
fontPattern = ('DengXian Light', 'Fira Code')
class disp:
'''音·创 的基本Tk窗口显示库'''
def __init__(
self,
root: tk.Tk = tk.Tk(),
debug: bool = False,
title: str = '音·创',
geometry: str = '0x0',
iconbitmap: tuple = ('', ''),
menuWidget: dict = {},
wordView: str = '音·创 Musicreater',
buttons: list = [],
settingBox: list = [],
notemap: list = [],
) -> None:
'''使用参数建立基本的 音·创 窗口
:param root 根窗口
:param debug 是否将日志输出到控制台
:param title 窗口标题
wordview: str #言论部分显示的字样
button: list = [ # 操作按钮部分
dict = {
按钮名称 : tuple(按钮图标,执行函数)
},
],
settingbox: list = [ # 设置部分显示的字样及其对应的设置函数
(
设置名称:str,
值类型:tuple,
显示内容:str,
设置操作函数:<function>,
)
],
map: list = [ # 一首曲目的音符数据
音符数据
]
'''
self.root = root
self.title = title
self.geometry = geometry
self.menuWidget = menuWidget
self.wordView = wordView
self.buttons = buttons
self.settingBox = settingBox
self.notemap = notemap
self.setTitle(title, debug)
self.setGeometry(geometry, debug)
self.setIcon(*iconbitmap, debug=debug)
self.setMenu(menuWidget)
self.initWidget(wordView, buttons, settingBox, notemap)
# 设定函数部分
def setTitle(self, title: str = '', debug: bool = False) -> None:
'''设置窗口标题'''
self.root.title = title
if debug:
log(f"设置窗口标题{title}")
def setGeometry(self, geometry: str = '0x0', debug: bool = False) -> None:
'''设置窗口大小'''
self.root.geometry(geometry)
if debug:
log(f"设置窗口大小{geometry}")
def setIcon(
self, bitmap: str = './musicreater.ico', default: str = '', debug: bool = False
) -> None:
'''设置窗口图标
注意default参数仅在Windows下有效其意为将所有没有图标的窗口设置默认图标
如果在非Windows环境使用default参数一个Error将被升起'''
if not debug:
try:
if default:
self.root.iconbitmap(bitmap, default)
log(f'设置图标为{bitmap},默认为{default}')
else:
self.root.iconbitmap(bitmap)
log(f'设置图标为{bitmap}')
return True
except Exception as e:
log(str(e), 'ERROR')
return False
else:
self.root.iconbitmap(bitmap, default)
return
def setMenu(self, menuWidgets: dict = {}, debug: bool = False) -> None:
'''设置根菜单'''
if not menuWidgets:
# 如果传入空参数则返回当前菜单
try:
return self.RootMenu
except Exception as E:
if debug:
raise E
else:
log('无法读取菜单信息', 'WARRING')
# 如果不是空参数则新建菜单
self.RootMenu = {}
self.mainMenuBar = tk.Menu(self.root)
for menuName, menuCmd in menuWidgets.items():
# 取得一个菜单名和一堆菜单函数及其显示名称
menu = tk.Menu(self.mainMenuBar, tearoff=0)
for cmdName, cmdFunc in menuCmd.items():
if cmdName:
menu.add_command(label=cmdName, command=cmdFunc)
else:
menu.add_separator()
self.mainMenuBar.add_cascade(label=menuName, menu=menu)
self.RootMenu[menuName] = menu
self.root.config(menu=self.mainMenuBar)
def addMenu(self, menuRoot: str = '', menuLabel: str = '', menuCommand=None):
'''增加一个菜单项
:param menuRoot : str
菜单的根菜单,即所属的菜单上的文字
:param menuLabel : str
所需要增加的项目显示的文字
:param menuCommand : <function>
'''
if menuRoot in self.RootMenu.keys:
# 如果已经有父菜单
if menuLabel:
# 增加菜单指令
self.RootMenu[menuRoot].add_command(
label=menuLabel, command=menuCommand
)
else:
# 增加分隔栏
self.RootMenu[menuRoot].add_separator()
else:
# 没有父菜单则新增一个父菜单
menu = tk.Menu(self.mainMenuBar, tearoff=False)
if menuLabel:
menu.add_command(label=menuLabel, command=menuCommand)
else:
menu.add_separator()
self.mainMenuBar.add_cascade(label=menuRoot, menu=menu)
self.RootMenu[menuRoot] = menu
def initWidget(
self,
wordView: str = '音·创 Musicreater',
buttons: list = [],
settingBox: list = [],
notemap: list = [],
) -> None:
'''设置窗口小部件,分为:
:言·论 WordView
:快捷按钮面板 ButtonBar
:设置框 SettingBar
:音轨框 TrackBar
:各个音轨的显示框 TrackFrame
:信息显示版 InfoBar
'''
self._wordviewBar = tk.Label(self.root, bg='white', fg='black', text=wordView)
self.setWordView(wordView)
def setWordView(self, text: str) -> None:
self._wordviewBar['text'] = text
# 预置函数部分
def authorMenu(
authors: tuple = (
('金羿', 'Email EillesWan@outlook.com', 'QQ 2647547478'),
('诸葛亮与八卦阵', 'QQ 474037765'),
),
translaters: tuple = None,
):
'''自定义作者界面'''
from languages.lang import _
from languages.lang import DEFAULTLANGUAGE
from msctLib.buildIN import version
aabw = tk.Tk()
aabw.title(_('关于'))
aabw.geometry('550x600') # 像素
tk.Label(aabw, text='', font=('', 15)).pack()
tk.Label(aabw, text=_('F音创'), font=('', 35)).pack()
tk.Label(
aabw,
text='{} {}'.format(version.version[1] + version.version[0]),
font=('', 15),
).pack()
# pack 的side可以赋值为LEFT RTGHT TOP BOTTOM
# grid 的row 是列数、column是行排注意这是针对空间控件本身大小来的即是指向当前控件的第几个。
# place的 x、y是(x,y)坐标
tk.Label(
aabw,
image=tk.PhotoImage(file='./resources/RyounLogo.png'),
width=200,
height=200,
).pack()
tk.Label(aabw, text=_('凌云pairs'), font=('', 20)).pack()
tk.Label(aabw, text='', font=('', 15)).pack()
tk.Label(aabw, text=_('开发者'), font=('', 15)).pack()
for i in authors:
for j in i:
tk.Label(
aabw,
text=j,
font=(
'',
17 if i.index(j) == 0 else 15,
'bold' if i.index(j) == 0 else '',
),
).pack()
tk.Label(aabw, text='', font=('', 5)).pack()
if DEFAULTLANGUAGE != 'zh-CN':
tk.Label(aabw, text=_('译者'), font=('', 15)).pack()
for i in _('TRANSLATERS').split(';'):
for j in i.split(','):
tk.Label(
aabw,
text=j,
font=(
'',
17 if i.split(',').index(j) == 0 else 15,
'bold' if i.split(',').index(j) == 0 else '',
),
).pack()
def exitAboutWindow():
aabw.destroy()
tk.Button(aabw, text=_('确定'), command=exitAboutWindow).pack()
aabw.mainloop()
class ProgressBar:
def __init__(
self,
root: tk.Tk = tk.Tk(),
style: tuple = (DEFAULTBLUE, BLACK, WHITE),
type: bool = False,
info: str = '',
debug: bool = False,
) -> None:
'''建立一个进度条或者加载等待界面
:param root : tk.Tk
建立进度条的根窗口
:param style : tuple
设置主题颜色,第一个参数为进度条或者等待转圈圈的颜色,第二个参数为前景色,第三个是背景色
:param type : bool
类型,为 False 时为进度条,为 True 时为等待板
:param info : str
显示的附加信息
:param debug : bool
是否输出日志到控制台'''
self.root = root
if __name__ == '__mian__':
import os
os.chdir('../')
disp.authorMenu()

View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
'''音·创的内置功能库
:若要加入其他功能,详见:
:开发说明|指南'''

View File

@@ -1,82 +0,0 @@
"""音·创的日志消息处理"""
# 诸葛亮与八卦阵帮忙修改语法 日期:---2022年1月19日
# 统计致命三级错误0个警告二级错误0个语法一级错误9个
# 对开发者说的话:
#
# 请不要修改这里的日志,日志是给开发者和专业人士看的
# 而不是给普通用户看的,因此,没必要使用开发者自己也
# 不习惯的日志系统,比如说,之前诸葛亮与八卦阵 (bgArray)
# 用了 logging 库来改写我原来的日志支持,但是我反
# 而找不到我想要的信息了,所以,日志系统给我们开发者
# 自己看得好就可以了昂,真的别改了。而且,诸葛八卦改
# 了之后并没有多好,喵喵喵,所以我就换回来了。我知道
# logging 库比较常用,而且功能也好,但是我们毕竟没
# 这个必要,就别用那个库了昂,球球了~
# ——金羿 Eilles
# 2022 03 09
# To ALL the developers who will change this part:
#
# Please do NOT change anything in this file!
# The log file is only for developers or
# someone who knows a lot about our program
# to see, but not the common users. So it
# is NOT NECESSARY to use a logging system
# that we do not familiar or we do not like.
# Take bgAray “诸葛亮与八卦阵” as a example,
# he once change this `log.py` into
# logging-library-based log support system.
# But after the change had done, I could NOT
# find useful infomation according to the
# log file... So use this file but not to
# make changes PLEASE!!! I know some libraries
# like logging is usually better than the
# simple system in this file and it is normal
# to use but, I think it is not necessery,
# so PLEASE DO NOT USE OTHER LIBs TO
# OVERWRITE MY LIBRARY, THANKS.
# ——Eilles 金羿
# 03/09/2022
import datetime,os
#载入日志功能
StrStartTime = str(datetime.datetime.now()).replace(':', '_')[:-7]
'''字符串型的程序开始时间'''
def log(info:str = '',level : str = 'INFO', isPrinted:bool = False):
'''将信息连同当前时间载入日志
:param info : str
日志信息
:param level : str['INFO','WARRING','ERROR','CRASH']
或 int[ 1, 2, 3, 4 ]
信息等级
:param isPrinted : bool
是否在控制台打印
:return bool
表示是否完成任务'''
if type(level) == type(1):
level = ['INFO','WARRING','ERROR','CRASH'][level-1]
try:
if not os.path.exists('./log/'):
os.makedirs('./log/')
outputinfo = f'{str(datetime.datetime.now())[11:19]}-[{level}] {info}'
with open('./log/'+StrStartTime+'.msct.log', 'a',encoding='UTF-8') as f:
f.write(outputinfo+'\n')
if isPrinted:
print(outputinfo)
return True
except:
return False

View File

@@ -1,25 +0,0 @@
# -*- coding:utf-8 -*-
DEFAULTBLUE = (0, 137, 242)
WEAKBLUE = (0, 161, 231)
LIGHTBLUE = (38, 226, 255)
RED = (255, 52, 50)
PURPLE = (171, 112, 255)
GREEN = (0, 255, 33)
WHITE = (242, 244, 246)
BLACK = (18, 17, 16)
settings = {
'language' : 'zh-CN',
'theme' : {
'' : '',
},
}
class msctSetting:
def __init__(self,**settings) -> None:
pass
def __call__(self, **kwds):
pass

View File

@@ -1,127 +0,0 @@
开发说明\|指南
==============
此文件旨在使后期欲参与开发之人员减轻其开发负担同时也为了我们正在开发的人员详细说明功能与用法
掌握开发指南之后在调用函数等的过程中将会更加方便
文件结构
--------
从主文件调用display.py以实现显示调用functions.py以使用功能
functions.py中会调取./addon/目录下的全部功能文件这些功能文件必须先由./addon/addons.pkl来预先定义好
详细说明
--------
### msctLib
用于支持主要功能
#### display.py
1. class disp
- 参数
1. `**kwgs`对窗口的基础设定 `{ '组件名称' : 函数自设定 }` 例如
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ python
{
'version': '0.0.1', # version指的是当前配置格式的版本
'title': "音·创",
'geometry': '1200x900',
'iconbitmap': ('./resources/musicreater.ico', './resources/musicreater.ico'),
'menu' : { #对setMenu有特殊说明
'文件': {
'新建': <function>,
'打开': <function>,
},
},
'widget': { #对窗口部件又详细说明
'wordview':{
'text':'言·论',
},
'settingbox':{}, #后文详细说明
'tracklist':{},
'operation':{},
'map':{},
},
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 函数
1. `setMenu`对菜单的基础设定
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ python
{
菜单名 : {
选项名 : 选项函数
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`菜单名` : `str` 显示在菜单上的字符串
`选项名` : `str` 显示在菜单选项上的字符串
`选项函数` : `function` 菜单调取的函数(无返回值无入参)
`选项名` 的布尔值判定为 `False` 的时候无论 `选项函数` 为何皆插入一段分割线 `选项函数` 不得为空
2. `setWidget`对窗口部件的放置
```python
wordview: dict = { #言论部分显示的字样
'text': str = 显示内容,
# ... 即可用 tk.Label 的参数
},
button: list = [
dict = {
按钮名称 : tuple(按钮图标,执行函数)
},
],
settingbox: list = [ #设置部分显示的字样及其对应的设置函数
(
设置名称:str,
值类型:tuple,
显示内容:str,
设置操作函数:<function>,
)
],
map: list = [
音符数据
]
```
上文中值类型可以是如下几个项
1. `('str',)` 字符串类型使用文本框输入数据
2. `('bool',)` 布尔类型使用复选框输入数据
3. `('num',最小值:int,最大值:int,步长:int = 1)` 数值类型使用数值滑动条输入数据
4. `('list',列表项:list)` 单选类型即列表中多选一使用单选框输入数据
值得注意的是在kwgs中修改的部件设置可以在其变量中读取或热修改对应的变量如下
| 参数 | 对应类中的变量 | 变量类型 | 说明 |
|------------|------------------|---------------------------|---------------------|
| wordview | wordview | str | 显示在 · 区域的文字 |
| button | button | list[dict{}] | 操作按钮 |
| settingbox | settings | list[ Any ] | 设置项目中的值 |
| tracklist | tracknum | tuple(int,int) | 当前选择到的音轨与音轨总数|
| map | notes | list[ class Note ] | 当前的音符列表 |
# 这里是全局设置
'name' : str, #项目名,即包名
'title': str, #音乐名称
'repeation': bool, #是否开启重复播放
'player_selection': str, #玩家选择器,用于选择播放的的玩家
# 下面是音轨独立设置
'track_name': str, #此音轨的名字
'track_sbname': str, #此音轨所使用的计分板
- 变量
使用类中的变量可以对类中的内容进行实时修改

View File

@@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
'''此文件为音·创的插件加载程序为了满足各平台需求使用的是CLI界面'''
# W-YI 金羿
# QQ 2647547478
# 音·创 开发交流群 861684859
# Email EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
# 版权所有 Team-Ryoun 金羿
# 若需转载或借鉴 请附作者
"""
Copyright 2022 Eilles Wan (金羿)
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# 代码写的并非十分的漂亮还请大佬多多包涵本软件源代码依照Apache软件协议公开
# 下面为正文
print("""
音·创 插件管理
Musicreater PLUGINS Management
""")

View File

View File

@@ -1,218 +0,0 @@
import os
import brotli
'''感谢由 Charlie_Ping “查理平” 带来的bdx转换代码'''
class BdxConverter:
__header = "BD@"
__bin_header = b"BDX"
__generator_author = b"&Musicreater"
keys = {
# x--, x++, addSmallX(-128~127), addX(-32768~32767), addBigX(-2147483648~2147483647)
"x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
"y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"],
"z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
"end": b"\x58",
"isSigned": b"\x5a",
"placeCommandBlockWithData": b"\x1b",
"placeBlock": b"\x07"
}
def __init__(self, file_path: str, author: str, blocks):
self.author = author
self.blocks = blocks
self.file_path = file_path
self.direction = [0, 0, 0]
self.block_type = self.get_block_type
self.__file = self.create_and_upload_file
@property
def get_block_type(self):
"""
blocks
[
{
"direction": [x: int, y: int, z: int],
block_name: str,
particular_value: int,
}
]
:return: list 给出的所有方块种类名称
"""
block_type = set()
for block in self.blocks:
block_type.add(block["block_name"])
block_type = list(block_type)
return block_type
@property
def create_and_upload_file(self):
"""
创建一个bdx文件
:return: 一个文件对象
"""
_dir = os.path.dirname(self.file_path)
if not os.path.isdir(_dir):
os.makedirs(_dir)
_bytes = self.__bin_header
_bytes += b"\x00"
_bytes += self.author.encode("utf-8") + self.__generator_author
for i in self.block_type:
_bytes += b"\x00\x01"
_bytes += bytes(i, encoding="utf-8")
_bytes += b"\x00"
_bytes += self.upload_blocks()
_bytes += b"X"
with open(self.file_path, "w+") as f:
f.write("BD@")
f.close()
with open(self.file_path, "ab+") as f:
f.write(brotli.compress(_bytes))
f.close()
return open(self.file_path,'a+')
def upload_blocks(self):
"""
计算差值
写入移动过程
写入方块
更新差值
:return:
"""
_types = b""
for block in self.blocks:
# print(f"当前方块:{block['block_name']}, 位置: {block['direction']}]")
diff = self.move_pointer(self.direction, block["direction"])
_types += diff
if block["block_name"] in ["command_block",
"chain_command_block",
"repeating_command_block"]:
_types += self.obtain_command_block(block)
else:
_types += self.obtain_universal_block(block)
self.direction = block["direction"]
return _types
def move_pointer(self, direction: list, new_direction):
"""
给出 两个[x, y, z]坐标返回pointer的移动过程
:param direction: 坐标 1
:param new_direction: 坐标 2
:return: bytes
"""
_bytes = b""
for i, sign in enumerate(["x", "y", "z"]):
# print(f"<{sign}> 新-旧={new_direction[i]-direction[i]}")
distance = new_direction[i] - direction[i]
if distance == 0:
# print("距离是0跳过了")
continue
_bytes += self.obtain_pointer_type(distance, sign)
# print(f"向 {sign} 运动了 {distance} 格子")
return _bytes
@classmethod
def obtain_pointer_type(cls, num: int, coordinate: str):
"""
用于确定辅助玩家以某一数据类型走指定长度
-1 -> 0
1 -> 1
[128, 127] -> 2
[-32768, 32767] -> 3
[-2147483648, 2147483647] -> 4
:param num:
:param coordinate: 坐标轴种类x y 或 z
:return:
"""
if num == 0:
return
pointer = 0
condition = (num != -1, # byte=0, pointer=1
num < -1 or num > 1, # byte=1, pointer=2
num < -128 or num > 127, # byte=2, pointer=3
num < -32768 or num > 32767, # byte=4, pointer=4
)
for i in condition:
if i:
pointer += 1
pointer_type = cls.keys[coordinate][pointer]
byte_len = 2 ** (pointer - 2)
if byte_len >= 1:
num_byte = num.to_bytes(byte_len, byteorder="big", signed=True)
return pointer_type + num_byte
return pointer_type
def obtain_universal_block(self, block):
"""
给定一个方块, 返回此方块在这个bdx中的id和方块data
:param block: {block_name: str,particular_value: int}
:return: bytes
"""
block_id = b"\x07" + self.block_type.index(block["block_name"]).to_bytes(2, byteorder="big", signed=False)
particular_value = block["particular_value"].to_bytes(2, byteorder="big", signed=False)
block_header = block_id + particular_value
return block_header
def obtain_command_block(self, block):
"""
给定一个命令方块,返回命令方块各种数据
:param block: {
"direction": [x: int, y: int, z: int]
"block_name": str,
"particular_value": int,
"impluse": int, # unsigned_int32
"command": str,
"customName": str,
"lastOutput": str, # 没特殊要求写个\x00就得了
"tickdelay": int, # int32
"executeOnFirstTick": int, # 1 bytes
"trackOutput": int, # 1 bytes
"conditional": int, # 1 bytes
"needRedstone": int # 1 bytes
}
:return: bytes of command_block
"""
block_id = b"\x1b" + self.block_type.index(block["block_name"]).to_bytes(2, byteorder="big", signed=False)
particular_value = block["particular_value"].to_bytes(2, byteorder="big", signed=False)
block_header = block_id + particular_value
for i in [
block["impluse"].to_bytes(4, byteorder="big", signed=False),
bytes(block["command"], encoding="utf-8") + b"\x00",
bytes(block["customName"], encoding="utf-8") + b"\x00",
bytes(block["lastOutput"], encoding="utf-8") + b"\x00",
block["tickdelay"].to_bytes(4, byteorder="big", signed=True),
block["executeOnFirstTick"].to_bytes(1, byteorder="big"),
block["trackOutput"].to_bytes(1, byteorder="big"),
block["conditional"].to_bytes(1, byteorder="big"),
block["needRedstone"].to_bytes(1, byteorder="big")
]:
block_header += i
return block_header
if __name__ == '__main__':
block = [{"direction": [-1, -1, -1], "block_name": "concrete", "particular_value": 5},
{"direction": [1, 5, 1], "block_name": "stained_glass", "particular_value": 7},
{"direction": [2, 4, 1], "block_name": "command_block", "particular_value": 3,
"impluse": 0,
"command": "say A generator test",
"customName": "test",
"lastOutput": "",
"tickdelay": 24,
"executeOnFirstTick": 0,
"trackOutput": 0,
"conditional": 0,
"needRedstone": 1
},
{"direction": [3, 4, 1], "block_name": "concrete", "particular_value": 6},
{"direction": [-123412133, 4, 1], "block_name": "concrete", "particular_value": 7}]
bdx = BdxConverter("./test02.bdx", "Charlie_Ping",block)

View File

@@ -1,137 +0,0 @@
# -*- coding: UTF-8 -*-
"""提供错误报告的基本操作及方法 顺便提供版本更新、安装库等功能"""
# 诸葛亮与八卦阵帮忙修改语法 日期:---2022年1月19日
# 统计致命三级错误0个警告二级错误1个语法一级错误72个
import os
def makeZip(sourceDir, outFilename, compression=8, exceptFile=None):
import zipfile
"""使用compression指定的算法打包目录为zip文件\n
默认算法为DEFLATED(8),可用算法如下:\n
STORED = 0\n
DEFLATED = 8\n
BZIP2 = 12\n
LZMA = 14\n
"""
zipf = zipfile.ZipFile(outFilename, 'w', compression)
pre_len = len(os.path.dirname(sourceDir))
for parent, dirnames, filenames in os.walk(sourceDir):
for filename in filenames:
if filename == exceptFile:
continue
print(filename)
pathfile = os.path.join(parent, filename)
arcname = pathfile[pre_len:].strip(os.path.sep) # 相对路径
zipf.write(pathfile, arcname)
zipf.close()
del zipf, pre_len
# 以上函数节选并修改自 正在攀登的小蜗牛 的博客https://blog.csdn.net/qq_21127151/article/details/107503942
class report:
"""发送报告以及相应的任务处理"""
def __init__(self, senderName: str = 'Unknown', senderContact: str = 'None', describetion: str = ''):
""":param senderName 发送者名称
:param senderContact 发送者联系方式
:param describetion 问题描述"""
self.senderName = senderName
self.senderContact = senderContact
self.describetion = describetion
if not self.senderName:
self.senderName = 'Unknown'
if not self.senderContact:
self.senderContact = 'None'
def emailReport(self):
"""使用E-mail方法发送当前的日志和临时文件等"""
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from nmcsup.log import log
log("发送错误报告")
import os
log("添加标题与正文")
msg = MIMEMultipart()
# 发送者与接收者显示名称
msg["From"] = Header(self.senderName, 'utf-8')
msg["To"] = Header("W-YI (QQ2647547478)", 'utf-8')
# 标题
msg["Subject"] = '音·创 - 来自 ' + self.senderName + ' 的错误报告'
# 正文
msg.attach(
MIMEText("来自" + self.senderName + "( " + self.senderContact + " )的错误描述:\n" + self.describetion,
'utf-8'))
log("添加完毕,正在生成压缩包...")
makeZip("./", "Temps&Logs.zip", exceptFile="Temps&Logs.zip")
attafile = MIMEText(str(open("Temps&Logs.zip", 'rb').read()), "base64", 'gb2312')
attafile["Content-Type"] = 'application/octet-stream'
attafile["Content-Disposition"] = 'attachmentfilename="BugReport_from_' + self.senderName + '.zip"'
msg.attach(attafile)
log("完毕,准备发送")
try:
smtp = smtplib.SMTP()
smtp.connect("smtp.163.com")
# smtp.login("RyounDevTeam@163.com","RyounDaiYi99")
# SIQQKQQYCZRVIDFJ是授权密码
smtp.login("RyounDevTeam@163.com", "SIQQKQQYCZRVIDFJ")
smtp.sendmail("RyounDevTeam@163.com", ["RyounDevTeam@163.com", ], msg.as_string())
log("错误汇报邮件已发送")
except smtplib.SMTPException as e:
log("错误汇报邮件发送失败:\n" + str(e))
log("清空内存和临时文件")
del msg, attafile
os.remove("./Temps&Logs.zip")
class version:
libraries = (
'mido', 'amulet', 'amulet-core', 'amulet-nbt', 'piano_transcription_inference', 'pypinyin',
'pyinstaller', 'py7zr','websockets', 'torch', 'requests'
)
"""当前所需库"""
version = ('0.2.0', 'Delta',)
"""当前版本"""
def __init__(self) -> None:
self.libraries = version.libraries
"""当前所需库"""
self.version = version.version
"""当前版本"""
def installLibraries(self,index:str = 'https://pypi.tuna.tsinghua.edu.cn/simple'):
"""安装全部开发用库"""
from sys import platform
import os
if platform == 'win32':
import shutil
try:
shutil.rmtree(os.getenv('APPDATA') + '\\Musicreater\\')
except FileNotFoundError:
pass
for i in self.libraries:
print("安装库:" + i)
os.system(f"python -m pip install {i} -i {index}")
elif platform == 'linux':
os.system("sudo apt-get install python3-pip")
os.system("sudo apt-get install python3-tk")
os.system("sudo apt-get install python3-tkinter")
for i in self.libraries:
print("安装库:" + i)
os.system(f"sudo python3 -m pip install {i} -i {index}")
def __call__(self):
'''直接安装库,顺便返回一下当前版本'''
self.installLibraries()
return self.version

View File

@@ -1,422 +0,0 @@
# -*- coding: utf-8 -*-
"""音·创 的函数操作和一些其他功能"""
# 诸葛亮与八卦阵帮忙修改语法 日期:---2022年1月19日
# 统计致命三级错误0个警告二级错误0个语法一级错误91个
from nmcsup.log import log
def delPart(Data, starter, ender, includeStart: bool = True, includend: bool = True):
"""删除序列从starter物件到ender物件之间的部分\n
includeStart与inclodend分别控制此函数是否包括starter和ender物件所在部分默认为真\n
starter与ender若为None则默认从首或尾开始"""
try:
if starter is None:
includeStart = True
starter = Data[0]
if ender is None:
includend = True
ender = Data[len(Data) - 1]
if includend:
if includeStart:
return Data[Data.index(starter):len(Data) - Data[len(Data)::-1].index(ender)]
else:
return Data[Data.index(starter) + 1:len(Data) - Data[len(Data)::-1].index(ender)]
else:
if includeStart:
return Data[Data.index(starter):len(Data) - Data[len(Data)::-1].index(ender) - 1]
else:
return Data[Data.index(starter) + 1:len(Data) - Data[len(Data)::-1].index(ender) - 1]
except:
return 0
def keepart(Data, starter, ender, includeStart: bool = True, includend: bool = True):
"""保留序列从starter物件到ender物件之间的部分\n
includeStart与inclodend分别控制此函数是否包括starter和ender物件所在部分默认为真\n
starter与ender若为None则默认从首或尾开始"""
try:
if starter is None:
includeStart = True
starter = Data[0]
if ender is None:
includend = True
ender = Data[len(Data) - 1]
if includend:
if includeStart:
return Data[Data.index(starter):Data.index(ender) + 1]
else:
return Data[Data.index(starter) + 1:Data.index(ender) + 1]
else:
if includeStart:
return Data[Data.index(starter):Data.index(ender)]
else:
return Data[Data.index(starter) + 1:Data.index(ender)]
except:
return 0
def lenFunction(fun) -> int:
"""取得函数指令部分长度,即忽略#开头的注释"""
try:
f = 0
for i in fun:
if i.replace(" ", '')[0] == '#':
f += 1
return len(fun) - f
except:
return -1
def funSplit(bigFile, maxCmdLen: int = 10000):
"""分割bigFile大的函数文件bigFile需要读入文件流\n
返回的部分,每行指令皆带有行尾换行符\\n\n
返回-1为大小低于maxCmdLen最长函数指令长度"""
bigFile = bigFile.readlines()
if lenFunction(bigFile) < maxCmdLen:
return -1
part = []
parts = []
h = 0
for i in bigFile:
if i.replace(" ", '')[0] == '#':
part.append(i + '\n')
else:
part.append(i + '\n')
h += 1
if h >= 10000:
parts.append(part)
part = []
h = 0
return parts
def makeFuncFiles(musicset, path='./'):
"""在指定目录下生成函数文件"""
from nmcsup.trans import Note2Cmd
commands = []
starts = []
log("=========================正在在此处生成文件:" + path)
maxlen = -1
for i in range(len(musicset['musics'])):
log('写入第' + str(i) + '个数据')
commands.append("scoreboard players add @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\"] " +
musicset['musics'][i]['set']['ScoreboardName'] + " 1\n")
commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
musicset['musics'][i]['set']['ScoreboardName'] + "=1..10}] ~~~ title @a" + musicset['mainset'][
'PlayerSelect'] + " title " + musicset['mainset']['MusicTitle'] + "\n")
commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
musicset['musics'][i]['set']['ScoreboardName'] + "=1..10}] ~~~ title @a" + musicset['mainset'][
'PlayerSelect'] + " subtitle 本函数乐曲由§b§l凌云§r§3函数音乐创建§r生成\n")
if len(musicset['musics'][i]['notes']) > maxlen:
maxlen = len(musicset['musics'][i]['notes'])
starts.append("scoreboard objectives add " + musicset['musics'][i]['set']['ScoreboardName'] + " dummy\n")
starts.append("summon armor_stand " + musicset['musics'][i]['set']['EntityName'] + '\n')
with open(path + musicset['mainset']['MusicTitle'] + '_Part' + str(i) + '.mcfunction', 'w',
encoding='UTF-8') as f:
f.writelines(Note2Cmd(musicset['musics'][i]['notes'], musicset['musics'][i]['set']['ScoreboardName'],
musicset['musics'][i]['set']['Instrument'], musicset['mainset']['PlayerSelect'],
True))
if musicset['mainset']['IsRepeat']:
log("增加重复语句")
for i in range(len(musicset['musics'])):
commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
musicset['musics'][i]['set']['ScoreboardName'] + "=" + str(
(maxlen + 2) * 10) + "}] ~~~ scoreboard players set @e[name=\"" + musicset['musics'][i]['set'][
'EntityName'] + "\"] " + musicset['musics'][i]['set']['ScoreboardName'] + " -1\n")
log("增加版权语句")
commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
starts.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
log("写入支持文件")
with open(path + musicset['mainset']['MusicTitle'] + '_Support.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(commands)
log("写入开始文件")
with open(path + 'Start_' + musicset['mainset']['MusicTitle'] + '.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(starts)
del commands, starts, maxlen
log("完成============================")
def makeFunDir(musicset, path='./'):
"""在指定目录下生成函数包文件夹"""
import os
import uuid
log("=============================生成函数包文件夹")
# note,packname="Ryoun",FileName="Music",EntityName_='music_support',ScoreboardName_='music_support',
# MusicTitle_='Noname',PlayerSelect_='',Repeat_=False,Instrument_='harp'
try:
os.makedirs(path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions")
log("已创建目录" + path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions")
except FileExistsError:
log("目录已有无需创建")
pass
# 判断文件皆存在
if not (os.path.exists(
path + musicset['mainset']['PackName'] + "Pack/world_behavior_packs.json") and os.path.exists(
path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/manifest.json")):
log("创建manifest.json以及world_behavior_packs.json")
behaviorUuid = uuid.uuid4()
with open(path + musicset['mainset']['PackName'] + "Pack/world_behavior_packs.json", "w") as f:
f.write("[\n {\"pack_id\": \"" + str(behaviorUuid) + "\",\n \"version\": [ 0, 0, 1 ]}\n]")
p = path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset']['PackName'] + \
"/manifest.json"
with open(p, "w") as f:
f.write("{\n \"format_version\": 1,\n \"header\": {\n \"description\": \"" + musicset['mainset'][
'PackName'] + " Pack : behavior pack\",\n \"version\": [ 0, 0, 1 ],\n \"name\": \"" +
musicset['mainset']['PackName'] + "Pack\",\n \"uuid\": \"" + str(
behaviorUuid) + "\"\n },\n \"modules\": [\n {\n \"description\": \"" + musicset['mainset'][
'PackName'] + " Pack : behavior pack\",\n \"type\": \"data\",\n \"version\":"
" [ 0, 0, 1 ],\n \"uuid\": \"" + str(
uuid.uuid4()) + "\"\n }\n ]\n}")
makeFuncFiles(musicset, path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions/")
log("完成============================")
def makeNewFuncFiles(musicset, path='./', isProsess:bool = False):
"""在指定目录下生成函数文件"""
from msctspt.transfer import newList_conversion_SinglePlayer
commands = []
starts = []
starts.__len__()
starts.append("scoreboard objectives add " + musicset['musics'][0]['set']['ScoreboardName'] + " dummy\n")
# starts.append("summon armor_stand " + musicset['musics'][0]['set']['EntityName'] + '\n')
# starts.append("scoreboard objectives setdisplay sidebar " + musicset['musics'][0]['set']['ScoreboardName'] + '\n')
# starts.append("scoreboard players set @e[type=armor_stand, name=\"" + musicset['musics'][0]['set']['EntityName'] +"\"] " + musicset['musics'][0]['set']['ScoreboardName'] + " 0" + '\n')
log("=========================正在在此处生成文件:" + path)
# commands.append("scoreboard players add @e[name=\"" + musicset['musics'][0]['set']['EntityName'] + "\"] " + musicset['musics'][0]['set']['ScoreboardName'] + " 1\n")
maxlen = -1
for i in range(len(musicset['musics'])):
log('写入第' + str(i) + '个数据')
if len(musicset['musics'][i]['notes']) > maxlen:
maxlen = len(musicset['musics'][i]['notes'])
with open(path + musicset['mainset']['MusicTitle'] + '_Part' + str(i) + '.mcfunction', 'w',
encoding='UTF-8') as f:
f.writelines(newList_conversion_SinglePlayer(musicset['musics'][i]['notes'],
musicset['musics'][i]['set']['ScoreboardName'],
musicset['musics'][i]['set']['Instrument']))
if musicset['mainset']['IsRepeat']:
log("增加重复语句")
for i in range(len(musicset['musics'])):
commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
musicset['musics'][i]['set']['ScoreboardName'] + "=" + str(
(maxlen + 2) * 10) + "}] ~~~ scoreboard players set @e[name=\"" + musicset['musics'][i]['set'][
'EntityName'] + "\"] " + musicset['musics'][i]['set']['ScoreboardName'] + " -1\n")
log("增加版权语句")
commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿bgArray诸葛亮与八卦阵\n")
starts.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿bgArray诸葛亮与八卦阵\n")
log("写入支持文件")
with open(path + musicset['mainset']['MusicTitle'] + '_Support.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(commands)
log("写入开始文件")
with open(path + 'Start_' + musicset['mainset']['MusicTitle'] + '.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(starts)
del commands, starts, maxlen
log("完成============================")
def makeNewFunDir(musicset, path='./'):
"""在指定目录下生成函数包文件夹"""
import os
import uuid
log("=============================生成函数包文件夹")
# note,packname="Ryoun",FileName="Music",EntityName_='music_support',ScoreboardName_='music_support',
# MusicTitle_='Noname',PlayerSelect_='',Repeat_=False,Instrument_='harp'
try:
os.makedirs(path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions")
log("已创建目录" + path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions")
except FileExistsError:
log("目录已有无需创建")
pass
# 判断文件皆存在
if not (os.path.exists(
path + musicset['mainset']['PackName'] + "Pack/world_behavior_packs.json") and os.path.exists(
path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/manifest.json")):
log("创建manifest.json以及world_behavior_packs.json")
behaviorUuid = uuid.uuid4()
with open(path + musicset['mainset']['PackName'] + "Pack/world_behavior_packs.json", "w") as f:
f.write("[\n {\"pack_id\": \"" + str(behaviorUuid) + "\",\n \"version\": [ 0, 0, 1 ]}\n]")
p = path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset']['PackName'] + \
"/manifest.json"
with open(p, "w") as f:
f.write("{\n \"format_version\": 1,\n \"header\": {\n \"description\": \"" + musicset['mainset'][
'PackName'] + " Pack : behavior pack\",\n \"version\": [ 0, 0, 1 ],\n \"name\": \"" +
musicset['mainset']['PackName'] + "Pack\",\n \"uuid\": \"" + str(
behaviorUuid) + "\"\n },\n \"modules\": [\n {\n \"description\": \"" + musicset['mainset'][
'PackName'] + " Pack : behavior pack\",\n \"type\": \"data\",\n \"version\":"
" [ 0, 0, 1 ],\n \"uuid\": \"" + str(
uuid.uuid4()) + "\"\n }\n ]\n}")
makeNewFuncFiles(musicset, path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions/")
log("完成============================")
def makeClassFuncFiles(musicset, path='./'):
"""在指定目录下生成函数文件"""
from msctspt.transfer import classList_conversion_SinglePlayer
commands = []
starts = []
starts.__len__()
starts.append("scoreboard objectives add " + musicset['musics'][0]['set']['ScoreboardName'] + " dummy\n")
starts.append("summon armor_stand " + musicset['musics'][0]['set']['EntityName'] + '\n')
starts.append("scoreboard objectives setdisplay sidebar " + musicset['musics'][0]['set']['ScoreboardName'] + '\n')
starts.append("scoreboard players set @e[type=armor_stand, name=\"" + musicset['musics'][0]['set']['EntityName'] +
"\"] " + musicset['musics'][0]['set']['ScoreboardName'] + " 0" + '\n')
log("=========================正在在此处生成文件:" + path)
commands.append("scoreboard players add @e[name=\"" + musicset['musics'][0]['set']['EntityName'] + "\"] " +
musicset['musics'][0]['set']['ScoreboardName'] + " 1\n")
maxlen = -1
for i in range(len(musicset['musics'])):
log('写入第' + str(i) + '个数据')
# commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
# musicset['musics'][i]['set']['ScoreboardName'] + "=1..10}] ~~~ title @a" + musicset['mainset']
# [
# 'PlayerSelect'] + " title " + musicset['mainset']['MusicTitle'] + "\n")
# commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
# musicset['musics'][i]['set']['ScoreboardName'] + "=1..10}] ~~~ title @a" + musicset['mainset']
# [
# 'PlayerSelect'] + " subtitle 本函数乐曲由§b§l凌云§r§3函数音乐创建§r生成\n")
if len(musicset['musics'][i]['notes']) > maxlen:
maxlen = len(musicset['musics'][i]['notes'])
with open(path + musicset['mainset']['MusicTitle'] + '_Part' + str(i) + '.mcfunction', 'w',
encoding='UTF-8') as f:
f.writelines(classList_conversion_SinglePlayer(musicset['musics'][i]['notes'],
musicset['musics'][i]['set']['ScoreboardName'],
musicset['mainset']['PlayerSelect'],
True))
if musicset['mainset']['IsRepeat']:
log("增加重复语句")
for i in range(len(musicset['musics'])):
commands.append("execute @e[name=\"" + musicset['musics'][i]['set']['EntityName'] + "\",scores={" +
musicset['musics'][i]['set']['ScoreboardName'] + "=" + str(
(maxlen + 2) * 10) + "}] ~~~ scoreboard players set @e[name=\"" + musicset['musics'][i]['set'][
'EntityName'] + "\"] " + musicset['musics'][i]['set']['ScoreboardName'] + " -1\n")
log("增加版权语句")
commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿bgArray诸葛亮与八卦阵\n")
starts.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿bgArray诸葛亮与八卦阵\n")
log("写入支持文件")
with open(path + musicset['mainset']['MusicTitle'] + '_Support.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(commands)
log("写入开始文件")
with open(path + 'Start_' + musicset['mainset']['MusicTitle'] + '.mcfunction', 'w', encoding='UTF-8') as f:
f.writelines(starts)
del commands, starts, maxlen
log("完成============================")
def makeClassFunDir(musicset, path='./'):
"""在指定目录下生成函数包文件夹"""
import os
import uuid
log("=============================生成函数包文件夹")
# note,packname="Ryoun",FileName="Music",EntityName_='music_support',ScoreboardName_='music_support',
# MusicTitle_='Noname',PlayerSelect_='',Repeat_=False,Instrument_='harp'
try:
os.makedirs(path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions")
log("已创建目录" + path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions")
except FileExistsError:
log("目录已有无需创建")
pass
# 判断文件皆存在
if not (os.path.exists(
path + musicset['mainset']['PackName'] + "Pack/world_behavior_packs.json") and os.path.exists(
path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/manifest.json")):
log("创建manifest.json以及world_behavior_packs.json")
behaviorUuid = uuid.uuid4()
with open(path + musicset['mainset']['PackName'] + "Pack/world_behavior_packs.json", "w") as f:
f.write("[\n {\"pack_id\": \"" + str(behaviorUuid) + "\",\n \"version\": [ 0, 0, 1 ]}\n]")
p = path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset']['PackName'] + \
"/manifest.json"
with open(p, "w") as f:
f.write("{\n \"format_version\": 1,\n \"header\": {\n \"description\": \"" + musicset['mainset'][
'PackName'] + " Pack : behavior pack\",\n \"version\": [ 0, 0, 1 ],\n \"name\": \"" +
musicset['mainset']['PackName'] + "Pack\",\n \"uuid\": \"" + str(
behaviorUuid) + "\"\n },\n \"modules\": [\n {\n \"description\": \"" + musicset['mainset'][
'PackName'] + " Pack : behavior pack\",\n \"type\": \"data\",\n \"version\":"
" [ 0, 0, 1 ],\n \"uuid\": \"" + str(
uuid.uuid4()) + "\"\n }\n ]\n}")
makeClassFuncFiles(musicset, path + musicset['mainset']['PackName'] + "Pack/behavior_packs/" + musicset['mainset'][
'PackName'] + "/functions/")
log("完成============================")
"""
这里是往事,用于记载一些用不到的功能
#存在于 Musicreater.py 播放(试听)音乐
def PlayNote(Notes, t=480): # Notes是音符列表t是一拍占有的毫秒数
tkinter.messagebox.showinfo(title='提示!', message="播放发音不一定标准\n说不定还会坏音响/(ㄒoㄒ)/~~qwq\n请注意。")
import winsound
import time
from nmcsup.trans import mcnote2freq
Notes = mcnote2freq(Notes)
for frequency, duration in Notes:
log("播放:"+str([int(frequency), int(duration*t)]))
if int(frequency) != 0:
winsound.Beep(int(frequency), int(duration*t))
elif int(frequency) == 0:
time.sleep(duration*t/1000)
#同上,执行播放命令
def PlayOne():
log("试听")
tkinter.messagebox.showwarning(title="警告⚠", message="试听音质可能引起您的不适,更可能引起您的扬声器的不适,请酌情播放。")
global NowMusic
PlayNote(dataset[0]['musics'][NowMusic]['notes'])
#同上,是早期 MinecraftMusicFunctionMaker.py (函数音创)的代码转移至音·创时的注解
n2c(dataset[0]['musics'][i]['notes'],EntityName=dataset[0]['musics'][i]['set']['EntityName'],ScoreboardName=dataset[0]['
musics'][i]['set']['ScoreboardName'],PlayerSelect=dataset[0]['mainset']['PlayerSelect'],Instrument=dataset[0]['musics']
i]['set']["Instrument"])
"""

View File

@@ -1,27 +0,0 @@
import threading
class NewThread(threading.Thread):
'''新建一个进程来运行函数,函数运行完毕后可以使用.getResult方法获取其返回值'''
def __init__(self, func, args=()):
super(NewThread, self).__init__()
self.func = func
self.args = args
self.result = None
def run(self):
self.result = self.func(*self.args)
def getResult(self):
threading.Thread.join(self) # 等待线程执行完毕
return self.result
#
# ————————————————
# 版权声明上面的类NewThread修改自CSDN博主「星火燎愿」的原创文章中的内容遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。
# 原文链接https://blog.csdn.net/xpt211314/article/details/109543014
# ————————————————
#

View File

@@ -1,569 +0,0 @@
"""音·创 的转换工具库"""
# 诸葛亮与八卦阵帮忙修改语法 日期:---2022年1月19日
# 统计致命三级错误0个警告二级错误4个--未解决1个语法一级错误302个
# 可序列化对象,即可迭代对象
from typing import Iterable
import amulet
from amulet.api.block import Block
from amulet.utils.world_utils import block_coords_to_chunk_coords as bc2cc
from amulet_nbt import TAG_String as ts
from nmcsup.log import log
def hans2pinyin(hans, style=3):
"""将汉字字符串转化为拼音字符串"""
from pypinyin import lazy_pinyin
result = lazy_pinyin(hans=hans, style=style)
final = ''
for i in result:
final += i
return final
def classList_conversion_SinglePlayer(List: list, ScoreboardName: str, playerSelection: str = '',
isProsess: bool = False) -> list:
from bgArrayLib.compute import round_up
from bgArrayLib.pitchStrConstant import pitch
from bgArrayLib.instrumentConstant import instrument_list
commands = []
length = len(List)
j = 1
print(List)
for k in range(len(List)):
i = List[k][0]
print(i)
print(type(i))
try:
commands.append(
f"execute @a{playerSelection} ~ ~ ~ execute @s[scores={{{ScoreboardName}="
f"{str(round_up(i.time_position)).replace('.0', '')}}}] ~ ~{127 - i.velocity} "
f"~ playsound note.{instrument_list.get(str(i.instrument))} @s ~ ~ ~ "
f"1000 {pitch.get(str(i.pitch))} 1000\n")
if isProsess:
commands.append(
f"execute @a{playerSelection} ~ ~ ~ execute @s[scores={{{ScoreboardName}="
f"{str(round_up(i.time_position)).replace('.0', '')}}}] ~ ~ ~ "
f"title @s actionbar §e▶ 播放中: §a{j}/{length} || {int(j / length * 1000) / 10}\n")
j += 1
except Exception:
pass
# a += List[i][1]
# commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
print(commands)
return commands
def newList_conversion_SinglePlayer(List: list, ScoreboardName: str, playerSelection: str = '',
isProsess: bool = False) -> list:
from bgArrayLib.compute import round_up
commands = []
length = len(List)
j = 1
print(List)
for k in range(len(List)):
i = List[k][0]
print(i)
print(type(i))
try:
if i.instrument > 119:
pass
else:
commands.append(
f"execute @a{playerSelection} ~ ~ ~ execute @s[scores={{{ScoreboardName}="
f"{str(round_up(i.time_position)).replace('.0', '')}}}] ~ ~{127 - i.velocity} "
f"~ playsound {i.instrument}{i.CD}.{i.pitch} @s ~ ~ ~ 1000 1.0 1000\n")
if isProsess:
commands.append(
f"execute @a{playerSelection} ~ ~ ~ execute @s[scores={{{ScoreboardName}="
f"{str(round_up(i.time_position)).replace('.0', '')}}}] ~ ~ ~ "
f"title @s actionbar §e▶ 播放中: §a{j}/{length} || {int(j / length * 1000) / 10}\n")
j += 1
except:
pass
# a += List[i][1]
# commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
print(commands)
return commands
def classList_conversion(List: list, ScoreboardName: str, isProsess: bool = False) -> list:
from bgArrayLib.compute import round_up
commands = []
length = len(List)
j = 1
print(List)
for k in range(len(List)):
i = List[k][0]
print(i)
print(type(i))
try:
if i.instrument > 119:
pass
else:
commands.append("execute @e[scores={" +
ScoreboardName + "=" + str(round_up(i.time_position)).replace(".0", "") + "}] ~ ~" +
str(127 - i.velocity) +
" ~ playsound " +
str(i.instrument) +
str(i.CD) + "." +
str(i.pitch)
+ " @a ~ ~ ~ 1000 1.0 1000\n")
if isProsess:
commands.append("execute @a"" ~ ~ ~ execute @s[scores={" + ScoreboardName + "=" +
str(round_up(i.time_position)).replace(".0", "") +
"}] ~ ~ ~ title @s actionbar §e▶ 播放中: §a" +
str(j) + "/" + str(length) + " || " + str(int(j / length * 1000) / 10) + "\n")
j += 1
except AttributeError:
pass
# a += List[i][1]
# commands.append("\n\n# 凌云我的世界开发团队 x 凌云软件开发团队 : W-YI金羿\n")
print(commands)
return commands
def formCmdBlock(direction: Iterable, command: str, particularValue: int, impluse: int = 0, condition: bool = False,
needRedstone: bool = True, tickDelay: int = 0, customName: str = '', lastOutput: str = '',
executeOnFirstTick: bool = False, trackOutput: bool = True):
"""
使用指定项目返回指定的指令方块格式字典
:param direction: `list[x: int, y: int, z: int]`
方块位置
:param command: `str`
指令
:param particularValue:
方块特殊值,即朝向
:0 下 无条件
:1 上 无条件
:2 z轴负方向 无条件
:3 z轴正方向 无条件
:4 x轴负方向 无条件
:5 x轴正方向 无条件
:6 下 无条件
:7 下 无条件
:8 下 有条件
:9 上 有条件
:10 z轴负方向 有条件
:11 z轴正方向 有条件
:12 x轴负方向 有条件
:13 x轴正方向 有条件
:14 下 有条件
:14 下 有条件
注意此处特殊值中的条件会被下面condition参数覆写
:param impluse: `int 0|1|2`
方块类型
0脉冲 1循环 2连锁
:param condition: `bool`
是否有条件
:param needRedstone: `bool`
是否需要红石
:param tickDelay: `int`
执行延时
:param customName: `str`
悬浮字
:param lastOutput: `str`
上次输出字符串,注意此处需要留空
:param executeOnFirstTick: `bool`
执行第一个已选项(循环指令方块是否激活后立即执行若为False则从激活时起延迟后第一次执行)
:param trackOutput: `bool`
是否输出
:return: 指令方块字典结构,如下
"""
'''
:param block: {
"direction": [x: int, y: int, z: int] #方块位置
"block_name": str, #方块名称无需指定默认为command_block
"particular_value": int, #方块特殊值
"impluse": int, #方块类型0脉冲 1循环 2连锁 unsigned_int32
"command": str, #指令
"customName": str, #悬浮字
"lastOutput": str, #上次输出
"tickdelay": int, #方块延时 int32
"executeOnFirstTick": int, #执行第一个选项 1 bytes
"trackOutput": int, #是否输出 1 bytes
"conditional": int, #是否有条件 1 bytes
"needRedstone": int #是否需要红石 1 bytes
}
'''
return {"direction": direction,
"block_name": "command_block",
"particular_value": particularValue,
"impluse": impluse,
"command": command,
"customName": customName,
"lastOutput": lastOutput,
"tickdelay": tickDelay,
"executeOnFirstTick": executeOnFirstTick,
"trackOutput": trackOutput,
"conditional": condition,
"needRedstone": needRedstone
}
def note2bdx(filePath: str, dire: list, Notes: list, ScoreboardName: str, Instrument: str,
PlayerSelect: str = '', isProsess: bool = False, height: int = 200):
"""使用方法同Note2Cmd
:param 参数说明:
filePath: 生成.bdx文件的位置
dire: 指令方块在地图中生成的起始位置(相对位置)
Notes: 以 list[ list[ float我的世界playsound指令音调 , float延续时常单位s ] ] 格式存储的音符列表
例如Musicreater.py的(dataset[0]['musics'][NowMusic]['notes'])
ScoreboardName: 用于执行的计分板名称
Instrument: 播放的乐器
PlayerSelect: 执行的玩家选择器
isProsess: 是否显示进度条(会很卡)
height: 生成结构的最高高度
:return 返回一个BdxConverter类同时在指定位置生成.bdx文件"""
from nmcsup.trans import Note2Cmd
from msctspt.bdxOpera_CP import BdxConverter
cmd = Note2Cmd(Notes, ScoreboardName, Instrument, PlayerSelect, isProsess)
cdl = []
for i in cmd:
if '#' in i:
if (i[:i.index('#')].replace(' ', '') != '\n') and (i[:i.index('#')].replace(' ', '') != ''):
cdl.append(i[:i.index('#')])
else:
cdl.append(i)
i = 0
down = False
blocks = [formCmdBlock(dire, cdl.pop(0), 1, 1)]
dire[1] += 1
for j in cdl:
if dire[1] + i > height:
dire[0] += 1
i = 0
down = not down
if dire[1] + i == height:
blocks.append(formCmdBlock([dire[0], dire[1] + i, dire[2]], j, 5, 2, False, False))
else:
if down:
blocks.append(formCmdBlock([dire[0], dire[1] + i, dire[2]], j, 0, 2, False, False))
else:
blocks.append(formCmdBlock([dire[0], dire[1] + i, dire[2]], j, 1, 2, False, False))
i += 1
del i, cdl, down, cmd
return BdxConverter(filePath, 'Build by RyounMusicreater', blocks)
def music2cmdBlocks(direction: Iterable, music: dict, isProsess: bool = False, height: int = 200,
isSquare: bool = False):
"""使用方法同Note2Cmd
:param 参数说明:
filePath: 生成.bdx文件的位置
dire: 指令方块在地图中生成的起始位置(相对位置)
music: 详见 Musicreater.py - dataset[0]
isProsess: 是否显示进度条(会很卡)
height: 生成结构的最高高度
isSquare: 生成的结构是否需要遵循生成正方形原则
:return 返回一个列表,其中包含了音乐生成的所有的指令方块数据"""
from msctspt.threadOpera import NewThread
allblocks = []
'''需要放置的方块'''
baseDire = direction
direction = list(direction)
def trackDealing(direction,track):
blocks = []
cmdList = classList_conversion_SinglePlayer(track['notes'], track['set']['ScoreboardName'],
music['mainset']['PlayerSelect'], isProsess)
if len(cmdList) == 0:
return
elif cmdList is []:
return
dire = direction
down = False
'''当前是否为向下的阶段?'''
# 开头的指令方块
blocks.append(formCmdBlock(dire,
f"scoreboard players add @a{music['mainset']['PlayerSelect']} "
f"{track['set']['ScoreboardName']} 1",
1, 1))
dire[1] += 1
blocks.append(formCmdBlock(dire, cmdList.pop(0), 2, needRedstone=False))
dire[1] += 1
# :0 下 无条件
# :1 上 无条件
# :2 z轴负方向 无条件
# :3 z轴正方向 无条件
# :4 x轴负方向 无条件
# :5 x轴正方向 无条件
for cmd in cmdList:
blocks.append(formCmdBlock(dire, cmd, 5 if (down is False and dire[1] == height + direction[1]) or (
down and dire[1] == direction + 1) else 0 if down else 1, 2, needRedstone=False))
if down:
if dire[1] > direction[1] + 1:
dire[1] -= 1
else:
if dire[1] < height + direction[1]:
dire[1] += 1
if (down is False and dire[1] == height + direction[1]) or (down and dire[1] == direction + 1):
down = not down
dire[0] += 1
return blocks
threads = []
for track in music['musics']:
threads.append(NewThread(trackDealing,(direction,track)))
threads[threads.__len__()-1].start()
direction[2] += 2
for th in threads:
allblocks += th.getResult()
return allblocks
def music2BDX(filePath: str, direction: Iterable, music: dict, isProsess: bool = False, height: int = 200,
isSquare: bool = False):
"""使用方法同Note2Cmd
:param 参数说明:
filePath: 生成.bdx文件的位置
dire: 指令方块在地图中生成的起始位置(相对位置)
music: 详见 Musicreater.py - dataset[0]
isProsess: 是否显示进度条(会很卡)
height: 生成结构的最高高度
isSquare: 生成的结构是否需要遵循生成正方形原则
:return 返回一个BdxConverter类同时在指定位置生成.bdx文件"""
from msctspt.bdxOpera_CP import BdxConverter
return BdxConverter(filePath, 'Build by Ryoun Musicreater', music2cmdBlocks(direction,music,isProsess,height,isSquare)
)
def note2webs(Notes: list, Instrument: str, speed: float = 5.0, PlayerSelect: str = '', isProsess: bool = False):
"""传入音符在oaclhost:8080上建立websocket服务器以供我的世界connect/wssever指令连接
:param 参数说明:
Notes: 以 list[ list[ float我的世界playsound指令音调 , float延续时常单位s ] ] 格式存储的音符列表
例如Musicreater.py的(dataset[0]['musics'][NowMusic]['notes'])
Instrument: 播放的乐器
speed: 用于控制播放速度,数值越大,播放速度越快,相当于把一秒变为几拍
PlayerSelect: 执行的玩家选择器
isProsess: 是否显示进度条
:return None"""
import time
import fcwslib
# import asyncio
from nmcsup.log import log
from nmcsup.vers import VER
async def run_server(websocket): # , path
log('服务器连接创建')
await fcwslib.tellraw(websocket, '已连接服务器——音·创' + VER[1] + VER[0] + ' 作者:金羿(W-YI)')
length = len(Notes)
j = 1
for i in range(len(Notes)):
await fcwslib.send_command(websocket,
f'execute @a{PlayerSelect} ~ ~ ~ playsound {Instrument} @s ~ ~ ~ 1000 '
f'{Notes[i][0]} 1000')
if isProsess:
await fcwslib.send_command(websocket,
'execute @a' + PlayerSelect + ' ~ ~ ~ title @s actionbar §e▶ 播放中: §a' +
str(
j) + '/' + str(length) + ' || ' + str(int(j / length * 1000) / 10))
j += 1
time.sleep(Notes[i][1] / speed)
fcwslib.run_server(run_server)
def note2RSworld(world: str, startpos: list, notes: list, instrument: str, speed: float = 2.5,
posadder: Iterable = (1, 0, 0), baseblock: str = 'stone'): # -> bool
"""传入音符,生成以音符盒存储的红石音乐
:param 参数说明:
world: 地图文件的路径
startpos: list[int,int,int] 开始生成的坐标
notes: list[list[float,float]] 以 list[ list[ float我的世界playsound指令音调 , float延续时常单位s ] ]
格式存储的音符列表 例如Musicreater.py的dataset[0]['musics'][NowMusic]['notes']
instrument: 播放的乐器
speed: 一拍占多少个中继器延迟(红石刻/rt)
posadder: list[int,int,int] 坐标增加规律,即红石的延长时按照此增加规律增加坐标
baseblock: 在中继器下垫着啥方块呢~
:return 是否生成成功
"""
from msctspt.values import height2note, instuments
def formNoteBlock(note: int, instrument1: str = 'note.harp', powered: bool = False):
"""生成音符盒方块
:param powered:
:param instrument1:
:param note: 0~24
:return Block()"""
if powered:
powered = 'true'
else:
powered = 'false'
return Block('universal_minecraft', 'notebooks',
{"instrument": ts(instrument1.replace("note.", '')), 'note': ts(str(note)),
'powered': ts(powered)})
def formRepeater(delay: int, facing: str, locked: bool = False, powered: bool = False):
"""生成中继器方块
:param powered:
:param locked:
:param facing:
:param delay: 1~4
:return Block()"""
if powered:
powered = 'true'
else:
powered = 'false'
if locked:
locked = 'true'
else:
locked = 'false'
return Block('universal_minecraft', 'repeater',
{"delay": ts(str(delay)), 'facing': ts(facing), 'locked': ts(locked), 'powered': ts(powered)})
level = amulet.load_level(world)
def setblock(block: Block, pos: list):
"""pos : list[int,int,int]"""
cx, cz = bc2cc(pos[0], pos[2])
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
offset_x, offset_z = pos[0] - 16 * cx, pos[2] - 16 * cz
chunk.blocks[offset_x, pos[1], offset_z] = level.block_palette.get_add_block(block)
chunk.changed = True
# 1拍 x 2.5 rt
def placeNoteBlock():
for i in notes:
error = True
try:
setblock(formNoteBlock(height2note[i[0]], instrument), [startpos[0], startpos[1] + 1, startpos[2]])
setblock(Block("universal_minecraft", instuments[i[0]][1]), startpos)
error = False
except ValueError:
log("无法放置音符:" + str(i) + '' + str(startpos))
setblock(Block("universal_minecraft", baseblock), startpos)
setblock(Block("universal_minecraft", baseblock), [startpos[0], startpos[1] + 1, startpos[2]])
finally:
if error is True:
log("无法放置音符:" + str(i) + '' + str(startpos))
setblock(Block("universal_minecraft", baseblock), startpos)
setblock(Block("universal_minecraft", baseblock), [startpos[0], startpos[1] + 1, startpos[2]])
delay = int(i[1] * speed + 0.5)
if delay <= 4:
startpos[0] += 1
setblock(formRepeater(delay, 'west'), [startpos[0], startpos[1] + 1, startpos[2]])
setblock(Block("universal_minecraft", baseblock), startpos)
else:
for j in range(int(delay / 4)):
startpos[0] += 1
setblock(formRepeater(4, 'west'), [startpos[0], startpos[1] + 1, startpos[2]])
setblock(Block("universal_minecraft", baseblock), startpos)
if delay % 4 != 0:
startpos[0] += 1
setblock(formRepeater(delay % 4, 'west'), [startpos[0], startpos[1] + 1, startpos[2]])
setblock(Block("universal_minecraft", baseblock), startpos)
startpos[0] += posadder[0]
startpos[1] += posadder[1]
startpos[2] += posadder[2]
# e = True
try:
placeNoteBlock()
# e = False
except: # ValueError
log("无法放置方块了,可能是因为区块未加载叭")
# finally:
# if e:
# log("无法放置方块了,可能是因为区块未加载叭")
level.save()
level.close()
class ryStruct:
def __init__(self, world: str) -> None:
self.RyStruct = dict()
self._world = world
self._level = amulet.load_level(world)
def reloadLevel(self):
# e = True
try:
self._level = amulet.load_level(self.world)
# e = False
except: # ValueError
log("无法重载地图")
# finally:
# if e:
# log("无法重载地图")
def closeLevel(self):
# e = True
try:
self._level.close()
# e = False
except: # ValueError
log("无法关闭地图")
# finally:
# if e:
# log("无法重载地图")
def world2Rys(self, startp: list, endp: list, includeAir: bool = False):
"""将世界转换为RyStruct字典注意此函数运行成功后将关闭地图若要打开需要运行 reloadLevel
:param startp: [x,y,z] 转化的起始坐标
:param endp : [x,y,z] 转换的终止坐标,注意,终止坐标需要大于起始坐标,且最终结果包含终止坐标
:param includeAir : bool = False 是否包含空气,即空气是否在生成之时覆盖地图内容
:return dict RyStruct """
level = self._level
for x in range(startp[0], endp[0] + 1):
for y in range(startp[1], endp[1] + 1):
for z in range(startp[2], endp[2] + 1):
RyStructBlock = dict()
cx, cz = bc2cc(x, z)
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
universal_block = chunk.block_palette[chunk.blocks[x - 16 * cx, y, z - 16 * cz]]
if universal_block == Block("universal_minecraft", "air") and includeAir:
continue
universal_block_entity = chunk.block_entities.get((x, y, z), None)
RyStructBlock["block"] = str(universal_block)
RyStructBlock["blockEntity"] = str(universal_block_entity)
log("载入方块数据" + str(RyStructBlock))
self.RyStruct[(x, y, z)] = RyStructBlock
level.close()
return self.RyStruct
"""
RyStruct = {
(0,0,0) = {
"block": str 完整的方块结构
"blockEntity": str | 'None'
}
}
"""

Some files were not shown because too many files have changed in this diff Show More