Compare commits

..

9 Commits

Author SHA1 Message Date
Eilles a9d04606f9 Update README.md 2023-04-02 00:51:21 +08:00
Eilles 3113eb53fa Update README.md 2023-04-02 00:35:17 +08:00
Eilles d13923f053 Update README.md 2023-03-12 13:09:24 +08:00
bgArray 889f59dd1c Delete .gitattributes 2023-01-26 10:07:52 +08:00
bgArray 38518337d3 Create .gitattributes 2023-01-26 10:06:45 +08:00
Eilles 2b096f6c48 Update README.md 2022-07-31 18:52:46 +08:00
Eilles 01a7cfe6ac Created NOTICE 2022-02-02 21:05:50 +08:00
Eilles ddad8eaa5e Delete Musicreater.py 2021-11-27 21:11:21 +08:00
Eilles d5c4e5c874 先传个主题看看 2021-11-21 01:21:41 +08:00
64 changed files with 24 additions and 14026 deletions
-3
View File
@@ -1,3 +0,0 @@
*.yaml linguist-language=Python
*.xml linguist-language=Python
*.md linguist-language=Python
-175
View File
@@ -1,175 +0,0 @@
# 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
View File
@@ -1 +0,0 @@
3.10
-123
View File
@@ -1,123 +0,0 @@
# 汉钰律许可协议,第一版
**总第一版 第二次修订 · 二〇二四年七月七日编 二〇二五年四月二十六日修订**
## 一、重要须知
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) 〔年份〕 〔著作权人〕〕
〔该作品〕根据 汉钰律许可协议,第一版(“本协议”)授权。
任何人皆可从以下地址获得本协议副本:〔本协议副本所在地址〕。
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
详细的准许和限制条款请见原协议文本。
```
-144
View File
@@ -1,144 +0,0 @@
# -*- coding: utf-8 -*-
"""一个简单的我的世界音频转换库
音·创 (Musicreater)
是一款免费开源的《我的世界》数字音频支持库。
Musicreater(音·创)
A free open source library used for dealing with **Minecraft** digital musics.
版权所有 © 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
__version__ = "2.4.2.3"
__vername__ = "音符附加信息升级"
__author__ = (
("金羿", "Eilles"),
("诸葛亮与八卦阵", "bgArray"),
("鱼旧梦", "ElapsingDreams"),
("偷吃不是Touch", "Touch"),
)
__all__ = [
# 主要类
"MusicSequence",
"MidiConvert",
# 附加类
# "SingleNote",
"MineNote",
"MineCommand",
"SingleNoteBox",
"ProgressBarStyle",
# "TimeStamp", 未来功能
# 字典键
"MIDI_PROGRAM",
"MIDI_VOLUME",
"MIDI_PAN",
# 默认值
"MIDI_DEFAULT_PROGRAM_VALUE",
"MIDI_DEFAULT_VOLUME_VALUE",
"DEFAULT_PROGRESSBAR_STYLE",
# Midi 自己的对照表
"MIDI_PITCH_NAME_TABLE",
"MIDI_PITCHED_NOTE_NAME_GROUP",
"MIDI_PITCHED_NOTE_NAME_TABLE",
"MIDI_PERCUSSION_NOTE_NAME_TABLE",
# Minecraft 自己的对照表
"MC_PERCUSSION_INSTRUMENT_LIST",
"MC_PITCHED_INSTRUMENT_LIST",
"MC_INSTRUMENT_BLOCKS_TABLE",
"MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE",
"MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE",
# Midi 与 游戏 的对照表
"MM_INSTRUMENT_RANGE_TABLE",
"MM_INSTRUMENT_DEVIATION_TABLE",
"MM_CLASSIC_PITCHED_INSTRUMENT_TABLE",
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
"MM_DISLINK_PITCHED_INSTRUMENT_TABLE",
"MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE",
"MM_NBS_PITCHED_INSTRUMENT_TABLE",
"MM_NBS_PERCUSSION_INSTRUMENT_TABLE",
# 操作性函数
"velocity_2_distance_natural",
"velocity_2_distance_straight",
"panning_2_rotation_linear",
"panning_2_rotation_trigonometric",
# 工具函数
"load_decode_musicsequence_metainfo",
"load_decode_msq_flush_release",
"load_decode_fsq_flush_release",
"guess_deviation",
"mctick2timestr",
"midi_inst_to_mc_sound",
]
from .main import MusicSequence, MidiConvert
from .subclass import (
MineNote,
MineCommand,
SingleNoteBox,
ProgressBarStyle,
mctick2timestr,
DEFAULT_PROGRESSBAR_STYLE,
)
from .utils import (
# 兼容性函数
load_decode_musicsequence_metainfo,
load_decode_msq_flush_release,
load_decode_fsq_flush_release,
# 工具函数
guess_deviation,
midi_inst_to_mc_sound,
# 处理用函数
velocity_2_distance_natural,
velocity_2_distance_straight,
panning_2_rotation_linear,
panning_2_rotation_trigonometric,
)
from .constants import (
# 字典键
MIDI_PROGRAM,
MIDI_PAN,
MIDI_VOLUME,
# 默认值
MIDI_DEFAULT_PROGRAM_VALUE,
MIDI_DEFAULT_VOLUME_VALUE,
# MIDI 表
MIDI_PITCH_NAME_TABLE,
MIDI_PITCHED_NOTE_NAME_GROUP,
MIDI_PITCHED_NOTE_NAME_TABLE,
MIDI_PERCUSSION_NOTE_NAME_TABLE,
# 我的世界 表
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE,
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
MC_INSTRUMENT_BLOCKS_TABLE,
MC_PERCUSSION_INSTRUMENT_LIST,
MC_PITCHED_INSTRUMENT_LIST,
# MIDI 到 我的世界 表
MM_INSTRUMENT_RANGE_TABLE,
MM_INSTRUMENT_DEVIATION_TABLE,
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
MM_NBS_PITCHED_INSTRUMENT_TABLE,
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
)
File diff suppressed because it is too large Load Diff
-421
View File
@@ -1,421 +0,0 @@
# -*- coding: utf-8 -*-
"""
存储音·创新数据存储类
"""
# WARNING 本文件中使用之功能尚未启用
"""
版权所有 © 2025 金羿
Copyright © 2025 Eilles
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf
from dataclasses import dataclass
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence, Callable
import bisect
from .types import FittingFunctionType
from .constants import MC_PITCHED_INSTRUMENT_LIST
class ArgumentCurve:
base_line: float = 0
"""基线/默认值"""
default_curve: Callable[[float], float]
"""默认曲线"""
defined_curves: Dict[float, "ArgumentCurve"] = {}
"""调整后的曲线集合"""
left_border: float = 0
"""定义域左边界"""
right_border: float = inf
"""定义域右边界"""
def __init__(self, baseline: float = 0, default_function: Callable[[float], float] = lambda x: 0, function_set: Dict = {}) -> None:
pass
def __call__(self, *args: Any, **kwds: Any) -> Any:
pass
class SoundAtmos:
sound_distance: float
"""声源距离 方块"""
sound_azimuth: Tuple[float, float]
"""声源方位 角度"""
def __init__(
self,
distance: Optional[float] = None,
azimuth: Optional[Tuple[float, float]] = None,
) -> None:
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]:
"""声像位移"""
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_tick: int
"""开始之时 命令刻"""
duration: int
"""音符持续时间 命令刻"""
high_precision_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
是否作为打击乐器
distance: float
发声源距离玩家的距离(半径 `r`)
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
azimuth: tuple[float, float]
声源方位
注:此参数为tuple,包含两个元素,分别表示:
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
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_tick: int = start_time
"""开始之时 命令刻"""
self.duration: int = last_time
"""音符持续时间 命令刻"""
self.high_precision_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:
print(
"[Error] 单音符解析错误,字节码`{}`{}启用高精度时间偏移\n".format(
code_buffer, "" if is_high_time_precision else ""
)
)
raise
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_tick)
<< 17
)
+ self.duration
).to_bytes(6, "big")
# + self.track_no.to_bytes(1, "big")
+ (
self.high_precision_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 TypeError("参数类型错误;键:`{}` 值:`{}`".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_tick,
self.duration,
self.high_precision_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_tick,
self.duration,
self.high_precision_time,
)
def __dict__(self):
return {
"Pitch": self.note_pitch,
"Velocity": self.velocity,
"StartTick": self.start_tick,
"Duration": self.duration,
"TimeOffset": self.high_precision_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_tick == other.start_tick:
return self.high_precision_time < other.high_precision_time
else:
return self.start_tick < other.start_tick
def __gt__(self, other) -> bool:
"""比较自己是否在开始时间上晚于另一个音符"""
if self.start_tick == other.start_tick:
return self.high_precision_time > other.high_precision_time
else:
return self.start_tick > other.start_tick
class SingleTrack(list):
"""存储单个轨道的类"""
track_name: str
"""轨道之名称"""
track_instrument: str
"""乐器ID"""
track_volume: float
"""该音轨的音量"""
is_high_time_precision: bool
"""该音轨是否使用高精度时间"""
is_percussive: bool
"""该音轨是否标记为打击乐器轨道"""
sound_position: SoundAtmos
"""声像方位"""
argument_curves: Dict[str, FittingFunctionType]
"""参数曲线"""
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 {}
super().__init__(*args)
@property
def note_amount(self) -> int:
"""音符数"""
return len(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 TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str, default: Any = None) -> Any:
"""获取附加信息"""
return self.extra_info.get(key, default)
class SingleMusic(object):
"""存储单个曲子的类"""
-162
View File
@@ -1,162 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放一些报错类型
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
class MSCTBaseException(Exception):
"""音·创 的所有错误均继承于此"""
def __init__(self, *args):
"""音·创 的所有错误均继承于此"""
super().__init__("音·创", *args)
def meow(
self,
):
for i in self.args:
print(i + "喵!")
def crash_it(self):
raise self
class MidiFormatException(MSCTBaseException):
"""音·创 的所有MIDI格式错误均继承于此"""
def __init__(self, *args):
"""音·创 的所有MIDI格式错误均继承于此"""
super().__init__("MIDI 格式错误", *args)
class MidiDestroyedError(MSCTBaseException):
"""Midi文件损坏"""
def __init__(self, *args):
"""Midi文件损坏"""
super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args)
# class MidiUnboundError(MSCTBaseException):
# """未定义Midi对象(无用)"""
# def __init__(self, *args):
# """未绑定Midi对象"""
# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args)
# 此错误在本版本内已经不再使用
class CommandFormatError(MSCTBaseException, RuntimeError):
"""指令格式与目标格式不匹配而引起的错误"""
def __init__(self, *args):
"""指令格式与目标格式不匹配而引起的错误"""
super().__init__("指令格式不匹配", *args)
# class CrossNoteError(MidiFormatException):
# """同通道下同音符交叉出现所产生的错误"""
# def __init__(self, *args):
# """同通道下同音符交叉出现所产生的错误"""
# super().__init__("同通道下同音符交叉", *args)
# 这TM是什么错误?
# 我什么时候写的这玩意?
# 我哪知道这说的是啥?
# !!!
# 我知道这是什么了 —— 金羿 2025 0401
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
class NotDefineTempoError(MidiFormatException):
"""没有Tempo设定导致时间无法计算的错误"""
def __init__(self, *args):
"""没有Tempo设定导致时间无法计算的错误"""
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args)
class ChannelOverFlowError(MidiFormatException):
"""一个midi中含有过多的通道"""
def __init__(self, max_channel=16, *args):
"""一个midi中含有过多的通道"""
super().__init__("含有过多的通道(数量应≤{}".format(max_channel), *args)
class NotDefineProgramError(MidiFormatException):
"""没有Program设定导致没有乐器可以选择的错误"""
def __init__(self, *args):
"""没有Program设定导致没有乐器可以选择的错误"""
super().__init__("未指定演奏乐器", *args)
class NoteOnOffMismatchError(MidiFormatException):
"""音符开音和停止不匹配的错误"""
def __init__(self, *args):
"""音符开音和停止不匹配的错误"""
super().__init__("音符不匹配", *args)
class LyricMismatchError(MSCTBaseException):
"""歌词匹配解析错误"""
def __init__(self, *args):
"""有可能产生了错误的歌词解析"""
super().__init__("歌词解析错误", *args)
class ZeroSpeedError(MSCTBaseException, ZeroDivisionError):
"""以0作为播放速度的错误"""
def __init__(self, *args):
"""以0作为播放速度的错误"""
super().__init__("播放速度为零", *args)
class IllegalMinimumVolumeError(MSCTBaseException, ValueError):
"""最小播放音量有误的错误"""
def __init__(self, *args):
"""最小播放音量错误"""
super().__init__("最小播放音量超出范围", *args)
class MusicSequenceDecodeError(MSCTBaseException):
"""音乐序列解码错误"""
def __init__(self, *args):
"""音乐序列无法正确解码的错误"""
super().__init__("解码音符序列文件时出现问题", *args)
class MusicSequenceTypeError(MSCTBaseException):
"""音乐序列类型错误"""
def __init__(self, *args):
"""无法识别音符序列字节码的类型"""
super().__init__("错误的音符序列字节类型", *args)
class MusicSequenceVerificationFailed(MusicSequenceDecodeError):
"""音乐序列校验失败"""
def __init__(self, *args):
"""音符序列文件与其校验值不一致"""
super().__init__("音符序列文件校验失败", *args)
File diff suppressed because it is too large Load Diff
-265
View File
@@ -1,265 +0,0 @@
# -*- coding: utf-8 -*-
"""
功能测试 若非已知 请勿更改
此文件仅供功能测试,并非实际调用的文件
请注意,此处的文件均为测试使用
不要更改 不要更改 不要更改
请注意这里的一切均需要其原作者更改
这里用于放置一些新奇的点子
用于测试
不要更改 不要更改 不要更改!
"""
# 音·创 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 版权所有 金羿("Eilles") & 诸葛亮与八卦阵("bgArray") & 鸣凤鸽子("MingFengPigeon")
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
"""
音·创 (Musicreater)
是一款免费开源的针对《我的世界》的midi音乐转换库
Musicreater (音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 ../License.md
Terms & Conditions: ../License.md
"""
# ============================
import mido
class NoteMessage:
def __init__(
self, channel, pitch, velocity, startT, lastT, midi, now_bpm, change_bpm=None
):
self.channel = channel
self.note = pitch
self.velocity = velocity
self.startTime = startT
self.lastTime = lastT
self.tempo = now_bpm # 这里要程序实现获取bpm可以参考我的程序
def mt2gt(mt, tpb_a, bpm_a):
return mt / tpb_a / bpm_a * 60
self.startTrueTime = mt2gt(
self.startTime, midi.ticks_per_beat, self.tempo
) # / 20
# delete_extra_zero(round_up())
if change_bpm is not None:
self.lastTrueTime = mt2gt(
self.lastTime, midi.ticks_per_beat, change_bpm
) # / 20
else:
self.lastTrueTime = mt2gt(
self.lastTime, midi.ticks_per_beat, self.tempo
) # / 20
# delete_extra_zero(round_up())
print((self.startTime * self.tempo) / (midi.ticks_per_beat * 50000))
def __str__(self):
return (
"noteMessage channel="
+ str(self.channel)
+ " note="
+ str(self.note)
+ " velocity="
+ str(self.velocity)
+ " startTime="
+ str(self.startTime)
+ " lastTime="
+ str(self.lastTime)
+ " startTrueTime="
+ str(self.startTrueTime)
+ " lastTrueTime="
+ str(self.lastTrueTime)
)
def load(mid: mido.MidiFile):
type_ = [False, False, False] # note_off / note_on+0 / mixed
is_tempo = False
# 预检
for i, track in enumerate(mid.tracks):
for msg in track:
# print(msg)
if msg.is_meta is not True:
if msg.type == "note_on" and msg.velocity == 0:
type_[1] = True
elif msg.type == "note_off":
type_[0] = True
if msg.is_meta is True and msg.type == "set_tempo":
is_tempo = True
if is_tempo is not True:
raise Exception("这个mid没有可供计算时间的tempo事件")
if type_[0] is True and type_[1] is True:
type_[2] = True
type_[1] = False
type_[0] = False
print(type_)
bpm = 0
recent_change_bpm = 0
is_change_bpm = False
# 实检
for i, track in enumerate(mid.tracks):
noteOn = []
trackS = []
ticks = 0
for msg in track:
print(msg)
ticks += msg.time
print(ticks)
if msg.is_meta is True and msg.type == "set_tempo":
recent_change_bpm = bpm
bpm = 60000000 / msg.tempo
is_change_bpm = True
if msg.type == "note_on" and msg.velocity != 0:
noteOn.append([msg, msg.note, ticks])
if type_[1] is True:
if msg.type == "note_on" and msg.velocity == 0:
for u in noteOn:
index = 0
if u[1] == msg.note:
lastMessage = u[0]
lastTick = u[2]
break
index += 1
print(lastTick)
if is_change_bpm and recent_change_bpm != 0:
trackS.append(
NoteMessage(
msg.channel,
msg.note,
lastMessage.velocity,
lastTick,
ticks - lastTick,
mid,
recent_change_bpm,
bpm,
)
)
is_change_bpm = False
else:
trackS.append(
NoteMessage(
msg.channel,
msg.note,
lastMessage.velocity,
lastTick,
ticks - lastTick,
mid,
bpm,
)
)
# print(noteOn)
# print(index)
try:
noteOn.pop(index)
except IndexError:
noteOn.pop(index - 1)
print(trackS)
for j in trackS:
print(j)
if __name__ == "__main__":
load(mido.MidiFile("test.mid"))
# ============================
from typing import Literal
from ..constants import x, y, z
# 不要用 没写完
def delay_to_note_blocks(
baseblock: str = "stone",
position_forward: Literal["x", "y", "z"] = z,
):
"""传入音符,生成以音符盒存储的红石音乐
:param:
baseblock: 中继器的下垫方块
position_forward: 结构延长方向
:return 是否生成成功
"""
from TrimMCStruct import Block, Structure
struct = Structure(
(_sideLength, max_height, _sideLength), # 声明结构大小
)
log = print
startpos = [0, 0, 0]
# 1拍 x 2.5 rt
for i in notes:
error = True
try:
struct.set_block(
[startpos[0], startpos[1] + 1, startpos[2]],
form_note_block_in_NBT_struct(height2note[i[0]], instrument),
)
struct.set_block(
startpos,
Block("universal_minecraft", instuments[i[0]][1]),
)
error = False
except ValueError:
log("无法放置音符:" + str(i) + "" + str(startpos))
struct.set_block(Block("universal_minecraft", baseblock), startpos)
struct.set_block(
Block("universal_minecraft", baseblock),
[startpos[0], startpos[1] + 1, startpos[2]],
)
finally:
if error is True:
log("无法放置音符:" + str(i) + "" + str(startpos))
struct.set_block(Block("universal_minecraft", baseblock), startpos)
struct.set_block(
Block("universal_minecraft", baseblock),
[startpos[0], startpos[1] + 1, startpos[2]],
)
delay = int(i[1] * speed + 0.5)
if delay <= 4:
startpos[0] += 1
struct.set_block(
form_repeater_in_NBT_struct(delay, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
else:
for j in range(int(delay / 4)):
startpos[0] += 1
struct.set_block(
form_repeater_in_NBT_struct(4, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
if delay % 4 != 0:
startpos[0] += 1
struct.set_block(
form_repeater_in_NBT_struct(delay % 4, "west"),
[startpos[0], startpos[1] + 1, startpos[2]],
)
struct.set_block(Block("universal_minecraft", baseblock), startpos)
startpos[0] += posadder[0]
startpos[1] += posadder[1]
startpos[2] += posadder[2]
-1816
View File
File diff suppressed because it is too large Load Diff
-73
View File
@@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放非音·创本体的附加功能件
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
# 通用
# "ConvertConfig",
"bottem_side_length_of_smallest_square_bottom_box",
# 打包
"compress_zipfile",
"behavior_mcpack_manifest",
# MCSTRUCTURE 函数
"antiaxis",
"forward_IER",
"command_statevalue",
"form_note_block_in_NBT_struct",
"form_repeater_in_NBT_struct",
"form_command_block_in_NBT_struct",
"commands_to_structure",
"commands_to_redstone_delay_structure",
# MCSTRUCTURE 常量
"AXIS_PARTICULAR_VALUE",
"COMPABILITY_VERSION_117",
"COMPABILITY_VERSION_119",
"COMPABILITY_VERSION_121",
# BDX 函数
"bdx_move",
"form_command_block_in_BDX_bytes",
"commands_to_BDX_bytes",
# BDX 常量
"BDX_MOVE_KEY",
]
__author__ = (("金羿", "Eilles"), ("诸葛亮与八卦阵", "bgArray"))
# from .main import ConvertConfig
from .archive import compress_zipfile, behavior_mcpack_manifest
from .bdx import (
BDX_MOVE_KEY,
bdx_move,
form_command_block_in_BDX_bytes,
commands_to_BDX_bytes,
)
from .common import bottem_side_length_of_smallest_square_bottom_box
from .mcstructure import (
antiaxis,
forward_IER,
AXIS_PARTICULAR_VALUE,
COMPABILITY_VERSION_119,
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_121,
command_statevalue,
form_note_block_in_NBT_struct,
form_repeater_in_NBT_struct,
form_command_block_in_NBT_struct,
commands_to_structure,
commands_to_redstone_delay_structure,
)
-30
View File
@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成附加包的附加功能
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_addon_pack_in_delay",
"to_addon_pack_in_score",
"to_addon_pack_in_repeater",
"to_addon_pack_in_repeater_divided_by_instrument",
]
__author__ = (("金羿", "Eilles"),)
from .main import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
to_addon_pack_in_repeater_divided_by_instrument,
)
-684
View File
@@ -1,684 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import json
import os
import shutil
from typing import Literal, Optional, Tuple
from ...main import MidiConvert
from ...subclass import ProgressBarStyle
from ..archive import behavior_mcpack_manifest, compress_zipfile
from ..mcstructure import (
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_119,
Structure,
commands_to_redstone_delay_structure,
commands_to_structure,
form_command_block_in_NBT_struct,
)
def to_addon_pack_in_score(
midi_cvt: MidiConvert,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
) -> Tuple[int, int]:
"""
将midi以计分播放器形式转换为我的世界函数附加包
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_score(
scoreboard_name=scoreboard_name,
)
# 当文件f夹{self.outputPath}/temp/functions存在时清空其下所有项目,然后创建
if os.path.exists(f"{dist_path}/temp/functions/"):
shutil.rmtree(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/functions/mscplay")
# 写入manifest.json
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.music_name} 音乐播放包,MCFUNCTION(MCPACK) 计分播放器 - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
)
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write("scoreboard players reset @a {}".format(scoreboard_name))
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
for i in range(len(cmdlist)):
index_file.write(f"function mscplay/track{i + 1}\n")
with open(
f"{dist_path}/temp/functions/mscplay/track{i + 1}.mcfunction",
"w",
encoding="utf-8",
) as f:
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
index_file.writelines(
(
"scoreboard players add @a[scores={"
+ scoreboard_name
+ "=1..}] "
+ scoreboard_name
+ " 1\n",
(
(
"scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(maxscore + 20)
+ "..}]"
+ f" {scoreboard_name}\n"
)
if auto_reset
else ""
),
f"function mscplay/progressShow\n" if progressbar_style else "",
)
)
if progressbar_style:
with open(
f"{dist_path}/temp/functions/mscplay/progressShow.mcfunction",
"w",
encoding="utf-8",
) as f:
f.writelines(
"\n".join(
[
single_cmd.cmd
for single_cmd in midi_cvt.form_progress_bar(
maxscore, scoreboard_name, progressbar_style
)
]
)
)
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[score].mcpack",
)
shutil.rmtree(f"{dist_path}/temp/")
return maxlen, maxscore
def to_addon_pack_in_delay(
midi_cvt: MidiConvert,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
player: str = "@a",
max_height: int = 64,
) -> Tuple[int, int]:
"""
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
command_list, max_delay = midi_cvt.to_command_list_in_delay(
player_selector=player,
)[:2]
if not os.path.exists(dist_path):
os.makedirs(dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{dist_path}/temp/"):
shutil.rmtree(f"{dist_path}/temp/")
os.makedirs(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.music_name} 音乐播放包,MCSTRUCTURE(MCPACK) 延迟播放器 - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
ensure_ascii=False,
)
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write(
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
)
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
struct, size, end_pos = commands_to_structure(
command_list,
max_height - 1,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_main.mcstructure",
)
),
"wb+",
) as f:
struct.dump(f)
del struct
if progressbar_style:
scb_name = midi_cvt.music_name[:3] + "Pgb"
index_file.write("scoreboard objectives add {0} dummy {0}\n".format(scb_name))
struct_a = Structure((1, 1, 1), compability_version=compability_ver)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players add {} {} 1".format(player, scb_name),
(0, 0, 0),
1,
1,
alwaysRun=False,
customName="显示进度条并加分",
compability_version_number=compability_ver,
),
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_start.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
index_file.write(f"structure load {midi_cvt.music_name}_start ~ ~ ~1\n")
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
max_height - 1,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_pgb.mcstructure",
)
),
"wb+",
) as f:
pgb_struct.dump(f)
index_file.write(f"structure load {midi_cvt.music_name}_pgb ~ ~1 ~1\n")
struct_a = Structure(
(1, 1, 1),
)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players reset {} {}".format(player, scb_name),
(0, 0, 0),
1,
0,
alwaysRun=False,
customName="重置进度条计分板",
compability_version_number=compability_ver,
),
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_reset.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
del struct_a, pgb_struct
index_file.write(
f"structure load {midi_cvt.music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
)
index_file.write(
f"structure load {midi_cvt.music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
)
else:
index_file.write(f"structure load {midi_cvt.music_name}_main ~ ~ ~1\n")
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[delay].mcpack",
)
shutil.rmtree(f"{dist_path}/temp/")
return len(command_list), max_delay
def to_addon_pack_in_repeater(
midi_cvt: MidiConvert,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
player: str = "@a",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
basement_block: str = "concrete",
max_height: int = 65,
) -> Tuple[int, int]:
"""
将midi以中继器播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
command_list, max_delay, max_together = midi_cvt.to_command_list_in_delay(
player_selector=player,
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{dist_path}/temp/"):
shutil.rmtree(f"{dist_path}/temp/")
os.makedirs(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.music_name} 音乐播放包,MCSTRUCTURE(MCPACK) 中继器播放器 - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
)
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write(
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
)
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
struct, size, end_pos = commands_to_redstone_delay_structure(
commands=command_list,
delay_length=max_delay,
max_multicmd_length=max_together,
base_block=basement_block,
axis_=axis_side,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_main.mcstructure",
)
),
"wb+",
) as f:
struct.dump(f)
del struct
if progressbar_style:
scb_name = midi_cvt.music_name[:3] + "Pgb"
index_file.write("scoreboard objectives add {0} dummy {0}\n".format(scb_name))
struct_a = Structure((1, 1, 1), compability_version=compability_ver)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players add {} {} 1".format(player, scb_name),
(0, 0, 0),
1,
1,
alwaysRun=False,
customName="显示进度条并加分",
compability_version_number=compability_ver,
),
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_start.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
index_file.write(f"structure load {midi_cvt.music_name}_start ~ ~ ~1\n")
pgb_struct, pgbSize, pgbNowPos = commands_to_structure(
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
max_height - 1,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_pgb.mcstructure",
)
),
"wb+",
) as f:
pgb_struct.dump(f)
index_file.write(f"structure load {midi_cvt.music_name}_pgb ~ ~1 ~1\n")
struct_a = Structure(
(1, 1, 1),
)
struct_a.set_block(
(0, 0, 0),
form_command_block_in_NBT_struct(
r"scoreboard players reset {} {}".format(player, scb_name),
(0, 0, 0),
1,
0,
alwaysRun=False,
customName="重置进度条计分板",
compability_version_number=compability_ver,
),
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
f"{midi_cvt.music_name}_reset.mcstructure",
)
),
"wb+",
) as f:
struct_a.dump(f)
del struct_a, pgb_struct
index_file.write(
f"structure load {midi_cvt.music_name}_reset ~{pgbSize[0] + 2} ~ ~1\n"
)
index_file.write(
f"structure load {midi_cvt.music_name}_main ~{pgbSize[0] + 2} ~1 ~1\n"
)
else:
index_file.write(f"structure load {midi_cvt.music_name}_main ~ ~ ~1\n")
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[repeater].mcpack",
)
shutil.rmtree(f"{dist_path}/temp/")
return len(command_list), max_delay
def to_addon_pack_in_repeater_divided_by_instrument(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
max_height: int = 65,
base_block: str = "concrete",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
) -> Tuple[int, int]:
"""
将midi以中继器播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
# 当文件f夹{self.outputPath}/temp/存在时清空其下所有项目,然后创建
if os.path.exists(f"{dist_path}/temp/"):
shutil.rmtree(f"{dist_path}/temp/")
os.makedirs(f"{dist_path}/temp/functions/")
os.makedirs(f"{dist_path}/temp/structures/")
# 写入manifest.json
with open(f"{dist_path}/temp/manifest.json", "w", encoding="utf-8") as f:
json.dump(
behavior_mcpack_manifest(
pack_description=f"{midi_cvt.music_name} 音乐播放包,MCSTRUCTURE(MCPACK) 中继器播放器(拆分) - 由 音·创 生成",
pack_name=midi_cvt.music_name + "播放",
modules_description=f"无 - 由 音·创 生成",
format_version=1 if midi_cvt.enable_old_exe_format else 2,
pack_engine_version=(
None if midi_cvt.enable_old_exe_format else [1, 19, 50]
),
),
fp=f,
indent=4,
)
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/functions/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write(
"gamerule commandblocksenabled false\ngamerule commandblocksenabled true"
)
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/functions/index.mcfunction", "w", encoding="utf-8"
)
cmd_dict, max_delay, max_multiple_cmd_count = (
midi_cvt.to_command_list_in_delay_devided_by_instrument(
player_selector=player,
)
)
base_height = 0
for inst, cmd_list in cmd_dict.items():
struct, size, end_pos = commands_to_redstone_delay_structure(
cmd_list,
max_delay,
max_multiple_cmd_count[inst],
base_block,
axis_=axis_side,
compability_version_=compability_ver,
)
bkn = "{}_{}".format(midi_cvt.music_name, inst.replace(".", "-"))
with open(
os.path.abspath(
os.path.join(
dist_path,
"temp/structures/",
"{}_main.mcstructure".format(bkn),
)
),
"wb+",
) as f:
struct.dump(f)
index_file.write("structure load {}_main ~ ~{} ~3\n".format(bkn, base_height))
base_height += 2 + size[1]
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.mcpack"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.mcpack")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[repeater-div].mcpack",
)
shutil.rmtree(f"{dist_path}/temp/")
return midi_cvt.total_note_count, max_delay
-104
View File
@@ -1,104 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放关于文件打包的内容
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import datetime
import os
import uuid
import zipfile
from typing import List, Literal, Union
def compress_zipfile(sourceDir, outFilename, compression=8, exceptFile=None):
"""
使用指定的压缩算法将目录打包为zip文件
Parameters
------------
sourceDir: str
要压缩的源目录路径
outFilename: str
输出的zip文件路径
compression: int, 可选
压缩算法,默认为8 (DEFLATED)
可用算法:
STORED = 0
DEFLATED = 8 (默认)
BZIP2 = 12
LZMA = 14
exceptFile: list[str], 可选
需要排除在压缩包外的文件名称列表(可选)
Returns
---------
None
"""
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
pathfile = os.path.join(parent, filename)
arc_name = pathfile[pre_len:].strip(os.path.sep) # 相对路径
zipf.write(pathfile, arc_name)
zipf.close()
def behavior_mcpack_manifest(
format_version: Union[Literal[1], Literal[2]] = 1,
pack_description: str = "",
pack_version: Union[List[int], Literal[None]] = None,
pack_name: str = "",
pack_uuid: Union[str, Literal[None]] = None,
pack_engine_version: Union[List[int], None] = None,
modules_description: str = "",
modules_version: List[int] = [0, 0, 1],
modules_uuid: Union[str, Literal[None]] = None,
):
"""
生成一个我的世界行为包组件的定义清单文件
"""
if not pack_version:
now_date = datetime.datetime.now()
pack_version = [
now_date.year,
now_date.month * 100 + now_date.day,
now_date.hour * 100 + now_date.minute,
]
result = {
"format_version": format_version,
"header": {
"description": pack_description,
"version": pack_version,
"name": pack_name,
"uuid": str(uuid.uuid4()) if not pack_uuid else pack_uuid,
},
"modules": [
{
"description": modules_description,
"type": "data",
"version": modules_version,
"uuid": str(uuid.uuid4()) if not modules_uuid else modules_uuid,
}
],
}
if pack_engine_version:
result["header"]["min_engine_version"] = pack_engine_version
return result
-229
View File
@@ -1,229 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关BDX结构操作的内容
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List
from ..constants import x, y, z
from ..subclass import MineCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
BDX_MOVE_KEY = {
"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"],
}
"""key存储了方块移动指令的数据,其中可以用key[x|y|z][0|1]来表示xyz的减或增
而key[][2+]是用来增加指定数目的"""
def bdx_move(axis: str, value: int):
if value == 0:
return b""
if abs(value) == 1:
return BDX_MOVE_KEY[axis][0 if value == -1 else 1]
pointer = sum(
[
value != -1,
value < -1 or value > 1,
value < -128 or value > 127,
value < -32768 or value > 32767,
]
)
return BDX_MOVE_KEY[axis][pointer] + value.to_bytes(
2 ** (pointer - 2), "big", signed=True
)
def form_command_block_in_BDX_bytes(
command: str,
particularValue: int,
impluse: int = 0,
condition: bool = False,
needRedstone: bool = True,
tickDelay: int = 0,
customName: str = "",
executeOnFirstTick: bool = False,
trackOutput: bool = True,
) -> bytes:
"""
使用指定参数生成指定的指令方块放置指令项
Parameters
------------
command: str
指令
particularValue: int
方块特殊值,即朝向
: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参数覆写
impluse: int (0|1|2)
方块类型
0脉冲 1循环 2连锁
condition: bool
是否有条件
needRedstone: bool
是否需要红石
tickDelay: int
执行延时
customName: str
悬浮字
lastOutput: str
命令方块的上次输出字符串,注意此处需要留空
executeOnFirstTick: bool
是否启用首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
trackOutput: bool
是否启用命令方块输出
Returns
---------
bytes
用以生成 bdx 结构的字节码
"""
block = b"\x24" + particularValue.to_bytes(2, byteorder="big", signed=False)
for i in [
impluse.to_bytes(4, byteorder="big", signed=False),
bytes(command, encoding="utf-8") + b"\x00",
bytes(customName, encoding="utf-8") + b"\x00",
bytes("", encoding="utf-8") + b"\x00",
tickDelay.to_bytes(4, byteorder="big", signed=True),
executeOnFirstTick.to_bytes(1, byteorder="big"),
trackOutput.to_bytes(1, byteorder="big"),
condition.to_bytes(1, byteorder="big"),
needRedstone.to_bytes(1, byteorder="big"),
]:
block += i
return block
def commands_to_BDX_bytes(
commands_list: List[MineCommand],
max_height: int = 64,
):
"""
指令列表转换为用以生成 bdx 结构的字节码
Parameters
------------
commands: list[tuple[str, int]]
指令列表,每个元素为 (指令, 延迟)
max_height: int
生成结构最大高度
Returns
---------
tuple[bool, bytes, int] or tuple[bool, str]
成功与否,成功返回 (True, 未经过压缩的源, 结构占用大小),失败返回 (False, str失败原因)
"""
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands_list), max_height
)
_bytes = b""
y_forward = True
z_forward = True
now_y = 0
now_z = 0
now_x = 0
for command in commands_list:
_bytes += form_command_block_in_BDX_bytes(
command.command_text,
(
(1 if y_forward else 0)
if (
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (
(3 if z_forward else 2)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength - 1))
)
else 5
)
),
impluse=2,
condition=command.conditional,
needRedstone=False,
tickDelay=command.delay,
customName=command.annotation_text,
executeOnFirstTick=False,
trackOutput=True,
)
# (1 if y_forward else 0) if ( # 如果y+则向上,反之向下
# ((now_y != 0) and (not y_forward)) # 如果不是y轴上首个方块
# or (y_forward and (now_y != (max_height - 1))) # 如果不是y轴上末端方块
# ) else ( # 否则,即是y轴末端或首个方块
# (3 if z_forward else 2) if ( # 如果z+则向z轴正方向,反之负方向
# ((now_z != 0) and (not z_forward)) # 如果不是z轴上的首个方块
# or (z_forward and (now_z != _sideLength - 1)) # 如果不是z轴上的末端方块
# ) else 5 # 否则,则要面向x轴正方向
# )
now_y += 1 if y_forward else -1
if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
now_y -= 1 if y_forward else -1
y_forward = not y_forward
now_z += 1 if z_forward else -1
if ((now_z >= _sideLength) and z_forward) or (
(now_z < 0) and (not z_forward)
):
now_z -= 1 if z_forward else -1
z_forward = not z_forward
_bytes += BDX_MOVE_KEY[x][1]
now_x += 1
else:
_bytes += BDX_MOVE_KEY[z][int(z_forward)]
else:
_bytes += BDX_MOVE_KEY[y][int(y_forward)]
return (
_bytes,
[
now_x + 1,
max_height if now_x or now_z else now_y,
_sideLength if now_x else now_z,
],
[now_x, now_y, now_z],
)
-20
View File
@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成BDX结构文件的附加功能
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = ["to_BDX_file_in_score", "to_BDX_file_in_delay"]
__author__ = (("金羿", "Eilles"),)
from .main import to_BDX_file_in_delay, to_BDX_file_in_score
-220
View File
@@ -1,220 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
from typing import Optional
import brotli
from ...main import MidiConvert
from ...subclass import MineCommand, ProgressBarStyle
from ..bdx import (
bdx_move,
commands_to_BDX_bytes,
form_command_block_in_BDX_bytes,
x,
y,
z,
)
def to_BDX_file_in_score(
midi_cvt: MidiConvert,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
author: str = "Eilles",
max_height: int = 64,
):
"""
将midi以计分播放器形式转换为BDX结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
author: str
作者名称
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标]
"""
cmdlist, command_count, max_score = midi_cvt.to_command_list_in_score(
scoreboard_name=scoreboard_name,
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].bdx")),
"w+",
) as f:
f.write("BD@")
_bytes = (
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
)
cmdBytes, size, finalPos = commands_to_BDX_bytes(
midi_cvt.music_command_list
+ (
[
MineCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(max_score + 20)
+ "}] "
+ scoreboard_name,
annotation="自动重置计分板",
)
]
if auto_reset
else []
),
max_height - 1,
)
if progressbar_style:
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(max_score, scoreboard_name, progressbar_style),
max_height - 1,
)
_bytes += pgbBytes
_bytes += bdx_move(y, -pgbNowPos[1])
_bytes += bdx_move(z, -pgbNowPos[2])
_bytes += bdx_move(x, 2)
size[0] += 2 + pgbSize[0]
size[1] = max(size[1], pgbSize[1])
size[2] = max(size[2], pgbSize[2])
_bytes += cmdBytes
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].bdx")),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))
return command_count, max_score, size, finalPos
def to_BDX_file_in_delay(
midi_cvt: MidiConvert,
dist_path: str,
progressbar_style: Optional[ProgressBarStyle],
player: str = "@a",
author: str = "Eilles",
max_height: int = 64,
):
"""
使用method指定的转换算法,将midi转换为BDX结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
player: str
玩家选择器,默认为`@a`
author: str
作者名称
max_height: int
生成结构最大高度
Returns
-------
tuple[int指令数量, int音乐总延迟, tuple[int,]结构大小, tuple[int,]终点坐标]
"""
cmdlist, max_delay = midi_cvt.to_command_list_in_delay(
player_selector=player,
)[:2]
if not os.path.exists(dist_path):
os.makedirs(dist_path)
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].bdx")),
"w+",
) as f:
f.write("BD@")
_bytes = (
b"BDX\x00" + author.encode("utf-8") + b" & Musicreater\x00\x01command_block\x00"
)
cmdBytes, size, finalPos = commands_to_BDX_bytes(cmdlist, max_height - 1)
if progressbar_style:
scb_name = midi_cvt.music_name[:3] + "Pgb"
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard objectives add {} dummy {}".replace(r"{}", scb_name),
1,
customName="初始化进度条",
)
_bytes += bdx_move(z, 2)
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard players add {} {} 1".format(player, scb_name),
1,
1,
customName="显示进度条并加分",
)
_bytes += bdx_move(y, 1)
pgbBytes, pgbSize, pgbNowPos = commands_to_BDX_bytes(
midi_cvt.form_progress_bar(max_delay, scb_name, progressbar_style),
max_height - 1,
)
_bytes += pgbBytes
_bytes += bdx_move(y, -1 - pgbNowPos[1])
_bytes += bdx_move(z, -2 - pgbNowPos[2])
_bytes += bdx_move(x, 2)
_bytes += form_command_block_in_BDX_bytes(
r"scoreboard players reset {} {}".format(player, scb_name),
1,
customName="置零进度条",
)
_bytes += bdx_move(y, 1)
size[0] += 2 + pgbSize[0]
size[1] = max(size[1], pgbSize[1])
size[2] = max(size[2], pgbSize[2])
size[1] += 1
_bytes += cmdBytes
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].bdx")),
"ab+",
) as f:
f.write(brotli.compress(_bytes + b"XE"))
return len(cmdlist), max_delay, size, finalPos
-40
View File
@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放通用的普遍性的插件内容
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import math
def bottem_side_length_of_smallest_square_bottom_box(
_total_block_count: int, _max_height: int
):
"""
给定结构的总方块数量和规定的最大高度,返回该结构应当构成的图形,在底面的外切正方形之边长
Parameters
------------
_total_block_count: int
总方块数量
_max_height: int
规定的结构最大高度
Returns
---------
int
外切正方形的边长
"""
return math.ceil(math.sqrt(math.ceil(_total_block_count / _max_height)))
-92
View File
@@ -1,92 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放附加内容功能
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# from dataclasses import dataclass
# from typing import Literal, Tuple, Union
# from ..subclass import DEFAULT_PROGRESSBAR_STYLE, ProgressBarStyle
# @dataclass(init=False)
# class ConvertConfig: # 必定要改
# """
# 转换通用设置存储类
# """
# progressbar_style: Union[ProgressBarStyle, None]
# """进度条样式"""
# dist_path: str
# """输出目录"""
# def __init__(
# self,
# output_path: str,
# progressbar: Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] = True,
# ignore_progressbar_param_error: bool = False,
# ):
# """
# 将已经转换好的数据内容指令载入MC可读格式
# Parameters
# ----------
# output_path: str
# 生成内容的输出目录
# volume: float
# 音量比率,范围为(0,1],其原理为在距离玩家 (1 / volume -1) 的地方播放音频
# speed: float
# 速度倍率,注意:这里的速度指的是播放速度倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
# progressbar: bool|tuple[str, Tuple[str,]]
# 进度条,当此参数为 `True` 时使用默认进度条,为其他的**值为真**的参数时识别为进度条自定义参数,为其他**值为假**的时候不生成进度条
# """
# self.dist_path = output_path
# """输出目录"""
# if progressbar:
# # 此处是对于仅有 True 的参数和自定义参数的判断
# # 改这一段没🐎
# if progressbar is True:
# self.progressbar_style = DEFAULT_PROGRESSBAR_STYLE
# """进度条样式"""
# return
# elif isinstance(progressbar, ProgressBarStyle):
# self.progressbar_style = progressbar
# """进度条样式"""
# return
# elif isinstance(progressbar, tuple):
# if isinstance(progressbar[0], str) and isinstance(
# progressbar[1], tuple
# ):
# if isinstance(progressbar[1][0], str) and isinstance(
# progressbar[1][1], str
# ):
# self.progressbar_style = ProgressBarStyle(
# progressbar[0], progressbar[1][0], progressbar[1][1]
# )
# return
# if not ignore_progressbar_param_error:
# raise TypeError(
# "参数 {} 的类型 {} 与所需类型 Union[bool, Tuple[str, Tuple[str, str]], ProgressBarStyle] 不符。".format(
# progressbar, type(progressbar)
# )
# )
# self.progressbar_style = None
# """进度条样式组"""
@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成单个mcstructure文件的附加功能
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
"to_mcstructure_file_in_delay",
"to_mcstructure_file_in_repeater",
"to_mcstructure_file_in_score",
"to_mcstructure_files_in_repeater_divided_by_instruments",
]
__author__ = (("金羿", "Eilles"),)
from .main import (
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
to_mcstructure_files_in_repeater_divided_by_instruments,
)
-298
View File
@@ -1,298 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import os
from typing import Literal
from ...main import MidiConvert
from ...subclass import MineCommand
from ..mcstructure import (
COMPABILITY_VERSION_117,
COMPABILITY_VERSION_119,
commands_to_redstone_delay_structure,
commands_to_structure,
)
def to_mcstructure_file_in_delay(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
max_height: int
生成结构最大高度
Returns
-------
tuple[tuple[int,int,int]结构大小, int音乐总延迟]
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
cmd_list, max_delay = midi_cvt.to_command_list_in_delay(
player_selector=player,
)[:2]
if not os.path.exists(dist_path):
os.makedirs(dist_path)
struct, size, end_pos = commands_to_structure(
cmd_list, max_height - 1, compability_version_=compability_ver
)
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[delay].mcstructure")),
"wb+",
) as f:
struct.dump(f)
return size, max_delay
def to_mcstructure_file_in_score(
midi_cvt: MidiConvert,
dist_path: str,
scoreboard_name: str = "mscplay",
auto_reset: bool = False,
max_height: int = 64,
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
max_height: int
生成结构最大高度
Returns
-------
tuple[tuple[int,int,int]结构大小, int音乐总延迟, int指令数量
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
cmd_list, cmd_count, max_delay = midi_cvt.to_command_list_in_score(
scoreboard_name=scoreboard_name,
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
struct, size, end_pos = commands_to_structure(
midi_cvt.music_command_list
+ (
[
MineCommand(
command="scoreboard players reset @a[scores={"
+ scoreboard_name
+ "="
+ str(max_delay + 20)
+ "}] "
+ scoreboard_name,
annotation="自动重置计分板",
)
]
if auto_reset
else []
),
max_height - 1,
compability_version_=compability_ver,
)
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[score].mcstructure")),
"wb+",
) as f:
struct.dump(f)
return size, max_delay, cmd_count
def to_mcstructure_file_in_repeater(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
basement_block: str = "concrete",
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
生成结构的延展方向
basement_block: str
结构的基底方块
Returns
-------
tuple[tuple[int,int,int]结构大小, int音乐总延迟]
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
cmd_list, max_delay, max_multiple_cmd = midi_cvt.to_command_list_in_delay(
player_selector=player,
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
struct, size, end_pos = commands_to_redstone_delay_structure(
cmd_list,
max_delay,
max_multiple_cmd,
basement_block,
axis_side,
compability_version_=compability_ver,
)
with open(
os.path.abspath(os.path.join(dist_path, f"{midi_cvt.music_name}[repeater].mcstructure")),
"wb+",
) as f:
struct.dump(f)
return size, max_delay
def to_mcstructure_files_in_repeater_divided_by_instruments(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
axis_side: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
basement_block: str = "concrete",
):
"""
将midi以延迟播放器形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
axis_side: Literal["z+","z-","Z+","Z-","x+","x-","X+","X-"]
生成结构的延展方向
basement_block: str
结构的基底方块
Returns
-------
int音乐总延迟
"""
compability_ver = (
COMPABILITY_VERSION_117
if midi_cvt.enable_old_exe_format
else COMPABILITY_VERSION_119
)
cmd_dict, max_delay, max_multiple_cmd_count = (
midi_cvt.to_command_list_in_delay_devided_by_instrument(
player_selector=player,
)
)
if not os.path.exists(dist_path):
os.makedirs(dist_path)
for inst, cmd_list in cmd_dict.items():
struct, size, end_pos = commands_to_redstone_delay_structure(
cmd_list,
max_delay,
max_multiple_cmd_count[inst],
basement_block,
axis_side,
compability_version_=compability_ver,
)
with open(
os.path.abspath(
os.path.join(
dist_path,
"{}[repeater-div]_{}.mcstructure".format(
midi_cvt.music_name, inst.replace(".", "-")
),
)
),
"wb+",
) as f:
struct.dump(f)
return max_delay
def to_mcstructure_file_in_blocks(
midi_cvt: MidiConvert,
dist_path: str,
player: str = "@a",
):
"""
将midi以方块形式转换为mcstructure结构文件
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
player: str
玩家选择器,默认为`@a`
Returns
-------
int音乐总延迟
"""
pass
-543
View File
@@ -1,543 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关MCSTRUCTURE结构操作的内容
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from typing import List, Literal, Tuple
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
from ..constants import x, y, z
from ..subclass import MineCommand
from .common import bottem_side_length_of_smallest_square_bottom_box
def antiaxis(axis: Literal["x", "z", "X", "Z"]):
return z if axis == x else x
def forward_IER(forward: bool):
return 1 if forward else -1
AXIS_PARTICULAR_VALUE = {
x: {
True: 5,
False: 4,
},
y: {
True: 1,
False: 0,
},
z: {
True: 3,
False: 2,
},
}
# 1.19的结构兼容版本号
COMPABILITY_VERSION_119: int = 17959425
"""
Minecraft 1.19 兼容版本号
"""
# 1.17的结构兼容版本号
COMPABILITY_VERSION_117: int = 17879555
"""
Minecraft 1.17 兼容版本号
"""
COMPABILITY_VERSION_121: int = 18168865
"""
Minecraft 1.21 兼容版本号
"""
def command_statevalue(axis_: Literal["x", "y", "z", "X", "Y", "Z"], forward_: bool):
return AXIS_PARTICULAR_VALUE[axis_.lower()][forward_]
def form_note_block_in_NBT_struct(
note: int,
coordinate: Tuple[int, int, int],
instrument: str = "note.harp",
powered: bool = False,
compability_version_number: int = COMPABILITY_VERSION_119,
) -> Block:
"""
生成音符盒方块
Parameters
------------
note: int (0~24)
音符的音高
coordinate: tuple[int, int, int]
此方块所在之相对坐标
instrument: str
音符盒的乐器
powered: bool
是否已被激活
Returns
-------
Block
生成的方块对象
"""
return Block(
"minecraft",
"noteblock",
{
"instrument": instrument.replace("note.", ""),
"note": note,
"powered": powered,
},
{
"block_entity_data": {
"note": TAG_Byte(note),
"id": "noteblock",
"x": coordinate[0],
"y": coordinate[1],
"z": coordinate[2],
} # type: ignore
},
compability_version=compability_version_number,
)
def form_repeater_in_NBT_struct(
delay: int,
facing: int,
compability_version_number: int = COMPABILITY_VERSION_119,
) -> Block:
"""
生成中继器方块
Parameters
----------
facing: int (0~3)
朝向:
Z- 北 0
X- 东 1
Z+ 南 2
X+ 西 3
delay: int (0~3)
信号延迟
Returns
-------
Block
生成的方块对象
"""
return Block(
"minecraft",
"unpowered_repeater",
{
"repeater_delay": delay,
"direction": facing,
},
compability_version=compability_version_number,
)
def form_command_block_in_NBT_struct(
command: str,
coordinate: tuple,
particularValue: int,
impluse: int = 0,
condition: bool = False,
alwaysRun: bool = True,
tickDelay: int = 0,
customName: str = "",
executeOnFirstTick: bool = False,
trackOutput: bool = True,
compability_version_number: int = COMPABILITY_VERSION_119,
) -> Block:
"""
使用指定参数生成指令方块
Parameters
----------
command: str
指令
coordinate: tuple[int,int,int]
此方块所在之相对坐标
particularValue: int
方块特殊值,即朝向
: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参数覆写
impluse: int (0|1|2)
方块类型
0脉冲 1循环 2连锁
condition: bool
是否有条件
alwaysRun: bool
是否始终执行
tickDelay: int
执行延时
customName: str
悬浮字
executeOnFirstTick: bool
是否启用首刻执行(循环指令方块是否激活后立即执行,若为False,则从激活时起延迟后第一次执行)
trackOutput: bool
是否启用命令方块输出
compability_version_number: int
版本兼容代号
Returns
-------
Block
生成的方块对象
"""
return Block(
"minecraft",
(
"command_block"
if impluse == 0
else ("repeating_command_block" if impluse == 1 else "chain_command_block")
),
states={"conditional_bit": condition, "facing_direction": particularValue},
extra_data={
"block_entity_data": {
"Command": command,
"CustomName": customName,
"ExecuteOnFirstTick": executeOnFirstTick,
"LPCommandMode": 0,
"LPCondionalMode": False,
"LPRedstoneMode": False,
"LastExecution": TAG_Long(0),
"LastOutput": "",
"LastOutputParams": [],
"SuccessCount": 0,
"TickDelay": tickDelay,
"TrackOutput": trackOutput,
"Version": (
25 if compability_version_number <= COMPABILITY_VERSION_119 else 43
),
"auto": alwaysRun,
"conditionMet": False, # 是否已经满足条件
"conditionalMode": condition,
"id": "CommandBlock",
"isMovable": True,
"powered": False, # 是否已激活
"x": coordinate[0],
"y": coordinate[1],
"z": coordinate[2],
} # type: ignore
},
compability_version=compability_version_number,
)
def commands_to_structure(
commands: List[MineCommand],
max_height: int = 64,
compability_version_: int = COMPABILITY_VERSION_119,
):
"""
由指令列表生成(纯指令方块)结构
Parameters
------------
commands: list
指令列表
max_height: int
生成结构最大高度
Returns
---------
Structure, tuple[int, int, int], tuple[int, int, int]
结构类, 结构占用大小, 终点坐标
"""
_sideLength = bottem_side_length_of_smallest_square_bottom_box(
len(commands), max_height
)
struct = Structure(
size=(_sideLength, max_height, _sideLength), # 声明结构大小
compability_version=compability_version_,
)
y_forward = True
z_forward = True
now_y = 0
now_z = 0
now_x = 0
for command in commands:
coordinate = (now_x, now_y, now_z)
struct.set_block(
coordinate,
form_command_block_in_NBT_struct(
command=command.command_text,
coordinate=coordinate,
particularValue=(
(1 if y_forward else 0)
if (
((now_y != 0) and (not y_forward))
or (y_forward and (now_y != (max_height - 1)))
)
else (
(3 if z_forward else 2)
if (
((now_z != 0) and (not z_forward))
or (z_forward and (now_z != _sideLength - 1))
)
else 5
)
),
impluse=2,
condition=False,
alwaysRun=True,
tickDelay=command.delay,
customName=command.annotation_text,
executeOnFirstTick=False,
trackOutput=True,
compability_version_number=compability_version_,
),
)
now_y += 1 if y_forward else -1
if ((now_y >= max_height) and y_forward) or ((now_y < 0) and (not y_forward)):
now_y -= 1 if y_forward else -1
y_forward = not y_forward
now_z += 1 if z_forward else -1
if ((now_z >= _sideLength) and z_forward) or (
(now_z < 0) and (not z_forward)
):
now_z -= 1 if z_forward else -1
z_forward = not z_forward
now_x += 1
return (
struct,
(
now_x + 1,
max_height if now_x or now_z else now_y,
_sideLength if now_x else now_z,
),
(now_x, now_y, now_z),
)
def commands_to_redstone_delay_structure(
commands: List[MineCommand],
delay_length: int,
max_multicmd_length: int,
base_block: str = "concrete",
axis_: Literal["z+", "z-", "Z+", "Z-", "x+", "x-", "X+", "X-"] = "z+",
compability_version_: int = COMPABILITY_VERSION_119,
) -> Tuple[Structure, Tuple[int, int, int], Tuple[int, int, int]]:
"""
由指令列表生成由红石中继器延迟的结构
Parameters
------------
commands: list
指令列表
delay_length: int
延时总长
max_multicmd_length: int
最大同时播放的音符数量
base_block: Block
生成结构的基底方块
axis_: str
生成结构的延展方向
Returns
---------
Structure, tuple[int, int, int], tuple[int, int, int]
结构类, 结构占用大小, 终点坐标
"""
if axis_ in ["z+", "Z+"]:
extensioon_direction = z
aside_direction = x
repeater_facing = 2
forward = True
elif axis_ in ["z-", "Z-"]:
extensioon_direction = z
aside_direction = x
repeater_facing = 0
forward = False
elif axis_ in ["x+", "X+"]:
extensioon_direction = x
aside_direction = z
repeater_facing = 3
forward = True
elif axis_ in ["x-", "X-"]:
extensioon_direction = x
aside_direction = z
repeater_facing = 1
forward = False
else:
raise ValueError(f"axis_({axis_}) 参数错误。")
goahead = forward_IER(forward)
command_actually_length = sum([int(bool(cmd.delay)) for cmd in commands])
a = 1
a_max = 0
total_cmd = 0
for cmd in commands:
# print("\r 正在进行处理:",end="")
if cmd.delay > 2:
a_max = max(a, a_max)
total_cmd += (a := 1)
else:
a += 1
struct = Structure(
size=(
round(delay_length / 2 + total_cmd) if extensioon_direction == x else a_max,
3,
round(delay_length / 2 + total_cmd) if extensioon_direction == z else a_max,
),
fill=Block("minecraft", "air", compability_version=compability_version_),
compability_version=compability_version_,
)
pos_now = {
x: ((1 if extensioon_direction == x else 0) if forward else struct.size[0]),
y: 0,
z: ((1 if extensioon_direction == z else 0) if forward else struct.size[2]),
}
chain_list = 0
# print("结构元信息设定完毕")
for cmd in commands:
# print("\r 正在进行处理:",end="")
if cmd.delay > 1:
# print("\rdelay > 0",end='')
single_repeater_value = int(cmd.delay / 2) % 4 - 1
additional_repeater = int(cmd.delay / 2 // 4)
for i in range(additional_repeater):
struct.set_block(
tuple(pos_now.values()), # type: ignore
Block(
"minecraft",
base_block,
compability_version=compability_version_,
),
)
struct.set_block(
(pos_now[x], 1, pos_now[z]),
form_repeater_in_NBT_struct(
delay=3,
facing=repeater_facing,
compability_version_number=compability_version_,
),
)
pos_now[extensioon_direction] += goahead
if single_repeater_value >= 0:
struct.set_block(
tuple(pos_now.values()), # type: ignore
Block(
"minecraft",
base_block,
compability_version=compability_version_,
),
)
struct.set_block(
(pos_now[x], 1, pos_now[z]),
form_repeater_in_NBT_struct(
delay=single_repeater_value,
facing=repeater_facing,
compability_version_number=compability_version_,
),
)
pos_now[extensioon_direction] += goahead
struct.set_block(
(pos_now[x], 1, pos_now[z]),
form_command_block_in_NBT_struct(
command=cmd.command_text,
coordinate=(pos_now[x], 1, pos_now[z]),
particularValue=command_statevalue(extensioon_direction, forward),
# impluse= (0 if first_impluse else 2),
impluse=0,
condition=False,
alwaysRun=False,
tickDelay=cmd.delay % 2,
customName=cmd.annotation_text,
compability_version_number=compability_version_,
),
)
struct.set_block(
(pos_now[x], 2, pos_now[z]),
Block(
"minecraft",
"redstone_wire",
compability_version=compability_version_,
),
)
pos_now[extensioon_direction] += goahead
chain_list = 1
else:
# print(pos_now)
now_pos_copy = pos_now.copy()
now_pos_copy[extensioon_direction] -= goahead
now_pos_copy[aside_direction] += chain_list
# print(pos_now,"\n=========")
struct.set_block(
(now_pos_copy[x], 1, now_pos_copy[z]),
form_command_block_in_NBT_struct(
command=cmd.command_text,
coordinate=(now_pos_copy[x], 1, now_pos_copy[z]),
particularValue=command_statevalue(extensioon_direction, forward),
# impluse= (0 if first_impluse else 2),
impluse=0,
condition=False,
alwaysRun=False,
tickDelay=cmd.delay % 2,
customName=cmd.annotation_text,
compability_version_number=compability_version_,
),
)
struct.set_block(
(now_pos_copy[x], 2, now_pos_copy[z]),
Block(
"minecraft",
"redstone_wire",
compability_version=compability_version_,
),
)
chain_list += 1
return struct, struct.size, tuple(pos_now.values()) # type: ignore
-117
View File
@@ -1,117 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关红石音乐生成操作的内容
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from ..exceptions import NotDefineProgramError, ZeroSpeedError
from ..main import MidiConvert
from ..subclass import MineCommand
from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX
# 你以为写完了吗?其实并没有
def to_note_list(
midi_cvt: MidiConvert,
speed: float = 1.0,
) -> list:
"""
使用金羿的转换思路,将midi转换为我的世界音符盒所用的音高列表,并输出每个音符之后的延迟
Parameters
----------
speed: float
速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed
Returns
-------
tuple( list[tuple(str指令, int距离上一个指令的延迟 ),...], int音乐时长游戏刻 )
"""
if speed == 0:
raise ZeroSpeedError("播放速度仅可为正实数")
midi_channels = (
midi_cvt.to_music_channels() if not midi_cvt.channels else midi_cvt.channels
)
tracks = {}
# 此处 我们把通道视为音轨
for i in midi_channels.keys():
# 如果当前通道为空 则跳过
if not midi_channels[i]:
continue
# 第十通道是打击乐通道
SpecialBits = True if i == 9 else False
# nowChannel = []
for track_no, track in midi_channels[i].items():
for msg in track:
if msg[0] == "PgmC":
InstID = msg[1]
elif msg[0] == "NoteS":
try:
soundID, _X = (
perc_inst_to_soundID_withX(InstID)
if SpecialBits
else inst_to_sould_with_deviation(InstID)
)
except UnboundLocalError as E:
soundID, _X = (
perc_inst_to_soundID_withX(-1)
if SpecialBits
else inst_to_sould_with_deviation(-1)
)
score_now = round(msg[-1] / float(speed) / 50)
# print(score_now)
try:
tracks[score_now].append(
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
)
except KeyError:
tracks[score_now] = [
self.execute_cmd_head.format(player)
+ f"playsound {soundID} @s ^ ^ ^{1 / MaxVolume - 1} {msg[2] / 128} "
f"{2 ** ((msg[1] - 60 - _X) / 12)}"
]
all_ticks = list(tracks.keys())
all_ticks.sort()
results = []
for i in range(len(all_ticks)):
for j in range(len(tracks[all_ticks[i]])):
results.append(
(
tracks[all_ticks[i]][j],
(
0
if j != 0
else (
all_ticks[i] - all_ticks[i - 1] if i != 0 else all_ticks[i]
)
),
)
)
return [results, max(all_ticks)]
-18
View File
@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放有关Schematic结构生成的内容
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
# import nbtschematic
-22
View File
@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以生成Schematic结构的附加功能
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = [
]
__author__ = (("金羿", "Eilles"),)
from .main import *
-14
View File
@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from ..schematic import *
-21
View File
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
"""
用以启动WebSocket服务器播放的附加功能
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
__all__ = []
__author__ = (("金羿", "Eilles"),)
from .main import *
-142
View File
@@ -1,142 +0,0 @@
# -*- coding: utf-8 -*-
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
import asyncio
import time
import uuid
from typing import List, Literal, Optional, Tuple
import fcwslib
from ...main import MidiConvert
from ...subclass import MineCommand, ProgressBarStyle
def to_websocket_server(
midi_cvt_lst: List[MidiConvert],
server_dist: str,
server_port: int,
progressbar_style: Optional[ProgressBarStyle],
) -> None:
"""
将midi以延迟播放器形式转换为mcstructure结构文件后打包成附加包,并在附加包中生成相应地导入函数
Parameters
----------
midi_cvt: List[MidiConvert]
一组用于转换的MidiConvert对象
server_dist: str
WebSocket播放服务器开启地址
server_port: str
WebSocket播放服务器开启端口
progressbar_style: ProgressBarStyle 对象
进度条对象
Returns
-------
None
"""
replacement = str(uuid.uuid4())
musics = dict(
[
(k.music_name, k.to_command_list_in_delay(replacement)[:2])
for k in midi_cvt_lst
]
)
class Plugin(fcwslib.Plugin):
async def on_connect(self) -> None:
print("已成功获连接")
await self.send_command("list", callback=self.cmd_feedback)
await self.subscribe("PlayerMessage", callback=self.player_message)
async def on_disconnect(self) -> None:
print("连接已然终止")
await self.disconnect()
async def on_receive(self, response) -> None:
print("已收取非已知列回复 {}".format(response))
async def cmd_feedback(self, response) -> None:
print("已收取指令执行回复 {}".format(response))
async def player_message(self, response) -> None:
print("已收取玩家事件回复 {}".format(response))
if response["body"]["message"].startswith(("。播放", ".play")):
whom_to_play: str = response["body"]["sender"]
music_to_play: str = (
response["body"]["message"]
.replace("。播放", "")
.replace(".play", "")
.strip()
)
if music_to_play in musics.keys():
self.check_play = True
delay_of_now = 0
now_played_cmd = 0
_time = time.time()
for i in range(musics[music_to_play][1]):
if not self.check_play:
break
await asyncio.sleep((0.05 - (time.time() - _time)) % 0.05)
_time = time.time()
if progressbar_style:
await self.send_command(
"title {} actionbar {}".format(
whom_to_play,
progressbar_style.play_output(
played_delays=i,
total_delays=musics[music_to_play][1],
music_name=music_to_play,
),
),
callback=self.cmd_feedback,
)
delay_of_now += 1
if (
delay_of_now
>= (cmd := musics[music_to_play][0][now_played_cmd]).delay
):
await self.send_command(
cmd.command_text.replace(replacement, whom_to_play),
callback=self.cmd_feedback,
)
now_played_cmd += 1
delay_of_now = 0
else:
await self.send_command(
"tellraw {} {}{}{}".format(
whom_to_play,
r'{"rawtext":[{"text":"§c§l所选歌曲',
music_to_play,
'无法播放:播放列表不存在之"}]}',
),
callback=self.cmd_feedback,
)
elif response["body"]["message"].startswith(
("。停止播放", ".stopplay", ".stoplay")
):
self.check_play = False
elif response["body"]["message"].startswith(
("。终止连接", ".terminate", ".endconnection")
):
await self.disconnect()
server = fcwslib.Server(server=server_dist, port=server_port, debug_mode=True)
server.add_plugin(Plugin)
asyncio.run(server.run_forever())
-861
View File
@@ -1,861 +0,0 @@
# -*- coding: utf-8 -*-
"""
存储音·创附属子类
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
# 睿乐组织 开发交流群 861684859
# Email TriM-Organization@hotmail.com
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
from math import sin, cos, asin, radians, degrees, sqrt, atan
from dataclasses import dataclass
from typing import Optional, Any, List, Tuple, Union, Dict, Sequence
from .constants import MC_PITCHED_INSTRUMENT_LIST
@dataclass(init=False)
class MineNote:
"""存储单个音符的类"""
sound_name: str
"""乐器ID"""
note_pitch: int
"""midi音高"""
velocity: int
"""力度"""
start_tick: int
"""开始之时 命令刻"""
duration: int
"""音符持续时间 命令刻"""
high_precision_time: int
"""高精度开始时间偏量 1/1250 秒"""
percussive: bool
"""是否作为打击乐器启用"""
sound_distance: float
"""声源距离 方块"""
sound_azimuth: Tuple[float, float]
"""声源方位 角度"""
extra_info: Dict[str, Any]
"""你觉得放什么好?"""
def __init__(
self,
mc_sound_name: str,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
is_percussion: Optional[bool] = None,
distance: Optional[float] = None,
azimuth: Optional[Tuple[float, float]] = None,
extra_information: Dict[str, Any] = {},
):
"""
用于存储单个音符的类
Parameters
------------
mc_sound_name: str
《我的世界》声音ID
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
distance: float
发声源距离玩家的距离(半径 `r`)
注:距离越近,音量越高,默认为 0。此参数可以与音量成某种函数关系。
azimuth: tuple[float, float]
声源方位
注:此参数为tuple,包含两个元素,分别表示:
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
extra_information: Dict[str, Any]
附加信息,尽量存储为字典
Returns
---------
MineNote 类
"""
self.sound_name: str = mc_sound_name
"""乐器ID"""
self.note_pitch: int = 66 if midi_pitch is None else midi_pitch
"""midi音高"""
self.velocity: int = midi_velocity
"""响度(力度)"""
self.start_tick: int = start_time
"""开始之时 命令刻"""
self.duration: int = last_time
"""音符持续时间 命令刻"""
self.high_precision_time: int = mass_precision_time
"""高精度开始时间偏量 0.4 毫秒"""
self.percussive = (
(mc_sound_name not in MC_PITCHED_INSTRUMENT_LIST)
if (is_percussion is None)
else is_percussion
)
"""是否为打击乐器"""
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
)
"""声源距离"""
self.extra_info = extra_information if extra_information else {}
@classmethod
def from_traditional(
cls,
mc_sound_name: str,
midi_pitch: Optional[int],
midi_velocity: int,
start_time: int,
last_time: int,
mass_precision_time: int = 0,
is_percussion: Optional[bool] = None,
displacement: Optional[Tuple[float, float, float]] = None,
extra_information: Optional[Any] = None,
):
"""
从传统音像位移格式传参,写入用于存储单个音符的类
Parameters
------------
mc_sound_name: str
《我的世界》声音ID
midi_pitch: int
midi音高
midi_velocity: int
midi响度(力度)
start_time: int
开始之时(命令刻)
注:此处的时间是用从乐曲开始到当前的刻数
last_time: int
音符延续时间(命令刻)
mass_precision_time: int
高精度的开始时间偏移量(1/1250秒)
is_percussion: bool
是否作为打击乐器
displacement: tuple[float, float, float]
声像位移
extra_information: Any
附加信息,尽量为字典。
Returns
---------
MineNote 类
"""
if displacement is None:
displacement = (0, 0, 0)
r = 0
alpha_v = 0
beta_h = 0
else:
r = sqrt(displacement[0] ** 2 + displacement[1] ** 2 + displacement[2] ** 2)
if r == 0:
alpha_v = 0
beta_h = 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(
mc_sound_name=mc_sound_name,
midi_pitch=midi_pitch,
midi_velocity=midi_velocity,
start_time=start_time,
last_time=last_time,
mass_precision_time=mass_precision_time,
is_percussion=is_percussion,
distance=r,
azimuth=(alpha_v, beta_h),
extra_information=(
(
extra_information
if isinstance(extra_information, dict)
else {"EXTRA_INFO": extra_information}
)
if extra_information
else {}
),
)
@property
def position_displacement(self) -> Tuple[float, float, float]:
"""声像位移"""
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),
)
@classmethod
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
"""自字节码析出 MineNote 类"""
group_1 = int.from_bytes(code_buffer[:6], "big")
percussive_ = bool(group_1 & 0b1)
duration_ = (group_1 := group_1 >> 1) & 0b11111111111111111
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
note_pitch_ = (group_1 := group_1 >> 17) & 0b1111111
sound_name_length = group_1 >> 7
if code_buffer[6] & 0b1:
distance_ = (
code_buffer[8 + sound_name_length]
if is_high_time_precision
else code_buffer[7 + sound_name_length]
) / 15
group_2 = int.from_bytes(
(
code_buffer[9 + sound_name_length : 14 + sound_name_length]
if is_high_time_precision
else code_buffer[8 + sound_name_length : 13 + sound_name_length]
),
"big",
)
azimuth_ = ((group_2 >> 20) / 2912, (group_2 & 0xFFFFF) / 2912)
else:
distance_ = 0
azimuth_ = (0, 0)
try:
return cls(
mc_sound_name=(
o := (
code_buffer[8 : 8 + sound_name_length]
if is_high_time_precision
else code_buffer[7 : 7 + sound_name_length]
)
).decode(encoding="GB18030"),
midi_pitch=note_pitch_,
midi_velocity=code_buffer[6] >> 1,
start_time=start_tick_,
last_time=duration_,
mass_precision_time=code_buffer[7] if is_high_time_precision else 0,
is_percussion=percussive_,
distance=distance_,
azimuth=azimuth_,
)
except:
print(code_buffer, "\n", o)
raise
def encode(
self, is_displacement_included: bool = True, is_high_time_precision: bool = True
) -> bytes:
"""
将数据打包为字节码
Parameters
------------
is_displacement_included: bool
是否包含声像偏移数据,默认为**是**
is_high_time_precision: bool
是否启用高精度,默认为**是**
Returns
---------
bytes
打包好的字节码
"""
# MineNote 的字节码共有三个顺次版本分别如下
# 字符串长度 6 位 支持到 63
# note_pitch 7 位 支持到 127
# start_tick 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# duration 17 位 支持到 131071 即 109.22583 分钟 合 1.8204305 小时
# percussive 长度 1 位 支持到 1
# 共 48 位 合 6 字节
# +++
# velocity 长度 7 位 支持到 127
# is_displacement_included 长度 1 位 支持到 1
# 共 8 位 合 1 字节
# +++
# (在第二版中已舍弃)
# track_no 长度 8 位 支持到 255 合 1 字节
# (在第二版中新增)
# high_time_precision(可选)长度 8 位 支持到 255 合 1 字节 支持 1/1250 秒
# +++
# sound_name 长度最多 63 支持到 31 个中文字符 或 63 个西文字符
# 第一版编码: UTF-8
# 第二版编码: GB18030
# +++
# (在第三版中已废弃)
# position_displacement 每个元素长 16 位 合 2 字节
# 共 48 位 合 6 字节 支持存储三位小数和两位整数,其值必须在 [0, 65.535] 之间
# (在第三版中新增)
# sound_distance 8 位 支持到 255 即 16 格 合 1 字节(按值放大 15 倍存储,精度可达 1 / 15)
# sound_azimuth 每个元素长 20 位 共 40 位 合 5 字节。每个值放大 2912 倍存储,即支持到 360.08756868131866 度,精度同理
return (
(
(
(
(
(
(
(
(
len(
r := self.sound_name.encode(
encoding="GB18030"
)
)
<< 7
)
+ self.note_pitch
)
<< 17
)
+ self.start_tick
)
<< 17
)
+ self.duration
)
<< 1
)
+ self.percussive
).to_bytes(6, "big")
+ ((self.velocity << 1) + is_displacement_included).to_bytes(1, "big")
# + self.track_no.to_bytes(1, "big")
+ (
self.high_precision_time.to_bytes(1, "big")
if is_high_time_precision
else b""
)
+ r
+ (
(
round(self.sound_distance * 15).to_bytes(1, "big")
+ (
(round(self.sound_azimuth[0] * 2912) << 20)
+ round(self.sound_azimuth[1] * 2912)
).to_bytes(5, "big")
)
if is_displacement_included
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 TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value))
def get_info(self, key: str) -> Any:
"""获取附加信息"""
if key in self.extra_info:
return self.extra_info[key]
elif "EXTRA_INFO" in self.extra_info:
if (
isinstance(self.extra_info["EXTRA_INFO"], dict)
and key in self.extra_info["EXTRA_INFO"]
):
return self.extra_info["EXTRA_INFO"].get(key)
else:
return self.extra_info["EXTRA_INFO"]
else:
return None
def stringize(
self, include_displacement: bool = False, include_extra_data: bool = False
) -> str:
return (
"{}Note(Instrument = {}, {}Velocity = {}, StartTick = {}, Duration = {}{}".format(
"Percussive" if self.percussive else "",
self.sound_name,
"" if self.percussive else "NotePitch = {}, ".format(self.note_pitch),
self.velocity,
self.start_tick,
self.duration,
)
+ (
", SoundDistance = `r`{}, SoundAzimuth = (`αV`{}, `βH`{})".format(
self.sound_distance, *self.sound_azimuth
)
if include_displacement
else ""
)
+ (", ExtraData = {}".format(self.extra_info) if include_extra_data else "")
+ ")"
)
def tuplize(self, is_displacement: bool = False):
tuplized = self.__tuple__()
return tuplized[:-2] + (tuplized[-2:] if is_displacement else ())
def __list__(self) -> List:
return (
[
self.percussive,
self.sound_name,
self.velocity,
self.start_tick,
self.duration,
self.sound_distance,
self.sound_azimuth,
]
if self.percussive
else [
self.percussive,
self.sound_name,
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.sound_distance,
self.sound_azimuth,
]
)
def __tuple__(
self,
) -> Union[
Tuple[bool, str, int, int, int, int, float, Tuple[float, float]],
Tuple[bool, str, int, int, int, float, Tuple[float, float]],
]:
return (
(
self.percussive,
self.sound_name,
self.velocity,
self.start_tick,
self.duration,
self.sound_distance,
self.sound_azimuth,
)
if self.percussive
else (
self.percussive,
self.sound_name,
self.note_pitch,
self.velocity,
self.start_tick,
self.duration,
self.sound_distance,
self.sound_azimuth,
)
)
def __dict__(self):
return (
{
"Percussive": self.percussive,
"Instrument": self.sound_name,
"Velocity": self.velocity,
"StartTick": self.start_tick,
"Duration": self.duration,
"SoundDistance": self.sound_distance,
"SoundAzimuth": self.sound_azimuth,
"ExtraData": self.extra_info,
}
if self.percussive
else {
"Percussive": self.percussive,
"Instrument": self.sound_name,
"Pitch": self.note_pitch,
"Velocity": self.velocity,
"StartTick": self.start_tick,
"Duration": self.duration,
"SoundDistance": self.sound_distance,
"SoundAzimuth": self.sound_azimuth,
"ExtraData": self.extra_info,
}
)
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.tuplize() == other.tuplize()
@dataclass(init=False)
class MineCommand:
"""存储单个指令的类"""
command_text: str
"""指令文本"""
conditional: bool
"""执行是否有条件"""
delay: int
"""执行的延迟"""
annotation_text: str
"""指令注释"""
def __init__(
self,
command: str,
condition: bool = False,
tick_delay: int = 0,
annotation: str = "",
):
"""
存储单个指令的类
Parameters
----------
command: str
指令
condition: bool
是否有条件
tick_delay: int
执行延时
annotation: str
注释
"""
self.command_text = command
self.conditional = condition
self.delay = tick_delay
self.annotation_text = annotation
def copy(self):
return MineCommand(
command=self.command_text,
condition=self.conditional,
tick_delay=self.delay,
annotation=self.annotation_text,
)
@property
def cmd(self) -> str:
"""
我的世界函数字符串(包含注释)
"""
return self.__str__()
def __str__(self) -> str:
"""
转为我的世界函数文件格式(包含注释)
"""
return "# {cdt}<{delay}> {ant}\n{cmd}".format(
cdt="[CDT]" if self.conditional else "",
delay=self.delay,
ant=self.annotation_text,
cmd=self.command_text,
)
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class SingleNoteBox:
"""存储单个音符盒"""
instrument_block: str
"""乐器方块"""
note_value: int
"""音符盒音高"""
annotation_text: str
"""音符注释"""
is_percussion: bool
"""是否为打击乐器"""
def __init__(
self,
instrument_block_: str,
note_value_: int,
percussion: Optional[bool] = None,
annotation: str = "",
):
"""
用于存储单个音符盒的类
Parameters
------------
instrument_block_: str
音符盒演奏所使用的乐器方块
note_value_: int
音符盒的演奏音高
percussion: bool
此音符盒乐器是否作为打击乐处理
注:若为空,则自动识别是否为打击乐器
annotation: Any
音符注释
Returns
---------
SingleNoteBox 类
"""
self.instrument_block = instrument_block_
"""乐器方块"""
self.note_value = note_value_
"""音符盒音高"""
self.annotation_text = annotation
"""音符注释"""
if percussion is None:
self.is_percussion = percussion not in MC_PITCHED_INSTRUMENT_LIST
else:
self.is_percussion = percussion
@property
def inst(self) -> str:
"""获取音符盒下的乐器方块"""
return self.instrument_block
@inst.setter
def inst(self, inst_):
self.instrument_block = inst_
@property
def note(self) -> int:
"""获取音符盒音调特殊值"""
return self.note_value
@note.setter
def note(self, note_):
self.note_value = note_
@property
def annotation(self) -> str:
"""获取音符盒的备注"""
return self.annotation_text
@annotation.setter
def annotation(self, annotation_):
self.annotation_text = annotation_
def copy(self):
return SingleNoteBox(
instrument_block_=self.instrument_block,
note_value_=self.note_value,
annotation=self.annotation_text,
)
def __str__(self) -> str:
return f"Note(inst = {self.inst}, note = {self.note}, )"
def __tuple__(self) -> tuple:
return self.inst, self.note, self.annotation
def __dict__(self) -> dict:
return {
"inst": self.inst,
"note": self.note,
"annotation": self.annotation,
}
def __eq__(self, other) -> bool:
if not isinstance(other, self.__class__):
return False
return self.__str__() == other.__str__()
@dataclass(init=False)
class ProgressBarStyle:
"""进度条样式类"""
base_style: str
"""基础样式"""
to_play_style: str
"""未播放之样式"""
played_style: str
"""已播放之样式"""
def __init__(
self,
base_s: Optional[str] = None,
to_play_s: Optional[str] = None,
played_s: Optional[str] = None,
):
"""
用于存储进度条样式的类
| 标识符 | 指定的可变量 |
|---------|----------------|
| `%%N` | 乐曲名(即传入的文件名)|
| `%%s` | 当前计分板值 |
| `%^s` | 计分板最大值 |
| `%%t` | 当前播放时间 |
| `%^t` | 曲目总时长 |
| `%%%` | 当前进度比率 |
| `_` | 用以表示进度条占位|
Parameters
------------
base_s: str
基础样式,用以定义进度条整体
to_play_s: str
进度条样式:尚未播放的样子
played_s: str
已经播放的样子
Returns
---------
ProgressBarStyle 类
"""
self.base_style = (
base_s if base_s else r"%%N [ %%s/%^s %%% §e__________§r %%t|%^t ]"
)
self.to_play_style = to_play_s if to_play_s else r"§7="
self.played_style = played_s if played_s else r"="
@classmethod
def from_tuple(cls, tuplized_style: Optional[Tuple[str, Tuple[str, str]]]):
"""自旧版进度条元组表示法读入数据(已不建议使用)"""
if tuplized_style is None:
return cls(
r"%%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
r"§7=",
r"=",
)
if isinstance(tuplized_style, tuple):
if isinstance(tuplized_style[0], str) and isinstance(
tuplized_style[1], tuple
):
if isinstance(tuplized_style[1][0], str) and isinstance(
tuplized_style[1][1], str
):
return cls(
tuplized_style[0], tuplized_style[1][0], tuplized_style[1][1]
)
raise ValueError(
"元组表示的进度条样式组 {} 格式错误,已不建议使用此功能,请尽快更换。".format(
tuplized_style
)
)
def set_base_style(self, value: str):
"""设置基础样式"""
self.base_style = value
def set_to_play_style(self, value: str):
"""设置未播放之样式"""
self.to_play_style = value
def set_played_style(self, value: str):
"""设置已播放之样式"""
self.played_style = value
def copy(self):
dst = ProgressBarStyle(self.base_style, self.to_play_style, self.played_style)
return dst
def play_output(
self,
played_delays: int,
total_delays: int,
music_name: str = "无题",
) -> str:
"""
直接依照此格式输出一个进度条
Parameters
------------
played_delays: int
当前播放进度积分值
total_delays: int
乐器总延迟数(计分板值)
music_name: str
曲名
Returns
---------
str
进度条字符串
"""
return (
self.base_style.replace(r"%%N", music_name)
.replace(r"%%s", str(played_delays))
.replace(r"%^s", str(total_delays))
.replace(r"%%t", mctick2timestr(played_delays))
.replace(r"%^t", mctick2timestr(total_delays))
.replace(
r"%%%",
"{:0>5.2f}%".format(int(10000 * played_delays / total_delays) / 100),
)
.replace(
"_",
self.played_style,
(played_delays * self.base_style.count("_") // total_delays) + 1,
)
.replace("_", self.to_play_style)
)
def mctick2timestr(mc_tick: int) -> str:
"""
将《我的世界》的游戏刻计转为表示时间的字符串
"""
return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60)
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle(
r"%%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
r"§7=",
r"=",
)
"""
默认的进度条样式
"""
-73
View File
@@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
"""
存放数据类型的定义
"""
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 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
from .subclass import MineNote
MidiNoteNameTableType = Mapping[int, Tuple[str, ...]]
"""
Midi音符名称对照表类型
"""
MidiInstrumentTableType = Mapping[int, str]
"""
Midi乐器对照表类型
"""
FittingFunctionType = Callable[[float], float]
"""
拟合函数类型
"""
ChannelType = Dict[
int,
Dict[
int,
List[
Union[
Tuple[Literal["PgmC"], int, int],
Tuple[Literal["NoteS"], int, int, int],
Tuple[Literal["NoteE"], int, int],
]
],
],
]
"""
以字典所标记的通道信息类型(已弃用)
Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],]
"""
MineNoteChannelType = Mapping[
int,
List[MineNote,],
]
"""
我的世界通道信息类型
Dict[int,Dict[int,List[MineNote,],],]
"""
MineNoteTrackType = Mapping[
int,
List[MineNote,],
]
-1029
View File
File diff suppressed because it is too large Load Diff
-68
View File
@@ -1,68 +0,0 @@
import Musicreater
import Musicreater.experiment
import Musicreater.plugin
# import Musicreater.previous
from Musicreater.plugin.addonpack import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
)
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
from Musicreater.plugin.mcstructfile import (
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
)
MSCT_MAIN = (
Musicreater,
Musicreater.experiment,
# Musicreater.previous,
)
MSCT_PLUGIN = (Musicreater.plugin,)
MSCT_PLUGIN_FUNCTION = (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
to_BDX_file_in_delay,
to_BDX_file_in_score,
)
import hashlib
import brotli
import dill
def enpack_msct_pack(sth, to_dist: str):
packing_bytes = brotli.compress(
dill.dumps(
sth,
)
)
with open(
to_dist,
"wb",
) as f:
f.write(packing_bytes)
return hashlib.sha256(packing_bytes)
with open("./Packer/checksum.txt", "w", encoding="utf-8") as f:
f.write("MSCT_MAIN:\n")
f.write(enpack_msct_pack(MSCT_MAIN, "./Packer/MSCT_MAIN.MPK").hexdigest())
f.write("\nMSCT_PLUGIN:\n")
f.write(enpack_msct_pack(MSCT_PLUGIN, "./Packer/MSCT_PLUGIN.MPK").hexdigest())
f.write("\nMSCT_PLUGIN_FUNCTION:\n")
f.write(
enpack_msct_pack(
MSCT_PLUGIN_FUNCTION, "./Packer/MSCT_PLUGIN_FUNCTION.MPK"
).hexdigest()
)
+24 -136
View File
@@ -1,136 +1,24 @@
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
[Bilibili: 诸葛亮与八卦阵]: 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-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://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: 诸葛亮与八卦阵]](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
```bash
pip install --upgrade Musicreater
```
- 如果无法更新最新,可以尝试:
```bash
pip install --upgrade -i https://pypi.python.org/simple Musicreater
```
- 克隆仓库并安装(最新版本但**不推荐**)
```bash
git clone https://gitee.com/TriM-Organization/Musicreater.git
cd Musicreater
python setup.py install
```
以上命令中 `python`、`pip` 请依照各个环境不同灵活更换,可能为`python3`或`pip3`之类。
## 文档 📄
[生成文件的使用](./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)
## 作者 ✒
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
**诸葛亮与八卦阵 bgArray**:我的世界基岩版玩家,喜欢编程和音乐,深圳学生。
**偷吃不是Touch Touch**:我的世界基岩版指令制作者,提供测试支持
## 致谢 🙏
本致谢列表排名无顺序。
- 感谢 **昀梦**\<QQ1515399885\> 找出指令生成错误 bug 并指正
- 感谢由 **Charlie_Ping “查理平”** 带来的 BDX 文件转换参考,以及 MIDI-我的世界对应乐器 参考表格
- 感谢由 **[CMA_2401PT](https://github.com/CMA2401PT)** 为我们的软件开发的一些方面进行指导,同时我们参考了他的 BDXworkshop 作为 BDX 结构编辑的参考
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发。
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
- 感谢 **小埋**\<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) 项目的开发为我们提供持续的追赶动力。
> 感谢广大群友为此库提供的测试和建议等
> 若您对我们有所贡献但您的名字没有出现在此列表中,请联系我们!
## 联系 📞
若遇到库中的问题,欢迎在[](https://gitee.com/TriM-Organization/Musicreater/issues/new)提出你的 issue。
如果需要与开发组进行交流,欢迎加入我们的[开发闲聊 Q 群](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)。
亦可以联系我们[睿乐组织官方邮箱](mailto:TriM-Organization@hotmail.com),取得进一步联系!
---
此项目并非一个官方 《我的世界》(_Minecraft_)项目
此项目不隶属或关联于 Mojang Studios 或 微软
此项目亦不隶属或关联于 网易
“Minecraft”是 Mojang Synergies AB 的商标,此项目中所有对于“我的世界”、“Minecraft”等相关称呼均为必要的介绍性使用
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易璀璨网络科技有限公司
NOT AN OFFICIAL MINECRAFT PRODUCT.
NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.
NOT APPROVED BY OR ASSOCIATED WITH NETEASE.
# ***NOTICE*** 请注意
## ***FOR UPDATING FILES IN HERE(GitHub)*** 此处编辑请注意
> We have setted a automatic mirror between Gitee(a Chinese native Code Hosting Platform).
> That means you should check whether there is another update on Gitee if you want to get file(s) updated on GitHub.
> If a update is pushed on Gitee and not been pushed into GitHub yet,
> A push will **COVERED** by the another one, which branch to deleted is under indeterminacy, maybe both destoried is possible.
> So we STRONG suggest not to push updates here in GitHub.
> But if you want to, make sure that in 30min, there is NOT ANY pushes in this repository on Gitee.
>
> 由于我们建立了对于 Gitee 和 GitHub 之间的双向更新镜像,
> 如果您需要在此处(GitHub处)进行更新之前请三思,我们非常建议你在 Gitee 上更新而不是 GitHub。
> 就我个人而言,仅仅是使用 GitHub 是非常愚蠢的,特别是因为使用的代码托管平台不同而放弃对于一个项目的热情更是令人不可理喻。
> 这种崇洋媚外的心理是不当的,尽管你会说,GitHub 在某些方面做的确实比 Gitee 好太多;但是也要考虑另外的一些方面,就是我们这个小项目用的用不上的问题。
> 如果需要切换一个主要的代码托管平台,我们会有自己的选择;但正如其名,代码托管平台只是托管一下代码,我认为我有必要自行地决定我的智慧结晶使用如何的服务。
> 况且,使用的服务是独立于代码托管之外的,并不妨碍你在哪更新,除非你一年四季开着 VPN 而访问不上 Gitee,不过这几乎不可能,因为 Gitee 的服务器是阿里巴巴的。
> 言归正传,在 GitHub 更新之前,你需要首先检查:在30分钟内是否有其他的更新在 Gitee 上提出,如果有则不要在此时提交更新。
> 否则会产生非常严重的后果,运气好可以合到一起去,运气不好,完蛋了,有可能两个更新**全都没了**。
IF YOU WANT TO TAKE PART IN THE DEVELOPMENT OF THIS PROJECT, PLEASE CONTACT EILLES,
and [Musicreater on Gitee](https://gitee.com/EillesWan/Musicreater/) is here, if you like to update here on GitHub, you **MUST** check it before.
若欲加入开发,联系金羿非常重要,[Gitee上的音·创](https://gitee.com/EillesWan/Musicreater/)也在这里,如果你一意孤行想要在这里更新的话,**必须、一定、确保**看过了Gitee上的更新状况。
-138
View File
@@ -1,138 +0,0 @@
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
[Bilibili: bgArray]: 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-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 open-source library of <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: bgArray]](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 NOT be up-to-date.**
## Introduction🚀
Musicreater is a free open-source library used for digital music that being 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_
**bgArray (诸葛亮与八卦阵)**: 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”等相关称呼均为必要的介绍性使用
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易璀璨网络科技有限公司
-22
View File
@@ -1,22 +0,0 @@
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
View File
@@ -1,13 +0,0 @@
<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
-315
View File
@@ -1,315 +0,0 @@
<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",
)
```
*为了避免生成错误,请尽量避免使用标识符作为定义样式字符串的其他部分*
-56
View File
@@ -1,56 +0,0 @@
<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. 若想要重置某实体的播放,可以将其播放用的计分板重置
-222
View File
@@ -1,222 +0,0 @@
<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 |
-149
View File
@@ -1,149 +0,0 @@
# 音乐序列文件格式
音·创 库的音符序列文件格式包含两种,一种是常规的音乐序列存储采用的 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 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 x、y、z 轴上的偏移,每个值支持数值 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 校验值,以 全曲音符总数 作为种子值。
-248
View File
@@ -1,248 +0,0 @@
# -*- coding: utf-8 -*-
# 伶伦 开发交流群 861684859
"""
音·创 (Musicreater) 演示程序
是一款免费开源的针对《我的世界》的midi音乐转换库
Musicreater (音·创)
A free open source library used for convert midi file into formats that is suitable for **Minecraft**.
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 ./License.md
Terms & Conditions: ./License.md
"""
import os
import Musicreater
from Musicreater.plugin.addonpack import (
to_addon_pack_in_delay,
to_addon_pack_in_repeater,
to_addon_pack_in_score,
)
from Musicreater.plugin.mcstructfile import (
to_mcstructure_file_in_delay,
to_mcstructure_file_in_repeater,
to_mcstructure_file_in_score,
)
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
# 获取midi列表
midi_path = input(f"请输入MIDI路径:")
# 获取输出地址
out_path = input(f"请输入输出路径:")
# 选择输出格式
fileFormat = int(
input(f"请输入输出格式[MCSTRUCTURE(2) 或 BDX(1) 或 MCPACK(0)]").lower()
)
playerFormat = int(input(f"请选择播放方式[红石(2) 或 计分板(1) 或 延迟(0)]").lower())
# 真假字符串判断
def bool_str(sth: str):
try:
return bool(float(sth))
except:
if str(sth).lower() in ("true", "", "", "y", "t"):
return True
elif str(sth).lower() in ("false", "", "", "f", "n"):
return False
else:
raise ValueError("非法逻辑字串")
def isin(sth: str, range_list: dict):
sth = sth.lower()
for bool_value, res_list in range_list.items():
if sth in res_list:
return bool_value
raise ValueError(
"不在可选范围内:{}".format([j for i in range_list.values() for j in i])
)
if os.path.exists("./demo_config.json"):
import json
prompts = json.load(open("./demo_config.json", "r", encoding="utf-8"))
else:
prompts = []
# 提示语 检测函数 错误提示语
for args in [
(
f"最小播放音量:",
float,
),
(
f"播放速度:",
float,
),
(
f"是否启用进度条:",
bool_str,
),
(
(
f"计分板名称:",
str,
)
if playerFormat == 1
else (
f"玩家选择器:",
str,
)
),
(
(
f"是否自动重置计分板:",
bool_str,
)
if playerFormat == 1
else ()
),
(
(
f"BDX作者署名:",
str,
)
if fileFormat == 1
else (
(
"结构延展方向:",
lambda a: isin(
a,
{
"z+": ["z+", "Z+"],
"x+": ["X+", "x+"],
"z-": ["Z-", "z-"],
"x-": ["x-", "X-"],
},
),
)
if (playerFormat == 2 and fileFormat == 2)
else ()
)
),
(
()
if playerFormat == 1
else (
(
"基础空白方块:",
str,
)
if (playerFormat == 2 and fileFormat == 2)
else (
f"最大结构高度:",
int,
)
)
),
]:
if args:
try:
prompts.append(args[1](input(args[0])))
except Exception:
print(args)
print(f"正在处理 {midi_path} ")
cvt_mid = Musicreater.MidiConvert.from_midi_file(
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
)
if fileFormat == 0:
if playerFormat == 1:
cvt_method = to_addon_pack_in_score
elif playerFormat == 0:
cvt_method = to_addon_pack_in_delay
elif playerFormat == 2:
cvt_method = to_addon_pack_in_repeater
elif fileFormat == 2:
if playerFormat == 1:
cvt_method = to_mcstructure_file_in_score
elif playerFormat == 0:
cvt_method = to_mcstructure_file_in_delay
elif playerFormat == 2:
cvt_method = to_mcstructure_file_in_repeater
# 测试
# print(cvt_mid)
print(
" 指令总长:{},最高延迟:{}".format(
*(
cvt_method(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
*prompts[3:],
)
)
)
if fileFormat == 0
else (
" 指令总长:{},最高延迟:{},结构大小{},终点坐标{}".format(
*(
to_BDX_file_in_score(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
*prompts[3:],
)
if playerFormat == 1
else to_BDX_file_in_delay(
cvt_mid,
out_path,
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
*prompts[3:],
)
)
)
if fileFormat == 1
else (
" 结构大小:{},延迟总数:{},指令数量:{}".format(
*(
cvt_method(
cvt_mid,
out_path,
*prompts[3:],
)
)
)
if playerFormat == 1
else " 结构大小:{},延迟总数:{}".format(
*(
cvt_method(
cvt_mid,
out_path,
# Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
*prompts[3:],
)
)
)
)
)
)
exitSth = input("回车退出").lower()
if exitSth == "record":
import json
with open("./demo_config.json", "w", encoding="utf-8") as f:
json.dump(prompts, f)
elif exitSth == "delrec":
os.remove("./demo_config.json")
-14
View File
@@ -1,14 +0,0 @@
import Musicreater.experiment
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
print(
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
Musicreater.experiment.FutureMidiConvertM4.from_midi_file(
input("midi路径:"), old_exe_format=False
),
input("输出路径:"),
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
)
)
-16
View File
@@ -1,16 +0,0 @@
import Musicreater
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
print(
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
Musicreater.MidiConvert.from_midi_file(
input("midi路径:"),
old_exe_format=False,
# note_table_replacement={"note.harp": "note.flute"},
),
input("输出路径:"),
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
# max_height=32,
)
)
-24
View File
@@ -1,24 +0,0 @@
import Musicreater
import Musicreater.plugin
import Musicreater.plugin.websocket
import os
dire = input("midi目录:")
print(
Musicreater.plugin.websocket.to_websocket_server(
[
Musicreater.MidiConvert.from_midi_file(
os.path.join(dire, names), old_exe_format=False
)
for names in os.listdir(
dire,
)
if names.endswith((".mid", ".midi"))
],
input("服务器地址:"),
int(input("服务器端口:")),
Musicreater.DEFAULT_PROGRESSBAR_STYLE,
)
)
-163
View File
@@ -1,163 +0,0 @@
"""
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
Copyright © 2025 Eilles & bgArray
开源相关声明请见 仓库根目录下的 License.md
Terms & Conditions: License.md in the root directory
"""
import os
import shutil
from typing import Optional, Tuple
import Musicreater.experiment
from Musicreater.plugin.archive import compress_zipfile
from Musicreater.utils import guess_deviation, is_in_diapason
def to_zip_pack_in_score(
midi_cvt: Musicreater.experiment.FutureMidiConvertJavaE,
dist_path: str,
progressbar_style: Optional[Musicreater.experiment.ProgressBarStyle],
scoreboard_name: str = "mscplay",
sound_source: str = "ambient",
auto_reset: bool = False,
) -> Tuple[int, int]:
"""
将midi以计分播放器形式转换为我的世界函数附加包
Parameters
----------
midi_cvt: MidiConvert 对象
用于转换的MidiConvert对象
dist_path: str
转换结果输出的目标路径
progressbar_style: ProgressBarStyle 对象
进度条对象
scoreboard_name: str
我的世界的计分板名称
auto_reset: bool
是否自动重置计分板
Returns
-------
tuple[int指令数量, int音乐总延迟]
"""
cmdlist, maxlen, maxscore = midi_cvt.to_command_list_in_java_score(
scoreboard_name=scoreboard_name,
source_of_sound=sound_source,
)
# 当文件f夹{self.outputPath}/temp/mscplyfuncs存在时清空其下所有项目,然后创建
if os.path.exists(f"{dist_path}/temp/mscplyfuncs/"):
shutil.rmtree(f"{dist_path}/temp/mscplyfuncs/")
os.makedirs(f"{dist_path}/temp/mscplyfuncs/mscplay")
# 写入stop.mcfunction
with open(
f"{dist_path}/temp/mscplyfuncs/stop.mcfunction", "w", encoding="utf-8"
) as f:
f.write("scoreboard players reset @a {}".format(scoreboard_name))
# 将命令列表写入文件
index_file = open(
f"{dist_path}/temp/mscplyfuncs/index.mcfunction", "w", encoding="utf-8"
)
for i in range(len(cmdlist)):
index_file.write(f"function mscplyfuncs:mscplay/track{i + 1}\n")
with open(
f"{dist_path}/temp/mscplyfuncs/mscplay/track{i + 1}.mcfunction",
"w",
encoding="utf-8",
) as f:
f.write("\n".join([single_cmd.cmd for single_cmd in cmdlist[i]]))
index_file.writelines(
(
"scoreboard players add @a[score_{0}_min=1] {0} 1\n".format(
scoreboard_name
),
(
"scoreboard players reset @a[score_{0}_min={1}] {0}\n".format(
scoreboard_name, maxscore + 20
)
if auto_reset
else ""
),
f"function mscplyfuncs:mscplay/progressShow\n" if progressbar_style else "",
)
)
if progressbar_style:
with open(
f"{dist_path}/temp/mscplyfuncs/mscplay/progressShow.mcfunction",
"w",
encoding="utf-8",
) as f:
f.writelines(
"\n".join(
[
single_cmd.cmd
for single_cmd in midi_cvt.form_java_progress_bar(
maxscore, scoreboard_name, progressbar_style
)
]
)
)
index_file.close()
if os.path.exists(f"{dist_path}/{midi_cvt.music_name}.zip"):
os.remove(f"{dist_path}/{midi_cvt.music_name}.zip")
compress_zipfile(
f"{dist_path}/temp/",
f"{dist_path}/{midi_cvt.music_name}[JEscore].zip",
)
shutil.rmtree(f"{dist_path}/temp/")
return maxlen, maxscore
msc_cvt = Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file(
input("midi路径:"),
play_speed=float(input("播放速度:")),
old_exe_format=True,
note_table_replacement=Musicreater.MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
)
msc_cvt.set_deviation(
guess_deviation(
msc_cvt.total_note_count,
len(msc_cvt.note_count_per_instrument),
msc_cvt.note_count_per_instrument,
music_channels=msc_cvt.channels,
)
)
in_diapason_count = 0
for this_note in [k for j in msc_cvt.channels.values() for k in j]:
if is_in_diapason(
this_note.note_pitch + msc_cvt.music_deviation, this_note.sound_name
):
in_diapason_count += 1
zip_res = to_zip_pack_in_score(
msc_cvt,
input("输出路径:"),
Musicreater.experiment.ProgressBarStyle(),
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
scoreboard_name=input("计分板名称:"),
sound_source=input("发音源:"),
auto_reset=True,
)
print(
"符合音符播放音高的音符数量:{}/{}({:.2f}%)".format(
in_diapason_count,
msc_cvt.total_note_count,
in_diapason_count * 100 / msc_cvt.total_note_count,
),
"\n指令数量:{};音乐总延迟:{}".format(*zip_res),
)
-98
View File
@@ -1,98 +0,0 @@
[project]
name = "Musicreater"
dynamic = ["version"]
requires-python = ">= 3.8, < 4.0"
dependencies = [
"mido >= 1.3",
"xxhash >= 3",
]
authors = [
{ name = "金羿Eilles" },
{ name = "玉衡Alioth" },
{ name = "鱼旧梦ElapsingDreams" },
{ name = "睿乐组织 TriMO", email = "TriM-Organization@hotmail.com" },
]
maintainers = [
{ name = "金羿Eilles", email = "EillesWan@outlook.com" },
]
description = "A free open source library used for dealing with **Minecraft** digital musics."
readme = "README_EN.md"
license = { file = "LICENSE.md" }
keywords = ["midi", "minecraft", "minecraft: bedrock edition"]
classifiers = [
"Intended Audience :: Developers",
"Natural Language :: Chinese (Simplified)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Topic :: Multimedia",
"Topic :: Multimedia :: Sound/Audio :: MIDI",
]
[project.urls]
# Homepage = "https://example.com"
# Documentation = "https://readthedocs.org"
Repository = "https://gitee.com/TriM-Organization/Musicreater"
Issues = "https://gitee.com/TriM-Organization/Musicreater/issues"
Mirror-Repository = "https://github.com/TriM-Organization/Musicreater"
Mirror-Issues = "https://github.com/TriM-Organization/Musicreater/issues"
[project.optional-dependencies]
full = [
"TrimMCStruct <= 0.0.5.9",
"brotli >= 1.0.0",
]
dev = [
"TrimMCStruct <= 0.0.5.9",
"brotli >= 1.0.0",
"dill",
"rich",
"pyinstaller",
"twine",
]
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
# https://backend.pdm-project.org/build_config/#build-configurations
[tool.pdm.build]
# includes = [
# # "README_EN.md",
# # "README.md",
# # "LICENSE.md",
# # "Musicreater/",
# # "docs/",
# ]
source-includes = [
"README_EN.md",
"README.md",
"LICENSE.md",
]
excludes = [
"fcwslib/",
"bgArrayLib/",
"Packer/",
"resources/",
"./*.mid",
"./*.msq",
"./*.fsq",
"./MSCT_Packer.py",
"resources/poem.md",
]
[tool.pdm.version]
source = "file"
path = "Musicreater/__init__.py"
[tool.pyright]
typeCheckingMode = "basic"
-21
View File
@@ -1,21 +0,0 @@
# 注意,这里是作者署名文件,文件格式开头为单子启
# 紧跟其后,不加空格留下常用名,常用名即常用网名
# 而在其后是各个语言下的名字。用 井字符 开头表示
# 注释,请注意,注释符号必须在一行之首否则无作用
# 每进行一次分段表示一个新的开发者,换行表示一个
# 新的语言。请全体开发者就此署名,谢谢!
启金羿
zh-CN 金羿
zh-TW 金羿
zh-ME 金羿羿喵
zh-HK 金 羿
en-GB Eilles
en-US EillesWan
启诸葛亮与八卦阵
zh-CN 诸葛亮与八卦阵
zh-TW 諸葛亮與八卦陣
zh-ME 诸葛八卦喵
zh-HK 諸葛亮與八卦陣
en-GB Bagua Array
en-US bgArray
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 MiB

-47
View File
@@ -1,47 +0,0 @@
> 是谁把科技的领域布满政治的火药
>
> 是谁把纯净的蓝天染上暗淡的沉灰
>
> 中国人民无不热爱自己伟大的祖国
>
> 我们不会忘记屈辱历史留下的惨痛
>
> 我们希望世界和平
>
> 我们希望获得世界的尊重
>
> 愿世上再也没有战争
>
> 无论是热还是冷
>
> 无论是经济还是政治
>
> 让美妙的和平的优雅的音乐响彻世界
>
> ——金羿
> 2022 5 7
> Who has dropped political gunpowder into the technology
>
> Who has dyed clear blue sky into the dark grey
>
> All Chinese people love our great homeland
>
> We *WILL* remember the remain pain of the humiliating history
>
> We love the whole world but in peace
>
> We love everyone but under respect
>
> It is to be hoped that the war ends forever
>
> Whatever it is cold or hot
>
> Whatever it is economical or political
>
> Just let the wonderful music of peace surround the world
>
> ---- Eilles
> 7/5 2022
@@ -1,260 +0,0 @@
import random
import time
from itertools import chain
from multiprocessing import Pool, Process, freeze_support
from rich.console import Console
from rich.progress import Progress
from rich.table import Table
console = Console()
# gening_stst = {"NOWIDX": 0, "DATA": {}}
# 生成单个字典的函数(用于多进程)
def generate_single_dict(args):
dict_id, dict_size = args
# if dict_id:
# console.print(
# f"字典 {dict_id + 1} 大小 {dict_size} 生成中...",
# )
# else:
# console.print(
# f"\n字典 {dict_id + 1} 大小 {dict_size} 生成中...",
# )
# final_d = {}
# gening_stst["DATA"][dict_id] = 0
# for i in range(dict_size):
# final_d[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
# gening_stst["DATA"][dict_id] += 1
return dict_id, {
i: [random.randint(0, 1000) for _ in range(random.randint(10000, 90000))]
for i in range(dict_size)
}
# return dict_id, final_d
# 合并函数定义
def chain_merging(dict_info: dict):
return sorted(chain(*dict_info.values()))
def seq_merging(dict_info: dict):
return sorted([i for sub in dict_info.values() for i in sub])
def summing(*_):
k = []
for i in _:
k += i
return k
def plus_merging(dict_info: dict):
return sorted(summing(*dict_info.values()))
if __name__ == "__main__":
freeze_support() # Windows系统需要这个调用
# 测试配置
dict_size = 50 # 每个字典的键值对数量
num_tests = 50 # 测试次数
function_list = [chain_merging, seq_merging, plus_merging]
# dict_list = []
results = {func.__name__: [] for func in function_list}
# 多进程生成多个字典
with Progress() as progress:
task = progress.add_task("[green]进行速度测试...", total=num_tests)
# gen_task = progress.add_task("[cyan] - 生成测试数据...", total=num_tests)
with Pool() as pool:
args_list = [
(
i,
dict_size,
)
for i in range(num_tests)
]
# def disp_work():
# while gening_stst["NOWIDX"] < num_tests:
# progress.update(
# gen_task,
# advance=1,
# description=f"[cyan]正在生成 {gening_stst['DATA']['NOWIDX']}/{dict_size -1}",
# # description="正在生成..."+console._render_buffer(
# # console.render(table,),
# # ),
# )
# Process(target=disp_work).start()
for result in pool.imap_unordered(generate_single_dict, args_list):
# dict_list.append(result)
progress.update(
task,
advance=1,
description=f"[cyan]正在测试 {result[0] + 1}/{num_tests}",
# description="正在生成..."+console._render_buffer(
# console.render(table,),
# ),
# refresh=True,
)
# gening_stst["NOWIDX"] += 1
# for _ in range(num_tests):
# 随机选择字典和打乱函数顺序
# current_dict = generate_single_dict((_, dict_size))
# progress.update(
# test_task,
# advance=1,
# # description=f"[cyan]正在测试 {_}/{num_tests -1}",
# # description="正在测试..."+console._render_buffer(
# # console.render(table,progress.console.options),
# # ),
# # refresh=True,
# )
# rangen_task = progress.add_task(
# "[green]正在生成测试数据...",
# total=dict_size,
# )
# current_dict = {}
# desc = "正在生成序列 {}/{}".format("{}",dict_size-1)
# for i in range(dict_size):
# # print("正在生成第", i, "个序列",end="\r",flush=True)
# progress.update(rangen_task, advance=1, description=desc.format(i))
# current_dict[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
shuffled_funcs = random.sample(function_list, len(function_list))
# table.rows
# table.columns = fine_column
# progress.live
# progress.console._buffer.extend(progress.console.render(table))
# for j in progress.console.render(table,progress.console.options):
# progress.console._buffer.insert(0,j)
for i, func in enumerate(shuffled_funcs):
start = time.perf_counter()
func(result[1])
elapsed = time.perf_counter() - start
results[func.__name__].append(elapsed)
# gening_stst["NOWIDX"] = num_tests
# fine_column = table.columns.copy()
# for func in function_list:
# name = func.__name__
# table.add_row(
# name,
# f"-",
# f"-",
# f"-",
# f"-",
# )
# # proc_pool = []
# 测试执行部分(保持顺序执行)
# with Progress() as progress:
# # progress.live.update(table, refresh=True)
# # progress.live.process_renderables([table],)
# # print([console._render_buffer(
# # console.render(table,),
# # )])
# # progress.console._buffer.extend(progress.console.render(table))
# test_task = progress.add_task("[cyan]进行速度测试...", total=num_tests)
# for _ in range(num_tests):
# # 随机选择字典和打乱函数顺序
# # current_dict = generate_single_dict((_, dict_size))
# progress.update(
# test_task,
# advance=1,
# description=f"[cyan]正在测试 {_}/{num_tests -1}",
# # description="正在测试..."+console._render_buffer(
# # console.render(table,progress.console.options),
# # ),
# # refresh=True,
# )
# rangen_task = progress.add_task(
# "[green]正在生成测试数据...",
# total=dict_size,
# )
# current_dict = {}
# desc = "正在生成序列 {}/{}".format("{}",dict_size-1)
# for i in range(dict_size):
# # print("正在生成第", i, "个序列",end="\r",flush=True)
# progress.update(rangen_task, advance=1, description=desc.format(i))
# current_dict[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
# shuffled_funcs = random.sample(function_list, len(function_list))
# # table.rows
# # table.columns = fine_column
# # progress.live
# # progress.console._buffer.extend(progress.console.render(table))
# # for j in progress.console.render(table,progress.console.options):
# # progress.console._buffer.insert(0,j)
# for i, func in enumerate(shuffled_funcs):
# start = time.perf_counter()
# func(current_dict)
# elapsed = time.perf_counter() - start
# results[func.__name__].append(elapsed)
# times = results[func.__name__]
# avg_time = sum(times) / len(times)
# min_time = min(times)
# max_time = max(times)
# table.columns[0]
# table.columns[0]._cells[i] = func.__name__
# table.columns[1]._cells[i] = f"{avg_time:.5f}"
# table.columns[2]._cells[i] = f"{min_time:.5f}"
# table.columns[3]._cells[i] = f"{max_time:.5f}"
# table.columns[4]._cells[i] = str(len(times))
# progress.update(test_task, advance=0.5)
# 结果展示部分
# 结果表格
table = Table(title="\n[cyan]性能测试结果", show_header=True, header_style="bold")
table.add_column("函数名称", style="dim", width=15)
table.add_column("平均耗时 (秒)", justify="right")
table.add_column("最小耗时 (秒)", justify="right")
table.add_column("最大耗时 (秒)", justify="right")
table.add_column("测试次数", justify="right")
for i, func in enumerate(function_list):
name = func.__name__
times = results[name]
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
table.add_row(
name,
f"{avg_time:.5f}",
f"{min_time:.5f}",
f"{max_time:.5f}",
str(len(times)),
)
# table.columns[0]._cells[i] = name
# table.columns[1]._cells[i] = f"{avg_time:.5f}"
# table.columns[2]._cells[i] = f"{min_time:.5f}"
# table.columns[3]._cells[i] = f"{max_time:.5f}"
# table.columns[4]._cells[i] = str(len(times))
console.print(table)
-39
View File
@@ -1,39 +0,0 @@
import random
import time
from itertools import chain
print("生成序列中")
fine_dict = {}
for i in range(50):
print("正在生成第", i, "个序列",end="\r",flush=True)
fine_dict[i] = [random.randint(0, 1000) for _ in range(random.randint(10000, 99999))]
print("序列生成完成")
def chain_merging(dict_info: dict):
return sorted(chain(*dict_info.values()))
def seq_merging(dict_info: dict):
return sorted([i for sub in dict_info.values() for i in sub])
def summing(*_):
k = []
for i in _:
k += i
return k
def plus_merging(dict_info: dict):
return sorted(summing(*dict_info.values()))
function_list = [chain_merging, seq_merging, plus_merging]
for func in function_list:
print("正在使用",func.__name__,"函数",)
start = time.time()
func(fine_dict)
print("耗时",time.time() - start)
print("结束")
-42
View File
@@ -1,42 +0,0 @@
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
def q_function1(x, a, a2, c1,):
return a * np.log( x + a2,)+ c1
def q_function2(x, b, b2, b3, b4, c2):
return b * ((x + b2) ** b3) + b4 * (x+b2) + c2
x_data = np.array([0, 16, 32, 48, 64, 80, 96, 112, 128])
y_data = np.array([16, 10, 6.75, 4, 2.5, 1.6, 0.8, 0.3, 0])
p_est1, err_est1 = curve_fit(q_function1, x_data[:5], y_data[:5], maxfev=1000000)
p_est2, err_est2 = curve_fit(q_function2, x_data[4:], y_data[4:], maxfev=1000000)
print(q_function1(x_data[:5], *p_est1))
print(q_function2(x_data[4:], *p_est2))
print("参数一:",*p_est1)
print("参数二:",*p_est2)
# 绘制图像
plt.plot(
np.arange(0, 64.1, 0.1), q_function1(np.arange(0, 64.1, 0.1), *p_est1), label=r"FIT1"
)
plt.plot(
np.arange(64, 128.1, 0.1), q_function2(np.arange(64, 128.1, 0.1), *p_est2), label=r"FIT2"
)
plt.scatter(x_data, y_data, color="red") # 标记给定的点
# plt.xlabel('x')
# plt.ylabel('y')
plt.title("Function Fit")
plt.legend()
# plt.grid(True)
plt.show()
-37
View File
@@ -1,37 +0,0 @@
import matplotlib.pyplot as plt
import numpy as np
# 定义对数函数
def q_function1(vol):
# return -23.65060754864053*((x+508.2130392724084)**0.8433764630986903) + 7.257078620637543 * (x+407.86870598508153) + 1585.6201108739122
# return -58.863374003875954 *((x+12.41481943150274 )**0.9973316187745871 ) +57.92341268595151 * (x+ 13.391132186222036) + -32.92986286030519
return -8.081720684086314 * np.log( vol + 14.579508825070013,)+ 37.65806375944386
def q_function2(vol):
return 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649) + -6.313841334963396 * (vol + 2592.272889454798) + 4558.496367823575
# 生成 x 值
x_values = np.linspace(0, 128, 1000)
x_data = np.array([0,16,32,48,64,80,96,112,128])
y_data = np.array([16, 10, 6.75, 4, 2.5, 1.6, 0.8, 0.3, 0])
print(q_function1(x_data))
print(q_function2(x_data))
# 绘制图像
plt.plot(x_values, q_function1(x_values,),label = "fit1")
plt.plot(x_values, q_function2(x_values,),label = "fit2")
plt.scatter(x_data, y_data, color='red') # 标记给定的点
# plt.scatter(x_data, y_data2, color='green') # 标记给定的点
# plt.scatter(x_data, y_data3, color='blue') # 标记给定的点
plt.xlabel('x')
plt.ylabel('y')
plt.title('Function')
plt.legend()
plt.grid(True)
plt.show()
Binary file not shown.
Binary file not shown.
@@ -1,14 +0,0 @@
音乐谱面文件
测试片段.mid、测试片段.mscz
皆系 音·创 库所用之连带软件
禁止在 音·创 库以外传播
不允许独立发行、独立副本
不允许未经允许的二次创作
不允许脱离于 音·创 独立存在
不得随任何其他非 音·创 软件连带发布
不得用于商业用途
若 音·创 库被用于商业用途,应当将其剔除
版权所有 © 2025 诸葛亮与八卦阵
-39
View File
@@ -1,39 +0,0 @@
from rich.pretty import pprint
import Musicreater
from Musicreater.utils import (
load_decode_fsq_flush_release,
load_decode_musicsequence_metainfo,
)
msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段",
)
pprint("音乐源取入成功:")
pprint(msc_seq)
with open("test.fsq", "wb") as f:
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
with open("test.fsq", "rb") as f:
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read(), verify=True)
pprint("FSQ 传入类成功:")
pprint(msc_seq_r)
with open("test.fsq", "rb") as f:
pprint("流式 FSQ 元数据:")
pprint(metas := load_decode_musicsequence_metainfo(f))
pprint("流式 FSQ 音符序列:")
cnt = 0
for i in load_decode_fsq_flush_release(f, metas[-2], metas[-3], metas[-1]):
pprint(
i,
)
cnt += 1
pprint(f"{cnt} 个音符")
-33
View File
@@ -1,33 +0,0 @@
import Musicreater.experiment
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
msct = Musicreater.experiment.FutureMidiConvertKamiRES.from_midi_file(
input("midi路径:"), old_exe_format=False
)
opt = input("输出路径:")
print(
"乐器使用情况",
)
for name in sorted(
set(
[
n.split(".")[0].replace("c", "").replace("d", "")
for n in msct.note_count_per_instrument.keys()
]
)
):
print("\t", name, flush=True)
print(
"\n输出:",
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
msct,
opt,
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
),
)
-33
View File
@@ -1,33 +0,0 @@
import Musicreater.experiment
import Musicreater.plugin
import Musicreater.plugin.mcstructfile
msct = Musicreater.experiment.FutureMidiConvertLyricSupport.from_midi_file(
input("midi路径:"), old_exe_format=False
)
opt = input("输出路径:")
# print(
# "乐器使用情况",
# )
# for name in sorted(
# set(
# [
# n.split(".")[0].replace("c", "").replace("d", "")
# for n in msct.note_count_per_instrument.keys()
# ]
# )
# ):
# print("\t", name, flush=True)
print(
"\n输出:",
Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay(
msct,
opt,
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
max_height=32,
),
)
-34
View File
@@ -1,34 +0,0 @@
from rich.pretty import pprint
import Musicreater
from Musicreater.utils import (
load_decode_msq_flush_release,
load_decode_musicsequence_metainfo,
)
msc_seq = Musicreater.MusicSequence.from_mido(
Musicreater.mido.MidiFile(
"./resources/测试片段.mid",
),
"TEST-测试片段",
)
pprint("音乐源取入成功:")
pprint(msc_seq)
with open("test.msq", "wb") as f:
f.write(msq_bytes := msc_seq.encode_dump())
with open("test.msq", "rb") as f:
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read())
pprint("常规 MSQ 读取成功:")
pprint(msc_seq_r)
with open("test.msq", "rb") as f:
pprint("流式 MSQ 元数据:")
pprint(metas := load_decode_musicsequence_metainfo(f))
pprint("流式 MSQ 音符序列:")
for i in load_decode_msq_flush_release(f, metas[-2], metas[-3], metas[-1]):
pprint(i)
-7
View File
@@ -1,7 +0,0 @@
uv build
python -m twine check dist/*
pause
uv publish
pause
python clean_update.py
pause
Generated
-1253
View File
File diff suppressed because it is too large Load Diff