mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-28 04:05:45 +00:00
Compare commits
446 Commits
beewarewin
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
825d275a5e | ||
|
|
de830262a7 | ||
|
|
9c1383360b | ||
|
|
60cbdfb9d0 | ||
|
|
d6944392cd | ||
|
|
ba7b10a25f | ||
|
|
307feb9b24 | ||
|
|
6e518dada4 | ||
|
|
4c036cbd4c | ||
|
|
c310ba5dc3 | ||
|
|
d9c92fa269 | ||
|
|
00c445f7ad | ||
|
|
3ee686c712 | ||
|
|
0e95a1e541 | ||
|
|
bbc67921d6 | ||
|
|
62cd4a0c94 | ||
|
|
295da53c60 | ||
|
|
fff8e43f53 | ||
|
|
2a5ccb8eeb | ||
|
|
d4901cf3dc | ||
|
|
13512df9ce | ||
|
|
1d9931f79d | ||
|
|
841f6e53c6 | ||
|
|
0de959c396 | ||
|
|
734ee2dd66 | ||
|
|
32b7930b26 | ||
|
|
583ca04ac9 | ||
|
|
1e0698c85d | ||
|
|
dc3be4120d | ||
|
|
bf173a9f81 | ||
|
|
6bea46977f | ||
|
|
3b8532af61 | ||
|
|
9f35e9e16a | ||
|
|
65358b12d0 | ||
|
|
046c2af27c | ||
|
|
fa6cfb4873 | ||
|
|
3d6d0e17a2 | ||
|
|
f1bea1d57e | ||
|
|
63b59442a4 | ||
|
|
7f74b29569 | ||
|
|
885dc085ea | ||
|
|
24742a140c | ||
|
|
6f94f477f2 | ||
|
|
c056eb1fcb | ||
|
|
b796a363d8 | ||
|
|
774f78359c | ||
|
|
8df63df26c | ||
|
|
9764019f08 | ||
|
|
250857c78f | ||
|
|
7ab6b77cff | ||
|
|
321f80540c | ||
|
|
50373b05d0 | ||
|
|
b65483b32d | ||
|
|
45e9adbbd1 | ||
|
|
7a1ca86132 | ||
|
|
64048a5e31 | ||
|
|
3739138059 | ||
|
|
048b631bd6 | ||
|
|
23bf69619b | ||
|
|
b4e16353ec | ||
|
|
c14489f3a7 | ||
|
|
889f8f9641 | ||
|
|
2df8d6a270 | ||
|
|
78eabf2df6 | ||
|
|
b3ddfc7ade | ||
|
|
bd031ef547 | ||
|
|
e4304dc3d1 | ||
|
|
794ee6d080 | ||
|
|
b67554c7c9 | ||
|
|
6f391567ba | ||
|
|
932b9a34e0 | ||
|
|
f6b884e38f | ||
|
|
bf1b7b99d8 | ||
|
|
fe7c11636b | ||
|
|
382b0e1601 | ||
|
|
c17ff0e53a | ||
|
|
f1ab2373b5 | ||
|
|
77351d767b | ||
|
|
69624df711 | ||
|
|
d4925e4d75 | ||
|
|
95c0ff1b47 | ||
|
|
3831c41b9a | ||
|
|
a4565b8529 | ||
|
|
041c64ff65 | ||
|
|
b0bdb7b445 | ||
|
|
99a7564648 | ||
|
|
dbb3f4c83f | ||
|
|
edd40c078f | ||
|
|
d3b637a0c8 | ||
|
|
d7e3c62deb | ||
|
|
7b319268fe | ||
|
|
41883f7798 | ||
|
|
fc138f1dbf | ||
|
|
0b0328bc44 | ||
|
|
72dfdfeb34 | ||
|
|
9a580132e5 | ||
|
|
29380d4151 | ||
|
|
76eff25a1d | ||
|
|
7b60d3f9ea | ||
|
|
483e45dcc2 | ||
|
|
627a26f64a | ||
|
|
357cb18c5b | ||
|
|
83c9750db3 | ||
|
|
5c86a28b44 | ||
|
|
70674ec6f7 | ||
|
|
f73c1be944 | ||
|
|
36f8db722b | ||
|
|
bedb3924c6 | ||
|
|
81ae7ae146 | ||
|
|
c39c6c4488 | ||
|
|
556ce74cfb | ||
|
|
3da472052e | ||
|
|
ffe5837c9f | ||
|
|
42e2fcdd98 | ||
|
|
755de846c7 | ||
|
|
cb95c51a47 | ||
|
|
6fe8e41dfa | ||
|
|
b8bae7b913 | ||
|
|
7663bf8838 | ||
|
|
8aa503710d | ||
|
|
d5d7230537 | ||
|
|
5d48fcd96a | ||
|
|
aa94e3758b | ||
|
|
8b06078a90 | ||
|
|
799b9b664d | ||
|
|
4874ace92d | ||
|
|
54cd6b196e | ||
|
|
882fa4175e | ||
|
|
b15c89b6c8 | ||
|
|
b758a2f967 | ||
|
|
a9ec7582a9 | ||
|
|
48f5a975db | ||
|
|
acb347b491 | ||
|
|
6ca8820fa8 | ||
|
|
fb4cd07dc6 | ||
|
|
3db35b24ab | ||
|
|
a07f723d7c | ||
|
|
7cc70d6948 | ||
|
|
b1fb82712d | ||
|
|
bd329082c2 | ||
|
|
16be83ea29 | ||
|
|
5ffb654ec9 | ||
|
|
7f5efea038 | ||
|
|
5efabd5463 | ||
|
|
24fd0121a3 | ||
|
|
a65be910a5 | ||
|
|
15cd0ad777 | ||
|
|
4904f612e1 | ||
|
|
27c6556693 | ||
|
|
26fbd346d8 | ||
|
|
c419adc274 | ||
|
|
f8926d2f4f | ||
|
|
4268139313 | ||
|
|
8b8334202c | ||
|
|
1cfbfe1ba1 | ||
|
|
3669b5f804 | ||
|
|
7146d17051 | ||
|
|
8a21b6c3e7 | ||
|
|
5cce59135a | ||
|
|
53fda9c986 | ||
|
|
75f94dab4c | ||
|
|
6aa557dcd2 | ||
|
|
215bcafb64 | ||
|
|
f568803b0d | ||
|
|
a8c0c9e1d5 | ||
|
|
4d7a1b614a | ||
|
|
975dbd71e3 | ||
|
|
8722d4fd83 | ||
|
|
8b9d9a78a3 | ||
|
|
032bbb81c4 | ||
|
|
49c4cfcbe2 | ||
|
|
352807cba0 | ||
|
|
8e938b4f84 | ||
|
|
bf0ef8ebb2 | ||
|
|
c4664f0e0c | ||
|
|
65a21dcfa7 | ||
|
|
032e2f0f0a | ||
|
|
87e80406a3 | ||
|
|
41a6a84342 | ||
|
|
4b9550db21 | ||
|
|
3d66c06e6d | ||
|
|
9872a69a66 | ||
|
|
0ce72fc4e3 | ||
|
|
e0557da3cf | ||
|
|
7d6faebc5b | ||
|
|
502f4eb54d | ||
|
|
290db45c23 | ||
|
|
782395b6c9 | ||
|
|
392e74d8d9 | ||
|
|
ebfaeafcda | ||
|
|
6955bb8eaf | ||
|
|
582e66cd58 | ||
|
|
d64540bfd0 | ||
|
|
63499732eb | ||
|
|
be40f8f920 | ||
|
|
190ef70fc4 | ||
|
|
a74fd4b4de | ||
|
|
13f7c0cda0 | ||
|
|
2ebdf6be24 | ||
|
|
9ca0788eb2 | ||
|
|
fba83ef968 | ||
|
|
68e019412d | ||
|
|
bde2b4e378 | ||
|
|
b04c01772f | ||
|
|
3cd85897fc | ||
|
|
00c2dcdeab | ||
|
|
88d36f94f0 | ||
|
|
8eda5c4736 | ||
|
|
37ccbe3dae | ||
|
|
be43e4a92a | ||
|
|
5929ae417d | ||
|
|
517c7db112 | ||
|
|
91c31d4e90 | ||
|
|
3ae456c4d1 | ||
|
|
b0af5f4950 | ||
|
|
1ccaa8e9aa | ||
|
|
8d1e6b2ecf | ||
|
|
75fabf9c37 | ||
|
|
95e34f5c7b | ||
|
|
5bb0227a7a | ||
|
|
397825b483 | ||
|
|
9c9ebe0a08 | ||
|
|
54d434404c | ||
|
|
0b3a19faad | ||
|
|
6cde168bf3 | ||
|
|
451d1d8e80 | ||
|
|
085ab3f4c0 | ||
|
|
a16109f29a | ||
|
|
813186bef9 | ||
|
|
3921190832 | ||
|
|
c32b94592a | ||
|
|
77b7344277 | ||
|
|
0a17f8b8f8 | ||
|
|
f8d707ac11 | ||
|
|
ad7e047abc | ||
|
|
5ea47c54cb | ||
|
|
5956e27d83 | ||
|
|
b73a041883 | ||
|
|
36f1831cd1 | ||
|
|
b20f350da4 | ||
|
|
5aaae83020 | ||
|
|
85e6340570 | ||
|
|
662c6506d8 | ||
|
|
ad2fcd6f14 | ||
|
|
5dde31d081 | ||
|
|
2e11a9949b | ||
|
|
0a9c70f97c | ||
|
|
814ab2ab0e | ||
|
|
3b53846ade | ||
|
|
96af5183ac | ||
|
|
4aebf9db16 | ||
|
|
fff689446a | ||
|
|
59fd2645e4 | ||
|
|
33ce7ed8c8 | ||
|
|
14f7d6fd7e | ||
|
|
fb2fd13b21 | ||
|
|
5025cba356 | ||
|
|
abb53a7499 | ||
|
|
73854be7be | ||
|
|
6f6a77d0b4 | ||
|
|
e0a3399fed | ||
|
|
2afd6beb3f | ||
|
|
9517c49ec5 | ||
|
|
887452184b | ||
|
|
d36134e525 | ||
|
|
453ca745af | ||
|
|
7d814bcda2 | ||
|
|
50dff06ac2 | ||
|
|
d88e50819d | ||
|
|
325bb37210 | ||
|
|
55b933ac85 | ||
|
|
56fb029e75 | ||
|
|
c5a7dfb608 | ||
|
|
0a640267ff | ||
|
|
a6d7022f87 | ||
|
|
dcc62ca66d | ||
|
|
4b53938adf | ||
|
|
8502a02880 | ||
|
|
80fab8ced0 | ||
|
|
1980aeb8de | ||
|
|
1fde8ca253 | ||
|
|
0ce24bff92 | ||
|
|
992572cbf1 | ||
|
|
ce1099a246 | ||
|
|
2340741bb9 | ||
|
|
1f94f558c4 | ||
|
|
0c50ce1134 | ||
|
|
2e0dd06db6 | ||
|
|
9fd32d499f | ||
|
|
5c501a3466 | ||
|
|
e872c19676 | ||
|
|
51d248cdc2 | ||
|
|
a1dfe65042 | ||
|
|
70fc686057 | ||
|
|
ba691d177a | ||
|
|
b81860dadd | ||
|
|
343a62be47 | ||
|
|
ab26c82bf9 | ||
|
|
a2ae755e87 | ||
|
|
e94af73c4f | ||
|
|
bc247e1507 | ||
|
|
874cd1aea9 | ||
|
|
5a6d874204 | ||
|
|
08ffa1a6e2 | ||
|
|
16d0ecf009 | ||
|
|
f57f4257fb | ||
|
|
3f0f714a7c | ||
|
|
65c23ca427 | ||
|
|
bc99f8f4be | ||
|
|
3b6cb3865f | ||
|
|
64292a21b3 | ||
|
|
3ba8fdad8c | ||
|
|
7357a09e5f | ||
|
|
cae0b7f23d | ||
|
|
e1d4cd9933 | ||
|
|
3bc19ac396 | ||
|
|
3499c05eb4 | ||
|
|
259fb04980 | ||
|
|
8ce7a9fa83 | ||
|
|
affb5b5404 | ||
|
|
1805ab53c0 | ||
|
|
f3c5044800 | ||
|
|
ca2e8c9155 | ||
|
|
eda3a957cf | ||
|
|
4788d8949b | ||
|
|
1715735800 | ||
|
|
d22b5b0c42 | ||
|
|
549dd3dbf9 | ||
|
|
a65bfeba20 | ||
|
|
7edf979aee | ||
|
|
8f6cc04780 | ||
|
|
6cb5c9ba89 | ||
|
|
ae83f9c21e | ||
|
|
dd34ac9998 | ||
|
|
2da06136c5 | ||
|
|
e9f4230a2d | ||
|
|
1b7ab73c02 | ||
|
|
2624c081bb | ||
|
|
87cc189da8 | ||
|
|
668bf480b2 | ||
|
|
4cc3f2678f | ||
|
|
701e9d5129 | ||
|
|
8dd2694e82 | ||
|
|
ac962f0ab7 | ||
|
|
b800384547 | ||
|
|
5da1041664 | ||
|
|
966ce515c3 | ||
|
|
349c255f5f | ||
|
|
cbb77086d0 | ||
|
|
280e50f4cb | ||
|
|
a2a37be8ef | ||
|
|
7acd6b7e38 | ||
|
|
4c95f76cf9 | ||
|
|
8a1a159eb8 | ||
|
|
cb43caef26 | ||
|
|
200167ef84 | ||
|
|
7e0a127406 | ||
|
|
5e70dd73a1 | ||
|
|
7229b12e99 | ||
|
|
074124bc3d | ||
|
|
91446bfd81 | ||
|
|
5011efcdee | ||
|
|
3b4af19379 | ||
|
|
d3d89d2567 | ||
|
|
441d1e9982 | ||
|
|
77e738cb7e | ||
|
|
46b6884e64 | ||
|
|
d0a63d0f8e | ||
|
|
887394570b | ||
|
|
c62806d470 | ||
|
|
d21c26d632 | ||
|
|
26ec42a71d | ||
|
|
245e2fa1ec | ||
|
|
e70fc806be | ||
|
|
72a3715722 | ||
|
|
fd3d27d596 | ||
|
|
3d283bfded | ||
|
|
7d9b63b9fe | ||
|
|
82850a3d74 | ||
|
|
629cfa402b | ||
|
|
0818957f51 | ||
|
|
954a30f722 | ||
|
|
96e6bc2f7a | ||
|
|
ca0e56e771 | ||
|
|
8fe71dbe0d | ||
|
|
97f334789e | ||
|
|
d2a6ce2529 | ||
|
|
066e0b0cac | ||
|
|
052142ac08 | ||
|
|
59c481f6da | ||
|
|
53c17f0328 | ||
|
|
ed28fc4866 | ||
|
|
320114533d | ||
|
|
aa210ac678 | ||
|
|
c4dd7b1ce8 | ||
|
|
99509be48c | ||
|
|
073ae827ab | ||
|
|
58b312554d | ||
|
|
55eeddb108 | ||
|
|
9d4a75cd41 | ||
|
|
882ce96b90 | ||
|
|
9093a66639 | ||
|
|
b48a66115a | ||
|
|
db20a87ae9 | ||
|
|
69eebd25a9 | ||
|
|
89ef3bcb55 | ||
|
|
f08a2ff9be | ||
|
|
9f8694ef14 | ||
|
|
81272a1166 | ||
|
|
a83052bd5d | ||
|
|
2fcfdca0a0 | ||
|
|
bcaeb129a5 | ||
|
|
36b639f029 | ||
|
|
4b241dfc0b | ||
|
|
255cc84ef3 | ||
|
|
c04b622297 | ||
|
|
b6a63a794e | ||
|
|
7c0dd218b2 | ||
|
|
19bbb92cff | ||
|
|
26ee16f071 | ||
|
|
05eb0c05ec | ||
|
|
5d2e0582c3 | ||
|
|
d3e0f78d2c | ||
|
|
79a2dfb5b1 | ||
|
|
bf3367c7e4 | ||
|
|
c91f5cf2f9 | ||
|
|
d8ea747ef1 | ||
|
|
39148a6c76 | ||
|
|
0e3eb394ab | ||
|
|
f0dedabe1a | ||
|
|
dda78ab35f | ||
|
|
5b6ff5ee68 | ||
|
|
fcb1a25227 | ||
|
|
81eb8e5376 | ||
|
|
7e13b22d46 | ||
|
|
07e153f215 | ||
|
|
908f622433 | ||
|
|
5f7ca0859f | ||
|
|
32ae868c34 | ||
|
|
ce441e4905 | ||
|
|
3dc099af7e | ||
|
|
cf7e51595e | ||
|
|
737b2d5f07 | ||
|
|
143de0bb37 | ||
|
|
17f9fa7ffc | ||
|
|
1bc1c6485b |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.yaml linguist-language=Python
|
||||||
|
*.xml linguist-language=Python
|
||||||
|
*.md linguist-language=Python
|
||||||
199
.gitignore
vendored
199
.gitignore
vendored
@@ -1,38 +1,40 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# 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__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.pyc
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# OSX useful to ignore
|
|
||||||
*.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
.com.apple.timemachine.donotpresent
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
@@ -44,23 +46,130 @@ lib64/
|
|||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
# IntelliJ Idea family of suites
|
# PyInstaller
|
||||||
.idea
|
# Usually these files are written by a python script from a template
|
||||||
*.iml
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
## File-based project format:
|
*.manifest
|
||||||
*.ipr
|
*.spec
|
||||||
*.iws
|
|
||||||
## mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# Briefcase build directories
|
# Installer logs
|
||||||
iOS/
|
pip-log.txt
|
||||||
macOS/
|
pip-delete-this-directory.txt
|
||||||
windows/
|
|
||||||
android/
|
# Unit test / coverage reports
|
||||||
linux/
|
htmlcov/
|
||||||
django/
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# Pycharm
|
||||||
|
/.idea
|
||||||
|
|
||||||
|
# log
|
||||||
|
/.log
|
||||||
|
|
||||||
|
# package
|
||||||
|
.7z
|
||||||
|
|||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.10
|
||||||
201
LICENSE
201
LICENSE
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
123
LICENSE.md
Normal file
123
LICENSE.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# 汉钰律许可协议,第一版
|
||||||
|
|
||||||
|
**总第一版 第二次修订 · 二〇二四年七月七日编 二〇二五年四月二十六日修订**
|
||||||
|
|
||||||
|
## 一、重要须知
|
||||||
|
|
||||||
|
1. 为保护采用本协议的作品在开源过程中,其著作权人所应有的权益,根据**中华人民共和国著作权法和相关法律法规**,制定本协议。
|
||||||
|
|
||||||
|
2. 本协议履行过程中,请注意本协议中**免除或限制**民事主体**责任或权利**的条款、法律适用和争议解决条款(尤其是加有特殊标记的条款),这些条款应在中国法律所允许的范围内最大程度地适用。
|
||||||
|
|
||||||
|
3. 若本协议所涉及的自然人**未满 18 周岁**,该自然人应在监护人的陪同下阅读本协议及有关本协议的条款内容,并在取得其监护人同意后开始或继续应用本协议所授权的行为。
|
||||||
|
|
||||||
|
4. 由于互联网服务、互联网内容的特殊性,若本协议以电子协议形式分发并签订,其依然有效。您一旦开始对本协议所授权之作品进行本协议所授权的行为,即视为您已经阅读、理解并同意并已经接受本协议的全部条款。
|
||||||
|
|
||||||
|
5. 本协议的订立、履行、解释及争议的解决均**适用中华人民共和国法律并排除其他一切冲突法的适用**。_本协议订立于许可证最初的颁发者的地址。若颁发者为自然人,则订立于该自然人户籍所在地;若为法人或非法人组织,则订立于其注册地_。本协议的订立各方应友好协商解决于协议所规定之行为的履行相关的争议;如协商不成,任何一方均可向合同签订地有管辖权的人民法院提起诉讼。
|
||||||
|
|
||||||
|
6. 本协议的原本仅为现代汉语,书写于简体中文。若存在其他语言的翻译或其他同等语言但非简体中文文本的版本,应当无法律效力。
|
||||||
|
|
||||||
|
## 二、术语定义
|
||||||
|
|
||||||
|
1. “**许可证**”、“**协议**”(后文称“本协议”)是指根据本文档中所列举的全部术语、定义、条款、限制等文本,是本合同的简称称谓。本合同全称是 **“汉钰律许可协议,第一版”**。
|
||||||
|
|
||||||
|
2. “**协议颁发者**”(后文称“颁发者”)是将条款或协议应用于其拥有著作财产权的作品的民事主体,或由其指定从而拥有颁发者身份的民事主体。
|
||||||
|
|
||||||
|
3. “**源**”形式是指对包括但不限于 软件、硬件、文档、配置项 等种类的作品进行修改、编辑的首选形式;若不存在首选形式,则初次编辑该作品所需的形式即为源形式。
|
||||||
|
|
||||||
|
4. “**目标**”形式是指对源形式进行机械转换、翻译、打印、制造、加工等同类型活动后形成的结果形式,包括但不限于源代码编译后的目标软件、生成的文件、转换出的媒体、制造出的机械、打印出的实体文本、加工后的零件。
|
||||||
|
|
||||||
|
5. “**采用本协议的作品**”(后文称“此作品”)是指经颁发者授权而使用本协议进行授权的任何作品,该作品应在自然人可见处明确附加一个自然人可读的版权通知(可以参考文末附录中提供的示例);若在一个可分割的作品中,部分地采用本协议进行授权,则该部分应当视为一个独立的采用本协议的作品,该作品应当在自然人可见处明确附加一个自然人可读的范围限定和版权通知(同样可以参考文末附录中提供的示例)。
|
||||||
|
|
||||||
|
6. “**贡献**”是指对作品进行的,意在提交给此作品颁发者以让著作权人包含在其作品中的任何修订或补充,该修订或补充同样属于一种作品。依据此定义,**“提交”**一词表示经由此作品颁发者所指定的形式,将其所进行的修改发送给此作品颁发者。该形式应当包括在此作品颁发者指定的平台内发送易于编辑的修改信息、在此作品颁发者指定的电子邮箱中发送易于编辑的修改信息、在此作品颁发者指定的源码控制系统或发布跟踪系统上提交的易于编辑的修改信息,但由著作权人以明显标注或指定为“非贡献”的活动除外。颁发者自己对作品进行的修改同样视作对作品的贡献。
|
||||||
|
|
||||||
|
7. “**贡献者**”是指此作品颁发者接受的贡献的提交者,或包含在作品的贡献清单中的民事主体。贡献者在提交贡献并经此作品颁发者通过且该贡献已经被应用于此作品中后,该贡献者应当视为此作品的著作权人之一,但不应视为此作品非其贡献的部分的著作权人。一个作品的颁发者同样属于其贡献者。**请注意**,针对贡献者提交的贡献,该贡献者应被视为该贡献的协议颁发者,但不应视作本作品的颁发者。
|
||||||
|
|
||||||
|
8. “**用户**”、“**使用者**”是指行使本协议所授权之行为的民事主体。据此,贡献者亦属于用户。
|
||||||
|
|
||||||
|
9. “**商业性使用**”、“**商用**”是指任何以谋取利益为目的的使用,包括但不限于以贩卖、出租的形式对作品进行使用;但若将该获取利益之活动明确指示为“捐赠”,且在获利者在进行本协议所授权的活动时不以捐赠数额为标准而区别之,则此种的获取利益的“捐赠”行为不属于商业性使用。
|
||||||
|
|
||||||
|
## 三、权利授予
|
||||||
|
|
||||||
|
1. 任何由颁发者所进行的特殊声明、特别注意等此类内容,应当在法律效力上高于本协议的条款或声明;这些声明若与本协议冲突,本协议的该冲突部分无效;本协议与这些声明共同构成颁发者与用户之间的合同。
|
||||||
|
|
||||||
|
2. 此作品的贡献者享有其贡献的完整著作权。
|
||||||
|
|
||||||
|
3. 此作品的贡献者将自己的贡献的全部著作财产权,免费、公开、不可撤销、无限期、非专有地授予此作品的全部著作权人,并准许其在全世界范围内使用上述权利;若无明确的标识,贡献者允许此作品的颁发者对其贡献进行免费、公开、不可撤销、无限期、非专有、世界范围内的商业性使用。
|
||||||
|
|
||||||
|
4. 此作品的著作权人及贡献者授予用户**免费、公开、不可撤销、非专有、非商用**地以任意形式**复制、发行、展览、表演、放映、广播、信息网络传播、摄制、改编、翻译、汇编、二次授权**的权利,准许其在此作品颁发者所指定的区域与时间内行使上述权利;若此作品颁发者未特别指定的,则视作在全世界范围内无限期地授权;若此作品颁发者特别指定在特定情况下可以商用,则应当按照其所指定的条件进行商业性使用,商用的过程中,应当明确标识此作品的著作权人。
|
||||||
|
|
||||||
|
5. 一旦此作品有任意由非贡献形式而产生的更改,更改的部分将不视为此作品的一部分,除非该部分不可离开此作品单独存在;若该部分必须依赖此作品而不可与此作品分离从而单独存在,则更改后的作品不视作此作品,在这种情况下,除非此更改后的作品已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人,否则对该作品进行的任何活动都应当遵守本协议。
|
||||||
|
|
||||||
|
6. 经贡献而产生的对此作品的更改,属于此作品的一部分;在此情况下,更改后的作品,依旧视作此作品。
|
||||||
|
|
||||||
|
7. 依据本款的第 4 条,若用户在本协议的授权下,将此作品授予他人进行任何形式的活动(即“二次授权”、“二次分发”),则应确保其使用的协议或授权内容,与本协议的条款不冲突;当存在与本协议条款的冲突时,则该冲突内容无效,被授权的第三方应依照本协议的条款进行活动;除非该用户获得了此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||||
|
|
||||||
|
8. 依据本款的第 5 条,若由非贡献形式而产生更改的部分是可分割而不需依赖此作品即可单独存在的,若该部分明确注明不使用本协议进行授权或明确声明了其他授权条款,则该部分不视作采用本协议;但未更改的部分仍应视作原此作品的一部分,需要采用本协议进行授权,除非此更改后的作品已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人。
|
||||||
|
|
||||||
|
9. 若此作品或所提交的贡献包含其著作权人的专利,则该专利所有人即此作品的著作权人应准许此作品全体著作权人**免费、公开、不可撤销、非专有、无版权费的专利许可**,以便贡献者对作品进行本协议所授权进行的活动。
|
||||||
|
|
||||||
|
10. 上述专利许可的授予,仅适用于在所提交的贡献中,可由专利所有者授予的,且在对此作品进行本协议所授权的活动中,必须使用的专利。
|
||||||
|
|
||||||
|
11. 如果用户对任何民事主体,因其在进行本协议所授权进行的活动中侵犯该用户的专利而提起诉讼,那么根据本协议授予该用户的所有关于此作品的任何其他专利许可将在提起上述诉讼之日起终止。
|
||||||
|
|
||||||
|
12. 如果本作品作为用户的其他作品的不可分割的一部分进行任何民事活动,本协议依旧对本作品(即该用户的其他作品的一部分)生效;若本作品完全融入该用户的其他作品之中而不可独立存在,则该用户需要保证其作品存在与本协议冲突的条款;除非该作品已获得此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||||
|
|
||||||
|
## 四、使用条件
|
||||||
|
|
||||||
|
在对此作品进行本协议所授权的民事活动中,应当同时满足以下条款:
|
||||||
|
|
||||||
|
1. 用户必须为此作品的任何其他接收者提供本协议的副本,在不得已无法提供副本的情况下,也应明确指示其他接收者可查阅本协议的位置。
|
||||||
|
|
||||||
|
2. 用户必须在修改后的作品中附带明显的通知,声明用户已更改文件,并注明更改位置。
|
||||||
|
|
||||||
|
3. 若用户二次分发此作品,可以选择向此作品的接收者提供无偿或有偿的担保维修、支持服务或其他责任、义务。但是,该用户只可以其自己的名义提供上述内容,不得以任何其他贡献者的名义。且该用户必须明确表明任何此类责任或义务是由其个人独立提供,且其同意并应当承担赔偿此作品的全体贡献者因其个人承担上述责任义务而产生的任何赔偿责任。
|
||||||
|
|
||||||
|
4. 用户不得删除或更改此作品中包含的任何许可声明(包括版权声明,专利声明,免责声明,或赔偿责任限制),除非该更改是对已知事实错误的修补、或其已获得此作品颁发者的特殊许可、或更改者即为此作品颁发者本人。
|
||||||
|
|
||||||
|
5. 若此作品将权益的声明通知作为一部分,那么由用户分发的任何版本的作品中须至少在下列三处之一包含该声明通知的自然人可读副本:
|
||||||
|
|
||||||
|
- 该作品的权益声明通知中
|
||||||
|
- 在源形式的文件中(当且仅当该作品开放源代码)
|
||||||
|
- 在惯例中作为第三方通知出现之处(当且仅当该作品会产生画面,且该画面可被自然人详细观察)
|
||||||
|
|
||||||
|
该通知的内容仅供信息提供,不应对许可证进行任何文字上的修改。用户可在其分发的作品中,在不构成修改本协议的前提下,在作品自身的声明通知或属性描述后或作为附录添加。
|
||||||
|
|
||||||
|
6. 依据本款第3条,若用户二次分发此作品时,选择向作品的接收者提供收费的担保服务,则必须明确告知该接收者本协议全部内容与此作品原出处,并确保其知悉上述内容;但若用户在二次分发此作品时,不选择提供任何服务,则该用户不允许向作品的接收者收取任何费用,除非该用户获得了此作品颁发者的特殊许可、或该用户即为此作品颁发者本人。
|
||||||
|
|
||||||
|
## 五、提交贡献
|
||||||
|
|
||||||
|
除非贡献者明确声明,在本作品中由该贡献者向颁发者的提供的提交,必须符合本协议的条款,并与本协议的条款不存在冲突;除非此贡献中与本协议冲突的附加条款已获得颁发者的特殊许可、或贡献者即为此作品颁发者本人。
|
||||||
|
|
||||||
|
## 六、商标相关
|
||||||
|
|
||||||
|
本协议并未授予用户,将颁发者的商标、专属标记或特定产品名称,用于合理的或惯例性的描述或此类声明之外其他任何位置的权利。
|
||||||
|
|
||||||
|
## 七、免责声明
|
||||||
|
|
||||||
|
1. 若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,**不予提供任何形式的担保、任何明示、任何暗示或类似承诺**,此类包括但不限于担保此作品毫无缺陷、担保此作品适于贩卖、担保此作品适于特定目的、担保使用此作品绝不侵权。用户将自行承担因此作品的质量或性能问题而产生的全部风险。若此作品在任何方面欠妥,将由用户(而非任何贡献者、而非任何颁发者)承担所有必要的服务、维修或除错的任何成本。本免责声明是本许可的重要组成部分。当且仅当遵守本免责声明时,本协议的其他条款中对本作品的使用授权方可生效。
|
||||||
|
|
||||||
|
2. 无论是因何种原因,如果不是在法律规定的特殊情况(如,确为贡献者的故意或重大过失)下或者经过了特殊准许,即使贡献者事先已知发生损害的可能,在使用本作品时,用户产生的任何直接、间接、特殊、偶然或必然造成的损失(包括但不限于商誉损失、工作延误、计算机系统故障等),**均不由任一贡献者承担**。
|
||||||
|
|
||||||
|
**以上是本许可协议的全部条款**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
附录
|
||||||
|
|
||||||
|
**如何在自己的作品中应用 汉钰律许可协议**
|
||||||
|
|
||||||
|
若要在自己源形式的作品应用本协议,请在其中附加下面的通知模板,并将六角括号“〔〕”中的字段替换成自身的实际信息来替换(不包括括号本身)。这些文本必须以对应文件格式适当的注释句法包含在其中,可以是实体的纸质文档、也可以是网络公告或者计算机文件;或者脱离该源之外,另起一个新的文件,使之指向要应用本协议的那个作品。同时也建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的页面上作为版权通知的整体,这样更加容易的区分出第三方内容。
|
||||||
|
|
||||||
|
若需要在自己以目标形式存在的作品中应用本协议,同样需要附加下面的通知模板并更改六角括号中的字样。但是,这些文本可以是位于作品的标签上、位于作品的用户可见且能被自然人详细观察的画面之中、或者按照惯例中许可协议应该出现的位置;同时,这些文本的所处位置应当能够明确指示到本协议应用的那个作品。另外,建议将作品名或类别名以及目的说明之类的声明囊括在同一个可被打印的位置上作为版权通知的整体,这样更加容易的区分出第三方内容。
|
||||||
|
|
||||||
|
**通知模板**
|
||||||
|
|
||||||
|
```
|
||||||
|
版权所有 © 〔年份〕 〔著作权人〕
|
||||||
|
〔或者:版权所有 (C) 〔年份〕 〔著作权人〕〕
|
||||||
|
|
||||||
|
〔该作品〕根据 汉钰律许可协议,第一版(“本协议”)授权。
|
||||||
|
任何人皆可从以下地址获得本协议副本:〔本协议副本所在地址〕。
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
||||||
|
```
|
||||||
70
Musicreater/__init__.py
Normal file
70
Musicreater/__init__.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创
|
||||||
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
|
||||||
|
Musicreater (音·创)
|
||||||
|
A cost free and open-source library for handling with **Minecraft** digital music.
|
||||||
|
|
||||||
|
版权所有 © 2026 睿乐组织
|
||||||
|
Copyright © 2026 TriM-Organization
|
||||||
|
|
||||||
|
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||||
|
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||||
|
任何人皆可从以下地址获得本协议副本:
|
||||||
|
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
|
||||||
|
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
|
||||||
|
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = "3.0.0-alpha"
|
||||||
|
|
||||||
|
__author__ = (
|
||||||
|
("金羿", "Eilles"),
|
||||||
|
("玉衡Alioth", "YuhengAlioth"),
|
||||||
|
("鱼旧梦", "ElapsingDreams"),
|
||||||
|
)
|
||||||
|
|
||||||
|
from .paramcurve import ParamCurve, InterpolationMethod, BoundaryBehaviour
|
||||||
|
|
||||||
|
from .data import (
|
||||||
|
SingleMusic,
|
||||||
|
SingleTrack,
|
||||||
|
SingleNote,
|
||||||
|
SoundAtmos,
|
||||||
|
MineNote,
|
||||||
|
CurvableParam,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .plugins import load_plugin_module
|
||||||
|
|
||||||
|
from .main import MusiCreater
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"__version__",
|
||||||
|
"__author__",
|
||||||
|
# 参数曲线相关
|
||||||
|
"ParamCurve",
|
||||||
|
"InterpolationMethod",
|
||||||
|
"BoundaryBehaviour",
|
||||||
|
# 音乐数据结构
|
||||||
|
"SingleMusic",
|
||||||
|
"SingleTrack",
|
||||||
|
"SingleNote",
|
||||||
|
"SoundAtmos",
|
||||||
|
"MineNote",
|
||||||
|
"CurvableParam",
|
||||||
|
# 工程项目相关
|
||||||
|
"load_plugin_module",
|
||||||
|
"MusiCreater",
|
||||||
|
]
|
||||||
634
Musicreater/_plugin_abc.py
Normal file
634
Musicreater/_plugin_abc.py
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# NOTE: [WARNING]
|
||||||
|
# 这个文件是一坨屎山代码
|
||||||
|
# 请勿模仿,请多包容
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Sequence,
|
||||||
|
BinaryIO,
|
||||||
|
Generator,
|
||||||
|
Iterator,
|
||||||
|
Set,
|
||||||
|
Type,
|
||||||
|
Mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
import tomllib
|
||||||
|
import tomli_w
|
||||||
|
else:
|
||||||
|
import tomli as tomllib # 第三方包
|
||||||
|
import tomli_w
|
||||||
|
|
||||||
|
from .exceptions import (
|
||||||
|
PluginConfigDumpError,
|
||||||
|
PluginConfigLoadError,
|
||||||
|
PluginMetainfoNotFoundError,
|
||||||
|
PluginMetainfoTypeError,
|
||||||
|
PluginMetainfoValueError,
|
||||||
|
PluginAttributeNotFoundError,
|
||||||
|
ParameterTypeError,
|
||||||
|
PluginInstanceNotFoundError,
|
||||||
|
)
|
||||||
|
from .data import SingleMusic, SingleTrack
|
||||||
|
|
||||||
|
# 已经全部由 plugins.py 提供接口
|
||||||
|
# 请用户从 plugins.py 导入
|
||||||
|
# 不要在这里导,会坏掉的
|
||||||
|
|
||||||
|
# __all__ = [
|
||||||
|
# # 枚举类
|
||||||
|
# "PluginType",
|
||||||
|
# # 抽象基类/数据类(插件参数定义)
|
||||||
|
# "PluginConfig",
|
||||||
|
# "PluginMetaInformation",
|
||||||
|
# # 抽象基类(插件定义)
|
||||||
|
# "MusicInputPlugin",
|
||||||
|
# "TrackInputPlugin",
|
||||||
|
# "MusicOperatePlugin",
|
||||||
|
# "TrackOperatePlugin",
|
||||||
|
# "MusicOutputPlugin",
|
||||||
|
# "TrackOutputPlugin",
|
||||||
|
# "ServicePlugin",
|
||||||
|
# "LibraryPlugin",
|
||||||
|
# # 插件注册用装饰函数
|
||||||
|
# "music_input_plugin",
|
||||||
|
# "track_input_plugin",
|
||||||
|
# "music_operate_plugin",
|
||||||
|
# "track_operate_plugin",
|
||||||
|
# "music_output_plugin",
|
||||||
|
# "track_output_plugin",
|
||||||
|
# "service_plugin",
|
||||||
|
# "library_plugin",
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# 枚举类
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
|
class PluginTypes(str, Enum):
|
||||||
|
"""插件类型枚举"""
|
||||||
|
|
||||||
|
FUNCTION_MUSIC_IMPORT = "import_music_data"
|
||||||
|
FUNCTION_TRACK_IMPORT = "import_track_data"
|
||||||
|
FUNCTION_MUSIC_OPERATE = "music_data_operating"
|
||||||
|
FUNCTION_TRACK_OPERATE = "track_data_operating"
|
||||||
|
FUNCTION_MUSIC_EXPORT = "export_music_data"
|
||||||
|
FUNCTION_TRACK_EXPORT = "export_track_data"
|
||||||
|
SERVICE = "service"
|
||||||
|
LIBRARY = "library"
|
||||||
|
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# 数据类
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PluginConfig(ABC):
|
||||||
|
"""插件配置基类"""
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
"""将配置内容转换为字典
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
Dict[str, Any]
|
||||||
|
配置项的字典表示,不包含以下划线开头的私有属性
|
||||||
|
"""
|
||||||
|
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Mapping[str, Any]) -> "PluginConfig":
|
||||||
|
"""从字典创建配置实例
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: Dict[str, Any]
|
||||||
|
包含配置字段的字典
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
PluginConfig
|
||||||
|
配置类的实例
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 只保留类中定义的字段
|
||||||
|
field_names = {f.name for f in cls.__dataclass_fields__.values()}
|
||||||
|
filtered_data = {k: v for k, v in data.items() if k in field_names}
|
||||||
|
return cls(**filtered_data)
|
||||||
|
|
||||||
|
def save_to_file(self, file_path: Path) -> None:
|
||||||
|
"""保存配置到 TOML 文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
目标文件路径;必须以 .toml 为后缀
|
||||||
|
|
||||||
|
异常
|
||||||
|
====
|
||||||
|
PluginConfigDumpError
|
||||||
|
当文件后缀不是 .toml 或写入失败时抛出
|
||||||
|
"""
|
||||||
|
if file_path.suffix.upper() == ".TOML":
|
||||||
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
else:
|
||||||
|
raise PluginConfigDumpError(
|
||||||
|
"插件配置文件类型不应为`{}`,须为`TOML`格式。".format(file_path.suffix)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
||||||
|
except Exception as e:
|
||||||
|
raise PluginConfigDumpError("插件配置文件无法保存。") from e
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||||
|
"""从 TOML 文件加载配置
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
源文件路径
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
PluginConfig
|
||||||
|
加载后的配置实例
|
||||||
|
|
||||||
|
异常
|
||||||
|
====
|
||||||
|
PluginConfigLoadError
|
||||||
|
当读取或解析失败时抛出
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return cls.from_dict(tomllib.load(f))
|
||||||
|
except Exception as e:
|
||||||
|
raise PluginConfigLoadError("插件配置文件无法加载。") from e
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PluginMetaInformation(ABC):
|
||||||
|
"""插件元信息"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""插件名称,应为惟一之名"""
|
||||||
|
author: str
|
||||||
|
"""插件作者"""
|
||||||
|
description: str
|
||||||
|
"""插件简介"""
|
||||||
|
version: Tuple[int, ...]
|
||||||
|
"""插件版本号"""
|
||||||
|
type: PluginTypes
|
||||||
|
"""插件类型"""
|
||||||
|
license: str = "MIT License"
|
||||||
|
"""插件发布时采用的许可协议"""
|
||||||
|
dependencies: Sequence[str] = tuple()
|
||||||
|
"""插件是否对其他插件存在依赖"""
|
||||||
|
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# 抽象基类
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
|
||||||
|
class TopPluginBase(ABC):
|
||||||
|
"""所有插件的抽象基类"""
|
||||||
|
|
||||||
|
metainfo: PluginMetaInformation
|
||||||
|
"""插件元信息"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
if hasattr(cls, "metainfo"):
|
||||||
|
if not isinstance(cls.metainfo, PluginMetaInformation):
|
||||||
|
raise PluginMetainfoTypeError(
|
||||||
|
"类`{cls_name}`之属性`metainfo`的类型,必须为`PluginMetaInformation`".format(
|
||||||
|
cls_name=cls.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not cls.__name__.endswith("PluginBase"):
|
||||||
|
raise PluginMetainfoNotFoundError(
|
||||||
|
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
|
||||||
|
cls_name=cls.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TopInOutPluginBase(TopPluginBase, ABC):
|
||||||
|
"""导入导出用抽象基类"""
|
||||||
|
|
||||||
|
supported_formats: Tuple[str, ...] = tuple()
|
||||||
|
"""支持的格式(定义后会自动转大写)"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if hasattr(cls, "supported_formats"):
|
||||||
|
if cls.supported_formats:
|
||||||
|
# 强制转换为大写,并使用元组
|
||||||
|
cls.supported_formats = tuple(map(str.upper, cls.supported_formats))
|
||||||
|
else:
|
||||||
|
cls.supported_formats = tuple()
|
||||||
|
else:
|
||||||
|
raise PluginAttributeNotFoundError(
|
||||||
|
"用于导入导出数据的类`{cls_name}`必须定义一个`supported_formats`属性。".format(
|
||||||
|
cls_name=cls.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def can_handle_file(self, file_path: Path) -> bool:
|
||||||
|
"""判断是否可处理某个文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
待检测的文件路径
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
bool
|
||||||
|
若文件后缀已在本类中定义,则返回 True
|
||||||
|
"""
|
||||||
|
return file_path.suffix.upper().endswith(self.supported_formats)
|
||||||
|
|
||||||
|
def can_handle_format(self, format_name: str) -> bool:
|
||||||
|
"""判断是否可处理某个格式
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
format_name: str
|
||||||
|
格式名称(如 'MIDI', 'WAV')
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
bool
|
||||||
|
若格式名本类中已经定义,则返回 True
|
||||||
|
"""
|
||||||
|
return format_name.upper().endswith(self.supported_formats)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicInputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导入用插件抽象基类-完整曲目"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_IMPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
"""从字节流加载数据到完整曲目
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
bytes_buffer_in: BinaryIO
|
||||||
|
输入的二进制字节流
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleMusic
|
||||||
|
解析得到的完整曲目对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||||
|
"""从文件加载数据到完整曲目
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
输入文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleMusic
|
||||||
|
解析得到的完整曲目对象
|
||||||
|
"""
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
class TrackInputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导入用插件抽象基类-单个音轨"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_IMPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
"""从字节流加载音符数据到单个音轨
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
bytes_buffer_in: BinaryIO
|
||||||
|
输入的二进制字节流
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleTrack
|
||||||
|
解析得到的单个音轨对象
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
|
||||||
|
"""从文件加载音符数据到单个音轨
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
file_path: Path
|
||||||
|
输入文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleTrack
|
||||||
|
解析得到的单个音轨对象
|
||||||
|
"""
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicOperatePluginBase(TopPluginBase, ABC):
|
||||||
|
"""音乐处理用插件抽象基类-完整曲目"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_OPERATE:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def process(
|
||||||
|
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
"""处理完整曲目的数据
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleMusic
|
||||||
|
待处理的完整曲目
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleMusic
|
||||||
|
处理后的完整曲目
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TrackOperatePluginBase(TopPluginBase, ABC):
|
||||||
|
"""音乐处理用插件抽象基类-单个音轨"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_OPERATE:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def process(
|
||||||
|
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
"""处理单个音轨的音符数据
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleTrack
|
||||||
|
待处理的单个音轨
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
SingleTrack
|
||||||
|
处理后的单个音轨
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导出用插件的抽象基类-完整曲目"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_EXPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stream_dump(
|
||||||
|
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
"""将完整曲目导出为对应格式的字节流
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleMusic
|
||||||
|
待导出的完整曲目
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
Iterator[bytes]
|
||||||
|
分块导出的二进制字节串
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dump(
|
||||||
|
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||||
|
):
|
||||||
|
"""将完整曲目导出为对应格式的文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleMusic
|
||||||
|
待导出的完整曲目
|
||||||
|
file_path: Path
|
||||||
|
输出文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
"""
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||||
|
"""导出用插件的抽象基类-单个音轨"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_EXPORT:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stream_dump(
|
||||||
|
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
"""将单个音轨导出为对应格式的字节流
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleTrack
|
||||||
|
待导出的单个音轨
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
|
||||||
|
返回
|
||||||
|
====
|
||||||
|
Iterator[bytes]
|
||||||
|
分块导出的二进制字节串
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dump(
|
||||||
|
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||||
|
):
|
||||||
|
"""将单个音轨导出为对应格式的文件
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
data: SingleTrack
|
||||||
|
待导出的单个音轨
|
||||||
|
file_path: Path
|
||||||
|
输出文件路径
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
"""
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
class ServicePluginBase(TopPluginBase, ABC):
|
||||||
|
"""服务插件抽象基类"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.SERVICE:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def serve(self, config: Optional[PluginConfig]) -> None:
|
||||||
|
"""服务插件的运行逻辑
|
||||||
|
|
||||||
|
参数
|
||||||
|
====
|
||||||
|
config: Optional[PluginConfig]
|
||||||
|
插件配置;**可选**
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryPluginBase(TopPluginBase, ABC):
|
||||||
|
"""插件依赖库的抽象基类"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
if cls.metainfo.type != PluginTypes.LIBRARY:
|
||||||
|
raise PluginMetainfoValueError(
|
||||||
|
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||||
|
cls_name=cls.__name__,
|
||||||
|
cls_type=cls.metainfo.type.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 怎么?
|
||||||
|
# 插件的依赖项就不需要什么调用了吧
|
||||||
32
Musicreater/_utils.py
Normal file
32
Musicreater/_utils.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 的功能性内容合辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from copy import deepcopy, copy
|
||||||
|
from typing import Any, Dict, Generator, List, Optional, Tuple, Union, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
def enumerated_stuffcopy_dictionary(
|
||||||
|
enumeration_times: int = 17, staff: T = {}
|
||||||
|
) -> Dict[int, T]:
|
||||||
|
"""
|
||||||
|
生成一个字典,其中键从 `0` 到 `enumeration_times-1`,值是 `staff` 的拷贝
|
||||||
|
"""
|
||||||
|
# 这告诉我们,你不能忽略任何一个复制的序列,因为它真的,我哭死,折磨我一整天,全在这个bug上了
|
||||||
|
# 上面的这指的是 copy.deepcopy —— 金羿 来自 20260210
|
||||||
|
return {i: deepcopy(staff) for i in range(enumeration_times)}
|
||||||
61
Musicreater/builtin_plugins/cli_seek/__init__.py
Normal file
61
Musicreater/builtin_plugins/cli_seek/__init__.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的乐曲查看器
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from TrimMCStruct import Structure
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicOperatePluginBase,
|
||||||
|
music_operate_plugin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TerminalSeekerConfig(PluginConfig):
|
||||||
|
"""
|
||||||
|
终端查看器配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@music_operate_plugin("music_terminal_seeker")
|
||||||
|
class TerminalSeekerPlugin(MusicOperatePluginBase):
|
||||||
|
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="基于终端的音乐查看器",
|
||||||
|
author="金羿",
|
||||||
|
description="将乐曲信息输出到终端",
|
||||||
|
version=(0,0,1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_OPERATE,
|
||||||
|
license="Same as Musiccreater",
|
||||||
|
dependencies=(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def process(
|
||||||
|
self, data: "SingleMusic", config: TerminalSeekerConfig
|
||||||
|
) -> "SingleMusic":
|
||||||
|
...
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from .main import McstructureExportConfig, NoteDataConvert2CommandPlugin
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"McstructureExportConfig",
|
||||||
|
"NoteDataConvert2CommandPlugin",
|
||||||
|
]
|
||||||
103
Musicreater/builtin_plugins/commands_to_structure/addon.py
Normal file
103
Musicreater/builtin_plugins/commands_to_structure/addon.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件中有关附加包操作的内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import 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
Musicreater/builtin_plugins/commands_to_structure/bdx.py
Normal file
229
Musicreater/builtin_plugins/commands_to_structure/bdx.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件中有关 BDX 结构操作的内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.to_commands import MineCommand
|
||||||
|
from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z
|
||||||
|
|
||||||
|
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,
|
||||||
|
(
|
||||||
|
(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,
|
||||||
|
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],
|
||||||
|
)
|
||||||
44
Musicreater/builtin_plugins/commands_to_structure/common.py
Normal file
44
Musicreater/builtin_plugins/commands_to_structure/common.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
存放通用的普遍性的插件内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
x = "x"
|
||||||
|
y = "y"
|
||||||
|
z = "z"
|
||||||
|
|
||||||
|
|
||||||
|
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)))
|
||||||
148
Musicreater/builtin_plugins/commands_to_structure/main.py
Normal file
148
Musicreater/builtin_plugins/commands_to_structure/main.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from TrimMCStruct import Structure
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_output_plugin,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
track_output_plugin,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.to_commands import (
|
||||||
|
MineCommand,
|
||||||
|
NoteDataConvert2CommandPlugin,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .mcstructure import (
|
||||||
|
COMPABILITY_VERSION_117,
|
||||||
|
COMPABILITY_VERSION_119,
|
||||||
|
commands_to_structure,
|
||||||
|
commands_to_redstone_delay_structure,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class McstructureExportConfig(PluginConfig):
|
||||||
|
"""
|
||||||
|
导出 MCSTRUCTURE 结构插件的配置类
|
||||||
|
"""
|
||||||
|
|
||||||
|
music_deviation: float = 0
|
||||||
|
"""
|
||||||
|
全曲音调偏移调整,单位为 Midi Pitch
|
||||||
|
"""
|
||||||
|
minimum_volume: float = 0.01
|
||||||
|
"""
|
||||||
|
指令参数:最小音量
|
||||||
|
"""
|
||||||
|
player_selector: str = "@a"
|
||||||
|
"""
|
||||||
|
玩家选择器
|
||||||
|
"""
|
||||||
|
max_height: int = 64
|
||||||
|
"""
|
||||||
|
生成结构的最大高度
|
||||||
|
"""
|
||||||
|
enable_old_execute_format: bool = False
|
||||||
|
"""
|
||||||
|
是否使用旧版指令格式
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def execute_command_head(self) -> str:
|
||||||
|
return (
|
||||||
|
"execute {} ~ ~ ~ "
|
||||||
|
if self.enable_old_execute_format
|
||||||
|
else "execute as {} at @s positioned ~ ~ ~ run "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@music_output_plugin("music_to_mcstructure_in_delay_plugin")
|
||||||
|
class MusicExportToMcstructureWithDelayPlayerPlugin(MusicOutputPluginBase):
|
||||||
|
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="导出全曲结构插件(mcstructure结构、延迟播放器)",
|
||||||
|
author="金羿、玉衡Alioth",
|
||||||
|
description="将音·创 v3 的整首音乐数据,以指令方块延迟的播放形式,导出为基岩版 MCSTRUCTURE 结构",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_EXPORT,
|
||||||
|
license="Same as Musicreater",
|
||||||
|
dependencies=("notedata_to_command_plugin",),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("MCSTRUCTURE",)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _go_convertion(
|
||||||
|
data: SingleMusic, config: McstructureExportConfig
|
||||||
|
) -> Tuple[Structure, Tuple[int, int, int], int]:
|
||||||
|
|
||||||
|
command_list, max_delay = (
|
||||||
|
NoteDataConvert2CommandPlugin.to_command_list_in_delay(
|
||||||
|
music=data,
|
||||||
|
music_deviation=config.music_deviation,
|
||||||
|
minimum_volume=config.minimum_volume,
|
||||||
|
player_selector=config.player_selector,
|
||||||
|
execute_command_head=config.execute_command_head,
|
||||||
|
)[:2]
|
||||||
|
)
|
||||||
|
|
||||||
|
struct, size, end_pos = commands_to_structure(
|
||||||
|
command_list,
|
||||||
|
config.max_height - 1,
|
||||||
|
compability_version_=(
|
||||||
|
COMPABILITY_VERSION_117
|
||||||
|
if config.enable_old_execute_format
|
||||||
|
else COMPABILITY_VERSION_119
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return struct, size, max_delay
|
||||||
|
|
||||||
|
def dump(self, data: SingleMusic, file_path: Path, config: McstructureExportConfig):
|
||||||
|
|
||||||
|
struct, size, max_delay = self._go_convertion(data, config)
|
||||||
|
|
||||||
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
struct.dump(f)
|
||||||
|
|
||||||
|
return size, max_delay
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleMusic, config: McstructureExportConfig
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
struct, size, max_delay = self._go_convertion(data, config)
|
||||||
|
|
||||||
|
b_out = BytesIO()
|
||||||
|
struct.dump(b_out)
|
||||||
|
b_out.seek(0)
|
||||||
|
|
||||||
|
yield from b_out
|
||||||
552
Musicreater/builtin_plugins/commands_to_structure/mcstructure.py
Normal file
552
Musicreater/builtin_plugins/commands_to_structure/mcstructure.py
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Minecraft 结构生成插件中有关 MCSTRUCTURE 结构操作的内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from typing import List, Literal, Tuple
|
||||||
|
|
||||||
|
from TrimMCStruct import Block, Structure, TAG_Byte, TAG_Long
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.to_commands import MineCommand
|
||||||
|
from .common import bottem_side_length_of_smallest_square_bottom_box, x, y, z
|
||||||
|
|
||||||
|
|
||||||
|
def antiaxis(axis: Literal["x", "z", "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]
|
||||||
|
结构类, 结构占用大小, 终点坐标
|
||||||
|
"""
|
||||||
|
|
||||||
|
_side_length = bottem_side_length_of_smallest_square_bottom_box(
|
||||||
|
len(commands), max_height
|
||||||
|
)
|
||||||
|
|
||||||
|
struct = Structure(
|
||||||
|
size=(_side_length, max_height, _side_length), # 声明结构大小
|
||||||
|
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,
|
||||||
|
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 != _side_length - 1))
|
||||||
|
)
|
||||||
|
else 5
|
||||||
|
)
|
||||||
|
),
|
||||||
|
impluse=2,
|
||||||
|
condition=False,
|
||||||
|
alwaysRun=True,
|
||||||
|
tickDelay=command.delay,
|
||||||
|
customName=command.annotation,
|
||||||
|
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 >= _side_length) 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,
|
||||||
|
_side_length 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,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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
|
||||||
64
Musicreater/builtin_plugins/midi_read/__init__.py
Normal file
64
Musicreater/builtin_plugins/midi_read/__init__.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth、偷吃不是Touch
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth, Touch
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from .main import MidiImportConfig, MidiImport2MusicPlugin
|
||||||
|
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .utils import (
|
||||||
|
volume_2_distance_natural,
|
||||||
|
volume_2_distance_straight,
|
||||||
|
panning_2_rotation_linear,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# 插件参数和插件本体类
|
||||||
|
"MidiImportConfig",
|
||||||
|
"MidiImport2MusicPlugin",
|
||||||
|
# 内置的拟合函数
|
||||||
|
"volume_2_distance_straight",
|
||||||
|
"volume_2_distance_natural",
|
||||||
|
"panning_2_rotation_linear",
|
||||||
|
"panning_2_rotation_trigonometric",
|
||||||
|
# Midi 相关默认值
|
||||||
|
"MIDI_DEFAULT_PROGRAM_VALUE",
|
||||||
|
"MIDI_DEFAULT_VOLUME_VALUE",
|
||||||
|
# Midi 与 游戏内容 的对照表
|
||||||
|
"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",
|
||||||
|
]
|
||||||
797
Musicreater/builtin_plugins/midi_read/constants.py
Normal file
797
Musicreater/builtin_plugins/midi_read/constants.py
Normal file
@@ -0,0 +1,797 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件的数值常量
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth、偷吃不是Touch
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth, Touch
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# Midi用对照表
|
||||||
|
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE: int = (
|
||||||
|
64 # Midi默认音量,当用户未指定时,默认使用折中默认音量
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE: int = (
|
||||||
|
74 # 当 Midi 本身与用户皆未指定音色时,默认 Flute 长笛
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Midi乐器对MC乐器对照表
|
||||||
|
|
||||||
|
# “经典”对照表,由 Chalie Ping “查理平” 和 金羿ELS 提供
|
||||||
|
|
||||||
|
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.harp",
|
||||||
|
2: "note.pling",
|
||||||
|
3: "note.harp",
|
||||||
|
4: "note.pling",
|
||||||
|
5: "note.pling",
|
||||||
|
6: "note.harp",
|
||||||
|
7: "note.harp",
|
||||||
|
8: "note.snare",
|
||||||
|
9: "note.harp",
|
||||||
|
10: "note.didgeridoo",
|
||||||
|
11: "note.harp",
|
||||||
|
12: "note.xylophone",
|
||||||
|
13: "note.chime",
|
||||||
|
14: "note.harp",
|
||||||
|
15: "note.harp",
|
||||||
|
16: "note.bass",
|
||||||
|
17: "note.harp",
|
||||||
|
18: "note.harp",
|
||||||
|
19: "note.harp",
|
||||||
|
20: "note.harp",
|
||||||
|
21: "note.harp",
|
||||||
|
22: "note.harp",
|
||||||
|
23: "note.guitar",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.guitar",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.guitar",
|
||||||
|
31: "note.bass",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.bass",
|
||||||
|
34: "note.bass",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.bass",
|
||||||
|
37: "note.bass",
|
||||||
|
38: "note.bass",
|
||||||
|
39: "note.bass",
|
||||||
|
40: "note.harp",
|
||||||
|
41: "note.harp",
|
||||||
|
42: "note.harp",
|
||||||
|
43: "note.harp",
|
||||||
|
44: "note.iron_xylophone",
|
||||||
|
45: "note.guitar",
|
||||||
|
46: "note.harp",
|
||||||
|
47: "note.harp",
|
||||||
|
48: "note.guitar",
|
||||||
|
49: "note.guitar",
|
||||||
|
50: "note.bit",
|
||||||
|
51: "note.bit",
|
||||||
|
52: "note.harp",
|
||||||
|
53: "note.harp",
|
||||||
|
54: "note.bit",
|
||||||
|
55: "note.flute",
|
||||||
|
56: "note.flute",
|
||||||
|
57: "note.flute",
|
||||||
|
58: "note.flute",
|
||||||
|
59: "note.flute",
|
||||||
|
60: "note.flute",
|
||||||
|
61: "note.flute",
|
||||||
|
62: "note.flute",
|
||||||
|
63: "note.flute",
|
||||||
|
64: "note.bit",
|
||||||
|
65: "note.bit",
|
||||||
|
66: "note.bit",
|
||||||
|
67: "note.bit",
|
||||||
|
68: "note.flute",
|
||||||
|
69: "note.harp",
|
||||||
|
70: "note.harp",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.harp",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.harp",
|
||||||
|
77: "note.harp",
|
||||||
|
78: "note.harp",
|
||||||
|
79: "note.harp",
|
||||||
|
80: "note.bit",
|
||||||
|
81: "note.bit",
|
||||||
|
82: "note.bit",
|
||||||
|
83: "note.bit",
|
||||||
|
84: "note.bit",
|
||||||
|
85: "note.bit",
|
||||||
|
86: "note.bit",
|
||||||
|
87: "note.bit",
|
||||||
|
88: "note.bit",
|
||||||
|
89: "note.bit",
|
||||||
|
90: "note.bit",
|
||||||
|
91: "note.bit",
|
||||||
|
92: "note.bit",
|
||||||
|
93: "note.bit",
|
||||||
|
94: "note.bit",
|
||||||
|
95: "note.bit",
|
||||||
|
96: "note.bit",
|
||||||
|
97: "note.bit",
|
||||||
|
98: "note.bit",
|
||||||
|
99: "note.bit",
|
||||||
|
100: "note.bit",
|
||||||
|
101: "note.bit",
|
||||||
|
102: "note.bit",
|
||||||
|
103: "note.bit",
|
||||||
|
104: "note.harp",
|
||||||
|
105: "note.banjo",
|
||||||
|
106: "note.harp",
|
||||||
|
107: "note.harp",
|
||||||
|
108: "note.harp",
|
||||||
|
109: "note.harp",
|
||||||
|
110: "note.harp",
|
||||||
|
111: "note.guitar",
|
||||||
|
112: "note.harp",
|
||||||
|
113: "note.bell",
|
||||||
|
114: "note.harp",
|
||||||
|
115: "note.cow_bell",
|
||||||
|
116: "note.bd",
|
||||||
|
117: "note.bass",
|
||||||
|
118: "note.bit",
|
||||||
|
119: "note.bd",
|
||||||
|
120: "note.guitar",
|
||||||
|
121: "note.harp",
|
||||||
|
122: "note.harp",
|
||||||
|
123: "note.harp",
|
||||||
|
124: "note.harp",
|
||||||
|
125: "note.hat",
|
||||||
|
126: "note.bd",
|
||||||
|
127: "note.snare",
|
||||||
|
}
|
||||||
|
"""“经典”乐音乐器对照表"""
|
||||||
|
|
||||||
|
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
34: "note.bd",
|
||||||
|
35: "note.bd",
|
||||||
|
36: "note.hat",
|
||||||
|
37: "note.snare",
|
||||||
|
38: "note.snare",
|
||||||
|
39: "note.snare",
|
||||||
|
40: "note.hat",
|
||||||
|
41: "note.snare",
|
||||||
|
42: "note.hat",
|
||||||
|
43: "note.snare",
|
||||||
|
44: "note.snare",
|
||||||
|
45: "note.bell",
|
||||||
|
46: "note.snare",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.bell",
|
||||||
|
49: "note.hat",
|
||||||
|
50: "note.bell",
|
||||||
|
51: "note.bell",
|
||||||
|
52: "note.bell",
|
||||||
|
53: "note.bell",
|
||||||
|
54: "note.bell",
|
||||||
|
55: "note.bell",
|
||||||
|
56: "note.snare",
|
||||||
|
57: "note.hat",
|
||||||
|
58: "note.chime",
|
||||||
|
59: "note.iron_xylophone",
|
||||||
|
60: "note.bd",
|
||||||
|
61: "note.bd",
|
||||||
|
62: "note.xylophone",
|
||||||
|
63: "note.xylophone",
|
||||||
|
64: "note.xylophone",
|
||||||
|
65: "note.hat",
|
||||||
|
66: "note.bell",
|
||||||
|
67: "note.bell",
|
||||||
|
68: "note.hat",
|
||||||
|
69: "note.hat",
|
||||||
|
70: "note.snare",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.hat",
|
||||||
|
73: "note.hat",
|
||||||
|
74: "note.xylophone",
|
||||||
|
75: "note.hat",
|
||||||
|
76: "note.hat",
|
||||||
|
77: "note.xylophone",
|
||||||
|
78: "note.xylophone",
|
||||||
|
79: "note.bell",
|
||||||
|
80: "note.bell",
|
||||||
|
}
|
||||||
|
"""“经典”打击乐器对照表"""
|
||||||
|
|
||||||
|
# Touch “偷吃” 高准确率音色对照表
|
||||||
|
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.harp",
|
||||||
|
2: "note.pling",
|
||||||
|
3: "note.harp",
|
||||||
|
4: "note.pling",
|
||||||
|
5: "note.pling",
|
||||||
|
6: "note.guitar",
|
||||||
|
7: "note.guitar",
|
||||||
|
8: "note.iron_xylophone",
|
||||||
|
9: "note.bell",
|
||||||
|
10: "note.iron_xylophone",
|
||||||
|
11: "note.iron_xylophone",
|
||||||
|
12: "note.iron_xylophone",
|
||||||
|
13: "note.xylophone",
|
||||||
|
14: "note.chime",
|
||||||
|
15: "note.banjo",
|
||||||
|
16: "note.xylophone",
|
||||||
|
17: "note.iron_xylophone",
|
||||||
|
18: "note.flute",
|
||||||
|
19: "note.flute",
|
||||||
|
20: "note.flute",
|
||||||
|
21: "note.flute",
|
||||||
|
22: "note.flute",
|
||||||
|
23: "note.flute",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.guitar",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.guitar",
|
||||||
|
31: "note.bass",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.bass",
|
||||||
|
34: "note.bass",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.bass",
|
||||||
|
37: "note.bass",
|
||||||
|
38: "note.bass",
|
||||||
|
39: "note.bass",
|
||||||
|
40: "note.flute",
|
||||||
|
41: "note.flute",
|
||||||
|
42: "note.flute",
|
||||||
|
43: "note.bass",
|
||||||
|
44: "note.flute",
|
||||||
|
45: "note.iron_xylophone",
|
||||||
|
46: "note.harp",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.flute",
|
||||||
|
49: "note.flute",
|
||||||
|
50: "note.flute",
|
||||||
|
51: "note.flute",
|
||||||
|
52: "note.didgeridoo",
|
||||||
|
53: "note.flute",
|
||||||
|
54: "note.flute",
|
||||||
|
55: "mob.zombie.wood",
|
||||||
|
56: "note.flute",
|
||||||
|
57: "note.flute",
|
||||||
|
58: "note.flute",
|
||||||
|
59: "note.flute",
|
||||||
|
60: "note.flute",
|
||||||
|
61: "note.flute",
|
||||||
|
62: "note.flute",
|
||||||
|
63: "note.flute",
|
||||||
|
64: "note.bit",
|
||||||
|
65: "note.bit",
|
||||||
|
66: "note.bit",
|
||||||
|
67: "note.bit",
|
||||||
|
68: "note.flute",
|
||||||
|
69: "note.bit",
|
||||||
|
70: "note.banjo",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.flute",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.iron_xylophone",
|
||||||
|
77: "note.iron_xylophone",
|
||||||
|
78: "note.flute",
|
||||||
|
79: "note.flute",
|
||||||
|
80: "note.bit",
|
||||||
|
81: "note.bit",
|
||||||
|
82: "note.flute",
|
||||||
|
83: "note.flute",
|
||||||
|
84: "note.guitar",
|
||||||
|
85: "note.flute",
|
||||||
|
86: "note.bass",
|
||||||
|
87: "note.bass",
|
||||||
|
88: "note.bit",
|
||||||
|
89: "note.flute",
|
||||||
|
90: "note.bit",
|
||||||
|
91: "note.flute",
|
||||||
|
92: "note.bell",
|
||||||
|
93: "note.guitar",
|
||||||
|
94: "note.flute",
|
||||||
|
95: "note.bit",
|
||||||
|
96: "note.bit",
|
||||||
|
97: "note.flute",
|
||||||
|
98: "note.bell",
|
||||||
|
99: "note.bit",
|
||||||
|
100: "note.bit",
|
||||||
|
101: "note.bit",
|
||||||
|
102: "note.bit",
|
||||||
|
103: "note.bit",
|
||||||
|
104: "note.iron_xylophone",
|
||||||
|
105: "note.banjo",
|
||||||
|
106: "note.harp",
|
||||||
|
107: "note.harp",
|
||||||
|
108: "note.bell",
|
||||||
|
109: "note.flute",
|
||||||
|
110: "note.flute",
|
||||||
|
111: "note.flute",
|
||||||
|
112: "note.bell",
|
||||||
|
113: "note.xylophone",
|
||||||
|
114: "note.flute",
|
||||||
|
115: "note.hat",
|
||||||
|
116: "note.snare",
|
||||||
|
117: "note.snare",
|
||||||
|
118: "note.bd",
|
||||||
|
119: "firework.blast",
|
||||||
|
120: "note.guitar",
|
||||||
|
121: "note.harp",
|
||||||
|
122: "note.harp",
|
||||||
|
123: "note.harp",
|
||||||
|
124: "note.bit",
|
||||||
|
125: "note.hat",
|
||||||
|
126: "firework.twinkle",
|
||||||
|
127: "mob.zombie.wood",
|
||||||
|
}
|
||||||
|
"""“偷吃”乐音乐器对照表"""
|
||||||
|
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
34: "note.hat",
|
||||||
|
35: "note.bd",
|
||||||
|
36: "note.bd",
|
||||||
|
37: "note.snare",
|
||||||
|
38: "note.snare",
|
||||||
|
39: "fire.ignite",
|
||||||
|
40: "note.snare",
|
||||||
|
41: "note.hat",
|
||||||
|
42: "note.hat",
|
||||||
|
43: "firework.blast",
|
||||||
|
44: "note.hat",
|
||||||
|
45: "note.snare",
|
||||||
|
46: "note.snare",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.bell",
|
||||||
|
49: "note.hat",
|
||||||
|
50: "note.bell",
|
||||||
|
51: "note.bell",
|
||||||
|
52: "note.bell",
|
||||||
|
53: "note.bell",
|
||||||
|
54: "note.bell",
|
||||||
|
55: "note.bell",
|
||||||
|
56: "note.snare",
|
||||||
|
57: "note.hat",
|
||||||
|
58: "note.chime",
|
||||||
|
59: "note.iron_xylophone",
|
||||||
|
60: "note.bd",
|
||||||
|
61: "note.bd",
|
||||||
|
62: "note.xylophone",
|
||||||
|
63: "note.xylophone",
|
||||||
|
64: "note.xylophone",
|
||||||
|
65: "note.hat",
|
||||||
|
66: "note.bell",
|
||||||
|
67: "note.bell",
|
||||||
|
68: "note.hat",
|
||||||
|
69: "note.hat",
|
||||||
|
70: "note.snare",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.hat",
|
||||||
|
73: "note.hat",
|
||||||
|
74: "note.xylophone",
|
||||||
|
75: "note.hat",
|
||||||
|
76: "note.hat",
|
||||||
|
77: "note.xylophone",
|
||||||
|
78: "note.xylophone",
|
||||||
|
79: "note.bell",
|
||||||
|
80: "note.bell",
|
||||||
|
}
|
||||||
|
"""“偷吃”打击乐器对照表"""
|
||||||
|
|
||||||
|
# Dislink “断联” 音色对照表
|
||||||
|
# https://github.com/Dislink/midi2bdx/blob/main/index.html
|
||||||
|
|
||||||
|
|
||||||
|
MM_DISLINK_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.harp",
|
||||||
|
2: "note.pling",
|
||||||
|
3: "note.harp",
|
||||||
|
4: "note.harp",
|
||||||
|
5: "note.harp",
|
||||||
|
6: "note.harp",
|
||||||
|
7: "note.harp",
|
||||||
|
8: "note.iron_xylophone",
|
||||||
|
9: "note.bell",
|
||||||
|
10: "note.iron_xylophone",
|
||||||
|
11: "note.iron_xylophone",
|
||||||
|
12: "note.iron_xylophone",
|
||||||
|
13: "note.iron_xylophone",
|
||||||
|
14: "note.chime",
|
||||||
|
15: "note.iron_xylophone",
|
||||||
|
16: "note.harp",
|
||||||
|
17: "note.harp",
|
||||||
|
18: "note.harp",
|
||||||
|
19: "note.harp",
|
||||||
|
20: "note.harp",
|
||||||
|
21: "note.harp",
|
||||||
|
22: "note.harp",
|
||||||
|
23: "note.harp",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.guitar",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.guitar",
|
||||||
|
31: "note.guitar",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.bass",
|
||||||
|
34: "note.bass",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.bass",
|
||||||
|
37: "note.bass",
|
||||||
|
38: "note.bass",
|
||||||
|
39: "note.bass",
|
||||||
|
40: "note.harp",
|
||||||
|
41: "note.flute",
|
||||||
|
42: "note.flute",
|
||||||
|
43: "note.flute",
|
||||||
|
44: "note.flute",
|
||||||
|
45: "note.harp",
|
||||||
|
46: "note.harp",
|
||||||
|
47: "note.harp",
|
||||||
|
48: "note.harp",
|
||||||
|
49: "note.harp",
|
||||||
|
50: "note.harp",
|
||||||
|
51: "note.harp",
|
||||||
|
52: "note.harp",
|
||||||
|
53: "note.harp",
|
||||||
|
54: "note.harp",
|
||||||
|
55: "note.harp",
|
||||||
|
56: "note.harp",
|
||||||
|
57: "note.harp",
|
||||||
|
58: "note.harp",
|
||||||
|
59: "note.harp",
|
||||||
|
60: "note.harp",
|
||||||
|
61: "note.harp",
|
||||||
|
62: "note.harp",
|
||||||
|
63: "note.harp",
|
||||||
|
64: "note.harp",
|
||||||
|
65: "note.harp",
|
||||||
|
66: "note.harp",
|
||||||
|
67: "note.harp",
|
||||||
|
68: "note.harp",
|
||||||
|
69: "note.harp",
|
||||||
|
70: "note.harp",
|
||||||
|
71: "note.harp",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.flute",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.flute",
|
||||||
|
77: "note.flute",
|
||||||
|
78: "note.flute",
|
||||||
|
79: "note.flute",
|
||||||
|
80: "note.bit",
|
||||||
|
81: "note.bit",
|
||||||
|
82: "note.harp",
|
||||||
|
83: "note.harp",
|
||||||
|
84: "note.harp",
|
||||||
|
85: "note.harp",
|
||||||
|
86: "note.harp",
|
||||||
|
87: "note.harp",
|
||||||
|
88: "note.harp",
|
||||||
|
89: "note.harp",
|
||||||
|
90: "note.harp",
|
||||||
|
91: "note.harp",
|
||||||
|
92: "note.harp",
|
||||||
|
93: "note.harp",
|
||||||
|
94: "note.harp",
|
||||||
|
95: "note.harp",
|
||||||
|
96: "note.harp",
|
||||||
|
97: "note.harp",
|
||||||
|
98: "note.harp",
|
||||||
|
99: "note.harp",
|
||||||
|
100: "note.harp",
|
||||||
|
101: "note.harp",
|
||||||
|
102: "note.harp",
|
||||||
|
103: "note.harp",
|
||||||
|
104: "note.harp",
|
||||||
|
105: "note.banjo",
|
||||||
|
106: "note.harp",
|
||||||
|
107: "note.harp",
|
||||||
|
108: "note.harp",
|
||||||
|
109: "note.harp",
|
||||||
|
110: "note.harp",
|
||||||
|
111: "note.harp",
|
||||||
|
112: "note.cow_bell",
|
||||||
|
113: "note.harp",
|
||||||
|
114: "note.harp",
|
||||||
|
115: "note.bd",
|
||||||
|
116: "note.bd",
|
||||||
|
117: "note.bd",
|
||||||
|
118: "note.bd",
|
||||||
|
119: "note.harp",
|
||||||
|
120: "note.harp",
|
||||||
|
121: "note.harp",
|
||||||
|
122: "note.harp",
|
||||||
|
123: "note.harp",
|
||||||
|
124: "note.harp",
|
||||||
|
125: "note.harp",
|
||||||
|
126: "note.harp",
|
||||||
|
127: "note.harp",
|
||||||
|
}
|
||||||
|
"""“断联”乐音乐器对照表"""
|
||||||
|
|
||||||
|
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
34: "note.bd",
|
||||||
|
35: "note.bd",
|
||||||
|
36: "note.snare",
|
||||||
|
37: "note.snare",
|
||||||
|
38: "note.bd",
|
||||||
|
39: "note.snare",
|
||||||
|
40: "note.bd",
|
||||||
|
41: "note.hat",
|
||||||
|
42: "note.bd",
|
||||||
|
43: "note.hat",
|
||||||
|
44: "note.bd",
|
||||||
|
45: "note.hat",
|
||||||
|
46: "note.bd",
|
||||||
|
47: "note.bd",
|
||||||
|
48: "note.bd",
|
||||||
|
49: "note.bd",
|
||||||
|
50: "note.bd",
|
||||||
|
51: "note.bd",
|
||||||
|
52: "note.bd",
|
||||||
|
53: "note.bd",
|
||||||
|
54: "note.bd",
|
||||||
|
55: "note.cow_bell",
|
||||||
|
56: "note.bd",
|
||||||
|
57: "note.bd",
|
||||||
|
58: "note.bd",
|
||||||
|
59: "note.bd",
|
||||||
|
60: "note.bd",
|
||||||
|
61: "note.bd",
|
||||||
|
62: "note.bd",
|
||||||
|
63: "note.bd",
|
||||||
|
64: "note.bd",
|
||||||
|
65: "note.bd",
|
||||||
|
66: "note.bd",
|
||||||
|
67: "note.bd",
|
||||||
|
68: "note.bd",
|
||||||
|
69: "note.bd",
|
||||||
|
70: "note.bd",
|
||||||
|
71: "note.bd",
|
||||||
|
72: "note.bd",
|
||||||
|
73: "note.bd",
|
||||||
|
74: "note.bd",
|
||||||
|
75: "note.bd",
|
||||||
|
76: "note.bd",
|
||||||
|
77: "note.bd",
|
||||||
|
78: "note.bd",
|
||||||
|
79: "note.bd",
|
||||||
|
80: "note.bd",
|
||||||
|
}
|
||||||
|
"""“断联”打击乐器对照表"""
|
||||||
|
|
||||||
|
# NoteBlockStudio “NBS”音色对照表
|
||||||
|
# https://github.com/OpenNBS/NoteBlockStudio/blob/main/scripts/midi_instruments/midi_instruments.gml
|
||||||
|
# 此表来自于 Commit 1ab5357c197872495197f27ad8374d711b2a5195
|
||||||
|
# 需要更新:https://github.com/OpenNBS/NoteBlockStudio/compare/main...development?diff=unified&w
|
||||||
|
|
||||||
|
MM_NBS_PITCHED_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
0: "note.harp",
|
||||||
|
1: "note.pling",
|
||||||
|
2: "note.harp",
|
||||||
|
3: "note.pling",
|
||||||
|
4: "note.harp",
|
||||||
|
5: "note.harp",
|
||||||
|
6: "note.guitar",
|
||||||
|
7: "note.banjo",
|
||||||
|
8: "note.bell",
|
||||||
|
9: "note.bell",
|
||||||
|
10: "note.bell",
|
||||||
|
11: "note.iron_xylophone",
|
||||||
|
12: "note.iron_xylophone",
|
||||||
|
13: "note.xylophone",
|
||||||
|
14: "note.bell",
|
||||||
|
15: "note.iron_xylophone",
|
||||||
|
16: "note.flute",
|
||||||
|
17: "note.flute",
|
||||||
|
18: "note.flute",
|
||||||
|
19: "note.flute",
|
||||||
|
20: "note.flute",
|
||||||
|
21: "note.flute",
|
||||||
|
22: "note.flute",
|
||||||
|
23: "note.flute",
|
||||||
|
24: "note.guitar",
|
||||||
|
25: "note.guitar",
|
||||||
|
26: "note.guitar",
|
||||||
|
27: "note.bass",
|
||||||
|
28: "note.guitar",
|
||||||
|
29: "note.guitar",
|
||||||
|
30: "note.bass",
|
||||||
|
31: "note.bass",
|
||||||
|
32: "note.bass",
|
||||||
|
33: "note.guitar",
|
||||||
|
34: "note.guitar",
|
||||||
|
35: "note.bass",
|
||||||
|
36: "note.pling",
|
||||||
|
37: "note.flute",
|
||||||
|
38: "note.flute",
|
||||||
|
39: "note.flute",
|
||||||
|
40: "note.flute",
|
||||||
|
41: "note.flute",
|
||||||
|
42: "note.didgeridoo",
|
||||||
|
43: "note.flute",
|
||||||
|
44: "note.didgeridoo",
|
||||||
|
45: "note.flute",
|
||||||
|
46: "note.flute",
|
||||||
|
47: "note.flute",
|
||||||
|
48: "note.flute",
|
||||||
|
49: "note.flute",
|
||||||
|
50: "note.flute",
|
||||||
|
51: "note.flute",
|
||||||
|
52: "note.flute",
|
||||||
|
53: "note.flute",
|
||||||
|
54: "note.flute",
|
||||||
|
55: "note.flute",
|
||||||
|
56: "note.flute",
|
||||||
|
57: "note.flute",
|
||||||
|
58: "note.flute",
|
||||||
|
59: "note.flute",
|
||||||
|
60: "note.bit",
|
||||||
|
61: "note.flute",
|
||||||
|
62: "note.flute",
|
||||||
|
63: "note.flute",
|
||||||
|
64: "note.flute",
|
||||||
|
65: "note.guitar",
|
||||||
|
66: "note.flute",
|
||||||
|
67: "note.flute",
|
||||||
|
68: "note.flute",
|
||||||
|
69: "note.bell",
|
||||||
|
70: "note.flute",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.flute",
|
||||||
|
74: "note.chime",
|
||||||
|
75: "note.flute",
|
||||||
|
76: "note.flute",
|
||||||
|
77: "note.guitar",
|
||||||
|
78: "note.pling",
|
||||||
|
79: "note.flute",
|
||||||
|
80: "note.guitar",
|
||||||
|
81: "note.banjo",
|
||||||
|
82: "note.banjo",
|
||||||
|
83: "note.banjo",
|
||||||
|
84: "note.guitar",
|
||||||
|
85: "note.iron_xylophone",
|
||||||
|
86: "note.flute",
|
||||||
|
87: "note.flute",
|
||||||
|
88: "note.chime",
|
||||||
|
89: "note.cow_bell",
|
||||||
|
90: "note.iron_xylophone",
|
||||||
|
91: "note.xylophone",
|
||||||
|
92: "note.basedrum",
|
||||||
|
93: "note.snare",
|
||||||
|
94: "note.snare",
|
||||||
|
95: "note.basedrum",
|
||||||
|
96: "note.snare",
|
||||||
|
97: "note.hat",
|
||||||
|
98: "note.snare",
|
||||||
|
99: "note.hat",
|
||||||
|
100: "note.basedrum",
|
||||||
|
101: "note.hat",
|
||||||
|
102: "note.basedrum",
|
||||||
|
103: "note.hat",
|
||||||
|
104: "note.basedrum",
|
||||||
|
105: "note.snare",
|
||||||
|
106: "note.snare",
|
||||||
|
107: "note.snare",
|
||||||
|
108: "note.cow_bell",
|
||||||
|
109: "note.snare",
|
||||||
|
110: "note.hat",
|
||||||
|
111: "note.snare",
|
||||||
|
112: "note.hat",
|
||||||
|
113: "note.hat",
|
||||||
|
114: "note.hat",
|
||||||
|
115: "note.hat",
|
||||||
|
116: "note.hat",
|
||||||
|
117: "note.chime",
|
||||||
|
118: "note.hat",
|
||||||
|
119: "note.snare",
|
||||||
|
120: "note.hat",
|
||||||
|
121: "note.hat",
|
||||||
|
122: "note.hat",
|
||||||
|
123: "note.hat",
|
||||||
|
124: "note.hat",
|
||||||
|
125: "note.snare",
|
||||||
|
126: "note.basedrum",
|
||||||
|
127: "note.basedrum",
|
||||||
|
}
|
||||||
|
"""“NBS”乐音乐器对照表"""
|
||||||
|
|
||||||
|
|
||||||
|
MM_NBS_PERCUSSION_INSTRUMENT_TABLE: Dict[int, str] = {
|
||||||
|
24: "note.bit",
|
||||||
|
25: "note.snare",
|
||||||
|
26: "note.hat",
|
||||||
|
27: "note.snare",
|
||||||
|
28: "note.snare",
|
||||||
|
29: "note.hat",
|
||||||
|
30: "note.hat",
|
||||||
|
31: "note.hat",
|
||||||
|
32: "note.hat",
|
||||||
|
33: "note.hat",
|
||||||
|
34: "note.chime",
|
||||||
|
35: "note.basedrum",
|
||||||
|
36: "note.basedrum",
|
||||||
|
37: "note.hat",
|
||||||
|
38: "note.snare",
|
||||||
|
39: "note.hat",
|
||||||
|
40: "note.snare",
|
||||||
|
41: "note.basedrum",
|
||||||
|
42: "note.snare",
|
||||||
|
43: "note.basedrum",
|
||||||
|
44: "note.snare",
|
||||||
|
45: "note.basedrum",
|
||||||
|
46: "note.basedrum",
|
||||||
|
47: "note.snare",
|
||||||
|
48: "note.snare",
|
||||||
|
49: "note.snare",
|
||||||
|
50: "note.snare",
|
||||||
|
51: "note.snare",
|
||||||
|
52: "note.snare",
|
||||||
|
53: "note.hat",
|
||||||
|
54: "note.snare",
|
||||||
|
55: "note.snare",
|
||||||
|
56: "note.cow_bell",
|
||||||
|
57: "note.snare",
|
||||||
|
58: "note.hat",
|
||||||
|
59: "note.snare",
|
||||||
|
60: "note.hat",
|
||||||
|
61: "note.hat",
|
||||||
|
62: "note.hat",
|
||||||
|
63: "note.basedrum",
|
||||||
|
64: "note.basedrum",
|
||||||
|
65: "note.snare",
|
||||||
|
66: "note.snare",
|
||||||
|
67: "note.xylophone",
|
||||||
|
68: "note.xylophone",
|
||||||
|
69: "note.hat",
|
||||||
|
70: "note.hat",
|
||||||
|
71: "note.flute",
|
||||||
|
72: "note.flute",
|
||||||
|
73: "note.hat",
|
||||||
|
74: "note.hat",
|
||||||
|
75: "note.hat",
|
||||||
|
76: "note.hat",
|
||||||
|
77: "note.hat",
|
||||||
|
78: "note.didgeridoo",
|
||||||
|
79: "note.didgeridoo",
|
||||||
|
80: "note.hat",
|
||||||
|
81: "note.chime",
|
||||||
|
82: "note.hat",
|
||||||
|
83: "note.chime",
|
||||||
|
84: "note.chime",
|
||||||
|
85: "note.hat",
|
||||||
|
86: "note.basedrum",
|
||||||
|
87: "note.basedrum",
|
||||||
|
}
|
||||||
|
"""“NBS”打击乐器对照表"""
|
||||||
69
Musicreater/builtin_plugins/midi_read/exceptions.py
Normal file
69
Musicreater/builtin_plugins/midi_read/exceptions.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件用到的一些报错类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles & YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from Musicreater.exceptions import MusicreaterOuterlyError
|
||||||
|
|
||||||
|
|
||||||
|
class MidiFormatError(MusicreaterOuterlyError):
|
||||||
|
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音·创 的所有MIDI格式错误均继承于此"""
|
||||||
|
super().__init__("MIDI 格式错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NotDefineTempoError(MidiFormatError):
|
||||||
|
"""没有Tempo设定导致时间无法计算的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""没有Tempo设定导致时间无法计算的错误"""
|
||||||
|
super().__init__("在曲目开始时没有声明 Tempo(未指定拍长):", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelOverFlowError(MidiFormatError):
|
||||||
|
"""一个midi中含有过多的通道"""
|
||||||
|
|
||||||
|
def __init__(self, max_channel=16, *args):
|
||||||
|
"""一个midi中含有过多的通道"""
|
||||||
|
super().__init__("含有过多的通道(数量应≤{}):".format(max_channel), *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NotDefineProgramError(MidiFormatError):
|
||||||
|
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""没有Program设定导致没有乐器可以选择的错误"""
|
||||||
|
super().__init__("未指定演奏乐器:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteOnOffMismatchError(MidiFormatError):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音符开音和停止不匹配的错误"""
|
||||||
|
super().__init__("音符不匹配:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class LyricMismatchError(MidiFormatError):
|
||||||
|
"""歌词匹配解析错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""有可能产生了错误的歌词解析"""
|
||||||
|
super().__init__("歌词解析错误:", *args)
|
||||||
493
Musicreater/builtin_plugins/midi_read/main.py
Normal file
493
Musicreater/builtin_plugins/midi_read/main.py
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth、偷吃不是Touch
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth, Touch
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
import mido
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
)
|
||||||
|
from .exceptions import (
|
||||||
|
NoteOnOffMismatchError,
|
||||||
|
ChannelOverFlowError,
|
||||||
|
LyricMismatchError,
|
||||||
|
)
|
||||||
|
from .utils import (
|
||||||
|
volume_2_distance_natural,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
midi_msgs_to_noteinfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MidiImportConfig(PluginConfig):
|
||||||
|
"""Midi 音乐数据导入插件配置"""
|
||||||
|
|
||||||
|
# 系统设置
|
||||||
|
ignore_errors: bool = True
|
||||||
|
|
||||||
|
# 处理设置
|
||||||
|
speed_multiplier: float = 1.0
|
||||||
|
|
||||||
|
# 兼容不良 Midi 所定义的默认值
|
||||||
|
default_program_value: int = MIDI_DEFAULT_PROGRAM_VALUE
|
||||||
|
default_volume_value: int = MIDI_DEFAULT_VOLUME_VALUE
|
||||||
|
default_tempo_value: int = mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
|
||||||
|
# 对照表,此处 None 值在下边 post init 函数中有处理
|
||||||
|
pitched_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
|
percussion_note_reference_table: Mapping[int, str] = None # type: ignore
|
||||||
|
note_replacement_table: Mapping[str, str] = None # type: ignore
|
||||||
|
|
||||||
|
# 参数转换函数
|
||||||
|
volume_process_function: Callable[[float], float] = volume_2_distance_natural
|
||||||
|
panning_processing_function: Callable[[float], float] = (
|
||||||
|
panning_2_rotation_trigonometric
|
||||||
|
)
|
||||||
|
|
||||||
|
# 分轨方式
|
||||||
|
divide_tracks_by_miditrack: bool = True
|
||||||
|
divide_tracks_by_midichannel: bool = False
|
||||||
|
divide_tracks_by_soundname: bool = True
|
||||||
|
divide_tracks_by_volume: bool = False
|
||||||
|
divide_tracks_by_panning: bool = False
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.pitched_note_reference_table = (
|
||||||
|
self.pitched_note_reference_table
|
||||||
|
if self.pitched_note_reference_table
|
||||||
|
else MM_TOUCH_PITCHED_INSTRUMENT_TABLE
|
||||||
|
)
|
||||||
|
self.percussion_note_reference_table = (
|
||||||
|
self.percussion_note_reference_table
|
||||||
|
if self.percussion_note_reference_table
|
||||||
|
else MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE
|
||||||
|
)
|
||||||
|
self.note_replacement_table = (
|
||||||
|
self.note_replacement_table if self.note_replacement_table else {}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ControlerKeys(Enum):
|
||||||
|
"""
|
||||||
|
Midi 控制器键
|
||||||
|
"""
|
||||||
|
|
||||||
|
MIDI_PROGRAM = "midi_program"
|
||||||
|
MIDI_VOLUME = "midi_volume"
|
||||||
|
MIDI_PAN = "midi_pan"
|
||||||
|
|
||||||
|
|
||||||
|
class TrackDivisionDict(
|
||||||
|
Dict[
|
||||||
|
Tuple[
|
||||||
|
Optional[int],
|
||||||
|
Optional[int],
|
||||||
|
Optional[str],
|
||||||
|
Optional[float],
|
||||||
|
Optional[Tuple[float, float]],
|
||||||
|
],
|
||||||
|
SingleTrack,
|
||||||
|
]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
音轨分轨字典
|
||||||
|
键为音轨信息元组[音轨编号, 通道编号, 乐器名称, 音量, 声相]
|
||||||
|
值为音轨对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
division_by_miditrack: bool = True
|
||||||
|
division_by_midichannel: bool = False
|
||||||
|
division_by_soundname: bool = True
|
||||||
|
division_by_volume: bool = False
|
||||||
|
division_by_panning: bool = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
midi_import_config: MidiImportConfig = MidiImportConfig(),
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.division_by_miditrack = midi_import_config.divide_tracks_by_miditrack
|
||||||
|
self.division_by_midichannel = midi_import_config.divide_tracks_by_midichannel
|
||||||
|
self.division_by_soundname = midi_import_config.divide_tracks_by_soundname
|
||||||
|
self.division_by_volume = midi_import_config.divide_tracks_by_volume
|
||||||
|
self.division_by_panning = midi_import_config.divide_tracks_by_panning
|
||||||
|
|
||||||
|
def __getitem__(
|
||||||
|
self,
|
||||||
|
key: Tuple[
|
||||||
|
Optional[int],
|
||||||
|
Optional[int],
|
||||||
|
Optional[str],
|
||||||
|
Optional[float],
|
||||||
|
Optional[Tuple[float, float]],
|
||||||
|
],
|
||||||
|
) -> SingleTrack:
|
||||||
|
key = (
|
||||||
|
key[0] if self.division_by_miditrack else None,
|
||||||
|
key[1] if self.division_by_midichannel else None,
|
||||||
|
key[2] if self.division_by_soundname else None,
|
||||||
|
key[3] if self.division_by_volume else None,
|
||||||
|
key[4] if self.division_by_panning else None,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return super().__getitem__(key)
|
||||||
|
except KeyError:
|
||||||
|
self[key] = SingleTrack()
|
||||||
|
return self[key]
|
||||||
|
|
||||||
|
|
||||||
|
@music_input_plugin("midi_to_music_plugin")
|
||||||
|
class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||||
|
"""Midi 音乐数据导入插件"""
|
||||||
|
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="Midi 导入插件",
|
||||||
|
author="金羿、玉衡Alioth",
|
||||||
|
description="从 Midi 文件导入音乐数据",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||||
|
license="Same as Musicreater",
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("MID", "MIDI")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self,
|
||||||
|
bytes_buffer_in: BinaryIO,
|
||||||
|
config: Optional[MidiImportConfig] = MidiImportConfig(),
|
||||||
|
) -> SingleMusic:
|
||||||
|
return self.midifile_2_singlemusic(
|
||||||
|
mido.MidiFile(file=bytes_buffer_in, clip=True),
|
||||||
|
config if config else MidiImportConfig(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[MidiImportConfig] = MidiImportConfig()
|
||||||
|
) -> SingleMusic:
|
||||||
|
"""从 Midi 文件导入音乐数据"""
|
||||||
|
return self.midifile_2_singlemusic(
|
||||||
|
mido.MidiFile(filename=file_path, clip=True),
|
||||||
|
config if config else MidiImportConfig(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def midifile_2_singlemusic(
|
||||||
|
midi: mido.MidiFile,
|
||||||
|
config: MidiImportConfig = MidiImportConfig(),
|
||||||
|
) -> SingleMusic:
|
||||||
|
"""
|
||||||
|
将midi解析并转换为频道音符字典
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
midi: mido.MidiFile 对象
|
||||||
|
需要处理的midi对象
|
||||||
|
speed: float
|
||||||
|
音乐播放速度倍数
|
||||||
|
default_program_value: int
|
||||||
|
默认的 MIDI 乐器值
|
||||||
|
default_volume_value: int
|
||||||
|
默认的通道音量值
|
||||||
|
default_tempo_value: int
|
||||||
|
默认的 MIDI TEMPO 值
|
||||||
|
pitched_note_rtable: Dict[int, Tuple[str, int]]
|
||||||
|
乐音乐器Midi-MC对照表
|
||||||
|
percussion_note_rtable: Dict[int, Tuple[str, int]]
|
||||||
|
打击乐器Midi-MC对照表
|
||||||
|
vol_processing_function: Callable[[float], float]
|
||||||
|
音量对播放距离的拟合函数
|
||||||
|
pan_processing_function: Callable[[float], float]
|
||||||
|
声像偏移对播放旋转角度的拟合函数
|
||||||
|
note_rtable_replacement: Dict[str, str]
|
||||||
|
音符名称替换表,此表用于对 Minecraft 乐器名称进行替换,而非 Midi Program 的替换
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[SingleMusic, int, Dict[str, int]]
|
||||||
|
以通道作为分割的Midi音符列表字典, 音符总数, 乐器使用统计
|
||||||
|
"""
|
||||||
|
|
||||||
|
if config.speed_multiplier == 0:
|
||||||
|
raise ZeroSpeedError("播放速度不得为零,应为 (0,1] 范围内的实数。")
|
||||||
|
|
||||||
|
# 一个midi中仅有16个通道 我们通过通道来识别而不是音轨
|
||||||
|
divided_tracks: TrackDivisionDict = TrackDivisionDict(midi_import_config=config)
|
||||||
|
|
||||||
|
value_controler_per_channel: Dict[int, Dict[ControlerKeys, int]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(
|
||||||
|
staff={
|
||||||
|
ControlerKeys.MIDI_PROGRAM: config.default_program_value,
|
||||||
|
ControlerKeys.MIDI_VOLUME: config.default_volume_value,
|
||||||
|
ControlerKeys.MIDI_PAN: 64,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
midi_tempo = config.default_tempo_value
|
||||||
|
"""微秒每拍"""
|
||||||
|
note_count = 0
|
||||||
|
"""音符计数"""
|
||||||
|
note_count_per_instrument: Dict[str, int] = {}
|
||||||
|
"""乐器使用统计"""
|
||||||
|
|
||||||
|
note_queue_A: Dict[int, List[Tuple[int, int]]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
)
|
||||||
|
"""音符队列甲 Dict[通道, List[Tuple[int音高, int轨道]]]"""
|
||||||
|
note_queue_B: Dict[int, List[Tuple[int, int, int, int, int]]] = (
|
||||||
|
enumerated_stuffcopy_dictionary(staff=[])
|
||||||
|
)
|
||||||
|
"""音符队列乙 Dict[通道, List[Tuple[int力度, int乐器, int音量, int偏移, int微秒时间]]]"""
|
||||||
|
|
||||||
|
midi_lyric_cache: List[Tuple[int, str]] = []
|
||||||
|
"""歌词缓存 List[Tuple[int微秒时间, str歌词内容]]"""
|
||||||
|
|
||||||
|
midi_text_list: List[str] = []
|
||||||
|
"""Midi 附加文本列表"""
|
||||||
|
midi_copyright_list: List[str] = []
|
||||||
|
"""Midi 版权列表"""
|
||||||
|
midi_track_name_dict: Dict[int, str] = {}
|
||||||
|
"""轨道名称字典 Dict[int轨道编号, str轨道名称]"""
|
||||||
|
|
||||||
|
for track_no, message_track in enumerate(midi.tracks):
|
||||||
|
# 每个音轨单独重置
|
||||||
|
|
||||||
|
microseconds = 0
|
||||||
|
"""当前的微妙时间"""
|
||||||
|
for msg in message_track:
|
||||||
|
if msg.type == "set_tempo":
|
||||||
|
# Tempo 改变是一个全局的控制
|
||||||
|
# 而且应该是很早出现的一个 Midi 消息
|
||||||
|
midi_tempo = msg.tempo
|
||||||
|
|
||||||
|
if msg.time != 0:
|
||||||
|
# 微秒
|
||||||
|
# 通常情况下,tempo 是 500000,tpb 在
|
||||||
|
microseconds += msg.time * midi_tempo / midi.ticks_per_beat
|
||||||
|
|
||||||
|
if msg.type == "program_change":
|
||||||
|
# 检测 乐器变化 之 midi 事件
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
] = msg.program
|
||||||
|
|
||||||
|
elif msg.is_cc(7):
|
||||||
|
# Control Change 更改当前通道的 音量 的事件(大幅度,最高有效位)
|
||||||
|
# print("通道、轨道、音量修改:",msg.channel, track_no, msg.value)
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_VOLUME
|
||||||
|
] = msg.value
|
||||||
|
elif msg.is_cc(10):
|
||||||
|
# Control Change 更改当前通道的 音调偏移 的事件(大幅度,最高有效位)
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PAN
|
||||||
|
] = msg.value
|
||||||
|
|
||||||
|
elif msg.type == "lyrics":
|
||||||
|
# 歌词事件
|
||||||
|
midi_lyric_cache.append((microseconds, msg.text))
|
||||||
|
# print(lyric_cache, flush=True)
|
||||||
|
elif msg.type == "text":
|
||||||
|
# 检测文本事件
|
||||||
|
midi_text_list.append(msg.text)
|
||||||
|
elif msg.type == "copyright":
|
||||||
|
# 检测版权事件
|
||||||
|
midi_copyright_list.append(msg.text)
|
||||||
|
elif msg.type == "track_name":
|
||||||
|
# 检测轨道名称事件
|
||||||
|
midi_track_name_dict[track_no] = msg.name
|
||||||
|
elif msg.type == "note_on" and msg.velocity != 0:
|
||||||
|
# 一个音符开始弹奏
|
||||||
|
|
||||||
|
# 加入音符队列甲(按通道分隔)
|
||||||
|
# (音高, 轨道)
|
||||||
|
note_queue_A[msg.channel].append((msg.note, track_no))
|
||||||
|
# 音符队列乙(按通道分隔)
|
||||||
|
# (力度, 乐器, 音量, 偏移, 微秒)
|
||||||
|
note_queue_B[msg.channel].append(
|
||||||
|
(
|
||||||
|
msg.velocity,
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PROGRAM
|
||||||
|
],
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_VOLUME
|
||||||
|
],
|
||||||
|
value_controler_per_channel[msg.channel][
|
||||||
|
ControlerKeys.MIDI_PAN
|
||||||
|
],
|
||||||
|
microseconds,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif (msg.type == "note_off") or (
|
||||||
|
msg.type == "note_on" and msg.velocity == 0
|
||||||
|
):
|
||||||
|
# 一个音符结束弹奏
|
||||||
|
|
||||||
|
if (
|
||||||
|
msg.note,
|
||||||
|
track_no,
|
||||||
|
) in note_queue_A[msg.channel]:
|
||||||
|
# 在甲队列中发现了同一个 音高和乐器且在同轨道 的音符
|
||||||
|
|
||||||
|
# 获取其音符力度和微秒数
|
||||||
|
_velocity, _program, _volume, _panning, _start_ms = (
|
||||||
|
note_queue_B[msg.channel][
|
||||||
|
note_queue_A[msg.channel].index((msg.note, track_no))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 在队列中删除此音符
|
||||||
|
note_queue_A[msg.channel].remove((msg.note, track_no))
|
||||||
|
note_queue_B[msg.channel].remove(
|
||||||
|
(_velocity, _program, _volume, _panning, _start_ms)
|
||||||
|
)
|
||||||
|
|
||||||
|
_lyric = ""
|
||||||
|
# 找一找歌词吧
|
||||||
|
if midi_lyric_cache:
|
||||||
|
for i in range(len(midi_lyric_cache)):
|
||||||
|
if midi_lyric_cache[i][0] >= _start_ms:
|
||||||
|
_lyric = midi_lyric_cache.pop(i)[1]
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新结果信息
|
||||||
|
|
||||||
|
that_note, sound_name, orign_distance, sound_rotation = (
|
||||||
|
midi_msgs_to_noteinfo(
|
||||||
|
inst=(
|
||||||
|
msg.note
|
||||||
|
if (_is_percussion := (msg.channel == 9))
|
||||||
|
else _program
|
||||||
|
),
|
||||||
|
note=(_program if _is_percussion else msg.note),
|
||||||
|
percussive=_is_percussion,
|
||||||
|
volume=_volume,
|
||||||
|
velocity=_velocity,
|
||||||
|
panning=_panning,
|
||||||
|
start_time=_start_ms, # 微秒
|
||||||
|
duration=microseconds - _start_ms, # 微秒
|
||||||
|
play_speed=config.speed_multiplier,
|
||||||
|
midi_reference_table=(
|
||||||
|
config.percussion_note_reference_table
|
||||||
|
if _is_percussion
|
||||||
|
else config.pitched_note_reference_table
|
||||||
|
),
|
||||||
|
volume_processing_method=config.volume_process_function,
|
||||||
|
panning_processing_method=config.panning_processing_function,
|
||||||
|
note_table_replacement=config.note_replacement_table,
|
||||||
|
lyric_line=_lyric,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# print(that_note.start_time, end=", ")
|
||||||
|
|
||||||
|
divided_tracks[
|
||||||
|
(
|
||||||
|
track_no,
|
||||||
|
msg.channel,
|
||||||
|
sound_name,
|
||||||
|
orign_distance,
|
||||||
|
sound_rotation,
|
||||||
|
)
|
||||||
|
].add(that_note)
|
||||||
|
|
||||||
|
# 更新统计信息
|
||||||
|
note_count += 1
|
||||||
|
if sound_name in note_count_per_instrument.keys():
|
||||||
|
note_count_per_instrument[sound_name] += 1
|
||||||
|
else:
|
||||||
|
note_count_per_instrument[sound_name] = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 什么?找不到 note on 消息??
|
||||||
|
if config.ignore_errors:
|
||||||
|
print(
|
||||||
|
"[WARRING] MIDI格式错误 音符不匹配`{}`无法在上文`{}`中找到与之匹配的音符开音消息".format(
|
||||||
|
msg, note_queue_A[msg.channel]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NoteOnOffMismatchError(
|
||||||
|
"当前的MIDI很可能有损坏之嫌……",
|
||||||
|
msg,
|
||||||
|
"无法在上文中找到与之匹配的音符开音消息。",
|
||||||
|
)
|
||||||
|
|
||||||
|
del midi_tempo
|
||||||
|
|
||||||
|
if midi_lyric_cache:
|
||||||
|
# 怎么有歌词多啊
|
||||||
|
if config.ignore_errors:
|
||||||
|
print(
|
||||||
|
"[WARRING] MIDI 解析错误 歌词对应错误,以下歌词未能填入音符之中,已经填入的仍可能有误 {}".format(
|
||||||
|
midi_lyric_cache
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise LyricMismatchError(
|
||||||
|
"MIDI 解析产生错误",
|
||||||
|
"歌词解析过程中无法对应音符,已填入的音符仍可能有误",
|
||||||
|
midi_lyric_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
final_music = SingleMusic(
|
||||||
|
credits="; ".join(midi_copyright_list),
|
||||||
|
extra_information={
|
||||||
|
"MIDI_TEXT_LIST": midi_text_list,
|
||||||
|
"NOTE_COUNT": note_count,
|
||||||
|
"NOTE_COUNT_PER_INSTRUMENT": note_count_per_instrument,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for track_properties, every_single_track in divided_tracks.items():
|
||||||
|
# [音轨编号, 通道编号, 乐器名称, 音量, 声相]
|
||||||
|
if track_properties[0] and (
|
||||||
|
track_name := midi_track_name_dict.get(track_properties[0])
|
||||||
|
): # 音轨编号
|
||||||
|
every_single_track.name = track_name
|
||||||
|
if track_properties[2]: # 乐器名称
|
||||||
|
every_single_track.instrument = track_properties[2]
|
||||||
|
if track_properties[3]: # 音量
|
||||||
|
every_single_track.sound_position.sound_distance = track_properties[3]
|
||||||
|
if track_properties[4]: # 声相
|
||||||
|
every_single_track.sound_position.sound_azimuth = track_properties[4]
|
||||||
|
final_music.append(every_single_track)
|
||||||
|
|
||||||
|
return final_music
|
||||||
222
Musicreater/builtin_plugins/midi_read/utils.py
Normal file
222
Musicreater/builtin_plugins/midi_read/utils.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 Midi 读取插件的功能方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Mapping
|
||||||
|
|
||||||
|
from Musicreater import SingleNote, SoundAtmos
|
||||||
|
|
||||||
|
|
||||||
|
def volume_2_distance_natural(
|
||||||
|
vol: float,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
Midi 力度值/音量值拟合成的距离函数,一种更加自然的听感?
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
vol: int
|
||||||
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float播放中心到玩家的距离
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
-8.081720684086314
|
||||||
|
* math.log(
|
||||||
|
vol + 14.579508825070013,
|
||||||
|
)
|
||||||
|
+ 37.65806375944386
|
||||||
|
if vol < 60.64
|
||||||
|
else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649)
|
||||||
|
+ -6.313841334963396 * (vol + 2592.272889454798)
|
||||||
|
+ 4558.496367823575
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def volume_2_distance_straight(vol: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 力度值/音量值拟合成的距离函数,线性转换
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
vol: int
|
||||||
|
Midi 音符力度值(0~127)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float播放中心到玩家的距离
|
||||||
|
"""
|
||||||
|
return (vol + 1) / -8 + 16
|
||||||
|
|
||||||
|
|
||||||
|
def panning_2_rotation_linear(pan_: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 左右平衡偏移值线性转为声源旋转角度
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pan_: int
|
||||||
|
Midi 左右平衡偏移值
|
||||||
|
注:此参数为int,范围从 0 到 127,当为 64 时,声源居中
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
声源旋转角度
|
||||||
|
"""
|
||||||
|
return (pan_ - 64) * 90 / 63
|
||||||
|
|
||||||
|
|
||||||
|
def panning_2_rotation_trigonometric(pan_: float) -> float:
|
||||||
|
"""
|
||||||
|
Midi 左右平衡偏移值,依照圆的声场定位,转为声源旋转角度
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pan_: int
|
||||||
|
Midi 左右平衡偏移值
|
||||||
|
注:此参数为int,范围从 0 到 127,当为 64 时,声源居中
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
声源旋转角度
|
||||||
|
"""
|
||||||
|
if pan_ <= 0:
|
||||||
|
return -90
|
||||||
|
elif pan_ >= 127:
|
||||||
|
return 90
|
||||||
|
else:
|
||||||
|
return math.degrees(math.acos((64 - pan_) / 63)) - 90
|
||||||
|
|
||||||
|
|
||||||
|
def midi_inst_to_mc_sound(
|
||||||
|
instrumentID: int,
|
||||||
|
reference_table: Mapping[int, str],
|
||||||
|
default_instrument: str = "note.flute",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
返回midi的乐器ID对应的我的世界乐器名
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
instrumentID: int
|
||||||
|
midi的乐器ID
|
||||||
|
reference_table: Dict[int, Tuple[str, int]]
|
||||||
|
转换乐器参照表
|
||||||
|
default_instrument: str
|
||||||
|
查无此乐器时的替换乐器
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str我的世界乐器名
|
||||||
|
"""
|
||||||
|
return reference_table.get(
|
||||||
|
instrumentID,
|
||||||
|
default_instrument,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def midi_msgs_to_noteinfo(
|
||||||
|
inst: int, # 乐器编号
|
||||||
|
note: int,
|
||||||
|
percussive: bool, # 是否作为打击乐器启用
|
||||||
|
volume: int,
|
||||||
|
velocity: int,
|
||||||
|
panning: int,
|
||||||
|
start_time: int,
|
||||||
|
duration: int,
|
||||||
|
play_speed: float,
|
||||||
|
midi_reference_table: Mapping[int, str],
|
||||||
|
volume_processing_method: Callable[[float], float],
|
||||||
|
panning_processing_method: Callable[[float], float],
|
||||||
|
note_table_replacement: Mapping[str, str] = {},
|
||||||
|
lyric_line: str = "",
|
||||||
|
) -> Tuple[SingleNote, str, float, Tuple[float, float]]:
|
||||||
|
"""
|
||||||
|
将 Midi信息转为音符对象
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
inst: int
|
||||||
|
乐器编号
|
||||||
|
note: int
|
||||||
|
音高编号(音符编号)
|
||||||
|
percussive: bool
|
||||||
|
是否作为打击乐器启用
|
||||||
|
volume: int
|
||||||
|
音量
|
||||||
|
velocity: int
|
||||||
|
力度
|
||||||
|
panning: int
|
||||||
|
声相偏移
|
||||||
|
start_time: int
|
||||||
|
音符起始时间(微秒)
|
||||||
|
duration: int
|
||||||
|
音符持续时间(微秒)
|
||||||
|
play_speed: float
|
||||||
|
曲目播放速度
|
||||||
|
midi_reference_table: Dict[int, str]
|
||||||
|
转换对照表
|
||||||
|
volume_processing_method: Callable[[float], float]
|
||||||
|
音量处理函数
|
||||||
|
panning_processing_method: Callable[[float], float]
|
||||||
|
立体声相偏移处理函数
|
||||||
|
note_table_replacement: Dict[str, str]
|
||||||
|
音符替换表,定义 Minecraft 音符字串的替换
|
||||||
|
lyric_line: str
|
||||||
|
该音符的歌词
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
Tuple[
|
||||||
|
MineNote我的世界音符对象,
|
||||||
|
str我的世界声音名,
|
||||||
|
float播放中心到玩家的距离,
|
||||||
|
Tuple[float, float]声源旋转角度
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
inst,
|
||||||
|
midi_reference_table,
|
||||||
|
"note.bd" if percussive else "note.flute",
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
SingleNote(
|
||||||
|
note_pitch=note,
|
||||||
|
note_volume=velocity,
|
||||||
|
start_tick=(tk := int(start_time / float(play_speed) / 50000)),
|
||||||
|
keep_tick=round(duration / float(play_speed) / 50000),
|
||||||
|
mass_precision_time=round(
|
||||||
|
(start_time / float(play_speed) - tk * 50000) / 800
|
||||||
|
),
|
||||||
|
extra_information={
|
||||||
|
"LYRIC_TEXT": lyric_line,
|
||||||
|
"VOLUME_VALUE": volume,
|
||||||
|
"PIN_VALUE": panning,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
||||||
|
volume_processing_method(volume),
|
||||||
|
(panning_processing_method(panning), 0),
|
||||||
|
)
|
||||||
33
Musicreater/builtin_plugins/to_commands/__init__.py
Normal file
33
Musicreater/builtin_plugins/to_commands/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 指令生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from .main import NoteDataConvert2CommandPlugin, MineCommand
|
||||||
|
|
||||||
|
from .progressbar import ProgressBarStyle, DEFAULT_PROGRESSBAR_STYLE
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# "CommandConvertionConfig",
|
||||||
|
# 插件主类
|
||||||
|
"NoteDataConvert2CommandPlugin",
|
||||||
|
# 进度条样式类
|
||||||
|
"ProgressBarStyle",
|
||||||
|
# Minecraft 指令类
|
||||||
|
"MineCommand",
|
||||||
|
# 默认进度条样式
|
||||||
|
"DEFAULT_PROGRESSBAR_STYLE",
|
||||||
|
]
|
||||||
646
Musicreater/builtin_plugins/to_commands/main.py
Normal file
646
Musicreater/builtin_plugins/to_commands/main.py
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 指令生成插件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack, SingleNote, SoundAtmos, MineNote
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
library_plugin,
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
LibraryPluginBase,
|
||||||
|
)
|
||||||
|
from Musicreater.exceptions import ZeroSpeedError, IllegalMinimumVolumeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from .progressbar import ProgressBarStyle, DEFAULT_PROGRESSBAR_STYLE, mctick2timestr
|
||||||
|
from .utils import minenote_to_command_parameters
|
||||||
|
|
||||||
|
|
||||||
|
# @dataclass
|
||||||
|
# class CommandConvertionConfig(PluginConfig):
|
||||||
|
# execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run "
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MineCommand:
|
||||||
|
"""存储单个指令的类"""
|
||||||
|
|
||||||
|
command: str
|
||||||
|
"""指令文本"""
|
||||||
|
conditional: bool = False
|
||||||
|
"""执行是否有条件"""
|
||||||
|
delay: int = 0
|
||||||
|
"""执行的延迟"""
|
||||||
|
annotation: str = ""
|
||||||
|
"""指令注释"""
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return MineCommand(
|
||||||
|
command=self.command,
|
||||||
|
conditional=self.conditional,
|
||||||
|
delay=self.delay,
|
||||||
|
annotation=self.annotation,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mcfunction_command_string(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,
|
||||||
|
cmd=self.command,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
# 不比较注释内容
|
||||||
|
return (
|
||||||
|
(self.command == other.command)
|
||||||
|
and (self.conditional == other.conditional)
|
||||||
|
and (self.delay == other.delay)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@library_plugin("notedata_to_command_plugin")
|
||||||
|
class NoteDataConvert2CommandPlugin(LibraryPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="音符数据指令支持插件",
|
||||||
|
author="金羿、玉衡Alioth",
|
||||||
|
description="从音符数据转换为我的世界指令相关格式",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.LIBRARY,
|
||||||
|
license="Same as Musicreater",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 暂时没有适配动画内容和替换顺序
|
||||||
|
# 金羿正在处理这个,不需要改
|
||||||
|
# 但是返回值和接口内容不会变,直接用即可
|
||||||
|
#
|
||||||
|
@staticmethod
|
||||||
|
def generate_progressbar(
|
||||||
|
max_score: int,
|
||||||
|
scoreboard_name: str,
|
||||||
|
music_name: str = "",
|
||||||
|
progressbar_style: ProgressBarStyle = DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ",
|
||||||
|
) -> List[MineCommand]:
|
||||||
|
"""
|
||||||
|
生成进度条
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
max_score: int
|
||||||
|
最大的积分值
|
||||||
|
scoreboard_name: str
|
||||||
|
所使用的计分板名称
|
||||||
|
progressbar_style: ProgressBarStyle
|
||||||
|
此参数详见 ../docs/库的生成与功能文档.md#进度条自定义
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[MineCommand,]
|
||||||
|
"""
|
||||||
|
orignal_style_string = progressbar_style.style_base_string
|
||||||
|
"""用于被替换的进度条原始样式"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
| 标识符 | 指定的可变量 |
|
||||||
|
|---------|----------------|
|
||||||
|
| `%%N` | 乐曲名 |
|
||||||
|
| `%^s` | 计分板最大值 |
|
||||||
|
| `%^t` | 曲目总时长 |
|
||||||
|
| `%%s` | 当前计分板值 |
|
||||||
|
| `%%t` | 当前播放时间 |
|
||||||
|
| `%%%` | 当前进度比率 |
|
||||||
|
| `_` | 用以表示进度条占位|
|
||||||
|
| `%*%` | 指定*的动画内容 |
|
||||||
|
"""
|
||||||
|
per_value_in_each = max_score / orignal_style_string.count("_")
|
||||||
|
"""每个进度条代表的分值"""
|
||||||
|
|
||||||
|
result: List[MineCommand] = []
|
||||||
|
|
||||||
|
orignal_style_string = (
|
||||||
|
orignal_style_string.replace("%%N", music_name)
|
||||||
|
.replace("%^s", str(max_score))
|
||||||
|
.replace("%^t", mctick2timestr(max_score))
|
||||||
|
)
|
||||||
|
|
||||||
|
scoreboard_name_part = scoreboard_name[:2]
|
||||||
|
if "%%%" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}PercT dummy "百分比计算"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增临时百分比变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set MaxScore {} {}".format(
|
||||||
|
scoreboard_name, max_score
|
||||||
|
),
|
||||||
|
annotation="设定音乐最大延迟分数",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n100 {} 100".format(scoreboard_name),
|
||||||
|
annotation="设置常量100",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} = @s {}".format(
|
||||||
|
scoreboard_name_part + "PercT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="赋值当前进度",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} *= n100 {}".format(
|
||||||
|
scoreboard_name_part + "PercT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="转换当前进度之单位至百分比(扩大精度)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} /= MaxScore {}".format(
|
||||||
|
scoreboard_name_part + "PercT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="计算进度百分比",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%%t" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}TMinT dummy "时间计算:分"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增临时分变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}TSecT dummy "时间计算:秒"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增临时秒变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n20 {} 20".format(scoreboard_name),
|
||||||
|
annotation="设置常量 20",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n60 {} 60".format(scoreboard_name),
|
||||||
|
annotation="设置常量 60",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} = @s {}".format(
|
||||||
|
scoreboard_name_part + "TMinT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="赋值临时分变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} /= n20 {}".format(
|
||||||
|
scoreboard_name_part + "TMinT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="转换临时分变量之单位为秒(缩减精度)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} = @s {}".format(
|
||||||
|
scoreboard_name_part + "TSecT", scoreboard_name_part + "TMinT"
|
||||||
|
),
|
||||||
|
annotation="赋值临时秒",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} /= n60 {}".format(
|
||||||
|
scoreboard_name_part + "TMinT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="转换临时分变量之单位为分(缩减精度)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {} %= n60 {}".format(
|
||||||
|
scoreboard_name_part + "TSecT", scoreboard_name
|
||||||
|
),
|
||||||
|
annotation="确定临时秒(框定精度区间)",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if progressbar_style.is_animate_autoloop and progressbar_style.animate_circle:
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
'scoreboard objectives add {}AniC dummy "动画循环控制"'.format(
|
||||||
|
scoreboard_name_part
|
||||||
|
),
|
||||||
|
annotation="新增动画循环控制变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for animate_placeholder in progressbar_style.animate_circle:
|
||||||
|
max_loop_score = max(
|
||||||
|
progressbar_style.animate_circle[animate_placeholder].keys()
|
||||||
|
)
|
||||||
|
if ("%%%" not in orignal_style_string or max_loop_score != 100) and (
|
||||||
|
"%%t" not in orignal_style_string or max_loop_score not in (60, 20)
|
||||||
|
):
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players set n{num} {sbn} {num}".format(
|
||||||
|
sbn=scoreboard_name,
|
||||||
|
num=max_loop_score,
|
||||||
|
),
|
||||||
|
annotation="设置常量 {num}".format(num=max_loop_score),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {sbnp}AniC = @s {sbn}".format(
|
||||||
|
sbnp=scoreboard_name_part, sbn=scoreboard_name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={" + scoreboard_name + "=1..}]"
|
||||||
|
)
|
||||||
|
+ "scoreboard players operation @s {sbnp}AniC %= n{num} {sbn}".format(
|
||||||
|
sbnp=scoreboard_name_part,
|
||||||
|
num=max_loop_score,
|
||||||
|
sbn=scoreboard_name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(orignal_style_string.count("_")):
|
||||||
|
npg_stl = (
|
||||||
|
orignal_style_string.replace(
|
||||||
|
"%%s",
|
||||||
|
'"},{"score":{"name":"*","objective":"'
|
||||||
|
+ scoreboard_name
|
||||||
|
+ '"}},{"text":"',
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%%t",
|
||||||
|
'"},{"score":{"name":"*","objective":"{-}TMinT"}},{"text":":"},'
|
||||||
|
'{"score":{"name":"*","objective":"{-}TSecT"}},{"text":"'.replace(
|
||||||
|
"{-}", scoreboard_name_part
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%%%",
|
||||||
|
'"},{"score":{"name":"*","objective":"'
|
||||||
|
+ scoreboard_name_part
|
||||||
|
+ 'PercT"}},{"text":"%',
|
||||||
|
)
|
||||||
|
.replace("_", progressbar_style.progress_played, i + 1)
|
||||||
|
.replace("_", progressbar_style.progress_toplay)
|
||||||
|
)
|
||||||
|
for animate_placeholder in progressbar_style.animate_circle:
|
||||||
|
animation_start_tick = 0
|
||||||
|
npg_stl = npg_stl.replace(
|
||||||
|
animate_placeholder,
|
||||||
|
'"},{"translate": "%%'
|
||||||
|
+ str(
|
||||||
|
len(progressbar_style.animate_circle[animate_placeholder]) + 1
|
||||||
|
)
|
||||||
|
+ '","with":{"rawtext":['
|
||||||
|
+ (
|
||||||
|
",".join(
|
||||||
|
(
|
||||||
|
'{"selector":"@s[scores={{-}={*}..{&}}]"}'.replace(
|
||||||
|
"{*}", str(animation_start_tick)
|
||||||
|
).replace("{&}", str(animation_start_tick := end_tick))
|
||||||
|
for end_tick in progressbar_style.animate_circle[
|
||||||
|
animate_placeholder
|
||||||
|
].keys()
|
||||||
|
)
|
||||||
|
).replace(
|
||||||
|
"{-}",
|
||||||
|
(
|
||||||
|
(scoreboard_name_part + "AniC")
|
||||||
|
if progressbar_style.is_animate_autoloop
|
||||||
|
else scoreboard_name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ ","
|
||||||
|
+ ",".join(
|
||||||
|
(
|
||||||
|
'{"text":"' + animation_text + '"}'
|
||||||
|
for animation_text in progressbar_style.animate_circle[
|
||||||
|
animate_placeholder
|
||||||
|
].values()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ ',{"text":"NaN"}]}},{"text":"',
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores={"
|
||||||
|
+ scoreboard_name
|
||||||
|
+ f"={int(i * per_value_in_each)}..{ceil((i + 1) * per_value_in_each)}"
|
||||||
|
+ "}]"
|
||||||
|
)
|
||||||
|
+ 'titleraw @s actionbar {"rawtext":[{"text":"'
|
||||||
|
+ npg_stl
|
||||||
|
+ '"}]}',
|
||||||
|
annotation="进度条显示",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%%%" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}PercT".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时百分比变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if "%%t" in orignal_style_string:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}TMinT".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时分变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}TSecT".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时秒变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if progressbar_style.is_animate_autoloop and progressbar_style.animate_circle:
|
||||||
|
result.append(
|
||||||
|
MineCommand(
|
||||||
|
"scoreboard objectives remove {}AniC".format(scoreboard_name_part),
|
||||||
|
annotation="移除临时动画循环控制变量",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_command_list_in_score(
|
||||||
|
music: SingleMusic,
|
||||||
|
music_deviation: float = 0,
|
||||||
|
minimum_volume: float = 0.01,
|
||||||
|
scoreboard_name: str = "mscplay",
|
||||||
|
execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ",
|
||||||
|
) -> Tuple[List[List[MineCommand]], int, int]:
|
||||||
|
"""
|
||||||
|
将midi转换为我的世界命令列表
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
scoreboard_name: str
|
||||||
|
我的世界的计分板名称
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[list[MineCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 )
|
||||||
|
"""
|
||||||
|
|
||||||
|
command_channels: List[List[MineCommand]] = []
|
||||||
|
command_amount = 0
|
||||||
|
max_score = 0
|
||||||
|
|
||||||
|
for track in music.music_tracks:
|
||||||
|
# 如果当前轨道为空 则跳过
|
||||||
|
if not track:
|
||||||
|
continue
|
||||||
|
|
||||||
|
this_channel = []
|
||||||
|
|
||||||
|
for note in track.minenotes:
|
||||||
|
max_score = max(max_score, note.start_tick)
|
||||||
|
|
||||||
|
(
|
||||||
|
relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
mc_pitch,
|
||||||
|
) = minenote_to_command_parameters(
|
||||||
|
note,
|
||||||
|
pitch_deviation=music_deviation,
|
||||||
|
)
|
||||||
|
|
||||||
|
this_channel.append(
|
||||||
|
MineCommand(
|
||||||
|
(
|
||||||
|
execute_command_head.format(
|
||||||
|
"@a[scores=({}={})]".format(
|
||||||
|
scoreboard_name, note.start_tick
|
||||||
|
)
|
||||||
|
.replace("(", r"{")
|
||||||
|
.replace(")", r"}")
|
||||||
|
)
|
||||||
|
+ r"playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||||
|
track.instrument,
|
||||||
|
*relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
1.0 if note.percussive else mc_pitch,
|
||||||
|
minimum_volume,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
annotation=(
|
||||||
|
"[{}] 打击乐音符{}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
)
|
||||||
|
if note.percussive
|
||||||
|
else "[{}] 音符{}:{:.2f}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
mc_pitch,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
command_amount += 1
|
||||||
|
|
||||||
|
if this_channel:
|
||||||
|
command_channels.append(this_channel)
|
||||||
|
|
||||||
|
return command_channels, command_amount, max_score
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_command_list_in_delay(
|
||||||
|
music: SingleMusic,
|
||||||
|
music_deviation: float = 0,
|
||||||
|
minimum_volume: float = 0.01,
|
||||||
|
player_selector: str = "@a",
|
||||||
|
execute_command_head: str = "execute as {} at @s positioned ~ ~ ~ run ",
|
||||||
|
) -> Tuple[List[MineCommand], int, int]:
|
||||||
|
"""
|
||||||
|
将midi转换为我的世界命令列表,并输出每个音符之后的延迟
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
player_selector: str
|
||||||
|
玩家选择器,默认为`@a`
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple( list[MineCommand指令,...], int音乐时长游戏刻, int最大同时播放的指令数量 )
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 音轨判断
|
||||||
|
music_command_list = []
|
||||||
|
multi = max_multi = 0
|
||||||
|
delaytime_previous = 0
|
||||||
|
last_note: MineNote
|
||||||
|
|
||||||
|
for note in music.get_minenotes(
|
||||||
|
start_time=0,
|
||||||
|
):
|
||||||
|
if (tickdelay := (note.start_tick - delaytime_previous)) == 0:
|
||||||
|
multi += 1
|
||||||
|
else:
|
||||||
|
max_multi = max(max_multi, multi)
|
||||||
|
multi = 0
|
||||||
|
|
||||||
|
(
|
||||||
|
relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
mc_pitch,
|
||||||
|
) = minenote_to_command_parameters(
|
||||||
|
note,
|
||||||
|
pitch_deviation=music_deviation,
|
||||||
|
)
|
||||||
|
|
||||||
|
music_command_list.append(
|
||||||
|
MineCommand(
|
||||||
|
command=(
|
||||||
|
execute_command_head.format(player_selector)
|
||||||
|
+ "playsound {} @s ^{} ^{} ^{} {} {} {}".format(
|
||||||
|
note.instrument,
|
||||||
|
*relative_coordinates,
|
||||||
|
volume_percentage,
|
||||||
|
1.0 if note.percussive else mc_pitch,
|
||||||
|
minimum_volume,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
annotation=(
|
||||||
|
"[{}] 打击乐音符{}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
)
|
||||||
|
if note.percussive
|
||||||
|
else "[{}] 音符{}:{:.2f}".format(
|
||||||
|
mctick2timestr(note.start_tick),
|
||||||
|
note.instrument,
|
||||||
|
mc_pitch,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
delay=tickdelay,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
delaytime_previous = note.start_tick
|
||||||
|
last_note = note
|
||||||
|
if music_command_list:
|
||||||
|
return (
|
||||||
|
music_command_list,
|
||||||
|
last_note.start_tick + last_note.duration_tick,
|
||||||
|
max_multi + 1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return [], 0, 0
|
||||||
179
Musicreater/builtin_plugins/to_commands/progressbar.py
Normal file
179
Musicreater/builtin_plugins/to_commands/progressbar.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的 指令生成插件的进度条相关内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import BinaryIO, Optional, Dict, List, Callable, Tuple, Mapping
|
||||||
|
|
||||||
|
|
||||||
|
# 这个类也有很大的优化空间a
|
||||||
|
@dataclass(init=False)
|
||||||
|
class ProgressBarStyle:
|
||||||
|
"""进度条样式类"""
|
||||||
|
|
||||||
|
style_base_string: str
|
||||||
|
"""基础样式"""
|
||||||
|
|
||||||
|
progress_toplay: str
|
||||||
|
"""未播放之样式"""
|
||||||
|
|
||||||
|
progress_played: str
|
||||||
|
"""已播放之样式"""
|
||||||
|
|
||||||
|
is_animate_autoloop: bool
|
||||||
|
"""所示动画是否循环"""
|
||||||
|
|
||||||
|
animate_circle: Dict[str, Dict[int, str]]
|
||||||
|
"""
|
||||||
|
定义动画样式
|
||||||
|
Dict[占位符, Dict[截止时间刻, 样式字符串]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_string: str = "【%%N】%A%▶ %%s/%^s (%%t|%^t) \n"
|
||||||
|
"[§e_________________________§r] %%%",
|
||||||
|
to_play_style: str = "§7=",
|
||||||
|
played_style: str = "=",
|
||||||
|
animate_loop: bool = True,
|
||||||
|
animate_circle: Dict[str, Dict[int, str]] = {
|
||||||
|
"%A%": {5: "-", 10: "\\\\", 15: "|", 20: "/"}
|
||||||
|
},
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
用于存储进度条样式的类,标识符替换顺序如下表
|
||||||
|
|
||||||
|
| 标识符 | 指定的可变量 |
|
||||||
|
|---------|----------------|
|
||||||
|
| `%%N` | 乐曲名 |
|
||||||
|
| `%^s` | 计分板最大值 |
|
||||||
|
| `%^t` | 曲目总时长 |
|
||||||
|
| `%%s` | 当前计分板值 |
|
||||||
|
| `%%t` | 当前播放时间 |
|
||||||
|
| `%%%` | 当前进度比率 |
|
||||||
|
| `_` | 用以表示进度条占位|
|
||||||
|
| `%*%` | 指定*的动画内容 |
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
base_string: str
|
||||||
|
基础样式,用以定义进度条整体
|
||||||
|
to_play_style: str
|
||||||
|
进度条样式:尚未播放的样子
|
||||||
|
played_style: str
|
||||||
|
已经播放的样子
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
ProgressBarStyle 类
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.style_base_string = base_string
|
||||||
|
self.progress_toplay = to_play_style
|
||||||
|
self.progress_played = played_style
|
||||||
|
self.is_animate_autoloop = animate_loop
|
||||||
|
self.animate_circle = animate_circle
|
||||||
|
|
||||||
|
def set_base_style(self, value: str):
|
||||||
|
"""设置基础样式"""
|
||||||
|
self.style_base_string = value
|
||||||
|
|
||||||
|
def set_to_play_style(self, value: str):
|
||||||
|
"""设置未播放之样式"""
|
||||||
|
self.progress_toplay = value
|
||||||
|
|
||||||
|
def set_played_style(self, value: str):
|
||||||
|
"""设置已播放之样式"""
|
||||||
|
self.progress_played = value
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return ProgressBarStyle(
|
||||||
|
self.style_base_string,
|
||||||
|
self.progress_toplay,
|
||||||
|
self.progress_played,
|
||||||
|
self.is_animate_autoloop,
|
||||||
|
self.animate_circle,
|
||||||
|
)
|
||||||
|
|
||||||
|
def play_output(
|
||||||
|
self,
|
||||||
|
played_ticks: int,
|
||||||
|
total_ticks: int,
|
||||||
|
music_name: str = "无题",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
直接依照此格式输出一个进度条
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
played_delays: int
|
||||||
|
当前播放进度积分值
|
||||||
|
total_delays: int
|
||||||
|
乐器总延迟数(计分板值)
|
||||||
|
music_name: str
|
||||||
|
曲名
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
str
|
||||||
|
进度条字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
alpha_string = (
|
||||||
|
self.style_base_string.replace("%%N", music_name)
|
||||||
|
.replace("%%s", str(played_ticks))
|
||||||
|
.replace("%^s", str(total_ticks))
|
||||||
|
.replace("%%t", mctick2timestr(played_ticks))
|
||||||
|
.replace("%^t", mctick2timestr(total_ticks))
|
||||||
|
.replace(
|
||||||
|
"%%%",
|
||||||
|
"{:0>5.2f}%".format(int(10000 * played_ticks / total_ticks) / 100),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"_",
|
||||||
|
self.progress_played,
|
||||||
|
(played_ticks * self.style_base_string.count("_") // total_ticks) + 1,
|
||||||
|
)
|
||||||
|
.replace("_", self.progress_toplay)
|
||||||
|
)
|
||||||
|
|
||||||
|
for key, animate_dict in self.animate_circle.items():
|
||||||
|
max_animate_tick = max(animate_dict.keys())
|
||||||
|
if self.is_animate_autoloop:
|
||||||
|
animate_time_key = 0
|
||||||
|
for time_key in animate_dict.keys():
|
||||||
|
animate_time_key = time_key
|
||||||
|
if time_key > played_ticks % max_animate_tick:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
animate_time_key = max_animate_tick
|
||||||
|
alpha_string = alpha_string.replace(key, animate_dict[animate_time_key])
|
||||||
|
return alpha_string
|
||||||
|
|
||||||
|
|
||||||
|
def mctick2timestr(mc_tick: int) -> str:
|
||||||
|
"""
|
||||||
|
将《我的世界》的游戏刻计转为表示时间的字符串
|
||||||
|
"""
|
||||||
|
return "{:0>2d}:{:0>2d}".format(mc_tick // 1200, (mc_tick // 20) % 60)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE = ProgressBarStyle()
|
||||||
|
"""
|
||||||
|
默认的进度条样式
|
||||||
|
"""
|
||||||
115
Musicreater/builtin_plugins/to_commands/utils.py
Normal file
115
Musicreater/builtin_plugins/to_commands/utils.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内置的指令生成插件的功能方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿、玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
BinaryIO,
|
||||||
|
Optional,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Callable,
|
||||||
|
Tuple,
|
||||||
|
Mapping,
|
||||||
|
Union,
|
||||||
|
Literal,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater import MineNote, SingleNote
|
||||||
|
from Musicreater.constants import MM_INSTRUMENT_DEVIATION_TABLE
|
||||||
|
|
||||||
|
|
||||||
|
# 这个函数可以直接被优化成一个只处理音调参数的,没必要完整留着
|
||||||
|
def minenote_to_command_parameters(
|
||||||
|
mine_note: MineNote,
|
||||||
|
pitch_deviation: float = 0,
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[float, float, float],
|
||||||
|
float,
|
||||||
|
Union[float, Literal[None]],
|
||||||
|
]:
|
||||||
|
"""
|
||||||
|
将 MineNote 对象转为《我的世界》音符播放所需之参数
|
||||||
|
|
||||||
|
参数
|
||||||
|
----
|
||||||
|
mine_note: MineNote
|
||||||
|
我的世界音符对象
|
||||||
|
pitch_deviation: float
|
||||||
|
音调偏移量
|
||||||
|
|
||||||
|
返回
|
||||||
|
----
|
||||||
|
tuple[float, float, float], float, float
|
||||||
|
播放视角坐标, 指令音量参数, 指令音调参数
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
mine_note.position.position_displacement,
|
||||||
|
mine_note.volume / 127,
|
||||||
|
(
|
||||||
|
None
|
||||||
|
if mine_note.percussive
|
||||||
|
else (
|
||||||
|
2
|
||||||
|
** (
|
||||||
|
(
|
||||||
|
mine_note.pitch
|
||||||
|
- 60
|
||||||
|
- MM_INSTRUMENT_DEVIATION_TABLE.get(mine_note.instrument, 6)
|
||||||
|
+ pitch_deviation
|
||||||
|
)
|
||||||
|
/ 12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_minecraft_pitch(
|
||||||
|
note: MineNote, pitch_deviation: float = 0
|
||||||
|
) -> Optional[float]:
|
||||||
|
"""
|
||||||
|
计算音符的音调参数
|
||||||
|
|
||||||
|
参数
|
||||||
|
----
|
||||||
|
note: MineNote
|
||||||
|
我的世界音符对象
|
||||||
|
deviation: float
|
||||||
|
音调偏移量
|
||||||
|
|
||||||
|
返回
|
||||||
|
----
|
||||||
|
Optional[float]
|
||||||
|
音调参数, 当为打击乐器时为 None
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
None
|
||||||
|
if note.percussive
|
||||||
|
else (
|
||||||
|
2
|
||||||
|
** (
|
||||||
|
(
|
||||||
|
note.pitch
|
||||||
|
- 60
|
||||||
|
- MM_INSTRUMENT_DEVIATION_TABLE.get(note.instrument, 6)
|
||||||
|
+ pitch_deviation
|
||||||
|
)
|
||||||
|
/ 12
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
683
Musicreater/constants.py
Normal file
683
Musicreater/constants.py
Normal file
@@ -0,0 +1,683 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
存放常量与数值性内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿 & 诸葛亮与八卦阵
|
||||||
|
Copyright © 2026 Eilles & bgArray
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# from .types import Dict, List, Tuple, MidiInstrumentTableType, MidiNoteNameTableType
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
x = "x"
|
||||||
|
"""
|
||||||
|
x
|
||||||
|
"""
|
||||||
|
|
||||||
|
y = "y"
|
||||||
|
"""
|
||||||
|
y
|
||||||
|
"""
|
||||||
|
|
||||||
|
z = "z"
|
||||||
|
"""
|
||||||
|
z
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Midi用对照表
|
||||||
|
MIDI_PITCH_NAME_TABLE: Dict[int, str] = {
|
||||||
|
0: "C", # Midi 最低,C-1
|
||||||
|
1: "C#",
|
||||||
|
2: "D",
|
||||||
|
3: "D#",
|
||||||
|
4: "E",
|
||||||
|
5: "F",
|
||||||
|
6: "F#",
|
||||||
|
7: "G",
|
||||||
|
8: "G#",
|
||||||
|
9: "A",
|
||||||
|
10: "A#",
|
||||||
|
11: "B",
|
||||||
|
12: "C", # C0
|
||||||
|
13: "C#",
|
||||||
|
14: "D",
|
||||||
|
15: "D#",
|
||||||
|
16: "E",
|
||||||
|
17: "F",
|
||||||
|
18: "F#",
|
||||||
|
19: "G",
|
||||||
|
20: "G#",
|
||||||
|
21: "A", # 钢琴最低,A0
|
||||||
|
22: "A#",
|
||||||
|
23: "B",
|
||||||
|
24: "C", # C1
|
||||||
|
25: "C#",
|
||||||
|
26: "D",
|
||||||
|
27: "D#",
|
||||||
|
28: "E",
|
||||||
|
29: "F",
|
||||||
|
30: "F#",
|
||||||
|
31: "G",
|
||||||
|
32: "G#",
|
||||||
|
33: "A",
|
||||||
|
34: "A#",
|
||||||
|
35: "B",
|
||||||
|
36: "C",
|
||||||
|
37: "C#",
|
||||||
|
38: "D",
|
||||||
|
39: "D#",
|
||||||
|
40: "E",
|
||||||
|
41: "F",
|
||||||
|
42: "F#",
|
||||||
|
43: "G",
|
||||||
|
44: "G#",
|
||||||
|
45: "A",
|
||||||
|
46: "A#",
|
||||||
|
47: "B",
|
||||||
|
48: "C",
|
||||||
|
49: "C#",
|
||||||
|
50: "D",
|
||||||
|
51: "D#",
|
||||||
|
52: "E",
|
||||||
|
53: "F",
|
||||||
|
54: "F#",
|
||||||
|
55: "G",
|
||||||
|
56: "G#",
|
||||||
|
57: "A",
|
||||||
|
58: "A#",
|
||||||
|
59: "B",
|
||||||
|
60: "C", # 钢琴中央 C,C4
|
||||||
|
61: "C#",
|
||||||
|
62: "D",
|
||||||
|
63: "D#",
|
||||||
|
64: "E",
|
||||||
|
65: "F",
|
||||||
|
66: "F#",
|
||||||
|
67: "G",
|
||||||
|
68: "G#",
|
||||||
|
69: "A",
|
||||||
|
70: "A#",
|
||||||
|
71: "B",
|
||||||
|
72: "C",
|
||||||
|
73: "C#",
|
||||||
|
74: "D",
|
||||||
|
75: "D#",
|
||||||
|
76: "E",
|
||||||
|
77: "F",
|
||||||
|
78: "F#",
|
||||||
|
79: "G",
|
||||||
|
80: "G#",
|
||||||
|
81: "A",
|
||||||
|
82: "A#",
|
||||||
|
83: "B",
|
||||||
|
84: "C",
|
||||||
|
85: "C#",
|
||||||
|
86: "D",
|
||||||
|
87: "D#",
|
||||||
|
88: "E",
|
||||||
|
89: "F",
|
||||||
|
90: "F#",
|
||||||
|
91: "G",
|
||||||
|
92: "G#",
|
||||||
|
93: "A",
|
||||||
|
94: "A#",
|
||||||
|
95: "B",
|
||||||
|
96: "C",
|
||||||
|
97: "C#",
|
||||||
|
98: "D",
|
||||||
|
99: "D#",
|
||||||
|
100: "E",
|
||||||
|
101: "F",
|
||||||
|
102: "F#",
|
||||||
|
103: "G",
|
||||||
|
104: "G#",
|
||||||
|
105: "A",
|
||||||
|
106: "A#",
|
||||||
|
107: "B",
|
||||||
|
108: "C", # 钢琴最高,C8
|
||||||
|
109: "C#",
|
||||||
|
110: "D",
|
||||||
|
111: "D#",
|
||||||
|
112: "E",
|
||||||
|
113: "F",
|
||||||
|
114: "F#",
|
||||||
|
115: "G",
|
||||||
|
116: "G#",
|
||||||
|
117: "A",
|
||||||
|
118: "A#",
|
||||||
|
119: "B",
|
||||||
|
120: "C",
|
||||||
|
121: "C#",
|
||||||
|
122: "D",
|
||||||
|
123: "D#",
|
||||||
|
124: "E",
|
||||||
|
125: "F",
|
||||||
|
126: "F#",
|
||||||
|
127: "G", # G9
|
||||||
|
}
|
||||||
|
"""Midi音高名称对照表"""
|
||||||
|
|
||||||
|
|
||||||
|
MIDI_PITCHED_NOTE_NAME_GROUP: Dict[int, Tuple[str, str]] = {
|
||||||
|
1: ("钢琴", "Piano"),
|
||||||
|
9: ("半音阶打击乐器", "Chromatic Percussion"),
|
||||||
|
17: ("风琴", "Organ"),
|
||||||
|
25: ("吉他", "Guitar"),
|
||||||
|
33: ("贝斯", "Bass"),
|
||||||
|
41: ("弦乐器", "Strings"),
|
||||||
|
49: ("合奏乐器", "Ensemble"),
|
||||||
|
57: ("铜管乐器", "Brass"),
|
||||||
|
65: ("簧乐器", "Reed"),
|
||||||
|
73: ("吹管乐器", "Pipe"),
|
||||||
|
81: ("合成主旋律", "Synth Lead"),
|
||||||
|
89: ("合成和弦", "Synth Pad"),
|
||||||
|
97: ("合成声效", "Synth Effects"),
|
||||||
|
105: ("民族乐器", "Ethnic"),
|
||||||
|
113: ("打击乐器", "Percussive"),
|
||||||
|
121: ("特殊音效", "Sound Effects"),
|
||||||
|
}
|
||||||
|
"""Midi乐音乐器分组名称对照表"""
|
||||||
|
|
||||||
|
MIDI_PITCHED_NOTE_NAME_TABLE: Dict[int, Tuple[str, str]] = {
|
||||||
|
1: ("原声平台钢琴", "Acoustic Grand Piano"),
|
||||||
|
2: ("亮音原声钢琴", "Bright Acoustic Piano"),
|
||||||
|
3: ("数码电钢琴", "Electric Grand Piano"),
|
||||||
|
4: ("酒吧钢琴", "Honky-tonk Piano"),
|
||||||
|
5: ("电气电钢琴", "Electric Piano 1(Rhodes Piano)"),
|
||||||
|
6: ("合唱效果电钢琴", "Electric Piano 2(Chorused Piano)"),
|
||||||
|
7: ("拨弦古钢琴(羽管键琴)", "Harpsichord"),
|
||||||
|
8: ("古钢琴", "Clavi"),
|
||||||
|
9: ("钢片琴", "Celesta"),
|
||||||
|
10: ("钟琴", "Glockenspiel"),
|
||||||
|
11: ("八音盒", "Music box"),
|
||||||
|
12: ("颤音琴", "Vibraphone"),
|
||||||
|
13: ("马林巴琴", "Marimba"),
|
||||||
|
14: ("木琴", "Xylophone"),
|
||||||
|
15: ("管钟", "Tubular Bells"),
|
||||||
|
16: ("扬琴", "Dulcimer"),
|
||||||
|
17: ("音栓风琴(击杆风琴)", "Drawbar Organ (Hammond Organ)"),
|
||||||
|
18: ("打击风琴", "Percussive Organ"),
|
||||||
|
19: ("摇滚管风琴", "Rock Organ"),
|
||||||
|
20: ("教堂管风琴", "Church Organ"),
|
||||||
|
21: ("簧风琴", "Reed Organ"),
|
||||||
|
22: ("手风琴", "Accordion"),
|
||||||
|
23: ("口琴", "Harmonica"),
|
||||||
|
24: ("探戈手风琴", "Tango Accordion"),
|
||||||
|
25: ("尼龙弦吉他", "Acoustic Guitar (nylon)"),
|
||||||
|
26: ("钢弦吉他", "Acoustic Guitar (steel)"),
|
||||||
|
27: ("爵士电吉他", "Electric Guitar (jazz)"),
|
||||||
|
28: ("清音电吉他", "Electric Guitar (clean)"),
|
||||||
|
29: ("弱音电吉他", "Electric Guitar (muted)"),
|
||||||
|
30: ("过驱电吉他", "Overdriven Guitar"),
|
||||||
|
31: ("失真电吉他", "Distortion Guitar"),
|
||||||
|
32: ("吉他泛音", "Guitar harmonics"),
|
||||||
|
33: ("原声贝斯", "Acoustic Bass"),
|
||||||
|
34: ("指奏电贝斯", "Electric Bass (finger)"),
|
||||||
|
35: ("拨奏电贝斯", "Electric Bass (pick)"),
|
||||||
|
36: ("无品贝斯", "Fretless Bass"),
|
||||||
|
37: ("击弦贝斯1", "Slap Bass 1"),
|
||||||
|
38: ("击弦贝斯2", "Slap Bass 2"),
|
||||||
|
39: ("合成贝斯1", "Synth Bass 1"),
|
||||||
|
40: ("合成贝斯2", "Synth Bass 2"),
|
||||||
|
41: ("小提琴", "Violin"),
|
||||||
|
42: ("中提琴", "Viola"),
|
||||||
|
43: ("大提琴", "Cello"),
|
||||||
|
44: ("低音提琴", "Contrabass"),
|
||||||
|
45: ("颤弓弦乐(弦乐震音)", "Tremolo Strings"),
|
||||||
|
46: ("弹拨弦乐(弦乐拨奏)", "Pizzicato Strings"),
|
||||||
|
47: ("竖琴", "Orchestral Harp"),
|
||||||
|
48: ("定音鼓", "Timpani"),
|
||||||
|
49: ("弦乐合奏1", "String Ensemble 1"),
|
||||||
|
50: ("弦乐合奏2", "String Ensemble 2"),
|
||||||
|
51: ("合成弦乐1", "Synth Strings 1"),
|
||||||
|
52: ("合成弦乐2", "Synth Strings 2"),
|
||||||
|
53: ("合唱“啊”音", "Choir Aahs"),
|
||||||
|
54: ("人声“嘟”音", "Voice Oohs"),
|
||||||
|
55: ("合成人声", "Synth Voice"),
|
||||||
|
56: ("交响打击乐", "Orchestra Hit"),
|
||||||
|
57: ("小号", "Trumpet"),
|
||||||
|
58: ("长号", "Trombone"),
|
||||||
|
59: ("大号", "Tuba"),
|
||||||
|
60: ("弱音小号", "Muted Trumpet"),
|
||||||
|
61: ("圆号(法国号)", "French Horn"),
|
||||||
|
62: ("铜管乐组", "Brass Section"),
|
||||||
|
63: ("合成铜管 1", "Synth Brass 1"),
|
||||||
|
64: ("合成铜管 2", "Synth Brass 2"),
|
||||||
|
65: ("高音萨克斯风", "Soprano Sax"),
|
||||||
|
66: ("中音萨克斯风", "Alto Sax"),
|
||||||
|
67: ("次中音萨克斯风", "Tenor Sax"),
|
||||||
|
68: ("上低音萨克斯风", "Baritone Sax"),
|
||||||
|
69: ("双簧管", "Oboe"),
|
||||||
|
70: ("英国管", "English Horn"),
|
||||||
|
71: ("大管(巴松管)", "Bassoon"),
|
||||||
|
72: ("单簧管(黑管)", "Clarinet"),
|
||||||
|
73: ("短笛", "Piccolo"),
|
||||||
|
74: ("长笛", "Flute"),
|
||||||
|
75: ("竖笛", "Recorder"),
|
||||||
|
76: ("排笛", "Pan Flute"),
|
||||||
|
77: ("瓶笛", "Blown Bottle"),
|
||||||
|
78: ("尺八", "Shakuhachi"),
|
||||||
|
79: ("哨子", "Whistle"),
|
||||||
|
80: ("陶笛", "Ocarina"),
|
||||||
|
81: ("合成方波", "Lead 1 (square)"),
|
||||||
|
82: ("锯齿波音", "Lead 2 (sawtooth)"),
|
||||||
|
83: ("汽笛风琴", "Lead 3 (calliope)"),
|
||||||
|
84: ("合成吹管", "Lead 4 (chiff)"),
|
||||||
|
85: ("合成电吉他", "Lead 5 (charang)"),
|
||||||
|
86: ("人声键盘", "Lead 6 (voice)"),
|
||||||
|
87: ("五度音", "Lead 7 (fifths)"),
|
||||||
|
88: ("低音与主音", "Lead 8 (bass+lead)"),
|
||||||
|
89: ("新纪", "Pad 1 (new age)"),
|
||||||
|
90: ("暖温", "Pad 2 (warm)"),
|
||||||
|
91: ("复音", "Pad 3 (polysynth)"),
|
||||||
|
92: ("合声", "Pad 4 (choir)"),
|
||||||
|
93: ("弓弦", "Pad 5 (bowed)"),
|
||||||
|
94: ("银铃", "Pad 6 (metallic)"),
|
||||||
|
95: ("荣光", "Pad 7 (halo)"),
|
||||||
|
96: ("轻扫", "Pad 8 (sweep)"),
|
||||||
|
97: ("夏雨", "FX 1 (rain)"),
|
||||||
|
98: ("音轨", "FX 2 (soundtrack)"),
|
||||||
|
99: ("水晶", "FX 3 (crystal)"),
|
||||||
|
100: ("大气", "FX 4 (atmosphere)"),
|
||||||
|
101: ("轻曼", "FX 5 (light)"),
|
||||||
|
102: ("魅影", "FX 6 (goblins)"),
|
||||||
|
103: ("回响", "FX 7 (echoes)"),
|
||||||
|
104: ("科幻", "FX 8 (sci-fi)"),
|
||||||
|
105: ("西塔琴", "Sitar"),
|
||||||
|
106: ("五弦琴(班卓琴)", "Banjo"),
|
||||||
|
107: ("三味线", "Shamisen"),
|
||||||
|
108: ("日本筝", "Koto"),
|
||||||
|
109: ("卡林巴铁片琴", "Kalimba"),
|
||||||
|
110: ("苏格兰风笛", "Bagpipe"),
|
||||||
|
111: ("古提琴", "Fiddle"),
|
||||||
|
112: ("唢呐", "Shanai"),
|
||||||
|
113: ("铃铛", "Tinkle Bell"),
|
||||||
|
114: ("阿哥铃", "Agogo"),
|
||||||
|
115: ("钢鼓", "Steel Drums"),
|
||||||
|
116: ("木块", "Woodblock"),
|
||||||
|
117: ("太鼓", "Taiko Drum"),
|
||||||
|
118: ("古式高音鼓", "Melodic Tom"),
|
||||||
|
119: ("合成鼓", "Synth Drum"),
|
||||||
|
120: ("铜钹", "Reverse Cymbal"),
|
||||||
|
121: ("吉他品格杂音", "Guitar Fret Noise"),
|
||||||
|
122: ("呼吸杂音", "Breath Noise"),
|
||||||
|
123: ("浪潮", "Seashore"),
|
||||||
|
124: ("鸟鸣", "Bird Tweet"),
|
||||||
|
125: ("电话", "Telephone"),
|
||||||
|
126: ("直升机", "Helicopter"),
|
||||||
|
127: ("鼓掌", "Applause"),
|
||||||
|
128: ("射击", "Gunshot"),
|
||||||
|
}
|
||||||
|
"""Midi乐音乐器名称对照表"""
|
||||||
|
|
||||||
|
MIDI_PERCUSSION_NOTE_NAME_TABLE: Dict[int, Tuple[str, str]] = {
|
||||||
|
35: ("原声大鼓", "Acoustic Bass Drum"),
|
||||||
|
36: ("大鼓", "Bass Drum 1"),
|
||||||
|
37: ("小鼓鼓边", "Side Stick"),
|
||||||
|
38: ("原声小军鼓", "Acoustic Snare"),
|
||||||
|
39: ("拍手", "Hand Clap"),
|
||||||
|
40: ("电子小军鼓", "Electric Snare"),
|
||||||
|
41: ("低音落地桶鼓", "Low Floor Tom"),
|
||||||
|
42: ("闭镲", "Closed Hi-Hat"),
|
||||||
|
43: ("高音落地桶鼓", "High Floor Tom"),
|
||||||
|
44: ("脚踏踩镲", "Pedal Hi-Hat"),
|
||||||
|
45: ("低桶鼓", "Low Tom"),
|
||||||
|
46: ("开镲", "Open Hi-Hat"),
|
||||||
|
47: ("低音中桶鼓", "Low-Mid Tom"),
|
||||||
|
48: ("高音中桶鼓", "Hi Mid Tom 2"),
|
||||||
|
49: ("强音钹1", "Crash Cymbal 1"),
|
||||||
|
50: ("高桶鼓", "High Tom"),
|
||||||
|
51: ("打点钹1", "Ride Cymbal 1"),
|
||||||
|
52: ("铙钹", "Chinese Cymbal"),
|
||||||
|
53: ("圆铃", "Ride Bell"),
|
||||||
|
54: ("铃鼓", "Tambourine"),
|
||||||
|
55: ("小钹铜钹", "Splash Cymbal"),
|
||||||
|
56: ("牛铃", "Cowbell"),
|
||||||
|
57: ("强音钹2", "Crash Cymbal 2"),
|
||||||
|
58: ("颤音器", "Vibra-Slap"),
|
||||||
|
59: ("打点钹2", "Ride Cymbal 2"),
|
||||||
|
60: ("高音邦加鼓", "Hi Bongo"),
|
||||||
|
61: ("低音邦加鼓", "Low Bongo"),
|
||||||
|
62: ("弱音高音康加鼓", "Mute Hi Conga"),
|
||||||
|
63: ("强音高音康加鼓", "Open Hi Conga"),
|
||||||
|
64: ("低音康加鼓", "Low Conga"),
|
||||||
|
65: ("高音天巴鼓", "High Timbale"),
|
||||||
|
66: ("低音天巴鼓", "Low Timbale"),
|
||||||
|
67: ("高音阿哥铃", "High Agogo"),
|
||||||
|
68: ("低音阿哥铃", "Low Agogo"),
|
||||||
|
69: ("串珠", "Cabasa"),
|
||||||
|
70: ("沙槌", "Maracas"),
|
||||||
|
71: ("短口哨", "Short Whistle"),
|
||||||
|
72: ("长口哨", "Long Whistle"),
|
||||||
|
73: ("短刮壶", "Short Guiro"),
|
||||||
|
74: ("长刮壶", "Long Guiro"),
|
||||||
|
75: ("梆子", "Claves"),
|
||||||
|
76: ("高音木块", "Hi Wood Block"),
|
||||||
|
77: ("低音木块", "Low Wood Block"),
|
||||||
|
78: ("弱音锯加鼓", "Mute Cuica"),
|
||||||
|
79: ("开音锯加鼓", "Open Cuica"),
|
||||||
|
80: ("弱音三角铁", "Mute Triangle"),
|
||||||
|
81: ("强音三角铁", "Open Triangle"),
|
||||||
|
}
|
||||||
|
"""Midi打击乐器名称对照表"""
|
||||||
|
|
||||||
|
# Minecraft用对照表
|
||||||
|
|
||||||
|
MC_PERCUSSION_INSTRUMENT_LIST: List[str] = [
|
||||||
|
"note.snare",
|
||||||
|
"note.bd",
|
||||||
|
"note.hat",
|
||||||
|
"note.basedrum",
|
||||||
|
"firework.blast",
|
||||||
|
"firework.twinkle",
|
||||||
|
"fire.ignite",
|
||||||
|
"mob.zombie.wood",
|
||||||
|
]
|
||||||
|
"""打击乐器列表"""
|
||||||
|
|
||||||
|
MC_PITCHED_INSTRUMENT_LIST: List[str] = [
|
||||||
|
"note.harp",
|
||||||
|
"note.pling",
|
||||||
|
"note.guitar",
|
||||||
|
"note.iron_xylophone",
|
||||||
|
"note.bell",
|
||||||
|
"note.xylophone",
|
||||||
|
"note.chime",
|
||||||
|
"note.banjo",
|
||||||
|
"note.flute",
|
||||||
|
"note.bass",
|
||||||
|
"note.didgeridoo",
|
||||||
|
"note.bit",
|
||||||
|
"note.cow_bell",
|
||||||
|
]
|
||||||
|
"""乐音乐器列表"""
|
||||||
|
|
||||||
|
MC_INSTRUMENT_BLOCKS_TABLE: Dict[str, Tuple[str, ...]] = {
|
||||||
|
"note.bass": ("planks",),
|
||||||
|
"note.snare": ("sand",),
|
||||||
|
"note.hat": ("glass",),
|
||||||
|
"note.bd": ("stone",),
|
||||||
|
"note.basedrum": ("stone",),
|
||||||
|
"note.bell": ("gold_block",),
|
||||||
|
"note.flute": ("clay",),
|
||||||
|
"note.chime": ("packed_ice",),
|
||||||
|
"note.guitar": ("wool",),
|
||||||
|
"note.xylobone": ("bone_block",),
|
||||||
|
"note.iron_xylophone": ("iron_block",),
|
||||||
|
"note.cow_bell": ("soul_sand",),
|
||||||
|
"note.didgeridoo": ("pumpkin",),
|
||||||
|
"note.bit": ("emerald_block",),
|
||||||
|
"note.banjo": ("hay_block",),
|
||||||
|
"note.pling": ("glowstone",),
|
||||||
|
"note.bassattack": ("stone",), # 无法找到此音效
|
||||||
|
"note.harp": ("dirt",),
|
||||||
|
# 呃……
|
||||||
|
"firework.blast": ("sandstone",),
|
||||||
|
"firework.twinkle": ("red_sandstone",),
|
||||||
|
"fire.ignite": ("concrete_powder",),
|
||||||
|
"mob.zombie.wood": ("sand",),
|
||||||
|
}
|
||||||
|
"""MC乐器对音符盒下垫方块对照表"""
|
||||||
|
|
||||||
|
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE: Dict[str, str] = {
|
||||||
|
"note.iron_xylophone": "note.xylophone",
|
||||||
|
"note.cow_bell": "note.xylophone",
|
||||||
|
"note.didgeridoo": "note.guitar",
|
||||||
|
"note.bit": "note.harp",
|
||||||
|
"note.banjo": "note.flute",
|
||||||
|
"note.pling": "note.harp",
|
||||||
|
}
|
||||||
|
"""在 Minecraft JE 1.12 ~ JE 1.14 的版本中,部分乐器是没有的,这是金羿的乐器替换表"""
|
||||||
|
|
||||||
|
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE: Dict[str, str] = {
|
||||||
|
# lt je 12
|
||||||
|
"note.bell": "note.harp",
|
||||||
|
"note.flute": "note.harp",
|
||||||
|
"note.chime": "note.harp",
|
||||||
|
"note.guitar": "note.bass",
|
||||||
|
"note.xylophone": "note.hat",
|
||||||
|
# rt je 12
|
||||||
|
"note.iron_xylophone": "note.hat",
|
||||||
|
"note.cow_bell": "note.ha",
|
||||||
|
"note.didgeridoo": "note.bass",
|
||||||
|
"note.bit": "note.harp",
|
||||||
|
"note.banjo": "note.harp",
|
||||||
|
"note.pling": "note.harp",
|
||||||
|
}
|
||||||
|
"""在 Minecraft JE Beta1.2 / BE 0.13.0 ~ JE 1.12 / BE 1.13.0 的版本中,部分乐器是没有的,这是金羿的乐器替换表"""
|
||||||
|
|
||||||
|
# Midi对MC通用对照表
|
||||||
|
|
||||||
|
MM_INSTRUMENT_RANGE_TABLE: Dict[str, Tuple[Tuple[int, int], int]] = {
|
||||||
|
"note.harp": ((42, 66), 54),
|
||||||
|
"note.pling": ((42, 66), 54),
|
||||||
|
"note.guitar": ((30, 54), 42),
|
||||||
|
"note.iron_xylophone": ((42, 66), 54),
|
||||||
|
"note.bell": ((66, 90), 78),
|
||||||
|
"note.xylophone": ((66, 90), 78),
|
||||||
|
"note.chime": ((66, 90), 78),
|
||||||
|
"note.banjo": ((42, 66), 54),
|
||||||
|
"note.flute": ((54, 78), 66),
|
||||||
|
"note.bass": ((18, 42), 30),
|
||||||
|
"note.snare": ((-1, 128), 0), # 实际上是 0~127
|
||||||
|
"note.didgeridoo": ((18, 42), 30),
|
||||||
|
"mob.zombie.wood": ((-1, 128), 0),
|
||||||
|
"note.bit": ((42, 66), 54),
|
||||||
|
"note.hat": ((-1, 128), 0),
|
||||||
|
"note.bd": ((-1, 128), 0),
|
||||||
|
"note.basedrum": ((-1, 128), 0),
|
||||||
|
"firework.blast": ((-1, 128), 0),
|
||||||
|
"firework.twinkle": ((-1, 128), 0),
|
||||||
|
"fire.ignite": ((-1, 128), 0),
|
||||||
|
"note.cow_bell": ((54, 78), 66),
|
||||||
|
}
|
||||||
|
"""不同乐器的音域偏离对照表"""
|
||||||
|
|
||||||
|
MM_INSTRUMENT_DEVIATION_TABLE: Dict[str, int] = {
|
||||||
|
"note.harp": 6,
|
||||||
|
"note.pling": 6,
|
||||||
|
"note.guitar": -6,
|
||||||
|
"note.iron_xylophone": 6,
|
||||||
|
"note.bell": 30,
|
||||||
|
"note.xylophone": 30,
|
||||||
|
"note.chime": 30,
|
||||||
|
"note.banjo": 6,
|
||||||
|
"note.flute": 18,
|
||||||
|
"note.bass": -18,
|
||||||
|
"note.snare": 0,
|
||||||
|
"note.didgeridoo": -18,
|
||||||
|
"mob.zombie.wood": 0,
|
||||||
|
"note.bit": 6,
|
||||||
|
"note.hat": 0,
|
||||||
|
"note.bd": 0,
|
||||||
|
"firework.blast": 0,
|
||||||
|
"firework.twinkle": 0,
|
||||||
|
"fire.ignite": 0,
|
||||||
|
"note.cow_bell": 6,
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
不同乐器的音调偏离对照表
|
||||||
|
*注意* 该表中的单位是对于 Midi Pitch 音调(整数)的低音偏移。
|
||||||
|
也就是说,该数值越高,则在 Midi Pitch 中的值域越低
|
||||||
|
默认的偏移量为 6 ,因为在计算音高时候少减去了 6 个 Pitch 单位
|
||||||
|
(在表里的数据是用作被减数的,实际计算时默认有 +6,所以在表中默认的 6 最后就会被抵消)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Midi音高对MC方块对照表
|
||||||
|
|
||||||
|
# 金羿ELS 音符方块对照表
|
||||||
|
|
||||||
|
MN_EILLES_NOTE_BLOCK_TABLE: Dict[int, str] = {
|
||||||
|
0: "C",
|
||||||
|
1: "C#",
|
||||||
|
2: "D",
|
||||||
|
3: "D#",
|
||||||
|
4: "E",
|
||||||
|
5: "F",
|
||||||
|
6: "F#",
|
||||||
|
7: "G",
|
||||||
|
8: "G#",
|
||||||
|
9: "A",
|
||||||
|
10: "A#",
|
||||||
|
11: "B",
|
||||||
|
12: "C",
|
||||||
|
13: "C#",
|
||||||
|
14: "D",
|
||||||
|
15: "D#",
|
||||||
|
16: "E",
|
||||||
|
17: "F",
|
||||||
|
18: "F#",
|
||||||
|
19: "G",
|
||||||
|
20: "G#",
|
||||||
|
21: "A",
|
||||||
|
22: "A#",
|
||||||
|
23: "B",
|
||||||
|
24: "C",
|
||||||
|
25: "C#",
|
||||||
|
26: "D",
|
||||||
|
27: "D#",
|
||||||
|
28: "E",
|
||||||
|
29: "F",
|
||||||
|
30: "F#",
|
||||||
|
31: "G",
|
||||||
|
32: "G#",
|
||||||
|
33: "A",
|
||||||
|
34: "A#",
|
||||||
|
35: "B",
|
||||||
|
36: "C",
|
||||||
|
37: "C#",
|
||||||
|
38: "D",
|
||||||
|
39: "D#",
|
||||||
|
40: "E",
|
||||||
|
41: "F",
|
||||||
|
42: "F#",
|
||||||
|
43: "G",
|
||||||
|
44: "G#",
|
||||||
|
45: "A",
|
||||||
|
46: "A#",
|
||||||
|
47: "B",
|
||||||
|
48: "C",
|
||||||
|
49: "C#",
|
||||||
|
50: "D",
|
||||||
|
51: "D#",
|
||||||
|
52: "E",
|
||||||
|
53: "F",
|
||||||
|
54: "F#",
|
||||||
|
55: "G",
|
||||||
|
56: "G#",
|
||||||
|
57: "A",
|
||||||
|
58: "A#",
|
||||||
|
59: "B",
|
||||||
|
60: "C",
|
||||||
|
61: "C#",
|
||||||
|
62: "D",
|
||||||
|
63: "D#",
|
||||||
|
64: "E",
|
||||||
|
65: "F",
|
||||||
|
66: "F#",
|
||||||
|
67: "G",
|
||||||
|
68: "G#",
|
||||||
|
69: "A",
|
||||||
|
70: "A#",
|
||||||
|
71: "B",
|
||||||
|
72: "C",
|
||||||
|
73: "C#",
|
||||||
|
74: "D",
|
||||||
|
75: "D#",
|
||||||
|
76: "E",
|
||||||
|
77: "F",
|
||||||
|
78: "F#",
|
||||||
|
79: "G",
|
||||||
|
80: "G#",
|
||||||
|
81: "A",
|
||||||
|
82: "A#",
|
||||||
|
83: "B",
|
||||||
|
84: "C",
|
||||||
|
85: "C#",
|
||||||
|
86: "D",
|
||||||
|
87: "D#",
|
||||||
|
88: "E",
|
||||||
|
89: "F",
|
||||||
|
90: "F#",
|
||||||
|
91: "G",
|
||||||
|
92: "G#",
|
||||||
|
93: "A",
|
||||||
|
94: "A#",
|
||||||
|
95: "B",
|
||||||
|
96: "C",
|
||||||
|
97: "C#",
|
||||||
|
98: "D",
|
||||||
|
99: "D#",
|
||||||
|
100: "E",
|
||||||
|
101: "F",
|
||||||
|
102: "F#",
|
||||||
|
103: "G",
|
||||||
|
104: "G#",
|
||||||
|
105: "A",
|
||||||
|
106: "A#",
|
||||||
|
107: "B",
|
||||||
|
108: "C",
|
||||||
|
109: "C#",
|
||||||
|
110: "D",
|
||||||
|
111: "D#",
|
||||||
|
112: "E",
|
||||||
|
113: "F",
|
||||||
|
114: "F#",
|
||||||
|
115: "G",
|
||||||
|
116: "G#",
|
||||||
|
117: "A",
|
||||||
|
118: "A#",
|
||||||
|
119: "B",
|
||||||
|
120: "C",
|
||||||
|
121: "C#",
|
||||||
|
122: "D",
|
||||||
|
123: "D#",
|
||||||
|
124: "E",
|
||||||
|
125: "F",
|
||||||
|
126: "F#",
|
||||||
|
127: "G",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 即将启用
|
||||||
|
# height2note = {
|
||||||
|
# 0.5: 0,
|
||||||
|
# 0.53: 1,
|
||||||
|
# 0.56: 2,
|
||||||
|
# 0.6: 3,
|
||||||
|
# 0.63: 4,
|
||||||
|
# 0.67: 5,
|
||||||
|
# 0.7: 6,
|
||||||
|
# 0.75: 7,
|
||||||
|
# 0.8: 8,
|
||||||
|
# 0.84: 9,
|
||||||
|
# 0.9: 10,
|
||||||
|
# 0.94: 11,
|
||||||
|
# 1.0: 12,
|
||||||
|
# 1.05: 13,
|
||||||
|
# 1.12: 14,
|
||||||
|
# 1.2: 15,
|
||||||
|
# 1.25: 16,
|
||||||
|
# 1.33: 17,
|
||||||
|
# 1.4: 18,
|
||||||
|
# 1.5: 19,
|
||||||
|
# 1.6: 20,
|
||||||
|
# 1.7: 21,
|
||||||
|
# 1.8: 22,
|
||||||
|
# 1.9: 23,
|
||||||
|
# 2.0: 24,
|
||||||
|
# }
|
||||||
|
# """音高对照表\n
|
||||||
|
# MC音高:音符盒音调"""
|
||||||
803
Musicreater/data.py
Normal file
803
Musicreater/data.py
Normal file
@@ -0,0 +1,803 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 的内部数据类
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# “
|
||||||
|
# 把代码 洒落在这里
|
||||||
|
# 和音符 留下的沙砾
|
||||||
|
# 一点一点爬进你类定义的缝隙
|
||||||
|
# ” —— 乐曲访问 by resnah
|
||||||
|
|
||||||
|
import heapq
|
||||||
|
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
Any,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Dict,
|
||||||
|
Set,
|
||||||
|
Sequence,
|
||||||
|
Callable,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
Literal,
|
||||||
|
Hashable,
|
||||||
|
TypeVar,
|
||||||
|
Mapping,
|
||||||
|
)
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
||||||
|
from .paramcurve import ParamCurve
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class SoundAtmos:
|
||||||
|
"""声源方位类"""
|
||||||
|
|
||||||
|
sound_distance: float
|
||||||
|
"""声源距离 方块"""
|
||||||
|
|
||||||
|
sound_azimuth: Tuple[float, float]
|
||||||
|
"""声源方位 角度(rV左右 rH上下)"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
distance: Optional[float] = None,
|
||||||
|
azimuth: Optional[Tuple[float, float]] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
定义一个发声方位
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
distance: float
|
||||||
|
发声源距离玩家的距离(半径 `r`)
|
||||||
|
注:距离越近,音量越高,默认为 0。此参数可以作为音轨的音量使用。
|
||||||
|
音量若默认为 +0,则此值当为 8;此值最小为 0.01,最大为 16。
|
||||||
|
azimuth: tuple[float, float]
|
||||||
|
声源方位
|
||||||
|
注:此参数为tuple,包含两个元素,分别表示:
|
||||||
|
`rV` 发声源在竖直(上下)轴上,从玩家视角正前方开始,向顺时针旋转的角度
|
||||||
|
`rH` 发声源在水平(左右)轴上,从玩家视角正前方开始,向上
|
||||||
|
(到达玩家正上方顶点后变为向下,以此类推的旋转)旋转的角度
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.sound_azimuth = (azimuth[0] % 360, azimuth[1] % 360) if azimuth else (0, 0)
|
||||||
|
"""声源方位"""
|
||||||
|
|
||||||
|
# 如果指定为零,那么为零,但如果不指定或者指定为负数,则为 0.01 的距离
|
||||||
|
self.sound_distance = (
|
||||||
|
(16 if distance > 16 else (distance if distance >= 0 else 0.01))
|
||||||
|
if distance is not None
|
||||||
|
else 0.01
|
||||||
|
)
|
||||||
|
"""声源距离"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_displacement(
|
||||||
|
cls,
|
||||||
|
displacement: Optional[Tuple[float, float, float]] = None,
|
||||||
|
) -> "SoundAtmos":
|
||||||
|
|
||||||
|
if displacement is None:
|
||||||
|
# displacement = (0, 0, 0)
|
||||||
|
return cls()
|
||||||
|
else:
|
||||||
|
r = sqrt(displacement[0] ** 2 + displacement[1] ** 2 + displacement[2] ** 2)
|
||||||
|
if r == 0:
|
||||||
|
return cls(distance=0, azimuth=(0, 0))
|
||||||
|
else:
|
||||||
|
beta_h = round(degrees(asin(displacement[1] / r)), 8)
|
||||||
|
if displacement[2] == 0:
|
||||||
|
alpha_v = -90 if displacement[0] > 0 else 90
|
||||||
|
else:
|
||||||
|
alpha_v = round(
|
||||||
|
degrees(atan(-displacement[0] / displacement[2])), 8
|
||||||
|
)
|
||||||
|
return cls(distance=r, azimuth=(alpha_v, beta_h))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position_displacement(self) -> Tuple[float, float, float]:
|
||||||
|
"""声像位移,直接可应用于我的世界的相对视角的坐标参考系中(^x ^y ^z)"""
|
||||||
|
dk1 = self.sound_distance * round(cos(radians(self.sound_azimuth[1])), 8)
|
||||||
|
return (
|
||||||
|
-dk1 * round(sin(radians(self.sound_azimuth[0])), 8),
|
||||||
|
self.sound_distance * round(sin(radians(self.sound_azimuth[1])), 8),
|
||||||
|
dk1 * round(cos(radians(self.sound_azimuth[0])), 8),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "SoundAtmos(d={}, rV={}, rH={})".format(
|
||||||
|
self.sound_distance, *self.sound_azimuth
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class SingleNote:
|
||||||
|
"""存储单个音符的类"""
|
||||||
|
|
||||||
|
midi_pitch: int
|
||||||
|
"""Midi 音高"""
|
||||||
|
|
||||||
|
volume: int
|
||||||
|
"""力度/播放响度 0~127 百廿七分比"""
|
||||||
|
|
||||||
|
start_time: int
|
||||||
|
"""开始之时 命令刻"""
|
||||||
|
|
||||||
|
duration: int
|
||||||
|
"""音符持续时间 命令刻"""
|
||||||
|
|
||||||
|
high_precision_start_time: int
|
||||||
|
"""高精度开始时间偏量 1/1250 秒"""
|
||||||
|
|
||||||
|
extra_info: Dict[str, Any]
|
||||||
|
"""你觉得放什么好?"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
note_pitch: Optional[int],
|
||||||
|
note_volume: int,
|
||||||
|
start_tick: int,
|
||||||
|
keep_tick: int,
|
||||||
|
mass_precision_time: int = 0,
|
||||||
|
extra_information: Dict[str, Any] = {},
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
用于存储单个音符的类
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
midi_pitch: int
|
||||||
|
Midi 音高
|
||||||
|
note_volume: int
|
||||||
|
响度/力度(百廿七分比, 0~127)
|
||||||
|
start_time: int
|
||||||
|
开始之时(命令刻)
|
||||||
|
注:此处的时间是用从乐曲开始到当前的刻数
|
||||||
|
last_time: int
|
||||||
|
音符延续时间(命令刻)
|
||||||
|
mass_precision_time: int
|
||||||
|
高精度的开始时间偏移量(1/1250秒)
|
||||||
|
is_percussion: bool
|
||||||
|
是否作为打击乐器
|
||||||
|
extra_information: Dict[str, Any]
|
||||||
|
附加信息,尽量存储为字典
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
MineNote 类
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.midi_pitch: int = 66 if note_pitch is None else note_pitch
|
||||||
|
"""Midi 音高"""
|
||||||
|
self.volume: int = note_volume
|
||||||
|
"""响度(力度)"""
|
||||||
|
self.start_time: int = start_tick
|
||||||
|
"""开始之时 命令刻"""
|
||||||
|
self.duration: int = keep_tick
|
||||||
|
"""音符持续时间 命令刻"""
|
||||||
|
self.high_precision_start_time: int = mass_precision_time
|
||||||
|
"""高精度开始时间偏量 0.4 毫秒"""
|
||||||
|
|
||||||
|
self.extra_info = extra_information if extra_information else {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decode(cls, code_buffer: bytes, is_high_time_precision: bool = True):
|
||||||
|
"""自字节码析出 SingleNote 类"""
|
||||||
|
duration_ = (
|
||||||
|
group_1 := int.from_bytes(code_buffer[:6], "big")
|
||||||
|
) & 0b11111111111111111
|
||||||
|
start_tick_ = (group_1 := group_1 >> 17) & 0b11111111111111111
|
||||||
|
note_volume_ = (group_1 := group_1 >> 17) & 0b1111111
|
||||||
|
note_pitch_ = (group_1 := group_1 >> 7) & 0b1111111
|
||||||
|
|
||||||
|
try:
|
||||||
|
return cls(
|
||||||
|
note_pitch=note_pitch_,
|
||||||
|
note_volume=note_volume_,
|
||||||
|
start_tick=start_tick_,
|
||||||
|
keep_tick=duration_,
|
||||||
|
mass_precision_time=code_buffer[6] if is_high_time_precision else 0,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# 我也不知道为什么这里要放一个异常处理
|
||||||
|
# 之前用到过吗?
|
||||||
|
# —— 2026.01.25 金羿
|
||||||
|
print(
|
||||||
|
"[Exception] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format(
|
||||||
|
code_buffer, "已" if is_high_time_precision else "未"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
raise SingleNoteDecodeError(
|
||||||
|
"技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format(
|
||||||
|
group_1, code_buffer
|
||||||
|
),
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def encode(self, is_high_time_precision: bool = True) -> bytes:
|
||||||
|
"""
|
||||||
|
将数据打包为字节码
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
is_high_time_precision: bool
|
||||||
|
是否启用高精度,默认为**是**
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
bytes
|
||||||
|
打包好的字节码
|
||||||
|
"""
|
||||||
|
|
||||||
|
# SingleNote 的字节码
|
||||||
|
|
||||||
|
# note_pitch 7 位 支持到 127
|
||||||
|
# volume 长度 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.midi_pitch << 7) + self.volume) << 17) + self.start_time)
|
||||||
|
<< 17
|
||||||
|
)
|
||||||
|
+ self.duration
|
||||||
|
).to_bytes(6, "big")
|
||||||
|
# + self.track_no.to_bytes(1, "big")
|
||||||
|
+ (
|
||||||
|
self.high_precision_start_time.to_bytes(1, "big")
|
||||||
|
if is_high_time_precision
|
||||||
|
else b""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
|
"""设置附加信息"""
|
||||||
|
if isinstance(key, str):
|
||||||
|
self.extra_info[key] = value
|
||||||
|
elif (
|
||||||
|
isinstance(key, Sequence)
|
||||||
|
and isinstance(value, Sequence)
|
||||||
|
and (k := len(key)) == len(value)
|
||||||
|
):
|
||||||
|
for i in range(k):
|
||||||
|
self.extra_info[key[i]] = value[i]
|
||||||
|
else:
|
||||||
|
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_info(self, key: str, default: Any = None) -> Any:
|
||||||
|
"""获取附加信息"""
|
||||||
|
return self.extra_info.get(key, default)
|
||||||
|
|
||||||
|
def stringize(self, include_extra_data: bool = False) -> str:
|
||||||
|
return "TrackedNote(Pitch = {}, Volume = {}, StartTick = {}, Duration = {}, TimeOffset = {}".format(
|
||||||
|
self.midi_pitch,
|
||||||
|
self.volume,
|
||||||
|
self.start_time,
|
||||||
|
self.duration,
|
||||||
|
self.high_precision_start_time,
|
||||||
|
) + (
|
||||||
|
", ExtraData = {})".format(self.extra_info) if include_extra_data else ")"
|
||||||
|
)
|
||||||
|
|
||||||
|
# def __list__(self) -> List[int]:
|
||||||
|
# 我不认为这个类应当被作为列表使用
|
||||||
|
|
||||||
|
def __tuple__(
|
||||||
|
self,
|
||||||
|
) -> Tuple[int, int, int, int, int]:
|
||||||
|
return (
|
||||||
|
self.midi_pitch,
|
||||||
|
self.volume,
|
||||||
|
self.start_time,
|
||||||
|
self.duration,
|
||||||
|
self.high_precision_start_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
return {
|
||||||
|
"Pitch": self.midi_pitch,
|
||||||
|
"Volume": self.volume,
|
||||||
|
"StartTick": self.start_time,
|
||||||
|
"Duration": self.duration,
|
||||||
|
"TimeOffset": self.high_precision_start_time,
|
||||||
|
"ExtraData": self.extra_info,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __eq__(self, other: "SingleNote") -> bool:
|
||||||
|
"""比较两个音符是否具有相同的属性,不计附加信息"""
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return False
|
||||||
|
return self.__tuple__() == other.__tuple__()
|
||||||
|
|
||||||
|
def __lt__(self, other: "SingleNote") -> bool:
|
||||||
|
"""比较自己是否在开始时间上早于另一个音符"""
|
||||||
|
if self.start_time == other.start_time:
|
||||||
|
return self.high_precision_start_time < other.high_precision_start_time
|
||||||
|
else:
|
||||||
|
return self.start_time < other.start_time
|
||||||
|
|
||||||
|
def __gt__(self, other: "SingleNote") -> bool:
|
||||||
|
"""比较自己是否在开始时间上晚于另一个音符"""
|
||||||
|
if self.start_time == other.start_time:
|
||||||
|
return self.high_precision_start_time > other.high_precision_start_time
|
||||||
|
else:
|
||||||
|
return self.start_time > other.start_time
|
||||||
|
|
||||||
|
|
||||||
|
class CurvableParam(str, Enum):
|
||||||
|
"""可曲线化的参数 枚举类"""
|
||||||
|
|
||||||
|
PITCH = "adjust_note_pitch"
|
||||||
|
VOLUME = "adjust_note_volume"
|
||||||
|
DISTANCE = "adjust_note_sound_distance"
|
||||||
|
LR_PANNING = "adjust_note_leftright_panning_degree"
|
||||||
|
UD_PANNING = "adjust_note_updown_panning_degree"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MineNote:
|
||||||
|
"""我的世界音符对象(仅提供我的世界相关接口)"""
|
||||||
|
|
||||||
|
pitch: float
|
||||||
|
"""Midi 音高"""
|
||||||
|
instrument: str
|
||||||
|
"""乐器 ID"""
|
||||||
|
volume: float
|
||||||
|
"""力度/播放音量 0~127 百廿七分比"""
|
||||||
|
start_tick: int
|
||||||
|
"""开始之时 命令刻"""
|
||||||
|
duration_tick: int
|
||||||
|
"""音符持续时间 命令刻"""
|
||||||
|
persiced_time: int
|
||||||
|
"""高精度开始时间偏量 1/1250 秒"""
|
||||||
|
percussive: bool
|
||||||
|
"""是否作为打击乐器启用"""
|
||||||
|
position: SoundAtmos
|
||||||
|
"""声像方位"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_single_note(
|
||||||
|
cls,
|
||||||
|
note: SingleNote,
|
||||||
|
note_instrument: str,
|
||||||
|
is_persiced_time: bool,
|
||||||
|
is_percussive_note: bool,
|
||||||
|
sound_position: SoundAtmos,
|
||||||
|
adjust_note_pitch: float = 0.0,
|
||||||
|
adjust_note_volume: float = 0.0,
|
||||||
|
adjust_note_sound_distance: float = 0.0,
|
||||||
|
adjust_note_leftright_panning_degree: float = 0.0,
|
||||||
|
adjust_note_updown_panning_degree: float = 0.0,
|
||||||
|
) -> "MineNote":
|
||||||
|
"""从SingleNote对象创建MineNote对象"""
|
||||||
|
sound_position.sound_distance += adjust_note_sound_distance
|
||||||
|
sound_position.sound_azimuth = (
|
||||||
|
sound_position.sound_azimuth[0] + adjust_note_leftright_panning_degree,
|
||||||
|
sound_position.sound_azimuth[1] + adjust_note_updown_panning_degree,
|
||||||
|
)
|
||||||
|
return cls(
|
||||||
|
pitch=note.midi_pitch + adjust_note_pitch,
|
||||||
|
instrument=note_instrument,
|
||||||
|
volume=note.volume + adjust_note_volume,
|
||||||
|
start_tick=note.start_time,
|
||||||
|
duration_tick=note.duration,
|
||||||
|
persiced_time=note.high_precision_start_time if is_persiced_time else 0,
|
||||||
|
percussive=is_percussive_note,
|
||||||
|
position=sound_position,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleTrack(List[SingleNote]):
|
||||||
|
"""存储单个轨道的类"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""轨道之名称"""
|
||||||
|
|
||||||
|
is_enabled: bool = True
|
||||||
|
"""该音轨是否启用"""
|
||||||
|
|
||||||
|
instrument: str
|
||||||
|
"""乐器ID"""
|
||||||
|
|
||||||
|
is_high_time_precision: bool
|
||||||
|
"""该音轨是否使用高精度时间"""
|
||||||
|
|
||||||
|
is_percussive: bool
|
||||||
|
"""该音轨是否标记为打击乐器轨道"""
|
||||||
|
|
||||||
|
sound_position: SoundAtmos
|
||||||
|
"""声像方位"""
|
||||||
|
|
||||||
|
argument_curves: Dict[CurvableParam, Union[ParamCurve, Literal[None]]]
|
||||||
|
"""参数曲线"""
|
||||||
|
|
||||||
|
extra_info: Dict[str, Any]
|
||||||
|
"""你觉得放什么好?"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args: SingleNote,
|
||||||
|
track_name: str = "未命名音轨",
|
||||||
|
track_instrument: str = "",
|
||||||
|
precise_time: bool = True,
|
||||||
|
percussion: bool = False,
|
||||||
|
sound_direction: Optional[SoundAtmos] = None,
|
||||||
|
extra_information: Dict[str, Any] = {},
|
||||||
|
):
|
||||||
|
self.name = track_name
|
||||||
|
"""音轨名称"""
|
||||||
|
|
||||||
|
self.instrument = track_instrument
|
||||||
|
"""乐器ID"""
|
||||||
|
|
||||||
|
self.is_high_time_precision = precise_time
|
||||||
|
"""是否使用高精度时间"""
|
||||||
|
|
||||||
|
self.is_percussive = percussion
|
||||||
|
"""是否为打击乐器"""
|
||||||
|
|
||||||
|
# 如果不这样的话,所有的新的 SingleTrack 类都会有一个共同的声像方位
|
||||||
|
self.sound_position = sound_direction if sound_direction else SoundAtmos()
|
||||||
|
"""声像方位"""
|
||||||
|
|
||||||
|
self.extra_info = extra_information if extra_information else {}
|
||||||
|
|
||||||
|
self.argument_curves = {item: None for item in CurvableParam}
|
||||||
|
|
||||||
|
super().__init__(*args)
|
||||||
|
super().sort()
|
||||||
|
|
||||||
|
def disable(self) -> None:
|
||||||
|
"""禁用音轨"""
|
||||||
|
|
||||||
|
self.is_enabled = False
|
||||||
|
|
||||||
|
def enable(self) -> None:
|
||||||
|
"""启用音轨"""
|
||||||
|
|
||||||
|
self.is_enabled = True
|
||||||
|
|
||||||
|
def toggle_able(self) -> None:
|
||||||
|
"""切换音轨的启用状态"""
|
||||||
|
|
||||||
|
self.is_enabled = not self.is_enabled
|
||||||
|
|
||||||
|
def append(self, object: SingleNote) -> None:
|
||||||
|
"""
|
||||||
|
添加一个音符,推荐使用 add 方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.add(object)
|
||||||
|
|
||||||
|
def add(self, item: SingleNote) -> None:
|
||||||
|
"""
|
||||||
|
在音轨里添加一个音符
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(item, SingleNote):
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"单音轨类的元素类型须为单音符(`SingleNote`),不可为:`{}`".format(
|
||||||
|
type(item).__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
super().append(item)
|
||||||
|
super().sort() # =========================== TODO 需要优化
|
||||||
|
|
||||||
|
def update(self, items: Iterable[SingleNote]):
|
||||||
|
"""
|
||||||
|
拼接两个音轨
|
||||||
|
"""
|
||||||
|
super().extend(items)
|
||||||
|
super().sort() # =========================== TODO 需要优化
|
||||||
|
|
||||||
|
def get(self, time: int) -> Generator[SingleNote, None, None]:
|
||||||
|
"""通过开始时间来获取音符"""
|
||||||
|
|
||||||
|
return (x for x in self if x.start_time == time)
|
||||||
|
|
||||||
|
def get_notes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Iterator[SingleNote]:
|
||||||
|
"""通过开始时间和结束时间来获取音符"""
|
||||||
|
if end_time < start_time:
|
||||||
|
raise ParameterValueError(
|
||||||
|
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".format(
|
||||||
|
end_time, start_time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif end_time < 0:
|
||||||
|
raise ParameterValueError(
|
||||||
|
"获取音符的时间范围有误,终止时间`{}`不可为负数".format(end_time)
|
||||||
|
)
|
||||||
|
elif start_time <= 0 and end_time >= self[-1].start_time:
|
||||||
|
return iter(self)
|
||||||
|
|
||||||
|
return (
|
||||||
|
x
|
||||||
|
for x in self
|
||||||
|
if (x.start_time >= start_time) and (x.start_time <= end_time)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_minenotes(
|
||||||
|
self, range_start_time: float = 0, range_end_time: float = inf
|
||||||
|
) -> Generator[MineNote, Any, None]:
|
||||||
|
"""获取能够用以在我的世界播放的音符数据类"""
|
||||||
|
|
||||||
|
for _note in self.get_notes(range_start_time, range_end_time):
|
||||||
|
yield MineNote.from_single_note(
|
||||||
|
note=_note,
|
||||||
|
note_instrument=self.instrument,
|
||||||
|
is_persiced_time=self.is_high_time_precision,
|
||||||
|
is_percussive_note=self.is_percussive,
|
||||||
|
sound_position=self.sound_position,
|
||||||
|
**{
|
||||||
|
item.value: argcrv.value_at(_note.start_time)
|
||||||
|
for item in CurvableParam
|
||||||
|
if (argcrv := self.argument_curves[item])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def note_amount(self) -> int:
|
||||||
|
"""音符数"""
|
||||||
|
return len(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notes(self) -> List[SingleNote]:
|
||||||
|
"""音符列表"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minenotes(self) -> Iterator[MineNote]:
|
||||||
|
"""
|
||||||
|
直接返回当前音轨所有音符的我的世界数据形式
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
MineNote.from_single_note(
|
||||||
|
note=_note,
|
||||||
|
note_instrument=self.instrument,
|
||||||
|
is_persiced_time=self.is_high_time_precision,
|
||||||
|
is_percussive_note=self.is_percussive,
|
||||||
|
sound_position=self.sound_position,
|
||||||
|
**{
|
||||||
|
item.value: argcrv.value_at(_note.start_time)
|
||||||
|
for item in CurvableParam
|
||||||
|
if (argcrv := self.argument_curves[item])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for _note in self
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
|
"""设置附加信息"""
|
||||||
|
if isinstance(key, str):
|
||||||
|
self.extra_info[key] = value
|
||||||
|
elif (
|
||||||
|
isinstance(key, Sequence)
|
||||||
|
and isinstance(value, Sequence)
|
||||||
|
and (k := len(key)) == len(value)
|
||||||
|
):
|
||||||
|
for i in range(k):
|
||||||
|
self.extra_info[key[i]] = value[i]
|
||||||
|
else:
|
||||||
|
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_info(self, key: str, default: Any = None) -> Any:
|
||||||
|
"""获取附加信息"""
|
||||||
|
return self.extra_info.get(key, default)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleMusic(List[SingleTrack]):
|
||||||
|
"""存储单个曲子的类"""
|
||||||
|
|
||||||
|
music_name: str
|
||||||
|
"""乐曲名称"""
|
||||||
|
|
||||||
|
music_creator: str
|
||||||
|
"""本我的世界曲目的制作者"""
|
||||||
|
|
||||||
|
music_original_author: str
|
||||||
|
"""曲目的原作者"""
|
||||||
|
|
||||||
|
music_description: str
|
||||||
|
"""当前曲目的简介"""
|
||||||
|
|
||||||
|
music_credits: str
|
||||||
|
"""曲目的版权信息"""
|
||||||
|
|
||||||
|
# 感叹一下什么叫冗余设计啊!(叉腰)
|
||||||
|
extra_info: Dict[str, Any]
|
||||||
|
"""这还得放东西?"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args: SingleTrack,
|
||||||
|
name: str = "未命名乐曲",
|
||||||
|
creator: str = "未命名制作者",
|
||||||
|
original_author: str = "未命名原曲作",
|
||||||
|
description: str = "无简介",
|
||||||
|
credits: str = "保留所有权利。All Rights Reserved.",
|
||||||
|
extra_information: Dict[str, Any] = {},
|
||||||
|
):
|
||||||
|
self.music_name = name
|
||||||
|
"""乐曲名称"""
|
||||||
|
|
||||||
|
self.music_creator = creator
|
||||||
|
"""曲目制作者"""
|
||||||
|
|
||||||
|
self.music_original_author = original_author
|
||||||
|
"""乐曲原作者"""
|
||||||
|
|
||||||
|
self.music_description = description
|
||||||
|
"""简介"""
|
||||||
|
|
||||||
|
self.music_credits = credits
|
||||||
|
"""版权信息"""
|
||||||
|
|
||||||
|
self.extra_info = extra_information if extra_information else {}
|
||||||
|
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def track_amount(self) -> int:
|
||||||
|
"""音轨数"""
|
||||||
|
return len(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def music_tracks(self) -> Iterator[SingleTrack]:
|
||||||
|
"""音轨列表,不包含被禁用的音轨"""
|
||||||
|
return (track for track in self if track.is_enabled)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def yield_from_tracks(
|
||||||
|
tracks: Sequence[Iterator[T]],
|
||||||
|
sort_key: Callable[[T], Any],
|
||||||
|
is_subseq_sorted: bool = True,
|
||||||
|
) -> Iterator[T]:
|
||||||
|
"""从任意迭代器列表迭代符合顺序的元素
|
||||||
|
(惰性多路归并多个迭代器,按 sort_key 排序)
|
||||||
|
|
||||||
|
参数
|
||||||
|
----
|
||||||
|
tracks: Sequence[Iterator[T]]
|
||||||
|
迭代器列表
|
||||||
|
sort_key: Callable[[T], Any]
|
||||||
|
接受 T 元素,返回可比较的键
|
||||||
|
is_subseq_sorted: bool = True
|
||||||
|
子序列是否已排序
|
||||||
|
|
||||||
|
迭代
|
||||||
|
----
|
||||||
|
归并后的每个元素,按 sort_key 升序
|
||||||
|
"""
|
||||||
|
if is_subseq_sorted:
|
||||||
|
# 必须这样处理,不能 return 这个 merge,测试过了
|
||||||
|
yield from heapq.merge(*tracks, key=sort_key)
|
||||||
|
else:
|
||||||
|
# 初始化堆
|
||||||
|
heap_pool: List[Tuple[Any, int, T]] = []
|
||||||
|
for _index, _track in enumerate(tracks):
|
||||||
|
try:
|
||||||
|
item = next(_track)
|
||||||
|
heapq.heappush(heap_pool, (sort_key(item), _index, item))
|
||||||
|
except StopIteration:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 归并主循环
|
||||||
|
while heap_pool:
|
||||||
|
_key, _index, item = heapq.heappop(heap_pool)
|
||||||
|
yield item
|
||||||
|
try:
|
||||||
|
next_item = next(tracks[_index])
|
||||||
|
heapq.heappush(heap_pool, (sort_key(next_item), _index, next_item))
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
# NEVER REACH:
|
||||||
|
# pool: List[Tuple[str, T]] = []
|
||||||
|
# remove_track: List[str] = []
|
||||||
|
# for _name, _track in tracks.items():
|
||||||
|
# try:
|
||||||
|
# pool.append((_name, next(_track)))
|
||||||
|
# except StopIteration:
|
||||||
|
# remove_track.append(_name)
|
||||||
|
# for _x in remove_track:
|
||||||
|
# tracks.pop(_x)
|
||||||
|
# del remove_track
|
||||||
|
# while tracks and pool:
|
||||||
|
# yield (_x := min(pool, key=sort_key))[1]
|
||||||
|
# try:
|
||||||
|
# pool.append((_x[0], next(tracks[_x[0]])))
|
||||||
|
# except StopIteration:
|
||||||
|
# tracks.pop(_x[0])
|
||||||
|
# pool.sort(key=sort_key)
|
||||||
|
# for _remain in pool:
|
||||||
|
# yield _remain[1]
|
||||||
|
|
||||||
|
def get_tracked_notes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Generator[Iterator[SingleNote], Any, None]:
|
||||||
|
"""获取指定时间段的各个音轨的音符数据"""
|
||||||
|
return (track.get_notes(start_time, end_time) for track in self.music_tracks)
|
||||||
|
|
||||||
|
def get_tracked_minenotes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Generator[Iterator[MineNote], Any, None]:
|
||||||
|
"""获取指定时间段的各个音轨的,供我的世界播放的音符数据类"""
|
||||||
|
return (
|
||||||
|
track.get_minenotes(start_time, end_time) for track in self.music_tracks
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_notes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Iterator[SingleNote]:
|
||||||
|
"""获取指定时间段的所有音符数据,按照时间顺序"""
|
||||||
|
if self.track_amount == 0:
|
||||||
|
return iter(())
|
||||||
|
return self.yield_from_tracks(
|
||||||
|
[track.get_notes(start_time, end_time) for track in self.music_tracks],
|
||||||
|
sort_key=lambda x: x.start_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_minenotes(
|
||||||
|
self, start_time: float, end_time: float = inf
|
||||||
|
) -> Iterator[MineNote]:
|
||||||
|
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
||||||
|
if self.track_amount == 0:
|
||||||
|
return iter(())
|
||||||
|
return self.yield_from_tracks(
|
||||||
|
[track.get_minenotes(start_time, end_time) for track in self.music_tracks],
|
||||||
|
sort_key=lambda x: x.start_tick,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
|
"""设置附加信息"""
|
||||||
|
if isinstance(key, str):
|
||||||
|
self.extra_info[key] = value
|
||||||
|
elif (
|
||||||
|
isinstance(key, Sequence)
|
||||||
|
and isinstance(value, Sequence)
|
||||||
|
and (k := len(key)) == len(value)
|
||||||
|
):
|
||||||
|
for i in range(k):
|
||||||
|
self.extra_info[key[i]] = value[i]
|
||||||
|
else:
|
||||||
|
# 提供简单报错就行了,如果放一堆 if 语句,降低处理速度
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"参数类型错误;键:`{}` 值:`{}`".format(key, value)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_info(self, key: str, default: Any = None) -> Any:
|
||||||
|
"""获取附加信息"""
|
||||||
|
return self.extra_info.get(key, default)
|
||||||
272
Musicreater/exceptions.py
Normal file
272
Musicreater/exceptions.py
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 用到的一些报错类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles & YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# “
|
||||||
|
# There are planty of "exception"s in this library
|
||||||
|
# for I know I will always go with my heart.
|
||||||
|
# ” —— Cyberdevil by resnah
|
||||||
|
|
||||||
|
|
||||||
|
class MusicreaterBaseException(Exception):
|
||||||
|
"""音·创 v3 的所有错误均继承于此"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音·创 的所有错误均继承于此"""
|
||||||
|
super().__init__("[音·创] - ", *args)
|
||||||
|
|
||||||
|
def meow(self):
|
||||||
|
for i in self.args:
|
||||||
|
print(i + "喵~", end=":")
|
||||||
|
|
||||||
|
def crash_it(self):
|
||||||
|
raise self
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "".join(self.args)
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================
|
||||||
|
# NOTE
|
||||||
|
# 面对用户时候爆出去的我们认为这就是“外部错误”
|
||||||
|
# 如果是在程序内部数据传输等情况下出现的就是“内部错误”
|
||||||
|
# 例如,无法读取文件,这就是一个外部错误
|
||||||
|
# 某个参数的数据类型错误,这就是内部错误
|
||||||
|
# =====================================
|
||||||
|
|
||||||
|
|
||||||
|
class MusicreaterInnerlyError(MusicreaterBaseException):
|
||||||
|
"""内部错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""内部错误(面向开发者的报错信息)"""
|
||||||
|
super().__init__("内部错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicreaterOuterlyError(MusicreaterBaseException):
|
||||||
|
"""外部错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""外部错误(面向用户的报错信息)"""
|
||||||
|
super().__init__("外部错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class InnerlyParameterError(MusicreaterInnerlyError):
|
||||||
|
"""内部传参错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""参数错误"""
|
||||||
|
super().__init__("传参错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterTypeError(InnerlyParameterError, TypeError):
|
||||||
|
"""参数类型错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""参数类型错误"""
|
||||||
|
super().__init__("参数类型错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterValueError(InnerlyParameterError, ValueError):
|
||||||
|
"""参数值存在错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""参数其值存在错误"""
|
||||||
|
super().__init__("参数数值错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginNotSpecifiedError(InnerlyParameterError, LookupError):
|
||||||
|
"""未指定插件"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""未指定插件"""
|
||||||
|
super().__init__("未指定插件:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class OuterlyParameterError(MusicreaterOuterlyError):
|
||||||
|
"""外部参数错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""参数错误"""
|
||||||
|
super().__init__("参数错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError):
|
||||||
|
"""以 0 作为播放速度的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""以 0 作为播放速度的错误"""
|
||||||
|
super().__init__("播放速度为零:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class IllegalMinimumVolumeError(OuterlyParameterError, ValueError):
|
||||||
|
"""最小播放音量有误的错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""最小播放音量错误"""
|
||||||
|
super().__init__("最小播放音量超出范围:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class FileFormatNotSupportedError(MusicreaterOuterlyError):
|
||||||
|
"""不支持的文件格式"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""文件格式不受支持"""
|
||||||
|
super().__init__("不支持的文件格式:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteBinaryDecodeError(MusicreaterOuterlyError):
|
||||||
|
"""音乐存储二进制数据解码错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音乐存储二进制数据无法正确解码"""
|
||||||
|
super().__init__("解码音乐存储二进制数据时出现问题 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleNoteDecodeError(NoteBinaryDecodeError):
|
||||||
|
"""单个音符的二进制数据解码错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""单个音符的二进制数据无法正确解码"""
|
||||||
|
super().__init__("音符解码出错:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteBinaryFileTypeError(NoteBinaryDecodeError):
|
||||||
|
"""音乐存储二进制数据的文件类型错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""无法识别音乐存储文件的类型"""
|
||||||
|
super().__init__("无法识别音乐存储文件对应的类型:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class NoteBinaryFileVerificationFailed(NoteBinaryDecodeError):
|
||||||
|
"""音乐存储二进制数据校验失败"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""音乐存储文件与其校验值不一致"""
|
||||||
|
super().__init__("音乐存储文件校验失败:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginDefineError(MusicreaterInnerlyError):
|
||||||
|
"""插件定义错误(内部相关)"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件本身存在错误"""
|
||||||
|
super().__init__("插件内部错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginInstanceNotFoundError(PluginDefineError, LookupError):
|
||||||
|
"""插件实例未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件实例未找到"""
|
||||||
|
super().__init__("插件实例未找到:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginAttributeNotFoundError(PluginDefineError, AttributeError):
|
||||||
|
"""插件属性定义错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件属性定义错误"""
|
||||||
|
super().__init__("插件类的必要属性不存在:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoError(PluginDefineError):
|
||||||
|
"""插件元信息定义错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义错误"""
|
||||||
|
super().__init__("插件元信息定义错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoTypeError(PluginMetainfoError, TypeError):
|
||||||
|
"""插件元信息定义类型错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义类型错误"""
|
||||||
|
super().__init__("插件元信息类型错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoValueError(PluginMetainfoError, ValueError):
|
||||||
|
"""插件元信息定义值错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义值错误"""
|
||||||
|
super().__init__("插件元信息数值错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginMetainfoNotFoundError(PluginMetainfoError, PluginAttributeNotFoundError):
|
||||||
|
"""插件元信息定义缺少错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件元信息定义缺少错误"""
|
||||||
|
super().__init__("插件元信息未定义:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginLoadError(MusicreaterOuterlyError):
|
||||||
|
"""插件加载错误(外部相关)"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件加载错误"""
|
||||||
|
super().__init__("插件加载错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginDependencyNotFound(PluginLoadError):
|
||||||
|
"""插件依赖未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("未找到所需的插件依赖:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginNotFoundError(PluginLoadError):
|
||||||
|
"""插件未找到"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件未找到"""
|
||||||
|
super().__init__("无法找到插件:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginRegisteredError(PluginLoadError):
|
||||||
|
"""插件重复注册"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件已被注册注册"""
|
||||||
|
super().__init__("插件重复注册:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginConfigRelatedError(MusicreaterOuterlyError):
|
||||||
|
"""插件配置相关错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""插件配置相关错误"""
|
||||||
|
super().__init__("插件配置相关错误 - ", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginConfigLoadError(PluginLoadError, PluginConfigRelatedError):
|
||||||
|
"""插件配置加载错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""配置文件无法加载"""
|
||||||
|
super().__init__("插件配置文件加载错误:", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginConfigDumpError(PluginConfigRelatedError):
|
||||||
|
"""插件配置保存错误"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""配置文件无法保存"""
|
||||||
|
super().__init__("插件配置文件保存错误:", *args)
|
||||||
291
Musicreater/main.py
Normal file
291
Musicreater/main.py
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创
|
||||||
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
|
||||||
|
Musicreater (音·创)
|
||||||
|
A cost free and open-source library for handling with **Minecraft** digital music.
|
||||||
|
|
||||||
|
版权所有 © 2026 睿乐组织
|
||||||
|
Copyright © 2026 TriM-Organization
|
||||||
|
|
||||||
|
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||||
|
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
|
||||||
|
|
||||||
|
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||||
|
任何人皆可从以下地址获得本协议副本:
|
||||||
|
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
|
||||||
|
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
|
||||||
|
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 音·创 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
# BUG退散!BUG退散!
|
||||||
|
# 异常与错误作乱之时
|
||||||
|
# 二六字组!万国码合!二六字组!万国码合!
|
||||||
|
# 赶快呼叫 程序员!Let's Go!
|
||||||
|
|
||||||
|
# BUG退散!BUG退散!
|
||||||
|
# 異常、誤りが、困った時は
|
||||||
|
# パラメータ メソッド!パラメータ メソッド!
|
||||||
|
# 助けてもらおう、開発者!レッツゴー!
|
||||||
|
|
||||||
|
# Bug retreat! Bug retreat!
|
||||||
|
# Exceptions and errors are causing chaos
|
||||||
|
# Words combine! Codes unite!
|
||||||
|
# Hurry to call the Programmer! Let's Go!
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
from difflib import get_close_matches
|
||||||
|
from typing import Dict, Generator, List, Optional, Tuple, Union, Mapping, Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
from .data import SingleMusic, SingleTrack
|
||||||
|
from .exceptions import (
|
||||||
|
FileFormatNotSupportedError,
|
||||||
|
PluginNotSpecifiedError,
|
||||||
|
PluginNotFoundError,
|
||||||
|
)
|
||||||
|
from ._plugin_abc import TopPluginBase
|
||||||
|
from .plugins import (
|
||||||
|
_global_plugin_registry,
|
||||||
|
PluginRegistry,
|
||||||
|
PluginConfig,
|
||||||
|
PluginTypes,
|
||||||
|
load_plugin_module,
|
||||||
|
T_IOPlugin,
|
||||||
|
T_Plugin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MusiCreater:
|
||||||
|
"""
|
||||||
|
音·创 v3 主要控制类
|
||||||
|
另:“创建者”一词的英文应该是“Creator”
|
||||||
|
"""
|
||||||
|
|
||||||
|
__plugin_registry: PluginRegistry
|
||||||
|
"""插件注册表实例"""
|
||||||
|
_plugin_cache: Dict[str, TopPluginBase]
|
||||||
|
"""插件缓存字典,插件id为键、插件实例为值"""
|
||||||
|
music: SingleMusic
|
||||||
|
"""当前曲目实例"""
|
||||||
|
|
||||||
|
def __init__(self, whole_music: SingleMusic) -> None:
|
||||||
|
global _global_plugin_registry
|
||||||
|
|
||||||
|
self.__plugin_registry = _global_plugin_registry
|
||||||
|
|
||||||
|
self._plugin_cache = {}
|
||||||
|
self._cache_all_plugins()
|
||||||
|
|
||||||
|
self.music = whole_music
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_plugin_within_iousage(
|
||||||
|
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
||||||
|
fpath: Path,
|
||||||
|
plg_regdict: Mapping[str, T_IOPlugin],
|
||||||
|
plg_id: Optional[str],
|
||||||
|
) -> T_IOPlugin:
|
||||||
|
"""这个函数是用于从指定的注册表项里面调取实例的,仅供下面这几个函数使用"""
|
||||||
|
|
||||||
|
__plugin: Optional[T_IOPlugin] = None
|
||||||
|
if plg_id:
|
||||||
|
__plugin = plg_regdict.get(plg_id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for __plg in get_func(fpath):
|
||||||
|
if __plugin:
|
||||||
|
raise PluginNotSpecifiedError(
|
||||||
|
"文件类型`{}`可被多个插件处理,请在调用函数的参数中指定插件名称".format(
|
||||||
|
fpath.suffix.upper()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
__plugin = __plg
|
||||||
|
if __plugin:
|
||||||
|
return __plugin
|
||||||
|
else:
|
||||||
|
if plg_id:
|
||||||
|
raise PluginNotFoundError(
|
||||||
|
"无法找到惟一识别码为`{}`、处理`{}`格式的插件".format(
|
||||||
|
plg_id, fpath.suffix.upper()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise FileFormatNotSupportedError(
|
||||||
|
"无法找到处理`{}`格式的插件".format(fpath.suffix.upper())
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def import_music(
|
||||||
|
cls,
|
||||||
|
file_path: Path,
|
||||||
|
plugin_id: Optional[str] = None,
|
||||||
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
|
) -> "MusiCreater":
|
||||||
|
return cls(
|
||||||
|
whole_music=cls._get_plugin_within_iousage(
|
||||||
|
_global_plugin_registry.get_music_input_plugin_by_format,
|
||||||
|
file_path,
|
||||||
|
_global_plugin_registry._music_input_plugins,
|
||||||
|
plugin_id,
|
||||||
|
).load(file_path, plugin_config)
|
||||||
|
)
|
||||||
|
|
||||||
|
def import_track(
|
||||||
|
self,
|
||||||
|
file_path: Path,
|
||||||
|
plugin_id: Optional[str] = None,
|
||||||
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
|
) -> SingleTrack:
|
||||||
|
self.music.append(
|
||||||
|
self._get_plugin_within_iousage(
|
||||||
|
self.__plugin_registry.get_track_input_plugin_by_format,
|
||||||
|
file_path,
|
||||||
|
self.__plugin_registry._track_input_plugins,
|
||||||
|
plugin_id,
|
||||||
|
).load(file_path, plugin_config)
|
||||||
|
)
|
||||||
|
return self.music[-1]
|
||||||
|
|
||||||
|
def export_music(
|
||||||
|
self,
|
||||||
|
file_path: Path,
|
||||||
|
plugin_id: Optional[str] = None,
|
||||||
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
|
) -> None:
|
||||||
|
return self._get_plugin_within_iousage(
|
||||||
|
self.__plugin_registry.get_music_output_plugin_by_format,
|
||||||
|
file_path,
|
||||||
|
self.__plugin_registry._music_output_plugins,
|
||||||
|
plugin_id,
|
||||||
|
).dump(self.music, file_path, plugin_config)
|
||||||
|
|
||||||
|
def export_track(
|
||||||
|
self,
|
||||||
|
track_index: int,
|
||||||
|
file_path: Path,
|
||||||
|
plugin_id: Optional[str] = None,
|
||||||
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
|
) -> None:
|
||||||
|
return self._get_plugin_within_iousage(
|
||||||
|
self.__plugin_registry.get_track_output_plugin_by_format,
|
||||||
|
file_path,
|
||||||
|
self.__plugin_registry._track_output_plugins,
|
||||||
|
plugin_id,
|
||||||
|
).dump(self.music[track_index], file_path, plugin_config)
|
||||||
|
|
||||||
|
def perform_operation_on_music(
|
||||||
|
self, plugin_id: str, plugin_config: Optional[PluginConfig] = None
|
||||||
|
):
|
||||||
|
if __plugin := self.__plugin_registry._music_operate_plugins.get(plugin_id):
|
||||||
|
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||||
|
self.music = __plugin.process(self.music, plugin_config)
|
||||||
|
else:
|
||||||
|
raise PluginNotFoundError(
|
||||||
|
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def perform_operation_on_track(
|
||||||
|
self,
|
||||||
|
track_index: int,
|
||||||
|
plugin_id: str,
|
||||||
|
plugin_config: Optional[PluginConfig] = None,
|
||||||
|
):
|
||||||
|
if __plugin := self.__plugin_registry._track_operate_plugins.get(plugin_id):
|
||||||
|
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||||
|
self.music[track_index] = __plugin.process(
|
||||||
|
self.music[track_index], plugin_config
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise PluginNotFoundError(
|
||||||
|
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _camel_to_snake(name: str) -> str:
|
||||||
|
"""
|
||||||
|
将驼峰命名转换为蛇形命名
|
||||||
|
CyberAngel -> cyber_angel
|
||||||
|
"""
|
||||||
|
return re.sub(
|
||||||
|
"([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||||
|
).lower()
|
||||||
|
|
||||||
|
def _parse_plugin_id(self, attr_name: str) -> Optional[str]:
|
||||||
|
"""解析属性名称为插件惟一识别码"""
|
||||||
|
|
||||||
|
# 尝试去除 _plugin 后缀
|
||||||
|
if attr_name.endswith("_plugin"):
|
||||||
|
candidate_name = attr_name[:-7] # 去除 "_plugin"
|
||||||
|
if candidate_name in self._plugin_cache:
|
||||||
|
return candidate_name
|
||||||
|
|
||||||
|
# 尝试转换为 snake_case(如果插件名是驼峰式)
|
||||||
|
snake_case_name = self._camel_to_snake(attr_name)
|
||||||
|
|
||||||
|
if snake_case_name != attr_name: # 避免重复转换
|
||||||
|
if snake_case_name in self._plugin_cache: # 尝试转换后的插件名
|
||||||
|
return snake_case_name
|
||||||
|
else:
|
||||||
|
return self._parse_plugin_id(snake_case_name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_closest_plugin_id(self, requested_id: str) -> Optional[str]:
|
||||||
|
"""找到最接近的插件识别码(用于更好的错误提示)"""
|
||||||
|
|
||||||
|
matches = get_close_matches(
|
||||||
|
requested_id, self._plugin_cache.keys(), n=1, cutoff=0.6
|
||||||
|
)
|
||||||
|
return matches[0] if matches else None
|
||||||
|
|
||||||
|
def get_plugin_by_id(self, plg_id: str):
|
||||||
|
"""获取插件实例,并缓存起来,提高性能"""
|
||||||
|
if plg_id.startswith("_"):
|
||||||
|
raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(plg_id))
|
||||||
|
|
||||||
|
if plg_id in self._plugin_cache:
|
||||||
|
return self._plugin_cache[plg_id]
|
||||||
|
else:
|
||||||
|
plugin_name = self._parse_plugin_id(plg_id)
|
||||||
|
if plugin_name:
|
||||||
|
self._plugin_cache[plg_id] = self._plugin_cache[plugin_name]
|
||||||
|
return self._plugin_cache[plg_id]
|
||||||
|
|
||||||
|
raise AttributeError(
|
||||||
|
"插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id)
|
||||||
|
+ (
|
||||||
|
";或者阁下可能想要使用的是`{}`插件?".format(closest)
|
||||||
|
if (closest := self._get_closest_plugin_id(plg_id))
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __getattr__(self, plugin_id: str):
|
||||||
|
"""动态属性访问,允许直接 实例.插件名 来访问插件"""
|
||||||
|
return self.get_plugin_by_id(plugin_id)
|
||||||
|
|
||||||
|
def _cache_all_plugins(self):
|
||||||
|
"""获取所有已注册插件的名称"""
|
||||||
|
for __plugin_type, __plugins_map in self.__plugin_registry:
|
||||||
|
for __plugin_id, __plugin in __plugins_map.items():
|
||||||
|
if __plugin_id in self._plugin_cache: # 避免重复缓存
|
||||||
|
if (
|
||||||
|
__plugin.metainfo.version
|
||||||
|
<= self._plugin_cache[__plugin_id].metainfo.version
|
||||||
|
): # 优先使用版本号最大的插件
|
||||||
|
continue
|
||||||
|
self._plugin_cache[__plugin_id] = __plugin
|
||||||
581
Musicreater/paramcurve.py
Normal file
581
Musicreater/paramcurve.py
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 内部数据使用的参数曲线
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
# WARNING 本文件所含之功能未经完整测试
|
||||||
|
# 鉴于白谭若佬给出的建议:本功能应是处于低优先级开发的
|
||||||
|
# 因此暂时用处不大,可以稍微放一会再进行开发
|
||||||
|
# 目前用人工智能生成了部分代码,只经过简单的测试
|
||||||
|
# 可以等伶伦工作站开发出来后再进行完整的测试
|
||||||
|
|
||||||
|
|
||||||
|
from math import ceil
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, Any, List, Tuple, Callable
|
||||||
|
from enum import Enum
|
||||||
|
import bisect
|
||||||
|
|
||||||
|
|
||||||
|
def _evaluate_bezier_segment(
|
||||||
|
t0: float,
|
||||||
|
v0: float,
|
||||||
|
t1: float,
|
||||||
|
v1: float,
|
||||||
|
out_tangent: Optional[Tuple[float, float]],
|
||||||
|
in_tangent: Optional[Tuple[float, float]],
|
||||||
|
u: float,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算贝塞尔区间 [t0, t1] 在归一化参数 u ∈ [0,1] 处的 y 值。
|
||||||
|
|
||||||
|
控制点:
|
||||||
|
P0 = (t0, v0)
|
||||||
|
P1 = (t0 + out_dt, v0 + out_dv)
|
||||||
|
P2 = (t1 - in_dt, v1 - in_dv) ← 注意:in_tangent 是相对于 t1 的偏移
|
||||||
|
P3 = (t1, v1)
|
||||||
|
"""
|
||||||
|
# 默认控制点:退化为线性
|
||||||
|
p0 = (t0, v0)
|
||||||
|
p3 = (t1, v1)
|
||||||
|
|
||||||
|
if out_tangent is not None:
|
||||||
|
p1 = (t0 + out_tangent[0], v0 + out_tangent[1])
|
||||||
|
else:
|
||||||
|
p1 = p0 # 无出手柄 → 与起点重合
|
||||||
|
|
||||||
|
if in_tangent is not None:
|
||||||
|
p2 = (t1 - in_tangent[0], v1 - in_tangent[1])
|
||||||
|
else:
|
||||||
|
p2 = p3 # 无入手柄 → 与终点重合
|
||||||
|
|
||||||
|
# 三次贝塞尔 y(t)
|
||||||
|
mt = 1.0 - u
|
||||||
|
return mt**3 * p0[1] + 3 * mt**2 * u * p1[1] + 3 * mt * u**2 * p2[1] + u**3 * p3[1]
|
||||||
|
|
||||||
|
|
||||||
|
class InterpolationMethod:
|
||||||
|
"""
|
||||||
|
预定义的标准化插值函数集合。所有函数接受归一化输入 u ∈ [0,1],返回 v ∈ [0,1]。
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def linear(u: float) -> float:
|
||||||
|
"""
|
||||||
|
线性插值。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重,范围 [0, 1]。
|
||||||
|
"""
|
||||||
|
return u
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ease_in_quad(u: float) -> float:
|
||||||
|
"""
|
||||||
|
二次缓入(慢进快出)。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重。
|
||||||
|
"""
|
||||||
|
return u * u
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ease_out_quad(u: float) -> float:
|
||||||
|
"""
|
||||||
|
二次缓出(快进慢出)。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重。
|
||||||
|
"""
|
||||||
|
return 1 - (1 - u) ** 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ease_in_out_quad(u: float) -> float:
|
||||||
|
"""
|
||||||
|
二次缓入缓出。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间,范围 [0, 1]。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值权重。
|
||||||
|
"""
|
||||||
|
if u < 0.5:
|
||||||
|
return 2 * u * u
|
||||||
|
else:
|
||||||
|
return 1 - pow(-2 * u + 2, 2) / 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hold(u: float) -> float:
|
||||||
|
"""
|
||||||
|
阶梯保持模式占位函数。实际插值逻辑在 ParamCurve.value_at 中特殊处理。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
u : float
|
||||||
|
归一化时间(忽略)。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
无意义,仅作标识。
|
||||||
|
"""
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Keyframe:
|
||||||
|
"""
|
||||||
|
参数曲线上的一个关键帧,支持完整的入/出切线控制。
|
||||||
|
|
||||||
|
插值优先级:
|
||||||
|
1. 若 use_bezier=True → 使用贝塞尔模式(需 in_tangent / out_tangent)
|
||||||
|
2. 否则 → 使用 out_interp 函数(in_interp 被忽略)
|
||||||
|
"""
|
||||||
|
|
||||||
|
time: float
|
||||||
|
value: float
|
||||||
|
|
||||||
|
# 函数插值模式
|
||||||
|
out_interp: Optional[Callable[[float], float]] = None
|
||||||
|
|
||||||
|
# 贝塞尔模式
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = (
|
||||||
|
None # (dt, dv) ← 相对于自身(负 dt 表示左侧)
|
||||||
|
)
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = (
|
||||||
|
None # (dt, dv) → 相对于自身(正 dt 表示右侧)
|
||||||
|
)
|
||||||
|
use_bezier: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class BoundaryBehaviour(str, Enum):
|
||||||
|
"""
|
||||||
|
边界行为枚举。
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONSTANT = "constant"
|
||||||
|
"""返回默认基线值"""
|
||||||
|
HOLD = "hold"
|
||||||
|
"""保持首/尾关键帧的值"""
|
||||||
|
|
||||||
|
|
||||||
|
class ParamCurve:
|
||||||
|
"""
|
||||||
|
参数曲线类
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
支持动态节点编辑
|
||||||
|
用户通过添加/修改关键帧(时间-值对)来定义曲线,类自动在相邻关键帧之间生成插值段。
|
||||||
|
支持多种插值模式:线性('linear')、平滑缓动('smooth')、保持('hold')或自定义函数。
|
||||||
|
"""
|
||||||
|
|
||||||
|
base_line: float = 0.0
|
||||||
|
"""基线/默认值"""
|
||||||
|
|
||||||
|
base_interpolation_function: Callable[[float], float]
|
||||||
|
"""默认(未指定区间时的)关键帧插值模式"""
|
||||||
|
|
||||||
|
boundary_behaviour: BoundaryBehaviour
|
||||||
|
"""边界行为,控制参数曲线在已定义的范围外的返回值"""
|
||||||
|
|
||||||
|
_keys: List[Keyframe]
|
||||||
|
"""关键帧列表"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_value: float = 0.0,
|
||||||
|
default_interpolation_function: Callable[[float], float] = InterpolationMethod.linear,
|
||||||
|
boundary_mode: BoundaryBehaviour = BoundaryBehaviour.CONSTANT,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化参数曲线。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
base_value : float
|
||||||
|
边界外默认值(当 boundary_mode 为 BoundaryBehaviour.CONSTANT 时使用)。
|
||||||
|
default_interpolation_function : FittingFunctionType
|
||||||
|
新关键帧的默认 out_interp。
|
||||||
|
boundary_mode : BoundaryBehaviour
|
||||||
|
范围外行为:
|
||||||
|
- BoundaryBehaviour.CONSTANT: 返回 base_value
|
||||||
|
- BoundaryBehaviour.HOLD: 保持首/尾关键帧值
|
||||||
|
"""
|
||||||
|
self.base_line = base_value
|
||||||
|
self.base_interpolation_function = default_interpolation_function
|
||||||
|
self.boundary_behaviour = boundary_mode
|
||||||
|
|
||||||
|
self._keys: List[Keyframe] = []
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._keys) or (self.base_line != 0)
|
||||||
|
|
||||||
|
def add_key(
|
||||||
|
self,
|
||||||
|
time: float,
|
||||||
|
value: float,
|
||||||
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
use_bezier: bool = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
添加或更新关键帧。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
time : float
|
||||||
|
关键帧时间。
|
||||||
|
value : float
|
||||||
|
参数值。
|
||||||
|
out_interp : Optional[Callable]
|
||||||
|
出插值函数(若 use_bezier=False)。
|
||||||
|
in_tangent : Optional[Tuple[float, float]]
|
||||||
|
入切线偏移 (dt, dv)。dt 通常为负(表示左侧),但存储为绝对偏移。
|
||||||
|
out_tangent : Optional[Tuple[float, float]]
|
||||||
|
出切线偏移 (dt, dv)。dt 通常为正。
|
||||||
|
use_bezier : bool
|
||||||
|
是否使用贝塞尔插值。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
若时间已存在,更新该关键帧的所有属性。
|
||||||
|
"""
|
||||||
|
interp = (
|
||||||
|
out_interp if out_interp is not None else self.base_interpolation_function
|
||||||
|
)
|
||||||
|
new_key = Keyframe(time, value, interp, in_tangent, out_tangent, use_bezier)
|
||||||
|
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
self._keys[idx] = new_key
|
||||||
|
else:
|
||||||
|
self._keys.insert(idx, new_key)
|
||||||
|
|
||||||
|
def remove_key(self, time: float):
|
||||||
|
"""
|
||||||
|
移除指定时间的关键帧。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
time : float
|
||||||
|
要移除的关键帧时间。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
del self._keys[idx]
|
||||||
|
|
||||||
|
def update_key_value(self, time: float, new_value: float):
|
||||||
|
"""更新关键帧值,保留其他属性。"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
self._keys[idx] = Keyframe(
|
||||||
|
time, new_value, k.out_interp, k.in_tangent, k.out_tangent, k.use_bezier
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_key_interp(
|
||||||
|
self,
|
||||||
|
time: float,
|
||||||
|
out_interp: Optional[Callable[[float], float]] = None,
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
use_bezier: bool = False,
|
||||||
|
):
|
||||||
|
"""更新关键帧的插值属性。"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
new_value = k.value
|
||||||
|
interp = out_interp if out_interp is not None else k.out_interp
|
||||||
|
self._keys[idx] = Keyframe(
|
||||||
|
time, new_value, interp, in_tangent, out_tangent, use_bezier
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_key_tangents(
|
||||||
|
self,
|
||||||
|
time: float,
|
||||||
|
in_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
out_tangent: Optional[Tuple[float, float]] = None,
|
||||||
|
use_bezier: bool = True,
|
||||||
|
):
|
||||||
|
"""单独设置关键帧的切线,不改变值。"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
self._keys[idx] = Keyframe(
|
||||||
|
time,
|
||||||
|
k.value,
|
||||||
|
out_interp=k.out_interp,
|
||||||
|
in_tangent=in_tangent,
|
||||||
|
out_tangent=out_tangent,
|
||||||
|
use_bezier=use_bezier,
|
||||||
|
)
|
||||||
|
|
||||||
|
def make_key_smooth(self, time: float):
|
||||||
|
"""
|
||||||
|
将关键帧设为“平滑”模式(自动对称切线,并设为贝塞尔模式)。
|
||||||
|
切线长度基于相邻关键帧的时间和值差。
|
||||||
|
"""
|
||||||
|
idx = bisect.bisect_left(self._keys, time, key=lambda k: k.time)
|
||||||
|
if idx < len(self._keys) and self._keys[idx].time == time:
|
||||||
|
k = self._keys[idx]
|
||||||
|
prev_k = self._keys[idx - 1] if idx > 0 else None
|
||||||
|
next_k = self._keys[idx + 1] if idx + 1 < len(self._keys) else None
|
||||||
|
|
||||||
|
# 默认切线长度:时间差的 1/3,值差按比例
|
||||||
|
dt_in = dt_out = 0.1
|
||||||
|
dv_in = dv_out = 0.0
|
||||||
|
|
||||||
|
if prev_k and next_k:
|
||||||
|
dt_total = next_k.time - prev_k.time
|
||||||
|
dv_total = next_k.value - prev_k.value
|
||||||
|
dt_in = dt_out = dt_total / 3.0
|
||||||
|
dv_in = dv_out = dv_total / 3.0
|
||||||
|
elif prev_k:
|
||||||
|
dt_out = (k.time - prev_k.time) / 2.0
|
||||||
|
dv_out = (k.value - prev_k.value) / 2.0
|
||||||
|
dt_in = dt_out
|
||||||
|
dv_in = dv_out
|
||||||
|
elif next_k:
|
||||||
|
dt_in = (next_k.time - k.time) / 2.0
|
||||||
|
dv_in = (next_k.value - k.value) / 2.0
|
||||||
|
dt_out = dt_in
|
||||||
|
dv_out = dv_in
|
||||||
|
|
||||||
|
self.set_key_tangents(
|
||||||
|
time,
|
||||||
|
in_tangent=(-dt_in, -dv_in), # in_tangent 存储为偏移,使用时做减法
|
||||||
|
out_tangent=(dt_out, dv_out),
|
||||||
|
use_bezier=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_boundary_value(self, t: float) -> float:
|
||||||
|
"""根据 boundary_mode 获取范围外的值。"""
|
||||||
|
if not self._keys:
|
||||||
|
return self.base_line
|
||||||
|
if self.boundary_behaviour == BoundaryBehaviour.CONSTANT:
|
||||||
|
return self.base_line
|
||||||
|
elif self.boundary_behaviour == BoundaryBehaviour.HOLD:
|
||||||
|
if t < self._keys[0].time:
|
||||||
|
return self._keys[0].value
|
||||||
|
else:
|
||||||
|
return self._keys[-1].value
|
||||||
|
else: # 可能会有别的模式吗?
|
||||||
|
return self.base_line
|
||||||
|
|
||||||
|
def value_at(self, t: float) -> float:
|
||||||
|
"""
|
||||||
|
计算时间 t 处的曲线值。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
t : float
|
||||||
|
查询时间。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
插值结果。
|
||||||
|
"""
|
||||||
|
keys = self._keys
|
||||||
|
if not keys:
|
||||||
|
return self._get_boundary_value(t)
|
||||||
|
|
||||||
|
if t < keys[0].time or t > keys[-1].time:
|
||||||
|
return self._get_boundary_value(t)
|
||||||
|
|
||||||
|
times = [k.time for k in keys]
|
||||||
|
idx = bisect.bisect_right(times, t) - 1
|
||||||
|
|
||||||
|
if idx < 0:
|
||||||
|
return self._get_boundary_value(t)
|
||||||
|
if idx >= len(keys) - 1:
|
||||||
|
return keys[-1].value
|
||||||
|
|
||||||
|
k0 = keys[idx]
|
||||||
|
k1 = keys[idx + 1]
|
||||||
|
|
||||||
|
if k0.time == k1.time:
|
||||||
|
return k0.value
|
||||||
|
if k0.time == t:
|
||||||
|
return k0.value
|
||||||
|
if k1.time == t:
|
||||||
|
return k1.value
|
||||||
|
|
||||||
|
t0, v0 = k0.time, k0.value
|
||||||
|
t1, v1 = k1.time, k1.value
|
||||||
|
u = (t - t0) / (t1 - t0)
|
||||||
|
u = max(0.0, min(1.0, u))
|
||||||
|
|
||||||
|
# 贝塞尔模式(高优先级)
|
||||||
|
if k0.use_bezier or k1.use_bezier:
|
||||||
|
return _evaluate_bezier_segment(
|
||||||
|
t0,
|
||||||
|
v0,
|
||||||
|
t1,
|
||||||
|
v1,
|
||||||
|
out_tangent=k0.out_tangent,
|
||||||
|
in_tangent=k1.in_tangent, # ← 关键:使用下一帧的 in_tangent!
|
||||||
|
u=u,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 函数插值模式,优先处理阶梯保持模式
|
||||||
|
elif k0.out_interp is InterpolationMethod.hold:
|
||||||
|
return v0
|
||||||
|
|
||||||
|
interp_func = k0.out_interp or self.base_interpolation_function
|
||||||
|
v_norm = interp_func(u)
|
||||||
|
return v0 + v_norm * (v1 - v0)
|
||||||
|
|
||||||
|
def __call__(self, t: float) -> float:
|
||||||
|
return self.value_at(t)
|
||||||
|
|
||||||
|
def get_all_keys(self) -> List[Tuple[float, float]]:
|
||||||
|
"""返回 (time, value) 列表。"""
|
||||||
|
return [(k.time, k.value) for k in self._keys]
|
||||||
|
|
||||||
|
def set_default_interpolation_function(self, interp_func: Callable[[float], float]):
|
||||||
|
"""设置默认插值函数。"""
|
||||||
|
self.base_interpolation_function = interp_func
|
||||||
|
|
||||||
|
def set_boundary_mode(
|
||||||
|
self, mode: BoundaryBehaviour, base_value: Optional[float] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
设置边界行为。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
mode : BoundaryBehaviour
|
||||||
|
边界行为设定
|
||||||
|
base_value : Optional[float]
|
||||||
|
当 mode=BoundaryBehaviour.CONSTANT 时,指定新的默认值。
|
||||||
|
"""
|
||||||
|
self.boundary_behaviour = mode
|
||||||
|
if base_value is not None:
|
||||||
|
self.base_line = base_value
|
||||||
|
|
||||||
|
def bake(
|
||||||
|
self,
|
||||||
|
start: float,
|
||||||
|
end: float,
|
||||||
|
sample_rate: Optional[float] = None,
|
||||||
|
num_samples: Optional[int] = None,
|
||||||
|
dtype: Any = None,
|
||||||
|
) -> "np.ndarray": # type: ignore 这里这样用会报错吗?不知道,但是人工智能这样写了都,大抵是能用的吧
|
||||||
|
"""
|
||||||
|
将参数曲线在指定时间范围内烘焙为 NumPy 数组,用于高性能实时查询或音频渲染。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start : float
|
||||||
|
烘焙起始时间(包含)。
|
||||||
|
end : float
|
||||||
|
烘焙结束时间(不包含)。
|
||||||
|
sample_rate : Optional[float]
|
||||||
|
采样率(单位:样本/时间单位)。例如,若时间单位为秒,sample_rate=48000 表示每秒 48k 样本。
|
||||||
|
必须与 `num_samples` 二选一提供。
|
||||||
|
num_samples : Optional[int]
|
||||||
|
输出数组的总样本数。若提供,则忽略 `sample_rate`。
|
||||||
|
dtype : Any, optional
|
||||||
|
输出数组的数据类型(如 np.float32)。默认为 np.float64。
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
np.ndarray
|
||||||
|
一维 NumPy 数组,长度为 `num_samples`,`arr[i] ≈ curve(start + i / sample_rate)`。
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
ValueError
|
||||||
|
- 若 `start >= end`
|
||||||
|
- 若未提供 `sample_rate` 且未提供 `num_samples`
|
||||||
|
- 若 `num_samples <= 0`
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
- 内部使用 `np.linspace` 生成时间轴,然后逐点调用 `self.value_at(t)`。
|
||||||
|
- 虽然目前是 Python 循环,但对于典型自动化曲线(<1000 关键帧),NumPy 向量化优势主要体现在内存布局和后续处理。
|
||||||
|
- 如需极致性能(如 >1M 样本),可未来优化为 C++/Numba 加速,但当前已满足 DAW 自动化需求。
|
||||||
|
"""
|
||||||
|
if start >= end:
|
||||||
|
raise ValueError("起始值须小于结束值。")
|
||||||
|
|
||||||
|
if num_samples is not None:
|
||||||
|
if num_samples <= 0:
|
||||||
|
raise ValueError("烘焙的采样数须为非零自然数。")
|
||||||
|
n = num_samples
|
||||||
|
elif sample_rate is not None:
|
||||||
|
if sample_rate <= 0:
|
||||||
|
raise ValueError("烘焙的采样率须为正值。")
|
||||||
|
duration = end - start
|
||||||
|
n = int(ceil(duration * sample_rate))
|
||||||
|
# 别因为小数数值会产生的问题而越界了来着
|
||||||
|
if n == 0:
|
||||||
|
n = 1
|
||||||
|
else:
|
||||||
|
raise ValueError("烘焙参数时,须提供采样率或采样数。")
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# 生成对应时间的节点:[start, ..., end - dt]
|
||||||
|
times = np.linspace(start, end, n, endpoint=False)
|
||||||
|
|
||||||
|
# 计算每个时间节点上的参数值
|
||||||
|
# 我们认为在数字音频工作站的环境里,此值可能最多到 ~1e6 的样子,因此这样 for 一下应当可以接受
|
||||||
|
# WARNING: 人工智能是这样理解的,如果有问题的话后续可能需要更改
|
||||||
|
values = np.empty(n, dtype=dtype or np.float64)
|
||||||
|
for i in range(n):
|
||||||
|
values[i] = self.value_at(float(times[i]))
|
||||||
|
|
||||||
|
return values
|
||||||
440
Musicreater/plugins.py
Normal file
440
Musicreater/plugins.py
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 的插件接口与管理相关内容
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Generator,
|
||||||
|
Set,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
TypeVar,
|
||||||
|
Mapping,
|
||||||
|
Callable,
|
||||||
|
Type,
|
||||||
|
)
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
|
||||||
|
from ._plugin_abc import (
|
||||||
|
# 枚举类
|
||||||
|
PluginTypes,
|
||||||
|
# 抽象基类/数据类(插件参数定义)
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
# 抽象基类(插件定义)
|
||||||
|
MusicInputPluginBase,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
MusicOperatePluginBase,
|
||||||
|
TrackOperatePluginBase,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
ServicePluginBase,
|
||||||
|
LibraryPluginBase,
|
||||||
|
# 顶层插件定义
|
||||||
|
TopPluginBase,
|
||||||
|
)
|
||||||
|
from .exceptions import (
|
||||||
|
PluginMetainfoNotFoundError,
|
||||||
|
ParameterTypeError,
|
||||||
|
PluginInstanceNotFoundError,
|
||||||
|
PluginRegisteredError,
|
||||||
|
PluginNotFoundError,
|
||||||
|
PluginDependencyNotFound,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# 枚举类
|
||||||
|
"PluginTypes",
|
||||||
|
# 抽象基类/数据类(插件参数定义)
|
||||||
|
"PluginConfig",
|
||||||
|
"PluginMetaInformation",
|
||||||
|
# 抽象基类(插件定义)
|
||||||
|
"MusicInputPluginBase",
|
||||||
|
"TrackInputPluginBase",
|
||||||
|
"MusicOperatePluginBase",
|
||||||
|
"TrackOperatePluginBase",
|
||||||
|
"MusicOutputPluginBase",
|
||||||
|
"TrackOutputPluginBase",
|
||||||
|
"ServicePluginBase",
|
||||||
|
"LibraryPluginBase",
|
||||||
|
# 插件注册用装饰函数
|
||||||
|
"music_input_plugin",
|
||||||
|
"track_input_plugin",
|
||||||
|
"music_operate_plugin",
|
||||||
|
"track_operate_plugin",
|
||||||
|
"music_output_plugin",
|
||||||
|
"track_output_plugin",
|
||||||
|
"service_plugin",
|
||||||
|
"library_plugin",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
T_IOPlugin = TypeVar(
|
||||||
|
"T_IOPlugin",
|
||||||
|
MusicInputPluginBase,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
T_Plugin = TypeVar(
|
||||||
|
"T_Plugin",
|
||||||
|
MusicInputPluginBase,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
MusicOperatePluginBase,
|
||||||
|
TrackOperatePluginBase,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
ServicePluginBase,
|
||||||
|
LibraryPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugin_module(package: Union[Path, str]):
|
||||||
|
"""自动发现并加载插件包中的插件
|
||||||
|
|
||||||
|
参数:
|
||||||
|
=====
|
||||||
|
package: Path | str, 可选
|
||||||
|
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(package, Path):
|
||||||
|
relative_path = package.resolve().relative_to(Path.cwd().resolve())
|
||||||
|
if relative_path.stem == "__init__":
|
||||||
|
return importlib.import_module(".".join(relative_path.parts[:-1]))
|
||||||
|
else:
|
||||||
|
return importlib.import_module(
|
||||||
|
".".join(relative_path.parts[:-1] + (relative_path.stem,))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return importlib.import_module(package)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise PluginNotFoundError("无法找到名为`{}`的插件包".format(package)) from e
|
||||||
|
|
||||||
|
|
||||||
|
class PluginRegistry:
|
||||||
|
"""插件注册管理器(注册表)"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._music_input_plugins: Dict[str, MusicInputPluginBase] = {}
|
||||||
|
self._track_input_plugins: Dict[str, TrackInputPluginBase] = {}
|
||||||
|
self._music_operate_plugins: Dict[str, MusicOperatePluginBase] = {}
|
||||||
|
self._track_operate_plugins: Dict[str, TrackOperatePluginBase] = {}
|
||||||
|
self._music_output_plugins: Dict[str, MusicOutputPluginBase] = {}
|
||||||
|
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
|
||||||
|
self._service_plugins: Dict[str, ServicePluginBase] = {}
|
||||||
|
self._library_plugins: Dict[str, LibraryPluginBase] = {}
|
||||||
|
self._all_plugin_id: List = []
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
|
||||||
|
"""迭代器,返回所有插件"""
|
||||||
|
return iter(
|
||||||
|
(
|
||||||
|
(PluginTypes.FUNCTION_MUSIC_IMPORT, self._music_input_plugins),
|
||||||
|
(PluginTypes.FUNCTION_TRACK_IMPORT, self._track_input_plugins),
|
||||||
|
(PluginTypes.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins),
|
||||||
|
(PluginTypes.FUNCTION_TRACK_OPERATE, self._track_operate_plugins),
|
||||||
|
(PluginTypes.FUNCTION_MUSIC_EXPORT, self._music_output_plugins),
|
||||||
|
(PluginTypes.FUNCTION_TRACK_EXPORT, self._track_output_plugins),
|
||||||
|
(PluginTypes.SERVICE, self._service_plugins),
|
||||||
|
(PluginTypes.LIBRARY, self._library_plugins),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _register_plugin(
|
||||||
|
self, cls_dict: dict, plg_class: Type[TopPluginBase], plg_id: str
|
||||||
|
) -> None:
|
||||||
|
"""注册插件"""
|
||||||
|
if plg_id in cls_dict:
|
||||||
|
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
|
||||||
|
raise PluginRegisteredError(
|
||||||
|
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件。".format(
|
||||||
|
plg_id, plg_class.metainfo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if missing_requirements := [
|
||||||
|
i for i in plg_class.metainfo.dependencies if i not in self._all_plugin_id
|
||||||
|
]:
|
||||||
|
raise PluginDependencyNotFound(
|
||||||
|
"插件 `{}` 依赖于这些插件:`{}`;当前环境中缺失:`{}`。加载此插件时,请务必将被依赖的插件提前载入。".format(
|
||||||
|
plg_id, plg_class.metainfo.dependencies, missing_requirements
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cls_dict[plg_id] = plg_class()
|
||||||
|
self._all_plugin_id.append(plg_id)
|
||||||
|
|
||||||
|
def register_music_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输入插件-整首曲目"""
|
||||||
|
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_track_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输入插件-单个音轨"""
|
||||||
|
self._register_plugin(self._track_input_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_music_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册曲目处理插件"""
|
||||||
|
self._register_plugin(self._music_operate_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_track_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册音轨处理插件"""
|
||||||
|
self._register_plugin(self._track_operate_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_music_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输出插件-整首曲目"""
|
||||||
|
self._register_plugin(self._music_output_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_track_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册输出插件-单个音轨"""
|
||||||
|
self._register_plugin(self._track_output_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_service_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册服务插件"""
|
||||||
|
self._register_plugin(self._service_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
def register_library_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||||
|
"""注册支持库插件"""
|
||||||
|
self._register_plugin(self._library_plugins, plugin_class, plugin_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_io_plugin_by_format(
|
||||||
|
plugin_regdict: Mapping[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[T_IOPlugin, None, None]:
|
||||||
|
if isinstance(fpath_or_format, str):
|
||||||
|
return (
|
||||||
|
plugin
|
||||||
|
for plugin in plugin_regdict.values()
|
||||||
|
if plugin.can_handle_format(fpath_or_format)
|
||||||
|
)
|
||||||
|
elif isinstance(fpath_or_format, Path):
|
||||||
|
# print("在",plugin_regdict,"中,查找可用于处理",fpath_or_format,"的插件")
|
||||||
|
return (
|
||||||
|
plugin
|
||||||
|
for plugin in plugin_regdict.values()
|
||||||
|
if plugin.can_handle_file(fpath_or_format)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ParameterTypeError(
|
||||||
|
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||||
|
type(fpath_or_format), fpath_or_format
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_input_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[MusicInputPluginBase, None, None]:
|
||||||
|
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._music_input_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_input_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[TrackInputPluginBase, None, None]:
|
||||||
|
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._track_input_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_output_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[MusicOutputPluginBase, None, None]:
|
||||||
|
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._music_output_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_output_plugin_by_format(
|
||||||
|
self, filepath_or_format: Union[Path, str]
|
||||||
|
) -> Generator[TrackOutputPluginBase, None, None]:
|
||||||
|
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
|
||||||
|
return self._get_io_plugin_by_format(
|
||||||
|
self._track_output_plugins, filepath_or_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_plugin_by_name(
|
||||||
|
self,
|
||||||
|
plugin_regdict: Mapping[str, T_Plugin],
|
||||||
|
plugin_name: str,
|
||||||
|
plugin_usage: str = "",
|
||||||
|
) -> T_Plugin:
|
||||||
|
"""通过指定名称,以获取对应的插件,当名称重叠时,取版本号最大的"""
|
||||||
|
try:
|
||||||
|
return max(
|
||||||
|
[
|
||||||
|
plugin
|
||||||
|
for plugin in plugin_regdict.values()
|
||||||
|
if plugin.metainfo.name == plugin_name
|
||||||
|
],
|
||||||
|
key=lambda plugin: plugin.metainfo.version,
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
raise PluginInstanceNotFoundError(
|
||||||
|
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
|
||||||
|
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._music_input_plugins, plugin_name, "导入全曲"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPluginBase:
|
||||||
|
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._track_input_plugins, plugin_name, "导入单轨"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePluginBase:
|
||||||
|
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._music_operate_plugins, plugin_name, "处理整个曲目"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePluginBase:
|
||||||
|
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._track_operate_plugins, plugin_name, "处理单个音轨"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPluginBase:
|
||||||
|
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._music_output_plugins, plugin_name, "导出完整曲目"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPluginBase:
|
||||||
|
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._track_output_plugins, plugin_name, "导出单个音轨"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_service_plugin(self, plugin_name: str) -> ServicePluginBase:
|
||||||
|
"""获取服务用插件,当名称重叠时,取版本号最大的"""
|
||||||
|
return self._get_plugin_by_name(self._service_plugins, plugin_name, "提供服务")
|
||||||
|
|
||||||
|
def get_library_plugin(self, plugin_name: str) -> LibraryPluginBase:
|
||||||
|
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
|
||||||
|
return self._get_plugin_by_name(
|
||||||
|
self._library_plugins, plugin_name, "作为依赖库"
|
||||||
|
)
|
||||||
|
|
||||||
|
def supported_input_formats(self) -> Set[str]:
|
||||||
|
"""所有支持的导入格式"""
|
||||||
|
return set(
|
||||||
|
chain.from_iterable(
|
||||||
|
plugin.supported_formats
|
||||||
|
for plugin in chain(
|
||||||
|
self._music_input_plugins.values(),
|
||||||
|
self._track_input_plugins.values(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def supported_output_formats(self) -> Set[str]:
|
||||||
|
"""所有支持的导出格式"""
|
||||||
|
return set(
|
||||||
|
chain.from_iterable(
|
||||||
|
plugin.supported_formats
|
||||||
|
for plugin in chain(
|
||||||
|
self._music_output_plugins.values(),
|
||||||
|
self._track_output_plugins.values(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_global_plugin_registry = PluginRegistry()
|
||||||
|
"""全局插件注册表实例"""
|
||||||
|
|
||||||
|
|
||||||
|
def __plugin_regist_decorator(plg_id: str, rgst_func: Callable[[type, str], None]):
|
||||||
|
|
||||||
|
def decorator(cls):
|
||||||
|
global _global_plugin_registry
|
||||||
|
cls.id = plg_id
|
||||||
|
rgst_func(cls, plg_id)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def music_input_plugin(plugin_id: str):
|
||||||
|
"""全曲输入用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_music_input_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def track_input_plugin(plugin_id: str):
|
||||||
|
"""单轨输入用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_track_input_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def music_operate_plugin(plugin_id: str):
|
||||||
|
"""全曲处理用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_music_operate_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def track_operate_plugin(plugin_id: str):
|
||||||
|
"""音轨处理插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_track_operate_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def music_output_plugin(plugin_id: str):
|
||||||
|
"""乐曲输出用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_music_output_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def track_output_plugin(plugin_id: str):
|
||||||
|
"""音轨输出用插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_track_output_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def service_plugin(plugin_id: str):
|
||||||
|
"""服务插件装饰器"""
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_service_plugin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def library_plugin(plugin_id: str):
|
||||||
|
"""支持库插件装饰器"""
|
||||||
|
|
||||||
|
return __plugin_regist_decorator(
|
||||||
|
plugin_id, _global_plugin_registry.register_library_plugin
|
||||||
|
)
|
||||||
21
Musicreater/types.py
Normal file
21
Musicreater/types.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
音·创 v3 定义的一些数据类型,可以用于类型检查器
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿 & 玉衡Alioth
|
||||||
|
Copyright © 2026 Eilles & YuhengAlioth
|
||||||
|
|
||||||
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
|
Terms & Conditions: License.md in the root directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union
|
||||||
|
|
||||||
159
README.md
159
README.md
@@ -1,49 +1,140 @@
|
|||||||
# 音·创 Musicreater
|
<!-- [Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge&label=作者B站
|
||||||
|
[Bilibili: 玉衡Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge&label=作者B站 -->
|
||||||
### 介绍
|
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge&label=代码风格
|
||||||
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
|
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
||||||
|
[release]: https://img.shields.io/github/v/release/TriM-Organization/Musicreater?style=for-the-badge&label=发行版
|
||||||
欢迎加群:861684859
|
[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&label=协议
|
||||||
|
[commit-activity]: https://img.shields.io/github/commit-activity/m/TriM-Organization/Musicreater%2Fmaster?style=for-the-badge&label=提交活动&color=AB70FF
|
||||||
### 软件架构
|
|
||||||
|
|
||||||
软件采用Python作为第一语言,目前还没有使用其他语言辅助。使用BeeWare作为图形库兼容安卓。
|
|
||||||
|
|
||||||
尽量全平台支持
|
|
||||||
|
|
||||||
|
|
||||||
### 安装教程
|
|
||||||
|
|
||||||
#### Windows
|
<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>
|
||||||
|
|
||||||
#### Linux
|
<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>
|
||||||
|
|
||||||
#### Android
|
<!-- [![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
||||||
|
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474) -->
|
||||||
|
[![CodeStyle: black]](https://github.com/psf/black)
|
||||||
|
[![][python]](https://www.python.org/)
|
||||||
|
[![][license]](LICENSE)
|
||||||
|
[![][release]](../../releases)
|
||||||
|
|
||||||
即将到来。
|
[](https://gitee.com/TriM-Organization/Musicreater/stargazers)
|
||||||
|
[](https://gitee.com/TriM-Organization/Musicreater/members)
|
||||||
|
[](https://github.com/TriM-Organization/Musicreater/stargazers)
|
||||||
|
[](https://github.com/TriM-Organization/Musicreater/forks)
|
||||||
|
|
||||||
### 使用说明
|
简体中文🇨🇳 | [English🇬🇧](README_EN.md)
|
||||||
|
|
||||||
1. 直接运行就好
|
## 介绍 🚀
|
||||||
2. 有不懂的问题来群里问
|
|
||||||
3. 请理解英文表述
|
|
||||||
|
|
||||||
### 致谢
|
音·创 是一款免费开源的针对 **《我的世界》** 音乐的支持库
|
||||||
|
|
||||||
1. 感谢由 [Fuckcraft](https://github.com/fuckcraft) “鸣凤鸽子”等 带来的我的世界websocket服务器功能
|
欢迎加群:[861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||||
2. 感谢 昀梦<QQ1515399885> 找出指令生成错误bug并指正
|
|
||||||
3. 感谢由 Charlie_Ping “查理平” 带来的bdx转换功能
|
> **注意** 本仓库内的项目仅仅是支持库,其用户为基岩版音乐相关软件的开发者
|
||||||
4. 感谢由 CMA_2401PT 提供的 BDXWorkShop作为.bdx结构的操作指导
|
>
|
||||||
5. 感谢广大群友为此程序提供的测试等支持
|
> 面向常规用户的 **基岩版音乐转换工具** 请参阅:[伶伦转换器](../../../Linglun-Converter)
|
||||||
6. 若您为我找出了错误但您的名字没有显示在此列表中,请联系我!
|
>
|
||||||
|
> 我们也正在开发面向高级用户的 **基岩版音乐编辑工具**(数字音频工作站):[伶伦](../../../LinglunStudio)
|
||||||
|
|
||||||
|
|
||||||
### 作者<金羿>联系方式
|
## 安装 🔳
|
||||||
|
|
||||||
1. QQ 2647547478
|
- 使用 pypi
|
||||||
2. 电邮 EillesWan2006@163.com W-YI_DoctorYI@outlook.com
|
|
||||||
3. 微信 WYI_DoctorYI
|
```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 主。
|
||||||
|
|
||||||
|
**玉衡Alioth Alioth**:我的世界基岩版玩家,喜欢编程和音乐,学生。
|
||||||
|
|
||||||
|
**偷吃不是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 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
||||||
|
- 感谢 **[神羽 “SnowyKami”](https://www.sfkm.me/)** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
||||||
|
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
|
||||||
|
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
|
||||||
|
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题。
|
||||||
|
- <table><tr><td>感谢 **油炸**<QQ2836146704> 激励我们不断开发新的内容。</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) 项目的开发为我们提供持续的追赶动力。
|
||||||
|
- 感谢 **启航与凡凡**\<QQ2777856500\> 找到 **音·创 v2** 版本音符序列文件解码的错误并指出修正方式。
|
||||||
|
|
||||||
|
> 感谢广大群友为此库提供的测试和建议等
|
||||||
|
> 若您对我们有所贡献但您的名字没有出现在此列表中,请联系我们!
|
||||||
|
|
||||||
|
## 联系 📞
|
||||||
|
|
||||||
|
若遇到库中的问题,欢迎在[此](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.
|
||||||
|
|||||||
12
README.rst
12
README.rst
@@ -1,12 +0,0 @@
|
|||||||
Musicreater
|
|
||||||
===========
|
|
||||||
|
|
||||||
**This cross-platform app was generated by** `Briefcase`_ **- part of**
|
|
||||||
`The BeeWare Project`_. **If you want to see more tools like Briefcase, please
|
|
||||||
consider** `becoming a financial member of BeeWare`_.
|
|
||||||
|
|
||||||
音·创(Musicreater)是由金羿(W-YI)开发的一款《我的世界》基岩版音乐生成辅助软件
|
|
||||||
|
|
||||||
.. _`Briefcase`: https://github.com/beeware/briefcase
|
|
||||||
.. _`The BeeWare Project`: https://beeware.org/
|
|
||||||
.. _`becoming a financial member of BeeWare`: https://beeware.org/contributing/membership
|
|
||||||
138
README_EN.md
Normal file
138
README_EN.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-00A1E7?style=for-the-badge
|
||||||
|
[Bilibili: Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
|
||||||
|
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
||||||
|
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
||||||
|
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
|
||||||
|
[license]: https://img.shields.io/badge/Licence-%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE-228B22?style=for-the-badge
|
||||||
|
|
||||||
|
<h1 align="center">
|
||||||
|
音·创 Musicreater
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="128" height="128" src="https://s1.ax1x.com/2022/05/06/Ouhghj.md.png" >
|
||||||
|
</img>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 align="center">A cost free and open-source library for handling with <i>Minecraft</i> digital music.</h3>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||||
|
</img>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
||||||
|
[![][Bilibili: Alioth]](https://space.bilibili.com/604072474)
|
||||||
|
[![CodeStyle: black]](https://github.com/psf/black)
|
||||||
|
[![][python]](https://www.python.org/)
|
||||||
|
[![][license]](LICENSE)
|
||||||
|
[![][release]](../../releases)
|
||||||
|
|
||||||
|
[](https://gitee.com/TriM-Organization/Musicreater/stargazers)
|
||||||
|
[](https://gitee.com/TriM-Organization/Musicreater/members)
|
||||||
|
[](https://github.com/TriM-Organization/Musicreater/stargazers)
|
||||||
|
[](https://github.com/TriM-Organization/Musicreater/forks)
|
||||||
|
|
||||||
|
[简体中文 🇨🇳](README.md) | English🇬🇧
|
||||||
|
|
||||||
|
**Notice that the localizations of documents may probably NOT be up-to-date. The original document is in Chinese.**
|
||||||
|
|
||||||
|
## Introduction🚀
|
||||||
|
|
||||||
|
Musicreater is a free open-source library used for handling with digital musics that being able to be played in _Minecraft_.
|
||||||
|
|
||||||
|
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||||
|
|
||||||
|
> **NOTICE** The project inside this repository is only the support library, which is mainly for the developers of music related software for _Minecraft: Bedrock Edition_.
|
||||||
|
>
|
||||||
|
> The _Bedrock Edition music convertor_ which is for common users is in [Linglun Converter](../../../Linglun-Converter).
|
||||||
|
>
|
||||||
|
> We are also developing a _BE music editor_ (digital audio workstation): [Linglun](../../../LinglunStudio)
|
||||||
|
|
||||||
|
|
||||||
|
## Installation 🔳
|
||||||
|
|
||||||
|
- Via pypi
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install Musicreater --upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
- If above command cannot fetch latest version, try:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -i https://pypi.python.org/simple Musicreater --upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
- Clone repo and Install (Latest but **NOT RECOMMANDED**):
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/TriM-Organization/Musicreater.git
|
||||||
|
cd Musicreater
|
||||||
|
python setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
Commands such as `python`、`pip` could be changed to some like `python3` or `pip3` according to the difference of platforms.
|
||||||
|
|
||||||
|
## Documentation 📄
|
||||||
|
|
||||||
|
(Not in English yet)
|
||||||
|
|
||||||
|
[生成文件的使用](./docs/%E7%94%9F%E6%88%90%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.md)
|
||||||
|
|
||||||
|
[仓库 API 文档](./docs/%E5%BA%93%E7%9A%84%E7%94%9F%E6%88%90%E4%B8%8E%E5%8A%9F%E8%83%BD%E6%96%87%E6%A1%A3.md)
|
||||||
|
|
||||||
|
### Authors ✒
|
||||||
|
|
||||||
|
**Eilles (金羿)**:A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
|
||||||
|
|
||||||
|
**Alioth (玉衡Alioth)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
|
||||||
|
|
||||||
|
**Touch (偷吃不是 Touch)**: A man who is good at using command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
|
||||||
|
|
||||||
|
## Acknowledgements 🙏
|
||||||
|
|
||||||
|
This list is not in any order.
|
||||||
|
|
||||||
|
- Thank _昀梦_\<QQ1515399885\> for finding and correcting the bugs in the commands that _Musicreater_ generated.
|
||||||
|
- Thank _Charlie_Ping “查理平”_ for the bdx convert function for reference, and the reference chart that's used to convert the mid's instruments into Minecraft's instruments.
|
||||||
|
- Thank _[CMA_2401PT](https://github.com/CMA2401PT)_ for BDXWorkShop for reference of the .bdx structure's operation, and his guidance in some aspects of our development.
|
||||||
|
- Thank _[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”_ \<QQ1600515314\> for his midi analysis algorithm brought to us, we had adapted it and made it applied in one of our working method; Also, thank him for the [WebConvertor](https://dislink.github.io/midi2bdx/) which brought us so much pressure and power to develop as well as update our projects better, instead of loaf on our project.
|
||||||
|
- Thank _Mono_\<QQ738893087\> for reporting problems while installing
|
||||||
|
- Thank _Ammelia “艾米利亚”_\<QQ2838334637\> for urging us to develop new functions, and put forward a lot of excellent suggestions for new functions, as well as the BDX file's importing test support provided, which has given a lot of practical theoretical support for our new Structure Generating Algorithm
|
||||||
|
- Thank _[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”_ for supporting and promoting our project, and also thanks him for his server which given us to use for free.
|
||||||
|
- Thank _指令师\_苦力怕 “playjuice123”_\<QQ240667197\> for finding bugs within our code, and noticed us to repair a big problem.
|
||||||
|
- Thank _雷霆_\<QQ3555268519\> for his annoying and provoking operations which may awake some problems within the program by chance and reminding us to repair.
|
||||||
|
- Thank _小埋_\<QQ2039310975\> for reporting the empty add-on packs title and description problem.
|
||||||
|
- <table><tr><td>Thank <i>油炸</i> <QQ2836146704> 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=""It was once, a convert-QQ-bot is just in front my eyes" "Until lose, I finally know cannot chase back what I needs""></td><td><small>"It was once, a convert-QQ-bot is just in front my eyes"<br>"Until lose, I finally know cannot chase back what I needs"</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”等相关称呼均为必要的介绍性使用
|
||||||
|
|
||||||
|
- 上文提及的 网易 公司,指代的是在中国大陆运营《我的世界:中国版》的上海网之易璀璨网络科技有限公司
|
||||||
52
README_en.md
52
README_en.md
@@ -1,52 +0,0 @@
|
|||||||
# Musicreater
|
|
||||||
|
|
||||||
### Introduction
|
|
||||||
Musicreater(音·创) is an Eilles(*W-YI*)'s app that is used for creating musics in **Minecraft: Bedrock Edition**.
|
|
||||||
|
|
||||||
Welcome to join our QQ group: 861684859
|
|
||||||
|
|
||||||
### Framework
|
|
||||||
|
|
||||||
Use *Python* to develop, use *BeeWare* as a Windows Library.
|
|
||||||
|
|
||||||
We are trying to support every platform.
|
|
||||||
|
|
||||||
|
|
||||||
### Tutorials
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
|
|
||||||
Please wait for a while...
|
|
||||||
Comming soon
|
|
||||||
|
|
||||||
#### Linux
|
|
||||||
|
|
||||||
Please wait for a while...
|
|
||||||
Comming soon
|
|
||||||
|
|
||||||
#### Android
|
|
||||||
|
|
||||||
Please wait for a while...
|
|
||||||
Comming soon
|
|
||||||
|
|
||||||
### Instructions
|
|
||||||
|
|
||||||
1. Just make u understand the Chinese
|
|
||||||
2. If u dont understand, u can come to the QQ group or email me to ask questions
|
|
||||||
3. The English Edition is comming soon.
|
|
||||||
|
|
||||||
### Thanks
|
|
||||||
|
|
||||||
1. Thank [Fuckcraft](https://github.com/fuckcraft) “鸣凤鸽子”and so on for the function of Creating the Websocket Server for Minecraft: Bedrock Edition.
|
|
||||||
2. Thank 昀梦<QQ1515399885> for finding and correcting the bugs in the commands that *Musicreater* Created.
|
|
||||||
3. Thank Charlie_Ping “查理平” for bdx convert funtion.
|
|
||||||
4. Thank CMA_2401PT for BDXWorkShop as the .bdx structure's operation guide.
|
|
||||||
5. Thanks for a lot of groupmates who support me and help me to test the program.
|
|
||||||
6. If u have give me some help but u haven't been in the list, please contact me.
|
|
||||||
|
|
||||||
|
|
||||||
### Contact *Eilles(W-YI)*(金羿)
|
|
||||||
|
|
||||||
1. QQ 2647547478
|
|
||||||
2. E-mail EillesWan2006@163.com W-YI_DoctorYI@outlook.com EillesWan@outlook.com
|
|
||||||
3. WeChat WYI_DoctorYI
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
briefcase dev
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import os,shutil
|
|
||||||
from sys import platform
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print("更新执行位置...")
|
|
||||||
if platform == 'win32':
|
|
||||||
try:
|
|
||||||
os.chdir(__file__[:len(__file__)-__file__[len(__file__)::-1].index('\\')]+'src\\')
|
|
||||||
print("更新执行位置,当前文件位置"+__file__)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
os.chdir(__file__[:len(__file__)-__file__[len(__file__)::-1].index('/')]+'src/')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
print("其他平台:"+platform+"更新执行位置,当前文件位置"+__file__)
|
|
||||||
print('完成!')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import toga,amulet
|
|
||||||
except:
|
|
||||||
print("You'd better install the libraries of this app\nNow, we're helping you with this.")
|
|
||||||
from src.musicreater.msctspt.bugReporter import version
|
|
||||||
version.installLibraries(version)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if platform == 'win32':
|
|
||||||
os.system("python ./Musicreater.py")
|
|
||||||
elif platform == 'linux':
|
|
||||||
os.system("python3 ./Musicreater.py")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.path.exists("./log/"):
|
|
||||||
shutil.rmtree("./log/")
|
|
||||||
if os.path.exists("./logs/"):
|
|
||||||
shutil.rmtree("./logs/")
|
|
||||||
if os.path.exists("./cache/"):
|
|
||||||
shutil.rmtree("./cache/")
|
|
||||||
except:
|
|
||||||
print("无法清除日志及临时文件")
|
|
||||||
53
TO-DO.md
Normal file
53
TO-DO.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 任务清单
|
||||||
|
|
||||||
|
## 待办事项
|
||||||
|
- 乐曲文件格式设计
|
||||||
|
目前想到的是:
|
||||||
|
1. 使用 `.MCT` 作为项目文件的后缀,然后考虑一下格式是否和之前的 MusicSequence 兼容,如果兼容的话可以照旧用 `.MSQ`,如不的话,可以试试想一个新的后缀名作为数据文件后缀
|
||||||
|
2. 要求数据文件支持完全流式读入
|
||||||
|
|
||||||
|
- [] 音轨静音处理
|
||||||
|
当前没有处理
|
||||||
|
|
||||||
|
- [] 优化音轨的存储方式
|
||||||
|
当前是用列表,且每一次变动元素都要重新排序,这样消耗太大了,需要优化,改用最小堆形式(heapq)
|
||||||
|
|
||||||
|
- 移植 v2 功能到内置插件
|
||||||
|
目前 v2 的功能有很多,都要移植到 v3。
|
||||||
|
1. [x] 导入 Midi 文件到全曲
|
||||||
|
2. [] 导入 Midi 文件到指定轨道
|
||||||
|
3. [x] 导出到延迟播放器的结构文件(MCSTRUCTURE、BDX)
|
||||||
|
4. [] 导出到延迟播放器的附加包
|
||||||
|
5. [] 导出到积分板播放器的以上两种形式
|
||||||
|
6. [] 导出到中继器播放器的以上两种形式
|
||||||
|
7. [] 在 WebSocket 播放器中播放
|
||||||
|
8. [] 导出到支持神羽资源包的以上 7 种形式
|
||||||
|
9. [] 对于 Midi 歌词的实验性功能
|
||||||
|
10. [] 对于 Java 版本适配的实验性功能
|
||||||
|
11. [] 对于听感优化的实验性功能(插值、偏移)
|
||||||
|
|
||||||
|
- [] 测试参数曲线的功能
|
||||||
|
|
||||||
|
- [] 支持导出音符盒构成的音乐
|
||||||
|
|
||||||
|
- [] 支持导出成 schematic 结构
|
||||||
|
|
||||||
|
## 讨论
|
||||||
|
|
||||||
|
1. [x] 是否应该在插件注册表 PluginRegistry 中采用 `Dict[插件名, 插件对象]` 的形式存储插件?
|
||||||
|
当前不采用这种方式是认为可以兼容一些极端场景下用户将一堆同名插件放在一起的情况。但是就算是插件放在一起,我们也可以有选择地读入注册表,比如依照版本号只读取最高版本的插件,并不需要全部存储在插件注册表中。所以其实用字典来存储是有利的?吗?
|
||||||
|
|
||||||
|
**当前已解决**
|
||||||
|
|
||||||
|
引入了插件惟一识别码之后当然是采用 `Dict[插件唯一识别码, 插件对象]` 来存储插件了~之前插件名称的内容是我想得太浅了,我写完所有代码之后才想到插件名称是中文还带空格的任意字符串……
|
||||||
|
|
||||||
|
2. [] 服务插件到底该怎么写?总不能留着一个 PluginType.SERVICE 的插件一直空在那里吧……
|
||||||
|
|
||||||
|
3. [x] 插件依赖性的优化。目前没有处理各个插件依赖关系的问题,如果插件之间彼此依赖要怎么做?
|
||||||
|
我的想法是,这个依赖的处理由调用端来完成。比如我们的 伶伦工作站 是以 音·创 为核心的一个可视化数字音频工作站。
|
||||||
|
那么应该由伶伦来处理依赖关系并加载之。
|
||||||
|
|
||||||
|
**当前已经大致解决**
|
||||||
|
|
||||||
|
首先有一个验证顺序,我们在插件加载后会验证,当前已加载的插件中是否包括了所需的插件,如果缺少则报错。
|
||||||
|
这样的加载顺序安排仍然需要调用端来实现。
|
||||||
22
clean_update.py
Normal file
22
clean_update.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import track
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with console.status("寻众迹于 .egg-info 内"):
|
||||||
|
egg_info: list = []
|
||||||
|
for file in os.listdir():
|
||||||
|
if file.endswith(".egg-info"):
|
||||||
|
egg_info.append(file)
|
||||||
|
console.print(file)
|
||||||
|
for file in track(["build", "dist", "logs", *egg_info], description="正删档"):
|
||||||
|
if os.path.isdir(file) and os.access(file, os.W_OK):
|
||||||
|
shutil.rmtree(file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
9
docs/API.md
Normal file
9
docs/API.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
**此为开发相关文档,内容包括:库的简单调用、所生成文件结构的详细说明、特殊参数的详细解释**
|
||||||
|
|
||||||
|
音·创 v3 的文档还在编纂过程中,请耐心等待。
|
||||||
237
docs/异常继承关系.mmd
Normal file
237
docs/异常继承关系.mmd
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
|
||||||
|
classDiagram
|
||||||
|
|
||||||
|
direction LR
|
||||||
|
|
||||||
|
class Exception{
|
||||||
|
Python 内置基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterBaseException {
|
||||||
|
"[音·创] - ..."
|
||||||
|
所有音·创 v3 错误的基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterInnerlyError {
|
||||||
|
"内部错误 - ..."
|
||||||
|
面向开发者的内部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicreaterOuterlyError {
|
||||||
|
"外部错误 - ..."
|
||||||
|
面向用户的外部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class InnerlyParameterError {
|
||||||
|
"内部传参错误 - ..."
|
||||||
|
内部参数相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class OuterlyParameterError {
|
||||||
|
"参数错误 - ..."
|
||||||
|
外部参数相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterTypeError {
|
||||||
|
"参数类型错误:..."
|
||||||
|
继承自 InnerlyParameterError 和 TypeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterValueError {
|
||||||
|
"参数数值错误:..."
|
||||||
|
继承自 InnerlyParameterError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginNotSpecifiedError {
|
||||||
|
"未指定插件:..."
|
||||||
|
继承自 InnerlyParameterError 和 LookupError
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZeroSpeedError {
|
||||||
|
"播放速度为零:..."
|
||||||
|
继承自 OuterlyParameterError 和 ZeroDivisionError
|
||||||
|
}
|
||||||
|
|
||||||
|
class IllegalMinimumVolumeError {
|
||||||
|
"最小播放音量超出范围:..."
|
||||||
|
继承自 OuterlyParameterError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileFormatNotSupportedError {
|
||||||
|
"不支持的文件格式:..."
|
||||||
|
继承自 MusicreaterOuterlyError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryDecodeError {
|
||||||
|
"解码音乐存储二进制数据时出现问题 - ..."
|
||||||
|
继承自 MusicreaterOuterlyError
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleNoteDecodeError {
|
||||||
|
"音符解码出错:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryFileTypeError {
|
||||||
|
"无法识别音乐存储文件对应的类型:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteBinaryFileVerificationFailed {
|
||||||
|
"音乐存储文件校验失败:..."
|
||||||
|
继承自 NoteBinaryDecodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginDefineError {
|
||||||
|
"插件内部错误 - ..."
|
||||||
|
插件定义相关的内部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginInstanceNotFoundError {
|
||||||
|
"插件实例未找到:..."
|
||||||
|
继承自 PluginDefineError 和 LookupError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginAttributeNotFoundError {
|
||||||
|
"插件类的必要属性不存在:..."
|
||||||
|
继承自 PluginDefineError 和 AttributeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoError {
|
||||||
|
"插件元信息定义错误 - ..."
|
||||||
|
插件元信息相关错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoTypeError {
|
||||||
|
"插件元信息类型错误:..."
|
||||||
|
继承自 PluginMetainfoError 和 TypeError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoValueError {
|
||||||
|
"插件元信息数值错误:..."
|
||||||
|
继承自 PluginMetainfoError 和 ValueError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMetainfoNotFoundError {
|
||||||
|
"插件元信息未定义:..."
|
||||||
|
继承自 PluginMetainfoError 和 PluginAttributeNotFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginLoadError {
|
||||||
|
"插件加载错误 - ..."
|
||||||
|
插件加载相关的外部错误
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginNotFoundError {
|
||||||
|
"插件未找到:..."
|
||||||
|
继承自 PluginLoadError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginRegisteredError {
|
||||||
|
"插件重复注册:..."
|
||||||
|
继承自 PluginLoadError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigRelatedError {
|
||||||
|
"插件配置相关错误 - ..."
|
||||||
|
插件配置相关错误基类
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigLoadError {
|
||||||
|
"插件配置文件加载错误:..."
|
||||||
|
继承自 PluginLoadError、PluginConfigRelatedError
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginConfigDumpError {
|
||||||
|
"插件配置文件保存错误:..."
|
||||||
|
继承自 PluginConfigRelatedError
|
||||||
|
}
|
||||||
|
%% 高亮定义
|
||||||
|
|
||||||
|
class ParameterTypeError ::: highlight
|
||||||
|
class ParameterValueError ::: highlight
|
||||||
|
class PluginNotSpecifiedError ::: highlight
|
||||||
|
class ZeroSpeedError ::: highlight
|
||||||
|
class IllegalMinimumVolumeError ::: highlight
|
||||||
|
class FileFormatNotSupportedError ::: highlight
|
||||||
|
class SingleNoteDecodeError ::: highlight
|
||||||
|
class NoteBinaryFileTypeError ::: highlight
|
||||||
|
class NoteBinaryFileVerificationFailed ::: highlight
|
||||||
|
class PluginInstanceNotFoundError ::: highlight
|
||||||
|
class PluginAttributeNotFoundError ::: highlight
|
||||||
|
class PluginMetainfoTypeError ::: highlight
|
||||||
|
class PluginMetainfoValueError ::: highlight
|
||||||
|
class PluginMetainfoNotFoundError ::: highlight
|
||||||
|
class PluginNotFoundError ::: highlight
|
||||||
|
class PluginRegisteredError ::: highlight
|
||||||
|
class PluginConfigLoadError ::: highlight
|
||||||
|
class PluginConfigDumpError ::: highlight
|
||||||
|
|
||||||
|
%% 定义高亮样式
|
||||||
|
classDef highlight fill:,stroke-width:5px
|
||||||
|
|
||||||
|
%% 继承关系(箭头从子类指向父类)
|
||||||
|
Exception <|-- MusicreaterBaseException
|
||||||
|
Exception <|-- TypeError
|
||||||
|
Exception <|-- ValueError
|
||||||
|
Exception <|-- LookupError
|
||||||
|
Exception <|-- AttributeError
|
||||||
|
Exception <|-- ZeroDivisionError
|
||||||
|
MusicreaterBaseException <|-- MusicreaterInnerlyError
|
||||||
|
MusicreaterBaseException <|-- MusicreaterOuterlyError
|
||||||
|
|
||||||
|
MusicreaterInnerlyError <|-- InnerlyParameterError
|
||||||
|
MusicreaterOuterlyError <|-- OuterlyParameterError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- ParameterTypeError
|
||||||
|
TypeError <|-- ParameterTypeError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- ParameterValueError
|
||||||
|
ValueError <|-- ParameterValueError
|
||||||
|
|
||||||
|
InnerlyParameterError <|-- PluginNotSpecifiedError
|
||||||
|
LookupError <|-- PluginNotSpecifiedError
|
||||||
|
|
||||||
|
OuterlyParameterError <|-- ZeroSpeedError
|
||||||
|
ZeroDivisionError <|-- ZeroSpeedError
|
||||||
|
|
||||||
|
OuterlyParameterError <|-- IllegalMinimumVolumeError
|
||||||
|
ValueError <|-- IllegalMinimumVolumeError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- FileFormatNotSupportedError
|
||||||
|
MusicreaterOuterlyError <|-- NoteBinaryDecodeError
|
||||||
|
|
||||||
|
NoteBinaryDecodeError <|-- SingleNoteDecodeError
|
||||||
|
NoteBinaryDecodeError <|-- NoteBinaryFileTypeError
|
||||||
|
NoteBinaryDecodeError <|-- NoteBinaryFileVerificationFailed
|
||||||
|
|
||||||
|
MusicreaterInnerlyError <|-- PluginDefineError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginInstanceNotFoundError
|
||||||
|
LookupError <|-- PluginInstanceNotFoundError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginAttributeNotFoundError
|
||||||
|
AttributeError <|-- PluginAttributeNotFoundError
|
||||||
|
|
||||||
|
PluginDefineError <|-- PluginMetainfoError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoTypeError
|
||||||
|
TypeError <|-- PluginMetainfoTypeError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoValueError
|
||||||
|
ValueError <|-- PluginMetainfoValueError
|
||||||
|
|
||||||
|
PluginMetainfoError <|-- PluginMetainfoNotFoundError
|
||||||
|
PluginAttributeNotFoundError <|-- PluginMetainfoNotFoundError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- PluginLoadError
|
||||||
|
|
||||||
|
PluginLoadError <|-- PluginNotFoundError
|
||||||
|
PluginLoadError <|-- PluginRegisteredError
|
||||||
|
|
||||||
|
MusicreaterOuterlyError <|-- PluginConfigRelatedError
|
||||||
|
|
||||||
|
PluginLoadError <|-- PluginConfigLoadError
|
||||||
|
PluginConfigRelatedError <|-- PluginConfigLoadError
|
||||||
|
|
||||||
|
PluginConfigRelatedError <|-- PluginConfigDumpError
|
||||||
67
docs/异常继承关系.svg
Normal file
67
docs/异常继承关系.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 652 KiB |
185
docs/编写插件.md
Normal file
185
docs/编写插件.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
|
||||||
|
# 教程:编写插件
|
||||||
|
|
||||||
|
> 版权所有 © 2026 金羿
|
||||||
|
> Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
睿乐组织 开发交流群 [861684859](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=fxNYIX_zKMgaO8X6K7pP7tHtLB7JRvdX&noverify=0&group_code=861684859)
|
||||||
|
Email [TriM-Organization@hotmail.com](mailto:TriM-Organization@hotmail.com)
|
||||||
|
|
||||||
|
```license
|
||||||
|
本示例模块开放授权,同时,本教程文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
本声明同样适用于所有直接转载的内容。
|
||||||
|
```
|
||||||
|
|
||||||
|
本教程文档的关联文件是:
|
||||||
|
- 全曲导入、音轨导入插件示例:[exp_importdata_plugin.py](../examples/exp_importdata_plugin.py)
|
||||||
|
- 导出曲目、导出音轨插件示例:[exp_dataexport_plugin.py](../examples/exp_dataexport_plugin.py)
|
||||||
|
|
||||||
|
## 新建文件
|
||||||
|
|
||||||
|
### 基础模块知识
|
||||||
|
|
||||||
|
首先,一个 **音·创 v3** 的插件应当存储于一个 Python 模块之中,也就是插件存在于可以被 import 语句引入的 module 中。
|
||||||
|
|
||||||
|
这就意味着,承载插件的模块本质上可以是多个 Python 的 `.py` 文件组成的,带有 `__init__.py` 的一个文件夹;
|
||||||
|
或者是一个简单的 `.py` 文件。
|
||||||
|
|
||||||
|
我们有这种共识:大家已经知道了模块的相关知识,后面的教程中你将会理解 **音·创 v3** 插件是什么东西,以及它和 Python 模块的关联和区别。
|
||||||
|
|
||||||
|
## 开始动笔
|
||||||
|
|
||||||
|
### 插件配置
|
||||||
|
|
||||||
|
如果插件需要配置项,则需进行此节。
|
||||||
|
|
||||||
|
从 `Musicreater.plugins` 导入 `PluginConfig` 类,并从此继承一个类,且须用 dataclass 装饰器来注册之:这就成为了一个插件的配置类。
|
||||||
|
_对于这个 `dataclass` “数据类”的使用方式,可以阅读 dataclass 的官方文档,或者直接在实例后面打个 `.`,让代码提示告诉你它能干什么_
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import PluginConfig
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编写插件
|
||||||
|
|
||||||
|
### 导入所需项目
|
||||||
|
|
||||||
|
首先在代码开头导入插件所需的东西。
|
||||||
|
|
||||||
|
在此之前,我们明确:一个 **音·创 v3** 的插件应当是一个继承自我们已经准备好的插件基类的**类**(缩句:插件是类);
|
||||||
|
在 **音·创 v3** 中,任何对音乐的操作,包括**导入**、**处理**、**导出**,都分为对 **整首曲目** 的操作和对 **单个音轨** 的操作。
|
||||||
|
|
||||||
|
在这里我们首先要对插件的类型进行判别,根据以下表格,可以得出所用功能对应的插件类型:
|
||||||
|
|
||||||
|
| 功能\对象 | 完整曲目 | 单个音轨 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| 导入数据 | `PluginTypes.FUNCTION_MUSIC_IMPORT` | `PluginTypes.FUNCTION_TRACK_IMPORT` |
|
||||||
|
| 数据处理 | `PluginTypes.FUNCTION_MUSIC_OPERATE` | `PluginTypes.FUNCTION_TRACK_OPERATE` |
|
||||||
|
| 导出数据 | `PluginTypes.FUNCTION_MUSIC_EXPORT` | `PluginTypes.FUNCTION_TRACK_EXPORT` |
|
||||||
|
| 支持库 | `PluginTypes.LIBRARY` | 同左 |
|
||||||
|
| 提供服务 | `PluginTypes.SERVICE` | 同左 |
|
||||||
|
|
||||||
|
也就是说,除了 `PluginTypes.LIBRARY` 和 `PluginTypes.SERVICE` 是不按照处理对象做区分的外,其余的这些都是对数据进行处理的插件、因此是做了处理数据的类型区分的。
|
||||||
|
|
||||||
|
我们对每个不同类型的插件都做了专用的抽象基类和一个装饰器函数。因为插件本身就是类,所以对应类型的插件只需要继承我们提供的抽象基类,并通过装饰器函数注册即可。(具体写法在后面会说哦)
|
||||||
|
|
||||||
|
也就是说,如果我们要写的是一个用来导入音乐的、对整个曲目进行处理的插件,那么就需要导入 `MusicInputPluginBase` 类和 `music_input_plugin` 函数以便后续调用。
|
||||||
|
|
||||||
|
同时,既然要导入内容,那就一并把 `PluginMetaInformation` 类和 `PluginTypes` 类也导入了吧,这是定义插件的信息所需要的。也就是说,这样的话,我们在导入部分就应该这样写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定义信息
|
||||||
|
|
||||||
|
接着我们来定义一个插件的信息并将其注册。
|
||||||
|
|
||||||
|
假设我们想要做一个对**整首曲目**进行**导入操作**的插件(参照前面举的例子),那么就需要继承 `MusicInputPluginBase` 类。
|
||||||
|
|
||||||
|
> 请注意:插件类的类名称不得以 `Base` 结尾,因为咱写的是插件,不是插件基类。
|
||||||
|
|
||||||
|
在插件的类的开头,需要用插件注册装饰函数来对插件类装饰。
|
||||||
|
|
||||||
|
```python
|
||||||
|
@music_input_plugin("example_import_plugin")
|
||||||
|
class xxx:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
我们这里对应插件类型的注册器是 `music_input_plugin` 函数。
|
||||||
|
在注册器函数后的参数,是这个插件的惟一识别码。不应与其他任何插件混淆。
|
||||||
|
通常,这个惟一识别码可以是这个插件的功能描述或者就是插件名。
|
||||||
|
|
||||||
|
接着编写这个插件,也即是此类。
|
||||||
|
|
||||||
|
每个插件的类必须包含一个用于指定插件元信息的 `metainfo` 属性。
|
||||||
|
如果插件是导入数据或者导出数据的插件,则必须包含一个 `supported_formats` 属性,用以声明插件所支持的数据格式。
|
||||||
|
|
||||||
|
对于插件的元信息,我们规定为一个 `PluginMetaInformation` 实例,这个实例需要的参数如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 注册插件
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
# 继承自对应类型的插件基类
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
|
||||||
|
# 插件元信息定义
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件", # 插件名称
|
||||||
|
author="金羿", # 插件作者
|
||||||
|
description="这是一个示例导入插件", # 插件描述
|
||||||
|
version=(0, 0, 1), # 插件版本
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT, # 插件类型,需要和注册的类型与继承的基类相符合
|
||||||
|
license="The Unlicense", # 插件许可证(可缺省,默认为字符串 `MIT License`)
|
||||||
|
dependencies=("something_convertion_library") # 插件对于其他插件的依赖项(可缺省,默认为空元组)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
对于实现导入导出数据的功能的插件,`supported_formats` 属性应当是一个元组,其中最好以全字母大写的字符串形式列出支持的**文件格式**或者**数据格式**(如果定义的时候没有大写的话,内部会自动处理成大写的,所以插件类的实例后面也会变成大写,这个时候,因为原定义是小写,有可能造成混淆,所以尽量不要写小写)。例如一个处理 `.mp4` 文件格式的插件可以这样写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@...
|
||||||
|
class ...:
|
||||||
|
...
|
||||||
|
|
||||||
|
supported_formats = ("MP4", "MPEG4", "MPEG-4")
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,你已经完成了插件基本信息的定义。
|
||||||
|
|
||||||
|
### 实现功能
|
||||||
|
|
||||||
|
根据插件的类型不同,每个插件都需要实现至少一个指定的方法。如下表所示:
|
||||||
|
|
||||||
|
| 插件功能 | 必须实现的方法 | 类型描述 | 可选实现的方法| 可选方法类型描述 | 备注 |
|
||||||
|
| ------ | ------------ | - | ----------- | - |----|
|
||||||
|
| 导入数据 | `loadbytes` | `Callable[[BinaryIO, Optional[PluginConfig]], T@插件处理对象类型]` | `load` | `Callable[[Path, Optional[PluginConfig]], T@插件处理对象类型]` | 如果 `load` 方法不单独实现,则会自动在打开文件后将文件 IO 变量传入 `loadbytes` 中并返回之 |
|
||||||
|
| 数据处理 | `process` | `Callable[[T@插件处理对象类型, Optional[PluginConfig]], T@插件处理对象类型]` | 无 | 无 | 根据处理对象是完整曲目(`SingleMusic`)还是单个音轨(`SingleTrack`),返回也是一样的。导入导出数据相关的插件亦皆同此说。 |
|
||||||
|
| 导出数据 | `stream_dump` | `Callable[[T@插件处理对象类型, Optional[PluginConfig]], Iterator[bytes]]` | `dump` | `Callable[[T@插件处理对象类型, Path, Optional[PluginConfig]], None]` | 若未重写 `dump` 方法,基类已提供默认实现:逐块写入 `stream_dump` 的结果 |
|
||||||
|
| 支持库 | 无 | 无 | 无 | 无 | 无 |
|
||||||
|
| 服务 | `serve` | `Callable[[Optional[PluginConfig]], None]` | 无 | 无 | 用于提供后台服务或一次性任务,由运行时调用(暂无设计思路,相关讨论请见[项目待办清单](../TO-DO.md#讨论)) |
|
||||||
|
|
||||||
|
也就是说,举个例子:一个**用于导入**的插件类必须定义一个 `loadbytes` 方法,用于从字节流中导入数据。可选是否单独实现 `load` 方法,如果不单独实现,则已经继承的方法会在调用时,直接通过打开文件后传参数给 `loadbytes` 来实现。
|
||||||
|
|
||||||
|
```python
|
||||||
|
@...
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
...
|
||||||
|
# 定义 loadbytes 方法,从字节流中导入数据
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
... # 这里写功能实现
|
||||||
|
|
||||||
|
# 插件可选地定义 load 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,一个插件的编写已经完成。
|
||||||
|
|
||||||
|
同时,如果有不清楚的地方,可以查看我们的[内置插件](../Musicreater/builtin_plugins/),说不定会给你一些启发。
|
||||||
222
docs/转换乐器对照表.md
Normal file
222
docs/转换乐器对照表.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<h1 align="center">音·创 Musicreater</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="128" height="128" src="https://gitee.com/TriM-Organization/Musicreater/raw/master/resources/msctIcon.png" >
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# 转换乐器对照表
|
||||||
|
|
||||||
|
**_注意!本文档中的对照表,版权归属于音·创作者,并按照本仓库根目录下 LICENSE.md 中规定开源_**
|
||||||
|
|
||||||
|
原表格请见[constant.py](../Musicreater/constants.py#176)
|
||||||
|
|
||||||
|
**_使用时请遵循协议规定_**
|
||||||
|
|
||||||
|
- 版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||||
|
- Copyright © 2025 Eilles & bgArray
|
||||||
|
|
||||||
|
* 音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||||
|
* The Licensor of Musicreater("this project") is Eilles, bgArray.
|
||||||
|
|
||||||
|
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||||
|
任何人皆可从以下地址获得本协议副本:https://gitee.com/EillesWan/YulvLicenses。
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
||||||
|
|
||||||
|
音·创 开发交流群 861684859\
|
||||||
|
Email TriM-Organization@hotmail.com\
|
||||||
|
若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
||||||
|
|
||||||
|
### 名词解释
|
||||||
|
|
||||||
|
| 名词 | 说明 |
|
||||||
|
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 音符名称 | 我的世界游戏内用于播放音乐的 `playsound` 指令所规定的 `Sound ID` |
|
||||||
|
| 音调偏移参数 | 在《我的世界》中,不同乐器的音域不同,对应的 `pitch` 值也不尽相同,该参数的解释请参考[文档说明](库的生成与功能文档.md#参数释义) |
|
||||||
|
|
||||||
|
# 乐音乐器
|
||||||
|
|
||||||
|
对照表版本:2023 0527
|
||||||
|
|
||||||
|
| Midi 乐器值 | 音符名称 | 音调偏移参数 |
|
||||||
|
| ----------- | ------------------- | ------------ |
|
||||||
|
| 0 | note.harp | 6 |
|
||||||
|
| 1 | note.harp | 6 |
|
||||||
|
| 2 | note.pling | 6 |
|
||||||
|
| 3 | note.harp | 6 |
|
||||||
|
| 4 | note.pling | 6 |
|
||||||
|
| 5 | note.pling | 6 |
|
||||||
|
| 6 | note.harp | 6 |
|
||||||
|
| 7 | note.harp | 6 |
|
||||||
|
| 8 | note.share | 7 |
|
||||||
|
| 9 | note.harp | 6 |
|
||||||
|
| 10 | note.didgeridoo | 8 |
|
||||||
|
| 11 | note.harp | 6 |
|
||||||
|
| 12 | note.xylophone | 4 |
|
||||||
|
| 13 | note.chime | 4 |
|
||||||
|
| 14 | note.harp | 6 |
|
||||||
|
| 15 | note.harp | 6 |
|
||||||
|
| 16 | note.bass | 8 |
|
||||||
|
| 17 | note.harp | 6 |
|
||||||
|
| 18 | note.harp | 6 |
|
||||||
|
| 19 | note.harp | 6 |
|
||||||
|
| 20 | note.harp | 6 |
|
||||||
|
| 21 | note.harp | 6 |
|
||||||
|
| 22 | note.harp | 6 |
|
||||||
|
| 23 | note.guitar | 7 |
|
||||||
|
| 24 | note.guitar | 7 |
|
||||||
|
| 25 | note.guitar | 7 |
|
||||||
|
| 26 | note.guitar | 7 |
|
||||||
|
| 27 | note.guitar | 7 |
|
||||||
|
| 28 | note.guitar | 7 |
|
||||||
|
| 29 | note.guitar | 7 |
|
||||||
|
| 30 | note.guitar | 7 |
|
||||||
|
| 31 | note.bass | 8 |
|
||||||
|
| 32 | note.bass | 8 |
|
||||||
|
| 33 | note.bass | 8 |
|
||||||
|
| 34 | note.bass | 8 |
|
||||||
|
| 35 | note.bass | 8 |
|
||||||
|
| 36 | note.bass | 8 |
|
||||||
|
| 37 | note.bass | 8 |
|
||||||
|
| 38 | note.bass | 8 |
|
||||||
|
| 39 | note.bass | 8 |
|
||||||
|
| 40 | note.harp | 6 |
|
||||||
|
| 41 | note.harp | 6 |
|
||||||
|
| 42 | note.harp | 6 |
|
||||||
|
| 43 | note.harp | 6 |
|
||||||
|
| 44 | note.iron_xylophone | 6 |
|
||||||
|
| 45 | note.guitar | 7 |
|
||||||
|
| 46 | note.harp | 6 |
|
||||||
|
| 47 | note.harp | 6 |
|
||||||
|
| 48 | note.guitar | 7 |
|
||||||
|
| 49 | note.guitar | 7 |
|
||||||
|
| 50 | note.bit | 6 |
|
||||||
|
| 51 | note.bit | 6 |
|
||||||
|
| 52 | note.harp | 6 |
|
||||||
|
| 53 | note.harp | 6 |
|
||||||
|
| 54 | note.bit | 6 |
|
||||||
|
| 55 | note.flute | 5 |
|
||||||
|
| 56 | note.flute | 5 |
|
||||||
|
| 57 | note.flute | 5 |
|
||||||
|
| 58 | note.flute | 5 |
|
||||||
|
| 59 | note.flute | 5 |
|
||||||
|
| 60 | note.flute | 5 |
|
||||||
|
| 61 | note.flute | 5 |
|
||||||
|
| 62 | note.flute | 5 |
|
||||||
|
| 63 | note.flute | 5 |
|
||||||
|
| 64 | note.bit | 6 |
|
||||||
|
| 65 | note.bit | 6 |
|
||||||
|
| 66 | note.bit | 6 |
|
||||||
|
| 67 | note.bit | 6 |
|
||||||
|
| 68 | note.flute | 5 |
|
||||||
|
| 69 | note.harp | 6 |
|
||||||
|
| 70 | note.harp | 6 |
|
||||||
|
| 71 | note.flute | 5 |
|
||||||
|
| 72 | note.flute | 5 |
|
||||||
|
| 73 | note.flute | 5 |
|
||||||
|
| 74 | note.harp | 6 |
|
||||||
|
| 75 | note.flute | 5 |
|
||||||
|
| 76 | note.harp | 6 |
|
||||||
|
| 77 | note.harp | 6 |
|
||||||
|
| 78 | note.harp | 6 |
|
||||||
|
| 79 | note.harp | 6 |
|
||||||
|
| 80 | note.bit | 6 |
|
||||||
|
| 81 | note.bit | 6 |
|
||||||
|
| 82 | note.bit | 6 |
|
||||||
|
| 83 | note.bit | 6 |
|
||||||
|
| 84 | note.bit | 6 |
|
||||||
|
| 85 | note.bit | 6 |
|
||||||
|
| 86 | note.bit | 6 |
|
||||||
|
| 87 | note.bit | 6 |
|
||||||
|
| 88 | note.bit | 6 |
|
||||||
|
| 89 | note.bit | 6 |
|
||||||
|
| 90 | note.bit | 6 |
|
||||||
|
| 91 | note.bit | 6 |
|
||||||
|
| 92 | note.bit | 6 |
|
||||||
|
| 93 | note.bit | 6 |
|
||||||
|
| 94 | note.bit | 6 |
|
||||||
|
| 95 | note.bit | 6 |
|
||||||
|
| 96 | note.bit | 6 |
|
||||||
|
| 97 | note.bit | 6 |
|
||||||
|
| 98 | note.bit | 6 |
|
||||||
|
| 99 | note.bit | 6 |
|
||||||
|
| 100 | note.bit | 6 |
|
||||||
|
| 101 | note.bit | 6 |
|
||||||
|
| 102 | note.bit | 6 |
|
||||||
|
| 103 | note.bit | 6 |
|
||||||
|
| 104 | note.harp | 6 |
|
||||||
|
| 105 | note.banjo | 6 |
|
||||||
|
| 106 | note.harp | 6 |
|
||||||
|
| 107 | note.harp | 6 |
|
||||||
|
| 108 | note.harp | 6 |
|
||||||
|
| 109 | note.harp | 6 |
|
||||||
|
| 110 | note.harp | 6 |
|
||||||
|
| 111 | note.guitar | 7 |
|
||||||
|
| 112 | note.harp | 6 |
|
||||||
|
| 113 | note.bell | 4 |
|
||||||
|
| 114 | note.harp | 6 |
|
||||||
|
| 115 | note.cow_bell | 5 |
|
||||||
|
| 116 | note.bd | 7 |
|
||||||
|
| 117 | note.bass | 8 |
|
||||||
|
| 118 | note.bit | 6 |
|
||||||
|
| 119 | note.bd | 7 |
|
||||||
|
| 120 | note.guitar | 7 |
|
||||||
|
| 121 | note.harp | 6 |
|
||||||
|
| 122 | note.harp | 6 |
|
||||||
|
| 123 | note.harp | 6 |
|
||||||
|
| 124 | note.harp | 6 |
|
||||||
|
| 125 | note.hat | 7 |
|
||||||
|
| 126 | note.bd | 7 |
|
||||||
|
| 127 | note.snare | 7 |
|
||||||
|
|
||||||
|
# 打击乐器
|
||||||
|
|
||||||
|
| Midi 打击乐器值 | 音符名称 | 音调偏移参数 |
|
||||||
|
| --------------- | ------------------- | ------------ |
|
||||||
|
| 34 | note.bd | 7 |
|
||||||
|
| 35 | note.bd | 7 |
|
||||||
|
| 36 | note.hat | 7 |
|
||||||
|
| 37 | note.snare | 7 |
|
||||||
|
| 38 | note.snare | 7 |
|
||||||
|
| 39 | note.snare | 7 |
|
||||||
|
| 40 | note.hat | 7 |
|
||||||
|
| 41 | note.snare | 7 |
|
||||||
|
| 42 | note.hat | 7 |
|
||||||
|
| 43 | note.snare | 7 |
|
||||||
|
| 44 | note.snare | 7 |
|
||||||
|
| 45 | note.bell | 4 |
|
||||||
|
| 46 | note.snare | 7 |
|
||||||
|
| 47 | note.snare | 7 |
|
||||||
|
| 48 | note.bell | 4 |
|
||||||
|
| 49 | note.hat | 7 |
|
||||||
|
| 50 | note.bell | 4 |
|
||||||
|
| 51 | note.bell | 4 |
|
||||||
|
| 52 | note.bell | 4 |
|
||||||
|
| 53 | note.bell | 4 |
|
||||||
|
| 54 | note.bell | 4 |
|
||||||
|
| 55 | note.bell | 4 |
|
||||||
|
| 56 | note.snare | 7 |
|
||||||
|
| 57 | note.hat | 7 |
|
||||||
|
| 58 | note.chime | 4 |
|
||||||
|
| 59 | note.iron_xylophone | 6 |
|
||||||
|
| 60 | note.bd | 7 |
|
||||||
|
| 61 | note.bd | 7 |
|
||||||
|
| 62 | note.xylophone | 4 |
|
||||||
|
| 63 | note.xylophone | 4 |
|
||||||
|
| 64 | note.xylophone | 4 |
|
||||||
|
| 65 | note.hat | 7 |
|
||||||
|
| 66 | note.bell | 4 |
|
||||||
|
| 67 | note.bell | 4 |
|
||||||
|
| 68 | note.hat | 7 |
|
||||||
|
| 69 | note.hat | 7 |
|
||||||
|
| 70 | note.flute | 5 |
|
||||||
|
| 71 | note.flute | 5 |
|
||||||
|
| 72 | note.hat | 7 |
|
||||||
|
| 73 | note.hat | 7 |
|
||||||
|
| 74 | note.xylophone | 4 |
|
||||||
|
| 75 | note.hat | 7 |
|
||||||
|
| 76 | note.hat | 7 |
|
||||||
|
| 77 | note.xylophone | 4 |
|
||||||
|
| 78 | note.xylophone | 4 |
|
||||||
|
| 79 | note.bell | 4 |
|
||||||
|
| 80 | note.bell | 4 |
|
||||||
149
docs/音乐序列文件格式.md
Normal file
149
docs/音乐序列文件格式.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# 音乐序列文件格式(已过时)
|
||||||
|
|
||||||
|
音·创 库的音符序列文件格式包含两种,一种是常规的音乐序列存储采用的 MSQ 格式,另一种是为了流式读取音符而采用的 FSQ 格式。
|
||||||
|
|
||||||
|
## MSQ 数据格式
|
||||||
|
|
||||||
|
MSQ 格式是 音·创 库存储音符序列的一种字节码格式,取自 **M**usic**S**e**Q**uence 类之名。
|
||||||
|
|
||||||
|
现在 音·创 库及其上游软件使用的是在 第二版 的基础上增设校验功能的 MSQ 第三版。
|
||||||
|
|
||||||
|
### MSQ 第三版
|
||||||
|
|
||||||
|
第三版 MSQ 格式的码头是 `MSQ!` ,这一版中,所有的**字符串**皆以 _**GB18030**_ 编码进行编解码,**数值**皆是以 _**大端字节序**_ 存储的无符号整数。
|
||||||
|
|
||||||
|
码头是字节码前四个字节的内容,这一部分内容是可读的 ASCII 字串。因此,第三版的字节码中前四个字节的内容必为 `MSQ!`。
|
||||||
|
|
||||||
|
第二版 MSQ 取 `MSQ@` 作为码头是因为美式键盘上 @ 是 Shift+2 键 按下取得的,故代表 MSQ 第二版。
|
||||||
|
|
||||||
|
你猜为什么第三版是 `MSQ!`。
|
||||||
|
|
||||||
|
#### 元信息
|
||||||
|
|
||||||
|
| 信息名称 | 西文代号 | 位长(多少个 0 或 1) | 支持说明 |
|
||||||
|
| ------------------------------ | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **码头** | _无_ | 32 位 | 值为 `MSQ!` |
|
||||||
|
| **音乐名称长度** | music_name_length | 6 位 | 支持数值 0~63 |
|
||||||
|
| **最小音量** | minimum_volume | 10 位 | 支持数值 0~1023,注意,这里每个 1 代表最小音量的 0.001 个单位,即取值是此处表示数字的千分倍 |
|
||||||
|
| **是否启用高精度音符时间控制** | enable_high_precision_time | 1 位 | 1 是启用,反之同理 |
|
||||||
|
| **总音调偏移** | music_deviation | 15 位 | 支持数值 -16383~16383,这里也是表示三位小数的,和最小音量一样。这里 15 位中的第一位(从左往右)是正负标记,若为 1 则为负数,反之为正数,后面的 14 位是数值 |
|
||||||
|
| **音乐名称** | music_name | 依据先前定义 | 最多可支持 31 个中文字符 或 63 个西文字符,其长度取决于先前获知的 “音乐名称长度” 的定义 |
|
||||||
|
|
||||||
|
在这一元信息中,**音乐名称长度**和**最小音量**合计共 2 字节;**高精度音符时间控制启用**和**总音调偏移**合计共 2 字节;因此,除**音乐名称**为任意长度,前四字节内容均为固定。
|
||||||
|
|
||||||
|
#### 音符序列
|
||||||
|
|
||||||
|
每个序列前 4 字节为一个用以表示当前通道中音符数量的值,也就是**通道音符数**(notes_count)。也即是说,一个通道内的音符可以是 0~4294967295 个。
|
||||||
|
|
||||||
|
在这之后,就是这些数量的音符了,其中每个音符的信息存储方式如下
|
||||||
|
|
||||||
|
| 信息名称 | 西文代号 | 位长 | 支持说明 |
|
||||||
|
| ---------------------------- | ------------------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **乐器名称长度** | name_length | 6 位 | 支持数值 0~63 |
|
||||||
|
| **Midi 音高** | note_pitch | 7 位 | 支持数值 0~127 |
|
||||||
|
| **开始时刻** | start_tick | 17 位 | 单位 二十分之一秒,即约为 1 命令刻;支持数值 0~131071 即 109.22583 分钟 合 1.8204305 小时 |
|
||||||
|
| **音符持续刻数** | duration | 17 位 | 同上 |
|
||||||
|
| **是否作为打击乐器** | percussive | 1 位 | 1 是启用,反之同理 |
|
||||||
|
| **响度(力度)** | velocity | 7 位 | 支持数值 0~127 |
|
||||||
|
| **是否启用声像位移** | is_displacement_included | 1 位 | 1 是启用,反之同理 |
|
||||||
|
| **时间精度提升值**(非必含) | high_time_precision | 8 位 | 支持数值 0~255,若在 元信息 中启用**高精度音符时间控制**,则此值启用,代表音符时间控制精度偏移,此值每增加 1,则音符开始时刻向后增加 1/1250 秒 |
|
||||||
|
| **乐器名称** | sound_name | 依据先前定义 | 最多可支持 31 个中文字符 或 63 个西文字符,其长度取决于先前获知的 “乐器名称长度” 的定义 |
|
||||||
|
| **声像位移**(非必含) | position_displacement | 共三个值,每个值 16 位 共 48 位 | 若前述**是否启用声像位移**已启用,则此值启用;三个值分别代表 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 校验值,以 全曲音符总数 作为种子值。
|
||||||
113
examples/exp_dataexport_plugin.py
Normal file
113
examples/exp_dataexport_plugin.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
示例插件:导出成其他文件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
本示例模块开放授权,本文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
本声明同样适用于所有直接转载的内容。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_output_plugin,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
track_output_plugin,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleExportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
|
||||||
|
def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
|
||||||
|
for k, v in self.to_dict().items():
|
||||||
|
yield k, v
|
||||||
|
|
||||||
|
|
||||||
|
@music_output_plugin("convert_music_to_something")
|
||||||
|
class ExampleExportMusicPlugin(MusicOutputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导出插件·甲",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导出插件,演示整首曲目导出到其他文件格式的插件的编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_EXPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
dependencies=("something_convertion_library"),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "EXAMPLE_FORMAT")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def something_data_convert(*args) -> bytes:
|
||||||
|
return b"This is something wonderful"
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleMusic, config: ExampleExportConfig | None
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if not config:
|
||||||
|
config = ExampleExportConfig(True)
|
||||||
|
for cfg in config:
|
||||||
|
yield self.something_data_convert(cfg)
|
||||||
|
|
||||||
|
# 插件可选地定义 dump 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def dump(
|
||||||
|
self, data: SingleMusic, file_path: Path, config: ExampleExportConfig | None
|
||||||
|
):
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
@track_output_plugin("convert_track_to_something")
|
||||||
|
class ExampleImportTrackPlugin(TrackOutputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导出插件·乙",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导出插件,演示从音轨导出的其他格式的插件的编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_TRACK_EXPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
# 可以缺省依赖,如果不需要的话
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleTrack, config: ExampleExportConfig | None
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if not config:
|
||||||
|
config = ExampleExportConfig(True)
|
||||||
|
for cfg in config:
|
||||||
|
yield ExampleExportMusicPlugin.something_data_convert(cfg)
|
||||||
|
|
||||||
|
# 可以缺省 dump 方法,会直接用上面展示过的方法输出
|
||||||
97
examples/exp_importdata_plugin.py
Normal file
97
examples/exp_importdata_plugin.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
示例插件:导入音符数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
本示例模块开放授权,本文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
本声明同样适用于所有直接转载的内容。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import BinaryIO, Optional
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_input_plugin,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
track_input_plugin,
|
||||||
|
TrackInputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
class ExampleImportMusicPlugin(MusicInputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件·甲",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导入插件,演示导入到全曲的插件编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
dependencies=("something_convertion_library"),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "EXAMPLE_FORMAT")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
return SingleMusic()
|
||||||
|
|
||||||
|
# 插件可选地定义 load 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
@track_input_plugin("something_convert_to_track")
|
||||||
|
class ExampleImportTrackPlugin(TrackInputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件·乙",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导入插件,演示导入到音轨的插件编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_TRACK_IMPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
# 可以缺省依赖,如果不需要的话
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
return SingleTrack()
|
||||||
|
|
||||||
|
# 可以缺省 load 方法,会直接用上面展示过的方法读取数据
|
||||||
1254
old-things/Musicreater/experiment.py
Normal file
1254
old-things/Musicreater/experiment.py
Normal file
File diff suppressed because it is too large
Load Diff
265
old-things/Musicreater/magicmain.py
Normal file
265
old-things/Musicreater/magicmain.py
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# -*- 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]
|
||||||
76
old-things/Musicreater/old_exceptions.py
Normal file
76
old-things/Musicreater/old_exceptions.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# -*- 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 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
|
||||||
|
# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息
|
||||||
|
# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题
|
||||||
|
|
||||||
|
|
||||||
148
old-things/Musicreater/old_init.py
Normal file
148
old-things/Musicreater/old_init.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# -*- 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 .old_main import (
|
||||||
|
MusicSequence,
|
||||||
|
MidiConvert,
|
||||||
|
# 字典键
|
||||||
|
MIDI_PROGRAM,
|
||||||
|
MIDI_PAN,
|
||||||
|
MIDI_VOLUME,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .subclass import (
|
||||||
|
MineNote,
|
||||||
|
MineCommand,
|
||||||
|
SingleNoteBox,
|
||||||
|
ProgressBarStyle,
|
||||||
|
mctick2timestr,
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .old_utils import (
|
||||||
|
# 兼容性函数
|
||||||
|
load_decode_musicsequence_metainfo,
|
||||||
|
load_decode_msq_flush_release,
|
||||||
|
load_decode_fsq_flush_release,
|
||||||
|
# 工具函数
|
||||||
|
guess_deviation,
|
||||||
|
midi_inst_to_mc_sound,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read import (
|
||||||
|
MIDI_DEFAULT_VOLUME_VALUE,
|
||||||
|
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||||
|
volume_2_distance_straight as velocity_2_distance_straight,
|
||||||
|
volume_2_distance_natural as velocity_2_distance_natural,
|
||||||
|
panning_2_rotation_linear,
|
||||||
|
panning_2_rotation_trigonometric,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.constants import (
|
||||||
|
# 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,
|
||||||
|
)
|
||||||
1870
old-things/Musicreater/old_main.py
Normal file
1870
old-things/Musicreater/old_main.py
Normal file
File diff suppressed because it is too large
Load Diff
73
old-things/Musicreater/old_plugin/__init__.py
Normal file
73
old-things/Musicreater/old_plugin/__init__.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/addonpack/__init__.py
Normal file
30
old-things/Musicreater/old_plugin/addonpack/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/addonpack/main.py
Normal file
684
old-things/Musicreater/old_plugin/addonpack/main.py
Normal file
@@ -0,0 +1,684 @@
|
|||||||
|
# -*- 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 ...old_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.mcfunction_command_string 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.mcfunction_command_string
|
||||||
|
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
old-things/Musicreater/old_plugin/archive.py
Normal file
104
old-things/Musicreater/old_plugin/archive.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# -*- 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
|
||||||
20
old-things/Musicreater/old_plugin/bdxfile/__init__.py
Normal file
20
old-things/Musicreater/old_plugin/bdxfile/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/bdxfile/main.py
Normal file
220
old-things/Musicreater/old_plugin/bdxfile/main.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# -*- 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 ...old_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
|
||||||
92
old-things/Musicreater/old_plugin/main.py
Normal file
92
old-things/Musicreater/old_plugin/main.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# -*- 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
|
||||||
|
# """进度条样式组"""
|
||||||
30
old-things/Musicreater/old_plugin/mcstructfile/__init__.py
Normal file
30
old-things/Musicreater/old_plugin/mcstructfile/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/mcstructfile/main.py
Normal file
298
old-things/Musicreater/old_plugin/mcstructfile/main.py
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
# -*- 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 ...old_main import MidiConvert
|
||||||
|
from ...subclass import MineCommand
|
||||||
|
from Musicreater.builtin_plugins.commands_to_structure.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
|
||||||
117
old-things/Musicreater/old_plugin/noteblock.py
Normal file
117
old-things/Musicreater/old_plugin/noteblock.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# -*- 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 ..old_exceptions import NotDefineProgramError, ZeroSpeedError
|
||||||
|
from ..old_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
old-things/Musicreater/old_plugin/schematic.py
Normal file
18
old-things/Musicreater/old_plugin/schematic.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/schematic/__init__.py
Normal file
22
old-things/Musicreater/old_plugin/schematic/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/schematic/main.py
Normal file
14
old-things/Musicreater/old_plugin/schematic/main.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/websocket/__init__.py
Normal file
21
old-things/Musicreater/old_plugin/websocket/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- 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
old-things/Musicreater/old_plugin/websocket/main.py
Normal file
142
old-things/Musicreater/old_plugin/websocket/main.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# -*- 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 ...old_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_ticks=i,
|
||||||
|
total_ticks=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.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())
|
||||||
73
old-things/Musicreater/old_types.py
Normal file
73
old-things/Musicreater/old_types.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# -*- 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,],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
775
old-things/Musicreater/old_utils.py
Normal file
775
old-things/Musicreater/old_utils.py
Normal file
@@ -0,0 +1,775 @@
|
|||||||
|
# -*- 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 random
|
||||||
|
|
||||||
|
# from io import BytesIO
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
BinaryIO,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Generator,
|
||||||
|
List,
|
||||||
|
Literal,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
|
from xxhash import xxh3_64, xxh3_128, xxh32
|
||||||
|
|
||||||
|
from Musicreater.constants import (
|
||||||
|
MC_INSTRUMENT_BLOCKS_TABLE,
|
||||||
|
MC_PITCHED_INSTRUMENT_LIST,
|
||||||
|
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||||
|
MM_INSTRUMENT_RANGE_TABLE,
|
||||||
|
)
|
||||||
|
from Musicreater.exceptions import SingleNoteDecodeError
|
||||||
|
from Musicreater._utils import enumerated_stuffcopy_dictionary
|
||||||
|
|
||||||
|
from Musicreater.builtin_plugins.midi_read.utils import midi_inst_to_mc_sound
|
||||||
|
|
||||||
|
from .subclass import MineNote, mctick2timestr, SingleNoteBox
|
||||||
|
from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType
|
||||||
|
|
||||||
|
|
||||||
|
def inst_to_sould_with_deviation(
|
||||||
|
instrumentID: int,
|
||||||
|
reference_table: MidiInstrumentTableType,
|
||||||
|
default_instrument: str = "note.flute",
|
||||||
|
) -> Tuple[str, int]:
|
||||||
|
"""
|
||||||
|
返回midi的乐器ID对应的我的世界乐器名,对于音域转换算法,如下:
|
||||||
|
2**( ( msg.note - 60 - X ) / 12 ) 即为MC的音高
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
instrumentID: int
|
||||||
|
midi的乐器ID
|
||||||
|
reference_table: Dict[int, Tuple[str, int]]
|
||||||
|
转换乐器参照表
|
||||||
|
default_instrument: str
|
||||||
|
查无此乐器时的替换乐器
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple(str我的世界乐器名, int转换算法中的偏移量)
|
||||||
|
"""
|
||||||
|
sound_id = midi_inst_to_mc_sound(
|
||||||
|
instrumentID=instrumentID,
|
||||||
|
reference_table=reference_table,
|
||||||
|
default_instrument=default_instrument,
|
||||||
|
)
|
||||||
|
return sound_id, MM_INSTRUMENT_DEVIATION_TABLE.get(
|
||||||
|
sound_id,
|
||||||
|
MM_INSTRUMENT_DEVIATION_TABLE.get(
|
||||||
|
default_instrument, 6 if sound_id in MC_PITCHED_INSTRUMENT_LIST else -1
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def midi_msgs_to_minenote_using_kami_respack(
|
||||||
|
inst_: int, # 乐器编号
|
||||||
|
note_: int,
|
||||||
|
percussive_: bool, # 是否作为打击乐器启用
|
||||||
|
volume_: int,
|
||||||
|
velocity_: int,
|
||||||
|
panning_: int,
|
||||||
|
start_time_: int,
|
||||||
|
duration_: int,
|
||||||
|
play_speed: float,
|
||||||
|
midi_reference_table: MidiInstrumentTableType,
|
||||||
|
volume_processing_method_: Callable[[float], float],
|
||||||
|
panning_processing_method_: FittingFunctionType,
|
||||||
|
note_table_replacement: Dict[str, str] = {},
|
||||||
|
lyric_line: str = "",
|
||||||
|
) -> MineNote:
|
||||||
|
"""
|
||||||
|
将Midi信息转为我的世界音符对象,使用神羽资源包兼容格式
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
inst_: int
|
||||||
|
乐器编号
|
||||||
|
note_: int
|
||||||
|
音高编号(音符编号)
|
||||||
|
percussive_: bool
|
||||||
|
是否作为打击乐器启用
|
||||||
|
volume_: int
|
||||||
|
音量
|
||||||
|
velocity_: int
|
||||||
|
力度
|
||||||
|
panning_: int
|
||||||
|
声相偏移
|
||||||
|
start_time_: int
|
||||||
|
音符起始时间(微秒)
|
||||||
|
duration_: int
|
||||||
|
音符持续时间(微秒)
|
||||||
|
play_speed: float
|
||||||
|
曲目播放速度
|
||||||
|
midi_reference_table: Dict[int, str]
|
||||||
|
转换对照表
|
||||||
|
volume_processing_method_: Callable[[float], float]
|
||||||
|
音量处理函数
|
||||||
|
panning_processing_method_: Callable[[float], float]
|
||||||
|
立体声相偏移处理函数
|
||||||
|
note_table_replacement: Dict[str, str]
|
||||||
|
音符替换表,定义 Minecraft 音符字串的替换
|
||||||
|
lyric_line: str
|
||||||
|
该音符的歌词
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
MineNote
|
||||||
|
我的世界音符对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
using_original = False
|
||||||
|
if not percussive_ and (0 <= inst_ <= 119):
|
||||||
|
mc_sound_ID = "{}{}.{}".format(
|
||||||
|
# inst_, "c" if (duration_ > 1000_000) and (inst_ in (0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53)) else "d", note_
|
||||||
|
inst_,
|
||||||
|
"d",
|
||||||
|
note_,
|
||||||
|
)
|
||||||
|
elif percussive_ and (27 <= inst_ <= 87):
|
||||||
|
mc_sound_ID = "-1d.{}".format(inst_)
|
||||||
|
else:
|
||||||
|
using_original = True
|
||||||
|
mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
inst_,
|
||||||
|
midi_reference_table,
|
||||||
|
"note.bd" if percussive_ else "note.flute",
|
||||||
|
)
|
||||||
|
|
||||||
|
return MineNote(
|
||||||
|
mc_sound_name=note_table_replacement.get(mc_sound_ID, mc_sound_ID),
|
||||||
|
midi_pitch=note_ if using_original else 1,
|
||||||
|
midi_velocity=velocity_,
|
||||||
|
start_time=(tk := int(start_time_ / float(play_speed) / 50000)),
|
||||||
|
last_time=round(duration_ / float(play_speed) / 50000),
|
||||||
|
mass_precision_time=round((start_time_ / float(play_speed) - tk * 50000) / 800),
|
||||||
|
is_percussion=percussive_,
|
||||||
|
distance=volume_processing_method_(volume_),
|
||||||
|
azimuth=(panning_processing_method_(panning_), 0),
|
||||||
|
extra_information={
|
||||||
|
"USING_ORIGINAL_SOUND": using_original, # 判断 extra_information 中是否有 USING_ORIGINAL_SOUND 键是判断是否使用神羽资源包解析的一个显著方法
|
||||||
|
"INST_VALUE": note_ if percussive_ else inst_,
|
||||||
|
"NOTE_VALUE": inst_ if percussive_ else note_,
|
||||||
|
"LYRIC_TEXT": lyric_line,
|
||||||
|
"VOLUME_VALUE": volume_,
|
||||||
|
"PIN_VALUE": panning_,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# def single_note_to_minenote(
|
||||||
|
# note_: SingleNote,
|
||||||
|
# reference_table: MidiInstrumentTableType,
|
||||||
|
# play_speed: float = 0,
|
||||||
|
# volume_processing_method: Callable[[float], float] = natural_curve,
|
||||||
|
# ) -> MineNote:
|
||||||
|
# """
|
||||||
|
# 将音符转为我的世界音符对象
|
||||||
|
# :param note_:SingleNote 音符对象
|
||||||
|
# :param reference_table:Dict[int, str] 转换对照表
|
||||||
|
# :param play_speed:float 播放速度
|
||||||
|
# :param volume_proccessing_method:Callable[[float], float] 音量处理函数
|
||||||
|
|
||||||
|
# :return MineNote我的世界音符对象
|
||||||
|
# """
|
||||||
|
# mc_sound_ID = midi_inst_to_mc_sound(
|
||||||
|
# note_.inst,
|
||||||
|
# reference_table,
|
||||||
|
# "note.bd" if note_.percussive else "note.flute",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# mc_distance_volume = volume_processing_method(note_.velocity)
|
||||||
|
|
||||||
|
# return MineNote(
|
||||||
|
# mc_sound_name=mc_sound_ID,
|
||||||
|
# midi_pitch=note_.pitch,
|
||||||
|
# midi_velocity=note_.velocity,
|
||||||
|
# start_time=round(note_.start_time / float(play_speed) / 50),
|
||||||
|
# last_time=round(note_.duration / float(play_speed) / 50),
|
||||||
|
# is_percussion=note_.percussive,
|
||||||
|
# displacement=(0, mc_distance_volume, 0),
|
||||||
|
# extra_information=note_.extra_info,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
def is_in_diapason(note_pitch: float, instrument: str) -> bool:
|
||||||
|
"""音是否在乐器可演奏之范围之内"""
|
||||||
|
note_range = MM_INSTRUMENT_RANGE_TABLE.get(instrument, ((-1, 128), 0))[0]
|
||||||
|
return note_pitch >= note_range[0] and note_pitch <= note_range[1]
|
||||||
|
|
||||||
|
|
||||||
|
def is_note_in_diapason(
|
||||||
|
note_: MineNote,
|
||||||
|
) -> bool:
|
||||||
|
"""一个 MineNote 音符是否在其乐器可演奏之范围之内"""
|
||||||
|
note_range = MM_INSTRUMENT_RANGE_TABLE.get(note_.sound_name, ((-1, 128), 0))[0]
|
||||||
|
return note_.note_pitch >= note_range[0] and note_.note_pitch <= note_range[1]
|
||||||
|
|
||||||
|
|
||||||
|
def note_to_redstone_block(
|
||||||
|
note_: MineNote, random_select: bool = False, default_block: str = "air"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
将我的世界乐器名改作音符盒所需的对应方块名称
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
note_: MineNote
|
||||||
|
音符类
|
||||||
|
random_select: bool
|
||||||
|
是否随机选取对应方块
|
||||||
|
default_block: str
|
||||||
|
查表查不到怎么办?默认一个!
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str方块名称
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
# return SingleNoteBox() # TO-DO
|
||||||
|
|
||||||
|
|
||||||
|
def soundID_to_blockID(
|
||||||
|
sound_id: str, random_select: bool = False, default_block: str = "air"
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
将我的世界乐器名改作音符盒所需的对应方块名称
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
sound_id: str
|
||||||
|
将我的世界乐器名
|
||||||
|
random_select: bool
|
||||||
|
是否随机选取对应方块
|
||||||
|
default_block: str
|
||||||
|
查表查不到怎么办?默认一个!
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str方块名称
|
||||||
|
"""
|
||||||
|
if random_select:
|
||||||
|
return random.choice(MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,)))
|
||||||
|
else:
|
||||||
|
return MC_INSTRUMENT_BLOCKS_TABLE.get(sound_id, (default_block,))[0]
|
||||||
|
|
||||||
|
|
||||||
|
def load_decode_musicsequence_metainfo(
|
||||||
|
buffer_in: BinaryIO,
|
||||||
|
) -> Tuple[str, float, float, bool, int, bool]:
|
||||||
|
"""
|
||||||
|
以流的方式解码音乐序列元信息
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
buffer_in: BytesIO
|
||||||
|
MSQ格式的字节流
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[str, float, float, bool, int]
|
||||||
|
音乐名称,最小音量,音调偏移,是否启用高精度,最后的流指针位置,是否使用新的音符存储格式(MineNote第三版)
|
||||||
|
|
||||||
|
"""
|
||||||
|
note_format_v3 = buffer_in.read(4) in (b"MSQ$", b"FSQ$")
|
||||||
|
group_1 = int.from_bytes(buffer_in.read(2), "big")
|
||||||
|
group_2 = int.from_bytes(buffer_in.read(2), "big", signed=False)
|
||||||
|
|
||||||
|
# high_quantity = bool(group_2 & 0b1000000000000000)
|
||||||
|
# print(group_2, high_quantity)
|
||||||
|
|
||||||
|
music_name_ = buffer_in.read(stt_index := (group_1 >> 10)).decode("GB18030")
|
||||||
|
|
||||||
|
return (
|
||||||
|
music_name_,
|
||||||
|
(group_1 & 0b1111111111) / 1000,
|
||||||
|
(
|
||||||
|
(-1 if group_2 & 0b100000000000000 else 1)
|
||||||
|
* (group_2 & 0b11111111111111)
|
||||||
|
/ 1000
|
||||||
|
),
|
||||||
|
bool(group_2 & 0b1000000000000000),
|
||||||
|
stt_index + 8,
|
||||||
|
note_format_v3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_decode_fsq_flush_release(
|
||||||
|
buffer_in: BinaryIO,
|
||||||
|
starter_index: int,
|
||||||
|
high_quantity_note: bool,
|
||||||
|
new_note_format: bool,
|
||||||
|
) -> Generator[MineNote, Any, None]:
|
||||||
|
"""
|
||||||
|
以流的方式解码FSQ音乐序列的音符序列并流式返回
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
buffer_in : BytesIO
|
||||||
|
输入的MSQ格式二进制字节流
|
||||||
|
starter_index : int
|
||||||
|
字节流中,音符序列的起始索引
|
||||||
|
high_quantity_note : bool
|
||||||
|
是否启用高精度音符解析
|
||||||
|
new_note_format : bool
|
||||||
|
是否启用新音符格式解析(MineNote第三版)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Generator[MineNote, Any, None]
|
||||||
|
以流的方式返回解码后的音符序列,每次返回一个元组
|
||||||
|
元组中包含两个元素,第一个元素为音符所在通道的索引,第二个元素为音符对象
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
MusicSequenceDecodeError
|
||||||
|
当解码过程中出现错误,抛出异常
|
||||||
|
"""
|
||||||
|
|
||||||
|
if buffer_in.tell() != starter_index:
|
||||||
|
buffer_in.seek(starter_index, 0)
|
||||||
|
|
||||||
|
total_note_count = int.from_bytes(
|
||||||
|
buffer_in.read(5),
|
||||||
|
"big",
|
||||||
|
signed=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(total_note_count):
|
||||||
|
if (i % 100 == 0) and i:
|
||||||
|
buffer_in.read(4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_note_bytes_length = (
|
||||||
|
12 + high_quantity_note + ((_first_byte := (buffer_in.read(1)))[0] >> 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
yield (
|
||||||
|
MineNote.decode(
|
||||||
|
code_buffer=_first_byte + buffer_in.read(_note_bytes_length),
|
||||||
|
is_high_time_precision=high_quantity_note,
|
||||||
|
)
|
||||||
|
if new_note_format
|
||||||
|
else decode_note_bytes_v2(
|
||||||
|
code_buffer_bytes=_first_byte + buffer_in.read(_note_bytes_length),
|
||||||
|
is_high_time_precision=high_quantity_note,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as _err:
|
||||||
|
# print(bytes_buffer_in[stt_index:end_index])
|
||||||
|
raise SingleNoteDecodeError(
|
||||||
|
"所截取的音符码之首个字节:",
|
||||||
|
_first_byte,
|
||||||
|
) from _err
|
||||||
|
|
||||||
|
|
||||||
|
def load_decode_msq_flush_release(
|
||||||
|
buffer_in: BinaryIO,
|
||||||
|
starter_index: int,
|
||||||
|
high_quantity_note: bool,
|
||||||
|
new_note_format: bool,
|
||||||
|
) -> Generator[Tuple[int, MineNote], Any, None]:
|
||||||
|
"""以流的方式解码MSQ音乐序列的音符序列并流式返回
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
buffer_in : BytesIO
|
||||||
|
输入的MSQ格式二进制字节流
|
||||||
|
starter_index : int
|
||||||
|
字节流中,音符序列的起始索引
|
||||||
|
high_quantity_note : bool
|
||||||
|
是否启用高精度音符解析
|
||||||
|
new_note_format : bool
|
||||||
|
是否启用新音符格式解析(MineNote第三版)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Generator[Tuple[int, MineNote], Any, None]
|
||||||
|
以流的方式返回解码后的音符序列,每次返回一个元组
|
||||||
|
元组中包含两个元素,第一个元素为音符所在通道的索引,第二个元素为音符对象
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
MusicSequenceDecodeError
|
||||||
|
当解码过程中出现错误,抛出异常
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# _total_verify = xxh3_64(buffer_in.read(starter_index), seed=total_note_count)
|
||||||
|
|
||||||
|
# buffer_in.seek(starter_index, 0)
|
||||||
|
if buffer_in.tell() != starter_index:
|
||||||
|
buffer_in.seek(starter_index, 0)
|
||||||
|
_bytes_buffer_in = buffer_in.read()
|
||||||
|
# int.from_bytes(_bytes_buffer_in[0 : 4], "big")
|
||||||
|
|
||||||
|
_now_channel_starter_index = 0
|
||||||
|
|
||||||
|
_total_note_count = 1
|
||||||
|
|
||||||
|
_channel_infos = enumerated_stuffcopy_dictionary(
|
||||||
|
staff={"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
||||||
|
)
|
||||||
|
|
||||||
|
for __channel_index in _channel_infos.keys():
|
||||||
|
# _channel_note_count = 0
|
||||||
|
|
||||||
|
_now_channel_ender_sign = xxh3_64(
|
||||||
|
_bytes_buffer_in[
|
||||||
|
_now_channel_starter_index : _now_channel_starter_index + 4
|
||||||
|
],
|
||||||
|
seed=3,
|
||||||
|
).digest()
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# "[DEBUG] 索引取得:",
|
||||||
|
# _bytes_buffer_in[
|
||||||
|
# _now_channel_starter_index : _now_channel_starter_index + 4
|
||||||
|
# ],
|
||||||
|
# "校验索引",
|
||||||
|
# _now_channel_ender_sign,
|
||||||
|
# )
|
||||||
|
|
||||||
|
_now_channel_ender_index = _bytes_buffer_in.find(_now_channel_ender_sign)
|
||||||
|
|
||||||
|
# print("[DEBUG] 索引取得:", _now_channel_ender_index,)
|
||||||
|
|
||||||
|
_channel_note_count = int.from_bytes(
|
||||||
|
_bytes_buffer_in[
|
||||||
|
_now_channel_starter_index : _now_channel_starter_index + 4
|
||||||
|
],
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
|
||||||
|
if _channel_note_count == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
while (
|
||||||
|
xxh3_64(
|
||||||
|
_bytes_buffer_in[_now_channel_starter_index:_now_channel_ender_index],
|
||||||
|
seed=_channel_note_count,
|
||||||
|
).digest()
|
||||||
|
!= _bytes_buffer_in[
|
||||||
|
_now_channel_ender_index + 8 : _now_channel_ender_index + 16
|
||||||
|
]
|
||||||
|
):
|
||||||
|
_now_channel_ender_index += 8 + _bytes_buffer_in[
|
||||||
|
_now_channel_ender_index + 8 :
|
||||||
|
].find(_now_channel_ender_sign)
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# "[WARNING] XXHASH 无法匹配,当前序列",
|
||||||
|
# __channel_index,
|
||||||
|
# "当前全部序列字节串",
|
||||||
|
# _bytes_buffer_in[
|
||||||
|
# _now_channel_starter_index:_now_channel_ender_index
|
||||||
|
# ],
|
||||||
|
# "校验值",
|
||||||
|
# xxh3_64(
|
||||||
|
# _bytes_buffer_in[
|
||||||
|
# _now_channel_starter_index:_now_channel_ender_index
|
||||||
|
# ],
|
||||||
|
# seed=_channel_note_count,
|
||||||
|
# ).digest(),
|
||||||
|
# _bytes_buffer_in[
|
||||||
|
# _now_channel_ender_index + 8 : _now_channel_ender_index + 16
|
||||||
|
# ],
|
||||||
|
# "改变结尾索引",
|
||||||
|
# _now_channel_ender_index,
|
||||||
|
# )
|
||||||
|
|
||||||
|
_channel_infos[__channel_index]["NOW_INDEX"] = _now_channel_starter_index + 4
|
||||||
|
_channel_infos[__channel_index]["END_INDEX"] = _now_channel_ender_index
|
||||||
|
_channel_infos[__channel_index]["NOTE_COUNT"] = _channel_note_count
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# "[DEBUG] 当前序列", __channel_index, "值", _channel_infos[__channel_index]
|
||||||
|
# )
|
||||||
|
|
||||||
|
_total_note_count += _channel_note_count
|
||||||
|
|
||||||
|
_now_channel_starter_index = _now_channel_ender_index + 16
|
||||||
|
# for i in range(
|
||||||
|
# int.from_bytes(
|
||||||
|
# bytes_buffer_in[stt_index : (stt_index := stt_index + 4)], "big"
|
||||||
|
# )
|
||||||
|
# ):
|
||||||
|
_to_yield_note_list: List[Tuple[MineNote, int]] = []
|
||||||
|
|
||||||
|
# {"NOW_INDEX": 0, "NOTE_COUNT": 0, "HAVE_READ": 0, "END_INDEX": -1}
|
||||||
|
|
||||||
|
while _total_note_count:
|
||||||
|
_read_in_note_list: List[Tuple[MineNote, int]] = []
|
||||||
|
for __channel_index in _channel_infos.keys():
|
||||||
|
if (
|
||||||
|
_channel_infos[__channel_index]["HAVE_READ"]
|
||||||
|
< _channel_infos[__channel_index]["NOTE_COUNT"]
|
||||||
|
):
|
||||||
|
# print("当前已读", _channel_infos[__channel_index]["HAVE_READ"])
|
||||||
|
try:
|
||||||
|
_end_index = (
|
||||||
|
(_stt_index := _channel_infos[__channel_index]["NOW_INDEX"])
|
||||||
|
+ 13
|
||||||
|
+ high_quantity_note
|
||||||
|
+ (_bytes_buffer_in[_stt_index] >> 2)
|
||||||
|
)
|
||||||
|
# print("读取音符字节串", _bytes_buffer_in[_stt_index:_end_index])
|
||||||
|
_read_in_note_list.append(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
MineNote.decode(
|
||||||
|
code_buffer=_bytes_buffer_in[_stt_index:_end_index],
|
||||||
|
is_high_time_precision=high_quantity_note,
|
||||||
|
)
|
||||||
|
if new_note_format
|
||||||
|
else decode_note_bytes_v2(
|
||||||
|
code_buffer_bytes=_bytes_buffer_in[
|
||||||
|
_stt_index:_end_index
|
||||||
|
],
|
||||||
|
is_high_time_precision=high_quantity_note,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
__channel_index,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_channel_infos[__channel_index]["HAVE_READ"] += 1
|
||||||
|
_channel_infos[__channel_index]["NOW_INDEX"] = _end_index
|
||||||
|
_total_note_count -= 1
|
||||||
|
except Exception as _err:
|
||||||
|
# print(channels_)
|
||||||
|
raise SingleNoteDecodeError("难以定位的解码错误") from _err
|
||||||
|
if not _read_in_note_list:
|
||||||
|
break
|
||||||
|
# _note_list.append
|
||||||
|
min_stt_note: MineNote = min(_read_in_note_list, key=lambda x: x[0].start_tick)[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
for i in range(len(_to_yield_note_list)):
|
||||||
|
__note, __channel_index = _to_yield_note_list[i]
|
||||||
|
if __note.start_tick >= min_stt_note.start_tick:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
yield __channel_index, __note
|
||||||
|
_to_yield_note_list.pop(i)
|
||||||
|
|
||||||
|
_to_yield_note_list.extend(_read_in_note_list)
|
||||||
|
_to_yield_note_list.sort(key=lambda x: x[0].start_tick)
|
||||||
|
|
||||||
|
for __note, __channel_index in sorted(
|
||||||
|
_to_yield_note_list, key=lambda x: x[0].start_tick
|
||||||
|
):
|
||||||
|
yield __channel_index, __note
|
||||||
|
# 俺寻思能用
|
||||||
|
|
||||||
|
|
||||||
|
def guess_deviation(
|
||||||
|
total_note_count: int,
|
||||||
|
total_instrument_count: int,
|
||||||
|
note_count_per_instrument: Optional[Dict[str, int]] = None,
|
||||||
|
qualified_note_count_per_instrument: Optional[Dict[str, int]] = None,
|
||||||
|
music_channels: Optional[MineNoteChannelType] = None,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
通过乐器权重来计算一首歌的音调偏移
|
||||||
|
这个方法未经验证,但理论有效,金羿首创
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
total_note_count: int
|
||||||
|
歌曲总音符数
|
||||||
|
total_instrument_count: int
|
||||||
|
歌曲乐器总数
|
||||||
|
note_count_per_instrument: Dict[str, int]
|
||||||
|
乐器名称与乐器音符数对照表
|
||||||
|
qualified_note_count_per_instrument: Dict[str, int]
|
||||||
|
每个乐器中,符合该乐器的音调范围的音符数
|
||||||
|
music_channels: MineNoteChannelType
|
||||||
|
MusicSequence类的音乐通道字典
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float估测的音调偏移值
|
||||||
|
"""
|
||||||
|
if note_count_per_instrument is None or qualified_note_count_per_instrument is None:
|
||||||
|
if music_channels is None:
|
||||||
|
raise ValueError("参数不足,算逑!")
|
||||||
|
note_count_per_instrument = {}
|
||||||
|
qualified_note_count_per_instrument = {}
|
||||||
|
for this_note in [k for j in music_channels.values() for k in j]:
|
||||||
|
if this_note.sound_name in note_count_per_instrument.keys():
|
||||||
|
note_count_per_instrument[this_note.sound_name] += 1
|
||||||
|
qualified_note_count_per_instrument[
|
||||||
|
this_note.sound_name
|
||||||
|
] += is_note_in_diapason(this_note)
|
||||||
|
else:
|
||||||
|
note_count_per_instrument[this_note.sound_name] = 1
|
||||||
|
qualified_note_count_per_instrument[this_note.sound_name] = int(
|
||||||
|
is_note_in_diapason(this_note)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
sum(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
MM_INSTRUMENT_RANGE_TABLE[inst][-1]
|
||||||
|
* note_count
|
||||||
|
/ total_note_count
|
||||||
|
- MM_INSTRUMENT_RANGE_TABLE[inst][-1]
|
||||||
|
)
|
||||||
|
* (note_count - qualified_note_count_per_instrument[inst])
|
||||||
|
)
|
||||||
|
for inst, note_count in note_count_per_instrument.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
/ total_instrument_count
|
||||||
|
/ total_note_count
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 延长支持用
|
||||||
|
|
||||||
|
|
||||||
|
def decode_note_bytes_v1(
|
||||||
|
code_buffer_bytes: bytes,
|
||||||
|
) -> MineNote:
|
||||||
|
"""使用第一版的 MineNote 字节码标准析出MineNote类"""
|
||||||
|
group_1 = int.from_bytes(code_buffer_bytes[: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_bytes[6] & 0b1:
|
||||||
|
position_displacement_ = (
|
||||||
|
int.from_bytes(
|
||||||
|
code_buffer_bytes[8 + sound_name_length : 10 + sound_name_length],
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
/ 1000,
|
||||||
|
int.from_bytes(
|
||||||
|
code_buffer_bytes[10 + sound_name_length : 12 + sound_name_length],
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
/ 1000,
|
||||||
|
int.from_bytes(
|
||||||
|
code_buffer_bytes[12 + sound_name_length : 14 + sound_name_length],
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
/ 1000,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
position_displacement_ = (0, 0, 0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return MineNote.from_traditional(
|
||||||
|
mc_sound_name=code_buffer_bytes[8 : 8 + sound_name_length].decode(
|
||||||
|
encoding="utf-8"
|
||||||
|
),
|
||||||
|
midi_pitch=note_pitch_,
|
||||||
|
midi_velocity=code_buffer_bytes[6] >> 1,
|
||||||
|
start_time=start_tick_,
|
||||||
|
last_time=duration_,
|
||||||
|
is_percussion=percussive_,
|
||||||
|
displacement=position_displacement_,
|
||||||
|
extra_information={"track_number": code_buffer_bytes[7]},
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
print(code_buffer_bytes, "\n", code_buffer_bytes[8 : 8 + sound_name_length])
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def decode_note_bytes_v2(
|
||||||
|
code_buffer_bytes: bytes, is_high_time_precision: bool = True
|
||||||
|
) -> MineNote:
|
||||||
|
"""使用第二版的 MineNote 字节码标准析出MineNote类"""
|
||||||
|
group_1 = int.from_bytes(code_buffer_bytes[: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_bytes[6] & 0b1:
|
||||||
|
position_displacement_ = (
|
||||||
|
int.from_bytes(
|
||||||
|
(
|
||||||
|
code_buffer_bytes[8 + sound_name_length : 10 + sound_name_length]
|
||||||
|
if is_high_time_precision
|
||||||
|
else code_buffer_bytes[
|
||||||
|
7 + sound_name_length : 9 + sound_name_length
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
/ 1000,
|
||||||
|
int.from_bytes(
|
||||||
|
(
|
||||||
|
code_buffer_bytes[10 + sound_name_length : 12 + sound_name_length]
|
||||||
|
if is_high_time_precision
|
||||||
|
else code_buffer_bytes[
|
||||||
|
9 + sound_name_length : 11 + sound_name_length
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
/ 1000,
|
||||||
|
int.from_bytes(
|
||||||
|
(
|
||||||
|
code_buffer_bytes[12 + sound_name_length : 14 + sound_name_length]
|
||||||
|
if is_high_time_precision
|
||||||
|
else code_buffer_bytes[
|
||||||
|
11 + sound_name_length : 13 + sound_name_length
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"big",
|
||||||
|
)
|
||||||
|
/ 1000,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
position_displacement_ = (0, 0, 0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return MineNote.from_traditional(
|
||||||
|
mc_sound_name=(
|
||||||
|
o := (
|
||||||
|
code_buffer_bytes[8 : 8 + sound_name_length]
|
||||||
|
if is_high_time_precision
|
||||||
|
else code_buffer_bytes[7 : 7 + sound_name_length]
|
||||||
|
)
|
||||||
|
).decode(encoding="GB18030"),
|
||||||
|
midi_pitch=note_pitch_,
|
||||||
|
midi_velocity=code_buffer_bytes[6] >> 1,
|
||||||
|
start_time=start_tick_,
|
||||||
|
last_time=duration_,
|
||||||
|
mass_precision_time=code_buffer_bytes[7] if is_high_time_precision else 0,
|
||||||
|
is_percussion=percussive_,
|
||||||
|
displacement=position_displacement_,
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
print(code_buffer_bytes, "\n", o)
|
||||||
|
raise
|
||||||
638
old-things/Musicreater/subclass.py
Normal file
638
old-things/Musicreater/subclass.py
Normal file
@@ -0,0 +1,638 @@
|
|||||||
|
# -*- 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 Musicreater.constants import MC_PITCHED_INSTRUMENT_LIST
|
||||||
|
from Musicreater.builtin_plugins.to_commands.main import MineCommand
|
||||||
|
from Musicreater.builtin_plugins.to_commands.progressbar import (
|
||||||
|
ProgressBarStyle,
|
||||||
|
mctick2timestr,
|
||||||
|
DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@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 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__()
|
||||||
BIN
old-things/Packer/MSCT_MAIN.MPK
Normal file
BIN
old-things/Packer/MSCT_MAIN.MPK
Normal file
Binary file not shown.
BIN
old-things/Packer/MSCT_PLUGIN.MPK
Normal file
BIN
old-things/Packer/MSCT_PLUGIN.MPK
Normal file
Binary file not shown.
1
old-things/Packer/MSCT_PLUGIN_FUNCTION.MPK
Normal file
1
old-things/Packer/MSCT_PLUGIN_FUNCTION.MPK
Normal file
@@ -0,0 +1 @@
|
|||||||
|
q@v,fxіБ<D196>Еџ<D095>лцmЩ5]Ќs"ЏџЦбBMXi<58>ЈnНхч<D185>Z8О=Г<7F>4<EFBFBD>PTUБQЈmтфджG<D0B6>жu_цп<D186>DS№|
|
||||||
68
old-things/Packer/MSCT_Packer.py
Normal file
68
old-things/Packer/MSCT_Packer.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import Musicreater.old_init as old_init
|
||||||
|
import Musicreater.experiment
|
||||||
|
import Musicreater.old_plugin
|
||||||
|
|
||||||
|
# import Musicreater.previous
|
||||||
|
from Musicreater.old_plugin.addonpack import (
|
||||||
|
to_addon_pack_in_delay,
|
||||||
|
to_addon_pack_in_repeater,
|
||||||
|
to_addon_pack_in_score,
|
||||||
|
)
|
||||||
|
from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||||
|
from Musicreater.old_plugin.mcstructfile import (
|
||||||
|
to_mcstructure_file_in_delay,
|
||||||
|
to_mcstructure_file_in_repeater,
|
||||||
|
to_mcstructure_file_in_score,
|
||||||
|
)
|
||||||
|
|
||||||
|
MSCT_MAIN = (
|
||||||
|
old_init,
|
||||||
|
old_init.experiment,
|
||||||
|
# Musicreater.previous,
|
||||||
|
)
|
||||||
|
|
||||||
|
MSCT_PLUGIN = (old_init.old_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()
|
||||||
|
)
|
||||||
6
old-things/Packer/checksum.txt
Normal file
6
old-things/Packer/checksum.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
MSCT_MAIN:
|
||||||
|
6b9f5a97d50beb07c834e375104c67ae44c57ae40f73fb71075b3668899029c7
|
||||||
|
MSCT_PLUGIN:
|
||||||
|
c280413a394a539438a5d10078c9b55f04bcd4cf6869c59a3f7a026039748cfc
|
||||||
|
MSCT_PLUGIN_FUNCTION:
|
||||||
|
40697f1d9b293268fe142fa3e9bffee2923a8f4811ec7bbdf7b14afb98723ef2
|
||||||
67
old-things/bgArrayLib/bpm.py
Normal file
67
old-things/bgArrayLib/bpm.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import mido
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
'''
|
||||||
|
bpm
|
||||||
|
bites per minutes
|
||||||
|
每分钟的拍数
|
||||||
|
'''
|
||||||
|
|
||||||
|
def mt2gt(mt, tpb_a, bpm_a):
|
||||||
|
return round(mt / tpb_a / bpm_a * 60)
|
||||||
|
|
||||||
|
|
||||||
|
def get(mid:mido.MidiFile) -> int:
|
||||||
|
'''传入一个 MidiFile, 返回其音乐的bpm
|
||||||
|
:param mid : mido.MidFile
|
||||||
|
mido库识别的midi文件数据
|
||||||
|
:return bpm : int
|
||||||
|
'''
|
||||||
|
# mid = mido.MidiFile(mf)
|
||||||
|
length = mid.length
|
||||||
|
tpb = mid.ticks_per_beat
|
||||||
|
bpm = 20
|
||||||
|
gotV = 0
|
||||||
|
|
||||||
|
for track in mid.tracks:
|
||||||
|
global_time = 0
|
||||||
|
for msg in track:
|
||||||
|
global_time += msg.time
|
||||||
|
if msg.type == "note_on" and msg.velocity > 0:
|
||||||
|
gotV = mt2gt(global_time, tpb, bpm)
|
||||||
|
errorV = numpy.fabs(gotV - length)
|
||||||
|
last_dic = {bpm: errorV}
|
||||||
|
if last_dic.get(bpm) > errorV:
|
||||||
|
last_dic = {bpm: errorV}
|
||||||
|
bpm += 2
|
||||||
|
|
||||||
|
while True:
|
||||||
|
for track in mid.tracks:
|
||||||
|
global_time = 0
|
||||||
|
for msg in track:
|
||||||
|
global_time += msg.time
|
||||||
|
if msg.type == "note_on" and msg.velocity > 0:
|
||||||
|
gotV = mt2gt(global_time, tpb, bpm)
|
||||||
|
errorV = numpy.fabs(gotV - length)
|
||||||
|
try:
|
||||||
|
if last_dic.get(bpm - 2) > errorV:
|
||||||
|
last_dic = {bpm: errorV}
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
bpm += 2
|
||||||
|
if bpm >= 252:
|
||||||
|
break
|
||||||
|
print(list(last_dic.keys())[0])
|
||||||
|
return list(last_dic.keys())[0]
|
||||||
|
|
||||||
|
|
||||||
|
def compute(mid:mido.MidiFile):
|
||||||
|
answer = 60000000/mid.ticks_per_beat
|
||||||
|
print(answer)
|
||||||
|
return answer
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mid = mido.MidiFile(r"C:\Users\lc\Documents\MuseScore3\乐谱\乐谱\Bad style - Time back.mid")
|
||||||
|
get(mid)
|
||||||
|
compute(mid)
|
||||||
40
old-things/bgArrayLib/compute.py
Normal file
40
old-things/bgArrayLib/compute.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
def round_up(num, power=0):
|
||||||
|
"""
|
||||||
|
实现精确四舍五入,包含正、负小数多种场景
|
||||||
|
:param num: 需要四舍五入的小数
|
||||||
|
:param power: 四舍五入位数,支持0-∞
|
||||||
|
:return: 返回四舍五入后的结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print(1 / 0)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
digit = 10 ** power
|
||||||
|
num2 = float(int(num * digit))
|
||||||
|
# 处理正数,power不为0的情况
|
||||||
|
if num >= 0 and power != 0:
|
||||||
|
tag = num * digit - num2 + 1 / (digit * 10)
|
||||||
|
if tag >= 0.5:
|
||||||
|
return (num2 + 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
|
# 处理正数,power为0取整的情况
|
||||||
|
elif num >= 0 and power == 0:
|
||||||
|
tag = num * digit - int(num)
|
||||||
|
if tag >= 0.5:
|
||||||
|
return (num2 + 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
|
# 处理负数,power为0取整的情况
|
||||||
|
elif power == 0 and num < 0:
|
||||||
|
tag = num * digit - int(num)
|
||||||
|
if tag <= -0.5:
|
||||||
|
return (num2 - 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
|
# 处理负数,power不为0的情况
|
||||||
|
else:
|
||||||
|
tag = num * digit - num2 - 1 / (digit * 10)
|
||||||
|
if tag <= -0.5:
|
||||||
|
return (num2 - 1) / digit
|
||||||
|
else:
|
||||||
|
return num2 / digit
|
||||||
130
old-things/bgArrayLib/instrumentConstant.py
Normal file
130
old-things/bgArrayLib/instrumentConstant.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
instrument_list = {
|
||||||
|
"0": "harp",
|
||||||
|
"1": "harp",
|
||||||
|
"2": "pling",
|
||||||
|
"3": "harp",
|
||||||
|
"4": "pling",
|
||||||
|
"5": "pling",
|
||||||
|
"6": "harp",
|
||||||
|
"7": "harp",
|
||||||
|
"8": "share",
|
||||||
|
"9": "harp",
|
||||||
|
"10": "didgeridoo",
|
||||||
|
"11": "harp",
|
||||||
|
"12": "xylophone",
|
||||||
|
"13": "chime",
|
||||||
|
"14": "harp",
|
||||||
|
"15": "harp",
|
||||||
|
"16": "bass",
|
||||||
|
"17": "harp",
|
||||||
|
"18": "harp",
|
||||||
|
"19": "harp",
|
||||||
|
"20": "harp",
|
||||||
|
"21": "harp",
|
||||||
|
"22": "harp",
|
||||||
|
"23": "guitar",
|
||||||
|
"24": "guitar",
|
||||||
|
"25": "guitar",
|
||||||
|
"26": "guitar",
|
||||||
|
"27": "guitar",
|
||||||
|
"28": "guitar",
|
||||||
|
"29": "guitar",
|
||||||
|
"30": "guitar",
|
||||||
|
"31": "bass",
|
||||||
|
"32": "bass",
|
||||||
|
"33": "bass",
|
||||||
|
"34": "bass",
|
||||||
|
"35": "bass",
|
||||||
|
"36": "bass",
|
||||||
|
"37": "bass",
|
||||||
|
"38": "bass",
|
||||||
|
"39": "bass",
|
||||||
|
"40": "harp",
|
||||||
|
"41": "harp",
|
||||||
|
"42": "harp",
|
||||||
|
"43": "harp",
|
||||||
|
"44": "iron_xylophone",
|
||||||
|
"45": "guitar",
|
||||||
|
"46": "harp",
|
||||||
|
"47": "harp",
|
||||||
|
"48": "guitar",
|
||||||
|
"49": "guitar",
|
||||||
|
"50": "bit",
|
||||||
|
"51": "bit",
|
||||||
|
"52": "harp",
|
||||||
|
"53": "harp",
|
||||||
|
"54": "bit",
|
||||||
|
"55": "flute",
|
||||||
|
"56": "flute",
|
||||||
|
"57": "flute",
|
||||||
|
"58": "flute",
|
||||||
|
"59": "flute",
|
||||||
|
"60": "flute",
|
||||||
|
"61": "flute",
|
||||||
|
"62": "flute",
|
||||||
|
"63": "flute",
|
||||||
|
"64": "bit",
|
||||||
|
"65": "bit",
|
||||||
|
"66": "bit",
|
||||||
|
"67": "bit",
|
||||||
|
"68": "flute",
|
||||||
|
"69": "harp",
|
||||||
|
"70": "harp",
|
||||||
|
"71": "flute",
|
||||||
|
"72": "flute",
|
||||||
|
"73": "flute",
|
||||||
|
"74": "harp",
|
||||||
|
"75": "flute",
|
||||||
|
"76": "harp",
|
||||||
|
"77": "harp",
|
||||||
|
"78": "harp",
|
||||||
|
"79": "harp",
|
||||||
|
"80": "bit",
|
||||||
|
"81": "bit",
|
||||||
|
"82": "bit",
|
||||||
|
"83": "bit",
|
||||||
|
"84": "bit",
|
||||||
|
"85": "bit",
|
||||||
|
"86": "bit",
|
||||||
|
"87": "bit",
|
||||||
|
"88": "bit",
|
||||||
|
"89": "bit",
|
||||||
|
"90": "bit",
|
||||||
|
"91": "bit",
|
||||||
|
"92": "bit",
|
||||||
|
"93": "bit",
|
||||||
|
"94": "bit",
|
||||||
|
"95": "bit",
|
||||||
|
"96": "bit",
|
||||||
|
"97": "bit",
|
||||||
|
"98": "bit",
|
||||||
|
"99": "bit",
|
||||||
|
"100": "bit",
|
||||||
|
"101": "bit",
|
||||||
|
"102": "bit",
|
||||||
|
"103": "bit",
|
||||||
|
"104": "harp",
|
||||||
|
"105": "banjo",
|
||||||
|
"106": "harp",
|
||||||
|
"107": "harp",
|
||||||
|
"108": "harp",
|
||||||
|
"109": "harp",
|
||||||
|
"110": "harp",
|
||||||
|
"111": "guitar",
|
||||||
|
"112": "harp",
|
||||||
|
"113": "bell",
|
||||||
|
"114": "harp",
|
||||||
|
"115": "cow_bell",
|
||||||
|
"116": "basedrum",
|
||||||
|
"117": "bass",
|
||||||
|
"118": "bit",
|
||||||
|
"119": "basedrum",
|
||||||
|
"120": "guitar",
|
||||||
|
"121": "harp",
|
||||||
|
"122": "harp",
|
||||||
|
"123": "harp",
|
||||||
|
"124": "harp",
|
||||||
|
"125": "hat",
|
||||||
|
"126": "basedrum",
|
||||||
|
"127": "snare",
|
||||||
|
}
|
||||||
250
old-things/bgArrayLib/namesConstant.py
Normal file
250
old-things/bgArrayLib/namesConstant.py
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
zip_name = {
|
||||||
|
-1: "-1.Acoustic_Kit_打击乐.zip",
|
||||||
|
0: "0.Acoustic_Grand_Piano_大钢琴.zip",
|
||||||
|
1: "1.Bright_Acoustic_Piano_亮音大钢琴.zip",
|
||||||
|
10: "10.Music_Box_八音盒.zip",
|
||||||
|
100: "100.FX_brightness_合成特效-亮音.zip",
|
||||||
|
101: "101.FX_goblins_合成特效-小妖.zip",
|
||||||
|
102: "102.FX_echoes_合成特效-回声.zip",
|
||||||
|
103: "103.FX_sci-fi_合成特效-科幻.zip",
|
||||||
|
104: "104.Sitar_锡塔尔.zip",
|
||||||
|
105: "105.Banjo_班卓.zip",
|
||||||
|
106: "106.Shamisen_三味线.zip",
|
||||||
|
107: "107.Koto_筝.zip",
|
||||||
|
108: "108.Kalimba_卡林巴.zip",
|
||||||
|
109: "109.Bagpipe_风笛.zip",
|
||||||
|
11: "11.Vibraphone_电颤琴.zip",
|
||||||
|
110: "110.Fiddle_古提琴.zip",
|
||||||
|
111: "111.Shanai_唢呐.zip",
|
||||||
|
112: "112.Tinkle_Bell_铃铛.zip",
|
||||||
|
113: "113.Agogo_拉丁打铃.zip",
|
||||||
|
114: "114.Steel_Drums_钢鼓.zip",
|
||||||
|
115: "115.Woodblock_木块.zip",
|
||||||
|
116: "116.Taiko_Drum_太鼓.zip",
|
||||||
|
117: "117.Melodic_Tom_嗵鼓.zip",
|
||||||
|
118: "118.Synth_Drum_合成鼓.zip",
|
||||||
|
119: "119.Reverse_Cymbal_镲波形反转.zip",
|
||||||
|
12: "12.Marimba_马林巴.zip",
|
||||||
|
13: "13.Xylophone_木琴.zip",
|
||||||
|
14: "14.Tubular_Bells_管钟.zip",
|
||||||
|
15: "15.Dulcimer_扬琴.zip",
|
||||||
|
16: "16.Drawbar_Organ_击杆风琴.zip",
|
||||||
|
17: "17.Percussive_Organ_打击型风琴.zip",
|
||||||
|
18: "18.Rock_Organ_摇滚风琴.zip",
|
||||||
|
19: "19.Church_Organ_管风琴.zip",
|
||||||
|
2: "2.Electric_Grand_Piano_电子大钢琴.zip",
|
||||||
|
20: "20.Reed_Organ_簧风琴.zip",
|
||||||
|
21: "21.Accordion_手风琴.zip",
|
||||||
|
22: "22.Harmonica_口琴.zip",
|
||||||
|
23: "23.Tango_Accordian_探戈手风琴.zip",
|
||||||
|
24: "24.Acoustic_Guitar_(nylon)_尼龙弦吉他.zip",
|
||||||
|
25: "25.Acoustic_Guitar(steel)_钢弦吉他.zip",
|
||||||
|
26: "26.Electric_Guitar_(jazz)_爵士乐电吉他.zip",
|
||||||
|
27: "27.Electric_Guitar_(clean)_清音电吉他.zip",
|
||||||
|
28: "28.Electric_Guitar_(muted)_弱音电吉他.zip",
|
||||||
|
29: "29.Overdriven_Guitar_驱动音效吉他.zip",
|
||||||
|
3: "3.Honky-Tonk_Piano_酒吧钢琴.zip",
|
||||||
|
30: "30.Distortion_Guitar_失真音效吉他.zip",
|
||||||
|
31: "31.Guitar_Harmonics_吉他泛音.zip",
|
||||||
|
32: "32.Acoustic_Bass_原声贝司.zip",
|
||||||
|
33: "33.Electric_Bass(finger)_指拨电贝司.zip",
|
||||||
|
34: "34.Electric_Bass(pick)_拨片拨电贝司.zip",
|
||||||
|
35: "35.Fretless_Bass_无品贝司.zip",
|
||||||
|
36: "36.Slap_Bass_A_击弦贝司A.zip",
|
||||||
|
37: "37.Slap_Bass_B_击弦贝司B.zip",
|
||||||
|
38: "38.Synth_Bass_A_合成贝司A.zip",
|
||||||
|
39: "39.Synth_Bass_B_合成贝司B.zip",
|
||||||
|
4: "4.Electric_Piano_1_电钢琴A.zip",
|
||||||
|
40: "40.Violin_小提琴.zip",
|
||||||
|
41: "41.Viola_中提琴.zip",
|
||||||
|
42: "42.Cello_大提琴.zip",
|
||||||
|
43: "43.Contrabass_低音提琴.zip",
|
||||||
|
44: "44.Tremolo_Strings_弦乐震音.zip",
|
||||||
|
45: "45.Pizzicato_Strings_弦乐拨奏.zip",
|
||||||
|
46: "46.Orchestral_Harp_竖琴.zip",
|
||||||
|
47: "47.Timpani_定音鼓.zip",
|
||||||
|
48: "48.String_Ensemble_A_弦乐合奏A.zip",
|
||||||
|
49: "49.String_Ensemble_B_弦乐合奏B.zip",
|
||||||
|
5: "5.Electric_Piano_2_电钢琴B.zip",
|
||||||
|
50: "50.SynthStrings_A_合成弦乐A.zip",
|
||||||
|
51: "51.SynthStrings_B_合成弦乐B.zip",
|
||||||
|
52: "52.Choir_Aahs_合唱“啊”音.zip",
|
||||||
|
53: "53.Voice_Oohs_人声“哦”音.zip",
|
||||||
|
54: "54.Synth_Voice_合成人声.zip",
|
||||||
|
55: "55.Orchestra_Hit_乐队打击乐.zip",
|
||||||
|
56: "56.Trumpet_小号.zip",
|
||||||
|
57: "57.Trombone_长号.zip",
|
||||||
|
58: "58.Tuba_大号.zip",
|
||||||
|
59: "59.Muted_Trumpet_弱音小号.zip",
|
||||||
|
6: "6.Harpsichord_拨弦古钢琴.zip",
|
||||||
|
60: "60.French_Horn_圆号.zip",
|
||||||
|
61: "61.Brass_Section_铜管组.zip",
|
||||||
|
62: "62.Synth_Brass_A_合成铜管A.zip",
|
||||||
|
63: "63.Synth_Brass_A_合成铜管B.zip",
|
||||||
|
64: "64.Soprano_Sax_高音萨克斯.zip",
|
||||||
|
65: "65.Alto_Sax_中音萨克斯.zip",
|
||||||
|
66: "66.Tenor_Sax_次中音萨克斯.zip",
|
||||||
|
67: "67.Baritone_Sax_上低音萨克斯.zip",
|
||||||
|
68: "68.Oboe_双簧管.zip",
|
||||||
|
69: "69.English_Horn_英国管.zip",
|
||||||
|
7: "7.Clavinet_击弦古钢琴.zip",
|
||||||
|
70: "70.Bassoon_大管.zip",
|
||||||
|
71: "71.Clarinet_单簧管.zip",
|
||||||
|
72: "72.Piccolo_短笛.zip",
|
||||||
|
73: "73.Flute_长笛.zip",
|
||||||
|
74: "74.Recorder_竖笛.zip",
|
||||||
|
75: "75.Pan_Flute_排笛.zip",
|
||||||
|
76: "76.Bottle_Blow_吹瓶口.zip",
|
||||||
|
77: "77.Skakuhachi_尺八.zip",
|
||||||
|
78: "78.Whistle_哨.zip",
|
||||||
|
79: "79.Ocarina_洋埙.zip",
|
||||||
|
8: "8.Celesta_钢片琴.zip",
|
||||||
|
80: "80.Lead_square_合成主音-方波.zip",
|
||||||
|
81: "81.Lead_sawtooth_合成主音-锯齿波.zip",
|
||||||
|
82: "82.Lead_calliope_lead_合成主音-汽笛风琴.zip",
|
||||||
|
83: "83.Lead_chiff_lead_合成主音-吹管.zip",
|
||||||
|
84: "84.Lead_charang_合成主音5-吉他.zip",
|
||||||
|
85: "85.Lead_voice_合成主音-人声.zip",
|
||||||
|
86: "86.Lead_fifths_合成主音-五度.zip",
|
||||||
|
87: "87.Lead_bass+lead_合成主音-低音加主音.zip",
|
||||||
|
88: "88.Pad_new_age_合成柔音-新时代.zip",
|
||||||
|
89: "89.Pad_warm_合成柔音-暖音.zip",
|
||||||
|
9: "9.Glockenspiel_钟琴.zip",
|
||||||
|
90: "90.Pad_polysynth_合成柔音-复合成.zip",
|
||||||
|
91: "91.Pad_choir_合成柔音-合唱.zip",
|
||||||
|
92: "92.Pad_bowed_合成柔音-弓弦.zip",
|
||||||
|
93: "93.Pad_metallic_合成柔音-金属.zip",
|
||||||
|
94: "94.Pad_halo_合成柔音-光环.zip",
|
||||||
|
95: "95.Pad_sweep_合成柔音-扫弦.zip",
|
||||||
|
96: "96.FX_rain_合成特效-雨.zip",
|
||||||
|
97: "97.FX_soundtrack_合成特效-音轨.zip",
|
||||||
|
98: "98.FX_crystal_合成特效-水晶.zip",
|
||||||
|
99: "99.FX_atmosphere_合成特效-大气.zip",
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpack_name = {
|
||||||
|
-1: "-1.Acoustic_Kit_打击乐.mcpack",
|
||||||
|
0: "0.Acoustic_Grand_Piano_大钢琴.mcpack",
|
||||||
|
1: "1.Bright_Acoustic_Piano_亮音大钢琴.mcpack",
|
||||||
|
10: "10.Music_Box_八音盒.mcpack",
|
||||||
|
100: "100.FX_brightness_合成特效-亮音.mcpack",
|
||||||
|
101: "101.FX_goblins_合成特效-小妖.mcpack",
|
||||||
|
102: "102.FX_echoes_合成特效-回声.mcpack",
|
||||||
|
103: "103.FX_sci-fi_合成特效-科幻.mcpack",
|
||||||
|
104: "104.Sitar_锡塔尔.mcpack",
|
||||||
|
105: "105.Banjo_班卓.mcpack",
|
||||||
|
106: "106.Shamisen_三味线.mcpack",
|
||||||
|
107: "107.Koto_筝.mcpack",
|
||||||
|
108: "108.Kalimba_卡林巴.mcpack",
|
||||||
|
109: "109.Bagpipe_风笛.mcpack",
|
||||||
|
11: "11.Vibraphone_电颤琴.mcpack",
|
||||||
|
110: "110.Fiddle_古提琴.mcpack",
|
||||||
|
111: "111.Shanai_唢呐.mcpack",
|
||||||
|
112: "112.Tinkle_Bell_铃铛.mcpack",
|
||||||
|
113: "113.Agogo_拉丁打铃.mcpack",
|
||||||
|
114: "114.Steel_Drums_钢鼓.mcpack",
|
||||||
|
115: "115.Woodblock_木块.mcpack",
|
||||||
|
116: "116.Taiko_Drum_太鼓.mcpack",
|
||||||
|
117: "117.Melodic_Tom_嗵鼓.mcpack",
|
||||||
|
118: "118.Synth_Drum_合成鼓.mcpack",
|
||||||
|
119: "119.Reverse_Cymbal_镲波形反转.mcpack",
|
||||||
|
12: "12.Marimba_马林巴.mcpack",
|
||||||
|
13: "13.Xylophone_木琴.mcpack",
|
||||||
|
14: "14.Tubular_Bells_管钟.mcpack",
|
||||||
|
15: "15.Dulcimer_扬琴.mcpack",
|
||||||
|
16: "16.Drawbar_Organ_击杆风琴.mcpack",
|
||||||
|
17: "17.Percussive_Organ_打击型风琴.mcpack",
|
||||||
|
18: "18.Rock_Organ_摇滚风琴.mcpack",
|
||||||
|
19: "19.Church_Organ_管风琴.mcpack",
|
||||||
|
2: "2.Electric_Grand_Piano_电子大钢琴.mcpack",
|
||||||
|
20: "20.Reed_Organ_簧风琴.mcpack",
|
||||||
|
21: "21.Accordion_手风琴.mcpack",
|
||||||
|
22: "22.Harmonica_口琴.mcpack",
|
||||||
|
23: "23.Tango_Accordian_探戈手风琴.mcpack",
|
||||||
|
24: "24.Acoustic_Guitar_(nylon)_尼龙弦吉他.mcpack",
|
||||||
|
25: "25.Acoustic_Guitar(steel)_钢弦吉他.mcpack",
|
||||||
|
26: "26.Electric_Guitar_(jazz)_爵士乐电吉他.mcpack",
|
||||||
|
27: "27.Electric_Guitar_(clean)_清音电吉他.mcpack",
|
||||||
|
28: "28.Electric_Guitar_(muted)_弱音电吉他.mcpack",
|
||||||
|
29: "29.Overdriven_Guitar_驱动音效吉他.mcpack",
|
||||||
|
3: "3.Honky-Tonk_Piano_酒吧钢琴.mcpack",
|
||||||
|
30: "30.Distortion_Guitar_失真音效吉他.mcpack",
|
||||||
|
31: "31.Guitar_Harmonics_吉他泛音.mcpack",
|
||||||
|
32: "32.Acoustic_Bass_原声贝司.mcpack",
|
||||||
|
33: "33.Electric_Bass(finger)_指拨电贝司.mcpack",
|
||||||
|
34: "34.Electric_Bass(pick)_拨片拨电贝司.mcpack",
|
||||||
|
35: "35.Fretless_Bass_无品贝司.mcpack",
|
||||||
|
36: "36.Slap_Bass_A_击弦贝司A.mcpack",
|
||||||
|
37: "37.Slap_Bass_B_击弦贝司B.mcpack",
|
||||||
|
38: "38.Synth_Bass_A_合成贝司A.mcpack",
|
||||||
|
39: "39.Synth_Bass_B_合成贝司B.mcpack",
|
||||||
|
4: "4.Electric_Piano_1_电钢琴A.mcpack",
|
||||||
|
40: "40.Violin_小提琴.mcpack",
|
||||||
|
41: "41.Viola_中提琴.mcpack",
|
||||||
|
42: "42.Cello_大提琴.mcpack",
|
||||||
|
43: "43.Contrabass_低音提琴.mcpack",
|
||||||
|
44: "44.Tremolo_Strings_弦乐震音.mcpack",
|
||||||
|
45: "45.Pizzicato_Strings_弦乐拨奏.mcpack",
|
||||||
|
46: "46.Orchestral_Harp_竖琴.mcpack",
|
||||||
|
47: "47.Timpani_定音鼓.mcpack",
|
||||||
|
48: "48.String_Ensemble_A_弦乐合奏A.mcpack",
|
||||||
|
49: "49.String_Ensemble_B_弦乐合奏B.mcpack",
|
||||||
|
5: "5.Electric_Piano_2_电钢琴B.mcpack",
|
||||||
|
50: "50.SynthStrings_A_合成弦乐A.mcpack",
|
||||||
|
51: "51.SynthStrings_B_合成弦乐B.mcpack",
|
||||||
|
52: "52.Choir_Aahs_合唱“啊”音.mcpack",
|
||||||
|
53: "53.Voice_Oohs_人声“哦”音.mcpack",
|
||||||
|
54: "54.Synth_Voice_合成人声.mcpack",
|
||||||
|
55: "55.Orchestra_Hit_乐队打击乐.mcpack",
|
||||||
|
56: "56.Trumpet_小号.mcpack",
|
||||||
|
57: "57.Trombone_长号.mcpack",
|
||||||
|
58: "58.Tuba_大号.mcpack",
|
||||||
|
59: "59.Muted_Trumpet_弱音小号.mcpack",
|
||||||
|
6: "6.Harpsichord_拨弦古钢琴.mcpack",
|
||||||
|
60: "60.French_Horn_圆号.mcpack",
|
||||||
|
61: "61.Brass_Section_铜管组.mcpack",
|
||||||
|
62: "62.Synth_Brass_A_合成铜管A.mcpack",
|
||||||
|
63: "63.Synth_Brass_A_合成铜管B.mcpack",
|
||||||
|
64: "64.Soprano_Sax_高音萨克斯.mcpack",
|
||||||
|
65: "65.Alto_Sax_中音萨克斯.mcpack",
|
||||||
|
66: "66.Tenor_Sax_次中音萨克斯.mcpack",
|
||||||
|
67: "67.Baritone_Sax_上低音萨克斯.mcpack",
|
||||||
|
68: "68.Oboe_双簧管.mcpack",
|
||||||
|
69: "69.English_Horn_英国管.mcpack",
|
||||||
|
7: "7.Clavinet_击弦古钢琴.mcpack",
|
||||||
|
70: "70.Bassoon_大管.mcpack",
|
||||||
|
71: "71.Clarinet_单簧管.mcpack",
|
||||||
|
72: "72.Piccolo_短笛.mcpack",
|
||||||
|
73: "73.Flute_长笛.mcpack",
|
||||||
|
74: "74.Recorder_竖笛.mcpack",
|
||||||
|
75: "75.Pan_Flute_排笛.mcpack",
|
||||||
|
76: "76.Bottle_Blow_吹瓶口.mcpack",
|
||||||
|
77: "77.Skakuhachi_尺八.mcpack",
|
||||||
|
78: "78.Whistle_哨.mcpack",
|
||||||
|
79: "79.Ocarina_洋埙.mcpack",
|
||||||
|
8: "8.Celesta_钢片琴.mcpack",
|
||||||
|
80: "80.Lead_square_合成主音-方波.mcpack",
|
||||||
|
81: "81.Lead_sawtooth_合成主音-锯齿波.mcpack",
|
||||||
|
82: "82.Lead_calliope_lead_合成主音-汽笛风琴.mcpack",
|
||||||
|
83: "83.Lead_chiff_lead_合成主音-吹管.mcpack",
|
||||||
|
84: "84.Lead_charang_合成主音5-吉他.mcpack",
|
||||||
|
85: "85.Lead_voice_合成主音-人声.mcpack",
|
||||||
|
86: "86.Lead_fifths_合成主音-五度.mcpack",
|
||||||
|
87: "87.Lead_bass+lead_合成主音-低音加主音.mcpack",
|
||||||
|
88: "88.Pad_new_age_合成柔音-新时代.mcpack",
|
||||||
|
89: "89.Pad_warm_合成柔音-暖音.mcpack",
|
||||||
|
9: "9.Glockenspiel_钟琴.mcpack",
|
||||||
|
90: "90.Pad_polysynth_合成柔音-复合成.mcpack",
|
||||||
|
91: "91.Pad_choir_合成柔音-合唱.mcpack",
|
||||||
|
92: "92.Pad_bowed_合成柔音-弓弦.mcpack",
|
||||||
|
93: "93.Pad_metallic_合成柔音-金属.mcpack",
|
||||||
|
94: "94.Pad_halo_合成柔音-光环.mcpack",
|
||||||
|
95: "95.Pad_sweep_合成柔音-扫弦.mcpack",
|
||||||
|
96: "96.FX_rain_合成特效-雨.mcpack",
|
||||||
|
97: "97.FX_soundtrack_合成特效-音轨.mcpack",
|
||||||
|
98: "98.FX_crystal_合成特效-水晶.mcpack",
|
||||||
|
99: "99.FX_atmosphere_合成特效-大气.mcpack",
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(zip_name[0])
|
||||||
134
old-things/bgArrayLib/pitchStrConstant.py
Normal file
134
old-things/bgArrayLib/pitchStrConstant.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
pitch = {
|
||||||
|
"0": "0.0220970869120796",
|
||||||
|
"1": "0.0234110480761981",
|
||||||
|
"2": "0.0248031414370031",
|
||||||
|
"3": "0.0262780129766786",
|
||||||
|
"4": "0.0278405849418856",
|
||||||
|
"5": "0.0294960722713029",
|
||||||
|
"6": "0.03125",
|
||||||
|
"7": "0.033108221698728",
|
||||||
|
"8": "0.0350769390096679",
|
||||||
|
"9": "0.037162722343835",
|
||||||
|
"10": "0.0393725328092148",
|
||||||
|
"11": "0.0417137454428136",
|
||||||
|
"12": "0.0441941738241592",
|
||||||
|
"13": "0.0468220961523963",
|
||||||
|
"14": "0.0496062828740062",
|
||||||
|
"15": "0.0525560259533572",
|
||||||
|
"16": "0.0556811698837712",
|
||||||
|
"17": "0.0589921445426059",
|
||||||
|
"18": "0.0625",
|
||||||
|
"19": "0.066216443397456",
|
||||||
|
"20": "0.0701538780193358",
|
||||||
|
"21": "0.0743254446876701",
|
||||||
|
"22": "0.0787450656184296",
|
||||||
|
"23": "0.0834274908856271",
|
||||||
|
"24": "0.0883883476483184",
|
||||||
|
"25": "0.0936441923047926",
|
||||||
|
"26": "0.0992125657480125",
|
||||||
|
"27": "0.105112051906714",
|
||||||
|
"28": "0.111362339767542",
|
||||||
|
"29": "0.117984289085212",
|
||||||
|
"30": "0.125",
|
||||||
|
"31": "0.132432886794912",
|
||||||
|
"32": "0.140307756038672",
|
||||||
|
"33": "0.14865088937534",
|
||||||
|
"34": "0.157490131236859",
|
||||||
|
"35": "0.166854981771254",
|
||||||
|
"36": "0.176776695296637",
|
||||||
|
"37": "0.187288384609585",
|
||||||
|
"38": "0.198425131496025",
|
||||||
|
"39": "0.210224103813429",
|
||||||
|
"40": "0.222724679535085",
|
||||||
|
"41": "0.235968578170423",
|
||||||
|
"42": "0.25",
|
||||||
|
"43": "0.264865773589824",
|
||||||
|
"44": "0.280615512077343",
|
||||||
|
"45": "0.29730177875068",
|
||||||
|
"46": "0.314980262473718",
|
||||||
|
"47": "0.333709963542509",
|
||||||
|
"48": "0.353553390593274",
|
||||||
|
"49": "0.37457676921917",
|
||||||
|
"50": "0.39685026299205",
|
||||||
|
"51": "0.420448207626857",
|
||||||
|
"52": "0.44544935907017",
|
||||||
|
"53": "0.471937156340847",
|
||||||
|
"54": "0.5",
|
||||||
|
"55": "0.529731547179648",
|
||||||
|
"56": "0.561231024154687",
|
||||||
|
"57": "0.594603557501361",
|
||||||
|
"58": "0.629960524947437",
|
||||||
|
"59": "0.667419927085017",
|
||||||
|
"60": "0.707106781186548",
|
||||||
|
"61": "0.749153538438341",
|
||||||
|
"62": "0.7937005259841",
|
||||||
|
"63": "0.840896415253715",
|
||||||
|
"64": "0.890898718140339",
|
||||||
|
"65": "0.943874312681694",
|
||||||
|
"66": "1",
|
||||||
|
"67": "1.0594630943593",
|
||||||
|
"68": "1.12246204830937",
|
||||||
|
"69": "1.18920711500272",
|
||||||
|
"70": "1.25992104989487",
|
||||||
|
"71": "1.33483985417003",
|
||||||
|
"72": "1.4142135623731",
|
||||||
|
"73": "1.49830707687668",
|
||||||
|
"74": "1.5874010519682",
|
||||||
|
"75": "1.68179283050743",
|
||||||
|
"76": "1.78179743628068",
|
||||||
|
"77": "1.88774862536339",
|
||||||
|
"78": "2",
|
||||||
|
"79": "2.11892618871859",
|
||||||
|
"80": "2.24492409661875",
|
||||||
|
"81": "2.37841423000544",
|
||||||
|
"82": "2.51984209978975",
|
||||||
|
"83": "2.66967970834007",
|
||||||
|
"84": "2.82842712474619",
|
||||||
|
"85": "2.99661415375336",
|
||||||
|
"86": "3.1748021039364",
|
||||||
|
"87": "3.36358566101486",
|
||||||
|
"88": "3.56359487256136",
|
||||||
|
"89": "3.77549725072677",
|
||||||
|
"90": "4",
|
||||||
|
"91": "4.23785237743718",
|
||||||
|
"92": "4.48984819323749",
|
||||||
|
"93": "4.75682846001088",
|
||||||
|
"94": "5.03968419957949",
|
||||||
|
"95": "5.33935941668014",
|
||||||
|
"96": "5.65685424949238",
|
||||||
|
"97": "5.99322830750673",
|
||||||
|
"98": "6.3496042078728",
|
||||||
|
"99": "6.72717132202972",
|
||||||
|
"100": "7.12718974512272",
|
||||||
|
"101": "7.55099450145355",
|
||||||
|
"102": "8",
|
||||||
|
"103": "8.47570475487436",
|
||||||
|
"104": "8.97969638647498",
|
||||||
|
"105": "9.51365692002177",
|
||||||
|
"106": "10.079368399159",
|
||||||
|
"107": "10.6787188333603",
|
||||||
|
"108": "11.3137084989848",
|
||||||
|
"109": "11.9864566150135",
|
||||||
|
"110": "12.6992084157456",
|
||||||
|
"111": "13.4543426440594",
|
||||||
|
"112": "14.2543794902454",
|
||||||
|
"113": "15.1019890029071",
|
||||||
|
"114": "16",
|
||||||
|
"115": "16.9514095097487",
|
||||||
|
"116": "17.95939277295",
|
||||||
|
"117": "19.0273138400435",
|
||||||
|
"118": "20.158736798318",
|
||||||
|
"119": "21.3574376667206",
|
||||||
|
"120": "22.6274169979695",
|
||||||
|
"121": "23.9729132300269",
|
||||||
|
"122": "25.3984168314912",
|
||||||
|
"123": "26.9086852881189",
|
||||||
|
"124": "28.5087589804909",
|
||||||
|
"125": "30.2039780058142",
|
||||||
|
"126": "32",
|
||||||
|
"127": "33.9028190194974",
|
||||||
|
"128": "35.9187855458999",
|
||||||
|
"129": "38.0546276800871",
|
||||||
|
"130": "40.3174735966359",
|
||||||
|
"131": "42.7148753334411",
|
||||||
|
}
|
||||||
208
old-things/bgArrayLib/reader.py
Normal file
208
old-things/bgArrayLib/reader.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
# from nmcsup.log import log
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
|
||||||
|
class Note:
|
||||||
|
def __init__(self, channel, pitch, velocity, time, time_position, instrument):
|
||||||
|
self.channel = channel
|
||||||
|
self.pitch = pitch
|
||||||
|
self.velocity = velocity
|
||||||
|
self.delay = time
|
||||||
|
self.time_position = time_position
|
||||||
|
self.instrument = instrument
|
||||||
|
self.CD = "d"
|
||||||
|
|
||||||
|
def get_CD(self, start, end):
|
||||||
|
if end - start > 1.00:
|
||||||
|
self.CD = "c"
|
||||||
|
else:
|
||||||
|
self.CD = "d"
|
||||||
|
|
||||||
|
|
||||||
|
def midiNewReader(midfile: str):
|
||||||
|
import mido
|
||||||
|
|
||||||
|
# from msctspt.threadOpera import NewThread
|
||||||
|
from bgArrayLib.bpm import get
|
||||||
|
|
||||||
|
def Time(mt, tpb_a, bpm_a):
|
||||||
|
return round(mt / tpb_a / bpm_a * 60 * 20)
|
||||||
|
|
||||||
|
Notes = []
|
||||||
|
tracks = []
|
||||||
|
note_list = []
|
||||||
|
close = []
|
||||||
|
on = []
|
||||||
|
off = []
|
||||||
|
instruments = []
|
||||||
|
isPercussion = False
|
||||||
|
try:
|
||||||
|
mid = mido.MidiFile(midfile)
|
||||||
|
except Exception:
|
||||||
|
print("找不到文件或无法读取文件" + midfile)
|
||||||
|
return False
|
||||||
|
tpb = mid.ticks_per_beat
|
||||||
|
bpm = get(mid)
|
||||||
|
# 解析
|
||||||
|
# def loadMidi(track1):
|
||||||
|
for track in mid.tracks:
|
||||||
|
overallTime = 0.0
|
||||||
|
instrument = 0
|
||||||
|
for i in track:
|
||||||
|
overallTime += i.time
|
||||||
|
try:
|
||||||
|
if i.channel != 9:
|
||||||
|
# try:
|
||||||
|
# log("event_type(事件): " + str(i.type) + " channel(音轨): " + str(i.channel) +
|
||||||
|
# " note/pitch(音高): " +
|
||||||
|
# str(i[2]) +
|
||||||
|
# " velocity(力度): " + str(i.velocity) + " time(间隔时间): " + str(i.time) +
|
||||||
|
# " overallTime/globalTime/timePosition: " + str(overallTime) + " \n")
|
||||||
|
# except AttributeError:
|
||||||
|
# log("event_type(事件): " + str(i.type) + " thing(内容):" + str(i) + " \n")
|
||||||
|
if "program_change" in str(i):
|
||||||
|
instrument = i.program
|
||||||
|
if instrument > 119: # 音色不够
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
instruments.append(i.program)
|
||||||
|
if "note_on" in str(i) and i.velocity > 0:
|
||||||
|
print(i)
|
||||||
|
# print(i.note)
|
||||||
|
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm), instrument)])
|
||||||
|
tracks.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
note_list.append(
|
||||||
|
[
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
on.append([i.note, Time(overallTime, tpb, bpm)])
|
||||||
|
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||||
|
if "note_off" in str(i) or "note_on" in str(i) and i.velocity == 0:
|
||||||
|
# print(i)
|
||||||
|
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm))])
|
||||||
|
close.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
off.append([i.note, Time(overallTime, tpb, bpm)])
|
||||||
|
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if "note_on" in str(i) and i.channel == 9:
|
||||||
|
if "note_on" in str(i) and i.velocity > 0:
|
||||||
|
print(i)
|
||||||
|
# print(i.note)
|
||||||
|
# print([Note(i.channel, i.note, i.velocity, i.time, Time(overallTime, tpb, bpm), -1)])
|
||||||
|
tracks.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
note_list.append(
|
||||||
|
[
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
-1,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
on.append([i.note, Time(overallTime, tpb, bpm)])
|
||||||
|
isPercussion = True
|
||||||
|
# return [Note(i.channel, i, i.velocity, i.time, Time(overallTime, tpb, bpm))]
|
||||||
|
Notes.append(tracks)
|
||||||
|
if instruments is []:
|
||||||
|
instruments.append(0)
|
||||||
|
instruments = list(set(instruments))
|
||||||
|
with open("1.pkl", "wb") as b:
|
||||||
|
pickle.dump([instruments, isPercussion], b)
|
||||||
|
|
||||||
|
# for j, track in enumerate(mid.tracks):
|
||||||
|
# th = NewThread(loadMidi, (track,))
|
||||||
|
# th.start()
|
||||||
|
# Notes.append(th.getResult())
|
||||||
|
|
||||||
|
# print(Notes)
|
||||||
|
print(Notes.__len__())
|
||||||
|
# print(note_list)
|
||||||
|
print(instruments)
|
||||||
|
return Notes
|
||||||
|
# return [Notes, note_list]
|
||||||
|
|
||||||
|
|
||||||
|
def midiClassReader(midfile: str):
|
||||||
|
import mido
|
||||||
|
|
||||||
|
from bgArrayLib.bpm import get
|
||||||
|
|
||||||
|
def Time(mt, tpb_a, bpm_a):
|
||||||
|
return round(mt / tpb_a / bpm_a * 60 * 20)
|
||||||
|
|
||||||
|
Notes = []
|
||||||
|
tracks = []
|
||||||
|
try:
|
||||||
|
mid = mido.MidiFile(filename=midfile, clip=True)
|
||||||
|
except Exception:
|
||||||
|
print("找不到文件或无法读取文件" + midfile)
|
||||||
|
return False
|
||||||
|
print("midi已经载入了。")
|
||||||
|
tpb = mid.ticks_per_beat
|
||||||
|
bpm = get(mid)
|
||||||
|
for track in mid.tracks:
|
||||||
|
overallTime = 0.0
|
||||||
|
instrument = 0
|
||||||
|
for i in track:
|
||||||
|
overallTime += i.time
|
||||||
|
if "note_on" in str(i) and i.velocity > 0:
|
||||||
|
print(i)
|
||||||
|
tracks.append(
|
||||||
|
[
|
||||||
|
Note(
|
||||||
|
i.channel,
|
||||||
|
i.note,
|
||||||
|
i.velocity,
|
||||||
|
i.time,
|
||||||
|
Time(overallTime, tpb, bpm),
|
||||||
|
instrument,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
Notes.append(tracks)
|
||||||
|
print(Notes.__len__())
|
||||||
|
return Notes
|
||||||
147
old-things/bgArrayLib/sy_resourcesPacker.py
Normal file
147
old-things/bgArrayLib/sy_resourcesPacker.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# import tkinter.filedialog
|
||||||
|
# from namesConstant import zip_name
|
||||||
|
# from namesConstant import mcpack_name
|
||||||
|
import bgArrayLib.namesConstant
|
||||||
|
|
||||||
|
zipN = bgArrayLib.namesConstant.zip_name
|
||||||
|
mpN = bgArrayLib.namesConstant.mcpack_name
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
"format_version": 1,
|
||||||
|
"header": {
|
||||||
|
"name": "羽音缭绕-midiout_25.5--音创使用",
|
||||||
|
"description": "羽音缭绕-midiout_25.0--音创使用",
|
||||||
|
"uuid": "c1adbda4-3b3e-4e5b-a57e-cde8ac80ee19",
|
||||||
|
"version": [25, 5, 0],
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"description": "羽音缭绕-midiout_25.0--音创使用",
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "c13455d5-b9f3-47f2-9706-c05ad86b3180 ",
|
||||||
|
"version": [25, 5, 0],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def resources_pathSetting(newPath: str = ""):
|
||||||
|
if not os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and newPath == "":
|
||||||
|
return [False, 1] # 1:没有路径文件
|
||||||
|
elif newPath != "": # not os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and
|
||||||
|
path = newPath
|
||||||
|
print(path)
|
||||||
|
with open("./bgArrayLib/resourcesPath.rpposi", "w") as w:
|
||||||
|
w.write(path)
|
||||||
|
if "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 1] # 1:都有
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" not in os.listdir(path):
|
||||||
|
return [True, path, 2] # 2:有pack
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" not in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 3] # 3:有zip
|
||||||
|
else:
|
||||||
|
return [False, 2] # 2:路径文件指示错误
|
||||||
|
if os.path.isfile("./bgArrayLib/resourcesPath.rpposi") and newPath == "":
|
||||||
|
with open("./bgArrayLib/resourcesPath.rpposi", "r") as f:
|
||||||
|
path = f.read()
|
||||||
|
if "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 1] # 1:都有
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" not in os.listdir(path):
|
||||||
|
return [True, path, 2] # 2:有pack
|
||||||
|
elif "mcpack(国际版推荐)格式_25.0" not in os.listdir(
|
||||||
|
path
|
||||||
|
) and "zip格式_25.0" in os.listdir(path):
|
||||||
|
return [True, path, 3] # 3:有zip
|
||||||
|
else:
|
||||||
|
return [False, 2] # 2:路径文件指示错误
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def choose_resources():
|
||||||
|
global zipN
|
||||||
|
global mpN
|
||||||
|
back_list = []
|
||||||
|
try:
|
||||||
|
with open(r"1.pkl", "rb") as rb:
|
||||||
|
instrument = list(pickle.load(rb))
|
||||||
|
print(instrument)
|
||||||
|
except FileNotFoundError:
|
||||||
|
with open(r"./nmcsup/1.pkl", "rb") as rb:
|
||||||
|
instrument = list(pickle.load(rb))
|
||||||
|
print(instrument)
|
||||||
|
path = resources_pathSetting()
|
||||||
|
if path.__len__() == 2:
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
dataT = path[2]
|
||||||
|
pathT = path[1]
|
||||||
|
if dataT == 1:
|
||||||
|
if instrument[1] is True: # 是否存在打击乐器
|
||||||
|
index = zipN.get(-1, "")
|
||||||
|
percussion_instrument = str(pathT) + "\\zip格式_25.0\\" + index
|
||||||
|
# print(percussion_instrument)
|
||||||
|
back_list.append(percussion_instrument)
|
||||||
|
for i in instrument[0]:
|
||||||
|
ins_p = str(pathT) + "\\zip格式_25.0\\" + str(zipN.get(i))
|
||||||
|
# print(ins_p)
|
||||||
|
back_list.append(ins_p)
|
||||||
|
print(back_list)
|
||||||
|
return back_list
|
||||||
|
elif dataT == 2:
|
||||||
|
if instrument[1] is True:
|
||||||
|
index = mpN.get(-1, "")
|
||||||
|
percussion_instrument = (
|
||||||
|
str(pathT) + "\\mcpack(国际版推荐)格式_25.0\\" + index
|
||||||
|
)
|
||||||
|
# print(percussion_instrument)
|
||||||
|
back_list.append(percussion_instrument)
|
||||||
|
for i in instrument[0]:
|
||||||
|
ins_p = str(pathT) + "\\mcpack(国际版推荐)格式_25.0\\" + str(mpN.get(i))
|
||||||
|
# print(ins_p)
|
||||||
|
back_list.append(ins_p)
|
||||||
|
print(back_list)
|
||||||
|
return back_list
|
||||||
|
elif dataT == 3:
|
||||||
|
if instrument[1] is True:
|
||||||
|
index = zipN.get(-1, "")
|
||||||
|
percussion_instrument = str(pathT) + "\\zip格式_25.0\\" + index
|
||||||
|
# print(percussion_instrument)
|
||||||
|
back_list.append(percussion_instrument)
|
||||||
|
for i in instrument[0]:
|
||||||
|
ins_p = str(pathT) + "\\zip格式_25.0\\" + str(zipN.get(i))
|
||||||
|
# print(ins_p)
|
||||||
|
back_list.append(ins_p)
|
||||||
|
print(back_list)
|
||||||
|
return back_list
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def scatteredPack(path):
|
||||||
|
pack_list = choose_resources()
|
||||||
|
print(pack_list)
|
||||||
|
print(path)
|
||||||
|
# os.close("L:/0WorldMusicCreater-MFMS new edition")
|
||||||
|
# shutil.copy("L:\\shenyu\\音源的资源包\\羽音缭绕-midiout_25.0\\mcpack(国际版推荐)格式_25.0\\0.Acoustic_Grand_Piano_大钢琴.mcpack",
|
||||||
|
# "L:/0WorldMusicCreater-MFMS new edition")
|
||||||
|
for i in pack_list:
|
||||||
|
shutil.copy(i, path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# print(resources_pathSetting(r"L:\shenyu\音源的资源包\羽音缭绕-midiout_25.0"))
|
||||||
|
choose_resources()
|
||||||
248
old-things/example.py
Normal file
248
old-things/example.py
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# -*- 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.old_init as old_init
|
||||||
|
from Musicreater.old_plugin.addonpack import (
|
||||||
|
to_addon_pack_in_delay,
|
||||||
|
to_addon_pack_in_repeater,
|
||||||
|
to_addon_pack_in_score,
|
||||||
|
)
|
||||||
|
from Musicreater.old_plugin.mcstructfile import (
|
||||||
|
to_mcstructure_file_in_delay,
|
||||||
|
to_mcstructure_file_in_repeater,
|
||||||
|
to_mcstructure_file_in_score,
|
||||||
|
)
|
||||||
|
|
||||||
|
from Musicreater.old_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 = old_init.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,
|
||||||
|
old_init.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,
|
||||||
|
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||||
|
*prompts[3:],
|
||||||
|
)
|
||||||
|
if playerFormat == 1
|
||||||
|
else to_BDX_file_in_delay(
|
||||||
|
cvt_mid,
|
||||||
|
out_path,
|
||||||
|
old_init.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
old-things/example_futureFunction.py
Normal file
14
old-things/example_futureFunction.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import Musicreater.experiment
|
||||||
|
import Musicreater.old_plugin
|
||||||
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
|
print(
|
||||||
|
Musicreater.old_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
old-things/example_singleConvert.py
Normal file
16
old-things/example_singleConvert.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import Musicreater.old_init as old_init
|
||||||
|
import Musicreater.old_plugin
|
||||||
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
|
print(
|
||||||
|
old_init.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
|
old_init.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
old-things/example_websocket.py
Normal file
24
old-things/example_websocket.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import Musicreater.old_init as old_init
|
||||||
|
import Musicreater.old_plugin
|
||||||
|
import Musicreater.old_plugin.websocket
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
dire = input("midi目录:")
|
||||||
|
|
||||||
|
print(
|
||||||
|
old_init.old_plugin.websocket.to_websocket_server(
|
||||||
|
[
|
||||||
|
old_init.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("服务器端口:")),
|
||||||
|
old_init.DEFAULT_PROGRESSBAR_STYLE,
|
||||||
|
)
|
||||||
|
)
|
||||||
1
old-things/fcwslib/LICENSE
Normal file
1
old-things/fcwslib/LICENSE
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SEE: https://mingfengpigeon.mit-license.org/
|
||||||
5
old-things/fcwslib/__init__.py
Normal file
5
old-things/fcwslib/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
__all__ = ['Server', 'Plugin', 'build_header']
|
||||||
|
__version__ = '3.0.1'
|
||||||
|
__author__ = ['mingfengpigeon <mingfengpigeon@gmail.com>',"Eilles Wan <EillesWan@outlook.com>"]
|
||||||
|
|
||||||
|
from .server import Server, Plugin, build_header
|
||||||
142
old-things/fcwslib/server.py
Normal file
142
old-things/fcwslib/server.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import asyncio
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
|
||||||
|
class Server:
|
||||||
|
sent_commands = {}
|
||||||
|
subscribed_events = {}
|
||||||
|
_plugins = []
|
||||||
|
_connections = []
|
||||||
|
|
||||||
|
def __init__(self, server='0.0.0.0', port=8000, debug_mode=False):
|
||||||
|
self._server = server
|
||||||
|
self._port = port
|
||||||
|
self._debug_mode = debug_mode
|
||||||
|
|
||||||
|
def handler(self):
|
||||||
|
return copy.deepcopy(self._plugins)
|
||||||
|
|
||||||
|
def add_plugin(self, plugin):
|
||||||
|
if self._plugins:
|
||||||
|
for connection in self._connections:
|
||||||
|
plugin_ = plugin()
|
||||||
|
asyncio.create_task(plugin_.on_connect())
|
||||||
|
connection.append(plugin_)
|
||||||
|
self._plugins.append(plugin)
|
||||||
|
|
||||||
|
def remove_plugin(self, plugin):
|
||||||
|
if self._connections:
|
||||||
|
for connection in self._connections:
|
||||||
|
for plugin_ in connection.plugins:
|
||||||
|
if isinstance(plugin_, plugin):
|
||||||
|
plugin_.remove(plugin_)
|
||||||
|
break
|
||||||
|
self._plugins.remove(plugin)
|
||||||
|
|
||||||
|
async def run_forever(self):
|
||||||
|
self.running = True
|
||||||
|
async with websockets.serve(self._on_connect, self._server, self._port):
|
||||||
|
await asyncio.Future()
|
||||||
|
|
||||||
|
async def _on_connect(self, websocket, path):
|
||||||
|
plugins = []
|
||||||
|
self._connections.append({
|
||||||
|
"websocket": websocket,
|
||||||
|
"path": path,
|
||||||
|
"plugins": plugins,
|
||||||
|
})
|
||||||
|
for plugin in self._plugins:
|
||||||
|
plugins.append(plugin(websocket, path, self, self._debug_mode))
|
||||||
|
for plugin in plugins:
|
||||||
|
asyncio.create_task(plugin.on_connect())
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
response = json.loads(await websocket.recv())
|
||||||
|
except (websockets.exceptions.ConnectionClosedOK, websockets.exceptions.ConnectionClosedError):
|
||||||
|
tasks = []
|
||||||
|
for plugin in plugins:
|
||||||
|
tasks.append(plugin.on_disconnect())
|
||||||
|
for task in tasks:
|
||||||
|
await task
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
message_purpose = response['header']['messagePurpose']
|
||||||
|
if message_purpose == 'commandResponse':
|
||||||
|
request_id = response['header']['requestId']
|
||||||
|
if request_id in self.sent_commands:
|
||||||
|
asyncio.create_task(self.sent_commands[request_id](response))
|
||||||
|
del self.sent_commands[request_id]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
event_name = response['header']['eventName']
|
||||||
|
asyncio.create_task(self.subscribed_events[event_name](response))
|
||||||
|
except KeyError:
|
||||||
|
print("ERROR EVENT NAME:\n{}".format(response))
|
||||||
|
|
||||||
|
async def disconnect(self, websocket: websockets.WebSocketServerProtocol):
|
||||||
|
self.running = False
|
||||||
|
await websocket.close_connection()
|
||||||
|
for number in range(len(self._connections) - 1):
|
||||||
|
connection = self._connections[number]
|
||||||
|
if connection['websocket'] == websocket:
|
||||||
|
del self._connections[number]
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
def __init__(self, websocket, path, server, debug_mode=False):
|
||||||
|
self._websocket = websocket
|
||||||
|
self._path = path
|
||||||
|
self._server = server
|
||||||
|
self._debug_mode = debug_mode
|
||||||
|
|
||||||
|
async def on_connect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_disconnect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_receive(self, response):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def send_command(self, command, callback=None):
|
||||||
|
request = {
|
||||||
|
'body': {'commandLine': command},
|
||||||
|
'header': build_header('commandRequest')
|
||||||
|
}
|
||||||
|
if callback:
|
||||||
|
self._server.sent_commands[request['header']['requestId']] = callback
|
||||||
|
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||||
|
|
||||||
|
async def subscribe(self, event_name, callback):
|
||||||
|
request = {
|
||||||
|
'body': {'eventName': event_name},
|
||||||
|
'header': build_header('subscribe')
|
||||||
|
}
|
||||||
|
self._server.subscribed_events[event_name] = callback
|
||||||
|
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||||
|
|
||||||
|
async def unsubscribe(self, event_name):
|
||||||
|
request = {
|
||||||
|
'body': {'eventName': event_name},
|
||||||
|
'header': build_header('unsubscribe')
|
||||||
|
}
|
||||||
|
del self._server.subscribed_events[event_name]
|
||||||
|
await self._websocket.send(json.dumps(request, **{'indent': 4} if self._debug_mode else {}))
|
||||||
|
|
||||||
|
async def disconnect(self):
|
||||||
|
await self._server.disconnect(self._websocket)
|
||||||
|
|
||||||
|
|
||||||
|
def build_header(message_purpose, request_id=None):
|
||||||
|
if not request_id:
|
||||||
|
request_id = str(uuid.uuid4())
|
||||||
|
return {
|
||||||
|
'requestId': request_id,
|
||||||
|
'messagePurpose': message_purpose,
|
||||||
|
'version': '1',
|
||||||
|
'messageType': 'commandRequest',
|
||||||
|
}
|
||||||
163
old-things/let_future_java.py
Normal file
163
old-things/let_future_java.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
版权所有 © 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.old_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.mcfunction_command_string 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.mcfunction_command_string
|
||||||
|
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.old_init.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),
|
||||||
|
)
|
||||||
39
old-things/test_fsq_opera.py
Normal file
39
old-things/test_fsq_opera.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from rich.pretty import pprint
|
||||||
|
|
||||||
|
import Musicreater.old_init as old_init
|
||||||
|
from Musicreater.utils import (
|
||||||
|
load_decode_fsq_flush_release,
|
||||||
|
load_decode_musicsequence_metainfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
msc_seq = old_init.MusicSequence.from_mido(
|
||||||
|
old_init.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 = old_init.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
old-things/test_future_kamires.py
Normal file
33
old-things/test_future_kamires.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Musicreater.experiment
|
||||||
|
import Musicreater.old_plugin
|
||||||
|
import Musicreater.old_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.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
|
msct,
|
||||||
|
opt,
|
||||||
|
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||||
|
max_height=32,
|
||||||
|
),
|
||||||
|
)
|
||||||
33
old-things/test_future_lyric.py
Normal file
33
old-things/test_future_lyric.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Musicreater.experiment
|
||||||
|
import Musicreater.old_plugin
|
||||||
|
import Musicreater.old_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.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
|
msct,
|
||||||
|
opt,
|
||||||
|
# Musicreater.plugin.ConvertConfig(input("输出路径:"),),
|
||||||
|
max_height=32,
|
||||||
|
),
|
||||||
|
)
|
||||||
34
old-things/test_msq_opera.py
Normal file
34
old-things/test_msq_opera.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from rich.pretty import pprint
|
||||||
|
|
||||||
|
import Musicreater.old_init as old_init
|
||||||
|
from Musicreater.utils import (
|
||||||
|
load_decode_msq_flush_release,
|
||||||
|
load_decode_musicsequence_metainfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
msc_seq = old_init.MusicSequence.from_mido(
|
||||||
|
old_init.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 = old_init.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)
|
||||||
140
pyproject.toml
140
pyproject.toml
@@ -1,49 +1,111 @@
|
|||||||
[tool.briefcase]
|
[project]
|
||||||
project_name = "Musicreater"
|
name = "Musicreater"
|
||||||
bundle = "com.ryoun.musicreater"
|
dynamic = ["version"]
|
||||||
version = "0.0.1"
|
requires-python = ">= 3.8, < 4.0"
|
||||||
url = "https://musicreater.ryoun.com/musicreater"
|
dependencies = [
|
||||||
license = "Apache Software License"
|
"tomli >= 2.4.0, < 3.0 ; python_version < '3.11'",
|
||||||
author = 'Eilles Wan'
|
"tomli-w >= 1.0.0, < 2.0",
|
||||||
author_email = "W-YI_DoctorYI@outlook.com"
|
"xxhash >= 3.0, < 4.0",
|
||||||
|
|
||||||
[tool.briefcase.app.musicreater]
|
|
||||||
formal_name = "Musicreater"
|
|
||||||
description = "Musicreater is an Eilles's app that is used for creating musics in Minecraft: Bedrock Edition"
|
|
||||||
icon = "src/musicreater/resources/musicreater"
|
|
||||||
sources = ['src/musicreater']
|
|
||||||
requires = []
|
|
||||||
|
|
||||||
|
|
||||||
[tool.briefcase.app.musicreater.macOS]
|
|
||||||
requires = [
|
|
||||||
'toga-cocoa>=0.3.0.dev20',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.briefcase.app.musicreater.linux]
|
authors = [
|
||||||
requires = [
|
{ name = "金羿Eilles" },
|
||||||
'toga-gtk>=0.3.0.dev20',
|
{ name = "玉衡Alioth" },
|
||||||
|
{ name = "鱼旧梦ElapsingDreams" },
|
||||||
|
{ name = "睿乐组织 TriMO", email = "TriM-Organization@hotmail.com" },
|
||||||
]
|
]
|
||||||
system_requires = [
|
maintainers = [
|
||||||
'libgirepository1.0-dev',
|
{ name = "金羿Eilles", email = "EillesWan@outlook.com" },
|
||||||
'libcairo2-dev',
|
|
||||||
'libpango1.0-dev',
|
|
||||||
'libwebkitgtk-3.0-0',
|
|
||||||
'gir1.2-webkit-3.0',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.briefcase.app.musicreater.windows]
|
description = "A free open source library used for dealing with **Minecraft** digital musics."
|
||||||
requires = [
|
readme = "README_EN.md"
|
||||||
'toga-winforms>=0.3.0.dev20',
|
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",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Mobile deployments
|
|
||||||
[tool.briefcase.app.musicreater.iOS]
|
[project.urls]
|
||||||
requires = [
|
# Homepage = "https://example.com"
|
||||||
'toga-iOS>=0.3.0.dev20',
|
# 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]
|
||||||
|
midi = [
|
||||||
|
"mido >= 1.3, < 2.0",
|
||||||
|
]
|
||||||
|
structure = [
|
||||||
|
"numpy",
|
||||||
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
|
"brotli >= 1.0.0, < 2.0",
|
||||||
|
]
|
||||||
|
full = [
|
||||||
|
"mido >= 1.3, < 2.0",
|
||||||
|
"numpy",
|
||||||
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
|
"brotli >= 1.0.0, < 2.0",
|
||||||
|
]
|
||||||
|
dev = [
|
||||||
|
"mido >= 1.3, < 2.0",
|
||||||
|
"numpy",
|
||||||
|
"TrimMCStruct <= 0.0.5.9",
|
||||||
|
"brotli >= 1.0.0, < 2.0",
|
||||||
|
"dill",
|
||||||
|
"rich",
|
||||||
|
"pyinstaller",
|
||||||
|
"twine",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.briefcase.app.musicreater.android]
|
|
||||||
requires = [
|
[build-system]
|
||||||
'toga-android>=0.3.0.dev20',
|
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"
|
||||||
|
|||||||
BIN
resources/msctIcon.ico
Normal file
BIN
resources/msctIcon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
resources/msctIcon.png
Normal file
BIN
resources/msctIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
260
resources/test/enfasted_list_merging_test.py
Normal file
260
resources/test/enfasted_list_merging_test.py
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
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)
|
||||||
21
resources/test/genexpr_vs_yieldfrom.py
Normal file
21
resources/test/genexpr_vs_yieldfrom.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
# 模拟两种写法
|
||||||
|
def method_A(self, start, end):
|
||||||
|
yield from (f"{track}.get_range(start, end)" for track in self)
|
||||||
|
|
||||||
|
def method_B(self, start, end):
|
||||||
|
return (f"{track}.get_range(start, end)" for track in self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tracks = ["A", "B"]
|
||||||
|
|
||||||
|
gen_a = method_A(tracks, 0, 10)
|
||||||
|
print(list(gen_a))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gen_b = method_B(tracks, 0, 10)
|
||||||
|
print(list(gen_b))
|
||||||
|
|
||||||
|
# they are the same output
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user